diff --git a/src/components/callbacks/import_csv_callbacks.rs b/src/components/callbacks/import_csv_callbacks.rs new file mode 100644 index 0000000..a10b6a3 --- /dev/null +++ b/src/components/callbacks/import_csv_callbacks.rs @@ -0,0 +1,308 @@ +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, HtmlElement, + 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}; + +use super::main_window_callbacks::create_close_button; + +type NoParamsCallback = Box ()>; + +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) + } + } + } + }) +} + +pub fn import_transfer_csv_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())); + + let close_button = create_close_button(&onclose_callback); + onclose_callback.forget(); + modal.append_child(&close_button).unwrap(); + + let form = document + .create_element("form") + .unwrap() + .dyn_into::() + .unwrap(); + let input = document + .create_element("input") + .unwrap() + .dyn_into::() + .unwrap(); + input.set_type("file"); + input.set_accept(".csv"); + form.append_child(&input).unwrap(); + + let input_callback = { + let main_dispatch = main_dispatch.clone(); + let modal = modal.clone(); + 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 :( + + modal.append_child(&form).unwrap(); + body.append_child(&modal).unwrap(); + modal.show_modal().unwrap(); + }) +} + +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_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(); + }); + }) +} diff --git a/src/components/callbacks/main_window_callbacks.rs b/src/components/callbacks/main_window_callbacks.rs index f94f9a5..3213792 100644 --- a/src/components/callbacks/main_window_callbacks.rs +++ b/src/components/callbacks/main_window_callbacks.rs @@ -22,7 +22,7 @@ use crate::data::csv::{state_to_csv, TransferRecord}; type NoParamsCallback = Box ()>; -fn create_close_button(close: &Closure) -> HtmlElement { +pub fn create_close_button(close: &Closure) -> HtmlElement { let document = web_sys::window().unwrap().document().unwrap(); let close_button = document .create_element("button") @@ -216,287 +216,10 @@ pub fn import_json_button_callback(main_dispatch: Dispatch) -> Callba }) } -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(); +pub use super::import_csv_callbacks::import_transfer_csv_callback; - 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]); +pub use super::import_csv_callbacks::import_transfer_csv_submit_callback; - ( - ( - 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) - } - } - } - }) -} - -pub fn import_transfer_csv_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())); - - let close_button = create_close_button(&onclose_callback); - onclose_callback.forget(); - modal.append_child(&close_button).unwrap(); - - let form = document - .create_element("form") - .unwrap() - .dyn_into::() - .unwrap(); - let input = document - .create_element("input") - .unwrap() - .dyn_into::() - .unwrap(); - input.set_type("file"); - input.set_accept(".csv"); - form.append_child(&input).unwrap(); - - let input_callback = { - let main_dispatch = main_dispatch.clone(); - let modal = modal.clone(); - 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 :( - - modal.append_child(&form).unwrap(); - body.append_child(&modal).unwrap(); - modal.show_modal().unwrap(); - }) -} +pub use super::import_csv_callbacks::import_transfer_csv_onload_callback; +pub use super::import_csv_callbacks::import_transfer_csv_input_callback; diff --git a/src/components/callbacks/mod.rs b/src/components/callbacks/mod.rs index 175182a..daec33c 100644 --- a/src/components/callbacks/mod.rs +++ b/src/components/callbacks/mod.rs @@ -2,3 +2,4 @@ pub mod main_window_callbacks; pub mod new_plate_dialog_callbacks; pub mod transfer_menu_callbacks; pub mod tree_callbacks; +mod import_csv_callbacks;