11import { useState , useMemo } from 'react' ;
2- import { X , Download , Copy , Check , FileImage , FileText , BookOpen } from 'lucide-react' ;
2+ import { X , Download , Copy , Check , FileImage , FileText , BookOpen , Image , Loader2 } from 'lucide-react' ;
33import { useDomainStore } from '@/stores' ;
4- import { generateDiagram , downloadDiagram , generateDocs , downloadDocumentation } from '@/utils/export' ;
5- import type { DiagramFormat } from '@/utils/export' ;
4+ import { generateDiagram , downloadDiagram , generateDocs , downloadDocumentation , exportAndDownloadCanvas } from '@/utils/export' ;
5+ import type { DiagramFormat , ImageFormat } from '@/utils/export' ;
66
77interface DiagramExportPanelProps {
88 isOpen : boolean ;
99 onClose : ( ) => void ;
1010}
1111
12- type ExportTab = 'diagram' | 'documentation' ;
12+ type ExportTab = 'image' | ' diagram' | 'documentation' ;
1313
1414const diagramFormats : { id : DiagramFormat ; name : string ; description : string } [ ] = [
1515 { id : 'mermaid-class' , name : 'Mermaid Class Diagram' , description : 'UML-style class diagram' } ,
@@ -18,10 +18,14 @@ const diagramFormats: { id: DiagramFormat; name: string; description: string }[]
1818
1919export function DiagramExportPanel ( { isOpen, onClose } : DiagramExportPanelProps ) {
2020 const { contexts, activeContextId, contextMaps } = useDomainStore ( ) ;
21- const [ activeTab , setActiveTab ] = useState < ExportTab > ( 'diagram ' ) ;
21+ const [ activeTab , setActiveTab ] = useState < ExportTab > ( 'image ' ) ;
2222 const [ selectedFormat , setSelectedFormat ] = useState < DiagramFormat > ( 'mermaid-class' ) ;
2323 const [ copied , setCopied ] = useState ( false ) ;
2424
25+ // Image export options
26+ const [ imageFormat , setImageFormat ] = useState < ImageFormat > ( 'png' ) ;
27+ const [ isExporting , setIsExporting ] = useState ( false ) ;
28+
2529 // Documentation options
2630 const [ includeTOC , setIncludeTOC ] = useState ( true ) ;
2731 const [ includeRelationships , setIncludeRelationships ] = useState ( true ) ;
@@ -74,6 +78,21 @@ export function DiagramExportPanel({ isOpen, onClose }: DiagramExportPanelProps)
7478 }
7579 } ;
7680
81+ const handleImageExport = async ( ) => {
82+ setIsExporting ( true ) ;
83+ try {
84+ const success = await exportAndDownloadCanvas ( { format : imageFormat } ) ;
85+ if ( ! success ) {
86+ alert ( 'Failed to export canvas. Make sure there are nodes on the canvas.' ) ;
87+ }
88+ } catch ( error ) {
89+ console . error ( 'Export failed:' , error ) ;
90+ alert ( 'Failed to export canvas.' ) ;
91+ } finally {
92+ setIsExporting ( false ) ;
93+ }
94+ } ;
95+
7796 if ( ! isOpen ) return null ;
7897
7998 return (
@@ -104,6 +123,17 @@ export function DiagramExportPanel({ isOpen, onClose }: DiagramExportPanelProps)
104123
105124 { /* Tabs */ }
106125 < div className = "flex border-b border-slate-200 dark:border-slate-700" >
126+ < button
127+ onClick = { ( ) => setActiveTab ( 'image' ) }
128+ className = { `flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 -mb-px ${
129+ activeTab === 'image'
130+ ? 'border-primary text-primary'
131+ : 'border-transparent text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-100'
132+ } `}
133+ >
134+ < Image className = "w-4 h-4" />
135+ Image
136+ </ button >
107137 < button
108138 onClick = { ( ) => setActiveTab ( 'diagram' ) }
109139 className = { `flex items-center gap-2 px-4 py-2 text-sm font-medium border-b-2 -mb-px ${
@@ -113,7 +143,7 @@ export function DiagramExportPanel({ isOpen, onClose }: DiagramExportPanelProps)
113143 } `}
114144 >
115145 < FileImage className = "w-4 h-4" />
116- Diagrams
146+ Mermaid
117147 </ button >
118148 < button
119149 onClick = { ( ) => setActiveTab ( 'documentation' ) }
@@ -136,7 +166,72 @@ export function DiagramExportPanel({ isOpen, onClose }: DiagramExportPanelProps)
136166 < div className = "flex-1 flex overflow-hidden" >
137167 { /* Sidebar */ }
138168 < div className = "w-64 border-r border-slate-200 dark:border-slate-700 p-4 overflow-y-auto" >
139- { activeTab === 'diagram' ? (
169+ { activeTab === 'image' ? (
170+ < >
171+ < label className = "block text-sm font-medium mb-3" > Image Format</ label >
172+ < div className = "space-y-2" >
173+ < button
174+ onClick = { ( ) => setImageFormat ( 'png' ) }
175+ className = { `w-full text-left p-3 rounded-lg border ${
176+ imageFormat === 'png'
177+ ? 'border-primary bg-primary/10'
178+ : 'border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700'
179+ } `}
180+ >
181+ < div className = "font-medium text-sm" > PNG</ div >
182+ < div className = "text-xs text-slate-500 dark:text-slate-400 mt-1" >
183+ Raster image, best for sharing
184+ </ div >
185+ </ button >
186+ < button
187+ onClick = { ( ) => setImageFormat ( 'svg' ) }
188+ className = { `w-full text-left p-3 rounded-lg border ${
189+ imageFormat === 'svg'
190+ ? 'border-primary bg-primary/10'
191+ : 'border-slate-200 dark:border-slate-600 hover:bg-slate-50 dark:hover:bg-slate-700'
192+ } `}
193+ >
194+ < div className = "font-medium text-sm" > SVG</ div >
195+ < div className = "text-xs text-slate-500 dark:text-slate-400 mt-1" >
196+ Vector format, scalable
197+ </ div >
198+ </ button >
199+ </ div >
200+
201+ < button
202+ onClick = { handleImageExport }
203+ disabled = { isExporting }
204+ className = "w-full mt-6 flex items-center justify-center gap-2 px-4 py-3 rounded-lg bg-primary text-white hover:bg-primary-hover disabled:opacity-50"
205+ >
206+ { isExporting ? (
207+ < >
208+ < Loader2 className = "w-4 h-4 animate-spin" />
209+ Exporting...
210+ </ >
211+ ) : (
212+ < >
213+ < Download className = "w-4 h-4" />
214+ Export { imageFormat . toUpperCase ( ) }
215+ </ >
216+ ) }
217+ </ button >
218+
219+ < div className = "mt-6 p-3 bg-slate-100 dark:bg-slate-700 rounded-lg" >
220+ < div className = "flex items-center gap-2 text-sm font-medium mb-2" >
221+ < Image className = "w-4 h-4" />
222+ Canvas Export
223+ </ div >
224+ < p className = "text-xs text-slate-600 dark:text-slate-400" >
225+ Exports the current canvas view as an image. The exported image includes:
226+ </ p >
227+ < ul className = "text-xs text-slate-600 dark:text-slate-400 mt-2 space-y-1" >
228+ < li > • All visible nodes</ li >
229+ < li > • Connections/edges</ li >
230+ < li > • Current zoom level</ li >
231+ </ ul >
232+ </ div >
233+ </ >
234+ ) : activeTab === 'diagram' ? (
140235 < >
141236 < label className = "block text-sm font-medium mb-3" > Diagram Format</ label >
142237 < div className = "space-y-2" >
@@ -229,41 +324,56 @@ export function DiagramExportPanel({ isOpen, onClose }: DiagramExportPanelProps)
229324
230325 { /* Preview */ }
231326 < div className = "flex-1 flex flex-col overflow-hidden" >
232- { /* Toolbar */ }
233- < div className = "flex items-center justify-between px-4 py-2 border-b border-slate-200 dark:border-slate-700" >
234- < span className = "text-sm font-medium text-slate-600 dark:text-slate-400" >
235- { currentFilename || 'No content' }
236- </ span >
237- < div className = "flex items-center gap-2" >
238- < button
239- onClick = { handleCopy }
240- disabled = { ! currentContent }
241- className = "flex items-center gap-1 px-3 py-1 text-sm rounded border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 disabled:opacity-50"
242- >
243- { copied ? < Check className = "w-4 h-4 text-green-500" /> : < Copy className = "w-4 h-4" /> }
244- { copied ? 'Copied!' : 'Copy' }
245- </ button >
246- < button
247- onClick = { handleDownload }
248- disabled = { ! currentContent }
249- className = "flex items-center gap-1 px-3 py-1 text-sm rounded bg-primary text-white hover:bg-primary-hover disabled:opacity-50"
250- >
251- < Download className = "w-4 h-4" />
252- Download
253- </ button >
327+ { activeTab === 'image' ? (
328+ < div className = "flex-1 flex items-center justify-center bg-slate-100 dark:bg-slate-800 p-8" >
329+ < div className = "text-center" >
330+ < Image className = "w-16 h-16 mx-auto mb-4 text-slate-400" />
331+ < h3 className = "text-lg font-medium mb-2" > Canvas Image Export</ h3 >
332+ < p className = "text-sm text-slate-500 dark:text-slate-400 max-w-md" >
333+ Select a format and click "Export" to download an image of your current canvas.
334+ The image will capture all nodes and connections as they appear on screen.
335+ </ p >
336+ </ div >
254337 </ div >
255- </ div >
338+ ) : (
339+ < >
340+ { /* Toolbar */ }
341+ < div className = "flex items-center justify-between px-4 py-2 border-b border-slate-200 dark:border-slate-700" >
342+ < span className = "text-sm font-medium text-slate-600 dark:text-slate-400" >
343+ { currentFilename || 'No content' }
344+ </ span >
345+ < div className = "flex items-center gap-2" >
346+ < button
347+ onClick = { handleCopy }
348+ disabled = { ! currentContent }
349+ className = "flex items-center gap-1 px-3 py-1 text-sm rounded border border-slate-300 dark:border-slate-600 hover:bg-slate-100 dark:hover:bg-slate-700 disabled:opacity-50"
350+ >
351+ { copied ? < Check className = "w-4 h-4 text-green-500" /> : < Copy className = "w-4 h-4" /> }
352+ { copied ? 'Copied!' : 'Copy' }
353+ </ button >
354+ < button
355+ onClick = { handleDownload }
356+ disabled = { ! currentContent }
357+ className = "flex items-center gap-1 px-3 py-1 text-sm rounded bg-primary text-white hover:bg-primary-hover disabled:opacity-50"
358+ >
359+ < Download className = "w-4 h-4" />
360+ Download
361+ </ button >
362+ </ div >
363+ </ div >
256364
257- { /* Content */ }
258- < div className = "flex-1 overflow-auto bg-slate-900 p-4" >
259- { currentContent ? (
260- < pre className = "text-sm text-slate-100 font-mono whitespace-pre-wrap" >
261- < code > { currentContent } </ code >
262- </ pre >
263- ) : (
264- < p className = "text-slate-400" > No content generated</ p >
265- ) }
266- </ div >
365+ { /* Content */ }
366+ < div className = "flex-1 overflow-auto bg-slate-900 p-4" >
367+ { currentContent ? (
368+ < pre className = "text-sm text-slate-100 font-mono whitespace-pre-wrap" >
369+ < code > { currentContent } </ code >
370+ </ pre >
371+ ) : (
372+ < p className = "text-slate-400" > No content generated</ p >
373+ ) }
374+ </ div >
375+ </ >
376+ ) }
267377 </ div >
268378 </ div >
269379 ) }
0 commit comments