mirror of
https://github.com/movie-web/movie-web.git
synced 2024-12-25 20:01:49 +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;
|
poster?: string;
|
||||||
type: "show" | "movie";
|
type: "show" | "movie";
|
||||||
};
|
};
|
||||||
duration: number;
|
duration: string;
|
||||||
watched: number;
|
watched: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,8 +81,8 @@ export function progressResponsesToEntries(responses: ProgressResponse[]) {
|
|||||||
const item = items[v.tmdbId];
|
const item = items[v.tmdbId];
|
||||||
if (item.type === "movie") {
|
if (item.type === "movie") {
|
||||||
item.progress = {
|
item.progress = {
|
||||||
duration: v.duration,
|
duration: Number(v.duration),
|
||||||
watched: v.watched,
|
watched: Number(v.watched),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +97,8 @@ export function progressResponsesToEntries(responses: ProgressResponse[]) {
|
|||||||
number: v.episode.number ?? 0,
|
number: v.episode.number ?? 0,
|
||||||
title: "",
|
title: "",
|
||||||
progress: {
|
progress: {
|
||||||
duration: v.duration,
|
duration: Number(v.duration),
|
||||||
watched: v.watched,
|
watched: Number(v.watched),
|
||||||
},
|
},
|
||||||
seasonId: v.season.id,
|
seasonId: v.season.id,
|
||||||
updatedAt: new Date(v.updatedAt).getTime(),
|
updatedAt: new Date(v.updatedAt).getTime(),
|
||||||
|
@ -1,48 +1,47 @@
|
|||||||
import { useMemo } from "react";
|
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 { MediaItem } from "@/utils/mediaTypes";
|
||||||
|
|
||||||
import { MediaCard } from "./MediaCard";
|
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 {
|
export interface WatchedMediaCardProps {
|
||||||
media: MediaItem;
|
media: MediaItem;
|
||||||
closable?: boolean;
|
closable?: boolean;
|
||||||
onClose?: () => void;
|
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) {
|
export function WatchedMediaCard(props: WatchedMediaCardProps) {
|
||||||
const progressItems = useProgressStore((s) => s.items);
|
const progressItems = useProgressStore((s) => s.items);
|
||||||
const item = useMemo(() => {
|
const item = useMemo(() => {
|
||||||
return progressItems[props.media.id];
|
return progressItems[props.media.id];
|
||||||
}, [progressItems, props.media]);
|
}, [progressItems, props.media]);
|
||||||
const series = useMemo(() => formatSeries(item), [item]);
|
const itemToDisplay = useMemo(
|
||||||
const progress = item?.progress ?? series?.progress;
|
() => (item ? shouldShowProgress(item) : null),
|
||||||
const percentage = progress
|
[item]
|
||||||
? (progress.watched / progress.duration) * 100
|
);
|
||||||
|
const percentage = itemToDisplay?.show
|
||||||
|
? (itemToDisplay.progress.watched / itemToDisplay.progress.duration) * 100
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MediaCard
|
<MediaCard
|
||||||
media={props.media}
|
media={props.media}
|
||||||
series={series}
|
series={formatSeries(itemToDisplay)}
|
||||||
linkable
|
linkable
|
||||||
percentage={percentage}
|
percentage={percentage}
|
||||||
onClose={props.onClose}
|
onClose={props.onClose}
|
||||||
|
@ -9,6 +9,7 @@ import { MediaGrid } from "@/components/media/MediaGrid";
|
|||||||
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
import { WatchedMediaCard } from "@/components/media/WatchedMediaCard";
|
||||||
import { useBookmarkStore } from "@/stores/bookmarks";
|
import { useBookmarkStore } from "@/stores/bookmarks";
|
||||||
import { useProgressStore } from "@/stores/progress";
|
import { useProgressStore } from "@/stores/progress";
|
||||||
|
import { shouldShowProgress } from "@/stores/progress/utils";
|
||||||
import { MediaItem } from "@/utils/mediaTypes";
|
import { MediaItem } from "@/utils/mediaTypes";
|
||||||
|
|
||||||
export function WatchingPart() {
|
export function WatchingPart() {
|
||||||
@ -22,6 +23,7 @@ export function WatchingPart() {
|
|||||||
const sortedProgressItems = useMemo(() => {
|
const sortedProgressItems = useMemo(() => {
|
||||||
let output: MediaItem[] = [];
|
let output: MediaItem[] = [];
|
||||||
Object.entries(progressItems)
|
Object.entries(progressItems)
|
||||||
|
.filter((entry) => shouldShowProgress(entry[1]).show)
|
||||||
.sort((a, b) => b[1].updatedAt - a[1].updatedAt)
|
.sort((a, b) => b[1].updatedAt - a[1].updatedAt)
|
||||||
.forEach((entry) => {
|
.forEach((entry) => {
|
||||||
output.push({
|
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