Add tags to publish page, infinite scroll, navigation improvements #2593

Merged
neb-b merged 21 commits from infinite into master 2019-07-02 04:54:36 +02:00
77 changed files with 694 additions and 429 deletions

View file

@ -23,6 +23,7 @@
"WEBPACK_PORT": true "WEBPACK_PORT": true
}, },
"rules": { "rules": {
"brace-style": 0,
"comma-dangle": ["error", "always-multiline"], "comma-dangle": ["error", "always-multiline"],
"handle-callback-err": 0, "handle-callback-err": 0,
"indent": 0, "indent": 0,

View file

@ -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/). 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] ## [0.33.1] - [2019-06-12]
### Fixed ### Fixed

View file

@ -1,6 +1,6 @@
{ {
"name": "LBRY", "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.", "description": "A browser for the LBRY network, a digital marketplace controlled by its users.",
"keywords": [ "keywords": [
"lbry" "lbry"
@ -63,7 +63,7 @@
"@exponent/electron-cookies": "^2.0.0", "@exponent/electron-cookies": "^2.0.0",
"@hot-loader/react-dom": "16.8", "@hot-loader/react-dom": "16.8",
"@lbry/color": "^1.0.2", "@lbry/color": "^1.0.2",
"@lbry/components": "^2.7.2", "@lbry/components": "^2.7.4",
"@reach/rect": "^0.2.1", "@reach/rect": "^0.2.1",
"@reach/tabs": "^0.1.5", "@reach/tabs": "^0.1.5",
"@reach/tooltip": "^0.2.1", "@reach/tooltip": "^0.2.1",
@ -123,7 +123,7 @@
"jsmediatags": "^3.8.1", "jsmediatags": "^3.8.1",
"json-loader": "^0.5.4", "json-loader": "^0.5.4",
"lbry-format": "https://github.com/lbryio/lbry-format.git", "lbry-format": "https://github.com/lbryio/lbry-format.git",
"lbry-redux": "lbryio/lbry-redux#b3bf3f6d53410ff1c5415b51ca425341e364959f", "lbry-redux": "lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66",
"lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845", "lbryinc": "lbryio/lbryinc#43d382d9b74d396a581a74d87e4c53105e04f845",
"lint-staged": "^7.0.2", "lint-staged": "^7.0.2",
"localforage": "^1.7.1", "localforage": "^1.7.1",
@ -198,7 +198,7 @@
"yarn": "^1.3" "yarn": "^1.3"
}, },
"lbrySettings": { "lbrySettings": {
"lbrynetDaemonVersion": "0.38.0rc10", "lbrynetDaemonVersion": "0.38.0",
"lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip", "lbrynetDaemonUrlTemplate": "https://github.com/lbryio/lbry/releases/download/vDAEMONVER/lbrynet-OSNAME.zip",
"lbrynetDaemonDir": "static/daemon", "lbrynetDaemonDir": "static/daemon",
"lbrynetDaemonFileName": "lbrynet" "lbrynetDaemonFileName": "lbrynet"

View file

@ -1,6 +1,6 @@
import { hot } from 'react-hot-loader/root'; import { hot } from 'react-hot-loader/root';
import { connect } from 'react-redux'; 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 { selectUser, doRewardList, doFetchRewardedContent } from 'lbryinc';
import { selectThemePath } from 'redux/selectors/settings'; import { selectThemePath } from 'redux/selectors/settings';
import App from './view'; import App from './view';
@ -15,6 +15,7 @@ const perform = dispatch => ({
updateBlockHeight: () => dispatch(doUpdateBlockHeight()), updateBlockHeight: () => dispatch(doUpdateBlockHeight()),
fetchRewards: () => dispatch(doRewardList()), fetchRewards: () => dispatch(doRewardList()),
fetchRewardedContent: () => dispatch(doFetchRewardedContent()), fetchRewardedContent: () => dispatch(doFetchRewardedContent()),
fetchTransactions: () => dispatch(doFetchTransactions()),
}); });
export default hot( export default hot(

View file

@ -9,6 +9,8 @@ import { openContextMenu } from 'util/context-menu';
import useKonamiListener from 'util/enhanced-layout'; import useKonamiListener from 'util/enhanced-layout';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
export const MAIN_WRAPPER_CLASS = 'main-wrapper';
type Props = { type Props = {
alertError: (string | {}) => void, alertError: (string | {}) => void,
pageTitle: ?string, pageTitle: ?string,
@ -16,18 +18,23 @@ type Props = {
theme: string, theme: string,
fetchRewards: () => void, fetchRewards: () => void,
fetchRewardedContent: () => void, fetchRewardedContent: () => void,
fetchTransactions: () => void,
}; };
function App(props: Props) { function App(props: Props) {
const { theme, fetchRewards, fetchRewardedContent } = props; const { theme, fetchRewards, fetchRewardedContent, fetchTransactions } = props;
const appRef = useRef(); const appRef = useRef();
const isEnhancedLayout = useKonamiListener(); const isEnhancedLayout = useKonamiListener();
useEffect(() => { useEffect(() => {
ReactModal.setAppElement(appRef.current); ReactModal.setAppElement(appRef.current);
fetchRewards();
fetchRewardedContent(); fetchRewardedContent();
}, [fetchRewards, fetchRewardedContent]);
// @if TARGET='app'
fetchRewards();
fetchTransactions();
// @endif
}, [fetchRewards, fetchRewardedContent, fetchTransactions]);
useEffect(() => { useEffect(() => {
// $FlowFixMe // $FlowFixMe
@ -38,7 +45,7 @@ function App(props: Props) {
<div ref={appRef} onContextMenu={e => openContextMenu(e)}> <div ref={appRef} onContextMenu={e => openContextMenu(e)}>
<Header /> <Header />
<div className="main-wrapper"> <div className={MAIN_WRAPPER_CLASS}>
<div className="main-wrapper-inner"> <div className="main-wrapper-inner">
<Router /> <Router />
<SideBar /> <SideBar />

View file

@ -1,6 +1,7 @@
// @flow // @flow
import { MAIN_WRAPPER_CLASS } from 'component/app/view';
import type { Node } from 'react'; import type { Node } from 'react';
import React from 'react'; import React, { useEffect } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import ClaimPreview from 'component/claimPreview'; import ClaimPreview from 'component/claimPreview';
import Spinner from 'component/spinner'; import Spinner from 'component/spinner';
@ -18,13 +19,29 @@ type Props = {
loading: boolean, loading: boolean,
type: string, type: string,
empty?: 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 // If using the default header, this is a unique ID needed to persist the state of the filter setting
persistedStorageKey?: string, persistedStorageKey?: string,
}; };
export default function ClaimList(props: Props) { 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 [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
const hasUris = uris && !!uris.length; const hasUris = uris && !!uris.length;
const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || []; const sortedUris = (hasUris && (currentSort === SORT_NEW ? uris : uris.slice().reverse())) || [];
neb-b commented 2019-07-01 18:23:16 +02:00 (Migrated from github.com)
Review

@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 the claim_search params

@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 the `claim_search` params
kauffj commented 2019-07-01 22:54:49 +02:00 (Migrated from github.com)
Review

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,33 +50,62 @@ export default function ClaimList(props: Props) {
setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); 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 ( return (
<section className={classnames('file-list')}> <section
className={classnames('claim-list', {
'claim-list--small': type === 'small',
})}
>
{header !== false && ( {header !== false && (
<div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}> <div className={classnames('claim-list__header', { 'claim-list__header--small': type === 'small' })}>
{header || ( {header}
<FormField
className="claim-list__dropdown"
type="select"
name="file_sort"
value={currentSort}
onChange={handleSortChange}
>
<option value={SORT_NEW}>{__('Newest First')}</option>
<option value={SORT_OLD}>{__('Oldest First')}</option>
</FormField>
)}
{loading && <Spinner light type="small" />} {loading && <Spinner light type="small" />}
<div className="claim-list__alt-controls">{headerAltControls}</div> <div className="claim-list__alt-controls">
{headerAltControls}
{defaultSort && (
<FormField
className="claim-list__dropdown"
type="select"
name="file_sort"
value={currentSort}
onChange={handleSortChange}
>
<option value={SORT_NEW}>{__('Newest First')}</option>
<option value={SORT_OLD}>{__('Oldest First')}</option>
</FormField>
)}
</div>
</div> </div>
)} )}
{meta && <div className="claim-list__meta">{meta}</div>}
{hasUris && ( {hasUris && (
<ul> <ul>
{sortedUris.map((uri, index) => ( {sortedUris.map((uri, index) => (
<React.Fragment key={uri}> <React.Fragment key={uri}>
<ClaimPreview uri={uri} type={type} /> <ClaimPreview uri={uri} type={type} placeholder={loading && (!page || page === 1)} />
{index === 4 && injectedItem && <li className="claim-list__item--injected">{injectedItem}</li>} {index === 4 && injectedItem && <li className="claim-preview--injected">{injectedItem}</li>}
</React.Fragment> </React.Fragment>
))} ))}
</ul> </ul>

View file

@ -1,10 +1,12 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux'; import { doClaimSearch, selectLastClaimSearchUris, selectFetchingClaimSearch, doToggleTagFollow } from 'lbry-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import ClaimListDiscover from './view'; import ClaimListDiscover from './view';
const select = state => ({ const select = state => ({
uris: selectLastClaimSearchUris(state), uris: selectLastClaimSearchUris(state),
loading: selectFetchingClaimSearch(state), loading: selectFetchingClaimSearch(state),
subscribedChannels: selectSubscriptions(state),
}); });
const perform = { const perform = {

View file

@ -1,12 +1,14 @@
// @flow // @flow
import type { Node } from 'react'; import type { Node } from 'react';
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import moment from 'moment'; import moment from 'moment';
import usePersistedState from 'util/use-persisted-state';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import Tag from 'component/tag'; 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_DAY = 'day';
const TIME_WEEK = 'week'; const TIME_WEEK = 'week';
const TIME_MONTH = 'month'; const TIME_MONTH = 'month';
@ -14,15 +16,18 @@ const TIME_YEAR = 'year';
const TIME_ALL = 'all'; const TIME_ALL = 'all';
const SEARCH_SORT_YOU = 'you'; const SEARCH_SORT_YOU = 'you';
const SEARCH_SORT_ALL = 'everyone'; const SEARCH_SORT_ALL = 'everyone';
const SEARCH_SORT_CHANNELS = 'channels';
const TYPE_TRENDING = 'trending'; const TYPE_TRENDING = 'trending';
const TYPE_TOP = 'top'; const TYPE_TOP = 'top';
const TYPE_NEW = 'new'; 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_TYPES = ['trending', 'top', 'new'];
const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL]; const SEARCH_TIMES = [TIME_DAY, TIME_WEEK, TIME_MONTH, TIME_YEAR, TIME_ALL];
type Props = { type Props = {
uris: Array<string>, uris: Array<string>,
subscribedChannels: Array<Subscription>,
doClaimSearch: (number, {}) => void, doClaimSearch: (number, {}) => void,
injectedItem: any, injectedItem: any,
tags: Array<string>, tags: Array<string>,
@ -33,19 +38,30 @@ type Props = {
}; };
function ClaimListDiscover(props: Props) { function ClaimListDiscover(props: Props) {
const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta } = props; const { doClaimSearch, uris, tags, loading, personal, injectedItem, meta, subscribedChannels } = props;
const [personalSort, setPersonalSort] = usePersistedState('file-list-trending:personalSort', SEARCH_SORT_YOU); const [personalSort, setPersonalSort] = usePersistedState('claim-list-discover:personalSort', SEARCH_SORT_YOU);
const [typeSort, setTypeSort] = usePersistedState('file-list-trending:typeSort', TYPE_TRENDING); const [typeSort, setTypeSort] = usePersistedState('claim-list-discover:typeSort', TYPE_TRENDING);
const [timeSort, setTimeSort] = usePersistedState('file-list-trending:timeSort', TIME_WEEK); 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 toCapitalCase = string => string.charAt(0).toUpperCase() + string.slice(1);
const tagsString = tags.join(','); const tagsString = tags.join(',');
const channelsIdString = subscribedChannels.map(channel => channel.uri.split('#')[1]).join(',');
useEffect(() => { 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 newTags = tagsString.split(',');
const newChannelIds = channelsIdString.split(',');
if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) { if ((newTags && !personal) || (newTags && personal && personalSort === SEARCH_SORT_YOU)) {
options.any_tags = newTags; options.any_tags = newTags;
} else if (personalSort === SEARCH_SORT_CHANNELS) {
options.channel_ids = newChannelIds;
} }
if (typeSort === TYPE_TRENDING) { if (typeSort === TYPE_TRENDING) {
@ -65,7 +81,15 @@ function ClaimListDiscover(props: Props) {
} }
doClaimSearch(20, options); 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 = ( const header = (
<h1 className="card__title--flex"> <h1 className="card__title--flex">
@ -91,20 +115,18 @@ function ClaimListDiscover(props: Props) {
name="trending_overview" name="trending_overview"
className="claim-list__dropdown" className="claim-list__dropdown"
value={personalSort} value={personalSort}
onChange={e => setPersonalSort(e.target.value)} onChange={e => {
setPage(1);
setPersonalSort(e.target.value);
}}
> >
{SEARCH_FILTER_TYPES.map(type => ( {SEARCH_FILTER_TYPES.map(type => (
<option key={type} value={type}> <option key={type} value={type}>
{toCapitalCase(type)} {getLabel(type)}
</option> </option>
))} ))}
</FormField> </FormField>
)} )}
</h1>
);
const headerAltControls = (
<React.Fragment>
{typeSort === 'top' && ( {typeSort === 'top' && (
<FormField <FormField
className="claim-list__dropdown" className="claim-list__dropdown"
@ -115,24 +137,29 @@ function ClaimListDiscover(props: Props) {
> >
{SEARCH_TIMES.map(time => ( {SEARCH_TIMES.map(time => (
<option key={time} value={time}> <option key={time} value={time}>
{toCapitalCase(time)} {/* i18fixme */}
{__('This')} {toCapitalCase(time)}
</option> </option>
))} ))}
</FormField> </FormField>
)} )}
</React.Fragment> </h1>
); );
return ( return (
<div className="card"> <div className="card">
<ClaimList <ClaimList
meta={meta}
loading={loading} loading={loading}
uris={uris} uris={uris}
injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem} injectedItem={personalSort === SEARCH_SORT_YOU && injectedItem}
header={header} header={header}
headerAltControls={headerAltControls} headerAltControls={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> </div>
); );
} }

View file

@ -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);

View file

@ -1,7 +1,7 @@
// @flow // @flow
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import { convertToShareLink } from 'lbry-redux'; import { parseURI, convertToShareLink } from 'lbry-redux';
import { withRouter } from 'react-router-dom'; import { withRouter } from 'react-router-dom';
import { openCopyLinkMenu } from 'util/context-menu'; import { openCopyLinkMenu } from 'util/context-menu';
import { formatLbryUriForWeb } from 'util/uri'; import { formatLbryUriForWeb } from 'util/uri';
@ -53,8 +53,8 @@ function ClaimPreview(props: Props) {
blackListedOutpoints, blackListedOutpoints,
} = props; } = props;
const haventFetched = claim === undefined; const haventFetched = claim === undefined;
const abandoned = !isResolvingUri && !claim; const abandoned = !isResolvingUri && !claim && !placeholder;
const isChannel = claim && claim.value_type === 'channel'; const { isChannel } = parseURI(uri);
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0; const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw); let shouldHide = abandoned || (!claimIsMine && obscureNsfw && nsfw);
@ -92,12 +92,12 @@ function ClaimPreview(props: Props) {
return null; return null;
} }
if (placeholder && !claim) { if (placeholder || isResolvingUri) {
return ( return (
<li className="claim-list__item" disabled> <li className="claim-preview" disabled>
<div className="placeholder media__thumb" /> <div className="placeholder media__thumb" />
<div className="placeholder__wrapper"> <div className="placeholder__wrapper">
<div className="placeholder claim-list__item-title" /> <div className="placeholder claim-preview-title" />
<div className="placeholder media__subtitle" /> <div className="placeholder media__subtitle" />
</div> </div>
</li> </li>
@ -109,15 +109,15 @@ function ClaimPreview(props: Props) {
role="link" role="link"
onClick={pending ? undefined : onClick} onClick={pending ? undefined : onClick}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
className={classnames('claim-list__item', { className={classnames('claim-preview', {
'claim-list__item--large': type === 'large', 'claim-preview--large': type === 'large',
'claim-list__pending': pending, 'claim-list__pending': pending,
})} })}
> >
{isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />} {isChannel ? <ChannelThumbnail uri={uri} /> : <CardMedia thumbnail={thumbnail} />}
<div className="claim-list__item-metadata"> <div className="claim-preview-metadata">
<div className="claim-list__item-info"> <div className="claim-preview-info">
<div className="claim-list__item-title"> <div className="claim-preview-title">
<TruncatedText text={title || (claim && claim.name)} lines={1} /> <TruncatedText text={title || (claim && claim.name)} lines={1} />
</div> </div>
{type !== 'small' && ( {type !== 'small' && (
@ -128,7 +128,7 @@ function ClaimPreview(props: Props) {
)} )}
</div> </div>
<div className="claim-list__item-properties"> <div className="claim-preview-properties">
<div className="media__subtitle"> <div className="media__subtitle">
<UriIndicator uri={uri} link /> <UriIndicator uri={uri} link />
{pending && <div>Pending...</div>} {pending && <div>Pending...</div>}

View file

@ -3,7 +3,7 @@ import * as React from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
import Button from 'component/button'; import Button from 'component/button';
const SLIM_TAGS = 2; const SLIM_TAGS = 1;
const NORMAL_TAGS = 4; const NORMAL_TAGS = 4;
const LARGE_TAGS = 10; const LARGE_TAGS = 10;

View file

@ -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" /> <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> </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( [ICONS.ARROW_LEFT]: buildIcon(
<g fill="none" fillRule="evenodd" strokeLinecap="round"> <g fill="none" fillRule="evenodd" strokeLinecap="round">
<path d="M4, 12 L21, 12" /> <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" /> <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> </g>
), ),
[ICONS.UPLOAD]: buildIcon( [ICONS.PUBLISH]: buildIcon(
<g fill="none" fillRule="evenodd" strokeLinecap="round"> <g fill="none" fillRule="evenodd" strokeLinecap="round">
<path <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" 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> </g>
), ),
[ICONS.PUBLISHED]: buildIcon( [ICONS.SUBSCRIBE]: 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(
<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" /> <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( [ICONS.SETTINGS]: buildIcon(
<g> <g>
<circle cx="12" cy="12" r="3" /> <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.OVERVIEW]: buildIcon(<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />),
[ICONS.WALLET]: buildIcon( [ICONS.WALLET]: buildIcon(
<g> <g>
<line x1="8" y1="6" x2="21" y2="6" /> <rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
<line x1="8" y1="12" x2="21" y2="12" /> <line x1="1" y1="10" x2="23" y2="10" />
<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" />
</g> </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" />), [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( [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" /> <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>
),
}; };

View file

@ -26,7 +26,7 @@ class IconComponent extends React.PureComponent<Props> {
return __('Featured content. Earn rewards for watching.'); return __('Featured content. Earn rewards for watching.');
case ICONS.DOWNLOAD: case ICONS.DOWNLOAD:
return __('This file is downloaded.'); return __('This file is downloaded.');
case ICONS.SUBSCRIPTION: case ICONS.SUBSCRIBE:
return __('You are subscribed to this channel.'); return __('You are subscribed to this channel.');
case ICONS.SETTINGS: case ICONS.SETTINGS:
return __('Your settings.'); return __('Your settings.');

View file

@ -1,5 +1,5 @@
// @flow // @flow
import type { Node } from 'react'; import type { ElementRef } from 'react';
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
@ -17,7 +17,7 @@ type Props = {
openModal: (id: string, { uri: string }) => void, openModal: (id: string, { uri: string }) => void,
claimIsMine: boolean, claimIsMine: boolean,
fileInfo: FileInfo, fileInfo: FileInfo,
viewerContainer: ?{ current: Node }, viewerContainer: { current: ElementRef<any> },
showFullscreen: boolean, showFullscreen: boolean,
}; };

View file

@ -42,7 +42,6 @@ class FileDetails extends PureComponent<Props> {
<Expandable> <Expandable>
{description && ( {description && (
<Fragment> <Fragment>
<div className="media__info-title">About</div>
<div className="media__info-text"> <div className="media__info-text">
<MarkdownPreview content={description} promptLinks /> <MarkdownPreview content={description} promptLinks />
</div> </div>

View file

@ -4,6 +4,7 @@ import * as React from 'react';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
import FilePrice from 'component/filePrice'; import FilePrice from 'component/filePrice';
import VideoDuration from 'component/videoDuration';
type Props = { type Props = {
uri: string, uri: string,
@ -21,10 +22,11 @@ export default function FileProperties(props: Props) {
return ( return (
<div className="file-properties"> <div className="file-properties">
{isSubscribed && <Icon tooltip icon={icons.SUBSCRIPTION} />} {isSubscribed && <Icon tooltip icon={icons.SUBSCRIBE} />}
{!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />} {!claimIsMine && downloaded && <Icon tooltip icon={icons.DOWNLOAD} />}
{isRewardContent && <Icon tooltip icon={icons.FEATURED} />} {isRewardContent && <Icon tooltip icon={icons.FEATURED} />}
<FilePrice hideFree uri={uri} /> <FilePrice hideFree uri={uri} />
<VideoDuration className="media__subtitle" uri={uri} />
</div> </div>
); );
} }

View file

@ -1,12 +1,18 @@
// @flow // @flow
import { remote } from 'electron'; import { remote } from 'electron';
import React from 'react'; import React, { Suspense } from 'react';
import LoadingScreen from 'component/common/loading-screen'; import LoadingScreen from 'component/common/loading-screen';
import VideoViewer from 'component/viewers/videoViewer'; import VideoViewer from 'component/viewers/videoViewer';
// Audio player on hold until the current player is dropped // Audio player on hold until the current player is dropped
// This component is half working // This component is half working
// const AudioViewer = React.lazy<*>(() => // const AudioViewer = React.lazy<*>(() =>
// import(
// /* webpackChunkName: "audioViewer" */
// 'component/viewers/audioViewer'
// )
// );
// const AudioViewer = React.lazy<*>(() =>
// import(/* webpackChunkName: "audioViewer" */ // import(/* webpackChunkName: "audioViewer" */
// 'component/viewers/audioViewer') // 'component/viewers/audioViewer')
// ); // );

View file

@ -1,4 +1,5 @@
// @flow // @flow
import type { ElementRef } from 'react';
import '@babel/polyfill'; import '@babel/polyfill';
import * as React from 'react'; import * as React from 'react';
@ -29,7 +30,7 @@ type Props = {
onFinishCb: ?() => void, onFinishCb: ?() => void,
savePosition: number => void, savePosition: number => void,
changeVolume: number => void, changeVolume: number => void,
viewerContainer: React.Ref, viewerContainer: { current: ElementRef<any> },
searchBarFocused: boolean, searchBarFocused: boolean,
}; };
@ -114,7 +115,9 @@ class MediaPlayer extends React.PureComponent<Props, State> {
componentWillUnmount() { componentWillUnmount() {
const mediaElement = this.mediaContainer.current.children[0]; 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) { if (mediaElement) {
mediaElement.removeEventListener('click', this.togglePlay); mediaElement.removeEventListener('click', this.togglePlay);
@ -128,11 +131,11 @@ class MediaPlayer extends React.PureComponent<Props, State> {
if (!searchBarFocused) { if (!searchBarFocused) {
// Handle fullscreen shortcut key (f) // Handle fullscreen shortcut key (f)
if (event.keyCode === F_KEYCODE) { if (event.keyCode === F_KEYCODE) {
this.toggleFullscreen(); // this.toggleFullscreen();
} }
// Handle toggle play // Handle toggle play
// @if TARGET='app' // @if TARGET='app'
this.togglePlay(event); // this.togglePlay(event);
// @endif // @endif
} }
}; };
@ -263,9 +266,6 @@ class MediaPlayer extends React.PureComponent<Props, State> {
this.renderFile(); this.renderFile();
} }
// @endif // @endif
// Fullscreen event for web and app
document.addEventListener('keydown', this.handleKeyDown);
} }
// @if TARGET='app' // @if TARGET='app'

View file

@ -1,4 +1,5 @@
// @flow // @flow
import type { ElementRef } from 'react';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React, { Suspense } from 'react'; import React, { Suspense } from 'react';
import classnames from 'classnames'; import classnames from 'classnames';
@ -52,7 +53,7 @@ type Props = {
nsfw: boolean, nsfw: boolean,
thumbnail: ?string, thumbnail: ?string,
isPlayableType: boolean, isPlayableType: boolean,
viewerContainer: React.Ref, viewerContainer: { current: ElementRef<any> },
}; };
class FileViewer extends React.PureComponent<Props> { class FileViewer extends React.PureComponent<Props> {
@ -126,7 +127,7 @@ class FileViewer extends React.PureComponent<Props> {
} }
this.props.cancelPlay(); this.props.cancelPlay();
window.removeEventListener('keydown', this.handleKeyDown); // window.removeEventListener('keydown', this.handleKeyDown);
} }
handleKeyDown(event: SyntheticKeyboardEvent<*>) { handleKeyDown(event: SyntheticKeyboardEvent<*>) {

View file

@ -91,11 +91,12 @@ const Header = (props: Props) => {
{__('Wallet')} {__('Wallet')}
</MenuItem> </MenuItem>
<MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}> <MenuItem className="menu__link" onSelect={() => history.push(`/$/publish`)}>
<Icon aria-hidden icon={ICONS.UPLOAD} /> <Icon aria-hidden icon={ICONS.PUBLISH} />
{__('Publish')} {__('Publish')}
</MenuItem> </MenuItem>
</MenuList> </MenuList>
</Menu> </Menu>
<Menu> <Menu>
<MenuButton className="header__navigation-item menu__title"> <MenuButton className="header__navigation-item menu__title">
<Icon size={18} icon={ICONS.SETTINGS} /> <Icon size={18} icon={ICONS.SETTINGS} />

View file

@ -15,6 +15,7 @@ const select = state => ({
channel: makeSelectPublishFormValue('channel')(state), channel: makeSelectPublishFormValue('channel')(state),
bid: makeSelectPublishFormValue('bid')(state), bid: makeSelectPublishFormValue('bid')(state),
uri: makeSelectPublishFormValue('uri')(state), uri: makeSelectPublishFormValue('uri')(state),
bid: makeSelectPublishFormValue('bid')(state),
isStillEditing: selectIsStillEditing(state), isStillEditing: selectIsStillEditing(state),
isResolvingUri: selectIsResolvingPublishUris(state), isResolvingUri: selectIsResolvingPublishUris(state),
amountNeededForTakeover: selectTakeOverAmount(state), amountNeededForTakeover: selectTakeOverAmount(state),

View file

@ -10,7 +10,7 @@ type Props = {
name: string, name: string,
channel: string, channel: string,
uri: string, uri: string,
bid: string, bid: number,
balance: number, balance: number,
isStillEditing: boolean, isStillEditing: boolean,
myClaimForUri: ?StreamClaim, myClaimForUri: ?StreamClaim,
@ -27,7 +27,7 @@ function PublishText(props: Props) {
uri, uri,
isStillEditing, isStillEditing,
myClaimForUri, myClaimForUri,
bid: bidString, bid,
isResolvingUri, isResolvingUri,
amountNeededForTakeover, amountNeededForTakeover,
prepareEdit, prepareEdit,
@ -37,7 +37,6 @@ function PublishText(props: Props) {
const [nameError, setNameError] = useState(undefined); const [nameError, setNameError] = useState(undefined);
const [bidError, setBidError] = useState(undefined); const [bidError, setBidError] = useState(undefined);
const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount); const previousBidAmount = myClaimForUri && Number(myClaimForUri.amount);
const bid = Number(bidString);
function editExistingClaim() { function editExistingClaim() {
if (myClaimForUri) { if (myClaimForUri) {

View file

@ -1,4 +1,5 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { doUpdatePublishForm } from 'redux/actions/publish';
import { makeSelectPublishFormValue } from 'redux/selectors/publish'; import { makeSelectPublishFormValue } from 'redux/selectors/publish';
import PublishPage from './view'; import PublishPage from './view';
@ -7,7 +8,11 @@ const select = state => ({
fee: makeSelectPublishFormValue('fee')(state), fee: makeSelectPublishFormValue('fee')(state),
}); });
const perform = dispatch => ({
updatePublishForm: values => dispatch(doUpdatePublishForm(values)),
});
export default connect( export default connect(
select, select,
null perform
)(PublishPage); )(PublishPage);

View file

@ -10,7 +10,8 @@ type Props = {
function RewardTotal(props: Props) { function RewardTotal(props: Props) {
const { rewards } = props; const { rewards } = props;
const rewardTotal = rewards.reduce((acc, val) => acc + val.reward_amount, 0); 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); const integer = Math.round(total * rewardTotal);
return ( return (

View file

@ -16,7 +16,7 @@ import AuthPage from 'page/auth';
import InvitePage from 'page/invite'; import InvitePage from 'page/invite';
import SubscriptionsPage from 'page/subscriptions'; import SubscriptionsPage from 'page/subscriptions';
import SearchPage from 'page/search'; import SearchPage from 'page/search';
import UserHistoryPage from 'page/userHistory'; import LibraryPage from 'page/library';
import WalletPage from 'page/wallet'; import WalletPage from 'page/wallet';
import NavigationHistory from 'page/navigationHistory'; import NavigationHistory from 'page/navigationHistory';
import TagsPage from 'page/tags'; import TagsPage from 'page/tags';
@ -24,6 +24,7 @@ import FollowingPage from 'page/following';
const Scroll = withRouter(function ScrollWrapper(props) { const Scroll = withRouter(function ScrollWrapper(props) {
const { pathname } = props.location; const { pathname } = props.location;
useEffect(() => { useEffect(() => {
// Auto scroll to the top of a window for new pages // Auto scroll to the top of a window for new pages
// The browser will handle scrolling if it needs to, but // 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.SEARCH}`} exact component={SearchPage} />
<Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} /> <Route path={`/$/${PAGES.SETTINGS}`} exact component={SettingsPage} />
<Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} /> <Route path={`/$/${PAGES.TRANSACTIONS}`} exact component={TransactionHistoryPage} />
<Route path={`/$/${PAGES.LIBRARY}`} exact component={UserHistoryPage} /> <Route path={`/$/${PAGES.LIBRARY}`} exact component={LibraryPage} />
<Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} /> <Route path={`/$/${PAGES.ACCOUNT}`} exact component={AccountPage} />
<Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} /> <Route path={`/$/${PAGES.LIBRARY}/all`} exact component={NavigationHistory} />
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} /> <Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
<Route path={`/$/${PAGES.FOLLOWING}`} exact component={SubscriptionsPage} /> <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} /> <Route path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
{/* Below need to go at the end to make sure we don't match any of our pages first */} {/* Below need to go at the end to make sure we don't match any of our pages first */}
<Route path="/:claimName" exact component={ShowPage} /> <Route path="/:claimName" exact component={ShowPage} />

View file

@ -26,7 +26,7 @@ const SearchOptions = (props: Props) => {
<div> <div>
<Button <Button
button="alt" button="alt"
label={__('FILTER')} label={__('Filter')}
iconRight={expanded ? ICONS.UP : ICONS.DOWN} iconRight={expanded ? ICONS.UP : ICONS.DOWN}
onClick={toggleSearchExpanded} onClick={toggleSearchExpanded}
/> />

View file

@ -33,37 +33,24 @@ function SideBar(props: Props) {
{ {
...buildLink(null, __('Home'), ICONS.HOME), ...buildLink(null, __('Home'), ICONS.HOME),
}, },
{
...buildLink(PAGES.FOLLOWING, __('Following'), ICONS.SUBSCRIPTION),
},
{ {
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY), ...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
}, },
{ {
...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISHED), ...buildLink(PAGES.PUBLISHED, __('Publishes'), ICONS.PUBLISH),
}, },
].map(renderLink)} ].map(renderLink)}
<li>
<Button
navigate="/$/following/edit"
icon={ICONS.EDIT}
className="navigation__link"
activeClass="navigation__link--active"
label={__('Edit')}
/>
</li>
</ul> </ul>
<ul className="navigation__links tags--vertical"> <ul className="navigation__links tags--vertical">
{followedTags.map(({ name }, key) => ( {followedTags.map(({ name }, key) => (
<li className="navigation__link--indented" key={name}> <li className="" key={name}>
<Tag navigate={`/$/tags?t${name}`} name={name} /> <Tag navigate={`/$/tags?t${name}`} name={name} />
</li> </li>
))} ))}
</ul> </ul>
<ul className="navigation__links--small"> <ul className="navigation__links--small">
{subscriptions.map(({ uri, channelName }, index) => ( {subscriptions.map(({ uri, channelName }, index) => (
<li key={uri} className="navigation__link--indented"> <li key={uri} className="">
<Button <Button
navigate={uri} navigate={uri}
label={channelName} label={channelName}

View file

@ -51,7 +51,11 @@ class SnackBar extends React.PureComponent<Props> {
<div>&#9432;</div> <div>&#9432;</div>
<div>{message}</div> <div>{message}</div>
</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> </div>
); );
} }

View file

@ -3,10 +3,9 @@ import * as ICONS from 'constants/icons';
import React from 'react'; import React from 'react';
import Button from 'component/button'; import Button from 'component/button';
import CopyableText from 'component/copyableText'; import CopyableText from 'component/copyableText';
import Tooltip from 'component/common/tooltip';
type Props = { type Props = {
claim: StreamClaim, claim: Claim,
onDone: () => void, onDone: () => void,
speechShareable: boolean, speechShareable: boolean,
isChannel: boolean, isChannel: boolean,
@ -27,21 +26,15 @@ class SocialShare extends React.PureComponent<Props> {
render() { render() {
const { claim, isChannel } = this.props; 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 { 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 => { const getLbryTvUri = (): string => {
if (isChannel) { return `${claimName}/${claimId}`;
// 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 getLbryUri = (): string => { 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 lbryPrefix = 'https://open.lbry.com/';
const lbryUri = getLbryUri(); const lbryUri = getLbryUri();
const speechUri = getSpeechUri(); const lbryTvUri = getLbryTvUri();
const encodedLbryURL: string = `${lbryPrefix}${encodeURIComponent(lbryUri)}`; const encodedLbryURL: string = `${lbryPrefix}${encodeURIComponent(lbryUri)}`;
const lbryURL: string = `${lbryPrefix}${getLbryUri()}`; const lbryURL: string = `${lbryPrefix}${getLbryUri()}`;
const encodedLbryTvUrl = `${lbryTvPrefix}${encodeURIComponent(lbryTvUri)}`;
const lbryTvUrl = `${lbryTvPrefix}${lbryTvUri}`;
const encodedSpeechURL = `${speechPrefix}${encodeURIComponent(speechUri)}`; const shareOnFb = __('Share on Facebook');
const speechURL = `${speechPrefix}${speechUri}`; const shareOnTwitter = __('Share On Twitter');
return ( return (
<React.Fragment> <React.Fragment>
{speechShareable && ( {speechShareable && (
<div className="card__content"> <div className="card__content">
<label className="help">{__('Web link')}</label> <label className="card__subtitle">{__('Web link')}</label>
<CopyableText copyable={speechURL} /> <CopyableText copyable={lbryTvUrl} />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Tooltip label={__('Facebook')}> <Button
<Button icon={ICONS.FACEBOOK}
iconColor="blue" button="link"
icon={ICONS.FACEBOOK} description={shareOnFb}
button="alt" href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryTvUrl}`}
label={__('')} />
href={`https://facebook.com/sharer/sharer.php?u=${encodedSpeechURL}`} <Button
/> icon={ICONS.TWITTER}
</Tooltip> button="link"
<Tooltip label={__('Twitter')}> description={shareOnTwitter}
<Button href={`https://twitter.com/home?status=${encodedLbryTvUrl}`}
iconColor="blue" />
icon={ICONS.TWITTER} <Button icon={ICONS.WEB} button="link" description={__('View on lbry.tv')} href={`${lbryTvUrl}`} />
button="alt"
label={__('')}
href={`https://twitter.com/home?status=${encodedSpeechURL}`}
/>
</Tooltip>
<Tooltip label={__('View on Spee.ch')}>
<Button icon={ICONS.WEB} iconColor="blue" button="alt" label={__('')} href={`${speechURL}`} />
</Tooltip>
</div> </div>
</div> </div>
)} )}
<div className="card__content"> <div className="card__content">
<label className="help">{__('LBRY App link')}</label> <label className="card__subtitle">{__('LBRY App link')}</label>
<CopyableText copyable={lbryURL} noSnackbar /> <CopyableText copyable={lbryURL} noSnackbar />
<div className="card__actions card__actions--center"> <div className="card__actions card__actions--center">
<Tooltip label={__('Facebook')}> <Button
<Button icon={ICONS.FACEBOOK}
iconColor="blue" button="link"
icon={ICONS.FACEBOOK} description={shareOnFb}
button="alt" href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`}
label={__('')} />
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`} <Button
/> icon={ICONS.TWITTER}
</Tooltip> button="link"
<Tooltip label={__('Twitter')}> description={shareOnTwitter}
<Button href={`https://twitter.com/home?status=${encodedLbryURL}`}
iconColor="blue" />
icon={ICONS.TWITTER}
button="alt"
label={__('')}
href={`https://twitter.com/home?status=${encodedLbryURL}`}
/>
</Tooltip>
</div> </div>
</div> </div>
<div className="card__actions"> <div className="card__actions">

View file

@ -1,7 +1,7 @@
// @flow // @flow
import * as MODALS from 'constants/modal_types'; import * as MODALS from 'constants/modal_types';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import React from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { parseURI } from 'lbry-redux'; import { parseURI } from 'lbry-redux';
import Button from 'component/button'; import Button from 'component/button';
@ -32,18 +32,36 @@ export default function SubscribeButton(props: Props) {
showSnackBarOnSubscribe, showSnackBarOnSubscribe,
doToast, doToast,
} = props; } = props;
const buttonRef = useRef();
const [isHovering, setIsHovering] = useState(false);
const { claimName } = parseURI(uri);
const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe; const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe;
const subscriptionLabel = isSubscribed ? __('Following') : __('Follow'); 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 ( return (
<Button <Button
ref={buttonRef}
iconColor="red" iconColor="red"
icon={ICONS.SUBSCRIPTION} icon={unfollowOverride ? ICONS.UNSUBSCRIBE : ICONS.SUBSCRIBE}
button={'alt'} button={'alt'}
label={subscriptionLabel} label={unfollowOverride || subscriptionLabel}
onClick={e => { onClick={e => {
e.stopPropagation(); e.stopPropagation();

View file

@ -15,11 +15,18 @@ export default function Tag(props: Props) {
const { name, onClick, type = 'link', disabled = false } = props; const { name, onClick, type = 'link', disabled = false } = props;
const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` }; const clickProps = onClick ? { onClick } : { navigate: `/$/tags?t=${name}` };
let title;
if (!onClick) {
title = __('View tag');
} else {
type === 'add' ? __('Add tag') : __('Remove tag');
}
return ( return (
<Button <Button
{...clickProps} {...clickProps}
disabled={disabled} disabled={disabled}
title={title}
className={classnames('tag', { className={classnames('tag', {
'tag--add': type === 'add', 'tag--add': type === 'add',
'tag--remove': type === 'remove', 'tag--remove': type === 'remove',

View file

@ -19,7 +19,7 @@ type Props = {
}; };
export default function TagSelect(props: 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(''); const [newTag, setNewTag] = useState('');
let tags = unfollowedTags.slice(); let tags = unfollowedTags.slice();
@ -27,9 +27,14 @@ export default function TagSelect(props: Props) {
tags.unshift({ name: newTag }); tags.unshift({ name: newTag });
} }
const doesTagMatch = ({ name }) => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true); const doesTagMatch = name => (newTag ? name.toLowerCase().includes(newTag.toLowerCase()) : true);
const suggestedTags = tags.filter(doesTagMatch).slice(0, 5); // Make sure there are no duplicates, then trim
const suggestedTransitions = useTransition(suggestedTags, tag => tag.name, unfollowedTagsAnimation); 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) { function onChange(e) {
setNewTag(e.target.value); setNewTag(e.target.value);
@ -42,11 +47,11 @@ export default function TagSelect(props: Props) {
if (onSelect) { if (onSelect) {
onSelect({ name: newTag }); onSelect({ name: newTag });
} else { } else {
if (!unfollowedTags.includes(newTag)) { if (!unfollowedTags.map(({ name }) => name).includes(newTag)) {
doAddTag(newTag); doAddTag(newTag);
} }
if (!followedTags.includes(newTag)) { if (!followedTags.map(({ name }) => name).includes(newTag)) {
doToggleTagFollow(newTag); doToggleTagFollow(newTag);
} }
} }
@ -54,7 +59,7 @@ export default function TagSelect(props: Props) {
function handleTagClick(tag) { function handleTagClick(tag) {
if (onSelect) { if (onSelect) {
onSelect(tag); onSelect({ name: tag });
} else { } else {
doToggleTagFollow(tag); doToggleTagFollow(tag);
} }
@ -74,7 +79,7 @@ export default function TagSelect(props: Props) {
<ul className="tags"> <ul className="tags">
{suggestedTransitions.map(({ item, key, props }) => ( {suggestedTransitions.map(({ item, key, props }) => (
<animated.li key={key} style={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> </animated.li>
))} ))}
{!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>} {!suggestedTransitions.length && <p className="empty tags__empty-message">No suggested tags</p>}

View file

@ -14,8 +14,10 @@ class TransactionListRecent extends React.PureComponent<Props> {
componentDidMount() { componentDidMount() {
const { fetchMyClaims, fetchTransactions } = this.props; const { fetchMyClaims, fetchTransactions } = this.props;
// @if TARGET='app'
fetchMyClaims(); fetchMyClaims();
fetchTransactions(); fetchTransactions();
// @endif
} }
render() { render() {

View file

@ -4,6 +4,7 @@ import Button from 'component/button';
import { FormField } from 'component/common/form'; import { FormField } from 'component/common/form';
import UserEmailNew from 'component/userEmailNew'; import UserEmailNew from 'component/userEmailNew';
import UserEmailVerify from 'component/userEmailVerify'; import UserEmailVerify from 'component/userEmailVerify';
import cookie from 'cookie';
type Props = { type Props = {
cancelButton: React.Node, cancelButton: React.Node,
@ -22,6 +23,15 @@ function UserEmail(props: Props) {
isVerified = user.has_verified_email; 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 ( return (
<section className="card card--section"> <section className="card card--section">
{!email && <UserEmailNew />} {!email && <UserEmailNew />}
@ -43,9 +53,7 @@ function UserEmail(props: Props) {
readOnly readOnly
label={__('Your Email')} label={__('Your Email')}
value={email} value={email}
inputButton={ inputButton={<Button button="inverse" label={__('Change')} {...buttonsProps} />}
<Button button="inverse" label={__('Change')} href="https://lbry.com/faq/how-to-change-email" />
}
/> />
)} )}
<p className="help"> <p className="help">

View 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);

View 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;

View file

@ -10,8 +10,7 @@ export const COPY = 'Clipboard';
export const ARROW_LEFT = 'ChevronLeft'; export const ARROW_LEFT = 'ChevronLeft';
export const ARROW_RIGHT = 'ChevronRight'; export const ARROW_RIGHT = 'ChevronRight';
export const DOWNLOAD = 'Download'; export const DOWNLOAD = 'Download';
export const UPLOAD = 'UploadCloud'; export const PUBLISH = 'UploadCloud';
export const PUBLISHED = 'Cloud';
export const REMOVE = 'X'; export const REMOVE = 'X';
export const ADD = 'Plus'; export const ADD = 'Plus';
export const EDIT = 'Edit'; export const EDIT = 'Edit';
@ -29,7 +28,7 @@ export const WALLET = 'List';
export const PHONE = 'Phone'; export const PHONE = 'Phone';
export const COMPLETE = 'Check'; export const COMPLETE = 'Check';
export const COMPLETED = 'CheckCircle'; export const COMPLETED = 'CheckCircle';
export const SUBSCRIPTION = 'Heart'; export const SUBSCRIBE = 'Heart';
export const UNSUBSCRIBE = 'BrokenHeart'; export const UNSUBSCRIBE = 'BrokenHeart';
export const UNLOCK = 'Unlock'; export const UNLOCK = 'Unlock';
export const WEB = 'Globe'; export const WEB = 'Globe';

View file

@ -15,9 +15,8 @@ export const SEND = 'send';
export const SETTINGS = 'settings'; export const SETTINGS = 'settings';
export const SHOW = 'show'; export const SHOW = 'show';
export const ACCOUNT = 'account'; export const ACCOUNT = 'account';
export const SUBSCRIPTIONS = 'subscriptions'; export const FOLLOWING = 'following';
export const SEARCH = 'search'; export const SEARCH = 'search';
export const TRANSACTIONS = 'transactions'; export const TRANSACTIONS = 'transactions';
export const TAGS = 'tags'; export const TAGS = 'tags';
export const WALLET = 'wallet'; export const WALLET = 'wallet';
export const FOLLOWING = 'following';

View file

@ -9,7 +9,7 @@ import InvitePage from 'page/invite';
const WalletPage = () => ( const WalletPage = () => (
<Page> <Page>
{IS_WEB && <UnsupportedOnWeb />} <UnsupportedOnWeb />
<div className={classnames({ 'card--disabled': IS_WEB })}> <div className={classnames({ 'card--disabled': IS_WEB })}>
<div className="columns"> <div className="columns">
<UserEmail /> <UserEmail />

View file

@ -77,7 +77,7 @@ function ChannelPage(props: Props) {
</div> </div>
</TabList> </TabList>
<TabPanels className="channel__data"> <TabPanels>
<TabPanel> <TabPanel>
<ChannelContent uri={uri} /> <ChannelContent uri={uri} />
</TabPanel> </TabPanel>

View file

@ -1,9 +1,11 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { selectFollowedTags } from 'lbry-redux'; import { selectFollowedTags } from 'lbry-redux';
import { selectSubscriptions } from 'redux/selectors/subscriptions';
import DiscoverPage from './view'; import DiscoverPage from './view';
const select = state => ({ const select = state => ({
followedTags: selectFollowedTags(state), followedTags: selectFollowedTags(state),
subscribedChannels: selectSubscriptions(state),
}); });
const perform = {}; const perform = {};

View file

@ -3,6 +3,7 @@ import React from 'react';
import ClaimListDiscover from 'component/claimListDiscover'; import ClaimListDiscover from 'component/claimListDiscover';
import TagsSelect from 'component/tagsSelect'; import TagsSelect from 'component/tagsSelect';
import Page from 'component/page'; import Page from 'component/page';
import Button from 'component/button';
type Props = { type Props = {
followedTags: Array<Tag>, followedTags: Array<Tag>,
@ -10,12 +11,14 @@ type Props = {
function DiscoverPage(props: Props) { function DiscoverPage(props: Props) {
const { followedTags } = props; const { followedTags } = props;
return ( return (
<Page> <Page>
<ClaimListDiscover <ClaimListDiscover
personal personal
tags={followedTags.map(tag => tag.name)} 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> </Page>
); );

View file

@ -22,6 +22,7 @@ import RecommendedContent from 'component/recommendedContent';
import ClaimTags from 'component/claimTags'; import ClaimTags from 'component/claimTags';
import CommentsList from 'component/commentsList'; import CommentsList from 'component/commentsList';
import CommentCreate from 'component/commentCreate'; import CommentCreate from 'component/commentCreate';
import VideoDuration from 'component/videoDuration';
type Props = { type Props = {
claim: StreamClaim, claim: StreamClaim,
@ -222,22 +223,11 @@ class FilePage extends React.Component<Props> {
<div className="grid-area--info media__content media__content--large"> <div className="grid-area--info media__content media__content--large">
<h1 className="media__title media__title--large">{title}</h1> <h1 className="media__title media__title--large">{title}</h1>
<div className="media__subtext media__subtext--large">
<div className="media__actions media__actions--between"> <div className="media__subtitle__channel">
<div className="media__subtext media__subtext--large"> <UriIndicator uri={uri} link />
<div className="media__subtitle__channel">
<UriIndicator uri={uri} link />
</div>
{__('Published on')} <DateTime uri={uri} show={DateTime.SHOW_DATE} />
</div> </div>
{claimIsMine && (
<p>
{viewCount} {viewCount !== 1 ? __('Views') : __('View')}
</p>
)}
</div> </div>
<div className="media__actions media__actions--between"> <div className="media__actions media__actions--between">
<div className="media__action-group--large"> <div className="media__action-group--large">
{claimIsMine && ( {claimIsMine && (
@ -282,10 +272,24 @@ class FilePage extends React.Component<Props> {
</div> </div>
</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"> <div className="media__info--large">
<ClaimTags uri={uri} type="large" /> <ClaimTags uri={uri} type="large" />
</div>
<div className="media__info--large">
<FileDetails uri={uri} /> <FileDetails uri={uri} />
<div className="media__info-title">{__('Comments')}</div> <div className="media__info-title">{__('Comments')}</div>

View file

@ -18,7 +18,13 @@ function FileListDownloaded(props: Props) {
<React.Fragment> <React.Fragment>
{hasDownloads ? ( {hasDownloads ? (
<div className="card"> <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>
) : ( ) : (
<div className="main--empty"> <div className="main--empty">

View file

@ -21,7 +21,14 @@ function FileListPublished(props: Props) {
<Page notContained> <Page notContained>
{uris && uris.length ? ( {uris && uris.length ? (
<div className="card"> <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>
) : ( ) : (
<div className="main--empty"> <div className="main--empty">

View file

@ -5,26 +5,22 @@ import TagsSelect from 'component/tagsSelect';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
type Props = { type Props = {
subscribedChannels: Array<{ uri: string }>, subscribedChannels: Array<Subscription>,
}; };
function DiscoverPage(props: Props) { function FollowingEditPage(props: Props) {
const { subscribedChannels } = props; const { subscribedChannels } = props;
const channelUris = subscribedChannels.map(({ uri }) => uri);
return ( return (
<Page> <Page>
<div className="card"> <div className="card">
<TagsSelect showClose={false} title={__('Find New Tags To Follow')} /> <TagsSelect showClose={false} title={__('Customize Your Tags')} />
</div> </div>
<div className="card"> <div className="card">
<ClaimList <ClaimList header={<h1>{__('Channels You Follow')}</h1>} uris={channelUris} />
header={<h1>{__('Channels You Are Following')}</h1>}
empty={__("You aren't following any channels.")}
uris={subscribedChannels.map(({ uri }) => uri)}
/>
</div> </div>
</Page> </Page>
); );
} }
export default DiscoverPage; export default FollowingEditPage;

View file

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

View 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;

View file

@ -1,8 +1,9 @@
// @flow // @flow
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import React, { useEffect } from 'react'; import React, { useEffect, useState } from 'react';
import Page from 'component/page'; import Page from 'component/page';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import ClaimPreview from 'component/claimPreview';
import Button from 'component/button'; import Button from 'component/button';
type Props = { type Props = {
@ -29,7 +30,7 @@ export default function SubscriptionsPage(props: Props) {
doClaimSearch, doClaimSearch,
uris, uris,
} = props; } = props;
const [page, setPage] = useState(1);
const hasSubscriptions = !!subscribedChannels.length; const hasSubscriptions = !!subscribedChannels.length;
const { search } = location; const { search } = location;
const urlParams = new URLSearchParams(search); const urlParams = new URLSearchParams(search);
@ -53,28 +54,33 @@ export default function SubscriptionsPage(props: Props) {
useEffect(() => { useEffect(() => {
const ids = idString.split(','); const ids = idString.split(',');
const options = { const options = {
page,
channel_ids: ids, channel_ids: ids,
order_by: ['release_time'], order_by: ['release_time'],
}; };
doClaimSearch(20, options); doClaimSearch(20, options);
}, [idString, doClaimSearch]); }, [idString, doClaimSearch, page]);
return ( return (
<Page> <Page>
<div className="card"> <div className="card">
<ClaimList <ClaimList
loading={loading} 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={ headerAltControls={
<Button <Button
button="link" button="link"
label={viewingSuggestedSubs ? hasSubscriptions && __('Following') : __('Find New Channels')} label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Feed') : __('Find New Channels')}
onClick={() => onClick()} onClick={() => onClick()}
/> />
} }
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => sub.uri) : uris} 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> </div>
</Page> </Page>
); );

View file

@ -32,9 +32,9 @@ function TagsPage(props: Props) {
tags={tags} tags={tags}
meta={ meta={
<Button <Button
button="alt" button="link"
onClick={() => doToggleTagFollow(tag)} onClick={() => doToggleTagFollow(tag)}
label={isFollowing ? __('Unfollow this tag') : __('Follow this tag')} label={isFollowing ? __('Following') : __('Follow')}
/> />
} }
/> />

View file

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

View file

@ -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;

View file

@ -4,13 +4,17 @@ import WalletSend from 'component/walletSend';
import WalletAddress from 'component/walletAddress'; import WalletAddress from 'component/walletAddress';
import TransactionListRecent from 'component/transactionListRecent'; import TransactionListRecent from 'component/transactionListRecent';
import Page from 'component/page'; import Page from 'component/page';
import UnsupportedOnWeb from 'component/common/unsupported-on-web';
const WalletPage = () => ( const WalletPage = () => (
<Page> <Page>
<WalletBalance /> <UnsupportedOnWeb />
<TransactionListRecent /> <div className={IS_WEB && 'card--disabled'}>
<WalletSend /> <WalletBalance />
<WalletAddress /> <TransactionListRecent />
<WalletSend />
<WalletAddress />
</div>
</Page> </Page>
); );

View file

@ -24,6 +24,7 @@ import {
import { doAuthenticate } from 'lbryinc'; import { doAuthenticate } from 'lbryinc';
import { lbrySettings as config, version as appVersion } from 'package.json'; import { lbrySettings as config, version as appVersion } from 'package.json';
import { push } from 'connected-react-router'; import { push } from 'connected-react-router';
import { whiteListedReducers } from 'store';
// @if TARGET='app' // @if TARGET='app'
const { autoUpdater } = remote.require('electron-updater'); const { autoUpdater } = remote.require('electron-updater');
@ -327,7 +328,8 @@ export function doDaemonReady() {
export function doClearCache() { export function doClearCache() {
return () => { return () => {
window.cacheStore.purge(); const reducersToClear = whiteListedReducers.filter(reducerKey => reducerKey !== 'tags');
window.cacheStore.purge(reducersToClear);
return Promise.resolve(); return Promise.resolve();
}; };

View file

@ -257,6 +257,7 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
tags: Array<string>, tags: Array<string>,
locations?: Array<Location>, locations?: Array<Location>,
license_url?: string, license_url?: string,
license?: string,
thumbnail_url?: string, thumbnail_url?: string,
release_time?: number, release_time?: number,
fee_currency?: string, fee_currency?: string,
@ -267,13 +268,19 @@ export const doPublish = () => (dispatch: Dispatch, getState: () => {}) => {
description, description,
locations, locations,
bid: creditsToString(bid), bid: creditsToString(bid),
license: publishingLicense,
languages: [language], languages: [language],
tags: tags && tags.map(tag => tag.name), tags: tags && tags.map(tag => tag.name),
license_url: licenseType === COPYRIGHT ? '' : licenseUrl,
thumbnail_url: thumbnail, thumbnail_url: thumbnail,
}; };
if (publishingLicense) {
publishPayload.license = publishingLicense;
}
if (licenseUrl) {
publishPayload.license_url = licenseUrl;
}
if (myClaimForUri && myClaimForUri.value.release_time) { if (myClaimForUri && myClaimForUri.value.release_time) {
publishPayload.release_time = Number(myClaimForUri.value.release_time); publishPayload.release_time = Number(myClaimForUri.value.release_time);
} }

View file

@ -13,13 +13,13 @@
@import 'component/button'; @import 'component/button';
@import 'component/card'; @import 'component/card';
@import 'component/channel'; @import 'component/channel';
@import 'component/claim-list';
@import 'component/comments'; @import 'component/comments';
@import 'component/content'; @import 'component/content';
@import 'component/credit'; @import 'component/credit';
@import 'component/dat-gui'; @import 'component/dat-gui';
@import 'component/expandable'; @import 'component/expandable';
@import 'component/file-download'; @import 'component/file-download';
@import 'component/file-list';
@import 'component/file-properties'; @import 'component/file-properties';
@import 'component/file-render'; @import 'component/file-render';
@import 'component/form-field'; @import 'component/form-field';

View file

@ -18,9 +18,11 @@
} }
} }
// Fix this in lbry/components .button--primary {
.button--primary:not(:hover) { &:hover {
background-color: $lbry-teal-4; background-color: $lbry-teal-3;
}
svg { svg {
color: white; color: white;
} }

View file

@ -16,7 +16,7 @@ $metadata-z-index: 1;
align-self: flex-start; align-self: flex-start;
position: absolute; position: absolute;
object-fit: cover; object-fit: cover;
filter: brightness(60%); filter: brightness(50%);
} }
.channel-cover, .channel-cover,
@ -27,8 +27,8 @@ $metadata-z-index: 1;
.channel-thumbnail { .channel-thumbnail {
display: flex; display: flex;
height: 5.3rem; height: 5rem;
width: 5.4rem; width: 6rem;
background-size: cover; background-size: cover;
margin-right: var(--spacing-medium); margin-right: var(--spacing-medium);
} }
@ -52,7 +52,6 @@ $metadata-z-index: 1;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
align-self: flex-end; align-self: flex-end;
// margin-bottom: -1px;
} }
.channel-thumbnail, .channel-thumbnail,
@ -94,8 +93,3 @@ $metadata-z-index: 1;
margin-top: -0.25rem; margin-top: -0.25rem;
color: rgba($lbry-white, 0.75); color: rgba($lbry-white, 0.75);
} }
// TODO: rename
.channel__data {
min-height: 10rem;
}

View file

@ -19,6 +19,7 @@
// Normal link buttons are too dark on the black file list background // Normal link buttons are too dark on the black file list background
.button--link { .button--link {
color: $lbry-teal-3; color: $lbry-teal-3;
font-size: 1.2em;
&:hover { &:hover {
color: $lbry-teal-1; color: $lbry-teal-1;
@ -43,6 +44,7 @@
background-size: 1.2rem; 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"); 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; height: 2.5rem;
font-size: 1.3rem;
padding: 0 var(--spacing-medium); padding: 0 var(--spacing-medium);
padding-right: var(--spacing-large); padding-right: var(--spacing-large);
margin-bottom: 0; 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 { .claim-list__alt-controls {
display: flex; display: flex;
align-items: center; align-items: center;
margin-left: auto; margin-left: auto;
font-size: 1.4em; font-size: 1.1em;
& > * { & > * {
margin-left: var(--spacing-small); margin-left: var(--spacing-small);
} }
} }
.claim-list__item { .claim-preview {
display: flex; display: flex;
position: relative; position: relative;
font-size: 1.3rem; font-size: 1.3rem;
@ -104,12 +96,12 @@
} }
} }
.claim-list__item--injected, .claim-preview--injected,
.claim-list__item + .claim-list__item { .claim-preview {
border-top: 1px solid rgba($lbry-teal-5, 0.1); border-bottom: 1px solid rgba($lbry-teal-5, 0.1);
} }
.claim-list__item--large { .claim-preview--large {
@include mediaThumbHoverZoom; @include mediaThumbHoverZoom;
font-size: 1.6rem; font-size: 1.6rem;
border-bottom: 0; border-bottom: 0;
@ -138,36 +130,32 @@
} }
} }
.claim-list__item-metadata { .claim-preview-metadata {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
} }
.claim-list__item-info { .claim-preview-info {
align-items: flex-start; align-items: flex-start;
} }
.claim-list__item-info, .claim-preview-info,
.claim-list__item-properties { .claim-preview-properties {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.claim-list__item-properties { .claim-preview-properties {
align-items: flex-end; align-items: flex-end;
flex: 1;
} }
.claim-list__item-title { .claim-preview-title {
font-weight: 600; font-weight: 600;
margin-right: auto; margin-right: auto;
} }
.claim-list__item-tags { .claim-preview-tags {
margin-left: 0; margin-left: 0;
} }
.claim-list__meta {
padding: var(--spacing-medium);
background-color: lighten($lbry-teal-5, 55%);
}

View file

@ -3,6 +3,14 @@
position: relative; position: relative;
align-items: center; align-items: center;
.icon {
stroke: rgba($lbry-black, 0.5);
html[data-mode='dark'] & {
stroke: rgba($lbry-white, 0.7);
}
}
& > *:not(:last-child) { & > *:not(:last-child) {
margin-right: var(--spacing-small); margin-right: var(--spacing-small);
} }
@ -15,7 +23,7 @@
.file-properties--large { .file-properties--large {
flex-wrap: wrap; flex-wrap: wrap;
font-size: 18px; font-size: 18px;
margin: var(--spacing-small) 0; margin: var(--spacing-medium) 0;
& > * { & > * {
margin-top: var(--spacing-small); margin-top: var(--spacing-small);

View file

@ -1,25 +1,5 @@
@import '~@lbry/components/sass/form/_index.scss'; @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 { textarea {
&::placeholder { &::placeholder {
opacity: 0.4; opacity: 0.4;

View file

@ -8,12 +8,17 @@
box-shadow: var(--card-box-shadow) $lbry-gray-1; box-shadow: var(--card-box-shadow) $lbry-gray-1;
padding-left: var(--spacing-large); padding-left: var(--spacing-large);
padding-right: var(--spacing-large); padding-right: var(--spacing-large);
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: mix($lbry-black, $lbry-gray-3, 90%); background-color: mix($lbry-black, $lbry-gray-3, 90%);
color: $lbry-white; color: $lbry-white;
border-bottom: none; border-bottom: none;
box-shadow: var(--card-box-shadow) $lbry-black; box-shadow: var(--card-box-shadow) $lbry-black;
} }
& > * {
user-select: none;
}
} }
.header__contents { .header__contents {
@ -49,10 +54,6 @@
align-items: center; align-items: center;
border-radius: 0; border-radius: 0;
svg {
stroke: $lbry-gray-5;
}
&:hover { &:hover {
color: $lbry-teal-5; color: $lbry-teal-5;

View file

@ -6,7 +6,6 @@
padding-left: var(--spacing-large); padding-left: var(--spacing-large);
padding-right: var(--spacing-large); padding-right: var(--spacing-large);
padding-bottom: var(--spacing-large); padding-bottom: var(--spacing-large);
background-color: mix($lbry-white, $lbry-gray-1, 70%);
display: flex; display: flex;
[data-mode='dark'] & { [data-mode='dark'] & {

View file

@ -132,10 +132,6 @@
color: rgba($lbry-black, 0.8); color: rgba($lbry-black, 0.8);
font-size: 0.9em; font-size: 0.9em;
&:not(:last-child) {
margin-bottom: var(--spacing-medium);
}
html[data-mode='dark'] & { html[data-mode='dark'] & {
color: rgba($lbry-white, 0.7); color: rgba($lbry-white, 0.7);
} }
@ -149,8 +145,9 @@
// S U B T I T L E // S U B T I T L E
.media__subtitle { .media__subtitle {
align-self: flex-start;
font-size: 0.8em; font-size: 0.8em;
color: rgba($lbry-black, 0.8); color: rgba($lbry-black, 0.6);
[data-mode='dark'] & { [data-mode='dark'] & {
color: rgba($lbry-white, 0.8); color: rgba($lbry-white, 0.8);
@ -167,6 +164,7 @@
.media__subtitle__channel { .media__subtitle__channel {
font-weight: 600; font-weight: 600;
margin: var(--spacing-small) 0;
} }
// M E D I A // M E D I A

View file

@ -7,6 +7,7 @@
.navigation { .navigation {
width: var(--side-nav-width); width: var(--side-nav-width);
padding-bottom: var(--spacing-main-padding);
font-size: 1.4rem; font-size: 1.4rem;
@media (max-width: 600px) { @media (max-width: 600px) {

View file

@ -9,7 +9,7 @@
.placeholder { .placeholder {
display: flex; display: flex;
&.claim-list__item-title { &.claim-preview-title {
width: 100%; width: 100%;
height: 3rem; height: 3rem;
} }
@ -18,6 +18,6 @@
margin-top: var(--spacing-small); margin-top: var(--spacing-small);
width: 30%; width: 30%;
height: 2em; height: 1.5em;
} }
} }

View file

@ -13,6 +13,10 @@
.search__options { .search__options {
margin-top: var(--spacing-large); margin-top: var(--spacing-large);
.button {
font-size: 2rem;
}
legend { legend {
&.search__legend--1 { &.search__legend--1 {
background-color: $lbry-teal-4; background-color: $lbry-teal-4;

View file

@ -21,6 +21,7 @@ $main: $lbry-teal-5;
@extend .tags; @extend .tags;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
margin-top: var(--spacing-medium);
} }
.tags--selected { .tags--selected {

View file

@ -31,6 +31,7 @@
[data-reach-menu-item] { [data-reach-menu-item] {
display: block; display: block;
z-index: 2;
} }
[data-reach-menu-item] { [data-reach-menu-item] {
@ -90,7 +91,6 @@
.icon { .icon {
margin-right: var(--spacing-small); margin-right: var(--spacing-small);
margin-bottom: 0.2rem;
stroke: $lbry-gray-5; stroke: $lbry-gray-5;
} }

View file

@ -1,15 +1,9 @@
// Generic html styles used accross the App // Generic html styles used accross the App
// component specific styling should go in the component scss file // component specific styling should go in the component scss file
*,
*::before,
*::after {
-webkit-user-select: text;
}
html { html {
@include font-sans; @include font-sans;
background-color: $lbry-white;
font-size: 12px; font-size: 12px;
height: 100%; height: 100%;
overflow-x: hidden; overflow-x: hidden;
@ -23,6 +17,7 @@ body {
height: 100%; height: 100%;
line-height: 1.5; line-height: 1.5;
overflow: hidden; overflow: hidden;
background-color: mix($lbry-white, $lbry-gray-1, 70%);
html[data-mode='dark'] & { html[data-mode='dark'] & {
background-color: $lbry-black; background-color: $lbry-black;

View file

@ -1,6 +1,6 @@
@mixin placeholder { @mixin placeholder {
animation: pulse 2s infinite ease-in-out; animation: pulse 2s infinite ease-in-out;
background-color: $lbry-gray-2; background-color: $lbry-gray-1;
border-radius: var(--card-radius); border-radius: var(--card-radius);
} }
@ -15,3 +15,7 @@
} }
} }
} }
@mixin focus {
box-shadow: 0 0 0 2px $lbry-blue-1;
}

View file

@ -20,10 +20,7 @@ html {
user-select: none; user-select: none;
} }
a,
area, area,
button,
[role='button'],
input, input,
label, label,
select, select,

View file

@ -74,20 +74,20 @@ const appFilter = createFilter('app', ['hasClickedComment', 'searchOptionsExpand
// We only need to persist the receiveAddress for the wallet // We only need to persist the receiveAddress for the wallet
const walletFilter = createFilter('wallet', ['receiveAddress']); const walletFilter = createFilter('wallet', ['receiveAddress']);
const searchFilter = createFilter('search', ['options']); const searchFilter = createFilter('search', ['options']);
const whiteListedReducers = [
// @if TARGET='app'
'publish',
'wallet',
'fileInfo',
// @endif
'content',
'subscriptions',
'app',
'search',
'tags',
];
const persistOptions = { const persistOptions = {
whitelist: [ whitelist: whiteListedReducers,
// @if TARGET='app'
'publish',
'wallet',
'fileInfo',
// @endif
'content',
'subscriptions',
'app',
'search',
'tags',
],
// Order is important. Needs to be compressed last or other transforms can't // Order is important. Needs to be compressed last or other transforms can't
// read the data // read the data
transforms: [ transforms: [
@ -110,4 +110,4 @@ window.cacheStore = persistStore(store, persistOptions, err => {
} }
}); });
export { store, history }; export { store, history, whiteListedReducers };

View file

@ -9,6 +9,8 @@ export default function usePersistedState(key, firstTimeDefault) {
defaultValue = true; defaultValue = true;
} else if (item === 'false') { } else if (item === 'false') {
defaultValue = false; defaultValue = false;
} else {
defaultValue = item;
} }
} }

View file

@ -12,7 +12,7 @@
<meta property="og:description" content="All your favorite LBRY content in your browser." /> <meta property="og:description" content="All your favorite LBRY content in your browser." />
<meta property="og:image" content="/og.png" /> <meta property="og:image" content="/og.png" />
<!-- @endif --> <!-- @endif -->
<meta name="viewport" content="width=device-width, initial-scale=1" /> <!-- <meta name="viewport" content="width=device-width, initial-scale=1" /> -->
</head> </head>
<body> <body>

View file

@ -292,7 +292,6 @@
"Comment": "Comment", "Comment": "Comment",
"Your comment": "Your comment", "Your comment": "Your comment",
"Post": "Post", "Post": "Post",
"Channel": "Channel",
"No modifier provided after separator %s.": "No modifier provided after separator %s.", "No modifier provided after separator %s.": "No modifier provided after separator %s.",
"Incompatible Daemon": "Incompatible Daemon", "Incompatible Daemon": "Incompatible Daemon",
"Incompatible daemon running": "Incompatible daemon running", "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.", "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?", "Want to know what has changed?": "Want to know what has changed?",
"release notes": "release notes", "release notes": "release notes",
"Remove from your library": "Remove from your library", "Read the FAQ": "Read the FAQ",
"Got it!": "Got it!", "Our FAQ answers many common questions.": "Our FAQ answers many common questions.",
"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.", "Get Live Help": "Get Live Help",
"No tags added": "No tags added", "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.", "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", "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.", "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.", "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", "If you bid more than": "If you bid more than",
"when someone navigates to": "when someone navigates to", "when someone navigates to": "when someone navigates to",
"it will load your published content": "it will load your published content", "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", "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...", "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.", "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", "Publish something new": "Publish something new",
"View it on spee.ch": "View it on spee.ch", "View it on spee.ch": "View it on spee.ch",
"New thumbnail": "New thumbnail", "New thumbnail": "New thumbnail",
"Add to your library": "Add to your library",
"Follow": "Follow", "Follow": "Follow",
"Claim sequence must be a number.": "Claim sequence must be a number.", "Claim sequence must be a number.": "Claim sequence must be a number.",
"A title is required": "A title is required",
"Clearing": "Clearing", "Clearing": "Clearing",
"A deposit amount is required": "A deposit amount is required", "A deposit amount is required": "A deposit amount is required",
"Deposit cannot be 0": "Deposit cannot be 0", "Deposit cannot be 0": "Deposit cannot be 0",
@ -358,5 +427,34 @@
"Error message": "Error message", "Error message": "Error message",
"Error data": "Error data", "Error data": "Error data",
"Error": "Error", "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!"
} }

View file

@ -862,10 +862,10 @@
resolved "https://registry.yarnpkg.com/@lbry/color/-/color-1.1.1.tgz#b80ec25fb515d436129332b20c767c5a7014ac09" resolved "https://registry.yarnpkg.com/@lbry/color/-/color-1.1.1.tgz#b80ec25fb515d436129332b20c767c5a7014ac09"
integrity sha512-BdxqWmm84WYOd1ZsPruJiGr7WBEophVfJKg7oywFOAMb0h6ERe4Idx1ceweMSvCOyPlR5GAhil9Gvk70SBFdxQ== integrity sha512-BdxqWmm84WYOd1ZsPruJiGr7WBEophVfJKg7oywFOAMb0h6ERe4Idx1ceweMSvCOyPlR5GAhil9Gvk70SBFdxQ==
"@lbry/components@^2.7.2": "@lbry/components@^2.7.4":
version "2.7.2" version "2.7.4"
resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.2.tgz#a941ced2adf78ab52026f5533e5f6b3454f862a9" resolved "https://registry.yarnpkg.com/@lbry/components/-/components-2.7.4.tgz#3a633725137ffcb1f081f71dbf5397d66bf444cd"
integrity sha512-TSBmxc4i2E3v7qGC7LgdcrUUcpiUYBA1KMVSW5vrpDJroNy3fnr3/niGJTudVQ4LQzlOXpt7bsRATFh8vRmXnQ== integrity sha512-X0QBpdlsPntmuC/COolx/rOmUxa5XeHDjdcpl79Db3Y3upkEYwwtvl+S/OkkPbdxouoJfZTB4QqQuGkNi1xGdw==
"@mapbox/hast-util-table-cell-style@^0.1.3": "@mapbox/hast-util-table-cell-style@^0.1.3":
version "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" yargs "^13.2.2"
zstd-codec "^0.1.1" zstd-codec "^0.1.1"
lbry-redux@lbryio/lbry-redux#b3bf3f6d53410ff1c5415b51ca425341e364959f: lbry-redux@lbryio/lbry-redux#2930ad82a90ca91f6caf3761597ef9a67da7db66:
version "0.0.1" 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: dependencies:
proxy-polyfill "0.1.6" proxy-polyfill "0.1.6"
reselect "^3.0.0" reselect "^3.0.0"