|
|
@ -1,5 +1,5 @@ |
|
|
|
import { DetailedMeta } from "@/backend/metadata/getmeta"; |
|
|
|
import { DetailedMeta } from "@/backend/metadata/getmeta"; |
|
|
|
import { MWMediaMeta } from "@/backend/metadata/types"; |
|
|
|
import { MWMediaMeta, MWMediaType } from "@/backend/metadata/types"; |
|
|
|
import { |
|
|
|
import { |
|
|
|
createContext, |
|
|
|
createContext, |
|
|
|
ReactNode, |
|
|
|
ReactNode, |
|
|
@ -33,6 +33,8 @@ function shouldSave(time: number, duration: number): boolean { |
|
|
|
interface MediaItem { |
|
|
|
interface MediaItem { |
|
|
|
meta: MWMediaMeta; |
|
|
|
meta: MWMediaMeta; |
|
|
|
series?: { |
|
|
|
series?: { |
|
|
|
|
|
|
|
episodeId: string; |
|
|
|
|
|
|
|
seasonId: string; |
|
|
|
episode: number; |
|
|
|
episode: number; |
|
|
|
season: number; |
|
|
|
season: number; |
|
|
|
}; |
|
|
|
}; |
|
|
@ -42,6 +44,7 @@ interface WatchedStoreItem { |
|
|
|
item: MediaItem; |
|
|
|
item: MediaItem; |
|
|
|
progress: number; |
|
|
|
progress: number; |
|
|
|
percentage: number; |
|
|
|
percentage: number; |
|
|
|
|
|
|
|
watchedAt: number; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export interface WatchedStoreData { |
|
|
|
export interface WatchedStoreData { |
|
|
@ -65,6 +68,15 @@ const WatchedContext = createContext<WatchedStoreDataWrapper>({ |
|
|
|
}); |
|
|
|
}); |
|
|
|
WatchedContext.displayName = "WatchedContext"; |
|
|
|
WatchedContext.displayName = "WatchedContext"; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
function isSameEpisode(media: MediaItem, v: MediaItem) { |
|
|
|
|
|
|
|
return ( |
|
|
|
|
|
|
|
media.meta.id === v.meta.id && |
|
|
|
|
|
|
|
(!media.series || |
|
|
|
|
|
|
|
(media.series.seasonId === v.series?.seasonId && |
|
|
|
|
|
|
|
media.series.episodeId === v.series?.episodeId)) |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function WatchedContextProvider(props: { children: ReactNode }) { |
|
|
|
export function WatchedContextProvider(props: { children: ReactNode }) { |
|
|
|
const watchedLocalstorage = VideoProgressStore.get(); |
|
|
|
const watchedLocalstorage = VideoProgressStore.get(); |
|
|
|
const [watched, setWatchedReal] = useState<WatchedStoreData>( |
|
|
|
const [watched, setWatchedReal] = useState<WatchedStoreData>( |
|
|
@ -95,12 +107,9 @@ export function WatchedContextProvider(props: { children: ReactNode }) { |
|
|
|
}); |
|
|
|
}); |
|
|
|
}, |
|
|
|
}, |
|
|
|
updateProgress(media: MediaItem, progress: number, total: number): void { |
|
|
|
updateProgress(media: MediaItem, progress: number, total: number): void { |
|
|
|
// TODO series support
|
|
|
|
|
|
|
|
setWatched((data: WatchedStoreData) => { |
|
|
|
setWatched((data: WatchedStoreData) => { |
|
|
|
const newData = { ...data }; |
|
|
|
const newData = { ...data }; |
|
|
|
let item = newData.items.find( |
|
|
|
let item = newData.items.find((v) => isSameEpisode(media, v.item)); |
|
|
|
(v) => v.item.meta.id === media.meta.id |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
if (!item) { |
|
|
|
if (!item) { |
|
|
|
item = { |
|
|
|
item = { |
|
|
|
item: { |
|
|
|
item: { |
|
|
@ -110,6 +119,7 @@ export function WatchedContextProvider(props: { children: ReactNode }) { |
|
|
|
}, |
|
|
|
}, |
|
|
|
progress: 0, |
|
|
|
progress: 0, |
|
|
|
percentage: 0, |
|
|
|
percentage: 0, |
|
|
|
|
|
|
|
watchedAt: Date.now(), |
|
|
|
}; |
|
|
|
}; |
|
|
|
newData.items.push(item); |
|
|
|
newData.items.push(item); |
|
|
|
} |
|
|
|
} |
|
|
@ -120,7 +130,7 @@ export function WatchedContextProvider(props: { children: ReactNode }) { |
|
|
|
// remove item if shouldnt save
|
|
|
|
// remove item if shouldnt save
|
|
|
|
if (!shouldSave(progress, total)) { |
|
|
|
if (!shouldSave(progress, total)) { |
|
|
|
newData.items = data.items.filter( |
|
|
|
newData.items = data.items.filter( |
|
|
|
(v) => v.item.meta.id !== media.meta.id |
|
|
|
(v) => !isSameEpisode(v.item, media) |
|
|
|
); |
|
|
|
); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
@ -130,34 +140,19 @@ export function WatchedContextProvider(props: { children: ReactNode }) { |
|
|
|
getFilteredWatched() { |
|
|
|
getFilteredWatched() { |
|
|
|
let filtered = watched.items; |
|
|
|
let filtered = watched.items; |
|
|
|
|
|
|
|
|
|
|
|
// get highest episode number for every anime/season
|
|
|
|
// get most recently watched for every single item
|
|
|
|
const highestEpisode: Record<string, [number, number]> = {}; |
|
|
|
const alreadyFoundMedia: string[] = []; |
|
|
|
const highestWatchedItem: Record<string, WatchedStoreItem> = {}; |
|
|
|
filtered = filtered |
|
|
|
filtered = filtered.filter((item) => { |
|
|
|
.sort((a, b) => { |
|
|
|
if (item.item.series) { |
|
|
|
return b.watchedAt - a.watchedAt; |
|
|
|
const key = item.item.meta.id; |
|
|
|
}) |
|
|
|
const current: [number, number] = [ |
|
|
|
.filter((item) => { |
|
|
|
item.item.series.episode, |
|
|
|
const mediaId = item.item.meta.id; |
|
|
|
item.item.series.season, |
|
|
|
if (alreadyFoundMedia.includes(mediaId)) return false; |
|
|
|
]; |
|
|
|
alreadyFoundMedia.push(mediaId); |
|
|
|
let existing = highestEpisode[key]; |
|
|
|
return true; |
|
|
|
if (!existing) { |
|
|
|
}); |
|
|
|
existing = current; |
|
|
|
return filtered; |
|
|
|
highestEpisode[key] = current; |
|
|
|
|
|
|
|
highestWatchedItem[key] = item; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if ( |
|
|
|
|
|
|
|
current[0] > existing[0] || |
|
|
|
|
|
|
|
(current[0] === existing[0] && current[1] > existing[1]) |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
highestEpisode[key] = current; |
|
|
|
|
|
|
|
highestWatchedItem[key] = item; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
return [...filtered, ...Object.values(highestWatchedItem)]; |
|
|
|
|
|
|
|
}, |
|
|
|
}, |
|
|
|
watched, |
|
|
|
watched, |
|
|
|
}), |
|
|
|
}), |
|
|
@ -175,26 +170,60 @@ export function useWatchedContext() { |
|
|
|
return useContext(WatchedContext); |
|
|
|
return useContext(WatchedContext); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function useWatchedItem(meta: DetailedMeta | null) { |
|
|
|
function isSameEpisodeMeta( |
|
|
|
|
|
|
|
media: MediaItem, |
|
|
|
|
|
|
|
mediaTwo: DetailedMeta | null, |
|
|
|
|
|
|
|
episodeId?: string |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
if (mediaTwo?.meta.type === MWMediaType.SERIES && episodeId) { |
|
|
|
|
|
|
|
return isSameEpisode(media, { |
|
|
|
|
|
|
|
meta: mediaTwo.meta, |
|
|
|
|
|
|
|
series: { |
|
|
|
|
|
|
|
season: 0, |
|
|
|
|
|
|
|
episode: 0, |
|
|
|
|
|
|
|
episodeId, |
|
|
|
|
|
|
|
seasonId: mediaTwo.meta.seasonData.id, |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
}); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (!mediaTwo) return () => false; |
|
|
|
|
|
|
|
return isSameEpisode(media, { meta: mediaTwo.meta }); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export function useWatchedItem(meta: DetailedMeta | null, episodeId?: string) { |
|
|
|
const { watched, updateProgress } = useContext(WatchedContext); |
|
|
|
const { watched, updateProgress } = useContext(WatchedContext); |
|
|
|
const item = useMemo( |
|
|
|
const item = useMemo( |
|
|
|
() => watched.items.find((v) => meta && v.item.meta.id === meta?.meta.id), |
|
|
|
() => watched.items.find((v) => isSameEpisodeMeta(v.item, meta, episodeId)), |
|
|
|
[watched, meta] |
|
|
|
[watched, meta, episodeId] |
|
|
|
); |
|
|
|
); |
|
|
|
const lastCommitedTime = useRef([0, 0]); |
|
|
|
const lastCommitedTime = useRef([0, 0]); |
|
|
|
|
|
|
|
|
|
|
|
const callback = useCallback( |
|
|
|
const callback = useCallback( |
|
|
|
(progress: number, total: number) => { |
|
|
|
(progress: number, total: number) => { |
|
|
|
// TODO add series support
|
|
|
|
|
|
|
|
const hasChanged = |
|
|
|
const hasChanged = |
|
|
|
lastCommitedTime.current[0] !== progress || |
|
|
|
lastCommitedTime.current[0] !== progress || |
|
|
|
lastCommitedTime.current[1] !== total; |
|
|
|
lastCommitedTime.current[1] !== total; |
|
|
|
if (meta && hasChanged) { |
|
|
|
if (meta && hasChanged) { |
|
|
|
lastCommitedTime.current = [progress, total]; |
|
|
|
lastCommitedTime.current = [progress, total]; |
|
|
|
updateProgress({ meta: meta.meta }, progress, total); |
|
|
|
const obj = { |
|
|
|
|
|
|
|
meta: meta.meta, |
|
|
|
|
|
|
|
series: |
|
|
|
|
|
|
|
meta.meta.type === MWMediaType.SERIES && episodeId |
|
|
|
|
|
|
|
? { |
|
|
|
|
|
|
|
seasonId: meta.meta.seasonData.id, |
|
|
|
|
|
|
|
episodeId, |
|
|
|
|
|
|
|
season: meta.meta.seasonData.number, |
|
|
|
|
|
|
|
episode: |
|
|
|
|
|
|
|
meta.meta.seasonData.episodes.find( |
|
|
|
|
|
|
|
(ep) => ep.id === episodeId |
|
|
|
|
|
|
|
)?.number || 0, |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
: undefined, |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
updateProgress(obj, progress, total); |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
}, |
|
|
|
[meta, updateProgress] |
|
|
|
[meta, updateProgress, episodeId] |
|
|
|
); |
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
return { updateProgress: callback, watchedItem: item }; |
|
|
|
return { updateProgress: callback, watchedItem: item }; |
|
|
|