mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-10-21 00:03:31 +00:00
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
196 lines
5.2 KiB
Svelte
196 lines
5.2 KiB
Svelte
<script lang="ts">
|
|
import { Menu, Menubar, MenuItem, MeltButton } from '$lib/components/meltComponents'
|
|
import { ListFilter, Lock, LockOpen } from 'lucide-svelte'
|
|
import Popover from '$lib/components/Popover.svelte'
|
|
import { twMerge } from 'tailwind-merge'
|
|
import VirtualList from '@tutorlatin/svelte-tiny-virtual-list'
|
|
import type { OnSelectedIteration } from '$lib/components/graph/graphBuilder.svelte'
|
|
import { untrack } from 'svelte'
|
|
|
|
interface Props {
|
|
id: string
|
|
moduleId: string
|
|
flowJobs: string[] | undefined
|
|
flowJobsSuccess: (boolean | undefined)[] | undefined
|
|
selected: number
|
|
selectedManually: boolean | undefined
|
|
onSelectedIteration?: OnSelectedIteration
|
|
showIcon?: boolean
|
|
}
|
|
|
|
let {
|
|
id,
|
|
flowJobs,
|
|
flowJobsSuccess,
|
|
selected,
|
|
selectedManually,
|
|
onSelectedIteration,
|
|
moduleId,
|
|
showIcon = true
|
|
}: Props = $props()
|
|
|
|
let filter: number | undefined = $state(undefined)
|
|
function onKeydown(event: KeyboardEvent) {
|
|
if (
|
|
event.key === 'Enter' &&
|
|
filter != undefined &&
|
|
flowJobs &&
|
|
filter < flowJobs.length &&
|
|
filter > 0
|
|
) {
|
|
event.preventDefault()
|
|
onSelectedIteration?.({
|
|
index: filter - 1,
|
|
id: flowJobs[filter - 1],
|
|
manuallySet: true,
|
|
moduleId: moduleId
|
|
})
|
|
menu?.close()
|
|
}
|
|
}
|
|
|
|
let buttonHover = $state(false)
|
|
let menu: Menu | undefined = $state(undefined)
|
|
|
|
let items: { id: string; success: boolean | undefined; index: number }[] = $state([])
|
|
function updateItems() {
|
|
items = (flowJobs ?? [])
|
|
.map((v, i) => {
|
|
return {
|
|
id: v,
|
|
success: flowJobsSuccess?.[i],
|
|
index: i
|
|
}
|
|
})
|
|
.filter((v) => {
|
|
return filter == undefined || (v.index + 1).toString().includes(filter.toString())
|
|
})
|
|
}
|
|
|
|
let isOpen = $state(false)
|
|
|
|
$effect(() => {
|
|
filter
|
|
isOpen && flowJobs && untrack(() => updateItems())
|
|
})
|
|
</script>
|
|
|
|
{#if selectedManually && flowJobsSuccess?.some((x) => x == undefined || x == null)}
|
|
<Popover class="absolute top-1.5 right-12 cursor-pointer">
|
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
<div
|
|
onmouseenter={() => (buttonHover = true)}
|
|
onmouseleave={() => (buttonHover = false)}
|
|
onclick={(e) => {
|
|
buttonHover = false
|
|
onSelectedIteration?.({ manuallySet: false, moduleId: moduleId })
|
|
}}
|
|
>
|
|
{#if buttonHover}
|
|
<LockOpen class="text-primary" size={14} />
|
|
{:else}
|
|
<Lock class="text-primary" size={12} />
|
|
{/if}
|
|
</div>
|
|
{#snippet text()}
|
|
<span>
|
|
The iteration picked is locked. Click to unlock to pick automatically new iterations.
|
|
</span>
|
|
{/snippet}
|
|
</Popover>
|
|
{/if}
|
|
|
|
<Menubar>
|
|
{#snippet children({ createMenu })}
|
|
<Menu
|
|
on:open={() => {
|
|
isOpen = true
|
|
updateItems()
|
|
}}
|
|
on:close={() => {
|
|
isOpen = false
|
|
}}
|
|
{createMenu}
|
|
placement="bottom"
|
|
bind:this={menu}
|
|
usePointerDownOutside
|
|
>
|
|
{#snippet triggr({ trigger })}
|
|
<MeltButton
|
|
title="Pick an iteration"
|
|
id={`flow-editor-iteration picker-${id}`}
|
|
type="button"
|
|
class={twMerge(
|
|
'text-xs bg-surface border-[1px] border-gray-300 dark:border-gray-500 focus:outline-none',
|
|
'hover:bg-surface-hover focus:ring-4 focus:ring-surface-selected font-medium rounded-sm w-[40px] gap-1 h-[20px]',
|
|
'flex items-center justify-center text-secondary'
|
|
)}
|
|
meltElement={trigger}
|
|
>
|
|
#{selected == -1 ? '?' : selected + 1}
|
|
{#if showIcon}
|
|
<ListFilter size={15} />
|
|
{/if}
|
|
</MeltButton>
|
|
{/snippet}
|
|
|
|
{#snippet children({ item: childrenItem })}
|
|
<div class="flex flex-col px-1">
|
|
<input type="number" bind:value={filter} onkeydown={onKeydown} />
|
|
|
|
<div class="max-h-[300px]">
|
|
{#key items}
|
|
{#if items.length > 0}
|
|
<VirtualList height={300} width="100%" itemCount={items.length} itemSize={24}>
|
|
{#snippet header()}{/snippet}
|
|
{#snippet footer()}{/snippet}
|
|
{#snippet item({ index: idx, style })}
|
|
<div {style}>
|
|
<MenuItem
|
|
class={twMerge(
|
|
'text-primary text-xs w-full text-left py-1 pl-2 hover:bg-surface-hover whitespace-nowrap flex flex-row gap-2 items-center',
|
|
'data-[highlighted]:bg-surface-hover',
|
|
items[idx].index == selected ? 'bg-surface-selected' : ''
|
|
)}
|
|
onClick={() => {
|
|
onSelectedIteration?.({
|
|
moduleId: moduleId,
|
|
index: items[idx].index,
|
|
id: items[idx].id,
|
|
manuallySet: true
|
|
})
|
|
menu?.close()
|
|
}}
|
|
item={childrenItem}
|
|
>
|
|
<span
|
|
class="inline-block w-2 h-2 rounded-full shrink-0 {items[idx].success ===
|
|
true
|
|
? 'bg-green-500'
|
|
: items[idx].success === false
|
|
? 'bg-red-500'
|
|
: 'bg-yellow-400'}"
|
|
></span>
|
|
#{items[idx].index + 1}
|
|
</MenuItem>
|
|
</div>
|
|
{/snippet}
|
|
</VirtualList>
|
|
{:else}
|
|
<div class="text-xs text-tertiary py-2 px-2">No iterations</div>
|
|
{/if}
|
|
{/key}
|
|
|
|
<!-- {#each flowJobs ?? [] as id, idx (id)}
|
|
{#if filter == undefined || (idx + 1).toString().includes(filter.toString())}
|
|
|
|
{/if}
|
|
{/each} -->
|
|
</div>
|
|
</div>
|
|
{/snippet}
|
|
</Menu>
|
|
{/snippet}
|
|
</Menubar>
|