1use tablo_ui::{table, table_body};
4use toasty::stmt::{List, Query};
5use topcoat::{Result, context::Cx, view::*};
6
7use super::{
8 super::WiredTable,
9 Frame, TABLE_CARD_CLASS,
10 filterbar::FilterViews,
11 rows::{RowChrome, RowView, render_rows, selectable_keys},
12 table_dom_id,
13 widths::ColumnWidths,
14};
15use crate::table::{
16 page::TablePage,
17 state::{TableSignals, TableState},
18};
19
20impl<M> WiredTable<M> {
21 pub async fn render<'a>(&self, cx: &'a Cx, query: Query<List<M>>) -> Result<BoxView<'a>>
38 where
39 M: toasty::schema::Model + Send + Sync + 'static,
40 {
41 let path = topcoat::router::request::uri(cx).path().to_string();
42 let (signals, state) = self.browser_state(cx);
43 let page = TablePage::load(cx, self, query, &state).await?;
44 self.render_page(cx, page, &state, &path, &signals).await
45 }
46
47 pub(crate) async fn render_page<'a>(
49 &self,
50 cx: &'a Cx,
51 page: TablePage<M>,
52 state: &TableState,
53 path: &str,
54 signals: &TableSignals,
55 ) -> Result<BoxView<'a>>
56 where
57 M: toasty::schema::Model + Send + Sync + 'static,
58 {
59 let errors = self.declaration_errors();
60 if !errors.is_empty() {
61 return Err(crate::error::misdeclared(&errors));
62 }
63 let frame = self.frame();
64 let group_key = self.effective_group_key(state);
65 let rows = self.row_views(cx, &frame, &page, group_key.as_ref());
66 let loaded = Loaded {
67 rows,
68 next_cursor: page.next_cursor,
69 prev_cursor: page.prev_cursor,
70 filters: self.filter_views(cx, state),
71 };
72 frame.render_page(cx, loaded, state, path, signals).await
73 }
74}
75
76struct Loaded<'a> {
78 rows: Vec<RowView<'a>>,
79 next_cursor: Option<String>,
80 prev_cursor: Option<String>,
81 filters: FilterViews<'a>,
82}
83
84impl Frame<'_> {
85 async fn render_page<'a>(
87 &self,
88 cx: &'a Cx,
89 loaded: Loaded<'a>,
90 state: &TableState,
91 path: &str,
92 signals: &TableSignals,
93 ) -> Result<BoxView<'a>> {
94 let Loaded {
95 rows: row_data,
96 next_cursor,
97 prev_cursor,
98 mut filters,
99 } = loaded;
100 let signals = &signals
101 .clone()
102 .with_inputs(cx, self.inputs.iter().map(|action| action.name));
103 let with_actions = self.with_actions();
104 let with_bulk = self.bulk_enabled();
105 let head = self
106 .render_thead(
107 cx,
108 state,
109 path,
110 with_actions,
111 with_bulk,
112 Some((signals, selectable_keys(&row_data))),
113 )
114 .await?;
115 let filter_warning = self.render_filter_warning(cx, state, path, signals, &filters);
116 let controls = std::mem::take(&mut filters.controls);
117 let toolbar = self
118 .render_toolbar(cx, state, path, signals, controls)
119 .await?;
120 let pager = self
121 .render_pager(
122 cx,
123 state,
124 path,
125 next_cursor.as_deref(),
126 prev_cursor.as_deref(),
127 signals,
128 )
129 .await?;
130 let write_form = self.render_write_form(cx, state, signals);
131 let input_dialogs = self.render_input_dialogs(cx, state, signals);
132
133 let ColumnWidths {
134 cells: cell_widths,
135 actions_min,
136 table_min_width,
137 ..
138 } = self.column_widths();
139 let mut pager_views: Vec<BoxView<'_>> = Vec::new();
140 let body: BoxView<'_> = if row_data.is_empty() {
141 let empty_cell = self
142 .render_empty_cell(cx, state, path, with_actions, with_bulk, signals)
143 .await?;
144 view! {
145 cx =>
146 table(
147 attrs: attributes! { class="table-fixed" style=(table_min_width.as_deref()) },
148 (head)
149 (empty_cell)
150 )
151 }
152 .boxed()
153 } else {
154 pager_views = pager;
155 let chrome = RowChrome {
156 with_bulk,
157 with_actions,
158 header_colspan: self.columns.len()
159 + usize::from(with_bulk)
160 + usize::from(with_actions),
161 cell_widths,
162 actions_min,
163 signals: signals.clone(),
164 form: table_dom_id(state, "writes"),
165 };
166 let rows = render_rows(cx, row_data, &chrome);
167 view! {
168 cx =>
169 table(
170 attrs: attributes! { class="table-fixed" style=(table_min_width.as_deref()) },
171 (head)
172 table_body(
173 #[key(row.key.as_str())]
174 for row in rows {
175 (row.view)
176 }
177 )
178 )
179 }
180 .boxed()
181 };
182
183 let content = view! {
184 cx =>
185 (toolbar)
186 if let Some(warning) = filter_warning {
187 (warning)
188 }
189 (body)
190 for p in pager_views {
191 (p)
192 }
193 if let Some(form) = write_form {
194 (form)
195 }
196 for dialog in input_dialogs {
197 (dialog)
198 }
199 };
200 Ok(table_frame(cx, false, content.boxed()))
201 }
202}
203
204#[cfg(test)]
205impl<M> crate::table::Table<M>
206where
207 M: toasty::schema::Model + Send + Sync + 'static,
208{
209 pub(crate) async fn render<'a>(&self, cx: &'a Cx, page: TablePage<M>) -> Result<BoxView<'a>> {
211 self.clone()
212 .wired()
213 .render_loaded_with(cx, page, &TableState::default(), "")
214 .await
215 }
216
217 pub(crate) async fn render_with_state<'a>(
219 &self,
220 cx: &'a Cx,
221 page: TablePage<M>,
222 state: &TableState,
223 path: &str,
224 ) -> Result<BoxView<'a>> {
225 self.clone()
226 .wired()
227 .render_loaded_with(cx, page, state, path)
228 .await
229 }
230}
231
232#[cfg(test)]
233impl<M> WiredTable<M>
234where
235 M: toasty::schema::Model + Send + Sync + 'static,
236{
237 pub(crate) async fn render_loaded<'a>(
239 self,
240 cx: &'a Cx,
241 page: TablePage<M>,
242 ) -> Result<BoxView<'a>> {
243 self.render_loaded_with(cx, page, &TableState::default(), "")
244 .await
245 }
246
247 pub(crate) async fn render_loaded_with<'a>(
249 self,
250 cx: &'a Cx,
251 page: TablePage<M>,
252 state: &TableState,
253 path: &str,
254 ) -> Result<BoxView<'a>> {
255 let errors = self.declaration_errors();
256 if !errors.is_empty() {
257 return Err(crate::error::misdeclared(&errors));
258 }
259 let (state, path) = (state.clone(), path.to_string());
260 Ok(crate::topcoat_compat::async_page(async move {
261 let signals = TableSignals::new(cx, state.prefix.as_deref());
262 let state = self.normalize_state(&state);
263 self.render_page(cx, page, &state, &path, &signals).await
264 }))
265 }
266}
267
268pub(super) fn table_frame<'a>(cx: &'a Cx, busy: bool, content: BoxView<'a>) -> BoxView<'a> {
270 let busy = busy.then_some("true");
271 view! { cx => <div class=(TABLE_CARD_CLASS) aria-busy=(busy)>(content)</div> }.boxed()
272}
273
274#[cfg(test)]
275pub(super) mod tests;