forked from StudioInfinity/dyna3

Since we're no longer using storage keys to refer to elements and regulators, we don't need to store these items in keyed collections anymore. To keep element and regulator pointers in `BTreeSet` collections, we implement `Ord` for `Serial` trait objects. As a bonus, this lets us turn the element-wise regulator collections back into `BTreeSet`.
260 lines
No EOL
8.6 KiB
Rust
260 lines
No EOL
8.6 KiB
Rust
use itertools::Itertools;
|
|
use std::rc::Rc;
|
|
use sycamore::prelude::*;
|
|
use web_sys::{
|
|
KeyboardEvent,
|
|
MouseEvent,
|
|
wasm_bindgen::JsCast
|
|
};
|
|
|
|
use crate::{
|
|
AppState,
|
|
assembly::{
|
|
Element,
|
|
HalfCurvatureRegulator,
|
|
InversiveDistanceRegulator,
|
|
Regulator
|
|
},
|
|
specified::SpecifiedValue
|
|
};
|
|
|
|
// an editable view of a regulator
|
|
#[component(inline_props)]
|
|
fn RegulatorInput(regulator: Rc<dyn Regulator>) -> View {
|
|
// get the regulator's measurement and set point signals
|
|
let measurement = regulator.measurement();
|
|
let set_point = regulator.set_point();
|
|
|
|
// the `valid` signal tracks whether the last entered value is a valid set
|
|
// point specification
|
|
let valid = create_signal(true);
|
|
|
|
// the `value` signal holds the current set point specification
|
|
let value = create_signal(
|
|
set_point.with_untracked(|set_pt| set_pt.spec.clone())
|
|
);
|
|
|
|
// this `reset_value` closure resets the input value to the regulator's set
|
|
// point specification
|
|
let reset_value = move || {
|
|
batch(|| {
|
|
valid.set(true);
|
|
value.set(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() {
|
|
set_point.with(|set_pt| {
|
|
if set_pt.is_present() {
|
|
"regulator-input constraint"
|
|
} else {
|
|
"regulator-input"
|
|
}
|
|
})
|
|
} else {
|
|
"regulator-input invalid"
|
|
}
|
|
},
|
|
placeholder=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) => {
|
|
set_point.set(set_pt);
|
|
true
|
|
}
|
|
Err(_) => false
|
|
}
|
|
)
|
|
},
|
|
on:keydown={
|
|
move |event: KeyboardEvent| {
|
|
match event.key().as_str() {
|
|
"Escape" => reset_value(),
|
|
_ => ()
|
|
}
|
|
}
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
pub trait OutlineItem {
|
|
fn outline_item(self: Rc<Self>, element: &Rc<dyn Element>) -> View;
|
|
}
|
|
|
|
impl OutlineItem for InversiveDistanceRegulator {
|
|
fn outline_item(self: Rc<Self>, element: &Rc<dyn Element>) -> View {
|
|
let other_subject_label = if self.subjects[0] == element.clone() {
|
|
self.subjects[1].label()
|
|
} else {
|
|
self.subjects[0].label()
|
|
}.clone();
|
|
view! {
|
|
li(class="regulator") {
|
|
div(class="regulator-label") { (other_subject_label) }
|
|
div(class="regulator-type") { "Inversive distance" }
|
|
RegulatorInput(regulator=self)
|
|
div(class="status")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl OutlineItem for HalfCurvatureRegulator {
|
|
fn outline_item(self: Rc<Self>, _element: &Rc<dyn Element>) -> View {
|
|
view! {
|
|
li(class="regulator") {
|
|
div(class="regulator-label") // for spacing
|
|
div(class="regulator-type") { "Half-curvature" }
|
|
RegulatorInput(regulator=self)
|
|
div(class="status")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// a list item that shows an element in an outline view of an assembly
|
|
#[component(inline_props)]
|
|
fn ElementOutlineItem(element: Rc<dyn Element>) -> View {
|
|
let state = use_context::<AppState>();
|
|
let class = {
|
|
let element_for_class = element.clone();
|
|
state.selection.map(
|
|
move |sel| if sel.contains(&element_for_class) { "selected" } else { "" }
|
|
)
|
|
};
|
|
let label = element.label().clone();
|
|
let representation = element.representation().clone();
|
|
let rep_components = move || {
|
|
representation.with(
|
|
|rep| rep.iter().map(
|
|
|u| {
|
|
let u_str = format!("{:.3}", u).replace("-", "\u{2212}");
|
|
view! { div { (u_str) } }
|
|
}
|
|
).collect::<Vec<_>>()
|
|
)
|
|
};
|
|
let regulated = element.regulators().map(|regs| regs.len() > 0);
|
|
let regulator_list = element.regulators().map(
|
|
|regs| regs
|
|
.clone()
|
|
.into_iter()
|
|
.sorted_by_key(|reg| reg.subjects().len())
|
|
.collect()
|
|
);
|
|
let details_node = create_node_ref();
|
|
view! {
|
|
li {
|
|
details(ref=details_node) {
|
|
summary(
|
|
class=class.get(),
|
|
on:keydown={
|
|
let element_for_handler = element.clone();
|
|
move |event: KeyboardEvent| {
|
|
match event.key().as_str() {
|
|
"Enter" => {
|
|
state.select(&element_for_handler, event.shift_key());
|
|
event.prevent_default();
|
|
},
|
|
"ArrowRight" if regulated.get() => {
|
|
let _ = details_node
|
|
.get()
|
|
.unchecked_into::<web_sys::Element>()
|
|
.set_attribute("open", "");
|
|
},
|
|
"ArrowLeft" => {
|
|
let _ = details_node
|
|
.get()
|
|
.unchecked_into::<web_sys::Element>()
|
|
.remove_attribute("open");
|
|
},
|
|
_ => ()
|
|
}
|
|
}
|
|
}
|
|
) {
|
|
div(
|
|
class="element-switch",
|
|
on:click=|event: MouseEvent| event.stop_propagation()
|
|
)
|
|
div(
|
|
class="element",
|
|
on:click={
|
|
let state_for_handler = state.clone();
|
|
let element_for_handler = element.clone();
|
|
move |event: MouseEvent| {
|
|
state_for_handler.select(&element_for_handler, event.shift_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| reg.outline_item(&element),
|
|
key=|reg| reg.serial()
|
|
)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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::<AppState>();
|
|
|
|
// list the elements alphabetically by ID
|
|
/* TO DO */
|
|
// this code is designed to generalize easily to other sort keys. if we only
|
|
// ever wanted to sort by ID, we could do that more simply using the
|
|
// `elements_by_id` index
|
|
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::<AppState>();
|
|
move |_| state.selection.update(|sel| sel.clear())
|
|
}
|
|
) {
|
|
Keyed(
|
|
list=element_list,
|
|
view=|elt| view! {
|
|
ElementOutlineItem(element=elt)
|
|
},
|
|
key=|elt| elt.serial()
|
|
)
|
|
}
|
|
}
|
|
} |