Skip to main content

tablo_core/schema/
tree.rs

1//! Composes field slots, layout containers, and embedded values into one tree.
2
3use std::{
4    collections::HashMap,
5    sync::atomic::{AtomicUsize, Ordering},
6};
7
8use topcoat::{Result, context::Cx, view::*};
9
10use super::{
11    Schema,
12    condition::{Condition, watch},
13    embedded::Embedded,
14    fields::{Field, Placement},
15    layouts::{Grid, Group, Section, Tab, Tabs},
16};
17use crate::values::{FieldError, FieldErrors, FormValue, FormValues};
18
19#[derive(Debug)]
20pub(crate) enum Node {
21    /// A field, by its index in the root schema's field list.
22    Field(usize),
23    Section(Box<Section>),
24    Group(Box<Group>),
25    Grid(Box<Grid>),
26    /// A `Tabs` block, whose children are its `Tab` nodes.
27    Tabs(Box<Tabs>),
28    /// One tab, placed only in a `Tabs` block's children.
29    Tab(Box<Tab>),
30    Embedded(Box<Embedded>),
31}
32
33/// Holds the values and errors a schema's controls render with.
34pub struct Source<'a> {
35    values: &'a FormValues,
36    errors: &'a FieldErrors,
37    /// The conditions each watched field's key drives.
38    watched: HashMap<String, Vec<Condition>>,
39    /// The prefix of the form's DOM ids and of its signals' keys; empty for a form alone on its
40    /// page.
41    scope: String,
42    /// Where a searchable choice fetches its options, when not the resource's own route.
43    options: Option<String>,
44    /// Whether the form edits a stored record.
45    editing: bool,
46    /// The keys the request's field access hides or locks.
47    mask: super::FormMask,
48    /// How many tabs hold the node rendering now, which tells a nested block's DOM ids from its
49    /// outer tab's.
50    tab_depth: AtomicUsize,
51}
52
53impl<'a> Source<'a> {
54    /// Renders controls hydrated with `values` and inline `errors`.
55    pub fn form(values: &'a FormValues, errors: &'a FieldErrors) -> Self {
56        Self {
57            values,
58            errors,
59            watched: HashMap::new(),
60            scope: String::new(),
61            options: None,
62            editing: false,
63            mask: super::FormMask::default(),
64            tab_depth: AtomicUsize::new(0),
65        }
66    }
67
68    /// Renders the edit form of a stored record, where `disabled_on_edit` disables a control.
69    pub(crate) fn editing(mut self, editing: bool) -> Self {
70        self.editing = editing;
71        self
72    }
73
74    /// Renders nothing for the keys `mask` hides, and disables the controls of the ones it locks.
75    pub(crate) fn masked(mut self, mask: super::FormMask) -> Self {
76        self.mask = mask;
77        self
78    }
79
80    /// Renders a form sharing its page with another: its DOM ids and its signals' keys carry
81    /// `scope`, so neither form's labels, conditions or variants reach the other's.
82    pub(crate) fn scoped(mut self, scope: impl Into<String>) -> Self {
83        self.scope = scope.into();
84        self
85    }
86
87    /// Fetches a searchable choice's options from `url` rather than the resource's own route.
88    pub(crate) fn options_at(mut self, url: impl Into<String>) -> Self {
89        self.options = Some(url.into());
90        self
91    }
92
93    /// The prefix of the form's DOM ids and of its signals' keys.
94    pub(crate) fn scope(&self) -> &str {
95        &self.scope
96    }
97
98    /// Renders each watched field with the handlers its conditions follow.
99    pub(crate) fn watching(mut self, watched: HashMap<String, Vec<Condition>>) -> Self {
100        self.watched = watched;
101        self
102    }
103
104    /// Renders `view` guarded by `condition`, or as it is without one.
105    pub(crate) fn guard<'v>(
106        &self,
107        cx: &'v Cx,
108        condition: Option<&Condition>,
109        view: BoxView<'v>,
110    ) -> BoxView<'v> {
111        match condition {
112            Some(condition) => condition.guard(cx, &self.scope, self.values, view),
113            None => view,
114        }
115    }
116
117    /// How many tabs hold the node rendering now.
118    pub(crate) fn tab_depth(&self) -> usize {
119        self.tab_depth.load(Ordering::Relaxed)
120    }
121
122    /// Renders what follows inside `depth` tabs.
123    pub(crate) fn set_tab_depth(&self, depth: usize) {
124        self.tab_depth.store(depth, Ordering::Relaxed);
125    }
126
127    /// Whether `condition` holds in the submission, as the page first renders it: always without
128    /// one.
129    pub(crate) fn holds(&self, condition: Option<&Condition>) -> bool {
130        condition.is_none_or(|condition| condition.holds(self.values))
131    }
132
133    pub(crate) fn value(&self, name: &str) -> Option<&FormValue> {
134        self.values.get(name)
135    }
136
137    /// The text `name` holds: a scalar control's value.
138    pub(crate) fn text(&self, name: &str) -> Option<&str> {
139        self.values.text(name)
140    }
141
142    /// The rows `name` holds: a repeater's, none when it holds none.
143    pub(crate) fn rows(&self, name: &str) -> &[FormValues] {
144        self.values.rows(name).unwrap_or_default()
145    }
146
147    /// The errors the controls render under.
148    pub(crate) fn errors(&self) -> &FieldErrors {
149        self.errors
150    }
151
152    /// Where a searchable choice fetches its options, when not the resource's own route.
153    pub(crate) fn options(&self) -> Option<&str> {
154        self.options.as_deref()
155    }
156
157    /// The DOM id of the control posting `name`.
158    pub(crate) fn id(&self, name: &str) -> String {
159        self.placement(name, None).id(name)
160    }
161
162    /// Where the field posting `name` renders in this form, under its parent's value `parent`.
163    fn placement<'p>(&'p self, name: &str, parent: Option<&'p str>) -> Placement<'p> {
164        Placement {
165            parent,
166            scope: &self.scope,
167            options: self.options.as_deref(),
168            editing: self.editing,
169            locked: self.mask.locks(name),
170        }
171    }
172
173    /// Returns the message the field `field` renders under its control.
174    pub(crate) fn error_for(&self, field: &Field) -> Option<&FieldError> {
175        self.errors.first(field.name())
176    }
177}
178
179impl Node {
180    /// Renders this node from `source`, reading field slots from `fields`.
181    pub(crate) async fn render<'a>(
182        &self,
183        cx: &'a Cx,
184        fields: &[Field],
185        source: &Source<'_>,
186    ) -> Result<BoxView<'a>> {
187        match self {
188            Node::Field(index) => {
189                if !shows(&fields[*index], source) {
190                    return Ok(().boxed());
191                }
192                let field = &fields[*index];
193                // A repeater's rows render from the keys and errors of each of their controls.
194                if let Some(repeater) = field.as_repeater() {
195                    let view = Box::pin(repeater.render(cx, field, source)).await?;
196                    return Ok(source.guard(cx, field.condition(), view));
197                }
198                let error = source.error_for(field);
199                let parent = field.parent_key().and_then(|key| source.text(key));
200                let mut view = Box::pin(field.render_under(
201                    cx,
202                    source.value(field.name()),
203                    error,
204                    source.placement(field.name(), parent),
205                ))
206                .await?;
207                if let Some(conditions) = source.watched.get(field.name()) {
208                    view = watch(cx, field, conditions, &source.scope, source.values, view);
209                }
210                Ok(source.guard(cx, field.condition(), view))
211            }
212            // A block whose every field is masked renders no heading over nothing.
213            Node::Section(_) | Node::Group(_) | Node::Grid(_) | Node::Tabs(_)
214                if !self.shows_any(fields, source) =>
215            {
216                Ok(().boxed())
217            }
218            Node::Section(s) => {
219                let view = Box::pin(s.render(cx, fields, source)).await?;
220                Ok(source.guard(cx, s.condition.as_ref(), view))
221            }
222            Node::Group(g) => {
223                let view = Box::pin(g.render(cx, fields, source)).await?;
224                Ok(source.guard(cx, g.condition.as_ref(), view))
225            }
226            Node::Grid(g) => {
227                let view = Box::pin(g.render(cx, fields, source)).await?;
228                Ok(source.guard(cx, g.condition.as_ref(), view))
229            }
230            Node::Tabs(t) => {
231                let view = Box::pin(t.render(cx, fields, source)).await?;
232                Ok(source.guard(cx, t.condition.as_ref(), view))
233            }
234            // A tab renders through its block, which lays out its trigger and its panel.
235            Node::Tab(t) => Box::pin(render_nodes(cx, &t.children.nodes, fields, source)).await,
236            Node::Embedded(e) => Box::pin(e.render(cx, fields, source)).await,
237        }
238    }
239
240    /// Whether a field under this node renders an error from `source`: under its own key, or a
241    /// repeater's under one of its rows' keys.
242    pub(crate) fn has_error(&self, fields: &[Field], source: &Source<'_>) -> bool {
243        let mut any = false;
244        self.visit_fields(&mut |index| {
245            let name = fields[index].name();
246            any |= source.errors.iter().any(|error| {
247                error.key == name
248                    || error
249                        .key
250                        .strip_prefix(name)
251                        .is_some_and(|rest| rest.starts_with('.'))
252            });
253        });
254        any
255    }
256
257    /// The first field slot under this node, in declaration order.
258    pub(crate) fn first_field(&self) -> Option<usize> {
259        let mut first = None;
260        self.visit_fields(&mut |index| {
261            first.get_or_insert(index);
262        });
263        first
264    }
265
266    /// Calls `f` with every field slot under this node, in declaration order.
267    fn visit_fields(&self, f: &mut impl FnMut(usize)) {
268        match self {
269            Node::Field(index) => f(*index),
270            Node::Embedded(e) => e.visit_fields(f),
271            node => {
272                for child in node.children().unwrap_or_default() {
273                    child.visit_fields(f);
274                }
275            }
276        }
277    }
278
279    /// Whether some field under this node renders under `source`'s mask.
280    pub(crate) fn shows_any(&self, fields: &[Field], source: &Source<'_>) -> bool {
281        match self {
282            Node::Field(index) => shows(&fields[*index], source),
283            Node::Embedded(e) => {
284                let mut any = false;
285                e.visit_fields(&mut |index| any |= shows(&fields[index], source));
286                any
287            }
288            node => node
289                .children()
290                .unwrap_or_default()
291                .iter()
292                .any(|child| child.shows_any(fields, source)),
293        }
294    }
295
296    /// Returns the nodes a layout container holds, or `None` for a field slot and an embedded
297    /// value.
298    pub(crate) fn children(&self) -> Option<&[Node]> {
299        match self {
300            Node::Section(s) => Some(&s.children.nodes),
301            Node::Group(g) => Some(&g.children.nodes),
302            Node::Grid(g) => Some(&g.children.nodes),
303            Node::Tabs(t) => Some(&t.children.nodes),
304            Node::Tab(t) => Some(&t.children.nodes),
305            Node::Field(_) | Node::Embedded(_) => None,
306        }
307    }
308
309    fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
310        match self {
311            Node::Section(s) => Some(&mut s.children.nodes),
312            Node::Group(g) => Some(&mut g.children.nodes),
313            Node::Grid(g) => Some(&mut g.children.nodes),
314            Node::Tabs(t) => Some(&mut t.children.nodes),
315            Node::Tab(t) => Some(&mut t.children.nodes),
316            Node::Field(_) | Node::Embedded(_) => None,
317        }
318    }
319
320    /// Shifts every field slot under this node by `by`.
321    pub(crate) fn offset(&mut self, by: usize) {
322        match self {
323            Node::Field(index) => *index += by,
324            Node::Embedded(e) => e.offset(by),
325            _ => {
326                for child in self.children_mut().into_iter().flatten() {
327                    child.offset(by);
328                }
329            }
330        }
331    }
332}
333
334/// Whether `field` renders under `source`'s mask: not when hidden, nor a locked repeater, whose
335/// rows' order posts its key.
336fn shows(field: &Field, source: &Source<'_>) -> bool {
337    !(source.mask.hides(field.name())
338        || (source.mask.locks(field.name()) && field.as_repeater().is_some()))
339}
340
341/// Renders `nodes` in order as one view.
342pub(crate) async fn render_nodes<'a>(
343    cx: &'a Cx,
344    nodes: &[Node],
345    fields: &[Field],
346    source: &Source<'_>,
347) -> Result<BoxView<'a>> {
348    let mut views = Vec::with_capacity(nodes.len());
349    for node in nodes {
350        views.push(node.render(cx, fields, source).await?);
351    }
352    Ok(view! {
353        cx =>
354        for v in views {
355            (v)
356        }
357    }
358    .boxed())
359}
360
361/// Converts a field, a layout block, a tuple of either, or a schema into a [`Schema`].
362///
363/// `F` is the record form whose controls the schema places, `()` for a page's or an action's
364/// schema: a control converts into the schema of its own form only.
365#[diagnostic::on_unimplemented(
366    message = "`{Self}` cannot be placed in a `Schema<{F}>`",
367    label = "not a control, layout block, schema, or tuple of 2 to 12 of them for this schema",
368    note = "a resource's form places its record form's own controls, from `controls()`; \
369            `Field::text` and the other constructors build a page's or an action's controls"
370)]
371pub trait IntoSchema<F = ()> {
372    fn into_schema(self) -> Schema<F>;
373}
374
375/// Moves a control or a schema to the form `G`: what `#[derive(RecordForm)]` hands its controls
376/// over with.
377#[doc(hidden)]
378pub trait Retype {
379    type As<G>;
380
381    fn retype<G>(self) -> Self::As<G>;
382}
383
384impl<F> IntoSchema<F> for Schema<F> {
385    fn into_schema(self) -> Schema<F> {
386        self
387    }
388}
389
390impl IntoSchema for Field {
391    fn into_schema(self) -> Schema {
392        Schema::from_parts(vec![Node::Field(0)], vec![self])
393    }
394}
395
396/// Generates the single-node [`IntoSchema`] impls for every layout container, holding a schema or
397/// nothing.
398macro_rules! container_nodes {
399    ($($ty:ident),+ $(,)?) => {
400        $(
401            impl<F> IntoSchema<F> for $ty<Schema<F>> {
402                fn into_schema(self) -> Schema<F> {
403                    let mut fields = Vec::new();
404                    let block = self.map(|mut children| {
405                        fields = std::mem::take(&mut children.fields);
406                        children.retype()
407                    });
408                    Schema::from_parts(vec![Node::$ty(Box::new(block))], fields)
409                }
410            }
411
412            impl<F> IntoSchema<F> for $ty<()> {
413                fn into_schema(self) -> Schema<F> {
414                    self.holding(Schema::<F>::default()).into_schema()
415                }
416            }
417        )+
418    };
419}
420
421container_nodes!(Section, Group, Grid);
422
423impl<F> IntoSchema<F> for Tabs<Schema<F>> {
424    fn into_schema(self) -> Schema<F> {
425        let mut fields = Vec::new();
426        let block = self.map(|mut children| {
427            fields = std::mem::take(&mut children.fields);
428            children.retype()
429        });
430        Schema::from_parts(vec![Node::Tabs(Box::new(block))], fields)
431    }
432}
433
434/// Generates the tuple impls of [`IntoSchema`] from one list per arity.
435macro_rules! into_schema_tuples {
436    ($($T:ident => $v:ident),+ $(,)?) => {
437        impl<Form, $($T),+> IntoSchema<Form> for ($($T,)+)
438        where
439            $($T: IntoSchema<Form>,)+
440        {
441            fn into_schema(self) -> Schema<Form> {
442                let ($($v,)+) = self;
443                let mut schema = Schema::default();
444                $( schema.append($v.into_schema()); )+
445                schema
446            }
447        }
448    };
449}
450
451into_schema_tuples!(A => a, B => b);
452into_schema_tuples!(A => a, B => b, C => c);
453into_schema_tuples!(A => a, B => b, C => c, D => d);
454into_schema_tuples!(A => a, B => b, C => c, D => d, E => e);
455into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
456into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
457into_schema_tuples!(
458    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
459);
460into_schema_tuples!(
461    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
462);
463into_schema_tuples!(
464    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
465);
466into_schema_tuples!(
467    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
468);
469into_schema_tuples!(
470    A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l
471);