Add callbacks per #2
Gitea Scan/plate-tool/pipeline/head There was a failure building this commit
Details
Gitea Scan/plate-tool/pipeline/head There was a failure building this commit
Details
Squashed commit of the following: commit4710335750
Author: Emilia <contact@emiliaallison.com> Date: Sat Feb 10 16:34:52 2024 -0500 tree callbacks commitf8216cb0bd
Author: Emilia <contact@emiliaallison.com> Date: Sat Feb 10 09:20:59 2024 -0500 transfer_menu callbacks commit15accc2fca
Author: Emilia <contact@emiliaallison.com> Date: Tue Jan 30 20:37:15 2024 -0500 I think this file was supposed to be in the last one lol commit53457a3e86
Author: Emilia <contact@emiliaallison.com> Date: Sat Jan 13 14:05:34 2024 -0500 new_plate_dialog callbacks For #2 commitc2a3f0302b
Author: Emilia <contact@emiliaallison.com> Date: Sat Jan 13 13:57:14 2024 -0500 Finish main_window callback refactor For #2 commit820f672cb7
Author: Emilia <contact@emiliaallison.com> Date: Sat Jan 13 13:23:11 2024 -0500 Import json button refactor Partial for #2 commita90b5f83d8
Author: Emilia <contact@emiliaallison.com> Date: Fri Jan 12 22:22:28 2024 -0500 A number of moves to address #2 commit62d870521e
Author: Emilia <contact@emiliaallison.com> Date: Fri Jan 12 21:44:10 2024 -0500 First callback move commite2ef9fa84d
Author: Emilia <contact@emiliaallison.com> Date: Fri Jan 12 21:43:50 2024 -0500 Increment version number in cargo.lock why wasn't this done already?
This commit is contained in:
parent
0b6aec2f6c
commit
020f7740d3
|
@ -558,7 +558,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "plate-tool"
|
name = "plate-tool"
|
||||||
version = "0.1.0"
|
version = "0.2.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
|
|
@ -0,0 +1,433 @@
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use js_sys::Array;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
|
use web_sys::{
|
||||||
|
Blob, FileReader, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement,
|
||||||
|
HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url,
|
||||||
|
};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
|
use crate::components::transfer_menu::letters_to_num;
|
||||||
|
|
||||||
|
use crate::data::transfer::Transfer;
|
||||||
|
use crate::data::transfer_region::{Region, TransferRegion};
|
||||||
|
|
||||||
|
use crate::data::csv::{state_to_csv, TransferRecord};
|
||||||
|
|
||||||
|
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 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()));
|
||||||
|
onclose_callback.forget();
|
||||||
|
|
||||||
|
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 fn import_transfer_csv_submit_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
from_source: HtmlSelectElement,
|
||||||
|
to_source: HtmlSelectElement,
|
||||||
|
from_dest: HtmlSelectElement,
|
||||||
|
to_dest: HtmlSelectElement,
|
||||||
|
records: Vec<TransferRecord>,
|
||||||
|
) -> Closure<dyn FnMut(Event)> {
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||||
|
let from_source = from_source.value();
|
||||||
|
let to_source = to_source.value();
|
||||||
|
let from_dest = from_dest.value();
|
||||||
|
let to_dest = to_dest.value();
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref REGEX: Regex = Regex::new(r"([A-Z]+)(\d+)").unwrap();
|
||||||
|
}
|
||||||
|
let records: Vec<((u8, u8), (u8, u8))> = records
|
||||||
|
.iter()
|
||||||
|
.filter(|record| record.source_plate == from_source)
|
||||||
|
.filter(|record| record.destination_plate == from_dest)
|
||||||
|
.map(|record| {
|
||||||
|
let c1 = REGEX.captures(&record.source_well).unwrap();
|
||||||
|
let c2 = REGEX.captures(&record.destination_well).unwrap();
|
||||||
|
log::debug!("{} {}", &record.source_well, &record.destination_well);
|
||||||
|
log::debug!("{},{} {},{}", &c1[1], &c1[2], &c2[1], &c2[2]);
|
||||||
|
|
||||||
|
(
|
||||||
|
(
|
||||||
|
letters_to_num(&c1[1]).unwrap(),
|
||||||
|
c1[2].parse::<u8>().unwrap(),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
letters_to_num(&c2[1]).unwrap(),
|
||||||
|
c2[2].parse::<u8>().unwrap(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let spi = main_dispatch
|
||||||
|
.get()
|
||||||
|
.source_plates
|
||||||
|
.iter()
|
||||||
|
.find(|src| src.name == to_source)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
let dpi = main_dispatch
|
||||||
|
.get()
|
||||||
|
.destination_plates
|
||||||
|
.iter()
|
||||||
|
.find(|dest| dest.name == to_dest)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let custom_region = Region::new_custom(&records);
|
||||||
|
let transfer_region = TransferRegion {
|
||||||
|
source_region: custom_region.clone(),
|
||||||
|
dest_region: custom_region,
|
||||||
|
interleave_source: (1, 1),
|
||||||
|
interleave_dest: (1, 1),
|
||||||
|
source_plate: spi.plate,
|
||||||
|
dest_plate: dpi.plate,
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer = Transfer::new(spi, dpi, transfer_region, "Custom Transfer".to_string());
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfers.push(transfer);
|
||||||
|
state.selected_transfer = state
|
||||||
|
.transfers
|
||||||
|
.last()
|
||||||
|
.expect("An element should have just been added")
|
||||||
|
.get_uuid();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_transfer_csv_onload_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
file_reader: FileReader,
|
||||||
|
modal: HtmlDialogElement,
|
||||||
|
) -> Closure<dyn FnMut(Event)> {
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||||
|
if let Some(value) = &file_reader.result().ok().and_then(|v| v.as_string()) {
|
||||||
|
let mut rdr = csv::Reader::from_reader(value.as_bytes());
|
||||||
|
let mut records = Vec::new();
|
||||||
|
for record in rdr.deserialize::<crate::data::csv::TransferRecord>() {
|
||||||
|
match record {
|
||||||
|
Ok(r) => {
|
||||||
|
//log::debug!("{:?}", r);
|
||||||
|
records.push(r);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
log::debug!("{:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sources: HashSet<String> = HashSet::new();
|
||||||
|
let mut destinations: HashSet<String> = HashSet::new();
|
||||||
|
for record in records.iter() {
|
||||||
|
sources.insert(record.source_plate.clone());
|
||||||
|
destinations.insert(record.destination_plate.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let window = web_sys::window().unwrap();
|
||||||
|
let document = window.document().unwrap();
|
||||||
|
let form = document
|
||||||
|
.create_element("form")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlFormElement>()
|
||||||
|
.unwrap();
|
||||||
|
let from_source = document
|
||||||
|
.create_element("select")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlSelectElement>()
|
||||||
|
.unwrap();
|
||||||
|
for source in sources {
|
||||||
|
let option = document
|
||||||
|
.create_element("option")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlOptionElement>()
|
||||||
|
.unwrap();
|
||||||
|
option.set_value(&source);
|
||||||
|
option.set_text(&source);
|
||||||
|
from_source.append_child(&option).unwrap();
|
||||||
|
}
|
||||||
|
let to_source = document
|
||||||
|
.create_element("select")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlSelectElement>()
|
||||||
|
.unwrap();
|
||||||
|
for source in &main_dispatch.get().source_plates {
|
||||||
|
let option = document
|
||||||
|
.create_element("option")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlOptionElement>()
|
||||||
|
.unwrap();
|
||||||
|
option.set_value(&source.name);
|
||||||
|
option.set_text(&source.name);
|
||||||
|
to_source.append_child(&option).unwrap();
|
||||||
|
}
|
||||||
|
let from_dest = document
|
||||||
|
.create_element("select")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlSelectElement>()
|
||||||
|
.unwrap();
|
||||||
|
for dest in destinations {
|
||||||
|
let option = document
|
||||||
|
.create_element("option")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlOptionElement>()
|
||||||
|
.unwrap();
|
||||||
|
option.set_value(&dest);
|
||||||
|
option.set_text(&dest);
|
||||||
|
from_dest.append_child(&option).unwrap();
|
||||||
|
}
|
||||||
|
let to_dest = document
|
||||||
|
.create_element("select")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlSelectElement>()
|
||||||
|
.unwrap();
|
||||||
|
for dest in &main_dispatch.get().destination_plates {
|
||||||
|
let option = document
|
||||||
|
.create_element("option")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlOptionElement>()
|
||||||
|
.unwrap();
|
||||||
|
option.set_value(&dest.name);
|
||||||
|
option.set_text(&dest.name);
|
||||||
|
to_dest.append_child(&option).unwrap();
|
||||||
|
}
|
||||||
|
let submit = document
|
||||||
|
.create_element("button")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlButtonElement>()
|
||||||
|
.unwrap();
|
||||||
|
submit.set_value("Submit");
|
||||||
|
submit.set_inner_text("Submit");
|
||||||
|
let submit_callback = {
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
|
let from_source = from_source.clone();
|
||||||
|
let to_source = to_source.clone();
|
||||||
|
let from_dest = from_dest.clone();
|
||||||
|
let to_dest = to_dest.clone();
|
||||||
|
import_transfer_csv_submit_callback(
|
||||||
|
main_dispatch,
|
||||||
|
from_source,
|
||||||
|
to_source,
|
||||||
|
from_dest,
|
||||||
|
to_dest,
|
||||||
|
records,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
submit.set_onclick(Some(submit_callback.as_ref().unchecked_ref()));
|
||||||
|
submit_callback.forget();
|
||||||
|
|
||||||
|
form.append_child(&from_source).unwrap();
|
||||||
|
form.append_child(&to_source).unwrap();
|
||||||
|
form.append_child(&from_dest).unwrap();
|
||||||
|
form.append_child(&to_dest).unwrap();
|
||||||
|
modal.append_child(&submit).unwrap();
|
||||||
|
modal.append_child(&form).unwrap();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn import_transfer_csv_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 = import_transfer_csv_onload_callback(main_dispatch, fr1, modal);
|
||||||
|
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
|
||||||
|
onload.forget(); // Magic (don't touch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
pub mod main_window_callbacks;
|
||||||
|
pub mod new_plate_dialog_callbacks;
|
||||||
|
pub mod transfer_menu_callbacks;
|
||||||
|
pub mod tree_callbacks;
|
|
@ -0,0 +1,61 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{EventTarget, FormData, HtmlDialogElement, HtmlFormElement};
|
||||||
|
|
||||||
|
use crate::components::states::MainState;
|
||||||
|
use crate::data::plate::*;
|
||||||
|
use crate::data::plate_instances::PlateInstance;
|
||||||
|
|
||||||
|
pub fn new_plate_callback(
|
||||||
|
dispatch: Dispatch<MainState>,
|
||||||
|
close_callback: Callback<()>,
|
||||||
|
) -> Callback<SubmitEvent> {
|
||||||
|
Callback::from(move |e: SubmitEvent| {
|
||||||
|
e.prevent_default();
|
||||||
|
close_callback.emit(());
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let form = target.and_then(|t| t.dyn_into::<HtmlFormElement>().ok());
|
||||||
|
if let Some(form) = form {
|
||||||
|
if let Ok(form_data) = FormData::new_with_form(&form) {
|
||||||
|
let name = form_data.get("new_plate_name").as_string().unwrap();
|
||||||
|
let format = match form_data.get("plate_format").as_string().unwrap().as_str() {
|
||||||
|
"6" => PlateFormat::W6,
|
||||||
|
"12" => PlateFormat::W12,
|
||||||
|
"24" => PlateFormat::W24,
|
||||||
|
"48" => PlateFormat::W48,
|
||||||
|
"96" => PlateFormat::W96,
|
||||||
|
"384" => PlateFormat::W384,
|
||||||
|
"1536" => PlateFormat::W1536,
|
||||||
|
"3456" => PlateFormat::W3456,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
if let Some(pt_string) = form_data.get("new_plate_type").as_string() {
|
||||||
|
let plate_type = match pt_string.as_str() {
|
||||||
|
"src" => PlateType::Source,
|
||||||
|
"dest" => PlateType::Destination,
|
||||||
|
_ => PlateType::Source,
|
||||||
|
};
|
||||||
|
dispatch.reduce_mut(|s| {
|
||||||
|
if plate_type == PlateType::Source {
|
||||||
|
s.add_source_plate(PlateInstance::new(PlateType::Source, format, name))
|
||||||
|
} else {
|
||||||
|
s.add_dest_plate(PlateInstance::new(
|
||||||
|
PlateType::Destination,
|
||||||
|
format,
|
||||||
|
name,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn onclose(close_callback: Callback<()>) -> Callback<Event> {
|
||||||
|
Callback::from(move |_: Event| {
|
||||||
|
close_callback.emit(());
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,242 @@
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::rc::Rc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{EventTarget, HtmlInputElement};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::transfer_menu::RegionDisplay;
|
||||||
|
use crate::data::{transfer::Transfer, transfer_region::Region};
|
||||||
|
|
||||||
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
|
|
||||||
|
pub fn on_name_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if input.value() == "" {
|
||||||
|
return;
|
||||||
|
} // We do not want empty inputs!
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.name = input.value();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_src_region_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
if matches!(
|
||||||
|
ct_dispatch.get().transfer.transfer_region.source_region,
|
||||||
|
Region::Custom(_)
|
||||||
|
) {
|
||||||
|
return; // Do nothing here!
|
||||||
|
}
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.source_region = Region::from(&rd);
|
||||||
|
});
|
||||||
|
input.set_custom_validity("");
|
||||||
|
} else {
|
||||||
|
input.set_custom_validity("Invalid region.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_dest_region_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.dest_region = Region::from(&rd);
|
||||||
|
});
|
||||||
|
input.set_custom_validity("");
|
||||||
|
} else {
|
||||||
|
input.set_custom_validity("Invalid region.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_source_interleave_x_change_callback(
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if let Ok(num) = input.value().parse::<i8>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.interleave_source =
|
||||||
|
(num, state.transfer.transfer_region.interleave_source.1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_source_interleave_y_change_callback(
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if let Ok(num) = input.value().parse::<i8>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.interleave_source =
|
||||||
|
(state.transfer.transfer_region.interleave_source.0, num);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_dest_interleave_x_change_callback(
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if let Ok(num) = input.value().parse::<i8>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.interleave_dest =
|
||||||
|
(num, state.transfer.transfer_region.interleave_dest.1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_dest_interleave_y_change_callback(
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let target: Option<EventTarget> = e.target();
|
||||||
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
|
if let Some(input) = input {
|
||||||
|
if let Ok(num) = input.value().parse::<i8>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.interleave_dest =
|
||||||
|
(state.transfer.transfer_region.interleave_dest.0, num);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_volume_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
|
||||||
|
Callback::from(move |e: Event| {
|
||||||
|
let input = e
|
||||||
|
.target()
|
||||||
|
.expect("Event must have target")
|
||||||
|
.dyn_into::<HtmlInputElement>()
|
||||||
|
.expect("Must have been emitted by input");
|
||||||
|
if let Ok(num) = input.value().parse::<f32>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.volume = num;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_transfer_button_callback_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
main_state: Rc<MainState>,
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_: MouseEvent| {
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.selected_transfer = Uuid::nil();
|
||||||
|
});
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer = Transfer::default();
|
||||||
|
state.transfer.source_id = main_state.selected_source_plate;
|
||||||
|
state.transfer.dest_id = main_state.selected_dest_plate;
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_transfer_button_callback_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
main_state: Rc<MainState>,
|
||||||
|
ct_state: Rc<CurrentTransfer>,
|
||||||
|
new_transfer_button_callback: Callback<MouseEvent>)
|
||||||
|
-> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |e: MouseEvent| {
|
||||||
|
log::debug!("Button pressed");
|
||||||
|
if main_state.selected_transfer.is_nil() {
|
||||||
|
if let Some(spi) = main_state
|
||||||
|
.source_plates
|
||||||
|
.iter()
|
||||||
|
.find(|spi| spi.get_uuid() == main_state.selected_source_plate)
|
||||||
|
{
|
||||||
|
if let Some(dpi) = main_state
|
||||||
|
.destination_plates
|
||||||
|
.iter()
|
||||||
|
.find(|dpi| dpi.get_uuid() == main_state.selected_dest_plate)
|
||||||
|
{
|
||||||
|
let new_transfer = Transfer::new(
|
||||||
|
spi.clone(),
|
||||||
|
dpi.clone(),
|
||||||
|
ct_state.transfer.transfer_region.clone(),
|
||||||
|
ct_state.transfer.name.clone(),
|
||||||
|
);
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfers.push(new_transfer);
|
||||||
|
state.selected_transfer = state
|
||||||
|
.transfers
|
||||||
|
.last()
|
||||||
|
.expect("An element should have just been added")
|
||||||
|
.get_uuid();
|
||||||
|
});
|
||||||
|
new_transfer_button_callback.emit(e); // If we just made a new transfer,
|
||||||
|
// then we should make another on
|
||||||
|
// save.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Some(index) = main_state
|
||||||
|
.transfers
|
||||||
|
.iter()
|
||||||
|
.position(|t| t.get_uuid() == main_state.selected_transfer)
|
||||||
|
{
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfers[index] = ct_state.transfer.clone();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete_transfer_button_callback(
|
||||||
|
main_state: Rc<MainState>,
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
ct_state: Rc<CurrentTransfer>,
|
||||||
|
new_transfer_button_callback: Callback<MouseEvent>
|
||||||
|
) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |e: MouseEvent| {
|
||||||
|
if main_state.selected_transfer.is_nil() {
|
||||||
|
// Maybe reset transfer?
|
||||||
|
} else if let Some(index) = main_state
|
||||||
|
.transfers
|
||||||
|
.iter()
|
||||||
|
.position(|t| t.get_uuid() == ct_state.transfer.get_uuid())
|
||||||
|
{
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfers.remove(index);
|
||||||
|
state.selected_transfer = Uuid::nil();
|
||||||
|
});
|
||||||
|
new_transfer_button_callback.emit(e); // We need a new transfer now
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
use uuid::Uuid;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use web_sys::{EventTarget, HtmlElement};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
|
use crate::data::transfer_region::Region;
|
||||||
|
|
||||||
|
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>>,
|
||||||
|
) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
plate_menu_id.set(None);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn plate_info_delete_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
plate_menu_id: UseStateHandle<Option<Uuid>>,
|
||||||
|
) -> Callback<MouseEvent> {
|
||||||
|
Callback::from(move |_| {
|
||||||
|
if let Some(id) = *plate_menu_id {
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.del_plate(id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn source_plate_select_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> 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>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.source_region = Region::default();
|
||||||
|
state.transfer.transfer_region.dest_region = Region::default();
|
||||||
|
});
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.selected_source_plate = Uuid::from_u128(id);
|
||||||
|
state.selected_transfer = Uuid::nil();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn destination_plate_select_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||||
|
) -> 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>() {
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer.transfer_region.source_region = Region::default();
|
||||||
|
state.transfer.transfer_region.dest_region = Region::default();
|
||||||
|
});
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.selected_dest_plate = Uuid::from_u128(id);
|
||||||
|
state.selected_transfer = Uuid::nil();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transfer_select_callback(main_state: Rc<MainState>) -> 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>() {
|
||||||
|
let id = Uuid::from_u128(id);
|
||||||
|
if let Some(transfer) = main_state
|
||||||
|
.transfers
|
||||||
|
.iter()
|
||||||
|
.find(|transfer| transfer.get_uuid() == id)
|
||||||
|
{
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.selected_source_plate = transfer.source_id;
|
||||||
|
state.selected_dest_plate = transfer.dest_id;
|
||||||
|
state.selected_transfer = id;
|
||||||
|
});
|
||||||
|
ct_dispatch.reduce_mut(|state| {
|
||||||
|
state.transfer = transfer.clone();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,27 +1,20 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use js_sys::Array;
|
use js_sys::Array;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
use web_sys::{
|
use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url};
|
||||||
Blob, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement,
|
|
||||||
HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url,
|
|
||||||
};
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewdux::prelude::*;
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
use super::new_plate_dialog::NewPlateDialog;
|
use crate::components::new_plate_dialog::NewPlateDialog;
|
||||||
use super::plates::plate_container::PlateContainer;
|
use crate::components::plates::plate_container::PlateContainer;
|
||||||
use super::states::{CurrentTransfer, MainState};
|
use crate::components::states::{CurrentTransfer, MainState};
|
||||||
use super::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu};
|
use crate::components::transfer_menu::TransferMenu;
|
||||||
use super::tree::Tree;
|
use crate::components::tree::Tree;
|
||||||
|
|
||||||
use crate::data::csv::state_to_csv;
|
|
||||||
use crate::data::plate_instances::PlateInstance;
|
use crate::data::plate_instances::PlateInstance;
|
||||||
use crate::data::transfer::Transfer;
|
|
||||||
use crate::data::transfer_region::{Region, TransferRegion};
|
use crate::components::callbacks::main_window_callbacks;
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
pub fn MainWindow() -> Html {
|
pub fn MainWindow() -> Html {
|
||||||
|
@ -51,149 +44,32 @@ pub fn MainWindow() -> Html {
|
||||||
|
|
||||||
let toggle_in_transfer_hashes_callback = {
|
let toggle_in_transfer_hashes_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
Callback::from(move |_| {
|
main_window_callbacks::toggle_in_transfer_hashes_callback(main_dispatch)
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.preferences.in_transfer_hashes ^= true;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
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 =
|
||||||
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
main_window_callbacks::new_plate_dialog_callback(new_plate_dialog_is_open.clone());
|
||||||
Callback::from(move |_| {
|
|
||||||
new_plate_dialog_is_open.set(false);
|
let open_new_plate_dialog_callback =
|
||||||
})
|
main_window_callbacks::open_new_plate_dialog_callback(new_plate_dialog_is_open.clone());
|
||||||
};
|
|
||||||
let open_new_plate_dialog_callback = {
|
|
||||||
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
|
||||||
Callback::from(move |_| {
|
|
||||||
new_plate_dialog_is_open.set(true);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_button_callback = {
|
let new_button_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
Callback::from(move |_| {
|
main_window_callbacks::new_button_callback(main_dispatch, ct_dispatch)
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let export_csv_button_callback = {
|
let export_csv_button_callback = {
|
||||||
let main_state = main_state.clone();
|
let main_state = main_state.clone();
|
||||||
Callback::from(move |_| {
|
main_window_callbacks::export_csv_button_callback(main_state)
|
||||||
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");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let export_json_button_callback = {
|
let export_json_button_callback =
|
||||||
Callback::from(move |_| {
|
{ main_window_callbacks::export_json_button_callback(main_state) };
|
||||||
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();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let import_json_button_callback = {
|
let import_json_button_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
Callback::from(move |_| {
|
main_window_callbacks::import_json_button_callback(main_dispatch)
|
||||||
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()));
|
|
||||||
onclose_callback.forget();
|
|
||||||
|
|
||||||
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();
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
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();
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let import_transfer_csv_callback = {
|
let import_transfer_csv_callback = {
|
||||||
|
@ -233,227 +109,7 @@ pub fn MainWindow() -> Html {
|
||||||
let input_callback = {
|
let input_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
let modal = modal.clone();
|
let modal = modal.clone();
|
||||||
Closure::<dyn FnMut(_)>::new(move |e: Event| {
|
main_window_callbacks::import_transfer_csv_input_callback(main_dispatch, modal)
|
||||||
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 mut rdr = csv::Reader::from_reader(value.as_bytes());
|
|
||||||
let mut records = Vec::new();
|
|
||||||
for record in
|
|
||||||
rdr.deserialize::<crate::data::csv::TransferRecord>()
|
|
||||||
{
|
|
||||||
match record {
|
|
||||||
Ok(r) => {
|
|
||||||
//log::debug!("{:?}", r);
|
|
||||||
records.push(r);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
log::debug!("{:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut sources: HashSet<String> = HashSet::new();
|
|
||||||
let mut destinations: HashSet<String> = HashSet::new();
|
|
||||||
for record in records.iter() {
|
|
||||||
sources.insert(record.source_plate.clone());
|
|
||||||
destinations.insert(record.destination_plate.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
|
||||||
let document = window.document().unwrap();
|
|
||||||
let form = document
|
|
||||||
.create_element("form")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlFormElement>()
|
|
||||||
.unwrap();
|
|
||||||
let from_source = document
|
|
||||||
.create_element("select")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlSelectElement>()
|
|
||||||
.unwrap();
|
|
||||||
for source in sources {
|
|
||||||
let option = document
|
|
||||||
.create_element("option")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlOptionElement>()
|
|
||||||
.unwrap();
|
|
||||||
option.set_value(&source);
|
|
||||||
option.set_text(&source);
|
|
||||||
from_source.append_child(&option).unwrap();
|
|
||||||
}
|
|
||||||
let to_source = document
|
|
||||||
.create_element("select")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlSelectElement>()
|
|
||||||
.unwrap();
|
|
||||||
for source in &main_dispatch.get().source_plates {
|
|
||||||
let option = document
|
|
||||||
.create_element("option")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlOptionElement>()
|
|
||||||
.unwrap();
|
|
||||||
option.set_value(&source.name);
|
|
||||||
option.set_text(&source.name);
|
|
||||||
to_source.append_child(&option).unwrap();
|
|
||||||
}
|
|
||||||
let from_dest = document
|
|
||||||
.create_element("select")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlSelectElement>()
|
|
||||||
.unwrap();
|
|
||||||
for dest in destinations {
|
|
||||||
let option = document
|
|
||||||
.create_element("option")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlOptionElement>()
|
|
||||||
.unwrap();
|
|
||||||
option.set_value(&dest);
|
|
||||||
option.set_text(&dest);
|
|
||||||
from_dest.append_child(&option).unwrap();
|
|
||||||
}
|
|
||||||
let to_dest = document
|
|
||||||
.create_element("select")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlSelectElement>()
|
|
||||||
.unwrap();
|
|
||||||
for dest in &main_dispatch.get().destination_plates {
|
|
||||||
let option = document
|
|
||||||
.create_element("option")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlOptionElement>()
|
|
||||||
.unwrap();
|
|
||||||
option.set_value(&dest.name);
|
|
||||||
option.set_text(&dest.name);
|
|
||||||
to_dest.append_child(&option).unwrap();
|
|
||||||
}
|
|
||||||
let submit = document
|
|
||||||
.create_element("button")
|
|
||||||
.unwrap()
|
|
||||||
.dyn_into::<HtmlButtonElement>()
|
|
||||||
.unwrap();
|
|
||||||
submit.set_value("Submit");
|
|
||||||
submit.set_inner_text("Submit");
|
|
||||||
let submit_callback = {
|
|
||||||
let main_dispatch = main_dispatch.clone();
|
|
||||||
let from_source = from_source.clone();
|
|
||||||
let to_source = to_source.clone();
|
|
||||||
let from_dest = from_dest.clone();
|
|
||||||
let to_dest = to_dest.clone();
|
|
||||||
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
|
||||||
let from_source = from_source.value();
|
|
||||||
let to_source = to_source.value();
|
|
||||||
let from_dest = from_dest.value();
|
|
||||||
let to_dest = to_dest.value();
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref REGEX: Regex =
|
|
||||||
Regex::new(r"([A-Z]+)(\d+)").unwrap();
|
|
||||||
}
|
|
||||||
let records: Vec<((u8, u8), (u8, u8))> = records
|
|
||||||
.iter()
|
|
||||||
.filter(|record| {
|
|
||||||
record.source_plate == from_source
|
|
||||||
})
|
|
||||||
.filter(|record| {
|
|
||||||
record.destination_plate == from_dest
|
|
||||||
})
|
|
||||||
.map(|record| {
|
|
||||||
let c1 = REGEX
|
|
||||||
.captures(&record.source_well)
|
|
||||||
.unwrap();
|
|
||||||
let c2 = REGEX
|
|
||||||
.captures(&record.destination_well)
|
|
||||||
.unwrap();
|
|
||||||
log::debug!("{} {}", &record.source_well, &record.destination_well);
|
|
||||||
log::debug!("{},{} {},{}", &c1[1], &c1[2], &c2[1], &c2[2]);
|
|
||||||
|
|
||||||
(
|
|
||||||
(
|
|
||||||
letters_to_num(&c1[1]).unwrap(),
|
|
||||||
c1[2].parse::<u8>().unwrap(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
letters_to_num(&c2[1]).unwrap(),
|
|
||||||
c2[2].parse::<u8>().unwrap(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let spi = main_dispatch
|
|
||||||
.get()
|
|
||||||
.source_plates
|
|
||||||
.iter()
|
|
||||||
.find(|src| src.name == to_source)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
let dpi = main_dispatch
|
|
||||||
.get()
|
|
||||||
.destination_plates
|
|
||||||
.iter()
|
|
||||||
.find(|dest| dest.name == to_dest)
|
|
||||||
.unwrap()
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let custom_region = Region::new_custom(&records);
|
|
||||||
let transfer_region = TransferRegion {
|
|
||||||
source_region: custom_region.clone(),
|
|
||||||
dest_region: custom_region,
|
|
||||||
interleave_source: (1, 1),
|
|
||||||
interleave_dest: (1, 1),
|
|
||||||
source_plate: spi.plate,
|
|
||||||
dest_plate: dpi.plate,
|
|
||||||
};
|
|
||||||
|
|
||||||
let transfer = Transfer::new(
|
|
||||||
spi,
|
|
||||||
dpi,
|
|
||||||
transfer_region,
|
|
||||||
"Custom Transfer".to_string(),
|
|
||||||
);
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfers.push(transfer);
|
|
||||||
state.selected_transfer = state
|
|
||||||
.transfers
|
|
||||||
.last()
|
|
||||||
.expect("An element should have just been added")
|
|
||||||
.get_uuid();
|
|
||||||
});
|
|
||||||
})
|
|
||||||
};
|
|
||||||
submit.set_onclick(Some(
|
|
||||||
submit_callback.as_ref().unchecked_ref(),
|
|
||||||
));
|
|
||||||
submit_callback.forget();
|
|
||||||
|
|
||||||
form.append_child(&from_source).unwrap();
|
|
||||||
form.append_child(&to_source).unwrap();
|
|
||||||
form.append_child(&from_dest).unwrap();
|
|
||||||
form.append_child(&to_dest).unwrap();
|
|
||||||
modal.append_child(&submit).unwrap();
|
|
||||||
modal.append_child(&form).unwrap();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
|
|
||||||
onload.forget(); // Magic (don't touch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
input.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
|
input.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
|
||||||
input_callback.forget(); // Magic straight from the docs, don't touch :(
|
input_callback.forget(); // Magic straight from the docs, don't touch :(
|
||||||
|
|
|
@ -4,3 +4,4 @@ pub mod plates;
|
||||||
pub mod states;
|
pub mod states;
|
||||||
pub mod transfer_menu;
|
pub mod transfer_menu;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
|
mod callbacks;
|
||||||
|
|
|
@ -8,6 +8,8 @@ use crate::components::states::MainState;
|
||||||
use crate::data::plate::*;
|
use crate::data::plate::*;
|
||||||
use crate::data::plate_instances::PlateInstance;
|
use crate::data::plate_instances::PlateInstance;
|
||||||
|
|
||||||
|
use crate::components::callbacks::new_plate_dialog_callbacks;
|
||||||
|
|
||||||
#[derive(PartialEq, Properties)]
|
#[derive(PartialEq, Properties)]
|
||||||
pub struct NewPlateDialogProps {
|
pub struct NewPlateDialogProps {
|
||||||
pub close_callback: Callback<()>,
|
pub close_callback: Callback<()>,
|
||||||
|
@ -19,57 +21,12 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html {
|
||||||
|
|
||||||
let new_plate_callback = {
|
let new_plate_callback = {
|
||||||
let close_callback = props.close_callback.clone();
|
let close_callback = props.close_callback.clone();
|
||||||
Callback::from(move |e: SubmitEvent| {
|
new_plate_dialog_callbacks::new_plate_callback(dispatch, close_callback)
|
||||||
e.prevent_default();
|
|
||||||
close_callback.emit(());
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let form = target.and_then(|t| t.dyn_into::<HtmlFormElement>().ok());
|
|
||||||
if let Some(form) = form {
|
|
||||||
if let Ok(form_data) = FormData::new_with_form(&form) {
|
|
||||||
let name = form_data.get("new_plate_name").as_string().unwrap();
|
|
||||||
let format = match form_data.get("plate_format").as_string().unwrap().as_str() {
|
|
||||||
"6" => PlateFormat::W6,
|
|
||||||
"12" => PlateFormat::W12,
|
|
||||||
"24" => PlateFormat::W24,
|
|
||||||
"48" => PlateFormat::W48,
|
|
||||||
"96" => PlateFormat::W96,
|
|
||||||
"384" => PlateFormat::W384,
|
|
||||||
"1536" => PlateFormat::W1536,
|
|
||||||
"3456" => PlateFormat::W3456,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
if let Some(pt_string) = form_data.get("new_plate_type").as_string() {
|
|
||||||
let plate_type = match pt_string.as_str() {
|
|
||||||
"src" => PlateType::Source,
|
|
||||||
"dest" => PlateType::Destination,
|
|
||||||
_ => PlateType::Source,
|
|
||||||
};
|
|
||||||
dispatch.reduce_mut(|s| {
|
|
||||||
if plate_type == PlateType::Source {
|
|
||||||
s.add_source_plate(PlateInstance::new(
|
|
||||||
PlateType::Source,
|
|
||||||
format,
|
|
||||||
name,
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
s.add_dest_plate(PlateInstance::new(
|
|
||||||
PlateType::Destination,
|
|
||||||
format,
|
|
||||||
name,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let onclose = {
|
let onclose = {
|
||||||
let close_callback = props.close_callback.clone();
|
let close_callback = props.close_callback.clone();
|
||||||
Callback::from(move |_: Event| {
|
new_plate_dialog_callbacks::onclose(close_callback)
|
||||||
close_callback.emit(());
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// This whole section is optional, only if you want the backdrop
|
// This whole section is optional, only if you want the backdrop
|
||||||
|
|
|
@ -9,6 +9,7 @@ use web_sys::{EventTarget, HtmlInputElement};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewdux::prelude::*;
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
|
use crate::components::callbacks::transfer_menu_callbacks;
|
||||||
use crate::data::{transfer::Transfer, transfer_region::Region};
|
use crate::data::{transfer::Transfer, transfer_region::Region};
|
||||||
|
|
||||||
use super::states::{CurrentTransfer, MainState};
|
use super::states::{CurrentTransfer, MainState};
|
||||||
|
@ -20,157 +21,52 @@ pub fn TransferMenu() -> Html {
|
||||||
|
|
||||||
let on_name_change = {
|
let on_name_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_name_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if input.value() == "" {
|
|
||||||
return;
|
|
||||||
} // We do not want empty inputs!
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.name = input.value();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_src_region_change = {
|
let on_src_region_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_src_region_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
if matches!(ct_dispatch.get().transfer.transfer_region.source_region, Region::Custom(_)) {
|
|
||||||
return; // Do nothing here!
|
|
||||||
}
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.source_region = Region::from(&rd);
|
|
||||||
});
|
|
||||||
input.set_custom_validity("");
|
|
||||||
} else {
|
|
||||||
input.set_custom_validity("Invalid region.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_dest_region_change = {
|
let on_dest_region_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_dest_region_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.dest_region = Region::from(&rd);
|
|
||||||
});
|
|
||||||
input.set_custom_validity("");
|
|
||||||
} else {
|
|
||||||
input.set_custom_validity("Invalid region.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_source_interleave_x_change = {
|
let on_source_interleave_x_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_source_interleave_x_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if let Ok(num) = input.value().parse::<i8>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.interleave_source =
|
|
||||||
(num, state.transfer.transfer_region.interleave_source.1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_source_interleave_y_change = {
|
let on_source_interleave_y_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_source_interleave_y_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if let Ok(num) = input.value().parse::<i8>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.interleave_source =
|
|
||||||
(state.transfer.transfer_region.interleave_source.0, num);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let on_dest_interleave_x_change = {
|
let on_dest_interleave_x_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_dest_interleave_x_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if let Ok(num) = input.value().parse::<i8>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.interleave_dest =
|
|
||||||
(num, state.transfer.transfer_region.interleave_dest.1);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_dest_interleave_y_change = {
|
let on_dest_interleave_y_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_dest_interleave_y_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let target: Option<EventTarget> = e.target();
|
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
|
||||||
if let Some(input) = input {
|
|
||||||
if let Ok(num) = input.value().parse::<i8>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.interleave_dest =
|
|
||||||
(state.transfer.transfer_region.interleave_dest.0, num);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let on_volume_change = {
|
let on_volume_change = {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
transfer_menu_callbacks::on_volume_change_callback(ct_dispatch)
|
||||||
Callback::from(move |e: Event| {
|
|
||||||
let input = e
|
|
||||||
.target()
|
|
||||||
.expect("Event must have target")
|
|
||||||
.dyn_into::<HtmlInputElement>()
|
|
||||||
.expect("Must have been emitted by input");
|
|
||||||
if let Ok(num) = input.value().parse::<f32>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.volume = num;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_transfer_button_callback = {
|
let new_transfer_button_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
let main_state = main_state.clone();
|
let main_state = main_state.clone();
|
||||||
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
Callback::from(move |_: MouseEvent| {
|
transfer_menu_callbacks::new_transfer_button_callback_callback(
|
||||||
main_dispatch.reduce_mut(|state| {
|
main_dispatch,
|
||||||
state.selected_transfer = Uuid::nil();
|
main_state,
|
||||||
});
|
ct_dispatch,
|
||||||
ct_dispatch.reduce_mut(|state| {
|
)
|
||||||
state.transfer = Transfer::default();
|
|
||||||
state.transfer.source_id = main_state.selected_source_plate;
|
|
||||||
state.transfer.dest_id = main_state.selected_dest_plate;
|
|
||||||
});
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let save_transfer_button_callback = {
|
let save_transfer_button_callback = {
|
||||||
|
@ -178,70 +74,25 @@ pub fn TransferMenu() -> Html {
|
||||||
let main_state = main_state.clone();
|
let main_state = main_state.clone();
|
||||||
let ct_state = ct_state.clone();
|
let ct_state = ct_state.clone();
|
||||||
let new_transfer_button_callback = new_transfer_button_callback.clone();
|
let new_transfer_button_callback = new_transfer_button_callback.clone();
|
||||||
|
transfer_menu_callbacks::save_transfer_button_callback_callback(
|
||||||
Callback::from(move |e: MouseEvent| {
|
main_dispatch,
|
||||||
log::debug!("Button pressed");
|
main_state,
|
||||||
if main_state.selected_transfer.is_nil() {
|
ct_state,
|
||||||
if let Some(spi) = main_state
|
new_transfer_button_callback,
|
||||||
.source_plates
|
)
|
||||||
.iter()
|
|
||||||
.find(|spi| spi.get_uuid() == main_state.selected_source_plate)
|
|
||||||
{
|
|
||||||
if let Some(dpi) = main_state
|
|
||||||
.destination_plates
|
|
||||||
.iter()
|
|
||||||
.find(|dpi| dpi.get_uuid() == main_state.selected_dest_plate)
|
|
||||||
{
|
|
||||||
let new_transfer = Transfer::new(
|
|
||||||
spi.clone(),
|
|
||||||
dpi.clone(),
|
|
||||||
ct_state.transfer.transfer_region.clone(),
|
|
||||||
ct_state.transfer.name.clone(),
|
|
||||||
);
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfers.push(new_transfer);
|
|
||||||
state.selected_transfer = state
|
|
||||||
.transfers
|
|
||||||
.last()
|
|
||||||
.expect("An element should have just been added")
|
|
||||||
.get_uuid();
|
|
||||||
});
|
|
||||||
new_transfer_button_callback.emit(e); // If we just made a new transfer,
|
|
||||||
// then we should make another on
|
|
||||||
// save.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if let Some(index) = main_state
|
|
||||||
.transfers
|
|
||||||
.iter()
|
|
||||||
.position(|t| t.get_uuid() == main_state.selected_transfer)
|
|
||||||
{
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfers[index] = ct_state.transfer.clone();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let delete_transfer_button_callback = {
|
let delete_transfer_button_callback = {
|
||||||
|
let main_state = main_state.clone();
|
||||||
|
let main_dispatch = main_dispatch.clone();
|
||||||
let ct_state = ct_state.clone();
|
let ct_state = ct_state.clone();
|
||||||
let new_callback = new_transfer_button_callback.clone();
|
let new_transfer_button_callback = new_transfer_button_callback.clone();
|
||||||
|
transfer_menu_callbacks::delete_transfer_button_callback(
|
||||||
Callback::from(move |e: MouseEvent| {
|
main_state,
|
||||||
if main_state.selected_transfer.is_nil() {
|
main_dispatch,
|
||||||
// Maybe reset transfer?
|
ct_state,
|
||||||
} else if let Some(index) = main_state
|
new_transfer_button_callback,
|
||||||
.transfers
|
)
|
||||||
.iter()
|
|
||||||
.position(|t| t.get_uuid() == ct_state.transfer.get_uuid())
|
|
||||||
{
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfers.remove(index);
|
|
||||||
state.selected_transfer = Uuid::nil();
|
|
||||||
});
|
|
||||||
new_callback.emit(e); // We need a new transfer now
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
|
@ -390,7 +241,13 @@ impl From<&Region> for RegionDisplay {
|
||||||
Region::Rect(c1, c2) => RegionDisplay::try_from((c1.0, c1.1, c2.0, c2.1))
|
Region::Rect(c1, c2) => RegionDisplay::try_from((c1.0, c1.1, c2.0, c2.1))
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Region::Custom(_) => RegionDisplay { text: "CUSTOM".to_string(), col_start: 0, row_start: 0, col_end: 0, row_end: 0 }
|
Region::Custom(_) => RegionDisplay {
|
||||||
|
text: "CUSTOM".to_string(),
|
||||||
|
col_start: 0,
|
||||||
|
row_start: 0,
|
||||||
|
col_end: 0,
|
||||||
|
row_end: 0,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{EventTarget, HtmlDialogElement, HtmlElement, HtmlInputElement};
|
use web_sys::{HtmlDialogElement, HtmlInputElement};
|
||||||
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 crate::data::transfer_region::Region;
|
use crate::components::callbacks::tree_callbacks;
|
||||||
|
|
||||||
#[derive(PartialEq, Properties)]
|
#[derive(PartialEq, Properties)]
|
||||||
pub struct TreeProps {
|
pub struct TreeProps {
|
||||||
|
@ -22,102 +22,31 @@ pub fn Tree(props: &TreeProps) -> Html {
|
||||||
|
|
||||||
let open_plate_info_callback = {
|
let open_plate_info_callback = {
|
||||||
let plate_menu_id = plate_modal_id.clone();
|
let plate_menu_id = plate_modal_id.clone();
|
||||||
Callback::from(move |e: MouseEvent| {
|
tree_callbacks::open_plate_info_callback(plate_menu_id)
|
||||||
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)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let plate_info_close_callback = {
|
let plate_info_close_callback = {
|
||||||
let plate_menu_id = plate_modal_id.clone();
|
let plate_menu_id = plate_modal_id.clone();
|
||||||
Callback::from(move |_| {
|
tree_callbacks::plate_info_close_callback(plate_menu_id)
|
||||||
plate_menu_id.set(None);
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let plate_info_delete_callback = {
|
let plate_info_delete_callback = {
|
||||||
let dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
let plate_menu_id = plate_modal_id.clone();
|
let plate_menu_id = plate_modal_id.clone();
|
||||||
Callback::from(move |_| {
|
tree_callbacks::plate_info_delete_callback(main_dispatch, plate_menu_id)
|
||||||
if let Some(id) = *plate_menu_id {
|
|
||||||
dispatch.reduce_mut(|state| {
|
|
||||||
state.del_plate(id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let source_plate_select_callback = {
|
let source_plate_select_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
tree_callbacks::source_plate_select_callback(main_dispatch, ct_dispatch)
|
||||||
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>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.source_region = Region::default();
|
|
||||||
state.transfer.transfer_region.dest_region = Region::default();
|
|
||||||
});
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.selected_source_plate = Uuid::from_u128(id);
|
|
||||||
state.selected_transfer = Uuid::nil();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
let destination_plate_select_callback = {
|
let destination_plate_select_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
let main_dispatch = main_dispatch.clone();
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
tree_callbacks::destination_plate_select_callback(main_dispatch, ct_dispatch)
|
||||||
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>() {
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer.transfer_region.source_region = Region::default();
|
|
||||||
state.transfer.transfer_region.dest_region = Region::default();
|
|
||||||
});
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.selected_dest_plate = Uuid::from_u128(id);
|
|
||||||
state.selected_transfer = Uuid::nil();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let transfer_select_callback = {
|
let transfer_select_callback = {
|
||||||
let main_state = main_state.clone();
|
let main_state = main_state.clone();
|
||||||
|
tree_callbacks::transfer_select_callback(main_state)
|
||||||
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>() {
|
|
||||||
let id = Uuid::from_u128(id);
|
|
||||||
if let Some(transfer) = main_state
|
|
||||||
.transfers
|
|
||||||
.iter()
|
|
||||||
.find(|transfer| transfer.get_uuid() == id)
|
|
||||||
{
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.selected_source_plate = transfer.source_id;
|
|
||||||
state.selected_dest_plate = transfer.dest_id;
|
|
||||||
state.selected_transfer = id;
|
|
||||||
});
|
|
||||||
ct_dispatch.reduce_mut(|state| {
|
|
||||||
state.transfer = transfer.clone();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let source_plates = main_state
|
let source_plates = main_state
|
||||||
|
|
Loading…
Reference in New Issue