use itertools::Itertools; use sycamore::prelude::*; use web_sys::{ Event, HtmlInputElement, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast }; use crate::{ AppState, assembly, assembly::{ Observable, ObservableKey, ObservableRole::*, ElementKey } }; // an editable view of the Lorentz product representing an observable #[component(inline_props)] fn ObservableInput(observable: Observable) -> View { view! { input( r#type="text", placeholder=observable.measured.with(|result| result.to_string()), bind:value=observable.desired_text, on:change=move |event: Event| { let target: HtmlInputElement = event.target().unwrap().unchecked_into(); let value = target.value(); if value.is_empty() { observable.role.set(Measure); } else { match target.value().parse::() { Ok(desired) => batch(|| { observable.desired.set(desired); observable.role.set(Constrain); }), Err(_) => observable.role.set(Invalid) }; } } ) } } // a list item that shows an observable in an outline view of an element #[component(inline_props)] fn ObservableOutlineItem(observable_key: ObservableKey, element_key: ElementKey) -> View { let state = use_context::(); let assembly = &state.assembly; let observable = assembly.observables.with(|obsls| obsls[observable_key].clone()); let other_subject = if observable.subjects.0 == element_key { observable.subjects.1 } else { observable.subjects.0 }; let other_subject_label = assembly.elements.with(|elts| elts[other_subject].label.clone()); let class = observable.role.map( |role| match role { Measure => "observable", Constrain => "observable constrained", Invalid => "observable invalid" } ); view! { li(class=class.get()) { div(class="observable-label") { (other_subject_label) } ObservableInput(observable=observable) 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 observed = element.observables.map(|obsls| obsls.len() > 0); let observable_list = element.observables.map( |obsls| obsls.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 observed.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="observables") { Keyed( list=observable_list, view=move |obs_key| view! { ObservableOutlineItem( observable_key=obs_key, element_key=key ) }, key=|obs_key| obs_key.clone() ) } } } } } // a component that lists the elements of the current assembly, showing the // observables associated with 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 ) } } }