moar discovery

This commit is contained in:
Sean Yesmunt 2019-06-17 16:32:38 -04:00
parent 15378594ce
commit dae603339b
91 changed files with 1123 additions and 890 deletions

View file

@ -40,6 +40,8 @@
"postinstall": "electron-builder install-app-deps && node ./build/downloadDaemon.js" "postinstall": "electron-builder install-app-deps && node ./build/downloadDaemon.js"
}, },
"dependencies": { "dependencies": {
"@reach/menu-button": "^0.1.18",
"@reach/tooltip": "^0.2.1",
"electron-dl": "^1.11.0", "electron-dl": "^1.11.0",
"electron-log": "^2.2.12", "electron-log": "^2.2.12",
"electron-updater": "^4.0.6", "electron-updater": "^4.0.6",
@ -119,7 +121,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#291324d03f694c4fefa6967aa7be02d9245596a8", "lbry-redux": "lbryio/lbry-redux#6a447d0542d23b9a37e266f5f85d3bde5297a451",
"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",

View file

@ -105,6 +105,7 @@ class Button extends React.PureComponent<Props> {
exact exact
to={path} to={path}
title={title} title={title}
disabled={disabled}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();
if (onClick) { if (onClick) {

View file

@ -44,7 +44,7 @@ export default class CopyableText extends React.PureComponent<Props> {
onFocus={this.onFocus} onFocus={this.onFocus}
inputButton={ inputButton={
<Button <Button
button="primary" button="inverse"
icon={ICONS.CLIPBOARD} icon={ICONS.CLIPBOARD}
onClick={() => { onClick={() => {
clipboard.writeText(copyable); clipboard.writeText(copyable);

View file

@ -1,5 +1,6 @@
// @flow // @flow
import * as React from 'react'; import type { Node } from 'react';
import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import FileListItem from 'component/fileListItem'; import FileListItem from 'component/fileListItem';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
@ -11,19 +12,31 @@ const SORT_OLD = 'old';
type Props = { type Props = {
uris: Array<string>, uris: Array<string>,
header: React.Node, header: Node,
headerAltControls: React.Node, headerAltControls: Node,
injectedItem?: React.Node, injectedItem?: Node,
loading: boolean, loading: boolean,
noHeader?: boolean, noHeader?: boolean,
slim?: string, slim?: string,
empty?: string, empty?: string,
meta?: Node,
// If using the default header, this is a unique ID needed to persist the state of the filter setting // If using the default header, this is a unique ID needed to persist the state of the filter setting
persistedStorageKey?: string, persistedStorageKey?: string,
}; };
export default function FileList(props: Props) { export default function FileList(props: Props) {
const { uris, header, headerAltControls, injectedItem, loading, persistedStorageKey, noHeader, slim, empty } = props; const {
uris,
header,
headerAltControls,
injectedItem,
loading,
persistedStorageKey,
noHeader,
slim,
empty,
meta,
} = props;
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
const sortedUris = uris && currentSort === SORT_OLD ? uris.reverse() : uris; const sortedUris = uris && currentSort === SORT_OLD ? uris.reverse() : uris;
const hasUris = uris && !!uris.length; const hasUris = uris && !!uris.length;
@ -35,7 +48,7 @@ export default function FileList(props: Props) {
return ( return (
<section className={classnames('file-list')}> <section className={classnames('file-list')}>
{!noHeader && ( {!noHeader && (
<div className="file-list__header"> <div className={classnames('file-list__header', { 'file-list__header--slim': slim })}>
{header || ( {header || (
<FormField <FormField
className="file-list__dropdown" className="file-list__dropdown"
@ -52,6 +65,7 @@ export default function FileList(props: Props) {
<div className="file-list__alt-controls">{headerAltControls}</div> <div className="file-list__alt-controls">{headerAltControls}</div>
</div> </div>
)} )}
{meta && <div className="file-list__meta">{meta}</div>}
{hasUris && ( {hasUris && (
<ul> <ul>
{sortedUris.map((uri, index) => ( {sortedUris.map((uri, index) => (

View file

@ -1,15 +1,15 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectFollowedTags, doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch } from 'lbry-redux'; import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
import FileListDiscover from './view'; import FileListDiscover from './view';
const select = state => ({ const select = state => ({
followedTags: selectFollowedTags(state),
uris: selectLastClaimSearchUris(state), uris: selectLastClaimSearchUris(state),
loading: selectFetchingClaimSearch(state), loading: selectFetchingClaimSearch(state),
}); });
const perform = { const perform = {
doClaimSearch, doClaimSearch,
doToggleTagFollow,
}; };
export default connect( export default connect(

View file

@ -1,8 +1,10 @@
// @flow // @flow
import type { Node } from 'react';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import moment from 'moment';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import FileList from 'component/fileList'; import FileList from 'component/fileList';
import moment from 'moment'; import Tag from 'component/tag';
import usePersistedState from 'util/use-persisted-state'; import usePersistedState from 'util/use-persisted-state';
const TIME_DAY = 'day'; const TIME_DAY = 'day';
@ -26,10 +28,12 @@ type Props = {
tags: Array<string>, tags: Array<string>,
loading: boolean, loading: boolean,
personal: boolean, personal: boolean,
doToggleTagFollow: string => void,
meta?: Node,
}; };
function FileListDiscover(props: Props) { function FileListDiscover(props: Props) {
const { doClaimSearch, uris, tags, loading, personal, injectedItem } = props; const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta } = props;
const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', SEARCH_SORT_YOU); const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', SEARCH_SORT_YOU);
const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING); const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING);
const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', TIME_WEEK); const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', TIME_WEEK);
@ -40,7 +44,7 @@ function FileListDiscover(props: Props) {
const options = {}; const options = {};
const newTags = tagsString.split(','); const newTags = tagsString.split(',');
if ((tags && !personal) || (tags && personal && personalSort === SEARCH_SORT_YOU)) { if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
options.any_tags = newTags; options.any_tags = newTags;
} }
@ -61,19 +65,26 @@ function FileListDiscover(props: Props) {
} }
doClaimSearch(20, options); doClaimSearch(20, options);
}, [personalSort, typeSort, timeSort, doClaimSearch, tagsString]); }, [personal, personalSort, typeSort, timeSort, doClaimSearch, tagsString]);
const header = ( const header = (
<React.Fragment> <h1 className="card__title--flex">
<h1 className={`card__title--flex`}> <FormField
{toCapitalCase(typeSort)} {'For'} className="file-list__dropdown"
</h1> type="select"
name="trending_sort"
value={typeSort}
onChange={e => setTypeSort(e.target.value)}
>
{SEARCH_TYPES.map(type => (
<option key={type} value={type}>
{toCapitalCase(type)}
</option>
))}
</FormField>
<span>{__('For')}</span>
{!personal && tags && tags.length ? ( {!personal && tags && tags.length ? (
tags.map(tag => ( tags.map(tag => <Tag key={tag} name={tag} disabled />)
<span key={tag} className="tag tag--remove" disabled>
{tag}
</span>
))
) : ( ) : (
<FormField <FormField
type="select" type="select"
@ -89,24 +100,11 @@ function FileListDiscover(props: Props) {
))} ))}
</FormField> </FormField>
)} )}
</React.Fragment> </h1>
); );
const headerAltControls = ( const headerAltControls = (
<React.Fragment> <React.Fragment>
<FormField
className="file-list__dropdown"
type="select"
name="trending_sort"
value={typeSort}
onChange={e => setTypeSort(e.target.value)}
>
{SEARCH_TYPES.map(type => (
<option key={type} value={type}>
{toCapitalCase(type)}
</option>
))}
</FormField>
{typeSort === 'top' && ( {typeSort === 'top' && (
<FormField <FormField
className="file-list__dropdown" className="file-list__dropdown"
@ -126,13 +124,16 @@ function FileListDiscover(props: Props) {
); );
return ( return (
<div className="card">
<FileList <FileList
meta={meta}
loading={loading} loading={loading}
uris={uris} uris={uris}
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem} injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
header={header} header={header}
headerAltControls={headerAltControls} headerAltControls={headerAltControls}
/> />
</div>
); );
} }

View file

@ -110,7 +110,7 @@ function FileListItem(props: Props) {
{!slim && ( {!slim && (
<div> <div>
{isChannel && <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />} {isChannel && <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />}
<FileProperties uri={uri} /> {!isChannel && <FileProperties uri={uri} />}
</div> </div>
)} )}
</div> </div>
@ -122,7 +122,7 @@ function FileListItem(props: Props) {
<div>{isChannel ? `${claimsInChannel} ${__('publishes')}` : <DateTime timeAgo uri={uri} />}</div> <div>{isChannel ? `${claimsInChannel} ${__('publishes')}` : <DateTime timeAgo uri={uri} />}</div>
</div> </div>
{!slim && <FileTags uri={uri} />} <FileTags uri={uri} slim={slim} />
</div> </div>
</div> </div>
</li> </li>

View file

@ -15,7 +15,7 @@ type Props = {
}; };
export default function FileProperties(props: Props) { export default function FileProperties(props: Props) {
const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed, isNew } = props; const { uri, downloaded, claimIsMine, rewardedContentClaimIds, isSubscribed } = props;
const { claimId } = parseURI(uri); const { claimId } = parseURI(uri);
const isRewardContent = rewardedContentClaimIds.includes(claimId); const isRewardContent = rewardedContentClaimIds.includes(claimId);
@ -24,7 +24,6 @@ export default function FileProperties(props: Props) {
{isSubscribed && <Icon icon={icons.SUBSCRIPTION} />} {isSubscribed && <Icon icon={icons.SUBSCRIPTION} />}
{!claimIsMine && downloaded && <Icon icon={icons.DOWNLOAD} />} {!claimIsMine && downloaded && <Icon icon={icons.DOWNLOAD} />}
{isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />} {isRewardContent && <Icon iconColor="red" icon={icons.FEATURED} />}
{isNew && <span className="badge badge--alert">{__('NEW')}</span>}
<FilePrice hideFree uri={uri} /> <FilePrice hideFree uri={uri} />
</div> </div>
); );

View file

@ -1,19 +1,25 @@
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import classnames from 'classnames';
import Button from 'component/button'; import Button from 'component/button';
const MAX_TAGS = 4; const SLIM_TAGS = 2;
const NORMAL_TAGS = 4;
const LARGE_TAGS = 10;
type Props = { type Props = {
tags: Array<string>, tags: Array<string>,
followedTags: Array<Tag>, followedTags: Array<Tag>,
large: boolean,
slim: boolean,
}; };
export default function FileTags(props: Props) { export default function FileTags(props: Props) {
const { tags, followedTags } = props; const { tags, followedTags, large, slim } = props;
const numberOfTags = slim ? SLIM_TAGS : large ? LARGE_TAGS : NORMAL_TAGS;
let tagsToDisplay = []; let tagsToDisplay = [];
for (var i = 0; tagsToDisplay.length < MAX_TAGS - 2; i++) { for (var i = 0; tagsToDisplay.length < numberOfTags - 2; i++) {
const tag = followedTags[i]; const tag = followedTags[i];
if (!tag) { if (!tag) {
break; break;
@ -28,7 +34,7 @@ export default function FileTags(props: Props) {
for (var i = 0; i < sortedTags.length; i++) { for (var i = 0; i < sortedTags.length; i++) {
const tag = sortedTags[i]; const tag = sortedTags[i];
if (!tag || tagsToDisplay.length === MAX_TAGS) { if (!tag || tagsToDisplay.length === numberOfTags) {
break; break;
} }
@ -38,11 +44,9 @@ export default function FileTags(props: Props) {
} }
return ( return (
<div className="file-properties"> <div className={classnames('file-properties', { 'file-properties--large': large })}>
{tagsToDisplay.map(tag => ( {tagsToDisplay.map(tag => (
<Button key={tag} navigate={`$/tags?t=${tag}`} className="tag"> <Button key={tag} title={tag} navigate={`$/tags?t=${tag}`} className="tag" label={tag} />
{tag}
</Button>
))} ))}
</div> </div>
); );

View file

@ -1,21 +1,21 @@
import * as SETTINGS from 'constants/settings';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectBalance, SETTINGS } from 'lbry-redux'; import { selectBalance, SETTINGS as LBRY_REDUX_SETTINGS } from 'lbry-redux';
import { formatCredits } from 'util/format-credits'; import { formatCredits } from 'util/format-credits';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app'; import { doSetClientSetting } from 'redux/actions/settings';
import { doDownloadUpgradeRequested } from 'redux/actions/app';
import Header from './view';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import Header from './view';
const select = state => ({ const select = state => ({
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
balance: selectBalance(state), balance: selectBalance(state),
language: makeSelectClientSetting(SETTINGS.LANGUAGE)(state), // trigger redraw on language change language: makeSelectClientSetting(LBRY_REDUX_SETTINGS.LANGUAGE)(state), // trigger redraw on language change
isUpgradeAvailable: selectIsUpgradeAvailable(state),
roundedBalance: formatCredits(selectBalance(state) || 0, 2), roundedBalance: formatCredits(selectBalance(state) || 0, 2),
currentTheme: makeSelectClientSetting(SETTINGS.THEME)(state),
automaticDarkModeEnabled: makeSelectClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED)(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({
downloadUpgradeRequested: () => dispatch(doDownloadUpgradeRequested()), setClientSetting: (key, value) => dispatch(doSetClientSetting(key, value)),
}); });
export default connect( export default connect(

View file

@ -1,9 +1,13 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as SETTINGS from 'constants/settings';
import * as React from 'react'; import * as React from 'react';
import { withRouter } from 'react-router';
import Button from 'component/button'; import Button from 'component/button';
import LbcSymbol from 'component/common/lbc-symbol'; import LbcSymbol from 'component/common/lbc-symbol';
import WunderBar from 'component/wunderbar'; import WunderBar from 'component/wunderbar';
import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
type Props = { type Props = {
autoUpdateDownloaded: boolean, autoUpdateDownloaded: boolean,
@ -11,16 +15,29 @@ type Props = {
isUpgradeAvailable: boolean, isUpgradeAvailable: boolean,
roundedBalance: number, roundedBalance: number,
downloadUpgradeRequested: any => void, downloadUpgradeRequested: any => void,
history: { push: string => void },
currentTheme: string,
automaticDarkModeEnabled: boolean,
setClientSetting: (string, boolean | string) => void,
}; };
const Header = (props: Props) => { const Header = (props: Props) => {
const { autoUpdateDownloaded, downloadUpgradeRequested, isUpgradeAvailable, roundedBalance } = props; const { roundedBalance, history, setClientSetting, currentTheme, automaticDarkModeEnabled } = props;
const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable); function handleThemeToggle() {
if (automaticDarkModeEnabled) {
setClientSetting(SETTINGS.AUTOMATIC_DARK_MODE_ENABLED, false);
}
if (currentTheme === 'dark') {
setClientSetting(SETTINGS.THEME, 'light');
} else {
setClientSetting(SETTINGS.THEME, 'dark');
}
}
return ( return (
<header className="header"> <header className="header">
<div className="title-bar" />
<div className="header__contents"> <div className="header__contents">
<div className="header__navigation"> <div className="header__navigation">
<Button <Button
@ -53,54 +70,55 @@ const Header = (props: Props) => {
<WunderBar /> <WunderBar />
<div className="header__navigation"> <div className="header__navigation">
<Button <Menu>
className="header__navigation-item header__navigation-item--right-action" <MenuButton className="header__navigation-item menu__title">
activeClass="header__navigation-item--active" <Icon icon={ICONS.ACCOUNT} />
label={ {roundedBalance > 0 ? (
roundedBalance > 0 ? (
<React.Fragment> <React.Fragment>
{roundedBalance} <LbcSymbol /> {roundedBalance} <LbcSymbol />
</React.Fragment> </React.Fragment>
) : ( ) : (
__('Account') __('Account')
)
}
icon={ICONS.ACCOUNT}
navigate="/$/account"
/>
<Button
className="header__navigation-item header__navigation-item--right-action"
activeClass="header__navigation-item--active"
description={__('Publish content')}
icon={ICONS.UPLOAD}
iconSize={24}
navigate="/$/publish"
/>
{/* @if TARGET='app' */}
{showUpgradeButton && (
<Button
className="header__navigation-item header__navigation-item--right-action header__navigation-item--upgrade"
icon={ICONS.DOWNLOAD}
iconSize={24}
label={__('Upgrade App')}
onClick={downloadUpgradeRequested}
/>
)} )}
{/* @endif */} </MenuButton>
<MenuList>
<Button <MenuItem className="menu__link" onSelect={() => history.push(`/$/account`)}>
className="header__navigation-item header__navigation-item--right-action" <Icon aria-hidden icon={ICONS.OVERVIEW} />
activeClass="header__navigation-item--active" {__('Overview')}
icon={ICONS.SETTINGS} </MenuItem>
iconSize={24} <MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}>
navigate="/$/settings" <Icon aria-hidden icon={ICONS.WALLET} />
/> {__('Wallet')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
<Icon aria-hidden icon={ICONS.UPLOAD} />
{__('Publish')}
</MenuItem>
</MenuList>
</Menu>
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon icon={ICONS.SETTINGS} />
</MenuButton>
<MenuList>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/settings`)}>
<Icon aria-hidden icon={ICONS.SETTINGS} />
{__('Settings')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/help`)}>
<Icon aria-hidden icon={ICONS.HELP} />
{__('Help')}
</MenuItem>
<MenuItem className="menu__link" onSelect={handleThemeToggle}>
<Icon icon={currentTheme === 'light' ? ICONS.DARK : ICONS.LIGHT} />
{currentTheme === 'light' ? 'Dark' : 'Light'}
</MenuItem>
</MenuList>
</Menu>
</div> </div>
</div> </div>
</header> </header>
); );
}; };
export default Header; export default withRouter(Header);

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { Form, FormField, Submit } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
type FormProps = { type FormProps = {
@ -48,7 +48,7 @@ class FormInviteNew extends React.PureComponent<FormProps, FormState> {
name="email" name="email"
value={this.state.email} value={this.state.email}
error={errorMessage} error={errorMessage}
inputButton={<Submit label="Invite" disabled={isPending} />} inputButton={<Button button="inverse" type="submit" label="Invite" disabled={isPending} />}
onChange={event => { onChange={event => {
this.handleEmailChanged(event); this.handleEmailChanged(event);
}} }}

View file

@ -1,2 +1,16 @@
import { connect } from 'react-redux';
import { selectIsUpgradeAvailable, selectAutoUpdateDownloaded } from 'redux/selectors/app';
import { doDownloadUpgradeRequested } from 'redux/actions/app';
import Page from './view'; import Page from './view';
export default Page;
const select = state => ({
autoUpdateDownloaded: selectAutoUpdateDownloaded(state),
isUpgradeAvailable: selectIsUpgradeAvailable(state),
});
export default connect(
select,
{
doDownloadUpgradeRequested,
}
)(Page);

View file

@ -1,87 +1,34 @@
// @flow // @flow
import * as ICONS from 'constants/icons';
import * as React from 'react'; import * as React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Spinner from 'component/spinner'; import Button from 'component/button';
// time in ms to wait to show loading spinner
const LOADER_TIMEOUT = 1000;
type Props = { type Props = {
children: React.Node | Array<React.Node>, children: React.Node | Array<React.Node>,
pageTitle: ?string,
loading: ?boolean,
className: ?string, className: ?string,
autoUpdateDownloaded: boolean,
isUpgradeAvailable: boolean,
doDownloadUpgradeRequested: () => void,
}; };
type State = { function Page(props: Props) {
showLoader: ?boolean, const { children, className, autoUpdateDownloaded, isUpgradeAvailable, doDownloadUpgradeRequested } = props;
}; const showUpgradeButton = autoUpdateDownloaded || (process.platform === 'linux' && isUpgradeAvailable);
class Page extends React.PureComponent<Props, State> {
constructor() {
super();
this.state = {
showLoader: false,
};
this.loaderTimeout = null;
}
componentDidMount() {
const { loading } = this.props;
if (loading) {
this.beginLoadingTimeout();
}
}
componentDidUpdate(prevProps: Props) {
const { loading } = this.props;
const { showLoader } = this.state;
if (!this.loaderTimeout && !prevProps.loading && loading) {
this.beginLoadingTimeout();
} else if (!loading && this.loaderTimeout) {
clearTimeout(this.loaderTimeout);
if (showLoader) {
this.removeLoader();
}
}
}
componentWillUnmount() {
if (this.loaderTimeout) {
clearTimeout(this.loaderTimeout);
}
}
beginLoadingTimeout() {
this.loaderTimeout = setTimeout(() => {
this.setState({ showLoader: true });
}, LOADER_TIMEOUT);
}
removeLoader() {
this.setState({ showLoader: false });
}
loaderTimeout: ?TimeoutID;
render() {
const { children, loading, className } = this.props;
const { showLoader } = this.state;
return ( return (
<main className={classnames('main', className)}> <main className={classnames('main', className)}>
{!loading && children} {/* @if TARGET='app' */}
{showLoader && ( {showUpgradeButton && (
<div className="main--empty"> <div className="main__status">
<Spinner /> {__('Update ready to install')}
<Button button="alt" icon={ICONS.DOWNLOAD} label={__('Install now')} onClick={doDownloadUpgradeRequested} />
</div> </div>
)} )}
{/* @endif */}
{children}
</main> </main>
); );
}
} }
export default Page; export default Page;

View file

@ -56,7 +56,7 @@ export default class RecommendedContent extends React.PureComponent<Props> {
slim slim
loading={isSearching} loading={isSearching}
uris={recommendedContent} uris={recommendedContent}
header={<span>Related</span>} header={<span>{__('Related')}</span>}
empty={<div className="empty">{__('No related content found')}</div>} empty={<div className="empty">{__('No related content found')}</div>}
/> />
</section> </section>

View file

@ -18,7 +18,7 @@ const RewardLink = (props: Props) => {
const { reward, claimReward, label, isPending, button } = props; const { reward, claimReward, label, isPending, button } = props;
return !reward ? null : ( return !reward ? null : (
<Button <Button
button={button ? 'primary' : 'link'} button={button ? 'inverse' : 'link'}
disabled={isPending} disabled={isPending}
label={isPending ? __('Claiming...') : label || `${__('Get')} ${reward.reward_amount} LBC`} label={isPending ? __('Claiming...') : label || `${__('Get')} ${reward.reward_amount} LBC`}
onClick={() => { onClick={() => {

View file

@ -23,8 +23,8 @@ const RewardListClaimed = (props: Props) => {
} }
return ( return (
<section className="card card--section"> <section className="card">
<header className="card__header"> <header className="table__header">
<h2 className="card__title">Claimed Rewards</h2> <h2 className="card__title">Claimed Rewards</h2>
<p className="card__subtitle"> <p className="card__subtitle">

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectUnclaimedRewardValue, selectFetchingRewards, doRewardList, doFetchRewardedContent } from 'lbryinc'; import { selectUnclaimedRewardValue, selectFetchingRewards, doFetchRewardedContent } from 'lbryinc';
import RewardSummary from './view'; import RewardSummary from './view';
const select = state => ({ const select = state => ({
@ -8,7 +8,6 @@ const select = state => ({
}); });
const perform = dispatch => ({ const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
}); });

View file

@ -2,36 +2,25 @@
import * as React from 'react'; import * as React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CreditAmount from 'component/common/credit-amount'; import CreditAmount from 'component/common/credit-amount';
import BusyIndicator from 'component/common/busy-indicator';
type Props = { type Props = {
unclaimedRewardAmount: number, unclaimedRewardAmount: number,
fetching: boolean, fetching: boolean,
fetchRewards: () => void,
fetchRewardedContent: () => void,
}; };
class RewardSummary extends React.Component<Props> { class RewardSummary extends React.Component<Props> {
componentDidMount() {
this.props.fetchRewards();
this.props.fetchRewardedContent();
}
render() { render() {
const { unclaimedRewardAmount, fetching } = this.props; const { unclaimedRewardAmount, fetching } = this.props;
const hasRewards = unclaimedRewardAmount > 0; const hasRewards = unclaimedRewardAmount > 0;
return ( return (
<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">{__('Rewards')}</h2>
{__('Rewards')} </header>
{fetching && <BusyIndicator />}
</h2>
<p className="card__subtitle"> <p className="card__subtitle">
{!fetching && {fetching && __('You have...')}
(hasRewards ? ( {!fetching && hasRewards ? (
<React.Fragment> <React.Fragment>
{__('You have')} {__('You have')}
&nbsp; &nbsp;
@ -40,13 +29,9 @@ class RewardSummary extends React.Component<Props> {
{__('in unclaimed rewards')}. {__('in unclaimed rewards')}.
</React.Fragment> </React.Fragment>
) : ( ) : (
<React.Fragment> __('You have no rewards available, please check')
{__('There are no rewards available at this time, please check back later')}. )}
</React.Fragment>
))}{' '}
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />.
</p> </p>
</header>
<div className="card__content"> <div className="card__content">
<div className="card__actions"> <div className="card__actions">
@ -55,6 +40,7 @@ class RewardSummary extends React.Component<Props> {
navigate="/$/rewards" navigate="/$/rewards"
label={hasRewards ? __('Claim Rewards') : __('View Rewards')} label={hasRewards ? __('Claim Rewards') : __('View Rewards')}
/> />
<Button button="link" label={__('Learn more')} href="https://lbry.com/faq/rewards" />
</div> </div>
</div> </div>
</section> </section>

View file

@ -33,10 +33,10 @@ const RewardTile = (props: Props) => {
<div className="card__content"> <div className="card__content">
<div className="card__actions"> <div className="card__actions">
{reward.reward_type === rewards.TYPE_GENERATED_CODE && ( {reward.reward_type === rewards.TYPE_GENERATED_CODE && (
<Button button="primary" onClick={openRewardCodeModal} label={__('Enter Code')} /> <Button button="inverse" onClick={openRewardCodeModal} label={__('Enter Code')} />
)} )}
{reward.reward_type === rewards.TYPE_REFERRAL && ( {reward.reward_type === rewards.TYPE_REFERRAL && (
<Button button="primary" navigate="/$/invite" label={__('Go To Invites')} /> <Button button="inverse" navigate="/$/invite" label={__('Go To Invites')} />
)} )}
{reward.reward_type !== rewards.TYPE_REFERRAL && {reward.reward_type !== rewards.TYPE_REFERRAL &&
(claimed ? ( (claimed ? (

View file

@ -0,0 +1,25 @@
import { connect } from 'react-redux';
import {
selectUnclaimedRewardValue,
selectFetchingRewards,
doRewardList,
doFetchRewardedContent,
selectClaimedRewards,
} from 'lbryinc';
import RewardSummary from './view';
const select = state => ({
unclaimedRewardAmount: selectUnclaimedRewardValue(state),
fetching: selectFetchingRewards(state),
rewards: selectClaimedRewards(state),
});
const perform = dispatch => ({
fetchRewards: () => dispatch(doRewardList()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
});
export default connect(
select,
perform
)(RewardSummary);

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,25 @@
// @flow
import React from 'react';
import TotalBackground from './total-background.png';
import useTween from 'util/use-tween';
type Props = {
rewards: Array<Reward>,
};
function RewardTotal(props: Props) {
const { rewards } = props;
const rewardTotal = rewards.reduce((acc, val) => acc + val.reward_amount, 0);
const total = useTween(rewardTotal * 25);
const integer = Math.round(total * rewardTotal);
return (
<section className="card card--section card--reward-total" style={{ backgroundImage: `url(${TotalBackground})` }}>
<span className="card__title">
{integer} LBC {__('Earned From Rewards')}
</span>
</section>
);
}
export default RewardTotal;

View file

@ -17,7 +17,7 @@ import InvitePage from 'page/invite';
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';
import SendCreditsPage from 'page/sendCredits'; import WalletPage from 'page/wallet';
import NavigationHistory from 'page/navigationHistory'; import NavigationHistory from 'page/navigationHistory';
import TagsPage from 'page/tags'; import TagsPage from 'page/tags';
import TagsEditPage from 'page/tagsEdit'; import TagsEditPage from 'page/tagsEdit';
@ -54,11 +54,10 @@ export default function AppRouter() {
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} /> <Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} /> <Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} />
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} /> <Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
<Route path={`/$/${PAGES.SEND}`} exact component={SendCreditsPage} />
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} /> <Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} /> <Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.TAGS}/edit`} exact component={TagsEditPage} /> <Route path={`/$/${PAGES.TAGS}/edit`} exact component={TagsEditPage} />
<Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */} {/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} /> <Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:claimId" exact component={ShowPage} /> <Route path="/:claimName/:claimId" exact component={ShowPage} />

View file

@ -1,9 +1,8 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions'; import { selectSubscriptions } from 'redux/selectors/subscriptions';
import { selectFollowedTags } from 'lbry-redux'; import { selectFollowedTags, SETTINGS } from 'lbry-redux';
import SideBar from './view'; import SideBar from './view';
import { makeSelectClientSetting } from 'redux/selectors/settings'; import { makeSelectClientSetting } from 'redux/selectors/settings';
import { SETTINGS } from 'lbry-redux';
const select = state => ({ const select = state => ({
subscriptions: selectSubscriptions(state), subscriptions: selectSubscriptions(state),

View file

@ -20,7 +20,9 @@ function SideBar(props: Props) {
}); });
const renderLink = linkProps => ( const renderLink = linkProps => (
<Button {...linkProps} key={linkProps.label} className="navigation__link" activeClass="navigation__link--active" /> <li key={linkProps.label}>
<Button {...linkProps} className="navigation__link" activeClass="navigation__link--active" />
</li>
); );
return ( return (
@ -41,31 +43,34 @@ function SideBar(props: Props) {
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.DOWNLOAD), ...buildLink(PAGES.LIBRARY, __('Library'), ICONS.DOWNLOAD),
}, },
].map(renderLink)} ].map(renderLink)}
</ul>
<li>
<Button <Button
navigate="/$/tags/edit" navigate="/$/tags/edit"
iconRight={ICONS.SETTINGS} icon={ICONS.EDIT}
className="navigation__link--title navigation__link" className="navigation__link"
activeClass="navigation__link--active" activeClass="navigation__link--active"
label={__('Following')} label={__('Following')}
/> />
<ul className="tags--vertical navigation__links"> </li>
</ul>
<ul className="navigation__links tags--vertical">
{followedTags.map(({ name }, key) => ( {followedTags.map(({ name }, key) => (
<li key={name}> <li className="navigation__link--indented" key={name}>
<Tag navigate={`/$/tags?t${name}`} name={name} /> <Tag navigate={`/$/tags?t${name}`} name={name} />
</li> </li>
))} ))}
</ul> </ul>
<ul className="navigation__links--small"> <ul className="navigation__links--small">
{subscriptions.map(({ uri, channelName }) => ( {subscriptions.map(({ uri, channelName }, index) => (
<li key={uri} className="navigation__link--indented">
<Button <Button
key={uri}
navigate={uri} navigate={uri}
label={channelName} label={channelName}
className="navigation__link" className="navigation__link"
activeClass="navigation__link--active" activeClass="navigation__link--active"
/> />
</li>
))} ))}
</ul> </ul>
</nav> </nav>

View file

@ -19,7 +19,6 @@ type Props = {
doOpenModal: (id: string) => void, doOpenModal: (id: string) => void,
showSnackBarOnSubscribe: boolean, showSnackBarOnSubscribe: boolean,
doToast: ({ message: string }) => void, doToast: ({ message: string }) => void,
buttonStyle: string,
}; };
export default function SubscribeButton(props: Props) { export default function SubscribeButton(props: Props) {
@ -32,7 +31,6 @@ export default function SubscribeButton(props: Props) {
isSubscribed, isSubscribed,
showSnackBarOnSubscribe, showSnackBarOnSubscribe,
doToast, doToast,
buttonStyle,
} = props; } = props;
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe; const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
@ -44,7 +42,7 @@ export default function SubscribeButton(props: Props) {
<Button <Button
iconColor="red" iconColor="red"
icon={ICONS.SUBSCRIPTION} icon={ICONS.SUBSCRIPTION}
button={buttonStyle || 'alt'} button={'alt'}
label={subscriptionLabel} label={subscriptionLabel}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();

View file

@ -1,35 +1,32 @@
// @flow // @flow
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react';
import React, { Fragment } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'component/common/icon';
import Button from 'component/button'; import Button from 'component/button';
type Props = { type Props = {
name: string, name: string,
type?: string, type?: string,
onClick?: any => any, onClick?: any => any,
disabled: boolean,
}; };
export default function Tag(props: Props) { export default function Tag(props: Props) {
const { name, type, onClick } = props; const { name, onClick, type = 'link', disabled = false } = props;
const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` }; const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` };
return ( return (
<Button <Button
{...clickProps} {...clickProps}
disabled={disabled}
className={classnames('tag', { className={classnames('tag', {
'tag--add': type === 'add', 'tag--add': type === 'add',
'tag--remove': type === 'remove', 'tag--remove': type === 'remove',
})} })}
label={ label={name}
<Fragment> iconSize={12}
{name} iconRight={type !== 'link' && (type === 'remove' ? ICONS.CLOSE : ICONS.ADD)}
{type && <Icon className="tag__action-label" icon={type === 'remove' ? ICONS.CLOSE : ICONS.ADD} />}
</Fragment>
}
/> />
); );
} }

View file

@ -37,6 +37,7 @@ export default function TagSelect(props: Props) {
{title} {title}
{showClose && !hasClosed && <Button button="close" icon={ICONS.CLOSE} onClick={handleClose} />} {showClose && !hasClosed && <Button button="close" icon={ICONS.CLOSE} onClick={handleClose} />}
</h2> </h2>
<p className="help">{__("The tags you follow will change what's trending for you.")}</p>
<div className="card__content"> <div className="card__content">
<ul className="tags--remove"> <ul className="tags--remove">

View file

@ -6,6 +6,7 @@ import {
selectSupportsByOutpoint, selectSupportsByOutpoint,
selectTransactionListFilter, selectTransactionListFilter,
doSetTransactionListFilter, doSetTransactionListFilter,
selectIsFetchingTransactions,
} from 'lbry-redux'; } from 'lbry-redux';
import TransactionList from './view'; import TransactionList from './view';
@ -14,6 +15,7 @@ const select = state => ({
mySupports: selectSupportsByOutpoint(state), mySupports: selectSupportsByOutpoint(state),
myClaims: selectAllMyClaimsByOutpoint(state), myClaims: selectAllMyClaimsByOutpoint(state),
filterSetting: selectTransactionListFilter(state), filterSetting: selectTransactionListFilter(state),
loading: selectIsFetchingTransactions(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -73,9 +73,7 @@ class TransactionListItem extends React.PureComponent<Props> {
<td className="table__item--actionable"> <td className="table__item--actionable">
{reward && <span>{reward.reward_title}</span>} {reward && <span>{reward.reward_title}</span>}
{claimName && claimId && ( {claimName && claimId && (
<Button button="link" navigate={buildURI({ claimName: claimName, claimId })}> <Button button="link" navigate={buildURI({ claimName: claimName, claimId })} label={claimName} />
{claimName}
</Button>
)} )}
</td> </td>

View file

@ -7,17 +7,21 @@ import Button from 'component/button';
import FileExporter from 'component/common/file-exporter'; import FileExporter from 'component/common/file-exporter';
import { TRANSACTIONS } from 'lbry-redux'; import { TRANSACTIONS } from 'lbry-redux';
import TransactionListItem from './internal/transaction-list-item'; import TransactionListItem from './internal/transaction-list-item';
import RefreshTransactionButton from 'component/transactionRefreshButton';
import Spinner from 'component/spinner';
type Props = { type Props = {
emptyMessage: ?string, emptyMessage: ?string,
slim?: boolean, filterSetting: string,
transactions: Array<Transaction>, loading: boolean,
rewards: {},
openModal: (id: string, { nout: number, txid: string }) => void,
mySupports: {}, mySupports: {},
myClaims: any, myClaims: any,
filterSetting: string, openModal: (id: string, { nout: number, txid: string }) => void,
rewards: {},
setTransactionFilter: string => void, setTransactionFilter: string => void,
slim?: boolean,
title: string,
transactions: Array<Transaction>,
}; };
class TransactionList extends React.PureComponent<Props> { class TransactionList extends React.PureComponent<Props> {
@ -53,8 +57,7 @@ class TransactionList extends React.PureComponent<Props> {
} }
render() { render() {
const { emptyMessage, rewards, transactions, slim, filterSetting } = this.props; const { emptyMessage, rewards, transactions, slim, filterSetting, title, loading } = this.props;
// The shorter "recent transactions" list shouldn't be filtered // The shorter "recent transactions" list shouldn't be filtered
const transactionList = slim ? transactions : transactions.filter(this.filterTransaction); const transactionList = slim ? transactions : transactions.filter(this.filterTransaction);
@ -65,9 +68,23 @@ class TransactionList extends React.PureComponent<Props> {
return ( return (
<React.Fragment> <React.Fragment>
<header className="card__header"> <header className="table__header">
<h2 className="card__title card__title--flex-between">
<span>
{title}
{loading && <Spinner type="small" />}
</span>
<div className="card__actions">
{slim && (
<Button button="link" className="button--alt" navigate="/$/transactions" label={__('Full History')} />
)}
<RefreshTransactionButton />
</div>
</h2>
</header>
{!slim && !!transactions.length && ( {!slim && !!transactions.length && (
<div className="card__actions card__actions--between card__actions--top-space"> <header className="card__header table__header">
<div className="card__actions card__actions--between">
<FileExporter <FileExporter
data={transactionList} data={transactionList}
label={__('Export')} label={__('Export')}
@ -100,9 +117,12 @@ class TransactionList extends React.PureComponent<Props> {
</FormField> </FormField>
</Form> </Form>
</div> </div>
)}
</header> </header>
{!transactionList.length && <p className="card__subtitle">{emptyMessage || __('No transactions to list.')}</p>} )}
{!loading && !transactionList.length && (
<p className="main--empty empty">{emptyMessage || __('No transactions.')}</p>
)}
{!!transactionList.length && ( {!!transactionList.length && (
<React.Fragment> <React.Fragment>

View file

@ -1,17 +1,9 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { doFetchTransactions, selectRecentTransactions, doFetchClaimListMine } from 'lbry-redux';
doFetchTransactions,
selectRecentTransactions,
selectHasTransactions,
selectIsFetchingTransactions,
doFetchClaimListMine,
} from 'lbry-redux';
import TransactionListRecent from './view'; import TransactionListRecent from './view';
const select = state => ({ const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactions: selectRecentTransactions(state), transactions: selectRecentTransactions(state),
hasTransactions: selectHasTransactions(state),
}); });
const perform = dispatch => ({ const perform = dispatch => ({

View file

@ -1,10 +1,6 @@
// @flow // @flow
import * as icons from 'constants/icons'; import React from 'react';
import React, { Fragment } from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';
import TransactionList from 'component/transactionList'; import TransactionList from 'component/transactionList';
import RefreshTransactionButton from 'component/transactionRefreshButton';
type Props = { type Props = {
fetchTransactions: () => void, fetchTransactions: () => void,
@ -23,42 +19,15 @@ class TransactionListRecent extends React.PureComponent<Props> {
} }
render() { render() {
const { fetchingTransactions, hasTransactions, transactions } = this.props; const { transactions } = this.props;
return ( return (
<section className="card card--section"> <section className="card card__content">
<header className="card__header card__header--flat">
<h2 className="card__title card__title--flex-between">
{__('Recent Transactions')}
<RefreshTransactionButton />
</h2>
</header>
{fetchingTransactions && !hasTransactions && (
<div className="card__content">
<BusyIndicator message={__('Loading transactions')} />
</div>
)}
{!fetchingTransactions && !hasTransactions && (
<div className="card__content">
<p className="card__subtitle">{__('No transactions.')}</p>
</div>
)}
{hasTransactions && (
<Fragment>
<div className="card__content">
<TransactionList <TransactionList
slim slim
title={__('Recent Transactions')}
transactions={transactions} transactions={transactions}
emptyMessage={__("Looks like you don't have any recent transactions.")} emptyMessage={__("Looks like you don't have any recent transactions.")}
/> />
</div>
<div className="card__actions">
<Button button="primary" navigate="/$/transactions" label={__('Full History')} icon={icons.HISTORY} />
</div>
</Fragment>
)}
</section> </section>
); );
} }

View file

@ -0,0 +1,18 @@
import { connect } from 'react-redux';
import { selectEmailToVerify, doUserResendVerificationEmail, doUserCheckEmailVerified, selectUser } from 'lbryinc';
import UserEmailVerify from './view';
const select = state => ({
email: selectEmailToVerify(state),
user: selectUser(state),
});
const perform = dispatch => ({
resendVerificationEmail: email => dispatch(doUserResendVerificationEmail(email)),
checkEmailVerified: () => dispatch(doUserCheckEmailVerified()),
});
export default connect(
select,
perform
)(UserEmailVerify);

View file

@ -0,0 +1,60 @@
// @flow
import * as React from 'react';
import Button from 'component/button';
import { FormField } from 'component/common/form';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
type Props = {
cancelButton: React.Node,
email: string,
resendVerificationEmail: string => void,
checkEmailVerified: () => void,
user: {
has_verified_email: boolean,
},
};
function UserEmail(props: Props) {
const { email, user } = props;
let isVerified = false;
if (user) {
isVerified = user.has_verified_email;
}
return (
<section className="card card--section">
{!email && <UserEmailNew />}
{user && email && !isVerified && <UserEmailVerify />}
{email && isVerified && (
<React.Fragment>
<div className="card__header">
<h2 className="card__title">{__('Email')}</h2>
<p className="card__subtitle">
{email && isVerified && __('Your email has been successfully verified')}
{!email && __('')}.
</p>
</div>
{isVerified && (
<FormField
type="text"
className="form-field--copyable"
readOnly
label={__('Your Email')}
value={email}
inputButton={<Button button="inverse" label={__('Change')} />}
/>
)}
<p className="help">
{`${__(
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.'
)} `}
</p>
</React.Fragment>
)}
</section>
);
}
export default UserEmail;

View file

@ -72,6 +72,7 @@ class UserEmailNew extends React.PureComponent<Props, State> {
/> />
</Form> </Form>
<div className="card__actions">{cancelButton}</div> <div className="card__actions">{cancelButton}</div>
<p className="help">{__('Your email address will never be sold and you can unsubscribe at any time.')}</p>
</React.Fragment> </React.Fragment>
); );
} }

View file

@ -56,7 +56,7 @@ class UserEmailVerify extends React.PureComponent<Props> {
</header> </header>
<div className="card__content"> <div className="card__content">
<p> <p className="card__subtitle">
{__('An email was sent to')} {email}.{' '} {__('An email was sent to')} {email}.{' '}
{__('Follow the link and you will be good to go. This will update automatically.')} {__('Follow the link and you will be good to go. This will update automatically.')}
</p> </p>

View file

@ -1,5 +1,4 @@
// @flow // @flow
import * as icons from 'constants/icons';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
@ -27,7 +26,7 @@ class WalletAddress extends React.PureComponent<Props, State> {
(this: any).toggleQR = this.toggleQR.bind(this); (this: any).toggleQR = this.toggleQR.bind(this);
} }
componentWillMount() { componentDidMount() {
const { checkAddressIsMine, receiveAddress, getNewAddress } = this.props; const { checkAddressIsMine, receiveAddress, getNewAddress } = this.props;
if (!receiveAddress) { if (!receiveAddress) {
getNewAddress(); getNewAddress();
@ -62,9 +61,8 @@ class WalletAddress extends React.PureComponent<Props, State> {
<div className="card__content"> <div className="card__content">
<div className="card__actions"> <div className="card__actions">
<Button <Button
button="primary" button="inverse"
label={__('Get New Address')} label={__('Get New Address')}
icon={icons.REFRESH}
onClick={getNewAddress} onClick={getNewAddress}
disabled={gettingNewAddress} disabled={gettingNewAddress}
/> />

View file

@ -19,11 +19,9 @@ const WalletBalance = (props: Props) => {
</header> </header>
<div className="card__content"> <div className="card__content">
<h3>{__('You currently have')}</h3> <h3>{__('You currently have')}</h3>
{(balance || balance === 0) && (
<span className="card__content--large"> <span className="card__content--large">
<CreditAmount badge={false} amount={balance} precision={8} /> {(balance || balance === 0) && <CreditAmount badge={false} amount={balance} precision={8} />}
</span> </span>
)}
</div> </div>
</section> </section>
); );

View file

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { Form, FormField } from 'component/common/form'; import { Form, FormField } from 'component/common/form';
import { Formik } from 'formik'; import { Formik } from 'formik';
@ -82,7 +82,7 @@ class WalletSend extends React.PureComponent<Props> {
</div> </div>
<div className="card__actions"> <div className="card__actions">
<Button <Button
button="primary" button="inverse"
type="submit" type="submit"
label={__('Send')} label={__('Send')}
disabled={ disabled={

View file

@ -24,6 +24,8 @@ export const CHANNEL = 'AtSign';
export const REFRESH = 'RefreshCw'; export const REFRESH = 'RefreshCw';
export const HISTORY = 'Clock'; export const HISTORY = 'Clock';
export const HOME = 'Home'; export const HOME = 'Home';
export const OVERVIEW = 'Activity';
export const WALLET = 'List';
export const PHONE = 'Phone'; export const PHONE = 'Phone';
export const COMPLETE = 'Check'; export const COMPLETE = 'Check';
export const COMPLETED = 'CheckCircle'; export const COMPLETED = 'CheckCircle';
@ -65,3 +67,6 @@ export const MUSIC_ALBUM = 'Disc';
export const MUSIC_ARTIST = 'Mic'; export const MUSIC_ARTIST = 'Mic';
export const MUSIC_SONG = 'Music'; export const MUSIC_SONG = 'Music';
export const MUSIC_EQUALIZER = 'Sliders'; export const MUSIC_EQUALIZER = 'Sliders';
export const LIGHT = 'Sun';
export const DARK = 'Moon';
export const AUTO = 'Clock';

View file

@ -19,3 +19,4 @@ export const SUBSCRIPTIONS = 'subscriptions';
export const SEARCH = 'search'; export const SEARCH = 'search';
export const TRANSACTIONS = 'transactions'; export const TRANSACTIONS = 'transactions';
export const TAGS = 'tags'; export const TAGS = 'tags';
export const WALLET = 'wallet';

14
src/ui/constants/tags.js Normal file
View file

@ -0,0 +1,14 @@
export const defaultFollowedTags = [
'blockchain',
'news',
'learning',
'technology',
'automotive',
'economics',
'food',
'science',
'art',
'nature',
];
export const defaultKnownTags = ['beliefs', 'funny', 'gaming', 'pop culture', 'music', 'sports', 'weapons'];

View file

@ -1,3 +1,9 @@
import WalletPage from './view'; import { connect } from 'react-redux';
import AccountPage from './view';
export default WalletPage; const select = state => ({});
export default connect(
select,
null
)(AccountPage);

View file

@ -1,24 +1,24 @@
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import WalletBalance from 'component/walletBalance';
import RewardSummary from 'component/rewardSummary'; import RewardSummary from 'component/rewardSummary';
import TransactionListRecent from 'component/transactionListRecent'; import RewardTotal from 'component/rewardTotal';
import WalletAddress from 'component/walletAddress';
import Page from 'component/page'; import Page from 'component/page';
import UnsupportedOnWeb from 'component/common/unsupported-on-web'; import UnsupportedOnWeb from 'component/common/unsupported-on-web';
import WalletSend from 'component/walletSend'; import UserEmail from 'component/userEmail';
import InvitePage from 'page/invite';
const WalletPage = () => ( const WalletPage = () => (
<Page> <Page>
{IS_WEB && <UnsupportedOnWeb />} {IS_WEB && <UnsupportedOnWeb />}
<div className={classnames({ 'card--disabled': IS_WEB })}> <div className={classnames({ 'card--disabled': IS_WEB })}>
<div className="columns"> <div className="columns">
<WalletBalance /> <UserEmail />
<div>
<RewardSummary /> <RewardSummary />
<RewardTotal />
</div> </div>
<WalletAddress /> </div>
<WalletSend /> <InvitePage />
<TransactionListRecent />
</div> </div>
</Page> </Page>
); );

View file

@ -1,19 +1,10 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { selectEmailToVerify, selectUser } from 'lbryinc';
selectAuthenticationIsPending,
selectEmailToVerify,
selectUserIsVerificationCandidate,
selectUser,
selectUserIsPending,
selectIdentityVerifyIsPending,
} from 'lbryinc';
import AuthPage from './view'; import AuthPage from './view';
const select = state => ({ const select = state => ({
isPending: selectAuthenticationIsPending(state) || selectUserIsPending(state) || selectIdentityVerifyIsPending(state),
email: selectEmailToVerify(state), email: selectEmailToVerify(state),
user: selectUser(state), user: selectUser(state),
isVerificationCandidate: selectUserIsVerificationCandidate(state),
}); });
export default connect( export default connect(

View file

@ -1,16 +1,12 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import BusyIndicator from 'component/common/busy-indicator'; import UserEmail from 'component/userEmail';
import Button from 'component/button';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
import UserVerify from 'component/userVerify'; import UserVerify from 'component/userVerify';
import Page from 'component/page'; import Page from 'component/page';
type Props = { type Props = {
isPending: boolean, isPending: boolean,
email: string, email: string,
pathAfterAuth: string,
location: UrlLocation, location: UrlLocation,
history: { push: string => void }, history: { push: string => void },
user: ?{ user: ?{
@ -21,12 +17,12 @@ type Props = {
}; };
class AuthPage extends React.PureComponent<Props> { class AuthPage extends React.PureComponent<Props> {
componentWillMount() { componentDidMount() {
this.navigateIfAuthenticated(this.props); this.navigateIfAuthenticated(this.props);
} }
componentWillReceiveProps(nextProps: Props) { componentDidUpdate() {
this.navigateIfAuthenticated(nextProps); this.navigateIfAuthenticated(this.props);
} }
navigateIfAuthenticated = (props: Props) => { navigateIfAuthenticated = (props: Props) => {
@ -40,42 +36,9 @@ class AuthPage extends React.PureComponent<Props> {
} }
}; };
renderMain() {
const { email, isPending, user } = this.props;
if (isPending) {
return [<BusyIndicator message={__('Authenticating')} />, true];
} else if (user && !user.has_verified_email && !email) {
return [<UserEmailNew />, true];
} else if (user && !user.has_verified_email) {
return [<UserEmailVerify />, true];
} else if (user && !user.is_identity_verified) {
return [<UserVerify />, false];
}
return [<span className="empty">{__('No further steps.')}</span>, true];
}
render() { render() {
const [innerContent, useTemplate] = this.renderMain(); const { user, email } = this.props;
return <Page>{user && email && !user.is_identity_verified ? <UserVerify /> : <UserEmail />}</Page>;
return (
<Page>
{useTemplate ? (
<section className="card card--section">
{innerContent}
<p className="help">
{`${__(
'This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards and may be used to sync usage data across devices.'
)} `}
<Button button="link" navigate="/" label={__('Return home.')} />
</p>
</section>
) : (
innerContent
)}
</Page>
);
} }
} }

View file

@ -11,7 +11,7 @@ type Props = {
function DiscoverPage(props: Props) { function DiscoverPage(props: Props) {
const { followedTags } = props; const { followedTags } = props;
return ( return (
<Page className="card"> <Page>
<FileListDiscover <FileListDiscover
personal personal
tags={followedTags.map(tag => tag.name)} tags={followedTags.map(tag => tag.name)}

View file

@ -19,6 +19,7 @@ import FileDownloadLink from 'component/fileDownloadLink';
import classnames from 'classnames'; import classnames from 'classnames';
import getMediaType from 'util/get-media-type'; import getMediaType from 'util/get-media-type';
import RecommendedContent from 'component/recommendedContent'; import RecommendedContent from 'component/recommendedContent';
import FileTags from 'component/fileTags';
type Props = { type Props = {
claim: StreamClaim, claim: StreamClaim,
@ -67,6 +68,8 @@ class FilePage extends React.Component<Props> {
(this: any).viewerContainer = React.createRef(); (this: any).viewerContainer = React.createRef();
} }
viewerContainer: { current: React.ElementRef<any> };
componentDidMount() { componentDidMount() {
const { const {
uri, uri,
@ -181,7 +184,7 @@ class FilePage extends React.Component<Props> {
return ( return (
<Page className="main--file-page"> <Page className="main--file-page">
<div className="grid-area--content"> <div className="grid-area--content card">
{!fileInfo && insufficientCredits && ( {!fileInfo && insufficientCredits && (
<div className="media__insufficient-credits help--warning"> <div className="media__insufficient-credits help--warning">
{__( {__(
@ -213,38 +216,11 @@ class FilePage extends React.Component<Props> {
<div className="card__media-text">{__("Sorry, looks like we can't preview this file.")}</div> <div className="card__media-text">{__("Sorry, looks like we can't preview this file.")}</div>
</div> </div>
))} ))}
<Button
className="media__uri"
button="alt"
label={uri}
onClick={() => {
clipboard.writeText(uri);
showToast({
message: __('Copied'),
});
}}
/>
</div> </div>
<div className="grid-area--info media__content media__content--large"> <div className="grid-area--info media__content media__content--large">
<h1 className="media__title media__title--large">{title}</h1> <h1 className="media__title media__title--large">{title}</h1>
<div className="file-properties">
{isRewardContent && (
<Icon
size={20}
iconColor="red"
icon={icons.FEATURED}
// Figure out how to get the tooltip to overlap the navbar on the file page and I will love you
// https://stackoverflow.com/questions/6421966/css-overflow-x-visible-and-overflow-y-hidden-causing-scrollbar-issue
// https://spee.ch/4/overflow-issue
// tooltip="bottom"
/>
)}
{nsfw && <div className="badge badge--nsfw">MATURE</div>}
<FilePrice badge uri={normalizeURI(uri)} />
</div>
<div className="media__actions media__actions--between"> <div className="media__actions media__actions--between">
<div className="media__subtext media__subtext--large"> <div className="media__subtext media__subtext--large">
<div className="media__subtitle__channel"> <div className="media__subtitle__channel">
@ -305,10 +281,39 @@ class FilePage extends React.Component<Props> {
</div> </div>
<div className="media__info--large"> <div className="media__info--large">
<FileTags uri={uri} large />
<FileDetails uri={uri} /> <FileDetails uri={uri} />
</div> </div>
</div> </div>
<div className="grid-area--related"> <div className="grid-area--related">
<div className="media__uri-wrapper">
<Button
className="media__uri"
button="alt"
label={uri}
onClick={() => {
clipboard.writeText(uri);
showToast({
message: __('Copied'),
});
}}
/>
<div className="file-properties">
{isRewardContent && (
<Icon
size={20}
iconColor="red"
icon={icons.FEATURED}
// Figure out how to get the tooltip to overlap the navbar on the file page and I will love you
// https://stackoverflow.com/questions/6421966/css-overflow-x-visible-and-overflow-y-hidden-causing-scrollbar-issue
// https://spee.ch/4/overflow-issue
// tooltip="bottom"
/>
)}
{nsfw && <div className="badge badge--nsfw">MATURE</div>}
<FilePrice badge uri={normalizeURI(uri)} />
</div>
</div>
<RecommendedContent uri={uri} /> <RecommendedContent uri={uri} />
</div> </div>
</Page> </Page>

View file

@ -130,7 +130,7 @@ class HelpPage extends React.PureComponent<Props, State> {
<div className="card__content"> <div className="card__content">
<div className="card__actions"> <div className="card__actions">
<Button href="https://lbry.com/faq" label={__('Read the FAQ')} icon={icons.HELP} button="primary" /> <Button href="https://lbry.com/faq" label={__('Read the FAQ')} icon={icons.HELP} button="inverse" />
</div> </div>
</div> </div>
</section> </section>
@ -147,7 +147,7 @@ class HelpPage extends React.PureComponent<Props, State> {
<div className="card__content"> <div className="card__content">
<div className="card__actions"> <div className="card__actions">
<Button button="primary" label={__('Join Our Chat')} icon={icons.CHAT} href="https://chat.lbry.com" /> <Button button="inverse" label={__('Join Our Chat')} icon={icons.CHAT} href="https://chat.lbry.com" />
</div> </div>
</div> </div>
</section> </section>
@ -167,7 +167,7 @@ class HelpPage extends React.PureComponent<Props, State> {
navigate="/$/report" navigate="/$/report"
label={__('Submit a Bug Report/Feature Request')} label={__('Submit a Bug Report/Feature Request')}
icon={icons.REPORT} icon={icons.REPORT}
button="primary" button="inverse"
/> />
</div> </div>
@ -188,8 +188,8 @@ class HelpPage extends React.PureComponent<Props, State> {
<div className="card__content"> <div className="card__content">
<div className="card__actions"> <div className="card__actions">
<Button button="primary" label={__('Open Log')} onClick={() => this.openLogFile(dataDirectory)} /> <Button button="inverse" label={__('Open Log')} onClick={() => this.openLogFile(dataDirectory)} />
<Button button="primary" label={__('Open Log Folder')} onClick={() => shell.openItem(dataDirectory)} /> <Button button="inverse" label={__('Open Log Folder')} onClick={() => shell.openItem(dataDirectory)} />
</div> </div>
</div> </div>
</section> </section>
@ -198,8 +198,8 @@ class HelpPage extends React.PureComponent<Props, State> {
<BackupSection /> <BackupSection />
{/* @endif */} {/* @endif */}
<section className="card card--section"> <section className="card">
<header className="card__header"> <header className="table__header">
<h2 className="card__title">{__('About')}</h2> <h2 className="card__title">{__('About')}</h2>
{this.state.upgradeAvailable !== null && this.state.upgradeAvailable ? ( {this.state.upgradeAvailable !== null && this.state.upgradeAvailable ? (

View file

@ -3,7 +3,6 @@ import React from 'react';
import BusyIndicator from 'component/common/busy-indicator'; import BusyIndicator from 'component/common/busy-indicator';
import InviteNew from 'component/inviteNew'; import InviteNew from 'component/inviteNew';
import InviteList from 'component/inviteList'; import InviteList from 'component/inviteList';
import Page from 'component/page';
type Props = { type Props = {
isPending: boolean, isPending: boolean,
@ -27,7 +26,7 @@ class InvitePage extends React.PureComponent<Props> {
const { isPending, isFailed } = this.props; const { isPending, isFailed } = this.props;
return ( return (
<Page> <div className="">
{isPending && <BusyIndicator message={__('Checking your invite status')} />} {isPending && <BusyIndicator message={__('Checking your invite status')} />}
{!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>} {!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}
{!isPending && !isFailed && ( {!isPending && !isFailed && (
@ -36,7 +35,7 @@ class InvitePage extends React.PureComponent<Props> {
<InviteList /> <InviteList />
</React.Fragment> </React.Fragment>
)} )}
</Page> </div>
); );
} }
} }

View file

@ -129,7 +129,7 @@ class RewardsPage extends PureComponent<Props> {
</p> </p>
</section> </section>
<div className="card__list--rewards">{this.renderCustomRewardCode()}</div> <div className="card__list">{this.renderCustomRewardCode()}</div>
</Fragment> </Fragment>
); );
} }
@ -138,7 +138,7 @@ class RewardsPage extends PureComponent<Props> {
return ( return (
<div <div
className={classnames('card__list--rewards', { className={classnames('card__list', {
'card--disabled': isNotEligible, 'card--disabled': isNotEligible,
})} })}
> >

View file

@ -1,3 +0,0 @@
import SendReceivePage from './view';
export default SendReceivePage;

View file

@ -1,18 +0,0 @@
import React from 'react';
import classnames from 'classnames';
import WalletSend from 'component/walletSend';
import WalletAddress from 'component/walletAddress';
import Page from 'component/page';
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
const SendReceivePage = () => (
<Page>
{IS_WEB && <UnsupportedOnWeb />}
<div className={classnames({ 'card--disabled': IS_WEB })}>
<WalletSend />
<WalletAddress />
</div>
</Page>
);
export default SendReceivePage;

View file

@ -1,19 +1,19 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import {
selectSubscriptionClaims,
selectSubscriptions, selectSubscriptions,
selectSubscriptionsBeingFetched, selectSubscriptionsBeingFetched,
selectIsFetchingSubscriptions, selectIsFetchingSubscriptions,
selectSuggestedChannels, selectSuggestedChannels,
} from 'redux/selectors/subscriptions'; } from 'redux/selectors/subscriptions';
import { doFetchMySubscriptions, doFetchRecommendedSubscriptions } from 'redux/actions/subscriptions'; import { doFetchMySubscriptions, doFetchRecommendedSubscriptions } from 'redux/actions/subscriptions';
import { selectLastClaimSearchUris, doClaimSearch } from 'lbry-redux';
import SubscriptionsPage from './view'; import SubscriptionsPage from './view';
const select = state => ({ const select = state => ({
loading: selectIsFetchingSubscriptions(state) || Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length), loading: selectIsFetchingSubscriptions(state) || Boolean(Object.keys(selectSubscriptionsBeingFetched(state)).length),
subscribedChannels: selectSubscriptions(state), subscribedChannels: selectSubscriptions(state),
subscriptionContent: selectSubscriptionClaims(state),
suggestedSubscriptions: selectSuggestedChannels(state), suggestedSubscriptions: selectSuggestedChannels(state),
uris: selectLastClaimSearchUris(state),
}); });
export default connect( export default connect(
@ -21,5 +21,6 @@ export default connect(
{ {
doFetchMySubscriptions, doFetchMySubscriptions,
doFetchRecommendedSubscriptions, doFetchRecommendedSubscriptions,
doClaimSearch,
} }
)(SubscriptionsPage); )(SubscriptionsPage);

View file

@ -1,53 +1,78 @@
// @flow // @flow
import React, { useEffect, useState } from 'react'; import * as PAGES from 'constants/pages';
import React, { useEffect } from 'react';
import Page from 'component/page'; import Page from 'component/page';
import FileList from 'component/fileList'; import FileList from 'component/fileList';
import Button from 'component/button'; import Button from 'component/button';
type Props = { type Props = {
subscribedChannels: Array<string>, // The channels a user is subscribed to subscribedChannels: Array<{ uri: string }>, // The channels a user is subscribed to
subscriptionContent: Array<{ uri: string, ...StreamClaim }>,
suggestedSubscriptions: Array<{ uri: string }>, suggestedSubscriptions: Array<{ uri: string }>,
loading: boolean, loading: boolean,
doFetchMySubscriptions: () => void, doFetchMySubscriptions: () => void,
doFetchRecommendedSubscriptions: () => void, doFetchRecommendedSubscriptions: () => void,
location: { search: string },
history: { push: string => void },
doClaimSearch: (number, {}) => void,
uris: Array<string>,
}; };
export default function SubscriptionsPage(props: Props) { export default function SubscriptionsPage(props: Props) {
const { const {
subscriptionContent,
subscribedChannels, subscribedChannels,
doFetchMySubscriptions, doFetchMySubscriptions,
doFetchRecommendedSubscriptions, doFetchRecommendedSubscriptions,
suggestedSubscriptions, suggestedSubscriptions,
loading, loading,
location,
history,
doClaimSearch,
uris,
} = props; } = props;
const hasSubscriptions = !!subscribedChannels.length; const hasSubscriptions = !!subscribedChannels.length;
const [showSuggested, setShowSuggested] = useState(!hasSubscriptions); const { search } = location;
const urlParams = new URLSearchParams(search);
const viewingSuggestedSubs = urlParams.get('view');
function onClick() {
let url = `/$/${PAGES.SUBSCRIPTIONS}`;
if (!viewingSuggestedSubs) {
url += '?view=discover';
}
history.push(url);
}
useEffect(() => { useEffect(() => {
doFetchMySubscriptions(); doFetchMySubscriptions();
doFetchRecommendedSubscriptions(); doFetchRecommendedSubscriptions();
}, [doFetchMySubscriptions, doFetchRecommendedSubscriptions]); }, [doFetchMySubscriptions, doFetchRecommendedSubscriptions]);
const idString = subscribedChannels.map(channel => channel.uri.split('#')[1]).join(',');
useEffect(() => {
const ids = idString.split(',');
const options = {
channel_ids: ids,
};
doClaimSearch(20, options);
}, [idString, doClaimSearch]);
return ( return (
<Page> <Page>
<div className="card"> <div className="card">
<FileList <FileList
loading={loading} loading={loading}
header={<h1>{showSuggested ? __('Discover New Channels') : __('Latest From Your Subscriptions')}</h1>} header={<h1>{viewingSuggestedSubs ? __('Discover New Channels') : __('Latest From Your Subscriptions')}</h1>}
headerAltControls={ headerAltControls={
<Button <Button
button="alt" button="link"
label={showSuggested ? hasSubscriptions && __('View Your Subscriptions') : __('Find New Channels')} label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Subscriptions') : __('Find New Channels')}
onClick={() => setShowSuggested(!showSuggested)} onClick={() => onClick()}
/> />
} }
uris={ uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
showSuggested
? suggestedSubscriptions.map(sub => sub.uri)
: subscriptionContent.map(sub => sub.permanent_url)
}
/> />
</div> </div>
</Page> </Page>

View file

@ -1,14 +1,14 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectFollowedTags } from 'lbry-redux'; import { selectFollowedTags, doToggleTagFollow } from 'lbry-redux';
import Tags from './view'; import Tags from './view';
const select = state => ({ const select = state => ({
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
}); });
const perform = {};
export default connect( export default connect(
select, select,
perform {
doToggleTagFollow,
}
)(Tags); )(Tags);

View file

@ -2,25 +2,42 @@
import React from 'react'; import React from 'react';
import Page from 'component/page'; import Page from 'component/page';
import FileListDiscover from 'component/fileListDiscover'; import FileListDiscover from 'component/fileListDiscover';
import Button from 'component/button';
type Props = { type Props = {
location: { search: string }, location: { search: string },
followedTags: Array<Tag>,
doToggleTagFollow: string => void,
}; };
function TagsPage(props: Props) { function TagsPage(props: Props) {
const { const {
location: { search }, location: { search },
followedTags,
doToggleTagFollow,
} = props; } = props;
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
const tagsQuery = urlParams.get('t') || ''; const tagsQuery = urlParams.get('t') || '';
const tags = tagsQuery.split(','); const tags = tagsQuery.split(',');
// Eventually allow more than one tag on this page
// Restricting to one to make follow/unfollow simpler
const tag = tags[0];
const isFollowing = followedTags.map(({ name }) => name).includes(tag);
return ( return (
<Page> <Page>
<div className="card"> <FileListDiscover
<FileListDiscover tags={tags} /> tags={tags}
</div> meta={
<Button
button="alt"
onClick={() => doToggleTagFollow(tag)}
label={isFollowing ? __('Unfollow this tag') : __('Follow this tag')}
/>
}
/>
</Page> </Page>
); );
} }

View file

@ -1,14 +1,8 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { import { doFetchTransactions, selectTransactionItems, doFetchClaimListMine } from 'lbry-redux';
doFetchTransactions,
selectTransactionItems,
selectIsFetchingTransactions,
doFetchClaimListMine,
} from 'lbry-redux';
import TransactionHistoryPage from './view'; import TransactionHistoryPage from './view';
const select = state => ({ const select = state => ({
fetchingTransactions: selectIsFetchingTransactions(state),
transactions: selectTransactionItems(state), transactions: selectTransactionItems(state),
}); });

View file

@ -1,10 +1,8 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import BusyIndicator from 'component/common/busy-indicator';
import TransactionList from 'component/transactionList'; import TransactionList from 'component/transactionList';
import Page from 'component/page'; import Page from 'component/page';
import RefreshTransactionButton from 'component/transactionRefreshButton';
import UnsupportedOnWeb from 'component/common/unsupported-on-web'; import UnsupportedOnWeb from 'component/common/unsupported-on-web';
type Props = { type Props = {
@ -23,36 +21,17 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
} }
render() { render() {
const { fetchingTransactions, transactions } = this.props; const { transactions } = this.props;
return ( return (
<Page> <Page>
{IS_WEB && <UnsupportedOnWeb />} {IS_WEB && <UnsupportedOnWeb />}
<section <section
className={classnames('card card--section', { className={classnames('card', {
'card--disabled': IS_WEB, 'card--disabled': IS_WEB,
})} })}
> >
<header className="card__header card__header--flat"> <TransactionList transactions={transactions} title={__('Transaction History')} />
<h2 className="card__title card__title--flex-between ">
{__('Transaction History')}
<RefreshTransactionButton />
</h2>
</header>
{fetchingTransactions && !transactions.length ? (
<div className="card__content">
<BusyIndicator message={__('Loading transactions')} />
</div>
) : (
''
)}
{transactions && transactions.length ? (
<div className="card__content">
<TransactionList transactions={transactions} />
</div>
) : (
<div className="card__content">{__("Looks like you don't have any transactions")}</div>
)}
</section> </section>
</Page> </Page>
); );

View file

@ -1,7 +1,6 @@
// @flow // @flow
import React from 'react'; import React from 'react';
import Page from 'component/page'; import Page from 'component/page';
import UserHistory from 'component/navigationHistoryRecent';
import DownloadList from 'page/fileListDownloaded'; import DownloadList from 'page/fileListDownloaded';
type Props = {}; type Props = {};
@ -10,7 +9,6 @@ class UserHistoryPage extends React.PureComponent<Props> {
render() { render() {
return ( return (
<Page> <Page>
<UserHistory {...this.props} />
<DownloadList {...this.props} /> <DownloadList {...this.props} />
</Page> </Page>
); );

View file

@ -0,0 +1,11 @@
import { connect } from 'react-redux';
import Wallet from './view';
const select = state => ({});
const perform = dispatch => ({});
export default connect(
select,
perform
)(Wallet);

View file

@ -0,0 +1,17 @@
import React from 'react';
import WalletBalance from 'component/walletBalance';
import WalletSend from 'component/walletSend';
import WalletAddress from 'component/walletAddress';
import TransactionListRecent from 'component/transactionListRecent';
import Page from 'component/page';
const WalletPage = () => (
<Page>
<WalletBalance />
<TransactionListRecent />
<WalletSend />
<WalletAddress />
</Page>
);
export default WalletPage;

View file

@ -6,7 +6,7 @@ import {
searchReducer, searchReducer,
walletReducer, walletReducer,
notificationsReducer, notificationsReducer,
tagsReducer, tagsReducerBuilder,
} from 'lbry-redux'; } from 'lbry-redux';
import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc'; import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc';
import appReducer from 'redux/reducers/app'; import appReducer from 'redux/reducers/app';
@ -15,6 +15,17 @@ import contentReducer from 'redux/reducers/content';
import settingsReducer from 'redux/reducers/settings'; import settingsReducer from 'redux/reducers/settings';
import subscriptionsReducer from 'redux/reducers/subscriptions'; import subscriptionsReducer from 'redux/reducers/subscriptions';
import publishReducer from 'redux/reducers/publish'; import publishReducer from 'redux/reducers/publish';
import { defaultKnownTags, defaultFollowedTags } from 'constants/tags';
function getDefaultKnownTags() {
return defaultFollowedTags.concat(defaultKnownTags).reduce(
(tagsMap, tag) => ({
...tagsMap,
[tag]: { name: tag },
}),
{}
);
}
export default history => export default history =>
combineReducers({ combineReducers({
@ -34,7 +45,7 @@ export default history =>
settings: settingsReducer, settings: settingsReducer,
stats: statsReducer, stats: statsReducer,
subscriptions: subscriptionsReducer, subscriptions: subscriptionsReducer,
tags: tagsReducer, tags: tagsReducerBuilder({ followedTags: defaultFollowedTags, knownTags: getDefaultKnownTags() }),
user: userReducer, user: userReducer,
wallet: walletReducer, wallet: walletReducer,
}); });

View file

@ -16,7 +16,7 @@ const selectState = state => state.subscriptions || {};
// Returns the list of channel uris a user is subscribed to // Returns the list of channel uris a user is subscribed to
export const selectSubscriptions = createSelector( export const selectSubscriptions = createSelector(
selectState, selectState,
state => state.subscriptions state => state.subscriptions.sort((a, b) => a.channelName.localeCompare(b.channelName))
); );
// Fetching list of users subscriptions // Fetching list of users subscriptions

View file

@ -23,12 +23,12 @@
@import 'component/file-render'; @import 'component/file-render';
@import 'component/form-field'; @import 'component/form-field';
@import 'component/header'; @import 'component/header';
@import 'component/icon';
@import 'component/item-list'; @import 'component/item-list';
@import 'component/main'; @import 'component/main';
@import 'component/markdown-editor'; @import 'component/markdown-editor';
@import 'component/markdown-preview'; @import 'component/markdown-preview';
@import 'component/media'; @import 'component/media';
@import 'component/menu-button';
@import 'component/modal'; @import 'component/modal';
@import 'component/navigation'; @import 'component/navigation';
@import 'component/notice'; @import 'component/notice';

View file

@ -4,6 +4,9 @@
@extend .badge; @extend .badge;
background-color: lighten($lbry-teal-5, 55%); background-color: lighten($lbry-teal-5, 55%);
color: darken($lbry-teal-5, 20%); color: darken($lbry-teal-5, 20%);
svg {
stroke: $lbry-teal-5;
}
[data-mode='dark'] & { [data-mode='dark'] & {
color: lighten($lbry-teal-5, 60%); color: lighten($lbry-teal-5, 60%);

View file

@ -2,6 +2,7 @@
.button { .button {
display: inline-block; display: inline-block;
font-weight: 400;
svg { svg {
stroke-width: 1.9; stroke-width: 1.9;
@ -17,7 +18,9 @@
} }
} }
.button--primary { // Fix this in lbry/components
.button--primary:not(:hover) {
background-color: $lbry-teal-4;
svg { svg {
color: white; color: white;
} }
@ -28,10 +31,12 @@
height: 5rem; height: 5rem;
width: 5rem; width: 5rem;
border-radius: 2.5rem; border-radius: 2.5rem;
&:not(:hover) {
background-color: $lbry-teal-4;
}
} }
.button--primary, .button--primary,
.button--alt,
.button--inverse { .button--inverse {
height: var(--button-height); height: var(--button-height);
line-height: var(--button-height); line-height: var(--button-height);
@ -42,6 +47,18 @@
box-sizing: border-box; box-sizing: border-box;
} }
.button--inverse {
border-color: $lbry-teal-4;
&:hover {
color: $lbry-white;
background-color: $lbry-teal-4;
.icon {
stroke: $lbry-white;
}
}
}
.button--alt { .button--alt {
padding: 0; padding: 0;
} }
@ -67,7 +84,7 @@
transition: all var(--transition-duration) var(--transition-style); transition: all var(--transition-duration) var(--transition-style);
&:hover { &:hover {
background-color: $lbry-red-3; background-color: $lbry-black;
color: $lbry-white; color: $lbry-white;
border-radius: var(--card-radius); border-radius: var(--card-radius);
} }
@ -78,12 +95,16 @@
align-items: flex-start; align-items: flex-start;
} }
.button__content {
display: flex;
align-items: center;
min-width: 0;
}
.button__label { .button__label {
// white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
// display: flex;
// align-items: center;
} }
// Handle icons on the left or right side of the button label // Handle icons on the left or right side of the button label
@ -91,8 +112,3 @@ svg + .button__label,
.button__label + svg { .button__label + svg {
margin-left: var(--spacing-miniscule); margin-left: var(--spacing-miniscule);
} }
.button__content {
display: flex;
align-items: center;
}

View file

@ -1,10 +1,11 @@
.card { .card {
background-color: $lbry-white; background-color: $lbry-white;
margin-bottom: var(--spacing-xlarge); margin-bottom: var(--spacing-large);
position: relative; position: relative;
border-radius: var(--card-radius); border-radius: var(--card-radius);
box-shadow: var(--card-box-shadow) $lbry-gray-1; box-shadow: var(--card-box-shadow) $lbry-gray-1;
overflow: hidden; overflow: hidden;
font-size: 1.25rem;
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: lighten($lbry-black, 5%); background-color: lighten($lbry-black, 5%);
@ -41,6 +42,13 @@
justify-content: space-between; justify-content: space-between;
} }
.card--reward-total {
background-repeat: no-repeat;
background-size: cover;
// justify-content: space-between;
color: $lbry-white;
}
.card--modal { .card--modal {
box-shadow: none; box-shadow: none;
} }
@ -50,6 +58,7 @@
.card__actions { .card__actions {
display: flex; display: flex;
align-items: center;
font-size: 1.15rem; font-size: 1.15rem;
> *:not(:last-child) { > *:not(:last-child) {
@ -83,19 +92,23 @@
padding-top: var(--spacing-small); padding-top: var(--spacing-small);
} }
.card__actions--table {
padding: var(--spacing-medium);
}
// C A R D // C A R D
// C O N T E N T // C O N T E N T
.card__content { .card__content {
font-size: 1.25rem;
p:not(:last-child) { p:not(:last-child) {
margin-bottom: var(--spacing-medium); margin-bottom: var(--spacing-medium);
} }
} }
.card__content--large { .card__content--large {
font-size: 4rem; font-size: 3rem;
line-height: 1.5;
font-weight: 700;
} }
// C A R D // C A R D
@ -113,41 +126,13 @@
// L I S T // L I S T
.card__list { .card__list {
display: grid;
grid-gap: var(--spacing-medium);
margin-top: var(--spacing-large);
// 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: 1801px) and (max-width: 2000px) {
// grid-template-columns: repeat(auto-fill, minmax(calc(100% / 8), 1fr));
// }
@media (min-width: 1551px) {
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 7), 1fr));
}
@media (min-width: 1200px) and (max-width: 1550px) {
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 6), 1fr));
}
grid-template-columns: repeat(auto-fill, minmax(calc(100% / 5), 1fr));
}
.card__list--rewards {
column-count: 2; column-count: 2;
column-gap: var(--spacing-medium); column-gap: var(--spacing-medium);
margin-bottom: var(--spacing-large); display: block;
.card { .card {
display: inline-block; display: inline-block;
margin: 0 0 var(--spacing-medium); margin: 0 0 var(--spacing-medium);
width: 100%;
} }
} }
@ -185,6 +170,7 @@
.card__subtitle { .card__subtitle {
@extend .help; @extend .help;
color: darken($lbry-gray-5, 25%); color: darken($lbry-gray-5, 25%);
background-color: lighten($lbry-gray-1, 5%);
font-size: 1.15rem; font-size: 1.15rem;
margin-bottom: var(--spacing-small); margin-bottom: var(--spacing-small);
flex: 1; flex: 1;
@ -200,8 +186,7 @@
} }
[data-mode='dark'] & { [data-mode='dark'] & {
// TODO: dark background-color: darken($lbry-gray-5, 20%);
// background-color: darken($lbry-gray-5, 20%);
} }
} }

View file

@ -23,7 +23,7 @@
.button--view, .button--view,
.button--play { .button--play {
background-color: $lbry-green-3; background-color: $lbry-teal-2;
} }
} }
} }
@ -104,6 +104,7 @@
} }
.content__view--container { .content__view--container {
background-color: black;
width: 100%; width: 100%;
height: 100%; height: 100%;
top: 0; top: 0;

View file

@ -1,8 +1,9 @@
.file-list__header { .file-list__header {
display: flex; display: flex;
align-items: center; align-items: center;
min-height: 4rem; height: 4.5rem;
padding: var(--spacing-medium); padding: var(--spacing-medium);
font-size: 1rem; // Ensures select & header text have same font-size
color: $lbry-white; color: $lbry-white;
border-top-left-radius: var(--card-radius); border-top-left-radius: var(--card-radius);
border-top-right-radius: var(--card-radius); border-top-right-radius: var(--card-radius);
@ -14,6 +15,20 @@
fieldset-section { fieldset-section {
margin-bottom: 0; margin-bottom: 0;
} }
// Normal link buttons are too dark on the black file list background
.button--link {
color: $lbry-teal-3;
&:hover {
color: $lbry-teal-1;
}
}
}
.file-list__header--slim {
height: 3rem;
font-size: 1em;
} }
.file-list__dropdown { .file-list__dropdown {
@ -53,6 +68,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: auto; margin-left: auto;
font-size: 1.4em;
& > * { & > * {
margin-left: var(--spacing-small); margin-left: var(--spacing-small);
@ -80,10 +96,6 @@
flex-shrink: 0; flex-shrink: 0;
margin-right: var(--spacing-medium); margin-right: var(--spacing-medium);
} }
.media__thumb--profile {
width: 6rem;
}
} }
.file-list__item--injected, .file-list__item--injected,
@ -135,3 +147,8 @@
.file-list__item-tags { .file-list__item-tags {
margin-left: 0; margin-left: 0;
} }
.file-list__meta {
padding: var(--spacing-medium);
background-color: lighten($lbry-teal-5, 55%);
}

View file

@ -3,11 +3,21 @@
position: relative; position: relative;
align-items: center; align-items: center;
& > *:not(:first-child) { & > *:not(:last-child) {
margin-left: var(--spacing-small); margin-right: var(--spacing-small);
} }
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;
} }
} }
.file-properties--large {
flex-wrap: wrap;
font-size: 18px;
margin: var(--spacing-small) 0;
& > * {
margin-top: var(--spacing-small);
}
}

View file

@ -176,15 +176,15 @@ fieldset-group {
form { form {
[type='button'], [type='button'],
[type='submit'] { [type='submit'] {
&.button--primary { &.button--inverse {
&:not(:hover) { &:not(:hover) {
background-color: $lbry-teal-5; background-color: transparent;
border-color: $lbry-teal-5; border-color: $lbry-black;
color: $lbry-black;
} }
&:hover { &:hover {
background-color: $lbry-teal-3; background-color: $lbry-teal-4;
border-color: $lbry-teal-3;
} }
} }
} }
@ -203,6 +203,7 @@ fieldset-section {
// input-submit is connected to a button // input-submit is connected to a button
// The input height needs to match the button height to lineup correctly // The input height needs to match the button height to lineup correctly
// Other inputs are fine since they are on their own and are used under different circumstances // Other inputs are fine since they are on their own and are used under different circumstances
input[type='email'],
input[type='text'] { input[type='text'] {
height: var(--button-height); height: var(--button-height);
@ -218,6 +219,7 @@ fieldset-section {
border-bottom-left-radius: 0; border-bottom-left-radius: 0;
border-top-right-radius: var(--input-border-radius); border-top-right-radius: var(--input-border-radius);
border-bottom-right-radius: var(--input-border-radius); border-bottom-right-radius: var(--input-border-radius);
border-color: $lbry-black;
} }
} }

View file

@ -5,11 +5,14 @@
width: 100%; width: 100%;
background-color: $lbry-white; background-color: $lbry-white;
border-bottom: 1px solid $lbry-gray-1; border-bottom: 1px solid $lbry-gray-1;
box-shadow: var(--card-box-shadow) $lbry-gray-1;
padding-left: var(--spacing-large);
padding-right: var(--spacing-large);
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: mix($lbry-black, $lbry-gray-3, 90%); background-color: mix($lbry-black, $lbry-gray-3, 90%);
color: $lbry-white; color: $lbry-white;
border-bottom: none; border-bottom: none;
box-shadow: var(--card-box-shadow) $lbry-black;
} }
} }
@ -17,7 +20,6 @@
width: 100%; width: 100%;
height: calc(var(--header-height) - 1px); height: calc(var(--header-height) - 1px);
max-width: var(--page-max-width); max-width: var(--page-max-width);
padding-left: var(--spacing-medium);
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
margin: auto; margin: auto;
@ -25,15 +27,14 @@
.header__navigation { .header__navigation {
display: flex; display: flex;
justify-content: space-between;
&:last-of-type { &:last-of-type {
padding-left: var(--spacing-small); width: calc(var(--side-nav-width) + var(--spacing-medium));
width: var(--side-nav-width);
}
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;
} }
}
} }
.header__navigation-arrows { .header__navigation-arrows {
@ -49,7 +50,7 @@
border-radius: 0; border-radius: 0;
svg { svg {
stroke: $lbry-black; stroke: $lbry-gray-5;
} }
&:hover { &:hover {
@ -109,28 +110,15 @@
} }
.header__navigation-item--right-action { .header__navigation-item--right-action {
&:first-of-type { align-self: flex-end;
margin-right: auto; margin-left: auto;
} padding: 0 var(--spacing-small);
&:not(:first-of-type) {
padding: 0 var(--spacing-medium);
}
&:last-of-type {
margin-right: 0;
}
} }
.header__navigation-item--upgrade { .header__navigation-item--upgrade {
background-color: $lbry-teal-5; color: $lbry-teal-5;
color: $lbry-white;
svg { svg {
stroke: $lbry-white; stroke: $lbry-teal-5;
}
&:hover {
background-color: $lbry-teal-4;
} }
} }

View file

@ -1,11 +0,0 @@
// Not all icons are created equally... at least the react-feather ones aren't
// Minor adjustments to ensure icons line up vertically
.icon--Flag,
.icon--Home {
top: -2px;
}
.icon--Heart {
top: -1px;
}

View file

@ -2,8 +2,9 @@
position: absolute; position: absolute;
min-height: 100vh; min-height: 100vh;
width: 100vw; width: 100vw;
padding-top: var(--spacing-main-padding); padding-top: var(--header-height);
padding-left: var(--spacing-medium); padding-left: var(--spacing-large);
padding-right: var(--spacing-large);
background-color: mix($lbry-white, $lbry-gray-1, 70%); background-color: mix($lbry-white, $lbry-gray-1, 70%);
display: flex; display: flex;
@ -18,7 +19,7 @@
width: 100%; width: 100%;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
margin-top: var(--spacing-main-padding); margin-top: var(--spacing-large);
position: relative; position: relative;
} }
@ -36,7 +37,6 @@
.main--file-page { .main--file-page {
display: grid; display: grid;
grid-gap: var(--spacing-large);
grid-template-rows: auto 1fr; grid-template-rows: auto 1fr;
grid-template-columns: 1fr auto; grid-template-columns: 1fr auto;
max-width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding)); max-width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding));
@ -57,7 +57,20 @@
} }
.grid-area--related { .grid-area--related {
grid-area: related; grid-area: related;
width: 40rem; min-width: 30rem;
max-width: 35rem;
}
@media (max-width: 600px) {
grid-template-areas:
'content'
'info'
'related';
.grid-area--related {
grid-area: related;
width: auto;
}
} }
} }
@ -69,3 +82,15 @@
padding-bottom: 100px; padding-bottom: 100px;
text-align: center; text-align: center;
} }
.main__status {
@extend .help;
display: flex;
justify-content: space-between;
background-color: $lbry-teal-4;
color: $lbry-white;
svg {
stroke: $lbry-white;
}
}

View file

@ -85,9 +85,13 @@
font-family: Consolas, 'Lucida Console', 'Source Sans', monospace; font-family: Consolas, 'Lucida Console', 'Source Sans', monospace;
} }
a { a,
color: $lbry-blue-1; button {
display: inline-block; display: inline-block;
.button__label {
white-space: normal;
}
} }
// Lists // Lists

View file

@ -45,13 +45,6 @@
background-image: linear-gradient(to bottom right, $lbry-teal-3, $lbry-grape-5 100%); background-image: linear-gradient(to bottom right, $lbry-teal-3, $lbry-grape-5 100%);
} }
.media__thumb--profile {
// height: var(--channel-thumbnail-width);
width: 70px;
height: 70px;
background-size: cover;
}
// M E D I A // M E D I A
// T I T L E // T I T L E
@ -69,16 +62,16 @@
margin-right: var(--spacing-small); margin-right: var(--spacing-small);
} }
.media__uri-wrapper {
display: flex;
justify-content: space-between;
margin-bottom: var(--spacing-small);
}
.media__uri { .media__uri {
font-size: 1.1rem; font-size: 1.1rem;
padding-bottom: 5px; min-width: 0;
opacity: 0.6; margin-right: var(--spacing-small);
user-select: all;
height: 2rem;
// position: absolute;
// top: 2rem;
// transform: translateY(-2rem);
// margin-bottom: -50px;
} }
.media__insufficient-credits { .media__insufficient-credits {
@ -107,7 +100,6 @@
.media__action-group--large { .media__action-group--large {
display: flex; display: flex;
align-items: flex-end;
margin-top: var(--spacing-small); margin-top: var(--spacing-small);
margin-bottom: var(--spacing-small); margin-bottom: var(--spacing-small);
@ -190,7 +182,7 @@
.media__info--large { .media__info--large {
border-top: 1px solid $lbry-gray-1; border-top: 1px solid $lbry-gray-1;
padding-top: var(--spacing-medium); margin-top: var(--spacing-medium);
html[data-mode='dark'] & { html[data-mode='dark'] & {
border-color: rgba($lbry-gray-5, 0.2); border-color: rgba($lbry-gray-5, 0.2);
@ -212,6 +204,11 @@
&.media__info-text--center { &.media__info-text--center {
text-align: center; text-align: center;
} }
.button__label {
text-align: left;
white-space: normal;
}
} }
.media__info-title { .media__info-title {

View file

@ -6,9 +6,9 @@
} }
.navigation { .navigation {
position: fixed;
width: var(--side-nav-width); width: var(--side-nav-width);
font-size: 1.4rem; font-size: 1.4rem;
position: fixed;
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;
@ -22,11 +22,11 @@
.navigation__links--small { .navigation__links--small {
@extend .navigation__links; @extend .navigation__links;
font-size: 1.2rem; font-size: 1.2rem;
margin-top: var(--spacing-small);
} }
.navigation__link { .navigation__link {
display: block; display: block;
line-height: 1.75;
position: relative; position: relative;
text-align: left; text-align: left;
transition: color 0.2s; transition: color 0.2s;
@ -34,6 +34,7 @@
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
color: lighten($lbry-black, 20%); color: lighten($lbry-black, 20%);
margin-top: var(--spacing-miniscule);
&:hover { &:hover {
color: $lbry-teal-4; color: $lbry-teal-4;
@ -112,3 +113,7 @@
} }
} }
} }
.navigation__link--indented {
padding-left: 2rem;
}

View file

@ -46,6 +46,7 @@
.spinner--small { .spinner--small {
height: 10px; height: 10px;
display: inline-block;
.rect { .rect {
width: 3px; width: 3px;

View file

@ -2,6 +2,8 @@
table, table,
.table { .table {
margin-bottom: var(--spacing-small);
[data-mode='dark'] & { [data-mode='dark'] & {
background-color: transparent; background-color: transparent;
@ -9,17 +11,23 @@ table,
border-bottom: 2px solid $lbry-white; border-bottom: 2px solid $lbry-white;
} }
} }
th,
td {
padding-left: var(--spacing-large);
}
} }
td { td {
max-width: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} }
tr { .table__header {
padding: 0 var(--spacing-small); margin: var(--spacing-large);
& + .table__header {
margin-top: 0;
}
} }
.table__item--actionable { .table__item--actionable {
@ -45,6 +53,17 @@ tr {
.table--transactions { .table--transactions {
table-layout: fixed; table-layout: fixed;
td:nth-of-type(3) {
// Only add ellipsis to the links in the table
// We still want to show the entire message if a TX includes one
a,
button {
@include constrict(10rem);
vertical-align: bottom;
display: inline-block;
}
}
} }
.table--rewards { .table--rewards {

View file

@ -11,15 +11,6 @@ $main: $lbry-teal-5;
} }
} }
.tags,
.tags--remove,
.tags--add {
.button__label {
display: flex;
align-items: center;
}
}
.tags--remove { .tags--remove {
@extend .tags; @extend .tags;
margin-bottom: var(--spacing-large); margin-bottom: var(--spacing-large);
@ -53,40 +44,34 @@ $main: $lbry-teal-5;
text-transform: lowercase; text-transform: lowercase;
font-size: 0.7em; font-size: 0.7em;
max-width: 10rem; max-width: 10rem;
min-width: 0;
&:hover:not(:disabled) { &:hover {
background-color: $main; background-color: $lbry-teal-4;
color: $lbry-white; color: $lbry-white;
svg {
stroke: $lbry-white;
}
} }
&:active, &:active {
&:focus {
background-color: $main; background-color: $main;
} }
} }
.tag--remove { .tag--remove {
@extend .tag; @extend .tag;
font-size: 1em !important;
max-width: 20rem; max-width: 20rem;
} }
.tag--add { .tag--add {
@extend .tag; background-color: lighten($lbry-teal-5, 60%);
background-color: rgba($main, 0.05);
border: 1px solid rgba($main, 0.3);
border-radius: 1rem;
transition: all var(--animation-duration) var(--animation-style);
&:hover {
border-radius: 3px;
}
} }
.tag__action-label { .tag__action-label {
border-left: 1px solid rgba($lbry-black, 0.1);
margin-left: 0.5rem; margin-left: 0.5rem;
padding-left: 0.5rem; padding-left: 0.5rem;
padding-top: 0.7rem;
html[data-mode='dark'] & { html[data-mode='dark'] & {
border-color: rgba($lbry-white, 0.1); border-color: rgba($lbry-white, 0.1);

View file

@ -7,7 +7,7 @@
flex: 1; flex: 1;
position: relative; position: relative;
z-index: 1; z-index: 1;
margin-right: var(--spacing-xlarge); margin-right: calc(var(--spacing-large));
@media (max-width: 600px) { @media (max-width: 600px) {
margin-right: 0; margin-right: 0;
@ -39,8 +39,12 @@
background-color: $lbry-gray-1; background-color: $lbry-gray-1;
align-items: center; align-items: center;
border: none; border: none;
display: flex; min-width: 0;
justify-content: center; padding-right: var(--spacing-small);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
padding-left: 2.5rem; padding-left: 2.5rem;
transition: all 0.2s; transition: all 0.2s;

View file

@ -0,0 +1,96 @@
// Extends reach-ui menu button base stylesheet
/* Used to detect in JavaScript if apps have loaded styles or not. */
:root {
--reach-menu-button: 1;
}
[data-reach-menu] {
font-family: sans-serif;
display: block;
position: absolute;
}
[data-reach-menu-list] {
display: block;
white-space: nowrap;
outline: none;
font-size: 1.2rem;
background-color: $lbry-white;
box-shadow: 0px 10px 30px 2px $lbry-gray-2;
border: 1px solid $lbry-gray-1;
border-top: none;
[data-mode='dark'] & {
background-color: lighten($lbry-black, 10%);
color: $lbry-white;
box-shadow: 0 10px 30px 2px $lbry-black;
border: 1px solid $lbry-gray-5;
border-top: none;
}
}
[data-reach-menu-item] {
display: block;
}
[data-reach-menu-item] {
cursor: pointer;
display: block;
color: inherit;
font: inherit;
text-decoration: initial;
}
[data-reach-menu-item][data-selected] {
background: lighten($lbry-teal-5, 55%);
color: darken($lbry-teal-5, 15%);
outline: none;
&:active {
background-color: $lbry-teal-4;
color: $lbry-white;
.icon {
stroke: $lbry-white;
}
}
[data-mode='dark'] & {
background-color: $lbry-teal-5;
color: $lbry-white;
&:hover,
&:focus {
.icon {
stroke: $lbry-white;
}
}
}
}
.menu__title {
padding: var(--spacing-large) 0;
padding-left: var(--spacing-medium);
padding-right: 0;
span {
margin-left: var(--spacing-tiny);
}
}
.menu__link {
display: flex;
align-items: center;
padding: var(--spacing-medium);
}
.menu__title,
.menu__link {
font-size: 1.2rem;
.icon {
margin-right: var(--spacing-small);
margin-bottom: 0.2rem;
stroke: $lbry-gray-5;
}
}

View file

@ -54,10 +54,10 @@ code {
justify-content: space-between; justify-content: space-between;
> * { > * {
flex-basis: 0;
flex-grow: 1; flex-grow: 1;
flex-basis: 0;
&:not(:last-of-type) { &:first-child {
margin-right: 1.5rem; margin-right: 1.5rem;
} }
} }
@ -130,7 +130,7 @@ code {
.help { .help {
font-size: 1rem; font-size: 1rem;
background-color: rgba($lbry-blue-1, 0.1); background-color: rgba($lbry-blue-1, 0.1);
color: $lbry-gray-5; color: darken($lbry-gray-5, 15%);
display: block; display: block;
padding: 1rem; padding: 1rem;
margin-top: var(--spacing-medium); margin-top: var(--spacing-medium);

View file

@ -46,7 +46,7 @@ $large-breakpoint: 1921px;
--button-height: 2.6rem; --button-height: 2.6rem;
// Header // Header
--header-height: 5rem; --header-height: 6rem;
// Card // Card
--card-radius: 5px; --card-radius: 5px;

View file

@ -5,8 +5,11 @@ export function formatCredits(amount, precision = 1) {
} }
export function formatFullPrice(amount, precision = 1) { export function formatFullPrice(amount, precision = 1) {
let formated = ''; if (!amount) {
return 0;
}
let formated = '';
const quantity = amount.toString().split('.'); const quantity = amount.toString().split('.');
const fraction = quantity[1]; const fraction = quantity[1];

31
src/ui/util/use-tween.js Normal file
View file

@ -0,0 +1,31 @@
import { useEffect, useState } from 'react';
const getProgress = (elapsed, duration) => Math.min(elapsed / duration, 1);
const easeOut = progress => Math.pow(progress - 1, 5) + 1;
export default function useTween(duration, onRest) {
const [value, setValue] = useState(0);
useEffect(() => {
let start = performance.now();
let elapsed = 0;
let frame;
const tick = now => {
elapsed = now - start;
const progress = getProgress(elapsed, duration);
setValue(easeOut(progress));
if (progress < 1) {
frame = requestAnimationFrame(tick);
} else {
onRest && onRest();
}
};
frame = requestAnimationFrame(tick);
return () => cancelAnimationFrame(frame);
}, [duration, onRest]);
return value;
}

View file

@ -4,160 +4,58 @@
"Cancel": "Cancel", "Cancel": "Cancel",
"Show More...": "Show More...", "Show More...": "Show More...",
"Show Less": "Show Less", "Show Less": "Show Less",
"Starting up": "Starting up",
"Connecting": "Connecting",
"LBRY": "LBRY", "LBRY": "LBRY",
"Navigate back": "Navigate back", "Navigate back": "Navigate back",
"Navigate forward": "Navigate forward", "Navigate forward": "Navigate forward",
"Menu": "Menu", "Account": "Account",
"Your wallet": "Your wallet", "Overview": "Overview",
"Publish content": "Publish content", "Wallet": "Wallet",
"Publish": "Publish", "Publish": "Publish",
"Discover": "Discover", "Settings": "Settings",
"Help": "Help",
"Make This Your Own": "Make This Your Own",
"For": "For",
"No results": "No results",
"Home": "Home",
"Subscriptions": "Subscriptions", "Subscriptions": "Subscriptions",
"Publishes": "Publishes", "Publishes": "Publishes",
"Library": "Library", "Library": "Library",
"Overview": "Overview", "Following": "Following",
"Invite": "Invite", "The tags you follow will change what's trending for you.": "The tags you follow will change what's trending for you.",
"Rewards": "Rewards",
"Send & Recieve": "Send & Recieve",
"Transactions": "Transactions",
"Settings": "Settings",
"Help": "Help",
"Failed to load landing content.": "Failed to load landing content.",
"Hi There": "Hi There",
"Using LBRY is like dating a centaur. Totally normal up top, and": "Using LBRY is like dating a centaur. Totally normal up top, and",
"way different": "way different",
"underneath.": "underneath.",
"Up top, LBRY is similar to popular media sites.": "Up top, LBRY is similar to popular media sites.",
"Below, LBRY is controlled by users -- you -- via blockchain and decentralization.": "Below, LBRY is controlled by users -- you -- via blockchain and decentralization.",
"I'm In": "I'm In",
"You Are Awesome!": "You Are Awesome!",
"Check out some of the neat content below me. I'll see you around!": "Check out some of the neat content below me. I'll see you around!",
"Lets Get Started": "Lets Get Started",
"Not Now": "Not Now",
"Subscribe": "Subscribe",
"Unsubscribe": "Unsubscribe",
"Invite a Friend": "Invite a Friend",
"When your friends start using LBRY, the network gets stronger!": "When your friends start using LBRY, the network gets stronger!",
"Or share this link with your friends": "Or share this link with your friends",
"Earn": "Earn",
"rewards": "rewards",
"for inviting your friends.": "for inviting your friends.",
"Read our": "Read our",
"FAQ": "FAQ",
"to learn more about referrals": "to learn more about referrals",
"Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.": "Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.",
"Invite History": "Invite History",
"Claim Your 150 LBC Invite Reward": "Claim Your 150 LBC Invite Reward",
"Invitee Email": "Invitee Email",
"Invite Status": "Invite Status",
"Reward": "Reward",
"Not Accepted": "Not Accepted",
"Unclaimable": "Unclaimable",
"Accepted": "Accepted",
"Claimed": "Claimed",
"Claimable": "Claimable",
"Checking your invite status": "Checking your invite status",
"Balance": "Balance",
"You currently have": "You currently have",
"LBC": "LBC",
"You have": "You have",
"in unclaimed rewards": "in unclaimed rewards",
"Claim Rewards": "Claim Rewards",
"to learn more about LBRY Rewards": "to learn more about LBRY Rewards",
"Recent Transactions": "Recent Transactions",
"No transactions... yet.": "No transactions... yet.",
"Refresh": "Refresh",
"Loading transactions": "Loading transactions",
"Looks like you don't have any recent transactions.": "Looks like you don't have any recent transactions.",
"Full History": "Full History",
"Amount": "Amount",
"Type": "Type",
"Details": "Details",
"Transaction": "Transaction",
"Date": "Date",
"Unlock Tip": "Unlock Tip",
"Download Directory": "Download Directory",
"LBRY downloads will be saved here.": "LBRY downloads will be saved here.",
"Max Purchase Price": "Max Purchase Price",
"No Limit": "No Limit",
"Choose limit": "Choose limit",
"This will prevent you from purchasing any content over a certain cost, as a safety measure.": "This will prevent you from purchasing any content over a certain cost, as a safety measure.",
"Purchase Confirmations": "Purchase Confirmations",
"Always confirm before purchasing content": "Always confirm before purchasing content",
"Only confirm purchases over a certain price": "Only confirm purchases over a certain price",
"When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price.": "When this option is chosen, LBRY won't ask you to confirm downloads below your chosen price.",
"Content Settings": "Content Settings",
"Show NSFW content": "Show NSFW content",
"NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ": "NSFW content may include nudity, intense sexuality, profanity, or other adult content. By displaying NSFW content, you are affirming you are of legal age to view mature content in your country or jurisdiction. ",
"Notifications": "Notifications",
"Show Desktop Notifications": "Show Desktop Notifications",
"Get notified when a publish is confirmed, or when new content is available to watch.": "Get notified when a publish is confirmed, or when new content is available to watch.",
"Share Diagnostic Data": "Share Diagnostic Data",
"Help make LBRY better by contributing analytics and diagnostic data about my usage.": "Help make LBRY better by contributing analytics and diagnostic data about my usage.",
"You will be ineligible to earn rewards while diagnostics are not being shared.": "You will be ineligible to earn rewards while diagnostics are not being shared.",
"Appearance": "Appearance",
"Theme": "Theme",
"Automatic dark mode (9pm to 8am)": "Automatic dark mode (9pm to 8am)",
"Wallet Security": "Wallet Security",
"Encrypt my wallet with a custom password.": "Encrypt my wallet with a custom password.",
"Secure your local wallet data with a custom password.": "Secure your local wallet data with a custom password.",
"Lost passwords cannot be recovered.": "Lost passwords cannot be recovered.",
"Learn more": "Learn more",
"Experimental Settings": "Experimental Settings",
"Automatically download new content from my subscriptions": "Automatically download new content from my subscriptions",
"The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published.": "The latest file from each of your subscriptions will be downloaded for quick access as soon as it's published.",
"Autoplay media files": "Autoplay media files",
"Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.": "Autoplay video and audio files when navigating to a file, as well as the next related item when a file finishes playing.",
"Application Cache": "Application Cache",
"This will clear the application cache. Your wallet will not be affected.": "This will clear the application cache. Your wallet will not be affected.",
"Clear Cache": "Clear Cache",
"Choose Directory": "Choose Directory",
"Price": "Price",
"Currency": "Currency",
"LBRY Credits (LBC)": "LBRY Credits (LBC)",
"US Dollars": "US Dollars",
"Blockchain Sync": "Blockchain Sync",
"Catching up with the blockchain": "Catching up with the blockchain",
"Uh oh. Sean must have messed something up. Try refreshing to fix it.": "Uh oh. Sean must have messed something up. Try refreshing to fix it.",
"If you still have issues, your anti-virus software or firewall may be preventing startup.": "If you still have issues, your anti-virus software or firewall may be preventing startup.",
"Reach out to hello@lbry.com for help, or check out": "Reach out to hello@lbry.com for help, or check out",
"Account": "Account",
"Make This Your Own": "Make This Your Own",
"You are already following a couple tags, try searching for a new one.": "You are already following a couple tags, try searching for a new one.",
"Tags": "Tags", "Tags": "Tags",
"Home": "Home", "Search for more tags": "Search for more tags",
"Aw shucks!": "Aw shucks!", "Publish content": "Publish content",
"There was an error. It's been reported and will be fixed": "There was an error. It's been reported and will be fixed", "Unfollow this tag": "Unfollow this tag",
"Try": "Try", "Follow this tag": "Follow this tag",
"refreshing the app": "refreshing the app",
"to fix it": "to fix it",
"NEW": "NEW",
"The publisher has chosen to charge LBC to view this content. Your balance is currently to low to view it.": "The publisher has chosen to charge LBC to view this content. Your balance is currently to low to view it.",
"Checkout": "Checkout",
"the rewards page": "the rewards page",
"or send more LBC to your wallet.": "or send more LBC to your wallet.",
"Published on": "Published on", "Published on": "Published on",
"Send a tip": "Send a tip", "Send a tip": "Send a tip",
"Share": "Share", "Share": "Share",
"Play": "Play", "Play": "Play",
"Subscribe": "Subscribe",
"Report content": "Report content", "Report content": "Report content",
"Content-Type": "Content-Type", "Content-Type": "Content-Type",
"Languages": "Languages", "Languages": "Languages",
"License": "License", "License": "License",
"Want to comment?": "Want to comment?", "Want to comment?": "Want to comment?",
"More": "More", "More": "More",
"FREE": "FREE",
"Related": "Related",
"No related content found": "No related content found",
"Download": "Download", "Download": "Download",
"Content": "Content", "Content": "Content",
"What are you publishing?": "What are you publishing?", "What are you publishing?": "What are you publishing?",
"Read our": "Read our",
"FAQ": "FAQ",
"to learn more.": "to learn more.", "to learn more.": "to learn more.",
"Title": "Title", "Title": "Title",
"Titular Title": "Titular Title", "Titular Title": "Titular Title",
"Description": "Description", "Description": "Description",
"Description of your content": "Description of your content", "Description of your content": "Description of your content",
"Thumbnail": "Thumbnail", "Thumbnail": "Thumbnail",
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.", "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to": "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to",
"spee.ch": "spee.ch",
"Recommended size: 800x450 (16:9)": "Recommended size: 800x450 (16:9)",
"Price": "Price",
"How much will this content cost?": "How much will this content cost?", "How much will this content cost?": "How much will this content cost?",
"Free": "Free", "Free": "Free",
"Choose price": "Choose price", "Choose price": "Choose price",
@ -166,6 +64,7 @@
"Ex. @Marvel, @TheBeatles, @BooksByJoe": "Ex. @Marvel, @TheBeatles, @BooksByJoe", "Ex. @Marvel, @TheBeatles, @BooksByJoe": "Ex. @Marvel, @TheBeatles, @BooksByJoe",
"Where can people find this content?": "Where can people find this content?", "Where can people find this content?": "Where can people find this content?",
"The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).": "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).", "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).": "The LBRY URL is the exact address where people find your content (ex. lbry://myvideo).",
"Learn more": "Learn more",
"Name": "Name", "Name": "Name",
"Deposit (LBC)": "Deposit (LBC)", "Deposit (LBC)": "Deposit (LBC)",
"Mature audiences only": "Mature audiences only", "Mature audiences only": "Mature audiences only",
@ -187,11 +86,12 @@
"LBRY Terms of Service": "LBRY Terms of Service", "LBRY Terms of Service": "LBRY Terms of Service",
"Choose File": "Choose File", "Choose File": "Choose File",
"No File Chosen": "No File Chosen", "No File Chosen": "No File Chosen",
"Thumbnail Preview": "Thumbnail Preview", "Choose Thumbnail": "Choose Thumbnail",
"Use thumbnail upload tool": "Use thumbnail upload tool", "Enter a thumbnail URL": "Enter a thumbnail URL",
"Anonymous": "Anonymous", "Anonymous": "Anonymous",
"New channel...": "New channel...", "New channel...": "New channel...",
"Create a URL for this content. Simpler names are easier to find and remember.": "Create a URL for this content. Simpler names are easier to find and remember.", "You already have a claim at": "You already have a claim at",
"Publishing will update your existing claim.": "Publishing will update your existing claim.",
"Any amount will give you the winning bid.": "Any amount will give you the winning bid.", "Any amount will give you the winning bid.": "Any amount will give you the winning bid.",
"This LBC remains yours and the deposit can be undone at any time.": "This LBC remains yours and the deposit can be undone at any time.", "This LBC remains yours and the deposit can be undone at any time.": "This LBC remains yours and the deposit can be undone at any time.",
"License (Optional)": "License (Optional)", "License (Optional)": "License (Optional)",
@ -199,83 +99,81 @@
"Public Domain": "Public Domain", "Public Domain": "Public Domain",
"Copyrighted...": "Copyrighted...", "Copyrighted...": "Copyrighted...",
"Other...": "Other...", "Other...": "Other...",
"Upload your thumbnail (.png/.jpg/.jpeg/.gif) to": "Upload your thumbnail (.png/.jpg/.jpeg/.gif) to", "Email": "Email",
"spee.ch": "spee.ch", "Your email has been successfully verified": "Your email has been successfully verified",
"Recommended size: 800x450 (16:9)": "Recommended size: 800x450 (16:9)", "Your Email": "Your Email",
"Choose Thumbnail": "Choose Thumbnail", "Change": "Change",
"Enter a thumbnail URL": "Enter a thumbnail URL", "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.": "This information is disclosed only to LBRY, Inc. and not to the LBRY network. It is only required to earn LBRY rewards.",
"Rewards": "Rewards",
"You have": "You have",
"in unclaimed rewards": "in unclaimed rewards",
"Claim Rewards": "Claim Rewards",
"LBC": "LBC",
"Earned From Rewards": "Earned From Rewards",
"Invite a Friend": "Invite a Friend",
"When your friends start using LBRY, the network gets stronger!": "When your friends start using LBRY, the network gets stronger!",
"Or share this link with your friends": "Or share this link with your friends",
"Earn": "Earn",
"rewards": "rewards",
"for inviting your friends.": "for inviting your friends.",
"to learn more about referrals": "to learn more about referrals",
"Power To The People": "Power To The People",
"LBRY is powered by the users. More users, more power… and with great power comes great responsibility.": "LBRY is powered by the users. More users, more power… and with great power comes great responsibility.",
"Checking your invite status": "Checking your invite status",
"You have ...": "You have ...",
"You have no rewards available, please check": "You have no rewards available, please check",
"Don't Miss Out": "Don't Miss Out",
"We'll let you know about LBRY updates, security issues, and great new content.": "We'll let you know about LBRY updates, security issues, and great new content.",
"Your email address will never be sold and you can unsubscribe at any time.": "Your email address will never be sold and you can unsubscribe at any time.",
"View Rewards": "View Rewards",
"Latest From Your Subscriptions": "Latest From Your Subscriptions",
"Find New Channels": "Find New Channels",
"Discover New Channels": "Discover New Channels",
"View Your Subscriptions": "View Your Subscriptions",
"publishes": "publishes",
"About": "About",
"Share Channel": "Share Channel",
"This channel hasn't uploaded anything.": "This channel hasn't uploaded anything.",
"Go to page:": "Go to page:",
"Nothing here yet": "Nothing here yet",
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
"Thumbnail Preview": "Thumbnail Preview",
"Use thumbnail upload tool": "Use thumbnail upload tool",
"Create a URL for this content. Simpler names are easier to find and remember.": "Create a URL for this content. Simpler names are easier to find and remember.",
"Subscribed": "Subscribed",
"Open file": "Open file",
"Delete this file": "Delete this file",
"Delete": "Delete",
"Downloaded to": "Downloaded to",
"You have...": "You have...",
"Balance": "Balance",
"You currently have": "You currently have",
"Recent Transactions": "Recent Transactions",
"Looks like you don't have any recent transactions.": "Looks like you don't have any recent transactions.",
"Full History": "Full History",
"Refresh": "Refresh",
"Send Credits": "Send Credits",
"Send LBC to your friends or favorite creators": "Send LBC to your friends or favorite creators",
"Amount": "Amount",
"Recipient address": "Recipient address",
"Send": "Send",
"Receive Credits": "Receive Credits", "Receive Credits": "Receive Credits",
"Use this wallet address to receive credits sent by another user (or yourself).": "Use this wallet address to receive credits sent by another user (or yourself).", "Use this wallet address to receive credits sent by another user (or yourself).": "Use this wallet address to receive credits sent by another user (or yourself).",
"Address copied.": "Address copied.", "Address copied.": "Address copied.",
"Get New Address": "Get New Address", "Get New Address": "Get New Address",
"Show QR code": "Show QR code", "Show QR code": "Show QR code",
"You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.": "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.", "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.": "You can generate a new address at any time, and any previous addresses will continue to work. Using multiple addresses can be helpful for keeping track of incoming payments from multiple sources.",
"Send Credits": "Send Credits", "Type": "Type",
"Send LBC to your friends or favorite creators": "Send LBC to your friends or favorite creators", "Details": "Details",
"Recipient address": "Recipient address", "Transaction": "Transaction",
"Send": "Send", "Date": "Date",
"No transactions.": "No transactions.", "Abandon Claim": "Abandon Claim",
"FREE": "FREE",
"Search": "Search",
"Invalid character %s in name: %s.": "Invalid character %s in name: %s.",
"View file": "View file",
"There's nothing available at this location.": "There's nothing available at this location.",
"Loading decentralized data...": "Loading decentralized data...",
"See All Visited Links": "See All Visited Links",
"Open file": "Open file",
"Delete this file": "Delete this file",
"Delete": "Delete",
"Downloaded to": "Downloaded to",
"Drop to remove": "Drop to remove",
"About": "About",
"Share Channel": "Share Channel",
"Newest First": "Newest First",
"Oldest First": "Oldest First",
"Go to page:": "Go to page:",
"Nothing here yet": "Nothing here yet",
"Claim sequence must be a number.": "Claim sequence must be a number.",
"Requesting stream...": "Requesting stream...",
"Connecting...": "Connecting...",
"Confirm Purchase": "Confirm Purchase",
"This will purchase": "This will purchase",
"for": "for",
"credits": "credits",
"Downloading stream... not long left now!": "Downloading stream... not long left now!",
"Downloading: ": "Downloading: ",
"% complete": "% complete",
"Waiting for blob.": "Waiting for blob.",
"Waiting for metadata.": "Waiting for metadata.",
"Sorry, looks like we can't play this file.": "Sorry, looks like we can't play this file.",
"Sorry, looks like we can't preview this file.": "Sorry, looks like we can't preview this file.",
"files": "files",
"hidden due to your": "hidden due to your",
"content viewing preferences": "content viewing preferences",
"Less": "Less",
"Find New Channels": "Find New Channels",
"Latest From Your Subscriptions": "Latest From Your Subscriptions",
"publishes": "publishes",
"No modifier provided after separator %s.": "No modifier provided after separator %s.",
"View Your Subscriptions": "View Your Subscriptions",
"You might like these channels": "You might like these channels",
"This channel hasn't uploaded anything.": "This channel hasn't uploaded anything.",
"Web link": "Web link",
"Facebook": "Facebook",
"": "",
"Twitter": "Twitter",
"View on Spee.ch": "View on Spee.ch",
"LBRY App link": "LBRY App link",
"Done": "Done",
"fee": "fee", "fee": "fee",
"Find what you were looking for?": "Find what you were looking for?", "Find New Tags To Follow": "Find New Tags To Follow",
"Yes": "Yes", "Aw shucks!": "Aw shucks!",
"No": "No", "There was an error. It's been reported and will be fixed": "There was an error. It's been reported and will be fixed",
"These search results are provided by LBRY, Inc.": "These search results are provided by LBRY, Inc.", "Try": "Try",
"FILTER": "FILTER", "refreshing the app": "refreshing the app",
"View": "View", "to fix it": "to fix it",
"No results": "No results", "Search": "Search"
"Following": "Following",
"Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.": "Uh oh. The flux in our Retro Encabulator must be out of whack. Try refreshing to fix it.",
"Failed to load settings.": "Failed to load settings.",
"Multi-language support is brand new. Switching your language may have unintended consequences. Email ": "Multi-language support is brand new. Switching your language may have unintended consequences. Email ",
"Multi-language support is brand new and incomplete. Switching your language may have unintended consequences.": "Multi-language support is brand new and incomplete. Switching your language may have unintended consequences."
} }

View file

@ -909,22 +909,41 @@
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf" resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ== integrity sha512-dEVG+ITnvqKGa4v040tP+n8LOKOqr94qjLva7bE5pnfm2KHJwsKz69J4KMxgWLznbpBJzy8vQfCayEk3vLZnZQ==
"@reach/auto-id@^0.2.0": "@reach/auto-id@0.2.0", "@reach/auto-id@^0.2.0":
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550" resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg== integrity sha512-lVK/svL2HuQdp7jgvlrLkFsUx50Az9chAhxpiPwBqcS83I2pVWvXp98FOcSCCJCV++l115QmzHhFd+ycw1zLBg==
"@reach/component-component@^0.1.3": "@reach/component-component@0.1.3", "@reach/component-component@^0.1.3":
version "0.1.3" version "0.1.3"
resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5" resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5"
integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg== integrity sha512-a1USH7L3bEfDdPN4iNZGvMEFuBfkdG+QNybeyDv8RloVFgZYRoM+KGXyy2KOfEnTUM8QWDRSROwaL3+ts5Angg==
"@reach/menu-button@^0.1.18":
version "0.1.18"
resolved "https://registry.yarnpkg.com/@reach/menu-button/-/menu-button-0.1.18.tgz#cb9e3bf1c2a2bdb5d618697b87ad353dfbca123e"
integrity sha512-MGdN8SWaQ0u0xj6KWUnK9fFc1VBg8NhNnFhEd2sp3D56XJcr08HU5GeVq/MFf5C97mxRnIPIKGnMPPeGAn9OFA==
dependencies:
"@reach/component-component" "0.1.3"
"@reach/portal" "^0.2.1"
"@reach/rect" "0.2.1"
"@reach/utils" "^0.2.3"
"@reach/window-size" "^0.1.4"
warning "^4.0.2"
"@reach/observe-rect@^1.0.3": "@reach/observe-rect@^1.0.3":
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8" resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg= integrity sha1-LqPcw2mrIr2fBQqS6jGTITVqYeg=
"@reach/rect@^0.2.1": "@reach/portal@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@reach/portal/-/portal-0.2.1.tgz#07720b999e0063a9e179c14dbdc60fd991cfc9fa"
integrity sha512-pUQ0EtCcYm4ormEjJmdk4uzZCxOpaRHB8FDKJXy6q6GqRqQwZ4lAT1f2Tvw0DAmULmyZTpe1/heXY27Tdnct+Q==
dependencies:
"@reach/component-component" "^0.1.3"
"@reach/rect@0.2.1", "@reach/rect@^0.2.1":
version "0.2.1" version "0.2.1"
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601" resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
integrity sha512-aZ9RsNHDMQ3zETonikqu9/85iXxj+LPqZ9Gr9UAncj3AufYmGeWG3XG6b37B+7ORH+mkhVpLU2ZlIWxmOe9Cqg== integrity sha512-aZ9RsNHDMQ3zETonikqu9/85iXxj+LPqZ9Gr9UAncj3AufYmGeWG3XG6b37B+7ORH+mkhVpLU2ZlIWxmOe9Cqg==
@ -941,11 +960,40 @@
"@reach/utils" "^0.2.2" "@reach/utils" "^0.2.2"
warning "^4.0.2" warning "^4.0.2"
"@reach/tooltip@^0.2.1":
version "0.2.1"
resolved "https://registry.yarnpkg.com/@reach/tooltip/-/tooltip-0.2.1.tgz#70a80d6defedee53cedf5480cd3d37dfb20020d0"
integrity sha512-3O7oXoymNkEAolHN9WbuspY7mA3zIOrTaibmYkKFtGT6FgyBrAQyOQn1ZhBuSza6RjSIkEtFRpbDEKK1UJEI6A==
dependencies:
"@reach/auto-id" "0.2.0"
"@reach/portal" "^0.2.1"
"@reach/rect" "^0.2.1"
"@reach/utils" "^0.2.3"
"@reach/visually-hidden" "^0.1.4"
prop-types "^15.7.2"
"@reach/utils@^0.2.2": "@reach/utils@^0.2.2":
version "0.2.2" version "0.2.2"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df" resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A== integrity sha512-jYeIi46AA5jh2gfdXD/nInUYfeLp3girRafiajP7AVHF6B4hpYAzUSx/ZH4xmPyf5alut5rml2DHxrv+X+Xu+A==
"@reach/utils@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.3.tgz#820f6a6af4301b4c5065cfc04bb89e6a3d1d723f"
integrity sha512-zM9rA8jDchr05giMhL95dPeYkK67cBQnIhCVrOKKqgWGsv+2GE/HZqeptvU4zqs0BvIqsThwov+YxVNVh5csTQ==
"@reach/visually-hidden@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@reach/visually-hidden/-/visually-hidden-0.1.4.tgz#0dc4ecedf523004337214187db70a46183bd945b"
integrity sha512-QHbzXjflSlCvDd6vJwdwx16mSB+vUCCQMiU/wK/CgVNPibtpEiIbisyxkpZc55DyDFNUIqP91rSUsNae+ogGDQ==
"@reach/window-size@^0.1.4":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@reach/window-size/-/window-size-0.1.4.tgz#3257b646548f61c2708a661a683620fbe0a706cb"
integrity sha512-JZshEuGsLvi6fUIJ7Unx12yNeM5SmqWjber2MLr9tfwf1hpNv73EiPBOIJyV0DjW7GXzjcOEvwnqysm59s2s/A==
dependencies:
"@reach/component-component" "^0.1.3"
"@samverschueren/stream-to-observable@^0.3.0": "@samverschueren/stream-to-observable@^0.3.0":
version "0.3.0" version "0.3.0"
resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f" resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz#ecdf48d532c58ea477acfcab80348424f8d0662f"
@ -6588,9 +6636,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#291324d03f694c4fefa6967aa7be02d9245596a8: lbry-redux@lbryio/lbry-redux#6a447d0542d23b9a37e266f5f85d3bde5297a451:
version "0.0.1" version "0.0.1"
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/291324d03f694c4fefa6967aa7be02d9245596a8" resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/6a447d0542d23b9a37e266f5f85d3bde5297a451"
dependencies: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"