From 5a989b38db8868b0e62d483170fe609ec8d973ca Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Sun, 18 Jan 2026 12:03:05 +0000 Subject: [PATCH] Import JSON UI wrappers directly --- json-components-registry.json | 18 +- src/lib/component-definitions.ts | 77 ++++ src/lib/json-ui/component-registry.ts | 25 ++ src/lib/json-ui/renderer.tsx | 15 +- src/lib/json-ui/wrappers/Breadcrumb.tsx | 29 ++ src/lib/json-ui/wrappers/LazyBarChart.tsx | 49 +++ src/lib/json-ui/wrappers/LazyD3BarChart.tsx | 96 +++++ src/lib/json-ui/wrappers/LazyLineChart.tsx | 49 +++ src/lib/json-ui/wrappers/SaveIndicator.tsx | 41 +++ src/lib/json-ui/wrappers/SeedDataManager.tsx | 108 ++++++ src/lib/json-ui/wrappers/StorageSettings.tsx | 117 +++++++ .../wrappers/definitions/breadcrumb.json | 106 ++++++ .../wrappers/definitions/save-indicator.json | 36 ++ .../definitions/seed-data-manager.json | 157 +++++++++ .../definitions/storage-settings.json | 331 ++++++++++++++++++ src/types/json-ui.ts | 1 + 16 files changed, 1243 insertions(+), 12 deletions(-) create mode 100644 src/lib/json-ui/wrappers/Breadcrumb.tsx create mode 100644 src/lib/json-ui/wrappers/LazyBarChart.tsx create mode 100644 src/lib/json-ui/wrappers/LazyD3BarChart.tsx create mode 100644 src/lib/json-ui/wrappers/LazyLineChart.tsx create mode 100644 src/lib/json-ui/wrappers/SaveIndicator.tsx create mode 100644 src/lib/json-ui/wrappers/SeedDataManager.tsx create mode 100644 src/lib/json-ui/wrappers/StorageSettings.tsx create mode 100644 src/lib/json-ui/wrappers/definitions/breadcrumb.json create mode 100644 src/lib/json-ui/wrappers/definitions/save-indicator.json create mode 100644 src/lib/json-ui/wrappers/definitions/seed-data-manager.json create mode 100644 src/lib/json-ui/wrappers/definitions/storage-settings.json diff --git a/json-components-registry.json b/json-components-registry.json index bb68d7b..9715f1a 100644 --- a/json-components-registry.json +++ b/json-components-registry.json @@ -847,7 +847,7 @@ "canHaveChildren": false, "description": "Navigation breadcrumb trail", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { @@ -1257,7 +1257,7 @@ "canHaveChildren": true, "description": "LazyBarChart component", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { @@ -1267,7 +1267,7 @@ "canHaveChildren": true, "description": "LazyD3BarChart component", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { @@ -1277,7 +1277,7 @@ "canHaveChildren": true, "description": "LazyLineChart component", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { @@ -1319,11 +1319,11 @@ { "type": "SeedDataManager", "name": "SeedDataManager", - "category": "data", + "category": "custom", "canHaveChildren": true, "description": "SeedDataManager component", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { @@ -1826,11 +1826,11 @@ { "type": "SaveIndicator", "name": "SaveIndicator", - "category": "custom", + "category": "feedback", "canHaveChildren": true, "description": "SaveIndicator component", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { @@ -2000,7 +2000,7 @@ "canHaveChildren": true, "description": "StorageSettings component", "status": "json-compatible", - "source": "molecules", + "source": "json-ui-wrappers", "jsonCompatible": true }, { diff --git a/src/lib/component-definitions.ts b/src/lib/component-definitions.ts index 8093398..00db70b 100644 --- a/src/lib/component-definitions.ts +++ b/src/lib/component-definitions.ts @@ -224,6 +224,19 @@ export const componentDefinitions: ComponentDefinition[] = [ canHaveChildren: true, defaultProps: { href: '#', children: 'Link' } }, + { + type: 'Breadcrumb', + label: 'Breadcrumb', + category: 'navigation', + icon: 'Path', + defaultProps: { + items: [ + { label: 'Home', href: '/' }, + { label: 'Section', href: '/section' }, + { label: 'Current Page' }, + ], + }, + }, // Feedback Components { type: 'Alert', @@ -256,6 +269,13 @@ export const componentDefinitions: ComponentDefinition[] = [ icon: 'Circle', defaultProps: { status: 'active', children: 'Active' } }, + { + type: 'SaveIndicator', + label: 'Save Indicator', + category: 'feedback', + icon: 'FloppyDisk', + defaultProps: { status: 'saved', label: 'Saved' } + }, // Data Components { type: 'List', @@ -285,6 +305,46 @@ export const componentDefinitions: ComponentDefinition[] = [ icon: 'ChartBar', defaultProps: { title: 'Metric', value: '0' } }, + { + type: 'LazyBarChart', + label: 'Bar Chart', + category: 'data', + icon: 'ChartBar', + defaultProps: { + data: [ + { label: 'Jan', value: 30 }, + { label: 'Feb', value: 45 }, + ], + xKey: 'label', + yKey: 'value', + }, + }, + { + type: 'LazyLineChart', + label: 'Line Chart', + category: 'data', + icon: 'ChartLine', + defaultProps: { + data: [ + { label: 'Jan', value: 10 }, + { label: 'Feb', value: 25 }, + ], + xKey: 'label', + yKey: 'value', + }, + }, + { + type: 'LazyD3BarChart', + label: 'D3 Bar Chart', + category: 'data', + icon: 'ChartBar', + defaultProps: { + data: [ + { label: 'A', value: 12 }, + { label: 'B', value: 18 }, + ], + }, + }, // Custom Components { type: 'DataCard', @@ -308,6 +368,23 @@ export const componentDefinitions: ComponentDefinition[] = [ canHaveChildren: true, defaultProps: { actions: [] } }, + { + type: 'SeedDataManager', + label: 'Seed Data Manager', + category: 'custom', + icon: 'Database', + defaultProps: { isLoaded: false, isLoading: false } + }, + { + type: 'StorageSettings', + label: 'Storage Settings', + category: 'custom', + icon: 'Gear', + defaultProps: { + backend: 'indexeddb', + flaskUrl: 'http://localhost:5001', + }, + }, ] export function getCategoryComponents(category: string): ComponentDefinition[] { diff --git a/src/lib/json-ui/component-registry.ts b/src/lib/json-ui/component-registry.ts index 3e53dfe..11c4c47 100644 --- a/src/lib/json-ui/component-registry.ts +++ b/src/lib/json-ui/component-registry.ts @@ -19,6 +19,13 @@ 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 { Breadcrumb } from './wrappers/Breadcrumb' +import { SaveIndicator } from './wrappers/SaveIndicator' +import { LazyBarChart } from './wrappers/LazyBarChart' +import { LazyLineChart } from './wrappers/LazyLineChart' +import { LazyD3BarChart } from './wrappers/LazyD3BarChart' +import { SeedDataManager } from './wrappers/SeedDataManager' +import { StorageSettings } from './wrappers/StorageSettings' import jsonComponentsRegistry from '../../../json-components-registry.json' import { ArrowLeft, ArrowRight, Check, X, Plus, Minus, MagnifyingGlass, @@ -68,6 +75,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 wrapperRegistryNames = jsonRegistryEntries + .filter((entry) => entry.source === 'json-ui-wrappers') + .map((entry) => entry.export ?? entry.name ?? entry.type) + .filter((name): name is string => Boolean(name)) export const primitiveComponents: UIComponentRegistry = { div: 'div' as any, @@ -146,6 +157,19 @@ export const moleculeComponents: UIComponentRegistry = buildRegistryFromNames( MoleculeComponents as Record> ) +export const wrapperComponents: UIComponentRegistry = buildRegistryFromNames( + wrapperRegistryNames, + { + Breadcrumb, + SaveIndicator, + LazyBarChart, + LazyLineChart, + LazyD3BarChart, + SeedDataManager, + StorageSettings, + } as Record> +) + export const iconComponents: UIComponentRegistry = { ArrowLeft, ArrowRight, @@ -192,6 +216,7 @@ export const uiComponentRegistry: UIComponentRegistry = { ...shadcnComponents, ...atomComponents, ...moleculeComponents, + ...wrapperComponents, ...iconComponents, } diff --git a/src/lib/json-ui/renderer.tsx b/src/lib/json-ui/renderer.tsx index 36252b0..36b54ef 100644 --- a/src/lib/json-ui/renderer.tsx +++ b/src/lib/json-ui/renderer.tsx @@ -147,6 +147,9 @@ export function JSONUIRenderer({ if (component.bindings) { Object.entries(component.bindings).forEach(([propName, binding]) => { + if (propName === 'children') { + return + } props[propName] = resolveDataBinding(binding, dataMap, renderContext) }) } @@ -200,14 +203,17 @@ export function JSONUIRenderer({ const props = resolveProps(renderContext) applyEventHandlers(props, renderContext) + const boundChildren = component.bindings?.children + ? resolveDataBinding(component.bindings.children, dataMap, renderContext) + : component.children if (typeof Component === 'string') { - return React.createElement(Component, props, renderChildren(component.children, renderContext)) + return React.createElement(Component, props, renderChildren(boundChildren, renderContext)) } return ( - {renderChildren(component.children, renderContext)} + {renderChildren(boundChildren, renderContext)} ) } @@ -231,7 +237,10 @@ export function JSONUIRenderer({ ...(component.loop!.indexVar ? { [component.loop!.indexVar]: index } : {}), } - let content = renderChildren(component.children, loopContext) + const loopChildrenBinding = component.bindings?.children + ? resolveDataBinding(component.bindings.children, dataMap, loopContext) + : component.children + let content = renderChildren(loopChildrenBinding, loopContext) if (component.conditional) { const conditionMet = evaluateCondition(component.conditional.if, { ...dataMap, ...loopContext }) diff --git a/src/lib/json-ui/wrappers/Breadcrumb.tsx b/src/lib/json-ui/wrappers/Breadcrumb.tsx new file mode 100644 index 0000000..93ccb93 --- /dev/null +++ b/src/lib/json-ui/wrappers/Breadcrumb.tsx @@ -0,0 +1,29 @@ +import { JSONUIRenderer } from '@/lib/json-ui' +import type { UIComponent } from '@/lib/json-ui/types' +import breadcrumbDefinition from './definitions/breadcrumb.json' + +export interface BreadcrumbItem { + label: string + href?: string + onClick?: () => void +} + +export interface BreadcrumbProps { + items: BreadcrumbItem[] + className?: string +} + +const breadcrumbComponent = breadcrumbDefinition as UIComponent + +export function Breadcrumb({ items, className }: BreadcrumbProps) { + if (!items?.length) { + return null + } + + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/LazyBarChart.tsx b/src/lib/json-ui/wrappers/LazyBarChart.tsx new file mode 100644 index 0000000..28ed579 --- /dev/null +++ b/src/lib/json-ui/wrappers/LazyBarChart.tsx @@ -0,0 +1,49 @@ +import { + Bar, + BarChart, + CartesianGrid, + Legend, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts' + +export interface LazyBarChartProps { + data: Array> + xKey: string + yKey: string + width?: number | string + height?: number + color?: string + showLegend?: boolean + showGrid?: boolean +} + +export function LazyBarChart({ + data, + xKey, + yKey, + width = 600, + height = 300, + color = '#8884d8', + showLegend = true, + showGrid = true, +}: LazyBarChartProps) { + if (!data?.length) { + return null + } + + return ( + + + {showGrid && } + + + + {showLegend && } + + + + ) +} diff --git a/src/lib/json-ui/wrappers/LazyD3BarChart.tsx b/src/lib/json-ui/wrappers/LazyD3BarChart.tsx new file mode 100644 index 0000000..b9e96ee --- /dev/null +++ b/src/lib/json-ui/wrappers/LazyD3BarChart.tsx @@ -0,0 +1,96 @@ +import { max, scaleBand, scaleLinear } from 'd3' + +export interface LazyD3BarChartProps { + data: Array<{ label: string; value: number }> + width?: number + height?: number + color?: string + showAxes?: boolean + showGrid?: boolean +} + +export function LazyD3BarChart({ + data, + width = 600, + height = 300, + color = '#8884d8', + showAxes = true, + showGrid = true, +}: LazyD3BarChartProps) { + if (!data?.length) { + return null + } + + const margin = { top: 20, right: 20, bottom: 30, left: 40 } + const chartWidth = Math.max(width - margin.left - margin.right, 0) + const chartHeight = Math.max(height - margin.top - margin.bottom, 0) + + const maxValue = max(data, (d) => d.value) ?? 0 + const xScale = scaleBand() + .domain(data.map((d) => d.label)) + .range([0, chartWidth]) + .padding(0.1) + + const yScale = scaleLinear() + .domain([0, maxValue]) + .nice() + .range([chartHeight, 0]) + + const yTicks = yScale.ticks(4) + + return ( + + + {showGrid && + yTicks.map((tick) => ( + + ))} + + {data.map((entry) => ( + + ))} + + {showAxes && ( + <> + + + {yTicks.map((tick) => ( + + + + {tick} + + + ))} + {data.map((entry) => ( + + {entry.label} + + ))} + + )} + + + ) +} diff --git a/src/lib/json-ui/wrappers/LazyLineChart.tsx b/src/lib/json-ui/wrappers/LazyLineChart.tsx new file mode 100644 index 0000000..1ce24bb --- /dev/null +++ b/src/lib/json-ui/wrappers/LazyLineChart.tsx @@ -0,0 +1,49 @@ +import { + CartesianGrid, + Legend, + Line, + LineChart, + ResponsiveContainer, + Tooltip, + XAxis, + YAxis, +} from 'recharts' + +export interface LazyLineChartProps { + data: Array> + xKey: string + yKey: string + width?: number | string + height?: number + color?: string + showLegend?: boolean + showGrid?: boolean +} + +export function LazyLineChart({ + data, + xKey, + yKey, + width = 600, + height = 300, + color = '#8884d8', + showLegend = true, + showGrid = true, +}: LazyLineChartProps) { + if (!data?.length) { + return null + } + + return ( + + + {showGrid && } + + + + {showLegend && } + + + + ) +} diff --git a/src/lib/json-ui/wrappers/SaveIndicator.tsx b/src/lib/json-ui/wrappers/SaveIndicator.tsx new file mode 100644 index 0000000..31fc173 --- /dev/null +++ b/src/lib/json-ui/wrappers/SaveIndicator.tsx @@ -0,0 +1,41 @@ +import { JSONUIRenderer } from '@/lib/json-ui' +import type { UIComponent } from '@/lib/json-ui/types' +import saveIndicatorDefinition from './definitions/save-indicator.json' + +export interface SaveIndicatorProps { + status?: 'saved' | 'synced' + label?: string + showLabel?: boolean + animate?: boolean + className?: string +} + +const saveIndicatorComponent = saveIndicatorDefinition as UIComponent + +export function SaveIndicator({ + status = 'saved', + label, + showLabel = true, + animate, + className, +}: SaveIndicatorProps) { + if (!status) { + return null + } + + const resolvedLabel = label ?? (status === 'saved' ? 'Saved' : 'Synced') + const shouldAnimate = animate ?? status === 'saved' + + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/SeedDataManager.tsx b/src/lib/json-ui/wrappers/SeedDataManager.tsx new file mode 100644 index 0000000..6d65740 --- /dev/null +++ b/src/lib/json-ui/wrappers/SeedDataManager.tsx @@ -0,0 +1,108 @@ +import { JSONUIRenderer } from '@/lib/json-ui' +import type { UIComponent } from '@/lib/json-ui/types' +import seedDataManagerDefinition from './definitions/seed-data-manager.json' + +interface SeedDataManagerCopy { + title: string + description: string + statusLoaded: string + buttons: { + load: string + reset: string + clear: string + loadingLoad: string + loadingReset: string + loadingClear: string + } + help: { + load: string + reset: string + clear: string + } +} + +const defaultCopy: SeedDataManagerCopy = { + title: 'Seed Data Management', + description: 'Load, reset, or clear application seed data from the database', + statusLoaded: 'Seed data is loaded and available', + buttons: { + load: 'Load Seed Data', + reset: 'Reset to Defaults', + clear: 'Clear All Data', + loadingLoad: 'Loading...', + loadingReset: 'Resetting...', + loadingClear: 'Clearing...', + }, + help: { + load: 'Populates database with initial data if not already loaded', + reset: 'Overwrites all data with fresh seed data', + clear: 'Removes all data from the database (destructive action)', + }, +} + +export interface SeedDataManagerProps { + isLoaded?: boolean + isLoading?: boolean + onLoadSeedData?: () => void + onResetSeedData?: () => void + onClearAllData?: () => void + copy?: Partial +} + +const seedDataManagerComponent = seedDataManagerDefinition as UIComponent + +export function SeedDataManager({ + isLoaded = false, + isLoading = false, + onLoadSeedData, + onResetSeedData, + onClearAllData, + copy, +}: SeedDataManagerProps) { + const resolvedCopy: SeedDataManagerCopy = { + ...defaultCopy, + ...copy, + buttons: { + ...defaultCopy.buttons, + ...copy?.buttons, + }, + help: { + ...defaultCopy.help, + ...copy?.help, + }, + } + + const loadDisabled = !onLoadSeedData || isLoading || isLoaded + const resetDisabled = !onResetSeedData || isLoading + const clearDisabled = !onClearAllData || isLoading + + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/StorageSettings.tsx b/src/lib/json-ui/wrappers/StorageSettings.tsx new file mode 100644 index 0000000..bb48f56 --- /dev/null +++ b/src/lib/json-ui/wrappers/StorageSettings.tsx @@ -0,0 +1,117 @@ +import type { ChangeEvent } from 'react' +import { JSONUIRenderer } from '@/lib/json-ui' +import type { UIComponent } from '@/lib/json-ui/types' +import { + storageSettingsCopy, + getBackendCopy, + type StorageBackendKey, +} from '@/components/storage/storageSettingsConfig' +import storageSettingsDefinition from './definitions/storage-settings.json' + +const defaultCopy = storageSettingsCopy.molecule + +type StorageSettingsCopy = typeof defaultCopy + +export interface StorageSettingsProps { + backend: StorageBackendKey | null + isLoading?: boolean + flaskUrl?: string + isSwitching?: boolean + onFlaskUrlChange?: (value: string) => void + onSwitchToFlask?: () => void + onSwitchToIndexedDB?: () => void + onSwitchToSQLite?: () => void + isExporting?: boolean + isImporting?: boolean + onExport?: () => void + onImport?: () => void + copy?: Partial +} + +const storageSettingsComponent = storageSettingsDefinition as UIComponent + +export function StorageSettings({ + backend, + isLoading = false, + flaskUrl = defaultCopy.flaskUrlPlaceholder, + isSwitching = false, + onFlaskUrlChange, + onSwitchToFlask, + onSwitchToIndexedDB, + onSwitchToSQLite, + isExporting = false, + isImporting = false, + onExport, + onImport, + copy, +}: StorageSettingsProps) { + const resolvedCopy: StorageSettingsCopy = { + ...defaultCopy, + ...copy, + buttons: { + ...defaultCopy.buttons, + ...copy?.buttons, + }, + backendDetails: { + ...defaultCopy.backendDetails, + ...copy?.backendDetails, + }, + } + + const backendCopy = getBackendCopy(backend) + + const handleFlaskUrlChange = onFlaskUrlChange + ? (event: ChangeEvent) => onFlaskUrlChange(event.target.value) + : undefined + + return ( + + ) +} diff --git a/src/lib/json-ui/wrappers/definitions/breadcrumb.json b/src/lib/json-ui/wrappers/definitions/breadcrumb.json new file mode 100644 index 0000000..ae353ea --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/breadcrumb.json @@ -0,0 +1,106 @@ +{ + "id": "breadcrumb-root", + "type": "nav", + "className": "overflow-x-auto", + "props": { + "aria-label": "Breadcrumb" + }, + "bindings": { + "className": { + "source": "className" + } + }, + "children": [ + { + "id": "breadcrumb-row", + "type": "div", + "className": "flex items-center gap-2 text-sm", + "loop": { + "source": "items", + "itemVar": "item", + "indexVar": "itemIndex" + }, + "children": [ + { + "id": "breadcrumb-link-last", + "type": "Link", + "conditional": { + "if": "(item.href || item.onClick) && itemIndex === items.length - 1" + }, + "props": { + "variant": "default", + "className": "text-foreground font-medium" + }, + "bindings": { + "href": { + "source": "item.href" + }, + "onClick": { + "source": "item.onClick" + }, + "children": { + "source": "item.label" + } + } + }, + { + "id": "breadcrumb-link", + "type": "Link", + "conditional": { + "if": "(item.href || item.onClick) && itemIndex < items.length - 1" + }, + "props": { + "variant": "muted", + "className": "text-muted-foreground hover:text-foreground" + }, + "bindings": { + "href": { + "source": "item.href" + }, + "onClick": { + "source": "item.onClick" + }, + "children": { + "source": "item.label" + } + } + }, + { + "id": "breadcrumb-text-last", + "type": "span", + "conditional": { + "if": "!item.href && !item.onClick && itemIndex === items.length - 1" + }, + "className": "text-foreground font-medium", + "bindings": { + "children": { + "source": "item.label" + } + } + }, + { + "id": "breadcrumb-text", + "type": "span", + "conditional": { + "if": "!item.href && !item.onClick && itemIndex < items.length - 1" + }, + "className": "text-muted-foreground", + "bindings": { + "children": { + "source": "item.label" + } + } + }, + { + "id": "breadcrumb-separator", + "type": "span", + "conditional": { + "if": "itemIndex < items.length - 1" + }, + "className": "text-muted-foreground", + "children": "/" + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/save-indicator.json b/src/lib/json-ui/wrappers/definitions/save-indicator.json new file mode 100644 index 0000000..c2aa654 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/save-indicator.json @@ -0,0 +1,36 @@ +{ + "id": "save-indicator-root", + "type": "div", + "className": "flex items-center gap-1.5 text-xs text-muted-foreground", + "bindings": { + "className": { + "source": "className" + } + }, + "children": [ + { + "id": "save-indicator-icon", + "type": "StatusIcon", + "bindings": { + "type": { + "source": "status" + }, + "animate": { + "source": "animate" + } + } + }, + { + "id": "save-indicator-label", + "type": "span", + "conditional": { + "if": "showLabel" + }, + "bindings": { + "children": { + "source": "label" + } + } + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/seed-data-manager.json b/src/lib/json-ui/wrappers/definitions/seed-data-manager.json new file mode 100644 index 0000000..0868c14 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/seed-data-manager.json @@ -0,0 +1,157 @@ +{ + "id": "seed-data-manager", + "type": "Card", + "children": [ + { + "id": "seed-data-header", + "type": "CardHeader", + "children": [ + { + "id": "seed-data-title", + "type": "CardTitle", + "bindings": { + "children": { + "source": "title" + } + } + }, + { + "id": "seed-data-description", + "type": "CardDescription", + "bindings": { + "children": { + "source": "description" + } + } + } + ] + }, + { + "id": "seed-data-content", + "type": "CardContent", + "className": "flex flex-col gap-4", + "children": [ + { + "id": "seed-data-alert", + "type": "Alert", + "conditional": { + "if": "isLoaded" + }, + "children": [ + { + "id": "seed-data-alert-description", + "type": "AlertDescription", + "bindings": { + "children": { + "source": "statusLoaded" + } + } + } + ] + }, + { + "id": "seed-data-actions-block", + "type": "div", + "className": "flex flex-col gap-3", + "children": [ + { + "id": "seed-data-actions", + "type": "div", + "className": "flex gap-2 flex-wrap", + "children": [ + { + "id": "seed-data-load", + "type": "Button", + "props": { + "variant": "default" + }, + "bindings": { + "onClick": { + "source": "onLoadSeedData" + }, + "disabled": { + "source": "loadDisabled" + }, + "children": { + "source": "loadButtonText" + } + } + }, + { + "id": "seed-data-reset", + "type": "Button", + "props": { + "variant": "outline" + }, + "bindings": { + "onClick": { + "source": "onResetSeedData" + }, + "disabled": { + "source": "resetDisabled" + }, + "children": { + "source": "resetButtonText" + } + } + }, + { + "id": "seed-data-clear", + "type": "Button", + "props": { + "variant": "destructive" + }, + "bindings": { + "onClick": { + "source": "onClearAllData" + }, + "disabled": { + "source": "clearDisabled" + }, + "children": { + "source": "clearButtonText" + } + } + } + ] + }, + { + "id": "seed-data-help", + "type": "div", + "className": "text-sm text-muted-foreground space-y-1", + "children": [ + { + "id": "seed-data-help-load", + "type": "p", + "bindings": { + "children": { + "source": "helpLoad" + } + } + }, + { + "id": "seed-data-help-reset", + "type": "p", + "bindings": { + "children": { + "source": "helpReset" + } + } + }, + { + "id": "seed-data-help-clear", + "type": "p", + "bindings": { + "children": { + "source": "helpClear" + } + } + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/lib/json-ui/wrappers/definitions/storage-settings.json b/src/lib/json-ui/wrappers/definitions/storage-settings.json new file mode 100644 index 0000000..6e28266 --- /dev/null +++ b/src/lib/json-ui/wrappers/definitions/storage-settings.json @@ -0,0 +1,331 @@ +{ + "id": "storage-settings-root", + "type": "div", + "className": "space-y-6", + "children": [ + { + "id": "storage-backend-card", + "type": "Card", + "children": [ + { + "id": "storage-backend-header", + "type": "CardHeader", + "children": [ + { + "id": "storage-backend-title", + "type": "CardTitle", + "bindings": { + "children": { + "source": "title" + } + } + }, + { + "id": "storage-backend-description", + "type": "CardDescription", + "bindings": { + "children": { + "source": "description" + } + } + } + ] + }, + { + "id": "storage-backend-content", + "type": "CardContent", + "className": "space-y-4", + "children": [ + { + "id": "storage-backend-current", + "type": "div", + "className": "flex items-center gap-2", + "children": [ + { + "id": "storage-backend-current-label", + "type": "span", + "className": "text-sm text-muted-foreground", + "bindings": { + "children": { + "source": "currentBackendLabel" + } + } + }, + { + "id": "storage-backend-current-badge", + "type": "Badge", + "props": { + "variant": "secondary", + "className": "flex items-center gap-1" + }, + "bindings": { + "children": { + "source": "backendBadge" + } + } + } + ] + }, + { + "id": "storage-backend-form", + "type": "div", + "className": "grid gap-4", + "children": [ + { + "id": "storage-backend-flask", + "type": "div", + "className": "space-y-2", + "children": [ + { + "id": "storage-backend-flask-label", + "type": "Label", + "props": { + "htmlFor": "flask-url" + }, + "bindings": { + "children": { + "source": "flaskUrlLabel" + } + } + }, + { + "id": "storage-backend-flask-row", + "type": "div", + "className": "flex gap-2", + "children": [ + { + "id": "storage-backend-flask-input", + "type": "Input", + "bindings": { + "value": { + "source": "flaskUrl" + }, + "onChange": { + "source": "onFlaskUrlChange" + }, + "disabled": { + "source": "flaskUrlDisabled" + }, + "placeholder": { + "source": "flaskUrlPlaceholder" + } + }, + "props": { + "id": "flask-url" + } + }, + { + "id": "storage-backend-flask-button", + "type": "Button", + "bindings": { + "onClick": { + "source": "onSwitchToFlask" + }, + "disabled": { + "source": "flaskButtonDisabled" + }, + "variant": { + "source": "flaskButtonVariant" + }, + "children": { + "source": "flaskButtonLabel" + } + } + } + ] + }, + { + "id": "storage-backend-flask-help", + "type": "p", + "className": "text-xs text-muted-foreground", + "bindings": { + "children": { + "source": "flaskHelp" + } + } + } + ] + }, + { + "id": "storage-backend-options", + "type": "div", + "className": "flex gap-2", + "children": [ + { + "id": "storage-backend-indexeddb", + "type": "Button", + "props": { + "className": "flex-1" + }, + "bindings": { + "onClick": { + "source": "onSwitchToIndexedDB" + }, + "disabled": { + "source": "indexedDbDisabled" + }, + "variant": { + "source": "indexedDbVariant" + }, + "children": { + "source": "indexedDbLabel" + } + } + }, + { + "id": "storage-backend-sqlite", + "type": "Button", + "props": { + "className": "flex-1" + }, + "bindings": { + "onClick": { + "source": "onSwitchToSQLite" + }, + "disabled": { + "source": "sqliteDisabled" + }, + "variant": { + "source": "sqliteVariant" + }, + "children": { + "source": "sqliteLabel" + } + } + } + ] + }, + { + "id": "storage-backend-details", + "type": "div", + "className": "text-xs text-muted-foreground space-y-1", + "children": [ + { + "id": "storage-backend-details-indexeddb", + "type": "p", + "bindings": { + "children": { + "source": "indexedDbDetails" + } + } + }, + { + "id": "storage-backend-details-sqlite", + "type": "p", + "bindings": { + "children": { + "source": "sqliteDetails" + } + } + }, + { + "id": "storage-backend-details-flask", + "type": "p", + "bindings": { + "children": { + "source": "flaskDetails" + } + } + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "storage-data-card", + "type": "Card", + "children": [ + { + "id": "storage-data-header", + "type": "CardHeader", + "children": [ + { + "id": "storage-data-title", + "type": "CardTitle", + "bindings": { + "children": { + "source": "dataTitle" + } + } + }, + { + "id": "storage-data-description", + "type": "CardDescription", + "bindings": { + "children": { + "source": "dataDescription" + } + } + } + ] + }, + { + "id": "storage-data-content", + "type": "CardContent", + "className": "space-y-4", + "children": [ + { + "id": "storage-data-actions", + "type": "div", + "className": "flex gap-2", + "children": [ + { + "id": "storage-data-export", + "type": "Button", + "props": { + "variant": "outline", + "className": "flex-1" + }, + "bindings": { + "onClick": { + "source": "onExport" + }, + "disabled": { + "source": "exportDisabled" + }, + "children": { + "source": "exportLabel" + } + } + }, + { + "id": "storage-data-import", + "type": "Button", + "props": { + "variant": "outline", + "className": "flex-1" + }, + "bindings": { + "onClick": { + "source": "onImport" + }, + "disabled": { + "source": "importDisabled" + }, + "children": { + "source": "importLabel" + } + } + } + ] + }, + { + "id": "storage-data-help", + "type": "p", + "className": "text-xs text-muted-foreground", + "bindings": { + "children": { + "source": "dataHelp" + } + } + } + ] + } + ] + } + ] +} diff --git a/src/types/json-ui.ts b/src/types/json-ui.ts index 05c14f2..695e256 100644 --- a/src/types/json-ui.ts +++ b/src/types/json-ui.ts @@ -8,6 +8,7 @@ export type ComponentType = | 'Alert' | 'InfoBox' | 'EmptyState' | 'StatusBadge' | 'Table' | 'KeyValue' | 'StatCard' | 'DataCard' | 'SearchInput' | 'ActionBar' | 'AppBranding' | 'LabelWithBadge' | 'EmptyEditorState' | 'LoadingFallback' | 'LoadingState' | 'NavigationGroupHeader' + | 'Breadcrumb' | 'SaveIndicator' | 'LazyBarChart' | 'LazyD3BarChart' | 'LazyLineChart' | 'SeedDataManager' | 'StorageSettings' export type ActionType = | 'create' | 'update' | 'delete' | 'navigate'