navigation redesign
This commit is contained in:
parent
e7c1085faa
commit
a8711c027f
33 changed files with 499 additions and 219 deletions
|
@ -936,5 +936,12 @@
|
||||||
"Text copied": "Text copied",
|
"Text copied": "Text copied",
|
||||||
"Rewards Disabled": "Rewards Disabled",
|
"Rewards Disabled": "Rewards Disabled",
|
||||||
"Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.": "Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.",
|
"Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.": "Woah, you have a lot of friends! You've claimed the maximum amount of referral rewards. Check back soon to see if more are available!.",
|
||||||
"Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.": "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%."
|
"Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.": "Wallet servers are used to relay data to and from the LBRY blockchain. They also determine what content shows in trending or is blocked. %learn_more%.",
|
||||||
}
|
"Your Tags": "Your Tags",
|
||||||
|
"All Content": "All Content",
|
||||||
|
"Claim Your 5 LBC Invite Reward": "Claim Your 5 LBC Invite Reward",
|
||||||
|
"Accepted": "Accepted",
|
||||||
|
"Claimable": "Claimable",
|
||||||
|
"Invite A Friend": "Invite A Friend",
|
||||||
|
"Navigation": "Navigation"
|
||||||
|
}
|
|
@ -46,7 +46,7 @@ type LogPublishParams = {
|
||||||
let analyticsEnabled: boolean = true;
|
let analyticsEnabled: boolean = true;
|
||||||
const analytics: Analytics = {
|
const analytics: Analytics = {
|
||||||
error: message => {
|
error: message => {
|
||||||
if (analyticsEnabled) {
|
if (analyticsEnabled && isProduction) {
|
||||||
Lbryio.call('event', 'desktop_error', { error_message: message });
|
Lbryio.call('event', 'desktop_error', { error_message: message });
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -147,6 +147,13 @@ function App(props: Props) {
|
||||||
}
|
}
|
||||||
}, [previousRewardApproved, isRewardApproved]);
|
}, [previousRewardApproved, isRewardApproved]);
|
||||||
|
|
||||||
|
// @if TARGET='app'
|
||||||
|
useEffect(() => {
|
||||||
|
console.log('update prefs');
|
||||||
|
updatePreferences();
|
||||||
|
}, []);
|
||||||
|
// @endif
|
||||||
|
|
||||||
// Keep this at the end to ensure initial setup effects are run first
|
// Keep this at the end to ensure initial setup effects are run first
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Wait for balance to be populated on desktop so we know when we can begin syncing
|
// Wait for balance to be populated on desktop so we know when we can begin syncing
|
||||||
|
|
|
@ -7,14 +7,12 @@ import {
|
||||||
doToggleTagFollow,
|
doToggleTagFollow,
|
||||||
selectBlockedChannels,
|
selectBlockedChannels,
|
||||||
} from 'lbry-redux';
|
} from 'lbry-redux';
|
||||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
|
||||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||||
import ClaimListDiscover from './view';
|
import ClaimListDiscover from './view';
|
||||||
|
|
||||||
const select = state => ({
|
const select = state => ({
|
||||||
claimSearchByQuery: selectClaimSearchByQuery(state),
|
claimSearchByQuery: selectClaimSearchByQuery(state),
|
||||||
loading: selectFetchingClaimSearch(state),
|
loading: selectFetchingClaimSearch(state),
|
||||||
subscribedChannels: selectSubscriptions(state),
|
|
||||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||||
hiddenUris: selectBlockedChannels(state),
|
hiddenUris: selectBlockedChannels(state),
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import type { Node } from 'react';
|
import type { Node } from 'react';
|
||||||
import * as PAGES from 'constants/pages';
|
import classnames from 'classnames';
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
import React, { Fragment, useEffect, useState } from 'react';
|
||||||
import { withRouter } from 'react-router';
|
import { withRouter } from 'react-router';
|
||||||
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
|
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
|
||||||
|
@ -8,7 +8,6 @@ import { FormField } from 'component/common/form';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import ClaimList from 'component/claimList';
|
import ClaimList from 'component/claimList';
|
||||||
import Tag from 'component/tag';
|
|
||||||
import ClaimPreview from 'component/claimPreview';
|
import ClaimPreview from 'component/claimPreview';
|
||||||
import { toCapitalCase } from 'util/string';
|
import { toCapitalCase } from 'util/string';
|
||||||
import I18nMessage from 'component/i18nMessage';
|
import I18nMessage from 'component/i18nMessage';
|
||||||
|
@ -19,15 +18,12 @@ const TIME_WEEK = 'week';
|
||||||
const TIME_MONTH = 'month';
|
const TIME_MONTH = 'month';
|
||||||
const TIME_YEAR = 'year';
|
const TIME_YEAR = 'year';
|
||||||
const TIME_ALL = 'all';
|
const TIME_ALL = 'all';
|
||||||
const SEARCH_SORT_YOU = 'you';
|
|
||||||
const SEARCH_SORT_ALL = 'everyone';
|
|
||||||
const SEARCH_SORT_CHANNELS = 'channels';
|
|
||||||
|
|
||||||
const TYPE_TRENDING = 'trending';
|
export const TYPE_TRENDING = 'trending';
|
||||||
const TYPE_TOP = 'top';
|
export const TYPE_TOP = 'top';
|
||||||
const TYPE_NEW = 'new';
|
export const TYPE_NEW = 'new';
|
||||||
const SEARCH_FILTER_TYPES = [SEARCH_SORT_YOU, SEARCH_SORT_CHANNELS, SEARCH_SORT_ALL];
|
|
||||||
const SEARCH_TYPES = [TYPE_TRENDING, TYPE_TOP, TYPE_NEW];
|
const SEARCH_TYPES = [TYPE_TRENDING, TYPE_NEW, TYPE_TOP];
|
||||||
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 = {
|
||||||
|
@ -40,7 +36,6 @@ type Props = {
|
||||||
doToggleTagFollow: string => void,
|
doToggleTagFollow: string => void,
|
||||||
meta?: Node,
|
meta?: Node,
|
||||||
showNsfw: boolean,
|
showNsfw: boolean,
|
||||||
hideCustomization: boolean,
|
|
||||||
history: { action: string, push: string => void, replace: string => void },
|
history: { action: string, push: string => void, replace: string => void },
|
||||||
location: { search: string, pathname: string },
|
location: { search: string, pathname: string },
|
||||||
claimSearchByQuery: {
|
claimSearchByQuery: {
|
||||||
|
@ -48,6 +43,8 @@ type Props = {
|
||||||
},
|
},
|
||||||
hiddenUris: Array<string>,
|
hiddenUris: Array<string>,
|
||||||
hiddenNsfwMessage?: Node,
|
hiddenNsfwMessage?: Node,
|
||||||
|
channelIds?: Array<string>,
|
||||||
|
defaultTypeSort?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
function ClaimListDiscover(props: Props) {
|
function ClaimListDiscover(props: Props) {
|
||||||
|
@ -58,21 +55,20 @@ function ClaimListDiscover(props: Props) {
|
||||||
loading,
|
loading,
|
||||||
personalView,
|
personalView,
|
||||||
meta,
|
meta,
|
||||||
subscribedChannels,
|
channelIds,
|
||||||
showNsfw,
|
showNsfw,
|
||||||
history,
|
history,
|
||||||
location,
|
location,
|
||||||
hiddenUris,
|
hiddenUris,
|
||||||
hideCustomization,
|
|
||||||
hiddenNsfwMessage,
|
hiddenNsfwMessage,
|
||||||
|
defaultTypeSort,
|
||||||
} = props;
|
} = props;
|
||||||
const didNavigateForward = history.action === 'PUSH';
|
const didNavigateForward = history.action === 'PUSH';
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const { search } = location;
|
const { search } = location;
|
||||||
const [forceRefresh, setForceRefresh] = useState();
|
const [forceRefresh, setForceRefresh] = useState();
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const personalSort = urlParams.get('sort') || (hideCustomization ? SEARCH_SORT_ALL : SEARCH_SORT_YOU);
|
const typeSort = urlParams.get('type') || defaultTypeSort || TYPE_TRENDING;
|
||||||
const typeSort = urlParams.get('type') || TYPE_TRENDING;
|
|
||||||
const timeSort = urlParams.get('time') || TIME_WEEK;
|
const timeSort = urlParams.get('time') || TIME_WEEK;
|
||||||
const tagsInUrl = urlParams.get('t') || '';
|
const tagsInUrl = urlParams.get('t') || '';
|
||||||
const options: {
|
const options: {
|
||||||
|
@ -91,8 +87,8 @@ function ClaimListDiscover(props: Props) {
|
||||||
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
// no_totals makes it so the sdk doesn't have to calculate total number pages for pagination
|
||||||
// it's faster, but we will need to remove it if we start using total_pages
|
// it's faster, but we will need to remove it if we start using total_pages
|
||||||
no_totals: true,
|
no_totals: true,
|
||||||
any_tags: (personalView && !hideCustomization && personalSort === SEARCH_SORT_YOU) || !personalView ? tags : [],
|
any_tags: tags || [],
|
||||||
channel_ids: personalSort === SEARCH_SORT_CHANNELS ? subscribedChannels.map(sub => sub.uri.split('#')[1]) : [],
|
channel_ids: channelIds || [],
|
||||||
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
not_channel_ids: hiddenUris && hiddenUris.length ? hiddenUris.map(hiddenUri => hiddenUri.split('#')[1]) : [],
|
||||||
not_tags: !showNsfw ? MATURE_TAGS : [],
|
not_tags: !showNsfw ? MATURE_TAGS : [],
|
||||||
order_by:
|
order_by:
|
||||||
|
@ -110,41 +106,17 @@ function ClaimListDiscover(props: Props) {
|
||||||
.unix()
|
.unix()
|
||||||
)}`;
|
)}`;
|
||||||
}
|
}
|
||||||
const hasContent =
|
|
||||||
(personalSort === SEARCH_SORT_CHANNELS && subscribedChannels.length) ||
|
const hasMatureTags = tags && tags.some(t => MATURE_TAGS.includes(t));
|
||||||
(personalSort === SEARCH_SORT_YOU && !!tags.length) ||
|
|
||||||
personalSort === SEARCH_SORT_ALL;
|
|
||||||
const hasMatureTags = tags.some(t => MATURE_TAGS.includes(t));
|
|
||||||
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
const claimSearchCacheQuery = createNormalizedClaimSearchKey(options);
|
||||||
const uris = (hasContent && claimSearchByQuery[claimSearchCacheQuery]) || [];
|
const uris = claimSearchByQuery[claimSearchCacheQuery] || [];
|
||||||
const shouldPerformSearch =
|
const shouldPerformSearch =
|
||||||
hasContent &&
|
uris.length === 0 ||
|
||||||
(uris.length === 0 ||
|
didNavigateForward ||
|
||||||
didNavigateForward ||
|
(!loading && uris.length < PAGE_SIZE * page && uris.length % PAGE_SIZE === 0);
|
||||||
(!loading && uris.length < PAGE_SIZE * page && uris.length % PAGE_SIZE === 0));
|
|
||||||
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
|
// Don't use the query from createNormalizedClaimSearchKey for the effect since that doesn't include page & release_time
|
||||||
const optionsStringForEffect = JSON.stringify(options);
|
const optionsStringForEffect = JSON.stringify(options);
|
||||||
|
|
||||||
const noChannels = (
|
|
||||||
<div>
|
|
||||||
<p>
|
|
||||||
<I18nMessage>You're not following any channels.</I18nMessage>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<I18nMessage
|
|
||||||
tokens={{
|
|
||||||
trending: (
|
|
||||||
<Button button="link" label={__('trending for everyone')} navigate={'/?type=trending&sort=everyone'} />
|
|
||||||
),
|
|
||||||
discover: <Button button="link" label={__('discover some channels!')} navigate={'/$/following'} />,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Look what's %trending% or %discover%
|
|
||||||
</I18nMessage>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
const noResults = (
|
const noResults = (
|
||||||
<div>
|
<div>
|
||||||
<p>
|
<p>
|
||||||
|
@ -174,22 +146,6 @@ function ClaimListDiscover(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
const noTags = (
|
|
||||||
<p>
|
|
||||||
<I18nMessage
|
|
||||||
tokens={{
|
|
||||||
customize: <Button button="link" navigate={`/$/${PAGES.FOLLOWING}`} label={__('customize')} />,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
You're not following any tags. Add tags above or smash that %customize% button!
|
|
||||||
</I18nMessage>
|
|
||||||
</p>
|
|
||||||
);
|
|
||||||
|
|
||||||
const noFollowing =
|
|
||||||
(personalSort === SEARCH_SORT_YOU && noTags) || (personalSort === SEARCH_SORT_CHANNELS && noChannels);
|
|
||||||
const emptyState = !loading && !hasContent ? noFollowing : noResults;
|
|
||||||
|
|
||||||
function getSearch() {
|
function getSearch() {
|
||||||
let search = `?`;
|
let search = `?`;
|
||||||
if (!personalView) {
|
if (!personalView) {
|
||||||
|
@ -200,7 +156,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleTypeSort(newTypeSort) {
|
function handleTypeSort(newTypeSort) {
|
||||||
let url = `${getSearch()}type=${newTypeSort}&sort=${personalSort}`;
|
let url = `${getSearch()}type=${newTypeSort}`;
|
||||||
if (newTypeSort === TYPE_TOP) {
|
if (newTypeSort === TYPE_TOP) {
|
||||||
url += `&time=${timeSort}`;
|
url += `&time=${timeSort}`;
|
||||||
}
|
}
|
||||||
|
@ -209,14 +165,9 @@ function ClaimListDiscover(props: Props) {
|
||||||
history.push(url);
|
history.push(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePersonalSort(newPersonalSort) {
|
|
||||||
setPage(1);
|
|
||||||
history.push(`${getSearch()}type=${typeSort}&sort=${newPersonalSort}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleTimeSort(newTimeSort) {
|
function handleTimeSort(newTimeSort) {
|
||||||
setPage(1);
|
setPage(1);
|
||||||
history.push(`${getSearch()}type=${typeSort}&sort=${personalSort}&time=${newTimeSort}`);
|
history.push(`${getSearch()}type=${typeSort}&time=${newTimeSort}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleScrollBottom() {
|
function handleScrollBottom() {
|
||||||
|
@ -234,47 +185,19 @@ function ClaimListDiscover(props: Props) {
|
||||||
|
|
||||||
const header = (
|
const header = (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<FormField
|
{SEARCH_TYPES.map(type => (
|
||||||
className="claim-list__dropdown"
|
<Button
|
||||||
type="select"
|
key={type}
|
||||||
name="trending_sort"
|
button="alt"
|
||||||
value={typeSort}
|
onClick={() => handleTypeSort(type)}
|
||||||
onChange={e => handleTypeSort(e.target.value)}
|
className={classnames(`button-toggle button-toggle--${type}`, {
|
||||||
>
|
'button-toggle--active': typeSort === type,
|
||||||
{SEARCH_TYPES.map(type => (
|
})}
|
||||||
<option key={type} value={type}>
|
icon={toCapitalCase(type)}
|
||||||
{__(toCapitalCase(type))}
|
label={__(toCapitalCase(type))}
|
||||||
</option>
|
/>
|
||||||
))}
|
))}
|
||||||
</FormField>
|
|
||||||
{!hideCustomization && (
|
|
||||||
<Fragment>
|
|
||||||
<span className="claim-list__conjuction">{__('for')}</span>
|
|
||||||
{!personalView && tags && tags.length ? (
|
|
||||||
tags.map(tag => <Tag key={tag} name={tag} disabled type="large" />)
|
|
||||||
) : (
|
|
||||||
<FormField
|
|
||||||
type="select"
|
|
||||||
name="trending_overview"
|
|
||||||
className="claim-list__dropdown"
|
|
||||||
value={personalSort}
|
|
||||||
onChange={e => {
|
|
||||||
handlePersonalSort(e.target.value);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{SEARCH_FILTER_TYPES.map(type => (
|
|
||||||
<option key={type} value={type}>
|
|
||||||
{type === SEARCH_SORT_ALL
|
|
||||||
? __('Everyone')
|
|
||||||
: type === SEARCH_SORT_YOU
|
|
||||||
? __('Tags You Follow')
|
|
||||||
: __('Channels You Follow')}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</FormField>
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
{typeSort === 'top' && (
|
{typeSort === 'top' && (
|
||||||
<FormField
|
<FormField
|
||||||
className="claim-list__dropdown"
|
className="claim-list__dropdown"
|
||||||
|
@ -308,7 +231,7 @@ function ClaimListDiscover(props: Props) {
|
||||||
onScrollBottom={handleScrollBottom}
|
onScrollBottom={handleScrollBottom}
|
||||||
page={page}
|
page={page}
|
||||||
pageSize={PAGE_SIZE}
|
pageSize={PAGE_SIZE}
|
||||||
empty={emptyState}
|
empty={noResults}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
{loading && new Array(PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
||||||
|
|
|
@ -329,4 +329,37 @@ export const icons = {
|
||||||
<line x1="3" y1="18" x2="21" y2="18" />
|
<line x1="3" y1="18" x2="21" y2="18" />
|
||||||
</g>
|
</g>
|
||||||
),
|
),
|
||||||
|
[ICONS.DISCOVER]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polygon points="16.24 7.76 14.12 14.12 7.76 16.24 9.88 9.88 16.24 7.76" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
|
[ICONS.TRENDING]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
|
||||||
|
<polyline points="17 6 23 6 23 12" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
|
[ICONS.TOP]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<line x1="12" y1="20" x2="12" y2="10" />
|
||||||
|
<line x1="18" y1="20" x2="18" y2="4" />
|
||||||
|
<line x1="6" y1="20" x2="6" y2="16" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
|
[ICONS.NEW]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<polyline points="12 6 12 12 16 14" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
|
[ICONS.INVITE]: buildIcon(
|
||||||
|
<g>
|
||||||
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
||||||
|
<circle cx="9" cy="7" r="4" />
|
||||||
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
||||||
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
||||||
|
</g>
|
||||||
|
),
|
||||||
};
|
};
|
||||||
|
|
|
@ -127,33 +127,42 @@ const Header = (props: Props) => {
|
||||||
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
|
<div className={classnames('header__menu', { 'header__menu--with-balance': !IS_WEB || authenticated })}>
|
||||||
{(!IS_WEB || authenticated) && (
|
{(!IS_WEB || authenticated) && (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Menu>
|
<Button
|
||||||
<MenuButton className="header__navigation-item menu__title">{getWalletTitle()}</MenuButton>
|
navigate={`/$/${PAGES.WALLET}`}
|
||||||
<MenuList className="menu__list--header">
|
className="header__navigation-item menu__title header__navigation-item--balance"
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.WALLET}`)}>
|
label={getWalletTitle()}
|
||||||
<Icon aria-hidden icon={ICONS.WALLET} />
|
/>
|
||||||
{__('Wallet')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.FEATURED} />
|
|
||||||
{__('Rewards')}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuList>
|
|
||||||
</Menu>
|
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton className="header__navigation-item menu__title header__navigation-item--icon">
|
<MenuButton className="header__navigation-item menu__title header__navigation-item--icon">
|
||||||
<Icon size={18} icon={ICONS.ACCOUNT} />
|
<Icon size={18} icon={ICONS.ACCOUNT} />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList className="menu__list--header">
|
<MenuList className="menu__list--header">
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.ACCOUNT}`)}>
|
|
||||||
<Icon aria-hidden icon={ICONS.OVERVIEW} />
|
|
||||||
{__('Overview')}
|
|
||||||
</MenuItem>
|
|
||||||
|
|
||||||
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||||
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||||
{__('Publish')}
|
{__('New Publish')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISHED}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.PUBLISH} />
|
||||||
|
{__('Your Publishes')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
{/* @if TARGET='app' */}
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.PUBLISH}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.LIBRARY} />
|
||||||
|
{__('Your Library')}
|
||||||
|
</MenuItem>
|
||||||
|
{/* @endif */}
|
||||||
|
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.REWARDS}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.FEATURED} />
|
||||||
|
{__('Rewards')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
|
<MenuItem className="menu__link" onSelect={() => history.push(`/$/${PAGES.INVITE}`)}>
|
||||||
|
<Icon aria-hidden icon={ICONS.INVITE} />
|
||||||
|
{__('Invite A Friend')}
|
||||||
|
</MenuItem>
|
||||||
|
|
||||||
{authenticated ? (
|
{authenticated ? (
|
||||||
<MenuItem className="menu__link" onSelect={signOut}>
|
<MenuItem className="menu__link" onSelect={signOut}>
|
||||||
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
<Icon aria-hidden icon={ICONS.SIGN_OUT} />
|
||||||
|
|
|
@ -17,14 +17,14 @@ type Props = {
|
||||||
|
|
||||||
function Page(props: Props) {
|
function Page(props: Props) {
|
||||||
const { children, className, authPage = false, authenticated } = props;
|
const { children, className, authPage = false, authenticated } = props;
|
||||||
const obscureSideBar = IS_WEB ? !authenticated : false;
|
const obscureSideNavigation = IS_WEB ? !authenticated : false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Header authHeader={authPage} />
|
<Header authHeader={authPage} />
|
||||||
<div className={classnames('main-wrapper__inner')}>
|
<div className={classnames('main-wrapper__inner')}>
|
||||||
<main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main>
|
<main className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage })}>{children}</main>
|
||||||
{!authPage && <SideNavigation obscureSideBar={obscureSideBar} />}
|
{!authPage && <SideNavigation obscureSideNavigation={obscureSideNavigation} />}
|
||||||
</div>
|
</div>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,10 +5,10 @@ import { Route, Redirect, Switch, withRouter } from 'react-router-dom';
|
||||||
import SettingsPage from 'page/settings';
|
import SettingsPage from 'page/settings';
|
||||||
import HelpPage from 'page/help';
|
import HelpPage from 'page/help';
|
||||||
import ReportPage from 'page/report';
|
import ReportPage from 'page/report';
|
||||||
import AccountPage from 'page/account';
|
|
||||||
import ShowPage from 'page/show';
|
import ShowPage from 'page/show';
|
||||||
import PublishPage from 'page/publish';
|
import PublishPage from 'page/publish';
|
||||||
import DiscoverPage from 'page/discover';
|
import DiscoverPage from 'page/discover';
|
||||||
|
// import HomePage from 'page/home';
|
||||||
import RewardsPage from 'page/rewards';
|
import RewardsPage from 'page/rewards';
|
||||||
import FileListDownloaded from 'page/fileListDownloaded';
|
import FileListDownloaded from 'page/fileListDownloaded';
|
||||||
import FileListPublished from 'page/fileListPublished';
|
import FileListPublished from 'page/fileListPublished';
|
||||||
|
@ -18,6 +18,9 @@ import SearchPage from 'page/search';
|
||||||
import LibraryPage from 'page/library';
|
import LibraryPage from 'page/library';
|
||||||
import WalletPage from 'page/wallet';
|
import WalletPage from 'page/wallet';
|
||||||
import TagsPage from 'page/tags';
|
import TagsPage from 'page/tags';
|
||||||
|
import TagsFollowingPage from 'page/tagsFollowing';
|
||||||
|
import ChannelsFollowingPage from 'page/channelsFollowing';
|
||||||
|
import ChannelsFollowingManagePage from 'page/channelsFollowingManage';
|
||||||
import FollowingPage from 'page/following';
|
import FollowingPage from 'page/following';
|
||||||
import ListBlockedPage from 'page/listBlocked';
|
import ListBlockedPage from 'page/listBlocked';
|
||||||
import FourOhFourPage from 'page/fourOhFour';
|
import FourOhFourPage from 'page/fourOhFour';
|
||||||
|
@ -70,10 +73,13 @@ function AppRouter(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/" exact component={DiscoverPage} />
|
{/* <Route path={`/$/${PAGES.HOME}`} exact component={HomePage} /> */}
|
||||||
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
<Route path={`/$/${PAGES.DISCOVER}`} exact component={DiscoverPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
|
<Route path={`/$/${PAGES.AUTH}`} exact component={SignInPage} />
|
||||||
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
<Route path={`/$/${PAGES.TAGS}`} exact component={TagsPage} />
|
||||||
|
<Route path={`/$/${PAGES.TAGS_FOLLOWING}`} exact component={TagsFollowingPage} />
|
||||||
|
<Route path={`/$/${PAGES.CHANNELS_FOLLOWING}`} exact component={ChannelsFollowingPage} />
|
||||||
|
<Route path={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} exact component={ChannelsFollowingManagePage} />
|
||||||
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
<Route path={`/$/${PAGES.HELP}`} exact component={HelpPage} />
|
||||||
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
|
<Route path={`/$/${PAGES.AUTH_VERIFY}`} exact component={SignInVerifyPage} />
|
||||||
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
<Route path={`/$/${PAGES.SEARCH}`} exact component={SearchPage} />
|
||||||
|
@ -87,7 +93,6 @@ function AppRouter(props: Props) {
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.REWARDS}`} component={RewardsPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.TRANSACTIONS}`} component={TransactionHistoryPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.ACCOUNT}`} component={AccountPage} />
|
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.FOLLOWING}`} component={FollowingPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.BLOCKED}`} component={ListBlockedPage} />
|
||||||
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
<PrivateRoute {...props} path={`/$/${PAGES.WALLET}`} exact component={WalletPage} />
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { withRouter } from 'react-router';
|
||||||
import Button from 'component/button';
|
import Button from 'component/button';
|
||||||
import Tag from 'component/tag';
|
import Tag from 'component/tag';
|
||||||
import StickyBox from 'react-sticky-box/dist/esnext';
|
import StickyBox from 'react-sticky-box/dist/esnext';
|
||||||
|
@ -11,24 +12,27 @@ type Props = {
|
||||||
subscriptions: Array<Subscription>,
|
subscriptions: Array<Subscription>,
|
||||||
followedTags: Array<Tag>,
|
followedTags: Array<Tag>,
|
||||||
email: ?string,
|
email: ?string,
|
||||||
obscureSideBar: boolean,
|
obscureSideNavigation: boolean,
|
||||||
uploadCount: number,
|
uploadCount: number,
|
||||||
sticky: boolean,
|
sticky: boolean,
|
||||||
expanded: boolean,
|
expanded: boolean,
|
||||||
doSignOut: () => void,
|
doSignOut: () => void,
|
||||||
|
location: { pathname: string },
|
||||||
};
|
};
|
||||||
|
|
||||||
function SideBar(props: Props) {
|
function SideNavigation(props: Props) {
|
||||||
const {
|
const {
|
||||||
subscriptions,
|
subscriptions,
|
||||||
followedTags,
|
followedTags,
|
||||||
obscureSideBar,
|
obscureSideNavigation,
|
||||||
uploadCount,
|
uploadCount,
|
||||||
doSignOut,
|
doSignOut,
|
||||||
email,
|
email,
|
||||||
sticky = true,
|
sticky = true,
|
||||||
expanded = false,
|
expanded = false,
|
||||||
|
location,
|
||||||
} = props;
|
} = props;
|
||||||
|
const { pathname } = location;
|
||||||
const isAuthenticated = Boolean(email);
|
const isAuthenticated = Boolean(email);
|
||||||
|
|
||||||
function buildLink(path, label, icon, onClick) {
|
function buildLink(path, label, icon, onClick) {
|
||||||
|
@ -49,7 +53,7 @@ function SideBar(props: Props) {
|
||||||
<div>{children}</div>
|
<div>{children}</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
return obscureSideBar ? (
|
return obscureSideNavigation ? (
|
||||||
<Wrapper>
|
<Wrapper>
|
||||||
<div className="card navigation--placeholder">
|
<div className="card navigation--placeholder">
|
||||||
<div className="wrap">
|
<div className="wrap">
|
||||||
|
@ -64,29 +68,13 @@ function SideBar(props: Props) {
|
||||||
<ul className="navigation-links">
|
<ul className="navigation-links">
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
...buildLink(null, __('Home'), ICONS.HOME),
|
...buildLink(PAGES.CHANNELS_FOLLOWING, __('Following'), ICONS.SUBSCRIBE),
|
||||||
},
|
|
||||||
// @if TARGET='app'
|
|
||||||
{
|
|
||||||
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
|
||||||
},
|
|
||||||
// @endif
|
|
||||||
{
|
|
||||||
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
...buildLink(
|
...buildLink(PAGES.TAGS_FOLLOWING, __('Your Tags'), ICONS.TAG),
|
||||||
PAGES.PUBLISHED,
|
},
|
||||||
uploadCount ? (
|
{
|
||||||
<span>
|
...buildLink(PAGES.DISCOVER, __('All Content'), ICONS.DISCOVER),
|
||||||
{__('Publishes')}
|
|
||||||
<Spinner type="small" />
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
__('Publishes')
|
|
||||||
),
|
|
||||||
ICONS.PUBLISH
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
].map(linkProps => (
|
].map(linkProps => (
|
||||||
<li key={linkProps.navigate}>
|
<li key={linkProps.navigate}>
|
||||||
|
@ -96,6 +84,28 @@ function SideBar(props: Props) {
|
||||||
|
|
||||||
{expanded &&
|
{expanded &&
|
||||||
[
|
[
|
||||||
|
// @if TARGET='app'
|
||||||
|
{
|
||||||
|
...buildLink(PAGES.LIBRARY, __('Library'), ICONS.LIBRARY),
|
||||||
|
},
|
||||||
|
// @endif
|
||||||
|
{
|
||||||
|
...buildLink(PAGES.CHANNELS, __('Channels'), ICONS.CHANNEL),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
...buildLink(
|
||||||
|
PAGES.PUBLISHED,
|
||||||
|
uploadCount ? (
|
||||||
|
<span>
|
||||||
|
{__('Publishes')}
|
||||||
|
<Spinner type="small" />
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
__('Publishes')
|
||||||
|
),
|
||||||
|
ICONS.PUBLISH
|
||||||
|
),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
...buildLink(PAGES.WALLET, __('Wallet'), ICONS.WALLET),
|
...buildLink(PAGES.WALLET, __('Wallet'), ICONS.WALLET),
|
||||||
},
|
},
|
||||||
|
@ -126,27 +136,20 @@ function SideBar(props: Props) {
|
||||||
</li>
|
</li>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<li>
|
|
||||||
<Button
|
|
||||||
navigate={`/$/${PAGES.FOLLOWING}`}
|
|
||||||
label={__('Customize')}
|
|
||||||
icon={ICONS.EDIT}
|
|
||||||
className="navigation-link"
|
|
||||||
activeClass="navigation-link--active"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<section className="navigation-links__inline">
|
{pathname.includes(PAGES.TAGS_FOLLOWING) && (
|
||||||
<ul className="navigation-links--small tags--vertical">
|
<ul className="navigation__secondary navigation-links--small tags--vertical">
|
||||||
{followedTags.map(({ name }, key) => (
|
{followedTags.map(({ name }, key) => (
|
||||||
<li className="navigation-link__wrapper" key={name}>
|
<li className="navigation-link__wrapper" 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">
|
)}
|
||||||
|
|
||||||
|
{pathname.includes(PAGES.CHANNELS_FOLLOWING) && (
|
||||||
|
<ul className="navigation__secondary navigation-links--small">
|
||||||
{subscriptions.map(({ uri, channelName }, index) => (
|
{subscriptions.map(({ uri, channelName }, index) => (
|
||||||
<li key={uri} className="navigation-link__wrapper">
|
<li key={uri} className="navigation-link__wrapper">
|
||||||
<Button
|
<Button
|
||||||
|
@ -158,10 +161,10 @@ function SideBar(props: Props) {
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</Wrapper>
|
</Wrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default SideBar;
|
export default withRouter(SideNavigation);
|
||||||
|
|
|
@ -78,3 +78,6 @@ export const EYE = 'Eye';
|
||||||
export const EYE_OFF = 'EyeOff';
|
export const EYE_OFF = 'EyeOff';
|
||||||
export const SIGN_OUT = 'SignOut';
|
export const SIGN_OUT = 'SignOut';
|
||||||
export const SIGN_IN = 'SignIn';
|
export const SIGN_IN = 'SignIn';
|
||||||
|
export const TRENDING = 'Trending';
|
||||||
|
export const TOP = 'Top';
|
||||||
|
export const NEW = 'New';
|
||||||
|
|
|
@ -3,6 +3,7 @@ exports.AUTH_VERIFY = 'verify';
|
||||||
exports.BACKUP = 'backup';
|
exports.BACKUP = 'backup';
|
||||||
exports.CHANNEL = 'channel';
|
exports.CHANNEL = 'channel';
|
||||||
exports.DISCOVER = 'discover';
|
exports.DISCOVER = 'discover';
|
||||||
|
exports.HOME = 'home';
|
||||||
exports.DOWNLOADED = 'downloaded';
|
exports.DOWNLOADED = 'downloaded';
|
||||||
exports.HELP = 'help';
|
exports.HELP = 'help';
|
||||||
exports.LIBRARY = 'library';
|
exports.LIBRARY = 'library';
|
||||||
|
@ -16,10 +17,13 @@ exports.SEND = 'send';
|
||||||
exports.SETTINGS = 'settings';
|
exports.SETTINGS = 'settings';
|
||||||
exports.SHOW = 'show';
|
exports.SHOW = 'show';
|
||||||
exports.ACCOUNT = 'account';
|
exports.ACCOUNT = 'account';
|
||||||
exports.FOLLOWING = 'following';
|
|
||||||
exports.SEARCH = 'search';
|
exports.SEARCH = 'search';
|
||||||
exports.TRANSACTIONS = 'transactions';
|
exports.TRANSACTIONS = 'transactions';
|
||||||
exports.TAGS = 'tags';
|
exports.TAGS = 'tags';
|
||||||
|
exports.TAGS_FOLLOWING = 'following/tags';
|
||||||
|
exports.CHANNELS_FOLLOWING = 'following/channels';
|
||||||
|
exports.CHANNELS_FOLLOWING_MANAGE = 'following/channels/manage';
|
||||||
|
exports.FOLLOWING = 'following';
|
||||||
exports.WALLET = 'wallet';
|
exports.WALLET = 'wallet';
|
||||||
exports.BLOCKED = 'blocked';
|
exports.BLOCKED = 'blocked';
|
||||||
exports.CHANNELS = 'channels';
|
exports.CHANNELS = 'channels';
|
||||||
|
|
20
ui/page/channelsFollowing/index.js
Normal file
20
ui/page/channelsFollowing/index.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
|
import { selectSubscriptions, selectSuggestedChannels } from 'redux/selectors/subscriptions';
|
||||||
|
import { doFetchRecommendedSubscriptions } from 'redux/actions/subscriptions';
|
||||||
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
subscribedChannels: selectSubscriptions(state),
|
||||||
|
email: selectUserVerifiedEmail(state),
|
||||||
|
suggestedSubscriptions: selectSuggestedChannels(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = {
|
||||||
|
doFetchRecommendedSubscriptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(DiscoverPage);
|
56
ui/page/channelsFollowing/view.jsx
Normal file
56
ui/page/channelsFollowing/view.jsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import React from 'react';
|
||||||
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
|
import ClaimList from 'component/claimList';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
import { TYPE_NEW } from 'component/claimListDiscover/view';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
email: string,
|
||||||
|
subscribedChannels: Array<Subscription>,
|
||||||
|
doFetchRecommendedSubscriptions: () => void,
|
||||||
|
suggestedSubscriptions: Array<{ uri: string }>,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChannelsFollowing(props: Props) {
|
||||||
|
const { subscribedChannels, suggestedSubscriptions, doFetchRecommendedSubscriptions } = props;
|
||||||
|
const hasSubsribedChannels = subscribedChannels.length > 0;
|
||||||
|
const [showTab, setShowTab] = React.useState(!hasSubsribedChannels);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (!hasSubsribedChannels) {
|
||||||
|
doFetchRecommendedSubscriptions();
|
||||||
|
}
|
||||||
|
}, [doFetchRecommendedSubscriptions, hasSubsribedChannels]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
{showTab ? (
|
||||||
|
<div className="card">
|
||||||
|
<ClaimList
|
||||||
|
header={__('Find Channels To Follow')}
|
||||||
|
headerAltControls={
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
label={hasSubsribedChannels && __('View Your Feed')}
|
||||||
|
onClick={() => setShowTab(false)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
uris={suggestedSubscriptions.map(sub => `lbry://${sub.uri}`)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<ClaimListDiscover
|
||||||
|
defaultTypeSort={TYPE_NEW}
|
||||||
|
channelIds={subscribedChannels.map(sub => sub.uri.split('#')[1])}
|
||||||
|
meta={<Button button="link" label={__('Manage')} navigate={`/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`} />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelsFollowing;
|
16
ui/page/channelsFollowingManage/index.js
Normal file
16
ui/page/channelsFollowingManage/index.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectSubscriptions, selectSuggestedChannels } from 'redux/selectors/subscriptions';
|
||||||
|
import { doFetchRecommendedSubscriptions } from 'redux/actions/subscriptions';
|
||||||
|
import ChannelsFollowingManagePage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
subscribedChannels: selectSubscriptions(state),
|
||||||
|
suggestedSubscriptions: selectSuggestedChannels(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
{
|
||||||
|
doFetchRecommendedSubscriptions,
|
||||||
|
}
|
||||||
|
)(ChannelsFollowingManagePage);
|
57
ui/page/channelsFollowingManage/view.jsx
Normal file
57
ui/page/channelsFollowingManage/view.jsx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import ClaimList from 'component/claimList';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
subscribedChannels: Array<Subscription>,
|
||||||
|
location: { search: string },
|
||||||
|
history: { push: string => void },
|
||||||
|
doFetchRecommendedSubscriptions: () => void,
|
||||||
|
suggestedSubscriptions: Array<{ uri: string }>,
|
||||||
|
};
|
||||||
|
|
||||||
|
function ChannelsFollowingManagePage(props: Props) {
|
||||||
|
const { subscribedChannels, location, history, doFetchRecommendedSubscriptions, suggestedSubscriptions } = props;
|
||||||
|
const hasSubscriptions = !!subscribedChannels.length;
|
||||||
|
const channelUris = subscribedChannels.map(({ uri }) => uri);
|
||||||
|
|
||||||
|
const { search } = location;
|
||||||
|
const urlParams = new URLSearchParams(search);
|
||||||
|
const viewingSuggestedSubs = urlParams.get('view') || !hasSubscriptions;
|
||||||
|
|
||||||
|
function onClick() {
|
||||||
|
let url = `/$/${PAGES.CHANNELS_FOLLOWING_MANAGE}`;
|
||||||
|
if (!viewingSuggestedSubs) {
|
||||||
|
url += '?view=discover';
|
||||||
|
}
|
||||||
|
|
||||||
|
history.push(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
doFetchRecommendedSubscriptions();
|
||||||
|
}, [doFetchRecommendedSubscriptions]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
<div className="card">
|
||||||
|
<ClaimList
|
||||||
|
header={viewingSuggestedSubs ? __('Discover New Channels') : __('Channels You Follow')}
|
||||||
|
headerAltControls={
|
||||||
|
<Button
|
||||||
|
button="link"
|
||||||
|
label={viewingSuggestedSubs ? hasSubscriptions && __('View Your Channels') : __('Find New Channels')}
|
||||||
|
onClick={() => onClick()}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
uris={viewingSuggestedSubs ? suggestedSubscriptions.map(sub => `lbry://${sub.uri}`) : channelUris}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ChannelsFollowingManagePage;
|
|
@ -1,28 +1,12 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ClaimListDiscover from 'component/claimListDiscover';
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
import TagsSelect from 'component/tagsSelect';
|
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
import Button from 'component/button';
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
followedTags: Array<Tag>,
|
|
||||||
email: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
function DiscoverPage(props: Props) {
|
|
||||||
const { followedTags, email } = props;
|
|
||||||
|
|
||||||
|
function DiscoverPage() {
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
{(email || !IS_WEB) && <TagsSelect showClose title={__('Customize Your Homepage')} />}
|
<ClaimListDiscover />
|
||||||
<ClaimListDiscover
|
|
||||||
hideCustomization={IS_WEB && !email}
|
|
||||||
personalView
|
|
||||||
tags={followedTags.map(tag => tag.name)}
|
|
||||||
meta={<Button button="link" label={__('Customize')} requiresAuth={IS_WEB} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
|
||||||
/>
|
|
||||||
</Page>
|
</Page>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
18
ui/page/home/index.js
Normal file
18
ui/page/home/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectFollowedTags } from 'lbry-redux';
|
||||||
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
followedTags: selectFollowedTags(state),
|
||||||
|
subscribedChannels: selectSubscriptions(state),
|
||||||
|
email: selectUserVerifiedEmail(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = {};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(DiscoverPage);
|
22
ui/page/home/view.jsx
Normal file
22
ui/page/home/view.jsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Page from 'component/page';
|
||||||
|
// import Button from 'component/button';
|
||||||
|
|
||||||
|
// type Props = {};
|
||||||
|
|
||||||
|
function HomePage() {
|
||||||
|
// props: Props
|
||||||
|
// const {} = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
{/* TODO */}
|
||||||
|
{/* Recent From Following */}
|
||||||
|
{/* Trending for your tags */}
|
||||||
|
{/* Trending for everyone */}
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default HomePage;
|
18
ui/page/tagsFollowing/index.js
Normal file
18
ui/page/tagsFollowing/index.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
import { selectFollowedTags } from 'lbry-redux';
|
||||||
|
import { selectUserVerifiedEmail } from 'lbryinc';
|
||||||
|
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||||
|
import DiscoverPage from './view';
|
||||||
|
|
||||||
|
const select = state => ({
|
||||||
|
followedTags: selectFollowedTags(state),
|
||||||
|
subscribedChannels: selectSubscriptions(state),
|
||||||
|
email: selectUserVerifiedEmail(state),
|
||||||
|
});
|
||||||
|
|
||||||
|
const perform = {};
|
||||||
|
|
||||||
|
export default connect(
|
||||||
|
select,
|
||||||
|
perform
|
||||||
|
)(DiscoverPage);
|
30
ui/page/tagsFollowing/view.jsx
Normal file
30
ui/page/tagsFollowing/view.jsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// @flow
|
||||||
|
import * as PAGES from 'constants/pages';
|
||||||
|
import React from 'react';
|
||||||
|
import ClaimListDiscover from 'component/claimListDiscover';
|
||||||
|
import TagsSelect from 'component/tagsSelect';
|
||||||
|
import Page from 'component/page';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
followedTags: Array<Tag>,
|
||||||
|
email: string,
|
||||||
|
};
|
||||||
|
|
||||||
|
function DiscoverPage(props: Props) {
|
||||||
|
const { followedTags, email } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page>
|
||||||
|
{(email || !IS_WEB) && <TagsSelect showClose title={__('Find New Tags To Follow')} />}
|
||||||
|
<ClaimListDiscover
|
||||||
|
hideCustomization={IS_WEB && !email}
|
||||||
|
personalView
|
||||||
|
tags={followedTags.map(tag => tag.name)}
|
||||||
|
meta={<Button button="link" label={__('Customize')} requiresAuth={IS_WEB} navigate={`/$/${PAGES.FOLLOWING}`} />}
|
||||||
|
/>
|
||||||
|
</Page>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DiscoverPage;
|
|
@ -467,6 +467,7 @@ export function doGetAndPopulatePreferences() {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
doPreferenceGet('shared', successCb, failCb);
|
doPreferenceGet('shared', successCb, failCb);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,3 +64,42 @@ svg + .button__label,
|
||||||
.button__label + svg {
|
.button__label + svg {
|
||||||
margin-left: var(--spacing-small);
|
margin-left: var(--spacing-small);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-toggle {
|
||||||
|
padding: 0 var(--spacing-medium);
|
||||||
|
height: var(--height-button);
|
||||||
|
font-size: var(--font-base);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-left-width: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-of-type {
|
||||||
|
border-left-width: 1px;
|
||||||
|
border-top-left-radius: var(--border-radius);
|
||||||
|
border-bottom-left-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-top-right-radius: var(--border-radius);
|
||||||
|
border-bottom-right-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-toggle--active {
|
||||||
|
color: var(--color-primary);
|
||||||
|
background-color: var(--color-primary-alt);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: default;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
.claim-list__header {
|
.claim-list__header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
min-height: 4.5rem;
|
min-height: 6rem;
|
||||||
padding: var(--spacing-medium);
|
padding: var(--spacing-medium);
|
||||||
font-size: var(--font-body);
|
font-size: var(--font-body);
|
||||||
border-bottom: 1px solid var(--color-border);
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
@ -15,10 +15,6 @@
|
||||||
border-top-left-radius: var(--card-radius);
|
border-top-left-radius: var(--card-radius);
|
||||||
border-top-right-radius: var(--card-radius);
|
border-top-right-radius: var(--card-radius);
|
||||||
|
|
||||||
& > *:not(:last-child) {
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset-section {
|
fieldset-section {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
@ -34,6 +30,7 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding: 0 var(--spacing-medium);
|
padding: 0 var(--spacing-medium);
|
||||||
padding-right: var(--spacing-large);
|
padding-right: var(--spacing-large);
|
||||||
|
margin-left: var(--spacing-medium);
|
||||||
|
|
||||||
@media (max-width: $breakpoint-small) {
|
@media (max-width: $breakpoint-small) {
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
|
@ -42,6 +39,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.claim-list__header-title {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.claim-list__conjuction {
|
.claim-list__conjuction {
|
||||||
color: var(--color-text-subtitle);
|
color: var(--color-text-subtitle);
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
height: var(--height-input);
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__menu {
|
.header__menu {
|
||||||
|
@ -113,7 +114,18 @@
|
||||||
.header__navigation-item--back,
|
.header__navigation-item--back,
|
||||||
.header__navigation-item--forward,
|
.header__navigation-item--forward,
|
||||||
.header__navigation-item--icon {
|
.header__navigation-item--icon {
|
||||||
width: 3rem;
|
width: var(--height-button);
|
||||||
|
background-color: var(--color-header-button);
|
||||||
|
border-radius: 1.5rem;
|
||||||
|
margin-left: var(--spacing-small);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: var(--color-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-primary-alt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header__navigation-item--lbry {
|
.header__navigation-item--lbry {
|
||||||
|
@ -125,3 +137,7 @@
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header__navigation-item--balance {
|
||||||
|
margin: 0 var(--spacing-medium);
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
.modal {
|
.modal {
|
||||||
@extend .card;
|
@extend .card;
|
||||||
background-color: var(--color-card-background);
|
background-color: var(--color-modal-background);
|
||||||
line-height: 1.55;
|
line-height: 1.55;
|
||||||
min-width: 500px;
|
min-width: 500px;
|
||||||
max-width: var(--modal-width);
|
max-width: var(--modal-width);
|
||||||
|
|
|
@ -7,6 +7,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.navigation__secondary {
|
||||||
|
margin-top: var(--spacing-large);
|
||||||
|
}
|
||||||
|
|
||||||
.navigation--placeholder {
|
.navigation--placeholder {
|
||||||
@extend .navigation;
|
@extend .navigation;
|
||||||
padding: 2rem 1.5rem;
|
padding: 2rem 1.5rem;
|
||||||
|
|
|
@ -25,8 +25,6 @@
|
||||||
@extend .tags;
|
@extend .tags;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
margin: 0;
|
|
||||||
margin-top: var(--spacing-small);
|
|
||||||
|
|
||||||
li:last-child .tag {
|
li:last-child .tag {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
font-size: var(--font-small);
|
font-size: var(--font-small);
|
||||||
|
height: var(--height-input);
|
||||||
|
|
||||||
> .icon {
|
> .icon {
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
|
@ -18,10 +18,6 @@
|
||||||
outline: none;
|
outline: none;
|
||||||
background-color: var(--color-menu-background);
|
background-color: var(--color-menu-background);
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
|
||||||
&:focus {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-reach-menu-item] {
|
[data-reach-menu-item] {
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
// Generic html styles used across the App
|
// Generic html styles used across the App
|
||||||
// component specific styling should go in the component scss file
|
// component specific styling should go in the component scss file
|
||||||
|
|
||||||
|
.top-bid {
|
||||||
|
width: 100%;
|
||||||
|
height: 400px;
|
||||||
|
background-color: var(--color-primary-alt);
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
*::selection {
|
*::selection {
|
||||||
background-color: var(--color-text-selection-bg);
|
background-color: var(--color-text-selection-bg);
|
||||||
color: var(--color-text-selection);
|
color: var(--color-text-selection);
|
||||||
|
|
|
@ -5,11 +5,11 @@
|
||||||
/* #5a6570; - 25% */
|
/* #5a6570; - 25% */
|
||||||
|
|
||||||
[theme='dark'] {
|
[theme='dark'] {
|
||||||
--color-primary-alt: #30574e;
|
--color-primary-alt: #3e675d;
|
||||||
--color-primary: #60e1ba;
|
--color-primary: #74dfbf;
|
||||||
|
|
||||||
// Button
|
// Button
|
||||||
--color-link: #38d9a9;
|
--color-link: var(--color-primary);
|
||||||
--color-link-hover: #60e1ba;
|
--color-link-hover: #60e1ba;
|
||||||
--color-link-active: #60e1ba;
|
--color-link-active: #60e1ba;
|
||||||
--color-link-icon: #89939e;
|
--color-link-icon: #89939e;
|
||||||
|
@ -21,6 +21,7 @@
|
||||||
--color-button-secondary-bg: #395877;
|
--color-button-secondary-bg: #395877;
|
||||||
--color-button-secondary-bg-hover: #4b6d8f;
|
--color-button-secondary-bg-hover: #4b6d8f;
|
||||||
--color-button-secondary-text: #a3c1e0;
|
--color-button-secondary-text: #a3c1e0;
|
||||||
|
--color-header-button: var(--color-link-icon);
|
||||||
|
|
||||||
// Color
|
// Color
|
||||||
--color-focus: #2d69a5;
|
--color-focus: #2d69a5;
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
--color-tab-text: var(--color-white);
|
--color-tab-text: var(--color-white);
|
||||||
--color-tabs-background: #434b53;
|
--color-tabs-background: #434b53;
|
||||||
--color-tab-divider: var(--color-white);
|
--color-tab-divider: var(--color-white);
|
||||||
|
--color-modal-background: var(--color-header-background);
|
||||||
|
|
||||||
// Text
|
// Text
|
||||||
--color-text: #eeeeee;
|
--color-text: #eeeeee;
|
||||||
|
@ -49,7 +51,7 @@
|
||||||
--color-input: #f4f4f5;
|
--color-input: #f4f4f5;
|
||||||
--color-input-label: #d4d4d4;
|
--color-input-label: #d4d4d4;
|
||||||
--color-input-placeholder: #f4f4f5;
|
--color-input-placeholder: #f4f4f5;
|
||||||
--color-input-bg: #7d8894;
|
--color-input-bg: #5d6772;
|
||||||
--color-input-bg-copyable: #434b53;
|
--color-input-bg-copyable: #434b53;
|
||||||
--color-input-border: var(--color-border);
|
--color-input-border: var(--color-border);
|
||||||
--color-input-border-active: var(--color-secondary);
|
--color-input-border-active: var(--color-secondary);
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
:root {
|
:root {
|
||||||
// Button
|
// Button
|
||||||
--color-link-icon: var(--color-gray-5);
|
--color-link-icon: var(--color-gray-4);
|
||||||
--color-link-active: var(--color-primary);
|
--color-link-active: var(--color-primary);
|
||||||
--color-navigation-link: var(--color-link-icon);
|
--color-navigation-link: var(--color-gray-5);
|
||||||
|
--color-header-button: #f7f7f7;
|
||||||
|
|
||||||
// Color
|
// Color
|
||||||
--color-background: #f7f7f7;
|
--color-background: #f7f7f7;
|
||||||
|
@ -34,6 +35,7 @@
|
||||||
--color-file-viewer-background: var(--color-card-background);
|
--color-file-viewer-background: var(--color-card-background);
|
||||||
--color-tabs-background: var(--color-secondary-alt);
|
--color-tabs-background: var(--color-secondary-alt);
|
||||||
--color-tab-divider: var(--color-secondary);
|
--color-tab-divider: var(--color-secondary);
|
||||||
|
--color-modal-background: var(--color-card-background);
|
||||||
|
|
||||||
// Menu
|
// Menu
|
||||||
--color-menu-background: var(--color-header-background);
|
--color-menu-background: var(--color-header-background);
|
||||||
|
|
Loading…
Reference in a new issue