Implementation 1
This commit is contained in:
parent
a9e5f05fd9
commit
6b09aad289
|
@ -11,7 +11,7 @@ yewdux = "0.9"
|
|||
wasm-bindgen = "0.2"
|
||||
web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement",
|
||||
"HtmlDialogElement", "Blob", "Url", "Window",
|
||||
"HtmlAnchorElement", "ReadableStream",
|
||||
"HtmlAnchorElement", "ReadableStream", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement",
|
||||
"FileReader"] }
|
||||
js-sys = "0.3"
|
||||
log = "0.4"
|
||||
|
|
|
@ -1,18 +1,27 @@
|
|||
#![allow(non_snake_case)]
|
||||
use std::collections::HashSet;
|
||||
|
||||
use js_sys::Array;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||
use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url};
|
||||
use web_sys::{
|
||||
Blob, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement,
|
||||
HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url,
|
||||
};
|
||||
use yew::prelude::*;
|
||||
use yewdux::prelude::*;
|
||||
|
||||
use super::new_plate_dialog::NewPlateDialog;
|
||||
use super::plates::plate_container::PlateContainer;
|
||||
use super::states::{CurrentTransfer, MainState};
|
||||
use super::transfer_menu::TransferMenu;
|
||||
use super::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu};
|
||||
use super::tree::Tree;
|
||||
|
||||
use crate::data::csv::state_to_csv;
|
||||
use crate::data::plate_instances::PlateInstance;
|
||||
use crate::data::transfer::Transfer;
|
||||
use crate::data::transfer_region::{Region, TransferRegion};
|
||||
|
||||
#[function_component]
|
||||
pub fn MainWindow() -> Html {
|
||||
|
@ -100,6 +109,7 @@ pub fn MainWindow() -> Html {
|
|||
};
|
||||
|
||||
let import_json_button_callback = {
|
||||
let main_dispatch = main_dispatch.clone();
|
||||
Callback::from(move |_| {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
|
@ -177,6 +187,273 @@ pub fn MainWindow() -> Html {
|
|||
})
|
||||
};
|
||||
|
||||
let import_transfer_csv_callback = {
|
||||
Callback::from(move |_| {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let body = document.body().unwrap();
|
||||
let modal = document
|
||||
.create_element("dialog")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlDialogElement>()
|
||||
.unwrap();
|
||||
modal.set_text_content(Some("Import File:"));
|
||||
let onclose_callback = {
|
||||
let modal = modal.clone();
|
||||
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||
modal.remove();
|
||||
})
|
||||
};
|
||||
modal.set_onclose(Some(onclose_callback.as_ref().unchecked_ref()));
|
||||
onclose_callback.forget();
|
||||
|
||||
let form = document
|
||||
.create_element("form")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlFormElement>()
|
||||
.unwrap();
|
||||
let input = document
|
||||
.create_element("input")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlInputElement>()
|
||||
.unwrap();
|
||||
input.set_type("file");
|
||||
input.set_accept(".csv");
|
||||
form.append_child(&input).unwrap();
|
||||
|
||||
let input_callback = {
|
||||
let main_dispatch = main_dispatch.clone();
|
||||
let modal = modal.clone();
|
||||
Closure::<dyn FnMut(_)>::new(move |e: Event| {
|
||||
if let Some(input) = e.current_target() {
|
||||
let input = input
|
||||
.dyn_into::<HtmlInputElement>()
|
||||
.expect("We know this is an input.");
|
||||
if let Some(files) = input.files() {
|
||||
if let Some(file) = files.get(0) {
|
||||
let fr = web_sys::FileReader::new().unwrap();
|
||||
fr.read_as_text(&file).unwrap();
|
||||
let fr1 = fr.clone(); // Clone to avoid outliving closure
|
||||
let main_dispatch = main_dispatch.clone(); // Clone to satisfy FnMut
|
||||
// trait
|
||||
let modal = modal.clone();
|
||||
let onload = Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||
if let Some(value) =
|
||||
&fr1.result().ok().and_then(|v| v.as_string())
|
||||
{
|
||||
let mut rdr = csv::Reader::from_reader(value.as_bytes());
|
||||
let mut records = Vec::new();
|
||||
for record in
|
||||
rdr.deserialize::<crate::data::csv::TransferRecord>()
|
||||
{
|
||||
match record {
|
||||
Ok(r) => {
|
||||
//log::debug!("{:?}", r);
|
||||
records.push(r);
|
||||
}
|
||||
Err(e) => {
|
||||
log::debug!("{:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut sources: HashSet<String> = HashSet::new();
|
||||
let mut destinations: HashSet<String> = HashSet::new();
|
||||
for record in records.iter() {
|
||||
sources.insert(record.source_plate.clone());
|
||||
destinations.insert(record.destination_plate.clone());
|
||||
}
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
let form = document
|
||||
.create_element("form")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlFormElement>()
|
||||
.unwrap();
|
||||
let from_source = document
|
||||
.create_element("select")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlSelectElement>()
|
||||
.unwrap();
|
||||
for source in sources {
|
||||
let option = document
|
||||
.create_element("option")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlOptionElement>()
|
||||
.unwrap();
|
||||
option.set_value(&source);
|
||||
option.set_text(&source);
|
||||
from_source.append_child(&option).unwrap();
|
||||
}
|
||||
let to_source = document
|
||||
.create_element("select")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlSelectElement>()
|
||||
.unwrap();
|
||||
for source in &main_dispatch.get().source_plates {
|
||||
let option = document
|
||||
.create_element("option")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlOptionElement>()
|
||||
.unwrap();
|
||||
option.set_value(&source.name);
|
||||
option.set_text(&source.name);
|
||||
to_source.append_child(&option).unwrap();
|
||||
}
|
||||
let from_dest = document
|
||||
.create_element("select")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlSelectElement>()
|
||||
.unwrap();
|
||||
for dest in destinations {
|
||||
let option = document
|
||||
.create_element("option")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlOptionElement>()
|
||||
.unwrap();
|
||||
option.set_value(&dest);
|
||||
option.set_text(&dest);
|
||||
from_dest.append_child(&option).unwrap();
|
||||
}
|
||||
let to_dest = document
|
||||
.create_element("select")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlSelectElement>()
|
||||
.unwrap();
|
||||
for dest in &main_dispatch.get().destination_plates {
|
||||
let option = document
|
||||
.create_element("option")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlOptionElement>()
|
||||
.unwrap();
|
||||
option.set_value(&dest.name);
|
||||
option.set_text(&dest.name);
|
||||
to_dest.append_child(&option).unwrap();
|
||||
}
|
||||
let submit = document
|
||||
.create_element("button")
|
||||
.unwrap()
|
||||
.dyn_into::<HtmlButtonElement>()
|
||||
.unwrap();
|
||||
submit.set_value("Submit");
|
||||
let submit_callback = {
|
||||
let main_dispatch = main_dispatch.clone();
|
||||
let from_source = from_source.clone();
|
||||
let to_source = to_source.clone();
|
||||
let from_dest = from_dest.clone();
|
||||
let to_dest = to_dest.clone();
|
||||
Closure::<dyn FnMut(_)>::new(move |_: Event| {
|
||||
let from_source = from_source.value();
|
||||
let to_source = to_source.value();
|
||||
let from_dest = from_dest.value();
|
||||
let to_dest = to_dest.value();
|
||||
|
||||
lazy_static! {
|
||||
static ref REGEX: Regex =
|
||||
Regex::new(r"([A-Z]+)(\d+)").unwrap();
|
||||
}
|
||||
let records: Vec<((u8, u8), (u8, u8))> = records
|
||||
.iter()
|
||||
.filter(|record| {
|
||||
record.source_plate == from_source
|
||||
})
|
||||
.filter(|record| {
|
||||
record.destination_plate == from_dest
|
||||
})
|
||||
.map(|record| {
|
||||
let c1 = REGEX
|
||||
.captures(&record.source_well)
|
||||
.unwrap();
|
||||
let c2 = REGEX
|
||||
.captures(&record.destination_well)
|
||||
.unwrap();
|
||||
log::debug!("{} {}", &record.source_well, &record.destination_well);
|
||||
log::debug!("{},{} {},{}", &c1[1], &c1[2], &c2[1], &c2[2]);
|
||||
|
||||
(
|
||||
(
|
||||
letters_to_num(&c1[1]).unwrap(),
|
||||
c1[2].parse::<u8>().unwrap(),
|
||||
),
|
||||
(
|
||||
letters_to_num(&c2[1]).unwrap(),
|
||||
c2[2].parse::<u8>().unwrap(),
|
||||
),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let spi = main_dispatch
|
||||
.get()
|
||||
.source_plates
|
||||
.iter()
|
||||
.find(|src| src.name == to_source)
|
||||
.unwrap()
|
||||
.clone();
|
||||
let dpi = main_dispatch
|
||||
.get()
|
||||
.destination_plates
|
||||
.iter()
|
||||
.find(|dest| dest.name == to_dest)
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
let custom_region = Region::new_custom(&records);
|
||||
let transfer_region = TransferRegion {
|
||||
source_region: custom_region.clone(),
|
||||
dest_region: custom_region,
|
||||
interleave_source: (1, 1),
|
||||
interleave_dest: (1, 1),
|
||||
source_plate: spi.plate,
|
||||
dest_plate: dpi.plate,
|
||||
};
|
||||
|
||||
let transfer = Transfer::new(
|
||||
spi,
|
||||
dpi,
|
||||
transfer_region,
|
||||
"Custom Transfer".to_string(),
|
||||
);
|
||||
main_dispatch.reduce_mut(|state| {
|
||||
state.transfers.push(transfer);
|
||||
state.selected_transfer = state
|
||||
.transfers
|
||||
.last()
|
||||
.expect("An element should have just been added")
|
||||
.get_uuid();
|
||||
});
|
||||
})
|
||||
};
|
||||
submit.set_onclick(Some(
|
||||
submit_callback.as_ref().unchecked_ref(),
|
||||
));
|
||||
submit_callback.forget();
|
||||
|
||||
form.append_child(&from_source).unwrap();
|
||||
form.append_child(&to_source).unwrap();
|
||||
form.append_child(&from_dest).unwrap();
|
||||
form.append_child(&to_dest).unwrap();
|
||||
modal.append_child(&submit).unwrap();
|
||||
modal.append_child(&form).unwrap();
|
||||
}
|
||||
});
|
||||
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
|
||||
onload.forget(); // Magic (don't touch)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
input.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
|
||||
input_callback.forget(); // Magic straight from the docs, don't touch :(
|
||||
|
||||
modal.append_child(&form).unwrap();
|
||||
body.append_child(&modal).unwrap();
|
||||
modal.show_modal().unwrap();
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<>
|
||||
<div class="upper_menu">
|
||||
|
@ -190,7 +467,13 @@ pub fn MainWindow() -> Html {
|
|||
<button onclick={export_json_button_callback}>{"Export as JSON"}</button>
|
||||
</div>
|
||||
</div>
|
||||
<button onclick={import_json_button_callback}>{"Import"}</button>
|
||||
<div class="dropdown-sub">
|
||||
<button>{"Import"}</button>
|
||||
<div>
|
||||
<button onclick={import_json_button_callback}>{"Import from JSON"}</button>
|
||||
<button onclick={import_transfer_csv_callback}>{"Import Transfer from CSV"}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main_container">
|
||||
|
|
|
@ -350,6 +350,37 @@ impl TryFrom<String> for RegionDisplay {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl TryFrom<&str> for RegionDisplay {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(value: &str) -> Result<Self, Self::Error> {
|
||||
lazy_static! {
|
||||
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");
|
||||
}
|
||||
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::<u8>()
|
||||
.or(Err("Row start failed to parse"))?;
|
||||
let row_end: u8 = captures[4]
|
||||
.parse::<u8>()
|
||||
.or(Err("Row end failed to parse"))?;
|
||||
Ok(RegionDisplay {
|
||||
text: value.to_string(),
|
||||
col_start,
|
||||
row_start,
|
||||
col_end,
|
||||
row_end,
|
||||
})
|
||||
} else {
|
||||
Err("Regex match failed")
|
||||
}
|
||||
}
|
||||
}
|
||||
impl From<&Region> for RegionDisplay {
|
||||
fn from(value: &Region) -> Self {
|
||||
match *value {
|
||||
|
@ -392,9 +423,10 @@ impl TryFrom<(u8, u8, u8, u8)> for RegionDisplay {
|
|||
})
|
||||
}
|
||||
}
|
||||
fn letters_to_num(letters: &str) -> Option<u8> {
|
||||
pub fn letters_to_num(letters: &str) -> Option<u8> {
|
||||
let mut num: u8 = 0;
|
||||
for (i, letter) in letters.chars().rev().enumerate() {
|
||||
for (i, letter) in letters.to_ascii_uppercase().chars().rev().enumerate() {
|
||||
log::debug!("{}, {}", i, letter);
|
||||
let n = letter as u8;
|
||||
if !(65..=90).contains(&n) {
|
||||
return None;
|
||||
|
|
|
@ -2,23 +2,23 @@ use crate::components::states::MainState;
|
|||
use crate::components::transfer_menu::num_to_letters;
|
||||
use crate::data::transfer::Transfer;
|
||||
|
||||
use serde::Serialize;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::error::Error;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
struct TransferRecord {
|
||||
#[serde(rename = "Source Plate Barcode")]
|
||||
source_plate: String,
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct TransferRecord {
|
||||
#[serde(rename = "Source Plate")]
|
||||
pub source_plate: String,
|
||||
#[serde(rename = "Source Well")]
|
||||
source_well: String,
|
||||
#[serde(rename = "Destination Plate Barcode")]
|
||||
destination_plate: String,
|
||||
pub source_well: String,
|
||||
#[serde(rename = "Dest Plate")]
|
||||
pub destination_plate: String,
|
||||
#[serde(rename = "Destination Well")]
|
||||
destination_well: String,
|
||||
#[serde(rename = "Volume")]
|
||||
volume: f32,
|
||||
pub destination_well: String,
|
||||
#[serde(rename = "Transfer Volume")]
|
||||
pub volume: f32,
|
||||
#[serde(rename = "Concentration")]
|
||||
concentration: Option<f32>,
|
||||
pub concentration: Option<f32>,
|
||||
}
|
||||
|
||||
pub fn state_to_csv(state: &MainState) -> Result<String, Box<dyn Error>> {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::components::transfer_menu::RegionDisplay;
|
||||
|
||||
use super::plate::Plate;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||
|
@ -30,6 +32,22 @@ impl TryFrom<Region> for ((u8, u8), (u8, u8)) {
|
|||
}
|
||||
}
|
||||
}
|
||||
impl Region {
|
||||
pub fn new_custom(transfers: &Vec<((u8, u8), (u8, u8))>) -> Self {
|
||||
let mut src_pts: Vec<(u8, u8)> = Vec::with_capacity(transfers.len());
|
||||
let mut dest_pts: Vec<(u8, u8)> = Vec::with_capacity(transfers.len());
|
||||
|
||||
for transfer in transfers {
|
||||
src_pts.push(transfer.0);
|
||||
dest_pts.push(transfer.1);
|
||||
}
|
||||
|
||||
Region::Custom(CustomRegion {
|
||||
src: src_pts,
|
||||
dest: dest_pts,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Serialize, Deserialize, Debug)]
|
||||
pub struct TransferRegion {
|
||||
|
@ -281,7 +299,7 @@ impl TransferRegion {
|
|||
// log::debug!("s1.1: {}, max.1: {}", s1.1, source_max.1);
|
||||
return Err("Source region is out-of-bounds! (Too wide)");
|
||||
}
|
||||
},
|
||||
}
|
||||
Region::Custom(_) => return Ok(()),
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue