Merge pull request #1987 from mermaid-js/sidv/examplesSelection
feat: add example picker dropdown to sample diagrams
This commit is contained in:
@@ -1,13 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Card from '$/components/Card/Card.svelte';
|
import Card from '$/components/Card/Card.svelte';
|
||||||
import { Button } from '$/components/ui/button';
|
import { Button, buttonVariants } from '$/components/ui/button';
|
||||||
import { getSampleDiagrams } from '$/util/mermaid';
|
import * as Popover from '$/components/ui/popover';
|
||||||
|
import { getSampleDiagrams, type SampleExample } from '$/util/mermaid';
|
||||||
import { updateCode } from '$lib/util/state.svelte';
|
import { updateCode } from '$lib/util/state.svelte';
|
||||||
import { logEvent } from '$lib/util/stats';
|
import { logEvent } from '$lib/util/stats';
|
||||||
|
import { cn } from '$lib/utils';
|
||||||
import ShapesIcon from '~icons/material-symbols/account-tree-outline-rounded';
|
import ShapesIcon from '~icons/material-symbols/account-tree-outline-rounded';
|
||||||
|
import ChevronDownIcon from '~icons/material-symbols/keyboard-arrow-down-rounded';
|
||||||
|
|
||||||
const extras = {
|
const extras: Record<string, SampleExample[]> = {
|
||||||
ZenUML: `zenuml
|
ZenUML: [
|
||||||
|
{
|
||||||
|
title: 'Order Service',
|
||||||
|
isDefault: true,
|
||||||
|
code: `zenuml
|
||||||
title Order Service
|
title Order Service
|
||||||
@Actor Client #FFEBE6
|
@Actor Client #FFEBE6
|
||||||
@Boundary OrderController #0747A6
|
@Boundary OrderController #0747A6
|
||||||
@@ -31,15 +38,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
const samples = { ...getSampleDiagrams(), ...extras } as const;
|
const samples = { ...getSampleDiagrams(), ...extras };
|
||||||
const loadSampleDiagram = (diagramType: string): void => {
|
|
||||||
updateCode(samples[diagramType as keyof typeof samples], {
|
const loadSampleDiagram = (diagramType: string, example: SampleExample): void => {
|
||||||
|
updateCode(example.code, {
|
||||||
resetPanZoom: true,
|
resetPanZoom: true,
|
||||||
updateDiagram: true
|
updateDiagram: true
|
||||||
});
|
});
|
||||||
logEvent('loadSampleDiagram', { diagramType });
|
logEvent('loadSampleDiagram', { diagramType, exampleTitle: example.title });
|
||||||
};
|
};
|
||||||
|
|
||||||
const mainDiagrams = [
|
const mainDiagrams = [
|
||||||
@@ -62,12 +72,39 @@
|
|||||||
<Card title="Sample Diagrams" isOpen isStackable icon={{ component: ShapesIcon }}>
|
<Card title="Sample Diagrams" isOpen isStackable icon={{ component: ShapesIcon }}>
|
||||||
<div class="flex h-fit max-h-52 flex-wrap gap-2 overflow-y-auto p-2">
|
<div class="flex h-fit max-h-52 flex-wrap gap-2 overflow-y-auto p-2">
|
||||||
{#each diagramOrder as sample (sample)}
|
{#each diagramOrder as sample (sample)}
|
||||||
|
{@const examples = samples[sample]}
|
||||||
|
<div class="flex min-w-20 flex-grow">
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
class="w-fit min-w-20 flex-grow normal-case"
|
class={cn('flex-grow normal-case', examples.length > 1 && 'rounded-r-none')}
|
||||||
onclick={() => loadSampleDiagram(sample)}>
|
onclick={() => loadSampleDiagram(sample, examples[0])}>
|
||||||
{sample}
|
{sample}
|
||||||
</Button>
|
</Button>
|
||||||
|
{#if examples.length > 1}
|
||||||
|
<Popover.Root>
|
||||||
|
<Popover.Trigger
|
||||||
|
aria-label="Choose a {sample} example"
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ size: 'sm' }),
|
||||||
|
'rounded-l-none border-l border-primary-foreground/30 px-0.5 [&_svg]:size-5'
|
||||||
|
)}>
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</Popover.Trigger>
|
||||||
|
<Popover.Content align="start" class="flex w-fit flex-col gap-1 p-1">
|
||||||
|
{#each examples as example (example.title)}
|
||||||
|
<Popover.Close
|
||||||
|
class={cn(
|
||||||
|
buttonVariants({ variant: 'ghost', size: 'sm' }),
|
||||||
|
'justify-start normal-case'
|
||||||
|
)}
|
||||||
|
onclick={() => loadSampleDiagram(sample, example)}>
|
||||||
|
{example.title}
|
||||||
|
</Popover.Close>
|
||||||
|
{/each}
|
||||||
|
</Popover.Content>
|
||||||
|
</Popover.Root>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { getSampleDiagrams } from './mermaid';
|
||||||
|
|
||||||
|
describe('getSampleDiagrams', () => {
|
||||||
|
const samples = getSampleDiagrams();
|
||||||
|
|
||||||
|
it('should return at least one example per diagram', () => {
|
||||||
|
expect(Object.keys(samples).length).toBeGreaterThan(0);
|
||||||
|
for (const [name, examples] of Object.entries(samples)) {
|
||||||
|
expect(examples.length, `${name} should have at least one example`).toBeGreaterThan(0);
|
||||||
|
for (const example of examples) {
|
||||||
|
expect(example.title, `${name} has an example without a title`).toBeTruthy();
|
||||||
|
expect(example.code, `${name} example "${example.title}" has no code`).toBeTruthy();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should list the default example first', () => {
|
||||||
|
for (const [name, examples] of Object.entries(samples)) {
|
||||||
|
expect(examples[0].isDefault, `${name} should have its default example first`).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
+11
-11
@@ -49,20 +49,20 @@ export const standardizeDiagramType = (diagramType: string) => {
|
|||||||
|
|
||||||
type DiagramDefinition = (typeof diagramData)[number];
|
type DiagramDefinition = (typeof diagramData)[number];
|
||||||
|
|
||||||
|
export type SampleExample = DiagramDefinition['examples'][number];
|
||||||
|
|
||||||
const isValidDiagram = (diagram: DiagramDefinition): diagram is Required<DiagramDefinition> => {
|
const isValidDiagram = (diagram: DiagramDefinition): diagram is Required<DiagramDefinition> => {
|
||||||
return Boolean(diagram.name && diagram.examples && diagram.examples.length > 0);
|
return Boolean(diagram.name && diagram.examples && diagram.examples.length > 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSampleDiagrams = () => {
|
export const getSampleDiagrams = (): Record<string, SampleExample[]> => {
|
||||||
const diagrams = diagramData
|
const samples: Record<string, SampleExample[]> = {};
|
||||||
.filter((d) => isValidDiagram(d))
|
for (const diagram of diagramData.filter((d) => isValidDiagram(d))) {
|
||||||
.map(({ examples, ...rest }) => ({
|
// The default example comes first, so it is loaded when clicking the
|
||||||
...rest,
|
// diagram name and shown at the top of the example dropdown.
|
||||||
example: examples?.filter(({ isDefault }) => isDefault)[0]
|
samples[diagram.name.replace(/ (Diagram|Chart|Graph)/, '')] = [...diagram.examples].sort(
|
||||||
}));
|
(a, b) => Number(b.isDefault ?? false) - Number(a.isDefault ?? false)
|
||||||
const examples: Record<string, string> = {};
|
);
|
||||||
for (const diagram of diagrams) {
|
|
||||||
examples[diagram.name.replace(/ (Diagram|Chart|Graph)/, '')] = diagram.example.code;
|
|
||||||
}
|
}
|
||||||
return examples;
|
return samples;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user