1use std::any::Any;
4use std::cell::{Cell, RefCell};
5use std::collections::VecDeque;
6use std::fmt;
7use std::rc::Rc;
8
9use ravenrs_reactive::State;
10
11use crate::action::{
12 ActionContext, ActionError, ActionInput, ActionKind, ActionResult, REASON_CONTROLLER, fill_url,
13};
14use crate::controller::{ViewController, WindowController};
15use crate::{Action, ModelError, ModelNode, Object, ObjectSpace};
16
17#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
20pub enum ViewKind {
21 List,
23 Detail,
25 Dashboard,
27}
28
29#[derive(Clone, Debug, PartialEq, Eq)]
31#[non_exhaustive]
32#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
33pub struct ViewInfo {
34 pub id: String,
36 pub kind: ViewKind,
38 pub entity: Option<String>,
40}
41
42#[cfg_attr(feature = "live", ravenrs_live::expose)]
43impl ViewInfo {
44 pub fn new(id: impl Into<String>, kind: ViewKind, entity: Option<&str>) -> Self {
46 Self { id: id.into(), kind, entity: entity.map(str::to_owned) }
47 }
48
49 pub fn from_model(root: &ModelNode, id: &str) -> Result<Self, ModelError> {
51 let node =
52 root.at(&["Views", id]).ok_or_else(|| ModelError(format!("the model has no view {id}")))?;
53 let kind = match node.text("Kind") {
54 Some("ListView") => ViewKind::List,
55 Some("DetailView") => ViewKind::Detail,
56 Some("DashboardView") => ViewKind::Dashboard,
57 other => return Err(ModelError(format!("the view {id} is of an unknown kind: {other:?}"))),
58 };
59 Ok(Self::new(id, kind, node.text("Entity")))
60 }
61}
62
63#[derive(Clone, Copy, Debug, PartialEq, Eq)]
65#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
66pub enum FrameContext {
67 Main,
69 Nested,
71 Popup,
73 Lookup,
75}
76
77#[derive(Clone, Debug, PartialEq, Eq)]
79#[non_exhaustive]
80#[cfg_attr(feature = "live", ravenrs_live::expose(part = "variants"))]
81pub enum FrameEvent {
82 ViewChanging(Option<String>),
84 ViewChanged(Option<String>),
86}
87
88type Validator = Rc<dyn Fn(&[String], &ActionContext) -> Result<(), String>>;
89type FrameWatcher = Rc<dyn Fn(&FrameEvent)>;
90
91type PendingView = (Option<ViewInfo>, Option<Rc<RefCell<dyn ObjectSpace>>>);
93
94struct ViewSlot {
95 controller: RefCell<Box<dyn ViewController>>,
96 id: String,
97 actions: Vec<Action>,
98 reasons: RefCell<crate::action::Reasons>,
100 on: Cell<bool>,
101}
102
103struct WindowSlot {
104 controller: RefCell<Box<dyn WindowController>>,
105 actions: Vec<Action>,
106}
107
108type Messenger = Rc<dyn Fn(crate::Message)>;
109
110struct FrameInner {
111 context: FrameContext,
112 space: RefCell<Rc<RefCell<dyn ObjectSpace>>>,
113 model: Option<Rc<ModelNode>>,
114 extensions: RefCell<Vec<Rc<dyn Any>>>,
115 entities: RefCell<Rc<Vec<crate::EntityInfo>>>,
116 view: State<Option<ViewInfo>>,
117 selected: State<Vec<Object>>,
118 popup: State<Option<(String, String)>>,
119 views: Vec<ViewSlot>,
120 windows: Vec<WindowSlot>,
121 watchers: RefCell<Vec<FrameWatcher>>,
122 validator: RefCell<Option<Validator>>,
123 messenger: RefCell<Option<Messenger>>,
124 security: RefCell<Option<Rc<crate::SecurityContext>>>,
125 pending: RefCell<VecDeque<PendingView>>,
127 changing: Cell<bool>,
128 jobs: RefCell<Vec<crate::Job>>,
130 job_waker: RefCell<Option<crate::JobWaker>>,
131}
132
133#[derive(Clone)]
139#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
140pub struct Frame(Rc<FrameInner>);
141
142impl fmt::Debug for Frame {
143 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
144 f.debug_struct("Frame").field("context", &self.0.context).finish_non_exhaustive()
145 }
146}
147
148#[cfg_attr(feature = "live", ravenrs_live::expose)]
149impl Frame {
150 pub fn new(
153 context: FrameContext,
154 space: Rc<RefCell<dyn ObjectSpace>>,
155 model: Option<Rc<ModelNode>>,
156 view_controllers: Vec<Box<dyn ViewController>>,
157 window_controllers: Vec<Box<dyn WindowController>>,
158 ) -> Self {
159 let views = view_controllers
160 .into_iter()
161 .map(|controller| ViewSlot {
162 id: controller.id().to_owned(),
163 actions: controller.actions(),
164 controller: RefCell::new(controller),
165 reasons: RefCell::default(),
166 on: Cell::new(false),
167 })
168 .collect();
169 let windows: Vec<WindowSlot> = window_controllers
170 .into_iter()
171 .map(|controller| WindowSlot {
172 actions: controller.actions(),
173 controller: RefCell::new(controller),
174 })
175 .collect();
176 let frame = Self(Rc::new(FrameInner {
177 context,
178 space: RefCell::new(space),
179 model,
180 extensions: RefCell::default(),
181 entities: RefCell::default(),
182 view: State::new(None),
183 selected: State::new(Vec::new()),
184 popup: State::new(None),
185 views,
186 windows,
187 watchers: RefCell::default(),
188 validator: RefCell::default(),
189 messenger: RefCell::default(),
190 security: RefCell::default(),
191 pending: RefCell::default(),
192 changing: Cell::new(false),
193 jobs: RefCell::default(),
194 job_waker: RefCell::default(),
195 }));
196 for slot in &frame.0.windows {
197 let fits = slot.controller.borrow().contexts().contains(&context);
198 for action in &slot.actions {
199 action.set_active(REASON_CONTROLLER, fits);
200 }
201 if fits {
202 slot.controller.borrow_mut().activated(&frame);
203 }
204 }
205 frame.refresh_controllers();
206 frame.refresh_actions();
207 frame
208 }
209
210 pub fn set_messenger(&self, messenger: impl Fn(crate::Message) + 'static) {
212 *self.0.messenger.borrow_mut() = Some(Rc::new(messenger));
213 }
214
215 pub fn show_message(&self, message: crate::Message) {
217 let messenger = self.0.messenger.borrow().clone();
218 if let Some(messenger) = messenger {
219 messenger(message);
220 }
221 }
222
223 pub fn context(&self) -> FrameContext {
225 self.0.context
226 }
227
228 pub fn space(&self) -> Rc<RefCell<dyn ObjectSpace>> {
230 self.0.space.borrow().clone()
231 }
232
233 pub fn set_entities(&self, entities: Rc<Vec<crate::EntityInfo>>) {
235 *self.0.entities.borrow_mut() = entities;
236 self.refresh_actions();
237 }
238
239 pub fn set_security(&self, security: Rc<crate::SecurityContext>) {
242 *self.0.security.borrow_mut() = Some(security);
243 self.refresh_actions();
244 }
245
246 pub fn security(&self) -> Option<Rc<crate::SecurityContext>> {
248 self.0.security.borrow().clone()
249 }
250
251 pub fn set_extension<T: 'static>(&self, value: Rc<T>) {
254 self.0.extensions.borrow_mut().retain(|kept| !kept.is::<T>());
255 self.0.extensions.borrow_mut().push(value);
256 }
257
258 pub(crate) fn keep_extension(&self, value: Rc<dyn Any>) {
259 self.0.extensions.borrow_mut().push(value);
260 }
261
262 pub fn extension<T: 'static>(&self) -> Option<Rc<T>> {
264 let kept = self.0.extensions.borrow().iter().find(|kept| kept.is::<T>()).cloned();
265 kept.and_then(|kept| kept.downcast().ok())
266 }
267
268 pub fn model(&self) -> Option<Rc<ModelNode>> {
270 self.0.model.clone()
271 }
272
273 pub fn view(&self) -> Option<ViewInfo> {
275 self.0.view.get()
276 }
277
278 pub fn selected(&self) -> Vec<Object> {
280 self.0.selected.get()
281 }
282
283 pub fn open_popup(&self) -> Option<(String, String)> {
285 self.0.popup.get()
286 }
287
288 pub fn on_event(&self, watcher: impl Fn(&FrameEvent) + 'static) {
290 self.0.watchers.borrow_mut().push(Rc::new(watcher));
291 }
292
293 pub fn set_validator(
295 &self,
296 validator: impl Fn(&[String], &ActionContext) -> Result<(), String> + 'static,
297 ) {
298 *self.0.validator.borrow_mut() = Some(Rc::new(validator));
299 }
300
301 fn tell(&self, event: &FrameEvent) {
302 let watchers = self.0.watchers.borrow().clone();
303 for watcher in watchers {
304 watcher(event);
305 }
306 }
307
308 pub fn show_view(&self, id: &str) -> Result<(), ModelError> {
310 let model = self.0.model.as_ref().ok_or_else(|| ModelError("this frame has no model".into()))?;
311 self.set_view(Some(ViewInfo::from_model(model, id)?));
312 Ok(())
313 }
314
315 pub fn set_view(&self, view: Option<ViewInfo>) {
318 self.queue_view(view, None);
319 }
320
321 pub fn set_view_in(&self, view: Option<ViewInfo>, space: Rc<RefCell<dyn ObjectSpace>>) {
324 self.queue_view(view, Some(space));
325 }
326
327 fn queue_view(&self, view: Option<ViewInfo>, space: Option<Rc<RefCell<dyn ObjectSpace>>>) {
328 self.0.pending.borrow_mut().push_back((view, space));
329 if self.0.changing.replace(true) {
330 return;
331 }
332 loop {
333 let next = self.0.pending.borrow_mut().pop_front();
334 let Some((next, space)) = next else { break };
335 self.change_view(next, space);
336 }
337 self.0.changing.set(false);
338 }
339
340 fn change_view(&self, view: Option<ViewInfo>, space: Option<Rc<RefCell<dyn ObjectSpace>>>) {
341 let id = view.as_ref().map(|v| v.id.clone());
342 self.tell(&FrameEvent::ViewChanging(id.clone()));
343 for slot in &self.0.views {
344 self.turn(slot, false);
345 }
346 if let Some(space) = space {
347 *self.0.space.borrow_mut() = space;
348 }
349 self.0.view.set(view);
350 self.0.selected.set(Vec::new());
351 self.refresh_controllers();
352 self.refresh_actions();
353 self.tell(&FrameEvent::ViewChanged(id));
354 }
355
356 fn turn(&self, slot: &ViewSlot, on: bool) {
357 if slot.on.replace(on) != on {
358 if on {
359 slot.controller.borrow_mut().activated(self);
360 } else {
361 slot.controller.borrow_mut().deactivated(self);
362 }
363 }
364 }
365
366 fn refresh_controllers(&self) {
367 let view = self.0.view.peek().clone();
368 for slot in &self.0.views {
369 let fits = slot.controller.borrow().scope().covers(view.as_ref(), self.0.context);
370 let on = fits && view.is_some() && slot.reasons.borrow().all();
371 for action in &slot.actions {
372 action.set_active(REASON_CONTROLLER, on);
373 }
374 self.turn(slot, on);
375 }
376 }
377
378 fn refresh_actions(&self) {
379 let view = self.0.view.peek().clone();
380 let selected = self.0.selected.peek().clone();
381 let entities = self.0.entities.borrow().clone();
382 let environment = crate::environment();
383 let entity = view
384 .as_ref()
385 .and_then(|view| view.entity.as_deref())
386 .and_then(|name| entities.iter().find(|entity| entity.name == name));
387 for action in self.actions() {
388 action.update(view.as_ref(), self.0.context, &selected, &environment);
389 let (mut enabled, mut visible) = (true, true);
390 if let (Some(entity), Some(view)) = (entity, view.as_ref()) {
391 let records: Vec<Option<&Object>> =
393 if selected.is_empty() { vec![None] } else { selected.iter().map(Some).collect() };
394 for record in records {
395 let found =
396 entity.appearance_of(crate::AppearanceTarget::Action, action.id(), view, record);
397 enabled &= found.enabled.unwrap_or(true);
398 visible &= found.visible.unwrap_or(true);
399 }
400 }
401 if view.as_ref().is_some_and(|view| view.kind == ViewKind::List) && !visible {
404 enabled = false;
405 visible = true;
406 }
407 action.set_enabled(crate::REASON_APPEARANCE, enabled);
408 action.set_active(crate::REASON_APPEARANCE, visible);
409 if let Some(security) = self.0.security.borrow().as_ref() {
410 let (exists, can) = security.with(|permissions| {
411 let can = match (action.required_operation(), entity) {
412 (Some(operation), Some(entity)) if selected.is_empty() => {
413 permissions.can(operation, &entity.name, None)
414 }
415 (Some(operation), Some(entity)) => selected
416 .iter()
417 .all(|record| permissions.can(operation, &entity.name, Some(record))),
418 _ => true,
419 };
420 (permissions.can_use_action(action.id()), can)
421 });
422 action.set_active(crate::REASON_SECURITY, exists);
423 action.set_enabled(crate::REASON_SECURITY, can);
424 }
425 }
426 }
427
428 pub fn view_controls_created(&self) {
430 for slot in self.0.views.iter().filter(|slot| slot.on.get()) {
431 slot.controller.borrow_mut().view_controls_created(self);
432 }
433 }
434
435 pub fn set_controller_active(&self, id: &str, reason: &str, on: bool) {
437 if let Some(slot) = self.0.views.iter().find(|slot| slot.id == id) {
438 slot.reasons.borrow_mut().set(reason, on);
439 self.refresh_controllers();
440 }
441 }
442
443 pub fn is_controller_active(&self, id: &str) -> bool {
445 self.0.views.iter().any(|slot| slot.id == id && slot.on.get())
446 }
447
448 pub fn set_selected(&self, selected: Vec<Object>) {
450 self.0.selected.set(selected);
451 self.refresh_actions();
452 }
453
454 pub fn actions(&self) -> Vec<Action> {
456 let window = self.0.windows.iter().flat_map(|slot| slot.actions.iter());
457 let view = self.0.views.iter().flat_map(|slot| slot.actions.iter());
458 window.chain(view).cloned().collect()
459 }
460
461 pub fn action(&self, id: &str) -> Option<Action> {
463 self.actions().into_iter().find(|action| action.id() == id)
464 }
465
466 fn context_for(&self, input: ActionInput) -> ActionContext {
467 ActionContext::new(self.clone(), self.space(), self.0.selected.get(), self.0.view.get(), input)
468 }
469
470 pub fn execute(&self, id: &str, input: ActionInput) -> Result<ActionResult, ActionError> {
473 let action = self.action(id).ok_or_else(|| ActionError::Unknown(id.to_owned()))?;
474 let against = |reasons: crate::action::Reasons| reasons.against().map(str::to_owned).collect();
475 if !action.is_active() {
476 return Err(ActionError::Inactive(against(action.active_reasons())));
477 }
478 if !action.is_enabled() {
479 return Err(ActionError::Disabled(against(action.enabled_reasons())));
480 }
481 action.check_input(&input)?;
482 let cx = self.context_for(input);
483 if !action.run_guard(&cx) {
484 return Err(ActionError::Canceled);
485 }
486 match action.kind() {
487 ActionKind::Popup { view } => {
488 self.0.popup.set(Some((action.id().to_owned(), view.clone())));
489 return Ok(ActionResult::Popup(view.clone()));
490 }
491 ActionKind::Url(template) => {
492 let address = fill_url(template, cx.selected.first());
493 action.run(&cx).map_err(ActionError::Failed)?;
494 action.run_notice(&cx);
495 return Ok(ActionResult::OpenUrl(address));
496 }
497 _ => {}
498 }
499 self.finish(&action, &cx)
500 }
501
502 fn finish(&self, action: &Action, cx: &ActionContext) -> Result<ActionResult, ActionError> {
503 if !action.contexts().is_empty() {
504 let validator = self.0.validator.borrow().clone();
505 if let Some(validator) = validator {
506 validator(action.contexts(), cx).map_err(ActionError::Failed)?;
507 }
508 }
509 action.run(cx).map_err(ActionError::Failed)?;
510 if let Some(work) = action.work() {
511 let waker = self.0.job_waker.borrow().clone();
512 let job = crate::Job::start(
513 action.id(),
514 action.caption(),
515 work,
516 cx.selected.clone(),
517 cx.input.clone(),
518 waker,
519 );
520 let id = job.id();
521 action.set_enabled(crate::REASON_RUNNING, false);
522 self.0.jobs.borrow_mut().push(job);
523 return Ok(ActionResult::Started(id));
524 }
525 action.run_notice(cx);
526 let next = cx.take_next().unwrap_or(ActionResult::Done);
527 if let ActionResult::ShowView(id) = &next {
528 self.show_view(id).map_err(|e| ActionError::Failed(e.0))?;
529 }
530 Ok(next)
531 }
532
533 pub fn set_job_waker(&self, waker: crate::JobWaker) {
535 for job in self.0.jobs.borrow().iter() {
536 job.set_waker(Some(waker.clone()));
537 }
538 *self.0.job_waker.borrow_mut() = Some(waker);
539 }
540
541 pub fn jobs(&self) -> Vec<crate::Job> {
543 self.0.jobs.borrow().clone()
544 }
545
546 pub fn apply_jobs(&self) -> bool {
550 let jobs = self.jobs();
551 let mut changed = false;
552 for job in &jobs {
553 changed |= self.keep_changes(job.take_changes());
554 let Some(end) = job.take_end() else { continue };
555 self.0.jobs.borrow_mut().retain(|running| running.id() != job.id());
556 let action = self.action(job.action());
557 if let Some(action) = &action {
558 action.set_enabled(crate::REASON_RUNNING, true);
559 }
560 match end {
561 Ok(()) => {
562 if let Some(action) = action {
563 action.run_notice(&self.context_for(ActionInput::None));
564 }
565 }
566 Err(error) => self.show_message(crate::Message::new(error, crate::MessageKind::Error)),
567 }
568 }
569 changed
570 }
571
572 fn keep_changes(&self, changes: Vec<crate::JobChange>) -> bool {
573 let space = self.space();
574 let mut wrote = false;
575 let mut failed = None;
576 for change in changes {
577 let done = match change {
578 crate::JobChange::Save(record) => space.borrow_mut().save(record).map_err(|e| e.to_string()),
579 crate::JobChange::Delete(record) => {
580 let entities = self.0.entities.borrow().clone();
581 let key = entities
582 .iter()
583 .find(|entity| entity.name == record.entity())
584 .and_then(|entity| entity.key_field())
585 .map(|field| record.get(&field.name));
586 match key {
587 Some(key) => {
588 space.borrow_mut().delete(record.entity(), &key).map_err(|e| e.to_string())
589 }
590 None => Err(format!("{} has no key to remove it by", record.entity())),
591 }
592 }
593 crate::JobChange::Tell(text, kind) => {
594 self.show_message(crate::Message::new(text, kind));
595 continue;
596 }
597 };
598 match done {
599 Ok(()) => wrote = true,
600 Err(error) => {
601 failed = Some(error);
602 break;
603 }
604 }
605 }
606 if let Some(error) = failed {
607 space.borrow_mut().rollback();
608 self.show_message(crate::Message::new(error, crate::MessageKind::Error));
609 return false;
610 }
611 if wrote && let Err(error) = space.borrow_mut().commit() {
612 space.borrow_mut().rollback();
613 self.show_message(crate::Message::new(error.to_string(), crate::MessageKind::Error));
614 return false;
615 }
616 wrote
617 }
618
619 pub fn open_popup_on(&self, action: &str, view: &str) {
622 self.0.popup.set(Some((action.to_owned(), view.to_owned())));
623 }
624
625 pub fn prepare_popup(&self, object: &mut Object) {
627 let Some((id, _)) = self.0.popup.get() else { return };
628 if let Some(action) = self.action(&id) {
629 action.run_prepare(&self.context_for(ActionInput::None), object);
630 }
631 }
632
633 pub fn accept_popup(&self, input: ActionInput) -> Result<ActionResult, ActionError> {
635 let (id, _) = self.0.popup.get().ok_or_else(|| ActionError::Unknown("popup".to_owned()))?;
636 let action = self.action(&id).ok_or(ActionError::Unknown(id))?;
637 let cx = self.context_for(input);
638 let done = self.finish(&action, &cx);
639 if done.is_ok() {
641 self.0.popup.set(None);
642 }
643 done
644 }
645
646 pub fn cancel_popup(&self) {
648 if let Some((id, _)) = self.0.popup.get()
649 && let Some(action) = self.action(&id)
650 {
651 action.run_cancel(&self.context_for(ActionInput::None));
652 }
653 self.0.popup.set(None);
654 }
655
656 pub fn close(&self) {
658 self.set_view(None);
659 for slot in &self.0.views {
660 self.turn(slot, false);
661 }
662 for slot in self.0.windows.iter().rev() {
663 if slot.controller.borrow().contexts().contains(&self.0.context) {
664 slot.controller.borrow_mut().deactivated(self);
665 }
666 }
667 }
668}