mirror of
https://github.com/sussy-code/smov.git
synced 2024-12-21 14:47:41 +01:00
add continue watching and bookmarks back
This commit is contained in:
parent
ca169769bb
commit
a369682a26
8 changed files with 99 additions and 97 deletions
|
@ -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
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -9,7 +9,6 @@ export const BookmarkStore = versionedStoreBuilder()
|
||||||
version: 1,
|
version: 1,
|
||||||
migrate() {
|
migrate() {
|
||||||
return {
|
return {
|
||||||
// TODO actually migrate
|
|
||||||
bookmarks: [],
|
bookmarks: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -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 };
|
||||||
|
}
|
||||||
|
|
|
@ -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: [],
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue