From c5cbfd3eea0e9f738723faba3093df96cc352ba5 Mon Sep 17 00:00:00 2001 From: Justin Visser Date: Wed, 10 Jun 2026 18:19:32 +0200 Subject: [PATCH] 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. --- web/src/components/ExportMenu.vue | 111 +++++++++++++++++++++++++ web/src/components/FocusModal.vue | 6 +- web/src/components/ScenarioPanel.vue | 6 +- web/src/components/ScenarioToolbar.vue | 20 +---- web/src/lib/export.ts | 13 ++- 5 files changed, 136 insertions(+), 20 deletions(-) create mode 100644 web/src/components/ExportMenu.vue diff --git a/web/src/components/ExportMenu.vue b/web/src/components/ExportMenu.vue new file mode 100644 index 0000000..a78ae2a --- /dev/null +++ b/web/src/components/ExportMenu.vue @@ -0,0 +1,111 @@ + + + + + diff --git a/web/src/components/FocusModal.vue b/web/src/components/FocusModal.vue index d94d604..891300d 100644 --- a/web/src/components/FocusModal.vue +++ b/web/src/components/FocusModal.vue @@ -28,7 +28,11 @@ const numStudents = computed(() => ) const reachedCount = computed(() => result.value - ? reachedCountDisplayed(result.value.reachedAtRound, store.state.round, store.state.roundProgress) + ? reachedCountDisplayed( + result.value.reachedAtRound, + store.state.round, + store.state.roundProgress, + ) : 0, ) const reachedPctNow = computed(() => (reachedCount.value / Math.max(numStudents.value, 1)) * 100) diff --git a/web/src/components/ScenarioPanel.vue b/web/src/components/ScenarioPanel.vue index f762d7c..de535d3 100644 --- a/web/src/components/ScenarioPanel.vue +++ b/web/src/components/ScenarioPanel.vue @@ -67,7 +67,11 @@ const numStudents = computed(() => store.effectiveConfig(props.panel).numStudent const reachedCount = computed(() => { const panelResult = result.value return panelResult - ? reachedCountDisplayed(panelResult.reachedAtRound, store.state.round, store.state.roundProgress) + ? reachedCountDisplayed( + panelResult.reachedAtRound, + store.state.round, + store.state.roundProgress, + ) : 0 }) const reachedPctNow = computed(() => (reachedCount.value / numStudents.value) * 100) diff --git a/web/src/components/ScenarioToolbar.vue b/web/src/components/ScenarioToolbar.vue index db90040..f13638a 100644 --- a/web/src/components/ScenarioToolbar.vue +++ b/web/src/components/ScenarioToolbar.vue @@ -1,5 +1,6 @@