Files
siftlode/frontend/src/lib/downloadActions.ts
T
peter f41c81eb56 fix(downloads): media-library UI review fixes (E7)
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).
2026-08-29 21:52:13 +02:00

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),
};
}