1use std::sync::Arc;
4
5use toasty::stmt::{List, Query};
6use topcoat::{context::Cx, view::*};
7
8#[cfg(feature = "rich-text")]
9use crate::column::RichTextColumn;
10use crate::{
11 DeclarationErrorKind,
12 column::{
13 BooleanColumn, BoxColumn, ColorColumn, Column, ComputedColumn, CountColumn, DateColumn,
14 EmbeddedColumn, FileColumn, ImageColumn, RelationColumn, RepeaterColumn, TextColumn,
15 include_relations, restrict_relations, shows, unavailable_sources,
16 },
17 schema::{Grid, Group, HoldsTabs, Section, Tab, Tabs, tab_id, tab_view, tabs_chrome},
18};
19
20pub struct Detail<M> {
47 nodes: Vec<Node<M>>,
48}
49
50enum Node<M> {
52 Column(BoxColumn<M>),
53 Section(Box<Section<Detail<M>>>),
54 Group(Box<Group<Detail<M>>>),
55 Grid(Box<Grid<Detail<M>>>),
56 Tabs(Box<Tabs<Detail<M>>>),
58 Tab(Box<Tab<Detail<M>>>),
60}
61
62impl<M> Detail<M> {
63 pub fn new(children: impl IntoDetail<M>) -> Self {
65 children.into_detail()
66 }
67
68 pub fn empty() -> Self {
70 Self { nodes: Vec::new() }
71 }
72
73 pub fn is_empty(&self) -> bool {
75 self.nodes.is_empty()
76 }
77
78 pub fn column(mut self, column: impl Column<M> + 'static) -> Self {
80 self.nodes.push(Node::Column(Arc::new(column)));
81 self
82 }
83
84 fn append(&mut self, other: Self) {
86 self.nodes.extend(other.nodes);
87 }
88
89 pub(crate) fn columns(&self) -> Vec<&BoxColumn<M>> {
91 fn walk<'d, M>(nodes: &'d [Node<M>], out: &mut Vec<&'d BoxColumn<M>>) {
92 for node in nodes {
93 match node {
94 Node::Column(column) => out.push(column),
95 Node::Section(section) => walk(§ion.children.nodes, out),
96 Node::Group(group) => walk(&group.children.nodes, out),
97 Node::Grid(grid) => walk(&grid.children.nodes, out),
98 Node::Tabs(tabs) => walk(&tabs.children.nodes, out),
99 Node::Tab(tab) => walk(&tab.children.nodes, out),
100 }
101 }
102 }
103 let mut out = Vec::new();
104 walk(&self.nodes, &mut out);
105 out
106 }
107
108 pub async fn load(&self, cx: &Cx, query: Query<List<M>>) -> topcoat::Result<Vec<M>>
117 where
118 M: toasty::schema::Model + Send + Sync + 'static,
119 {
120 let mut db = crate::db::db(cx)?;
121 let records = self
122 .include_relations(query)
123 .exec(&mut db)
124 .await
125 .map_err(crate::error::unavailable)?;
126 self.restrict_relations(cx, &records, &mut db).await?;
127 Ok(records)
128 }
129
130 pub(crate) fn include_relations(&self, query: Query<List<M>>) -> Query<List<M>>
132 where
133 M: toasty::schema::Model,
134 {
135 include_relations(query, self.columns())
136 }
137
138 pub(crate) async fn restrict_relations(
141 &self,
142 cx: &Cx,
143 records: &[M],
144 ex: &mut dyn toasty::Executor,
145 ) -> topcoat::Result<()> {
146 restrict_relations(cx, self.columns(), records, ex).await
147 }
148
149 pub(crate) fn unavailable_sources(&self, cx: &Cx) -> Vec<DeclarationErrorKind> {
151 unavailable_sources(cx, self.columns().into_iter().map(|c| &**c))
152 }
153
154 pub fn declaration_errors(&self) -> Vec<DeclarationErrorKind> {
156 self.columns()
157 .into_iter()
158 .filter_map(|column| column.misdeclared())
159 .collect()
160 }
161
162 pub fn render<'a>(&self, cx: &'a Cx, record: &M) -> BoxView<'a> {
169 self.render_without(cx, record, &[])
170 }
171
172 pub(crate) fn render_without<'a>(
175 &self,
176 cx: &'a Cx,
177 record: &M,
178 hidden: &[String],
179 ) -> BoxView<'a> {
180 self.render_in(cx, record, hidden, 0)
181 }
182
183 fn render_in<'a>(
185 &self,
186 cx: &'a Cx,
187 record: &M,
188 hidden: &[String],
189 depth: usize,
190 ) -> BoxView<'a> {
191 let views: Vec<BoxView<'a>> = self
192 .nodes
193 .iter()
194 .filter(|node| match node {
195 Node::Column(column) => shows(cx, &**column, hidden),
196 Node::Section(section) => {
197 section.children.is_empty() || section.children.shows_any(cx, hidden)
198 }
199 Node::Group(group) => group.children.shows_any(cx, hidden),
200 Node::Grid(grid) => grid.children.shows_any(cx, hidden),
201 Node::Tabs(tabs) => tabs.children.shows_any(cx, hidden),
202 Node::Tab(tab) => tab.children.shows_any(cx, hidden),
203 })
204 .map(|node| match node {
205 Node::Column(column) => column.entry(cx, record),
206 Node::Section(section) => {
207 let body = (!section.children.is_empty())
208 .then(|| section.children.render_in(cx, record, hidden, depth));
209 section.chrome(cx, body)
210 }
211 Node::Group(group) => {
212 group.chrome(cx, group.children.render_in(cx, record, hidden, depth))
213 }
214 Node::Grid(grid) => {
215 grid.chrome(cx, grid.children.render_in(cx, record, hidden, depth))
216 }
217 Node::Tabs(tabs) => tabs.children.render_tabs(cx, record, hidden, depth),
218 Node::Tab(tab) => tab.children.render_in(cx, record, hidden, depth),
220 })
221 .collect();
222 stack(cx, views)
223 }
224
225 fn render_tabs<'a>(
228 &self,
229 cx: &'a Cx,
230 record: &M,
231 hidden: &[String],
232 depth: usize,
233 ) -> BoxView<'a> {
234 let views = self
235 .nodes
236 .iter()
237 .filter_map(|node| match node {
238 Node::Tab(tab) if tab.children.shows_any(cx, hidden) => Some(tab),
239 _ => None,
240 })
241 .enumerate()
242 .map(|(index, tab)| {
243 let id = tab
244 .children
245 .columns()
246 .first()
247 .map(|column| tab_id(&format!("detail-{}", column.name()), depth))
248 .unwrap_or_default();
249 let body = tab.children.render_in(cx, record, hidden, depth + 1);
250 tab_view(cx, &id, tab.label(), index == 0, false, body)
251 })
252 .collect();
253 tabs_chrome(cx, views)
254 }
255
256 fn shows_any(&self, cx: &Cx, hidden: &[String]) -> bool {
258 self.columns()
259 .into_iter()
260 .any(|column| shows(cx, &**column, hidden))
261 }
262
263 pub(crate) fn undeclared_reads(&self) -> Vec<&str> {
265 self.columns()
266 .into_iter()
267 .filter(|column| column.reads().is_none())
268 .map(|column| column.name())
269 .collect()
270 }
271}
272
273fn stack<'a>(cx: &'a Cx, views: Vec<BoxView<'a>>) -> BoxView<'a> {
276 view! {
277 cx =>
278 for v in views {
279 (v)
280 }
281 }
282 .boxed()
283}
284
285impl<M> Default for Detail<M> {
286 fn default() -> Self {
287 Self::empty()
288 }
289}
290
291impl<M> std::fmt::Debug for Detail<M> {
292 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
293 f.debug_struct("Detail")
294 .field(
295 "columns",
296 &self
297 .columns()
298 .into_iter()
299 .map(|column| column.name())
300 .collect::<Vec<_>>(),
301 )
302 .finish()
303 }
304}
305
306pub trait IntoDetail<M> {
334 fn into_detail(self) -> Detail<M>;
335}
336
337impl<M> IntoDetail<M> for Detail<M> {
338 fn into_detail(self) -> Detail<M> {
339 self
340 }
341}
342
343impl<M> IntoDetail<M> for () {
344 fn into_detail(self) -> Detail<M> {
345 Detail::empty()
346 }
347}
348
349impl<M> IntoDetail<M> for BoxColumn<M> {
350 fn into_detail(self) -> Detail<M> {
351 Detail {
352 nodes: vec![Node::Column(self)],
353 }
354 }
355}
356
357macro_rules! column_details {
359 ($($ty:ident<$($g:ident),+>),+ $(,)?) => {
360 $(
361 impl<$($g),+> IntoDetail<M> for $ty<$($g),+>
362 where
363 Self: Column<M> + 'static,
364 {
365 fn into_detail(self) -> Detail<M> {
366 Detail::empty().column(self)
367 }
368 }
369 )+
370 };
371}
372
373column_details!(
374 TextColumn<M, T>,
375 DateColumn<M, T>,
376 ComputedColumn<M>,
377 RelationColumn<M>,
378 CountColumn<M>,
379 BooleanColumn<M>,
380 FileColumn<M>,
381 ColorColumn<M, T>,
382 ImageColumn<M, T>,
383 EmbeddedColumn<M, T>,
384 RepeaterColumn<M, T>,
385);
386#[cfg(feature = "rich-text")]
387column_details!(RichTextColumn<M, T>);
388
389macro_rules! block_columns {
391 ($($ty:ident),+ $(,)?) => {
392 $(
393 impl $ty<()> {
394 pub fn columns<M>(self, children: impl IntoDetail<M>) -> $ty<Detail<M>> {
396 self.holding(children.into_detail())
397 }
398 }
399 )+
400 };
401}
402
403block_columns!(Section, Group, Grid, Tab);
404
405impl<M> HoldsTabs for Detail<M> {
406 fn push_tab(&mut self, tab: Tab<Self>) {
407 self.nodes.push(Node::Tab(Box::new(tab)));
408 }
409}
410
411impl<M> IntoDetail<M> for Tabs<Detail<M>> {
412 fn into_detail(self) -> Detail<M> {
413 Detail {
414 nodes: vec![Node::Tabs(Box::new(self))],
415 }
416 }
417}
418
419macro_rules! block_details {
421 ($($ty:ident),+ $(,)?) => {
422 $(
423 impl<M> IntoDetail<M> for $ty<Detail<M>> {
424 fn into_detail(self) -> Detail<M> {
425 Detail {
426 nodes: vec![Node::$ty(Box::new(self))],
427 }
428 }
429 }
430
431 impl<M> IntoDetail<M> for $ty<()> {
432 fn into_detail(self) -> Detail<M> {
433 self.holding(Detail::empty()).into_detail()
434 }
435 }
436 )+
437 };
438}
439
440block_details!(Section, Group, Grid);
441
442macro_rules! into_detail_tuples {
444 ($($T:ident => $v:ident),+ $(,)?) => {
445 impl<M, $($T),+> IntoDetail<M> for ($($T,)+)
446 where
447 $($T: IntoDetail<M>,)+
448 {
449 fn into_detail(self) -> Detail<M> {
450 let ($($v,)+) = self;
451 let mut detail = Detail::empty();
452 $( detail.append($v.into_detail()); )+
453 detail
454 }
455 }
456 };
457}
458
459into_detail_tuples!(A => a);
460into_detail_tuples!(A => a, B => b);
461into_detail_tuples!(A => a, B => b, C => c);
462into_detail_tuples!(A => a, B => b, C => c, D => d);
463into_detail_tuples!(A => a, B => b, C => c, D => d, E => e);
464into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
465into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
466into_detail_tuples!(
467 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
468);
469into_detail_tuples!(
470 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
471);
472into_detail_tuples!(
473 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
474);
475into_detail_tuples!(
476 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
477);
478into_detail_tuples!(
479 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l
480);
481
482#[cfg(test)]
483mod tests;