fix: rework multiselect as app component (#1599)

This commit is contained in:
Ruben Fiszel
2023-05-18 16:28:58 +02:00
committed by GitHub
parent ad45e3dde3
commit 811501d277
17 changed files with 219 additions and 462 deletions
@@ -182,6 +182,7 @@ static PYTHON_IMPORTS_REPLACEMENT: phf::Map<&'static str, &'static str> = phf_ma
"shopify" => "ShopifyAPI",
"seleniumwire" => "selenium-wire",
"openbb-terminal" => "openbb[all]",
"riskfolio" => "riskfolio-lib",
};
fn replace_import(x: String) -> String {
+9 -10
View File
@@ -7,7 +7,6 @@
"": {
"name": "windmill",
"version": "1.101.1",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
"@aws-crypto/sha256-js": "^4.0.0",
@@ -76,7 +75,7 @@
"svelte-awesome-color-picker": "^2.4.3",
"svelte-check": "^3.3.2",
"svelte-highlight": "^7.3.0",
"svelte-multiselect": "^8.6.0",
"svelte-multiselect": "^8.6.2",
"svelte-overlay": "^1.4.1",
"svelte-popperjs": "^1.3.2",
"svelte-preprocess": "^5.0.1",
@@ -6482,12 +6481,12 @@
}
},
"node_modules/svelte-multiselect": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/svelte-multiselect/-/svelte-multiselect-8.6.0.tgz",
"integrity": "sha512-ce1axNn5YrvDwpUA1R4pSKC7oCD8buP7If61VRoOEM5cxMU8t14tLp5fJf27qd8FBJ7vzvJokiN9BUl1u8oL6w==",
"version": "8.6.2",
"resolved": "https://registry.npmjs.org/svelte-multiselect/-/svelte-multiselect-8.6.2.tgz",
"integrity": "sha512-lR7zc/B6yAi9oZxIZyuv8q2VTRDS9aT84HRjHiEcmWD7vbz64pfbmKJZgCUcH5oCtr0wN1NfYymXQvRMfolZkQ==",
"dev": true,
"dependencies": {
"svelte": "^3.57.0"
"svelte": "^3.59.1"
}
},
"node_modules/svelte-overlay": {
@@ -12040,12 +12039,12 @@
"requires": {}
},
"svelte-multiselect": {
"version": "8.6.0",
"resolved": "https://registry.npmjs.org/svelte-multiselect/-/svelte-multiselect-8.6.0.tgz",
"integrity": "sha512-ce1axNn5YrvDwpUA1R4pSKC7oCD8buP7If61VRoOEM5cxMU8t14tLp5fJf27qd8FBJ7vzvJokiN9BUl1u8oL6w==",
"version": "8.6.2",
"resolved": "https://registry.npmjs.org/svelte-multiselect/-/svelte-multiselect-8.6.2.tgz",
"integrity": "sha512-lR7zc/B6yAi9oZxIZyuv8q2VTRDS9aT84HRjHiEcmWD7vbz64pfbmKJZgCUcH5oCtr0wN1NfYymXQvRMfolZkQ==",
"dev": true,
"requires": {
"svelte": "^3.57.0"
"svelte": "^3.59.1"
}
},
"svelte-overlay": {
+1 -1
View File
@@ -54,7 +54,7 @@
"svelte-awesome-color-picker": "^2.4.3",
"svelte-check": "^3.3.2",
"svelte-highlight": "^7.3.0",
"svelte-multiselect": "^8.6.0",
"svelte-multiselect": "^8.6.2",
"svelte-overlay": "^1.4.1",
"svelte-popperjs": "^1.3.2",
"svelte-preprocess": "^5.0.1",
@@ -1,326 +0,0 @@
<script>
// @ts-nocheck
import { onMount } from 'svelte'
import { fly } from 'svelte/transition'
export let id = ''
export let value = []
export let readonly = false
export let placeholder = ''
let input,
inputValue,
options = [],
activeOption,
showOptions = false,
selected = {},
first = true,
slot
const iconClearPath =
'M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'
onMount(() => {
slot.querySelectorAll('option').forEach((o) => {
o.selected && !value.includes(o.value) && (value = [...value, o.value])
options = [...options, { value: o.value, name: o.textContent }]
})
value &&
(selected = options.reduce(
(obj, op) => (value.includes(op.value) ? { ...obj, [op.value]: op } : obj),
{}
))
first = false
})
$: if (!first) value = Object.values(selected).map((o) => o.value)
$: filtered = options.filter((o) =>
inputValue ? o.name.toLowerCase().includes(inputValue.toLowerCase()) : o
)
$: if ((activeOption && !filtered.includes(activeOption)) || (!activeOption && inputValue))
activeOption = filtered[0]
function add(token) {
if (!readonly) selected[token.value] = token
}
function remove(value) {
if (!readonly) {
const { [value]: val, ...rest } = selected
selected = rest
}
}
function optionsVisibility(show) {
if (readonly) return
if (typeof show === 'boolean') {
showOptions = show
show && input.focus()
} else {
showOptions = !showOptions
}
if (!showOptions) {
activeOption = undefined
}
}
function handleKeyup(e) {
if (e.keyCode === 13) {
Object.keys(selected).includes(activeOption.value)
? remove(activeOption.value)
: add(activeOption)
inputValue = ''
}
if ([38, 40].includes(e.keyCode)) {
// up and down arrows
const increment = e.keyCode === 38 ? -1 : 1
const calcIndex = filtered.indexOf(activeOption) + increment
activeOption =
calcIndex < 0
? filtered[filtered.length - 1]
: calcIndex === filtered.length
? filtered[0]
: filtered[calcIndex]
}
}
function handleBlur() {
optionsVisibility(false)
}
function handleTokenClick(e) {
if (e.target.closest('.token-remove')) {
e.stopPropagation()
remove(e.target.closest('.token').dataset.id)
} else if (e.target.closest('.remove-all')) {
selected = []
inputValue = ''
} else {
optionsVisibility(true)
}
}
function handleOptionMousedown(e) {
const value = e.target.dataset.value
if (selected[value]) {
remove(value)
} else {
add(options.filter((o) => o.value === value)[0])
input.focus()
}
}
</script>
<div class="multiselect" class:readonly>
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div class="tokens" class:showOptions on:click={handleTokenClick}>
{#each Object.values(selected) as s}
<div class="token" data-id={s.value}>
<span>{s.name}</span>
{#if !readonly}
<div class="token-remove" title="Remove {s.name}">
<svg
class="icon-clear"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
>
<path d={iconClearPath} />
</svg>
</div>
{/if}
</div>
{/each}
<div class="actions">
{#if !readonly}
<input
{id}
autocomplete="off"
bind:value={inputValue}
bind:this={input}
on:keyup={handleKeyup}
on:blur={handleBlur}
{placeholder}
/>
<div class="remove-all" title="Remove All" class:hidden={!Object.keys(selected).length}>
<svg
class="icon-clear"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24"
>
<path d={iconClearPath} />
</svg>
</div>
<svg
class="dropdown-arrow"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 18 18"><path d="M5 8l4 4 4-4z" /></svg
>
{/if}
</div>
</div>
<select bind:this={slot} type="multiple" class="hidden"><slot /></select>
{#if showOptions}
<ul
class="options"
transition:fly|local={{ duration: 200, y: 5 }}
on:mousedown|preventDefault={handleOptionMousedown}
>
{#each filtered as option}
<li
class:selected={selected[option.value]}
class:active={activeOption === option}
data-value={option.value}
>
{option.name}
</li>
{/each}
</ul>
{/if}
</div>
<style>
.multiselect {
background-color: white;
border-bottom: 1px solid hsl(0, 0%, 70%);
position: relative;
}
.multiselect:not(.readonly):hover {
border-bottom-color: hsl(0, 0%, 50%);
}
.tokens {
align-items: center;
display: flex;
flex-wrap: wrap;
position: relative;
}
.tokens::after {
background: none repeat scroll 0 0 transparent;
bottom: -1px;
content: '';
display: block;
height: 2px;
left: 50%;
position: absolute;
background: hsl(45, 100%, 51%);
transition: width 0.3s ease 0s, left 0.3s ease 0s;
width: 0;
}
.tokens.showOptions::after {
width: 100%;
left: 0;
}
.token {
align-items: center;
background-color: hsl(214, 17%, 92%);
border-radius: 1.25rem;
display: flex;
margin: 0.25rem 0.5rem 0.25rem 0;
max-height: 1.3rem;
padding: 0.25rem 0.5rem 0.25rem 0.5rem;
transition: background-color 0.3s;
white-space: nowrap;
}
.token:hover {
background-color: hsl(214, 15%, 88%);
}
.readonly .token {
color: hsl(0, 0%, 40%);
}
.token-remove,
.remove-all {
align-items: center;
background-color: hsl(214, 15%, 55%);
border-radius: 50%;
color: hsl(214, 17%, 92%);
display: flex;
justify-content: center;
height: 1.25rem;
margin-left: 0.25rem;
min-width: 1.25rem;
}
.token-remove:hover,
.remove-all:hover {
background-color: hsl(215, 21%, 43%);
cursor: pointer;
}
.actions {
align-items: center;
display: flex;
flex: 1;
min-width: 15rem;
}
input {
border: none;
font-size: 1.5rem;
line-height: 1.5rem;
margin: 0;
outline: none;
padding: 0;
width: 100%;
}
.dropdown-arrow path {
fill: hsl(0, 0%, 70%);
}
.multiselect:hover .dropdown-arrow path {
fill: hsl(0, 0%, 50%);
}
.icon-clear path {
fill: white;
}
.options {
box-shadow: 0px 2px 4px rgba(0, 0, 0, 0.1), 0px -2px 4px rgba(0, 0, 0, 0.1);
left: 0;
list-style: none;
margin-block-end: 0;
margin-block-start: 0;
max-height: 70vh;
overflow: auto;
padding-inline-start: 0;
position: absolute;
top: calc(100% + 1px);
width: 100%;
}
li {
background-color: white;
cursor: pointer;
padding: 0.5rem;
}
li:last-child {
border-bottom-left-radius: 0.2rem;
border-bottom-right-radius: 0.2rem;
}
li:not(.selected):hover {
background-color: hsl(214, 17%, 92%);
}
li.selected {
background-color: hsl(232, 54%, 41%);
color: white;
}
li.selected:nth-child(even) {
background-color: hsl(232, 50%, 45%);
color: white;
}
li.active {
background-color: hsl(214, 17%, 88%);
}
li.selected.active,
li.selected:hover {
background-color: hsl(232, 48%, 50%);
}
.hidden {
display: none;
}
</style>
@@ -22,6 +22,7 @@
import { initConfig, initOutput } from '$lib/components/apps/editor/appUtils'
import ResolveConfig from '../../helpers/ResolveConfig.svelte'
import AppCheckbox from '../../inputs/AppCheckbox.svelte'
import AppSelect from '../../inputs/AppSelect.svelte'
export let id: string
export let componentInput: AppInput | undefined
@@ -297,7 +298,7 @@
on:keypress={() => toggleRow(row, rowIndex)}
on:click={() => toggleRow(row, rowIndex)}
>
<div class="center-center h-full w-full flex-wrap gap-1">
<div class="center-center h-full w-full flex-wrap gap-1.5">
{#each actionButtons as actionButton, actionIndex (actionButton?.id)}
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
<div
@@ -387,6 +388,19 @@
}}
{controls}
/>
{:else if actionButton.type == 'selectcomponent'}
<AppSelect
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
{controls}
/>
{/if}
{:else if actionButton.type == 'buttoncomponent'}
<AppButton
@@ -415,6 +429,18 @@
toggleRow(row, rowIndex)
}}
/>
{:else if actionButton.type == 'selectcomponent'}
<AppSelect
extraKey={'idx' + rowIndex}
{render}
id={actionButton.id}
customCss={actionButton.customCss}
configuration={actionButton.configuration}
recomputeIds={actionButton.recomputeIds}
preclickAction={async () => {
toggleRow(row, rowIndex)
}}
/>
{/if}
</div>
{/each}
@@ -6,6 +6,7 @@
export let horizontalAlignment: HorizontalAlignment | undefined = undefined
export let verticalAlignment: VerticalAlignment | undefined = undefined
export let noWFull = false
export let hFull = false
let c = ''
export { c as class }
export let style = ''
@@ -16,7 +17,7 @@
noWFull ? '' : 'w-full',
tailwindHorizontalAlignment(horizontalAlignment),
tailwindVerticalAlignment(verticalAlignment),
verticalAlignment ? 'h-full' : '',
verticalAlignment || hFull ? 'h-full' : '',
c
)
</script>
@@ -1,12 +1,13 @@
<script lang="ts">
import Toggle from '$lib/components/Toggle.svelte'
import { getContext } from 'svelte'
import { initOutput } from '../../editor/appUtils'
import { initConfig, initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import { components } from '../../editor/component'
export let id: string
export let configuration: RichConfigurations
@@ -24,13 +25,15 @@
const { app, worldStore, componentControl, runnableComponents } =
getContext<AppViewerContext>('AppViewerContext')
let resolvedConfig = initConfig(
components['checkboxcomponent'].initialData.configuration,
configuration
)
if (controls) {
$componentControl[id] = controls
}
let defaultValue: boolean | undefined = undefined
let labelValue: string = ''
// As the checkbox is a special case and has no input
// we need to manually set the output
@@ -38,22 +41,28 @@
result: false
})
$: defaultValue != undefined && outputs?.result.set(defaultValue)
$: resolvedConfig.defaultValue != undefined && outputs?.result.set(resolvedConfig.defaultValue)
$: css = concatCustomCss($app.css?.checkboxcomponent, customCss)
</script>
<InputValue {id} input={configuration.label} bind:value={labelValue} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
{#each Object.keys(components['checkboxcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{extraKey}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<InitializeComponent {id} />
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<Toggle
size="sm"
{extraKey}
checked={defaultValue}
options={{ right: labelValue }}
checked={resolvedConfig.defaultValue}
options={{ right: resolvedConfig.label }}
textClass={css?.text?.class ?? ''}
textStyle={css?.text?.style ?? ''}
on:change={(e) => {
@@ -1,69 +1,72 @@
<script lang="ts">
import { getContext } from 'svelte'
import Select from 'svelte-select'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { initOutput } from '../../editor/appUtils'
// import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { initConfig, initOutput } from '../../editor/appUtils'
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import { components } from '../../editor/component'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
// @ts-ignore
import MultiSelect from 'svelte-multiselect'
export let id: string
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'multiselectcomponent'> | undefined = undefined
export let render: boolean
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
let items: { label: string; value: string }[]
let placeholder: string = 'Select an item'
const { app, worldStore, selectedComponent } = getContext<AppViewerContext>('AppViewerContext')
let items: string[]
let outputs = initOutput($worldStore, id, {
result: [] as string[]
})
let resolvedConfig = initConfig(
components['multiselectcomponent'].initialData.configuration,
configuration
)
// $: outputs && handleOutputs()
// function handleOutputs() {
// value = outputs.result.peak()
// }
let value: { value: string }[] | undefined = outputs?.result.peak()
let value: string[] | undefined = outputs?.result.peak()
$: labels && handleItems()
let labels: string[] | undefined = []
$: resolvedConfig.items && handleItems()
function handleItems() {
if (Array.isArray(labels)) {
items = labels?.map((label) => {
const stringLabel = typeof label === 'string' ? label : `NOT_STRING`
return {
label: stringLabel,
value: label
}
if (Array.isArray(resolvedConfig.items)) {
items = resolvedConfig.items?.map((label) => {
return typeof label === 'string' ? label : `NOT_STRING`
})
}
}
$: value ? outputs?.result.set(value.map((v) => v.value)) : outputs?.result.set([])
$: value ? outputs?.result.set(value) : outputs?.result.set([])
$: css = concatCustomCss($app.css?.multiselectcomponent, customCss)
$: outerDiv && css?.multiselect?.style && outerDiv.setAttribute('style', css?.multiselect?.style)
let outerDiv: HTMLDivElement | undefined = undefined
</script>
<InputValue {id} input={configuration.items} bind:value={labels} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
{#each Object.keys(components['multiselectcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<InitializeComponent {id} />
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<AlignWrapper {render} hFull>
<div
class="app-select w-full"
style="height: 100%; overflow: auto;"
class="app-select w-full h-full"
on:pointerdown={(e) => {
if (!e.shiftKey) {
e.stopPropagation()
@@ -71,30 +74,16 @@
}}
>
{#if !value || Array.isArray(value)}
<Select
--border-radius="0"
--border-color="#999"
multiple
on:change={(e) => e.stopPropagation()}
{items}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={'border-color: #999; min-height: 100%;' +
SELECT_INPUT_DEFAULT_STYLE.containerStyles +
css?.input?.style}
bind:value
{placeholder}
on:click={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
}}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
}}
floatingConfig={{
strategy: 'fixed'
<MultiSelect
bind:outerDiv
outerDivClass={' h-full'}
ulSelectedClass={`${resolvedConfig.allowOverflow ? '' : 'overflow-auto'} max-h-full`}
bind:selected={value}
options={items}
placeholder={resolvedConfig.placeholder}
allowUserOptions={resolvedConfig.create}
on:open={() => {
$selectedComponent = [id]
}}
/>
{:else}
@@ -106,6 +95,7 @@
<style global>
.app-select .value-container {
padding: 0 !important;
overflow: auto;
}
.svelte-select-list {
z-index: 1000 !important;
@@ -5,22 +5,40 @@
import type { AppViewerContext, ComponentCustomCSS, RichConfigurations } from '../../types'
import { concatCustomCss } from '../../utils'
import AlignWrapper from '../helpers/AlignWrapper.svelte'
import InputValue from '../helpers/InputValue.svelte'
import { SELECT_INPUT_DEFAULT_STYLE } from '../../../../defaults'
import { initOutput } from '../../editor/appUtils'
import { initConfig, initOutput } from '../../editor/appUtils'
import InitializeComponent from '../helpers/InitializeComponent.svelte'
import ResolveConfig from '../helpers/ResolveConfig.svelte'
import { components } from '../../editor/component'
export let id: string
export let configuration: RichConfigurations
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'selectcomponent'> | undefined = undefined
export let render: boolean
export let extraKey: string | undefined = undefined
export let preclickAction: (() => Promise<void>) | undefined = undefined
export let recomputeIds: string[] | undefined = undefined
export let controls: { left: () => boolean; right: () => boolean | string } | undefined =
undefined
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppViewerContext>('AppViewerContext')
let items: { label: string; value: any; created?: boolean }[]
let placeholder: string = 'Select an item'
const {
app,
worldStore,
connectingInput,
selectedComponent,
runnableComponents,
componentControl
} = getContext<AppViewerContext>('AppViewerContext')
if (controls) {
$componentControl[id] = controls
}
let resolvedConfig = initConfig(
components['selectcomponent'].initialData.configuration,
configuration
)
let outputs = initOutput($worldStore, id, {
result: undefined as string | undefined
@@ -28,13 +46,13 @@
let value: string | undefined = outputs?.result.peak()
$: items && handleItems()
$: resolvedConfig.items && handleItems()
let listItems: { label: string; value: string; created?: boolean }[] = []
function handleItems() {
listItems = Array.isArray(items)
? items.map((item) => {
listItems = Array.isArray(resolvedConfig.items)
? resolvedConfig.items.map((item) => {
return {
label: item.label,
value: JSON.stringify(item.value)
@@ -42,10 +60,10 @@
})
: []
let rawValue
if (defaultValue !== undefined) {
rawValue = defaultValue
if (resolvedConfig.defaultValue !== undefined) {
rawValue = resolvedConfig.defaultValue
} else if (listItems.length > 0) {
rawValue = items[0].value
rawValue = resolvedConfig.items[0].value
}
if (rawValue !== undefined) {
value = JSON.stringify(rawValue)
@@ -56,27 +74,29 @@
function onChange(e: CustomEvent) {
e?.stopPropagation()
if (create) {
if (resolvedConfig.create) {
listItems = listItems.map((i) => {
delete i.created
return i
})
}
preclickAction?.()
let result: any = undefined
try {
result = JSON.parse(e.detail?.['value'])
} catch (_) {}
value = e.detail?.['value']
outputs?.result.set(result)
if (recomputeIds) {
recomputeIds.forEach((id) => $runnableComponents?.[id]?.cb())
}
}
$: css = concatCustomCss($app.css?.selectcomponent, customCss)
let defaultValue: any = undefined
function handleFilter(e) {
if (create) {
if (resolvedConfig.create) {
if (e.detail.length === 0 && filterText.length > 0) {
const prev = listItems.filter((i) => !i.created)
listItems = [
@@ -87,26 +107,29 @@
}
}
$: defaultValue && handleDefault()
$: resolvedConfig.defaultValue && handleDefault()
function handleDefault() {
if (defaultValue) {
value = JSON.stringify(defaultValue)
outputs?.result.set(defaultValue)
if (resolvedConfig.defaultValue) {
value = JSON.stringify(resolvedConfig.defaultValue)
outputs?.result.set(resolvedConfig.defaultValue)
}
}
let create = false
let filterText = ''
</script>
<InputValue {id} input={configuration.items} bind:value={items} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputValue {id} input={configuration.create} bind:value={create} />
{#each Object.keys(components['selectcomponent'].initialData.configuration) as key (key)}
<ResolveConfig
{id}
{extraKey}
{key}
bind:resolvedConfig={resolvedConfig[key]}
configuration={configuration[key]}
/>
{/each}
<InitializeComponent {id} />
<AlignWrapper {render} {horizontalAlignment} {verticalAlignment}>
<AlignWrapper {render} {verticalAlignment}>
<div
class="app-select w-full"
style="height: 34px;"
@@ -124,23 +147,21 @@
on:clear={onChange}
on:change={onChange}
items={listItems}
listAutoWidth={false}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={'border-color: #999;' +
SELECT_INPUT_DEFAULT_STYLE.containerStyles +
css?.input?.style}
{value}
{placeholder}
placeholder={resolvedConfig.placeholder}
on:focus={() => {
if (!$connectingInput.opened) {
$selectedComponent = [id]
}
}}
>
<div slot="item" let:item>
{#if create}
{item.created ? 'Add new: ' : ''}
{/if}
{item.label}
<div slot="item" let:item
>{#if resolvedConfig.create}{item.created ? 'Add new: ' : ''}{/if}{item.label}
</div>
</Select>
</div>
@@ -252,9 +252,9 @@
/>
{:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'}
<AppSelect
recomputeIds={component.recomputeIds}
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
{render}
@@ -262,8 +262,6 @@
{:else if component.type === 'multiselectcomponent'}
<AppMultiSelect
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
{render}
@@ -90,8 +90,9 @@ export type AggridComponent = BaseComponent<'aggridcomponent'>
export type DisplayComponent = BaseComponent<'displaycomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'resourceselectcomponent'>
export type ResourceSelectComponent = BaseComponent<'selectcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'> & RecomputeOthersSource
export type ResourceSelectComponent = BaseComponent<'resourceselectcomponent'> &
RecomputeOthersSource
export type MultiSelectComponent = BaseComponent<'multiselectcomponent'>
export type CheckboxComponent = BaseComponent<'checkboxcomponent'> & RecomputeOthersSource
export type RadioComponent = BaseComponent<'radiocomponent'>
@@ -470,7 +471,7 @@ export const components = {
fields: {},
runnable: undefined
},
recomputeIds: undefined,
recomputeIds: true,
configuration: {
label: {
type: 'static',
@@ -480,7 +481,6 @@ export const components = {
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
selectOptions: selectOptions.buttonColorOptions,
value: 'blue',
tooltip: 'Theses presets can be overwritten with custom styles.'
@@ -562,7 +562,6 @@ export const components = {
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
selectOptions: selectOptions.buttonColorOptions,
value: 'blue'
},
@@ -610,7 +609,7 @@ export const components = {
fields: {},
runnable: undefined
},
recomputeIds: undefined,
recomputeIds: true,
configuration: {
label: {
type: 'static',
@@ -620,7 +619,6 @@ export const components = {
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
value: 'dark',
selectOptions: selectOptions.buttonColorOptions
},
@@ -653,7 +651,7 @@ export const components = {
fields: {},
runnable: undefined
},
recomputeIds: undefined,
recomputeIds: true,
configuration: {
label: {
type: 'static',
@@ -663,7 +661,6 @@ export const components = {
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
value: 'dark',
selectOptions: buttonColorOptions,
tooltip: 'Theses presets can be overwritten with custom styles.'
@@ -1060,7 +1057,7 @@ Hello \${ctx.username}
initialData: {
...defaultAlignement,
componentInput: undefined,
recomputeIds: undefined,
recomputeIds: true,
configuration: {
label: {
type: 'static',
@@ -1132,9 +1129,13 @@ Hello \${ctx.username}
dims: '2:1-3:1' as AppComponentDimensions,
customCss: {
input: { style: '' }
input: {
style: '',
tooltip: 'https://github.com/rob-balfre/svelte-select/blob/master/docs/theming_variables.md'
}
},
initialData: {
recomputeIds: true,
verticalAlignment: 'center',
componentInput: undefined,
configuration: {
@@ -1153,7 +1154,7 @@ Hello \${ctx.username}
value: false,
onlyStatic: true,
tooltip: 'Allows user to manually add new value',
customTitle: 'Manually add new value '
customTitle: 'User creatable'
},
placeholder: {
type: 'static',
@@ -1175,7 +1176,11 @@ Hello \${ctx.username}
dims: '2:1-3:1' as AppComponentDimensions,
customCss: {
input: { style: '' }
multiselect: {
style: '',
tooltip:
'See https://multiselect.janosh.dev/#with-css-variables for the available variables'
}
},
initialData: {
componentInput: undefined,
@@ -1191,6 +1196,22 @@ Hello \${ctx.username}
fieldType: 'text',
value: 'Select items',
onlyStatic: true
},
create: {
type: 'static',
fieldType: 'boolean',
value: false,
onlyStatic: true,
tooltip: 'Allows user to manually add new value',
customTitle: 'User creatable'
},
allowOverflow: {
type: 'static',
fieldType: 'boolean',
value: true,
onlyStatic: true,
tooltip:
'If too many items, the box overflow its container instead of having an internal scroll'
}
}
}
@@ -1718,7 +1739,6 @@ Hello \${ctx.username}
color: {
fieldType: 'select',
type: 'static',
onlyStatic: true,
selectOptions: buttonColorOptions,
value: 'blue',
tooltip:
@@ -16,6 +16,8 @@
export let forceClass: boolean = false
export let quickStyleProperties: PropertyGroup[] | undefined = undefined
export let componentType: TypedComponent['type'] | undefined = undefined
export let tooltip: string | undefined = undefined
const dispatch = createEventDispatcher()
let isQuickMenuOpen = false
@@ -33,6 +35,9 @@
</div>
{#if value}
<div class="px-3">
{#if tooltip}
<div class="text-gray-600 text-2xs py-2">{tooltip}</div>
{/if}
{#if value.style !== undefined || forceStyle}
<div class="pb-2">
<!-- svelte-ignore a11y-label-has-associated-control -->
@@ -267,7 +267,7 @@
<TableActions id={component.id} bind:components={componentSettings.item.data.actionButtons} />
{/if}
{#if componentSettings.item.data.type === 'buttoncomponent' || componentSettings.item.data.type === 'formcomponent' || componentSettings.item.data.type === 'formbuttoncomponent' || componentSettings.item.data.type === 'checkboxcomponent'}
{#if (`recomputeIds` in componentSettings.item.data && Array.isArray(componentSettings.item.data.recomputeIds)) || componentSettings.item.data.type === 'buttoncomponent' || componentSettings.item.data.type === 'formcomponent' || componentSettings.item.data.type === 'formbuttoncomponent' || componentSettings.item.data.type === 'checkboxcomponent'}
<Recompute
bind:recomputeIds={componentSettings.item.data.recomputeIds}
ownId={component.id}
@@ -306,6 +306,7 @@
<CssProperty
forceStyle={ccomponents[component.type].customCss[name].style != undefined}
forceClass={ccomponents[component.type].customCss[name].class != undefined}
tooltip={ccomponents[component.type].customCss[name].tooltip}
{name}
bind:value={componentSettings.item.data.customCss[name]}
/>
@@ -20,8 +20,8 @@
</script>
<PanelSection
title="Recompute others"
tooltip="Select components to recompute after running this script"
title="Trigger Runnable"
tooltip="Select components to recompute after running this runnable as a success"
documentationLink="https://docs.windmill.dev/docs/apps/app_settings#recompute-others"
>
{#if Object.keys($runnableComponents ?? {}).filter((id) => id !== ownId).length > 0}
@@ -60,6 +60,7 @@
quickStyleProperties={quickStyleProperties?.[component.type]?.[name]}
forceStyle={ccomponents[component.type].customCss[name].style !== undefined}
forceClass={ccomponents[component.type].customCss[name].class !== undefined}
tooltip={ccomponents[component.type].customCss[name].tooltip}
{name}
componentType={component.type}
bind:value={component.customCss[name]}
@@ -12,18 +12,19 @@
clearErrorByComponentId,
clearJobsByComponentId
} from '../appUtils'
import type { ButtonComponent, CheckboxComponent } from '../component'
import type { ButtonComponent, CheckboxComponent, SelectComponent } from '../component'
import PanelSection from './common/PanelSection.svelte'
import TableActionLabel from './TableActionLabel.svelte'
import { Inspect, ToggleRightIcon } from 'lucide-svelte'
import { Inspect, List, ToggleRightIcon } from 'lucide-svelte'
export let components: (BaseAppComponent & (ButtonComponent | CheckboxComponent))[]
export let components: (BaseAppComponent &
(ButtonComponent | CheckboxComponent | SelectComponent))[]
export let id: string
const { selectedComponent, app, errorByComponent, jobs } =
getContext<AppViewerContext>('AppViewerContext')
function addComponent(typ: 'buttoncomponent' | 'checkboxcomponent') {
function addComponent(typ: 'buttoncomponent' | 'checkboxcomponent' | 'selectcomponent') {
const actionId = getNextId(components.map((x) => x.id.split('_')[1]))
const newComponent = {
@@ -96,5 +97,15 @@
>
+ <ToggleRightIcon size={14} />
</Button>
<Button
btnClasses="gap-1 flex items-center text-sm text-gray-600"
wrapperClasses="w-full"
color="light"
variant="border"
on:click={() => addComponent('selectcomponent')}
title="Add Select"
>
+ <List size={14} />
</Button>
</div>
</PanelSection>
@@ -21,8 +21,8 @@
Run on start and app refresh
<Tooltip>
You may want to disable this so that the background runnable is only triggered by
changes to other values or triggered by another computation on a button (See
'Recompute Others')
changes to other values or triggered by another computation on a button (See 'Trigger
Runnables')
</Tooltip>
</div>
<Toggle