diff --git a/ui/component/claimMenuList/index.js b/ui/component/claimMenuList/index.js index ee8a70625..3079b61cd 100644 --- a/ui/component/claimMenuList/index.js +++ b/ui/component/claimMenuList/index.js @@ -38,6 +38,7 @@ const select = (state, props) => { claim, claimIsMine: makeSelectSigningIsMine(props.uri)(state), hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state), + hasClaimInCustom: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.FAVORITES_ID, permanentUri)(state), channelIsMuted: makeSelectChannelIsMuted(props.uri)(state), channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state), fileInfo: makeSelectFileInfoForUri(props.uri)(state), diff --git a/ui/component/claimMenuList/view.jsx b/ui/component/claimMenuList/view.jsx index ce9e4de91..712a81c26 100644 --- a/ui/component/claimMenuList/view.jsx +++ b/ui/component/claimMenuList/view.jsx @@ -40,6 +40,7 @@ type Props = { isRepost: boolean, doCollectionEdit: (string, any) => void, hasClaimInWatchLater: boolean, + hasClaimInCustom: boolean, claimInCollection: boolean, collectionName?: string, collectionId: string, @@ -75,6 +76,7 @@ function ClaimMenuList(props: Props) { doCommentModUnBlockAsAdmin, doCollectionEdit, hasClaimInWatchLater, + hasClaimInCustom, collectionId, collectionName, isMyCollection, @@ -96,6 +98,7 @@ function ClaimMenuList(props: Props) { const isChannel = !incognitoClaim && signingChannel === claim; const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0)); const subscriptionLabel = isSubscribed ? __('Unfollow') : __('Follow'); + const lastCollectionName = 'Favorites'; const { push, replace } = useHistory(); if (!claim) { @@ -245,6 +248,29 @@ function ClaimMenuList(props: Props) { )} + {/* CUSTOM LIST */} + {isPlayable && !collectionId && ( + { + doToast({ + message: __(`Item %action% ${lastCollectionName}`, { + action: hasClaimInCustom ? __('removed from') : __('added to'), + }), + }); + doCollectionEdit(COLLECTIONS_CONSTS.FAVORITES_ID, { + claims: [contentClaim], + remove: hasClaimInCustom, + type: 'playlist', + }); + }} + > +
+ + {hasClaimInCustom ? __(`In ${lastCollectionName}`) : __(`${lastCollectionName}`)} +
+
+ )} {/* COLLECTION OPERATIONS */} {collectionId && collectionName && isCollectionClaim && ( <> diff --git a/ui/component/claimPreview/view.jsx b/ui/component/claimPreview/view.jsx index f787ea852..4e04211b2 100644 --- a/ui/component/claimPreview/view.jsx +++ b/ui/component/claimPreview/view.jsx @@ -19,6 +19,7 @@ import ClaimPreviewTitle from 'component/claimPreviewTitle'; import ClaimPreviewSubtitle from 'component/claimPreviewSubtitle'; import ClaimRepostAuthor from 'component/claimRepostAuthor'; import FileDownloadLink from 'component/fileDownloadLink'; +import FileWatchLaterLink from 'component/fileWatchLaterLink'; import PublishPending from 'component/publishPending'; import ClaimMenuList from 'component/claimMenuList'; import ClaimPreviewLoading from './claim-preview-loading'; @@ -157,6 +158,15 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { isValid = false; } } + // $FlowFixMe + const isPlayable = + claim && + // $FlowFixMe + claim.value && + // $FlowFixMe + claim.value.stream_type && + // $FlowFixMe + (claim.value.stream_type === 'audio' || claim.value.stream_type === 'video'); const isCollection = claim && claim.value_type === 'collection'; const isChannelUri = isValid ? parseURI(uri).isChannel : false; const signingChannel = claim && claim.signing_channel; @@ -318,6 +328,11 @@ const ClaimPreview = forwardRef((props: Props, ref: any) => { )} + {isPlayable && ( +
+ +
+ )} ) : ( diff --git a/ui/component/claimPreviewTile/view.jsx b/ui/component/claimPreviewTile/view.jsx index fdf4c3a1a..6533dbba8 100644 --- a/ui/component/claimPreviewTile/view.jsx +++ b/ui/component/claimPreviewTile/view.jsx @@ -13,6 +13,7 @@ import { formatLbryUrlForWeb } from 'util/url'; import { parseURI, COLLECTIONS_CONSTS } from 'lbry-redux'; import PreviewOverlayProperties from 'component/previewOverlayProperties'; import FileDownloadLink from 'component/fileDownloadLink'; +import FileWatchLaterLink from 'component/fileWatchLaterLink'; import ClaimRepostAuthor from 'component/claimRepostAuthor'; import ClaimMenuList from 'component/claimMenuList'; import CollectionPreviewOverlay from 'component/collectionPreviewOverlay'; @@ -75,6 +76,15 @@ function ClaimPreviewTile(props: Props) { const isRepost = claim && claim.repost_channel_url; const isCollection = claim && claim.value_type === 'collection'; const isStream = claim && claim.value_type === 'stream'; + // $FlowFixMe + const isPlayable = + claim && + // $FlowFixMe + claim.value && + // $FlowFixMe + claim.value.stream_type && + // $FlowFixMe + (claim.value.stream_type === 'audio' || claim.value.stream_type === 'video'); const collectionClaimId = isCollection && claim && claim.claim_id; const shouldFetch = claim === undefined; const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail; @@ -195,6 +205,12 @@ function ClaimPreviewTile(props: Props) { )} {/* @endif */} + {isPlayable && ( +
+ +
+ )} +
diff --git a/ui/component/collectionContentSidebar/view.jsx b/ui/component/collectionContentSidebar/view.jsx index 7bdc7eeec..49d932100 100644 --- a/ui/component/collectionContentSidebar/view.jsx +++ b/ui/component/collectionContentSidebar/view.jsx @@ -6,6 +6,7 @@ import Button from 'component/button'; import * as PAGES from 'constants/pages'; import Icon from 'component/common/icon'; import * as ICONS from 'constants/icons'; +import { COLLECTIONS_CONSTS } from 'lbry-redux'; type Props = { collectionUrls: Array, @@ -26,7 +27,10 @@ export default function CollectionContent(props: Props) { className="file-page__recommended" title={ - + {collectionName} } diff --git a/ui/component/collectionSelectItem/view.jsx b/ui/component/collectionSelectItem/view.jsx index 14aab0079..267d0f2f3 100644 --- a/ui/component/collectionSelectItem/view.jsx +++ b/ui/component/collectionSelectItem/view.jsx @@ -25,7 +25,7 @@ function CollectionSelectItem(props: Props) { let icon; switch (category) { case 'builtin': - icon = id === COLLECTIONS_CONSTS.WATCH_LATER_ID ? ICONS.TIME : ICONS.STACK; + icon = (id === COLLECTIONS_CONSTS.WATCH_LATER_ID && ICONS.TIME) || (id === COLLECTIONS_CONSTS.FAVORITES_ID && ICONS.STAR) || ICONS.STACK; break; case 'published': icon = ICONS.STACK; diff --git a/ui/component/collectionsListMine/view.jsx b/ui/component/collectionsListMine/view.jsx index 324cee079..45b3b6c96 100644 --- a/ui/component/collectionsListMine/view.jsx +++ b/ui/component/collectionsListMine/view.jsx @@ -68,7 +68,9 @@ export default function CollectionsListMine(props: Props) { {__(`${list.name}`)}
- + {itemUrls.length}
diff --git a/ui/component/fileWatchLaterLink/index.js b/ui/component/fileWatchLaterLink/index.js new file mode 100644 index 000000000..107ac4eaf --- /dev/null +++ b/ui/component/fileWatchLaterLink/index.js @@ -0,0 +1,26 @@ +import { connect } from 'react-redux'; +import { + makeSelectClaimForUri, + COLLECTIONS_CONSTS, + makeSelectCollectionForIdHasClaimUrl, + doCollectionEdit, +} from 'lbry-redux'; +import FileWatchLaterLink from './view'; +import { doToast } from 'redux/actions/notifications'; + +const select = (state, props) => { + const claim = makeSelectClaimForUri(props.uri)(state); + const permanentUri = claim && claim.permanent_url; + + return { + claim, + hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state), + }; +}; + +const perform = dispatch => ({ + doToast: (props) => dispatch(doToast(props)), + doCollectionEdit: (collection, props) => dispatch(doCollectionEdit(collection, props)), +}); + +export default connect(select, perform)(FileWatchLaterLink); diff --git a/ui/component/fileWatchLaterLink/view.jsx b/ui/component/fileWatchLaterLink/view.jsx new file mode 100644 index 000000000..daeb6d833 --- /dev/null +++ b/ui/component/fileWatchLaterLink/view.jsx @@ -0,0 +1,62 @@ +// @flow +import * as ICONS from 'constants/icons'; +import React, { useRef } from 'react'; +import Button from 'component/button'; +import useHover from 'effects/use-hover'; +import { COLLECTIONS_CONSTS } from 'lbry-redux'; + +type Props = { + uri: string, + claim: StreamClaim, + hasClaimInWatchLater: boolean, + doToast: ({ message: string }) => void, + doCollectionEdit: (string, any) => void, +}; + +function FileWatchLaterLink(props: Props) { + const { + claim, + hasClaimInWatchLater, + doToast, + doCollectionEdit, + } = props; + const buttonRef = useRef(); + let isHovering = useHover(buttonRef); + + if (!claim) { + return null; + } + + function handleWatchLater(e) { + e.preventDefault(); + doToast({ + message: __('Item %action% Watch Later', { + action: hasClaimInWatchLater ? __('removed from') : __('added to'), + }), + linkText: !hasClaimInWatchLater && __('See All'), + linkTarget: !hasClaimInWatchLater && '/list/watchlater', + }); + doCollectionEdit(COLLECTIONS_CONSTS.WATCH_LATER_ID, { + claims: [claim], + remove: hasClaimInWatchLater, + type: 'playlist', + }); + } + + const title = hasClaimInWatchLater ? __('Remove from Watch Later') : __('Add to Watch Later'); + const label = !hasClaimInWatchLater ? __('Add') : __('Added'); + + return ( +