From ffce5711178e2ac68935a199bc5cc47d571a64b1 Mon Sep 17 00:00:00 2001 From: Alexander Petric Date: Tue, 8 Oct 2024 17:38:11 -0400 Subject: [PATCH] feat(app builder): adding downloadFile frontend helper --- .../components/helpers/RunnableWrapper.svelte | 14 +++++++++++ .../apps/components/helpers/eval.ts | 7 +++++- .../components/inputs/AppFileInput.svelte | 3 +++ frontend/src/lib/components/apps/types.ts | 1 + frontend/src/lib/components/apps/utils.ts | 6 +++++ .../common/fileInput/FileInput.svelte | 25 +++++++++++++++++++ 6 files changed, 55 insertions(+), 1 deletion(-) diff --git a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte index 9bd8204c01..5fd6609ee7 100644 --- a/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte +++ b/frontend/src/lib/components/apps/components/helpers/RunnableWrapper.svelte @@ -31,6 +31,7 @@ | 'open' | 'close' | 'clearFiles' + | 'downloadFile' configuration: { gotoUrl: { url: (() => string) | string | undefined; newTab: boolean | undefined } setTab: { @@ -61,6 +62,10 @@ clearFiles?: { id: string | undefined } + downloadFile?: { + id: string | undefined + fileName: string | undefined + } } } | undefined @@ -235,6 +240,15 @@ $componentControl[id].clearFiles?.() break } + case 'downloadFile': { + const id = sideEffect?.configuration?.downloadFile?.id + const fileName = sideEffect?.configuration?.downloadFile?.fileName + + if (!id || !fileName) return + + $componentControl[id].downloadFile?.(fileName) + break + } default: break } diff --git a/frontend/src/lib/components/apps/components/helpers/eval.ts b/frontend/src/lib/components/apps/components/helpers/eval.ts index 9646250631..3a63a17ca6 100644 --- a/frontend/src/lib/components/apps/components/helpers/eval.ts +++ b/frontend/src/lib/components/apps/components/helpers/eval.ts @@ -25,7 +25,7 @@ function create_context_function_template( ) { let hasReturnAsLastLine = noReturn || eval_string.split('\n').some((x) => x.startsWith('return ')) return ` -return async function (context, state, createProxy, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal, open, close, validate, invalidate, validateAll, clearFiles, showToast, waitJob, askNewResource) { +return async function (context, state, createProxy, goto, setTab, recompute, getAgGrid, setValue, setSelectedIndex, openModal, closeModal, open, close, validate, invalidate, validateAll, clearFiles, downloadFile, showToast, waitJob, askNewResource) { "use strict"; ${ contextKeys && contextKeys.length > 0 @@ -61,6 +61,7 @@ type WmFunctor = ( invalidate, validateAll, clearFiles, + downloadFile, showToast, waitJob, askNewResource @@ -111,6 +112,7 @@ export async function eval_like( invalidate?: (key: string, error: string) => void validateAll?: () => void clearFiles?: () => void + downloadFile?: (fileName: string) => void showToast?: (message: string, error?: boolean) => void waitJob?: (jobId: string) => void askNewResource?: () => void @@ -231,6 +233,9 @@ export async function eval_like( (id) => { controlComponents[id]?.clearFiles?.() }, + (id, fileName) => { + controlComponents[id]?.downloadFile?.(fileName) + }, (message, error) => { sendUserToast(message, error) }, diff --git a/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte b/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte index fa8e956709..f789eb5b09 100644 --- a/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte +++ b/frontend/src/lib/components/apps/components/inputs/AppFileInput.svelte @@ -48,6 +48,9 @@ $componentControl[id] = { clearFiles: () => { fileInput?.clearFiles() + }, + downloadFile: (fileName: string) => { + fileInput?.downloadFile(fileName) } } diff --git a/frontend/src/lib/components/apps/types.ts b/frontend/src/lib/components/apps/types.ts index aa19e57b6b..323ebc32cf 100644 --- a/frontend/src/lib/components/apps/types.ts +++ b/frontend/src/lib/components/apps/types.ts @@ -261,6 +261,7 @@ export type AppViewerContext = { invalidate?: (key: string, error: string) => void validateAll?: () => void clearFiles?: () => void + downloadFile?: (fileName: string) => void showToast?: (message: string, error?: boolean) => void recompute?: () => void askNewResource?: () => void diff --git a/frontend/src/lib/components/apps/utils.ts b/frontend/src/lib/components/apps/utils.ts index 3f63922f25..1a233e902d 100644 --- a/frontend/src/lib/components/apps/utils.ts +++ b/frontend/src/lib/components/apps/utils.ts @@ -281,6 +281,12 @@ declare function validateAll(id: string): void; */ declare function clearFiles(id: string): void; +/** Download a file from a file input component + * @param id component's id + * @param fileName name of the file to download + */ +declare function downloadFile(id: string, fileName: string): void; + /** Display a toast message * @param message message to display */ diff --git a/frontend/src/lib/components/common/fileInput/FileInput.svelte b/frontend/src/lib/components/common/fileInput/FileInput.svelte index 1ce7347143..96b3ab0e56 100644 --- a/frontend/src/lib/components/common/fileInput/FileInput.svelte +++ b/frontend/src/lib/components/common/fileInput/FileInput.svelte @@ -121,6 +121,31 @@ files = undefined dispatchChange() } + + export function downloadFile(fileName: string) { + if (!files) { + console.error('Trying to download a file: No files uploaded'); + return; + } + const file = files.find(f => f.name === fileName); + if (!file) { + console.error(`Trying to download a file: File "${fileName}" doesn't exist`); + return; + } + + const url = URL.createObjectURL(file); + const a = document.createElement('a'); + a.href = url; + a.download = fileName; + document.body.appendChild(a); + a.click(); + document.body.removeChild(a); + + // Delay revoking the object URL to ensure the download works + setTimeout(() => { + URL.revokeObjectURL(url); + }, 100); + }