// @flow import { URL, SHARE_DOMAIN_URL } from 'config'; import * as ICONS from 'constants/icons'; import * as PAGES from 'constants/pages'; import * as MODALS from 'constants/modal_types'; import * as COLLECTIONS_CONSTS from 'constants/collections'; import React from 'react'; import classnames from 'classnames'; import { Menu, MenuButton, MenuList, MenuItem } from '@reach/menu-button'; import Icon from 'component/common/icon'; import { generateShareUrl, generateLbryContentUrl, formatLbryUrlForWeb, generateListSearchUrlParams } from 'util/url'; import { useHistory } from 'react-router'; import { buildURI, parseURI } from 'util/lbryURI'; const SHARE_DOMAIN = SHARE_DOMAIN_URL || URL; const PAGE_VIEW_QUERY = 'view'; const EDIT_PAGE = 'edit'; type SubscriptionArgs = { channelName: string, uri: string, notificationsDisabled?: boolean, }; type Props = { uri: string, claim: ?Claim, repostedClaim: ?Claim, contentClaim: ?Claim, contentSigningChannel: ?Claim, contentChannelUri: string, openModal: (id: string, {}) => void, inline?: boolean, channelIsMuted: boolean, channelIsBlocked: boolean, channelIsAdminBlocked: boolean, isAdmin: boolean, doChannelMute: (string) => void, doChannelUnmute: (string) => void, doCommentModBlock: (string) => void, doCommentModUnBlock: (string) => void, doCommentModBlockAsAdmin: (string, ?string, ?string) => void, doCommentModUnBlockAsAdmin: (string, string) => void, doCollectionEdit: (string, any) => void, hasClaimInWatchLater: boolean, hasClaimInFavorites: boolean, claimInCollection: boolean, collectionId: string, isMyCollection: boolean, doToast: ({ message: string, isError?: boolean }) => void, claimIsMine: boolean, fileInfo: FileListItem, prepareEdit: ({}, string, {}) => void, isSubscribed: boolean, doChannelSubscribe: (SubscriptionArgs) => void, doChannelUnsubscribe: (SubscriptionArgs) => void, isChannelPage: boolean, editedCollection: Collection, playNextUri: string, resolvedList: boolean, fetchCollectionItems: (string) => void, doToggleShuffleList: (string) => void, lastUsedCollection: ?Collection, hasClaimInLastUsedCollection: boolean, lastUsedCollectionIsNotBuiltin: boolean, }; function ClaimMenuList(props: Props) { const { uri, claim, repostedClaim, contentClaim, contentSigningChannel, contentChannelUri, openModal, inline = false, doChannelMute, doChannelUnmute, channelIsMuted, channelIsBlocked, channelIsAdminBlocked, isAdmin, doCommentModBlock, doCommentModUnBlock, doCommentModBlockAsAdmin, doCommentModUnBlockAsAdmin, doCollectionEdit, hasClaimInWatchLater, hasClaimInFavorites, collectionId, isMyCollection, doToast, claimIsMine, fileInfo, prepareEdit, isSubscribed, doChannelSubscribe, doChannelUnsubscribe, isChannelPage = false, editedCollection, playNextUri, resolvedList, fetchCollectionItems, doToggleShuffleList, lastUsedCollection, hasClaimInLastUsedCollection, lastUsedCollectionIsNotBuiltin, } = props; const [doShuffle, setDoShuffle] = React.useState(false); const incognitoClaim = contentChannelUri && !contentChannelUri.includes('@'); const isChannel = !incognitoClaim && !contentSigningChannel; // $FlowFixMe const claimLength = claim && claim.value && claim.value.claims && claim.value.claims.length; // $FlowFixMe const claimCount = editedCollection ? editedCollection.items.length : claimLength; const isEmptyCollection = (Number(claimCount) || 0) <= 0; const { channelName } = parseURI(contentChannelUri); const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0)); const subscriptionLabel = repostedClaim ? isSubscribed ? __('Unfollow @%channelName%', { channelName }) : __('Follow @%channelName%', { channelName }) : isSubscribed ? __('Unfollow') : __('Follow'); const { push, replace } = useHistory(); const fetchItems = React.useCallback(() => { if (collectionId) { fetchCollectionItems(collectionId); } }, [collectionId, fetchCollectionItems]); React.useEffect(() => { if (doShuffle && resolvedList) { doToggleShuffleList(collectionId); if (playNextUri) { const navigateUrl = formatLbryUrlForWeb(playNextUri); push({ pathname: navigateUrl, search: generateListSearchUrlParams(collectionId), state: { collectionId, forceAutoplay: true }, }); } } }, [collectionId, doShuffle, doToggleShuffleList, playNextUri, push, resolvedList]); if (!claim) { return null; } const lbryUrl: string = generateLbryContentUrl(claim.canonical_url, claim.permanent_url); const shareUrl: string = generateShareUrl(SHARE_DOMAIN, lbryUrl); const isCollectionClaim = claim && claim.value_type === 'collection'; // $FlowFixMe const isPlayable = contentClaim && // $FlowFixMe contentClaim.value && // $FlowFixMe contentClaim.value.stream_type && // $FlowFixMe (contentClaim.value.stream_type === 'audio' || contentClaim.value.stream_type === 'video'); function handleAdd(source, name, collectionId) { doToast({ message: source ? __('Item removed from %name%', { name }) : __('Item added to %name%', { name }), }); if (contentClaim) { doCollectionEdit(collectionId, { uris: [contentClaim.permanent_url], remove: source, type: 'playlist' }); } } function handleFollow() { const subscriptionHandler = isSubscribed ? doChannelUnsubscribe : doChannelSubscribe; if (channelName) { subscriptionHandler({ channelName: '@' + channelName, uri: contentChannelUri, notificationsDisabled: true, }); } } function handleToggleMute() { if (channelIsMuted) { doChannelUnmute(contentChannelUri); } else { doChannelMute(contentChannelUri); } } function handleToggleBlock() { if (channelIsBlocked) { doCommentModUnBlock(contentChannelUri); } else { doCommentModBlock(contentChannelUri); } } function handleEdit() { if (!isChannel) { const signingChannelName = contentSigningChannel && contentSigningChannel.name; const uriObject: LbryUrlObj = { streamName: claim.name, streamClaimId: claim.claim_id, }; if (signingChannelName) { uriObject.channelName = signingChannelName; } const editUri = buildURI(uriObject); push(`/$/${PAGES.UPLOAD}`); prepareEdit(claim, editUri, fileInfo); } else { const channelUrl = claim.name + ':' + claim.claim_id; push(`/${channelUrl}?${PAGE_VIEW_QUERY}=${EDIT_PAGE}`); } } function handleDelete() { if (!repostedClaim && !isChannel) { openModal(MODALS.CONFIRM_FILE_REMOVE, { uri, doGoBack: false }); } else { openModal(MODALS.CONFIRM_CLAIM_REVOKE, { claim, cb: isChannel && (() => replace(`/$/${PAGES.CHANNELS}`)) }); } } function handleSupport() { openModal(MODALS.SEND_TIP, { uri, isSupport: true }); } function handleToggleAdminBlock() { if (channelIsAdminBlocked) { doCommentModUnBlockAsAdmin(contentChannelUri, ''); } else { doCommentModBlockAsAdmin(contentChannelUri, undefined, undefined); } } function copyToClipboard(textToCopy, successMsg, failureMsg) { navigator.clipboard .writeText(textToCopy) .then(() => { doToast({ message: __(successMsg) }); }) .catch(() => { doToast({ message: __(failureMsg), isError: true }); }); } function handleCopyLink() { copyToClipboard(shareUrl, 'Link copied.', 'Failed to copy link.'); } function handleReportContent() { // $FlowFixMe push(`/$/${PAGES.REPORT_CONTENT}?claimId=${contentClaim && contentClaim.claim_id}`); } // changes MenuButton behavior to show/hide MenuList not on press but on click event let isEventSimulated = false; const menuButtonHandler = (e) => { if (e.key === 'Enter') { e.preventDefault(); e.type = 'click'; } if (!isEventSimulated && e.type === 'mousedown') { e.preventDefault(); } else if (e.type === 'click') { isEventSimulated = true; e.target.dispatchEvent(new MouseEvent('mousedown', { bubbles: true })); isEventSimulated = false; } }; return ( { e.stopPropagation(); e.preventDefault(); menuButtonHandler(e); }} onKeyDown={menuButtonHandler} > <> {/* COLLECTION OPERATIONS */} {collectionId && isCollectionClaim ? ( <> push(`/$/${PAGES.LIST}/${collectionId}`)}> {__('View List')} {!isEmptyCollection && ( { if (!resolvedList) fetchItems(); setDoShuffle(true); }} >
{__('Shuffle Play')}
)} {isMyCollection && ( <> push(`/$/${PAGES.LIST}/${collectionId}?view=edit`)} >
{editedCollection ? __('Publish') : __('Edit List')}
openModal(MODALS.COLLECTION_DELETE, { collectionId })} >
{__('Delete List')}
)} ) : ( isPlayable && ( <> {/* WATCH LATER */} handleAdd(hasClaimInWatchLater, __('Watch Later'), COLLECTIONS_CONSTS.WATCH_LATER_ID)} >
{hasClaimInWatchLater ? __('In Watch Later') : __('Watch Later')}
{/* FAVORITES LIST */} handleAdd(hasClaimInFavorites, __('Favorites'), COLLECTIONS_CONSTS.FAVORITES_ID)} >
{hasClaimInFavorites ? __('In Favorites') : __('Favorites')}
{/* CURRENTLY ONLY SUPPORT PLAYLISTS FOR PLAYABLE; LATER DIFFERENT TYPES */} openModal(MODALS.COLLECTION_ADD, { uri, type: 'playlist' })} >
{__('Add to Lists')}
{lastUsedCollection && lastUsedCollectionIsNotBuiltin && ( handleAdd(hasClaimInLastUsedCollection, lastUsedCollection.name, lastUsedCollection.id) } >
{!hasClaimInLastUsedCollection && } {hasClaimInLastUsedCollection && } {!hasClaimInLastUsedCollection && __('Add to %collection%', { collection: lastUsedCollection.name })} {hasClaimInLastUsedCollection && __('In %collection%', { collection: lastUsedCollection.name })}
)}
) )} {!isChannelPage && ( <>
{__('Support --[button to support a claim]--')}
)} {!incognitoClaim && !claimIsMine && !isChannelPage && ( <>
{subscriptionLabel}
)} {!isMyCollection && ( <> {(!claimIsMine || channelIsBlocked) && contentChannelUri ? ( !incognitoClaim && ( <>
{channelIsBlocked ? __('Unblock Channel') : __('Block Channel')}
{isAdmin && (
{channelIsAdminBlocked ? __('Global Unblock Channel') : __('Global Block Channel')}
)}
{channelIsMuted ? __('Unmute Channel') : __('Mute Channel')}
) ) : ( <> {!isChannelPage && !repostedClaim && (
{__('Edit')}
)} )} {showDelete && (
{__('Delete')}
)} )}
{__('Copy Link')}
{!claimIsMine && !isMyCollection && (
{__('Report Content')}
)}
); } export default ClaimMenuList;