Add watch later hover action and Favorites
add watch later hover action replace watch later popup item for favorites lint styling for watch_later overlay Add label Use just claim, add requiresAuth Add list icon Tone down text Turn WL Hover Button into component Change WL hover icons small revert Keep watch later in the menu
This commit is contained in:
parent
16ef013025
commit
aced8fb593
11 changed files with 183 additions and 6 deletions
|
@ -38,6 +38,7 @@ const select = (state, props) => {
|
||||||
claim,
|
claim,
|
||||||
claimIsMine: makeSelectSigningIsMine(props.uri)(state),
|
claimIsMine: makeSelectSigningIsMine(props.uri)(state),
|
||||||
hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state),
|
hasClaimInWatchLater: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.WATCH_LATER_ID, permanentUri)(state),
|
||||||
|
hasClaimInCustom: makeSelectCollectionForIdHasClaimUrl(COLLECTIONS_CONSTS.FAVORITES_ID, permanentUri)(state),
|
||||||
channelIsMuted: makeSelectChannelIsMuted(props.uri)(state),
|
channelIsMuted: makeSelectChannelIsMuted(props.uri)(state),
|
||||||
channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state),
|
channelIsBlocked: makeSelectChannelIsBlocked(props.uri)(state),
|
||||||
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
fileInfo: makeSelectFileInfoForUri(props.uri)(state),
|
||||||
|
|
|
@ -40,6 +40,7 @@ type Props = {
|
||||||
isRepost: boolean,
|
isRepost: boolean,
|
||||||
doCollectionEdit: (string, any) => void,
|
doCollectionEdit: (string, any) => void,
|
||||||
hasClaimInWatchLater: boolean,
|
hasClaimInWatchLater: boolean,
|
||||||
|
hasClaimInCustom: boolean,
|
||||||
claimInCollection: boolean,
|
claimInCollection: boolean,
|
||||||
collectionName?: string,
|
collectionName?: string,
|
||||||
collectionId: string,
|
collectionId: string,
|
||||||
|
@ -75,6 +76,7 @@ function ClaimMenuList(props: Props) {
|
||||||
doCommentModUnBlockAsAdmin,
|
doCommentModUnBlockAsAdmin,
|
||||||
doCollectionEdit,
|
doCollectionEdit,
|
||||||
hasClaimInWatchLater,
|
hasClaimInWatchLater,
|
||||||
|
hasClaimInCustom,
|
||||||
collectionId,
|
collectionId,
|
||||||
collectionName,
|
collectionName,
|
||||||
isMyCollection,
|
isMyCollection,
|
||||||
|
@ -96,6 +98,7 @@ function ClaimMenuList(props: Props) {
|
||||||
const isChannel = !incognitoClaim && signingChannel === claim;
|
const isChannel = !incognitoClaim && signingChannel === claim;
|
||||||
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
|
const showDelete = claimIsMine || (fileInfo && (fileInfo.written_bytes > 0 || fileInfo.blobs_completed > 0));
|
||||||
const subscriptionLabel = isSubscribed ? __('Unfollow') : __('Follow');
|
const subscriptionLabel = isSubscribed ? __('Unfollow') : __('Follow');
|
||||||
|
const lastCollectionName = 'Favorites';
|
||||||
|
|
||||||
const { push, replace } = useHistory();
|
const { push, replace } = useHistory();
|
||||||
if (!claim) {
|
if (!claim) {
|
||||||
|
@ -245,6 +248,29 @@ function ClaimMenuList(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
)}
|
)}
|
||||||
|
{/* CUSTOM LIST */}
|
||||||
|
{isPlayable && !collectionId && (
|
||||||
|
<MenuItem
|
||||||
|
className="comment__menu-option"
|
||||||
|
onSelect={() => {
|
||||||
|
doToast({
|
||||||
|
message: __(`Item %action% ${lastCollectionName}`, {
|
||||||
|
action: hasClaimInCustom ? __('removed from') : __('added to'),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
doCollectionEdit(COLLECTIONS_CONSTS.FAVORITES_ID, {
|
||||||
|
claims: [contentClaim],
|
||||||
|
remove: hasClaimInCustom,
|
||||||
|
type: 'playlist',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="menu__link">
|
||||||
|
<Icon aria-hidden icon={hasClaimInCustom ? ICONS.DELETE : ICONS.STAR} />
|
||||||
|
{hasClaimInCustom ? __(`In ${lastCollectionName}`) : __(`${lastCollectionName}`)}
|
||||||
|
</div>
|
||||||
|
</MenuItem>
|
||||||
|
)}
|
||||||
{/* COLLECTION OPERATIONS */}
|
{/* COLLECTION OPERATIONS */}
|
||||||
{collectionId && collectionName && isCollectionClaim && (
|
{collectionId && collectionName && isCollectionClaim && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -19,6 +19,7 @@ import ClaimPreviewTitle from 'component/claimPreviewTitle';
|
||||||
import ClaimPreviewSubtitle from 'component/claimPreviewSubtitle';
|
import ClaimPreviewSubtitle from 'component/claimPreviewSubtitle';
|
||||||
import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
|
import FileWatchLaterLink from 'component/fileWatchLaterLink';
|
||||||
import PublishPending from 'component/publishPending';
|
import PublishPending from 'component/publishPending';
|
||||||
import ClaimMenuList from 'component/claimMenuList';
|
import ClaimMenuList from 'component/claimMenuList';
|
||||||
import ClaimPreviewLoading from './claim-preview-loading';
|
import ClaimPreviewLoading from './claim-preview-loading';
|
||||||
|
@ -157,6 +158,15 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
isValid = false;
|
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 isCollection = claim && claim.value_type === 'collection';
|
||||||
const isChannelUri = isValid ? parseURI(uri).isChannel : false;
|
const isChannelUri = isValid ? parseURI(uri).isChannel : false;
|
||||||
const signingChannel = claim && claim.signing_channel;
|
const signingChannel = claim && claim.signing_channel;
|
||||||
|
@ -318,6 +328,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
|
||||||
<PreviewOverlayProperties uri={uri} small={type === 'small'} properties={liveProperty} />
|
<PreviewOverlayProperties uri={uri} small={type === 'small'} properties={liveProperty} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{isPlayable && (
|
||||||
|
<div className="claim-preview__hover-actions">
|
||||||
|
<FileWatchLaterLink uri={uri} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</FileThumbnail>
|
</FileThumbnail>
|
||||||
</NavLink>
|
</NavLink>
|
||||||
) : (
|
) : (
|
||||||
|
|
|
@ -13,6 +13,7 @@ import { formatLbryUrlForWeb } from 'util/url';
|
||||||
import { parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';
|
import { parseURI, COLLECTIONS_CONSTS } from 'lbry-redux';
|
||||||
import PreviewOverlayProperties from 'component/previewOverlayProperties';
|
import PreviewOverlayProperties from 'component/previewOverlayProperties';
|
||||||
import FileDownloadLink from 'component/fileDownloadLink';
|
import FileDownloadLink from 'component/fileDownloadLink';
|
||||||
|
import FileWatchLaterLink from 'component/fileWatchLaterLink';
|
||||||
import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
import ClaimRepostAuthor from 'component/claimRepostAuthor';
|
||||||
import ClaimMenuList from 'component/claimMenuList';
|
import ClaimMenuList from 'component/claimMenuList';
|
||||||
import CollectionPreviewOverlay from 'component/collectionPreviewOverlay';
|
import CollectionPreviewOverlay from 'component/collectionPreviewOverlay';
|
||||||
|
@ -75,6 +76,15 @@ function ClaimPreviewTile(props: Props) {
|
||||||
const isRepost = claim && claim.repost_channel_url;
|
const isRepost = claim && claim.repost_channel_url;
|
||||||
const isCollection = claim && claim.value_type === 'collection';
|
const isCollection = claim && claim.value_type === 'collection';
|
||||||
const isStream = claim && claim.value_type === 'stream';
|
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 collectionClaimId = isCollection && claim && claim.claim_id;
|
||||||
const shouldFetch = claim === undefined;
|
const shouldFetch = claim === undefined;
|
||||||
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
|
const thumbnailUrl = useGetThumbnail(uri, claim, streamingUrl, getFile, placeholder) || thumbnail;
|
||||||
|
@ -195,6 +205,12 @@ function ClaimPreviewTile(props: Props) {
|
||||||
)}
|
)}
|
||||||
{/* @endif */}
|
{/* @endif */}
|
||||||
|
|
||||||
|
{isPlayable && (
|
||||||
|
<div className="claim-preview__hover-actions">
|
||||||
|
<FileWatchLaterLink uri={uri} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="claim-preview__file-property-overlay">
|
<div className="claim-preview__file-property-overlay">
|
||||||
<PreviewOverlayProperties uri={uri} properties={liveProperty || properties} />
|
<PreviewOverlayProperties uri={uri} properties={liveProperty || properties} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Button from 'component/button';
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
import Icon from 'component/common/icon';
|
import Icon from 'component/common/icon';
|
||||||
import * as ICONS from 'constants/icons';
|
import * as ICONS from 'constants/icons';
|
||||||
|
import { COLLECTIONS_CONSTS } from 'lbry-redux';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
collectionUrls: Array<Claim>,
|
collectionUrls: Array<Claim>,
|
||||||
|
@ -26,7 +27,10 @@ export default function CollectionContent(props: Props) {
|
||||||
className="file-page__recommended"
|
className="file-page__recommended"
|
||||||
title={
|
title={
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={ICONS.STACK} className="icon--margin-right" />
|
<Icon
|
||||||
|
icon={(id === COLLECTIONS_CONSTS.WATCH_LATER_ID && ICONS.TIME) ||
|
||||||
|
(id === COLLECTIONS_CONSTS.FAVORITES_ID && ICONS.STAR) || ICONS.STACK}
|
||||||
|
className="icon--margin-right" />
|
||||||
{collectionName}
|
{collectionName}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ function CollectionSelectItem(props: Props) {
|
||||||
let icon;
|
let icon;
|
||||||
switch (category) {
|
switch (category) {
|
||||||
case 'builtin':
|
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;
|
break;
|
||||||
case 'published':
|
case 'published':
|
||||||
icon = ICONS.STACK;
|
icon = ICONS.STACK;
|
||||||
|
|
|
@ -68,7 +68,9 @@ export default function CollectionsListMine(props: Props) {
|
||||||
<span className="claim-grid__title-span">
|
<span className="claim-grid__title-span">
|
||||||
{__(`${list.name}`)}
|
{__(`${list.name}`)}
|
||||||
<div className="claim-grid__title--empty">
|
<div className="claim-grid__title--empty">
|
||||||
<Icon className="icon--margin-right" icon={ICONS.STACK} />
|
<Icon className="icon--margin-right"
|
||||||
|
icon={(list.id === COLLECTIONS_CONSTS.WATCH_LATER_ID && ICONS.TIME) ||
|
||||||
|
(list.id === COLLECTIONS_CONSTS.FAVORITES_ID && ICONS.STAR) || ICONS.STACK} />
|
||||||
{itemUrls.length}
|
{itemUrls.length}
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
26
ui/component/fileWatchLaterLink/index.js
Normal file
26
ui/component/fileWatchLaterLink/index.js
Normal file
|
@ -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);
|
62
ui/component/fileWatchLaterLink/view.jsx
Normal file
62
ui/component/fileWatchLaterLink/view.jsx
Normal file
|
@ -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 (
|
||||||
|
<Button
|
||||||
|
ref={buttonRef}
|
||||||
|
requiresAuth={IS_WEB}
|
||||||
|
title={title}
|
||||||
|
label={label}
|
||||||
|
className="button--file-action"
|
||||||
|
icon={(hasClaimInWatchLater && (isHovering ? ICONS.REMOVE : ICONS.COMPLETED)) || (isHovering ? ICONS.COMPLETED : ICONS.TIME)}
|
||||||
|
onClick={(e) => handleWatchLater(e)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default FileWatchLaterLink;
|
|
@ -111,7 +111,10 @@ export default function CollectionPage(props: Props) {
|
||||||
<Card
|
<Card
|
||||||
title={
|
title={
|
||||||
<span>
|
<span>
|
||||||
<Icon icon={ICONS.STACK} className="icon--margin-right" />
|
<Icon
|
||||||
|
icon={(collectionId === COLLECTIONS_CONSTS.WATCH_LATER_ID && ICONS.TIME) ||
|
||||||
|
(collectionId === COLLECTIONS_CONSTS.FAVORITES_ID && ICONS.STAR) || ICONS.STACK}
|
||||||
|
className="icon--margin-right" />
|
||||||
{claim ? claim.value.title || claim.name : collection && collection.name}
|
{claim ? claim.value.title || claim.name : collection && collection.name}
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
|
|
|
@ -677,9 +677,31 @@
|
||||||
|
|
||||||
& > * {
|
& > * {
|
||||||
color: var(--color-black);
|
color: var(--color-black);
|
||||||
background-color: var(--color-white);
|
background-color: var(--color-black);
|
||||||
padding: var(--spacing-xs);
|
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
padding: var(--spacing-xxs);
|
||||||
|
margin-right: 0;
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-button-alt-bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--file-action {
|
||||||
|
margin: 0 0;
|
||||||
|
padding: var(--spacing-xxs) var(--spacing-xxs);
|
||||||
|
height: unset;
|
||||||
|
|
||||||
|
.button__label {
|
||||||
|
color: var(--color-text);
|
||||||
|
font-size: var(--font-small);
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.button__label {
|
||||||
|
display: inline;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue