tablo_core/table/column/
embedded.rs1use std::{
4 collections::HashMap,
5 sync::{Arc, OnceLock},
6};
7
8use topcoat::{context::Cx, view::*};
9
10use super::{Column, ColumnWidth};
11use crate::{
12 Lens,
13 schema::{Binding, EmbeddedForm, FieldResolver, Schema},
14};
15
16pub struct EmbeddedColumn<M, T> {
33 lens: Lens<M, T>,
34 binding: Binding,
35 label: Option<String>,
37 schema: Arc<OnceLock<Schema>>,
39 width: ColumnWidth,
40}
41
42impl<M, T> EmbeddedColumn<M, T>
43where
44 M: toasty::schema::Model,
45 T: EmbeddedForm,
46{
47 pub fn new(lens: Lens<M, T>) -> Self {
49 let binding = Binding::of(lens.path());
50 Self {
51 lens,
52 binding,
53 label: None,
54 schema: Arc::default(),
55 width: ColumnWidth::Wide,
56 }
57 }
58
59 pub fn label(mut self, label: impl Into<String>) -> Self {
61 self.label = Some(label.into());
62 self
63 }
64
65 pub fn width(mut self, width: ColumnWidth) -> Self {
67 self.width = width;
68 self
69 }
70
71 fn spelled(&self, row: &M) -> Option<(&Schema, HashMap<String, String>)> {
73 let schema = self.schema.get()?;
74 let mut values = HashMap::new();
75 self.lens
76 .read(row)
77 .write_node(schema.embedded_root(), &mut values);
78 Some((schema, values))
79 }
80}
81
82impl<M, T> Column<M> for EmbeddedColumn<M, T>
83where
84 M: toasty::schema::Model + Send + Sync + 'static,
85 T: EmbeddedForm + Send + Sync + 'static,
86{
87 fn name(&self) -> &str {
88 self.binding.name()
89 }
90
91 fn label(&self) -> &str {
92 self.label.as_deref().unwrap_or(self.binding.label())
93 }
94
95 fn text(&self, _cx: &Cx, row: &M) -> String {
96 let Some((schema, values)) = self.spelled(row) else {
97 return String::new();
98 };
99 schema
100 .embedded_root()
101 .shown(&schema.fields, &values)
102 .into_iter()
103 .filter_map(|index| {
104 let field = &schema.fields[index];
105 let value = values.get(field.name()).map_or("", String::as_str);
106 let read = field.read(value)?;
107 Some(format!("{}: {read}", field.label_str()))
108 })
109 .collect::<Vec<_>>()
110 .join(", ")
111 }
112
113 fn entry<'a>(&self, cx: &'a Cx, row: &M) -> BoxView<'a> {
114 match self.spelled(row) {
115 Some((schema, values)) => schema.embedded_root().display(cx, &schema.fields, &values),
116 None => ().boxed(),
117 }
118 }
119
120 fn column_width(&self) -> ColumnWidth {
121 self.width
122 }
123
124 fn misdeclared(&self) -> Option<crate::DeclarationErrorKind> {
125 if let Some(error) = self.binding.misdeclared() {
126 return Some(error);
127 }
128 match self.schema.get() {
129 Some(schema) => schema.declaration_errors().into_iter().next(),
130 None => Some(crate::DeclarationErrorKind::Unbound {
131 item: std::any::type_name::<T>(),
132 }),
133 }
134 }
135
136 fn bind(&self, resolver: &FieldResolver) {
137 self.binding.bind(resolver);
138 self.schema
139 .get_or_init(|| T::build_schema(resolver, self.lens.path().clone()));
140 }
141}
142
143impl<M, T> Clone for EmbeddedColumn<M, T> {
144 fn clone(&self) -> Self {
145 Self {
146 lens: self.lens.clone(),
147 binding: self.binding.clone(),
148 label: self.label.clone(),
149 schema: Arc::clone(&self.schema),
150 width: self.width,
151 }
152 }
153}
154
155impl<M, T> std::fmt::Debug for EmbeddedColumn<M, T> {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 f.debug_struct("EmbeddedColumn")
158 .field("name", &self.binding.name())
159 .field(
160 "label",
161 &self.label.as_deref().unwrap_or(self.binding.label()),
162 )
163 .field("value", &std::any::type_name::<T>())
164 .field("bound", &self.schema.get().is_some())
165 .finish_non_exhaustive()
166 }
167}