Merge pull request #2553 from lbryio/discovery-2

Moar discovery
This commit is contained in:
Sean Yesmunt 2019-06-19 10:46:11 -04:00 committed by GitHub
commit 97ddcd0a9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
99 changed files with 1236 additions and 936 deletions

View file

@ -40,6 +40,8 @@
"postinstall": "electron-builder install-app-deps && node ./build/downloadDaemon.js"
},
"dependencies": {
"@reach/menu-button": "^0.1.18",
"@reach/tooltip": "^0.2.1",
"electron-dl": "^1.11.0",
"electron-log": "^2.2.12",
"electron-updater": "^4.0.6",
@ -119,7 +121,7 @@
"jsmediatags": "^3.8.1",
"json-loader": "^0.5.4",
"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",
"lint-staged": "^7.0.2",
"localforage": "^1.7.1",

View file

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

View file

@ -1,6 +1,6 @@
// @flow
import React, { Fragment } from 'react';
import FileList from 'component/fileList';
import ClaimList from 'component/claimList';
import HiddenNsfwClaims from 'component/hiddenNsfwClaims';
import { withRouter } from 'react-router-dom';
import Paginate from 'component/common/paginate';
@ -35,7 +35,7 @@ function ChannelContent(props: Props) {
{!channelIsMine && <HiddenNsfwClaims className="card__content help" uri={uri} />}
{hasContent && <FileList noHeader uris={claimsInChannel.map(claim => claim.permanent_url)} />}
{hasContent && <ClaimList header={false} uris={claimsInChannel.map(claim => claim.permanent_url)} />}
<Paginate
onPageChange={page => fetchClaims(uri, page)}

View file

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

View file

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

View file

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

View file

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

View file

@ -10,7 +10,7 @@ import {
makeSelectClaimIsNsfw,
} from 'lbry-redux';
import { selectShowNsfw } from 'redux/selectors/settings';
import FileListItem from './view';
import ClaimListItem from './view';
const select = (state, props) => ({
pending: makeSelectClaimIsPending(props.uri)(state),
@ -30,4 +30,4 @@ const perform = dispatch => ({
export default connect(
select,
perform
)(FileListItem);
)(ClaimListItem);

View file

@ -10,7 +10,7 @@ import UriIndicator from 'component/uriIndicator';
import TruncatedText from 'component/common/truncated-text';
import DateTime from 'component/dateTime';
import FileProperties from 'component/fileProperties';
import FileTags from 'component/fileTags';
import ClaimTags from 'component/claimTags';
import SubscribeButton from 'component/subscribeButton';
import ChannelThumbnail from 'component/channelThumbnail';
@ -27,12 +27,11 @@ type Props = {
thumbnail: string,
title: string,
nsfw: boolean,
large: boolean,
placeholder: boolean,
slim: boolean,
type: string,
};
function FileListItem(props: Props) {
function ClaimListItem(props: Props) {
const {
obscureNsfw,
claimIsMine,
@ -45,9 +44,8 @@ function FileListItem(props: Props) {
nsfw,
resolveUri,
claim,
large,
placeholder,
slim,
type,
} = props;
const haventFetched = claim === undefined;
@ -98,7 +96,7 @@ function FileListItem(props: Props) {
onClick={onClick}
onContextMenu={handleContextMenu}
className={classnames('file-list__item', {
'file-list__item--large': large,
'file-list__item--large': type === 'large',
})}
>
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
@ -107,10 +105,10 @@ function FileListItem(props: Props) {
<div className="file-list__item-title">
<TruncatedText text={title || (claim && claim.name)} lines={1} />
</div>
{!slim && (
{type !== 'small' && (
<div>
{isChannel && <SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />}
<FileProperties uri={uri} />
{!isChannel && <FileProperties uri={uri} />}
</div>
)}
</div>
@ -122,11 +120,11 @@ function FileListItem(props: Props) {
<div>{isChannel ? `${claimsInChannel} ${__('publishes')}` : <DateTime timeAgo uri={uri} />}</div>
</div>
{!slim && <FileTags uri={uri} />}
<ClaimTags uri={uri} type={type} />
</div>
</div>
</li>
);
}
export default withRouter(FileListItem);
export default withRouter(ClaimListItem);

View file

@ -1,6 +1,6 @@
import { connect } from 'react-redux';
import { makeSelectTagsForUri, selectFollowedTags } from 'lbry-redux';
import FileTags from './view';
import ClaimTags from './view';
const select = (state, props) => ({
tags: makeSelectTagsForUri(props.uri)(state),
@ -10,4 +10,4 @@ const select = (state, props) => ({
export default connect(
select,
null
)(FileTags);
)(ClaimTags);

View file

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

View file

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

View file

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

View file

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

View file

@ -1,9 +1,13 @@
// @flow
import * as ICONS from 'constants/icons';
import * as SETTINGS from 'constants/settings';
import * as React from 'react';
import { withRouter } from 'react-router';
import Button from 'component/button';
import LbcSymbol from 'component/common/lbc-symbol';
import WunderBar from 'component/wunderbar';
import Icon from 'component/common/icon';
import { Menu, MenuList, MenuButton, MenuItem } from '@reach/menu-button';
type Props = {
autoUpdateDownloaded: boolean,
@ -11,16 +15,29 @@ type Props = {
isUpgradeAvailable: boolean,
roundedBalance: number,
downloadUpgradeRequested: any => void,
history: { push: string => void },
currentTheme: string,
automaticDarkModeEnabled: boolean,
setClientSetting: (string, boolean | string) => void,
};
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 (
<header className="header">
<div className="title-bar" />
<div className="header__contents">
<div className="header__navigation">
<Button
@ -53,54 +70,55 @@ const Header = (props: Props) => {
<WunderBar />
<div className="header__navigation">
<Button
className="header__navigation-item header__navigation-item--right-action"
activeClass="header__navigation-item--active"
label={
roundedBalance > 0 ? (
<Menu>
<MenuButton className="header__navigation-item menu__title">
<Icon icon={ICONS.ACCOUNT} />
{roundedBalance > 0 ? (
<React.Fragment>
{roundedBalance} <LbcSymbol />
</React.Fragment>
) : (
__('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 */}
<Button
className="header__navigation-item header__navigation-item--right-action"
activeClass="header__navigation-item--active"
icon={ICONS.SETTINGS}
iconSize={24}
navigate="/$/settings"
/>
)}
</MenuButton>
<MenuList>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/account`)}>
<Icon aria-hidden icon={ICONS.OVERVIEW} />
{__('Overview')}
</MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/wallet`)}>
<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>
</header>
);
};
export default Header;
export default withRouter(Header);

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
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';
type FormProps = {
@ -48,7 +48,7 @@ class FormInviteNew extends React.PureComponent<FormProps, FormState> {
name="email"
value={this.state.email}
error={errorMessage}
inputButton={<Submit label="Invite" disabled={isPending} />}
inputButton={<Button button="inverse" type="submit" label="Invite" disabled={isPending} />}
onChange={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';
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
import * as ICONS from 'constants/icons';
import * as React from 'react';
import classnames from 'classnames';
import Spinner from 'component/spinner';
// time in ms to wait to show loading spinner
const LOADER_TIMEOUT = 1000;
import Button from 'component/button';
type Props = {
children: React.Node | Array<React.Node>,
pageTitle: ?string,
loading: ?boolean,
className: ?string,
autoUpdateDownloaded: boolean,
isUpgradeAvailable: boolean,
doDownloadUpgradeRequested: () => void,
};
type State = {
showLoader: ?boolean,
};
function Page(props: Props) {
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 (
<main className={classnames('main', className)}>
{!loading && children}
{showLoader && (
<div className="main--empty">
<Spinner />
</div>
)}
</main>
);
}
return (
<main className={classnames('main', className)}>
{/* @if TARGET='app' */}
{showUpgradeButton && (
<div className="main__status">
{__('Update ready to install')}
<Button button="alt" icon={ICONS.DOWNLOAD} label={__('Install now')} onClick={doDownloadUpgradeRequested} />
</div>
)}
{/* @endif */}
{children}
</main>
);
}
export default Page;

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import FileList from 'component/fileList';
import ClaimList from 'component/claimList';
type Props = {
uri: string,
@ -52,11 +52,11 @@ export default class RecommendedContent extends React.PureComponent<Props> {
return (
<section className="card">
<FileList
slim
<ClaimList
type="small"
loading={isSearching}
uris={recommendedContent}
header={<span>Related</span>}
header={<span>{__('Related')}</span>}
empty={<div className="empty">{__('No related content found')}</div>}
/>
</section>

View file

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

View file

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

View file

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

View file

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

View file

@ -33,10 +33,10 @@ const RewardTile = (props: Props) => {
<div className="card__content">
<div className="card__actions">
{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 && (
<Button button="primary" navigate="/$/invite" label={__('Go To Invites')} />
<Button button="inverse" navigate="/$/invite" label={__('Go To Invites')} />
)}
{reward.reward_type !== rewards.TYPE_REFERRAL &&
(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 SearchPage from 'page/search';
import UserHistoryPage from 'page/userHistory';
import SendCreditsPage from 'page/sendCredits';
import WalletPage from 'page/wallet';
import NavigationHistory from 'page/navigationHistory';
import TagsPage from 'page/tags';
import TagsEditPage from 'page/tagsEdit';
@ -54,11 +54,10 @@ export default function AppRouter() {
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} />
<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.TAGS}`} exact component={TagsPage} />
<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 */}
<Route path="/:claimName" exact component={ShowPage} />
<Route path="/:claimName/:claimId" exact component={ShowPage} />

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,6 @@
// @flow
import * as icons from 'constants/icons';
import React, { Fragment } from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';
import React from 'react';
import TransactionList from 'component/transactionList';
import RefreshTransactionButton from 'component/transactionRefreshButton';
type Props = {
fetchTransactions: () => void,
@ -23,42 +19,15 @@ class TransactionListRecent extends React.PureComponent<Props> {
}
render() {
const { fetchingTransactions, hasTransactions, transactions } = this.props;
const { transactions } = this.props;
return (
<section className="card card--section">
<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
slim
transactions={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 className="card card__content">
<TransactionList
slim
title={__('Recent Transactions')}
transactions={transactions}
emptyMessage={__("Looks like you don't have any recent transactions.")}
/>
</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>
<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>
);
}

View file

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

View file

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

View file

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

View file

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

View file

@ -24,6 +24,8 @@ export const CHANNEL = 'AtSign';
export const REFRESH = 'RefreshCw';
export const HISTORY = 'Clock';
export const HOME = 'Home';
export const OVERVIEW = 'Activity';
export const WALLET = 'List';
export const PHONE = 'Phone';
export const COMPLETE = 'Check';
export const COMPLETED = 'CheckCircle';
@ -65,3 +67,6 @@ export const MUSIC_ALBUM = 'Disc';
export const MUSIC_ARTIST = 'Mic';
export const MUSIC_SONG = 'Music';
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 TRANSACTIONS = 'transactions';
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 classnames from 'classnames';
import WalletBalance from 'component/walletBalance';
import RewardSummary from 'component/rewardSummary';
import TransactionListRecent from 'component/transactionListRecent';
import WalletAddress from 'component/walletAddress';
import RewardTotal from 'component/rewardTotal';
import Page from 'component/page';
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 = () => (
<Page>
{IS_WEB && <UnsupportedOnWeb />}
<div className={classnames({ 'card--disabled': IS_WEB })}>
<div className="columns">
<WalletBalance />
<RewardSummary />
<UserEmail />
<div>
<RewardSummary />
<RewardTotal />
</div>
</div>
<WalletAddress />
<WalletSend />
<TransactionListRecent />
<InvitePage />
</div>
</Page>
);

View file

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

View file

@ -1,16 +1,12 @@
// @flow
import React from 'react';
import BusyIndicator from 'component/common/busy-indicator';
import Button from 'component/button';
import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify';
import UserEmail from 'component/userEmail';
import UserVerify from 'component/userVerify';
import Page from 'component/page';
type Props = {
isPending: boolean,
email: string,
pathAfterAuth: string,
location: UrlLocation,
history: { push: string => void },
user: ?{
@ -21,12 +17,12 @@ type Props = {
};
class AuthPage extends React.PureComponent<Props> {
componentWillMount() {
componentDidMount() {
this.navigateIfAuthenticated(this.props);
}
componentWillReceiveProps(nextProps: Props) {
this.navigateIfAuthenticated(nextProps);
componentDidUpdate() {
this.navigateIfAuthenticated(this.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() {
const [innerContent, useTemplate] = this.renderMain();
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>
);
const { user, email } = this.props;
return <Page>{user && email && !user.is_identity_verified ? <UserVerify /> : <UserEmail />}</Page>;
}
}

View file

@ -1,6 +1,6 @@
// @flow
import React from 'react';
import FileListDiscover from 'component/fileListDiscover';
import ClaimListDiscover from 'component/claimListDiscover';
import TagsSelect from 'component/tagsSelect';
import Page from 'component/page';
@ -11,8 +11,8 @@ type Props = {
function DiscoverPage(props: Props) {
const { followedTags } = props;
return (
<Page className="card">
<FileListDiscover
<Page>
<ClaimListDiscover
personal
tags={followedTags.map(tag => tag.name)}
injectedItem={<TagsSelect showClose title={__('Make This Your Own')} />}

View file

@ -19,6 +19,7 @@ import FileDownloadLink from 'component/fileDownloadLink';
import classnames from 'classnames';
import getMediaType from 'util/get-media-type';
import RecommendedContent from 'component/recommendedContent';
import ClaimTags from 'component/claimTags';
type Props = {
claim: StreamClaim,
@ -67,6 +68,8 @@ class FilePage extends React.Component<Props> {
(this: any).viewerContainer = React.createRef();
}
viewerContainer: { current: React.ElementRef<any> };
componentDidMount() {
const {
uri,
@ -181,7 +184,7 @@ class FilePage extends React.Component<Props> {
return (
<Page className="main--file-page">
<div className="grid-area--content">
<div className="grid-area--content card">
{!fileInfo && insufficientCredits && (
<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>
))}
<Button
className="media__uri"
button="alt"
label={uri}
onClick={() => {
clipboard.writeText(uri);
showToast({
message: __('Copied'),
});
}}
/>
</div>
<div className="grid-area--info media__content media__content--large">
<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__subtext media__subtext--large">
<div className="media__subtitle__channel">
@ -304,11 +280,42 @@ class FilePage extends React.Component<Props> {
</div>
</div>
<div className="media__info--large">
<ClaimTags uri={uri} type="large" />
</div>
<div className="media__info--large">
<FileDetails uri={uri} />
</div>
</div>
<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} />
</div>
</Page>

View file

@ -1,7 +1,7 @@
// @flow
import React from 'react';
import Button from 'component/button';
import FileList from 'component/fileList';
import ClaimList from 'component/claimList';
type Props = {
fetching: boolean,
@ -18,7 +18,7 @@ function FileListDownloaded(props: Props) {
<React.Fragment>
{hasDownloads ? (
<div className="card">
<FileList persistedStorageKey="file-list-downloaded" uris={downloadedUris} loading={fetching} />
<ClaimList persistedStorageKey="claim-list-downloaded" uris={downloadedUris} loading={fetching} />
</div>
) : (
<div className="main--empty">

View file

@ -1,7 +1,7 @@
// @flow
import React, { useEffect } from 'react';
import Button from 'component/button';
import FileList from 'component/fileList';
import ClaimList from 'component/claimList';
import Page from 'component/page';
type Props = {
@ -21,7 +21,7 @@ function FileListPublished(props: Props) {
<Page notContained>
{uris && uris.length ? (
<div className="card">
<FileList loading={fetching} persistedStorageKey="file-list-published" uris={uris} />
<ClaimList loading={fetching} persistedStorageKey="claim-list-published" uris={uris} />
</div>
) : (
<div className="main--empty">

View file

@ -130,7 +130,7 @@ class HelpPage extends React.PureComponent<Props, State> {
<div className="card__content">
<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>
</section>
@ -147,7 +147,7 @@ class HelpPage extends React.PureComponent<Props, State> {
<div className="card__content">
<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>
</section>
@ -167,7 +167,7 @@ class HelpPage extends React.PureComponent<Props, State> {
navigate="/$/report"
label={__('Submit a Bug Report/Feature Request')}
icon={icons.REPORT}
button="primary"
button="inverse"
/>
</div>
@ -188,8 +188,8 @@ class HelpPage extends React.PureComponent<Props, State> {
<div className="card__content">
<div className="card__actions">
<Button button="primary" label={__('Open Log')} onClick={() => this.openLogFile(dataDirectory)} />
<Button button="primary" label={__('Open Log Folder')} onClick={() => shell.openItem(dataDirectory)} />
<Button button="inverse" label={__('Open Log')} onClick={() => this.openLogFile(dataDirectory)} />
<Button button="inverse" label={__('Open Log Folder')} onClick={() => shell.openItem(dataDirectory)} />
</div>
</div>
</section>
@ -198,8 +198,8 @@ class HelpPage extends React.PureComponent<Props, State> {
<BackupSection />
{/* @endif */}
<section className="card card--section">
<header className="card__header">
<section className="card">
<header className="table__header">
<h2 className="card__title">{__('About')}</h2>
{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 InviteNew from 'component/inviteNew';
import InviteList from 'component/inviteList';
import Page from 'component/page';
type Props = {
isPending: boolean,
@ -27,7 +26,7 @@ class InvitePage extends React.PureComponent<Props> {
const { isPending, isFailed } = this.props;
return (
<Page>
<div>
{isPending && <BusyIndicator message={__('Checking your invite status')} />}
{!isPending && isFailed && <span className="empty">{__('Failed to retrieve invite status.')}</span>}
{!isPending && !isFailed && (
@ -36,7 +35,7 @@ class InvitePage extends React.PureComponent<Props> {
<InviteList />
</React.Fragment>
)}
</Page>
</div>
);
}
}

View file

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

View file

@ -2,8 +2,8 @@
import * as ICONS from 'constants/icons';
import React, { useEffect, Fragment } from 'react';
import { isURIValid, normalizeURI } from 'lbry-redux';
import FileListItem from 'component/fileListItem';
import FileList from 'component/fileList';
import ClaimListItem from 'component/claimListItem';
import ClaimList from 'component/claimList';
import Page from 'component/page';
import SearchOptions from 'component/searchOptions';
import Button from 'component/button';
@ -49,12 +49,12 @@ export default function SearchPage(props: Props) {
<Button button="alt" navigate={uri} className="media__uri">
{uri}
</Button>
<FileListItem uri={uri} large />
<ClaimListItem uri={uri} type="large" />
</header>
)}
<div className="card">
<FileList
<ClaimList
uris={uris}
header={<SearchOptions />}
headerAltControls={

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

View file

@ -1,53 +1,78 @@
// @flow
import React, { useEffect, useState } from 'react';
import * as PAGES from 'constants/pages';
import React, { useEffect } from 'react';
import Page from 'component/page';
import FileList from 'component/fileList';
import ClaimList from 'component/claimList';
import Button from 'component/button';
type Props = {
subscribedChannels: Array<string>, // The channels a user is subscribed to
subscriptionContent: Array<{ uri: string, ...StreamClaim }>,
subscribedChannels: Array<{ uri: string }>, // The channels a user is subscribed to
suggestedSubscriptions: Array<{ uri: string }>,
loading: boolean,
doFetchMySubscriptions: () => void,
doFetchRecommendedSubscriptions: () => void,
location: { search: string },
history: { push: string => void },
doClaimSearch: (number, {}) => void,
uris: Array<string>,
};
export default function SubscriptionsPage(props: Props) {
const {
subscriptionContent,
subscribedChannels,
doFetchMySubscriptions,
doFetchRecommendedSubscriptions,
suggestedSubscriptions,
loading,
location,
history,
doClaimSearch,
uris,
} = props;
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(() => {
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 (
<Page>
<div className="card">
<FileList
<ClaimList
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={
<Button
button="alt"
label={showSuggested ? hasSubscriptions && __('View Your Subscriptions') : __('Find New Channels')}
onClick={() => setShowSuggested(!showSuggested)}
button="link"
label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Subscriptions') : __('Find New Channels')}
onClick={() => onClick()}
/>
}
uris={
showSuggested
? suggestedSubscriptions.map(sub => sub.uri)
: subscriptionContent.map(sub => sub.permanent_url)
}
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
/>
</div>
</Page>

View file

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

View file

@ -1,26 +1,43 @@
// @flow
import React from 'react';
import Page from 'component/page';
import FileListDiscover from 'component/fileListDiscover';
import ClaimListDiscover from 'component/claimListDiscover';
import Button from 'component/button';
type Props = {
location: { search: string },
followedTags: Array<Tag>,
doToggleTagFollow: string => void,
};
function TagsPage(props: Props) {
const {
location: { search },
followedTags,
doToggleTagFollow,
} = props;
const urlParams = new URLSearchParams(search);
const tagsQuery = urlParams.get('t') || '';
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 (
<Page>
<div className="card">
<FileListDiscover tags={tags} />
</div>
<ClaimListDiscover
tags={tags}
meta={
<Button
button="alt"
onClick={() => doToggleTagFollow(tag)}
label={isFollowing ? __('Unfollow this tag') : __('Follow this tag')}
/>
}
/>
</Page>
);
}

View file

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

View file

@ -1,10 +1,8 @@
// @flow
import React from 'react';
import classnames from 'classnames';
import BusyIndicator from 'component/common/busy-indicator';
import TransactionList from 'component/transactionList';
import Page from 'component/page';
import RefreshTransactionButton from 'component/transactionRefreshButton';
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
type Props = {
@ -23,36 +21,17 @@ class TransactionHistoryPage extends React.PureComponent<Props> {
}
render() {
const { fetchingTransactions, transactions } = this.props;
const { transactions } = this.props;
return (
<Page>
{IS_WEB && <UnsupportedOnWeb />}
<section
className={classnames('card card--section', {
className={classnames('card', {
'card--disabled': IS_WEB,
})}
>
<header className="card__header card__header--flat">
<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>
)}
<TransactionList transactions={transactions} title={__('Transaction History')} />
</section>
</Page>
);

View file

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

View file

@ -1,5 +1,5 @@
import { connect } from 'react-redux';
import FileList from './view';
import Wallet from './view';
const select = state => ({});
@ -8,4 +8,4 @@ const perform = dispatch => ({});
export default connect(
select,
perform
)(FileList);
)(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,
walletReducer,
notificationsReducer,
tagsReducer,
tagsReducerBuilder,
} from 'lbry-redux';
import { userReducer, rewardsReducer, costInfoReducer, blacklistReducer, homepageReducer, statsReducer } from 'lbryinc';
import appReducer from 'redux/reducers/app';
@ -15,6 +15,17 @@ import contentReducer from 'redux/reducers/content';
import settingsReducer from 'redux/reducers/settings';
import subscriptionsReducer from 'redux/reducers/subscriptions';
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 =>
combineReducers({
@ -34,7 +45,7 @@ export default history =>
settings: settingsReducer,
stats: statsReducer,
subscriptions: subscriptionsReducer,
tags: tagsReducer,
tags: tagsReducerBuilder({ followedTags: defaultFollowedTags, knownTags: getDefaultKnownTags() }),
user: userReducer,
wallet: walletReducer,
});

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,8 +1,9 @@
.file-list__header {
display: flex;
align-items: center;
min-height: 4rem;
height: 4.5rem;
padding: var(--spacing-medium);
font-size: 1rem; // Ensures select & header text have same font-size
color: $lbry-white;
border-top-left-radius: var(--card-radius);
border-top-right-radius: var(--card-radius);
@ -14,6 +15,20 @@
fieldset-section {
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--small {
height: 3rem;
font-size: 1em;
}
.file-list__dropdown {
@ -53,6 +68,7 @@
display: flex;
align-items: center;
margin-left: auto;
font-size: 1.4em;
& > * {
margin-left: var(--spacing-small);
@ -80,10 +96,6 @@
flex-shrink: 0;
margin-right: var(--spacing-medium);
}
.media__thumb--profile {
width: 6rem;
}
}
.file-list__item--injected,
@ -135,3 +147,8 @@
.file-list__item-tags {
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;
align-items: center;
& > *:not(:first-child) {
margin-left: var(--spacing-small);
& > *:not(:last-child) {
margin-right: var(--spacing-small);
}
@media (max-width: 600px) {
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 {
[type='button'],
[type='submit'] {
&.button--primary {
&.button--inverse {
&:not(:hover) {
background-color: $lbry-teal-5;
border-color: $lbry-teal-5;
background-color: transparent;
border-color: $lbry-black;
color: $lbry-black;
}
&:hover {
background-color: $lbry-teal-3;
border-color: $lbry-teal-3;
background-color: $lbry-teal-4;
}
}
}
@ -203,6 +203,7 @@ fieldset-section {
// input-submit is connected to a button
// 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
input[type='email'],
input[type='text'] {
height: var(--button-height);
@ -218,6 +219,7 @@ fieldset-section {
border-bottom-left-radius: 0;
border-top-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%;
background-color: $lbry-white;
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'] & {
background-color: mix($lbry-black, $lbry-gray-3, 90%);
color: $lbry-white;
border-bottom: none;
box-shadow: var(--card-box-shadow) $lbry-black;
}
}
@ -17,7 +20,6 @@
width: 100%;
height: calc(var(--header-height) - 1px);
max-width: var(--page-max-width);
padding-left: var(--spacing-medium);
display: flex;
justify-content: space-between;
margin: auto;
@ -25,14 +27,13 @@
.header__navigation {
display: flex;
justify-content: space-between;
&:last-of-type {
padding-left: var(--spacing-small);
width: var(--side-nav-width);
}
@media (max-width: 600px) {
display: none;
width: calc(var(--side-nav-width) + var(--spacing-medium));
@media (max-width: 600px) {
display: none;
}
}
}
@ -49,7 +50,7 @@
border-radius: 0;
svg {
stroke: $lbry-black;
stroke: $lbry-gray-5;
}
&:hover {
@ -109,28 +110,15 @@
}
.header__navigation-item--right-action {
&:first-of-type {
margin-right: auto;
}
&:not(:first-of-type) {
padding: 0 var(--spacing-medium);
}
&:last-of-type {
margin-right: 0;
}
align-self: flex-end;
margin-left: auto;
padding: 0 var(--spacing-small);
}
.header__navigation-item--upgrade {
background-color: $lbry-teal-5;
color: $lbry-white;
color: $lbry-teal-5;
svg {
stroke: $lbry-white;
}
&:hover {
background-color: $lbry-teal-4;
stroke: $lbry-teal-5;
}
}

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;
min-height: 100vh;
width: 100vw;
padding-top: var(--spacing-main-padding);
padding-left: var(--spacing-medium);
padding-top: var(--header-height);
padding-left: var(--spacing-large);
padding-right: var(--spacing-large);
background-color: mix($lbry-white, $lbry-gray-1, 70%);
display: flex;
@ -18,7 +19,7 @@
width: 100%;
margin-left: auto;
margin-right: auto;
margin-top: var(--spacing-main-padding);
margin-top: var(--spacing-large);
position: relative;
}
@ -36,7 +37,6 @@
.main--file-page {
display: grid;
grid-gap: var(--spacing-large);
grid-template-rows: auto 1fr;
grid-template-columns: 1fr auto;
max-width: calc(100% - var(--side-nav-width) - var(--spacing-main-padding));
@ -57,7 +57,20 @@
}
.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;
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;
}
a {
color: $lbry-blue-1;
a,
button {
display: inline-block;
.button__label {
white-space: normal;
}
}
// Lists

View file

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

View file

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

View file

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

View file

@ -2,6 +2,8 @@
table,
.table {
margin-bottom: var(--spacing-small);
[data-mode='dark'] & {
background-color: transparent;
@ -9,17 +11,23 @@ table,
border-bottom: 2px solid $lbry-white;
}
}
th,
td {
padding-left: var(--spacing-large);
}
}
td {
max-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
tr {
padding: 0 var(--spacing-small);
.table__header {
margin: var(--spacing-large);
& + .table__header {
margin-top: 0;
}
}
.table__item--actionable {
@ -45,6 +53,17 @@ tr {
.table--transactions {
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 {

View file

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

View file

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

View file

@ -0,0 +1,97 @@
// 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-small);
}
}
.menu__link {
display: flex;
align-items: center;
padding: var(--spacing-medium);
}
.menu__title,
.menu__link {
font-size: 1.3rem;
color: lighten($lbry-black, 20%);
.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;
> * {
flex-basis: 0;
flex-grow: 1;
flex-basis: 0;
&:not(:last-of-type) {
&:first-child {
margin-right: 1.5rem;
}
}
@ -130,7 +130,7 @@ code {
.help {
font-size: 1rem;
background-color: rgba($lbry-blue-1, 0.1);
color: $lbry-gray-5;
color: darken($lbry-gray-5, 15%);
display: block;
padding: 1rem;
margin-top: var(--spacing-medium);

View file

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

View file

@ -5,8 +5,11 @@ export function formatCredits(amount, precision = 1) {
}
export function formatFullPrice(amount, precision = 1) {
let formated = '';
if (!amount) {
return 0;
}
let formated = '';
const quantity = amount.toString().split('.');
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,80 +4,192 @@
"Cancel": "Cancel",
"Show More...": "Show More...",
"Show Less": "Show Less",
"Starting up": "Starting up",
"Connecting": "Connecting",
"LBRY": "LBRY",
"Navigate back": "Navigate back",
"Navigate forward": "Navigate forward",
"Menu": "Menu",
"Your wallet": "Your wallet",
"Publish content": "Publish content",
"Account": "Account",
"Overview": "Overview",
"Wallet": "Wallet",
"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",
"Publishes": "Publishes",
"Library": "Library",
"Overview": "Overview",
"Invite": "Invite",
"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",
"Following": "Following",
"The tags you follow will change what's trending for you.": "The tags you follow will change what's trending for you.",
"Tags": "Tags",
"Search for more tags": "Search for more tags",
"Publish content": "Publish content",
"Unfollow this tag": "Unfollow this tag",
"Follow this tag": "Follow this tag",
"Published on": "Published on",
"Send a tip": "Send a tip",
"Share": "Share",
"Play": "Play",
"Subscribe": "Subscribe",
"Unsubscribe": "Unsubscribe",
"Report content": "Report content",
"Content-Type": "Content-Type",
"Languages": "Languages",
"License": "License",
"Want to comment?": "Want to comment?",
"More": "More",
"FREE": "FREE",
"Related": "Related",
"No related content found": "No related content found",
"Download": "Download",
"Content": "Content",
"What are you publishing?": "What are you publishing?",
"Read our": "Read our",
"FAQ": "FAQ",
"to learn more.": "to learn more.",
"Title": "Title",
"Titular Title": "Titular Title",
"Description": "Description",
"Description of your content": "Description of your content",
"Thumbnail": "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?",
"Free": "Free",
"Choose price": "Choose price",
"Anonymous or under a channel?": "Anonymous or under a channel?",
"This is a username or handle that your content can be found under.": "This is a username or handle that your content can be found under.",
"Ex. @Marvel, @TheBeatles, @BooksByJoe": "Ex. @Marvel, @TheBeatles, @BooksByJoe",
"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).",
"Learn more": "Learn more",
"Name": "Name",
"Deposit (LBC)": "Deposit (LBC)",
"Mature audiences only": "Mature audiences only",
"Language": "Language",
"English": "English",
"Chinese": "Chinese",
"French": "French",
"German": "German",
"Japanese": "Japanese",
"Russian": "Russian",
"Spanish": "Spanish",
"Indonesian": "Indonesian",
"Italian": "Italian",
"Dutch": "Dutch",
"Turkish": "Turkish",
"Polish": "Polish",
"Malay": "Malay",
"By continuing, you accept the": "By continuing, you accept the",
"LBRY Terms of Service": "LBRY Terms of Service",
"Choose File": "Choose File",
"No File Chosen": "No File Chosen",
"Choose Thumbnail": "Choose Thumbnail",
"Enter a thumbnail URL": "Enter a thumbnail URL",
"Anonymous": "Anonymous",
"New channel...": "New channel...",
"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.",
"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)",
"None": "None",
"Public Domain": "Public Domain",
"Copyrighted...": "Copyrighted...",
"Other...": "Other...",
"Email": "Email",
"Your email has been successfully verified": "Your email has been successfully verified",
"Your Email": "Your Email",
"Change": "Change",
"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.",
"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",
"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",
"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",
"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",
"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.",
"Get New Address": "Get New Address",
"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.",
"Type": "Type",
"Details": "Details",
"Transaction": "Transaction",
"Date": "Date",
"Unlock Tip": "Unlock Tip",
"Abandon Claim": "Abandon Claim",
"fee": "fee",
"Find New Tags To Follow": "Find New Tags To Follow",
"Aw shucks!": "Aw shucks!",
"There was an error. It's been reported and will be fixed": "There was an error. It's been reported and will be fixed",
"Try": "Try",
"refreshing the app": "refreshing the app",
"to fix it": "to fix it",
"Search": "Search",
"Starting up": "Starting up",
"Connecting": "Connecting",
"It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.": "It looks like you deleted or moved this file. We're rebuilding it now. It will only take a few seconds.",
"Newest First": "Newest First",
"Oldest First": "Oldest First",
"Contact": "Contact",
"Site": "Site",
"Send a tip to": "Send a tip to",
"This will appear as a tip for \"Why I Quit YouTube\".": "This will appear as a tip for \"Why I Quit YouTube\".",
"You sent 10 LBC as a tip, Mahalo!": "You sent 10 LBC as a tip, Mahalo!",
"History": "History",
"/wallet": "/wallet",
"Pending": "Pending",
"You have %s in unclaimed rewards.": "You have %s in unclaimed rewards.",
"Download Directory": "Download Directory",
"LBRY downloads will be saved here.": "LBRY downloads will be saved here.",
"Max Purchase Price": "Max Purchase Price",
@ -104,178 +216,36 @@
"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.",
"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.",
"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",
"Home": "Home",
"Aw shucks!": "Aw shucks!",
"There was an error. It's been reported and will be fixed": "There was an error. It's been reported and will be fixed",
"Try": "Try",
"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",
"Send a tip": "Send a tip",
"Share": "Share",
"Play": "Play",
"Report content": "Report content",
"Content-Type": "Content-Type",
"Languages": "Languages",
"License": "License",
"Want to comment?": "Want to comment?",
"More": "More",
"Download": "Download",
"Content": "Content",
"What are you publishing?": "What are you publishing?",
"to learn more.": "to learn more.",
"Title": "Title",
"Titular Title": "Titular Title",
"Description": "Description",
"Description of your content": "Description of your content",
"Thumbnail": "Thumbnail",
"Enter a URL for your thumbnail.": "Enter a URL for your thumbnail.",
"How much will this content cost?": "How much will this content cost?",
"Free": "Free",
"Choose price": "Choose price",
"Anonymous or under a channel?": "Anonymous or under a channel?",
"This is a username or handle that your content can be found under.": "This is a username or handle that your content can be found under.",
"Ex. @Marvel, @TheBeatles, @BooksByJoe": "Ex. @Marvel, @TheBeatles, @BooksByJoe",
"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).",
"Name": "Name",
"Deposit (LBC)": "Deposit (LBC)",
"Mature audiences only": "Mature audiences only",
"Language": "Language",
"English": "English",
"Chinese": "Chinese",
"French": "French",
"German": "German",
"Japanese": "Japanese",
"Russian": "Russian",
"Spanish": "Spanish",
"Indonesian": "Indonesian",
"Italian": "Italian",
"Dutch": "Dutch",
"Turkish": "Turkish",
"Polish": "Polish",
"Malay": "Malay",
"By continuing, you accept the": "By continuing, you accept the",
"LBRY Terms of Service": "LBRY Terms of Service",
"Choose File": "Choose File",
"No File Chosen": "No File Chosen",
"Thumbnail Preview": "Thumbnail Preview",
"Use thumbnail upload tool": "Use thumbnail upload tool",
"Anonymous": "Anonymous",
"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.",
"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.",
"License (Optional)": "License (Optional)",
"None": "None",
"Public Domain": "Public Domain",
"Copyrighted...": "Copyrighted...",
"Other...": "Other...",
"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)",
"Choose Thumbnail": "Choose Thumbnail",
"Enter a thumbnail URL": "Enter a thumbnail URL",
"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).",
"Address copied.": "Address copied.",
"Get New Address": "Get New Address",
"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.",
"Send Credits": "Send Credits",
"Send LBC to your friends or favorite creators": "Send LBC to your friends or favorite creators",
"Recipient address": "Recipient address",
"Send": "Send",
"No transactions.": "No transactions.",
"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",
"Confirm File Remove": "Confirm File Remove",
"Remove": "Remove",
"Are you sure you'd like to remove": "Are you sure you'd like to remove",
"from the LBRY app?": "from the LBRY app?",
"Also delete this file from my computer": "Also delete this file from my computer",
"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",
"Warning!": "Warning!",
"Confirm External Resource": "Confirm External Resource",
"Continue": "Continue",
"This file has been shared with you by other people.": "This file has been shared with you by other people.",
"LBRY Inc is not responsible for its content, click continue to proceed at your own risk.": "LBRY Inc is not responsible for its content, click continue to proceed at your own risk.",
"Find what you were looking for?": "Find what you were looking for?",
"Yes": "Yes",
"No": "No",
"These search results are provided by LBRY, Inc.": "These search results are provided by LBRY, Inc.",
"FILTER": "FILTER",
"View": "View",
"No results": "No results",
"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": "View file"
}

View file

@ -283,5 +283,8 @@
"Open file": "Otwórz plik",
"NEW": "NEW",
"Failed to load settings.": "Failed to load settings.",
"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."
"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.",
"Wallet": "Wallet",
"Home": "Home",
"Following": "Following"
}

View file

@ -909,22 +909,41 @@
resolved "https://registry.yarnpkg.com/@posthtml/esm/-/esm-1.0.0.tgz#09bcb28a02438dcee22ad1970ca1d85a000ae0cf"
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"
resolved "https://registry.yarnpkg.com/@reach/auto-id/-/auto-id-0.2.0.tgz#97f9e48fe736aa5c6f4f32cf73c1f19d005f8550"
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"
resolved "https://registry.yarnpkg.com/@reach/component-component/-/component-component-0.1.3.tgz#5d156319572dc38995b246f81878bc2577c517e5"
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":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@reach/observe-rect/-/observe-rect-1.0.3.tgz#2ea3dcc369ab22bd9f050a92ea319321356a61e8"
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"
resolved "https://registry.yarnpkg.com/@reach/rect/-/rect-0.2.1.tgz#7343020174c90e2290b844d17c03fd9c78e6b601"
integrity sha512-aZ9RsNHDMQ3zETonikqu9/85iXxj+LPqZ9Gr9UAncj3AufYmGeWG3XG6b37B+7ORH+mkhVpLU2ZlIWxmOe9Cqg==
@ -941,11 +960,40 @@
"@reach/utils" "^0.2.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":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.2.2.tgz#c3a05ae9fd1f921988ae8a89b5a0d28d1a2b92df"
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":
version "0.3.0"
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"
zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#291324d03f694c4fefa6967aa7be02d9245596a8:
lbry-redux@lbryio/lbry-redux#6a447d0542d23b9a37e266f5f85d3bde5297a451:
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:
proxy-polyfill "0.1.6"
reselect "^3.0.0"