mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-30 00:16:53 +00:00
* 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>
217 lines
5.5 KiB
Svelte
217 lines
5.5 KiB
Svelte
<script lang="ts">
|
|
import { createBubbler } from 'svelte/legacy'
|
|
|
|
const bubble = createBubbler()
|
|
import { copyToClipboard, formatDate, formatDateShort } from '$lib/utils'
|
|
import ObjectViewerWrapper from '$lib/components/propertyPicker/ObjectViewerWrapper.svelte'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import Cell from '$lib/components/table/Cell.svelte'
|
|
import { Popover } from '$lib/components/meltComponents'
|
|
import { CopyIcon, Eye, Loader2 } from 'lucide-svelte'
|
|
import Button from '$lib/components/common/button/Button.svelte'
|
|
import { isObjectTooBig } from '$lib/utils'
|
|
import { createEventDispatcher, untrack } from 'svelte'
|
|
import type { FloatingConfig } from '@melt-ui/svelte/internal/actions'
|
|
|
|
interface Props {
|
|
payloadData: Record<string, any> | string
|
|
date: string | undefined
|
|
hovering?: boolean
|
|
placement?: 'bottom-start' | 'top-start' | 'bottom-end' | 'top-end'
|
|
viewerOpen?: boolean
|
|
limitPayloadSize?: boolean
|
|
forceLoad?: boolean
|
|
start?: import('svelte').Snippet
|
|
extra?: import('svelte').Snippet<[any]>
|
|
}
|
|
|
|
let {
|
|
payloadData,
|
|
date,
|
|
hovering = false,
|
|
placement = 'bottom-start',
|
|
viewerOpen = false,
|
|
limitPayloadSize = false,
|
|
forceLoad = $bindable(false),
|
|
start,
|
|
extra
|
|
}: Props = $props()
|
|
|
|
let popover: Popover | undefined = $state(undefined)
|
|
let popoverOpen = false
|
|
let objectViewerLoaded = $state(false)
|
|
let popoverFullyOpened = $state(false)
|
|
let popoverOpenTimeout: ReturnType<typeof setTimeout> | null = null
|
|
|
|
const dispatch = createEventDispatcher()
|
|
const payloadTooBigForPreview =
|
|
untrack(() => payloadData) != 'WINDMILL_TOO_BIG' && isObjectTooBig(untrack(() => payloadData))
|
|
const isTooBig = untrack(() => payloadData) === 'WINDMILL_TOO_BIG' || payloadTooBigForPreview
|
|
|
|
const floatingConfig: FloatingConfig = {
|
|
placement: untrack(() => placement),
|
|
strategy: 'fixed',
|
|
offset: { mainAxis: 4 },
|
|
gutter: 0,
|
|
fitViewport: true
|
|
}
|
|
|
|
function handlePopoverChange(event: CustomEvent<boolean>) {
|
|
const isOpen = event.detail
|
|
popoverOpen = isOpen
|
|
|
|
if (!isOpen) {
|
|
objectViewerLoaded = false
|
|
popoverFullyOpened = false
|
|
forceLoad = 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 handleHoveringChange(isHovering: boolean) {
|
|
if (isHovering && viewerOpen && popover && !popoverOpen) {
|
|
popoverOpenTimeout = setTimeout(() => {
|
|
popover?.open()
|
|
popoverOpenTimeout = null
|
|
}, 100)
|
|
} else if (!isHovering && popoverOpenTimeout) {
|
|
clearTimeout(popoverOpenTimeout)
|
|
popoverOpenTimeout = null
|
|
}
|
|
}
|
|
|
|
$effect(() => {
|
|
hovering
|
|
untrack(() => {
|
|
handleHoveringChange(hovering)
|
|
})
|
|
})
|
|
</script>
|
|
|
|
<Cell>
|
|
{@render start?.()}
|
|
</Cell>
|
|
|
|
<Cell
|
|
wrap
|
|
class="text-2xs font-normal text-left py-2 overflow-auto text-ellipsis scrollbar-none"
|
|
title={formatDate(date)}
|
|
>
|
|
{formatDateShort(date)}
|
|
</Cell>
|
|
|
|
<Cell class="items-center flex flex-row gap-2">
|
|
<div class="flex items-center justify-center border grow min-w-0 rounded-md">
|
|
<div
|
|
class={twMerge(
|
|
'grow min-w-0 text-xs p-1 font-normal text-primary text-left whitespace-nowrap overflow-hidden text-ellipsis',
|
|
hovering && 'border-surface'
|
|
)}
|
|
>
|
|
{JSON.stringify(payloadData)}
|
|
</div>
|
|
|
|
<Popover
|
|
bind:this={popover}
|
|
class="w-fit"
|
|
contentClasses="overflow-auto"
|
|
usePointerDownOutside
|
|
closeOnOtherPopoverOpen
|
|
on:click={(e) => {
|
|
e.stopPropagation()
|
|
}}
|
|
{floatingConfig}
|
|
on:openChange={handlePopoverChange}
|
|
>
|
|
{#snippet trigger()}
|
|
<Button
|
|
variant="contained"
|
|
size="xs2"
|
|
color="light"
|
|
btnClasses="bg-transparent hover:bg-surface"
|
|
nonCaptureEvent
|
|
>
|
|
<Eye size={16} />
|
|
</Button>
|
|
{/snippet}
|
|
|
|
{#snippet content()}
|
|
<div class="relative p-2 max-w-[400px]">
|
|
{#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="w-full h-full"
|
|
role="button"
|
|
tabindex="0"
|
|
aria-label="Copy JSON payload to clipboard"
|
|
onclick={() => {
|
|
copyToClipboard(JSON.stringify(payloadData))
|
|
}}
|
|
onkeydown={bubble('keydown')}
|
|
>
|
|
{#if !objectViewerLoaded && isTooBig}
|
|
<div class="flex justify-center items-center p-4">
|
|
<Loader2 size={20} class="animate-spin text-primary" />
|
|
<span class="ml-2 text-xs text-primary">Loading data...</span>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if popoverFullyOpened || !isTooBig}
|
|
<div
|
|
class={(!objectViewerLoaded && isTooBig ? 'invisible h-0 overflow-hidden' : '') +
|
|
' pr-6'}
|
|
>
|
|
<ObjectViewerWrapper
|
|
json={payloadData}
|
|
allowCopy
|
|
pureViewer
|
|
on:mounted={() => (objectViewerLoaded = true)}
|
|
/>
|
|
</div>
|
|
{/if}
|
|
|
|
<div class="absolute top-2 right-2">
|
|
<Button
|
|
variant="contained"
|
|
size="xs2"
|
|
class="absolute top-0 right-0"
|
|
iconOnly
|
|
startIcon={{ icon: CopyIcon }}
|
|
nonCaptureEvent
|
|
/>
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</div>
|
|
{/snippet}
|
|
</Popover>
|
|
</div>
|
|
|
|
{@render extra?.({ isTooBig })}
|
|
</Cell>
|
|
|
|
<style>
|
|
.scrollbar-none {
|
|
-ms-overflow-style: none; /* IE and Edge */
|
|
scrollbar-width: none; /* Firefox */
|
|
}
|
|
|
|
.scrollbar-none::-webkit-scrollbar {
|
|
display: none; /* Chrome, Safari and Opera */
|
|
}
|
|
</style>
|