lbry-desktop/ui/component/claimPreview/view.jsx

366 lines
12 KiB
React
Raw Normal View History

2019-06-11 20:10:58 +02:00
// @flow
import type { Node } from 'react';
import React, { useEffect, forwardRef } from 'react';
import { NavLink, withRouter } from 'react-router-dom';
2021-01-28 23:48:41 +01:00
import { useImage } from 'react-image';
2019-06-11 20:10:58 +02:00
import classnames from 'classnames';
2021-01-28 23:48:41 +01:00
import { CHANNEL_THUMBNAIL_FALLBACK, STREAM_THUMBNAIL_FALLBACK } from 'config';
2019-06-27 08:18:45 +02:00
import { parseURI, convertToShareLink } from 'lbry-redux';
2019-06-11 20:10:58 +02:00
import { openCopyLinkMenu } from 'util/context-menu';
import { formatLbryUrlForWeb } from 'util/url';
2019-08-25 13:42:25 +02:00
import { isEmpty } from 'util/object';
2020-01-06 19:32:35 +01:00
import FileThumbnail from 'component/fileThumbnail';
2019-06-11 20:10:58 +02:00
import UriIndicator from 'component/uriIndicator';
import FileProperties from 'component/fileProperties';
2019-06-19 07:05:43 +02:00
import ClaimTags from 'component/claimTags';
2019-06-11 20:10:58 +02:00
import SubscribeButton from 'component/subscribeButton';
import ChannelThumbnail from 'component/channelThumbnail';
import BlockButton from 'component/blockButton';
2020-02-12 19:59:48 +01:00
import ClaimSupportButton from 'component/claimSupportButton';
import useGetThumbnail from 'effects/use-get-thumbnail';
2020-01-30 23:25:15 +01:00
import ClaimPreviewTitle from 'component/claimPreviewTitle';
2020-01-31 17:12:47 +01:00
import ClaimPreviewSubtitle from 'component/claimPreviewSubtitle';
2020-01-31 17:43:14 +01:00
import ClaimRepostAuthor from 'component/claimRepostAuthor';
2020-02-05 03:54:19 +01:00
import FileDownloadLink from 'component/fileDownloadLink';
import AbandonedChannelPreview from 'component/abandonedChannelPreview';
2020-05-07 14:22:55 +02:00
import PublishPending from 'component/publishPending';
import ClaimPreviewLoading from './claim-preview-loading';
import ClaimPreviewHidden from './claim-preview-no-mature';
import ClaimPreviewNoContent from './claim-preview-no-content';
2019-06-11 20:10:58 +02:00
type Props = {
uri: string,
claim: ?Claim,
obscureNsfw: boolean,
showUserBlocked: boolean,
2019-06-11 20:10:58 +02:00
claimIsMine: boolean,
pending?: boolean,
reflectingProgress?: any, // fxme
2019-06-11 20:10:58 +02:00
resolveUri: string => void,
isResolvingUri: boolean,
history: { push: string => void },
title: string,
nsfw: boolean,
2019-07-21 23:31:22 +02:00
placeholder: string,
2019-06-19 07:05:43 +02:00
type: string,
2019-07-11 20:06:25 +02:00
hasVisitedUri: boolean,
blackListedOutpoints: Array<{
txid: string,
nout: number,
}>,
filteredOutpoints: Array<{
txid: string,
nout: number,
}>,
blockedChannelUris: Array<string>,
channelIsBlocked: boolean,
isSubscribed: boolean,
actions: boolean | Node | string | number,
2020-02-11 20:04:51 +01:00
properties: boolean | Node | string | number | (Claim => Node),
empty?: Node,
onClick?: any => any,
2019-11-22 22:13:00 +01:00
hideBlock?: boolean,
streamingUrl: ?string,
getFile: string => void,
2020-01-08 17:36:49 +01:00
customShouldHide?: Claim => boolean,
2020-02-05 19:01:07 +01:00
showUnresolvedClaim?: boolean,
showNullPlaceholder?: boolean,
2020-02-12 19:59:48 +01:00
includeSupportAction?: boolean,
hideActions?: boolean,
renderActions?: Claim => ?Node,
wrapperElement?: string,
hideRepostLabel?: boolean,
repostUrl?: string,
2019-06-11 20:10:58 +02:00
};
2019-08-02 08:28:14 +02:00
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
2019-06-11 20:10:58 +02:00
const {
// core
2019-06-11 20:10:58 +02:00
uri,
claim,
2019-06-11 20:10:58 +02:00
isResolvingUri,
// core actions
getFile,
2019-06-11 20:10:58 +02:00
resolveUri,
// claim properties
// is the claim consider nsfw?
nsfw,
claimIsMine,
isSubscribed,
streamingUrl,
// user properties
channelIsBlocked,
2019-07-11 20:06:25 +02:00
hasVisitedUri,
// component
history,
wrapperElement,
type,
placeholder,
// pending
reflectingProgress,
pending,
empty,
// modifiers
customShouldHide,
showNullPlaceholder,
// value from show mature content user setting
// true if the user doesn't wanna see nsfw content
obscureNsfw,
showUserBlocked,
showUnresolvedClaim,
hideRepostLabel = false,
hideActions = false,
properties,
onClick,
2019-11-22 22:13:00 +01:00
hideBlock,
actions,
blockedChannelUris,
blackListedOutpoints,
filteredOutpoints,
2020-02-12 19:59:48 +01:00
includeSupportAction,
renderActions,
// repostUrl,
2019-06-11 20:10:58 +02:00
} = props;
const WrapperElement = wrapperElement || 'li';
2019-10-03 23:40:54 +02:00
const shouldFetch =
claim === undefined || (claim !== null && claim.value_type === 'channel' && isEmpty(claim.meta) && !pending);
2019-07-21 23:31:22 +02:00
const abandoned = !isResolvingUri && !claim;
const shouldHideActions = hideActions || type === 'small' || type === 'tooltip';
const canonicalUrl = claim && claim.canonical_url;
let isValid = false;
if (uri) {
try {
parseURI(uri);
isValid = true;
} catch (e) {
isValid = false;
}
2019-07-11 21:03:31 +02:00
}
2021-01-13 16:44:44 +01:00
const isRepost = claim && claim.repost_url;
2019-07-05 20:11:55 +02:00
2021-01-13 16:44:44 +01:00
const contentUri = claim && isRepost ? claim.canonical_url || claim.permanent_url : uri;
const isChannelUri = isValid ? parseURI(contentUri).isChannel : false;
2019-07-29 16:12:53 +02:00
const signingChannel = claim && claim.signing_channel;
2020-02-05 00:54:49 +01:00
const navigateUrl = formatLbryUrlForWeb((claim && claim.canonical_url) || uri || '/');
const navLinkProps = {
to: navigateUrl,
onClick: e => e.stopPropagation(),
};
// do not block abandoned and nsfw claims if showUserBlocked is passed
2019-10-17 18:58:28 +02:00
let shouldHide =
placeholder !== 'loading' &&
!showUserBlocked &&
((abandoned && !showUnresolvedClaim) || (!claimIsMine && obscureNsfw && nsfw));
2019-07-05 20:11:55 +02:00
// This will be replaced once blocking is done at the wallet server level
if (claim && !claimIsMine && !shouldHide && blackListedOutpoints) {
shouldHide = blackListedOutpoints.some(
outpoint =>
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
);
}
// We're checking to see if the stream outpoint
// or signing channel outpoint is in the filter list
if (claim && !claimIsMine && !shouldHide && filteredOutpoints) {
shouldHide = filteredOutpoints.some(
outpoint =>
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
);
2019-07-05 20:11:55 +02:00
}
// block stream claims
if (claim && !shouldHide && !showUserBlocked && blockedChannelUris.length && signingChannel) {
shouldHide = blockedChannelUris.some(blockedUri => blockedUri === signingChannel.permanent_url);
}
// block channel claims if we can't control for them in claim search
2019-08-02 17:11:31 +02:00
// e.g. fetchRecommendedSubscriptions
2021-01-13 16:44:44 +01:00
if (claim && isChannelUri && !shouldHide && !showUserBlocked && blockedChannelUris.length) {
shouldHide = blockedChannelUris.some(blockedUri => blockedUri === claim.permanent_url);
}
2019-06-11 20:10:58 +02:00
2020-01-08 17:36:49 +01:00
if (!shouldHide && customShouldHide && claim) {
if (customShouldHide(claim)) {
shouldHide = true;
}
}
2020-01-13 20:12:27 +01:00
// Weird placement warning
// Make sure this happens after we figure out if this claim needs to be hidden
const thumbnailUrl = useGetThumbnail(contentUri, claim, streamingUrl, getFile, shouldHide);
2021-01-28 23:48:41 +01:00
const isStream = claim && claim.value_type === 'stream';
const { src: thumbnailUrlWithFallback } = useImage({
srcList: [thumbnailUrl, isStream ? STREAM_THUMBNAIL_FALLBACK : CHANNEL_THUMBNAIL_FALLBACK],
useSuspense: false,
});
2020-01-13 20:12:27 +01:00
2019-06-11 20:10:58 +02:00
function handleContextMenu(e) {
// @if TARGET='app'
2019-06-11 20:10:58 +02:00
e.preventDefault();
e.stopPropagation();
if (claim) {
const shareLink = convertToShareLink(claim.canonical_url || claim.permanent_url);
2020-06-05 17:05:47 +02:00
openCopyLinkMenu(shareLink.replace(/#/g, ':'), e);
2019-06-11 20:10:58 +02:00
}
// @endif
2019-06-11 20:10:58 +02:00
}
function handleOnClick(e) {
if (onClick) {
onClick(e);
}
if (claim && !pending) {
2020-02-05 00:54:49 +01:00
history.push(navigateUrl);
2019-06-11 20:10:58 +02:00
}
}
useEffect(() => {
2020-12-28 20:26:55 +01:00
if (isValid && !isResolvingUri && shouldFetch && uri) {
2019-06-11 20:10:58 +02:00
resolveUri(uri);
}
2020-12-28 20:26:55 +01:00
}, [isValid, uri, isResolvingUri, shouldFetch]);
2019-06-11 20:10:58 +02:00
if (shouldHide && !showNullPlaceholder) {
2019-06-11 20:10:58 +02:00
return null;
}
if (placeholder === 'loading' || (uri && !claim && isResolvingUri)) {
2021-01-13 16:44:44 +01:00
return <ClaimPreviewLoading isChannel={isChannelUri} type={type} />;
}
if (claim && showNullPlaceholder && shouldHide && nsfw && obscureNsfw) {
return (
2021-01-13 16:44:44 +01:00
<ClaimPreviewHidden
message={__('Mature content hidden by your preferences')}
isChannel={isChannelUri}
type={type}
/>
);
}
if (claim && showNullPlaceholder && shouldHide) {
2021-01-13 16:44:44 +01:00
return <ClaimPreviewHidden message={__('This content is hidden')} isChannel={isChannelUri} type={type} />;
2019-06-11 20:10:58 +02:00
}
if (!claim && (showNullPlaceholder || empty)) {
2021-01-13 16:44:44 +01:00
return empty || <ClaimPreviewNoContent isChannel={isChannelUri} type={type} />;
}
2020-02-05 19:01:07 +01:00
if (!shouldFetch && showUnresolvedClaim && !isResolvingUri && claim === null) {
return <AbandonedChannelPreview uri={contentUri} type />;
}
if (placeholder === 'publish' && !claim && contentUri.startsWith('lbry://@')) {
return null;
}
2019-06-11 20:10:58 +02:00
return (
<WrapperElement
2019-08-02 08:28:14 +02:00
ref={ref}
2019-06-11 20:10:58 +02:00
role="link"
onClick={pending || type === 'inline' ? undefined : handleOnClick}
2019-06-11 20:10:58 +02:00
onContextMenu={handleContextMenu}
2020-01-31 17:43:14 +01:00
className={classnames('claim-preview__wrapper', {
2021-01-13 16:44:44 +01:00
'claim-preview__wrapper--channel': isChannelUri && type !== 'inline',
2020-01-31 17:43:14 +01:00
'claim-preview__wrapper--inline': type === 'inline',
'claim-preview__wrapper--small': type === 'small',
2019-06-11 20:10:58 +02:00
})}
>
{!hideRepostLabel && <ClaimRepostAuthor uri={uri} />}
2019-06-11 20:10:58 +02:00
2020-01-31 17:43:14 +01:00
<div
className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip',
'claim-preview--large': type === 'large',
'claim-preview--inline': type === 'inline',
'claim-preview--tooltip': type === 'tooltip',
2021-01-13 16:44:44 +01:00
'claim-preview--channel': isChannelUri,
'claim-preview--visited': !isChannelUri && !claimIsMine && hasVisitedUri,
2020-01-31 17:43:14 +01:00
'claim-preview--pending': pending,
})}
>
2021-01-13 16:44:44 +01:00
{isChannelUri && claim ? (
<UriIndicator uri={contentUri} link>
<ChannelThumbnail uri={contentUri} obscure={channelIsBlocked} />
2020-01-31 17:43:14 +01:00
</UriIndicator>
) : (
2020-05-07 14:22:55 +02:00
<>
{!pending ? (
2020-05-07 14:22:55 +02:00
<NavLink {...navLinkProps}>
2021-01-28 23:48:41 +01:00
<FileThumbnail thumbnail={thumbnailUrlWithFallback}>
2020-05-07 14:22:55 +02:00
{/* @if TARGET='app' */}
{claim && (
<div className="claim-preview__hover-actions">
<FileDownloadLink uri={canonicalUrl} hideOpenButton hideDownloadStatus />
</div>
)}
{/* @endif */}
2021-01-13 16:44:44 +01:00
{!isRepost && !isChannelUri && (
<div className="claim-preview__file-property-overlay">
<FileProperties uri={contentUri} small />
</div>
)}
2020-05-07 14:22:55 +02:00
</FileThumbnail>
</NavLink>
) : (
2021-01-28 23:48:41 +01:00
<FileThumbnail thumbnail={thumbnailUrlWithFallback} />
2020-05-07 14:22:55 +02:00
)}
</>
2020-01-31 17:43:14 +01:00
)}
<div className="claim-preview__text">
<div className="claim-preview-metadata">
<div className="claim-preview-info">
2020-05-07 14:22:55 +02:00
{pending ? (
<ClaimPreviewTitle uri={contentUri} />
2020-05-07 14:22:55 +02:00
) : (
<NavLink {...navLinkProps}>
2021-01-13 16:44:44 +01:00
<ClaimPreviewTitle uri={uri} />
2020-05-07 14:22:55 +02:00
</NavLink>
)}
2020-01-31 17:43:14 +01:00
</div>
2021-01-13 16:44:44 +01:00
<ClaimPreviewSubtitle uri={uri} type={type} />
{(pending || !!reflectingProgress) && <PublishPending uri={uri} />}
2020-01-31 17:43:14 +01:00
</div>
2020-02-11 19:19:09 +01:00
{type !== 'small' && (
<div className="claim-preview__actions">
{!pending && (
<>
{renderActions && claim && renderActions(claim)}
{shouldHideActions || renderActions ? null : actions !== undefined ? (
2020-02-11 19:19:09 +01:00
actions
) : (
<div className="claim-preview__primary-actions">
2021-01-13 16:44:44 +01:00
{isChannelUri && !channelIsBlocked && !claimIsMine && (
<SubscribeButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
2020-02-11 19:19:09 +01:00
)}
2021-01-13 16:44:44 +01:00
{!hideBlock && isChannelUri && !isSubscribed && (!claimIsMine || channelIsBlocked) && (
<BlockButton uri={contentUri.startsWith('lbry://') ? contentUri : `lbry://${contentUri}`} />
2020-02-11 19:19:09 +01:00
)}
2021-01-15 14:32:48 +01:00
{includeSupportAction && <ClaimSupportButton uri={uri} />}
2020-02-11 19:19:09 +01:00
</div>
)}
</>
2020-02-11 19:19:09 +01:00
)}
2020-02-11 20:04:51 +01:00
{claim && (
<React.Fragment>
{typeof properties === 'function' ? (
properties(claim)
) : properties !== undefined ? (
properties
) : (
<ClaimTags uri={uri} type={type} />
)}
</React.Fragment>
)}
2020-02-11 19:19:09 +01:00
</div>
)}
2019-06-11 20:10:58 +02:00
</div>
</div>
</WrapperElement>
2019-06-11 20:10:58 +02:00
);
2019-08-02 08:28:14 +02:00
});
2019-06-11 20:10:58 +02:00
export default withRouter(ClaimPreview);