@perspective-dev/viewer 5.3.1 → 5.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +6 -6
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/ar.css +1 -0
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/extensions.d.ts +0 -5
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +3 -3
- package/dist/esm/perspective-viewer.js +5 -5
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
- package/dist/wasm/perspective-viewer.d.ts +62 -73
- package/dist/wasm/perspective-viewer.js +226 -238
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
- package/package.json +7 -7
- package/src/css/_context_menu.css +1 -1
- package/src/css/_dropdown_menu.css +1 -1
- package/src/css/_viewer_bundle.css +7 -8
- package/src/css/column-dropdown.css +0 -3
- package/src/css/column-selector.css +8 -5
- package/src/css/column-settings-panel.css +0 -225
- package/src/css/column-style.css +13 -0
- package/src/css/config-selector.css +18 -10
- package/src/css/filter-dropdown.css +5 -5
- package/src/css/form/debug.css +28 -4
- package/src/css/function-dropdown.css +5 -5
- package/src/css/panel-tab.css +23 -0
- package/src/css/plugin-selector.css +5 -5
- package/src/css/render-warning.css +1 -1
- package/src/css/status-bar.css +52 -21
- package/src/css/type-icon.css +1 -3
- package/src/css/{containers → ui/containers}/context-menu.css +22 -16
- package/src/css/ui/containers/dropdown-menu-items.css +66 -0
- package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
- package/src/css/viewer.css +32 -26
- package/src/rust/components/chat_panel.rs +1 -1
- package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
- package/src/rust/components/column_dropdown.rs +2 -2
- package/src/rust/components/column_selector/active_column.rs +15 -9
- package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
- package/src/rust/components/column_selector/config_selector.rs +2 -2
- package/src/rust/components/column_selector/filter_column.rs +8 -5
- package/src/rust/components/column_selector/inactive_column.rs +4 -2
- package/src/rust/components/column_selector/pivot_column.rs +4 -3
- package/src/rust/components/column_selector/sort_column.rs +7 -4
- package/src/rust/components/column_selector.rs +3 -3
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +15 -17
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +4 -5
- package/src/rust/components/column_settings_sidebar.rs +3 -4
- package/src/rust/components/copy_dropdown.rs +1 -1
- package/src/rust/components/datetime_column_style/custom.rs +1 -3
- package/src/rust/components/datetime_column_style/simple.rs +1 -2
- package/src/rust/components/datetime_column_style.rs +1 -2
- package/src/rust/components/{form/debug.rs → debug_panel.rs} +5 -5
- package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
- package/src/rust/components/export_dropdown.rs +1 -1
- package/src/rust/components/expression_editor.rs +1 -1
- package/src/rust/components/filter_dropdown.rs +2 -2
- package/src/rust/components/function_dropdown.rs +2 -2
- package/src/rust/components/main_panel/frame_theme.rs +3 -59
- package/src/rust/components/main_panel/reconcile.rs +9 -4
- package/src/rust/components/main_panel/render.rs +62 -11
- package/src/rust/components/main_panel/update.rs +39 -10
- package/src/rust/components/main_panel.rs +26 -2
- package/src/rust/components/mod.rs +5 -7
- package/src/rust/components/new_panel_menu.rs +97 -0
- package/src/rust/components/number_series_style.rs +2 -3
- package/src/rust/components/panel_menu.rs +61 -87
- package/src/rust/components/panel_tab.rs +100 -43
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/render_warning.rs +2 -2
- package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
- package/src/rust/components/settings_panel.rs +2 -2
- package/src/rust/components/status_bar.rs +113 -19
- package/src/rust/components/status_indicator.rs +5 -14
- package/src/rust/components/string_column_style.rs +1 -2
- package/src/rust/components/{style/mod.rs → style.rs} +32 -15
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
- package/src/rust/components/viewer/msg.rs +43 -30
- package/src/rust/components/viewer/panels.rs +50 -19
- package/src/rust/components/viewer/render.rs +15 -13
- package/src/rust/components/viewer/settings.rs +137 -68
- package/src/rust/components/viewer/snapshots.rs +5 -10
- package/src/rust/components/viewer/wiring.rs +1 -21
- package/src/rust/components/viewer.rs +15 -10
- package/src/rust/components/window_editor.rs +8 -9
- package/src/rust/config/column_config_schema.rs +2 -27
- package/src/rust/config/mod.rs +0 -2
- package/src/rust/config/workspace_config.rs +2 -1
- package/src/rust/custom_elements/debug_plugin.rs +1 -10
- package/src/rust/custom_elements/mod.rs +0 -3
- package/src/rust/custom_elements/viewer.rs +0 -18
- package/src/rust/js/plugin.rs +0 -86
- package/src/rust/lib.rs +12 -15
- package/src/rust/presentation/column_locator.rs +3 -7
- package/src/rust/presentation.rs +68 -38
- package/src/rust/queries/hosted_tables.rs +38 -0
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +1 -1
- package/src/rust/renderer/activate.rs +1 -3
- package/src/rust/renderer/dispatch.rs +83 -22
- package/src/rust/renderer/limits.rs +24 -5
- package/src/rust/renderer/plugin_config.rs +1 -0
- package/src/rust/renderer/props.rs +2 -1
- package/src/rust/renderer.rs +14 -27
- package/src/rust/session/props.rs +4 -3
- package/src/rust/session/view_subscription.rs +19 -6
- package/src/rust/session.rs +38 -70
- package/src/rust/tasks/create_panel.rs +1 -1
- package/src/rust/tasks/dismiss_render_warning.rs +1 -1
- package/src/rust/tasks/eject.rs +1 -0
- package/src/rust/tasks/pipeline.rs +0 -60
- package/src/rust/tasks/presize_panels.rs +24 -1
- package/src/rust/tasks/restore_and_render.rs +0 -17
- package/src/rust/tasks/send_column_config.rs +1 -1
- package/src/rust/tasks/send_plugin_config.rs +1 -1
- package/src/rust/tasks/set_edit_mode.rs +1 -1
- package/src/rust/{components → ui}/containers/control_group.rs +25 -3
- package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
- package/src/rust/{components → ui}/containers/mod.rs +0 -4
- package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
- package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
- package/src/rust/{components → ui}/containers/select.rs +0 -6
- package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
- package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
- package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
- package/src/rust/{components → ui}/context_menu.rs +8 -13
- package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
- package/src/rust/{components → ui}/font_loader.rs +0 -26
- package/src/rust/{components → ui}/form/color_selector.rs +4 -2
- package/src/rust/ui/form/intl_label.rs +81 -0
- package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
- package/src/rust/{components → ui}/form/mod.rs +1 -10
- package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
- package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
- package/src/rust/{components → ui}/form/number_field.rs +1 -1
- package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
- package/src/rust/{components → ui}/form/optional_field.rs +3 -1
- package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
- package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
- package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
- package/src/rust/ui/mod.rs +56 -0
- package/src/rust/ui/modal.rs +61 -0
- package/src/rust/{components → ui}/portal.rs +174 -79
- package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
- package/src/rust/utils/completion.rs +0 -6
- package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
- package/src/rust/utils/mod.rs +2 -0
- package/src/rust/workspace.rs +35 -0
- package/src/svg/boolean-type.svg +3 -2
- package/src/svg/checkbox-checked-icon.svg +4 -7
- package/src/svg/checkbox-hover.svg +3 -3
- package/src/svg/checkbox-off.svg +2 -3
- package/src/svg/checkbox-on.svg +3 -3
- package/src/svg/checkbox-unchecked-icon.svg +3 -3
- package/src/svg/datagrid-select-column.svg +8 -15
- package/src/svg/datagrid-select-region.svg +8 -15
- package/src/svg/datagrid-select-row-tree.svg +7 -12
- package/src/svg/datagrid-select-row.svg +8 -13
- package/src/svg/date-type.svg +4 -23
- package/src/svg/drawer-tab-hover.svg +4 -6
- package/src/svg/drawer-tab-invert-hover.svg +5 -5
- package/src/svg/drawer-tab-invert.svg +3 -5
- package/src/svg/drawer-tab.svg +5 -5
- package/src/svg/mega-menu-icons-candlestick.svg +5 -37
- package/src/svg/mega-menu-icons-ohlc.svg +4 -39
- package/src/svg/mega-menu-icons-sunburst.svg +4 -38
- package/src/svg/mega-menu-icons-treemap.svg +3 -43
- package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
- package/src/svg/new-panel-icon.svg +4 -0
- package/src/svg/number-type.svg +3 -3
- package/src/svg/radio-hover.svg +3 -3
- package/src/svg/radio-off.svg +3 -3
- package/src/svg/radio-on.svg +3 -3
- package/src/svg/status_ok.svg +3 -26
- package/src/svg/string-type.svg +4 -8
- package/src/svg/theme-icon.svg +5 -5
- package/src/themes/blueprint.css +0 -57
- package/src/themes/botanical.css +0 -36
- package/src/themes/defaults.css +1 -24
- package/src/themes/dracula.css +0 -58
- package/src/themes/eggplant.css +0 -58
- package/src/themes/gruvbox-dark.css +0 -65
- package/src/themes/gruvbox.css +1 -39
- package/src/themes/icons.css +1 -8
- package/src/themes/intl/ar.css +185 -0
- package/src/themes/intl/de.css +45 -35
- package/src/themes/intl/es.css +40 -30
- package/src/themes/intl/fr.css +39 -29
- package/src/themes/intl/ja.css +41 -31
- package/src/themes/intl/pt.css +37 -27
- package/src/themes/intl/zh.css +47 -37
- package/src/themes/intl.css +16 -3
- package/src/themes/ledger.css +1 -59
- package/src/themes/monokai.css +0 -51
- package/src/themes/nord.css +0 -55
- package/src/themes/phosphor.css +0 -48
- package/src/themes/pro-dark.css +0 -37
- package/src/themes/pro.css +3 -23
- package/src/themes/solarized-dark.css +0 -31
- package/src/themes/solarized.css +1 -24
- package/src/themes/vaporwave.css +0 -44
- package/src/themes/velvet.css +0 -60
- package/src/ts/extensions.ts +0 -13
- package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
- package/src/css/plugin-settings-panel.css +0 -171
- package/src/rust/components/modal.rs +0 -177
- package/src/rust/custom_elements/copy_dropdown.rs +0 -219
- package/src/rust/custom_elements/export_dropdown.rs +0 -216
- package/src/rust/custom_elements/modal.rs +0 -299
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline5.js +0 -0
- /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
- /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
- /package/src/css/{containers → ui/containers}/tabs.css +0 -0
- /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
- /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
- /package/src/css/{dom → ui/dom}/select.css +0 -0
- /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
- /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
- /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
- /package/src/rust/{components → ui}/form/number_input.rs +0 -0
|
@@ -19,6 +19,7 @@ use std::future::Future;
|
|
|
19
19
|
use std::rc::Rc;
|
|
20
20
|
|
|
21
21
|
use perspective_client::View;
|
|
22
|
+
use perspective_client::proto::ViewDimensionsResp;
|
|
22
23
|
use perspective_js::utils::{ApiResult, ResultTApiErrorExt};
|
|
23
24
|
use wasm_bindgen::prelude::*;
|
|
24
25
|
use web_sys::*;
|
|
@@ -44,25 +45,27 @@ impl Renderer {
|
|
|
44
45
|
let _ = el.class_list().toggle_with_force("multi", !is_solo);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
|
-
/// Stamp this panel's effective `theme` attribute.
|
|
48
48
|
pub fn stamp_theme(&self, plugin: Option<&JsPerspectiveViewerPlugin>) {
|
|
49
|
+
let theme = self.theme();
|
|
49
50
|
let active_plugin = self.active_plugin();
|
|
50
51
|
if let Some(plugin) = plugin.or(active_plugin.as_ref()) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
let _ = theme_elem.set_attribute("theme", &theme);
|
|
57
|
-
},
|
|
58
|
-
Some(_) => {},
|
|
59
|
-
None => {
|
|
60
|
-
let _ = theme_elem.remove_attribute("theme");
|
|
61
|
-
},
|
|
62
|
-
}
|
|
52
|
+
stamp_theme_attr(plugin.unchecked_ref::<Element>(), theme.as_deref());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if let Some(tab) = self.tab_host() {
|
|
56
|
+
stamp_theme_attr(&tab, theme.as_deref());
|
|
63
57
|
}
|
|
64
58
|
}
|
|
65
59
|
|
|
60
|
+
fn tab_host(&self) -> Option<Element> {
|
|
61
|
+
let slot = format!("tab-{}", self.slot_name()?);
|
|
62
|
+
let viewer = self.plugin_data.borrow().viewer_elem.clone();
|
|
63
|
+
let children = viewer.children();
|
|
64
|
+
(0..children.length())
|
|
65
|
+
.filter_map(|i| children.item(i))
|
|
66
|
+
.find(|el| el.get_attribute("slot").as_deref() == Some(slot.as_str()))
|
|
67
|
+
}
|
|
68
|
+
|
|
66
69
|
/// Restyle the active plugin and re-draw.
|
|
67
70
|
pub async fn restyle_all(&self) -> ApiResult<JsValue> {
|
|
68
71
|
self.render_task(|guard| async move {
|
|
@@ -78,8 +81,14 @@ impl Renderer {
|
|
|
78
81
|
self.stamp_active(&plugin);
|
|
79
82
|
plugin.restyle();
|
|
80
83
|
*self.0.captured_theme.borrow_mut() = Some(stamped_theme);
|
|
81
|
-
let
|
|
82
|
-
|
|
84
|
+
let dimensions = ctx.view.dimensions().await?;
|
|
85
|
+
let mut limits = get_row_and_col_limits(
|
|
86
|
+
&dimensions,
|
|
87
|
+
&ctx.view,
|
|
88
|
+
&meta,
|
|
89
|
+
self.is_render_warning_enabled(),
|
|
90
|
+
)
|
|
91
|
+
.await?;
|
|
83
92
|
|
|
84
93
|
limits.is_update = true;
|
|
85
94
|
plugin
|
|
@@ -215,6 +224,7 @@ impl Renderer {
|
|
|
215
224
|
};
|
|
216
225
|
|
|
217
226
|
self.stamp_active(&jsplugin);
|
|
227
|
+
self.0.presized_box.set(None);
|
|
218
228
|
jsplugin.resize().await?;
|
|
219
229
|
Ok(None)
|
|
220
230
|
},
|
|
@@ -241,7 +251,33 @@ impl Renderer {
|
|
|
241
251
|
self.stamp_active(&plugin);
|
|
242
252
|
self.clear_presize()?;
|
|
243
253
|
if plugin.capabilities().presize {
|
|
244
|
-
|
|
254
|
+
let Some(present) = plugin.presize(width, height).await? else {
|
|
255
|
+
return Ok(None);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
self.0.presized_box.set(Some((width, height)));
|
|
259
|
+
let inline = js_sys::Reflect::get(&present, &"inline".into())
|
|
260
|
+
.ok()
|
|
261
|
+
.and_then(|v| v.as_bool())
|
|
262
|
+
.unwrap_or(false);
|
|
263
|
+
|
|
264
|
+
if inline {
|
|
265
|
+
self.0.presized_box.set(None);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let renderer = self.clone();
|
|
269
|
+
let wrapped = Closure::once_into_js(move || {
|
|
270
|
+
let filled = present
|
|
271
|
+
.call0(&JsValue::NULL)
|
|
272
|
+
.map(|v| v.as_bool() != Some(false))
|
|
273
|
+
.unwrap_or(false);
|
|
274
|
+
|
|
275
|
+
if !filled {
|
|
276
|
+
renderer.0.presized_box.set(None);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
return Ok(Some(wrapped.unchecked_into::<js_sys::Function>()));
|
|
245
281
|
}
|
|
246
282
|
|
|
247
283
|
let main_panel: &web_sys::HtmlElement = plugin.unchecked_ref();
|
|
@@ -278,6 +314,7 @@ impl Renderer {
|
|
|
278
314
|
}
|
|
279
315
|
|
|
280
316
|
result?;
|
|
317
|
+
self.0.presized_box.set(Some((width, height)));
|
|
281
318
|
}
|
|
282
319
|
|
|
283
320
|
Ok(None)
|
|
@@ -307,14 +344,16 @@ impl Renderer {
|
|
|
307
344
|
pub async fn draw_fresh(&self, guard: &RenderGuard, view: FreshView) -> ApiResult<()> {
|
|
308
345
|
let timer = self.render_timer();
|
|
309
346
|
timer
|
|
310
|
-
.capture_time(self.draw_view(guard, view.view(), false))
|
|
347
|
+
.capture_time(self.draw_view(guard, view.view(), false, None))
|
|
311
348
|
.await
|
|
312
349
|
}
|
|
313
350
|
|
|
314
351
|
/// Repaint the ALREADY-BOUND `view` on the active plugin.
|
|
315
352
|
pub async fn update_bound(&self, guard: &RenderGuard, view: &View) -> ApiResult<()> {
|
|
316
353
|
let timer = self.render_timer();
|
|
317
|
-
timer
|
|
354
|
+
timer
|
|
355
|
+
.capture_time(self.draw_view(guard, view, true, None))
|
|
356
|
+
.await
|
|
318
357
|
}
|
|
319
358
|
|
|
320
359
|
/// Mint the [`FreshView`] full-draw witness for a plugin that has never
|
|
@@ -363,7 +402,7 @@ impl Renderer {
|
|
|
363
402
|
/// Redraw an already-bound view, debounced.
|
|
364
403
|
pub async fn update_lazy(
|
|
365
404
|
&self,
|
|
366
|
-
view: impl Future<Output = ApiResult<Option<View>>>,
|
|
405
|
+
view: impl Future<Output = ApiResult<Option<(View, Option<ViewDimensionsResp>)>>>,
|
|
367
406
|
) -> ApiResult<()> {
|
|
368
407
|
let timer = self.render_timer();
|
|
369
408
|
self.draw_lock()
|
|
@@ -382,14 +421,14 @@ impl Renderer {
|
|
|
382
421
|
}
|
|
383
422
|
|
|
384
423
|
self.mount_active_plugin()?;
|
|
385
|
-
if let Some(view) = view.await? {
|
|
424
|
+
if let Some((view, dimensions)) = view.await? {
|
|
386
425
|
let _pin = self
|
|
387
426
|
.cached_context()
|
|
388
427
|
.map(|ctx| self.pin_context(&guard, ctx));
|
|
389
428
|
|
|
390
429
|
let timer = self.render_timer();
|
|
391
430
|
timer
|
|
392
|
-
.capture_time(self.draw_view(&guard, &view, true))
|
|
431
|
+
.capture_time(self.draw_view(&guard, &view, true, dimensions))
|
|
393
432
|
.await
|
|
394
433
|
} else {
|
|
395
434
|
tracing::debug!("Render skipped, no `View` attached");
|
|
@@ -399,11 +438,14 @@ impl Renderer {
|
|
|
399
438
|
.await
|
|
400
439
|
}
|
|
401
440
|
|
|
441
|
+
/// `dimensions` is the caller's already-fetched view dimensions, or
|
|
442
|
+
/// `None` to fetch here.
|
|
402
443
|
async fn draw_view(
|
|
403
444
|
&self,
|
|
404
445
|
_guard: &RenderGuard,
|
|
405
446
|
view: &perspective_client::View,
|
|
406
447
|
is_update: bool,
|
|
448
|
+
dimensions: Option<ViewDimensionsResp>,
|
|
407
449
|
) -> ApiResult<()> {
|
|
408
450
|
debug_assert!(
|
|
409
451
|
!is_update || self.cached_context().is_none() || self.render_context().is_some(),
|
|
@@ -420,8 +462,14 @@ impl Renderer {
|
|
|
420
462
|
};
|
|
421
463
|
|
|
422
464
|
let meta = self.metadata();
|
|
465
|
+
let dimensions = match dimensions {
|
|
466
|
+
Some(dimensions) => dimensions,
|
|
467
|
+
None => view.dimensions().await?,
|
|
468
|
+
};
|
|
469
|
+
|
|
423
470
|
let mut limits =
|
|
424
|
-
get_row_and_col_limits(view, &meta, self.is_render_warning_enabled())
|
|
471
|
+
get_row_and_col_limits(&dimensions, view, &meta, self.is_render_warning_enabled())
|
|
472
|
+
.await?;
|
|
425
473
|
|
|
426
474
|
limits.is_update = is_update;
|
|
427
475
|
if let Some(cb) = self.0.on_render_limits_changed.borrow().as_ref() {
|
|
@@ -440,6 +488,7 @@ impl Renderer {
|
|
|
440
488
|
}
|
|
441
489
|
|
|
442
490
|
self.0.data_stale.set(false);
|
|
491
|
+
self.0.presized_box.set(None);
|
|
443
492
|
let result = if is_update {
|
|
444
493
|
let task = plugin.update(view.clone().into(), limits.max_cols, limits.max_rows, false);
|
|
445
494
|
activate_plugin(_guard, &viewer_elem, &plugin, slot.as_deref(), task).await
|
|
@@ -499,3 +548,15 @@ impl Drop for PresizePendingGuard {
|
|
|
499
548
|
cell.set(cell.get() - 1);
|
|
500
549
|
}
|
|
501
550
|
}
|
|
551
|
+
|
|
552
|
+
fn stamp_theme_attr(el: &Element, theme: Option<&str>) {
|
|
553
|
+
match theme {
|
|
554
|
+
Some(theme) if el.get_attribute("theme").as_deref() != Some(theme) => {
|
|
555
|
+
let _ = el.set_attribute("theme", theme);
|
|
556
|
+
},
|
|
557
|
+
Some(_) => {},
|
|
558
|
+
None => {
|
|
559
|
+
let _ = el.remove_attribute("theme");
|
|
560
|
+
},
|
|
561
|
+
}
|
|
562
|
+
}
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
use perspective_client::proto::ViewDimensionsResp;
|
|
13
14
|
use perspective_js::utils::ApiError;
|
|
14
15
|
|
|
15
16
|
use crate::config::PluginStaticConfig;
|
|
@@ -31,16 +32,34 @@ pub struct RenderLimits {
|
|
|
31
32
|
pub max_rows: Option<usize>,
|
|
32
33
|
}
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
///
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
impl RenderLimits {
|
|
36
|
+
/// The column cap is in effect (fewer columns render than the view has).
|
|
37
|
+
pub fn is_col_capped(&self) -> bool {
|
|
38
|
+
self.max_cols.is_some_and(|x| x < self.num_cols)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// The row cap is in effect (fewer rows render than the view has).
|
|
42
|
+
pub fn is_row_capped(&self) -> bool {
|
|
43
|
+
self.max_rows.is_some_and(|x| x < self.num_rows)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/// Either cap is in effect — exactly when the render warning shows.
|
|
47
|
+
pub fn is_capped(&self) -> bool {
|
|
48
|
+
self.is_col_capped() || self.is_row_capped()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Compute the row/column caps for `view` from its already-fetched
|
|
53
|
+
/// `dimensions` against the plugin's static limits. `render_warning` is the
|
|
54
|
+
/// renderer-state flag controlling whether the warning is currently armed;
|
|
55
|
+
/// when `false`, an oversized view renders uncapped (the user has dismissed
|
|
56
|
+
/// the warning).
|
|
38
57
|
pub async fn get_row_and_col_limits(
|
|
58
|
+
dimensions: &ViewDimensionsResp,
|
|
39
59
|
view: &perspective_client::View,
|
|
40
60
|
config: &PluginStaticConfig,
|
|
41
61
|
render_warning: bool,
|
|
42
62
|
) -> Result<RenderLimits, ApiError> {
|
|
43
|
-
let dimensions = view.dimensions().await?;
|
|
44
63
|
let num_cols = dimensions.num_view_columns as usize;
|
|
45
64
|
let num_rows = dimensions.num_view_rows as usize;
|
|
46
65
|
match (config.max_columns, render_warning) {
|
|
@@ -26,6 +26,7 @@ use super::Renderer;
|
|
|
26
26
|
use crate::config::*;
|
|
27
27
|
use crate::queries::resolve_abs_max;
|
|
28
28
|
use crate::session::Session;
|
|
29
|
+
use crate::utils::{CssKind, CssLiteralUse, parse_var_ref, resolve_css_refs};
|
|
29
30
|
|
|
30
31
|
/// A per-column config map. Each inner [`serde_json::Map`] is a flat collection
|
|
31
32
|
/// of plugin-defined JSON keys whose shape is dictated by the active plugin's
|
|
@@ -30,7 +30,8 @@ pub struct RendererProps {
|
|
|
30
30
|
/// internal `metadata` — `Rc::ptr_eq` works.
|
|
31
31
|
pub config: Rc<PluginStaticConfig>,
|
|
32
32
|
|
|
33
|
-
///
|
|
33
|
+
/// The most recently emitted render limits, only while a cap is in
|
|
34
|
+
/// effect.
|
|
34
35
|
pub render_limits: Option<RenderLimits>,
|
|
35
36
|
|
|
36
37
|
/// Names of all registered plugins, in registration order.
|
package/src/rust/renderer.rs
CHANGED
|
@@ -120,6 +120,10 @@ pub struct RendererData {
|
|
|
120
120
|
/// [`Renderer::presize_with_box`] presize calls.
|
|
121
121
|
presize_pending: Cell<u32>,
|
|
122
122
|
|
|
123
|
+
/// The `(width, height)` the plugin last painted at through a presize,
|
|
124
|
+
/// consumed by the post-commit reactive resize.
|
|
125
|
+
presized_box: Cell<Option<(f64, f64)>>,
|
|
126
|
+
|
|
123
127
|
/// Debounce slot for geometry tasks (`resize` / presize), separate from
|
|
124
128
|
/// the default slot data-update redraws coalesce on. Invariant: a
|
|
125
129
|
/// geometry request must never resolve via a parked DATA task — a
|
|
@@ -160,14 +164,7 @@ pub struct RendererData {
|
|
|
160
164
|
|
|
161
165
|
/// The effective theme stamped at the active plugin's last `--psp-*`
|
|
162
166
|
/// CSS CAPTURE — its first paint ([`Renderer::draw_view`]) or last
|
|
163
|
-
/// [`Renderer::restyle_all`].
|
|
164
|
-
/// state behind [`Renderer::needs_restyle`]: restyle necessity is a
|
|
165
|
-
/// comparison against what the plugin actually captured, never
|
|
166
|
-
/// inferred from which call site mutated a theme record (the
|
|
167
|
-
/// raycasting boot double render — see
|
|
168
|
-
/// `PLUGIN_DRAW_INVARIANT_PLAN.md`, captured-theme revision). Same
|
|
169
|
-
/// lifecycle as [`Self::has_drawn`]: cleared on plugin swap,
|
|
170
|
-
/// `dispose` and `delete`; written only inside locked dispatches.
|
|
167
|
+
/// [`Renderer::restyle_all`].
|
|
171
168
|
captured_theme: RefCell<Option<Option<String>>>,
|
|
172
169
|
|
|
173
170
|
/// The [`RenderContext`] of the currently-bound `View` (built at bind
|
|
@@ -268,6 +265,7 @@ impl Renderer {
|
|
|
268
265
|
plugin_config_changed: Default::default(),
|
|
269
266
|
render_warning: Cell::new(true),
|
|
270
267
|
presize_pending: Cell::new(0),
|
|
268
|
+
presized_box: Cell::new(None),
|
|
271
269
|
on_render_limits_changed: Default::default(),
|
|
272
270
|
slot_name: Default::default(),
|
|
273
271
|
theme: Default::default(),
|
|
@@ -315,14 +313,7 @@ impl Renderer {
|
|
|
315
313
|
|
|
316
314
|
/// Whether the active plugin's captured `--psp-*` CSS is STALE — the
|
|
317
315
|
/// effective theme differs from the one stamped at the plugin's last
|
|
318
|
-
/// CSS capture (first paint / last restyle).
|
|
319
|
-
/// every theme-driven `restyle_all` (the `has_drawn` analog for
|
|
320
|
-
/// `restyle` — `PLUGIN_DRAW_INVARIANT_PLAN.md`, captured-theme
|
|
321
|
-
/// revision): a caller that just mutated a theme RECORD may not need a
|
|
322
|
-
/// restyle at all if the plugin's capture already reflects the new
|
|
323
|
-
/// value (e.g. the same restore also performed the first paint,
|
|
324
|
-
/// post-stamp). `false` when no capture exists yet — the owed first
|
|
325
|
-
/// paint captures fresh by construction ("stamp before draw").
|
|
316
|
+
/// CSS capture (first paint / last restyle).
|
|
326
317
|
pub fn needs_restyle(&self) -> bool {
|
|
327
318
|
match &*self.0.captured_theme.borrow() {
|
|
328
319
|
Some(captured) => *captured != self.theme(),
|
|
@@ -336,17 +327,6 @@ impl Renderer {
|
|
|
336
327
|
/// [`Self::delete`], which clears the entire light DOM). Removes the
|
|
337
328
|
/// plugin (`slot=<id>`) and any panel-scoped aux element it mounted
|
|
338
329
|
/// (e.g. the datagrid toolbar, `slot=statusbar-extra-<id>`).
|
|
339
|
-
///
|
|
340
|
-
/// The teardown is DEFERRED through this renderer's draw lock: spawned
|
|
341
|
-
/// draws are uncancellable, so a synchronous `plugin.delete()` here could
|
|
342
|
-
/// land mid-`plugin.draw()`, which the serialized-call contract forbids
|
|
343
|
-
/// (see [`crate::js::plugin`]). Deferral is invisible — the caller
|
|
344
|
-
/// (`eject_panel`) has already removed the panel from the `Workspace`, so
|
|
345
|
-
/// the same commit unmounts its frame and the plugin element sits
|
|
346
|
-
/// unslotted (unrendered) until teardown lands; panel ids are never
|
|
347
|
-
/// reused, so it can't collide with a replacement panel's slot. Draws
|
|
348
|
-
/// still queued behind the lock no-op via the synchronous session reset
|
|
349
|
-
/// (`get_view()` → `None`).
|
|
350
330
|
pub fn dispose(&self) -> ApiFuture<()> {
|
|
351
331
|
self.0.has_drawn.set(false);
|
|
352
332
|
self.0.captured_theme.borrow_mut().take();
|
|
@@ -488,6 +468,12 @@ impl Renderer {
|
|
|
488
468
|
Ok(self.0.has_drawn.get())
|
|
489
469
|
}
|
|
490
470
|
|
|
471
|
+
/// Take the box the plugin last painted at through a presize (see
|
|
472
|
+
/// `RendererData::presized_box`).
|
|
473
|
+
pub fn take_presized_box(&self) -> Option<(f64, f64)> {
|
|
474
|
+
self.0.presized_box.take()
|
|
475
|
+
}
|
|
476
|
+
|
|
491
477
|
/// Mount the selected plugin element into the viewer's light DOM without
|
|
492
478
|
/// drawing it (see [`activate::mount_plugin`] — idempotent). Pure query:
|
|
493
479
|
/// a renderer with no selection is a no-op, deferring to the draw path's
|
|
@@ -695,6 +681,7 @@ impl Renderer {
|
|
|
695
681
|
/// suitable for passing as a Yew prop. Called by the root component
|
|
696
682
|
/// whenever a renderer-related PubSub event fires.
|
|
697
683
|
pub fn to_props(&self, render_limits: Option<RenderLimits>) -> RendererProps {
|
|
684
|
+
let render_limits = render_limits.filter(RenderLimits::is_capped);
|
|
698
685
|
let has_plugin = self.active_plugin().is_some();
|
|
699
686
|
if has_plugin {
|
|
700
687
|
let config = self.metadata();
|
|
@@ -19,7 +19,7 @@ use crate::session::column_defaults_update::ViewConfigUpdateExt;
|
|
|
19
19
|
use crate::session::drag_drop_update::ViewConfigExt as DragDropExt;
|
|
20
20
|
use crate::session::metadata::SessionMetadataRc;
|
|
21
21
|
use crate::session::replace_expression_update::ViewConfigExt as ReplaceExprExt;
|
|
22
|
-
use crate::session::{ColumnStats, TableErrorState
|
|
22
|
+
use crate::session::{ColumnStats, TableErrorState};
|
|
23
23
|
use crate::utils::*;
|
|
24
24
|
|
|
25
25
|
#[derive(Clone, Debug, PartialEq, Default)]
|
|
@@ -39,8 +39,9 @@ pub struct SessionProps {
|
|
|
39
39
|
/// The current `ViewConfig` driving the active `View`.
|
|
40
40
|
pub config: PtrEqRc<ViewConfig>,
|
|
41
41
|
|
|
42
|
-
///
|
|
43
|
-
|
|
42
|
+
/// Whether the view stats carry table dimensions yet, the status
|
|
43
|
+
/// indicator's loaded/uninitialized split.
|
|
44
|
+
pub has_table_cells: bool,
|
|
44
45
|
|
|
45
46
|
/// `true` if a `Table` has been loaded into this session.
|
|
46
47
|
pub has_table: Option<TableLoadState>,
|
|
@@ -10,10 +10,11 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
use std::cell::Cell;
|
|
13
|
+
use std::cell::{Cell, RefCell};
|
|
14
14
|
use std::rc::Rc;
|
|
15
15
|
|
|
16
16
|
use perspective_client::config::*;
|
|
17
|
+
use perspective_client::proto::ViewDimensionsResp;
|
|
17
18
|
use perspective_client::{OnUpdateOptions, View};
|
|
18
19
|
use wasm_bindgen::prelude::*;
|
|
19
20
|
use yew::prelude::*;
|
|
@@ -41,6 +42,10 @@ struct ViewSubscriptionData {
|
|
|
41
42
|
on_stats: Callback<ViewStats>,
|
|
42
43
|
on_update: Option<Callback<()>>,
|
|
43
44
|
is_deleted: Rc<Cell<bool>>,
|
|
45
|
+
|
|
46
|
+
/// The bound `View`'s latest known dimensions, written before
|
|
47
|
+
/// `on_update`/`on_stats` fire.
|
|
48
|
+
dimensions: Rc<RefCell<Option<ViewDimensionsResp>>>,
|
|
44
49
|
}
|
|
45
50
|
|
|
46
51
|
/// A subscription to `on_update()` events from a Perspective `View()`, managing
|
|
@@ -52,20 +57,21 @@ pub struct ViewSubscription {
|
|
|
52
57
|
impl ViewSubscriptionData {
|
|
53
58
|
/// Main handler when underlying `View()` calls `on_update()`.
|
|
54
59
|
async fn on_view_update(self) -> ApiResult<JsValue> {
|
|
60
|
+
self.clone().update_view_stats().await?;
|
|
55
61
|
if let Some(on_update) = &self.on_update {
|
|
56
62
|
on_update.emit(());
|
|
57
63
|
};
|
|
58
64
|
|
|
59
|
-
self.clone().update_view_stats().await?;
|
|
60
65
|
Ok(JsValue::UNDEFINED)
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
async fn update_view_stats(self) -> ApiResult<JsValue> {
|
|
64
69
|
let dimensions = self.view.dimensions().await?;
|
|
65
|
-
let num_rows = dimensions.num_table_rows
|
|
66
|
-
let num_cols = dimensions.num_table_columns
|
|
67
|
-
let virtual_rows = dimensions.num_view_rows
|
|
68
|
-
let virtual_cols = dimensions.num_view_columns
|
|
70
|
+
let num_rows = dimensions.num_table_rows;
|
|
71
|
+
let num_cols = dimensions.num_table_columns;
|
|
72
|
+
let virtual_rows = dimensions.num_view_rows;
|
|
73
|
+
let virtual_cols = dimensions.num_view_columns;
|
|
74
|
+
*self.dimensions.borrow_mut() = Some(dimensions);
|
|
69
75
|
let stats = ViewStats {
|
|
70
76
|
num_table_cells: Some((num_rows, num_cols)),
|
|
71
77
|
num_view_cells: Some((virtual_rows, virtual_cols)),
|
|
@@ -119,6 +125,7 @@ impl ViewSubscription {
|
|
|
119
125
|
callback_id: Rc::default(),
|
|
120
126
|
on_update,
|
|
121
127
|
is_deleted: Rc::default(),
|
|
128
|
+
dimensions: Rc::default(),
|
|
122
129
|
};
|
|
123
130
|
|
|
124
131
|
if data.on_update.is_some() {
|
|
@@ -160,6 +167,12 @@ impl ViewSubscription {
|
|
|
160
167
|
&self.data.view
|
|
161
168
|
}
|
|
162
169
|
|
|
170
|
+
/// The bound `View`'s latest known dimensions (`None` until the first
|
|
171
|
+
/// fetch resolves).
|
|
172
|
+
pub fn dimensions(&self) -> Option<ViewDimensionsResp> {
|
|
173
|
+
self.data.dimensions.borrow().clone()
|
|
174
|
+
}
|
|
175
|
+
|
|
163
176
|
/// User-facing snapshot of the [`ViewConfig`] the bound `View` was
|
|
164
177
|
/// constructed from (the persisted config at build time — global-filter
|
|
165
178
|
/// overlay excluded). This is the [`ViewConfig`] consistent with the
|
package/src/rust/session.rs
CHANGED
|
@@ -24,6 +24,7 @@ use std::ops::Deref;
|
|
|
24
24
|
use std::rc::Rc;
|
|
25
25
|
|
|
26
26
|
use perspective_client::config::*;
|
|
27
|
+
use perspective_client::proto::ViewDimensionsResp;
|
|
27
28
|
use perspective_client::{Client, ClientError, ReconnectCallback, View};
|
|
28
29
|
use perspective_js::apierror;
|
|
29
30
|
use perspective_js::utils::*;
|
|
@@ -56,11 +57,7 @@ pub struct SessionHandle {
|
|
|
56
57
|
pub table_loaded: PubSub<()>,
|
|
57
58
|
pub table_unloaded: PubSub<bool>,
|
|
58
59
|
|
|
59
|
-
/// Fires when a `View` was CREATED — literally
|
|
60
|
-
/// path constructed and bound a new engine `View`. Nothing else may
|
|
61
|
-
/// emit this (a SKIP/REUSE/paused reconcile is [`Self::commit_reconciled`],
|
|
62
|
-
/// which REBUILD also emits) — overloading it broke consumers that
|
|
63
|
-
/// relied on the name's meaning (see `UPDATE_COUNT_REGRESSION_PLAN.md`).
|
|
60
|
+
/// Fires when a `View` was CREATED — literally.
|
|
64
61
|
pub view_created: PubSub<()>,
|
|
65
62
|
|
|
66
63
|
/// Fires exactly once per locked run that RECONCILED the committed
|
|
@@ -74,43 +71,23 @@ pub struct SessionHandle {
|
|
|
74
71
|
pub view_config_changed: PubSub<()>,
|
|
75
72
|
pub title_changed: PubSub<Option<String>>,
|
|
76
73
|
|
|
77
|
-
/// Count of in-flight CONFIG-DRIVEN pipeline runs
|
|
78
|
-
/// settled), incremented by [`Session::begin_config_run`] and
|
|
79
|
-
/// decremented when the returned [`ConfigRunToken`] drops. Drives the
|
|
80
|
-
/// `StatusIndicator` "updating" spinner via [`Self::run_state_changed`].
|
|
81
|
-
/// Pure redraws (resize, activation nudges, `just_render`) do NOT
|
|
82
|
-
/// count — the spinner means "a config requery is in flight".
|
|
74
|
+
/// Count of in-flight CONFIG-DRIVEN pipeline runs.
|
|
83
75
|
in_flight_config_runs: Cell<u32>,
|
|
84
76
|
|
|
85
77
|
/// Fires with the ABSOLUTE [`Self::in_flight_config_runs`] count after
|
|
86
|
-
/// every change.
|
|
87
|
-
/// payload rather than accumulate deltas, so a dropped or reordered
|
|
88
|
-
/// notification is corrected by the next one — the delta-counting
|
|
89
|
-
/// (`view_config_changed`+1 / `view_created`−1) design this replaces
|
|
90
|
-
/// drifted whenever those streams weren't perfectly paired (see
|
|
91
|
-
/// `UPDATE_COUNT_REGRESSION_PLAN.md`).
|
|
78
|
+
/// every change.
|
|
92
79
|
pub run_state_changed: PubSub<u32>,
|
|
93
80
|
|
|
94
81
|
/// Fires when the user clicks the status indicator while in
|
|
95
|
-
/// [`StatusIconState::Normal`].
|
|
96
|
-
/// listener and fans this out as the `perspective-status-indicator-click`
|
|
97
|
-
/// `CustomEvent`.
|
|
82
|
+
/// [`StatusIconState::Normal`].
|
|
98
83
|
pub status_indicator_clicked: PubSub<()>,
|
|
99
84
|
|
|
100
|
-
/// Per-column numeric stats cache.
|
|
101
|
-
/// `fetch_column_abs_max` task and consumed by the schema-query
|
|
102
|
-
/// path so plugins emit gradient defaults without
|
|
103
|
-
/// per-render `View::get_min_max` round trips. Cleared
|
|
104
|
-
/// synchronously inside [`Session::commit_view_config`].
|
|
85
|
+
/// Per-column numeric stats cache.
|
|
105
86
|
column_stats: RefCell<HashMap<String, ColumnStats>>,
|
|
106
87
|
|
|
107
88
|
/// Memoized snapshots used by [`Session::to_props`] to keep
|
|
108
89
|
/// `PtrEqRc` identity stable across repeated `to_props()` calls
|
|
109
|
-
/// when the underlying value hasn't changed.
|
|
110
|
-
/// `PubSub` fire (including `column_stats_changed`) produces a
|
|
111
|
-
/// fresh `Rc` for `config` / `metadata`, triggering downstream
|
|
112
|
-
/// `use_effect_with(view_config, ...)` effects to spuriously
|
|
113
|
-
/// refire — closing a loop with the stats fetch path.
|
|
90
|
+
/// when the underlying value hasn't changed.
|
|
114
91
|
cached_config: RefCell<Option<PtrEqRc<ViewConfig>>>,
|
|
115
92
|
cached_metadata: RefCell<Option<SessionMetadataRc>>,
|
|
116
93
|
|
|
@@ -131,18 +108,7 @@ pub struct SessionHandle {
|
|
|
131
108
|
load_generation: Cell<u32>,
|
|
132
109
|
|
|
133
110
|
/// Open between a `load()` call and its payload's classification as
|
|
134
|
-
/// `Table`/`Client` ([`PendingLoad`]).
|
|
135
|
-
/// unknown, so its RESET/no-RESET disposition cannot be decided yet — but
|
|
136
|
-
/// the window's POSITION on the config-commit stream is fixed at the
|
|
137
|
-
/// `load()` call site. During the window, config commits still apply live
|
|
138
|
-
/// (so `save()` stays coherent — I1) but ALSO append their raw delta to
|
|
139
|
-
/// the journal, and config-driven binds DEFER ([`Session::snapshot`]'s
|
|
140
|
-
/// caller [`crate::tasks::bind_snapshot`]) — the incoming config must
|
|
141
|
-
/// never draw against the still-bound outgoing table (hold the last
|
|
142
|
-
/// frame). Classified as `Table` the journal replays over a reset base
|
|
143
|
-
/// (`reset ∘ Δ₁ ∘ Δ₂ …`, the program-order result the old synchronous
|
|
144
|
-
/// reset guaranteed); as `Client` it is dropped (no reset — live already
|
|
145
|
-
/// carries every Δ). See `SESSION_CONFIG_COHERENCE_PLAN.md`.
|
|
111
|
+
/// `Table`/`Client` ([`PendingLoad`]).
|
|
146
112
|
pending_load: RefCell<Option<PendingLoad>>,
|
|
147
113
|
|
|
148
114
|
/// Coalesces `view_config_changed`: multiple synchronous commits in one
|
|
@@ -165,8 +131,11 @@ pub struct SessionHandle {
|
|
|
165
131
|
/// new value.
|
|
166
132
|
pub column_stats_changed: PubSub<()>,
|
|
167
133
|
|
|
168
|
-
///
|
|
169
|
-
|
|
134
|
+
/// Fires when view stats are updated.
|
|
135
|
+
pub stats_changed: PubSub<()>,
|
|
136
|
+
|
|
137
|
+
/// Injected callback from the root component, driving
|
|
138
|
+
/// `session_props.has_table_cells` for the active panel.
|
|
170
139
|
pub on_stats_changed: RefCell<Option<Callback<()>>>,
|
|
171
140
|
|
|
172
141
|
/// Injected callback from the root component, replacing the former
|
|
@@ -279,13 +248,7 @@ pub struct ResetOptions {
|
|
|
279
248
|
#[derive(Clone)]
|
|
280
249
|
pub struct Session(Rc<SessionHandle>);
|
|
281
250
|
|
|
282
|
-
/// RAII spinner accounting for one config-driven pipeline run
|
|
283
|
-
/// [`Session::begin_config_run`] and `UPDATE_COUNT_REGRESSION_PLAN.md`):
|
|
284
|
-
/// created when the run is scheduled — immediately after its commit — and
|
|
285
|
-
/// moved INTO the run future, so `Drop` settles the count on every exit
|
|
286
|
-
/// path: completion, error, cancellation, and runs that never reach
|
|
287
|
-
/// `bind_view` (the deferred-draw restore). A stranded count is
|
|
288
|
-
/// unrepresentable; there is no other writer.
|
|
251
|
+
/// RAII spinner accounting for one config-driven pipeline run.
|
|
289
252
|
pub struct ConfigRunToken(Session);
|
|
290
253
|
|
|
291
254
|
impl Drop for ConfigRunToken {
|
|
@@ -664,10 +627,27 @@ impl Session {
|
|
|
664
627
|
.map(|sub| sub.get_view().clone())
|
|
665
628
|
}
|
|
666
629
|
|
|
630
|
+
/// The bound `View` together with its latest known dimensions, read under
|
|
631
|
+
/// one borrow so they always belong together.
|
|
632
|
+
pub fn get_view_with_dimensions(&self) -> Option<(View, Option<ViewDimensionsResp>)> {
|
|
633
|
+
self.borrow()
|
|
634
|
+
.view_sub
|
|
635
|
+
.as_ref()
|
|
636
|
+
.map(|sub| (sub.get_view().clone(), sub.dimensions()))
|
|
637
|
+
}
|
|
638
|
+
|
|
667
639
|
pub(crate) fn get_table_stats(&self) -> Option<ViewStats> {
|
|
668
640
|
self.borrow().stats.clone()
|
|
669
641
|
}
|
|
670
642
|
|
|
643
|
+
/// Whether the stats snapshot carries table dimensions.
|
|
644
|
+
pub(crate) fn has_table_cells(&self) -> bool {
|
|
645
|
+
self.borrow()
|
|
646
|
+
.stats
|
|
647
|
+
.as_ref()
|
|
648
|
+
.is_some_and(|s| s.num_table_cells.is_some())
|
|
649
|
+
}
|
|
650
|
+
|
|
671
651
|
pub fn get_view_config(&'_ self) -> Ref<'_, ViewConfig> {
|
|
672
652
|
Ref::map(self.borrow(), |x| &x.config)
|
|
673
653
|
}
|
|
@@ -924,15 +904,6 @@ impl Session {
|
|
|
924
904
|
/// Emit `view_config_changed` coalesced to one event per microtask batch.
|
|
925
905
|
/// Stats are cleared synchronously per commit (idempotent — an
|
|
926
906
|
/// already-empty cache no-ops, so rapid commit sequences fetch once).
|
|
927
|
-
///
|
|
928
|
-
/// CONTRACT: this channel is COALESCED and DEFERRED — N same-batch
|
|
929
|
-
/// commits produce ONE emission, on a microtask AFTER the mutation.
|
|
930
|
-
/// Subscribers must be batch-tolerant snapshot refreshers (they re-read
|
|
931
|
-
/// live state; today: the root `SessionProps` snapshot and the debug
|
|
932
|
-
/// panel). It is NOT suitable for per-commit accounting — pairing its
|
|
933
|
-
/// edges against any other event stream WILL drift (the stuck-spinner
|
|
934
|
-
/// regression, `UPDATE_COUNT_REGRESSION_PLAN.md`); run accounting is
|
|
935
|
-
/// [`Self::begin_config_run`]'s RAII token instead.
|
|
936
907
|
fn notify_view_config_changed(&self) {
|
|
937
908
|
self.clear_column_stats();
|
|
938
909
|
if !self.0.config_event_scheduled.replace(true) {
|
|
@@ -1218,6 +1189,8 @@ impl Session {
|
|
|
1218
1189
|
if let Some(cb) = self.on_stats_changed.borrow().as_ref() {
|
|
1219
1190
|
cb.emit(());
|
|
1220
1191
|
}
|
|
1192
|
+
|
|
1193
|
+
self.stats_changed.emit(());
|
|
1221
1194
|
}
|
|
1222
1195
|
|
|
1223
1196
|
fn all_columns(&self) -> Vec<String> {
|
|
@@ -1258,7 +1231,10 @@ impl Session {
|
|
|
1258
1231
|
|
|
1259
1232
|
SessionProps {
|
|
1260
1233
|
config,
|
|
1261
|
-
|
|
1234
|
+
has_table_cells: data
|
|
1235
|
+
.stats
|
|
1236
|
+
.as_ref()
|
|
1237
|
+
.is_some_and(|s| s.num_table_cells.is_some()),
|
|
1262
1238
|
has_table: if data.table.is_some() {
|
|
1263
1239
|
Some(TableLoadState::Loaded)
|
|
1264
1240
|
} else if data.is_loading {
|
|
@@ -1290,15 +1266,7 @@ pub struct ConfigSnapshot {
|
|
|
1290
1266
|
pub struct ValidatedSnapshot(ConfigSnapshot);
|
|
1291
1267
|
|
|
1292
1268
|
/// Type-state witness that a `View` is NEW for the plugin about to render
|
|
1293
|
-
/// it
|
|
1294
|
-
/// `draw` fires iff there is a new `View`, never otherwise). Minted in
|
|
1295
|
-
/// exactly two places: [`Session::bind_view`]'s REBUILD arm (a new engine
|
|
1296
|
-
/// `View` was constructed) and
|
|
1297
|
-
/// [`crate::renderer::Renderer::promote_first_paint`] (a freshly-selected
|
|
1298
|
-
/// plugin owes its first paint of the bound `View`). Do NOT construct it
|
|
1299
|
-
/// anywhere else — `Renderer::draw_fresh` is the only consumer, and this
|
|
1300
|
-
/// witness is what makes "full draw without a new `View`" (the stacked-chart
|
|
1301
|
-
/// activation regression) uncompilable.
|
|
1269
|
+
/// it.
|
|
1302
1270
|
pub struct FreshView(View);
|
|
1303
1271
|
|
|
1304
1272
|
impl FreshView {
|