Guard and preserve behavior forms across document lifecycle
This commit is contained in:
@@ -12,6 +12,9 @@ use oa_core::{
|
||||
use serde_json::{Value, json};
|
||||
use std::{collections::BTreeSet, path::PathBuf, sync::Arc};
|
||||
|
||||
#[cfg(test)]
|
||||
mod behavior_lifecycle_tests;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum LoadedSnapshot {
|
||||
Source(DiskProject),
|
||||
@@ -351,7 +354,10 @@ impl App {
|
||||
self.draft.as_ref().is_some_and(Draft::dirty)
|
||||
}
|
||||
pub fn dirty(&self) -> bool {
|
||||
self.document.dirty() || self.draft_dirty() || self.source_dirty()
|
||||
self.document.dirty()
|
||||
|| self.draft_dirty()
|
||||
|| self.source_dirty()
|
||||
|| self.behavior.has_pending()
|
||||
}
|
||||
fn sync_source(&mut self) {
|
||||
self.source_workspace.accept(self.document.bundle());
|
||||
@@ -435,6 +441,11 @@ impl App {
|
||||
}
|
||||
}
|
||||
pub fn apply_source(&mut self) -> bool {
|
||||
if self.behavior.has_pending() {
|
||||
self.status =
|
||||
"Apply or cancel the behavior form before applying source changes.".into();
|
||||
return false;
|
||||
}
|
||||
if !self.source_dirty() {
|
||||
return true;
|
||||
}
|
||||
@@ -465,6 +476,10 @@ impl App {
|
||||
}
|
||||
}
|
||||
pub fn apply(&mut self) -> bool {
|
||||
if self.behavior.has_pending() {
|
||||
self.status = "Apply or cancel the behavior form before changing the model.".into();
|
||||
return false;
|
||||
}
|
||||
if self.source_dirty() {
|
||||
self.status =
|
||||
"Apply all source drafts or revert them before changing the model.".into();
|
||||
@@ -540,6 +555,30 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
fn apply_behavior(&mut self) -> bool {
|
||||
if !self.behavior.has_pending() {
|
||||
return true;
|
||||
}
|
||||
if self.source_dirty() || self.draft_dirty() {
|
||||
self.status = "Property, source and behavior drafts are retained. Revert the other drafts before applying the behavior form.".into();
|
||||
return false;
|
||||
}
|
||||
match self.behavior.prepare_pending(self.project()) {
|
||||
Ok(Some(command)) => {
|
||||
if self.execute(command) {
|
||||
self.behavior.acknowledge_commit();
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Ok(None) => true,
|
||||
Err(error) => {
|
||||
self.status = error;
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
fn reset(&mut self, project: Project, path: Option<PathBuf>) {
|
||||
let window_size = self.window_size;
|
||||
let canvas_size = self.canvas_size;
|
||||
@@ -674,7 +713,7 @@ impl App {
|
||||
) {
|
||||
self.model_menu = None;
|
||||
}
|
||||
if self.source_dirty()
|
||||
if (self.source_dirty() || self.behavior.has_pending())
|
||||
&& matches!(
|
||||
message,
|
||||
Message::Field(..)
|
||||
@@ -687,7 +726,8 @@ impl App {
|
||||
)
|
||||
{
|
||||
self.status =
|
||||
"Apply all source drafts or revert them before editing properties.".into();
|
||||
"Apply or cancel pending source and behavior drafts before editing properties."
|
||||
.into();
|
||||
return Task::none();
|
||||
}
|
||||
if self.busy
|
||||
@@ -768,6 +808,35 @@ impl App {
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
self.selected_bend = None;
|
||||
}
|
||||
if self.behavior.has_pending()
|
||||
&& matches!(
|
||||
message,
|
||||
Message::Page(page) if page != Page::Behavior
|
||||
)
|
||||
{
|
||||
self.status = "Apply or cancel the behavior form before leaving this view.".into();
|
||||
return Task::none();
|
||||
}
|
||||
if self.behavior.has_pending()
|
||||
&& matches!(
|
||||
message,
|
||||
Message::BrowseGroup(_)
|
||||
| Message::SourceEdit(_)
|
||||
| Message::SourceCreateFile
|
||||
| Message::SourceCloseFile(_)
|
||||
| Message::SourceUndo
|
||||
| Message::SourceRedo
|
||||
| Message::SourceRecover
|
||||
| Message::SourceLoadIncludes
|
||||
| Message::Menu(_)
|
||||
| Message::Connection(_)
|
||||
| Message::PortAnchor(_)
|
||||
| Message::Route(_)
|
||||
)
|
||||
{
|
||||
self.status = "Apply or cancel the behavior form before changing another view.".into();
|
||||
return Task::none();
|
||||
}
|
||||
let previous = self.selected;
|
||||
let previous_layer = self.layer;
|
||||
let entering_catalog =
|
||||
@@ -1297,14 +1366,22 @@ impl App {
|
||||
}
|
||||
Message::InspectorTab(tab) => self.inspector_tab = tab,
|
||||
Message::Behavior(event) => {
|
||||
if event.is_submit() && !self.apply() {
|
||||
if event.starts_form() && !self.behavior.has_pending() && !self.apply() {
|
||||
return Task::none();
|
||||
}
|
||||
if event.is_submit() && (self.source_dirty() || self.draft_dirty()) {
|
||||
self.status = "Revert the other drafts before applying the behavior form. Your inputs are retained.".into();
|
||||
return Task::none();
|
||||
}
|
||||
let mut state = std::mem::take(&mut self.behavior);
|
||||
let result = state.update(self.project(), event);
|
||||
self.behavior = state;
|
||||
match result {
|
||||
Ok(Some(command)) => return self.handle(Message::Command(command)),
|
||||
Ok(Some(command)) => {
|
||||
if self.execute(command) {
|
||||
self.behavior.acknowledge_commit();
|
||||
}
|
||||
}
|
||||
Ok(None) => {}
|
||||
Err(error) => self.status = error,
|
||||
}
|
||||
@@ -1440,6 +1517,9 @@ impl App {
|
||||
}
|
||||
}
|
||||
Message::ConfirmDelete => {
|
||||
if !self.delete_pending || self.behavior.has_pending() {
|
||||
return Task::none();
|
||||
}
|
||||
self.delete_pending = false;
|
||||
self.execute(Command::Delete(self.selected));
|
||||
}
|
||||
@@ -1858,7 +1938,7 @@ impl App {
|
||||
return self.handle(Message::Save(false));
|
||||
}
|
||||
Message::Save(save_as) => {
|
||||
if !self.apply_source() || !self.apply() {
|
||||
if !self.apply_behavior() || !self.apply_source() || !self.apply() {
|
||||
self.continue_after_save = false;
|
||||
self.pending = None;
|
||||
return Task::none();
|
||||
|
||||
Reference in New Issue
Block a user