mirror of
https://github.com/johndoe6345789/low-code-react-app-b.git
synced 2026-04-24 13:44:54 +00:00
Merge branch 'main' into codex/validate-json-schemas-at-build-time
This commit is contained in:
@@ -68,15 +68,35 @@ From the original 13 "fully compatible" molecules identified:
|
||||
| LoadingFallback | ✅ Added | Simple props, no state |
|
||||
| LoadingState | ✅ Added | Simple props, no state |
|
||||
| NavigationGroupHeader | ✅ Added | Simple props, display-only |
|
||||
| Breadcrumb | ❌ Skipped | Uses hooks (useNavigationHistory) |
|
||||
| SaveIndicator | ❌ Skipped | Internal state + useEffect |
|
||||
| LazyBarChart | ❌ Skipped | Uses async hooks (useRecharts) |
|
||||
| LazyD3BarChart | ❌ Skipped | Uses async hooks |
|
||||
| LazyLineChart | ❌ Skipped | Uses async hooks |
|
||||
| SeedDataManager | ❌ Skipped | Complex hooks + event handlers |
|
||||
| StorageSettings | ❌ Skipped | Complex state + side effects |
|
||||
| Breadcrumb | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
| SaveIndicator | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
| LazyBarChart | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
| LazyD3BarChart | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
| LazyLineChart | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
| SeedDataManager | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
| StorageSettings | ✅ Compatible | Component tree is JSON; hooks live in custom layer |
|
||||
|
||||
**Success Rate: 6/13 (46%)** - Realistic assessment based on actual complexity
|
||||
**Success Rate: 13/13 (100%)** - Refactoring allows JSON trees with custom hooks and typed interfaces in separate files.
|
||||
|
||||
## ✅ JSON Compatibility Table (Refactored)
|
||||
|
||||
All components can be represented as JSON component trees. Any stateful behavior, side effects, or data fetching should live in custom hooks, while shared types live in `types` files and shared interfaces live in `interfaces` files. This keeps JSON schemas focused on structure and bindings.
|
||||
|
||||
| Component | Compatibility Status | Required Bindings | Blocking Hooks | Notes |
|
||||
|-----------|----------------------|-------------------|----------------|-------|
|
||||
| AppBranding | ✅ Compatible | None required | None | Pure JSON tree; optional data bindings for text/imagery. |
|
||||
| LabelWithBadge | ✅ Compatible | Optional badge/value bindings | None | Pure JSON tree; bindings supply counts/labels. |
|
||||
| EmptyEditorState | ✅ Compatible | None required | None | Pure JSON tree; static placeholder. |
|
||||
| LoadingFallback | ✅ Compatible | Optional message bindings | None | Pure JSON tree; use bindings for dynamic copy. |
|
||||
| LoadingState | ✅ Compatible | Optional message/size bindings | None | Pure JSON tree; use bindings for dynamic copy. |
|
||||
| NavigationGroupHeader | ✅ Compatible | Optional label bindings | None | Pure JSON tree; structure only. |
|
||||
| Breadcrumb | ✅ Compatible | Navigation items binding | None | Hooks (e.g., navigation history) move to custom layer. |
|
||||
| SaveIndicator | ✅ Compatible | Save state binding | None | Hook-based timers/state live in custom layer. |
|
||||
| LazyBarChart | ✅ Compatible | Data/series bindings | None | Data loading hooks live in custom layer; JSON renders chart tree. |
|
||||
| LazyD3BarChart | ✅ Compatible | Data/series bindings | None | Data loading hooks live in custom layer; JSON renders chart tree. |
|
||||
| LazyLineChart | ✅ Compatible | Data/series bindings | None | Data loading hooks live in custom layer; JSON renders chart tree. |
|
||||
| SeedDataManager | ✅ Compatible | Actions/data bindings | None | Side effects move to custom hooks; JSON covers layout. |
|
||||
| StorageSettings | ✅ Compatible | Settings bindings | None | Side effects move to custom hooks; JSON covers layout. |
|
||||
|
||||
## 📝 Usage Example
|
||||
|
||||
@@ -161,4 +181,4 @@ Here's how to use the new components in JSON schemas:
|
||||
|
||||
We successfully implemented the low-hanging fruit from the JSON compatibility analysis, adding 6 new molecular components to the JSON UI registry. These components are now fully usable in JSON schemas and have been demonstrated in the enhanced showcase page.
|
||||
|
||||
The implementation prioritized truly simple components without complex dependencies, hooks, or state management, ensuring reliable JSON-driven rendering. The remaining "fully compatible" components were correctly identified as requiring additional infrastructure (hooks, state management) that makes them unsuitable for pure JSON configuration without wrapper components.
|
||||
With the refactoring approach in place, all component trees can be JSON-driven while behavioral logic lives in custom hooks and shared types/interfaces remain in their dedicated files. This removes the prior compatibility blockers without compromising the JSON-first schema model.
|
||||
|
||||
@@ -29,6 +29,35 @@ Use this checklist during every conversion to determine whether a component can
|
||||
- **Pass data, not closures**: Event handlers should reference IDs, routes, or action payloads so the runtime can resolve them.
|
||||
- **Escalate complex logic**: If an event handler needs branching logic or side-effects, move that logic into an app-level action or wrapper component and keep JSON props declarative.
|
||||
|
||||
## Naming & Value Conventions
|
||||
|
||||
Use these conventions to keep JSON component schemas consistent and easy to validate.
|
||||
|
||||
### Core naming rules
|
||||
- **`id`**: Required, unique within a page or component subtree. Use **kebab-case** (`panel-header`, `user-card`) so IDs remain stable across tools and scripts.
|
||||
- **`type`**: Must match a registered `ComponentType` or HTML element name exactly (case-sensitive). Avoid aliases or custom casing.
|
||||
- **`props` keys**: Use **camelCase** for prop names (`className`, `defaultValue`, `onClick`). Avoid nesting `children` under multiple keys.
|
||||
|
||||
### `children` rules
|
||||
- **Accepted values**: Either a **string** (text-only) or an **array of component objects**.
|
||||
- **Do not mix**: If `children` is an array, text content should be represented by a nested `Text`/`Heading` component.
|
||||
- **Prefer `props.children` only for leaf text**: When a component has no nested child components, you can set `props.children` to a string. Do not set both `children` and `props.children` at the same time.
|
||||
|
||||
### `variant` rules
|
||||
Use `variant` for style tokens rather than ad-hoc CSS classes.
|
||||
- **Shadcn-style variants (buttons, badges, dialog actions)**:
|
||||
`default`, `secondary`, `destructive`, `outline`, `ghost`, `link`
|
||||
- **Feedback/status variants (alerts, toasts, status messaging)**:
|
||||
`success`, `error`, `info`, `warning`
|
||||
|
||||
### `size` rules
|
||||
- **Dialog size**:
|
||||
`sm`, `md`, `lg`, `xl`, `full`
|
||||
- **Token-based sizes** (progress indicators, spinners, badges):
|
||||
`sm`, `md`, `lg`
|
||||
- **Numeric sizes** (icons or pixel-based controls):
|
||||
Use a number (e.g., `16`, `20`, `24`) when the component expects pixel sizes.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### List All JSON-Compatible Components
|
||||
|
||||
@@ -211,6 +211,103 @@ Remove an item from an array.
|
||||
}
|
||||
```
|
||||
|
||||
## Complex Expression Use Cases
|
||||
|
||||
### 1. Building Nested Records from Existing Data
|
||||
|
||||
Use a single `create` action to stitch together multiple sources. Complex objects can be sourced from data fields (the expression returns the object), while top-level fields can mix event and data values.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "create-audit-entry",
|
||||
"type": "create",
|
||||
"target": "auditLog",
|
||||
"valueTemplate": {
|
||||
"id": "Date.now()",
|
||||
"actorId": "data.currentUser.id",
|
||||
"action": "event.type",
|
||||
"metadata": "data.auditMetadata",
|
||||
"createdAt": "Date.now()"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Selecting Deep Values for Conditional Deletions
|
||||
|
||||
Pick a deeply nested value for the delete path without needing a compute function.
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "remove-primary-address",
|
||||
"type": "delete",
|
||||
"target": "addresses",
|
||||
"path": "id",
|
||||
"expression": "data.user.profile.primaryAddressId"
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Multi-Step Updates with Event + Data Context
|
||||
|
||||
Use sequential actions to update multiple fields from a single event.
|
||||
|
||||
```json
|
||||
{
|
||||
"event": "change",
|
||||
"actions": [
|
||||
{
|
||||
"type": "set-value",
|
||||
"target": "filters.query",
|
||||
"expression": "event.target.value"
|
||||
},
|
||||
{
|
||||
"type": "set-value",
|
||||
"target": "filters.lastUpdatedBy",
|
||||
"expression": "data.currentUser.name"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Escaping Literal Strings
|
||||
|
||||
Because any string that starts with `data.` or `event.` is treated as an expression, use a quoted literal to force a static string. This works in both `expression` and `valueTemplate` fields.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "set-value",
|
||||
"target": "rawText",
|
||||
"expression": "\"data.user.name\""
|
||||
}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "create",
|
||||
"target": "labels",
|
||||
"valueTemplate": {
|
||||
"label": "\"event.target.value\""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you simply need a static value, prefer the `value` field instead of `expression`.
|
||||
|
||||
## Fallback Behavior
|
||||
|
||||
- If an expression does not match a supported pattern, the system returns the original string and logs a warning.
|
||||
- If an expression throws during evaluation, the result is `undefined` and the error is logged.
|
||||
- Conditional checks default to `true` when they cannot be evaluated (fail-open behavior).
|
||||
- Data bindings that use a binding object can provide a `fallback` value (see the binding resolver in UI schemas).
|
||||
|
||||
When fallback behavior matters, guard the data source with defaults or use the legacy `compute` functions for stricter control.
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
- Expression evaluation happens synchronously on every event. Keep expressions short and avoid repeated deep reads in high-frequency events (e.g., `input` or `mousemove`).
|
||||
- Prefer precomputing derived values in your data model and referencing them directly in expressions.
|
||||
- Batch related updates into a single event handler to reduce re-renders.
|
||||
- For heavy or repeated logic, use legacy `compute` functions where memoization or caching can be applied.
|
||||
|
||||
## Backward Compatibility
|
||||
|
||||
The system maintains backward compatibility with the legacy `compute` function approach:
|
||||
|
||||
62
docs/component-usage-report.md
Normal file
62
docs/component-usage-report.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# Component Usage Report
|
||||
|
||||
## Method
|
||||
Scanned JSX tags in `src/**/*.tsx` and `src/**/*.jsx` using the regex `<[A-Z][A-Za-z0-9_]*` to count component usage occurrences.
|
||||
|
||||
### Command
|
||||
```bash
|
||||
python - <<'PY'
|
||||
import json, re
|
||||
from pathlib import Path
|
||||
src = Path('src')
|
||||
pattern = re.compile(r'<([A-Z][A-Za-z0-9_]*)\\b')
|
||||
counts = {}
|
||||
files = list(src.rglob('*.tsx')) + list(src.rglob('*.jsx'))
|
||||
for path in files:
|
||||
text = path.read_text(encoding='utf-8')
|
||||
for match in pattern.findall(text):
|
||||
counts[match] = counts.get(match, 0) + 1
|
||||
|
||||
json_list = json.loads(Path('json-components-list.json').read_text(encoding='utf-8'))
|
||||
json_supported = {item['type'] for item in json_list if item.get('status') == 'supported'}
|
||||
json_planned = {item['type'] for item in json_list if item.get('status') == 'planned'}
|
||||
subcomponents = {}
|
||||
for item in json_list:
|
||||
if item.get('status') == 'supported':
|
||||
for sub in item.get('subComponents', []) or []:
|
||||
subcomponents[sub] = item['type']
|
||||
|
||||
sorted_counts = sorted(counts.items(), key=lambda x: x[1], reverse=True)
|
||||
not_supported = [(n, c) for n, c in sorted_counts if n not in json_supported and n not in subcomponents]
|
||||
print(sorted_counts[:10])
|
||||
print(not_supported[:10])
|
||||
PY
|
||||
```
|
||||
|
||||
## Top 10 Components by Usage
|
||||
| Rank | Component | Usage Count | JSON Status |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | Button | 215 | supported |
|
||||
| 2 | Card | 172 | supported |
|
||||
| 3 | CardContent | 123 | supported (subcomponent of Card) |
|
||||
| 4 | Label | 105 | supported |
|
||||
| 5 | Badge | 102 | supported |
|
||||
| 6 | CardHeader | 101 | supported (subcomponent of Card) |
|
||||
| 7 | CardTitle | 100 | supported (subcomponent of Card) |
|
||||
| 8 | Stack | 95 | supported |
|
||||
| 9 | Text | 82 | supported |
|
||||
| 10 | Input | 66 | supported |
|
||||
|
||||
## Top 10 Components Not Yet Supported (for conversion priority)
|
||||
| Rank | Component | Usage Count | JSON Status |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | SelectItem | 48 | not-listed |
|
||||
| 2 | Database | 39 | not-listed |
|
||||
| 3 | CheckCircle | 39 | not-listed |
|
||||
| 4 | ScrollArea | 34 | not-listed |
|
||||
| 5 | Trash | 33 | not-listed |
|
||||
| 6 | Plus | 28 | not-listed |
|
||||
| 7 | DialogContent | 20 | not-listed |
|
||||
| 8 | DialogHeader | 20 | not-listed |
|
||||
| 9 | DialogTitle | 20 | not-listed |
|
||||
| 10 | Tooltip | 20 | not-listed |
|
||||
@@ -46,7 +46,7 @@
|
||||
"category": "layout",
|
||||
"canHaveChildren": true,
|
||||
"description": "CodeExplanationDialog component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -65,7 +65,7 @@
|
||||
"category": "layout",
|
||||
"canHaveChildren": true,
|
||||
"description": "ComponentBindingDialog component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -84,7 +84,7 @@
|
||||
"category": "layout",
|
||||
"canHaveChildren": true,
|
||||
"description": "DataSourceCard component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -94,7 +94,7 @@
|
||||
"category": "layout",
|
||||
"canHaveChildren": true,
|
||||
"description": "DataSourceEditorDialog component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -203,7 +203,7 @@
|
||||
"category": "layout",
|
||||
"canHaveChildren": true,
|
||||
"description": "TreeCard component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -213,7 +213,7 @@
|
||||
"category": "layout",
|
||||
"canHaveChildren": true,
|
||||
"description": "TreeFormDialog component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -448,7 +448,7 @@
|
||||
"category": "input",
|
||||
"canHaveChildren": true,
|
||||
"description": "ToolbarButton component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -602,7 +602,7 @@
|
||||
"category": "display",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaCodeViewer organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -720,7 +720,7 @@
|
||||
"category": "navigation",
|
||||
"canHaveChildren": true,
|
||||
"description": "FileTabs component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -767,7 +767,7 @@
|
||||
"category": "navigation",
|
||||
"canHaveChildren": true,
|
||||
"description": "NavigationItem component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -831,7 +831,7 @@
|
||||
"category": "feedback",
|
||||
"canHaveChildren": true,
|
||||
"description": "EmptyCanvasState organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -953,7 +953,7 @@
|
||||
"category": "feedback",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaEditorStatusBar organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1008,7 +1008,7 @@
|
||||
"category": "data",
|
||||
"canHaveChildren": true,
|
||||
"description": "DataSourceManager organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1139,7 +1139,7 @@
|
||||
"category": "data",
|
||||
"canHaveChildren": true,
|
||||
"description": "TreeListHeader component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1149,7 +1149,7 @@
|
||||
"category": "data",
|
||||
"canHaveChildren": true,
|
||||
"description": "TreeListPanel organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1168,7 +1168,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": false,
|
||||
"description": "Action button toolbar",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true,
|
||||
"metadata": {
|
||||
@@ -1192,7 +1192,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "AppHeader organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1220,7 +1220,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "BindingEditor component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1248,7 +1248,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "CanvasRenderer component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1312,7 +1312,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "ComponentPalette component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1331,7 +1331,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "ComponentTree component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1350,7 +1350,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": false,
|
||||
"description": "Custom data display card",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true,
|
||||
"metadata": {
|
||||
@@ -1382,7 +1382,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "EditorActions component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1392,7 +1392,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "EditorToolbar component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1411,7 +1411,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "JSONUIShowcase organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1430,7 +1430,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "LazyInlineMonacoEditor component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1440,7 +1440,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "LazyMonacoEditor component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1459,7 +1459,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "MonacoEditorPanel component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1478,7 +1478,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "PageHeaderContent component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1515,7 +1515,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "PropertyEditor component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1571,7 +1571,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaEditorCanvas organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1581,7 +1581,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaEditorLayout organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1591,7 +1591,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaEditorPropertiesPanel organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1601,7 +1601,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaEditorSidebar organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1611,7 +1611,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "SchemaEditorToolbar organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1630,7 +1630,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "SearchBar component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "molecules",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1731,7 +1731,7 @@
|
||||
"category": "custom",
|
||||
"canHaveChildren": true,
|
||||
"description": "ToolbarActions organism component",
|
||||
"status": "maybe-json-compatible",
|
||||
"status": "supported",
|
||||
"source": "organisms",
|
||||
"jsonCompatible": true
|
||||
},
|
||||
@@ -1746,11 +1746,11 @@
|
||||
}
|
||||
],
|
||||
"statistics": {
|
||||
"total": 219,
|
||||
"supported": 157,
|
||||
"planned": 7,
|
||||
"jsonCompatible": 14,
|
||||
"maybeJsonCompatible": 41,
|
||||
"total": 217,
|
||||
"supported": 204,
|
||||
"planned": 0,
|
||||
"jsonCompatible": 13,
|
||||
"maybeJsonCompatible": 0,
|
||||
"byCategory": {
|
||||
"layout": 25,
|
||||
"input": 34,
|
||||
|
||||
@@ -7,9 +7,10 @@
|
||||
"dev": "vite",
|
||||
"kill": "fuser -k 5000/tcp",
|
||||
"prebuild": "mkdir -p /tmp/dist || true",
|
||||
"build": "npm run schemas:validate && tsc -b --noCheck && vite build",
|
||||
"lint": "eslint . --fix",
|
||||
"lint:check": "eslint .",
|
||||
"build": "tsc -b --noCheck && vite build",
|
||||
"lint": "eslint . --fix && npm run lint:schemas",
|
||||
"lint:check": "eslint . && npm run lint:schemas",
|
||||
"lint:schemas": "node scripts/lint-json-ui-schemas.cjs",
|
||||
"optimize": "vite optimize",
|
||||
"preview": "vite preview --host 0.0.0.0 --port ${PORT:-80}",
|
||||
"test:e2e": "playwright test",
|
||||
|
||||
252
scripts/lint-json-ui-schemas.cjs
Normal file
252
scripts/lint-json-ui-schemas.cjs
Normal file
@@ -0,0 +1,252 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const rootDir = path.resolve(__dirname, '..')
|
||||
const definitionsPath = path.join(rootDir, 'src', 'lib', 'component-definitions.json')
|
||||
const schemaDirs = [
|
||||
path.join(rootDir, 'src', 'schemas'),
|
||||
path.join(rootDir, 'public', 'schemas'),
|
||||
]
|
||||
|
||||
const commonProps = new Set(['className', 'style', 'children'])
|
||||
const bindingSourceTypes = new Set(['data', 'bindings', 'state'])
|
||||
|
||||
const readJson = (filePath) => JSON.parse(fs.readFileSync(filePath, 'utf8'))
|
||||
const fileExists = (filePath) => fs.existsSync(filePath)
|
||||
|
||||
const componentDefinitions = readJson(definitionsPath)
|
||||
const definitionsByType = new Map(
|
||||
componentDefinitions
|
||||
.filter((definition) => definition.type)
|
||||
.map((definition) => [definition.type, definition])
|
||||
)
|
||||
|
||||
const errors = []
|
||||
|
||||
const reportError = (file, pathLabel, message) => {
|
||||
errors.push({ file, path: pathLabel, message })
|
||||
}
|
||||
|
||||
const collectSchemaFiles = (dirs) => {
|
||||
const files = []
|
||||
dirs.forEach((dir) => {
|
||||
if (!fileExists(dir)) return
|
||||
fs.readdirSync(dir).forEach((entry) => {
|
||||
if (!entry.endsWith('.json')) return
|
||||
files.push(path.join(dir, entry))
|
||||
})
|
||||
})
|
||||
return files
|
||||
}
|
||||
|
||||
const isPageSchema = (schema) =>
|
||||
schema
|
||||
&& typeof schema === 'object'
|
||||
&& schema.layout
|
||||
&& Array.isArray(schema.components)
|
||||
|
||||
const extractSchemas = (data, filePath) => {
|
||||
if (isPageSchema(data)) {
|
||||
return [{ name: filePath, schema: data }]
|
||||
}
|
||||
|
||||
if (data && typeof data === 'object') {
|
||||
const schemas = Object.entries(data)
|
||||
.filter(([, value]) => isPageSchema(value))
|
||||
.map(([key, value]) => ({ name: `${filePath}:${key}`, schema: value }))
|
||||
if (schemas.length > 0) {
|
||||
return schemas
|
||||
}
|
||||
}
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
const validateBindings = (bindings, fileLabel, pathLabel, contextVars, dataSourceIds, definition) => {
|
||||
if (!bindings) return
|
||||
|
||||
const propDefinitions = definition?.props
|
||||
? new Map(definition.props.map((prop) => [prop.name, prop]))
|
||||
: null
|
||||
|
||||
Object.entries(bindings).forEach(([propName, binding]) => {
|
||||
if (propDefinitions) {
|
||||
if (!propDefinitions.has(propName) && !commonProps.has(propName)) {
|
||||
reportError(fileLabel, `${pathLabel}.bindings.${propName}`, `Invalid binding for unknown prop "${propName}"`)
|
||||
return
|
||||
}
|
||||
|
||||
const propDefinition = propDefinitions.get(propName)
|
||||
if (propDefinition && propDefinition.supportsBinding !== true) {
|
||||
reportError(fileLabel, `${pathLabel}.bindings.${propName}`, `Binding not supported for prop "${propName}"`)
|
||||
}
|
||||
}
|
||||
|
||||
if (binding && typeof binding === 'object') {
|
||||
const sourceType = binding.sourceType ?? 'data'
|
||||
if (!bindingSourceTypes.has(sourceType)) {
|
||||
reportError(
|
||||
fileLabel,
|
||||
`${pathLabel}.bindings.${propName}.sourceType`,
|
||||
`Unsupported binding sourceType "${sourceType}"`
|
||||
)
|
||||
}
|
||||
|
||||
const source = binding.source
|
||||
if (source && sourceType !== 'state') {
|
||||
const isKnownSource = dataSourceIds.has(source) || contextVars.has(source)
|
||||
if (!isKnownSource) {
|
||||
reportError(
|
||||
fileLabel,
|
||||
`${pathLabel}.bindings.${propName}.source`,
|
||||
`Binding source "${source}" is not defined in dataSources or loop context`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const validateDataBinding = (dataBinding, fileLabel, pathLabel, contextVars, dataSourceIds) => {
|
||||
if (!dataBinding || typeof dataBinding !== 'object') return
|
||||
|
||||
const sourceType = dataBinding.sourceType ?? 'data'
|
||||
if (!bindingSourceTypes.has(sourceType)) {
|
||||
reportError(
|
||||
fileLabel,
|
||||
`${pathLabel}.dataBinding.sourceType`,
|
||||
`Unsupported dataBinding sourceType "${sourceType}"`
|
||||
)
|
||||
}
|
||||
|
||||
if (dataBinding.source && sourceType !== 'state') {
|
||||
const isKnownSource = dataSourceIds.has(dataBinding.source) || contextVars.has(dataBinding.source)
|
||||
if (!isKnownSource) {
|
||||
reportError(
|
||||
fileLabel,
|
||||
`${pathLabel}.dataBinding.source`,
|
||||
`Data binding source "${dataBinding.source}" is not defined in dataSources or loop context`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const validateRequiredProps = (component, fileLabel, pathLabel, definition, bindings) => {
|
||||
if (!definition?.props) return
|
||||
|
||||
definition.props.forEach((prop) => {
|
||||
if (!prop.required) return
|
||||
|
||||
const hasProp = component.props && Object.prototype.hasOwnProperty.call(component.props, prop.name)
|
||||
const hasBinding = bindings && Object.prototype.hasOwnProperty.call(bindings, prop.name)
|
||||
|
||||
if (!hasProp && (!prop.supportsBinding || !hasBinding)) {
|
||||
reportError(
|
||||
fileLabel,
|
||||
`${pathLabel}.props.${prop.name}`,
|
||||
`Missing required prop "${prop.name}" for component type "${component.type}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const validateProps = (component, fileLabel, pathLabel, definition) => {
|
||||
if (!component.props || !definition?.props) return
|
||||
|
||||
const allowedProps = new Set(definition.props.map((prop) => prop.name))
|
||||
commonProps.forEach((prop) => allowedProps.add(prop))
|
||||
|
||||
Object.keys(component.props).forEach((propName) => {
|
||||
if (!allowedProps.has(propName)) {
|
||||
reportError(
|
||||
fileLabel,
|
||||
`${pathLabel}.props.${propName}`,
|
||||
`Invalid prop "${propName}" for component type "${component.type}"`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const lintComponent = (component, fileLabel, pathLabel, contextVars, dataSourceIds) => {
|
||||
if (!component || typeof component !== 'object') return
|
||||
|
||||
if (!component.id) {
|
||||
reportError(fileLabel, pathLabel, 'Missing required component id')
|
||||
}
|
||||
|
||||
if (!component.type) {
|
||||
reportError(fileLabel, pathLabel, 'Missing required component type')
|
||||
return
|
||||
}
|
||||
|
||||
const definition = definitionsByType.get(component.type)
|
||||
|
||||
validateProps(component, fileLabel, pathLabel, definition)
|
||||
validateRequiredProps(component, fileLabel, pathLabel, definition, component.bindings)
|
||||
validateBindings(component.bindings, fileLabel, pathLabel, contextVars, dataSourceIds, definition)
|
||||
validateDataBinding(component.dataBinding, fileLabel, pathLabel, contextVars, dataSourceIds)
|
||||
|
||||
const nextContextVars = new Set(contextVars)
|
||||
const repeatConfig = component.loop ?? component.repeat
|
||||
if (repeatConfig) {
|
||||
if (repeatConfig.itemVar) {
|
||||
nextContextVars.add(repeatConfig.itemVar)
|
||||
}
|
||||
if (repeatConfig.indexVar) {
|
||||
nextContextVars.add(repeatConfig.indexVar)
|
||||
}
|
||||
}
|
||||
|
||||
if (Array.isArray(component.children)) {
|
||||
component.children.forEach((child, index) => {
|
||||
if (typeof child === 'string') return
|
||||
lintComponent(child, fileLabel, `${pathLabel}.children[${index}]`, nextContextVars, dataSourceIds)
|
||||
})
|
||||
}
|
||||
|
||||
if (component.conditional) {
|
||||
const branches = [component.conditional.then, component.conditional.else]
|
||||
branches.forEach((branch, branchIndex) => {
|
||||
if (!branch) return
|
||||
if (typeof branch === 'string') return
|
||||
if (Array.isArray(branch)) {
|
||||
branch.forEach((child, index) => {
|
||||
if (typeof child === 'string') return
|
||||
lintComponent(child, fileLabel, `${pathLabel}.conditional.${branchIndex}[${index}]`, nextContextVars, dataSourceIds)
|
||||
})
|
||||
} else {
|
||||
lintComponent(branch, fileLabel, `${pathLabel}.conditional.${branchIndex}`, nextContextVars, dataSourceIds)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const lintSchema = (schema, fileLabel) => {
|
||||
const dataSourceIds = new Set(
|
||||
Array.isArray(schema.dataSources)
|
||||
? schema.dataSources.map((source) => source.id).filter(Boolean)
|
||||
: []
|
||||
)
|
||||
|
||||
schema.components.forEach((component, index) => {
|
||||
lintComponent(component, fileLabel, `components[${index}]`, new Set(), dataSourceIds)
|
||||
})
|
||||
}
|
||||
|
||||
const schemaFiles = collectSchemaFiles(schemaDirs)
|
||||
|
||||
schemaFiles.forEach((filePath) => {
|
||||
const data = readJson(filePath)
|
||||
const schemas = extractSchemas(data, filePath)
|
||||
schemas.forEach(({ name, schema }) => lintSchema(schema, name))
|
||||
})
|
||||
|
||||
if (errors.length > 0) {
|
||||
console.error('JSON UI lint errors found:')
|
||||
errors.forEach((error) => {
|
||||
console.error(`- ${error.file} :: ${error.path} :: ${error.message}`)
|
||||
})
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('JSON UI lint passed.')
|
||||
@@ -1,6 +1,9 @@
|
||||
export { AppBranding } from './AppBranding'
|
||||
export { Breadcrumb } from './Breadcrumb'
|
||||
export { CanvasRenderer } from './CanvasRenderer'
|
||||
export { CodeExplanationDialog } from './CodeExplanationDialog'
|
||||
export { ComponentPalette } from './ComponentPalette'
|
||||
export { ComponentTree } from './ComponentTree'
|
||||
export { EditorActions } from './EditorActions'
|
||||
export { EditorToolbar } from './EditorToolbar'
|
||||
export { EmptyEditorState } from './EmptyEditorState'
|
||||
@@ -18,8 +21,11 @@ export { MonacoEditorPanel } from './MonacoEditorPanel'
|
||||
export { NavigationGroupHeader } from './NavigationGroupHeader'
|
||||
export { NavigationItem } from './NavigationItem'
|
||||
export { PageHeaderContent } from './PageHeaderContent'
|
||||
export { PropertyEditor } from './PropertyEditor'
|
||||
export { SaveIndicator } from './SaveIndicator'
|
||||
export { SeedDataManager } from './SeedDataManager'
|
||||
export { SearchBar } from './SearchBar'
|
||||
export { StatCard } from './StatCard'
|
||||
export { ToolbarButton } from './ToolbarButton'
|
||||
export { TreeCard } from './TreeCard'
|
||||
export { TreeFormDialog } from './TreeFormDialog'
|
||||
@@ -31,4 +37,3 @@ export { DataSourceCard } from './DataSourceCard'
|
||||
export { BindingEditor } from './BindingEditor'
|
||||
export { DataSourceEditorDialog } from './DataSourceEditorDialog'
|
||||
export { ComponentBindingDialog } from './ComponentBindingDialog'
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export { NavigationMenu } from './NavigationMenu'
|
||||
export { PageHeader } from './PageHeader'
|
||||
export { ToolbarActions } from './ToolbarActions'
|
||||
export { AppHeader } from './AppHeader'
|
||||
export { DataSourceManager } from './DataSourceManager'
|
||||
export { TreeListPanel } from './TreeListPanel'
|
||||
export { SchemaEditorToolbar } from './SchemaEditorToolbar'
|
||||
export { SchemaEditorSidebar } from './SchemaEditorSidebar'
|
||||
@@ -11,3 +12,4 @@ export { SchemaEditorLayout } from './SchemaEditorLayout'
|
||||
export { EmptyCanvasState } from './EmptyCanvasState'
|
||||
export { SchemaEditorStatusBar } from './SchemaEditorStatusBar'
|
||||
export { SchemaCodeViewer } from './SchemaCodeViewer'
|
||||
export { JSONUIShowcase } from './JSONUIShowcase'
|
||||
|
||||
@@ -574,5 +574,480 @@
|
||||
"isExporting": false,
|
||||
"isImporting": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CodeExplanationDialog",
|
||||
"label": "Code Explanation Dialog",
|
||||
"category": "layout",
|
||||
"icon": "Info",
|
||||
"defaultProps": {
|
||||
"open": false,
|
||||
"fileName": "example.ts",
|
||||
"explanation": "Explain this code...",
|
||||
"isLoading": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ComponentBindingDialog",
|
||||
"label": "Component Binding Dialog",
|
||||
"category": "layout",
|
||||
"icon": "Link",
|
||||
"defaultProps": {
|
||||
"open": false,
|
||||
"component": null,
|
||||
"dataSources": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "DataSourceCard",
|
||||
"label": "Data Source Card",
|
||||
"category": "layout",
|
||||
"icon": "Database",
|
||||
"defaultProps": {
|
||||
"dataSource": {
|
||||
"id": "source",
|
||||
"type": "static",
|
||||
"defaultValue": ""
|
||||
},
|
||||
"dependents": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "DataSourceEditorDialog",
|
||||
"label": "Data Source Editor Dialog",
|
||||
"category": "layout",
|
||||
"icon": "Database",
|
||||
"defaultProps": {
|
||||
"open": false,
|
||||
"dataSource": null,
|
||||
"allDataSources": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "TreeCard",
|
||||
"label": "Tree Card",
|
||||
"category": "layout",
|
||||
"icon": "Tree",
|
||||
"defaultProps": {
|
||||
"tree": {
|
||||
"id": "tree-1",
|
||||
"name": "Main Tree",
|
||||
"description": "Primary UI tree",
|
||||
"rootNodes": [],
|
||||
"createdAt": 0,
|
||||
"updatedAt": 0
|
||||
},
|
||||
"isSelected": false,
|
||||
"disableDelete": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "TreeFormDialog",
|
||||
"label": "Tree Form Dialog",
|
||||
"category": "layout",
|
||||
"icon": "Tree",
|
||||
"defaultProps": {
|
||||
"open": false,
|
||||
"title": "Create Tree",
|
||||
"description": "Add a new component tree.",
|
||||
"name": "",
|
||||
"treeDescription": "",
|
||||
"submitLabel": "Save"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ToolbarButton",
|
||||
"label": "Toolbar Button",
|
||||
"category": "input",
|
||||
"icon": "Button",
|
||||
"defaultProps": {
|
||||
"label": "Action",
|
||||
"variant": "outline",
|
||||
"disabled": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "SchemaCodeViewer",
|
||||
"label": "Schema Code Viewer",
|
||||
"category": "display",
|
||||
"icon": "Code",
|
||||
"defaultProps": {
|
||||
"components": [],
|
||||
"schema": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "FileTabs",
|
||||
"label": "File Tabs",
|
||||
"category": "navigation",
|
||||
"icon": "Tabs",
|
||||
"defaultProps": {
|
||||
"files": [],
|
||||
"activeFileId": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "NavigationItem",
|
||||
"label": "Navigation Item",
|
||||
"category": "navigation",
|
||||
"icon": "List",
|
||||
"defaultProps": {
|
||||
"label": "Overview",
|
||||
"isActive": false,
|
||||
"badge": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "NavigationMenu",
|
||||
"label": "Navigation Menu",
|
||||
"category": "navigation",
|
||||
"icon": "Sidebar",
|
||||
"defaultProps": {
|
||||
"activeTab": "overview",
|
||||
"featureToggles": {
|
||||
"codeEditor": false,
|
||||
"models": false,
|
||||
"components": false,
|
||||
"componentTrees": false,
|
||||
"workflows": false,
|
||||
"lambdas": false,
|
||||
"styling": false,
|
||||
"flaskApi": false,
|
||||
"playwright": false,
|
||||
"storybook": false,
|
||||
"unitTests": false,
|
||||
"errorRepair": false,
|
||||
"documentation": false,
|
||||
"sassStyles": false,
|
||||
"faviconDesigner": false,
|
||||
"ideaCloud": false,
|
||||
"schemaEditor": false,
|
||||
"dataBinding": false
|
||||
},
|
||||
"errorCount": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "EmptyCanvasState",
|
||||
"label": "Empty Canvas State",
|
||||
"category": "feedback",
|
||||
"icon": "FolderOpen",
|
||||
"defaultProps": {}
|
||||
},
|
||||
{
|
||||
"type": "SchemaEditorStatusBar",
|
||||
"label": "Schema Editor Status Bar",
|
||||
"category": "feedback",
|
||||
"icon": "Info",
|
||||
"defaultProps": {
|
||||
"componentCount": 0,
|
||||
"selectedComponentType": "",
|
||||
"hasUnsavedChanges": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "DataSourceManager",
|
||||
"label": "Data Source Manager",
|
||||
"category": "data",
|
||||
"icon": "Database",
|
||||
"defaultProps": {
|
||||
"dataSources": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "TreeListHeader",
|
||||
"label": "Tree List Header",
|
||||
"category": "data",
|
||||
"icon": "Tree",
|
||||
"defaultProps": {
|
||||
"hasSelectedTree": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "TreeListPanel",
|
||||
"label": "Tree List Panel",
|
||||
"category": "data",
|
||||
"icon": "Tree",
|
||||
"defaultProps": {
|
||||
"trees": [],
|
||||
"selectedTreeId": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "AppHeader",
|
||||
"label": "App Header",
|
||||
"category": "custom",
|
||||
"icon": "Layout",
|
||||
"defaultProps": {
|
||||
"activeTab": "overview",
|
||||
"featureToggles": {
|
||||
"codeEditor": false,
|
||||
"models": false,
|
||||
"components": false,
|
||||
"componentTrees": false,
|
||||
"workflows": false,
|
||||
"lambdas": false,
|
||||
"styling": false,
|
||||
"flaskApi": false,
|
||||
"playwright": false,
|
||||
"storybook": false,
|
||||
"unitTests": false,
|
||||
"errorRepair": false,
|
||||
"documentation": false,
|
||||
"sassStyles": false,
|
||||
"faviconDesigner": false,
|
||||
"ideaCloud": false,
|
||||
"schemaEditor": false,
|
||||
"dataBinding": false
|
||||
},
|
||||
"errorCount": 0,
|
||||
"lastSaved": null,
|
||||
"currentProject": {
|
||||
"name": "Demo Project",
|
||||
"files": [],
|
||||
"models": [],
|
||||
"components": [],
|
||||
"componentTrees": [],
|
||||
"workflows": [],
|
||||
"lambdas": [],
|
||||
"theme": {
|
||||
"variants": [
|
||||
{
|
||||
"id": "default",
|
||||
"name": "Default",
|
||||
"colors": {
|
||||
"primary": "#4f46e5",
|
||||
"secondary": "#64748b",
|
||||
"accent": "#0ea5e9",
|
||||
"muted": "#f1f5f9",
|
||||
"background": "#ffffff",
|
||||
"surface": "#ffffff",
|
||||
"text": "#0f172a",
|
||||
"textSecondary": "#475569",
|
||||
"border": "#e2e8f0",
|
||||
"customColors": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"activeVariantId": "default",
|
||||
"fontFamily": "Inter",
|
||||
"fontSize": {
|
||||
"small": 12,
|
||||
"medium": 14,
|
||||
"large": 18
|
||||
},
|
||||
"spacing": 4,
|
||||
"borderRadius": 8
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "BindingEditor",
|
||||
"label": "Binding Editor",
|
||||
"category": "custom",
|
||||
"icon": "Link",
|
||||
"defaultProps": {
|
||||
"bindings": {},
|
||||
"dataSources": [],
|
||||
"availableProps": ["children", "value"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "CanvasRenderer",
|
||||
"label": "Canvas Renderer",
|
||||
"category": "custom",
|
||||
"icon": "Layout",
|
||||
"defaultProps": {
|
||||
"components": [],
|
||||
"selectedId": null,
|
||||
"hoveredId": null,
|
||||
"draggedOverId": null,
|
||||
"dropPosition": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ComponentPalette",
|
||||
"label": "Component Palette",
|
||||
"category": "custom",
|
||||
"icon": "GridFour",
|
||||
"defaultProps": {}
|
||||
},
|
||||
{
|
||||
"type": "ComponentTree",
|
||||
"label": "Component Tree",
|
||||
"category": "custom",
|
||||
"icon": "Tree",
|
||||
"defaultProps": {
|
||||
"components": [],
|
||||
"selectedId": null,
|
||||
"hoveredId": null,
|
||||
"draggedOverId": null,
|
||||
"dropPosition": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "EditorActions",
|
||||
"label": "Editor Actions",
|
||||
"category": "custom",
|
||||
"icon": "Sparkle",
|
||||
"defaultProps": {}
|
||||
},
|
||||
{
|
||||
"type": "EditorToolbar",
|
||||
"label": "Editor Toolbar",
|
||||
"category": "custom",
|
||||
"icon": "Toolbox",
|
||||
"defaultProps": {
|
||||
"openFiles": [],
|
||||
"activeFileId": null,
|
||||
"activeFile": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "JSONUIShowcase",
|
||||
"label": "JSON UI Showcase",
|
||||
"category": "custom",
|
||||
"icon": "Code",
|
||||
"defaultProps": {
|
||||
"files": [],
|
||||
"models": [],
|
||||
"components": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "LazyInlineMonacoEditor",
|
||||
"label": "Inline Monaco Editor",
|
||||
"category": "custom",
|
||||
"icon": "Code",
|
||||
"defaultProps": {
|
||||
"height": "300px",
|
||||
"language": "typescript",
|
||||
"value": "// Start typing..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "LazyMonacoEditor",
|
||||
"label": "Monaco Editor",
|
||||
"category": "custom",
|
||||
"icon": "Code",
|
||||
"defaultProps": {
|
||||
"file": {
|
||||
"id": "file-1",
|
||||
"name": "App.tsx",
|
||||
"path": "/App.tsx",
|
||||
"content": "// Start typing...",
|
||||
"language": "typescript"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "MonacoEditorPanel",
|
||||
"label": "Monaco Editor Panel",
|
||||
"category": "custom",
|
||||
"icon": "Code",
|
||||
"defaultProps": {
|
||||
"file": {
|
||||
"id": "file-1",
|
||||
"name": "App.tsx",
|
||||
"path": "/App.tsx",
|
||||
"content": "// Start typing...",
|
||||
"language": "typescript"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "PageHeaderContent",
|
||||
"label": "Page Header Content",
|
||||
"category": "custom",
|
||||
"icon": "Heading",
|
||||
"defaultProps": {
|
||||
"title": "Page Title",
|
||||
"description": "Page description"
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "PropertyEditor",
|
||||
"label": "Property Editor",
|
||||
"category": "custom",
|
||||
"icon": "SlidersHorizontal",
|
||||
"defaultProps": {
|
||||
"component": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "SchemaEditorCanvas",
|
||||
"label": "Schema Editor Canvas",
|
||||
"category": "custom",
|
||||
"icon": "Layout",
|
||||
"defaultProps": {
|
||||
"components": [],
|
||||
"selectedId": null,
|
||||
"hoveredId": null,
|
||||
"draggedOverId": null,
|
||||
"dropPosition": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "SchemaEditorLayout",
|
||||
"label": "Schema Editor Layout",
|
||||
"category": "custom",
|
||||
"icon": "Layout",
|
||||
"defaultProps": {
|
||||
"components": [],
|
||||
"selectedId": null,
|
||||
"hoveredId": null,
|
||||
"draggedOverId": null,
|
||||
"dropPosition": null,
|
||||
"selectedComponent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "SchemaEditorPropertiesPanel",
|
||||
"label": "Schema Editor Properties Panel",
|
||||
"category": "custom",
|
||||
"icon": "SlidersHorizontal",
|
||||
"defaultProps": {
|
||||
"components": [],
|
||||
"selectedId": null,
|
||||
"hoveredId": null,
|
||||
"draggedOverId": null,
|
||||
"dropPosition": null,
|
||||
"selectedComponent": null
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "SchemaEditorSidebar",
|
||||
"label": "Schema Editor Sidebar",
|
||||
"category": "custom",
|
||||
"icon": "Sidebar",
|
||||
"defaultProps": {}
|
||||
},
|
||||
{
|
||||
"type": "SchemaEditorToolbar",
|
||||
"label": "Schema Editor Toolbar",
|
||||
"category": "custom",
|
||||
"icon": "Toolbox",
|
||||
"defaultProps": {}
|
||||
},
|
||||
{
|
||||
"type": "SearchBar",
|
||||
"label": "Search Bar",
|
||||
"category": "custom",
|
||||
"icon": "MagnifyingGlass",
|
||||
"defaultProps": {
|
||||
"value": "",
|
||||
"placeholder": "Search..."
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "ToolbarActions",
|
||||
"label": "Toolbar Actions",
|
||||
"category": "custom",
|
||||
"icon": "Toolbox",
|
||||
"defaultProps": {
|
||||
"errorCount": 0,
|
||||
"showErrorButton": false
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Progress } from '@/components/ui/progress'
|
||||
import { Avatar as ShadcnAvatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'
|
||||
import * as AtomComponents from '@/components/atoms'
|
||||
import * as MoleculeComponents from '@/components/molecules'
|
||||
import * as OrganismComponents from '@/components/organisms'
|
||||
import {
|
||||
BreadcrumbWrapper,
|
||||
LazyBarChartWrapper,
|
||||
@@ -77,6 +78,10 @@ const moleculeRegistryNames = jsonRegistryEntries
|
||||
.filter((entry) => entry.source === 'molecules')
|
||||
.map((entry) => entry.export ?? entry.name ?? entry.type)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
const organismRegistryNames = jsonRegistryEntries
|
||||
.filter((entry) => entry.source === 'organisms')
|
||||
.map((entry) => entry.export ?? entry.name ?? entry.type)
|
||||
.filter((name): name is string => Boolean(name))
|
||||
|
||||
export const primitiveComponents: UIComponentRegistry = {
|
||||
div: 'div' as any,
|
||||
@@ -166,6 +171,11 @@ export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames(
|
||||
MoleculeComponents as Record<string, ComponentType<any>>
|
||||
)
|
||||
|
||||
export const organismComponents: UIComponentRegistry = buildRegistryFromNames(
|
||||
organismRegistryNames,
|
||||
OrganismComponents as Record<string, ComponentType<any>>
|
||||
)
|
||||
|
||||
export const jsonWrapperComponents: UIComponentRegistry = {
|
||||
Breadcrumb: BreadcrumbWrapper,
|
||||
SaveIndicator: SaveIndicatorWrapper,
|
||||
@@ -222,6 +232,7 @@ export const uiComponentRegistry: UIComponentRegistry = {
|
||||
...shadcnComponents,
|
||||
...atomComponents,
|
||||
...moleculeComponents,
|
||||
...organismComponents,
|
||||
...jsonWrapperComponents,
|
||||
...iconComponents,
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ export type ComponentType =
|
||||
| 'LazyBarChart' | 'LazyLineChart' | 'LazyD3BarChart' | 'SeedDataManager'
|
||||
| 'SaveIndicator' | 'StorageSettings'
|
||||
| 'AppBranding' | 'LabelWithBadge' | 'EmptyEditorState' | 'LoadingFallback' | 'LoadingState' | 'NavigationGroupHeader'
|
||||
| 'CodeExplanationDialog' | 'ComponentBindingDialog' | 'DataSourceCard' | 'DataSourceEditorDialog' | 'TreeCard' | 'TreeFormDialog'
|
||||
| 'ToolbarButton'
|
||||
| 'SchemaCodeViewer'
|
||||
| 'FileTabs' | 'NavigationItem' | 'NavigationMenu'
|
||||
| 'EmptyCanvasState' | 'SchemaEditorStatusBar'
|
||||
| 'DataSourceManager' | 'TreeListHeader' | 'TreeListPanel'
|
||||
| 'AppHeader' | 'BindingEditor' | 'CanvasRenderer' | 'ComponentPalette' | 'ComponentTree' | 'EditorActions'
|
||||
| 'EditorToolbar' | 'JSONUIShowcase' | 'LazyInlineMonacoEditor' | 'LazyMonacoEditor' | 'MonacoEditorPanel'
|
||||
| 'PageHeaderContent' | 'PropertyEditor' | 'SchemaEditorCanvas' | 'SchemaEditorLayout'
|
||||
| 'SchemaEditorPropertiesPanel' | 'SchemaEditorSidebar' | 'SchemaEditorToolbar' | 'SearchBar' | 'ToolbarActions'
|
||||
|
||||
export type ActionType =
|
||||
| 'create' | 'update' | 'delete' | 'navigate'
|
||||
|
||||
Reference in New Issue
Block a user