diff --git a/.flowconfig b/.flowconfig index 8a6fdca75..2b4def62b 100644 --- a/.flowconfig +++ b/.flowconfig @@ -2,7 +2,8 @@ .*\.typeface\.json .*/node_modules/findup/.* .*/node_modules/react-plastic/.* - +.*/node_modules/raf-schd/.* +.*/node_modules/react-beautiful-dnd/.* [include] diff --git a/flow-typed/Collections.js b/flow-typed/Collections.js index f70825a4f..a6939307f 100644 --- a/flow-typed/Collections.js +++ b/flow-typed/Collections.js @@ -24,9 +24,8 @@ declare type CollectionGroup = { } declare type CollectionEditParams = { - claims?: Array, + uris?: Array, remove?: boolean, - claimIds?: Array, replace?: boolean, order?: { from: number, to: number }, type?: string, diff --git a/package.json b/package.json index 9a83474d8..8aed5063e 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,8 @@ "parse-duration": "^1.0.0", "proxy-polyfill": "0.1.6", "re-reselect": "^4.0.0", - "react-datetime-picker": "^3.2.1", + "react-beautiful-dnd": "^13.1.0", + "react-datetime-picker": "^3.4.3", "remove-markdown": "^0.3.0", "rss": "^1.2.2", "source-map-explorer": "^2.5.2", diff --git a/static/app-strings.json b/static/app-strings.json index 0e1d72689..3d730e596 100644 --- a/static/app-strings.json +++ b/static/app-strings.json @@ -2249,5 +2249,11 @@ "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%": "Amount of $%input_amount% LBC in USB is lower than price of $%price_amount%", "Hosting for content you have downloaded": "Hosting for content you have downloaded", "Hosting content selected by the network": "Hosting content selected by the network", + "Remove all unavailable claims": "Remove all unavailable claims", + "Drag": "Drag", + "Move Top": "Move Top", + "Move Bottom": "Move Bottom", + "Move Up": "Move Up", + "Move Down": "Move Down", "--end--": "--end--" } diff --git a/ui/component/claimCollectionAdd/view.jsx b/ui/component/claimCollectionAdd/view.jsx index 848445866..445ed25c4 100644 --- a/ui/component/claimCollectionAdd/view.jsx +++ b/ui/component/claimCollectionAdd/view.jsx @@ -79,15 +79,7 @@ const ClaimCollectionAdd = (props: Props) => { .filter((list) => (isChannel ? list.type === 'collection' : list.type === 'playlist')) .map((l) => { const { id } = l; - return ( - - ); + return ; })} {unpublished && (Object.values(unpublished): any) @@ -96,13 +88,7 @@ const ClaimCollectionAdd = (props: Props) => { .map((l) => { const { id } = l; return ( - + ); })} {published && @@ -110,13 +96,7 @@ const ClaimCollectionAdd = (props: Props) => { // $FlowFixMe const { id } = l; return ( - + ); })} diff --git a/ui/component/claimList/view.jsx b/ui/component/claimList/view.jsx index aeac02b0a..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'; @@ -44,7 +47,13 @@ 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, + showEdit?: boolean, + droppableProvided?: any, + unavailableUris?: Array, }; export default function ClaimList(props: Props) { @@ -75,7 +84,13 @@ export default function ClaimList(props: Props) { collectionId, showNoSourceClaims, onClick, - noEmpty, + maxClaimRender, + excludeUris = [], + loadedCallback, + swipeLayout = false, + showEdit, + droppableProvided, + unavailableUris, } = props; const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); @@ -85,8 +100,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 +121,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) => { @@ -123,8 +158,32 @@ export default function ClaimList(props: Props) { } }, [loading, onScrollBottom, urisLength, pageSize, page]); + const getClaimPreview = (uri: string, index: number, draggableProvided?: any) => ( + + ); + 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,43 +236,52 @@ export default function ClaimList(props: Props) { {urisLength > 0 && (
    - {sortedUris.map((uri, index) => ( - - {injectedItem && index === 4 &&
  • {injectedItem}
  • } - { - // 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'; + {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)} +
  • + ); }} - onClick={(e, claim, index) => handleClaimClicked(e, claim, index)} - /> -
    - ))} + + ) : ( + getClaimPreview(uri, index) + ) + )} + + {droppableProvided && droppableProvided.placeholder}
)} - {!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/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 25e0277a4..08fa04e67 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -6,10 +6,14 @@ 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 { 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'; @@ -25,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 = { @@ -49,7 +51,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, @@ -68,15 +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) => { @@ -97,7 +101,6 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { streamingUrl, mediaDuration, // user properties - channelIsBlocked, hasVisitedUri, // component history, @@ -129,34 +132,40 @@ 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 || null; + const listId = collectionId || collectionClaimId; const WrapperElement = wrapperElement || 'li'; const shouldFetch = 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); @@ -170,12 +179,13 @@ 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' ? 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; @@ -225,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; } @@ -320,18 +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 ? ( @@ -342,7 +348,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
- {isPlayable && } + {isPlayable && }
{/* @if TARGET='app' */}
@@ -352,7 +358,7 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => {
{/* @endif */}
- +
@@ -391,8 +397,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..d8150f995 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'; @@ -84,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) : ''); @@ -168,6 +171,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, })} > @@ -175,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 && ( + + + ); + }; + 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) && (