add continue watching and bookmarks back

This commit is contained in:
Jelle van Snik 2023-01-16 21:19:49 +01:00
parent ca169769bb
commit a369682a26
8 changed files with 99 additions and 97 deletions

View File

@ -8,8 +8,6 @@ export interface MediaCardProps {
linkable?: boolean; linkable?: boolean;
} }
// TODO add progress back
function MediaCardContent({ media, linkable }: MediaCardProps) { function MediaCardContent({ media, linkable }: MediaCardProps) {
return ( return (
<div <div

View File

@ -35,6 +35,8 @@ if (key) {
// TODO general todos: // TODO general todos:
// - localize everything // - localize everything
// - add titles to pages // - add titles to pages
// - find place for bookmarks
// - find place for progress bar for "continue watching" section
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>

View File

@ -63,15 +63,7 @@ export function BookmarkContextProvider(props: { children: ReactNode }) {
if (bookmarked) { if (bookmarked) {
const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media); const itemIndex = getBookmarkIndexFromMedia(data.bookmarks, media);
if (itemIndex === -1) { if (itemIndex === -1) {
const item = { const item: MWMediaMeta = { ...media };
id: media.id,
type: media.type,
// providerId: media.providerId,
title: media.title,
year: media.year,
// episodeId: media.episodeId,
// seasonId: media.seasonId,
};
data.bookmarks.push(item); data.bookmarks.push(item);
} }
} else { } else {

View File

@ -9,7 +9,6 @@ export const BookmarkStore = versionedStoreBuilder()
version: 1, version: 1,
migrate() { migrate() {
return { return {
// TODO actually migrate
bookmarks: [], bookmarks: [],
}; };
}, },

View File

@ -1,5 +1,6 @@
import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types"; import { DetailedMeta } from "@/backend/metadata/getmeta";
import React, { import { MWMediaMeta } from "@/backend/metadata/types";
import {
createContext, createContext,
ReactNode, ReactNode,
useCallback, useCallback,
@ -9,7 +10,16 @@ import React, {
} from "react"; } from "react";
import { VideoProgressStore } from "./store"; import { VideoProgressStore } from "./store";
interface WatchedStoreItem extends MWMediaMeta { interface MediaItem {
meta: MWMediaMeta;
series?: {
episode: number;
season: number;
};
}
interface WatchedStoreItem {
item: MediaItem;
progress: number; progress: number;
percentage: number; percentage: number;
} }
@ -19,18 +29,11 @@ export interface WatchedStoreData {
} }
interface WatchedStoreDataWrapper { interface WatchedStoreDataWrapper {
updateProgress(media: MWMediaMeta, progress: number, total: number): void; updateProgress(media: MediaItem, progress: number, total: number): void;
getFilteredWatched(): WatchedStoreItem[]; getFilteredWatched(): WatchedStoreItem[];
watched: WatchedStoreData; watched: WatchedStoreData;
} }
export function getWatchedFromPortable(
items: WatchedStoreItem[],
media: MWMediaMeta
): WatchedStoreItem | undefined {
return undefined;
}
const WatchedContext = createContext<WatchedStoreDataWrapper>({ const WatchedContext = createContext<WatchedStoreDataWrapper>({
updateProgress: () => {}, updateProgress: () => {},
getFilteredWatched: () => [], getFilteredWatched: () => [],
@ -62,49 +65,39 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
const contextValue = useMemo( const contextValue = useMemo(
() => ({ () => ({
updateProgress( updateProgress(media: MediaItem, progress: number, total: number): void {
media: MWMediaMeta, setWatched((data: WatchedStoreData) => {
progress: number, let item = data.items.find((v) => v.item.meta.id === media.meta.id);
total: number if (!item) {
): void { item = {
// setWatched((data: WatchedStoreData) => { item: {
// let item = getWatchedFromPortable(data.items, media); ...media,
// if (!item) { meta: { ...media.meta },
// item = { series: media.series ? { ...media.series } : undefined,
// mediaId: media.mediaId, },
// mediaType: media.mediaType, progress: 0,
// providerId: media.providerId, percentage: 0,
// title: media.title, };
// year: media.year, data.items.push(item);
// percentage: 0, }
// progress: 0, // update actual item
// episodeId: media.episodeId, item.progress = progress;
// seasonId: media.seasonId, item.percentage = Math.round((progress / total) * 100);
// }; return data;
// data.items.push(item); });
// }
// // update actual item
// item.progress = progress;
// item.percentage = Math.round((progress / total) * 100);
// return data;
// });
}, },
getFilteredWatched() { getFilteredWatched() {
// remove disabled providers
// let filtered = watched.items.filter(
// (item) => getProviderMetadata(item.providerId)?.enabled
// );
let filtered = watched.items; let filtered = watched.items;
// // get highest episode number for every anime/season // get highest episode number for every anime/season
const highestEpisode: Record<string, [number, number]> = {}; const highestEpisode: Record<string, [number, number]> = {};
const highestWatchedItem: Record<string, WatchedStoreItem> = {}; const highestWatchedItem: Record<string, WatchedStoreItem> = {};
filtered = filtered.filter((item) => { filtered = filtered.filter((item) => {
if ([MWMediaType.ANIME, MWMediaType.SERIES].includes(item.type)) { if (item.item.series) {
const key = `${item.type}-${item.id}`; const key = item.item.meta.id;
const current: [number, number] = [ const current: [number, number] = [
item.episodeId ? parseInt(item.episodeId, 10) : -1, item.item.series.episode,
item.seasonId ? parseInt(item.seasonId, 10) : -1, item.item.series.season,
]; ];
let existing = highestEpisode[key]; let existing = highestEpisode[key];
if (!existing) { if (!existing) {
@ -127,7 +120,7 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
}, },
watched, watched,
}), }),
[watched] [watched, setWatched]
); );
return ( return (
@ -140,3 +133,23 @@ export function WatchedContextProvider(props: { children: ReactNode }) {
export function useWatchedContext() { export function useWatchedContext() {
return useContext(WatchedContext); return useContext(WatchedContext);
} }
export function useWatchedItem(meta: DetailedMeta | null) {
const { watched, updateProgress } = useContext(WatchedContext);
const item = useMemo(
() => watched.items.find((v) => meta && v.item.meta.id === meta?.meta.id),
[watched, meta]
);
const callback = useCallback(
(progress: number, total: number) => {
if (meta) {
// TODO add series support
updateProgress({ meta: meta.meta }, progress, total);
}
},
[updateProgress, meta]
);
return { updateProgress: callback, watchedItem: item };
}

View File

@ -8,7 +8,6 @@ export const VideoProgressStore = versionedStoreBuilder()
.addVersion({ .addVersion({
version: 1, version: 1,
migrate() { migrate() {
// TODO add migration back
return { return {
items: [], items: [],
}; };
@ -17,7 +16,6 @@ export const VideoProgressStore = versionedStoreBuilder()
.addVersion({ .addVersion({
version: 2, version: 2,
migrate() { migrate() {
// TODO actually migrate
return { return {
items: [], items: [],
}; };

View File

@ -13,6 +13,8 @@ import { MWMediaType } from "@/backend/metadata/types";
import { useGoBack } from "@/hooks/useGoBack"; import { useGoBack } from "@/hooks/useGoBack";
import { IconPatch } from "@/components/buttons/IconPatch"; import { IconPatch } from "@/components/buttons/IconPatch";
import { Icons } from "@/components/Icon"; import { Icons } from "@/components/Icon";
import { useWatchedItem } from "@/state/watched";
import { ProgressListenerControl } from "@/components/video/controls/ProgressListenerControl";
import { MediaFetchErrorView } from "./MediaErrorView"; import { MediaFetchErrorView } from "./MediaErrorView";
import { MediaScrapeLog } from "./MediaScrapeLog"; import { MediaScrapeLog } from "./MediaScrapeLog";
import { NotFoundMedia, NotFoundWrapper } from "../notfound/NotFoundView"; import { NotFoundMedia, NotFoundWrapper } from "../notfound/NotFoundView";
@ -102,6 +104,8 @@ export function MediaView() {
}); });
const [stream, setStream] = useState<MWStream | null>(null); const [stream, setStream] = useState<MWStream | null>(null);
const { updateProgress, watchedItem } = useWatchedItem(meta);
useEffect(() => { useEffect(() => {
exec(params.media).then((v) => { exec(params.media).then((v) => {
setMeta(v ?? null); setMeta(v ?? null);
@ -115,8 +119,6 @@ export function MediaView() {
}); });
}, [exec, params.media]); }, [exec, params.media]);
// TODO watched store
if (loading) return <MediaViewLoading onGoBack={goBack} />; if (loading) return <MediaViewLoading onGoBack={goBack} />;
if (error) return <MediaFetchErrorView />; if (error) return <MediaFetchErrorView />;
if (!meta || !selected) if (!meta || !selected)
@ -142,6 +144,10 @@ export function MediaView() {
<div className="h-screen w-screen"> <div className="h-screen w-screen">
<DecoratedVideoPlayer title={meta.meta.title} onGoBack={goBack} autoPlay> <DecoratedVideoPlayer title={meta.meta.title} onGoBack={goBack} autoPlay>
<SourceControl source={stream.streamUrl} type={stream.type} /> <SourceControl source={stream.streamUrl} type={stream.type} />
<ProgressListenerControl
startAt={watchedItem?.progress}
onProgress={updateProgress}
/>
</DecoratedVideoPlayer> </DecoratedVideoPlayer>
</div> </div>
); );

View File

@ -7,6 +7,7 @@ import {
useBookmarkContext, useBookmarkContext,
} from "@/state/bookmark"; } from "@/state/bookmark";
import { useWatchedContext } from "@/state/watched"; import { useWatchedContext } from "@/state/watched";
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
function Bookmarks() { function Bookmarks() {
const { t } = useTranslation(); const { t } = useTranslation();
@ -21,52 +22,45 @@ function Bookmarks() {
icon={Icons.BOOKMARK} icon={Icons.BOOKMARK}
> >
<MediaGrid> <MediaGrid>
{/* {bookmarks.map((v) => ( {bookmarks.map((v) => (
<WatchedMediaCard <WatchedMediaCard key={v.id} media={v} />
key={[v.mediaId, v.providerId].join("|")} ))}
media={v}
/>
))} */}
</MediaGrid> </MediaGrid>
</SectionHeading> </SectionHeading>
); );
} }
// function Watched() { function Watched() {
// const { t } = useTranslation(); const { t } = useTranslation();
// const { getFilteredBookmarks } = useBookmarkContext(); const { getFilteredBookmarks } = useBookmarkContext();
// const { getFilteredWatched } = useWatchedContext(); const { getFilteredWatched } = useWatchedContext();
// const bookmarks = getFilteredBookmarks(); const bookmarks = getFilteredBookmarks();
// const watchedItems = getFilteredWatched().filter( const watchedItems = getFilteredWatched().filter(
// (v) => !getIfBookmarkedFromPortable(bookmarks, v) (v) => !getIfBookmarkedFromPortable(bookmarks, v.item.meta)
// ); );
// if (watchedItems.length === 0) return null; if (watchedItems.length === 0) return null;
// return ( return (
// <SectionHeading <SectionHeading
// title={t("search.continueWatching") || "Continue Watching"} title={t("search.continueWatching") || "Continue Watching"}
// icon={Icons.CLOCK} icon={Icons.CLOCK}
// > >
// <MediaGrid> <MediaGrid>
// {/* {watchedItems.map((v) => ( {watchedItems.map((v) => (
// <WatchedMediaCard <WatchedMediaCard key={v.item.meta.id} media={v.item.meta} />
// key={[v.mediaId, v.providerId].join("|")} ))}
// media={v} </MediaGrid>
// series </SectionHeading>
// /> );
// ))} */} }
// </MediaGrid>
// </SectionHeading>
// );
// }
export function HomeView() { export function HomeView() {
return ( return (
<div className="mb-16 mt-32"> <div className="mb-16 mt-32">
{/* <Bookmarks /> */} <Bookmarks />
{/* <Watched /> */} <Watched />
</div> </div>
); );
} }