use itertools::Itertools; use sycamore::prelude::*; use web_sys::{ KeyboardEvent, MouseEvent, wasm_bindgen::JsCast }; use crate::{ AppState, assembly, assembly::{ ElementKey, Regulator, RegulatorKey }, specified::SpecifiedValue }; // an editable view of a regulator #[component(inline_props)] fn RegulatorInput(regulator: Regulator) -> View { let valid = create_signal(true); let value = create_signal( regulator.set_point.with_untracked(|set_pt| set_pt.spec.clone()) ); // this closure resets the input value to the regulator's set point // specification let reset_value = move || { batch(|| { valid.set(true); value.set(regulator.set_point.with(|set_pt| set_pt.spec.clone())); }) }; // reset the input value whenever the regulator's set point specification // is updated create_effect(reset_value); view! { input( r#type="text", class=move || { if valid.get() { regulator.set_point.with(|set_pt| { if set_pt.is_present() { "regulator-input constraint" } else { "regulator-input" } }) } else { "regulator-input invalid" } }, placeholder=regulator.measurement.with(|result| result.to_string()), bind:value=value, on:change=move |_| { valid.set( match SpecifiedValue::try_from(value.get_clone_untracked()) { Ok(set_pt) => { regulator.set_point.set(set_pt); true } Err(_) => false } ) }, on:keydown={ move |event: KeyboardEvent| { match event.key().as_str() { "Escape" => reset_value(), _ => () } } } ) } } // a list item that shows a regulator in an outline view of an element #[component(inline_props)] fn RegulatorOutlineItem(regulator_key: RegulatorKey, element_key: ElementKey) -> View { let state = use_context::(); let assembly = &state.assembly; let regulator = assembly.regulators.with(|regs| regs[regulator_key]); let other_subject = if regulator.subjects.0 == element_key { regulator.subjects.1 } else { regulator.subjects.0 }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); view! { li(class="regulator") { div(class="regulator-label") { (other_subject_label) } div(class="regulator-type") { "Inversive distance" } RegulatorInput(regulator=regulator) 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 = move || { element.representation.with( |rep| rep.iter().map( |u| { let u_str = format!("{:.3}", u).replace("-", "\u{2212}"); view! { div { (u_str) } } } ).collect::>() ) }; let regulated = element.regulators.map(|regs| regs.len() > 0); let regulator_list = element.regulators.map( |regs| regs.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 regulated.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") { (rep_components) } div(class="status") } } ul(class="regulators") { Keyed( list=regulator_list, view=move |reg_key| view! { RegulatorOutlineItem( regulator_key=reg_key, element_key=key ) }, key=|reg_key| reg_key.clone() ) } } } } } // a component that lists the elements of the current assembly, showing each // element's regulators in 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 ) } } }