diff --git a/locales/en-US/main.ftl b/locales/en-US/main.ftl index 92429bc..94c5ae5 100644 --- a/locales/en-US/main.ftl +++ b/locales/en-US/main.ftl @@ -43,3 +43,13 @@ specctra-session-file = Specctra session file title-error-messages = Error Messages reset-error-messages = Reset Messages discard-item = Discard + +specctra-dsn-loader = Specctra DSN file loader +history-loader = History file loader +invoker = Invoker + +error-file-load = unable to read file +error-file-specctra-dsn-parse = file failed to parse as Specctra DSN +error-file-history-parse = file failed to parse as History JSON +error-overlay-init = unable to initialize overlay +error-autorouter-init = unable to initialize autorouter diff --git a/src/bin/topola-egui/app.rs b/src/bin/topola-egui/app.rs index 72c0567..6c1157d 100644 --- a/src/bin/topola-egui/app.rs +++ b/src/bin/topola-egui/app.rs @@ -146,36 +146,75 @@ impl App { fn update_state(&mut self) -> bool { let mut content_file_receiver = FileReceiver::new(&self.content_channel.1); - if let Ok(bufread) = content_file_receiver.try_recv() { - let design = SpecctraDesign::load(bufread).unwrap(); - let board = design.make_board(); - self.maybe_overlay = Some(Overlay::new(&board).unwrap()); - self.maybe_layers = Some(Layers::new(&board)); - self.maybe_design = Some(design); - self.arc_mutex_maybe_invoker = Arc::new(Mutex::new(Some(Invoker::new( - Autorouter::new(board).unwrap(), - )))); - self.viewport.scheduled_zoom_to_fit = true; + if let Some(input) = content_file_receiver.try_recv() { + match self.load_specctra_dsn(input) { + Ok(()) => {} + Err(err) => { + self.error_dialog.push_error("specctra-dsn-loader", err); + } + } } if let Some(invoker) = self.arc_mutex_maybe_invoker.lock().unwrap().as_mut() { let mut history_file_receiver = FileReceiver::new(&self.history_channel.1); - if let Ok(bufread) = history_file_receiver.try_recv() { - invoker.replay(serde_json::from_reader(bufread).unwrap()) + if let Some(input) = history_file_receiver.try_recv() { + let tr = &self.translator; + match input { + Ok(bufread) => match serde_json::from_reader(bufread) { + Ok(res) => invoker.replay(res), + Err(err) => { + self.error_dialog.push_error( + "history-loader", + format!("{}; {}", tr.text("error-file-history-parse"), err), + ); + } + }, + Err(err) => { + self.error_dialog.push_error( + "history-loader", + format!("{}; {}", tr.text("error-file-load"), err), + ); + } + } } if let Some(ref mut activity) = self.maybe_activity { - match activity.step(invoker) { - Ok(ActivityStatus::Running) => return true, - Ok(ActivityStatus::Finished(..)) => return false, - Err(err) => return false, - } + return match activity.step(invoker) { + Ok(ActivityStatus::Running) => true, + Ok(ActivityStatus::Finished(..)) => false, + Err(err) => { + self.error_dialog.push_error("invoker", format!("{}", err)); + false + } + }; } } false } + + fn load_specctra_dsn( + &mut self, + input: std::io::Result>, + ) -> Result<(), String> { + let tr = &self.translator; + let bufread = input.map_err(|err| format!("{}; {}", tr.text("error-file-load"), err))?; + let design = SpecctraDesign::load(bufread) + .map_err(|err| format!("{}; {}", tr.text("error-file-specctra-dsn-parse"), err))?; + let board = design.make_board(); + let overlay = Overlay::new(&board) + .map_err(|err| format!("{}; {}", tr.text("error-overlay-init"), err))?; + let layers = Layers::new(&board); + let autorouter = Autorouter::new(board) + .map_err(|err| format!("{}; {}", tr.text("error-autorouter-init"), err))?; + self.maybe_overlay = Some(overlay); + self.maybe_layers = Some(layers); + self.maybe_design = Some(design); + self.arc_mutex_maybe_invoker = Arc::new(Mutex::new(Some(Invoker::new(autorouter)))); + self.viewport.scheduled_zoom_to_fit = true; + Ok(()) + } } impl eframe::App for App { diff --git a/src/bin/topola-egui/error_dialog.rs b/src/bin/topola-egui/error_dialog.rs index a75fd18..8957ada 100644 --- a/src/bin/topola-egui/error_dialog.rs +++ b/src/bin/topola-egui/error_dialog.rs @@ -6,8 +6,13 @@ use std::sync::Arc; use crate::{translator::Translator, viewport::Viewport}; pub struct ErrorDialog { - pub messages: Vec<(&'static str, String)>, - pub window_open: bool, + /// messages is a list of error messages to display. + /// + /// first field is the emitting component (as a translator ID), + /// second field is the error message + messages: Vec<(&'static str, String)>, + + window_open: bool, } impl ErrorDialog { diff --git a/src/bin/topola-egui/file_receiver.rs b/src/bin/topola-egui/file_receiver.rs index 4e2e00a..f5e6b1b 100644 --- a/src/bin/topola-egui/file_receiver.rs +++ b/src/bin/topola-egui/file_receiver.rs @@ -1,5 +1,5 @@ use std::io::{BufReader, Cursor}; -use std::sync::mpsc::{Receiver, TryRecvError}; +use std::sync::mpsc::Receiver; pub struct FileReceiver<'a> { receiver: &'a Receiver, @@ -11,14 +11,12 @@ impl<'a> FileReceiver<'a> { } #[cfg(not(target_arch = "wasm32"))] - pub fn try_recv(&mut self) -> Result, TryRecvError> { - Ok(std::io::BufReader::new( - std::fs::File::open(self.receiver.try_recv()?).unwrap(), - )) + pub fn try_recv(&mut self) -> Option, std::io::Error>> { + Some(std::fs::File::open(self.receiver.try_recv().ok()?).map(std::io::BufReader::new)) } #[cfg(target_arch = "wasm32")] - pub fn try_recv(&mut self) -> Result>, TryRecvError> { - Ok(Cursor::new(self.receiver.try_recv()?.into())) + pub fn try_recv(&mut self) -> Option>, std::io::Error>> { + Some(Ok(Cursor::new(self.receiver.try_recv().ok()?.into()))) } }