use std::{
collections::HashMap,
sync::atomic::{AtomicUsize, Ordering},
};
use topcoat::{Result, context::Cx, view::*};
use super::{
Schema,
condition::{Condition, watch},
embedded::Embedded,
fields::{Field, Placement},
layouts::{Grid, Group, Section, Tab, Tabs},
};
use crate::values::{FieldError, FieldErrors, FormValue, FormValues};
#[derive(Debug)]
pub(crate) enum Node {
Field(usize),
Section(Box<Section>),
Group(Box<Group>),
Grid(Box<Grid>),
Tabs(Box<Tabs>),
Tab(Box<Tab>),
Embedded(Box<Embedded>),
}
pub struct Source<'a> {
values: &'a FormValues,
errors: &'a FieldErrors,
watched: HashMap<String, Vec<Condition>>,
scope: String,
options: Option<String>,
editing: bool,
mask: super::FormMask,
tab_depth: AtomicUsize,
}
impl<'a> Source<'a> {
pub fn form(values: &'a FormValues, errors: &'a FieldErrors) -> Self {
Self {
values,
errors,
watched: HashMap::new(),
scope: String::new(),
options: None,
editing: false,
mask: super::FormMask::default(),
tab_depth: AtomicUsize::new(0),
}
}
pub(crate) fn editing(mut self, editing: bool) -> Self {
self.editing = editing;
self
}
pub(crate) fn masked(mut self, mask: super::FormMask) -> Self {
self.mask = mask;
self
}
pub(crate) fn scoped(mut self, scope: impl Into<String>) -> Self {
self.scope = scope.into();
self
}
pub(crate) fn options_at(mut self, url: impl Into<String>) -> Self {
self.options = Some(url.into());
self
}
pub(crate) fn scope(&self) -> &str {
&self.scope
}
pub(crate) fn watching(mut self, watched: HashMap<String, Vec<Condition>>) -> Self {
self.watched = watched;
self
}
pub(crate) fn guard<'v>(
&self,
cx: &'v Cx,
condition: Option<&Condition>,
view: BoxView<'v>,
) -> BoxView<'v> {
match condition {
Some(condition) => condition.guard(cx, &self.scope, self.values, view),
None => view,
}
}
pub(crate) fn tab_depth(&self) -> usize {
self.tab_depth.load(Ordering::Relaxed)
}
pub(crate) fn set_tab_depth(&self, depth: usize) {
self.tab_depth.store(depth, Ordering::Relaxed);
}
pub(crate) fn holds(&self, condition: Option<&Condition>) -> bool {
condition.is_none_or(|condition| condition.holds(self.values))
}
pub(crate) fn value(&self, name: &str) -> Option<&FormValue> {
self.values.get(name)
}
pub(crate) fn text(&self, name: &str) -> Option<&str> {
self.values.text(name)
}
pub(crate) fn rows(&self, name: &str) -> &[FormValues] {
self.values.rows(name).unwrap_or_default()
}
pub(crate) fn errors(&self) -> &FieldErrors {
self.errors
}
pub(crate) fn options(&self) -> Option<&str> {
self.options.as_deref()
}
pub(crate) fn id(&self, name: &str) -> String {
self.placement(name, None).id(name)
}
fn placement<'p>(&'p self, name: &str, parent: Option<&'p str>) -> Placement<'p> {
Placement {
parent,
scope: &self.scope,
options: self.options.as_deref(),
editing: self.editing,
locked: self.mask.locks(name),
}
}
pub(crate) fn error_for(&self, field: &Field) -> Option<&FieldError> {
self.errors.first(field.name())
}
}
impl Node {
pub(crate) async fn render<'a>(
&self,
cx: &'a Cx,
fields: &[Field],
source: &Source<'_>,
) -> Result<BoxView<'a>> {
match self {
Node::Field(index) => {
if !shows(&fields[*index], source) {
return Ok(().boxed());
}
let field = &fields[*index];
if let Some(repeater) = field.as_repeater() {
let view = Box::pin(repeater.render(cx, field, source)).await?;
return Ok(source.guard(cx, field.condition(), view));
}
let error = source.error_for(field);
let parent = field.parent_key().and_then(|key| source.text(key));
let mut view = Box::pin(field.render_under(
cx,
source.value(field.name()),
error,
source.placement(field.name(), parent),
))
.await?;
if let Some(conditions) = source.watched.get(field.name()) {
view = watch(cx, field, conditions, &source.scope, source.values, view);
}
Ok(source.guard(cx, field.condition(), view))
}
Node::Section(_) | Node::Group(_) | Node::Grid(_) | Node::Tabs(_)
if !self.shows_any(fields, source) =>
{
Ok(().boxed())
}
Node::Section(s) => {
let view = Box::pin(s.render(cx, fields, source)).await?;
Ok(source.guard(cx, s.condition.as_ref(), view))
}
Node::Group(g) => {
let view = Box::pin(g.render(cx, fields, source)).await?;
Ok(source.guard(cx, g.condition.as_ref(), view))
}
Node::Grid(g) => {
let view = Box::pin(g.render(cx, fields, source)).await?;
Ok(source.guard(cx, g.condition.as_ref(), view))
}
Node::Tabs(t) => {
let view = Box::pin(t.render(cx, fields, source)).await?;
Ok(source.guard(cx, t.condition.as_ref(), view))
}
Node::Tab(t) => Box::pin(render_nodes(cx, &t.children.nodes, fields, source)).await,
Node::Embedded(e) => Box::pin(e.render(cx, fields, source)).await,
}
}
pub(crate) fn has_error(&self, fields: &[Field], source: &Source<'_>) -> bool {
let mut any = false;
self.visit_fields(&mut |index| {
let name = fields[index].name();
any |= source.errors.iter().any(|error| {
error.key == name
|| error
.key
.strip_prefix(name)
.is_some_and(|rest| rest.starts_with('.'))
});
});
any
}
pub(crate) fn first_field(&self) -> Option<usize> {
let mut first = None;
self.visit_fields(&mut |index| {
first.get_or_insert(index);
});
first
}
fn visit_fields(&self, f: &mut impl FnMut(usize)) {
match self {
Node::Field(index) => f(*index),
Node::Embedded(e) => e.visit_fields(f),
node => {
for child in node.children().unwrap_or_default() {
child.visit_fields(f);
}
}
}
}
pub(crate) fn shows_any(&self, fields: &[Field], source: &Source<'_>) -> bool {
match self {
Node::Field(index) => shows(&fields[*index], source),
Node::Embedded(e) => {
let mut any = false;
e.visit_fields(&mut |index| any |= shows(&fields[index], source));
any
}
node => node
.children()
.unwrap_or_default()
.iter()
.any(|child| child.shows_any(fields, source)),
}
}
pub(crate) fn children(&self) -> Option<&[Node]> {
match self {
Node::Section(s) => Some(&s.children.nodes),
Node::Group(g) => Some(&g.children.nodes),
Node::Grid(g) => Some(&g.children.nodes),
Node::Tabs(t) => Some(&t.children.nodes),
Node::Tab(t) => Some(&t.children.nodes),
Node::Field(_) | Node::Embedded(_) => None,
}
}
fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
match self {
Node::Section(s) => Some(&mut s.children.nodes),
Node::Group(g) => Some(&mut g.children.nodes),
Node::Grid(g) => Some(&mut g.children.nodes),
Node::Tabs(t) => Some(&mut t.children.nodes),
Node::Tab(t) => Some(&mut t.children.nodes),
Node::Field(_) | Node::Embedded(_) => None,
}
}
pub(crate) fn offset(&mut self, by: usize) {
match self {
Node::Field(index) => *index += by,
Node::Embedded(e) => e.offset(by),
_ => {
for child in self.children_mut().into_iter().flatten() {
child.offset(by);
}
}
}
}
}
fn shows(field: &Field, source: &Source<'_>) -> bool {
!(source.mask.hides(field.name())
|| (source.mask.locks(field.name()) && field.as_repeater().is_some()))
}
pub(crate) async fn render_nodes<'a>(
cx: &'a Cx,
nodes: &[Node],
fields: &[Field],
source: &Source<'_>,
) -> Result<BoxView<'a>> {
let mut views = Vec::with_capacity(nodes.len());
for node in nodes {
views.push(node.render(cx, fields, source).await?);
}
Ok(view! {
cx =>
for v in views {
(v)
}
}
.boxed())
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be placed in a `Schema<{F}>`",
label = "not a control, layout block, schema, or tuple of 2 to 12 of them for this schema",
note = "a resource's form places its record form's own controls, from `controls()`; \
`Field::text` and the other constructors build a page's or an action's controls"
)]
pub trait IntoSchema<F = ()> {
fn into_schema(self) -> Schema<F>;
}
#[doc(hidden)]
pub trait Retype {
type As<G>;
fn retype<G>(self) -> Self::As<G>;
}
impl<F> IntoSchema<F> for Schema<F> {
fn into_schema(self) -> Schema<F> {
self
}
}
impl IntoSchema for Field {
fn into_schema(self) -> Schema {
Schema::from_parts(vec![Node::Field(0)], vec![self])
}
}
macro_rules! container_nodes {
($($ty:ident),+ $(,)?) => {
$(
impl<F> IntoSchema<F> for $ty<Schema<F>> {
fn into_schema(self) -> Schema<F> {
let mut fields = Vec::new();
let block = self.map(|mut children| {
fields = std::mem::take(&mut children.fields);
children.retype()
});
Schema::from_parts(vec![Node::$ty(Box::new(block))], fields)
}
}
impl<F> IntoSchema<F> for $ty<()> {
fn into_schema(self) -> Schema<F> {
self.holding(Schema::<F>::default()).into_schema()
}
}
)+
};
}
container_nodes!(Section, Group, Grid);
impl<F> IntoSchema<F> for Tabs<Schema<F>> {
fn into_schema(self) -> Schema<F> {
let mut fields = Vec::new();
let block = self.map(|mut children| {
fields = std::mem::take(&mut children.fields);
children.retype()
});
Schema::from_parts(vec![Node::Tabs(Box::new(block))], fields)
}
}
macro_rules! into_schema_tuples {
($($T:ident => $v:ident),+ $(,)?) => {
impl<Form, $($T),+> IntoSchema<Form> for ($($T,)+)
where
$($T: IntoSchema<Form>,)+
{
fn into_schema(self) -> Schema<Form> {
let ($($v,)+) = self;
let mut schema = Schema::default();
$( schema.append($v.into_schema()); )+
schema
}
}
};
}
into_schema_tuples!(A => a, B => b);
into_schema_tuples!(A => a, B => b, C => c);
into_schema_tuples!(A => a, B => b, C => c, D => d);
into_schema_tuples!(A => a, B => b, C => c, D => d, E => e);
into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
into_schema_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
);
into_schema_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
);
into_schema_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
);
into_schema_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
);
into_schema_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l
);