create AppViewer for .lbry files

This commit is contained in:
Sean Yesmunt 2019-08-05 23:25:33 -04:00
parent cd3edca074
commit ba2ccd45fe
13 changed files with 157 additions and 121 deletions

View file

@ -125,7 +125,7 @@
"jsmediatags": "^3.8.1", "jsmediatags": "^3.8.1",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "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", "lbryinc": "lbryio/lbryinc#a93596c51c8fb0a226cb84df04c26a6bb60a45fb",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",

View file

@ -5,24 +5,18 @@ import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import Tooltip from 'component/common/tooltip'; import Tooltip from 'component/common/tooltip';
type FileInfo = {
claim_id: string,
};
type Props = { type Props = {
uri: string, uri: string,
claimId: string, claimId: string,
openModal: (id: string, { uri: string }) => void, openModal: (id: string, { uri: string }) => void,
claimIsMine: boolean, claimIsMine: boolean,
fileInfo: FileInfo, fileInfo: FileListItem,
}; };
class FileActions extends React.PureComponent<Props> { class FileActions extends React.PureComponent<Props> {
render() { render() {
const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props; const { fileInfo, uri, openModal, claimIsMine, claimId } = this.props;
const showDelete = claimIsMine || (fileInfo && Object.keys(fileInfo).length > 0); const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed === true));
// fix me
// const showDelete = claimIsMine || (fileInfo && fileInfo.writtenBytes > 0 || fileInfo.blobs_completed;
return ( return (
<React.Fragment> <React.Fragment>

View file

@ -4,10 +4,9 @@ import {
makeSelectDownloadingForUri, makeSelectDownloadingForUri,
makeSelectLoadingForUri, makeSelectLoadingForUri,
makeSelectClaimIsMine, makeSelectClaimIsMine,
makeSelectUriIsStreamable,
} from 'lbry-redux'; } from 'lbry-redux';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { doSetPlayingUri } from 'redux/actions/content'; import { doSetPlayingUri, doPlayUri } from 'redux/actions/content';
import FileDownloadLink from './view'; import FileDownloadLink from './view';
const select = (state, props) => ({ const select = (state, props) => ({
@ -15,12 +14,12 @@ const select = (state, props) => ({
downloading: makeSelectDownloadingForUri(props.uri)(state), downloading: makeSelectDownloadingForUri(props.uri)(state),
loading: makeSelectLoadingForUri(props.uri)(state), loading: makeSelectLoadingForUri(props.uri)(state),
claimIsMine: makeSelectClaimIsMine(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state),
isStreamable: makeSelectUriIsStreamable(props.uri)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
openModal: (modal, props) => dispatch(doOpenModal(modal, props)), openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
pause: () => dispatch(doSetPlayingUri(null)), pause: () => dispatch(doSetPlayingUri(null)),
download: uri => dispatch(doPlayUri(uri, false, true)),
}); });
export default connect( export default connect(

View file

@ -6,19 +6,21 @@ import Button from 'component/button';
import ToolTip from 'component/common/tooltip'; import ToolTip from 'component/common/tooltip';
type Props = { type Props = {
uri: string,
claimIsMine: boolean, claimIsMine: boolean,
downloading: boolean, downloading: boolean,
loading: boolean, loading: boolean,
isStreamable: boolean, isStreamable: boolean,
fileInfo: ?FileInfo, fileInfo: ?FileListItem,
openModal: (id: string, { path: string }) => void, openModal: (id: string, { path: string }) => void,
pause: () => void, pause: () => void,
download: string => void,
}; };
function FileDownloadLink(props: Props) { 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 progress = fileInfo && fileInfo.written_bytes > 0 ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0;
const label = const label =
fileInfo && fileInfo.written_bytes > 0 fileInfo && fileInfo.written_bytes > 0
@ -32,6 +34,7 @@ function FileDownloadLink(props: Props) {
return ( return (
<ToolTip label={__('Open file')}> <ToolTip label={__('Open file')}>
<Button <Button
title="Remove from library"
button="link" button="link"
icon={ICONS.EXTERNAL} icon={ICONS.EXTERNAL}
onClick={() => { onClick={() => {
@ -41,9 +44,19 @@ function FileDownloadLink(props: Props) {
/> />
</ToolTip> </ToolTip>
); );
} else {
return (
<ToolTip label={__('Add to your library')}>
<Button
button="link"
icon={ICONS.DOWNLOAD}
onClick={() => {
download(uri);
}}
/>
</ToolTip>
);
} }
return null;
} }
export default FileDownloadLink; export default FileDownloadLink;

View file

@ -3,6 +3,8 @@ import { remote } from 'electron';
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';
import VideoViewer from 'component/viewers/videoViewer'; import VideoViewer from 'component/viewers/videoViewer';
import ImageViewer from 'component/viewers/imageViewer';
import AppViewer from 'component/viewers/appViewer';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
@ -59,13 +61,14 @@ const ThreeViewer = React.lazy<*>(() =>
// @endif // @endif
type Props = { type Props = {
uri: string,
mediaType: string, mediaType: string,
streamingUrl: string, streamingUrl: string,
contentType: string, contentType: string,
claim: StreamClaim, claim: StreamClaim,
currentTheme: string, currentTheme: string,
downloadPath?: string, downloadPath: string,
fileName?: string, fileName: string,
}; };
class FileRender extends React.PureComponent<Props> { class FileRender extends React.PureComponent<Props> {
@ -77,64 +80,12 @@ class FileRender extends React.PureComponent<Props> {
componentDidMount() { componentDidMount() {
window.addEventListener('keydown', this.escapeListener, true); 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() { componentWillUnmount() {
window.removeEventListener('keydown', this.escapeListener, true); 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<*>) { escapeListener(e: SyntheticKeyboardEvent<*>) {
if (e.keyCode === 27) { if (e.keyCode === 27) {
e.preventDefault(); e.preventDefault();
@ -150,7 +101,7 @@ class FileRender extends React.PureComponent<Props> {
} }
renderViewer() { 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); const fileType = fileName && path.extname(fileName).substring(1);
@ -162,30 +113,12 @@ class FileRender extends React.PureComponent<Props> {
// @if TARGET='app' // @if TARGET='app'
'3D-file': <ThreeViewer source={{ fileType, downloadPath }} theme={currentTheme} />, '3D-file': <ThreeViewer source={{ fileType, downloadPath }} theme={currentTheme} />,
'comic-book': <ComicBookViewer source={{ fileType, downloadPath }} theme={currentTheme} />, 'comic-book': <ComicBookViewer source={{ fileType, downloadPath }} theme={currentTheme} />,
// application: !source.url ? null : ( application: <AppViewer uri={uri} />,
// <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"
// />
// ),
// @endif // @endif
video: <VideoViewer source={streamingUrl} contentType={contentType} />, video: <VideoViewer source={streamingUrl} contentType={contentType} />,
audio: <VideoViewer source={streamingUrl} contentType={contentType} />, audio: <VideoViewer source={streamingUrl} contentType={contentType} />,
// audio: ( image: <ImageViewer source={streamingUrl} />,
// <AudioViewer
// claim={claim}
// source={{ url: streamingUrl, downloadPath, downloadCompleted, status }}
// contentType={contentType}
// />
// ),
// Add routes to viewer... // Add routes to viewer...
}; };
@ -198,7 +131,7 @@ class FileRender extends React.PureComponent<Props> {
}; };
// Check for a valid fileType or mediaType // 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 // Check for Human-readable files
if (!viewer && readableFiles.includes(mediaType)) { if (!viewer && readableFiles.includes(mediaType)) {

View file

@ -13,7 +13,7 @@ type Props = {
mediaType: string, mediaType: string,
isLoading: boolean, isLoading: boolean,
isPlaying: boolean, isPlaying: boolean,
fileInfo: FileInfo, fileInfo: FileListItem,
uri: string, uri: string,
obscurePreview: boolean, obscurePreview: boolean,
insufficientCredits: boolean, insufficientCredits: boolean,

View 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);

View 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;

View 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;

View file

@ -11,12 +11,12 @@ const VIDEO_JS_OPTIONS = {
controls: true, controls: true,
preload: 'auto', preload: 'auto',
playbackRates: [0.5, 1, 1.25, 1.5, 2], playbackRates: [0.5, 1, 1.25, 1.5, 2],
fluid: true,
}; };
type Props = { type Props = {
source: string, source: string,
contentType: string, contentType: string,
muted: boolean,
}; };
function VideoViewer(props: Props) { function VideoViewer(props: Props) {
@ -25,7 +25,6 @@ function VideoViewer(props: Props) {
// Handle any other effects separately to avoid re-mounting the video player when props change // Handle any other effects separately to avoid re-mounting the video player when props change
useEffect(() => { useEffect(() => {
if (videoRef && source && contentType) {
const videoNode = videoRef.current; const videoNode = videoRef.current;
const videoJsOptions = { const videoJsOptions = {
...VIDEO_JS_OPTIONS, ...VIDEO_JS_OPTIONS,
@ -38,8 +37,9 @@ function VideoViewer(props: Props) {
}; };
const player = videojs(videoNode, videoJsOptions); const player = videojs(videoNode, videoJsOptions);
return () => player.dispose(); return () => {
} player.dispose();
};
}, [videoRef, source, contentType]); }, [videoRef, source, contentType]);
useEffect(() => { useEffect(() => {
@ -47,8 +47,6 @@ function VideoViewer(props: Props) {
const videoNode = videoRef && videoRef.current; const videoNode = videoRef && videoRef.current;
if (!videoNode) return; if (!videoNode) return;
// This should be done in a reusable way
// maybe a custom useKeyboardListener hook?
if (!isUserTyping() && e.keyCode === SPACE_BAR_KEYCODE) { if (!isUserTyping() && e.keyCode === SPACE_BAR_KEYCODE) {
e.preventDefault(); e.preventDefault();

View file

@ -12,7 +12,6 @@
} }
} }
.file-render__viewer,
.document-viewer { .document-viewer {
background-color: $lbry-white; background-color: $lbry-white;
@ -27,15 +26,10 @@
iframe, iframe,
webview, webview,
.video-js { img {
width: 100%; width: 100%;
height: 100%; height: 100%;
} object-fit: contain;
// 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;
} }
} }
@ -97,3 +91,14 @@
color: $lbry-gray-5; 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;
}
}

View file

@ -90,6 +90,7 @@
display: flex; display: flex;
font-size: var(--font-multiplier-medium); font-size: var(--font-multiplier-medium);
margin-bottom: var(--spacing-large); margin-bottom: var(--spacing-large);
line-height: 1;
> * { > * {
&:not(:last-child) { &:not(:last-child) {

View file

@ -6762,9 +6762,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
yargs "^13.2.2" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#66f70c7745d23e79f9e8ec9ca32ef9d3a576688e: lbry-redux@lbryio/lbry-redux#d2c0d999d6909a6ebb80a318052d0358e12048ee:
version "0.0.1" 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: dependencies:
mime "^2.4.4" mime "^2.4.4"
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"