1use 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
11const LANGUAGE_LAYER: &str = "Language";
13
14pub trait Module {
17 fn name(&self) -> &str;
19
20 fn dependencies(&self) -> Vec<String> {
22 Vec::new()
23 }
24
25 fn entities(&self) -> Vec<EntityInfo> {
27 Vec::new()
28 }
29
30 fn view_controllers(&self) -> Vec<Box<dyn ViewController>> {
32 Vec::new()
33 }
34
35 fn window_controllers(&self) -> Vec<Box<dyn WindowController>> {
37 Vec::new()
38 }
39
40 fn extend_model(&self, _root: &mut ModelNode) {}
43
44 fn update_generated(&self, _root: &mut ModelNode) {}
46
47 fn model_difference(&self) -> Option<&'static str> {
49 None
50 }
51
52 fn initial_records(&self, _space: &mut dyn ObjectSpace) -> Result<(), String> {
55 Ok(())
56 }
57}
58
59#[derive(Clone, Debug, PartialEq, Eq)]
61#[non_exhaustive]
62#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
63pub enum DashboardContent {
64 View(String),
66 Control(String),
68}
69
70#[derive(Clone, Debug, PartialEq, Eq)]
72#[non_exhaustive]
73#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
74pub struct DashboardItem {
75 pub id: String,
77 pub caption: String,
79 pub content: DashboardContent,
81}
82
83#[derive(Clone, Debug, PartialEq, Eq)]
85#[non_exhaustive]
86#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
87pub struct DashboardInfo {
88 pub name: String,
90 pub caption: String,
92 pub navigation: Option<String>,
94 pub icon: Option<String>,
96 pub items: Vec<DashboardItem>,
98 pub variants: Vec<(String, String, String)>,
100}
101
102#[cfg_attr(feature = "live", ravenrs_live::expose)]
103impl DashboardInfo {
104 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 pub fn navigation(mut self, group: impl Into<String>) -> Self {
118 self.navigation = Some(group.into());
119 self
120 }
121
122 pub fn icon(mut self, icon: impl Into<String>) -> Self {
124 self.icon = Some(icon.into());
125 self
126 }
127
128 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 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 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
163fn 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#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
206pub struct Application {
207 title: String,
208 modules: Vec<Box<dyn Module>>,
209 declared: Vec<EntityInfo>,
211 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 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 pub fn title(&self) -> &str {
243 &self.title
244 }
245
246 pub fn entities(&self) -> &[EntityInfo] {
248 &self.entities
249 }
250
251 pub fn model(&self) -> Rc<ModelNode> {
253 self.model.clone()
254 }
255
256 pub fn unusable(&self) -> &[crate::UnusableNode] {
258 &self.unusable
259 }
260
261 pub fn layers_mut(&mut self) -> &mut ModelLayers {
263 &mut self.layers
264 }
265
266 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 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 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 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 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 pub fn clear_user_settings(&mut self) -> Result<(), ModelError> {
320 self.layers.put(&ModelLayer::User, Diff::new());
321 self.refresh_model()
322 }
323
324 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 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 pub fn set_security(&mut self, security: Rc<crate::SecurityContext>) {
349 self.security = Some(security);
350 }
351
352 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#[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 pub fn module(mut self, module: impl Module + 'static) -> Self {
389 self.modules.push(Box::new(module));
390 self
391 }
392
393 pub fn entity(mut self, entity: EntityInfo) -> Self {
395 self.entities.push(entity);
396 self
397 }
398
399 pub fn entity_of<T: crate::BusinessObject>(self) -> Self {
401 self.entity(T::entity_info())
402 }
403
404 pub fn dashboard(mut self, dashboard: DashboardInfo) -> Self {
406 self.dashboards.push(dashboard);
407 self
408 }
409
410 pub fn service<T: 'static>(mut self, service: T) -> Self {
413 self.services.push(Rc::new(service));
414 self
415 }
416
417 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 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 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}