Files
ARCADIA-Studio/crates/oa-desktop/src/forms/context_fields.rs
T
Codex e2ecfea69b
Rust checks / check (ubuntu-latest) (push) Canceled after 0s
Rust checks / check (windows-latest) (push) Canceled after 0s
Checkpoint source 7 workflows and in-progress diagram authoring redesign
Preserve current core, source, REST, desktop, recovery, documentation and validation work, including AGENTS.md and the development summary. This is a work-in-progress checkpoint: the new compact palette has outstanding compilation errors and native acceptance; earlier passing gates retain their documented scope.
2026-09-10 10:06:55 -07:00

264 lines
11 KiB
Rust

//! Qualified editors share core's resolver, including incomplete leaf selection.
use super::*;
use iced::widget::column;
use oa_core::topology_context::TopologyIndex;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Purpose {
Precedence,
Exchange,
CommonWork,
}
impl std::fmt::Display for Purpose {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Precedence => "Precedence",
Self::Exchange => "Exchange assembly",
Self::CommonWork => "Common work incidence",
})
}
}
fn choice(p: &Project, id: Id) -> crate::app::Choice {
crate::app::Choice {
id,
label: p
.elements
.get(&id)
.map(|e| format!("{} (@{})", e.name, e.alias))
.or_else(|| {
p.relations
.get(&id)
.map(|r| format!("{} (@{})", r.name, r.alias))
})
.unwrap_or_else(|| format!("Unavailable {id}")),
}
}
pub(super) fn property<'a>(
draft: &'a Draft,
p: &'a Project,
value: &'a Value,
path: &str,
) -> Option<View<'a, Message>> {
if !path.contains("/ContextualLink/") {
return None;
}
let relation: Relation = serde_json::from_value(draft.context_value())
.ok()
.or_else(|| p.relations.get(&draft.original).cloned())?;
let RelationBody::ContextualLink { owner, .. } = relation.body else {
return None;
};
if path.ends_with("/source/occurrence") || path.ends_with("/target/occurrence") {
let parent = path.strip_suffix("/occurrence")?;
let endpoint: OccurrenceEndpoint =
serde_json::from_value(draft.context_value().pointer(parent)?.clone()).ok()?;
let index = TopologyIndex::new(p);
let scope = match index.resolve_scope(owner, &endpoint.references) {
Ok(scope) => scope,
Err(error) => return Some(text(error.to_string()).size(12).into()),
};
let choices = index
.contributions(scope)
.into_iter()
.map(|id| choice(p, id))
.collect::<Vec<_>>();
let selected = (!endpoint.occurrence.is_nil()).then(|| choice(p, endpoint.occurrence));
return Some(
column![
text("Only the leaf changes; the caller and complete reference path remain fixed.")
.size(11),
pick_list(choices, selected, {
let path = path.to_owned();
move |choice| Message::FieldShape(path.clone(), json!(choice.id))
})
.padding(8)
.text_size(12)
.width(Fill)
]
.spacing(6)
.into(),
);
}
if path.ends_with("/source") || path.ends_with("/target") {
let endpoint: OccurrenceEndpoint = serde_json::from_value(value.clone()).ok()?;
let index = TopologyIndex::new(p);
let mut out = column![
text(crate::behavior::topology::contextual::address_label(
p, owner, &endpoint
))
.size(12)
]
.spacing(6);
match index.resolve_scope(owner, &endpoint.references) {
Ok(scope) => {
let choices = index
.contributions(scope)
.into_iter()
.map(|id| choice(p, id))
.collect::<Vec<_>>();
let selected =
(!endpoint.occurrence.is_nil()).then(|| choice(p, endpoint.occurrence));
out = out.push(
pick_list(choices, selected, {
let path = path.to_owned();
let references = endpoint.references.clone();
move |choice| {
Message::FieldShape(
path.clone(),
json!(OccurrenceEndpoint {
references: references.clone(),
occurrence: choice.id
}),
)
}
})
.placeholder("Choose a contribution in this context…")
.padding(8)
.text_size(12)
.width(Fill),
);
if index.resolve(owner, &endpoint).is_ok()
&& matches!(
p.elements
.get(&endpoint.occurrence)
.and_then(|e| e.shared()),
Some(SharedBody::Occurrence {
body: OccurrenceBody::SequenceReference { sequence: Some(_) },
..
})
)
{
let mut references = endpoint.references.clone();
references.push(endpoint.occurrence);
out = out.push(button("Enter selected reference").on_press(
Message::FieldShape(
path.to_owned(),
json!(OccurrenceEndpoint {
references,
occurrence: Id::nil()
}),
),
));
}
}
Err(error) => out = out.push(text(error.to_string()).size(12)),
}
if let Some(reference) = endpoint.references.last() {
let mut references = endpoint.references.clone();
references.pop();
out = out.push(button("Up to caller context").on_press(Message::FieldShape(
path.to_owned(),
json!(OccurrenceEndpoint {
references,
occurrence: *reference
}),
)));
}
return Some(out.push(text("Entering a reference retains an unfinished endpoint until you choose its contribution. Apply checks the complete caller path.").size(11)).into());
}
if path.ends_with("/owner") {
let mut choices = p
.elements
.values()
.filter(|e| matches!(e.kind(), Kind::Process | Kind::FunctionalChain))
.map(|e| choice(p, e.id))
.collect::<Vec<_>>();
choices.sort_by(|a, b| a.label.cmp(&b.label).then(a.id.cmp(&b.id)));
return Some(
pick_list(choices, Some(choice(p, owner)), {
let path = path.to_owned();
move |choice| Message::FieldShape(path.clone(), json!(choice.id))
})
.padding(8)
.text_size(12)
.width(Fill)
.into(),
);
}
if path.ends_with("/purpose") {
let purpose: ContextualLinkPurpose = serde_json::from_value(value.clone()).ok()?;
let selected = match purpose {
ContextualLinkPurpose::Precedence { .. } => Purpose::Precedence,
ContextualLinkPurpose::Exchange { .. } => Purpose::Exchange,
ContextualLinkPurpose::CommonWork { .. } => Purpose::CommonWork,
};
let mut out = column![
pick_list(
vec![Purpose::Precedence, Purpose::Exchange, Purpose::CommonWork],
Some(selected),
{
let path = path.to_owned();
move |purpose| {
Message::FieldVariant(
path.clone(),
match purpose {
Purpose::Precedence => json!(ContextualLinkPurpose::Precedence {
guard: String::new()
}),
Purpose::Exchange => json!(ContextualLinkPurpose::Exchange {
exchange: Id::nil()
}),
Purpose::CommonWork => {
json!(ContextualLinkPurpose::CommonWork { work: Id::nil() })
}
},
)
}
}
)
.padding(8)
.text_size(12)
.width(Fill)
]
.spacing(7);
match purpose {
ContextualLinkPurpose::Precedence { .. } => {
let path = format!("{path}/Precedence/guard");
let raw = draft
.inputs
.get(&path)
.map(|input| input.raw.as_str())
.unwrap_or("");
out = out.push(
text_input("Precedence guard", raw)
.on_input(move |raw| Message::Field(path.clone(), raw))
.padding(8)
.size(12),
);
}
purpose => {
let (selected, choices, leaf) = match purpose {
ContextualLinkPurpose::Exchange { exchange } => (
exchange,
p.relations
.values()
.filter(|r| {
matches!(
r.kind(),
RelationKind::Interaction | RelationKind::FunctionalExchange
) && p.layer_of(r.id) == p.layer_of(owner)
})
.map(|r| choice(p, r.id))
.collect::<Vec<_>>(),
"Exchange/exchange",
),
ContextualLinkPurpose::CommonWork { work } => (
work,
p.elements
.values()
.filter(|e| e.kind().is_work() && Some(e.layer()) == p.layer_of(owner))
.map(|e| choice(p, e.id))
.collect::<Vec<_>>(),
"CommonWork/work",
),
_ => unreachable!(),
};
let path = format!("{path}/{leaf}");
out=out.push(pick_list(choices,(!selected.is_nil()).then(||choice(p,selected)),move|choice|Message::FieldShape(path.clone(),json!(choice.id))).placeholder("Choose exact engineering identity…").padding(8).text_size(12).width(Fill)).push(text("Assembly incidence does not imply execution order or merge executions.").size(11));
}
}
return Some(out.into());
}
None
}