+3
-2
@@ -2,5 +2,6 @@
|
||||
"useTabs": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "none",
|
||||
"printWidth": 100
|
||||
}
|
||||
"printWidth": 100,
|
||||
"svelteBracketNewLine": false
|
||||
}
|
||||
+4
-2
@@ -36,8 +36,10 @@
|
||||
"@tailwindcss/forms": "^0.3.2",
|
||||
"cookie": "^0.4.1",
|
||||
"js-base64": "^3.6.0",
|
||||
"mermaid": "^8.9.3",
|
||||
"mermaid": "^8.10.1",
|
||||
"moment": "^2.29.1",
|
||||
"monaco-editor": "^0.23.0",
|
||||
"random-word-slugs": "^0.0.2",
|
||||
"svelte-watch-resize": "^1.0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -2,8 +2,22 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Online FlowChart & Diagrams Editor - Mermaid Live Editor</title>
|
||||
<meta
|
||||
name="og:image"
|
||||
content="https://github.com/mermaid-js/mermaid/raw/develop/img/header.png"
|
||||
/>
|
||||
<link rel="canonical" href="https://mermaid-js.github.io/mermaid-live-editor/" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Simplify documentation and avoid heavy tools. Open source Visio Alternative. Commonly used for explaining your code! Mermaid is a simple markdown-like script language for generating charts from text via javascript."
|
||||
/>
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css"
|
||||
/>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/mermaid@8.10.1/dist/mermaid.min.js"
|
||||
integrity="sha256-aQCGsx3/OLAXwNyOUrAedielIQAjIrMFkcWdXMlIkGc="
|
||||
|
||||
Vendored
+1
-1
@@ -27,6 +27,6 @@ interface State {
|
||||
interface HistoryEntry {
|
||||
state: State;
|
||||
time: number;
|
||||
name: string;
|
||||
name?: string;
|
||||
auto: boolean;
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<div class={`bg-white rounded overflow-hidden shadow m-2 flex-grow flex flex-col ${$$props.class}`}>
|
||||
<div class="bg-blue-400 border-gray-400 p-2 flex-none">
|
||||
<slot name="title" />
|
||||
</div>
|
||||
<div class="flex-grow">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,98 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { spring } from 'svelte/motion';
|
||||
|
||||
let count = 0;
|
||||
|
||||
const displayed_count = spring();
|
||||
$: displayed_count.set(count);
|
||||
$: offset = modulo($displayed_count, 1);
|
||||
|
||||
function modulo(n: number, m: number) {
|
||||
// handle negative numbers
|
||||
return ((n % m) + m) % m;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="counter">
|
||||
<button on:click={() => (count -= 1)} aria-label="Decrease the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5" />
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div class="counter-viewport">
|
||||
<div class="counter-digits" style="transform: translate(0, {100 * offset}%)">
|
||||
<strong style="top: -100%" aria-hidden="true">{Math.floor($displayed_count + 1)}</strong>
|
||||
<strong>{Math.floor($displayed_count)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button on:click={() => (count += 1)} aria-label="Increase the counter by one">
|
||||
<svg aria-hidden="true" viewBox="0 0 1 1">
|
||||
<path d="M0,0.5 L1,0.5 M0.5,0 L0.5,1" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.counter {
|
||||
display: flex;
|
||||
border-top: 1px solid rgba(0, 0, 0, 0.1);
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.counter button {
|
||||
width: 2em;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.counter button:hover {
|
||||
background-color: var(--secondary-color);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 25%;
|
||||
height: 25%;
|
||||
}
|
||||
|
||||
path {
|
||||
vector-effect: non-scaling-stroke;
|
||||
stroke-width: 2px;
|
||||
stroke: var(--text-color);
|
||||
}
|
||||
|
||||
.counter-viewport {
|
||||
width: 8em;
|
||||
height: 4em;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.counter-viewport strong {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-weight: 400;
|
||||
color: var(--accent-color);
|
||||
font-size: 4rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.counter-digits {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -1,119 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import logo from './svelte-logo.svg';
|
||||
</script>
|
||||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="https://kit.svelte.dev">
|
||||
<img src={logo} alt="SvelteKit" />
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<nav>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L1,2 C1.5,3 1.5,3 2,3 L2,0 Z" />
|
||||
</svg>
|
||||
<ul>
|
||||
<li class:active={$page.path === '/'}><a sveltekit:prefetch href="/">Home</a></li>
|
||||
<li class:active={$page.path === '/about'}><a sveltekit:prefetch href="/about">About</a></li>
|
||||
</ul>
|
||||
<svg viewBox="0 0 2 3" aria-hidden="true">
|
||||
<path d="M0,0 L0,3 C0.5,3 0.5,3 1,2 L2,0 Z" />
|
||||
</svg>
|
||||
</nav>
|
||||
|
||||
<div class="corner">
|
||||
<!-- TODO put something else here? github link? -->
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<style>
|
||||
header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.corner {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
}
|
||||
|
||||
.corner a {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.corner img {
|
||||
width: 2em;
|
||||
height: 2em;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
nav {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
--background: rgba(255, 255, 255, 0.7);
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 2em;
|
||||
height: 3em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
path {
|
||||
fill: var(--background);
|
||||
}
|
||||
|
||||
ul {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
list-style: none;
|
||||
background: var(--background);
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
li.active::before {
|
||||
--size: 6px;
|
||||
content: '';
|
||||
width: 0;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: calc(50% - var(--size));
|
||||
border: var(--size) solid transparent;
|
||||
border-top: var(--size) solid var(--accent-color);
|
||||
}
|
||||
|
||||
nav a {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
align-items: center;
|
||||
padding: 0 1em;
|
||||
color: var(--heading-color);
|
||||
font-weight: 700;
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 10%;
|
||||
text-decoration: none;
|
||||
transition: color 0.2s linear;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--accent-color);
|
||||
}
|
||||
</style>
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.1566,22.8189c-10.4-14.8851-30.94-19.2971-45.7914-9.8348L22.2825,29.6078A29.9234,29.9234,0,0,0,8.7639,49.6506a31.5136,31.5136,0,0,0,3.1076,20.2318A30.0061,30.0061,0,0,0,7.3953,81.0653a31.8886,31.8886,0,0,0,5.4473,24.1157c10.4022,14.8865,30.9423,19.2966,45.7914,9.8348L84.7167,98.3921A29.9177,29.9177,0,0,0,98.2353,78.3493,31.5263,31.5263,0,0,0,95.13,58.117a30,30,0,0,0,4.4743-11.1824,31.88,31.88,0,0,0-5.4473-24.1157" style="fill:#ff3e00"/><path d="M45.8171,106.5815A20.7182,20.7182,0,0,1,23.58,98.3389a19.1739,19.1739,0,0,1-3.2766-14.5025,18.1886,18.1886,0,0,1,.6233-2.4357l.4912-1.4978,1.3363.9815a33.6443,33.6443,0,0,0,10.203,5.0978l.9694.2941-.0893.9675a5.8474,5.8474,0,0,0,1.052,3.8781,6.2389,6.2389,0,0,0,6.6952,2.485,5.7449,5.7449,0,0,0,1.6021-.7041L69.27,76.281a5.4306,5.4306,0,0,0,2.4506-3.631,5.7948,5.7948,0,0,0-.9875-4.3712,6.2436,6.2436,0,0,0-6.6978-2.4864,5.7427,5.7427,0,0,0-1.6.7036l-9.9532,6.3449a19.0329,19.0329,0,0,1-5.2965,2.3259,20.7181,20.7181,0,0,1-22.2368-8.2427,19.1725,19.1725,0,0,1-3.2766-14.5024,17.9885,17.9885,0,0,1,8.13-12.0513L55.8833,23.7472a19.0038,19.0038,0,0,1,5.3-2.3287A20.7182,20.7182,0,0,1,83.42,29.6611a19.1739,19.1739,0,0,1,3.2766,14.5025,18.4,18.4,0,0,1-.6233,2.4357l-.4912,1.4978-1.3356-.98a33.6175,33.6175,0,0,0-10.2037-5.1l-.9694-.2942.0893-.9675a5.8588,5.8588,0,0,0-1.052-3.878,6.2389,6.2389,0,0,0-6.6952-2.485,5.7449,5.7449,0,0,0-1.6021.7041L37.73,51.719a5.4218,5.4218,0,0,0-2.4487,3.63,5.7862,5.7862,0,0,0,.9856,4.3717,6.2437,6.2437,0,0,0,6.6978,2.4864,5.7652,5.7652,0,0,0,1.602-.7041l9.9519-6.3425a18.978,18.978,0,0,1,5.2959-2.3278,20.7181,20.7181,0,0,1,22.2368,8.2427,19.1725,19.1725,0,0,1,3.2766,14.5024,17.9977,17.9977,0,0,1-8.13,12.0532L51.1167,104.2528a19.0038,19.0038,0,0,1-5.3,2.3287" style="fill:#fff"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1,88 +0,0 @@
|
||||
<script lang="ts">
|
||||
import Tabs from '$lib/Tabs/index.svelte';
|
||||
import Card from '$lib/Card/index.svelte';
|
||||
import { codeStore, getStateString } from '$lib/Util/state';
|
||||
import {
|
||||
addHistoryEntry,
|
||||
autoHistoryMode,
|
||||
clearHistoryData,
|
||||
getPreviousState,
|
||||
historyStore
|
||||
} from './history';
|
||||
import { notify, prompt } from '$lib/Util/notify';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
const HISTORY_SAVE_INTERVAL: number = 60000;
|
||||
|
||||
const tabSelectHandler = (message: CustomEvent<Tab>) => {
|
||||
autoHistoryMode.set('timeline' === message.detail.id);
|
||||
};
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
id: 'saved',
|
||||
title: 'Saved'
|
||||
},
|
||||
{
|
||||
id: 'timeline',
|
||||
title: 'Timeline'
|
||||
}
|
||||
];
|
||||
|
||||
const saveHistory = (auto = false) => {
|
||||
const currentState: string = getStateString();
|
||||
const previousState: string = getPreviousState(auto);
|
||||
if (previousState !== currentState) {
|
||||
addHistoryEntry({
|
||||
state: $codeStore,
|
||||
time: Date.now(),
|
||||
name: 'test',
|
||||
auto
|
||||
});
|
||||
} else if (!auto) {
|
||||
notify('State already saved.');
|
||||
}
|
||||
};
|
||||
|
||||
const clearHistory = (date?: number): void => {
|
||||
if (!date && !prompt('Clear all saved items?')) {
|
||||
return;
|
||||
}
|
||||
clearHistoryData(date);
|
||||
};
|
||||
|
||||
const restoreHistory = (state: State): void => {
|
||||
codeStore.set({ ...state, updateEditor: true, updateDiagram: true });
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
autoHistoryMode.set(false);
|
||||
setInterval(() => {
|
||||
saveHistory(true);
|
||||
}, HISTORY_SAVE_INTERVAL);
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card class="h-2/5">
|
||||
<div slot="title" class="flex">
|
||||
<div class="flex"><Tabs on:select={tabSelectHandler} {tabs} /></div>
|
||||
<div class="flex-grow" />
|
||||
<div class="flex gap-x-4 text-white">
|
||||
<button class="bg-yellow-500 hover:bg-yellow-700 rounded px-1" on:click={() => saveHistory()}
|
||||
>💾</button
|
||||
>
|
||||
<button class="bg-yellow-500 hover:bg-yellow-700 rounded px-1" on:click={() => clearHistory()}
|
||||
>❌</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="p-2 space-y-2">
|
||||
{#each $historyStore as { state, time, name }}
|
||||
<li class="rounded p-2 shadow block">
|
||||
{new Date(time)}
|
||||
{name}
|
||||
<button on:click={() => restoreHistory(state)}>Restore</button>
|
||||
<button on:click={() => clearHistory(time)}>Delete</button>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</Card>
|
||||
@@ -1,28 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let tabs: Tab[] = [];
|
||||
export let activeTabID: string = tabs[0].id;
|
||||
const dispatch = createEventDispatcher<TabEvents>();
|
||||
const toggleTabs = (tab: Tab) => {
|
||||
activeTabID = tab.id;
|
||||
dispatch('select', tab);
|
||||
};
|
||||
</script>
|
||||
|
||||
<ul class="flex list-none flex-wrap flex-row">
|
||||
{#each tabs as tab}
|
||||
<li class="mr-2 last:mr-0 flex-auto text-center">
|
||||
<!-- svelte-ignore a11y-missing-attribute -->
|
||||
<a
|
||||
class="text font-bold min-w-16 w-auto px-2 py-1 -mb-4 rounded-t block leading-normal {activeTabID ===
|
||||
tab.id
|
||||
? 'text-blue-500 bg-white border-white'
|
||||
: 'text-white bg-blue-500 border-blue-500'}"
|
||||
on:click={() => toggleTabs(tab)}
|
||||
>
|
||||
{tab.title}
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
+26
-7
@@ -1,4 +1,4 @@
|
||||
import { writable, get } from 'svelte/store';
|
||||
import { writable, get, derived } from 'svelte/store';
|
||||
import { toBase64, fromBase64 } from 'js-base64';
|
||||
import { persist, localStorage } from '@macfja/svelte-persistent-store';
|
||||
|
||||
@@ -19,12 +19,14 @@ const defaultState: State = {
|
||||
};
|
||||
|
||||
export const codeStore = persist(writable(defaultState), localStorage(), 'codeStore');
|
||||
export const base64State = derived([codeStore], ([code], set) => {
|
||||
set(toBase64(JSON.stringify(code), true));
|
||||
});
|
||||
|
||||
export const loadState = (data: string): void => {
|
||||
let state: State;
|
||||
try {
|
||||
const stateStr = fromBase64(data);
|
||||
console.log('state from url', stateStr);
|
||||
state = JSON.parse(stateStr);
|
||||
} catch (e) {
|
||||
console.error('Init error', e);
|
||||
@@ -34,12 +36,29 @@ export const loadState = (data: string): void => {
|
||||
};
|
||||
|
||||
export const updateCodeStore = (newState: State): void => {
|
||||
codeStore.set(newState);
|
||||
codeStore.update((state) => {
|
||||
return { ...state, ...newState };
|
||||
});
|
||||
};
|
||||
|
||||
export const updateCode = (code: string, updateEditor: boolean): void => {
|
||||
let prompted = false;
|
||||
export const updateCode = (code: string, updateEditor: boolean, updateDiagram = false): void => {
|
||||
const lines = (code.match(/\n/g) || '').length + 1;
|
||||
|
||||
if (lines > 50 && !prompted && get(codeStore).autoSync) {
|
||||
const turnOff = confirm(
|
||||
'Long diagram deteced. Turn off Auto Sync? Click the sync logo to manually sync.'
|
||||
);
|
||||
prompted = true;
|
||||
if (turnOff) {
|
||||
updateCodeStore({
|
||||
autoSync: false
|
||||
} as State);
|
||||
}
|
||||
}
|
||||
|
||||
codeStore.update((state) => {
|
||||
return { ...state, code, updateEditor };
|
||||
return { ...state, code, updateEditor, updateDiagram };
|
||||
});
|
||||
};
|
||||
|
||||
@@ -50,8 +69,8 @@ export const updateConfig = (config: string, updateEditor: boolean): void => {
|
||||
};
|
||||
|
||||
export const initURLSubscription = (): void => {
|
||||
codeStore.subscribe((state: State) => {
|
||||
window.location.hash = toBase64(JSON.stringify(state), true);
|
||||
base64State.subscribe((state: string) => {
|
||||
window.location.hash = state;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
+13
-1
@@ -1,5 +1,17 @@
|
||||
import { loadState } from './state';
|
||||
import { initURLSubscription, loadState, updateCodeStore } from './state';
|
||||
|
||||
export const loadStateFromURL = (): void => {
|
||||
loadState(window.location.hash.slice(1));
|
||||
};
|
||||
|
||||
export const syncDiagram = (): void => {
|
||||
updateCodeStore({
|
||||
updateDiagram: true
|
||||
} as State);
|
||||
};
|
||||
|
||||
export const initHandler = (): void => {
|
||||
loadStateFromURL();
|
||||
syncDiagram();
|
||||
initURLSubscription();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
<script lang="ts">
|
||||
import Card from '$lib/components/card/card.svelte';
|
||||
import { base64State } from '$lib/util/state';
|
||||
|
||||
import { toBase64 } from 'js-base64';
|
||||
import moment from 'moment';
|
||||
|
||||
type Exporter = (
|
||||
canvas: HTMLCanvasElement,
|
||||
context: CanvasRenderingContext2D,
|
||||
image: HTMLImageElement
|
||||
) => () => void;
|
||||
|
||||
const getBase64SVG = (): string => {
|
||||
const container: HTMLElement = document.getElementById('container');
|
||||
const svg = container.innerHTML.replaceAll('<br>', '<br/>');
|
||||
return toBase64(svg);
|
||||
};
|
||||
|
||||
const exportImage = (event: Event, exporter: Exporter) => {
|
||||
const canvas: HTMLCanvasElement = document.createElement('canvas');
|
||||
const svg: HTMLElement = document.querySelector('#container svg');
|
||||
debugger;
|
||||
const box: DOMRect = svg.getBoundingClientRect();
|
||||
canvas.width = box.width;
|
||||
canvas.height = box.height;
|
||||
if (imagemodeselected === 'width') {
|
||||
const ratio = box.height / box.width;
|
||||
canvas.width = userimagesize;
|
||||
canvas.height = userimagesize * ratio;
|
||||
} else if (imagemodeselected === 'height') {
|
||||
const ratio = box.width / box.height;
|
||||
canvas.width = userimagesize * ratio;
|
||||
canvas.height = userimagesize;
|
||||
}
|
||||
const context = canvas.getContext('2d');
|
||||
context.fillStyle = 'white';
|
||||
context.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const image = new Image();
|
||||
image.onload = exporter(canvas, context, image);
|
||||
|
||||
image.src = `data:image/svg+xml;base64,${getBase64SVG()}`;
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
const simulateDownload = (download: string, href: string): void => {
|
||||
const a = document.createElement('a');
|
||||
a.download = download;
|
||||
a.href = href;
|
||||
a.click();
|
||||
a.remove();
|
||||
};
|
||||
const downloadImage: Exporter = (canvas, context, image) => {
|
||||
return () => {
|
||||
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
simulateDownload(
|
||||
`mermaid-diagram-${moment().format('YYYYMMDDHHmmss')}.png`,
|
||||
canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream')
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
const isClipboardAvailable = () => {
|
||||
return window?.hasOwnProperty('ClipboardItem');
|
||||
};
|
||||
|
||||
const clipboardCopy: Exporter = (canvas, context, image) => {
|
||||
return () => {
|
||||
context.drawImage(image, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
try {
|
||||
// @ts-ignore
|
||||
navigator.clipboard.write([
|
||||
// @ts-ignore
|
||||
new ClipboardItem({
|
||||
[blob.type]: blob
|
||||
})
|
||||
]);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const onCopyClipboard = (event) => {
|
||||
exportImage(event, clipboardCopy);
|
||||
};
|
||||
|
||||
const onDownloadPNG = (event) => {
|
||||
exportImage(event, downloadImage);
|
||||
};
|
||||
|
||||
const onDownloadSVG = (event) => {
|
||||
simulateDownload(
|
||||
`mermaid-diagram-${moment().format('YYYYMMDDHHmmss')}.svg`,
|
||||
`data:image/svg+xml;base64,${getBase64SVG()}`
|
||||
);
|
||||
};
|
||||
|
||||
const onCopyMarkdown = (event) => {
|
||||
event.target.select();
|
||||
document.execCommand('Copy');
|
||||
};
|
||||
|
||||
let iUrl;
|
||||
let svgUrl;
|
||||
let mdCode;
|
||||
let imagemodeselected = 'auto';
|
||||
let userimagesize = 1080;
|
||||
|
||||
const unsubscribe = base64State.subscribe((b64Code) => {
|
||||
iUrl = `https://mermaid.ink/img/${b64Code}`;
|
||||
svgUrl = `https://mermaid.ink/svg/${b64Code}`;
|
||||
mdCode = `[](${window.location.protocol}//${window.location.host}${window.location.pathname}/edit#${b64Code})`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<Card title="Actions" isOpen={false}>
|
||||
<div class="flex flex-wrap gap-2 m-2">
|
||||
{#if isClipboardAvailable()}
|
||||
<button class="btn w-full" on:click={onCopyClipboard}> 📋 Copy Image </button>
|
||||
{/if}
|
||||
<button class="btn flex-auto" on:click={onDownloadPNG}> Download PNG </button>
|
||||
<button class="btn flex-auto" on:click={onDownloadSVG}> Download SVG </button>
|
||||
<button class="btn flex-auto">
|
||||
<a class="link-style" target="_blank" href={iUrl}>Link to Image</a>
|
||||
</button>
|
||||
<button class="btn flex-auto">
|
||||
<a class="link-style" target="_blank" href={svgUrl}>Link to SVG</a>
|
||||
</button>
|
||||
|
||||
<div class="flex gap-2 items-center">
|
||||
PNG size
|
||||
<input type="radio" value="auto" id="autosize" bind:group={imagemodeselected} />
|
||||
<label for="autosize">Auto</label>
|
||||
<input type="radio" value="width" id="width-active" bind:group={imagemodeselected} />
|
||||
<label for="width">Width</label>
|
||||
<input type="radio" value="height" id="height-active" bind:group={imagemodeselected} />
|
||||
<label for="height">Height</label>
|
||||
{#if imagemodeselected !== 'auto'}
|
||||
<input id="height" type="number" min="3" max="10000" bind:value={userimagesize} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="w-full flex gap-2 items-center">
|
||||
<label for="markdown">Copy Markdown</label>
|
||||
<input class="flex-1" id="markdown" type="text" value={mdCode} on:click={onCopyMarkdown} />
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<style>
|
||||
.btn {
|
||||
@apply rounded p-2 bg-indigo-400 shadow flex-auto text-white hover:bg-indigo-500;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,25 @@
|
||||
<script lang="ts">
|
||||
import { slide } from 'svelte/transition';
|
||||
import Tabs from './tabs.svelte';
|
||||
export let isCloseable: boolean = true;
|
||||
export let isOpen: boolean = true;
|
||||
export let tabs: Tab[] = [];
|
||||
export let title: string;
|
||||
$: isOpen = isCloseable ? isOpen : true;
|
||||
</script>
|
||||
|
||||
<div class={`bg-white rounded overflow-hidden shadow m-2 flex-grow flex flex-col `}>
|
||||
<div class="bg-indigo-400 border-gray-400 p-2 flex-none" on:click={() => (isOpen = !isOpen)}>
|
||||
<div class="flex justify-between">
|
||||
<Tabs on:select {tabs} bind:isOpen {title} {isCloseable} />
|
||||
<div class="flex gap-x-4 items-center text-white">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if isOpen}
|
||||
<div class="flex-grow overflow-auto" transition:slide>
|
||||
<slot />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
@@ -0,0 +1,63 @@
|
||||
<script lang="ts">
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
export let isCloseable: boolean = true;
|
||||
export let tabs: Tab[] = [];
|
||||
export let title: string;
|
||||
export let isOpen = false;
|
||||
|
||||
let activeTabID: string = tabs[0]?.id;
|
||||
|
||||
const dispatch = createEventDispatcher<TabEvents>();
|
||||
const toggleTabs = (tab: Tab) => {
|
||||
activeTabID = tab.id;
|
||||
dispatch('select', tab);
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="flex cursor-default">
|
||||
<span class="text-white mr-2 font-semibold" on:click|stopPropagation={() => (isOpen = !isOpen)}>
|
||||
{#if isCloseable}
|
||||
<svg
|
||||
class="inline"
|
||||
class:isOpen
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="12"
|
||||
height="7"
|
||||
viewBox="0 0 12 7"
|
||||
><defs
|
||||
><style>
|
||||
.cls-1 {
|
||||
fill: #fff;
|
||||
fill-rule: evenodd;
|
||||
}
|
||||
</style></defs
|
||||
><path
|
||||
class="cls-1"
|
||||
d="M10.38.45,6,4.83,1.56.39h0A.84.84,0,0,0,.38,1.57h0l5,5h0a.83.83,0,0,0,.6.26H6a.83.83,0,0,0,.6-.26h0l5-5h0A.84.84,0,1,0,10.38.45Z" /></svg>
|
||||
{/if}
|
||||
{title}</span>
|
||||
{#if isOpen && tabs}
|
||||
<ul class="flex flex-wrap flex-row" transition:fade>
|
||||
{#each tabs as tab}
|
||||
<li class="mr-2 last:mr-0 w-28 flex-auto text-center">
|
||||
<div
|
||||
class="text cursor-pointer font-semibold min-w-16 w-auto px-2 py-1 -mb-4 rounded-t block leading-normal {activeTabID ===
|
||||
tab.id
|
||||
? 'text-indigo-500 bg-white border-white'
|
||||
: 'text-white bg-indigo-500 border-indigo-500 hover:bg-indigo-600'}"
|
||||
on:click|stopPropagation={() => toggleTabs(tab)}>
|
||||
{tab.title}
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.isOpen {
|
||||
transform: rotate(-90deg);
|
||||
transition-duration: 0.5s;
|
||||
}
|
||||
</style>
|
||||
@@ -28,7 +28,6 @@
|
||||
$: editor?.setValue(text);
|
||||
$: {
|
||||
Monaco?.editor.setModelMarkers(editor.getModel(), 'test', errorMarkers);
|
||||
console.log(errorMarkers);
|
||||
}
|
||||
|
||||
const dispatch = createEventDispatcher<EditorEvents>();
|
||||
@@ -55,28 +54,25 @@
|
||||
Monaco = await import('monaco-editor');
|
||||
initEditor(Monaco);
|
||||
|
||||
// divEl = document.getElementById('editor') as HTMLDivElement;
|
||||
editor = Monaco.editor.create(divEl, editorOptions);
|
||||
editor.onDidChangeModelContent(async () => {
|
||||
dispatch('update', {
|
||||
text: editor.getValue()
|
||||
});
|
||||
});
|
||||
editor.layout({
|
||||
height: divEl.parentElement.offsetHeight,
|
||||
width: divEl.parentElement.offsetWidth
|
||||
});
|
||||
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
editor.layout({
|
||||
height: entries[0].contentRect.height,
|
||||
width: entries[0].contentRect.width
|
||||
});
|
||||
});
|
||||
resizeObserver.observe(divEl);
|
||||
|
||||
resizeObserver.observe(divEl.parentElement);
|
||||
return () => {
|
||||
editor.dispose();
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<div bind:this={divEl} />
|
||||
<div bind:this={divEl} class="overflow-hidden" />
|
||||
@@ -69,7 +69,3 @@ export const initEditor = (monaco) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const getResizeHandler = (editor) => {
|
||||
return (node) => editor && editor.layout();
|
||||
};
|
||||
@@ -0,0 +1,104 @@
|
||||
<script lang="ts">
|
||||
import Card from '$lib/components/card/card.svelte';
|
||||
import { codeStore, getStateString } from '$lib/util/state';
|
||||
import {
|
||||
addHistoryEntry,
|
||||
autoHistoryMode,
|
||||
clearHistoryData,
|
||||
getPreviousState,
|
||||
historyStore
|
||||
} from './history';
|
||||
import { notify, prompt } from '$lib/util/notify';
|
||||
import { onMount } from 'svelte';
|
||||
import moment from 'moment';
|
||||
|
||||
const HISTORY_SAVE_INTERVAL: number = 60000;
|
||||
|
||||
const tabSelectHandler = (message: CustomEvent<Tab>) => {
|
||||
autoHistoryMode.set('timeline' === message.detail.id);
|
||||
};
|
||||
const tabs: Tab[] = [
|
||||
{
|
||||
id: 'saved',
|
||||
title: 'Saved'
|
||||
},
|
||||
{
|
||||
id: 'timeline',
|
||||
title: 'Timeline'
|
||||
}
|
||||
];
|
||||
|
||||
const saveHistory = (auto = false) => {
|
||||
const currentState: string = getStateString();
|
||||
const previousState: string = getPreviousState(auto);
|
||||
if (previousState !== currentState) {
|
||||
addHistoryEntry({
|
||||
state: $codeStore,
|
||||
time: Date.now(),
|
||||
auto
|
||||
});
|
||||
} else if (!auto) {
|
||||
notify('State already saved.');
|
||||
}
|
||||
};
|
||||
|
||||
const clearHistory = (date?: number): void => {
|
||||
if (!date && !prompt('Clear all saved items?')) {
|
||||
return;
|
||||
}
|
||||
clearHistoryData(date);
|
||||
};
|
||||
|
||||
const restoreHistory = (state: State): void => {
|
||||
codeStore.set({ ...state, updateEditor: true, updateDiagram: true });
|
||||
};
|
||||
|
||||
const relativeTime = (time: number) => {
|
||||
const t = new Date(time);
|
||||
return `${new Date(t).toLocaleString()} (${moment(t).fromNow()})`;
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
autoHistoryMode.set(false);
|
||||
setInterval(() => {
|
||||
saveHistory(true);
|
||||
}, HISTORY_SAVE_INTERVAL);
|
||||
});
|
||||
|
||||
let isOpen = true;
|
||||
</script>
|
||||
|
||||
<Card on:select={tabSelectHandler} bind:isOpen {tabs} title="History">
|
||||
<div slot="actions">
|
||||
<button class="btn" on:click|stopPropagation={() => saveHistory()}>💾</button>
|
||||
<button class="btn" on:click|stopPropagation={() => clearHistory()}>❌</button>
|
||||
</div>
|
||||
<ul class="p-2 space-y-2 overflow-auto h-56">
|
||||
{#each $historyStore as { state, time, name }}
|
||||
<li class="rounded p-2 shadow flex-col">
|
||||
<div class="flex">
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col">
|
||||
<span>{name}</span>
|
||||
<span class="text-gray-400 text-sm">{relativeTime(time)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2 content-center">
|
||||
<button
|
||||
class="rounded px-2 w-24 bg-green-200 hover:bg-green-300"
|
||||
on:click={() => restoreHistory(state)}>Restore</button>
|
||||
<button
|
||||
class="rounded px-2 w-24 bg-red-200 hover:bg-red-300"
|
||||
on:click={() => clearHistory(time)}>Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</Card>
|
||||
|
||||
<style lang="postcss">
|
||||
.btn {
|
||||
@apply bg-indigo-500 hover:bg-indigo-700 rounded px-1 shadow;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,8 @@
|
||||
import { derived, Readable, Writable, writable, get } from 'svelte/store';
|
||||
import { persist, localStorage } from '@macfja/svelte-persistent-store';
|
||||
import { generateSlug } from 'random-word-slugs';
|
||||
|
||||
const MAX_AUTO_HISTORY_LENGTH = 10;
|
||||
const MAX_AUTO_HISTORY_LENGTH = 30;
|
||||
|
||||
export const autoHistoryMode: Writable<boolean> = persist(
|
||||
writable(true),
|
||||
@@ -25,6 +26,8 @@ export const historyStore: Readable<HistoryEntry[]> = derived(
|
||||
}
|
||||
);
|
||||
export const addHistoryEntry = (entry: HistoryEntry): void => {
|
||||
entry.name = generateSlug(2);
|
||||
|
||||
if (!entry.auto) {
|
||||
manualHistoryStore.update((entries) => [entry, ...entries]);
|
||||
return;
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { version } from 'mermaid/package.json';
|
||||
interface Link {
|
||||
title: string;
|
||||
href: string;
|
||||
@@ -12,10 +13,6 @@
|
||||
title: 'Tutorial',
|
||||
href: 'https://github.com/mermaid-js/mermaid/blob/develop/docs/Tutorials.md'
|
||||
},
|
||||
{
|
||||
title: 'Github',
|
||||
href: 'https://github.com/mermaid-js/mermaid-live-editor'
|
||||
},
|
||||
{
|
||||
title: 'Mermaid',
|
||||
href: 'https://github.com/mermaid-js/mermaid'
|
||||
@@ -23,13 +20,17 @@
|
||||
{
|
||||
title: 'CLI',
|
||||
href: 'https://github.com/mermaid-js/mermaid-cli'
|
||||
},
|
||||
{
|
||||
title: 'Github',
|
||||
href: 'https://github.com/mermaid-js/mermaid-live-editor'
|
||||
}
|
||||
];
|
||||
</script>
|
||||
|
||||
<header class="p-2 bg-blue-400 text-white font-bold flex flex-wrap items-center">
|
||||
<header class="p-2 bg-indigo-400 text-white font-bold flex flex-wrap items-center">
|
||||
<div class="flex-1 flex justify-between items-center">
|
||||
<a href="/"> Mermaid Live Editor </a>
|
||||
<a href="/">Mermaid<span class="text-xs font-thin">v{version}</span> Live Editor</a>
|
||||
</div>
|
||||
|
||||
<label for="menu-toggle" class="pointer-cursor lg:hidden block"
|
||||
@@ -40,8 +41,7 @@
|
||||
height="20"
|
||||
viewBox="0 0 20 20"
|
||||
><title>menu</title><path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" /></svg
|
||||
></label
|
||||
>
|
||||
></label>
|
||||
<input class="hidden" type="checkbox" id="menu-toggle" />
|
||||
|
||||
<div class="hidden lg:flex lg:items-center lg:w-auto w-full" id="menu">
|
||||
@@ -49,7 +49,7 @@
|
||||
<ul class="lg:flex items-center justify-between text-base pt-4 lg:pt-0">
|
||||
{#each links as { title, href }}
|
||||
<li>
|
||||
<a class="nav-btn" {href}>{title}</a>
|
||||
<a class="nav-btn" target="_blank" {href}>{title}</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
@@ -0,0 +1,88 @@
|
||||
<script lang="ts">
|
||||
import { updateCode } from '$lib/util/state';
|
||||
import Card from '$lib/components/card/card.svelte';
|
||||
|
||||
const samples = {
|
||||
'Flow Chart': `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
|
||||
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
|
||||
Animal <|-- Duck
|
||||
Animal <|-- Fish
|
||||
Animal <|-- Zebra
|
||||
Animal : +int age
|
||||
Animal : +String gender
|
||||
Animal: +isMammal()
|
||||
Animal: +mate()
|
||||
class Duck{
|
||||
+String beakColor
|
||||
+swim()
|
||||
+quack()
|
||||
}
|
||||
class Fish{
|
||||
-int sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
}
|
||||
`,
|
||||
'State Diagram': `stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
Still --> Moving
|
||||
Moving --> Still
|
||||
Moving --> Crash
|
||||
Crash --> [*]
|
||||
`,
|
||||
'Gantt Chart': `gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat YYYY-MM-DD
|
||||
section Section
|
||||
A task :a1, 2014-01-01, 30d
|
||||
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
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
`,
|
||||
'ER Diagram': `erDiagram
|
||||
CUSTOMER }|..|{ DELIVERY-ADDRESS : has
|
||||
CUSTOMER ||--o{ ORDER : places
|
||||
CUSTOMER ||--o{ INVOICE : "liable for"
|
||||
DELIVERY-ADDRESS ||--o{ ORDER : receives
|
||||
INVOICE ||--|{ ORDER : covers
|
||||
ORDER ||--|{ ORDER-ITEM : includes
|
||||
PRODUCT-CATEGORY ||--|{ PRODUCT : contains
|
||||
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
|
||||
`
|
||||
};
|
||||
|
||||
const loadSampleDiagram = (diagramType: string): void => {
|
||||
updateCode(samples[diagramType], true, true);
|
||||
};
|
||||
</script>
|
||||
|
||||
<Card title="Presets" isOpen={false}>
|
||||
<div class="flex gap-2 flex-wrap p-2">
|
||||
{#each Object.keys(samples) as sample}
|
||||
<button
|
||||
class="rounded p-2 bg-indigo-400 shadow flex-auto text-white hover:bg-indigo-500"
|
||||
on:click={() => loadSampleDiagram(sample)}>{sample}</button>
|
||||
{/each}
|
||||
</div>
|
||||
</Card>
|
||||
@@ -1,7 +1,7 @@
|
||||
<script lang="ts">
|
||||
import { errorStore } from '$lib/Util/error';
|
||||
import { errorStore } from '$lib/util/error';
|
||||
|
||||
import { codeStore } from '$lib/Util/state';
|
||||
import { codeStore } from '$lib/util/state';
|
||||
import { onMount } from 'svelte';
|
||||
import type { Mermaid } from 'mermaid';
|
||||
|
||||
@@ -22,12 +22,13 @@
|
||||
manualUpdate = true;
|
||||
// Replacing special characters '<' and '>' with encoded '<' and '>'
|
||||
code = state.code; //.replace(/</g, '<').replace(/>/g, '>');
|
||||
|
||||
const scroll = container.parentElement.parentElement.parentElement.scrollTop;
|
||||
container.innerHTML = code;
|
||||
delete container.dataset.processed;
|
||||
mermaid.initialize(Object.assign({}, JSON.parse(state.mermaid)));
|
||||
mermaid.init(container);
|
||||
mermaid.render('graph-div', code, insertSvg);
|
||||
container.parentElement.parentElement.parentElement.scrollTop = scroll;
|
||||
} else if (manualUpdate) {
|
||||
manualUpdate = false;
|
||||
} else {
|
||||
@@ -50,8 +51,8 @@
|
||||
const insertSvg = (svgCode, bindFunctions) => {};
|
||||
</script>
|
||||
|
||||
<div id="view" class="p-4" class:error class:outOfSync>
|
||||
<div bind:this={container} class="flex-grow overflow-auto" />
|
||||
<div id="view" class="p-2" class:error class:outOfSync>
|
||||
<div id="container" bind:this={container} class="flex-1 overflow-auto" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
@@ -2,13 +2,9 @@
|
||||
import '../app.postcss';
|
||||
</script>
|
||||
|
||||
<div class="h-screen">
|
||||
<!-- <Header /> -->
|
||||
|
||||
<main class="h-full">
|
||||
<slot />
|
||||
</main>
|
||||
</div>
|
||||
<main class="h-screen">
|
||||
<slot />
|
||||
</main>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
+51
-48
@@ -1,19 +1,22 @@
|
||||
<script lang="ts">
|
||||
import Editor from '$lib/Editor/index.svelte';
|
||||
import Navbar from '$lib/Navbar/index.svelte';
|
||||
|
||||
import View from '$lib/View/index.svelte';
|
||||
import Card from '$lib/Card/index.svelte';
|
||||
import Tabs from '$lib/Tabs/index.svelte';
|
||||
import History from '$lib/History/index.svelte';
|
||||
import { initURLSubscription, updateCode, updateConfig, codeStore } from '$lib/Util/state';
|
||||
import { loadStateFromURL } from '$lib/Util/util';
|
||||
import { errorStore } from '$lib/Util/error';
|
||||
import Editor from '$lib/components/editor/editor.svelte';
|
||||
import Navbar from '$lib/components/navbar.svelte';
|
||||
import Preset from '$lib/components/preset.svelte';
|
||||
import Actions from '$lib/components/actions.svelte';
|
||||
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, codeStore, base64State } from '$lib/util/state';
|
||||
import { initHandler, syncDiagram } from '$lib/util/util';
|
||||
import { errorStore } from '$lib/util/error';
|
||||
import { onMount } from 'svelte';
|
||||
import type monaco from 'monaco-editor';
|
||||
import type { Mermaid } from 'mermaid';
|
||||
import { version } from 'mermaid/package.json';
|
||||
import { goto } from '$app/navigation';
|
||||
|
||||
const mermaid: Mermaid = (window.mermaid as unknown) as Mermaid;
|
||||
|
||||
let selectedMode = 'code';
|
||||
const languageMap = {
|
||||
code: 'mermaid',
|
||||
@@ -57,10 +60,6 @@
|
||||
updateConfig(config, false);
|
||||
};
|
||||
|
||||
const syncDiagram = () => {
|
||||
$codeStore.updateDiagram = true;
|
||||
};
|
||||
|
||||
const updateHandler = async (message: CustomEvent<EditorUpdateEvent>) => {
|
||||
try {
|
||||
if (selectedMode === 'code') {
|
||||
@@ -90,51 +89,55 @@
|
||||
console.error(e);
|
||||
}
|
||||
};
|
||||
loadStateFromURL();
|
||||
|
||||
onMount(() => {
|
||||
syncDiagram();
|
||||
initURLSubscription();
|
||||
});
|
||||
const viewDiagram = async () => {
|
||||
await goto(`/view#${$base64State}`, { replaceState: true });
|
||||
};
|
||||
onMount(initHandler);
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Edit</title>
|
||||
</svelte:head>
|
||||
<div>
|
||||
|
||||
<div class="h-full flex flex-col overflow-hidden bg-gray-100">
|
||||
<Navbar />
|
||||
<div class="flex">
|
||||
<div class="w-2/5 flex-grow flex flex-col" style="resize:both;">
|
||||
<Card class="h-3/5">
|
||||
<div slot="title" class="flex">
|
||||
<div class="flex"><Tabs on:select={tabSelectHandler} {tabs} /></div>
|
||||
<div class="flex-grow" />
|
||||
<div class="flex gap-x-4 text-white">
|
||||
{#if !$codeStore.autoSync}
|
||||
<button class="bg-blue-500 hover:bg-blue-700 rounded px-1" on:click={syncDiagram}
|
||||
>🔄</button
|
||||
>
|
||||
{/if}
|
||||
<label for="autoSync">
|
||||
<input type="checkbox" name="autoSync" bind:checked={$codeStore.autoSync} />
|
||||
Auto sync
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<div class="w-2/5 flex flex-col">
|
||||
<Card on:select={tabSelectHandler} {tabs} isCloseable={false} title="Mermaid">
|
||||
<div slot="actions">
|
||||
{#if !$codeStore.autoSync}
|
||||
<button
|
||||
class="bg-indigo-500 hover:bg-indigo-700 rounded px-1 mx-2"
|
||||
on:click={syncDiagram}>🔄</button>
|
||||
{/if}
|
||||
<label for="autoSync">
|
||||
<input type="checkbox" name="autoSync" bind:checked={$codeStore.autoSync} />
|
||||
Auto sync
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="h-full flex-grow flex flex-col">
|
||||
<div class="flex-grow">
|
||||
<Editor on:update={updateHandler} {language} {text} {errorMarkers} />
|
||||
</div>
|
||||
<div class="flex-none">Sample</div>
|
||||
</div>
|
||||
<Editor on:update={updateHandler} {language} {text} {errorMarkers} />
|
||||
</Card>
|
||||
<History />
|
||||
|
||||
<div class="flex-1">
|
||||
<Preset />
|
||||
<History />
|
||||
<Actions />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Card class="w-3/5 h-3/5">
|
||||
<div slot="title" class="text-white">Diagram</div>
|
||||
<View />
|
||||
</Card>
|
||||
<div class="flex-1 flex flex-col overflow-hidden">
|
||||
<Card title="Diagram" isCloseable={false}>
|
||||
<button
|
||||
slot="actions"
|
||||
class="rounded shadow px-2 bg-indigo-500 hover:bg-indigo-700"
|
||||
on:click|stopPropagation={() => viewDiagram()}>View</button>
|
||||
|
||||
<div class="flex-1 overflow-auto">
|
||||
<View />
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
if (hash.length > 2) {
|
||||
newURL = `/${hash[1]}#${hash[2]}`;
|
||||
}
|
||||
debugger;
|
||||
await goto(newURL, {
|
||||
replaceState: true
|
||||
});
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import View from '$lib/components/view.svelte';
|
||||
import { initHandler } from '$lib/util/util';
|
||||
import { onMount } from 'svelte';
|
||||
onMount(initHandler);
|
||||
</script>
|
||||
|
||||
<View />
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.1 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 352 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 113 KiB |
+1
-1
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"module": "es2020",
|
||||
"lib": ["es2020"],
|
||||
"lib": ["es2020", "ESNext"],
|
||||
"target": "es2019",
|
||||
/**
|
||||
svelte-preprocess cannot figure out whether you have a value or a type, so tell TypeScript
|
||||
|
||||
@@ -1835,6 +1835,11 @@ moment-mini@^2.22.1:
|
||||
resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
|
||||
integrity sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==
|
||||
|
||||
moment@^2.29.1:
|
||||
version "2.29.1"
|
||||
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
|
||||
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
|
||||
|
||||
monaco-editor@^0.23.0:
|
||||
version "0.23.0"
|
||||
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.23.0.tgz#24844ba5640c7adb3a2a3ff3b520cf2d7170a6f0"
|
||||
@@ -2324,6 +2329,11 @@ quick-lru@^5.1.1:
|
||||
resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932"
|
||||
integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==
|
||||
|
||||
random-word-slugs@^0.0.2:
|
||||
version "0.0.2"
|
||||
resolved "https://registry.yarnpkg.com/random-word-slugs/-/random-word-slugs-0.0.2.tgz#d725a85f638da0f4f9872ec6b6d0635f88e0a62c"
|
||||
integrity sha512-vKwx5X94uBZqTZbCyHiYghyU1B7qbodY8dqTbaarQnT/boqD9KreKV0z7iUZqauQFuETTxdHWsm75EUOjUHC5A==
|
||||
|
||||
readdirp@~3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e"
|
||||
|
||||
Reference in New Issue
Block a user