1use std::{ops::Deref, sync::Arc};
4
5use topcoat::{context::Cx, icon::IconData};
6
7use super::TableSignals;
8use crate::{
9 Color,
10 resource::Trashed,
11 schema::Schema,
12 table::{BoxColumn, ColumnChoice, Table, TableState},
13};
14
15pub(crate) type RowPolicy<M> = Arc<dyn Fn(&M) -> RowActions + Send + Sync>;
17
18pub(crate) type DisabledReason<M> = Arc<dyn Fn(&M) -> Option<String> + Send + Sync>;
20
21pub(crate) type RecordFace<M> = Arc<dyn Fn(&M) -> (String, Option<IconData>) + Send + Sync>;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub(crate) struct RowActions {
29 pub view: bool,
31 pub edit: bool,
33 pub delete: bool,
35}
36
37impl RowActions {
38 pub const ALL: Self = Self {
40 view: true,
41 edit: true,
42 delete: true,
43 };
44}
45
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
48pub(crate) struct TableChrome {
49 pub(crate) delete: bool,
51 pub(crate) edit: bool,
53 pub(crate) view: bool,
55 pub(crate) actions: bool,
57}
58
59pub(crate) struct TableAction<M> {
61 pub(crate) name: &'static str,
62 pub(crate) label: String,
63 pub(crate) row: bool,
64 pub(crate) bulk: bool,
65 pub(crate) confirm: bool,
67 pub(crate) prompt: String,
69 pub(crate) bulk_prompt: String,
71 pub(crate) icon: Option<IconData>,
73 pub(crate) record_face: RecordFace<M>,
75 pub(crate) tooltip: Option<String>,
77 pub(crate) color: Color,
79 pub(crate) input: Option<fn() -> Schema>,
81 pub(crate) trashed: Trashed,
83 pub(crate) allowed: Arc<dyn Fn(&M) -> bool + Send + Sync>,
84 pub(crate) disabled: DisabledReason<M>,
86}
87
88pub(crate) struct CellEdits<M> {
91 pub(crate) changes: Arc<dyn Fn(&str) -> bool + Send + Sync>,
94 pub(crate) version: fn(&M) -> Option<u64>,
96}
97
98pub(crate) struct Wiring<M> {
104 row_policy: Option<RowPolicy<M>>,
105 delete_prefix: Option<String>,
106 edit_prefix: Option<String>,
107 cell_edits: Option<CellEdits<M>>,
109 view_prefix: Option<String>,
110 bulk_delete: bool,
111 custom_actions: Vec<TableAction<M>>,
113 actions_prefix: Option<String>,
114 return_to: Option<String>,
116 select_matching: bool,
119 prefix: Option<String>,
121 choice: ColumnChoice,
123 columns_url: Option<String>,
125}
126
127impl<M> Default for Wiring<M> {
128 fn default() -> Self {
129 Self {
130 row_policy: None,
131 delete_prefix: None,
132 edit_prefix: None,
133 cell_edits: None,
134 view_prefix: None,
135 bulk_delete: false,
136 custom_actions: Vec::new(),
137 actions_prefix: None,
138 return_to: None,
139 select_matching: false,
140 prefix: None,
141 choice: ColumnChoice::default(),
142 columns_url: None,
143 }
144 }
145}
146
147pub struct WiredTable<M> {
153 table: Arc<Table<M>>,
154 pub(super) wiring: Wiring<M>,
155}
156
157impl<M> Deref for WiredTable<M> {
158 type Target = Table<M>;
159
160 fn deref(&self) -> &Table<M> {
161 &self.table
162 }
163}
164
165impl<M> std::fmt::Debug for WiredTable<M> {
166 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 let wiring = &self.wiring;
168 f.debug_struct("WiredTable")
169 .field("table", &self.table)
170 .field("row_policy", &wiring.row_policy.is_some())
171 .field("delete_prefix", &wiring.delete_prefix)
172 .field("edit_prefix", &wiring.edit_prefix)
173 .field("cell_edits", &wiring.cell_edits.is_some())
174 .field("view_prefix", &wiring.view_prefix)
175 .field("bulk_delete", &wiring.bulk_delete)
176 .field(
177 "custom_actions",
178 &wiring
179 .custom_actions
180 .iter()
181 .map(|a| a.name)
182 .collect::<Vec<_>>(),
183 )
184 .field("return_to", &wiring.return_to)
185 .field("prefix", &wiring.prefix)
186 .field("choice", &wiring.choice)
187 .field("columns_url", &wiring.columns_url)
188 .finish()
189 }
190}
191
192impl<M> WiredTable<M> {
193 pub(crate) fn new(table: Arc<Table<M>>) -> Self {
195 Self {
196 table,
197 wiring: Wiring::default(),
198 }
199 }
200
201 pub(crate) fn row_actions(
203 mut self,
204 policy: impl Fn(&M) -> RowActions + Send + Sync + 'static,
205 ) -> Self {
206 self.wiring.row_policy = Some(Arc::new(policy));
207 self
208 }
209
210 pub(crate) fn with_delete(mut self, prefix: String) -> Self {
212 if self.is_addressable() {
213 self.wiring.delete_prefix = Some(prefix);
214 }
215 self
216 }
217
218 pub(crate) fn with_edit(mut self, prefix: String) -> Self {
220 if self.is_addressable() {
221 self.wiring.edit_prefix = Some(prefix);
222 }
223 self
224 }
225
226 pub(crate) fn editing_cells(
230 mut self,
231 changes: impl Fn(&str) -> bool + Send + Sync + 'static,
232 version: fn(&M) -> Option<u64>,
233 ) -> Self {
234 if self.is_addressable() {
235 self.wiring.cell_edits = Some(CellEdits {
236 changes: Arc::new(changes),
237 version,
238 });
239 }
240 self
241 }
242
243 pub(crate) fn with_view(mut self, prefix: String) -> Self {
245 if self.is_addressable() {
246 self.wiring.view_prefix = Some(prefix);
247 }
248 self
249 }
250
251 pub(crate) fn with_bulk_delete(mut self, enabled: bool) -> Self {
253 self.wiring.bulk_delete = enabled;
254 self
255 }
256
257 pub(crate) fn with_custom_actions(
259 mut self,
260 prefix: String,
261 actions: Vec<TableAction<M>>,
262 ) -> Self {
263 if self.is_addressable() {
264 self.wiring.actions_prefix = Some(prefix);
265 self.wiring.custom_actions = actions;
266 }
267 self
268 }
269
270 pub(crate) fn with_column_choice(mut self, choice: ColumnChoice, url: Option<String>) -> Self {
273 let shows_any = self
274 .declared_columns()
275 .iter()
276 .any(|col| choice.shows(col.name(), col.toggle()));
277 if shows_any {
278 self.wiring.choice = choice;
279 }
280 self.wiring.columns_url = url;
281 self
282 }
283
284 pub(crate) fn selecting_matching(mut self) -> Self {
288 self.wiring.select_matching = true;
289 self
290 }
291
292 pub(crate) fn returning_to(mut self, url: String) -> Self {
294 self.wiring.return_to = Some(url);
295 self
296 }
297
298 #[must_use]
301 pub fn prefixed(mut self, prefix: impl Into<String>) -> Self {
302 self.wiring.prefix = Some(prefix.into());
303 self
304 }
305
306 pub(crate) fn taking(mut self, shown: Trashed) -> Self {
309 let overlaps = |taken: Trashed| {
311 [false, true]
312 .into_iter()
313 .any(|trashed| shown.admits(trashed) && taken.admits(trashed))
314 };
315 self.wiring
316 .custom_actions
317 .retain(|action| overlaps(action.trashed));
318 if !overlaps(Trashed::Without) {
319 self.wiring.bulk_delete = false;
320 }
321 self
322 }
323
324 pub(crate) fn browser_state(&self, cx: &Cx) -> (TableSignals, TableState) {
327 let prefix = self.wiring.prefix.as_deref();
328 let signals = TableSignals::new(cx, prefix);
329 let state = self.normalize_state(&signals.state(prefix));
330 (signals, state)
331 }
332
333 pub(crate) fn actions_for(&self, record: &M) -> RowActions {
335 self.wiring
336 .row_policy
337 .as_ref()
338 .map_or(RowActions::ALL, |policy| policy(record))
339 }
340
341 pub(super) fn shows(&self, col: &BoxColumn<M>) -> bool {
343 self.wiring.choice.shows(col.name(), col.toggle())
344 }
345
346 pub(super) fn visible_columns(&self) -> impl Iterator<Item = &BoxColumn<M>> {
348 self.declared_columns().iter().filter(|col| self.shows(col))
349 }
350
351 pub(super) fn prefix(&self) -> Option<&str> {
353 self.wiring.prefix.as_deref()
354 }
355
356 pub(super) fn columns_url(&self) -> Option<&str> {
358 self.wiring.columns_url.as_deref()
359 }
360
361 pub(super) fn return_to(&self) -> Option<&str> {
362 self.wiring.return_to.as_deref()
363 }
364
365 pub(super) fn select_matching(&self) -> bool {
366 self.wiring.select_matching && self.wiring.prefix.is_none()
367 }
368
369 pub(super) fn delete_prefix(&self) -> Option<&str> {
370 self.wiring.delete_prefix.as_deref()
371 }
372
373 pub(super) fn edit_prefix(&self) -> Option<&str> {
374 self.wiring.edit_prefix.as_deref()
375 }
376
377 pub(super) fn cell_edits(&self) -> Option<&CellEdits<M>> {
379 self.wiring
380 .cell_edits
381 .as_ref()
382 .filter(|_| self.wiring.edit_prefix.is_some())
383 }
384
385 pub(super) fn view_prefix(&self) -> Option<&str> {
386 self.wiring.view_prefix.as_deref()
387 }
388
389 pub(super) fn actions_prefix(&self) -> Option<&str> {
390 self.wiring.actions_prefix.as_deref()
391 }
392
393 pub(super) fn row_custom_actions(&self) -> impl Iterator<Item = &TableAction<M>> {
394 self.wiring
395 .custom_actions
396 .iter()
397 .filter(|a| a.row && self.wiring.actions_prefix.is_some())
398 }
399
400 pub(super) fn bulk_custom_actions(&self) -> impl Iterator<Item = &TableAction<M>> {
401 self.wiring
402 .custom_actions
403 .iter()
404 .filter(|a| a.bulk && self.wiring.actions_prefix.is_some())
405 }
406
407 pub(super) fn bulk_delete_enabled(&self) -> bool {
408 self.wiring.bulk_delete && self.wiring.delete_prefix.is_some()
409 }
410}
411
412#[cfg(test)]
413impl<M> Table<M> {
414 pub(crate) fn wired(self) -> WiredTable<M> {
416 WiredTable::new(Arc::new(self))
417 }
418}