feat(files): a file that is not media opens in a viewer — an image, a PDF, text as text
- GET /api/files/{id}/preview: an allow-list on the stored extension (downloads/preview.py) picks
image / PDF / text; served inline under `CSP: sandbox; frame-ancestors 'self'`, the type from the
table; HTML and SVG arrive as plain text; a text is its first 1 MiB (X-Preview-Truncated), sent
as UTF-8 whatever it was written in (BOM, UTF-8, else cp1250); a NUL means binary: 415
- the list says which preview each file gets (asset.preview); FE/BE contract on kinds and header
- the viewer (shared dialog shell): ← / → through the list's such files in its order, Save, open in
a tab, the file's menu; one stage height for every kind; a no-preview card offers the file
- the name, the card's picture and the menu's Preview open it; an image up to 8 MB is its own
thumbnail
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import type { DownloadJob } from "./api";
|
||||
|
||||
// Which row actions apply to a completed library / shared item — gated on the stored media_kind and
|
||||
// job_kind. 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
|
||||
// job_kind. A generic uploaded file (media_kind='file') opens in the Files viewer instead of a player,
|
||||
// and has no trim and 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.
|
||||
|
||||
@@ -11,6 +11,8 @@ const hasDuration = (job: DownloadJob): boolean => (job.asset?.duration_s ?? 0)
|
||||
|
||||
export interface LibraryRowActions {
|
||||
play: boolean;
|
||||
/** Opens in the Files viewer: a file that is not media (its preview, or the "no preview" card). */
|
||||
view: boolean;
|
||||
edit: boolean;
|
||||
editDetails: boolean;
|
||||
redownload: boolean;
|
||||
@@ -20,6 +22,7 @@ export function libraryRowActions(job: DownloadJob): LibraryRowActions {
|
||||
const media = isMediaKind(job);
|
||||
return {
|
||||
play: media,
|
||||
view: !media && job.can_download,
|
||||
edit: media && job.can_download && hasDuration(job),
|
||||
editDetails: media,
|
||||
redownload: !job.job_kind || job.job_kind === "download",
|
||||
@@ -28,6 +31,7 @@ export function libraryRowActions(job: DownloadJob): LibraryRowActions {
|
||||
|
||||
export interface SharedRowActions {
|
||||
play: boolean;
|
||||
view: boolean;
|
||||
save: boolean;
|
||||
edit: boolean;
|
||||
}
|
||||
@@ -37,6 +41,7 @@ export function sharedRowActions(job: DownloadJob): SharedRowActions {
|
||||
// The owner's `can_download` gates save/play/edit on a shared item (it reflects their link ACL).
|
||||
return {
|
||||
play: media && job.can_download,
|
||||
view: !media && job.can_download,
|
||||
save: job.can_download,
|
||||
edit: media && job.can_download && hasDuration(job),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user