use darling::util::IdentString;
use darling::{ast, util, FromDeriveInput, FromField};
use heck::ToTitleCase;
use quote::{quote, ToTokens};
use syn::__private::TokenStream2;
use syn::spanned::Spanned;
#[derive(Debug, FromField)]
#[darling(attributes(table))]
struct TableDataField {
ident: Option<syn::Ident>,
#[darling(default)]
renderer: Option<IdentString>,
#[darling(default)]
precision: Option<i64>,
#[darling(default)]
class: Option<String>,
#[darling(default)]
cell_class: Option<String>,
#[darling(default)]
head_class: Option<String>,
#[darling(default)]
title: Option<String>,
#[darling(default)]
skip: bool,
}
impl TableDataField {
pub fn cell_class(&self) -> String {
let mut class = "".to_owned();
if let Some(ref c) = self.class {
class.push_str(c);
}
if let Some(ref c) = self.cell_class {
class.push(' ');
class.push_str(c);
}
class
}
pub fn head_class(&self) -> String {
let mut class = "".to_owned();
if let Some(ref c) = self.class {
class.push_str(c);
}
if let Some(ref c) = self.head_class {
class.push(' ');
class.push_str(c);
}
class
}
}
#[derive(Debug, FromDeriveInput)]
#[darling(
attributes(table),
supports(struct_named),
forward_attrs(allow, doc, cfg)
)]
pub struct TableDataDeriveInput {
ident: syn::Ident,
data: ast::Data<util::Ignored, TableDataField>,
#[darling(default)]
tag: Option<IdentString>,
#[darling(default)]
row_renderer: Option<IdentString>,
#[darling(default)]
row_class: Option<String>,
#[darling(default)]
head_row_class: Option<String>,
#[darling(default)]
head_cell_renderer: Option<IdentString>,
#[darling(default)]
dyn_row_classes: bool,
}
fn get_renderer_for_field(name: &syn::Ident, field: &TableDataField) -> TokenStream2 {
let props = get_props_for_field(name, &field);
if let Some(renderer) = &field.renderer {
let ident = renderer.as_ident();
quote! {
rsx! {
#ident {
#props
}
}
}
} else {
quote! {
rsx! {
DefaultTableCellRenderer {
#props
}
}
}
}
}
fn get_head_renderer_for_field(
head_cell_renderer: &Option<IdentString>,
) -> TokenStream2 {
if let Some(renderer) = &head_cell_renderer {
let ident = renderer.as_ident();
quote! {#ident}
} else {
quote! {DefaultTableHeaderRenderer}
}
}
fn get_props_for_field(name: &syn::Ident, field: &TableDataField) -> TokenStream2 {
let class = field.cell_class();
let precision = if let Some(p) = &field.precision {
quote!(precision: #p,)
} else {
quote!()
};
quote! {
value: item.#name.clone(),
class: #class,
#precision
}
}
impl ToTokens for TableDataDeriveInput {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let TableDataDeriveInput {
ref ident,
ref data,
ref row_renderer,
ref tag,
ref row_class,
ref head_row_class,
ref head_cell_renderer,
dyn_row_classes,
} = *self;
let row_renderer = row_renderer
.as_ref()
.map(|r| r.as_ident().clone())
.unwrap_or_else(|| syn::Ident::new("DefaultTableRowRenderer", row_renderer.span()));
let tag = tag
.as_ref()
.map(|s| {
let t = s.as_ident();
quote!(#t)
})
.unwrap_or(quote!(table));
let mut row_class = row_class
.as_ref()
.map(|s| s.clone())
.unwrap_or("".to_owned());
let dyn_row_class_compute = if dyn_row_classes {
row_class.push_str(" {dyn_row_class}");
quote! {
let dyn_row_class = item.row_classes(i, cx).join(" ");
}
} else {
quote! {}
};
let head_row_class = head_row_class
.as_ref()
.map(|s| s.clone())
.unwrap_or("".to_owned());
let fields = data.as_ref().take_struct().expect("Is not enum").fields;
let mut titles = vec![];
let mut cells = vec![];
for (i, f) in fields.into_iter().enumerate() {
if f.skip {
continue;
}
let name = f.ident.as_ref().expect("named field");
let name_str = name.to_string();
let title = if let Some(ref t) = f.title {
t.clone()
} else {
name.to_string().to_title_case()
};
let head_class = f.head_class();
let head_renderer = get_head_renderer_for_field(head_cell_renderer);
titles.push(quote! { rsx! {
#head_renderer {
class: #head_class,
column_index: #i,
field: #name_str,
onclick: move |evt| cx.props.onheadclick.call(evt),
#title
}
}});
let cell_renderer = get_renderer_for_field(name, f);
cells.push(quote! { rsx! {
#cell_renderer
}});
}
tokens.extend(quote! {
use dioxus::events::MouseEvent;
#[derive(Props)]
pub struct TableProps<'a> {
#[props(default)]
class: &'a str,
items: &'a Vec<#ident>,
#[props(default)]
pub onrowclick: EventHandler<'a, TableRowEvent<'a, #ident, MouseEvent>>,
#[props(default)]
pub onheadclick: EventHandler<'a, TableHeadEvent<MouseEvent>>,
}
#[allow(non_snake_case)]
pub fn Table<'a>(cx: Scope<'a, TableProps<'a>>) -> Element {
cx.render(rsx!{
#tag {
class: "{cx.props.class}",
tr {
class: #head_row_class,
#(#titles)*
}
cx.props.items.iter().enumerate().map(|(i, item)| {
#dyn_row_class_compute
rsx!{
#row_renderer {
index: i,
item: item,
class: #row_class,
onclick: move |evt| cx.props.onrowclick.call(evt),
#(#cells)*
}
}
})
}
})
}
});
}
}