mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-20 16:02:19 +00:00
feat: add S3 support to download button and PDF preview components (#7271)
* feat: add S3 support to download button and PDF preview components Add S3 object and s3:// URL support to AppDownload and AppPdf components, following the same pattern used in AppImage component. Both components now: - Handle partial S3 objects with storage and presigned URL support - Handle s3:// URL format - Construct proper API endpoints for S3 file downloads Fixes #7240 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com> * handle policy + fix s3 picker --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
This commit is contained in:
@@ -757,8 +757,8 @@ JsonNode ${windmillPathToCamelCaseName(path)} = JsonNode.Parse(await client.GetS
|
||||
<S3FilePicker
|
||||
bind:this={s3FilePicker}
|
||||
readOnlyMode={false}
|
||||
on:selectAndClose={(s3obj) => {
|
||||
let s = `'${formatS3Object(s3obj.detail)}'`
|
||||
onSelectAndClose={(s3obj) => {
|
||||
let s = `'${formatS3Object(s3obj)}'`
|
||||
if (lang === 'duckdb') {
|
||||
editor?.insertAtCursor(`SELECT * FROM ${s}`)
|
||||
} else if (lang === 'python3') {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { emptyString, type S3Object } from '$lib/utils'
|
||||
import { Button, Drawer } from './common'
|
||||
import DrawerContent from './common/drawer/DrawerContent.svelte'
|
||||
import { createEventDispatcher, tick, untrack } from 'svelte'
|
||||
import { tick, untrack } from 'svelte'
|
||||
import S3FilePickerInner from './S3FilePickerInner.svelte'
|
||||
import Select from './select/Select.svelte'
|
||||
import { FileUp } from 'lucide-svelte'
|
||||
@@ -10,11 +10,6 @@
|
||||
import { SettingService } from '$lib/gen'
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
|
||||
let dispatch = createEventDispatcher<{
|
||||
close: { s3: string; storage: string | undefined } | undefined
|
||||
selectAndClose: { s3: string; storage: string | undefined }
|
||||
}>()
|
||||
|
||||
interface Props {
|
||||
fromWorkspaceSettings?: boolean
|
||||
readOnlyMode: boolean
|
||||
@@ -22,6 +17,8 @@
|
||||
selectedFileKey?: { s3: string; storage?: string } | undefined
|
||||
folderOnly?: boolean
|
||||
regexFilter?: RegExp | undefined
|
||||
onClose?: () => void
|
||||
onSelectAndClose?: (selected: { s3: string; storage: string | undefined }) => void
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -30,7 +27,9 @@
|
||||
initialFileKey = $bindable(undefined),
|
||||
selectedFileKey = $bindable(undefined),
|
||||
folderOnly = false,
|
||||
regexFilter = undefined
|
||||
regexFilter = undefined,
|
||||
onClose,
|
||||
onSelectAndClose
|
||||
}: Props = $props()
|
||||
|
||||
let drawer: Drawer | undefined = $state()
|
||||
@@ -76,7 +75,7 @@
|
||||
<Drawer
|
||||
bind:this={drawer}
|
||||
on:close={() => {
|
||||
dispatch('close')
|
||||
onClose?.()
|
||||
s3FilePickerInner?.close?.()
|
||||
}}
|
||||
size="1200px"
|
||||
@@ -93,7 +92,7 @@
|
||||
<S3FilePickerInner
|
||||
bind:this={s3FilePickerInner}
|
||||
on:selectAndClose={(e) => {
|
||||
dispatch('selectAndClose', e.detail)
|
||||
onSelectAndClose?.(e.detail)
|
||||
drawer?.closeDrawer?.()
|
||||
}}
|
||||
{fromWorkspaceSettings}
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
<S3FilePicker
|
||||
bind:this={s3FilePicker}
|
||||
bind:selectedFileKey={value}
|
||||
on:close={() => {
|
||||
onClose={() => {
|
||||
rawValue = JSON.stringify(value, null, 2)
|
||||
editor?.setCode(rawValue)
|
||||
}}
|
||||
|
||||
@@ -12,6 +12,8 @@
|
||||
import ComponentErrorHandler from '../helpers/ComponentErrorHandler.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import AlignWrapper from '../helpers/AlignWrapper.svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { isPartialS3Object, getS3File } from '../../editor/appUtilsS3'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
@@ -37,7 +39,8 @@
|
||||
initConfig(components['downloadcomponent'].initialData.configuration, configuration)
|
||||
)
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, worldStore, appPath, workspace, isEditor } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
//used so that we can count number of outputs setup for first refresh
|
||||
initOutput($worldStore, id, {})
|
||||
@@ -45,6 +48,10 @@
|
||||
let beforeIconComponent: any = $state()
|
||||
let afterIconComponent: any = $state()
|
||||
|
||||
let downloadUrl: string | undefined = $state(undefined)
|
||||
|
||||
let token = getContext<{ token?: string }>('AuthToken')
|
||||
|
||||
async function handleBeforeIcon() {
|
||||
if (resolvedConfig.beforeIcon) {
|
||||
beforeIconComponent = await loadIcon(
|
||||
@@ -69,6 +76,36 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSource() {
|
||||
if (isPartialS3Object(resolvedConfig.source)) {
|
||||
downloadUrl = await getS3File({
|
||||
source: resolvedConfig.source.s3,
|
||||
storage: resolvedConfig.source.storage,
|
||||
presigned: resolvedConfig.source.presigned,
|
||||
appPath: $appPath,
|
||||
username: $userStore?.username,
|
||||
workspace,
|
||||
token: token?.token,
|
||||
isEditor,
|
||||
configuration
|
||||
})
|
||||
} else if (resolvedConfig.source && typeof resolvedConfig.source !== 'string') {
|
||||
throw new Error('Invalid source object' + typeof resolvedConfig.source)
|
||||
} else if (resolvedConfig.source?.startsWith('s3://')) {
|
||||
downloadUrl = await getS3File({
|
||||
source: resolvedConfig.source?.replace('s3://', ''),
|
||||
appPath: $appPath,
|
||||
username: $userStore?.username,
|
||||
workspace,
|
||||
token: token?.token,
|
||||
isEditor,
|
||||
configuration
|
||||
})
|
||||
} else {
|
||||
downloadUrl = transformBareBase64IfNecessary(resolvedConfig.source)
|
||||
}
|
||||
}
|
||||
|
||||
let css = $state(initCss($app.css?.downloadcomponent, customCss))
|
||||
$effect(() => {
|
||||
resolvedConfig.beforeIcon && beforeIconComponent && untrack(() => handleBeforeIcon())
|
||||
@@ -76,6 +113,9 @@
|
||||
$effect(() => {
|
||||
resolvedConfig.afterIcon && afterIconComponent && untrack(() => handleAfterIcon())
|
||||
})
|
||||
$effect(() => {
|
||||
resolvedConfig && loadSource()
|
||||
})
|
||||
</script>
|
||||
|
||||
<InitializeComponent {id} />
|
||||
@@ -102,7 +142,9 @@
|
||||
{#if render}
|
||||
<AlignWrapper {noWFull} {horizontalAlignment} {verticalAlignment}>
|
||||
<ComponentErrorHandler
|
||||
hasError={resolvedConfig?.source != undefined && typeof resolvedConfig.source !== 'string'}
|
||||
hasError={resolvedConfig?.source != undefined &&
|
||||
typeof resolvedConfig.source !== 'string' &&
|
||||
!isPartialS3Object(resolvedConfig.source)}
|
||||
>
|
||||
<Button
|
||||
on:pointerdown={(e) => e.stopPropagation()}
|
||||
@@ -122,7 +164,7 @@
|
||||
extendedSize={resolvedConfig.size}
|
||||
color={resolvedConfig.color}
|
||||
download={resolvedConfig.filename}
|
||||
href={transformBareBase64IfNecessary(resolvedConfig.source)}
|
||||
href={downloadUrl}
|
||||
target="_blank"
|
||||
ref="external"
|
||||
nonCaptureEvent
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
import ResolveConfig from '../helpers/ResolveConfig.svelte'
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import { defaultIfEmptyString } from '$lib/utils'
|
||||
|
||||
import { userStore } from '$lib/stores'
|
||||
import { computeS3ImageViewerPolicy, isPartialS3Object } from '../../editor/appUtilsS3'
|
||||
import { isPartialS3Object, getS3File } from '../../editor/appUtilsS3'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
@@ -25,14 +25,6 @@
|
||||
|
||||
let { id, configuration, customCss = undefined, render }: Props = $props()
|
||||
|
||||
function computeForceViewerPolicies() {
|
||||
if (!isEditor) {
|
||||
return undefined
|
||||
}
|
||||
const policy = computeS3ImageViewerPolicy(configuration)
|
||||
return policy
|
||||
}
|
||||
|
||||
const resolvedConfig = $state(
|
||||
initConfig(components['imagecomponent'].initialData.configuration, configuration)
|
||||
)
|
||||
@@ -54,43 +46,34 @@
|
||||
|
||||
let token = getContext<{ token?: string }>('AuthToken')
|
||||
|
||||
async function getS3Image(source: string | undefined, storage?: string, presigned?: string) {
|
||||
if (!source) return ''
|
||||
const appPathOrUser = defaultIfEmptyString(
|
||||
$appPath,
|
||||
`u/${$userStore?.username ?? 'unknown'}/newapp`
|
||||
)
|
||||
const params = new URLSearchParams()
|
||||
params.append('s3', source)
|
||||
if (storage) {
|
||||
params.append('storage', storage)
|
||||
}
|
||||
|
||||
if (token?.token && token.token != '') {
|
||||
params.append('token', token.token)
|
||||
}
|
||||
const forceViewerPolicies = computeForceViewerPolicies()
|
||||
if (forceViewerPolicies) {
|
||||
params.append('force_viewer_allowed_s3_keys', JSON.stringify([forceViewerPolicies]))
|
||||
}
|
||||
|
||||
return `/api/w/${workspace}/apps_u/download_s3_file/${appPathOrUser}?${params.toString()}${presigned ? `&${presigned}` : ''}`
|
||||
}
|
||||
|
||||
async function loadImage() {
|
||||
if (isPartialS3Object(resolvedConfig.source)) {
|
||||
imageUrl = await getS3Image(
|
||||
resolvedConfig.source.s3,
|
||||
resolvedConfig.source.storage,
|
||||
resolvedConfig.source.presigned
|
||||
)
|
||||
imageUrl = await getS3File({
|
||||
source: resolvedConfig.source.s3,
|
||||
storage: resolvedConfig.source.storage,
|
||||
presigned: resolvedConfig.source.presigned,
|
||||
appPath: $appPath,
|
||||
username: $userStore?.username,
|
||||
workspace,
|
||||
token: token?.token,
|
||||
isEditor,
|
||||
configuration
|
||||
})
|
||||
} else if (resolvedConfig.source && typeof resolvedConfig.source !== 'string') {
|
||||
throw new Error('Invalid image object' + typeof resolvedConfig.source)
|
||||
} else if (
|
||||
resolvedConfig.sourceKind === 's3 (workspace storage)' ||
|
||||
resolvedConfig.source?.startsWith('s3://')
|
||||
) {
|
||||
imageUrl = await getS3Image(resolvedConfig.source?.replace('s3://', ''))
|
||||
imageUrl = await getS3File({
|
||||
source: resolvedConfig.source?.replace('s3://', ''),
|
||||
appPath: $appPath,
|
||||
username: $userStore?.username,
|
||||
workspace,
|
||||
token: token?.token,
|
||||
isEditor,
|
||||
configuration
|
||||
})
|
||||
} else if (resolvedConfig.sourceKind === 'png encoded as base64') {
|
||||
imageUrl = 'data:image/png;base64,' + resolvedConfig.source
|
||||
} else if (resolvedConfig.sourceKind === 'jpeg encoded as base64') {
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import InitializeComponent from '../helpers/InitializeComponent.svelte'
|
||||
import ResolveStyle from '../helpers/ResolveStyle.svelte'
|
||||
import { Loader2 } from 'lucide-svelte'
|
||||
import { userStore } from '$lib/stores'
|
||||
import { isPartialS3Object, getS3File } from '../../editor/appUtilsS3'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
@@ -17,7 +19,8 @@
|
||||
|
||||
let { id, configuration, customCss = undefined, render }: Props = $props()
|
||||
|
||||
const { app, worldStore } = getContext<AppViewerContext>('AppViewerContext')
|
||||
const { app, worldStore, appPath, workspace, isEditor } =
|
||||
getContext<AppViewerContext>('AppViewerContext')
|
||||
|
||||
const outputs = initOutput($worldStore, id, {
|
||||
loading: false
|
||||
@@ -26,6 +29,44 @@
|
||||
let source: string | ArrayBuffer | undefined = $state(undefined)
|
||||
let zoom: number | undefined = $state(undefined)
|
||||
|
||||
let pdfSource: string | ArrayBuffer | undefined = $state(undefined)
|
||||
|
||||
let token = getContext<{ token?: string }>('AuthToken')
|
||||
|
||||
async function loadSource() {
|
||||
if (isPartialS3Object(source)) {
|
||||
pdfSource = await getS3File({
|
||||
source: source.s3,
|
||||
storage: source.storage,
|
||||
presigned: source.presigned,
|
||||
appPath: $appPath,
|
||||
username: $userStore?.username,
|
||||
workspace,
|
||||
token: token?.token,
|
||||
isEditor,
|
||||
configuration
|
||||
})
|
||||
} else if (source && typeof source !== 'string' && !(source instanceof ArrayBuffer)) {
|
||||
throw new Error('Invalid PDF source object' + typeof source)
|
||||
} else if (typeof source === 'string' && source?.startsWith('s3://')) {
|
||||
pdfSource = await getS3File({
|
||||
source: source?.replace('s3://', ''),
|
||||
appPath: $appPath,
|
||||
username: $userStore?.username,
|
||||
workspace,
|
||||
token: token?.token,
|
||||
isEditor,
|
||||
configuration
|
||||
})
|
||||
} else {
|
||||
pdfSource = source
|
||||
}
|
||||
}
|
||||
|
||||
$effect(() => {
|
||||
source && loadSource()
|
||||
})
|
||||
|
||||
let css = $state(initCss($app.css?.pdfcomponent, customCss))
|
||||
</script>
|
||||
|
||||
@@ -50,7 +91,7 @@
|
||||
<Loader2 class="animate-spin" />
|
||||
{:then Module}
|
||||
<Module.default
|
||||
{source}
|
||||
source={pdfSource}
|
||||
{zoom}
|
||||
class={css?.container?.class}
|
||||
style={css?.container?.style}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { App } from '../types'
|
||||
import {
|
||||
computeS3FileInputPolicy,
|
||||
computeWorkspaceS3FileInputPolicy,
|
||||
computeS3ImageViewerPolicy
|
||||
computeS3FileViewerPolicy
|
||||
} from './appUtilsS3'
|
||||
import { collectStaticFields, type TriggerableV2 } from './commonAppUtils'
|
||||
import type { Policy } from '$lib/gen'
|
||||
@@ -165,11 +165,16 @@ export async function updatePolicy(app: App, currentPolicy: Policy | undefined):
|
||||
}
|
||||
|
||||
const s3FileKeys = items
|
||||
.filter((x) => (x.data as AppComponent).type === 'imagecomponent')
|
||||
.filter(
|
||||
(x) =>
|
||||
(x.data as AppComponent).type === 'imagecomponent' ||
|
||||
(x.data as AppComponent).type === 'pdfcomponent' ||
|
||||
(x.data as AppComponent).type === 'downloadcomponent'
|
||||
)
|
||||
.map((x) => {
|
||||
const c = x.data as AppComponent
|
||||
const config = c.configuration
|
||||
return computeS3ImageViewerPolicy(config)
|
||||
return computeS3FileViewerPolicy(config)
|
||||
})
|
||||
.filter(Boolean) as { s3_path: string; storage?: string | undefined }[]
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { defaultIfEmptyString } from '$lib/utils'
|
||||
import type { AppInput, EvalInputV2 } from '../inputType'
|
||||
import type { App, RichConfigurations } from '../types'
|
||||
import { collectOneOfFields } from './appUtilsCore'
|
||||
@@ -86,7 +87,61 @@ export function isPartialS3Object(
|
||||
return input != undefined && typeof input === 'object' && typeof input['s3'] === 'string'
|
||||
}
|
||||
|
||||
export function computeS3ImageViewerPolicy(config: RichConfigurations) {
|
||||
function computeForceViewerPolicies({
|
||||
isEditor,
|
||||
configuration
|
||||
}: {
|
||||
isEditor: boolean
|
||||
configuration: RichConfigurations
|
||||
}) {
|
||||
if (!isEditor) {
|
||||
return undefined
|
||||
}
|
||||
const policy = computeS3FileViewerPolicy(configuration)
|
||||
return policy
|
||||
}
|
||||
|
||||
export async function getS3File({
|
||||
source,
|
||||
storage,
|
||||
presigned,
|
||||
appPath,
|
||||
username,
|
||||
workspace,
|
||||
token,
|
||||
isEditor,
|
||||
configuration
|
||||
}: {
|
||||
source: string | undefined
|
||||
storage?: string
|
||||
presigned?: string
|
||||
appPath: string
|
||||
username: string | undefined
|
||||
workspace: string
|
||||
token: string | undefined
|
||||
isEditor: boolean
|
||||
configuration: RichConfigurations
|
||||
}) {
|
||||
if (!source) return ''
|
||||
const appPathOrUser = defaultIfEmptyString(appPath, `u/${username ?? 'unknown'}/newapp`)
|
||||
const params = new URLSearchParams()
|
||||
params.append('s3', source)
|
||||
if (storage) {
|
||||
params.append('storage', storage)
|
||||
}
|
||||
|
||||
if (token && token != '') {
|
||||
params.append('token', token)
|
||||
}
|
||||
const forceViewerPolicies = computeForceViewerPolicies({ isEditor, configuration })
|
||||
if (forceViewerPolicies) {
|
||||
params.append('force_viewer_allowed_s3_keys', JSON.stringify([forceViewerPolicies]))
|
||||
}
|
||||
|
||||
return `/api/w/${workspace}/apps_u/download_s3_file/${appPathOrUser}?${params.toString()}${presigned ? `&${presigned}` : ''}`
|
||||
}
|
||||
|
||||
export function computeS3FileViewerPolicy(config: RichConfigurations) {
|
||||
if (config.source.type === 'uploadS3' && isPartialS3Object(config.source.value)) {
|
||||
return {
|
||||
s3_path: config.source.value.s3,
|
||||
|
||||
@@ -1642,6 +1642,9 @@ export const components = {
|
||||
accept: '*',
|
||||
convertTo: 'base64'
|
||||
},
|
||||
fileUploadS3: {
|
||||
accept: '*'
|
||||
},
|
||||
placeholder: 'Enter URL or upload file (base64)'
|
||||
},
|
||||
filename: {
|
||||
@@ -3459,8 +3462,7 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
convertTo: 'base64'
|
||||
},
|
||||
fileUploadS3: {
|
||||
accept: 'image/*',
|
||||
convertTo: 'base64'
|
||||
accept: 'image/*'
|
||||
}
|
||||
},
|
||||
sourceKind: {
|
||||
@@ -3660,6 +3662,9 @@ See date-fns format for more information. By default, it is 'dd.MM.yyyy HH:mm'
|
||||
accept: 'application/pdf',
|
||||
convertTo: 'base64'
|
||||
},
|
||||
fileUploadS3: {
|
||||
accept: 'application/pdf'
|
||||
},
|
||||
placeholder: 'Enter URL or upload file (base64)'
|
||||
},
|
||||
zoom: {
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
import ConnectionButton from '$lib/components/common/button/ConnectionButton.svelte'
|
||||
|
||||
import Toggle from '$lib/components/Toggle.svelte'
|
||||
import type SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
|
||||
interface Props {
|
||||
id: string
|
||||
@@ -108,6 +109,7 @@
|
||||
let s3PickerSelection: { s3: string; storage?: string } | undefined = $state(undefined)
|
||||
let s3FolderPrefix: string = $state('')
|
||||
let s3FileUploadRawMode = $state(componentInput?.type == 'uploadS3' && !!componentInput.value?.s3)
|
||||
let s3JsonEditor: SimpleEditor | undefined = $state()
|
||||
|
||||
function updateSelectedS3File() {
|
||||
if (s3PickerSelection) {
|
||||
@@ -115,6 +117,7 @@
|
||||
componentInput.value = {
|
||||
...s3PickerSelection
|
||||
}
|
||||
s3JsonEditor?.setCode(JSON.stringify(s3PickerSelection, null, 2))
|
||||
}
|
||||
s3FileUploadRawMode = true
|
||||
}
|
||||
@@ -317,6 +320,7 @@
|
||||
<Module.default
|
||||
code={JSON.stringify(componentInput.value ?? { s3: '' }, null, 2)}
|
||||
bind:value={componentInput.value}
|
||||
bind:editor={s3JsonEditor}
|
||||
/>
|
||||
{/await}
|
||||
{:else}
|
||||
@@ -351,8 +355,8 @@
|
||||
<S3FilePicker
|
||||
bind:this={s3FilePicker}
|
||||
folderOnly={false}
|
||||
on:close={(e) => {
|
||||
s3PickerSelection = e.detail
|
||||
onSelectAndClose={(selected) => {
|
||||
s3PickerSelection = selected
|
||||
updateSelectedS3File()
|
||||
}}
|
||||
readOnlyMode={false}
|
||||
|
||||
+11
-7
@@ -29,6 +29,7 @@
|
||||
import S3FilePicker from '$lib/components/S3FilePicker.svelte'
|
||||
import FileUpload from '$lib/components/common/fileUpload/FileUpload.svelte'
|
||||
import DucklakePicker from '$lib/components/DucklakePicker.svelte'
|
||||
import type SimpleEditor from '$lib/components/SimpleEditor.svelte'
|
||||
|
||||
interface Props {
|
||||
componentInput: StaticInput<any> | undefined
|
||||
@@ -56,8 +57,11 @@
|
||||
componentInput && appContext?.onchange?.()
|
||||
})
|
||||
|
||||
let s3FileUploadRawMode = $state(false)
|
||||
let s3FileUploadRawMode = $state(
|
||||
componentInput?.value && typeof componentInput.value == 'object' && !!componentInput.value?.s3
|
||||
)
|
||||
let s3FilePicker: S3FilePicker | undefined = $state(undefined)
|
||||
let s3JsonEditor: SimpleEditor | undefined = $state()
|
||||
</script>
|
||||
|
||||
{#key subFieldType}
|
||||
@@ -216,6 +220,7 @@
|
||||
<Module.default
|
||||
code={JSON.stringify(componentInput.value ?? { s3: '' }, null, 2)}
|
||||
bind:value={componentInput.value}
|
||||
bind:editor={s3JsonEditor}
|
||||
/>
|
||||
{/await}
|
||||
{:else}
|
||||
@@ -255,13 +260,12 @@
|
||||
<S3FilePicker
|
||||
bind:this={s3FilePicker}
|
||||
readOnlyMode={false}
|
||||
on:close={(e) => {
|
||||
if (e.detail) {
|
||||
if (componentInput) {
|
||||
componentInput.value = e.detail
|
||||
}
|
||||
s3FileUploadRawMode = true
|
||||
onSelectAndClose={(selected) => {
|
||||
if (componentInput) {
|
||||
componentInput.value = selected
|
||||
s3JsonEditor?.setCode(JSON.stringify(selected, null, 2))
|
||||
}
|
||||
s3FileUploadRawMode = true
|
||||
}}
|
||||
/>
|
||||
{:else if format?.startsWith('resource-') && (componentInput.value == undefined || typeof componentInput.value == 'string')}
|
||||
|
||||
@@ -51,16 +51,16 @@
|
||||
{#if $userStore}
|
||||
<S3FilePicker
|
||||
bind:this={s3FilePicker}
|
||||
on:selectAndClose={(ev) => {
|
||||
onSelectAndClose={(selected) => {
|
||||
if (multiple) {
|
||||
if (Array.isArray(value)) {
|
||||
value.push(ev.detail)
|
||||
value.push(selected)
|
||||
} else {
|
||||
value = [ev.detail]
|
||||
value = [selected]
|
||||
}
|
||||
fileUpload?.addUpload(ev.detail)
|
||||
fileUpload?.addUpload(selected)
|
||||
} else {
|
||||
value = ev.detail
|
||||
value = selected
|
||||
fileUpload?.setUpload(value)
|
||||
}
|
||||
editor?.setCode(JSON.stringify(value))
|
||||
|
||||
@@ -377,7 +377,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update config for captures
|
||||
function getCaptureConfig() {
|
||||
const newCaptureConfig = {
|
||||
@@ -408,7 +407,7 @@
|
||||
bind:this={s3FilePicker}
|
||||
folderOnly={is_static_website}
|
||||
bind:selectedFileKey={static_asset_config}
|
||||
on:close={() => {
|
||||
onClose={() => {
|
||||
s3Editor?.setCode(JSON.stringify(static_asset_config, null, 2))
|
||||
s3FileUploadRawMode = true
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user