Create history page and navigation entry
This commit is contained in:
parent
bfb8f0dd28
commit
6d4c15f72c
5 changed files with 321 additions and 0 deletions
|
@ -70,6 +70,7 @@ const LibraryPage = lazyImport(() => import('page/library' /* webpackChunkName:
|
|||
const ListBlockedPage = lazyImport(() => import('page/listBlocked' /* webpackChunkName: "listBlocked" */));
|
||||
const ListsPage = lazyImport(() => import('page/lists' /* 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 LivestreamCurrentPage = lazyImport(() =>
|
||||
import('page/livestreamCurrent' /* webpackChunkName: "livestreamCurrent" */)
|
||||
|
@ -362,6 +363,7 @@ function AppRouter(props: Props) {
|
|||
<PrivateRoute {...props} path={`/$/${PAGES.LIBRARY}`} component={LibraryPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.LISTS}`} component={ListsPage} />
|
||||
<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.SETTINGS_BLOCKED_MUTED}`} component={ListBlockedPage} />
|
||||
<PrivateRoute {...props} path={`/$/${PAGES.SETTINGS_CREATOR}`} component={SettingsCreatorPage} />
|
||||
|
|
|
@ -87,6 +87,13 @@ const PLAYLISTS: SideNavLink = {
|
|||
hideForUnauth: true,
|
||||
};
|
||||
|
||||
const HISTORY: SideNavLink = {
|
||||
title: 'History',
|
||||
link: `/$/${PAGES.HISTORY}`,
|
||||
icon: ICONS.STACK,
|
||||
hideForUnauth: true,
|
||||
};
|
||||
|
||||
const PREMIUM: SideNavLink = {
|
||||
title: 'Premium',
|
||||
link: `/$/${PAGES.ODYSEE_MEMBERSHIP}`,
|
||||
|
@ -529,6 +536,7 @@ function SideNavigation(props: Props) {
|
|||
{!showMicroMenu && getLink(WATCH_LATER)}
|
||||
{!showMicroMenu && getLink(FAVORITES)}
|
||||
{getLink(PLAYLISTS)}
|
||||
{getLink(HISTORY)}
|
||||
</ul>
|
||||
|
||||
<ul
|
||||
|
|
|
@ -34,6 +34,7 @@ exports.HELP = 'help';
|
|||
exports.LIBRARY = 'library';
|
||||
exports.LISTS = 'lists';
|
||||
exports.PLAYLISTS = 'playlists';
|
||||
exports.HISTORY = 'history';
|
||||
exports.INVITE = 'invite';
|
||||
exports.DEPRECATED__DOWNLOADED = 'downloaded';
|
||||
exports.DEPRECATED__PUBLISH = 'publish';
|
||||
|
|
59
ui/page/history/index.js
Normal file
59
ui/page/history/index.js
Normal 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
251
ui/page/history/view.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue