1use std::{ops::Deref, sync::Arc};
4
5use topcoat::context::Cx;
6
7use super::TableSignals;
8use crate::{
9 resource::Trashed,
10 schema::Schema,
11 table::{BoxColumn, ColumnChoice, Table, TableState},
12};
13
14pub(crate) type RowPolicy<M> = Arc<dyn Fn(&M) -> RowActions + Send + Sync>;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub(crate) struct RowActions {
22 pub view: bool,
24 pub edit: bool,
26 pub delete: bool,
28}
29
30impl RowActions {
31 pub const ALL: Self = Self {
33 view: true,
34 edit: true,
35 delete: true,
36 };
37}
38
39#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
41pub(crate) struct TableChrome {
42 pub(crate) delete: bool,
44 pub(crate) edit: bool,
46 pub(crate) view: bool,
48 pub(crate) actions: bool,
50}
51
52pub(crate) struct TableAction<M> {
54 pub(crate) name: &'static str,
55 pub(crate) label: String,
56 pub(crate) row: bool,
57 pub(crate) bulk: bool,
58 pub(crate) confirm: bool,
60 pub(crate) input: Option<fn() -> Schema>,
62 pub(crate) trashed: Trashed,
64 pub(crate) allowed: Arc<dyn Fn(&M) -> bool + Send + Sync>,
65}
66
67pub(crate) struct Wiring<M> {
73 row_policy: Option<RowPolicy<M>>,
74 delete_prefix: Option<String>,
75 edit_prefix: Option<String>,
76 view_prefix: Option<String>,
77 bulk_delete: bool,
78 custom_actions: Vec<TableAction<M>>,
80 actions_prefix: Option<String>,
81 return_to: Option<String>,
83 prefix: Option<String>,
85 choice: ColumnChoice,
87 columns_url: Option<String>,
89}
90
91impl<M> Default for Wiring<M> {
92 fn default() -> Self {
93 Self {
94 row_policy: None,
95 delete_prefix: None,
96 edit_prefix: None,
97 view_prefix: None,
98 bulk_delete: false,
99 custom_actions: Vec::new(),
100 actions_prefix: None,
101 return_to: None,
102 prefix: None,
103 choice: ColumnChoice::default(),
104 columns_url: None,
105 }
106 }
107}
108
109pub struct WiredTable<M> {
115 table: Arc<Table<M>>,
116 pub(super) wiring: Wiring<M>,
117}
118
119impl<M> Deref for WiredTable<M> {
120 type Target = Table<M>;
121
122 fn deref(&self) -> &Table<M> {
123 &self.table
124 }
125}
126
127impl<M> std::fmt::Debug for WiredTable<M> {
128 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129 let wiring = &self.wiring;
130 f.debug_struct("WiredTable")
131 .field("table", &self.table)
132 .field("row_policy", &wiring.row_policy.is_some())
133 .field("delete_prefix", &wiring.delete_prefix)
134 .field("edit_prefix", &wiring.edit_prefix)
135 .field("view_prefix", &wiring.view_prefix)
136 .field("bulk_delete", &wiring.bulk_delete)
137 .field(
138 "custom_actions",
139 &wiring
140 .custom_actions
141 .iter()
142 .map(|a| a.name)
143 .collect::<Vec<_>>(),
144 )
145 .field("return_to", &wiring.return_to)
146 .field("prefix", &wiring.prefix)
147 .field("choice", &wiring.choice)
148 .field("columns_url", &wiring.columns_url)
149 .finish()
150 }
151}
152
153impl<M> WiredTable<M> {
154 pub(crate) fn new(table: Arc<Table<M>>) -> Self {
156 Self {
157 table,
158 wiring: Wiring::default(),
159 }
160 }
161
162 pub(crate) fn row_actions(
164 mut self,
165 policy: impl Fn(&M) -> RowActions + Send + Sync + 'static,
166 ) -> Self {
167 self.wiring.row_policy = Some(Arc::new(policy));
168 self
169 }
170
171 pub(crate) fn with_delete(mut self, prefix: String) -> Self {
173 if self.is_addressable() {
174 self.wiring.delete_prefix = Some(prefix);
175 }
176 self
177 }
178
179 pub(crate) fn with_edit(mut self, prefix: String) -> Self {
181 if self.is_addressable() {
182 self.wiring.edit_prefix = Some(prefix);
183 }
184 self
185 }
186
187 pub(crate) fn with_view(mut self, prefix: String) -> Self {
189 if self.is_addressable() {
190 self.wiring.view_prefix = Some(prefix);
191 }
192 self
193 }
194
195 pub(crate) fn with_bulk_delete(mut self, enabled: bool) -> Self {
197 self.wiring.bulk_delete = enabled;
198 self
199 }
200
201 pub(crate) fn with_custom_actions(
203 mut self,
204 prefix: String,
205 actions: Vec<TableAction<M>>,
206 ) -> Self {
207 if self.is_addressable() {
208 self.wiring.actions_prefix = Some(prefix);
209 self.wiring.custom_actions = actions;
210 }
211 self
212 }
213
214 pub(crate) fn with_column_choice(mut self, choice: ColumnChoice, url: Option<String>) -> Self {
217 let shows_any = self
218 .declared_columns()
219 .iter()
220 .any(|col| choice.shows(col.name(), col.toggle()));
221 if shows_any {
222 self.wiring.choice = choice;
223 }
224 self.wiring.columns_url = url;
225 self
226 }
227
228 pub(crate) fn returning_to(mut self, url: String) -> Self {
230 self.wiring.return_to = Some(url);
231 self
232 }
233
234 #[must_use]
237 pub fn prefixed(mut self, prefix: impl Into<String>) -> Self {
238 self.wiring.prefix = Some(prefix.into());
239 self
240 }
241
242 pub(crate) fn taking(mut self, shown: Trashed) -> Self {
245 let overlaps = |taken: Trashed| {
247 [false, true]
248 .into_iter()
249 .any(|trashed| shown.admits(trashed) && taken.admits(trashed))
250 };
251 self.wiring
252 .custom_actions
253 .retain(|action| overlaps(action.trashed));
254 if !overlaps(Trashed::Without) {
255 self.wiring.bulk_delete = false;
256 }
257 self
258 }
259
260 pub(crate) fn browser_state(&self, cx: &Cx) -> (TableSignals, TableState) {
263 let prefix = self.wiring.prefix.as_deref();
264 let signals = TableSignals::new(cx, prefix);
265 let state = self.normalize_state(&signals.state(prefix));
266 (signals, state)
267 }
268
269 pub(crate) fn actions_for(&self, record: &M) -> RowActions {
271 self.wiring
272 .row_policy
273 .as_ref()
274 .map_or(RowActions::ALL, |policy| policy(record))
275 }
276
277 pub(super) fn shows(&self, col: &BoxColumn<M>) -> bool {
279 self.wiring.choice.shows(col.name(), col.toggle())
280 }
281
282 pub(super) fn visible_columns(&self) -> impl Iterator<Item = &BoxColumn<M>> {
284 self.declared_columns().iter().filter(|col| self.shows(col))
285 }
286
287 pub(super) fn columns_url(&self) -> Option<&str> {
289 self.wiring.columns_url.as_deref()
290 }
291
292 pub(super) fn return_to(&self) -> Option<&str> {
293 self.wiring.return_to.as_deref()
294 }
295
296 pub(super) fn delete_prefix(&self) -> Option<&str> {
297 self.wiring.delete_prefix.as_deref()
298 }
299
300 pub(super) fn edit_prefix(&self) -> Option<&str> {
301 self.wiring.edit_prefix.as_deref()
302 }
303
304 pub(super) fn view_prefix(&self) -> Option<&str> {
305 self.wiring.view_prefix.as_deref()
306 }
307
308 pub(super) fn actions_prefix(&self) -> Option<&str> {
309 self.wiring.actions_prefix.as_deref()
310 }
311
312 pub(super) fn row_custom_actions(&self) -> impl Iterator<Item = &TableAction<M>> {
313 self.wiring
314 .custom_actions
315 .iter()
316 .filter(|a| a.row && self.wiring.actions_prefix.is_some())
317 }
318
319 pub(super) fn bulk_custom_actions(&self) -> impl Iterator<Item = &TableAction<M>> {
320 self.wiring
321 .custom_actions
322 .iter()
323 .filter(|a| a.bulk && self.wiring.actions_prefix.is_some())
324 }
325
326 pub(super) fn bulk_delete_enabled(&self) -> bool {
327 self.wiring.bulk_delete && self.wiring.delete_prefix.is_some()
328 }
329}
330
331#[cfg(test)]
332impl<M> Table<M> {
333 pub(crate) fn wired(self) -> WiredTable<M> {
335 WiredTable::new(Arc::new(self))
336 }
337}