diff --git a/src/lib/components/view.svelte b/src/lib/components/view.svelte
index 8cf21549..b9986c6a 100644
--- a/src/lib/components/view.svelte
+++ b/src/lib/components/view.svelte
@@ -1,11 +1,11 @@
-{#if error && $stateStore.error instanceof Error}
-
-{/if}
+{#await $stateStore then state}
+ {#if error && state.error instanceof Error}
+
diff --git a/src/lib/util/env.ts b/src/lib/util/env.ts
index 128761f2..f5738f65 100644
--- a/src/lib/util/env.ts
+++ b/src/lib/util/env.ts
@@ -1,4 +1,9 @@
-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 env = {
+ rendererUrl: import.meta.env.MERMAID_RENDERER_URL ?? 'https://mermaid.ink',
+ krokiRendererUrl: import.meta.env.MERMAID_KROKI_RENDERER_URL ?? 'https://kroki.io',
+ mermaidCDNUrl: import.meta.env.MERMAID_CDN_URL ?? 'https://unpkg.com/@mermaid-js',
+ mermaidBaseURL: import.meta.env.MERMAID_BASE_URL ?? 'http://localhost:9000',
+ useLocalMermaid: import.meta.env.MERMAID_LOCAL ?? false,
+ isDev: import.meta.env.DEV,
+ baseURL: import.meta.env.BASE_URL
+};
diff --git a/src/lib/util/mermaid.ts b/src/lib/util/mermaid.ts
new file mode 100644
index 00000000..1b95b5f2
--- /dev/null
+++ b/src/lib/util/mermaid.ts
@@ -0,0 +1,41 @@
+import mermaid from 'mermaid';
+// We need to export MermaidConfig and all related types from mermaid.
+import type { MermaidConfig } from 'mermaid/dist/config.type';
+import { env } from './env';
+const { mermaidBaseURL, mermaidCDNUrl, useLocalMermaid } = env;
+
+const getDiagramURL = (name: string, version: string): string => {
+ if (useLocalMermaid) {
+ return `${mermaidBaseURL}/${name}-detector.esm.mjs`;
+ }
+ return `${mermaidCDNUrl}/${name}@${version}/dist/${name}-detector.esm.mjs`;
+};
+
+const initialize = mermaid.initializeAsync({
+ // logLevel: 0,
+ lazyLoadedDiagrams: [getDiagramURL('mermaid-mindmap', '9.2.0-rc4')],
+ loadExternalDiagramsAtStartup: true
+});
+
+export const init = async () => {
+ await initialize;
+};
+
+export const render = async (
+ config: MermaidConfig,
+ code: string,
+ id: string,
+ callback: Parameters
[2]
+): Promise => {
+ // Should be able to call this multiple times without any issues.
+ mermaid.initialize(config);
+ // console.log('Rendering', code);
+ // mermaid.mermaidAPI.render(id, code, callback);
+ await init();
+ await mermaid.mermaidAPI.renderAsync(id, code, callback);
+};
+
+export const parse = async (code: string): Promise => {
+ await init();
+ return mermaid.parseAsync(code);
+};
diff --git a/src/lib/util/state.ts b/src/lib/util/state.ts
index 9237c44f..d678d54f 100644
--- a/src/lib/util/state.ts
+++ b/src/lib/util/state.ts
@@ -1,13 +1,13 @@
-import { writable, get, derived } from 'svelte/store';
+import { writable, get, type Readable } from 'svelte/store';
import { persist, localStorage } from './persist';
import { saveStatistics, countLines } from './stats';
import { serializeState, deserializeState } from './serde';
-import { cmdKey } from './util';
-import mermaid from 'mermaid';
+import { asyncable, syncable } from 'svelte-asyncable';
+import { cmdKey, errorDebug } from './util';
+import { parse } from './mermaid';
-import type { Readable } from 'svelte/store';
import type { MarkerData, State, ValidatedState } from '$lib/types';
-
+let count = 0;
export const defaultState: State = {
code: `graph TD
A[Christmas] -->|Get money| B(Go shopping)
@@ -41,42 +41,96 @@ const urlParseFailedState = `graph TD
// inputStateStore handles all updates and is shared externally when exporting via URL, History, etc.
export const inputStateStore = persist(writable(defaultState), localStorage(), 'codeStore');
-// All internal reads should be done via stateStore, but it should not be persisted/shared externally.
-export const stateStore: Readable = derived([inputStateStore], ([state]) => {
- const processed: ValidatedState = {
+export let currentState: ValidatedState = (() => {
+ const state = get(inputStateStore);
+ return {
...state,
- serialized: '',
+ serialized: serializeState(state),
errorMarkers: [],
error: undefined,
editorMode: state.editorMode ?? 'code'
};
+})();
- // No changes should be done to fields part of `state`.
- try {
- processed.serialized = serializeState(state);
- mermaid.parse(state.code);
- JSON.parse(state.mermaid);
- } catch (e) {
- processed.error = e;
- console.error(e);
- if (e.hash) {
- try {
- const marker: MarkerData = {
- severity: 8, // Error
- startLineNumber: e.hash.loc.first_line,
- startColumn: e.hash.loc.first_column,
- endLineNumber: e.hash.loc.last_line,
- endColumn: (e.hash.loc.last_column as number) + 1,
- message: e.str
- };
- processed.errorMarkers = [marker];
- } catch (err) {
- console.error('Error without line helper', err);
+// All internal reads should be done via stateStore, but it should not be persisted/shared externally.
+export const stateStore = asyncable(
+ async (state: State) => {
+ const processed: ValidatedState = {
+ ...state,
+ serialized: '',
+ errorMarkers: [],
+ error: undefined,
+ editorMode: state.editorMode ?? 'code'
+ };
+
+ console.log('asyncable', state);
+ // No changes should be done to fields part of `state`.
+ try {
+ processed.serialized = serializeState(state);
+ await parse(state.code);
+ JSON.parse(state.mermaid);
+ } catch (e) {
+ processed.error = e;
+ errorDebug();
+ console.error(e);
+ if (e.hash) {
+ try {
+ const marker: MarkerData = {
+ severity: 8, // Error
+ startLineNumber: e.hash.loc.first_line,
+ startColumn: e.hash.loc.first_column,
+ endLineNumber: e.hash.loc.last_line,
+ endColumn: (e.hash.loc.last_column as number) + 1,
+ message: e.str
+ };
+ processed.errorMarkers = [marker];
+ } catch (err) {
+ console.error('Error without line helper', err);
+ }
}
}
- }
- return processed;
-});
+ currentState = processed;
+ return processed;
+ },
+ undefined,
+ [inputStateStore]
+);
+
+// export const stateStore: Readable = derived([inputStateStore], ([state]) => {
+// const processed: ValidatedState = {
+// ...state,
+// serialized: '',
+// errorMarkers: [],
+// error: undefined,
+// editorMode: state.editorMode ?? 'code'
+// };
+
+// // No changes should be done to fields part of `state`.
+// try {
+// processed.serialized = serializeState(state);
+// mermaid.parse(state.code);
+// JSON.parse(state.mermaid);
+// } catch (e) {
+// processed.error = e;
+// console.error(e);
+// if (e.hash) {
+// try {
+// const marker: MarkerData = {
+// severity: 8, // Error
+// startLineNumber: e.hash.loc.first_line,
+// startColumn: e.hash.loc.first_column,
+// endLineNumber: e.hash.loc.last_line,
+// endColumn: (e.hash.loc.last_column as number) + 1,
+// message: e.str
+// };
+// processed.errorMarkers = [marker];
+// } catch (err) {
+// console.error('Error without line helper', err);
+// }
+// }
+// }
+// return processed;
+// });
export const loadState = (data: string): void => {
let state: State;
@@ -103,7 +157,7 @@ export const loadState = (data: string): void => {
state.mermaid = defaultState.mermaid;
}
}
- updateCodeStore({ ...state });
+ updateCodeStore(state);
};
export const updateCodeStore = (newState: Partial): void => {
@@ -114,17 +168,18 @@ export const updateCodeStore = (newState: Partial): void => {
};
let prompted = false;
-export const updateCode = (
+export const updateCode = async (
code: string,
{
updateDiagram = false,
resetPanZoom = false
}: { updateDiagram?: boolean; resetPanZoom?: boolean } = {}
-): void => {
+): Promise => {
+ console.log('updateCode', code);
const lines = countLines(code);
saveStatistics(code);
-
- if (lines > 50 && !prompted && get(stateStore).autoSync) {
+ errorDebug();
+ if (lines > 50 && !prompted && (await get(stateStore)).autoSync) {
const turnOff = confirm(
`Long diagram detected. Turn off Auto Sync? Use ${cmdKey} + Enter or click the sync logo to manually sync.`
);
@@ -146,6 +201,7 @@ export const updateCode = (
};
export const updateConfig = (config: string): void => {
+ console.log('updateConfig', config);
inputStateStore.update((state) => {
return { ...state, mermaid: config };
});
@@ -164,7 +220,8 @@ export const toggleDarkTheme = (dark: boolean): void => {
let urlDebounce: number;
export const initURLSubscription = (): void => {
- stateStore.subscribe(({ serialized }) => {
+ stateStore.subscribe(async (state) => {
+ const { serialized } = await state;
clearTimeout(urlDebounce);
urlDebounce = window.setTimeout(() => {
history.replaceState(undefined, undefined, `#${serialized}`);
diff --git a/src/lib/util/util.ts b/src/lib/util/util.ts
index d3995a2f..7b5456b4 100644
--- a/src/lib/util/util.ts
+++ b/src/lib/util/util.ts
@@ -26,3 +26,13 @@ export const initHandler = async (): Promise => {
export const isMac = navigator.platform.toUpperCase().indexOf('MAC') >= 0;
export const cmdKey = isMac ? 'Cmd' : 'Ctrl';
+
+let count = 0;
+export const errorDebug = (limit = 100) => {
+ count += 1;
+ if (count > limit) {
+ console.log(count, limit);
+ // eslint-disable-next-line no-debugger
+ debugger;
+ }
+};
diff --git a/src/routes/edit/+page.svelte b/src/routes/edit/+page.svelte
index 19293e86..fc219c6e 100644
--- a/src/routes/edit/+page.svelte
+++ b/src/routes/edit/+page.svelte
@@ -54,7 +54,8 @@
};
let docURL = docURLBase;
let activeTabID = 'code';
- stateStore.subscribe(({ code, editorMode }: ValidatedState) => {
+ stateStore.subscribe(async (state) => {
+ const { code, editorMode } = await state;
activeTabID = editorMode;
const codeTypeMatch = /([\S]+)[\s\n]/.exec(code);
if (codeTypeMatch && codeTypeMatch.length > 1) {
@@ -84,28 +85,30 @@
onMount(async () => {
await initHandler();
- const resizer = document.getElementById('resizeHandler');
- const element = document.getElementById('editorPane');
- const resize = (e: { pageX: number }) => {
- const newWidth = e.pageX - element.getBoundingClientRect().left;
- if (newWidth > 50) {
- element.style.width = `${newWidth}px`;
- }
- };
- const stopResize = () => {
- window.removeEventListener('mousemove', resize);
- };
- resizer.addEventListener('mousedown', (e) => {
- e.preventDefault();
- window.addEventListener('mousemove', resize);
- window.addEventListener('mouseup', stopResize);
- });
+ // const resizer = document.getElementById('resizeHandler');
+ // const element = document.getElementById('editorPane');
+ // const resize = (e: { pageX: number }) => {
+ // const newWidth = e.pageX - element.getBoundingClientRect().left;
+ // if (newWidth > 50) {
+ // element.style.width = `${newWidth}px`;
+ // }
+ // };
+
+ // const stopResize = () => {
+ // window.removeEventListener('mousemove', resize);
+ // };
+ // resizer.addEventListener('mousedown', (e) => {
+ // e.preventDefault();
+ // window.addEventListener('mousemove', resize);
+ // window.addEventListener('mouseup', stopResize);
+ // });
});
+
@@ -113,21 +116,21 @@
- {#if !$stateStore.autoSync}
-
- {/if}
+