diff --git a/package.json b/package.json index 49e40ba03..2f0918b84 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ }, "main": "./dist/electron/main.js", "scripts": { + "analyze": "source-map-explorer --only-mapped dist/electron/webpack/ui*.js --html dist/sourceMap.html", "compile:electron": "node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.electron.config.js", "compile:web": "yarn copyenv && cd web && node --max_old_space_size=4096 ./node_modules/webpack/bin/webpack.js --config webpack.config.js", "compile": "cross-env NODE_ENV=production yarn compile:electron && cross-env NODE_ENV=production yarn compile:web", @@ -54,7 +55,9 @@ "express": "^4.17.1", "if-env": "^1.0.4", "react-datetime-picker": "^3.2.1", + "react-top-loading-bar": "^2.0.1", "remove-markdown": "^0.3.0", + "source-map-explorer": "^2.5.2", "tempy": "^0.6.0", "videojs-logo": "^2.1.4" }, diff --git a/static/img/fileRenderPlaceholder.png b/static/img/fileRenderPlaceholder.png new file mode 100644 index 000000000..12babce33 Binary files /dev/null and b/static/img/fileRenderPlaceholder.png differ diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 8f3244af1..2493db4b0 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -6,31 +6,20 @@ import analytics from 'analytics'; import { buildURI, parseURI } from 'lbry-redux'; import { SIMPLE_SITE } from 'config'; import Router from 'component/router/index'; -import ModalRouter from 'modal/modalRouter'; import ReactModal from 'react-modal'; import { openContextMenu } from 'util/context-menu'; import useKonamiListener from 'util/enhanced-layout'; -import Yrbl from 'component/yrbl'; import FileRenderFloating from 'component/fileRenderFloating'; import { withRouter } from 'react-router'; import usePrevious from 'effects/use-previous'; -import Nag from 'component/common/nag'; import REWARDS from 'rewards'; import usePersistedState from 'effects/use-persisted-state'; -import FileDrop from 'component/fileDrop'; -import NagContinueFirstRun from 'component/nagContinueFirstRun'; import Spinner from 'component/spinner'; -import SyncFatalError from 'component/syncFatalError'; // @if TARGET='app' import useZoom from 'effects/use-zoom'; import useHistoryNav from 'effects/use-history-nav'; // @endif // @if TARGET='web' -import OpenInAppLink from 'web/component/openInAppLink'; -import YoutubeWelcome from 'web/component/youtubeReferralWelcome'; -import NagDegradedPerformance from 'web/component/nag-degraded-performance'; -import NagDataCollection from 'web/component/nag-data-collection'; -import NagNoUser from 'web/component/nag-no-user'; import { useDegradedPerformance, STATUS_OK, @@ -40,6 +29,33 @@ import { } from 'web/effects/use-degraded-performance'; // @endif import LANGUAGE_MIGRATIONS from 'constants/language-migrations'; + +const FileDrop = React.lazy(() => import('component/fileDrop' /* webpackChunkName: "secondary" */)); +const ModalRouter = React.lazy(() => import('modal/modalRouter' /* webpackChunkName: "secondary" */)); +const Nag = React.lazy(() => import('component/common/nag' /* webpackChunkName: "secondary" */)); +const NagContinueFirstRun = React.lazy(() => + import('component/nagContinueFirstRun' /* webpackChunkName: "secondary" */) +); +const OpenInAppLink = React.lazy(() => import('web/component/openInAppLink' /* webpackChunkName: "secondary" */)); + +// @if TARGET='web' +const NagDataCollection = React.lazy(() => + import('web/component/nag-data-collection' /* webpackChunkName: "secondary" */) +); +const NagDegradedPerformance = React.lazy(() => + import('web/component/nag-degraded-performance' /* webpackChunkName: "secondary" */) +); +const NagNoUser = React.lazy(() => import('web/component/nag-no-user' /* webpackChunkName: "nag-no-user" */)); +const YoutubeWelcome = React.lazy(() => + import('web/component/youtubeReferralWelcome' /* webpackChunkName: "secondary" */) +); +// @endif + +const SyncFatalError = React.lazy(() => import('component/syncFatalError' /* webpackChunkName: "syncFatalError" */)); +const Yrbl = React.lazy(() => import('component/yrbl' /* webpackChunkName: "yrbl" */)); + +// **************************************************************************** + export const MAIN_WRAPPER_CLASS = 'main-wrapper'; export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1; @@ -377,11 +393,13 @@ function App(props: Props) { if (syncFatalError) { return ( - + + + ); } @@ -397,42 +415,48 @@ function App(props: Props) { onContextMenu={IS_WEB ? undefined : (e) => openContextMenu(e)} > {IS_WEB && lbryTvApiStatus === STATUS_DOWN ? ( - + + + ) : ( - - + + + + - {isEnhancedLayout && } + + {isEnhancedLayout && } - {/* @if TARGET='app' */} - {showUpgradeButton && ( - setUpgradeNagClosed(true)} - /> - )} - {/* @endif */} + {/* @if TARGET='app' */} + {showUpgradeButton && ( + setUpgradeNagClosed(true)} + /> + )} + {/* @endif */} - {/* @if TARGET='web' */} - - {!SIMPLE_SITE && !shouldHideNag && } - {!shouldHideNag && } - {(lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) && !shouldHideNag && ( - setLbryTvApiStatus(STATUS_OK)} /> - )} - {!SIMPLE_SITE && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && ( - - )} - {user === null && } - {/* @endif */} + {/* @if TARGET='web' */} + + {!SIMPLE_SITE && !shouldHideNag && } + {!shouldHideNag && } + {(lbryTvApiStatus === STATUS_DEGRADED || lbryTvApiStatus === STATUS_FAILING) && !shouldHideNag && ( + setLbryTvApiStatus(STATUS_OK)} /> + )} + {!SIMPLE_SITE && lbryTvApiStatus === STATUS_OK && showAnalyticsNag && !shouldHideNag && ( + + )} + {user === null && } + {/* @endif */} + )} diff --git a/ui/component/claimLink/view.jsx b/ui/component/claimLink/view.jsx index f5e7ee69c..c67a1fd82 100644 --- a/ui/component/claimLink/view.jsx +++ b/ui/component/claimLink/view.jsx @@ -4,7 +4,7 @@ import classnames from 'classnames'; import EmbedPlayButton from 'component/embedPlayButton'; import Button from 'component/button'; import UriIndicator from 'component/uriIndicator'; -import { INLINE_PLAYER_WRAPPER_CLASS } from 'component/fileRenderFloating/view'; +import { INLINE_PLAYER_WRAPPER_CLASS } from 'constants/classnames'; import { SIMPLE_SITE } from 'config'; type Props = { diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index 6a8c0e14a..cb0d6feae 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -18,7 +18,6 @@ import ClaimPreviewTitle from 'component/claimPreviewTitle'; import ClaimPreviewSubtitle from 'component/claimPreviewSubtitle'; import ClaimRepostAuthor from 'component/claimRepostAuthor'; import FileDownloadLink from 'component/fileDownloadLink'; -import AbandonedChannelPreview from 'component/abandonedChannelPreview'; import PublishPending from 'component/publishPending'; import ClaimMenuList from 'component/claimMenuList'; import ClaimPreviewLoading from './claim-preview-loading'; @@ -28,6 +27,10 @@ import { ENABLE_NO_SOURCE_CLAIMS } from 'config'; import Button from 'component/button'; import * as ICONS from 'constants/icons'; +const AbandonedChannelPreview = React.lazy(() => + import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */) +); + type Props = { uri: string, claim: ?Claim, // maybe? @@ -254,7 +257,11 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { } if (!shouldFetch && showUnresolvedClaim && !isResolvingUri && isChannelUri && claim === null) { - return ; + return ( + + + + ); } if (placeholder === 'publish' && !claim && uri.startsWith('lbry://@')) { return null; diff --git a/ui/component/common/form-components/form-field.jsx b/ui/component/common/form-components/form-field.jsx index 68cd85230..83e01f85d 100644 --- a/ui/component/common/form-components/form-field.jsx +++ b/ui/component/common/form-components/form-field.jsx @@ -274,22 +274,24 @@ export class FormField extends React.PureComponent { {...inputProps} />
- {!noEmojis &&
- {QUICK_EMOJIS.map((emoji) => ( -
} + {!noEmojis && ( +
+ {QUICK_EMOJIS.map((emoji) => ( +
+ )} {countInfo}
diff --git a/ui/component/common/tooltip.jsx b/ui/component/common/tooltip.jsx index 1d52bf8db..fd06493ab 100644 --- a/ui/component/common/tooltip.jsx +++ b/ui/component/common/tooltip.jsx @@ -2,7 +2,7 @@ import type { Node } from 'react'; import React from 'react'; import ReachTooltip from '@reach/tooltip'; -import '@reach/tooltip/styles.css'; +// import '@reach/tooltip/styles.css'; --> 'scss/third-party.scss' type Props = { label: string | Node, diff --git a/ui/component/errorBoundary/view.jsx b/ui/component/errorBoundary/view.jsx index 6c064d719..65e090fe8 100644 --- a/ui/component/errorBoundary/view.jsx +++ b/ui/component/errorBoundary/view.jsx @@ -1,18 +1,19 @@ // @flow import type { Node } from 'react'; import React from 'react'; -import Yrbl from 'component/yrbl'; -import Button from 'component/button'; import { withRouter } from 'react-router'; import analytics from 'analytics'; -import I18nMessage from 'component/i18nMessage'; import Native from 'native'; import { Lbry } from 'lbry-redux'; +const Button = React.lazy(() => import('component/button' /* webpackChunkName: "button" */)); +const I18nMessage = React.lazy(() => import('component/i18nMessage' /* webpackChunkName: "i18nMessage" */)); +const Yrbl = React.lazy(() => import('component/yrbl' /* webpackChunkName: "yrbl" */)); + type Props = { children: Node, history: { - replace: string => void, + replace: (string) => void, }, }; @@ -36,7 +37,7 @@ class ErrorBoundary extends React.Component { componentDidCatch(error, errorInfo) { // @if TARGET='web' - analytics.sentryError(error, errorInfo).then(sentryEventId => { + analytics.sentryError(error, errorInfo).then((sentryEventId) => { this.setState({ sentryEventId }); }); // @endif @@ -49,7 +50,7 @@ class ErrorBoundary extends React.Component { errorMessage += `sdk version: ${sdkVersion}\n`; errorMessage += `page: ${window.location.href.split('.html')[1]}\n`; errorMessage += `${error.stack}`; - analytics.error(errorMessage).then(isSharingData => { + analytics.error(errorMessage).then((isSharingData) => { this.setState({ desktopErrorReported: isSharingData }); }); }); @@ -74,27 +75,29 @@ class ErrorBoundary extends React.Component { if (hasError) { return (
- - ), - }} - > - There was an error. Try %refreshing_the_app_link% to fix it. If that doesn't work, try pressing - Ctrl+R/Cmd+R. - - } - /> + + + ), + }} + > + There was an error. Try %refreshing_the_app_link% to fix it. If that doesn't work, try pressing + Ctrl+R/Cmd+R. + + } + /> + {!errorWasReported && (
diff --git a/ui/component/fileRender/view.jsx b/ui/component/fileRender/view.jsx index 55cc914c8..c10f0d189 100644 --- a/ui/component/fileRender/view.jsx +++ b/ui/component/fileRender/view.jsx @@ -3,16 +3,11 @@ import { remote } from 'electron'; import React from 'react'; import classnames from 'classnames'; import * as RENDER_MODES from 'constants/file_render_modes'; -import VideoViewer from 'component/viewers/videoViewer'; -import ImageViewer from 'component/viewers/imageViewer'; -import AppViewer from 'component/viewers/appViewer'; import { withRouter } from 'react-router-dom'; import fs from 'fs'; import analytics from 'analytics'; import DocumentViewer from 'component/viewers/documentViewer'; -import PdfViewer from 'component/viewers/pdfViewer'; -import HtmlViewer from 'component/viewers/htmlViewer'; // @if TARGET='app' // should match @@ -21,6 +16,12 @@ import ComicBookViewer from 'component/viewers/comicBookViewer'; import ThreeViewer from 'component/viewers/threeViewer'; // @endif +const AppViewer = React.lazy(() => import('component/viewers/appViewer' /* webpackChunkName: "appViewer" */)); +const HtmlViewer = React.lazy(() => import('component/viewers/htmlViewer' /* webpackChunkName: "htmlViewer" */)); +const ImageViewer = React.lazy(() => import('component/viewers/imageViewer' /* webpackChunkName: "imageViewer" */)); +const PdfViewer = React.lazy(() => import('component/viewers/pdfViewer' /* webpackChunkName: "pdfViewer" */)); +const VideoViewer = React.lazy(() => import('component/viewers/videoViewer' /* webpackChunkName: "videoViewer" */)); + type Props = { uri: string, streamingUrl: string, @@ -85,17 +86,27 @@ class FileRender extends React.PureComponent { case RENDER_MODES.AUDIO: case RENDER_MODES.VIDEO: return ( - + + + ); case RENDER_MODES.IMAGE: - return ; + return ( + + ; + + ); case RENDER_MODES.HTML: - return ; + return ( + + ; + + ); case RENDER_MODES.DOCUMENT: case RENDER_MODES.MARKDOWN: return ( @@ -115,7 +126,11 @@ class FileRender extends React.PureComponent { case RENDER_MODES.DOCX: return ; case RENDER_MODES.PDF: - return ; + return ( + + ; + + ); case RENDER_MODES.CAD: return ( { /> ); case RENDER_MODES.APPLICATION: - return ; + return ( + + ; + + ); } return null; diff --git a/ui/component/fileRenderFloating/view.jsx b/ui/component/fileRenderFloating/view.jsx index 24c1002bc..81dc6d9ec 100644 --- a/ui/component/fileRenderFloating/view.jsx +++ b/ui/component/fileRenderFloating/view.jsx @@ -1,23 +1,24 @@ // @flow import * as ICONS from 'constants/icons'; import * as RENDER_MODES from 'constants/file_render_modes'; +import { INLINE_PLAYER_WRAPPER_CLASS } from 'constants/classnames'; import React, { useEffect, useState } from 'react'; import Button from 'component/button'; import classnames from 'classnames'; import LoadingScreen from 'component/common/loading-screen'; -import FileRender from 'component/fileRender'; import UriIndicator from 'component/uriIndicator'; import usePersistedState from 'effects/use-persisted-state'; import { PRIMARY_PLAYER_WRAPPER_CLASS } from 'page/file/view'; -import Draggable from 'react-draggable'; import { onFullscreenChange } from 'util/full-screen'; import { useIsMobile } from 'effects/use-screensize'; import debounce from 'util/debounce'; import { useHistory } from 'react-router'; +const Draggable = React.lazy(() => import('react-draggable' /* webpackChunkName: "draggable" */)); +const FileRender = React.lazy(() => import('component/fileRender' /* webpackChunkName: "fileRender" */)); + const IS_DESKTOP_MAC = typeof process === 'object' ? process.platform === 'darwin' : false; const DEBOUNCE_WINDOW_RESIZE_HANDLER_MS = 60; -export const INLINE_PLAYER_WRAPPER_CLASS = 'inline-player__wrapper'; type Props = { isFloating: boolean, @@ -243,72 +244,76 @@ export default function FileRenderFloating(props: Props) { } return ( - -
+
- {isFloating && ( -
+
- -
- )} + )} +
- - + + ); } diff --git a/ui/component/fileRenderInitiator/index.js b/ui/component/fileRenderInitiator/index.js index 8e8865ecc..ad57e20e8 100644 --- a/ui/component/fileRenderInitiator/index.js +++ b/ui/component/fileRenderInitiator/index.js @@ -23,7 +23,7 @@ import FileRenderInitiator from './view'; import { doAnaltyicsPurchaseEvent } from 'redux/actions/app'; const select = (state, props) => ({ - thumbnail: makeSelectThumbnailForUri(props.uri)(state), + claimThumbnail: makeSelectThumbnailForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state), obscurePreview: makeSelectShouldObscurePreview(props.uri)(state), isPlaying: makeSelectIsPlaying(props.uri)(state), diff --git a/ui/component/fileRenderInitiator/view.jsx b/ui/component/fileRenderInitiator/view.jsx index 5034356cd..e3ac0204f 100644 --- a/ui/component/fileRenderInitiator/view.jsx +++ b/ui/component/fileRenderInitiator/view.jsx @@ -9,7 +9,10 @@ import * as PAGES from 'constants/pages'; import * as RENDER_MODES from 'constants/file_render_modes'; import Button from 'component/button'; import isUserTyping from 'util/detect-typing'; +import { getThumbnailCdnUrl } from 'util/thumbnail'; import Nag from 'component/common/nag'; +// $FlowFixMe cannot resolve ... +import FileRenderPlaceholder from 'static/img/fileRenderPlaceholder.png'; const SPACE_BAR_KEYCODE = 32; @@ -23,7 +26,7 @@ type Props = { location: { search: ?string, pathname: string }, obscurePreview: boolean, insufficientCredits: boolean, - thumbnail?: string, + claimThumbnail?: string, autoplay: boolean, hasCostInfo: boolean, costInfo: any, @@ -45,7 +48,7 @@ export default function FileRenderInitiator(props: Props) { insufficientCredits, history, location, - thumbnail, + claimThumbnail, renderMode, hasCostInfo, costInfo, @@ -68,6 +71,29 @@ export default function FileRenderInitiator(props: Props) { const fileStatus = fileInfo && fileInfo.status; const isPlayable = RENDER_MODES.FLOATING_MODES.includes(renderMode); const isText = RENDER_MODES.TEXT_MODES.includes(renderMode); + const [thumbnail, setThumbnail] = React.useState(FileRenderPlaceholder); + const containerRef = React.useRef(); + + React.useEffect(() => { + if (claimThumbnail) { + setTimeout(() => { + let newThumbnail = claimThumbnail; + + // @if TARGET='web' + if ( + containerRef.current && + containerRef.current.parentElement && + containerRef.current.parentElement.offsetWidth + ) { + const dimen = containerRef.current.parentElement.offsetWidth; + newThumbnail = getThumbnailCdnUrl({ thumbnail: newThumbnail, width: dimen, height: dimen }); + } + // @endif + + setThumbnail(newThumbnail); + }, 200); + } + }, []); // eslint-disable-line react-hooks/exhaustive-deps function doAuthRedirect() { history.push(`/$/${PAGES.AUTH}?redirect=${encodeURIComponent(location.pathname)}`); @@ -131,8 +157,9 @@ export default function FileRenderInitiator(props: Props) { return (
import('component/fileRender' /* webpackChunkName: "fileRender" */)); + type Props = { isPlaying: boolean, fileInfo: FileListItem, @@ -69,5 +70,11 @@ export default function FileRenderInline(props: Props) { return null; } - return renderContent ? : ; + return renderContent ? ( + + + + ) : ( + + ); } diff --git a/ui/component/loadingBarOneOff/index.js b/ui/component/loadingBarOneOff/index.js new file mode 100644 index 000000000..88ce5627b --- /dev/null +++ b/ui/component/loadingBarOneOff/index.js @@ -0,0 +1,6 @@ +import { connect } from 'react-redux'; +import LoadingBarOneOff from './view'; + +const select = (state, props) => ({}); + +export default connect(select)(LoadingBarOneOff); diff --git a/ui/component/loadingBarOneOff/view.jsx b/ui/component/loadingBarOneOff/view.jsx new file mode 100644 index 000000000..78fc2b305 --- /dev/null +++ b/ui/component/loadingBarOneOff/view.jsx @@ -0,0 +1,20 @@ +// @flow +import * as React from 'react'; +import LoadingBar from 'react-top-loading-bar'; + +// TODO: Retrieve from CSS? +export const COLOR_LOADING_BAR = '#2bbb90'; + +function LoadingBarOneOff(props: any) { + const loadingBarRef = React.useRef(null); + + React.useEffect(() => { + if (loadingBarRef.current) { + loadingBarRef.current.continuousStart(); + } + }, []); + + return ; +} + +export default LoadingBarOneOff; diff --git a/ui/component/page/view.jsx b/ui/component/page/view.jsx index 99ff67299..0fa2ba090 100644 --- a/ui/component/page/view.jsx +++ b/ui/component/page/view.jsx @@ -4,7 +4,6 @@ import React, { Fragment } from 'react'; import classnames from 'classnames'; import SideNavigation from 'component/sideNavigation'; import Header from 'component/header'; -import Footer from 'web/component/footer'; /* @if TARGET='app' */ import StatusBar from 'component/common/status-bar'; /* @endif */ @@ -13,6 +12,8 @@ import { useHistory } from 'react-router'; import { useIsMobile, useIsMediumScreen } from 'effects/use-screensize'; import { parseURI } from 'lbry-redux'; +const Footer = React.lazy(() => import('web/component/footer' /* webpackChunkName: "secondary" */)); + export const MAIN_CLASS = 'main'; type Props = { children: Node | Array, @@ -126,7 +127,11 @@ function Page(props: Props) { {/* @endif */}
{/* @if TARGET='web' */} - {!noFooter &&