Merge pull request #1576 from lbryio/file-preview
Add FileRender component
This commit is contained in:
commit
e317f573bf
17 changed files with 348 additions and 58 deletions
|
@ -48,8 +48,9 @@
|
|||
"formik": "^0.10.4",
|
||||
"hast-util-sanitize": "^1.1.2",
|
||||
"keytar": "^4.2.1",
|
||||
"lbry-redux": "lbryio/lbry-redux#201d78b68a329065ee5d2a03bfb1607ea0666588",
|
||||
"lbry-redux": "lbryio/lbry-redux#341db02c090e0121ff7f5d2e74fa35b06213d858",
|
||||
"localforage": "^1.7.1",
|
||||
"mime": "^2.3.1",
|
||||
"mixpanel-browser": "^2.17.1",
|
||||
"moment": "^2.22.0",
|
||||
"qrcode.react": "^0.8.0",
|
||||
|
@ -76,6 +77,7 @@
|
|||
"semver": "^5.3.0",
|
||||
"shapeshift.io": "^1.3.1",
|
||||
"source-map-support": "^0.5.4",
|
||||
"stream-to-blob-url": "^2.1.1",
|
||||
"tree-kill": "^1.1.0",
|
||||
"y18n": "^4.0.0"
|
||||
},
|
||||
|
|
|
@ -14,7 +14,7 @@ export default appState => {
|
|||
defaultHeight: height,
|
||||
});
|
||||
|
||||
let windowConfiguration = {
|
||||
const windowConfiguration = {
|
||||
backgroundColor: '#44b098',
|
||||
minWidth: 950,
|
||||
minHeight: 600,
|
||||
|
@ -26,17 +26,13 @@ export default appState => {
|
|||
// If state is undefined, create window as maximized.
|
||||
width: windowState.width === undefined ? width : windowState.width,
|
||||
height: windowState.height === undefined ? height : windowState.height,
|
||||
};
|
||||
|
||||
// Disable renderer process's webSecurity on development to enable CORS.
|
||||
windowConfiguration = isDev
|
||||
? {
|
||||
...windowConfiguration,
|
||||
webPreferences: {
|
||||
webSecurity: false,
|
||||
},
|
||||
}
|
||||
: windowConfiguration;
|
||||
webPreferences: {
|
||||
// Disable renderer process's webSecurity on development to enable CORS.
|
||||
webSecurity: !isDev,
|
||||
plugins: true,
|
||||
},
|
||||
};
|
||||
|
||||
const rendererURL = isDev
|
||||
? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`
|
||||
|
|
|
@ -3,8 +3,8 @@ import React from 'react';
|
|||
import Spinner from 'component/spinner';
|
||||
|
||||
type Props = {
|
||||
spinner: boolean,
|
||||
status: string,
|
||||
spinner: boolean,
|
||||
};
|
||||
|
||||
class LoadingScreen extends React.PureComponent<Props> {
|
||||
|
@ -17,8 +17,7 @@ class LoadingScreen extends React.PureComponent<Props> {
|
|||
return (
|
||||
<div className="content__loading">
|
||||
{spinner && <Spinner light />}
|
||||
|
||||
<span className="content__loading-text">{status}</span>
|
||||
{status && <span className="content__loading-text">{status}</span>}
|
||||
</div>
|
||||
);
|
||||
}
|
16
src/renderer/component/fileRender/index.js
Normal file
16
src/renderer/component/fileRender/index.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { THEME } from 'constants/settings';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
|
||||
import FileRender from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
currentTheme: makeSelectClientSetting(THEME)(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileRender);
|
47
src/renderer/component/fileRender/view.jsx
Normal file
47
src/renderer/component/fileRender/view.jsx
Normal file
|
@ -0,0 +1,47 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import PdfViewer from 'component/viewers/pdfViewer';
|
||||
|
||||
type Props = {
|
||||
mediaType: string,
|
||||
source: {
|
||||
filePath: string,
|
||||
fileType: string,
|
||||
downloadPath: string,
|
||||
},
|
||||
currentTheme: string,
|
||||
};
|
||||
|
||||
class FileRender extends React.PureComponent<Props> {
|
||||
renderViewer() {
|
||||
const { source, mediaType, currentTheme } = this.props;
|
||||
const viewerProps = { source, theme: currentTheme };
|
||||
|
||||
// Supported mediaTypes
|
||||
const mediaTypes = {
|
||||
// '3D-file': <ThreeViewer {...viewerProps}/>,
|
||||
// Add routes to viewer...
|
||||
};
|
||||
|
||||
// Supported fileType
|
||||
const fileTypes = {
|
||||
pdf: <PdfViewer {...viewerProps} />,
|
||||
// Add routes to viewer...
|
||||
};
|
||||
|
||||
const { fileType } = source;
|
||||
const viewer = mediaType && source && (mediaTypes[mediaType] || fileTypes[fileType]);
|
||||
const unsupportedMessage = __("Sorry, looks like we can't preview this file.");
|
||||
const unsupported = <LoadingScreen status={unsupportedMessage} spinner={false} />;
|
||||
|
||||
// Return viewer
|
||||
return viewer || unsupported;
|
||||
}
|
||||
|
||||
render() {
|
||||
return <div className="file-render">{this.renderViewer()}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
export default FileRender;
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { makeSelectClientSetting, selectShowNsfw } from 'redux/selectors/settings';
|
||||
import { selectMediaPaused, makeSelectMediaPositionForUri } from 'redux/selectors/media';
|
||||
import { selectPlayingUri } from 'redux/selectors/content';
|
||||
import Video from './view';
|
||||
import FileViewer from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
|
@ -45,4 +45,7 @@ const perform = dispatch => ({
|
|||
savePosition: (claimId, position) => dispatch(savePosition(claimId, position)),
|
||||
});
|
||||
|
||||
export default connect(select, perform)(Video);
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(FileViewer);
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import * as icons from 'constants/icons';
|
||||
|
||||
type Props = {
|
||||
play: () => void,
|
||||
|
@ -14,8 +15,8 @@ class VideoPlayButton extends React.PureComponent<Props> {
|
|||
const { fileInfo, mediaType, isLoading, play } = this.props;
|
||||
const disabled = isLoading || fileInfo === undefined;
|
||||
const doesPlayback = ['audio', 'video'].indexOf(mediaType) !== -1;
|
||||
const icon = doesPlayback ? 'Play' : 'Folder';
|
||||
const label = doesPlayback ? 'Play' : 'View';
|
||||
const icon = doesPlayback ? icons.PLAY : icons.EYE;
|
||||
const label = doesPlayback ? __('Play') : __('View');
|
||||
|
||||
return <Button button="primary" disabled={disabled} label={label} icon={icon} onClick={play} />;
|
||||
}
|
|
@ -1,13 +1,17 @@
|
|||
/* eslint-disable */
|
||||
import React from 'react';
|
||||
import { remote } from 'electron';
|
||||
import Thumbnail from 'component/common/thumbnail';
|
||||
import player from 'render-media';
|
||||
import fs from 'fs';
|
||||
import LoadingScreen from './loading-screen';
|
||||
import path from 'path';
|
||||
import player from 'render-media';
|
||||
import toBlobURL from 'stream-to-blob-url';
|
||||
import FileRender from 'component/fileRender';
|
||||
import Thumbnail from 'component/common/thumbnail';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
|
||||
class VideoPlayer extends React.PureComponent {
|
||||
static MP3_CONTENT_TYPES = ['audio/mpeg3', 'audio/mpeg'];
|
||||
static FILE_MEDIA_TYPES = ['e-book', 'comic-book', 'document', '3D-file'];
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
@ -16,6 +20,7 @@ class VideoPlayer extends React.PureComponent {
|
|||
hasMetadata: false,
|
||||
startedPlaying: false,
|
||||
unplayable: false,
|
||||
fileSource: null,
|
||||
};
|
||||
|
||||
this.togglePlayListener = this.togglePlay.bind(this);
|
||||
|
@ -29,7 +34,7 @@ class VideoPlayer extends React.PureComponent {
|
|||
|
||||
componentDidMount() {
|
||||
const container = this.media;
|
||||
const { contentType, changeVolume, volume, position, claim } = this.props;
|
||||
const { downloadCompleted, contentType, changeVolume, volume, position, claim } = this.props;
|
||||
|
||||
const loadedMetadata = () => {
|
||||
this.setState({ hasMetadata: true, startedPlaying: true });
|
||||
|
@ -51,7 +56,13 @@ class VideoPlayer extends React.PureComponent {
|
|||
// use renderAudio override for mp3
|
||||
if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) {
|
||||
this.renderAudio(container, null, false);
|
||||
} else {
|
||||
}
|
||||
// Render custom viewer: FileRender
|
||||
else if (this.fileType()) {
|
||||
downloadCompleted && this.renderFile();
|
||||
}
|
||||
// Render default viewer: render-media (video, audio, img, iframe)
|
||||
else {
|
||||
player.append(
|
||||
this.file(),
|
||||
container,
|
||||
|
@ -89,7 +100,7 @@ class VideoPlayer extends React.PureComponent {
|
|||
|
||||
componentDidUpdate() {
|
||||
const { contentType, downloadCompleted } = this.props;
|
||||
const { startedPlaying } = this.state;
|
||||
const { startedPlaying, fileSource } = this.state;
|
||||
|
||||
if (this.playableType() && !startedPlaying && downloadCompleted) {
|
||||
const container = this.media.children[0];
|
||||
|
@ -102,6 +113,8 @@ class VideoPlayer extends React.PureComponent {
|
|||
controls: true,
|
||||
});
|
||||
}
|
||||
} else if (this.fileType() && !fileSource && downloadCompleted) {
|
||||
this.renderFile();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,6 +172,40 @@ class VideoPlayer extends React.PureComponent {
|
|||
return ['audio', 'video'].indexOf(mediaType) !== -1;
|
||||
}
|
||||
|
||||
supportedType() {
|
||||
// Files supported by render-media
|
||||
const { contentType, mediaType } = this.props;
|
||||
|
||||
return Object.values(player.mime).indexOf(contentType) !== -1;
|
||||
}
|
||||
|
||||
fileType() {
|
||||
// This files are supported using a custom viewer
|
||||
const { mediaType } = this.props;
|
||||
|
||||
return VideoPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1;
|
||||
}
|
||||
|
||||
renderFile() {
|
||||
// This is what render-media does with unplayable files
|
||||
const { filename, downloadPath, contentType, mediaType } = this.props;
|
||||
|
||||
toBlobURL(fs.createReadStream(downloadPath), contentType, (err, url) => {
|
||||
if (err) {
|
||||
this.setState({ unsupported: true });
|
||||
return false;
|
||||
}
|
||||
// File to render
|
||||
const fileSource = {
|
||||
downloadPath,
|
||||
filePath: url,
|
||||
fileType: path.extname(filename).substring(1),
|
||||
};
|
||||
// Update state
|
||||
this.setState({ fileSource });
|
||||
});
|
||||
}
|
||||
|
||||
renderAudio(container, autoplay) {
|
||||
if (container.firstChild) {
|
||||
container.firstChild.remove();
|
||||
|
@ -173,25 +220,61 @@ class VideoPlayer extends React.PureComponent {
|
|||
container.appendChild(audio);
|
||||
}
|
||||
|
||||
showLoadingScreen(isFileType, isPlayableType) {
|
||||
const { mediaType } = this.props;
|
||||
const { hasMetadata, unplayable, unsupported, fileSource } = this.state;
|
||||
|
||||
const loader = {
|
||||
isLoading: false,
|
||||
loadingStatus: null,
|
||||
};
|
||||
|
||||
// Loading message
|
||||
const noFileMessage = __('Waiting for blob.');
|
||||
const noMetadataMessage = __('Waiting for metadata.');
|
||||
|
||||
// Error message
|
||||
const unplayableMessage = __("Sorry, looks like we can't play this file.");
|
||||
const unsupportedMessage = __("Sorry, looks like we can't preview this file.");
|
||||
|
||||
// Files
|
||||
const isLoadingFile = !fileSource && isFileType;
|
||||
const isUnsupported =
|
||||
(mediaType === 'application' || !this.supportedType()) && !isFileType && !isPlayableType;
|
||||
|
||||
// Media (audio, video)
|
||||
const isUnplayable = isPlayableType && unplayable;
|
||||
const isLoadingMetadata = isPlayableType && (!hasMetadata && !unplayable);
|
||||
|
||||
// Show loading message
|
||||
if (isLoadingFile || isLoadingMetadata) {
|
||||
loader.loadingStatus = isFileType ? noFileMessage : noMetadataMessage;
|
||||
loader.isLoading = true;
|
||||
|
||||
// Show unsupported error message
|
||||
} else if (isUnsupported || isUnplayable) {
|
||||
loader.loadingStatus = isUnsupported ? unsupportedMessage : unplayableMessage;
|
||||
}
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { mediaType, poster } = this.props;
|
||||
const { hasMetadata, unplayable } = this.state;
|
||||
const noMetadataMessage = 'Waiting for metadata.';
|
||||
const unplayableMessage = "Sorry, looks like we can't play this file.";
|
||||
const hideMedia = this.playableType() && !hasMetadata && !unplayable;
|
||||
const { mediaType } = this.props;
|
||||
const { fileSource } = this.state;
|
||||
|
||||
const isFileType = this.fileType();
|
||||
const isFileReady = fileSource && isFileType;
|
||||
const isPlayableType = this.playableType();
|
||||
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{['audio', 'application'].indexOf(mediaType) !== -1 &&
|
||||
(!this.playableType() || hasMetadata) &&
|
||||
!unplayable && <Thumbnail src={poster} />}
|
||||
{this.playableType() &&
|
||||
!hasMetadata &&
|
||||
!unplayable && <LoadingScreen status={noMetadataMessage} />}
|
||||
{unplayable && <LoadingScreen status={unplayableMessage} spinner={false} />}
|
||||
{loadingStatus && <LoadingScreen status={loadingStatus} spinner={isLoading} />}
|
||||
{isFileReady && <FileRender source={fileSource} mediaType={mediaType} />}
|
||||
<div
|
||||
className={'content__view--container'}
|
||||
style={{ opacity: hideMedia ? 0 : 1 }}
|
||||
style={{ opacity: isLoading ? 0 : 1 }}
|
||||
ref={container => {
|
||||
this.media = container;
|
||||
}}
|
|
@ -1,11 +1,10 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import { Lbry } from 'lbry-redux';
|
||||
import classnames from 'classnames';
|
||||
import type { Claim } from 'types/claim';
|
||||
import VideoPlayer from './internal/player';
|
||||
import VideoPlayButton from './internal/play-button';
|
||||
import LoadingScreen from './internal/loading-screen';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import Player from './internal/player';
|
||||
import PlayButton from './internal/play-button';
|
||||
|
||||
const SPACE_BAR_KEYCODE = 32;
|
||||
|
||||
|
@ -40,9 +39,10 @@ type Props = {
|
|||
obscureNsfw: boolean,
|
||||
play: string => void,
|
||||
searchBarFocused: boolean,
|
||||
mediaType: string,
|
||||
};
|
||||
|
||||
class Video extends React.PureComponent<Props> {
|
||||
class FileViewer extends React.PureComponent<Props> {
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
|
@ -123,12 +123,12 @@ class Video extends React.PureComponent<Props> {
|
|||
mediaPosition,
|
||||
className,
|
||||
obscureNsfw,
|
||||
mediaType,
|
||||
} = this.props;
|
||||
|
||||
const isPlaying = playingUri === uri;
|
||||
const isReadyToPlay = fileInfo && fileInfo.written_bytes > 0;
|
||||
const shouldObscureNsfw = obscureNsfw && metadata && metadata.nsfw;
|
||||
const mediaType = Lbry.getMediaType(contentType, fileInfo && fileInfo.file_name);
|
||||
|
||||
let loadStatusMessage = '';
|
||||
|
||||
|
@ -156,7 +156,7 @@ class Video extends React.PureComponent<Props> {
|
|||
<LoadingScreen status={loadStatusMessage} />
|
||||
</div>
|
||||
) : (
|
||||
<VideoPlayer
|
||||
<Player
|
||||
filename={fileInfo.file_name}
|
||||
poster={poster}
|
||||
downloadPath={fileInfo.download_path}
|
||||
|
@ -183,7 +183,7 @@ class Video extends React.PureComponent<Props> {
|
|||
className={layoverClass}
|
||||
style={layoverStyle}
|
||||
>
|
||||
<VideoPlayButton
|
||||
<PlayButton
|
||||
play={e => {
|
||||
e.stopPropagation();
|
||||
this.playContent();
|
||||
|
@ -200,4 +200,4 @@ class Video extends React.PureComponent<Props> {
|
|||
}
|
||||
}
|
||||
|
||||
export default Video;
|
||||
export default FileViewer;
|
37
src/renderer/component/viewers/pdfViewer.jsx
Normal file
37
src/renderer/component/viewers/pdfViewer.jsx
Normal file
|
@ -0,0 +1,37 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
source: {
|
||||
fileType: string,
|
||||
filePath: string,
|
||||
downloadPath: string,
|
||||
},
|
||||
};
|
||||
|
||||
class PdfViewer extends React.PureComponent<Props> {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.viewer = React.createRef();
|
||||
}
|
||||
|
||||
// TODO: Enable context-menu
|
||||
stopContextMenu = event => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
};
|
||||
|
||||
render() {
|
||||
const { source } = this.props;
|
||||
return (
|
||||
<div className="file-render__viewer" onContextMenu={this.stopContextMenu}>
|
||||
<webview
|
||||
ref={this.viewer}
|
||||
src={`chrome://pdf-viewer/index.html?src=file://${source.downloadPath}`}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PdfViewer;
|
|
@ -28,3 +28,5 @@ export const CHECK_SIMPLE = 'Check';
|
|||
export const GLOBE = 'Globe';
|
||||
export const EXTERNAL_LINK = 'ExternalLink';
|
||||
export const GIFT = 'Gift';
|
||||
export const EYE = 'Eye';
|
||||
export const PLAY = 'Play';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import * as React from 'react';
|
||||
import { Lbry, buildURI, normalizeURI, MODALS } from 'lbry-redux';
|
||||
import Video from 'component/video';
|
||||
import { buildURI, normalizeURI, MODALS } from 'lbry-redux';
|
||||
import FileViewer from 'component/fileViewer';
|
||||
import Thumbnail from 'component/common/thumbnail';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import FileDetails from 'component/fileDetails';
|
||||
|
@ -14,7 +14,6 @@ import Button from 'component/button';
|
|||
import SubscribeButton from 'component/subscribeButton';
|
||||
import ViewOnWebButton from 'component/viewOnWebButton';
|
||||
import Page from 'component/page';
|
||||
import player from 'render-media';
|
||||
import * as settings from 'constants/settings';
|
||||
import type { Claim } from 'types/claim';
|
||||
import type { Subscription } from 'types/subscription';
|
||||
|
@ -22,6 +21,7 @@ import FileDownloadLink from 'component/fileDownloadLink';
|
|||
import classnames from 'classnames';
|
||||
import { FormField, FormRow } from 'component/common/form';
|
||||
import ToolTip from 'component/common/tooltip';
|
||||
import getMediaType from 'util/getMediaType';
|
||||
|
||||
type Props = {
|
||||
claim: Claim,
|
||||
|
@ -29,6 +29,7 @@ type Props = {
|
|||
metadata: {
|
||||
title: string,
|
||||
thumbnail: string,
|
||||
file_name: string,
|
||||
nsfw: boolean,
|
||||
},
|
||||
contentType: string,
|
||||
|
@ -49,6 +50,18 @@ type Props = {
|
|||
};
|
||||
|
||||
class FilePage extends React.Component<Props> {
|
||||
static PLAYABLE_MEDIA_TYPES = ['audio', 'video'];
|
||||
static PREVIEW_MEDIA_TYPES = [
|
||||
'text',
|
||||
'model',
|
||||
'image',
|
||||
'3D-file',
|
||||
'document',
|
||||
// Bypass unplayable files
|
||||
// TODO: Find a better way to detect supported types
|
||||
'application',
|
||||
];
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
|
@ -108,15 +121,19 @@ class FilePage extends React.Component<Props> {
|
|||
navigate,
|
||||
autoplay,
|
||||
costInfo,
|
||||
fileInfo,
|
||||
} = this.props;
|
||||
|
||||
// File info
|
||||
const { title, thumbnail } = metadata;
|
||||
const { height, channel_name: channelName, value } = claim;
|
||||
const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage;
|
||||
const isRewardContent = rewardedContentClaimIds.includes(claim.claim_id);
|
||||
const shouldObscureThumbnail = obscureNsfw && metadata.nsfw;
|
||||
const { height, channel_name: channelName, value } = claim;
|
||||
const mediaType = Lbry.getMediaType(contentType);
|
||||
const isPlayable = Object.values(player.mime).includes(contentType) || mediaType === 'audio';
|
||||
const fileName = fileInfo ? fileInfo.file_name : null;
|
||||
const mediaType = getMediaType(contentType, fileName);
|
||||
const showFile =
|
||||
PLAYABLE_MEDIA_TYPES.includes(mediaType) || PREVIEW_MEDIA_TYPES.includes(mediaType);
|
||||
const channelClaimId =
|
||||
value && value.publisherSignature && value.publisherSignature.certificateId;
|
||||
let subscriptionUri;
|
||||
|
@ -150,8 +167,10 @@ class FilePage extends React.Component<Props> {
|
|||
</section>
|
||||
) : (
|
||||
<section className="card">
|
||||
{isPlayable && <Video className="content__embedded" uri={uri} />}
|
||||
{!isPlayable &&
|
||||
{showFile && (
|
||||
<FileViewer className="content__embedded" uri={uri} mediaType={mediaType} />
|
||||
)}
|
||||
{!showFile &&
|
||||
(thumbnail ? (
|
||||
<Thumbnail shouldObscure={shouldObscureThumbnail} src={thumbnail} />
|
||||
) : (
|
||||
|
@ -160,7 +179,9 @@ class FilePage extends React.Component<Props> {
|
|||
'content__empty--nsfw': shouldObscureThumbnail,
|
||||
})}
|
||||
>
|
||||
<div className="card__media-text">{__('This content is not playable.')}</div>
|
||||
<div className="card__media-text">
|
||||
{__("Sorry, looks like we can't preview this file.")}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="card__content">
|
||||
|
|
|
@ -23,5 +23,6 @@
|
|||
@import 'component/_spinner.scss';
|
||||
@import 'component/_nav.scss';
|
||||
@import 'component/_file-list.scss';
|
||||
@import 'component/_file-render.scss';
|
||||
@import 'component/_search.scss';
|
||||
@import 'component/_toggle.scss';
|
||||
|
|
|
@ -96,6 +96,25 @@
|
|||
}
|
||||
}
|
||||
|
||||
.file-render {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.file-render__viewer {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
|
|
25
src/renderer/scss/component/_file-render.scss
Normal file
25
src/renderer/scss/component/_file-render.scss
Normal file
|
@ -0,0 +1,25 @@
|
|||
.file-render {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
|
||||
.file-render__viewer {
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: black;
|
||||
|
||||
iframe,
|
||||
webview {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
32
src/renderer/util/getMediaType.js
Normal file
32
src/renderer/util/getMediaType.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import mime from 'mime';
|
||||
|
||||
const formats = [
|
||||
[/\.(mp4|m4v|webm|flv|f4v|ogv)$/i, 'video'],
|
||||
[/\.(mp3|m4a|aac|wav|flac|ogg|opus)$/i, 'audio'],
|
||||
[/\.(html|htm|xml|pdf|odf|doc|docx|md|markdown|txt|epub|org)$/i, 'document'],
|
||||
[/\.(stl|obj|fbx|gcode)$/i, '3D-file'],
|
||||
];
|
||||
|
||||
export default function getMediaType(contentType, fileName) {
|
||||
const extName = mime.getExtension(contentType);
|
||||
const fileExt = extName ? `.${extName}` : null;
|
||||
const testString = fileName || fileExt;
|
||||
|
||||
// Get mediaType from file extension
|
||||
if (testString) {
|
||||
const res = formats.reduce((ret, testpair) => {
|
||||
const [regex, mediaType] = testpair;
|
||||
|
||||
return regex.test(ret) ? mediaType : ret;
|
||||
}, testString);
|
||||
|
||||
if (res !== testString) return res;
|
||||
}
|
||||
|
||||
// Get mediaType from contentType
|
||||
if (contentType) {
|
||||
return /^[^/]+/.exec(contentType)[0];
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
10
yarn.lock
10
yarn.lock
|
@ -5564,9 +5564,9 @@ lazy-val@^1.0.3:
|
|||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/lazy-val/-/lazy-val-1.0.3.tgz#bb97b200ef00801d94c317e29dc6ed39e31c5edc"
|
||||
|
||||
lbry-redux@lbryio/lbry-redux#201d78b68a329065ee5d2a03bfb1607ea0666588:
|
||||
lbry-redux@lbryio/lbry-redux#341db02c090e0121ff7f5d2e74fa35b06213d858:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/201d78b68a329065ee5d2a03bfb1607ea0666588"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/341db02c090e0121ff7f5d2e74fa35b06213d858"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
@ -8726,6 +8726,12 @@ stream-to-blob-url@^2.0.0:
|
|||
dependencies:
|
||||
stream-to-blob "^1.0.0"
|
||||
|
||||
stream-to-blob-url@^2.1.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/stream-to-blob-url/-/stream-to-blob-url-2.1.1.tgz#e1ac97f86ca8e9f512329a48e7830ce9a50beef2"
|
||||
dependencies:
|
||||
stream-to-blob "^1.0.0"
|
||||
|
||||
stream-to-blob@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/stream-to-blob/-/stream-to-blob-1.0.0.tgz#9f7a1ada39e16ea282ebb7e4cda307edabde658d"
|
||||
|
|
Loading…
Add table
Reference in a new issue