From c04fa61d17ef932a95817db628a3f2d51ff38e88 Mon Sep 17 00:00:00 2001 From: moppeeer Date: Thu, 22 Oct 2026 00:53:21 +0200 Subject: [PATCH] Package: a failed package keeps the dialog open; the ink table's Type column; no document is an error - When `file.package` fails, the Package or Printing Instructions dialog comes back with its options and instructions, and the status line says why. Before, they were lost. - The Colors and Inks table heads its kind column with the "Type:" key (without its colon). The "Type" key is the menu's and reads Text in most languages. - `app.packageDialog` with no document open is an error rather than a silent success. Co-Authored-By: Claude Opus 5.5 --- crates/ui-egui/src/dialogs.rs | 18 +++++++++++++-- crates/ui-egui/src/menus.rs | 5 +--- crates/ui-egui/src/package_dialog.rs | 34 ++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 9182f5843..41b1d0504 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -3581,8 +3581,22 @@ pub fn confirm(app: &mut DesignApp) -> Result { } "alert" => Ok(Value::Null), crate::effects_dialog::ID => crate::effects_dialog::confirm(app, &d), - crate::package_dialog::ID => crate::package_dialog::confirm(app, &d), - crate::package_dialog::INSTRUCTIONS => crate::package_dialog::confirm_instructions(app, &d), + crate::package_dialog::ID | crate::package_dialog::INSTRUCTIONS => { + let r = if d.id == crate::package_dialog::ID { + crate::package_dialog::confirm(app, &d) + } else { + crate::package_dialog::confirm_instructions(app, &d) + }; + // Packaging failed: the dialog stays open with its options and instructions, and the + // status line says why. + if let Err(e) = &r + && app.ui.dialog.is_none() + { + app.status(e.clone()); + app.ui.dialog = Some(d); + } + r + } "findChange" => { let result = if d.b("objectMode") { Ok(Value::Null) } else { app.run("find.change", find_change_params(&d)) }; // Confirmation consumes retained bounds before closing, even on an error. diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 794e58f2d..923ff2a86 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -1149,10 +1149,7 @@ fn run_ui_inner(app: &mut DesignApp, id: &str, p: &Value) -> Option export_bytes(app, p, "rtf", "file.exportText"), - "app.packageDialog" => { - crate::package_dialog::open(app); - Ok(Value::Null) - } + "app.packageDialog" => crate::package_dialog::open(app).map(|()| Value::Null), "app.language" => { let Some(v) = p.get("lang").and_then(Value::as_str) else { return Some(app.run("app.preferences", json!({}))) }; if !crate::i18n::LANGUAGES.iter().any(|(c, _)| *c == v) { diff --git a/crates/ui-egui/src/package_dialog.rs b/crates/ui-egui/src/package_dialog.rs index 6c00ca060..741de6b44 100644 --- a/crates/ui-egui/src/package_dialog.rs +++ b/crates/ui-egui/src/package_dialog.rs @@ -33,9 +33,9 @@ const INSTRUCTION_FIELDS: [(&str, &str, usize); 7] = [ ]; /// Open the Package dialog on its Summary, with the document's fonts, links and spot inks. -pub fn open(app: &mut DesignApp) { +pub fn open(app: &mut DesignApp) -> Result<(), String> { if app.session.active().is_none() { - return; + return Err("no document open".into()); } let fonts: Vec = app .session @@ -85,6 +85,7 @@ pub fn open(app: &mut DesignApp) { ID, json!({"page": "summary", "fonts": fonts, "links": links, "spot": spot, "instructions": false, "idml": true, "pdf": false, "problemsOnly": false}), )); + Ok(()) } fn rows(d: &Dialog, key: &str) -> Vec { @@ -228,7 +229,9 @@ pub fn show(app: &DesignApp, ui: &mut egui::Ui, d: &mut Dialog) { "inks" => { let mut cells: Vec<_> = PROCESS_INKS.iter().map(|n| (vec![tr(n), tr("Process")], None)).collect(); cells.extend(rows(d, "spot").iter().filter_map(Value::as_str).map(|n| (vec![n.to_string(), tr("Spot")], None))); - table(ui, "package_inks", &[tr("Name"), tr("Type")], &cells); + // The ink's kind: the "Type:" key (the "Type" key is the menu's, which reads Text). + let kind = tr("Type:").trim_end_matches([':', ':']).trim_end().to_string(); + table(ui, "package_inks", &[tr("Name"), kind], &cells); } _ => { ui.horizontal(|ui| { @@ -441,7 +444,7 @@ mod tests { let out = dir.join("Packed Folder"); let picked = out.to_string_lossy().to_string(); app.services.pick_save = Some(Box::new(move |_| Some(picked.clone()))); - open(&mut app); + open(&mut app).unwrap(); let mut h = harness(app); h.run_steps(3); h.get_by_label("Include IDML").click(); @@ -454,4 +457,27 @@ mod tests { assert!(out.join("Packed.pdf").exists() && !out.join("Packed.idml").exists()); std::fs::remove_dir_all(dir).unwrap(); } + + /// Packaging that fails (here, into a folder under a file) keeps the dialog open with its + /// options, and the status line says why. Without a document there's nothing to open. + #[test] + fn a_failed_package_keeps_the_dialog_open() { + let dir = temp_dir("fails"); + let mut app = app_with_missing_link(&dir); + let blocker = dir.join("blocker"); + std::fs::write(&blocker, b"a file, not a folder").unwrap(); + let picked = blocker.join("Packed Folder").to_string_lossy().to_string(); + app.services.pick_save = Some(Box::new(move |_| Some(picked.clone()))); + open(&mut app).unwrap(); + app.ui.dialog.as_mut().unwrap().fields.insert("pdf".into(), json!(true)); + assert!(crate::dialogs::confirm(&mut app).is_err()); + let d = app.ui.dialog.as_ref().expect("the dialog stays open"); + assert_eq!((d.id.as_str(), d.b("pdf")), (ID, true), "with its options"); + assert!(!app.ui.status.is_empty()); + app.run("file.close", json!({})).unwrap(); + app.ui.dialog = None; + assert!(app.run("app.packageDialog", json!({})).is_err()); + assert!(app.ui.dialog.is_none()); + std::fs::remove_dir_all(dir).unwrap(); + } }