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}