diff --git a/frontend/src/lib/components/apps/components/display/AppDownload.svelte b/frontend/src/lib/components/apps/components/display/AppDownload.svelte new file mode 100644 index 0000000000..9f7f4366ff --- /dev/null +++ b/frontend/src/lib/components/apps/components/display/AppDownload.svelte @@ -0,0 +1,91 @@ + + + + +{#each Object.keys(components['downloadcomponent'].initialData.configuration) as key (key)} + +{/each} + +{#if render} + + + +{/if} diff --git a/frontend/src/lib/components/apps/components/layout/AppModal.svelte b/frontend/src/lib/components/apps/components/layout/AppModal.svelte index ddc99991a0..a31eac4ae5 100644 --- a/frontend/src/lib/components/apps/components/layout/AppModal.svelte +++ b/frontend/src/lib/components/apps/components/layout/AppModal.svelte @@ -29,7 +29,7 @@ //used so that we can count number of outputs setup for first refresh initOutput($worldStore, id, {}) - $: css = concatCustomCss($app.css?.containercomponent, customCss) + $: css = concatCustomCss($app.css?.modalcomponent, customCss) let open = false function handleKeyUp(event: KeyboardEvent): void { @@ -95,7 +95,9 @@
diff --git a/frontend/src/lib/components/apps/editor/component/Component.svelte b/frontend/src/lib/components/apps/editor/component/Component.svelte index b1e999e27f..90b810c564 100644 --- a/frontend/src/lib/components/apps/editor/component/Component.svelte +++ b/frontend/src/lib/components/apps/editor/component/Component.svelte @@ -44,6 +44,7 @@ import AppStepper from '../../components/layout/AppStepper.svelte' import AppSelectTab from '../../components/inputs/AppSelectTab.svelte' import AppConditionalWrapper from '../../components/layout/AppConditionalWrapper.svelte' + import AppDownload from '../../components/display/AppDownload.svelte' export let component: AppComponent export let selected: boolean @@ -239,6 +240,15 @@ bind:errorHandledByComponent {render} /> + {:else if component.type === 'downloadcomponent'} + {:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'} export type PlotlyComponent = BaseComponent<'plotlycomponent'> export type TimeseriesComponent = BaseComponent<'timeseriescomponent'> export type ButtonComponent = BaseComponent<'buttoncomponent'> & RecomputeOthersSource +export type DownloadComponent = BaseComponent<'downloadcomponent'> export type FormComponent = BaseComponent<'formcomponent'> & RecomputeOthersSource export type FormButtonComponent = BaseComponent<'formbuttoncomponent'> & RecomputeOthersSource @@ -164,6 +166,7 @@ export type TypedComponent = | Schemaformcomponent | SelectTabComponent | ConditionalWrapperComponent + | DownloadComponent export type AppComponent = BaseAppComponent & TypedComponent @@ -509,6 +512,72 @@ export const components = { } } }, + downloadcomponent: { + name: 'Download Button', + icon: Download, + dims: '1:1-2:1' as AppComponentDimensions, + + customCss: { + button: { style: '', class: '' } + }, + initialData: { + ...defaultAlignement, + configuration: { + source: { + type: 'static', + value: '', + fieldType: 'text', + fileUpload: { + accept: '*', + convertTo: 'base64' + }, + placeholder: 'Enter URL or upload file' + }, + filename: { + type: 'static', + fieldType: 'text', + value: 'windmill.file' + }, + label: { + type: 'static', + fieldType: 'text', + value: 'Press me' + }, + color: { + fieldType: 'select', + type: 'static', + onlyStatic: true, + selectOptions: selectOptions.buttonColorOptions, + value: 'blue' + }, + size: { + fieldType: 'select', + type: 'static', + onlyStatic: true, + selectOptions: selectOptions.buttonSizeOptions, + value: 'xs' + }, + fillContainer: { + fieldType: 'boolean', + type: 'static', + onlyStatic: true, + value: false + }, + beforeIcon: { + type: 'static', + value: undefined, + fieldType: 'icon-select', + onlyStatic: true + }, + afterIcon: { + type: 'static', + value: undefined, + fieldType: 'icon-select', + onlyStatic: true + } + } + } + }, formcomponent: { name: 'Submit form', icon: FormInput, @@ -1746,7 +1815,8 @@ Hello \${ctx.username} fileUpload: { accept: 'application/pdf', convertTo: 'base64' - } + }, + placeholder: 'Enter URL or upload file' }, zoom: { fieldType: 'number', diff --git a/frontend/src/lib/components/apps/editor/component/sets.ts b/frontend/src/lib/components/apps/editor/component/sets.ts index 3c45f00e48..0850365340 100644 --- a/frontend/src/lib/components/apps/editor/component/sets.ts +++ b/frontend/src/lib/components/apps/editor/component/sets.ts @@ -21,7 +21,7 @@ const layout: ComponentSet = { const buttons: ComponentSet = { title: 'Buttons', - components: ['buttoncomponent', 'formcomponent', 'formbuttoncomponent'] + components: ['buttoncomponent', 'formcomponent', 'formbuttoncomponent', 'downloadcomponent'] } as const const inputs: ComponentSet = { diff --git a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts index 0f3557a655..ee42a80ca6 100644 --- a/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts +++ b/frontend/src/lib/components/apps/editor/componentsPanel/quickStyleProperties.ts @@ -612,6 +612,9 @@ export const quickStyleProperties: Record< buttoncomponent: { button: buttonDefaultProps }, + downloadcomponent: { + button: buttonDefaultProps + }, drawercomponent: { container: containerDefaultProps }, diff --git a/frontend/src/lib/components/apps/utils.ts b/frontend/src/lib/components/apps/utils.ts index db665b2757..6fd793c6eb 100644 --- a/frontend/src/lib/components/apps/utils.ts +++ b/frontend/src/lib/components/apps/utils.ts @@ -309,3 +309,14 @@ export function tailwindVerticalAlignment(alignment?: VerticalAlignment) { } return classes[alignment] } + +export function transformBareBase64IfNecessary(source: string | undefined) { + if (!source) { + return source + } + if (source.startsWith('data:') || !source.includes(',')) { + return source + } else { + return `data:application/octet-stream;base64,${source}` + } +} diff --git a/frontend/src/lib/components/common/button/Button.svelte b/frontend/src/lib/components/common/button/Button.svelte index 4e0f58cc57..0558c88402 100644 --- a/frontend/src/lib/components/common/button/Button.svelte +++ b/frontend/src/lib/components/common/button/Button.svelte @@ -28,6 +28,7 @@ export let loading = false export let title: string | undefined = undefined export let style: string = '' + export let download: string | undefined = undefined type MenuItem = { label: string @@ -111,6 +112,9 @@ event.stopPropagation() dispatch('click', event) if (href) { + if (href.startsWith('data')) { + return + } if (href.startsWith('http') || target == '_blank') { window.open(href, target) } else { @@ -148,6 +152,7 @@ on:click={onClick} on:focus on:blur + {download} class={twMerge(buttonClass, disabled ? '!bg-gray-300 !text-gray-600 !cursor-not-allowed' : '')} {...buttonProps} disabled={disabled || loading}