diff --git a/package.json b/package.json index b10cec32d..0fe9622c2 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js" }, "dependencies": { + "@types/three": "^0.93.1", "bluebird": "^3.5.1", "breakdance": "^3.0.1", "classnames": "^2.2.5", diff --git a/src/renderer/component/common/markdown-preview.jsx b/src/renderer/component/common/markdown-preview.jsx index b00ae7025..78af2273d 100644 --- a/src/renderer/component/common/markdown-preview.jsx +++ b/src/renderer/component/common/markdown-preview.jsx @@ -6,25 +6,34 @@ import remarkEmoji from 'remark-emoji'; import ExternalLink from 'component/externalLink'; import defaultSchema from 'hast-util-sanitize/lib/github.json'; +type MarkdownProps = { + content: ?string, + promptLinks?: boolean, +}; + +type SimpleLinkProps = { + href?: string, + title?: string, + children?: React.Node, +}; + +const SimpleLink = (props: SimpleLinkProps) => { + const { href, title, children } = props; + return ( + <a href={href} title={title}> + {children} + </a> + ); +}; + // Use github sanitation schema const schema = { ...defaultSchema }; // Extend sanitation schema to support lbry protocol schema.protocols.href[3] = 'lbry'; -type MarkdownProps = { - content: string, - promptLinks?: boolean, -}; - -const SimpleLink = ({ href, title, children }) => ( - <a href={href} title={title}> - {children} - </a> -); - const MarkdownPreview = (props: MarkdownProps) => { - const { content, externalLinks, promptLinks } = props; + const { content, promptLinks } = props; const remarkOptions = { sanitize: schema, remarkReactComponents: { diff --git a/src/renderer/component/fileRender/view.jsx b/src/renderer/component/fileRender/view.jsx index b9901564e..80a3236a5 100644 --- a/src/renderer/component/fileRender/view.jsx +++ b/src/renderer/component/fileRender/view.jsx @@ -10,11 +10,11 @@ import HtmlViewer from 'component/viewers/htmlViewer'; type Props = { mediaType: string, source: { + stream: string => void, fileName: string, fileType: string, + contentType: string, downloadPath: string, - stream: opts => void, - blob: callback => void, }, currentTheme: string, }; diff --git a/src/renderer/component/viewers/codeViewer.jsx b/src/renderer/component/viewers/codeViewer.jsx index 1d5164468..d10f3135c 100644 --- a/src/renderer/component/viewers/codeViewer.jsx +++ b/src/renderer/component/viewers/codeViewer.jsx @@ -1,6 +1,6 @@ // @flow -import React from 'react'; +import * as React from 'react'; import CodeMirror from 'codemirror/lib/codemirror'; import { openSnippetMenu, stopContextMenu } from 'util/contextMenu'; @@ -22,21 +22,20 @@ import 'codemirror/mode/javascript/javascript'; type Props = { theme: string, - value: string, + value: ?string, contentType: string, }; class CodeViewer extends React.PureComponent<Props> { - constructor(props) { + constructor(props: 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, { + this.codeMirror = CodeMirror.fromTextArea(this.textarea, { // Auto detect syntax with file contentType mode: contentType, // Adaptive theme @@ -54,11 +53,14 @@ class CodeViewer extends React.PureComponent<Props> { this.codeMirror.on('contextmenu', openSnippetMenu); } + textarea: ?HTMLTextAreaElement; + codeMirror: any; + render() { const { value } = this.props; return ( <div className="code-viewer" onContextMenu={stopContextMenu}> - <textarea ref={this.textarea} disabled value={value} /> + <textarea ref={textarea => (this.textarea = textarea)} disabled value={value} /> </div> ); } diff --git a/src/renderer/component/viewers/documentViewer.jsx b/src/renderer/component/viewers/documentViewer.jsx index 608d34873..ed309236d 100644 --- a/src/renderer/component/viewers/documentViewer.jsx +++ b/src/renderer/component/viewers/documentViewer.jsx @@ -8,19 +8,25 @@ import MarkdownPreview from 'component/common/markdown-preview'; type Props = { theme: string, source: { - stream: opts => void, + stream: string => any, fileType: string, contentType: string, }, }; -class DocumentViewer extends React.PureComponent<Props> { - constructor(props) { +type State = { + error: boolean, + loading: boolean, + content: ?string, +}; + +class DocumentViewer extends React.PureComponent<Props, State> { + constructor(props: Props) { super(props); this.state = { - error: null, - content: null, + error: false, loading: true, + content: null, }; } @@ -38,13 +44,14 @@ class DocumentViewer extends React.PureComponent<Props> { this.setState({ content: data, loading: false }); }); - stream.on('error', error => { + stream.on('error', () => { this.setState({ error: true, loading: false }); }); } - renderDocument(content = null) { + renderDocument() { let viewer = null; + const { content } = this.state; const { source, theme } = this.props; const { fileType, contentType } = source; const markdownType = ['md', 'markdown']; @@ -70,7 +77,7 @@ class DocumentViewer extends React.PureComponent<Props> { <div className="file-render__viewer document-viewer"> {loading && !error && <LoadingScreen status={loadingMessage} spinner />} {error && <LoadingScreen status={errorMessage} spinner={!error} />} - {isReady && this.renderDocument(content)} + {isReady && this.renderDocument()} </div> ); } diff --git a/src/renderer/component/viewers/docxViewer.jsx b/src/renderer/component/viewers/docxViewer.jsx index 9959678c5..790269fe6 100644 --- a/src/renderer/component/viewers/docxViewer.jsx +++ b/src/renderer/component/viewers/docxViewer.jsx @@ -10,11 +10,17 @@ type Props = { source: string, }; -class DocxViewer extends React.PureComponent<Props> { - constructor(props) { +type State = { + error: boolean, + loading: boolean, + content: ?string, +}; + +class DocxViewer extends React.PureComponent<Props, State> { + constructor(props: Props) { super(props); this.state = { - error: null, + error: false, content: null, loading: true, }; @@ -46,7 +52,7 @@ class DocxViewer extends React.PureComponent<Props> { const markdown = breakdance.render(result.value); this.setState({ content: markdown, loading: false }); }) - .catch(error => { + .catch(() => { this.setState({ error: true, loading: false }); }) .done(); diff --git a/src/renderer/component/viewers/pdfViewer.jsx b/src/renderer/component/viewers/pdfViewer.jsx index ce0a2095a..e297adfd1 100644 --- a/src/renderer/component/viewers/pdfViewer.jsx +++ b/src/renderer/component/viewers/pdfViewer.jsx @@ -1,5 +1,5 @@ // @flow -import React from 'react'; +import * as React from 'react'; import { stopContextMenu } from 'util/contextMenu'; type Props = { @@ -7,16 +7,11 @@ type Props = { }; class PdfViewer extends React.PureComponent<Props> { - constructor(props) { - super(props); - this.viewer = React.createRef(); - } - render() { const { source } = this.props; return ( <div className="file-render__viewer" onContextMenu={stopContextMenu}> - <webview ref={this.viewer} src={`chrome://pdf-viewer/index.html?src=file://${source}`} /> + <webview src={`chrome://pdf-viewer/index.html?src=file://${source}`} /> </div> ); } diff --git a/src/renderer/component/viewers/threeViewer/index.jsx b/src/renderer/component/viewers/threeViewer/index.jsx index 0f4afb433..229347830 100644 --- a/src/renderer/component/viewers/threeViewer/index.jsx +++ b/src/renderer/component/viewers/threeViewer/index.jsx @@ -12,6 +12,13 @@ import ThreeScene from './internal/scene'; import ThreeLoader from './internal/loader'; import ThreeRenderer from './internal/renderer'; +type viewerTheme = { + gridColor: string, + groundColor: string, + backgroundColor: string, + centerLineColor: string, +}; + type Props = { theme: string, source: { @@ -27,12 +34,13 @@ type State = { }; class ThreeViewer extends React.PureComponent<Props, State> { - static testWebgl = new Promise((resolve, reject) => { - if (detectWebGL()) resolve(); - else reject(); - }); + static testWebgl = (): Promise<void> => + new Promise((resolve, reject) => { + if (detectWebGL()) resolve(); + else reject(); + }); - static createOrbitControls(camera, canvas) { + static createOrbitControls(camera: any, canvas: any) { const controls = new THREE.OrbitControls(camera, canvas); // Controls configuration controls.enableDamping = true; @@ -46,7 +54,7 @@ class ThreeViewer extends React.PureComponent<Props, State> { return controls; } - static fitMeshToCamera(group) { + static fitMeshToCamera(group: any) { const max = { x: 0, y: 0, z: 0 }; const min = { x: 0, y: 0, z: 0 }; @@ -82,23 +90,9 @@ class ThreeViewer extends React.PureComponent<Props, State> { See: https://github.com/mrdoob/three.js/blob/dev/docs/scenes/js/material.js#L195 */ - static updateMaterial(material, geometry) { - material.vertexColors = +material.vertexColors; // Ensure number - material.side = +material.side; // Ensure number - material.needsUpdate = true; - // If Geometry needs update - if (geometry) { - geometry.verticesNeedUpdate = true; - geometry.normalsNeedUpdate = true; - geometry.colorsNeedUpdate = true; - } - } - constructor(props: Props) { super(props); const { theme } = this.props; - this.viewer = React.createRef(); - this.guiContainer = React.createRef(); // Object defualt color this.materialColor = '#44b098'; // Viewer themes @@ -124,10 +118,21 @@ class ThreeViewer extends React.PureComponent<Props, State> { isReady: false, isLoading: false, }; + // Internal objects + this.gui = null; + this.grid = null; + this.mesh = null; + this.camera = null; + this.frameID = null; + this.renderer = null; + this.material = null; + this.geometry = null; + this.targetCenter = null; + this.bufferGeometry = null; } componentDidMount() { - ThreeViewer.testWebgl + ThreeViewer.testWebgl() .then(() => { this.renderScene(); // Update render on resize window @@ -187,7 +192,39 @@ class ThreeViewer extends React.PureComponent<Props, State> { } } - transformGroup(group) { + // Define component types + theme: viewerTheme; + themes: { dark: viewerTheme, light: viewerTheme }; + materialColor: string; + // Refs + viewer: ?HTMLElement; + guiContainer: ?HTMLElement; + // Too complex to add a custom type + gui: any; + grid: any; + mesh: any; + scene: any; + camera: any; + frameID: any; + controls: any; + material: any; + geometry: any; + targetCenter: any; + bufferGeometry: any; + + updateMaterial(material: any, geometry: any) { + this.material.vertexColors = +material.vertexColors; // Ensure number + this.material.side = +material.side; // Ensure number + this.material.needsUpdate = true; + // If Geometry needs update + if (geometry) { + this.geometry.verticesNeedUpdate = true; + this.geometry.normalsNeedUpdate = true; + this.geometry.colorsNeedUpdate = true; + } + } + + transformGroup(group: any) { ThreeViewer.fitMeshToCamera(group); if (!this.targetCenter) { @@ -203,20 +240,19 @@ class ThreeViewer extends React.PureComponent<Props, State> { const config = { color: this.materialColor, - }; - - config.reset = () => { - // Reset material color - config.color = this.materialColor; - // Reset material - this.material.color.set(config.color); - this.material.flatShading = true; - this.material.shininess = 30; - this.material.wireframe = false; - // Reset autoRotate - this.controls.autoRotate = false; - // Reset camera - this.restoreCamera(); + reset: () => { + // Reset material color + config.color = this.materialColor; + // Reset material + this.material.color.set(config.color); + this.material.flatShading = true; + this.material.shininess = 30; + this.material.wireframe = false; + // Reset autoRotate + this.controls.autoRotate = false; + // Reset camera + this.restoreCamera(); + }, }; const materialFolder = this.gui.addFolder('Material'); @@ -240,7 +276,7 @@ class ThreeViewer extends React.PureComponent<Props, State> { .add(this.material, 'flatShading') .name('FlatShading') .onChange(() => { - ThreeViewer.updateMaterial(this.material); + this.updateMaterial(this.material); }) .listen(); @@ -258,11 +294,13 @@ class ThreeViewer extends React.PureComponent<Props, State> { sceneFolder.add(config, 'reset').name('Reset'); - this.guiContainer.current.appendChild(this.gui.domElement); + if (this.guiContainer) { + this.guiContainer.appendChild(this.gui.domElement); + } } } - createGeometry(data) { + createGeometry(data: any) { this.bufferGeometry = data; this.bufferGeometry.computeBoundingBox(); this.bufferGeometry.center(); @@ -301,14 +339,14 @@ class ThreeViewer extends React.PureComponent<Props, State> { }; handleResize = () => { - const { offsetWidth: width, offsetHeight: height } = this.viewer.current; + const { offsetWidth: width, offsetHeight: height } = this.viewer || {}; this.camera.aspect = width / height; this.camera.updateProjectionMatrix(); this.controls.update(); this.renderer.setSize(width, height); }; - updateControlsTarget(point) { + updateControlsTarget(point: { x: number, y: number, z: number }) { this.controls.target.fromArray([point.x, point.y, point.z]); this.controls.update(); } @@ -319,7 +357,10 @@ class ThreeViewer extends React.PureComponent<Props, State> { this.updateControlsTarget(this.targetCenter); } - renderStl(data) { + // Flow requested to add it here + renderer: any; + + renderStl(data: any) { this.createGeometry(data); this.mesh = new THREE.Mesh(this.geometry, this.material); this.mesh.name = 'model'; @@ -327,7 +368,7 @@ class ThreeViewer extends React.PureComponent<Props, State> { this.transformGroup(this.mesh); } - renderObj(event) { + renderObj(event: any) { const mesh = event.detail.loaderRootNode; this.mesh = new THREE.Group(); this.mesh.name = 'model'; @@ -352,7 +393,7 @@ class ThreeViewer extends React.PureComponent<Props, State> { this.transformGroup(this.mesh); } - renderModel(fileType, parsedData) { + renderModel(fileType: string, parsedData: any) { const renderTypes = { stl: data => this.renderStl(data), obj: data => this.renderObj(data), @@ -377,9 +418,8 @@ class ThreeViewer extends React.PureComponent<Props, State> { ...this.theme, }); - const viewer = this.viewer.current; const canvas = this.renderer.domElement; - const { offsetWidth: width, offsetHeight: height } = viewer; + const { offsetWidth: width, offsetHeight: height } = this.viewer || {}; // Grid this.grid = ThreeGrid({ size: 100, gridColor, centerLineColor }); @@ -408,7 +448,9 @@ class ThreeViewer extends React.PureComponent<Props, State> { this.startLoader(); // Append canvas - viewer.appendChild(canvas); + if (this.viewer) { + this.viewer.appendChild(canvas); + } const updateScene = () => { this.frameID = requestAnimationFrame(updateScene); @@ -433,11 +475,11 @@ class ThreeViewer extends React.PureComponent<Props, State> { <React.Fragment> {error && <LoadingScreen status={error} spinner={false} />} {showLoading && <LoadingScreen status={loadingMessage} spinner />} - <div ref={this.guiContainer} className={containerClass} /> + <div ref={element => (this.guiContainer = element)} className={containerClass} /> <div style={{ opacity: showViewer ? 1 : 0 }} className="three-viewer file-render__viewer" - ref={this.viewer} + ref={viewer => (this.viewer = viewer)} /> </React.Fragment> ); diff --git a/yarn.lock b/yarn.lock index ff1003b87..5f1f4193e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -130,6 +130,10 @@ version "8.10.21" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.21.tgz#12b3f2359b27aa05a45d886c8ba1eb8d1a77e285" +"@types/three@^0.93.1": + version "0.93.1" + resolved "https://registry.yarnpkg.com/@types/three/-/three-0.93.1.tgz#0b9ba53182afe16659e220d670471b4475687d64" + "@types/webpack-env@^1.13.5": version "1.13.6" resolved "https://registry.yarnpkg.com/@types/webpack-env/-/webpack-env-1.13.6.tgz#128d1685a7c34d31ed17010fc87d6a12c1de6976"