Repository navigation
FileControl: View only for browser-viewable files, plus a Download icon named by x-file.fileName #99
Copy link
Copy link
Closed
Labels
enhancementNew feature or requestNew feature or request
Description
What
FileControl's View opens a file in a new tab. That works for files a browser can display: images, PDFs, and HTML documents that are opened in a tab and printed with Ctrl+P. Anything else (xlsx, docx, csv) the browser saves instead, under the storage key, e.g. 0f8e7c1a-….xlsx.
The row label also depends on who is looking. The user who just uploaded the file sees its original name. Anyone viewing a saved form has only the key, so they see "Uploaded File".
Schemas need a way to give files a meaningful, consistent name. That naming must not take away the in-tab View for types a browser can display. This issue keeps the two apart:
- View: look at the file. Shown only for types a browser can display. Behaviour unchanged.
- Download (new icon, every row): save the file, named by
x-file.fileNamewhen it's set.
Proposal
Each layer below builds on the one before it and can be its own PR.
1. Pure helpers (src/utils/, no React)
matchesAccept(file: { name: string; type?: string }, accept: string): booleanin a newsrc/utils/file.ts. Move the matching code out of FileControl'sprocessFile(FileControl.tsx~:104-110) unchanged: wildcards,.extentries and exact MIME types. Upload validation then calls it, with no change in behaviour.isBrowserViewable(file: { name: string; type?: string }): boolean, also infile.ts, ismatchesAccept(file, VIEWABLE_TYPES)against an explicit allowlist:image/png, image/jpeg, image/gif, image/webp, application/pdf, text/html, text/plainplus their extensions (.png, .jpg, .jpeg, .gif, .webp, .pdf, .html, .htm, .txt). Notimage/*: browsers can't display TIFF or HEIC. A file with no type and no extension counts as viewable, so View stays visible (today's behaviour).fileExtension(nameOrKey: string): string | undefinedinfile.ts:.xlsxfromreport.xlsxor0f8e….xlsx.downloadName(template: string | undefined, values: { index: number }, file: { name?: string; key: string }): stringinsrc/utils/download.ts:- with a template, render it with the existing
renderFileName, then add the real extension (fromname, otherwisekey) unless the result already ends with it; - without one, use the original
name, otherwise thekey.
- with a template, render it with the existing
downloadFromUrl(url: string, fileName: string): Promise<void>insrc/utils/download.ts:fetch, throw on a network error or a non-OK response, then pass the Blob todownloadBlob(blob, fileName).downloadBlobis split out of the existingdownloadTextFile, which then calls it.
2. Getting a file's URL (FileControl)
- Store
type(File.type) inFileEntrywhen a file is uploaded. - Add one
resolveFileUrl(key): the localblobUrlif there is one, otherwiseuploadContext.getDownloadUrl(key).url. View and Download both use it, replacing the lookup in today'sonView(FileControl.tsx~:186).
3. FileControl UI
- Row label: the result of
downloadName(...)whenx-file.fileNameis set, otherwise the original name / "Uploaded File" as today. When a template is set and the original name is known, show the original name as secondary text. - View: rendered only when
isBrowserViewable(...). Behaviour unchanged: open the window synchronously, then set its URL. Neverawaitbeforewindow.open, or the popup blocker stops it. - Download: an
IconButtonwithDownloadIcon(@radix-ui/react-icons),aria-label="Download"and a tooltip. It's on every row, including in read-only forms, and callsdownloadFromUrl(await resolveFileUrl(key), downloadName(...)). - Errors: a per-row error message (
Unable to download file./Unable to open file.) shown under that row and cleared on the next attempt. It's kept separate from the uploaderror, which shows only in the drop zone, and the drop zone is hidden when the control is read-only or full. - Meaning of
x-file.fileName: the row label and the Download name only. It never changes what View does.
4. Docs and fixtures
README.mdFileControl entry and theXFileOptionsJSDoc: describefileNameas a base name ({index}is the file's 1-based position, which shifts when an earlier file is removed), with the real extension added. Use a neutral example (Attachment_{index}).- Note that Download fetches the signed storage link from the browser, so the storage bucket's CORS must allow
GETfrom the app's origin. dev/fixtures.tsfilefixture: add a multi-file field withx-file.fileNamewhoseacceptcovers images, PDF and xlsx, so View-hidden and View-shown rows appear side by side.
Out of scope
- Naming through the signed link (
response-content-disposition): this avoids CORS and loading the whole file into memory, but needsgetDownloadUrl(key, { fileName })and support from the storage backend. Separate issue. - Form values (
{consignmentNo}) or{seq(padding)}inx-file.fileName, to matchx-xml-export.fileNameandx-template. Follow-up; this issue supports{index}only (plus the built-in{today(…)}). - The name of the browser tab, or of a "Save as PDF" from it: an HTML document's
<title>controls that. - Configurable button text (Configurable text for upload/download buttons and drop zones #89 covers wording for the other controls).
Acceptance criteria
- Layer 1:
src/utils/file.test.tscoversmatchesAccept(wildcard,.ext, exact MIME, no match),isBrowserViewable(allowlisted MIME, allowlisted extension on a key, xlsx → false, TIFF → false, no type and no extension → true) andfileExtension.download.test.tscoversdownloadName(template + extension, template already ending in it, no template → name, no name → key) anddownloadFromUrl(OK →downloadBlobcalled with the name; non-OK and network error → throws). - Layer 1: FileControl's upload validation uses
matchesAccept, and the existing FileControl tests pass unchanged. - Layer 3:
FileControl.test.tsxcovers:- View hidden for an xlsx key and shown for png, pdf and html keys;
- Download named from the template with the real extension, through the local blob (just uploaded) and through
getDownloadUrl(a saved form); - Download falling back to the original name when there's no template;
- a failed Download showing a visible row error when read-only and when the single file slot is full;
- View still using
window.open.
- Layer 4: README and JSDoc updated,
dev/fixtures.tsexample added. - With no
x-file.fileName, behaviour is unchanged apart from the new Download icon and View hidden for non-viewable types. -
pnpm --filter @opennsw/jsonforms-renderers test,pnpm type-check,pnpm lintandpnpm format:checkpass.
Line numbers are as of main (c39d889).
Related: #95.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
enhancementNew feature or requestNew feature or request
Activity
feat(jsonforms-renderers): View for viewable files, Download named by…