standardize select auto-complete inputs

* Replaced some Autocompletes

* more AutoComplete replace

* usePromise + replace resources with Select

* Select Chevron + changed operation to new Select

* simplified EditableSchemaWrapper

* replace autocompletes

* Replace last autocomplete

* Remove 'simple-svelte-autocomplete' dependency

* re-added some createText
This commit is contained in:
Diego Imbert
2025-06-12 21:36:55 +02:00
committed by GitHub
parent bebfa76fc2
commit bb354cfeb8
15 changed files with 205 additions and 363 deletions
-7
View File
@@ -125,7 +125,6 @@
"postcss-load-config": "^4.0.1",
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.3.3",
"simple-svelte-autocomplete": "^2.5.1",
"style-to-object": "^0.4.1",
"stylelint-config-recommended": "^13.0.0",
"svelte": "^5.0.0",
@@ -11151,12 +11150,6 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-svelte-autocomplete": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/simple-svelte-autocomplete/-/simple-svelte-autocomplete-2.5.2.tgz",
"integrity": "sha512-6+kZP3XQGb7zs0djTWoFv8tQorBZdv64YfCKrlsYIYBCFsEeikSiNsBSSYw1Om+LYmFeMEOAYSSKoEw+qpnBXQ==",
"dev": true
},
"node_modules/sirv": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.1.tgz",
-1
View File
@@ -50,7 +50,6 @@
"postcss-load-config": "^4.0.1",
"prettier": "^3.1.0",
"prettier-plugin-svelte": "^3.3.3",
"simple-svelte-autocomplete": "^2.5.1",
"style-to-object": "^0.4.1",
"stylelint-config-recommended": "^13.0.0",
"svelte": "^5.0.0",
@@ -262,7 +262,6 @@
? o.split('.')[1]
: o
}))}
clearable={false}
/>
</Cell>
<Cell>
@@ -7,7 +7,6 @@
GranularAclService,
GroupService
} from '$lib/gen'
import AutoComplete from 'simple-svelte-autocomplete'
import TableCustom from './TableCustom.svelte'
import { Alert, Button, Drawer, DrawerContent } from './common'
import Skeleton from './common/skeleton/Skeleton.svelte'
@@ -19,6 +18,7 @@
import Label from './Label.svelte'
import { sendUserToast } from '$lib/toast'
import { createEventDispatcher } from 'svelte'
import Select from './Select.svelte'
export let name: string
let can_write = false
@@ -193,14 +193,11 @@
</div>
{#key ownerKind}
<AutoComplete
required
noInputStyles
items={ownerKind === 'user'
{@const items =
ownerKind === 'user'
? usernames.filter((x) => !perms?.map((y) => y.owner_name).includes('u/' + x))
: groups.filter((x) => !perms?.map((y) => y.owner_name).includes('g/' + x))}
bind:selectedItem={ownerItem}
/>
<Select items={items.map((x) => ({ label: x, value: x }))} bind:value={ownerItem} />
{#if ownerKind == 'group'}
<Button
title="View Group"
@@ -7,7 +7,6 @@
type InstanceGroup
} from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import AutoComplete from 'simple-svelte-autocomplete'
import { createEventDispatcher } from 'svelte'
import { Button } from './common'
import Skeleton from './common/skeleton/Skeleton.svelte'
@@ -18,6 +17,7 @@
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Section from './Section.svelte'
import Label from './Label.svelte'
import Select from './Select.svelte'
export let name: string
let can_write = false
@@ -129,7 +129,7 @@
<Section label={`Members (${members?.length ?? 0})`}>
{#if can_write}
<div class="flex items-start">
<AutoComplete required noInputStyles items={usernames} bind:selectedItem={username} />
<Select items={usernames?.map((u) => ({ value: u, label: u }))} bind:value={username} />
<Button variant="contained" color="blue" size="sm" btnClasses="!ml-4" on:click={addToGroup}>
Add member
</Button>
+32 -10
View File
@@ -15,7 +15,7 @@
value = $bindable(),
filterText: _filterTextBind = $bindable(undefined),
class: className = '',
clearable = true,
clearable = false,
listAutoWidth = true,
disabled: _disabled = false,
containerStyle = '',
@@ -23,11 +23,14 @@
disablePortal = false,
loading = false,
autofocus,
RightIcon,
createText,
groupBy,
sortBy,
onFocus,
onBlur,
onClear
onClear,
onCreateItem
}: {
items?: Item[]
value: Value | undefined
@@ -42,11 +45,14 @@
disablePortal?: boolean
loading?: boolean
autofocus?: boolean
RightIcon?: any
createText?: string
groupBy?: (item: Item) => string
sortBy?: (a: Item, b: Item) => number
onFocus?: () => void
onBlur?: () => void
onClear?: () => void
onCreateItem?: (value: string) => void
} = $props()
let disabled = $derived(_disabled || loading)
@@ -76,7 +82,9 @@
if (!open) filterText = ''
})
let processedItems: (Item & { __select_group?: string; label: string })[] = $derived.by(() => {
type ProcessedItem = Item & { __select_group?: string; __is_create?: true; label: string }
let processedItems: ProcessedItem[] = $derived.by(() => {
let items2 =
items?.map((item) => ({
...item,
@@ -97,12 +105,23 @@
if (sortBy) {
items2 = items2?.sort(sortBy)
}
if (onCreateItem && filterText && !items2.some((item) => item.label === filterText)) {
items2.push({
label: createText ?? `Add new: "${filterText}"`,
value: filterText,
__is_create: true
} as any)
}
return items2
})
let valueEntry = $derived(value && processedItems?.find((item) => item.value === value))
function setValue(item: Item) {
value = item.value
function setValue(item: ProcessedItem) {
if (item.__is_create && onCreateItem) {
onCreateItem(item.value)
} else {
value = item.value
}
filterText = ''
open = false
}
@@ -173,14 +192,17 @@
onfocus={() => onFocus?.()}
onblur={() => onBlur?.()}
>
{#if clearable && !disabled && value}
{#if loading}
<div class="absolute z-10 right-2 h-full flex items-center">
<Loader2 size={18} class="animate-spin" />
</div>
{:else if clearable && !disabled && value}
<div class="absolute z-10 right-2 h-full flex items-center">
<CloseButton noBg small on:close={clearValue} />
</div>
{/if}
{#if loading}
{:else if RightIcon}
<div class="absolute z-10 right-2 h-full flex items-center">
<Loader2 size={20} class="animate-spin" />
<RightIcon size={18} class="text-tertiary/35" />
</div>
{/if}
<!-- svelte-ignore a11y_autofocus -->
@@ -195,7 +217,7 @@
'!bg-surface text-ellipsis',
open ? '' : 'cursor-pointer',
valueEntry && !loading ? '!placeholder-primary' : '',
clearable && !disabled && value ? '!pr-8' : '',
(clearable || RightIcon) && !disabled && value ? '!pr-8' : '',
inputClass ?? ''
)}
autocomplete="off"
@@ -5,7 +5,6 @@
import { GroupService, UserService, GranularAclService } from '$lib/gen'
import { createEventDispatcher } from 'svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import { userStore, workspaceStore } from '$lib/stores'
import { Alert, Button, Drawer } from './common'
import DrawerContent from './common/drawer/DrawerContent.svelte'
@@ -14,6 +13,7 @@
import { isOwner } from '$lib/utils'
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
import Select from './Select.svelte'
const dispatch = createEventDispatcher()
@@ -137,11 +137,11 @@
</ToggleButtonGroup>
</div>
{#key ownerKind}
<AutoComplete
required
noInputStyles
items={ownerKind === 'user' ? usernames : groups}
bind:selectedItem={owner}
<Select
items={(ownerKind === 'user' ? usernames : groups)
.map((x) => x.toString())
.map((x) => ({ value: x, label: x }))}
bind:value={owner}
/>
{/key}
<Button size="sm" on:click={() => addAcl(newOwner, write)}>Add permission</Button>
@@ -3,8 +3,8 @@
import { Button } from './common'
import { superadmin } from '$lib/stores'
import { createEventDispatcher } from 'svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import { defaultTags, nativeTags } from './worker_group'
import Select from './Select.svelte'
const dispatch = createEventDispatcher()
type Props = {
@@ -50,24 +50,14 @@
{#if $superadmin}
<div class="max-w-md space-y-2">
<AutoComplete
noInputStyles
items={[...(customTags ?? []), ...createdTags, ...defaultTags, ...nativeTags].filter(
(x) => !worker_tags?.includes(x)
)}
<Select
items={[...(customTags ?? []), ...createdTags, ...defaultTags, ...nativeTags]
.filter((x) => !worker_tags?.includes(x))
.map((x) => ({ value: x, label: x }))}
{disabled}
bind:selectedItem={newTag}
hideArrow={true}
inputClassName="w-full text-sm bg-surface-primary border border-gray-300 rounded-md px-3 py-2 text-primary placeholder-secondary focus:outline-none"
dropdownClassName="!text-sm !py-2 !rounded-md !border-gray-200 !border !shadow-md bg-white"
className="w-full font-primary text-primary"
bind:value={newTag}
onFocus={() => dispatch('focus')}
create
onCreate={(c: string) => {
createdTags.push(c)
createdTags = [...createdTags]
return c
}}
onCreateItem={(c) => (createdTags.push(c), (createdTags = [...createdTags]), (newTag = c))}
createText="Press Enter to use this tag"
/>
+5 -11
View File
@@ -15,12 +15,12 @@
import DrawerContent from './common/drawer/DrawerContent.svelte'
import Section from './Section.svelte'
import Label from './Label.svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import YAML from 'yaml'
import Toggle from './Toggle.svelte'
import { defaultTags, nativeTags, type AutoscalingConfig } from './worker_group'
import AutoscalingConfigEditor from './AutoscalingConfigEditor.svelte'
import TagsToListenTo from './TagsToListenTo.svelte'
import Select from './Select.svelte'
function computeVCpuAndMemory(workers: [string, WorkerPing[]][]) {
let vcpus = 0
@@ -338,16 +338,10 @@
</Button>
{#if defaultTagPerWorkspace}
<AutoComplete
bind:selectedItem={workspaceTag}
noInputStyles
hideArrow={true}
items={workspaces.map((w) => w.id)}
inputClassName={'flex !font-gray-600 !font-primary !bg-surface-primary'}
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md"
className="!font-gray-600 !font-primary !bg-surface-primary"
create
onCreate={(c) => c}
<Select
bind:value={workspaceTag}
items={workspaces.map((w) => ({ value: w.id, label: w.id }))}
onCreateItem={(c) => (workspaceTag = c)}
placeholder="Workspace ID"
/>
{/if}
@@ -1,3 +1,18 @@
<script lang="ts" module>
async function loadResources(workspace: string): Promise<string[]> {
const r = await ResourceService.listResource({ workspace })
const sPaths = await ScriptService.listScriptPaths({ workspace })
const fPaths = await FlowService.listFlowPaths({ workspace })
const a = await AppService.listApps({ workspace })
return r
.map((r) => r.path)
.concat(sPaths)
.concat(fPaths)
.concat(a.map((a) => a.path))
.sort()
}
</script>
<script lang="ts">
import { goto } from '$lib/navigation'
import type { ActionKind } from '$lib/common'
@@ -15,14 +30,15 @@
} from '$lib/gen'
import { userStore, workspaceStore } from '$lib/stores'
import { Loader2, RefreshCcw } from 'lucide-svelte'
import { onDestroy, tick } from 'svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import { ChevronDown, Loader2, RefreshCcw } from 'lucide-svelte'
import { onDestroy, tick, untrack } from 'svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import Select from '../Select.svelte'
import { usePromise } from '$lib/svelte5Utils.svelte'
let usernames: string[] | undefined = $state()
let resources: string[] | undefined = $state()
let resources = usePromise(() => loadResources($workspaceStore!), { loadInit: false })
let loading: boolean = $state(false)
let page: number | undefined = undefined
@@ -122,31 +138,10 @@
: [$userStore?.username ?? '']
}
async function loadResources() {
const r = await ResourceService.listResource({
workspace: $workspaceStore!
})
const sPaths = await ScriptService.listScriptPaths({
workspace: $workspaceStore!
})
const fPaths = await FlowService.listFlowPaths({
workspace: $workspaceStore!
})
const a = await AppService.listApps({
workspace: $workspaceStore!
})
resources = r
.map((r) => r.path)
.concat(sPaths)
.concat(fPaths)
.concat(a.map((a) => a.path))
.sort()
}
let initialLoad = true
function refreshLogs() {
loadUsers()
loadResources()
resources.refresh()
loadLogs(username, page, perPage, before, after, operation, resource, actionKind, scope)
tick().then(() => {
initialLoad = false
@@ -307,7 +302,7 @@
let refresh = $state(1)
$effect(() => {
$workspaceStore && refresh && refreshLogs()
$workspaceStore && refresh && untrack(() => refreshLogs())
})
// observe all the variables that should trigger an update
$effect(() => {
@@ -398,32 +393,25 @@
<div class="flex gap-1 relative w-full">
<span class="text-xs absolute -top-4">Resource</span>
<AutoComplete
create
onCreate={(resource) => {
resources?.push(resource)
return resource
}}
<Select
onCreateItem={(r) => (resources.value?.push(r), (resource = r))}
createText="Press enter to use this value"
noInputStyles
items={resources}
value={resource}
bind:selectedItem={resource}
inputClassName="!h-[34px] py-1 !text-xs !w-48"
hideArrow
dropdownClassName="!text-sm"
bind:value={resource}
items={['all', ...(resources.value ?? [])].map((r) => ({ value: r, label: r }))}
inputClass="dark:!bg-gray-700"
RightIcon={ChevronDown}
/>
</div>
<div class="flex gap-1 relative w-full">
<span class="text-xs absolute -top-4">Operation</span>
<select bind:value={operation}>
<option selected value="all">all</option>
{#each Object.keys(operations) as e}
<option value={operations[e]}>{e}</option>
{/each}
</select>
<Select
bind:value={operation}
items={['all', ...Object.keys(operations)].map((r) => ({ value: r, label: r }))}
inputClass="dark:!bg-gray-700"
RightIcon={ChevronDown}
/>
</div>
<div class="flex gap-1 relative w-full">
@@ -3,16 +3,7 @@
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
import Tooltip from '../Tooltip.svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import {
AlertCircle,
CheckCircle2,
ChevronDown,
Filter,
Hourglass,
PlayCircle,
X
} from 'lucide-svelte'
import { AlertCircle, CheckCircle2, Filter, Hourglass, PlayCircle, X } from 'lucide-svelte'
import JsonEditor from '../JsonEditor.svelte'
import Toggle from '../Toggle.svelte'
import Label from '../Label.svelte'
@@ -21,6 +12,7 @@
import { createEventDispatcher, untrack } from 'svelte'
import ToggleButtonMore from '../common/toggleButton-v2/ToggleButtonMore.svelte'
import Popover from '$lib/components/meltComponents/Popover.svelte'
import Select from '../Select.svelte'
interface Props {
// Filters
@@ -180,104 +172,44 @@
{#if filterBy == 'user'}
{#key user}
<div class="relative">
{#if user}
<button
class="absolute top-2 right-2 z-50"
onclick={() => {
user = null
dispatch('reset')
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">User</span>
<AutoComplete
create
onCreate={(user) => {
usernames.push(user)
return user
}}
<Select
items={usernames.map((p) => ({ label: p, value: p }))}
bind:value={() => user ?? undefined, (v) => (user = v ?? null)}
clearable
onClear={() => ((user = null), dispatch('reset'))}
inputClass="!h-[32px]"
onCreateItem={(item) => (usernames.push(item), (user = item))}
createText="Press enter to use this value"
noInputStyles
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
{:else if filterBy == 'folder'}
{#key folder}
<div class="relative">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
onclick={() => {
folder = null
dispatch('reset')
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">Folder</span>
<AutoComplete
noInputStyles
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
<Select
items={folders.map((p) => ({ label: p, value: p }))}
bind:value={() => folder ?? undefined, (v) => (folder = v ?? null)}
clearable
onClear={() => ((folder = null), dispatch('reset'))}
inputClass="!h-[32px]"
/>
</div>
{/key}
{:else if filterBy === 'path'}
{#key path}
<div class="relative">
{#if path}
<button
class="absolute top-2 right-2 z-50"
onclick={() => {
path = null
dispatch('reset')
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<span class="text-xs absolute -top-4">Path</span>
<AutoComplete
create
onCreate={(path) => {
paths.push(path)
return path
}}
<Select
items={paths.map((p) => ({ label: p, value: p }))}
bind:value={() => path ?? undefined, (v) => (path = v ?? null)}
clearable
onClear={() => ((path = null), dispatch('reset'))}
inputClass="!h-[32px]"
onCreateItem={(item) => (paths.push(item), (path = item))}
createText="Press enter to use this value"
noInputStyles
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-64 !max-w-64"
/>
</div>
{/key}
@@ -629,94 +561,38 @@
</Label>
{#if filterBy == 'user'}
{#key user}
<Label label="User">
<div class="relative w-full">
{#if user}
<button
class="absolute top-2 right-2 z-50"
onclick={() => {
user = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
items={usernames}
value={user}
bind:selectedItem={user}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={user ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
<Label label="User">
<Select
disablePortal
items={usernames.map((p) => ({ label: p, value: p }))}
bind:value={() => user ?? undefined, (v) => (user = v ?? null)}
clearable
onClear={() => ((user = null), dispatch('reset'))}
inputClass="!h-[32px]"
/>
</Label>
{:else if filterBy == 'folder'}
{#key folder}
<Label label="Folder">
<div class="relative w-full">
{#if folder}
<button
class="absolute top-2 right-2 z-50"
onclick={() => {
folder = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
noInputStyles
items={folders}
value={folder}
bind:selectedItem={folder}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={folder ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
<Label label="Folder">
<Select
disablePortal
items={folders.map((p) => ({ label: p, value: p }))}
bind:value={() => folder ?? undefined, (v) => (folder = v ?? null)}
clearable
onClear={() => ((folder = null), dispatch('reset'))}
inputClass="!h-[32px]"
/>
</Label>
{:else if filterBy === 'path'}
{#key path}
<Label label="Path">
<div class="relative w-full">
{#if path}
<button
class="absolute top-2 right-2 z-50"
onclick={() => {
path = null
}}
>
<X size={14} />
</button>
{:else}
<ChevronDown class="absolute top-2 right-2" size={14} />
{/if}
<AutoComplete
noInputStyles
items={paths}
value={path}
bind:selectedItem={path}
inputClassName="!h-[32px] py-1 !text-xs !w-80"
hideArrow
className={path ? '!font-bold' : ''}
dropdownClassName="!font-normal !w-80 !max-w-80"
/>
</div>
</Label>
{/key}
<Label label="Path">
<Select
disablePortal
items={paths.map((p) => ({ label: p, value: p }))}
bind:value={() => path ?? undefined, (v) => (path = v ?? null)}
clearable
onClear={() => ((path = null), dispatch('reset'))}
inputClass="!h-[32px]"
/>
</Label>
{:else if filterBy === 'tag'}
{#key tag}
<Label label="Tag">
@@ -5,10 +5,10 @@
import { createEventDispatcher } from 'svelte'
import Toggle from '../Toggle.svelte'
import { emptySchema, validateFileExtension } from '$lib/utils'
import AutoComplete from 'simple-svelte-autocomplete'
import { Alert } from '../common'
import AddPropertyV2 from '$lib/components/schema/AddPropertyV2.svelte'
import { Plus } from 'lucide-svelte'
import Select from '../Select.svelte'
interface Props {
schema: Schema | undefined | any
@@ -62,22 +62,7 @@
dispatch('change', schema)
}
function numberOfMatches(listItem: string | undefined, searchWords: string[]): number {
if (!listItem) {
return 0
}
let matches = 0
searchWords.forEach((searchWord) => {
const searchLetters = searchWord.split('')
if (searchLetters.every((l) => listItem.includes(l))) {
matches++
}
})
return matches
}
let suggestedFileExtensions = [
let suggestedFileExtensions = $state([
'json',
'yaml',
'jinja',
@@ -88,8 +73,7 @@
'html',
'xml',
'yml'
]
let autocompleteExtension = $state(true)
])
</script>
{#if !resourceIsTextFile}
@@ -155,55 +139,18 @@
</div>
{/if}
{#if resourceIsTextFile}
<div class="flex items-center space-x-2 w-5/12">
<label for="format-extension" class="text-base font-medium whitespace-nowrap">
File extension{autocompleteExtension ? '' : ' (free text)'}:
</label>
{#if autocompleteExtension}
<AutoComplete
inputId="format-extension"
autofocus={true}
items={[...suggestedFileExtensions, 'Choose another extension']}
onChange={(a) => {
if (a == 'Choose another extension') {
formatExtension = ''
autocompleteExtension = false
}
}}
itemFilterFunction={(listItem, searchWords) => {
if (searchWords.length == 0 || listItem === 'Choose another extension') {
return true
}
return numberOfMatches(listItem, searchWords) > 0
}}
noResultsText="No matches, try the 'Choose another extension' option"
bind:selectedItem={formatExtension}
inputClassName="!h-[32px] py-1 !text-xs !w-64"
hideArrow
className={'!font-bold'}
dropdownClassName="!font-normal !w-64 !max-w-64"
maxItemsToShowInList={8}
moreItemsText={null}
lock={true}
/>
{:else}
<!-- svelte-ignore a11y_autofocus -->
<input
autofocus={true}
bind:value={formatExtension}
class="!h-[32px] py-1 !text-xs !w-64"
placeholder="Enter your extension"
onkeydown={(event) => {
if (event.key === 'Enter') {
if (formatExtension && !suggestedFileExtensions.includes(formatExtension))
suggestedFileExtensions.push(formatExtension)
autocompleteExtension = true
}
}}
/>
{/if}
</div>
<label
for="format-extension"
class="text-base font-medium whitespace-nowrap flex items-center gap-4"
>
File extension :
<Select
autofocus
items={suggestedFileExtensions.map((e) => ({ value: e, label: e }))}
bind:value={formatExtension}
onCreateItem={(ext) => ((formatExtension = ext), suggestedFileExtensions.push(ext))}
/>
</label>
{#if invalidExtension}
<Alert title="Invalid file extension" type="error">
+44
View File
@@ -16,3 +16,47 @@ export function stateSnapshot<T>(state: T) {
export function refreshStateStore<T>(store: StateStore<T>): void {
store.val = $state.snapshot(store.val) as any
}
export type UsePromiseResult<T> = (
| { status: 'loading'; value?: undefined; error?: undefined }
| { status: 'error'; error: any; value?: undefined }
| { status: 'ok'; value: T; error?: undefined }
) & {
refresh: () => void
}
export type UsePromiseOptions = {
loadInit?: boolean
}
export function usePromise<T>(
createPromise: () => Promise<T>,
{ loadInit = true }: UsePromiseOptions = {}
): UsePromiseResult<T> {
const ret: any = $state({
status: 'loading',
__promise: undefined,
refresh: () => {
let promise = createPromise()
ret.__promise = promise
ret.status = 'loading'
ret.value = undefined
ret.error = undefined
promise
.then((value) => {
if (ret.__promise !== promise) return
ret.value = value
ret.status = 'ok'
})
.catch((error) => {
if (ret.__promise !== promise) return
ret.error = error
ret.status = 'error'
})
}
})
if (loadInit) ret.refresh()
return ret
}
@@ -34,13 +34,13 @@
} from '$lib/utils'
import { AlertTriangle, LineChart, List, Plus, Search, Terminal } from 'lucide-svelte'
import { getContext, onDestroy, onMount } from 'svelte'
import AutoComplete from 'simple-svelte-autocomplete'
import YAML from 'yaml'
import { DEFAULT_TAGS_WORKSPACES_SETTING } from '$lib/consts'
import AutoscalingEvents from '$lib/components/AutoscalingEvents.svelte'
import HttpAgentWorkerDrawer from '$lib/components/HttpAgentWorkerDrawer.svelte'
import WorkerRepl from '$lib/components/WorkerRepl.svelte'
import Select from '$lib/components/Select.svelte'
let workers: WorkerPing[] | undefined = undefined
let workerGroups: Record<string, any> | undefined = undefined
@@ -484,14 +484,9 @@
{#if (groupedWorkers ?? []).length > 5}
<div class="flex gap-2 items-center">
<div class="text-secondary text-sm">Worker group:</div>
<AutoComplete
noInputStyles
items={groupedWorkers.map((x) => x[0])}
bind:selectedItem={selectedTab}
hideArrow={true}
inputClassName={'flex !font-gray-600 !font-primary !bg-surface-primary"'}
dropdownClassName="!text-sm !py-2 !rounded-sm !border-gray-200 !border !shadow-md"
className="!font-gray-600 !font-primary !bg-surface-primary"
<Select
items={groupedWorkers.map((x) => ({ value: x[0], label: x[0] }))}
bind:value={selectedTab}
/>
<!-- <select
-2
View File
@@ -1,3 +1 @@
import type { AppComponent } from '$lib/components/apps/types'
declare module 'simple-svelte-autocomplete'