Clean up the outline view #19

Merged
glen merged 23 commits from outline-cleanup_on_main into main 2024-11-15 03:32:48 +00:00
Showing only changes of commit dcf5764993 - Show all commits

View File

@ -1,7 +1,6 @@
use itertools::Itertools;
use sycamore::{prelude::*, web::tags::div};
use web_sys::{
Element,
Event,
HtmlInputElement,
KeyboardEvent,
@ -9,7 +8,7 @@ use web_sys::{
wasm_bindgen::JsCast
};
use crate::{AppState, assembly::{Constraint, ConstraintKey, ElementKey}};
use crate::{AppState, assembly, assembly::{Constraint, ConstraintKey, ElementKey}};
// an editable view of the Lorentz product representing a constraint
#[component(inline_props)]
@ -53,6 +52,112 @@ fn ConstraintOutlineItem(constraint_key: ConstraintKey, element_key: ElementKey)
}
}
// 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 = create_memo({
move || {
if state.selection.with(|sel| sel.contains(&key)) {
"selected"
} else {
""
}
}
});
let label = element.label.clone();
let rep_components = element.representation.iter().map(|u| {
let u_coord = u.to_string().replace("-", "\u{2212}");
View::from(div().children(u_coord))
}).collect::<Vec<_>>();
let constrained = element.constraints.len() > 0;
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" => {
if event.shift_key() {
glen marked this conversation as resolved
Review

What's the "native" form of event.key()? Seems a pity to convert it to a str just to match it against three options. Can't constants of the actual values of the three options (before str conversion), perhaps defined above if need be, be used in place?

What's the "native" form of event.key()? Seems a pity to convert it to a str just to match it against three options. Can't constants of the actual values of the three options (before str conversion), perhaps defined above if need be, be used in place?
Review

The key method returns a String (a heap-allocated, variable-length string object). Each of the string literals we're matching with is a &str (a reference to a "string slice," which seems to be a pointer to a fixed-length string of bytes). An explicit conversion is required, and as_str seems to be the standard way to do it.

The [`key`](https://docs.rs/web-sys/latest/web_sys/struct.KeyboardEvent.html#method.key) method returns a [`String`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html) (a heap-allocated, variable-length string object). Each of the string literals we're matching with is a [`&str`](https://doc.rust-lang.org/nightly/core/primitive.str.html) (a reference to a "string slice," which seems to be a pointer to a fixed-length string of bytes). An explicit conversion is required, and [`as_str`](https://doc.rust-lang.org/nightly/alloc/string/struct.String.html#method.as_str) seems to be the [standard way](https://stackoverflow.com/questions/25383488/how-to-match-a-string-against-string-literals?rq=3) to do it.
Review

Oh, gotcha. A Rustism I was not yet used to. Hope you can see why it looked odd.

Oh, gotcha. A Rustism I was not yet used to. Hope you can see why it looked odd.
Review

Yup. Stuff like "hello".to_string() looked even weirder to me at first, but the Rust book authors seem to consider it the most idiomatic way of going in the other direction...

Yup. Stuff like `"hello".to_string()` looked even weirder to me at first, but the Rust book authors seem to consider it the most idiomatic way of going in the other direction...
Review

Well yes in fact that looks so weird that I believe that in husht we will implement a labeled-literal syntax like s"hello" for exactly this. Note that room has been left in the Rust syntax for such a thing, it's just not part of the language (yet, anyway).

Well yes in fact that looks so weird that I believe that in husht we will implement a labeled-literal syntax like `s"hello"` for exactly this. Note that room has been left in the Rust syntax for such a thing, it's just not part of the language (yet, anyway).
state.selection.update(|sel| {
if !sel.remove(&key) {
sel.insert(key);
}
});
} else {
state.selection.update(|sel| {
sel.clear();
sel.insert(key);
});
}
event.prevent_default();
},
"ArrowRight" if constrained => {
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="elt-switch",
on:click=|event: MouseEvent| event.stop_propagation()
)
div(
class="elt",
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="elt-label") { (label) }
div(class="elt-rep") { (rep_components) }
}
}
ul(class="constraints") {
Keyed(
list=element.constraints.into_iter().collect::<Vec<_>>(),
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:
@ -81,108 +186,8 @@ pub fn Outline() -> View {
) {
Keyed(
list=elements_sorted,
view=|(key, elt)| {
let state = use_context::<AppState>();
let class = create_memo({
move || {
if state.selection.with(|sel| sel.contains(&key)) {
"selected"
} else {
""
}
}
});
let label = elt.label.clone();
let rep_components = elt.representation.iter().map(|u| {
let u_coord = u.to_string().replace("-", "\u{2212}");
View::from(div().children(u_coord))
}).collect::<Vec<_>>();
let constrained = elt.constraints.len() > 0;
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" => {
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.prevent_default();
},
"ArrowRight" if constrained => {
let _ = details_node
.get()
.unchecked_into::<Element>()
.set_attribute("open", "");
},
"ArrowLeft" => {
let _ = details_node
.get()
.unchecked_into::<Element>()
.remove_attribute("open");
},
_ => ()
}
}
}
) {
div(
class="elt-switch",
on:click=|event: MouseEvent| event.stop_propagation()
)
div(
class="elt",
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="elt-label") { (label) }
div(class="elt-rep") { (rep_components) }
}
}
ul(class="constraints") {
Keyed(
list=elt.constraints.into_iter().collect::<Vec<_>>(),
view=move |cst_key| view! {
ConstraintOutlineItem(
constraint_key=cst_key,
element_key=key
)
},
key=|cst_key| cst_key.clone()
)
}
}
}
}
view=|(key, elt)| view! {
ElementOutlineItem(key=key, element=elt)
},
key=|(key, elt)| (
key.clone(),