Create history page and navigation entry

This commit is contained in:
Raphael Wickihalder 2022-04-20 16:15:42 +02:00 committed by Thomas Zarebczan
parent bfb8f0dd28
commit 6d4c15f72c
5 changed files with 321 additions and 0 deletions

View file

@ -70,6 +70,7 @@ const LibraryPage = lazyImport(() => import('page/library' /* webpackChunkName:
const ListBlockedPage = lazyImport(() => import('page/listBlocked' /* webpackChunkName: "listBlocked" */)); const ListBlockedPage = lazyImport(() => import('page/listBlocked' /* webpackChunkName: "listBlocked" */));
const ListsPage = lazyImport(() => import('page/lists' /* webpackChunkName: "lists" */)); const ListsPage = lazyImport(() => import('page/lists' /* webpackChunkName: "lists" */));
const PlaylistsPage = lazyImport(() => import('page/playlists' /* webpackChunkName: "lists" */)); const PlaylistsPage = lazyImport(() => import('page/playlists' /* webpackChunkName: "lists" */));
const HistoryPage = lazyImport(() => import('page/history' /* webpackChunkName: "history" */));
const LiveStreamSetupPage = lazyImport(() => import('page/livestreamSetup' /* webpackChunkName: "livestreamSetup" */)); const LiveStreamSetupPage = lazyImport(() => import('page/livestreamSetup' /* webpackChunkName: "livestreamSetup" */));
const LivestreamCurrentPage = lazyImport(() => const LivestreamCurrentPage = lazyImport(() =>
import('page/livestreamCurrent' /* webpackChunkName: "livestreamCurrent" */) import('page/livestreamCurrent' /* webpackChunkName: "livestreamCurrent" */)
@ -362,6 +363,7 @@ function AppRouter(props: Props) {
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.LISTS}`} component={ListsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.LISTS}`} component={ListsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.PLAYLISTS}`} component={PlaylistsPage} /> <PrivateRoute {...props} path={`/$/${PAGES.PLAYLISTS}`} component={PlaylistsPage} />
<PrivateRoute {...props} path={`/$/${PAGES.HISTORY}`} component={HistoryPage} />
<PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} /> <PrivateRoute {...props} path={`/$/${PAGES.TAGS_FOLLOWING_MANAGE}`} component={TagsFollowingManagePage} />
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_BLOCKED_MUTED}`} component={ListBlockedPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_BLOCKED_MUTED}`} component={ListBlockedPage} />
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} /> <PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />

View file

@ -87,6 +87,13 @@ const PLAYLISTS: SideNavLink = {
hideForUnauth: true, hideForUnauth: true,
}; };
const HISTORY: SideNavLink = {
title: 'History',
link: `/$/${PAGES.HISTORY}`,
icon: ICONS.STACK,
hideForUnauth: true,
};
const PREMIUM: SideNavLink = { const PREMIUM: SideNavLink = {
title: 'Premium', title: 'Premium',
link: `/$/${PAGES.ODYSEE_MEMBERSHIP}`, link: `/$/${PAGES.ODYSEE_MEMBERSHIP}`,
@ -529,6 +536,7 @@ function SideNavigation(props: Props) {
{!showMicroMenu && getLink(WATCH_LATER)} {!showMicroMenu && getLink(WATCH_LATER)}
{!showMicroMenu && getLink(FAVORITES)} {!showMicroMenu && getLink(FAVORITES)}
{getLink(PLAYLISTS)} {getLink(PLAYLISTS)}
{getLink(HISTORY)}
</ul> </ul>
<ul <ul

View file

@ -34,6 +34,7 @@ exports.HELP = 'help';
exports.LIBRARY = 'library'; exports.LIBRARY = 'library';
exports.LISTS = 'lists'; exports.LISTS = 'lists';
exports.PLAYLISTS = 'playlists'; exports.PLAYLISTS = 'playlists';
exports.HISTORY = 'history';
exports.INVITE = 'invite'; exports.INVITE = 'invite';
exports.DEPRECATED__DOWNLOADED = 'downloaded'; exports.DEPRECATED__DOWNLOADED = 'downloaded';
exports.DEPRECATED__PUBLISH = 'publish'; exports.DEPRECATED__PUBLISH = 'publish';

59
ui/page/history/index.js Normal file
View file

@ -0,0 +1,59 @@
import { connect } from 'react-redux';
import { withRouter } from 'react-router-dom';
import HistoryPage from './view';
import {
selectTitleForUri,
selectClaimIsMine,
makeSelectClaimIsPending,
makeSelectClaimForClaimId,
makeSelectChannelForClaimUri,
} from 'redux/selectors/claims';
import {
makeSelectCollectionForId,
makeSelectUrlsForCollectionId,
makeSelectIsResolvingCollectionForId,
makeSelectCollectionIsMine,
makeSelectCountForCollectionId,
makeSelectEditedCollectionForId,
} from 'redux/selectors/collections';
import { getThumbnailFromClaim } from 'util/claim';
import { doFetchItemsInCollection, doCollectionDelete, doCollectionEdit } from 'redux/actions/collections';
import { selectUser } from 'redux/selectors/user';
const select = (state, props) => {
const { match } = props;
const { params } = match;
const { collectionId } = params;
const claim = collectionId && makeSelectClaimForClaimId(collectionId)(state);
const uri = (claim && (claim.canonical_url || claim.permanent_url)) || null;
return {
collectionId,
claim,
collection: makeSelectCollectionForId(collectionId)(state),
collectionUrls: makeSelectUrlsForCollectionId(collectionId)(state),
collectionCount: makeSelectCountForCollectionId(collectionId)(state),
isResolvingCollection: makeSelectIsResolvingCollectionForId(collectionId)(state),
title: selectTitleForUri(state, uri),
thumbnail: getThumbnailFromClaim(claim),
isMyClaim: selectClaimIsMine(state, claim), // or collection is mine?
isMyCollection: makeSelectCollectionIsMine(collectionId)(state),
claimIsPending: makeSelectClaimIsPending(uri)(state),
collectionHasEdits: Boolean(makeSelectEditedCollectionForId(collectionId)(state)),
uri,
user: selectUser(state),
channel: uri && makeSelectChannelForClaimUri(uri)(state),
};
};
const perform = (dispatch) => ({
fetchCollectionItems: (claimId, cb) => dispatch(doFetchItemsInCollection({ collectionId: claimId }, cb)), // if this collection is not resolved, resolve it
deleteCollection: (id, colKey) => dispatch(doCollectionDelete(id, colKey)),
editCollection: (id, params) => dispatch(doCollectionEdit(id, params)),
});
export default withRouter(connect(select, perform)(HistoryPage));

251
ui/page/history/view.jsx Normal file
View file

@ -0,0 +1,251 @@
// @flow
import React from 'react';
import ClaimList from 'component/claimList';
import Page from 'component/page';
import * as PAGES from 'constants/pages';
import { useHistory } from 'react-router-dom';
import CollectionEdit from 'component/collectionEdit';
import Card from 'component/common/card';
import Button from 'component/button';
import CollectionActions from 'component/collectionActions';
import classnames from 'classnames';
import ClaimAuthor from 'component/claimAuthor';
import FileDescription from 'component/fileDescription';
import * as COLLECTIONS_CONSTS from 'constants/collections';
import Icon from 'component/common/icon';
import * as ICONS from 'constants/icons';
import Spinner from 'component/spinner';
// 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 EDIT_PAGE = 'edit';
type Props = {
collectionId: string,
uri: string,
claim: Claim,
title: string,
thumbnail: string,
collection: Collection,
collectionUrls: Array<string>,
collectionCount: number,
isResolvingCollection: boolean,
isMyClaim: boolean,
isMyCollection: boolean,
claimIsPending: boolean,
collectionHasEdits: boolean,
deleteCollection: (string, string) => void,
editCollection: (string, CollectionEditParams) => void,
fetchCollectionItems: (string, () => void) => void,
resolveUris: (string) => void,
user: ?User,
};
export default function HistoryPage(props: Props) {
const {
collectionId,
uri,
claim,
collection,
collectionUrls,
collectionCount,
collectionHasEdits,
claimIsPending,
isResolvingCollection,
editCollection,
fetchCollectionItems,
deleteCollection,
} = props;
const {
replace,
location: { search },
} = useHistory();
const [didTryResolve, setDidTryResolve] = React.useState(false);
const [showInfo, setShowInfo] = React.useState(false);
const [showEdit, setShowEdit] = React.useState(false);
const [unavailableUris, setUnavailable] = React.useState([]);
const { name, totalItems } = collection || {};
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);
const editing = urlParams.get(PAGE_VIEW_QUERY) === EDIT_PAGE;
const urlsReady =
collectionUrls && (totalItems === undefined || (totalItems && totalItems === collectionUrls.length));
React.useEffect(() => {
if (collectionId && !urlsReady && !didTryResolve && !collection) {
fetchCollectionItems(collectionId, () => setDidTryResolve(true));
}
}, [collectionId, urlsReady, didTryResolve, setDidTryResolve, fetchCollectionItems, collection]);
const pending = (
<div className="help card__title--help">
<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 subTitle = (
<div>
<span className="collection__subtitle">
{collectionCount === 1 ? __('1 item') : __('%collectionCount% items', { collectionCount })}
</span>
{uri && <ClaimAuthor uri={uri} />}
</div>
);
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.STACK
}
className="icon--margin-right"
/>
{isBuiltin ? __(listName) : listName}
</span>
}
titleActions={unavailableUris.length > 0 ? removeUnavailable : titleActions}
subtitle={subTitle}
body={
<CollectionActions
uri={uri}
collectionId={collectionId}
setShowInfo={setShowInfo}
showInfo={showInfo}
isBuiltin={isBuiltin}
collectionUrls={collectionUrls}
setShowEdit={setShowEdit}
showEdit={showEdit}
/>
}
actions={
showInfo &&
uri && (
<div className="section">
<FileDescription uri={uri} expandOverride />
</div>
)
}
/>
);
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 (editing) {
return (
<Page
noFooter
noSideNavigation={editing}
backout={{
title: __('%action% %collection%', { collection: name, action: uri ? __('Editing') : __('Publishing') }),
simpleTitle: uri ? __('Editing') : __('Publishing'),
}}
>
<CollectionEdit
uri={uri}
collectionId={collectionId}
onDone={(id) => {
replace(`/$/${PAGES.LIST}/${id}`);
}}
/>
</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={collectionUrls}
collectionId={collectionId}
showEdit={showEdit}
droppableProvided={DroppableProvided}
unavailableUris={unavailableUris}
/>
)}
</Lazy.Droppable>
</Lazy.DragDropContext>
</React.Suspense>
</div>
</Page>
);
}
}