From bbcc91dc80b74b9604c33810d04b84260a373f9a Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 16:19:42 +0000 Subject: [PATCH] Update JSON UI actions and conditionals --- JSON_EXPRESSION_SYSTEM.md | 15 +++++++ docs/JSON_CONVERSION.md | 19 +++++++- src/config/pages/flask-designer.json | 44 +++++++------------ src/config/pages/lambda-designer.json | 16 +++---- .../pages/molecules/label-with-badge.json | 4 +- .../pages/molecules/save-indicator.json | 4 +- src/config/pages/molecules/search-input.json | 10 ++--- src/config/pages/style-designer.json | 14 +++--- src/config/pages/workflow-designer.json | 12 ++--- src/lib/json-ui/component-renderer.tsx | 5 ++- src/lib/json-ui/expression-evaluator.ts | 8 ++++ src/lib/json-ui/renderer.tsx | 5 ++- 12 files changed, 84 insertions(+), 72 deletions(-) diff --git a/JSON_EXPRESSION_SYSTEM.md b/JSON_EXPRESSION_SYSTEM.md index a32aad4..a3a8ae3 100644 --- a/JSON_EXPRESSION_SYSTEM.md +++ b/JSON_EXPRESSION_SYSTEM.md @@ -27,6 +27,7 @@ Use the `expression` field to evaluate dynamic values: - Supports nested objects using dot notation - **Event Access**: `"event.target.value"`, `"event.key"`, `"event.type"` + - You can also reference the full event payload with `"event"` - Access event properties - Commonly used for form inputs @@ -110,6 +111,20 @@ To update nested values inside a data source, use a dotted `target` where the pr This dotted `target` format works with `set-value`, `update`, `toggle-value`, `increment`, and `decrement`. +## Conditional Expressions + +Conditional rendering uses `conditional.if` strings that are evaluated against the current data context: + +```json +{ + "conditional": { + "if": "statusFilter === 'running'" + } +} +``` + +Legacy conditional objects (with `source`/`operator`/`value`) should be migrated to these inline expressions so schemas stay compatible with the JSON UI renderer. + ### create Add a new item to an array data source. diff --git a/docs/JSON_CONVERSION.md b/docs/JSON_CONVERSION.md index f527f99..dde3275 100644 --- a/docs/JSON_CONVERSION.md +++ b/docs/JSON_CONVERSION.md @@ -106,7 +106,12 @@ Converted three complex pages (Models, Component Trees, and Workflows) from trad "type": "Component", "bindings": { "prop": { "source": "...", "path": "..." } }, "events": [ - { "event": "click", "actions": [...] } + { + "event": "click", + "actions": [ + { "type": "set-value", "target": "selectedId", "expression": "event" } + ] + } ] } ] @@ -115,6 +120,18 @@ Converted three complex pages (Models, Component Trees, and Workflows) from trad } ``` +### Action & Conditional Syntax +- Use supported JSON UI action types (for example, `set-value`, `toggle-value`, `show-toast`) with `target`, `path`, `value`, or `expression` fields instead of legacy `setState` actions. +- Replace legacy conditional objects (`{ "source": "...", "operator": "eq|gt|truthy|falsy", "value": ... }`) with `conditional.if` expressions: + +```json +{ + "conditional": { + "if": "modelCount === 0" + } +} +``` + ### Component Registry Integration All JSON page wrappers are registered in `component-registry.ts`: - `JSONModelDesigner` diff --git a/src/config/pages/flask-designer.json b/src/config/pages/flask-designer.json index be18833..b0a37c3 100644 --- a/src/config/pages/flask-designer.json +++ b/src/config/pages/flask-designer.json @@ -117,7 +117,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "createBlueprintDialogOpen", "value": true } @@ -150,9 +150,7 @@ "className": "p-4 space-y-2" }, "conditional": { - "source": "blueprintCount", - "operator": "gt", - "value": 0 + "if": "blueprintCount > 0" }, "children": [ { @@ -165,11 +163,7 @@ "className": "group cursor-pointer hover:bg-accent/10 transition-all duration-200 hover:shadow-lg hover:shadow-accent/20 border-2 hover:border-accent/50" }, "conditionalClass": { - "condition": { - "source": "selectedBlueprintId", - "operator": "eq", - "valueFrom": "item.id" - }, + "condition": "selectedBlueprintId === item.id", "trueClass": "bg-accent/20 border-accent shadow-md shadow-accent/30", "falseClass": "border-transparent" }, @@ -178,9 +172,9 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "selectedBlueprintId", - "valueFrom": "item.id" + "expression": "event.item.id" } ] } @@ -330,9 +324,7 @@ "className": "flex flex-col items-center justify-center h-full p-8 text-center" }, "conditional": { - "source": "blueprintCount", - "operator": "eq", - "value": 0 + "if": "blueprintCount === 0" }, "children": [ { @@ -367,7 +359,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "createBlueprintDialogOpen", "value": true } @@ -404,8 +396,7 @@ "className": "flex-1 overflow-auto p-8" }, "conditional": { - "source": "selectedBlueprint", - "operator": "truthy" + "if": "selectedBlueprint" }, "children": [ { @@ -467,7 +458,7 @@ "event": "onClick", "actions": [ { - "type": "toast", + "type": "show-toast", "message": "Edit blueprint coming soon", "variant": "info" } @@ -496,7 +487,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "createEndpointDialogOpen", "value": true } @@ -662,9 +653,7 @@ "className": "space-y-3" }, "conditional": { - "source": "endpointCount", - "operator": "gt", - "value": 0 + "if": "endpointCount > 0" }, "children": [ { @@ -743,7 +732,7 @@ "event": "onClick", "actions": [ { - "type": "toast", + "type": "show-toast", "message": "Edit endpoint coming soon", "variant": "info" } @@ -804,9 +793,7 @@ "className": "text-center py-12" }, "conditional": { - "source": "endpointCount", - "operator": "eq", - "value": 0 + "if": "endpointCount === 0" }, "children": [ { @@ -835,7 +822,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "createEndpointDialogOpen", "value": true } @@ -869,8 +856,7 @@ "className": "flex-1 flex items-center justify-center p-8" }, "conditional": { - "source": "selectedBlueprint", - "operator": "falsy" + "if": "!selectedBlueprint" }, "children": [ { diff --git a/src/config/pages/lambda-designer.json b/src/config/pages/lambda-designer.json index 4792bb3..b4d3c42 100644 --- a/src/config/pages/lambda-designer.json +++ b/src/config/pages/lambda-designer.json @@ -96,7 +96,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "createDialogOpen", "value": true } @@ -128,9 +128,7 @@ "className": "space-y-2" }, "conditional": { - "source": "lambdaCount", - "operator": "gt", - "value": 0 + "if": "lambdaCount > 0" }, "children": [ { @@ -145,9 +143,7 @@ "className": "flex flex-col items-center justify-center py-12 px-4 text-center" }, "conditional": { - "source": "lambdaCount", - "operator": "eq", - "value": 0 + "if": "lambdaCount === 0" }, "children": [ { @@ -191,8 +187,7 @@ "className": "flex-1 flex items-center justify-center p-8" }, "conditional": { - "source": "selectedLambda", - "operator": "truthy" + "if": "selectedLambda" }, "children": [ { @@ -248,8 +243,7 @@ "className": "flex-1 flex items-center justify-center p-8" }, "conditional": { - "source": "selectedLambda", - "operator": "falsy" + "if": "!selectedLambda" }, "children": [ { diff --git a/src/config/pages/molecules/label-with-badge.json b/src/config/pages/molecules/label-with-badge.json index b1f734a..5279e9c 100644 --- a/src/config/pages/molecules/label-with-badge.json +++ b/src/config/pages/molecules/label-with-badge.json @@ -20,9 +20,7 @@ "className": "text-xs" }, "conditional": { - "source": "badge", - "operator": "neq", - "value": null + "if": "badge !== null" } } ] diff --git a/src/config/pages/molecules/save-indicator.json b/src/config/pages/molecules/save-indicator.json index 6a5d313..f0655f9 100644 --- a/src/config/pages/molecules/save-indicator.json +++ b/src/config/pages/molecules/save-indicator.json @@ -33,9 +33,7 @@ "className": "flex items-center gap-1.5 text-xs text-muted-foreground" }, "conditional": { - "source": "lastSaved", - "operator": "neq", - "value": null + "if": "lastSaved !== null" }, "children": [ { diff --git a/src/config/pages/molecules/search-input.json b/src/config/pages/molecules/search-input.json index 37dfad8..02dd154 100644 --- a/src/config/pages/molecules/search-input.json +++ b/src/config/pages/molecules/search-input.json @@ -31,9 +31,9 @@ "event": "onChange", "actions": [ { - "type": "setState", + "type": "set-value", "target": "searchValue", - "valueFrom": "event.target.value" + "expression": "event.target.value" } ] } @@ -48,16 +48,14 @@ "className": "absolute right-1 h-7 w-7 p-0" }, "conditional": { - "source": "searchValue", - "operator": "neq", - "value": "" + "if": "searchValue !== \"\"" }, "events": [ { "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "searchValue", "value": "" } diff --git a/src/config/pages/style-designer.json b/src/config/pages/style-designer.json index cfe39a1..c8237c2 100644 --- a/src/config/pages/style-designer.json +++ b/src/config/pages/style-designer.json @@ -314,9 +314,9 @@ "event": "onValueChange", "actions": [ { - "type": "setState", + "type": "set-value", "target": "selectedTab", - "valueFrom": "event" + "expression": "event" } ] } @@ -571,7 +571,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "customColorDialogOpen", "value": true } @@ -602,9 +602,7 @@ "className": "text-center py-8" }, "conditional": { - "source": "customColorCount", - "operator": "eq", - "value": 0 + "if": "customColorCount === 0" }, "children": [ { @@ -630,9 +628,7 @@ "className": "text-sm text-muted-foreground" }, "conditional": { - "source": "customColorCount", - "operator": "gt", - "value": 0 + "if": "customColorCount > 0" }, "children": "Custom colors will be displayed here" } diff --git a/src/config/pages/workflow-designer.json b/src/config/pages/workflow-designer.json index 6486690..6fd6f93 100644 --- a/src/config/pages/workflow-designer.json +++ b/src/config/pages/workflow-designer.json @@ -84,7 +84,7 @@ "event": "onClick", "actions": [ { - "type": "setState", + "type": "set-value", "target": "createDialogOpen", "value": true } @@ -131,9 +131,7 @@ { "type": "div", "conditional": { - "source": "workflowCount", - "operator": "eq", - "value": 0 + "if": "workflowCount === 0" }, "props": { "className": "text-center py-8 text-muted-foreground" @@ -158,8 +156,7 @@ "className": "flex-1 flex items-center justify-center" }, "conditional": { - "source": "selectedWorkflow", - "operator": "falsy" + "if": "!selectedWorkflow" }, "children": [ { @@ -204,8 +201,7 @@ "className": "flex-1 p-6 overflow-auto" }, "conditional": { - "source": "selectedWorkflow", - "operator": "truthy" + "if": "selectedWorkflow" }, "children": [ { diff --git a/src/lib/json-ui/component-renderer.tsx b/src/lib/json-ui/component-renderer.tsx index 6a10850..281b3d9 100644 --- a/src/lib/json-ui/component-renderer.tsx +++ b/src/lib/json-ui/component-renderer.tsx @@ -102,7 +102,10 @@ export function ComponentRenderer({ component, data, context = {}, state, onEven ? handler.condition(mergedData as Record) : evaluateCondition(handler.condition, mergedData as Record)) if (conditionMet) { - onEvent(component.id, handler, e) + const eventPayload = typeof e === 'object' && e !== null + ? Object.assign(e as Record, context) + : e + onEvent(component.id, handler, eventPayload) } } }) diff --git a/src/lib/json-ui/expression-evaluator.ts b/src/lib/json-ui/expression-evaluator.ts index 97e88cb..ceaf751 100644 --- a/src/lib/json-ui/expression-evaluator.ts +++ b/src/lib/json-ui/expression-evaluator.ts @@ -26,6 +26,14 @@ export function evaluateExpression( const { data, event } = context try { + if (expression === 'event') { + return event + } + + if (expression === 'data') { + return data + } + // Handle direct data access: "data.fieldName" if (expression.startsWith('data.')) { return getNestedValue(data, expression.substring(5)) diff --git a/src/lib/json-ui/renderer.tsx b/src/lib/json-ui/renderer.tsx index f3e4098..e532df7 100644 --- a/src/lib/json-ui/renderer.tsx +++ b/src/lib/json-ui/renderer.tsx @@ -157,7 +157,10 @@ export function JSONUIRenderer({ : evaluateCondition(handler.condition, { ...dataMap, ...renderContext }) if (!conditionMet) return } - onAction?.(handler.actions, event) + const eventPayload = typeof event === 'object' && event !== null + ? Object.assign(event, renderContext) + : event + onAction?.(handler.actions, eventPayload) } }) }