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) {