Change plate format
This commit is contained in:
		
							parent
							
								
									9db0d4aa19
								
							
						
					
					
						commit
						b79c377793
					
				| 
						 | 
					@ -1,4 +1,3 @@
 | 
				
			||||||
// use crate::components::states::MainState;
 | 
					 | 
				
			||||||
use crate::transfer::Transfer;
 | 
					use crate::transfer::Transfer;
 | 
				
			||||||
use crate::util::*;
 | 
					use crate::util::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,7 +30,7 @@ pub struct TransferRecord {
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn volume_default() -> f32 {
 | 
					pub fn volume_default() -> f32 {
 | 
				
			||||||
    1f32
 | 
					    Transfer::default().volume
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn transfer_to_records(
 | 
					pub fn transfer_to_records(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,6 +60,23 @@ impl std::fmt::Display for PlateFormat {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					impl TryFrom<&str> for PlateFormat {
 | 
				
			||||||
 | 
					    type Error = ();
 | 
				
			||||||
 | 
					    fn try_from(value: &str) -> Result<Self, Self::Error> {
 | 
				
			||||||
 | 
					        let lower = value.to_lowercase();
 | 
				
			||||||
 | 
					        match lower.trim() {
 | 
				
			||||||
 | 
					            "w6" | "6" => Ok(PlateFormat::W6),
 | 
				
			||||||
 | 
					            "w12" | "12" => Ok(PlateFormat::W12),
 | 
				
			||||||
 | 
					            "w24" | "24" => Ok(PlateFormat::W24),
 | 
				
			||||||
 | 
					            "w48" | "48" => Ok(PlateFormat::W48),
 | 
				
			||||||
 | 
					            "w96" | "96" => Ok(PlateFormat::W96),
 | 
				
			||||||
 | 
					            "w384" | "384" => Ok(PlateFormat::W384),
 | 
				
			||||||
 | 
					            "w1536" | "1536" => Ok(PlateFormat::W1536),
 | 
				
			||||||
 | 
					            "w3456" | "3456" => Ok(PlateFormat::W3456),
 | 
				
			||||||
 | 
					            _ => Err(())
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl PlateFormat {
 | 
					impl PlateFormat {
 | 
				
			||||||
    pub fn size(&self) -> (u8, u8) {
 | 
					    pub fn size(&self) -> (u8, u8) {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -30,6 +30,10 @@ impl PlateInstance {
 | 
				
			||||||
    pub fn change_name(&mut self, new_name: String) {
 | 
					    pub fn change_name(&mut self, new_name: String) {
 | 
				
			||||||
        self.name = new_name;
 | 
					        self.name = new_name;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn change_format(&mut self, new_format: &PlateFormat) {
 | 
				
			||||||
 | 
					        self.plate.plate_format = *new_format;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl From<Plate> for PlateInstance {
 | 
					impl From<Plate> for PlateInstance {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ impl Default for Transfer {
 | 
				
			||||||
            name: "New Transfer".to_string(),
 | 
					            name: "New Transfer".to_string(),
 | 
				
			||||||
            id: Default::default(),
 | 
					            id: Default::default(),
 | 
				
			||||||
            transfer_region: Default::default(),
 | 
					            transfer_region: Default::default(),
 | 
				
			||||||
            volume: 2.5f32,
 | 
					            volume: default_volume(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,2 @@
 | 
				
			||||||
 | 
					/target
 | 
				
			||||||
 | 
					/dist
 | 
				
			||||||
| 
						 | 
					@ -13,7 +13,7 @@ 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", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement",
 | 
				
			||||||
 "FileReader"] }
 | 
					 "FileReader", "HtmlCollection"] }
 | 
				
			||||||
js-sys = "0.3"
 | 
					js-sys = "0.3"
 | 
				
			||||||
log = "0.4"
 | 
					log = "0.4"
 | 
				
			||||||
wasm-logger = "0.2"
 | 
					wasm-logger = "0.2"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
					@ -1,608 +0,0 @@
 | 
				
			||||||
@charset "UTF-8";
 | 
					 | 
				
			||||||
/* ----------------- */
 | 
					 | 
				
			||||||
/* Custom Properties */
 | 
					 | 
				
			||||||
/* ----------------- */
 | 
					 | 
				
			||||||
/*
 | 
					 | 
				
			||||||
  This values are to be overridden
 | 
					 | 
				
			||||||
  after being injected into
 | 
					 | 
				
			||||||
  the global scope.
 | 
					 | 
				
			||||||
*/
 | 
					 | 
				
			||||||
/* colors */
 | 
					 | 
				
			||||||
/* Black */
 | 
					 | 
				
			||||||
/* Gray */
 | 
					 | 
				
			||||||
/* White */
 | 
					 | 
				
			||||||
/* font */
 | 
					 | 
				
			||||||
/* Sizes divided by 16 so values given in px */
 | 
					 | 
				
			||||||
/* --------------- */
 | 
					 | 
				
			||||||
/* Utility Classes */
 | 
					 | 
				
			||||||
/* --------------- */
 | 
					 | 
				
			||||||
/* Layouts */
 | 
					 | 
				
			||||||
.container {
 | 
					 | 
				
			||||||
  padding-inline: 4rem;
 | 
					 | 
				
			||||||
  margin-inline: auto;
 | 
					 | 
				
			||||||
  max-width: 80rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.flex {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  gap: 2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.column {
 | 
					 | 
				
			||||||
  justify-content: center;
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  align-content: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.row {
 | 
					 | 
				
			||||||
  flex-direction: row;
 | 
					 | 
				
			||||||
  flex-wrap: wrap;
 | 
					 | 
				
			||||||
  justify-content: space-evenly;
 | 
					 | 
				
			||||||
  align-content: baseline;
 | 
					 | 
				
			||||||
  max-width: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.two-columns {
 | 
					 | 
				
			||||||
  display: grid;
 | 
					 | 
				
			||||||
  /* Will shrink to one column, never exceed two!
 | 
					 | 
				
			||||||
   * The `max` in `minmax` asks that the columns
 | 
					 | 
				
			||||||
   * be no smaller */
 | 
					 | 
				
			||||||
  grid-template-columns: repeat(auto-fit, minmax(max(30rem, 40%), 1fr));
 | 
					 | 
				
			||||||
  column-gap: 1rem;
 | 
					 | 
				
			||||||
  justify-content: space-evenly;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.lock-bottom {
 | 
					 | 
				
			||||||
  position: fixed;
 | 
					 | 
				
			||||||
  bottom: 0%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Other */
 | 
					 | 
				
			||||||
.hr::after { /* Add fake hr after header */
 | 
					 | 
				
			||||||
  content: "";
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  display: block;
 | 
					 | 
				
			||||||
  clear: both;
 | 
					 | 
				
			||||||
  width: 100%;
 | 
					 | 
				
			||||||
  height: 0.15rem;
 | 
					 | 
				
			||||||
  background-color: black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Color Classes */
 | 
					 | 
				
			||||||
.bg-dark {
 | 
					 | 
				
			||||||
  background-color: #504d49;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.bg-light {
 | 
					 | 
				
			||||||
  background-color: hsl(190, 80%, 30%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.bg-white {
 | 
					 | 
				
			||||||
  background-color: hsl(30, 5%, 90%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.text-dark {
 | 
					 | 
				
			||||||
  color: #504d49;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.text-light {
 | 
					 | 
				
			||||||
  color: hsl(190, 80%, 30%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.text-white {
 | 
					 | 
				
			||||||
  color: hsl(30, 5%, 90%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Font Classes */
 | 
					 | 
				
			||||||
.fs-900 {
 | 
					 | 
				
			||||||
  font-size: 6.25rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-800 {
 | 
					 | 
				
			||||||
  font-size: 4.6875rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-700 {
 | 
					 | 
				
			||||||
  font-size: 3.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-600 {
 | 
					 | 
				
			||||||
  font-size: 2rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-500 {
 | 
					 | 
				
			||||||
  font-size: 1.75rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-400 {
 | 
					 | 
				
			||||||
  font-size: 1.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-300 {
 | 
					 | 
				
			||||||
  font-size: 1.125rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-200 {
 | 
					 | 
				
			||||||
  font-size: 1rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.ff-serif {
 | 
					 | 
				
			||||||
  font-family: "Inconsolata", monospace;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.ff-sans-cond {
 | 
					 | 
				
			||||||
  font-family: sans-serif;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.ff-sans {
 | 
					 | 
				
			||||||
  font-family: "Jost", sans-serif;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.uppercase {
 | 
					 | 
				
			||||||
  text-transform: uppercase;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.lowercase {
 | 
					 | 
				
			||||||
  text-transform: lowercase;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* Semantic Tags and Their Classes */
 | 
					 | 
				
			||||||
header {
 | 
					 | 
				
			||||||
  margin-bottom: 3vh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
section:not(:last-of-type) {
 | 
					 | 
				
			||||||
  margin-bottom: 3vh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
footer {
 | 
					 | 
				
			||||||
  margin-top: 3vh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/* ----- */
 | 
					 | 
				
			||||||
/* Reset */
 | 
					 | 
				
			||||||
/* ----- */
 | 
					 | 
				
			||||||
*,
 | 
					 | 
				
			||||||
*::before,
 | 
					 | 
				
			||||||
*::after {
 | 
					 | 
				
			||||||
  box-sizing: border-box;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body, h1, h2, h3, h4, h5, h6, p {
 | 
					 | 
				
			||||||
  margin: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h1, h2, h3, h4, h5, h6, p {
 | 
					 | 
				
			||||||
  font-weight: 400;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
h1, h2, h3 {
 | 
					 | 
				
			||||||
  line-height: 1.1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
body {
 | 
					 | 
				
			||||||
  font-family: "Jost", sans-serif;
 | 
					 | 
				
			||||||
  font-size: 1.5rem;
 | 
					 | 
				
			||||||
  color: #504d49;
 | 
					 | 
				
			||||||
  background-color: hsl(30, 5%, 90%);
 | 
					 | 
				
			||||||
  line-height: 1.5;
 | 
					 | 
				
			||||||
  min-height: 100vh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
main {
 | 
					 | 
				
			||||||
  margin-left: 1vw;
 | 
					 | 
				
			||||||
  margin-top: 1vh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
main * {
 | 
					 | 
				
			||||||
  z-index: 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
footer {
 | 
					 | 
				
			||||||
  z-index: 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
img, picture {
 | 
					 | 
				
			||||||
  max-width: 100%;
 | 
					 | 
				
			||||||
  display: block;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
input, button, textarea, select {
 | 
					 | 
				
			||||||
  font: inherit;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@media (prefers-reduced-motion: reduce) {
 | 
					 | 
				
			||||||
  *,
 | 
					 | 
				
			||||||
  *::before,
 | 
					 | 
				
			||||||
  *::after {
 | 
					 | 
				
			||||||
    animation-duration: 0.01ms !important;
 | 
					 | 
				
			||||||
    animation-iteration-count: 1 !important;
 | 
					 | 
				
			||||||
    transition-duration: 0.01ms !important;
 | 
					 | 
				
			||||||
    scroll-behaviour: auto !important;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
/* -------------------- */
 | 
					 | 
				
			||||||
/* Non-Reusable Classes */
 | 
					 | 
				
			||||||
/* -------------------- */
 | 
					 | 
				
			||||||
/* meant for these pages
 | 
					 | 
				
			||||||
 * only, not to be used
 | 
					 | 
				
			||||||
 * in practice          */
 | 
					 | 
				
			||||||
.colors--block {
 | 
					 | 
				
			||||||
  padding: 3rem 1rem 1rem;
 | 
					 | 
				
			||||||
  border: 1px solid black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@font-face {
 | 
					 | 
				
			||||||
  font-family: "Inconsolata";
 | 
					 | 
				
			||||||
  src: url("/fonts/Inconsolata.ttf");
 | 
					 | 
				
			||||||
  font-display: swap;
 | 
					 | 
				
			||||||
  font-variation-settings: "wdth" 85;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@font-face {
 | 
					 | 
				
			||||||
  font-family: "Jost";
 | 
					 | 
				
			||||||
  src: url("/fonts/Jost.ttf");
 | 
					 | 
				
			||||||
  font-display: swap;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
body {
 | 
					 | 
				
			||||||
  overflow: hidden;
 | 
					 | 
				
			||||||
  height: 100%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.main_container {
 | 
					 | 
				
			||||||
  height: 97vh;
 | 
					 | 
				
			||||||
  width: 98vw;
 | 
					 | 
				
			||||||
  margin-top: 2.5vh;
 | 
					 | 
				
			||||||
  margin-left: 1vw;
 | 
					 | 
				
			||||||
  display: grid;
 | 
					 | 
				
			||||||
  grid-template-columns: [left] minmax(min-content, 1fr) [right] 2fr;
 | 
					 | 
				
			||||||
  grid-template-rows: [upper] 2fr [lower] 1fr;
 | 
					 | 
				
			||||||
  column-gap: 1vw;
 | 
					 | 
				
			||||||
  row-gap: 1vh;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.dialog {
 | 
					 | 
				
			||||||
  padding: 1em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.dialog::backdrop {
 | 
					 | 
				
			||||||
  background: rgba(0, 125, 255, 0.3);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.plate_container {
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  justify-content: space-evenly;
 | 
					 | 
				
			||||||
  align-items: center;
 | 
					 | 
				
			||||||
  border: 2px solid #504d49;
 | 
					 | 
				
			||||||
  grid-column: right/right;
 | 
					 | 
				
			||||||
  grid-row: upper/3;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.plate_container h2 {
 | 
					 | 
				
			||||||
  margin-bottom: 1%;
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.plate_container > div {
 | 
					 | 
				
			||||||
  display: grid;
 | 
					 | 
				
			||||||
  grid-template-rows: auto auto;
 | 
					 | 
				
			||||||
  grid-template-rows: auto auto;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.plate_container > div > h2:nth-of-type(1) {
 | 
					 | 
				
			||||||
  grid-column: 2;
 | 
					 | 
				
			||||||
  grid-row: 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.plate_container > div > h2:nth-of-type(2) {
 | 
					 | 
				
			||||||
  grid-column: 1;
 | 
					 | 
				
			||||||
  grid-row: 2;
 | 
					 | 
				
			||||||
  writing-mode: vertical-rl;
 | 
					 | 
				
			||||||
  transform: rotate(-180deg);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.plate_container > div > div {
 | 
					 | 
				
			||||||
  grid-column: 2;
 | 
					 | 
				
			||||||
  grid-row: 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.source_plate, div.dest_plate {
 | 
					 | 
				
			||||||
  padding: 3px 3px 3px 3px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.source_plate {
 | 
					 | 
				
			||||||
  border: 2px solid blue;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.dest_plate {
 | 
					 | 
				
			||||||
  border: 2px solid red;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
table, tr, td {
 | 
					 | 
				
			||||||
  user-select: none; /* Prevents dragging issue */
 | 
					 | 
				
			||||||
  border-spacing: 0px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
th {
 | 
					 | 
				
			||||||
  font-family: monospace;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
td.plate_cell {
 | 
					 | 
				
			||||||
  background: none;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.plate_cell_inner {
 | 
					 | 
				
			||||||
  aspect-ratio: 1/1;
 | 
					 | 
				
			||||||
  height: 90%;
 | 
					 | 
				
			||||||
  border-radius: 50%;
 | 
					 | 
				
			||||||
  border: 1px solid black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
td.plate_cell:hover div.plate_cell_inner {
 | 
					 | 
				
			||||||
  background: black !important;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
td.plate_cell.in_transfer div.plate_cell_inner::after {
 | 
					 | 
				
			||||||
  content: "";
 | 
					 | 
				
			||||||
  width: 100%;
 | 
					 | 
				
			||||||
  height: 100%;
 | 
					 | 
				
			||||||
  display: block;
 | 
					 | 
				
			||||||
  border-radius: 50%;
 | 
					 | 
				
			||||||
  background-image: repeating-linear-gradient(45deg, rgba(0, 0, 0, 0.8), rgba(0, 0, 0, 0.8) 2px, transparent 2px, transparent 5px);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
td.current_select div.plate_cell_inner {
 | 
					 | 
				
			||||||
  border: 3px solid black;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.W1536 th {
 | 
					 | 
				
			||||||
  font-size: 0.9rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.W3456 th {
 | 
					 | 
				
			||||||
  font-size: 0.9rem;
 | 
					 | 
				
			||||||
  line-height: 0px;
 | 
					 | 
				
			||||||
  padding-bottom: 0.4rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.tree {
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
  grid-column: left/left;
 | 
					 | 
				
			||||||
  grid-row: upper/upper;
 | 
					 | 
				
			||||||
  width: 100%;
 | 
					 | 
				
			||||||
  height: 100%;
 | 
					 | 
				
			||||||
  border: 2px solid #504d49;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.tree h3 {
 | 
					 | 
				
			||||||
  margin-left: 0.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.tree div#controls {
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  bottom: 2%;
 | 
					 | 
				
			||||||
  right: 2%;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.tree ul {
 | 
					 | 
				
			||||||
  width: 80%;
 | 
					 | 
				
			||||||
  margin-left: 10%;
 | 
					 | 
				
			||||||
  padding: 0;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  align-items: stretch;
 | 
					 | 
				
			||||||
  overflow: scroll;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.tree li {
 | 
					 | 
				
			||||||
  display: inline;
 | 
					 | 
				
			||||||
  margin-left: 0;
 | 
					 | 
				
			||||||
  margin-bottom: 0.4rem;
 | 
					 | 
				
			||||||
  border: 2px solid transparent;
 | 
					 | 
				
			||||||
  user-select: none;
 | 
					 | 
				
			||||||
  list-style: none;
 | 
					 | 
				
			||||||
  line-height: 1em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.tree li:hover {
 | 
					 | 
				
			||||||
  background: rgba(15, 117, 138, 0.08);
 | 
					 | 
				
			||||||
  border: 2px solid rgba(15, 117, 138, 0.3);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.tree li.selected {
 | 
					 | 
				
			||||||
  background: rgba(15, 117, 138, 0.2);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.transfer_menu {
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
  width: 100%;
 | 
					 | 
				
			||||||
  height: 100%;
 | 
					 | 
				
			||||||
  grid-column: left/left;
 | 
					 | 
				
			||||||
  grid-row: lower/lower;
 | 
					 | 
				
			||||||
  border: 2px solid #504d49;
 | 
					 | 
				
			||||||
  padding-left: 0.5rem;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.transfer_menu form {
 | 
					 | 
				
			||||||
  padding-top: 3%;
 | 
					 | 
				
			||||||
  padding-bottom: 1%;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  justify-content: flex-start;
 | 
					 | 
				
			||||||
  align-items: flex-start;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.transfer_menu form label {
 | 
					 | 
				
			||||||
  display: inline;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.transfer_menu form label * {
 | 
					 | 
				
			||||||
  display: inline;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.transfer_menu input:invalid {
 | 
					 | 
				
			||||||
  background-color: #faa;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.transfer_menu div#controls {
 | 
					 | 
				
			||||||
  align-self: flex-end;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.transfer_menu div#controls input {
 | 
					 | 
				
			||||||
  padding: 2px 3px 2px 3px;
 | 
					 | 
				
			||||||
  margin-left: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
input {
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
  margin-left: 0.5em;
 | 
					 | 
				
			||||||
  margin-right: 0.5em;
 | 
					 | 
				
			||||||
  margin-top: 1%;
 | 
					 | 
				
			||||||
  margin-bottom: 1%;
 | 
					 | 
				
			||||||
  line-height: 1em;
 | 
					 | 
				
			||||||
  padding: 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
input[type=text] {
 | 
					 | 
				
			||||||
  width: 4em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
input[name=name] {
 | 
					 | 
				
			||||||
  width: 6em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
input[type=number] {
 | 
					 | 
				
			||||||
  width: 2em;
 | 
					 | 
				
			||||||
  -webkit-appearance: none;
 | 
					 | 
				
			||||||
  -moz-appearance: textfield;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
input.volume_input {
 | 
					 | 
				
			||||||
  width: 4em;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
div.upper_menu {
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  top: 0px;
 | 
					 | 
				
			||||||
  left: 0px;
 | 
					 | 
				
			||||||
  height: min(2.5vh, 25px);
 | 
					 | 
				
			||||||
  padding-left: 1vw;
 | 
					 | 
				
			||||||
  visibility: inherit;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown {
 | 
					 | 
				
			||||||
  margin-right: 2px;
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown button {
 | 
					 | 
				
			||||||
  vertical-align: top;
 | 
					 | 
				
			||||||
  border: none;
 | 
					 | 
				
			||||||
  padding: 0px 0.4em 0px 0.4em;
 | 
					 | 
				
			||||||
  margin: 0;
 | 
					 | 
				
			||||||
  cursor: pointer;
 | 
					 | 
				
			||||||
  font-size: calc(min(2.5vh, 25px) * 0.7);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown * {
 | 
					 | 
				
			||||||
  visibility: hidden;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown > *:first-child {
 | 
					 | 
				
			||||||
  outline: 1px solid #504d49;
 | 
					 | 
				
			||||||
  visibility: visible;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown:hover {
 | 
					 | 
				
			||||||
  outline: 2px solid #504d49;
 | 
					 | 
				
			||||||
  z-index: 2;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown:hover * {
 | 
					 | 
				
			||||||
  visibility: visible;
 | 
					 | 
				
			||||||
  outline: 1px solid #504d49;
 | 
					 | 
				
			||||||
  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.4);
 | 
					 | 
				
			||||||
  z-index: 1;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown-sub {
 | 
					 | 
				
			||||||
  position: relative;
 | 
					 | 
				
			||||||
  height: min(2.5vh, 25px);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown-sub * {
 | 
					 | 
				
			||||||
  visibility: hidden;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown-sub div {
 | 
					 | 
				
			||||||
  display: none;
 | 
					 | 
				
			||||||
  visibility: hidden;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown-sub > *:first-child {
 | 
					 | 
				
			||||||
  visibility: inherit;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown-sub:hover {
 | 
					 | 
				
			||||||
  visibility: visible;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
div.upper_menu div.dropdown-sub:hover div {
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  left: 100%;
 | 
					 | 
				
			||||||
  top: 0;
 | 
					 | 
				
			||||||
  visibility: hidden;
 | 
					 | 
				
			||||||
  display: flex;
 | 
					 | 
				
			||||||
  flex-direction: column;
 | 
					 | 
				
			||||||
  width: max-content;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dialog {
 | 
					 | 
				
			||||||
  border: 3px solid #504d49;
 | 
					 | 
				
			||||||
  border-radius: 2%;
 | 
					 | 
				
			||||||
  color: #504d49;
 | 
					 | 
				
			||||||
  background: hsl(30, 5%, 90%);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
dialog > form[method=dialog] {
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  top: 0;
 | 
					 | 
				
			||||||
  right: 0;
 | 
					 | 
				
			||||||
  line-height: 0px;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
dialog > form[method=dialog] button {
 | 
					 | 
				
			||||||
  padding-top: 6px;
 | 
					 | 
				
			||||||
  padding-left: 3px;
 | 
					 | 
				
			||||||
  font-size: 150%;
 | 
					 | 
				
			||||||
  line-height: 0px;
 | 
					 | 
				
			||||||
  color: #504d49;
 | 
					 | 
				
			||||||
  border: 0;
 | 
					 | 
				
			||||||
  background: transparent;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
dialog > form[method=dialog] button::before {
 | 
					 | 
				
			||||||
  text-align: center;
 | 
					 | 
				
			||||||
  vertical-align: middle;
 | 
					 | 
				
			||||||
  content: "×";
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
dialog > form[method=dialog] button:hover {
 | 
					 | 
				
			||||||
  color: black;
 | 
					 | 
				
			||||||
  transition: color 0.1s;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.close_button {
 | 
					 | 
				
			||||||
  color: red;
 | 
					 | 
				
			||||||
  position: absolute;
 | 
					 | 
				
			||||||
  top: 5%;
 | 
					 | 
				
			||||||
  right: 2%;
 | 
					 | 
				
			||||||
  background: rgba(0, 0, 0, 0.1);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
.close_button:hover {
 | 
					 | 
				
			||||||
  color: rgb(0, 255, 255);
 | 
					 | 
				
			||||||
  background: rgba(0, 0, 0, 0.8);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-900 {
 | 
					 | 
				
			||||||
  font-size: 6.25rem;
 | 
					 | 
				
			||||||
  font-weight: 400;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-800 {
 | 
					 | 
				
			||||||
  font-size: 4.6875rem;
 | 
					 | 
				
			||||||
  font-weight: 300;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-700 {
 | 
					 | 
				
			||||||
  font-size: 3.5rem;
 | 
					 | 
				
			||||||
  font-weight: 250;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-600 {
 | 
					 | 
				
			||||||
  font-size: 2rem;
 | 
					 | 
				
			||||||
  font-weight: 300;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-500 {
 | 
					 | 
				
			||||||
  font-size: 1.75rem;
 | 
					 | 
				
			||||||
  font-weight: 200;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.fs-400 {
 | 
					 | 
				
			||||||
  font-size: 1.5rem;
 | 
					 | 
				
			||||||
  font-weight: 200;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
html {
 | 
					 | 
				
			||||||
  font-size: 1vmin;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,146 +0,0 @@
 | 
				
			||||||
<!DOCTYPE html><html><head>
 | 
					 | 
				
			||||||
        <meta charset="utf-8">
 | 
					 | 
				
			||||||
        <link rel="stylesheet" href="/index-136b7d4afc3986e5.css" integrity="sha384-K1xb_hlSB1PuzRgXufX4uYfJCrimViJUZLwHuy2nqRmgR8LHKEVguY8A1ceunwFf">
 | 
					 | 
				
			||||||
        
 | 
					 | 
				
			||||||
        <script src="/screenshot_utility-18639e2bdd58e7f7.js" integrity="sha384-rRHgJ-Dx271W2uHdkpxy2XEPlo_9wIonkalIbWq_EVn9y0FO8L9MF96Oz57FVXeu"></script>
 | 
					 | 
				
			||||||
        <script src="/html2canvas-bafc21265de2354d.js" integrity="sha384-q0KsXOmCeCEoVzvj91Xfq-Z9A_MA9_K7PhfVXTTZHQiZ-snp_JlaXl08LNAT7mrM"></script>
 | 
					 | 
				
			||||||
        <title>Plate Tool</title>
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
<link rel="preload" href="/plate-tool-web-6c71e2a649203e02_bg.wasm" as="fetch" type="application/wasm" crossorigin="anonymous" integrity="sha384-ZLrGyVAnMTOTUFsd2bIX3oxXsct5Ze-_BbG7YdSAxS-Q8hyJIZNfPBb5LJ6rpPrE">
 | 
					 | 
				
			||||||
<link rel="modulepreload" href="/plate-tool-web-6c71e2a649203e02.js" crossorigin="anonymous" integrity="sha384-o-MTA3ga5eJ_8m7WwV3axAGC2KE2rSO345Jk7g7PonnYbt0H_wRHpLIdWZUB1eBv"></head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script type="module">
 | 
					 | 
				
			||||||
import init, * as bindings from '/plate-tool-web-6c71e2a649203e02.js';
 | 
					 | 
				
			||||||
init('/plate-tool-web-6c71e2a649203e02_bg.wasm');
 | 
					 | 
				
			||||||
window.wasmBindings = bindings;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
</script><script>"use strict";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
(function () {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const address = '{{__TRUNK_ADDRESS__}}';
 | 
					 | 
				
			||||||
    let protocol = '';
 | 
					 | 
				
			||||||
    protocol =
 | 
					 | 
				
			||||||
        protocol
 | 
					 | 
				
			||||||
            ? protocol
 | 
					 | 
				
			||||||
            : window.location.protocol === 'https:'
 | 
					 | 
				
			||||||
                ? 'wss'
 | 
					 | 
				
			||||||
                : 'ws';
 | 
					 | 
				
			||||||
    const url = protocol + '://' + address + '/_trunk/ws';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Overlay {
 | 
					 | 
				
			||||||
        constructor() {
 | 
					 | 
				
			||||||
            // create an overlay
 | 
					 | 
				
			||||||
            this._overlay = document.createElement("div");
 | 
					 | 
				
			||||||
            const style = this._overlay.style;
 | 
					 | 
				
			||||||
            style.height = "100vh";
 | 
					 | 
				
			||||||
            style.width = "100vw";
 | 
					 | 
				
			||||||
            style.position = "fixed";
 | 
					 | 
				
			||||||
            style.top = "0";
 | 
					 | 
				
			||||||
            style.left = "0";
 | 
					 | 
				
			||||||
            style.backgroundColor = "rgba(222, 222, 222, 0.5)";
 | 
					 | 
				
			||||||
            style.fontFamily = "sans-serif";
 | 
					 | 
				
			||||||
            // not sure that's the right approach
 | 
					 | 
				
			||||||
            style.zIndex = "1000000";
 | 
					 | 
				
			||||||
            style.backdropFilter = "blur(1rem)";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const container = document.createElement("div");
 | 
					 | 
				
			||||||
            // center it
 | 
					 | 
				
			||||||
            container.style.position = "absolute";
 | 
					 | 
				
			||||||
            container.style.top = "30%";
 | 
					 | 
				
			||||||
            container.style.left = "15%";
 | 
					 | 
				
			||||||
            container.style.maxWidth = "85%";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this._title = document.createElement("div");
 | 
					 | 
				
			||||||
            this._title.innerText = "Build failure";
 | 
					 | 
				
			||||||
            this._title.style.paddingBottom = "2rem";
 | 
					 | 
				
			||||||
            this._title.style.fontSize = "2.5rem";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this._message = document.createElement("div");
 | 
					 | 
				
			||||||
            this._message.style.whiteSpace = "pre-wrap";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const icon= document.createElement("div");
 | 
					 | 
				
			||||||
            icon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" fill="#dc3545" viewBox="0 0 16 16"><path d="M8.982 1.566a1.13 1.13 0 0 0-1.96 0L.165 13.233c-.457.778.091 1.767.98 1.767h13.713c.889 0 1.438-.99.98-1.767L8.982 1.566zM8 5c.535 0 .954.462.9.995l-.35 3.507a.552.552 0 0 1-1.1 0L7.1 5.995A.905.905 0 0 1 8 5zm.002 6a1 1 0 1 1 0 2 1 1 0 0 1 0-2z"/></svg>';
 | 
					 | 
				
			||||||
            this._title.prepend(icon);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            container.append(this._title, this._message);
 | 
					 | 
				
			||||||
            this._overlay.append(container);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            this._inject();
 | 
					 | 
				
			||||||
            window.setInterval(() => {
 | 
					 | 
				
			||||||
                this._inject();
 | 
					 | 
				
			||||||
            }, 250);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        set reason(reason) {
 | 
					 | 
				
			||||||
            this._message.textContent = reason;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        _inject() {
 | 
					 | 
				
			||||||
            if (!this._overlay.isConnected) {
 | 
					 | 
				
			||||||
                // prepend it
 | 
					 | 
				
			||||||
                document.body?.prepend(this._overlay);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    class Client {
 | 
					 | 
				
			||||||
        constructor(url) {
 | 
					 | 
				
			||||||
            this.url = url;
 | 
					 | 
				
			||||||
            this.poll_interval = 5000;
 | 
					 | 
				
			||||||
            this._overlay = null;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        start() {
 | 
					 | 
				
			||||||
            const ws = new WebSocket(this.url);
 | 
					 | 
				
			||||||
            ws.onmessage = (ev) => {
 | 
					 | 
				
			||||||
                const msg = JSON.parse(ev.data);
 | 
					 | 
				
			||||||
                switch (msg.type) {
 | 
					 | 
				
			||||||
                    case "reload":
 | 
					 | 
				
			||||||
                        this.reload();
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                    case "buildFailure":
 | 
					 | 
				
			||||||
                        this.buildFailure(msg.data)
 | 
					 | 
				
			||||||
                        break;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            ws.onclose = this.onclose;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        onclose() {
 | 
					 | 
				
			||||||
            window.setTimeout(
 | 
					 | 
				
			||||||
                () => {
 | 
					 | 
				
			||||||
                    // when we successfully reconnect, we'll force a
 | 
					 | 
				
			||||||
                    // reload (since we presumably lost connection to
 | 
					 | 
				
			||||||
                    // trunk due to it being killed, so it will have
 | 
					 | 
				
			||||||
                    // rebuilt on restart)
 | 
					 | 
				
			||||||
                    const ws = new WebSocket(this.url);
 | 
					 | 
				
			||||||
                    ws.onopen = () => window.location.reload();
 | 
					 | 
				
			||||||
                    ws.onclose = this.onclose;
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                this.poll_interval);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        reload() {
 | 
					 | 
				
			||||||
            window.location.reload();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        buildFailure({reason}) {
 | 
					 | 
				
			||||||
            // also log the console
 | 
					 | 
				
			||||||
            console.error("Build failed:", reason);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            console.debug("Overlay", this._overlay);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (!this._overlay) {
 | 
					 | 
				
			||||||
                this._overlay = new Overlay();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            this._overlay.reason = reason;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    new Client(url).start();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
})()
 | 
					 | 
				
			||||||
</script></body></html>
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							| 
						 | 
					@ -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);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,12 @@
 | 
				
			||||||
use std::rc::Rc;
 | 
					use std::rc::Rc;
 | 
				
			||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
use wasm_bindgen::JsCast;
 | 
					use wasm_bindgen::JsCast;
 | 
				
			||||||
use web_sys::{EventTarget, HtmlElement};
 | 
					use web_sys::{EventTarget, HtmlElement, HtmlInputElement, HtmlSelectElement, HtmlOptionElement};
 | 
				
			||||||
use yew::prelude::*;
 | 
					use yew::prelude::*;
 | 
				
			||||||
use yewdux::prelude::*;
 | 
					use yewdux::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::components::states::{CurrentTransfer, MainState};
 | 
					use crate::components::states::{CurrentTransfer, MainState};
 | 
				
			||||||
use plate_tool_lib::transfer_region::Region;
 | 
					use plate_tool_lib::{transfer_region::Region, plate::PlateFormat};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type NoParamsCallback = Box<dyn Fn(())>;
 | 
					type NoParamsCallback = Box<dyn Fn(())>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,38 @@ pub fn plate_info_delete_callback(
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn rename_onchange(id: Uuid, main_dispatch: Dispatch<MainState>) -> Callback<Event> {
 | 
				
			||||||
 | 
					    Callback::from(move |e: Event| {
 | 
				
			||||||
 | 
					        log::debug!("Changed name");
 | 
				
			||||||
 | 
					        let input = e
 | 
				
			||||||
 | 
					            .target()
 | 
				
			||||||
 | 
					            .expect("Event must have target")
 | 
				
			||||||
 | 
					            .dyn_into::<HtmlInputElement>()
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					        main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value()))
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub fn format_onchange(id: Uuid, main_dispatch: Dispatch<MainState>) -> Callback<Event> {
 | 
				
			||||||
 | 
					    Callback::from(move |e: Event| {
 | 
				
			||||||
 | 
					        log::debug!("Changing plate format");
 | 
				
			||||||
 | 
					        let new_format: Option<PlateFormat> = e.target()
 | 
				
			||||||
 | 
					            .expect("Event must have target")
 | 
				
			||||||
 | 
					            .dyn_into::<HtmlSelectElement>()
 | 
				
			||||||
 | 
					            .unwrap()
 | 
				
			||||||
 | 
					            .selected_options()
 | 
				
			||||||
 | 
					            .get_with_index(0)
 | 
				
			||||||
 | 
					            .map(|el| {
 | 
				
			||||||
 | 
					                el.dyn_into::<HtmlOptionElement>().unwrap()
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .map(|opt_el| opt_el.value())
 | 
				
			||||||
 | 
					            .and_then(|value| PlateFormat::try_from(value.as_str()).ok());
 | 
				
			||||||
 | 
					        if let Some(format) = new_format {
 | 
				
			||||||
 | 
					            main_dispatch.reduce_mut(|state| state.change_format(id, &format));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub fn source_plate_select_callback(
 | 
					pub fn source_plate_select_callback(
 | 
				
			||||||
    main_dispatch: Dispatch<MainState>,
 | 
					    main_dispatch: Dispatch<MainState>,
 | 
				
			||||||
    ct_dispatch: Dispatch<CurrentTransfer>,
 | 
					    ct_dispatch: Dispatch<CurrentTransfer>,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,6 @@ use yewdux::prelude::*;
 | 
				
			||||||
use web_sys::HtmlDialogElement;
 | 
					use web_sys::HtmlDialogElement;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::components::states::MainState;
 | 
					use crate::components::states::MainState;
 | 
				
			||||||
use plate_tool_lib::plate_instances::PlateInstance;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::components::callbacks::new_plate_dialog_callbacks;
 | 
					use crate::components::callbacks::new_plate_dialog_callbacks;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ pub fn Plate(props: &PlateProps) -> Html {
 | 
				
			||||||
        m_end_handle.set(Some(pt2));
 | 
					        m_end_handle.set(Some(pt2));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let transfer_map = {
 | 
					    let tooltip_map = {
 | 
				
			||||||
        let transfers = main_state.transfers.iter().filter(|t| match props.ptype {
 | 
					        let transfers = main_state.transfers.iter().filter(|t| match props.ptype {
 | 
				
			||||||
            PlateType::Source => t.source_id == props.source_plate.get_uuid(),
 | 
					            PlateType::Source => t.source_id == props.source_plate.get_uuid(),
 | 
				
			||||||
            PlateType::Destination => t.dest_id == props.destination_plate.get_uuid(),
 | 
					            PlateType::Destination => t.dest_id == props.destination_plate.get_uuid(),
 | 
				
			||||||
| 
						 | 
					@ -125,12 +125,12 @@ pub fn Plate(props: &PlateProps) -> Html {
 | 
				
			||||||
                        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={wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
 | 
					                        in_transfer={wells.contains(&(i,j)) && main_state.preferences.in_transfer_hashes}
 | 
				
			||||||
                        color={transfer_map.get(&(i,j))
 | 
					                        color={tooltip_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_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={tooltip_map.get(&(i,j)).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone())
 | 
				
			||||||
                                    .collect::<Vec<_>>().join(", ")))}
 | 
					                                    .collect::<Vec<_>>().join(", ")))}
 | 
				
			||||||
                        />
 | 
					                        />
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -108,4 +108,13 @@ impl MainState {
 | 
				
			||||||
            self.destination_plates[index].change_name(new_name.to_string());
 | 
					            self.destination_plates[index].change_name(new_name.to_string());
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn change_format(&mut self, id: Uuid, new_format: &PlateFormat) {
 | 
				
			||||||
 | 
					        if let Some(index) = self.source_plates.iter().position(|spi| spi.get_uuid() == id) {
 | 
				
			||||||
 | 
					            self.source_plates[index].change_format(new_format);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if let Some(index) = self.destination_plates.iter().position(|dpi| dpi.get_uuid() == id) {
 | 
				
			||||||
 | 
					            self.destination_plates[index].change_format(new_format);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,13 +1,14 @@
 | 
				
			||||||
#![allow(non_snake_case)]
 | 
					#![allow(non_snake_case)]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use plate_tool_lib::plate::PlateFormat;
 | 
				
			||||||
use uuid::Uuid;
 | 
					use uuid::Uuid;
 | 
				
			||||||
use wasm_bindgen::JsCast;
 | 
					use wasm_bindgen::JsCast;
 | 
				
			||||||
use web_sys::{HtmlDialogElement, HtmlInputElement};
 | 
					use web_sys::{HtmlDialogElement, HtmlInputElement};
 | 
				
			||||||
use yew::prelude::*;
 | 
					use yew::prelude::*;
 | 
				
			||||||
use yewdux::prelude::*;
 | 
					use yewdux::prelude::*;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::components::states::{CurrentTransfer, MainState};
 | 
					 | 
				
			||||||
use crate::components::callbacks::tree_callbacks;
 | 
					use crate::components::callbacks::tree_callbacks;
 | 
				
			||||||
 | 
					use crate::components::states::{CurrentTransfer, MainState};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Properties)]
 | 
					#[derive(PartialEq, Properties)]
 | 
				
			||||||
pub struct TreeProps {
 | 
					pub struct TreeProps {
 | 
				
			||||||
| 
						 | 
					@ -157,23 +158,32 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
 | 
				
			||||||
        Some(plate) => plate.name.clone(),
 | 
					        Some(plate) => plate.name.clone(),
 | 
				
			||||||
        None => "Not Found".to_string(),
 | 
					        None => "Not Found".to_string(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    let plate_format = plate.map(|p| p.plate.plate_format);
 | 
				
			||||||
 | 
					    let plate_formats = vec![
 | 
				
			||||||
 | 
					        PlateFormat::W6,
 | 
				
			||||||
 | 
					        PlateFormat::W12,
 | 
				
			||||||
 | 
					        PlateFormat::W24,
 | 
				
			||||||
 | 
					        PlateFormat::W48,
 | 
				
			||||||
 | 
					        PlateFormat::W96,
 | 
				
			||||||
 | 
					        PlateFormat::W384,
 | 
				
			||||||
 | 
					        PlateFormat::W1536,
 | 
				
			||||||
 | 
					        PlateFormat::W3456,
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					    let plate_format_options = plate_formats.iter().map(|v| {
 | 
				
			||||||
 | 
					        let selected = Some(v) == plate_format.as_ref();
 | 
				
			||||||
 | 
					        html!{
 | 
				
			||||||
 | 
					            <option value={v.to_string()} selected={selected}>{ v.to_string() }</option>
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let onclose = {
 | 
					    let onclose = {
 | 
				
			||||||
        let dialog_close_callback = props.dialog_close_callback.clone();
 | 
					        let dialog_close_callback = props.dialog_close_callback.clone();
 | 
				
			||||||
        move |_| dialog_close_callback.emit(())
 | 
					        move |_| dialog_close_callback.emit(())
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let rename_onchange = {
 | 
					    let rename_onchange = tree_callbacks::rename_onchange(props.id, main_dispatch.clone());
 | 
				
			||||||
        let id = props.id;
 | 
					
 | 
				
			||||||
        Callback::from(move |e: Event| {
 | 
					    let format_onchange = tree_callbacks::format_onchange(props.id, main_dispatch.clone());
 | 
				
			||||||
            log::debug!("Changed name");
 | 
					 | 
				
			||||||
            let input = e
 | 
					 | 
				
			||||||
                .target()
 | 
					 | 
				
			||||||
                .expect("Event must have target")
 | 
					 | 
				
			||||||
                .dyn_into::<HtmlInputElement>()
 | 
					 | 
				
			||||||
                .unwrap();
 | 
					 | 
				
			||||||
            main_dispatch.reduce_mut(|state| state.rename_plate(id, &input.value()))
 | 
					 | 
				
			||||||
        })
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let delete_onclick = {
 | 
					    let delete_onclick = {
 | 
				
			||||||
        let delete_button_callback = props.delete_button_callback.clone();
 | 
					        let delete_button_callback = props.delete_button_callback.clone();
 | 
				
			||||||
| 
						 | 
					@ -204,6 +214,11 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
 | 
				
			||||||
            <h2>{"Plate Info"}</h2>
 | 
					            <h2>{"Plate Info"}</h2>
 | 
				
			||||||
            <h3>{"Name: "}<input type="text" value={plate_name} onchange={rename_onchange}/></h3>
 | 
					            <h3>{"Name: "}<input type="text" value={plate_name} onchange={rename_onchange}/></h3>
 | 
				
			||||||
            <button onclick={delete_onclick}>{"Delete"}</button>
 | 
					            <button onclick={delete_onclick}>{"Delete"}</button>
 | 
				
			||||||
 | 
					            if let Some(_) = plate_format {
 | 
				
			||||||
 | 
					            <select name="modal_plate_format" onchange={format_onchange}>
 | 
				
			||||||
 | 
					            { for plate_format_options }
 | 
				
			||||||
 | 
					            </select>
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            <form class="modal_close" method="dialog"><button /></form>
 | 
					            <form class="modal_close" method="dialog"><button /></form>
 | 
				
			||||||
        </dialog>
 | 
					        </dialog>
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue