Merge from import_from_csv feature branch
Of course, there were other features that got tacked on... Squashed commit of the following: commit3ee3bd2dabAuthor: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 19:12:16 2023 -0500 Superior clipboard manipulation Won't work on non-https connections, but actually works... commit08f647cd01Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:50:01 2023 -0500 Utility for copying plates as image commit3456be2e9aAuthor: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 17:46:38 2023 -0500 Change wording in options menu Father suggests that this wording is more clear to the end user. I agree! commit4c79cc0b4dAuthor: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:20:00 2023 -0500 Set default plate format to 96 well commit056688c4ecAuthor: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:12:00 2023 -0500 Implement in_transfer hashes toggle in plates commit4937d4ad28Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:11:00 2023 -0500 Preferences menu and toggle for in_transfer hashes commit0101846b52Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:10:00 2023 -0500 Add preferences struct to main state commitec37887c2fAuthor: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:05:00 2023 -0500 Squashed commit of the following: commit5e1137c460Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:03:00 2023 -0500 Fix: indexing error w.r.t. logarithm argument commit535b14a586Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:02:00 2023 -0500 Space colors evenly, consistently, etc Colors should now: - Not change if new transfers are added - Be evenly spaced throughout the palette - Be persistent across refreshes commit6e08f47955Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:01:00 2023 -0500 Add palette function for ordered ids Given an id and a list of sorted ids, yields a color commit88e838e102Author: Emilia <contact@emiliaallison.com> Date: Fri Dec 29 18:00:00 2023 -0500 Switch to v7 UUIDs from v4 v7 UUIDs are timestamp based and thus we can establish a useful total ordering over them; will base colors on this commit85d4b30d47Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 21:18:10 2023 -0400 Update README.md Updated info about import/export, including the new Import Transfer from CSV feature. commit11a561c1d4Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 20:32:51 2023 -0400 Add text to button commit562dc2adf6Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 20:32:40 2023 -0400 Change to make colors more evenly distributed commit6b09aad289Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 19:27:02 2023 -0400 Implementation 1 commita9e5f05fd9Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 17:18:45 2023 -0400 Hide parts of transfer menu when Custom transfer selected commitdb345bfbb5Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 17:18:08 2023 -0400 delete weird whitespace from Cargo.toml commitedcc3528aaAuthor: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 16:41:58 2023 -0400 First implementation of custom region type commit9a3a10c8b4Author: Emilia <contact@emiliaallison.com> Date: Tue Oct 24 16:21:30 2023 -0400 Transfer region no longer copy
This commit is contained in:
		
							parent
							
								
									0b2704d4ab
								
							
						
					
					
						commit
						a710054a98
					
				| 
						 | 
					@ -34,6 +34,12 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -555,9 +561,11 @@ 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",
 | 
				
			||||||
| 
						 | 
					@ -883,10 +891,11 @@ checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "uuid"
 | 
					name = "uuid"
 | 
				
			||||||
version = "1.3.3"
 | 
					version = "1.6.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2"
 | 
					checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "atomic",
 | 
				
			||||||
 "getrandom",
 | 
					 "getrandom",
 | 
				
			||||||
 "rand",
 | 
					 "rand",
 | 
				
			||||||
 "serde",
 | 
					 "serde",
 | 
				
			||||||
| 
						 | 
					@ -896,9 +905,9 @@ dependencies = [
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[package]]
 | 
					[[package]]
 | 
				
			||||||
name = "uuid-macro-internal"
 | 
					name = "uuid-macro-internal"
 | 
				
			||||||
version = "1.3.3"
 | 
					version = "1.6.1"
 | 
				
			||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
					source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
				
			||||||
checksum = "3f67b459f42af2e6e1ee213cb9da4dbd022d3320788c3fb3e1b893093f1e45da"
 | 
					checksum = "f49e7f3f3db8040a100710a11932239fd30697115e2ba4107080d8252939845e"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 "proc-macro2",
 | 
					 "proc-macro2",
 | 
				
			||||||
 "quote",
 | 
					 "quote",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -11,17 +11,19 @@ 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",
 | 
					 "HtmlAnchorElement", "ReadableStream", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement",
 | 
				
			||||||
 "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.3", features = ["v4", "fast-rng", "macro-diagnostics", "js", "serde"] }
 | 
					uuid = { version = "1.6", features = ["v7", "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"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								README.md
								
								
								
								
							
							
						
						
									
										14
									
								
								README.md
								
								
								
								
							| 
						 | 
					@ -54,6 +54,7 @@ To add a new plate, click the "New Plate" 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.
 | 
				
			||||||
| 
						 | 
					@ -61,20 +62,29 @@ To add a new plate, click the "New Plate" button:
 | 
				
			||||||
 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.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 Currently, it is not possible to import from nor export to a format produced by other similar software.
 | 
					 #### Export as JSON (Saving Your Work)
 | 
				
			||||||
 | 
					 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 select "Import", and finally click "Import from JSON".
 | 
				
			||||||
 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)
 | 
				
			||||||
 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)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
					@ -0,0 +1,25 @@
 | 
				
			||||||
 | 
					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,6 +12,8 @@ div.upper_menu {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	visibility: inherit;
 | 
						visibility: inherit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	div.dropdown {
 | 
						div.dropdown {
 | 
				
			||||||
		margin-right: 2px;
 | 
							margin-right: 2px;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,8 @@
 | 
				
			||||||
        <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,18 +1,27 @@
 | 
				
			||||||
#![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::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url};
 | 
					use web_sys::{
 | 
				
			||||||
 | 
					    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::TransferMenu;
 | 
					use super::transfer_menu::{letters_to_num, RegionDisplay, 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 {
 | 
				
			||||||
| 
						 | 
					@ -40,6 +49,15 @@ 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();
 | 
				
			||||||
| 
						 | 
					@ -100,6 +118,7 @@ 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();
 | 
				
			||||||
| 
						 | 
					@ -177,6 +196,274 @@ 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">
 | 
				
			||||||
| 
						 | 
					@ -190,7 +477,22 @@ 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>
 | 
				
			||||||
                <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 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">{"96"}</option>
 | 
					                <option value="96" selected={true}>{"96"}</option>
 | 
				
			||||||
                <option value="384" selected={true}>{"384"}</option>
 | 
					                <option value="384">{"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,12 +34,21 @@ 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();
 | 
				
			||||||
| 
						 | 
					@ -98,6 +107,11 @@ 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| {
 | 
				
			||||||
| 
						 | 
					@ -114,10 +128,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))}
 | 
					                in_transfer={destination_wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
 | 
				
			||||||
                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_uuid(t.get_uuid()))
 | 
					                    .map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                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())
 | 
				
			||||||
| 
						 | 
					@ -134,7 +148,8 @@ pub fn DestinationPlate(props: &DestinationPlateProps) -> Html {
 | 
				
			||||||
        .collect::<Html>();
 | 
					        .collect::<Html>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    html! {
 | 
					    html! {
 | 
				
			||||||
        <div class={classes!{"dest_plate",
 | 
					        <div ondblclick={screenshot_callback}
 | 
				
			||||||
 | 
					        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,6 +34,7 @@ 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));
 | 
				
			||||||
| 
						 | 
					@ -60,6 +61,14 @@ 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();
 | 
				
			||||||
| 
						 | 
					@ -99,6 +108,10 @@ 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| {
 | 
				
			||||||
| 
						 | 
					@ -118,10 +131,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))}
 | 
					                        in_transfer={source_wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
 | 
				
			||||||
                        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_uuid(t.get_uuid()))
 | 
					                            .map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                        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())
 | 
				
			||||||
| 
						 | 
					@ -139,7 +152,8 @@ pub fn SourcePlate(props: &SourcePlateProps) -> Html {
 | 
				
			||||||
        .collect::<Html>();
 | 
					        .collect::<Html>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    html! {
 | 
					    html! {
 | 
				
			||||||
        <div class={classes!{"source_plate",
 | 
					        <div ondblclick={screenshot_callback}
 | 
				
			||||||
 | 
					        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,6 +2,10 @@
 | 
				
			||||||
// 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],
 | 
				
			||||||
| 
						 | 
					@ -31,9 +35,23 @@ 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] {
 | 
				
			||||||
        log::debug!("{}", t.as_u128() as f64 / (u128::MAX) as f64);
 | 
					    //     // self.get(t.as_u128() as f64 / (u128::MAX) as f64)
 | 
				
			||||||
        self.get(t.as_u128() as f64 / (u128::MAX) as f64)
 | 
					    //     let mut r = SmallRng::seed_from_u64(t.as_u128() as u64);
 | 
				
			||||||
 | 
					    //     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);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -54,4 +72,5 @@ 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,6 +13,17 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -22,6 +33,9 @@ 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,6 +39,9 @@ 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 {
 | 
				
			||||||
| 
						 | 
					@ -192,7 +195,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,
 | 
					                            ct_state.transfer.transfer_region.clone(),
 | 
				
			||||||
                            ct_state.transfer.name.clone(),
 | 
					                            ct_state.transfer.name.clone(),
 | 
				
			||||||
                        );
 | 
					                        );
 | 
				
			||||||
                        main_dispatch.reduce_mut(|state| {
 | 
					                        main_dispatch.reduce_mut(|state| {
 | 
				
			||||||
| 
						 | 
					@ -250,6 +253,8 @@ 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"
 | 
				
			||||||
| 
						 | 
					@ -291,6 +296,7 @@ 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"} />
 | 
				
			||||||
| 
						 | 
					@ -344,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 {
 | 
					impl From<&Region> for RegionDisplay {
 | 
				
			||||||
    fn from(value: &Region) -> Self {
 | 
					    fn from(value: &Region) -> Self {
 | 
				
			||||||
        match *value {
 | 
					        match *value {
 | 
				
			||||||
| 
						 | 
					@ -353,6 +390,7 @@ 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 }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -385,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;
 | 
					    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;
 | 
					        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;
 | 
					use serde::{Serialize, Deserialize};
 | 
				
			||||||
use std::error::Error;
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Serialize, Debug)]
 | 
					#[derive(Serialize, Deserialize, Debug)]
 | 
				
			||||||
struct TransferRecord {
 | 
					pub struct TransferRecord {
 | 
				
			||||||
    #[serde(rename = "Source Plate Barcode")]
 | 
					    #[serde(rename = "Source Plate")]
 | 
				
			||||||
    source_plate: String,
 | 
					    pub source_plate: String,
 | 
				
			||||||
    #[serde(rename = "Source Well")]
 | 
					    #[serde(rename = "Source Well")]
 | 
				
			||||||
    source_well: String,
 | 
					    pub source_well: String,
 | 
				
			||||||
    #[serde(rename = "Destination Plate Barcode")]
 | 
					    #[serde(rename = "Dest Plate")]
 | 
				
			||||||
    destination_plate: String,
 | 
					    pub destination_plate: String,
 | 
				
			||||||
    #[serde(rename = "Destination Well")]
 | 
					    #[serde(rename = "Destination Well")]
 | 
				
			||||||
    destination_well: String,
 | 
					    pub destination_well: String,
 | 
				
			||||||
    #[serde(rename = "Volume")]
 | 
					    #[serde(rename = "Transfer Volume")]
 | 
				
			||||||
    volume: f32,
 | 
					    pub volume: f32,
 | 
				
			||||||
    #[serde(rename = "Concentration")]
 | 
					    #[serde(rename = "Concentration")]
 | 
				
			||||||
    concentration: Option<f32>,
 | 
					    pub 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,6 +5,8 @@ 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,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -16,7 +18,7 @@ impl PlateInstance {
 | 
				
			||||||
                plate_type: sort,
 | 
					                plate_type: sort,
 | 
				
			||||||
                plate_format: format,
 | 
					                plate_format: format,
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            id: Uuid::new_v4(),
 | 
					            id: Uuid::now_v7(),
 | 
				
			||||||
            name,
 | 
					            name,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -34,7 +36,7 @@ impl From<Plate> for PlateInstance {
 | 
				
			||||||
    fn from(value: Plate) -> Self {
 | 
					    fn from(value: Plate) -> Self {
 | 
				
			||||||
        PlateInstance {
 | 
					        PlateInstance {
 | 
				
			||||||
            plate: value,
 | 
					            plate: value,
 | 
				
			||||||
            id: Uuid::new_v4(),
 | 
					            id: Uuid::now_v7(),
 | 
				
			||||||
            name: "New Plate".to_string(),
 | 
					            name: "New Plate".to_string(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,9 @@ 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,
 | 
				
			||||||
    id: Uuid,
 | 
					    #[serde(rename = "id_v7")]
 | 
				
			||||||
 | 
					    #[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,
 | 
				
			||||||
| 
						 | 
					@ -44,7 +46,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::new_v4(),
 | 
					            id: Uuid::now_v7(),
 | 
				
			||||||
            transfer_region: tr,
 | 
					            transfer_region: tr,
 | 
				
			||||||
            volume: 2.5,
 | 
					            volume: 2.5,
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,20 @@
 | 
				
			||||||
use serde::{Deserialize, Serialize};
 | 
					use serde::{Deserialize, Serialize};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::components::transfer_menu::RegionDisplay;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use super::plate::Plate;
 | 
					use super::plate::Plate;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Debug)]
 | 
					#[derive(Clone, 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 {
 | 
				
			||||||
| 
						 | 
					@ -23,8 +32,24 @@ 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());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Debug)]
 | 
					        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 {
 | 
					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.
 | 
				
			||||||
| 
						 | 
					@ -49,7 +74,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);
 | 
				
			||||||
| 
						 | 
					@ -71,7 +96,8 @@ impl TransferRegion {
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                wells
 | 
					                wells
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            Region::Point(p) => vec![p],
 | 
					            Region::Point(p) => vec![*p],
 | 
				
			||||||
 | 
					            Region::Custom(c) => c.src.clone(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -112,6 +138,7 @@ 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,
 | 
				
			||||||
| 
						 | 
					@ -120,7 +147,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)) {
 | 
				
			||||||
| 
						 | 
					@ -224,6 +251,22 @@ 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)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -257,6 +300,7 @@ 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