Files
windmill/frontend/src/lib/components/flows/pickers/PickHubScriptQuick.svelte
T
8e27392afa feat: show path in flow script picker (#4574)
* Add popover with path to flow modules

* Add path to items in the flow picker

* fix virtual item display issue

* fix min size

* open popover on keyboard selection

---------

Co-authored-by: Guilhem <guilhem@mbp-de-windmill.home>
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
2024-11-05 08:58:29 +01:00

202 lines
5.1 KiB
Svelte

<script lang="ts">
import { createEventDispatcher } from 'svelte'
import { Skeleton } from '$lib/components/common'
import { classNames } from '$lib/utils'
import { APP_TO_ICON_COMPONENT } from '$lib/components/icons'
import { IntegrationService, ScriptService, type HubScriptKind } from '$lib/gen'
import { Circle } from 'lucide-svelte'
import Popover from '$lib/components/Popover.svelte'
export let kind: HubScriptKind & string = 'script'
export let filter = ''
export let loading = false
export let selected: number | undefined = undefined
let hubNotAvailable = false
const dispatch = createEventDispatcher()
export let appFilter: string | undefined = undefined
export let items: {
path: string
summary: string
id: number
version_id: number
ask_id: number
app: string
kind: HubScriptKind
}[] = []
export let displayPath = false
export let apps: string[] = []
let allApps: string[] = []
$: applyFilter(filter, kind, appFilter)
$: getAllApps(kind)
async function getAllApps(filterKind: typeof kind) {
try {
hubNotAvailable = false
allApps = (
await IntegrationService.listHubIntegrations({
kind: filterKind
})
).map((x) => x.name)
apps = allApps
} catch (err) {
console.error('Hub is not available')
allApps = []
apps = []
hubNotAvailable = true
}
}
let startTs = 0
async function applyFilter(
filter: string,
filterKind: typeof kind,
appFilter: string | undefined
) {
try {
loading = true
hubNotAvailable = false
const ts = Date.now()
startTs = ts
await new Promise((resolved, rejected) => setTimeout(resolved, 200))
if (ts < startTs) return
const scripts =
filter.length > 0
? await ScriptService.queryHubScripts({
text: `${filter}`,
limit: 40,
kind: filterKind
})
: (
await ScriptService.getTopHubScripts({
limit: 40,
kind: filterKind,
app: appFilter
})
).asks ?? []
const mappedItems = scripts.map(
(x: {
summary: string
version_id: number
id: number
ask_id: number
app: string
kind: HubScriptKind
}) => ({
...x,
path: `hub/${x.version_id}/${x.app}/${x.summary.toLowerCase().replaceAll(/\s+/g, '_')}`,
summary: `${x.summary} (${x.app})`
})
)
if (filter.length > 0) {
apps = Array.from(new Set(mappedItems?.map((x) => x.app) ?? [])).sort()
} else {
apps = allApps
}
items = appFilter ? mappedItems.filter((x) => x.app === appFilter) : mappedItems
if (ts === startTs) {
loading = false
}
hubNotAvailable = false
} catch (err) {
hubNotAvailable = true
console.error('Hub not available')
loading = false
}
}
function onKeyDown(e: KeyboardEvent) {
if (
selected != undefined &&
items &&
selected >= 0 &&
selected < items?.length! &&
e.key === 'Enter'
) {
e.preventDefault()
let item = items![selected]
dispatch('pickScript', item)
}
}
</script>
<svelte:window on:keydown={onKeyDown} />
{#if hubNotAvailable}
<div class="text-2xs text-red-400 ftext-2xs font-light text-center py-2 px-3 items-center">
Hub not available
</div>
{:else if loading}
{#each Array(15).fill(0) as _}
<Skeleton layout={[0.1, [1.5]]} />
{/each}
{:else if items.length > 0 && apps.length > 0}
<ul>
{#each items as item, index (item.path)}
<li class="w-full">
<Popover class="w-full" placement="right" forceOpen={index === selected}>
<svelte:fragment slot="text">
<div class="flex flex-col">
<div class="text-left text-xs font-normal leading-tight py-0"
>{item.summary ?? ''}</div
>
<div class="text-left text-2xs font-normal">
{item.path ?? ''}
</div>
</div>
</svelte:fragment>
<button
class="px-3 py-2 gap-2 flex flex-row w-full hover:bg-surface-hover transition-all items-center rounded-md {index ===
selected
? 'bg-surface-hover'
: ''}"
on:click={() => dispatch('pickScript', item)}
>
<div class={classNames('flex justify-center items-center')}>
{#if item['app'] in APP_TO_ICON_COMPONENT}
<svelte:component
this={APP_TO_ICON_COMPONENT[item['app']]}
height={14}
width={14}
/>
{:else}
<div
class="w-[14px] h-[14px] text-gray-400 flex flex-row items-center justify-center"
>
<Circle size="12" />
</div>
{/if}
</div>
<div class="flex flex-col grow min-w-0">
<div
class="grow truncate text-left text-2xs text-primary font-normal leading-tight py-0.5"
>{item.summary ?? ''}</div
>
{#if displayPath && item.path}
<div class="grow truncate text-left text-2xs text-secondary font-[220]">
{item.path}
</div>
{/if}
</div>
{#if index === selected}
<kbd class="!text-xs">&crarr;</kbd>
{/if}
</button>
</Popover>
</li>
{/each}
</ul>
{#if items.length == 40}
<div class="text-2xs text-tercary font-extralight text-center py-2 px-3 items-center">
There are more items than being displayed. Refine your search.
</div>
{/if}
{/if}