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:
@@ -18,15 +18,16 @@ import {
|
||||
import { MoreButton } from "./ui/MoreButton";
|
||||
import { Pressable } from "./ui/Pressable";
|
||||
|
||||
// A library file in the Files grid: the one media card (ui/MediaCard) — its picture plays it, the
|
||||
// title under it, then where it came from and what needs saying about it (kept, leaving soon). The
|
||||
// A library file in the Files grid: the one media card (ui/MediaCard) — its picture plays it (a file
|
||||
// that is not media: opens it in the viewer), the title under it, then where it came from and what
|
||||
// needs saying about it (kept, leaving soon). The
|
||||
// right click, the long press and the "…" open the same menu as its table row (DESIGN.md "Files page":
|
||||
// the grid is the list's other paint, a toggle remembered per folder).
|
||||
|
||||
export function FileCard({
|
||||
file,
|
||||
index,
|
||||
onPlay,
|
||||
onOpen,
|
||||
menuProps,
|
||||
moreProps,
|
||||
menuOpen,
|
||||
@@ -35,8 +36,8 @@ export function FileCard({
|
||||
file: LibraryFile;
|
||||
/** Its place on the page, for the keyboard walk. */
|
||||
index: number;
|
||||
/** Null: nothing to play (a plain file, a shared one its owner keeps from me). */
|
||||
onPlay: (() => void) | null;
|
||||
/** Plays it, or opens it in the viewer. Null: a shared file its owner keeps from me. */
|
||||
onOpen: (() => void) | null;
|
||||
menuProps: Record<string, unknown>;
|
||||
moreProps: Record<string, unknown>;
|
||||
menuOpen: boolean;
|
||||
@@ -47,6 +48,7 @@ export function FileCard({
|
||||
const title = jobTitle(file);
|
||||
const channel = file.display_uploader || file.asset?.uploader || null;
|
||||
const clip = file.job_kind === "edit" || file.job_kind === "clip";
|
||||
const media = file.asset?.media_kind !== "file";
|
||||
const thumb = <JobThumb job={file} box="h-full w-full" />;
|
||||
return (
|
||||
<MediaCard
|
||||
@@ -55,17 +57,19 @@ export function FileCard({
|
||||
className={clsx(arrived && "ring-2 ring-accent/60")}
|
||||
picture={
|
||||
<div className={mediaPictureBox("wide")}>
|
||||
{onPlay ? (
|
||||
{onOpen ? (
|
||||
<Pressable
|
||||
onClick={onPlay}
|
||||
aria-label={t("downloads.actions.play")}
|
||||
onClick={onOpen}
|
||||
aria-label={t(media ? "downloads.actions.play" : "files.actions.view")}
|
||||
data-nav-open=""
|
||||
className={MEDIA_PICTURE}
|
||||
>
|
||||
{thumb}
|
||||
<div className={MEDIA_HOVER_LAYER}>
|
||||
<MediaPlayGlyph small />
|
||||
</div>
|
||||
{media && (
|
||||
<div className={MEDIA_HOVER_LAYER}>
|
||||
<MediaPlayGlyph small />
|
||||
</div>
|
||||
)}
|
||||
</Pressable>
|
||||
) : (
|
||||
<div className={clsx(MEDIA_PICTURE, "cursor-default")}>{thumb}</div>
|
||||
|
||||
Reference in New Issue
Block a user