Complete source 6 engineering workflows and preserve release validation evidence
This commit is contained in:
+168
-22
@@ -1,5 +1,13 @@
|
||||
mod checkpoints;
|
||||
#[cfg(test)]
|
||||
mod diagram_selection_tests;
|
||||
mod hosted;
|
||||
#[cfg(test)]
|
||||
mod review_navigation_tests;
|
||||
#[cfg(test)]
|
||||
mod transition_context_tests;
|
||||
#[cfg(test)]
|
||||
mod view_copy_tests;
|
||||
use crate::forms::Draft;
|
||||
use crate::{
|
||||
document::Document,
|
||||
@@ -288,6 +296,7 @@ pub enum Pending {
|
||||
HostedOpen,
|
||||
New,
|
||||
Demo,
|
||||
Walkthrough,
|
||||
Open,
|
||||
Close(window::Id),
|
||||
}
|
||||
@@ -346,6 +355,7 @@ pub enum Message {
|
||||
InspectorTab(crate::navigation::InspectorTab),
|
||||
RevealInDiagram(Id, Id),
|
||||
AddDiagram,
|
||||
DuplicateDiagram(Id),
|
||||
AddArtifact,
|
||||
AddTrace,
|
||||
Field(String, String),
|
||||
@@ -354,6 +364,7 @@ pub enum Message {
|
||||
FieldAppend(String, Value),
|
||||
FieldValuePath(String, crate::data_values::FieldPathKey),
|
||||
FieldValueBrowse(String, Option<crate::data_values::FieldPathKey>, usize),
|
||||
FieldReferenceBrowse(String, String, usize),
|
||||
FieldVariant(String, Value),
|
||||
FieldArray(String, Option<usize>, i32),
|
||||
Apply,
|
||||
@@ -897,6 +908,44 @@ impl App {
|
||||
fn diagram_for(&self, layer: Layer, element: Id) -> Option<Id> {
|
||||
preferred_diagram(self.project(), self.active_diagram, layer, element)
|
||||
}
|
||||
/// Navigation after an accepted review is not another model operation.
|
||||
/// Callers retain their workspace policy; keep an already visible target's
|
||||
/// camera and never leave an old-layer diagram behind after counterpart focus.
|
||||
fn focus_after_review(&mut self, target: Id, destination: Option<Id>) -> bool {
|
||||
if !self.project().contains(target) {
|
||||
return false;
|
||||
}
|
||||
let previous = (self.layer, self.active_diagram);
|
||||
self.selected = target;
|
||||
self.selected_node = None;
|
||||
if let Some(layer) = self.project().layer_of(target) {
|
||||
let retained = self.active_diagram.filter(|id| {
|
||||
self.project().diagrams.get(id).is_some_and(|d| {
|
||||
d.layer == layer
|
||||
&& (d.nodes.values().any(|n| n.element == target)
|
||||
|| self.project().elements.get(&target).is_some_and(|e| {
|
||||
e.kind() == Kind::Port
|
||||
&& project_view(self.project(), *id)
|
||||
.ports
|
||||
.iter()
|
||||
.any(|port| port.element == target)
|
||||
}))
|
||||
})
|
||||
});
|
||||
self.layer = layer;
|
||||
self.active_diagram = destination
|
||||
.filter(|id| {
|
||||
self.project()
|
||||
.diagrams
|
||||
.get(id)
|
||||
.is_some_and(|d| d.layer == layer)
|
||||
})
|
||||
.or(retained)
|
||||
.or_else(|| self.diagram_for(layer, target));
|
||||
}
|
||||
self.refresh_draft();
|
||||
previous != (self.layer, self.active_diagram)
|
||||
}
|
||||
fn select(&mut self, id: Id) {
|
||||
if self.selected == id {
|
||||
return;
|
||||
@@ -1530,7 +1579,14 @@ impl App {
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
self.selected_bend = None;
|
||||
}
|
||||
Message::Guide(event) => self.guide.update(event),
|
||||
Message::Guide(event) => {
|
||||
if self.guide.update(event) {
|
||||
return iced::widget::operation::snap_to(
|
||||
crate::guide::ARTICLE_SCROLL_ID,
|
||||
iced::widget::scrollable::RelativeOffset::START,
|
||||
);
|
||||
}
|
||||
}
|
||||
Message::ExactStrings(enabled) => {
|
||||
if let Some(d) = &mut self.draft {
|
||||
match d.set_exact(enabled) {
|
||||
@@ -1575,6 +1631,22 @@ impl App {
|
||||
.insert(path, crate::forms::ValueBrowser { scope, page });
|
||||
}
|
||||
}
|
||||
Message::FieldReferenceBrowse(path, query, page) => {
|
||||
if let Some(draft) = &mut self.draft {
|
||||
let page = if draft
|
||||
.reference_browsers
|
||||
.get(&path)
|
||||
.is_some_and(|b| b.query == query)
|
||||
{
|
||||
page
|
||||
} else {
|
||||
0
|
||||
};
|
||||
draft
|
||||
.reference_browsers
|
||||
.insert(path, crate::forms::ReferenceBrowser { query, page });
|
||||
}
|
||||
}
|
||||
Message::FieldValuePath(path, key) => {
|
||||
if let Some(draft) = &mut self.draft
|
||||
&& let Err(error) = draft.value_path(&path, key)
|
||||
@@ -1799,13 +1871,11 @@ impl App {
|
||||
match state.command(self.project()) {
|
||||
Ok(command) => {
|
||||
if self.execute(command) {
|
||||
if let Some(target) = focus {
|
||||
self.selected = target;
|
||||
if let Some(layer) = self.project().layer_of(target)
|
||||
{
|
||||
self.layer = layer;
|
||||
}
|
||||
self.refresh_draft();
|
||||
if let Some(target) = focus
|
||||
&& self.focus_after_review(target, None)
|
||||
&& self.page == Page::Diagram
|
||||
{
|
||||
self.fit();
|
||||
}
|
||||
self.status = "Semantic review applied · model, source and baseline form one undoable change".into();
|
||||
return Task::none();
|
||||
@@ -1827,11 +1897,19 @@ impl App {
|
||||
&& matches!(&workflow, crate::workflow::Workflow::Ports(_));
|
||||
let transition_focus = match &workflow {
|
||||
crate::workflow::Workflow::Ports(state) => Some(state.focus()),
|
||||
crate::workflow::Workflow::Transition(t) => {
|
||||
t.plan.changes.first().map(|c| c.target)
|
||||
}
|
||||
crate::workflow::Workflow::Transition(t) => t
|
||||
.request
|
||||
.sources
|
||||
.first()
|
||||
.and_then(|source| t.plan.changes.iter().find(|c| c.source == *source))
|
||||
.map(|c| c.target),
|
||||
_ => None,
|
||||
};
|
||||
let behavior_transition = matches!(&workflow,
|
||||
crate::workflow::Workflow::Transition(t)
|
||||
if t.request.sources.first().is_some_and(|source|
|
||||
crate::behavior::selected_behavior(self.project(), *source).is_some()));
|
||||
let previous_behavior_context = self.state_view_identity();
|
||||
match workflow.update(self.project(), event) {
|
||||
Ok(Some(mut candidate)) => {
|
||||
let focus = transition_focus
|
||||
@@ -1849,25 +1927,33 @@ impl App {
|
||||
.find(|id| !self.project().traces.contains_key(id))
|
||||
.copied()
|
||||
});
|
||||
let behavior_owner = focus
|
||||
.filter(|_| behavior_transition)
|
||||
.and_then(|id| crate::behavior::selected_behavior(&candidate, id));
|
||||
let destination = focus.and_then(|id| {
|
||||
candidate.layer_of(id).and_then(|layer| {
|
||||
preferred_diagram(&candidate, self.active_diagram, layer, id)
|
||||
})
|
||||
});
|
||||
if let (Some(id), Some(diagram)) = (focus, destination) {
|
||||
if let (None, Some(id), Some(diagram)) =
|
||||
(behavior_owner, focus, destination)
|
||||
{
|
||||
reveal_port_owner(&mut candidate, diagram, id);
|
||||
}
|
||||
if self.execute(Command::Replace(candidate)) {
|
||||
if let Some(id) = focus {
|
||||
self.selected = id;
|
||||
if let Some(layer) = self.project().layer_of(id) {
|
||||
self.layer = layer;
|
||||
self.active_diagram =
|
||||
destination.or_else(|| self.diagram_for(layer, id));
|
||||
}
|
||||
self.refresh_draft();
|
||||
self.focus_after_review(id, destination);
|
||||
}
|
||||
if !stay_in_catalog {
|
||||
if let Some(owner) = behavior_owner {
|
||||
self.selected_node = None;
|
||||
self.behavior.active_owner = Some(owner);
|
||||
self.page = Page::Behavior;
|
||||
if self.reset_state_view_after_navigation(
|
||||
previous_behavior_context,
|
||||
) {
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
}
|
||||
} else if !stay_in_catalog {
|
||||
self.page = Page::Diagram;
|
||||
self.fit();
|
||||
}
|
||||
@@ -2105,6 +2191,7 @@ impl App {
|
||||
layer: self.layer,
|
||||
projection: ProjectionKind::Allocation,
|
||||
routes: Vec::new(),
|
||||
view_options: Default::default(),
|
||||
nodes: Default::default(),
|
||||
};
|
||||
if self.execute(Command::CreateDiagram(d)) {
|
||||
@@ -2113,6 +2200,39 @@ impl App {
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::DuplicateDiagram(source) => {
|
||||
if !self.project().diagrams.contains_key(&source) {
|
||||
self.status = "The saved view to duplicate no longer exists.".into();
|
||||
return Task::none();
|
||||
}
|
||||
let original = self.selected;
|
||||
if self.apply() {
|
||||
let source = if source == original {
|
||||
self.selected
|
||||
} else {
|
||||
source
|
||||
};
|
||||
match crate::view_copy::command(self.project(), source) {
|
||||
Ok(command) => {
|
||||
if self.execute(command) {
|
||||
self.active_diagram = Some(self.selected);
|
||||
self.layer = self.project().diagrams[&self.selected].layer;
|
||||
self.selected_node = None;
|
||||
self.selected_bend = None;
|
||||
self.page = Page::Diagram;
|
||||
self.canvas_epoch = self.canvas_epoch.wrapping_add(1);
|
||||
self.route_state_diagram_workspace();
|
||||
if self.page == Page::Diagram {
|
||||
self.fit();
|
||||
}
|
||||
self.status =
|
||||
"View copied; engineering elements are shared.".into();
|
||||
}
|
||||
}
|
||||
Err(error) => self.status = error,
|
||||
}
|
||||
}
|
||||
}
|
||||
Message::AddArtifact => {
|
||||
if self.apply() {
|
||||
let id = Id::new_v4();
|
||||
@@ -2206,11 +2326,28 @@ impl App {
|
||||
});
|
||||
}
|
||||
Message::Diagram(choice) => {
|
||||
// Reject a stale picker event before applying unrelated property input.
|
||||
if !self.project().diagrams.contains_key(&choice.id) {
|
||||
self.status = "Diagram no longer exists.".into();
|
||||
return Task::none();
|
||||
}
|
||||
let previous_diagram = self.active_diagram;
|
||||
let follow_view = previous_diagram == Some(self.selected);
|
||||
if self.apply() {
|
||||
self.active_diagram = Some(choice.id);
|
||||
if let Some(d) = self.project().diagrams.get(&choice.id) {
|
||||
// Apply may have changed the current diagram's UUID. A picker
|
||||
// event for that same view must follow its new identity.
|
||||
let diagram = if previous_diagram == Some(choice.id) {
|
||||
self.active_diagram.unwrap_or(choice.id)
|
||||
} else {
|
||||
choice.id
|
||||
};
|
||||
self.active_diagram = Some(diagram);
|
||||
if let Some(d) = self.project().diagrams.get(&diagram) {
|
||||
self.layer = d.layer;
|
||||
}
|
||||
if follow_view {
|
||||
self.selected = diagram;
|
||||
}
|
||||
self.selected_node = None;
|
||||
self.refresh_draft();
|
||||
self.fit();
|
||||
@@ -2841,6 +2978,7 @@ impl App {
|
||||
layer: self.layer,
|
||||
projection: preferred,
|
||||
routes: Vec::new(),
|
||||
view_options: Default::default(),
|
||||
nodes: Default::default(),
|
||||
}));
|
||||
diagram_target = Some(view);
|
||||
@@ -3009,6 +3147,13 @@ impl App {
|
||||
self.reset(Project::demo(), None);
|
||||
Task::none()
|
||||
}
|
||||
Pending::Walkthrough => match crate::home::walkthrough_path() {
|
||||
Ok(path) => self.load_from(path),
|
||||
Err(error) => {
|
||||
self.status = error;
|
||||
Task::none()
|
||||
}
|
||||
},
|
||||
Pending::Open => self.choose_file(FileOperation::Open),
|
||||
Pending::Close(id) => {
|
||||
self.retire_checkpoint();
|
||||
@@ -4440,6 +4585,7 @@ mod port_view_tests {
|
||||
layer: Layer::Sa,
|
||||
projection: ProjectionKind::Capabilities,
|
||||
routes: Vec::new(),
|
||||
view_options: Default::default(),
|
||||
nodes: Default::default(),
|
||||
},
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user