Merge branch 'master' into comicbook-reader

This commit is contained in:
Baltazar Gomez 2019-05-16 00:47:49 -06:00 committed by GitHub
commit 0106ac255b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 648 additions and 505 deletions

View file

@ -19,11 +19,13 @@
"__": true, "__": true,
"__n": true, "__n": true,
"app": true, "app": true,
"IS_WEB": true "IS_WEB": true,
"WEBPACK_PORT": true
}, },
"rules": { "rules": {
"comma-dangle": ["error", "always-multiline"], "comma-dangle": ["error", "always-multiline"],
"handle-callback-err": 0, "handle-callback-err": 0,
"indent": 0,
"jsx-quotes": ["error", "prefer-double"], "jsx-quotes": ["error", "prefer-double"],
"new-cap": 0, "new-cap": 0,
"no-multi-spaces": 0, "no-multi-spaces": 0,

BIN
build/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

13
flow-typed/publish.js vendored
View file

@ -3,8 +3,8 @@
declare type UpdatePublishFormData = { declare type UpdatePublishFormData = {
filePath?: string, filePath?: string,
contentIsFree?: boolean, contentIsFree?: boolean,
price?: { fee?: {
amount: number, amount: string,
currency: string, currency: string,
}, },
title?: string, title?: string,
@ -23,7 +23,7 @@ declare type UpdatePublishFormData = {
licenseUrl?: string, licenseUrl?: string,
licenseType?: string, licenseType?: string,
uri?: string, uri?: string,
replace?: boolean, nsfw: boolean,
}; };
declare type PublishParams = { declare type PublishParams = {
@ -43,12 +43,9 @@ declare type PublishParams = {
license: ?string, license: ?string,
licenseUrl: ?string, licenseUrl: ?string,
fee?: { fee?: {
amount: string,
currency: string, currency: string,
amount: number,
}, },
replace?: boolean, claim: StreamClaim,
// This is bad.
// Will be removed for tags soon
nsfw: boolean, nsfw: boolean,
}; };

View file

@ -1,6 +1,6 @@
{ {
"name": "LBRY", "name": "LBRY",
"version": "0.31.1", "version": "0.32.1",
"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"
@ -26,7 +26,7 @@
"dev": "yarn dev:electron", "dev": "yarn dev:electron",
"dev:electron": "cross-env NODE_ENV=development node ./src/platforms/electron/devServer.js", "dev:electron": "cross-env NODE_ENV=development node ./src/platforms/electron/devServer.js",
"dev:web": "cross-env 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", "dev:internal-apis": "LBRY_API_URL='http://localhost:8080' yarn dev:electron",
"run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js", "run:web": "cross-env NODE_ENV=production yarn compile:web && node ./dist/web/server.js",
"pack": "electron-builder --dir", "pack": "electron-builder --dir",
"dist": "electron-builder", "dist": "electron-builder",
@ -59,6 +59,7 @@
"@babel/preset-flow": "^7.0.0", "@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0", "@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0", "@babel/register": "^7.0.0",
"@exponent/electron-cookies": "^2.0.0",
"@hot-loader/react-dom": "16.8", "@hot-loader/react-dom": "16.8",
"@lbry/color": "^1.0.2", "@lbry/color": "^1.0.2",
"@lbry/components": "^2.7.0", "@lbry/components": "^2.7.0",
@ -117,7 +118,7 @@
"jsmediatags": "^3.8.1", "jsmediatags": "^3.8.1",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#32916b04e4888c06a9bb2b07c57ce6821a4acf1a", "lbry-redux": "lbryio/lbry-redux#02f6918238110726c0b3b4248c61a84ac0b969e3",
"lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",
@ -151,7 +152,6 @@
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
"react-simplemde-editor": "^4.0.0", "react-simplemde-editor": "^4.0.0",
"react-toggle": "^4.0.2", "react-toggle": "^4.0.2",
"react-virtualized": "^9.21.0",
"redux": "^3.6.0", "redux": "^3.6.0",
"redux-persist": "^4.8.0", "redux-persist": "^4.8.0",
"redux-persist-transform-compress": "^4.2.0", "redux-persist-transform-compress": "^4.2.0",
@ -189,7 +189,7 @@
"yarn": "^1.3" "yarn": "^1.3"
}, },
"lbrySettings": { "lbrySettings": {
"lbrynetDaemonVersion": "0.37.0rc5", "lbrynetDaemonVersion": "0.37.0",
"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"

View file

@ -1,4 +1,3 @@
import path from 'path';
import { app, BrowserWindow, dialog, shell, screen } from 'electron'; import { app, BrowserWindow, dialog, shell, screen } from 'electron';
import isDev from 'electron-is-dev'; import isDev from 'electron-is-dev';
import windowStateKeeper from 'electron-window-state'; import windowStateKeeper from 'electron-window-state';
@ -28,7 +27,7 @@ export default appState => {
// If state is undefined, create window as maximized. // If state is undefined, create window as maximized.
width: windowState.width === undefined ? width : windowState.width, width: windowState.width === undefined ? width : windowState.width,
height: windowState.height === undefined ? height : windowState.height, height: windowState.height === undefined ? height : windowState.height,
icon: 'static/img/tray/default/tray.png', icon: 'static/img/tray/windows/tray.png',
webPreferences: { webPreferences: {
// Disable renderer process's webSecurity on development to enable CORS. // Disable renderer process's webSecurity on development to enable CORS.
webSecurity: !isDev, webSecurity: !isDev,
@ -37,7 +36,7 @@ export default appState => {
}; };
const lbryProto = 'lbry://'; const lbryProto = 'lbry://';
const lbryProtoQ = 'lbry://?'; const lbryProtoQ = 'lbry://?';
const rendererURL = isDev ? `http://localhost:8080` : `file://${__dirname}/index.html`; const rendererURL = isDev ? `http://localhost:${WEBPACK_PORT}` : `file://${__dirname}/index.html`;
let window = new BrowserWindow(windowConfiguration); let window = new BrowserWindow(windowConfiguration);

View file

@ -1,5 +1,3 @@
const fs = require('fs');
const path = require('path');
const chalk = require('chalk'); const chalk = require('chalk');
const webpack = require('webpack'); const webpack = require('webpack');
const merge = require('webpack-merge'); const merge = require('webpack-merge');
@ -7,12 +5,9 @@ const middleware = require('webpack-dev-middleware');
const express = require('express'); const express = require('express');
const app = express(); const app = express();
// TODO: Spawn separate threads so realtime status logging can be used // Primary definition for this is in webpack.web.config.js
// without overwriting information/execptions logged by the compilers // We can't access it here because webpack isn't running on this file
const logRealtime = str => { const WEBPACK_PORT = 9090;
let lineCount = (str.match(/\n/) || []).length + 1;
console.log('\u001B[' + lineCount + 'F\u001B[G\u001B[2K' + str);
};
console.log( console.log(
chalk.magenta(`Compiling ${chalk.underline('main')} and ${chalk.underline('render')}, this will take a while.`) chalk.magenta(`Compiling ${chalk.underline('main')} and ${chalk.underline('render')}, this will take a while.`)
@ -46,8 +41,8 @@ app.use(require('webpack-hot-middleware')(renderCompiler));
app.use(renderInstance); app.use(renderInstance);
app.use(express.static('dist/electron/static')); app.use(express.static('dist/electron/static'));
app.listen(8080, () => { app.listen(WEBPACK_PORT, () => {
console.log(chalk.yellow.bold('Renderer listening on port 8080 (still compiling)')); console.log(chalk.yellow.bold(`Renderer listening on port ${WEBPACK_PORT} (still compiling)`));
}); });
mainInstance.waitUntilValid(() => console.log(chalk.green(`${chalk.underline('main')} compilation complete.`))); mainInstance.waitUntilValid(() => console.log(chalk.green(`${chalk.underline('main')} compilation complete.`)));

View file

@ -2,7 +2,6 @@
import '@babel/polyfill'; import '@babel/polyfill';
import keytar from 'keytar'; import keytar from 'keytar';
import SemVer from 'semver'; import SemVer from 'semver';
import url from 'url';
import https from 'https'; import https from 'https';
import { app, dialog, ipcMain, session, shell } from 'electron'; import { app, dialog, ipcMain, session, shell } from 'electron';
import { autoUpdater } from 'electron-updater'; import { autoUpdater } from 'electron-updater';
@ -191,6 +190,20 @@ app.on('will-quit', event => {
} }
}); });
app.on('will-finish-launching', () => {
// Protocol handler for macOS
app.on('open-url', (event, URL) => {
event.preventDefault();
if (rendererWindow) {
rendererWindow.webContents.send('open-uri-requested', URL);
rendererWindow.show();
} else {
appState.macDeepLinkingURI = URL;
}
});
});
app.on('before-quit', () => { app.on('before-quit', () => {
appState.isQuitting = true; appState.isQuitting = true;
}); });

View file

@ -2,6 +2,9 @@
import { Lbryio } from 'lbryinc'; import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga'; import ReactGA from 'react-ga';
import { history } from './store'; import { history } from './store';
// @if TARGET='app'
import ElectronCookies from '@exponent/electron-cookies';
// @endif
type Analytics = { type Analytics = {
pageView: string => void, pageView: string => void,
@ -15,7 +18,7 @@ let analyticsEnabled: boolean = true;
const analytics: Analytics = { const analytics: Analytics = {
pageView: path => { pageView: path => {
if (analyticsEnabled) { if (analyticsEnabled) {
ReactGA.pageview(path, IS_WEB ? ['web'] : ['desktop']); ReactGA.pageview(path);
} }
}, },
setUser: user => { setUser: user => {
@ -76,18 +79,29 @@ const analytics: Analytics = {
// Initialize google analytics // Initialize google analytics
// Set `debug: true` for debug info // Set `debug: true` for debug info
// Will change once we have separate ids for desktop/web // Will change once we have separate ids for desktop/web
const UA_ID = IS_WEB ? 'UA-60403362-12' : 'UA-60403362-12'; const UA_ID = IS_WEB ? 'UA-60403362-12' : 'UA-60403362-13';
// @if TARGET='app'
ElectronCookies.enable({
origin: 'https://lbry.tv',
});
// @endif
ReactGA.initialize(UA_ID, { ReactGA.initialize(UA_ID, {
gaOptions: { name: IS_WEB ? 'web' : 'desktop' },
testMode: process.env.NODE_ENV !== 'production', testMode: process.env.NODE_ENV !== 'production',
// debug: true, cookieDomain: 'auto',
}); });
// Manually call the first page view // Manually call the first page view
// React Router doesn't include this on `history.listen` // React Router doesn't include this on `history.listen`
analytics.pageView(window.location.pathname + window.location.search); analytics.pageView(window.location.pathname + window.location.search);
// @if TARGET='app'
ReactGA.set({ checkProtocolTask: null });
ReactGA.set({ location: 'https://lbry.tv' });
analytics.pageView(window.location.pathname.split('.html')[1] + window.location.search || '/');
// @endif;
// Listen for url changes and report // Listen for url changes and report
// This will include search queries // This will include search queries
history.listen(location => { history.listen(location => {

View file

@ -14,7 +14,6 @@ type Props = {
claimsInChannel: Array<StreamClaim>, claimsInChannel: Array<StreamClaim>,
channelIsMine: boolean, channelIsMine: boolean,
fetchClaims: (string, number) => void, fetchClaims: (string, number) => void,
location: UrlLocation,
}; };
function ChannelContent(props: Props) { function ChannelContent(props: Props) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -1,15 +1,30 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import { parseURI } from 'lbry-redux';
import classnames from 'classnames';
import Gerbil from './gerbil.png';
type Props = { type Props = {
thumbnail: ?string, thumbnail: ?string,
uri: string,
}; };
function ChannelThumbnail(props: Props) { function ChannelThumbnail(props: Props) {
const { thumbnail } = props; const { thumbnail, uri } = props;
// Generate a random color class based on the first letter of the channel name
const { channelName } = parseURI(uri);
const initializer = channelName.charCodeAt(0) - 65; // will be between 0 and 57
const className = `channel-thumbnail__default--${initializer % 4}`;
return ( return (
<div className="channel__thumbnail"> <div
{thumbnail && <img className="channel__thumbnail--custom" src={thumbnail} />} className={classnames('channel-thumbnail', {
[className]: !thumbnail,
})}
>
{!thumbnail && <img className="channel-thumbnail__default" src={Gerbil} />}
{thumbnail && <img className="channel-thumbnail__custom" src={thumbnail} />}
</div> </div>
); );
} }

View file

@ -1,16 +1,12 @@
// @flow // @flow
import type { ElementRef } from 'react'; import type { ElementRef } from 'react';
import React, { Suspense } from 'react'; import React from 'react';
import ReactDOMServer from 'react-dom/server'; import ReactDOMServer from 'react-dom/server';
import MarkdownPreview from 'component/common/markdown-preview';
import 'easymde/dist/easymde.min.css'; import 'easymde/dist/easymde.min.css';
import Toggle from 'react-toggle'; import Toggle from 'react-toggle';
import { openEditorMenu, stopContextMenu } from 'util/context-menu'; import { openEditorMenu, stopContextMenu } from 'util/context-menu';
import SimpleMDE from 'react-simplemde-editor';
const SimpleMDE = React.lazy(() => import MarkdownPreview from 'component/common/markdown-preview-internal';
import(/* webpackChunkName: "SimpleMDE" */
'react-simplemde-editor')
);
type Props = { type Props = {
name: string, name: string,
@ -137,21 +133,20 @@ export class FormField extends React.PureComponent<Props> {
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}> <div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
<fieldset-section> <fieldset-section>
<label htmlFor={name}>{label}</label> <label htmlFor={name}>{label}</label>
<Suspense fallback={<div />}> <SimpleMDE
<SimpleMDE {...inputProps}
{...inputProps} id={name}
id={name} type="textarea"
type="textarea" events={handleEvents}
events={handleEvents} options={{
options={{ spellChecker: false,
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'], hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
previewRender(plainText) { previewRender(plainText) {
const preview = <MarkdownPreview content={plainText} />; const preview = <MarkdownPreview content={plainText} />;
return ReactDOMServer.renderToString(preview); return ReactDOMServer.renderToString(preview);
}, },
}} }}
/> />
</Suspense>
</fieldset-section> </fieldset-section>
</div> </div>
); );

View file

@ -25,7 +25,7 @@ class IconComponent extends React.PureComponent<Props> {
switch (icon) { switch (icon) {
case ICONS.FEATURED: case ICONS.FEATURED:
return __('Featured content. Earn rewards for watching.'); return __('Featured content. Earn rewards for watching.');
case ICONS.LOCAL: case ICONS.DOWNLOAD:
return __('This file is downloaded.'); return __('This file is downloaded.');
default: default:
return null; return null;

View file

@ -16,7 +16,7 @@ type Props = {
}; };
function Paginate(props: Props) { function Paginate(props: Props) {
const { totalPages, loading, location, history, onPageChange } = props; const { totalPages = 1, loading, location, history, onPageChange } = props;
const { search } = location; const { search } = location;
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const currentPage = Number(urlParams.get(PAGINATE_PARAM)) || 1; const currentPage = Number(urlParams.get(PAGINATE_PARAM)) || 1;
@ -40,12 +40,10 @@ function Paginate(props: Props) {
} }
} }
if (totalPages <= 1 || loading) {
return null;
}
return ( return (
<Form> // Hide the paginate controls if we are loading or there is only one page
// It should still be rendered to trigger the onPageChange callback
<Form style={totalPages <= 1 || loading ? { display: 'none' } : null}>
<fieldset-group class="fieldset-group--smushed fieldgroup--paginate"> <fieldset-group class="fieldset-group--smushed fieldgroup--paginate">
<fieldset-section> <fieldset-section>
<ReactPaginate <ReactPaginate

View file

@ -3,19 +3,25 @@ import { Lbryio } from 'lbryinc';
import * as React from 'react'; import * as React from 'react';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
import Button from 'component/button'; import Button from 'component/button';
import { withRouter } from 'react-router';
type Props = { type Props = {
children: React.Node, children: React.Node,
history: {
replace: string => void,
},
}; };
type State = { type State = {
hasError: boolean, hasError: boolean,
}; };
export default class ErrorBoundary extends React.Component<Props, State> { class ErrorBoundary extends React.Component<Props, State> {
constructor() { constructor() {
super(); super();
this.state = { hasError: false }; this.state = { hasError: false };
(this: any).refresh = this.refresh.bind(this);
} }
static getDerivedStateFromError() { static getDerivedStateFromError() {
@ -35,6 +41,13 @@ export default class ErrorBoundary extends React.Component<Props, State> {
} }
} }
refresh() {
const { history } = this.props;
// use history.replace instead of history.push so the user can't click back to the errored page
history.replace('');
this.setState({ hasError: false });
}
render() { render() {
if (this.state.hasError) { if (this.state.hasError) {
return ( return (
@ -50,7 +63,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
button="link" button="link"
className="load-screen__button" className="load-screen__button"
label={__('refreshing the app')} label={__('refreshing the app')}
onClick={() => (window.location.href = '/')} onClick={this.refresh}
/>{' '} />{' '}
{__('to fix it')}. {__('to fix it')}.
</p> </p>
@ -64,3 +77,5 @@ export default class ErrorBoundary extends React.Component<Props, State> {
return this.props.children; return this.props.children;
} }
} }
export default withRouter(ErrorBoundary);

View file

@ -37,7 +37,7 @@ class ExternalLink extends React.PureComponent<Props> {
title={title || href} title={title || href}
label={children} label={children}
className="button--external-link" className="button--external-link"
onClick={() => openModal(MODALS.CONFIRM_EXTERNAL_LINK, { uri: href })} onClick={() => openModal(MODALS.CONFIRM_EXTERNAL_RESOURCE, { uri: href })}
/> />
); );
} }

View file

@ -12,7 +12,6 @@ import { openCopyLinkMenu } from 'util/context-menu';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUriForWeb } from 'util/uri';
import get from 'lodash.get';
type Props = { type Props = {
uri: string, uri: string,
@ -65,7 +64,6 @@ class FileCard extends React.PureComponent<Props> {
const { const {
claim, claim,
fileInfo, fileInfo,
metadata,
rewardedContentClaimIds, rewardedContentClaimIds,
obscureNsfw, obscureNsfw,
claimIsMine, claimIsMine,
@ -106,7 +104,6 @@ class FileCard extends React.PureComponent<Props> {
const uri = !pending ? normalizeURI(this.props.uri) : this.props.uri; const uri = !pending ? normalizeURI(this.props.uri) : this.props.uri;
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id); const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
const height = claim && claim.height;
const handleContextMenu = event => { const handleContextMenu = event => {
event.preventDefault(); event.preventDefault();
event.stopPropagation(); event.stopPropagation();
@ -146,7 +143,7 @@ class FileCard extends React.PureComponent<Props> {
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />} {isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
{isSubscribed && <Icon icon={icons.SUBSCRIPTION} />} {isSubscribed && <Icon icon={icons.SUBSCRIPTION} />}
{claimIsMine && <Icon icon={icons.PUBLISHED} />} {claimIsMine && <Icon icon={icons.PUBLISHED} />}
{!claimIsMine && fileInfo && <Icon icon={icons.LOCAL} />} {!claimIsMine && fileInfo && <Icon icon={icons.DOWNLOAD} />}
{isNew && <span className="badge badge--alert">{__('NEW')}</span>} {isNew && <span className="badge badge--alert">{__('NEW')}</span>}
</div> </div>
</li> </li>

View file

@ -92,7 +92,6 @@ class FileDetails extends PureComponent<Props> {
{__('Downloaded to')} {__('Downloaded to')}
{': '} {': '}
<Button <Button
constrict
button="link" button="link"
onClick={() => { onClick={() => {
if (downloadPath) { if (downloadPath) {

View file

@ -6,7 +6,7 @@ import {
makeSelectClaimForUri, makeSelectClaimForUri,
} from 'lbry-redux'; } from 'lbry-redux';
import { makeSelectCostInfoForUri } from 'lbryinc'; import { makeSelectCostInfoForUri } from 'lbryinc';
import { doOpenFileInShell } from 'redux/actions/file'; import { doOpenModal } from 'redux/actions/app';
import { doPurchaseUri, doStartDownload, doSetPlayingUri } from 'redux/actions/content'; import { doPurchaseUri, doStartDownload, doSetPlayingUri } from 'redux/actions/content';
import FileDownloadLink from './view'; import FileDownloadLink from './view';
@ -20,7 +20,7 @@ const select = (state, props) => ({
}); });
const perform = dispatch => ({ const perform = dispatch => ({
openInShell: path => dispatch(doOpenFileInShell(path)), openModal: (modal, props) => dispatch(doOpenModal(modal, props)),
purchaseUri: uri => dispatch(doPurchaseUri(uri)), purchaseUri: uri => dispatch(doPurchaseUri(uri)),
restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)), restartDownload: (uri, outpoint) => dispatch(doStartDownload(uri, outpoint)),
pause: () => dispatch(doSetPlayingUri(null)), pause: () => dispatch(doSetPlayingUri(null)),

View file

@ -1,5 +1,6 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as MODALS from 'constants/modal_types';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import ToolTip from 'component/common/tooltip'; import ToolTip from 'component/common/tooltip';
@ -20,7 +21,7 @@ type Props = {
loading: boolean, loading: boolean,
costInfo: ?{}, costInfo: ?{},
restartDownload: (string, number) => void, restartDownload: (string, number) => void,
openInShell: string => void, openModal: (id: string, { path: string }) => void,
purchaseUri: string => void, purchaseUri: string => void,
pause: () => void, pause: () => void,
}; };
@ -43,14 +44,7 @@ class FileDownloadLink extends React.PureComponent<Props> {
uri: ?string; uri: ?string;
render() { render() {
const { fileInfo, downloading, uri, openInShell, purchaseUri, costInfo, loading, pause, claim } = this.props; const { fileInfo, downloading, uri, openModal, purchaseUri, costInfo, loading, pause, claim } = this.props;
const openFile = () => {
if (fileInfo) {
openInShell(fileInfo.download_path);
pause();
}
};
if (loading || downloading) { if (loading || downloading) {
const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0; const progress = fileInfo && fileInfo.written_bytes ? (fileInfo.written_bytes / fileInfo.total_bytes) * 100 : 0;
@ -83,7 +77,15 @@ class FileDownloadLink extends React.PureComponent<Props> {
} else if (fileInfo && fileInfo.download_path) { } else if (fileInfo && fileInfo.download_path) {
return ( return (
<ToolTip onComponent body={__('Open file')}> <ToolTip onComponent body={__('Open file')}>
<Button button="alt" iconColor="green" icon={ICONS.EXTERNAL} onClick={() => openFile()} /> <Button
button="alt"
iconColor="green"
icon={ICONS.EXTERNAL}
onClick={() => {
pause();
openModal(MODALS.CONFIRM_EXTERNAL_RESOURCE, { path: fileInfo.download_path });
}}
/>
</ToolTip> </ToolTip>
); );
} }

View file

@ -47,8 +47,8 @@ class FileTile extends React.PureComponent<Props> {
if (!isResolvingUri && !claim && uri) resolveUri(uri); if (!isResolvingUri && !claim && uri) resolveUri(uri);
} }
componentWillReceiveProps(nextProps: Props) { componentDidUpdate() {
const { isResolvingUri, claim, uri, resolveUri } = nextProps; const { isResolvingUri, claim, uri, resolveUri } = this.props;
if (!isResolvingUri && claim === undefined && uri) resolveUri(uri); if (!isResolvingUri && claim === undefined && uri) resolveUri(uri);
} }
@ -68,7 +68,7 @@ class FileTile extends React.PureComponent<Props> {
{isNew && <span className="badge badge--alert icon">{__('NEW')}</span>} {isNew && <span className="badge badge--alert icon">{__('NEW')}</span>}
{isSubscribed && <Icon icon={ICONS.SUBSCRIPTION} />} {isSubscribed && <Icon icon={ICONS.SUBSCRIPTION} />}
{isRewardContent && <Icon iconColor="red" icon={ICONS.FEATURED} />} {isRewardContent && <Icon iconColor="red" icon={ICONS.FEATURED} />}
{!claimIsMine && isDownloaded && <Icon icon={ICONS.LOCAL} />} {!claimIsMine && isDownloaded && <Icon icon={ICONS.DOWNLOAD} />}
{claimIsMine && <Icon icon={ICONS.PUBLISHED} />} {claimIsMine && <Icon icon={ICONS.PUBLISHED} />}
</div> </div>
); );

View file

@ -34,12 +34,25 @@ type State = {
contentType?: string, contentType?: string,
downloadPath?: string, downloadPath?: string,
fileType?: string, fileType?: string,
// Just using `any` because flow isn't working with `fs.createReadStream`
stream?: ({}) => any,
}, },
}; };
class MediaPlayer extends React.PureComponent<Props, State> { class MediaPlayer extends React.PureComponent<Props, State> {
static SANDBOX_TYPES = ['application/x-lbry', 'application/x-ext-lbry']; static SANDBOX_TYPES = ['application/x-lbry', 'application/x-ext-lbry'];
static FILE_MEDIA_TYPES = ['text', 'script', 'e-book', 'comic-book', 'document', '3D-file', 'video', 'audio']; static FILE_MEDIA_TYPES = [
'text',
'script',
'e-book',
'comic-book',
'document',
'3D-file',
// @if TARGET='web'
'video',
'audio',
// @endif
];
static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/'; static SANDBOX_SET_BASE_URL = 'http://localhost:5278/set/';
static SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278'; static SANDBOX_CONTENT_BASE_URL = 'http://localhost:5278';
@ -276,6 +289,10 @@ class MediaPlayer extends React.PureComponent<Props, State> {
contentType, contentType,
downloadPath, downloadPath,
fileType: path.extname(fileName).substring(1), fileType: path.extname(fileName).substring(1),
// Readable stream from file
// @if TARGET='app'
stream: opts => fs.createReadStream(downloadPath, opts),
// @endif
}; };
// Update state // Update state

View file

@ -78,7 +78,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
return historyItems.length ? ( return historyItems.length ? (
<React.Fragment> <React.Fragment>
<div className="card__actions card__actions--between"> <div className="card__header card__actions card__actions--between">
{Object.keys(itemsSelected).length ? ( {Object.keys(itemsSelected).length ? (
<Button button="link" label={__('Delete')} onClick={this.removeSelected} /> <Button button="link" label={__('Delete')} onClick={this.removeSelected} />
) : ( ) : (
@ -87,7 +87,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
<Button button="link" label={allSelected ? __('Cancel') : __('Select All')} onClick={selectHandler} /> <Button button="link" label={allSelected ? __('Cancel') : __('Select All')} onClick={selectHandler} />
</div> </div>
{!!historyItems.length && ( {!!historyItems.length && (
<section className="card__content item-list"> <section className="card card__content item-list">
{historyItems.map(item => ( {historyItems.map(item => (
<NavigationHistoryItem <NavigationHistoryItem
key={item.uri} key={item.uri}
@ -107,9 +107,7 @@ class UserHistoryPage extends React.PureComponent<Props, State> {
<div className="main--empty"> <div className="main--empty">
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header">
<h2 className="card__title"> <h2 className="card__title">{__('Your history is empty, what are you doing here?')}</h2>
{__("You don't have anything saved in history yet, go check out some content on LBRY!")}
</h2>
</header> </header>
<div className="card__content"> <div className="card__content">

View file

@ -16,7 +16,7 @@ export default function NavigationHistoryRecent(props: Props) {
const { history = [] } = props; const { history = [] } = props;
return history.length ? ( return history.length ? (
<div className="item-list"> <div className="card item-list">
<section className="card__content"> <section className="card__content">
{history.map(({ lastViewed, uri }) => ( {history.map(({ lastViewed, uri }) => (
<NavigationHistoryItem slim key={uri} uri={uri} lastViewed={lastViewed} /> <NavigationHistoryItem slim key={uri} uri={uri} lastViewed={lastViewed} />

View file

@ -28,8 +28,8 @@ type Props = {
language: string, language: string,
nsfw: boolean, nsfw: boolean,
contentIsFree: boolean, contentIsFree: boolean,
price: { fee: {
amount: number, amount: string,
currency: string, currency: string,
}, },
channel: string, channel: string,
@ -70,11 +70,14 @@ class PublishForm extends React.PureComponent<Props> {
(this: any).getNewUri = this.getNewUri.bind(this); (this: any).getNewUri = this.getNewUri.bind(this);
} }
componentWillMount() { componentDidMount() {
const { thumbnail } = this.props; const { thumbnail, name, channel, editingURI } = this.props;
if (!thumbnail) { if (!thumbnail) {
this.props.resetThumbnailStatus(); this.props.resetThumbnailStatus();
} }
if (editingURI) {
this.getNewUri(name, channel);
}
} }
getNewUri(name: string, channel: string) { getNewUri(name: string, channel: string) {
@ -156,7 +159,7 @@ class PublishForm extends React.PureComponent<Props> {
let previousBidAmount = 0; let previousBidAmount = 0;
if (myClaimForUri) { if (myClaimForUri) {
previousBidAmount = myClaimForUri.amount; previousBidAmount = Number(myClaimForUri.amount);
} }
const totalAvailableBidAmount = previousBidAmount + balance; const totalAvailableBidAmount = previousBidAmount + balance;
@ -190,7 +193,7 @@ class PublishForm extends React.PureComponent<Props> {
} }
handlePublish() { handlePublish() {
const { filePath, licenseType, licenseUrl, otherLicenseDescription, myClaimForUri, publish } = this.props; const { filePath, licenseType, licenseUrl, otherLicenseDescription, publish } = this.props;
let publishingLicense; let publishingLicense;
switch (licenseType) { switch (licenseType) {
@ -217,10 +220,11 @@ class PublishForm extends React.PureComponent<Props> {
otherLicenseDescription, otherLicenseDescription,
name: this.props.name || undefined, name: this.props.name || undefined,
contentIsFree: this.props.contentIsFree, contentIsFree: this.props.contentIsFree,
price: this.props.price, fee: this.props.fee,
uri: this.props.uri || undefined, uri: this.props.uri || undefined,
channel: this.props.channel, channel: this.props.channel,
isStillEditing: this.props.isStillEditing, isStillEditing: this.props.isStillEditing,
claim: this.props.myClaimForUri,
}; };
publish(publishParams); publish(publishParams);
@ -264,7 +268,7 @@ class PublishForm extends React.PureComponent<Props> {
// If there is an error it will be presented as an inline error as well // If there is an error it will be presented as an inline error as well
return ( return (
!isFormValid && ( !isFormValid && (
<div className="card__content card__subtitle error-text"> <div className="card__content error-text">
{!title && <div>{__('A title is required')}</div>} {!title && <div>{__('A title is required')}</div>}
{!name && <div>{__('A URL is required')}</div>} {!name && <div>{__('A URL is required')}</div>}
{name && nameError && <div>{__('The URL you created is not valid')}</div>} {name && nameError && <div>{__('The URL you created is not valid')}</div>}
@ -292,7 +296,7 @@ class PublishForm extends React.PureComponent<Props> {
language, language,
nsfw, nsfw,
contentIsFree, contentIsFree,
price, fee,
channel, channel,
name, name,
updatePublishForm, updatePublishForm,
@ -440,11 +444,11 @@ class PublishForm extends React.PureComponent<Props> {
<FormFieldPrice <FormFieldPrice
name="content_cost_amount" name="content_cost_amount"
min="0" min="0"
price={price} price={fee}
onChange={newPrice => updatePublishForm({ price: newPrice })} onChange={newFee => updatePublishForm({ fee: newFee })}
/> />
)} )}
{price.currency !== 'LBC' && ( {fee && fee.currency !== 'LBC' && (
<p className="form-field__help"> <p className="form-field__help">
{__( {__(
'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.' 'All content fees are charged in LBC. For non-LBC payment methods, the number of credits charged will be adjusted based on the value of LBRY credits at the time of purchase.'
@ -581,22 +585,17 @@ class PublishForm extends React.PureComponent<Props> {
</section> </section>
<section className="card card--section"> <section className="card card--section">
<div className="card__content"> <div className="card__actions">
<Submit
label={submitLabel}
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS}
/>
<Button button="link" onClick={this.handleCancelPublish} label={__('Cancel')} />
</div>
<p className="help">
{__('By continuing, you accept the')}{' '} {__('By continuing, you accept the')}{' '}
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />. <Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />.
</div> </p>
</section>
<section className="card card--section">
<div className="card__content">
<div className="card__actions">
<Submit
label={submitLabel}
disabled={formDisabled || !formValid || uploadThumbnailStatus === THUMBNAIL_STATUSES.IN_PROGRESS}
/>
<Button button="link" onClick={this.handleCancelPublish} label={__('Cancel')} />
</div>
</div>
</section> </section>
</div> </div>

View file

@ -14,7 +14,6 @@ import FileListPublished from 'page/fileListPublished';
import TransactionHistoryPage from 'page/transactionHistory'; import TransactionHistoryPage from 'page/transactionHistory';
import AuthPage from 'page/auth'; import AuthPage from 'page/auth';
import InvitePage from 'page/invite'; import InvitePage from 'page/invite';
import BackupPage from 'page/backup';
import SubscriptionsPage from 'page/subscriptions'; import SubscriptionsPage from 'page/subscriptions';
import SearchPage from 'page/search'; import SearchPage from 'page/search';
import UserHistoryPage from 'page/userHistory'; import UserHistoryPage from 'page/userHistory';
@ -39,7 +38,6 @@ export default function AppRouter() {
<Switch> <Switch>
<Route path="/" exact component={DiscoverPage} /> <Route path="/" exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} /> <Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
<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} />
<Route path={`/$/${PAGES.PUBLISHED}`} exact component={FileListPublished} /> <Route path={`/$/${PAGES.PUBLISHED}`} exact component={FileListPublished} />

View file

@ -62,10 +62,10 @@ class SideBar extends React.PureComponent<Props> {
), ),
}, },
{ {
...buildLink(PAGES.PUBLISHED, 'Publishes', ICONS.PUBLISHED), ...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISHED),
}, },
{ {
...buildLink(PAGES.HISTORY, 'History', ICONS.HISTORY), ...buildLink(PAGES.HISTORY, __('Library'), ICONS.DOWNLOAD),
}, },
].map(renderLink)} ].map(renderLink)}
</ul> </ul>
@ -74,35 +74,30 @@ class SideBar extends React.PureComponent<Props> {
<ul className="navigation__links"> <ul className="navigation__links">
{[ {[
{ {
...buildLink(PAGES.ACCOUNT, 'Overview', ICONS.ACCOUNT), ...buildLink(PAGES.ACCOUNT, __('Overview'), ICONS.ACCOUNT),
}, },
{ {
...buildLink(PAGES.INVITE, 'Invite', ICONS.INVITE, shouldShowInviteGuide && __('Check this out!')), ...buildLink(PAGES.INVITE, __('Invite'), ICONS.INVITE, shouldShowInviteGuide && __('Check this out!')),
}, },
{ {
...buildLink(PAGES.REWARDS, 'Rewards', ICONS.FEATURED), ...buildLink(PAGES.REWARDS, __('Rewards'), ICONS.FEATURED),
}, },
{ {
...buildLink(PAGES.SEND, 'Send & Recieve', ICONS.SEND), ...buildLink(PAGES.SEND, __('Send & Recieve'), ICONS.SEND),
}, },
{ {
...buildLink(PAGES.TRANSACTIONS, 'Transactions', ICONS.TRANSACTIONS), ...buildLink(PAGES.TRANSACTIONS, __('Transactions'), ICONS.TRANSACTIONS),
}, },
{ {
...buildLink(PAGES.SETTINGS, 'Settings', ICONS.SETTINGS), ...buildLink(PAGES.SETTINGS, __('Settings'), ICONS.SETTINGS),
}, },
// @if TARGET='app'
{
...buildLink(PAGES.BACKUP, 'Backup', ICONS.BACKUP),
},
// @endif
].map(renderLink)} ].map(renderLink)}
</ul> </ul>
<ul className="navigation__links navigation__links--bottom"> <ul className="navigation__links navigation__links--bottom">
{[ {[
{ {
...buildLink(PAGES.HELP, 'Help', ICONS.HELP), ...buildLink(PAGES.HELP, __('Help'), ICONS.HELP),
}, },
].map(renderLink)} ].map(renderLink)}
</ul> </ul>

View file

@ -27,7 +27,7 @@ class SocialShare extends React.PureComponent<Props> {
render() { render() {
const { claim, isChannel } = this.props; const { claim, isChannel } = this.props;
const { claim_id: claimId, name: claimName, channel_name: channelName, value } = claim; const { claim_id: claimId, name: claimName, channel_name: channelName } = claim;
const { speechShareable, onDone } = this.props; const { speechShareable, onDone } = this.props;
const channelClaimId = claim.signing_channel && claim.signing_channel.claim_id; const channelClaimId = claim.signing_channel && claim.signing_channel.claim_id;
@ -70,7 +70,7 @@ class SocialShare extends React.PureComponent<Props> {
<React.Fragment> <React.Fragment>
{speechShareable && ( {speechShareable && (
<div className="card__content"> <div className="card__content">
<label className="card__subtitle">{__('Web link')}</label> <label className="help">{__('Web link')}</label>
<CopyableText copyable={speechURL} /> <CopyableText copyable={speechURL} />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}> <ToolTip onComponent body={__('Facebook')}>
@ -98,7 +98,7 @@ class SocialShare extends React.PureComponent<Props> {
</div> </div>
)} )}
<div className="card__content"> <div className="card__content">
<label className="card__subtitle">{__('LBRY App link')}</label> <label className="help">{__('LBRY App link')}</label>
<CopyableText copyable={lbryURL} noSnackbar /> <CopyableText copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}> <ToolTip onComponent body={__('Facebook')}>

View file

@ -1,11 +1,17 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectClaimedRewardsByTransactionId } from 'lbryinc'; import { selectClaimedRewardsByTransactionId } from 'lbryinc';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { selectAllMyClaimsByOutpoint, selectTransactionListFilter, doSetTransactionListFilter } from 'lbry-redux'; import {
selectAllMyClaimsByOutpoint,
selectSupportsByOutpoint,
selectTransactionListFilter,
doSetTransactionListFilter,
} from 'lbry-redux';
import TransactionList from './view'; import TransactionList from './view';
const select = state => ({ const select = state => ({
rewards: selectClaimedRewardsByTransactionId(state), rewards: selectClaimedRewardsByTransactionId(state),
mySupports: selectSupportsByOutpoint(state),
myClaims: selectAllMyClaimsByOutpoint(state), myClaims: selectAllMyClaimsByOutpoint(state),
filterSetting: selectTransactionListFilter(state), filterSetting: selectTransactionListFilter(state),
}); });

View file

@ -6,8 +6,7 @@ import ButtonTransaction from 'component/common/transaction-link';
import CreditAmount from 'component/common/credit-amount'; import CreditAmount from 'component/common/credit-amount';
import DateTime from 'component/dateTime'; import DateTime from 'component/dateTime';
import Button from 'component/button'; import Button from 'component/button';
import { buildURI } from 'lbry-redux'; import { buildURI, parseURI } from 'lbry-redux';
import { formatLbryUriForWeb } from 'util/uri';
type Props = { type Props = {
transaction: Transaction, transaction: Transaction,
@ -43,6 +42,9 @@ class TransactionListItem extends React.PureComponent<Props> {
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);
const dateFormat = { const dateFormat = {
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',
@ -66,9 +68,9 @@ class TransactionListItem extends React.PureComponent<Props> {
</td> </td>
<td className="table__item--actionable"> <td className="table__item--actionable">
{reward && <span>{reward.reward_title}</span>} {reward && <span>{reward.reward_title}</span>}
{name && claimId && ( {claimName && claimId && (
<Button constrict button="link" navigate={buildURI({ claimName: name, claimId })}> <Button button="link" navigate={buildURI({ claimName: claimName, claimId })}>
{name} {claimName}
</Button> </Button>
)} )}
</td> </td>

View file

@ -2,7 +2,6 @@
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as React from 'react'; import * as React from 'react';
import { List } from 'react-virtualized';
import { FormField, Form } from 'component/common/form'; import { FormField, Form } from 'component/common/form';
import Button from 'component/button'; import Button from 'component/button';
import FileExporter from 'component/common/file-exporter'; import FileExporter from 'component/common/file-exporter';
@ -15,6 +14,7 @@ type Props = {
transactions: Array<Transaction>, transactions: Array<Transaction>,
rewards: {}, rewards: {},
openModal: (id: string, { nout: number, txid: string }) => void, openModal: (id: string, { nout: number, txid: string }) => void,
mySupports: {},
myClaims: any, myClaims: any,
filterSetting: string, filterSetting: string,
setTransactionFilter: string => void, setTransactionFilter: string => void,
@ -43,10 +43,9 @@ class TransactionList extends React.PureComponent<Props> {
} }
isRevokeable(txid: string, nout: number) { isRevokeable(txid: string, nout: number) {
const { myClaims } = this.props; const outpoint = `${txid}:${nout}`;
// a claim/support/update is revokable if it const { mySupports, myClaims } = this.props;
// is in my claim list(claim_list_mine) return !!mySupports[outpoint] || myClaims.has(outpoint);
return myClaims.has(`${txid}:${nout}`);
} }
revokeClaim(txid: string, nout: number) { revokeClaim(txid: string, nout: number) {
@ -103,7 +102,7 @@ class TransactionList extends React.PureComponent<Props> {
</div> </div>
)} )}
</header> </header>
{!transactionList.length && <p>{emptyMessage || __('No transactions to list.')}</p>} {!transactionList.length && <p className="card__subtitle">{emptyMessage || __('No transactions to list.')}</p>}
{!!transactionList.length && ( {!!transactionList.length && (
<React.Fragment> <React.Fragment>

View file

@ -31,11 +31,6 @@ class TransactionListRecent extends React.PureComponent<Props> {
{__('Recent Transactions')} {__('Recent Transactions')}
<RefreshTransactionButton /> <RefreshTransactionButton />
</h2> </h2>
<p className="card__subtitle">
{__('To view all of your transactions, navigate to the')}{' '}
<Button button="link" navigate="/$/transactions" label={__('transactions page')} />.
</p>
</header> </header>
{fetchingTransactions && !hasTransactions && ( {fetchingTransactions && !hasTransactions && (
@ -44,6 +39,12 @@ class TransactionListRecent extends React.PureComponent<Props> {
</div> </div>
)} )}
{!fetchingTransactions && !hasTransactions && (
<div className="card__content">
<p className="card__subtitle">{__('No transactions... yet.')}</p>
</div>
)}
{hasTransactions && ( {hasTransactions && (
<Fragment> <Fragment>
<div className="card__content"> <div className="card__content">

View file

@ -28,7 +28,7 @@ class UserVerify extends React.PureComponent<Props> {
return ( return (
<React.Fragment> <React.Fragment>
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header--flat">
<h1 className="card__title">{__('Final Human Proof')}</h1> <h1 className="card__title">{__('Final Human Proof')}</h1>
<p className="card__subtitle"> <p className="card__subtitle">
Finally, please complete <strong>one and only one</strong> of the options below. Finally, please complete <strong>one and only one</strong> of the options below.

View file

@ -1,9 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectDaemonSettings } from 'redux/selectors/settings'; import { selectDaemonSettings } from 'redux/selectors/settings';
import BackupPage from './view'; import WalletBackup from './view';
const select = state => ({ const select = state => ({
daemonSettings: selectDaemonSettings(state), daemonSettings: selectDaemonSettings(state),
}); });
export default connect(select)(BackupPage); export default connect(select)(WalletBackup);

View file

@ -0,0 +1,66 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
type Props = {
daemonSettings: {
wallet_dir: ?string,
},
};
class WalletBackup extends React.PureComponent<Props> {
render() {
const { daemonSettings } = this.props;
const { wallet_dir: lbryumWalletDir } = daemonSettings;
const noDaemonSettings = Object.keys(daemonSettings).length === 0;
return (
<section className="card card--section">
{noDaemonSettings ? (
<header className="card__header">
<h2 className="card__title">{__('Failed to load settings.')}</h2>
</header>
) : (
<React.Fragment>
<header className="card__header">
<h2 className="card__title">{__('Backup Your LBRY Credits')}</h2>
<p className="card__subtitle">
{__(
'Your LBRY credits are controllable by you and only you, via wallet file(s) stored locally on your computer.'
)}
</p>
</header>
<div className="card__content">
<p>
{__(
'Currently, there is no automatic wallet backup. If you lose access to these files, you will lose your credits permanently.'
)}
</p>
<p>
{__(
'However, it is fairly easy to back up manually. To backup your wallet, make a copy of the folder listed below:'
)}
</p>
<p className="card__message">{lbryumWalletDir}</p>
<p>
{__(
'Access to these files are equivalent to having access to your credits. Keep any copies you make of your wallet in a secure place.'
)}
</p>
<p>
For more details on backing up and best practices,{' '}
<Button button="link" href="https://lbry.com/faq/how-to-backup-wallet" label={__('see this article')} />
.
</p>
</div>
</React.Fragment>
)}
</section>
);
}
}
export default WalletBackup;

View file

@ -16,9 +16,9 @@ const WalletBalance = (props: Props) => {
> >
<header className="card__header"> <header className="card__header">
<h2 className="card__title">{__('Balance')}</h2> <h2 className="card__title">{__('Balance')}</h2>
<p className="card__subtitle">{__('You currently have')}</p>
</header> </header>
<div className="card__content"> <div className="card__content">
<h3>{__('You currently have')}</h3>
{(balance || balance === 0) && <CreditAmount large badge={false} amount={balance} precision={8} />} {(balance || balance === 0) && <CreditAmount large badge={false} amount={balance} precision={8} />}
</div> </div>
</section> </section>

View file

@ -92,12 +92,12 @@ class WalletSend extends React.PureComponent<Props> {
parseFloat(values.amount) === balance parseFloat(values.amount) === balance
} }
/> />
{!!Object.keys(errors).length && ( {!!Object.keys(errors).length || (
<span className="error-text"> <span className="error-text">
{(!!values.address && touched.address && errors.address) || {(!!values.address && touched.address && errors.address) ||
(!!values.amount && touched.amount && errors.amount) || (!!values.amount && touched.amount && errors.amount) ||
(values.amount === balance && __('Decrease amount to account for transaction fee')) || (parseFloat(values.amount) === balance && __('Decrease amount to account for transaction fee')) ||
(values.amount > balance && __('Not enough credits'))} (parseFloat(values.amount) > balance && __('Not enough credits'))}
</span> </span>
)} )}
</div> </div>

View file

@ -1,5 +1,5 @@
export const CONFIRM_FILE_REMOVE = 'confirm_file_remove'; export const CONFIRM_FILE_REMOVE = 'confirm_file_remove';
export const CONFIRM_EXTERNAL_LINK = 'confirm_external_link'; export const CONFIRM_EXTERNAL_RESOURCE = 'confirm_external_resource';
export const INCOMPATIBLE_DAEMON = 'incompatible_daemon'; export const INCOMPATIBLE_DAEMON = 'incompatible_daemon';
export const FILE_TIMEOUT = 'file_timeout'; export const FILE_TIMEOUT = 'file_timeout';
export const DOWNLOADING = 'downloading'; export const DOWNLOADING = 'downloading';

View file

@ -1,52 +0,0 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import { formatLbryUriForWeb } from 'util/uri';
// @if TARGET='app'
import { shell } from 'electron';
// @endif
type Props = {
uri: string,
closeModal: () => void,
};
class ModalOpenExternalLink extends React.PureComponent<Props> {
openExternalLink() {
const { uri, closeModal } = this.props;
// @if TARGET='app'
const { openExternal } = shell;
if (uri) {
openExternal(uri);
}
// @endif
// @if TARGET='web'
window.open(uri);
// @endif
closeModal();
}
render() {
const { uri, closeModal } = this.props;
return (
<Modal
isOpen
title={__('Warning!')}
contentLabel={__('Confirm External Link')}
type="confirm"
confirmButtonLabel={__('Continue')}
onConfirmed={() => this.openExternalLink()}
onAborted={closeModal}
>
<section className="card__content">
<p>{__('This link leads to an external website.')}</p>
<blockquote>{uri}</blockquote>
<p>{__('LBRY Inc is not responsible for its content, click continue to proceed at your own risk.')}</p>
</section>
</Modal>
);
}
}
export default ModalOpenExternalLink;

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doHideModal } from 'redux/actions/app'; import { doHideModal } from 'redux/actions/app';
import ModalOpenExternalLink from './view'; import ModalOpenExternalResource from './view';
const perform = dispatch => ({ const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()), closeModal: () => dispatch(doHideModal()),
@ -9,4 +9,4 @@ const perform = dispatch => ({
export default connect( export default connect(
null, null,
perform perform
)(ModalOpenExternalLink); )(ModalOpenExternalResource);

View file

@ -0,0 +1,72 @@
// @flow
import React from 'react';
import { Modal } from 'modal/modal';
import { formatLbryUriForWeb } from 'util/uri';
// @if TARGET='app'
import { shell } from 'electron';
// @endif
type Props = {
uri: string,
path: string,
closeModal: () => void,
};
class ModalOpenExternalResource extends React.PureComponent<Props> {
openExternalResource() {
const { uri, path, closeModal } = this.props;
// @if TARGET='app'
const { openExternal, openItem, showItemInFolder } = shell;
if (uri) {
openExternal(uri);
} else if (path) {
const success = openItem(path);
if (!success) {
showItemInFolder(path);
}
}
// @endif
// @if TARGET='web'
if (uri) {
window.open(uri);
} else if (path) {
// Converintg path into uri, like "file://path/to/file"
let _uri = path.replace(/\\/g, '/');
// Windows drive letter must be prefixed with a slash
if (_uri[0] !== '/') {
_uri = `/${_uri}`;
}
_uri = encodeURI(`file://${_uri}`).replace(/[?#]/g, encodeURIComponent);
window.open(_uri);
}
// @endif
closeModal();
}
render() {
const { uri, path, closeModal } = this.props;
return (
<Modal
isOpen
title={__('Warning!')}
contentLabel={__('Confirm External Resource')}
type="confirm"
confirmButtonLabel={__('Continue')}
onConfirmed={() => this.openExternalResource()}
onAborted={closeModal}
>
<section className="card__content">
<p>
{(uri && __('This link leads to an external website.')) ||
(path && __('This file has been shared with you by other people.'))}
</p>
<blockquote>{uri || path}</blockquote>
<p>{__('LBRY Inc is not responsible for its content, click continue to proceed at your own risk.')}</p>
</section>
</Modal>
);
}
}
export default ModalOpenExternalResource;

View file

@ -3,7 +3,7 @@ import React, { Suspense } from 'react';
import { Modal } from 'modal/modal'; import { Modal } from 'modal/modal';
import Button from 'component/button'; import Button from 'component/button';
import UserPhoneVerify from 'component/userPhoneVerify'; import UserPhoneVerify from 'component/userPhoneVerify';
import { withRouter } from 'react-router-dom'; import { Redirect } from 'react-router';
const LazyUserPhoneNew = React.lazy(() => const LazyUserPhoneNew = React.lazy(() =>
import(/* webpackChunkName: "userPhoneNew" */ import(/* webpackChunkName: "userPhoneNew" */
@ -13,7 +13,7 @@ const LazyUserPhoneNew = React.lazy(() =>
type Props = { type Props = {
phone: ?number, phone: ?number,
user: { user: {
phone_number: ?number, is_identity_verified: boolean,
}, },
closeModal: () => void, closeModal: () => void,
history: { push: string => void }, history: { push: string => void },
@ -23,29 +23,30 @@ class ModalPhoneCollection extends React.PureComponent<Props> {
getTitle() { getTitle() {
const { user, phone } = this.props; const { user, phone } = this.props;
if (!user.phone_number && !phone) { if (!user.is_identity_verified && !phone) {
return __('Enter Your Phone Number'); return __('Enter Your Phone Number');
} }
return __('Enter The Verification Code'); return __('Enter The Verification Code');
} }
renderInner() { renderInner() {
const { closeModal, phone, user, history } = this.props; const { closeModal, phone, user } = this.props;
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />; const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
if (!user.phone_number && !phone) { if (!user.is_identity_verified && !phone) {
return ( return (
<Suspense fallback={<div />}> <Suspense fallback={<div />}>
<LazyUserPhoneNew cancelButton={cancelButton} /> <LazyUserPhoneNew cancelButton={cancelButton} />
</Suspense> </Suspense>
); );
} else if (!user.phone_number) { } else if (!user.is_identity_verified) {
return <UserPhoneVerify cancelButton={cancelButton} />; return <UserPhoneVerify cancelButton={cancelButton} />;
} }
history.push('/$/rewards'); closeModal();
return closeModal(); return <Redirect to="/$/rewards" />;
} }
render() { render() {
@ -64,4 +65,4 @@ class ModalPhoneCollection extends React.PureComponent<Props> {
} }
} }
export default withRouter(ModalPhoneCollection); export default ModalPhoneCollection;

View file

@ -1,6 +1,11 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doDeleteFileAndMaybeGoBack } from 'redux/actions/file'; import { doDeleteFileAndMaybeGoBack } from 'redux/actions/file';
import { makeSelectTitleForUri, makeSelectClaimIsMine, makeSelectFileInfoForUri } from 'lbry-redux'; import {
makeSelectTitleForUri,
makeSelectClaimIsMine,
makeSelectFileInfoForUri,
makeSelectClaimForUri,
} from 'lbry-redux';
import { doHideModal } from 'redux/actions/app'; import { doHideModal } from 'redux/actions/app';
import ModalRemoveFile from './view'; import ModalRemoveFile from './view';
@ -8,6 +13,7 @@ const select = (state, props) => ({
claimIsMine: makeSelectClaimIsMine(props.uri)(state), claimIsMine: makeSelectClaimIsMine(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state), title: makeSelectTitleForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -4,6 +4,7 @@ import { Modal } from 'modal/modal';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
type Props = { type Props = {
claim: StreamClaim,
claimIsMine: boolean, claimIsMine: boolean,
closeModal: () => void, closeModal: () => void,
deleteFile: (string, boolean, boolean) => void, deleteFile: (string, boolean, boolean) => void,
@ -45,10 +46,11 @@ class ModalRemoveFile extends React.PureComponent<Props, State> {
} }
render() { render() {
const { claimIsMine, closeModal, deleteFile, fileInfo, title } = this.props; const { claim, claimIsMine, closeModal, deleteFile, fileInfo, title } = this.props;
const { deleteChecked, abandonClaimChecked } = this.state; const { deleteChecked, abandonClaimChecked } = this.state;
const { txid, nout } = claim;
const outpoint = fileInfo ? fileInfo.outpoint : `${txid}:${nout}`;
const outpoint = fileInfo ? fileInfo.outpoint : '';
return ( return (
<Modal <Modal
isOpen isOpen

View file

@ -21,7 +21,7 @@ import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
import ModalSocialShare from 'modal/modalSocialShare'; import ModalSocialShare from 'modal/modalSocialShare';
import ModalSendTip from 'modal/modalSendTip'; import ModalSendTip from 'modal/modalSendTip';
import ModalPublish from 'modal/modalPublish'; import ModalPublish from 'modal/modalPublish';
import ModalOpenExternalLink from 'modal/modalOpenExternalLink'; import ModalOpenExternalResource from 'modal/modalOpenExternalResource';
import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload'; import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload';
import ModalWalletEncrypt from 'modal/modalWalletEncrypt'; import ModalWalletEncrypt from 'modal/modalWalletEncrypt';
import ModalWalletDecrypt from 'modal/modalWalletDecrypt'; import ModalWalletDecrypt from 'modal/modalWalletDecrypt';
@ -85,8 +85,8 @@ function ModalRouter(props: Props) {
return <ModalSocialShare {...modalProps} />; return <ModalSocialShare {...modalProps} />;
case MODALS.PUBLISH: case MODALS.PUBLISH:
return <ModalPublish {...modalProps} />; return <ModalPublish {...modalProps} />;
case MODALS.CONFIRM_EXTERNAL_LINK: case MODALS.CONFIRM_EXTERNAL_RESOURCE:
return <ModalOpenExternalLink {...modalProps} />; return <ModalOpenExternalResource {...modalProps} />;
case MODALS.CONFIRM_TRANSACTION: case MODALS.CONFIRM_TRANSACTION:
return <ModalConfirmTransaction {...modalProps} />; return <ModalConfirmTransaction {...modalProps} />;
case MODALS.CONFIRM_THUMBNAIL_UPLOAD: case MODALS.CONFIRM_THUMBNAIL_UPLOAD:

View file

@ -1,73 +0,0 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import Page from 'component/page';
type Props = {
daemonSettings: {
wallet_dir: ?string,
},
};
class BackupPage extends React.PureComponent<Props> {
render() {
const { daemonSettings } = this.props;
const { wallet_dir: lbryumWalletDir } = daemonSettings;
const noDaemonSettings = Object.keys(daemonSettings).length === 0;
return (
<Page>
<section className="card card--section">
{noDaemonSettings ? (
<header className="card__header">
<h2 className="card__title">{__('Failed to load settings.')}</h2>
</header>
) : (
<React.Fragment>
<header className="card__header">
<h2 className="card__title">{__('Backup Your LBRY Credits')}</h2>
<p className="card__subtitle">
{__(
'Your LBRY credits are controllable by you and only you, via wallet file(s) stored locally on your computer.'
)}
</p>
</header>
<div className="card__content">
<p>
{__(
'Currently, there is no automatic wallet backup. If you lose access to these files, you will lose your credits permanently.'
)}
</p>
<p>
{__(
'However, it is fairly easy to back up manually. To backup your wallet, make a copy of the folder listed below:'
)}
</p>
<p className="card__message">{lbryumWalletDir}</p>
<p>
{__(
'Access to these files are equivalent to having access to your credits. Keep any copies you make of your wallet in a secure place.'
)}
</p>
<p>
For more details on backing up and best practices,{' '}
<Button
button="link"
href="https://lbry.com/faq/how-to-backup-wallet"
label={__('see this article')}
/>
.
</p>
</div>
</React.Fragment>
)}
</section>
</Page>
);
}
}
export default BackupPage;

View file

@ -4,6 +4,7 @@ import {
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
makeSelectCoverForUri, makeSelectCoverForUri,
selectCurrentChannelPage,
} from 'lbry-redux'; } from 'lbry-redux';
import ChannelPage from './view'; import ChannelPage from './view';
@ -12,6 +13,7 @@ const select = (state, props) => ({
thumbnail: makeSelectThumbnailForUri(props.uri)(state), thumbnail: makeSelectThumbnailForUri(props.uri)(state),
cover: makeSelectCoverForUri(props.uri)(state), cover: makeSelectCoverForUri(props.uri)(state),
channelIsMine: makeSelectClaimIsMine(props.uri)(state), channelIsMine: makeSelectClaimIsMine(props.uri)(state),
page: selectCurrentChannelPage(state),
}); });
export default connect( export default connect(

View file

@ -19,13 +19,14 @@ type Props = {
title: ?string, title: ?string,
cover: ?string, cover: ?string,
thumbnail: ?string, thumbnail: ?string,
page: number,
location: { search: string }, location: { search: string },
history: { push: string => void }, history: { push: string => void },
match: { params: { attribute: ?string } }, match: { params: { attribute: ?string } },
}; };
function ChannelPage(props: Props) { function ChannelPage(props: Props) {
const { uri, title, cover, history, location } = props; const { uri, title, cover, history, location, page } = props;
const { channelName, claimName, claimId } = parseURI(uri); const { channelName, claimName, claimId } = parseURI(uri);
const { search } = location; const { search } = location;
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
@ -37,17 +38,20 @@ function ChannelPage(props: Props) {
const tabIndex = currentView === ABOUT_PAGE ? 1 : 0; const tabIndex = currentView === ABOUT_PAGE ? 1 : 0;
const onTabChange = newTabIndex => { const onTabChange = newTabIndex => {
let url = formatLbryUriForWeb(uri); let url = formatLbryUriForWeb(uri);
let search = '?';
if (newTabIndex !== 0) { if (newTabIndex !== 0) {
url += `?${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`; search += `${PAGE_VIEW_QUERY}=${ABOUT_PAGE}`;
} else {
search += `page=${page}`;
} }
history.push(url); history.push(`${url}${search}`);
}; };
return ( return (
<Page notContained className="main--no-padding-top"> <Page notContained className="main--no-padding-top">
<header className="channel__cover main__item--extend-outside"> <header className="channel-cover main__item--extend-outside">
{cover && <img className="channel__cover--custom" src={cover} />} {cover && <img className="channel-cover__custom" src={cover} />}
<div className="channel__primary-info"> <div className="channel__primary-info">
<ChannelThumbnail uri={uri} /> <ChannelThumbnail uri={uri} />

View file

@ -16,6 +16,7 @@ import {
makeSelectTitleForUri, makeSelectTitleForUri,
makeSelectThumbnailForUri, makeSelectThumbnailForUri,
makeSelectClaimIsNsfw, makeSelectClaimIsNsfw,
doToast,
} from 'lbry-redux'; } from 'lbry-redux';
import { doFetchViewCount, makeSelectViewCountForUri, makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc'; import { doFetchViewCount, makeSelectViewCountForUri, makeSelectCostInfoForUri, doFetchCostInfoForUri } from 'lbryinc';
import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings'; import { selectShowNsfw, makeSelectClientSetting } from 'redux/selectors/settings';
@ -53,6 +54,7 @@ const perform = dispatch => ({
setViewed: uri => dispatch(doSetContentHistoryItem(uri)), setViewed: uri => dispatch(doSetContentHistoryItem(uri)),
markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)), markSubscriptionRead: (channel, uri) => dispatch(doRemoveUnreadSubscription(channel, uri)),
fetchViewCount: claimId => dispatch(doFetchViewCount(claimId)), fetchViewCount: claimId => dispatch(doFetchViewCount(claimId)),
showToast: options => dispatch(doToast(options)),
}); });
export default connect( export default connect(

View file

@ -2,6 +2,7 @@
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import { clipboard } from 'electron';
import { buildURI, normalizeURI } from 'lbry-redux'; import { buildURI, normalizeURI } from 'lbry-redux';
import FileViewer from 'component/fileViewer'; import FileViewer from 'component/fileViewer';
import Thumbnail from 'component/common/thumbnail'; import Thumbnail from 'component/common/thumbnail';
@ -22,7 +23,6 @@ import RecommendedContent from 'component/recommendedContent';
type Props = { type Props = {
claim: StreamClaim, claim: StreamClaim,
fileInfo: FileListItem, fileInfo: FileListItem,
metadata: StreamMetadata,
contentType: string, contentType: string,
uri: string, uri: string,
rewardedContentClaimIds: Array<string>, rewardedContentClaimIds: Array<string>,
@ -44,6 +44,7 @@ type Props = {
title: string, title: string,
thumbnail: ?string, thumbnail: ?string,
nsfw: boolean, nsfw: boolean,
showToast: ({}) => void,
}; };
class FilePage extends React.Component<Props> { class FilePage extends React.Component<Props> {
@ -126,7 +127,6 @@ class FilePage extends React.Component<Props> {
render() { render() {
const { const {
claim, claim,
metadata,
contentType, contentType,
uri, uri,
rewardedContentClaimIds, rewardedContentClaimIds,
@ -142,10 +142,11 @@ class FilePage extends React.Component<Props> {
title, title,
thumbnail, thumbnail,
nsfw, nsfw,
showToast,
} = this.props; } = this.props;
// File info // File info
const { height, channel_name: channelName } = claim; const { channel_name: channelName } = claim;
const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage; const { PLAYABLE_MEDIA_TYPES, PREVIEW_MEDIA_TYPES } = FilePage;
const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id); const isRewardContent = (rewardedContentClaimIds || []).includes(claim.claim_id);
const shouldObscureThumbnail = obscureNsfw && nsfw; const shouldObscureThumbnail = obscureNsfw && nsfw;
@ -176,7 +177,17 @@ class FilePage extends React.Component<Props> {
return ( return (
<Page notContained className="main--file-page"> <Page notContained className="main--file-page">
<div className="grid-area--content"> <div className="grid-area--content">
<h1 className="media__uri">{uri}</h1> <Button
className="media__uri"
button="alt"
label={uri}
onClick={() => {
clipboard.writeText(uri);
showToast({
message: __('Text copied'),
});
}}
/>
{!fileInfo && insufficientCredits && ( {!fileInfo && insufficientCredits && (
<div className="media__insufficient-credits help--warning"> <div className="media__insufficient-credits help--warning">
{__( {__(

View file

@ -2,7 +2,6 @@
// @flow // @flow
import * as icons from 'constants/icons'; import * as icons from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import classnames from 'classnames';
// @if TARGET='app' // @if TARGET='app'
import { shell } from 'electron'; import { shell } from 'electron';
// @endif // @endif
@ -10,6 +9,7 @@ import { Lbry } from 'lbry-redux';
import Native from 'native'; import Native from 'native';
import Button from 'component/button'; import Button from 'component/button';
import Page from 'component/page'; import Page from 'component/page';
import BackupSection from 'component/walletBackup';
type DeamonSettings = { type DeamonSettings = {
data_dir: string | any, data_dir: string | any,
@ -194,6 +194,10 @@ class HelpPage extends React.PureComponent<Props, State> {
</div> </div>
</section> </section>
{/* @if TARGET='app' */}
<BackupSection />
{/* @endif */}
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header">
<h2 className="card__title">{__('About')}</h2> <h2 className="card__title">{__('About')}</h2>

View file

@ -39,7 +39,7 @@ export default function SearchPage(props: Props) {
<Fragment> <Fragment>
{isValid && ( {isValid && (
<header className="search__header"> <header className="search__header">
<Button navigate={uri} className="media__uri"> <Button button="alt" navigate={uri} className="media__uri">
{uri} {uri}
</Button> </Button>
{isChannel ? ( {isChannel ? (
@ -52,7 +52,6 @@ export default function SearchPage(props: Props) {
<div className="search__results-wrapper"> <div className="search__results-wrapper">
<SearchOptions /> <SearchOptions />
<FileListSearch query={urlQuery} /> <FileListSearch query={urlQuery} />
<div className="card__content help">{__('These search results are provided by LBRY, Inc.')}</div> <div className="card__content help">{__('These search results are provided by LBRY, Inc.')}</div>
</div> </div>

View file

@ -159,7 +159,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header">
<h2 className="card__title">{__('Download Directory')}</h2> <h2 className="card__title">{__('Download Directory')}</h2>
<p className="card__subtitle">{__('LBRY downloads will be saved here.')}</p>
</header> </header>
<div className="card__content"> <div className="card__content">
@ -171,6 +170,7 @@ class SettingsPage extends React.PureComponent<Props, State> {
setDaemonSetting('download_dir', newDirectory); setDaemonSetting('download_dir', newDirectory);
}} }}
/> />
<p className="help">{__('LBRY downloads will be saved here.')}</p>
</Form> </Form>
</div> </div>
</section> </section>
@ -178,9 +178,6 @@ class SettingsPage extends React.PureComponent<Props, State> {
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header">
<h2 className="card__title">{__('Max Purchase Price')}</h2> <h2 className="card__title">{__('Max Purchase Price')}</h2>
<p className="card__subtitle">
{__('This will prevent you from purchasing any content over a certain cost, as a safety measure.')}
</p>
</header> </header>
<Form className="card__content"> <Form className="card__content">
@ -212,15 +209,16 @@ class SettingsPage extends React.PureComponent<Props, State> {
price={daemonSettings.max_key_fee ? daemonSettings.max_key_fee : defaultMaxKeyFee} price={daemonSettings.max_key_fee ? daemonSettings.max_key_fee : defaultMaxKeyFee}
/> />
)} )}
<p className="help">
{__('This will prevent you from purchasing any content over a certain cost, as a safety measure.')}
</p>
</Form> </Form>
</section> </section>
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header">
<h2 className="card__title">{__('Purchase Confirmations')}</h2> <h2 className="card__title">{__('Purchase Confirmations')}</h2>
<p className="card__subtitle">
{__("When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price.")}
</p>
</header> </header>
<Form className="card__content"> <Form className="card__content">
@ -251,6 +249,10 @@ class SettingsPage extends React.PureComponent<Props, State> {
price={instantPurchaseMax} price={instantPurchaseMax}
/> />
)} )}
<p className="help">
{__("When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price.")}
</p>
</Form> </Form>
</section> </section>
@ -397,11 +399,12 @@ class SettingsPage extends React.PureComponent<Props, State> {
<section className="card card--section"> <section className="card card--section">
<header className="card__header"> <header className="card__header">
<h2 className="card__title">{__('Application Cache')}</h2> <h2 className="card__title">{__('Application Cache')}</h2>
<p className="card__subtitle">
{__('This will clear the application cache. Your wallet will not be affected.')}
</p>
</header> </header>
<p className="help--warning">
{__('This will clear the application cache. Your wallet will not be affected.')}
</p>
<div className="card__content"> <div className="card__content">
<Button <Button
button="primary" button="primary"

View file

@ -26,8 +26,8 @@ class ShowPage extends React.PureComponent<Props> {
if (!isResolvingUri) resolveUri(uri); if (!isResolvingUri) resolveUri(uri);
} }
componentWillReceiveProps(nextProps: Props) { componentDidUpdate() {
const { isResolvingUri, resolveUri, claim, uri, totalPages } = nextProps; const { isResolvingUri, resolveUri, claim, uri, totalPages } = this.props;
if ( if (
!isResolvingUri && !isResolvingUri &&
uri && uri &&

View file

@ -2,7 +2,7 @@ import * as ACTIONS from 'constants/action_types';
// @if TARGET='app' // @if TARGET='app'
import { shell } from 'electron'; import { shell } from 'electron';
// @endif // @endif
import { Lbry, batchActions, doAbandonClaim, selectMyClaimsOutpoints, selectFileInfosByOutpoint } from 'lbry-redux'; import { Lbry, batchActions, doAbandonClaim, selectMyClaimsOutpoints } from 'lbry-redux';
import { doHideModal } from 'redux/actions/app'; import { doHideModal } from 'redux/actions/app';
import { goBack } from 'connected-react-router'; import { goBack } from 'connected-react-router';
@ -33,15 +33,9 @@ export function doDeleteFile(outpoint, deleteFromComputer, abandonClaim) {
// If the file is for a claim we published then also abandon the claim // If the file is for a claim we published then also abandon the claim
const myClaimsOutpoints = selectMyClaimsOutpoints(state); const myClaimsOutpoints = selectMyClaimsOutpoints(state);
if (abandonClaim && myClaimsOutpoints.indexOf(outpoint) !== -1) { if (abandonClaim && myClaimsOutpoints.indexOf(outpoint) !== -1) {
const byOutpoint = selectFileInfosByOutpoint(state); const [txid, nout] = outpoint.split(':');
const fileInfo = byOutpoint[outpoint];
if (fileInfo) { dispatch(doAbandonClaim(txid, Number(nout)));
const txid = fileInfo.outpoint.slice(0, -2);
const nout = Number(fileInfo.outpoint.slice(-1));
dispatch(doAbandonClaim(txid, nout));
}
} }
dispatch({ dispatch({

View file

@ -11,6 +11,7 @@ import {
selectPendingById, selectPendingById,
selectMyClaimsWithoutChannels, selectMyClaimsWithoutChannels,
doError, doError,
isClaimNsfw,
} from 'lbry-redux'; } from 'lbry-redux';
import { doOpenModal } from 'redux/actions/app'; import { doOpenModal } from 'redux/actions/app';
import { selectosNotificationsEnabled } from 'redux/selectors/settings'; import { selectosNotificationsEnabled } from 'redux/selectors/settings';
@ -42,7 +43,6 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
data: { data: {
uploadThumbnailStatus: THUMBNAIL_STATUSES.READY, uploadThumbnailStatus: THUMBNAIL_STATUSES.READY,
thumbnail: '', thumbnail: '',
nsfw: false,
}, },
}); });
}) })
@ -52,7 +52,6 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
data: { data: {
uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN, uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN,
thumbnail: '', thumbnail: '',
nsfw: false,
}, },
}) })
); );
@ -152,14 +151,14 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string) => (dispatch: Dis
contentIsFree: !fee.amount, contentIsFree: !fee.amount,
author, author,
description, description,
fee, fee: { amount: fee.amount, currency: fee.currency },
languages, languages,
thumbnail: thumbnail ? thumbnail.url : null, thumbnail: thumbnail ? thumbnail.url : null,
title, title,
uri, uri,
uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined, uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined,
licenseUrl, licenseUrl,
replace: true, nsfw: isClaimNsfw(claim),
}; };
// Make sure custom liscence's are mapped properly // Make sure custom liscence's are mapped properly
@ -203,6 +202,7 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
fee, fee,
uri, uri,
nsfw, nsfw,
claim,
} = params; } = params;
// get the claim id from the channel name, we will use that instead // get the claim id from the channel name, we will use that instead
@ -214,36 +214,58 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
channel_id?: string, channel_id?: string,
bid: number, bid: number,
file_path?: string, file_path?: string,
fee?: { amount: string, currency: string },
tags: Array<string>, tags: Array<string>,
locations?: Array<Location>,
license_url?: string,
thumbnail_url?: string,
release_time?: number,
fee_currency?: string,
fee_amount?: string,
} = { } = {
name, name,
bid: creditsToString(bid), bid: creditsToString(bid),
title, title,
license, license,
license_url: licenseUrl,
languages: [language], languages: [language],
description, description,
thumbnail_url: thumbnail, tags: (claim && claim.value.tags) || [],
tags: [], locations: claim && claim.value.locations,
}; };
// Temporary solution to keep the same publish flow with the new tags api // Temporary solution to keep the same publish flow with the new tags api
// Eventually we will allow users to enter their own tags on publish // Eventually we will allow users to enter their own tags on publish
// `nsfw` will probably be removed // `nsfw` will probably be removed
if (licenseUrl) {
publishPayload.license_url = licenseUrl;
}
if (thumbnail) {
publishPayload.thumbnail_url = thumbnail;
}
if (claim && claim.value.release_time) {
publishPayload.release_time = Number(claim.value.release_time);
}
if (nsfw) { if (nsfw) {
publishPayload.tags.push('mature'); if (!publishPayload.tags.includes('mature')) {
publishPayload.tags.push('mature');
}
} else {
const indexToRemove = publishPayload.tags.indexOf('mature');
if (indexToRemove > -1) {
publishPayload.tags.splice(indexToRemove, 1);
}
} }
if (channelId) { if (channelId) {
publishPayload.channel_id = channelId; publishPayload.channel_id = channelId;
} }
if (fee) { if (!contentIsFree && fee && (fee.currency && Number(fee.amount) > 0)) {
publishPayload.fee = { publishPayload.fee_currency = fee.currency;
currency: fee.currency, publishPayload.fee_amount = creditsToString(fee.amount);
amount: creditsToString(fee.amount),
};
} }
// Only pass file on new uploads, not metadata only edits. // Only pass file on new uploads, not metadata only edits.

View file

@ -9,7 +9,7 @@ type PublishState = {
editingURI: ?string, editingURI: ?string,
filePath: ?string, filePath: ?string,
contentIsFree: boolean, contentIsFree: boolean,
price: { fee: {
amount: number, amount: number,
currency: string, currency: string,
}, },
@ -33,7 +33,7 @@ const defaultState: PublishState = {
editingURI: undefined, editingURI: undefined,
filePath: undefined, filePath: undefined,
contentIsFree: true, contentIsFree: true,
price: { fee: {
amount: 1, amount: 1,
currency: 'LBC', currency: 'LBC',
}, },

View file

@ -56,19 +56,6 @@
box-sizing: border-box; box-sizing: border-box;
} }
// We need the :disabled to override the default disabled button styles
// This is a little different because we still want to keep the button styled
// since it acts as an "active" state
.button--subscription-view-selected:disabled {
color: $lbry-teal-5;
border-bottom: 1px solid $lbry-teal-5;
opacity: 1;
html[data-mode='dark'] & {
color: $lbry-teal-3;
}
}
.button--uri-indicator { .button--uri-indicator {
max-width: 100%; max-width: 100%;
height: 1.2em; height: 1.2em;

View file

@ -1,15 +1,13 @@
.card { .card {
background-color: $lbry-white; background-color: $lbry-white;
border: 1px solid $lbry-gray-1;
margin-bottom: var(--spacing-vertical-xlarge); margin-bottom: var(--spacing-vertical-xlarge);
position: relative; position: relative;
border-radius: var(--card-radius); border-radius: var(--card-radius);
box-shadow: var(--box-shadow) $lbry-gray-1; box-shadow: var(--card-box-shadow) $lbry-gray-1;
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: rgba($lbry-white, 0.1); background-color: rgba($lbry-white, 0.1);
border-color: rgba($lbry-white, 0.1); box-shadow: var(--card-box-shadow) darken($lbry-gray-1, 80%);
box-shadow: var(--box-shadow) darken($lbry-gray-1, 80%);
} }
} }
@ -125,15 +123,15 @@
// Depending on screen width, the amount of items in // Depending on screen width, the amount of items in
// each row change and are auto-sized // each row change and are auto-sized
@media (min-width: 2001px) { // @media (min-width: 2001px) {
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr)); // grid-template-columns: repeat(auto-fill, minmax(calc(100% / 10), 1fr));
} // }
@media (min-width: 1801px) and (max-width: 2000px) { // @media (min-width: 1801px) and (max-width: 2000px) {
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr)); // grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr));
} // }
@media (min-width: 1551px) and (max-width: 1800px) { @media (min-width: 1551px) {
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr)); grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr));
} }
@ -199,6 +197,9 @@
// S U B T I T L E // S U B T I T L E
.card__subtitle { .card__subtitle {
@extend .help;
background-color: lighten($lbry-gray-1, 7%);
color: darken($lbry-gray-5, 30%);
font-size: 1.15rem; font-size: 1.15rem;
margin-bottom: var(--spacing-vertical-small); margin-bottom: var(--spacing-vertical-small);
@ -210,6 +211,10 @@
bottom: -0.12rem; bottom: -0.12rem;
position: relative; position: relative;
} }
[data-mode='dark'] & {
background-color: darken($lbry-gray-5, 20%);
}
} }
// C A R D // C A R D
@ -218,7 +223,7 @@
.card__title { .card__title {
font-size: 2rem; font-size: 2rem;
font-weight: 600; font-weight: 600;
padding-bottom: var(--spacing-vertical-medium); margin-bottom: var(--spacing-vertical-medium);
+ .card__content { + .card__content {
margin-top: var(--spacing-vertical-medium); margin-top: var(--spacing-vertical-medium);

View file

@ -1,7 +1,7 @@
$cover-z-index: 0; $cover-z-index: 0;
$metadata-z-index: 1; $metadata-z-index: 1;
.channel__cover { .channel-cover {
background-image: linear-gradient(to right, $lbry-indigo-4, $lbry-cyan-5 80%); background-image: linear-gradient(to right, $lbry-indigo-4, $lbry-cyan-5 80%);
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
@ -9,41 +9,62 @@ $metadata-z-index: 1;
color: $lbry-white; color: $lbry-white;
} }
.channel__cover--custom { .channel-cover__custom {
z-index: $cover-z-index; z-index: $cover-z-index;
align-self: flex-start; align-self: flex-start;
position: absolute; position: absolute;
object-fit: cover; object-fit: cover;
filter: brightness(40%); filter: brightness(60%);
} }
.channel__cover, .channel-cover,
.channel__cover--custom { .channel-cover__custom {
height: var(--cover-photo-height); height: var(--cover-photo-height);
width: 100%; width: 100%;
} }
.channel__thumbnail { .channel-thumbnail {
position: absolute; position: absolute;
display: flex;
left: var(--spacing-main-padding); left: var(--spacing-main-padding);
height: var(--channel-thumbnail-size); height: var(--channel-thumbnail-size);
width: var(--channel-thumbnail-size); width: var(--channel-thumbnail-size);
background-color: $lbry-gray-3;
background-image: linear-gradient(to right, $lbry-white, $lbry-gray-3 80%);
background-size: cover; background-size: cover;
box-shadow: 0px 8px 40px -3px $lbry-black; box-shadow: 0px 8px 40px -3px $lbry-black;
} }
.channel__thumbnail--custom { .channel-thumbnail__custom {
width: 100%; width: 100%;
object-fit: cover; object-fit: cover;
} }
.channel__thumbnail, .channel-thumbnail__default {
.channel__thumbnail--custom { width: 80%;
height: 80%;
margin-left: auto;
margin-right: auto;
align-self: flex-end;
margin-bottom: -1px;
}
.channel-thumbnail,
.channel-thumbnail__custom {
border-radius: var(--card-radius); border-radius: var(--card-radius);
} }
.channel-thumbnail__default--0 {
background-color: $lbry-indigo-3;
}
.channel-thumbnail__default--1 {
background-color: $lbry-orange-2;
}
.channel-thumbnail__default--2 {
background-color: $lbry-blue-3;
}
.channel-thumbnail__default--3 {
background-color: $lbry-red-1;
}
.channel__primary-info { .channel__primary-info {
// Ensure the profile pic/title sit ontop of the default cover background // Ensure the profile pic/title sit ontop of the default cover background
z-index: $metadata-z-index; z-index: $metadata-z-index;
@ -61,11 +82,6 @@ $metadata-z-index: 1;
.channel__url { .channel__url {
font-size: 1.2rem; font-size: 1.2rem;
user-select: all;
margin-top: -0.25rem; margin-top: -0.25rem;
color: rgba($lbry-white, 0.75);
} }
// .channel__description {
// font-size: 1.3rem;
// margin: var(--spacing-vertical-large) 0;
// }

View file

@ -5,6 +5,12 @@
// This is because many styles inside `lbry/components/sass/form/` are very specific // This is because many styles inside `lbry/components/sass/form/` are very specific
// As styles become hardened here, they _should_ slowly move over to that repo // As styles become hardened here, they _should_ slowly move over to that repo
input,
textarea,
select {
border-radius: var(--input-border-radius);
}
input-submit { input-submit {
align-items: center; align-items: center;
} }
@ -82,6 +88,16 @@ fieldset-group {
&:first-of-type { &:first-of-type {
input { input {
border-right: none; border-right: none;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
&:nth-of-type(2) {
input,
select {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
} }
} }
} }
@ -126,7 +142,12 @@ fieldset-group {
padding-right: 0; padding-right: 0;
border: 1px solid; border: 1px solid;
border-right: 0; border-right: 0;
border-color: $lbry-black;
color: $lbry-gray-4; color: $lbry-gray-4;
[data-mode='dark'] & {
border-color: $lbry-gray-4;
}
} }
} }
@ -135,6 +156,8 @@ fieldset-group {
input { input {
border-left: 0; border-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
padding-left: var(--spacing-xs); padding-left: var(--spacing-xs);
&:focus { &:focus {
@ -172,10 +195,11 @@ form {
fieldset-section { fieldset-section {
input-submit { input-submit {
input, input {
select { &:first-child {
&:first-child:not(:focus) {
border-right-color: transparent; border-right-color: transparent;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
} }
} }
@ -193,7 +217,10 @@ fieldset-section {
} }
.button { .button {
border-radius: 0; border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-top-right-radius: var(--input-border-radius);
border-bottom-right-radius: var(--input-border-radius);
} }
} }

View file

@ -1,6 +1,5 @@
.item-list { .item-list {
background-color: $lbry-white; background-color: $lbry-white;
margin-top: var(--spacing-vertical-large);
margin-bottom: var(--spacing-vertical-large); margin-bottom: var(--spacing-vertical-large);
padding: var(--spacing-vertical-large); padding: var(--spacing-vertical-large);

View file

@ -3,7 +3,8 @@
border-right: 1px solid $lbry-gray-1; border-right: 1px solid $lbry-gray-1;
border-bottom: 1px solid $lbry-gray-1; border-bottom: 1px solid $lbry-gray-1;
border-left: 1px solid $lbry-gray-1; border-left: 1px solid $lbry-gray-1;
border-radius: 0; border-bottom-left-radius: var(--input-border-radius);
border-bottom-right-radius: var(--input-border-radius);
background: $lbry-white; background: $lbry-white;
color: $lbry-black; color: $lbry-black;
@ -49,7 +50,8 @@
.editor-toolbar { .editor-toolbar {
background-color: $lbry-gray-1; background-color: $lbry-gray-1;
border: none; border: none;
border-radius: 0; border-top-left-radius: var(--input-border-radius);
border-top-right-radius: var(--input-border-radius);
opacity: 1; // ? opacity: 1; // ?
&:hover { &:hover {

View file

@ -1,7 +1,9 @@
// M E D I A // M E D I A
// C A R D // C A R D
.media-card { .media-card {
@include mediaThumbHoverZoom;
font-size: 1.2rem; font-size: 1.2rem;
border-radius: var(--card-radius);
.media__title { .media__title {
margin-bottom: var(--spacing-vertical-small); margin-bottom: var(--spacing-vertical-small);
@ -10,22 +12,13 @@
.media__properties { .media__properties {
height: 1em; height: 1em;
} }
&:hover {
box-shadow: 0 0 50px rgba($lbry-black, 0.08);
background-color: rgba($lbry-black, 0.04);
html[data-mode='dark'] & {
box-shadow: 0 0 50px rgba($lbry-white, 0.07);
background-color: rgba($lbry-white, 0.03);
}
}
} }
// M E D I A // M E D I A
// T I L E // T I L E
.media-tile { .media-tile {
@include mediaThumbHoverZoom;
display: flex; display: flex;
font-size: 1.5rem; font-size: 1.5rem;
position: relative; position: relative;
@ -106,6 +99,7 @@
.media__thumb { .media__thumb {
@include thumbnail; @include thumbnail;
border-radius: var(--card-radius);
&:not(.media__thumb--nsfw) { &:not(.media__thumb--nsfw) {
background-color: $lbry-gray-2; background-color: $lbry-gray-2;
@ -266,6 +260,7 @@
.media__info-text { .media__info-text {
font-size: 1.15rem; font-size: 1.15rem;
word-break: break-all;
&:not(:last-of-type) { &:not(:last-of-type) {
margin-bottom: var(--spacing-vertical-large); margin-bottom: var(--spacing-vertical-large);

View file

@ -35,17 +35,6 @@
} }
.media-tile--large.media-placeholder { .media-tile--large.media-placeholder {
.media__title,
.media__channel,
.media__subtitle {
// display: block;
// padding-left: var(--spacing-vertical-large);
}
.media__thumb {
// margin-right: var(--spacing-vertical-large);
}
.media__title { .media__title {
width: 60%; width: 60%;
height: 3rem; height: 3rem;

View file

@ -1,5 +1,5 @@
.search__header { .search__header {
padding: var(--spacing-vertical-large); margin-bottom: var(--spacing-vertical-large);
.placeholder { .placeholder {
background-color: rgba($lbry-white, 0.1); background-color: rgba($lbry-white, 0.1);
@ -24,24 +24,15 @@
} }
} }
.search__results-wrapper {
margin: var(--spacing-vertical-large);
}
.search__results-section { .search__results-section {
margin-bottom: var(--spacing-vertical-large); margin-bottom: var(--spacing-vertical-large);
min-height: 200px; min-height: 200px;
position: relative; position: relative;
} }
.search__results-title {
@extend .media-group__header-title;
margin-bottom: var(--spacing-vertical-large);
}
.search__options-wrapper { .search__options-wrapper {
font-size: 1.25em; font-size: 1.25em;
margin-bottom: var(--spacing-vertical-large); margin: var(--spacing-vertical-xlarge) 0;
} }
.search__options { .search__options {

View file

@ -29,27 +29,22 @@ table,
} }
.table--transactions { .table--transactions {
table-layout: fixed;
td:nth-of-type(1) { td:nth-of-type(1) {
// TX amounts // TX amounts
width: 25%;
font-size: 0.9em; font-size: 0.9em;
font-weight: 600; font-weight: 600;
} }
td:nth-of-type(2) {
width: 20%;
}
td:nth-of-type(3) { td:nth-of-type(3) {
width: 22.5%; // Only add ellipsis to the links in the table
} // We still want to show the entire message if a TX includes one
.button__content {
td:nth-of-type(4) { @include constrict(10rem);
width: 17.5%; vertical-align: bottom;
} display: inline-block;
}
td:nth-of-type(5) {
width: 15%;
} }
} }

View file

@ -132,8 +132,8 @@ code {
color: $lbry-gray-5; color: $lbry-gray-5;
display: block; display: block;
padding: 1rem; padding: 1rem;
margin-top: var(--spacing-vertical-large); margin-top: var(--spacing-vertical-medium);
margin-bottom: var(--spacing-vertical-large); margin-bottom: var(--spacing-vertical-medium);
border-radius: 5px; border-radius: 5px;
html[data-mode='dark'] & { html[data-mode='dark'] & {
@ -142,6 +142,7 @@ code {
} }
.help--warning { .help--warning {
@extend .help;
background-color: $lbry-yellow-3; background-color: $lbry-yellow-3;
color: $lbry-black; color: $lbry-black;

View file

@ -1,8 +1,21 @@
@mixin placeholder { @mixin placeholder {
animation: pulse 2s infinite ease-in-out; animation: pulse 2s infinite ease-in-out;
background-color: $lbry-gray-2; background-color: $lbry-gray-2;
border-radius: var(--card-radius);
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: rgba($lbry-white, 0.1); background-color: rgba($lbry-white, 0.1);
} }
} }
@mixin mediaThumbHoverZoom {
.media__thumb {
transition: all 0.2s ease;
}
&:hover {
.media__thumb {
transform: scale(1.05);
}
}
}

View file

@ -36,6 +36,7 @@ $large-breakpoint: 1921px;
// Input // Input
--input-border-size: 1px; --input-border-size: 1px;
--input-border-radius: 2px;
// Select // Select
--select-height: 30px; --select-height: 30px;
@ -53,7 +54,7 @@ $large-breakpoint: 1921px;
// Card // Card
--card-radius: 5px; --card-radius: 5px;
--card-max-width: 1000px; --card-max-width: 1000px;
--card-box-shadow: 0px 8px 20px; --card-box-shadow: 0px 0px 30px 2px;
// File // File
--file-tile-media-height: 125px; --file-tile-media-height: 125px;
@ -76,5 +77,5 @@ $large-breakpoint: 1921px;
// Image // Image
--thumbnail-preview-height: 100px; --thumbnail-preview-height: 100px;
--thumbnail-preview-width: 177px; --thumbnail-preview-width: 177px;
--cover-photo-height: 250px; --cover-photo-height: 300px;
} }

View file

@ -7,6 +7,10 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script type="text/javascript" src="http://localhost:8080/ui.js"></script> <!--
Primary definition for this is in webpack.web.config.js
We can't access it here because webpack isn't running on this file
-->
<script type="text/javascript" src="http://localhost:9090/ui.js"></script>
</body> </body>
</html> </html>

View file

@ -13,6 +13,12 @@ const UI_ROOT = path.resolve(__dirname, 'src/ui/');
const STATIC_ROOT = path.resolve(__dirname, 'static/'); const STATIC_ROOT = path.resolve(__dirname, 'static/');
const DIST_ROOT = path.resolve(__dirname, 'dist/'); const DIST_ROOT = path.resolve(__dirname, 'dist/');
// There are a two other uses of this value that can't access it from webpack
// They exist in
// src/platforms/electron/devServer.js
// static/index.dev.html
const WEBPACK_PORT = 9090;
console.log(ifProduction('production', 'development')); console.log(ifProduction('production', 'development'));
let baseConfig = { let baseConfig = {
@ -32,6 +38,10 @@ let baseConfig = {
node: { node: {
__dirname: false, __dirname: false,
}, },
devServer: {
historyApiFallback: true,
port: WEBPACK_PORT,
},
module: { module: {
rules: [ rules: [
{ {
@ -117,6 +127,7 @@ let baseConfig = {
'process.env.NODE_ENV': JSON.stringify(NODE_ENV), 'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_API_URL), 'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_API_URL),
'process.env.LBRY_API_URL': JSON.stringify(process.env.LBRY_API_URL), 'process.env.LBRY_API_URL': JSON.stringify(process.env.LBRY_API_URL),
WEBPACK_PORT,
}), }),
], ],
}; };

View file

@ -18,9 +18,6 @@ const webConfig = {
path: __dirname + '/dist/web', path: __dirname + '/dist/web',
publicPath: '/', publicPath: '/',
}, },
devServer: {
historyApiFallback: true,
},
module: { module: {
rules: [ rules: [
{ {

View file

@ -830,6 +830,14 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg== integrity sha512-Qv4LTqO11jepd5Qmlp3M1YEjBumoTHcHFdgPTQ+sFlIL5myi/7xu/POwP7IRu6odBdmLXdtIs1D6TuW6kbwbbg==
"@exponent/electron-cookies@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@exponent/electron-cookies/-/electron-cookies-2.0.0.tgz#4cf8dcf851454036cc524c40e9e482fc4e23f2d9"
integrity sha1-TPjc+FFFQDbMUkxA6eSC/E4j8tk=
dependencies:
tough-cookie "^2.2.2"
tough-cookie-web-storage-store "^1.0.0"
"@hot-loader/react-dom@16.8": "@hot-loader/react-dom@16.8":
version "16.8.6" version "16.8.6"
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.8.6.tgz#7923ba27db1563a7cc48d4e0b2879a140df461ea" resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.8.6.tgz#7923ba27db1563a7cc48d4e0b2879a140df461ea"
@ -2396,7 +2404,7 @@ class-utils@^0.3.5:
isobject "^3.0.0" isobject "^3.0.0"
static-extend "^0.1.1" static-extend "^0.1.1"
classnames@^2.2.3, classnames@^2.2.5: classnames@^2.2.5:
version "2.2.6" version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q== integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@ -3557,13 +3565,6 @@ dom-converter@^0.2:
dependencies: dependencies:
utila "~0.4" utila "~0.4"
"dom-helpers@^2.4.0 || ^3.0.0":
version "3.4.0"
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
dependencies:
"@babel/runtime" "^7.1.2"
dom-scroll-into-view@^1.2.1: dom-scroll-into-view@^1.2.1:
version "1.2.1" version "1.2.1"
resolved "https://registry.yarnpkg.com/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz#e8f36732dd089b0201a88d7815dc3f88e6d66c7e" resolved "https://registry.yarnpkg.com/dom-scroll-into-view/-/dom-scroll-into-view-1.2.1.tgz#e8f36732dd089b0201a88d7815dc3f88e6d66c7e"
@ -6515,9 +6516,9 @@ lazy-val@^1.0.3, lazy-val@^1.0.4:
yargs "^13.2.2" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#32916b04e4888c06a9bb2b07c57ce6821a4acf1a: lbry-redux@lbryio/lbry-redux#02f6918238110726c0b3b4248c61a84ac0b969e3:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/32916b04e4888c06a9bb2b07c57ce6821a4acf1a" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/02f6918238110726c0b3b4248c61a84ac0b969e3"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"
@ -6718,7 +6719,7 @@ lodash-es@^4.17.11, lodash-es@^4.17.4, lodash-es@^4.2.1:
resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.11.tgz#145ab4a7ac5c5e52a3531fb4f310255a152b4be0"
integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q== integrity sha512-DHb1ub+rMjjrxqlB3H56/6MXtm1lSksDp2rA2cNWjG8mlDUYFhUj3Di2Zn5IwSU87xLv8tNIQ7sSwE/YOX/D/Q==
lodash.assign@^4.0.3, lodash.assign@^4.0.6, lodash.assign@^4.2.0: lodash.assign@^4.0.3, lodash.assign@^4.0.6:
version "4.2.0" version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc= integrity sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=
@ -6728,11 +6729,6 @@ lodash.camelcase@^4.3.0:
resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6"
integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY= integrity sha1-soqmKIorn8ZRA1x3EfZathkDMaY=
lodash.clonedeep@^4.3.2:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
lodash.findkey@^4.6.0: lodash.findkey@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718" resolved "https://registry.yarnpkg.com/lodash.findkey/-/lodash.findkey-4.6.0.tgz#83058e903b51cbb759d09ccf546dea3ea39c4718"
@ -6768,11 +6764,6 @@ lodash.memoize@^4.1.2:
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
lodash.mergewith@^4.6.0:
version "4.6.1"
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927"
integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ==
lodash.pickby@^4.6.0: lodash.pickby@^4.6.0:
version "4.6.0" version "4.6.0"
resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff" resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"
@ -6808,7 +6799,7 @@ lodash.unset@^4.5.2:
resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed" resolved "https://registry.yarnpkg.com/lodash.unset/-/lodash.unset-4.5.2.tgz#370d1d3e85b72a7e1b0cdf2d272121306f23e4ed"
integrity sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0= integrity sha1-Nw0dPoW3Kn4bDN8tJyEhMG8j5O0=
lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@~4.17.10: lodash@^4.0.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.6.1, lodash@~4.17.10:
version "4.17.11" version "4.17.11"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
@ -6846,7 +6837,7 @@ longest-streak@^2.0.1:
resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e" resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-2.0.2.tgz#2421b6ba939a443bb9ffebf596585a50b4c38e2e"
integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA== integrity sha512-TmYTeEYxiAmSVdpbnQDXGtvYOIRsCMg89CVZzwzc2o7GFL1CjoiRPjH5ec0NFAVlAx3fVof9dX/t6KKRAo2OWA==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.0, loose-envify@^1.3.1, loose-envify@^1.4.0: loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -7397,7 +7388,7 @@ nan@2.12.1:
resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552"
integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==
nan@^2.10.0, nan@^2.9.2: nan@^2.13.2, nan@^2.9.2:
version "2.13.2" version "2.13.2"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw== integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
@ -7578,9 +7569,9 @@ node-releases@^1.1.13:
semver "^5.3.0" semver "^5.3.0"
node-sass@^4.11.0: node-sass@^4.11.0:
version "4.11.0" version "4.12.0"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a" resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA== integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==
dependencies: dependencies:
async-foreach "^0.1.3" async-foreach "^0.1.3"
chalk "^1.1.1" chalk "^1.1.1"
@ -7589,12 +7580,10 @@ node-sass@^4.11.0:
get-stdin "^4.0.1" get-stdin "^4.0.1"
glob "^7.0.3" glob "^7.0.3"
in-publish "^2.0.0" in-publish "^2.0.0"
lodash.assign "^4.2.0" lodash "^4.17.11"
lodash.clonedeep "^4.3.2"
lodash.mergewith "^4.6.0"
meow "^3.7.0" meow "^3.7.0"
mkdirp "^0.5.1" mkdirp "^0.5.1"
nan "^2.10.0" nan "^2.13.2"
node-gyp "^3.8.0" node-gyp "^3.8.0"
npmlog "^4.0.0" npmlog "^4.0.0"
request "^2.88.0" request "^2.88.0"
@ -9108,7 +9097,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.24: psl@^1.1.24, psl@^1.1.28:
version "1.1.31" version "1.1.31"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184"
integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==
@ -9168,7 +9157,7 @@ punycode@^1.2.4, punycode@^1.4.1:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0: punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@ -9430,19 +9419,7 @@ react-toggle@^4.0.2:
dependencies: dependencies:
classnames "^2.2.5" classnames "^2.2.5"
react-virtualized@^9.21.0: react@^16.8.2:
version "9.21.0"
resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.21.0.tgz#8267c40ffb48db35b242a36dea85edcf280a6506"
integrity sha512-duKD2HvO33mqld4EtQKm9H9H0p+xce1c++2D5xn59Ma7P8VT7CprfAe5hwjd1OGkyhqzOZiTMlTal7LxjH5yBQ==
dependencies:
babel-runtime "^6.26.0"
classnames "^2.2.3"
dom-helpers "^2.4.0 || ^3.0.0"
loose-envify "^1.3.0"
prop-types "^15.6.0"
react-lifecycles-compat "^3.0.4"
react@^16.8.2, react@^16.8.6:
version "16.8.6" version "16.8.6"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
@ -11135,6 +11112,21 @@ toposort@^1.0.0:
resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029" resolved "https://registry.yarnpkg.com/toposort/-/toposort-1.0.7.tgz#2e68442d9f64ec720b8cc89e6443ac6caa950029"
integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk= integrity sha1-LmhELZ9k7HILjMieZEOsbKqVACk=
tough-cookie-web-storage-store@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/tough-cookie-web-storage-store/-/tough-cookie-web-storage-store-1.0.0.tgz#8021fce24290bf0b6151e491d7312343451d390d"
integrity sha1-gCH84kKQvwthUeSR1zEjQ0UdOQ0=
dependencies:
lodash "^4.6.1"
tough-cookie@^2.2.2:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
dependencies:
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@~2.4.3: tough-cookie@~2.4.3:
version "2.4.3" version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"