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