From 3a31d8a18fe05230edc9196225744629e14e6385 Mon Sep 17 00:00:00 2001 From: Shubham Date: Sat, 13 Dec 2025 01:10:07 +0530 Subject: [PATCH] Enhance download options in Toolbar and improve SVG compatibility in utils --- src/lib/components/Toolbar.svelte | 70 +++++++++++++++++++++++++++++-- src/lib/components/View.svelte | 4 +- src/lib/diagram.ts | 14 ++++++- src/lib/utils.test.ts | 2 +- src/lib/utils.ts | 2 +- 5 files changed, 84 insertions(+), 8 deletions(-) diff --git a/src/lib/components/Toolbar.svelte b/src/lib/components/Toolbar.svelte index b4cac5b..2659201 100644 --- a/src/lib/components/Toolbar.svelte +++ b/src/lib/components/Toolbar.svelte @@ -14,6 +14,7 @@ import IconShare from './icons/IconShare.svelte'; let root: HTMLDivElement; + let downloadDropdownOpen = false; export let url: string | undefined; export let loading: boolean; @@ -22,7 +23,8 @@ const dispatch = createEventDispatcher<{ reload: undefined; fit: undefined; - download: undefined; + downloadPng: undefined; + downloadSvg: undefined; copy: undefined; share: undefined; settings: undefined; @@ -38,9 +40,17 @@ dispatch('copy'); }; + const handleClickOutside = (event: MouseEvent) => { + if (root && !root.contains(event.target as Node)) { + downloadDropdownOpen = false; + } + }; + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('click', handleClickOutside); onDestroy(() => { window.removeEventListener('keydown', handleKeyDown); + window.removeEventListener('click', handleClickOutside); }); @@ -74,9 +84,25 @@ > - dispatch('download')}> - - +
+ downloadDropdownOpen = !downloadDropdownOpen} + > + + + {#if downloadDropdownOpen} +
+ + +
+ {/if} +
downloadPng(data, $settings)} + on:downloadPng={() => downloadPng(data, $settings)} + on:downloadSvg={() => downloadSvg(data, $settings)} on:copy={handleCopy} on:share={handleShare} on:settings={() => (settingsVisible = !settingsVisible)} diff --git a/src/lib/diagram.ts b/src/lib/diagram.ts index 142cfb7..1cac06e 100644 --- a/src/lib/diagram.ts +++ b/src/lib/diagram.ts @@ -1,5 +1,5 @@ import { browser } from '$app/environment'; -import { draw } from 'nomnoml'; +import { draw, renderSvg } from 'nomnoml'; import PocketBase, { type CollectionModel } from 'pocketbase'; import type { Settings } from './settings'; import { sanitizeId, spaces, stripBackslashes } from './utils'; @@ -250,6 +250,18 @@ export function downloadPng(collections: CollectionModel[] | undefined, settings link.remove(); } +export function downloadSvg(collections: CollectionModel[] | undefined, settings: Settings) { + if (!(browser && collections)) return; + const markup = generateMarkup(collections, settings); + const svg = renderSvg(markup, document); + const blob = new Blob([svg], { type: 'image/svg+xml' }); + const link = document.createElement('a'); + link.download = 'pb_diagram.svg'; + link.href = URL.createObjectURL(blob); + link.click(); + link.remove(); +} + export function copyPng(collections: CollectionModel[] | undefined, settings: Settings) { if (!(browser && collections)) return; const hiddenCanvas = document.createElement('canvas'); diff --git a/src/lib/utils.test.ts b/src/lib/utils.test.ts index 95e0a6b..4c27aa0 100644 --- a/src/lib/utils.test.ts +++ b/src/lib/utils.test.ts @@ -15,7 +15,7 @@ describe('strip backslashes', () => { describe('spaces', () => { it('works with positive input', () => { - expect(spaces(3)).toBe(' '); + expect(spaces(3)).toBe('\u00A0\u00A0\u00A0'); }); it('works when input is 0', () => { expect(spaces(0)).toBe(''); diff --git a/src/lib/utils.ts b/src/lib/utils.ts index b6b7c7e..860e9ab 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -3,7 +3,7 @@ export function stripBackslashes(s: string) { } export function spaces(n: number) { - return n < 1 ? '' : ' '.repeat(n); + return n < 1 ? '' : '\u00A0'.repeat(n); // Use non-breaking spaces for better SVG compatibility } export function absRoundedHalfDiff(a: number, b: number) {