asyncable
This commit is contained in:
Vendored
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"editor.formatOnSave": true,
|
||||
"cSpell.words": ["pako", "Serde", "serdes"],
|
||||
"cSpell.words": ["asyncable", "pako", "Serde", "serdes"],
|
||||
"vitest.commandLine": "yarn test:unit",
|
||||
"vitest.enable": true,
|
||||
"testing.autoRun.mode": "rerun",
|
||||
|
||||
@@ -10,7 +10,7 @@ describe('Auto sync tests', () => {
|
||||
cy.contains('Auto sync').click();
|
||||
cy.get('#view').should('not.have.class', 'outOfSync');
|
||||
cy.get('#view').should('not.contain.text', 'Diagram out of sync.');
|
||||
getEditor().type(' C --> Test');
|
||||
getEditor({ bottom: true, newline: true }).type(' C --> Test');
|
||||
cy.get('#view').should('have.class', 'outOfSync');
|
||||
cy.get('#view').should('contain.text', 'Diagram out of sync.');
|
||||
cy.getLocalStorage('codeStore').snapshot();
|
||||
@@ -71,7 +71,7 @@ describe('Auto sync tests', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe.only('Pan and Zoom', () => {
|
||||
describe('Pan and Zoom', () => {
|
||||
beforeEach(() => {
|
||||
cy.clearLocalStorage();
|
||||
cy.visit('/');
|
||||
|
||||
@@ -20,12 +20,13 @@ describe('Site Loads', () => {
|
||||
|
||||
it('should load sample diagrams when clicked', () => {
|
||||
cy.contains('Sample Diagrams').click();
|
||||
cy.contains('Pie Chart').click();
|
||||
cy.contains('Pie').click();
|
||||
cy.contains('pie title Pets adopted by volunteers');
|
||||
cy.contains('Class Diagram').click();
|
||||
cy.contains('Class').click();
|
||||
cy.contains('classDiagram');
|
||||
});
|
||||
|
||||
describe.skip('github', () => {
|
||||
it('should load diagram from gist', () => {
|
||||
cy.visit(`/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a`);
|
||||
cy.contains('History').click();
|
||||
@@ -54,6 +55,7 @@ describe('Site Loads', () => {
|
||||
cy.contains('Party');
|
||||
cy.getLocalStorage('codeStore').snapshot();
|
||||
});
|
||||
});
|
||||
|
||||
// Disabled temporarily. Should be enabled after the issue is fixed in Mermaid.
|
||||
// it('should prevent setting the "securityLevel" option via URL', () => {
|
||||
@@ -71,7 +73,7 @@ describe('Site Loads', () => {
|
||||
// cy.get('#view').contains(`src='https://via.placeholder.com/64'`);
|
||||
// });
|
||||
|
||||
it('should allow persisting "securityLevel" using confirm dialogue', () => {
|
||||
it.only('should allow persisting "securityLevel" using confirm dialogue', () => {
|
||||
const b64State = toBase64(
|
||||
`{"code":"graph TD\\nA[\\"<img src='https://dummyimage.com/64' width=64/>\\"]","mermaid":"{\\"securityLevel\\": \\"loose\\", \\"theme\\": \\"forest\\"}","autoSync":true,"updateDiagram":true}`,
|
||||
true
|
||||
|
||||
+3
-2
@@ -5,6 +5,7 @@
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"dev:force": "MERMAID_LOCAL=true yarn dev --force",
|
||||
"dev:test": "yarn dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
@@ -26,7 +27,6 @@
|
||||
"@sveltejs/kit": "1.0.0-next.516",
|
||||
"@testing-library/jest-dom": "5.16.5",
|
||||
"@testing-library/svelte": "3.2.2",
|
||||
"@types/mermaid": "9.1.0",
|
||||
"@types/pako": "2.0.0",
|
||||
"@types/uuid": "8.3.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.40.1",
|
||||
@@ -57,6 +57,7 @@
|
||||
"prettier": "2.7.1",
|
||||
"prettier-plugin-svelte": "2.8.0",
|
||||
"svelte": "3.52.0",
|
||||
"svelte-asyncable": "^2.1.0",
|
||||
"svelte-preprocess": "4.10.7",
|
||||
"tailwindcss": "3.1.8",
|
||||
"tslib": "2.4.0",
|
||||
@@ -69,7 +70,7 @@
|
||||
"analytics-plugin-plausible": "0.0.6",
|
||||
"daisyui": "2.31.0",
|
||||
"js-base64": "3.7.2",
|
||||
"mermaid": "9.1.7",
|
||||
"mermaid": "9.2.0-rc7",
|
||||
"moment": "2.29.4",
|
||||
"monaco-editor": "0.34.1",
|
||||
"monaco-mermaid": "1.0.6",
|
||||
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
interface ImportMetaEnv {
|
||||
readonly MERMAID_RENDERER_URL: string;
|
||||
readonly MERMAID_KROKI_RENDERER_URL: string;
|
||||
readonly MERMAID_CDN_URL: string;
|
||||
readonly MERMAID_BASE_URL: string;
|
||||
readonly MERMAID_LOCAL: boolean;
|
||||
// more env variables...
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { browser } from '$app/environment';
|
||||
import Card from '$lib/components/card/card.svelte';
|
||||
import { krokiRendererUrl, rendererUrl } from '$lib/util/env';
|
||||
import { env } from '$lib/util/env';
|
||||
import { pakoSerde } from '$lib/util/serde';
|
||||
import { stateStore } from '$lib/util/state';
|
||||
import { logEvent } from '$lib/util/stats';
|
||||
import { toBase64 } from 'js-base64';
|
||||
import moment from 'moment';
|
||||
const { krokiRendererUrl, rendererUrl } = env;
|
||||
|
||||
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
|
||||
|
||||
@@ -139,7 +140,8 @@
|
||||
};
|
||||
|
||||
let gistURL = '';
|
||||
stateStore.subscribe(({ loader }) => {
|
||||
stateStore.subscribe(async (state) => {
|
||||
const { loader } = await state;
|
||||
if (loader?.type === 'gist') {
|
||||
// @ts-ignore Gist will have url
|
||||
gistURL = loader.config.url;
|
||||
@@ -165,7 +167,8 @@
|
||||
if (browser && ['mermaid.live', 'netlify'].some((path) => window.location.host.includes(path))) {
|
||||
isNetlify = true;
|
||||
}
|
||||
stateStore.subscribe(({ code, serialized }) => {
|
||||
stateStore.subscribe(async (state) => {
|
||||
const { code, serialized } = await state;
|
||||
iUrl = `${rendererUrl}/img/${serialized}?type=png`;
|
||||
svgUrl = `${rendererUrl}/svg/${serialized}`;
|
||||
krokiUrl = `${krokiRendererUrl}/mermaid/svg/${pakoSerde.serialize(code)}`;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
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 { errorDebug, syncDiagram } from '$lib/util/util';
|
||||
import type monaco from 'monaco-editor';
|
||||
import { onMount } from 'svelte';
|
||||
import initEditor from 'monaco-mermaid';
|
||||
@@ -20,12 +20,15 @@
|
||||
};
|
||||
let text = '';
|
||||
|
||||
stateStore.subscribe(({ errorMarkers, editorMode, code, mermaid }) => {
|
||||
stateStore.subscribe(async (state) => {
|
||||
const { errorMarkers, editorMode, code, mermaid } = await state;
|
||||
console.log('editor store subscription', { code, mermaid });
|
||||
if (!editor) return;
|
||||
|
||||
// Update editor text if it's different
|
||||
const newText = editorMode === 'code' ? code : mermaid;
|
||||
if (newText !== text) {
|
||||
console.log('updating editor text', newText);
|
||||
editor.setValue(newText);
|
||||
text = newText;
|
||||
}
|
||||
@@ -44,24 +47,25 @@
|
||||
editor && Monaco?.editor.setTheme(isDark ? 'mermaid-dark' : 'mermaid');
|
||||
});
|
||||
|
||||
const handleUpdate = (text: string, mode: EditorMode) => {
|
||||
const handleUpdate = async (text: string, mode: EditorMode) => {
|
||||
if (mode === 'code') {
|
||||
updateCode(text);
|
||||
await updateCode(text);
|
||||
} else {
|
||||
updateConfig(text);
|
||||
}
|
||||
};
|
||||
|
||||
const loadMonaco = async () => {
|
||||
console.log('Loading Monaco...');
|
||||
// errorDebug();
|
||||
let i = 0;
|
||||
while (i++ < 500) {
|
||||
try {
|
||||
// @ts-ignore : This is a hack to handle a svelte-kit error when importing monaco.
|
||||
Monaco = window.monaco;
|
||||
if (Monaco !== undefined) {
|
||||
return;
|
||||
} catch {
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 100));
|
||||
}
|
||||
alert('Loading Monaco Editor failed. Please try refreshing the page.');
|
||||
};
|
||||
@@ -69,10 +73,17 @@
|
||||
onMount(async () => {
|
||||
await loadMonaco(); // Fix https://github.com/mermaid-js/mermaid-live-editor/issues/175
|
||||
initEditor(Monaco);
|
||||
errorDebug(100);
|
||||
editor = Monaco.editor.create(divEl, editorOptions);
|
||||
editor.onDidChangeModelContent(() => {
|
||||
text = editor.getValue();
|
||||
handleUpdate(text, $stateStore.editorMode);
|
||||
editor.onDidChangeModelContent(async () => {
|
||||
const newText = editor.getValue();
|
||||
console.log({ text, newText });
|
||||
// errorDebug(500);
|
||||
if (text === newText) {
|
||||
return;
|
||||
}
|
||||
text = newText;
|
||||
await handleUpdate(text, (await $stateStore).editorMode);
|
||||
});
|
||||
editor.addAction({
|
||||
id: 'mermaid-render-diagram',
|
||||
@@ -94,7 +105,9 @@
|
||||
});
|
||||
|
||||
resizeObserver.observe(divEl.parentElement);
|
||||
console.log(`editor mounted`);
|
||||
return () => {
|
||||
console.log(`editor disposed`);
|
||||
editor.dispose();
|
||||
};
|
||||
});
|
||||
|
||||
@@ -4,19 +4,18 @@
|
||||
import { logEvent } from '$lib/util/stats';
|
||||
|
||||
const samples = {
|
||||
'Flow Chart': `graph TD
|
||||
Flow: `graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
B --> C{Let me think}
|
||||
C -->|One| D[Laptop]
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]`,
|
||||
'Sequence Diagram': `sequenceDiagram
|
||||
Sequence: `sequenceDiagram
|
||||
Alice->>+John: Hello John, how are you?
|
||||
Alice->>+John: John, can you hear me?
|
||||
John-->>-Alice: Hi Alice, I can hear you!
|
||||
John-->>-Alice: I feel great!
|
||||
`,
|
||||
'Class Diagram': `classDiagram
|
||||
John-->>-Alice: I feel great!`,
|
||||
Class: `classDiagram
|
||||
Animal <|-- Duck
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
@@ -36,17 +35,15 @@
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
`,
|
||||
'State Diagram': `stateDiagram-v2
|
||||
}`,
|
||||
State: `stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
`,
|
||||
'Gantt Chart': `gantt
|
||||
Crash --> [*]`,
|
||||
Gantt: `gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
@@ -54,14 +51,12 @@
|
||||
Another task :after a1 , 20d
|
||||
section Another
|
||||
Task in sec :2014-01-12 , 12d
|
||||
another task : 24d
|
||||
`,
|
||||
'Pie Chart': `pie title Pets adopted by volunteers
|
||||
another task : 24d`,
|
||||
Pie: `pie title Pets adopted by volunteers
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
`,
|
||||
'ER Diagram': `erDiagram
|
||||
"Rats" : 15`,
|
||||
ER: `erDiagram
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER ||--o{ INVOICE : "liable for"
|
||||
@@ -69,9 +64,8 @@
|
||||
INVOICE ||--|{ ORDER : covers
|
||||
ORDER ||--|{ ORDER-ITEM : includes
|
||||
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
|
||||
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
|
||||
`,
|
||||
'User Journey': ` journey
|
||||
PRODUCT ||--o{ ORDER-ITEM : "ordered in"`,
|
||||
'User Journey': `journey
|
||||
title My working day
|
||||
section Go to work
|
||||
Make tea: 5: Me
|
||||
@@ -79,9 +73,8 @@
|
||||
Do work: 1: Me, Cat
|
||||
section Go home
|
||||
Go downstairs: 5: Me
|
||||
Sit down: 3: Me
|
||||
`,
|
||||
'Git Graph': ` gitGraph
|
||||
Sit down: 3: Me`,
|
||||
Git: `gitGraph
|
||||
commit
|
||||
commit
|
||||
branch develop
|
||||
@@ -91,24 +84,61 @@
|
||||
checkout main
|
||||
merge develop
|
||||
commit
|
||||
commit
|
||||
`
|
||||
commit`,
|
||||
Mindmap: `mindmap
|
||||
root((mindmap))
|
||||
Origins
|
||||
Long history
|
||||
::icon(fa fa-book)
|
||||
Popularisation
|
||||
British popular psychology author Tony Buzan
|
||||
Research
|
||||
On effectivness<br/>and features
|
||||
On Automatic creation
|
||||
Uses
|
||||
Creative techniques
|
||||
Strategic planning
|
||||
Argument mapping
|
||||
Tools
|
||||
Pen and paper
|
||||
Mermaid`
|
||||
};
|
||||
|
||||
const loadSampleDiagram = (diagramType: string): void => {
|
||||
updateCode(samples[diagramType], {
|
||||
const loadSampleDiagram = async (diagramType: string): Promise<void> => {
|
||||
await updateCode(samples[diagramType], {
|
||||
updateDiagram: true,
|
||||
resetPanZoom: true
|
||||
});
|
||||
logEvent('loadSampleDiagram', { diagramType });
|
||||
};
|
||||
|
||||
// Adding in this array will add an icon to the preset menu
|
||||
const newDiagrams: Array<keyof typeof samples> = ['Mindmap'];
|
||||
const diagramOrder: Array<keyof typeof samples> = [
|
||||
'Sequence',
|
||||
'Flow',
|
||||
'Class',
|
||||
'State',
|
||||
'ER',
|
||||
'Gantt',
|
||||
'User Journey',
|
||||
'Git',
|
||||
'Pie',
|
||||
'Mindmap'
|
||||
];
|
||||
</script>
|
||||
|
||||
<Card title="Sample Diagrams" isOpen={false}>
|
||||
<div class="flex gap-2 flex-wrap p-2">
|
||||
{#each Object.keys(samples) as sample}
|
||||
<button class="btn btn-primary normal-case btn-sm" on:click={() => loadSampleDiagram(sample)}
|
||||
>{sample}</button>
|
||||
<div class="flex flex-wrap p-2 gap-2">
|
||||
{#each diagramOrder as sample}
|
||||
<button
|
||||
class="btn btn-sm btn-primary w-28 normal-case flex-grow"
|
||||
on:click={() => loadSampleDiagram(sample)}>
|
||||
{sample}
|
||||
{#if newDiagrams.includes(sample)}
|
||||
<span class="ml-2 fa fa-heart" />
|
||||
{/if}
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script lang="ts">
|
||||
import { inputStateStore, stateStore, updateCodeStore } from '$lib/util/state';
|
||||
import { onMount } from 'svelte';
|
||||
import mermaid from 'mermaid';
|
||||
import panzoom from 'svg-pan-zoom';
|
||||
import type { State, ValidatedState } from '$lib/types';
|
||||
import { logEvent } from '$lib/util/stats';
|
||||
import { cmdKey } from '$lib/util/util';
|
||||
import { render as renderDiagram } from '$lib/util/mermaid';
|
||||
|
||||
let code = '';
|
||||
let config = '';
|
||||
@@ -15,7 +15,7 @@
|
||||
let outOfSync = false;
|
||||
let hide = false;
|
||||
let manualUpdate = true;
|
||||
let panZoomEnabled = $stateStore.panZoom;
|
||||
let panZoomEnabled = false;
|
||||
let pzoom: SvgPanZoom.Instance;
|
||||
|
||||
const handlePanZoomChange = () => {
|
||||
@@ -50,9 +50,15 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleStateChange = (state: ValidatedState) => {
|
||||
// let count = 0;
|
||||
const handleStateChange = async (state: ValidatedState) => {
|
||||
// const c = count;
|
||||
// count += 1;
|
||||
// console.log('handleStateChange', c, state);
|
||||
panZoomEnabled;
|
||||
if (state.error !== undefined) {
|
||||
error = true;
|
||||
// console.log('handleStateChange End error', c);
|
||||
return;
|
||||
}
|
||||
error = false;
|
||||
@@ -64,6 +70,7 @@
|
||||
outOfSync = false;
|
||||
manualUpdate = true;
|
||||
if (code === state.code && config === state.mermaid && panZoomEnabled === state.panZoom) {
|
||||
// console.log('handleStateChange End nochange', c);
|
||||
// Do not render if there is no change in Code/Config/PanZoom
|
||||
return;
|
||||
}
|
||||
@@ -72,16 +79,24 @@
|
||||
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) => {
|
||||
await renderDiagram(
|
||||
Object.assign({}, JSON.parse(state.mermaid)),
|
||||
code,
|
||||
'graph-div',
|
||||
(svgCode, bindFunctions) => {
|
||||
if (svgCode.length > 0) {
|
||||
handlePanZoom(state);
|
||||
container.innerHTML = svgCode;
|
||||
// console.log(container.innerHTML);
|
||||
const graphDiv = document.getElementById('graph-div');
|
||||
graphDiv.setAttribute('height', '100%');
|
||||
graphDiv.style.maxWidth = '100%';
|
||||
if (bindFunctions) {
|
||||
bindFunctions(graphDiv);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
view.parentElement.scrollTop = scroll;
|
||||
error = false;
|
||||
} else if (manualUpdate) {
|
||||
@@ -93,22 +108,44 @@
|
||||
console.error('view fail', e);
|
||||
error = true;
|
||||
}
|
||||
// console.log('handleStateChange End', c);
|
||||
};
|
||||
|
||||
const stateChanges = [];
|
||||
let processing = false;
|
||||
const processStateChanges = async () => {
|
||||
if (processing) {
|
||||
return;
|
||||
}
|
||||
processing = true;
|
||||
while (stateChanges.length > 0) {
|
||||
const state = stateChanges.shift();
|
||||
await handleStateChange(state);
|
||||
// if (stateChanges.length > 0) {
|
||||
// Promise.resolve().then(processStateChanges);
|
||||
// }
|
||||
}
|
||||
processing = false;
|
||||
};
|
||||
onMount(() => {
|
||||
stateStore.subscribe((state) => {
|
||||
handleStateChange(state);
|
||||
stateStore.subscribe(async (state) => {
|
||||
stateChanges.push(await state);
|
||||
await processStateChanges();
|
||||
});
|
||||
window.addEventListener('resize', () => {
|
||||
if ($stateStore.panZoom && pzoom) {
|
||||
window.addEventListener('resize', async () => {
|
||||
if ((await $stateStore).panZoom && pzoom) {
|
||||
pzoom.resize();
|
||||
}
|
||||
});
|
||||
console.log('View mounted');
|
||||
});
|
||||
</script>
|
||||
|
||||
{#if error && $stateStore.error instanceof Error}
|
||||
<div class="p-2 text-red-600" id="errorContainer">{$stateStore.error}</div>
|
||||
{/if}
|
||||
{#await $stateStore then state}
|
||||
{#if error && state.error instanceof Error}
|
||||
<div class="p-2 text-red-600" id="errorContainer">{state.error}</div>
|
||||
{/if}
|
||||
{/await}
|
||||
|
||||
{#if outOfSync}
|
||||
<div class="absolute w-full p-2 z-10 text-yellow-600 bg-base-100 bg-opacity-80 text-center">
|
||||
|
||||
+9
-4
@@ -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
|
||||
};
|
||||
|
||||
@@ -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<typeof mermaid.render>[2]
|
||||
): Promise<void> => {
|
||||
// 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<boolean> => {
|
||||
await init();
|
||||
return mermaid.parseAsync(code);
|
||||
};
|
||||
+71
-14
@@ -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,8 +41,20 @@ 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');
|
||||
|
||||
export let currentState: ValidatedState = (() => {
|
||||
const state = get(inputStateStore);
|
||||
return {
|
||||
...state,
|
||||
serialized: serializeState(state),
|
||||
errorMarkers: [],
|
||||
error: undefined,
|
||||
editorMode: state.editorMode ?? 'code'
|
||||
};
|
||||
})();
|
||||
|
||||
// All internal reads should be done via stateStore, but it should not be persisted/shared externally.
|
||||
export const stateStore: Readable<ValidatedState> = derived([inputStateStore], ([state]) => {
|
||||
export const stateStore = asyncable(
|
||||
async (state: State) => {
|
||||
const processed: ValidatedState = {
|
||||
...state,
|
||||
serialized: '',
|
||||
@@ -51,13 +63,15 @@ export const stateStore: Readable<ValidatedState> = derived([inputStateStore], (
|
||||
editorMode: state.editorMode ?? 'code'
|
||||
};
|
||||
|
||||
console.log('asyncable', state);
|
||||
// No changes should be done to fields part of `state`.
|
||||
try {
|
||||
processed.serialized = serializeState(state);
|
||||
mermaid.parse(state.code);
|
||||
await parse(state.code);
|
||||
JSON.parse(state.mermaid);
|
||||
} catch (e) {
|
||||
processed.error = e;
|
||||
errorDebug();
|
||||
console.error(e);
|
||||
if (e.hash) {
|
||||
try {
|
||||
@@ -75,8 +89,48 @@ export const stateStore: Readable<ValidatedState> = derived([inputStateStore], (
|
||||
}
|
||||
}
|
||||
}
|
||||
currentState = processed;
|
||||
return processed;
|
||||
});
|
||||
},
|
||||
undefined,
|
||||
[inputStateStore]
|
||||
);
|
||||
|
||||
// export const stateStore: Readable<ValidatedState> = 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<State>): void => {
|
||||
@@ -114,17 +168,18 @@ export const updateCodeStore = (newState: Partial<State>): void => {
|
||||
};
|
||||
|
||||
let prompted = false;
|
||||
export const updateCode = (
|
||||
export const updateCode = async (
|
||||
code: string,
|
||||
{
|
||||
updateDiagram = false,
|
||||
resetPanZoom = false
|
||||
}: { updateDiagram?: boolean; resetPanZoom?: boolean } = {}
|
||||
): void => {
|
||||
): Promise<void> => {
|
||||
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}`);
|
||||
|
||||
@@ -26,3 +26,13 @@ export const initHandler = async (): Promise<void> => {
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
// });
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col overflow-hidden">
|
||||
<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} {activeTabID} title="Mermaid">
|
||||
@@ -113,21 +116,21 @@
|
||||
<div class="form-control flex-row items-center">
|
||||
<label class="cursor-pointer label" for="autoSync">
|
||||
<span> Auto sync</span>
|
||||
<input
|
||||
<!-- <input
|
||||
type="checkbox"
|
||||
class="toggle {$stateStore.autoSync ? 'btn-secondary' : 'toggle-primary'} ml-1"
|
||||
class="toggle {state.autoSync ? 'btn-secondary' : 'toggle-primary'} ml-1"
|
||||
id="autoSync"
|
||||
bind:checked={$inputStateStore.autoSync} />
|
||||
bind:checked={$inputStateStore.autoSync} /> -->
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if !$stateStore.autoSync}
|
||||
<!-- {#if !state.autoSync}
|
||||
<button
|
||||
class="btn btn-secondary btn-xs mr-1"
|
||||
title="Sync Diagram ({cmdKey} + Enter)"
|
||||
data-cy="sync"
|
||||
on:click={syncDiagram}><i class="fas fa-sync" /></button>
|
||||
{/if}
|
||||
{/if} -->
|
||||
|
||||
<button class="btn btn-secondary btn-xs" title="View documentation">
|
||||
<a target="_blank" rel="noreferrer" href={docURL} data-cy="docs">
|
||||
@@ -149,21 +152,21 @@
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<Card title="Diagram" isCloseable={false}>
|
||||
<div slot="actions" class="flex flex-row items-center">
|
||||
<label class="cursor-pointer label py-0" for="panZoom">
|
||||
<!-- <label class="cursor-pointer label py-0" for="panZoom">
|
||||
<span>Pan & Zoom</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="toggle {$stateStore.panZoom ? 'btn-secondary' : 'toggle-primary'} ml-1"
|
||||
class="toggle {state.panZoom ? 'btn-secondary' : 'toggle-primary'} ml-1"
|
||||
id="panZoom"
|
||||
bind:checked={$inputStateStore.panZoom} />
|
||||
</label>
|
||||
<a
|
||||
href={`${base}/view#${$stateStore.serialized}`}
|
||||
href={`${base}/view#${state.serialized}`}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
class="btn btn-secondary btn-xs"
|
||||
title="View diagram in new page"
|
||||
><i class="fas fa-external-link-alt mr-1" />Full screen</a>
|
||||
><i class="fas fa-external-link-alt mr-1" />Full screen</a> -->
|
||||
</div>
|
||||
|
||||
<div class="flex-1 overflow-auto">
|
||||
|
||||
@@ -410,11 +410,6 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
|
||||
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
|
||||
|
||||
"@types/mermaid@9.1.0":
|
||||
version "9.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/mermaid/-/mermaid-9.1.0.tgz#e9ba511d8a6793749d6be84f86325f0f58553154"
|
||||
integrity sha512-rc8QqhveKAY7PouzY/p8ljS+eBSNCv7o79L97RSub/Ic2SQ34ph1Ng3s8wFLWVjvaEt6RLOWtSCsgYWd95NY8A==
|
||||
|
||||
"@types/node@*":
|
||||
version "15.0.2"
|
||||
resolved "https://registry.npmjs.org/@types/node/-/node-15.0.2.tgz"
|
||||
@@ -2740,6 +2735,11 @@ extsprintf@^1.2.0:
|
||||
resolved "https://registry.npmjs.org/extsprintf/-/extsprintf-1.4.0.tgz"
|
||||
integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8=
|
||||
|
||||
fast-clone@^1.5.13:
|
||||
version "1.5.13"
|
||||
resolved "https://registry.yarnpkg.com/fast-clone/-/fast-clone-1.5.13.tgz#7fe17542ae1c872e71bf80d177d00c11f51c2ea7"
|
||||
integrity sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==
|
||||
|
||||
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
version "3.1.3"
|
||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||
@@ -3672,20 +3672,24 @@ merge2@^1.3.0, merge2@^1.4.1:
|
||||
resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz"
|
||||
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
|
||||
|
||||
mermaid@9.1.7:
|
||||
version "9.1.7"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.1.7.tgz#e24de9b2d36c8cb25a09d72ffce966941b24bd6e"
|
||||
integrity sha512-MRVHXy5FLjnUQUG7YS3UN9jEN6FXCJbFCXVGJQjVIbiR6Vhw0j/6pLIjqsiah9xoHmQU6DEaKOvB3S1g/1nBPA==
|
||||
mermaid@9.2.0-rc7:
|
||||
version "9.2.0-rc7"
|
||||
resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-9.2.0-rc7.tgz#4f5203f3ff89ecc2bbc79d14c278c47341feb526"
|
||||
integrity sha512-zudYRTbjsWClH8WR/qz8QmbRjRgctTsg1mmOObUe0OiLZ7+DESQyF4i5IpP4fUXNSIsNuszASL1UFQfeVcyCqg==
|
||||
dependencies:
|
||||
"@braintree/sanitize-url" "^6.0.0"
|
||||
d3 "^7.0.0"
|
||||
dagre "^0.8.5"
|
||||
dagre-d3 "^0.6.4"
|
||||
dompurify "2.4.0"
|
||||
fast-clone "^1.5.13"
|
||||
graphlib "^2.1.8"
|
||||
khroma "^2.0.0"
|
||||
moment-mini "2.24.0"
|
||||
stylis "^4.0.10"
|
||||
lodash "^4.17.21"
|
||||
moment-mini "^2.24.0"
|
||||
non-layered-tidy-tree-layout "^2.0.2"
|
||||
stylis "^4.1.2"
|
||||
uuid "^9.0.0"
|
||||
|
||||
micromatch@^4.0.4, micromatch@^4.0.5:
|
||||
version "4.0.5"
|
||||
@@ -3746,10 +3750,10 @@ mkdirp@^0.5.1, mkdirp@~0.5.1:
|
||||
dependencies:
|
||||
minimist "^1.2.5"
|
||||
|
||||
moment-mini@2.24.0:
|
||||
version "2.24.0"
|
||||
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
|
||||
integrity sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==
|
||||
moment-mini@^2.24.0:
|
||||
version "2.29.4"
|
||||
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.29.4.tgz#cbbcdc58ce1b267506f28ea6668dbe060a32758f"
|
||||
integrity sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==
|
||||
|
||||
moment@2.29.4:
|
||||
version "2.29.4"
|
||||
@@ -3819,6 +3823,11 @@ node-releases@^2.0.6:
|
||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
|
||||
integrity sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==
|
||||
|
||||
non-layered-tidy-tree-layout@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
|
||||
integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
|
||||
|
||||
nopt@~4.0.1:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz"
|
||||
@@ -4913,10 +4922,10 @@ stylehacks@^5.1.0:
|
||||
browserslist "^4.16.6"
|
||||
postcss-selector-parser "^6.0.4"
|
||||
|
||||
stylis@^4.0.10:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
|
||||
integrity sha512-m3k+dk7QeJw660eIKRRn3xPF6uuvHs/FFzjX3HQ5ove0qYsiygoAhwn5a3IYKaZPo5LrYD0rfVmtv1gNY1uYwg==
|
||||
stylis@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.2.tgz#870b3c1c2275f51b702bb3da9e94eedad87bba41"
|
||||
integrity sha512-Nn2CCrG2ZaFziDxaZPN43CXqn+j7tcdjPFCkRBkFue8QYXC2HdEwnw5TCBo4yQZ2WxKYeSi0fdoOrtEqgDrXbA==
|
||||
|
||||
supports-color@^2.0.0:
|
||||
version "2.0.0"
|
||||
@@ -4949,6 +4958,11 @@ supports-preserve-symlinks-flag@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
|
||||
integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
|
||||
|
||||
svelte-asyncable@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/svelte-asyncable/-/svelte-asyncable-2.1.0.tgz#cac4d2209135519bc65a4722a0ace89ed568ea2f"
|
||||
integrity sha512-x9ysuou0dzmwZ4g5bVLL2FmSy/+pugbbm80FmjOvg8mINkA2RENZPF3QKhVdrMKXM9T/PI3oub/J9TsX9lSZMg==
|
||||
|
||||
svelte-hmr@^0.14.12:
|
||||
version "0.14.12"
|
||||
resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.14.12.tgz#a127aec02f1896500b10148b2d4d21ddde39973f"
|
||||
@@ -5239,7 +5253,7 @@ util-deprecate@^1.0.2:
|
||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
|
||||
|
||||
uuid@9.0.0:
|
||||
uuid@9.0.0, uuid@^9.0.0:
|
||||
version "9.0.0"
|
||||
resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5"
|
||||
integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==
|
||||
|
||||
Reference in New Issue
Block a user