forked from 1b-agent/opencadstudio-upstream
feat(block-editor): isolate session history and add save checkpoints
This commit is contained in:
1 parent
535710e5d1
commit
f39fb31137
15 files changed
+1002
-255
No files matched your search
@@ -887,11 +887,9 @@ fn locked_palette_remove_refuses_a_block_with_an_open_bedit_session() {
|
||||
);
|
||||
}
|
||||
|
||||
/// Nested BEDIT sessions all count: the outer and the inner record are both
|
||||
/// open, so both are refused — the whole session stack is handed to the
|
||||
/// service, not just the active tab.
|
||||
/// Switching replaces the open session; the active definition stays protected.
|
||||
#[test]
|
||||
fn locked_palette_remove_refuses_every_block_of_a_nested_bedit_session() {
|
||||
fn locked_palette_remove_refuses_the_open_block_after_switching() {
|
||||
let mut app = app();
|
||||
let inner = "BO_NEST_INNER";
|
||||
define_block(&mut app, inner);
|
||||
@@ -915,10 +913,10 @@ fn locked_palette_remove_refuses_every_block_of_a_nested_bedit_session() {
|
||||
begin_bedit(&mut app, inner_reference);
|
||||
assert_eq!(
|
||||
app.tabs[tab(&app)].block_edits.len(),
|
||||
2,
|
||||
"the nested session did not open"
|
||||
1,
|
||||
"switching must keep one session"
|
||||
);
|
||||
for name in [outer, inner] {
|
||||
for name in [inner] {
|
||||
palette_remove(&mut app, name);
|
||||
assert!(record_exists(&app, name), "{name} went while open");
|
||||
let expected = command_line_error(&format!(
|
||||
|
||||
@@ -151,7 +151,7 @@ impl OpenCADStudio {
|
||||
/// Close queued drawings until a dirty tab requires confirmation. Queue
|
||||
/// entries use stable document ids because removing an earlier tab changes
|
||||
/// every later vector index.
|
||||
pub(super) fn continue_tab_close_queue(&mut self) -> Task<Message> {
|
||||
pub(in crate::app) fn continue_tab_close_queue(&mut self) -> Task<Message> {
|
||||
let mut tasks = Vec::new();
|
||||
while let Some(tab_id) = self.pending_tab_closes.pop_front() {
|
||||
let Some(idx) = self.tabs.iter().position(|tab| tab.id == tab_id) else {
|
||||
@@ -160,6 +160,13 @@ impl OpenCADStudio {
|
||||
if self.tabs[idx].is_start {
|
||||
continue;
|
||||
}
|
||||
if self.tabs[idx].active_block_edit.is_some() {
|
||||
tasks.push(self.on_tab_close(idx));
|
||||
if self.pending_block_close.is_some() || self.pending_close.is_some() {
|
||||
break;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if self.tabs[idx].dirty {
|
||||
self.pending_close = Some(crate::app::PendingClose::Tab(tab_id));
|
||||
tasks.push(self.open_unsaved_dialog_window());
|
||||
@@ -170,11 +177,21 @@ impl OpenCADStudio {
|
||||
Task::batch(tasks)
|
||||
}
|
||||
|
||||
pub(super) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
pub(in crate::app) fn on_tab_close(&mut self, idx: usize) -> Task<Message> {
|
||||
// Start tab is fixed — close requests on it are no-ops.
|
||||
if self.tabs.get(idx).map_or(false, |t| t.is_start) {
|
||||
return Task::none();
|
||||
}
|
||||
if self
|
||||
.tabs
|
||||
.get(idx)
|
||||
.is_some_and(|tab| tab.active_block_edit.is_some())
|
||||
{
|
||||
return self.request_block_edit_close(
|
||||
idx,
|
||||
crate::app::block_editor::BlockEditContinuation::Tab(self.tabs[idx].id),
|
||||
);
|
||||
}
|
||||
// Closing a tab shifts indices / the active tab. The attribute
|
||||
// editor holds a document-local handle into one tab, so drop it
|
||||
// now rather than risk it applying to a different tab's document.
|
||||
@@ -4344,7 +4361,7 @@ impl OpenCADStudio {
|
||||
/// Close the attribute editor without applying, if it is open. Used when
|
||||
/// the tab it belongs to closes or the active tab switches — its handle is
|
||||
/// document-local and must not outlive its tab.
|
||||
pub(super) fn cancel_attr_editor(&mut self) {
|
||||
pub(in crate::app) fn cancel_attr_editor(&mut self) {
|
||||
if self.active_modal == Some(crate::app::ModalKind::AttributeEditor) {
|
||||
self.active_modal = None;
|
||||
self.reset_modal_geometry();
|
||||
|
||||
@@ -215,6 +215,7 @@ impl OpenCADStudio {
|
||||
match self.active_modal {
|
||||
// Dismissing these via ✕ is the cancel/decline path.
|
||||
Some(Unsaved) => self.pending_close = None,
|
||||
Some(BlockEditChanges) => self.cancel_block_edit_close(),
|
||||
Some(AssocPrompt) => self.mark_assoc_prompted(),
|
||||
// Cancel: leave the layer (and its objects) untouched.
|
||||
Some(LayerDeleteWarning) => self.layer_delete_pending = None,
|
||||
@@ -3895,11 +3896,7 @@ impl OpenCADStudio {
|
||||
|
||||
Message::WindowCloseRequested(id) => {
|
||||
if self.main_window == Some(id) {
|
||||
if self.tabs.iter().any(|t| t.dirty) {
|
||||
self.pending_close = Some(super::PendingClose::Quit);
|
||||
return self.open_unsaved_dialog_window();
|
||||
}
|
||||
return self.exit_app();
|
||||
return self.request_quit_with_block_edits();
|
||||
}
|
||||
Task::none()
|
||||
}
|
||||
@@ -10434,6 +10431,12 @@ impl OpenCADStudio {
|
||||
Message::UnsavedDialogDiscard => self.on_unsaved_dialog_discard(),
|
||||
|
||||
Message::UnsavedDialogSave => self.on_unsaved_dialog_save(),
|
||||
Message::BlockEditChangesSave => self.resolve_block_edit_close(true),
|
||||
Message::BlockEditChangesDiscard => self.resolve_block_edit_close(false),
|
||||
Message::BlockEditChangesCancel => {
|
||||
self.cancel_block_edit_close();
|
||||
Task::none()
|
||||
}
|
||||
|
||||
Message::AecDropSameVersion => self.on_aec_drop_same_version(),
|
||||
Message::AecDropProceed => self.on_aec_drop_proceed(),
|
||||
|
||||
@@ -6446,6 +6446,12 @@ properties={:.1}ms picked={}",
|
||||
}
|
||||
|
||||
pub(crate) fn on_layout_switch(&mut self, name: String) -> Task<Message> {
|
||||
if self.tabs[self.active_tab].active_block_edit.is_some() {
|
||||
return self.request_block_edit_close(
|
||||
self.active_tab,
|
||||
crate::app::block_editor::BlockEditContinuation::Layout(name),
|
||||
);
|
||||
}
|
||||
self.on_layout_switch_inner(name, false)
|
||||
}
|
||||
|
||||
@@ -6508,6 +6514,12 @@ properties={:.1}ms picked={}",
|
||||
/// define a view and then returns to its paper layout. This is the only
|
||||
/// layout transition allowed to preserve an active command.
|
||||
pub(crate) fn on_layout_switch_preserving_command(&mut self, name: String) -> Task<Message> {
|
||||
if self.tabs[self.active_tab].active_block_edit.is_some() {
|
||||
return self.request_block_edit_close(
|
||||
self.active_tab,
|
||||
crate::app::block_editor::BlockEditContinuation::Layout(name),
|
||||
);
|
||||
}
|
||||
self.on_layout_switch_inner(name, true)
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user