Again... Ignore

This commit is contained in:
OkinawaBoss 2025-09-17 18:27:32 -07:00
parent c16ab49d56
commit 763e5afef8
6 changed files with 296 additions and 64 deletions

View file

@ -1,4 +1,4 @@
# Generated by Django 5.0.14 on 2025-09-16 18:34
# Generated by Django 5.2.4 on 2025-09-18 01:25
import django.core.validators
import django.db.models.deletion
@ -63,6 +63,7 @@ class Migration(migrations.Migration):
('started_at', models.DateTimeField(blank=True, null=True)),
('finished_at', models.DateTimeField(blank=True, null=True)),
('total_files', models.PositiveIntegerField(default=0)),
('processed_files', models.PositiveIntegerField(default=0)),
('new_files', models.PositiveIntegerField(default=0)),
('updated_files', models.PositiveIntegerField(default=0)),
('removed_files', models.PositiveIntegerField(default=0)),

View file

@ -1,18 +0,0 @@
# Generated by Django 5.0.14 on 2025-09-17 15:54
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('media_library', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='libraryscan',
name='processed_files',
field=models.PositiveIntegerField(default=0),
),
]

View file

@ -12,6 +12,8 @@ services:
- 5678:5678 # Debugging port
volumes:
- ../:/app
- "D:/Server Backup/Movies:/library/Movies"
- "D:/Server Backup/TV Shows:/library/TVShows"
environment:
- DISPATCHARR_ENV=dev
- DISPATCHARR_DEBUG=true

View file

@ -24,6 +24,7 @@
"dayjs": "^1.11.13",
"formik": "^2.4.6",
"hls.js": "^1.5.20",
"immer": "^10.1.1",
"lucide-react": "^0.511.0",
"mpegts.js": "^1.8.0",
"react": "^19.0.0",
@ -2177,6 +2178,16 @@
"node": ">= 4"
}
},
"node_modules/immer": {
"version": "10.1.3",
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.3.tgz",
"integrity": "sha512-tmjF/k8QDKydUlm3mZU+tjM6zeq9/fFpPqH9SzWmBnVVKsPBg/V66qsMwb3/Bo90cgUN+ghdVBess+hPsxUyRw==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/immer"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",

View file

@ -14,6 +14,7 @@ import {
Stack,
Text,
Title,
rem,
} from '@mantine/core';
import { notifications } from '@mantine/notifications';
import {
@ -31,18 +32,26 @@ import API from '../../api';
import useMediaLibraryStore from '../../store/mediaLibrary';
import useVideoStore from '../../store/useVideoStore';
// ---- quick tuning knobs ----
const CAST_TILE_WIDTH = 96; // was 116
const CAST_TILE_HEIGHT = 148; // was 160
const CAST_AVATAR_SIZE = 72; // was 80
const CAST_GAP = 8; // was "md" (~16)
const STACK_TIGHT = 4; // was 6
const SECTION_STACK = 12; // was larger in some places
// ----------------------------
const runtimeLabel = (runtimeMs) => {
if (!runtimeMs) return null;
const totalSeconds = Math.round(runtimeMs / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
if (hours) {
return `${hours}h ${minutes}m`;
}
if (hours) return `${hours}h ${minutes}m`;
return `${minutes}m`;
};
const MediaDetailModal = ({ opened, onClose }) => {
<<<<<<< Updated upstream
const activeItem = useMediaLibraryStore((state) => state.activeItem);
const activeItemLoading = useMediaLibraryStore((state) => state.activeItemLoading);
const activeProgress = useMediaLibraryStore((state) => state.activeProgress);
@ -59,6 +68,26 @@ const MediaDetailModal = ({ opened, onClose }) => {
const [episodePlayLoadingId, setEpisodePlayLoadingId] = useState(null);
const [episodeActionLoading, setEpisodeActionLoading] = useState({});
=======
const activeItem = useMediaLibraryStore((s) => s.activeItem);
const activeItemLoading = useMediaLibraryStore((s) => s.activeItemLoading);
const activeProgress = useMediaLibraryStore((s) => s.activeProgress);
const resumePrompt = useMediaLibraryStore((s) => s.resumePrompt);
const requestResume = useMediaLibraryStore((s) => s.requestResume);
const clearResumePrompt = useMediaLibraryStore((s) => s.clearResumePrompt);
const setActiveProgress = useMediaLibraryStore((s) => s.setActiveProgress);
const showVideo = useVideoStore((s) => s.showVideo);
const [startingPlayback, setStartingPlayback] = useState(false);
const [resumeModalOpen, setResumeModalOpen] = useState(false);
const [resumeMode, setResumeMode] = useState('start');
const [episodes, setEpisodes] = useState([]);
const [episodesLoading, setEpisodesLoading] = useState(false);
const [episodePlayLoadingId, setEpisodePlayLoadingId] = useState(null);
const [episodeActionLoading, setEpisodeActionLoading] = useState({});
>>>>>>> Stashed changes
const setEpisodeLoading = useCallback((episodeId, action) => {
setEpisodeActionLoading((prev) => ({ ...prev, [episodeId]: action }));
}, []);
@ -100,6 +129,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
}, [activeItem]);
const orderedEpisodes = useMemo(() => {
<<<<<<< Updated upstream
if (!episodes || episodes.length === 0) {
return [];
}
@ -114,6 +144,16 @@ const MediaDetailModal = ({ opened, onClose }) => {
if (episodeA !== episodeB) {
return episodeA - episodeB;
}
=======
if (!episodes || episodes.length === 0) return [];
return [...episodes].sort((a, b) => {
const seasonA = a.season_number ?? 0;
const seasonB = b.season_number ?? 0;
if (seasonA !== seasonB) return seasonA - seasonB;
const episodeA = a.episode_number ?? 0;
const episodeB = b.episode_number ?? 0;
if (episodeA !== episodeB) return episodeA - episodeB;
>>>>>>> Stashed changes
return (a.sort_title || '').localeCompare(b.sort_title || '');
});
}, [episodes]);
@ -121,6 +161,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
const showWatchSummary = activeItem?.watch_summary || null;
const playbackPlan = useMemo(() => {
<<<<<<< Updated upstream
if (!activeItem || activeItem.item_type !== 'show') {
return null;
}
@ -163,7 +204,37 @@ const MediaDetailModal = ({ opened, onClose }) => {
const canResume = useMemo(() => {
if (!activeProgress || !activeProgress.position_ms || !activeProgress.duration_ms) {
return false;
=======
if (!activeItem || activeItem.item_type !== 'show') return null;
const sorted = orderedEpisodes;
if (!sorted || sorted.length === 0) {
return { sorted: [], resumeEpisode: null, nextEpisode: null };
>>>>>>> Stashed changes
}
const resumeId = showWatchSummary?.resume_episode_id;
const nextId = showWatchSummary?.next_episode_id;
let resumeEpisode = sorted.find((ep) => ep.id === resumeId) || null;
let nextEpisode = sorted.find((ep) => ep.id === nextId) || null;
if (!resumeEpisode) {
resumeEpisode = sorted.find((ep) => ep.watch_summary?.status === 'in_progress') || null;
}
if (!resumeEpisode) {
resumeEpisode = sorted.find((ep) => ep.watch_summary?.status !== 'watched') || sorted[0];
}
if (!nextEpisode && resumeEpisode) {
const currentIndex = sorted.findIndex((ep) => ep.id === resumeEpisode.id);
if (currentIndex >= 0 && currentIndex + 1 < sorted.length) {
nextEpisode = sorted[currentIndex + 1];
}
}
return { sorted, resumeEpisode, nextEpisode };
}, [activeItem, orderedEpisodes, showWatchSummary]);
const canResume = useMemo(() => {
if (!activeProgress || !activeProgress.position_ms || !activeProgress.duration_ms) return false;
const remaining = activeProgress.duration_ms - activeProgress.position_ms;
return remaining > activeProgress.duration_ms * 0.04;
}, [activeProgress]);
@ -191,6 +262,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
}, [opened, activeItem?.id, activeItem?.item_type, loadEpisodes]);
const castPeople = useMemo(() => {
<<<<<<< Updated upstream
if (!activeItem?.cast) {
return [];
}
@ -211,6 +283,17 @@ const MediaDetailModal = ({ opened, onClose }) => {
if (!name) {
return null;
}
=======
if (!activeItem?.cast) return [];
return activeItem.cast
.map((entry, index) => {
if (!entry) return null;
if (typeof entry === 'string') {
return { key: `${entry}-${index}`, name: entry, role: null, profile: null };
}
const name = entry.name || entry.character || entry.job;
if (!name) return null;
>>>>>>> Stashed changes
return {
key: `${name}-${index}`,
name,
@ -222,6 +305,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
}, [activeItem]);
const crewPeople = useMemo(() => {
<<<<<<< Updated upstream
if (!activeItem?.crew) {
return [];
}
@ -242,6 +326,17 @@ const MediaDetailModal = ({ opened, onClose }) => {
if (!name) {
return null;
}
=======
if (!activeItem?.crew) return [];
return activeItem.crew
.map((entry, index) => {
if (!entry) return null;
if (typeof entry === 'string') {
return { key: `${entry}-${index}`, name: entry, role: null, profile: null };
}
const name = entry.name || entry.job || entry.department;
if (!name) return null;
>>>>>>> Stashed changes
return {
key: `${name}-${index}`,
name,
@ -266,9 +361,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
setResumeMode(mode);
setStartingPlayback(true);
try {
const streamInfo = await API.streamMediaItem(activeItem.id, {
fileId,
});
const streamInfo = await API.streamMediaItem(activeItem.id, { fileId });
const playbackUrl = streamInfo?.url || streamInfo?.stream_url;
if (!playbackUrl) {
notifications.show({
@ -330,13 +423,31 @@ const MediaDetailModal = ({ opened, onClose }) => {
return 'Play';
}, [activeItem, showWatchSummary, canResume, resumePrompt, activeProgress]);
<<<<<<< Updated upstream
=======
const primaryButtonLabel = useMemo(() => {
if (!activeItem) return 'Play';
if (activeItem.item_type === 'show') {
if (showWatchSummary?.status === 'in_progress') return 'Continue Watching';
if (showWatchSummary?.status === 'watched') return 'Watch Again';
return 'Play';
}
if (canResume && (resumePrompt || activeProgress)) return 'Continue Watching';
return 'Play';
}, [activeItem, showWatchSummary, canResume, resumePrompt, activeProgress]);
>>>>>>> Stashed changes
const handlePrimaryAction = () => {
if (!activeItem) return;
if (activeItem.item_type === 'show') {
const targetEpisode =
<<<<<<< Updated upstream
playbackPlan?.resumeEpisode ||
playbackPlan?.nextEpisode ||
playbackPlan?.sorted?.[0];
=======
playbackPlan?.resumeEpisode || playbackPlan?.nextEpisode || playbackPlan?.sorted?.[0];
>>>>>>> Stashed changes
if (!targetEpisode) {
notifications.show({
title: 'No episodes available',
@ -345,6 +456,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
});
return;
}
<<<<<<< Updated upstream
const startIndex = playbackPlan.sorted.findIndex(
(ep) => ep.id === targetEpisode.id
);
@ -352,6 +464,10 @@ const MediaDetailModal = ({ opened, onClose }) => {
sequence: playbackPlan.sorted,
startIndex,
});
=======
const startIndex = playbackPlan.sorted.findIndex((ep) => ep.id === targetEpisode.id);
void handlePlayEpisode(targetEpisode, { sequence: playbackPlan.sorted, startIndex });
>>>>>>> Stashed changes
return;
}
if (canResume && (resumePrompt || activeProgress)) {
@ -380,9 +496,13 @@ const MediaDetailModal = ({ opened, onClose }) => {
return;
}
<<<<<<< Updated upstream
const streamInfo = await API.streamMediaItem(episodeDetail.id, {
fileId: episodeFileId,
});
=======
const streamInfo = await API.streamMediaItem(episodeDetail.id, { fileId: episodeFileId });
>>>>>>> Stashed changes
const playbackUrl = streamInfo?.url || streamInfo?.stream_url;
if (!playbackUrl) {
notifications.show({
@ -400,10 +520,14 @@ const MediaDetailModal = ({ opened, onClose }) => {
const computedIndex = startIndex ?? episodeIds.findIndex((id) => id === episode.id);
const playbackSequence = episodeIds.length
<<<<<<< Updated upstream
? {
episodeIds,
currentIndex: computedIndex >= 0 ? computedIndex : 0,
}
=======
? { episodeIds, currentIndex: computedIndex >= 0 ? computedIndex : 0 }
>>>>>>> Stashed changes
: null;
const episodeProgress = episodeDetail.watch_progress;
@ -452,21 +576,33 @@ const MediaDetailModal = ({ opened, onClose }) => {
const grouped = new Map();
orderedEpisodes.forEach((episode) => {
const season = episode.season_number || 1;
<<<<<<< Updated upstream
if (!grouped.has(season)) {
grouped.set(season, []);
}
=======
if (!grouped.has(season)) grouped.set(season, []);
>>>>>>> Stashed changes
grouped.get(season).push(episode);
});
return grouped;
}, [orderedEpisodes]);
<<<<<<< Updated upstream
const sortedSeasons = useMemo(() => {
return Array.from(episodesBySeason.keys()).sort((a, b) => a - b);
}, [episodesBySeason]);
=======
const sortedSeasons = useMemo(
() => Array.from(episodesBySeason.keys()).sort((a, b) => a - b),
[episodesBySeason]
);
>>>>>>> Stashed changes
const formatEpisodeCode = (episode) => {
const season = episode.season_number || 0;
const ep = episode.episode_number || 0;
<<<<<<< Updated upstream
if (!season && !ep) {
return '';
}
@ -476,6 +612,11 @@ const MediaDetailModal = ({ opened, onClose }) => {
if (!ep) {
return `S${season.toString().padStart(2, '0')}`;
}
=======
if (!season && !ep) return '';
if (!season) return `E${ep.toString().padStart(2, '0')}`;
if (!ep) return `S${season.toString().padStart(2, '0')}`;
>>>>>>> Stashed changes
return `S${season.toString().padStart(2, '0')}E${ep.toString().padStart(2, '0')}`;
};
@ -563,10 +704,14 @@ const MediaDetailModal = ({ opened, onClose }) => {
<ScrollArea h="70vh" offsetScrollbars>
<Group align="flex-start" gap="xl" wrap="wrap">
{activeItem.poster_url ? (
<<<<<<< Updated upstream
<Box
w={{ base: '100%', sm: 240 }}
style={{ flexShrink: 0, maxWidth: 260 }}
>
=======
<Box w={{ base: '100%', sm: 240 }} style={{ flexShrink: 0, maxWidth: 260 }}>
>>>>>>> Stashed changes
<Box
style={{
borderRadius: 16,
@ -589,8 +734,13 @@ const MediaDetailModal = ({ opened, onClose }) => {
</Box>
) : null}
<<<<<<< Updated upstream
<Stack spacing="xl" style={{ flex: 1, minWidth: 0 }}>
<Stack spacing="sm">
=======
<Stack spacing={SECTION_STACK} style={{ flex: 1, minWidth: 0 }}>
<Stack spacing={STACK_TIGHT}>
>>>>>>> Stashed changes
<Group justify="space-between" align="center">
<Title order={3}>{activeItem.title}</Title>
<Group gap="xs">
@ -625,15 +775,11 @@ const MediaDetailModal = ({ opened, onClose }) => {
)}
<Group gap="lg" mt="sm">
{runtimeLabel(
activeItem.runtime_ms || files[0]?.duration_ms
) && (
{runtimeLabel(activeItem.runtime_ms || files[0]?.duration_ms) && (
<Group gap={4} align="center">
<Clock size={18} />
<Text size="sm">
{runtimeLabel(
activeItem.runtime_ms || files[0]?.duration_ms
)}
{runtimeLabel(activeItem.runtime_ms || files[0]?.duration_ms)}
</Text>
</Group>
)}
@ -648,6 +794,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
)}
</Group>
<<<<<<< Updated upstream
{activeItem.item_type === 'show' &&
showWatchSummary?.total_episodes ? (
<Text size="sm" c="dimmed">
@ -655,6 +802,14 @@ const MediaDetailModal = ({ opened, onClose }) => {
{showWatchSummary.total_episodes} episodes watched
</Text>
) : null}
=======
{activeItem.item_type === 'show' && showWatchSummary?.total_episodes ? (
<Text size="sm" c="dimmed">
{showWatchSummary.completed_episodes || 0} of{' '}
{showWatchSummary.total_episodes} episodes watched
</Text>
) : null}
>>>>>>> Stashed changes
<Group gap="sm" mt="md" align="center" wrap="wrap">
<Button
@ -672,13 +827,11 @@ const MediaDetailModal = ({ opened, onClose }) => {
<Text size="sm" c="dimmed">
Resume at{' '}
{runtimeLabel(
resumePrompt?.position_ms ||
activeProgress?.position_ms
resumePrompt?.position_ms || activeProgress?.position_ms
)}{' '}
of{' '}
{runtimeLabel(
resumePrompt?.duration_ms ||
activeProgress?.duration_ms
resumePrompt?.duration_ms || activeProgress?.duration_ms
)}
</Text>
)}
@ -708,7 +861,11 @@ const MediaDetailModal = ({ opened, onClose }) => {
{sortedSeasons.map((season) => {
const seasonEpisodes = episodesBySeason.get(season) || [];
return (
<<<<<<< Updated upstream
<Stack key={season} spacing={6}>
=======
<Stack key={season} spacing={STACK_TIGHT}>
>>>>>>> Stashed changes
<Group justify="space-between" align="center">
<Group gap="xs" align="center">
<Title order={5}>Season {season}</Title>
@ -718,7 +875,11 @@ const MediaDetailModal = ({ opened, onClose }) => {
</Badge>
</Group>
</Group>
<<<<<<< Updated upstream
<Stack spacing={6}>
=======
<Stack spacing={STACK_TIGHT}>
>>>>>>> Stashed changes
{seasonEpisodes.map((episode) => {
const episodeProgress = episode.watch_progress;
const episodeStatus = episode.watch_summary?.status;
@ -740,9 +901,15 @@ const MediaDetailModal = ({ opened, onClose }) => {
padding: '10px 12px',
}}
>
<<<<<<< Updated upstream
<Stack spacing={4} style={{ flex: 1, minWidth: 0 }}>
<Group justify="space-between" align="center">
<Text fw={600} size="sm">
=======
<Stack spacing={STACK_TIGHT} style={{ flex: 1, minWidth: 0 }}>
<Group justify="space-between" align="center">
<Text fw={600} size="sm" lineClamp={1}>
>>>>>>> Stashed changes
{[formatEpisodeCode(episode), episode.title]
.filter(Boolean)
.join(' ')}
@ -780,12 +947,20 @@ const MediaDetailModal = ({ opened, onClose }) => {
)}
</Group>
{(episode.synopsis || activeItem?.synopsis) && (
<<<<<<< Updated upstream
<Text size="xs" c="dimmed" lineClamp={3}>
=======
<Text size="xs" c="dimmed" lineClamp={2}>
>>>>>>> Stashed changes
{episode.synopsis || activeItem?.synopsis}
</Text>
)}
</Stack>
<<<<<<< Updated upstream
<Stack spacing={6} align="flex-end">
=======
<Stack spacing={STACK_TIGHT} align="flex-end">
>>>>>>> Stashed changes
<Group gap={6}>
<Button
size="xs"
@ -840,8 +1015,13 @@ const MediaDetailModal = ({ opened, onClose }) => {
<Divider label="Metadata" labelPosition="center" />
<<<<<<< Updated upstream
<Stack spacing="sm">
<Group gap="sm">
=======
<Stack spacing={STACK_TIGHT}>
<Group gap="xs">
>>>>>>> Stashed changes
{activeItem.tmdb_id && (
<Badge
component="a"
@ -866,6 +1046,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
)}
</Group>
<<<<<<< Updated upstream
{castPeople.length > 0 && (
<Stack spacing="sm">
<Text fw={500}>Cast</Text>
@ -880,10 +1061,36 @@ const MediaDetailModal = ({ opened, onClose }) => {
>
<Avatar
size={80}
=======
{/* CAST */}
{castPeople.length > 0 && (
<Stack spacing={STACK_TIGHT}>
<Text fw={500}>Cast</Text>
<ScrollArea
type="auto"
offsetScrollbars
styles={{ viewport: { paddingBottom: rem(6) } }}
>
<Group gap={CAST_GAP} wrap="nowrap">
{castPeople.map((person) => (
<Stack
key={person.key}
spacing={STACK_TIGHT}
align="center"
style={{
width: rem(CAST_TILE_WIDTH),
height: rem(CAST_TILE_HEIGHT),
flex: `0 0 ${rem(CAST_TILE_WIDTH)}`,
}}
>
<Avatar
size={CAST_AVATAR_SIZE}
>>>>>>> Stashed changes
radius="md"
src={person.profile || undefined}
alt={person.name}
color="indigo"
<<<<<<< Updated upstream
>
{!person.profile && person.name?.[0]}
</Avatar>
@ -892,6 +1099,17 @@ const MediaDetailModal = ({ opened, onClose }) => {
</Text>
{person.role && (
<Text size="xs" c="dimmed" ta="center" lineClamp={2}>
=======
styles={{ image: { objectFit: 'cover' } }}
>
{!person.profile && person.name?.[0]}
</Avatar>
<Text size="xs" fw={600} ta="center" lineClamp={2} style={{ minHeight: 30 }}>
{person.name}
</Text>
{person.role && (
<Text size="xs" c="dimmed" ta="center" lineClamp={1} style={{ minHeight: 16 }}>
>>>>>>> Stashed changes
{person.role}
</Text>
)}
@ -902,6 +1120,7 @@ const MediaDetailModal = ({ opened, onClose }) => {
</Stack>
)}
<<<<<<< Updated upstream
{crewPeople.length > 0 && (
<Stack spacing="sm">
<Text fw={500}>Crew</Text>
@ -916,10 +1135,36 @@ const MediaDetailModal = ({ opened, onClose }) => {
>
<Avatar
size={80}
=======
{/* CREW */}
{crewPeople.length > 0 && (
<Stack spacing={STACK_TIGHT}>
<Text fw={500}>Crew</Text>
<ScrollArea
type="auto"
offsetScrollbars
styles={{ viewport: { paddingBottom: rem(6) } }}
>
<Group gap={CAST_GAP} wrap="nowrap">
{crewPeople.map((person) => (
<Stack
key={person.key}
spacing={STACK_TIGHT}
align="center"
style={{
width: rem(CAST_TILE_WIDTH),
height: rem(CAST_TILE_HEIGHT),
flex: `0 0 ${rem(CAST_TILE_WIDTH)}`,
}}
>
<Avatar
size={CAST_AVATAR_SIZE}
>>>>>>> Stashed changes
radius="md"
src={person.profile || undefined}
alt={person.name}
color="grape"
<<<<<<< Updated upstream
>
{!person.profile && person.name?.[0]}
</Avatar>
@ -928,6 +1173,17 @@ const MediaDetailModal = ({ opened, onClose }) => {
</Text>
{person.role && (
<Text size="xs" c="dimmed" ta="center" lineClamp={2}>
=======
styles={{ image: { objectFit: 'cover' } }}
>
{!person.profile && person.name?.[0]}
</Avatar>
<Text size="xs" fw={600} ta="center" lineClamp={2} style={{ minHeight: 30 }}>
{person.name}
</Text>
{person.role && (
<Text size="xs" c="dimmed" ta="center" lineClamp={1} style={{ minHeight: 16 }}>
>>>>>>> Stashed changes
{person.role}
</Text>
)}
@ -955,15 +1211,8 @@ const MediaDetailModal = ({ opened, onClose }) => {
>
<Stack spacing="md">
<Text>
Resume from{' '}
{runtimeLabel(
resumePrompt?.position_ms || activeProgress?.position_ms
)}{' '}
of{' '}
{runtimeLabel(
resumePrompt?.duration_ms || activeProgress?.duration_ms
)}
?
Resume from {runtimeLabel(resumePrompt?.position_ms || activeProgress?.position_ms)} of{' '}
{runtimeLabel(resumePrompt?.duration_ms || activeProgress?.duration_ms)}?
</Text>
<Group justify="flex-end">
<Button

View file

@ -50,26 +50,13 @@ const SettingsPage = () => {
const suppressWarning = useWarningsStore((s) => s.suppressWarning);
const isWarningSuppressed = useWarningsStore((s) => s.isWarningSuppressed);
const {
libraries: mediaLibraries,
loading: librariesLoading,
fetchLibraries: fetchMediaLibraries,
createLibrary: createMediaLibrary,
updateLibrary: updateMediaLibrary,
deleteLibrary: deleteMediaLibrary,
triggerScan: triggerLibraryScan,
} = useLibraryStore(
(state) => ({
libraries: state.libraries,
loading: state.loading,
fetchLibraries: state.fetchLibraries,
createLibrary: state.createLibrary,
updateLibrary: state.updateLibrary,
deleteLibrary: state.deleteLibrary,
triggerScan: state.triggerScan,
}),
shallow
);
const mediaLibraries = useLibraryStore((state) => state.libraries);
const librariesLoading = useLibraryStore((state) => state.loading);
const fetchMediaLibraries = useLibraryStore((state) => state.fetchLibraries);
const createMediaLibrary = useLibraryStore((state) => state.createLibrary);
const updateMediaLibrary = useLibraryStore((state) => state.updateLibrary);
const deleteMediaLibrary = useLibraryStore((state) => state.deleteLibrary);
const triggerLibraryScan = useLibraryStore((state) => state.triggerScan);
const [accordianValue, setAccordianValue] = useState(null);
const [networkAccessSaved, setNetworkAccessSaved] = useState(false);