Compare commits
3 Commits
5aa29ff836
...
45f81d2409
| Author | SHA1 | Date |
|---|---|---|
|
|
45f81d2409 | |
|
|
7e55ad033e | |
|
|
9fff3608d6 |
|
|
@ -11,6 +11,7 @@ eframe = { version = "0.33", default-features = false, features = [
|
||||||
"wayland",
|
"wayland",
|
||||||
"persistence",
|
"persistence",
|
||||||
]}
|
]}
|
||||||
|
poll-promise = "0.3"
|
||||||
|
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ pub struct MainWindowState {
|
||||||
pub plate_display_options: PlateDisplayOptions,
|
pub plate_display_options: PlateDisplayOptions,
|
||||||
pub csv_export_type: CsvExportType,
|
pub csv_export_type: CsvExportType,
|
||||||
pub show_plates_horizontal: bool,
|
pub show_plates_horizontal: bool,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub json_upload_promise: Option<FileUploadPromise>,
|
||||||
|
#[serde(skip)]
|
||||||
|
pub csv_upload_promise: Option<FileUploadPromise>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MainWindowState {
|
impl Default for MainWindowState {
|
||||||
|
|
@ -32,6 +36,8 @@ impl Default for MainWindowState {
|
||||||
plate_display_options: PlateDisplayOptions::default(),
|
plate_display_options: PlateDisplayOptions::default(),
|
||||||
csv_export_type: CsvExportType::default(),
|
csv_export_type: CsvExportType::default(),
|
||||||
show_plates_horizontal: false,
|
show_plates_horizontal: false,
|
||||||
|
json_upload_promise: None,
|
||||||
|
csv_upload_promise: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -53,6 +59,14 @@ impl std::fmt::Display for CsvExportType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct FileUploadPromise(pub poll_promise::Promise<Option<Vec<u8>>>);
|
||||||
|
|
||||||
|
impl std::fmt::Debug for FileUploadPromise {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.debug_tuple("FileUploadPromise").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct PlateToolEframe {
|
pub struct PlateToolEframe {
|
||||||
|
|
@ -276,5 +290,51 @@ impl eframe::App for PlateToolEframe {
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process_promises(
|
||||||
|
&mut self.main_state,
|
||||||
|
&mut self.modal_state,
|
||||||
|
&mut self.current_transfer_state,
|
||||||
|
&mut self.main_window_state,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_promises(
|
||||||
|
main_state: &mut MainState,
|
||||||
|
_modal_state: &mut ModalState,
|
||||||
|
_current_transfer_state: &mut CurrentTransferState,
|
||||||
|
main_window_state: &mut MainWindowState,
|
||||||
|
) {
|
||||||
|
if let Some(promise) = &main_window_state.json_upload_promise {
|
||||||
|
if let Some(result) = promise.0.ready() {
|
||||||
|
if let Some(data) = result {
|
||||||
|
match serde_json::from_slice::<MainState>(data) {
|
||||||
|
Ok(new_main_state) => {
|
||||||
|
log::info!("Loaded new main state from file.");
|
||||||
|
*main_state = new_main_state
|
||||||
|
}
|
||||||
|
Err(e) => log::error!("Failed loading imported state JSON: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main_window_state.json_upload_promise = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(promise) = &main_window_state.csv_upload_promise {
|
||||||
|
if let Some(result) = promise.0.ready() {
|
||||||
|
log::info!("csv file read");
|
||||||
|
if let Some(data) = result {
|
||||||
|
if let Ok(data) = String::from_utf8(data.to_vec()) {
|
||||||
|
let auto_output = plate_tool_lib::csv::read_csv_auto(&data);
|
||||||
|
main_state.source_plates.extend(auto_output.sources);
|
||||||
|
main_state
|
||||||
|
.destination_plates
|
||||||
|
.extend(auto_output.destinations);
|
||||||
|
main_state.transfers.extend(auto_output.transfers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main_window_state.csv_upload_promise = None;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ where
|
||||||
response
|
response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// literally why is this not given in the emath::Numeric definition
|
||||||
fn clamp_partial<T: PartialOrd + Copy + std::fmt::Debug>(val: T, min: T, max: T) -> T {
|
fn clamp_partial<T: PartialOrd + Copy + std::fmt::Debug>(val: T, min: T, max: T) -> T {
|
||||||
if val < min { min }
|
if val < min { min }
|
||||||
else if val > max { max }
|
else if val > max { max }
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
use crate::app;
|
||||||
|
|
||||||
// Native case, use rfd
|
// Native case, use rfd
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
pub fn save_file(data: &[u8], default_filename: Option<&str>) {
|
pub fn save_file(data: &[u8], default_filename: Option<&str>) {
|
||||||
|
|
@ -20,27 +23,51 @@ pub fn save_file(data: &[u8], default_filename: Option<&str>) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
pub fn upload_file(filetype: &str, extension: &str) -> app::FileUploadPromise {
|
||||||
|
use poll_promise;
|
||||||
|
use rfd::FileDialog;
|
||||||
|
|
||||||
|
let filetype: String = filetype.to_owned();
|
||||||
|
let extension: String = extension.to_owned();
|
||||||
|
|
||||||
|
app::FileUploadPromise(poll_promise::Promise::spawn_thread(
|
||||||
|
"file_upload",
|
||||||
|
move || {
|
||||||
|
FileDialog::new()
|
||||||
|
.add_filter(filetype, &[extension])
|
||||||
|
.pick_file()
|
||||||
|
.and_then(|path| {
|
||||||
|
let data = std::fs::read(path).ok()?;
|
||||||
|
log::info!("Read {} bytes", data.len());
|
||||||
|
Some(data)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
// Web case, use web_sys
|
// Web case, use web_sys
|
||||||
// Not tested yet!!
|
// Not tested yet!!
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn save_file(default_filename: Option<&str>, data: &[u8]) {
|
pub fn save_file(default_filename: Option<&str>, data: &[u8]) {
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{Blob, HtmlAnchorElement, Url};
|
use web_sys::{Blob, HtmlAnchorElement, Url};
|
||||||
|
|
||||||
let array = js_sys::Uint8Array::from(data);
|
let array = js_sys::Uint8Array::from(data);
|
||||||
let blob = Blob::new_with_u8_array_sequence(
|
let blob = Blob::new_with_u8_array_sequence(&js_sys::Array::from_iter([array])).unwrap();
|
||||||
&js_sys::Array::from_iter([array])
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
let url = Url::create_object_url_with_blob(&blob).unwrap();
|
let url = Url::create_object_url_with_blob(&blob).unwrap();
|
||||||
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
let anchor = document.create_element("a").unwrap()
|
let anchor = document
|
||||||
.dyn_into::<HtmlAnchorElement>().unwrap();
|
.create_element("a")
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<HtmlAnchorElement>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
anchor.set_href(&url);
|
anchor.set_href(&url);
|
||||||
anchor.set_download(default_filename.unwrap_or("download.csv"));
|
anchor.set_download(default_filename.unwrap_or("download.csv"));
|
||||||
anchor.click();
|
anchor.click();
|
||||||
|
|
||||||
Url::revoke_object_url(&url).unwrap();
|
Url::revoke_object_url(&url).unwrap();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -419,7 +419,7 @@ fn add_plate_sub(
|
||||||
row: c_row,
|
row: c_row,
|
||||||
col: c_column,
|
col: c_column,
|
||||||
},
|
},
|
||||||
None,
|
Some(plate_type),
|
||||||
)
|
)
|
||||||
}) {
|
}) {
|
||||||
painter.circle_stroke(center, radius * 0.80, *STROKE_CURRENT);
|
painter.circle_stroke(center, radius * 0.80, *STROKE_CURRENT);
|
||||||
|
|
|
||||||
|
|
@ -117,8 +117,7 @@ impl CurrentTransferStateInterior {
|
||||||
let volume: f32 = match transfer.volume {
|
let volume: f32 = match transfer.volume {
|
||||||
plate_tool_lib::transfer_volume::TransferVolume::Single(x) => x,
|
plate_tool_lib::transfer_volume::TransferVolume::Single(x) => x,
|
||||||
plate_tool_lib::transfer_volume::TransferVolume::WellMap(_) => {
|
plate_tool_lib::transfer_volume::TransferVolume::WellMap(_) => {
|
||||||
log::warn!("COOL BUG: I genuinely don't know when this variant is used and honestly assume that it just never is constructed! Anyway, here's main state:\n{:?}", ms);
|
f32::NAN
|
||||||
unreachable!("It better not!");
|
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
@ -213,16 +212,18 @@ pub fn transfer_menu(
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
if state.volume.is_finite() {
|
||||||
ui.add(egui::Label::new("Volume"));
|
ui.horizontal(|ui| {
|
||||||
extra_widgets::drag_value_with_scroll(
|
ui.add(egui::Label::new("Volume"));
|
||||||
ui,
|
extra_widgets::drag_value_with_scroll(
|
||||||
&mut state.volume,
|
ui,
|
||||||
0.0..=f32::INFINITY,
|
&mut state.volume,
|
||||||
0.5,
|
0.0..=f32::INFINITY,
|
||||||
1.0,
|
0.5,
|
||||||
);
|
1.0,
|
||||||
});
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
ui.horizontal(|ui| {
|
||||||
ui.add(egui::Label::new("Source Region"));
|
ui.add(egui::Label::new("Source Region"));
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::app::{CsvExportType, MainWindowState};
|
use crate::app::{CsvExportType, MainWindowState};
|
||||||
use crate::file_handling::save_file;
|
use crate::file_handling::{self, save_file};
|
||||||
use crate::main_state::MainState;
|
use crate::main_state::{self, MainState};
|
||||||
use crate::modals::ModalState;
|
use crate::modals::ModalState;
|
||||||
use crate::transfer_menu::CurrentTransferState;
|
use crate::transfer_menu::CurrentTransferState;
|
||||||
|
|
||||||
|
|
@ -100,11 +100,11 @@ fn render_export_menu(
|
||||||
.iter()
|
.iter()
|
||||||
.find(|dpi| dpi.get_uuid() == transfer.dest_id)?;
|
.find(|dpi| dpi.get_uuid() == transfer.dest_id)?;
|
||||||
|
|
||||||
if main_window_state.csv_export_type != CsvExportType::EchoClient
|
if main_window_state.csv_export_type == CsvExportType::EchoClient
|
||||||
|| main_state
|
&& main_state
|
||||||
.get_current_source_uuid()
|
.get_current_source_uuid()
|
||||||
.is_some_and(|x| x != src_barcode.get_uuid())
|
.is_some_and(|x| x != src_barcode.get_uuid())
|
||||||
|| main_state
|
&& main_state
|
||||||
.get_current_destination_uuid()
|
.get_current_destination_uuid()
|
||||||
.is_some_and(|x| x != dest_barcode.get_uuid())
|
.is_some_and(|x| x != dest_barcode.get_uuid())
|
||||||
{
|
{
|
||||||
|
|
@ -119,6 +119,7 @@ fn render_export_menu(
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
.collect();
|
.collect();
|
||||||
|
log::info!("There are {} TransferRecords to write", records.len());
|
||||||
|
|
||||||
let data = match main_window_state.csv_export_type {
|
let data = match main_window_state.csv_export_type {
|
||||||
CsvExportType::Normal => records_to_csv(records),
|
CsvExportType::Normal => records_to_csv(records),
|
||||||
|
|
@ -126,6 +127,7 @@ fn render_export_menu(
|
||||||
};
|
};
|
||||||
if let Ok(data) = data {
|
if let Ok(data) = data {
|
||||||
let bytes: &[u8] = data.as_bytes();
|
let bytes: &[u8] = data.as_bytes();
|
||||||
|
log::info!("There are {} bytes to be written to chosen file", bytes.len());
|
||||||
save_file(bytes, Some("transfers.csv"));
|
save_file(bytes, Some("transfers.csv"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -140,13 +142,47 @@ fn render_export_menu(
|
||||||
|
|
||||||
fn render_import_menu(
|
fn render_import_menu(
|
||||||
ui: &mut egui::Ui,
|
ui: &mut egui::Ui,
|
||||||
_main_state: &mut MainState,
|
main_state: &mut MainState,
|
||||||
_modal_state: &mut ModalState,
|
_modal_state: &mut ModalState,
|
||||||
_current_transfer_state: &mut CurrentTransferState,
|
_current_transfer_state: &mut CurrentTransferState,
|
||||||
_main_window_state: &mut MainWindowState,
|
main_window_state: &mut MainWindowState,
|
||||||
) {
|
) {
|
||||||
if ui.button("Import from JSON").clicked() {}
|
if ui.button("Import from JSON").clicked() {
|
||||||
if ui.button("Import transfer from CSV").clicked() {}
|
main_window_state.json_upload_promise = Some(file_handling::upload_file("JSON", "json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(promise) = &main_window_state.json_upload_promise {
|
||||||
|
if let Some(result) = promise.0.ready() {
|
||||||
|
if let Some(data) = result {
|
||||||
|
match serde_json::from_slice::<MainState>(data) {
|
||||||
|
Ok(new_main_state) => {
|
||||||
|
log::info!("Loaded new main state from file.");
|
||||||
|
*main_state = new_main_state
|
||||||
|
},
|
||||||
|
Err(e) => log::error!("Failed loading imported state JSON: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main_window_state.json_upload_promise = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ui.button("Import transfer from CSV").clicked() {
|
||||||
|
main_window_state.csv_upload_promise = Some(file_handling::upload_file("CSV", "csv"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(promise) = &main_window_state.csv_upload_promise {
|
||||||
|
if let Some(result) = promise.0.ready() {
|
||||||
|
if let Some(data) = result {
|
||||||
|
if let Ok(data) = String::from_utf8(data.to_vec()) {
|
||||||
|
let auto_output = plate_tool_lib::csv::read_csv_auto(&data);
|
||||||
|
main_state.source_plates.extend(auto_output.sources);
|
||||||
|
main_state.destination_plates.extend(auto_output.destinations);
|
||||||
|
main_state.transfers.extend(auto_output.transfers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
main_window_state.json_upload_promise = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_options_menu(
|
fn render_options_menu(
|
||||||
|
|
@ -186,6 +222,9 @@ fn render_options_menu(
|
||||||
});
|
});
|
||||||
ui.menu_button("Windows", |ui| {
|
ui.menu_button("Windows", |ui| {
|
||||||
ui.toggle_value(&mut main_window_state.show_side_panel, "Toggle side panel");
|
ui.toggle_value(&mut main_window_state.show_side_panel, "Toggle side panel");
|
||||||
ui.toggle_value(&mut main_window_state.show_plates_horizontal, "Show plates horizontally");
|
ui.toggle_value(
|
||||||
|
&mut main_window_state.show_plates_horizontal,
|
||||||
|
"Show plates horizontally",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ use crate::transfer_region::{CustomRegion, Region, TransferRegion};
|
||||||
|
|
||||||
use std::collections::{HashSet, HashMap};
|
use std::collections::{HashSet, HashMap};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct AutoOutput {
|
pub struct AutoOutput {
|
||||||
pub sources: Vec<PlateInstance>,
|
pub sources: Vec<PlateInstance>,
|
||||||
pub destinations: Vec<PlateInstance>,
|
pub destinations: Vec<PlateInstance>,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue