web: export menu, slice 11 of M3

The toolbar's Export button becomes a real menu: Data (JSON) downloads
{ base, panels, results } exactly as held in the store, Per-node CSV
writes one row per panel and node (educated flag, reachedAtRound with
-1 kept), and Picture (PNG) rasterizes each panel's live SVG with the
current theme's token values resolved, drawn side by side with labels
and the live percentage at the current round, plus legend and the
disclaimer footer line, at 2x. Items disable while a run is in flight
so exports always capture settled state.
This commit is contained in:
Justin Visser 2026-06-10 18:19:32 +02:00
parent 3cd48ca7ea
commit c5cbfd3eea
5 changed files with 136 additions and 20 deletions

View file

@ -81,7 +81,10 @@ function resolveThemeTokens(): Record<string, string> {
return tokens
}
function svgToImage(svgElement: SVGSVGElement, tokens: Record<string, string>): Promise<HTMLImageElement> {
function svgToImage(
svgElement: SVGSVGElement,
tokens: Record<string, string>,
): Promise<HTMLImageElement> {
const clone = svgElement.cloneNode(true) as SVGSVGElement
clone.setAttribute('width', `${PANEL_WIDTH}`)
clone.setAttribute('height', `${PANEL_HEIGHT}`)
@ -114,7 +117,13 @@ export async function exportPng(store: SimStore): Promise<void> {
const panelCount = panelsWithSvg.length
const width = 2 * PAGE_PADDING + panelCount * PANEL_WIDTH + (panelCount - 1) * PANEL_GAP
const height =
PAGE_PADDING + HEADER_HEIGHT + PANEL_HEIGHT + 20 + LEGEND_HEIGHT + FOOTER_HEIGHT + PAGE_PADDING / 2
PAGE_PADDING +
HEADER_HEIGHT +
PANEL_HEIGHT +
20 +
LEGEND_HEIGHT +
FOOTER_HEIGHT +
PAGE_PADDING / 2
const canvas = document.createElement('canvas')
canvas.width = width
canvas.height = height