diff --git a/prd.json b/prd.json index b9122fb..fb3e809 100644 --- a/prd.json +++ b/prd.json @@ -620,7 +620,7 @@ "Verify in browser using dev-browser skill" ], "priority": 35, - "passes": false, + "passes": true, "notes": "" }, { diff --git a/progress.txt b/progress.txt index d900036..a260d39 100644 --- a/progress.txt +++ b/progress.txt @@ -506,3 +506,17 @@ - Toast state uses object with message and type for flexibility - Loading spinner SVG with animate-spin class for visual feedback during save --- + +## 2026-01-22 - US-035 +- What was implemented: Export project as .vnflow file functionality +- Files changed: + - src/app/editor/[projectId]/FlowchartEditor.tsx - implemented handleExport with blob creation and download trigger, added projectName prop + - src/app/editor/[projectId]/page.tsx - passed projectName prop to FlowchartEditor +- **Learnings for future iterations:** + - Use Blob with type 'application/json' for JSON file downloads + - JSON.stringify(data, null, 2) creates pretty-printed JSON with 2-space indentation + - URL.createObjectURL creates a temporary URL for the blob + - Create temporary anchor element with download attribute to trigger file download + - Remember to cleanup: remove the anchor from DOM and revoke the object URL + - Props needed for export: pass data down from server components (e.g., projectName) to client components that need them +--- diff --git a/src/app/editor/[projectId]/FlowchartEditor.tsx b/src/app/editor/[projectId]/FlowchartEditor.tsx index 6462ae4..6ded190 100644 --- a/src/app/editor/[projectId]/FlowchartEditor.tsx +++ b/src/app/editor/[projectId]/FlowchartEditor.tsx @@ -53,6 +53,7 @@ type ConditionEditorState = { type FlowchartEditorProps = { projectId: string + projectName: string initialData: FlowchartData } @@ -145,7 +146,7 @@ function flowchartDataEquals(a: FlowchartData, b: FlowchartData): boolean { } // Inner component that uses useReactFlow hook -function FlowchartEditorInner({ projectId, initialData }: FlowchartEditorProps) { +function FlowchartEditorInner({ projectId, projectName, initialData }: FlowchartEditorProps) { // Define custom node types - memoized to prevent re-renders const nodeTypes: NodeTypes = useMemo( () => ({ @@ -351,8 +352,32 @@ function FlowchartEditorInner({ projectId, initialData }: FlowchartEditorProps) }, [isSaving, nodes, edges, projectId]) const handleExport = useCallback(() => { - // TODO: Implement in US-035 - }, []) + // Convert React Flow state to FlowchartData + const flowchartData: FlowchartData = { + nodes: fromReactFlowNodes(nodes), + edges: fromReactFlowEdges(edges), + } + + // Create pretty-printed JSON + const jsonContent = JSON.stringify(flowchartData, null, 2) + + // Create blob with JSON content + const blob = new Blob([jsonContent], { type: 'application/json' }) + + // Create download URL + const url = URL.createObjectURL(blob) + + // Create temporary link element and trigger download + const link = document.createElement('a') + link.href = url + link.download = `${projectName}.vnflow` + document.body.appendChild(link) + link.click() + + // Cleanup + document.body.removeChild(link) + URL.revokeObjectURL(url) + }, [nodes, edges, projectName]) const handleImport = useCallback(() => { // TODO: Implement in US-036 diff --git a/src/app/editor/[projectId]/page.tsx b/src/app/editor/[projectId]/page.tsx index 8117932..953274e 100644 --- a/src/app/editor/[projectId]/page.tsx +++ b/src/app/editor/[projectId]/page.tsx @@ -66,6 +66,7 @@ export default async function EditorPage({ params }: PageProps) {