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, IconColumn, ImageColumn, ListColumn, RelationColumn,
15 RepeaterColumn, SelectColumn, TextColumn, TextInputColumn, ToggleColumn, include_relations,
16 restrict_relations, shows, unavailable_sources,
17 },
18 schema::{Grid, Group, HoldsTabs, Section, Tab, TabView, Tabs, tab_id, tab_view},
19};
20
21pub struct Detail<M> {
48 nodes: Vec<Node<M>>,
49}
50
51fn entry<'a>(cx: &'a Cx, name: &str, entry: BoxView<'a>) -> BoxView<'a> {
53 let name = name.to_string();
54 view! { <div class="contents" data-entry=(name)>(entry)</div> }.boxed()
55}
56
57enum Node<M> {
59 Column(BoxColumn<M>),
60 Section(Box<Section<Detail<M>>>),
61 Group(Box<Group<Detail<M>>>),
62 Grid(Box<Grid<Detail<M>>>),
63 Tabs(Box<Tabs<Detail<M>>>),
65 Tab(Box<Tab<Detail<M>>>),
67}
68
69impl<M> Detail<M> {
70 pub fn new(children: impl IntoDetail<M>) -> Self {
72 children.into_detail()
73 }
74
75 pub fn empty() -> Self {
79 Self { nodes: Vec::new() }
80 }
81
82 pub(crate) fn of_columns(columns: &[BoxColumn<M>]) -> Self {
84 Self {
85 nodes: columns.iter().cloned().map(Node::Column).collect(),
86 }
87 }
88
89 pub fn is_empty(&self) -> bool {
91 self.nodes.is_empty()
92 }
93
94 pub fn column(mut self, column: impl Column<M> + 'static) -> Self {
96 self.nodes.push(Node::Column(Arc::new(column)));
97 self
98 }
99
100 fn append(&mut self, other: Self) {
102 self.nodes.extend(other.nodes);
103 }
104
105 pub(crate) fn read_bare(&mut self, bare: fn(&M) -> M)
108 where
109 M: toasty::schema::Model + 'static,
110 {
111 fn walk<M: toasty::schema::Model + 'static>(nodes: &mut [Node<M>], bare: fn(&M) -> M) {
112 for node in nodes {
113 match node {
114 Node::Column(column) => {
115 *column = crate::column::bare_column(column.clone(), bare);
116 }
117 Node::Section(section) => walk(&mut section.children.nodes, bare),
118 Node::Group(group) => walk(&mut group.children.nodes, bare),
119 Node::Grid(grid) => walk(&mut grid.children.nodes, bare),
120 Node::Tabs(tabs) => walk(&mut tabs.children.nodes, bare),
121 Node::Tab(tab) => walk(&mut tab.children.nodes, bare),
122 }
123 }
124 }
125 let loads = self
126 .columns()
127 .iter()
128 .any(|column| !column.includes().into_vec().is_empty());
129 if loads {
130 walk(&mut self.nodes, bare);
131 }
132 }
133
134 pub(crate) fn columns(&self) -> Vec<&BoxColumn<M>> {
136 fn walk<'d, M>(nodes: &'d [Node<M>], out: &mut Vec<&'d BoxColumn<M>>) {
137 for node in nodes {
138 match node {
139 Node::Column(column) => out.push(column),
140 Node::Section(section) => walk(§ion.children.nodes, out),
141 Node::Group(group) => walk(&group.children.nodes, out),
142 Node::Grid(grid) => walk(&grid.children.nodes, out),
143 Node::Tabs(tabs) => walk(&tabs.children.nodes, out),
144 Node::Tab(tab) => walk(&tab.children.nodes, out),
145 }
146 }
147 }
148 let mut out = Vec::new();
149 walk(&self.nodes, &mut out);
150 out
151 }
152
153 pub async fn load(&self, cx: &Cx, query: Query<List<M>>) -> topcoat::Result<Vec<M>>
162 where
163 M: toasty::schema::Model + Send + Sync + 'static,
164 {
165 let mut db = crate::db::db(cx)?;
166 let records = self
167 .include_relations(query)
168 .exec(&mut db)
169 .await
170 .map_err(crate::error::unavailable)?;
171 self.restrict_relations(cx, &records, &mut db).await?;
172 Ok(records)
173 }
174
175 pub(crate) fn include_relations(&self, query: Query<List<M>>) -> Query<List<M>>
177 where
178 M: toasty::schema::Model,
179 {
180 include_relations(query, self.columns())
181 }
182
183 pub(crate) async fn restrict_relations(
186 &self,
187 cx: &Cx,
188 records: &[M],
189 ex: &mut dyn toasty::Executor,
190 ) -> topcoat::Result<()> {
191 restrict_relations(cx, self.columns(), records, ex).await
192 }
193
194 pub(crate) fn unavailable_sources(&self, cx: &Cx) -> Vec<DeclarationErrorKind> {
196 unavailable_sources(cx, self.columns().into_iter().map(|c| &**c))
197 }
198
199 pub fn declaration_errors(&self) -> Vec<DeclarationErrorKind> {
201 self.columns()
202 .into_iter()
203 .filter_map(|column| column.internals().misdeclared())
204 .collect()
205 }
206
207 pub fn render<'a>(&self, cx: &'a Cx, record: &M) -> BoxView<'a> {
214 self.render_without(cx, record, &[])
215 }
216
217 pub(crate) fn render_without<'a>(
220 &self,
221 cx: &'a Cx,
222 record: &M,
223 hidden: &[String],
224 ) -> BoxView<'a> {
225 self.render_in(cx, record, hidden, 0)
226 }
227
228 fn render_in<'a>(
230 &self,
231 cx: &'a Cx,
232 record: &M,
233 hidden: &[String],
234 depth: usize,
235 ) -> BoxView<'a> {
236 let views: Vec<BoxView<'a>> = self
237 .nodes
238 .iter()
239 .filter(|node| match node {
240 Node::Column(column) => shows(cx, &**column, hidden),
241 Node::Section(section) => {
242 section.children.is_empty() || section.children.shows_any(cx, hidden)
243 }
244 Node::Group(group) => group.children.shows_any(cx, hidden),
245 Node::Grid(grid) => grid.children.shows_any(cx, hidden),
246 Node::Tabs(tabs) => tabs.children.shows_any(cx, hidden),
247 Node::Tab(tab) => tab.children.shows_any(cx, hidden),
248 })
249 .map(|node| match node {
250 Node::Column(column) => entry(cx, column.name(), column.entry(cx, record)),
251 Node::Section(section) => {
252 let body = (!section.children.is_empty())
253 .then(|| section.children.render_in(cx, record, hidden, depth));
254 section.chrome(cx, body)
255 }
256 Node::Group(group) => {
257 group.chrome(cx, group.children.render_in(cx, record, hidden, depth))
258 }
259 Node::Grid(grid) => {
260 grid.chrome(cx, grid.children.render_in(cx, record, hidden, depth))
261 }
262 Node::Tabs(tabs) => {
263 tabs.chrome(cx, tabs.children.render_tabs(cx, record, hidden, depth))
264 }
265 Node::Tab(tab) => tab.children.render_in(cx, record, hidden, depth),
267 })
268 .collect();
269 stack(cx, views)
270 }
271
272 fn render_tabs<'a>(
275 &self,
276 cx: &'a Cx,
277 record: &M,
278 hidden: &[String],
279 depth: usize,
280 ) -> Vec<TabView<'a>> {
281 self.nodes
282 .iter()
283 .filter_map(|node| match node {
284 Node::Tab(tab) if tab.children.shows_any(cx, hidden) => Some(tab),
285 _ => None,
286 })
287 .enumerate()
288 .map(|(index, tab)| {
289 let id = tab
290 .children
291 .columns()
292 .first()
293 .map(|column| tab_id(&format!("detail-{}", column.name()), depth))
294 .unwrap_or_default();
295 let body = tab.children.render_in(cx, record, hidden, depth + 1);
296 tab_view(cx, &id, tab.face(), index == 0, false, body)
297 })
298 .collect()
299 }
300
301 fn shows_any(&self, cx: &Cx, hidden: &[String]) -> bool {
303 self.columns()
304 .into_iter()
305 .any(|column| shows(cx, &**column, hidden))
306 }
307
308 pub(crate) fn undeclared_reads(&self) -> Vec<&str> {
310 self.columns()
311 .into_iter()
312 .filter(|column| column.reads().is_none())
313 .map(|column| column.name())
314 .collect()
315 }
316}
317
318fn stack<'a>(cx: &'a Cx, views: Vec<BoxView<'a>>) -> BoxView<'a> {
321 view! {
322 for v in views {
323 (v)
324 }
325 }
326 .boxed()
327}
328
329impl<M> Default for Detail<M> {
330 fn default() -> Self {
331 Self::empty()
332 }
333}
334
335impl<M> std::fmt::Debug for Detail<M> {
336 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
337 f.debug_struct("Detail")
338 .field(
339 "columns",
340 &self
341 .columns()
342 .into_iter()
343 .map(|column| column.name())
344 .collect::<Vec<_>>(),
345 )
346 .finish()
347 }
348}
349
350pub trait IntoDetail<M> {
378 fn into_detail(self) -> Detail<M>;
379}
380
381impl<M> IntoDetail<M> for Detail<M> {
382 fn into_detail(self) -> Detail<M> {
383 self
384 }
385}
386
387impl<M> IntoDetail<M> for () {
388 fn into_detail(self) -> Detail<M> {
389 Detail::empty()
390 }
391}
392
393impl<M> IntoDetail<M> for BoxColumn<M> {
394 fn into_detail(self) -> Detail<M> {
395 Detail {
396 nodes: vec![Node::Column(self)],
397 }
398 }
399}
400
401macro_rules! column_details {
403 ($($ty:ident<$($g:ident),+>),+ $(,)?) => {
404 $(
405 impl<$($g),+> IntoDetail<M> for $ty<$($g),+>
406 where
407 Self: Column<M> + 'static,
408 {
409 fn into_detail(self) -> Detail<M> {
410 Detail::empty().column(self)
411 }
412 }
413 )+
414 };
415}
416
417column_details!(
418 TextColumn<M, T>,
419 DateColumn<M, T>,
420 ComputedColumn<M>,
421 RelationColumn<M>,
422 CountColumn<M>,
423 BooleanColumn<M>,
424 FileColumn<M>,
425 ColorColumn<M, T>,
426 IconColumn<M, T>,
427 ImageColumn<M, T>,
428 ToggleColumn<M>,
429 SelectColumn<M, T>,
430 TextInputColumn<M, T>,
431 EmbeddedColumn<M, T>,
432 ListColumn<M, T>,
433 RepeaterColumn<M, T>,
434);
435#[cfg(feature = "rich-text")]
436column_details!(RichTextColumn<M, T>);
437
438macro_rules! block_columns {
440 ($($ty:ident),+ $(,)?) => {
441 $(
442 impl $ty<()> {
443 pub fn columns<M>(self, children: impl IntoDetail<M>) -> $ty<Detail<M>> {
445 self.holding(children.into_detail())
446 }
447 }
448 )+
449 };
450}
451
452block_columns!(Section, Group, Grid, Tab);
453
454impl<M> HoldsTabs for Detail<M> {
455 fn push_tab(&mut self, tab: Tab<Self>) {
456 self.nodes.push(Node::Tab(Box::new(tab)));
457 }
458}
459
460impl<M> IntoDetail<M> for Tabs<Detail<M>> {
461 fn into_detail(self) -> Detail<M> {
462 Detail {
463 nodes: vec![Node::Tabs(Box::new(self))],
464 }
465 }
466}
467
468macro_rules! block_details {
470 ($($ty:ident),+ $(,)?) => {
471 $(
472 impl<M> IntoDetail<M> for $ty<Detail<M>> {
473 fn into_detail(self) -> Detail<M> {
474 Detail {
475 nodes: vec![Node::$ty(Box::new(self))],
476 }
477 }
478 }
479
480 impl<M> IntoDetail<M> for $ty<()> {
481 fn into_detail(self) -> Detail<M> {
482 self.holding(Detail::empty()).into_detail()
483 }
484 }
485 )+
486 };
487}
488
489block_details!(Section, Group, Grid);
490
491macro_rules! into_detail_tuples {
493 ($($T:ident => $v:ident),+ $(,)?) => {
494 impl<M, $($T),+> IntoDetail<M> for ($($T,)+)
495 where
496 $($T: IntoDetail<M>,)+
497 {
498 fn into_detail(self) -> Detail<M> {
499 let ($($v,)+) = self;
500 let mut detail = Detail::empty();
501 $( detail.append($v.into_detail()); )+
502 detail
503 }
504 }
505 };
506}
507
508into_detail_tuples!(A => a);
509into_detail_tuples!(A => a, B => b);
510into_detail_tuples!(A => a, B => b, C => c);
511into_detail_tuples!(A => a, B => b, C => c, D => d);
512into_detail_tuples!(A => a, B => b, C => c, D => d, E => e);
513into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
514into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
515into_detail_tuples!(
516 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
517);
518into_detail_tuples!(
519 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
520);
521into_detail_tuples!(
522 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
523);
524into_detail_tuples!(
525 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
526);
527into_detail_tuples!(
528 A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k, L => l
529);
530
531#[cfg(test)]
532mod tests;