2024-09-13 07:07:49 +00:00
|
|
|
use itertools::Itertools;
|
2024-09-12 22:24:41 +00:00
|
|
|
use sycamore::{prelude::*, web::tags::div};
|
2024-09-23 06:55:07 +00:00
|
|
|
use web_sys::{Element, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast};
|
2024-09-12 22:24:41 +00:00
|
|
|
|
2024-09-13 22:15:55 +00:00
|
|
|
use crate::AppState;
|
2024-09-12 22:24:41 +00:00
|
|
|
|
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/
|
|
|
|
//
|
2024-09-12 22:24:41 +00:00
|
|
|
#[component]
|
2024-09-13 22:15:55 +00:00
|
|
|
pub fn Outline() -> View {
|
2024-09-13 07:07:49 +00:00
|
|
|
// sort the elements alphabetically by ID
|
2024-09-19 23:08:55 +00:00
|
|
|
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()
|
2024-09-22 09:38:17 +00:00
|
|
|
.sorted_by_key(|(_, elt)| elt.id.clone())
|
2024-09-13 07:07:49 +00:00
|
|
|
.collect()
|
2024-09-19 23:08:55 +00:00
|
|
|
});
|
2024-09-13 07:07:49 +00:00
|
|
|
|
2024-09-12 22:24:41 +00:00
|
|
|
view! {
|
2024-09-20 00:53:07 +00:00
|
|
|
ul(
|
2024-09-22 21:05:40 +00:00
|
|
|
id="outline",
|
2024-09-20 00:53:07 +00:00
|
|
|
on:click={
|
|
|
|
let state = use_context::<AppState>();
|
|
|
|
move |_| state.selection.update(|sel| sel.clear())
|
|
|
|
}
|
|
|
|
) {
|
2024-09-12 22:24:41 +00:00
|
|
|
Keyed(
|
2024-09-13 07:07:49 +00:00
|
|
|
list=elements_sorted,
|
2024-09-22 09:38:17 +00:00
|
|
|
view=|(key, elt)| {
|
2024-09-19 23:08:55 +00:00
|
|
|
let state = use_context::<AppState>();
|
|
|
|
let class = create_memo({
|
|
|
|
move || {
|
2024-09-22 09:38:17 +00:00
|
|
|
if state.selection.with(|sel| sel.contains(&key)) {
|
2024-09-23 06:55:07 +00:00
|
|
|
"selected"
|
2024-09-19 23:08:55 +00:00
|
|
|
} else {
|
2024-09-23 06:55:07 +00:00
|
|
|
""
|
2024-09-19 23:08:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
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<_>>();
|
2024-09-23 06:55:07 +00:00
|
|
|
let constrained = elt.constraints.len() > 0;
|
|
|
|
let details_node = create_node_ref();
|
2024-09-12 22:24:41 +00:00
|
|
|
view! {
|
2024-09-13 21:53:12 +00:00
|
|
|
/* [TO DO] switch to integer-valued parameters whenever
|
|
|
|
that becomes possible again */
|
2024-09-22 21:05:40 +00:00
|
|
|
li {
|
2024-09-23 06:55:07 +00:00
|
|
|
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
|
|
|
}
|
2024-09-22 21:05:40 +00:00
|
|
|
}
|
2024-09-23 06:55: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();
|
2024-09-22 21:05:40 +00:00
|
|
|
sel.insert(key);
|
2024-09-23 06:55:07 +00:00
|
|
|
});
|
|
|
|
}
|
|
|
|
event.stop_propagation();
|
|
|
|
event.prevent_default();
|
2024-09-22 21:05:40 +00:00
|
|
|
}
|
|
|
|
}
|
2024-09-23 06:55:07 +00:00
|
|
|
) {
|
|
|
|
div(class="elt-label") { (label) }
|
|
|
|
div(class="elt-rep") { (rep_components) }
|
2024-09-19 23:08:55 +00:00
|
|
|
}
|
2024-09-16 18:29:44 +00:00
|
|
|
}
|
2024-09-23 06:55:07 +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) }
|
|
|
|
}
|
2024-09-23 06:55:07 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
key=|c_key| c_key.clone()
|
|
|
|
)
|
|
|
|
}
|
2024-09-16 18:29:44 +00:00
|
|
|
}
|
2024-09-12 22:24:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
2024-09-27 02:10:34 +00:00
|
|
|
key=|(key, elt)| (key.clone(), elt.constraints.clone())
|
2024-09-12 22:24:41 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|