use std::{ops::Deref, sync::Arc};
use topcoat::context::Cx;
use super::TableSignals;
use crate::{
resource::Trashed,
schema::Schema,
table::{BoxColumn, ColumnChoice, Table, TableState},
};
pub(crate) type RowPolicy<M> = Arc<dyn Fn(&M) -> RowActions + Send + Sync>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct RowActions {
pub view: bool,
pub edit: bool,
pub delete: bool,
}
impl RowActions {
pub const ALL: Self = Self {
view: true,
edit: true,
delete: true,
};
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub(crate) struct TableChrome {
pub(crate) delete: bool,
pub(crate) edit: bool,
pub(crate) view: bool,
pub(crate) actions: bool,
}
pub(crate) struct TableAction<M> {
pub(crate) name: &'static str,
pub(crate) label: String,
pub(crate) row: bool,
pub(crate) bulk: bool,
pub(crate) confirm: bool,
pub(crate) input: Option<fn() -> Schema>,
pub(crate) trashed: Trashed,
pub(crate) allowed: Arc<dyn Fn(&M) -> bool + Send + Sync>,
}
pub(crate) struct Wiring<M> {
row_policy: Option<RowPolicy<M>>,
delete_prefix: Option<String>,
edit_prefix: Option<String>,
view_prefix: Option<String>,
bulk_delete: bool,
custom_actions: Vec<TableAction<M>>,
actions_prefix: Option<String>,
return_to: Option<String>,
prefix: Option<String>,
choice: ColumnChoice,
columns_url: Option<String>,
}
impl<M> Default for Wiring<M> {
fn default() -> Self {
Self {
row_policy: None,
delete_prefix: None,
edit_prefix: None,
view_prefix: None,
bulk_delete: false,
custom_actions: Vec::new(),
actions_prefix: None,
return_to: None,
prefix: None,
choice: ColumnChoice::default(),
columns_url: None,
}
}
}
pub struct WiredTable<M> {
table: Arc<Table<M>>,
pub(super) wiring: Wiring<M>,
}
impl<M> Deref for WiredTable<M> {
type Target = Table<M>;
fn deref(&self) -> &Table<M> {
&self.table
}
}
impl<M> std::fmt::Debug for WiredTable<M> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let wiring = &self.wiring;
f.debug_struct("WiredTable")
.field("table", &self.table)
.field("row_policy", &wiring.row_policy.is_some())
.field("delete_prefix", &wiring.delete_prefix)
.field("edit_prefix", &wiring.edit_prefix)
.field("view_prefix", &wiring.view_prefix)
.field("bulk_delete", &wiring.bulk_delete)
.field(
"custom_actions",
&wiring
.custom_actions
.iter()
.map(|a| a.name)
.collect::<Vec<_>>(),
)
.field("return_to", &wiring.return_to)
.field("prefix", &wiring.prefix)
.field("choice", &wiring.choice)
.field("columns_url", &wiring.columns_url)
.finish()
}
}
impl<M> WiredTable<M> {
pub(crate) fn new(table: Arc<Table<M>>) -> Self {
Self {
table,
wiring: Wiring::default(),
}
}
pub(crate) fn row_actions(
mut self,
policy: impl Fn(&M) -> RowActions + Send + Sync + 'static,
) -> Self {
self.wiring.row_policy = Some(Arc::new(policy));
self
}
pub(crate) fn with_delete(mut self, prefix: String) -> Self {
if self.is_addressable() {
self.wiring.delete_prefix = Some(prefix);
}
self
}
pub(crate) fn with_edit(mut self, prefix: String) -> Self {
if self.is_addressable() {
self.wiring.edit_prefix = Some(prefix);
}
self
}
pub(crate) fn with_view(mut self, prefix: String) -> Self {
if self.is_addressable() {
self.wiring.view_prefix = Some(prefix);
}
self
}
pub(crate) fn with_bulk_delete(mut self, enabled: bool) -> Self {
self.wiring.bulk_delete = enabled;
self
}
pub(crate) fn with_custom_actions(
mut self,
prefix: String,
actions: Vec<TableAction<M>>,
) -> Self {
if self.is_addressable() {
self.wiring.actions_prefix = Some(prefix);
self.wiring.custom_actions = actions;
}
self
}
pub(crate) fn with_column_choice(mut self, choice: ColumnChoice, url: Option<String>) -> Self {
let shows_any = self
.declared_columns()
.iter()
.any(|col| choice.shows(col.name(), col.toggle()));
if shows_any {
self.wiring.choice = choice;
}
self.wiring.columns_url = url;
self
}
pub(crate) fn returning_to(mut self, url: String) -> Self {
self.wiring.return_to = Some(url);
self
}
#[must_use]
pub fn prefixed(mut self, prefix: impl Into<String>) -> Self {
self.wiring.prefix = Some(prefix.into());
self
}
pub(crate) fn taking(mut self, shown: Trashed) -> Self {
let overlaps = |taken: Trashed| {
[false, true]
.into_iter()
.any(|trashed| shown.admits(trashed) && taken.admits(trashed))
};
self.wiring
.custom_actions
.retain(|action| overlaps(action.trashed));
if !overlaps(Trashed::Without) {
self.wiring.bulk_delete = false;
}
self
}
pub(crate) fn browser_state(&self, cx: &Cx) -> (TableSignals, TableState) {
let prefix = self.wiring.prefix.as_deref();
let signals = TableSignals::new(cx, prefix);
let state = self.normalize_state(&signals.state(prefix));
(signals, state)
}
pub(crate) fn actions_for(&self, record: &M) -> RowActions {
self.wiring
.row_policy
.as_ref()
.map_or(RowActions::ALL, |policy| policy(record))
}
pub(super) fn shows(&self, col: &BoxColumn<M>) -> bool {
self.wiring.choice.shows(col.name(), col.toggle())
}
pub(super) fn visible_columns(&self) -> impl Iterator<Item = &BoxColumn<M>> {
self.declared_columns().iter().filter(|col| self.shows(col))
}
pub(super) fn columns_url(&self) -> Option<&str> {
self.wiring.columns_url.as_deref()
}
pub(super) fn return_to(&self) -> Option<&str> {
self.wiring.return_to.as_deref()
}
pub(super) fn delete_prefix(&self) -> Option<&str> {
self.wiring.delete_prefix.as_deref()
}
pub(super) fn edit_prefix(&self) -> Option<&str> {
self.wiring.edit_prefix.as_deref()
}
pub(super) fn view_prefix(&self) -> Option<&str> {
self.wiring.view_prefix.as_deref()
}
pub(super) fn actions_prefix(&self) -> Option<&str> {
self.wiring.actions_prefix.as_deref()
}
pub(super) fn row_custom_actions(&self) -> impl Iterator<Item = &TableAction<M>> {
self.wiring
.custom_actions
.iter()
.filter(|a| a.row && self.wiring.actions_prefix.is_some())
}
pub(super) fn bulk_custom_actions(&self) -> impl Iterator<Item = &TableAction<M>> {
self.wiring
.custom_actions
.iter()
.filter(|a| a.bulk && self.wiring.actions_prefix.is_some())
}
pub(super) fn bulk_delete_enabled(&self) -> bool {
self.wiring.bulk_delete && self.wiring.delete_prefix.is_some()
}
}
#[cfg(test)]
impl<M> Table<M> {
pub(crate) fn wired(self) -> WiredTable<M> {
WiredTable::new(Arc::new(self))
}
}