Files
windmill/frontend/src/lib/components/LightweightResourcePicker.svelte
Ruben Fiszel 95101f222d nit UX
2024-11-25 23:14:34 +01:00

230 lines
6.0 KiB
Svelte

<script lang="ts">
import { ResourceService } from '$lib/gen'
import { workspaceStore } from '$lib/stores'
import { createEventDispatcher, getContext } from 'svelte'
import Select from './apps/svelte-select/lib/index'
import { SELECT_INPUT_DEFAULT_STYLE } from '../defaults'
import DarkModeObserver from './DarkModeObserver.svelte'
import { Button, Drawer, DrawerContent } from './common'
import { Plus, Loader2, Link2Off } from 'lucide-svelte'
import type { AppViewerContext } from './apps/types'
import { sendUserToast } from '$lib/toast'
const dispatch = createEventDispatcher()
export let initialValue: string | undefined = undefined
export let value: string | undefined = initialValue
export let resourceType: string | undefined = undefined
export let disablePortal = false
export let expressOAuthSetup = false
export let disabled = false
let open = false
let refreshCount = 0
const appViewerContext = getContext<AppViewerContext>('AppViewerContext')
let valueSelect =
initialValue || value
? {
value: value ?? initialValue,
label: value ?? initialValue
}
: undefined
let collection = [valueSelect]
let loading = true
async function loadResources(resourceType: string | undefined) {
loading = true
try {
const nc = (
await ResourceService.listResource({
workspace: appViewerContext?.workspace ?? $workspaceStore,
resourceType
})
).map((x) => ({
value: x.path,
label: x.path
}))
// TODO check if this is needed
if (!nc.find((x) => x.value == value) && (initialValue || value)) {
nc.push({ value: value ?? initialValue!, label: value ?? initialValue! })
}
collection = nc
if (expressOAuthSetup && nc.length > 0) {
value = nc[0].value
valueSelect = nc[0]
}
} finally {
loading = false
}
}
$: $workspaceStore && loadResources(resourceType)
$: dispatch('change', value)
let darkMode: boolean = false
let drawer: Drawer | undefined = undefined
export function askNewResource() {
refreshCount += 1
open = true
}
</script>
<DarkModeObserver bind:darkMode />
{#if expressOAuthSetup}
{#if open}
{#key refreshCount}
{#await import('./AppConnectLightweightResourcePicker.svelte') then Module}
<Module.default
workspace={appViewerContext?.workspace ?? $workspaceStore}
{resourceType}
express={true}
on:error={(e) => {
sendUserToast(e.detail, true)
open = false
}}
on:refresh={async (e) => {
await loadResources(resourceType)
value = e.detail
valueSelect = { value, label: value }
open = false
}}
/>
{/await}
{/key}
{/if}
{:else}
<Drawer bind:this={drawer} size="800px">
<DrawerContent
title="Add a Resource"
on:close={drawer.closeDrawer}
tooltip="Resources represent connections to third party systems. Learn more on how to integrate external APIs."
documentationLink="https://www.windmill.dev/docs/integrations/integrations_on_windmill"
>
{#await import('./AppConnectLightweightResourcePicker.svelte')}
<Loader2 class="animate-spin" />
{:then Module}
<Module.default
workspace={appViewerContext?.workspace ?? $workspaceStore}
{resourceType}
express={false}
on:error={(e) => {
sendUserToast(e.detail, true)
}}
on:refresh={async (e) => {
await loadResources(resourceType)
value = e.detail
valueSelect = { value, label: value }
drawer?.closeDrawer?.()
open = false
}}
/>
{/await}
<!-- <iframe
title="App connection"
class="w-full h-full"
src="{base}/embed_connect?resource_type={resourceType}&workspace={appViewerContext?.workspace ??
$workspaceStore}&express=false"
/> -->
</DrawerContent>
</Drawer>
{/if}
<div class="flex flex-col w-full items-start">
<div class="flex flex-row gap-x-1 w-full justify-between items-center">
{#if loading}
<Loader2 size={12} class="animate-spin" />
{:else if expressOAuthSetup && collection.length < 2}
{#if collection.length == 0}
<div class="text-2xs text-primary pr-2">Connect {resourceType}</div>
{:else if collection.length == 1 && value == collection[0]?.value}
<div class="text-2xs text-primary pr-2 flex items-center gap-1">
{resourceType} <div class="rounded-full w-2 h-2 bg-green-600 animate-pulse" />
</div>
{:else}
<Button
color="light"
size="sm"
on:click={() => {
const item = collection[0]
if (item) {
value = item.value
valueSelect = item
}
}}>Use existing</Button
>
{/if}
{:else}
<Select
{disabled}
portal={!disablePortal}
value={valueSelect}
on:change={(e) => {
value = e.detail.value
valueSelect = e.detail
}}
on:clear={() => {
value = undefined
valueSelect = undefined
}}
items={collection}
class="text-clip grow min-w-0"
placeholder="{resourceType ?? 'any'} resource"
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={darkMode
? SELECT_INPUT_DEFAULT_STYLE.containerStylesDark
: SELECT_INPUT_DEFAULT_STYLE.containerStyles}
/>
{/if}
<div class="flex gap-1 items-center">
{#if valueSelect && expressOAuthSetup}
<Button
{disabled}
color="light"
variant="contained"
size="sm"
btnClasses="w-8 px-0.5 py-1.5"
iconOnly
on:click={async () => {
if (valueSelect && valueSelect.label) {
value = undefined
await ResourceService.deleteResource({
workspace: appViewerContext?.workspace ?? $workspaceStore,
path: valueSelect.label
})
await loadResources(resourceType)
value = undefined
valueSelect = undefined
}
}}
startIcon={{ icon: Link2Off }}
/>
{/if}
{#if resourceType}
<Button
{disabled}
color="light"
variant="contained"
btnClasses="w-8 px-0.5 py-1.5"
size="sm"
on:click={() => {
refreshCount += 1
open = true
drawer?.openDrawer?.()
}}
startIcon={{ icon: Plus }}
iconOnly
/>
{/if}
</div>
</div>
</div>