tablo_core/table/column/
repeater.rs1use std::collections::HashMap;
4
5use toasty::stmt::List;
6use topcoat::{context::Cx, view::*};
7
8use super::{Column, ColumnWidth};
9use crate::{
10 Lens,
11 schema::{Binding, FieldResolver, RepeaterItem, read_only, value_cell},
12};
13
14pub struct RepeaterColumn<M, T> {
30 lens: Lens<M, Vec<T>, List<T>>,
31 binding: Binding,
32 label: Option<String>,
34 width: ColumnWidth,
35}
36
37impl<M, T> RepeaterColumn<M, T>
38where
39 M: toasty::schema::Model,
40 T: RepeaterItem,
41{
42 pub fn new(lens: Lens<M, Vec<T>, List<T>>) -> Self {
44 let binding = Binding::of(lens.path());
45 Self {
46 lens,
47 binding,
48 label: None,
49 width: ColumnWidth::Wide,
50 }
51 }
52
53 pub fn label(mut self, label: impl Into<String>) -> Self {
55 self.label = Some(label.into());
56 self
57 }
58
59 pub fn width(mut self, width: ColumnWidth) -> Self {
61 self.width = width;
62 self
63 }
64
65 fn spelled(&self, row: &M) -> Vec<HashMap<String, String>> {
67 self.lens
68 .read(row)
69 .iter()
70 .map(|item| {
71 let mut values = HashMap::new();
72 item.write(&mut values);
73 values
74 })
75 .collect()
76 }
77}
78
79impl<M, T> Column<M> for RepeaterColumn<M, T>
80where
81 M: toasty::schema::Model + Send + Sync + 'static,
82 T: RepeaterItem,
83{
84 fn name(&self) -> &str {
85 self.binding.name()
86 }
87
88 fn label(&self) -> &str {
89 self.label.as_deref().unwrap_or(self.binding.label())
90 }
91
92 fn text(&self, _cx: &Cx, row: &M) -> String {
93 let schema = T::schema();
94 self.spelled(row)
95 .iter()
96 .map(|values| {
97 schema
98 .fields()
99 .filter_map(|field| {
100 let value = values.get(field.name()).map_or("", String::as_str);
101 let read = field.read(value)?;
102 Some(format!("{}: {read}", field.label_str()))
103 })
104 .collect::<Vec<_>>()
105 .join(", ")
106 })
107 .collect::<Vec<_>>()
108 .join("; ")
109 }
110
111 fn entry<'a>(&self, cx: &'a Cx, row: &M) -> BoxView<'a> {
112 let label = Column::label(self);
113 let items = self.spelled(row);
114 if items.is_empty() {
115 return read_only(cx, label, value_cell(cx, ""));
116 }
117 let schema = T::schema();
118 let items: Vec<BoxView<'a>> = items
119 .iter()
120 .map(|values| {
121 let fields: Vec<BoxView<'a>> = schema
122 .fields()
123 .map(|field| {
124 field.display(cx, values.get(field.name()).map_or("", String::as_str))
125 })
126 .collect();
127 view! {
128 cx =>
129 <li class="flex flex-col gap-3 rounded-lg border border-border p-4">
130 for field in fields {
131 (field)
132 }
133 </li>
134 }
135 .boxed()
136 })
137 .collect();
138 read_only(
139 cx,
140 label,
141 view! {
142 cx =>
143 <ol class="flex flex-col gap-3 whitespace-normal">
144 for item in items {
145 (item)
146 }
147 </ol>
148 }
149 .boxed(),
150 )
151 }
152
153 fn column_width(&self) -> ColumnWidth {
154 self.width
155 }
156
157 fn misdeclared(&self) -> Option<crate::DeclarationErrorKind> {
158 self.binding.misdeclared()
159 }
160
161 fn bind(&self, resolver: &FieldResolver) {
162 self.binding.bind(resolver);
163 }
164}
165
166impl<M, T> Clone for RepeaterColumn<M, T> {
167 fn clone(&self) -> Self {
168 Self {
169 lens: self.lens.clone(),
170 binding: self.binding.clone(),
171 label: self.label.clone(),
172 width: self.width,
173 }
174 }
175}
176
177impl<M, T> std::fmt::Debug for RepeaterColumn<M, T> {
178 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
179 f.debug_struct("RepeaterColumn")
180 .field("name", &self.binding.name())
181 .field(
182 "label",
183 &self.label.as_deref().unwrap_or(self.binding.label()),
184 )
185 .field("item", &std::any::type_name::<T>())
186 .finish_non_exhaustive()
187 }
188}