create getProgressPercentage() function that handles greater than 100% values

Replace all old uses like
(itemToDisplay.progress.watched / itemToDisplay.progress.duration) * 100
This commit is contained in:
Pas 2025-10-28 10:27:56 -06:00
parent ba59405612
commit 84165370da
8 changed files with 63 additions and 28 deletions

View file

@ -1,6 +1,6 @@
import { useMemo } from "react";
import { useProgressStore } from "@/stores/progress";
import { getProgressPercentage, useProgressStore } from "@/stores/progress";
import {
ShowProgressResult,
shouldShowProgress,
@ -36,7 +36,10 @@ export function WatchedMediaCard(props: WatchedMediaCardProps) {
[item],
);
const percentage = itemToDisplay?.show
? (itemToDisplay.progress.watched / itemToDisplay.progress.duration) * 100
? getProgressPercentage(
itemToDisplay.progress.watched,
itemToDisplay.progress.duration,
)
: undefined;
return (

View file

@ -9,7 +9,7 @@ import { Icon, Icons } from "@/components/Icon";
import { Modal, ModalCard, useModal } from "@/components/overlays/Modal";
import { hasAired } from "@/components/player/utils/aired";
import { useBookmarkStore } from "@/stores/bookmarks";
import { useProgressStore } from "@/stores/progress";
import { getProgressPercentage, useProgressStore } from "@/stores/progress";
import { EpisodeCarouselProps } from "../../types";
@ -170,9 +170,10 @@ export function EpisodeCarousel({
const episodeProgress =
progress[mediaId.toString()]?.episodes?.[episodeId];
const percentage = episodeProgress
? (episodeProgress.progress.watched /
episodeProgress.progress.duration) *
100
? getProgressPercentage(
episodeProgress.progress.watched,
episodeProgress.progress.duration,
)
: 0;
// If watched (>90%), reset to 0%, otherwise set to 100%
@ -282,9 +283,10 @@ export function EpisodeCarousel({
const episodeProgress =
progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id];
const percentage = episodeProgress
? (episodeProgress.progress.watched /
episodeProgress.progress.duration) *
100
? getProgressPercentage(
episodeProgress.progress.watched,
episodeProgress.progress.duration,
)
: 0;
const isAired = hasAired(episode.air_date);
const isWatched = percentage > 90;
@ -299,9 +301,10 @@ export function EpisodeCarousel({
const episodeProgress =
progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id];
const percentage = episodeProgress
? (episodeProgress.progress.watched /
episodeProgress.progress.duration) *
100
? getProgressPercentage(
episodeProgress.progress.watched,
episodeProgress.progress.duration,
)
: 0;
const isAired = hasAired(episode.air_date);
const isWatched = percentage > 90;
@ -378,9 +381,10 @@ export function EpisodeCarousel({
const episodeProgress =
progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id];
const percentage = episodeProgress
? (episodeProgress.progress.watched /
episodeProgress.progress.duration) *
100
? getProgressPercentage(
episodeProgress.progress.watched,
episodeProgress.progress.duration,
)
: 0;
const isAired = hasAired(episode.air_date);
const isWatched = percentage > 90;
@ -565,9 +569,10 @@ export function EpisodeCarousel({
const episodeProgress =
progress[mediaId?.toString() ?? ""]?.episodes?.[episode.id];
const percentage = episodeProgress
? (episodeProgress.progress.watched /
episodeProgress.progress.duration) *
100
? getProgressPercentage(
episodeProgress.progress.watched,
episodeProgress.progress.duration,
)
: 0;
const isAired = hasAired(episode.air_date);
const isExpanded = expandedEpisodes[episode.id];

View file

@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/buttons/Button";
import { Icon, Icons } from "@/components/Icon";
import { useWatchPartySync } from "@/hooks/useWatchPartySync";
import { getProgressPercentage } from "@/stores/progress";
import { useWatchPartyStore } from "@/stores/watchParty";
export function WatchPartyStatus() {
@ -110,7 +111,7 @@ export function WatchPartyStatus() {
</span>
<span className="text-type-secondary">
{user.player.duration > 0
? `${Math.floor((user.player.time / user.player.duration) * 100)}%`
? `${Math.floor(getProgressPercentage(user.player.time, user.player.duration))}%`
: `${Math.floor(user.player.time)}s`}
</span>
</div>

View file

@ -13,6 +13,7 @@ import { useBackendUrl } from "@/hooks/auth/useBackendUrl";
import { useOverlayRouter } from "@/hooks/useOverlayRouter";
import { useWatchPartySync } from "@/hooks/useWatchPartySync";
import { useAuthStore } from "@/stores/auth";
import { getProgressPercentage } from "@/stores/progress";
import { useWatchPartyStore } from "@/stores/watchParty";
import { useDownloadLink } from "./Downloads";
@ -326,7 +327,7 @@ export function WatchPartyView({ id }: { id: string }) {
</span>
<span className="text-type-secondary">
{user.player.duration > 0
? `${Math.floor((user.player.time / user.player.duration) * 100)}%`
? `${Math.floor(getProgressPercentage(user.player.time, user.player.duration))}%`
: `${Math.floor(user.player.time)}s`}
</span>
</div>

View file

@ -26,7 +26,7 @@ import { SourceSelectPart } from "@/pages/parts/player/SourceSelectPart";
import { useLastNonPlayerLink } from "@/stores/history";
import { PlayerMeta, playerStatus } from "@/stores/player/slices/source";
import { usePreferencesStore } from "@/stores/preferences";
import { useProgressStore } from "@/stores/progress";
import { getProgressPercentage, useProgressStore } from "@/stores/progress";
import { needsOnboarding } from "@/utils/onboarding";
import { parseTimestamp } from "@/utils/timestamp";
@ -107,16 +107,20 @@ export function RealPlayerView() {
if (meta.type === "movie") {
if (!item.progress) return false;
const percentage =
(item.progress.watched / item.progress.duration) * 100;
const percentage = getProgressPercentage(
item.progress.watched,
item.progress.duration,
);
return percentage > 80;
}
if (meta.type === "show" && meta.episode?.tmdbId) {
const episode = item.episodes?.[meta.episode.tmdbId];
if (!episode) return false;
const percentage =
(episode.progress.watched / episode.progress.duration) * 100;
const percentage = getProgressPercentage(
episode.progress.watched,
episode.progress.duration,
);
return percentage > 80;
}

View file

@ -8,7 +8,7 @@ import { Title } from "@/components/text/Title";
import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout";
import { PlayerMeta } from "@/stores/player/slices/source";
import { usePlayerStore } from "@/stores/player/store";
import { useProgressStore } from "@/stores/progress";
import { getProgressPercentage, useProgressStore } from "@/stores/progress";
export interface ResumePartProps {
onResume: () => void;
@ -30,13 +30,19 @@ export function ResumePart(props: ResumePartProps) {
if (meta.type === "movie") {
if (!item.progress) return 0;
return (item.progress.watched / item.progress.duration) * 100;
return getProgressPercentage(
item.progress.watched,
item.progress.duration,
);
}
if (meta.type === "show" && meta.episode?.tmdbId) {
const episode = item.episodes?.[meta.episode.tmdbId];
if (!episode) return 0;
return (episode.progress.watched / episode.progress.duration) * 100;
return getProgressPercentage(
episode.progress.watched,
episode.progress.duration,
);
}
return 0;

View file

@ -4,6 +4,8 @@ import { immer } from "zustand/middleware/immer";
import { PlayerMeta } from "@/stores/player/slices/source";
export { getProgressPercentage } from "./utils";
export interface ProgressItem {
watched: number;
duration: number;

View file

@ -55,6 +55,19 @@ function isFirstEpisodeOfShow(
return season.number === 1 && episode.number === 1;
}
export function getProgressPercentage(
watched: number,
duration: number,
): number {
// Handle edge cases to prevent infinity or invalid percentages
if (!duration || duration <= 0) return 0;
if (!watched || watched < 0) return 0;
// Cap percentage at 100% to prevent >100% values
const percentage = Math.min((watched / duration) * 100, 100);
return percentage;
}
export function shouldShowProgress(
item: ProgressMediaItem,
): ShowProgressResult {