Merge branch 'master' into smart-links
This commit is contained in:
commit
05e4f87707
23 changed files with 243 additions and 242 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
|
## [0.33.0] - [Unreleased]
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Channel page styling ([#2520](https://github.com/lbryio/lbry-desktop/pull/2520))
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Comic book reader ([#2484](https://github.com/lbryio/lbry-desktop/pull/2484))
|
||||||
|
- Base functionality for more language support ([#2495](https://github.com/lbryio/lbry-desktop/pull2495))
|
||||||
|
- Add easy thumbnail selector for videos ([#2492](https://github.com/lbryio/lbry-desktop/pull2492))
|
||||||
|
|
||||||
## [0.32.2] - [2019-5-20]
|
## [0.32.2] - [2019-5-20]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -24,14 +24,13 @@ const downloadDaemon = targetPlatform =>
|
||||||
const daemonFilePath = path.join(daemonDir, daemonFileName);
|
const daemonFilePath = path.join(daemonDir, daemonFileName);
|
||||||
const daemonVersionPath = path.join(__dirname, 'daemon.ver');
|
const daemonVersionPath = path.join(__dirname, 'daemon.ver');
|
||||||
const tmpZipPath = path.join(__dirname, '..', 'dist', 'daemon.zip');
|
const tmpZipPath = path.join(__dirname, '..', 'dist', 'daemon.zip');
|
||||||
const daemonURL = daemonURLTemplate
|
const daemonURL = daemonURLTemplate.replace(/DAEMONVER/g, daemonVersion).replace(/OSNAME/g, daemonPlatform);
|
||||||
.replace(/DAEMONVER/g, daemonVersion)
|
|
||||||
.replace(/OSNAME/g, daemonPlatform);
|
|
||||||
|
|
||||||
// If a daemon and daemon.ver exists, check to see if it matches the current daemon version
|
// If a daemon and daemon.ver exists, check to see if it matches the current daemon version
|
||||||
const hasDaemonDownloaded = fs.existsSync(daemonFilePath);
|
const hasDaemonDownloaded = fs.existsSync(daemonFilePath);
|
||||||
const hasDaemonVersion = fs.existsSync(daemonVersionPath);
|
const hasDaemonVersion = fs.existsSync(daemonVersionPath);
|
||||||
let downloadedDaemonVersion;
|
let downloadedDaemonVersion;
|
||||||
|
|
||||||
if (hasDaemonVersion) {
|
if (hasDaemonVersion) {
|
||||||
downloadedDaemonVersion = fs.readFileSync(daemonVersionPath, 'utf8');
|
downloadedDaemonVersion = fs.readFileSync(daemonVersionPath, 'utf8');
|
||||||
}
|
}
|
||||||
|
@ -65,6 +64,7 @@ const downloadDaemon = targetPlatform =>
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.then(() => del(`${daemonFilePath}*`))
|
.then(() => del(`${daemonFilePath}*`))
|
||||||
|
.then()
|
||||||
.then(() =>
|
.then(() =>
|
||||||
decompress(tmpZipPath, daemonDir, {
|
decompress(tmpZipPath, daemonDir, {
|
||||||
filter: file => path.basename(file.path) === daemonFileName,
|
filter: file => path.basename(file.path) === daemonFileName,
|
||||||
|
@ -80,9 +80,7 @@ const downloadDaemon = targetPlatform =>
|
||||||
resolve('Done');
|
resolve('Done');
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error(
|
console.error(`\x1b[31merror\x1b[0m Daemon download failed due to: \x1b[35m${error}\x1b[0m`);
|
||||||
`\x1b[31merror\x1b[0m Daemon download failed due to: \x1b[35m${error}\x1b[0m`
|
|
||||||
);
|
|
||||||
reject(error);
|
reject(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "LBRY",
|
"name": "LBRY",
|
||||||
"version": "0.32.2",
|
"version": "0.33.0",
|
||||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"lbry"
|
"lbry"
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
"flow-defs": "flow-typed install",
|
"flow-defs": "flow-typed install",
|
||||||
"precommit": "lint-staged",
|
"precommit": "lint-staged",
|
||||||
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
|
"preinstall": "yarn cache clean lbry-redux && yarn cache clean lbryinc",
|
||||||
"postinstall": "electron-builder install-app-deps && node build/downloadDaemon.js"
|
"postinstall": "electron-builder install-app-deps && node ./build/downloadDaemon.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"electron-dl": "^1.11.0",
|
"electron-dl": "^1.11.0",
|
||||||
|
@ -81,6 +81,7 @@
|
||||||
"copy-webpack-plugin": "^4.6.0",
|
"copy-webpack-plugin": "^4.6.0",
|
||||||
"country-data": "^0.0.31",
|
"country-data": "^0.0.31",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
|
"css-doodle": "^0.7.1",
|
||||||
"css-loader": "^2.1.0",
|
"css-loader": "^2.1.0",
|
||||||
"cssnano": "^4.1.10",
|
"cssnano": "^4.1.10",
|
||||||
"dat.gui": "^0.7.2",
|
"dat.gui": "^0.7.2",
|
||||||
|
@ -193,7 +194,7 @@
|
||||||
"yarn": "^1.3"
|
"yarn": "^1.3"
|
||||||
},
|
},
|
||||||
"lbrySettings": {
|
"lbrySettings": {
|
||||||
"lbrynetDaemonVersion": "0.37.1",
|
"lbrynetDaemonVersion": "0.37.2",
|
||||||
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
|
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
|
||||||
"lbrynetDaemonDir": "static/daemon",
|
"lbrynetDaemonDir": "static/daemon",
|
||||||
"lbrynetDaemonFileName": "lbrynet"
|
"lbrynetDaemonFileName": "lbrynet"
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default appState => {
|
||||||
});
|
});
|
||||||
|
|
||||||
const windowConfiguration = {
|
const windowConfiguration = {
|
||||||
backgroundColor: '#2f9176',
|
backgroundColor: '#270f34', // Located in src/scss/init/_vars.scss `--color-background`
|
||||||
minWidth: 950,
|
minWidth: 950,
|
||||||
minHeight: 600,
|
minHeight: 600,
|
||||||
autoHideMenuBar: true,
|
autoHideMenuBar: true,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
|
import MarkdownPreview from 'component/common/markdown-preview';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
description: ?string,
|
description: ?string,
|
||||||
|
@ -7,6 +8,15 @@ type Props = {
|
||||||
website: ?string,
|
website: ?string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const formatEmail = (email: string) => {
|
||||||
|
if (email) {
|
||||||
|
const protocolRegex = new RegExp('^mailto:', 'i');
|
||||||
|
const protocol = protocolRegex.exec(email);
|
||||||
|
return protocol ? email : `mailto:${email}`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
function ChannelContent(props: Props) {
|
function ChannelContent(props: Props) {
|
||||||
const { description, email, website } = props;
|
const { description, email, website } = props;
|
||||||
const showAbout = description || email || website;
|
const showAbout = description || email || website;
|
||||||
|
@ -16,17 +26,25 @@ function ChannelContent(props: Props) {
|
||||||
{!showAbout && <h2 className="empty">{__('Nothing here yet')}</h2>}
|
{!showAbout && <h2 className="empty">{__('Nothing here yet')}</h2>}
|
||||||
{showAbout && (
|
{showAbout && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
{description && <div className="media__info-text">{description}</div>}
|
{description && (
|
||||||
|
<div className="media__info-text media__info-text--small">
|
||||||
|
<MarkdownPreview content={description} promptLinks />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{email && (
|
{email && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="media__info-title">{__('Contact')}</div>
|
<div className="media__info-title">{__('Contact')}</div>
|
||||||
<div className="media__info-text">{email}</div>
|
<div className="media__info-text">
|
||||||
|
<MarkdownPreview content={formatEmail(email)} promptLinks />
|
||||||
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{website && (
|
{website && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<div className="media__info-title">{__('Site')}</div>
|
<div className="media__info-title">{__('Site')}</div>
|
||||||
<div className="media__info-text">{website}</div>
|
<div className="media__info-text">
|
||||||
|
<MarkdownPreview content={website} promptLinks />
|
||||||
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
5
src/ui/component/doodle/index.js
Normal file
5
src/ui/component/doodle/index.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
/* eslint react/display-name: 0 */
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default ({ rule = '' }) => <css-doodle use="var(--rule)">{rule}</css-doodle>;
|
|
@ -5,6 +5,7 @@ import Yrbl from 'component/yrbl';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import Native from 'native';
|
import Native from 'native';
|
||||||
|
import { Lbry } from 'lbry-redux';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
children: React.Node,
|
children: React.Node,
|
||||||
|
@ -30,20 +31,22 @@ class ErrorBoundary extends React.Component<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error: { stack: string }) {
|
componentDidCatch(error: { stack: string }) {
|
||||||
let errorMessage = '\n';
|
let errorMessage = 'Uncaught error\n';
|
||||||
|
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
errorMessage += 'lbry.tv error\n';
|
errorMessage += 'lbry.tv\n';
|
||||||
errorMessage += window.location.pathname + window.location.search;
|
errorMessage += window.location.pathname + window.location.search;
|
||||||
this.log(errorMessage);
|
this.log(errorMessage);
|
||||||
// @endif
|
// @endif
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
Native.getAppVersionInfo().then(({ localVersion }) => {
|
Native.getAppVersionInfo().then(({ localVersion }) => {
|
||||||
errorMessage += `version: ${localVersion}\n`;
|
Lbry.version().then(({ lbrynet_version: sdkVersion }) => {
|
||||||
errorMessage += `page: ${window.location.href.split('.html')[1]}\n`;
|
errorMessage += `app version: ${localVersion}\n`;
|
||||||
errorMessage += `${error.stack}`;
|
errorMessage += `sdk version: ${sdkVersion}\n`;
|
||||||
|
errorMessage += `page: ${window.location.href.split('.html')[1]}\n`;
|
||||||
this.log(errorMessage);
|
errorMessage += `${error.stack}`;
|
||||||
|
this.log(errorMessage);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
// @endif
|
// @endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,12 +24,12 @@ class ExternalLink extends React.PureComponent<Props> {
|
||||||
createLink() {
|
createLink() {
|
||||||
const { href, title, children, openModal } = this.props;
|
const { href, title, children, openModal } = this.props;
|
||||||
// Regex for url protocol
|
// Regex for url protocol
|
||||||
const protocolRegex = new RegExp('^(https?|lbry)+:', 'i');
|
const protocolRegex = new RegExp('^(https?|lbry|mailto)+:', 'i');
|
||||||
const protocol = href ? protocolRegex.exec(href) : null;
|
const protocol = href ? protocolRegex.exec(href) : null;
|
||||||
// Return plain text if no valid url
|
// Return plain text if no valid url
|
||||||
let element = <span>{children}</span>;
|
let element = <span>{children}</span>;
|
||||||
// Return external link if protocol is http or https
|
// Return external link if protocol is http or https
|
||||||
if (protocol && (protocol[0] === 'http:' || protocol[0] === 'https:')) {
|
if (protocol && (protocol[0] === 'http:' || protocol[0] === 'https:' || protocol[0] === 'mailto:')) {
|
||||||
element = (
|
element = (
|
||||||
<Button
|
<Button
|
||||||
button="link"
|
button="link"
|
||||||
|
|
|
@ -77,10 +77,12 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
// Temp hack to force the video to play if the metadataloaded event was never fired
|
// Temp hack to force the video to play if the metadataloaded event was never fired
|
||||||
// Will be removed with the new video player
|
// Will be removed with the new video player
|
||||||
// Unoptimized MP4s will fail to render.
|
// Unoptimized MP4s will fail to render.
|
||||||
|
// Note: Don't use this for non-playable files
|
||||||
// @if TARGET='app'
|
// @if TARGET='app'
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const { hasMetadata } = this.state;
|
const { hasMetadata } = this.state;
|
||||||
if (!hasMetadata) {
|
const isPlayableType = this.playableType();
|
||||||
|
if (!hasMetadata && isPlayableType) {
|
||||||
this.refreshMetadata();
|
this.refreshMetadata();
|
||||||
this.playMedia();
|
this.playMedia();
|
||||||
}
|
}
|
||||||
|
@ -88,6 +90,20 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
// @endif
|
// @endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps: Props) {
|
||||||
|
const { fileSource } = this.state;
|
||||||
|
const { downloadCompleted } = this.props;
|
||||||
|
|
||||||
|
// Attemp to render a non-playable file once download is completed
|
||||||
|
if (prevProps.downloadCompleted !== downloadCompleted) {
|
||||||
|
const isFileType = this.isSupportedFile();
|
||||||
|
|
||||||
|
if (isFileType && !fileSource && downloadCompleted) {
|
||||||
|
this.playMedia();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
const mediaElement = this.mediaContainer.current.children[0];
|
const mediaElement = this.mediaContainer.current.children[0];
|
||||||
|
|
||||||
|
@ -139,8 +155,10 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Render custom viewer: FileRender
|
// Render custom viewer: FileRender
|
||||||
if (this.isSupportedFile() && downloadCompleted) {
|
if (this.isSupportedFile()) {
|
||||||
this.renderFile();
|
if (downloadCompleted) {
|
||||||
|
this.renderFile();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Render default viewer: render-media (video, audio, img, iframe)
|
// Render default viewer: render-media (video, audio, img, iframe)
|
||||||
const currentMediaContainer = this.mediaContainer.current;
|
const currentMediaContainer = this.mediaContainer.current;
|
||||||
|
@ -256,9 +274,7 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
isRenderMediaSupported() {
|
isRenderMediaSupported() {
|
||||||
// Files supported by render-media
|
// Files supported by render-media
|
||||||
const { contentType } = this.props;
|
const { contentType } = this.props;
|
||||||
return (
|
return Object.values(player.mime).indexOf(contentType) !== -1;
|
||||||
Object.values(player.mime).indexOf(contentType) !== -1 || MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isSupportedFile() {
|
isSupportedFile() {
|
||||||
|
@ -274,33 +290,36 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
if (MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1) {
|
if (MediaPlayer.SANDBOX_TYPES.indexOf(contentType) > -1) {
|
||||||
const outpoint = `${claim.txid}:${claim.nout}`;
|
const outpoint = `${claim.txid}:${claim.nout}`;
|
||||||
|
// Fetch unpacked url
|
||||||
return fetch(`${MediaPlayer.SANDBOX_SET_BASE_URL}${outpoint}`)
|
fetch(`${MediaPlayer.SANDBOX_SET_BASE_URL}${outpoint}`)
|
||||||
.then(res => res.text())
|
.then(res => res.text())
|
||||||
.then(url => {
|
.then(url => {
|
||||||
const fileSource = { url: `${MediaPlayer.SANDBOX_CONTENT_BASE_URL}${url}` };
|
const fileSource = { url: `${MediaPlayer.SANDBOX_CONTENT_BASE_URL}${url}` };
|
||||||
return this.setState({ fileSource });
|
this.setState({ fileSource });
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
// File to render
|
||||||
|
const fileSource = {
|
||||||
|
fileName,
|
||||||
|
contentType,
|
||||||
|
downloadPath,
|
||||||
|
fileType: path.extname(fileName).substring(1),
|
||||||
|
// Readable stream from file
|
||||||
|
// @if TARGET='app'
|
||||||
|
stream: opts => fs.createReadStream(downloadPath, opts),
|
||||||
|
// @endif
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
this.setState({ fileSource });
|
||||||
}
|
}
|
||||||
|
|
||||||
// File to render
|
|
||||||
const fileSource = {
|
|
||||||
fileName,
|
|
||||||
contentType,
|
|
||||||
downloadPath,
|
|
||||||
fileType: path.extname(fileName).substring(1),
|
|
||||||
// Readable stream from file
|
|
||||||
// @if TARGET='app'
|
|
||||||
stream: opts => fs.createReadStream(downloadPath, opts),
|
|
||||||
// @endif
|
|
||||||
};
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
this.setState({ fileSource });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
showLoadingScreen(isFileType: boolean, isPlayableType: boolean) {
|
showLoadingScreen(isFileType: boolean, isPlayableType: boolean) {
|
||||||
const { mediaType, contentType } = this.props;
|
const { mediaType } = this.props;
|
||||||
const { unplayable, fileSource, hasMetadata } = this.state;
|
const { unplayable, fileSource, hasMetadata } = this.state;
|
||||||
|
|
||||||
if (IS_WEB && ['audio', 'video'].indexOf(mediaType) === -1) {
|
if (IS_WEB && ['audio', 'video'].indexOf(mediaType) === -1) {
|
||||||
|
@ -325,10 +344,7 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
// Files
|
// Files
|
||||||
const isLoadingFile = !fileSource && isFileType;
|
const isLoadingFile = !fileSource && isFileType;
|
||||||
const isLbryPackage = /application\/x(-ext)?-lbry$/.test(contentType);
|
const isUnsupported = !this.isRenderMediaSupported() && !isFileType && !isPlayableType;
|
||||||
const isUnsupported =
|
|
||||||
(mediaType === 'application' && !isLbryPackage) ||
|
|
||||||
(!this.isRenderMediaSupported() && !isFileType && !isPlayableType);
|
|
||||||
// Media (audio, video)
|
// Media (audio, video)
|
||||||
const isUnplayable = isPlayableType && unplayable;
|
const isUnplayable = isPlayableType && unplayable;
|
||||||
const isLoadingMetadata = isPlayableType && (!hasMetadata && !unplayable);
|
const isLoadingMetadata = isPlayableType && (!hasMetadata && !unplayable);
|
||||||
|
@ -341,8 +357,6 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
// Show unsupported error message
|
// Show unsupported error message
|
||||||
} else if (isUnsupported || isUnplayable) {
|
} else if (isUnsupported || isUnplayable) {
|
||||||
loader.loadingStatus = isUnsupported ? unsupportedMessage : unplayableMessage;
|
loader.loadingStatus = isUnsupported ? unsupportedMessage : unplayableMessage;
|
||||||
} else if (isLbryPackage && !isLoadingFile) {
|
|
||||||
loader.loadingStatus = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return loader;
|
return loader;
|
||||||
|
@ -352,8 +366,7 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
||||||
const { mediaType, claim } = this.props;
|
const { mediaType, claim } = this.props;
|
||||||
const { fileSource } = this.state;
|
const { fileSource } = this.state;
|
||||||
const isFileType = this.isSupportedFile();
|
const isFileType = this.isSupportedFile();
|
||||||
|
const isFileReady = fileSource !== null && isFileType;
|
||||||
const isFileReady = fileSource && isFileType;
|
|
||||||
const isPlayableType = this.playableType();
|
const isPlayableType = this.playableType();
|
||||||
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
|
const { isLoading, loadingStatus } = this.showLoadingScreen(isFileType, isPlayableType);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ export default function AppRouter() {
|
||||||
<Scroll>
|
<Scroll>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" exact component={DiscoverPage} />
|
<Route path="/" exact component={DiscoverPage} />
|
||||||
|
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
|
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
|
||||||
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
|
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
|
||||||
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
|
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
|
||||||
|
|
|
@ -1,78 +0,0 @@
|
||||||
// @flow
|
|
||||||
import * as ICONS from 'constants/icons';
|
|
||||||
import React, { Fragment } from 'react';
|
|
||||||
import Icon from 'component/common/icon';
|
|
||||||
import Spinner from 'component/spinner';
|
|
||||||
import Button from 'component/button';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
message: string,
|
|
||||||
details: ?string,
|
|
||||||
isWarning: boolean,
|
|
||||||
error: boolean,
|
|
||||||
};
|
|
||||||
|
|
||||||
class LoadScreen extends React.PureComponent<Props> {
|
|
||||||
static defaultProps = {
|
|
||||||
isWarning: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const { details, message, isWarning, error } = this.props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="load-screen">
|
|
||||||
<div>
|
|
||||||
<div className="load-screen__header">
|
|
||||||
<h1 className="load-screen__title">
|
|
||||||
{__('LBRY')}
|
|
||||||
<sup>beta</sup>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
{error ? (
|
|
||||||
<Fragment>
|
|
||||||
<h3>{__('Uh oh. Sean must have messed something up. Try refreshing to fix it.')}</h3>
|
|
||||||
<div className="load-screen__actions">
|
|
||||||
<Button
|
|
||||||
label="Refresh"
|
|
||||||
button="link"
|
|
||||||
className="load-screen__button"
|
|
||||||
onClick={() => window.location.reload()}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="load-screen__help">
|
|
||||||
<p>{__('If you still have issues, your anti-virus software or firewall may be preventing startup.')}</p>
|
|
||||||
<p>
|
|
||||||
{__('Reach out to hello@lbry.com for help, or check out')}{' '}
|
|
||||||
<Button
|
|
||||||
button="link"
|
|
||||||
className="load-screen__button"
|
|
||||||
href="https://lbry.com/faq/startup-troubleshooting"
|
|
||||||
label="this link"
|
|
||||||
/>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
{isWarning ? (
|
|
||||||
<span className="load-screen__message">
|
|
||||||
<Icon size={20} icon={ICONS.ALERT} />
|
|
||||||
{` ${message}`}
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<div className="load-screen__message">{message}</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{details && <div className="load-screen__details">{details}</div>}
|
|
||||||
<Spinner type="splash" />
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LoadScreen;
|
|
|
@ -1,14 +1,15 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
|
||||||
import * as MODALS from 'constants/modal_types';
|
import * as MODALS from 'constants/modal_types';
|
||||||
|
import React from 'react';
|
||||||
import { Lbry } from 'lbry-redux';
|
import { Lbry } from 'lbry-redux';
|
||||||
|
import Button from 'component/button';
|
||||||
import ModalWalletUnlock from 'modal/modalWalletUnlock';
|
import ModalWalletUnlock from 'modal/modalWalletUnlock';
|
||||||
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
|
import ModalIncompatibleDaemon from 'modal/modalIncompatibleDaemon';
|
||||||
import ModalUpgrade from 'modal/modalUpgrade';
|
import ModalUpgrade from 'modal/modalUpgrade';
|
||||||
import ModalDownloading from 'modal/modalDownloading';
|
import ModalDownloading from 'modal/modalDownloading';
|
||||||
import LoadScreen from './internal/load-screen';
|
import 'css-doodle';
|
||||||
|
|
||||||
const ONE_MINUTE = 60 * 1000;
|
const FORTY_FIVE_SECONDS = 45 * 1000;
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
checkDaemonVersion: () => Promise<any>,
|
checkDaemonVersion: () => Promise<any>,
|
||||||
|
@ -87,7 +88,7 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
|
||||||
// If nothing changes after 1 minute, show the error message.
|
// If nothing changes after 1 minute, show the error message.
|
||||||
this.timeout = setTimeout(() => {
|
this.timeout = setTimeout(() => {
|
||||||
this.setState({ error: true });
|
this.setState({ error: true });
|
||||||
}, ONE_MINUTE);
|
}, FORTY_FIVE_SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStatus() {
|
updateStatus() {
|
||||||
|
@ -206,16 +207,66 @@ export default class SplashScreen extends React.PureComponent<Props, State> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { message, details, error } = this.state;
|
const { error } = this.state;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<div className="splash">
|
||||||
<LoadScreen message={message} details={details} error={error} />
|
<css-doodle>
|
||||||
|
{`
|
||||||
|
--color: @p(var(--lbry-teal-1), var(--lbry-orange-1), var(--lbry-cyan-3), var(--lbry-pink-5));
|
||||||
|
:doodle {
|
||||||
|
@grid: 30x1 / 18vmin;
|
||||||
|
--deg: @p(-180deg, 180deg);
|
||||||
|
}
|
||||||
|
:container {
|
||||||
|
perspective: 30vmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@place-cell: center;
|
||||||
|
@size: 100%;
|
||||||
|
|
||||||
|
box-shadow: @m2(0 0 50px var(--color));
|
||||||
|
will-change: transform, opacity;
|
||||||
|
animation: scale-up 12s linear infinite;
|
||||||
|
animation-delay: calc(-12s / @size() * @i());
|
||||||
|
|
||||||
|
@keyframes scale-up {
|
||||||
|
0%, 95.01%, 100% {
|
||||||
|
transform: translateZ(0) rotate(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
95% {
|
||||||
|
transform:
|
||||||
|
translateZ(35vmin) rotateZ(@var(--deg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
`}
|
||||||
|
</css-doodle>
|
||||||
|
<h1 className="splash__title">LBRY</h1>
|
||||||
|
{error && (
|
||||||
|
<div className="splash__error card card--section">
|
||||||
|
<h3>{__('Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.')}</h3>
|
||||||
|
<div className="card__actions--top-space card__actions--center">
|
||||||
|
<Button button="primary" label={__('Refresh')} onClick={() => window.location.reload()} />
|
||||||
|
</div>
|
||||||
|
<div className="help">
|
||||||
|
<p>{__('If you still have issues, your anti-virus software or firewall may be preventing startup.')}</p>
|
||||||
|
<p>
|
||||||
|
{__('Reach out to hello@lbry.com for help, or check out')}{' '}
|
||||||
|
<Button button="link" href="https://lbry.com/faq/startup-troubleshooting" label="this link" />.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{/* Temp hack: don't show any modals on splash screen daemon is running;
|
{/* Temp hack: don't show any modals on splash screen daemon is running;
|
||||||
daemon doesn't let you quit during startup, so the "Quit" buttons
|
daemon doesn't let you quit during startup, so the "Quit" buttons
|
||||||
in the modals won't work. */}
|
in the modals won't work. */}
|
||||||
{this.renderModals()}
|
{this.renderModals()}
|
||||||
</React.Fragment>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,13 +37,17 @@ class TransactionListItem extends React.PureComponent<Props> {
|
||||||
this.props.revokeClaim(txid, nout);
|
this.props.revokeClaim(txid, nout);
|
||||||
}
|
}
|
||||||
|
|
||||||
capitalize = (string: string) => string.charAt(0).toUpperCase() + string.slice(1);
|
capitalize = (string: ?string) => string && string.charAt(0).toUpperCase() + string.slice(1);
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { reward, transaction, isRevokeable } = this.props;
|
const { reward, transaction, isRevokeable } = this.props;
|
||||||
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
const { amount, claim_id: claimId, claim_name: name, date, fee, txid, type } = transaction;
|
||||||
// Ensure the claim name is valid
|
|
||||||
const { claimName } = parseURI(name);
|
// Ensure the claim name exists and is valid
|
||||||
|
let claimName = name;
|
||||||
|
if (claimName) {
|
||||||
|
({ claimName } = parseURI(name));
|
||||||
|
}
|
||||||
|
|
||||||
const dateFormat = {
|
const dateFormat = {
|
||||||
month: 'short',
|
month: 'short',
|
||||||
|
|
|
@ -10,8 +10,14 @@ type Props = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let workerPath = 'webworkers/worker-bundle.js';
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
// Don't add a leading slash in production because electron treats it as an absolute path
|
||||||
|
workerPath = `/${workerPath}`;
|
||||||
|
}
|
||||||
|
|
||||||
const opts = {
|
const opts = {
|
||||||
workerPath: '/webworkers/worker-bundle.js',
|
workerPath,
|
||||||
allowFullScreen: false,
|
allowFullScreen: false,
|
||||||
autoHideControls: true,
|
autoHideControls: true,
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@ class AudioVideoViewer extends React.PureComponent<Props> {
|
||||||
// Will need to be changed to include time to start
|
// Will need to be changed to include time to start
|
||||||
analytics.apiLogView(`${name}#${claimId}`, `${txid}:${nout}`, claimId);
|
analytics.apiLogView(`${name}#${claimId}`, `${txid}:${nout}`, claimId);
|
||||||
|
|
||||||
const path = `https://api.piratebay.com/content/claims/${claim.name}/${claim.claim_id}/stream.mp4`;
|
const path = `https://api.lbry.tv/content/claims/${claim.name}/${claim.claim_id}/stream.mp4`;
|
||||||
const sources = [
|
const sources = [
|
||||||
{
|
{
|
||||||
src: path,
|
src: path,
|
||||||
|
|
|
@ -44,7 +44,7 @@ if (process.env.SEARCH_API_URL) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// @if TARGET='web'
|
// @if TARGET='web'
|
||||||
const SDK_API_URL = process.env.SDK_API_URL || 'https://api.piratebay.com/api/proxy';
|
const SDK_API_URL = process.env.SDK_API_URL || 'https://api.lbry.tv/api/proxy';
|
||||||
Lbry.setDaemonConnectionString(SDK_API_URL);
|
Lbry.setDaemonConnectionString(SDK_API_URL);
|
||||||
// @endif
|
// @endif
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,6 @@
|
||||||
@import 'component/header';
|
@import 'component/header';
|
||||||
@import 'component/icon';
|
@import 'component/icon';
|
||||||
@import 'component/item-list';
|
@import 'component/item-list';
|
||||||
@import 'component/load-screen';
|
|
||||||
@import 'component/main';
|
@import 'component/main';
|
||||||
@import 'component/markdown-editor';
|
@import 'component/markdown-editor';
|
||||||
@import 'component/markdown-preview';
|
@import 'component/markdown-preview';
|
||||||
|
@ -38,6 +37,7 @@
|
||||||
@import 'component/search';
|
@import 'component/search';
|
||||||
@import 'component/snack-bar';
|
@import 'component/snack-bar';
|
||||||
@import 'component/spinner';
|
@import 'component/spinner';
|
||||||
|
@import 'component/splash';
|
||||||
@import 'component/subscriptions';
|
@import 'component/subscriptions';
|
||||||
@import 'component/syntax-highlighter';
|
@import 'component/syntax-highlighter';
|
||||||
@import 'component/table';
|
@import 'component/table';
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
.load-screen {
|
|
||||||
min-width: 100vw;
|
|
||||||
min-height: 100vh;
|
|
||||||
|
|
||||||
align-items: center;
|
|
||||||
background-image: linear-gradient(to right, $lbry-teal-5, $lbry-cyan-5 100%);
|
|
||||||
background-size: cover;
|
|
||||||
color: $lbry-white;
|
|
||||||
cursor: default;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
.spinner {
|
|
||||||
margin-right: auto;
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__button {
|
|
||||||
transition: none;
|
|
||||||
color: $lbry-white;
|
|
||||||
border-bottom: 1px solid $lbry-white;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
border-bottom: 1px solid $lbry-blue-1;
|
|
||||||
color: $lbry-blue-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__details {
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1;
|
|
||||||
max-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: var(--spacing-vertical-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__help {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
padding-top: var(--spacing-vertical-large);
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__message {
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 800;
|
|
||||||
line-height: 1;
|
|
||||||
margin-bottom: var(--spacing-vertical-medium);
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__title {
|
|
||||||
font-size: 60px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 1;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
sup {
|
|
||||||
margin-left: -var(--spacing-vertical-tiny);
|
|
||||||
padding: var(--spacing-vertical-miniscule) var(--spacing-vertical-small);
|
|
||||||
|
|
||||||
background-color: rgba($lbry-white, 0.3);
|
|
||||||
border-radius: 0.2rem;
|
|
||||||
color: $lbry-white;
|
|
||||||
font-size: 0.6rem;
|
|
||||||
font-weight: 400;
|
|
||||||
letter-spacing: 0.1rem;
|
|
||||||
line-height: 1;
|
|
||||||
position: absolute;
|
|
||||||
text-transform: uppercase;
|
|
||||||
top: 2.15rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen--help {
|
|
||||||
font-size: 14px;
|
|
||||||
padding-top: $spacing-vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.load-screen__actions {
|
|
||||||
font-size: 1.2em;
|
|
||||||
margin-top: var(--spacing-vertical-medium);
|
|
||||||
}
|
|
|
@ -260,12 +260,16 @@
|
||||||
|
|
||||||
.media__info-text {
|
.media__info-text {
|
||||||
font-size: 1.15rem;
|
font-size: 1.15rem;
|
||||||
word-break: break-all;
|
word-break: break-word;
|
||||||
|
|
||||||
&:not(:last-of-type) {
|
&:not(:last-of-type) {
|
||||||
margin-bottom: var(--spacing-vertical-large);
|
margin-bottom: var(--spacing-vertical-large);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.media__info-text--small {
|
||||||
|
max-width: 50rem;
|
||||||
|
}
|
||||||
|
|
||||||
&.media__info-text--center {
|
&.media__info-text--center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
|
@ -90,8 +90,7 @@
|
||||||
|
|
||||||
.error-modal__content {
|
.error-modal__content {
|
||||||
display: flex;
|
display: flex;
|
||||||
padding: 0 var(--spacing-vertical-medium) var(--spacing-vertical-medium)
|
padding: 0 var(--spacing-vertical-medium) var(--spacing-vertical-medium) var(--spacing-vertical-medium);
|
||||||
var(--spacing-vertical-medium);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-modal__warning-symbol {
|
.error-modal__warning-symbol {
|
||||||
|
|
46
src/ui/scss/component/_splash.scss
Normal file
46
src/ui/scss/component/_splash.scss
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
.splash {
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--color-background);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
// .splash__actions {
|
||||||
|
// margin-top: var(--spacing-vertical-medium);
|
||||||
|
// align-items: center;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.splash__button {
|
||||||
|
border-bottom: 1px solid $lbry-white;
|
||||||
|
color: $lbry-white;
|
||||||
|
transition: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-bottom: 1px solid $lbry-blue-1;
|
||||||
|
color: $lbry-blue-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash__details {
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash__title {
|
||||||
|
position: absolute;
|
||||||
|
font-size: 40px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $lbry-white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.splash__error {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: 25rem;
|
||||||
|
box-shadow: var(--card-box-shadow) $lbry-gray-5;
|
||||||
|
}
|
|
@ -9,7 +9,6 @@ $large-breakpoint: 1921px;
|
||||||
// Width & spacing
|
// Width & spacing
|
||||||
--side-nav-width: 180px;
|
--side-nav-width: 180px;
|
||||||
--font-size-subtext-multiple: 0.92;
|
--font-size-subtext-multiple: 0.92;
|
||||||
|
|
||||||
--spacing-vertical-miniscule: calc(2rem / 5);
|
--spacing-vertical-miniscule: calc(2rem / 5);
|
||||||
--spacing-vertical-tiny: calc(2rem / 4);
|
--spacing-vertical-tiny: calc(2rem / 4);
|
||||||
--spacing-vertical-small: calc(2rem / 3);
|
--spacing-vertical-small: calc(2rem / 3);
|
||||||
|
@ -17,15 +16,15 @@ $large-breakpoint: 1921px;
|
||||||
--spacing-vertical-large: 2rem;
|
--spacing-vertical-large: 2rem;
|
||||||
--spacing-vertical-xlarge: 3rem;
|
--spacing-vertical-xlarge: 3rem;
|
||||||
--spacing-main-padding: var(--spacing-vertical-xlarge);
|
--spacing-main-padding: var(--spacing-vertical-xlarge);
|
||||||
|
|
||||||
--file-page-max-width: 1787px;
|
--file-page-max-width: 1787px;
|
||||||
--file-max-height: 788px;
|
--file-max-height: 788px;
|
||||||
--file-max-width: 1400px;
|
--file-max-width: 1400px;
|
||||||
|
|
||||||
--video-aspect-ratio: 56.25%; // 9 x 16
|
--video-aspect-ratio: 56.25%; // 9 x 16
|
||||||
|
|
||||||
--channel-thumbnail-size: 10rem;
|
--channel-thumbnail-size: 10rem;
|
||||||
|
|
||||||
|
// Color
|
||||||
|
--color-background: #270f34;
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
--text-max-width: 660px;
|
--text-max-width: 660px;
|
||||||
--text-link-padding: 4px;
|
--text-link-padding: 4px;
|
||||||
|
|
|
@ -3001,6 +3001,11 @@ css-declaration-sorter@^4.0.1:
|
||||||
postcss "^7.0.1"
|
postcss "^7.0.1"
|
||||||
timsort "^0.3.0"
|
timsort "^0.3.0"
|
||||||
|
|
||||||
|
css-doodle@^0.7.1:
|
||||||
|
version "0.7.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/css-doodle/-/css-doodle-0.7.1.tgz#477466310df6554ec5182349745194ba37415670"
|
||||||
|
integrity sha512-TRs+7YLgP/yc0EdrL5Us2GRHbhIgf4GsQWAt5WdRxr5pwwpWl8Q9jZle431Z/5u5C29jiEoItjIaKE84xFh+kA==
|
||||||
|
|
||||||
css-hot-loader@^1.4.3:
|
css-hot-loader@^1.4.3:
|
||||||
version "1.4.4"
|
version "1.4.4"
|
||||||
resolved "https://registry.yarnpkg.com/css-hot-loader/-/css-hot-loader-1.4.4.tgz#ae784932cd8b7d092f7f15702af08b3ec9436052"
|
resolved "https://registry.yarnpkg.com/css-hot-loader/-/css-hot-loader-1.4.4.tgz#ae784932cd8b7d092f7f15702af08b3ec9436052"
|
||||||
|
|
Loading…
Add table
Reference in a new issue