dyna3/app-proto/full-interface/src/outline.rs

155 lines
7.9 KiB
Rust
Raw Normal View History

2024-09-13 07:07:49 +00:00
use itertools::Itertools;
use sycamore::{prelude::*, web::tags::div};
use web_sys::{Element, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast};
use crate::AppState;
2024-09-23 07:39:14 +00:00
// this component lists the elements of the assembly, showing the constraints
// on each element as a collapsible sub-list. its implementation is based on
// Kate Morley's HTML + CSS tree views:
//
// https://iamkate.com/code/tree-views/
//
#[component]
pub fn Outline() -> View {
2024-09-13 07:07:49 +00:00
// sort the elements alphabetically by ID
let elements_sorted = create_memo(|| {
let state = use_context::<AppState>();
2024-09-13 07:40:34 +00:00
state.assembly.elements
2024-09-13 07:07:49 +00:00
.get_clone()
.into_iter()
.sorted_by_key(|(_, elt)| elt.id.clone())
2024-09-13 07:07:49 +00:00
.collect()
});
2024-09-13 07:07:49 +00:00
view! {
2024-09-20 00:53:07 +00:00
ul(
id="outline",
2024-09-20 00:53:07 +00:00
on:click={
let state = use_context::<AppState>();
move |_| state.selection.update(|sel| sel.clear())
}
) {
Keyed(
2024-09-13 07:07:49 +00:00
list=elements_sorted,
view=|(key, elt)| {
let state = use_context::<AppState>();
let class = create_memo({
move || {
if state.selection.with(|sel| sel.contains(&key)) {
"selected"
} else {
""
}
}
});
2024-09-13 05:36:54 +00:00
let label = elt.label.clone();
2024-09-13 07:43:19 +00:00
let rep_components = elt.rep.iter().map(|u| {
let u_coord = u.to_string().replace("-", "\u{2212}");
View::from(div().children(u_coord))
}).collect::<Vec<_>>();
let constrained = elt.constraints.len() > 0;
let details_node = create_node_ref();
view! {
2024-09-13 21:53:12 +00:00
/* [TO DO] switch to integer-valued parameters whenever
that becomes possible again */
li {
details(ref=details_node) {
summary(
class=class.get(),
on:keydown={
move |event: KeyboardEvent| {
match event.key().as_str() {
"Enter" => {
if event.shift_key() {
state.selection.update(|sel| {
if !sel.remove(&key) {
sel.insert(key);
}
});
} else {
state.selection.update(|sel| {
sel.clear();
sel.insert(key);
});
}
event.prevent_default();
},
"ArrowRight" if constrained => {
let _ = details_node
.get()
.unchecked_into::<Element>()
.set_attribute("open", "");
},
"ArrowLeft" => {
let _ = details_node
.get()
.unchecked_into::<Element>()
.remove_attribute("open");
},
_ => ()
}
2024-09-20 00:53:07 +00:00
}
}
) {
div(
class="elt-switch",
on:click=|event: MouseEvent| event.stop_propagation()
)
div(
class="elt",
on:click={
move |event: MouseEvent| {
if event.shift_key() {
state.selection.update(|sel| {
if !sel.remove(&key) {
sel.insert(key);
}
});
} else {
state.selection.update(|sel| {
sel.clear();
sel.insert(key);
});
}
event.stop_propagation();
event.prevent_default();
}
}
) {
div(class="elt-label") { (label) }
div(class="elt-rep") { (rep_components) }
}
2024-09-16 18:29:44 +00:00
}
ul(class="constraints") {
Keyed(
list=elt.constraints.into_iter().collect::<Vec<_>>(),
2024-09-23 07:39:14 +00:00
view=move |c_key: usize| {
let c_state = use_context::<AppState>();
let assembly = &c_state.assembly;
let cst = assembly.constraints.with(|csts| csts[c_key].clone());
let other_arg = if cst.args.0 == key {
cst.args.1
} else {
cst.args.0
};
let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
view! {
li(class="cst") {
div(class="cst-label") { (other_arg_label) }
div(class="cst-rep") { (cst.rep) }
}
}
},
key=|c_key| c_key.clone()
)
}
2024-09-16 18:29:44 +00:00
}
}
}
},
key=|(key, elt)| (key.clone(), elt.constraints.clone())
)
}
}
}