mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-18 16:02:10 +00:00
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:
Generated
-7
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
|
||||
Vendored
-2
@@ -1,3 +1 @@
|
||||
import type { AppComponent } from '$lib/components/apps/types'
|
||||
|
||||
declare module 'simple-svelte-autocomplete'
|
||||
|
||||
Reference in New Issue
Block a user