From 020f7740d3a62a91d054b625dbc3f86bad4f758b Mon Sep 17 00:00:00 2001 From: Emilia Date: Sat, 10 Feb 2024 16:38:00 -0500 Subject: [PATCH] Add callbacks per #2 Squashed commit of the following: commit 47103357509094203689db14c4bcc7ae5fd9b932 Author: Emilia Date: Sat Feb 10 16:34:52 2024 -0500 tree callbacks commit f8216cb0bdbf2201307276544a45daa6fc64f2c3 Author: Emilia Date: Sat Feb 10 09:20:59 2024 -0500 transfer_menu callbacks commit 15accc2fcabc20fb0e7f3fea041c47d9e1ef1661 Author: Emilia Date: Tue Jan 30 20:37:15 2024 -0500 I think this file was supposed to be in the last one lol commit 53457a3e8688683a7c5d892af1a0d69c9d16b2d8 Author: Emilia Date: Sat Jan 13 14:05:34 2024 -0500 new_plate_dialog callbacks For #2 commit c2a3f0302bab95f0a811f2806668ee22d18e65e0 Author: Emilia Date: Sat Jan 13 13:57:14 2024 -0500 Finish main_window callback refactor For #2 commit 820f672cb7a630a0cb3a4e27cd9354ddd2c29ba7 Author: Emilia Date: Sat Jan 13 13:23:11 2024 -0500 Import json button refactor Partial for #2 commit a90b5f83d88005456a0923802d7dab6d9bebcb4d Author: Emilia Date: Fri Jan 12 22:22:28 2024 -0500 A number of moves to address #2 commit 62d870521e100580ccd3277e99a64a3a08ad8047 Author: Emilia Date: Fri Jan 12 21:44:10 2024 -0500 First callback move commit e2ef9fa84df5b78b54847fecc65ccd59264facfe Author: Emilia Date: Fri Jan 12 21:43:50 2024 -0500 Increment version number in cargo.lock why wasn't this done already? --- Cargo.lock | 2 +- .../callbacks/main_window_callbacks.rs | 433 ++++++++++++++++++ src/components/callbacks/mod.rs | 4 + .../callbacks/new_plate_dialog_callbacks.rs | 61 +++ .../callbacks/transfer_menu_callbacks.rs | 242 ++++++++++ src/components/callbacks/tree_callbacks.rs | 114 +++++ src/components/main_window.rs | 384 +--------------- src/components/mod.rs | 1 + src/components/new_plate_dialog.rs | 51 +-- src/components/transfer_menu.rs | 223 ++------- src/components/tree.rs | 89 +--- 11 files changed, 929 insertions(+), 675 deletions(-) create mode 100644 src/components/callbacks/main_window_callbacks.rs create mode 100644 src/components/callbacks/mod.rs create mode 100644 src/components/callbacks/new_plate_dialog_callbacks.rs create mode 100644 src/components/callbacks/transfer_menu_callbacks.rs create mode 100644 src/components/callbacks/tree_callbacks.rs 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", diff --git a/src/components/callbacks/main_window_callbacks.rs b/src/components/callbacks/main_window_callbacks.rs new file mode 100644 index 0000000..268407b --- /dev/null +++ b/src/components/callbacks/main_window_callbacks.rs @@ -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 ()>; + +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; + }) + }) +} + +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(); + } + }) +} + +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(); + }) +} + +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/callbacks/mod.rs b/src/components/callbacks/mod.rs new file mode 100644 index 0000000..175182a --- /dev/null +++ b/src/components/callbacks/mod.rs @@ -0,0 +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/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(()); + }) +} 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/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/main_window.rs b/src/components/main_window.rs index 55a7970..e72f035 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -1,27 +1,20 @@ #![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 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::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 { @@ -51,149 +44,32 @@ 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; - }) - }) + 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(); - } - }) - }; + 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 = { @@ -233,227 +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 :( 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; 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 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, + }, } } } 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