UI: the control channel's dialog cancel closes the macro step editor as its own close does

ui.dialog.cancel emptied the dialog slot, so with the step editor open
it went without its cleanup: the draft stayed behind, the Check overlays
stayed on the grid, the selection the reference boxes moved was not put
back and a debug run went on with no controls. The cancel now closes
the editor the way its close button does, and is refused, with the
reason, while the editor holds unsaved steps or a debug run, which only
its window asks about.
This commit is contained in:
prxnvt
2026-10-25 06:48:57 -04:00
parent a206178209
commit a18ecc81aa
3 changed files with 54 additions and 0 deletions
+7
View File
@@ -320,6 +320,13 @@ pub fn handle(app: &mut SheetApp, ctx: &egui::Context, method: &str, p: &Json) -
None => err("no dialog is open"),
},
"ui.dialog.cancel" => {
// The macro step editor's draft, overlays and debug run go with its own close.
if app.dialog.as_ref().is_some_and(|d| d.name == "macroEditor") {
return match crate::macro_editor::close_now(app) {
Ok(()) => ok(Json::Null),
Err(e) => err(e),
};
}
app.dialog = None;
ok(Json::Null)
}
+20
View File
@@ -838,6 +838,26 @@ pub fn window(app: &mut SheetApp, ctx: &egui::Context, d: &mut Dialog, open: &mu
}
}
/// Closes the step editor at once for a caller outside its window (the control channel's
/// `ui.dialog.cancel`), with the cleanup its own close does. Refused while it holds a debug run
/// or unsaved steps, which only its window asks about.
pub fn close_now(app: &mut SheetApp) -> Result<(), String> {
if app.session.macro_debug.is_some() {
return Err("A macro is being debugged: stop it first (macro.debug {\"action\": \"stop\"}).".into());
}
if app.macros.editor.as_ref().is_some_and(|ed| ed.dirty) {
return Err("The macro step editor has unsaved steps: save them or close the editor in its window.".into());
}
if let Some(ed) = app.macros.editor.take()
&& app.session.active().is_some_and(|d| d.uid == ed.doc)
{
restore_home(app, &ed.refs);
}
app.grid.macro_overlay = None;
app.dialog = None;
Ok(())
}
// ---------------------------------------------------------------- reference boxes
/// A range as an absolute reference: `$B$2:$D$9`.
+27
View File
@@ -402,3 +402,30 @@ fn the_editor_works_only_on_the_workbook_it_opened_in() {
assert!(h.state().macros.editor.is_none());
assert!(h.state().dialog.as_ref().is_none_or(|d| d.name() != "macroEditor"));
}
#[test]
fn the_control_channels_cancel_closes_the_editor_as_its_own_close_does() {
let mut h = harness();
three(&mut h);
let ctx = h.ctx.clone();
let reply = |o: gridcraft_ui_egui::control::Outcome| match o {
gridcraft_ui_egui::control::Outcome::Done(v) => v,
_ => json!(null),
};
// Unsaved steps are only the editor's to ask about: cancel is refused and keeps them.
h.state_mut().macros.editor.as_mut().unwrap().dirty = true;
let r = reply(gridcraft_ui_egui::control::handle(h.state_mut(), &ctx, "ui.dialog.cancel", &json!({})));
assert!(r.get("error").is_some(), "{r}");
assert!(h.state().macros.editor.is_some());
// A saved editor closes, and takes its overlays with it.
h.state_mut().macros.editor.as_mut().unwrap().dirty = false;
h.get_by_label("Check").click();
h.run_steps(3);
assert!(h.state().grid.macro_overlay.is_some());
let r = reply(gridcraft_ui_egui::control::handle(h.state_mut(), &ctx, "ui.dialog.cancel", &json!({})));
assert!(r.get("error").is_none(), "{r}");
h.run_steps(2);
assert!(h.state().dialog.is_none());
assert!(h.state().macros.editor.is_none());
assert!(h.state().grid.macro_overlay.is_none());
}