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:
parent
a3ef81cded
commit
5b2c901496
5 changed files with 58 additions and 31 deletions
|
@ -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.",
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue