distillation_shortcut_unit\gui/
mod.rs

1use 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        //create a compound list that contains the actual compounds, and if not present, also the selected compounds
23        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                //current status
94                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                //fill the streams table
112                let ports=self.unit.get_ports();
113                //port name and connected objects
114                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                    //get stream
118                    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                //stream connections
128                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                        //get stream name
134                        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                    //stream temperature
153                    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                    //stream pressure
176                    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                    //component flows and recoveries
199                    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                        //iterate over compounds by index
215                        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                                    //these are all real values
332                                    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