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:
saltrafael 2021-06-18 12:22:44 -03:00 committed by jessopb
parent 16ef013025
commit aced8fb593
11 changed files with 183 additions and 6 deletions

View file

@ -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),

View file

@ -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 && (
<> <>

View file

@ -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>
) : ( ) : (

View file

@ -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>

View file

@ -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>
} }

View file

@ -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;

View file

@ -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>

View 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);

View 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;

View file

@ -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>
} }

View file

@ -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;
}
}
} }
} }