Compare commits
12 Commits
96f8b6b5f3
...
f5486fb0dd
Author | SHA1 | Date | |
---|---|---|---|
|
f5486fb0dd | ||
|
4e3c86fb71 | ||
|
7ff1b9cb65 | ||
|
e6281cdcc6 | ||
|
fc85d15f83 | ||
|
7709c61f71 | ||
|
edee153e37 | ||
|
4a24a01928 | ||
|
050e2373a6 | ||
|
147e275823 | ||
|
d121385c18 | ||
|
78f8ef8215 |
4
app-proto/full-interface/.gitignore
vendored
Normal file
4
app-proto/full-interface/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
target
|
||||||
|
dist
|
||||||
|
profiling
|
||||||
|
Cargo.lock
|
@ -12,6 +12,7 @@ itertools = "0.13.0"
|
|||||||
js-sys = "0.3.70"
|
js-sys = "0.3.70"
|
||||||
nalgebra = "0.33.0"
|
nalgebra = "0.33.0"
|
||||||
rustc-hash = "2.0.0"
|
rustc-hash = "2.0.0"
|
||||||
|
slab = "0.4.9"
|
||||||
sycamore = "0.9.0-beta.3"
|
sycamore = "0.9.0-beta.3"
|
||||||
|
|
||||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
# The `console_error_panic_hook` crate provides better debugging of panics by
|
120
app-proto/full-interface/main.css
Normal file
120
app-proto/full-interface/main.css
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
body {
|
||||||
|
margin: 0px;
|
||||||
|
color: #fcfcfc;
|
||||||
|
background-color: #222;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* sidebar */
|
||||||
|
|
||||||
|
#sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
float: left;
|
||||||
|
width: 450px;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
border-width: 0px 1px 0px 0px;
|
||||||
|
border-style: solid;
|
||||||
|
border-color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* add-remove */
|
||||||
|
|
||||||
|
#add-remove {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-remove > button {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
font-size: large;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* outline */
|
||||||
|
|
||||||
|
#outline {
|
||||||
|
flex-grow: 1;
|
||||||
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
overflow-y: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary.selected {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary > div, .cst {
|
||||||
|
padding-top: 4px;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elt, .cst {
|
||||||
|
display: flex;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding-left: 8px;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elt-switch {
|
||||||
|
width: 18px;
|
||||||
|
padding-left: 2px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
details:has(li) .elt-switch::after {
|
||||||
|
content: '▸';
|
||||||
|
}
|
||||||
|
|
||||||
|
details[open]:has(li) .elt-switch::after {
|
||||||
|
content: '▾';
|
||||||
|
}
|
||||||
|
|
||||||
|
.elt-label {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cst-label {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elt-rep {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.elt-rep > div, .cst-rep {
|
||||||
|
padding: 2px 0px 0px 0px;
|
||||||
|
font-size: 10pt;
|
||||||
|
text-align: center;
|
||||||
|
width: 56px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cst {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* display */
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
float: left;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
background-color: #020202;
|
||||||
|
border: 1px solid #555;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas:focus {
|
||||||
|
border-color: #aaa;
|
||||||
|
}
|
55
app-proto/full-interface/src/add_remove.rs
Normal file
55
app-proto/full-interface/src/add_remove.rs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
use sycamore::prelude::*;
|
||||||
|
use web_sys::{MouseEvent, console, wasm_bindgen::JsValue};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
use crate::Constraint;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn AddRemove() -> View {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
|
||||||
|
view! {
|
||||||
|
div(id="add-remove") {
|
||||||
|
button(
|
||||||
|
on:click=move |event: MouseEvent| {
|
||||||
|
console::log_1(&JsValue::from("constraints:"));
|
||||||
|
state.assembly.constraints.with(|csts| {
|
||||||
|
for (_, cst) in csts.into_iter() {
|
||||||
|
console::log_4(
|
||||||
|
&JsValue::from(cst.args.0),
|
||||||
|
&JsValue::from(cst.args.1),
|
||||||
|
&JsValue::from(":"),
|
||||||
|
&JsValue::from(cst.rep)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
) { "+" }
|
||||||
|
button(
|
||||||
|
disabled={
|
||||||
|
state.selection.with(|sel| sel.len() != 2)
|
||||||
|
},
|
||||||
|
on:click=move |event: MouseEvent| {
|
||||||
|
let args = state.selection.with(
|
||||||
|
|sel| {
|
||||||
|
let arg_vec: Vec<_> = sel.into_iter().collect();
|
||||||
|
(arg_vec[0].clone(), arg_vec[1].clone())
|
||||||
|
}
|
||||||
|
);
|
||||||
|
console::log_5(
|
||||||
|
&JsValue::from("add constraint"),
|
||||||
|
&JsValue::from(args.0),
|
||||||
|
&JsValue::from(args.1),
|
||||||
|
&JsValue::from(":"),
|
||||||
|
&JsValue::from(0.0)
|
||||||
|
);
|
||||||
|
state.assembly.insert_constraint(Constraint {
|
||||||
|
args: args,
|
||||||
|
rep: 0.0
|
||||||
|
});
|
||||||
|
state.selection.update(|sel| sel.clear());
|
||||||
|
}
|
||||||
|
) { "🔗" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
app-proto/full-interface/src/assembly.rs
Normal file
44
app-proto/full-interface/src/assembly.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
use nalgebra::DVector;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
use slab::Slab;
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub struct Element {
|
||||||
|
pub id: String,
|
||||||
|
pub label: String,
|
||||||
|
pub color: [f32; 3],
|
||||||
|
pub rep: DVector<f64>,
|
||||||
|
pub constraints: FxHashSet<usize>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Constraint {
|
||||||
|
pub args: (usize, usize),
|
||||||
|
pub rep: f64
|
||||||
|
}
|
||||||
|
|
||||||
|
// a complete, view-independent description of an assembly
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Assembly {
|
||||||
|
pub elements: Signal<Slab<Element>>,
|
||||||
|
pub constraints: Signal<Slab<Constraint>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Assembly {
|
||||||
|
pub fn new() -> Assembly {
|
||||||
|
Assembly {
|
||||||
|
elements: create_signal(Slab::new()),
|
||||||
|
constraints: create_signal(Slab::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_constraint(&self, constraint: Constraint) {
|
||||||
|
let args = constraint.args;
|
||||||
|
let key = self.constraints.update(|csts| csts.insert(constraint));
|
||||||
|
self.elements.update(|elts| {
|
||||||
|
elts[args.0].constraints.insert(key);
|
||||||
|
elts[args.1].constraints.insert(key);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -288,17 +288,17 @@ pub fn Display() -> View {
|
|||||||
|
|
||||||
// get the assembly
|
// get the assembly
|
||||||
let elements = state.assembly.elements.get_clone();
|
let elements = state.assembly.elements.get_clone();
|
||||||
let element_iter = (&elements).values();
|
let element_iter = (&elements).into_iter();
|
||||||
let reps_world: Vec<_> = element_iter.clone().map(|elt| &assembly_to_world * &elt.rep).collect();
|
let reps_world: Vec<_> = element_iter.clone().map(|(_, elt)| &assembly_to_world * &elt.rep).collect();
|
||||||
let colors: Vec<_> = element_iter.clone().map(|elt|
|
let colors: Vec<_> = element_iter.clone().map(|(key, elt)|
|
||||||
if state.selection.with(|sel| sel.contains(&elt.id)) {
|
if state.selection.with(|sel| sel.contains(&key)) {
|
||||||
elt.color.map(|ch| 0.2 + 0.8*ch)
|
elt.color.map(|ch| 0.2 + 0.8*ch)
|
||||||
} else {
|
} else {
|
||||||
elt.color
|
elt.color
|
||||||
}
|
}
|
||||||
).collect();
|
).collect();
|
||||||
let highlights: Vec<_> = element_iter.map(|elt|
|
let highlights: Vec<_> = element_iter.map(|(key, _)|
|
||||||
if state.selection.with(|sel| sel.contains(&elt.id)) {
|
if state.selection.with(|sel| sel.contains(&key)) {
|
||||||
1.0_f32
|
1.0_f32
|
||||||
} else {
|
} else {
|
||||||
HIGHLIGHT
|
HIGHLIGHT
|
||||||
@ -386,8 +386,8 @@ pub fn Display() -> View {
|
|||||||
// again
|
// again
|
||||||
canvas(
|
canvas(
|
||||||
ref=display,
|
ref=display,
|
||||||
width="750",
|
width="600",
|
||||||
height="750",
|
height="600",
|
||||||
tabindex="0",
|
tabindex="0",
|
||||||
on:keydown=move |event: KeyboardEvent| {
|
on:keydown=move |event: KeyboardEvent| {
|
||||||
if event.key() == "Shift" {
|
if event.key() == "Shift" {
|
@ -1,66 +1,79 @@
|
|||||||
|
mod add_remove;
|
||||||
mod assembly;
|
mod assembly;
|
||||||
mod display;
|
mod display;
|
||||||
mod outline;
|
mod outline;
|
||||||
|
|
||||||
use nalgebra::DVector;
|
use nalgebra::DVector;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::FxHashSet;
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
use assembly::{Assembly, Element};
|
use add_remove::AddRemove;
|
||||||
|
use assembly::{Assembly, Constraint, Element};
|
||||||
use display::Display;
|
use display::Display;
|
||||||
use outline::Outline;
|
use outline::Outline;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
assembly: Assembly,
|
assembly: Assembly,
|
||||||
selection: Signal<FxHashSet<String>>
|
selection: Signal<FxHashSet<usize>>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AppState {
|
||||||
|
fn new() -> AppState {
|
||||||
|
AppState {
|
||||||
|
assembly: Assembly::new(),
|
||||||
|
selection: create_signal(FxHashSet::default())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
sycamore::render(|| {
|
sycamore::render(|| {
|
||||||
let state = AppState {
|
let state = AppState::new();
|
||||||
assembly: Assembly {
|
let key_a = state.assembly.elements.update(
|
||||||
elements: create_signal(FxHashMap::default())
|
|
||||||
},
|
|
||||||
selection: create_signal(FxHashSet::default())
|
|
||||||
};
|
|
||||||
state.assembly.elements.update(
|
|
||||||
|elts| elts.insert(
|
|elts| elts.insert(
|
||||||
"wing_a".to_string(),
|
|
||||||
Element {
|
Element {
|
||||||
id: String::from("wing_a"),
|
id: String::from("wing_a"),
|
||||||
label: String::from("Wing A"),
|
label: String::from("Wing A"),
|
||||||
color: [1.00_f32, 0.25_f32, 0.00_f32],
|
color: [1.00_f32, 0.25_f32, 0.00_f32],
|
||||||
rep: DVector::<f64>::from_column_slice(&[0.5, 0.5, 0.0, 0.5, -0.25])
|
rep: DVector::<f64>::from_column_slice(&[0.5, 0.5, 0.0, 0.5, -0.25]),
|
||||||
|
constraints: FxHashSet::default()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
state.assembly.elements.update(
|
let key_b = state.assembly.elements.update(
|
||||||
|elts| elts.insert(
|
|elts| elts.insert(
|
||||||
"wing_b".to_string(),
|
|
||||||
Element {
|
Element {
|
||||||
id: String::from("wing_b"),
|
id: String::from("wing_b"),
|
||||||
label: String::from("Wing B"),
|
label: String::from("Wing B"),
|
||||||
color: [0.00_f32, 0.25_f32, 1.00_f32],
|
color: [0.00_f32, 0.25_f32, 1.00_f32],
|
||||||
rep: DVector::<f64>::from_column_slice(&[-0.5, -0.5, 0.0, 0.5, -0.25])
|
rep: DVector::<f64>::from_column_slice(&[-0.5, -0.5, 0.0, 0.5, -0.25]),
|
||||||
|
constraints: FxHashSet::default()
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
state.assembly.elements.update(
|
state.assembly.elements.update(
|
||||||
|elts| elts.insert(
|
|elts| elts.insert(
|
||||||
"central".to_string(),
|
|
||||||
Element {
|
Element {
|
||||||
id: String::from("central"),
|
id: String::from("central"),
|
||||||
label: String::from("Central"),
|
label: String::from("Central"),
|
||||||
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
color: [0.75_f32, 0.75_f32, 0.75_f32],
|
||||||
rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.4, -0.625])
|
rep: DVector::<f64>::from_column_slice(&[0.0, 0.0, 0.0, 0.4, -0.625]),
|
||||||
|
constraints: FxHashSet::default()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
state.assembly.insert_constraint(Constraint {
|
||||||
|
args: (key_a, key_b),
|
||||||
|
rep: 0.5
|
||||||
|
});
|
||||||
provide_context(state);
|
provide_context(state);
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
Outline {}
|
div(id="sidebar") {
|
||||||
|
AddRemove {}
|
||||||
|
Outline {}
|
||||||
|
}
|
||||||
Display {}
|
Display {}
|
||||||
}
|
}
|
||||||
});
|
});
|
155
app-proto/full-interface/src/outline.rs
Normal file
155
app-proto/full-interface/src/outline.rs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use sycamore::{prelude::*, web::tags::div};
|
||||||
|
use web_sys::{Element, KeyboardEvent, MouseEvent, wasm_bindgen::JsCast};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
// this component lists the elements of the assembly, showing the constraints
|
||||||
|
// on 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 {
|
||||||
|
// sort the elements alphabetically by ID
|
||||||
|
let elements_sorted = create_memo(|| {
|
||||||
|
let state = use_context::<AppState>();
|
||||||
|
state.assembly.elements
|
||||||
|
.get_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=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.rep.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! {
|
||||||
|
/* [TO DO] switch to integer-valued parameters whenever
|
||||||
|
that becomes possible again */
|
||||||
|
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 |c_key: usize| {
|
||||||
|
let c_state = use_context::<AppState>();
|
||||||
|
let assembly = &c_state.assembly;
|
||||||
|
let cst = assembly.constraints.with(|csts| csts[c_key].clone());
|
||||||
|
let other_arg = if cst.args.0 == key {
|
||||||
|
cst.args.1
|
||||||
|
} else {
|
||||||
|
cst.args.0
|
||||||
|
};
|
||||||
|
let other_arg_label = assembly.elements.with(|elts| elts[other_arg].label.clone());
|
||||||
|
view! {
|
||||||
|
li(class="cst") {
|
||||||
|
div(class="cst-label") { (other_arg_label) }
|
||||||
|
div(class="cst-rep") { (cst.rep) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key=|c_key| c_key.clone()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
key=|(key, _)| key.clone()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
app-proto/inversive-display/.gitignore
vendored
1
app-proto/inversive-display/.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
target
|
target
|
||||||
dist
|
dist
|
||||||
|
profiling
|
||||||
Cargo.lock
|
Cargo.lock
|
3
app-proto/sketch-outline/.gitignore
vendored
3
app-proto/sketch-outline/.gitignore
vendored
@ -1,3 +0,0 @@
|
|||||||
target
|
|
||||||
dist
|
|
||||||
Cargo.lock
|
|
@ -1,79 +0,0 @@
|
|||||||
body {
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
color: #fcfcfc;
|
|
||||||
background-color: #222;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* outline */
|
|
||||||
|
|
||||||
ul {
|
|
||||||
float: left;
|
|
||||||
width: 450px;
|
|
||||||
height: 750px;
|
|
||||||
margin: 0px;
|
|
||||||
padding: 8px;
|
|
||||||
border: 1px solid #555;
|
|
||||||
border-radius: 16px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
padding: 3px;
|
|
||||||
list-style-type: none;
|
|
||||||
background-color: #444;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.selected {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:not(:last-child) {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > .elt-label {
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: 2px 0px 2px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > .elt-rep {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > .elt-rep > div {
|
|
||||||
padding: 2px;
|
|
||||||
margin-left: 3px;
|
|
||||||
text-align: center;
|
|
||||||
width: 60px;
|
|
||||||
background-color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
li.selected > .elt-rep > div {
|
|
||||||
background-color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > .elt-rep > div:first-child {
|
|
||||||
border-radius: 6px 0px 0px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > .elt-rep > div:last-child {
|
|
||||||
border-radius: 0px 6px 6px 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* display */
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
float: left;
|
|
||||||
margin-left: 16px;
|
|
||||||
background-color: #020202;
|
|
||||||
border: 1px solid #555;
|
|
||||||
border-radius: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas:focus {
|
|
||||||
border-color: #aaa;
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
use nalgebra::DVector;
|
|
||||||
use rustc_hash::FxHashMap;
|
|
||||||
use sycamore::reactive::Signal;
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
|
||||||
pub struct Element {
|
|
||||||
pub id: String,
|
|
||||||
pub label: String,
|
|
||||||
pub color: [f32; 3],
|
|
||||||
pub rep: DVector<f64>
|
|
||||||
}
|
|
||||||
|
|
||||||
// a complete, view-independent description of an assembly
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Assembly {
|
|
||||||
// the order of the elements is arbitrary, and it could change at any time
|
|
||||||
pub elements: Signal<FxHashMap<String, Element>>
|
|
||||||
}
|
|
@ -1,80 +0,0 @@
|
|||||||
use itertools::Itertools;
|
|
||||||
use sycamore::{prelude::*, web::tags::div};
|
|
||||||
use web_sys::KeyboardEvent;
|
|
||||||
|
|
||||||
use crate::AppState;
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn Outline() -> View {
|
|
||||||
// sort the elements alphabetically by ID
|
|
||||||
let elements_sorted = create_memo(|| {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
state.assembly.elements
|
|
||||||
.get_clone()
|
|
||||||
.into_iter()
|
|
||||||
.sorted_by_key(|(id, _)| id.clone())
|
|
||||||
.map(|(_, elt)| elt)
|
|
||||||
.collect()
|
|
||||||
});
|
|
||||||
|
|
||||||
view! {
|
|
||||||
ul {
|
|
||||||
Keyed(
|
|
||||||
list=elements_sorted,
|
|
||||||
view=|elt| {
|
|
||||||
let state = use_context::<AppState>();
|
|
||||||
let class = create_memo({
|
|
||||||
let id = elt.id.clone();
|
|
||||||
move || {
|
|
||||||
if state.selection.with(|sel| sel.contains(&id)) {
|
|
||||||
"selected"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let label = elt.label.clone();
|
|
||||||
let rep_components = elt.rep.iter().map(|u| {
|
|
||||||
let u_coord = u.to_string().replace("-", "\u{2212}");
|
|
||||||
View::from(div().children(u_coord))
|
|
||||||
}).collect::<Vec<_>>();
|
|
||||||
view! {
|
|
||||||
/* [TO DO] switch to integer-valued parameters whenever
|
|
||||||
that becomes possible again */
|
|
||||||
li(
|
|
||||||
class=class.get(),
|
|
||||||
tabindex="0",
|
|
||||||
on:click={
|
|
||||||
let id = elt.id.clone();
|
|
||||||
move |_| {
|
|
||||||
state.selection.update(|sel| {
|
|
||||||
if !sel.remove(&id) {
|
|
||||||
sel.insert(id.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
on:keydown={
|
|
||||||
let id = elt.id.clone();
|
|
||||||
move |event: KeyboardEvent| {
|
|
||||||
if event.key() == "Enter" {
|
|
||||||
state.selection.update(|sel| {
|
|
||||||
if !sel.remove(&id) {
|
|
||||||
sel.insert(id.clone());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
event.prevent_default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
div(class="elt-label") { (label) }
|
|
||||||
div(class="elt-rep") { (rep_components) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
key=|elt| elt.id.clone()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user