Compare commits
6 Commits
b79c377793
...
9366d29202
Author | SHA1 | Date |
---|---|---|
Emilia Allison | 9366d29202 | |
Emilia Allison | c2a64d679a | |
Emilia Allison | 1aa4c1b7bb | |
Emilia Allison | 6c8533f7a6 | |
Emilia Allison | d7c98d37a2 | |
Emilia Allison | bfa1fef9d8 |
|
@ -637,6 +637,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"getrandom",
|
"getrandom",
|
||||||
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
"rand",
|
||||||
"regex",
|
"regex",
|
||||||
|
|
23
README.md
23
README.md
|
@ -78,13 +78,6 @@ To add a new plate, click the "New Plate" button:
|
||||||
Keep in mind that this will overwrite any work you currently have open,
|
Keep in mind that this will overwrite any work you currently have open,
|
||||||
so you may wish to export first (see above).
|
so you may wish to export first (see above).
|
||||||
|
|
||||||
#### Import Transfer from CSV (Using a picklist as a transfer)
|
|
||||||
If you have a CSV generated by another tool (or plate-tool),
|
|
||||||
you can import it as a single transfer.
|
|
||||||
To do so, mouse over the "File" tab, then "Import", and finally "Import Transfer from CSV".
|
|
||||||
When creating transfers via this method, the transfer cannot be edited.
|
|
||||||
This is useful if you have a pre-existing picklist that you would like to visualize in plate-tool.
|
|
||||||
|
|
||||||
_Note 1_: JSON files are plaintext!
|
_Note 1_: JSON files are plaintext!
|
||||||
By default there is little whitespace (this makes comprehending them a challenge)
|
By default there is little whitespace (this makes comprehending them a challenge)
|
||||||
but if we pass it through a "JSON Beautifier" (enter this into your search engine of choice)
|
but if we pass it through a "JSON Beautifier" (enter this into your search engine of choice)
|
||||||
|
@ -99,6 +92,18 @@ To add a new plate, click the "New Plate" button:
|
||||||
that this application does not "phone home".
|
that this application does not "phone home".
|
||||||
Your data is stored locally (unless you choose to export it and distribute it yourself).
|
Your data is stored locally (unless you choose to export it and distribute it yourself).
|
||||||
|
|
||||||
|
#### Import Transfer from CSV (Using a picklist as a transfer)
|
||||||
|
If you have a CSV generated by another tool (or plate-tool),
|
||||||
|
you can import it as a single transfer.
|
||||||
|
To do so, mouse over the "File" tab, then "Import", and finally "Import Transfer from CSV".
|
||||||
|
When creating transfers via this method, the transfer cannot be edited.
|
||||||
|
This is useful if you have a pre-existing picklist that you would like to visualize in plate-tool.
|
||||||
|
|
||||||
|
_Note_: If you try to use this feature and no plates are available to select,
|
||||||
|
there was likely an issue parsing your picklist.
|
||||||
|
Your browser's console may have guidance as to why parsing failed;
|
||||||
|
plate-tool was probably expecting a different name for a column than was in your file.
|
||||||
|
|
||||||
### Other Neat Features
|
### Other Neat Features
|
||||||
|
|
||||||
#### Taking Pictures of Plates
|
#### Taking Pictures of Plates
|
||||||
|
@ -130,7 +135,7 @@ To add a new plate, click the "New Plate" button:
|
||||||
Plate tool is hosted [here](https://ilia.moe/cool-stuff/plate-tool/) for your convenience.
|
Plate tool is hosted [here](https://ilia.moe/cool-stuff/plate-tool/) for your convenience.
|
||||||
However, you're absolutely welcome to host your own instance (even locally).
|
However, you're absolutely welcome to host your own instance (even locally).
|
||||||
Here's how:
|
Here's how:
|
||||||
(_Note:_ If you run Windows you're probably best off doing the following in WSL2)
|
(_Note:_ ~~If you run Windows you're probably best off doing the following in WSL2~~ You're absolutely fine to install rustup in Powershell, and the subsequent steps should be very similar but likely with different filepaths.)
|
||||||
|
|
||||||
1. Make sure you have a working Rust toolchain
|
1. Make sure you have a working Rust toolchain
|
||||||
1. Installing `rustup` is the easiest way to do this. See [their website](https://rustup.rs/),
|
1. Installing `rustup` is the easiest way to do this. See [their website](https://rustup.rs/),
|
||||||
|
@ -140,7 +145,7 @@ Here's how:
|
||||||
2. Install [trunk](https://trunkrs.dev/)
|
2. Install [trunk](https://trunkrs.dev/)
|
||||||
- Run `cargo install --locked trunk`
|
- Run `cargo install --locked trunk`
|
||||||
3. Clone this repository using git
|
3. Clone this repository using git
|
||||||
4. Enter the project directory and run `trunk serve`
|
4. Enter the plate-tool-web directory and run `trunk serve`
|
||||||
- You may need to check where `cargo` is installing binaries by default. For me, they're at `~/.cargo/bin`.
|
- You may need to check where `cargo` is installing binaries by default. For me, they're at `~/.cargo/bin`.
|
||||||
If trunk is not automatically placed in your path, you would then run `/your/path/to/.cargo/bin/trunk serve`.
|
If trunk is not automatically placed in your path, you would then run `/your/path/to/.cargo/bin/trunk serve`.
|
||||||
- You can instead run `trunk build --release` for a more performant binary.
|
- You can instead run `trunk build --release` for a more performant binary.
|
||||||
|
|
|
@ -14,3 +14,4 @@ serde_json = "1.0"
|
||||||
csv = "1.2"
|
csv = "1.2"
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
getrandom = { version = "0.2", features = ["js"] }
|
||||||
rand = { version = "0.8", features = ["small_rng"] }
|
rand = { version = "0.8", features = ["small_rng"] }
|
||||||
|
lazy_static = "1.4"
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
use super::{string_well_to_pt, TransferRecord, read_csv};
|
||||||
|
use crate::plate::{PlateFormat, PlateType};
|
||||||
|
use crate::plate_instances::PlateInstance;
|
||||||
|
use crate::transfer::Transfer;
|
||||||
|
use crate::transfer_region::{CustomRegion, Region, TransferRegion};
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
pub struct AutoOutput {
|
||||||
|
pub sources: Vec<PlateInstance>,
|
||||||
|
pub destinations: Vec<PlateInstance>,
|
||||||
|
pub transfers: Vec<Transfer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UniquePlates {
|
||||||
|
sources: Vec<PlateInstance>,
|
||||||
|
destinations: Vec<PlateInstance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const W96_COL_MAX: u8 = 12;
|
||||||
|
const W96_ROW_MAX: u8 = 8;
|
||||||
|
const W384_COL_MAX: u8 = 24;
|
||||||
|
const W384_ROW_MAX: u8 = 16;
|
||||||
|
const W1536_COL_MAX: u8 = 48;
|
||||||
|
const W1536_ROW_MAX: u8 = 32;
|
||||||
|
|
||||||
|
pub fn auto(records: &[TransferRecord]) -> AutoOutput {
|
||||||
|
let unique_plates = find_unique_plates(records);
|
||||||
|
let transfers = get_transfer_for_all_pairs(records, &unique_plates);
|
||||||
|
|
||||||
|
AutoOutput { sources: unique_plates.sources, destinations: unique_plates.destinations, transfers }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_csv_auto(data: &str) -> AutoOutput {
|
||||||
|
let transfer_records = read_csv(data);
|
||||||
|
auto(&transfer_records)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_unique_plates(records: &[TransferRecord]) -> UniquePlates {
|
||||||
|
let mut source_names: HashSet<&str> = HashSet::new();
|
||||||
|
let mut destination_names: HashSet<&str> = HashSet::new();
|
||||||
|
|
||||||
|
for record in records {
|
||||||
|
source_names.insert(&record.source_plate);
|
||||||
|
destination_names.insert(&record.destination_plate);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut sources: Vec<PlateInstance> = Vec::with_capacity(source_names.len());
|
||||||
|
for source_name in source_names {
|
||||||
|
let filtered_records: Vec<&TransferRecord> = records
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.source_plate == source_name)
|
||||||
|
.collect();
|
||||||
|
let format_guess = guess_plate_size(&filtered_records, PlateType::Source);
|
||||||
|
sources.push(PlateInstance::new(
|
||||||
|
PlateType::Source,
|
||||||
|
format_guess,
|
||||||
|
source_name.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut destinations: Vec<PlateInstance> = Vec::with_capacity(destination_names.len());
|
||||||
|
for destination_name in destination_names {
|
||||||
|
let filtered_records: Vec<&TransferRecord> = records
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.destination_plate == destination_name)
|
||||||
|
.collect();
|
||||||
|
let format_guess = guess_plate_size(&filtered_records, PlateType::Destination);
|
||||||
|
destinations.push(PlateInstance::new(
|
||||||
|
PlateType::Destination,
|
||||||
|
format_guess,
|
||||||
|
destination_name.to_string(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePlates {
|
||||||
|
sources,
|
||||||
|
destinations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guess_plate_size(plate_filtered_records: &[&TransferRecord], which: PlateType) -> PlateFormat {
|
||||||
|
let mut guess = PlateFormat::W96; // Never guess smaller than 96
|
||||||
|
for record in plate_filtered_records {
|
||||||
|
if let Some((row, col)) = string_well_to_pt(match which {
|
||||||
|
PlateType::Source => &record.source_well,
|
||||||
|
PlateType::Destination => &record.destination_well,
|
||||||
|
}) {
|
||||||
|
if row > W1536_ROW_MAX || col > W1536_COL_MAX {
|
||||||
|
return PlateFormat::W3456;
|
||||||
|
} else if row > W384_ROW_MAX || col > W384_COL_MAX {
|
||||||
|
guess = PlateFormat::W1536;
|
||||||
|
} else if row > W96_ROW_MAX || col > W96_COL_MAX {
|
||||||
|
guess = PlateFormat::W384;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guess
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transfer_for_all_pairs(
|
||||||
|
records: &[TransferRecord],
|
||||||
|
unique_plates: &UniquePlates,
|
||||||
|
) -> Vec<Transfer> {
|
||||||
|
let mut transfers: Vec<Transfer> = Vec::new();
|
||||||
|
for source_instance in &unique_plates.sources {
|
||||||
|
for destination_instance in &unique_plates.destinations {
|
||||||
|
if let Some(transfer) =
|
||||||
|
get_transfer_for_pair(records, source_instance, destination_instance)
|
||||||
|
{
|
||||||
|
transfers.push(transfer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
transfers
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_transfer_for_pair(
|
||||||
|
records: &[TransferRecord],
|
||||||
|
source: &PlateInstance,
|
||||||
|
destination: &PlateInstance,
|
||||||
|
) -> Option<Transfer> {
|
||||||
|
let source_name: &str = &source.name;
|
||||||
|
let destination_name: &str = &destination.name;
|
||||||
|
|
||||||
|
let mut filtered_records = records
|
||||||
|
.iter()
|
||||||
|
.filter(|x| x.source_plate == source_name && x.destination_plate == destination_name)
|
||||||
|
.peekable();
|
||||||
|
|
||||||
|
if filtered_records.peek().is_none() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut source_wells: HashSet<(u8, u8)> = HashSet::new();
|
||||||
|
let mut destination_wells: HashSet<(u8, u8)> = HashSet::new();
|
||||||
|
for record in filtered_records {
|
||||||
|
let source_point_opt = string_well_to_pt(&record.source_well);
|
||||||
|
let destination_point_opt = string_well_to_pt(&record.destination_well);
|
||||||
|
|
||||||
|
if source_point_opt.and(destination_point_opt).is_some() {
|
||||||
|
let source_point = source_point_opt.unwrap();
|
||||||
|
let destination_point = destination_point_opt.unwrap();
|
||||||
|
|
||||||
|
source_wells.insert(source_point);
|
||||||
|
destination_wells.insert(destination_point);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let source_wells_vec: Vec<(u8, u8)> = source_wells.into_iter().collect();
|
||||||
|
let destination_wells_vec: Vec<(u8, u8)> = destination_wells.into_iter().collect();
|
||||||
|
|
||||||
|
let custom_region: Region =
|
||||||
|
Region::Custom(CustomRegion::new(source_wells_vec, destination_wells_vec));
|
||||||
|
|
||||||
|
let transfer_region = TransferRegion {
|
||||||
|
source_plate: source.plate,
|
||||||
|
dest_plate: destination.plate,
|
||||||
|
interleave_source: (1, 1),
|
||||||
|
interleave_dest: (1, 1),
|
||||||
|
source_region: custom_region.clone(),
|
||||||
|
dest_region: custom_region,
|
||||||
|
};
|
||||||
|
|
||||||
|
let transfer_name = format!("{} to {}", source.name, destination.name);
|
||||||
|
|
||||||
|
Some(Transfer::new(
|
||||||
|
source.clone(),
|
||||||
|
destination.clone(),
|
||||||
|
transfer_region,
|
||||||
|
transfer_name,
|
||||||
|
))
|
||||||
|
}
|
|
@ -1,38 +1,12 @@
|
||||||
use crate::transfer::Transfer;
|
use crate::transfer::Transfer;
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
|
use super::TransferRecord;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub struct TransferRecord {
|
|
||||||
#[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")]
|
|
||||||
pub source_plate: String,
|
|
||||||
#[serde(rename = "Source Well", alias = "source well", alias = "src well")]
|
|
||||||
pub source_well: String,
|
|
||||||
#[serde(
|
|
||||||
rename = "Dest Plate",
|
|
||||||
alias = "dest plate",
|
|
||||||
alias = "destination plate"
|
|
||||||
)]
|
|
||||||
pub destination_plate: String,
|
|
||||||
#[serde(
|
|
||||||
rename = "Destination Well",
|
|
||||||
alias = "destination well",
|
|
||||||
alias = "dest well"
|
|
||||||
)]
|
|
||||||
pub destination_well: String,
|
|
||||||
#[serde(rename = "Transfer Volume", alias = "transfer volume")]
|
|
||||||
#[serde(default = "volume_default")]
|
|
||||||
pub volume: f32,
|
|
||||||
#[serde(rename = "Concentration", alias = "concentration")]
|
|
||||||
pub concentration: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn volume_default() -> f32 {
|
|
||||||
Transfer::default().volume
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transfer_to_records(
|
pub fn transfer_to_records(
|
||||||
tr: &Transfer,
|
tr: &Transfer,
|
||||||
src_barcode: &str,
|
src_barcode: &str,
|
||||||
|
@ -70,6 +44,21 @@ pub fn records_to_csv(trs: Vec<TransferRecord>) -> Result<String, Box<dyn Error>
|
||||||
Ok(data)
|
Ok(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn string_well_to_pt(input: &str) -> Option<(u8, u8)> {
|
||||||
|
lazy_static! {
|
||||||
|
static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap();
|
||||||
|
}
|
||||||
|
if let Some(c1) = REGEX.captures(input) {
|
||||||
|
if let (Some(row), Some(col)) = (letters_to_num(&c1[1]), c1[2].parse::<u8>().ok()) {
|
||||||
|
Some((row, col))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn read_csv(data: &str) -> Vec<TransferRecord> {
|
pub fn read_csv(data: &str) -> Vec<TransferRecord> {
|
||||||
let (header, data) = data.split_at(data.find('\n').unwrap());
|
let (header, data) = data.split_at(data.find('\n').unwrap());
|
||||||
let modified: String = header.to_lowercase() + data;
|
let modified: String = header.to_lowercase() + data;
|
|
@ -0,0 +1,9 @@
|
||||||
|
mod transfer_record;
|
||||||
|
mod conversion;
|
||||||
|
mod auto;
|
||||||
|
|
||||||
|
pub use transfer_record::volume_default;
|
||||||
|
pub use transfer_record::TransferRecord;
|
||||||
|
pub use conversion::*;
|
||||||
|
|
||||||
|
pub use auto::{auto, read_csv_auto};
|
|
@ -0,0 +1,33 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::transfer::Transfer;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||||
|
pub struct TransferRecord {
|
||||||
|
#[serde(rename = "Source Plate", alias = "source plate", alias = "src plate")]
|
||||||
|
pub source_plate: String,
|
||||||
|
#[serde(rename = "Source Well", alias = "source well", alias = "src well")]
|
||||||
|
pub source_well: String,
|
||||||
|
#[serde(
|
||||||
|
rename = "Dest Plate",
|
||||||
|
alias = "dest plate",
|
||||||
|
alias = "destination plate"
|
||||||
|
)]
|
||||||
|
pub destination_plate: String,
|
||||||
|
#[serde(
|
||||||
|
rename = "Destination Well",
|
||||||
|
alias = "destination well",
|
||||||
|
alias = "dest well"
|
||||||
|
)]
|
||||||
|
pub destination_well: String,
|
||||||
|
#[serde(rename = "Transfer Volume", alias = "transfer volume")]
|
||||||
|
#[serde(default = "volume_default")]
|
||||||
|
pub volume: f32,
|
||||||
|
#[serde(rename = "Concentration", alias = "concentration")]
|
||||||
|
pub concentration: Option<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn volume_default() -> f32 {
|
||||||
|
Transfer::default().volume
|
||||||
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ use super::plate::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Serialize, Deserialize, Debug)]
|
||||||
pub struct PlateInstance {
|
pub struct PlateInstance {
|
||||||
pub plate: Plate,
|
pub plate: Plate,
|
||||||
#[serde(rename = "id_v7")]
|
#[serde(rename = "id_v7")]
|
||||||
|
|
|
@ -8,6 +8,12 @@ pub struct CustomRegion {
|
||||||
dest: Vec<(u8, u8)>,
|
dest: Vec<(u8, u8)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CustomRegion {
|
||||||
|
pub fn new(src: Vec<(u8, u8)>, dest: Vec<(u8, u8)>) -> Self {
|
||||||
|
CustomRegion { src, dest }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
pub enum Region {
|
pub enum Region {
|
||||||
Rect((u8, u8), (u8, u8)),
|
Rect((u8, u8), (u8, u8)),
|
||||||
|
|
|
@ -33,7 +33,7 @@ pub fn num_to_letters(num: u8) -> Option<String> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{letters_to_num, num_to_letters, RegionDisplay};
|
use super::{letters_to_num, num_to_letters};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_letters_to_num() {
|
fn test_letters_to_num() {
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use wasm_bindgen::{prelude::*, JsCast};
|
use wasm_bindgen::{prelude::*, JsCast};
|
||||||
use web_sys::{
|
use web_sys::{
|
||||||
FileReader, HtmlButtonElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement,
|
FileReader, HtmlButtonElement, HtmlDialogElement, HtmlElement, HtmlFormElement,
|
||||||
HtmlOptionElement, HtmlSelectElement,
|
HtmlInputElement, HtmlOptionElement, HtmlSelectElement,
|
||||||
};
|
};
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewdux::prelude::*;
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
use crate::components::states::MainState;
|
use crate::components::states::MainState;
|
||||||
use plate_tool_lib::util::letters_to_num;
|
|
||||||
|
|
||||||
use plate_tool_lib::transfer::Transfer;
|
use plate_tool_lib::transfer::Transfer;
|
||||||
use plate_tool_lib::transfer_region::{Region, TransferRegion};
|
use plate_tool_lib::transfer_region::{Region, TransferRegion};
|
||||||
|
|
||||||
use plate_tool_lib::csv::TransferRecord;
|
use plate_tool_lib::csv::{auto, string_well_to_pt, TransferRecord};
|
||||||
|
|
||||||
use super::main_window_callbacks::create_close_button;
|
use super::main_window_callbacks::create_close_button;
|
||||||
|
|
||||||
|
@ -115,6 +112,17 @@ pub fn import_transfer_csv_onload_callback(
|
||||||
|
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
|
|
||||||
|
let auto_button = document
|
||||||
|
.create_element("button")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlElement>()
|
||||||
|
.unwrap();
|
||||||
|
auto_button.set_inner_text("Auto");
|
||||||
|
let auto_button_callback = auto_callback(main_dispatch.clone(), &records);
|
||||||
|
auto_button.set_onclick(Some(auto_button_callback.as_ref().unchecked_ref()));
|
||||||
|
auto_button_callback.forget();
|
||||||
|
|
||||||
let form = document
|
let form = document
|
||||||
.create_element("form")
|
.create_element("form")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -211,6 +219,7 @@ pub fn import_transfer_csv_onload_callback(
|
||||||
form.append_child(&to_dest).unwrap();
|
form.append_child(&to_dest).unwrap();
|
||||||
modal.append_child(&submit).unwrap();
|
modal.append_child(&submit).unwrap();
|
||||||
modal.append_child(&form).unwrap();
|
modal.append_child(&form).unwrap();
|
||||||
|
modal.append_child(&auto_button).unwrap();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -229,28 +238,14 @@ pub fn import_transfer_csv_submit_callback(
|
||||||
let from_dest = from_dest.value();
|
let from_dest = from_dest.value();
|
||||||
let to_dest = to_dest.value();
|
let to_dest = to_dest.value();
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref REGEX: Regex = Regex::new(r"([A-Z,a-z]+)(\d+)").unwrap();
|
|
||||||
}
|
|
||||||
let records: Vec<((u8, u8), (u8, u8))> = records
|
let records: Vec<((u8, u8), (u8, u8))> = records
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|record| record.source_plate == from_source)
|
.filter(|record| record.source_plate == from_source)
|
||||||
.filter(|record| record.destination_plate == from_dest)
|
.filter(|record| record.destination_plate == from_dest)
|
||||||
.map(|record| {
|
.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]);
|
|
||||||
|
|
||||||
(
|
(
|
||||||
(
|
string_well_to_pt(&record.source_well).unwrap(),
|
||||||
letters_to_num(&c1[1]).unwrap(),
|
string_well_to_pt(&record.destination_well).unwrap(),
|
||||||
c1[2].parse::<u8>().unwrap(),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
letters_to_num(&c2[1]).unwrap(),
|
|
||||||
c2[2].parse::<u8>().unwrap(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -291,3 +286,20 @@ pub fn import_transfer_csv_submit_callback(
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn auto_callback(
|
||||||
|
main_dispatch: Dispatch<MainState>,
|
||||||
|
records: &[TransferRecord],
|
||||||
|
) -> Closure<dyn FnMut(Event)> {
|
||||||
|
let records = Vec::from(records);
|
||||||
|
Closure::<dyn FnMut(_)>::new(move |_| {
|
||||||
|
let res = auto(&records);
|
||||||
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
state.source_plates.extend(res.sources.into_iter());
|
||||||
|
state
|
||||||
|
.destination_plates
|
||||||
|
.extend(res.destinations.into_iter());
|
||||||
|
state.transfers.extend(res.transfers.into_iter());
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue