Moar discovery #2553
99 changed files with 1236 additions and 936 deletions
|
@ -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",
|
||||
|
|
|
@ -105,6 +105,7 @@ class Button extends React.PureComponent<Props> {
|
|||
exact
|
||||
to={path}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (onClick) {
|
||||
|
|
|
@ -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)}
|
||||
|
|
11
src/ui/component/claimList/index.js
Normal file
11
src/ui/component/claimList/index.js
Normal 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);
|
|
@ -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>
|
||||
))}
|
|
@ -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);
|
|
@ -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;
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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>
|
||||
);
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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={() => {
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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()),
|
||||
});
|
||||
|
||||
|
|
|
@ -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')}
|
||||
|
||||
<CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />
|
||||
|
||||
{__('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')}
|
||||
|
||||
<CreditAmount inheritStyle amount={unclaimedRewardAmount} precision={8} />
|
||||
|
||||
{__('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>
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
25
src/ui/component/rewardTotal/index.js
Normal file
25
src/ui/component/rewardTotal/index.js
Normal 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);
|
BIN
src/ui/component/rewardTotal/total-background.png
Normal file
BIN
src/ui/component/rewardTotal/total-background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
25
src/ui/component/rewardTotal/view.jsx
Normal file
25
src/ui/component/rewardTotal/view.jsx
Normal 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;
|
|
@ -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} />
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 => ({
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
18
src/ui/component/userEmail/index.js
Normal file
18
src/ui/component/userEmail/index.js
Normal 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);
|
60
src/ui/component/userEmail/view.jsx
Normal file
60
src/ui/component/userEmail/view.jsx
Normal 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,
|
||||
Should user be a first class type and/or should this value simply be selected directly off of the user? Should user be a first class type and/or should this value simply be selected directly off of the user?
Yes to both of these Yes to both of these
|
||||
},
|
||||
};
|
||||
|
||||
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;
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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
14
src/ui/constants/tags.js
Normal 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'];
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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')} />}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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 ? (
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
})}
|
||||
>
|
||||
|
|
|
@ -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={
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import SendReceivePage from './view';
|
||||
|
||||
export default SendReceivePage;
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
is the channel uri guaranteed to have a is the channel uri guaranteed to have a `#`? they will never be URIs without a claim id?
The channel id is required (for now?) in internal-apis for subscriptions. The app always uses the full uri for the subscribeButton component The channel id is required (for now?) in internal-apis for subscriptions. The app always uses the full uri for the subscribeButton component
|
||||
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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
});
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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);
|
17
src/ui/page/wallet/view.jsx
Normal file
17
src/ui/page/wallet/view.jsx
Normal 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;
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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%);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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%);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
minor tsk tsk for BEM violation minor tsk tsk for BEM violation
|
||||
}
|
||||
|
||||
.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%);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
minor boos minor boos
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -46,6 +46,7 @@
|
|||
|
||||
.spinner--small {
|
||||
height: 10px;
|
||||
display: inline-block;
|
||||
|
||||
.rect {
|
||||
width: 3px;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
97
src/ui/scss/component/menu-button.scss
Normal file
97
src/ui/scss/component/menu-button.scss
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -46,7 +46,7 @@ $large-breakpoint: 1921px;
|
|||
--button-height: 2.6rem;
|
||||
|
||||
// Header
|
||||
--header-height: 5rem;
|
||||
--header-height: 6rem;
|
||||
|
||||
// Card
|
||||
--card-radius: 5px;
|
||||
|
|
|
@ -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
31
src/ui/util/use-tween.js
Normal file
|
@ -0,0 +1,31 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
I approve of copy and pasting this wherever you copy and pasted it from I approve of copy and pasting this wherever you copy and pasted it from
📈 📈
|
||||
|
||||
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;
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
58
yarn.lock
58
yarn.lock
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue
https://github.com/lbryio/lbry/issues/2245