new layout 🕺
This commit is contained in:
parent
02d2962004
commit
19fb7d7f06
37 changed files with 728 additions and 484 deletions
|
@ -70,7 +70,7 @@ type RowDataItem = {
|
|||
options?: {},
|
||||
};
|
||||
|
||||
export default function getHomePageRowData(
|
||||
export default function GetHomePageRowData(
|
||||
authenticated: boolean,
|
||||
showPersonalizedChannels: boolean,
|
||||
showPersonalizedTags: boolean,
|
||||
|
|
|
@ -8,6 +8,7 @@ import Spinner from 'component/spinner';
|
|||
import { FormField } from 'component/common/form';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import debounce from 'util/debounce';
|
||||
import ClaimPreviewTile from 'component/claimPreviewTile';
|
||||
|
||||
const DEBOUNCE_SCROLL_HANDLER_MS = 150;
|
||||
const SORT_NEW = 'new';
|
||||
|
@ -34,7 +35,7 @@ type Props = {
|
|||
hideBlock: boolean,
|
||||
injectedItem: ?Node,
|
||||
timedOutMessage?: Node,
|
||||
isCardBody?: boolean,
|
||||
tileLayout?: boolean,
|
||||
};
|
||||
|
||||
export default function ClaimList(props: Props) {
|
||||
|
@ -57,8 +58,9 @@ export default function ClaimList(props: Props) {
|
|||
hideBlock,
|
||||
injectedItem,
|
||||
timedOutMessage,
|
||||
isCardBody = false,
|
||||
tileLayout = false,
|
||||
} = props;
|
||||
|
||||
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
|
||||
const timedOut = uris === null;
|
||||
const urisLength = (uris && uris.length) || 0;
|
||||
|
@ -89,7 +91,11 @@ export default function ClaimList(props: Props) {
|
|||
}
|
||||
}, [loading, onScrollBottom, urisLength, pageSize, page]);
|
||||
|
||||
return (
|
||||
return tileLayout && !header ? (
|
||||
<section className="claim-grid">
|
||||
{urisLength > 0 && uris.map(uri => <ClaimPreviewTile key={uri} uri={uri} />)}
|
||||
</section>
|
||||
) : (
|
||||
<section
|
||||
className={classnames('claim-list', {
|
||||
'claim-list--small': type === 'small',
|
||||
|
@ -124,8 +130,8 @@ export default function ClaimList(props: Props) {
|
|||
{urisLength > 0 && (
|
||||
<ul
|
||||
className={classnames('ul--no-style', {
|
||||
card: !isCardBody,
|
||||
'claim-list--card-body': isCardBody,
|
||||
card: !tileLayout,
|
||||
'claim-list--card-body': tileLayout,
|
||||
})}
|
||||
>
|
||||
{sortedUris.map((uri, index) => (
|
||||
|
@ -154,6 +160,7 @@ export default function ClaimList(props: Props) {
|
|||
))}
|
||||
</ul>
|
||||
)}
|
||||
|
||||
{!timedOut && urisLength === 0 && !loading && (
|
||||
<div className="empty empty--centered">{empty || __('No results')}</div>
|
||||
)}
|
||||
|
|
|
@ -1,20 +1,16 @@
|
|||
// @flow
|
||||
import type { Node } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import React from 'react';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { withRouter } from 'react-router';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import { createNormalizedClaimSearchKey, MATURE_TAGS } from 'lbry-redux';
|
||||
import { FormField } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import moment from 'moment';
|
||||
import ClaimList from 'component/claimList';
|
||||
import ClaimPreview from 'component/claimPreview';
|
||||
import { toCapitalCase } from 'util/string';
|
||||
import I18nMessage from 'component/i18nMessage';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import Card from 'component/common/card';
|
||||
import ClaimListHeader from 'component/claimListHeader';
|
||||
|
||||
type Props = {
|
||||
uris: Array<string>,
|
||||
|
@ -58,6 +54,7 @@ type Props = {
|
|||
injectedItem: ?Node,
|
||||
infiniteScroll?: Boolean,
|
||||
feeAmount?: string,
|
||||
tileLayout: boolean,
|
||||
};
|
||||
|
||||
function ClaimListDiscover(props: Props) {
|
||||
|
@ -98,13 +95,12 @@ function ClaimListDiscover(props: Props) {
|
|||
injectedItem,
|
||||
feeAmount,
|
||||
uris,
|
||||
tileLayout,
|
||||
} = props;
|
||||
const didNavigateForward = history.action === 'PUSH';
|
||||
const { search } = location;
|
||||
|
||||
const [page, setPage] = useState(1);
|
||||
const [forceRefresh, setForceRefresh] = useState();
|
||||
const [expanded, setExpanded] = usePersistedState(`expanded-${location.pathname}`, false);
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [forceRefresh, setForceRefresh] = React.useState();
|
||||
const [orderParamEntry, setOrderParamEntry] = usePersistedState(`entry-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
const followed = (followedTags && followedTags.map(t => t.name)) || [];
|
||||
|
@ -123,22 +119,6 @@ function ClaimListDiscover(props: Props) {
|
|||
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
|
||||
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
|
||||
const feeAmountParam = urlParams.get('fee_amount') || feeAmount || CS.FEE_AMOUNT_ANY;
|
||||
const showDuration = !(claimType && claimType === CS.CLAIM_CHANNEL);
|
||||
const isFiltered = () =>
|
||||
Boolean(
|
||||
urlParams.get(CS.FRESH_KEY) ||
|
||||
urlParams.get(CS.CONTENT_KEY) ||
|
||||
urlParams.get(CS.DURATION_KEY) ||
|
||||
urlParams.get(CS.TAGS_KEY) ||
|
||||
urlParams.get(CS.FEE_AMOUNT_KEY)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (history.action !== 'POP' && isFiltered()) {
|
||||
setExpanded(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
let orderParam = orderBy || urlParams.get(CS.ORDER_BY_KEY) || defaultOrderBy;
|
||||
if (!orderParam) {
|
||||
|
@ -151,11 +131,11 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
setOrderParamUser(orderParam);
|
||||
}, [orderParam]);
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
// One-time update to stash the finalized 'orderParam' at entry.
|
||||
if (history.action !== 'POP') {
|
||||
setOrderParamEntry(orderParam);
|
||||
|
@ -303,7 +283,7 @@ function ClaimListDiscover(props: Props) {
|
|||
const claimSearchResult = claimSearchByQuery[claimSearchCacheQuery];
|
||||
const claimSearchResultLastPageReached = claimSearchByQueryLastPageReached[claimSearchCacheQuery];
|
||||
|
||||
const [prevOptions, setPrevOptions] = useState(null);
|
||||
const [prevOptions, setPrevOptions] = React.useState(null);
|
||||
|
||||
if (!isJustScrollingToNewPage(prevOptions, options)) {
|
||||
// --- New search, or search options changed.
|
||||
|
@ -385,21 +365,6 @@ function ClaimListDiscover(props: Props) {
|
|||
return JSON.stringify(tmpOptions) === JSON.stringify(tmpPrevOptions);
|
||||
}
|
||||
|
||||
function handleChange(change) {
|
||||
const url = buildUrl(change);
|
||||
setPage(1);
|
||||
history.push(url);
|
||||
}
|
||||
|
||||
function handleAdvancedReset() {
|
||||
const newUrlParams = new URLSearchParams(search);
|
||||
newUrlParams.delete('claim_type');
|
||||
newUrlParams.delete('channel_ids');
|
||||
const newSearch = `?${newUrlParams.toString()}`;
|
||||
|
||||
history.push(newSearch);
|
||||
}
|
||||
|
||||
function getParamFromTags(t) {
|
||||
if (t === CS.TAGS_ALL || t === CS.TAGS_FOLLOWED) {
|
||||
return t;
|
||||
|
@ -408,69 +373,6 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
function buildUrl(delta) {
|
||||
const newUrlParams = new URLSearchParams(location.search);
|
||||
CS.KEYS.forEach(k => {
|
||||
// $FlowFixMe append() can't take null as second arg, but get() can return null
|
||||
if (urlParams.get(k) !== null) newUrlParams.append(k, urlParams.get(k));
|
||||
});
|
||||
|
||||
switch (delta.key) {
|
||||
case CS.ORDER_BY_KEY:
|
||||
newUrlParams.set(CS.ORDER_BY_KEY, delta.value);
|
||||
break;
|
||||
case CS.FRESH_KEY:
|
||||
if (delta.value === defaultFreshness || delta.value === CS.FRESH_DEFAULT) {
|
||||
newUrlParams.delete(CS.FRESH_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.FRESH_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.CONTENT_KEY:
|
||||
if (delta.value === CS.CLAIM_CHANNEL || delta.value === CS.CLAIM_REPOST) {
|
||||
newUrlParams.delete(CS.DURATION_KEY);
|
||||
newUrlParams.set(CS.CONTENT_KEY, delta.value);
|
||||
} else if (delta.value === CS.CONTENT_ALL) {
|
||||
newUrlParams.delete(CS.CONTENT_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.CONTENT_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.DURATION_KEY:
|
||||
if (delta.value === CS.DURATION_ALL) {
|
||||
newUrlParams.delete(CS.DURATION_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.DURATION_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.TAGS_KEY:
|
||||
if (delta.value === CS.TAGS_ALL) {
|
||||
if (defaultTags === CS.TAGS_ALL) {
|
||||
newUrlParams.delete(CS.TAGS_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value);
|
||||
}
|
||||
} else if (delta.value === CS.TAGS_FOLLOWED) {
|
||||
if (defaultTags === CS.TAGS_FOLLOWED) {
|
||||
newUrlParams.delete(CS.TAGS_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value); // redundant but special
|
||||
}
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.FEE_AMOUNT_KEY:
|
||||
if (delta.value === CS.FEE_AMOUNT_ANY) {
|
||||
newUrlParams.delete(CS.FEE_AMOUNT_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.FEE_AMOUNT_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return `?${newUrlParams.toString()}`;
|
||||
}
|
||||
|
||||
function handleScrollBottom() {
|
||||
if (!loading && infiniteScroll) {
|
||||
if (claimSearchResult && !claimSearchResultLastPageReached) {
|
||||
|
@ -479,282 +381,84 @@ function ClaimListDiscover(props: Props) {
|
|||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
React.useEffect(() => {
|
||||
if (shouldPerformSearch) {
|
||||
const searchOptions = JSON.parse(optionsStringForEffect);
|
||||
doClaimSearch(searchOptions);
|
||||
}
|
||||
}, [doClaimSearch, shouldPerformSearch, optionsStringForEffect, forceRefresh]);
|
||||
|
||||
const defaultHeader = repostedClaimId ? null : (
|
||||
<Fragment>
|
||||
<div className={'claim-search__wrapper'}>
|
||||
<div className={'claim-search__top'}>
|
||||
<div className={'claim-search__top-row'}>
|
||||
{CS.ORDER_BY_TYPES.map(type => (
|
||||
<Button
|
||||
key={type}
|
||||
button="alt"
|
||||
onClick={e =>
|
||||
handleChange({
|
||||
key: CS.ORDER_BY_KEY,
|
||||
value: type,
|
||||
})
|
||||
}
|
||||
className={classnames(`button-toggle button-toggle--${type}`, {
|
||||
'button-toggle--active': orderParam === type,
|
||||
})}
|
||||
disabled={orderBy}
|
||||
icon={toCapitalCase(type)}
|
||||
label={__(toCapitalCase(type))}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div>
|
||||
{!hideFilter && (
|
||||
<Button
|
||||
button={'alt'}
|
||||
aria-label={__('More')}
|
||||
className={classnames(`button-toggle button-toggle--top button-toggle--more`, {
|
||||
'button-toggle--custom': isFiltered(),
|
||||
})}
|
||||
icon={ICONS.SLIDERS}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{expanded && (
|
||||
<>
|
||||
<div className={classnames('card--inline', `claim-search__menus`)}>
|
||||
{/* FRESHNESS FIELD */}
|
||||
{orderParam === CS.ORDER_BY_TOP && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': freshnessParam !== defaultFreshness,
|
||||
})}
|
||||
type="select"
|
||||
name="trending_time"
|
||||
label={__('How Fresh')}
|
||||
value={freshnessParam}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.FRESH_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.FRESH_TYPES.map(time => (
|
||||
<option key={time} value={time}>
|
||||
{/* i18fixme */}
|
||||
{time === CS.FRESH_DAY && __('Today')}
|
||||
{time !== CS.FRESH_ALL &&
|
||||
time !== CS.FRESH_DEFAULT &&
|
||||
time !== CS.FRESH_DAY &&
|
||||
__('This ' + toCapitalCase(time)) /* yes, concat before i18n, since it is read from const */}
|
||||
{time === CS.FRESH_ALL && __('All time')}
|
||||
{time === CS.FRESH_DEFAULT && __('Default')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CONTENT_TYPES FIELD */}
|
||||
{!claimType && (
|
||||
<div
|
||||
className={classnames('claim-search__input-container', {
|
||||
'claim-search__input-container--selected': contentTypeParam,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': contentTypeParam,
|
||||
})}
|
||||
type="select"
|
||||
name="claimType"
|
||||
label={__('Content Type')}
|
||||
value={contentTypeParam || CS.CONTENT_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.CONTENT_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.CONTENT_TYPES.map(type => {
|
||||
if (type !== CS.CLAIM_CHANNEL || (type === CS.CLAIM_CHANNEL && !channelIdsParam)) {
|
||||
return (
|
||||
<option key={type} value={type}>
|
||||
{/* i18fixme */}
|
||||
{type === CS.CLAIM_CHANNEL && __('Channel')}
|
||||
{type === CS.CLAIM_REPOST && __('Repost')}
|
||||
{type === CS.FILE_VIDEO && __('Video')}
|
||||
{type === CS.FILE_AUDIO && __('Audio')}
|
||||
{type === CS.FILE_IMAGE && __('Image')}
|
||||
{type === CS.FILE_MODEL && __('Model')}
|
||||
{type === CS.FILE_BINARY && __('Other')}
|
||||
{type === CS.FILE_DOCUMENT && __('Document')}
|
||||
{type === CS.CONTENT_ALL && __('Any')}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* DURATIONS FIELD */}
|
||||
{showDuration && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': durationParam,
|
||||
})}
|
||||
label={__('Duration')}
|
||||
type="select"
|
||||
name="duration"
|
||||
disabled={
|
||||
!(
|
||||
contentTypeParam === null ||
|
||||
streamTypeParam === CS.FILE_AUDIO ||
|
||||
streamTypeParam === CS.FILE_VIDEO
|
||||
)
|
||||
}
|
||||
value={durationParam || CS.DURATION_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.DURATION_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.DURATION_TYPES.map(dur => (
|
||||
<option key={dur} value={dur}>
|
||||
{/* i18fixme */}
|
||||
{dur === CS.DURATION_SHORT && __('Short')}
|
||||
{dur === CS.DURATION_LONG && __('Long')}
|
||||
{dur === CS.DURATION_ALL && __('Any')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAGS FIELD */}
|
||||
{!tags && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected':
|
||||
((!defaultTags || defaultTags === CS.TAGS_ALL) && tagsParam && tagsParam !== CS.TAGS_ALL) ||
|
||||
(defaultTags === CS.TAGS_FOLLOWED && tagsParam !== CS.TAGS_FOLLOWED),
|
||||
})}
|
||||
label={__('Tags')}
|
||||
type="select"
|
||||
name="tags"
|
||||
value={tagsParam || CS.TAGS_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.TAGS_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{[
|
||||
CS.TAGS_ALL,
|
||||
CS.TAGS_FOLLOWED,
|
||||
...followed,
|
||||
...(followed.includes(tagsParam) || tagsParam === CS.TAGS_ALL || tagsParam === CS.TAGS_FOLLOWED
|
||||
? []
|
||||
: [tagsParam]), // if they unfollow while filtered, add Other
|
||||
].map(tag => (
|
||||
<option
|
||||
key={tag}
|
||||
value={tag}
|
||||
className={classnames({
|
||||
'claim-search__input-special': !followed.includes(tag),
|
||||
})}
|
||||
>
|
||||
{followed.includes(tag) && typeof tag === 'string' && toCapitalCase(tag)}
|
||||
{tag === CS.TAGS_ALL && __('Any')}
|
||||
{tag === CS.TAGS_FOLLOWED && __('Following')}
|
||||
{!followed.includes(tag) && tag !== CS.TAGS_ALL && tag !== CS.TAGS_FOLLOWED && __('Other')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* PAID FIELD */}
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected':
|
||||
feeAmountParam === CS.FEE_AMOUNT_ONLY_FREE || feeAmountParam === CS.FEE_AMOUNT_ONLY_PAID,
|
||||
})}
|
||||
label={__('Price')}
|
||||
type="select"
|
||||
name="paidcontent"
|
||||
value={feeAmountParam}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.FEE_AMOUNT_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value={CS.FEE_AMOUNT_ANY}>{__('Anything')}</option>
|
||||
<option value={CS.FEE_AMOUNT_ONLY_FREE}>{__('Free')}</option>
|
||||
<option value={CS.FEE_AMOUNT_ONLY_PAID}>{__('Paid')}</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
{channelIdsInUrl && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<label>{__('Advanced Filters from URL')}</label>
|
||||
<Button button="alt" label={__('Clear')} onClick={handleAdvancedReset} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasMatureTags && hiddenNsfwMessage}
|
||||
</Fragment>
|
||||
const headerToUse = header || (
|
||||
<ClaimListHeader
|
||||
channelIds={channelIds}
|
||||
defaultTags={defaultTags}
|
||||
tags={tags}
|
||||
freshness={freshness}
|
||||
defaultFreshness={defaultFreshness}
|
||||
claimType={claimType}
|
||||
streamType={streamType}
|
||||
defaultStreamType={defaultStreamType}
|
||||
feeAmount={feeAmount}
|
||||
orderBy={orderBy}
|
||||
defaultOrderBy={defaultOrderBy}
|
||||
hideFilter={hideFilter}
|
||||
hasMatureTags={hasMatureTags}
|
||||
hiddenNsfwMessage={hiddenNsfwMessage}
|
||||
setPage={setPage}
|
||||
tileLayout={tileLayout}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
{headerLabel && <label className="claim-list__header-label">{headerLabel}</label>}
|
||||
<Card
|
||||
title={header || defaultHeader}
|
||||
titleActions={meta && <div className="card__actions--inline">{meta}</div>}
|
||||
isBodyList
|
||||
body={
|
||||
<>
|
||||
<ClaimList
|
||||
isCardBody
|
||||
id={claimSearchCacheQuery}
|
||||
loading={loading}
|
||||
uris={uris || claimSearchResult}
|
||||
onScrollBottom={handleScrollBottom}
|
||||
page={page}
|
||||
pageSize={CS.PAGE_SIZE}
|
||||
timedOutMessage={timedOutMessage}
|
||||
renderProperties={renderProperties}
|
||||
includeSupportAction={includeSupportAction}
|
||||
hideBlock={hideBlock}
|
||||
injectedItem={injectedItem}
|
||||
/>
|
||||
{loading &&
|
||||
new Array(pageSize || CS.PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
{tileLayout ? (
|
||||
<div>
|
||||
{!repostedClaimId && (
|
||||
<div className="section__header--actions">
|
||||
{headerToUse}
|
||||
{meta && <div className="card__actions--inline">{meta}</div>}
|
||||
</div>
|
||||
)}
|
||||
<ClaimList
|
||||
tileLayout
|
||||
id={claimSearchCacheQuery}
|
||||
loading={loading}
|
||||
uris={uris || claimSearchResult}
|
||||
onScrollBottom={handleScrollBottom}
|
||||
page={page}
|
||||
pageSize={CS.PAGE_SIZE}
|
||||
timedOutMessage={timedOutMessage}
|
||||
renderProperties={renderProperties}
|
||||
includeSupportAction={includeSupportAction}
|
||||
hideBlock={hideBlock}
|
||||
injectedItem={injectedItem}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="section__header--actions">
|
||||
{headerToUse}
|
||||
{meta && <div className="card__actions--inline">{meta}</div>}
|
||||
</div>
|
||||
|
||||
<ClaimList
|
||||
id={claimSearchCacheQuery}
|
||||
loading={loading}
|
||||
uris={uris || claimSearchResult}
|
||||
onScrollBottom={handleScrollBottom}
|
||||
page={page}
|
||||
pageSize={CS.PAGE_SIZE}
|
||||
timedOutMessage={timedOutMessage}
|
||||
renderProperties={renderProperties}
|
||||
includeSupportAction={includeSupportAction}
|
||||
hideBlock={hideBlock}
|
||||
injectedItem={injectedItem}
|
||||
/>
|
||||
{loading &&
|
||||
new Array(pageSize || CS.PAGE_SIZE).fill(1).map((x, i) => <ClaimPreview key={i} placeholder="loading" />)}
|
||||
</div>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
|
21
ui/component/claimListHeader/index.js
Normal file
21
ui/component/claimListHeader/index.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { selectFetchingClaimSearch, SETTINGS, selectFollowedTags } from 'lbry-redux';
|
||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import { doSetClientSetting, doSyncClientSettings } from 'redux/actions/settings';
|
||||
|
||||
import ClaimListDiscover from './view';
|
||||
|
||||
const select = state => ({
|
||||
followedTags: selectFollowedTags(state),
|
||||
loading: selectFetchingClaimSearch(state),
|
||||
showNsfw: makeSelectClientSetting(SETTINGS.SHOW_MATURE)(state),
|
||||
});
|
||||
|
||||
const perform = {
|
||||
doToggleTagFollowDesktop,
|
||||
doSetClientSetting,
|
||||
doSyncClientSettings,
|
||||
};
|
||||
|
||||
export default connect(select, perform)(ClaimListDiscover);
|
459
ui/component/claimListHeader/view.jsx
Normal file
459
ui/component/claimListHeader/view.jsx
Normal file
|
@ -0,0 +1,459 @@
|
|||
// @flow
|
||||
import { SIMPLE_SITE } from 'config';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import * as ICONS from 'constants/icons';
|
||||
import type { Node } from 'react';
|
||||
import classnames from 'classnames';
|
||||
import React from 'react';
|
||||
import usePersistedState from 'effects/use-persisted-state';
|
||||
import { useHistory } from 'react-router';
|
||||
import { SETTINGS } from 'lbry-redux';
|
||||
import { FormField } from 'component/common/form';
|
||||
import Button from 'component/button';
|
||||
import { toCapitalCase } from 'util/string';
|
||||
|
||||
type Props = {
|
||||
defaultTags: string,
|
||||
followedTags?: Array<Tag>,
|
||||
tags: string,
|
||||
freshness?: string,
|
||||
defaultFreshness?: string,
|
||||
claimType?: Array<string>,
|
||||
streamType?: string | Array<string>,
|
||||
defaultStreamType?: string | Array<string>,
|
||||
feeAmount: string,
|
||||
orderBy?: Array<string>,
|
||||
defaultOrderBy?: string,
|
||||
hideFilter: boolean,
|
||||
hasMatureTags: boolean,
|
||||
hiddenNsfwMessage?: Node,
|
||||
channelIds?: Array<string>,
|
||||
tileLayout: boolean,
|
||||
doSetClientSetting: (string, boolean) => void,
|
||||
setPage: number => void,
|
||||
doSyncClientSettings: () => void,
|
||||
};
|
||||
|
||||
function ClaimListHeader(props: Props) {
|
||||
const {
|
||||
defaultTags,
|
||||
followedTags,
|
||||
tags,
|
||||
freshness,
|
||||
defaultFreshness,
|
||||
claimType,
|
||||
streamType,
|
||||
defaultStreamType,
|
||||
feeAmount,
|
||||
orderBy,
|
||||
defaultOrderBy,
|
||||
hideFilter,
|
||||
hasMatureTags,
|
||||
hiddenNsfwMessage,
|
||||
channelIds,
|
||||
tileLayout,
|
||||
doSetClientSetting,
|
||||
doSyncClientSettings,
|
||||
setPage,
|
||||
} = props;
|
||||
const { action, push, location } = useHistory();
|
||||
const { search } = location;
|
||||
const [expanded, setExpanded] = usePersistedState(`expanded-${location.pathname}`, false);
|
||||
const [orderParamEntry, setOrderParamEntry] = usePersistedState(`entry-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
const [orderParamUser, setOrderParamUser] = usePersistedState(`orderUser-${location.pathname}`, CS.ORDER_BY_TRENDING);
|
||||
const followed = (followedTags && followedTags.map(t => t.name)) || [];
|
||||
const urlParams = new URLSearchParams(search);
|
||||
const tagsParam = // can be 'x,y,z' or 'x' or ['x','y'] or CS.CONSTANT
|
||||
(tags && getParamFromTags(tags)) ||
|
||||
(urlParams.get(CS.TAGS_KEY) !== null && urlParams.get(CS.TAGS_KEY)) ||
|
||||
(defaultTags && getParamFromTags(defaultTags));
|
||||
const freshnessParam = freshness || urlParams.get(CS.FRESH_KEY) || defaultFreshness;
|
||||
const contentTypeParam = urlParams.get(CS.CONTENT_KEY);
|
||||
const streamTypeParam =
|
||||
streamType || (CS.FILE_TYPES.includes(contentTypeParam) && contentTypeParam) || defaultStreamType || null;
|
||||
const durationParam = urlParams.get(CS.DURATION_KEY) || null;
|
||||
const channelIdsInUrl = urlParams.get(CS.CHANNEL_IDS_KEY);
|
||||
const channelIdsParam = channelIdsInUrl ? channelIdsInUrl.split(',') : channelIds;
|
||||
const feeAmountParam = urlParams.get('fee_amount') || feeAmount || CS.FEE_AMOUNT_ANY;
|
||||
const showDuration = !(claimType && claimType === CS.CLAIM_CHANNEL);
|
||||
const isFiltered = () =>
|
||||
Boolean(
|
||||
urlParams.get(CS.FRESH_KEY) ||
|
||||
urlParams.get(CS.CONTENT_KEY) ||
|
||||
urlParams.get(CS.DURATION_KEY) ||
|
||||
urlParams.get(CS.TAGS_KEY) ||
|
||||
urlParams.get(CS.FEE_AMOUNT_KEY)
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (action !== 'POP' && isFiltered()) {
|
||||
setExpanded(true);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
let orderParam = orderBy || urlParams.get(CS.ORDER_BY_KEY) || defaultOrderBy;
|
||||
if (!orderParam) {
|
||||
if (action === 'POP') {
|
||||
// Reaching here means user have popped back to the page's entry point (e.g. '/$/tags' without any '?order=').
|
||||
orderParam = orderParamEntry;
|
||||
} else {
|
||||
// This is the direct entry into the page, so we load the user's previous value.
|
||||
orderParam = orderParamUser;
|
||||
}
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
setOrderParamUser(orderParam);
|
||||
}, [orderParam]);
|
||||
|
||||
React.useEffect(() => {
|
||||
// One-time update to stash the finalized 'orderParam' at entry.
|
||||
if (action !== 'POP') {
|
||||
setOrderParamEntry(orderParam);
|
||||
}
|
||||
}, []);
|
||||
|
||||
function handleChange(change) {
|
||||
const url = buildUrl(change);
|
||||
setPage(1);
|
||||
push(url);
|
||||
}
|
||||
|
||||
function handleAdvancedReset() {
|
||||
const newUrlParams = new URLSearchParams(search);
|
||||
newUrlParams.delete('claim_type');
|
||||
newUrlParams.delete('channel_ids');
|
||||
const newSearch = `?${newUrlParams.toString()}`;
|
||||
|
||||
push(newSearch);
|
||||
}
|
||||
|
||||
function getParamFromTags(t) {
|
||||
if (t === CS.TAGS_ALL || t === CS.TAGS_FOLLOWED) {
|
||||
return t;
|
||||
} else if (Array.isArray(t)) {
|
||||
return t.join(',');
|
||||
}
|
||||
}
|
||||
|
||||
function buildUrl(delta) {
|
||||
const newUrlParams = new URLSearchParams(location.search);
|
||||
CS.KEYS.forEach(k => {
|
||||
// $FlowFixMe append() can't take null as second arg, but get() can return null
|
||||
if (urlParams.get(k) !== null) newUrlParams.append(k, urlParams.get(k));
|
||||
});
|
||||
|
||||
switch (delta.key) {
|
||||
case CS.ORDER_BY_KEY:
|
||||
newUrlParams.set(CS.ORDER_BY_KEY, delta.value);
|
||||
break;
|
||||
case CS.FRESH_KEY:
|
||||
if (delta.value === defaultFreshness || delta.value === CS.FRESH_DEFAULT) {
|
||||
newUrlParams.delete(CS.FRESH_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.FRESH_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.CONTENT_KEY:
|
||||
if (delta.value === CS.CLAIM_CHANNEL || delta.value === CS.CLAIM_REPOST) {
|
||||
newUrlParams.delete(CS.DURATION_KEY);
|
||||
newUrlParams.set(CS.CONTENT_KEY, delta.value);
|
||||
} else if (delta.value === CS.CONTENT_ALL) {
|
||||
newUrlParams.delete(CS.CONTENT_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.CONTENT_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.DURATION_KEY:
|
||||
if (delta.value === CS.DURATION_ALL) {
|
||||
newUrlParams.delete(CS.DURATION_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.DURATION_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.TAGS_KEY:
|
||||
if (delta.value === CS.TAGS_ALL) {
|
||||
if (defaultTags === CS.TAGS_ALL) {
|
||||
newUrlParams.delete(CS.TAGS_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value);
|
||||
}
|
||||
} else if (delta.value === CS.TAGS_FOLLOWED) {
|
||||
if (defaultTags === CS.TAGS_FOLLOWED) {
|
||||
newUrlParams.delete(CS.TAGS_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value); // redundant but special
|
||||
}
|
||||
} else {
|
||||
newUrlParams.set(CS.TAGS_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
case CS.FEE_AMOUNT_KEY:
|
||||
if (delta.value === CS.FEE_AMOUNT_ANY) {
|
||||
newUrlParams.delete(CS.FEE_AMOUNT_KEY);
|
||||
} else {
|
||||
newUrlParams.set(CS.FEE_AMOUNT_KEY, delta.value);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return `?${newUrlParams.toString()}`;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="claim-search__wrapper">
|
||||
<div className="claim-search__top">
|
||||
<div className="claim-search__top-row">
|
||||
{CS.ORDER_BY_TYPES.map(type => (
|
||||
<Button
|
||||
key={type}
|
||||
button="alt"
|
||||
onClick={e =>
|
||||
handleChange({
|
||||
key: CS.ORDER_BY_KEY,
|
||||
value: type,
|
||||
})
|
||||
}
|
||||
className={classnames(`button-toggle button-toggle--${type}`, {
|
||||
'button-toggle--active': orderParam === type,
|
||||
})}
|
||||
disabled={orderBy}
|
||||
icon={toCapitalCase(type)}
|
||||
label={__(toCapitalCase(type))}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
{!hideFilter && !SIMPLE_SITE && (
|
||||
<Button
|
||||
button="alt"
|
||||
aria-label={__('More')}
|
||||
className={classnames(`button-toggle button-toggle--top button-toggle--more`, {
|
||||
'button-toggle--custom': isFiltered(),
|
||||
})}
|
||||
icon={ICONS.SLIDERS}
|
||||
onClick={() => setExpanded(!expanded)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tileLayout !== undefined && (
|
||||
<Button
|
||||
onClick={() => {
|
||||
doSetClientSetting(SETTINGS.TILE_LAYOUT, !tileLayout);
|
||||
doSyncClientSettings();
|
||||
}}
|
||||
button="alt"
|
||||
className="button-toggle"
|
||||
aria-label={tileLayout ? __('Change to list layout') : __('Change to tile layout')}
|
||||
icon={ICONS.LAYOUT}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{expanded && !SIMPLE_SITE && (
|
||||
<>
|
||||
<div className={classnames('card--inline', `claim-search__menus`)}>
|
||||
{/* FRESHNESS FIELD */}
|
||||
{orderParam === CS.ORDER_BY_TOP && (
|
||||
<div className="claim-search__input-container">
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': freshnessParam !== defaultFreshness,
|
||||
})}
|
||||
type="select"
|
||||
name="trending_time"
|
||||
label={__('How Fresh')}
|
||||
value={freshnessParam}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.FRESH_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.FRESH_TYPES.map(time => (
|
||||
<option key={time} value={time}>
|
||||
{/* i18fixme */}
|
||||
{time === CS.FRESH_DAY && __('Today')}
|
||||
{time !== CS.FRESH_ALL &&
|
||||
time !== CS.FRESH_DEFAULT &&
|
||||
time !== CS.FRESH_DAY &&
|
||||
__('This ' + toCapitalCase(time)) /* yes, concat before i18n, since it is read from const */}
|
||||
{time === CS.FRESH_ALL && __('All time')}
|
||||
{time === CS.FRESH_DEFAULT && __('Default')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* CONTENT_TYPES FIELD */}
|
||||
{!claimType && (
|
||||
<div
|
||||
className={classnames('claim-search__input-container', {
|
||||
'claim-search__input-container--selected': contentTypeParam,
|
||||
})}
|
||||
>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': contentTypeParam,
|
||||
})}
|
||||
type="select"
|
||||
name="claimType"
|
||||
label={__('Content Type')}
|
||||
value={contentTypeParam || CS.CONTENT_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.CONTENT_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.CONTENT_TYPES.map(type => {
|
||||
if (type !== CS.CLAIM_CHANNEL || (type === CS.CLAIM_CHANNEL && !channelIdsParam)) {
|
||||
return (
|
||||
<option key={type} value={type}>
|
||||
{/* i18fixme */}
|
||||
{type === CS.CLAIM_CHANNEL && __('Channel')}
|
||||
{type === CS.CLAIM_REPOST && __('Repost')}
|
||||
{type === CS.FILE_VIDEO && __('Video')}
|
||||
{type === CS.FILE_AUDIO && __('Audio')}
|
||||
{type === CS.FILE_IMAGE && __('Image')}
|
||||
{type === CS.FILE_MODEL && __('Model')}
|
||||
{type === CS.FILE_BINARY && __('Other')}
|
||||
{type === CS.FILE_DOCUMENT && __('Document')}
|
||||
{type === CS.CONTENT_ALL && __('Any')}
|
||||
</option>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* DURATIONS FIELD */}
|
||||
{showDuration && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected': durationParam,
|
||||
})}
|
||||
label={__('Duration')}
|
||||
type="select"
|
||||
name="duration"
|
||||
disabled={
|
||||
!(
|
||||
contentTypeParam === null ||
|
||||
streamTypeParam === CS.FILE_AUDIO ||
|
||||
streamTypeParam === CS.FILE_VIDEO
|
||||
)
|
||||
}
|
||||
value={durationParam || CS.DURATION_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.DURATION_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{CS.DURATION_TYPES.map(dur => (
|
||||
<option key={dur} value={dur}>
|
||||
{/* i18fixme */}
|
||||
{dur === CS.DURATION_SHORT && __('Short')}
|
||||
{dur === CS.DURATION_LONG && __('Long')}
|
||||
{dur === CS.DURATION_ALL && __('Any')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* TAGS FIELD */}
|
||||
{!tags && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected':
|
||||
((!defaultTags || defaultTags === CS.TAGS_ALL) && tagsParam && tagsParam !== CS.TAGS_ALL) ||
|
||||
(defaultTags === CS.TAGS_FOLLOWED && tagsParam !== CS.TAGS_FOLLOWED),
|
||||
})}
|
||||
label={__('Tags')}
|
||||
type="select"
|
||||
name="tags"
|
||||
value={tagsParam || CS.TAGS_ALL}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.TAGS_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
{[
|
||||
CS.TAGS_ALL,
|
||||
CS.TAGS_FOLLOWED,
|
||||
...followed,
|
||||
...(followed.includes(tagsParam) || tagsParam === CS.TAGS_ALL || tagsParam === CS.TAGS_FOLLOWED
|
||||
? []
|
||||
: [tagsParam]), // if they unfollow while filtered, add Other
|
||||
].map(tag => (
|
||||
<option
|
||||
key={tag}
|
||||
value={tag}
|
||||
className={classnames({
|
||||
'claim-search__input-special': !followed.includes(tag),
|
||||
})}
|
||||
>
|
||||
{followed.includes(tag) && typeof tag === 'string' && toCapitalCase(tag)}
|
||||
{tag === CS.TAGS_ALL && __('Any')}
|
||||
{tag === CS.TAGS_FOLLOWED && __('Following')}
|
||||
{!followed.includes(tag) && tag !== CS.TAGS_ALL && tag !== CS.TAGS_FOLLOWED && __('Other')}
|
||||
</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* PAID FIELD */}
|
||||
<div className={'claim-search__input-container'}>
|
||||
<FormField
|
||||
className={classnames('claim-search__dropdown', {
|
||||
'claim-search__dropdown--selected':
|
||||
feeAmountParam === CS.FEE_AMOUNT_ONLY_FREE || feeAmountParam === CS.FEE_AMOUNT_ONLY_PAID,
|
||||
})}
|
||||
label={__('Price')}
|
||||
type="select"
|
||||
name="paidcontent"
|
||||
value={feeAmountParam}
|
||||
onChange={e =>
|
||||
handleChange({
|
||||
key: CS.FEE_AMOUNT_KEY,
|
||||
value: e.target.value,
|
||||
})
|
||||
}
|
||||
>
|
||||
<option value={CS.FEE_AMOUNT_ANY}>{__('Anything')}</option>
|
||||
<option value={CS.FEE_AMOUNT_ONLY_FREE}>{__('Free')}</option>
|
||||
<option value={CS.FEE_AMOUNT_ONLY_PAID}>{__('Paid')}</option>
|
||||
))}
|
||||
</FormField>
|
||||
</div>
|
||||
|
||||
{channelIdsInUrl && (
|
||||
<div className={'claim-search__input-container'}>
|
||||
<label>{__('Advanced Filters from URL')}</label>
|
||||
<Button button="alt" label={__('Clear')} onClick={handleAdvancedReset} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{hasMatureTags && hiddenNsfwMessage}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ClaimListHeader;
|
|
@ -714,4 +714,11 @@ export const icons = {
|
|||
<line x1="17.5" y1="15" x2="9" y2="15" />
|
||||
</g>
|
||||
),
|
||||
[ICONS.LAYOUT]: buildIcon(
|
||||
<g>
|
||||
<rect x="3" y="3" width="18" height="18" rx="2" ry="2" />
|
||||
<line x1="3" y1="9" x2="21" y2="9" />
|
||||
<line x1="9" y1="21" x2="9" y2="9" />
|
||||
</g>
|
||||
),
|
||||
};
|
||||
|
|
|
@ -14,7 +14,13 @@ type Props = {
|
|||
};
|
||||
|
||||
export default function NotificationHeaderButton(props: Props) {
|
||||
const { unreadCount, doReadNotifications, user } = props;
|
||||
const {
|
||||
unreadCount,
|
||||
// notifications,
|
||||
// fetching,
|
||||
doReadNotifications,
|
||||
user,
|
||||
} = props;
|
||||
const notificationsEnabled = user && user.experimental_ui;
|
||||
const { push } = useHistory();
|
||||
|
||||
|
|
|
@ -21,10 +21,11 @@ type Props = {
|
|||
isUpgradeAvailable: boolean,
|
||||
authPage: boolean,
|
||||
filePage: boolean,
|
||||
homePage: boolean,
|
||||
noHeader: boolean,
|
||||
noFooter: boolean,
|
||||
noSideNavigation: boolean,
|
||||
fullWidth: boolean,
|
||||
fullWidthPage: boolean,
|
||||
backout: {
|
||||
backLabel?: string,
|
||||
backNavDefault?: string,
|
||||
|
@ -37,12 +38,12 @@ function Page(props: Props) {
|
|||
const {
|
||||
children,
|
||||
className,
|
||||
authPage = false,
|
||||
filePage = false,
|
||||
authPage = false,
|
||||
fullWidthPage = false,
|
||||
noHeader = false,
|
||||
noFooter = false,
|
||||
noSideNavigation = false,
|
||||
|
||||
backout,
|
||||
} = props;
|
||||
const {
|
||||
|
@ -51,6 +52,7 @@ function Page(props: Props) {
|
|||
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', true);
|
||||
const isMediumScreen = useIsMediumScreen();
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
let isOnFilePage = false;
|
||||
try {
|
||||
const url = pathname.slice(1).replace(/:/g, '#');
|
||||
|
@ -89,7 +91,11 @@ function Page(props: Props) {
|
|||
/>
|
||||
)}
|
||||
<main
|
||||
className={classnames(MAIN_CLASS, className, { 'main--full-width': authPage, 'main--file-page': filePage })}
|
||||
className={classnames(MAIN_CLASS, className, {
|
||||
'main--full-width': fullWidthPage,
|
||||
'main--auth-page': authPage,
|
||||
'main--file-page': filePage,
|
||||
})}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
|
|
|
@ -112,3 +112,4 @@ export const OPEN_LOG = 'FilePlus';
|
|||
export const OPEN_LOG_FOLDER = 'Folder';
|
||||
export const LBRY_STATUS = 'BarChart';
|
||||
export const NOTIFICATION = 'Bell';
|
||||
export const LAYOUT = 'Layout';
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { useState, useEffect } from 'react';
|
||||
|
||||
// https://usehooks.com/useMedia/
|
||||
export default function useMedia(queries, values, defaultValue) {
|
||||
// Array containing a media query list for each query
|
||||
const mediaQueryLists = queries.map(q => window.matchMedia(q));
|
||||
|
||||
// Function that gets value based on matching media query
|
||||
const getValue = () => {
|
||||
// Get index of first media query that matches
|
||||
const index = mediaQueryLists.findIndex(mql => mql.matches);
|
||||
// Return related value or defaultValue if none
|
||||
return typeof values[index] !== 'undefined' ? values[index] : defaultValue;
|
||||
};
|
||||
|
||||
// State and setter for matched value
|
||||
const [value, setValue] = useState(getValue);
|
||||
|
||||
useEffect(
|
||||
() => {
|
||||
// Event listener callback
|
||||
// Note: By defining getValue outside of useEffect we ensure that it has ...
|
||||
// ... current values of hook args (as this hook callback is created once on mount).
|
||||
const handler = () => setValue(getValue);
|
||||
// Set a listener for each media query with above handler as callback.
|
||||
mediaQueryLists.forEach(mql => mql.addListener(handler));
|
||||
// Remove listeners on cleanup
|
||||
return () => mediaQueryLists.forEach(mql => mql.removeListener(handler));
|
||||
},
|
||||
[] // Empty array ensures effect is only run on mount and unmount
|
||||
);
|
||||
|
||||
return value;
|
||||
}
|
|
@ -1,11 +1,36 @@
|
|||
import useMedia from './use-media';
|
||||
// Widths are taken from "ui/scss/init/vars.scss"
|
||||
import React from 'react';
|
||||
|
||||
function useWindowSize() {
|
||||
const isWindowClient = typeof window === 'object';
|
||||
const [windowSize, setWindowSize] = React.useState(isWindowClient ? window.innerWidth : undefined);
|
||||
|
||||
React.useEffect(() => {
|
||||
function setSize() {
|
||||
setWindowSize(window.innerWidth);
|
||||
}
|
||||
|
||||
if (isWindowClient) {
|
||||
window.addEventListener('resize', setSize);
|
||||
|
||||
return () => window.removeEventListener('resize', setSize);
|
||||
}
|
||||
}, [isWindowClient, setWindowSize]);
|
||||
|
||||
return windowSize;
|
||||
}
|
||||
|
||||
export function useIsMobile() {
|
||||
const isMobile = useMedia(['(min-width: 901px)'], [false], true);
|
||||
return isMobile;
|
||||
const windowSize = useWindowSize();
|
||||
return windowSize < 901;
|
||||
}
|
||||
|
||||
export function useIsMediumScreen() {
|
||||
const isMobile = useMedia(['(min-width: 1151px)'], [false], true);
|
||||
return isMobile;
|
||||
const windowSize = useWindowSize();
|
||||
return windowSize < 1151;
|
||||
}
|
||||
|
||||
export function useIsLargeScreen() {
|
||||
const windowSize = useWindowSize();
|
||||
return windowSize > 1600;
|
||||
}
|
||||
|
|
26
ui/index.jsx
26
ui/index.jsx
|
@ -197,6 +197,18 @@ remote.getCurrentWindow().on('leave-full-screen', event => {
|
|||
document.webkitExitFullscreen();
|
||||
});
|
||||
|
||||
document.addEventListener('click', event => {
|
||||
let { target } = event;
|
||||
|
||||
while (target && target !== document) {
|
||||
if (target.matches('a[href^="http"]') || target.matches('a[href^="mailto"]')) {
|
||||
event.preventDefault();
|
||||
shell.openExternal(target.href);
|
||||
return;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
});
|
||||
// @endif
|
||||
|
||||
document.addEventListener('dragover', event => {
|
||||
|
@ -205,20 +217,6 @@ document.addEventListener('dragover', event => {
|
|||
document.addEventListener('drop', event => {
|
||||
event.preventDefault();
|
||||
});
|
||||
document.addEventListener('click', event => {
|
||||
let { target } = event;
|
||||
|
||||
while (target && target !== document) {
|
||||
if (target.matches('a[href^="http"]') || target.matches('a[href^="mailto"]')) {
|
||||
// @if TARGET='app'
|
||||
event.preventDefault();
|
||||
shell.openExternal(target.href);
|
||||
return;
|
||||
// @endif
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
});
|
||||
|
||||
function AppWrapper() {
|
||||
// Splash screen and sdk setup not needed on web
|
||||
|
|
|
@ -12,7 +12,7 @@ type Props = {
|
|||
function ChannelNew(props: Props) {
|
||||
const { history } = props;
|
||||
return (
|
||||
<Page noSideNavigation backout={{ title: __('Create Channel') }} className="main--auth-page">
|
||||
<Page noSideNavigation authPage backout={{ title: __('Create Channel') }}>
|
||||
<ChannelEdit onDone={() => history.push(`/$/${PAGES.CHANNELS}`)} />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -45,7 +45,7 @@ export default function ChannelsPage(props: Props) {
|
|||
</>
|
||||
}
|
||||
isBodyList
|
||||
body={<ClaimList isCardBody loading={fetchingChannels} uris={channelUrls} />}
|
||||
body={<ClaimList loading={fetchingChannels} uris={channelUrls} />}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { SETTINGS } from 'lbry-redux';
|
||||
import { selectSubscriptions } from 'redux/selectors/subscriptions';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
|
||||
import ChannelsFollowingPage from './view';
|
||||
|
||||
const select = state => ({
|
||||
subscribedChannels: selectSubscriptions(state),
|
||||
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
||||
});
|
||||
|
||||
export default connect(select)(ChannelsFollowingPage);
|
||||
|
|
|
@ -11,17 +11,19 @@ import Icon from 'component/common/icon';
|
|||
|
||||
type Props = {
|
||||
subscribedChannels: Array<Subscription>,
|
||||
tileLayout: boolean,
|
||||
};
|
||||
|
||||
function ChannelsFollowingPage(props: Props) {
|
||||
const { subscribedChannels } = props;
|
||||
const { subscribedChannels, tileLayout } = props;
|
||||
const hasSubsribedChannels = subscribedChannels.length > 0;
|
||||
|
||||
return !hasSubsribedChannels ? (
|
||||
<ChannelsFollowingDiscoverPage />
|
||||
) : (
|
||||
<Page noFooter>
|
||||
<Page noFooter fullWidthPage={tileLayout}>
|
||||
<ClaimListDiscover
|
||||
tileLayout={tileLayout}
|
||||
headerLabel={
|
||||
<span>
|
||||
<Icon icon={ICONS.SUBSCRIBE} size={10} />
|
||||
|
|
|
@ -7,7 +7,7 @@ import CreditCards from './credit-card-logos.png';
|
|||
|
||||
export default function CheckoutPage() {
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<Card
|
||||
title={__('Checkout')}
|
||||
subtitle={__('Your cart contains 1 item.')}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import * as CS from 'constants/claim_search';
|
||||
import { connect } from 'react-redux';
|
||||
import { makeSelectClaimForUri, selectFollowedTags, doResolveUri } from 'lbry-redux';
|
||||
import { makeSelectClaimForUri, selectFollowedTags, doResolveUri, SETTINGS } from 'lbry-redux';
|
||||
import { selectUserVerifiedEmail } from 'redux/selectors/user';
|
||||
import { doToggleTagFollowDesktop } from 'redux/actions/tags';
|
||||
import * as CS from 'constants/claim_search';
|
||||
import { makeSelectClientSetting } from 'redux/selectors/settings';
|
||||
import Tags from './view';
|
||||
|
||||
const select = (state, props) => {
|
||||
|
@ -15,6 +16,7 @@ const select = (state, props) => {
|
|||
repostedUri: repostedUri,
|
||||
repostedClaim: repostedUri ? makeSelectClaimForUri(repostedUri)(state) : null,
|
||||
isAuthenticated: selectUserVerifiedEmail(state),
|
||||
tileLayout: makeSelectClientSetting(SETTINGS.TILE_LAYOUT)(state),
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ type Props = {
|
|||
doToggleTagFollowDesktop: string => void,
|
||||
doResolveUri: string => void,
|
||||
isAuthenticated: boolean,
|
||||
tileLayout: boolean,
|
||||
};
|
||||
|
||||
function DiscoverPage(props: Props) {
|
||||
|
@ -32,6 +33,7 @@ function DiscoverPage(props: Props) {
|
|||
doToggleTagFollowDesktop,
|
||||
doResolveUri,
|
||||
isAuthenticated,
|
||||
tileLayout,
|
||||
} = props;
|
||||
const buttonRef = useRef();
|
||||
const isHovering = useHover(buttonRef);
|
||||
|
@ -88,8 +90,9 @@ function DiscoverPage(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Page noFooter>
|
||||
<Page noFooter fullWidthPage={tileLayout}>
|
||||
<ClaimListDiscover
|
||||
tileLayout={tileLayout}
|
||||
claimType={claimType ? [claimType] : undefined}
|
||||
headerLabel={headerLabel}
|
||||
tags={tags}
|
||||
|
|
|
@ -116,7 +116,6 @@ function FileListDownloaded(props: Props) {
|
|||
) : (
|
||||
<div>
|
||||
<ClaimList
|
||||
isCardBody
|
||||
renderProperties={() => null}
|
||||
empty={
|
||||
viewMode === VIEW_PURCHASES && !query ? (
|
||||
|
|
|
@ -85,7 +85,7 @@ function FileListPublished(props: Props) {
|
|||
isBodyList
|
||||
body={
|
||||
<div>
|
||||
<ClaimList isCardBody loading={fetching} persistedStorageKey="claim-list-published" uris={urls} />
|
||||
<ClaimList loading={fetching} persistedStorageKey="claim-list-published" uris={urls} />
|
||||
<Paginate totalPages={urlTotal > 0 ? Math.ceil(urlTotal / Number(pageSize)) : 1} />
|
||||
</div>
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ function HomePage(props: Props) {
|
|||
);
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<Page fullWidthPage>
|
||||
{(authenticated || !IS_WEB) && !subscribedChannels.length && (
|
||||
<div className="notice-message">
|
||||
<h1 className="section__title">
|
||||
|
|
|
@ -11,7 +11,7 @@ export default function ReferredPage(props: Props) {
|
|||
const { fullUri, referrer } = props;
|
||||
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<Invited fullUri={fullUri} referrer={referrer} />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -17,7 +17,7 @@ function ListBlocked(props: Props) {
|
|||
<Card
|
||||
isBodyList
|
||||
title={__('Your Blocked Channels')}
|
||||
body={<ClaimList isCardBody uris={uris} showUnresolvedClaims showHiddenByUser />}
|
||||
body={<ClaimList uris={uris} showUnresolvedClaims showHiddenByUser />}
|
||||
/>
|
||||
) : (
|
||||
<div className="main--empty">
|
||||
|
|
|
@ -5,7 +5,7 @@ import Page from 'component/page';
|
|||
|
||||
export default function PasswordResetPage() {
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<UserPasswordReset />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ import Page from 'component/page';
|
|||
|
||||
export default function PasswordSetPage() {
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<UserPasswordSet />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -5,7 +5,7 @@ import Page from 'component/page';
|
|||
|
||||
export default function SignInPage() {
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<UserSignIn />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -88,7 +88,7 @@ function SignInVerifyPage(props: Props) {
|
|||
}
|
||||
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<div className="main__sign-up">
|
||||
<Card
|
||||
title={isAuthenticationSuccess ? __('Sign In Success!') : __('Sign In to lbry.tv')}
|
||||
|
|
|
@ -5,7 +5,7 @@ import Page from 'component/page';
|
|||
|
||||
export default function SignUpPage() {
|
||||
return (
|
||||
<Page authPage className="main--auth-page">
|
||||
<Page authPage>
|
||||
<UserSignUp />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -8,9 +8,9 @@ import Button from 'component/button';
|
|||
import Icon from 'component/common/icon';
|
||||
import * as CS from 'constants/claim_search';
|
||||
|
||||
function DiscoverPage() {
|
||||
function TagsFollowingPage() {
|
||||
return (
|
||||
<Page noFooter>
|
||||
<Page noFooter fullWidthPage>
|
||||
<ClaimListDiscover
|
||||
headerLabel={
|
||||
<span>
|
||||
|
@ -34,4 +34,4 @@ function DiscoverPage() {
|
|||
);
|
||||
}
|
||||
|
||||
export default DiscoverPage;
|
||||
export default TagsFollowingPage;
|
||||
|
|
|
@ -5,7 +5,7 @@ import Page from 'component/page';
|
|||
|
||||
export default function Welcome() {
|
||||
return (
|
||||
<Page noHeader noSideNavigation className="main--auth-page">
|
||||
<Page noHeader noSideNavigation>
|
||||
<PrivacyAgreement />
|
||||
</Page>
|
||||
);
|
||||
|
|
|
@ -40,6 +40,7 @@ const defaultState = {
|
|||
[SETTINGS.HIDE_BALANCE]: false,
|
||||
[SETTINGS.OS_NOTIFICATIONS_ENABLED]: true,
|
||||
[SETTINGS.AUTOMATIC_DARK_MODE_ENABLED]: false,
|
||||
[SETTINGS.TILE_LAYOUT]: true,
|
||||
|
||||
[SETTINGS.DARK_MODE_TIMES]: {
|
||||
from: { hour: '21', min: '00', formattedTime: '21:00' },
|
||||
|
|
|
@ -323,34 +323,45 @@
|
|||
}
|
||||
|
||||
.claim-preview--tile {
|
||||
$width: calc((100% - var(--spacing-m) * 3) / 4);
|
||||
width: $width;
|
||||
@include handleClaimTileGifThumbnail($width);
|
||||
|
||||
margin-bottom: var(--spacing-l);
|
||||
margin-right: 0;
|
||||
margin-top: 0;
|
||||
margin-left: var(--spacing-m);
|
||||
justify-content: flex-start;
|
||||
|
||||
@media (min-width: $breakpoint-medium) {
|
||||
&:first-child,
|
||||
&:nth-child(4n + 1) {
|
||||
margin-left: 0;
|
||||
}
|
||||
.media__thumb {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.media__thumb {
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
@media (min-width: $breakpoint-large) {
|
||||
$width: calc((100% - var(--spacing-m) * 5) / 6);
|
||||
width: $width;
|
||||
@include handleClaimTileGifThumbnail($width);
|
||||
|
||||
&:first-child,
|
||||
&:nth-child(6n + 1) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-large) and (min-width: $breakpoint-medium) {
|
||||
$width: calc((100% - var(--spacing-m) * 3) / 4);
|
||||
width: $width;
|
||||
@include handleClaimTileGifThumbnail($width);
|
||||
|
||||
&:first-child,
|
||||
&:nth-child(4n + 1) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: $breakpoint-medium) and (min-width: $breakpoint-small) {
|
||||
$width: calc((100vw - var(--side-nav-width--micro) - (var(--spacing-l) * 3)) / 3);
|
||||
$width: calc((100vw - var(--side-nav-width--micro) - var(--spacing-l) * 3) / 3);
|
||||
width: $width;
|
||||
@include handleClaimTileGifThumbnail($width);
|
||||
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
|
||||
.claim-search__top > div {
|
||||
@media (max-width: $breakpoint-small) {
|
||||
margin: var(--spacing-xxs) 0;
|
||||
margin-bottom: var(--spacing-xxs);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -95,7 +95,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.main--full-width {
|
||||
@extend .main;
|
||||
|
||||
@media (min-width: $breakpoint-large) {
|
||||
max-width: none;
|
||||
width: 100%;
|
||||
padding: 0 var(--spacing-l);
|
||||
}
|
||||
}
|
||||
|
||||
.main--auth-page {
|
||||
width: 100%;
|
||||
max-width: 70rem;
|
||||
margin-top: var(--spacing-main-padding);
|
||||
margin-left: auto;
|
||||
|
@ -160,10 +171,6 @@
|
|||
}
|
||||
}
|
||||
|
||||
.main--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main__sign-in,
|
||||
.main__sign-up {
|
||||
max-width: 27rem;
|
||||
|
|
|
@ -18,6 +18,13 @@
|
|||
margin-bottom: var(--spacing-l);
|
||||
}
|
||||
|
||||
.section__header--actions {
|
||||
margin-bottom: var(--spacing-m);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.section__flex {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
|
|
|
@ -4,6 +4,7 @@ import * as CS from 'constants/claim_search';
|
|||
import { parseURI } from 'lbry-redux';
|
||||
import moment from 'moment';
|
||||
import { toCapitalCase } from 'util/string';
|
||||
import { useIsLargeScreen } from 'effects/use-screensize';
|
||||
|
||||
type RowDataItem = {
|
||||
title: string,
|
||||
|
@ -12,7 +13,7 @@ type RowDataItem = {
|
|||
options?: {},
|
||||
};
|
||||
|
||||
export default function getHomePageRowData(
|
||||
export default function GetHomePageRowData(
|
||||
authenticated: boolean,
|
||||
showPersonalizedChannels: boolean,
|
||||
showPersonalizedTags: boolean,
|
||||
|
@ -20,6 +21,12 @@ export default function getHomePageRowData(
|
|||
followedTags: Array<Tag>,
|
||||
showIndividualTags: boolean
|
||||
) {
|
||||
const isLargeScreen = useIsLargeScreen();
|
||||
|
||||
function getPageSize(originalSize) {
|
||||
return isLargeScreen ? originalSize * (3 / 2) : originalSize;
|
||||
}
|
||||
|
||||
let rowData: Array<RowDataItem> = [];
|
||||
const individualTagDataItems: Array<RowDataItem> = [];
|
||||
const YOUTUBER_CHANNEL_IDS = [
|
||||
|
@ -114,7 +121,7 @@ export default function getHomePageRowData(
|
|||
options: {
|
||||
claimType: ['stream'],
|
||||
orderBy: ['release_time'],
|
||||
pageSize: 12,
|
||||
pageSize: getPageSize(12),
|
||||
channelIds: YOUTUBER_CHANNEL_IDS,
|
||||
limitClaimsPerChannel: 1,
|
||||
releaseTime: `>${Math.floor(
|
||||
|
@ -160,7 +167,7 @@ export default function getHomePageRowData(
|
|||
.startOf('week')
|
||||
.unix()
|
||||
)}`,
|
||||
pageSize: subscribedChannels.length > 3 ? (subscribedChannels.length > 6 ? 16 : 8) : 4,
|
||||
pageSize: getPageSize(subscribedChannels.length > 3 ? (subscribedChannels.length > 6 ? 16 : 8) : 4),
|
||||
channelIds: subscribedChannels.map((subscription: Subscription) => {
|
||||
const { channelClaimId } = parseURI(subscription.uri);
|
||||
return channelClaimId;
|
||||
|
@ -172,7 +179,7 @@ export default function getHomePageRowData(
|
|||
title: __('Top Content from Today'),
|
||||
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TOP}&${CS.FRESH_KEY}=${CS.FRESH_DAY}`,
|
||||
options: {
|
||||
pageSize: showPersonalizedChannels || showPersonalizedTags ? 4 : 8,
|
||||
pageSize: getPageSize(showPersonalizedChannels || showPersonalizedTags ? 4 : 8),
|
||||
orderBy: ['effective_amount'],
|
||||
claimType: ['stream'],
|
||||
limitClaimsPerChannel: 2,
|
||||
|
@ -198,7 +205,7 @@ export default function getHomePageRowData(
|
|||
title: __('Trending Classics'),
|
||||
link: `/$/${PAGES.DISCOVER}?${CS.ORDER_BY_KEY}=${CS.ORDER_BY_TRENDING}&${CS.FRESH_KEY}=${CS.FRESH_WEEK}`,
|
||||
options: {
|
||||
pageSize: 4,
|
||||
pageSize: getPageSize(4),
|
||||
claimType: ['stream'],
|
||||
limitClaimsPerChannel: 1,
|
||||
releaseTime: `<${Math.floor(
|
||||
|
@ -222,6 +229,7 @@ export default function getHomePageRowData(
|
|||
title: __('Trending For Your Tags'),
|
||||
link: `/$/${PAGES.TAGS_FOLLOWING}`,
|
||||
options: {
|
||||
pageSize: getPageSize(4),
|
||||
tags: followedTags.map(tag => tag.name),
|
||||
claimType: ['stream'],
|
||||
limitClaimsPerChannel: 2,
|
||||
|
@ -233,7 +241,7 @@ export default function getHomePageRowData(
|
|||
link: `/@lbry:3f`,
|
||||
options: {
|
||||
orderBy: ['release_time'],
|
||||
pageSize: 4,
|
||||
pageSize: getPageSize(4),
|
||||
channelIds: ['3fda836a92faaceedfe398225fb9b2ee2ed1f01a'],
|
||||
},
|
||||
};
|
||||
|
@ -243,7 +251,7 @@ export default function getHomePageRowData(
|
|||
link: `/@lbrycast:4`,
|
||||
options: {
|
||||
orderBy: ['release_time'],
|
||||
pageSize: 4,
|
||||
pageSize: getPageSize(4),
|
||||
channelIds: ['4c29f8b013adea4d5cca1861fb2161d5089613ea'],
|
||||
},
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue