From a26305d75a70bc9915ab8024601b7ae3c79592df Mon Sep 17 00:00:00 2001 From: Dan Peterson Date: Thu, 6 Jan 2022 15:13:26 -0600 Subject: [PATCH 1/3] Add horizontal layout (#636) * Test out a horizontal scroll for upcoming (tile only for now) * - add support for list layout - add following label on home page - clan up css and naming conventions * Update header type + show only if scheduled streams are showing --- ui/component/claimList/view.jsx | 68 +++++++++++------- ui/component/claimListDiscover/view.jsx | 38 +++++++++- ui/component/claimPreview/view.jsx | 14 ++-- ui/component/claimPreviewTile/view.jsx | 3 + ui/component/router/view.jsx | 5 +- ui/page/home/view.jsx | 39 +++++++---- ui/scss/all.scss | 2 + ui/scss/component/_claim-list.scss | 8 ++- ui/scss/component/_swipe-list.scss | 13 ++++ ui/scss/component/_utils.scss | 92 +++++++++++++++++++++++++ ui/util/buildHomepage.js | 4 +- ui/util/claim.js | 23 +++++++ 12 files changed, 260 insertions(+), 49 deletions(-) create mode 100644 ui/scss/component/_swipe-list.scss create mode 100644 ui/scss/component/_utils.scss diff --git a/ui/component/claimList/view.jsx b/ui/component/claimList/view.jsx index aeac02b0a..5f3a2f8c1 100644 --- a/ui/component/claimList/view.jsx +++ b/ui/component/claimList/view.jsx @@ -44,7 +44,10 @@ type Props = { collectionId?: string, showNoSourceClaims?: boolean, onClick?: (e: any, claim?: ?Claim, index?: number) => void, - noEmpty: boolean, + maxClaimRender?: number, + excludeUris?: Array, + loadedCallback?: (number) => void, + swipeLayout: boolean, }; export default function ClaimList(props: Props) { @@ -75,7 +78,10 @@ export default function ClaimList(props: Props) { collectionId, showNoSourceClaims, onClick, - noEmpty, + maxClaimRender, + excludeUris = [], + loadedCallback, + swipeLayout = false, } = props; const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); @@ -85,8 +91,18 @@ export default function ClaimList(props: Props) { const timedOut = uris === null; const urisLength = (uris && uris.length) || 0; - const tileUris = (prefixUris || []).concat(uris); - const sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? tileUris : tileUris.slice().reverse())) || []; + let tileUris = (prefixUris || []).concat(uris || []); + tileUris = tileUris.filter((uri) => !excludeUris.includes(uri)); + + const totalLength = tileUris.length; + + if (maxClaimRender) tileUris = tileUris.slice(0, maxClaimRender); + + let sortedUris = (urisLength > 0 && (currentSort === SORT_NEW ? tileUris : tileUris.slice().reverse())) || []; + + React.useEffect(() => { + if (typeof loadedCallback === 'function') loadedCallback(totalLength); + }, [totalLength]); // eslint-disable-line react-hooks/exhaustive-deps const noResultMsg = searchInLanguage ? __('No results. Contents may be hidden by the Language filter.') @@ -96,11 +112,21 @@ export default function ClaimList(props: Props) { setCurrentSort(currentSort === SORT_NEW ? SORT_OLD : SORT_NEW); } - function handleClaimClicked(e, claim, index) { - if (onClick) { - onClick(e, claim, index); - } - } + const handleClaimClicked = React.useCallback( + (e, claim, index) => { + if (onClick) { + onClick(e, claim, index); + } + }, + [onClick] + ); + + const customShouldHide = React.useCallback((claim: StreamClaim) => { + // Hack to hide spee.ch thumbnail publishes + // If it meets these requirements, it was probably uploaded here: + // https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79 + return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch'; + }, []); useEffect(() => { const handleScroll = debounce((e) => { @@ -124,7 +150,7 @@ export default function ClaimList(props: Props) { }, [loading, onScrollBottom, urisLength, pageSize, page]); return tileLayout && !header ? ( -
+
{urisLength > 0 && tileUris.map((uri) => ( ))} - {!timedOut && urisLength === 0 && !loading && !noEmpty && ( -
{empty || noResultMsg}
- )} + {!timedOut && urisLength === 0 && !loading &&
{empty || noResultMsg}
} {timedOut && timedOutMessage &&
{timedOutMessage}
}
) : ( @@ -178,8 +203,9 @@ export default function ClaimList(props: Props) { {urisLength > 0 && (
    {sortedUris.map((uri, index) => ( @@ -199,22 +225,16 @@ export default function ClaimList(props: Props) { showHiddenByUser={showHiddenByUser} collectionId={collectionId} showNoSourceClaims={showNoSourceClaims} - customShouldHide={(claim: StreamClaim) => { - // Hack to hide spee.ch thumbnail publishes - // If it meets these requirements, it was probably uploaded here: - // https://github.com/lbryio/lbry-redux/blob/master/src/redux/actions/publish.js#L74-L79 - return claim.name.length === 24 && !claim.name.includes(' ') && claim.value.author === 'Spee.ch'; - }} - onClick={(e, claim, index) => handleClaimClicked(e, claim, index)} + customShouldHide={customShouldHide} + onClick={handleClaimClicked} + swipeLayout={swipeLayout} /> ))}
)} - {!timedOut && urisLength === 0 && !loading && !noEmpty && ( -
{empty || noResultMsg}
- )} + {!timedOut && urisLength === 0 && !loading &&
{empty || noResultMsg}
} {!loading && timedOut && timedOutMessage &&
{timedOutMessage}
}
); diff --git a/ui/component/claimListDiscover/view.jsx b/ui/component/claimListDiscover/view.jsx index 7f3e03065..c457b55a5 100644 --- a/ui/component/claimListDiscover/view.jsx +++ b/ui/component/claimListDiscover/view.jsx @@ -94,6 +94,13 @@ type Props = { doClaimSearch: ({}) => void, doToggleTagFollowDesktop: (string) => void, doFetchViewCount: (claimIdCsv: string) => void, + + loadedCallback?: (number) => void, + maxClaimRender?: number, + useSkeletonScreen?: boolean, + excludeUris?: Array, + + swipeLayout: boolean, }; function ClaimListDiscover(props: Props) { @@ -157,6 +164,11 @@ function ClaimListDiscover(props: Props) { empty, claimsByUri, doFetchViewCount, + loadedCallback, + maxClaimRender, + useSkeletonScreen = true, + excludeUris = [], + swipeLayout = false, } = props; const didNavigateForward = history.action === 'PUSH'; const { search } = location; @@ -493,6 +505,21 @@ function ClaimListDiscover(props: Props) { } function resolveOrderByOption(orderBy: string | Array, sortBy: string | Array) { + // let order_by; // peterson 038692cafc793616cceaf10b88909fecde07ad0b + // + // switch (orderBy) { + // case CS.ORDER_BY_TRENDING: + // order_by = CS.ORDER_BY_TRENDING_VALUE; + // break; + // case CS.ORDER_BY_NEW: + // order_by = CS.ORDER_BY_NEW_VALUE; + // break; + // case CS.ORDER_BY_NEW_ASC: + // order_by = CS.ORDER_BY_NEW_ASC_VALUE; + // break; + // default: + // order_by = CS.ORDER_BY_TOP_VALUE; + // } const order_by = orderBy === CS.ORDER_BY_TRENDING ? CS.ORDER_BY_TRENDING_VALUE @@ -569,8 +596,12 @@ function ClaimListDiscover(props: Props) { searchOptions={options} showNoSourceClaims={showNoSourceClaims} empty={empty} + maxClaimRender={maxClaimRender} + excludeUris={excludeUris} + loadedCallback={loadedCallback} + swipeLayout={swipeLayout} /> - {loading && ( + {loading && useSkeletonScreen && (
{new Array(dynamicPageSize).fill(1).map((x, i) => ( @@ -602,8 +633,13 @@ function ClaimListDiscover(props: Props) { searchOptions={options} showNoSourceClaims={hasNoSource || showNoSourceClaims} empty={empty} + maxClaimRender={maxClaimRender} + excludeUris={excludeUris} + loadedCallback={loadedCallback} + swipeLayout={swipeLayout} /> {loading && + useSkeletonScreen && new Array(dynamicPageSize) .fill(1) .map((x, i) => ( diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index 25e0277a4..6cd237d8a 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -6,6 +6,7 @@ import { isEmpty } from 'util/object'; import classnames from 'classnames'; import { isURIValid } from 'util/lbryURI'; import * as COLLECTIONS_CONSTS from 'constants/collections'; +import { isChannelClaim } from 'util/claim'; import { formatLbryUrlForWeb } from 'util/url'; import { formatClaimPreviewTitle } from 'util/formatAriaLabel'; import FileThumbnail from 'component/fileThumbnail'; @@ -49,7 +50,7 @@ type Props = { type: string, banState: { blacklisted?: boolean, filtered?: boolean, muted?: boolean, blocked?: boolean }, hasVisitedUri: boolean, - channelIsBlocked: boolean, + blockedUris: Array, actions: boolean | Node | string | number, properties: boolean | Node | string | number | ((Claim) => Node), empty?: Node, @@ -77,6 +78,7 @@ type Props = { date?: any, indexInContainer?: number, // The index order of this component within 'containerId'. channelSubCount?: number, + swipeLayout: boolean, }; const ClaimPreview = forwardRef((props: Props, ref: any) => { @@ -97,7 +99,6 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { streamingUrl, mediaDuration, // user properties - channelIsBlocked, hasVisitedUri, // component history, @@ -136,10 +137,11 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { disableNavigation, indexInContainer, channelSubCount, + swipeLayout = false, } = props; const isCollection = claim && claim.value_type === 'collection'; const collectionClaimId = isCollection && claim && claim.claim_id; - const listId = collectionId || collectionClaimId || null; + const listId = collectionId || collectionClaimId; const WrapperElement = wrapperElement || 'li'; const shouldFetch = claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending); @@ -170,7 +172,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { claim.value.stream_type && // $FlowFixMe (claim.value.stream_type === 'audio' || claim.value.stream_type === 'video'); - const isChannelUri = claim ? claim.value_type === 'channel' : false; + const isChannelUri = isChannelClaim(claim, uri); const signingChannel = claim && claim.signing_channel; const repostedChannelUri = claim && claim.repost_channel_url && claim.value_type === 'channel' @@ -321,6 +323,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { 'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri, 'claim-preview--pending': pending, 'claim-preview--collection-mine': isMyCollection && listId && type === 'listview', + 'swipe-list__item': swipeLayout, })} > {isMyCollection && listId && type === 'listview' && ( @@ -391,8 +394,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
)} - - {isChannelUri && !channelIsBlocked && !claimIsMine && ( + {isChannelUri && !banState.muted && !claimIsMine && ( diff --git a/ui/component/claimPreviewTile/view.jsx b/ui/component/claimPreviewTile/view.jsx index 908ec5f1b..3b431e29c 100644 --- a/ui/component/claimPreviewTile/view.jsx +++ b/ui/component/claimPreviewTile/view.jsx @@ -42,6 +42,7 @@ type Props = { properties?: (Claim) => void, collectionId?: string, viewCount: string, + swipeLayout: boolean, }; // preview image cards used in related video functionality, channel overview page and homepage @@ -66,6 +67,7 @@ function ClaimPreviewTile(props: Props) { collectionId, mediaDuration, viewCount, + swipeLayout = false, } = props; const isRepost = claim && claim.repost_channel_url; const isCollection = claim && claim.value_type === 'collection'; @@ -168,6 +170,7 @@ function ClaimPreviewTile(props: Props) { onClick={handleClick} className={classnames('card claim-preview--tile', { 'claim-preview__wrapper--channel': isChannel, + 'swipe-list__item claim-preview--horizontal-tile': swipeLayout, })} > diff --git a/ui/component/router/view.jsx b/ui/component/router/view.jsx index 095a84cdb..510e7bf3d 100644 --- a/ui/component/router/view.jsx +++ b/ui/component/router/view.jsx @@ -8,6 +8,7 @@ import { LINKED_COMMENT_QUERY_PARAM } from 'constants/comment'; import { parseURI, isURIValid } from 'util/lbryURI'; import { WELCOME_VERSION } from 'config'; import { GetLinksData } from 'util/buildHomepage'; +import { useIsLargeScreen } from 'effects/use-screensize'; import HomePage from 'page/home'; import BackupPage from 'page/backup'; @@ -125,8 +126,8 @@ function AppRouter(props: Props) { const urlParams = new URLSearchParams(search); const resetScroll = urlParams.get('reset_scroll'); const hasLinkedCommentInUrl = urlParams.get(LINKED_COMMENT_QUERY_PARAM); - - const dynamicRoutes = GetLinksData(homepageData).filter( + const isLargeScreen = useIsLargeScreen(); + const dynamicRoutes = GetLinksData(homepageData, isLargeScreen).filter( (potentialRoute: any) => potentialRoute && potentialRoute.route ); diff --git a/ui/page/home/view.jsx b/ui/page/home/view.jsx index 93646341e..6f8c815df 100644 --- a/ui/page/home/view.jsx +++ b/ui/page/home/view.jsx @@ -1,7 +1,7 @@ // @flow import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; -import { SITE_NAME, ENABLE_NO_SOURCE_CLAIMS } from 'config'; +import { SITE_NAME } from 'config'; import React from 'react'; import Page from 'component/page'; import Button from 'component/button'; @@ -9,6 +9,7 @@ import ClaimTilesDiscover from 'component/claimTilesDiscover'; import ClaimPreviewTile from 'component/claimPreviewTile'; import Icon from 'component/common/icon'; import WaitUntilOnPage from 'component/common/wait-until-on-page'; +import { useIsLargeScreen } from 'effects/use-screensize'; // have this? import { GetLinksData } from 'util/buildHomepage'; type Props = { @@ -24,9 +25,11 @@ function HomePage(props: Props) { const showPersonalizedChannels = subscribedChannels && subscribedChannels.length > 0; const showPersonalizedTags = followedTags && followedTags.length > 0; const showIndividualTags = showPersonalizedTags && followedTags.length < 5; + const isLargeScreen = useIsLargeScreen(); const rowData: Array = GetLinksData( homepageData, + isLargeScreen, true, authenticated, showPersonalizedChannels, @@ -37,29 +40,40 @@ function HomePage(props: Props) { showNsfw ); + type SectionHeaderProps = { + title: string, + navigate?: string, + icon?: string, + help?: string, + }; + const SectionHeader = ({ title, navigate = '/', icon = '', help }: SectionHeaderProps) => { + return ( +

+ +

+ ); + }; + function getRowElements(title, route, link, icon, help, options, index, pinUrls) { const tilePlaceholder = (
    {new Array(options.pageSize || 8).fill(1).map((x, i) => ( - + ))}
); - const claimTiles = ( - - ); + const claimTiles = ; return (
+ {/* category header */} {index !== 0 && title && typeof title === 'string' && ( -

- -

+ )} {index === 0 && <>{claimTiles}} @@ -69,6 +83,7 @@ function HomePage(props: Props) { )} + {/* view more button */} {(route || link) && (
diff --git a/ui/component/claimList/view.jsx b/ui/component/claimList/view.jsx index 5f3a2f8c1..891f926b8 100644 --- a/ui/component/claimList/view.jsx +++ b/ui/component/claimList/view.jsx @@ -1,4 +1,7 @@ // @flow + +// $FlowFixMe +import { Draggable } from 'react-beautiful-dnd'; import { MAIN_CLASS } from 'constants/classnames'; import type { Node } from 'react'; import React, { useEffect } from 'react'; @@ -48,6 +51,9 @@ type Props = { excludeUris?: Array, loadedCallback?: (number) => void, swipeLayout: boolean, + showEdit?: boolean, + droppableProvided?: any, + unavailableUris?: Array, }; export default function ClaimList(props: Props) { @@ -82,6 +88,9 @@ export default function ClaimList(props: Props) { excludeUris = [], loadedCallback, swipeLayout = false, + showEdit, + droppableProvided, + unavailableUris, } = props; const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); @@ -149,6 +158,30 @@ export default function ClaimList(props: Props) { } }, [loading, onScrollBottom, urisLength, pageSize, page]); + const getClaimPreview = (uri: string, index: number, draggableProvided?: any) => ( + + ); + return tileLayout && !header ? (
{urisLength > 0 && @@ -207,30 +240,44 @@ export default function ClaimList(props: Props) { 'claim-list--card-body': tileLayout, 'swipe-list': swipeLayout, })} + {...(droppableProvided && droppableProvided.droppableProps)} + ref={droppableProvided && droppableProvided.innerRef} > - {sortedUris.map((uri, index) => ( - - {injectedItem && index === 4 &&
  • {injectedItem}
  • } - -
    - ))} + {injectedItem && sortedUris.some((uri, index) => index === 4) &&
  • {injectedItem}
  • } + + {sortedUris.map((uri, index) => + droppableProvided ? ( + + {(draggableProvided, draggableSnapshot) => { + // Restrict dragging to vertical axis + // https://github.com/atlassian/react-beautiful-dnd/issues/958#issuecomment-980548919 + let transform = draggableProvided.draggableProps.style.transform; + + if (draggableSnapshot.isDragging && transform) { + transform = transform.replace(/\(.+,/, '(0,'); + } + + const style = { + ...draggableProvided.draggableProps.style, + transform, + }; + + return ( +
  • + {/* https://github.com/atlassian/react-beautiful-dnd/issues/1756 */} +
    + + {getClaimPreview(uri, index, draggableProvided)} +
  • + ); + }} +
    + ) : ( + getClaimPreview(uri, index) + ) + )} + + {droppableProvided && droppableProvided.placeholder} )} diff --git a/ui/component/claimMenuList/view.jsx b/ui/component/claimMenuList/view.jsx index f4b458841..c502875f6 100644 --- a/ui/component/claimMenuList/view.jsx +++ b/ui/component/claimMenuList/view.jsx @@ -157,11 +157,9 @@ function ClaimMenuList(props: Props) { doToast({ message: source ? __('Item removed from %name%', { name }) : __('Item added to %name%', { name }), }); - doCollectionEdit(collectionId, { - claims: [contentClaim], - remove: source, - type: 'playlist', - }); + if (contentClaim) { + doCollectionEdit(collectionId, { uris: [contentClaim.permanent_url], remove: source, type: 'playlist' }); + } } function handleFollow() { diff --git a/ui/component/claimPreview/index.js b/ui/component/claimPreview/index.js index 4e07f98c3..df20d0ac5 100644 --- a/ui/component/claimPreview/index.js +++ b/ui/component/claimPreview/index.js @@ -10,17 +10,12 @@ import { selectDateForUri, } from 'redux/selectors/claims'; import { makeSelectStreamingUrlForUri } from 'redux/selectors/file_info'; -import { - makeSelectCollectionIsMine, - makeSelectUrlsForCollectionId, - makeSelectIndexForUrlInCollection, -} from 'redux/selectors/collections'; +import { makeSelectCollectionIsMine } from 'redux/selectors/collections'; import { doResolveUri } from 'redux/actions/claims'; -import { doCollectionEdit } from 'redux/actions/collections'; import { doFileGet } from 'redux/actions/file'; import { selectBanStateForUri } from 'lbryinc'; -import { selectShowMatureContent } from 'redux/selectors/settings'; +import { selectLanguage, selectShowMatureContent } from 'redux/selectors/settings'; import { makeSelectHasVisitedUri } from 'redux/selectors/content'; import { selectIsSubscribedForUri } from 'redux/selectors/subscriptions'; import { isClaimNsfw } from 'util/claim'; @@ -50,15 +45,13 @@ const select = (state, props) => { streamingUrl: props.uri && makeSelectStreamingUrlForUri(props.uri)(state), wasPurchased: props.uri && makeSelectClaimWasPurchased(props.uri)(state), isCollectionMine: makeSelectCollectionIsMine(props.collectionId)(state), - collectionUris: makeSelectUrlsForCollectionId(props.collectionId)(state), - collectionIndex: makeSelectIndexForUrlInCollection(props.uri, props.collectionId)(state), + lang: selectLanguage(state), }; }; const perform = (dispatch) => ({ resolveUri: (uri) => dispatch(doResolveUri(uri)), getFile: (uri) => dispatch(doFileGet(uri, false)), - editCollection: (id, params) => dispatch(doCollectionEdit(id, params)), }); export default connect(select, perform)(ClaimPreview); diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index 6cd237d8a..08fa04e67 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -9,8 +9,11 @@ import * as COLLECTIONS_CONSTS from 'constants/collections'; import { isChannelClaim } from 'util/claim'; import { formatLbryUrlForWeb } from 'util/url'; import { formatClaimPreviewTitle } from 'util/formatAriaLabel'; +import { toCompactNotation } from 'util/string'; +import Tooltip from 'component/common/tooltip'; import FileThumbnail from 'component/fileThumbnail'; import UriIndicator from 'component/uriIndicator'; +import PreviewOverlayProperties from 'component/previewOverlayProperties'; import ClaimTags from 'component/claimTags'; import SubscribeButton from 'component/subscribeButton'; import ChannelThumbnail from 'component/channelThumbnail'; @@ -26,10 +29,8 @@ import ClaimMenuList from 'component/claimMenuList'; import ClaimPreviewLoading from './claim-preview-loading'; import ClaimPreviewHidden from './claim-preview-no-mature'; import ClaimPreviewNoContent from './claim-preview-no-content'; -import CollectionEditButtons from './collection-buttons'; - +import CollectionEditButtons from 'component/collectionEditButtons'; import AbandonedChannelPreview from 'component/abandonedChannelPreview'; -import PreviewOverlayProperties from 'component/previewOverlayProperties'; // preview images used on the landing page and on the channel page type Props = { @@ -69,16 +70,17 @@ type Props = { repostUrl?: string, hideMenu?: boolean, collectionId?: string, - editCollection: (string, CollectionEditParams) => void, isCollectionMine: boolean, - collectionUris: Array, - collectionIndex?: number, disableNavigation?: boolean, mediaDuration?: string, date?: any, indexInContainer?: number, // The index order of this component within 'containerId'. channelSubCount?: number, swipeLayout: boolean, + lang: string, + showEdit?: boolean, + dragHandleProps?: any, + unavailableUris?: Array, }; const ClaimPreview = forwardRef((props: Props, ref: any) => { @@ -130,15 +132,17 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { hideMenu = false, // repostUrl, collectionId, - collectionIndex, - editCollection, isCollectionMine, - collectionUris, disableNavigation, indexInContainer, channelSubCount, swipeLayout = false, + lang, + showEdit, + dragHandleProps, + unavailableUris, } = props; + const isCollection = claim && claim.value_type === 'collection'; const collectionClaimId = isCollection && claim && claim.claim_id; const listId = collectionId || collectionClaimId; @@ -147,18 +151,21 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending); const abandoned = !isResolvingUri && !claim; const isMyCollection = listId && (isCollectionMine || listId.includes('-')); + if (isMyCollection && claim === null && unavailableUris) unavailableUris.push(uri); + const shouldHideActions = hideActions || isMyCollection || type === 'small' || type === 'tooltip'; const canonicalUrl = claim && claim.canonical_url; - const lastCollectionIndex = collectionUris ? collectionUris.length - 1 : 0; const channelSubscribers = React.useMemo(() => { if (channelSubCount === undefined) { return ; } - const formattedSubCount = Number(channelSubCount).toLocaleString(); + const formattedSubCount = toCompactNotation(channelSubCount, lang, 10000); return ( - - {channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })} - + + + {channelSubCount === 1 ? __('1 Follower') : __('%formattedSubCount% Followers', { formattedSubCount })} + + ); }, [channelSubCount]); const isValid = uri && isURIValid(uri); @@ -178,6 +185,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { claim && claim.repost_channel_url && claim.value_type === 'channel' ? claim.permanent_url || claim.canonical_url : undefined; + const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url); // Get channel title ( use name as fallback ) let channelTitle = null; @@ -227,7 +235,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { ((abandoned && !showUnresolvedClaim) || (!claimIsMine && obscureNsfw && nsfw)); // This will be replaced once blocking is done at the wallet server level - if (claim && !claimIsMine && (banState.blacklisted || banState.filtered)) { + if (!shouldHide && !claimIsMine && (banState.blacklisted || banState.filtered)) { shouldHide = true; } @@ -322,19 +330,14 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { 'claim-preview--channel': isChannelUri, 'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri, 'claim-preview--pending': pending, - 'claim-preview--collection-mine': isMyCollection && listId && type === 'listview', + 'claim-preview--collection-mine': isMyCollection && showEdit, 'swipe-list__item': swipeLayout, })} > - {isMyCollection && listId && type === 'listview' && ( - + {isMyCollection && showEdit && ( + )} + {isChannelUri && claim ? ( @@ -345,7 +348,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
    - {isPlayable && } + {isPlayable && }
    {/* @if TARGET='app' */}
    @@ -355,7 +358,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
    {/* @endif */}
    - +
    diff --git a/ui/component/claimPreviewTile/view.jsx b/ui/component/claimPreviewTile/view.jsx index 3b431e29c..d8150f995 100644 --- a/ui/component/claimPreviewTile/view.jsx +++ b/ui/component/claimPreviewTile/view.jsx @@ -86,6 +86,7 @@ function ClaimPreviewTile(props: Props) { const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail; const canonicalUrl = claim && claim.canonical_url; const permanentUrl = claim && claim.permanent_url; + const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url); const listId = collectionId || collectionClaimId; const navigateUrl = formatLbryUrlForWeb(canonicalUrl || uri || '/') + (listId ? generateListSearchUrlParams(listId) : ''); @@ -178,7 +179,7 @@ function ClaimPreviewTile(props: Props) { {!isChannel && (
    - {isPlayable && } + {isPlayable && }
    {/* @if TARGET='app' */}
    diff --git a/ui/component/collectionActions/view.jsx b/ui/component/collectionActions/view.jsx index 0a06d9bb7..1322a7002 100644 --- a/ui/component/collectionActions/view.jsx +++ b/ui/component/collectionActions/view.jsx @@ -55,20 +55,6 @@ function CollectionActions(props: Props) { const claimId = claim && claim.claim_id; const webShareable = true; // collections have cost? - /* - A bit too much dependency with both ordering and shuffling depending on a single list item index selector - For now when they click edit, we'll toggle shuffle off for them. - */ - const handleSetShowEdit = (setting) => { - doToggleShuffleList(collectionId, false); - setShowEdit(setting); - }; - - const handlePublishMode = () => { - doToggleShuffleList(collectionId, false); - push(`?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`); - }; - const doPlay = React.useCallback( (playUri) => { const navigateUrl = formatLbryUrlForWeb(playUri); @@ -138,7 +124,7 @@ function CollectionActions(props: Props) { title={uri ? __('Update') : __('Publish')} label={uri ? __('Update') : __('Publish')} className={classnames('button--file-action')} - onClick={() => handlePublishMode()} + onClick={() => push(`?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`)} icon={ICONS.PUBLISH} iconColor={collectionHasEdits && 'red'} iconSize={18} @@ -165,26 +151,28 @@ function CollectionActions(props: Props) { ); - const infoButton = ( -
    ); if (isMobile) { @@ -192,7 +180,7 @@ function CollectionActions(props: Props) {
    {lhsSection} {rhsSection} - {uri && {infoButton}} + {infoButtons}
    ); } else { @@ -202,10 +190,8 @@ function CollectionActions(props: Props) { {lhsSection} {rhsSection} -
    - {uri && infoButton} - {showEditButton} -
    + + {infoButtons} ); } diff --git a/ui/component/collectionContentSidebar/index.js b/ui/component/collectionContentSidebar/index.js index e65f79f84..876f47240 100644 --- a/ui/component/collectionContentSidebar/index.js +++ b/ui/component/collectionContentSidebar/index.js @@ -1,13 +1,15 @@ import { connect } from 'react-redux'; import CollectionContent from './view'; -import { selectClaimForUri, selectClaimIsMine } from 'redux/selectors/claims'; +import { selectClaimForUri } from 'redux/selectors/claims'; import { makeSelectUrlsForCollectionId, makeSelectNameForCollectionId, makeSelectCollectionForId, + makeSelectCollectionIsMine, } from 'redux/selectors/collections'; import { selectPlayingUri, selectListLoop, selectListShuffle } from 'redux/selectors/content'; import { doToggleLoopList, doToggleShuffleList } from 'redux/actions/content'; +import { doCollectionEdit } from 'redux/actions/collections'; const select = (state, props) => { const playingUri = selectPlayingUri(state); @@ -24,7 +26,7 @@ const select = (state, props) => { collection: makeSelectCollectionForId(props.id)(state), collectionUrls: makeSelectUrlsForCollectionId(props.id)(state), collectionName: makeSelectNameForCollectionId(props.id)(state), - isMine: selectClaimIsMine(state, claim), + isMyCollection: makeSelectCollectionIsMine(props.id)(state), loop, shuffle, }; @@ -33,4 +35,5 @@ const select = (state, props) => { export default connect(select, { doToggleLoopList, doToggleShuffleList, + doCollectionEdit, })(CollectionContent); diff --git a/ui/component/collectionContentSidebar/view.jsx b/ui/component/collectionContentSidebar/view.jsx index e21302803..5ba082225 100644 --- a/ui/component/collectionContentSidebar/view.jsx +++ b/ui/component/collectionContentSidebar/view.jsx @@ -1,5 +1,10 @@ // @flow + +// $FlowFixMe +import { DragDropContext, Droppable } from 'react-beautiful-dnd'; + import React from 'react'; +import classnames from 'classnames'; import ClaimList from 'component/claimList'; import Card from 'component/common/card'; import Button from 'component/button'; @@ -11,7 +16,7 @@ import * as ICONS from 'constants/icons'; type Props = { id: string, url: string, - isMine: boolean, + isMyCollection: boolean, collectionUrls: Array, collectionName: string, collection: any, @@ -20,10 +25,35 @@ type Props = { doToggleLoopList: (string, boolean) => void, doToggleShuffleList: (string, string, boolean) => void, createUnpublishedCollection: (string, Array, ?string) => void, + doCollectionEdit: (string, CollectionEditParams) => void, }; export default function CollectionContent(props: Props) { - const { collectionUrls, collectionName, id, url, loop, shuffle, doToggleLoopList, doToggleShuffleList } = props; + const { + isMyCollection, + collectionUrls, + collectionName, + id, + url, + loop, + shuffle, + doToggleLoopList, + doToggleShuffleList, + doCollectionEdit, + } = props; + + const [showEdit, setShowEdit] = React.useState(false); + + function handleOnDragEnd(result) { + const { source, destination } = result; + + if (!destination) return; + + const { index: from } = source; + const { index: to } = destination; + + doCollectionEdit(id, { order: { from, to } }); + } return ( } titleActions={ -
    - {/* TODO: BUTTON TO SAVE COLLECTION - Probably save/copy modal */} -
    + <> +
    + {/* TODO: BUTTON TO SAVE COLLECTION - Probably save/copy modal */} +
    + + {isMyCollection && ( +