mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-25 07:31:50 +01:00
Logic to conditionally show continue watching items
Co-authored-by: William Oldham <github@binaryoverload.co.uk>
This commit is contained in:
parent
258b3be687
commit
7a591c82b9
@ -45,8 +45,8 @@ export interface ProgressResponse {
|
||||
poster?: string;
|
||||
type: "show" | "movie";
|
||||
};
|
||||
duration: number;
|
||||
watched: number;
|
||||
duration: string;
|
||||
watched: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
@ -81,8 +81,8 @@ export function progressResponsesToEntries(responses: ProgressResponse[]) {
|
||||
const item = items[v.tmdbId];
|
||||
if (item.type === "movie") {
|
||||
item.progress = {
|
||||
duration: v.duration,
|
||||
watched: v.watched,
|
||||
duration: Number(v.duration),
|
||||
watched: Number(v.watched),
|
||||
};
|
||||
}
|
||||
|
||||
@ -97,8 +97,8 @@ export function progressResponsesToEntries(responses: ProgressResponse[]) {
|
||||
number: v.episode.number ?? 0,
|
||||
title: "",
|
||||
progress: {
|
||||
duration: v.duration,
|
||||
watched: v.watched,
|
||||
duration: Number(v.duration),
|
||||
watched: Number(v.watched),
|
||||
},
|
||||
seasonId: v.season.id,
|
||||
updatedAt: new Date(v.updatedAt).getTime(),
|
||||
|
@ -1,48 +1,47 @@
|
||||
import { useMemo } from "react";
|
||||
|
||||
import { ProgressMediaItem, useProgressStore } from "@/stores/progress";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
import {
|
||||
ShowProgressResult,
|
||||
shouldShowProgress,
|
||||
} from "@/stores/progress/utils";
|
||||
import { MediaItem } from "@/utils/mediaTypes";
|
||||
|
||||
import { MediaCard } from "./MediaCard";
|
||||
|
||||
function formatSeries(series?: ShowProgressResult | null) {
|
||||
if (!series || !series.episode || !series.season) return undefined;
|
||||
return {
|
||||
episode: series.episode?.number,
|
||||
season: series.season?.number,
|
||||
episodeId: series.episode?.id,
|
||||
seasonId: series.season?.id,
|
||||
};
|
||||
}
|
||||
|
||||
export interface WatchedMediaCardProps {
|
||||
media: MediaItem;
|
||||
closable?: boolean;
|
||||
onClose?: () => void;
|
||||
}
|
||||
|
||||
function formatSeries(obj: ProgressMediaItem | undefined) {
|
||||
if (!obj) return undefined;
|
||||
if (obj.type !== "show") return;
|
||||
const ep = Object.values(obj.episodes).sort(
|
||||
(a, b) => b.updatedAt - a.updatedAt
|
||||
)[0];
|
||||
const season = obj.seasons[ep?.seasonId];
|
||||
if (!ep || !season) return;
|
||||
return {
|
||||
season: season.number,
|
||||
episode: ep.number,
|
||||
episodeId: ep.id,
|
||||
seasonId: ep.seasonId,
|
||||
progress: ep.progress,
|
||||
};
|
||||
}
|
||||
|
||||
export function WatchedMediaCard(props: WatchedMediaCardProps) {
|
||||
const progressItems = useProgressStore((s) => s.items);
|
||||
const item = useMemo(() => {
|
||||
return progressItems[props.media.id];
|
||||
}, [progressItems, props.media]);
|
||||
const series = useMemo(() => formatSeries(item), [item]);
|
||||
const progress = item?.progress ?? series?.progress;
|
||||
const percentage = progress
|
||||
? (progress.watched / progress.duration) * 100
|
||||
const itemToDisplay = useMemo(
|
||||
() => (item ? shouldShowProgress(item) : null),
|
||||
[item]
|
||||
);
|
||||
const percentage = itemToDisplay?.show
|
||||
? (itemToDisplay.progress.watched / itemToDisplay.progress.duration) * 100
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MediaCard
|
||||
media={props.media}
|
||||
series={series}
|
||||
series={formatSeries(itemToDisplay)}
|
||||
linkable
|
||||
percentage={percentage}
|
||||
onClose={props.onClose}
|
||||
|
@ -9,6 +9,7 @@ import { MediaGrid } from "@/components/media/MediaGrid";
|
||||
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
||||
import { useBookmarkStore } from "@/stores/bookmarks";
|
||||
import { useProgressStore } from "@/stores/progress";
|
||||
import { shouldShowProgress } from "@/stores/progress/utils";
|
||||
import { MediaItem } from "@/utils/mediaTypes";
|
||||
|
||||
export function WatchingPart() {
|
||||
@ -22,6 +23,7 @@ export function WatchingPart() {
|
||||
const sortedProgressItems = useMemo(() => {
|
||||
let output: MediaItem[] = [];
|
||||
Object.entries(progressItems)
|
||||
.filter((entry) => shouldShowProgress(entry[1]).show)
|
||||
.sort((a, b) => b[1].updatedAt - a[1].updatedAt)
|
||||
.forEach((entry) => {
|
||||
output.push({
|
||||
|
83
src/stores/progress/utils.ts
Normal file
83
src/stores/progress/utils.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import {
|
||||
ProgressEpisodeItem,
|
||||
ProgressItem,
|
||||
ProgressMediaItem,
|
||||
ProgressSeasonItem,
|
||||
} from "@/stores/progress";
|
||||
|
||||
export interface ShowProgressResult {
|
||||
episode?: ProgressEpisodeItem;
|
||||
season?: ProgressSeasonItem;
|
||||
progress: ProgressItem;
|
||||
show: boolean;
|
||||
}
|
||||
|
||||
const defaultProgress = {
|
||||
duration: 0,
|
||||
watched: 0,
|
||||
};
|
||||
|
||||
function progressIsCompleted(duration: number, watched: number): boolean {
|
||||
const timeFromEnd = duration - watched;
|
||||
|
||||
// too close to the end, is completed
|
||||
if (timeFromEnd < 60 * 2) return true;
|
||||
|
||||
// satisfies all constraints, not completed
|
||||
return false;
|
||||
}
|
||||
|
||||
function progressIsNotStarted(duration: number, watched: number): boolean {
|
||||
// too short watch time
|
||||
if (watched < 20) return true;
|
||||
|
||||
// satisfies all constraints, not completed
|
||||
return false;
|
||||
}
|
||||
|
||||
function progressIsAcceptableRange(duration: number, watched: number): boolean {
|
||||
// not started enough yet, not acceptable
|
||||
if (progressIsNotStarted(duration, watched)) return false;
|
||||
|
||||
// is already at the end, not acceptable
|
||||
if (progressIsCompleted(duration, watched)) return false;
|
||||
|
||||
// satisfied all constraints
|
||||
return true;
|
||||
}
|
||||
|
||||
export function shouldShowProgress(
|
||||
item: ProgressMediaItem
|
||||
): ShowProgressResult {
|
||||
// non shows just hide or show depending on acceptable ranges
|
||||
if (item.type !== "show") {
|
||||
return {
|
||||
show: progressIsAcceptableRange(
|
||||
item.progress?.duration ?? 0,
|
||||
item.progress?.watched ?? 0
|
||||
),
|
||||
progress: item.progress ?? defaultProgress,
|
||||
};
|
||||
}
|
||||
|
||||
// shows only hide an item if its too early in episode, it still shows if its near the end.
|
||||
// Otherwise you would lose episode progress
|
||||
const ep = Object.values(item.episodes)
|
||||
.sort((a, b) => b.updatedAt - a.updatedAt)
|
||||
.filter(
|
||||
(epi) =>
|
||||
!progressIsNotStarted(epi.progress.duration, epi.progress.watched)
|
||||
)[0];
|
||||
const season = item.seasons[ep?.seasonId];
|
||||
if (!ep || !season)
|
||||
return {
|
||||
show: false,
|
||||
progress: defaultProgress,
|
||||
};
|
||||
return {
|
||||
season,
|
||||
episode: ep,
|
||||
show: true,
|
||||
progress: ep.progress,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user