use itertools::Itertools; use sycamore::prelude::*; use web_sys::{ Event, HtmlInputElement, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast }; use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}}; // an editable view of the Lorentz product representing a constraint #[component(inline_props)] fn LorentzProductInput(constraint: Constraint) -> View { view! { input( r#type="text", bind:value=constraint.lorentz_prod_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); match target.value().parse::() { Ok(lorentz_prod) => batch(|| { constraint.lorentz_prod.set(lorentz_prod); constraint.lorentz_prod_valid.set(true); }), Err(_) => constraint.lorentz_prod_valid.set(false) }; } ) } } // a list item that shows a constraint in an outline view of an element #[component(inline_props)] fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey) -> View { let state = use_context::(); let assembly = &state.assembly; let constraint = assembly.constraints.with(|csts| csts[constraint_key].clone()); let other_subject = if constraint.subjects.0 == element_key { constraint.subjects.1 } else { constraint.subjects.0 }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); let class = constraint.lorentz_prod_valid.map( |&lorentz_prod_valid| if lorentz_prod_valid { "constraint" } else { "constraint invalid" } ); view! { li(class=class.get()) { input(r#type="checkbox", bind:checked=constraint.active) div(class="constraint-label") { (other_subject_label) } LorentzProductInput(constraint=constraint) div(class="status") } } } // a list item that shows an element in an outline view of an assembly #[component(inline_props)] fn ElementOutlineItem(key: ElementKey, element: assembly::Element) -> View { let state = use_context::(); let class = state.selection.map( move |sel| if sel.contains(&key) { "selected" } else { "" } ); let label = element.label.clone(); let rep_components = element.representation.map( |rep| rep.iter().map( |u| format!("{:.3}", u).replace("-", "\u{2212}") ).collect() ); let constrained = element.constraints.map(|csts| csts.len() > 0); let constraint_list = element.constraints.map( |csts| csts.clone().into_iter().collect() ); let details_node = create_node_ref(); view! { li { details(ref=details_node) { summary( class=class.get(), on:keydown={ move |event: KeyboardEvent| { match event.key().as_str() { "Enter" => { state.select(key, event.shift_key()); event.prevent_default(); }, "ArrowRight" if constrained.get() => { let _ = details_node .get() .unchecked_into::() .set_attribute("open", ""); }, "ArrowLeft" => { let _ = details_node .get() .unchecked_into::() .remove_attribute("open"); }, _ => () } } } ) { div( class="element-switch", on:click=|event: MouseEvent| event.stop_propagation() ) div( class="element", 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="element-label") { (label) } div(class="element-representation") { Indexed( list=rep_components, view=|coord_str| view! { div { (coord_str) } } ) } div(class="status") } } ul(class="constraints") { Keyed( list=constraint_list, view=move |cst_key| view! { ConstraintOutlineItem( constraint_key=cst_key, element_key=key ) }, key=|cst_key| cst_key.clone() ) } } } } } // a component that lists the elements of the current 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 { let state = use_context::(); // list the elements alphabetically by ID let element_list = state.assembly.elements.map( |elts| elts .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=element_list, view=|(key, elt)| view! { ElementOutlineItem(key=key, element=elt) }, key=|(_, elt)| elt.serial ) } } }