1use std::collections::HashMap;
4
5use topcoat::{Result, context::Cx, view::*};
6
7use super::{
8 Schema,
9 embedded::Embedded,
10 fields::Field,
11 layouts::{Grid, Group, Section},
12 lenses::FieldResolver,
13};
14use crate::form::FieldErrors;
15
16#[derive(Debug)]
17pub(crate) enum Node {
18 Field(usize),
20 Section(Box<Section>),
21 Group(Box<Group>),
22 Grid(Box<Grid>),
23 Embedded(Box<Embedded>),
24 Unbound(Unbound),
27}
28
29pub(crate) struct Unbound {
31 pub(crate) build: Box<dyn Fn(&FieldResolver) -> Schema + Send + Sync>,
32 pub(crate) value: &'static str,
34}
35
36impl std::fmt::Debug for Unbound {
37 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38 f.debug_tuple("Unbound").field(&self.value).finish()
39 }
40}
41
42pub struct Source<'a> {
44 values: &'a HashMap<String, String>,
45 errors: Option<&'a FieldErrors>,
46}
47
48impl<'a> Source<'a> {
49 pub fn form(values: &'a HashMap<String, String>, errors: &'a FieldErrors) -> Self {
51 Self {
52 values,
53 errors: Some(errors),
54 }
55 }
56
57 pub fn view(values: &'a HashMap<String, String>) -> Self {
59 Self {
60 values,
61 errors: None,
62 }
63 }
64
65 pub(crate) fn mode(&self) -> Mode {
66 if self.errors.is_some() {
67 Mode::Form
68 } else {
69 Mode::View
70 }
71 }
72
73 pub(crate) fn value(&self, name: &str) -> Option<&str> {
74 self.values.get(name).map(String::as_str)
75 }
76
77 pub(crate) fn error_for(&self, field: &Field) -> Option<String> {
79 self.errors
80 .and_then(|errors| errors.first(field.name()))
81 .map(|error| error.message(field.label_str()))
82 }
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub(crate) enum Mode {
88 Form,
89 View,
90}
91
92impl Node {
93 pub(crate) async fn render<'a>(
95 &self,
96 cx: &'a Cx,
97 fields: &[Field],
98 source: &Source<'_>,
99 ) -> Result<BoxView<'a>> {
100 match self {
101 Node::Field(index) => {
102 let field = &fields[*index];
103 let error = source.error_for(field);
104 Box::pin(field.render(
105 cx,
106 source.value(field.name()),
107 error.as_deref(),
108 source.mode(),
109 ))
110 .await
111 }
112 Node::Section(s) => Box::pin(s.render(cx, fields, source)).await,
113 Node::Group(g) => Box::pin(g.render(cx, fields, source)).await,
114 Node::Grid(g) => Box::pin(g.render(cx, fields, source)).await,
115 Node::Embedded(e) => Box::pin(e.render(cx, fields, source)).await,
116 Node::Unbound(_) => Ok(().boxed()),
118 }
119 }
120
121 pub(crate) fn children(&self) -> Option<&[Node]> {
124 match self {
125 Node::Section(s) => Some(&s.children.nodes),
126 Node::Group(g) => Some(&g.children.nodes),
127 Node::Grid(g) => Some(&g.children.nodes),
128 Node::Field(_) | Node::Embedded(_) | Node::Unbound(_) => None,
129 }
130 }
131
132 fn children_mut(&mut self) -> Option<&mut Vec<Node>> {
133 match self {
134 Node::Section(s) => Some(&mut s.children.nodes),
135 Node::Group(g) => Some(&mut g.children.nodes),
136 Node::Grid(g) => Some(&mut g.children.nodes),
137 Node::Field(_) | Node::Embedded(_) | Node::Unbound(_) => None,
138 }
139 }
140
141 pub(crate) fn offset(&mut self, by: usize) {
143 match self {
144 Node::Field(index) => *index += by,
145 Node::Embedded(e) => e.offset(by),
146 _ => {
147 for child in self.children_mut().into_iter().flatten() {
148 child.offset(by);
149 }
150 }
151 }
152 }
153}
154
155pub(crate) fn bind_nodes(nodes: &mut Vec<Node>, fields: &mut Vec<Field>, resolver: &FieldResolver) {
158 let mut bound = Vec::with_capacity(nodes.len());
159 for node in std::mem::take(nodes) {
160 let mut node = match node {
161 Node::Unbound(unbound) => {
162 let Schema {
163 nodes: mut built,
164 fields: built_fields,
165 } = (unbound.build)(resolver);
166 let offset = fields.len();
167 for node in &mut built {
168 node.offset(offset);
169 }
170 fields.extend(built_fields);
171 bound.extend(built);
172 continue;
173 }
174 node => node,
175 };
176 if let Some(children) = node.children_mut() {
177 bind_nodes(children, fields, resolver);
178 }
179 bound.push(node);
180 }
181 *nodes = bound;
182}
183
184pub(crate) fn unbound_values(nodes: &[Node], out: &mut Vec<&'static str>) {
186 for node in nodes {
187 match node {
188 Node::Unbound(unbound) => out.push(unbound.value),
189 node => unbound_values(node.children().unwrap_or_default(), out),
190 }
191 }
192}
193
194pub(crate) async fn render_nodes<'a>(
196 cx: &'a Cx,
197 nodes: &[Node],
198 fields: &[Field],
199 source: &Source<'_>,
200) -> Result<BoxView<'a>> {
201 let mut views = Vec::with_capacity(nodes.len());
202 for node in nodes {
203 views.push(node.render(cx, fields, source).await?);
204 }
205 Ok(view! {
206 cx =>
207 for v in views {
208 (v)
209 }
210 }
211 .boxed())
212}
213
214pub trait IntoSchema {
216 fn into_schema(self) -> Schema;
217}
218
219impl IntoSchema for Schema {
220 fn into_schema(self) -> Schema {
221 self
222 }
223}
224
225impl IntoSchema for Field {
226 fn into_schema(self) -> Schema {
227 Schema {
228 nodes: vec![Node::Field(0)],
229 fields: vec![self],
230 }
231 }
232}
233
234macro_rules! container_nodes {
236 ($($ty:ident),+ $(,)?) => {
237 $(
238 impl IntoSchema for $ty {
239 fn into_schema(mut self) -> Schema {
240 let fields = std::mem::take(&mut self.children.fields);
241 Schema {
242 nodes: vec![Node::$ty(Box::new(self))],
243 fields,
244 }
245 }
246 }
247 )+
248 };
249}
250
251container_nodes!(Section, Group, Grid);
252
253macro_rules! into_schema_tuples {
255 ($($T:ident => $v:ident),+ $(,)?) => {
256 impl<$($T),+> IntoSchema for ($($T,)+)
257 where
258 $($T: IntoSchema,)+
259 {
260 fn into_schema(self) -> Schema {
261 let ($($v,)+) = self;
262 let mut schema = Schema::empty();
263 $( schema.append($v.into_schema()); )+
264 schema
265 }
266 }
267 };
268}
269
270into_schema_tuples!(A => a, B => b);
271into_schema_tuples!(A => a, B => b, C => c);
272into_schema_tuples!(A => a, B => b, C => c, D => d);
273into_schema_tuples!(A => a, B => b, C => c, D => d, E => e);
274into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
275into_schema_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
276into_schema_tuples!(
277 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
278);
279
280#[cfg(test)]
281mod tests;