Add dialog action handling

This commit is contained in:
2026-01-18 13:35:43 +00:00
parent 9ea7c15f5d
commit 5080026ef7
4 changed files with 54 additions and 4 deletions

View File

@@ -169,7 +169,7 @@ The JSON UI system already supports `events` for action execution and `bindings`
"onImport": { "actions": [{ "id": "import-json", "type": "custom" }] },
"onExport": { "actions": [{ "id": "export-json", "type": "custom" }] },
"onCopy": { "actions": [{ "id": "copy-json", "type": "custom" }] },
"onPreview": { "actions": [{ "id": "open-preview", "type": "open-dialog", "target": "preview" }] },
"onPreview": { "actions": [{ "id": "open-preview", "type": "open-dialog", "target": "uiState", "path": "preview" }] },
"onClear": { "actions": [{ "id": "clear-schema", "type": "set-value", "target": "schema.components", "value": [] }] }
}
}
@@ -177,6 +177,8 @@ The JSON UI system already supports `events` for action execution and `bindings`
**Why:** these are pure event triggers; `custom` actions cover app-specific flows that arent part of the built-in action types.
**Dialog storage convention:** `open-dialog`/`close-dialog` actions store booleans in `uiState.dialogs.<dialogId>`. Use `target` for the data source (typically `uiState`) and `path` for the dialog id.
### 5) Drag-and-drop/hover state (CanvasRenderer, ComponentTree)
**Bindings:** IDs and `dropPosition` stored in data; events mapped to custom actions for editor logic.
@@ -229,7 +231,7 @@ The JSON UI system already supports `events` for action execution and `bindings`
},
"onEdit": {
"actions": [
{ "id": "open-source-editor", "type": "open-dialog", "target": "dataSourceEditor" }
{ "id": "open-source-editor", "type": "open-dialog", "target": "uiState", "path": "dataSourceEditor" }
]
},
"onDelete": {

View File

@@ -4,11 +4,43 @@ import { Action, JSONUIContext } from '@/types/json-ui'
import { evaluateExpression, evaluateTemplate } from '@/lib/json-ui/expression-evaluator'
export function useActionExecutor(context: JSONUIContext) {
const { data, updateData, executeAction: contextExecute } = context
const { data, updateData, updatePath, executeAction: contextExecute } = context
const executeAction = useCallback(async (action: Action, event?: any) => {
try {
const evaluationContext = { data, event }
const updateByPath = (sourceId: string, path: string, value: any) => {
if (updatePath) {
updatePath(sourceId, path, value)
return
}
const sourceData = data[sourceId] ?? {}
const pathParts = path.split('.')
const newData = { ...sourceData }
let current: any = newData
for (let i = 0; i < pathParts.length - 1; i++) {
const key = pathParts[i]
current[key] = typeof current[key] === 'object' && current[key] !== null ? { ...current[key] } : {}
current = current[key]
}
current[pathParts[pathParts.length - 1]] = value
updateData(sourceId, newData)
}
const resolveDialogTarget = () => {
const defaultSourceId = 'uiState'
const hasExplicitTarget = Boolean(action.target && action.path)
const sourceId = hasExplicitTarget ? action.target : defaultSourceId
const dialogId = action.path ?? action.target
if (!dialogId) return null
const dialogPath = dialogId.startsWith('dialogs.') ? dialogId : `dialogs.${dialogId}`
return { sourceId, dialogPath }
}
switch (action.type) {
case 'create': {
@@ -134,6 +166,20 @@ export function useActionExecutor(context: JSONUIContext) {
break
}
case 'open-dialog': {
const dialogTarget = resolveDialogTarget()
if (!dialogTarget) return
updateByPath(dialogTarget.sourceId, dialogTarget.dialogPath, true)
break
}
case 'close-dialog': {
const dialogTarget = resolveDialogTarget()
if (!dialogTarget) return
updateByPath(dialogTarget.sourceId, dialogTarget.dialogPath, false)
break
}
case 'custom': {
if (contextExecute) {
await contextExecute(action, event)
@@ -145,7 +191,7 @@ export function useActionExecutor(context: JSONUIContext) {
console.error('Action execution failed:', error)
toast.error('Action failed')
}
}, [data, updateData, contextExecute])
}, [data, updateData, updatePath, contextExecute])
const executeActions = useCallback(async (actions: Action[], event?: any) => {
for (const action of actions) {

View File

@@ -17,6 +17,7 @@ export function PageRenderer({ schema, onCustomAction }: PageRendererProps) {
const context = {
data,
updateData,
updatePath,
executeAction: onCustomAction || (async () => {}),
}

View File

@@ -147,6 +147,7 @@ export interface PageSchema {
export interface JSONUIContext {
data: Record<string, any>
updateData: (sourceId: string, value: any) => void
updatePath?: (sourceId: string, path: string, value: any) => void
executeAction: (action: Action, event?: any) => Promise<void>
}