Import json and csv

plus the worlds dumbest boolean logic
This commit is contained in:
Emilia Allison 2025-11-25 20:56:48 -05:00
parent 9fff3608d6
commit 7e55ad033e
Signed by: emilia
GPG Key ID: FEC1CE6360EEC9A8
5 changed files with 148 additions and 20 deletions

View File

@ -11,6 +11,7 @@ eframe = { version = "0.33", default-features = false, features = [
"wayland",
"persistence",
]}
poll-promise = "0.3"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }

View File

@ -23,6 +23,10 @@ pub struct MainWindowState {
pub plate_display_options: PlateDisplayOptions,
pub csv_export_type: CsvExportType,
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 {
@ -32,6 +36,8 @@ impl Default for MainWindowState {
plate_display_options: PlateDisplayOptions::default(),
csv_export_type: CsvExportType::default(),
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]
#[derive(Debug, serde::Serialize, serde::Deserialize)]
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;
}
}
}

View File

@ -30,6 +30,7 @@ where
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 {
if val < min { min }
else if val > max { max }

View File

@ -1,3 +1,6 @@
#[cfg(not(target_arch = "wasm32"))]
use crate::app;
// Native case, use rfd
#[cfg(not(target_arch = "wasm32"))]
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
// Not tested yet!!
#[cfg(target_arch = "wasm32")]
pub fn save_file(default_filename: Option<&str>, data: &[u8]) {
use wasm_bindgen::JsCast;
use web_sys::{Blob, HtmlAnchorElement, Url};
let array = js_sys::Uint8Array::from(data);
let blob = Blob::new_with_u8_array_sequence(
&js_sys::Array::from_iter([array])
).unwrap();
let blob = Blob::new_with_u8_array_sequence(&js_sys::Array::from_iter([array])).unwrap();
let url = Url::create_object_url_with_blob(&blob).unwrap();
let document = web_sys::window().unwrap().document().unwrap();
let anchor = document.create_element("a").unwrap()
.dyn_into::<HtmlAnchorElement>().unwrap();
let anchor = document
.create_element("a")
.unwrap()
.dyn_into::<HtmlAnchorElement>()
.unwrap();
anchor.set_href(&url);
anchor.set_download(default_filename.unwrap_or("download.csv"));
anchor.click();
Url::revoke_object_url(&url).unwrap();
}

View File

@ -1,6 +1,6 @@
use crate::app::{CsvExportType, MainWindowState};
use crate::file_handling::save_file;
use crate::main_state::MainState;
use crate::file_handling::{self, save_file};
use crate::main_state::{self, MainState};
use crate::modals::ModalState;
use crate::transfer_menu::CurrentTransferState;
@ -100,11 +100,11 @@ fn render_export_menu(
.iter()
.find(|dpi| dpi.get_uuid() == transfer.dest_id)?;
if main_window_state.csv_export_type != CsvExportType::EchoClient
|| main_state
if main_window_state.csv_export_type == CsvExportType::EchoClient
&& main_state
.get_current_source_uuid()
.is_some_and(|x| x != src_barcode.get_uuid())
|| main_state
&& main_state
.get_current_destination_uuid()
.is_some_and(|x| x != dest_barcode.get_uuid())
{
@ -119,6 +119,7 @@ fn render_export_menu(
})
.flatten()
.collect();
log::info!("There are {} TransferRecords to write", records.len());
let data = match main_window_state.csv_export_type {
CsvExportType::Normal => records_to_csv(records),
@ -126,6 +127,7 @@ fn render_export_menu(
};
if let Ok(data) = data {
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"));
}
}
@ -140,13 +142,47 @@ fn render_export_menu(
fn render_import_menu(
ui: &mut egui::Ui,
_main_state: &mut MainState,
main_state: &mut MainState,
_modal_state: &mut ModalState,
_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 transfer from CSV").clicked() {}
if ui.button("Import from JSON").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(
@ -186,6 +222,9 @@ fn render_options_menu(
});
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_plates_horizontal, "Show plates horizontally");
ui.toggle_value(
&mut main_window_state.show_plates_horizontal,
"Show plates horizontally",
);
});
}