feat(apps): add resource picker
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
: []
|
||||
if (defaultValue) {
|
||||
value = JSON.stringify(defaultValue)
|
||||
} else if (listItems.length > 0) {
|
||||
value = listItems[0]?.value
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -212,7 +212,7 @@
|
||||
<ComponentNavigation />
|
||||
|
||||
<div on:pointerdown|stopPropagation class={width}>
|
||||
<GridEditor {policy} />
|
||||
<GridEditor {history} {policy} />
|
||||
</div>
|
||||
|
||||
<div id="app-editor-top-level-drawer" />
|
||||
|
||||
@@ -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)
|
||||
}}
|
||||
|
||||
@@ -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'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -32,6 +32,7 @@ const inputs: ComponentSet = {
|
||||
'fileinputcomponent',
|
||||
'checkboxcomponent',
|
||||
'selectcomponent',
|
||||
'resourceselectcomponent',
|
||||
'multiselectcomponent',
|
||||
]
|
||||
} as const
|
||||
|
||||
@@ -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' ||
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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' }>
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user