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::(); 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::(); move |_| state.selection.update(|sel| sel.clear()) } ) { Keyed( list=elements_sorted, view=|(key, elt)| { let state = use_context::(); 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::>(); 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::() .set_attribute("open", ""); }, "ArrowLeft" => { let _ = details_node .get() .unchecked_into::() .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::>(), view=move |c_key: usize| { let c_state = use_context::(); 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, _)| key.clone() ) } } }