| 1 | --- |
| 2 | // A diagram from src/diagrams, by name: drawn as an inline SVG in the g1t |
| 3 | // tokens, with its caption, a key, and Expand, which opens it at full size |
| 4 | // in a dialog. See src/diagrams/engine.ts. |
| 5 | import { DIAGRAMS } from '../diagrams/index.ts'; |
| 6 | import { render } from '../diagrams/engine.ts'; |
| 7 | import LucideIcon from './LucideIcon.astro'; |
| 8 | |
| 9 | interface Props { |
| 10 | name: string; |
| 11 | } |
| 12 | |
| 13 | const { name } = Astro.props; |
| 14 | const spec = DIAGRAMS[name]; |
| 15 | if (!spec) throw new Error(`No diagram named "${name}". Add it to src/diagrams/index.ts.`); |
| 16 | // The dialog's copy has its own ids, so its arrowheads and title never |
| 17 | // point at the page's copy. |
| 18 | const svg = render(`d-${name}`, spec); |
| 19 | const large = render(`d-${name}-full`, spec); |
| 20 | const asyncUsed = spec.kind === 'graph' ? spec.edges.some((e) => e.async) : spec.steps.some((s) => 'from' in s && (s.async || s.reply)); |
| 21 | --- |
| 22 | |
| 23 | <figure class="g1t-diagram not-content" data-diagram> |
| 24 | <div class="g1t-diagram__scroll" tabindex="0" role="group" aria-label={`${spec.caption}. Scrolls sideways on a narrow screen.`}> |
| 25 | <Fragment set:html={svg} /> |
| 26 | </div> |
| 27 | <figcaption> |
| 28 | <span>{spec.caption}</span> |
| 29 | <span class="g1t-diagram__tools"> |
| 30 | <span class="g1t-diagram__key" aria-hidden="true"> |
| 31 | <span><svg width="22" height="8"><line x1="1" y1="4" x2="21" y2="4" class="d-key" /></svg> |
| 32 | {spec.kind === 'sequence' ? 'call' : 'calls'}</span> |
| 33 | { |
| 34 | asyncUsed && ( |
| 35 | <span><svg width="22" height="8"><line x1="1" y1="4" x2="21" y2="4" class="d-key d-async" /></svg> |
| 36 | {spec.kind === 'sequence' ? 'answer or event' : 'event, queue or stream'}</span> |
| 37 | ) |
| 38 | } |
| 39 | </span> |
| 40 | <button type="button" class="g1t-diagram__expand" data-diagram-open aria-haspopup="dialog"> |
| 41 | <LucideIcon name="maximize-2" size="0.875rem" /> |
| 42 | Expand |
| 43 | </button> |
| 44 | </span> |
| 45 | </figcaption> |
| 46 | <dialog class="g1t-diagram__dialog" aria-label={spec.caption}> |
| 47 | <div class="g1t-diagram__dialog-bar"> |
| 48 | <span>{spec.caption}</span> |
| 49 | <button type="button" class="g1t-diagram__expand" data-diagram-close> |
| 50 | <LucideIcon name="x" size="0.875rem" /> |
| 51 | Close |
| 52 | </button> |
| 53 | </div> |
| 54 | <div class="g1t-diagram g1t-diagram__dialog-body"> |
| 55 | <div class="g1t-diagram__scroll"><Fragment set:html={large} /></div> |
| 56 | </div> |
| 57 | </dialog> |
| 58 | </figure> |
| 59 | |
| 60 | <script> |
| 61 | // Expand opens the figure's dialog; Close, Escape or a click on the |
| 62 | // backdrop closes it. |
| 63 | for (const figure of document.querySelectorAll<HTMLElement>('[data-diagram]')) { |
| 64 | const dialog = figure.querySelector('dialog'); |
| 65 | if (!dialog) continue; |
| 66 | figure.querySelector('[data-diagram-open]')?.addEventListener('click', () => dialog.showModal()); |
| 67 | dialog.querySelector('[data-diagram-close]')?.addEventListener('click', () => dialog.close()); |
| 68 | dialog.addEventListener('click', (event) => { |
| 69 | if (event.target === dialog) dialog.close(); |
| 70 | }); |
| 71 | } |
| 72 | </script> |