Optimize editor data flow

This commit is contained in:
Sidharth Vinod
2022-09-02 19:41:51 +05:30
parent 38824e8bab
commit 70b67afb87
9 changed files with 87 additions and 108 deletions
+2 -1
View File
@@ -3,5 +3,6 @@
"cSpell.words": ["pako", "Serde", "serdes"],
"vitest.commandLine": "yarn test:unit",
"vitest.enable": true,
"testing.autoRun.mode": "rerun"
"testing.autoRun.mode": "rerun",
"svelte.enable-ts-plugin": true
}
+2 -1
View File
@@ -5,6 +5,7 @@
export let isCloseable = true;
export let isOpen = true;
export let tabs: Tab[] = [];
export let activeTabID: string = '';
export let title: string;
$: isOpen = isCloseable ? isOpen : true;
$: isTabsShown = isOpen && tabs.length > 0;
@@ -15,7 +16,7 @@
class="bg-primary p-2 {isTabsShown ? 'pb-0' : ''} flex-none cursor-pointer"
on:click={() => (isOpen = !isOpen)}>
<div class="flex justify-between">
<Tabs on:select {tabs} bind:isOpen {title} {isCloseable} />
<Tabs on:select {tabs} bind:isOpen {title} {isCloseable} {activeTabID} />
<div class="flex gap-x-4 items-center {isTabsShown ? '-mt-2' : ''}">
<slot name="actions" />
</div>
+5 -3
View File
@@ -3,12 +3,14 @@
import { createEventDispatcher } from 'svelte';
import { fade } from 'svelte/transition';
export let isCloseable = true;
export let tabs: Tab[] = [];
export let tabs: Tab[];
export let title: string;
export let isOpen = false;
export let activeTabID: string;
$: activeTabID = tabs[0]?.id;
if (!activeTabID && tabs.length > 0) {
activeTabID = tabs[0].id;
}
const dispatch = createEventDispatcher<TabEvents>();
const toggleTabs = (tab: Tab) => {
activeTabID = tab.id;
+58 -38
View File
@@ -1,77 +1,97 @@
<script lang="ts">
import type { EditorEvents } from '$lib/types';
import { stateStore } from '$lib/util/state';
import type { EditorMode } from '$lib/types';
import { stateStore, updateCode, updateConfig } from '$lib/util/state';
import { themeStore } from '$lib/util/theme';
import { syncDiagram } from '$lib/util/util';
import { debounceEnabled, syncDiagram } from '$lib/util/util';
import type monaco from 'monaco-editor';
import { createEventDispatcher, onMount } from 'svelte';
import { onMount } from 'svelte';
import initEditor from 'monaco-mermaid';
import { logEvent } from '$lib/util/stats';
let divEl: HTMLDivElement = null;
let editor: monaco.editor.IStandaloneCodeEditor;
let Monaco;
export let text: string;
export let language: string;
export let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
value: text,
language: language,
let Monaco: typeof monaco;
let editorOptions: monaco.editor.IStandaloneEditorConstructionOptions = {
minimap: {
enabled: false
},
theme: 'mermaid',
overviewRulerLanes: 0
};
let oldText = text;
$: editor && Monaco?.editor.setModelLanguage(editor.getModel(), language);
let text = '';
let mode: EditorMode | undefined = undefined;
const handleTextUpdate = (newText: string) => {
if (newText !== oldText) {
if ($stateStore.updateEditor) {
editor?.setValue(newText);
}
oldText = newText;
stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
if (!editor) return;
// Update editor text if it's different
const newText = editorMode === 'code' ? code : mermaid;
if (newText !== text) {
editor.setValue(newText);
text = newText;
}
};
$: handleTextUpdate(text);
stateStore.subscribe(({ errorMarkers }) => {
editor && Monaco?.editor.setModelMarkers(editor.getModel(), 'test', errorMarkers);
// Update editor mode if it's different
if (mode !== editorMode) {
const language = editorMode === 'code' ? 'mermaid' : 'json';
Monaco?.editor.setModelLanguage(editor.getModel(), language);
mode = editorMode;
}
// Display errors if present
if (errorMarkers.length > 0) {
Monaco?.editor.setModelMarkers(editor.getModel(), 'test', errorMarkers);
}
});
themeStore.subscribe(({ isDark }) => {
editor && Monaco?.editor.setTheme(isDark ? 'mermaid-dark' : 'mermaid');
});
const dispatch = createEventDispatcher<EditorEvents>();
const handleUpdate = (text: string, mode: EditorMode) => {
if (mode === 'code') {
updateCode(text, {
updateEditor: false
});
} else {
updateConfig(text, false);
}
};
// Debounce state updates to avoid performance issues
let debounce: { [key: string]: number } = {};
const updateHandler = (newText: string) => {
text = newText;
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++ < 10) {
while (i++ < 500) {
try {
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
Monaco = monaco;
Monaco = window.monaco;
return;
} catch {
await new Promise((r) => setTimeout(r, 500));
await new Promise((r) => setTimeout(r, 100));
}
}
alert('Loading Monaco Editor failed. Please try refreshing the page.');
};
onMount(async () => {
try {
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
Monaco = monaco;
} catch {
await loadMonaco(); // Fix https://github.com/mermaid-js/mermaid-live-editor/issues/175
}
await loadMonaco(); // Fix https://github.com/mermaid-js/mermaid-live-editor/issues/175
initEditor(Monaco);
editor = Monaco.editor.create(divEl, editorOptions);
editor.onDidChangeModelContent(() => {
oldText = editor.getValue();
dispatch('update', {
text: oldText
});
editor.onDidChangeModelContent((e) => {
updateHandler(editor.getValue());
});
editor.addAction({
id: 'mermaid-render-diagram',
+1 -1
View File
@@ -94,7 +94,7 @@
outOfSync = true;
}
} catch (e) {
console.log('view fail', e);
console.error('view fail', e);
error = true;
}
});
+3 -7
View File
@@ -16,13 +16,6 @@ export interface MarkerData {
endColumn: number;
}
export interface EditorUpdateEvent {
text: string;
}
export interface EditorEvents {
update: EditorUpdateEvent;
}
export interface TabEvents {
select: Tab;
}
@@ -39,6 +32,7 @@ export interface State {
updateEditor: boolean;
updateDiagram: boolean;
autoSync: boolean;
editorMode?: EditorMode;
panZoom?: boolean;
pan?: { x: number; y: number };
zoom?: number;
@@ -86,5 +80,7 @@ export interface DocConfig {
};
}
export type EditorMode = 'code' | 'config';
export type Loader = (url: string) => Promise<State>;
export type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
+2
View File
@@ -2,6 +2,7 @@ import { initURLSubscription, loadState, updateCodeStore } from './state';
import { analytics, initAnalytics } from './stats';
import { loadDataFromUrl } from './fileLoaders/loader';
import { initLoading } from './loading';
import { applyMigrations } from './migrations';
export const loadStateFromURL = (): void => {
loadState(window.location.hash.slice(1));
@@ -14,6 +15,7 @@ export const syncDiagram = (): void => {
};
export const initHandler = async (): Promise<void> => {
applyMigrations();
loadStateFromURL();
await initLoading('Loading Gist...', loadDataFromUrl().catch(console.error));
syncDiagram();
-2
View File
@@ -6,12 +6,10 @@
import { setTheme, themeStore } from '$lib/util/theme';
import { toggleDarkTheme } from '$lib/util/state';
import { initHandler } from '$lib/util/util';
import { applyMigrations } from '$lib/util/migrations';
// This can be removed once https://github.com/sveltejs/kit/issues/1612 is fixed.
// Then move it into src and vite will bundle it automatically.
onMount(() => {
applyMigrations();
window.addEventListener('hashchange', async (ev) => {
await initHandler();
});
+14 -55
View File
@@ -6,20 +6,12 @@
import View from '$lib/components/view.svelte';
import Card from '$lib/components/card/card.svelte';
import History from '$lib/components/history/history.svelte';
import { updateCode, updateConfig, inputStateStore, stateStore } from '$lib/util/state';
import { cmdKey, debounceEnabled, initHandler, syncDiagram } from '$lib/util/util';
import { inputStateStore, stateStore, updateCodeStore } from '$lib/util/state';
import { cmdKey, initHandler, syncDiagram } from '$lib/util/util';
import { onMount } from 'svelte';
import type { EditorUpdateEvent, State, Tab, DocConfig } from '$lib/types';
import type { Tab, DocConfig, EditorMode, ValidatedState } from '$lib/types';
import { base } from '$app/paths';
type Modes = 'code' | 'config';
type Languages = 'mermaid' | 'json';
let selectedMode: Modes = 'code';
const languageMap: { [key in Modes]: Languages } = {
code: 'mermaid',
config: 'json'
};
const docURLBase = 'https://mermaid-js.github.io/mermaid';
const docMap: DocConfig = {
graph: {
@@ -60,34 +52,23 @@
config: '/#/gitgraph?id=gitgraph-specific-configuration-options'
}
};
let text = '';
let docURL = docURLBase;
let language: Languages = 'mermaid';
const handleModeUpdate = (mode: Modes) => {
if (mode === 'code') {
text = $stateStore.code;
} else {
text = $stateStore.mermaid;
}
};
$: language = languageMap[selectedMode];
$: handleModeUpdate(selectedMode);
stateStore.subscribe((state: State) => {
if (state.updateEditor) {
text = selectedMode === 'code' ? state.code : state.mermaid;
}
const codeTypeMatch = /([\S]+)[\s\n]/.exec(state.code);
let activeTabID = 'code';
stateStore.subscribe(({ code, editorMode }: ValidatedState) => {
activeTabID = editorMode ?? 'code';
const codeTypeMatch = /([\S]+)[\s\n]/.exec(code);
if (codeTypeMatch && codeTypeMatch.length > 1) {
const docKey = codeTypeMatch[1];
const docConfig = docMap[docKey] ?? { code: '' };
docURL = docURLBase + (docConfig[selectedMode] ?? docConfig.code ?? '');
docURL = docURLBase + (docConfig[editorMode] ?? docConfig.code ?? '');
}
});
const tabSelectHandler = (message: CustomEvent<Tab>) => {
selectedMode = message.detail.id === 'code' ? 'code' : 'config';
$inputStateStore.updateEditor = true;
const editorMode: EditorMode = message.detail.id === 'code' ? 'code' : 'config';
updateCodeStore({ updateEditor: true, editorMode });
};
const tabs: Tab[] = [
{
id: 'code',
@@ -101,28 +82,6 @@
}
];
const handleUpdate = (text: string) => {
if (selectedMode === 'code') {
updateCode(text, {
updateEditor: false
});
} else {
updateConfig(text, false);
}
};
let debounce: { [key: string]: number } = {};
const updateHandler = ({ detail: { text } }: CustomEvent<EditorUpdateEvent>) => {
if (debounceEnabled) {
clearTimeout(debounce[selectedMode]);
debounce[selectedMode] = window.setTimeout(() => {
handleUpdate(text);
}, 300);
} else {
handleUpdate(text);
}
};
onMount(async () => {
await initHandler();
const resizer = document.getElementById('resizeHandler');
@@ -149,7 +108,7 @@
<Navbar />
<div class="flex-1 flex overflow-hidden">
<div class="hidden md:flex flex-col" id="editorPane" style="width: 40%">
<Card on:select={tabSelectHandler} {tabs} isCloseable={false} title="Mermaid">
<Card on:select={tabSelectHandler} {tabs} isCloseable={false} {activeTabID} title="Mermaid">
<div slot="actions" class="flex flex-row items-center">
<div class="form-control flex-row items-center">
<label class="cursor-pointer label" for="autoSync">
@@ -175,7 +134,7 @@
</button>
</div>
<Editor on:update={updateHandler} {language} {text} />
<Editor />
</Card>
<div class="-mt-2">