diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d292fdd1..925dc00c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,20 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/). ### Added * Wallet Encryption/Decryption user flows ([#1785](https://github.com/lbryio/lbry-desktop/pull/1785)) * Add FAQ to Publishing Area ([#1833](https://github.com/lbryio/lbry-desktop/pull/1833)) + * Better preview for content ([#620](https://github.com/lbryio/lbry-desktop/pull/620)) + * Add new markdown and docx viewer ([#1826](https://github.com/lbryio/lbry-desktop/pull/1826)) + * Add new viewer for human-readable text files ([#1826](https://github.com/lbryio/lbry-desktop/pull/1826)) + * Add csv and json viewer ([#1410](https://github.com/lbryio/lbry-desktop/pull/1410)) ### Changed * Pass error message from spee.ch API during thumbnail upload ([#1840](https://github.com/lbryio/lbry-desktop/pull/1840)) + * Use router pattern for rendering file viewer ([#1544](https://github.com/lbryio/lbry-desktop/pull/1544)) ### Fixed - * **Wallet -> Get Credits** page now shows correct ShapeShift status when it's avialable ([#1836](https://github.com/lbryio/lbry-desktop/issue)) + * **Wallet -> Get Credits** page now shows correct ShapeShift status when it's avialable ([#1836](https://github.com/lbryio/lbry-desktop/issues/1836)) * Fix middle click link error ([#1843](https://github.com/lbryio/lbry-desktop/issues/1843)} + * Problem with search auto-complete menu when scrolling over file viewer ([#1847](https://github.com/lbryio/lbry-desktop/issues/1847)) + ## [0.23.0] - 2018-07-25 diff --git a/package.json b/package.json index 2409d62d4..37043ce21 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,9 @@ }, "dependencies": { "bluebird": "^3.5.1", + "breakdance": "^3.0.1", "classnames": "^2.2.5", + "codemirror": "^5.39.2", "country-data": "^0.0.31", "dom-scroll-into-view": "^1.2.1", "electron-dl": "^1.11.0", @@ -51,6 +53,7 @@ "keytar": "^4.2.1", "lbry-redux": "lbryio/lbry-redux#b4fffe863df316bc73183567ab978221ee623b8c", "localforage": "^1.7.1", + "mammoth": "^1.4.6", "mime": "^2.3.1", "mixpanel-browser": "^2.17.1", "moment": "^2.22.0", diff --git a/src/renderer/component/common/form-components/form-field.jsx b/src/renderer/component/common/form-components/form-field.jsx index 4c2045881..ab8569b9e 100644 --- a/src/renderer/component/common/form-components/form-field.jsx +++ b/src/renderer/component/common/form-components/form-field.jsx @@ -6,7 +6,7 @@ import MarkdownPreview from 'component/common/markdown-preview'; import SimpleMDE from 'react-simplemde-editor'; import 'simplemde/dist/simplemde.min.css'; // eslint-disable-line import/no-extraneous-dependencies import Toggle from 'react-toggle'; -import { openEditorMenu } from 'util/contextMenu'; +import { openEditorMenu, stopContextMenu } from 'util/contextMenu'; type Props = { name: string, @@ -57,15 +57,10 @@ export class FormField extends React.PureComponent { ); } else if (type === 'markdown') { - const stopContextMenu = event => { - event.preventDefault(); - event.stopPropagation(); - }; const handleEvents = { - contextmenu(codeMirror, event) { - openEditorMenu(event, codeMirror); - }, + contextmenu: openEditorMenu, }; + input = (
void, + blob: callback => void, }, currentTheme: string, }; @@ -17,22 +22,36 @@ type Props = { class FileRender extends React.PureComponent { renderViewer() { const { source, mediaType, currentTheme } = this.props; - const viewerProps = { source, theme: currentTheme }; + + // Extract relevant data to render file + const { blob, stream, fileName, fileType, contentType, downloadPath } = source; + + // Human-readable files (scripts and plain-text files) + const readableFiles = ['text', 'document', 'script']; // Supported mediaTypes const mediaTypes = { - '3D-file': , + '3D-file': , // Add routes to viewer... }; // Supported fileType const fileTypes = { - pdf: , + pdf: , + docx: , + html: , // Add routes to viewer... }; - const { fileType } = source; - const viewer = mediaType && source && (mediaTypes[mediaType] || fileTypes[fileType]); + // Check for a valid fileType or mediaType + let viewer = fileTypes[fileType] || mediaTypes[mediaType]; + + // Check for Human-readable files + if (!viewer && readableFiles.includes(mediaType)) { + viewer = ; + } + + // Message Error const unsupportedMessage = __("Sorry, looks like we can't preview this file."); const unsupported = ; diff --git a/src/renderer/component/fileViewer/internal/player.jsx b/src/renderer/component/fileViewer/internal/player.jsx index 9ae490b66..375f8d152 100644 --- a/src/renderer/component/fileViewer/internal/player.jsx +++ b/src/renderer/component/fileViewer/internal/player.jsx @@ -9,9 +9,9 @@ import FileRender from 'component/fileRender'; import Thumbnail from 'component/common/thumbnail'; import LoadingScreen from 'component/common/loading-screen'; -class VideoPlayer extends React.PureComponent { +class MediaPlayer extends React.PureComponent { static MP3_CONTENT_TYPES = ['audio/mpeg3', 'audio/mpeg']; - static FILE_MEDIA_TYPES = ['e-book', 'comic-book', 'document', '3D-file']; + static FILE_MEDIA_TYPES = ['text', 'script', 'e-book', 'comic-book', 'document', '3D-file']; constructor(props) { super(props); @@ -54,7 +54,7 @@ class VideoPlayer extends React.PureComponent { }; // use renderAudio override for mp3 - if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { + if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { this.renderAudio(container, null, false); } // Render custom viewer: FileRender @@ -105,7 +105,7 @@ class VideoPlayer extends React.PureComponent { if (this.playableType() && !startedPlaying && downloadCompleted) { const container = this.media.children[0]; - if (VideoPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { + if (MediaPlayer.MP3_CONTENT_TYPES.indexOf(contentType) > -1) { this.renderAudio(this.media, true); } else { player.render(this.file(), container, { @@ -158,10 +158,10 @@ class VideoPlayer extends React.PureComponent { } file() { - const { downloadPath, filename } = this.props; + const { downloadPath, fileName } = this.props; return { - name: filename, + name: fileName, createReadStream: opts => fs.createReadStream(downloadPath, opts), }; } @@ -183,27 +183,30 @@ class VideoPlayer extends React.PureComponent { // This files are supported using a custom viewer const { mediaType } = this.props; - return VideoPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1; + return MediaPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1; } renderFile() { // This is what render-media does with unplayable files - const { filename, downloadPath, contentType, mediaType } = this.props; + 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 }); - }); + // File to render + const fileSource = { + fileName, + contentType, + downloadPath, + fileType: path.extname(fileName).substring(1), + }; + + // Readable stream from file + fileSource.stream = opts => fs.createReadStream(downloadPath, opts); + + // Blob url from stream + fileSource.blob = callback => + toBlobURL(fs.createReadStream(downloadPath), contentType, callback); + + // Update state + this.setState({ fileSource }); } renderAudio(container, autoplay) { @@ -284,5 +287,5 @@ class VideoPlayer extends React.PureComponent { } } -export default VideoPlayer; +export default MediaPlayer; /* eslint-disable */ diff --git a/src/renderer/component/fileViewer/view.jsx b/src/renderer/component/fileViewer/view.jsx index 406c25186..cfa653c2c 100644 --- a/src/renderer/component/fileViewer/view.jsx +++ b/src/renderer/component/fileViewer/view.jsx @@ -163,7 +163,7 @@ class FileViewer extends React.PureComponent {
) : ( { + constructor(props) { + super(props); + this.codeMirror = null; + this.textarea = React.createRef(); + } + + componentDidMount() { + const { theme, contentType } = this.props; + // Init CodeMirror + this.codeMirror = CodeMirror.fromTextArea(this.textarea.current, { + // Auto detect syntax with file contentType + mode: contentType, + // Adaptive theme + theme: theme === 'dark' ? 'one-dark' : 'default', + // Hide the cursor + readOnly: true, + // Styled text selection + styleSelectedText: true, + // Additional config opts + dragDrop: false, + lineNumbers: true, + lineWrapping: true, + }); + // Add events + this.codeMirror.on('contextmenu', openSnippetMenu); + } + + render() { + const { value } = this.props; + return ( +
+