diff --git a/src/lib/components/actions.svelte b/src/lib/components/actions.svelte index 8b3899ec..d96c292a 100644 --- a/src/lib/components/actions.svelte +++ b/src/lib/components/actions.svelte @@ -115,19 +115,19 @@ const onCopyClipboard = (event: Event) => { exportImage(event, clipboardCopy); - void logEvent('copyClipboard'); + logEvent('copyClipboard'); }; const onDownloadPNG = (event: Event) => { exportImage(event, downloadImage); - void logEvent('download', { + logEvent('download', { type: 'png' }); }; const onDownloadSVG = () => { simulateDownload(getFileName('svg'), `data:image/svg+xml;base64,${getBase64SVG()}`); - void logEvent('download', { + logEvent('download', { type: 'svg' }); }; @@ -135,7 +135,7 @@ const onCopyMarkdown = () => { (document.getElementById('markdown') as HTMLInputElement).select(); document.execCommand('Copy'); - void logEvent('copyMarkdown'); + logEvent('copyMarkdown'); }; let gistURL = ''; @@ -151,7 +151,7 @@ alert('Please enter a Gist URL first'); } window.location.href = `${window.location.pathname}?gist=${gistURL}`; - void logEvent('loadGist'); + logEvent('loadGist'); }; let iUrl: string; diff --git a/src/lib/components/editor.svelte b/src/lib/components/editor.svelte index 0b5b8572..e7a971e0 100644 --- a/src/lib/components/editor.svelte +++ b/src/lib/components/editor.svelte @@ -7,7 +7,6 @@ import { onMount } from 'svelte'; import initEditor from 'monaco-mermaid'; import { logEvent } from '$lib/util/stats'; - import { debounceEnabled } from '$lib/util/env'; let divEl: HTMLDivElement = null; let editor: monaco.editor.IStandaloneCodeEditor; @@ -37,10 +36,8 @@ Monaco?.editor.setModelLanguage(editor.getModel(), language); } - // Display errors if present - if (errorMarkers.length > 0) { - Monaco?.editor.setModelMarkers(editor.getModel(), 'test', errorMarkers); - } + // Display/clear errors + Monaco?.editor.setModelMarkers(editor.getModel(), 'mermaid', errorMarkers); }); themeStore.subscribe(({ isDark }) => { @@ -55,21 +52,6 @@ } }; - // Debounce state updates to avoid performance issues - let debounce: { [key: string]: number } = {}; - const updateHandler = (newText: string) => { - text = newText; - const mode = $stateStore.editorMode; - if (debounceEnabled) { - clearTimeout(debounce[mode]); - debounce[mode] = window.setTimeout(() => { - handleUpdate(text, mode); - }, 300); - } else { - handleUpdate(text, mode); - } - }; - const loadMonaco = async () => { let i = 0; while (i++ < 500) { @@ -89,7 +71,8 @@ initEditor(Monaco); editor = Monaco.editor.create(divEl, editorOptions); editor.onDidChangeModelContent(() => { - updateHandler(editor.getValue()); + text = editor.getValue(); + handleUpdate(text, $stateStore.editorMode); }); editor.addAction({ id: 'mermaid-render-diagram', @@ -97,7 +80,7 @@ keybindings: [Monaco.KeyMod.CtrlCmd | Monaco.KeyCode.Enter], run: function () { syncDiagram(); - void logEvent('renderDiagram', { + logEvent('renderDiagram', { method: 'keyboadShortcut' }); } diff --git a/src/lib/components/preset.svelte b/src/lib/components/preset.svelte index 16b909cb..fc42ac9e 100644 --- a/src/lib/components/preset.svelte +++ b/src/lib/components/preset.svelte @@ -100,7 +100,7 @@ updateDiagram: true, resetPanZoom: true }); - void logEvent('loadSampleDiagram', { diagramType }); + logEvent('loadSampleDiagram', { diagramType }); }; diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte index e50e1580..b3c2d330 100644 --- a/src/lib/components/view.svelte +++ b/src/lib/components/view.svelte @@ -3,7 +3,7 @@ import { onMount } from 'svelte'; import mermaid from 'mermaid'; import panzoom from 'svg-pan-zoom'; - import type { State } from '$lib/types'; + import type { State, ValidatedState } from '$lib/types'; import { logEvent } from '$lib/util/stats'; let code = ''; @@ -16,16 +16,18 @@ let manualUpdate = true; let panZoomEnabled = $stateStore.panZoom; let pzoom: SvgPanZoom.Instance; - let debounce: number; + // let panZoomDebounce: number; + // let stateDebounce: number; const handlePanZoomChange = () => { + // This needs to be debounced here separately to avoid frequent updates to browser history API, breaking it. const pan = pzoom.getPan(); const zoom = pzoom.getZoom(); - clearTimeout(debounce); - debounce = window.setTimeout(() => { - updateCodeStore({ pan, zoom }); - void logEvent('panZoom'); - }, 200); + // clearTimeout(panZoomDebounce); + // panZoomDebounce = window.setTimeout(() => { + updateCodeStore({ pan, zoom }); + logEvent('panZoom'); + // }, 200); }; const handlePanZoom = (state: State) => { @@ -53,50 +55,56 @@ }); }; + const handleStateChange = (state: ValidatedState) => { + if (state.error !== undefined) { + error = true; + return; + } + error = false; + try { + if (container && state && (state.updateDiagram || state.autoSync)) { + if (!state.autoSync) { + $inputStateStore.updateDiagram = false; + } + outOfSync = false; + manualUpdate = true; + if (code === state.code && config === state.mermaid && panZoomEnabled === state.panZoom) { + // Do not render if there is no change in Code/Config/PanZoom + return; + } + code = state.code; + config = state.mermaid; + panZoomEnabled = state.panZoom; + const scroll = view.parentElement.scrollTop; + delete container.dataset.processed; + mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid))); + mermaid.render('graph-div', code, (svgCode) => { + if (svgCode.length > 0) { + handlePanZoom(state); + container.innerHTML = svgCode; + const graphDiv = document.getElementById('graph-div'); + graphDiv.setAttribute('height', '100%'); + graphDiv.style.maxWidth = '100%'; + } + }); + view.parentElement.scrollTop = scroll; + error = false; + } else if (manualUpdate) { + manualUpdate = false; + } else if (code !== state.code || config !== state.mermaid) { + outOfSync = true; + } + } catch (e) { + console.error('view fail', e); + error = true; + } + }; onMount(() => { stateStore.subscribe((state) => { - if (state.error !== undefined) { - error = true; - return; - } - error = false; - try { - if (container && state && (state.updateDiagram || state.autoSync)) { - if (!state.autoSync) { - $inputStateStore.updateDiagram = false; - } - outOfSync = false; - manualUpdate = true; - if (code === state.code && config === state.mermaid && panZoomEnabled === state.panZoom) { - // Do not render if there is no change in Code/Config/PanZoom - return; - } - code = state.code; - config = state.mermaid; - panZoomEnabled = state.panZoom; - const scroll = view.parentElement.scrollTop; - delete container.dataset.processed; - mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid))); - mermaid.render('graph-div', code, (svgCode) => { - if (svgCode.length > 0) { - handlePanZoom(state); - container.innerHTML = svgCode; - const graphDiv = document.getElementById('graph-div'); - graphDiv.setAttribute('height', '100%'); - graphDiv.style.maxWidth = '100%'; - } - }); - view.parentElement.scrollTop = scroll; - error = false; - } else if (manualUpdate) { - manualUpdate = false; - } else if (code !== state.code || config !== state.mermaid) { - outOfSync = true; - } - } catch (e) { - console.error('view fail', e); - error = true; - } + // clearTimeout(stateDebounce); + // stateDebounce = window.setTimeout(() => + handleStateChange(state); + // , 200); }); window.addEventListener('resize', () => { if ($stateStore.panZoom && pzoom) { diff --git a/src/lib/util/env.ts b/src/lib/util/env.ts index dd40b8dc..590c1f00 100644 --- a/src/lib/util/env.ts +++ b/src/lib/util/env.ts @@ -2,4 +2,4 @@ export const rendererUrl: string = (import.meta.env.MERMAID_RENDERER_URL as string) ?? 'https://mermaid.ink'; export const krokiRendererUrl: string = (import.meta.env.MERMAID_KROKI_RENDERER_URL as string) ?? 'https://kroki.io'; -export const debounceEnabled: boolean = import.meta.env.MERMAID_DISABLE_DEBOUNCE !== 'true'; +// export const debounceEnabled: boolean = import.meta.env.MERMAID_DISABLE_DEBOUNCE !== 'true'; diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index 989269fa..a4940f45 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -162,9 +162,13 @@ export const toggleDarkTheme = (dark: boolean): void => { }); }; +let urlDebounce: number; export const initURLSubscription = (): void => { stateStore.subscribe(({ serialized }) => { - history.replaceState(undefined, undefined, `#${serialized}`); + clearInterval(urlDebounce); + urlDebounce = window.setTimeout(() => { + history.replaceState(undefined, undefined, `#${serialized}`); + }, 250); }); }; diff --git a/src/lib/util/stats.ts b/src/lib/util/stats.ts index 6df0afcf..8cafc005 100644 --- a/src/lib/util/stats.ts +++ b/src/lib/util/stats.ts @@ -42,21 +42,20 @@ const detectType = (text: string): string => { .split(' ')[0]; }; -// manual debounce -let timeout: number; export const saveStatistics = (graph: string): void => { + const graphType = detectType(graph); + console.debug(`ga: send event: render ${graphType}`); + logEvent('render', { graphType }); +}; + +// manual debounce to only send analytics event every 5 seconds if same event is repeated frequently. +const timeouts: Record = {}; +export const logEvent = (name: string, data?: unknown): void => { if (analytics) { - clearTimeout(timeout); - // Only save statistics after a 5 sec delay - timeout = window.setTimeout(() => { - const graphType = detectType(graph); - console.debug(`ga: send event: render ${graphType}`); - void logEvent('render', { graphType }); + const key = data ? JSON.stringify({ name, data }) : name; + clearInterval(timeouts[key]); + timeouts[key] = window.setTimeout(() => { + void analytics.track(name, data); }, 5000); } }; - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export const logEvent = async (name: string, data?: any): Promise => { - await analytics?.track(name, data); -}; diff --git a/src/lib/util/theme.ts b/src/lib/util/theme.ts index 24ae78fd..4221ee06 100644 --- a/src/lib/util/theme.ts +++ b/src/lib/util/theme.ts @@ -34,5 +34,5 @@ export const setTheme = (theme: string): void => { const isDark = darkThemes.includes(theme); console.log('Setting theme', theme); themeStore.set({ theme, isDark }); - void logEvent('themeChange', { theme, isDark }); + logEvent('themeChange', { theme, isDark }); };