tablo_core/resource/
soft_delete.rs1use toasty::stmt::{Expr, Path};
5use topcoat::{context::Cx, view::BoxView};
6
7use crate::{
8 DeclarationErrorKind, Lens,
9 table::filter::{Filter, FilterInput, FilterValue},
10 toasty_compat::model::{self, ModelPath},
11};
12
13#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
19pub enum Trashed {
20 #[default]
22 Without,
23 With,
25 Only,
27}
28
29impl Trashed {
30 pub fn admits(self, trashed: bool) -> bool {
32 match self {
33 Self::Without => !trashed,
34 Self::With => true,
35 Self::Only => trashed,
36 }
37 }
38
39 pub(crate) fn from_filter(value: Option<&FilterValue>) -> Option<Self> {
42 match value.map(FilterValue::single) {
43 None => Some(Self::Without),
44 Some(Some(WITH)) => Some(Self::With),
45 Some(Some(ONLY)) => Some(Self::Only),
46 Some(_) => None,
47 }
48 }
49}
50
51pub(crate) struct SoftDelete<M> {
53 path: Path<M, Option<jiff::Timestamp>>,
54 read: fn(&M) -> &Option<jiff::Timestamp>,
56 field: Result<(usize, String), DeclarationErrorKind>,
58}
59
60impl<M: toasty::schema::Model + Send + Sync + 'static> SoftDelete<M> {
61 pub(crate) fn new(lens: Lens<M, Option<jiff::Timestamp>>) -> Self {
62 let field =
63 model::field::<M>(&ModelPath::of(lens.path())).map(|field| (field.index, field.name));
64 Self {
65 path: lens.path().clone(),
66 read: lens.reader(),
67 field,
68 }
69 }
70}
71
72impl<M> SoftDelete<M> {
73 pub(crate) fn filter(&self, trashed: Trashed) -> Option<Expr<bool>> {
75 match trashed {
76 Trashed::Without => Some(self.path.clone().is_none()),
77 Trashed::With => None,
78 Trashed::Only => Some(self.path.clone().is_some()),
79 }
80 }
81
82 pub(crate) fn is_trashed(&self, record: &M) -> bool {
84 (self.read)(record).is_some()
85 }
86
87 pub(crate) fn field(&self) -> Result<&(usize, String), &DeclarationErrorKind> {
89 self.field.as_ref()
90 }
91}
92
93pub(crate) const TRASH_FILTER: &str = "trashed";
95const WITH: &str = "with";
96const ONLY: &str = "only";
97
98pub(crate) struct TrashFilter;
102
103impl<M> Filter<M> for TrashFilter {
104 fn name(&self) -> &str {
105 TRASH_FILTER
106 }
107
108 fn label(&self) -> crate::Text {
109 crate::Text::message("tablo-trash")
110 }
111
112 fn reads(&self) -> Option<crate::lens::Reads<M>> {
115 Some(crate::lens::Reads::nothing())
116 }
117
118 fn to_expr(&self, _cx: &Cx, _value: &FilterValue) -> Option<Expr<bool>> {
119 None
120 }
121
122 fn is_noop_value(&self, value: &FilterValue) -> bool {
123 Trashed::from_filter(Some(value)).is_some()
124 }
125
126 fn control<'a>(&self, cx: &'a Cx, input: FilterInput) -> BoxView<'a> {
127 input.select_led_by(
128 cx,
129 &crate::t!(cx, "tablo-trash-without"),
130 vec![
131 (WITH.to_string(), crate::t!(cx, "tablo-trash-with")),
132 (ONLY.to_string(), crate::t!(cx, "tablo-trash-only")),
133 ],
134 )
135 }
136}