Deep-review remediation on the frontend, all findings adversarially verified: - LibraryPlayer picks audio vs video from the canonical media_kind (single source), not a hardcoded container whitelist, so an uploaded audio file with an uncommon/absent container renders in <audio> instead of a broken video box (legacy null media_kind keeps the heuristic). - UploadDialog aborts the in-flight upload when the dialog is closed by X/ESC/backdrop/Back (not only the footer Cancel); drag highlight uses an enter/leave depth counter to stop the flicker; the progress bar gets a stable role=progressbar aria-label with the value on aria-valuenow. - DownloadCenter's Shared tab renders honest loading/error states instead of a blank panel, and the library/shared row-action gating moves to a single tested helper (lib/downloadActions). Tests: downloadActions gating matrix, the uploadReq XHR path (abort/401/422-localize/onerror), and the media_kind-driven audio/video branch. i18n: downloads.upload.uploadProgress (HU + EN).
44 lines
1.5 KiB
TypeScript
44 lines
1.5 KiB
TypeScript
import type { DownloadJob } from "./api";
|
|
|
|
// Which row actions apply to a completed library / shared item — gated on the stored media_kind and
|
|
// job_kind (E7·S5b). A generic uploaded file (media_kind='file') is download + share only: no player,
|
|
// no trim, no details edit. Re-download needs a re-fetchable source, which an edit clip and an upload
|
|
// both lack (the backend 409s both). A single source for the gate the Library + Shared rows render,
|
|
// so it's unit-testable instead of buried in JSX conditionals — and the two tabs can't drift apart.
|
|
|
|
const isMediaKind = (job: DownloadJob): boolean => job.asset?.media_kind !== "file";
|
|
const hasDuration = (job: DownloadJob): boolean => (job.asset?.duration_s ?? 0) > 0;
|
|
|
|
export interface LibraryRowActions {
|
|
play: boolean;
|
|
edit: boolean;
|
|
editDetails: boolean;
|
|
redownload: boolean;
|
|
}
|
|
|
|
export function libraryRowActions(job: DownloadJob): LibraryRowActions {
|
|
const media = isMediaKind(job);
|
|
return {
|
|
play: media,
|
|
edit: media && job.can_download && hasDuration(job),
|
|
editDetails: media,
|
|
redownload: job.job_kind !== "edit" && job.job_kind !== "upload",
|
|
};
|
|
}
|
|
|
|
export interface SharedRowActions {
|
|
play: boolean;
|
|
save: boolean;
|
|
edit: boolean;
|
|
}
|
|
|
|
export function sharedRowActions(job: DownloadJob): SharedRowActions {
|
|
const media = isMediaKind(job);
|
|
// The owner's `can_download` gates save/play/edit on a shared item (it reflects their link ACL).
|
|
return {
|
|
play: media && job.can_download,
|
|
save: job.can_download,
|
|
edit: media && job.can_download && hasDuration(job),
|
|
};
|
|
}
|