Add callbacks per #2
Gitea Scan/plate-tool/pipeline/head There was a failure building this commit Details

Squashed commit of the following:

commit 4710335750
Author: Emilia <contact@emiliaallison.com>
Date:   Sat Feb 10 16:34:52 2024 -0500

    tree callbacks

commit f8216cb0bd
Author: Emilia <contact@emiliaallison.com>
Date:   Sat Feb 10 09:20:59 2024 -0500

    transfer_menu callbacks

commit 15accc2fca
Author: Emilia <contact@emiliaallison.com>
Date:   Tue Jan 30 20:37:15 2024 -0500

    I think this file was supposed to be in the last one lol

commit 53457a3e86
Author: Emilia <contact@emiliaallison.com>
Date:   Sat Jan 13 14:05:34 2024 -0500

    new_plate_dialog callbacks

    For #2

commit c2a3f0302b
Author: Emilia <contact@emiliaallison.com>
Date:   Sat Jan 13 13:57:14 2024 -0500

    Finish main_window callback refactor

    For #2

commit 820f672cb7
Author: Emilia <contact@emiliaallison.com>
Date:   Sat Jan 13 13:23:11 2024 -0500

    Import json button refactor

    Partial for #2

commit a90b5f83d8
Author: Emilia <contact@emiliaallison.com>
Date:   Fri Jan 12 22:22:28 2024 -0500

    A number of moves to address #2

commit 62d870521e
Author: Emilia <contact@emiliaallison.com>
Date:   Fri Jan 12 21:44:10 2024 -0500

    First callback move

commit e2ef9fa84d
Author: Emilia <contact@emiliaallison.com>
Date:   Fri Jan 12 21:43:50 2024 -0500

    Increment version number in cargo.lock

    why wasn't this done already?
This commit is contained in:
Emilia Allison 2024-02-10 16:38:00 -05:00
parent 0b6aec2f6c
commit 020f7740d3
Signed by: emilia
GPG Key ID: 05D5D1107E5100A1
11 changed files with 929 additions and 675 deletions

2
Cargo.lock generated
View File

@ -558,7 +558,7 @@ dependencies = [
[[package]] [[package]]
name = "plate-tool" name = "plate-tool"
version = "0.1.0" version = "0.2.0"
dependencies = [ dependencies = [
"csv", "csv",
"getrandom", "getrandom",

View File

@ -0,0 +1,433 @@
#![allow(non_snake_case)]
use std::collections::HashSet;
use js_sys::Array;
use lazy_static::lazy_static;
use regex::Regex;
use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{
Blob, FileReader, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement,
HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url,
};
use yew::prelude::*;
use yewdux::prelude::*;
use crate::components::states::{CurrentTransfer, MainState};
use crate::components::transfer_menu::letters_to_num;
use crate::data::transfer::Transfer;
use crate::data::transfer_region::{Region, TransferRegion};
use crate::data::csv::{state_to_csv, TransferRecord};
type NoParamsCallback = Box<dyn Fn(()) -> ()>;
pub fn toggle_in_transfer_hashes_callback(
main_dispatch: Dispatch<MainState>,
) -> Callback<web_sys::MouseEvent> {
let main_dispatch = main_dispatch.clone();
Callback::from(move |_| {
main_dispatch.reduce_mut(|state| {
state.preferences.in_transfer_hashes ^= true;
})
})
}
pub fn new_plate_dialog_callback(
new_plate_dialog_is_open: UseStateHandle<bool>,
) -> NoParamsCallback {
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
Box::new(move |_| {
new_plate_dialog_is_open.set(false);
})
}
pub fn open_new_plate_dialog_callback(
new_plate_dialog_is_open: UseStateHandle<bool>,
) -> NoParamsCallback {
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
Box::new(move |_| {
new_plate_dialog_is_open.set(true);
})
}
pub fn new_button_callback(
main_dispatch: Dispatch<MainState>,
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<web_sys::MouseEvent> {
Callback::from(move |_| {
let window = web_sys::window().unwrap();
let confirm =
window.confirm_with_message("This will reset all plates and transfers. Proceed?");
if let Ok(confirm) = confirm {
if confirm {
main_dispatch.set(MainState::default());
ct_dispatch.set(CurrentTransfer::default());
}
}
})
}
fn save_str(data: &str, name: &str) {
let blob =
Blob::new_with_str_sequence(&Array::from_iter(std::iter::once(JsValue::from_str(data))));
if let Ok(blob) = blob {
let url = Url::create_object_url_with_blob(&blob).expect("We have a blob, why not URL?");
// Beneath is the cool hack to download files
let window = web_sys::window().unwrap();
let document = window.document().unwrap();
let anchor = document
.create_element("a")
.unwrap()
.dyn_into::<HtmlAnchorElement>()
.unwrap();
anchor.set_download(name);
anchor.set_href(&url);
anchor.click();
}
}
pub fn export_csv_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
Callback::from(move |_| {
if main_state.transfers.is_empty() {
web_sys::window()
.unwrap()
.alert_with_message("No transfers to export.")
.unwrap();
return;
}
web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap();
if let Ok(csv) = state_to_csv(&main_state) {
save_str(&csv, "transfers.csv");
}
})
}
pub fn export_json_button_callback(main_state: std::rc::Rc<MainState>) -> Callback<MouseEvent> {
Callback::from(move |_| {
if let Ok(json) = serde_json::to_string(&main_state) {
save_str(&json, "plate-tool-state.json");
} else {
web_sys::window()
.unwrap()
.alert_with_message("Failed to export.")
.unwrap();
}
})
}
pub fn input_json_input_callback(
main_dispatch: Dispatch<MainState>,
modal: HtmlDialogElement,
) -> Closure<dyn FnMut(Event)> {
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 ms = serde_json::from_str::<MainState>(value);
match ms {
Ok(ms) => main_dispatch.set(ms),
Err(e) => log::debug!("{:?}", e),
};
modal.close();
}
});
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
onload.forget(); // Magic (don't touch)
}
}
}
})
}
pub fn import_json_button_callback(main_dispatch: Dispatch<MainState>) -> Callback<MouseEvent> {
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(".json");
form.append_child(&input).unwrap();
let input_callback = {
let main_dispatch = main_dispatch.clone();
let modal = modal.clone();
input_json_input_callback(main_dispatch, modal)
};
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();
})
}
pub fn import_transfer_csv_submit_callback(
main_dispatch: Dispatch<MainState>,
from_source: HtmlSelectElement,
to_source: HtmlSelectElement,
from_dest: HtmlSelectElement,
to_dest: HtmlSelectElement,
records: Vec<TransferRecord>,
) -> Closure<dyn FnMut(Event)> {
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();
});
})
}
pub fn import_transfer_csv_onload_callback(
main_dispatch: Dispatch<MainState>,
file_reader: FileReader,
modal: HtmlDialogElement,
) -> Closure<dyn FnMut(Event)> {
Closure::<dyn FnMut(_)>::new(move |_: Event| {
if let Some(value) = &file_reader.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();
import_transfer_csv_submit_callback(
main_dispatch,
from_source,
to_source,
from_dest,
to_dest,
records,
)
};
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();
}
})
}
pub fn import_transfer_csv_input_callback(
main_dispatch: Dispatch<MainState>,
modal: HtmlDialogElement,
) -> Closure<dyn FnMut(Event)> {
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 = import_transfer_csv_onload_callback(main_dispatch, fr1, modal);
fr.set_onload(Some(onload.as_ref().unchecked_ref()));
onload.forget(); // Magic (don't touch)
}
}
}
})
}

View File

@ -0,0 +1,4 @@
pub mod main_window_callbacks;
pub mod new_plate_dialog_callbacks;
pub mod transfer_menu_callbacks;
pub mod tree_callbacks;

View File

@ -0,0 +1,61 @@
use yew::prelude::*;
use yewdux::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{EventTarget, FormData, HtmlDialogElement, HtmlFormElement};
use crate::components::states::MainState;
use crate::data::plate::*;
use crate::data::plate_instances::PlateInstance;
pub fn new_plate_callback(
dispatch: Dispatch<MainState>,
close_callback: Callback<()>,
) -> Callback<SubmitEvent> {
Callback::from(move |e: SubmitEvent| {
e.prevent_default();
close_callback.emit(());
let target: Option<EventTarget> = e.target();
let form = target.and_then(|t| t.dyn_into::<HtmlFormElement>().ok());
if let Some(form) = form {
if let Ok(form_data) = FormData::new_with_form(&form) {
let name = form_data.get("new_plate_name").as_string().unwrap();
let format = match form_data.get("plate_format").as_string().unwrap().as_str() {
"6" => PlateFormat::W6,
"12" => PlateFormat::W12,
"24" => PlateFormat::W24,
"48" => PlateFormat::W48,
"96" => PlateFormat::W96,
"384" => PlateFormat::W384,
"1536" => PlateFormat::W1536,
"3456" => PlateFormat::W3456,
_ => unreachable!(),
};
if let Some(pt_string) = form_data.get("new_plate_type").as_string() {
let plate_type = match pt_string.as_str() {
"src" => PlateType::Source,
"dest" => PlateType::Destination,
_ => PlateType::Source,
};
dispatch.reduce_mut(|s| {
if plate_type == PlateType::Source {
s.add_source_plate(PlateInstance::new(PlateType::Source, format, name))
} else {
s.add_dest_plate(PlateInstance::new(
PlateType::Destination,
format,
name,
))
}
});
}
}
}
})
}
pub fn onclose(close_callback: Callback<()>) -> Callback<Event> {
Callback::from(move |_: Event| {
close_callback.emit(());
})
}

View File

@ -0,0 +1,242 @@
use lazy_static::lazy_static;
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::rc::Rc;
use uuid::Uuid;
use wasm_bindgen::JsCast;
use web_sys::{EventTarget, HtmlInputElement};
use yew::prelude::*;
use yewdux::prelude::*;
use crate::components::transfer_menu::RegionDisplay;
use crate::data::{transfer::Transfer, transfer_region::Region};
use crate::components::states::{CurrentTransfer, MainState};
pub fn on_name_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if input.value() == "" {
return;
} // We do not want empty inputs!
ct_dispatch.reduce_mut(|state| {
state.transfer.name = input.value();
});
}
})
}
pub fn on_src_region_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<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 input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.source_region = Region::from(&rd);
});
input.set_custom_validity("");
} else {
input.set_custom_validity("Invalid region.")
}
}
})
}
pub fn on_dest_region_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.dest_region = Region::from(&rd);
});
input.set_custom_validity("");
} else {
input.set_custom_validity("Invalid region.")
}
}
})
}
pub fn on_source_interleave_x_change_callback(
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<Event> {
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_source =
(num, state.transfer.transfer_region.interleave_source.1);
});
}
}
})
}
pub fn on_source_interleave_y_change_callback(
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<Event> {
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_source =
(state.transfer.transfer_region.interleave_source.0, num);
});
}
}
})
}
pub fn on_dest_interleave_x_change_callback(
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<Event> {
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_dest =
(num, state.transfer.transfer_region.interleave_dest.1);
});
}
}
})
}
pub fn on_dest_interleave_y_change_callback(
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<Event> {
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_dest =
(state.transfer.transfer_region.interleave_dest.0, num);
});
}
}
})
}
pub fn on_volume_change_callback(ct_dispatch: Dispatch<CurrentTransfer>) -> Callback<Event> {
Callback::from(move |e: Event| {
let input = e
.target()
.expect("Event must have target")
.dyn_into::<HtmlInputElement>()
.expect("Must have been emitted by input");
if let Ok(num) = input.value().parse::<f32>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.volume = num;
});
}
})
}
pub fn new_transfer_button_callback_callback(
main_dispatch: Dispatch<MainState>,
main_state: Rc<MainState>,
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<MouseEvent> {
Callback::from(move |_: MouseEvent| {
main_dispatch.reduce_mut(|state| {
state.selected_transfer = Uuid::nil();
});
ct_dispatch.reduce_mut(|state| {
state.transfer = Transfer::default();
state.transfer.source_id = main_state.selected_source_plate;
state.transfer.dest_id = main_state.selected_dest_plate;
});
})
}
pub fn save_transfer_button_callback_callback(
main_dispatch: Dispatch<MainState>,
main_state: Rc<MainState>,
ct_state: Rc<CurrentTransfer>,
new_transfer_button_callback: Callback<MouseEvent>)
-> Callback<MouseEvent> {
Callback::from(move |e: MouseEvent| {
log::debug!("Button pressed");
if main_state.selected_transfer.is_nil() {
if let Some(spi) = main_state
.source_plates
.iter()
.find(|spi| spi.get_uuid() == main_state.selected_source_plate)
{
if let Some(dpi) = main_state
.destination_plates
.iter()
.find(|dpi| dpi.get_uuid() == main_state.selected_dest_plate)
{
let new_transfer = Transfer::new(
spi.clone(),
dpi.clone(),
ct_state.transfer.transfer_region.clone(),
ct_state.transfer.name.clone(),
);
main_dispatch.reduce_mut(|state| {
state.transfers.push(new_transfer);
state.selected_transfer = state
.transfers
.last()
.expect("An element should have just been added")
.get_uuid();
});
new_transfer_button_callback.emit(e); // If we just made a new transfer,
// then we should make another on
// save.
}
}
} else if let Some(index) = main_state
.transfers
.iter()
.position(|t| t.get_uuid() == main_state.selected_transfer)
{
main_dispatch.reduce_mut(|state| {
state.transfers[index] = ct_state.transfer.clone();
});
}
})
}
pub fn delete_transfer_button_callback(
main_state: Rc<MainState>,
main_dispatch: Dispatch<MainState>,
ct_state: Rc<CurrentTransfer>,
new_transfer_button_callback: Callback<MouseEvent>
) -> Callback<MouseEvent> {
Callback::from(move |e: MouseEvent| {
if main_state.selected_transfer.is_nil() {
// Maybe reset transfer?
} else if let Some(index) = main_state
.transfers
.iter()
.position(|t| t.get_uuid() == ct_state.transfer.get_uuid())
{
main_dispatch.reduce_mut(|state| {
state.transfers.remove(index);
state.selected_transfer = Uuid::nil();
});
new_transfer_button_callback.emit(e); // We need a new transfer now
}
})
}

View File

@ -0,0 +1,114 @@
use std::rc::Rc;
use uuid::Uuid;
use wasm_bindgen::JsCast;
use web_sys::{EventTarget, HtmlElement};
use yew::prelude::*;
use yewdux::prelude::*;
use crate::components::states::{CurrentTransfer, MainState};
use crate::data::transfer_region::Region;
pub fn open_plate_info_callback(
plate_menu_id: UseStateHandle<Option<Uuid>>,
) -> Callback<MouseEvent> {
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
plate_menu_id.set(Some(Uuid::from_u128(id)));
}
}
})
}
pub fn plate_info_close_callback(
plate_menu_id: UseStateHandle<Option<Uuid>>,
) -> Callback<MouseEvent> {
Callback::from(move |_| {
plate_menu_id.set(None);
})
}
pub fn plate_info_delete_callback(
main_dispatch: Dispatch<MainState>,
plate_menu_id: UseStateHandle<Option<Uuid>>,
) -> Callback<MouseEvent> {
Callback::from(move |_| {
if let Some(id) = *plate_menu_id {
main_dispatch.reduce_mut(|state| {
state.del_plate(id);
});
}
})
}
pub fn source_plate_select_callback(
main_dispatch: Dispatch<MainState>,
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<MouseEvent> {
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.source_region = Region::default();
state.transfer.transfer_region.dest_region = Region::default();
});
main_dispatch.reduce_mut(|state| {
state.selected_source_plate = Uuid::from_u128(id);
state.selected_transfer = Uuid::nil();
});
}
}
})
}
pub fn destination_plate_select_callback(
main_dispatch: Dispatch<MainState>,
ct_dispatch: Dispatch<CurrentTransfer>,
) -> Callback<MouseEvent> {
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.source_region = Region::default();
state.transfer.transfer_region.dest_region = Region::default();
});
main_dispatch.reduce_mut(|state| {
state.selected_dest_plate = Uuid::from_u128(id);
state.selected_transfer = Uuid::nil();
});
}
}
})
}
pub fn transfer_select_callback(main_state: Rc<MainState>) -> Callback<MouseEvent> {
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
let id = Uuid::from_u128(id);
if let Some(transfer) = main_state
.transfers
.iter()
.find(|transfer| transfer.get_uuid() == id)
{
main_dispatch.reduce_mut(|state| {
state.selected_source_plate = transfer.source_id;
state.selected_dest_plate = transfer.dest_id;
state.selected_transfer = id;
});
ct_dispatch.reduce_mut(|state| {
state.transfer = transfer.clone();
});
}
}
}
})
}

View File

@ -1,27 +1,20 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use std::collections::HashSet;
use js_sys::Array; use js_sys::Array;
use lazy_static::lazy_static;
use regex::Regex;
use wasm_bindgen::{prelude::*, JsCast, JsValue}; use wasm_bindgen::{prelude::*, JsCast, JsValue};
use web_sys::{ use web_sys::{Blob, HtmlAnchorElement, HtmlDialogElement, HtmlFormElement, HtmlInputElement, Url};
Blob, HtmlAnchorElement, HtmlButtonElement, HtmlDialogElement, HtmlFormElement,
HtmlInputElement, HtmlOptionElement, HtmlSelectElement, Url,
};
use yew::prelude::*; use yew::prelude::*;
use yewdux::prelude::*; use yewdux::prelude::*;
use super::new_plate_dialog::NewPlateDialog; use crate::components::new_plate_dialog::NewPlateDialog;
use super::plates::plate_container::PlateContainer; use crate::components::plates::plate_container::PlateContainer;
use super::states::{CurrentTransfer, MainState}; use crate::components::states::{CurrentTransfer, MainState};
use super::transfer_menu::{letters_to_num, RegionDisplay, TransferMenu}; use crate::components::transfer_menu::TransferMenu;
use super::tree::Tree; use crate::components::tree::Tree;
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}; use crate::components::callbacks::main_window_callbacks;
#[function_component] #[function_component]
pub fn MainWindow() -> Html { pub fn MainWindow() -> Html {
@ -51,149 +44,32 @@ pub fn MainWindow() -> Html {
let toggle_in_transfer_hashes_callback = { let toggle_in_transfer_hashes_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
Callback::from(move |_| { main_window_callbacks::toggle_in_transfer_hashes_callback(main_dispatch)
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(); main_window_callbacks::new_plate_dialog_callback(new_plate_dialog_is_open.clone());
Callback::from(move |_| {
new_plate_dialog_is_open.set(false); let open_new_plate_dialog_callback =
}) main_window_callbacks::open_new_plate_dialog_callback(new_plate_dialog_is_open.clone());
};
let open_new_plate_dialog_callback = {
let new_plate_dialog_is_open = new_plate_dialog_is_open.clone();
Callback::from(move |_| {
new_plate_dialog_is_open.set(true);
})
};
let new_button_callback = { let new_button_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
Callback::from(move |_| { main_window_callbacks::new_button_callback(main_dispatch, ct_dispatch)
let window = web_sys::window().unwrap();
let confirm =
window.confirm_with_message("This will reset all plates and transfers. Proceed?");
if let Ok(confirm) = confirm {
if confirm {
main_dispatch.set(MainState::default());
ct_dispatch.set(CurrentTransfer::default());
}
}
})
}; };
let export_csv_button_callback = { let export_csv_button_callback = {
let main_state = main_state.clone(); let main_state = main_state.clone();
Callback::from(move |_| { main_window_callbacks::export_csv_button_callback(main_state)
if main_state.transfers.is_empty() {
web_sys::window()
.unwrap()
.alert_with_message("No transfers to export.")
.unwrap();
return;
}
web_sys::window().unwrap().alert_with_message("CSV export is currently not importable. Export as JSON if you'd like to back up your work!").unwrap();
if let Ok(csv) = state_to_csv(&main_state) {
save_str(&csv, "transfers.csv");
}
})
}; };
let export_json_button_callback = { let export_json_button_callback =
Callback::from(move |_| { { main_window_callbacks::export_json_button_callback(main_state) };
if let Ok(json) = serde_json::to_string(&main_state) {
save_str(&json, "plate-tool-state.json");
} else {
web_sys::window()
.unwrap()
.alert_with_message("Failed to export.")
.unwrap();
}
})
};
let import_json_button_callback = { let import_json_button_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
Callback::from(move |_| { main_window_callbacks::import_json_button_callback(main_dispatch)
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(".json");
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 ms = serde_json::from_str::<MainState>(value);
match ms {
Ok(ms) => main_dispatch.set(ms),
Err(e) => log::debug!("{:?}", e),
};
modal.close();
}
});
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();
})
}; };
let import_transfer_csv_callback = { let import_transfer_csv_callback = {
@ -233,227 +109,7 @@ pub fn MainWindow() -> Html {
let input_callback = { let input_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
let modal = modal.clone(); let modal = modal.clone();
Closure::<dyn FnMut(_)>::new(move |e: Event| { main_window_callbacks::import_transfer_csv_input_callback(main_dispatch, modal)
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.set_onchange(Some(input_callback.as_ref().unchecked_ref()));
input_callback.forget(); // Magic straight from the docs, don't touch :( input_callback.forget(); // Magic straight from the docs, don't touch :(

View File

@ -4,3 +4,4 @@ pub mod plates;
pub mod states; pub mod states;
pub mod transfer_menu; pub mod transfer_menu;
pub mod tree; pub mod tree;
mod callbacks;

View File

@ -8,6 +8,8 @@ use crate::components::states::MainState;
use crate::data::plate::*; use crate::data::plate::*;
use crate::data::plate_instances::PlateInstance; use crate::data::plate_instances::PlateInstance;
use crate::components::callbacks::new_plate_dialog_callbacks;
#[derive(PartialEq, Properties)] #[derive(PartialEq, Properties)]
pub struct NewPlateDialogProps { pub struct NewPlateDialogProps {
pub close_callback: Callback<()>, pub close_callback: Callback<()>,
@ -19,57 +21,12 @@ pub fn NewPlateDialog(props: &NewPlateDialogProps) -> Html {
let new_plate_callback = { let new_plate_callback = {
let close_callback = props.close_callback.clone(); let close_callback = props.close_callback.clone();
Callback::from(move |e: SubmitEvent| { new_plate_dialog_callbacks::new_plate_callback(dispatch, close_callback)
e.prevent_default();
close_callback.emit(());
let target: Option<EventTarget> = e.target();
let form = target.and_then(|t| t.dyn_into::<HtmlFormElement>().ok());
if let Some(form) = form {
if let Ok(form_data) = FormData::new_with_form(&form) {
let name = form_data.get("new_plate_name").as_string().unwrap();
let format = match form_data.get("plate_format").as_string().unwrap().as_str() {
"6" => PlateFormat::W6,
"12" => PlateFormat::W12,
"24" => PlateFormat::W24,
"48" => PlateFormat::W48,
"96" => PlateFormat::W96,
"384" => PlateFormat::W384,
"1536" => PlateFormat::W1536,
"3456" => PlateFormat::W3456,
_ => unreachable!(),
};
if let Some(pt_string) = form_data.get("new_plate_type").as_string() {
let plate_type = match pt_string.as_str() {
"src" => PlateType::Source,
"dest" => PlateType::Destination,
_ => PlateType::Source,
};
dispatch.reduce_mut(|s| {
if plate_type == PlateType::Source {
s.add_source_plate(PlateInstance::new(
PlateType::Source,
format,
name,
))
} else {
s.add_dest_plate(PlateInstance::new(
PlateType::Destination,
format,
name,
))
}
});
}
}
}
})
}; };
let onclose = { let onclose = {
let close_callback = props.close_callback.clone(); let close_callback = props.close_callback.clone();
Callback::from(move |_: Event| { new_plate_dialog_callbacks::onclose(close_callback)
close_callback.emit(());
})
}; };
// This whole section is optional, only if you want the backdrop // This whole section is optional, only if you want the backdrop

View File

@ -9,6 +9,7 @@ use web_sys::{EventTarget, HtmlInputElement};
use yew::prelude::*; use yew::prelude::*;
use yewdux::prelude::*; use yewdux::prelude::*;
use crate::components::callbacks::transfer_menu_callbacks;
use crate::data::{transfer::Transfer, transfer_region::Region}; use crate::data::{transfer::Transfer, transfer_region::Region};
use super::states::{CurrentTransfer, MainState}; use super::states::{CurrentTransfer, MainState};
@ -20,157 +21,52 @@ pub fn TransferMenu() -> Html {
let on_name_change = { let on_name_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_name_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if input.value() == "" {
return;
} // We do not want empty inputs!
ct_dispatch.reduce_mut(|state| {
state.transfer.name = input.value();
});
}
})
}; };
let on_src_region_change = { let on_src_region_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_src_region_change_callback(ct_dispatch)
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 input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.source_region = Region::from(&rd);
});
input.set_custom_validity("");
} else {
input.set_custom_validity("Invalid region.")
}
}
})
}; };
let on_dest_region_change = { let on_dest_region_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_dest_region_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(rd) = RegionDisplay::try_from(input.value().to_uppercase()) {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.dest_region = Region::from(&rd);
});
input.set_custom_validity("");
} else {
input.set_custom_validity("Invalid region.")
}
}
})
}; };
let on_source_interleave_x_change = { let on_source_interleave_x_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_source_interleave_x_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_source =
(num, state.transfer.transfer_region.interleave_source.1);
});
}
}
})
}; };
let on_source_interleave_y_change = { let on_source_interleave_y_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_source_interleave_y_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_source =
(state.transfer.transfer_region.interleave_source.0, num);
});
}
}
})
}; };
let on_dest_interleave_x_change = { let on_dest_interleave_x_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_dest_interleave_x_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_dest =
(num, state.transfer.transfer_region.interleave_dest.1);
});
}
}
})
}; };
let on_dest_interleave_y_change = { let on_dest_interleave_y_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_dest_interleave_y_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let target: Option<EventTarget> = e.target();
let input = target.and_then(|t| t.dyn_into::<HtmlInputElement>().ok());
if let Some(input) = input {
if let Ok(num) = input.value().parse::<i8>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.interleave_dest =
(state.transfer.transfer_region.interleave_dest.0, num);
});
}
}
})
}; };
let on_volume_change = { let on_volume_change = {
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
transfer_menu_callbacks::on_volume_change_callback(ct_dispatch)
Callback::from(move |e: Event| {
let input = e
.target()
.expect("Event must have target")
.dyn_into::<HtmlInputElement>()
.expect("Must have been emitted by input");
if let Ok(num) = input.value().parse::<f32>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.volume = num;
});
}
})
}; };
let new_transfer_button_callback = { let new_transfer_button_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
let main_state = main_state.clone(); let main_state = main_state.clone();
let ct_dispatch = ct_dispatch.clone();
Callback::from(move |_: MouseEvent| { transfer_menu_callbacks::new_transfer_button_callback_callback(
main_dispatch.reduce_mut(|state| { main_dispatch,
state.selected_transfer = Uuid::nil(); main_state,
}); ct_dispatch,
ct_dispatch.reduce_mut(|state| { )
state.transfer = Transfer::default();
state.transfer.source_id = main_state.selected_source_plate;
state.transfer.dest_id = main_state.selected_dest_plate;
});
})
}; };
let save_transfer_button_callback = { let save_transfer_button_callback = {
@ -178,70 +74,25 @@ pub fn TransferMenu() -> Html {
let main_state = main_state.clone(); let main_state = main_state.clone();
let ct_state = ct_state.clone(); let ct_state = ct_state.clone();
let new_transfer_button_callback = new_transfer_button_callback.clone(); let new_transfer_button_callback = new_transfer_button_callback.clone();
transfer_menu_callbacks::save_transfer_button_callback_callback(
Callback::from(move |e: MouseEvent| { main_dispatch,
log::debug!("Button pressed"); main_state,
if main_state.selected_transfer.is_nil() { ct_state,
if let Some(spi) = main_state new_transfer_button_callback,
.source_plates )
.iter()
.find(|spi| spi.get_uuid() == main_state.selected_source_plate)
{
if let Some(dpi) = main_state
.destination_plates
.iter()
.find(|dpi| dpi.get_uuid() == main_state.selected_dest_plate)
{
let new_transfer = Transfer::new(
spi.clone(),
dpi.clone(),
ct_state.transfer.transfer_region.clone(),
ct_state.transfer.name.clone(),
);
main_dispatch.reduce_mut(|state| {
state.transfers.push(new_transfer);
state.selected_transfer = state
.transfers
.last()
.expect("An element should have just been added")
.get_uuid();
});
new_transfer_button_callback.emit(e); // If we just made a new transfer,
// then we should make another on
// save.
}
}
} else if let Some(index) = main_state
.transfers
.iter()
.position(|t| t.get_uuid() == main_state.selected_transfer)
{
main_dispatch.reduce_mut(|state| {
state.transfers[index] = ct_state.transfer.clone();
});
}
})
}; };
let delete_transfer_button_callback = { let delete_transfer_button_callback = {
let main_state = main_state.clone();
let main_dispatch = main_dispatch.clone();
let ct_state = ct_state.clone(); let ct_state = ct_state.clone();
let new_callback = new_transfer_button_callback.clone(); let new_transfer_button_callback = new_transfer_button_callback.clone();
transfer_menu_callbacks::delete_transfer_button_callback(
Callback::from(move |e: MouseEvent| { main_state,
if main_state.selected_transfer.is_nil() { main_dispatch,
// Maybe reset transfer? ct_state,
} else if let Some(index) = main_state new_transfer_button_callback,
.transfers )
.iter()
.position(|t| t.get_uuid() == ct_state.transfer.get_uuid())
{
main_dispatch.reduce_mut(|state| {
state.transfers.remove(index);
state.selected_transfer = Uuid::nil();
});
new_callback.emit(e); // We need a new transfer now
}
})
}; };
html! { html! {
@ -390,7 +241,13 @@ 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 } Region::Custom(_) => RegionDisplay {
text: "CUSTOM".to_string(),
col_start: 0,
row_start: 0,
col_end: 0,
row_end: 0,
},
} }
} }
} }

View File

@ -2,12 +2,12 @@
use uuid::Uuid; use uuid::Uuid;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::{EventTarget, HtmlDialogElement, HtmlElement, 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::states::{CurrentTransfer, MainState};
use crate::data::transfer_region::Region; use crate::components::callbacks::tree_callbacks;
#[derive(PartialEq, Properties)] #[derive(PartialEq, Properties)]
pub struct TreeProps { pub struct TreeProps {
@ -22,102 +22,31 @@ pub fn Tree(props: &TreeProps) -> Html {
let open_plate_info_callback = { let open_plate_info_callback = {
let plate_menu_id = plate_modal_id.clone(); let plate_menu_id = plate_modal_id.clone();
Callback::from(move |e: MouseEvent| { tree_callbacks::open_plate_info_callback(plate_menu_id)
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
plate_menu_id.set(Some(Uuid::from_u128(id)));
}
}
})
}; };
let plate_info_close_callback = { let plate_info_close_callback = {
let plate_menu_id = plate_modal_id.clone(); let plate_menu_id = plate_modal_id.clone();
Callback::from(move |_| { tree_callbacks::plate_info_close_callback(plate_menu_id)
plate_menu_id.set(None);
})
}; };
let plate_info_delete_callback = { let plate_info_delete_callback = {
let dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
let plate_menu_id = plate_modal_id.clone(); let plate_menu_id = plate_modal_id.clone();
Callback::from(move |_| { tree_callbacks::plate_info_delete_callback(main_dispatch, plate_menu_id)
if let Some(id) = *plate_menu_id {
dispatch.reduce_mut(|state| {
state.del_plate(id);
});
}
})
}; };
let source_plate_select_callback = { let source_plate_select_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
tree_callbacks::source_plate_select_callback(main_dispatch, ct_dispatch)
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.source_region = Region::default();
state.transfer.transfer_region.dest_region = Region::default();
});
main_dispatch.reduce_mut(|state| {
state.selected_source_plate = Uuid::from_u128(id);
state.selected_transfer = Uuid::nil();
});
}
}
})
}; };
let destination_plate_select_callback = { let destination_plate_select_callback = {
let main_dispatch = main_dispatch.clone(); let main_dispatch = main_dispatch.clone();
let ct_dispatch = ct_dispatch.clone(); let ct_dispatch = ct_dispatch.clone();
tree_callbacks::destination_plate_select_callback(main_dispatch, ct_dispatch)
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
ct_dispatch.reduce_mut(|state| {
state.transfer.transfer_region.source_region = Region::default();
state.transfer.transfer_region.dest_region = Region::default();
});
main_dispatch.reduce_mut(|state| {
state.selected_dest_plate = Uuid::from_u128(id);
state.selected_transfer = Uuid::nil();
});
}
}
})
}; };
let transfer_select_callback = { let transfer_select_callback = {
let main_state = main_state.clone(); let main_state = main_state.clone();
tree_callbacks::transfer_select_callback(main_state)
Callback::from(move |e: MouseEvent| {
let target: Option<EventTarget> = e.target();
let li = target.and_then(|t| t.dyn_into::<HtmlElement>().ok());
if let Some(li) = li {
if let Ok(id) = li.id().as_str().parse::<u128>() {
let id = Uuid::from_u128(id);
if let Some(transfer) = main_state
.transfers
.iter()
.find(|transfer| transfer.get_uuid() == id)
{
main_dispatch.reduce_mut(|state| {
state.selected_source_plate = transfer.source_id;
state.selected_dest_plate = transfer.dest_id;
state.selected_transfer = id;
});
ct_dispatch.reduce_mut(|state| {
state.transfer = transfer.clone();
});
}
}
}
})
}; };
let source_plates = main_state let source_plates = main_state