From 73f5ea573d6dfad51e07c383f3c21c37223923d5 Mon Sep 17 00:00:00 2001 From: "Dylan M. Taylor" Date: Tue, 3 Nov 2026 12:58:27 +0000 Subject: [PATCH] fix(ui): automation hand-offs refused, preview page named inside its folder, graph grid bounded --- crates/ui-egui/src/dialogs/form.rs | 5 ++++- crates/ui-egui/src/dialogs/graph_data.rs | 19 ++++++++++++++++--- crates/ui-egui/src/dialogs/save_for_web.rs | 10 +++++++++- .../ui-egui/src/dialogs/tests_save_for_web.rs | 15 +++++++++++++++ crates/ui-egui/src/io/save.rs | 3 ++- 5 files changed, 46 insertions(+), 6 deletions(-) diff --git a/crates/ui-egui/src/dialogs/form.rs b/crates/ui-egui/src/dialogs/form.rs index ed6be47e8..47567e51e 100644 --- a/crates/ui-egui/src/dialogs/form.rs +++ b/crates/ui-egui/src/dialogs/form.rs @@ -468,7 +468,10 @@ fn typed_number(x: f64) -> Value { /// the rows then scroll ([`super::modal::scrolled_rows`]). pub(super) fn schema_fields(app: &mut crate::VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, m: &Manifest) -> bool { let (changed, help) = schema_rows(ui, d, m); - if let Some(url) = help { + // The browser is another app: a click automation sent opens nothing. + if let Some(url) = help + && app.refuse_automation("the plug-in help button").is_ok() + { app.open_url(&url); } changed diff --git a/crates/ui-egui/src/dialogs/graph_data.rs b/crates/ui-egui/src/dialogs/graph_data.rs index c5b7d7e58..16d22d443 100644 --- a/crates/ui-egui/src/dialogs/graph_data.rs +++ b/crates/ui-egui/src/dialogs/graph_data.rs @@ -106,8 +106,11 @@ pub(crate) fn grid_from_data(v: &Value) -> Grid { /// columns past the data. pub(crate) fn data_from_grid(grid: &Grid) -> Value { let filled = |s: &String| !s.trim().is_empty(); - let rows = grid.iter().rposition(|r| r.iter().any(filled)).map_or(1, |i| i + 1).max(1); - let cols = grid.iter().take(rows).filter_map(|r| r.iter().rposition(filled)).max().map_or(1, |i| i + 1).max(1); + // At most a graph's rows and columns, as a file gives (`grid_from_text`): a grid sent by + // automation is any size, and the values are their product. + let grid = &grid[..grid.len().min(MAX_ROWS)]; + let rows = grid.iter().rposition(|r| r.iter().take(MAX_COLS).any(filled)).map_or(1, |i| i + 1).max(1); + let cols = grid.iter().take(rows).filter_map(|r| r.iter().take(MAX_COLS).rposition(filled)).max().map_or(1, |i| i + 1).max(1); let label = |s: &str| { let s = s.trim(); // A label typed in straight quotes (as the CSV form takes a number as a label) is the text inside. @@ -163,7 +166,7 @@ fn clean(s: &str) -> String { s.chars().map(|c| if matches!(c, '\t' | '\r' | '\n') { ' ' } else { c }).take(MAX_CELL).collect() } -fn cell(grid: &Grid, r: usize, c: usize) -> String { +fn cell(grid: &[Vec], r: usize, c: usize) -> String { grid.get(r).and_then(|row| row.get(c)).cloned().unwrap_or_default() } @@ -514,6 +517,16 @@ mod tests { assert_eq!(grid_from_text(&wide)[0].len(), MAX_COLS); } + #[test] + fn a_sheet_sent_wider_and_taller_than_a_graph_is_cut_to_one() { + // One long row and one long column: their product would be ten billion cells. + let mut grid: Grid = vec![vec!["1".to_string(); 100_000]]; + grid.extend((0..100_000).map(|_| vec!["2".to_string()])); + let p = data_from_grid(&grid); + assert_eq!(p["rows"].as_array().unwrap().len(), MAX_ROWS - 1); + assert_eq!(p["series"].as_array().unwrap().len(), MAX_COLS - 1); + } + #[test] fn transpose_switch_and_moves() { assert_eq!(transpose(&g(&[&["", "a", "b"], &["Q1", "1", "2"]])), g(&[&["", "Q1"], &["a", "1"], &["b", "2"]])); diff --git a/crates/ui-egui/src/dialogs/save_for_web.rs b/crates/ui-egui/src/dialogs/save_for_web.rs index 26c01f052..924f8c407 100644 --- a/crates/ui-egui/src/dialogs/save_for_web.rs +++ b/crates/ui-egui/src/dialogs/save_for_web.rs @@ -175,6 +175,13 @@ fn has_warnings(r: &Value) -> bool { r["warnings"].as_array().is_some_and(|w| !w.is_empty()) } +/// The preview page's name from the document's title `stem`: one file name in the preview folder. +/// `file_stem` gives back a title it can't take a stem of ("../.."), so one with a separator or of +/// dots only is "Untitled". +pub(super) fn preview_name(stem: &str) -> &str { + if stem.trim().is_empty() || stem.contains(['/', '\\']) || stem.chars().all(|c| c == '.') { "Untitled" } else { stem } +} + /// `file.saveForWeb.browser`: the HTML page and its images in a new temporary folder, opened in the /// default browser (desktop). When the export has warnings, the status bar shows the first in /// place of "Opened …". @@ -182,9 +189,10 @@ pub(crate) fn browser_preview(app: &mut VectorcraftApp, p: &Value) -> Result /// Open a written file in the system viewer (not on the web, which downloads it). pub(super) fn view_file(app: &mut VectorcraftApp, path: &str) { - if !is_web(app) { + // The viewer is another app: an export automation asked for is written but not opened. + if !is_web(app) && app.refuse_automation("viewAfterSaving").is_ok() { app.open_url(&file_url(path)); } }