Compare commits
6 Commits
f7f492b70e
...
ad57482dea
Author | SHA1 | Date |
---|---|---|
Emilia Allison | ad57482dea | |
Emilia Allison | 12684c0eea | |
Emilia Allison | 0ec29f6783 | |
Emilia Allison | e01468b63a | |
Emilia Allison | 8f82c4e224 | |
Emilia Allison | 2fdc15c9aa |
|
@ -633,7 +633,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plate-tool-lib"
|
name = "plate-tool-lib"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
@ -648,7 +648,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plate-tool-web"
|
name = "plate-tool-web"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "plate-tool-web"
|
name = "plate-tool-web"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
|
@ -36,6 +36,12 @@ dialog > form[method="dialog"] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shifted_dialog {
|
||||||
|
top: 50vh;
|
||||||
|
left: 50vw;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.close_button {
|
.close_button {
|
||||||
color: red;
|
color: red;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -1,221 +0,0 @@
|
||||||
#![allow(non_snake_case)]
|
|
||||||
|
|
||||||
|
|
||||||
use js_sys::Array;
|
|
||||||
|
|
||||||
|
|
||||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
|
||||||
use web_sys::{
|
|
||||||
Blob, HtmlAnchorElement, HtmlDialogElement, HtmlElement,
|
|
||||||
HtmlFormElement, HtmlInputElement, Url,
|
|
||||||
};
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yewdux::prelude::*;
|
|
||||||
|
|
||||||
use crate::components::states::{CurrentTransfer, MainState};
|
|
||||||
|
|
||||||
use crate::state_to_csv;
|
|
||||||
|
|
||||||
type NoParamsCallback = Box<dyn Fn(())>;
|
|
||||||
|
|
||||||
pub fn create_close_button(close: &Closure<dyn FnMut(Event)>) -> HtmlElement {
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
let close_button = document
|
|
||||||
.create_element("button")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlElement>()
|
|
||||||
.unwrap();
|
|
||||||
close_button.set_text_content(Some("✖"));
|
|
||||||
close_button.set_class_name("close_button");
|
|
||||||
close_button.set_onclick(Some(close.as_ref().unchecked_ref()));
|
|
||||||
|
|
||||||
close_button
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn toggle_in_transfer_hashes_callback(
|
|
||||||
main_dispatch: Dispatch<MainState>,
|
|
||||||
) -> Callback<web_sys::MouseEvent> {
|
|
||||||
let main_dispatch = main_dispatch.clone();
|
|
||||||
Callback::from(move |_| {
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.preferences.in_transfer_hashes ^= true;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_plate_dialog_callback(
|
|
||||||
new_plate_dialog_is_open: UseStateHandle<bool>,
|
|
||||||
) -> NoParamsCallback {
|
|
||||||
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
|
||||||
Box::new(move |_| {
|
|
||||||
new_plate_dialog_is_open.set(false);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn open_new_plate_dialog_callback(
|
|
||||||
new_plate_dialog_is_open: UseStateHandle<bool>,
|
|
||||||
) -> NoParamsCallback {
|
|
||||||
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
|
||||||
Box::new(move |_| {
|
|
||||||
new_plate_dialog_is_open.set(true);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_button_callback(
|
|
||||||
main_dispatch: Dispatch<MainState>,
|
|
||||||
ct_dispatch: Dispatch<CurrentTransfer>,
|
|
||||||
) -> Callback<web_sys::MouseEvent> {
|
|
||||||
Callback::from(move |_| {
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let confirm =
|
|
||||||
window.confirm_with_message("This will reset all plates and transfers. Proceed?");
|
|
||||||
if let Ok(confirm) = confirm {
|
|
||||||
if confirm {
|
|
||||||
main_dispatch.set(MainState::default());
|
|
||||||
ct_dispatch.set(CurrentTransfer::default());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn save_str(data: &str, name: &str) {
|
|
||||||
let blob =
|
|
||||||
Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data))));
|
|
||||||
if let Ok(blob) = blob {
|
|
||||||
let url = Url::create_object_url_with_blob(&blob).expect("We have a blob, why not URL?");
|
|
||||||
// Beneath is the cool hack to download files
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let document = window.document().unwrap();
|
|
||||||
let anchor = document
|
|
||||||
.create_element("a")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlAnchorElement>()
|
|
||||||
.unwrap();
|
|
||||||
anchor.set_download(name);
|
|
||||||
anchor.set_href(&url);
|
|
||||||
anchor.click();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn export_csv_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
|
|
||||||
Callback::from(move |_| {
|
|
||||||
if main_state.transfers.is_empty() {
|
|
||||||
web_sys::window()
|
|
||||||
.unwrap()
|
|
||||||
.alert_with_message("No transfers to export.")
|
|
||||||
.unwrap();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap();
|
|
||||||
if let Ok(csv) = state_to_csv(&main_state) {
|
|
||||||
save_str(&csv, "transfers.csv");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn export_json_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
|
|
||||||
Callback::from(move |_| {
|
|
||||||
if let Ok(json) = serde_json::to_string(&main_state) {
|
|
||||||
save_str(&json, "plate-tool-state.json");
|
|
||||||
} else {
|
|
||||||
web_sys::window()
|
|
||||||
.unwrap()
|
|
||||||
.alert_with_message("Failed to export.")
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn input_json_input_callback(
|
|
||||||
main_dispatch: Dispatch<MainState>,
|
|
||||||
modal: HtmlDialogElement,
|
|
||||||
) -> Closure<dyn FnMut(Event)> {
|
|
||||||
Closure::<dyn FnMut(_)>::new(move |e: Event| {
|
|
||||||
if let Some(input) = e.current_target() {
|
|
||||||
let input = input
|
|
||||||
.dyn_into::<HtmlInputElement>()
|
|
||||||
.expect("We know this is an input.");
|
|
||||||
if let Some(files) = input.files() {
|
|
||||||
if let Some(file) = files.get(0) {
|
|
||||||
let fr = web_sys::FileReader::new().unwrap();
|
|
||||||
fr.read_as_text(&file).unwrap();
|
|
||||||
let fr1 = fr.clone(); // Clone to avoid outliving closure
|
|
||||||
let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut
|
|
||||||
// trait
|
|
||||||
let modal = modal.clone();
|
|
||||||
let onload = Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
|
||||||
if let Some(value) = &fr1.result().ok().and_then(|v| v.as_string()) {
|
|
||||||
let ms = serde_json::from_str::<MainState>(value);
|
|
||||||
match ms {
|
|
||||||
Ok(ms) => main_dispatch.set(ms),
|
|
||||||
Err(e) => log::debug!("{:?}", e),
|
|
||||||
};
|
|
||||||
modal.close();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
|
|
||||||
onload.forget(); // Magic (don't touch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn import_json_button_callback(main_dispatch: Dispatch<MainState>) -> Callback<MouseEvent> {
|
|
||||||
Callback::from(move |_| {
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let document = window.document().unwrap();
|
|
||||||
let body = document.body().unwrap();
|
|
||||||
let modal = document
|
|
||||||
.create_element("dialog")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlDialogElement>()
|
|
||||||
.unwrap();
|
|
||||||
modal.set_text_content(Some("Import File:"));
|
|
||||||
let onclose_callback = {
|
|
||||||
let modal = modal.clone();
|
|
||||||
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
|
||||||
modal.remove();
|
|
||||||
})
|
|
||||||
};
|
|
||||||
modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
|
||||||
|
|
||||||
let close_button = create_close_button(&onclose_callback);
|
|
||||||
onclose_callback.forget();
|
|
||||||
modal.append_child(&close_button).unwrap();
|
|
||||||
|
|
||||||
let form = document
|
|
||||||
.create_element("form")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlFormElement>()
|
|
||||||
.unwrap();
|
|
||||||
let input = document
|
|
||||||
.create_element("input")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlInputElement>()
|
|
||||||
.unwrap();
|
|
||||||
input.set_type("file");
|
|
||||||
input.set_accept(".json");
|
|
||||||
form.append_child(&input).unwrap();
|
|
||||||
|
|
||||||
let input_callback = {
|
|
||||||
let main_dispatch = main_dispatch.clone();
|
|
||||||
let modal = modal.clone();
|
|
||||||
input_json_input_callback(main_dispatch, modal)
|
|
||||||
};
|
|
||||||
input.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
|
|
||||||
input_callback.forget(); // Magic straight from the docs, don't touch :(
|
|
||||||
|
|
||||||
modal.append_child(&form).unwrap();
|
|
||||||
body.append_child(&modal).unwrap();
|
|
||||||
modal.show_modal().unwrap();
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub use super::import_csv_callbacks::import_transfer_csv_callback;
|
|
||||||
|
|
||||||
pub use super::import_csv_callbacks::import_transfer_csv_submit_callback;
|
|
||||||
|
|
||||||
pub use super::import_csv_callbacks::import_transfer_csv_onload_callback;
|
|
||||||
|
|
||||||
pub use super::import_csv_callbacks::import_transfer_csv_input_callback;
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use js_sys::Array;
|
||||||
|
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
|
use web_sys::{
|
||||||
|
Blob, HtmlAnchorElement, HtmlDialogElement, HtmlElement, HtmlFormElement, HtmlInputElement, Url,
|
||||||
|
};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
|
|
||||||
|
use crate::state_to_csv;
|
||||||
|
|
||||||
|
type NoParamsCallback = Box<dyn Fn(())>;
|
||||||
|
|
||||||
|
pub fn export_csv_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
if main_state.transfers.is_empty() {
|
||||||
|
web_sys::window()
|
||||||
|
.unwrap()
|
||||||
|
.alert_with_message("No transfers to export.")
|
||||||
|
.unwrap();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap();
|
||||||
|
if let Ok(csv) = state_to_csv(&main_state) {
|
||||||
|
save_str(&csv, "transfers.csv");
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn export_json_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
if let Ok(json) = serde_json::to_string(&main_state) {
|
||||||
|
save_str(&json, "plate-tool-state.json");
|
||||||
|
} else {
|
||||||
|
web_sys::window()
|
||||||
|
.unwrap()
|
||||||
|
.alert_with_message("Failed to export.")
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn save_str(data: &str, name: &str) {
|
||||||
|
let blob =
|
||||||
|
Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data))));
|
||||||
|
if let Ok(blob) = blob {
|
||||||
|
let url = Url::create_object_url_with_blob(&blob).expect("We have a blob, why not URL?");
|
||||||
|
// Beneath is the cool hack to download files
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let anchor = document
|
||||||
|
.create_element("a")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlAnchorElement>()
|
||||||
|
.unwrap();
|
||||||
|
anchor.set_download(name);
|
||||||
|
anchor.set_href(&url);
|
||||||
|
anchor.click();
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ use plate_tool_lib::transfer_region::{Region, TransferRegion};
|
||||||
|
|
||||||
use plate_tool_lib::csv::{auto, string_well_to_pt, TransferRecord};
|
use plate_tool_lib::csv::{auto, string_well_to_pt, TransferRecord};
|
||||||
|
|
||||||
use super::main_window_callbacks::create_close_button;
|
use super::create_close_button;
|
||||||
|
|
||||||
pub fn import_transfer_csv_input_callback(
|
pub fn import_transfer_csv_input_callback(
|
||||||
main_dispatch: Dispatch<MainState>,
|
main_dispatch: Dispatch<MainState>,
|
|
@ -0,0 +1,98 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast};
|
||||||
|
use web_sys::{HtmlDialogElement, HtmlFormElement, HtmlInputElement};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::MainState;
|
||||||
|
|
||||||
|
use super::create_close_button;
|
||||||
|
|
||||||
|
type NoParamsCallback = Box<dyn Fn(())>;
|
||||||
|
|
||||||
|
pub fn input_json_input_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
modal: HtmlDialogElement,
|
||||||
|
) -> Closure<dyn FnMut(Event)> {
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |e: Event| {
|
||||||
|
if let Some(input) = e.current_target() {
|
||||||
|
let input = input
|
||||||
|
.dyn_into::<HtmlInputElement>()
|
||||||
|
.expect("We know this is an input.");
|
||||||
|
if let Some(files) = input.files() {
|
||||||
|
if let Some(file) = files.get(0) {
|
||||||
|
let fr = web_sys::FileReader::new().unwrap();
|
||||||
|
fr.read_as_text(&file).unwrap();
|
||||||
|
let fr1 = fr.clone(); // Clone to avoid outliving closure
|
||||||
|
let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut
|
||||||
|
// trait
|
||||||
|
let modal = modal.clone();
|
||||||
|
let onload = Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||||
|
if let Some(value) = &fr1.result().ok().and_then(|v| v.as_string()) {
|
||||||
|
let ms = serde_json::from_str::<MainState>(value);
|
||||||
|
match ms {
|
||||||
|
Ok(ms) => main_dispatch.set(ms),
|
||||||
|
Err(e) => log::debug!("{:?}", e),
|
||||||
|
};
|
||||||
|
modal.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
|
||||||
|
onload.forget(); // Magic (don't touch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_json_button_callback(main_dispatch: Dispatch<MainState>) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let body = document.body().unwrap();
|
||||||
|
let modal = document
|
||||||
|
.create_element("dialog")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlDialogElement>()
|
||||||
|
.unwrap();
|
||||||
|
modal.set_text_content(Some("Import File:"));
|
||||||
|
let onclose_callback = {
|
||||||
|
let modal = modal.clone();
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||||
|
modal.remove();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let close_button = create_close_button(&onclose_callback);
|
||||||
|
onclose_callback.forget();
|
||||||
|
modal.append_child(&close_button).unwrap();
|
||||||
|
|
||||||
|
let form = document
|
||||||
|
.create_element("form")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlFormElement>()
|
||||||
|
.unwrap();
|
||||||
|
let input = document
|
||||||
|
.create_element("input")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlInputElement>()
|
||||||
|
.unwrap();
|
||||||
|
input.set_type("file");
|
||||||
|
input.set_accept(".json");
|
||||||
|
form.append_child(&input).unwrap();
|
||||||
|
|
||||||
|
let input_callback = {
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
|
let modal = modal.clone();
|
||||||
|
input_json_input_callback(main_dispatch, modal)
|
||||||
|
};
|
||||||
|
input.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
|
||||||
|
input_callback.forget(); // Magic straight from the docs, don't touch :(
|
||||||
|
|
||||||
|
modal.append_child(&form).unwrap();
|
||||||
|
body.append_child(&modal).unwrap();
|
||||||
|
modal.show_modal().unwrap();
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
mod export_callbacks;
|
||||||
|
mod input_json_callbacks;
|
||||||
|
mod import_csv_callbacks;
|
||||||
|
mod settings_callbacks;
|
||||||
|
mod plate_edits_callbacks;
|
||||||
|
mod util;
|
||||||
|
|
||||||
|
pub use util::*;
|
||||||
|
|
||||||
|
pub use export_callbacks::*;
|
||||||
|
|
||||||
|
pub use input_json_callbacks::*;
|
||||||
|
|
||||||
|
pub use import_csv_callbacks::*;
|
||||||
|
|
||||||
|
pub use settings_callbacks::*;
|
||||||
|
|
||||||
|
pub use plate_edits_callbacks::*;
|
|
@ -0,0 +1,51 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use js_sys::Array;
|
||||||
|
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
|
use web_sys::{
|
||||||
|
Blob, HtmlAnchorElement, HtmlDialogElement, HtmlElement, HtmlFormElement, HtmlInputElement, Url,
|
||||||
|
};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
|
|
||||||
|
use crate::state_to_csv;
|
||||||
|
|
||||||
|
type NoParamsCallback = Box<dyn Fn(())>;
|
||||||
|
|
||||||
|
pub fn new_plate_dialog_callback(
|
||||||
|
new_plate_dialog_is_open: UseStateHandle<bool>,
|
||||||
|
) -> NoParamsCallback {
|
||||||
|
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
||||||
|
Box::new(move |_| {
|
||||||
|
new_plate_dialog_is_open.set(false);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn open_new_plate_dialog_callback(
|
||||||
|
new_plate_dialog_is_open: UseStateHandle<bool>,
|
||||||
|
) -> NoParamsCallback {
|
||||||
|
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
||||||
|
Box::new(move |_| {
|
||||||
|
new_plate_dialog_is_open.set(true);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_button_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<web_sys::MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let confirm =
|
||||||
|
window.confirm_with_message("This will reset all plates and transfers. Proceed?");
|
||||||
|
if let Ok(confirm) = confirm {
|
||||||
|
if confirm {
|
||||||
|
main_dispatch.set(MainState::default());
|
||||||
|
ct_dispatch.set(CurrentTransfer::default());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::MainState;
|
||||||
|
|
||||||
|
type NoParamsCallback = Box<dyn Fn(())>;
|
||||||
|
|
||||||
|
pub fn toggle_in_transfer_hashes_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
) -> Callback<web_sys::MouseEvent> {
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.preferences.in_transfer_hashes ^= true;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn toggle_volume_heatmap_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
) -> Callback<web_sys::MouseEvent> {
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.preferences.volume_heatmap ^= true;
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast};
|
||||||
|
use web_sys::HtmlElement;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
type NoParamsCallback = Box<dyn Fn(())>;
|
||||||
|
|
||||||
|
pub fn create_close_button(close: &Closure<dyn FnMut(Event)>) -> HtmlElement {
|
||||||
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
|
let close_button = document
|
||||||
|
.create_element("button")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlElement>()
|
||||||
|
.unwrap();
|
||||||
|
close_button.set_text_content(Some("✖"));
|
||||||
|
close_button.set_class_name("close_button");
|
||||||
|
close_button.set_onclick(Some(close.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
close_button
|
||||||
|
}
|
|
@ -2,4 +2,3 @@ pub mod main_window_callbacks;
|
||||||
pub mod new_plate_dialog_callbacks;
|
pub mod new_plate_dialog_callbacks;
|
||||||
pub mod transfer_menu_callbacks;
|
pub mod transfer_menu_callbacks;
|
||||||
pub mod tree_callbacks;
|
pub mod tree_callbacks;
|
||||||
mod import_csv_callbacks;
|
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
use uuid::Uuid;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{EventTarget, HtmlElement, HtmlInputElement, HtmlOptionElement, HtmlSelectElement};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::MainState;
|
||||||
|
use plate_tool_lib::plate::PlateFormat;
|
||||||
|
|
||||||
|
type NoParamsCallback = Box<dyn Fn(())>;
|
||||||
|
|
||||||
|
pub fn open_plate_info_callback(
|
||||||
|
plate_menu_id: UseStateHandle<Option<Uuid>>,
|
||||||
|
) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |e: MouseEvent| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
|
||||||
|
if let Some(li) = li {
|
||||||
|
if let Ok(id) = li.id().as_str().parse::<u128>() {
|
||||||
|
plate_menu_id.set(Some(Uuid::from_u128(id)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plate_info_close_callback(plate_menu_id: UseStateHandle<Option<Uuid>>) -> NoParamsCallback {
|
||||||
|
Box::new(move |_| {
|
||||||
|
plate_menu_id.set(None);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plate_info_delete_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
plate_menu_id: UseStateHandle<Option<Uuid>>,
|
||||||
|
) -> NoParamsCallback {
|
||||||
|
Box::new(move |_| {
|
||||||
|
if let Some(id) = *plate_menu_id {
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.del_plate(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rename_onchange(id: Uuid, main_dispatch: Dispatch<MainState>) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
log::debug!("Changed name");
|
||||||
|
let input = e
|
||||||
|
.target()
|
||||||
|
.expect("Event must have target")
|
||||||
|
.dyn_into::<HtmlInputElement>()
|
||||||
|
.unwrap();
|
||||||
|
main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value()))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn format_onchange(id: Uuid, main_dispatch: Dispatch<MainState>) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
log::debug!("Changing plate format");
|
||||||
|
let new_format: Option<PlateFormat> = e
|
||||||
|
.target()
|
||||||
|
.expect("Event must have target")
|
||||||
|
.dyn_into::<HtmlSelectElement>()
|
||||||
|
.unwrap()
|
||||||
|
.selected_options()
|
||||||
|
.get_with_index(0)
|
||||||
|
.map(|el| el.dyn_into::<HtmlOptionElement>().unwrap())
|
||||||
|
.map(|opt_el| opt_el.value())
|
||||||
|
.and_then(|value| PlateFormat::try_from(value.as_str()).ok());
|
||||||
|
if let Some(format) = new_format {
|
||||||
|
main_dispatch.reduce_mut(|state| state.change_format(id, &format));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
use plate_tool_lib::{plate::PlateType, plate_instances::PlateInstance};
|
||||||
|
use uuid::Uuid;
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast};
|
||||||
|
use web_sys::{
|
||||||
|
HtmlDialogElement, HtmlElement, HtmlFormElement, HtmlInputElement, HtmlOptionElement,
|
||||||
|
HtmlSelectElement,
|
||||||
|
};
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use crate::components::callbacks::main_window_callbacks::create_close_button;
|
||||||
|
use crate::components::states::MainState;
|
||||||
|
|
||||||
|
pub fn merge_button_callback(main_dispatch: Dispatch<MainState>, id: Uuid) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let dialog_opt = create_merge_dialog(main_dispatch.clone(), id);
|
||||||
|
if let Some(dialog) = dialog_opt {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let body = document.body().unwrap();
|
||||||
|
let _ = body.append_child(&dialog);
|
||||||
|
dialog.set_open(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_merge_dialog(main_dispatch: Dispatch<MainState>, id: Uuid) -> Option<HtmlDialogElement> {
|
||||||
|
let other_plates = get_all_plates_except(main_dispatch.clone(), id);
|
||||||
|
if other_plates.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let dialog = create_dialog();
|
||||||
|
|
||||||
|
let select = create_select_for_plates(&other_plates);
|
||||||
|
dialog.append_child(&select).unwrap();
|
||||||
|
let select_rc = Rc::new(select);
|
||||||
|
let ok_button = create_ok_button(main_dispatch.clone(), select_rc.clone(), id);
|
||||||
|
dialog.append_child(&ok_button).unwrap();
|
||||||
|
|
||||||
|
Some(dialog)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_dialog() -> HtmlDialogElement {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
|
||||||
|
let dialog = document
|
||||||
|
.create_element("dialog")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlDialogElement>()
|
||||||
|
.unwrap();
|
||||||
|
dialog.set_class_name("dialog shifted_dialog");
|
||||||
|
let onclose_callback = {
|
||||||
|
let dialog = dialog.clone();
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||||
|
dialog.remove();
|
||||||
|
})
|
||||||
|
};
|
||||||
|
dialog.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||||
|
|
||||||
|
let close_button = create_close_button(&onclose_callback);
|
||||||
|
onclose_callback.forget();
|
||||||
|
dialog.append_child(&close_button).unwrap();
|
||||||
|
|
||||||
|
let header = document.create_element("h2").unwrap();
|
||||||
|
header.set_inner_html("Merge Plates");
|
||||||
|
dialog.append_child(&header).unwrap();
|
||||||
|
|
||||||
|
dialog
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_ok_button(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
select: Rc<HtmlSelectElement>,
|
||||||
|
id: Uuid,
|
||||||
|
) -> HtmlElement {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
|
||||||
|
let button = document
|
||||||
|
.create_element("button")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlElement>()
|
||||||
|
.unwrap();
|
||||||
|
button.set_inner_html("Ok");
|
||||||
|
let callback = ok_button_callback(main_dispatch, select, id);
|
||||||
|
button.set_onclick(Some(callback.as_ref().unchecked_ref()));
|
||||||
|
callback.forget();
|
||||||
|
|
||||||
|
button
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ok_button_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
select: Rc<HtmlSelectElement>,
|
||||||
|
to_id: Uuid,
|
||||||
|
) -> Closure<dyn FnMut(Event)> {
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||||
|
let selected = select
|
||||||
|
.selected_options()
|
||||||
|
.get_with_index(0)
|
||||||
|
.map(|el| el.dyn_into::<HtmlOptionElement>().unwrap())
|
||||||
|
.map(|opt_el| opt_el.value())
|
||||||
|
.and_then(|uuid_str| Uuid::from_str(&uuid_str).ok());
|
||||||
|
if let Some(from_id) = selected {
|
||||||
|
let plate_type = find_plate_type(main_dispatch.clone(), from_id).unwrap();
|
||||||
|
let binding = main_dispatch.get();
|
||||||
|
let merge_to = match plate_type {
|
||||||
|
PlateType::Source => binding.source_plates.iter().find(|x| x.get_uuid() == to_id).unwrap(),
|
||||||
|
PlateType::Destination => binding
|
||||||
|
.destination_plates
|
||||||
|
.iter()
|
||||||
|
.find(|x| x.get_uuid() == to_id).unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
main_dispatch.reduce_mut(|x| {
|
||||||
|
let merged_transfers = x.transfers.iter_mut().filter(|x| match plate_type {
|
||||||
|
PlateType::Source => x.source_id,
|
||||||
|
PlateType::Destination => x.dest_id
|
||||||
|
} == from_id);
|
||||||
|
|
||||||
|
for transfer in merged_transfers {
|
||||||
|
match plate_type {
|
||||||
|
PlateType::Source => {
|
||||||
|
transfer.source_id = to_id;
|
||||||
|
transfer.transfer_region.source_plate = merge_to.plate;
|
||||||
|
},
|
||||||
|
PlateType::Destination => {
|
||||||
|
transfer.dest_id = to_id;
|
||||||
|
transfer.transfer_region.dest_plate = merge_to.plate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match plate_type {
|
||||||
|
PlateType::Source => x.source_plates.retain(|y| y.get_uuid() != from_id),
|
||||||
|
PlateType::Destination => x.destination_plates.retain(|y| y.get_uuid() != from_id)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_select_for_plates(infos: &Vec<(Uuid, String)>) -> HtmlSelectElement {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
|
||||||
|
let select = document
|
||||||
|
.create_element("select")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlSelectElement>()
|
||||||
|
.unwrap();
|
||||||
|
for info in infos {
|
||||||
|
let _ = select.append_child(&create_option_for_plate(info));
|
||||||
|
}
|
||||||
|
|
||||||
|
select
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_option_for_plate(info: &(Uuid, String)) -> HtmlOptionElement {
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
|
||||||
|
let opt = document
|
||||||
|
.create_element("option")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlOptionElement>()
|
||||||
|
.unwrap();
|
||||||
|
opt.set_value(&info.0.to_string());
|
||||||
|
opt.set_inner_text(&info.1);
|
||||||
|
|
||||||
|
opt
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_all_plates_except(main_dispatch: Dispatch<MainState>, id: Uuid) -> Vec<(Uuid, String)> {
|
||||||
|
let plate_type = find_plate_type(main_dispatch.clone(), id);
|
||||||
|
if plate_type.is_none() {
|
||||||
|
return Vec::new();
|
||||||
|
} // Return early if not found somehow
|
||||||
|
let plate_type = plate_type.unwrap();
|
||||||
|
|
||||||
|
let binding = main_dispatch.get();
|
||||||
|
let all_plates_iter = match plate_type {
|
||||||
|
PlateType::Source => binding.source_plates.iter(),
|
||||||
|
PlateType::Destination => binding.destination_plates.iter(),
|
||||||
|
};
|
||||||
|
|
||||||
|
all_plates_iter
|
||||||
|
.map(|x| (x.get_uuid(), x.name.to_string()))
|
||||||
|
.filter(|y| y.0 != id)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_plate_type(main_dispatch: Dispatch<MainState>, id: Uuid) -> Option<PlateType> {
|
||||||
|
if main_dispatch
|
||||||
|
.get()
|
||||||
|
.source_plates
|
||||||
|
.iter()
|
||||||
|
.any(|x| x.get_uuid() == id)
|
||||||
|
{
|
||||||
|
return Some(PlateType::Source);
|
||||||
|
} else if main_dispatch
|
||||||
|
.get()
|
||||||
|
.destination_plates
|
||||||
|
.iter()
|
||||||
|
.any(|x| x.get_uuid() == id)
|
||||||
|
{
|
||||||
|
return Some(PlateType::Destination);
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
mod select_callbacks;
|
||||||
|
mod info_dialog_callbacks;
|
||||||
|
mod merge_callbacks;
|
||||||
|
|
||||||
|
pub use select_callbacks::*;
|
||||||
|
|
||||||
|
pub use info_dialog_callbacks::*;
|
||||||
|
|
||||||
|
pub use merge_callbacks::*;
|
|
@ -1,81 +1,12 @@
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{EventTarget, HtmlElement, HtmlInputElement, HtmlSelectElement, HtmlOptionElement};
|
use web_sys::{EventTarget, HtmlElement};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewdux::prelude::*;
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
use crate::components::states::{CurrentTransfer, MainState};
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
use plate_tool_lib::{transfer_region::Region, plate::PlateFormat};
|
use plate_tool_lib::transfer_region::Region;
|
||||||
|
|
||||||
type NoParamsCallback = Box<dyn Fn(())>;
|
|
||||||
|
|
||||||
pub fn open_plate_info_callback(
|
|
||||||
plate_menu_id: UseStateHandle<Option<Uuid>>,
|
|
||||||
) -> Callback<MouseEvent> {
|
|
||||||
Callback::from(move |e: MouseEvent| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
|
|
||||||
if let Some(li) = li {
|
|
||||||
if let Ok(id) = li.id().as_str().parse::<u128>() {
|
|
||||||
plate_menu_id.set(Some(Uuid::from_u128(id)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn plate_info_close_callback(
|
|
||||||
plate_menu_id: UseStateHandle<Option<Uuid>>,
|
|
||||||
) -> NoParamsCallback {
|
|
||||||
Box::new(move |_| {
|
|
||||||
plate_menu_id.set(None);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn plate_info_delete_callback(
|
|
||||||
main_dispatch: Dispatch<MainState>,
|
|
||||||
plate_menu_id: UseStateHandle<Option<Uuid>>,
|
|
||||||
) -> NoParamsCallback {
|
|
||||||
Box::new(move |_| {
|
|
||||||
if let Some(id) = *plate_menu_id {
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.del_plate(id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rename_onchange(id: Uuid, main_dispatch: Dispatch<MainState>) -> Callback<Event> {
|
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
log::debug!("Changed name");
|
|
||||||
let input = e
|
|
||||||
.target()
|
|
||||||
.expect("Event must have target")
|
|
||||||
.dyn_into::<HtmlInputElement>()
|
|
||||||
.unwrap();
|
|
||||||
main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn format_onchange(id: Uuid, main_dispatch: Dispatch<MainState>) -> Callback<Event> {
|
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
log::debug!("Changing plate format");
|
|
||||||
let new_format: Option<PlateFormat> = e.target()
|
|
||||||
.expect("Event must have target")
|
|
||||||
.dyn_into::<HtmlSelectElement>()
|
|
||||||
.unwrap()
|
|
||||||
.selected_options()
|
|
||||||
.get_with_index(0)
|
|
||||||
.map(|el| {
|
|
||||||
el.dyn_into::<HtmlOptionElement>().unwrap()
|
|
||||||
})
|
|
||||||
.map(|opt_el| opt_el.value())
|
|
||||||
.and_then(|value| PlateFormat::try_from(value.as_str()).ok());
|
|
||||||
if let Some(format) = new_format {
|
|
||||||
main_dispatch.reduce_mut(|state| state.change_format(id, &format));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn source_plate_select_callback(
|
pub fn source_plate_select_callback(
|
||||||
main_dispatch: Dispatch<MainState>,
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
@ -121,7 +52,11 @@ pub fn destination_plate_select_callback(
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transfer_select_callback(main_state: Rc<MainState>, main_dispatch: Dispatch<MainState>, ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<MouseEvent> {
|
pub fn transfer_select_callback(
|
||||||
|
main_state: Rc<MainState>,
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<MouseEvent> {
|
||||||
Callback::from(move |e: MouseEvent| {
|
Callback::from(move |e: MouseEvent| {
|
||||||
let target: Option<EventTarget> = e.target();
|
let target: Option<EventTarget> = e.target();
|
||||||
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
|
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
|
|
@ -43,6 +43,11 @@ pub fn MainWindow() -> Html {
|
||||||
main_window_callbacks::toggle_in_transfer_hashes_callback(main_dispatch)
|
main_window_callbacks::toggle_in_transfer_hashes_callback(main_dispatch)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let toggle_volume_heatmap_callback = {
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
|
main_window_callbacks::toggle_volume_heatmap_callback(main_dispatch)
|
||||||
|
};
|
||||||
|
|
||||||
let new_plate_dialog_is_open = use_state_eq(|| false);
|
let new_plate_dialog_is_open = use_state_eq(|| false);
|
||||||
let new_plate_dialog_callback =
|
let new_plate_dialog_callback =
|
||||||
main_window_callbacks::new_plate_dialog_callback(new_plate_dialog_is_open.clone());
|
main_window_callbacks::new_plate_dialog_callback(new_plate_dialog_is_open.clone());
|
||||||
|
@ -98,6 +103,7 @@ pub fn MainWindow() -> Html {
|
||||||
<button>{"Styles"}</button>
|
<button>{"Styles"}</button>
|
||||||
<div>
|
<div>
|
||||||
<button onclick={toggle_in_transfer_hashes_callback}>{"Toggle transfer hashes"}</button>
|
<button onclick={toggle_in_transfer_hashes_callback}>{"Toggle transfer hashes"}</button>
|
||||||
|
<button onclick={toggle_volume_heatmap_callback}>{"Toggle volume heatmap"}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -40,26 +40,40 @@ pub fn Plate(props: &PlateProps) -> Html {
|
||||||
m_end_handle.set(Some(pt2));
|
m_end_handle.set(Some(pt2));
|
||||||
}
|
}
|
||||||
|
|
||||||
let tooltip_map = {
|
let tooltip_map: HashMap<(u8, u8), Vec<&Transfer>>;
|
||||||
|
let volume_map: HashMap<(u8, u8), f32>;
|
||||||
|
let volume_max: f32;
|
||||||
|
{
|
||||||
let transfers = main_state.transfers.iter().filter(|t| match props.ptype {
|
let transfers = main_state.transfers.iter().filter(|t| match props.ptype {
|
||||||
PlateType::Source => t.source_id == props.source_plate.get_uuid(),
|
PlateType::Source => t.source_id == props.source_plate.get_uuid(),
|
||||||
PlateType::Destination => t.dest_id == props.destination_plate.get_uuid(),
|
PlateType::Destination => t.dest_id == props.destination_plate.get_uuid(),
|
||||||
});
|
});
|
||||||
let mut tooltip_map: HashMap<(u8, u8), Vec<&Transfer>> = HashMap::new();
|
let mut tooltip_map_temp: HashMap<(u8, u8), Vec<&Transfer>> = HashMap::new();
|
||||||
|
let mut volume_map_temp: HashMap<(u8,u8), f32> = HashMap::new();
|
||||||
|
let mut volume_max_temp: f32 = f32::NEG_INFINITY;
|
||||||
for transfer in transfers {
|
for transfer in transfers {
|
||||||
let wells = match props.ptype {
|
let wells = match props.ptype {
|
||||||
PlateType::Source => transfer.transfer_region.get_source_wells(),
|
PlateType::Source => transfer.transfer_region.get_source_wells(),
|
||||||
PlateType::Destination => transfer.transfer_region.get_destination_wells(),
|
PlateType::Destination => transfer.transfer_region.get_destination_wells(),
|
||||||
};
|
};
|
||||||
for well in wells {
|
for well in wells {
|
||||||
if let Some(val) = tooltip_map.get_mut(&well) {
|
if let Some(val) = tooltip_map_temp.get_mut(&well) {
|
||||||
val.push(transfer);
|
val.push(transfer);
|
||||||
} else {
|
} else {
|
||||||
tooltip_map.insert(well, vec![transfer]);
|
tooltip_map_temp.insert(well, vec![transfer]);
|
||||||
}
|
}
|
||||||
|
if let Some(val) = volume_map_temp.get_mut(&well) {
|
||||||
|
*val += transfer.volume;
|
||||||
|
} else {
|
||||||
|
log::info!("well: {:?}, vol: {:?}", well, transfer.volume);
|
||||||
|
volume_map_temp.insert(well, transfer.volume);
|
||||||
|
}
|
||||||
|
volume_max_temp = f32::max(volume_max_temp, transfer.volume);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tooltip_map
|
tooltip_map = tooltip_map_temp;
|
||||||
|
volume_map = volume_map_temp;
|
||||||
|
volume_max = volume_max_temp;
|
||||||
};
|
};
|
||||||
|
|
||||||
let wells = match props.ptype {
|
let wells = match props.ptype {
|
||||||
|
@ -120,18 +134,39 @@ pub fn Plate(props: &PlateProps) -> Html {
|
||||||
let row_header = html! {<th>{num_to_letters(i)}</th>};
|
let row_header = html! {<th>{num_to_letters(i)}</th>};
|
||||||
let row = (1..=width)
|
let row = (1..=width)
|
||||||
.map(|j| {
|
.map(|j| {
|
||||||
|
let color = {
|
||||||
|
if !main_state.preferences.volume_heatmap {
|
||||||
|
tooltip_map.get(&(i,j))
|
||||||
|
.and_then(|t| t.last())
|
||||||
|
.map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
|
||||||
|
} else {
|
||||||
|
volume_map.get(&(i,j))
|
||||||
|
.map(|t| PALETTE.get_linear(*t as f64, volume_max as f64))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let title = {
|
||||||
|
let mut out = String::new();
|
||||||
|
let used_by = tooltip_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
|
||||||
|
.collect::<Vec<_>>().join(", ")));
|
||||||
|
if let Some(val) = used_by {
|
||||||
|
out += &val;
|
||||||
|
}
|
||||||
|
let volume_sum = volume_map.get(&(i,j))
|
||||||
|
.map(|t| format!("Volume: {}", t));
|
||||||
|
if let Some(val) = volume_sum {
|
||||||
|
if !out.is_empty() { out += "\n" }
|
||||||
|
out += &val;
|
||||||
|
}
|
||||||
|
out
|
||||||
|
};
|
||||||
html! {
|
html! {
|
||||||
<PlateCell i={i} j={j}
|
<PlateCell i={i} j={j}
|
||||||
selected={in_rect(*m_start_handle.clone(), *m_end_handle.clone(), (i,j))}
|
selected={in_rect(*m_start_handle.clone(), *m_end_handle.clone(), (i,j))}
|
||||||
mouse={mouse_callback.clone()}
|
mouse={mouse_callback.clone()}
|
||||||
in_transfer={wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
|
in_transfer={wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
|
||||||
color={tooltip_map.get(&(i,j))
|
color={ color }
|
||||||
.and_then(|t| t.last())
|
|
||||||
.map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
|
|
||||||
}
|
|
||||||
cell_height={props.cell_height}
|
cell_height={props.cell_height}
|
||||||
title={tooltip_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
|
title={title}
|
||||||
.collect::<Vec<_>>().join(", ")))}
|
|
||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
|
@ -26,7 +26,7 @@ impl ColorPalette {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn _get_u8(&self, t: u8) -> [f64; 3] {
|
fn get_u8(&self, t: u8) -> [f64; 3] {
|
||||||
assert!(t > 0, "t must be greater than zero!");
|
assert!(t > 0, "t must be greater than zero!");
|
||||||
self.get((2f64.powi(-(t.ilog2() as i32))) * (t as f64 + 0.5f64) - 1.0f64)
|
self.get((2f64.powi(-(t.ilog2() as i32))) * (t as f64 + 0.5f64) - 1.0f64)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,11 @@ impl ColorPalette {
|
||||||
self.get(Self::space_evenly(index))
|
self.get(Self::space_evenly(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_linear(&self, t: f64, max: f64) -> [f64; 3] {
|
||||||
|
let scaled = t / max;
|
||||||
|
self.get(scaled)
|
||||||
|
}
|
||||||
|
|
||||||
fn space_evenly(x: usize) -> f64 {
|
fn space_evenly(x: usize) -> f64 {
|
||||||
let e: usize = (x.ilog2() + 1) as usize;
|
let e: usize = (x.ilog2() + 1) as usize;
|
||||||
let d: usize = 2usize.pow(e as u32);
|
let d: usize = 2usize.pow(e as u32);
|
||||||
|
|
|
@ -15,12 +15,15 @@ pub struct CurrentTransfer {
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Copy, Serialize, Deserialize)]
|
||||||
pub struct Preferences {
|
pub struct Preferences {
|
||||||
|
#[serde(default)]
|
||||||
pub in_transfer_hashes: bool,
|
pub in_transfer_hashes: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub volume_heatmap: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Preferences {
|
impl Default for Preferences {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self { in_transfer_hashes: true }
|
Self { in_transfer_hashes: true, volume_heatmap: false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,6 +181,11 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
|
||||||
move |_| dialog_close_callback.emit(())
|
move |_| dialog_close_callback.emit(())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let onclose_secondary = {
|
||||||
|
let dialog_close_callback = props.dialog_close_callback.clone();
|
||||||
|
move |_| dialog_close_callback.emit(())
|
||||||
|
};
|
||||||
|
|
||||||
let rename_onchange = tree_callbacks::rename_onchange(props.id, main_dispatch.clone());
|
let rename_onchange = tree_callbacks::rename_onchange(props.id, main_dispatch.clone());
|
||||||
|
|
||||||
let format_onchange = tree_callbacks::format_onchange(props.id, main_dispatch.clone());
|
let format_onchange = tree_callbacks::format_onchange(props.id, main_dispatch.clone());
|
||||||
|
@ -209,6 +214,11 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let merge_button_callback = {
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
|
tree_callbacks::merge_button_callback(main_dispatch, props.id)
|
||||||
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<dialog ref={dialog_ref} class="dialog" onclose={onclose}>
|
<dialog ref={dialog_ref} class="dialog" onclose={onclose}>
|
||||||
<h2>{"Plate Info"}</h2>
|
<h2>{"Plate Info"}</h2>
|
||||||
|
@ -219,6 +229,7 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
|
||||||
{ for plate_format_options }
|
{ for plate_format_options }
|
||||||
</select>
|
</select>
|
||||||
}
|
}
|
||||||
|
<button onclick={merge_button_callback} onclick={onclose_secondary}>{"Merge"}</button>
|
||||||
<form class="modal_close" method="dialog"><button /></form>
|
<form class="modal_close" method="dialog"><button /></form>
|
||||||
</dialog>
|
</dialog>
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue