use std::sync::Arc;
use toasty::stmt::{List, Query};
use topcoat::{context::Cx, view::*};
use crate::{
DeclarationErrorKind, EmbeddedForm,
schema::{FieldResolver, Grid, Group, RepeaterItem, Section},
table::{
BooleanColumn, BoxColumn, Column, ComputedColumn, CountColumn, EmbeddedColumn, FileColumn,
RelationColumn, RepeaterColumn, TextColumn, include_relations,
},
};
pub struct Detail<M> {
nodes: Vec<Node<M>>,
}
enum Node<M> {
Column(BoxColumn<M>),
Section(Box<Section<Detail<M>>>),
Group(Box<Group<Detail<M>>>),
Grid(Box<Grid<Detail<M>>>),
}
impl<M> Detail<M> {
pub fn new(children: impl IntoDetail<M>) -> Self {
children.into_detail()
}
pub fn empty() -> Self {
Self { nodes: Vec::new() }
}
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn column(mut self, column: impl Column<M> + 'static) -> Self {
self.nodes.push(Node::Column(Arc::new(column)));
self
}
fn append(&mut self, other: Self) {
self.nodes.extend(other.nodes);
}
fn columns(&self) -> Vec<&BoxColumn<M>> {
fn walk<'d, M>(nodes: &'d [Node<M>], out: &mut Vec<&'d BoxColumn<M>>) {
for node in nodes {
match node {
Node::Column(column) => out.push(column),
Node::Section(section) => walk(§ion.children.nodes, out),
Node::Group(group) => walk(&group.children.nodes, out),
Node::Grid(grid) => walk(&grid.children.nodes, out),
}
}
}
let mut out = Vec::new();
walk(&self.nodes, &mut out);
out
}
pub fn include_relations(&self, query: Query<List<M>>) -> Query<List<M>>
where
M: toasty::schema::Model,
{
include_relations(query, self.columns())
}
pub(crate) fn unavailable_sources(&self, cx: &Cx) -> Vec<DeclarationErrorKind> {
crate::table::column::unavailable_sources(cx, self.columns().into_iter().map(|c| &**c))
}
pub fn declaration_errors(&self) -> Vec<DeclarationErrorKind> {
self.columns()
.into_iter()
.filter_map(|column| column.misdeclared())
.collect()
}
pub fn bind(self, db: &toasty::Db) -> Self {
self.bind_with(&FieldResolver::of_db(db));
self
}
pub(crate) fn bind_with(&self, resolver: &FieldResolver) {
for column in self.columns() {
column.bind(resolver);
}
}
pub fn render<'a>(&self, cx: &'a Cx, record: &M) -> BoxView<'a> {
let views: Vec<BoxView<'a>> = self
.nodes
.iter()
.map(|node| match node {
Node::Column(column) => column.entry(cx, record),
Node::Section(section) => {
let body =
(!section.children.is_empty()).then(|| section.children.render(cx, record));
section.chrome(cx, body)
}
Node::Group(group) => group.chrome(cx, group.children.render(cx, record)),
Node::Grid(grid) => grid.chrome(cx, grid.children.render(cx, record)),
})
.collect();
view! {
cx =>
for v in views {
(v)
}
}
.boxed()
}
}
impl<M> Default for Detail<M> {
fn default() -> Self {
Self::empty()
}
}
impl<M> std::fmt::Debug for Detail<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Detail")
.field(
"columns",
&self
.columns()
.into_iter()
.map(|column| column.name())
.collect::<Vec<_>>(),
)
.finish()
}
}
pub trait IntoDetail<M> {
fn into_detail(self) -> Detail<M>;
}
impl<M> IntoDetail<M> for Detail<M> {
fn into_detail(self) -> Detail<M> {
self
}
}
impl<M> IntoDetail<M> for () {
fn into_detail(self) -> Detail<M> {
Detail::empty()
}
}
impl<M> IntoDetail<M> for BoxColumn<M> {
fn into_detail(self) -> Detail<M> {
Detail {
nodes: vec![Node::Column(self)],
}
}
}
impl<M, T> IntoDetail<M> for TextColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: Send + Sync + 'static,
{
fn into_detail(self) -> Detail<M> {
Detail::empty().column(self)
}
}
impl<M, T> IntoDetail<M> for EmbeddedColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: EmbeddedForm + Send + Sync + 'static,
{
fn into_detail(self) -> Detail<M> {
Detail::empty().column(self)
}
}
impl<M, T> IntoDetail<M> for RepeaterColumn<M, T>
where
M: toasty::schema::Model + Send + Sync + 'static,
T: RepeaterItem,
{
fn into_detail(self) -> Detail<M> {
Detail::empty().column(self)
}
}
macro_rules! column_details {
($($ty:ident),+ $(,)?) => {
$(
impl<M> IntoDetail<M> for $ty<M>
where
M: toasty::schema::Model + Send + Sync + 'static,
{
fn into_detail(self) -> Detail<M> {
Detail::empty().column(self)
}
}
)+
};
}
column_details!(
BooleanColumn,
ComputedColumn,
CountColumn,
FileColumn,
RelationColumn
);
macro_rules! block_details {
($($ty:ident),+ $(,)?) => {
$(
impl<M> IntoDetail<M> for $ty<Detail<M>> {
fn into_detail(self) -> Detail<M> {
Detail {
nodes: vec![Node::$ty(Box::new(self))],
}
}
}
impl<M> IntoDetail<M> for $ty<()> {
fn into_detail(self) -> Detail<M> {
self.holding(Detail::empty()).into_detail()
}
}
)+
};
}
block_details!(Section, Group, Grid);
macro_rules! into_detail_tuples {
($($T:ident => $v:ident),+ $(,)?) => {
impl<M, $($T),+> IntoDetail<M> for ($($T,)+)
where
$($T: IntoDetail<M>,)+
{
fn into_detail(self) -> Detail<M> {
let ($($v,)+) = self;
let mut detail = Detail::empty();
$( detail.append($v.into_detail()); )+
detail
}
}
};
}
into_detail_tuples!(A => a);
into_detail_tuples!(A => a, B => b);
into_detail_tuples!(A => a, B => b, C => c);
into_detail_tuples!(A => a, B => b, C => c, D => d);
into_detail_tuples!(A => a, B => b, C => c, D => d, E => e);
into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f);
into_detail_tuples!(A => a, B => b, C => c, D => d, E => e, F => f, G => g);
into_detail_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h
);
into_detail_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i
);
into_detail_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j
);
into_detail_tuples!(
A => a, B => b, C => c, D => d, E => e, F => f, G => g, H => h, I => i, J => j, K => k
);
into_detail_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
);
#[cfg(test)]
mod tests;