ui: properties and status lines in words, current card on top, Use selection makes its set on OK, marks on the left (#335, #333, #360, #337, #315, #361)

- describe.rs: a definition's fields in its form's labels and units (sets by
  members and what they resolve to), for the context panel.
- reply::done: status lines that say what changed ("Showing Load deformed",
  "Mode 2: 1637 Hz", "Added material Al").
- The context panel puts the form and the selection above reports; opening
  another document clears both; what a form makes is selected.
- Use selection reuses a set with the same members, otherwise names one made
  on OK; Cancel leaves the model unchanged. The Target's set row is labelled.
- The browser's state marks sit in one column at its left edge; a right click
  selects the row its menu acts on.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UhhZMBTviDQxZvwnyP89fo
This commit is contained in:
jyvikoandClaude Opus 5.5 authored and Irakli Kourtis committed 2026-10-15 08:55:25 -04:00
1 parent b65f1696b2
commit a53c79cb55
8 files changed
+513 -39

No files matched your search

+59
View File
@@ -78,6 +78,65 @@ pub fn summary(command: &str, v: &Value) -> String {
if line.is_empty() { "done".into() } else { one_line(&line) }
}
/// The status line after `command` changed the model or the display, written for the person
/// rather than from the command's label: "Showing Load deformed ×2", "Mode 2: 1637 Hz", "Added
/// load Push". Commands without a sentence of their own say "<label> done".
pub fn done(command: &str, label: &str, params: &Value, v: &Value) -> String {
let view = v.get("view").filter(|x| x.is_object());
let step = view.and_then(|x| x["step"].as_str()).unwrap_or("the step");
let line = match command {
"results.deformed" if view.is_some_and(|x| x["deformed"] == false) => Some(format!("Showing {step} undeformed")),
"results.deformed" => {
let scale = view.and_then(|x| x["scale"].as_f64()).map(|f| format!(" ×{}", crate::describe::quantity(f))).unwrap_or_default();
Some(format!("Showing {step} deformed{scale}"))
}
"results.contour" => Some(match v.pointer("/contour/label").and_then(Value::as_str) {
Some(field) => format!("Showing {field} on {step}"),
None => format!("Contours off on {step}"),
}),
"results.modes" => {
let mode = view.and_then(|x| x["mode"].as_u64()).unwrap_or(1);
let hz = v
.get("modes")
.and_then(Value::as_array)
.and_then(|m| m.iter().find(|m| m["mode"].as_u64() == Some(mode)))
.and_then(|m| m["frequency"].as_f64());
Some(match hz {
Some(hz) => format!("Mode {mode}: {} Hz", crate::describe::quantity(hz)),
None => format!("Showing mode {mode} of {step}"),
})
}
"results.animate" => Some(if view.is_some_and(|x| x["animate"] == true) { "Animating" } else { "Animation stopped" }.to_string()),
"results.frame" => Some(match view.and_then(|x| x["frame"].as_u64()) {
Some(f) => format!("Showing {step} frame {f}"),
None => format!("Showing {step}, last frame"),
}),
"results.clear" => Some("Back to the model".to_string()),
"solve.run" => v.get("steps").and_then(Value::as_array).map(|s| format!("Solved {}", plural(s.len() as u64, "step"))),
c if c.starts_with("solve.") => Some(format!("Solved {step}", step = params["step"].as_str().unwrap_or("the step"))),
_ => None,
};
if let Some(line) = line {
return line;
}
// A definition added, changed or deleted, by its kind and name.
let name = params.get("name").and_then(Value::as_str);
let def = Def::ALL
.into_iter()
.find_map(|kind| ["create", "edit", "delete"].into_iter().find(|verb| kind.command(verb) == command).map(|verb| (kind, verb)));
match (def, name) {
(Some((kind, verb)), Some(name)) => {
let did = match verb {
"create" => "Added",
"edit" => "Changed",
_ => "Deleted",
};
format!("{did} {} {name}", kind.label().to_lowercase())
}
_ => format!("{label} done"),
}
}
/// Whether the reply says more than its summary line, so it is worth the panel.
pub fn is_rich(v: &Value) -> bool {
match v {