fix: Use McWrapper, fix PNG export

This commit is contained in:
Sidharth Vinod
2025-03-17 11:18:40 -07:00
parent a72e13bb5c
commit 8da3cdedd9
10 changed files with 129 additions and 126 deletions
+13 -7
View File
@@ -4,10 +4,10 @@
import { Input } from '$/components/ui/input';
import { Separator } from '$/components/ui/separator';
import * as ToggleGroup from '$/components/ui/toggle-group';
import { env } from '$/util/env';
import { browser } from '$app/environment';
import { waitForRender } from '$lib/util/autoSync';
import { env } from '$lib/util/env';
import { stateStore, urlsStore } from '$lib/util/state';
import { inputStateStore, stateStore, urlsStore } from '$lib/util/state';
import { logEvent } from '$lib/util/stats';
import { version as FAVersion } from '@fortawesome/fontawesome-free/package.json';
import dayjs from 'dayjs';
@@ -19,7 +19,6 @@
import CopyInput from './CopyInput.svelte';
const FONT_AWESOME_URL = `https://cdnjs.cloudflare.com/ajax/libs/font-awesome/${FAVersion}/css/all.min.css`;
const { krokiRendererUrl } = env;
type Exporter = (context: CanvasRenderingContext2D, image: HTMLImageElement) => () => void;
@@ -62,6 +61,8 @@ ${svgString}`);
};
const exportImage = async (event: Event, exporter: Exporter) => {
$inputStateStore.panZoom = false;
await new Promise((resolve) => setTimeout(resolve, 1000));
await waitForRender();
if (document.querySelector('.outOfSync')) {
throw new Error('Diagram is out of sync');
@@ -74,8 +75,6 @@ ${svgString}`);
}
const box = svg.getBoundingClientRect();
canvas.width = box.width;
canvas.height = box.height;
if (imageSizeMode === 'width') {
const ratio = box.height / box.width;
@@ -85,6 +84,10 @@ ${svgString}`);
const ratio = box.width / box.height;
canvas.width = imageSize * ratio;
canvas.height = imageSize;
} else {
const multiplier = 2;
canvas.width = box.width * multiplier;
canvas.height = box.height * multiplier;
}
const context = canvas.getContext('2d');
@@ -96,7 +99,10 @@ ${svgString}`);
context.fillRect(0, 0, canvas.width, canvas.height);
const image = new Image();
image.addEventListener('load', exporter(context, image));
image.addEventListener('load', () => {
exporter(context, image)();
$inputStateStore.panZoom = true;
});
image.src = `data:image/svg+xml;base64,${getBase64SVG(svg, canvas.width, canvas.height)}`;
event.stopPropagation();
@@ -226,7 +232,7 @@ ${svgString}`);
{@render dualActionButton('PNG', onDownloadPNG, $urlsStore.png)}
{@render dualActionButton('SVG', onDownloadSVG, $urlsStore.svg)}
{#if krokiRendererUrl}
{#if env.krokiRendererUrl}
<a target="_blank" rel="noreferrer" class="flex-grow" href={$urlsStore.kroki}>
<Button class="action-btn w-full">
<ExternalLinkIcon class="mr-2" /> Kroki
+7 -9
View File
@@ -8,7 +8,7 @@
</script>
<script lang="ts">
import McTooltip from '$/components/MCTooltip.svelte';
import McWrapper from '$/components/McWrapper.svelte';
import { Button } from '$/components/ui/button';
import { env } from '$/util/env';
import type { EditorMode } from '$lib/types';
@@ -147,14 +147,12 @@
{/if}
</div>
</div>
{#if env.isEnabledMermaidChartLinks}
<McTooltip>
<Button variant="accent" size="sm" href={$urlsStore.mermaidChart.save}>
<img class="size-4" src="./mermaidchart-logo.svg" alt="Mermaid Chart" />
AI Repair
</Button>
</McTooltip>
{/if}
<McWrapper>
<Button variant="accent" size="sm" href={$urlsStore.mermaidChart.save}>
<img class="size-4" src="./mermaidchart-logo.svg" alt="Mermaid Chart" />
AI Repair
</Button>
</McWrapper>
</div>
<output class="max-h-32 overflow-auto bg-muted p-2" name="mermaid-error" for="editor">
<pre>{$stateStore.error?.toString()}</pre>
-23
View File
@@ -1,23 +0,0 @@
<script lang="ts">
import * as Tooltip from '$lib/components/ui/tooltip';
import type { Snippet } from 'svelte';
import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded';
let { children }: { children: Snippet } = $props();
</script>
<Tooltip.Provider>
<Tooltip.Root delayDuration={100}>
<Tooltip.Trigger>
{@render children()}
</Tooltip.Trigger>
<Tooltip.Content>
<div class="flex items-center gap-2">
<ExternalLinkIcon />
<span class="flex items-center gap-1">
Opens a new tab in <div class="text-accent">MermaidChart.com</div>
</span>
</div>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
+32 -38
View File
@@ -1,8 +1,7 @@
<script lang="ts">
import McTooltip from '$/components/MCTooltip.svelte';
import McWrapper from '$/components/McWrapper.svelte';
import * as Popover from '$/components/ui/popover';
import { Switch } from '$/components/ui/switch';
import { env } from '$/util/env';
import { urlsStore } from '$/util/state';
import { cn } from '$/utils';
import { mode, setMode } from 'mode-watcher';
@@ -38,44 +37,39 @@
{/snippet}
<Popover.Root>
<Popover.Trigger class="flex shrink-0 items-center gap-0">
<Popover.Trigger class="shrink-0">
<img class="size-6" src="./favicon.svg" alt="Mermaid Live Editor" />
</Popover.Trigger>
<Popover.Content align="start" class="overflow-hidden p-0" sideOffset={16}>
<div class="flex flex-col">
{#each menuItems as item}
{@render menuItem(item)}
{/each}
<div
class="flex items-center justify-between border-b bg-muted px-3 py-2 hover:bg-background">
<span class="flex items-center gap-2">
<div class="grid">
{#key $mode}
<div transition:scale class="col-start-1 row-start-1">
{#if $mode === 'dark'}
<MoonIcon />
{:else}
<SunIcon />
{/if}
</div>
{/key}
</div>
Dark Mode
</span>
<Switch
checked={$mode === 'dark'}
onCheckedChange={(dark) => setMode(dark ? 'dark' : 'light')} />
</div>
{#if env.isEnabledMermaidChartLinks}
<McTooltip>
{@render menuItem({
label: 'Edit in Playground',
icon: PlaygroundIcon,
href: $urlsStore.mermaidChart.playground,
class: 'text-accent bg-background hover:bg-background/50'
})}
</McTooltip>
{/if}
<Popover.Content align="start" class="flex flex-col overflow-hidden p-0" sideOffset={16}>
{#each menuItems as item}
{@render menuItem(item)}
{/each}
<div class="flex items-center justify-between border-b bg-muted px-3 py-2 hover:bg-background">
<span class="flex items-center gap-2">
<div class="grid">
{#key $mode}
<div transition:scale class="col-start-1 row-start-1">
{#if $mode === 'dark'}
<MoonIcon />
{:else}
<SunIcon />
{/if}
</div>
{/key}
</div>
Dark Mode
</span>
<Switch
checked={$mode === 'dark'}
onCheckedChange={(dark) => setMode(dark ? 'dark' : 'light')} />
</div>
<McWrapper>
{@render menuItem({
label: 'Edit in Playground',
icon: PlaygroundIcon,
href: $urlsStore.mermaidChart.playground,
class: 'text-accent bg-background hover:bg-background/50'
})}
</McWrapper>
</Popover.Content>
</Popover.Root>
+26
View File
@@ -0,0 +1,26 @@
<script lang="ts">
import { env } from '$/util/env';
import * as Tooltip from '$lib/components/ui/tooltip';
import type { Snippet } from 'svelte';
import ExternalLinkIcon from '~icons/material-symbols/open-in-new-rounded';
let { children }: { children: Snippet } = $props();
</script>
{#if env.isEnabledMermaidChartLinks}
<Tooltip.Provider>
<Tooltip.Root delayDuration={100}>
<Tooltip.Trigger>
{@render children()}
</Tooltip.Trigger>
<Tooltip.Content>
<div class="flex items-center gap-2">
<ExternalLinkIcon />
<span class="flex items-center gap-1">
Opens a new tab in <div class="text-accent">MermaidChart.com</div>
</span>
</div>
</Tooltip.Content>
</Tooltip.Root>
</Tooltip.Provider>
{/if}
+28 -32
View File
@@ -9,11 +9,10 @@
<script lang="ts">
import MainMenu from '$/components/MainMenu.svelte';
import McTooltip from '$/components/MCTooltip.svelte';
import McWrapper from '$/components/McWrapper.svelte';
import { Button } from '$/components/ui/button';
import { Separator } from '$/components/ui/separator';
import { Switch } from '$/components/ui/switch';
import { env } from '$/util/env';
import { dismissPromotion, getActivePromotion } from '$lib/util/promos/promo';
import { urlsStore } from '$lib/util/state';
import { MCBaseURL } from '$lib/util/util';
@@ -84,7 +83,6 @@
<nav class="z-50 flex p-6">
<div class="flex flex-1 items-center gap-4">
<MainMenu />
<div
id="switcher"
class="flex items-center justify-center gap-4 font-medium"
@@ -95,40 +93,38 @@
{/if}
Live Editor
</a>
{#if env.isEnabledMermaidChartLinks}
<McTooltip>
<div class="hidden items-center justify-center gap-4 md:flex">
<Separator orientation="vertical" />
<Switch
id="editorMode"
class="data-[state=checked]:bg-secondary"
checked={isReferral}
onclick={() => {
logEvent('playgroundToggle', { isReferred: isReferral });
// Wait for the event to be logged
setTimeout(() => {
window.open(
$urlsStore.mermaidChart.playground,
'_self',
// Do not send referrer header, if the user already came from playground
isReferral ? 'noreferrer' : ''
);
}, 100);
}} />
<a href={$urlsStore.mermaidChart.playground} class="whitespace-nowrap">
Playground <span class="hidden text-sm opacity-50 lg:inline"
>- more features, no account required</span>
</a>
</div>
</McTooltip>
{/if}
<McWrapper>
<div class="hidden items-center justify-center gap-4 md:flex">
<Separator orientation="vertical" />
<Switch
id="editorMode"
class="data-[state=checked]:bg-secondary"
checked={isReferral}
onclick={() => {
logEvent('playgroundToggle', { isReferred: isReferral });
// Wait for the event to be logged
setTimeout(() => {
window.open(
$urlsStore.mermaidChart.playground,
'_self',
// Do not send referrer header, if the user already came from playground
isReferral ? 'noreferrer' : ''
);
}, 100);
}} />
<a href={$urlsStore.mermaidChart.playground} class="whitespace-nowrap">
Playground <span class="hidden text-sm opacity-50 lg:inline"
>- more features, no account required</span>
</a>
</div>
</McWrapper>
</div>
</div>
<div
id="menu"
class="hidden h-full w-full flex-nowrap items-center justify-between gap-3 overflow-hidden pt-4 text-base md:flex md:w-auto md:items-center md:pt-0">
class="hidden flex-nowrap items-center justify-between gap-3 overflow-hidden md:flex">
<DropdownNavMenu icon={GithubIcon} links={githubLinks} />
<Separator orientation="vertical" />
{@render children()}
+12 -5
View File
@@ -21,6 +21,7 @@
let rough: boolean;
let view: HTMLDivElement | undefined = $state();
let error = $state(false);
let panZoom = true;
let manualUpdate = true;
// Set up panZoom state observer to update the store when pan/zoom changes
@@ -32,9 +33,7 @@
};
const handlePanZoom = (state: State, graphDiv: SVGSVGElement) => {
void Promise.resolve().then(() => {
panZoomState.updateElement(graphDiv, state);
});
panZoomState.updateElement(graphDiv, state);
};
const handleStateChange = async (state: ValidatedState) => {
@@ -49,7 +48,12 @@
if (container) {
manualUpdate = true;
// Do not render if there is no change in Code/Config/PanZoom
if (code === state.code && config === state.mermaid && rough === state.rough) {
if (
code === state.code &&
config === state.mermaid &&
rough === state.rough &&
panZoom === state.panZoom
) {
return;
}
@@ -60,6 +64,7 @@
code = state.code;
config = state.mermaid;
rough = state.rough;
panZoom = state.panZoom ?? true;
const scroll = view?.parentElement?.scrollTop;
delete container.dataset.processed;
const viewID = uniqueID('graph-');
@@ -99,7 +104,9 @@
bindFunctions(graphDiv);
}
}
handlePanZoom(state, graphDiv);
if (state.panZoom) {
handlePanZoom(state, graphDiv);
}
}
if (view?.parentElement && scroll) {
view.parentElement.scrollTop = scroll;
+2
View File
@@ -28,6 +28,8 @@ export interface State {
updateDiagram: boolean;
rough: boolean;
// All new options must be optional, as users would have old states saved
renderCount?: number;
panZoom?: boolean;
grid?: boolean;
editorMode?: EditorMode;
pan?: { x: number; y: number };
+2 -2
View File
@@ -4,8 +4,8 @@ let shouldSync = true;
let updater: () => void;
let renderPromise: Promise<void> | undefined;
let resolveRenderPromise: (() => void) | undefined;
const renderDelay = 1000;
const slowRenderThreshold = 150;
const renderDelay = 2000;
const slowRenderThreshold = 100;
const debouncedRender = debounce(() => {
shouldSync = true;
+7 -10
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import DiagramDocButton from '$/components/DiagramDocumentationButton.svelte';
import History from '$/components/History/History.svelte';
import McTooltip from '$/components/MCTooltip.svelte';
import McWrapper from '$/components/McWrapper.svelte';
import PanZoomToolbar from '$/components/PanZoomToolbar.svelte';
import Share from '$/components/Share.svelte';
import SyncRoughToolbar from '$/components/SyncRoughToolbar.svelte';
@@ -9,7 +9,6 @@
import * as Resizable from '$/components/ui/resizable';
import { Toggle } from '$/components/ui/toggle';
import VersionSecurityToolbar from '$/components/VersionSecurityToolbar.svelte';
import { env } from '$/util/env';
import { PanZoomState } from '$/util/panZoom';
import Actions from '$lib/components/Actions.svelte';
import Card from '$lib/components/Card/Card.svelte';
@@ -58,14 +57,12 @@
<HistoryIcon />
</Toggle>
<Share />
{#if env.isEnabledMermaidChartLinks}
<McTooltip>
<Button variant="accent" size="sm" href={$urlsStore.mermaidChart.save} target="_blank">
<img class="size-4" src="./mermaidchart-logo.svg" alt="Mermaid Chart" />
Save diagram
</Button>
</McTooltip>
{/if}
<McWrapper>
<Button variant="accent" size="sm" href={$urlsStore.mermaidChart.save} target="_blank">
<img class="size-4" src="./mermaidchart-logo.svg" alt="Mermaid Chart" />
Save diagram
</Button>
</McWrapper>
</Navbar>
<div class="flex flex-1 overflow-hidden">