use toasty::{schema::Model, stmt::IntoExpr};
use crate::{Detail, Table};
#[diagnostic::on_unimplemented(
message = "`{Self}` declares no default columns",
label = "a resource's model implements `ModelColumns`",
note = "add `#[derive(tablo::ModelColumns)]` to `{Self}`, or `impl tablo::ModelColumns for \
{Self} {{ const TENANT_COLUMNS: &[&str] = &[..]; }}`, naming its `TenantId` fields, \
when its resources declare their own table and detail page"
)]
pub trait ModelColumns: Model + IntoExpr<Self> + Sized {
const TENANT_COLUMNS: &'static [&'static str];
fn table() -> Table<Self> {
Table::new(())
}
fn detail() -> Detail<Self> {
Detail::empty()
}
fn version(&self) -> Option<u64> {
None
}
}
#[doc(hidden)]
pub mod pick {
use toasty::{
schema::{Field, Model},
stmt::List,
};
use crate::{
BooleanColumn, DateColumn, DateValue, Detail, EmbeddedColumn, Lens, RepeaterColumn, Table,
TextColumn,
schema::{EmbeddedForm, FormInput},
values::FormScalar,
};
pub struct ModelField<M, T, P = T>(pub Lens<M, T, P>);
pub type PathOf<T> = <T as Field>::ExprTarget;
pub trait PickDate<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M, T> PickDate<M> for &&&&&&ModelField<M, T>
where
M: Model + Send + Sync + 'static,
T: DateValue + FormScalar,
{
fn table(&self, table: Table<M>) -> Table<M> {
table.column(DateColumn::new(self.0.clone()).sortable())
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail.column(TextColumn::new(self.0.clone()))
}
}
pub trait PickToggle<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M> PickToggle<M> for &&&&&ModelField<M, bool>
where
M: Model + Send + Sync + 'static,
{
fn table(&self, table: Table<M>) -> Table<M> {
table.column(BooleanColumn::new(self.0.clone()).sortable())
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail.column(BooleanColumn::new(self.0.clone()))
}
}
pub trait PickString<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M, T> PickString<M> for &&&&ModelField<M, T>
where
M: Model + Send + Sync + 'static,
T: FormScalar + Field<Inner = String> + Send + Sync + 'static,
{
fn table(&self, table: Table<M>) -> Table<M> {
table.column(TextColumn::new(self.0.clone()).sortable().searchable())
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail.column(TextColumn::new(self.0.clone()))
}
}
pub trait PickScalar<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M, T> PickScalar<M> for &&&ModelField<M, T>
where
M: Model + Send + Sync + 'static,
T: FormScalar + Send + Sync + 'static,
{
fn table(&self, table: Table<M>) -> Table<M> {
table.column(TextColumn::new(self.0.clone()).sortable())
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail.column(TextColumn::new(self.0.clone()))
}
}
pub trait PickEmbedded<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M, T> PickEmbedded<M> for &&ModelField<M, T>
where
M: Model + Send + Sync + 'static,
T: EmbeddedForm + Send + Sync + 'static,
{
fn table(&self, table: Table<M>) -> Table<M> {
table
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail.column(EmbeddedColumn::new(self.0.clone()))
}
}
pub trait PickRepeater<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M, T> PickRepeater<M> for &ModelField<M, Vec<T>, List<T>>
where
M: Model + Send + Sync + 'static,
T: FormInput,
{
fn table(&self, table: Table<M>) -> Table<M> {
table
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail.column(RepeaterColumn::new(self.0.clone()))
}
}
pub trait PickNone<M> {
fn table(&self, table: Table<M>) -> Table<M>;
fn detail(&self, detail: Detail<M>) -> Detail<M>;
}
impl<M, T, P> PickNone<M> for ModelField<M, T, P> {
fn table(&self, table: Table<M>) -> Table<M> {
table
}
fn detail(&self, detail: Detail<M>) -> Detail<M> {
detail
}
}
}
#[cfg(test)]
mod tests;