diff --git a/src/components/main_window.rs b/src/components/main_window.rs index 31b95ec..d4f4cef 100644 --- a/src/components/main_window.rs +++ b/src/components/main_window.rs @@ -2,11 +2,11 @@ use yew::prelude::*; use yewdux::prelude::*; -use super::states::{MainState, CurrentTransfer}; -use super::plates::plate_container::PlateContainer; -use super::tree::Tree; -use super::transfer_menu::TransferMenu; use super::new_plate_dialog::NewPlateDialog; +use super::plates::plate_container::PlateContainer; +use super::states::{CurrentTransfer, MainState}; +use super::transfer_menu::TransferMenu; +use super::tree::Tree; use crate::data::plate_instances::PlateInstance; @@ -15,20 +15,24 @@ pub fn MainWindow() -> Html { let (main_state, main_dispatch) = use_store::(); let (ct_state, ct_dispatch) = use_store::(); - let source_plate_instance: Option = main_state.source_plates.iter() - .find(|spi| {spi.get_uuid() == main_state.selected_source_plate}) + let source_plate_instance: Option = main_state + .source_plates + .iter() + .find(|spi| spi.get_uuid() == main_state.selected_source_plate) .cloned(); if let Some(spi) = source_plate_instance.clone() { - ct_dispatch.reduce_mut(|state| { - state.transfer.source_plate = spi.plate; + ct_dispatch.reduce_mut(|state| { + state.transfer.transfer_region.source_plate = spi.plate; }); } - let destination_plate_instance: Option = main_state.destination_plates.iter() - .find(|dpi| {dpi.get_uuid() == main_state.selected_dest_plate}) + let destination_plate_instance: Option = main_state + .destination_plates + .iter() + .find(|dpi| dpi.get_uuid() == main_state.selected_dest_plate) .cloned(); if let Some(dpi) = destination_plate_instance.clone() { - ct_dispatch.reduce_mut(|state| { - state.transfer.dest_plate = dpi.plate; + ct_dispatch.reduce_mut(|state| { + state.transfer.transfer_region.dest_plate = dpi.plate; }); } @@ -46,7 +50,7 @@ pub fn MainWindow() -> Html { }) }; - html!{ + html! {
diff --git a/src/components/mod.rs b/src/components/mod.rs index ed345c7..ac482b6 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,6 +1,6 @@ -pub mod states; pub mod main_window; -pub mod tree; -pub mod transfer_menu; -pub mod plates; pub mod new_plate_dialog; +pub mod plates; +pub mod states; +pub mod transfer_menu; +pub mod tree; diff --git a/src/components/new_plate_dialog.rs b/src/components/new_plate_dialog.rs index 8b2a129..dcfb117 100644 --- a/src/components/new_plate_dialog.rs +++ b/src/components/new_plate_dialog.rs @@ -2,11 +2,11 @@ use yew::prelude::*; use yewdux::prelude::*; use wasm_bindgen::JsCast; -use web_sys::{EventTarget, HtmlFormElement, FormData, HtmlDialogElement}; +use web_sys::{EventTarget, FormData, HtmlDialogElement, HtmlFormElement}; -use crate::data::{plate_instances::PlateInstance, transfer::Transfer}; -use crate::data::plate::*; use crate::components::states::MainState; +use crate::data::plate::*; +use crate::data::{plate_instances::PlateInstance, transfer::Transfer}; #[derive(PartialEq, Properties)] pub struct NewPlateDialogProps { @@ -41,9 +41,17 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html { }; dispatch.reduce_mut(|s| { if plate_type == PlateType::Source { - s.add_source_plate(PlateInstance::new(PlateType::Source, format, name)) + s.add_source_plate(PlateInstance::new( + PlateType::Source, + format, + name, + )) } else { - s.add_dest_plate(PlateInstance::new(PlateType::Destination, format, name)) + s.add_dest_plate(PlateInstance::new( + PlateType::Destination, + format, + name, + )) } }); } @@ -57,10 +65,16 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html { { let dialog_ref = dialog_ref.clone(); - use_effect_with_deps(|dialog_ref| { - dialog_ref.cast::().unwrap().show_modal().ok(); - }, - dialog_ref); + use_effect_with_deps( + |dialog_ref| { + dialog_ref + .cast::() + .unwrap() + .show_modal() + .ok(); + }, + dialog_ref, + ); } html! { diff --git a/src/components/plates/destination_plate.rs b/src/components/plates/destination_plate.rs index 497bc7d..9de0048 100644 --- a/src/components/plates/destination_plate.rs +++ b/src/components/plates/destination_plate.rs @@ -1,11 +1,11 @@ #![allow(non_snake_case)] +use std::rc::Rc; use yew::prelude::*; use yewdux::prelude::*; -use std::rc::Rc; +use crate::components::states::CurrentTransfer; use crate::data::plate_instances::PlateInstance; -use crate::data::transfer_region::{TransferRegion, Region}; -use crate::components::states::{CurrentTransfer}; +use crate::data::transfer_region::{Region, TransferRegion}; use super::super::transfer_menu::RegionDisplay; @@ -18,38 +18,36 @@ pub struct DestinationPlateProps { #[function_component] pub fn DestinationPlate(props: &DestinationPlateProps) -> Html { let (ct_state, ct_dispatch) = use_store::(); - let m_start_handle: UseStateHandle> = use_state_eq(|| None); - let m_end_handle: UseStateHandle> = use_state_eq(|| None); + let m_start_handle: UseStateHandle> = use_state_eq(|| None); + let m_end_handle: UseStateHandle> = use_state_eq(|| None); let m_stat_handle: UseStateHandle = use_state_eq(|| false); let m_start = m_start_handle.clone(); let m_end = m_end_handle.clone(); if !(*m_stat_handle) { - let (pt1, pt2) = match ct_state.transfer.dest_region { - Region::Point((x,y)) => ((x,y),(x,y)), + let (pt1, pt2) = match ct_state.transfer.transfer_region.dest_region { + Region::Point((x, y)) => ((x, y), (x, y)), Region::Rect(c1, c2) => (c1, c2), }; m_start_handle.set(Some(pt1)); m_end_handle.set(Some(pt2)); } - let destination_wells = ct_state.transfer.get_destination_wells(); + let destination_wells = ct_state.transfer.transfer_region.get_destination_wells(); let mouse_callback = { let m_start_handle = m_start_handle.clone(); let m_end_handle = m_end_handle.clone(); let m_stat_handle = m_stat_handle.clone(); - Callback::from(move |(i,j,t)| { - match t { - MouseEventType::MOUSEDOWN => { - m_start_handle.set(Some((i,j))); - m_end_handle.set(Some((i,j))); - m_stat_handle.set(true); - }, - MouseEventType::MOUSEENTER => { - if *m_stat_handle { - m_end_handle.set(Some((i,j))); - } + Callback::from(move |(i, j, t)| match t { + MouseEventType::MOUSEDOWN => { + m_start_handle.set(Some((i, j))); + m_end_handle.set(Some((i, j))); + m_stat_handle.set(true); + } + MouseEventType::MOUSEENTER => { + if *m_stat_handle { + m_end_handle.set(Some((i, j))); } } }) @@ -66,7 +64,7 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html { if let Some(br) = *m_end_handle { if let Ok(rd) = RegionDisplay::try_from((ul.0, ul.1, br.0, br.1)) { ct_dispatch.reduce_mut(|state| { - state.transfer.dest_region = Region::from(&rd); + state.transfer.transfer_region.dest_region = Region::from(&rd); }); } } @@ -76,22 +74,24 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html { let mouseleave_callback = Callback::clone(&mouseup_callback); - let rows = (1..=props.destination_plate.plate.size().0).map(|i| { - let row = (1..=props.destination_plate.plate.size().1).map(|j| { + let rows = (1..=props.destination_plate.plate.size().0) + .map(|i| { + let row = (1..=props.destination_plate.plate.size().1).map(|j| { html! { - } }).collect::(); - html! { - - { row } - - } - }).collect::(); + html! { + + { row } + + } + }) + .collect::(); html! {
@@ -111,7 +111,7 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html { #[derive(Debug)] pub enum MouseEventType { MOUSEDOWN, - MOUSEENTER + MOUSEENTER, } #[derive(Properties, PartialEq)] @@ -119,7 +119,7 @@ pub struct DestPlateCellProps { pub i: u8, pub j: u8, pub selected: bool, - pub mouse: Callback<(u8,u8, MouseEventType)>, + pub mouse: Callback<(u8, u8, MouseEventType)>, pub in_transfer: Option, } @@ -131,11 +131,11 @@ fn DestPlateCell(props: &DestPlateCellProps) -> Html { }; let in_transfer_class = match props.in_transfer { Some(true) => Some("in_transfer"), - _ => None + _ => None, }; let mouse = Callback::clone(&props.mouse); let mouse2 = Callback::clone(&props.mouse); - let (i,j) = (props.i.clone(), props.j.clone()); + let (i, j) = (props.i.clone(), props.j.clone()); html! { Html { let (ct_state, ct_dispatch) = use_store::(); - let m_start_handle: UseStateHandle> = use_state_eq(|| None); - let m_end_handle: UseStateHandle> = use_state_eq(|| None); + let m_start_handle: UseStateHandle> = use_state_eq(|| None); + let m_end_handle: UseStateHandle> = use_state_eq(|| None); let m_stat_handle: UseStateHandle = use_state_eq(|| false); let m_start = m_start_handle.clone(); let m_end = m_end_handle.clone(); if !(*m_stat_handle) { - let (pt1, pt2) = match ct_state.transfer.source_region { - Region::Point((x,y)) => ((x,y),(x,y)), + let (pt1, pt2) = match ct_state.transfer.transfer_region.source_region { + Region::Point((x, y)) => ((x, y), (x, y)), Region::Rect(c1, c2) => (c1, c2), }; m_start_handle.set(Some(pt1)); m_end_handle.set(Some(pt2)); } - let source_wells = ct_state.transfer.get_source_wells(); + let source_wells = ct_state.transfer.transfer_region.get_source_wells(); let mouse_callback = { let m_start_handle = m_start_handle.clone(); let m_end_handle = m_end_handle.clone(); let m_stat_handle = m_stat_handle.clone(); - Callback::from(move |(i,j,t)| { - match t { - MouseEventType::MOUSEDOWN => { - m_start_handle.set(Some((i,j))); - m_end_handle.set(Some((i,j))); - m_stat_handle.set(true); - }, - MouseEventType::MOUSEENTER => { - if *m_stat_handle { - m_end_handle.set(Some((i,j))); - } + Callback::from(move |(i, j, t)| match t { + MouseEventType::MOUSEDOWN => { + m_start_handle.set(Some((i, j))); + m_end_handle.set(Some((i, j))); + m_stat_handle.set(true); + } + MouseEventType::MOUSEENTER => { + if *m_stat_handle { + m_end_handle.set(Some((i, j))); } } }) @@ -63,37 +61,41 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html { let m_stat_handle = m_stat_handle.clone(); Callback::from(move |_: MouseEvent| { - m_stat_handle.set(false); - if let Some(ul) = *m_start_handle { - if let Some(br) = *m_end_handle { - if let Ok(rd) = RegionDisplay::try_from((ul.0, ul.1, br.0, br.1)) { - ct_dispatch.reduce_mut(|state| { - state.transfer.source_region = Region::from(&rd); - }); + m_stat_handle.set(false); + if let Some(ul) = *m_start_handle { + if let Some(br) = *m_end_handle { + if let Ok(rd) = RegionDisplay::try_from((ul.0, ul.1, br.0, br.1)) { + ct_dispatch.reduce_mut(|state| { + state.transfer.transfer_region.source_region = Region::from(&rd); + }); + } } } - } }) }; let mouseleave_callback = Callback::clone(&mouseup_callback); - let rows = (1..=props.source_plate.plate.size().0).map(|i| { - let row = (1..=props.source_plate.plate.size().1).map(|j| { + let rows = (1..=props.source_plate.plate.size().0) + .map(|i| { + let row = (1..=props.source_plate.plate.size().1) + .map(|j| { + html! { + + } + }) + .collect::(); html! { - + + { row } + } - }).collect::(); - html! { - - { row } - - } - }).collect::(); + }) + .collect::(); html! {
@@ -115,13 +117,13 @@ pub struct SourcePlateCellProps { i: u8, j: u8, selected: bool, - mouse: Callback<(u8,u8, MouseEventType)>, + mouse: Callback<(u8, u8, MouseEventType)>, in_transfer: Option, } #[derive(Debug)] pub enum MouseEventType { MOUSEDOWN, - MOUSEENTER + MOUSEENTER, } #[function_component] @@ -132,11 +134,11 @@ fn SourcePlateCell(props: &SourcePlateCellProps) -> Html { }; let in_transfer_class = match props.in_transfer { Some(true) => Some("in_transfer"), - _ => None + _ => None, }; let mouse = Callback::clone(&props.mouse); let mouse2 = Callback::clone(&props.mouse); - let (i,j) = (props.i.clone(), props.j.clone()); + let (i, j) = (props.i.clone(), props.j.clone()); html! { Html { let (main_state, main_dispatch) = use_store::(); let (ct_state, ct_dispatch) = use_store::(); + 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 { + ct_dispatch.reduce_mut(|state| { + state.transfer.name = input.value().clone(); + }); + } + } + + )}; + let on_src_region_change = { let ct_dispatch = ct_dispatch.clone(); @@ -26,7 +42,7 @@ pub fn TransferMenu() -> Html { if let Some(input) = input { if let Ok(rd) = RegionDisplay::try_from(input.value()) { ct_dispatch.reduce_mut(|state| { - state.transfer.source_region = Region::from(&rd); + state.transfer.transfer_region.source_region = Region::from(&rd); }); input.set_custom_validity(""); } else { @@ -44,7 +60,7 @@ pub fn TransferMenu() -> Html { if let Some(input) = input { if let Ok(rd) = RegionDisplay::try_from(input.value()) { ct_dispatch.reduce_mut(|state| { - state.transfer.dest_region = Region::from(&rd); + state.transfer.transfer_region.dest_region = Region::from(&rd); }); input.set_custom_validity(""); } else { @@ -53,7 +69,7 @@ pub fn TransferMenu() -> Html { } }) }; - + let on_source_interleave_x_change = { let ct_dispatch = ct_dispatch.clone(); @@ -63,7 +79,8 @@ pub fn TransferMenu() -> Html { if let Some(input) = input { if let Ok(num) = input.value().parse::() { ct_dispatch.reduce_mut(|state| { - state.transfer.interleave_source = (num, state.transfer.interleave_source.1); + state.transfer.transfer_region.interleave_source = + (num, state.transfer.transfer_region.interleave_source.1); }); } } @@ -78,7 +95,8 @@ pub fn TransferMenu() -> Html { if let Some(input) = input { if let Ok(num) = input.value().parse::() { ct_dispatch.reduce_mut(|state| { - state.transfer.interleave_source = (state.transfer.interleave_source.0, num); + state.transfer.transfer_region.interleave_source = + (state.transfer.transfer_region.interleave_source.0, num); }); } } @@ -93,7 +111,8 @@ pub fn TransferMenu() -> Html { if let Some(input) = input { if let Ok(num) = input.value().parse::() { ct_dispatch.reduce_mut(|state| { - state.transfer.interleave_dest = (num, state.transfer.interleave_dest.1); + state.transfer.transfer_region.interleave_dest = + (num, state.transfer.transfer_region.interleave_dest.1); }); } } @@ -108,38 +127,87 @@ pub fn TransferMenu() -> Html { if let Some(input) = input { if let Ok(num) = input.value().parse::() { ct_dispatch.reduce_mut(|state| { - state.transfer.interleave_dest = (state.transfer.interleave_dest.0, num); + state.transfer.transfer_region.interleave_dest = + (state.transfer.transfer_region.interleave_dest.0, num); }); } } }) }; - let create_transfer_button_callback = { + + let new_transfer_button_callback = { + let main_dispatch = main_dispatch.clone(); + let main_state = main_state.clone(); + let ct_dispatch = ct_dispatch.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 save_transfer_button_callback = { let main_dispatch = main_dispatch.clone(); let main_state = main_state.clone(); let ct_state = ct_state.clone(); - Callback::from(move |e: MouseEvent| { - log::debug!("Button pressed"); + Callback::from(move |_: MouseEvent| { + log::debug!("Button pressed"); if main_state.selected_transfer.is_nil() { - log::debug!("Was 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.clone(), - "Reginald".to_string()); - main_dispatch.reduce_mut(|state| { - state.transfers.push(new_transfer) - }); - } + 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, + ct_state.transfer.name.clone() + ); + main_dispatch.reduce_mut(|state| state.transfers.push(new_transfer)); + } + } + } 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(); + }); + } + } + }) + }; + + let delete_transfer_button_callback = { + let main_dispatch = main_dispatch.clone(); + let main_state = main_state.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 = Transfer::new(); - main_dispatch.reduce_mut(|state| { - }); } }) }; @@ -147,40 +215,52 @@ pub fn TransferMenu() -> Html { html! {
+
+ + +
+ value={RegionDisplay::from(&ct_state.transfer.transfer_region.source_region).text}/>
+ value={RegionDisplay::from(&ct_state.transfer.transfer_region.dest_region).text}/>
{"Source Interleave "} + value={ct_state.transfer.transfer_region.interleave_source.0.to_string()}/> + value={ct_state.transfer.transfer_region.interleave_source.1.to_string()}/>
{"Destination Interleave "} + onchange={on_dest_interleave_x_change} + value={ct_state.transfer.transfer_region.interleave_dest.0.to_string()}/> + onchange={on_dest_interleave_y_change} + value={ct_state.transfer.transfer_region.interleave_dest.1.to_string()}/>
- + + +
} @@ -203,30 +283,38 @@ impl TryFrom for RegionDisplay { static ref REGION_REGEX: Regex = Regex::new(r"([A-Z]+)(\d+):([A-Z]+)(\d+)").unwrap(); } if let Some(captures) = REGION_REGEX.captures(&value) { - if captures.len() != 5 { return Err("Not enough capture groups") } + if captures.len() != 5 { + return Err("Not enough capture groups"); + } let col_start = letters_to_num(&captures[1]).ok_or("Column start failed to parse")?; let col_end = letters_to_num(&captures[3]).ok_or("Column end failed to parse")?; - let row_start: u8 = captures[2].parse::().or(Err("Row start failed to parse"))?; - let row_end: u8 = captures[4].parse::().or(Err("Row end failed to parse"))?; - return Ok(RegionDisplay { + let row_start: u8 = captures[2] + .parse::() + .or(Err("Row start failed to parse"))?; + let row_end: u8 = captures[4] + .parse::() + .or(Err("Row end failed to parse"))?; + return Ok(RegionDisplay { text: value, col_start, row_start, col_end, row_end, - }) + }); } else { - return Err("Regex match failed") + return Err("Regex match failed"); } } - } impl From<&Region> for RegionDisplay { fn from(value: &Region) -> Self { match *value { - Region::Point((col, row)) => RegionDisplay::try_from((col,row,col,row)).ok().unwrap(), - Region::Rect(c1, c2) => - RegionDisplay::try_from((c1.0,c1.1,c2.0,c2.1)).ok().unwrap() + Region::Point((col, row)) => { + RegionDisplay::try_from((col, row, col, row)).ok().unwrap() + } + Region::Rect(c1, c2) => RegionDisplay::try_from((c1.0, c1.1, c2.0, c2.1)) + .ok() + .unwrap(), } } } @@ -235,14 +323,17 @@ impl From<&RegionDisplay> for Region { if value.col_start == value.col_end && value.row_start == value.row_end { Region::Point((value.col_start, value.row_start)) } else { - Region::Rect((value.col_start, value.row_start), (value.col_end, value.row_end)) + Region::Rect( + (value.col_start, value.row_start), + (value.col_end, value.row_end), + ) } } } -impl TryFrom<(u8,u8,u8,u8)> for RegionDisplay { - type Error = &'static str; +impl TryFrom<(u8, u8, u8, u8)> for RegionDisplay { + type Error = &'static str; - fn try_from(value: (u8,u8,u8,u8)) -> Result { + fn try_from(value: (u8, u8, u8, u8)) -> Result { // (Column Start, Row Start, Column End, Row End) // This can only possibly fail if one of the coordinates is zero... let cs = num_to_letters(value.0).ok_or("Column start failed to parse")?; @@ -260,15 +351,19 @@ fn letters_to_num(letters: &str) -> Option { let mut num: u8 = 0; for (i, letter) in letters.chars().rev().enumerate() { let n = letter as u8; - if n < 65 || n > 90 { return None } - num = num.checked_add((26_i32.pow(i as u32)*(n as i32 - 64)).try_into().ok()?)?; + if n < 65 || n > 90 { + return None; + } + num = num.checked_add((26_i32.pow(i as u32) * (n as i32 - 64)).try_into().ok()?)?; } - return Some(num) + return Some(num); } fn num_to_letters(num: u8) -> Option { - if num == 0 { return None } // Otherwise, we will not return none! - // As another note, we can't represent higher than "IV" anyway; - // thus there's no reason for a loop (26^n with n>1 will NOT occur). + if num == 0 { + return None; + } // Otherwise, we will not return none! + // As another note, we can't represent higher than "IV" anyway; + // thus there's no reason for a loop (26^n with n>1 will NOT occur). let mut text = "".to_string(); let mut digit1 = num.div_euclid(26u8); let mut digit2 = num.rem_euclid(26u8); @@ -277,9 +372,9 @@ fn num_to_letters(num: u8) -> Option { digit2 = 26; } if digit1 != 0 { - text.push((64+digit1) as char) + text.push((64 + digit1) as char) } - text.push((64+digit2) as char); + text.push((64 + digit2) as char); return Some(text.to_string()); } @@ -295,8 +390,8 @@ mod tests { fn test_letters_to_num() { assert_eq!(letters_to_num("D"), Some(4)); assert_eq!(letters_to_num("d"), None); - assert_eq!(letters_to_num("AD"), Some(26+4)); - assert_eq!(letters_to_num("CG"), Some(3*26+7)); + assert_eq!(letters_to_num("AD"), Some(26 + 4)); + assert_eq!(letters_to_num("CG"), Some(3 * 26 + 7)); } #[test] @@ -312,8 +407,14 @@ mod tests { #[test] #[wasm_bindgen_test] fn test_l2n_and_n2l() { - assert_eq!(num_to_letters(letters_to_num("A").unwrap()), Some("A".to_string())); - assert_eq!(num_to_letters(letters_to_num("BJ").unwrap()), Some("BJ".to_string())); + assert_eq!( + num_to_letters(letters_to_num("A").unwrap()), + Some("A".to_string()) + ); + assert_eq!( + num_to_letters(letters_to_num("BJ").unwrap()), + Some("BJ".to_string()) + ); for i in 1..=255 { assert_eq!(letters_to_num(&num_to_letters(i as u8).unwrap()), Some(i)); } @@ -327,7 +428,7 @@ mod tests { row_start: 1, row_end: 5, col_start: 1, - col_end: 5 + col_end: 5, }; assert_eq!(desired, "A1:E5".to_string().try_into().unwrap()); } diff --git a/src/components/tree.rs b/src/components/tree.rs index f902fda..b7882c5 100644 --- a/src/components/tree.rs +++ b/src/components/tree.rs @@ -2,11 +2,11 @@ use uuid::Uuid; use wasm_bindgen::JsCast; -use web_sys::{EventTarget, HtmlElement, HtmlDialogElement}; +use web_sys::{EventTarget, HtmlDialogElement, HtmlElement}; use yew::prelude::*; use yewdux::prelude::*; -use crate::components::states::{MainState, CurrentTransfer}; +use crate::components::states::{CurrentTransfer, MainState}; use crate::components::transfer_menu::RegionDisplay; use crate::data::transfer_region::Region; @@ -19,10 +19,10 @@ pub struct TreeProps { pub fn Tree(props: &TreeProps) -> Html { let (main_state, main_dispatch) = use_store::(); let (ct_state, ct_dispatch) = use_store::(); - let plate_modal_id: UseStateHandle> = use_state(|| {None}); + let plate_modal_id: UseStateHandle> = use_state(|| None); let open_plate_info_callback = { - let plate_menu_id =plate_modal_id.clone(); + 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()); @@ -34,7 +34,7 @@ pub fn Tree(props: &TreeProps) -> Html { }) }; let plate_info_close_callback = { - let plate_menu_id =plate_modal_id.clone(); + let plate_menu_id = plate_modal_id.clone(); Callback::from(move |_| { plate_menu_id.set(None); }) @@ -60,10 +60,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) { ct_dispatch.reduce_mut(|state| { - state.transfer.source_region = Region::default(); + state.transfer.transfer_region.source_region = Region::default(); }); main_dispatch.reduce_mut(|state| { state.selected_source_plate = Uuid::from_u128(id); + state.selected_transfer = Uuid::nil(); }); } } @@ -79,48 +80,88 @@ pub fn Tree(props: &TreeProps) -> Html { if let Some(li) = li { if let Ok(id) = u128::from_str_radix(li.id().as_str(), 10) { ct_dispatch.reduce_mut(|state| { - state.transfer.dest_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(); }); } } }) }; - let source_plates = main_state.source_plates.iter() + let transfer_select_callback = { + let main_state = main_state.clone(); + 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) = 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) { + + 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(); + }); + } + } + } + }) + }; + + let source_plates = main_state + .source_plates + .iter() .map(|spi| { - html!{
  • - {String::from(spi)} -
  • } - }).collect::(); - let dest_plates = main_state.destination_plates.iter() + html! {
  • + {String::from(spi)} +
  • } + }) + .collect::(); + let dest_plates = main_state + .destination_plates + .iter() .map(|dpi| { - html!{
  • {String::from(dpi)}
  • } - }).collect::(); - let transfers = main_state.transfers.iter() + html! {
  • {String::from(dpi)}
  • } + }) + .collect::(); + let transfers = main_state + .transfers + .iter() .map(|transfer| { - html!{
  • + html! {
  • {transfer.name.clone()}
  • } }) .collect::(); - html! {
    @@ -170,19 +211,23 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html { let (state, dispatch) = use_store::(); let dialog_ref = use_node_ref(); - let mut plate = state.source_plates.iter() - .find(|spi| {spi.get_uuid() == props.id}); + let mut plate = state + .source_plates + .iter() + .find(|spi| spi.get_uuid() == props.id); if plate == None { - plate = state.destination_plates.iter() - .find(|dpi| {dpi.get_uuid() == props.id}); + plate = state + .destination_plates + .iter() + .find(|dpi| dpi.get_uuid() == props.id); } let plate_name = match plate { Some(plate) => plate.name.clone(), - None => "Not Found".to_string() + None => "Not Found".to_string(), }; let onclose = { let dialog_close_callback = props.dialog_close_callback.clone(); - move |_| {dialog_close_callback.emit(())} + move |_| dialog_close_callback.emit(()) }; let delete_onclick = { @@ -197,10 +242,16 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html { { let dialog_ref = dialog_ref.clone(); - use_effect_with_deps(|dialog_ref| { - dialog_ref.cast::().unwrap().show_modal().ok(); - }, - dialog_ref); + use_effect_with_deps( + |dialog_ref| { + dialog_ref + .cast::() + .unwrap() + .show_modal() + .ok(); + }, + dialog_ref, + ); } html! { diff --git a/src/data/mod.rs b/src/data/mod.rs index c07b5f5..3654d37 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,4 +1,4 @@ pub mod plate; -pub mod transfer_region; -pub mod transfer; pub mod plate_instances; +pub mod transfer; +pub mod transfer_region; diff --git a/src/data/plate.rs b/src/data/plate.rs index 7fa53b0..97634a4 100644 --- a/src/data/plate.rs +++ b/src/data/plate.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; #[derive(PartialEq, Eq, Default, Clone, Copy, Serialize, Deserialize, Debug)] pub struct Plate { @@ -76,12 +76,12 @@ impl PlateFormat { } */ match self { - PlateFormat::W6 => (2, 3), - PlateFormat::W12 => (3, 4), - PlateFormat::W24 => (4, 6), - PlateFormat::W48 => (6, 8), - PlateFormat::W96 => (8, 12), - PlateFormat::W384 => (16, 24), + PlateFormat::W6 => (2, 3), + PlateFormat::W12 => (3, 4), + PlateFormat::W24 => (4, 6), + PlateFormat::W48 => (6, 8), + PlateFormat::W96 => (8, 12), + PlateFormat::W384 => (16, 24), PlateFormat::W1536 => (32, 48), PlateFormat::W3456 => (48, 72), } diff --git a/src/data/plate_instances.rs b/src/data/plate_instances.rs index eb17ab4..23599b8 100644 --- a/src/data/plate_instances.rs +++ b/src/data/plate_instances.rs @@ -1,6 +1,6 @@ -use uuid::Uuid; -use serde::{Serialize, Deserialize}; use super::plate::*; +use serde::{Deserialize, Serialize}; +use uuid::Uuid; #[derive(PartialEq, Clone, Serialize, Deserialize)] pub struct PlateInstance { @@ -12,7 +12,10 @@ pub struct PlateInstance { impl PlateInstance { pub fn new(sort: PlateType, format: PlateFormat, name: String) -> Self { PlateInstance { - plate: Plate { plate_type: sort, plate_format: format }, + plate: Plate { + plate_type: sort, + plate_format: format, + }, id: Uuid::new_v4(), name, } @@ -29,6 +32,10 @@ impl PlateInstance { impl From for PlateInstance { fn from(value: Plate) -> Self { - PlateInstance { plate: value, id: Uuid::new_v4(), name: "New Plate".to_string() } + PlateInstance { + plate: value, + id: Uuid::new_v4(), + name: "New Plate".to_string(), + } } } diff --git a/src/data/transfer.rs b/src/data/transfer.rs index 3e0832f..f65b36d 100644 --- a/src/data/transfer.rs +++ b/src/data/transfer.rs @@ -1,10 +1,10 @@ +use super::plate_instances::*; +use super::transfer_region::*; use serde::Deserialize; use serde::Serialize; use uuid::Uuid; -use super::transfer_region::*; -use super::plate_instances::*; -#[derive(PartialEq, Clone, Serialize, Deserialize)] +#[derive(PartialEq, Clone, Default, Debug, Serialize, Deserialize)] pub struct Transfer { pub source_id: Uuid, pub dest_id: Uuid, @@ -14,7 +14,12 @@ pub struct Transfer { } impl Transfer { - pub fn new(source: PlateInstance, dest: PlateInstance, tr: TransferRegion, name: String) -> Self { + pub fn new( + source: PlateInstance, + dest: PlateInstance, + tr: TransferRegion, + name: String, + ) -> Self { Self { source_id: source.get_uuid(), dest_id: dest.get_uuid(), diff --git a/src/data/transfer_region.rs b/src/data/transfer_region.rs index 5a9d261..865d2dd 100644 --- a/src/data/transfer_region.rs +++ b/src/data/transfer_region.rs @@ -1,4 +1,4 @@ -use serde::{Serialize, Deserialize}; +use serde::{Deserialize, Serialize}; use super::plate::Plate; @@ -9,7 +9,7 @@ pub enum Region { } impl Default for Region { fn default() -> Self { - Region::Point((1,1)) + Region::Point((1, 1)) } } impl TryFrom for ((u8, u8), (u8, u8)) { @@ -41,8 +41,8 @@ impl Default for TransferRegion { source_region: Region::default(), dest_plate: Plate::default(), dest_region: Region::default(), - interleave_source: (1,1), - interleave_dest: (1,1) + interleave_source: (1, 1), + interleave_dest: (1, 1), } } } @@ -51,26 +51,27 @@ impl TransferRegion { pub fn get_source_wells(&self) -> Vec<(u8, u8)> { match self.source_region { Region::Rect(c1, c2) => { - let mut wells = Vec::<(u8, u8)>::new(); - let (ul, br) = standardize_rectangle(&c1, &c2); - let (interleave_i, interleave_j) = self.interleave_source; - // NOTE: This will panic if either is 0! - // We'll reassign these values (still not mutable) just in case. - // This behaviour shouldn't be replicated for destination wells - // because a zero step permits pooling. - let (interleave_i, interleave_j) = (i8::max(interleave_i, 1), i8::max(interleave_j, 1)); + let mut wells = Vec::<(u8, u8)>::new(); + let (ul, br) = standardize_rectangle(&c1, &c2); + let (interleave_i, interleave_j) = self.interleave_source; + // NOTE: This will panic if either is 0! + // We'll reassign these values (still not mutable) just in case. + // This behaviour shouldn't be replicated for destination wells + // because a zero step permits pooling. + let (interleave_i, interleave_j) = + (i8::max(interleave_i, 1), i8::max(interleave_j, 1)); - for i in (ul.0..=br.0).step_by(i8::abs(interleave_i) as usize) { - for j in (ul.1..=br.1).step_by(i8::abs(interleave_j) as usize) { - // NOTE: It looks like we're ignoring negative interleaves, - // because it wouldn't make a difference here---the same - // wells will still be involved in the transfer. - wells.push((i, j)) + for i in (ul.0..=br.0).step_by(i8::abs(interleave_i) as usize) { + for j in (ul.1..=br.1).step_by(i8::abs(interleave_j) as usize) { + // NOTE: It looks like we're ignoring negative interleaves, + // because it wouldn't make a difference here---the same + // wells will still be involved in the transfer. + wells.push((i, j)) + } } + return wells; } - return wells; - }, - Region::Point(p) => return vec![p] + Region::Point(p) => return vec![p], } } @@ -108,8 +109,8 @@ impl TransferRegion { let il_source = self.interleave_source; let source_corners: ((u8, u8), (u8, u8)) = match self.source_region { - Region::Point((x,y)) => ((x,y),(x,y)), - Region::Rect(c1,c2) => (c1,c2) + Region::Point((x, y)) => ((x, y), (x, y)), + Region::Rect(c1, c2) => (c1, c2), }; let (source_ul, _) = standardize_rectangle(&source_corners.0, &source_corners.1); // This map is not necessarily injective or surjective, @@ -147,7 +148,8 @@ impl TransferRegion { if source_wells.contains(&(i, j)) { let possible_destination_wells = create_dense_rectangle(&c1, &c2); let (d_ul, d_br) = standardize_rectangle(&c1, &c2); - let (s_ul, s_br) = standardize_rectangle(&source_corners.0, &source_corners.1); + let (s_ul, s_br) = + standardize_rectangle(&source_corners.0, &source_corners.1); let s_dims = ( s_br.0.checked_sub(s_ul.0).unwrap() + 1, s_br.1.checked_sub(s_ul.1).unwrap() + 1, @@ -156,24 +158,39 @@ impl TransferRegion { d_br.0.checked_sub(d_ul.0).unwrap() + 1, d_br.1.checked_sub(d_ul.1).unwrap() + 1, ); - let N_s = ( // Number of used source wells - (s_dims.0 + il_source.0.abs() as u8 - 1).div_euclid(il_source.0.abs() as u8), - (s_dims.1 + il_source.1.abs() as u8 - 1).div_euclid(il_source.1.abs() as u8), + let N_s = ( + // Number of used source wells + (s_dims.0 + il_source.0.abs() as u8 - 1) + .div_euclid(il_source.0.abs() as u8), + (s_dims.1 + il_source.1.abs() as u8 - 1) + .div_euclid(il_source.1.abs() as u8), ); - let D_per_replicate = ( // How many wells are used per replicate? + let D_per_replicate = ( + // How many wells are used per replicate? (N_s.0 * (il_dest.0.abs() as u8)), - (N_s.1 * (il_dest.1.abs() as u8)) + (N_s.1 * (il_dest.1.abs() as u8)), ); - let count = ( // How many times can we replicate? - (1..).position( - |n| n*N_s.0*il_dest.0.abs() as u8 - il_dest.0.abs() as u8 + 1 - > d_dims.0).unwrap() as u8, - (1..).position( - |n| n*N_s.1*il_dest.1.abs() as u8 - il_dest.1.abs() as u8 + 1 - > d_dims.1).unwrap() as u8, + let count = ( + // How many times can we replicate? + (1..) + .position(|n| { + n * N_s.0 * il_dest.0.abs() as u8 - il_dest.0.abs() as u8 + 1 + > d_dims.0 + }) + .unwrap() as u8, + (1..) + .position(|n| { + n * N_s.1 * il_dest.1.abs() as u8 - il_dest.1.abs() as u8 + 1 + > d_dims.1 + }) + .unwrap() as u8, ); - let i = i.saturating_sub(s_ul.0).saturating_div(il_source.0.abs() as u8); - let j = j.saturating_sub(s_ul.1).saturating_div(il_source.1.abs() as u8); + let i = i + .saturating_sub(s_ul.0) + .saturating_div(il_source.0.abs() as u8); + let j = j + .saturating_sub(s_ul.1) + .saturating_div(il_source.1.abs() as u8); // log::debug!("N_s: {:?}, d_dims: {:?}, D_per: {:?}, count: {:?}", N_s, d_dims, D_per_replicate, count); Some( @@ -191,13 +208,17 @@ impl TransferRegion { == ((il_dest.1.abs() as u8 *j)) % (N_s.1 * il_dest.1.abs() as u8) }) - .filter(|(x,y)| { + .filter(|(x, y)| { // How many times have we replicated? < How many are we allowed // to replicate? - x.checked_sub(d_ul.0).unwrap().div_euclid(N_s.0 * il_dest.0.abs() as u8) - < count.0 && - y.checked_sub(d_ul.1).unwrap().div_euclid(N_s.1 * il_dest.1.abs() as u8) - < count.1 + x.checked_sub(d_ul.0) + .unwrap() + .div_euclid(N_s.0 * il_dest.0.abs() as u8) + < count.0 + && y.checked_sub(d_ul.1) + .unwrap() + .div_euclid(N_s.1 * il_dest.1.abs() as u8) + < count.1 }) .collect(), ) @@ -223,7 +244,7 @@ impl TransferRegion { match self.source_region { Region::Point(_) => return Ok(()), // Should make sure it's actually in the plate, leave for - // later + // later Region::Rect(s1, s2) => { // Check if all source wells exist: if s1.0 == 0 || s1.1 == 0 || s2.0 == 0 || s2.1 == 0 { @@ -383,42 +404,86 @@ mod tests { source_plate: source, source_region: Region::Rect((1, 1), (3, 3)), dest_plate: destination, - dest_region: Region::Point((3,3)), - interleave_source: (1,1), - interleave_dest: (1,1), + dest_region: Region::Point((3, 3)), + interleave_source: (1, 1), + interleave_dest: (1, 1), }; let transfer1_map = transfer1.calculate_map(); - assert_eq!(transfer1_map((1,1)), Some(vec!{(3,3)}), "Failed basic shift transfer 1"); - assert_eq!(transfer1_map((1,2)), Some(vec!{(3,4)}), "Failed basic shift transfer 2"); - assert_eq!(transfer1_map((2,2)), Some(vec!{(4,4)}), "Failed basic shift transfer 3"); + assert_eq!( + transfer1_map((1, 1)), + Some(vec! {(3,3)}), + "Failed basic shift transfer 1" + ); + assert_eq!( + transfer1_map((1, 2)), + Some(vec! {(3,4)}), + "Failed basic shift transfer 2" + ); + assert_eq!( + transfer1_map((2, 2)), + Some(vec! {(4,4)}), + "Failed basic shift transfer 3" + ); let transfer2 = TransferRegion { source_plate: source, source_region: Region::Rect((1, 1), (3, 3)), dest_plate: destination, - dest_region: Region::Point((3,3)), - interleave_source: (2,2), - interleave_dest: (1,1), - }; + dest_region: Region::Point((3, 3)), + interleave_source: (2, 2), + interleave_dest: (1, 1), + }; let transfer2_map = transfer2.calculate_map(); - assert_eq!(transfer2_map((1,1)), Some(vec!{(3,3)}), "Failed source interleave, type simple 1"); - assert_eq!(transfer2_map((1,2)), None, "Failed source interleave, type simple 2"); - assert_eq!(transfer2_map((2,2)), None, "Failed source interleave, type simple 3"); - assert_eq!(transfer2_map((3,3)), Some(vec!{(4,4)}), "Failed source interleave, type simple 4"); + assert_eq!( + transfer2_map((1, 1)), + Some(vec! {(3,3)}), + "Failed source interleave, type simple 1" + ); + assert_eq!( + transfer2_map((1, 2)), + None, + "Failed source interleave, type simple 2" + ); + assert_eq!( + transfer2_map((2, 2)), + None, + "Failed source interleave, type simple 3" + ); + assert_eq!( + transfer2_map((3, 3)), + Some(vec! {(4,4)}), + "Failed source interleave, type simple 4" + ); let transfer3 = TransferRegion { source_plate: source, source_region: Region::Rect((1, 1), (3, 3)), dest_plate: destination, - dest_region: Region::Point((3,3)), - interleave_source: (1,1), - interleave_dest: (2,3), - }; + dest_region: Region::Point((3, 3)), + interleave_source: (1, 1), + interleave_dest: (2, 3), + }; let transfer3_map = transfer3.calculate_map(); - assert_eq!(transfer3_map((1,1)), Some(vec!{(3,3)}), "Failed destination interleave, type simple 1"); - assert_eq!(transfer3_map((2,1)), Some(vec!{(5,3)}), "Failed destination interleave, type simple 2"); - assert_eq!(transfer3_map((1,2)), Some(vec!{(3,6)}), "Failed destination interleave, type simple 3"); - assert_eq!(transfer3_map((2,2)), Some(vec!{(5,6)}), "Failed destination interleave, type simple 4"); + assert_eq!( + transfer3_map((1, 1)), + Some(vec! {(3,3)}), + "Failed destination interleave, type simple 1" + ); + assert_eq!( + transfer3_map((2, 1)), + Some(vec! {(5,3)}), + "Failed destination interleave, type simple 2" + ); + assert_eq!( + transfer3_map((1, 2)), + Some(vec! {(3,6)}), + "Failed destination interleave, type simple 3" + ); + assert_eq!( + transfer3_map((2, 2)), + Some(vec! {(5,6)}), + "Failed destination interleave, type simple 4" + ); } #[test] @@ -431,17 +496,24 @@ mod tests { source_plate: source, source_region: Region::Rect((1, 1), (2, 2)), dest_plate: destination, - dest_region: Region::Rect((2,2),(11,11)), - interleave_source: (1,1), - interleave_dest: (3,3), + dest_region: Region::Rect((2, 2), (11, 11)), + interleave_source: (1, 1), + interleave_dest: (3, 3), }; let transfer1_map = transfer1.calculate_map(); - assert_eq!(transfer1_map((1,1)), Some(vec!{(2, 2), (2, 8), (8, 2), (8, 8)}), "Failed type replicate 1"); - assert_eq!(transfer1_map((2,1)), Some(vec!{(5, 2), (5, 8), (11, 2), (11, 8)}), "Failed type replicate 1"); + assert_eq!( + transfer1_map((1, 1)), + Some(vec! {(2, 2), (2, 8), (8, 2), (8, 8)}), + "Failed type replicate 1" + ); + assert_eq!( + transfer1_map((2, 1)), + Some(vec! {(5, 2), (5, 8), (11, 2), (11, 8)}), + "Failed type replicate 1" + ); } #[test] #[wasm_bindgen_test] - fn test_pooling_transfer() { - } + fn test_pooling_transfer() {} } diff --git a/src/lib.rs b/src/lib.rs index 6b2d101..d5b84f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,8 +2,8 @@ mod components; mod data; -use yew::prelude::*; use components::main_window::MainWindow; +use yew::prelude::*; #[cfg(debug_assertions)] use data::*; @@ -24,14 +24,14 @@ pub fn plate_test() { source_plate: source, source_region: transfer_region::Region::Rect((1, 1), (2, 2)), dest_plate: destination, - dest_region: transfer_region::Region::Rect((2,2),(11,11)), - interleave_source: (1,1), - interleave_dest: (3,3), + dest_region: transfer_region::Region::Rect((2, 2), (11, 11)), + interleave_source: (1, 1), + interleave_dest: (3, 3), }; println!("{}", transfer); let sws = transfer.get_source_wells(); let m = transfer.calculate_map(); for w in sws { - println!("{:?} -> {:?}", w,m(w)); + println!("{:?} -> {:?}", w, m(w)); } }