diff --git a/MAYBE_JSON_BINDING_REVIEW.md b/MAYBE_JSON_BINDING_REVIEW.md index 2973416..a47673f 100644 --- a/MAYBE_JSON_BINDING_REVIEW.md +++ b/MAYBE_JSON_BINDING_REVIEW.md @@ -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 aren’t part of the built-in action types. +**Dialog storage convention:** `open-dialog`/`close-dialog` actions store booleans in `uiState.dialogs.`. 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": { diff --git a/src/hooks/ui/use-action-executor.ts b/src/hooks/ui/use-action-executor.ts index 528a5c8..ce4f7ce 100644 --- a/src/hooks/ui/use-action-executor.ts +++ b/src/hooks/ui/use-action-executor.ts @@ -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) { diff --git a/src/lib/json-ui/page-renderer.tsx b/src/lib/json-ui/page-renderer.tsx index 41e4c86..13d589e 100644 --- a/src/lib/json-ui/page-renderer.tsx +++ b/src/lib/json-ui/page-renderer.tsx @@ -17,6 +17,7 @@ export function PageRenderer({ schema, onCustomAction }: PageRendererProps) { const context = { data, updateData, + updatePath, executeAction: onCustomAction || (async () => {}), } diff --git a/src/types/json-ui.ts b/src/types/json-ui.ts index 7e1cd9b..8a9e41a 100644 --- a/src/types/json-ui.ts +++ b/src/types/json-ui.ts @@ -147,6 +147,7 @@ export interface PageSchema { export interface JSONUIContext { data: Record updateData: (sourceId: string, value: any) => void + updatePath?: (sourceId: string, path: string, value: any) => void executeAction: (action: Action, event?: any) => Promise }