Remove individual entries

This commit is contained in:
Raphael Wickihalder 2022-04-26 16:59:40 +02:00 committed by Thomas Zarebczan
parent 1a7b92c4fa
commit ea686474fc
6 changed files with 92 additions and 166 deletions

View file

@ -60,6 +60,7 @@ type Props = {
droppableProvided?: any, droppableProvided?: any,
unavailableUris?: Array<string>, unavailableUris?: Array<string>,
showMemberBadge?: boolean, showMemberBadge?: boolean,
inHistory?: boolean,
}; };
export default function ClaimList(props: Props) { export default function ClaimList(props: Props) {
@ -100,6 +101,7 @@ export default function ClaimList(props: Props) {
droppableProvided, droppableProvided,
unavailableUris, unavailableUris,
showMemberBadge, showMemberBadge,
inHistory,
} = props; } = props;
const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW); const [currentSort, setCurrentSort] = usePersistedState(persistedStorageKey, SORT_NEW);
@ -202,6 +204,7 @@ export default function ClaimList(props: Props) {
dragHandleProps={draggableProvided && draggableProvided.dragHandleProps} dragHandleProps={draggableProvided && draggableProvided.dragHandleProps}
unavailableUris={unavailableUris} unavailableUris={unavailableUris}
showMemberBadge={showMemberBadge} showMemberBadge={showMemberBadge}
inHistory={inHistory}
/> />
); );

View file

@ -37,6 +37,7 @@ import { ENABLE_NO_SOURCE_CLAIMS } from 'config';
import CollectionEditButtons from 'component/collectionEditButtons'; import CollectionEditButtons from 'component/collectionEditButtons';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import { useIsMobile } from 'effects/use-screensize'; import { useIsMobile } from 'effects/use-screensize';
import usePersistedState from 'effects/use-persisted-state';
const AbandonedChannelPreview = lazyImport(() => const AbandonedChannelPreview = lazyImport(() =>
import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */) import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */)
@ -96,6 +97,7 @@ type Props = {
dragHandleProps?: any, dragHandleProps?: any,
unavailableUris?: Array<string>, unavailableUris?: Array<string>,
showMemberBadge?: boolean, showMemberBadge?: boolean,
inHistory?: boolean,
}; };
const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => { const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
@ -161,6 +163,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
dragHandleProps, dragHandleProps,
unavailableUris, unavailableUris,
showMemberBadge, showMemberBadge,
inHistory,
} = props; } = props;
const isMobile = useIsMobile(); const isMobile = useIsMobile();
@ -211,6 +214,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
? claim.permanent_url || claim.canonical_url ? claim.permanent_url || claim.canonical_url
: undefined; : undefined;
const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url); const repostedContentUri = claim && (claim.reposted_claim ? claim.reposted_claim.permanent_url : claim.permanent_url);
const [watchHistory, setHistory] = usePersistedState('watch-history', []);
// Get channel title ( use name as fallback ) // Get channel title ( use name as fallback )
let channelTitle = null; let channelTitle = null;
@ -281,6 +285,7 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
function handleOnClick(e) { function handleOnClick(e) {
if (onClick) { if (onClick) {
console.log('click: ', e);
onClick(e, claim, indexInContainer); onClick(e, claim, indexInContainer);
} }
@ -292,6 +297,14 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
} }
} }
function removeFromHistory(e, uri) {
e.stopPropagation();
if (watchHistory.find((entry) => entry === uri)) {
watchHistory.splice(watchHistory.indexOf(uri), 1);
setHistory(watchHistory);
}
}
useEffect(() => { useEffect(() => {
if (isValid && !isResolvingUri && shouldFetch && uri) { if (isValid && !isResolvingUri && shouldFetch && uri) {
resolveUri(uri); resolveUri(uri);
@ -364,7 +377,6 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
> >
<> <>
{!hideRepostLabel && <ClaimRepostAuthor uri={uri} />} {!hideRepostLabel && <ClaimRepostAuthor uri={uri} />}
<div <div
className={classnames('claim-preview', { className={classnames('claim-preview', {
'claim-preview--small': type === 'small' || type === 'tooltip', 'claim-preview--small': type === 'small' || type === 'tooltip',
@ -487,7 +499,11 @@ const ClaimPreview = forwardRef<any, {}>((props: Props, ref: any) => {
)} )}
</div> </div>
</div> </div>
{inHistory && (
<div onClick={(e) => removeFromHistory(e, uri)} className="claim-preview__history-remove">
<Icon icon={ICONS.REMOVE} />
</div>
)}
{/* Todo: check isLivestreamActive once we have that data consistently everywhere. */} {/* Todo: check isLivestreamActive once we have that data consistently everywhere. */}
{claim && isLivestream && <ClaimPreviewReset uri={uri} />} {claim && isLivestream && <ClaimPreviewReset uri={uri} />}

View file

@ -90,7 +90,7 @@ const PLAYLISTS: SideNavLink = {
const HISTORY: SideNavLink = { const HISTORY: SideNavLink = {
title: 'History', title: 'History',
link: `/$/${PAGES.HISTORY}`, link: `/$/${PAGES.HISTORY}`,
icon: ICONS.TIME, icon: ICONS.EYE,
hideForUnauth: true, hideForUnauth: true,
}; };

View file

@ -154,7 +154,7 @@ function VideoViewer(props: Props) {
const [history, setHistory] = usePersistedState('watch-history', []); const [history, setHistory] = usePersistedState('watch-history', []);
React.useEffect(() => { React.useEffect(() => {
if (!history[0].indexOf(claim.permanent_url) !== -1) { if (!history[0] || !history[0].indexOf(claim.permanent_url) !== -1) {
if (!history || !history.length) { if (!history || !history.length) {
setHistory([claim.permanent_url]); setHistory([claim.permanent_url]);
} else { } else {
@ -306,7 +306,6 @@ function VideoViewer(props: Props) {
setIsEndedEmbed(false); setIsEndedEmbed(false);
setReplay(false); setReplay(false);
setDoNavigate(false); setDoNavigate(false);
// setWatchHistory()
analytics.videoIsPlaying(true, player); analytics.videoIsPlaying(true, player);
} }

View file

@ -2,27 +2,15 @@
import React from 'react'; import React from 'react';
import ClaimList from 'component/claimList'; import ClaimList from 'component/claimList';
import Page from 'component/page'; import Page from 'component/page';
import { useHistory } from 'react-router-dom';
import Card from 'component/common/card';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import * as COLLECTIONS_CONSTS from 'constants/collections';
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 Spinner from 'component/spinner';
import usePersistedState from 'effects/use-persisted-state'; import usePersistedState from 'effects/use-persisted-state';
// prettier-ignore
const Lazy = {
// $FlowFixMe
DragDropContext: React.lazy(() => import('react-beautiful-dnd' /* webpackChunkName: "dnd" */).then((module) => ({ default: module.DragDropContext }))),
// $FlowFixMe
Droppable: React.lazy(() => import('react-beautiful-dnd' /* webpackChunkName: "dnd" */).then((module) => ({ default: module.Droppable }))),
};
export const PAGE_VIEW_QUERY = 'view'; export const PAGE_VIEW_QUERY = 'view';
export const EDIT_PAGE = 'edit'; // export const EDIT_PAGE = 'edit';
type Props = { type Props = {
collectionId: string, collectionId: string,
@ -31,163 +19,51 @@ type Props = {
thumbnail: string, thumbnail: string,
collectionUrls: Array<string>, collectionUrls: Array<string>,
isResolvingCollection: boolean, isResolvingCollection: boolean,
isMyClaim: boolean, // isMyClaim: boolean,
isMyCollection: boolean, // isMyCollection: boolean,
claimIsPending: boolean, // claimIsPending: boolean,
collectionHasEdits: boolean, // collectionHasEdits: boolean,
deleteCollection: (string, string) => void, // deleteCollection: (string, string) => void,
editCollection: (string, CollectionEditParams) => void, // editCollection: (string, CollectionEditParams) => void,
fetchCollectionItems: (string, () => void) => void, fetchCollectionItems: (string, () => void) => void,
resolveUris: (string) => void, resolveUris: (string) => void,
user: ?User, user: ?User,
}; };
export default function HistoryPage(props: Props) { export default function HistoryPage(props: Props) {
const { const { collectionId } = props;
collectionId, const [history, setHistory] = usePersistedState('watch-history', []);
claim, const [unavailableUris] = React.useState([]);
collectionHasEdits,
claimIsPending,
isResolvingCollection,
editCollection,
fetchCollectionItems,
deleteCollection,
} = props;
const collection = { function clearHistory() {
id: 'watchhistory', setHistory([]);
name: __('Watch History'),
type: 'playlist',
};
const collectionUrls = [];
const [history] = usePersistedState('watch-history', []);
const {
location: { search },
} = useHistory();
const [didTryResolve, setDidTryResolve] = React.useState(false);
const [unavailableUris, setUnavailable] = React.useState([]);
const isBuiltin = COLLECTIONS_CONSTS.BUILTIN_LISTS.includes(collectionId);
function handleOnDragEnd(result) {
const { source, destination } = result;
if (!destination) return;
const { index: from } = source;
const { index: to } = destination;
editCollection(collectionId, { order: { from, to } });
} }
const urlParams = new URLSearchParams(search); return (
const editing = urlParams.get(PAGE_VIEW_QUERY) === EDIT_PAGE; <Page className="historyPage-wrapper">
<div className={classnames('section card-stack')}>
<div className="claim-list__header">
<h1 className="card__title">
<Icon icon={ICONS.EYE} style={{ marginRight: 'var(--spacing-s)' }} />
{__('Watch History')}
</h1>
const urlsReady = collectionUrls && history.length; <div className="claim-list__alt-controls--wrap">
{history.length > 0 && (
React.useEffect(() => { <Button
if (collectionId && !urlsReady && !didTryResolve && !collection) { title={__('Clear History')}
fetchCollectionItems(collectionId, () => setDidTryResolve(true)); button="primary"
} label={__('Clear History')}
}, [collectionId, urlsReady, didTryResolve, setDidTryResolve, fetchCollectionItems, collection]); onClick={() => clearHistory()}
/>
const pending = ( )}
<div className="help card__title--help"> </div>
<Spinner type={'small'} />
{__('Your publish is being confirmed and will be live soon')}
</div>
);
const unpublished = (
<Button
button="close"
icon={ICONS.REFRESH}
label={__('Clear Edits')}
onClick={() => deleteCollection(collectionId, COLLECTIONS_CONSTS.COL_KEY_EDITED)}
/>
);
const removeUnavailable = (
<Button
button="close"
icon={ICONS.DELETE}
label={__('Remove all unavailable claims')}
onClick={() => {
editCollection(collectionId, { uris: unavailableUris, remove: true });
setUnavailable([]);
}}
/>
);
let titleActions;
if (collectionHasEdits) {
titleActions = unpublished;
} else if (claimIsPending) {
titleActions = pending;
}
const listName = claim ? claim.value.title || claim.name : collection && collection.name;
const info = (
<Card
title={
<span>
<Icon
icon={
(collectionId === COLLECTIONS_CONSTS.WATCH_LATER_ID && ICONS.TIME) ||
(collectionId === COLLECTIONS_CONSTS.FAVORITES_ID && ICONS.STAR) ||
ICONS.TIME
}
className="icon--margin-right"
/>
{isBuiltin ? __(listName) : listName}
</span>
}
titleActions={unavailableUris.length > 0 ? removeUnavailable : titleActions}
/>
);
if (!collection && (isResolvingCollection || !didTryResolve)) {
return (
<Page>
<h2 className="main--empty empty">{__('Loading...')}</h2>
</Page>
);
}
if (!collection && !isResolvingCollection && didTryResolve) {
return (
<Page>
<h2 className="main--empty empty">{__('Nothing here')}</h2>
</Page>
);
}
if (urlsReady) {
return (
<Page className="playlistPage-wrapper">
{editing}
<div className={classnames('section card-stack')}>
{info}
<React.Suspense fallback={null}>
<Lazy.DragDropContext onDragEnd={handleOnDragEnd}>
<Lazy.Droppable droppableId="list__ordering">
{(DroppableProvided) => (
<ClaimList
uris={history}
collectionId={collectionId}
// showEdit={showEdit}
droppableProvided={DroppableProvided}
unavailableUris={unavailableUris}
/>
)}
</Lazy.Droppable>
</Lazy.DragDropContext>
</React.Suspense>
</div> </div>
</Page> {history.length > 0 && (
); <ClaimList uris={history} collectionId={collectionId} unavailableUris={unavailableUris} inHistory />
} )}
{history.length === 0 && <h2 className="main--empty empty">{__('Nothing here')}</h2>}
</div>
</Page>
);
} }

View file

@ -1205,6 +1205,38 @@ img {
} }
} }
.historyPage-wrapper {
.claim-preview__wrapper {
.claim-preview__history-remove {
position: absolute;
top: var(--spacing-s);
right: var(--spacing-xs);
opacity: 0;
.icon {
width: 22px;
height: 22px;
}
&:hover {
.icon {
stroke: var(--color-primary);
cursor: pointer;
}
}
}
.menu__button.claim__menu-button {
top: 2.2rem;
}
&:hover {
.claim-preview__history-remove {
opacity: 1;
}
}
}
}
.premium-wrapper { .premium-wrapper {
.membership_title { .membership_title {
.comment__badge { .comment__badge {