diff --git a/frontend/src/components/DownloadCenter.tsx b/frontend/src/components/DownloadCenter.tsx index d67eae6..a423692 100644 --- a/frontend/src/components/DownloadCenter.tsx +++ b/frontend/src/components/DownloadCenter.tsx @@ -1,4 +1,4 @@ -import { lazy, Suspense, useMemo, useState } from "react"; +import { lazy, Suspense, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { @@ -586,6 +586,12 @@ export default function DownloadCenter({ me }: { me: Me }) { { id: "shared", label: t("downloads.tabs.shared") }, ...(me.role === "admin" ? [{ id: "system", label: t("downloads.tabs.system") }] : []), ]; + // A persisted tab can point at one no longer available (e.g. "system" restored for a now-non-admin + // account) — that would render a blank page with no active tab. Fall back to the default. + useEffect(() => { + if (!tabs.some((tb) => tb.id === tab)) setTab("queue"); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tab, me.role]); const saveBtn = (job: DownloadJob) => ( (s.length === 1 && s[0].end <= 0 ? [{ id: 0, start: 0, end: d, keep: true }] : s)); + // Reconcile the still-pristine full-length segment to the REAL decoded duration. The initial + // segment ends at the (integer, often-rounded) metadata `srcDur`; once the media loads we know + // the true length (e.g. 213.44 vs 213). Without this the untouched last segment stays short, so + // the no-op guard (isFullSingle: end >= duration) reads false and enables a "Create" that files a + // clip of an unmodified video and drops its tail. Only touches the untouched segment (end===srcDur). + setSegments((s) => (s.length === 1 && s[0].end === srcDur ? [{ ...s[0], end: d }] : s)); }; const onTimeUpdate = () => { const v = videoRef.current; @@ -242,7 +247,7 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos segments.length === 1 && kept.length === 1 && kept[0].start <= 0.01 && kept[0].end >= duration - 0.01; const valid = kept.length >= 1 && keptDur >= MIN_SEG && (cropOn || !isFullSingle); const willJoin = output === "join" && kept.length > 1; - const reencode = cropOn || (willJoin ? accurate : accurate); + const reencode = cropOn || accurate; const cropPx = (): { x: number; y: number; w: number; h: number } | undefined => { const v = videoRef.current;