From 90445dbf4675e01fae3a7c6ede74c8ed4bfa1be3 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 10 Sep 2022 13:53:03 +0530 Subject: [PATCH 1/4] Simplify debounce handling Previously events were debounced at source of generation. Now they are debounced only at effected areas where necessary. --- src/lib/components/actions.svelte | 10 +-- src/lib/components/editor.svelte | 27 ++------ src/lib/components/preset.svelte | 2 +- src/lib/components/view.svelte | 106 ++++++++++++++++-------------- src/lib/util/env.ts | 2 +- src/lib/util/state.ts | 6 +- src/lib/util/stats.ts | 25 ++++--- src/lib/util/theme.ts | 2 +- 8 files changed, 87 insertions(+), 93 deletions(-) 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 }); }; From fc533d8e119fd7e26a122f27c4866d172e2e4d51 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 10 Sep 2022 20:51:21 +0530 Subject: [PATCH 2/4] Apply suggestions from code review Co-authored-by: Alois Klink --- src/lib/util/state.ts | 2 +- src/lib/util/stats.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts index a4940f45..81df4a72 100644 --- a/src/lib/util/state.ts +++ b/src/lib/util/state.ts @@ -165,7 +165,7 @@ export const toggleDarkTheme = (dark: boolean): void => { let urlDebounce: number; export const initURLSubscription = (): void => { stateStore.subscribe(({ serialized }) => { - clearInterval(urlDebounce); + clearTimeout(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 8cafc005..2ee252b9 100644 --- a/src/lib/util/stats.ts +++ b/src/lib/util/stats.ts @@ -53,7 +53,7 @@ const timeouts: Record = {}; export const logEvent = (name: string, data?: unknown): void => { if (analytics) { const key = data ? JSON.stringify({ name, data }) : name; - clearInterval(timeouts[key]); + clearTimeout(timeouts[key]); timeouts[key] = window.setTimeout(() => { void analytics.track(name, data); }, 5000); From b596c58ca7f58976c2b1b539584d53b2903a4b0d Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Sat, 10 Sep 2022 20:53:34 +0530 Subject: [PATCH 3/4] chore: Remove commented code --- .github/workflows/tests.yml | 1 - package.json | 2 +- src/lib/components/view.svelte | 9 --------- src/lib/util/env.ts | 1 - 4 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 49ae58b5..5498b747 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,4 +48,3 @@ jobs: parallel: true env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - MERMAID_DISABLE_DEBOUNCE: 'true' diff --git a/package.json b/package.json index d5674859..4981722d 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "license": "MIT", "scripts": { "dev": "vite dev", - "dev:test": "MERMAID_DISABLE_DEBOUNCE=true yarn dev", + "dev:test": "yarn dev", "build": "vite build", "preview": "vite preview", "lint": "prettier --check --cache --plugin-search-dir=. .;eslint --ignore-path .gitignore .", diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte index b3c2d330..bb6d9f2d 100644 --- a/src/lib/components/view.svelte +++ b/src/lib/components/view.svelte @@ -16,18 +16,12 @@ let manualUpdate = true; let panZoomEnabled = $stateStore.panZoom; let pzoom: SvgPanZoom.Instance; - // 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(panZoomDebounce); - // panZoomDebounce = window.setTimeout(() => { updateCodeStore({ pan, zoom }); logEvent('panZoom'); - // }, 200); }; const handlePanZoom = (state: State) => { @@ -101,10 +95,7 @@ }; onMount(() => { stateStore.subscribe((state) => { - // 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 590c1f00..128761f2 100644 --- a/src/lib/util/env.ts +++ b/src/lib/util/env.ts @@ -2,4 +2,3 @@ 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'; From 330f02f81bea57549670d779dd3bcc314cacee92 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 10 Sep 2022 18:52:03 +0000 Subject: [PATCH 4/4] chore(deps): update all non-major dependencies to v0.23.2 --- package.json | 4 ++-- yarn.lock | 30 +++++++++++++++--------------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 4981722d..cc312237 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@types/uuid": "8.3.4", "@typescript-eslint/eslint-plugin": "5.36.2", "@typescript-eslint/parser": "5.36.2", - "@vitest/ui": "0.23.1", + "@vitest/ui": "0.23.2", "autoprefixer": "10.4.8", "c8": "7.12.0", "chai": "4.3.6", @@ -62,7 +62,7 @@ "tslib": "2.4.0", "typescript": "4.8.3", "vite": "3.1.0", - "vitest": "0.23.1" + "vitest": "0.23.2" }, "dependencies": { "@analytics/google-analytics": "1.0.3", diff --git a/yarn.lock b/yarn.lock index 1f9f5226..4f41ec81 100644 --- a/yarn.lock +++ b/yarn.lock @@ -598,10 +598,10 @@ "@typescript-eslint/types" "5.36.2" eslint-visitor-keys "^3.3.0" -"@vitest/ui@0.23.1": - version "0.23.1" - resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-0.23.1.tgz#de3e95b7cc01137767d64c4a8464f2d586009925" - integrity sha512-W1ygPK4aTyLTPsf6NX3gZFbH0X1ipNzlHhxTRnZ4/HpQXs/qKw5NDY45/U2yC66Fevj5kXHI1tdrNfN00NouFQ== +"@vitest/ui@0.23.2": + version "0.23.2" + resolved "https://registry.yarnpkg.com/@vitest/ui/-/ui-0.23.2.tgz#0d4a3918edaca6d38f20c49467a03a51d4b26eff" + integrity sha512-dO1UxSwEm11OgumUVbEnF7wKwDPoBGjkrvATvvhkFaDw2iiQm8WewF0hmnUVHj6BSNoHUIJ3uXEzfF9T9W+2yg== dependencies: sirv "^2.0.2" @@ -5129,15 +5129,15 @@ tiny-glob@^0.2.9: globalyzer "0.1.0" globrex "^0.1.2" -tinybench@^2.1.3: +tinybench@^2.1.5: version "2.1.5" resolved "https://registry.yarnpkg.com/tinybench/-/tinybench-2.1.5.tgz#6864341415ff0f912ed160cfd90b7f833ece674c" integrity sha512-ak+PZZEuH3mw6CCFOgf5S90YH0MARnZNhxjhjguAmoJimEMAJuNip/rJRd6/wyylHItomVpKTzZk9zrhTrQCoQ== -tinypool@^0.2.4: - version "0.2.4" - resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c" - integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ== +tinypool@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/tinypool/-/tinypool-0.3.0.tgz#c405d8b743509fc28ea4ca358433190be654f819" + integrity sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ== tinyspy@^1.0.2: version "1.0.2" @@ -5355,10 +5355,10 @@ vite@3.1.0: optionalDependencies: fsevents "~2.3.2" -vitest@0.23.1: - version "0.23.1" - resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.23.1.tgz#da3f79a88a6570e4d8c1fe8cf5e2d997be2dc3bd" - integrity sha512-kn9pG+h6VA3yj/xRvwgLKEd33rOlzMqJEg3tl5HSm3WUPlkY1Lr1FK8RN1uIqVKvFxmz6HGU3EQW+xW2kazRkQ== +vitest@0.23.2: + version "0.23.2" + resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.23.2.tgz#f978de0f2ada1b7c5ff8dc479ce75b976957ff19" + integrity sha512-kTBKp3ROPDkYC+x2zWt4znkDtnT08W1FQ6ngRFuqxpBGNuNVS+eWZKfffr8y2JGvEzZ9EzMAOcNaiqMj/FZqMw== dependencies: "@types/chai" "^4.3.3" "@types/chai-subset" "^1.3.3" @@ -5367,8 +5367,8 @@ vitest@0.23.1: debug "^4.3.4" local-pkg "^0.4.2" strip-literal "^0.4.0" - tinybench "^2.1.3" - tinypool "^0.2.4" + tinybench "^2.1.5" + tinypool "^0.3.0" tinyspy "^1.0.2" vite "^2.9.12 || ^3.0.0-0"