Repository navigation
UI: an unsaved macro-editor draft counts as unsaved work when its workbook closes #492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
fccee7a
cc11e95
6e74967
2cd3d16
fc19422
0377442
d794911
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,9 @@ pub struct Editor { | |
| pub draft: Macro, | ||
| /// The name the macro is saved under (`None` until it is first saved). | ||
| pub saved_as: Option<String>, | ||
| /// The name the editor opened with: a new macro that still has it and nothing else counts | ||
| /// as untouched. | ||
| pub opened_as: String, | ||
| pub dirty: bool, | ||
| /// The selected row: a step path (`3`, `2.1`, `4.then.1`) or an If branch (`4.then`). | ||
| pub selected: Option<String>, | ||
|
|
@@ -79,6 +82,9 @@ pub struct Editor { | |
| pub close_requested: bool, | ||
| /// The window is quitting: once the editor has closed, the window close goes on. | ||
| pub quit: bool, | ||
| /// The workbook is closing: once the editor has closed, this command (`file.close` or | ||
| /// `window.close` with its params) runs again. | ||
| pub then_close: Option<(String, Json)>, | ||
| /// Asking whether to save before closing. | ||
| pub ask_close: bool, | ||
| /// Start debugging on the first frame (Macros › Step Into). | ||
|
|
@@ -523,6 +529,7 @@ pub fn open(app: &mut SheetApp, params: &Json) -> Option<Dialog> { | |
| let doc = doc.uid; | ||
| app.macros.editor = Some(Editor { | ||
| doc, | ||
| opened_as: draft.name.clone(), | ||
| draft, | ||
| dirty: saved_as.is_none(), | ||
| saved_as, | ||
|
|
@@ -538,6 +545,7 @@ pub fn open(app: &mut SheetApp, params: &Json) -> Option<Dialog> { | |
| closing: false, | ||
| close_requested: false, | ||
| quit: false, | ||
| then_close: None, | ||
| ask_close: false, | ||
| start_debug, | ||
| start_check: params["check"].as_bool() == Some(true), | ||
|
|
@@ -612,6 +620,50 @@ fn ensure_saved(app: &mut SheetApp, ed: &mut Editor) -> bool { | |
| if ed.dirty || ed.saved_as.is_none() { save(app, ed) } else { true } | ||
| } | ||
|
|
||
| impl Editor { | ||
| /// The draft holds changes not yet saved into the workbook. A new macro counts once it | ||
| /// differs from the empty one the editor started with, its name included. | ||
| pub fn has_unsaved_changes(&self) -> bool { | ||
| let untouched = Macro { name: self.opened_as.clone(), inputs: vec![NamedInput::default()], ..Default::default() }; | ||
| self.dirty && (self.saved_as.is_some() || self.draft != untouched) | ||
| } | ||
| } | ||
|
|
||
| /// Closing the editor's workbook (File › Close, Cmd+W, the Backstage Close, a window's close | ||
| /// button) while the draft has unsaved changes: the editor asks about them first, as its own | ||
| /// close button does, and the close (`id` with `params`) goes on once the editor has closed; | ||
| /// Cancel keeps both. Closing the window that shows the editor asks too, even when the workbook | ||
| /// stays open in another window. Returns whether the editor was asked. A close of another of the | ||
| /// workbook's windows, or a forced one, goes ahead as before. | ||
| pub fn ask_before_closing(app: &mut SheetApp, id: &str, params: &Json) -> bool { | ||
| let flag = |k: &str| params.get(k).and_then(Json::as_bool) == Some(true); | ||
| if flag("force") { | ||
| return false; | ||
| } | ||
| let Some(doc) = app.macros.editor.as_ref().filter(|ed| ed.has_unsaved_changes()).map(|ed| ed.doc) else { return false }; | ||
| let window = match id { | ||
| "file.close" => app.session.active_window(), | ||
| "window.close" => gridcraft_engine::Target::from_json(params).ok().and_then(|t| app.session.target_window(t).ok()), | ||
| _ => None, | ||
| }; | ||
| let Some(owner) = window.and_then(|w| app.session.window(w)).map(|w| w.doc_uid) else { return false }; | ||
| // The editor asks in the window it is shown in. | ||
| let Some(home) = app.window_with_dialog("macroEditor") else { return false }; | ||
| let only = app.session.windows().iter().filter(|w| w.doc_uid == owner).count() <= 1; | ||
| // The workbook closes, or the window that shows the editor does (taking the editor with it). | ||
| if owner != doc || !(only || (id == "file.close" && flag("document")) || window == Some(home)) { | ||
| return false; | ||
| } | ||
| if app.ui_window() != Some(home) { | ||
| app.enter_window(home); | ||
| } | ||
|
Comment on lines
+647
to
+659
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| if let Some(ed) = app.macros.editor.as_mut() { | ||
| ed.close_requested = true; | ||
| ed.then_close = Some((id.to_string(), params.clone())); | ||
| } | ||
| true | ||
| } | ||
|
|
||
|
coderabbitai[bot] marked this conversation as resolved.
Resolved
|
||
| /// The inputs a run from the editor uses: the first input's example. | ||
| fn run_inputs(ed: &Editor) -> Json { | ||
| match ed.draft.inputs.first() { | ||
|
|
@@ -863,7 +915,7 @@ pub fn window(app: &mut SheetApp, ctx: &egui::Context, d: &mut Dialog, open: &mu | |
| if app.session.macro_debug.is_some() { | ||
| ed.ask_stop = true; | ||
| ed.closing = true; | ||
| } else if ed.dirty { | ||
| } else if ed.has_unsaved_changes() { | ||
| ed.ask_close = true; | ||
| } else { | ||
| *open = false; | ||
|
|
@@ -882,14 +934,25 @@ pub fn window(app: &mut SheetApp, ctx: &egui::Context, d: &mut Dialog, open: &mu | |
| restore_home(app, &ed.refs); | ||
| } | ||
| if ed.quit { | ||
| ctx.send_viewport_cmd(egui::ViewportCommand::Close); | ||
| // A quit takes over a workbook close waiting on the editor: it asks about every | ||
| // unsaved workbook itself, and the close replayed after it would act on whichever | ||
| // workbook the quit is asking about. | ||
| if app.native_windows { | ||
| // Each workbook window is a native window whose close button closes only it: | ||
| // the quit goes on directly. | ||
| app.continue_quit(ctx); | ||
| } else { | ||
| ctx.send_viewport_cmd(egui::ViewportCommand::Close); | ||
| } | ||
| } else if let Some((id, params)) = ed.then_close.take() { | ||
| app.run_or_alert(&id, params); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Asks the open step editor to close, as its close button does: it asks about a debug run or | ||
| /// unsaved steps first. With `quit` (the window is closing), the window close goes on once the | ||
| /// editor has closed; a Cancel in its questions keeps both open. | ||
| /// unsaved steps first. With `quit` (the window is closing, or the app is quitting), the close or | ||
| /// quit goes on once the editor has closed; a Cancel in its questions keeps both open. | ||
| pub fn request_close(app: &mut SheetApp, quit: bool) { | ||
| if let Some(ed) = app.macros.editor.as_mut() { | ||
| ed.close_requested = true; | ||
|
|
@@ -904,7 +967,7 @@ 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) { | ||
| if app.macros.editor.as_ref().is_some_and(Editor::has_unsaved_changes) { | ||
| 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() | ||
|
|
@@ -1018,14 +1081,15 @@ fn prompts(app: &mut SheetApp, ctx: &egui::Context, ed: &mut Editor, open: &mut | |
| ed.ask_stop = false; | ||
| stop(app, ed, keep); | ||
| if ed.closing { | ||
| if ed.dirty { ed.ask_close = true } else { *open = false } | ||
| if ed.has_unsaved_changes() { ed.ask_close = true } else { *open = false } | ||
| } | ||
| ed.closing = false; | ||
| } | ||
| Some(None) => { | ||
| ed.ask_stop = false; | ||
| ed.closing = false; | ||
| ed.quit = false; | ||
| ed.then_close = None; | ||
| } | ||
| None => {} | ||
| } | ||
|
|
@@ -1054,6 +1118,11 @@ fn prompts(app: &mut SheetApp, ctx: &egui::Context, ed: &mut Editor, open: &mut | |
| ed.ask_close = false; | ||
| if save(app, ed) { | ||
| *open = false; | ||
| } else { | ||
| // The draft stays open with the reason shown; the workbook is not closed and | ||
| // the app does not quit, then or when the editor closes later. | ||
| ed.then_close = None; | ||
| ed.quit = false; | ||
| } | ||
| } | ||
| Some(Some(false)) => { | ||
|
|
@@ -1063,6 +1132,7 @@ fn prompts(app: &mut SheetApp, ctx: &egui::Context, ed: &mut Editor, open: &mut | |
| Some(None) => { | ||
| ed.ask_close = false; | ||
| ed.quit = false; | ||
| ed.then_close = None; | ||
| } | ||
| None => {} | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.