create AppViewer for .lbry files
This commit is contained in:
parent
cd3edca074
commit
ba2ccd45fe
13 changed files with 157 additions and 121 deletions
|
@ -125,7 +125,7 @@
|
|||
"jsmediatags": "^3.8.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#66f70c7745d23e79f9e8ec9ca32ef9d3a576688e",
|
||||
"lbry-redux": "lbryio/lbry-redux#d2c0d999d6909a6ebb80a318052d0358e12048ee",
|
||||
"lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
|
|
@ -5,24 +5,18 @@ import React from 'react';
|
|||
import Button from 'component/button';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
|
||||
type FileInfo = {
|
||||
claim_id: string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claimId: string,
|
||||
openModal: (id: string, { uri: string }) => void,
|
||||
claimIsMine: boolean,
|
||||
fileInfo: FileInfo,
|
||||
fileInfo: FileListItem,
|
||||
};
|
||||
|
||||
class FileActions extends React.PureComponent<Props> {
|
||||
render() {
|
||||
const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props;
|
||||
const showDelete = claimIsMine || (fileInfo && Object.keys(fileInfo).length > 0);
|
||||
// fix me
|
||||
// const showDelete = claimIsMine || (fileInfo && fileInfo.writtenBytes > 0 || fileInfo.blobs_completed;
|
||||
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed === true));
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
|
|
|
@ -4,10 +4,9 @@ import {
|
|||
makeSelectDownloadingForUri,
|
||||
makeSelectLoadingForUri,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectUriIsStreamable,
|
||||
} from 'lbry-redux';
|
||||
import { doOpenModal } from 'redux/actions/app';
|
||||
import { doSetPlayingUri } from 'redux/actions/content';
|
||||
import { doSetPlayingUri, doPlayUri } from 'redux/actions/content';
|
||||
import FileDownloadLink from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
|
@ -15,12 +14,12 @@ const select = (state, props) => ({
|
|||
downloading: makeSelectDownloadingForUri(props.uri)(state),
|
||||
loading: makeSelectLoadingForUri(props.uri)(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
isStreamable: makeSelectUriIsStreamable(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
|
||||
pause: () => dispatch(doSetPlayingUri(null)),
|
||||
download: uri => dispatch(doPlayUri(uri, false, true)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
|
|
|
@ -6,19 +6,21 @@ import Button from 'component/button';
|
|||
import ToolTip from 'component/common/tooltip';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
claimIsMine: boolean,
|
||||
downloading: boolean,
|
||||
loading: boolean,
|
||||
isStreamable: boolean,
|
||||
fileInfo: ?FileInfo,
|
||||
fileInfo: ?FileListItem,
|
||||
openModal: (id: string, { path: string }) => void,
|
||||
pause: () => void,
|
||||
download: string => void,
|
||||
};
|
||||
|
||||
function FileDownloadLink(props: Props) {
|
||||
const { fileInfo, downloading, loading, openModal, pause, claimIsMine, isStreamable } = props;
|
||||
const { fileInfo, downloading, loading, openModal, pause, claimIsMine, download, uri } = props;
|
||||
|
||||
if (!isStreamable && (loading || downloading)) {
|
||||
if (loading || downloading) {
|
||||
const progress = fileInfo && fileInfo.written_bytes > 0 ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0;
|
||||
const label =
|
||||
fileInfo && fileInfo.written_bytes > 0
|
||||
|
@ -32,6 +34,7 @@ function FileDownloadLink(props: Props) {
|
|||
return (
|
||||
<ToolTip label={__('Open file')}>
|
||||
<Button
|
||||
title="Remove from library"
|
||||
button="link"
|
||||
icon={ICONS.EXTERNAL}
|
||||
onClick={() => {
|
||||
|
@ -41,9 +44,19 @@ function FileDownloadLink(props: Props) {
|
|||
/>
|
||||
</ToolTip>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<ToolTip label={__('Add to your library')}>
|
||||
<Button
|
||||
button="link"
|
||||
icon={ICONS.DOWNLOAD}
|
||||
onClick={() => {
|
||||
download(uri);
|
||||
}}
|
||||
/>
|
||||
</ToolTip>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export default FileDownloadLink;
|
||||
|
|
|
@ -3,6 +3,8 @@ import { remote } from 'electron';
|
|||
import React, { Suspense } from 'react';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import VideoViewer from 'component/viewers/videoViewer';
|
||||
import ImageViewer from 'component/viewers/imageViewer';
|
||||
import AppViewer from 'component/viewers/appViewer';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
|
@ -59,13 +61,14 @@ const ThreeViewer = React.lazy<*>(() =>
|
|||
// @endif
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
mediaType: string,
|
||||
streamingUrl: string,
|
||||
contentType: string,
|
||||
claim: StreamClaim,
|
||||
currentTheme: string,
|
||||
downloadPath?: string,
|
||||
fileName?: string,
|
||||
downloadPath: string,
|
||||
fileName: string,
|
||||
};
|
||||
|
||||
class FileRender extends React.PureComponent<Props> {
|
||||
|
@ -77,64 +80,12 @@ class FileRender extends React.PureComponent<Props> {
|
|||
|
||||
componentDidMount() {
|
||||
window.addEventListener('keydown', this.escapeListener, true);
|
||||
|
||||
// ugh
|
||||
// const { claim, streamingUrl, fileStatus, fileName, downloadPath, downloadCompleted, contentType } = this.props;
|
||||
// if(MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1) {
|
||||
// const outpoint = `${claim.txid}:${claim.nout}`;
|
||||
// // Fetch unpacked url
|
||||
// fetch(`${MediaPlayer.SANDBOX_SET_BASE_URL}${outpoint}`)
|
||||
// .then(res => res.text())
|
||||
// .then(url => {
|
||||
// const source = {url: `${MediaPlayer.SANDBOX_CONTENT_BASE_URL}${url}`};
|
||||
// this.setState({source});
|
||||
// })
|
||||
// .catch(err => {
|
||||
// console.error(err);
|
||||
// });
|
||||
// } else {
|
||||
// File to render
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
window.removeEventListener('keydown', this.escapeListener, true);
|
||||
}
|
||||
|
||||
// This should use React.createRef()
|
||||
// processSandboxRef(element: any) {
|
||||
// if (!element) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// window.sandbox = element;
|
||||
|
||||
// element.addEventListener('permissionrequest', e => {
|
||||
// console.log('permissionrequest', e);
|
||||
// });
|
||||
|
||||
// element.addEventListener('console-message', (e: { message: string }) => {
|
||||
// if (/^\$LBRY_IPC:/.test(e.message)) {
|
||||
// // Process command
|
||||
// let message = {};
|
||||
// try {
|
||||
// // $FlowFixMe
|
||||
// message = JSON.parse(/^\$LBRY_IPC:(.*)/.exec(e.message)[1]);
|
||||
// } catch (err) {}
|
||||
// console.log('IPC', message);
|
||||
// } else {
|
||||
// console.log('Sandbox:', e.message);
|
||||
// }
|
||||
// });
|
||||
|
||||
// element.addEventListener('enter-html-full-screen', () => {
|
||||
// // stub
|
||||
// });
|
||||
|
||||
// element.addEventListener('leave-html-full-screen', () => {
|
||||
// // stub
|
||||
// });
|
||||
// }
|
||||
|
||||
escapeListener(e: SyntheticKeyboardEvent<*>) {
|
||||
if (e.keyCode === 27) {
|
||||
e.preventDefault();
|
||||
|
@ -150,7 +101,7 @@ class FileRender extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
renderViewer() {
|
||||
const { mediaType, currentTheme, claim, contentType, downloadPath, fileName, streamingUrl } = this.props;
|
||||
const { mediaType, currentTheme, claim, contentType, downloadPath, fileName, streamingUrl, uri } = this.props;
|
||||
|
||||
const fileType = fileName && path.extname(fileName).substring(1);
|
||||
|
||||
|
@ -162,30 +113,12 @@ class FileRender extends React.PureComponent<Props> {
|
|||
// @if TARGET='app'
|
||||
'3D-file': <ThreeViewer source={{ fileType, downloadPath }} theme={currentTheme} />,
|
||||
'comic-book': <ComicBookViewer source={{ fileType, downloadPath }} theme={currentTheme} />,
|
||||
// application: !source.url ? null : (
|
||||
// <webview
|
||||
// ref={element => this.processSandboxRef(element)}
|
||||
// title=""
|
||||
// sandbox="allow-scripts allow-forms allow-pointer-lock"
|
||||
// src={source.url}
|
||||
// autosize="on"
|
||||
// style={{ border: 0, width: '100%', height: '100%' }}
|
||||
// useragent="Mozilla/5.0 AppleWebKit/537 Chrome/60 Safari/537"
|
||||
// enableremotemodule="false"
|
||||
// webpreferences="sandbox=true,contextIsolation=true,webviewTag=false,enableRemoteModule=false,devTools=false"
|
||||
// />
|
||||
// ),
|
||||
application: <AppViewer uri={uri} />,
|
||||
// @endif
|
||||
|
||||
video: <VideoViewer source={streamingUrl} contentType={contentType} />,
|
||||
audio: <VideoViewer source={streamingUrl} contentType={contentType} />,
|
||||
// audio: (
|
||||
// <AudioViewer
|
||||
// claim={claim}
|
||||
// source={{ url: streamingUrl, downloadPath, downloadCompleted, status }}
|
||||
// contentType={contentType}
|
||||
// />
|
||||
// ),
|
||||
image: <ImageViewer source={streamingUrl} />,
|
||||
// Add routes to viewer...
|
||||
};
|
||||
|
||||
|
@ -198,7 +131,7 @@ class FileRender extends React.PureComponent<Props> {
|
|||
};
|
||||
|
||||
// Check for a valid fileType or mediaType
|
||||
let viewer = fileTypes[fileType] || mediaTypes[mediaType];
|
||||
let viewer = fileType ? fileTypes[fileType] : mediaTypes[mediaType];
|
||||
|
||||
// Check for Human-readable files
|
||||
if (!viewer && readableFiles.includes(mediaType)) {
|
||||
|
|
|
@ -13,7 +13,7 @@ type Props = {
|
|||
mediaType: string,
|
||||
isLoading: boolean,
|
||||
isPlaying: boolean,
|
||||
fileInfo: FileInfo,
|
||||
fileInfo: FileListItem,
|
||||
uri: string,
|
||||
obscurePreview: boolean,
|
||||
insufficientCredits: boolean,
|
||||
|
|
15
src/ui/component/viewers/appViewer/index.js
Normal file
15
src/ui/component/viewers/appViewer/index.js
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, makeSelectContentTypeForUri } from 'lbry-redux';
|
||||
import AppViewer from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
contentType: makeSelectContentTypeForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(AppViewer);
|
61
src/ui/component/viewers/appViewer/view.jsx
Normal file
61
src/ui/component/viewers/appViewer/view.jsx
Normal file
|
@ -0,0 +1,61 @@
|
|||
// @flow
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
|
||||
type Props = {
|
||||
source: string,
|
||||
claim: StreamClaim,
|
||||
contentType: string,
|
||||
};
|
||||
|
||||
const SANDBOX_TYPES = ['application/x-lbry', 'application/x-ext-lbry'];
|
||||
|
||||
// This server exists in src/platforms/electron/startSandBox.js
|
||||
const SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/';
|
||||
const SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278';
|
||||
|
||||
function AppViewer(props: Props) {
|
||||
const { claim, contentType } = props;
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [appUrl, setAppUrl] = useState(false);
|
||||
|
||||
const outpoint = `${claim.txid}:${claim.nout}`;
|
||||
useEffect(() => {
|
||||
if (SANDBOX_TYPES.indexOf(contentType) > -1) {
|
||||
fetch(`${SANDBOX_SET_BASE_URL}${outpoint}`)
|
||||
.then(res => res.text())
|
||||
.then(url => {
|
||||
const appUrl = `${SANDBOX_CONTENT_BASE_URL}${url}`;
|
||||
setAppUrl(appUrl);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(err => {
|
||||
setLoading(false);
|
||||
});
|
||||
} else {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [outpoint, contentType, setAppUrl, setLoading]);
|
||||
|
||||
return (
|
||||
<div className="file-render__viewer">
|
||||
{!appUrl && (
|
||||
<LoadingScreen status={loading ? __('Almost there') : __('Unable to view this file')} spinner={loading} />
|
||||
)}
|
||||
{appUrl && (
|
||||
<webview
|
||||
title=""
|
||||
sandbox="allow-scripts allow-forms allow-pointer-lock"
|
||||
src={appUrl}
|
||||
autosize="on"
|
||||
style={{ border: 0, width: '100%', height: '100%' }}
|
||||
useragent="Mozilla/5.0 AppleWebKit/537 Chrome/60 Safari/537"
|
||||
enableremotemodule="false"
|
||||
webpreferences="sandbox=true,contextIsolation=true,webviewTag=false,enableRemoteModule=false,devTools=false"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default AppViewer;
|
17
src/ui/component/viewers/imageViewer.jsx
Normal file
17
src/ui/component/viewers/imageViewer.jsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
source: string,
|
||||
};
|
||||
|
||||
function ImageViewer(props: Props) {
|
||||
const { source } = props;
|
||||
return (
|
||||
<div className="file-render__viewer">
|
||||
<img src={source} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ImageViewer;
|
|
@ -11,12 +11,12 @@ const VIDEO_JS_OPTIONS = {
|
|||
controls: true,
|
||||
preload: 'auto',
|
||||
playbackRates: [0.5, 1, 1.25, 1.5, 2],
|
||||
fluid: true,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
source: string,
|
||||
contentType: string,
|
||||
muted: boolean,
|
||||
};
|
||||
|
||||
function VideoViewer(props: Props) {
|
||||
|
@ -25,21 +25,21 @@ function VideoViewer(props: Props) {
|
|||
|
||||
// Handle any other effects separately to avoid re-mounting the video player when props change
|
||||
useEffect(() => {
|
||||
if (videoRef && source && contentType) {
|
||||
const videoNode = videoRef.current;
|
||||
const videoJsOptions = {
|
||||
...VIDEO_JS_OPTIONS,
|
||||
sources: [
|
||||
{
|
||||
src: source,
|
||||
type: contentType,
|
||||
},
|
||||
],
|
||||
};
|
||||
const videoNode = videoRef.current;
|
||||
const videoJsOptions = {
|
||||
...VIDEO_JS_OPTIONS,
|
||||
sources: [
|
||||
{
|
||||
src: source,
|
||||
type: contentType,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const player = videojs(videoNode, videoJsOptions);
|
||||
return () => player.dispose();
|
||||
}
|
||||
const player = videojs(videoNode, videoJsOptions);
|
||||
return () => {
|
||||
player.dispose();
|
||||
};
|
||||
}, [videoRef, source, contentType]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -47,8 +47,6 @@ function VideoViewer(props: Props) {
|
|||
const videoNode = videoRef && videoRef.current;
|
||||
if (!videoNode) return;
|
||||
|
||||
// This should be done in a reusable way
|
||||
// maybe a custom useKeyboardListener hook?
|
||||
if (!isUserTyping() && e.keyCode === SPACE_BAR_KEYCODE) {
|
||||
e.preventDefault();
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.file-render__viewer,
|
||||
.document-viewer {
|
||||
background-color: $lbry-white;
|
||||
|
||||
|
@ -27,15 +26,10 @@
|
|||
|
||||
iframe,
|
||||
webview,
|
||||
.video-js {
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
// Removing the play button because we have autoplay turned on
|
||||
// These are classes added by video.js
|
||||
.video-js .vjs-big-play-button {
|
||||
display: none;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -97,3 +91,14 @@
|
|||
color: $lbry-gray-5;
|
||||
}
|
||||
}
|
||||
|
||||
.video-js {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
// Removing the play button because we have autoplay turned on
|
||||
// These are classes added by video.js
|
||||
.vjs-big-play-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
display: flex;
|
||||
font-size: var(--font-multiplier-medium);
|
||||
margin-bottom: var(--spacing-large);
|
||||
line-height: 1;
|
||||
|
||||
> * {
|
||||
&:not(:last-child) {
|
||||
|
|
|
@ -6762,9 +6762,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
|
|||
yargs "^13.2.2"
|
||||
zstd-codec "^0.1.1"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#66f70c7745d23e79f9e8ec9ca32ef9d3a576688e:
|
||||
lbry-redux@lbryio/lbry-redux#d2c0d999d6909a6ebb80a318052d0358e12048ee:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/66f70c7745d23e79f9e8ec9ca32ef9d3a576688e"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/d2c0d999d6909a6ebb80a318052d0358e12048ee"
|
||||
dependencies:
|
||||
mime "^2.4.4"
|
||||
proxy-polyfill "0.1.6"
|
||||
|
|
Loading…
Reference in a new issue