use derive_where::derive_where;
use toasty::stmt::{Expr, IntoExpr, List, Path, Query};
use topcoat::{Result, context::Cx};
use super::{Table, TableState};
use crate::{DeclarationErrorKind, lens::Reads, schema::Options, values::FormScalar};
#[derive_where(Clone)]
pub struct ListTab<M> {
key: String,
label: crate::Text,
query: Option<Expr<bool>>,
badge: bool,
_model: std::marker::PhantomData<fn() -> M>,
}
impl<M> ListTab<M> {
pub fn new(key: impl Into<String>, label: impl Into<crate::Text>) -> Self {
Self {
key: key.into(),
label: label.into(),
query: None,
badge: false,
_model: std::marker::PhantomData,
}
}
pub fn query(mut self, predicate: Expr<bool>) -> Self {
self.query = Some(predicate);
self
}
pub fn badge(mut self) -> Self {
self.badge = true;
self
}
pub(crate) fn key(&self) -> &str {
&self.key
}
pub(crate) fn label(&self) -> crate::Text {
self.label.clone()
}
pub(crate) fn has_badge(&self) -> bool {
self.badge
}
pub(crate) fn reads(&self) -> Reads<M>
where
M: toasty::schema::Model,
{
self.query
.as_ref()
.map_or_else(Reads::nothing, Reads::of_predicate)
}
}
impl<M> Table<M> {
pub fn tab(mut self, tab: ListTab<M>) -> Self {
self.tabs.push(tab);
self
}
pub fn option_tabs<T>(mut self, lens: impl Into<Path<M, T>>) -> Self
where
T: Options + FormScalar + IntoExpr<T>,
{
let path: Path<M, T> = lens.into();
self.tabs
.push(ListTab::new("all", crate::text!("tablo-list-tab-all")));
for (value, label) in T::options() {
match T::parse_form(&value) {
Ok(option) => self.tabs.push(
ListTab::new(value, label)
.query(path.clone().eq(option))
.badge(),
),
Err(_) => self
.misdeclared
.push(DeclarationErrorKind::UnparsedTabOption { value }),
}
}
self
}
pub(crate) fn declared_tabs(&self) -> &[ListTab<M>] {
&self.tabs
}
pub(crate) fn active_tab(&self, state: &TableState) -> Option<&ListTab<M>> {
match &state.tab {
Some(key) => self.tabs.iter().find(|tab| tab.key == *key),
None => self.tabs.first(),
}
}
pub(crate) fn unknown_tab<'s>(&self, state: &'s TableState) -> Option<&'s str> {
state
.tab
.as_deref()
.filter(|key| self.tabs.iter().all(|tab| tab.key != *key))
}
pub(super) fn narrow_tab(&self, query: Query<List<M>>, state: &TableState) -> Query<List<M>>
where
M: toasty::schema::Model,
{
match self.active_tab(state).and_then(|tab| tab.query.clone()) {
Some(predicate) => query.filter(predicate),
None => query,
}
}
pub(crate) async fn tab_counts(
&self,
cx: &Cx,
query: &Query<List<M>>,
state: &TableState,
refused: &[String],
) -> Result<Vec<(String, u64)>>
where
M: toasty::schema::Model,
{
let mut counts = Vec::new();
if !self.tabs.iter().any(ListTab::has_badge) {
return Ok(counts);
}
let mut db = crate::db::db(cx)?;
let narrowed = self.narrow_untabbed(cx, &db, query.clone(), state, refused);
for tab in self.tabs.iter().filter(|tab| tab.badge) {
let rows = match &tab.query {
Some(predicate) => narrowed.clone().filter(predicate.clone()),
None => narrowed.clone(),
};
let count = crate::toasty_compat::count(rows, &mut db)
.await
.map_err(crate::error::unavailable)?;
counts.push((tab.key.clone(), count));
}
Ok(counts)
}
pub(super) fn tab_errors(&self) -> Vec<DeclarationErrorKind> {
let mut seen = std::collections::HashSet::with_capacity(self.tabs.len());
self.tabs
.iter()
.filter(|tab| !seen.insert(tab.key.as_str()))
.map(|tab| DeclarationErrorKind::DuplicateTab {
key: tab.key.clone(),
})
.collect()
}
}
#[cfg(test)]
mod tests;