Add tags to publish page, infinite scroll, navigation improvements #2593
77 changed files with 694 additions and 429 deletions
|
@ -23,6 +23,7 @@
|
|||
"WEBPACK_PORT": true
|
||||
},
|
||||
"rules": {
|
||||
"brace-style": 0,
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"handle-callback-err": 0,
|
||||
"indent": 0,
|
||||
|
|
12
CHANGELOG.md
12
CHANGELOG.md
|
@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [0.34.0] - [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Lots of speed improvements
|
||||
|
||||
### Added
|
||||
|
||||
- New app design for better content discovery ([2477](https://github.com/lbryio/lbry-desktop/pull/2477))
|
||||
- New loading page ([2491](https://github.com/lbryio/lbry-desktop/pull/2491))
|
||||
- Comments ([2510](https://github.com/lbryio/lbry-desktop/pull/2510))
|
||||
|
||||
## [0.33.1] - [2019-06-12]
|
||||
|
||||
### Fixed
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "LBRY",
|
||||
"version": "0.33.1",
|
||||
"version": "0.34.0-rc.3",
|
||||
"description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
|
||||
"keywords": [
|
||||
"lbry"
|
||||
|
@ -63,7 +63,7 @@
|
|||
"@exponent/electron-cookies": "^2.0.0",
|
||||
"@hot-loader/react-dom": "16.8",
|
||||
"@lbry/color": "^1.0.2",
|
||||
"@lbry/components": "^2.7.2",
|
||||
"@lbry/components": "^2.7.4",
|
||||
"@reach/rect": "^0.2.1",
|
||||
"@reach/tabs": "^0.1.5",
|
||||
"@reach/tooltip": "^0.2.1",
|
||||
|
@ -123,7 +123,7 @@
|
|||
"jsmediatags": "^3.8.1",
|
||||
"json-loader": "^0.5.4",
|
||||
"lbry-format": "https://github.com/lbryio/lbry-format.git",
|
||||
"lbry-redux": "lbryio/lbry-redux#b3bf3f6d53410ff1c5415b51ca425341e364959f",
|
||||
"lbry-redux": "lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66",
|
||||
"lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845",
|
||||
"lint-staged": "^7.0.2",
|
||||
"localforage": "^1.7.1",
|
||||
|
@ -198,7 +198,7 @@
|
|||
"yarn": "^1.3"
|
||||
},
|
||||
"lbrySettings": {
|
||||
"lbrynetDaemonVersion": "0.38.0rc10",
|
||||
"lbrynetDaemonVersion": "0.38.0",
|
||||
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
|
||||
"lbrynetDaemonDir": "static/daemon",
|
||||
"lbrynetDaemonFileName": "lbrynet"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { hot } from 'react-hot-loader/root';
|
||||
import { connect } from 'react-redux';
|
||||
import { doUpdateBlockHeight, doError } from 'lbry-redux';
|
||||
import { doUpdateBlockHeight, doError, doFetchTransactions } from 'lbry-redux';
|
||||
import { selectUser, doRewardList, doFetchRewardedContent } from 'lbryinc';
|
||||
import { selectThemePath } from 'redux/selectors/settings';
|
||||
import App from './view';
|
||||
|
@ -15,6 +15,7 @@ const perform = dispatch => ({
|
|||
updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
|
||||
fetchRewards: () => dispatch(doRewardList()),
|
||||
fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
|
||||
fetchTransactions: () => dispatch(doFetchTransactions()),
|
||||
});
|
||||
|
||||
export default hot(
|
||||
|
|
|
@ -9,6 +9,8 @@ import { openContextMenu } from 'util/context-menu';
|
|||
import useKonamiListener from 'util/enhanced-layout';
|
||||
import Yrbl from 'component/yrbl';
|
||||
|
||||
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
|
||||
|
||||
type Props = {
|
||||
alertError: (string | {}) => void,
|
||||
pageTitle: ?string,
|
||||
|
@ -16,18 +18,23 @@ type Props = {
|
|||
theme: string,
|
||||
fetchRewards: () => void,
|
||||
fetchRewardedContent: () => void,
|
||||
fetchTransactions: () => void,
|
||||
};
|
||||
|
||||
function App(props: Props) {
|
||||
const { theme, fetchRewards, fetchRewardedContent } = props;
|
||||
const { theme, fetchRewards, fetchRewardedContent, fetchTransactions } = props;
|
||||
const appRef = useRef();
|
||||
const isEnhancedLayout = useKonamiListener();
|
||||
|
||||
useEffect(() => {
|
||||
ReactModal.setAppElement(appRef.current);
|
||||
fetchRewards();
|
||||
fetchRewardedContent();
|
||||
}, [fetchRewards, fetchRewardedContent]);
|
||||
|
||||
// @if TARGET='app'
|
||||
fetchRewards();
|
||||
fetchTransactions();
|
||||
// @endif
|
||||
}, [fetchRewards, fetchRewardedContent, fetchTransactions]);
|
||||
|
||||
useEffect(() => {
|
||||
// $FlowFixMe
|
||||
|
@ -38,7 +45,7 @@ function App(props: Props) {
|
|||
<div ref={appRef} onContextMenu={e => openContextMenu(e)}>
|
||||
<Header />
|
||||
|
||||
<div className="main-wrapper">
|
||||
<div className={MAIN_WRAPPER_CLASS}>
|
||||
<div className="main-wrapper-inner">
|
||||
<Router />
|
||||
<SideBar />
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
import { MAIN_WRAPPER_CLASS } from 'component/app/view';
|
||||
import type { Node } from 'react';
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import Spinner from 'component/spinner';
|
||||
|
@ -18,13 +19,29 @@ type Props = {
|
|||
loading: boolean,
|
||||
type: string,
|
||||
empty?: string,
|
||||
meta?: Node,
|
||||
defaultSort?: boolean,
|
||||
onScrollBottom?: any => void,
|
||||
page?: number,
|
||||
pageSize?: number,
|
||||
// If using the default header, this is a unique ID needed to persist the state of the filter setting
|
||||
persistedStorageKey?: string,
|
||||
};
|
||||
|
||||
export default function ClaimList(props: Props) {
|
||||
const { uris, headerAltControls, injectedItem, loading, persistedStorageKey, empty, meta, type, header } = props;
|
||||
const {
|
||||
uris,
|
||||
headerAltControls,
|
||||
injectedItem,
|
||||
loading,
|
||||
persistedStorageKey,
|
||||
empty,
|
||||
defaultSort,
|
||||
type,
|
||||
header,
|
||||
onScrollBottom,
|
||||
page,
|
||||
pageSize,
|
||||
} = props;
|
||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||
const hasUris = uris && !!uris.length;
|
||||
const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
|
||||
It could potentially make sense to move this out of here and instead have the scroll state be something that is generally consumable by any component (i.e. track the scroll state app-wide). I'm not up to date on the best techniques for this though. It could potentially make sense to move this out of here and instead have the scroll state be something that is generally consumable by any component (i.e. track the scroll state app-wide).
I'm not up to date on the best techniques for this though.
|
||||
|
@ -33,11 +50,42 @@ export default function ClaimList(props: Props) {
|
|||
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW);
|
||||
}
|
||||
|
||||
const urisLength = uris && uris.length;
|
||||
useEffect(() => {
|
||||
function handleScroll(e) {
|
||||
if (pageSize && onScrollBottom) {
|
||||
const x = document.querySelector(`.${MAIN_WRAPPER_CLASS}`);
|
||||
|
||||
if (x && window.scrollY + window.innerHeight >= x.offsetHeight) {
|
||||
if (!loading && urisLength >= pageSize) {
|
||||
onScrollBottom();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (onScrollBottom) {
|
||||
window.addEventListener('scroll', handleScroll);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('scroll', handleScroll);
|
||||
};
|
||||
}
|
||||
}, [loading, onScrollBottom, urisLength]);
|
||||
|
||||
return (
|
||||
<section className={classnames('file-list')}>
|
||||
<section
|
||||
className={classnames('claim-list', {
|
||||
'claim-list--small': type === 'small',
|
||||
})}
|
||||
>
|
||||
{header !== false && (
|
||||
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
|
||||
{header || (
|
||||
{header}
|
||||
{loading && <Spinner light type="small" />}
|
||||
<div className="claim-list__alt-controls">
|
||||
{headerAltControls}
|
||||
{defaultSort && (
|
||||
<FormField
|
||||
className="claim-list__dropdown"
|
||||
type="select"
|
||||
|
@ -49,17 +97,15 @@ export default function ClaimList(props: Props) {
|
|||
<option value={SORT_OLD}>{__('Oldest First')}</option>
|
||||
</FormField>
|
||||
)}
|
||||
{loading && <Spinner light type="small" />}
|
||||
<div className="claim-list__alt-controls">{headerAltControls}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{meta && <div className="claim-list__meta">{meta}</div>}
|
||||
{hasUris && (
|
||||
<ul>
|
||||
{sortedUris.map((uri, index) => (
|
||||
<React.Fragment key={uri}>
|
||||
<ClaimPreview uri={uri} type={type} />
|
||||
{index === 4 && injectedItem && <li className="claim-list__item--injected">{injectedItem}</li>}
|
||||
<ClaimPreview uri={uri} type={type} placeholder={loading && (!page || page === 1)} />
|
||||
{index === 4 && injectedItem && <li className="claim-preview--injected">{injectedItem}</li>}
|
||||
</React.Fragment>
|
||||
))}
|
||||
</ul>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import ClaimListDiscover from './view';
|
||||
|
||||
const select = state => ({
|
||||
uris: selectLastClaimSearchUris(state),
|
||||
loading: selectFetchingClaimSearch(state),
|
||||
subscribedChannels: selectSubscriptions(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import moment from 'moment';
|
||||
import usePersistedState from 'util/use-persisted-state';
|
||||
import { FormField } from 'component/common/form';
|
||||
import ClaimList from 'component/claimList';
|
||||
import Tag from 'component/tag';
|
||||
import usePersistedState from 'util/use-persisted-state';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
const TIME_DAY = 'day';
|
||||
const TIME_WEEK = 'week';
|
||||
const TIME_MONTH = 'month';
|
||||
|
@ -14,15 +16,18 @@ const TIME_YEAR = 'year';
|
|||
const TIME_ALL = 'all';
|
||||
const SEARCH_SORT_YOU = 'you';
|
||||
const SEARCH_SORT_ALL = 'everyone';
|
||||
const SEARCH_SORT_CHANNELS = 'channels';
|
||||
|
||||
const TYPE_TRENDING = 'trending';
|
||||
const TYPE_TOP = 'top';
|
||||
const TYPE_NEW = 'new';
|
||||
const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_ALL];
|
||||
const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_CHANNELS, SEARCH_SORT_ALL];
|
||||
const SEARCH_TYPES = ['trending', 'top', 'new'];
|
||||
const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL];
|
||||
|
||||
type Props = {
|
||||
uris: Array<string>,
|
||||
subscribedChannels: Array<Subscription>,
|
||||
doClaimSearch: (number, {}) => void,
|
||||
injectedItem: any,
|
||||
tags: Array<string>,
|
||||
|
@ -33,19 +38,30 @@ type 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);
|
||||
const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta, subscribedChannels } = props;
|
||||
const [personalSort, setPersonalSort] = usePersistedState('claim-list-discover:personalSort', SEARCH_SORT_YOU);
|
||||
const [typeSort, setTypeSort] = usePersistedState('claim-list-discover:typeSort', TYPE_TRENDING);
|
||||
const [timeSort, setTimeSort] = usePersistedState('claim-list-discover:timeSort', TIME_WEEK);
|
||||
const [page, setPage] = useState(1);
|
||||
|
||||
const toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1);
|
||||
const tagsString = tags.join(',');
|
||||
const channelsIdString = subscribedChannels.map(channel => channel.uri.split('#')[1]).join(',');
|
||||
useEffect(() => {
|
||||
const options = {};
|
||||
const options: {
|
||||
page_size: number,
|
||||
any_tags?: Array<string>,
|
||||
order_by?: Array<string>,
|
||||
channel_ids?: Array<string>,
|
||||
release_time?: string,
|
||||
} = { page_size: PAGE_SIZE, page };
|
||||
const newTags = tagsString.split(',');
|
||||
const newChannelIds = channelsIdString.split(',');
|
||||
|
||||
if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
|
||||
options.any_tags = newTags;
|
||||
} else if (personalSort === SEARCH_SORT_CHANNELS) {
|
||||
options.channel_ids = newChannelIds;
|
||||
}
|
||||
|
||||
if (typeSort === TYPE_TRENDING) {
|
||||
|
@ -65,7 +81,15 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
|
||||
doClaimSearch(20, options);
|
||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, tagsString]);
|
||||
}, [personal, personalSort, typeSort, timeSort, doClaimSearch, page, tagsString, channelsIdString]);
|
||||
|
||||
function getLabel(type) {
|
||||
if (type === SEARCH_SORT_ALL) {
|
||||
return __('Everyone');
|
||||
}
|
||||
|
||||
return type === SEARCH_SORT_YOU ? __('Tags You Follow') : __('Channels You Follow');
|
||||
}
|
||||
|
||||
const header = (
|
||||
<h1 className="card__title--flex">
|
||||
|
@ -91,20 +115,18 @@ function ClaimListDiscover(props: Props) {
|
|||
name="trending_overview"
|
||||
className="claim-list__dropdown"
|
||||
value={personalSort}
|
||||
onChange={e => setPersonalSort(e.target.value)}
|
||||
onChange={e => {
|
||||
setPage(1);
|
||||
setPersonalSort(e.target.value);
|
||||
}}
|
||||
>
|
||||
{SEARCH_FILTER_TYPES.map(type => (
|
||||
<option key={type} value={type}>
|
||||
{toCapitalCase(type)}
|
||||
{getLabel(type)}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
)}
|
||||
</h1>
|
||||
);
|
||||
|
||||
const headerAltControls = (
|
||||
<React.Fragment>
|
||||
{typeSort === 'top' && (
|
||||
<FormField
|
||||
className="claim-list__dropdown"
|
||||
|
@ -115,24 +137,29 @@ function ClaimListDiscover(props: Props) {
|
|||
>
|
||||
{SEARCH_TIMES.map(time => (
|
||||
<option key={time} value={time}>
|
||||
{toCapitalCase(time)}
|
||||
{/* i18fixme */}
|
||||
{__('This')} {toCapitalCase(time)}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
)}
|
||||
</React.Fragment>
|
||||
</h1>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
meta={meta}
|
||||
loading={loading}
|
||||
uris={uris}
|
||||
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
|
||||
header={header}
|
||||
headerAltControls={headerAltControls}
|
||||
headerAltControls={meta}
|
||||
onScrollBottom={() => setPage(page + 1)}
|
||||
page={page}
|
||||
pageSize={PAGE_SIZE}
|
||||
/>
|
||||
|
||||
{loading && page > 1 && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
doResolveUri,
|
||||
makeSelectClaimForUri,
|
||||
makeSelectIsUriResolving,
|
||||
makeSelectClaimIsMine,
|
||||
makeSelectClaimIsPending,
|
||||
makeSelectThumbnailForUri,
|
||||
makeSelectTitleForUri,
|
||||
makeSelectClaimIsNsfw,
|
||||
} from 'lbry-redux';
|
||||
import { selectBlackListedOutpoints } from 'lbryinc';
|
||||
import { selectShowNsfw } from 'redux/selectors/settings';
|
||||
import ClaimPreview from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
pending: makeSelectClaimIsPending(props.uri)(state),
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
obscureNsfw: !selectShowNsfw(state),
|
||||
claimIsMine: makeSelectClaimIsMine(props.uri)(state),
|
||||
isResolvingUri: makeSelectIsUriResolving(props.uri)(state),
|
||||
thumbnail: makeSelectThumbnailForUri(props.uri)(state),
|
||||
title: makeSelectTitleForUri(props.uri)(state),
|
||||
nsfw: makeSelectClaimIsNsfw(props.uri)(state),
|
||||
blackListedOutpoints: selectBlackListedOutpoints(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
resolveUri: uri => dispatch(doResolveUri(uri)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
perform
|
||||
)(ClaimPreview);
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import React, { useEffect } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import { convertToShareLink } from 'lbry-redux';
|
||||
import { parseURI, convertToShareLink } from 'lbry-redux';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { openCopyLinkMenu } from 'util/context-menu';
|
||||
import { formatLbryUriForWeb } from 'util/uri';
|
||||
|
@ -53,8 +53,8 @@ function ClaimPreview(props: Props) {
|
|||
blackListedOutpoints,
|
||||
} = props;
|
||||
const haventFetched = claim === undefined;
|
||||
const abandoned = !isResolvingUri && !claim;
|
||||
const isChannel = claim && claim.value_type === 'channel';
|
||||
const abandoned = !isResolvingUri && !claim && !placeholder;
|
||||
const { isChannel } = parseURI(uri);
|
||||
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
||||
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
|
||||
|
||||
|
@ -92,12 +92,12 @@ function ClaimPreview(props: Props) {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (placeholder && !claim) {
|
||||
if (placeholder || isResolvingUri) {
|
||||
return (
|
||||
<li className="claim-list__item" disabled>
|
||||
<li className="claim-preview" disabled>
|
||||
<div className="placeholder media__thumb" />
|
||||
<div className="placeholder__wrapper">
|
||||
<div className="placeholder claim-list__item-title" />
|
||||
<div className="placeholder claim-preview-title" />
|
||||
<div className="placeholder media__subtitle" />
|
||||
</div>
|
||||
</li>
|
||||
|
@ -109,15 +109,15 @@ function ClaimPreview(props: Props) {
|
|||
role="link"
|
||||
onClick={pending ? undefined : onClick}
|
||||
onContextMenu={handleContextMenu}
|
||||
className={classnames('claim-list__item', {
|
||||
'claim-list__item--large': type === 'large',
|
||||
className={classnames('claim-preview', {
|
||||
'claim-preview--large': type === 'large',
|
||||
'claim-list__pending': pending,
|
||||
})}
|
||||
>
|
||||
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
|
||||
<div className="claim-list__item-metadata">
|
||||
<div className="claim-list__item-info">
|
||||
<div className="claim-list__item-title">
|
||||
<div className="claim-preview-metadata">
|
||||
<div className="claim-preview-info">
|
||||
<div className="claim-preview-title">
|
||||
<TruncatedText text={title || (claim && claim.name)} lines={1} />
|
||||
</div>
|
||||
{type !== 'small' && (
|
||||
|
@ -128,7 +128,7 @@ function ClaimPreview(props: Props) {
|
|||
)}
|
||||
</div>
|
||||
|
||||
<div className="claim-list__item-properties">
|
||||
<div className="claim-preview-properties">
|
||||
<div className="media__subtitle">
|
||||
<UriIndicator uri={uri} link />
|
||||
{pending && <div>Pending...</div>}
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as React from 'react';
|
|||
import classnames from 'classnames';
|
||||
import Button from 'component/button';
|
||||
|
||||
const SLIM_TAGS = 2;
|
||||
const SLIM_TAGS = 1;
|
||||
const NORMAL_TAGS = 4;
|
||||
const LARGE_TAGS = 10;
|
||||
|
||||
|
|
|
@ -40,6 +40,12 @@ export const icons = {
|
|||
<path d="M294.3,150.9l2-12.6l-12.2-2.1l0.8-4.9l17.1,2.9l-2.8,17.5L294.3,150.9L294.3,150.9z" />
|
||||
</svg>
|
||||
),
|
||||
[ICONS.FEATURED]: buildIcon(
|
||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||
<circle cx="12" cy="8" r="7" />
|
||||
<polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.88" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.ARROW_LEFT]: buildIcon(
|
||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||
<path d="M4, 12 L21, 12" />
|
||||
|
@ -62,7 +68,7 @@ export const icons = {
|
|||
<path d="M3, 10 C3, 10 3, 10.4453982 3, 10.9968336 L3, 20.0170446 C3, 20.5675806 3.43788135, 21.0138782 4.00292933, 21.0138781 L8.99707067, 21.0138779 C9.55097324, 21.0138779 10, 20.5751284 10, 20.0089602 L10, 15.0049177 C10, 14.449917 10.4433532, 14 11.0093689, 14 L12.9906311, 14 C13.5480902, 14 14, 14.4387495 14, 15.0049177 L14, 20.0089602 C14, 20.5639609 14.4378817, 21.0138779 15.0029302, 21.0138779 L19.9970758, 21.0138781 C20.5509789, 21.0138782 21.000006, 20.56848 21.000006, 20.0170446 L21.0000057, 10" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.UPLOAD]: buildIcon(
|
||||
[ICONS.PUBLISH]: buildIcon(
|
||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||
<path
|
||||
d="M8, 18 L5, 18 L5, 18 C2.790861, 18 1, 16.209139 1, 14 C1, 11.790861 2.790861, 10 5, 10 C5.35840468, 10 5.70579988, 10.0471371 6.03632437, 10.1355501 C6.01233106, 9.92702603 6, 9.71495305 6, 9.5 C6, 6.46243388 8.46243388, 4 11.5, 4 C14.0673313, 4 16.2238156, 5.7590449 16.8299648, 8.1376465 C17.2052921, 8.04765874 17.5970804, 8 18, 8 C20.7614237, 8 23, 10.2385763 23, 13 C23, 15.7614237 20.7614237, 18 18, 18 L16, 18"
|
||||
|
@ -76,18 +82,12 @@ export const icons = {
|
|||
/>
|
||||
</g>
|
||||
),
|
||||
[ICONS.PUBLISHED]: buildIcon(
|
||||
<g fill="none" fillRule="evenodd" strokeLinecap="round">
|
||||
<path
|
||||
d="M8, 18 L5, 18 L5, 18 C2.790861, 18 1, 16.209139 1, 14 C1, 11.790861 2.790861, 10 5, 10 C5.35840468, 10 5.70579988, 10.0471371 6.03632437, 10.1355501 C6.01233106, 9.92702603 6, 9.71495305 6, 9.5 C6, 6.46243388 8.46243388, 4 11.5, 4 C14.0673313, 4 16.2238156, 5.7590449 16.8299648, 8.1376465 C17.2052921, 8.04765874 17.5970804, 8 18, 8 C20.7614237, 8 23, 10.2385763 23, 13 C23, 15.7614237 20.7614237, 18 18, 18 L16, 18, L8, 18"
|
||||
strokeLinejoin="round"
|
||||
transform="scale(1, 1.2) translate(0, -2)"
|
||||
/>
|
||||
</g>
|
||||
),
|
||||
[ICONS.SUBSCRIPTION]: buildIcon(
|
||||
[ICONS.SUBSCRIBE]: buildIcon(
|
||||
<path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z" />
|
||||
),
|
||||
[ICONS.UNSUBSCRIBE]: buildIcon(
|
||||
<path d="M 12,5.67 10.94,4.61 C 5.7533356,-0.57666427 -2.0266644,7.2033357 3.16,12.39 l 1.06,1.06 7.78,7.78 7.78,-7.78 1.06,-1.06 c 2.149101,-2.148092 2.149101,-5.6319078 0,-7.78 -2.148092,-2.1491008 -5.631908,-2.1491008 -7.78,0 L 9.4481298,8.2303201 15.320603,9.2419066 11.772427,13.723825" />
|
||||
),
|
||||
[ICONS.SETTINGS]: buildIcon(
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
|
@ -103,12 +103,8 @@ export const icons = {
|
|||
[ICONS.OVERVIEW]: buildIcon(<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />),
|
||||
[ICONS.WALLET]: buildIcon(
|
||||
<g>
|
||||
<line x1="8" y1="6" x2="21" y2="6" />
|
||||
<line x1="8" y1="12" x2="21" y2="12" />
|
||||
<line x1="8" y1="18" x2="21" y2="18" />
|
||||
<line x1="3" y1="6" x2="3" y2="6" />
|
||||
<line x1="3" y1="12" x2="3" y2="12" />
|
||||
<line x1="3" y1="18" x2="3" y2="18" />
|
||||
<rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
|
||||
<line x1="1" y1="10" x2="23" y2="10" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.LIBRARY]: buildIcon(<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" />),
|
||||
|
@ -210,4 +206,38 @@ export const icons = {
|
|||
[ICONS.CHAT]: buildIcon(
|
||||
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z" />
|
||||
),
|
||||
[ICONS.YES]: buildIcon(
|
||||
<path d="M14 9V5a3 3 0 0 0-3-3l-4 9v11h11.28a2 2 0 0 0 2-1.7l1.38-9a2 2 0 0 0-2-2.3zM7 22H4a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2h3" />
|
||||
),
|
||||
[ICONS.NO]: buildIcon(
|
||||
<path d="M10 15v4a3 3 0 0 0 3 3l4-9V2H5.72a2 2 0 0 0-2 1.7l-1.38 9a2 2 0 0 0 2 2.3zm7-13h2.67A2.31 2.31 0 0 1 22 4v7a2.31 2.31 0 0 1-2.33 2H17" />
|
||||
),
|
||||
[ICONS.UP]: buildIcon(<polyline points="18 15 12 9 6 15" />),
|
||||
[ICONS.DOWN]: buildIcon(<polyline points="6 9 12 15 18 9" />),
|
||||
[ICONS.FULLSCREEN]: buildIcon(
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3" />
|
||||
),
|
||||
[ICONS.FILE]: buildIcon(
|
||||
<g>
|
||||
<path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z" />
|
||||
<polyline points="13 2 13 9 20 9" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.CHANNEL]: buildIcon(
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="4" />
|
||||
<path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.TWITTER]: buildIcon(
|
||||
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" />
|
||||
),
|
||||
[ICONS.FACEBOOK]: buildIcon(<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />),
|
||||
[ICONS.WEB]: buildIcon(
|
||||
<g>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<line x1="2" y1="12" x2="22" y2="12" />
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
||||
</g>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -26,7 +26,7 @@ class IconComponent extends React.PureComponent<Props> {
|
|||
return __('Featured content. Earn rewards for watching.');
|
||||
case ICONS.DOWNLOAD:
|
||||
return __('This file is downloaded.');
|
||||
case ICONS.SUBSCRIPTION:
|
||||
case ICONS.SUBSCRIBE:
|
||||
return __('You are subscribed to this channel.');
|
||||
case ICONS.SETTINGS:
|
||||
return __('Your settings.');
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import type { ElementRef } from 'react';
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
|
@ -17,7 +17,7 @@ type Props = {
|
|||
openModal: (id: string, { uri: string }) => void,
|
||||
claimIsMine: boolean,
|
||||
fileInfo: FileInfo,
|
||||
viewerContainer: ?{ current: Node },
|
||||
viewerContainer: { current: ElementRef<any> },
|
||||
showFullscreen: boolean,
|
||||
};
|
||||
|
||||
|
|
|
@ -42,7 +42,6 @@ class FileDetails extends PureComponent<Props> {
|
|||
<Expandable>
|
||||
{description && (
|
||||
<Fragment>
|
||||
<div className="media__info-title">About</div>
|
||||
<div className="media__info-text">
|
||||
<MarkdownPreview content={description} promptLinks />
|
||||
</div>
|
||||
|
|
|
@ -4,6 +4,7 @@ import * as React from 'react';
|
|||
import { parseURI } from 'lbry-redux';
|
||||
import Icon from 'component/common/icon';
|
||||
import FilePrice from 'component/filePrice';
|
||||
import VideoDuration from 'component/videoDuration';
|
||||
|
||||
type Props = {
|
||||
uri: string,
|
||||
|
@ -21,10 +22,11 @@ export default function FileProperties(props: Props) {
|
|||
|
||||
return (
|
||||
<div className="file-properties">
|
||||
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />}
|
||||
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIBE} />}
|
||||
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
|
||||
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
|
||||
<FilePrice hideFree uri={uri} />
|
||||
<VideoDuration className="media__subtitle" uri={uri} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
// @flow
|
||||
import { remote } from 'electron';
|
||||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import LoadingScreen from 'component/common/loading-screen';
|
||||
import VideoViewer from 'component/viewers/videoViewer';
|
||||
|
||||
// Audio player on hold until the current player is dropped
|
||||
// This component is half working
|
||||
// const AudioViewer = React.lazy<*>(() =>
|
||||
// import(
|
||||
// /* webpackChunkName: "audioViewer" */
|
||||
// 'component/viewers/audioViewer'
|
||||
// )
|
||||
// );
|
||||
// const AudioViewer = React.lazy<*>(() =>
|
||||
// import(/* webpackChunkName: "audioViewer" */
|
||||
// 'component/viewers/audioViewer')
|
||||
// );
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import type { ElementRef } from 'react';
|
||||
import '@babel/polyfill';
|
||||
import * as React from 'react';
|
||||
|
||||
|
@ -29,7 +30,7 @@ type Props = {
|
|||
onFinishCb: ?() => void,
|
||||
savePosition: number => void,
|
||||
changeVolume: number => void,
|
||||
viewerContainer: React.Ref,
|
||||
viewerContainer: { current: ElementRef<any> },
|
||||
searchBarFocused: boolean,
|
||||
};
|
||||
|
||||
|
@ -114,7 +115,9 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
|||
componentWillUnmount() {
|
||||
const mediaElement = this.mediaContainer.current.children[0];
|
||||
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
// Temorarily removing for comments the keydown handler needs to know
|
||||
// if a user is typing
|
||||
// document.removeEventListener('keydown', this.handleKeyDown);
|
||||
|
||||
if (mediaElement) {
|
||||
mediaElement.removeEventListener('click', this.togglePlay);
|
||||
|
@ -128,11 +131,11 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
|||
if (!searchBarFocused) {
|
||||
// Handle fullscreen shortcut key (f)
|
||||
if (event.keyCode === F_KEYCODE) {
|
||||
this.toggleFullscreen();
|
||||
// this.toggleFullscreen();
|
||||
}
|
||||
// Handle toggle play
|
||||
// @if TARGET='app'
|
||||
this.togglePlay(event);
|
||||
// this.togglePlay(event);
|
||||
// @endif
|
||||
}
|
||||
};
|
||||
|
@ -263,9 +266,6 @@ class MediaPlayer extends React.PureComponent<Props, State> {
|
|||
this.renderFile();
|
||||
}
|
||||
// @endif
|
||||
|
||||
// Fullscreen event for web and app
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
// @if TARGET='app'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
// @flow
|
||||
import type { ElementRef } from 'react';
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { Suspense } from 'react';
|
||||
import classnames from 'classnames';
|
||||
|
@ -52,7 +53,7 @@ type Props = {
|
|||
nsfw: boolean,
|
||||
thumbnail: ?string,
|
||||
isPlayableType: boolean,
|
||||
viewerContainer: React.Ref,
|
||||
viewerContainer: { current: ElementRef<any> },
|
||||
};
|
||||
|
||||
class FileViewer extends React.PureComponent<Props> {
|
||||
|
@ -126,7 +127,7 @@ class FileViewer extends React.PureComponent<Props> {
|
|||
}
|
||||
|
||||
this.props.cancelPlay();
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
// window.removeEventListener('keydown', this.handleKeyDown);
|
||||
}
|
||||
|
||||
handleKeyDown(event: SyntheticKeyboardEvent<*>) {
|
||||
|
|
|
@ -91,11 +91,12 @@ const Header = (props: Props) => {
|
|||
{__('Wallet')}
|
||||
</MenuItem>
|
||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
|
||||
<Icon aria-hidden icon={ICONS.UPLOAD} />
|
||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||
{__('Publish')}
|
||||
</MenuItem>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
||||
<Menu>
|
||||
<MenuButton className="header__navigation-item menu__title">
|
||||
<Icon size={18} icon={ICONS.SETTINGS} />
|
||||
|
|
|
@ -15,6 +15,7 @@ const select = state => ({
|
|||
channel: makeSelectPublishFormValue('channel')(state),
|
||||
bid: makeSelectPublishFormValue('bid')(state),
|
||||
uri: makeSelectPublishFormValue('uri')(state),
|
||||
bid: makeSelectPublishFormValue('bid')(state),
|
||||
isStillEditing: selectIsStillEditing(state),
|
||||
isResolvingUri: selectIsResolvingPublishUris(state),
|
||||
amountNeededForTakeover: selectTakeOverAmount(state),
|
||||
|
|
|
@ -10,7 +10,7 @@ type Props = {
|
|||
name: string,
|
||||
channel: string,
|
||||
uri: string,
|
||||
bid: string,
|
||||
bid: number,
|
||||
balance: number,
|
||||
isStillEditing: boolean,
|
||||
myClaimForUri: ?StreamClaim,
|
||||
|
@ -27,7 +27,7 @@ function PublishText(props: Props) {
|
|||
uri,
|
||||
isStillEditing,
|
||||
myClaimForUri,
|
||||
bid: bidString,
|
||||
bid,
|
||||
isResolvingUri,
|
||||
amountNeededForTakeover,
|
||||
prepareEdit,
|
||||
|
@ -37,7 +37,6 @@ function PublishText(props: Props) {
|
|||
const [nameError, setNameError] = useState(undefined);
|
||||
const [bidError, setBidError] = useState(undefined);
|
||||
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
|
||||
const bid = Number(bidString);
|
||||
|
||||
function editExistingClaim() {
|
||||
if (myClaimForUri) {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { doUpdatePublishForm } from 'redux/actions/publish';
|
||||
import { makeSelectPublishFormValue } from 'redux/selectors/publish';
|
||||
import PublishPage from './view';
|
||||
|
||||
|
@ -7,7 +8,11 @@ const select = state => ({
|
|||
fee: makeSelectPublishFormValue('fee')(state),
|
||||
});
|
||||
|
||||
const perform = dispatch => ({
|
||||
updatePublishForm: values => dispatch(doUpdatePublishForm(values)),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
perform
|
||||
)(PublishPage);
|
||||
|
|
|
@ -10,7 +10,8 @@ type Props = {
|
|||
function RewardTotal(props: Props) {
|
||||
const { rewards } = props;
|
||||
const rewardTotal = rewards.reduce((acc, val) => acc + val.reward_amount, 0);
|
||||
const total = useTween(rewardTotal * 25);
|
||||
const modifier = rewardTotal > 500 ? 1 : 15; // used to tweak the reward count speed
|
||||
const total = useTween(rewardTotal * modifier);
|
||||
const integer = Math.round(total * rewardTotal);
|
||||
|
||||
return (
|
||||
|
|
|
@ -16,7 +16,7 @@ import AuthPage from 'page/auth';
|
|||
import InvitePage from 'page/invite';
|
||||
import SubscriptionsPage from 'page/subscriptions';
|
||||
import SearchPage from 'page/search';
|
||||
import UserHistoryPage from 'page/userHistory';
|
||||
import LibraryPage from 'page/library';
|
||||
import WalletPage from 'page/wallet';
|
||||
import NavigationHistory from 'page/navigationHistory';
|
||||
import TagsPage from 'page/tags';
|
||||
|
@ -24,6 +24,7 @@ import FollowingPage from 'page/following';
|
|||
|
||||
const Scroll = withRouter(function ScrollWrapper(props) {
|
||||
const { pathname } = props.location;
|
||||
|
||||
useEffect(() => {
|
||||
// Auto scroll to the top of a window for new pages
|
||||
// The browser will handle scrolling if it needs to, but
|
||||
|
@ -51,12 +52,12 @@ export default function AppRouter() {
|
|||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
|
||||
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
|
||||
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} />
|
||||
<Route path={`/$/${PAGES.LIBRARY}`} exact component={LibraryPage} />
|
||||
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
|
||||
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
|
||||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={SubscriptionsPage} />
|
||||
<Route path={`/$/${PAGES.FOLLOWING}/edit`} exact component={FollowingPage} />
|
||||
<Route path={`/$/${PAGES.FOLLOWING}/customize`} exact component={FollowingPage} />
|
||||
<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} />
|
||||
|
|
|
@ -26,7 +26,7 @@ const SearchOptions = (props: Props) => {
|
|||
<div>
|
||||
<Button
|
||||
button="alt"
|
||||
label={__('FILTER')}
|
||||
label={__('Filter')}
|
||||
iconRight={expanded ? ICONS.UP : ICONS.DOWN}
|
||||
onClick={toggleSearchExpanded}
|
||||
/>
|
||||
|
|
|
@ -33,37 +33,24 @@ function SideBar(props: Props) {
|
|||
{
|
||||
...buildLink(null, __('Home'), ICONS.HOME),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.FOLLOWING, __('Following'), ICONS.SUBSCRIPTION),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
||||
},
|
||||
{
|
||||
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISHED),
|
||||
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
|
||||
},
|
||||
].map(renderLink)}
|
||||
|
||||
<li>
|
||||
<Button
|
||||
navigate="/$/following/edit"
|
||||
icon={ICONS.EDIT}
|
||||
className="navigation__link"
|
||||
activeClass="navigation__link--active"
|
||||
label={__('Edit')}
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
<ul className="navigation__links tags--vertical">
|
||||
{followedTags.map(({ name }, key) => (
|
||||
<li className="navigation__link--indented" key={name}>
|
||||
<li className="" key={name}>
|
||||
<Tag navigate={`/$/tags?t${name}`} name={name} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<ul className="navigation__links--small">
|
||||
{subscriptions.map(({ uri, channelName }, index) => (
|
||||
<li key={uri} className="navigation__link--indented">
|
||||
<li key={uri} className="">
|
||||
<Button
|
||||
navigate={uri}
|
||||
label={channelName}
|
||||
|
|
|
@ -51,7 +51,11 @@ class SnackBar extends React.PureComponent<Props> {
|
|||
<div>ⓘ</div>
|
||||
<div>{message}</div>
|
||||
</div>
|
||||
{linkText && linkTarget && <Button navigate={linkTarget} className="snack-bar__action" label={linkText} />}
|
||||
{linkText && linkTarget && (
|
||||
// This is a little weird because of `linkTarget` code in `lbry-redux`
|
||||
// Any navigation code should happen in the app, and that should be removed from lbry-redux
|
||||
<Button navigate={`/$${linkTarget}`} className="snack-bar__action" label={linkText} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,10 +3,9 @@ import * as ICONS from 'constants/icons';
|
|||
import React from 'react';
|
||||
import Button from 'component/button';
|
||||
import CopyableText from 'component/copyableText';
|
||||
import Tooltip from 'component/common/tooltip';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
claim: Claim,
|
||||
onDone: () => void,
|
||||
speechShareable: boolean,
|
||||
isChannel: boolean,
|
||||
|
@ -27,21 +26,15 @@ class SocialShare extends React.PureComponent<Props> {
|
|||
|
||||
render() {
|
||||
const { claim, isChannel } = this.props;
|
||||
const { claim_id: claimId, name: claimName, channel_name: channelName } = claim;
|
||||
const { claim_id: claimId, name: claimName } = claim;
|
||||
|
||||
const { speechShareable, onDone } = this.props;
|
||||
const channelClaimId = claim.signing_channel && claim.signing_channel.claim_id;
|
||||
const signingChannel = claim.signing_channel;
|
||||
const channelClaimId = signingChannel && signingChannel.claim_id;
|
||||
const channelName = signingChannel && signingChannel.name;
|
||||
|
||||
const getSpeechUri = (): string => {
|
||||
if (isChannel) {
|
||||
// For channel claims, the channel name (@something) is in `claim.name`
|
||||
return `${claimName}:${claimId}`;
|
||||
} else {
|
||||
// If it's for a regular claim, check if it has an associated channel
|
||||
return channelName && channelClaimId
|
||||
? `${channelName}:${channelClaimId}/${claimName}`
|
||||
: `${claimId}/${claimName}`;
|
||||
}
|
||||
const getLbryTvUri = (): string => {
|
||||
return `${claimName}/${claimId}`;
|
||||
};
|
||||
|
||||
const getLbryUri = (): string => {
|
||||
|
@ -56,69 +49,57 @@ class SocialShare extends React.PureComponent<Props> {
|
|||
}
|
||||
};
|
||||
|
||||
const speechPrefix = 'https://spee.ch/';
|
||||
const lbryTvPrefix = 'https://beta.lbry.tv/';
|
||||
const lbryPrefix = 'https://open.lbry.com/';
|
||||
const lbryUri = getLbryUri();
|
||||
const speechUri = getSpeechUri();
|
||||
const lbryTvUri = getLbryTvUri();
|
||||
const encodedLbryURL: string = `${lbryPrefix}${encodeURIComponent(lbryUri)}`;
|
||||
const lbryURL: string = `${lbryPrefix}${getLbryUri()}`;
|
||||
const encodedLbryTvUrl = `${lbryTvPrefix}${encodeURIComponent(lbryTvUri)}`;
|
||||
const lbryTvUrl = `${lbryTvPrefix}${lbryTvUri}`;
|
||||
|
||||
const encodedSpeechURL = `${speechPrefix}${encodeURIComponent(speechUri)}`;
|
||||
const speechURL = `${speechPrefix}${speechUri}`;
|
||||
const shareOnFb = __('Share on Facebook');
|
||||
const shareOnTwitter = __('Share On Twitter');
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{speechShareable && (
|
||||
<div className="card__content">
|
||||
<label className="help">{__('Web link')}</label>
|
||||
<CopyableText copyable={speechURL} />
|
||||
<label className="card__subtitle">{__('Web link')}</label>
|
||||
<CopyableText copyable={lbryTvUrl} />
|
||||
<div className="card__actions card__actions--center">
|
||||
<Tooltip label={__('Facebook')}>
|
||||
<Button
|
||||
iconColor="blue"
|
||||
icon={ICONS.FACEBOOK}
|
||||
button="alt"
|
||||
label={__('')}
|
||||
href={`https://facebook.com/sharer/sharer.php?u=${encodedSpeechURL}`}
|
||||
button="link"
|
||||
description={shareOnFb}
|
||||
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryTvUrl}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={__('Twitter')}>
|
||||
<Button
|
||||
iconColor="blue"
|
||||
icon={ICONS.TWITTER}
|
||||
button="alt"
|
||||
label={__('')}
|
||||
href={`https://twitter.com/home?status=${encodedSpeechURL}`}
|
||||
button="link"
|
||||
description={shareOnTwitter}
|
||||
href={`https://twitter.com/home?status=${encodedLbryTvUrl}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={__('View on Spee.ch')}>
|
||||
<Button icon={ICONS.WEB} iconColor="blue" button="alt" label={__('')} href={`${speechURL}`} />
|
||||
</Tooltip>
|
||||
<Button icon={ICONS.WEB} button="link" description={__('View on lbry.tv')} href={`${lbryTvUrl}`} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="card__content">
|
||||
<label className="help">{__('LBRY App link')}</label>
|
||||
<label className="card__subtitle">{__('LBRY App link')}</label>
|
||||
<CopyableText copyable={lbryURL} noSnackbar />
|
||||
<div className="card__actions card__actions--center">
|
||||
<Tooltip label={__('Facebook')}>
|
||||
<Button
|
||||
iconColor="blue"
|
||||
icon={ICONS.FACEBOOK}
|
||||
button="alt"
|
||||
label={__('')}
|
||||
button="link"
|
||||
description={shareOnFb}
|
||||
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Tooltip label={__('Twitter')}>
|
||||
<Button
|
||||
iconColor="blue"
|
||||
icon={ICONS.TWITTER}
|
||||
button="alt"
|
||||
label={__('')}
|
||||
button="link"
|
||||
description={shareOnTwitter}
|
||||
href={`https://twitter.com/home?status=${encodedLbryURL}`}
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="card__actions">
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// @flow
|
||||
import * as MODALS from 'constants/modal_types';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import React from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { parseURI } from 'lbry-redux';
|
||||
import Button from 'component/button';
|
||||
|
||||
|
@ -32,18 +32,36 @@ export default function SubscribeButton(props: Props) {
|
|||
showSnackBarOnSubscribe,
|
||||
doToast,
|
||||
} = props;
|
||||
|
||||
const buttonRef = useRef();
|
||||
const [isHovering, setIsHovering] = useState(false);
|
||||
const { claimName } = parseURI(uri);
|
||||
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
|
||||
const subscriptionLabel = isSubscribed ? __('Following') : __('Follow');
|
||||
const unfollowOverride = isSubscribed && isHovering && __('Unfollow');
|
||||
|
||||
const { claimName } = parseURI(uri);
|
||||
useEffect(() => {
|
||||
function handleHover() {
|
||||
setIsHovering(!isHovering);
|
||||
}
|
||||
|
||||
const button = buttonRef.current;
|
||||
if (button) {
|
||||
button.addEventListener('mouseover', handleHover);
|
||||
button.addEventListener('mouseleave', handleHover);
|
||||
return () => {
|
||||
button.removeEventListener('mouseover', handleHover);
|
||||
button.removeEventListener('mouseleave', handleHover);
|
||||
};
|
||||
}
|
||||
}, [buttonRef, isHovering]);
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={buttonRef}
|
||||
iconColor="red"
|
||||
icon={ICONS.SUBSCRIPTION}
|
||||
icon={unfollowOverride ? ICONS.UNSUBSCRIBE : ICONS.SUBSCRIBE}
|
||||
button={'alt'}
|
||||
label={subscriptionLabel}
|
||||
label={unfollowOverride || subscriptionLabel}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
|
||||
|
|
|
@ -15,11 +15,18 @@ export default function Tag(props: Props) {
|
|||
const { name, onClick, type = 'link', disabled = false } = props;
|
||||
|
||||
const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` };
|
||||
let title;
|
||||
if (!onClick) {
|
||||
title = __('View tag');
|
||||
} else {
|
||||
type === 'add' ? __('Add tag') : __('Remove tag');
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
{...clickProps}
|
||||
disabled={disabled}
|
||||
title={title}
|
||||
className={classnames('tag', {
|
||||
'tag--add': type === 'add',
|
||||
'tag--remove': type === 'remove',
|
||||
|
|
|
@ -19,7 +19,7 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function TagSelect(props: Props) {
|
||||
const { unfollowedTags, followedTags, doToggleTagFollow, doAddTag, onSelect } = props;
|
||||
const { unfollowedTags = [], followedTags = [], doToggleTagFollow, doAddTag, onSelect } = props;
|
||||
const [newTag, setNewTag] = useState('');
|
||||
|
||||
let tags = unfollowedTags.slice();
|
||||
|
@ -27,9 +27,14 @@ export default function TagSelect(props: Props) {
|
|||
tags.unshift({ name: newTag });
|
||||
}
|
||||
|
||||
const doesTagMatch = ({ name }) => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true);
|
||||
const suggestedTags = tags.filter(doesTagMatch).slice(0, 5);
|
||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag.name, unfollowedTagsAnimation);
|
||||
const doesTagMatch = name => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true);
|
||||
// Make sure there are no duplicates, then trim
|
||||
const suggestedTagsSet = new Set(tags.map(tag => tag.name));
|
||||
const suggestedTags = Array.from(suggestedTagsSet)
|
||||
.filter(doesTagMatch)
|
||||
.slice(0, 5);
|
||||
|
||||
const suggestedTransitions = useTransition(suggestedTags, tag => tag, unfollowedTagsAnimation);
|
||||
|
||||
function onChange(e) {
|
||||
setNewTag(e.target.value);
|
||||
|
@ -42,11 +47,11 @@ export default function TagSelect(props: Props) {
|
|||
if (onSelect) {
|
||||
onSelect({ name: newTag });
|
||||
} else {
|
||||
if (!unfollowedTags.includes(newTag)) {
|
||||
if (!unfollowedTags.map(({ name }) => name).includes(newTag)) {
|
||||
doAddTag(newTag);
|
||||
}
|
||||
|
||||
if (!followedTags.includes(newTag)) {
|
||||
if (!followedTags.map(({ name }) => name).includes(newTag)) {
|
||||
doToggleTagFollow(newTag);
|
||||
}
|
||||
}
|
||||
|
@ -54,7 +59,7 @@ export default function TagSelect(props: Props) {
|
|||
|
||||
function handleTagClick(tag) {
|
||||
if (onSelect) {
|
||||
onSelect(tag);
|
||||
onSelect({ name: tag });
|
||||
} else {
|
||||
doToggleTagFollow(tag);
|
||||
}
|
||||
|
@ -74,7 +79,7 @@ export default function TagSelect(props: Props) {
|
|||
<ul className="tags">
|
||||
{suggestedTransitions.map(({ item, key, props }) => (
|
||||
<animated.li key={key} style={props}>
|
||||
<Tag name={item.name} type="add" onClick={() => handleTagClick(item)} />
|
||||
<Tag name={item} type="add" onClick={() => handleTagClick(item)} />
|
||||
</animated.li>
|
||||
))}
|
||||
{!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>}
|
||||
|
|
|
@ -14,8 +14,10 @@ class TransactionListRecent extends React.PureComponent<Props> {
|
|||
componentDidMount() {
|
||||
const { fetchMyClaims, fetchTransactions } = this.props;
|
||||
|
||||
// @if TARGET='app'
|
||||
fetchMyClaims();
|
||||
fetchTransactions();
|
||||
// @endif
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import Button from 'component/button';
|
|||
import { FormField } from 'component/common/form';
|
||||
import UserEmailNew from 'component/userEmailNew';
|
||||
import UserEmailVerify from 'component/userEmailVerify';
|
||||
import cookie from 'cookie';
|
||||
|
||||
type Props = {
|
||||
cancelButton: React.Node,
|
||||
|
@ -22,6 +23,15 @@ function UserEmail(props: Props) {
|
|||
isVerified = user.has_verified_email;
|
||||
}
|
||||
|
||||
const buttonsProps = IS_WEB
|
||||
? {
|
||||
onClick: () => {
|
||||
document.cookie = cookie.serialize('auth_token', '');
|
||||
window.location.reload();
|
||||
},
|
||||
}
|
||||
: { href: 'https://lbry.com/faq/how-to-change-email' };
|
||||
|
||||
return (
|
||||
<section className="card card--section">
|
||||
{!email && <UserEmailNew />}
|
||||
|
@ -43,9 +53,7 @@ function UserEmail(props: Props) {
|
|||
readOnly
|
||||
label={__('Your Email')}
|
||||
value={email}
|
||||
inputButton={
|
||||
<Button button="inverse" label={__('Change')} href="https://lbry.com/faq/how-to-change-email" />
|
||||
}
|
||||
inputButton={<Button button="inverse" label={__('Change')} {...buttonsProps} />}
|
||||
/>
|
||||
)}
|
||||
<p className="help">
|
||||
|
|
12
src/ui/component/videoDuration/index.js
Normal file
12
src/ui/component/videoDuration/index.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri } from 'lbry-redux';
|
||||
import VideoDuration from './view';
|
||||
|
||||
const select = (state, props) => ({
|
||||
claim: makeSelectClaimForUri(props.uri)(state),
|
||||
});
|
||||
|
||||
export default connect(
|
||||
select,
|
||||
null
|
||||
)(VideoDuration);
|
30
src/ui/component/videoDuration/view.jsx
Normal file
30
src/ui/component/videoDuration/view.jsx
Normal file
|
@ -0,0 +1,30 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
type Props = {
|
||||
claim: ?StreamClaim,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
function VideoDuration(props: Props) {
|
||||
const { claim, className } = props;
|
||||
|
||||
const video = claim && claim.value && claim.value.video;
|
||||
let duration;
|
||||
if (video && video.duration) {
|
||||
// $FlowFixMe
|
||||
let date = new Date(null);
|
||||
date.setSeconds(video.duration);
|
||||
let timeString = date.toISOString().substr(11, 8);
|
||||
|
||||
if (timeString.startsWith('00:')) {
|
||||
timeString = timeString.substr(3);
|
||||
}
|
||||
|
||||
duration = timeString;
|
||||
}
|
||||
|
||||
return duration ? <span className={className}>{duration}</span> : null;
|
||||
}
|
||||
|
||||
export default VideoDuration;
|
|
@ -10,8 +10,7 @@ export const COPY = 'Clipboard';
|
|||
export const ARROW_LEFT = 'ChevronLeft';
|
||||
export const ARROW_RIGHT = 'ChevronRight';
|
||||
export const DOWNLOAD = 'Download';
|
||||
export const UPLOAD = 'UploadCloud';
|
||||
export const PUBLISHED = 'Cloud';
|
||||
export const PUBLISH = 'UploadCloud';
|
||||
export const REMOVE = 'X';
|
||||
export const ADD = 'Plus';
|
||||
export const EDIT = 'Edit';
|
||||
|
@ -29,7 +28,7 @@ export const WALLET = 'List';
|
|||
export const PHONE = 'Phone';
|
||||
export const COMPLETE = 'Check';
|
||||
export const COMPLETED = 'CheckCircle';
|
||||
export const SUBSCRIPTION = 'Heart';
|
||||
export const SUBSCRIBE = 'Heart';
|
||||
export const UNSUBSCRIBE = 'BrokenHeart';
|
||||
export const UNLOCK = 'Unlock';
|
||||
export const WEB = 'Globe';
|
||||
|
|
|
@ -15,9 +15,8 @@ export const SEND = 'send';
|
|||
export const SETTINGS = 'settings';
|
||||
export const SHOW = 'show';
|
||||
export const ACCOUNT = 'account';
|
||||
export const SUBSCRIPTIONS = 'subscriptions';
|
||||
export const FOLLOWING = 'following';
|
||||
export const SEARCH = 'search';
|
||||
export const TRANSACTIONS = 'transactions';
|
||||
export const TAGS = 'tags';
|
||||
export const WALLET = 'wallet';
|
||||
export const FOLLOWING = 'following';
|
||||
|
|
|
@ -9,7 +9,7 @@ import InvitePage from 'page/invite';
|
|||
|
||||
const WalletPage = () => (
|
||||
<Page>
|
||||
{IS_WEB && <UnsupportedOnWeb />}
|
||||
<UnsupportedOnWeb />
|
||||
<div className={classnames({ 'card--disabled': IS_WEB })}>
|
||||
<div className="columns">
|
||||
<UserEmail />
|
||||
|
|
|
@ -77,7 +77,7 @@ function ChannelPage(props: Props) {
|
|||
</div>
|
||||
</TabList>
|
||||
|
||||
<TabPanels className="channel__data">
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<ChannelContent uri={uri} />
|
||||
</TabPanel>
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectFollowedTags } from 'lbry-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import DiscoverPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
followedTags: selectFollowedTags(state),
|
||||
subscribedChannels: selectSubscriptions(state),
|
||||
});
|
||||
|
||||
const perform = {};
|
||||
|
|
|
@ -3,6 +3,7 @@ import React from 'react';
|
|||
import ClaimListDiscover from 'component/claimListDiscover';
|
||||
import TagsSelect from 'component/tagsSelect';
|
||||
import Page from 'component/page';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
followedTags: Array<Tag>,
|
||||
|
@ -10,12 +11,14 @@ type Props = {
|
|||
|
||||
function DiscoverPage(props: Props) {
|
||||
const { followedTags } = props;
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<ClaimListDiscover
|
||||
personal
|
||||
tags={followedTags.map(tag => tag.name)}
|
||||
injectedItem={<TagsSelect showClose title={__('Make This Your Own')} />}
|
||||
meta={<Button button="link" label={__('Customize')} navigate="/$/following/customize" />}
|
||||
injectedItem={<TagsSelect showClose title={__('Customize Your Homepage')} />}
|
||||
/>
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -22,6 +22,7 @@ import RecommendedContent from 'component/recommendedContent';
|
|||
import ClaimTags from 'component/claimTags';
|
||||
import CommentsList from 'component/commentsList';
|
||||
import CommentCreate from 'component/commentCreate';
|
||||
import VideoDuration from 'component/videoDuration';
|
||||
|
||||
type Props = {
|
||||
claim: StreamClaim,
|
||||
|
@ -222,22 +223,11 @@ class FilePage extends React.Component<Props> {
|
|||
|
||||
<div className="grid-area--info media__content media__content--large">
|
||||
<h1 className="media__title media__title--large">{title}</h1>
|
||||
|
||||
<div className="media__actions media__actions--between">
|
||||
<div className="media__subtext media__subtext--large">
|
||||
<div className="media__subtitle__channel">
|
||||
<UriIndicator uri={uri} link />
|
||||
</div>
|
||||
{__('Published on')} <DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
</div>
|
||||
|
||||
{claimIsMine && (
|
||||
<p>
|
||||
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="media__actions media__actions--between">
|
||||
<div className="media__action-group--large">
|
||||
{claimIsMine && (
|
||||
|
@ -282,10 +272,24 @@ class FilePage extends React.Component<Props> {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div className="media__actions media__actions--between">
|
||||
<div className="media__subtext media__subtext--large">
|
||||
<DateTime uri={uri} show={DateTime.SHOW_DATE} />
|
||||
</div>
|
||||
|
||||
<div className="media__subtext media__subtext--large">
|
||||
<VideoDuration uri={uri} />
|
||||
|
||||
{claimIsMine && (
|
||||
<p>
|
||||
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="media__info--large">
|
||||
<ClaimTags uri={uri} type="large" />
|
||||
</div>
|
||||
<div className="media__info--large">
|
||||
<FileDetails uri={uri} />
|
||||
|
||||
<div className="media__info-title">{__('Comments')}</div>
|
||||
|
|
|
@ -18,7 +18,13 @@ function FileListDownloaded(props: Props) {
|
|||
<React.Fragment>
|
||||
{hasDownloads ? (
|
||||
<div className="card">
|
||||
<ClaimList persistedStorageKey="claim-list-downloaded" uris={downloadedUris} loading={fetching} />
|
||||
<ClaimList
|
||||
header={<h1>{__('Your Library')}</h1>}
|
||||
defaultSort
|
||||
persistedStorageKey="claim-list-downloaded"
|
||||
uris={downloadedUris}
|
||||
loading={fetching}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
|
|
|
@ -21,7 +21,14 @@ function FileListPublished(props: Props) {
|
|||
<Page notContained>
|
||||
{uris && uris.length ? (
|
||||
<div className="card">
|
||||
<ClaimList loading={fetching} persistedStorageKey="claim-list-published" uris={uris} />
|
||||
<ClaimList
|
||||
header={<h1>{__('Your Publishes')}</h1>}
|
||||
loading={fetching}
|
||||
persistedStorageKey="claim-list-published"
|
||||
uris={uris}
|
||||
defaultSort
|
||||
headerAltControls={<Button button="link" label={__('New Publish')} navigate="/$/publish" />}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
|
|
|
@ -5,26 +5,22 @@ import TagsSelect from 'component/tagsSelect';
|
|||
import ClaimList from 'component/claimList';
|
||||
|
||||
type Props = {
|
||||
subscribedChannels: Array<{ uri: string }>,
|
||||
subscribedChannels: Array<Subscription>,
|
||||
};
|
||||
|
||||
function DiscoverPage(props: Props) {
|
||||
function FollowingEditPage(props: Props) {
|
||||
const { subscribedChannels } = props;
|
||||
|
||||
const channelUris = subscribedChannels.map(({ uri }) => uri);
|
||||
return (
|
||||
<Page>
|
||||
<div className="card">
|
||||
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} />
|
||||
<TagsSelect showClose={false} title={__('Customize Your Tags')} />
|
||||
</div>
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
header={<h1>{__('Channels You Are Following')}</h1>}
|
||||
empty={__("You aren't following any channels.")}
|
||||
uris={subscribedChannels.map(({ uri }) => uri)}
|
||||
/>
|
||||
<ClaimList header={<h1>{__('Channels You Follow')}</h1>} uris={channelUris} />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default DiscoverPage;
|
||||
export default FollowingEditPage;
|
||||
|
|
3
src/ui/page/library/index.js
Normal file
3
src/ui/page/library/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
import LibraryPage from './view';
|
||||
|
||||
export default LibraryPage;
|
14
src/ui/page/library/view.jsx
Normal file
14
src/ui/page/library/view.jsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Page from 'component/page';
|
||||
import DownloadList from 'page/fileListDownloaded';
|
||||
|
||||
function LibraryPage() {
|
||||
return (
|
||||
<Page>
|
||||
<DownloadList />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
export default LibraryPage;
|
|
@ -1,8 +1,9 @@
|
|||
// @flow
|
||||
import * as PAGES from 'constants/pages';
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import Page from 'component/page';
|
||||
import ClaimList from 'component/claimList';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import Button from 'component/button';
|
||||
|
||||
type Props = {
|
||||
|
@ -29,7 +30,7 @@ export default function SubscriptionsPage(props: Props) {
|
|||
doClaimSearch,
|
||||
uris,
|
||||
} = props;
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const hasSubscriptions = !!subscribedChannels.length;
|
||||
const { search } = location;
|
||||
const urlParams = new URLSearchParams(search);
|
||||
|
@ -53,28 +54,33 @@ export default function SubscriptionsPage(props: Props) {
|
|||
useEffect(() => {
|
||||
const ids = idString.split(',');
|
||||
const options = {
|
||||
page,
|
||||
channel_ids: ids,
|
||||
order_by: ['release_time'],
|
||||
};
|
||||
|
||||
doClaimSearch(20, options);
|
||||
}, [idString, doClaimSearch]);
|
||||
}, [idString, doClaimSearch, page]);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<div className="card">
|
||||
<ClaimList
|
||||
loading={loading}
|
||||
header={<h1>{viewingSuggestedSubs ? __('Discover New Channels') : __('Latest From Your Subscriptions')}</h1>}
|
||||
header={
|
||||
<h1>{viewingSuggestedSubs ? __('Discover New Channels') : __("Latest From Who You're Following")}</h1>
|
||||
}
|
||||
headerAltControls={
|
||||
<Button
|
||||
button="link"
|
||||
label={viewingSuggestedSubs ? hasSubscriptions && __('Following') : __('Find New Channels')}
|
||||
label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Feed') : __('Find New Channels')}
|
||||
onClick={() => onClick()}
|
||||
/>
|
||||
}
|
||||
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris}
|
||||
onScrollBottom={() => console.log('scroll bottom') || setPage(page + 1)}
|
||||
/>
|
||||
{loading && page > 1 && new Array(20).fill(1).map((x, i) => <ClaimPreview key={i} placeholder />)}
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -32,9 +32,9 @@ function TagsPage(props: Props) {
|
|||
tags={tags}
|
||||
meta={
|
||||
<Button
|
||||
button="alt"
|
||||
button="link"
|
||||
onClick={() => doToggleTagFollow(tag)}
|
||||
label={isFollowing ? __('Unfollow this tag') : __('Follow this tag')}
|
||||
label={isFollowing ? __('Following') : __('Follow')}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
import UserHistoryPage from './view';
|
||||
|
||||
export default UserHistoryPage;
|
|
@ -1,17 +0,0 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import Page from 'component/page';
|
||||
import DownloadList from 'page/fileListDownloaded';
|
||||
|
||||
type Props = {};
|
||||
|
||||
class UserHistoryPage extends React.PureComponent<Props> {
|
||||
render() {
|
||||
return (
|
||||
<Page>
|
||||
<DownloadList {...this.props} />
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
}
|
||||
export default UserHistoryPage;
|
|
@ -4,13 +4,17 @@ import WalletSend from 'component/walletSend';
|
|||
import WalletAddress from 'component/walletAddress';
|
||||
import TransactionListRecent from 'component/transactionListRecent';
|
||||
import Page from 'component/page';
|
||||
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
|
||||
|
||||
const WalletPage = () => (
|
||||
<Page>
|
||||
<UnsupportedOnWeb />
|
||||
<div className={IS_WEB && 'card--disabled'}>
|
||||
<WalletBalance />
|
||||
<TransactionListRecent />
|
||||
<WalletSend />
|
||||
<WalletAddress />
|
||||
</div>
|
||||
</Page>
|
||||
);
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
import { doAuthenticate } from 'lbryinc';
|
||||
import { lbrySettings as config, version as appVersion } from 'package.json';
|
||||
import { push } from 'connected-react-router';
|
||||
import { whiteListedReducers } from 'store';
|
||||
|
||||
// @if TARGET='app'
|
||||
const { autoUpdater } = remote.require('electron-updater');
|
||||
|
@ -327,7 +328,8 @@ export function doDaemonReady() {
|
|||
|
||||
export function doClearCache() {
|
||||
return () => {
|
||||
window.cacheStore.purge();
|
||||
const reducersToClear = whiteListedReducers.filter(reducerKey => reducerKey !== 'tags');
|
||||
window.cacheStore.purge(reducersToClear);
|
||||
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
|
|
@ -257,6 +257,7 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
|
|||
tags: Array<string>,
|
||||
locations?: Array<Location>,
|
||||
license_url?: string,
|
||||
license?: string,
|
||||
thumbnail_url?: string,
|
||||
release_time?: number,
|
||||
fee_currency?: string,
|
||||
|
@ -267,13 +268,19 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
|
|||
description,
|
||||
locations,
|
||||
bid: creditsToString(bid),
|
||||
license: publishingLicense,
|
||||
languages: [language],
|
||||
tags: tags && tags.map(tag => tag.name),
|
||||
license_url: licenseType === COPYRIGHT ? '' : licenseUrl,
|
||||
thumbnail_url: thumbnail,
|
||||
};
|
||||
|
||||
if (publishingLicense) {
|
||||
publishPayload.license = publishingLicense;
|
||||
}
|
||||
|
||||
if (licenseUrl) {
|
||||
publishPayload.license_url = licenseUrl;
|
||||
}
|
||||
|
||||
if (myClaimForUri && myClaimForUri.value.release_time) {
|
||||
publishPayload.release_time = Number(myClaimForUri.value.release_time);
|
||||
}
|
||||
|
|
|
@ -13,13 +13,13 @@
|
|||
@import 'component/button';
|
||||
@import 'component/card';
|
||||
@import 'component/channel';
|
||||
@import 'component/claim-list';
|
||||
@import 'component/comments';
|
||||
@import 'component/content';
|
||||
@import 'component/credit';
|
||||
@import 'component/dat-gui';
|
||||
@import 'component/expandable';
|
||||
@import 'component/file-download';
|
||||
@import 'component/file-list';
|
||||
@import 'component/file-properties';
|
||||
@import 'component/file-render';
|
||||
@import 'component/form-field';
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
}
|
||||
}
|
||||
|
||||
// Fix this in lbry/components
|
||||
.button--primary:not(:hover) {
|
||||
background-color: $lbry-teal-4;
|
||||
.button--primary {
|
||||
&:hover {
|
||||
background-color: $lbry-teal-3;
|
||||
}
|
||||
|
||||
svg {
|
||||
color: white;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ $metadata-z-index: 1;
|
|||
align-self: flex-start;
|
||||
position: absolute;
|
||||
object-fit: cover;
|
||||
filter: brightness(60%);
|
||||
filter: brightness(50%);
|
||||
}
|
||||
|
||||
.channel-cover,
|
||||
|
@ -27,8 +27,8 @@ $metadata-z-index: 1;
|
|||
|
||||
.channel-thumbnail {
|
||||
display: flex;
|
||||
height: 5.3rem;
|
||||
width: 5.4rem;
|
||||
height: 5rem;
|
||||
width: 6rem;
|
||||
background-size: cover;
|
||||
margin-right: var(--spacing-medium);
|
||||
}
|
||||
|
@ -52,7 +52,6 @@ $metadata-z-index: 1;
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
align-self: flex-end;
|
||||
// margin-bottom: -1px;
|
||||
}
|
||||
|
||||
.channel-thumbnail,
|
||||
|
@ -94,8 +93,3 @@ $metadata-z-index: 1;
|
|||
margin-top: -0.25rem;
|
||||
color: rgba($lbry-white, 0.75);
|
||||
}
|
||||
|
||||
// TODO: rename
|
||||
.channel__data {
|
||||
min-height: 10rem;
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
// Normal link buttons are too dark on the black file list background
|
||||
.button--link {
|
||||
color: $lbry-teal-3;
|
||||
font-size: 1.2em;
|
||||
|
||||
&:hover {
|
||||
color: $lbry-teal-1;
|
||||
|
@ -43,6 +44,7 @@
|
|||
background-size: 1.2rem;
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 96 96' xmlns='http://www.w3.org/2000/svg' fill='%23ffffff'%3E%3Cpath d='M17.172, 31.172c1.562, -1.562 4.095, -1.562 5.656, 0l25.172, 25.171l25.172, -25.171c1.562, -1.562 4.095, -1.562 5.656, 0c1.562, 1.562 1.562, 4.095 0, 5.656l-28, 28c-1.562, 1.562 -4.095, 1.562 -5.656, 0l-28, -28c-0.781, -0.781 -1.172, -1.805 -1.172, -2.828c0, -1.023 0.391, -2.047 1.172, -2.828Z'/%3E%3C/svg%3E%0A");
|
||||
height: 2.5rem;
|
||||
font-size: 1.3rem;
|
||||
padding: 0 var(--spacing-medium);
|
||||
padding-right: var(--spacing-large);
|
||||
margin-bottom: 0;
|
||||
|
@ -60,28 +62,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__header-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.claim-list__header-text,
|
||||
.claim-list__dropdown {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.claim-list__alt-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
font-size: 1.4em;
|
||||
font-size: 1.1em;
|
||||
|
||||
& > * {
|
||||
margin-left: var(--spacing-small);
|
||||
}
|
||||
}
|
||||
|
||||
.claim-list__item {
|
||||
.claim-preview {
|
||||
display: flex;
|
||||
position: relative;
|
||||
font-size: 1.3rem;
|
||||
|
@ -104,12 +96,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__item--injected,
|
||||
.claim-list__item + .claim-list__item {
|
||||
border-top: 1px solid rgba($lbry-teal-5, 0.1);
|
||||
.claim-preview--injected,
|
||||
.claim-preview {
|
||||
border-bottom: 1px solid rgba($lbry-teal-5, 0.1);
|
||||
}
|
||||
|
||||
.claim-list__item--large {
|
||||
.claim-preview--large {
|
||||
@include mediaThumbHoverZoom;
|
||||
font-size: 1.6rem;
|
||||
border-bottom: 0;
|
||||
|
@ -138,36 +130,32 @@
|
|||
}
|
||||
}
|
||||
|
||||
.claim-list__item-metadata {
|
||||
.claim-preview-metadata {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.claim-list__item-info {
|
||||
.claim-preview-info {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.claim-list__item-info,
|
||||
.claim-list__item-properties {
|
||||
.claim-preview-info,
|
||||
.claim-preview-properties {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.claim-list__item-properties {
|
||||
.claim-preview-properties {
|
||||
align-items: flex-end;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.claim-list__item-title {
|
||||
.claim-preview-title {
|
||||
font-weight: 600;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.claim-list__item-tags {
|
||||
.claim-preview-tags {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.claim-list__meta {
|
||||
padding: var(--spacing-medium);
|
||||
background-color: lighten($lbry-teal-5, 55%);
|
||||
}
|
|
@ -3,6 +3,14 @@
|
|||
position: relative;
|
||||
align-items: center;
|
||||
|
||||
.icon {
|
||||
stroke: rgba($lbry-black, 0.5);
|
||||
|
||||
html[data-mode='dark'] & {
|
||||
stroke: rgba($lbry-white, 0.7);
|
||||
}
|
||||
}
|
||||
|
||||
& > *:not(:last-child) {
|
||||
margin-right: var(--spacing-small);
|
||||
}
|
||||
|
@ -15,7 +23,7 @@
|
|||
.file-properties--large {
|
||||
flex-wrap: wrap;
|
||||
font-size: 18px;
|
||||
margin: var(--spacing-small) 0;
|
||||
margin: var(--spacing-medium) 0;
|
||||
|
||||
& > * {
|
||||
margin-top: var(--spacing-small);
|
||||
|
|
|
@ -1,25 +1,5 @@
|
|||
@import '~@lbry/components/sass/form/_index.scss';
|
||||
|
||||
// replace this
|
||||
form {
|
||||
// setting the font size here sizes everything within
|
||||
&:not(:last-child) {
|
||||
margin-bottom: var(--spacing-s);
|
||||
}
|
||||
}
|
||||
|
||||
input,
|
||||
select {
|
||||
height: var(--spacing-l);
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
checkbox-element,
|
||||
radio-element,
|
||||
select {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
textarea {
|
||||
&::placeholder {
|
||||
opacity: 0.4;
|
||||
|
|
|
@ -8,12 +8,17 @@
|
|||
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;
|
||||
}
|
||||
|
||||
& > * {
|
||||
user-select: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header__contents {
|
||||
|
@ -49,10 +54,6 @@
|
|||
align-items: center;
|
||||
border-radius: 0;
|
||||
|
||||
svg {
|
||||
stroke: $lbry-gray-5;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: $lbry-teal-5;
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
padding-left: var(--spacing-large);
|
||||
padding-right: var(--spacing-large);
|
||||
padding-bottom: var(--spacing-large);
|
||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||
display: flex;
|
||||
|
||||
[data-mode='dark'] & {
|
||||
|
|
|
@ -132,10 +132,6 @@
|
|||
color: rgba($lbry-black, 0.8);
|
||||
font-size: 0.9em;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-bottom: var(--spacing-medium);
|
||||
}
|
||||
|
||||
html[data-mode='dark'] & {
|
||||
color: rgba($lbry-white, 0.7);
|
||||
}
|
||||
|
@ -149,8 +145,9 @@
|
|||
// S U B T I T L E
|
||||
|
||||
.media__subtitle {
|
||||
align-self: flex-start;
|
||||
font-size: 0.8em;
|
||||
color: rgba($lbry-black, 0.8);
|
||||
color: rgba($lbry-black, 0.6);
|
||||
|
||||
[data-mode='dark'] & {
|
||||
color: rgba($lbry-white, 0.8);
|
||||
|
@ -167,6 +164,7 @@
|
|||
|
||||
.media__subtitle__channel {
|
||||
font-weight: 600;
|
||||
margin: var(--spacing-small) 0;
|
||||
}
|
||||
|
||||
// M E D I A
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
.navigation {
|
||||
width: var(--side-nav-width);
|
||||
padding-bottom: var(--spacing-main-padding);
|
||||
font-size: 1.4rem;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
.placeholder {
|
||||
display: flex;
|
||||
|
||||
&.claim-list__item-title {
|
||||
&.claim-preview-title {
|
||||
width: 100%;
|
||||
height: 3rem;
|
||||
}
|
||||
|
@ -18,6 +18,6 @@
|
|||
margin-top: var(--spacing-small);
|
||||
|
||||
width: 30%;
|
||||
height: 2em;
|
||||
height: 1.5em;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,10 @@
|
|||
.search__options {
|
||||
margin-top: var(--spacing-large);
|
||||
|
||||
.button {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
legend {
|
||||
&.search__legend--1 {
|
||||
background-color: $lbry-teal-4;
|
||||
|
|
|
@ -21,6 +21,7 @@ $main: $lbry-teal-5;
|
|||
@extend .tags;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
margin-top: var(--spacing-medium);
|
||||
}
|
||||
|
||||
.tags--selected {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
|
||||
[data-reach-menu-item] {
|
||||
display: block;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
[data-reach-menu-item] {
|
||||
|
@ -90,7 +91,6 @@
|
|||
|
||||
.icon {
|
||||
margin-right: var(--spacing-small);
|
||||
margin-bottom: 0.2rem;
|
||||
stroke: $lbry-gray-5;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,9 @@
|
|||
// Generic html styles used accross the App
|
||||
// component specific styling should go in the component scss file
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-webkit-user-select: text;
|
||||
}
|
||||
|
||||
html {
|
||||
@include font-sans;
|
||||
background-color: $lbry-white;
|
||||
|
||||
font-size: 12px;
|
||||
height: 100%;
|
||||
overflow-x: hidden;
|
||||
|
@ -23,6 +17,7 @@ body {
|
|||
height: 100%;
|
||||
line-height: 1.5;
|
||||
overflow: hidden;
|
||||
background-color: mix($lbry-white, $lbry-gray-1, 70%);
|
||||
|
||||
html[data-mode='dark'] & {
|
||||
background-color: $lbry-black;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
@mixin placeholder {
|
||||
animation: pulse 2s infinite ease-in-out;
|
||||
background-color: $lbry-gray-2;
|
||||
background-color: $lbry-gray-1;
|
||||
border-radius: var(--card-radius);
|
||||
}
|
||||
|
||||
|
@ -15,3 +15,7 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin focus {
|
||||
box-shadow: 0 0 0 2px $lbry-blue-1;
|
||||
}
|
||||
|
|
|
@ -20,10 +20,7 @@ html {
|
|||
user-select: none;
|
||||
}
|
||||
|
||||
a,
|
||||
area,
|
||||
button,
|
||||
[role='button'],
|
||||
input,
|
||||
label,
|
||||
select,
|
||||
|
|
|
@ -74,9 +74,7 @@ const appFilter = createFilter('app', ['hasClickedComment', 'searchOptionsExpand
|
|||
// We only need to persist the receiveAddress for the wallet
|
||||
const walletFilter = createFilter('wallet', ['receiveAddress']);
|
||||
const searchFilter = createFilter('search', ['options']);
|
||||
|
||||
const persistOptions = {
|
||||
whitelist: [
|
||||
const whiteListedReducers = [
|
||||
// @if TARGET='app'
|
||||
'publish',
|
||||
'wallet',
|
||||
|
@ -87,7 +85,9 @@ const persistOptions = {
|
|||
'app',
|
||||
'search',
|
||||
'tags',
|
||||
],
|
||||
];
|
||||
const persistOptions = {
|
||||
whitelist: whiteListedReducers,
|
||||
// Order is important. Needs to be compressed last or other transforms can't
|
||||
// read the data
|
||||
transforms: [
|
||||
|
@ -110,4 +110,4 @@ window.cacheStore = persistStore(store, persistOptions, err => {
|
|||
}
|
||||
});
|
||||
|
||||
export { store, history };
|
||||
export { store, history, whiteListedReducers };
|
||||
|
|
|
@ -9,6 +9,8 @@ export default function usePersistedState(key, firstTimeDefault) {
|
|||
defaultValue = true;
|
||||
} else if (item === 'false') {
|
||||
defaultValue = false;
|
||||
} else {
|
||||
defaultValue = item;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<meta property="og:description" content="All your favorite LBRY content in your browser." />
|
||||
<meta property="og:image" content="/og.png" />
|
||||
<!-- @endif -->
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<!-- <meta name="viewport" content="width=device-width, initial-scale=1" /> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
|
|
@ -292,7 +292,6 @@
|
|||
"Comment": "Comment",
|
||||
"Your comment": "Your comment",
|
||||
"Post": "Post",
|
||||
"Channel": "Channel",
|
||||
"No modifier provided after separator %s.": "No modifier provided after separator %s.",
|
||||
"Incompatible Daemon": "Incompatible Daemon",
|
||||
"Incompatible daemon running": "Incompatible daemon running",
|
||||
|
@ -309,39 +308,109 @@
|
|||
"Your version is out of date and may be unreliable or insecure.": "Your version is out of date and may be unreliable or insecure.",
|
||||
"Want to know what has changed?": "Want to know what has changed?",
|
||||
"release notes": "release notes",
|
||||
"Remove from your library": "Remove from your library",
|
||||
"Got it!": "Got it!",
|
||||
"The better your tags are, the easier it will be for people to discover your content.": "The better your tags are, the easier it will be for people to discover your content.",
|
||||
"No tags added": "No tags added",
|
||||
"Read the FAQ": "Read the FAQ",
|
||||
"Our FAQ answers many common questions.": "Our FAQ answers many common questions.",
|
||||
"Get Live Help": "Get Live Help",
|
||||
"Live help is available most hours in the": "Live help is available most hours in the",
|
||||
"channel of our Discord chat room.": "channel of our Discord chat room.",
|
||||
"Join Our Chat": "Join Our Chat",
|
||||
"Report a Bug or Suggest a New Feature": "Report a Bug or Suggest a New Feature",
|
||||
"Did you find something wrong? Think LBRY could add something useful and cool?": "Did you find something wrong? Think LBRY could add something useful and cool?",
|
||||
"Submit a Bug Report/Feature Request": "Submit a Bug Report/Feature Request",
|
||||
"Thanks! LBRY is made by its users.": "Thanks! LBRY is made by its users.",
|
||||
"View your Log": "View your Log",
|
||||
"Did something go wrong? Have a look in your log file, or send it to": "Did something go wrong? Have a look in your log file, or send it to",
|
||||
"support": "support",
|
||||
"Open Log": "Open Log",
|
||||
"Open Log Folder": "Open Log Folder",
|
||||
"Your LBRY app is up to date.": "Your LBRY app is up to date.",
|
||||
"App": "App",
|
||||
"Daemon (lbrynet)": "Daemon (lbrynet)",
|
||||
"Loading...": "Loading...",
|
||||
"Connected Email": "Connected Email",
|
||||
"Update mailing preferences": "Update mailing preferences",
|
||||
"Reward Eligible": "Reward Eligible",
|
||||
"Platform": "Platform",
|
||||
"Installation ID": "Installation ID",
|
||||
"Access Token": "Access Token",
|
||||
"Backup Your LBRY Credits": "Backup Your LBRY Credits",
|
||||
"Your LBRY credits are controllable by you and only you, via wallet file(s) stored locally on your computer.": "Your LBRY credits are controllable by you and only you, via wallet file(s) stored locally on your computer.",
|
||||
"Currently, there is no automatic wallet backup. If you lose access to these files, you will lose your credits permanently.": "Currently, there is no automatic wallet backup. If you lose access to these files, you will lose your credits permanently.",
|
||||
"However, it is fairly easy to back up manually. To backup your wallet, make a copy of the folder listed below:": "However, it is fairly easy to back up manually. To backup your wallet, make a copy of the folder listed below:",
|
||||
"Access to these files are equivalent to having access to your credits. Keep any copies you make of your wallet in a secure place.": "Access to these files are equivalent to having access to your credits. Keep any copies you make of your wallet in a secure place.",
|
||||
"see this article": "see this article",
|
||||
"A newer version of LBRY is available.": "A newer version of LBRY is available.",
|
||||
"Download now!": "Download now!",
|
||||
"none": "none",
|
||||
"set email": "set email",
|
||||
"Failed to load settings.": "Failed to load settings.",
|
||||
"Transaction History": "Transaction History",
|
||||
"Export": "Export",
|
||||
"Export Transactions": "Export Transactions",
|
||||
"lbry-transactions-history": "lbry-transactions-history",
|
||||
"Show": "Show",
|
||||
"All": "All",
|
||||
"Spend": "Spend",
|
||||
"Receive": "Receive",
|
||||
"Channel": "Channel",
|
||||
"Tip": "Tip",
|
||||
"Support": "Support",
|
||||
"Update": "Update",
|
||||
"Abandon": "Abandon",
|
||||
"Unlock Tip": "Unlock Tip",
|
||||
"Only channel URIs may have a path.": "Only channel URIs may have a path.",
|
||||
"Confirm Purchase": "Confirm Purchase",
|
||||
"This will purchase": "This will purchase",
|
||||
"for": "for",
|
||||
"credits": "credits",
|
||||
"No channel name after @.": "No channel name after @.",
|
||||
"View channel": "View channel",
|
||||
"Add to your library": "Add to your library",
|
||||
"Web link": "Web link",
|
||||
"Facebook": "Facebook",
|
||||
"": "",
|
||||
"Twitter": "Twitter",
|
||||
"View on Spee.ch": "View on Spee.ch",
|
||||
"LBRY App link": "LBRY App link",
|
||||
"Done": "Done",
|
||||
"You can't publish things quite yet": "You can't publish things quite yet",
|
||||
"LBRY uses a blockchain, which is a fancy way of saying that users (you) are in control of your data.": "LBRY uses a blockchain, which is a fancy way of saying that users (you) are in control of your data.",
|
||||
"Because of the blockchain, some actions require LBRY credits": "Because of the blockchain, some actions require LBRY credits",
|
||||
"allows you to do some neat things, like paying your favorite creators for their content. And no company can stop you.": "allows you to do some neat things, like paying your favorite creators for their content. And no company can stop you.",
|
||||
"LBRY Credits Required": "LBRY Credits Required",
|
||||
" There are a variety of ways to get credits, including more than": " There are a variety of ways to get credits, including more than",
|
||||
"in free rewards for participating in the LBRY beta.": "in free rewards for participating in the LBRY beta.",
|
||||
"Checkout the rewards": "Checkout the rewards",
|
||||
"Choose a File": "Choose a File",
|
||||
"Choose A File": "Choose A File",
|
||||
"Choose a file": "Choose a file",
|
||||
"Choose Tags": "Choose Tags",
|
||||
"The better the tags, the better people will find your content.": "The better the tags, the better people will find your content.",
|
||||
"Clear": "Clear",
|
||||
"A title is required": "A title is required",
|
||||
"Checking the winning claim amount...": "Checking the winning claim amount...",
|
||||
"The better the tags, the easier your content is to find.": "The better the tags, the easier your content is to find.",
|
||||
"You aren't following any tags, try searching for one.": "You aren't following any tags, try searching for one.",
|
||||
"Publishing...": "Publishing...",
|
||||
"Success": "Success",
|
||||
"File published": "File published",
|
||||
"Your file has been published to LBRY at the address": "Your file has been published to LBRY at the address",
|
||||
"The file will take a few minutes to appear for other LBRY users. Until then it will be listed as \"pending\" under your published files.": "The file will take a few minutes to appear for other LBRY users. Until then it will be listed as \"pending\" under your published files.",
|
||||
"You are currently editing a claim.": "You are currently editing a claim.",
|
||||
"If you don't choose a file, the file from your existing claim": "If you don't choose a file, the file from your existing claim",
|
||||
"will be used.": "will be used.",
|
||||
"My description for this and that": "My description for this and that",
|
||||
"Upload your thumbnail to": "Upload your thumbnail to",
|
||||
"You are currently editing this claim. If you change the URL, you will need to reselect a file.": "You are currently editing this claim. If you change the URL, you will need to reselect a file.",
|
||||
"Add a price to this file": "Add a price to this file",
|
||||
"Additional Options": "Additional Options",
|
||||
"Checking the winning claim amount...": "Checking the winning claim amount...",
|
||||
"If you bid more than": "If you bid more than",
|
||||
"when someone navigates to": "when someone navigates to",
|
||||
"it will load your published content": "it will load your published content",
|
||||
"However, you can get a longer version of this URL for any bid": "However, you can get a longer version of this URL for any bid",
|
||||
"Choose a thumbnail": "Choose a thumbnail",
|
||||
"URI does not include name.": "URI does not include name.",
|
||||
"Editing...": "Editing...",
|
||||
"Success": "Success",
|
||||
"The updates will take a few minutes to appear for other LBRY users. Until then it will be listed as \"pending\" under your published files.": "The updates will take a few minutes to appear for other LBRY users. Until then it will be listed as \"pending\" under your published files.",
|
||||
"Choose a file": "Choose a file",
|
||||
"A name is required": "A name is required",
|
||||
"This file is downloaded.": "This file is downloaded.",
|
||||
"It looks like you haven't published anything to LBRY yet.": "It looks like you haven't published anything to LBRY yet.",
|
||||
"Publish something new": "Publish something new",
|
||||
"View it on spee.ch": "View it on spee.ch",
|
||||
"New thumbnail": "New thumbnail",
|
||||
"Add to your library": "Add to your library",
|
||||
"Follow": "Follow",
|
||||
"Claim sequence must be a number.": "Claim sequence must be a number.",
|
||||
"A title is required": "A title is required",
|
||||
"Clearing": "Clearing",
|
||||
"A deposit amount is required": "A deposit amount is required",
|
||||
"Deposit cannot be 0": "Deposit cannot be 0",
|
||||
|
@ -358,5 +427,34 @@
|
|||
"Error message": "Error message",
|
||||
"Error data": "Error data",
|
||||
"Error": "Error",
|
||||
"We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.": "We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem."
|
||||
"We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.": "We're sorry that LBRY has encountered an error. This has been reported and we will investigate the problem.",
|
||||
"Customize": "Customize",
|
||||
"Customize Your Homepage": "Customize Your Homepage",
|
||||
"Tags You Follow": "Tags You Follow",
|
||||
"Channels You Follow": "Channels You Follow",
|
||||
"Everyone": "Everyone",
|
||||
"This file is downloaded.": "This file is downloaded.",
|
||||
"Featured content. Earn rewards for watching.": "Featured content. Earn rewards for watching.",
|
||||
"You are subscribed to this channel.": "You are subscribed to this channel.",
|
||||
"Remove from your library": "Remove from your library",
|
||||
"View tag": "View tag",
|
||||
"Customize Your Tags": "Customize Your Tags",
|
||||
"Remove tag": "Remove tag",
|
||||
"Add tag": "Add tag",
|
||||
"The better your tags are, the easier it will be for people to discover your content.": "The better your tags are, the easier it will be for people to discover your content.",
|
||||
"No tags added": "No tags added",
|
||||
"My description for this and that": "My description for this and that",
|
||||
"Choose a thumbnail": "Choose a thumbnail",
|
||||
"Take a snapshot from your video": "Take a snapshot from your video",
|
||||
"Upload your thumbnail to": "Upload your thumbnail to",
|
||||
"Add a price to this file": "Add a price to this file",
|
||||
"Additional Options": "Additional Options",
|
||||
"A URL is required": "A URL is required",
|
||||
"A name is required": "A name is required",
|
||||
"The updates will take a few minutes to appear for other LBRY users. Until then it will be listed as \"pending\" under your published files.": "The updates will take a few minutes to appear for other LBRY users. Until then it will be listed as \"pending\" under your published files.",
|
||||
"Your Publishes": "Your Publishes",
|
||||
"New Publish": "New Publish",
|
||||
"Your Library": "Your Library",
|
||||
"This will appear as a tip for \"Original LBRY porn - Nude Hot Girl masturbates FREE\".": "This will appear as a tip for \"Original LBRY porn - Nude Hot Girl masturbates FREE\".",
|
||||
"You sent 25 LBC as a tip, Mahalo!": "You sent 25 LBC as a tip, Mahalo!"
|
||||
}
|
12
yarn.lock
12
yarn.lock
|
@ -862,10 +862,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@lbry/color/-/color-1.1.1.tgz#b80ec25fb515d436129332b20c767c5a7014ac09"
|
||||
integrity sha512-BdxqWmm84WYOd1ZsPruJiGr7WBEophVfJKg7oywFOAMb0h6ERe4Idx1ceweMSvCOyPlR5GAhil9Gvk70SBFdxQ==
|
||||
|
||||
"@lbry/components@^2.7.2":
|
||||
version "2.7.2"
|
||||
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.2.tgz#a941ced2adf78ab52026f5533e5f6b3454f862a9"
|
||||
integrity sha512-TSBmxc4i2E3v7qGC7LgdcrUUcpiUYBA1KMVSW5vrpDJroNy3fnr3/niGJTudVQ4LQzlOXpt7bsRATFh8vRmXnQ==
|
||||
"@lbry/components@^2.7.4":
|
||||
version "2.7.4"
|
||||
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.4.tgz#3a633725137ffcb1f081f71dbf5397d66bf444cd"
|
||||
integrity sha512-X0QBpdlsPntmuC/COolx/rOmUxa5XeHDjdcpl79Db3Y3upkEYwwtvl+S/OkkPbdxouoJfZTB4QqQuGkNi1xGdw==
|
||||
|
||||
"@mapbox/hast-util-table-cell-style@^0.1.3":
|
||||
version "0.1.3"
|
||||
|
@ -6641,9 +6641,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#b3bf3f6d53410ff1c5415b51ca425341e364959f:
|
||||
lbry-redux@lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66:
|
||||
version "0.0.1"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/b3bf3f6d53410ff1c5415b51ca425341e364959f"
|
||||
resolved "https://codeload.github.com/lbryio/lbry-redux/tar.gz/2930ad82a90ca91f6caf3761597ef9a67da7db66"
|
||||
dependencies:
|
||||
proxy-polyfill "0.1.6"
|
||||
reselect "^3.0.0"
|
||||
|
|
Loading…
Reference in a new issue
@kauffj does this seem like a reasonable way to do this or is there something simpler?
It is to check if a full page was loaded, if 15 items are loaded don't fetch more, because page 2 wont' exist. If that seems fine I can create a prop called
claimListSearchCount
or something and add a constant that we use for theclaim_search
params