feat(apps): add resource picker

This commit is contained in:
Ruben Fiszel
2023-03-10 20:01:00 +01:00
parent 3be96d8773
commit 7ec85d42e4
14 changed files with 120 additions and 49 deletions

View File

@@ -88,7 +88,7 @@
}}
items={collection}
class="text-clip grow min-w-0"
placeholder="{resourceType} resource"
placeholder="{resourceType ?? 'any'} resource"
inputStyles={SELECT_INPUT_DEFAULT_STYLE.inputStyles}
containerStyles={SELECT_INPUT_DEFAULT_STYLE.containerStyles}
/>

View File

@@ -43,6 +43,8 @@
: []
if (defaultValue) {
value = JSON.stringify(defaultValue)
} else if (listItems.length > 0) {
value = listItems[0]?.value
}
}

View File

@@ -212,7 +212,7 @@
<ComponentNavigation />
<div on:pointerdown|stopPropagation class={width}>
<GridEditor {policy} />
<GridEditor {history} {policy} />
</div>
<div id="app-editor-top-level-drawer" />

View File

@@ -471,8 +471,8 @@
<input type="text" placeholder="App summary" class="text-sm w-full" bind:value={$summary} />
</div>
<UndoRedo
undoProps={{ disabled: $history.index === 0 }}
redoProps={{ disabled: $history.index === $history.history.length - 1 }}
undoProps={{ disabled: $history?.index === 0 }}
redoProps={{ disabled: $history && $history?.index === $history.history.length - 1 }}
on:undo={() => {
$app = undo(history, $app)
}}

View File

@@ -56,15 +56,15 @@
{/if}
</button>
<button
title="Position locking"
title="Expand"
class={classNames(
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[0.5rem] z-50 cursor-pointer',
'text-gray-800 px-1 text-2xs py-0.5 font-bold w-fit shadow border border-gray-300 absolute -top-1 right-[4.5rem] z-50 cursor-pointer',
' hover:bg-gray-300',
selected ? 'bg-gray-200/80' : 'bg-gray-200/80'
)}
on:click={() => dispatch('expand')}
>
<Expand aria-label="Lock position" size={14} />
<Expand aria-label="Expand position" size={14} />
</button>
{/if}
@@ -73,7 +73,7 @@
title="Move"
on:mousedown|stopPropagation|capture
class={classNames(
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute border border-gray-300 -top-1 shadow right-[4.5rem] z-50 cursor-move',
'text-gray-600 px-1 text-2xs py-0.5 font-bold rounded-t-sm w-fit absolute border border-gray-300 -top-1 shadow right-[0.5rem] z-50 cursor-move',
'bg-gray-200/80'
)}
>

View File

@@ -11,10 +11,11 @@
import HiddenComponent from '../components/helpers/HiddenComponent.svelte'
import Component from './component/Component.svelte'
import { deepEqual } from 'fast-equals'
import { push } from '$lib/history'
import { push, type History } from '$lib/history'
import { expandGriditem, findGridItem } from './appUtils'
export let policy: Policy
export let history: History<App> | undefined = undefined
const {
selectedComponent,
@@ -29,8 +30,6 @@
breakpoint
} = getContext<AppViewerContext>('AppViewerContext')
const { history } = getContext<AppEditorContext>('AppEditorContext')
// The drag is disabled when the user is connecting an input
// or when the user is previewing the app
// or when the focused grid is a subgrid
@@ -108,13 +107,17 @@
let pointerdown = false
let lastapp: App | undefined = undefined
const onpointerdown = () => {
lastapp = JSON.parse(JSON.stringify($app))
pointerdown = true
if (history) {
lastapp = JSON.parse(JSON.stringify($app))
pointerdown = true
}
}
const onpointerup = () => {
pointerdown = false
if (!deepEqual(lastapp, $app)) {
push(history, lastapp, false, true)
if (history) {
pointerdown = false
if (!deepEqual(lastapp, $app)) {
push(history, lastapp, false, true)
}
}
}

View File

@@ -222,7 +222,7 @@
recomputeIds={component.recomputeIds}
{render}
/>
{:else if component.type === 'selectcomponent'}
{:else if component.type === 'selectcomponent' || component.type === 'resourceselectcomponent'}
<AppSelect
id={component.id}
verticalAlignment={component.verticalAlignment}

View File

@@ -74,7 +74,8 @@ export type AggridComponent = BaseComponent<'aggridcomponent'>
export type DisplayComponent = BaseComponent<'displaycomponent'>
export type ImageComponent = BaseComponent<'imagecomponent'>
export type InputComponent = BaseComponent<'inputcomponent'>
export type SelectComponent = BaseComponent<'selectcomponent'>
export type SelectComponent = BaseComponent<'resourceselectcomponent'>
export type ResourceSelectComponent = BaseComponent<'selectcomponent'>
export type MultiSelectComponent = BaseComponent<'multiselectcomponent'>
export type CheckboxComponent = BaseComponent<'checkboxcomponent'>
export type RadioComponent = BaseComponent<'radiocomponent'>
@@ -116,6 +117,7 @@ export type AppComponent = BaseAppComponent &
| PieChartComponent
| ScatterChartComponent
| SelectComponent
| ResourceSelectComponent
| MultiSelectComponent
| CheckboxComponent
| FormComponent
@@ -911,7 +913,7 @@ Hello \${ctx.username}
}
},
multiselectcomponent: {
name: 'MultiSelect',
name: 'Multi Select',
icon: List,
dims: '2:1-3:1',
data: {
@@ -940,6 +942,36 @@ Hello \${ctx.username}
softWrap: true
}
},
resourceselectcomponent: {
name: 'Resource Select',
icon: List,
dims: '2:1-3:1',
data: {
verticalAlignment: 'center',
id: '',
type: 'resourceselectcomponent',
componentInput: undefined,
configuration: {
items: {
type: 'static',
fieldType: 'array',
subFieldType: 'labeledresource',
value: []
},
placeholder: {
type: 'static',
fieldType: 'text',
value: 'Select an item',
onlyStatic: true
}
},
customCss: {
input: { style: '' }
} as const,
card: false,
softWrap: true
}
},
numberinputcomponent: {
name: 'Number',
icon: Binary,

View File

@@ -32,6 +32,7 @@ const inputs: ComponentSet = {
'fileinputcomponent',
'checkboxcomponent',
'selectcomponent',
'resourceselectcomponent',
'multiselectcomponent',
]
} as const

View File

@@ -22,6 +22,8 @@
componentInput.value.push(0)
} else if (componentInput.subFieldType === 'object') {
componentInput.value.push({})
} else if (componentInput.subFieldType === 'labeledresource') {
componentInput.value.push({ value: '', label: '' })
} else if (
componentInput.subFieldType === 'text' ||
componentInput.subFieldType === 'textarea' ||

View File

@@ -39,6 +39,24 @@
</select>
{:else if componentInput.fieldType === 'icon-select'}
<IconSelectInput bind:componentInput />
{:else if componentInput.fieldType === 'labeledresource'}
{#if componentInput?.value && typeof componentInput?.value == 'object' && 'label' in componentInput?.value}
<div class="flex flex-col gap-1 w-full">
<input placeholder="Label" type="text" bind:value={componentInput.value['label']} />
<ResourcePicker
initialValue={componentInput.value?.['value']?.split('$res:')[1] || ''}
on:change={(e) => {
let path = e.detail
if (componentInput && path) {
componentInput.value['value'] = `$res:${path}`
}
}}
/>
</div>
{:else}
Inconsistent labeled resource object
{/if}
{:else if componentInput.fieldType === 'color'}
<ColorInput bind:componentInput />
{:else if componentInput.fieldType === 'object'}

View File

@@ -17,6 +17,7 @@ export type InputType =
| 'object'
| 'array'
| 'any'
| 'labeledresource'
// Connection to an output of another component
// defined by the id of the component and the path of the output
@@ -139,11 +140,11 @@ export type AppInput =
| AppInputSpec<'object', Record<string | number, any>>
| AppInputSpec<'object', string>
| (AppInputSpec<'select', string> & {
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
})
/**
* One of the keys of `staticValues` from `lib/components/apps/editor/componentsPanel/componentStaticValues`
*/
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'select', string> & StaticOptions)
| AppInputSpec<'icon-select', string>
| AppInputSpec<'color', string>
@@ -156,9 +157,12 @@ export type AppInput =
| AppInputSpec<'array', string[], 'datetime'>
| AppInputSpec<'array', object[], 'object'>
| (AppInputSpec<'array', string[], 'select'> & {
optionValuesKey: keyof typeof staticValues
})
optionValuesKey: keyof typeof staticValues
})
| (AppInputSpec<'array', string[], 'select'> & StaticOptions)
| AppInputSpec<'array', object[], 'labeledresource'>
| AppInputSpec<'labeledresource', object>
export type RowAppInput = Extract<AppInput, { type: 'row' }>
export type StaticAppInput = Extract<AppInput, { type: 'static' }>

View File

@@ -137,7 +137,7 @@ export type AppViewerContext = {
}
export type AppEditorContext = {
history: History<App>
history: History<App> | undefined
componentControl: Writable<Record<string, { left?: () => boolean; right?: () => boolean }>>
pickVariableCallback: Writable<((path: string) => void) | undefined>
}

View File

@@ -7,34 +7,43 @@ export function initHistory<T>(initial: T): History<T> {
return writable({ history: [initial], index: 0 })
}
export function undo<T>(history: History<T>, now: T): T {
let chistory = get(history)
if (chistory.index == 0) return now
if (!deepEqual(chistory.history[chistory.index], now)) {
push(history, now, true)
export function undo<T>(history: History<T> | undefined, now: T): T {
if (history) {
let chistory = get(history)
if (chistory.index == 0) return now
if (!deepEqual(chistory.history[chistory.index], now)) {
push(history, now, true)
} else {
history.update((history) => {
history.index--
return history
})
}
let nhistory = get(history)
return nhistory.history[nhistory.index]
} else {
history.update((history) => {
history.index--
return now
}
}
export function redo<T>(history: History<T> | undefined): T {
if (history) {
history?.update((history) => {
if (history.index < history.history.length - 1) {
history.index++
}
return history
})
let nhistory = get(history)
return nhistory.history[nhistory.index]
} else {
return undefined as T
}
let nhistory = get(history)
return nhistory.history[nhistory.index]
}
export function redo<T>(history: History<T>): T {
history.update((history) => {
if (history.index < history.history.length - 1) {
history.index++
}
return history
})
let nhistory = get(history)
return nhistory.history[nhistory.index]
}
export function push<T>(history: History<T>, value: T, noSetIndex: boolean = false, skipCopy: boolean = false) {
history.update((history) => {
export function push<T>(history: History<T> | undefined, value: T, noSetIndex: boolean = false, skipCopy: boolean = false) {
history?.update((history) => {
history.history = JSON.parse(JSON.stringify(history.history.slice(0, history.index + 1)))
const toPush = skipCopy ? value : JSON.parse(JSON.stringify(value))
history.history.push(toPush)