diff --git a/REMOVED_DUPLICATES.md b/REMOVED_DUPLICATES.md new file mode 100644 index 0000000..a301710 --- /dev/null +++ b/REMOVED_DUPLICATES.md @@ -0,0 +1,42 @@ +# Removed Duplicate Components + +The following components were replaced in favor of their JSON-based versions as part of the JSON Component Tree architecture migration. + +## Replaced Files (Now Export JSON Versions) +1. `ModelDesigner.tsx` → Now exports `JSONModelDesigner` +2. `ComponentTreeManager.tsx` → Now exports `JSONComponentTreeManager` +3. `WorkflowDesigner.tsx` → Now exports `JSONWorkflowDesigner` +4. `LambdaDesigner.tsx` → Now exports `JSONLambdaDesigner` +5. `FlaskDesigner.tsx` → Now exports `JSONFlaskDesigner` +6. `StyleDesigner.tsx` → Now exports `JSONStyleDesigner` +7. `ProjectDashboard.tsx` → Replaced with JSON-driven implementation from `ProjectDashboard.new.tsx` + +## Registry Updates +The `component-registry.json` was updated to point all component references to their JSON-based implementations: +- All designer components now use JSON-based PageRenderer +- Removed "experimental" tags from JSON implementations +- Updated descriptions to reflect JSON-driven architecture + +## Implementation Strategy +Instead of deleting the old files, they now re-export the JSON versions. This maintains backward compatibility while ensuring all code paths use the JSON-driven architecture. + +**Example:** +```tsx +// ModelDesigner.tsx +export { JSONModelDesigner as ModelDesigner } from './JSONModelDesigner' +``` + +## Benefits +- ✅ Single source of truth using JSON-driven component trees +- ✅ More maintainable and configurable components +- ✅ Aligns with Redux + IndexedDB integration strategy +- ✅ Backward compatible with existing imports +- ✅ Reduced code duplication by ~6 components + +## Files Kept (Not Duplicates) +The following similarly-named files serve different purposes and were kept: +- `ConflictResolutionDemo.tsx` vs `ConflictResolutionPage.tsx` (demo vs UI) +- `PersistenceExample.tsx` vs `PersistenceDashboard.tsx` (example vs dashboard) +- `StorageExample.tsx` vs `StorageSettings.tsx` (different features) +- `AtomicComponentDemo.tsx` vs `AtomicComponentShowcase.tsx` vs `AtomicLibraryShowcase.tsx` (different demos) +- `JSONUIShowcase.tsx` vs `JSONUIShowcasePage.tsx` (component vs page wrapper) diff --git a/component-registry.json b/component-registry.json index 36bc957..6187ba9 100644 --- a/component-registry.json +++ b/component-registry.json @@ -36,22 +36,12 @@ }, { "name": "ModelDesigner", - "path": "@/components/ModelDesigner", - "export": "ModelDesigner", - "type": "feature", - "preload": false, - "category": "designer", - "description": "Visual Prisma model designer" - }, - { - "name": "JSONModelDesigner", "path": "@/components/JSONModelDesigner", "export": "JSONModelDesigner", "type": "feature", "preload": false, "category": "designer", - "experimental": true, - "description": "JSON-based model designer (experimental)" + "description": "JSON-based model designer" }, { "name": "ComponentTreeBuilder", @@ -64,73 +54,43 @@ }, { "name": "ComponentTreeManager", - "path": "@/components/ComponentTreeManager", - "export": "ComponentTreeManager", - "type": "feature", - "preload": false, - "category": "designer", - "description": "Manage multiple component trees" - }, - { - "name": "JSONComponentTreeManager", "path": "@/components/JSONComponentTreeManager", "export": "JSONComponentTreeManager", "type": "feature", "preload": false, "category": "designer", - "experimental": true, - "description": "JSON-based component tree manager (experimental)" + "description": "JSON-based component tree manager" }, { "name": "WorkflowDesigner", - "path": "@/components/WorkflowDesigner", - "export": "WorkflowDesigner", - "type": "feature", - "preload": false, - "category": "designer", - "dependencies": ["monaco-editor"], - "preloadDependencies": ["preloadMonacoEditor"], - "description": "n8n-style visual workflow designer" - }, - { - "name": "JSONWorkflowDesigner", "path": "@/components/JSONWorkflowDesigner", "export": "JSONWorkflowDesigner", "type": "feature", "preload": false, "category": "designer", - "experimental": true, - "description": "JSON-based workflow designer (experimental)" + "dependencies": ["monaco-editor"], + "preloadDependencies": ["preloadMonacoEditor"], + "description": "JSON-based workflow designer" }, { "name": "LambdaDesigner", - "path": "@/components/LambdaDesigner", - "export": "LambdaDesigner", - "type": "feature", - "preload": false, - "category": "designer", - "dependencies": ["monaco-editor"], - "preloadDependencies": ["preloadMonacoEditor"], - "description": "Serverless function designer with multi-runtime support" - }, - { - "name": "JSONLambdaDesigner", "path": "@/components/JSONLambdaDesigner", "export": "JSONLambdaDesigner", "type": "feature", "preload": false, "category": "designer", - "experimental": true, - "description": "JSON-based lambda designer (experimental)" + "dependencies": ["monaco-editor"], + "preloadDependencies": ["preloadMonacoEditor"], + "description": "JSON-based lambda designer" }, { "name": "StyleDesigner", - "path": "@/components/StyleDesigner", - "export": "StyleDesigner", + "path": "@/components/JSONStyleDesigner", + "export": "JSONStyleDesigner", "type": "feature", "preload": false, "category": "designer", - "description": "Visual theme and styling designer" + "description": "JSON-based theme and styling designer" }, { "name": "PlaywrightDesigner", @@ -161,12 +121,12 @@ }, { "name": "FlaskDesigner", - "path": "@/components/FlaskDesigner", - "export": "FlaskDesigner", + "path": "@/components/JSONFlaskDesigner", + "export": "JSONFlaskDesigner", "type": "feature", "preload": false, "category": "backend", - "description": "Flask REST API designer" + "description": "JSON-based Flask REST API designer" }, { "name": "ProjectSettingsDesigner", diff --git a/src/components/ComponentTreeManager.tsx b/src/components/ComponentTreeManager.tsx index 5190c3a..ae9538a 100644 --- a/src/components/ComponentTreeManager.tsx +++ b/src/components/ComponentTreeManager.tsx @@ -1,239 +1 @@ -import { useState, useRef } from 'react' -import { ComponentTree, ComponentNode } from '@/types/project' -import { TreeFormDialog } from '@/components/molecules' -import { TreeListPanel } from '@/components/organisms' -import { ComponentTreeBuilder } from '@/components/ComponentTreeBuilder' -import { TreeIcon } from '@/components/atoms' -import { toast } from 'sonner' - -interface ComponentTreeManagerProps { - trees: ComponentTree[] - onTreesChange: (updater: (trees: ComponentTree[]) => ComponentTree[]) => void -} - -export function ComponentTreeManager({ trees, onTreesChange }: ComponentTreeManagerProps) { - const [selectedTreeId, setSelectedTreeId] = useState(trees[0]?.id || null) - const [createDialogOpen, setCreateDialogOpen] = useState(false) - const [editDialogOpen, setEditDialogOpen] = useState(false) - const [newTreeName, setNewTreeName] = useState('') - const [newTreeDescription, setNewTreeDescription] = useState('') - const [editingTree, setEditingTree] = useState(null) - const fileInputRef = useRef(null) - - const selectedTree = trees.find(t => t.id === selectedTreeId) - - const handleCreateTree = () => { - if (!newTreeName.trim()) { - toast.error('Please enter a tree name') - return - } - - const newTree: ComponentTree = { - id: `tree-${Date.now()}`, - name: newTreeName, - description: newTreeDescription, - rootNodes: [], - createdAt: Date.now(), - updatedAt: Date.now(), - } - - onTreesChange((current) => [...current, newTree]) - setSelectedTreeId(newTree.id) - setNewTreeName('') - setNewTreeDescription('') - setCreateDialogOpen(false) - toast.success('Component tree created') - } - - const handleEditTree = () => { - if (!editingTree || !newTreeName.trim()) return - - onTreesChange((current) => - current.map((tree) => - tree.id === editingTree.id - ? { ...tree, name: newTreeName, description: newTreeDescription, updatedAt: Date.now() } - : tree - ) - ) - setEditDialogOpen(false) - setEditingTree(null) - setNewTreeName('') - setNewTreeDescription('') - toast.success('Component tree updated') - } - - const handleDeleteTree = (treeId: string) => { - if (trees.length === 1) { - toast.error('Cannot delete the last component tree') - return - } - - if (confirm('Are you sure you want to delete this component tree?')) { - onTreesChange((current) => current.filter((t) => t.id !== treeId)) - if (selectedTreeId === treeId) { - setSelectedTreeId(trees.find(t => t.id !== treeId)?.id || null) - } - toast.success('Component tree deleted') - } - } - - const handleDuplicateTree = (tree: ComponentTree) => { - const duplicatedTree: ComponentTree = { - ...tree, - id: `tree-${Date.now()}`, - name: `${tree.name} (Copy)`, - createdAt: Date.now(), - updatedAt: Date.now(), - } - - onTreesChange((current) => [...current, duplicatedTree]) - setSelectedTreeId(duplicatedTree.id) - toast.success('Component tree duplicated') - } - - const handleComponentsChange = (components: ComponentNode[]) => { - if (!selectedTreeId) return - - onTreesChange((current) => - current.map((tree) => - tree.id === selectedTreeId - ? { ...tree, rootNodes: components, updatedAt: Date.now() } - : tree - ) - ) - } - - const openEditDialog = (tree: ComponentTree) => { - setEditingTree(tree) - setNewTreeName(tree.name) - setNewTreeDescription(tree.description) - setEditDialogOpen(true) - } - - const handleExportJson = () => { - if (!selectedTree) { - toast.error('No tree selected to export') - return - } - - try { - const json = JSON.stringify(selectedTree, null, 2) - const blob = new Blob([json], { type: 'application/json' }) - const url = URL.createObjectURL(blob) - const a = document.createElement('a') - a.href = url - a.download = `${selectedTree.name.toLowerCase().replace(/\s+/g, '-')}-tree.json` - document.body.appendChild(a) - a.click() - document.body.removeChild(a) - URL.revokeObjectURL(url) - toast.success('Component tree exported as JSON') - } catch (error) { - console.error('Export failed:', error) - toast.error('Failed to export component tree') - } - } - - const handleImportJson = () => { - fileInputRef.current?.click() - } - - const handleFileChange = async (event: React.ChangeEvent) => { - const file = event.target.files?.[0] - if (!file) return - - try { - const text = await file.text() - const importedTree = JSON.parse(text) as ComponentTree - - if (!importedTree.id || !importedTree.name || !Array.isArray(importedTree.rootNodes)) { - toast.error('Invalid component tree JSON format') - return - } - - const newTree: ComponentTree = { - ...importedTree, - id: `tree-${Date.now()}`, - createdAt: Date.now(), - updatedAt: Date.now(), - } - - onTreesChange((current) => [...current, newTree]) - setSelectedTreeId(newTree.id) - toast.success(`Component tree "${newTree.name}" imported successfully`) - } catch (error) { - console.error('Import failed:', error) - toast.error('Failed to import component tree. Please check the JSON format.') - } finally { - if (fileInputRef.current) { - fileInputRef.current.value = '' - } - } - } - - return ( -
- - - setCreateDialogOpen(true)} - onImportJson={handleImportJson} - onExportJson={handleExportJson} - /> - -
- {selectedTree ? ( - - ) : ( -
-
- -

Select a component tree to edit

-
-
- )} -
- - - - -
- ) -} +export { JSONComponentTreeManager as ComponentTreeManager } from './JSONComponentTreeManager' diff --git a/src/components/FlaskDesigner.tsx b/src/components/FlaskDesigner.tsx index 131770b..2554226 100644 --- a/src/components/FlaskDesigner.tsx +++ b/src/components/FlaskDesigner.tsx @@ -1,605 +1 @@ -import { useState } from 'react' -import { FlaskBlueprint, FlaskEndpoint, FlaskParam, FlaskConfig } from '@/types/project' -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' -import { Textarea } from '@/components/ui/textarea' -import { Switch } from '@/components/ui/switch' -import { ScrollArea } from '@/components/ui/scroll-area' -import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/components/ui/dialog' -import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { Plus, Trash, Flask, Pencil } from '@phosphor-icons/react' -import { Badge } from '@/components/ui/badge' -import { Separator } from '@/components/ui/separator' - -interface FlaskDesignerProps { - config: FlaskConfig - onConfigChange: (config: FlaskConfig | ((current: FlaskConfig) => FlaskConfig)) => void -} - -export function FlaskDesigner({ config, onConfigChange }: FlaskDesignerProps) { - const [selectedBlueprintId, setSelectedBlueprintId] = useState( - config.blueprints[0]?.id || null - ) - const [blueprintDialogOpen, setBlueprintDialogOpen] = useState(false) - const [endpointDialogOpen, setEndpointDialogOpen] = useState(false) - const [editingBlueprint, setEditingBlueprint] = useState(null) - const [editingEndpoint, setEditingEndpoint] = useState(null) - - const selectedBlueprint = config.blueprints.find((b) => b.id === selectedBlueprintId) - - const handleAddBlueprint = () => { - setEditingBlueprint({ - id: `blueprint-${Date.now()}`, - name: '', - urlPrefix: '/', - endpoints: [], - description: '', - }) - setBlueprintDialogOpen(true) - } - - const handleEditBlueprint = (blueprint: FlaskBlueprint) => { - setEditingBlueprint({ ...blueprint }) - setBlueprintDialogOpen(true) - } - - const handleSaveBlueprint = () => { - if (!editingBlueprint) return - - onConfigChange((current) => { - const existingIndex = current.blueprints.findIndex((b) => b.id === editingBlueprint.id) - if (existingIndex >= 0) { - const updated = [...current.blueprints] - updated[existingIndex] = editingBlueprint - return { ...current, blueprints: updated } - } else { - return { ...current, blueprints: [...current.blueprints, editingBlueprint] } - } - }) - - setSelectedBlueprintId(editingBlueprint.id) - setBlueprintDialogOpen(false) - setEditingBlueprint(null) - } - - const handleDeleteBlueprint = (blueprintId: string) => { - onConfigChange((current) => ({ - ...current, - blueprints: current.blueprints.filter((b) => b.id !== blueprintId), - })) - if (selectedBlueprintId === blueprintId) { - setSelectedBlueprintId(null) - } - } - - const handleAddEndpoint = () => { - setEditingEndpoint({ - id: `endpoint-${Date.now()}`, - path: '/', - method: 'GET', - name: '', - description: '', - queryParams: [], - pathParams: [], - authentication: false, - corsEnabled: true, - }) - setEndpointDialogOpen(true) - } - - const handleEditEndpoint = (endpoint: FlaskEndpoint) => { - setEditingEndpoint({ ...endpoint }) - setEndpointDialogOpen(true) - } - - const handleSaveEndpoint = () => { - if (!editingEndpoint || !selectedBlueprintId) return - - onConfigChange((current) => { - const blueprints = [...current.blueprints] - const blueprintIndex = blueprints.findIndex((b) => b.id === selectedBlueprintId) - if (blueprintIndex >= 0) { - const blueprint = { ...blueprints[blueprintIndex] } - const endpointIndex = blueprint.endpoints.findIndex((e) => e.id === editingEndpoint.id) - - if (endpointIndex >= 0) { - blueprint.endpoints[endpointIndex] = editingEndpoint - } else { - blueprint.endpoints.push(editingEndpoint) - } - - blueprints[blueprintIndex] = blueprint - } - return { ...current, blueprints } - }) - - setEndpointDialogOpen(false) - setEditingEndpoint(null) - } - - const handleDeleteEndpoint = (endpointId: string) => { - if (!selectedBlueprintId) return - - onConfigChange((current) => { - const blueprints = [...current.blueprints] - const blueprintIndex = blueprints.findIndex((b) => b.id === selectedBlueprintId) - if (blueprintIndex >= 0) { - blueprints[blueprintIndex] = { - ...blueprints[blueprintIndex], - endpoints: blueprints[blueprintIndex].endpoints.filter((e) => e.id !== endpointId), - } - } - return { ...current, blueprints } - }) - } - - const addQueryParam = () => { - if (!editingEndpoint) return - setEditingEndpoint({ - ...editingEndpoint, - queryParams: [ - ...(editingEndpoint.queryParams || []), - { id: `param-${Date.now()}`, name: '', type: 'string', required: false }, - ], - }) - } - - const removeQueryParam = (paramId: string) => { - if (!editingEndpoint) return - setEditingEndpoint({ - ...editingEndpoint, - queryParams: editingEndpoint.queryParams?.filter((p) => p.id !== paramId) || [], - }) - } - - const updateQueryParam = (paramId: string, updates: Partial) => { - if (!editingEndpoint) return - setEditingEndpoint({ - ...editingEndpoint, - queryParams: - editingEndpoint.queryParams?.map((p) => (p.id === paramId ? { ...p, ...updates } : p)) || [], - }) - } - - const getMethodColor = (method: string) => { - switch (method) { - case 'GET': - return 'bg-blue-500/10 text-blue-500 border-blue-500/30' - case 'POST': - return 'bg-green-500/10 text-green-500 border-green-500/30' - case 'PUT': - return 'bg-yellow-500/10 text-yellow-500 border-yellow-500/30' - case 'DELETE': - return 'bg-red-500/10 text-red-500 border-red-500/30' - case 'PATCH': - return 'bg-purple-500/10 text-purple-500 border-purple-500/30' - default: - return 'bg-muted text-muted-foreground' - } - } - - return ( -
-
-
-
-
- -
-
-

Flask Backend Designer

-

- Design REST API endpoints and blueprints -

-
-
- -
-
- -
-
-
-

Blueprints

-
- -
- {config.blueprints.map((blueprint) => ( -
setSelectedBlueprintId(blueprint.id)} - > -
-
-

{blueprint.name}

-

- {blueprint.urlPrefix} -

- - {blueprint.endpoints.length} endpoints - -
-
- - -
-
-
- ))} -
-
-
- -
- {selectedBlueprint ? ( - <> -
-
-
-

{selectedBlueprint.name}

-

- {selectedBlueprint.description || 'No description'} -

-
- -
-
- - -
- {selectedBlueprint.endpoints.map((endpoint) => ( - - -
-
-
- - {endpoint.method} - - - {selectedBlueprint.urlPrefix} - {endpoint.path} - -
- {endpoint.name} - {endpoint.description} -
-
- - -
-
-
- -
- {endpoint.authentication && ( -
- 🔒 Authentication Required -
- )} - {endpoint.queryParams && endpoint.queryParams.length > 0 && ( -
-

Query Parameters:

-
- {endpoint.queryParams.map((param) => ( -
- {param.name} - - {param.type} - - {param.required && ( - - required - - )} -
- ))} -
-
- )} -
-
-
- ))} - - {selectedBlueprint.endpoints.length === 0 && ( - -

No endpoints yet

- -
- )} -
-
- - ) : ( -
-
- -

Select a blueprint or create a new one

-
-
- )} -
-
- - - - - - {editingBlueprint?.name ? 'Edit Blueprint' : 'New Blueprint'} - - Configure your Flask blueprint - - {editingBlueprint && ( -
-
- - - setEditingBlueprint({ ...editingBlueprint, name: e.target.value }) - } - placeholder="e.g., users, auth, products" - /> -
-
- - - setEditingBlueprint({ ...editingBlueprint, urlPrefix: e.target.value }) - } - placeholder="/api/v1" - /> -
-
- -