From e2ef9fa84df5b78b54847fecc65ccd59264facfe Mon Sep 17 00:00:00 2001 From: Emilia Date: Fri, 12 Jan 2024 21:43:50 -0500 Subject: [PATCH 01/10] Increment version number in cargo.lock why wasn't this done already? --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 0d0e6cf..e921acb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,7 +558,7 @@ dependencies = [ [[package]] name = "plate-tool" -version = "0.1.0" +version = "0.2.0" dependencies = [ "csv", "getrandom", From 62d870521e100580ccd3277e99a64a3a08ad8047 Mon Sep 17 00:00:00 2001 From: Emilia Date: Fri, 12 Jan 2024 21:44:10 -0500 Subject: [PATCH 02/10] First callback move --- .../callbacks/main_window_callbacks.rs | 15 +++++++++++++ src/components/callbacks/mod.rs | 1 + src/components/main_window.rs | 21 +++++++------------ src/components/mod.rs | 1 + 4 files changed, 25 insertions(+), 13 deletions(-) create mode 100644 src/components/callbacks/main_window_callbacks.rs create mode 100644 src/components/callbacks/mod.rs diff --git a/src/components/callbacks/main_window_callbacks.rs b/src/components/callbacks/main_window_callbacks.rs new file mode 100644 index 0000000..c3af4ea --- /dev/null +++ b/src/components/callbacks/main_window_callbacks.rs @@ -0,0 +1,15 @@ +use yew::prelude::*; +use yewdux::prelude::*; + +use crate::components::states::{CurrentTransfer, MainState}; + +pub fn toggle_in_transfer_hashes_callback( + main_dispatch: &Dispatch, +) -> Callback { + let main_dispatch = main_dispatch.clone(); + Callback::from(move |_| { + main_dispatch.reduce_mut(|state| { + state.preferences.in_transfer_hashes ^= true; + }) + }) +} diff --git a/src/components/callbacks/mod.rs b/src/components/callbacks/mod.rs new file mode 100644 index 0000000..e87e443 --- /dev/null +++ b/src/components/callbacks/mod.rs @@ -0,0 +1 @@ +pub mod main_window_callbacks; diff --git a/src/components/main_window.rs b/src/components/main_window.rs index 55a7970..a70ce90 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -12,17 +12,19 @@ use web_sys::{ use yew::prelude::*; use yewdux::prelude::*; -use super::new_plate_dialog::NewPlateDialog; -use super::plates::plate_container::PlateContainer; -use super::states::{CurrentTransfer, MainState}; -use super::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu}; -use super::tree::Tree; +use crate::components::new_plate_dialog::NewPlateDialog; +use crate::components::plates::plate_container::PlateContainer; +use crate::components::states::{CurrentTransfer, MainState}; +use crate::components::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu}; +use crate::components::tree::Tree; use crate::data::csv::state_to_csv; 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] pub fn MainWindow() -> Html { let (main_state, main_dispatch) = use_store::(); @@ -49,14 +51,7 @@ pub fn MainWindow() -> Html { }); } - let toggle_in_transfer_hashes_callback = { - let main_dispatch = main_dispatch.clone(); - Callback::from(move |_| { - main_dispatch.reduce_mut(|state| { - state.preferences.in_transfer_hashes ^= true; - }) - }) - }; + let toggle_in_transfer_hashes_callback = main_window_callbacks::test(&main_dispatch); let new_plate_dialog_is_open = use_state_eq(|| false); let new_plate_dialog_callback = { diff --git a/src/components/mod.rs b/src/components/mod.rs index ac482b6..41c01e7 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -4,3 +4,4 @@ pub mod plates; pub mod states; pub mod transfer_menu; pub mod tree; +mod callbacks; From a90b5f83d88005456a0923802d7dab6d9bebcb4d Mon Sep 17 00:00:00 2001 From: Emilia Date: Fri, 12 Jan 2024 22:22:28 -0500 Subject: [PATCH 03/10] A number of moves to address #2 --- .../callbacks/main_window_callbacks.rs | 93 ++++++++++++++++++- src/components/main_window.rs | 74 +++++---------- 2 files changed, 116 insertions(+), 51 deletions(-) diff --git a/src/components/callbacks/main_window_callbacks.rs b/src/components/callbacks/main_window_callbacks.rs index c3af4ea..c7fe771 100644 --- a/src/components/callbacks/main_window_callbacks.rs +++ b/src/components/callbacks/main_window_callbacks.rs @@ -1,10 +1,18 @@ +use js_sys::Array; +use wasm_bindgen::{JsCast, JsValue}; +use web_sys::{Blob, HtmlAnchorElement, Url}; + use yew::prelude::*; use yewdux::prelude::*; use crate::components::states::{CurrentTransfer, MainState}; +use crate::data::csv::state_to_csv; + +type NoParamsCallback = Box ()>; + pub fn toggle_in_transfer_hashes_callback( - main_dispatch: &Dispatch, + main_dispatch: Dispatch, ) -> Callback { let main_dispatch = main_dispatch.clone(); Callback::from(move |_| { @@ -13,3 +21,86 @@ pub fn toggle_in_transfer_hashes_callback( }) }) } + +pub fn new_plate_dialog_callback( + new_plate_dialog_is_open: UseStateHandle, +) -> 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, +) -> 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, + ct_dispatch: Dispatch, +) -> Callback { + 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::() + .unwrap(); + anchor.set_download(name); + anchor.set_href(&url); + anchor.click(); + } +} + +pub fn export_csv_button_callback(main_state: std::rc::Rc) -> Callback { + 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) -> Callback { + 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(); + } + }) +} diff --git a/src/components/main_window.rs b/src/components/main_window.rs index a70ce90..b3cd8fe 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -18,7 +18,6 @@ use crate::components::states::{CurrentTransfer, MainState}; use crate::components::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu}; use crate::components::tree::Tree; -use crate::data::csv::state_to_csv; use crate::data::plate_instances::PlateInstance; use crate::data::transfer::Transfer; use crate::data::transfer_region::{Region, TransferRegion}; @@ -51,65 +50,30 @@ pub fn MainWindow() -> Html { }); } - let toggle_in_transfer_hashes_callback = main_window_callbacks::test(&main_dispatch); + let toggle_in_transfer_hashes_callback = { + let main_dispatch = main_dispatch.clone(); + main_window_callbacks::toggle_in_transfer_hashes_callback(main_dispatch) + }; let new_plate_dialog_is_open = use_state_eq(|| false); - let 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(false); - }) - }; - 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_plate_dialog_callback = + main_window_callbacks::new_plate_dialog_callback(new_plate_dialog_is_open.clone()); + + let open_new_plate_dialog_callback = + main_window_callbacks::open_new_plate_dialog_callback(new_plate_dialog_is_open.clone()); let new_button_callback = { let main_dispatch = main_dispatch.clone(); - 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()); - } - } - }) + main_window_callbacks::new_button_callback(main_dispatch, ct_dispatch) }; let export_csv_button_callback = { let main_state = main_state.clone(); - 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"); - } - }) + main_window_callbacks::export_csv_button_callback(main_state) }; let export_json_button_callback = { - 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(); - } - }) + main_window_callbacks::export_json_button_callback(main_state) }; let import_json_button_callback = { @@ -373,8 +337,18 @@ pub fn MainWindow() -> Html { 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]); + log::debug!( + "{} {}", + &record.source_well, + &record.destination_well + ); + log::debug!( + "{},{} {},{}", + &c1[1], + &c1[2], + &c2[1], + &c2[2] + ); ( ( From 820f672cb7a630a0cb3a4e27cd9354ddd2c29ba7 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 13 Jan 2024 13:23:11 -0500 Subject: [PATCH 04/10] Import json button refactor Partial for #2 --- .../callbacks/main_window_callbacks.rs | 87 ++++++++++++++++++- src/components/main_window.rs | 81 +---------------- 2 files changed, 88 insertions(+), 80 deletions(-) diff --git a/src/components/callbacks/main_window_callbacks.rs b/src/components/callbacks/main_window_callbacks.rs index c7fe771..b0cb57e 100644 --- a/src/components/callbacks/main_window_callbacks.rs +++ b/src/components/callbacks/main_window_callbacks.rs @@ -1,6 +1,6 @@ use js_sys::Array; -use wasm_bindgen::{JsCast, JsValue}; -use web_sys::{Blob, HtmlAnchorElement, Url}; +use wasm_bindgen::{prelude::*, JsCast, JsValue}; +use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url}; use yew::prelude::*; use yewdux::prelude::*; @@ -104,3 +104,86 @@ pub fn export_json_button_callback(main_state: std::rc::Rc) -> Callba } }) } + +pub fn input_json_input_callback( + main_dispatch: Dispatch, + modal: HtmlDialogElement, +) -> Closure { + Closure::::new(move |e: Event| { + if let Some(input) = e.current_target() { + let input = input + .dyn_into::() + .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::::new(move |_: Event| { + if let Some(value) = &fr1.result().ok().and_then(|v| v.as_string()) { + let ms = serde_json::from_str::(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) -> Callback { + 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::() + .unwrap(); + modal.set_text_content(Some("Import File:")); + let onclose_callback = { + let modal = modal.clone(); + Closure::::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::() + .unwrap(); + let input = document + .create_element("input") + .unwrap() + .dyn_into::() + .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(); + }) +} diff --git a/src/components/main_window.rs b/src/components/main_window.rs index b3cd8fe..f042f51 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -72,87 +72,12 @@ pub fn MainWindow() -> Html { main_window_callbacks::export_csv_button_callback(main_state) }; - let export_json_button_callback = { - main_window_callbacks::export_json_button_callback(main_state) - }; + let export_json_button_callback = + { main_window_callbacks::export_json_button_callback(main_state) }; let import_json_button_callback = { let main_dispatch = main_dispatch.clone(); - 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::() - .unwrap(); - modal.set_text_content(Some("Import File:")); - let onclose_callback = { - let modal = modal.clone(); - Closure::::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::() - .unwrap(); - let input = document - .create_element("input") - .unwrap() - .dyn_into::() - .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::::new(move |e: Event| { - if let Some(input) = e.current_target() { - let input = input - .dyn_into::() - .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::::new(move |_: Event| { - if let Some(value) = - &fr1.result().ok().and_then(|v| v.as_string()) - { - let ms = serde_json::from_str::(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(); - }) + main_window_callbacks::import_json_button_callback(main_dispatch) }; let import_transfer_csv_callback = { From c2a3f0302bab95f0a811f2806668ee22d18e65e0 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 13 Jan 2024 13:57:14 -0500 Subject: [PATCH 05/10] Finish main_window callback refactor For #2 --- .../callbacks/main_window_callbacks.rs | 252 +++++++++++++++++- src/components/main_window.rs | 244 +---------------- 2 files changed, 251 insertions(+), 245 deletions(-) diff --git a/src/components/callbacks/main_window_callbacks.rs b/src/components/callbacks/main_window_callbacks.rs index b0cb57e..268407b 100644 --- a/src/components/callbacks/main_window_callbacks.rs +++ b/src/components/callbacks/main_window_callbacks.rs @@ -1,13 +1,24 @@ -use js_sys::Array; -use wasm_bindgen::{prelude::*, JsCast, JsValue}; -use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url}; +#![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::csv::state_to_csv; +use crate::data::transfer::Transfer; +use crate::data::transfer_region::{Region, TransferRegion}; + +use crate::data::csv::{state_to_csv, TransferRecord}; type NoParamsCallback = Box ()>; @@ -187,3 +198,236 @@ pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callba modal.show_modal().unwrap(); }) } + +pub fn import_transfer_csv_submit_callback( + main_dispatch: Dispatch, + from_source: HtmlSelectElement, + to_source: HtmlSelectElement, + from_dest: HtmlSelectElement, + to_dest: HtmlSelectElement, + records: Vec, +) -> Closure { + Closure::::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::().unwrap(), + ), + ( + letters_to_num(&c2[1]).unwrap(), + c2[2].parse::().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, + file_reader: FileReader, + modal: HtmlDialogElement, +) -> Closure { + Closure::::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::() { + match record { + Ok(r) => { + //log::debug!("{:?}", r); + records.push(r); + } + Err(e) => { + log::debug!("{:?}", e); + } + } + } + + let mut sources: HashSet = HashSet::new(); + let mut destinations: HashSet = 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::() + .unwrap(); + let from_source = document + .create_element("select") + .unwrap() + .dyn_into::() + .unwrap(); + for source in sources { + let option = document + .create_element("option") + .unwrap() + .dyn_into::() + .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::() + .unwrap(); + for source in &main_dispatch.get().source_plates { + let option = document + .create_element("option") + .unwrap() + .dyn_into::() + .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::() + .unwrap(); + for dest in destinations { + let option = document + .create_element("option") + .unwrap() + .dyn_into::() + .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::() + .unwrap(); + for dest in &main_dispatch.get().destination_plates { + let option = document + .create_element("option") + .unwrap() + .dyn_into::() + .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::() + .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, + modal: HtmlDialogElement, +) -> Closure { + Closure::::new(move |e: Event| { + if let Some(input) = e.current_target() { + let input = input + .dyn_into::() + .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) + } + } + } + }) +} diff --git a/src/components/main_window.rs b/src/components/main_window.rs index f042f51..e72f035 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -1,26 +1,18 @@ #![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, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement, - HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url, -}; +use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url}; use yew::prelude::*; use yewdux::prelude::*; use crate::components::new_plate_dialog::NewPlateDialog; use crate::components::plates::plate_container::PlateContainer; use crate::components::states::{CurrentTransfer, MainState}; -use crate::components::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu}; +use crate::components::transfer_menu::TransferMenu; use crate::components::tree::Tree; 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; @@ -117,237 +109,7 @@ pub fn MainWindow() -> Html { let input_callback = { let main_dispatch = main_dispatch.clone(); let modal = modal.clone(); - Closure::::new(move |e: Event| { - if let Some(input) = e.current_target() { - let input = input - .dyn_into::() - .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::::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::() - { - match record { - Ok(r) => { - //log::debug!("{:?}", r); - records.push(r); - } - Err(e) => { - log::debug!("{:?}", e); - } - } - } - - let mut sources: HashSet = HashSet::new(); - let mut destinations: HashSet = 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::() - .unwrap(); - let from_source = document - .create_element("select") - .unwrap() - .dyn_into::() - .unwrap(); - for source in sources { - let option = document - .create_element("option") - .unwrap() - .dyn_into::() - .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::() - .unwrap(); - for source in &main_dispatch.get().source_plates { - let option = document - .create_element("option") - .unwrap() - .dyn_into::() - .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::() - .unwrap(); - for dest in destinations { - let option = document - .create_element("option") - .unwrap() - .dyn_into::() - .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::() - .unwrap(); - for dest in &main_dispatch.get().destination_plates { - let option = document - .create_element("option") - .unwrap() - .dyn_into::() - .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::() - .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::::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::().unwrap(), - ), - ( - letters_to_num(&c2[1]).unwrap(), - c2[2].parse::().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) - } - } - } - }) + main_window_callbacks::import_transfer_csv_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 :( From 53457a3e8688683a7c5d892af1a0d69c9d16b2d8 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 13 Jan 2024 14:05:34 -0500 Subject: [PATCH 06/10] new_plate_dialog callbacks For #2 --- src/components/callbacks/mod.rs | 1 + src/components/new_plate_dialog.rs | 51 +++--------------------------- 2 files changed, 5 insertions(+), 47 deletions(-) diff --git a/src/components/callbacks/mod.rs b/src/components/callbacks/mod.rs index e87e443..4141163 100644 --- a/src/components/callbacks/mod.rs +++ b/src/components/callbacks/mod.rs @@ -1 +1,2 @@ pub mod main_window_callbacks; +pub mod new_plate_dialog_callbacks; diff --git a/src/components/new_plate_dialog.rs b/src/components/new_plate_dialog.rs index 0765f26..6451000 100644 --- a/src/components/new_plate_dialog.rs +++ b/src/components/new_plate_dialog.rs @@ -8,6 +8,8 @@ use crate::components::states::MainState; use crate::data::plate::*; use crate::data::plate_instances::PlateInstance; +use crate::components::callbacks::new_plate_dialog_callbacks; + #[derive(PartialEq, Properties)] pub struct NewPlateDialogProps { pub close_callback: Callback<()>, @@ -19,57 +21,12 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html { let new_plate_callback = { let close_callback = props.close_callback.clone(); - Callback::from(move |e: SubmitEvent| { - e.prevent_default(); - close_callback.emit(()); - let target: Option = e.target(); - let form = target.and_then(|t| t.dyn_into::().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, - )) - } - }); - } - } - } - }) + new_plate_dialog_callbacks::new_plate_callback(dispatch, close_callback) }; let onclose = { let close_callback = props.close_callback.clone(); - Callback::from(move |_: Event| { - close_callback.emit(()); - }) + new_plate_dialog_callbacks::onclose(close_callback) }; // This whole section is optional, only if you want the backdrop From 15accc2fcabc20fb0e7f3fea041c47d9e1ef1661 Mon Sep 17 00:00:00 2001 From: Emilia Date: Tue, 30 Jan 2024 20:37:15 -0500 Subject: [PATCH 07/10] I think this file was supposed to be in the last one lol --- .../callbacks/new_plate_dialog_callbacks.rs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 src/components/callbacks/new_plate_dialog_callbacks.rs diff --git a/src/components/callbacks/new_plate_dialog_callbacks.rs b/src/components/callbacks/new_plate_dialog_callbacks.rs new file mode 100644 index 0000000..3aa4609 --- /dev/null +++ b/src/components/callbacks/new_plate_dialog_callbacks.rs @@ -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, + close_callback: Callback<()>, +) -> Callback { + Callback::from(move |e: SubmitEvent| { + e.prevent_default(); + close_callback.emit(()); + let target: Option = e.target(); + let form = target.and_then(|t| t.dyn_into::().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 { + Callback::from(move |_: Event| { + close_callback.emit(()); + }) +} From f8216cb0bdbf2201307276544a45daa6fc64f2c3 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 10 Feb 2024 09:20:59 -0500 Subject: [PATCH 08/10] transfer_menu callbacks --- src/components/callbacks/mod.rs | 1 + .../callbacks/transfer_menu_callbacks.rs | 242 ++++++++++++++++++ src/components/transfer_menu.rs | 223 +++------------- 3 files changed, 283 insertions(+), 183 deletions(-) create mode 100644 src/components/callbacks/transfer_menu_callbacks.rs diff --git a/src/components/callbacks/mod.rs b/src/components/callbacks/mod.rs index 4141163..dacd723 100644 --- a/src/components/callbacks/mod.rs +++ b/src/components/callbacks/mod.rs @@ -1,2 +1,3 @@ pub mod main_window_callbacks; pub mod new_plate_dialog_callbacks; +pub mod transfer_menu_callbacks; diff --git a/src/components/callbacks/transfer_menu_callbacks.rs b/src/components/callbacks/transfer_menu_callbacks.rs new file mode 100644 index 0000000..885ee22 --- /dev/null +++ b/src/components/callbacks/transfer_menu_callbacks.rs @@ -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) -> Callback { + Callback::from(move |e: Event| { + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().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) -> Callback { + Callback::from(move |e: Event| { + if matches!( + ct_dispatch.get().transfer.transfer_region.source_region, + Region::Custom(_) + ) { + return; // Do nothing here! + } + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().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) -> Callback { + Callback::from(move |e: Event| { + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().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, +) -> Callback { + Callback::from(move |e: Event| { + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().ok()); + if let Some(input) = input { + if let Ok(num) = input.value().parse::() { + 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, +) -> Callback { + Callback::from(move |e: Event| { + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().ok()); + if let Some(input) = input { + if let Ok(num) = input.value().parse::() { + 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, +) -> Callback { + Callback::from(move |e: Event| { + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().ok()); + if let Some(input) = input { + if let Ok(num) = input.value().parse::() { + 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, +) -> Callback { + Callback::from(move |e: Event| { + let target: Option = e.target(); + let input = target.and_then(|t| t.dyn_into::().ok()); + if let Some(input) = input { + if let Ok(num) = input.value().parse::() { + 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) -> Callback { + Callback::from(move |e: Event| { + let input = e + .target() + .expect("Event must have target") + .dyn_into::() + .expect("Must have been emitted by input"); + if let Ok(num) = input.value().parse::() { + ct_dispatch.reduce_mut(|state| { + state.transfer.volume = num; + }); + } + }) +} + +pub fn new_transfer_button_callback_callback( + main_dispatch: Dispatch, + main_state: Rc, + ct_dispatch: Dispatch, +) -> Callback { + 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, + main_state: Rc, + ct_state: Rc, + new_transfer_button_callback: Callback) + -> Callback { + 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, + main_dispatch: Dispatch, + ct_state: Rc, + new_transfer_button_callback: Callback +) -> Callback { + 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 + } + }) +} diff --git a/src/components/transfer_menu.rs b/src/components/transfer_menu.rs index fa9f343..052f53f 100644 --- a/src/components/transfer_menu.rs +++ b/src/components/transfer_menu.rs @@ -9,6 +9,7 @@ use web_sys::{EventTarget, HtmlInputElement}; use yew::prelude::*; use yewdux::prelude::*; +use crate::components::callbacks::transfer_menu_callbacks; use crate::data::{transfer::Transfer, transfer_region::Region}; use super::states::{CurrentTransfer, MainState}; @@ -20,157 +21,52 @@ pub fn TransferMenu() -> Html { let on_name_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().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(); - }); - } - }) + transfer_menu_callbacks::on_name_change_callback(ct_dispatch) }; let on_src_region_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - if matches!(ct_dispatch.get().transfer.transfer_region.source_region, Region::Custom(_)) { - return; // Do nothing here! - } - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().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.") - } - } - }) + transfer_menu_callbacks::on_src_region_change_callback(ct_dispatch) }; + let on_dest_region_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().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.") - } - } - }) + transfer_menu_callbacks::on_dest_region_change_callback(ct_dispatch) }; let on_source_interleave_x_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().ok()); - if let Some(input) = input { - if let Ok(num) = input.value().parse::() { - ct_dispatch.reduce_mut(|state| { - state.transfer.transfer_region.interleave_source = - (num, state.transfer.transfer_region.interleave_source.1); - }); - } - } - }) + transfer_menu_callbacks::on_source_interleave_x_change_callback(ct_dispatch) }; + let on_source_interleave_y_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().ok()); - if let Some(input) = input { - if let Ok(num) = input.value().parse::() { - ct_dispatch.reduce_mut(|state| { - state.transfer.transfer_region.interleave_source = - (state.transfer.transfer_region.interleave_source.0, num); - }); - } - } - }) + transfer_menu_callbacks::on_source_interleave_y_change_callback(ct_dispatch) }; let on_dest_interleave_x_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().ok()); - if let Some(input) = input { - if let Ok(num) = input.value().parse::() { - ct_dispatch.reduce_mut(|state| { - state.transfer.transfer_region.interleave_dest = - (num, state.transfer.transfer_region.interleave_dest.1); - }); - } - } - }) + transfer_menu_callbacks::on_dest_interleave_x_change_callback(ct_dispatch) }; + let on_dest_interleave_y_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let target: Option = e.target(); - let input = target.and_then(|t| t.dyn_into::().ok()); - if let Some(input) = input { - if let Ok(num) = input.value().parse::() { - ct_dispatch.reduce_mut(|state| { - state.transfer.transfer_region.interleave_dest = - (state.transfer.transfer_region.interleave_dest.0, num); - }); - } - } - }) + transfer_menu_callbacks::on_dest_interleave_y_change_callback(ct_dispatch) }; let on_volume_change = { let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: Event| { - let input = e - .target() - .expect("Event must have target") - .dyn_into::() - .expect("Must have been emitted by input"); - if let Ok(num) = input.value().parse::() { - ct_dispatch.reduce_mut(|state| { - state.transfer.volume = num; - }); - } - }) + transfer_menu_callbacks::on_volume_change_callback(ct_dispatch) }; let new_transfer_button_callback = { let main_dispatch = main_dispatch.clone(); let main_state = main_state.clone(); - - 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; - }); - }) + let ct_dispatch = ct_dispatch.clone(); + transfer_menu_callbacks::new_transfer_button_callback_callback( + main_dispatch, + main_state, + ct_dispatch, + ) }; let save_transfer_button_callback = { @@ -178,70 +74,25 @@ pub fn TransferMenu() -> Html { let main_state = main_state.clone(); let ct_state = ct_state.clone(); let new_transfer_button_callback = new_transfer_button_callback.clone(); - - 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(); - }); - } - }) + transfer_menu_callbacks::save_transfer_button_callback_callback( + main_dispatch, + main_state, + ct_state, + new_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 new_callback = new_transfer_button_callback.clone(); - - 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_callback.emit(e); // We need a new transfer now - } - }) + let new_transfer_button_callback = new_transfer_button_callback.clone(); + transfer_menu_callbacks::delete_transfer_button_callback( + main_state, + main_dispatch, + ct_state, + new_transfer_button_callback, + ) }; 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)) .ok() .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, + }, } } } From 47103357509094203689db14c4bcc7ae5fd9b932 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 10 Feb 2024 16:34:52 -0500 Subject: [PATCH 09/10] tree callbacks --- src/components/callbacks/mod.rs | 1 + src/components/callbacks/tree_callbacks.rs | 114 +++++++++++++++++++++ src/components/tree.rs | 89 ++-------------- 3 files changed, 124 insertions(+), 80 deletions(-) create mode 100644 src/components/callbacks/tree_callbacks.rs diff --git a/src/components/callbacks/mod.rs b/src/components/callbacks/mod.rs index dacd723..175182a 100644 --- a/src/components/callbacks/mod.rs +++ b/src/components/callbacks/mod.rs @@ -1,3 +1,4 @@ pub mod main_window_callbacks; pub mod new_plate_dialog_callbacks; pub mod transfer_menu_callbacks; +pub mod tree_callbacks; diff --git a/src/components/callbacks/tree_callbacks.rs b/src/components/callbacks/tree_callbacks.rs new file mode 100644 index 0000000..68db2e4 --- /dev/null +++ b/src/components/callbacks/tree_callbacks.rs @@ -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>, +) -> Callback { + Callback::from(move |e: MouseEvent| { + let target: Option = e.target(); + let li = target.and_then(|t| t.dyn_into::().ok()); + if let Some(li) = li { + if let Ok(id) = li.id().as_str().parse::() { + plate_menu_id.set(Some(Uuid::from_u128(id))); + } + } + }) +} + +pub fn plate_info_close_callback( + plate_menu_id: UseStateHandle>, +) -> Callback { + Callback::from(move |_| { + plate_menu_id.set(None); + }) +} + +pub fn plate_info_delete_callback( + main_dispatch: Dispatch, + plate_menu_id: UseStateHandle>, +) -> Callback { + 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, + ct_dispatch: Dispatch, +) -> Callback { + Callback::from(move |e: MouseEvent| { + let target: Option = e.target(); + let li = target.and_then(|t| t.dyn_into::().ok()); + if let Some(li) = li { + if let Ok(id) = li.id().as_str().parse::() { + 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, + ct_dispatch: Dispatch, +) -> Callback { + Callback::from(move |e: MouseEvent| { + let target: Option = e.target(); + let li = target.and_then(|t| t.dyn_into::().ok()); + if let Some(li) = li { + if let Ok(id) = li.id().as_str().parse::() { + 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) -> Callback { + Callback::from(move |e: MouseEvent| { + let target: Option = e.target(); + let li = target.and_then(|t| t.dyn_into::().ok()); + if let Some(li) = li { + if let Ok(id) = li.id().as_str().parse::() { + 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(); + }); + } + } + } + }) +} diff --git a/src/components/tree.rs b/src/components/tree.rs index fbe7b8a..90359c5 100644 --- a/src/components/tree.rs +++ b/src/components/tree.rs @@ -2,12 +2,12 @@ use uuid::Uuid; use wasm_bindgen::JsCast; -use web_sys::{EventTarget, HtmlDialogElement, HtmlElement, HtmlInputElement}; +use web_sys::{HtmlDialogElement, HtmlInputElement}; use yew::prelude::*; use yewdux::prelude::*; use crate::components::states::{CurrentTransfer, MainState}; -use crate::data::transfer_region::Region; +use crate::components::callbacks::tree_callbacks; #[derive(PartialEq, Properties)] pub struct TreeProps { @@ -22,102 +22,31 @@ pub fn Tree(props: &TreeProps) -> Html { let open_plate_info_callback = { let plate_menu_id = plate_modal_id.clone(); - Callback::from(move |e: MouseEvent| { - let target: Option = e.target(); - let li = target.and_then(|t| t.dyn_into::().ok()); - if let Some(li) = li { - if let Ok(id) = li.id().as_str().parse::() { - plate_menu_id.set(Some(Uuid::from_u128(id))); - } - } - }) + tree_callbacks::open_plate_info_callback(plate_menu_id) }; let plate_info_close_callback = { let plate_menu_id = plate_modal_id.clone(); - Callback::from(move |_| { - plate_menu_id.set(None); - }) + tree_callbacks::plate_info_close_callback(plate_menu_id) }; let plate_info_delete_callback = { - let dispatch = main_dispatch.clone(); + let main_dispatch = main_dispatch.clone(); let plate_menu_id = plate_modal_id.clone(); - Callback::from(move |_| { - if let Some(id) = *plate_menu_id { - dispatch.reduce_mut(|state| { - state.del_plate(id); - }); - } - }) + tree_callbacks::plate_info_delete_callback(main_dispatch, plate_menu_id) }; let source_plate_select_callback = { let main_dispatch = main_dispatch.clone(); let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: MouseEvent| { - let target: Option = e.target(); - let li = target.and_then(|t| t.dyn_into::().ok()); - if let Some(li) = li { - if let Ok(id) = li.id().as_str().parse::() { - 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(); - }); - } - } - }) + tree_callbacks::source_plate_select_callback(main_dispatch, ct_dispatch) }; let destination_plate_select_callback = { let main_dispatch = main_dispatch.clone(); let ct_dispatch = ct_dispatch.clone(); - - Callback::from(move |e: MouseEvent| { - let target: Option = e.target(); - let li = target.and_then(|t| t.dyn_into::().ok()); - if let Some(li) = li { - if let Ok(id) = li.id().as_str().parse::() { - 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(); - }); - } - } - }) + tree_callbacks::destination_plate_select_callback(main_dispatch, ct_dispatch) }; let transfer_select_callback = { let main_state = main_state.clone(); - - Callback::from(move |e: MouseEvent| { - let target: Option = e.target(); - let li = target.and_then(|t| t.dyn_into::().ok()); - if let Some(li) = li { - if let Ok(id) = li.id().as_str().parse::() { - 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(); - }); - } - } - } - }) + tree_callbacks::transfer_select_callback(main_state) }; let source_plates = main_state From 5430b3d42cdacbee21204eca8de99df8ca8f71e5 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 10 Feb 2024 16:47:32 -0500 Subject: [PATCH 10/10] hotfix --- .../callbacks/new_plate_dialog_callbacks.rs | 2 +- src/components/callbacks/transfer_menu_callbacks.rs | 3 --- src/components/callbacks/tree_callbacks.rs | 12 +++++++----- src/components/new_plate_dialog.rs | 4 +--- src/components/transfer_menu.rs | 5 +---- src/components/tree.rs | 4 +++- 6 files changed, 13 insertions(+), 17 deletions(-) diff --git a/src/components/callbacks/new_plate_dialog_callbacks.rs b/src/components/callbacks/new_plate_dialog_callbacks.rs index 3aa4609..71cb8ec 100644 --- a/src/components/callbacks/new_plate_dialog_callbacks.rs +++ b/src/components/callbacks/new_plate_dialog_callbacks.rs @@ -2,7 +2,7 @@ use yew::prelude::*; use yewdux::prelude::*; use wasm_bindgen::JsCast; -use web_sys::{EventTarget, FormData, HtmlDialogElement, HtmlFormElement}; +use web_sys::{EventTarget, FormData, HtmlFormElement}; use crate::components::states::MainState; use crate::data::plate::*; diff --git a/src/components/callbacks/transfer_menu_callbacks.rs b/src/components/callbacks/transfer_menu_callbacks.rs index 885ee22..2803ac1 100644 --- a/src/components/callbacks/transfer_menu_callbacks.rs +++ b/src/components/callbacks/transfer_menu_callbacks.rs @@ -1,6 +1,3 @@ -use lazy_static::lazy_static; -use regex::Regex; -use serde::{Deserialize, Serialize}; use std::rc::Rc; use uuid::Uuid; use wasm_bindgen::JsCast; diff --git a/src/components/callbacks/tree_callbacks.rs b/src/components/callbacks/tree_callbacks.rs index 68db2e4..0bb32ce 100644 --- a/src/components/callbacks/tree_callbacks.rs +++ b/src/components/callbacks/tree_callbacks.rs @@ -8,6 +8,8 @@ use yewdux::prelude::*; use crate::components::states::{CurrentTransfer, MainState}; use crate::data::transfer_region::Region; +type NoParamsCallback = Box ()>; + pub fn open_plate_info_callback( plate_menu_id: UseStateHandle>, ) -> Callback { @@ -24,8 +26,8 @@ pub fn open_plate_info_callback( pub fn plate_info_close_callback( plate_menu_id: UseStateHandle>, -) -> Callback { - Callback::from(move |_| { +) -> NoParamsCallback { + Box::new(move |_| { plate_menu_id.set(None); }) } @@ -33,8 +35,8 @@ pub fn plate_info_close_callback( pub fn plate_info_delete_callback( main_dispatch: Dispatch, plate_menu_id: UseStateHandle>, -) -> Callback { - Callback::from(move |_| { +) -> NoParamsCallback { + Box::new(move |_| { if let Some(id) = *plate_menu_id { main_dispatch.reduce_mut(|state| { state.del_plate(id); @@ -87,7 +89,7 @@ pub fn destination_plate_select_callback( }) } -pub fn transfer_select_callback(main_state: Rc) -> Callback { +pub fn transfer_select_callback(main_state: Rc, main_dispatch: Dispatch, ct_dispatch: Dispatch) -> Callback { Callback::from(move |e: MouseEvent| { let target: Option = e.target(); let li = target.and_then(|t| t.dyn_into::().ok()); diff --git a/src/components/new_plate_dialog.rs b/src/components/new_plate_dialog.rs index 6451000..31fee6c 100644 --- a/src/components/new_plate_dialog.rs +++ b/src/components/new_plate_dialog.rs @@ -1,11 +1,9 @@ use yew::prelude::*; use yewdux::prelude::*; -use wasm_bindgen::JsCast; -use web_sys::{EventTarget, FormData, HtmlDialogElement, HtmlFormElement}; +use web_sys::HtmlDialogElement; use crate::components::states::MainState; -use crate::data::plate::*; use crate::data::plate_instances::PlateInstance; use crate::components::callbacks::new_plate_dialog_callbacks; diff --git a/src/components/transfer_menu.rs b/src/components/transfer_menu.rs index 052f53f..28d355e 100644 --- a/src/components/transfer_menu.rs +++ b/src/components/transfer_menu.rs @@ -3,14 +3,11 @@ use lazy_static::lazy_static; use regex::Regex; use serde::{Deserialize, Serialize}; -use uuid::Uuid; -use wasm_bindgen::JsCast; -use web_sys::{EventTarget, HtmlInputElement}; use yew::prelude::*; use yewdux::prelude::*; use crate::components::callbacks::transfer_menu_callbacks; -use crate::data::{transfer::Transfer, transfer_region::Region}; +use crate::data::transfer_region::Region; use super::states::{CurrentTransfer, MainState}; diff --git a/src/components/tree.rs b/src/components/tree.rs index 90359c5..82e3766 100644 --- a/src/components/tree.rs +++ b/src/components/tree.rs @@ -46,7 +46,9 @@ pub fn Tree(props: &TreeProps) -> Html { let transfer_select_callback = { let main_state = main_state.clone(); - tree_callbacks::transfer_select_callback(main_state) + let main_dispatch = main_dispatch.clone(); + let ct_dispatch = ct_dispatch.clone(); + tree_callbacks::transfer_select_callback(main_state, main_dispatch, ct_dispatch) }; let source_plates = main_state