
This essentially reverts commit 894931a
and then inlines `try_set` in
the one place where it's used. If we ever want to reuse that code, we'll
factor it out again.
239 lines
No EOL
8.1 KiB
Rust
239 lines
No EOL
8.1 KiB
Rust
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::<AppState>();
|
|
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::<AppState>();
|
|
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::<Vec<_>>()
|
|
)
|
|
};
|
|
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::<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={
|
|
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::<AppState>();
|
|
|
|
// 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::<AppState>();
|
|
move |_| state.selection.update(|sel| sel.clear())
|
|
}
|
|
) {
|
|
Keyed(
|
|
list=element_list,
|
|
view=|(key, elt)| view! {
|
|
ElementOutlineItem(key=key, element=elt)
|
|
},
|
|
key=|(_, elt)| elt.serial
|
|
)
|
|
}
|
|
}
|
|
} |