distillation_shortcut_unit\gui/
mod.rs1use html_dialog::{HtmlDialogHandler,HtmlDialogResourceType,HtmlDialog,Window};
2use cobia::*;
3use cobia::prelude::*;
4use super::distillation_shortcut_unit::DistillationShortcutUnit;
5
6pub(crate) enum UnitDialogEvent {
7 GetContent,
8 GetStatus,
9 DataEntry,
10 Streams,
11}
12
13pub(crate) struct UnitDialogHandler<'a > {
14 data : json::JsonValue,
15 unit: &'a mut DistillationShortcutUnit,
16 modified : bool
17}
18
19impl<'a> UnitDialogHandler<'a> {
20
21 pub fn new(parent:CapeWindowId,unit: &'a mut DistillationShortcutUnit) -> HtmlDialog<UnitDialogHandler<'a>,UnitDialogEvent> {
22 let mut comps=unit.get_compound_list();
24 let light_key=unit.get_light_key_compound();
25 if !light_key.is_empty() && !comps.contains(&light_key) {
26 comps.push(light_key.clone());
27 }
28 let heavy_key=unit.get_heavy_key_compound();
29 if !heavy_key.is_empty() && !comps.contains(&heavy_key) {
30 comps.push(heavy_key.clone());
31 }
32 let data = json::object!{
33 unit_name: unit.get_name(),
34 compound_list: comps,
35 unit_description: unit.get_description(),
36 light_key_compound: light_key,
37 heavy_key_compound: heavy_key,
38 light_key_compound_recovery: unit.get_light_key_compound_recovery(),
39 heavy_key_compound_recovery: unit.get_heavy_key_compound_recovery(),
40 reflux_ratio_factor: unit.get_reflux_ratio_factor(),
41 maximum_iterations: unit.get_maximum_iterations(),
42 convergence_tolerance: unit.get_convergence_tolerance(),
43 number_of_stages: unit.get_number_of_stages(),
44 reflux_ratio: unit.get_reflux_ratio(),
45 feed_stage_location: unit.get_feed_stage_location(),
46 };
47 let handler=UnitDialogHandler {
48 data,
49 unit,
50 modified: false
51 };
52 let mut dlg=HtmlDialog::<UnitDialogHandler,UnitDialogEvent>::new(handler);
53 dlg.add("/".into(),HtmlDialogResourceType::Content((include_bytes!("gui.html"),"text/html")));
54 dlg.add("/gui.css".into(),HtmlDialogResourceType::Content((include_bytes!("gui.css"),"text/css")));
55 dlg.add("/gui.js".into(),HtmlDialogResourceType::Content((include_bytes!("gui.js"),"text/javascript")));
56 dlg.add("/gui.png".into(),HtmlDialogResourceType::Content((include_bytes!("gui.png"),"image/png")));
57 dlg.add("/content".into(),HtmlDialogResourceType::Info(UnitDialogEvent::GetContent));
58 dlg.add("/streams".into(),HtmlDialogResourceType::Info(UnitDialogEvent::Streams));
59 dlg.add("/status".into(),HtmlDialogResourceType::Info(UnitDialogEvent::GetStatus));
60 dlg.add("/data_entry".into(),HtmlDialogResourceType::Info(UnitDialogEvent::DataEntry));
61 #[cfg(target_os = "windows")] let parent= Some((parent as *mut core::ffi::c_void).into());
62 dlg.set_parent(parent,true);
63 dlg
64 }
65
66 pub fn is_modified(&self) -> bool {
67 self.modified
68 }
69
70 pub fn short_error(e: COBIAError) -> String {
71 match e {
72 COBIAError::Message(msg) => msg,
73 COBIAError::MessageWithCause(msg, _cause) => msg,
74 COBIAError::Code(_) => e.into(),
75 COBIAError::CAPEOPEN(ref err) => {
76 if let Ok(msg) = err.get_error_text() {
77 msg
78 } else {
79 e.into()
80 }
81 }
82 }
83 }
84}
85
86impl<'a> HtmlDialogHandler<UnitDialogEvent> for UnitDialogHandler<'a> {
87 fn provide_content(&mut self, event: &UnitDialogEvent, content: Option<String>, _dialog_window : Option<Window>) -> Result<(Vec<u8>,String), Box<dyn std::error::Error>> {
88 match event {
89 UnitDialogEvent::GetContent => {
90 Ok((json::stringify(self.data.clone()).into_bytes(),"application/json".to_string()))
91 },
92 UnitDialogEvent::GetStatus => {
93 let status=match self.unit.validate_internal() {
95 Ok(()) => {
96 json::object!{
97 text: "Specification is complete",
98 error: false,
99 }
100 },
101 Err(e) => {
102 json::object!{
103 text: Self::short_error(e),
104 error: true,
105 }
106 }
107 };
108 Ok((json::stringify(status).into_bytes(),"application/json".to_string()))
109 },
110 UnitDialogEvent::Streams => {
111 let ports=self.unit.get_ports();
113 let mut streams=Vec::<Option<cape_open_1_2::CapeThermoMaterial>>::with_capacity(3);
115 let mut port_info=Vec::<Vec::<String>>::new();
116 for port in ports {
117 if let Ok(stream)=port.get_connected_object() {
119 match cape_open_1_2::CapeThermoMaterial::from_object(&stream) {
120 Ok(s) => {streams.push(Some(s));}
121 Err(_) => {streams.push(None);}
122 }
123 } else {
124 streams.push(None);
125 }
126 }
127 let mut stream_names=Vec::<String>::with_capacity(4);
129 let mut any=false;
130 stream_names.push("Stream".into());
131 for stream in &streams {
132 if let Some(stream) = stream {
133 any=true;
135 let mut name="<Unnamed>".to_string();
136 if let Ok(iden)=cape_open_1_2::CapeIdentification::from_object(stream) {
137 let mut stream_name=cobia::CapeStringImpl::new();
138 if iden.get_component_name(&mut stream_name).is_ok() {
139 name=stream_name.to_string();
140 }
141 }
142 stream_names.push(name);
143 } else {
144 stream_names.push("<Not connected>".into());
145 }
146 }
147 port_info.push(stream_names);
148 if any {
149 let mut values=cobia::CapeArrayRealVec::new();
150 let str_no_basis=cobia::CapeStringImpl::new();
151 let str_mass_basis=cobia::CapeStringImpl::from("mass");
152 let mut stream_temperature=Vec::<String>::with_capacity(4);
154 stream_temperature.push("Temperature / [°C]".into());
155 let str_temperature=cobia::CapeStringImpl::from("temperature");
156 for stream in &streams {
157 if let Some(stream) = stream {
158 match stream.get_overall_prop(&str_temperature,&str_no_basis,&mut values) {
159 Ok(()) => {
160 if values.size()==1 {
161 stream_temperature.push(format!("{:.2}", values[0]-273.15));
162 } else {
163 stream_temperature.push("N/A".into());
164 }
165 },
166 Err(_) => {
167 stream_temperature.push("N/A".into());
168 }
169 }
170 } else {
171 stream_temperature.push(String::new());
172 }
173 }
174 port_info.push(stream_temperature);
175 let mut stream_pressure=Vec::<String>::with_capacity(4);
177 stream_pressure.push("Pressure / [bar]".into());
178 let str_pressure=cobia::CapeStringImpl::from("pressure");
179 for stream in &streams {
180 if let Some(stream) = stream {
181 match stream.get_overall_prop(&str_pressure,&str_no_basis,&mut values) {
182 Ok(()) => {
183 if values.size()==1 {
184 stream_pressure.push(format!("{:.3}", values[0]*1e-5));
185 } else {
186 stream_pressure.push("N/A".into());
187 }
188 },
189 Err(_) => {
190 stream_pressure.push("N/A".into());
191 }
192 }
193 } else {
194 stream_pressure.push(String::new());
195 }
196 }
197 port_info.push(stream_pressure);
198 let comp_list=self.unit.get_compound_list();
200 let str_flow=cobia::CapeStringImpl::from("flow");
201 let mut feed_rates=cobia::CapeArrayRealVec::new();
202 if let Some(ref feed) = streams[0] {
203 let _=feed.get_overall_prop(&str_flow,&str_mass_basis,&mut feed_rates);
204 }
205 if feed_rates.size()==comp_list.len() {
206 let mut distillate_rates=cobia::CapeArrayRealVec::new();
207 let mut bottoms_rates=cobia::CapeArrayRealVec::new();
208 if let Some(ref distillate) = streams[1] {
209 let _=distillate.get_overall_prop(&str_flow,&str_mass_basis,&mut distillate_rates);
210 }
211 if let Some(ref bottoms) = streams[2] {
212 let _=bottoms.get_overall_prop(&str_flow,&str_mass_basis,&mut bottoms_rates);
213 }
214 comp_list.iter().enumerate().for_each(|(i,comp)| {
216 let mut stream_comp=Vec::<String>::with_capacity(4);
217 stream_comp.push("F[".to_string()+comp+"] / [kg/s]");
218 stream_comp.push(format!("{:.3}", feed_rates[i]));
219 if distillate_rates.size()==comp_list.len() {
220 if distillate_rates[i]<=0.0 {
221 stream_comp.push("0".into());
222 } else {
223 stream_comp.push(format!("{:.3} ({:.2}%)", distillate_rates[i],100.0*distillate_rates[i]/feed_rates[i]));
224 }
225 } else {
226 stream_comp.push(String::new());
227 }
228 if bottoms_rates.size()==comp_list.len() {
229 if bottoms_rates[i]<=0.0 {
230 stream_comp.push("0".into());
231 } else {
232 stream_comp.push(format!("{:.3} ({:.2}%)", bottoms_rates[i],100.0*bottoms_rates[i]/feed_rates[i]));
233 }
234 } else {
235 stream_comp.push(String::new());
236 }
237 port_info.push(stream_comp);
238 });
239 }
240 }
241 let port_info=json::object!{
242 table:port_info
243 };
244 Ok((json::stringify(port_info).into_bytes(),"application/json".to_string()))
245 },
246 UnitDialogEvent::DataEntry => {
247 let mut error_text=String::new();
248 if let Some(content) = content {
249 match json::parse(&content) {
250 Ok(data) => {
251 let value=data["value"].as_str().unwrap_or("");
252 let control_id=data["controlId"].as_str().unwrap_or("");
253 match control_id {
254 "unit_name" => {
255 let new_name:String=value.into();
256 if new_name.is_empty() {
257 error_text="Unit name cannot be empty".into();
258 } else {
259 if new_name!=self.data["unit_name"] {
260 self.unit.set_name(&new_name);
261 self.data["unit_name"]=new_name.into();
262 self.modified=true;
263 }
264 }
265 },
266 "unit_description" => {
267 let new_desc:String=value.into();
268 if new_desc!=self.data["unit_description"] {
269 self.unit.set_description(&new_desc);
270 self.data["unit_description"]=new_desc.into();
271 self.modified=true;
272 }
273 },
274 "light_key_compound" => {
275 let new_comp:String=value.into();
276 if new_comp!=self.data["light_key_compound"] {
277 if !self.unit.get_compound_list().contains(&new_comp) {
278 error_text=format!("Compound {} is not defined", new_comp);
279 } else {
280 match self.unit.set_light_key_compound(&new_comp) {
281 Ok(()) => {
282 self.data["light_key_compound"]=new_comp.into();
283 self.modified=true;
284 },
285 Err(e) => {
286 error_text=Self::short_error(e);
287 }
288 }
289 }
290 }
291 },
292 "heavy_key_compound" => {
293 let new_comp:String=value.into();
294 if new_comp!=self.data["heavy_key_compound"] {
295 if !self.unit.get_compound_list().contains(&new_comp) {
296 error_text=format!("Compound {} is not defined", new_comp);
297 } else {
298 match self.unit.set_heavy_key_compound(&new_comp) {
299 Ok(()) => {
300 self.data["heavy_key_compound"]=new_comp.into();
301 self.modified=true;
302 },
303 Err(e) => {
304 error_text=Self::short_error(e);
305 }
306 }
307 }
308 }
309 },
310 "maximum_iterations" => {
311 match value.parse::<i32>() {
312 Ok(value) => {
313 if value!=self.unit.get_maximum_iterations() {
314 match self.unit.set_maximum_iterations(value) {
315 Ok(()) => {
316 self.data["maximum_iterations"]=value.into();
317 self.modified=true;
318 },
319 Err(e) => {
320 error_text=Self::short_error(e);
321 }
322 };
323 }
324 },
325 Err(_) => {
326 error_text=format!("Invalid integer value: {}", value);
327 }
328 };
329 },
330 _ => {
331 match value.parse::<f64>() {
333 Ok(value) => {
334 match control_id {
335 "light_key_compound_recovery" => {
336 if value!=self.unit.get_light_key_compound_recovery() {
337 match self.unit.set_light_key_compound_recovery(value) {
338 Ok(()) => {
339 self.data["light_key_compound_recovery"]=value.into();
340 self.modified=true;
341 },
342 Err(e) => {
343 error_text=Self::short_error(e);
344 }
345 }
346
347 }
348 },
349 "heavy_key_compound_recovery" => {
350 if value!=self.unit.get_heavy_key_compound_recovery() {
351 match self.unit.set_heavy_key_compound_recovery(value) {
352 Ok(()) => {
353 self.data["heavy_key_compound_recovery"]=value.into();
354 self.modified=true;
355 },
356 Err(e) => {
357 error_text=Self::short_error(e);
358 }
359 }
360 }
361 },
362 "reflux_ratio_factor" => {
363 if value!=self.unit.get_reflux_ratio_factor() {
364 match self.unit.set_reflux_ratio_factor(value) {
365 Ok(()) => {
366 self.data["reflux_ratio_factor"]=value.into();
367 self.modified=true;
368 },
369 Err(e) => {
370 error_text=Self::short_error(e);
371 }
372 }
373 }
374 },
375 "convergence_tolerance" => {
376 if value!=self.unit.get_convergence_tolerance() {
377 match self.unit.set_convergence_tolerance(value) {
378 Ok(()) => {
379 self.data["convergence_tolerance"]=value.into();
380 self.modified=true;
381 },
382 Err(e) => {
383 error_text=Self::short_error(e);
384 }
385 }
386 }
387 },
388 _ => {
389 error_text=format!("Unknown control ID: {}", control_id);
390 }
391 }
392 },
393 Err(_) => {
394 error_text=format!("Invalid numeric value: {}", value);
395 }
396 };
397 }
398 }
399 },
400 Err(e) => {
401 error_text=format!("Error parsing data: {}", e);
402 }
403 }
404 } else {
405 error_text="Missing POST data".into();
406 }
407 let is_error=!error_text.is_empty();
408 let response_json=json::object!{
409 error_text: error_text,
410 error: is_error,
411 };
412 Ok((json::stringify(response_json).into_bytes(),"application/json".to_string()))
413 },
414 }
415 }
416}
417