Compare commits
No commits in common. "c348d111ecbb388e2256843e7ee21061c1a3f9bd" and "6aee3ded2cad7f329ced1d77f2790bfd960425af" have entirely different histories.
c348d111ec
...
6aee3ded2c
|
@ -34,12 +34,6 @@ dependencies = [
|
||||||
"syn 2.0.16",
|
"syn 2.0.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "atomic"
|
|
||||||
version = "0.5.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -561,11 +555,9 @@ name = "plate-tool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"csv",
|
"csv",
|
||||||
"getrandom",
|
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"log",
|
"log",
|
||||||
"rand",
|
|
||||||
"regex",
|
"regex",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -891,11 +883,10 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid"
|
name = "uuid"
|
||||||
version = "1.6.1"
|
version = "1.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
|
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"atomic",
|
|
||||||
"getrandom",
|
"getrandom",
|
||||||
"rand",
|
"rand",
|
||||||
"serde",
|
"serde",
|
||||||
|
@ -905,9 +896,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "uuid-macro-internal"
|
name = "uuid-macro-internal"
|
||||||
version = "1.6.1"
|
version = "1.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e"
|
checksum = "3f67b459f42af2e6e1ee213cb9da4dbd022d3320788c3fb3e1b893093f1e45da"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
12
Cargo.toml
12
Cargo.toml
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "plate-tool"
|
name = "plate-tool"
|
||||||
version = "0.2.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
@ -10,20 +10,18 @@ yew = { version = "0.20.0", features = ["csr"] }
|
||||||
yewdux = "0.9"
|
yewdux = "0.9"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement",
|
web-sys = { version = "0.3", features = ["FormData", "HtmlFormElement",
|
||||||
"HtmlDialogElement", "Blob", "Url", "Window",
|
"HtmlDialogElement", "Blob", "Url", "Window",
|
||||||
"HtmlAnchorElement", "ReadableStream", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement",
|
"HtmlAnchorElement", "ReadableStream",
|
||||||
"FileReader"] }
|
"FileReader"] }
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
wasm-logger = "0.2"
|
wasm-logger = "0.2"
|
||||||
regex = "1"
|
regex = "1"
|
||||||
lazy_static = "1.4"
|
lazy_static = "1.4"
|
||||||
uuid = { version = "1.6", features = ["v7", "fast-rng", "macro-diagnostics", "js", "serde"] }
|
uuid = { version = "1.3", features = ["v4", "fast-rng", "macro-diagnostics", "js", "serde"] }
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
csv = "1.2"
|
csv = "1.2"
|
||||||
getrandom = { version = "0.2", features = ["js"] }
|
|
||||||
rand = { version = "0.8", features = ["small_rng"] }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
wasm-bindgen-test = "0.3.0"
|
wasm-bindgen-test = "0.3.0"
|
||||||
|
|
20
README.md
20
README.md
|
@ -53,37 +53,27 @@ To add a new plate, click the "New Plate" button:
|
||||||
If you no longer need a transfer, select it as above and then click the "Delete" button.
|
If you no longer need a transfer, select it as above and then click the "Delete" button.
|
||||||
|
|
||||||
### Importing and Exporting
|
### Importing and Exporting
|
||||||
|
|
||||||
#### Export as CSV
|
|
||||||
Exporting the transfers we have created to a CSV format is the primary (if not sole) usage of Plate Tool.
|
Exporting the transfers we have created to a CSV format is the primary (if not sole) usage of Plate Tool.
|
||||||
To do so, first note the "File" tab at the top-left of the screen (above the list pane).
|
To do so, first note the "File" tab at the top-left of the screen (above the list pane).
|
||||||
Mouse over this tab, and a few more options will be revealed.
|
Mouse over this tab, and a few more options will be revealed.
|
||||||
We want to export: mouse over export and select "Export as CSV".
|
We want to export: mouse over export and select "Export as CSV".
|
||||||
You will be reminded that this is a one-way export (see JSON export/import below),
|
You will be reminded that this is a one-way export (see JSON export/import below),
|
||||||
and then prompted by your browser to select a location for your file.
|
and then prompted by your browser to select a location for your file.
|
||||||
|
|
||||||
#### Export as JSON (Saving Your Work)
|
Currently, it is not possible to import from nor export to a format produced by other similar software.
|
||||||
Currently, it is not possible to export to a format produced by other similar software.
|
|
||||||
However, you might reasonably want to save a copy of your work
|
However, you might reasonably want to save a copy of your work
|
||||||
either as a backup or to share.
|
either as a backup or to share.
|
||||||
Mouse over the "File" tab, then "Export" as above, then alternatively select "Export as JSON".
|
Mouse over the "File" tab, then "Export" as above, then alternatively select "Export as JSON".
|
||||||
Your browser will then prompt you to pick a suitable location to save your work as a file.
|
Your browser will then prompt you to pick a suitable location to save your work as a file.
|
||||||
(See note 1 below)
|
(See note 1 below)
|
||||||
|
|
||||||
#### Import from JSON (Recovering Your Work)
|
|
||||||
If we want to import one such file, mouse over the "File" tab as before
|
If we want to import one such file, mouse over the "File" tab as before
|
||||||
and select "Import", and finally click "Import from JSON".
|
and select "Import".
|
||||||
This opens a modal where you are prompted to upload (see note 2)
|
This opens a modal where you are prompted to upload (see note 2)
|
||||||
your file; it will then be processed and loaded.
|
your file; it will then be processed and loaded.
|
||||||
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)
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,25 +0,0 @@
|
||||||
function copy_screenshot(el) {
|
|
||||||
html2canvas(el).then((canvas) => {
|
|
||||||
console.log("Copying image to clipboard");
|
|
||||||
canvas.toBlob((b) => {
|
|
||||||
try {
|
|
||||||
navigator.clipboard.write([
|
|
||||||
new ClipboardItem({
|
|
||||||
'image/png': b
|
|
||||||
})
|
|
||||||
]);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to copy!");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function copy_screenshot_dest() {
|
|
||||||
let plate = document.getElementsByClassName("dest_plate")[0];
|
|
||||||
copy_screenshot(plate);
|
|
||||||
}
|
|
||||||
function copy_screenshot_src() {
|
|
||||||
let plate = document.getElementsByClassName("source_plate")[0];
|
|
||||||
copy_screenshot(plate);
|
|
||||||
}
|
|
|
@ -12,8 +12,6 @@ div.upper_menu {
|
||||||
|
|
||||||
visibility: inherit;
|
visibility: inherit;
|
||||||
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
div.dropdown {
|
div.dropdown {
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,6 @@
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link data-trunk rel="scss" href="assets/scss/index.scss">
|
<link data-trunk rel="scss" href="assets/scss/index.scss">
|
||||||
<link data-trunk rel="copy-dir" href="assets/fonts">
|
<link data-trunk rel="copy-dir" href="assets/fonts">
|
||||||
<script data-trunk src="assets/js/screenshot_utility.js"></script>
|
|
||||||
<script data-trunk src="assets/js/html2canvas.js"></script>
|
|
||||||
<title>Plate Tool</title>
|
<title>Plate Tool</title>
|
||||||
</head>
|
</head>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,27 +1,18 @@
|
||||||
#![allow(non_snake_case)]
|
#![allow(non_snake_case)]
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use js_sys::Array;
|
use js_sys::Array;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
use wasm_bindgen::{prelude::*, JsCast, JsValue};
|
||||||
use web_sys::{
|
use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url};
|
||||||
Blob, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement,
|
|
||||||
HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url,
|
|
||||||
};
|
|
||||||
use yew::prelude::*;
|
use yew::prelude::*;
|
||||||
use yewdux::prelude::*;
|
use yewdux::prelude::*;
|
||||||
|
|
||||||
use super::new_plate_dialog::NewPlateDialog;
|
use super::new_plate_dialog::NewPlateDialog;
|
||||||
use super::plates::plate_container::PlateContainer;
|
use super::plates::plate_container::PlateContainer;
|
||||||
use super::states::{CurrentTransfer, MainState};
|
use super::states::{CurrentTransfer, MainState};
|
||||||
use super::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu};
|
use super::transfer_menu::TransferMenu;
|
||||||
use super::tree::Tree;
|
use super::tree::Tree;
|
||||||
|
|
||||||
use crate::data::csv::state_to_csv;
|
use crate::data::csv::state_to_csv;
|
||||||
use crate::data::plate_instances::PlateInstance;
|
use crate::data::plate_instances::PlateInstance;
|
||||||
use crate::data::transfer::Transfer;
|
|
||||||
use crate::data::transfer_region::{Region, TransferRegion};
|
|
||||||
|
|
||||||
#[function_component]
|
#[function_component]
|
||||||
pub fn MainWindow() -> Html {
|
pub fn MainWindow() -> Html {
|
||||||
|
@ -49,15 +40,6 @@ pub fn MainWindow() -> Html {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let toggle_in_transfer_hashes_callback = {
|
|
||||||
let main_dispatch = main_dispatch.clone();
|
|
||||||
Callback::from(move |_| {
|
|
||||||
main_dispatch.reduce_mut(|state| {
|
|
||||||
state.preferences.in_transfer_hashes ^= true;
|
|
||||||
})
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
let new_plate_dialog_is_open = use_state_eq(|| false);
|
let new_plate_dialog_is_open = use_state_eq(|| false);
|
||||||
let new_plate_dialog_callback = {
|
let new_plate_dialog_callback = {
|
||||||
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
|
||||||
|
@ -118,7 +100,6 @@ pub fn MainWindow() -> Html {
|
||||||
};
|
};
|
||||||
|
|
||||||
let import_json_button_callback = {
|
let import_json_button_callback = {
|
||||||
let main_dispatch = main_dispatch.clone();
|
|
||||||
Callback::from(move |_| {
|
Callback::from(move |_| {
|
||||||
let window = web_sys::window().unwrap();
|
let window = web_sys::window().unwrap();
|
||||||
let document = window.document().unwrap();
|
let document = window.document().unwrap();
|
||||||
|
@ -196,274 +177,6 @@ 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");
|
|
||||||
submit.set_inner_text("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! {
|
html! {
|
||||||
<>
|
<>
|
||||||
<div class="upper_menu">
|
<div class="upper_menu">
|
||||||
|
@ -477,22 +190,7 @@ pub fn MainWindow() -> Html {
|
||||||
<button onclick={export_json_button_callback}>{"Export as JSON"}</button>
|
<button onclick={export_json_button_callback}>{"Export as JSON"}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dropdown-sub">
|
<button onclick={import_json_button_callback}>{"Import"}</button>
|
||||||
<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 class="dropdown">
|
|
||||||
<button>{"Options"}</button>
|
|
||||||
<div class="dropdown-sub">
|
|
||||||
<button>{"Styles"}</button>
|
|
||||||
<div>
|
|
||||||
<button onclick={toggle_in_transfer_hashes_callback}>{"Toggle transfer hashes"}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main_container">
|
<div class="main_container">
|
||||||
|
|
|
@ -99,8 +99,8 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html {
|
||||||
<option value="12">{"12"}</option>
|
<option value="12">{"12"}</option>
|
||||||
<option value="24">{"24"}</option>
|
<option value="24">{"24"}</option>
|
||||||
<option value="48">{"48"}</option>
|
<option value="48">{"48"}</option>
|
||||||
<option value="96" selected={true}>{"96"}</option>
|
<option value="96">{"96"}</option>
|
||||||
<option value="384">{"384"}</option>
|
<option value="384" selected={true}>{"384"}</option>
|
||||||
<option value="1536">{"1536"}</option>
|
<option value="1536">{"1536"}</option>
|
||||||
<option value="3456">{"3456"}</option>
|
<option value="3456">{"3456"}</option>
|
||||||
</select>
|
</select>
|
||||||
|
|
|
@ -34,21 +34,12 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html {
|
||||||
let (pt1, pt2) = match ct_state.transfer.transfer_region.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),
|
||||||
Region::Custom(_) => ((0,0), (0,0)),
|
|
||||||
};
|
};
|
||||||
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.transfer_region.get_destination_wells();
|
let destination_wells = ct_state.transfer.transfer_region.get_destination_wells();
|
||||||
|
|
||||||
let ordered_ids: Vec<uuid::Uuid> = {
|
|
||||||
let mut ids: Vec<uuid::Uuid> = main_state.transfers.clone().iter()
|
|
||||||
.map(|x| x.id)
|
|
||||||
.collect();
|
|
||||||
ids.sort_unstable();
|
|
||||||
ids
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
||||||
|
@ -107,11 +98,6 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html {
|
||||||
|
|
||||||
let mouseleave_callback = Callback::clone(&mouseup_callback);
|
let mouseleave_callback = Callback::clone(&mouseup_callback);
|
||||||
|
|
||||||
|
|
||||||
let screenshot_callback = Callback::from(|_| {
|
|
||||||
let _ = js_sys::eval("copy_screenshot_dest()");
|
|
||||||
});
|
|
||||||
|
|
||||||
let column_header = {
|
let column_header = {
|
||||||
let headers = (1..=props.destination_plate.plate.size().1)
|
let headers = (1..=props.destination_plate.plate.size().1)
|
||||||
.map(|j| {
|
.map(|j| {
|
||||||
|
@ -128,10 +114,10 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html {
|
||||||
<DestPlateCell i={i} j={j}
|
<DestPlateCell i={i} j={j}
|
||||||
selected={super::source_plate::in_rect(*m_start_handle.clone(), *m_end_handle.clone(), (i,j))}
|
selected={super::source_plate::in_rect(*m_start_handle.clone(), *m_end_handle.clone(), (i,j))}
|
||||||
mouse={mouse_callback.clone()}
|
mouse={mouse_callback.clone()}
|
||||||
in_transfer={destination_wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
|
in_transfer={destination_wells.contains(&(i,j))}
|
||||||
color={transfer_map.get(&(i,j))
|
color={transfer_map.get(&(i,j))
|
||||||
.and_then(|t| t.last())
|
.and_then(|t| t.last())
|
||||||
.map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
|
.map(|t| PALETTE.get_uuid(t.get_uuid()))
|
||||||
}
|
}
|
||||||
cell_height={props.cell_height}
|
cell_height={props.cell_height}
|
||||||
title={transfer_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
|
title={transfer_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
|
||||||
|
@ -148,8 +134,7 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html {
|
||||||
.collect::<Html>();
|
.collect::<Html>();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div ondblclick={screenshot_callback}
|
<div class={classes!{"dest_plate",
|
||||||
class={classes!{"dest_plate",
|
|
||||||
"W".to_owned()+&props.source_plate.plate.plate_format.to_string()}}>
|
"W".to_owned()+&props.source_plate.plate.plate_format.to_string()}}>
|
||||||
<table
|
<table
|
||||||
onmouseup={move |e| {
|
onmouseup={move |e| {
|
||||||
|
|
|
@ -34,7 +34,6 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html {
|
||||||
let (pt1, pt2) = match ct_state.transfer.transfer_region.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),
|
||||||
Region::Custom(_) => ((0,0), (0,0)),
|
|
||||||
};
|
};
|
||||||
m_start_handle.set(Some(pt1));
|
m_start_handle.set(Some(pt1));
|
||||||
m_end_handle.set(Some(pt2));
|
m_end_handle.set(Some(pt2));
|
||||||
|
@ -61,14 +60,6 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html {
|
||||||
|
|
||||||
let source_wells = ct_state.transfer.transfer_region.get_source_wells();
|
let source_wells = ct_state.transfer.transfer_region.get_source_wells();
|
||||||
|
|
||||||
let ordered_ids: Vec<uuid::Uuid> = {
|
|
||||||
let mut ids: Vec<uuid::Uuid> = main_state.transfers.clone().iter()
|
|
||||||
.map(|x| x.id)
|
|
||||||
.collect();
|
|
||||||
ids.sort_unstable();
|
|
||||||
ids
|
|
||||||
};
|
|
||||||
|
|
||||||
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();
|
||||||
|
@ -108,10 +99,6 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html {
|
||||||
|
|
||||||
let mouseleave_callback = Callback::clone(&mouseup_callback);
|
let mouseleave_callback = Callback::clone(&mouseup_callback);
|
||||||
|
|
||||||
let screenshot_callback = Callback::from(|_| {
|
|
||||||
let _ = js_sys::eval("copy_screenshot_src()");
|
|
||||||
});
|
|
||||||
|
|
||||||
let column_header = {
|
let column_header = {
|
||||||
let headers = (1..=props.source_plate.plate.size().1)
|
let headers = (1..=props.source_plate.plate.size().1)
|
||||||
.map(|j| {
|
.map(|j| {
|
||||||
|
@ -131,10 +118,10 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html {
|
||||||
<SourcePlateCell i={i} j={j}
|
<SourcePlateCell i={i} j={j}
|
||||||
selected={in_rect(*m_start_handle.clone(), *m_end_handle.clone(), (i,j))}
|
selected={in_rect(*m_start_handle.clone(), *m_end_handle.clone(), (i,j))}
|
||||||
mouse={mouse_callback.clone()}
|
mouse={mouse_callback.clone()}
|
||||||
in_transfer={source_wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
|
in_transfer={source_wells.contains(&(i,j))}
|
||||||
color={transfer_map.get(&(i,j))
|
color={transfer_map.get(&(i,j))
|
||||||
.and_then(|t| t.last())
|
.and_then(|t| t.last())
|
||||||
.map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
|
.map(|t| PALETTE.get_uuid(t.get_uuid()))
|
||||||
}
|
}
|
||||||
cell_height={props.cell_height}
|
cell_height={props.cell_height}
|
||||||
title={transfer_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
|
title={transfer_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
|
||||||
|
@ -152,8 +139,7 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html {
|
||||||
.collect::<Html>();
|
.collect::<Html>();
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div ondblclick={screenshot_callback}
|
<div class={classes!{"source_plate",
|
||||||
class={classes!{"source_plate",
|
|
||||||
"W".to_owned()+&props.source_plate.plate.plate_format.to_string()}}>
|
"W".to_owned()+&props.source_plate.plate.plate_format.to_string()}}>
|
||||||
<table
|
<table
|
||||||
onmouseup={move |e| {
|
onmouseup={move |e| {
|
||||||
|
|
|
@ -2,10 +2,6 @@
|
||||||
// https://iquilezles.org/articles/palettes/
|
// https://iquilezles.org/articles/palettes/
|
||||||
// http://dev.thi.ng/gradients/
|
// http://dev.thi.ng/gradients/
|
||||||
|
|
||||||
use rand::prelude::*;
|
|
||||||
use rand::rngs::SmallRng;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Debug)]
|
||||||
pub struct ColorPalette {
|
pub struct ColorPalette {
|
||||||
a: [f64; 3],
|
a: [f64; 3],
|
||||||
|
@ -35,23 +31,9 @@ impl ColorPalette {
|
||||||
self.get((2f64.powi(-(t.ilog2() as i32))) * (t as f64 + 0.5f64) - 1.0f64)
|
self.get((2f64.powi(-(t.ilog2() as i32))) * (t as f64 + 0.5f64) - 1.0f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn get_uuid(&self, t: uuid::Uuid) -> [f64; 3] {
|
pub fn get_uuid(&self, t: uuid::Uuid) -> [f64; 3] {
|
||||||
// // self.get(t.as_u128() as f64 / (u128::MAX) as f64)
|
log::debug!("{}", t.as_u128() as f64 / (u128::MAX) as f64);
|
||||||
// let mut r = SmallRng::seed_from_u64(t.as_u128() as u64);
|
self.get(t.as_u128() as f64 / (u128::MAX) as f64)
|
||||||
// self.get(r.gen_range(0.0..1.0f64))
|
|
||||||
// }
|
|
||||||
|
|
||||||
pub fn get_ordered(&self, t: uuid::Uuid, ordered_uuids: &Vec<uuid::Uuid>)
|
|
||||||
-> [f64; 3] {
|
|
||||||
let index = ordered_uuids.iter().position(|&x| x == t).expect("uuid must be in list of uuids") + 1;
|
|
||||||
return self.get(Self::space_evenly(index))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn space_evenly(x: usize) -> f64 {
|
|
||||||
let e: usize = (x.ilog2() + 1) as usize;
|
|
||||||
let d: usize = (2usize.pow(e as u32)) as usize;
|
|
||||||
let n: usize = (2*x + 1) % d;
|
|
||||||
return (n as f64) / (d as f64);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,5 +54,4 @@ impl Palettes {
|
||||||
c: [0.100, 0.500, 0.360],
|
c: [0.100, 0.500, 0.360],
|
||||||
d: [0.000, 0.000, 0.650],
|
d: [0.000, 0.000, 0.650],
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,17 +13,6 @@ pub struct CurrentTransfer {
|
||||||
pub transfer: Transfer,
|
pub transfer: Transfer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy, Serialize, Deserialize)]
|
|
||||||
pub struct Preferences {
|
|
||||||
pub in_transfer_hashes: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Preferences {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self { in_transfer_hashes: true }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(Default, PartialEq, Clone, Serialize, Deserialize)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub struct MainState {
|
pub struct MainState {
|
||||||
|
@ -33,9 +22,6 @@ pub struct MainState {
|
||||||
pub selected_source_plate: Uuid,
|
pub selected_source_plate: Uuid,
|
||||||
pub selected_dest_plate: Uuid,
|
pub selected_dest_plate: Uuid,
|
||||||
pub selected_transfer: Uuid,
|
pub selected_transfer: Uuid,
|
||||||
|
|
||||||
#[serde(default)]
|
|
||||||
pub preferences: Preferences,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Store for MainState {
|
impl Store for MainState {
|
||||||
|
|
|
@ -39,9 +39,6 @@ pub fn TransferMenu() -> Html {
|
||||||
let ct_dispatch = ct_dispatch.clone();
|
let ct_dispatch = ct_dispatch.clone();
|
||||||
|
|
||||||
Callback::from(move |e: Event| {
|
Callback::from(move |e: Event| {
|
||||||
if matches!(ct_dispatch.get().transfer.transfer_region.source_region, Region::Custom(_)) {
|
|
||||||
return; // Do nothing here!
|
|
||||||
}
|
|
||||||
let target: Option<EventTarget> = e.target();
|
let target: Option<EventTarget> = e.target();
|
||||||
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
|
||||||
if let Some(input) = input {
|
if let Some(input) = input {
|
||||||
|
@ -195,7 +192,7 @@ pub fn TransferMenu() -> Html {
|
||||||
let new_transfer = Transfer::new(
|
let new_transfer = Transfer::new(
|
||||||
spi.clone(),
|
spi.clone(),
|
||||||
dpi.clone(),
|
dpi.clone(),
|
||||||
ct_state.transfer.transfer_region.clone(),
|
ct_state.transfer.transfer_region,
|
||||||
ct_state.transfer.name.clone(),
|
ct_state.transfer.name.clone(),
|
||||||
);
|
);
|
||||||
main_dispatch.reduce_mut(|state| {
|
main_dispatch.reduce_mut(|state| {
|
||||||
|
@ -253,8 +250,6 @@ pub fn TransferMenu() -> Html {
|
||||||
onchange={on_name_change}
|
onchange={on_name_change}
|
||||||
value={ct_state.transfer.name.clone()}/>
|
value={ct_state.transfer.name.clone()}/>
|
||||||
</div>
|
</div>
|
||||||
// Anything below here is not rendered when a Custom transfer is selected
|
|
||||||
if !matches!(&ct_state.transfer.transfer_region.source_region, Region::Custom(_)) {
|
|
||||||
<div>
|
<div>
|
||||||
<label for="src_region"><h3>{"Source Region:"}</h3></label>
|
<label for="src_region"><h3>{"Source Region:"}</h3></label>
|
||||||
<input type="text" name="src_region"
|
<input type="text" name="src_region"
|
||||||
|
@ -296,7 +291,6 @@ pub fn TransferMenu() -> Html {
|
||||||
onchange={on_volume_change}
|
onchange={on_volume_change}
|
||||||
value={ct_state.transfer.volume.to_string()}/>
|
value={ct_state.transfer.volume.to_string()}/>
|
||||||
</div>
|
</div>
|
||||||
}
|
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<input type="button" name="new_transfer" onclick={new_transfer_button_callback}
|
<input type="button" name="new_transfer" onclick={new_transfer_button_callback}
|
||||||
value={"New"} />
|
value={"New"} />
|
||||||
|
@ -350,37 +344,6 @@ 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 {
|
impl From<&Region> for RegionDisplay {
|
||||||
fn from(value: &Region) -> Self {
|
fn from(value: &Region) -> Self {
|
||||||
match *value {
|
match *value {
|
||||||
|
@ -390,7 +353,6 @@ impl From<&Region> for RegionDisplay {
|
||||||
Region::Rect(c1, c2) => RegionDisplay::try_from((c1.0, c1.1, c2.0, c2.1))
|
Region::Rect(c1, c2) => RegionDisplay::try_from((c1.0, c1.1, c2.0, c2.1))
|
||||||
.ok()
|
.ok()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
Region::Custom(_) => RegionDisplay { text: "CUSTOM".to_string(), col_start: 0, row_start: 0, col_end: 0, row_end: 0 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -423,10 +385,9 @@ impl TryFrom<(u8, u8, u8, u8)> for RegionDisplay {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn letters_to_num(letters: &str) -> Option<u8> {
|
fn letters_to_num(letters: &str) -> Option<u8> {
|
||||||
let mut num: u8 = 0;
|
let mut num: u8 = 0;
|
||||||
for (i, letter) in letters.to_ascii_uppercase().chars().rev().enumerate() {
|
for (i, letter) in letters.chars().rev().enumerate() {
|
||||||
log::debug!("{}, {}", i, letter);
|
|
||||||
let n = letter as u8;
|
let n = letter as u8;
|
||||||
if !(65..=90).contains(&n) {
|
if !(65..=90).contains(&n) {
|
||||||
return None;
|
return None;
|
||||||
|
|
|
@ -2,23 +2,23 @@ use crate::components::states::MainState;
|
||||||
use crate::components::transfer_menu::num_to_letters;
|
use crate::components::transfer_menu::num_to_letters;
|
||||||
use crate::data::transfer::Transfer;
|
use crate::data::transfer::Transfer;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::Serialize;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct TransferRecord {
|
struct TransferRecord {
|
||||||
#[serde(rename = "Source Plate")]
|
#[serde(rename = "Source Plate Barcode")]
|
||||||
pub source_plate: String,
|
source_plate: String,
|
||||||
#[serde(rename = "Source Well")]
|
#[serde(rename = "Source Well")]
|
||||||
pub source_well: String,
|
source_well: String,
|
||||||
#[serde(rename = "Dest Plate")]
|
#[serde(rename = "Destination Plate Barcode")]
|
||||||
pub destination_plate: String,
|
destination_plate: String,
|
||||||
#[serde(rename = "Destination Well")]
|
#[serde(rename = "Destination Well")]
|
||||||
pub destination_well: String,
|
destination_well: String,
|
||||||
#[serde(rename = "Transfer Volume")]
|
#[serde(rename = "Volume")]
|
||||||
pub volume: f32,
|
volume: f32,
|
||||||
#[serde(rename = "Concentration")]
|
#[serde(rename = "Concentration")]
|
||||||
pub concentration: Option<f32>,
|
concentration: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn state_to_csv(state: &MainState) -> Result<String, Box<dyn Error>> {
|
pub fn state_to_csv(state: &MainState) -> Result<String, Box<dyn Error>> {
|
||||||
|
|
|
@ -5,8 +5,6 @@ use uuid::Uuid;
|
||||||
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
#[derive(PartialEq, Clone, Serialize, Deserialize)]
|
||||||
pub struct PlateInstance {
|
pub struct PlateInstance {
|
||||||
pub plate: Plate,
|
pub plate: Plate,
|
||||||
#[serde(rename = "id_v7")]
|
|
||||||
#[serde(default = "Uuid::now_v7")]
|
|
||||||
id: Uuid,
|
id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
@ -18,7 +16,7 @@ impl PlateInstance {
|
||||||
plate_type: sort,
|
plate_type: sort,
|
||||||
plate_format: format,
|
plate_format: format,
|
||||||
},
|
},
|
||||||
id: Uuid::now_v7(),
|
id: Uuid::new_v4(),
|
||||||
name,
|
name,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,7 +34,7 @@ impl From<Plate> for PlateInstance {
|
||||||
fn from(value: Plate) -> Self {
|
fn from(value: Plate) -> Self {
|
||||||
PlateInstance {
|
PlateInstance {
|
||||||
plate: value,
|
plate: value,
|
||||||
id: Uuid::now_v7(),
|
id: Uuid::new_v4(),
|
||||||
name: "New Plate".to_string(),
|
name: "New Plate".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,7 @@ pub struct Transfer {
|
||||||
pub source_id: Uuid,
|
pub source_id: Uuid,
|
||||||
pub dest_id: Uuid,
|
pub dest_id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
#[serde(rename = "id_v7")]
|
id: Uuid,
|
||||||
#[serde(default = "Uuid::now_v7")]
|
|
||||||
pub id: Uuid,
|
|
||||||
pub transfer_region: TransferRegion,
|
pub transfer_region: TransferRegion,
|
||||||
#[serde(default = "default_volume")]
|
#[serde(default = "default_volume")]
|
||||||
pub volume: f32,
|
pub volume: f32,
|
||||||
|
@ -46,7 +44,7 @@ impl Transfer {
|
||||||
source_id: source.get_uuid(),
|
source_id: source.get_uuid(),
|
||||||
dest_id: dest.get_uuid(),
|
dest_id: dest.get_uuid(),
|
||||||
name,
|
name,
|
||||||
id: Uuid::now_v7(),
|
id: Uuid::new_v4(),
|
||||||
transfer_region: tr,
|
transfer_region: tr,
|
||||||
volume: 2.5,
|
volume: 2.5,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::components::transfer_menu::RegionDisplay;
|
|
||||||
|
|
||||||
use super::plate::Plate;
|
use super::plate::Plate;
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
||||||
pub struct CustomRegion {
|
|
||||||
src: Vec<(u8, u8)>,
|
|
||||||
dest: Vec<(u8, u8)>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Eq, Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Region {
|
pub enum Region {
|
||||||
Rect((u8, u8), (u8, u8)),
|
Rect((u8, u8), (u8, u8)),
|
||||||
Point((u8, u8)),
|
Point((u8, u8)),
|
||||||
Custom(CustomRegion),
|
|
||||||
}
|
}
|
||||||
impl Default for Region {
|
impl Default for Region {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
@ -32,24 +23,8 @@ 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 {
|
#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
|
||||||
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 {
|
pub struct TransferRegion {
|
||||||
pub source_plate: Plate,
|
pub source_plate: Plate,
|
||||||
pub source_region: Region, // Even if it is just a point, we don't want corners.
|
pub source_region: Region, // Even if it is just a point, we don't want corners.
|
||||||
|
@ -74,7 +49,7 @@ impl Default for TransferRegion {
|
||||||
|
|
||||||
impl TransferRegion {
|
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);
|
||||||
|
@ -96,8 +71,7 @@ impl TransferRegion {
|
||||||
}
|
}
|
||||||
wells
|
wells
|
||||||
}
|
}
|
||||||
Region::Point(p) => vec![*p],
|
Region::Point(p) => vec![p],
|
||||||
Region::Custom(c) => c.src.clone(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,7 +112,6 @@ impl TransferRegion {
|
||||||
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),
|
||||||
Region::Custom(_) => ((0, 0), (0, 0)),
|
|
||||||
};
|
};
|
||||||
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 +120,7 @@ impl TransferRegion {
|
||||||
// and simple then we *will* have injectivity.
|
// and simple then we *will* have injectivity.
|
||||||
|
|
||||||
// Non-replicate transfers:
|
// Non-replicate transfers:
|
||||||
match &self.dest_region {
|
match self.dest_region {
|
||||||
Region::Point((x, y)) => {
|
Region::Point((x, y)) => {
|
||||||
Box::new(move |(i, j)| {
|
Box::new(move |(i, j)| {
|
||||||
if source_wells.contains(&(i, j)) {
|
if source_wells.contains(&(i, j)) {
|
||||||
|
@ -251,22 +224,6 @@ impl TransferRegion {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Region::Custom(c) => Box::new(move |(i, j)| {
|
|
||||||
let src = c.src.clone();
|
|
||||||
let dest = c.dest.clone();
|
|
||||||
|
|
||||||
let points: Vec<(u8, u8)> = src
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.filter(|(_index, (x, y))| *x == i && *y == j)
|
|
||||||
.map(|(index, _)| dest[index])
|
|
||||||
.collect();
|
|
||||||
if points.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(points)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +257,6 @@ impl TransferRegion {
|
||||||
return Err("Source region is out-of-bounds! (Too wide)");
|
return Err("Source region is out-of-bounds! (Too wide)");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Region::Custom(_) => return Ok(()),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if il_source.0 == 0 || il_dest.1 == 0 {
|
if il_source.0 == 0 || il_dest.1 == 0 {
|
||||||
|
|
Loading…
Reference in New Issue