Skip to main content

ravenrs_app_ui/
session.rs

1//! What the screens of a running application share.
2
3use std::cell::{Cell, RefCell};
4use std::collections::{BTreeMap, BTreeSet};
5use std::rc::Rc;
6
7use ravenrs_app::{
8    Action, ActionContext, ActionInput, ActionResult, Application, AttachmentStore, CONTEXT_DELETE,
9    CONTEXT_SAVE, Criteria, EntityInfo, FieldKind, Frame, FrameContext, Message, MessageKind, ModelLayer,
10    ModelNode, ModelStore, Object, ObjectSpace, Query, RuleWords, Severity, Value, ViewInfo, ViewKind,
11    Violation, merge_changes, validate,
12};
13use ravenrs_app_a11y::{AccessWords, Announcer};
14use ravenrs_app_i18n::{Language, Texts};
15use ravenrs_reactive::State;
16use ravenrs_widgets::{ConfirmDialog, Date, FilePicker, ToastExtras, Toaster, Toasts, Tone};
17
18use crate::editors::EditorRegistry;
19use crate::values::key_text;
20
21/// The words the generated screens use.
22#[derive(Clone, Debug)]
23#[non_exhaustive]
24#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
25pub struct Words {
26    /// Creates a record.
27    pub new: String,
28    /// Removes what is selected.
29    pub delete: String,
30    /// Keeps the changes.
31    pub save: String,
32    /// Keeps the changes and leaves.
33    pub save_and_close: String,
34    /// Keeps the changes and starts another.
35    pub save_and_new: String,
36    /// Throws away the changes and leaves.
37    pub cancel: String,
38    /// The record before.
39    pub previous: String,
40    /// The record after.
41    pub next: String,
42    /// Reads again.
43    pub refresh: String,
44    /// Goes to the screen before.
45    pub back: String,
46    /// The question before removing.
47    pub delete_question: String,
48    /// What the removal does.
49    pub delete_detail: String,
50    /// The question before losing changes.
51    pub discard_question: String,
52    /// What losing changes does.
53    pub discard_detail: String,
54    /// Agrees to lose them.
55    pub discard: String,
56    /// A record being created.
57    pub new_record: String,
58    /// The menu of the ways to see a list.
59    pub variant: String,
60    /// Puts an existing record on a list inside a form.
61    pub link: String,
62    /// Takes a record off a list inside a form, without removing it.
63    pub unlink: String,
64    /// Where a list inside a form would be, while its record is not kept yet.
65    pub save_first: String,
66    /// The menu of the filters ready on a list.
67    pub filter: String,
68    /// Makes a new record from the one selected.
69    pub clone: String,
70    /// Why a record cannot be cloned while it has changes.
71    pub clone_unsaved: String,
72    /// Takes a file out, to wherever people say.
73    pub save_file: String,
74    /// The question when rules only warn.
75    pub warning_question: String,
76    /// Goes on in spite of what rules warn about.
77    pub warning_answer: String,
78    /// The question when someone else changed the same fields, where `{0}` is which ones.
79    pub conflict_question: String,
80    /// What keeping one's own changes does, where `{0}` is which fields.
81    pub conflict_detail: String,
82    /// Keeps one's own changes over the other person's.
83    pub conflict_answer: String,
84    /// What a way of showing a list says when something it needs is missing.
85    pub list_editor_needs: String,
86    /// A row of a timeline for records that point to no resource.
87    pub no_resource: String,
88    /// The series of a chart that counts records.
89    pub chart_count: String,
90    /// Things to be reminded of.
91    pub reminders: String,
92    /// Background work that cannot say what it is doing.
93    pub working: String,
94    /// Background work asked to stop, until it does.
95    pub stopping: String,
96    /// Reminds again later.
97    pub snooze: String,
98    /// Stops reminding.
99    pub dismiss: String,
100    /// Opens what a reminder is about.
101    pub open: String,
102    /// A field that is on.
103    pub yes: String,
104    /// A field that is off.
105    pub no: String,
106    /// What people are told when something is refused.
107    pub access_denied: String,
108    /// What shows in place of a field the person cannot read.
109    pub protected_value: String,
110    /// Enters the application.
111    pub sign_in: String,
112    /// Leaves the application.
113    pub sign_out: String,
114    /// Says signing in did not work, without saying why.
115    pub sign_in_failed: String,
116    /// The name someone signs in with.
117    pub user_name: String,
118    /// Their password.
119    pub password: String,
120    /// The password they have now.
121    pub current_password: String,
122    /// The password they want.
123    pub new_password: String,
124    /// The same again.
125    pub repeat_password: String,
126    /// Signing in could not be tried.
127    pub sign_in_unavailable: String,
128    /// The two are not the same.
129    pub passwords_differ: String,
130    /// It is too short.
131    pub password_too_short: String,
132    /// The current one is not right.
133    pub password_wrong_current: String,
134    /// It was changed.
135    pub password_saved: String,
136    /// Keeps the new password.
137    pub save_password: String,
138    /// Changes one's own password.
139    pub change_password: String,
140    /// Gives someone a password.
141    pub set_password: String,
142    /// Why they have to choose one now.
143    pub must_change_password: String,
144    /// What the first screen of a new installation says.
145    pub first_user: String,
146    /// Creates the first person.
147    pub create_user: String,
148    /// The editor of the application's model.
149    pub model_editor: String,
150    /// Looks for a node of the model.
151    pub search_model: String,
152    /// What shows before a node is picked.
153    pub pick_model_node: String,
154    /// Applies what was typed.
155    pub apply: String,
156    /// Throws away the person's own changes to the model.
157    pub restore_model: String,
158    /// Why what is on a form cannot be kept any more.
159    pub record_gone: String,
160    /// What fails a rule.
161    pub rules: RuleWords,
162    /// In front of money.
163    pub money_prefix: String,
164    /// Between thousands.
165    pub thousands: char,
166    /// Before decimals.
167    pub decimal: char,
168}
169
170impl Default for Words {
171    fn default() -> Self {
172        Self::from_texts(&Texts::new(Language::English))
173    }
174}
175
176#[cfg_attr(feature = "live", ravenrs_live::expose)]
177impl Words {
178    /// The texts of a catalog, in its language. What it does not have comes from English.
179    pub fn from_texts(texts: &Texts) -> Self {
180        Self {
181            new: texts.get("ui.new"),
182            delete: texts.get("ui.delete"),
183            save: texts.get("ui.save"),
184            save_and_close: texts.get("ui.save_and_close"),
185            save_and_new: texts.get("ui.save_and_new"),
186            cancel: texts.get("ui.cancel"),
187            previous: texts.get("ui.previous"),
188            next: texts.get("ui.next"),
189            refresh: texts.get("ui.refresh"),
190            back: texts.get("ui.back"),
191            delete_question: texts.get("ui.delete_question"),
192            delete_detail: texts.get("ui.delete_detail"),
193            discard_question: texts.get("ui.discard_question"),
194            discard_detail: texts.get("ui.discard_detail"),
195            discard: texts.get("ui.discard"),
196            new_record: texts.get("ui.new_record"),
197            variant: texts.get("ui.variant"),
198            link: texts.get("ui.link"),
199            unlink: texts.get("ui.unlink"),
200            save_first: texts.get("ui.save_first"),
201            filter: texts.get("ui.filter"),
202            clone: texts.get("ui.clone"),
203            clone_unsaved: texts.get("ui.clone_unsaved"),
204            save_file: texts.get("ui.save_file"),
205            warning_question: texts.get("ui.warning_question"),
206            warning_answer: texts.get("ui.warning_answer"),
207            conflict_question: texts.get("ui.conflict_question"),
208            conflict_detail: texts.get("ui.conflict_detail"),
209            conflict_answer: texts.get("ui.conflict_answer"),
210            list_editor_needs: texts.get("ui.list_editor_needs"),
211            no_resource: texts.get("ui.no_resource"),
212            chart_count: texts.get("ui.chart_count"),
213            reminders: texts.get("ui.reminders"),
214            working: texts.get("ui.working"),
215            stopping: texts.get("ui.stopping"),
216            snooze: texts.get("ui.snooze"),
217            dismiss: texts.get("ui.dismiss"),
218            open: texts.get("ui.open"),
219            yes: texts.get("ui.yes"),
220            no: texts.get("ui.no"),
221            access_denied: texts.get("ui.access_denied"),
222            protected_value: texts.get("ui.protected_value"),
223            sign_in: texts.get("ui.sign_in"),
224            sign_out: texts.get("ui.sign_out"),
225            sign_in_failed: texts.get("ui.sign_in_failed"),
226            user_name: texts.get("ui.user_name"),
227            password: texts.get("ui.password"),
228            current_password: texts.get("ui.current_password"),
229            new_password: texts.get("ui.new_password"),
230            repeat_password: texts.get("ui.repeat_password"),
231            sign_in_unavailable: texts.get("ui.sign_in_unavailable"),
232            passwords_differ: texts.get("ui.passwords_differ"),
233            password_too_short: texts.get("ui.password_too_short"),
234            password_wrong_current: texts.get("ui.password_wrong_current"),
235            password_saved: texts.get("ui.password_saved"),
236            save_password: texts.get("ui.save_password"),
237            change_password: texts.get("ui.change_password"),
238            set_password: texts.get("ui.set_password"),
239            must_change_password: texts.get("ui.must_change_password"),
240            first_user: texts.get("ui.first_user"),
241            create_user: texts.get("ui.create_user"),
242            model_editor: texts.get("ui.model_editor"),
243            search_model: texts.get("ui.search_model"),
244            pick_model_node: texts.get("ui.pick_model_node"),
245            apply: texts.get("ui.apply"),
246            restore_model: texts.get("ui.restore_model"),
247            record_gone: texts.get("ui.record_gone"),
248            rules: RuleWords::from_texts(texts),
249            money_prefix: texts.get("format.money_prefix"),
250            thousands: texts.char("format.thousands", ','),
251            decimal: texts.char("format.decimal", '.'),
252        }
253    }
254
255    /// In Brazilian Portuguese.
256    pub fn pt_br() -> Self {
257        Self::from_texts(&Texts::new(Language::BrazilianPortuguese))
258    }
259}
260
261/// The record a form is editing: what it was, and one state per field.
262#[derive(Debug)]
263pub(crate) struct FormState {
264    pub entity: EntityInfo,
265    pub original: Object,
266    pub is_new: bool,
267    pub fields: BTreeMap<String, State<Value>>,
268}
269
270#[cfg_attr(feature = "live", ravenrs_live::expose)]
271impl FormState {
272    fn new(entity: EntityInfo, original: Object, is_new: bool) -> Self {
273        let fields = entity
274            .fields
275            .iter()
276            .filter(|f| f.calculated.is_none())
277            .map(|f| (f.name.clone(), State::new(original.get(&f.name))))
278            .collect();
279        Self { entity, original, is_new, fields }
280    }
281
282    /// The record with what the fields hold now, without reading them for the build.
283    pub fn current(&self) -> Object {
284        let mut object = self.original.clone();
285        for (name, value) in &self.fields {
286            object.set(name, value.peek().clone());
287        }
288        object
289    }
290
291    /// The same, reading the fields for the build, so what depends on them follows.
292    pub fn watched(&self) -> Object {
293        let mut object = self.original.clone();
294        for (name, value) in &self.fields {
295            object.set(name, value.get());
296        }
297        object
298    }
299
300    pub fn is_dirty(&self) -> bool {
301        self.fields.iter().any(|(name, value)| *value.peek() != self.original.get(name))
302    }
303}
304
305/// Takes a file, by its name and bytes.
306pub(crate) type FileHandler = Rc<dyn Fn(String, Vec<u8>)>;
307
308/// Takes an address to open.
309pub(crate) type UrlHandler = Rc<dyn Fn(String)>;
310
311/// Makes a unit of work.
312pub(crate) type SpaceFactory = Rc<dyn Fn() -> Rc<RefCell<dyn ObjectSpace>>>;
313
314/// Says what a field of a form is called, for a field and the record as it stands, when that
315/// depends on the record; nothing means the usual.
316pub type FormCaption = Rc<dyn Fn(&str, &Object) -> Option<String>>;
317
318/// Gives the records of a collection with no storage, from the record that has it.
319pub type CollectionSource = Rc<dyn Fn(&Object) -> Vec<Object>>;
320
321/// Makes a control an application registered.
322pub(crate) type ControlFactory = Rc<dyn Fn() -> ravenrs_widgets::AnyView>;
323
324/// Something shown in a window over the screens, with its title.
325pub(crate) type Presented = (String, ControlFactory);
326
327struct SessionInner {
328    application: Rc<RefCell<Application>>,
329    new_space: SpaceFactory,
330    frame: Frame,
331    words: Words,
332    editors: EditorRegistry,
333    /// Goes up whenever records may have changed, so lists read again.
334    revision: State<u64>,
335    form: State<Option<Rc<FormState>>>,
336    /// A save failed its rules, so the form says what is wrong as people fix it.
337    checking: State<bool>,
338    /// A save went through with notes from its rules, which the form keeps showing.
339    noting: State<bool>,
340    toasts: Toasts,
341    /// Where entities no storage keeps get their records.
342    record_sources: Rc<RefCell<BTreeMap<String, crate::list_view::ListSource>>>,
343    /// Where lists no storage keeps get theirs, by entity and field.
344    collection_sources: Rc<RefCell<BTreeMap<(String, String), CollectionSource>>>,
345    /// Where it came from, for going back.
346    history: State<Vec<String>>,
347    error: State<Option<String>>,
348    confirm: ConfirmDialog,
349    on_file: Rc<RefCell<Option<FileHandler>>>,
350    on_url: Rc<RefCell<Option<UrlHandler>>>,
351    controls: Rc<RefCell<BTreeMap<String, ControlFactory>>>,
352    presented: State<Option<Presented>>,
353    /// Something that takes the place of the view, as an editor of its own does.
354    full: State<Option<ControlFactory>>,
355    /// How many reminders are due, and whether they are showing.
356    reminders: State<(usize, bool)>,
357    form_caption: RefCell<Option<FormCaption>>,
358    /// Whether the form repeats, above it, what it says on each field.
359    lists_field_errors: Cell<bool>,
360    list_editors: Rc<RefCell<BTreeMap<String, crate::list_view::ListEditorFactory>>>,
361    attachment_store: Rc<RefCell<Option<Rc<dyn AttachmentStore>>>>,
362    texts: Rc<RefCell<Texts>>,
363    model_store: Rc<RefCell<Option<Rc<dyn ModelStore>>>>,
364    /// What the list of the popup about to open has to meet.
365    popup_filter: RefCell<Option<Criteria>>,
366    /// Records that come with the one on the form, kept when it is.
367    pending: RefCell<Vec<Object>>,
368    /// The fields the keyboard has left, whose errors a form that checks on leaving shows.
369    left: State<BTreeSet<String>>,
370    today: Cell<Date>,
371    /// The keys of the list a form came from, for moving between records.
372    siblings: RefCell<Vec<Value>>,
373    /// Who is signed in and how they leave.
374    account: RefCell<Option<Account>>,
375    /// Where screen readers hear what changed, and in which words.
376    announcer: Announcer,
377    access_words: Rc<RefCell<AccessWords>>,
378}
379
380/// Who is signed in, for the window to show.
381#[derive(Clone)]
382pub(crate) struct Account {
383    pub(crate) name: String,
384    pub(crate) sign_out: Rc<dyn Fn()>,
385}
386
387/// What the screens of a running application share: the frame, the current form, and what
388/// has to happen next. A handle.
389#[derive(Clone)]
390#[cfg_attr(feature = "live", ravenrs_live::expose(part = "fields"))]
391pub struct Session(Rc<SessionInner>);
392
393impl std::fmt::Debug for Session {
394    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
395        f.debug_struct("Session").field("frame", &self.0.frame).finish_non_exhaustive()
396    }
397}
398
399/// How a frame finds its session.
400struct SessionLink(std::rc::Weak<SessionInner>);
401
402#[cfg_attr(feature = "live", ravenrs_live::expose(part = "part1"))]
403impl Session {
404    pub(crate) fn new(
405        application: Application,
406        new_space: SpaceFactory,
407        words: Words,
408        editors: EditorRegistry,
409        today: Date,
410    ) -> Self {
411        let frame = application.new_frame(FrameContext::Main, new_space());
412        Self::linked(SessionInner {
413            application: Rc::new(RefCell::new(application)),
414            new_space,
415            frame,
416            words,
417            editors,
418            revision: State::new(0),
419            form: State::new(None),
420            checking: State::new(false),
421            noting: State::new(false),
422            toasts: Toasts::new(),
423            record_sources: Rc::default(),
424            collection_sources: Rc::default(),
425            history: State::new(Vec::new()),
426            error: State::new(None),
427            confirm: ConfirmDialog::new(),
428            on_file: Rc::default(),
429            on_url: Rc::default(),
430            controls: Rc::default(),
431            presented: State::new(None),
432            full: State::new(None),
433            reminders: State::new((0, false)),
434            form_caption: RefCell::default(),
435            lists_field_errors: Cell::new(true),
436            list_editors: Rc::default(),
437            attachment_store: Rc::default(),
438            texts: Rc::default(),
439            model_store: Rc::default(),
440            popup_filter: RefCell::default(),
441            pending: RefCell::default(),
442            left: State::new(BTreeSet::new()),
443            today: Cell::new(today),
444            siblings: RefCell::default(),
445            account: RefCell::default(),
446            announcer: Announcer::new(),
447            access_words: Rc::default(),
448        })
449    }
450
451    fn linked(inner: SessionInner) -> Self {
452        let is_first = inner.list_editors.borrow().is_empty();
453        let inner = Rc::new(inner);
454        let frame = inner.frame.clone();
455        frame.set_extension(Rc::new(SessionLink(Rc::downgrade(&inner))));
456        // Declared actions say what is wrong in the application's words.
457        frame.set_extension(Rc::new(inner.words.rules.clone()));
458        let telling = Rc::downgrade(&inner);
459        frame.set_messenger(move |message| {
460            if let Some(inner) = telling.upgrade() {
461                Session(inner).show_message(message);
462            }
463        });
464        frame.set_validator(|contexts, cx| {
465            let Some(session) = Session::of(cx) else { return Ok(()) };
466            let form = session.form();
467            let records = match (&form, cx.selected.is_empty()) {
468                (Some(form), true) => vec![form.current()],
469                _ => cx.selected.clone(),
470            };
471            let mut messages = Vec::new();
472            for record in &records {
473                for context in contexts {
474                    let found = session.violations_of(record, context);
475                    messages.extend(found.into_iter().filter(Violation::is_error).map(|v| v.message));
476                }
477            }
478            if messages.is_empty() { Ok(()) } else { Err(messages.join("; ")) }
479        });
480        let session = Self(inner);
481        if is_first {
482            crate::list_editors::register(&session);
483        }
484        session
485    }
486
487    /// A session of its own for a frame inside this one or over it: its own form and unit of
488    /// work, the same application, and lists that follow what the others change.
489    pub(crate) fn child(&self, context: FrameContext) -> Self {
490        let parent = &self.0;
491        Self::linked(SessionInner {
492            application: parent.application.clone(),
493            new_space: parent.new_space.clone(),
494            frame: self.new_frame(context),
495            words: parent.words.clone(),
496            editors: parent.editors.clone(),
497            revision: parent.revision.clone(),
498            form: State::new(None),
499            checking: State::new(false),
500            noting: State::new(false),
501            toasts: parent.toasts.clone(),
502            record_sources: parent.record_sources.clone(),
503            collection_sources: parent.collection_sources.clone(),
504            history: State::new(Vec::new()),
505            error: State::new(None),
506            confirm: ConfirmDialog::new(),
507            on_file: parent.on_file.clone(),
508            on_url: parent.on_url.clone(),
509            controls: parent.controls.clone(),
510            presented: parent.presented.clone(),
511            full: parent.full.clone(),
512            reminders: parent.reminders.clone(),
513            form_caption: RefCell::new(parent.form_caption.borrow().clone()),
514            lists_field_errors: Cell::new(parent.lists_field_errors.get()),
515            list_editors: parent.list_editors.clone(),
516            attachment_store: parent.attachment_store.clone(),
517            texts: parent.texts.clone(),
518            model_store: parent.model_store.clone(),
519            popup_filter: RefCell::default(),
520            pending: RefCell::default(),
521            left: State::new(BTreeSet::new()),
522            today: Cell::new(parent.today.get()),
523            siblings: RefCell::default(),
524            account: RefCell::default(),
525            announcer: parent.announcer.clone(),
526            access_words: parent.access_words.clone(),
527        })
528    }
529
530    /// A session for lists and actions inside a screen an application made itself, with no window
531    /// around them. Showing a view is up to it, with [`show_list`](Self::show_list).
532    pub fn embedded<S: ObjectSpace + 'static>(
533        application: Application,
534        space: impl Fn() -> S + 'static,
535        words: Words,
536    ) -> Self {
537        let factory: SpaceFactory =
538            Rc::new(move || Rc::new(RefCell::new(space())) as Rc<RefCell<dyn ObjectSpace>>);
539        Self::standalone(application, factory, words)
540    }
541
542    /// Shows this view of the model in the frame, without asking anything.
543    pub fn show_list(&self, id: &str) {
544        self.show_view_now(id);
545    }
546
547    /// What asks before actions run, for a screen with no window of the framework around it.
548    pub fn confirmations(&self) -> ConfirmDialog {
549        self.0.confirm.clone()
550    }
551
552    /// What shows messages, for a screen with no window of the framework around it.
553    pub fn toaster(&self) -> Toaster {
554        Toaster::new(self.0.toasts.clone())
555    }
556
557    /// Tells people something, which goes away by itself.
558    pub fn show_message(&self, message: Message) {
559        let tone = match message.kind {
560            MessageKind::Success => Tone::Success,
561            MessageKind::Warning => Tone::Warning,
562            MessageKind::Error => Tone::Danger,
563            _ => Tone::Accent,
564        };
565        let mut extras = ToastExtras::new();
566        if let Some((label, run)) = message.action {
567            extras = extras.action(label, move || run());
568        }
569        if let Some(run) = message.on_close {
570            extras = extras.on_close(move || run());
571        }
572        self.0.toasts.show_with(self.title(), message.text, tone, message.seconds.unwrap_or(4.0), extras);
573    }
574
575    /// Gives the records of an entity no storage keeps, for its list and its forms.
576    pub fn use_records(&self, entity: impl Into<String>, source: impl Fn() -> Vec<Object> + 'static) {
577        self.0.record_sources.borrow_mut().insert(entity.into(), Rc::new(source));
578    }
579
580    /// Gives the records of a list no storage keeps, inside a form of this entity.
581    pub fn use_collection(
582        &self,
583        entity: impl Into<String>,
584        field: impl Into<String>,
585        source: impl Fn(&Object) -> Vec<Object> + 'static,
586    ) {
587        self.0.collection_sources.borrow_mut().insert((entity.into(), field.into()), Rc::new(source));
588    }
589
590    pub(crate) fn record_source(&self, entity: &str) -> Option<crate::list_view::ListSource> {
591        self.0.record_sources.borrow().get(entity).cloned()
592    }
593
594    pub(crate) fn collection_source(&self, entity: &str, field: &str) -> Option<CollectionSource> {
595        self.0.collection_sources.borrow().get(&(entity.to_owned(), field.to_owned())).cloned()
596    }
597
598    /// What people see in the model, as CSV with an empty column for its translation.
599    pub fn export_captions(&self) -> String {
600        self.0.application.borrow().export_captions()
601    }
602
603    /// Takes the CSV of [`export_captions`](Self::export_captions), filled in, as the model in this
604    /// language, which [`set_language`](Self::set_language) picks.
605    pub fn import_captions(&self, language: &str, csv: &str) -> Result<(), String> {
606        self.0.application.borrow_mut().import_captions(language, csv).map_err(|e| e.0)
607    }
608
609    /// Registers a control by name, for a dashboard to show.
610    pub fn register_control(
611        &self,
612        name: impl Into<String>,
613        control: impl Fn() -> ravenrs_widgets::AnyView + 'static,
614    ) {
615        self.0.controls.borrow_mut().insert(name.into(), Rc::new(control));
616    }
617
618    pub(crate) fn control(&self, name: &str) -> Option<ControlFactory> {
619        self.0.controls.borrow().get(name).cloned()
620    }
621
622    pub(crate) fn account(&self) -> Option<Account> {
623        self.0.account.borrow().clone()
624    }
625
626    pub(crate) fn use_account(&self, account: Account) {
627        *self.0.account.borrow_mut() = Some(account);
628    }
629
630    /// Whether this navigation item leads somewhere this person can go. It only hides: what
631    /// protects records is the secured space.
632    pub(crate) fn can_open(&self, group: Option<&str>, item: &str, view: &str) -> bool {
633        let Some(security) = self.0.frame.security() else { return true };
634        let model = self.model();
635        let entity = model.at(&["Views", view]).and_then(|node| node.text("Entity"));
636        security.with(|permissions| {
637            permissions.can_navigate(group, item)
638                && entity.is_none_or(|entity| permissions.can(ravenrs_app::Operation::Read, entity, None))
639                && (view != crate::security::MODEL_EDITOR_VIEW || permissions.can_edit_model())
640        })
641    }
642
643    /// Changes a setting of the model in the person's own layer, if they may, and keeps it.
644    pub fn set_model_setting(&self, path: &[&str], name: &str, value: &str) -> Result<(), String> {
645        self.check_model_permission()?;
646        self.0.application.borrow_mut().set_typed_setting(path, name, value).map_err(|e| e.0)?;
647        self.keep_settings()?;
648        self.touch();
649        Ok(())
650    }
651
652    /// Throws away the person's own changes to the model, if they may.
653    pub fn restore_model_settings(&self) -> Result<(), String> {
654        self.check_model_permission()?;
655        self.0.application.borrow_mut().clear_user_settings().map_err(|e| e.0)?;
656        self.keep_settings()?;
657        self.touch();
658        Ok(())
659    }
660
661    fn check_model_permission(&self) -> Result<(), String> {
662        match self.0.frame.security() {
663            Some(security) if !security.with(ravenrs_app::Permissions::can_edit_model) => {
664                Err(self.0.words.access_denied.clone())
665            }
666            _ => Ok(()),
667        }
668    }
669
670    /// Shows this in place of the view, with the navigation still around it, until
671    /// [`close_full`](Self::close_full) or going anywhere else.
672    pub fn show_full(&self, content: impl Fn() -> ravenrs_widgets::AnyView + 'static) {
673        self.0.full.set(Some(Rc::new(content)));
674    }
675
676    /// Goes back to the view from what [`show_full`](Self::show_full) showed.
677    pub fn close_full(&self) {
678        self.0.full.set(None);
679    }
680
681    pub(crate) fn entities_with_reminders(&self) -> Vec<EntityInfo> {
682        self.0.application.borrow().entities().iter().filter(|e| e.reminder.is_some()).cloned().collect()
683    }
684
685    pub(crate) fn reminders(&self) -> &State<(usize, bool)> {
686        &self.0.reminders
687    }
688
689    pub(crate) fn full(&self) -> Option<ControlFactory> {
690        self.0.full.get()
691    }
692
693    /// Closes what [`present`](Self::present) showed.
694    pub fn close_presented(&self) {
695        self.0.presented.set(None);
696    }
697
698    /// Shows this in a window over the screens, until it is closed.
699    pub fn present(
700        &self,
701        title: impl Into<String>,
702        content: impl Fn() -> ravenrs_widgets::AnyView + 'static,
703    ) {
704        self.0.presented.set(Some((title.into(), Rc::new(content))));
705    }
706
707    pub(crate) fn presented(&self) -> &State<Option<Presented>> {
708        &self.0.presented
709    }
710
711    /// Says what a field of a form is called when that depends on the record.
712    pub fn set_form_caption(&self, caption: FormCaption) {
713        *self.0.form_caption.borrow_mut() = Some(caption);
714    }
715
716    pub(crate) fn form_caption(&self) -> Option<FormCaption> {
717        self.0.form_caption.borrow().clone()
718    }
719
720    /// Keeps what is wrong with a field on the field alone, for a form short enough to see it.
721    pub(crate) fn set_lists_field_errors(&self, lists: bool) {
722        self.0.lists_field_errors.set(lists);
723    }
724
725    pub(crate) fn lists_field_errors(&self) -> bool {
726        self.0.lists_field_errors.get()
727    }
728
729    /// The record on the form as it stands, following its fields.
730    pub fn form_record(&self) -> Option<Object> {
731        self.form().map(|form| form.watched())
732    }
733
734    /// What this list offers under `part`, `Variants` or `Filters`, as their id and what people see.
735    pub(crate) fn choices(&self, view: &str, part: &str) -> Vec<(String, String)> {
736        let model = self.model();
737        model.at(&["Views", view, part]).map_or_else(Vec::new, |choices| {
738            choices
739                .in_order()
740                .iter()
741                .map(|item| (item.id.clone(), item.text("Caption").unwrap_or(&item.id).to_owned()))
742                .collect()
743        })
744    }
745
746    /// The choice in force under `part`: the one picked or else the first.
747    fn chosen(model: &ModelNode, view: &str, part: &str) -> Option<ModelNode> {
748        let choices = model.at(&["Views", view, part])?;
749        let current = model.at(&["Views", view]).and_then(|node| node.text(&format!("Current{part}")));
750        let order = choices.in_order();
751        current
752            .and_then(|id| order.iter().find(|item| item.id == id))
753            .or(order.first())
754            .map(|&item| item.clone())
755    }
756
757    /// What the model asks of the records of this list: its criteria, and the way to see it and the
758    /// filter picked.
759    pub(crate) fn model_filter(&self, view: &str) -> Option<Criteria> {
760        let model = self.model();
761        let own = model.at(&["Views", view]).and_then(|node| node.text("Criteria")).map(str::to_owned);
762        let picked = ["Variants", "Filters"].into_iter().map(|part| {
763            Self::chosen(&model, view, part).and_then(|item| item.text("Filter").map(str::to_owned))
764        });
765        std::iter::once(own)
766            .chain(picked)
767            .flatten()
768            .filter_map(|text| Criteria::parse(&text).ok())
769            .reduce(Criteria::and)
770    }
771
772    /// The dashboard to show for this one: the one people changed it to, if any.
773    pub(crate) fn dashboard_shown(&self, view: &str) -> ViewInfo {
774        let model = self.model();
775        let id = Self::chosen(&model, view, "Variants")
776            .and_then(|item| item.text("View").map(str::to_owned))
777            .unwrap_or_else(|| view.to_owned());
778        ViewInfo::from_model(&model, &id).unwrap_or_else(|_| ViewInfo::new(view, ViewKind::Dashboard, None))
779    }
780
781    /// The list editor the way picked to see this list asks for, if it says.
782    pub(crate) fn variant_editor(&self, view: &str) -> Option<String> {
783        let model = self.model();
784        Self::chosen(&model, view, "Variants").and_then(|item| item.text("EditorType").map(str::to_owned))
785    }
786
787    /// The fields the way picked to see this list shows, if it says.
788    pub(crate) fn variant_columns(&self, view: &str) -> Option<Vec<String>> {
789        let item = Self::chosen(&self.model(), view, "Variants")?;
790        item.get("Columns").map(|columns| columns.in_order().iter().map(|c| c.id.clone()).collect())
791    }
792
793    /// Picks under `part`, which stays in the person's own layer of the model.
794    pub(crate) fn choose(&self, view: &str, part: &str, choice: &str) -> Result<(), String> {
795        self.0
796            .application
797            .borrow_mut()
798            .set_user_setting(&["Views", view], &format!("Current{part}"), choice)
799            .map_err(|e| e.0)?;
800        self.keep_settings()?;
801        self.touch();
802        Ok(())
803    }
804
805    /// Keeps the person's own layer of the model there, and reads what is there now.
806    pub fn use_model_store(&self, store: Rc<dyn ModelStore>) -> Result<(), String> {
807        {
808            let mut application = self.0.application.borrow_mut();
809            application.layers_mut().load(&ModelLayer::User, &*store).map_err(|e| e.0)?;
810            application.refresh_model().map_err(|e| e.0)?;
811        }
812        *self.0.model_store.borrow_mut() = Some(store);
813        self.touch();
814        Ok(())
815    }
816
817    fn keep_settings(&self) -> Result<(), String> {
818        let store = self.0.model_store.borrow().clone();
819        let Some(store) = store else { return Ok(()) };
820        let mut application = self.0.application.borrow_mut();
821        application.layers_mut().save(&ModelLayer::User, &*store).map_err(|e| e.0)
822    }
823
824    /// Shows the model in this language, from the translations given.
825    pub fn set_language(&self, language: &str) -> Result<(), String> {
826        self.0.application.borrow_mut().set_language(language).map_err(|e| e.0)?;
827        self.touch();
828        Ok(())
829    }
830
831    /// Registers how a list can be shown, for a view's `EditorType` in the model to pick by name,
832    /// or `DefaultListEditor` on `Views` for all of them.
833    pub fn register_list_editor(
834        &self,
835        name: impl Into<String>,
836        editor: impl Fn(&crate::ListEditorRequest) -> ravenrs_widgets::AnyView + 'static,
837    ) {
838        self.0.list_editors.borrow_mut().insert(name.into(), Rc::new(editor));
839    }
840
841    /// Shows this list with the editor of this name, which stays in the person's own layer.
842    pub fn choose_list_editor(&self, view: &str, editor: &str) -> Result<(), String> {
843        self.0
844            .application
845            .borrow_mut()
846            .set_user_setting(&["Views", view], "EditorType", editor)
847            .map_err(|e| e.0)?;
848        self.keep_settings()?;
849        self.touch();
850        Ok(())
851    }
852
853    pub(crate) fn list_editor(&self, name: &str) -> Option<crate::list_view::ListEditorFactory> {
854        self.0.list_editors.borrow().get(name).cloned()
855    }
856
857    /// Keeps the content of attached files there, apart from the records. Without it, a file stays
858    /// inside its record's field.
859    pub fn use_attachment_store(&self, store: Rc<dyn AttachmentStore>) {
860        *self.0.attachment_store.borrow_mut() = Some(store);
861    }
862
863    pub(crate) fn attachment_store(&self) -> Option<Rc<dyn AttachmentStore>> {
864        self.0.attachment_store.borrow().clone()
865    }
866
867    pub(crate) fn set_popup_filter(&self, filter: Option<Criteria>) {
868        *self.0.popup_filter.borrow_mut() = filter;
869    }
870
871    pub(crate) fn popup_filter(&self) -> Option<Criteria> {
872        self.0.popup_filter.borrow().clone()
873    }
874
875    /// The keyboard left this field, so a form that checks on leaving can say what is wrong with it.
876    pub(crate) fn left_field(&self, field: &str) {
877        if !self.0.left.peek().contains(field) {
878            self.0.left.update(|left| {
879                left.insert(field.to_owned());
880            });
881        }
882    }
883
884    /// Opens on a new record with what this one holds, leaving out what its fields say stays
885    /// behind. Lists that belong to it come along, and are kept with it.
886    pub(crate) fn clone_record(&self, source: &Object) -> Result<(), String> {
887        let entity = self.entity(source.entity()).ok_or_else(|| source.entity().to_owned())?;
888        let view = self.detail_of(&entity)?;
889        if self.0.frame.view().is_some_and(|v| v.kind == ViewKind::List) {
890            self.0.history.update(|history| history.push(view.id.clone()));
891        }
892        self.show(view);
893        let mut pending = Vec::new();
894        let copy = self.copy_of(&entity, source, &mut pending)?;
895        *self.0.pending.borrow_mut() = pending;
896        self.0.form.set(Some(Rc::new(FormState::new(entity.clone(), copy, true))));
897        self.update_position(&entity);
898        Ok(())
899    }
900
901    /// A new record with what this one holds; what belongs to it goes in `pending`, pointing to it.
902    fn copy_of(
903        &self,
904        entity: &EntityInfo,
905        source: &Object,
906        pending: &mut Vec<Object>,
907    ) -> Result<Object, String> {
908        let space = self.0.frame.space();
909        let mut copy = space.borrow_mut().new_object(&entity.name).map_err(|e| e.to_string())?;
910        let key_name = entity.key_field().map(|key| key.name.clone()).unwrap_or_default();
911        let copied = entity.fields.iter().filter(|field| {
912            !field.is_key
913                && !field.non_cloneable
914                && field.calculated.is_none()
915                && !matches!(field.kind, FieldKind::Collection(_))
916        });
917        for field in copied {
918            copy.set(&field.name, source.get(&field.name));
919        }
920        for field in entity.fields.iter().filter(|field| field.aggregated) {
921            let FieldKind::Collection(target) = &field.kind else { continue };
922            let Some(child) = self.entity(target) else { continue };
923            let named = field.back_reference.as_deref();
924            let Some(back) = crate::nested::back_reference(self, entity, target, named) else { continue };
925            let filter = Criteria::property(&back).equals(Criteria::value(source.get(&key_name)));
926            let found = space.borrow().query(target, &Query::all().filter(filter));
927            for record in found.map_err(|e| e.to_string())? {
928                let mut child_copy = self.copy_of(&child, &record, pending)?;
929                child_copy.set(&back, copy.get(&key_name));
930                pending.push(child_copy);
931            }
932        }
933        Ok(copy)
934    }
935
936    /// What the person changed in the model, to keep between runs.
937    pub fn user_settings(&self) -> Option<String> {
938        let mut application = self.0.application.borrow_mut();
939        application.layers_mut().difference(&ModelLayer::User).map(|diff| diff.to_json())
940    }
941
942    /// Puts this record on the form, as it is, without looking in storage.
943    pub(crate) fn open_object(&self, entity: &EntityInfo, object: Object, is_new: bool) {
944        match self.detail_of(entity) {
945            Ok(view) => self.show(view),
946            Err(error) => return self.0.error.set(Some(error)),
947        }
948        self.0.form.set(Some(Rc::new(FormState::new(entity.clone(), object, is_new))));
949    }
950
951    /// Changes a field of the form, as a screen of its own around the form would.
952    pub fn set_form_value(&self, field: &str, value: Value) {
953        if let Some(state) = self.0.form.peek().as_ref().and_then(|form| form.fields.get(field).cloned()) {
954            state.set(value);
955        }
956    }
957
958    /// The record on the form, if it meets its rules. If not, the form says what is wrong.
959    pub(crate) fn check_form(&self) -> Option<(Object, bool)> {
960        let form = self.0.form.peek().clone()?;
961        let object = form.current();
962        if !self.violations_of(&object, CONTEXT_SAVE).iter().any(Violation::is_error) {
963            self.0.checking.set(false);
964            Some((object, form.is_new))
965        } else {
966            self.0.checking.set(true);
967            None
968        }
969    }
970
971    /// The session an action is running in.
972    pub fn of(cx: &ActionContext) -> Option<Self> {
973        Self::of_frame(&cx.frame)
974    }
975
976    /// The session of a frame.
977    pub fn of_frame(frame: &Frame) -> Option<Self> {
978        frame.extension::<SessionLink>().and_then(|link| link.0.upgrade()).map(Self)
979    }
980
981    /// The main frame.
982    pub fn frame(&self) -> Frame {
983        self.0.frame.clone()
984    }
985
986    /// The words of the screens.
987    pub fn words(&self) -> &Words {
988        &self.0.words
989    }
990
991    /// Every text of the application in its language, with what it added over the ones that come
992    /// with the framework, for words of its own by key.
993    pub fn texts(&self) -> Texts {
994        self.0.texts.borrow().clone()
995    }
996
997    /// The texts [`texts`](Self::texts) gives, for this session and the ones inside it.
998    pub fn use_texts(&self, texts: Texts) {
999        *self.0.texts.borrow_mut() = texts;
1000    }
1001
1002    pub(crate) fn editors(&self) -> &EditorRegistry {
1003        &self.0.editors
1004    }
1005
1006    /// The day pickers open on.
1007    pub(crate) fn today(&self) -> Date {
1008        self.0.today.get()
1009    }
1010
1011    /// The model as it stands.
1012    pub fn model(&self) -> Rc<ModelNode> {
1013        self.0.application.borrow().model()
1014    }
1015
1016    /// A session with no window around it, for a piece of a screen.
1017    pub(crate) fn standalone(application: Application, new_space: SpaceFactory, words: Words) -> Self {
1018        Self::with_editors(application, new_space, words, EditorRegistry::default())
1019    }
1020
1021    /// The same, with controls of its own.
1022    pub(crate) fn with_editors(
1023        application: Application,
1024        new_space: SpaceFactory,
1025        words: Words,
1026        editors: EditorRegistry,
1027    ) -> Self {
1028        let today = Date::from_days(ravenrs_app::local_now().days);
1029        Self::new(application, new_space, words, editors, today)
1030    }
1031
1032    /// Tells lists to read again, after something changed the records from outside.
1033    pub fn records_changed(&self) {
1034        self.touch();
1035    }
1036
1037    /// A frame of its own for a window over this one, with a unit of work of its own.
1038    pub(crate) fn new_frame(&self, context: FrameContext) -> Frame {
1039        self.0.application.borrow().new_frame(context, (self.0.new_space)())
1040    }
1041
1042    /// The title of the application.
1043    pub fn title(&self) -> String {
1044        self.0.application.borrow().title().to_owned()
1045    }
1046
1047    /// An entity by name.
1048    pub fn entity(&self, name: &str) -> Option<EntityInfo> {
1049        self.0.application.borrow().entities().iter().find(|e| e.name == name).cloned()
1050    }
1051
1052    /// Where screen readers hear what changed: saving, deleting, the screen that opened. Its
1053    /// region is in the window; an application says more with it.
1054    pub fn announcer(&self) -> &Announcer {
1055        &self.0.announcer
1056    }
1057
1058    /// The words of what screen readers hear, from the language of the application unless given.
1059    pub fn set_access_words(&self, words: AccessWords) {
1060        *self.0.access_words.borrow_mut() = words;
1061    }
1062
1063    /// The words of what screen readers hear.
1064    pub fn access_words(&self) -> AccessWords {
1065        self.0.access_words.borrow().clone()
1066    }
1067
1068    /// What names a record when it is announced: its default property, or what its entity is called.
1069    fn spoken_name(entity: &EntityInfo, object: &Object) -> String {
1070        entity
1071            .default_property()
1072            .map(|field| crate::values::display(field, &object.get(&field.name)))
1073            .filter(|name| !name.trim().is_empty())
1074            .unwrap_or_else(|| entity.caption.clone())
1075    }
1076
1077    /// Goes up whenever records may have changed; reading it builds again when they do.
1078    pub fn revision(&self) -> u64 {
1079        self.0.revision.get()
1080    }
1081
1082    pub(crate) fn touch(&self) {
1083        self.0.revision.update(|r| *r += 1);
1084    }
1085
1086    pub(crate) fn form(&self) -> Option<Rc<FormState>> {
1087        self.0.form.get()
1088    }
1089
1090    /// What this record fails in this context.
1091    pub fn violations_of(&self, record: &Object, context: &str) -> Vec<Violation> {
1092        let Some(entity) = self.entity(record.entity()) else { return Vec::new() };
1093        let space = self.0.frame.space();
1094        let space = space.borrow();
1095        validate(&entity, record, context, &*space, &self.words().rules)
1096    }
1097
1098    /// What the form fails, once a save has found something. Follows the fields.
1099    pub(crate) fn form_violations(&self) -> Vec<Violation> {
1100        let Some(form) = self.form() else { return Vec::new() };
1101        let found = |record: &Object| self.violations_of(record, CONTEXT_SAVE);
1102        if self.0.checking.get() || self.0.noting.get() {
1103            return found(&form.watched());
1104        }
1105        if !form.entity.checks_on_leave {
1106            return Vec::new();
1107        }
1108        let left = self.0.left.get();
1109        if left.is_empty() {
1110            return Vec::new();
1111        }
1112        // Only what the person already went through: what is still ahead is not an error yet.
1113        let mut all = found(&form.watched());
1114        all.retain(|violation| violation.field.as_ref().is_some_and(|field| left.contains(field)));
1115        all
1116    }
1117
1118    /// What went wrong last, in words for people.
1119    pub fn error(&self) -> Option<String> {
1120        self.0.error.get()
1121    }
1122
1123    pub(crate) fn set_error(&self, error: Option<String>) {
1124        self.0.error.set(error);
1125    }
1126
1127    pub(crate) fn confirm(&self) -> &ConfirmDialog {
1128        &self.0.confirm
1129    }
1130
1131    /// Hands exported files over to this, as a name and its bytes.
1132    pub(crate) fn set_on_file(&self, handler: FileHandler) {
1133        *self.0.on_file.borrow_mut() = Some(handler);
1134    }
1135
1136    /// Hands addresses to open over to this.
1137    pub(crate) fn set_on_url(&self, handler: UrlHandler) {
1138        *self.0.on_url.borrow_mut() = Some(handler);
1139    }
1140
1141    /// Hands a file over to whoever asked for them, or else to the person.
1142    pub fn hand_file(&self, name: String, bytes: Vec<u8>, picker: &FilePicker) {
1143        let handler = self.0.on_file.borrow().clone();
1144        match handler {
1145            Some(handler) => handler(name, bytes),
1146            None => picker.save(&name, &bytes),
1147        }
1148    }
1149
1150    /// Whether a save found something on the form.
1151    pub(crate) fn is_checking(&self) -> bool {
1152        *self.0.checking.peek()
1153    }
1154
1155    /// Whether there is a form with changes that were not kept.
1156    pub fn has_unsaved_changes(&self) -> bool {
1157        self.0.form.peek().as_ref().is_some_and(|form| form.is_dirty())
1158    }
1159
1160    /// Runs `then`, asking first if it would lose changes.
1161    pub(crate) fn leaving(&self, then: impl Fn() + 'static) {
1162        if self.has_unsaved_changes() {
1163            let words = self.words();
1164            self.0.confirm.ask(
1165                words.discard_question.clone(),
1166                words.discard_detail.clone(),
1167                words.discard.clone(),
1168                then,
1169            );
1170        } else {
1171            then();
1172        }
1173    }
1174
1175    fn show(&self, view: ViewInfo) {
1176        self.0.full.set(None);
1177        self.0.error.set(None);
1178        self.0.checking.set(false);
1179        self.0.noting.set(false);
1180        self.0.pending.borrow_mut().clear();
1181        if !self.0.left.peek().is_empty() {
1182            self.0.left.set(BTreeSet::new());
1183        }
1184        self.0.frame.set_view_in(Some(view), (self.0.new_space)());
1185    }
1186
1187    /// Shows a list, asking first if that would lose changes.
1188    pub fn open_view(&self, id: &str) {
1189        let (session, id) = (self.clone(), id.to_owned());
1190        self.leaving(move || session.show_view_now(&id));
1191    }
1192
1193    /// Goes to where a navigation item points: a list, or a form with no list before it, on the
1194    /// record with this key or on a new one. Asks first if that would lose changes.
1195    pub(crate) fn open_navigation(&self, id: &str, key: Option<&str>) {
1196        let (session, id, key) = (self.clone(), id.to_owned(), key.map(str::to_owned));
1197        self.leaving(move || {
1198            let model = session.model();
1199            let Ok(view) = ViewInfo::from_model(&model, &id) else {
1200                return session.show_view_now(&id);
1201            };
1202            let Some(entity) = view.entity.as_deref().filter(|_| view.kind == ViewKind::Detail) else {
1203                return session.show_view_now(&id);
1204            };
1205            session.0.history.set(vec![id.clone()]);
1206            match (&key, session.entity(entity)) {
1207                (Some(key), Some(info)) => {
1208                    session.open_detail(entity, &crate::values::key_from_text(&info, key));
1209                }
1210                _ => session.open_new(entity),
1211            }
1212            session.touch();
1213        });
1214    }
1215
1216    pub(crate) fn show_view_now(&self, id: &str) {
1217        let model = self.model();
1218        match ViewInfo::from_model(&model, id) {
1219            Ok(view) if view.kind == ViewKind::Detail => {
1220                self.0.history.set(vec![id.to_owned()]);
1221                match view.entity.clone() {
1222                    Some(entity) => self.open_new(&entity),
1223                    None => self.show(view),
1224                }
1225                self.touch();
1226            }
1227            Ok(view) => {
1228                self.0.form.set(None);
1229                self.0.history.set(vec![id.to_owned()]);
1230                if let Some(title) = model.at(&["Views", id]).and_then(|node| node.text("Caption")) {
1231                    self.0.announcer.polite(self.access_words().showing(title));
1232                }
1233                self.show(view);
1234                self.touch();
1235            }
1236            Err(error) => self.0.error.set(Some(error.0)),
1237        }
1238    }
1239
1240    fn detail_of(&self, entity: &EntityInfo) -> Result<ViewInfo, String> {
1241        let id = format!("{}_DetailView", entity.name);
1242        ViewInfo::from_model(&self.model(), &id).map_err(|e| e.0)
1243    }
1244
1245    fn remember_siblings(&self, entity: &EntityInfo) {
1246        let Some(field) = entity.key_field() else { return };
1247        let space = self.0.frame.space();
1248        let found = space.borrow().query(&entity.name, &Query::all());
1249        *self.0.siblings.borrow_mut() =
1250            found.map_or_else(|_| Vec::new(), |all| all.iter().map(|o| o.get(&field.name)).collect());
1251    }
1252
1253    /// A record by its key, in storage or from what gives the records no storage keeps.
1254    fn find_record(
1255        &self,
1256        entity: &EntityInfo,
1257        key: &Value,
1258    ) -> Result<Option<Object>, ravenrs_app::SpaceError> {
1259        if let Some(source) = self.record_source(&entity.name) {
1260            let name = entity.key_field().map(|field| field.name.clone()).unwrap_or_default();
1261            return Ok(source().into_iter().find(|object| object.get(&name) == *key));
1262        }
1263        self.0.frame.space().borrow().find(&entity.name, key)
1264    }
1265
1266    /// Opens the form of a record, in a unit of work of its own.
1267    pub fn open_detail(&self, entity_name: &str, key: &Value) {
1268        let Some(entity) = self.entity(entity_name) else { return };
1269        let view = match self.detail_of(&entity) {
1270            Ok(view) => view,
1271            Err(error) => return self.0.error.set(Some(error)),
1272        };
1273        if self.0.frame.view().is_some_and(|v| v.kind == ViewKind::List) {
1274            self.remember_siblings(&entity);
1275            self.0.history.update(|history| history.push(view.id.clone()));
1276        }
1277        self.show(view);
1278        let found = self.find_record(&entity, key);
1279        match found {
1280            Ok(Some(object)) => {
1281                self.0.frame.set_selected(vec![object.clone()]);
1282                self.0.form.set(Some(Rc::new(FormState::new(entity.clone(), object, false))));
1283            }
1284            Ok(None) => self.0.error.set(Some(format!("{} {}", entity.caption, key_text(&entity, key)))),
1285            Err(error) => self.0.error.set(Some(error.to_string())),
1286        }
1287        self.update_position(&entity);
1288    }
1289
1290    /// Opens the form of a new record.
1291    pub fn open_new(&self, entity_name: &str) {
1292        let Some(entity) = self.entity(entity_name) else { return };
1293        let view = match self.detail_of(&entity) {
1294            Ok(view) => view,
1295            Err(error) => return self.0.error.set(Some(error)),
1296        };
1297        if self.0.frame.view().is_some_and(|v| v.kind == ViewKind::List) {
1298            self.0.history.update(|history| history.push(view.id.clone()));
1299        }
1300        self.show(view);
1301        // What no storage keeps starts from its declaration.
1302        let made = if entity.transient {
1303            Ok(entity.new_object())
1304        } else {
1305            self.0.frame.space().borrow_mut().new_object(entity_name)
1306        };
1307        match made {
1308            Ok(object) => self.0.form.set(Some(Rc::new(FormState::new(entity.clone(), object, true)))),
1309            Err(error) => self.0.error.set(Some(error.to_string())),
1310        }
1311        self.update_position(&entity);
1312    }
1313
1314    /// Turns moving between records on and off, by where this one is among the others.
1315    fn update_position(&self, entity: &EntityInfo) {
1316        let at = self.position(entity);
1317        let last = self.0.siblings.borrow().len().saturating_sub(1);
1318        if let Some(action) = self.0.frame.action(crate::ids::PREVIOUS) {
1319            action.set_enabled("position", at.is_some_and(|at| at > 0));
1320        }
1321        if let Some(action) = self.0.frame.action(crate::ids::NEXT) {
1322            action.set_enabled("position", at.is_some_and(|at| at < last));
1323        }
1324    }
1325
1326    fn position(&self, entity: &EntityInfo) -> Option<usize> {
1327        let form = self.0.form.peek().clone()?;
1328        if form.is_new {
1329            return None;
1330        }
1331        let key = entity.key_field().map(|f| form.original.get(&f.name))?;
1332        self.0.siblings.borrow().iter().position(|k| *k == key)
1333    }
1334
1335    /// Moves to the record this far from the current one, asking first if that would lose changes.
1336    pub(crate) fn step(&self, by: isize) {
1337        let Some(form) = self.0.form.peek().clone() else { return };
1338        let Some(at) = self.position(&form.entity) else { return };
1339        let Some(to) = at.checked_add_signed(by) else { return };
1340        let Some(key) = self.0.siblings.borrow().get(to).cloned() else { return };
1341        let (session, name) = (self.clone(), form.entity.name.clone());
1342        self.leaving(move || session.open_detail(&name, &key));
1343    }
1344
1345    /// Keeps what the form holds. Stays where it is; what is wrong goes on the screen.
1346    pub(crate) fn save(&self) -> Result<(), String> {
1347        self.save_then(|_| {})
1348    }
1349
1350    /// Keeps what the form holds and then runs `then`. What only warns asks first, and so does a
1351    /// field someone else changed too, so `then` may run later, when the person answers, or never.
1352    pub(crate) fn save_then(&self, then: impl Fn(&Self) + 'static) -> Result<(), String> {
1353        let Some(form) = self.0.form.peek().clone() else { return Ok(()) };
1354        let object = form.current();
1355        let found = self.violations_of(&object, CONTEXT_SAVE);
1356        let errors: Vec<String> = found.iter().filter(|v| v.is_error()).map(|v| v.message.clone()).collect();
1357        if !errors.is_empty() {
1358            self.0.error.set(None);
1359            self.0.checking.set(true);
1360            let why = errors.join("; ");
1361            self.0.announcer.assertive(self.access_words().not_saved(&why));
1362            return Err(why);
1363        }
1364        self.0.checking.set(false);
1365        let then: Rc<dyn Fn(&Self)> = Rc::new(then);
1366        let warnings: Vec<String> =
1367            found.iter().filter(|v| v.severity == Severity::Warning).map(|v| v.message.clone()).collect();
1368        if warnings.is_empty() {
1369            return self.save_merged(&then);
1370        }
1371        let session = self.clone();
1372        let words = self.words();
1373        self.0.confirm.ask(
1374            words.warning_question.clone(),
1375            warnings.join("\n"),
1376            words.warning_answer.clone(),
1377            move || {
1378                // What fails goes on the screen.
1379                let _ = session.save_merged(&then);
1380            },
1381        );
1382        Ok(())
1383    }
1384
1385    /// Puts together what the form changed and what someone else kept since it opened. Different
1386    /// fields both stand; the same field asks first.
1387    fn save_merged(&self, then: &Rc<dyn Fn(&Self)>) -> Result<(), String> {
1388        let Some(form) = self.0.form.peek().clone() else { return Ok(()) };
1389        let object = form.current();
1390        if form.is_new || form.entity.transient {
1391            return self.keep(&form, object, then);
1392        }
1393        let key = form.entity.key_field().map_or(Value::Null, |field| object.get(&field.name));
1394        let stored = self.0.frame.space().borrow().find(&form.entity.name, &key);
1395        let stored = match stored {
1396            Ok(stored) => stored,
1397            Err(error) => {
1398                let message = error.to_string();
1399                self.0.error.set(Some(message.clone()));
1400                return Err(message);
1401            }
1402        };
1403        let Some(stored) = stored else {
1404            let message = self.words().record_gone.clone();
1405            self.0.error.set(Some(message.clone()));
1406            return Err(message);
1407        };
1408        let merged = merge_changes(&form.entity, &form.original, &object, &stored);
1409        if merged.conflicts.is_empty() {
1410            return self.keep(&form, merged.record, then);
1411        }
1412        let names: Vec<&str> = merged
1413            .conflicts
1414            .iter()
1415            .filter_map(|name| form.entity.field_named(name))
1416            .map(|field| field.caption.as_str())
1417            .collect();
1418        let (session, then, record) = (self.clone(), then.clone(), merged.record);
1419        let (words, kept_form) = (self.words(), form.clone());
1420        self.0.confirm.ask(
1421            words.conflict_question.clone(),
1422            words.conflict_detail.replace("{0}", &names.join(", ")),
1423            words.conflict_answer.clone(),
1424            move || {
1425                // What fails goes on the screen.
1426                let _ = session.keep(&kept_form, record.clone(), &then);
1427            },
1428        );
1429        Ok(())
1430    }
1431
1432    /// Writes the record, with what comes along with it.
1433    fn keep(&self, form: &Rc<FormState>, object: Object, then: &Rc<dyn Fn(&Self)>) -> Result<(), String> {
1434        let has_notes = self.violations_of(&object, CONTEXT_SAVE).iter().any(|v| !v.is_error());
1435        self.0.noting.set(has_notes);
1436        if form.entity.transient {
1437            self.0.error.set(None);
1438            self.0.form.set(Some(Rc::new(FormState::new(form.entity.clone(), object, false))));
1439            self.touch();
1440            then(self);
1441            return Ok(());
1442        }
1443        let space = self.0.frame.space();
1444        let done = {
1445            let mut space = space.borrow_mut();
1446            let mut kept =
1447                if form.is_new { space.insert(object.clone()) } else { space.save(object.clone()) };
1448            // After the record, which they point to.
1449            for record in self.0.pending.borrow_mut().drain(..) {
1450                kept = kept.and_then(|()| space.insert(record));
1451            }
1452            kept.and_then(|()| space.commit())
1453        };
1454        match done {
1455            Ok(()) => {
1456                self.0.error.set(None);
1457                self.0.announcer.polite(self.access_words().saved(&Self::spoken_name(&form.entity, &object)));
1458                let saved = form.entity.key_field().map(|f| object.get(&f.name));
1459                self.touch();
1460                if let Some(key) = saved {
1461                    // The same record, no longer new or changed.
1462                    self.remember_siblings(&form.entity);
1463                    let found = space.borrow().find(&form.entity.name, &key).ok().flatten();
1464                    let fresh = found.unwrap_or(object);
1465                    self.0.frame.set_selected(vec![fresh.clone()]);
1466                    self.0.form.set(Some(Rc::new(FormState::new(form.entity.clone(), fresh, false))));
1467                    self.update_position(&form.entity);
1468                }
1469                then(self);
1470                Ok(())
1471            }
1472            Err(error) => {
1473                // What it could not keep must not stay waiting for the next try.
1474                space.borrow_mut().rollback();
1475                let message = error.to_string();
1476                self.0.error.set(Some(message.clone()));
1477                Err(message)
1478            }
1479        }
1480    }
1481
1482    /// Goes back to where the form came from, without asking.
1483    pub(crate) fn back_now(&self) {
1484        self.0.form.set(None);
1485        let previous = {
1486            let mut history = self.0.history.get();
1487            history.pop();
1488            let previous = history.last().cloned();
1489            self.0.history.set(history);
1490            previous
1491        };
1492        let target = previous.or_else(|| self.first_view());
1493        if let Some(id) = target {
1494            self.show_view_now(&id);
1495        }
1496    }
1497
1498    /// Goes back, asking first if that would lose changes.
1499    pub(crate) fn back(&self) {
1500        let session = self.clone();
1501        self.leaving(move || session.back_now());
1502    }
1503
1504    /// Throws away what the form changed, and leaves, which is what was asked.
1505    pub(crate) fn cancel(&self) {
1506        self.0.frame.space().borrow_mut().rollback();
1507        self.back_now();
1508    }
1509
1510    /// Takes away these records and keeps that, in the frame's unit of work.
1511    pub(crate) fn delete(&self, objects: &[Object]) -> Result<(), String> {
1512        let found: Vec<Violation> =
1513            objects.iter().flat_map(|object| self.violations_of(object, CONTEXT_DELETE)).collect();
1514        let refused: Vec<String> = found.iter().filter(|v| v.is_error()).map(|v| v.message.clone()).collect();
1515        if !refused.is_empty() {
1516            return Err(refused.join("; "));
1517        }
1518        let warnings: Vec<String> =
1519            found.iter().filter(|v| v.severity == Severity::Warning).map(|v| v.message.clone()).collect();
1520        if warnings.is_empty() {
1521            return self.delete_now(objects);
1522        }
1523        let (session, objects, words) = (self.clone(), objects.to_vec(), self.words());
1524        self.0.confirm.ask(
1525            words.delete_question.clone(),
1526            warnings.join("\n"),
1527            words.delete.clone(),
1528            move || {
1529                // What fails goes on the screen.
1530                let _ = session.delete_now(&objects);
1531            },
1532        );
1533        Ok(())
1534    }
1535
1536    fn delete_now(&self, objects: &[Object]) -> Result<(), String> {
1537        let space = self.0.frame.space();
1538        let done = {
1539            let mut space = space.borrow_mut();
1540            let mut result = Ok(());
1541            for object in objects {
1542                let Some(entity) = self.entity(object.entity()) else { continue };
1543                let Some(field) = entity.key_field() else { continue };
1544                result = result.and_then(|()| space.delete(&entity.name, &object.get(&field.name)));
1545            }
1546            result.and_then(|()| space.commit())
1547        };
1548        match done {
1549            Ok(()) => {
1550                self.0.announcer.polite(self.access_words().deleted(objects.len()));
1551                self.touch();
1552                let leaving_form = self.0.form.peek().is_some();
1553                if leaving_form {
1554                    self.back_now();
1555                } else {
1556                    self.0.frame.set_selected(Vec::new());
1557                }
1558                Ok(())
1559            }
1560            Err(error) => {
1561                space.borrow_mut().rollback();
1562                let message = error.to_string();
1563                self.0.error.set(Some(message.clone()));
1564                Err(message)
1565            }
1566        }
1567    }
1568
1569    /// Reads again. A form goes back to what is kept, asking first if that would lose changes.
1570    pub(crate) fn refresh(&self) {
1571        let session = self.clone();
1572        self.leaving(move || {
1573            session.0.frame.space().borrow_mut().refresh();
1574            session.touch();
1575            let form = session.0.form.peek().clone();
1576            if let Some(form) = form {
1577                if form.is_new {
1578                    session.back_now();
1579                } else if let Some(field) = form.entity.key_field() {
1580                    let key = form.original.get(&field.name);
1581                    session.open_detail(&form.entity.name, &key);
1582                }
1583            }
1584        });
1585    }
1586
1587    /// The first thing in the navigation, where the application opens.
1588    pub(crate) fn first_view(&self) -> Option<String> {
1589        let model = self.model();
1590        model.get("NavigationItems")?.in_order().into_iter().find_map(|group| {
1591            if let Some(view) = group.text("View") {
1592                return self.can_open(None, &group.id, view).then(|| view.to_owned());
1593            }
1594            group.in_order().into_iter().find_map(|item| {
1595                let view = item.text("View")?;
1596                self.can_open(Some(&group.id), &item.id, view).then(|| view.to_owned())
1597            })
1598        })
1599    }
1600
1601    /// Runs an action from a screen: asks its question first, and does what it asks for next.
1602    pub fn run_action(&self, action: &Action, input: ActionInput) {
1603        let Some(question) = action.confirmation_text().map(str::to_owned) else {
1604            self.execute_after_warnings(action, input);
1605            return;
1606        };
1607        let (session, kept) = (self.clone(), action.clone());
1608        let detail = action.confirmation_detail().unwrap_or(&self.words().delete_detail).to_owned();
1609        let answer = action.confirmation_answer().unwrap_or(action.caption()).to_owned();
1610        self.0.confirm.ask(question, detail, answer, move || {
1611            session.execute_after_warnings(&kept, input.clone());
1612        });
1613    }
1614
1615    /// Runs an action, asking first about what its contexts only warn of.
1616    fn execute_after_warnings(&self, action: &Action, input: ActionInput) {
1617        let selected = self.0.frame.selected();
1618        let records = match (self.form(), selected.is_empty()) {
1619            (Some(form), true) => vec![form.current()],
1620            _ => selected,
1621        };
1622        let warnings: Vec<String> = records
1623            .iter()
1624            .flat_map(|record| {
1625                action.contexts().iter().flat_map(move |context| self.violations_of(record, context))
1626            })
1627            .filter(|violation| violation.severity == Severity::Warning)
1628            .map(|violation| violation.message)
1629            .collect();
1630        if warnings.is_empty() {
1631            return self.execute(action.id(), input);
1632        }
1633        let (session, id, words) = (self.clone(), action.id().to_owned(), self.words());
1634        self.0.confirm.ask(
1635            words.warning_question.clone(),
1636            warnings.join("\n"),
1637            words.warning_answer.clone(),
1638            move || session.execute(&id, input.clone()),
1639        );
1640    }
1641
1642    /// An action changed records: what is selected reads again, so actions go by what they are now,
1643    /// and a form with nothing typed in shows what they are now. What someone typed stays, and
1644    /// saving puts it together with what changed.
1645    /// Background work changed records, or ended: what shows records and actions reads again.
1646    pub(crate) fn after_background_work(&self) {
1647        self.reread_after_action();
1648    }
1649
1650    fn reread_after_action(&self) {
1651        let frame = &self.0.frame;
1652        let fresh: Vec<Object> = frame
1653            .selected()
1654            .into_iter()
1655            .filter_map(|record| {
1656                let entity = self.entity(record.entity())?;
1657                let key = record.get(&entity.key_field()?.name);
1658                frame.space().borrow().find(&entity.name, &key).ok().flatten()
1659            })
1660            .collect();
1661        frame.set_selected(fresh);
1662        self.touch();
1663        let form = self.0.form.peek().clone();
1664        if let Some(form) = form.filter(|form| !form.is_new && !form.is_dirty())
1665            && let Some(field) = form.entity.key_field()
1666        {
1667            self.open_detail(&form.entity.name, &form.original.get(&field.name));
1668        }
1669    }
1670
1671    fn execute(&self, id: &str, input: ActionInput) {
1672        match self.0.frame.execute(id, input) {
1673            Ok(ActionResult::OpenUrl(address)) => {
1674                let handler = self.0.on_url.borrow().clone();
1675                if let Some(handler) = handler {
1676                    handler(address);
1677                }
1678            }
1679            Ok(ActionResult::RecordsChanged) => self.reread_after_action(),
1680            Ok(_) => {}
1681            // The form already says what is wrong, on each field.
1682            Err(_) if *self.0.checking.peek() => {}
1683            Err(error) => self.0.error.set(Some(error.to_string())),
1684        }
1685    }
1686}