From 63806845ca141ea595857c491dec2c2c904eba51 Mon Sep 17 00:00:00 2001 From: Shawn Date: Wed, 3 Apr 2019 00:56:58 -0500 Subject: [PATCH] WIP Checkpoint, spaghetti code! --- .babelrc | 2 + package.json | 18 +- postcss.config.js | 16 + src/platforms/electron/index.js | 8 +- .../common/form-components/form-field.jsx | 36 +- .../common/markdown-preview-internal.jsx | 55 + src/ui/component/common/markdown-preview.jsx | 58 +- src/ui/component/common/qr-code.jsx | 12 +- src/ui/component/fileRender/view.jsx | 41 +- .../component/fileViewer/internal/player.jsx | 15 +- src/ui/component/fileViewer/view.jsx | 48 +- src/ui/component/viewers/audioViewer.jsx | 277 +++ .../component/viewers/audioViewer.module.scss | 193 ++ src/ui/component/viewers/codeViewer.jsx | 2 +- src/ui/component/viewers/documentViewer.jsx | 4 +- .../{audioVideoViewer.jsx => videoViewer.jsx} | 25 +- src/ui/constants/icons.js | 10 + src/ui/index.jsx | 6 +- src/ui/modal/modalPhoneCollection/view.jsx | 14 +- src/ui/page/file/view.jsx | 5 + src/ui/redux/actions/content.js | 4 +- src/ui/redux/actions/subscriptions.js | 1 - webpack.base.config.js | 39 +- webpack.electron.config.js | 2 + yarn.lock | 2081 +++++++++++------ 25 files changed, 2113 insertions(+), 859 deletions(-) create mode 100644 postcss.config.js create mode 100644 src/ui/component/common/markdown-preview-internal.jsx create mode 100644 src/ui/component/viewers/audioViewer.jsx create mode 100644 src/ui/component/viewers/audioViewer.module.scss rename src/ui/component/viewers/{audioVideoViewer.jsx => videoViewer.jsx} (72%) diff --git a/.babelrc b/.babelrc index 132333597..4d27aba66 100644 --- a/.babelrc +++ b/.babelrc @@ -1,6 +1,8 @@ { "presets": ["@babel/react", "@babel/flow"], "plugins": [ + "import-glob", + "@babel/plugin-transform-runtime", "@babel/plugin-syntax-dynamic-import", "react-hot-loader/babel", ["@babel/plugin-proposal-decorators", { "decoratorsBeforeExport": true }], diff --git a/package.json b/package.json index ecd5f3f66..0291c3bf8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,7 @@ "compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web", "dev": "yarn dev:electron", "dev:electron": "cross-env NODE_ENV=development node ./src/platforms/electron/devServer.js", - "dev:web": "NODE_ENV=development webpack-dev-server --open --hot --progress --config webpack.web.config.js", + "dev:web": "cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --config webpack.web.config.js", "dev:internal-apis": "LBRY_API_URL='http://localhost:9090' yarn dev:electron", "run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js", "pack": "electron-builder --dir", @@ -52,6 +52,7 @@ "@babel/plugin-proposal-decorators": "^7.3.0", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/plugin-transform-flow-strip-types": "^7.2.3", + "@babel/plugin-transform-runtime": "^7.4.3", "@babel/polyfill": "^7.2.5", "@babel/preset-flow": "^7.0.0", "@babel/preset-react": "^7.0.0", @@ -64,8 +65,11 @@ "babel-eslint": "^10.0.1", "babel-loader": "^8.0.5", "babel-plugin-add-module-exports": "^1.0.0", + "babel-plugin-import-glob": "^2.0.0", "babel-plugin-transform-imports": "^1.5.1", "bluebird": "^3.5.1", + "butterchurn": "^2.6.7", + "butterchurn-presets": "^2.4.7", "chalk": "^2.4.2", "classnames": "^2.2.5", "codemirror": "^5.39.2", @@ -74,6 +78,7 @@ "country-data": "^0.0.31", "cross-env": "^5.2.0", "css-loader": "^2.1.0", + "cssnano": "^4.1.10", "dat.gui": "^0.7.2", "decompress": "^4.2.0", "del": "^3.0.0", @@ -106,12 +111,14 @@ "hast-util-sanitize": "^1.1.2", "history": "^4.9.0", "husky": "^0.14.3", + "jsmediatags": "^3.8.1", "json-loader": "^0.5.4", "lbry-format": "https://github.com/lbryio/lbry-format.git", - "lbry-redux": "lbryio/lbry-redux#86f1340f834d0f5cd5365492a8ff15d4b213a050", - "lbryinc": "lbryio/lbryinc#d9f9035113c8b9ab3b0ee7ffbd38f910086a665e", + "lbry-redux": "lbryio/lbry-redux#d4c7dea65f7179974e9b96c863022fe7b049ff7d", + "lbryinc": "lbryio/lbryinc#4f2d4a50986bffab0b05d9f6cd7c2f0a856a0e02", "lint-staged": "^7.0.2", "localforage": "^1.7.1", + "lodash-es": "^4.17.11", "make-runnable": "^1.3.6", "mammoth": "^1.4.6", "mime": "^2.3.1", @@ -121,6 +128,8 @@ "node-libs-browser": "^2.1.0", "node-loader": "^0.6.0", "node-sass": "^4.11.0", + "postcss-import": "^12.0.1", + "postcss-loader": "^3.0.0", "preprocess-loader": "^0.3.0", "prettier": "^1.11.1", "prop-types": "^15.6.2", @@ -156,9 +165,10 @@ "style-loader": "^0.23.1", "terser-webpack-plugin": "^1.2.3", "three": "^0.93.0", - "three-full": "^11.3.2", + "three-full": "^17.1.0", "tree-kill": "^1.1.0", "video.js": "^7.2.2", + "wavesurfer.js": "^2.2.1", "webpack": "^4.28.4", "webpack-bundle-analyzer": "^3.1.0", "webpack-config-utils": "^2.3.1", diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 000000000..70e448a04 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,16 @@ +module.exports = ({ file, options, env }) => { + env = env || {}; + file = file || {}; + options = options || {}; + options.cssnext = options.cssnext || null; + options.autoprefixer = options.autoprefixer || null; + options.cssnano = options.cssnano || null; + + return { + parser: file.extname === '.sss' ? 'sugarss' : false, + plugins: { + 'postcss-import': { root: file.dirname }, + 'cssnano': env === 'production' ? options.cssnano : false + } + }; +}; diff --git a/src/platforms/electron/index.js b/src/platforms/electron/index.js index 9ad7390c0..3c7fda73a 100644 --- a/src/platforms/electron/index.js +++ b/src/platforms/electron/index.js @@ -40,12 +40,8 @@ app.setName('LBRY'); app.setAppUserModelId('io.lbry.LBRY'); if (isDev) { - // Enable WEBGL - app.commandLine.appendSwitch('ignore-gpu-blacklist'); - app.commandLine.appendSwitch('--disable-gpu-process-crash-limit'); - app.disableDomainBlockingFor3DAPIs(); - - // Disable security warnings in dev mode - https://github.com/electron/electron/blob/master/docs/tutorial/security.md#electron-security-warnings + // Disable security warnings in dev mode: + // https://github.com/electron/electron/blob/master/docs/tutorial/security.md#electron-security-warnings process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = true; } diff --git a/src/ui/component/common/form-components/form-field.jsx b/src/ui/component/common/form-components/form-field.jsx index cc86c8cb5..5fd77eb3b 100644 --- a/src/ui/component/common/form-components/form-field.jsx +++ b/src/ui/component/common/form-components/form-field.jsx @@ -1,12 +1,16 @@ // @flow -import * as React from 'react'; +import React, { Suspense } from 'react'; import ReactDOMServer from 'react-dom/server'; import MarkdownPreview from 'component/common/markdown-preview'; -import SimpleMDE from 'react-simplemde-editor'; import 'easymde/dist/easymde.min.css'; import Toggle from 'react-toggle'; import { openEditorMenu, stopContextMenu } from 'util/context-menu'; +const SimpleMDE = React.lazy(() => import( + /* webpackChunkName: "SimpleMDE" */ + 'react-simplemde-editor' +)); + type Props = { name: string, label?: string, @@ -132,19 +136,21 @@ export class FormField extends React.PureComponent {
- ; - return ReactDOMServer.renderToString(preview); - }, - }} - /> +
}> + ; + return ReactDOMServer.renderToString(preview); + }, + }} + /> + ); diff --git a/src/ui/component/common/markdown-preview-internal.jsx b/src/ui/component/common/markdown-preview-internal.jsx new file mode 100644 index 000000000..78af2273d --- /dev/null +++ b/src/ui/component/common/markdown-preview-internal.jsx @@ -0,0 +1,55 @@ +// @flow +import * as React from 'react'; +import remark from 'remark'; +import reactRenderer from 'remark-react'; +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 ( + + {children} + + ); +}; + +// Use github sanitation schema +const schema = { ...defaultSchema }; + +// Extend sanitation schema to support lbry protocol +schema.protocols.href[3] = 'lbry'; + +const MarkdownPreview = (props: MarkdownProps) => { + const { content, promptLinks } = props; + const remarkOptions = { + sanitize: schema, + remarkReactComponents: { + a: promptLinks ? ExternalLink : SimpleLink, + }, + }; + return ( +
+ { + remark() + .use(remarkEmoji) + .use(reactRenderer, remarkOptions) + .processSync(content).contents + } +
+ ); +}; + +export default MarkdownPreview; diff --git a/src/ui/component/common/markdown-preview.jsx b/src/ui/component/common/markdown-preview.jsx index 78af2273d..1c850887c 100644 --- a/src/ui/component/common/markdown-preview.jsx +++ b/src/ui/component/common/markdown-preview.jsx @@ -1,54 +1,16 @@ -// @flow -import * as React from 'react'; -import remark from 'remark'; -import reactRenderer from 'remark-react'; -import remarkEmoji from 'remark-emoji'; -import ExternalLink from 'component/externalLink'; -import defaultSchema from 'hast-util-sanitize/lib/github.json'; +import React, { Suspense } from 'react'; -type MarkdownProps = { - content: ?string, - promptLinks?: boolean, -}; +const MarkdownPreviewInternal = React.lazy(() => import( + /* webpackChunkName: "markdownPreview" */ + /* webpackPrefetch: true */ + './markdown-preview-internal' +)); -type SimpleLinkProps = { - href?: string, - title?: string, - children?: React.Node, -}; - -const SimpleLink = (props: SimpleLinkProps) => { - const { href, title, children } = props; +const MarkdownPreview = (props) => { return ( - - {children} - - ); -}; - -// Use github sanitation schema -const schema = { ...defaultSchema }; - -// Extend sanitation schema to support lbry protocol -schema.protocols.href[3] = 'lbry'; - -const MarkdownPreview = (props: MarkdownProps) => { - const { content, promptLinks } = props; - const remarkOptions = { - sanitize: schema, - remarkReactComponents: { - a: promptLinks ? ExternalLink : SimpleLink, - }, - }; - return ( -
- { - remark() - .use(remarkEmoji) - .use(reactRenderer, remarkOptions) - .processSync(content).contents - } -
+ }> + + ); }; diff --git a/src/ui/component/common/qr-code.jsx b/src/ui/component/common/qr-code.jsx index daf08d9c3..e3869e2dd 100644 --- a/src/ui/component/common/qr-code.jsx +++ b/src/ui/component/common/qr-code.jsx @@ -1,8 +1,12 @@ // @flow -import React from 'react'; -import QRCodeElement from 'qrcode.react'; +import React, { Suspense } from 'react'; import classnames from 'classnames'; +const LazyQRCodeElement = React.lazy(() => import( + /* webpackChunkName: "qrCode" */ + 'qrcode.react' +)); + type Props = { value: string, paddingRight?: boolean, @@ -24,7 +28,9 @@ class QRCode extends React.Component { 'qr-code--top-padding': paddingTop, })} > - + }> + + ); } diff --git a/src/ui/component/fileRender/view.jsx b/src/ui/component/fileRender/view.jsx index fa125fcde..f73224fe1 100644 --- a/src/ui/component/fileRender/view.jsx +++ b/src/ui/component/fileRender/view.jsx @@ -1,13 +1,35 @@ // @flow import type { Claim } from 'types/claim'; import { remote } from 'electron'; -import React from 'react'; +import React, { Suspense } from 'react'; import LoadingScreen from 'component/common/loading-screen'; -import PdfViewer from 'component/viewers/pdfViewer'; -import DocumentViewer from 'component/viewers/documentViewer'; -import DocxViewer from 'component/viewers/docxViewer'; -import HtmlViewer from 'component/viewers/htmlViewer'; -import AudioVideoViewer from 'component/viewers/audioVideoViewer'; +import VideoViewer from 'component/viewers/videoViewer'; + +const AudioViewer = React.lazy(() => import( + /* webpackChunkName: "audioViewer" */ + 'component/viewers/audioViewer' +)); + +const DocumentViewer = React.lazy(() => import( + /* webpackChunkName: "documentViewer" */ + 'component/viewers/documentViewer' +)); + +const DocxViewer = React.lazy(() => import( + /* webpackChunkName: "docxViewer" */ + 'component/viewers/docxViewer' +)); + +const HtmlViewer = React.lazy(() => import( + /* webpackChunkName: "htmlViewer" */ + 'component/viewers/htmlViewer' +)); + +const PdfViewer = React.lazy(() => import( + /* webpackChunkName: "pdfViewer" */ + 'component/viewers/pdfViewer' +)); + // @if TARGET='app' const ThreeViewer = React.lazy(() => import( /* webpackChunkName: "threeViewer" */ @@ -97,6 +119,8 @@ class FileRender extends React.PureComponent { renderViewer() { const { source, mediaType, currentTheme, poster, claim } = this.props; + console.log('mediaType', mediaType); + // Extract relevant data to render file const { stream, fileType, contentType, downloadPath, fileName } = source; @@ -123,7 +147,7 @@ class FileRender extends React.PureComponent { /> ), video: ( - { /> ), audio: ( - { } render() { + console.log('RENDER') return (
}> diff --git a/src/ui/component/fileViewer/internal/player.jsx b/src/ui/component/fileViewer/internal/player.jsx index cc602c63e..c34467814 100644 --- a/src/ui/component/fileViewer/internal/player.jsx +++ b/src/ui/component/fileViewer/internal/player.jsx @@ -47,11 +47,8 @@ class MediaPlayer extends React.PureComponent { 'comic-book', 'document', '3D-file', - // The web can use the new video player, which has it's own file renderer - // @if TARGET='web' 'video', 'audio', - // @endif ]; static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/'; static SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278'; @@ -266,6 +263,11 @@ class MediaPlayer extends React.PureComponent { // This files are supported using a custom viewer const { mediaType, contentType } = this.props; + console.log({ + mediaType, + contentType + }) + return ( MediaPlayer.FILE_MEDIA_TYPES.indexOf(mediaType) > -1 || MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1 @@ -360,6 +362,13 @@ class MediaPlayer extends React.PureComponent { const isPlayableType = this.playableType(); const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType); + console.log({ + mediaType, + fileSource, + isFileReady, + isFileType + }) + return ( {loadingStatus && } diff --git a/src/ui/component/fileViewer/view.jsx b/src/ui/component/fileViewer/view.jsx index 34336951c..02e05996d 100644 --- a/src/ui/component/fileViewer/view.jsx +++ b/src/ui/component/fileViewer/view.jsx @@ -1,13 +1,17 @@ // @flow import * as PAGES from 'constants/pages'; -import React from 'react'; +import React, { Suspense } from 'react'; import classnames from 'classnames'; import analytics from 'analytics'; import type { Claim } from 'types/claim'; import LoadingScreen from 'component/common/loading-screen'; -import Player from './internal/player'; import PlayButton from './internal/play-button'; +const Player = React.lazy(() => import( + /* webpackChunkName: "player-legacy" */ + './internal/player' +)); + const SPACE_BAR_KEYCODE = 32; type Props = { @@ -269,25 +273,27 @@ class FileViewer extends React.PureComponent { ) : ( - - savePosition(claim.claim_id, `${claim.txid}:${claim.nout}`, newPosition) - } - claim={claim} - uri={uri} - position={position} - onStartCb={this.onFileStartCb} - onFinishCb={this.onFileFinishCb} - playingUri={playingUri} - /> + }> + + savePosition(claim.claim_id, `${claim.txid}:${claim.nout}`, newPosition) + } + claim={claim} + uri={uri} + position={position} + onStartCb={this.onFileStartCb} + onFinishCb={this.onFileFinishCb} + playingUri={playingUri} + /> + )} )} diff --git a/src/ui/component/viewers/audioViewer.jsx b/src/ui/component/viewers/audioViewer.jsx new file mode 100644 index 000000000..db2e81301 --- /dev/null +++ b/src/ui/component/viewers/audioViewer.jsx @@ -0,0 +1,277 @@ +// @flow +import type { Claim } from 'types/claim'; +import React from 'react'; +import * as ICONS from 'constants/icons'; +import Button from 'component/button'; +import Tooltip from 'component/common/tooltip'; +import { stopContextMenu } from 'util/context-menu'; +import butterchurn from 'butterchurn'; +import detectButterchurnSupport from 'butterchurn/lib/isSupported.min'; +import butterchurnPresets from 'butterchurn-presets'; +import jsmediatags from 'jsmediatags/dist/jsmediatags'; +import WaveSurfer from 'wavesurfer.js'; + +import styles from './audioViewer.module.scss'; + +const isButterchurnSupported = detectButterchurnSupport(); + +const EQ_BANDS_SIMPLE = [ + 55, + 150, + 250, + 400, + 500, + 1000, + 2000, + 4000, + 8000, + 16000, +] +/* +const EQ_LOWSHELF = EQ_BANDS_SIMPLE.shift(); +const EQ_HIGHSHELF = EQ_BANDS_SIMPLE.pop(); + +const eqFilters = EQ.map(function(band) { + var filter = wavesurfer.backend.ac.createBiquadFilter(); + filter.type = 'peaking'; + filter.gain.value = 0; + filter.Q.value = 1; + filter.frequency.value = band.f; + return filter; + }); +*/ + +type Props = { + source: { + downloadPath: string, + fileName: string, + }, + contentType: string, + poster?: string, + claim: Claim, +}; + +const presets = [ + require('butterchurn-presets/presets/converted/Flexi - when monopolies were the future [simple warp + non-reactive moebius].json'), + require('butterchurn-presets/presets/converted/Rovastar & Loadus - FractalDrop (Active Sparks Mix).json'), + require('butterchurn-presets/presets/converted/shifter - tumbling cubes (ripples).json'), + require('butterchurn-presets/presets/converted/ORB - Blue Emotion.json'), + require('butterchurn-presets/presets/converted/shifter - urchin mod.json'), + require('butterchurn-presets/presets/converted/Stahlregen & fishbrain + flexi + geiss - The Machine that conquered the Aether.json'), + require('butterchurn-presets/presets/converted/Zylot - Crosshair Dimension (Light of Ages).json'), +]; + +class AudioVideoViewer extends React.PureComponent { + audioNode: ?HTMLAudioElement; + player: ?{ dispose: () => void }; + + state = { + playing: false, + enableMilkdrop: isButterchurnSupported, + showEqualizer: false, + showSongDetails: true, + enableArt: true, + artLoaded: false, + artist: null, + title: null, + album: null, + }; + + componentDidMount() { + const me = this; + const { contentType, poster, claim } = me.props; + + const path = `https://api.lbry.tv/content/claims/${claim.name}/${claim.claim_id}/stream.mp4`; + const sources = [ + { + src: path, + type: contentType, + }, + ]; + + const audioNode = this.audioNode; + + audioNode.crossOrigin = 'anonymous'; + + const canvasHeight = me.canvasNode.offsetHeight; + const canvasWidth = me.canvasNode.offsetWidth; + + // Required for canvas, nuance of rendering + me.canvasNode.height = canvasHeight; + me.canvasNode.width = canvasWidth; + + const AudioContext = window.AudioContext || window.webkitAudioContext; + const audioContext = new AudioContext(); + + const audioSource = audioContext.createMediaElementSource(audioNode); + audioSource.connect(audioContext.destination); + + if (isButterchurnSupported) { + const visualizer = me.visualizer = butterchurn.createVisualizer(audioContext, me.canvasNode, { + height: canvasHeight, + width: canvasWidth, + pixelRatio: window.devicePixelRatio || 1, + textureRatio: 1, + }); + + visualizer.connectAudio(audioSource); + visualizer.loadPreset(presets[Math.floor(Math.random() * presets.length)], 2.0); + + me._frameCycle = () => { + requestAnimationFrame(me._frameCycle); + + if(me.state.enableMilkdrop === true) { + visualizer.render(); + } + } + me._frameCycle(); + } + + const wavesurfer = WaveSurfer.create({ + barWidth: 3, + container: this.waveNode, + waveColor: '#000', + progressColor: '#fff', + mediaControls: true, + responsive: true, + normalize: true, + backend: 'MediaElement', + minPxPerSec: 100, + height: this.waveNode.offsetHeight, + }); + + wavesurfer.load(audioNode); + + jsmediatags.Config.setDisallowedXhrHeaders(['If-Modified-Since', 'Range']); + jsmediatags.read(path, { + onSuccess: function(result) { + const { + album, + artist, + title, + picture + } = result.tags; + + if (picture) { + const byteArray = new Uint8Array(picture.data); + const blob = new Blob([byteArray], { type: picture.type }); + const albumArtUrl = URL.createObjectURL(blob); + me.artNode.src = albumArtUrl; + + me.setState({ artLoaded: true }); + } + + me.setState({ + album, + artist, + title, + }); + }, + onError: function(error) { + console.log(':(', error.type, error.info); + } + }); + } + + componentWillUnmount() { + if (this.player) { + this.player.dispose(); + } + + // Kill the render loop + this._frameCycle = () => {}; + } + + render() { + const me = this; + const { contentType, poster, claim } = me.props; + const { + album, + artist, + title, + enableMilkdrop, + showEqualizer, + showSongDetails, + enableArt, + artLoaded, + playing, + userActive, + } = this.state; + + const renderArt = enableArt && artLoaded; + + const path = `https://api.lbry.tv/content/claims/${claim.name}/${claim.claim_id}/stream.mp4`; + + const playButton = ( +
{ + const audioNode = this.audioNode; + if (audioNode.paused) { + audioNode.play(); + } else { + audioNode.pause(); + } + }} className={playing ? styles.playButtonPause : styles.playButtonPlay}>
+ ); + + return ( +
me.setState({ userActive: true })} onMouseLeave={()=>me.setState({ userActive: false })} onContextMenu={stopContextMenu}> +
+
+ +
+
(this.waveNode = node)} className={styles.wave}>
+
+
+ (this.artNode = node)} /> + {renderArt && playButton} +
+
+
+ {artist &&
} + {title &&
} + {album &&
} +
+
+
+ {!renderArt &&
{playButton}
} +
+ (this.canvasNode = node)} className={enableMilkdrop ? styles.milkdrop : styles.milkdropDisabled} /> +
+ ); + } +} + +export default AudioVideoViewer; diff --git a/src/ui/component/viewers/audioViewer.module.scss b/src/ui/component/viewers/audioViewer.module.scss new file mode 100644 index 000000000..215b4fdd2 --- /dev/null +++ b/src/ui/component/viewers/audioViewer.module.scss @@ -0,0 +1,193 @@ +.wrapper { + composes: 'file-render__viewer' from global; +} + +.userActive { + composes: wrapper; +} + +.container { + background: #212529; + position: absolute; + height: 100%; + width: 100%; + display: flex; +} + +.containerWithMilkdrop { + composes: container; + + background: rgba(50, 50, 55, .7); +} + +.wave { + position: absolute; + bottom: -20%; + height: 40%; + opacity: 0.5; + overflow: hidden; + width: 100%; +} + +.infoContainer { + padding: 0 20%; + display: flex; + align-items: center; + justify-content: center; + min-height: 42%; + align-self: center; + width: 100%; + margin-top: -10%; +} + +.infoArtContainer { + align-self: flex-start; + width: 40%; + float: left; + position: relative; + background: rgba(0, 0, 0 , 0.4); +} + +.infoArtContainerHidden { + display: none; +} + +.infoArtImage { + display: block; + opacity: 1; + transition: opacity 0.7s; + + .userActive & { + opacity: 0.2; + } +} + +.songDetailsContainer { + text-align: left; + padding: 3%; + width: 50%; +} + +.songDetailsContainerHidden { + display: none; +} + +.songDetailsContainerNoArt { + composes: songDetailsContainer; + + text-align: center; +} + +.songDetails { + width: 150%; + text-shadow: 2px 2px 3px #000; +} + +.songDetailsNoArt { + composes: songDetails; + + width: 200%; + margin-left: -50%; +} + +.detailsIcon { + color: rgba(255, 255, 255, .5); + top: -3px; + padding-right: 10px; + width: 30px; +} + +.detailsIconArtist { + composes: detailsIcon; + + top: -3px; +} + +.detailsIconSong { + composes: detailsIcon; + + top: -5px; +} + +.detailsIconAlbum { + composes: detailsIcon; +} + +.detailsLineArtist { + font-size: 26px; + padding-bottom: 5px; +} + +.detailsLineSong { + font-size: 34px; + line-height: 36px; +} + +.detailsLineAlbum { + font-size: 20px; + padding-top: 8px; +} + +.playButton { + position: absolute; + border: 5px solid #fff; + border-radius: 45px; + color: #fff; + font-family: arial; + font-size: 60px; + left: 50%; + line-height: 80px; + margin-left: -45px; + padding-left: 20px; + bottom: 50%; + margin-bottom: -45px; + height: 90px; + width: 90px; + opacity: 0; + transition: opacity .7s; + + .userActive & { + opacity: 0.6; + } +} + +.playButtonPlay { + composes: playButton; + + &::after { + display: block; + content: "▶"; + } +} + +.playButtonPause { + composes: playButton; + + font-size: 50px; + line-height: 75px; + padding-left: 20px; + letter-spacing: -24px; + + &::after { + display: block; + content: "▎▎"; + } +} + +.playButtonDetachedContainer { + bottom: 35%; + position: absolute; + left: 50%; +} + +.milkdrop { + top: 0; + z-index: 100; + height: 100%; + width: 100%; + display: block; +} + +.milkdropDisabled { + display: none; +} diff --git a/src/ui/component/viewers/codeViewer.jsx b/src/ui/component/viewers/codeViewer.jsx index c0a2c0238..0c3432aa8 100644 --- a/src/ui/component/viewers/codeViewer.jsx +++ b/src/ui/component/viewers/codeViewer.jsx @@ -36,7 +36,7 @@ class CodeViewer extends React.PureComponent { const { theme, contentType } = me.props; // Init CodeMirror import( - /* webpackChunkName: "codeViewer" */ + /* webpackChunkName: "codemirror" */ 'codemirror/lib/codemirror' ).then((CodeMirror) => { me.codeMirror = CodeMirror.fromTextArea(me.textarea, { diff --git a/src/ui/component/viewers/documentViewer.jsx b/src/ui/component/viewers/documentViewer.jsx index bf01c110a..ce8a7992e 100644 --- a/src/ui/component/viewers/documentViewer.jsx +++ b/src/ui/component/viewers/documentViewer.jsx @@ -1,6 +1,6 @@ // @flow -import React from 'react'; +import React, { Suspense } from 'react'; import LoadingScreen from 'component/common/loading-screen'; import MarkdownPreview from 'component/common/markdown-preview'; @@ -84,7 +84,7 @@ class DocumentViewer extends React.PureComponent {
{loading && !error && } {error && } - {isReady && this.renderDocument()} + {isReady &&
}>{this.renderDocument()}} ); } diff --git a/src/ui/component/viewers/audioVideoViewer.jsx b/src/ui/component/viewers/videoViewer.jsx similarity index 72% rename from src/ui/component/viewers/audioVideoViewer.jsx rename to src/ui/component/viewers/videoViewer.jsx index afd3d3950..bfb2bc67b 100644 --- a/src/ui/component/viewers/audioVideoViewer.jsx +++ b/src/ui/component/viewers/videoViewer.jsx @@ -1,10 +1,12 @@ // @flow import type { Claim } from 'types/claim'; -import React from 'react'; +import React, { Suspense } from 'react'; import { stopContextMenu } from 'util/context-menu'; -import(/* webpackChunkName: "videojs" */ -/* webpackPreload: true */ -'video.js/dist/video-js.css'); +import( + /* webpackChunkName: "videojs" */ + /* webpackPreload: true */ + 'video.js/dist/video-js.css' +); type Props = { source: { @@ -39,11 +41,16 @@ class AudioVideoViewer extends React.PureComponent { sources, }; - import(/* webpackChunkName: "videojs" */ - /* webpackMode: "lazy" */ - /* webpackPreload: true */ - 'video.js').then(videojs => { - this.player = videojs.default(this.videoNode, videoJsOptions, () => {}); + import( + /* webpackChunkName: "videojs" */ + /* webpackMode: "lazy" */ + /* webpackPreload: true */ + 'video.js' + ).then(videojs => { + if (videojs.__esModule) { + videojs = videojs.default; + } + this.player = videojs(this.videoNode, videoJsOptions, () => {}); }); } diff --git a/src/ui/constants/icons.js b/src/ui/constants/icons.js index a01724211..895c7b594 100644 --- a/src/ui/constants/icons.js +++ b/src/ui/constants/icons.js @@ -53,3 +53,13 @@ export const TRANSACTIONS = 'FileText'; export const LBRY = 'Lbry'; export const SEND = 'Send'; export const DISCOVER = 'Compass'; +export const VISUALIZER_ON = 'Eye'; +export const VISUALIZER_OFF = 'EyeOff'; +export const MUSIC_DETAILS_ON = 'AlignLeft'; +export const MUSIC_DETAILS_OFF = 'AlignLeft'; +export const MUSIC_ART_ON = 'Image'; +export const MUSIC_ART_OFF = 'Image'; +export const MUSIC_ALBUM = 'Disc'; +export const MUSIC_ARTIST = 'Mic'; +export const MUSIC_SONG = 'Music'; +export const MUSIC_EQUALIZER = 'Sliders'; diff --git a/src/ui/index.jsx b/src/ui/index.jsx index 3c68037ee..f0a16868e 100644 --- a/src/ui/index.jsx +++ b/src/ui/index.jsx @@ -21,7 +21,11 @@ import { import { Lbry, doToast, isURIValid, setSearchApi } from 'lbry-redux'; import { doDownloadLanguages, doUpdateIsNightAsync } from 'redux/actions/settings'; import { doAuthenticate, Lbryio, rewards, doBlackListedOutpointsSubscribe } from 'lbryinc'; -import 'scss/all.scss'; +import( + /* webpackChunkName: "styles" */ + /* webpackPrefetch: true */ + 'scss/all.scss' +); import { store, history } from 'store'; import pjson from 'package.json'; import app from './app'; diff --git a/src/ui/modal/modalPhoneCollection/view.jsx b/src/ui/modal/modalPhoneCollection/view.jsx index c18cdb738..722abfdae 100644 --- a/src/ui/modal/modalPhoneCollection/view.jsx +++ b/src/ui/modal/modalPhoneCollection/view.jsx @@ -1,11 +1,15 @@ // @flow -import React from 'react'; +import React, { Suspense } from 'react'; import { Modal } from 'modal/modal'; import Button from 'component/button'; -import UserPhoneNew from 'component/userPhoneNew'; import UserPhoneVerify from 'component/userPhoneVerify'; import { withRouter } from 'react-router-dom'; +const LazyUserPhoneNew = React.lazy(() => import( + /* webpackChunkName: "userPhoneNew" */ + 'component/userPhoneNew' +)); + type Props = { phone: ?number, user: { @@ -31,7 +35,11 @@ class ModalPhoneCollection extends React.PureComponent { const cancelButton =