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::util::*;
|
||||
|
||||
|
@ -31,7 +30,7 @@ pub struct TransferRecord {
|
|||
}
|
||||
|
||||
pub fn volume_default() -> f32 {
|
||||
1f32
|
||||
Transfer::default().volume
|
||||
}
|
||||
|
||||
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 {
|
||||
pub fn size(&self) -> (u8, u8) {
|
||||
|
|
|
@ -30,6 +30,10 @@ impl PlateInstance {
|
|||
pub fn change_name(&mut self, new_name: String) {
|
||||
self.name = new_name;
|
||||
}
|
||||
|
||||
pub fn change_format(&mut self, new_format: &PlateFormat) {
|
||||
self.plate.plate_format = *new_format;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Plate> for PlateInstance {
|
||||
|
|
|
@ -26,7 +26,7 @@ impl Default for Transfer {
|
|||
name: "New Transfer".to_string(),
|
||||
id: 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",
|
||||
"HtmlDialogElement", "Blob", "Url", "Window",
|
||||
"HtmlAnchorElement", "ReadableStream", "HtmlSelectElement", "HtmlOptionElement", "HtmlButtonElement",
|
||||
"FileReader"] }
|
||||
"FileReader", "HtmlCollection"] }
|
||||
js-sys = "0.3"
|
||||
log = "0.4"
|
||||
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 uuid::Uuid;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{EventTarget, HtmlElement};
|
||||
use web_sys::{EventTarget, HtmlElement, HtmlInputElement, HtmlSelectElement, HtmlOptionElement};
|
||||
use yew::prelude::*;
|
||||
use yewdux::prelude::*;
|
||||
|
||||
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(())>;
|
||||
|
||||
|
@ -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(
|
||||
main_dispatch: Dispatch<MainState>,
|
||||
ct_dispatch: Dispatch<CurrentTransfer>,
|
||||
|
|
|
@ -4,7 +4,6 @@ use yewdux::prelude::*;
|
|||
use web_sys::HtmlDialogElement;
|
||||
|
||||
use crate::components::states::MainState;
|
||||
use plate_tool_lib::plate_instances::PlateInstance;
|
||||
|
||||
use crate::components::callbacks::new_plate_dialog_callbacks;
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ pub fn Plate(props: &PlateProps) -> Html {
|
|||
m_end_handle.set(Some(pt2));
|
||||
}
|
||||
|
||||
let transfer_map = {
|
||||
let tooltip_map = {
|
||||
let transfers = main_state.transfers.iter().filter(|t| match props.ptype {
|
||||
PlateType::Source => t.source_id == props.source_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))}
|
||||
mouse={mouse_callback.clone()}
|
||||
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())
|
||||
.map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids))
|
||||
}
|
||||
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(", ")))}
|
||||
/>
|
||||
}
|
||||
|
|
|
@ -108,4 +108,13 @@ impl MainState {
|
|||
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)]
|
||||
|
||||
use plate_tool_lib::plate::PlateFormat;
|
||||
use uuid::Uuid;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{HtmlDialogElement, HtmlInputElement};
|
||||
use yew::prelude::*;
|
||||
use yewdux::prelude::*;
|
||||
|
||||
use crate::components::states::{CurrentTransfer, MainState};
|
||||
use crate::components::callbacks::tree_callbacks;
|
||||
use crate::components::states::{CurrentTransfer, MainState};
|
||||
|
||||
#[derive(PartialEq, Properties)]
|
||||
pub struct TreeProps {
|
||||
|
@ -157,23 +158,32 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
|
|||
Some(plate) => plate.name.clone(),
|
||||
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 dialog_close_callback = props.dialog_close_callback.clone();
|
||||
move |_| dialog_close_callback.emit(())
|
||||
};
|
||||
|
||||
let rename_onchange = {
|
||||
let id = props.id;
|
||||
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()))
|
||||
})
|
||||
};
|
||||
let rename_onchange = tree_callbacks::rename_onchange(props.id, main_dispatch.clone());
|
||||
|
||||
let format_onchange = tree_callbacks::format_onchange(props.id, main_dispatch.clone());
|
||||
|
||||
let delete_onclick = {
|
||||
let delete_button_callback = props.delete_button_callback.clone();
|
||||
|
@ -204,6 +214,11 @@ fn PlateInfoModal(props: &PlateInfoModalProps) -> Html {
|
|||
<h2>{"Plate Info"}</h2>
|
||||
<h3>{"Name: "}<input type="text" value={plate_name} onchange={rename_onchange}/></h3>
|
||||
<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>
|
||||
</dialog>
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue