Skip to main content

ravenrs_app/
module.rs

1//! How an application is put together: modules bring entities, behavior and model changes.
2
3use std::any::Any;
4use std::cell::RefCell;
5use std::rc::Rc;
6
7use crate::frame::FrameContext;
8use crate::model::{Diff, MergedModel, ModelLayer, ModelLayers, check_model, generate_model};
9use crate::{EntityInfo, Frame, ModelError, ModelNode, ObjectSpace, ViewController, WindowController};
10
11/// The layer that holds the model's words in the language picked.
12const LANGUAGE_LAYER: &str = "Language";
13
14/// A part of an application: what it adds, in one place. Modules stack, each over those it
15/// depends on.
16pub trait Module {
17    /// What it is known by.
18    fn name(&self) -> &str;
19
20    /// The modules that have to come before it, by name.
21    fn dependencies(&self) -> Vec<String> {
22        Vec::new()
23    }
24
25    /// The entities it declares.
26    fn entities(&self) -> Vec<EntityInfo> {
27        Vec::new()
28    }
29
30    /// New controllers for a frame; each frame has its own.
31    fn view_controllers(&self) -> Vec<Box<dyn ViewController>> {
32        Vec::new()
33    }
34
35    /// New window controllers for a frame.
36    fn window_controllers(&self) -> Vec<Box<dyn WindowController>> {
37        Vec::new()
38    }
39
40    /// Adds nodes and settings to the generated model. What comes from code is not a difference,
41    /// so a person's own settings still win over it.
42    fn extend_model(&self, _root: &mut ModelNode) {}
43
44    /// Changes what was generated, once every module added its part.
45    fn update_generated(&self, _root: &mut ModelNode) {}
46
47    /// Its layer of the model, as JSON.
48    fn model_difference(&self) -> Option<&'static str> {
49        None
50    }
51
52    /// Puts in place the records it needs, when an application opens on its storage. It runs every
53    /// time, so it looks before adding. The commit is up to whoever calls it.
54    fn initial_records(&self, _space: &mut dyn ObjectSpace) -> Result<(), String> {
55        Ok(())
56    }
57}
58
59/// What a piece of a dashboard shows.
60#[derive(Clone, Debug, PartialEq, Eq)]
61#[non_exhaustive]
62#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
63pub enum DashboardContent {
64    /// A view of the model, by its id.
65    View(String),
66    /// A control the application registered, by its name.
67    Control(String),
68}
69
70/// A piece of a dashboard.
71#[derive(Clone, Debug, PartialEq, Eq)]
72#[non_exhaustive]
73#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
74pub struct DashboardItem {
75    /// What it is known by.
76    pub id: String,
77    /// What people see over it.
78    pub caption: String,
79    /// What it shows.
80    pub content: DashboardContent,
81}
82
83/// Other views together. It is bound to no records: each piece keeps its own frame.
84#[derive(Clone, Debug, PartialEq, Eq)]
85#[non_exhaustive]
86#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
87pub struct DashboardInfo {
88    /// Its name, which its view's id starts with.
89    pub name: String,
90    /// What people see.
91    pub caption: String,
92    /// The group it goes under in the navigation.
93    pub navigation: Option<String>,
94    /// The name of its icon.
95    pub icon: Option<String>,
96    /// Its pieces, in order.
97    pub items: Vec<DashboardItem>,
98    /// Other dashboards people can change it to: an id, what they see, and the other's name.
99    pub variants: Vec<(String, String, String)>,
100}
101
102#[cfg_attr(feature = "live", ravenrs_live::expose)]
103impl DashboardInfo {
104    /// A dashboard with nothing in it yet.
105    pub fn new(name: impl Into<String>, caption: impl Into<String>) -> Self {
106        Self {
107            name: name.into(),
108            caption: caption.into(),
109            navigation: None,
110            icon: None,
111            items: Vec::new(),
112            variants: Vec::new(),
113        }
114    }
115
116    /// The group it goes under in the navigation.
117    pub fn navigation(mut self, group: impl Into<String>) -> Self {
118        self.navigation = Some(group.into());
119        self
120    }
121
122    /// The name of its icon.
123    pub fn icon(mut self, icon: impl Into<String>) -> Self {
124        self.icon = Some(icon.into());
125        self
126    }
127
128    /// Lets people change it to another dashboard, by that one's name. It takes two for the menu
129    /// to show, so a dashboard lists itself as well.
130    pub fn variant(
131        mut self,
132        id: impl Into<String>,
133        caption: impl Into<String>,
134        dashboard: impl Into<String>,
135    ) -> Self {
136        self.variants.push((id.into(), caption.into(), dashboard.into()));
137        self
138    }
139
140    /// Shows a view of the model, by its id.
141    pub fn view(mut self, caption: impl Into<String>, view: impl Into<String>) -> Self {
142        let view = view.into();
143        self.items.push(DashboardItem {
144            id: view.clone(),
145            caption: caption.into(),
146            content: DashboardContent::View(view),
147        });
148        self
149    }
150
151    /// Shows a control the application registered, by its name.
152    pub fn control(mut self, caption: impl Into<String>, control: impl Into<String>) -> Self {
153        let control = control.into();
154        self.items.push(DashboardItem {
155            id: control.clone(),
156            caption: caption.into(),
157            content: DashboardContent::Control(control),
158        });
159        self
160    }
161}
162
163/// Puts modules in an order where each comes after what it depends on, keeping the order they
164/// were added in as far as possible.
165fn in_dependency_order(modules: Vec<Box<dyn Module>>) -> Result<Vec<Box<dyn Module>>, ModelError> {
166    let names: Vec<String> = modules.iter().map(|m| m.name().to_owned()).collect();
167    for (i, name) in names.iter().enumerate() {
168        if names[..i].contains(name) {
169            return Err(ModelError(format!("the module {name} was added twice")));
170        }
171    }
172    let mut waiting: Vec<Option<Box<dyn Module>>> = modules.into_iter().map(Some).collect();
173    let mut placed: Vec<String> = Vec::new();
174    let mut ordered = Vec::with_capacity(waiting.len());
175    while ordered.len() < names.len() {
176        let mut progressed = false;
177        for slot in &mut waiting {
178            let Some(module) = slot.as_ref() else { continue };
179            let needs = module.dependencies();
180            if let Some(missing) = needs.iter().find(|d| !names.contains(d)) {
181                return Err(ModelError(format!(
182                    "the module {} depends on {missing}, which was not added",
183                    module.name()
184                )));
185            }
186            if needs.iter().all(|d| placed.contains(d)) {
187                placed.push(module.name().to_owned());
188                if let Some(module) = slot.take() {
189                    ordered.push(module);
190                }
191                progressed = true;
192                break;
193            }
194        }
195        if !progressed {
196            let stuck: Vec<&str> = waiting.iter().flatten().map(|m| m.name()).collect();
197            return Err(ModelError(format!("these modules depend on each other: {}", stuck.join(", "))));
198        }
199    }
200    Ok(ordered)
201}
202
203/// An application put together: its entities, its model and what makes frames. Layers from
204/// the administrator and the user go on with [`layers_mut`](Self::layers_mut).
205#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
206pub struct Application {
207    title: String,
208    modules: Vec<Box<dyn Module>>,
209    /// As they were declared.
210    declared: Vec<EntityInfo>,
211    /// As the model has them now.
212    entities: Vec<EntityInfo>,
213    services: Vec<Rc<dyn Any>>,
214    translations: Vec<(String, Diff)>,
215    layers: ModelLayers,
216    model: Rc<ModelNode>,
217    unusable: Vec<crate::UnusableNode>,
218    security: Option<Rc<crate::SecurityContext>>,
219}
220
221impl std::fmt::Debug for Application {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223        f.debug_struct("Application").field("title", &self.title).finish_non_exhaustive()
224    }
225}
226
227#[cfg_attr(feature = "live", ravenrs_live::expose)]
228impl Application {
229    /// Starts an application with this title.
230    pub fn builder(title: impl Into<String>) -> ApplicationBuilder {
231        ApplicationBuilder {
232            title: title.into(),
233            modules: Vec::new(),
234            entities: Vec::new(),
235            dashboards: Vec::new(),
236            services: Vec::new(),
237            translations: Vec::new(),
238        }
239    }
240
241    /// Its title.
242    pub fn title(&self) -> &str {
243        &self.title
244    }
245
246    /// Every entity it knows.
247    pub fn entities(&self) -> &[EntityInfo] {
248        &self.entities
249    }
250
251    /// The model as it stands.
252    pub fn model(&self) -> Rc<ModelNode> {
253        self.model.clone()
254    }
255
256    /// What in the layers has nowhere to go.
257    pub fn unusable(&self) -> &[crate::UnusableNode] {
258        &self.unusable
259    }
260
261    /// Its layers. Call [`refresh_model`](Self::refresh_model) after changing them.
262    pub fn layers_mut(&mut self) -> &mut ModelLayers {
263        &mut self.layers
264    }
265
266    /// Shows the model in this language, from the translation given for it; one that was not
267    /// given brings back what was declared.
268    pub fn set_language(&mut self, language: &str) -> Result<(), ModelError> {
269        let mut difference = Diff::new();
270        for (_, part) in self.translations.iter().filter(|(name, _)| name == language) {
271            difference.merge(part);
272        }
273        self.layers.put(&ModelLayer::Module(LANGUAGE_LAYER.to_owned()), difference);
274        self.refresh_model()
275    }
276
277    /// What people see in the model, for translating outside the code: a line per caption, as CSV
278    /// with its path, the caption and an empty column for the translation.
279    pub fn export_captions(&self) -> String {
280        let mut lines = vec!["path,caption,translation".to_owned()];
281        crate::model::captions_of(&self.model, &self.entities, &mut lines);
282        lines.join("\n") + "\n"
283    }
284
285    /// Takes what [`export_captions`](Self::export_captions) gave, with its last column filled in,
286    /// as the model in this language. Lines left empty stay as they were. What points to nothing
287    /// shows in [`unusable`](Self::unusable) once the language is picked.
288    pub fn import_captions(&mut self, language: &str, csv: &str) -> Result<(), ModelError> {
289        let difference = crate::model::translation_of(csv)?;
290        self.translations.retain(|(name, _)| name != language);
291        self.translations.push((language.to_owned(), difference));
292        Ok(())
293    }
294
295    /// Puts in place the records every module needs, and keeps them.
296    pub fn run_initial_records(&self, space: &mut dyn ObjectSpace) -> Result<(), String> {
297        for module in &self.modules {
298            module.initial_records(space)?;
299        }
300        space.commit().map_err(|e| e.to_string())
301    }
302
303    /// Keeps a setting typed as text in the person's own layer, and applies it. It stays what it
304    /// was: a number has to be typed as a number, and yes or no as `true` or `false`.
305    pub fn set_typed_setting(&mut self, path: &[&str], name: &str, text: &str) -> Result<(), ModelError> {
306        let current = self.model.at(path).and_then(|node| node.properties.get(name));
307        let value = match current {
308            Some(serde_json::Value::String(_)) | None => serde_json::Value::String(text.to_owned()),
309            Some(kept) => match serde_json::from_str::<serde_json::Value>(text) {
310                Ok(value) if std::mem::discriminant(&value) == std::mem::discriminant(kept) => value,
311                _ => return Err(ModelError(format!("{name} cannot be {text}"))),
312            },
313        };
314        self.layers.set(&ModelLayer::User, path, name, value);
315        self.refresh_model()
316    }
317
318    /// Throws away the person's own layer of the model.
319    pub fn clear_user_settings(&mut self) -> Result<(), ModelError> {
320        self.layers.put(&ModelLayer::User, Diff::new());
321        self.refresh_model()
322    }
323
324    /// Keeps a setting in the person's own layer of the model, and applies it.
325    pub fn set_user_setting(&mut self, path: &[&str], name: &str, value: &str) -> Result<(), ModelError> {
326        self.layers.set(&ModelLayer::User, path, name, serde_json::Value::String(value.to_owned()));
327        self.refresh_model()
328    }
329
330    /// Applies the layers again. Frames made after this see the result; views already showing
331    /// have to be shown again.
332    pub fn refresh_model(&mut self) -> Result<(), ModelError> {
333        let MergedModel { mut root, unusable } = self.layers.merged();
334        let problems = check_model(&root);
335        if !problems.is_empty() {
336            return Err(ModelError(problems.join("; ")));
337        }
338        crate::model::add_custom_fields(&mut root, &self.declared);
339        crate::model::follow_captions(&mut root, &self.declared);
340        self.entities =
341            self.declared.iter().map(|entity| entity.as_in_model(&root)).collect::<Result<_, _>>()?;
342        self.model = Rc::new(root);
343        self.unusable = unusable;
344        Ok(())
345    }
346
347    /// What every frame made from now on goes by, for who is signed in.
348    pub fn set_security(&mut self, security: Rc<crate::SecurityContext>) {
349        self.security = Some(security);
350    }
351
352    /// A frame with new controllers from every module, in order, over this unit of work.
353    pub fn new_frame(&self, context: FrameContext, space: Rc<RefCell<dyn ObjectSpace>>) -> Frame {
354        let view = self.modules.iter().flat_map(|m| m.view_controllers()).collect();
355        let window = self.modules.iter().flat_map(|m| m.window_controllers()).collect();
356        let frame = Frame::new(context, space, Some(self.model.clone()), view, window);
357        for service in &self.services {
358            frame.keep_extension(service.clone());
359        }
360        frame.set_entities(Rc::new(self.entities.clone()));
361        if let Some(security) = &self.security {
362            frame.set_security(security.clone());
363        }
364        frame
365    }
366}
367
368/// Collects what goes in an [`Application`].
369#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
370pub struct ApplicationBuilder {
371    title: String,
372    modules: Vec<Box<dyn Module>>,
373    entities: Vec<EntityInfo>,
374    dashboards: Vec<DashboardInfo>,
375    services: Vec<Rc<dyn Any>>,
376    translations: Vec<(String, String)>,
377}
378
379impl std::fmt::Debug for ApplicationBuilder {
380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381        f.debug_struct("ApplicationBuilder").field("title", &self.title).finish_non_exhaustive()
382    }
383}
384
385#[cfg_attr(feature = "live", ravenrs_live::expose)]
386impl ApplicationBuilder {
387    /// Adds a module.
388    pub fn module(mut self, module: impl Module + 'static) -> Self {
389        self.modules.push(Box::new(module));
390        self
391    }
392
393    /// Adds an entity outside any module.
394    pub fn entity(mut self, entity: EntityInfo) -> Self {
395        self.entities.push(entity);
396        self
397    }
398
399    /// Adds an entity declared as a Rust struct.
400    pub fn entity_of<T: crate::BusinessObject>(self) -> Self {
401        self.entity(T::entity_info())
402    }
403
404    /// Adds a dashboard.
405    pub fn dashboard(mut self, dashboard: DashboardInfo) -> Self {
406        self.dashboards.push(dashboard);
407        self
408    }
409
410    /// Gives actions and controllers something the application has, which they ask for by its
411    /// type with [`ActionContext::service`](crate::ActionContext::service).
412    pub fn service<T: 'static>(mut self, service: T) -> Self {
413        self.services.push(Rc::new(service));
414        self
415    }
416
417    /// The model in another language, as a difference in JSON.
418    /// [`Application::set_language`] picks it.
419    pub fn translation(mut self, language: impl Into<String>, difference: impl Into<String>) -> Self {
420        self.translations.push((language.into(), difference.into()));
421        self
422    }
423
424    /// Puts it together: orders the modules, generates the model, lets the modules change it,
425    /// applies their layers and checks the result. What is wrong comes out here, and not when
426    /// someone reaches that screen.
427    pub fn build(self) -> Result<Application, ModelError> {
428        let mut modules = in_dependency_order(self.modules)?;
429        let mut entities = self.entities;
430        for module in &modules {
431            entities.extend(module.entities());
432        }
433        let with_states: Vec<EntityInfo> =
434            entities.iter().filter(|entity| !entity.state_machines.is_empty()).cloned().collect();
435        if !with_states.is_empty() {
436            modules.push(Box::new(crate::workflow::StateMachineModule(with_states)));
437        }
438        let with_actions: Vec<EntityInfo> =
439            entities.iter().filter(|entity| !entity.actions.is_empty()).cloned().collect();
440        if !with_actions.is_empty() {
441            modules.push(Box::new(crate::record_action::RecordActionModule(with_actions)));
442        }
443        let mut zero = generate_model(&entities)?;
444        for dashboard in &self.dashboards {
445            crate::model::add_dashboard(&mut zero, dashboard);
446        }
447        for module in &modules {
448            module.extend_model(&mut zero);
449        }
450        for module in &modules {
451            module.update_generated(&mut zero);
452        }
453        let mut seen: Vec<String> = Vec::new();
454        for module in &modules {
455            let mut actions: Vec<crate::Action> =
456                module.view_controllers().iter().flat_map(|c| c.actions()).collect();
457            actions.extend(module.window_controllers().iter().flat_map(|c| c.actions()));
458            for action in actions {
459                action.check()?;
460                if seen.iter().any(|id| id == action.id()) {
461                    return Err(ModelError(format!("two actions have the id {}", action.id())));
462                }
463                seen.push(action.id().to_owned());
464            }
465        }
466        let mut layers = ModelLayers::new(zero);
467        for module in &modules {
468            if let Some(text) = module.model_difference() {
469                let difference = Diff::parse(text)
470                    .map_err(|e| ModelError(format!("the module {}: {e}", module.name())))?;
471                layers.put(&ModelLayer::Module(module.name().to_owned()), difference);
472            }
473        }
474        // Over the other modules, whatever comes in, and under the person's own settings.
475        layers.put(&ModelLayer::Module(LANGUAGE_LAYER.to_owned()), Diff::new());
476        let translations = self
477            .translations
478            .into_iter()
479            .map(|(language, text)| {
480                Diff::parse(&text)
481                    .map(|difference| (language.clone(), difference))
482                    .map_err(|e| ModelError(format!("the translation {language}: {e}")))
483            })
484            .collect::<Result<_, _>>()?;
485        let mut application = Application {
486            title: self.title,
487            modules,
488            declared: entities.clone(),
489            entities,
490            services: self.services,
491            translations,
492            layers,
493            model: Rc::new(ModelNode::default()),
494            unusable: Vec::new(),
495            security: None,
496        };
497        application.refresh_model()?;
498        Ok(application)
499    }
500}