This tells Sycamore that the outline view of an element should update when the element's constraint set has changed. To make the constraint set hashable, so we can include it in the diff key, we store it as a `BTreeSet` instead of an `FxHashSet`.
155 lines
No EOL
7.9 KiB
Rust
155 lines
No EOL
7.9 KiB
Rust
use itertools::Itertools;
|
|
use sycamore::{prelude::*, web::tags::div};
|
|
use web_sys::{Element, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast};
|
|
|
|
use crate::AppState;
|
|
|
|
// 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 {
|
|
// sort the elements alphabetically by ID
|
|
let elements_sorted = create_memo(|| {
|
|
let state = use_context::<AppState>();
|
|
state.assembly.elements
|
|
.get_clone()
|
|
.into_iter()
|
|
.sorted_by_key(|(_, elt)| elt.id.clone())
|
|
.collect()
|
|
});
|
|
|
|
view! {
|
|
ul(
|
|
id="outline",
|
|
on:click={
|
|
let state = use_context::<AppState>();
|
|
move |_| state.selection.update(|sel| sel.clear())
|
|
}
|
|
) {
|
|
Keyed(
|
|
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 {
|
|
""
|
|
}
|
|
}
|
|
});
|
|
let label = elt.label.clone();
|
|
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! {
|
|
/* [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");
|
|
},
|
|
_ => ()
|
|
}
|
|
}
|
|
}
|
|
) {
|
|
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) }
|
|
}
|
|
}
|
|
ul(class="constraints") {
|
|
Keyed(
|
|
list=elt.constraints.into_iter().collect::<Vec<_>>(),
|
|
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()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
key=|(key, elt)| (key.clone(), elt.constraints.clone())
|
|
)
|
|
}
|
|
}
|
|
} |