List Page: use swipe layout for mobile (#1069)

* Add swipe layout support for Collection Tiles

* Lists: use swipe layout for mobile

Ticket: 950 "playlists page - right now we show watch later on top, and if you have stuff here, you have to scroll down to other playlists. Show a selector on top? Whatever other improvements we can make here to improve UX."
This commit is contained in:
infinite-persistence 2022-03-11 03:53:49 -08:00 committed by GitHub
parent a3ef81cded
commit 5b2c901496
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 58 additions and 31 deletions

View file

@ -2122,6 +2122,7 @@
"Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8", "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8": "Enter the full channel name or URL to search.\n\nExamples:\n - @channel\n - @channel#3\n - https://odysee.com/@Odysee:8\n - lbry://@Odysee#8",
"Choose %asset%": "Choose %asset%", "Choose %asset%": "Choose %asset%",
"Showing %filtered% results of %total%": "Showing %filtered% results of %total%", "Showing %filtered% results of %total%": "Showing %filtered% results of %total%",
"filtered": "filtered",
"Failed to synchronize settings. Wait a while before retrying.": "Failed to synchronize settings. Wait a while before retrying.", "Failed to synchronize settings. Wait a while before retrying.": "Failed to synchronize settings. Wait a while before retrying.",
"You are offline. Check your internet connection.": "You are offline. Check your internet connection.", "You are offline. Check your internet connection.": "You are offline. Check your internet connection.",
"A new version of Odysee is available.": "A new version of Odysee is available.", "A new version of Odysee is available.": "A new version of Odysee is available.",

View file

@ -152,7 +152,11 @@ function ClaimPreviewTile(props: Props) {
if (placeholder || (!claim && isResolvingUri)) { if (placeholder || (!claim && isResolvingUri)) {
return ( return (
<li className={classnames('placeholder claim-preview--tile', {})}> <li
className={classnames('placeholder claim-preview--tile', {
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
})}
>
<div className="media__thumb"> <div className="media__thumb">
<img src={PlaceholderTx} alt="Placeholder" /> <img src={PlaceholderTx} alt="Placeholder" />
</div> </div>

View file

@ -33,8 +33,8 @@ function CollectionPreviewOverlay(props: Props) {
collectionItemUrls.map((item, index) => { collectionItemUrls.map((item, index) => {
if (index < 2) { if (index < 2) {
return ( return (
<div className="collection-preview__overlay-grid-items"> <div className="collection-preview__overlay-grid-items" key={item}>
<FileThumbnail uri={item} key={item} /> <FileThumbnail uri={item} />
</div> </div>
); );
} }

View file

@ -25,18 +25,12 @@ type Props = {
thumbnail?: string, thumbnail?: string,
title?: string, title?: string,
placeholder: boolean, placeholder: boolean,
blackListedOutpoints: Array<{ swipeLayout?: boolean,
txid: string, blackListedOutpoints: Array<{ txid: string, nout: number }>,
nout: number, filteredOutpoints: Array<{ txid: string, nout: number }>,
}>,
filteredOutpoints: Array<{
txid: string,
nout: number,
}>,
blockedChannelUris: Array<string>, blockedChannelUris: Array<string>,
isMature?: boolean, isMature?: boolean,
showMature: boolean, showMature: boolean,
collectionId: string,
deleteCollection: (string) => void, deleteCollection: (string) => void,
resolveCollectionItems: (any) => void, resolveCollectionItems: (any) => void,
isResolvingCollectionClaims: boolean, isResolvingCollectionClaims: boolean,
@ -53,6 +47,7 @@ function CollectionPreviewTile(props: Props) {
collectionItemUrls, collectionItemUrls,
claim, claim,
resolveCollectionItems, resolveCollectionItems,
swipeLayout = false,
} = props; } = props;
const { push } = useHistory(); const { push } = useHistory();
@ -120,7 +115,11 @@ function CollectionPreviewTile(props: Props) {
if (isResolvingUri || isResolvingCollectionClaims) { if (isResolvingUri || isResolvingCollectionClaims) {
return ( return (
<li className={classnames('claim-preview--tile', {})}> <li
className={classnames('claim-preview--tile', {
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
})}
>
<div className="placeholder media__thumb" /> <div className="placeholder media__thumb" />
<div className="placeholder__wrapper"> <div className="placeholder__wrapper">
<div className="placeholder claim-tile__title" /> <div className="placeholder claim-tile__title" />
@ -129,12 +128,19 @@ function CollectionPreviewTile(props: Props) {
</li> </li>
); );
} }
if (uri) { if (uri) {
return <ClaimPreviewTile uri={uri} />; return <ClaimPreviewTile swipeLayout={swipeLayout} uri={uri} />;
} }
return ( return (
<li role="link" onClick={handleClick} className={'card claim-preview--tile'}> <li
role="link"
onClick={handleClick}
className={classnames('card claim-preview--tile', {
'swipe-list__item claim-preview--horizontal-tile': swipeLayout,
})}
>
<NavLink {...navLinkProps}> <NavLink {...navLinkProps}>
<FileThumbnail uri={collectionItemUrls && collectionItemUrls.length && collectionItemUrls[0]}> <FileThumbnail uri={collectionItemUrls && collectionItemUrls.length && collectionItemUrls[0]}>
<React.Fragment> <React.Fragment>

View file

@ -8,10 +8,10 @@ import Icon from 'component/common/icon';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import * as KEYCODES from 'constants/keycodes'; import * as KEYCODES from 'constants/keycodes';
import Yrbl from 'component/yrbl'; import Yrbl from 'component/yrbl';
import classnames from 'classnames'; import classnames from 'classnames';
import { FormField, Form } from 'component/common/form'; import { FormField, Form } from 'component/common/form';
import { useIsMobile } from 'effects/use-screensize';
type Props = { type Props = {
builtinCollections: CollectionGroup, builtinCollections: CollectionGroup,
@ -21,10 +21,8 @@ type Props = {
fetchingCollections: boolean, fetchingCollections: boolean,
}; };
const ALL = 'All'; const LIST_TYPE = Object.freeze({ ALL: 'All', PRIVATE: 'Private', PUBLIC: 'Public' });
const PRIVATE = 'Private'; const COLLECTION_FILTERS = [LIST_TYPE.ALL, LIST_TYPE.PRIVATE, LIST_TYPE.PUBLIC];
const PUBLIC = 'Public';
const COLLECTION_FILTERS = [ALL, PRIVATE, PUBLIC];
const COLLECTION_SHOW_COUNT = 12; const COLLECTION_SHOW_COUNT = 12;
export default function CollectionsListMine(props: Props) { export default function CollectionsListMine(props: Props) {
@ -40,16 +38,17 @@ export default function CollectionsListMine(props: Props) {
const unpublishedCollectionsList = (Object.keys(unpublishedCollections || {}): any); const unpublishedCollectionsList = (Object.keys(unpublishedCollections || {}): any);
const publishedList = (Object.keys(publishedCollections || {}): any); const publishedList = (Object.keys(publishedCollections || {}): any);
const hasCollections = unpublishedCollectionsList.length || publishedList.length; const hasCollections = unpublishedCollectionsList.length || publishedList.length;
const [filterType, setFilterType] = React.useState(ALL); const [filterType, setFilterType] = React.useState(LIST_TYPE.ALL);
const [searchText, setSearchText] = React.useState(''); const [searchText, setSearchText] = React.useState('');
const isMobileScreen = useIsMobile();
const playlistPageUrl = `/$/${PAGES.PLAYLISTS}?type=${filterType}`; const playlistPageUrl = `/$/${PAGES.PLAYLISTS}?type=${filterType}`;
let collectionsToShow = []; let collectionsToShow = [];
if (filterType === ALL) { if (filterType === LIST_TYPE.ALL) {
collectionsToShow = unpublishedCollectionsList.concat(publishedList); collectionsToShow = unpublishedCollectionsList.concat(publishedList);
} else if (filterType === PRIVATE) { } else if (filterType === LIST_TYPE.PRIVATE) {
collectionsToShow = unpublishedCollectionsList; collectionsToShow = unpublishedCollectionsList;
} else if (filterType === PUBLIC) { } else if (filterType === LIST_TYPE.PUBLIC) {
collectionsToShow = publishedList; collectionsToShow = publishedList;
} }
@ -92,6 +91,7 @@ export default function CollectionsListMine(props: Props) {
} }
return ( return (
<> <>
{/* Built-in lists */}
{builtin.map((list: Collection) => { {builtin.map((list: Collection) => {
const { items: itemUrls } = list; const { items: itemUrls } = list;
return ( return (
@ -122,7 +122,13 @@ export default function CollectionsListMine(props: Props) {
} }
/> />
</h1> </h1>
<ClaimList tileLayout key={list.name} uris={itemUrls.slice(0, 6)} collectionId={list.id} /> <ClaimList
swipeLayout={isMobileScreen}
tileLayout
key={list.name}
uris={itemUrls.slice(0, 6)}
collectionId={list.id}
/>
</> </>
)} )}
{!(itemUrls && itemUrls.length) && ( {!(itemUrls && itemUrls.length) && (
@ -135,6 +141,8 @@ export default function CollectionsListMine(props: Props) {
</div> </div>
); );
})} })}
{/* Playlists: header */}
<div className="claim-grid__wrapper"> <div className="claim-grid__wrapper">
<div className="claim-grid__header section"> <div className="claim-grid__header section">
<h1 className="claim-grid__title"> <h1 className="claim-grid__title">
@ -159,6 +167,8 @@ export default function CollectionsListMine(props: Props) {
)} )}
</h1> </h1>
</div> </div>
{/* Playlists: search */}
<div className="section__header-action-stack"> <div className="section__header-action-stack">
<div className="section__header--actions"> <div className="section__header--actions">
<div className="claim-search__wrapper"> <div className="claim-search__wrapper">
@ -179,6 +189,7 @@ export default function CollectionsListMine(props: Props) {
<Form onSubmit={() => {}} className="wunderbar--inline"> <Form onSubmit={() => {}} className="wunderbar--inline">
<Icon icon={ICONS.SEARCH} /> <Icon icon={ICONS.SEARCH} />
<FormField <FormField
name="collection_search"
onFocus={onTextareaFocus} onFocus={onTextareaFocus}
onBlur={onTextareaBlur} onBlur={onTextareaBlur}
className="wunderbar__input--inline" className="wunderbar__input--inline"
@ -192,10 +203,7 @@ export default function CollectionsListMine(props: Props) {
<p className="collection-grid__results-summary"> <p className="collection-grid__results-summary">
{isTruncated && ( {isTruncated && (
<> <>
{__(`Showing %filtered% results of %total%`, { {__('Showing %filtered% results of %total%', { filtered: filteredLength, total: totalLength })}
filtered: filteredLength,
total: totalLength,
})}
{`${searchText ? ' (' + __('filtered') + ') ' : ' '}`} {`${searchText ? ' (' + __('filtered') + ') ' : ' '}`}
</> </>
)} )}
@ -206,12 +214,20 @@ export default function CollectionsListMine(props: Props) {
/> />
</p> </p>
</div> </div>
{/* Playlists: tiles */}
{Boolean(hasCollections) && ( {Boolean(hasCollections) && (
<div> <div>
<div className="claim-grid"> <div
className={classnames('claim-grid', {
'swipe-list': isMobileScreen,
})}
>
{filteredCollections && {filteredCollections &&
filteredCollections.length > 0 && filteredCollections.length > 0 &&
filteredCollections.map((key) => <CollectionPreviewTile tileLayout collectionId={key} key={key} />)} filteredCollections.map((key) => (
<CollectionPreviewTile swipeLayout={isMobileScreen} tileLayout collectionId={key} key={key} />
))}
{!filteredCollections.length && <div className="empty main--empty">{__('No matching playlists')}</div>} {!filteredCollections.length && <div className="empty main--empty">{__('No matching playlists')}</div>}
</div> </div>
</div> </div>