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,
"__n": true,
"app": true,
"IS_WEB": true
"IS_WEB": true,
"WEBPACK_PORT": true
},
"rules": {
"comma-dangle": ["error", "always-multiline"],
"handle-callback-err": 0,
"indent": 0,
"jsx-quotes": ["error", "prefer-double"],
"new-cap": 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 = {
filePath?: string,
contentIsFree?: boolean,
price?: {
amount: number,
fee?: {
amount: string,
currency: string,
},
title?: string,
@ -23,7 +23,7 @@ declare type UpdatePublishFormData = {
licenseUrl?: string,
licenseType?: string,
uri?: string,
replace?: boolean,
nsfw: boolean,
};
declare type PublishParams = {
@ -43,12 +43,9 @@ declare type PublishParams = {
license: ?string,
licenseUrl: ?string,
fee?: {
amount: string,
currency: string,
amount: number,
},
replace?: boolean,
// This is bad.
// Will be removed for tags soon
claim: StreamClaim,
nsfw: boolean,
};

View file

@ -1,6 +1,6 @@
{
"name": "LBRY",
"version": "0.31.1",
"version": "0.32.1",
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"keywords": [
"lbry"
@ -26,7 +26,7 @@
"dev": "yarn dev:electron",
"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: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",
"pack": "electron-builder --dir",
"dist": "electron-builder",
@ -59,6 +59,7 @@
"@babel/preset-flow": "^7.0.0",
"@babel/preset-react": "^7.0.0",
"@babel/register": "^7.0.0",
"@exponent/electron-cookies": "^2.0.0",
"@hot-loader/react-dom": "16.8",
"@lbry/color": "^1.0.2",
"@lbry/components": "^2.7.0",
@ -117,7 +118,7 @@
"jsmediatags": "^3.8.1",
"json-loader": "^0.5.4",
"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",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",
@ -151,7 +152,6 @@
"react-router-dom": "^5.0.0",
"react-simplemde-editor": "^4.0.0",
"react-toggle": "^4.0.2",
"react-virtualized": "^9.21.0",
"redux": "^3.6.0",
"redux-persist": "^4.8.0",
"redux-persist-transform-compress": "^4.2.0",
@ -189,7 +189,7 @@
"yarn": "^1.3"
},
"lbrySettings": {
"lbrynetDaemonVersion": "0.37.0rc5",
"lbrynetDaemonVersion": "0.37.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet"

View file

@ -1,4 +1,3 @@
import path from 'path';
import { app, BrowserWindow, dialog, shell, screen } from 'electron';
import isDev from 'electron-is-dev';
import windowStateKeeper from 'electron-window-state';
@ -28,7 +27,7 @@ export default appState => {
// If state is undefined, create window as maximized.
width: windowState.width === undefined ? width : windowState.width,
height: windowState.height === undefined ? height : windowState.height,
icon: 'static/img/tray/default/tray.png',
icon: 'static/img/tray/windows/tray.png',
webPreferences: {
// Disable renderer process's webSecurity on development to enable CORS.
webSecurity: !isDev,
@ -37,7 +36,7 @@ export default appState => {
};
const lbryProto = '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);

View file

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

View file

@ -2,7 +2,6 @@
import '@babel/polyfill';
import keytar from 'keytar';
import SemVer from 'semver';
import url from 'url';
import https from 'https';
import { app, dialog, ipcMain, session, shell } from 'electron';
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', () => {
appState.isQuitting = true;
});

View file

@ -2,6 +2,9 @@
import { Lbryio } from 'lbryinc';
import ReactGA from 'react-ga';
import { history } from './store';
// @if TARGET='app'
import ElectronCookies from '@exponent/electron-cookies';
// @endif
type Analytics = {
pageView: string => void,
@ -15,7 +18,7 @@ let analyticsEnabled: boolean = true;
const analytics: Analytics = {
pageView: path => {
if (analyticsEnabled) {
ReactGA.pageview(path, IS_WEB ? ['web'] : ['desktop']);
ReactGA.pageview(path);
}
},
setUser: user => {
@ -76,18 +79,29 @@ const analytics: Analytics = {
// Initialize google analytics
// Set `debug: true` for debug info
// 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, {
gaOptions: { name: IS_WEB ? 'web' : 'desktop' },
testMode: process.env.NODE_ENV !== 'production',
// debug: true,
cookieDomain: 'auto',
});
// Manually call the first page view
// React Router doesn't include this on `history.listen`
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
// This will include search queries
history.listen(location => {

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

View file

@ -1,15 +1,30 @@
// @flow
import React from 'react';
import { parseURI } from 'lbry-redux';
import classnames from 'classnames';
import Gerbil from './gerbil.png';
type Props = {
thumbnail: ?string,
uri: string,
};
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 (
<div className="channel__thumbnail">
{thumbnail && <img className="channel__thumbnail--custom" src={thumbnail} />}
<div
className={classnames('channel-thumbnail', {
[className]: !thumbnail,
})}
>
{!thumbnail && <img className="channel-thumbnail__default" src={Gerbil} />}
{thumbnail && <img className="channel-thumbnail__custom" src={thumbnail} />}
</div>
);
}

View file

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

View file

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

View file

@ -16,7 +16,7 @@ type Props = {
};
function Paginate(props: Props) {
const { totalPages, loading, location, history, onPageChange } = props;
const { totalPages = 1, loading, location, history, onPageChange } = props;
const { search } = location;
const urlParams = new URLSearchParams(search);
const currentPage = Number(urlParams.get(PAGINATE_PARAM)) || 1;
@ -40,12 +40,10 @@ function Paginate(props: Props) {
}
}
if (totalPages <= 1 || loading) {
return null;
}
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-section>
<ReactPaginate

View file

@ -3,19 +3,25 @@ import { Lbryio } from 'lbryinc';
import * as React from 'react';
import Yrbl from 'component/yrbl';
import Button from 'component/button';
import { withRouter } from 'react-router';
type Props = {
children: React.Node,
history: {
replace: string => void,
},
};
type State = {
hasError: boolean,
};
export default class ErrorBoundary extends React.Component<Props, State> {
class ErrorBoundary extends React.Component<Props, State> {
constructor() {
super();
this.state = { hasError: false };
(this: any).refresh = this.refresh.bind(this);
}
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() {
if (this.state.hasError) {
return (
@ -50,7 +63,7 @@ export default class ErrorBoundary extends React.Component<Props, State> {
button="link"
className="load-screen__button"
label={__('refreshing the app')}
onClick={() => (window.location.href = '/')}
onClick={this.refresh}
/>{' '}
{__('to fix it')}.
</p>
@ -64,3 +77,5 @@ export default class ErrorBoundary extends React.Component<Props, State> {
return this.props.children;
}
}
export default withRouter(ErrorBoundary);

View file

@ -37,7 +37,7 @@ class ExternalLink extends React.PureComponent<Props> {
title={title || href}
label={children}
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 { withRouter } from 'react-router-dom';
import { formatLbryUriForWeb } from 'util/uri';
import get from 'lodash.get';
type Props = {
uri: string,
@ -65,7 +64,6 @@ class FileCard extends React.PureComponent<Props> {
const {
claim,
fileInfo,
metadata,
rewardedContentClaimIds,
obscureNsfw,
claimIsMine,
@ -106,7 +104,6 @@ class FileCard extends React.PureComponent<Props> {
const uri = !pending ? normalizeURI(this.props.uri) : this.props.uri;
const isRewardContent = claim && rewardedContentClaimIds.includes(claim.claim_id);
const height = claim && claim.height;
const handleContextMenu = event => {
event.preventDefault();
event.stopPropagation();
@ -146,7 +143,7 @@ class FileCard extends React.PureComponent<Props> {
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
{isSubscribed && <Icon icon={icons.SUBSCRIPTION} />}
{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>}
</div>
</li>

View file

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

View file

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

View file

@ -1,5 +1,6 @@
// @flow
import * as ICONS from 'constants/icons';
import * as MODALS from 'constants/modal_types';
import React from 'react';
import Button from 'component/button';
import ToolTip from 'component/common/tooltip';
@ -20,7 +21,7 @@ type Props = {
loading: boolean,
costInfo: ?{},
restartDownload: (string, number) => void,
openInShell: string => void,
openModal: (id: string, { path: string }) => void,
purchaseUri: string => void,
pause: () => void,
};
@ -43,14 +44,7 @@ class FileDownloadLink extends React.PureComponent<Props> {
uri: ?string;
render() {
const { fileInfo, downloading, uri, openInShell, purchaseUri, costInfo, loading, pause, claim } = this.props;
const openFile = () => {
if (fileInfo) {
openInShell(fileInfo.download_path);
pause();
}
};
const { fileInfo, downloading, uri, openModal, purchaseUri, costInfo, loading, pause, claim } = this.props;
if (loading || downloading) {
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) {
return (
<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>
);
}

View file

@ -47,8 +47,8 @@ class FileTile extends React.PureComponent<Props> {
if (!isResolvingUri && !claim && uri) resolveUri(uri);
}
componentWillReceiveProps(nextProps: Props) {
const { isResolvingUri, claim, uri, resolveUri } = nextProps;
componentDidUpdate() {
const { isResolvingUri, claim, uri, resolveUri } = this.props;
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>}
{isSubscribed && <Icon icon={ICONS.SUBSCRIPTION} />}
{isRewardContent && <Icon iconColor="red" icon={ICONS.FEATURED} />}
{!claimIsMine && isDownloaded && <Icon icon={ICONS.LOCAL} />}
{!claimIsMine && isDownloaded && <Icon icon={ICONS.DOWNLOAD} />}
{claimIsMine && <Icon icon={ICONS.PUBLISHED} />}
</div>
);

View file

@ -34,12 +34,25 @@ type State = {
contentType?: string,
downloadPath?: string,
fileType?: string,
// Just using `any` because flow isn't working with `fs.createReadStream`
stream?: ({}) => any,
},
};
class MediaPlayer extends React.PureComponent<Props, State> {
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_CONTENT_BASE_URL = 'http://localhost:5278';
@ -276,6 +289,10 @@ class MediaPlayer extends React.PureComponent<Props, State> {
contentType,
downloadPath,
fileType: path.extname(fileName).substring(1),
// Readable stream from file
// @if TARGET='app'
stream: opts => fs.createReadStream(downloadPath, opts),
// @endif
};
// Update state

View file

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

View file

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

View file

@ -28,8 +28,8 @@ type Props = {
language: string,
nsfw: boolean,
contentIsFree: boolean,
price: {
amount: number,
fee: {
amount: string,
currency: string,
},
channel: string,
@ -70,11 +70,14 @@ class PublishForm extends React.PureComponent<Props> {
(this: any).getNewUri = this.getNewUri.bind(this);
}
componentWillMount() {
const { thumbnail } = this.props;
componentDidMount() {
const { thumbnail, name, channel, editingURI } = this.props;
if (!thumbnail) {
this.props.resetThumbnailStatus();
}
if (editingURI) {
this.getNewUri(name, channel);
}
}
getNewUri(name: string, channel: string) {
@ -156,7 +159,7 @@ class PublishForm extends React.PureComponent<Props> {
let previousBidAmount = 0;
if (myClaimForUri) {
previousBidAmount = myClaimForUri.amount;
previousBidAmount = Number(myClaimForUri.amount);
}
const totalAvailableBidAmount = previousBidAmount + balance;
@ -190,7 +193,7 @@ class PublishForm extends React.PureComponent<Props> {
}
handlePublish() {
const { filePath, licenseType, licenseUrl, otherLicenseDescription, myClaimForUri, publish } = this.props;
const { filePath, licenseType, licenseUrl, otherLicenseDescription, publish } = this.props;
let publishingLicense;
switch (licenseType) {
@ -217,10 +220,11 @@ class PublishForm extends React.PureComponent<Props> {
otherLicenseDescription,
name: this.props.name || undefined,
contentIsFree: this.props.contentIsFree,
price: this.props.price,
fee: this.props.fee,
uri: this.props.uri || undefined,
channel: this.props.channel,
isStillEditing: this.props.isStillEditing,
claim: this.props.myClaimForUri,
};
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
return (
!isFormValid && (
<div className="card__content card__subtitle error-text">
<div className="card__content error-text">
{!title && <div>{__('A title is required')}</div>}
{!name && <div>{__('A URL is required')}</div>}
{name && nameError && <div>{__('The URL you created is not valid')}</div>}
@ -292,7 +296,7 @@ class PublishForm extends React.PureComponent<Props> {
language,
nsfw,
contentIsFree,
price,
fee,
channel,
name,
updatePublishForm,
@ -440,11 +444,11 @@ class PublishForm extends React.PureComponent<Props> {
<FormFieldPrice
name="content_cost_amount"
min="0"
price={price}
onChange={newPrice => updatePublishForm({ price: newPrice })}
price={fee}
onChange={newFee => updatePublishForm({ fee: newFee })}
/>
)}
{price.currency !== 'LBC' && (
{fee && fee.currency !== 'LBC' && (
<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.'
@ -581,22 +585,17 @@ class PublishForm extends React.PureComponent<Props> {
</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')}{' '}
<Button button="link" href="https://www.lbry.com/termsofservice" label={__('LBRY Terms of Service')} />.
</div>
</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>
</p>
</section>
</div>

View file

@ -14,7 +14,6 @@ import FileListPublished from 'page/fileListPublished';
import TransactionHistoryPage from 'page/transactionHistory';
import AuthPage from 'page/auth';
import InvitePage from 'page/invite';
import BackupPage from 'page/backup';
import SubscriptionsPage from 'page/subscriptions';
import SearchPage from 'page/search';
import UserHistoryPage from 'page/userHistory';
@ -39,7 +38,6 @@ export default function AppRouter() {
<Switch>
<Route path="/" exact component={DiscoverPage} />
<Route path={`/$/${PAGES.AUTH}`} exact component={AuthPage} />
<Route path={`/$/${PAGES.BACKUP}`} exact component={BackupPage} />
<Route path={`/$/${PAGES.INVITE}`} exact component={InvitePage} />
<Route path={`/$/${PAGES.DOWNLOADED}`} exact component={FileListDownloaded} />
<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)}
</ul>
@ -74,35 +74,30 @@ class SideBar extends React.PureComponent<Props> {
<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)}
</ul>
<ul className="navigation__links navigation__links--bottom">
{[
{
...buildLink(PAGES.HELP, 'Help', ICONS.HELP),
...buildLink(PAGES.HELP, __('Help'), ICONS.HELP),
},
].map(renderLink)}
</ul>

View file

@ -27,7 +27,7 @@ class SocialShare extends React.PureComponent<Props> {
render() {
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 channelClaimId = claim.signing_channel && claim.signing_channel.claim_id;
@ -70,7 +70,7 @@ class SocialShare extends React.PureComponent<Props> {
<React.Fragment>
{speechShareable && (
<div className="card__content">
<label className="card__subtitle">{__('Web link')}</label>
<label className="help">{__('Web link')}</label>
<CopyableText copyable={speechURL} />
<div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}>
@ -98,7 +98,7 @@ class SocialShare extends React.PureComponent<Props> {
</div>
)}
<div className="card__content">
<label className="card__subtitle">{__('LBRY App link')}</label>
<label className="help">{__('LBRY App link')}</label>
<CopyableText copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center">
<ToolTip onComponent body={__('Facebook')}>

View file

@ -1,11 +1,17 @@
import { connect } from 'react-redux';
import { selectClaimedRewardsByTransactionId } from 'lbryinc';
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';
const select = state => ({
rewards: selectClaimedRewardsByTransactionId(state),
mySupports: selectSupportsByOutpoint(state),
myClaims: selectAllMyClaimsByOutpoint(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 DateTime from 'component/dateTime';
import Button from 'component/button';
import { buildURI } from 'lbry-redux';
import { formatLbryUriForWeb } from 'util/uri';
import { buildURI, parseURI } from 'lbry-redux';
type Props = {
transaction: Transaction,
@ -43,6 +42,9 @@ class TransactionListItem extends React.PureComponent<Props> {
render() {
const { reward, transaction, isRevokeable } = this.props;
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 = {
month: 'short',
day: 'numeric',
@ -66,9 +68,9 @@ class TransactionListItem extends React.PureComponent<Props> {
</td>
<td className="table__item--actionable">
{reward && <span>{reward.reward_title}</span>}
{name && claimId && (
<Button constrict button="link" navigate={buildURI({ claimName: name, claimId })}>
{name}
{claimName && claimId && (
<Button button="link" navigate={buildURI({ claimName: claimName, claimId })}>
{claimName}
</Button>
)}
</td>

View file

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

View file

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

View file

@ -28,7 +28,7 @@ class UserVerify extends React.PureComponent<Props> {
return (
<React.Fragment>
<section className="card card--section">
<header className="card__header">
<header className="card__header--flat">
<h1 className="card__title">{__('Final Human Proof')}</h1>
<p className="card__subtitle">
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 { selectDaemonSettings } from 'redux/selectors/settings';
import BackupPage from './view';
import WalletBackup from './view';
const select = 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">
<h2 className="card__title">{__('Balance')}</h2>
<p className="card__subtitle">{__('You currently have')}</p>
</header>
<div className="card__content">
<h3>{__('You currently have')}</h3>
{(balance || balance === 0) && <CreditAmount large badge={false} amount={balance} precision={8} />}
</div>
</section>

View file

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

View file

@ -1,5 +1,5 @@
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 FILE_TIMEOUT = 'file_timeout';
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 { doHideModal } from 'redux/actions/app';
import ModalOpenExternalLink from './view';
import ModalOpenExternalResource from './view';
const perform = dispatch => ({
closeModal: () => dispatch(doHideModal()),
@ -9,4 +9,4 @@ const perform = dispatch => ({
export default connect(
null,
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 Button from 'component/button';
import UserPhoneVerify from 'component/userPhoneVerify';
import { withRouter } from 'react-router-dom';
import { Redirect } from 'react-router';
const LazyUserPhoneNew = React.lazy(() =>
import(/* webpackChunkName: "userPhoneNew" */
@ -13,7 +13,7 @@ const LazyUserPhoneNew = React.lazy(() =>
type Props = {
phone: ?number,
user: {
phone_number: ?number,
is_identity_verified: boolean,
},
closeModal: () => void,
history: { push: string => void },
@ -23,29 +23,30 @@ class ModalPhoneCollection extends React.PureComponent<Props> {
getTitle() {
const { user, phone } = this.props;
if (!user.phone_number && !phone) {
if (!user.is_identity_verified && !phone) {
return __('Enter Your Phone Number');
}
return __('Enter The Verification Code');
}
renderInner() {
const { closeModal, phone, user, history } = this.props;
const { closeModal, phone, user } = this.props;
const cancelButton = <Button button="link" onClick={closeModal} label={__('Not Now')} />;
if (!user.phone_number && !phone) {
if (!user.is_identity_verified && !phone) {
return (
<Suspense fallback={<div />}>
<LazyUserPhoneNew cancelButton={cancelButton} />
</Suspense>
);
} else if (!user.phone_number) {
} else if (!user.is_identity_verified) {
return <UserPhoneVerify cancelButton={cancelButton} />;
}
history.push('/$/rewards');
return closeModal();
closeModal();
return <Redirect to="/$/rewards" />;
}
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 { 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 ModalRemoveFile from './view';
@ -8,6 +13,7 @@ const select = (state, props) => ({
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
title: makeSelectTitleForUri(props.uri)(state),
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
claim: makeSelectClaimForUri(props.uri)(state),
});
const perform = dispatch => ({

View file

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

View file

@ -21,7 +21,7 @@ import ModalConfirmTransaction from 'modal/modalConfirmTransaction';
import ModalSocialShare from 'modal/modalSocialShare';
import ModalSendTip from 'modal/modalSendTip';
import ModalPublish from 'modal/modalPublish';
import ModalOpenExternalLink from 'modal/modalOpenExternalLink';
import ModalOpenExternalResource from 'modal/modalOpenExternalResource';
import ModalConfirmThumbnailUpload from 'modal/modalConfirmThumbnailUpload';
import ModalWalletEncrypt from 'modal/modalWalletEncrypt';
import ModalWalletDecrypt from 'modal/modalWalletDecrypt';
@ -85,8 +85,8 @@ function ModalRouter(props: Props) {
return <ModalSocialShare {...modalProps} />;
case MODALS.PUBLISH:
return <ModalPublish {...modalProps} />;
case MODALS.CONFIRM_EXTERNAL_LINK:
return <ModalOpenExternalLink {...modalProps} />;
case MODALS.CONFIRM_EXTERNAL_RESOURCE:
return <ModalOpenExternalResource {...modalProps} />;
case MODALS.CONFIRM_TRANSACTION:
return <ModalConfirmTransaction {...modalProps} />;
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,
makeSelectThumbnailForUri,
makeSelectCoverForUri,
selectCurrentChannelPage,
} from 'lbry-redux';
import ChannelPage from './view';
@ -12,6 +13,7 @@ const select = (state, props) => ({
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
cover: makeSelectCoverForUri(props.uri)(state),
channelIsMine: makeSelectClaimIsMine(props.uri)(state),
page: selectCurrentChannelPage(state),
});
export default connect(

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,7 +2,7 @@ import * as ACTIONS from 'constants/action_types';
// @if TARGET='app'
import { shell } from 'electron';
// @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 { 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
const myClaimsOutpoints = selectMyClaimsOutpoints(state);
if (abandonClaim && myClaimsOutpoints.indexOf(outpoint) !== -1) {
const byOutpoint = selectFileInfosByOutpoint(state);
const fileInfo = byOutpoint[outpoint];
const [txid, nout] = outpoint.split(':');
if (fileInfo) {
const txid = fileInfo.outpoint.slice(0, -2);
const nout = Number(fileInfo.outpoint.slice(-1));
dispatch(doAbandonClaim(txid, nout));
}
dispatch(doAbandonClaim(txid, Number(nout)));
}
dispatch({

View file

@ -11,6 +11,7 @@ import {
selectPendingById,
selectMyClaimsWithoutChannels,
doError,
isClaimNsfw,
} from 'lbry-redux';
import { doOpenModal } from 'redux/actions/app';
import { selectosNotificationsEnabled } from 'redux/selectors/settings';
@ -42,7 +43,6 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
data: {
uploadThumbnailStatus: THUMBNAIL_STATUSES.READY,
thumbnail: '',
nsfw: false,
},
});
})
@ -52,7 +52,6 @@ export const doResetThumbnailStatus = () => (dispatch: Dispatch) => {
data: {
uploadThumbnailStatus: THUMBNAIL_STATUSES.API_DOWN,
thumbnail: '',
nsfw: false,
},
})
);
@ -152,14 +151,14 @@ export const doPrepareEdit = (claim: StreamClaim, uri: string) => (dispatch: Dis
contentIsFree: !fee.amount,
author,
description,
fee,
fee: { amount: fee.amount, currency: fee.currency },
languages,
thumbnail: thumbnail ? thumbnail.url : null,
title,
uri,
uploadThumbnailStatus: thumbnail ? THUMBNAIL_STATUSES.MANUAL : undefined,
licenseUrl,
replace: true,
nsfw: isClaimNsfw(claim),
};
// Make sure custom liscence's are mapped properly
@ -203,6 +202,7 @@ export const doPublish = (params: PublishParams) => (dispatch: Dispatch, getStat
fee,
uri,
nsfw,
claim,
} = params;
// 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,
bid: number,
file_path?: string,
fee?: { amount: string, currency: string },
tags: Array<string>,
locations?: Array<Location>,
license_url?: string,
thumbnail_url?: string,
release_time?: number,
fee_currency?: string,
fee_amount?: string,
} = {
name,
bid: creditsToString(bid),
title,
license,
license_url: licenseUrl,
languages: [language],
description,
thumbnail_url: thumbnail,
tags: [],
tags: (claim && claim.value.tags) || [],
locations: claim && claim.value.locations,
};
// 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
// `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) {
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) {
publishPayload.channel_id = channelId;
}
if (fee) {
publishPayload.fee = {
currency: fee.currency,
amount: creditsToString(fee.amount),
};
if (!contentIsFree && fee && (fee.currency && Number(fee.amount) > 0)) {
publishPayload.fee_currency = fee.currency;
publishPayload.fee_amount = creditsToString(fee.amount);
}
// Only pass file on new uploads, not metadata only edits.

View file

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

View file

@ -56,19 +56,6 @@
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 {
max-width: 100%;
height: 1.2em;

View file

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

View file

@ -1,7 +1,7 @@
$cover-z-index: 0;
$metadata-z-index: 1;
.channel__cover {
.channel-cover {
background-image: linear-gradient(to right, $lbry-indigo-4, $lbry-cyan-5 80%);
display: flex;
align-items: flex-end;
@ -9,41 +9,62 @@ $metadata-z-index: 1;
color: $lbry-white;
}
.channel__cover--custom {
.channel-cover__custom {
z-index: $cover-z-index;
align-self: flex-start;
position: absolute;
object-fit: cover;
filter: brightness(40%);
filter: brightness(60%);
}
.channel__cover,
.channel__cover--custom {
.channel-cover,
.channel-cover__custom {
height: var(--cover-photo-height);
width: 100%;
}
.channel__thumbnail {
.channel-thumbnail {
position: absolute;
display: flex;
left: var(--spacing-main-padding);
height: 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;
box-shadow: 0px 8px 40px -3px $lbry-black;
}
.channel__thumbnail--custom {
.channel-thumbnail__custom {
width: 100%;
object-fit: cover;
}
.channel__thumbnail,
.channel__thumbnail--custom {
.channel-thumbnail__default {
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);
}
.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 {
// Ensure the profile pic/title sit ontop of the default cover background
z-index: $metadata-z-index;
@ -61,11 +82,6 @@ $metadata-z-index: 1;
.channel__url {
font-size: 1.2rem;
user-select: all;
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
// As styles become hardened here, they _should_ slowly move over to that repo
input,
textarea,
select {
border-radius: var(--input-border-radius);
}
input-submit {
align-items: center;
}
@ -82,6 +88,16 @@ fieldset-group {
&:first-of-type {
input {
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;
border: 1px solid;
border-right: 0;
border-color: $lbry-black;
color: $lbry-gray-4;
[data-mode='dark'] & {
border-color: $lbry-gray-4;
}
}
}
@ -135,6 +156,8 @@ fieldset-group {
input {
border-left: 0;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
padding-left: var(--spacing-xs);
&:focus {
@ -172,10 +195,11 @@ form {
fieldset-section {
input-submit {
input,
select {
&:first-child:not(:focus) {
input {
&:first-child {
border-right-color: transparent;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
@ -193,7 +217,10 @@ fieldset-section {
}
.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 {
background-color: $lbry-white;
margin-top: var(--spacing-vertical-large);
margin-bottom: var(--spacing-vertical-large);
padding: var(--spacing-vertical-large);

View file

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

View file

@ -1,7 +1,9 @@
// M E D I A
// C A R D
.media-card {
@include mediaThumbHoverZoom;
font-size: 1.2rem;
border-radius: var(--card-radius);
.media__title {
margin-bottom: var(--spacing-vertical-small);
@ -10,22 +12,13 @@
.media__properties {
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
// T I L E
.media-tile {
@include mediaThumbHoverZoom;
display: flex;
font-size: 1.5rem;
position: relative;
@ -106,6 +99,7 @@
.media__thumb {
@include thumbnail;
border-radius: var(--card-radius);
&:not(.media__thumb--nsfw) {
background-color: $lbry-gray-2;
@ -266,6 +260,7 @@
.media__info-text {
font-size: 1.15rem;
word-break: break-all;
&:not(:last-of-type) {
margin-bottom: var(--spacing-vertical-large);

View file

@ -35,17 +35,6 @@
}
.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 {
width: 60%;
height: 3rem;

View file

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

View file

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

View file

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

View file

@ -1,8 +1,21 @@
@mixin placeholder {
animation: pulse 2s infinite ease-in-out;
background-color: $lbry-gray-2;
border-radius: var(--card-radius);
html[data-mode='dark'] & {
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-border-size: 1px;
--input-border-radius: 2px;
// Select
--select-height: 30px;
@ -53,7 +54,7 @@ $large-breakpoint: 1921px;
// Card
--card-radius: 5px;
--card-max-width: 1000px;
--card-box-shadow: 0px 8px 20px;
--card-box-shadow: 0px 0px 30px 2px;
// File
--file-tile-media-height: 125px;
@ -76,5 +77,5 @@ $large-breakpoint: 1921px;
// Image
--thumbnail-preview-height: 100px;
--thumbnail-preview-width: 177px;
--cover-photo-height: 250px;
--cover-photo-height: 300px;
}

View file

@ -7,6 +7,10 @@
<body>
<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>
</html>

View file

@ -13,6 +13,12 @@ const UI_ROOT = path.resolve(__dirname, 'src/ui/');
const STATIC_ROOT = path.resolve(__dirname, 'static/');
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'));
let baseConfig = {
@ -32,6 +38,10 @@ let baseConfig = {
node: {
__dirname: false,
},
devServer: {
historyApiFallback: true,
port: WEBPACK_PORT,
},
module: {
rules: [
{
@ -117,6 +127,7 @@ let baseConfig = {
'process.env.NODE_ENV': JSON.stringify(NODE_ENV),
'process.env.SDK_API_URL': JSON.stringify(process.env.SDK_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',
publicPath: '/',
},
devServer: {
historyApiFallback: true,
},
module: {
rules: [
{

View file

@ -830,6 +830,14 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.1.tgz#e93c13942592cf5ef01aa8297444dc192beee52f"
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":
version "16.8.6"
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"
static-extend "^0.1.1"
classnames@^2.2.3, classnames@^2.2.5:
classnames@^2.2.5:
version "2.2.6"
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
@ -3557,13 +3565,6 @@ dom-converter@^0.2:
dependencies:
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:
version "1.2.1"
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"
zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#32916b04e4888c06a9bb2b07c57ce6821a4acf1a:
lbry-redux@lbryio/lbry-redux#02f6918238110726c0b3b4248c61a84ac0b969e3:
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:
proxy-polyfill "0.1.6"
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"
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"
resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7"
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"
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:
version "4.6.0"
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"
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:
version "4.6.0"
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"
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"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
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"
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"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
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"
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"
resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7"
integrity sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==
@ -7578,9 +7569,9 @@ node-releases@^1.1.13:
semver "^5.3.0"
node-sass@^4.11.0:
version "4.11.0"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.11.0.tgz#183faec398e9cbe93ba43362e2768ca988a6369a"
integrity sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==
version "4.12.0"
resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.12.0.tgz#0914f531932380114a30cc5fa4fa63233a25f017"
integrity sha512-A1Iv4oN+Iel6EPv77/HddXErL2a+gZ4uBeZUy+a8O35CFYTXhgA8MgLCWBtwpGZdCvTvQ9d+bQxX/QC36GDPpQ==
dependencies:
async-foreach "^0.1.3"
chalk "^1.1.1"
@ -7589,12 +7580,10 @@ node-sass@^4.11.0:
get-stdin "^4.0.1"
glob "^7.0.3"
in-publish "^2.0.0"
lodash.assign "^4.2.0"
lodash.clonedeep "^4.3.2"
lodash.mergewith "^4.6.0"
lodash "^4.17.11"
meow "^3.7.0"
mkdirp "^0.5.1"
nan "^2.10.0"
nan "^2.13.2"
node-gyp "^3.8.0"
npmlog "^4.0.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"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.24:
psl@^1.1.24, psl@^1.1.28:
version "1.1.31"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184"
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"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0:
punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
@ -9430,19 +9419,7 @@ react-toggle@^4.0.2:
dependencies:
classnames "^2.2.5"
react-virtualized@^9.21.0:
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:
react@^16.8.2:
version "16.8.6"
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
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"
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:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"