1use std::sync::Arc;
4
5use toasty::stmt::{List, Query};
6use topcoat::{context::Cx, view::*};
7
8use crate::{
9 DeclarationErrorKind, EmbeddedForm,
10 schema::{FieldResolver, Grid, Group, RepeaterItem, Section},
11 table::{
12 BooleanColumn, BoxColumn, Column, ComputedColumn, CountColumn, EmbeddedColumn, FileColumn,
13 RelationColumn, RepeaterColumn, TextColumn, include_relations,
14 },
15};
16
17pub struct Detail<M> {
44 nodes: Vec<Node<M>>,
45}
46
47enum Node<M> {
49 Column(BoxColumn<M>),
50 Section(Box<Section<Detail<M>>>),
51 Group(Box<Group<Detail<M>>>),
52 Grid(Box<Grid<Detail<M>>>),
53}
54
55impl<M> Detail<M> {
56 pub fn new(children: impl IntoDetail<M>) -> Self {
58 children.into_detail()
59 }
60
61 pub fn empty() -> Self {
63 Self { nodes: Vec::new() }
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.nodes.is_empty()
69 }
70
71 pub fn column(mut self, column: impl Column<M> + 'static) -> Self {
73 self.nodes.push(Node::Column(Arc::new(column)));
74 self
75 }
76
77 fn append(&mut self, other: Self) {
79 self.nodes.extend(other.nodes);
80 }
81
82 fn columns(&self) -> Vec<&BoxColumn<M>> {
84 fn walk<'d, M>(nodes: &'d [Node<M>], out: &mut Vec<&'d BoxColumn<M>>) {
85 for node in nodes {
86 match node {
87 Node::Column(column) => out.push(column),
88 Node::Section(section) => walk(§ion.children.nodes, out),
89 Node::Group(group) => walk(&group.children.nodes, out),
90 Node::Grid(grid) => walk(&grid.children.nodes, out),
91 }
92 }
93 }
94 let mut out = Vec::new();
95 walk(&self.nodes, &mut out);
96 out
97 }
98
99 pub fn include_relations(&self, query: Query<List<M>>) -> Query<List<M>>
102 where
103 M: toasty::schema::Model,
104 {
105 include_relations(query, self.columns())
106 }
107
108 pub(crate) fn unavailable_sources(&self, cx: &Cx) -> Vec<DeclarationErrorKind> {
110 crate::table::column::unavailable_sources(cx, self.columns().into_iter().map(|c| &**c))
111 }
112
113 pub fn declaration_errors(&self) -> Vec<DeclarationErrorKind> {
115 self.columns()
116 .into_iter()
117 .filter_map(|column| column.misdeclared())
118 .collect()
119 }
120
121 pub fn bind(self, db: &toasty::Db) -> Self {
127 self.bind_with(&FieldResolver::of_db(db));
128 self
129 }
130
131 pub(crate) fn bind_with(&self, resolver: &FieldResolver) {
133 for column in self.columns() {
134 column.bind(resolver);
135 }
136 }
137
138 pub fn render<'a>(&self, cx: &'a Cx, record: &M) -> BoxView<'a> {
145 let views: Vec<BoxView<'a>> = self
146 .nodes
147 .iter()
148 .map(|node| match node {
149 Node::Column(column) => column.entry(cx, record),
150 Node::Section(section) => {
151 let body =
152 (!section.children.is_empty()).then(|| section.children.render(cx, record));
153 section.chrome(cx, body)
154 }
155 Node::Group(group) => group.chrome(cx, group.children.render(cx, record)),
156 Node::Grid(grid) => grid.chrome(cx, grid.children.render(cx, record)),
157 })
158 .collect();
159 view! {
160 cx =>
161 for v in views {
162 (v)
163 }
164 }
165 .boxed()
166 }
167}
168
169impl<M> Default for Detail<M> {
170 fn default() -> Self {
171 Self::empty()
172 }
173}
174
175impl<M> std::fmt::Debug for Detail<M> {
176 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
177 f.debug_struct("Detail")
178 .field(
179 "columns",
180 &self
181 .columns()
182 .into_iter()
183 .map(|column| column.name())
184 .collect::<Vec<_>>(),
185 )
186 .finish()
187 }
188}
189
190pub trait IntoDetail<M> {
218 fn into_detail(self) -> Detail<M>;
219}
220
221impl<M> IntoDetail<M> for Detail<M> {
222 fn into_detail(self) -> Detail<M> {
223 self
224 }
225}
226
227impl<M> IntoDetail<M> for () {
228 fn into_detail(self) -> Detail<M> {
229 Detail::empty()
230 }
231}
232
233impl<M> IntoDetail<M> for BoxColumn<M> {
234 fn into_detail(self) -> Detail<M> {
235 Detail {
236 nodes: vec![Node::Column(self)],
237 }
238 }
239}
240
241impl<M, T> IntoDetail<M> for TextColumn<M, T>
242where
243 M: toasty::schema::Model + Send + Sync + 'static,
244 T: Send + Sync + 'static,
245{
246 fn into_detail(self) -> Detail<M> {
247 Detail::empty().column(self)
248 }
249}
250
251impl<M, T> IntoDetail<M> for EmbeddedColumn<M, T>
252where
253 M: toasty::schema::Model + Send + Sync + 'static,
254 T: EmbeddedForm + Send + Sync + 'static,
255{
256 fn into_detail(self) -> Detail<M> {
257 Detail::empty().column(self)
258 }
259}
260
261impl<M, T> IntoDetail<M> for RepeaterColumn<M, T>
262where
263 M: toasty::schema::Model + Send + Sync + 'static,
264 T: RepeaterItem,
265{
266 fn into_detail(self) -> Detail<M> {
267 Detail::empty().column(self)
268 }
269}
270
271macro_rules! column_details {
273 ($($ty:ident),+ $(,)?) => {
274 $(
275 impl<M> IntoDetail<M> for $ty<M>
276 where
277 M: toasty::schema::Model + Send + Sync + 'static,
278 {
279 fn into_detail(self) -> Detail<M> {
280 Detail::empty().column(self)
281 }
282 }
283 )+
284 };
285}
286
287column_details!(
288 BooleanColumn,
289 ComputedColumn,
290 CountColumn,
291 FileColumn,
292 RelationColumn
293);
294
295macro_rules! block_details {
297 ($($ty:ident),+ $(,)?) => {
298 $(
299 impl<M> IntoDetail<M> for $ty<Detail<M>> {
300 fn into_detail(self) -> Detail<M> {
301 Detail {
302 nodes: vec![Node::$ty(Box::new(self))],
303 }
304 }
305 }
306
307 impl<M> IntoDetail<M> for $ty<()> {
308 fn into_detail(self) -> Detail<M> {
309 self.holding(Detail::empty()).into_detail()
310 }
311 }
312 )+
313 };
314}
315
316block_details!(Section, Group, Grid);
317
318macro_rules! into_detail_tuples {
320 ($($T:ident => $v:ident),+ $(,)?) => {
321 impl<M, $($T),+> IntoDetail<M> for ($($T,)+)
322 where
323 $($T: IntoDetail<M>,)+
324 {
325 fn into_detail(self) -> Detail<M> {
326 let ($($v,)+) = self;
327 let mut detail = Detail::empty();
328 $( detail.append($v.into_detail()); )+
329 detail
330 }
331 }
332 };
333}
334
335into_detail_tuples!(A => a);
336into_detail_tuples!(A => a, B => b);
337into_detail_tuples!(A => a, B => b, C => c);
338into_detail_tuples!(A => a, B => b, C => c, D => d);
339into_detail_tuples!(A => a, B => b, C => c, D => d, E => e);
340into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
341into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
342into_detail_tuples!(
343 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
344);
345into_detail_tuples!(
346 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
347);
348into_detail_tuples!(
349 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
350);
351into_detail_tuples!(
352 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
353);
354into_detail_tuples!(
355 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l
356);
357
358#[cfg(test)]
359mod tests;