1use tokio::runtime::Runtime;
2use tokio::sync::mpsc;
3
4use crate::engine::{Engine, FilePickTarget, FrontendCtx};
5use crate::messages::UiUpdate;
6use crate::types::AuthMode;
7
8pub struct UaApp {
9 engine: Engine,
10 update_rx: mpsc::UnboundedReceiver<UiUpdate>,
11}
12
13#[derive(Clone)]
14struct EguiCtx(egui::Context);
15
16impl FrontendCtx for EguiCtx {
17 fn request_repaint(&self) {
18 self.0.request_repaint();
19 }
20
21 fn set_clipboard(&self, text: &str) {
22 let s = text.to_owned();
23 self.0.output_mut(|o| o.copied_text = s);
24 }
25
26 fn pick_file(
27 &self,
28 rt: &Runtime,
29 update_tx: &mpsc::UnboundedSender<UiUpdate>,
30 target: FilePickTarget,
31 title: &str,
32 default_dir: &str,
33 ) {
34 let tx = update_tx.clone();
35 let ctx = self.clone();
36 let title = title.to_owned();
37 let default_dir = default_dir.to_owned();
38 rt.spawn_blocking(move || {
39 let mut dlg = rfd::FileDialog::new().set_title(&title);
40 if let Some(parent) = std::path::Path::new(&default_dir).parent()
41 && parent.exists()
42 {
43 dlg = dlg.set_directory(parent);
44 }
45 if let Some(path) = dlg.pick_file() {
46 let s = path.to_string_lossy().into_owned();
47 let update = match target {
48 FilePickTarget::CertPath => UiUpdate::CertPathPicked(s),
49 FilePickTarget::KeyPath => UiUpdate::KeyPathPicked(s),
50 };
51 let _ = tx.send(update);
52 }
53 let _ = tx.send(UiUpdate::FilePickerClosed);
54 ctx.request_repaint();
55 });
56 }
57}
58
59const STORAGE_ENDPOINT_URL: &str = "endpoint_url";
60const STORAGE_ENDPOINT_HISTORY: &str = "endpoint_history";
61const STORAGE_AUTH_MODE: &str = "auth_mode";
62const STORAGE_AUTH_USERNAME: &str = "auth_username";
63const STORAGE_AUTH_CERT_PATH: &str = "auth_cert_path";
64const STORAGE_AUTH_KEY_PATH: &str = "auth_key_path";
65const STORAGE_LAST_SELECTIONS: &str = "last_selection_paths";
66
67impl UaApp {
68 pub fn new(
69 rt: Runtime,
70 log_rx: mpsc::UnboundedReceiver<UiUpdate>,
71 storage: Option<&dyn eframe::Storage>,
72 ) -> Self {
73 let (mut engine, update_rx) = Engine::new(rt, log_rx);
74 if let Some(s) = storage {
75 if let Some(url) = eframe::get_value::<String>(s, STORAGE_ENDPOINT_URL) {
76 engine.model.endpoint_url = url;
77 }
78 if let Some(hist) = eframe::get_value::<Vec<String>>(s, STORAGE_ENDPOINT_HISTORY) {
79 engine.model.endpoint_history = hist;
80 }
81 if let Some(m) = eframe::get_value::<String>(s, STORAGE_AUTH_MODE) {
82 engine.model.auth_mode = match m.as_str() {
83 "UserName" => AuthMode::UserName,
84 "Certificate" => AuthMode::Certificate,
85 _ => AuthMode::Anonymous,
86 };
87 }
88 if let Some(s2) = eframe::get_value::<String>(s, STORAGE_AUTH_USERNAME) {
89 engine.model.auth_username = s2;
90 }
91 if let Some(s2) = eframe::get_value::<String>(s, STORAGE_AUTH_CERT_PATH) {
92 engine.model.auth_cert_path = s2;
93 }
94 if let Some(s2) = eframe::get_value::<String>(s, STORAGE_AUTH_KEY_PATH) {
95 engine.model.auth_key_path = s2;
96 }
97 if let Some(stored) = eframe::get_value::<std::collections::HashMap<String, Vec<String>>>(
98 s,
99 STORAGE_LAST_SELECTIONS,
100 ) {
101 use std::str::FromStr;
102 for (url, ids) in stored {
103 let path: Vec<opcua::types::NodeId> = ids
104 .iter()
105 .filter_map(|s| opcua::types::NodeId::from_str(s).ok())
106 .collect();
107 if !path.is_empty() {
108 engine.model.last_selection_paths.insert(url, path);
109 }
110 }
111 }
112 }
113 Self { engine, update_rx }
114 }
115}
116
117impl eframe::App for UaApp {
118 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
119 let c = EguiCtx(ctx.clone());
120 while let Ok(update) = self.update_rx.try_recv() {
121 self.engine.apply_update(&c, update);
122 }
123 let mut actions = Vec::new();
124 super::ui::draw(&self.engine.model, ctx, &mut actions);
125 for action in actions {
126 self.engine.dispatch(&c, action);
127 }
128 }
129
130 fn save(&mut self, storage: &mut dyn eframe::Storage) {
131 eframe::set_value(storage, STORAGE_ENDPOINT_URL, &self.engine.model.endpoint_url);
132 eframe::set_value(
133 storage,
134 STORAGE_ENDPOINT_HISTORY,
135 &self.engine.model.endpoint_history,
136 );
137 let auth_mode_str = match self.engine.model.auth_mode {
138 AuthMode::Anonymous => "Anonymous",
139 AuthMode::UserName => "UserName",
140 AuthMode::Certificate => "Certificate",
141 };
142 eframe::set_value(storage, STORAGE_AUTH_MODE, &auth_mode_str.to_string());
143 eframe::set_value(
144 storage,
145 STORAGE_AUTH_USERNAME,
146 &self.engine.model.auth_username,
147 );
148 eframe::set_value(
149 storage,
150 STORAGE_AUTH_CERT_PATH,
151 &self.engine.model.auth_cert_path,
152 );
153 eframe::set_value(
154 storage,
155 STORAGE_AUTH_KEY_PATH,
156 &self.engine.model.auth_key_path,
157 );
158 let paths: std::collections::HashMap<String, Vec<String>> = self
159 .engine
160 .model
161 .last_selection_paths
162 .iter()
163 .map(|(url, path)| (url.clone(), path.iter().map(|n| n.to_string()).collect()))
164 .collect();
165 eframe::set_value(storage, STORAGE_LAST_SELECTIONS, &paths);
166 }
167}