2019-06-11 20:10:58 +02:00
|
|
|
// @flow
|
2019-09-26 18:28:08 +02:00
|
|
|
import type { Node } from 'react';
|
2019-08-02 08:28:14 +02:00
|
|
|
import React, { Fragment, useEffect, forwardRef } from 'react';
|
2019-06-11 20:10:58 +02:00
|
|
|
import classnames from 'classnames';
|
2019-06-27 08:18:45 +02:00
|
|
|
import { parseURI, convertToShareLink } from 'lbry-redux';
|
2019-06-11 20:10:58 +02:00
|
|
|
import { withRouter } from 'react-router-dom';
|
|
|
|
import { openCopyLinkMenu } from 'util/context-menu';
|
2019-12-02 18:30:08 +01:00
|
|
|
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 TruncatedText from 'component/common/truncated-text';
|
|
|
|
import DateTime from 'component/dateTime';
|
|
|
|
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';
|
2019-07-08 22:54:58 +02:00
|
|
|
import BlockButton from 'component/blockButton';
|
2019-07-21 23:31:22 +02:00
|
|
|
import Button from 'component/button';
|
2020-01-07 22:07:12 +01:00
|
|
|
import useGetThumbnail from 'effects/use-get-thumbnail';
|
2019-06-11 20:10:58 +02:00
|
|
|
|
|
|
|
type Props = {
|
|
|
|
uri: string,
|
|
|
|
claim: ?Claim,
|
|
|
|
obscureNsfw: boolean,
|
2019-07-08 22:54:58 +02:00
|
|
|
showUserBlocked: boolean,
|
2019-06-11 20:10:58 +02:00
|
|
|
claimIsMine: boolean,
|
|
|
|
pending?: boolean,
|
|
|
|
resolveUri: string => void,
|
|
|
|
isResolvingUri: boolean,
|
|
|
|
history: { push: string => void },
|
|
|
|
thumbnail: string,
|
|
|
|
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,
|
2019-06-28 09:27:55 +02:00
|
|
|
blackListedOutpoints: Array<{
|
|
|
|
txid: string,
|
|
|
|
nout: number,
|
|
|
|
}>,
|
2019-07-17 05:32:29 +02:00
|
|
|
filteredOutpoints: Array<{
|
|
|
|
txid: string,
|
|
|
|
nout: number,
|
|
|
|
}>,
|
2019-07-08 22:54:58 +02:00
|
|
|
blockedChannelUris: Array<string>,
|
|
|
|
channelIsBlocked: boolean,
|
|
|
|
isSubscribed: boolean,
|
2019-08-14 03:24:20 +02:00
|
|
|
beginPublish: string => void,
|
2019-09-26 18:28:08 +02:00
|
|
|
actions: boolean | Node | string | number,
|
|
|
|
properties: boolean | Node | string | number,
|
|
|
|
onClick?: any => any,
|
2019-11-22 22:13:00 +01:00
|
|
|
hideBlock?: boolean,
|
2020-01-07 22:07:12 +01:00
|
|
|
streamingUrl: ?string,
|
|
|
|
getFile: string => void,
|
2020-01-08 17:36:49 +01:00
|
|
|
customShouldHide?: Claim => boolean,
|
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 {
|
|
|
|
obscureNsfw,
|
|
|
|
claimIsMine,
|
|
|
|
pending,
|
|
|
|
history,
|
|
|
|
uri,
|
|
|
|
isResolvingUri,
|
|
|
|
thumbnail,
|
|
|
|
title,
|
|
|
|
nsfw,
|
|
|
|
resolveUri,
|
|
|
|
claim,
|
|
|
|
placeholder,
|
2019-06-19 07:05:43 +02:00
|
|
|
type,
|
2019-06-28 09:27:55 +02:00
|
|
|
blackListedOutpoints,
|
2019-07-17 05:32:29 +02:00
|
|
|
filteredOutpoints,
|
2019-07-08 22:54:58 +02:00
|
|
|
blockedChannelUris,
|
2019-07-11 20:06:25 +02:00
|
|
|
hasVisitedUri,
|
2019-07-08 22:54:58 +02:00
|
|
|
showUserBlocked,
|
|
|
|
channelIsBlocked,
|
|
|
|
isSubscribed,
|
2019-08-14 03:24:20 +02:00
|
|
|
beginPublish,
|
2019-09-26 18:28:08 +02:00
|
|
|
actions,
|
|
|
|
properties,
|
|
|
|
onClick,
|
2019-11-22 22:13:00 +01:00
|
|
|
hideBlock,
|
2020-01-07 22:07:12 +01:00
|
|
|
getFile,
|
|
|
|
streamingUrl,
|
2020-01-08 17:36:49 +01:00
|
|
|
customShouldHide,
|
2019-06-11 20:10:58 +02:00
|
|
|
} = props;
|
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;
|
2019-06-11 20:10:58 +02:00
|
|
|
const claimsInChannel = (claim && claim.meta.claims_in_channel) || 0;
|
2019-07-21 23:31:22 +02:00
|
|
|
const showPublishLink = abandoned && placeholder === 'publish';
|
2019-07-23 10:05:51 +02:00
|
|
|
const hideActions = type === 'small' || type === 'tooltip';
|
2019-07-21 23:31:22 +02:00
|
|
|
|
2019-08-14 03:24:20 +02:00
|
|
|
let name;
|
2019-08-19 06:49:54 +02:00
|
|
|
let isValid = false;
|
|
|
|
if (uri) {
|
|
|
|
try {
|
2019-08-30 01:18:06 +02:00
|
|
|
({ streamName: name } = parseURI(uri));
|
2019-08-19 06:49:54 +02:00
|
|
|
isValid = true;
|
|
|
|
} catch (e) {
|
|
|
|
isValid = false;
|
|
|
|
}
|
2019-07-11 21:03:31 +02:00
|
|
|
}
|
2019-07-05 20:11:55 +02:00
|
|
|
|
2019-07-11 21:03:31 +02:00
|
|
|
const isChannel = isValid ? parseURI(uri).isChannel : false;
|
2019-09-26 18:28:08 +02:00
|
|
|
const includeChannelTooltip = type !== 'inline' && type !== 'tooltip' && !isChannel;
|
2019-07-29 16:12:53 +02:00
|
|
|
const signingChannel = claim && claim.signing_channel;
|
2019-10-24 22:41:27 +02:00
|
|
|
|
|
|
|
// do not block abandoned and nsfw claims if showUserBlocked is passed
|
2019-10-17 18:58:28 +02:00
|
|
|
let shouldHide =
|
2019-10-24 22:41:27 +02:00
|
|
|
placeholder !== 'loading' &&
|
|
|
|
!showUserBlocked &&
|
|
|
|
((abandoned && !showPublishLink) || (!claimIsMine && obscureNsfw && nsfw));
|
2019-07-05 20:11:55 +02:00
|
|
|
|
|
|
|
// This will be replaced once blocking is done at the wallet server level
|
2019-12-13 19:44:28 +01:00
|
|
|
if (claim && !claimIsMine && !shouldHide && blackListedOutpoints) {
|
2019-08-29 03:39:21 +02:00
|
|
|
shouldHide = blackListedOutpoints.some(
|
|
|
|
outpoint =>
|
|
|
|
(signingChannel && outpoint.txid === signingChannel.txid && outpoint.nout === signingChannel.nout) ||
|
|
|
|
(outpoint.txid === claim.txid && outpoint.nout === claim.nout)
|
|
|
|
);
|
2019-07-17 05:32:29 +02:00
|
|
|
}
|
2019-07-25 22:26:42 +02:00
|
|
|
// We're checking to see if the stream outpoint
|
|
|
|
// or signing channel outpoint is in the filter list
|
2019-12-13 19:44:28 +01:00
|
|
|
if (claim && !claimIsMine && !shouldHide && filteredOutpoints) {
|
2019-07-25 22:26:42 +02:00
|
|
|
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
|
|
|
}
|
2019-08-02 02:56:25 +02:00
|
|
|
// block stream claims
|
2019-07-08 22:54:58 +02:00
|
|
|
if (claim && !shouldHide && !showUserBlocked && blockedChannelUris.length && signingChannel) {
|
|
|
|
shouldHide = blockedChannelUris.some(blockedUri => blockedUri === signingChannel.permanent_url);
|
|
|
|
}
|
2019-08-02 02:56:25 +02:00
|
|
|
// block channel claims if we can't control for them in claim search
|
2019-08-02 17:11:31 +02:00
|
|
|
// e.g. fetchRecommendedSubscriptions
|
2019-10-08 18:25:33 +02:00
|
|
|
if (claim && isChannel && !shouldHide && !showUserBlocked && blockedChannelUris.length) {
|
2019-08-02 02:56:25 +02:00
|
|
|
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(uri, claim, streamingUrl, getFile, shouldHide) || thumbnail;
|
|
|
|
|
2019-06-11 20:10:58 +02:00
|
|
|
function handleContextMenu(e) {
|
2019-10-29 07:55:55 +01:00
|
|
|
// @if TARGET='app'
|
2019-06-11 20:10:58 +02:00
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
if (claim) {
|
2019-11-02 16:03:21 +01:00
|
|
|
openCopyLinkMenu(convertToShareLink(claim.canonical_url || claim.permanent_url), e);
|
2019-06-11 20:10:58 +02:00
|
|
|
}
|
2019-10-29 07:55:55 +01:00
|
|
|
// @endif
|
2019-06-11 20:10:58 +02:00
|
|
|
}
|
|
|
|
|
2019-09-26 18:28:08 +02:00
|
|
|
function handleOnClick(e) {
|
|
|
|
if (onClick) {
|
|
|
|
onClick(e);
|
|
|
|
} else if ((isChannel || title) && !pending) {
|
2019-12-02 18:30:08 +01:00
|
|
|
history.push(formatLbryUrlForWeb(claim && claim.canonical_url ? claim.canonical_url : uri));
|
2019-06-11 20:10:58 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
2019-08-25 13:42:25 +02:00
|
|
|
if (isValid && !isResolvingUri && shouldFetch && uri) {
|
2019-06-11 20:10:58 +02:00
|
|
|
resolveUri(uri);
|
|
|
|
}
|
2019-08-25 13:42:25 +02:00
|
|
|
}, [isValid, isResolvingUri, uri, resolveUri, shouldFetch]);
|
2019-06-11 20:10:58 +02:00
|
|
|
|
|
|
|
if (shouldHide) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-07-21 23:31:22 +02:00
|
|
|
if (placeholder === 'loading' || (isResolvingUri && !claim)) {
|
2019-06-11 20:10:58 +02:00
|
|
|
return (
|
2019-07-21 23:31:22 +02:00
|
|
|
<li className={classnames('claim-preview', { 'claim-preview--large': type === 'large' })} disabled>
|
2019-06-11 20:10:58 +02:00
|
|
|
<div className="placeholder media__thumb" />
|
|
|
|
<div className="placeholder__wrapper">
|
2019-06-27 08:18:45 +02:00
|
|
|
<div className="placeholder claim-preview-title" />
|
2019-06-11 20:10:58 +02:00
|
|
|
<div className="placeholder media__subtitle" />
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-12-20 16:13:50 +01:00
|
|
|
if (placeholder === 'publish' && !claim && uri.startsWith('lbry://@')) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-06-11 20:10:58 +02:00
|
|
|
return (
|
|
|
|
<li
|
2019-08-02 08:28:14 +02:00
|
|
|
ref={ref}
|
2019-06-11 20:10:58 +02:00
|
|
|
role="link"
|
2019-09-26 18:28:08 +02:00
|
|
|
onClick={pending || type === 'inline' ? undefined : handleOnClick}
|
2019-06-11 20:10:58 +02:00
|
|
|
onContextMenu={handleContextMenu}
|
2019-06-27 08:18:45 +02:00
|
|
|
className={classnames('claim-preview', {
|
2019-07-23 10:05:51 +02:00
|
|
|
'claim-preview--small': type === 'small' || type === 'tooltip',
|
2019-06-27 08:18:45 +02:00
|
|
|
'claim-preview--large': type === 'large',
|
2019-07-21 23:31:22 +02:00
|
|
|
'claim-preview--inline': type === 'inline',
|
2019-07-23 10:05:51 +02:00
|
|
|
'claim-preview--tooltip': type === 'tooltip',
|
2019-11-22 22:13:00 +01:00
|
|
|
'claim-preview--channel': isChannel,
|
2019-07-23 10:05:51 +02:00
|
|
|
'claim-preview--visited': !isChannel && !claimIsMine && hasVisitedUri,
|
2019-07-11 20:06:25 +02:00
|
|
|
'claim-preview--pending': pending,
|
2019-06-11 20:10:58 +02:00
|
|
|
})}
|
|
|
|
>
|
2019-12-11 21:54:26 +01:00
|
|
|
{isChannel && claim ? (
|
|
|
|
<UriIndicator uri={uri} link>
|
|
|
|
<ChannelThumbnail uri={uri} obscure={channelIsBlocked} />
|
|
|
|
</UriIndicator>
|
|
|
|
) : (
|
2020-01-07 22:07:12 +01:00
|
|
|
<FileThumbnail thumbnail={thumbnailUrl} />
|
2019-12-11 21:54:26 +01:00
|
|
|
)}
|
2019-12-18 06:27:08 +01:00
|
|
|
<div className="claim-preview__text">
|
|
|
|
<div className="claim-preview-metadata">
|
|
|
|
<div className="claim-preview-info">
|
|
|
|
<div className="claim-preview-title">
|
|
|
|
{claim ? <TruncatedText text={title || claim.name} lines={2} /> : <span>{__('Nothing here')}</span>}
|
|
|
|
</div>
|
|
|
|
{!isChannel && claim && <FileProperties uri={uri} />}
|
2019-06-11 20:10:58 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="media__subtitle">
|
2019-07-21 23:31:22 +02:00
|
|
|
{!isResolvingUri && (
|
|
|
|
<div>
|
|
|
|
{claim ? (
|
2019-12-18 06:27:08 +01:00
|
|
|
<React.Fragment>
|
|
|
|
<UriIndicator uri={uri} link addTooltip={includeChannelTooltip} />{' '}
|
|
|
|
{pending
|
|
|
|
? __('Pending...')
|
|
|
|
: claim &&
|
|
|
|
(isChannel ? (
|
|
|
|
type !== 'inline' && `${claimsInChannel} ${__('publishes')}`
|
|
|
|
) : (
|
|
|
|
<DateTime timeAgo uri={uri} />
|
|
|
|
))}
|
|
|
|
</React.Fragment>
|
2019-07-21 23:31:22 +02:00
|
|
|
) : (
|
|
|
|
<Fragment>
|
|
|
|
<div>{__('Publish something and claim this spot!')}</div>
|
|
|
|
<div className="card__actions">
|
2019-08-14 03:24:20 +02:00
|
|
|
<Button
|
|
|
|
onClick={() => beginPublish(name)}
|
|
|
|
button="primary"
|
|
|
|
label={`${__('Publish to')} ${uri}`}
|
|
|
|
/>
|
2019-07-21 23:31:22 +02:00
|
|
|
</div>
|
|
|
|
</Fragment>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)}
|
2019-06-11 20:10:58 +02:00
|
|
|
</div>
|
2019-12-18 06:27:08 +01:00
|
|
|
</div>
|
|
|
|
<div className="claim-preview__actions">
|
|
|
|
{!pending && (
|
|
|
|
<React.Fragment>
|
|
|
|
{hideActions ? null : actions !== undefined ? (
|
|
|
|
actions
|
|
|
|
) : (
|
|
|
|
<div className="card__actions--inline">
|
|
|
|
{isChannel && !channelIsBlocked && !claimIsMine && (
|
|
|
|
<SubscribeButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
|
|
|
|
)}
|
|
|
|
{!hideBlock && isChannel && !isSubscribed && !claimIsMine && (
|
|
|
|
<BlockButton uri={uri.startsWith('lbry://') ? uri : `lbry://${uri}`} />
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</React.Fragment>
|
|
|
|
)}
|
2019-09-26 18:28:08 +02:00
|
|
|
{properties !== undefined ? properties : <ClaimTags uri={uri} type={type} />}
|
2019-06-11 20:10:58 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</li>
|
|
|
|
);
|
2019-08-02 08:28:14 +02:00
|
|
|
});
|
2019-06-11 20:10:58 +02:00
|
|
|
|
2019-06-28 09:27:55 +02:00
|
|
|
export default withRouter(ClaimPreview);
|