Merge pull request #1 from sidharthv96/sidebar

sidebar
This commit is contained in:
Sidharth Vinod
2021-05-17 10:53:03 +05:30
committed by GitHub
32 changed files with 599 additions and 439 deletions
+3 -2
View File
@@ -2,5 +2,6 @@
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100
}
"printWidth": 100,
"svelteBracketNewLine": false
}
+4 -2
View File
@@ -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
View File
@@ -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 &amp; 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="
+1 -1
View File
@@ -27,6 +27,6 @@ interface State {
interface HistoryEntry {
state: State;
time: number;
name: string;
name?: string;
auto: boolean;
}
-8
View File
@@ -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>
-98
View File
@@ -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>
-119
View File
@@ -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
View File
@@ -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

-88
View File
@@ -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>
-28
View File
@@ -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
View File
@@ -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
View File
@@ -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();
};
+160
View File
@@ -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 = `[![](${iUrl})](${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>
+25
View File
@@ -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>
+63
View File
@@ -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();
};
+104
View File
@@ -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>
+88
View File
@@ -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 '&lt;' and '&gt;'
code = state.code; //.replace(/</g, '&lt;').replace(/>/g, '&gt;');
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>
+3 -7
View File
@@ -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
View File
@@ -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>
+1
View File
@@ -8,6 +8,7 @@
if (hash.length > 2) {
newURL = `/${hash[1]}#${hash[2]}`;
}
debugger;
await goto(newURL, {
replaceState: true
});
+8
View File
@@ -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
View File
@@ -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
+10
View File
@@ -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"