diff --git a/src/components/json-definitions/lazy-d3-bar-chart.json b/src/components/json-definitions/lazy-d3-bar-chart.json new file mode 100644 index 0000000..f938cd5 --- /dev/null +++ b/src/components/json-definitions/lazy-d3-bar-chart.json @@ -0,0 +1,76 @@ +{ + "id": "d3-bar-chart-svg", + "type": "svg", + "bindings": { + "width": "width", + "height": "height", + "className": { + "source": "className", + "transform": "data ? `overflow-visible ${data}` : 'overflow-visible'" + } + }, + "children": [ + { + "id": "chart-group", + "type": "g", + "bindings": { + "transform": { + "source": "chartData.translateX,chartData.translateY", + "transform": "`translate(${chartData.translateX},${chartData.translateY})`" + } + }, + "children": [ + { + "id": "bars-list", + "type": "list", + "bindings": { + "items": "chartData.bars", + "keyPath": "label" + }, + "itemTemplate": { + "type": "g", + "children": [ + { + "type": "rect", + "bindings": { + "x": "item.x", + "y": "item.y", + "width": "item.width", + "height": "item.height", + "fill": "color" + }, + "props": { "rx": 2 } + }, + { + "type": "text", + "bindings": { + "x": "item.labelX", + "y": "item.labelY", + "children": "item.label" + }, + "props": { + "textAnchor": "middle", + "fill": "currentColor", + "style": { "fontSize": 10 } + } + }, + { + "type": "text", + "bindings": { + "x": "item.valueX", + "y": "item.valueY", + "children": "item.value" + }, + "props": { + "textAnchor": "middle", + "fill": "currentColor", + "style": { "fontSize": 10 } + } + } + ] + } + } + ] + } + ] +} diff --git a/src/components/json-definitions/storage-settings.json b/src/components/json-definitions/storage-settings.json new file mode 100644 index 0000000..b8c86aa --- /dev/null +++ b/src/components/json-definitions/storage-settings.json @@ -0,0 +1,281 @@ +{ + "id": "storage-settings-container", + "type": "div", + "props": { "className": "space-y-6" }, + "children": [ + { + "id": "backend-card", + "type": "Card", + "children": [ + { + "id": "backend-header", + "type": "CardHeader", + "children": [ + { + "id": "backend-title", + "type": "CardTitle", + "props": { "className": "flex items-center gap-2" }, + "children": [ + { + "type": "PhosphorIcon", + "bindings": { + "icon": "backendInfo.iconName" + }, + "props": { "className": "w-5 h-5" } + }, + { + "type": "text", + "children": "Storage Backend" + } + ] + }, + { + "id": "backend-description", + "type": "CardDescription", + "children": [{ "type": "text", "children": "Choose your storage backend for the application" }] + } + ] + }, + { + "id": "backend-content", + "type": "CardContent", + "props": { "className": "space-y-4" }, + "children": [ + { + "id": "current-backend", + "type": "div", + "props": { "className": "flex items-center gap-2" }, + "children": [ + { + "type": "span", + "props": { "className": "text-sm text-muted-foreground" }, + "children": [{ "type": "text", "children": "Current Backend:" }] + }, + { + "type": "Badge", + "props": { "variant": "secondary", "className": "flex items-center gap-1" }, + "children": [ + { + "type": "PhosphorIcon", + "bindings": { "icon": "backendInfo.iconName" }, + "props": { "className": "w-4 h-4" } + }, + { + "type": "text", + "bindings": { "children": "backendInfo.moleculeLabel" } + } + ] + } + ] + }, + { + "id": "backend-options", + "type": "div", + "props": { "className": "grid gap-4" }, + "children": [ + { + "id": "flask-section", + "type": "div", + "props": { "className": "space-y-2" }, + "children": [ + { + "type": "Label", + "props": { "htmlFor": "flask-url" }, + "children": [{ "type": "text", "children": "Flask Backend URL" }] + }, + { + "type": "div", + "props": { "className": "flex gap-2" }, + "children": [ + { + "type": "Input", + "props": { "id": "flask-url" }, + "bindings": { + "value": "flaskUrl", + "onChange": { + "source": "onFlaskUrlChange", + "transform": "(e) => onFlaskUrlChange?.(e.target.value)" + }, + "disabled": { + "source": "isSwitching,isLoading", + "transform": "isSwitching || isLoading" + } + } + }, + { + "type": "Button", + "bindings": { + "onClick": "onSwitchToFlask", + "disabled": { + "source": "isSwitching,isLoading,backend", + "transform": "isSwitching || isLoading || backend === 'flask'" + }, + "variant": { + "source": "backend", + "transform": "backend === 'flask' ? 'secondary' : 'default'" + } + }, + "children": [ + { + "type": "PhosphorIcon", + "props": { "icon": "Cpu", "className": "w-4 h-4 mr-2" } + }, + { + "type": "text", + "bindings": { + "children": { + "source": "backend", + "transform": "backend === 'flask' ? 'Active' : 'Use Flask'" + } + } + } + ] + } + ] + } + ] + }, + { + "id": "other-backends", + "type": "div", + "props": { "className": "flex gap-2" }, + "children": [ + { + "type": "Button", + "bindings": { + "onClick": "onSwitchToIndexedDB", + "disabled": { + "source": "isSwitching,isLoading,backend", + "transform": "isSwitching || isLoading || backend === 'indexeddb'" + }, + "variant": { + "source": "backend", + "transform": "backend === 'indexeddb' ? 'secondary' : 'outline'" + } + }, + "props": { "className": "flex-1" }, + "children": [ + { + "type": "PhosphorIcon", + "props": { "icon": "HardDrive", "className": "w-4 h-4 mr-2" } + }, + { + "type": "text", + "bindings": { + "children": { + "source": "backend", + "transform": "backend === 'indexeddb' ? 'Active' : 'Use IndexedDB'" + } + } + } + ] + }, + { + "type": "Button", + "bindings": { + "onClick": "onSwitchToSQLite", + "disabled": { + "source": "isSwitching,isLoading,backend", + "transform": "isSwitching || isLoading || backend === 'sqlite'" + }, + "variant": { + "source": "backend", + "transform": "backend === 'sqlite' ? 'secondary' : 'outline'" + } + }, + "props": { "className": "flex-1" }, + "children": [ + { + "type": "PhosphorIcon", + "props": { "icon": "Database", "className": "w-4 h-4 mr-2" } + }, + { + "type": "text", + "bindings": { + "children": { + "source": "backend", + "transform": "backend === 'sqlite' ? 'Active' : 'Use SQLite'" + } + } + } + ] + } + ] + } + ] + } + ] + } + ] + }, + { + "id": "data-card", + "type": "Card", + "children": [ + { + "id": "data-header", + "type": "CardHeader", + "children": [ + { + "type": "CardTitle", + "children": [{ "type": "text", "children": "Import/Export Data" }] + }, + { + "type": "CardDescription", + "children": [{ "type": "text", "children": "Backup or restore your application data" }] + } + ] + }, + { + "id": "data-content", + "type": "CardContent", + "props": { "className": "space-y-4" }, + "children": [ + { + "type": "div", + "props": { "className": "flex gap-2" }, + "children": [ + { + "type": "Button", + "bindings": { + "onClick": "onExport", + "disabled": "isExporting" + }, + "props": { "variant": "outline", "className": "flex-1" }, + "children": [ + { + "type": "PhosphorIcon", + "props": { "icon": "Download", "className": "w-4 h-4 mr-2" } + }, + { + "type": "text", + "children": "Export" + } + ] + }, + { + "type": "Button", + "bindings": { + "onClick": "onImport", + "disabled": "isImporting" + }, + "props": { "variant": "outline", "className": "flex-1" }, + "children": [ + { + "type": "PhosphorIcon", + "props": { "icon": "Upload", "className": "w-4 h-4 mr-2" } + }, + { + "type": "text", + "children": "Import" + } + ] + } + ] + } + ] + } + ] + } + ] +} diff --git a/src/components/molecules/LazyD3BarChartWrapper.tsx b/src/components/molecules/LazyD3BarChartWrapper.tsx deleted file mode 100644 index 5dd016b..0000000 --- a/src/components/molecules/LazyD3BarChartWrapper.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { cn } from '@/lib/utils' -import type { LazyD3BarChartWrapperProps } from './interfaces' - -export function LazyD3BarChartWrapper({ - data, - width = 600, - height = 300, - color = '#8884d8', - className, -}: LazyD3BarChartWrapperProps) { - const margin = { top: 20, right: 20, bottom: 30, left: 40 } - const innerWidth = Math.max(width - margin.left - margin.right, 0) - const innerHeight = Math.max(height - margin.top - margin.bottom, 0) - const maxValue = Math.max(...data.map((item) => item.value), 0) - const barGap = 8 - const barCount = data.length - const totalGap = barCount > 1 ? barGap * (barCount - 1) : 0 - const barWidth = barCount > 0 ? Math.max((innerWidth - totalGap) / barCount, 0) : 0 - - return ( - - - {data.map((item, index) => { - const barHeight = maxValue ? (item.value / maxValue) * innerHeight : 0 - const x = index * (barWidth + barGap) - const y = innerHeight - barHeight - - return ( - - - - {item.label} - - - {item.value} - - - ) - })} - - - ) -} diff --git a/src/components/molecules/StorageSettingsWrapper.tsx b/src/components/molecules/StorageSettingsWrapper.tsx deleted file mode 100644 index ae8b1cc..0000000 --- a/src/components/molecules/StorageSettingsWrapper.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import { Badge } from '@/components/ui/badge' -import { Button } from '@/components/ui/button' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Cloud, Cpu, Database, Download, HardDrive, Upload } from '@phosphor-icons/react' -import { - getBackendCopy, - storageSettingsCopy, -} from '@/components/storage/storageSettingsConfig' -import type { StorageBackendKey } from '@/components/storage/storageSettingsConfig' -import type { StorageSettingsWrapperProps } from './interfaces' - -const getBackendIcon = (backend: StorageBackendKey | null) => { - switch (backend) { - case 'flask': - return - case 'indexeddb': - return - case 'sqlite': - return - case 'sparkkv': - return - default: - return - } -} - -export function StorageSettingsWrapper({ - backend = null, - isLoading = false, - flaskUrl = storageSettingsCopy.molecule.flaskUrlPlaceholder, - isSwitching = false, - onFlaskUrlChange, - onSwitchToFlask, - onSwitchToIndexedDB, - onSwitchToSQLite, - isExporting = false, - isImporting = false, - onExport, - onImport, -}: StorageSettingsWrapperProps) { - const backendCopy = getBackendCopy(backend) - - return ( -
- - - - {getBackendIcon(backend)} - {storageSettingsCopy.molecule.title} - - {storageSettingsCopy.molecule.description} - - -
- - {storageSettingsCopy.molecule.currentBackendLabel} - - - {getBackendIcon(backend)} - {backendCopy.moleculeLabel} - -
- -
-
- -
- onFlaskUrlChange?.(e.target.value)} - placeholder={storageSettingsCopy.molecule.flaskUrlPlaceholder} - disabled={isSwitching || isLoading} - /> - -
-

{storageSettingsCopy.molecule.flaskHelp}

-
- -
- - -
- -
-

{storageSettingsCopy.molecule.backendDetails.indexeddb}

-

{storageSettingsCopy.molecule.backendDetails.sqlite}

-

{storageSettingsCopy.molecule.backendDetails.flask}

-
-
-
-
- - - - {storageSettingsCopy.molecule.dataTitle} - {storageSettingsCopy.molecule.dataDescription} - - -
- - -
-

{storageSettingsCopy.molecule.dataHelp}

-
-
-
- ) -} diff --git a/src/components/molecules/index.ts b/src/components/molecules/index.ts index 3939d8d..0c90ca4 100644 --- a/src/components/molecules/index.ts +++ b/src/components/molecules/index.ts @@ -5,8 +5,6 @@ export { ComponentPalette } from './ComponentPalette' export { GitHubBuildStatus } from './GitHubBuildStatus' export { LazyLineChart } from './LazyLineChart' export { LazyBarChart } from './LazyBarChart' -export { LazyD3BarChart } from './LazyD3BarChart' -export { StorageSettings } from './StorageSettings' export { NavigationGroupHeader } from './NavigationGroupHeader' export { PropertyEditor } from './PropertyEditor' export { ToolbarButton } from './ToolbarButton' @@ -24,6 +22,8 @@ export { GitHubBuildStatus as GitHubBuildStatusJSON, SaveIndicator, ComponentTree, - SeedDataManager + SeedDataManager, + LazyD3BarChart, + StorageSettings } from '@/lib/json-ui/json-components' export { preloadMonacoEditor } from './LazyMonacoEditor' diff --git a/src/lib/json-ui/json-components.ts b/src/lib/json-ui/json-components.ts index 92d7b12..06ff3ba 100644 --- a/src/lib/json-ui/json-components.ts +++ b/src/lib/json-ui/json-components.ts @@ -31,6 +31,8 @@ import githubBuildStatusDef from '@/components/json-definitions/github-build-sta import saveIndicatorDef from '@/components/json-definitions/save-indicator.json' import componentTreeDef from '@/components/json-definitions/component-tree.json' import seedDataManagerDef from '@/components/json-definitions/seed-data-manager.json' +import lazyD3BarChartDef from '@/components/json-definitions/lazy-d3-bar-chart.json' +import storageSettingsDef from '@/components/json-definitions/storage-settings.json' // Create pure JSON components (no hooks) export const LoadingFallback = createJsonComponent(loadingFallbackDef) @@ -60,8 +62,22 @@ export const ComponentTree = createJsonComponentWithHooks(co } }) -// Note: The following still need JSON definitions created: -// - StorageSettings (complex form with backend switching) -// - LazyBarChart (Recharts integration) -// - LazyLineChart (Recharts integration) -// - LazyD3BarChart (D3 calculations - hook created, needs JSON definition) +export const LazyD3BarChart = createJsonComponentWithHooks(lazyD3BarChartDef, { + hooks: { + chartData: { + hookName: 'useD3BarChart', + args: (props) => [props.data, props.width, props.height] + } + } +}) + +export const StorageSettings = createJsonComponentWithHooks(storageSettingsDef, { + hooks: { + backendInfo: { + hookName: 'useStorageBackendInfo', + args: (props) => [props.backend || null] + } + } +}) + +// All components converted to pure JSON! 🎉