fix(downloads-ui): editor tail no-op + persisted-tab fallback (+ dead ternary)
Found during the Phase-2 Downloads-frontend review: - VideoEditor: onLoaded now reconciles the still-pristine full-length segment to the REAL decoded duration (its guard `end<=0` was dead since the metadata srcDur is always >0). Without this the untouched last segment kept the rounded metadata length, so isFullSingle read false and "Create" was enabled on an unmodified video — producing a bogus edit that dropped the tail. - DownloadCenter: fall back to the "queue" tab when a persisted tab (e.g. admin-only "system") isn't in the current set, instead of rendering a blank page. - VideoEditor: reencode = cropOn || accurate (was a no-op `willJoin ? accurate : accurate`). tsc clean. GUI-affecting → pending an E2E visual test (needs an authed localdev session) before user UAT. Cross-cutting UI-consistency cleanup deferred to Phase 3.
This commit is contained in:
parent
aee1bdc85d
commit
94fd7ba9a6
2 changed files with 14 additions and 3 deletions
|
|
@ -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 { useTranslation } from "react-i18next";
|
||||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import {
|
import {
|
||||||
|
|
@ -586,6 +586,12 @@ export default function DownloadCenter({ me }: { me: Me }) {
|
||||||
{ id: "shared", label: t("downloads.tabs.shared") },
|
{ id: "shared", label: t("downloads.tabs.shared") },
|
||||||
...(me.role === "admin" ? [{ id: "system", label: t("downloads.tabs.system") }] : []),
|
...(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) => (
|
const saveBtn = (job: DownloadJob) => (
|
||||||
<a
|
<a
|
||||||
|
|
|
||||||
|
|
@ -87,7 +87,12 @@ export default function VideoEditor({ job, onClose }: { job: DownloadJob; onClos
|
||||||
if (!v) return;
|
if (!v) return;
|
||||||
const d = v.duration && isFinite(v.duration) ? v.duration : srcDur;
|
const d = v.duration && isFinite(v.duration) ? v.duration : srcDur;
|
||||||
setDuration(d);
|
setDuration(d);
|
||||||
setSegments((s) => (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 onTimeUpdate = () => {
|
||||||
const v = videoRef.current;
|
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;
|
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 valid = kept.length >= 1 && keptDur >= MIN_SEG && (cropOn || !isFullSingle);
|
||||||
const willJoin = output === "join" && kept.length > 1;
|
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 cropPx = (): { x: number; y: number; w: number; h: number } | undefined => {
|
||||||
const v = videoRef.current;
|
const v = videoRef.current;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue