Files
windmill/frontend/src/lib/components/SavedInputsPickerViewer.svelte
T
Diego ImbertandClaude Opus 4.6 5d79f33590 Final Svelte 5 migration (#8211)
* Remove $$props.field usage

* Rename slots to ensure no hyphen

* _props

* _trigger

* OnSelectedIteration type correct capitalization

* rename _content

* Remove afterUpdate

* Migrate everything to svelte 5

* array bind

* Fix popover

* type never

* nit fixes

* Fixed many trivial errors

* onClick

* Fix errors

* use let:

* nit typing

* fix: wrap state_referenced_locally vars with untrack()

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add untrack import

* Fix all syntax errors due to untrack migration

* Fix undefined errors

* Fix more undefined errors

* untrack(() => initialOpen)

* svelte-ignore

* Fix state_descriptors_fixed error in Chart.svelte

Use $state.snapshot() to pass plain copies of data/options to Chart.js
instead of $state proxies. Chart.js's listenArrayEvents tries to define
property descriptors on data arrays, which Svelte 5 proxies reject.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* nit typing

* Merge issue

* Fix "path is not set" error in resource picker / editor

* Fix InputTransformForm error when rerunning some flows

* fix npm run check

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-05 18:11:40 +01:00

179 lines
4.5 KiB
Svelte

<script lang="ts">
import { run, createBubbler } from 'svelte/legacy'
const bubble = createBubbler()
import { Button } from '$lib/components/common'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import ObjectViewerWrapper from '$lib/components/propertyPicker/ObjectViewerWrapper.svelte'
import { copyToClipboard, isObjectTooBig } from '$lib/utils'
import { Eye, CopyIcon, Loader2 } from 'lucide-svelte'
import { createEventDispatcher, untrack } from 'svelte'
interface Props {
payloadData: any
limitPayloadSize?: boolean
hover?: boolean
viewerOpen?: boolean
maxWidth?: number | undefined
editOptions?: boolean
}
let {
payloadData,
limitPayloadSize = false,
hover = false,
viewerOpen = false,
maxWidth = undefined,
editOptions = true
}: Props = $props()
const dispatch = createEventDispatcher()
const payloadTooBigForPreview = untrack(() => payloadData) != 'WINDMILL_TOO_BIG' && isObjectTooBig(untrack(() => payloadData))
const buttonWidth = 34
const floatingConfig = {
placement: 'bottom-end',
strategy: 'fixed',
offset: { mainAxis: 8, crossAxis: -buttonWidth },
gutter: 0,
middleware: [
{
name: 'flip',
options: {
fallbackPlacements: ['bottom-top']
}
}
]
}
const xOffset = untrack(() => editOptions) ? 218 : 168 // width of the optional buttons on the right
let popover: Popover | undefined = $state()
let popoverOpen = false
let hoverTimeout: ReturnType<typeof setTimeout> | undefined
let objectViewerLoaded = $state(false)
let popoverFullyOpened = $state(false)
function handlePopoverChange(event: CustomEvent<boolean>) {
const isOpen = event.detail
popoverOpen = isOpen
if (!isOpen) {
objectViewerLoaded = false
popoverFullyOpened = false
} else {
// Set a small delay to ensure the popover is fully rendered before
// marking it as fully opened
setTimeout(() => {
popoverFullyOpened = true
}, 50)
}
dispatch('openChange', isOpen)
}
function handleHoverChange(isHovering: boolean) {
if (isHovering) {
// Start debounce timer when hovering starts
hoverTimeout = setTimeout(() => {
if (viewerOpen && popover && !popoverOpen) {
popover.open()
}
}, 100)
} else {
// Clear timeout if hover ends before debounce period
if (hoverTimeout) {
clearTimeout(hoverTimeout)
hoverTimeout = undefined
}
}
}
run(() => {
handleHoverChange(hover)
})
let ajustedWidth = $derived(maxWidth ? Math.abs(maxWidth - xOffset) : undefined)
</script>
<Popover
bind:this={popover}
{floatingConfig}
on:openChange={handlePopoverChange}
on:click={(e) => {
e.stopPropagation()
}}
closeOnOtherPopoverOpen
closeOnOutsideClick={false}
usePointerDownOutside
>
{#snippet trigger({ isOpen })}
<Button
variant="subtle"
unifiedSize="sm"
btnClasses={hover || isOpen ? 'block -my-2 bg-transparent' : 'hidden'}
wrapperClasses="p-0"
iconOnly
nonCaptureEvent
startIcon={{ icon: Eye }}
/>
{/snippet}
{#snippet content()}
<div
class="p-2 overflow-auto"
style="width: {ajustedWidth ? ajustedWidth + 'px' : '50vh'}; max-height: 50vh"
>
{#if payloadData === 'WINDMILL_TOO_BIG'}
<div class="text-center text-primary text-xs">
{#if limitPayloadSize}
Payload too big to be used
{:else}
Payload too big to preview but can still be loaded
{/if}
</div>
{:else}
<div
class="relative p-2"
role="button"
tabindex="0"
aria-label="Copy JSON payload to clipboard"
onclick={() => {
copyToClipboard(JSON.stringify(payloadData))
}}
onkeydown={bubble('keydown')}
>
{#if !objectViewerLoaded && payloadTooBigForPreview}
<div class="flex justify-center items-center py-4">
<Loader2 size={20} class="animate-spin text-primary" />
<span class="ml-2 text-xs text-primary">Loading data...</span>
</div>
{/if}
{#if popoverFullyOpened || !payloadTooBigForPreview}
<div
class={!objectViewerLoaded && payloadTooBigForPreview
? 'invisible h-0 overflow-hidden'
: ''}
>
<ObjectViewerWrapper
json={payloadData}
allowCopy
pureViewer
on:mounted={() => (objectViewerLoaded = true)}
/>
</div>
{/if}
<div class="absolute top-2 right-2 w-full h-full">
<Button
variant="subtle"
unifiedSize="sm"
class="absolute top-0 right-0"
iconOnly
startIcon={{ icon: CopyIcon }}
nonCaptureEvent
/>
</div>
</div>
{/if}
</div>
{/snippet}
</Popover>