mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-08-21 08:02:26 +00:00
fix(frontend): Fix context panel + delete component (#937)
* fix(frontend): Fix context panel + delete component * fix(frontend): Handle nested paths
This commit is contained in:
@@ -14,7 +14,6 @@
|
||||
export const staticOutputs: string[] = ['loading', 'result']
|
||||
|
||||
let labelValue: string = 'Default label'
|
||||
|
||||
let runnableComponent: RunnableComponent
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte'
|
||||
import type { StaticInput, DynamicInput, AppEditorContext, UserInput } from '../../types'
|
||||
import { accessPropertyByPath } from '../../utils'
|
||||
|
||||
type T = $$Generic
|
||||
export let input: DynamicInput | StaticInput | UserInput
|
||||
export let value: T
|
||||
export let value: T | undefined = undefined
|
||||
|
||||
const { worldStore } = getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
@@ -26,7 +27,13 @@
|
||||
|
||||
function onValueChange(newValue: T): void {
|
||||
if (input.type === 'output') {
|
||||
value = newValue
|
||||
if (input.name?.includes('.')) {
|
||||
const path = input.name.split('.').slice(1).join('.')
|
||||
|
||||
value = accessPropertyByPath(newValue, path)
|
||||
} else {
|
||||
value = newValue
|
||||
}
|
||||
} else {
|
||||
// TODO: handle disconnect
|
||||
}
|
||||
|
||||
@@ -69,7 +69,9 @@
|
||||
<AppEditorHeader bind:title={$appStore.title} bind:mode={$mode} />
|
||||
<SplitPanesWrapper>
|
||||
<Pane size={20} minSize={20} maxSize={40}>
|
||||
<ContextPanel appPath={path} />
|
||||
{#if $mode === 'dnd'}
|
||||
<ContextPanel appPath={path} />
|
||||
{/if}
|
||||
</Pane>
|
||||
<Pane size={60} maxSize={100}>
|
||||
<div class="p-4 bg-gray-100 h-full" id="faton">
|
||||
@@ -79,36 +81,38 @@
|
||||
</div>
|
||||
</Pane>
|
||||
<Pane size={20} minSize={20} maxSize={40}>
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
{#each $appStore.grid as gridItem (gridItem.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel bind:component={gridItem.data} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if $selectedComponent === undefined}
|
||||
<div class="p-4 text-sm">No component selected.</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{#if $mode === 'dnd'}
|
||||
<Tabs bind:selected={selectedTab}>
|
||||
<Tab value="insert" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faPlus} />
|
||||
<span>Insert</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<Tab value="settings" size="xs">
|
||||
<div class="m-1 flex flex-row gap-2">
|
||||
<Icon data={faSliders} />
|
||||
<span>Settings</span>
|
||||
</div>
|
||||
</Tab>
|
||||
<svelte:fragment slot="content">
|
||||
<TabContent value="settings">
|
||||
{#if $selectedComponent !== undefined}
|
||||
{#each $appStore.grid as gridItem (gridItem.id)}
|
||||
{#if gridItem.data.id === $selectedComponent}
|
||||
<ComponentPanel bind:component={gridItem.data} />
|
||||
{/if}
|
||||
{/each}
|
||||
{/if}
|
||||
{#if $selectedComponent === undefined}
|
||||
<div class="p-4 text-sm">No component selected.</div>
|
||||
{/if}
|
||||
</TabContent>
|
||||
<TabContent value="insert">
|
||||
<ComponentList />
|
||||
</TabContent>
|
||||
</svelte:fragment>
|
||||
</Tabs>
|
||||
{/if}
|
||||
</Pane>
|
||||
</SplitPanesWrapper>
|
||||
|
||||
@@ -27,19 +27,25 @@
|
||||
|
||||
<div class="bg-white h-full">
|
||||
<Grid bind:items={$app.grid} rowHeight={32} let:dataItem {cols}>
|
||||
{@const index = $app.grid.findIndex((c) => c.data.id === dataItem.data.id)}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class={classNames('h-full w-full flex justify-center align-center border border-gray-100')}
|
||||
on:click={() => {
|
||||
$selectedComponent = dataItem.data.id
|
||||
}}
|
||||
>
|
||||
<ComponentEditor
|
||||
bind:component={$app.grid[index].data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
/>
|
||||
</div>
|
||||
{#each $app.grid as gridComponent (gridComponent.id)}
|
||||
{#if gridComponent.data.id === dataItem.data.id}
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
||||
<div
|
||||
class={classNames(
|
||||
'h-full w-full flex justify-center align-center border border-gray-100'
|
||||
)}
|
||||
on:click={() => {
|
||||
$selectedComponent = dataItem.data.id
|
||||
}}
|
||||
>
|
||||
<ComponentEditor
|
||||
bind:component={gridComponent.data}
|
||||
selected={$selectedComponent === dataItem.data.id}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/each}
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import ObjectViewer from '$lib/components/propertyPicker/ObjectViewer.svelte'
|
||||
import { getContext } from 'svelte'
|
||||
import type { Output } from '../../rx'
|
||||
import type { AppEditorContext } from '../../types'
|
||||
|
||||
export let outputs: string[] = []
|
||||
@@ -10,13 +11,20 @@
|
||||
|
||||
let object = {}
|
||||
|
||||
outputs.forEach((output: string) => {
|
||||
$worldStore?.outputsById[componentId][output].subscribe({
|
||||
next: (value) => {
|
||||
object[output] = value
|
||||
}
|
||||
})
|
||||
})
|
||||
function subscribeToAllOutputs(observableOutputs: Record<string, Output<any>>) {
|
||||
if (observableOutputs) {
|
||||
outputs.forEach((output) => {
|
||||
observableOutputs[output].subscribe({
|
||||
next: (value) => {
|
||||
object[output] = value
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
$: $worldStore?.outputsById[componentId] &&
|
||||
subscribeToAllOutputs($worldStore.outputsById[componentId])
|
||||
</script>
|
||||
|
||||
<ObjectViewer json={object} on:select topBrackets={true} />
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
export let appPath: string
|
||||
|
||||
const { connectingInput, staticOutputs, app } = getContext<AppEditorContext>('AppEditorContext')
|
||||
const { connectingInput, staticOutputs, app, worldStore } =
|
||||
getContext<AppEditorContext>('AppEditorContext')
|
||||
|
||||
function connectInput(id: string, name: string) {
|
||||
if ($connectingInput) {
|
||||
@@ -152,7 +153,7 @@
|
||||
|
||||
<PanelSection title="Outputs">
|
||||
{#each Object.entries($staticOutputs) as [componentId, outputs], index}
|
||||
{#if outputs.length > 0}
|
||||
{#if outputs.length > 0 && $worldStore?.outputsById[componentId]}
|
||||
<Badge color="blue">Component: {componentId}</Badge>
|
||||
|
||||
<div class="w-full p-2 rounded-xs border">
|
||||
@@ -160,8 +161,7 @@
|
||||
{outputs}
|
||||
{componentId}
|
||||
on:select={({ detail }) => {
|
||||
const [output] = detail.split('.')
|
||||
connectInput(componentId, output)
|
||||
connectInput(componentId, detail)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -26,8 +26,7 @@
|
||||
function removeGridElement() {
|
||||
if (component) {
|
||||
const COLS = 6
|
||||
const index = $app.grid.findIndex((gridComponent) => gridComponent.data.id === component?.id)
|
||||
$app.grid.splice(index, 1)
|
||||
$app.grid = $app.grid.filter((gridComponent) => gridComponent.data.id !== component?.id)
|
||||
$app.grid = gridHelp.adjust($app.grid, COLS)
|
||||
|
||||
// Delete static inputs
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
export let inputSpecs: InputsSpec
|
||||
|
||||
const userTypeKeys = ['schemaProperty', 'defaultValue', 'value']
|
||||
const staticTypeKeys = ['visible', 'value', 'fieldType']
|
||||
const staticTypeKeys = ['value']
|
||||
const dynamicTypeKeys = ['id', 'name', 'defaultValue']
|
||||
|
||||
function sanitizeInputSpec(type: 'user' | 'static' | 'output', inputSpecKey: string) {
|
||||
|
||||
@@ -45,10 +45,13 @@ export function buildObservableWorld() {
|
||||
}
|
||||
} else if (inputSpec.type === 'output') {
|
||||
const input = cachedInput(next)
|
||||
let obs = observables[`${inputSpec.id}.${inputSpec.name}`]
|
||||
|
||||
const [name] = inputSpec.name ? inputSpec.name.split('.') : [undefined]
|
||||
|
||||
let obs = observables[`${inputSpec.id}.${name}`]
|
||||
|
||||
if (!obs) {
|
||||
console.warn('Observable at ' + inputSpec.id + '.' + inputSpec.name + ' not found')
|
||||
console.warn('Observable at ' + inputSpec.id + '.' + name + ' not found')
|
||||
return {
|
||||
peak: () => undefined,
|
||||
next: () => {}
|
||||
|
||||
@@ -73,3 +73,23 @@ export const displayData = {
|
||||
icon: faBarChart
|
||||
}
|
||||
}
|
||||
|
||||
export function accessPropertyByPath<T>(object: T, path: string): T | undefined {
|
||||
// convert indexes to properties
|
||||
path = path.replace(/\[(\w+)\]/g, '.$1')
|
||||
// strip a leading dot
|
||||
path = path.replace(/^\./, '')
|
||||
|
||||
let a = path.split('.')
|
||||
|
||||
for (let i = 0, depth = a.length; i < depth; ++i) {
|
||||
let key = a[i]
|
||||
if (object[key]) {
|
||||
object = object[key]
|
||||
} else {
|
||||
// Value not found
|
||||
return
|
||||
}
|
||||
}
|
||||
return object
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user