diff --git a/.eslintrc.cjs b/.eslintrc.cjs index bc6f10b9..5e9b065b 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -77,6 +77,7 @@ module.exports = { 'error', { allowList: { + args: true, ctx: true, db: true, doc: true, diff --git a/src/lib/components/FloatingToolbar.svelte b/src/lib/components/FloatingToolbar.svelte new file mode 100644 index 00000000..5e7c63f4 --- /dev/null +++ b/src/lib/components/FloatingToolbar.svelte @@ -0,0 +1,13 @@ + + +
+ {@render children()} +
diff --git a/src/lib/components/SyncRoughToolbar.svelte b/src/lib/components/SyncRoughToolbar.svelte new file mode 100644 index 00000000..14839407 --- /dev/null +++ b/src/lib/components/SyncRoughToolbar.svelte @@ -0,0 +1,29 @@ + + + + + + + + {#if !$stateStore.autoSync} + + {/if} + diff --git a/src/lib/components/View.svelte b/src/lib/components/View.svelte index 6b3d108a..d6d37339 100644 --- a/src/lib/components/View.svelte +++ b/src/lib/components/View.svelte @@ -102,7 +102,6 @@ ); if (svg.length > 0) { - handlePanZoom(state); container.innerHTML = svg; const graphDiv = document.querySelector('#graph-div'); if (!graphDiv) { @@ -119,6 +118,7 @@ } const height = sketch.getAttribute('height'); const width = sketch.getAttribute('width'); + sketch.setAttribute('id', 'graph-div'); sketch.setAttribute('height', '100%'); sketch.setAttribute('width', '100%'); sketch.setAttribute('viewBox', `0 0 ${width} ${height}`); @@ -130,6 +130,7 @@ bindFunctions(graphDiv); } } + handlePanZoom(state); } if (view?.parentElement && scroll) { view.parentElement.scrollTop = scroll; @@ -165,7 +166,7 @@ {#if outOfSync}
Diagram out of sync.
{#if $stateStore.autoSync} diff --git a/src/lib/components/ui/switch/switch.svelte b/src/lib/components/ui/switch/switch.svelte index 03f5ef31..fe111cbd 100644 --- a/src/lib/components/ui/switch/switch.svelte +++ b/src/lib/components/ui/switch/switch.svelte @@ -1,27 +1,25 @@ - + bind:ref + bind:checked + class={cn( + 'peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-secondary data-[state=unchecked]:bg-input', + className + )} + {...restProps}> + diff --git a/src/lib/util/panZoom.ts b/src/lib/util/panZoom.ts new file mode 100644 index 00000000..694eeab4 --- /dev/null +++ b/src/lib/util/panZoom.ts @@ -0,0 +1,94 @@ +import type { State } from '$/types'; +import type { Point } from 'mermaid/dist/types.js'; +import panzoom from 'svg-pan-zoom'; +type PanZoom = typeof panzoom; + +export class PanZoomState { + private pan?: Point; + private zoom?: number; + private pzoom: PanZoom | undefined; + private isDirty = false; + private resizeObserver: ResizeObserver; + + public isPanEnabled: boolean; + + constructor() { + this.isPanEnabled = true; + this.resizeObserver = new ResizeObserver(() => { + this.resize(); + if (!this.isDirty) { + this.reset(); + } + }); + } + + public updateElement(diagramView: SVGElement, { pan, zoom }: Pick) { + this.pzoom = panzoom(diagramView, { + onPan: (pan) => { + this.pan = pan; + this.zoom = this.pzoom?.getZoom(); + this.isDirty = true; + }, + onZoom: (zoom) => { + this.zoom = zoom; + this.pan = this.pzoom?.getPan(); + this.isDirty = true; + }, + controlIconsEnabled: false, + panEnabled: true, + zoomEnabled: true, + fit: true, + center: true, + maxZoom: 12, + minZoom: 0.2 + }); + + this.pzoom.disableDblClickZoom(); + + this.resizeObserver.disconnect(); + this.resizeObserver.observe(diagramView); + + // TODO: Investigate why this is necessary + if (pan !== undefined && zoom !== undefined && Number.isFinite(zoom)) { + this.restorePanZoom(pan, zoom); + } else if (this.pan && this.zoom) { + this.restorePanZoom(this.pan, this.zoom); + } + + // we start out with both pan and zoom enabled so that the tool can auto position view refreshed + // then set enable/disable pan based on state + if (this.isPanEnabled) { + this.pzoom.enablePan(); + this.pzoom.enableZoom(); + } else { + this.pzoom.disableZoom(); + this.pzoom.disablePan(); + } + } + + public restorePanZoom(pan: Point, zoom: number) { + if (!this.pzoom) { + console.error('PanZoomState.restorePanZoom: pzoom is not initialized'); + return; + } + this.pzoom.zoom(zoom); + this.pzoom.pan(pan); + } + + public resize() { + this.pzoom?.resize(); + } + + public zoomIn() { + this.pzoom?.zoomIn(); + } + + public zoomOut() { + this.pzoom?.zoomOut(); + } + + public reset() { + this.pzoom?.reset(); + this.isDirty = false; + } +} diff --git a/src/routes/edit/+page.svelte b/src/routes/edit/+page.svelte index 7868fee0..db581875 100644 --- a/src/routes/edit/+page.svelte +++ b/src/routes/edit/+page.svelte @@ -1,4 +1,5 @@