diff --git a/src/components/main_window.rs b/src/components/main_window.rs index ad7b865..d31a40f 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -1,9 +1,9 @@ #![allow(non_snake_case)] +use js_sys::Array; +use wasm_bindgen::{prelude::*, JsCast, JsValue}; +use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url}; use yew::prelude::*; use yewdux::prelude::*; -use wasm_bindgen::{JsValue, JsCast, prelude::*}; -use web_sys::{Blob, Url, HtmlAnchorElement, HtmlDialogElement, HtmlInputElement, HtmlFormElement}; -use js_sys::Array; use super::new_plate_dialog::NewPlateDialog; use super::plates::plate_container::PlateContainer; @@ -11,8 +11,8 @@ use super::states::{CurrentTransfer, MainState}; use super::transfer_menu::TransferMenu; use super::tree::Tree; -use crate::data::plate_instances::PlateInstance; use crate::data::csv::state_to_csv; +use crate::data::plate_instances::PlateInstance; #[function_component] pub fn MainWindow() -> Html { @@ -59,7 +59,8 @@ pub fn MainWindow() -> Html { let ct_dispatch = ct_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?"); + 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()); @@ -73,8 +74,11 @@ pub fn MainWindow() -> Html { let main_state = main_state.clone(); Callback::from(move |_| { if main_state.transfers.len() == 0 { - web_sys::window().unwrap().alert_with_message("No transfers to export.").unwrap(); - return () + 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) { @@ -89,7 +93,10 @@ pub fn MainWindow() -> Html { 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(); + web_sys::window() + .unwrap() + .alert_with_message("Failed to export.") + .unwrap(); } }) }; @@ -100,7 +107,11 @@ pub fn MainWindow() -> Html { 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(); + let modal = document + .create_element("dialog") + .unwrap() + .dyn_into::() + .unwrap(); modal.set_text_content(Some("Import File:")); let onclose_callback = { let modal = modal.clone(); @@ -111,8 +122,16 @@ pub fn MainWindow() -> Html { 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(); + 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(); @@ -121,33 +140,38 @@ pub fn MainWindow() -> Html { 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 |e: Event| { - log::debug!("{:?}",&fr1.result()); - 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) + 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 |e: Event| { + log::debug!("{:?}", &fr1.result()); + 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 :( @@ -187,15 +211,18 @@ pub fn MainWindow() -> Html { } fn save_str(data: &str, name: &str) { - let blob = Blob::new_with_str_sequence( - &Array::from_iter(std::iter::once(JsValue::from_str(data)))); + 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(); + let anchor = document + .create_element("a") + .unwrap() + .dyn_into::() + .unwrap(); anchor.set_download(name); anchor.set_href(&url); anchor.click(); diff --git a/src/components/plates/destination_plate.rs b/src/components/plates/destination_plate.rs index c17fa85..a415258 100644 --- a/src/components/plates/destination_plate.rs +++ b/src/components/plates/destination_plate.rs @@ -61,8 +61,11 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html { let mut color_counter: u8 = 0; let color_map = { - let ts = main_state.transfers.iter().filter(|t| t.dest_id == props.destination_plate.get_uuid()); - let mut color_map: HashMap<(u8,u8), u8> = HashMap::new(); + let ts = main_state + .transfers + .iter() + .filter(|t| t.dest_id == props.destination_plate.get_uuid()); + let mut color_map: HashMap<(u8, u8), u8> = HashMap::new(); for t in ts { color_counter += 1; let dws = t.transfer_region.get_destination_wells(); @@ -142,7 +145,7 @@ pub struct DestPlateCellProps { pub selected: bool, pub mouse: Callback<(u8, u8, MouseEventType)>, pub in_transfer: Option, - color: Option<(u8,u8)>, + color: Option<(u8, u8)>, } #[function_component] @@ -156,8 +159,8 @@ fn DestPlateCell(props: &DestPlateCellProps) -> Html { _ => None, }; let color = match props.color { - Some(num) => PALETTE.get_u8(num.0,num.1), - None => [255.0,255.0,255.0] + Some(num) => PALETTE.get_u8(num.0, num.1), + None => [255.0, 255.0, 255.0], }; let mouse = Callback::clone(&props.mouse); let mouse2 = Callback::clone(&props.mouse); diff --git a/src/components/plates/source_plate.rs b/src/components/plates/source_plate.rs index 221858b..14ba971 100644 --- a/src/components/plates/source_plate.rs +++ b/src/components/plates/source_plate.rs @@ -41,8 +41,11 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html { let mut color_counter: u8 = 0; let color_map = { - let ts = main_state.transfers.iter().filter(|t| t.source_id == props.source_plate.get_uuid()); - let mut color_map: HashMap<(u8,u8), u8> = HashMap::new(); + let ts = main_state + .transfers + .iter() + .filter(|t| t.source_id == props.source_plate.get_uuid()); + let mut color_map: HashMap<(u8, u8), u8> = HashMap::new(); for t in ts { color_counter += 1; let sws = t.transfer_region.get_source_wells(); @@ -139,7 +142,7 @@ pub struct SourcePlateCellProps { selected: bool, mouse: Callback<(u8, u8, MouseEventType)>, in_transfer: Option, - color: Option<(u8,u8)>, + color: Option<(u8, u8)>, } #[derive(Debug)] pub enum MouseEventType { @@ -158,8 +161,8 @@ fn SourcePlateCell(props: &SourcePlateCellProps) -> Html { _ => None, }; let color = match props.color { - Some(num) => PALETTE.get_u8(num.0,num.1), - None => [255.0,255.0,255.0] + Some(num) => PALETTE.get_u8(num.0, num.1), + None => [255.0, 255.0, 255.0], }; let mouse = Callback::clone(&props.mouse); let mouse2 = Callback::clone(&props.mouse); @@ -174,7 +177,7 @@ fn SourcePlateCell(props: &SourcePlateCellProps) -> Html { onmouseenter={move |_| { mouse2.emit((i,j, MouseEventType::MOUSEENTER)) }}> -
} diff --git a/src/components/plates/util.rs b/src/components/plates/util.rs index 7a6847f..a923119 100644 --- a/src/components/plates/util.rs +++ b/src/components/plates/util.rs @@ -17,16 +17,16 @@ impl ColorPalette { pub fn get(&self, t: f64) -> [f64; 3] { [ - (self.a[0] + self.b[0]*f64::cos(6.28318*(self.c[0]*t+self.d[0])))*255.0, - (self.a[1] + self.b[1]*f64::cos(6.28318*(self.c[1]*t+self.d[1])))*255.0, - (self.a[2] + self.b[2]*f64::cos(6.28318*(self.c[2]*t+self.d[2])))*255.0, + (self.a[0] + self.b[0] * f64::cos(6.28318 * (self.c[0] * t + self.d[0]))) * 255.0, + (self.a[1] + self.b[1] * f64::cos(6.28318 * (self.c[1] * t + self.d[1]))) * 255.0, + (self.a[2] + self.b[2] * f64::cos(6.28318 * (self.c[2] * t + self.d[2]))) * 255.0, ] } pub fn get_u8(&self, t: u8, n: u8) -> [f64; 3] { - assert!(t>0, "t must be greater than zero!"); - assert!(n>0, "There cannot be zero points!"); - self.get((t-1) as f64 / (n-1) as f64) + assert!(t > 0, "t must be greater than zero!"); + assert!(n > 0, "There cannot be zero points!"); + self.get((t - 1) as f64 / (n - 1) as f64) } } @@ -36,15 +36,15 @@ pub struct Palettes; #[allow(dead_code)] impl Palettes { pub const RAINBOW: ColorPalette = ColorPalette { - a: [0.500,0.500,0.500], - b: [0.500,0.500,0.500], - c: [0.800,0.800,0.800], - d: [0.000,0.333,0.667], + a: [0.500, 0.500, 0.500], + b: [0.500, 0.500, 0.500], + c: [0.800, 0.800, 0.800], + d: [0.000, 0.333, 0.667], }; pub const YELLOW_PINK: ColorPalette = ColorPalette { - a: [0.500,0.500,0.320], - b: [0.500,0.500,0.500], - c: [0.100,0.500,0.360], - d: [0.000,0.000,0.650], + a: [0.500, 0.500, 0.320], + b: [0.500, 0.500, 0.500], + c: [0.100, 0.500, 0.360], + d: [0.000, 0.000, 0.650], }; } diff --git a/src/components/transfer_menu.rs b/src/components/transfer_menu.rs index 8b077fc..54d3ddc 100644 --- a/src/components/transfer_menu.rs +++ b/src/components/transfer_menu.rs @@ -3,9 +3,9 @@ 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 uuid::Uuid; use yew::prelude::*; use yewdux::prelude::*; @@ -28,10 +28,9 @@ pub fn TransferMenu() -> Html { ct_dispatch.reduce_mut(|state| { state.transfer.name = input.value().clone(); }); - } } - - )}; + }) + }; let on_src_region_change = { let ct_dispatch = ct_dispatch.clone(); @@ -174,19 +173,24 @@ pub fn TransferMenu() -> Html { spi.clone(), dpi.clone(), ct_state.transfer.transfer_region, - ct_state.transfer.name.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(); + state.selected_transfer = state + .transfers + .last() + .expect("An element should have just been added") + .get_uuid(); }); } } } else { - if let Some(index) = main_state.transfers.iter() - .position(|t| t.get_uuid() == main_state.selected_transfer) { + 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(); }); @@ -205,8 +209,11 @@ pub fn TransferMenu() -> Html { 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()) { + 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(); diff --git a/src/components/tree.rs b/src/components/tree.rs index d85029b..917bb9a 100644 --- a/src/components/tree.rs +++ b/src/components/tree.rs @@ -104,9 +104,11 @@ pub fn Tree(props: &TreeProps) -> Html { if let Some(li) = li { if let Ok(id) = u128::from_str_radix(li.id().as_str(), 10) { let id = Uuid::from_u128(id); - if let Some(transfer) = main_state.transfers - .iter().find(|transfer| transfer.get_uuid() == 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; @@ -237,11 +239,12 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html { let id = props.id; Callback::from(move |e: Event| { log::debug!("Changed name"); - let input = e.target().expect("Event must have target") - .dyn_into::().unwrap(); - main_dispatch.reduce_mut(|state| { - state.rename_plate(id, &input.value()) - }) + let input = e + .target() + .expect("Event must have target") + .dyn_into::() + .unwrap(); + main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value())) }) }; diff --git a/src/data/csv.rs b/src/data/csv.rs index 094d078..749ad05 100644 --- a/src/data/csv.rs +++ b/src/data/csv.rs @@ -1,9 +1,9 @@ -use crate::data::transfer::Transfer; -use crate::components::transfer_menu::num_to_letters; use crate::components::states::MainState; +use crate::components::transfer_menu::num_to_letters; +use crate::data::transfer::Transfer; -use std::{error::Error}; use serde::Serialize; +use std::error::Error; #[derive(Serialize, Debug)] struct TransferRecord { @@ -24,13 +24,23 @@ struct TransferRecord { pub fn state_to_csv(state: &MainState) -> Result> { let mut records: Vec = Vec::new(); for transfer in &state.transfers { - let src_barcode = state.source_plates.iter().find(|spi| spi.get_uuid() == transfer.source_id) - .ok_or("Found unpurged transfer")?; - let dest_barcode = state.destination_plates.iter().find(|dpi| dpi.get_uuid() == transfer.dest_id) - .ok_or("Found unpurged transfer")?; - records.append(&mut transfer_to_records(transfer, &src_barcode.name, &dest_barcode.name)) + let src_barcode = state + .source_plates + .iter() + .find(|spi| spi.get_uuid() == transfer.source_id) + .ok_or("Found unpurged transfer")?; + let dest_barcode = state + .destination_plates + .iter() + .find(|dpi| dpi.get_uuid() == transfer.dest_id) + .ok_or("Found unpurged transfer")?; + records.append(&mut transfer_to_records( + transfer, + &src_barcode.name, + &dest_barcode.name, + )) } - return records_to_csv(records) + return records_to_csv(records); } fn transfer_to_records( @@ -47,17 +57,18 @@ fn transfer_to_records( let dest_wells = map(s_well); if let Some(dest_wells) = dest_wells { for d_well in dest_wells { - records.push(TransferRecord { + records.push(TransferRecord { source_plate: src_barcode.to_string(), source_well: format!("{}{}", num_to_letters(s_well.0).unwrap(), s_well.1), destination_plate: dest_barcode.to_string(), destination_well: format!("{}{}", num_to_letters(d_well.0).unwrap(), d_well.1), volume: 2.5, // Default value since not yet implemented - concentration: None }) + concentration: None, + }) } } } - return records + return records; } fn records_to_csv(trs: Vec) -> Result> { @@ -66,5 +77,5 @@ fn records_to_csv(trs: Vec) -> Result> { wtr.serialize(record)? } let data = String::from_utf8(wtr.into_inner()?)?; - return Ok(data) + return Ok(data); } diff --git a/src/data/mod.rs b/src/data/mod.rs index 5b695a1..bc5afaa 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,5 +1,5 @@ +pub mod csv; pub mod plate; pub mod plate_instances; pub mod transfer; pub mod transfer_region; -pub mod csv; diff --git a/src/data/transfer_region.rs b/src/data/transfer_region.rs index de01ec6..ead1db1 100644 --- a/src/data/transfer_region.rs +++ b/src/data/transfer_region.rs @@ -515,19 +515,48 @@ mod tests { let transfer2 = TransferRegion { source_plate: Plate::new(PlateType::Source, PlateFormat::W384), dest_plate: Plate::new(PlateType::Destination, PlateFormat::W384), - source_region: Region::Rect((1,1), (2,3)), - dest_region: Region::Rect((2,2), (11,16)), + source_region: Region::Rect((1, 1), (2, 3)), + dest_region: Region::Rect((2, 2), (11, 16)), interleave_source: (1, 1), interleave_dest: (2, 2), }; let transfer2_source = transfer2.get_source_wells(); let transfer2_dest = transfer2.get_destination_wells(); - assert_eq!(transfer2_source, vec![(1,1),(1,2),(1,3),(2,1),(2,2),(2,3)], "Failed type replicate 2 source"); - assert_eq!(transfer2_dest, - vec![(2,2),(2,8),(6,2),(6,8),(2,4),(2,10),(6,4),(6,10),(2,6),(2,12),(6,6),(6,12), - (4,2),(4,8),(8,2),(8,8),(4,4),(4,10),(8,4),(8,10),(4,6),(4,12),(8,6),(8,12)], - "Failed type replicate 2 destination"); - + assert_eq!( + transfer2_source, + vec![(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)], + "Failed type replicate 2 source" + ); + assert_eq!( + transfer2_dest, + vec![ + (2, 2), + (2, 8), + (6, 2), + (6, 8), + (2, 4), + (2, 10), + (6, 4), + (6, 10), + (2, 6), + (2, 12), + (6, 6), + (6, 12), + (4, 2), + (4, 8), + (8, 2), + (8, 8), + (4, 4), + (4, 10), + (8, 4), + (8, 10), + (4, 6), + (4, 12), + (8, 6), + (8, 12) + ], + "Failed type replicate 2 destination" + ); } #[test] @@ -536,8 +565,8 @@ mod tests { let transfer1 = TransferRegion { source_plate: Plate::new(PlateType::Source, PlateFormat::W384), dest_plate: Plate::new(PlateType::Destination, PlateFormat::W384), - source_region: Region::Rect((1,4),(3,7)), - dest_region: Region::Point((1,9)), + source_region: Region::Rect((1, 4), (3, 7)), + dest_region: Region::Point((1, 9)), interleave_source: (1, 1), interleave_dest: (0, 2), }; @@ -547,8 +576,20 @@ mod tests { transfer1_dest.dedup(); // Makes our check easier, otherwise we have repeated wells let transfer1_map = transfer1.calculate_map(); // Skipping source check---it's just 12 wells. - assert_eq!(transfer1_dest, vec![(1,9),(1,11),(1,13),(1,15)], "Failed type pool 1 dest"); - assert_eq!(transfer1_map((2,6)), Some(vec![(1,13)]), "Failed type pool 1 map 1"); - assert_eq!(transfer1_map((3,7)), Some(vec![(1,15)]), "Failed type pool 1 map 2"); + assert_eq!( + transfer1_dest, + vec![(1, 9), (1, 11), (1, 13), (1, 15)], + "Failed type pool 1 dest" + ); + assert_eq!( + transfer1_map((2, 6)), + Some(vec![(1, 13)]), + "Failed type pool 1 map 1" + ); + assert_eq!( + transfer1_map((3, 7)), + Some(vec![(1, 15)]), + "Failed type pool 1 map 2" + ); } }