mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-25 08:00:59 +00:00
feat: UX improvements (all inputs)
* Monaco transparent bg
* started improving input transform form
* always show static/f selector
* fix connecting btn changing size
* pretty Fill Inputs button
* ResizeTransitionWrapper
* Prettier TemplateEditor
* prevent double onpointerdown when clicking button to close
* text hint
* force focus border for TemplateEditor
* styling in js mode
* update select style
* fix jittery fake monaco placeholder
* select nits
* aiproviderpicker + nits
* smaller ${...} badge
* no-default-style
* select dropdown slide
* nit
* Refresh button in flow picker quick
* jsonEditor pretty
* ai provider toggle button more
* change resource edit button pos
* ResourcePicker Add and Refresh btn
* fix scrollbar
* Fix FileInput and S3 Arg Input
* fix textarea styling
* nicer refresh button in Test This Step
* fix togglebutton border in darkmode
* rounded nit
* Fix multiselect styling
* Prevent crash when selecting dyn-multiselect
* missing $derived and $state => reactivity issue when switching between DynSelect and DynMultiselect
* forgot $effect.pre
* fix nested objects
* nits
* prettier json toggle and array inputs
* array input nits
* nit
* fix json toggle appearing in fileinputs
* nit
* started updating PropertyEditor
* (stash) fix select dropdown animation teleporting from bottom to top
* nit
* nit
* resize transition in schemaform
* nit
* nit typo
* nit enableFlyTransition
* shadow nit
* small consistency changes
* user setting nit
* resize transition in module preview form
* more space
* nit readability on hover
* DateTimeInput new style
* nit fix
* remove yPadding in template and simple editor
* nits
* Revert "remove yPadding in template and simple editor"
This reverts commit 8f27c8d0b8.
* nit
* Fix proppicker border
* fix inconsistent spacing btw arginput and input transform form field headers
* consistent add item button
* nit
* s3 settings nits
* RunsFilter fix
* gray ${...} badge
* border fix darkmode
* nit
* nit app editor consistency
* fix step input gen style
* nit fix
* nits
* toggle border
* nit toggle button
* nit font-medium
* nit font-medium
* nit font-medium
* nit font-medium
* nit
---------
Co-authored-by: Ruben Fiszel <ruben@windmill.dev>
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ResourcePicker from './ResourcePicker.svelte'
|
||||
import ToggleButtonMore from './common/toggleButton-v2/ToggleButtonMore.svelte'
|
||||
|
||||
interface ProviderValue {
|
||||
kind?: AIProvider
|
||||
@@ -154,22 +155,36 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full flex flex-col gap-3">
|
||||
<div
|
||||
class="w-full flex flex-col gap-1 bg-surface-secondary border-[1px] border-nord-400 dark:border-nord-300 rounded-md"
|
||||
>
|
||||
<!-- Provider Selection -->
|
||||
<div class="flex flex-col gap-2">
|
||||
<ToggleButtonGroup selected={value?.kind} onSelected={onProviderChange} {disabled} wrap>
|
||||
<div class="flex flex-col gap-2 m-[-1px] mt-[-1px]">
|
||||
<ToggleButtonGroup
|
||||
selected={value?.kind}
|
||||
onSelected={onProviderChange}
|
||||
{disabled}
|
||||
wrap
|
||||
tabListClass="w-full bg-transparent"
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
{#each providerOptions as option}
|
||||
<ToggleButton value={option.value} label={option.label} {item} />
|
||||
{#each providerOptions.slice(0, 3) as option}
|
||||
<ToggleButton value={option.value} label={option.label} {item} class="bg-transparent" />
|
||||
{/each}
|
||||
<ToggleButtonMore
|
||||
class="ml-auto"
|
||||
togglableItems={providerOptions.slice(3)}
|
||||
{item}
|
||||
bind:selected={() => value?.kind, (v) => v && onProviderChange(v)}
|
||||
/>
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
<!-- Resource Selection -->
|
||||
<div class="flex flex-col border border-gray-200 rounded-md p-2 gap-2">
|
||||
<div class="flex flex-col rounded-md p-2 gap-2">
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm font-medium text-primary">resource</p>
|
||||
<p class="text-sm font-normal text-tertiary">resource</p>
|
||||
<ResourcePicker
|
||||
bind:value={
|
||||
() => resourceValueToPath(value?.resource),
|
||||
@@ -183,12 +198,13 @@
|
||||
disabled={disabled || !value?.kind}
|
||||
placeholder="Select resource"
|
||||
selectFirst={true}
|
||||
selectInputClass="!bg-surface"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Model Selection -->
|
||||
<div class="flex flex-col gap-1">
|
||||
<p class="text-sm font-medium text-primary">model</p>
|
||||
<p class="text-sm font-normal text-tertiary">model</p>
|
||||
<Select
|
||||
{items}
|
||||
bind:value={value.model}
|
||||
@@ -203,6 +219,7 @@
|
||||
clearable={false}
|
||||
noItemsMsg={'No models available'}
|
||||
bind:filterText
|
||||
inputClass="min-h-10 !bg-surface disabled:!bg-surface-disabled"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
valid: boolean
|
||||
create: boolean
|
||||
enumLabels?: Record<string, string> | undefined
|
||||
selectClass?: string
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -22,7 +23,8 @@
|
||||
defaultValue,
|
||||
valid,
|
||||
create,
|
||||
enumLabels = undefined
|
||||
enumLabels = undefined,
|
||||
selectClass = ''
|
||||
}: Props = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -54,7 +56,7 @@
|
||||
<div class="w-full flex-col">
|
||||
<div class="w-full">
|
||||
<Select
|
||||
inputClass={valid ? '' : '!border-red-500/60'}
|
||||
error={!valid}
|
||||
clearable
|
||||
{disabled}
|
||||
autofocus={autofocus ?? undefined}
|
||||
@@ -70,6 +72,7 @@
|
||||
}
|
||||
onFocus={() => dispatch('focus')}
|
||||
onBlur={() => dispatch('blur')}
|
||||
inputClass={selectClass}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
import { workspaceStore } from '$lib/stores'
|
||||
import { getJsonSchemaFromResource } from './schema/jsonSchemaResource.svelte'
|
||||
import AIProviderPicker from './AIProviderPicker.svelte'
|
||||
import TextInput, { inputBaseClass, inputBorderClass } from './text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
label?: string
|
||||
@@ -71,6 +72,7 @@
|
||||
properties?: { [name: string]: SchemaProperty }
|
||||
}
|
||||
| undefined
|
||||
lightHeaderFont?: boolean
|
||||
displayHeader?: boolean
|
||||
properties?: { [name: string]: SchemaProperty } | undefined
|
||||
nestedRequired?: string[] | undefined
|
||||
@@ -120,6 +122,8 @@
|
||||
| undefined
|
||||
workspace?: string | undefined
|
||||
actions?: import('svelte').Snippet
|
||||
innerBottomSnippet?: import('svelte').Snippet
|
||||
fieldHeaderActions?: import('svelte').Snippet
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -133,7 +137,7 @@
|
||||
oneOf = $bindable(undefined),
|
||||
required = false,
|
||||
pattern = $bindable(undefined),
|
||||
valid = $bindable(undefined),
|
||||
valid = $bindable(undefined), // Note : this should not exist, valid and error should be one coherent state
|
||||
enum_ = $bindable(undefined),
|
||||
disabled = false,
|
||||
itemsType = $bindable(undefined),
|
||||
@@ -176,7 +180,10 @@
|
||||
appPath = undefined,
|
||||
computeS3ForceViewerPolicies = undefined,
|
||||
workspace = undefined,
|
||||
actions
|
||||
actions,
|
||||
innerBottomSnippet,
|
||||
fieldHeaderActions,
|
||||
lightHeaderFont = false
|
||||
}: Props = $props()
|
||||
|
||||
$effect(() => {
|
||||
@@ -468,6 +475,11 @@
|
||||
|
||||
let { debounced, clearDebounce } = debounce(() => compareValues(value), 50)
|
||||
let inputCat = $derived(computeInputCat(type, format, itemsType?.type, enum_, contentEncoding))
|
||||
let displayJsonToggleHeader = $derived(
|
||||
displayHeader &&
|
||||
inputCat === 'list' &&
|
||||
!(itemsType?.resourceType === 's3_object' || itemsType?.resourceType === 's3object')
|
||||
)
|
||||
$effect(() => {
|
||||
oneOf && untrack(() => updateOneOfSelected(oneOf))
|
||||
})
|
||||
@@ -518,10 +530,10 @@
|
||||
|
||||
{#snippet variableInput()}
|
||||
{#if variableEditor}
|
||||
<div class="text-sm text-tertiary">
|
||||
<div class="text-sm text-hint">
|
||||
{#if value && typeof value == 'string' && value?.startsWith('$var:')}
|
||||
Linked to variable <button
|
||||
class="text-blue-500 underline"
|
||||
class="text-nord-950 underline font-normal"
|
||||
onclick={() => variableEditor?.editVariable?.(value.slice(5))}>{value.slice(5)}</button
|
||||
>
|
||||
{/if}
|
||||
@@ -587,24 +599,43 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if displayHeader}
|
||||
<FieldHeader
|
||||
prettify={prettifyHeader}
|
||||
label={title && !emptyString(title) ? title : label}
|
||||
{disabled}
|
||||
{required}
|
||||
{type}
|
||||
{contentEncoding}
|
||||
{format}
|
||||
{simpleTooltip}
|
||||
{lightHeader}
|
||||
{displayType}
|
||||
labelClass={css?.label?.class}
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex min-h-7 items-end pb-1">
|
||||
<FieldHeader
|
||||
prettify={prettifyHeader}
|
||||
label={title && !emptyString(title) ? title : label}
|
||||
{disabled}
|
||||
{required}
|
||||
{type}
|
||||
{contentEncoding}
|
||||
{format}
|
||||
{simpleTooltip}
|
||||
{lightHeader}
|
||||
{displayType}
|
||||
labelClass={twMerge(
|
||||
lightHeaderFont ? '!font-normal !text-sm text-tertiary' : '',
|
||||
css?.label?.class
|
||||
)}
|
||||
/>
|
||||
<div class="ml-auto flex gap-2">
|
||||
{#if displayJsonToggleHeader}
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
// Once the user has changed the input type, we should not change it back automatically
|
||||
if (!hasIsListJsonChanged) {
|
||||
hasIsListJsonChanged = true
|
||||
}
|
||||
|
||||
{#if description}
|
||||
<div class={twMerge('text-xs italic pb-1 text-secondary', css?.description?.class)}>
|
||||
<pre class="font-main whitespace-normal">{description}</pre>
|
||||
evalValueToRaw()
|
||||
isListJson = !isListJson
|
||||
}}
|
||||
checked={isListJson}
|
||||
textClass="text-secondary"
|
||||
size="xs"
|
||||
options={{ left: 'json' }}
|
||||
/>
|
||||
{/if}
|
||||
{@render fieldHeaderActions?.()}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -629,7 +660,22 @@
|
||||
/>
|
||||
{:else}
|
||||
<div class="relative w-full">
|
||||
<input
|
||||
<TextInput
|
||||
inputProps={{
|
||||
autofocus,
|
||||
onfocus: bubble('focus'),
|
||||
onblur: bubble('blur'),
|
||||
disabled,
|
||||
type: 'number',
|
||||
onkeydown: () => (ignoreValueUndefined = true),
|
||||
placeholder: placeholder ?? defaultValue ?? '',
|
||||
min: extra['min'],
|
||||
max: extra['max']
|
||||
}}
|
||||
{error}
|
||||
bind:value
|
||||
/>
|
||||
<!-- <input
|
||||
{autofocus}
|
||||
onfocus={bubble('focus')}
|
||||
onblur={bubble('blur')}
|
||||
@@ -645,7 +691,7 @@
|
||||
bind:value
|
||||
min={extra['min']}
|
||||
max={extra['max']}
|
||||
/>
|
||||
/> -->
|
||||
</div>
|
||||
{/if}
|
||||
{:else if inputCat == 'boolean'}
|
||||
@@ -679,6 +725,7 @@
|
||||
bind:editor
|
||||
{appPath}
|
||||
{computeS3ForceViewerPolicies}
|
||||
bottom={innerBottomSnippet}
|
||||
/>
|
||||
{:else if inputCat == 'object' && format == 'json-schema'}
|
||||
{#await import('$lib/components/EditableSchemaForm.svelte')}
|
||||
@@ -722,8 +769,9 @@
|
||||
<Module.default code={JSON.stringify(value, null, 2)} bind:value />
|
||||
{/await}
|
||||
{:else}
|
||||
<div class="py-4 pr-2 pl-6 border rounded-md w-full">
|
||||
<div class="px-3 pt-6 border rounded-md w-full">
|
||||
<SchemaForm
|
||||
lightHeaderFont
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
@@ -768,14 +816,34 @@
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-full">
|
||||
<div class="w-full flex flex-col gap-y-1">
|
||||
{#key redraw}
|
||||
{#if Array.isArray(value)}
|
||||
{#each value ?? [] as v, i}
|
||||
{#if i < itemsLimit}
|
||||
<div class="flex max-w-md mt-1 w-full items-center">
|
||||
<div class="flex w-full items-center relative">
|
||||
{#snippet deleteItemBtn()}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
aria-label="Clear"
|
||||
onclick={() => {
|
||||
value = value.filter((_, index) => index !== i)
|
||||
redraw += 1
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
{/snippet}
|
||||
{#if itemsType?.type == 'number'}
|
||||
<input type="number" bind:value={value[i]} id="arg-input-number-array" />
|
||||
<TextInput
|
||||
inputProps={{ type: 'number', id: 'arg-input-number-array' }}
|
||||
class="pr-8"
|
||||
bind:value={value[i]}
|
||||
/>
|
||||
<div class="absolute z-10 right-1.5">
|
||||
{@render deleteItemBtn()}
|
||||
</div>
|
||||
{:else if itemsType?.type == 'string' && itemsType?.contentEncoding == 'base64'}
|
||||
<input
|
||||
type="file"
|
||||
@@ -783,6 +851,7 @@
|
||||
onchange={(x) => fileChanged(x, (val) => (value[i] = val))}
|
||||
multiple={false}
|
||||
/>
|
||||
{@render deleteItemBtn()}
|
||||
{:else if itemsType?.type == 'object' && itemsType?.resourceType === undefined && itemsType?.properties === undefined && !(format?.startsWith('resource-') && resourceTypes?.includes(format.split('-')[1]))}
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
@@ -792,6 +861,7 @@
|
||||
bind:value={value[i]}
|
||||
/>
|
||||
{/await}
|
||||
{@render deleteItemBtn()}
|
||||
{:else if Array.isArray(itemsType?.enum)}
|
||||
<ArgEnum
|
||||
create={extra['disableCreate'] != true}
|
||||
@@ -809,6 +879,7 @@
|
||||
enum_={itemsType?.enum ?? []}
|
||||
enumLabels={extra['enumLabels']}
|
||||
/>
|
||||
{@render deleteItemBtn()}
|
||||
{:else if (itemsType?.type == 'resource' && itemsType?.resourceType && resourceTypes?.includes(itemsType.resourceType)) || (format?.startsWith('resource-') && resourceTypes?.includes(format.split('-')[1]))}
|
||||
{@const resourceFormat =
|
||||
itemsType?.type == 'resource' &&
|
||||
@@ -821,6 +892,7 @@
|
||||
format={resourceFormat}
|
||||
defaultValue={undefined}
|
||||
/>
|
||||
{@render deleteItemBtn()}
|
||||
{:else if itemsType?.type == 'resource'}
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
@@ -837,9 +909,11 @@
|
||||
bind:value={value[i]}
|
||||
/>
|
||||
{/await}
|
||||
{@render deleteItemBtn()}
|
||||
{:else if itemsType?.type === 'object' && itemsType?.properties}
|
||||
<div class="p-8 border rounded-md w-full">
|
||||
<SchemaForm
|
||||
lightHeaderFont
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
@@ -848,20 +922,17 @@
|
||||
bind:args={value[i]}
|
||||
/>
|
||||
</div>
|
||||
{@render deleteItemBtn()}
|
||||
{:else}
|
||||
<input type="text" bind:value={value[i]} id="arg-input-array" />
|
||||
<TextInput
|
||||
inputProps={{ type: 'text', id: 'arg-input-array' }}
|
||||
class="pr-8"
|
||||
bind:value={value[i]}
|
||||
/>
|
||||
<div class="absolute z-10 right-1.5">
|
||||
{@render deleteItemBtn()}
|
||||
</div>
|
||||
{/if}
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
aria-label="Clear"
|
||||
onclick={() => {
|
||||
value = value.filter((_, index) => index !== i)
|
||||
redraw += 1
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
@@ -874,7 +945,7 @@
|
||||
{#if value.startsWith('$res:')}
|
||||
{@render resourceInput()}
|
||||
{:else}
|
||||
<div class="text-red-500">
|
||||
<div class="text-red-500 text-xs">
|
||||
Invalid string value: "{value}", expected array. Click add item to turn it
|
||||
into an array.
|
||||
</div>
|
||||
@@ -882,59 +953,58 @@
|
||||
{/if}
|
||||
{/key}
|
||||
</div>
|
||||
<div class="flex mt-2 gap-20 items-baseline">
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
btnClasses="mt-1"
|
||||
on:click={() => {
|
||||
if (value == undefined || !Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
if (itemsType?.type == 'number') {
|
||||
value = value.concat(0)
|
||||
} else if (
|
||||
(itemsType?.type == 'object' &&
|
||||
!(
|
||||
format?.startsWith('resource-') &&
|
||||
resourceTypes?.includes(format.split('-')[1])
|
||||
)) ||
|
||||
(itemsType?.type == 'resource' &&
|
||||
!(
|
||||
itemsType?.resourceType && resourceTypes?.includes(itemsType?.resourceType)
|
||||
))
|
||||
) {
|
||||
value = value.concat({})
|
||||
} else {
|
||||
value = value.concat('')
|
||||
}
|
||||
}}
|
||||
id="arg-input-add-item"
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add item
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
btnClasses="text-tertiary py-2.5"
|
||||
wrapperClasses="w-full {Array.isArray(value) && value.length > 0 ? 'mt-1.5' : ''}"
|
||||
on:click={() => {
|
||||
if (value == undefined || !Array.isArray(value)) {
|
||||
value = []
|
||||
}
|
||||
if (itemsType?.type == 'number') {
|
||||
value = value.concat(0)
|
||||
} else if (
|
||||
(itemsType?.type == 'object' &&
|
||||
!(
|
||||
format?.startsWith('resource-') &&
|
||||
resourceTypes?.includes(format.split('-')[1])
|
||||
)) ||
|
||||
(itemsType?.type == 'resource' &&
|
||||
!(itemsType?.resourceType && resourceTypes?.includes(itemsType?.resourceType)))
|
||||
) {
|
||||
value = value.concat({})
|
||||
} else {
|
||||
value = value.concat('')
|
||||
}
|
||||
}}
|
||||
id="arg-input-add-item"
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add item
|
||||
</Button>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="mt-2 mr-4">
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
// Once the user has changed the input type, we should not change it back automatically
|
||||
if (!hasIsListJsonChanged) {
|
||||
hasIsListJsonChanged = true
|
||||
}
|
||||
{#if !displayHeader}
|
||||
<div class="block mt-2.5 pl-2">
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
// Once the user has changed the input type, we should not change it back automatically
|
||||
if (!hasIsListJsonChanged) {
|
||||
hasIsListJsonChanged = true
|
||||
}
|
||||
|
||||
evalValueToRaw()
|
||||
isListJson = !isListJson
|
||||
}}
|
||||
checked={isListJson}
|
||||
textClass="text-secondary"
|
||||
size="xs"
|
||||
options={{ right: 'json' }}
|
||||
/>
|
||||
</div>
|
||||
evalValueToRaw()
|
||||
isListJson = !isListJson
|
||||
}}
|
||||
checked={isListJson}
|
||||
textClass="text-secondary"
|
||||
size="xs"
|
||||
options={{ left: 'json' }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if inputCat == 'dynamic'}
|
||||
<DynamicInput name={label} {otherArgs} {helperScript} bind:value format={format ?? ''} />
|
||||
@@ -957,7 +1027,7 @@
|
||||
/>
|
||||
{:else if inputCat == 'object' || inputCat == 'resource-object' || isListJson}
|
||||
{#if oneOf && oneOf.length >= 2}
|
||||
<div class="flex flex-col gap-2 w-full">
|
||||
<div class="flex flex-col gap-2 w-full border rounded-md p-2">
|
||||
{#if oneOf && oneOf.length >= 2}
|
||||
<ToggleButtonGroup
|
||||
selected={oneOfSelected}
|
||||
@@ -997,70 +1067,70 @@
|
||||
{@const obj = oneOf[objIdx]}
|
||||
{#if obj && obj.properties && Object.keys(obj.properties).length > 0}
|
||||
{#key redraw}
|
||||
<div class="py-4 pr-2 pl-6 border rounded w-full">
|
||||
{#if orderEditable}
|
||||
<SchemaFormDnd
|
||||
{nestedClasses}
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
{prettifyHeader}
|
||||
bind:schema={
|
||||
() => ({
|
||||
properties: obj.properties ?? {},
|
||||
order: obj.order,
|
||||
$schema: '',
|
||||
required: obj.required ?? [],
|
||||
type: 'object'
|
||||
}),
|
||||
() => {
|
||||
dispatch('nestedChange')
|
||||
}
|
||||
}
|
||||
bind:args={value}
|
||||
hiddenArgs={[
|
||||
oneOf?.find((o) => Object.keys(o.properties ?? {}).includes('kind'))
|
||||
? 'kind'
|
||||
: 'label'
|
||||
]}
|
||||
on:reorder={(e) => {
|
||||
if (oneOf && oneOf[objIdx]) {
|
||||
const keys = e.detail
|
||||
oneOf[objIdx].order = keys
|
||||
}
|
||||
}}
|
||||
on:nestedChange
|
||||
{shouldDispatchChanges}
|
||||
/>
|
||||
{:else}
|
||||
<SchemaForm
|
||||
{nestedClasses}
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
{prettifyHeader}
|
||||
hiddenArgs={['label', 'kind']}
|
||||
schema={{
|
||||
properties: obj.properties,
|
||||
{#if orderEditable}
|
||||
<SchemaFormDnd
|
||||
lightHeaderFont
|
||||
{nestedClasses}
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
{prettifyHeader}
|
||||
bind:schema={
|
||||
() => ({
|
||||
properties: obj.properties ?? {},
|
||||
order: obj.order,
|
||||
$schema: '',
|
||||
required: obj.required ?? [],
|
||||
type: 'object'
|
||||
}}
|
||||
bind:args={
|
||||
() => value,
|
||||
(v) => {
|
||||
value = { ...v, [tagKey]: oneOfSelected }
|
||||
}
|
||||
}
|
||||
{shouldDispatchChanges}
|
||||
on:change={() => {
|
||||
}),
|
||||
() => {
|
||||
dispatch('nestedChange')
|
||||
}}
|
||||
on:nestedChange
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
bind:args={value}
|
||||
hiddenArgs={[
|
||||
oneOf?.find((o) => Object.keys(o.properties ?? {}).includes('kind'))
|
||||
? 'kind'
|
||||
: 'label'
|
||||
]}
|
||||
on:reorder={(e) => {
|
||||
if (oneOf && oneOf[objIdx]) {
|
||||
const keys = e.detail
|
||||
oneOf[objIdx].order = keys
|
||||
}
|
||||
}}
|
||||
on:nestedChange
|
||||
{shouldDispatchChanges}
|
||||
/>
|
||||
{:else}
|
||||
<SchemaForm
|
||||
lightHeaderFont
|
||||
{nestedClasses}
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
{disabled}
|
||||
{prettifyHeader}
|
||||
hiddenArgs={['label', 'kind']}
|
||||
schema={{
|
||||
properties: obj.properties,
|
||||
order: obj.order,
|
||||
$schema: '',
|
||||
required: obj.required ?? [],
|
||||
type: 'object'
|
||||
}}
|
||||
bind:args={
|
||||
() => value,
|
||||
(v) => {
|
||||
value = { ...v, [tagKey]: oneOfSelected }
|
||||
}
|
||||
}
|
||||
{shouldDispatchChanges}
|
||||
on:change={() => {
|
||||
dispatch('nestedChange')
|
||||
}}
|
||||
on:nestedChange
|
||||
/>
|
||||
{/if}
|
||||
{/key}
|
||||
{:else if disabled}
|
||||
<textarea disabled></textarea>
|
||||
@@ -1107,9 +1177,10 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else if properties && Object.keys(properties).length > 0 && inputCat !== 'list'}
|
||||
<div class={hideNested ? 'hidden' : 'py-4 pr-2 pl-6 border rounded-md w-full'}>
|
||||
<div class={hideNested ? 'hidden' : 'px-3 pt-6 border rounded-md w-full'}>
|
||||
{#if orderEditable}
|
||||
<SchemaFormDnd
|
||||
lightHeaderFont
|
||||
{nestedClasses}
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
@@ -1140,6 +1211,7 @@
|
||||
/>
|
||||
{:else}
|
||||
<SchemaForm
|
||||
lightHeaderFont
|
||||
{nestedClasses}
|
||||
{onlyMaskPassword}
|
||||
{disablePortal}
|
||||
@@ -1190,8 +1262,8 @@
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
{#if inputCat == 'list'}
|
||||
<div class="block">
|
||||
{#if inputCat == 'list' && !displayHeader}
|
||||
<div class="block mt-2.5 pl-2">
|
||||
<Toggle
|
||||
on:change={(e) => {
|
||||
isListJson = !isListJson
|
||||
@@ -1199,7 +1271,7 @@
|
||||
checked={isListJson}
|
||||
textClass="text-secondary"
|
||||
size="xs"
|
||||
options={{ right: 'json' }}
|
||||
options={{ left: 'json' }}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -1220,6 +1292,7 @@
|
||||
dispatch('blur')
|
||||
}}
|
||||
enumLabels={extra['enumLabels']}
|
||||
selectClass="min-h-10"
|
||||
/>
|
||||
</div>
|
||||
{:else if inputCat == 'date'}
|
||||
@@ -1328,12 +1401,7 @@
|
||||
use:autosize
|
||||
onkeydown={onKeyDown}
|
||||
{disabled}
|
||||
class={twMerge(
|
||||
'w-full',
|
||||
valid
|
||||
? ''
|
||||
: 'border border-red-700 border-opacity-30 focus:border-red-700 focus:border-opacity-3'
|
||||
)}
|
||||
class={twMerge('w-full', inputBaseClass, inputBorderClass({ error: !!error }))}
|
||||
placeholder={placeholder ?? defaultValue ?? ''}
|
||||
bind:value
|
||||
></textarea>
|
||||
@@ -1344,7 +1412,7 @@
|
||||
<button
|
||||
class="absolute {password || extra?.['password'] == true
|
||||
? 'right-16 top-1.5'
|
||||
: 'right-1 top-1'} opacity-0 group-hover:opacity-100 duration-200 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
|
||||
: 'right-1 top-[7px]'} opacity-0 group-hover:opacity-100 duration-200 py-1 min-w-min !px-2 items-center text-gray-800 bg-surface-secondary border rounded center-center hover:bg-gray-300 transition-all cursor-pointer"
|
||||
onclick={() => {
|
||||
pickForField = label
|
||||
itemPicker?.openDrawer?.()
|
||||
@@ -1362,14 +1430,20 @@
|
||||
{@render actions?.()}
|
||||
</div>
|
||||
|
||||
{#if !compact || (error && error != '')}
|
||||
<div class="text-right text-xs text-red-600 dark:text-red-400">
|
||||
{#if disabled || error === ''}
|
||||
|
||||
{:else}
|
||||
{error}
|
||||
{/if}
|
||||
{#if description}
|
||||
<div class={twMerge('text-2xs italic py-1 text-hint', css?.description?.class)}>
|
||||
<pre class="font-main whitespace-normal">{description}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if !compact || (error && error != '')}
|
||||
{#if disabled || error === ''}
|
||||
|
||||
{:else}
|
||||
<div class="text-right text-xs text-red-600 dark:text-red-400 mb-2">
|
||||
{error}
|
||||
</div>
|
||||
{/if}
|
||||
{:else if !noMargin}
|
||||
<div class="mb-2"></div>
|
||||
{/if}
|
||||
@@ -1381,10 +1455,4 @@
|
||||
-webkit-appearance: none !important;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
input[type='number'] {
|
||||
-moz-appearance: textfield !important;
|
||||
appearance: textfield !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import type { SchemaProperty } from '$lib/common'
|
||||
import Toggle from './Toggle.svelte'
|
||||
import { tick } from 'svelte'
|
||||
import Select from './select/Select.svelte'
|
||||
|
||||
interface Props {
|
||||
canEditResourceType?: boolean
|
||||
@@ -54,39 +55,44 @@
|
||||
|
||||
{#if canEditResourceType || originalType == 'string[]' || originalType == 'object[]'}
|
||||
<Label label="Items type">
|
||||
<select
|
||||
bind:value={selected}
|
||||
onchange={() => {
|
||||
if (selected == 'enum') {
|
||||
itemsType = { type: 'string', enum: [] }
|
||||
} else if (selected == 'string') {
|
||||
itemsType = { type: 'string' }
|
||||
} else if (selected == 'number') {
|
||||
itemsType = { type: 'number' }
|
||||
} else if (selected == 'object') {
|
||||
itemsType = { ...itemsType, type: 'object' }
|
||||
} else if (selected == 'bytes') {
|
||||
itemsType = { type: 'string', contentEncoding: 'base64' }
|
||||
} else if (selected == 'resource') {
|
||||
itemsType = { type: 'resource', resourceType: itemsType?.resourceType }
|
||||
} else if (selected == 's3object') {
|
||||
itemsType = { type: 'object', resourceType: 's3object' }
|
||||
} else {
|
||||
itemsType = undefined
|
||||
<Select
|
||||
bind:value={
|
||||
() => selected,
|
||||
(v) => {
|
||||
selected = v
|
||||
if (selected == 'enum') {
|
||||
itemsType = { type: 'string', enum: [] }
|
||||
} else if (selected == 'string') {
|
||||
itemsType = { type: 'string' }
|
||||
} else if (selected == 'number') {
|
||||
itemsType = { type: 'number' }
|
||||
} else if (selected == 'object') {
|
||||
itemsType = { ...itemsType, type: 'object' }
|
||||
} else if (selected == 'bytes') {
|
||||
itemsType = { type: 'string', contentEncoding: 'base64' }
|
||||
} else if (selected == 'resource') {
|
||||
itemsType = { type: 'resource', resourceType: itemsType?.resourceType }
|
||||
} else if (selected == 's3object') {
|
||||
itemsType = { type: 'object', resourceType: 's3object' }
|
||||
} else {
|
||||
itemsType = undefined
|
||||
}
|
||||
}
|
||||
}}
|
||||
id="array-type-narrowing"
|
||||
>
|
||||
<option value="string"> Items are strings</option>
|
||||
<option value="enum">Items are strings from an enum</option>
|
||||
{#if originalType != 'string[]'}
|
||||
<option value="s3object">Items are S3 objects</option>
|
||||
<option value="object"> Items are objects (JSON)</option>
|
||||
<option value="resource"> Items are resources</option>
|
||||
<option value="number">Items are numbers</option>
|
||||
<option value="bytes">Items are bytes</option>
|
||||
{/if}
|
||||
</select>
|
||||
}
|
||||
items={[
|
||||
{ value: 'string', label: 'Items are strings' },
|
||||
{ value: 'enum', label: 'Items are strings from an enum' },
|
||||
...(originalType != 'string[]'
|
||||
? [
|
||||
{ value: 's3object', label: 'Items are S3 objects' },
|
||||
{ value: 'object', label: 'Items are objects (JSON)' },
|
||||
{ value: 'resource', label: 'Items are resources' },
|
||||
{ value: 'number', label: 'Items are numbers' },
|
||||
{ value: 'bytes', label: 'Items are bytes' }
|
||||
]
|
||||
: [])
|
||||
] as { value: string; label: string }[]}
|
||||
/>
|
||||
</Label>
|
||||
{:else if itemsType?.resourceType}
|
||||
<Label label="Resource type">
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { Clock, X } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
import { inputBaseClass, inputBorderClass } from './text_input/TextInput.svelte'
|
||||
// import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
|
||||
interface Props {
|
||||
@@ -110,20 +111,21 @@
|
||||
bind:value={date}
|
||||
{autofocus}
|
||||
{disabled}
|
||||
class={twMerge('h-8 text-sm !w-3/4 ', inputClass)}
|
||||
class={twMerge(inputBaseClass, inputBorderClass(), 'text-sm !w-3/4 ', inputClass)}
|
||||
min={minDate}
|
||||
max={maxDate}
|
||||
/>
|
||||
<input
|
||||
type="time"
|
||||
bind:value={time}
|
||||
class={twMerge('h-8 text-sm !w-1/4 min-w-[100px] ', inputClass)}
|
||||
class={twMerge(inputBaseClass, inputBorderClass(), 'text-sm !w-1/4 min-w-[100px] ', inputClass)}
|
||||
{disabled}
|
||||
/>
|
||||
<Button
|
||||
variant="border"
|
||||
variant="contained"
|
||||
color="light"
|
||||
wrapperClasses="h-8"
|
||||
wrapperClasses="h-full"
|
||||
btnClasses="bg-surface-secondary"
|
||||
startIcon={{
|
||||
icon: Clock
|
||||
}}
|
||||
@@ -168,7 +170,7 @@
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
wrapperClasses="h-8"
|
||||
wrapperClasses="h-full"
|
||||
{disabled}
|
||||
on:click={() => {
|
||||
value = undefined
|
||||
|
||||
@@ -358,11 +358,11 @@
|
||||
['String', 'string'],
|
||||
['Number', 'number'],
|
||||
['Integer', 'integer'],
|
||||
['Object', 'object'],
|
||||
['Boolean', 'boolean'],
|
||||
['Resource', 'resource'],
|
||||
['Object', 'object'],
|
||||
['OneOf', 'oneOf'],
|
||||
['Array', 'array'],
|
||||
['Boolean', 'boolean'],
|
||||
['S3', 'S3']
|
||||
])
|
||||
|
||||
@@ -530,7 +530,7 @@
|
||||
{:else}
|
||||
<!-- WIP -->
|
||||
{#if jsonEnabled && customUi?.jsonOnly != true}
|
||||
<div class="w-full p-3 flex gap-4 justify-end items-center">
|
||||
<div class="w-full px-3 flex gap-4 justify-end items-center">
|
||||
{#if addPropertyInEditorTab}
|
||||
<AddPropertyV2
|
||||
bind:schema
|
||||
@@ -549,8 +549,8 @@
|
||||
label="JSON View"
|
||||
size="xs"
|
||||
options={{
|
||||
right: 'JSON editor',
|
||||
rightTooltip:
|
||||
left: 'JSON editor',
|
||||
leftTooltip:
|
||||
'Arguments can be edited either using the wizard, or by editing their JSON Schema.'
|
||||
}}
|
||||
lightMode
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
autoheight?: boolean
|
||||
lineNumbersWidth?: number
|
||||
lineNumbersOffset?: number
|
||||
showNumbers?: boolean
|
||||
class?: string
|
||||
fontSize?: number
|
||||
}
|
||||
@@ -18,6 +19,7 @@
|
||||
lineNumbersWidth = 51,
|
||||
lineNumbersOffset = 0,
|
||||
class: className = '',
|
||||
showNumbers = true,
|
||||
fontSize = 14
|
||||
}: Props = $props()
|
||||
|
||||
@@ -76,7 +78,9 @@
|
||||
class="margin-view-overlays"
|
||||
role="presentation"
|
||||
aria-hidden="true"
|
||||
style="position: absolute; font-family: {fontFamily}; font-weight: normal; font-size: {fontSize}px; font-feature-settings: 'liga' 0, 'calt' 0; font-variation-settings: normal; line-height: {lineHeight}px; letter-spacing: 0px; width: {lineNumbersWidth}px; height: 4893px;"
|
||||
style="position: absolute; font-family: {fontFamily}; font-weight: normal; font-size: {fontSize}px; font-feature-settings: 'liga' 0, 'calt' 0; font-variation-settings: normal; line-height: {lineHeight}px; letter-spacing: 0px; width: {lineNumbersWidth}px; height: 4893px; display: {!showNumbers
|
||||
? 'none'
|
||||
: 'block'};"
|
||||
>
|
||||
{#each lines as _, i}
|
||||
<div style="top:{lineHeight * i}px;height:{lineHeight}px;">
|
||||
|
||||
@@ -40,17 +40,15 @@
|
||||
{/if}
|
||||
|
||||
{#if displayType}
|
||||
{#if format && !format.startsWith('resource') && !format.startsWith('jsonschema-')}
|
||||
<span class="text-xs italic ml-2 text-tertiary dark:text-indigo-400">
|
||||
<span class="text-xs italic ml-2 text-hint">
|
||||
{#if format && !format.startsWith('resource') && !format.startsWith('jsonschema-')}
|
||||
{format}
|
||||
</span>
|
||||
{:else}
|
||||
<span class="text-xs italic ml-2 text-tertiary dark:text-indigo-400">
|
||||
{:else}
|
||||
{type ?? 'any'}{contentEncoding && contentEncoding != ''
|
||||
? `, encoding: ${contentEncoding}`
|
||||
: ''}
|
||||
</span>
|
||||
{/if}
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if !emptyString(simpleTooltip)}
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
import { Plug } from 'lucide-svelte'
|
||||
import { Button } from './common'
|
||||
import AnimatedButton from './common/button/AnimatedButton.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let connecting: boolean
|
||||
export let id: undefined | string = undefined
|
||||
export let wrapperClasses = ''
|
||||
</script>
|
||||
|
||||
<AnimatedButton animate={connecting} baseRadius="6px" animationDuration="2s" marginWidth="2px">
|
||||
@@ -12,10 +14,14 @@
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs2"
|
||||
btnClasses={connecting ? 'text-blue-500' : 'text-primary'}
|
||||
btnClasses={twMerge(
|
||||
connecting ? 'text-blue-500' : 'text-tertiary',
|
||||
'group/plug-btn overflow-clip flex p-0'
|
||||
)}
|
||||
on:click
|
||||
{...id ? { id } : {}}
|
||||
{wrapperClasses}
|
||||
>
|
||||
<Plug size={16} /> →
|
||||
<Plug size={14} />
|
||||
</Button>
|
||||
</AnimatedButton>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<script lang="ts">
|
||||
import { run } from 'svelte/legacy'
|
||||
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import {
|
||||
type Folder,
|
||||
@@ -19,7 +17,7 @@
|
||||
import { Eye, Plus } from 'lucide-svelte'
|
||||
import Label from './Label.svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import Select from './select/Select.svelte'
|
||||
import { safeSelectItems } from './select/utils.svelte'
|
||||
|
||||
@@ -139,9 +137,11 @@
|
||||
dispatch('update')
|
||||
loadFolder()
|
||||
}
|
||||
run(() => {
|
||||
$effect.pre(() => {
|
||||
if ($workspaceStore && $userStore) {
|
||||
load()
|
||||
untrack(() => {
|
||||
load()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -327,7 +327,6 @@
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
position="center"
|
||||
value="writer"
|
||||
label="Writer"
|
||||
tooltip="A writer of a folder has read AND write access to all the elements (scripts/flows/apps/schedules/resources/variables) inside the folder"
|
||||
@@ -335,7 +334,6 @@
|
||||
/>
|
||||
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
label="Admin"
|
||||
tooltip="An admin of a folder has read AND write access to all the elements inside the folders and can manage the permissions as well as add new admins"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
type InstanceGroup
|
||||
} from '$lib/gen'
|
||||
import { userStore, workspaceStore } from '$lib/stores'
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createEventDispatcher, untrack } from 'svelte'
|
||||
import { Button } from './common'
|
||||
import Skeleton from './common/skeleton/Skeleton.svelte'
|
||||
import TableCustom from './TableCustom.svelte'
|
||||
@@ -20,16 +20,20 @@
|
||||
import Select from './select/Select.svelte'
|
||||
import { safeSelectItems } from './select/utils.svelte'
|
||||
|
||||
export let name: string
|
||||
let can_write = false
|
||||
interface Props {
|
||||
name: string
|
||||
}
|
||||
|
||||
let { name }: Props = $props()
|
||||
let can_write = $state(false)
|
||||
|
||||
type Role = 'member' | 'manager' | 'admin'
|
||||
let group: Group | undefined
|
||||
let instance_group: InstanceGroup | undefined
|
||||
let members: { member_name: string; role: Role }[] | undefined = undefined
|
||||
let usernames: string[] | undefined = []
|
||||
let username: string = ''
|
||||
let summary = ''
|
||||
let instance_group: InstanceGroup | undefined = $state()
|
||||
let members: { member_name: string; role: Role }[] | undefined = $state(undefined)
|
||||
let usernames: string[] | undefined = $state([])
|
||||
let username: string = $state('')
|
||||
let summary = $state('')
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
@@ -37,12 +41,6 @@
|
||||
usernames = await UserService.listUsernames({ workspace: $workspaceStore! })
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($workspaceStore && $userStore) {
|
||||
load()
|
||||
}
|
||||
}
|
||||
|
||||
async function load() {
|
||||
return Promise.all([loadGroup(), loadInstanceGroup(), loadUsernames()])
|
||||
}
|
||||
@@ -104,6 +102,13 @@
|
||||
return 'member'
|
||||
}
|
||||
}
|
||||
$effect.pre(() => {
|
||||
if ($workspaceStore && $userStore) {
|
||||
untrack(() => {
|
||||
load()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<Section label="Metadata" class="mb-4">
|
||||
@@ -138,150 +143,155 @@
|
||||
{/if}
|
||||
{#if members}
|
||||
<TableCustom>
|
||||
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
|
||||
<tr slot="header-row">
|
||||
<th>user</th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each members as { member_name, role }}<tr>
|
||||
<td>{member_name}</td>
|
||||
<td>
|
||||
{#if can_write}
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={role}
|
||||
on:selected={async (e) => {
|
||||
const role = e.detail
|
||||
// const wasInGroup = (group?.members ?? []).includes(group)
|
||||
// const inAcl = (
|
||||
// group?.extra_perms ? Object.keys(group?.extra_perms) : []
|
||||
// ).includes(group)
|
||||
if (role == 'member') {
|
||||
await GroupService.addUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: {
|
||||
username: member_name
|
||||
}
|
||||
})
|
||||
await GranularAclService.removeGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name
|
||||
}
|
||||
})
|
||||
} else if (role == 'manager') {
|
||||
await GroupService.removeUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: {
|
||||
username: member_name
|
||||
}
|
||||
})
|
||||
await GranularAclService.addGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name,
|
||||
write: true
|
||||
}
|
||||
})
|
||||
} else if (role == 'admin') {
|
||||
await GroupService.addUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: {
|
||||
username: member_name
|
||||
}
|
||||
})
|
||||
await GranularAclService.addGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name,
|
||||
write: true
|
||||
}
|
||||
})
|
||||
}
|
||||
loadGroup()
|
||||
}}
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="member"
|
||||
size="xs"
|
||||
label="Member"
|
||||
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="admin"
|
||||
size="xs"
|
||||
label="Admin"
|
||||
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
|
||||
{item}
|
||||
/>
|
||||
{#if role === 'manager'}
|
||||
{#snippet body()}
|
||||
<tbody>
|
||||
{#each members ?? [] as { member_name, role }}<tr>
|
||||
<td>{member_name}</td>
|
||||
<td>
|
||||
{#if can_write}
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={role}
|
||||
on:selected={async (e) => {
|
||||
const role = e.detail
|
||||
// const wasInGroup = (group?.members ?? []).includes(group)
|
||||
// const inAcl = (
|
||||
// group?.extra_perms ? Object.keys(group?.extra_perms) : []
|
||||
// ).includes(group)
|
||||
if (role == 'member') {
|
||||
await GroupService.addUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: {
|
||||
username: member_name
|
||||
}
|
||||
})
|
||||
await GranularAclService.removeGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name
|
||||
}
|
||||
})
|
||||
} else if (role == 'manager') {
|
||||
await GroupService.removeUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: {
|
||||
username: member_name
|
||||
}
|
||||
})
|
||||
await GranularAclService.addGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name,
|
||||
write: true
|
||||
}
|
||||
})
|
||||
} else if (role == 'admin') {
|
||||
await GroupService.addUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: {
|
||||
username: member_name
|
||||
}
|
||||
})
|
||||
await GranularAclService.addGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name,
|
||||
write: true
|
||||
}
|
||||
})
|
||||
}
|
||||
loadGroup()
|
||||
}}
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="manager"
|
||||
size="xs"
|
||||
label="Manager"
|
||||
tooltip="A manager of a group can manage the group, adding and removing users and
|
||||
change their roles. Being a manager does not make you a member"
|
||||
value="member"
|
||||
small
|
||||
label="Member"
|
||||
tooltip="A Member of a group can see everything the group can see, write to everything the group can write, and generally act on behalf of the group"
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
{role}
|
||||
{/if}</td
|
||||
>
|
||||
<td>
|
||||
{#if can_write}
|
||||
<button
|
||||
class="ml-2 text-red-500"
|
||||
on:click={async () => {
|
||||
await GroupService.removeUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: { username: member_name }
|
||||
})
|
||||
await GranularAclService.removeGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name
|
||||
}
|
||||
})
|
||||
loadGroup()
|
||||
}}>remove</button
|
||||
>
|
||||
{/if}</td
|
||||
>
|
||||
</tr>{/each}
|
||||
</tbody>
|
||||
<ToggleButton
|
||||
value="admin"
|
||||
small
|
||||
label="Admin"
|
||||
tooltip="An admin of a group is a member of a group that can also add and remove members to the group, or make them admin."
|
||||
{item}
|
||||
/>
|
||||
{#if role === 'manager'}
|
||||
<ToggleButton
|
||||
value="manager"
|
||||
small
|
||||
label="Manager"
|
||||
tooltip="A manager of a group can manage the group, adding and removing users and
|
||||
change their roles. Being a manager does not make you a member"
|
||||
{item}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
{:else}
|
||||
{role}
|
||||
{/if}</td
|
||||
>
|
||||
<td>
|
||||
{#if can_write}
|
||||
<button
|
||||
class="ml-2 text-red-500"
|
||||
onclick={async () => {
|
||||
await GroupService.removeUserToGroup({
|
||||
workspace: $workspaceStore ?? '',
|
||||
name,
|
||||
requestBody: { username: member_name }
|
||||
})
|
||||
await GranularAclService.removeGranularAcls({
|
||||
workspace: $workspaceStore ?? '',
|
||||
path: name,
|
||||
kind: 'group_',
|
||||
requestBody: {
|
||||
owner: 'u/' + member_name
|
||||
}
|
||||
})
|
||||
loadGroup()
|
||||
}}>remove</button
|
||||
>
|
||||
{/if}</td
|
||||
>
|
||||
</tr>{/each}
|
||||
</tbody>
|
||||
{/snippet}
|
||||
</TableCustom>
|
||||
|
||||
{#if instance_group?.emails}
|
||||
<h2 class="mt-10">Members from the instance group</h2>
|
||||
<TableCustom>
|
||||
<!-- @migration-task: migrate this slot by hand, `header-row` is an invalid identifier -->
|
||||
<tr slot="header-row">
|
||||
<th>user</th>
|
||||
</tr>
|
||||
<tbody slot="body">
|
||||
{#each instance_group?.emails ?? [] as email}<tr>
|
||||
<td>{email}</td>
|
||||
</tr>{/each}
|
||||
</tbody>
|
||||
{#snippet body()}
|
||||
<tbody>
|
||||
{#each instance_group?.emails ?? [] as email}<tr>
|
||||
<td>{email}</td>
|
||||
</tr>{/each}
|
||||
</tbody>
|
||||
{/snippet}
|
||||
</TableCustom>
|
||||
{/if}
|
||||
{:else}
|
||||
|
||||
@@ -23,7 +23,6 @@
|
||||
import ToggleButtonGroup from '$lib/components/common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import ToggleButton from '$lib/components/common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { tick } from 'svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import { buildPrefixRegex } from './flows/previousResults'
|
||||
import type VariableEditor from './VariableEditor.svelte'
|
||||
import type ItemPicker from './ItemPicker.svelte'
|
||||
@@ -39,6 +38,7 @@
|
||||
import FlowPlugConnect from './FlowPlugConnect.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import S3ArrayHelperButton from './S3ArrayHelperButton.svelte'
|
||||
import { inputBorderClass } from './text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
schema: Schema | { properties?: Record<string, any>; required?: string[] }
|
||||
@@ -470,18 +470,10 @@
|
||||
)
|
||||
</script>
|
||||
|
||||
{#if arg != undefined && !hidden}
|
||||
<div
|
||||
class={twMerge(
|
||||
'pl-2 pt-2 pb-2 ml-2 relative hover:bg-surface hover:shadow-md transition-all duration-200',
|
||||
$propPickerConfig?.propName == argName
|
||||
? 'bg-surface border-l-4 border-blue-500 shadow-md rounded-l-md z-50 '
|
||||
: 'hover:rounded-md',
|
||||
className
|
||||
)}
|
||||
>
|
||||
<div class="flex flex-row justify-between gap-1 pb-1 px-2">
|
||||
<div class="flex flex-wrap grow">
|
||||
{#if arg != undefined}
|
||||
<div class={twMerge('pt-2 pb-2 relative group', className)}>
|
||||
<div class="flex flex-row justify-between gap-1 pb-1">
|
||||
<div class="flex flex-wrap grow min-h-7 items-end">
|
||||
<FieldHeader
|
||||
label={argName}
|
||||
simpleTooltip={headerTooltip}
|
||||
@@ -496,7 +488,7 @@
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<div>
|
||||
<span
|
||||
class="bg-blue-100 text-blue-800 text-sm font-medium mr-2 px-2.5 !py-0.5 rounded ml-2 {propertyType ==
|
||||
class="border text-gray-400 dark:text-gray-500 text-2xs font-medium mr-2 px-1 !py-[1px] rounded ml-2.5 {propertyType ==
|
||||
'static' && arg.type === 'javascript'
|
||||
? 'visible'
|
||||
: 'invisible'}"
|
||||
@@ -507,7 +499,9 @@
|
||||
{/if}
|
||||
</div>
|
||||
{#if !noDynamicToggle}
|
||||
<div class="flex flex-row gap-x-2 gap-y-1 flex-wrap z-10 items-center">
|
||||
<div
|
||||
class="flex flex-row gap-x-2 z-10 absolute right-0 group-hover:bg-surface transition-colors"
|
||||
>
|
||||
{#if enableAi}
|
||||
<StepInputGen
|
||||
bind:this={stepInputGen}
|
||||
@@ -536,8 +530,40 @@
|
||||
}}
|
||||
{pickableProperties}
|
||||
{argName}
|
||||
btnClass={twMerge(
|
||||
'h-6 min-w-8 px-2',
|
||||
'group-hover:opacity-100 transition-opacity',
|
||||
!connecting ? 'opacity-0' : ''
|
||||
)}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if propPickerWrapperContext}
|
||||
<FlowPlugConnect
|
||||
wrapperClasses={twMerge(
|
||||
connecting ? 'h-5 w-7' : 'h-6 w-8',
|
||||
'group-hover:opacity-100 transition-opacity p-0',
|
||||
!connecting ? 'opacity-0' : ''
|
||||
)}
|
||||
id="flow-editor-plug"
|
||||
{connecting}
|
||||
on:click={() => {
|
||||
if (
|
||||
$propPickerConfig?.propName == argName &&
|
||||
$propPickerConfig?.insertionMode == 'connect'
|
||||
) {
|
||||
clearFocus()
|
||||
} else {
|
||||
focusProp?.(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
dispatch('change', { argName })
|
||||
return true
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div>
|
||||
<ToggleButtonGroup
|
||||
selected={propertyType}
|
||||
@@ -593,19 +619,18 @@
|
||||
propertyType = 'static'
|
||||
}
|
||||
}}
|
||||
class="h-6"
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
{#if isStaticTemplate(inputCat)}
|
||||
<ToggleButton
|
||||
tooltip={`Write text or surround javascript with \`\$\{\` and \`\}\`. Use \`results\` to connect to another node\'s output.`}
|
||||
light
|
||||
value="static"
|
||||
size="xs2"
|
||||
label={'${}'}
|
||||
{item}
|
||||
/>
|
||||
{:else}
|
||||
<ToggleButton small label="Static" value="static" {item} />
|
||||
<ToggleButton small label="static" value="static" {item} />
|
||||
{/if}
|
||||
|
||||
{#if codeInjectionDetected && propertyType == 'static'}
|
||||
@@ -634,27 +659,6 @@
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
{#if propPickerWrapperContext && inputCat !== 'dynamic'}
|
||||
<FlowPlugConnect
|
||||
id="flow-editor-plug"
|
||||
{connecting}
|
||||
on:click={() => {
|
||||
if (
|
||||
$propPickerConfig?.propName == argName &&
|
||||
$propPickerConfig?.insertionMode == 'connect'
|
||||
) {
|
||||
clearFocus()
|
||||
} else {
|
||||
focusProp?.(argName, 'connect', (path) => {
|
||||
connectProperty(path)
|
||||
dispatch('change', { argName })
|
||||
return true
|
||||
})
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -674,12 +678,7 @@
|
||||
<div class="relative flex flex-row items-top gap-2 justify-between">
|
||||
<div class="min-w-0 grow">
|
||||
{#if isStaticTemplate(inputCat) && propertyType == 'static' && !noDynamicToggle}
|
||||
{#if argName && schema?.properties?.[argName]?.description}
|
||||
<div class="text-xs italic pb-1 text-secondary">
|
||||
<pre class="font-main">{schema.properties[argName].description}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
<div class="mt-2 min-h-[28px]">
|
||||
<div>
|
||||
{#if arg}
|
||||
<TemplateEditor
|
||||
bind:this={monacoTemplate}
|
||||
@@ -694,8 +693,14 @@
|
||||
dispatch('change', { argName, arg })
|
||||
}}
|
||||
loadAsync
|
||||
class="bg-surface-secondary"
|
||||
/>
|
||||
{/if}
|
||||
{#if argName && schema?.properties?.[argName]?.description}
|
||||
<div class="text-xs italic py-1 text-hint">
|
||||
<pre class="font-main">{schema.properties[argName].description}</pre>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if (propertyType === undefined || propertyType == 'static') && schema?.properties?.[argName]}
|
||||
<ArgInput
|
||||
@@ -743,23 +748,31 @@
|
||||
transform?.type === 'static' ? transform.value : transform?.expr
|
||||
])
|
||||
)}
|
||||
/>
|
||||
|
||||
{#if shouldShowS3ArrayHelper}
|
||||
<S3ArrayHelperButton
|
||||
{connecting}
|
||||
onClick={() =>
|
||||
switchToJsAndConnect((path) => appendPathToArrayExpr(arg.expr, path))}
|
||||
/>
|
||||
{/if}
|
||||
>
|
||||
{#snippet innerBottomSnippet()}
|
||||
{#if shouldShowS3ArrayHelper}
|
||||
<S3ArrayHelperButton
|
||||
{connecting}
|
||||
onClick={() =>
|
||||
switchToJsAndConnect((path) => appendPathToArrayExpr(arg.expr, path))}
|
||||
/>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ArgInput>
|
||||
{:else if arg.expr != undefined}
|
||||
<div class="border mt-2">
|
||||
<div
|
||||
class={`bg-surface-secondary rounded-md flex flex-col pl-4 ${inputBorderClass({ forceFocus: focused })}`}
|
||||
>
|
||||
<SimpleEditor
|
||||
bind:this={monaco}
|
||||
bind:code={arg.expr}
|
||||
yPadding={8}
|
||||
{extraLib}
|
||||
lang="javascript"
|
||||
shouldBindKey={false}
|
||||
renderLineHighlight="none"
|
||||
hideLineNumbers
|
||||
fakeMonacoPlaceholderClass="mt-2"
|
||||
on:focus={() => {
|
||||
focused = true
|
||||
focusProp?.(argName, 'insert', (path) => {
|
||||
@@ -778,12 +791,10 @@
|
||||
/>
|
||||
<!-- <input type="text" bind:value={arg.expr} /> -->
|
||||
</div>
|
||||
{#if !hideHelpButton}
|
||||
<DynamicInputHelpBox />
|
||||
{/if}
|
||||
|
||||
{#if shouldShowS3ArrayHelper}
|
||||
<S3ArrayHelperButton
|
||||
class="mt-2"
|
||||
{connecting}
|
||||
onClick={() =>
|
||||
focusProp?.(argName, 'connect', (path) => {
|
||||
@@ -793,9 +804,15 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if !hideHelpButton}
|
||||
<DynamicInputHelpBox />
|
||||
{/if}
|
||||
|
||||
<div class="mb-2"></div>
|
||||
{:else}
|
||||
Not recognized input type {argName} ({arg.expr}, {propertyType})
|
||||
<span class="text-xs text-red-500">
|
||||
Not recognized input type {argName} ({arg.expr}, {propertyType})
|
||||
</span>
|
||||
<div class="flex mt-2">
|
||||
<Button
|
||||
variant="border"
|
||||
@@ -807,29 +824,6 @@
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if $propPickerConfig?.propName == argName}
|
||||
<div
|
||||
class="text-blue-500 absolute top-2 lg:-right-2.5 -right-1"
|
||||
in:fade={{ duration: 200 }}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<polyline points="24 24 12 12 24 0" />
|
||||
</svg>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="w-0"></div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import ItemPicker from './ItemPicker.svelte'
|
||||
import VariableEditor from './VariableEditor.svelte'
|
||||
import { Plus } from 'lucide-svelte'
|
||||
import ResizeTransitionWrapper from './common/ResizeTransitionWrapper.svelte'
|
||||
|
||||
interface Props {
|
||||
schema: Schema | { properties?: Record<string, any> }
|
||||
@@ -80,9 +81,9 @@
|
||||
})
|
||||
</script>
|
||||
|
||||
<div class="w-full {clazz}">
|
||||
<div class="w-full mb-6 {clazz}">
|
||||
{#if enableAi}
|
||||
<div class="px-0.5 pt-0.5">
|
||||
<div class="mt-2">
|
||||
<StepInputsGen
|
||||
{pickableProperties}
|
||||
argNames={keys
|
||||
@@ -101,7 +102,7 @@
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName, index (argName)}
|
||||
{#if (!filter || filter.includes(argName)) && Object.keys(schema.properties ?? {}).includes(argName)}
|
||||
<div class="pt-2 relative">
|
||||
<ResizeTransitionWrapper class="mt-2 relative" innerClass="w-full" vertical>
|
||||
<InputTransformForm
|
||||
{previousModuleId}
|
||||
bind:arg={args[argName]}
|
||||
@@ -122,7 +123,7 @@
|
||||
Object.entries(args ?? {}).filter(([key]) => key !== argName)
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</ResizeTransitionWrapper>
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import { createEventDispatcher } from 'svelte'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
import Button from './common/button/Button.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { inputBorderClass } from './text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
code: string | undefined
|
||||
@@ -34,6 +36,7 @@
|
||||
|
||||
let tooBig = $derived(code && code?.length > 1000000)
|
||||
let loadTooBigAnyway = $state(false)
|
||||
let focused = $state(false)
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const dispatchIfMounted = createDispatcherIfMounted(dispatch)
|
||||
@@ -66,12 +69,17 @@
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="border w-full">
|
||||
<div
|
||||
class={twMerge(
|
||||
'w-full rounded-md bg-surface-secondary',
|
||||
inputBorderClass({ error: !!error, forceFocus: focused })
|
||||
)}
|
||||
>
|
||||
<SimpleEditor
|
||||
{loadAsync}
|
||||
{small}
|
||||
on:focus
|
||||
on:blur
|
||||
on:focus={() => (dispatch('focus'), (focused = true))}
|
||||
on:blur={() => (dispatch('blur'), (focused = false))}
|
||||
bind:this={editor}
|
||||
on:change
|
||||
autoHeight
|
||||
@@ -80,10 +88,12 @@
|
||||
class={clazz}
|
||||
{disabled}
|
||||
{fixedOverflowWidgets}
|
||||
renderLineHighlight="none"
|
||||
yPadding={8}
|
||||
/>
|
||||
</div>
|
||||
{#if error != ''}
|
||||
<span class="text-red-600 text-xs">{error}</span>
|
||||
<span class="text-red-600 text-xs mt-1">{error}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
<!-- Add a hidden button that can receive focus -->
|
||||
<button bind:this={focusTrap} class="sr-only" tabindex="-1" aria-hidden="true">Focus trap</button>
|
||||
|
||||
<div class="h-full">
|
||||
<div class="h-full py-3 rounded-md border">
|
||||
<SimpleEditor
|
||||
bind:this={simpleEditor}
|
||||
on:focus={() => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { Schema } from '$lib/common'
|
||||
|
||||
import ResizeTransitionWrapper from './common/ResizeTransitionWrapper.svelte'
|
||||
import { allTrue } from '$lib/utils'
|
||||
import { RefreshCw } from 'lucide-svelte'
|
||||
import ArgInput from './ArgInput.svelte'
|
||||
@@ -115,12 +115,14 @@
|
||||
{#if keys.length > 0}
|
||||
{#each keys as argName, i (argName)}
|
||||
{#if Object.keys(schema.properties ?? {}).includes(argName)}
|
||||
<div
|
||||
<ResizeTransitionWrapper
|
||||
vertical
|
||||
class={twMerge(
|
||||
'flex gap-2',
|
||||
'flex gap-2 relative',
|
||||
animateArg === argName && 'animate-pulse ring-2 ring-offset-2 ring-blue-500 rounded'
|
||||
)}
|
||||
data-arg={argName}
|
||||
innerClass="w-full"
|
||||
outerDivProps={{ 'data-arg': argName }}
|
||||
>
|
||||
{#if schema?.properties?.[argName]}
|
||||
<ArgInput
|
||||
@@ -150,22 +152,23 @@
|
||||
nullable={schema.properties[argName].nullable}
|
||||
title={schema.properties[argName].title}
|
||||
placeholder={schema.properties[argName].placeholder}
|
||||
/>
|
||||
>
|
||||
{#snippet fieldHeaderActions()}
|
||||
{#if stepsInputArgs?.isArgManuallySet(mod.id, argName)}
|
||||
<Button
|
||||
on:click={() => {
|
||||
plugIt(argName)
|
||||
}}
|
||||
size="xs2"
|
||||
variant="contained"
|
||||
color="light"
|
||||
title="Re-evaluate input step"><RefreshCw size={12} /></Button
|
||||
>
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ArgInput>
|
||||
{/if}
|
||||
{#if stepsInputArgs?.isArgManuallySet(mod.id, argName)}
|
||||
<div class="pt-6 mt-0.5">
|
||||
<Button
|
||||
on:click={() => {
|
||||
plugIt(argName)
|
||||
}}
|
||||
size="sm"
|
||||
variant="border"
|
||||
color="light"
|
||||
title="Re-evaluate input step"><RefreshCw size={14} /></Button
|
||||
>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</ResizeTransitionWrapper>
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
import Toggle from './Toggle.svelte'
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { selectOptions } from './apps/editor/component'
|
||||
import Select from './select/Select.svelte'
|
||||
import TextInput from './text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
min: number | undefined
|
||||
@@ -44,7 +46,11 @@
|
||||
options={{ right: 'Enabled' }}
|
||||
size="xs"
|
||||
/>
|
||||
{/snippet} <input type="number" bind:value={min} disabled={!minChecked} />
|
||||
{/snippet}
|
||||
<TextInput
|
||||
inputProps={{ type: 'number', disabled: !minChecked }}
|
||||
bind:value={() => min?.toString(), (v) => (min = v ? parseInt(v) : undefined)}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Max" class="w-full col-span-1 ">
|
||||
@@ -68,31 +74,41 @@
|
||||
size="xs"
|
||||
/>
|
||||
{/snippet}
|
||||
<input type="number" bind:value={max} disabled={!maxChecked} />
|
||||
<TextInput
|
||||
inputProps={{ type: 'number', disabled: !maxChecked }}
|
||||
bind:value={() => max?.toString(), (v) => (max = v ? parseInt(v) : undefined)}
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<Label label="Currency" class="w-full col-span-2">
|
||||
<div class="flex gap-2">
|
||||
<Label label="Currency" class=" col-span-2">
|
||||
{#snippet header()}
|
||||
<Tooltip light small>
|
||||
Select a currency to display the number in. If a currency is selected, you can also select
|
||||
a locale to format the number according to that locale.
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
<select bind:value={currency}>
|
||||
<option value={undefined}> No currency </option>
|
||||
{#each selectOptions.currencyOptions as c}
|
||||
<option value={c}>{c}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Select
|
||||
bind:value={
|
||||
() => currency,
|
||||
(v) => {
|
||||
currency = v
|
||||
if (!v) currencyLocale = undefined
|
||||
}
|
||||
}
|
||||
items={selectOptions.currencyOptions.map((c) => ({ label: c, value: c }))}
|
||||
placeholder="No currency"
|
||||
clearable
|
||||
/>
|
||||
</Label>
|
||||
<Label label="Currency locale" class="w-full">
|
||||
<select bind:value={currencyLocale} disabled={!currency}>
|
||||
<option value={undefined}> No locale </option>
|
||||
{#each selectOptions.localeOptions as c}
|
||||
<option value={c}>{c}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<Select
|
||||
bind:value={currencyLocale}
|
||||
items={selectOptions.localeOptions.map((c) => ({ label: c, value: c }))}
|
||||
placeholder="No locale"
|
||||
disabled={!currency}
|
||||
clearable
|
||||
/>
|
||||
</Label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -420,10 +420,7 @@
|
||||
<ToggleButton
|
||||
icon={User}
|
||||
disabled={disabled || disableEditing}
|
||||
light
|
||||
size="xs"
|
||||
value="user"
|
||||
position="left"
|
||||
label="User"
|
||||
{item}
|
||||
/>
|
||||
@@ -431,10 +428,7 @@
|
||||
<ToggleButton
|
||||
icon={Folder}
|
||||
disabled={disabled || disableEditing}
|
||||
light
|
||||
size="xs"
|
||||
value="folder"
|
||||
position="right"
|
||||
label="Folder"
|
||||
{item}
|
||||
/>
|
||||
@@ -443,7 +437,7 @@
|
||||
</div>
|
||||
{/if}
|
||||
{#if !hideUser}
|
||||
<div class="text-xl">/</div>
|
||||
<div class="text-xl text-tertiary">/</div>
|
||||
{/if}
|
||||
<div>
|
||||
{#if meta.ownerKind === 'user'}
|
||||
@@ -465,7 +459,7 @@
|
||||
</label>
|
||||
{/if}
|
||||
</div>
|
||||
<span class="text-xl">/</span>
|
||||
<span class="text-xl text-tertiary">/</span>
|
||||
<label class="block grow w-full max-w-md">
|
||||
<!-- svelte-ignore a11y_autofocus -->
|
||||
<input
|
||||
|
||||
@@ -6,11 +6,13 @@
|
||||
import ResourceEditorDrawer from './ResourceEditorDrawer.svelte'
|
||||
|
||||
import { Button } from './common'
|
||||
import { Pen, Plus, RotateCw } from 'lucide-svelte'
|
||||
import { Loader2, Pen, Plus, RotateCw } from 'lucide-svelte'
|
||||
import { sendUserToast } from '$lib/toast'
|
||||
import Select from './select/Select.svelte'
|
||||
import DbManagerDrawer from './DBManagerDrawer.svelte'
|
||||
import ExploreAssetButton, { assetCanBeExplored } from './ExploreAssetButton.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import DropdownV2 from './DropdownV2.svelte'
|
||||
|
||||
interface Props {
|
||||
initialValue?: string | undefined
|
||||
@@ -24,6 +26,7 @@
|
||||
expressOAuthSetup?: boolean
|
||||
defaultValues?: Record<string, any> | undefined
|
||||
placeholder?: string | undefined
|
||||
selectInputClass?: string
|
||||
onClear?: () => void
|
||||
excludedValues?: string[]
|
||||
}
|
||||
@@ -40,6 +43,7 @@
|
||||
expressOAuthSetup = false,
|
||||
defaultValues = undefined,
|
||||
placeholder = undefined,
|
||||
selectInputClass = '',
|
||||
onClear = undefined,
|
||||
excludedValues = undefined
|
||||
}: Props = $props()
|
||||
@@ -176,91 +180,102 @@
|
||||
}}
|
||||
/>
|
||||
<!-- {JSON.stringify({ value, collection })} -->
|
||||
<div class="flex flex-col w-full items-start min-h-9">
|
||||
<div class="flex flex-col w-full items-start min-h-10">
|
||||
<div class="flex flex-row w-full items-center">
|
||||
{#if collection?.length > 0}
|
||||
<Select
|
||||
{disabled}
|
||||
{disablePortal}
|
||||
bind:value={
|
||||
() => value,
|
||||
(v) => {
|
||||
value = v
|
||||
valueType = collection.find((x) => x?.value == v)?.type
|
||||
}
|
||||
<Select
|
||||
{disabled}
|
||||
{disablePortal}
|
||||
bind:value={
|
||||
() => value,
|
||||
(v) => {
|
||||
value = v
|
||||
valueType = collection.find((x) => x?.value == v)?.type
|
||||
}
|
||||
onClear={() => {
|
||||
initialValue = undefined
|
||||
value = undefined
|
||||
valueType = undefined
|
||||
onClear?.()
|
||||
}}
|
||||
items={collection}
|
||||
clearable
|
||||
class="text-clip grow min-w-0"
|
||||
placeholder={placeholder ?? `${resourceType ?? 'any'} resource`}
|
||||
/>
|
||||
{:else if !loading}
|
||||
<div class="text-2xs text-tertiary mr-2">0 found</div>
|
||||
{/if}
|
||||
{#if !loading}
|
||||
<div class="mx-0.5"></div>
|
||||
{#if value && value != ''}
|
||||
}
|
||||
onClear={() => {
|
||||
initialValue = undefined
|
||||
value = undefined
|
||||
valueType = undefined
|
||||
onClear?.()
|
||||
}}
|
||||
items={collection}
|
||||
clearable
|
||||
class="text-clip grow min-w-0"
|
||||
inputClass={twMerge('min-h-10', selectInputClass)}
|
||||
placeholder={placeholder ?? `${resourceType ?? 'any'} resource`}
|
||||
itemLabelWrapperClasses="flex-1"
|
||||
itemButtonWrapperClasses="flex"
|
||||
>
|
||||
{#snippet endSnippet({ item, close })}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="contained"
|
||||
size="sm"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
on:click={() => resourceEditor?.initEdit?.(value ?? '')}
|
||||
size="xs3"
|
||||
btnClasses="w-8 px-0.5 py-1.5 bg-transparent hover:bg-surface-secondary"
|
||||
wrapperClasses="-mr-2 pl-1"
|
||||
on:click={() => (resourceEditor?.initEdit?.(item.value ?? ''), close())}
|
||||
startIcon={{ icon: Pen }}
|
||||
iconOnly
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if resourceType?.includes(',')}
|
||||
{#each resourceType.split(',') as rt}
|
||||
{/snippet}
|
||||
{#snippet bottomSnippet({ close })}
|
||||
<div class="flex bg-surface border-t divide-x">
|
||||
{#if resourceType?.includes(',')}
|
||||
<DropdownV2
|
||||
enableFlyTransition
|
||||
items={resourceType?.split(',').map((rt) => ({
|
||||
displayName: `${rt} resource`,
|
||||
icon: Plus,
|
||||
action: () => (appConnect?.open?.(rt), close())
|
||||
})) ?? []}
|
||||
>
|
||||
{#snippet buttonReplacement()}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="contained"
|
||||
wrapperClasses="flex-1"
|
||||
btnClasses="rounded-none mt-0.5"
|
||||
size="sm"
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add a resource
|
||||
</Button>
|
||||
{/snippet}
|
||||
</DropdownV2>
|
||||
{:else}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="contained"
|
||||
wrapperClasses="flex-1"
|
||||
btnClasses="rounded-none"
|
||||
size="sm"
|
||||
on:click={() => (appConnect?.open?.(resourceType), close())}
|
||||
startIcon={{ icon: Plus }}
|
||||
>
|
||||
Add a {resourceType} resource
|
||||
</Button>
|
||||
{/if}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="contained"
|
||||
color="light"
|
||||
btnClasses="rounded-none"
|
||||
size="sm"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
on:click={() => appConnect?.open?.(rt)}
|
||||
startIcon={{ icon: Plus }}>{rt}</Button
|
||||
>
|
||||
{/each}
|
||||
{:else}
|
||||
<Button
|
||||
{disabled}
|
||||
color="light"
|
||||
variant="border"
|
||||
size="sm"
|
||||
on:click={() => appConnect?.open?.(resourceType)}
|
||||
startIcon={{ icon: Plus }}
|
||||
iconOnly={collection?.length > 0}
|
||||
>{#if collection?.length == 0}
|
||||
Add a {resourceType} resource
|
||||
{/if}</Button
|
||||
>
|
||||
<div class="mx-0.5"></div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
color="light"
|
||||
btnClasses="w-8 px-0.5 py-1.5"
|
||||
size="sm"
|
||||
on:click={() => {
|
||||
loadResources(resourceType)
|
||||
}}
|
||||
startIcon={{ icon: RotateCw }}
|
||||
iconOnly
|
||||
/>
|
||||
on:click={() => {
|
||||
loadResources(resourceType)
|
||||
}}
|
||||
startIcon={loading ? { icon: Loader2, classes: 'animate-spin' } : { icon: RotateCw }}
|
||||
iconOnly
|
||||
/>
|
||||
</div>
|
||||
{/snippet}
|
||||
</Select>
|
||||
</div>
|
||||
{#if showSchemaExplorer && value && assetCanBeExplored({ kind: 'resource', path: value }, { resource_type: resourceType })}
|
||||
<ExploreAssetButton
|
||||
class="mt-1"
|
||||
_resourceMetadata={{ resource_type: resourceType }}
|
||||
asset={{ kind: 'resource', path: value }}
|
||||
{dbManagerDrawer}
|
||||
|
||||
@@ -6,15 +6,27 @@
|
||||
label?: string
|
||||
onClick: () => void
|
||||
connecting?: boolean
|
||||
class?: string
|
||||
}
|
||||
|
||||
let { label = 'Add object from an expression', onClick, connecting = false }: Props = $props()
|
||||
let {
|
||||
label = 'Add object from an expression',
|
||||
onClick,
|
||||
connecting = false,
|
||||
class: className = ''
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
{#if !connecting}
|
||||
<div class="mt-2 mb-2">
|
||||
<Button variant="border" color="light" size="xs" startIcon={{ icon: Plug }} onclick={onClick}>
|
||||
{label}
|
||||
</Button>
|
||||
</div>
|
||||
<Button
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
btnClasses="font-normal text-tertiary"
|
||||
startIcon={{ icon: Plug }}
|
||||
onclick={onClick}
|
||||
wrapperClasses={className}
|
||||
>
|
||||
{label}
|
||||
</Button>
|
||||
{/if}
|
||||
|
||||
@@ -819,7 +819,7 @@
|
||||
<div class="flex gap-1">
|
||||
{#if secondaryStorageNames.value?.length}
|
||||
<Select
|
||||
inputClass="h-10 min-w-44 !placeholder-primary"
|
||||
inputClass="h-10 min-w-44 !placeholder-secondary"
|
||||
items={[
|
||||
{ value: undefined, label: 'Default storage' },
|
||||
...secondaryStorageNames.value.map((value) => ({ value }))
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
import { dragHandleZone, type Options as DndOptions } from '@windmill-labs/svelte-dnd-action'
|
||||
import type { SchemaDiff } from '$lib/components/schema/schemaUtils.svelte'
|
||||
import type { ComponentCustomCSS } from './apps/types'
|
||||
import ResizeTransitionWrapper from './common/ResizeTransitionWrapper.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
interface Props {
|
||||
schema: Schema | any
|
||||
@@ -56,6 +58,7 @@
|
||||
displayType?: boolean
|
||||
appPath?: string | undefined
|
||||
className?: string
|
||||
lightHeaderFont?: boolean
|
||||
computeS3ForceViewerPolicies?:
|
||||
| (() =>
|
||||
| {
|
||||
@@ -106,6 +109,7 @@
|
||||
displayType = true,
|
||||
appPath = undefined,
|
||||
className = '',
|
||||
lightHeaderFont = false,
|
||||
computeS3ForceViewerPolicies = undefined,
|
||||
workspace = undefined,
|
||||
actions: actions_render = undefined
|
||||
@@ -269,10 +273,15 @@
|
||||
{#if keys.length > 0 && args}
|
||||
{#each fields as item, i (item.id)}
|
||||
{@const argName = item.value}
|
||||
<div
|
||||
class={typeof diff[argName] === 'object' && diff[argName].diff !== 'same'
|
||||
? 'bg-red-300 dark:bg-red-800 rounded-md'
|
||||
: ''}
|
||||
<ResizeTransitionWrapper
|
||||
vertical
|
||||
class={twMerge(
|
||||
typeof diff[argName] === 'object' &&
|
||||
diff[argName].diff !== 'same' &&
|
||||
'bg-red-300 dark:bg-red-800 rounded-md',
|
||||
'relative'
|
||||
)}
|
||||
innerClass="w-full"
|
||||
>
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||
{#if !hiddenArgs.includes(argName) && keys.includes(argName)}
|
||||
@@ -280,6 +289,7 @@
|
||||
{@const formerProperty = diff[argName].oldSchema}
|
||||
<div class="px-2">
|
||||
<ArgInput
|
||||
{lightHeaderFont}
|
||||
{disablePortal}
|
||||
{resourceTypes}
|
||||
{prettifyHeader}
|
||||
@@ -342,6 +352,7 @@
|
||||
{JSON.stringify(args?.[argName])} -->
|
||||
{#if !hidden[argName]}
|
||||
<ArgInput
|
||||
{lightHeaderFont}
|
||||
on:change={() => {
|
||||
dispatch('change')
|
||||
}}
|
||||
@@ -419,15 +430,14 @@
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
value="inlined"
|
||||
size="sm"
|
||||
small
|
||||
label="Inlined"
|
||||
tooltip="The value is inlined in the resource and thus has no special treatment."
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
position="right"
|
||||
value="secret"
|
||||
size="sm"
|
||||
small
|
||||
label="Secret"
|
||||
tooltip="The value will be stored in a newly created linked secret variable at the same path. That variable can be permissioned differently, will be treated as a secret the UI, operators will not be able to load it and every access will generate a corresponding audit log."
|
||||
{item}
|
||||
@@ -443,7 +453,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</ResizeTransitionWrapper>
|
||||
{/each}
|
||||
{:else if !shouldHideNoInputs}
|
||||
<div class="text-secondary text-sm">No inputs</div>
|
||||
|
||||
@@ -537,7 +537,7 @@
|
||||
<SplitPanesWrapper>
|
||||
<Splitpanes class="!overflow-visible">
|
||||
<Pane bind:size={codePanelSize} minSize={10} class="!overflow-visible">
|
||||
<div class="h-full !overflow-visible bg-gray-50 dark:bg-[#272D38] relative">
|
||||
<div class="h-full !overflow-visible bg-surface dark:bg-[#272D38] relative">
|
||||
<div class="absolute top-2 right-4 z-10 flex flex-row gap-2">
|
||||
{#if assets?.length}
|
||||
<AssetsDropdownButton {assets} />
|
||||
|
||||
@@ -129,8 +129,8 @@
|
||||
<div>
|
||||
<ToggleButtonGroup bind:selected={ownerKind} on:selected={() => (owner = '')}>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="user" size="xs" label="User" {item} />
|
||||
<ToggleButton value="group" size="xs" label="Group" {item} />
|
||||
<ToggleButton value="user" small label="User" {item} />
|
||||
<ToggleButton value="group" small label="Group" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
@@ -175,8 +175,8 @@
|
||||
}}
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="viewer" size="xs" label="Viewer" {item} />
|
||||
<ToggleButton value="writer" size="xs" label="Writer" {item} />
|
||||
<ToggleButton value="viewer" small label="Viewer" {item} />
|
||||
<ToggleButton value="writer" small label="Writer" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -85,10 +85,13 @@
|
||||
allowVim = false,
|
||||
tailwindClasses = [],
|
||||
class: className = '',
|
||||
fakeMonacoPlaceholderClass = '',
|
||||
loadAsync = false,
|
||||
key,
|
||||
disabled = false,
|
||||
minHeight = 1000
|
||||
minHeight = 1000,
|
||||
renderLineHighlight = 'none',
|
||||
yPadding
|
||||
}: {
|
||||
lang: string
|
||||
code?: string
|
||||
@@ -110,11 +113,14 @@
|
||||
allowVim?: boolean
|
||||
tailwindClasses?: string[]
|
||||
class?: string
|
||||
fakeMonacoPlaceholderClass?: string
|
||||
loadAsync?: boolean
|
||||
initialCursorPos?: IPosition
|
||||
key?: string
|
||||
disabled?: boolean
|
||||
minHeight?: number
|
||||
renderLineHighlight?: 'all' | 'line' | 'gutter' | 'none'
|
||||
yPadding?: number
|
||||
} = $props()
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
@@ -332,6 +338,8 @@
|
||||
$relativeLineNumbers
|
||||
),
|
||||
model,
|
||||
...(yPadding !== undefined ? { padding: { bottom: yPadding, top: yPadding } } : {}),
|
||||
renderLineHighlight,
|
||||
lineDecorationsWidth: 6,
|
||||
lineNumbersMinChars: 2,
|
||||
fontSize: fontSize,
|
||||
@@ -602,9 +610,11 @@
|
||||
<FakeMonacoPlaceHolder
|
||||
{code}
|
||||
autoheight
|
||||
lineNumbersWidth={(23 * fontSize) / 14}
|
||||
lineNumbersWidth={hideLineNumbers ? 0 : (23 * fontSize) / 14}
|
||||
lineNumbersOffset={fontSize == 14 ? -8 : -11}
|
||||
{fontSize}
|
||||
showNumbers={!hideLineNumbers}
|
||||
class={fakeMonacoPlaceholderClass}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
import ToggleButton from './common/toggleButton-v2/ToggleButton.svelte'
|
||||
import ToggleButtonGroup from './common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import RegexGen from './copilot/RegexGen.svelte'
|
||||
import TextInput from './text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
pattern: string | undefined
|
||||
@@ -341,7 +342,10 @@
|
||||
{:else if kind == 'none'}
|
||||
{#if !noExtra}
|
||||
<Label label="Min textarea rows">
|
||||
<input type="number" bind:value={minRows} />
|
||||
<TextInput
|
||||
inputProps={{ type: 'number' }}
|
||||
bind:value={() => minRows?.toString(), (v) => (minRows = v ? parseInt(v) : undefined)}
|
||||
/>
|
||||
</Label>
|
||||
{/if}
|
||||
{:else if kind === 'base64'}
|
||||
|
||||
@@ -300,17 +300,17 @@
|
||||
}}
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value={'user'} size="xs" label="User" {item} />
|
||||
<ToggleButton value={'user'} small label="User" {item} />
|
||||
<ToggleButton
|
||||
value={'devops'}
|
||||
size="xs"
|
||||
small
|
||||
label="Devops"
|
||||
tooltip="Devops is a role that grants visibilty similar to that of a super admin, but without giving all rights. For example devops users can see service logs and crtical alerts. You can think of it as a 'readonly' super admin"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
value={'super_admin'}
|
||||
size="xs"
|
||||
small
|
||||
label="Superadmin"
|
||||
{item}
|
||||
/>
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
import EditorTheme from './EditorTheme.svelte'
|
||||
import FakeMonacoPlaceHolder from './FakeMonacoPlaceHolder.svelte'
|
||||
import { setMonacoJsonOptions } from './monacoLanguagesOptions'
|
||||
import { inputBorderClass } from './text_input/TextInput.svelte'
|
||||
|
||||
export const conf = {
|
||||
wordPattern:
|
||||
@@ -449,7 +450,14 @@
|
||||
lineDecorationsWidth: 6,
|
||||
lineNumbersMinChars: 2,
|
||||
fontSize,
|
||||
suggestOnTriggerCharacters: true
|
||||
suggestOnTriggerCharacters: true,
|
||||
renderLineHighlight: 'none',
|
||||
lineNumbers: 'off',
|
||||
|
||||
padding: {
|
||||
bottom: 8,
|
||||
top: 8
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
console.error('Error loading monaco:', e)
|
||||
@@ -498,10 +506,12 @@
|
||||
|
||||
editor.onDidFocusEditorText(() => {
|
||||
dispatch('focus')
|
||||
isFocus = true
|
||||
})
|
||||
|
||||
editor.onDidBlurEditorText(() => {
|
||||
dispatch('blur')
|
||||
isFocus = false
|
||||
updateCode()
|
||||
})
|
||||
|
||||
@@ -597,6 +607,7 @@
|
||||
editor?.focus()
|
||||
}
|
||||
|
||||
let isFocus = false
|
||||
let mounted = false
|
||||
let loadTimeout: number | undefined = undefined
|
||||
onMount(async () => {
|
||||
@@ -650,19 +661,18 @@
|
||||
{#if !editor}
|
||||
<FakeMonacoPlaceHolder
|
||||
autoheight
|
||||
showNumbers={false}
|
||||
{code}
|
||||
lineNumbersWidth={23}
|
||||
lineNumbersOffset={-8}
|
||||
class="border template nonmain-editor rounded min-h-4 mx-0.5 overflow-clip"
|
||||
lineNumbersWidth={14}
|
||||
lineNumbersOffset={-20}
|
||||
class="template nonmain-editor rounded-md min-h-4 bg-surface-secondary !py-[9px] overflow-clip"
|
||||
/>
|
||||
{/if}
|
||||
<div
|
||||
bind:this={divEl}
|
||||
style="height: 18px;"
|
||||
class="{$$props.class ??
|
||||
''} border template nonmain-editor rounded min-h-4 mx-0.5 overflow-clip {!editor
|
||||
? 'hidden'
|
||||
: ''}"
|
||||
style="height: 18px; padding-left: 6px;"
|
||||
class="{inputBorderClass({ forceFocus: isFocus })} {$$props.class ??
|
||||
''} template nonmain-editor rounded-md min-h-4 overflow-clip {!editor ? 'hidden' : ''}"
|
||||
bind:clientWidth={width}
|
||||
></div>
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Tooltip from './Tooltip.svelte'
|
||||
import { AlertTriangle } from 'lucide-svelte'
|
||||
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
|
||||
import { inputBorderClass } from './text_input/TextInput.svelte'
|
||||
|
||||
export let options: {
|
||||
left?: string
|
||||
@@ -18,7 +19,7 @@
|
||||
export let disabled = false
|
||||
export let textClass = ''
|
||||
export let textStyle = ''
|
||||
export let color: 'blue' | 'red' | 'nord' = 'blue'
|
||||
export let color: 'blue' | 'red' | 'nord' = 'nord'
|
||||
export let id = (Math.random() + 1).toString(36).substring(10)
|
||||
export let lightMode: boolean = false
|
||||
export let eeOnly: boolean = false
|
||||
@@ -43,8 +44,12 @@
|
||||
{#if Boolean(options?.left)}
|
||||
<span
|
||||
class={twMerge(
|
||||
'mr-2 font-medium duration-50 select-none',
|
||||
bothOptions || textDisabled ? (checked ? 'text-disabled' : 'text-primary') : 'text-primary',
|
||||
'mr-2 font-normal duration-50 select-none',
|
||||
bothOptions || textDisabled
|
||||
? checked
|
||||
? 'text-disabled'
|
||||
: 'text-tertiary'
|
||||
: 'text-tertiary',
|
||||
size === 'xs' || size === '2sm' ? 'text-xs' : size === '2xs' ? 'text-[0.5rem]' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
@@ -89,22 +94,27 @@
|
||||
? 'peer-checked:bg-red-600'
|
||||
: color == 'blue'
|
||||
? 'peer-checked:bg-blue-600 dark:peer-checked:bg-blue-500'
|
||||
: 'peer-checked:bg-nord-950 dark:peer-checked:bg-nord-400',
|
||||
: 'peer-checked:bg-nord-950 dark:peer-checked:bg-nord-900',
|
||||
size === 'sm'
|
||||
? 'w-11 h-6 after:top-0.5 after:left-[2px] after:h-5 after:w-5'
|
||||
: size === '2sm'
|
||||
? 'w-9 h-5 after:top-0.5 after:left-[2px] after:h-4 after:w-4'
|
||||
: size === '2xs'
|
||||
? 'w-5 h-3 after:top-0.5 after:left-[2px] after:h-2 after:w-2'
|
||||
: 'w-7 h-4 after:top-0.5 after:left-[2px] after:h-3 after:w-3'
|
||||
: 'w-7 h-4 after:top-0.5 after:left-[2px] after:h-3 after:w-3',
|
||||
inputBorderClass()
|
||||
)}
|
||||
></div>
|
||||
</div>
|
||||
{#if Boolean(options?.right)}
|
||||
<span
|
||||
class={twMerge(
|
||||
'ml-2 font-medium duration-50 select-none',
|
||||
bothOptions || textDisabled ? (checked ? 'text-primary' : 'text-disabled') : 'text-primary',
|
||||
'ml-2 font-normal duration-50 select-none',
|
||||
bothOptions || textDisabled
|
||||
? checked
|
||||
? 'text-tertiary'
|
||||
: 'text-disabled'
|
||||
: 'text-tertiary',
|
||||
size === 'xs' || size === '2sm' ? 'text-xs' : size === '2xs' ? 'text-xs' : 'text-sm',
|
||||
textClass
|
||||
)}
|
||||
|
||||
@@ -244,20 +244,8 @@
|
||||
class="mb-4"
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton
|
||||
position="left"
|
||||
value="normal"
|
||||
size="sm"
|
||||
label="Any jobs within worker tags"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton
|
||||
position="dedicated"
|
||||
value="dedicated"
|
||||
size="sm"
|
||||
label="Dedicated to a script/flow"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton value="normal" small label="Any jobs within worker tags" {item} />
|
||||
<ToggleButton value="dedicated" small label="Dedicated to a script/flow" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{#if selected == 'normal'}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import DarkModeObserver from '$lib/components/DarkModeObserver.svelte'
|
||||
import { inputBaseClass, inputBorderClass } from '$lib/components/text_input/TextInput.svelte'
|
||||
import { onMount, untrack } from 'svelte'
|
||||
|
||||
/* Forked from MIT LICENSE
|
||||
@@ -208,7 +209,7 @@
|
||||
bind:value
|
||||
/>
|
||||
<input
|
||||
class="
|
||||
class="{inputBaseClass} {inputBorderClass()}
|
||||
{inputClasses?.formatted ?? DEFAULT_CLASS_FORMATTED}
|
||||
{isNegativeAllowed && !isZero && !isNegative
|
||||
? (inputClasses?.formattedPositive ?? DEFAULT_CLASS_FORMATTED_POSITIVE)
|
||||
|
||||
@@ -128,7 +128,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<input />
|
||||
<div class="w-full z-[1000] overflow-visible h-full">
|
||||
<div class={$app.hideLegacyTopBar ? 'hidden' : ''}>
|
||||
<div
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
</script>
|
||||
|
||||
{#if !(resourceOnly && (fieldType !== 'object' || !format?.startsWith('resource-')))}
|
||||
<div class={classNames('flex gap-1', 'flex-col')}>
|
||||
<div class={classNames('flex gap-1 flex-col group')}>
|
||||
<div class="flex justify-between items-end">
|
||||
<div class="flex flex-row gap-4 items-center">
|
||||
<div class="flex items-center">
|
||||
@@ -188,8 +188,14 @@
|
||||
|
||||
<div class={classNames('flex gap-x-2 gap-y-1 justify-end items-center')}>
|
||||
{#if componentInput?.type && allowTypeChange !== false}
|
||||
<ConnectionButton
|
||||
{closeConnection}
|
||||
{openConnection}
|
||||
isOpen={!!$connectingInput.opened}
|
||||
btnWrapperClasses={'h-6 w-8 opacity-0 group-hover:opacity-100 transition-opacity'}
|
||||
/>
|
||||
<ToggleButtonGroup
|
||||
class="h-7"
|
||||
class="h-6"
|
||||
bind:selected={componentInput.type}
|
||||
on:selected={(e) => {
|
||||
if (
|
||||
@@ -242,7 +248,6 @@
|
||||
<ToggleButton value="evalv2" icon={FunctionSquare} iconOnly tooltip="Eval" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
<ConnectionButton {closeConnection} {openConnection} isOpen={!!$connectingInput.opened} />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<script lang="ts">
|
||||
import type { HTMLAttributes } from 'svelte/elements'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
type Props = {
|
||||
children: import('svelte').Snippet
|
||||
innerClass?: string
|
||||
class?: string
|
||||
horizontal?: boolean
|
||||
vertical?: boolean
|
||||
outerDivProps?: HTMLAttributes<HTMLDivElement>
|
||||
}
|
||||
let {
|
||||
children,
|
||||
innerClass,
|
||||
class: className = '',
|
||||
horizontal,
|
||||
vertical,
|
||||
outerDivProps
|
||||
}: Props = $props()
|
||||
|
||||
let innerContainer: HTMLElement | null = $state(null)
|
||||
let outerContainer: HTMLElement | null = $state(null)
|
||||
|
||||
let width: number | null = $state(null)
|
||||
let height: number | null = $state(null)
|
||||
|
||||
let style = $derived.by(() => {
|
||||
let s = 'transition: width 0.2s, height 0.2s; overflow: clip;'
|
||||
if (width !== null) s += `width: ${width}px;`
|
||||
if (height !== null) s += `height: ${height}px;`
|
||||
return s
|
||||
})
|
||||
|
||||
$effect(() => {
|
||||
if (!innerContainer || !outerContainer) return
|
||||
let observer = new ResizeObserver((entries) => {
|
||||
for (const entry of entries) {
|
||||
if (horizontal) width = entry.contentRect.width
|
||||
if (vertical) height = entry.contentRect.height
|
||||
}
|
||||
})
|
||||
observer.observe(innerContainer)
|
||||
return () => {
|
||||
if (innerContainer) observer.unobserve(innerContainer)
|
||||
observer.disconnect()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={outerContainer} {...outerDivProps} class={className} {style}>
|
||||
<div class={twMerge('absolute', innerClass)} bind:this={innerContainer}>
|
||||
{@render children()}
|
||||
</div>
|
||||
</div>
|
||||
@@ -154,7 +154,7 @@
|
||||
variant === 'border' ? 'border' : '',
|
||||
ButtonType.FontSizeClasses[size],
|
||||
ButtonType.SpacingClasses[spacingSize][variant],
|
||||
'focus-visible:ring-2 font-semibold',
|
||||
'focus-visible:ring-2 font-normal',
|
||||
dropdownItems && dropdownItems.length > 0 ? 'rounded-l-md h-full' : 'rounded-md',
|
||||
'justify-center items-center text-center whitespace-nowrap inline-flex gap-2',
|
||||
btnClasses,
|
||||
@@ -349,6 +349,7 @@
|
||||
on:open={() => dispatch('dropdownOpen', true)}
|
||||
on:close={() => dispatch('dropdownOpen', false)}
|
||||
bind:open={dropdownOpen}
|
||||
enableFlyTransition
|
||||
>
|
||||
{#snippet buttonReplacement()}
|
||||
<div
|
||||
|
||||
@@ -5,10 +5,12 @@
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { AppViewerContext } from '$lib/components/apps/types'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
export let isOpen = false
|
||||
export let openConnection: () => void
|
||||
export let closeConnection: () => void
|
||||
export let btnWrapperClasses = ''
|
||||
|
||||
let selected = false
|
||||
|
||||
@@ -82,6 +84,8 @@
|
||||
title="Connect"
|
||||
on:click={() => handleConnect(true)}
|
||||
id="schema-plug"
|
||||
wrapperClasses={twMerge(btnWrapperClasses, selected ? 'opacity-100' : '')}
|
||||
btnClasses="p-0"
|
||||
>
|
||||
<Plug size={14} />
|
||||
</Button>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<button
|
||||
title="Open calendar picker"
|
||||
class={twMerge(
|
||||
'absolute bottom-1 right-2 top-1 py-1 min-w-min !px-2 items-center text-primary bg-surface border rounded center-center hover:bg-surface-hover transition-all cursor-pointer',
|
||||
'absolute bottom-1 right-2 top-1 py-1 min-w-min !px-2.5 items-center text-primary bg-surface-secondary rounded center-center hover:bg-surface-hover transition-all cursor-pointer',
|
||||
$$props.class
|
||||
)}
|
||||
aria-label="Open calendar picker"
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
export let multiple = false
|
||||
export let convertTo: ReadFileAs | undefined = undefined
|
||||
export let hideIcon = false
|
||||
export let iconSize = 36
|
||||
export let iconSize = 24
|
||||
export let returnFileNames = false
|
||||
export let submittedText: string | undefined = undefined
|
||||
export let defaultFile: string | string[] | undefined = undefined
|
||||
@@ -187,10 +187,11 @@
|
||||
|
||||
<button
|
||||
class={twMerge(
|
||||
`relative center-center flex-col text-center font-medium text-tertiary
|
||||
border border-dashed border-gray-400 hover:border-blue-500
|
||||
`relative center-center flex flex-col gap-x-2 gap-y-1 justify-center items-center flex-wrap text-center font-normal text-hint text-xs rounded-md
|
||||
bg-surface-secondary
|
||||
border border-dashed border-nord-400 dark:border-nord-300 hover:border-nord-900 dark:hover:border-nord-900
|
||||
focus-within:border-blue-300 hover:bg-blue-50 dark:hover:bg-frost-900
|
||||
duration-200 rounded-component p-1`,
|
||||
duration-200 px-1 py-8`,
|
||||
c
|
||||
)}
|
||||
on:dragover={handleDragOver}
|
||||
@@ -209,7 +210,7 @@
|
||||
{disabled}
|
||||
>
|
||||
{#if !hideIcon && !files}
|
||||
<FileUp size={iconSize} class="mb-2" />
|
||||
<FileUp size={iconSize} />
|
||||
{/if}
|
||||
{#if files}
|
||||
<div class="w-full max-h-full overflow-auto px-6">
|
||||
@@ -258,7 +259,7 @@
|
||||
/>
|
||||
{#if defaultFile && (!Array.isArray(defaultFile) || defaultFile.length > 0)}
|
||||
<div class="w-full border-dashed border-t-2 text-2xs pt-1 text-tertiary mt-2">
|
||||
Default file: <span class="text-blue-500">{defaultFile}</span>
|
||||
Default file: <span class="text-nord-900">{defaultFile}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
computeS3ForceViewerPolicies,
|
||||
workspace,
|
||||
editor = $bindable(),
|
||||
appPath
|
||||
appPath,
|
||||
bottom
|
||||
}: {
|
||||
multiple: boolean
|
||||
value: any
|
||||
@@ -38,6 +39,7 @@
|
||||
workspace: string | undefined
|
||||
editor: SimpleEditor | undefined
|
||||
appPath: string | undefined
|
||||
bottom?: import('svelte').Snippet
|
||||
} = $props()
|
||||
|
||||
let s3FileUploadRawMode: boolean = $state(false)
|
||||
@@ -67,13 +69,7 @@
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="flex flex-col w-full gap-1">
|
||||
<Toggle
|
||||
class="flex justify-end"
|
||||
bind:checked={s3FileUploadRawMode}
|
||||
size="xs"
|
||||
options={{ left: `Raw S3 object${multiple ? 's' : ''} input` }}
|
||||
/>
|
||||
<div class="flex flex-col w-full gap-1 relative">
|
||||
{#if s3FileUploadRawMode}
|
||||
{#await import('$lib/components/JsonEditor.svelte')}
|
||||
<Loader2 class="animate-spin" />
|
||||
@@ -135,7 +131,7 @@
|
||||
variant="border"
|
||||
color="light"
|
||||
size="xs"
|
||||
btnClasses="mt-1"
|
||||
btnClasses="mt-1 font-normal text-tertiary"
|
||||
on:click={() => {
|
||||
s3FilePicker?.open?.(value)
|
||||
}}
|
||||
@@ -144,4 +140,12 @@
|
||||
{multiple ? 'Add' : 'Choose'} an object from the catalog
|
||||
</Button>
|
||||
{/if}
|
||||
{@render bottom?.()}
|
||||
<Toggle
|
||||
class="mt-1"
|
||||
textClass="font-normal text-hint"
|
||||
bind:checked={s3FileUploadRawMode}
|
||||
size="xs"
|
||||
options={{ right: `Raw S3 object${multiple ? 's' : ''} input` }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
<div
|
||||
use:clickOutside={{
|
||||
eventToListenName: 'pointerdown',
|
||||
stopPropagation: true,
|
||||
onClickOutside: () => (open = false)
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -4,20 +4,41 @@
|
||||
import { type ToggleGroupElements, type ToggleGroupItemProps, melt } from '@melt-ui/svelte'
|
||||
import { Info } from 'lucide-svelte'
|
||||
|
||||
export let label: string | undefined = undefined
|
||||
export let iconOnly: boolean = false
|
||||
export let tooltip: string | undefined = undefined
|
||||
export let icon: any | undefined = undefined
|
||||
export let disabled: boolean = false
|
||||
export let selectedColor: string = '#3b82f6'
|
||||
export let small = false
|
||||
export let light = false
|
||||
export let iconProps: Record<string, any> = {}
|
||||
export let showTooltipIcon: boolean = false
|
||||
export let documentationLink: string | undefined = undefined
|
||||
export let id: string | undefined = undefined
|
||||
export let item: ToggleGroupElements['item']
|
||||
export let value: ToggleGroupItemProps
|
||||
interface Props {
|
||||
label?: string | undefined
|
||||
iconOnly?: boolean
|
||||
tooltip?: string | undefined
|
||||
icon?: any | undefined
|
||||
disabled?: boolean
|
||||
selectedColor?: string | undefined
|
||||
small?: boolean
|
||||
light?: boolean
|
||||
iconProps?: Record<string, any>
|
||||
showTooltipIcon?: boolean
|
||||
documentationLink?: string | undefined
|
||||
id?: string | undefined
|
||||
item: ToggleGroupElements['item']
|
||||
value: ToggleGroupItemProps
|
||||
class?: string
|
||||
}
|
||||
|
||||
let {
|
||||
label = undefined,
|
||||
iconOnly = false,
|
||||
tooltip = undefined,
|
||||
icon = undefined,
|
||||
disabled = false,
|
||||
selectedColor = undefined,
|
||||
small = false,
|
||||
light = false,
|
||||
iconProps = {},
|
||||
showTooltipIcon = false,
|
||||
documentationLink = undefined,
|
||||
id = undefined,
|
||||
item,
|
||||
value,
|
||||
class: className = ''
|
||||
}: Props = $props()
|
||||
</script>
|
||||
|
||||
<Tooltip
|
||||
@@ -30,25 +51,28 @@
|
||||
{id}
|
||||
{disabled}
|
||||
class={twMerge(
|
||||
'group rounded-md transition-all text-xs flex gap-1 flex-row items-center',
|
||||
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1',
|
||||
'group rounded-md transition-all font-normal flex gap-1 flex-row items-center border',
|
||||
small ? 'px-1.5 py-0.5 text-2xs' : 'px-2 py-1 text-sm',
|
||||
light ? 'font-medium' : '',
|
||||
'data-[state=on]:bg-surface data-[state=on]:shadow-md',
|
||||
'data-[state=on]:bg-surface data-[state=off]:border-transparent data-[state=on]:border-gray-300 dark:data-[state=on]:border-gray-500',
|
||||
'text-hint hover:text-secondary data-[state=on]:text-secondary data-[state=on]:font-medium hover:font-medium',
|
||||
'bg-surface-secondary hover:bg-surface-hover',
|
||||
disabled ? '!shadow-none' : '',
|
||||
$$props.class
|
||||
className
|
||||
)}
|
||||
use:melt={$item(value)}
|
||||
style={`--selected-color: ${selectedColor}`}
|
||||
style={selectedColor ? `--selected-color: ${selectedColor}` : ''}
|
||||
>
|
||||
{#if icon}
|
||||
<svelte:component
|
||||
this={icon}
|
||||
{@const SvelteComponent = icon}
|
||||
<SvelteComponent
|
||||
size={small ? 12 : 14}
|
||||
{...iconProps}
|
||||
class={twMerge(
|
||||
'text-gray-400',
|
||||
'group-data-[state=on]:text-[var(--selected-color)]',
|
||||
selectedColor
|
||||
? 'group-data-[state=on]:text-[var(--selected-color)]'
|
||||
: 'group-data-[state=on]:text-nord-950 dark:group-data-[state=on]:text-nord-900',
|
||||
iconProps.class
|
||||
)}
|
||||
/>
|
||||
@@ -61,7 +85,7 @@
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
<svelte:fragment slot="text">
|
||||
{#snippet text()}
|
||||
{tooltip}
|
||||
</svelte:fragment>
|
||||
{/snippet}
|
||||
</Tooltip>
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
>
|
||||
<div
|
||||
class={twMerge(
|
||||
'flex bg-surface-secondary rounded-md p-0.5 gap-1 h-full',
|
||||
'flex bg-surface-secondary rounded-md h-full',
|
||||
wrap ? 'flex-wrap' : '',
|
||||
tabListClass
|
||||
)}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
item?: any | undefined
|
||||
selected?: string | undefined
|
||||
togglableItems: TogglableItem[]
|
||||
class?: string
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -27,7 +28,8 @@
|
||||
id = undefined,
|
||||
item = undefined,
|
||||
selected = $bindable(undefined),
|
||||
togglableItems
|
||||
togglableItems,
|
||||
class: className = ''
|
||||
}: Props = $props()
|
||||
|
||||
let items = togglableItems.map((i) => ({
|
||||
@@ -44,7 +46,7 @@
|
||||
<Popover
|
||||
disablePopup={true}
|
||||
notClickable
|
||||
class={twMerge('flex', disabled ? 'cursor-not-allowed' : 'cursor-pointer')}
|
||||
class={twMerge('flex', disabled ? 'cursor-not-allowed' : 'cursor-pointer', className)}
|
||||
disappearTimeout={0}
|
||||
>
|
||||
<div {id} class="flex">
|
||||
@@ -63,7 +65,7 @@
|
||||
/>
|
||||
{/if}
|
||||
<div class="flex items-center">
|
||||
<DropdownV2 {items} />
|
||||
<DropdownV2 enableFlyTransition {items} />
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
|
||||
@@ -1,3 +1,14 @@
|
||||
<script lang="ts" module>
|
||||
export function stepInputGenButtonClasses(selected: boolean) {
|
||||
return twMerge(
|
||||
'text-violet-500 dark:text-violet-400 border',
|
||||
selected
|
||||
? 'bg-green-50 hover:bg-green-50 dark:bg-green-400/15 dark:hover:bg-green-400/15 text-green-800 border-green-200 dark:border-green-300/60 dark:text-green-400 '
|
||||
: 'hover:bg-violet-50 border-violet-100 dark:hover:bg-violet-400/15 dark:border-violet-400/20'
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { Check, Loader2, Wand2 } from 'lucide-svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
@@ -27,6 +38,7 @@
|
||||
export let pickableProperties: PickableProperties | undefined = undefined
|
||||
export let argName: string
|
||||
export let showPopup: boolean
|
||||
export let btnClass = ''
|
||||
|
||||
let empty = false
|
||||
$: empty =
|
||||
@@ -211,10 +223,8 @@ Only return the expression without any wrapper.`
|
||||
size="xs"
|
||||
color="light"
|
||||
btnClasses={twMerge(
|
||||
'text-violet-800 dark:text-violet-400 bg-violet-100 dark:bg-gray-700 dark:hover:bg-surface-hover',
|
||||
!loading && generatedContent.length > 0
|
||||
? 'bg-green-100 text-green-800 hover:bg-green-100 dark:text-green-400 dark:bg-green-700 dark:hover:bg-green-700'
|
||||
: ''
|
||||
stepInputGenButtonClasses(!loading && generatedContent.length > 0),
|
||||
btnClass
|
||||
)}
|
||||
on:click={() => {
|
||||
if (!loading && generatedContent.length > 0) {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import FlowCopilotInputsModal from './FlowCopilotInputsModal.svelte'
|
||||
import type { Flow } from '$lib/gen'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { stepInputGenButtonClasses } from './StepInputGen.svelte'
|
||||
|
||||
let loading = false
|
||||
export let pickableProperties: PickableProperties | undefined = undefined
|
||||
@@ -178,11 +179,9 @@ input_name2: expression2
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
wrapperClasses="flex-1"
|
||||
btnClasses={twMerge(
|
||||
'text-violet-800 dark:text-violet-400',
|
||||
!loading && Object.keys($generatedExprs || {}).length > 0
|
||||
? 'bg-green-100 text-green-800 hover:bg-green-100 dark:text-green-400 dark:bg-green-700 dark:hover:bg-green-700'
|
||||
: ''
|
||||
stepInputGenButtonClasses(!loading && Object.keys($generatedExprs || {}).length > 0)
|
||||
)}
|
||||
on:mouseenter={(ev) => {
|
||||
if (out) {
|
||||
@@ -219,12 +218,13 @@ input_name2: expression2
|
||||
floatingConfig={{
|
||||
placement: 'top-end'
|
||||
}}
|
||||
class="w-full"
|
||||
>
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button
|
||||
size="xs"
|
||||
color="light"
|
||||
btnClasses="text-violet-800 dark:text-violet-400"
|
||||
btnClasses="text-violet-800 dark:text-violet-400 border border-violet-200 hover:bg-violet-50"
|
||||
nonCaptureEvent
|
||||
startIcon={{
|
||||
icon: Wand2
|
||||
|
||||
@@ -2,19 +2,19 @@
|
||||
import Button from '$lib/components/common/button/Button.svelte'
|
||||
import { ChevronDown } from 'lucide-svelte'
|
||||
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
$: opened = false
|
||||
</script>
|
||||
|
||||
<div class="text-xs flex flex-row-reverse mt-1">
|
||||
<div class="text-xs flex flex-row-reverse">
|
||||
<Button
|
||||
on:click={() => {
|
||||
opened = !opened
|
||||
}}
|
||||
color="light"
|
||||
variant="divider"
|
||||
size="xs2"
|
||||
endIcon={{ icon: ChevronDown, classes: `rotate-0 duration-300 ${opened ? '!rotate-180' : ''}` }}
|
||||
btnClasses="text-hint font-normal pt-1"
|
||||
>
|
||||
Help
|
||||
</Button>
|
||||
@@ -22,8 +22,7 @@
|
||||
|
||||
{#if opened}
|
||||
<div
|
||||
transition:slide|local
|
||||
class="bg-surface-secondary border-l-4 text-secondary p-4 m-4"
|
||||
class="bg-surface-secondary border-l-4 text-sm text-secondary p-4 mt-2"
|
||||
role="alert"
|
||||
id="dynamic-input-help-box"
|
||||
>
|
||||
|
||||
@@ -572,7 +572,7 @@
|
||||
></div>
|
||||
{/if}
|
||||
<InputTransformSchemaForm
|
||||
class="px-1 xl:px-2"
|
||||
class="px-2 xl:px-4"
|
||||
bind:this={inputTransformSchemaForm}
|
||||
pickableProperties={stepPropPicker.pickableProperties}
|
||||
schema={flowStateStore.val[$selectedId]?.schema ?? {}}
|
||||
@@ -852,19 +852,14 @@
|
||||
{#if flowModule.value['language'] === 'deno'}
|
||||
<ToggleButton
|
||||
value="s3_client"
|
||||
size="sm"
|
||||
small
|
||||
label="S3 lite client"
|
||||
{item}
|
||||
/>
|
||||
{:else}
|
||||
<ToggleButton
|
||||
value="s3_client"
|
||||
size="sm"
|
||||
label="Boto3"
|
||||
{item}
|
||||
/>
|
||||
<ToggleButton value="polars" size="sm" label="Polars" {item} />
|
||||
<ToggleButton value="duckdb" size="sm" label="DuckDB" {item} />
|
||||
<ToggleButton value="s3_client" small label="Boto3" {item} />
|
||||
<ToggleButton value="polars" small label="Polars" {item} />
|
||||
<ToggleButton value="duckdb" small label="DuckDB" {item} />
|
||||
{/if}
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
|
||||
@@ -108,11 +108,7 @@
|
||||
>
|
||||
<slot />
|
||||
</Pane>
|
||||
<Pane
|
||||
minSize={20}
|
||||
size={40}
|
||||
class="!transition-none z-1000 {$propPickerConfig ? 'ml-[-1px]' : ''} {paneClass}"
|
||||
>
|
||||
<Pane minSize={20} size={40} class="!transition-none z-1000 {paneClass}">
|
||||
<AnimatedButton
|
||||
animate={$propPickerConfig?.insertionMode == 'connect'}
|
||||
baseRadius="4px"
|
||||
@@ -158,14 +154,3 @@
|
||||
</Pane>
|
||||
</Splitpanes>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:global(.splitpanes-remove-splitter > .splitpanes__pane) {
|
||||
background-color: inherit !important;
|
||||
}
|
||||
:global(.splitpanes-remove-splitter > .splitpanes__splitter) {
|
||||
background-color: transparent !important;
|
||||
width: 0 !important;
|
||||
border: none !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
let { label, children, tooltip, for: forAttr = '', noLabel = false }: Props = $props()
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col items-start w-fit">
|
||||
<div class="flex flex-col items-start w-fit gap-0.5">
|
||||
<div class="text-xs truncate">
|
||||
{#if forAttr}
|
||||
<label for={forAttr} class:text-transparent={noLabel}>{label}</label>
|
||||
<label for={forAttr} class="text-hint" class:text-transparent={noLabel}>{label}</label>
|
||||
{:else}
|
||||
<span class:text-transparent={noLabel}>{label}</span>
|
||||
{/if}
|
||||
|
||||
@@ -183,9 +183,10 @@
|
||||
</RunOption>
|
||||
{/if}
|
||||
<!-- Filter by -->
|
||||
<div class="flex flex-row gap-1">
|
||||
<div class="flex flex-row gap-4">
|
||||
<RunOption label="Filter by" for="filter-by">
|
||||
<ToggleButtonGroup
|
||||
class="h-9"
|
||||
bind:selected={filterBy}
|
||||
on:selected={(e) => {
|
||||
if (e.detail != filterBy) {
|
||||
@@ -226,7 +227,7 @@
|
||||
bind:value={() => user ?? undefined, (v) => (user = v ?? null)}
|
||||
clearable
|
||||
onClear={() => ((user = null), dispatch('reset'))}
|
||||
inputClass="!h-[32px] min-w-36"
|
||||
inputClass="!max-h-9 min-w-36 border-none"
|
||||
onCreateItem={(item) => (usernames.push(item), (user = item))}
|
||||
createText="Press enter to use this value"
|
||||
id="user"
|
||||
@@ -241,7 +242,7 @@
|
||||
bind:value={() => folder ?? undefined, (v) => (folder = v ?? null)}
|
||||
clearable
|
||||
onClear={() => ((folder = null), dispatch('reset'))}
|
||||
inputClass="!h-[32px] min-w-36"
|
||||
inputClass="!max-h-9 min-w-36 border-none"
|
||||
id="folder"
|
||||
/>
|
||||
{/key}
|
||||
@@ -254,7 +255,7 @@
|
||||
bind:value={() => path ?? undefined, (v) => (path = v ?? null)}
|
||||
clearable
|
||||
onClear={() => ((path = null), dispatch('reset'))}
|
||||
inputClass="!h-[32px] min-w-36"
|
||||
inputClass="!max-h-9 min-w-36 border-none"
|
||||
onCreateItem={(item) => (paths.push(item), (path = item))}
|
||||
createText="Press enter to use this value"
|
||||
id="path"
|
||||
@@ -479,7 +480,7 @@
|
||||
<RunOption label="Kind" for="kind">
|
||||
{#if small && !calendarSmall}
|
||||
<DropdownSelect
|
||||
btnClasses="min-w-24"
|
||||
btnClasses="min-w-24 h-9 bg-surface-secondary font-normal"
|
||||
items={[
|
||||
{
|
||||
displayName: 'All',
|
||||
@@ -524,7 +525,7 @@
|
||||
selected={jobKindsCat}
|
||||
/>
|
||||
{:else}
|
||||
<ToggleButtonGroup bind:selected={jobKindsCat}>
|
||||
<ToggleButtonGroup bind:selected={jobKindsCat} class="h-9">
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="all" label="All" {item} />
|
||||
<ToggleButton
|
||||
@@ -571,6 +572,7 @@
|
||||
<!-- Status -->
|
||||
<RunOption label="Status" for="status">
|
||||
<ToggleButtonGroup
|
||||
class="h-9"
|
||||
selected={success ?? 'all'}
|
||||
on:selected={({ detail }) => {
|
||||
success = detail === 'all' ? undefined : detail
|
||||
@@ -638,11 +640,12 @@
|
||||
{#snippet trigger()}
|
||||
<Button
|
||||
color="light"
|
||||
variant="border"
|
||||
variant="contained"
|
||||
size="xs"
|
||||
nonCaptureEvent={true}
|
||||
startIcon={{ icon: ListFilterPlus }}
|
||||
iconOnly
|
||||
btnClasses="bg-surface-secondary py-[8.5px]"
|
||||
></Button>
|
||||
{/snippet}
|
||||
|
||||
|
||||
@@ -444,9 +444,10 @@
|
||||
showExpr = showExpr ? undefined : 'true //fields.foo == 42'
|
||||
}}
|
||||
/>
|
||||
{#if showExpr != undefined}
|
||||
<div class="border">
|
||||
{#if showExpr}
|
||||
<div class="mt-2 bg-surface-secondary rounded-md pl-4">
|
||||
<SimpleEditor
|
||||
yPadding={8}
|
||||
extraLib={`declare const fields: Record<${propsNames
|
||||
?.filter((x) => x != name)
|
||||
.map((x) => `"${x}"`)
|
||||
@@ -455,6 +456,7 @@
|
||||
bind:code={showExpr}
|
||||
shouldBindKey={false}
|
||||
fixedOverflowWidgets={false}
|
||||
hideLineNumbers
|
||||
autoHeight
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
import ToggleButton from '../common/toggleButton-v2/ToggleButton.svelte'
|
||||
import { createEventDispatcher, onMount, untrack } from 'svelte'
|
||||
import { createDispatcherIfMounted } from '$lib/createDispatcherIfMounted'
|
||||
import TextInput, { inputBaseClass, inputBorderClass } from '../text_input/TextInput.svelte'
|
||||
|
||||
interface Props {
|
||||
description?: string
|
||||
@@ -179,6 +180,7 @@
|
||||
onkeydown={onKeyDown}
|
||||
onchange={() => dispatch('change')}
|
||||
placeholder="Field description"
|
||||
class={twMerge(inputBorderClass(), inputBaseClass, 'w-full')}
|
||||
></textarea>
|
||||
</Label>
|
||||
|
||||
@@ -186,30 +188,31 @@
|
||||
{#snippet header()}
|
||||
<Tooltip light>Will be displayed in the UI instead of the field name.</Tooltip>
|
||||
{/snippet}
|
||||
<input
|
||||
<TextInput
|
||||
bind:value={title}
|
||||
onchange={() => dispatch('change')}
|
||||
onkeydown={onKeyDown}
|
||||
placeholder="Field title"
|
||||
inputProps={{ placeholder: 'Field title', onkeydown, onchange: () => dispatch('change') }}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
<Label label="Placeholder">
|
||||
{#snippet header()}
|
||||
<Tooltip light>
|
||||
Will be displayed in the input field when the field is empty. If not set, the default
|
||||
value will be used. The placeholder is disabled depending on the field type, format, etc.
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
{#if shouldDisplayPlaceholder(type, format, enum_, contentEncoding, pattern, extra)}
|
||||
<Label label="Placeholder">
|
||||
{#snippet header()}
|
||||
<Tooltip light>
|
||||
Will be displayed in the input field when the field is empty. If not set, the default
|
||||
value will be used. The placeholder is disabled depending on the field type, format,
|
||||
etc.
|
||||
</Tooltip>
|
||||
{/snippet}
|
||||
|
||||
<textarea
|
||||
placeholder="Enter a placeholder"
|
||||
rows="1"
|
||||
bind:value={placeholder}
|
||||
onchange={() => dispatch('change')}
|
||||
disabled={!shouldDisplayPlaceholder(type, format, enum_, contentEncoding, pattern, extra)}
|
||||
></textarea>
|
||||
</Label>
|
||||
<textarea
|
||||
placeholder="Enter a placeholder"
|
||||
rows="1"
|
||||
bind:value={placeholder}
|
||||
onchange={() => dispatch('change')}
|
||||
class={twMerge(inputBorderClass(), inputBaseClass, 'w-full')}
|
||||
></textarea>
|
||||
</Label>
|
||||
{/if}
|
||||
|
||||
{#if type == 'array'}
|
||||
<ArrayTypeNarrowing
|
||||
@@ -285,11 +288,3 @@
|
||||
{@render children?.()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
input::-webkit-outer-spin-button,
|
||||
input::-webkit-inner-spin-button {
|
||||
-webkit-appearance: none !important;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
helperScript?: DynamicInput.HelperScript
|
||||
className?: string
|
||||
dndType?: string
|
||||
lightHeaderFont?: boolean
|
||||
}
|
||||
|
||||
let {
|
||||
@@ -44,7 +45,8 @@
|
||||
isValid = $bindable(true),
|
||||
noVariablePicker = false,
|
||||
className = '',
|
||||
dndType = generateRandomString()
|
||||
dndType = generateRandomString(),
|
||||
lightHeaderFont
|
||||
}: Props = $props()
|
||||
|
||||
$effect.pre(() => {
|
||||
@@ -135,10 +137,11 @@
|
||||
{shouldDispatchChanges}
|
||||
bind:isValid
|
||||
{noVariablePicker}
|
||||
{lightHeaderFont}
|
||||
>
|
||||
{#snippet actions()}
|
||||
{#if !disableDnd}
|
||||
<div class="w-4 h-8 cursor-move ml-2 handle" aria-label="drag-handle" use:dragHandle>
|
||||
<div class="w-4 h-8 cursor-move ml-2 handle mt-[9px]" aria-label="drag-handle" use:dragHandle>
|
||||
<GripVertical size={16} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -30,7 +30,7 @@
|
||||
role="listitem"
|
||||
class={twMerge(
|
||||
allowClear ? 'pr-1' : 'pr-3',
|
||||
'pl-3 min-h-6 bg-surface-secondary rounded-full flex items-center gap-0.5',
|
||||
'pl-3 min-h-6 border bg-surface rounded-full flex items-center gap-0.5',
|
||||
currentlyDraggingIndex !== undefined ? 'hover:opacity-20' : ''
|
||||
)}
|
||||
style={currentlyDraggingIndex === index
|
||||
@@ -54,7 +54,7 @@
|
||||
<span class="text-sm select-none">{item.label || item.value}</span>
|
||||
{#if allowClear}
|
||||
<CloseButton
|
||||
class="text-tertiary"
|
||||
class="text-hint bg-transparent"
|
||||
small
|
||||
on:close={(e) => (onRemove(item), e.stopPropagation())}
|
||||
/>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import DraggableTags from './DraggableTags.svelte'
|
||||
import { Search } from 'lucide-svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import TextInput, { inputBorderClass } from '../text_input/TextInput.svelte'
|
||||
|
||||
type Value = Item['value']
|
||||
|
||||
@@ -59,7 +60,7 @@
|
||||
let filterText = $state<string>('')
|
||||
let open = $state<boolean>(false)
|
||||
let wrapperEl: HTMLDivElement | undefined = $state()
|
||||
let searchInputEl: HTMLInputElement | undefined = $state()
|
||||
let searchInputEl: TextInput | undefined = $state()
|
||||
|
||||
$effect(() => searchInputEl?.focus())
|
||||
|
||||
@@ -99,7 +100,8 @@
|
||||
<div
|
||||
bind:this={wrapperEl}
|
||||
class={twMerge(
|
||||
'relative min-h-8 flex items-center w-full bg-surface border border-gray-300 rounded-md text-tertiary',
|
||||
'relative min-h-10 flex items-center w-full bg-surface-secondary !text-secondary rounded-md',
|
||||
inputBorderClass({ forceFocus: open && !disabled }),
|
||||
disabled ? 'pointer-events-none' : '',
|
||||
open && !disabled ? 'open' : '',
|
||||
disabled ? 'disabled' : '',
|
||||
@@ -113,7 +115,9 @@
|
||||
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
|
||||
|
||||
{#if value.length === 0}
|
||||
<span class={twMerge('text-sm ml-2 h-full flex items-center flex-1', placeholderClass)}>
|
||||
<span
|
||||
class={twMerge('text-sm ml-4 h-full flex items-center flex-1 text-hint', placeholderClass)}
|
||||
>
|
||||
{placeholder}
|
||||
</span>
|
||||
{:else}
|
||||
@@ -134,10 +138,10 @@
|
||||
/>
|
||||
</ul>
|
||||
{/if}
|
||||
{#if allowClear && !hideMainClearBtn}
|
||||
{#if allowClear && !hideMainClearBtn && !!value?.length}
|
||||
<CloseButton
|
||||
noBg
|
||||
class="mr-1 remove-all"
|
||||
class="mr-1 remove-all bg-transparent text-hint"
|
||||
small
|
||||
on:close={(e) => (clearValue(), e.stopPropagation())}
|
||||
/>
|
||||
@@ -163,12 +167,14 @@
|
||||
{#snippet header()}
|
||||
{#if processedItems.length - value.length > 0 || onCreateItem}
|
||||
<div class="mx-2 mb-1 mt-2 flex items-center relative">
|
||||
<input
|
||||
<TextInput
|
||||
bind:this={searchInputEl}
|
||||
bind:value={filterText}
|
||||
onblur={(e) => (e.preventDefault(), searchInputEl?.focus())}
|
||||
placeholder="Search"
|
||||
class="!pr-7"
|
||||
inputProps={{
|
||||
onblur: (e) => (e.preventDefault(), searchInputEl?.focus()),
|
||||
placeholder: 'Search...'
|
||||
}}
|
||||
class="!pr-7 !bg-surface"
|
||||
/>
|
||||
<Search size={16} class="absolute right-2 text-tertiary" />
|
||||
</div>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
import { getLabel, processItems, type ProcessedItem } from './utils.svelte'
|
||||
import SelectDropdown from './SelectDropdown.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import { inputBaseClass, inputBorderClass } from '../text_input/TextInput.svelte'
|
||||
|
||||
type Value = Item['value']
|
||||
|
||||
@@ -23,19 +24,24 @@
|
||||
inputClass = '',
|
||||
disablePortal = false,
|
||||
loading = false,
|
||||
error = false,
|
||||
autofocus,
|
||||
RightIcon,
|
||||
createText,
|
||||
noItemsMsg,
|
||||
open = $bindable(false),
|
||||
id,
|
||||
itemLabelWrapperClasses,
|
||||
itemButtonWrapperClasses,
|
||||
groupBy,
|
||||
sortBy,
|
||||
onFocus,
|
||||
onBlur,
|
||||
onClear,
|
||||
onCreateItem,
|
||||
startSnippet
|
||||
startSnippet,
|
||||
endSnippet,
|
||||
bottomSnippet
|
||||
}: {
|
||||
items?: Item[]
|
||||
value: Value | undefined
|
||||
@@ -49,19 +55,24 @@
|
||||
inputClass?: string
|
||||
disablePortal?: boolean
|
||||
loading?: boolean
|
||||
error?: boolean
|
||||
autofocus?: boolean
|
||||
RightIcon?: any
|
||||
createText?: string
|
||||
noItemsMsg?: string
|
||||
open?: boolean
|
||||
id?: string
|
||||
itemLabelWrapperClasses?: string
|
||||
itemButtonWrapperClasses?: string
|
||||
groupBy?: (item: Item) => string
|
||||
sortBy?: (a: Item, b: Item) => number
|
||||
onFocus?: () => void
|
||||
onBlur?: () => void
|
||||
onClear?: () => void
|
||||
onCreateItem?: (value: string) => void
|
||||
startSnippet?: Snippet<[{ item: ProcessedItem<Value> }]>
|
||||
startSnippet?: Snippet<[{ item: ProcessedItem<Value>; close: () => void }]>
|
||||
endSnippet?: Snippet<[{ item: ProcessedItem<Value>; close: () => void }]>
|
||||
bottomSnippet?: Snippet<[{ close: () => void }]>
|
||||
} = $props()
|
||||
|
||||
let disabled = $derived(_disabled || (loading && !value))
|
||||
@@ -112,7 +123,7 @@
|
||||
</div>
|
||||
{:else if clearable && !disabled && value}
|
||||
<div class="absolute z-10 right-2 h-full flex items-center">
|
||||
<CloseButton class="text-secondary" noBg small on:close={clearValue} />
|
||||
<CloseButton class="bg-transparent text-hint" noBg small on:close={clearValue} />
|
||||
</div>
|
||||
{:else if RightIcon}
|
||||
<div class="absolute z-10 right-2 h-full flex items-center">
|
||||
@@ -130,10 +141,12 @@
|
||||
: (valueEntry?.label ?? getLabel({ value }) ?? placeholder)}
|
||||
style={containerStyle}
|
||||
class={twMerge(
|
||||
'!bg-surface text-ellipsis',
|
||||
inputBaseClass,
|
||||
inputBorderClass({ error, forceFocus: open }),
|
||||
'w-full',
|
||||
open ? '' : 'cursor-pointer',
|
||||
!loading && value ? '!placeholder-primary' : '',
|
||||
(clearable || RightIcon) && !disabled && value ? '!pr-8' : '',
|
||||
!loading && value && !disabled ? '!placeholder-secondary' : 'placeholder-hint',
|
||||
(clearable || RightIcon) && !disabled && value ? 'pr-8' : '',
|
||||
inputClass ?? ''
|
||||
)}
|
||||
autocomplete="off"
|
||||
@@ -152,6 +165,10 @@
|
||||
getInputRect={inputEl && (() => inputEl!.getBoundingClientRect())}
|
||||
{listAutoWidth}
|
||||
{noItemsMsg}
|
||||
{itemLabelWrapperClasses}
|
||||
{itemButtonWrapperClasses}
|
||||
{startSnippet}
|
||||
{endSnippet}
|
||||
{bottomSnippet}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import { untrack, type Snippet } from 'svelte'
|
||||
import type { ProcessedItem } from './utils.svelte'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
import { slide } from 'svelte/transition'
|
||||
|
||||
let {
|
||||
processedItems: _processedItems,
|
||||
@@ -16,10 +17,14 @@
|
||||
noItemsMsg = 'No items found',
|
||||
class: className = '',
|
||||
ulClass = '',
|
||||
itemLabelWrapperClasses = '',
|
||||
itemButtonWrapperClasses = '',
|
||||
header,
|
||||
getInputRect,
|
||||
onSelectValue,
|
||||
startSnippet
|
||||
startSnippet,
|
||||
endSnippet,
|
||||
bottomSnippet
|
||||
}: {
|
||||
processedItems?: ProcessedItem<T>[]
|
||||
value: T | undefined
|
||||
@@ -31,10 +36,14 @@
|
||||
noItemsMsg?: string
|
||||
class?: string
|
||||
ulClass?: string
|
||||
itemLabelWrapperClasses?: string
|
||||
itemButtonWrapperClasses?: string
|
||||
header?: Snippet
|
||||
getInputRect?: () => DOMRect
|
||||
onSelectValue: (item: ProcessedItem<T>) => void
|
||||
startSnippet?: Snippet<[{ item: ProcessedItem<T> }]>
|
||||
startSnippet?: Snippet<[{ item: ProcessedItem<T>; close: () => void }]>
|
||||
endSnippet?: Snippet<[{ item: ProcessedItem<T>; close: () => void }]>
|
||||
bottomSnippet?: Snippet<[{ close: () => void }]>
|
||||
} = $props()
|
||||
|
||||
let processedItems = $derived(
|
||||
@@ -101,53 +110,60 @@
|
||||
<ConditionalPortal condition={!disablePortal} name="select-dropdown-portal">
|
||||
{#if open && !disabled}
|
||||
<div
|
||||
transition:slide={{ duration: 150 }}
|
||||
class={twMerge(
|
||||
disablePortal ? 'absolute' : 'fixed',
|
||||
'flex flex-col z-[5001] max-h-64 overflow-y-auto bg-surface-secondary text-tertiary text-sm select-none border rounded-lg shadow-lg',
|
||||
'z-[5001] rounded-md bg-surface-secondary text-tertiary shadow-lg text-sm select-none',
|
||||
className
|
||||
)}
|
||||
style="{`top: ${dropdownPos.y}px; left: ${dropdownPos.x}px;`} {listAutoWidth
|
||||
? `min-width: ${dropdownPos.width}px;`
|
||||
: ''}"
|
||||
bind:this={listEl}
|
||||
>
|
||||
{@render header?.()}
|
||||
{#if processedItems?.length === 0}
|
||||
<div class="py-8 px-4 text-center text-primary">{noItemsMsg}</div>
|
||||
{/if}
|
||||
<ul class={twMerge('flex-1 overflow-y-auto flex flex-col', ulClass)}>
|
||||
{#each processedItems ?? [] as item, itemIndex}
|
||||
{#if (item.__select_group && itemIndex === 0) || processedItems?.[itemIndex - 1]?.__select_group !== item.__select_group}
|
||||
<li
|
||||
class={twMerge(
|
||||
'mx-4 pb-1 mb-2 text-xs font-semibold text-primary border-b',
|
||||
itemIndex === 0 ? 'mt-3' : 'mt-6'
|
||||
)}
|
||||
>
|
||||
{item.__select_group}
|
||||
<div bind:this={listEl} class="flex flex-col max-h-64 border rounded-md overflow-clip">
|
||||
{@render header?.()}
|
||||
{#if processedItems?.length === 0}
|
||||
<div class="py-8 px-4 text-center text-primary">{noItemsMsg}</div>
|
||||
{/if}
|
||||
<ul class={twMerge('flex-1 overflow-y-auto flex flex-col', ulClass)}>
|
||||
{#each processedItems ?? [] as item, itemIndex}
|
||||
{#if (item.__select_group && itemIndex === 0) || processedItems?.[itemIndex - 1]?.__select_group !== item.__select_group}
|
||||
<li
|
||||
class={twMerge(
|
||||
'mx-4 pb-1 mb-2 text-xs font-semibold text-primary border-b',
|
||||
itemIndex === 0 ? 'mt-3' : 'mt-6'
|
||||
)}
|
||||
>
|
||||
{item.__select_group}
|
||||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
<button
|
||||
class={twMerge(
|
||||
'py-2 px-4 w-full font-normal text-left text-primary',
|
||||
itemIndex === keyArrowPos ? 'bg-surface-hover' : '',
|
||||
item.value === value ? 'bg-surface-selected' : 'hover:bg-surface-hover',
|
||||
itemButtonWrapperClasses
|
||||
)}
|
||||
onclick={(e) => {
|
||||
e.stopImmediatePropagation()
|
||||
onSelectValue(item)
|
||||
}}
|
||||
>
|
||||
{@render startSnippet?.({ item, close: () => (open = false) })}
|
||||
<span class={itemLabelWrapperClasses}>
|
||||
{item.label || '\xa0'}
|
||||
</span>
|
||||
{@render endSnippet?.({ item, close: () => (open = false) })}
|
||||
{#if item.subtitle}
|
||||
<div class="text-xs text-tertiary">{item.subtitle}</div>
|
||||
{/if}
|
||||
</button>
|
||||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
<button
|
||||
class={twMerge(
|
||||
'py-2 px-4 w-full font-normal text-left text-primary',
|
||||
itemIndex === keyArrowPos ? 'bg-surface-hover' : '',
|
||||
item.value === value ? 'bg-surface-selected' : 'hover:bg-surface-hover'
|
||||
)}
|
||||
onclick={(e) => {
|
||||
e.stopImmediatePropagation()
|
||||
onSelectValue(item)
|
||||
}}
|
||||
>
|
||||
{@render startSnippet?.({ item })}
|
||||
{item.label || '\xa0'}
|
||||
{#if item.subtitle}
|
||||
<div class="text-xs text-tertiary">{item.subtitle}</div>
|
||||
{/if}
|
||||
</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/each}
|
||||
</ul>
|
||||
{@render bottomSnippet?.({ close: () => (open = false) })}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</ConditionalPortal>
|
||||
|
||||
@@ -5,13 +5,21 @@
|
||||
import ToggleButtonGroup from '../common/toggleButton-v2/ToggleButtonGroup.svelte'
|
||||
import { triggerableByAI } from '$lib/actions/triggerableByAI.svelte'
|
||||
import Toggle from '../Toggle.svelte'
|
||||
import { FlowService, IntegrationService, ScriptService, UserService, type NewToken } from '$lib/gen'
|
||||
import {
|
||||
FlowService,
|
||||
IntegrationService,
|
||||
ScriptService,
|
||||
UserService,
|
||||
type NewToken
|
||||
} from '$lib/gen'
|
||||
import MultiSelect from '../select/MultiSelect.svelte'
|
||||
import { safeSelectItems } from '../select/utils.svelte'
|
||||
import TokenDisplay from './TokenDisplay.svelte'
|
||||
import ScopeSelector from './ScopeSelector.svelte'
|
||||
import Alert from '../common/alert/Alert.svelte'
|
||||
import FolderPicker from '../FolderPicker.svelte'
|
||||
import TextInput from '../text_input/TextInput.svelte'
|
||||
import Select from '../select/Select.svelte'
|
||||
|
||||
interface Props {
|
||||
showMcpMode?: boolean
|
||||
@@ -44,7 +52,7 @@
|
||||
let loadingRunnables = $state(false)
|
||||
let includedRunnables = $state<string[]>([])
|
||||
let selectedFolder = $state<string>('')
|
||||
|
||||
|
||||
let runnablesCache = new Map<string, string[]>()
|
||||
|
||||
let customScopes = $state<string[]>([])
|
||||
@@ -110,7 +118,11 @@
|
||||
|
||||
const workspaces = $derived(ensureCurrentWorkspaceIncluded($userWorkspaces, $workspaceStore))
|
||||
const mcpBaseUrl = $derived(`${window.location.origin}/api/mcp/w/${newTokenWorkspace}/sse?token=`)
|
||||
const warning = $derived(newMcpScope === 'favorites' ? `You do not have any favorite scripts or flows. You can favorite some scripts and flows to include them, or change the scope to "All scripts/flows" to include all your scripts and flows.` : 'Create your first scripts or flows to make them available via MCP.')
|
||||
const warning = $derived(
|
||||
newMcpScope === 'favorites'
|
||||
? `You do not have any favorite scripts or flows. You can favorite some scripts and flows to include them, or change the scope to "All scripts/flows" to include all your scripts and flows.`
|
||||
: 'Create your first scripts or flows to make them available via MCP.'
|
||||
)
|
||||
const noScriptsOrFlowsAvailableWarning = $derived(includedRunnables.length === 0 ? warning : '')
|
||||
|
||||
$effect(() => {
|
||||
@@ -141,7 +153,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function getScripts(favoriteOnly: boolean = false, workspace: string, folder: string | undefined) {
|
||||
async function getScripts(
|
||||
favoriteOnly: boolean = false,
|
||||
workspace: string,
|
||||
folder: string | undefined
|
||||
) {
|
||||
if (!workspace) {
|
||||
return []
|
||||
}
|
||||
@@ -154,7 +170,11 @@
|
||||
return scripts.map((x) => x.path)
|
||||
}
|
||||
|
||||
async function getFlows(favoriteOnly: boolean = false, workspace: string, folder: string | undefined) {
|
||||
async function getFlows(
|
||||
favoriteOnly: boolean = false,
|
||||
workspace: string,
|
||||
folder: string | undefined
|
||||
) {
|
||||
if (!workspace) {
|
||||
return []
|
||||
}
|
||||
@@ -167,7 +187,11 @@
|
||||
return flows.map((x) => x.path)
|
||||
}
|
||||
|
||||
async function getScriptsAndFlows(favoriteOnly: boolean = false, workspace: string, folder: string | undefined) {
|
||||
async function getScriptsAndFlows(
|
||||
favoriteOnly: boolean = false,
|
||||
workspace: string,
|
||||
folder: string | undefined
|
||||
) {
|
||||
const cacheKey = `${workspace}-${favoriteOnly}${folder ? `-${folder}` : ''}`
|
||||
if (runnablesCache.has(cacheKey)) {
|
||||
includedRunnables = runnablesCache.get(cacheKey) || []
|
||||
@@ -176,7 +200,10 @@
|
||||
|
||||
try {
|
||||
loadingRunnables = true
|
||||
const [scripts, flows] = await Promise.all([getScripts(favoriteOnly, workspace, folder), getFlows(favoriteOnly, workspace, folder)])
|
||||
const [scripts, flows] = await Promise.all([
|
||||
getScripts(favoriteOnly, workspace, folder),
|
||||
getFlows(favoriteOnly, workspace, folder)
|
||||
])
|
||||
const combined = [...scripts, ...flows]
|
||||
runnablesCache.set(cacheKey, combined)
|
||||
includedRunnables = combined
|
||||
@@ -333,7 +360,11 @@
|
||||
|
||||
<div>
|
||||
<span class="block mb-1">Label <span class="text-xs text-tertiary">(optional)</span></span>
|
||||
<input type="text" bind:value={newTokenLabel} class="w-full" />
|
||||
<TextInput
|
||||
inputProps={{ type: 'text' }}
|
||||
bind:value={newTokenLabel}
|
||||
class="w-full !bg-surface"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if !mcpCreationMode}
|
||||
@@ -341,58 +372,61 @@
|
||||
<span class="block mb-1"
|
||||
>Expires In <span class="text-xs text-tertiary">(optional)</span></span
|
||||
>
|
||||
<select bind:value={newTokenExpiration} class="w-full">
|
||||
<option value={undefined}>No expiration</option>
|
||||
<option value={15 * 60}>15m</option>
|
||||
<option value={30 * 60}>30m</option>
|
||||
<option value={1 * 60 * 60}>1h</option>
|
||||
<option value={1 * 24 * 60 * 60}>1d</option>
|
||||
<option value={7 * 24 * 60 * 60}>7d</option>
|
||||
<option value={30 * 24 * 60 * 60}>30d</option>
|
||||
<option value={90 * 24 * 60 * 60}>90d</option>
|
||||
</select>
|
||||
<Select
|
||||
bind:value={newTokenExpiration}
|
||||
placeholder="No expiration"
|
||||
inputClass="!bg-surface"
|
||||
items={[
|
||||
{ label: 'No expiration', value: undefined },
|
||||
{ label: '15 minutes', value: 15 * 60 },
|
||||
{ label: '30 minutes', value: 30 * 60 },
|
||||
{ label: '1 hour', value: 1 * 60 * 60 },
|
||||
{ label: '1 day', value: 1 * 24 * 60 * 60 },
|
||||
{ label: '7 days', value: 7 * 24 * 60 * 60 },
|
||||
{ label: '30 days', value: 30 * 24 * 60 * 60 },
|
||||
{ label: '90 days', value: 90 * 24 * 60 * 60 }
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if mcpCreationMode && (newMcpScope !== 'folder' || selectedFolder.length > 0)}
|
||||
{#if loadingRunnables}
|
||||
<div class="flex flex-col gap-2 col-span-2 pr-4">
|
||||
<span class="block text-xs text-tertiary">Scripts & Flows that will be available via MCP</span>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<Badge rounded small color="dark-gray" baseClass="animate-skeleton">Loading...</Badge>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2 col-span-2 pr-4">
|
||||
{#if noScriptsOrFlowsAvailableWarning}
|
||||
<Alert type="info" title="No scripts or flows available" size="xs">
|
||||
{noScriptsOrFlowsAvailableWarning}
|
||||
</Alert>
|
||||
{:else}
|
||||
<span class="block text-xs text-tertiary">Scripts & Flows that will be available via MCP</span>
|
||||
{#if loadingRunnables}
|
||||
<div class="flex flex-col gap-2 col-span-2 pr-4">
|
||||
<span class="block text-xs text-tertiary"
|
||||
>Scripts & Flows that will be available via MCP</span
|
||||
>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
|
||||
{#if includedRunnables.length <= 5}
|
||||
{#each includedRunnables as scriptOrFlow}
|
||||
<Badge rounded small color="blue">{scriptOrFlow}</Badge>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each includedRunnables.slice(0, 3) as scriptOrFlow}
|
||||
<Badge rounded small color="blue">{scriptOrFlow}</Badge>
|
||||
{/each}
|
||||
<Badge
|
||||
rounded
|
||||
small
|
||||
color="dark-gray"
|
||||
>
|
||||
+{includedRunnables.length - 3} more
|
||||
</Badge>
|
||||
{/if}
|
||||
<Badge rounded small color="dark-gray" baseClass="animate-skeleton">Loading...</Badge>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex flex-col gap-2 col-span-2 pr-4">
|
||||
{#if noScriptsOrFlowsAvailableWarning}
|
||||
<Alert type="info" title="No scripts or flows available" size="xs">
|
||||
{noScriptsOrFlowsAvailableWarning}
|
||||
</Alert>
|
||||
{:else}
|
||||
<span class="block text-xs text-tertiary"
|
||||
>Scripts & Flows that will be available via MCP</span
|
||||
>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
{#if includedRunnables.length <= 5}
|
||||
{#each includedRunnables as scriptOrFlow}
|
||||
<Badge rounded small color="blue">{scriptOrFlow}</Badge>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each includedRunnables.slice(0, 3) as scriptOrFlow}
|
||||
<Badge rounded small color="blue">{scriptOrFlow}</Badge>
|
||||
{/each}
|
||||
<Badge rounded small color="dark-gray">
|
||||
+{includedRunnables.length - 3} more
|
||||
</Badge>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex justify-end gap-2 flex-row">
|
||||
@@ -405,7 +439,8 @@
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => createToken(mcpCreationMode)}
|
||||
disabled={mcpCreationMode && (newTokenWorkspace == undefined || (newMcpScope === 'folder' && !selectedFolder))}
|
||||
disabled={mcpCreationMode &&
|
||||
(newTokenWorkspace == undefined || (newMcpScope === 'folder' && !selectedFolder))}
|
||||
>
|
||||
New token
|
||||
</Button>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
let nbDisplayed = $state(30)
|
||||
|
||||
// Instance group auto-add settings
|
||||
let instanceGroups: Array<{name: string, summary?: string, emails?: string[]}> = $state([])
|
||||
let instanceGroups: Array<{ name: string; summary?: string; emails?: string[] }> = $state([])
|
||||
let autoAddInstanceGroups: string[] = $state([])
|
||||
let autoAddInstanceGroupsRoles: Record<string, string> = $state({})
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
// Available groups for dropdowns - filter out already configured groups
|
||||
let availableGroupItems = $derived(
|
||||
instanceGroups
|
||||
.filter(group => !autoAddInstanceGroups.includes(group.name))
|
||||
.map(group => ({
|
||||
.filter((group) => !autoAddInstanceGroups.includes(group.name))
|
||||
.map((group) => ({
|
||||
value: group.name,
|
||||
label: group.name + (group.summary ? ` - ${group.summary}` : '')
|
||||
}))
|
||||
@@ -65,7 +65,10 @@
|
||||
})
|
||||
|
||||
let hasNonManualUsers = $derived(
|
||||
(filteredUsers || users || []).some((user: User) => user.added_via?.source === 'instance_group' || user.added_via?.source === 'domain')
|
||||
(filteredUsers || users || []).some(
|
||||
(user: User) =>
|
||||
user.added_via?.source === 'instance_group' || user.added_via?.source === 'domain'
|
||||
)
|
||||
)
|
||||
|
||||
// Function to check if a manual user can be converted to a group user
|
||||
@@ -78,7 +81,7 @@
|
||||
// Check if user's email is in any configured instance group
|
||||
const userEmail = user.email
|
||||
for (const groupName of autoAddInstanceGroups) {
|
||||
const group = instanceGroups.find(g => g.name === groupName)
|
||||
const group = instanceGroups.find((g) => g.name === groupName)
|
||||
if (group && group.emails && group.emails.includes(userEmail)) {
|
||||
return true
|
||||
}
|
||||
@@ -87,7 +90,6 @@
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
async function loadSettings(): Promise<void> {
|
||||
const settings = await WorkspaceService.getSettings({ workspace: $workspaceStore! })
|
||||
auto_invite_domain = settings.auto_invite_domain
|
||||
@@ -177,7 +179,7 @@
|
||||
await saveInstanceGroupSettings()
|
||||
} catch (e) {
|
||||
// Rollback on error
|
||||
autoAddInstanceGroups = autoAddInstanceGroups.filter(g => g !== groupToAdd)
|
||||
autoAddInstanceGroups = autoAddInstanceGroups.filter((g) => g !== groupToAdd)
|
||||
delete autoAddInstanceGroupsRoles[groupToAdd]
|
||||
sendUserToast('Failed to add instance group', true)
|
||||
}
|
||||
@@ -188,7 +190,7 @@
|
||||
const previousRole = autoAddInstanceGroupsRoles[groupName]
|
||||
|
||||
try {
|
||||
autoAddInstanceGroups = autoAddInstanceGroups.filter(g => g !== groupName)
|
||||
autoAddInstanceGroups = autoAddInstanceGroups.filter((g) => g !== groupName)
|
||||
delete autoAddInstanceGroupsRoles[groupName]
|
||||
await saveInstanceGroupSettings()
|
||||
} catch (e) {
|
||||
@@ -368,8 +370,8 @@
|
||||
}}
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="invite" size="xs" label="Auto-invite" {item} />
|
||||
<ToggleButton value="add" size="xs" label="Auto-add" {item} />
|
||||
<ToggleButton value="invite" small label="Auto-invite" {item} />
|
||||
<ToggleButton value="add" small label="Auto-add" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
{/if}
|
||||
@@ -469,9 +471,7 @@
|
||||
{#snippet content()}
|
||||
<div class="flex flex-col p-4 min-w-[500px]">
|
||||
<div class="flex flex-col gap-4">
|
||||
<span class="text-sm leading-6 font-semibold">
|
||||
Auto-add instance groups
|
||||
</span>
|
||||
<span class="text-sm leading-6 font-semibold"> Auto-add instance groups </span>
|
||||
|
||||
<!-- Add new instance group form -->
|
||||
{#if availableGroupItems.length > 0}
|
||||
@@ -547,7 +547,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each autoAddInstanceGroups as groupName (groupName)}
|
||||
{@const group = instanceGroups.find(g => g.name === groupName)}
|
||||
{@const group = instanceGroups.find((g) => g.name === groupName)}
|
||||
<tr class="border-t border-gray-200 dark:border-gray-700">
|
||||
<td class="py-2">
|
||||
<div class="font-medium">{groupName}</div>
|
||||
@@ -647,7 +647,8 @@
|
||||
<Cell head>
|
||||
Added via
|
||||
<Tooltip>
|
||||
Shows how the user was added to the workspace: manually, via domain auto-invite, or through an instance group.
|
||||
Shows how the user was added to the workspace: manually, via domain auto-invite, or
|
||||
through an instance group.
|
||||
</Tooltip>
|
||||
</Cell>
|
||||
{/if}
|
||||
@@ -674,9 +675,7 @@
|
||||
{#if hasNonManualUsers && index > 0 && sortedUsers()[index - 1]?.added_via?.source !== 'instance_group' && added_via?.source === 'instance_group'}
|
||||
<tr class="bg-surface-secondary">
|
||||
<td colspan={hasNonManualUsers ? 8 : 7} class="px-4 py-2">
|
||||
<div class="text-xs text-tertiary font-bold">
|
||||
Instance group users
|
||||
</div>
|
||||
<div class="text-xs text-tertiary font-bold"> Instance group users </div>
|
||||
</td>
|
||||
</tr>
|
||||
{/if}
|
||||
@@ -807,7 +806,10 @@
|
||||
>
|
||||
Remove
|
||||
</Button>
|
||||
<Tooltip>Cannot remove users synced from instance groups. Either disable the user or remove them from the SCIM group.</Tooltip>
|
||||
<Tooltip
|
||||
>Cannot remove users synced from instance groups. Either disable the user or
|
||||
remove them from the SCIM group.</Tooltip
|
||||
>
|
||||
</div>
|
||||
{:else if canConvertToGroup(user)}
|
||||
<Button
|
||||
@@ -1028,7 +1030,10 @@
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col w-full space-y-4">
|
||||
<span>Are you sure you want to remove this instance group from auto-add? This will not remove users already added from this group.</span>
|
||||
<span
|
||||
>Are you sure you want to remove this instance group from auto-add? This will not remove
|
||||
users already added from this group.</span
|
||||
>
|
||||
</div>
|
||||
</ConfirmationModal>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<script module lang="ts">
|
||||
export function inputBorderClass({
|
||||
error,
|
||||
forceFocus
|
||||
}: {
|
||||
error?: boolean
|
||||
forceFocus?: boolean
|
||||
} = {}) {
|
||||
return twMerge(
|
||||
'transition-colors border',
|
||||
forceFocus
|
||||
? '!border-nord-900 dark:!border-nord-900'
|
||||
: '!border-nord-400 dark:!border-nord-300 hover:!border-nord-900/50 hover:dark:!border-nord-900/50 focus:!border-nord-900 dark:focus:!border-nord-900',
|
||||
error
|
||||
? '!border-red-300 focus:!border-red-400 hover:!border-red-500 dark:!border-red-400/40 dark:hover:!border-red-600/40'
|
||||
: ''
|
||||
)
|
||||
}
|
||||
|
||||
export const inputBaseClass =
|
||||
'rounded-md focus:ring-0 no-default-style text-sm text-tertiary dark:text-secondary !bg-surface-secondary disabled:!bg-surface-disabled disabled:!border-none disabled:!text-hint disabled:cursor-not-allowed shadow-none py-2 px-3 placeholder-hint'
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import type { HTMLInputAttributes } from 'svelte/elements'
|
||||
import { twMerge } from 'tailwind-merge'
|
||||
|
||||
type Props = {
|
||||
inputProps?: HTMLInputAttributes
|
||||
value?: string
|
||||
class?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
export function focus() {
|
||||
inputEl?.focus()
|
||||
}
|
||||
|
||||
let inputEl: HTMLInputElement | undefined = $state()
|
||||
|
||||
let { inputProps, value = $bindable(), class: className = '', error }: Props = $props()
|
||||
</script>
|
||||
|
||||
<input
|
||||
{...inputProps}
|
||||
class={twMerge(
|
||||
inputBaseClass,
|
||||
'w-full',
|
||||
'[appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none',
|
||||
inputBorderClass({ error: !!error }),
|
||||
className
|
||||
)}
|
||||
bind:this={inputEl}
|
||||
bind:value
|
||||
/>
|
||||
@@ -377,7 +377,7 @@
|
||||
{/key}
|
||||
{:else}
|
||||
<span class="text-sm text-tertiary text-center mx-auto mt-2"
|
||||
>{`Select a trigger from the ${useVerticalTriggerBar ? 'left toolbar' : 'table'} or a create a new one`}</span
|
||||
>{`Select a trigger from the ${useVerticalTriggerBar ? 'left toolbar' : 'table'} or create a new one`}</span
|
||||
>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -231,7 +231,7 @@ export async function initializeVscode(caller?: string, htmlContainer?: HTMLElem
|
||||
],
|
||||
colors: {
|
||||
'editor.foreground': '#D8DEE9',
|
||||
'editor.background': '#272D38',
|
||||
'editor.background': '#00000000',
|
||||
'editor.selectionBackground': '#434C5ECC',
|
||||
'editor.lineHighlightBackground': '#3B4252',
|
||||
'editorCursor.foreground': '#D8DEE9',
|
||||
@@ -244,8 +244,11 @@ export async function initializeVscode(caller?: string, htmlContainer?: HTMLElem
|
||||
inherit: true,
|
||||
rules: [],
|
||||
colors: {
|
||||
'editorLineNumber.foreground': '#999',
|
||||
'editorGutter.background': '#F9FAFB'
|
||||
'editor.background': '#FFFFFF00',
|
||||
'editor.foreground': '#2d3748',
|
||||
'editorLineNumber.foreground': '#C2C9D1',
|
||||
'editorLineNumber.activeForeground': '#989DA5',
|
||||
'editorGutter.background': '#FFFFFF00'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -105,7 +105,8 @@
|
||||
code_completion_model,
|
||||
default_model,
|
||||
custom_prompts: Object.keys(custom_prompts).length > 0 ? custom_prompts : undefined,
|
||||
max_tokens_per_model: Object.keys(maxTokensPerModel).length > 0 ? maxTokensPerModel : undefined
|
||||
max_tokens_per_model:
|
||||
Object.keys(maxTokensPerModel).length > 0 ? maxTokensPerModel : undefined
|
||||
}
|
||||
await WorkspaceService.editCopilotConfig({
|
||||
workspace: $workspaceStore!,
|
||||
@@ -228,9 +229,9 @@
|
||||
: provider}
|
||||
initialValue={aiProviders[provider].resource_path}
|
||||
bind:value={
|
||||
() => aiProviders[provider].resource_path,
|
||||
() => aiProviders[provider].resource_path || undefined,
|
||||
(v) => {
|
||||
aiProviders[provider].resource_path = v
|
||||
aiProviders[provider].resource_path = v ?? ''
|
||||
onAiProviderChange(provider as AIProvider)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -399,8 +399,8 @@
|
||||
}}
|
||||
>
|
||||
{#snippet children({ item })}
|
||||
<ToggleButton value="operator" size="xs" label="Operator" {item} />
|
||||
<ToggleButton value="developer" size="xs" label="Developer" {item} />
|
||||
<ToggleButton value="operator" small label="Operator" {item} />
|
||||
<ToggleButton value="developer" small label="Developer" {item} />
|
||||
{/snippet}
|
||||
</ToggleButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
import Alert from '../common/alert/Alert.svelte'
|
||||
import { deepEqual } from 'fast-equals'
|
||||
import Popover from '../meltComponents/Popover.svelte'
|
||||
import TextInput from '../text_input/TextInput.svelte'
|
||||
|
||||
const DEFAULT_DUCKLAKE_CATALOG_NAME = 'ducklake_catalog'
|
||||
|
||||
@@ -229,7 +230,7 @@
|
||||
<code class="px-1 py-0.5 border rounded-md">ATTACH 'ducklake' AS dl;</code> shorthand
|
||||
</Tooltip>
|
||||
{/if}
|
||||
<input bind:value={ducklake.name} placeholder="Name" />
|
||||
<TextInput bind:value={ducklake.name} inputProps={{ placeholder: 'Name' }} />
|
||||
</Cell>
|
||||
<Cell>
|
||||
<div class="flex gap-4">
|
||||
@@ -289,9 +290,12 @@
|
||||
}
|
||||
}
|
||||
class="w-48"
|
||||
inputClass="!placeholder-primary"
|
||||
inputClass="!placeholder-secondary"
|
||||
/>
|
||||
<TextInput
|
||||
inputProps={{ placeholder: 'Data path (defaults to /)' }}
|
||||
bind:value={ducklake.storage.path}
|
||||
/>
|
||||
<input placeholder="Data path (defaults to /)" bind:value={ducklake.storage.path} />
|
||||
</div>
|
||||
</Cell>
|
||||
<Cell class="w-12">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { enterpriseLicense, workspaceStore } from '$lib/stores'
|
||||
import { emptyString, sendUserToast } from '$lib/utils'
|
||||
import { ChevronDown, Plus, Shield, X } from 'lucide-svelte'
|
||||
import { ChevronDown, Plus, Shield } from 'lucide-svelte'
|
||||
import Alert from '../common/alert/Alert.svelte'
|
||||
import Button from '../common/button/Button.svelte'
|
||||
import Tab from '../common/tabs/Tab.svelte'
|
||||
@@ -19,11 +19,12 @@
|
||||
import { WorkspaceService } from '$lib/gen'
|
||||
import S3FilePicker from '../S3FilePicker.svelte'
|
||||
import Portal from '../Portal.svelte'
|
||||
import { fade } from 'svelte/transition'
|
||||
import Popover from '../meltComponents/Popover.svelte'
|
||||
import ClearableInput from '../common/clearableInput/ClearableInput.svelte'
|
||||
import MultiSelect from '../select/MultiSelect.svelte'
|
||||
import CloseButton from '../common/CloseButton.svelte'
|
||||
import TextInput from '../text_input/TextInput.svelte'
|
||||
import Select from '../select/Select.svelte'
|
||||
|
||||
let { s3ResourceSettings = $bindable() }: { s3ResourceSettings: S3ResourceSettings } = $props()
|
||||
|
||||
@@ -114,10 +115,10 @@
|
||||
<div class="mt-6">
|
||||
<div class="flex mt-2 flex-col gap-y-4 max-w-5xl">
|
||||
{#each s3ResourceSettings.secondaryStorage ?? [] as _, idx}
|
||||
<div class="flex gap-1 items-center">
|
||||
<input
|
||||
<div class="flex gap-1 relative">
|
||||
<TextInput
|
||||
class="max-w-[200px]"
|
||||
type="text"
|
||||
inputProps={{ type: 'text', placeholder: 'Storage name' }}
|
||||
bind:value={
|
||||
() => s3ResourceSettings.secondaryStorage?.[idx]?.[0] || '',
|
||||
(v) => {
|
||||
@@ -126,9 +127,8 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
placeholder="Storage name"
|
||||
/>
|
||||
<select
|
||||
<Select
|
||||
class="max-w-[125px]"
|
||||
bind:value={
|
||||
() => s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourceType || 's3',
|
||||
@@ -138,19 +138,19 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<option value="s3">S3</option>
|
||||
<option value="azure_blob">Azure Blob</option>
|
||||
<option value="s3_aws_oidc">AWS OIDC</option>
|
||||
<option value="azure_workload_identity">Azure Workload Identity</option>
|
||||
<option value="gcloud_storage">Google Cloud Storage</option>
|
||||
</select>
|
||||
<!-- this can be removed once parent moves to runes -->
|
||||
<!-- svelte-ignore binding_property_non_reactive -->
|
||||
items={[
|
||||
{ value: 's3', label: 'S3' },
|
||||
{ value: 'azure_blob', label: 'Azure Blob' },
|
||||
{ value: 's3_aws_oidc', label: 'AWS OIDC' },
|
||||
{ value: 'azure_workload_identity', label: 'Azure Workload Identity' },
|
||||
{ value: 'gcloud_storage', label: 'Google Cloud Storage' }
|
||||
]}
|
||||
/>
|
||||
|
||||
<ResourcePicker
|
||||
resourceType={s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourceType || 's3'}
|
||||
bind:value={
|
||||
() => s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourcePath || '',
|
||||
() => s3ResourceSettings.secondaryStorage?.[idx]?.[1].resourcePath || undefined,
|
||||
(v) => {
|
||||
if (s3ResourceSettings.secondaryStorage?.[idx]) {
|
||||
s3ResourceSettings.secondaryStorage[idx][1].resourcePath = v
|
||||
@@ -173,19 +173,16 @@
|
||||
}
|
||||
}}>Browse content (save first)</Button
|
||||
>
|
||||
<button
|
||||
transition:fade|local={{ duration: 100 }}
|
||||
class="rounded-full p-1 bg-surface-secondary duration-200 hover:bg-surface-hover ml-2"
|
||||
aria-label="Clear"
|
||||
onclick={() => {
|
||||
<CloseButton
|
||||
class="my-auto"
|
||||
small
|
||||
on:close={() => {
|
||||
if (s3ResourceSettings.secondaryStorage) {
|
||||
s3ResourceSettings.secondaryStorage.splice(idx, 1)
|
||||
s3ResourceSettings.secondaryStorage = [...s3ResourceSettings.secondaryStorage]
|
||||
}
|
||||
}}
|
||||
>
|
||||
<X size={14} />
|
||||
</button>
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
<div class="flex gap-1">
|
||||
@@ -229,7 +226,7 @@
|
||||
{#snippet permissionBtn(storage: NonNullable<S3ResourceSettings['secondaryStorage']>[number][1])}
|
||||
<Popover closeOnOtherPopoverOpen placement="left">
|
||||
<svelte:fragment slot="trigger">
|
||||
<Button variant="border" btnClasses="px-2.5" color="dark" size="sm">
|
||||
<Button variant="border" wrapperClasses="h-full" btnClasses="px-2.5" color="dark" size="sm">
|
||||
<Shield size={16} /> Permissions <ChevronDown size={14} />
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -953,6 +953,7 @@
|
||||
{#if minTs || maxTs}
|
||||
<input
|
||||
type="text"
|
||||
class="!text-sm text-tertiary !bg-surface-secondary h-9 !border-none"
|
||||
value={minTs ? new Date(minTs).toLocaleString() : 'zoom x axis to set min'}
|
||||
disabled
|
||||
name="min-datetimes"
|
||||
@@ -984,6 +985,7 @@
|
||||
{#if maxTs || minTs}
|
||||
<input
|
||||
type="text"
|
||||
class="!text-sm text-tertiary !bg-surface-secondary h-9 !border-none"
|
||||
value={maxTs ? new Date(maxTs).toLocaleString() : 'zoom x axis to set max'}
|
||||
name="max-datetimes"
|
||||
disabled
|
||||
@@ -1013,7 +1015,9 @@
|
||||
|
||||
{#if minTs || maxTs}
|
||||
<RunOption label="Reset" for="reset" noLabel>
|
||||
<Button color="light" variant="border" size="xs" onClick={reset}>Reset</Button>
|
||||
<Button color="light" variant="border" size="xs" onClick={reset} btnClasses="h-9">
|
||||
Reset
|
||||
</Button>
|
||||
</RunOption>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -14,6 +14,7 @@ const lightTheme = {
|
||||
textPrimary: '#2d3748',
|
||||
textSecondary: '#4a5568',
|
||||
textTertiary: '#505c70',
|
||||
textHint: '#757E8F',
|
||||
textDisabled: '#a0aec0',
|
||||
|
||||
border: '#dddddd',
|
||||
@@ -32,6 +33,7 @@ const darkTheme = {
|
||||
textPrimary: '#EEEEEE',
|
||||
textSecondary: '#C2C9D1',
|
||||
textTertiary: '#A8AEB7',
|
||||
textHint: '#989DA5',
|
||||
textDisabled: '#989DA5',
|
||||
|
||||
border: '#3e4c60',
|
||||
@@ -413,6 +415,7 @@ const config = {
|
||||
primary: 'rgb(var(--color-text-primary) / <alpha-value>)',
|
||||
secondary: 'rgb(var(--color-text-secondary) / <alpha-value>)',
|
||||
tertiary: 'rgb(var(--color-text-tertiary) / <alpha-value>)',
|
||||
hint: 'rgb(var(--color-text-hint) / <alpha-value>)',
|
||||
disabled: 'rgb(var(--color-text-disabled) / <alpha-value>)',
|
||||
|
||||
'surface-inverse': 'rgb(var(--color-surface-inverse) / <alpha-value>)',
|
||||
@@ -424,6 +427,7 @@ const config = {
|
||||
'primary-inverse': 'rgb(var(--color-text-primary-inverse) / <alpha-value>)',
|
||||
'secondary-inverse': 'rgb(var(--color-text-secondary-inverse) / <alpha-value>)',
|
||||
'tertiary-inverse': 'rgb(var(--color-text-tertiary-inverse) / <alpha-value>)',
|
||||
'hint-inverse': 'rgb(var(--color-text-hint-inverse) / <alpha-value>)',
|
||||
'disabled-inverse': 'rgb(var(--color-text-disabled-inverse) / <alpha-value>)'
|
||||
},
|
||||
fontFamily: {
|
||||
@@ -519,6 +523,7 @@ const config = {
|
||||
'--color-text-primary': lightThemeRgb.textPrimary,
|
||||
'--color-text-secondary': lightThemeRgb.textSecondary,
|
||||
'--color-text-tertiary': lightThemeRgb.textTertiary,
|
||||
'--color-text-hint': lightThemeRgb.textHint,
|
||||
'--color-text-disabled': lightThemeRgb.textDisabled,
|
||||
|
||||
'--color-surface-inverse': darkThemeRgb.surface,
|
||||
@@ -530,6 +535,7 @@ const config = {
|
||||
'--color-text-primary-inverse': darkThemeRgb.textPrimary,
|
||||
'--color-text-secondary-inverse': darkThemeRgb.textSecondary,
|
||||
'--color-text-tertiary-inverse': darkThemeRgb.textTertiary,
|
||||
'--color-text-hint-inverse': darkThemeRgb.textHint,
|
||||
'--color-text-disabled-inverse': darkThemeRgb.textDisabled,
|
||||
|
||||
'--color-border': lightThemeRgb.border,
|
||||
@@ -560,6 +566,7 @@ const config = {
|
||||
'--color-text-primary': darkThemeRgb.textPrimary,
|
||||
'--color-text-secondary': darkThemeRgb.textSecondary,
|
||||
'--color-text-tertiary': darkThemeRgb.textTertiary,
|
||||
'--color-text-hint': darkThemeRgb.textHint,
|
||||
'--color-text-disabled': darkThemeRgb.textDisabled,
|
||||
|
||||
'--color-surface-inverse': lightThemeRgb.surface,
|
||||
@@ -571,6 +578,7 @@ const config = {
|
||||
'--color-text-primary-inverse': lightThemeRgb.textPrimary,
|
||||
'--color-text-secondary-inverse': lightThemeRgb.textSecondary,
|
||||
'--color-text-tertiary-inverse': lightThemeRgb.textTertiary,
|
||||
'--color-text-hint-inverse': lightThemeRgb.textHint,
|
||||
'--color-text-disabled-inverse': lightThemeRgb.textDisabled,
|
||||
|
||||
'--color-border': darkThemeRgb.border,
|
||||
@@ -661,7 +669,7 @@ const config = {
|
||||
".dark [type='checkbox']:checked": {
|
||||
backgroundImage: `url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")`
|
||||
},
|
||||
'input:not(.windmillapp),input[type="text"]:not(.windmillapp),input[type="email"]:not(.windmillapp),input[type="url"]:not(.windmillapp),input[type="password"]:not(.windmillapp),input[type="number"]:not(.windmillapp),input[type="date"]:not(.windmillapp),input[type="datetime-local"]:not(.windmillapp),input[type="month"]:not(.windmillapp),input[type="search"]:not(.windmillapp),input[type="tel"]:not(.windmillapp),input[type="time"]:not(.windmillapp),input[type="week"]:not(.windmillapp),textarea:not(.windmillapp):not(.monaco-mouse-cursor-text),select:not(.windmillapp)':
|
||||
'input:not(.windmillapp):not(.no-default-style),input[type="text"]:not(.windmillapp):not(.no-default-style),input[type="email"]:not(.windmillapp):not(.no-default-style),input[type="url"]:not(.windmillapp):not(.no-default-style),input[type="password"]:not(.windmillapp):not(.no-default-style),input[type="number"]:not(.windmillapp):not(.no-default-style),input[type="date"]:not(.windmillapp):not(.no-default-style),input[type="datetime-local"]:not(.windmillapp):not(.no-default-style),input[type="month"]:not(.windmillapp):not(.no-default-style),input[type="search"]:not(.windmillapp):not(.no-default-style),input[type="tel"]:not(.windmillapp):not(.no-default-style),input[type="time"]:not(.windmillapp):not(.no-default-style),input[type="week"]:not(.windmillapp):not(.no-default-style),textarea:not(.windmillapp):not(.no-default-style):not(.monaco-mouse-cursor-text),select:not(.windmillapp):not(.no-default-style)':
|
||||
{
|
||||
display: 'block',
|
||||
fontSize: theme('fontSize.sm'),
|
||||
@@ -686,7 +694,7 @@ const config = {
|
||||
}
|
||||
}
|
||||
},
|
||||
'.dark input:not(.windmillapp),.dark input[type="text"]:not(.windmillapp),.dark input[type="email"]:not(.windmillapp),.dark input[type="url"]:not(.windmillapp),.dark input[type="password"]:not(.windmillapp),.dark input[type="number"]:not(.windmillapp),.dark input[type="date"]:not(.windmillapp),.dark input[type="datetime-local"]:not(.windmillapp),.dark input[type="month"]:not(.windmillapp),.dark input[type="search"]:not(.windmillapp),.dark input[type="tel"]:not(.windmillapp),.dark input[type="time"]:not(.windmillapp),.dark input[type="week"]:not(.windmillapp),.dark textarea:not(.windmillapp):not(.monaco-mouse-cursor-text),.dark select:not(.windmillapp)':
|
||||
'.dark input:not(.windmillapp):not(.no-default-style),.dark input[type="text"]:not(.windmillapp):not(.no-default-style),.dark input[type="email"]:not(.windmillapp):not(.no-default-style),.dark input[type="url"]:not(.windmillapp):not(.no-default-style),.dark input[type="password"]:not(.windmillapp):not(.no-default-style),.dark input[type="number"]:not(.windmillapp):not(.no-default-style),.dark input[type="date"]:not(.windmillapp):not(.no-default-style),.dark input[type="datetime-local"]:not(.windmillapp):not(.no-default-style),.dark input[type="month"]:not(.windmillapp):not(.no-default-style),.dark input[type="search"]:not(.windmillapp):not(.no-default-style),.dark input[type="tel"]:not(.windmillapp):not(.no-default-style),.dark input[type="time"]:not(.windmillapp):not(.no-default-style),.dark input[type="week"]:not(.windmillapp):not(.no-default-style),.dark textarea:not(.windmillapp):not(.no-default-style):not(.monaco-mouse-cursor-text),.dark select:not(.windmillapp):not(.no-default-style)':
|
||||
{
|
||||
backgroundColor: theme('colors.gray.700'),
|
||||
color: theme('colors.gray.200'),
|
||||
|
||||
Reference in New Issue
Block a user