feat(frontend): add creatable select

This commit is contained in:
Ruben Fiszel
2023-03-03 01:06:36 +01:00
parent b796aeef7a
commit e428662481
14 changed files with 212 additions and 40 deletions
+7 -7
View File
@@ -45,7 +45,7 @@
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.5",
"@windmill-labs/svelte-grid": "^5.1.6",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
@@ -1318,9 +1318,9 @@
}
},
"node_modules/@windmill-labs/svelte-grid": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.5.tgz",
"integrity": "sha512-aCMaOghJHlFbw+Q9xg3zm+/zRDve6Ub+BMDyfm8cVJeJ2GL7arLxh+G6s46kqxfOBz388hHSKowqVNzQxDxw6A==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
"dev": true
},
"node_modules/@windmill-labs/svelvet": {
@@ -8225,9 +8225,9 @@
}
},
"@windmill-labs/svelte-grid": {
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.5.tgz",
"integrity": "sha512-aCMaOghJHlFbw+Q9xg3zm+/zRDve6Ub+BMDyfm8cVJeJ2GL7arLxh+G6s46kqxfOBz388hHSKowqVNzQxDxw6A==",
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/@windmill-labs/svelte-grid/-/svelte-grid-5.1.6.tgz",
"integrity": "sha512-wXHIG7XeMxwxnfaHajhjvz9c0u5uGgiQtTk/QxAVyViGdshsclfIfIWZF113QQBKCKp2jY/zP1cj64BrqioRoQ==",
"dev": true
},
"@windmill-labs/svelvet": {
+1 -1
View File
@@ -25,7 +25,7 @@
"@types/vscode": "~1.74.0",
"@typescript-eslint/eslint-plugin": "^5.49.0",
"@typescript-eslint/parser": "^5.48.0",
"@windmill-labs/svelte-grid": "^5.1.5",
"@windmill-labs/svelte-grid": "^5.1.6",
"@windmill-labs/svelvet": "^4.0.20",
"@zerodevx/svelte-toast": "^0.8.1",
"autoprefixer": "^10.4.13",
+1
View File
@@ -47,6 +47,7 @@ declare module '@windmill-labs/svelte-grid' {
onTopId?: string
scroller?: undefined
sensor?: number
parentWidth?: number
}
export interface Slots<T> {
@@ -0,0 +1,97 @@
<script lang="ts">
import { getContext } from 'svelte'
import Select from 'svelte-select'
import type { AppInput } from '../../inputType'
import type { Output } from '../../rx'
import type { AppEditorContext, ComponentCustomCSS } 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 { stopPropagation } from 'ol/events/Event'
export const staticOutputs: string[] = ['result']
export let id: string
export let configuration: Record<string, AppInput>
export let horizontalAlignment: 'left' | 'center' | 'right' | undefined = undefined
export let verticalAlignment: 'top' | 'center' | 'bottom' | undefined = undefined
export let customCss: ComponentCustomCSS<'input'> | undefined = undefined
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let items: { label: string; value: string }[]
let placeholder: string = 'Select an item'
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string[] | undefined>
}
// $: outputs && handleOutputs()
// function handleOutputs() {
// value = outputs.result.peak()
// }
let value: { value: string }[] | undefined = outputs?.result.peak()
$: labels && handleItems()
let labels: string[] | undefined = []
function handleItems() {
if (labels) {
items = labels?.map((label) => {
return {
label,
value: label
}
})
}
}
$: value && outputs?.result.set(value.map((v) => v.value))
$: css = concatCustomCss($app.css?.selectcomponent, customCss)
</script>
<InputValue {id} input={configuration.items} bind:value={labels} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
{#if !value || Array.isArray(value)}
<Select
multiple
on:change={(e) => e.stopPropagation()}
{items}
class={css?.input?.class ?? ''}
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles + css?.input?.style}
bind:value
{placeholder}
on:click={() => {
if (!$connectingInput.opened) {
$selectedComponent = id
}
}}
on:focus={() => {
$selectedComponent = id
}}
floatingConfig={{
strategy: 'fixed'
}}
/>
{:else}
Value {value} is not an array
{/if}
</div>
</AlignWrapper>
<style global>
.app-select .value-container {
padding: 0 !important;
}
.svelte-select-list {
z-index: 1000 !important;
}
</style>
@@ -18,25 +18,18 @@
const { app, worldStore, connectingInput, selectedComponent } =
getContext<AppEditorContext>('AppEditorContext')
let items: { label: string; value: any }[]
let multiple: boolean = false
let items: { label: string; value: any; created?: boolean }[]
let placeholder: string = 'Select an item'
$: outputs = $worldStore?.outputsById[id] as {
result: Output<string | undefined>
result: Output<any | undefined>
}
// $: outputs && handleOutputs()
// function handleOutputs() {
// value = outputs.result.peak()
// }
let value: string | undefined = outputs?.result.peak()
$: items && handleItems()
let listItems: { label: string; value: string }[] = []
let listItems: { label: string; value: string; created?: boolean }[] = []
function handleItems() {
listItems = Array.isArray(items)
@@ -47,21 +40,22 @@
}
})
: []
const valuePeak = outputs?.result.peak()
if (!valuePeak) {
const value0 = items?.[0]?.['value']
if (value0 != value) {
value = JSON.stringify(value0)
outputs?.result.set(value0)
}
} else {
value = JSON.stringify(valuePeak)
if (defaultValue) {
value = JSON.stringify(defaultValue)
}
}
function onChange(e: CustomEvent) {
e?.stopPropagation()
window.dispatchEvent(new Event('pointerup'))
if (create) {
listItems = listItems.map((i) => {
delete i.created
return i
})
}
let result: any = undefined
try {
result = JSON.parse(e.detail?.['value'])
@@ -73,23 +67,40 @@
let defaultValue: any = undefined
$: {
function handleFilter(e) {
if (create) {
if (e.detail.length === 0 && filterText.length > 0) {
const prev = listItems.filter((i) => !i.created)
listItems = [
...prev,
{ value: JSON.stringify(filterText), label: filterText, created: true }
]
}
}
}
$: defaultValue && handleDefault()
function handleDefault() {
if (defaultValue) {
value = JSON.stringify(defaultValue)
outputs?.result.set(defaultValue)
}
}
let create = false
let filterText = ''
</script>
<InputValue {id} input={configuration.items} bind:value={items} />
<InputValue {id} input={configuration.multiple} bind:value={multiple} />
<InputValue {id} input={configuration.placeholder} bind:value={placeholder} />
<InputValue {id} input={configuration.defaultValue} bind:value={defaultValue} />
<InputValue {id} input={configuration.create} bind:value={create} />
<AlignWrapper {horizontalAlignment} {verticalAlignment}>
<div class="app-select w-full mx-0.5" style="height: 34px" on:pointerdown|stopPropagation>
<Select
{multiple}
bind:filterText
on:filter={handleFilter}
on:clear={onChange}
on:change={onChange}
items={listItems}
@@ -109,7 +120,15 @@
floatingConfig={{
strategy: 'fixed'
}}
/>
>
<div slot="item" let:item>
{#if create}
{item.created ? 'Add new: ' : ''}
{/if}
{item.label}
</div>
</Select>
</div>
</AlignWrapper>
@@ -33,6 +33,8 @@
selectedTabIndex: Output<number | null>
}
$: outputs?.selectedTabIndex && handleOutputs()
function handleTabSelection() {
outputs?.selectedTabIndex.set(selectedIndex)
if ($selectedComponent != id) {
@@ -60,6 +62,10 @@
$: $selectedComponent === id && selectedIndex >= 0 && onFocus()
$: css = concatCustomCss($app.css?.tabscomponent, customCss)
function handleOutputs() {
outputs?.selectedTabIndex.set(outputs.selectedTabIndex.peak())
}
</script>
<InputValue {id} input={configuration.noPadding} bind:value={noPadding} />
@@ -81,7 +81,8 @@
errorByComponent,
openDebugRun: writable(undefined),
focusedGrid,
stateId: writable(0)
stateId: writable(0),
parentWidth: writable(0)
})
let timeout: NodeJS.Timeout | undefined = undefined
@@ -62,7 +62,8 @@
errorByComponent: writable({}),
openDebugRun: writable(undefined),
focusedGrid: writable(undefined),
stateId: writable(0)
stateId: writable(0),
parentWidth: writable(0)
})
let mounted = false
@@ -21,7 +21,8 @@
staticOutputs,
runnableComponents,
summary,
focusedGrid
focusedGrid,
parentWidth
} = getContext<AppEditorContext>('AppEditorContext')
// The drag is disabled when the user is connecting an input
@@ -148,6 +149,7 @@
$selectedComponent = undefined
$focusedGrid = undefined
}}
bind:clientWidth={$parentWidth}
>
<div class={!$focusedGrid && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
<Grid
@@ -21,7 +21,7 @@
const dispatch = createEventDispatcher()
const { app, connectingInput, selectedComponent, focusedGrid, mode } =
const { app, connectingInput, selectedComponent, focusedGrid, mode, parentWidth } =
getContext<AppEditorContext>('AppEditorContext')
$: highlight = id === $focusedGrid?.parentComponentId && shouldHighlight
@@ -82,9 +82,7 @@
on:pointerup={onpointerup}
style="height: {containerHeight}px; {style ?? ''}"
>
<div
class={highlight && $mode !== 'preview' ? 'border-gray-400 border border-dashed h-full' : ''}
>
<div class={highlight && $mode !== 'preview' ? 'border-gray-400 border border-dashed' : ''}>
<Grid
bind:items={subGrid}
let:dataItem
@@ -93,6 +91,7 @@
fastStart={true}
gap={[4, 2]}
scroller={container}
parentWidth={$parentWidth - 17}
>
{#each subGrid as gridComponent (gridComponent.id)}
{#if gridComponent?.data?.id && gridComponent?.data?.id === dataItem?.data?.id}
@@ -39,6 +39,7 @@
AppMap,
AppSplitpanes
} from '../../components'
import AppMultiSelect from '../../components/inputs/AppMultiSelect.svelte'
export let component: AppComponent
export let selected: boolean
@@ -203,6 +204,15 @@
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'multiselectcomponent'}
<AppMultiSelect
id={component.id}
verticalAlignment={component.verticalAlignment}
horizontalAlignment={component.horizontalAlignment}
configuration={component.configuration}
customCss={component.customCss}
bind:staticOutputs={$staticOutputs[component.id]}
/>
{:else if component.type === 'formcomponent'}
<AppForm
id={component.id}
@@ -72,6 +72,7 @@ export type DisplayComponent = BaseComponent<'displaycomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'>
export type MultiSelectComponent = BaseComponent<'multiselectcomponent'>
export type CheckboxComponent = BaseComponent<'checkboxcomponent'>
export type RadioComponent = BaseComponent<'radiocomponent'>
export type IconComponent = BaseComponent<'iconcomponent'>
@@ -110,6 +111,7 @@ export type AppComponent = BaseAppComponent &
| PieChartComponent
| ScatterChartComponent
| SelectComponent
| MultiSelectComponent
| CheckboxComponent
| FormComponent
| FormButtonComponent
@@ -871,7 +873,7 @@ Hello \${ctx.username}
{ value: 'bar', label: 'Bar' }
]
},
multiple: {
create: {
type: 'static',
fieldType: 'boolean',
value: false,
@@ -896,6 +898,38 @@ Hello \${ctx.username}
softWrap: true
}
},
multiselectcomponent: {
name: 'MultiSelect',
icon: List,
dims: '2:1-3:1',
data: {
verticalAlignment: 'center',
id: '',
type: 'multiselectcomponent',
componentInput: undefined,
configuration: {
items: {
type: 'static',
fieldType: 'array',
subFieldType: 'text',
value: [
"Foo", "Bar"
]
},
placeholder: {
type: 'static',
fieldType: 'text',
value: 'Select items',
onlyStatic: true
}
},
customCss: {
input: { style: '' }
} as const,
card: false,
softWrap: true
}
},
numberinputcomponent: {
name: 'Number',
icon: Binary,
@@ -30,7 +30,8 @@ const inputs: ComponentSet = {
'dateinputcomponent',
'fileinputcomponent',
'checkboxcomponent',
'selectcomponent'
'selectcomponent',
'multiselectcomponent',
]
} as const
@@ -131,6 +131,7 @@ export type AppEditorContext = {
openDebugRun: Writable<((componentID: string) => void) | undefined>
focusedGrid: Writable<FocusedGrid | undefined>
stateId: Writable<number>
parentWidth: Writable<number>
}
export type FocusedGrid = { parentComponentId: string; subGridIndex: number }