@@ -226,28 +226,98 @@ const erdLinks: ERDLink[] = [
226
226
const diagramContainer = ref <HTMLElement | null >(null )
227
227
let simulation: d3 .Simulation <ModelNode , undefined >
228
228
229
- // Add download function
230
- const downloadDiagram = () => {
231
- if (! diagramContainer .value ) return
229
+ // Add format state
230
+ const downloadFormat = ref <' svg' | ' png' >(' svg' )
231
+ const isDownloading = ref (false )
232
+
233
+ // Update download function to support both formats
234
+ const downloadDiagram = async () => {
235
+ if (! diagramContainer .value ) {
236
+ console .error (' No diagram container found' )
237
+ return
238
+ }
232
239
233
240
const svg = diagramContainer .value .querySelector (' svg' )
234
- if (! svg ) return
235
-
236
- // Get SVG content
237
- const svgData = svg .outerHTML
238
- const blob = new Blob ([svgData ], { type: ' image/svg+xml' })
239
- const url = URL .createObjectURL (blob )
240
-
241
- // Create download link
242
- const link = document .createElement (' a' )
243
- link .href = url
244
- link .download = ' model-relationships.svg'
245
- document .body .appendChild (link )
246
- link .click ()
247
-
248
- // Clean up
249
- document .body .removeChild (link )
250
- URL .revokeObjectURL (url )
241
+ if (! svg ) {
242
+ console .error (' No SVG element found' )
243
+ return
244
+ }
245
+
246
+ isDownloading .value = true
247
+
248
+ try {
249
+ if (downloadFormat .value === ' svg' ) {
250
+ // SVG download
251
+ const svgData = new XMLSerializer ().serializeToString (svg )
252
+ const blob = new Blob ([svgData ], { type: ' image/svg+xml' })
253
+ const url = URL .createObjectURL (blob )
254
+
255
+ const link = document .createElement (' a' )
256
+ link .href = url
257
+ link .download = ' model-relationships.svg'
258
+ document .body .appendChild (link )
259
+ link .click ()
260
+ document .body .removeChild (link )
261
+ URL .revokeObjectURL (url )
262
+ } else {
263
+ // PNG download
264
+ const svgData = new XMLSerializer ().serializeToString (svg )
265
+ const blob = new Blob ([svgData ], { type: ' image/svg+xml' })
266
+ const url = URL .createObjectURL (blob )
267
+
268
+ const img = new Image ()
269
+ const canvas = document .createElement (' canvas' )
270
+ const ctx = canvas .getContext (' 2d' )
271
+
272
+ if (! ctx ) {
273
+ throw new Error (' Could not get canvas context' )
274
+ }
275
+
276
+ // Set canvas size to match SVG
277
+ const svgSize = svg .getBoundingClientRect ()
278
+ const scale = 2 // For better resolution
279
+ canvas .width = svgSize .width * scale
280
+ canvas .height = svgSize .height * scale
281
+
282
+ // Set white background
283
+ ctx .fillStyle = ' #ffffff'
284
+ ctx .fillRect (0 , 0 , canvas .width , canvas .height )
285
+
286
+ // Scale for better resolution
287
+ ctx .scale (scale , scale )
288
+
289
+ return new Promise ((resolve , reject ) => {
290
+ img .onload = () => {
291
+ try {
292
+ ctx .drawImage (img , 0 , 0 )
293
+ const pngUrl = canvas .toDataURL (' image/png' )
294
+
295
+ const link = document .createElement (' a' )
296
+ link .href = pngUrl
297
+ link .download = ' model-relationships.png'
298
+ document .body .appendChild (link )
299
+ link .click ()
300
+ document .body .removeChild (link )
301
+
302
+ URL .revokeObjectURL (url )
303
+ resolve (true )
304
+ } catch (error ) {
305
+ reject (error )
306
+ }
307
+ }
308
+
309
+ img .onerror = () => {
310
+ reject (new Error (' Failed to load SVG into image' ))
311
+ }
312
+
313
+ img .src = url
314
+ })
315
+ }
316
+ } catch (error ) {
317
+ console .error (' Error downloading diagram:' , error )
318
+ } finally {
319
+ isDownloading .value = false
320
+ }
251
321
}
252
322
253
323
// Create model diagram
@@ -469,18 +539,29 @@ onUnmounted(() => {
469
539
<div class =" p-6" >
470
540
<div class =" flex items-center justify-between mb-4" >
471
541
<div class =" flex items-center gap-2" >
472
- <h4 class =" text-base font-medium text-gray-900 dark:text-gray-100" >Model Relationships </h4 >
542
+ <h4 class =" text-base font-medium text-gray-900 dark:text-gray-100" >Entity Relationship Diagram </h4 >
473
543
<span class =" text-sm text-gray-500 dark:text-gray-400" >
474
544
(Drag nodes to rearrange)
475
545
</span >
476
546
</div >
477
- <button
478
- @click =" downloadDiagram"
479
- class =" inline-flex items-center gap-2 px-3 py-2 text-sm font-medium text-gray-700 dark:text-gray-200 bg-white dark:bg-blue-gray-600 rounded-md shadow-sm ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-blue-gray-500 transition-colors duration-200"
480
- >
481
- <div class =" i-heroicons-arrow-down-tray w-5 h-5" />
482
- Download SVG
483
- </button >
547
+ <div class =" inline-flex rounded-md shadow-sm" >
548
+ <select
549
+ v-model =" downloadFormat"
550
+ class =" h-9 text-sm border-0 rounded-l-md bg-white dark:bg-blue-gray-600 py-1.5 pl-3 pr-7 text-gray-700 dark:text-gray-200 ring-1 ring-inset ring-gray-300 dark:ring-gray-600 hover:bg-gray-50 dark:hover:bg-blue-gray-500 focus:ring-2 focus:ring-blue-600 transition-colors duration-200"
551
+ >
552
+ <option value =" svg" >SVG</option >
553
+ <option value =" png" >PNG</option >
554
+ </select >
555
+ <button
556
+ @click =" downloadDiagram"
557
+ :disabled =" isDownloading"
558
+ class =" relative inline-flex items-center gap-1.5 px-3 py-1.5 text-sm font-medium text-white bg-blue-600 border border-transparent rounded-r-md shadow-sm hover:bg-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
559
+ >
560
+ <div v-if =" isDownloading" class =" i-heroicons-arrow-path w-4 h-4 animate-spin" />
561
+ <div v-else class =" i-heroicons-arrow-down-tray w-4 h-4" />
562
+ Download
563
+ </button >
564
+ </div >
484
565
</div >
485
566
<div ref =" diagramContainer" class =" w-full h-[800px] bg-gray-50 dark:bg-blue-gray-800 rounded-lg" ></div >
486
567
</div >
0 commit comments