13 changed files with 254 additions and 18 deletions
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
import classNames from "classnames"; |
||||
|
||||
export function Toggle(props: { onClick: () => void; enabled?: boolean }) { |
||||
return ( |
||||
<button |
||||
type="button" |
||||
onClick={props.onClick} |
||||
className={classNames( |
||||
"w-11 h-6 p-1 rounded-full grid transition-colors duration-100 group/toggle", |
||||
props.enabled ? "bg-buttons-toggle" : "bg-buttons-toggleDisabled" |
||||
)} |
||||
> |
||||
<div className="relative w-full h-full"> |
||||
<div |
||||
className={classNames( |
||||
"scale-90 group-hover/toggle:scale-100 h-full aspect-square rounded-full bg-white absolute transition-all duration-100", |
||||
props.enabled ? "left-full transform -translate-x-full" : "left-0" |
||||
)} |
||||
/> |
||||
</div> |
||||
</button> |
||||
); |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
import { useEffect, useRef } from "react"; |
||||
import { useInterval } from "react-use"; |
||||
|
||||
import { usePlayerStore } from "@/stores/player/store"; |
||||
import { useProgressStore } from "@/stores/progress"; |
||||
|
||||
export function ProgressSaver() { |
||||
const meta = usePlayerStore((s) => s.meta); |
||||
const progress = usePlayerStore((s) => s.progress); |
||||
const updateItem = useProgressStore((s) => s.updateItem); |
||||
|
||||
const updateItemRef = useRef(updateItem); |
||||
useEffect(() => { |
||||
updateItemRef.current = updateItem; |
||||
}, [updateItem]); |
||||
|
||||
const metaRef = useRef(meta); |
||||
useEffect(() => { |
||||
metaRef.current = meta; |
||||
}, [meta]); |
||||
|
||||
const progressRef = useRef(progress); |
||||
useEffect(() => { |
||||
progressRef.current = progress; |
||||
}, [progress]); |
||||
|
||||
useInterval(() => { |
||||
if (updateItemRef.current && metaRef.current && progressRef.current) |
||||
updateItemRef.current({ |
||||
meta: metaRef.current, |
||||
progress: { |
||||
duration: progress.duration, |
||||
watched: progress.time, |
||||
}, |
||||
}); |
||||
}, 3000); |
||||
|
||||
return null; |
||||
} |
@ -0,0 +1,100 @@
@@ -0,0 +1,100 @@
|
||||
import { create } from "zustand"; |
||||
import { persist } from "zustand/middleware"; |
||||
import { immer } from "zustand/middleware/immer"; |
||||
|
||||
import { PlayerMeta } from "@/stores/player/slices/source"; |
||||
|
||||
export interface ProgressItem { |
||||
watched: number; |
||||
duration: number; |
||||
} |
||||
|
||||
export interface ProgressSeasonItem { |
||||
title: string; |
||||
number: number; |
||||
id: string; |
||||
} |
||||
|
||||
export interface ProgressEpisodeItem { |
||||
title: string; |
||||
number: number; |
||||
id: string; |
||||
seasonId: string; |
||||
progress: ProgressItem; |
||||
} |
||||
|
||||
export interface ProgressMediaItem { |
||||
title: string; |
||||
year: number; |
||||
type: "show" | "movie"; |
||||
progress?: ProgressItem; |
||||
seasons: Record<string, ProgressSeasonItem>; |
||||
episodes: Record<string, ProgressEpisodeItem>; |
||||
} |
||||
|
||||
export interface UpdateItemOptions { |
||||
meta: PlayerMeta; |
||||
progress: ProgressItem; |
||||
} |
||||
|
||||
export interface ProgressStore { |
||||
items: Record<string, ProgressMediaItem>; |
||||
updateItem(ops: UpdateItemOptions): void; |
||||
} |
||||
|
||||
// TODO add migration from previous progress store
|
||||
export const useProgressStore = create( |
||||
persist( |
||||
immer<ProgressStore>((set) => ({ |
||||
items: {}, |
||||
updateItem({ meta, progress }) { |
||||
set((s) => { |
||||
if (!s.items[meta.tmdbId]) |
||||
s.items[meta.tmdbId] = { |
||||
type: meta.type, |
||||
episodes: {}, |
||||
seasons: {}, |
||||
title: meta.title, |
||||
year: meta.releaseYear, |
||||
}; |
||||
const item = s.items[meta.tmdbId]; |
||||
if (meta.type === "movie") { |
||||
if (!item.progress) |
||||
item.progress = { |
||||
duration: 0, |
||||
watched: 0, |
||||
}; |
||||
item.progress = { ...progress }; |
||||
return; |
||||
} |
||||
|
||||
if (!meta.episode || !meta.season) return; |
||||
|
||||
if (!item.seasons[meta.season.tmdbId]) |
||||
item.seasons[meta.season.tmdbId] = { |
||||
id: meta.season.tmdbId, |
||||
number: meta.season.number, |
||||
title: meta.season.title, |
||||
}; |
||||
|
||||
if (!item.episodes[meta.episode.tmdbId]) |
||||
item.episodes[meta.episode.tmdbId] = { |
||||
id: meta.episode.tmdbId, |
||||
number: meta.episode.number, |
||||
title: meta.episode.title, |
||||
seasonId: meta.season.tmdbId, |
||||
progress: { |
||||
duration: 0, |
||||
watched: 0, |
||||
}, |
||||
}; |
||||
|
||||
item.episodes[meta.episode.tmdbId].progress = { ...progress }; |
||||
}); |
||||
}, |
||||
})), |
||||
{ |
||||
name: "__MW::progress", |
||||
} |
||||
) |
||||
); |
Loading…
Reference in new issue