use std::collections::HashSet; use wasm_bindgen::{prelude::*, JsCast}; use web_sys::{ FileReader, HtmlButtonElement, HtmlDialogElement, HtmlElement, HtmlFormElement, HtmlInputElement, HtmlOptionElement, HtmlSelectElement, }; use yew::prelude::*; use yewdux::prelude::*; use crate::components::states::MainState; use plate_tool_lib::transfer::Transfer; use plate_tool_lib::transfer_region::{Region, TransferRegion}; use plate_tool_lib::csv::{auto, string_well_to_pt, TransferRecord}; use super::main_window_callbacks::create_close_button; 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 records = plate_tool_lib::csv::read_csv(value); 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 auto_button = document .create_element("button") .unwrap() .dyn_into::() .unwrap(); auto_button.set_inner_text("Auto"); let auto_button_callback = auto_callback(main_dispatch.clone(), &records); auto_button.set_onclick(Some(auto_button_callback.as_ref().unchecked_ref())); auto_button_callback.forget(); 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(); modal.append_child(&auto_button).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(); 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| { ( string_well_to_pt(&record.source_well).unwrap(), string_well_to_pt(&record.destination_well).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(); }); }) } fn auto_callback( main_dispatch: Dispatch, records: &[TransferRecord], ) -> Closure { let records = Vec::from(records); Closure::::new(move |_| { let res = auto(&records); main_dispatch.reduce_mut(|state| { state.source_plates.extend(res.sources.into_iter()); state .destination_plates .extend(res.destinations.into_iter()); state.transfers.extend(res.transfers.into_iter()); }); }) }