@perspective-dev/viewer 4.4.0 → 4.5.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.
Files changed (228) hide show
  1. package/dist/cdn/perspective-viewer.js +1 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -0
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/esm/bootstrap.d.ts +2 -1
  24. package/dist/esm/column-format.d.ts +51 -0
  25. package/dist/esm/extensions.d.ts +6 -0
  26. package/dist/esm/perspective-viewer.d.ts +4 -1
  27. package/dist/esm/perspective-viewer.inline.js +1 -2
  28. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  29. package/dist/esm/perspective-viewer.js +1 -2
  30. package/dist/esm/perspective-viewer.js.map +4 -4
  31. package/dist/esm/perspective-viewer.worker.d.ts +2 -0
  32. package/dist/esm/plugin.d.ts +21 -77
  33. package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
  34. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
  35. package/dist/esm/ts-rs/ViewerConfig.d.ts +39 -0
  36. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
  37. package/dist/wasm/perspective-viewer.d.ts +88 -24
  38. package/dist/wasm/perspective-viewer.js +320 -151
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +22 -17
  41. package/package.json +24 -2
  42. package/src/css/column-selector.css +3 -2
  43. package/src/css/column-settings-panel.css +36 -6
  44. package/src/css/column-style.css +27 -2
  45. package/src/css/containers/scroll-panel.css +2 -1
  46. package/src/css/containers/tabs.css +8 -52
  47. package/src/css/dom/checkbox.css +0 -4
  48. package/src/css/form/code-editor.css +1 -0
  49. package/src/css/form/debug.css +3 -10
  50. package/src/css/plugin-selector.css +33 -0
  51. package/src/css/plugin-settings-panel.css +99 -0
  52. package/src/css/status-bar.css +1 -1
  53. package/src/css/viewer.css +65 -3
  54. package/src/rust/components/column_dropdown.rs +3 -1
  55. package/src/rust/components/column_selector/active_column.rs +13 -19
  56. package/src/rust/components/column_selector/config_selector.rs +20 -20
  57. package/src/rust/components/column_selector/filter_column.rs +14 -14
  58. package/src/rust/components/column_selector/inactive_column.rs +9 -15
  59. package/src/rust/components/column_selector/pivot_column.rs +7 -7
  60. package/src/rust/components/column_selector/sort_column.rs +7 -7
  61. package/src/rust/components/column_selector.rs +55 -37
  62. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
  63. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +394 -0
  64. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
  65. package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
  66. package/src/rust/components/column_settings_sidebar.rs +43 -49
  67. package/src/rust/components/containers/dragdrop_list.rs +5 -5
  68. package/src/rust/components/containers/mod.rs +0 -1
  69. package/src/rust/components/containers/scroll_panel.rs +21 -7
  70. package/src/rust/components/containers/sidebar.rs +8 -6
  71. package/src/rust/components/containers/split_panel.rs +3 -3
  72. package/src/rust/components/containers/tab_list.rs +3 -9
  73. package/src/rust/components/copy_dropdown.rs +2 -3
  74. package/src/rust/components/datetime_column_style.rs +19 -81
  75. package/src/rust/components/editable_header.rs +2 -3
  76. package/src/rust/components/export_dropdown.rs +2 -3
  77. package/src/rust/components/expression_editor.rs +29 -17
  78. package/src/rust/components/filter_dropdown.rs +2 -1
  79. package/src/rust/components/form/color_range_selector.rs +14 -7
  80. package/src/rust/components/form/debug.rs +47 -37
  81. package/src/rust/components/main_panel.rs +24 -65
  82. package/src/rust/components/mod.rs +2 -1
  83. package/src/rust/components/number_series_style.rs +161 -0
  84. package/src/rust/components/plugin_tab.rs +221 -0
  85. package/src/rust/components/settings_panel.rs +181 -59
  86. package/src/rust/components/status_bar.rs +140 -173
  87. package/src/rust/components/status_indicator.rs +15 -22
  88. package/src/rust/components/string_column_style.rs +20 -82
  89. package/src/rust/components/style_controls/number_string_format.rs +14 -30
  90. package/src/rust/components/viewer.rs +92 -132
  91. package/src/rust/config/column_config_schema.rs +195 -0
  92. package/src/rust/config/columns_config.rs +4 -97
  93. package/src/rust/config/datetime_column_style.rs +0 -5
  94. package/src/rust/config/mod.rs +8 -2
  95. package/src/rust/config/number_series_style.rs +79 -0
  96. package/src/rust/config/plugin_static_config.rs +144 -0
  97. package/src/rust/config/string_column_style.rs +0 -5
  98. package/src/rust/config/viewer_config.rs +7 -8
  99. package/src/rust/custom_elements/copy_dropdown.rs +30 -18
  100. package/src/rust/custom_elements/debug_plugin.rs +5 -7
  101. package/src/rust/custom_elements/export_dropdown.rs +26 -18
  102. package/src/rust/custom_elements/viewer.rs +77 -77
  103. package/src/rust/custom_events.rs +181 -224
  104. package/src/rust/js/plugin.rs +45 -117
  105. package/src/rust/lib.rs +39 -5
  106. package/src/rust/presentation/drag_helpers.rs +206 -0
  107. package/src/rust/presentation/props.rs +8 -0
  108. package/src/rust/presentation.rs +256 -41
  109. package/src/rust/{tasks → queries}/column_locator.rs +17 -73
  110. package/src/rust/queries/column_values.rs +59 -0
  111. package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
  112. package/src/rust/queries/exports.rs +96 -0
  113. package/src/rust/queries/fetch_column_stats.rs +94 -0
  114. package/src/rust/queries/get_viewer_config.rs +54 -0
  115. package/src/rust/queries/mod.rs +44 -0
  116. package/src/rust/queries/plugin_column_styles.rs +101 -0
  117. package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
  118. package/src/rust/renderer/activate.rs +1 -0
  119. package/src/rust/renderer/limits.rs +9 -4
  120. package/src/rust/renderer/plugin_store.rs +12 -0
  121. package/src/rust/renderer/props.rs +28 -3
  122. package/src/rust/renderer/registry.rs +40 -15
  123. package/src/rust/renderer.rs +649 -55
  124. package/src/rust/session/column_defaults_update.rs +20 -28
  125. package/src/rust/session/drag_drop_update.rs +10 -10
  126. package/src/rust/session/metadata.rs +31 -16
  127. package/src/rust/session/props.rs +15 -6
  128. package/src/rust/session/view_subscription.rs +10 -0
  129. package/src/rust/session.rs +109 -147
  130. package/src/rust/tasks/copy_export.rs +178 -158
  131. package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
  132. package/src/rust/tasks/edit_expression.rs +68 -88
  133. package/src/rust/tasks/eject.rs +25 -22
  134. package/src/rust/tasks/intersection_observer.rs +8 -21
  135. package/src/rust/tasks/mod.rs +19 -21
  136. package/src/rust/tasks/reset_all.rs +78 -0
  137. package/src/rust/tasks/resize_observer.rs +11 -33
  138. package/src/rust/tasks/restore_and_render.rs +117 -89
  139. package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +38 -35
  140. package/src/rust/tasks/send_plugin_config.rs +32 -33
  141. package/src/rust/tasks/update_and_render.rs +66 -47
  142. package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
  143. package/src/rust/tasks/validate_expression.rs +61 -0
  144. package/src/rust/utils/browser/selection.rs +4 -4
  145. package/src/rust/utils/mod.rs +0 -63
  146. package/src/svg/datagrid-select-row-tree.svg +13 -0
  147. package/src/svg/mega-menu-icons-density.svg +23 -0
  148. package/src/svg/mega-menu-icons-map-density.svg +24 -0
  149. package/src/svg/mega-menu-icons-map-line.svg +19 -0
  150. package/src/themes/botanical.css +27 -53
  151. package/src/themes/defaults.css +42 -36
  152. package/src/themes/dracula.css +36 -54
  153. package/src/themes/gruvbox-dark.css +39 -59
  154. package/src/themes/gruvbox.css +16 -28
  155. package/src/themes/icons.css +4 -18
  156. package/src/themes/intl/de.css +42 -6
  157. package/src/themes/intl/es.css +42 -6
  158. package/src/themes/intl/fr.css +42 -6
  159. package/src/themes/intl/ja.css +42 -6
  160. package/src/themes/intl/pt.css +42 -6
  161. package/src/themes/intl/zh.css +42 -6
  162. package/src/themes/intl.css +37 -4
  163. package/src/themes/monokai.css +45 -61
  164. package/src/themes/phosphor.css +175 -0
  165. package/src/themes/pro-dark.css +25 -34
  166. package/src/themes/solarized-dark.css +21 -36
  167. package/src/themes/solarized.css +13 -23
  168. package/src/themes/themes.css +1 -0
  169. package/src/themes/vaporwave.css +40 -74
  170. package/src/ts/bootstrap.ts +14 -3
  171. package/src/ts/column-format.ts +162 -0
  172. package/src/ts/extensions.ts +12 -1
  173. package/src/ts/perspective-viewer.ts +10 -1
  174. package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
  175. package/src/ts/plugin.ts +29 -105
  176. package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
  177. package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
  178. package/src/ts/ts-rs/ViewerConfig.ts +14 -0
  179. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
  180. package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
  181. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
  182. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
  183. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
  184. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  185. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
  186. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  187. package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
  188. package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
  189. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
  190. package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
  191. package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
  192. package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
  193. package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
  194. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
  195. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
  196. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  197. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
  198. package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
  199. package/src/rust/components/number_column_style.rs +0 -483
  200. package/src/rust/config/number_column_style.rs +0 -132
  201. package/src/rust/dragdrop.rs +0 -481
  202. package/src/rust/tasks/plugin_column_styles.rs +0 -98
  203. package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
  204. package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
  205. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
  206. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
  207. package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
  208. package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
  209. package/src/ts/ts-rs/FormatMode.ts +0 -3
  210. package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
  211. package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
  212. package/src/ts/ts-rs/PluginConfig.ts +0 -4
  213. package/src/ts/ts-rs/RoundingMode.ts +0 -3
  214. package/src/ts/ts-rs/RoundingPriority.ts +0 -3
  215. package/src/ts/ts-rs/SignDisplay.ts +0 -3
  216. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
  217. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
  218. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  219. package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
  220. package/src/ts/ts-rs/UseGrouping.ts +0 -3
  221. /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline0.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline1.js +0 -0
  223. /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline2.js +0 -0
  224. /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline3.js +0 -0
  225. /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline4.js +0 -0
  226. /package/src/rust/{tasks → config}/export_method.rs +0 -0
  227. /package/src/rust/{tasks → queries}/export_app.rs +0 -0
  228. /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
@@ -18,13 +18,13 @@ pub(crate) mod replace_expression_update;
18
18
  mod view_subscription;
19
19
 
20
20
  use std::cell::{Ref, RefCell};
21
- use std::collections::HashSet;
21
+ use std::collections::{HashMap, HashSet};
22
22
  use std::future::Future;
23
23
  use std::ops::Deref;
24
24
  use std::rc::Rc;
25
25
 
26
26
  use perspective_client::config::*;
27
- use perspective_client::{Client, ClientError, ReconnectCallback, View, ViewWindow};
27
+ use perspective_client::{Client, ClientError, ReconnectCallback, View};
28
28
  use perspective_js::apierror;
29
29
  use perspective_js::utils::*;
30
30
  use wasm_bindgen::prelude::*;
@@ -36,9 +36,18 @@ pub use self::metadata::{MetadataRef, SessionMetadata, SessionMetadataRc};
36
36
  pub use self::props::{SessionProps, TableLoadState};
37
37
  pub use self::view_subscription::ViewStats;
38
38
  use self::view_subscription::*;
39
- use crate::js::plugin::*;
39
+ use crate::config::PluginStaticConfig;
40
40
  use crate::utils::*;
41
41
 
42
+ /// Per-column numeric stats sourced from `View::get_min_max`. Keyed by
43
+ /// column name in [`SessionHandle::column_stats`]; populated lazily by
44
+ /// the `fetch_column_abs_max` task; cleared on every
45
+ /// `view_config_changed`.
46
+ #[derive(Clone, Copy, Debug, Default, PartialEq)]
47
+ pub struct ColumnStats {
48
+ pub abs_max: Option<f64>,
49
+ }
50
+
42
51
  /// Immutable state for `Session`.
43
52
  #[derive(Default)]
44
53
  pub struct SessionHandle {
@@ -50,6 +59,34 @@ pub struct SessionHandle {
50
59
  pub view_config_changed: PubSub<()>,
51
60
  pub title_changed: PubSub<Option<String>>,
52
61
 
62
+ /// Fires when the user clicks the status indicator while in
63
+ /// [`StatusIconState::Normal`]. `wire_custom_events` is the only
64
+ /// listener and fans this out as the `perspective-status-indicator-click`
65
+ /// `CustomEvent`.
66
+ pub status_indicator_clicked: PubSub<()>,
67
+
68
+ /// Per-column numeric stats cache. Populated by the
69
+ /// `fetch_column_abs_max` task and consumed by the schema-query
70
+ /// path so plugins emit gradient defaults without
71
+ /// per-render `View::get_min_max` round trips. Cleared
72
+ /// synchronously inside [`Session::update_view_config`].
73
+ column_stats: RefCell<HashMap<String, ColumnStats>>,
74
+
75
+ /// Memoized snapshots used by [`Session::to_props`] to keep
76
+ /// `PtrEqRc` identity stable across repeated `to_props()` calls
77
+ /// when the underlying value hasn't changed. Without this, every
78
+ /// `PubSub` fire (including `column_stats_changed`) produces a
79
+ /// fresh `Rc` for `config` / `metadata`, triggering downstream
80
+ /// `use_effect_with(view_config, ...)` effects to spuriously
81
+ /// refire — closing a loop with the stats fetch path.
82
+ cached_config: RefCell<Option<PtrEqRc<ViewConfig>>>,
83
+ cached_metadata: RefCell<Option<SessionMetadataRc>>,
84
+
85
+ /// Fires when [`SessionHandle::column_stats`] is updated (insert or
86
+ /// clear). Subscribers re-render and re-query the schema with the
87
+ /// new value.
88
+ pub column_stats_changed: PubSub<()>,
89
+
53
90
  /// Injected callback from the root component, replacing the former
54
91
  /// `stats_changed: PubSub` field. Fires when view stats are updated.
55
92
  pub on_stats_changed: RefCell<Option<Callback<()>>>,
@@ -325,10 +362,10 @@ impl Session {
325
362
  }
326
363
  }
327
364
 
328
- pub fn update_column_defaults(&self, requirements: &ViewConfigRequirements) {
365
+ pub fn update_column_defaults(&self, config_static: &PluginStaticConfig) {
329
366
  if self.borrow().config.columns.is_empty() {
330
367
  let mut update = ViewConfigUpdate::default();
331
- self.set_update_column_defaults(&mut update, requirements);
368
+ self.set_update_column_defaults(&mut update, config_static);
332
369
  self.borrow_mut().config.apply_update(update);
333
370
  }
334
371
  }
@@ -418,92 +455,6 @@ impl Session {
418
455
  Ok(())
419
456
  }
420
457
 
421
- /// Validate an expression string and marshall the results.
422
- pub async fn validate_expr(
423
- &self,
424
- expr: &str,
425
- ) -> Result<Option<perspective_client::ExprValidationError>, ApiError> {
426
- let table = self.borrow().table.as_ref().unwrap().clone();
427
- let errors = table
428
- .validate_expressions(
429
- ExpressionsDeserde::Map(std::collections::HashMap::from_iter([(
430
- "_".to_string(),
431
- expr.to_string(),
432
- )]))
433
- .into(),
434
- )
435
- .await?
436
- .errors;
437
-
438
- Ok(errors.get("_").cloned())
439
- }
440
-
441
- pub async fn arrow_as_vec(
442
- &self,
443
- flat: bool,
444
- window: Option<ViewWindow>,
445
- ) -> Result<Vec<u8>, ApiError> {
446
- Ok(self
447
- .flat_view(flat)
448
- .await?
449
- .to_arrow(window.unwrap_or_default())
450
- .await?
451
- .into())
452
- }
453
-
454
- pub async fn arrow_as_jsvalue(
455
- &self,
456
- flat: bool,
457
- window: Option<ViewWindow>,
458
- ) -> Result<js_sys::ArrayBuffer, ApiError> {
459
- let arrow = self
460
- .flat_view(flat)
461
- .await?
462
- .to_arrow(window.unwrap_or_default())
463
- .await?;
464
- Ok(js_sys::Uint8Array::from(&arrow[..])
465
- .buffer()
466
- .unchecked_into())
467
- }
468
-
469
- pub async fn ndjson_as_jsvalue(
470
- &self,
471
- flat: bool,
472
- window: Option<ViewWindow>,
473
- ) -> Result<js_sys::JsString, ApiError> {
474
- let json: String = self
475
- .flat_view(flat)
476
- .await?
477
- .to_ndjson(window.unwrap_or_default())
478
- .await?;
479
-
480
- Ok(json.into())
481
- }
482
-
483
- pub async fn json_as_jsvalue(
484
- &self,
485
- flat: bool,
486
- window: Option<ViewWindow>,
487
- ) -> Result<js_sys::Object, ApiError> {
488
- let json: String = self
489
- .flat_view(flat)
490
- .await?
491
- .to_columns_string(window.unwrap_or_default())
492
- .await?;
493
-
494
- Ok(js_sys::JSON::parse(&json)?.unchecked_into())
495
- }
496
-
497
- pub async fn csv_as_jsvalue(
498
- &self,
499
- flat: bool,
500
- window: Option<ViewWindow>,
501
- ) -> Result<js_sys::JsString, ApiError> {
502
- let window = window.unwrap_or_default();
503
- let csv = self.flat_view(flat).await?.to_csv(window).await;
504
- Ok(csv.map(js_sys::JsString::from)?)
505
- }
506
-
507
458
  pub fn get_view(&self) -> Option<View> {
508
459
  self.borrow()
509
460
  .view_sub
@@ -519,58 +470,29 @@ impl Session {
519
470
  Ref::map(self.borrow(), |x| &x.config)
520
471
  }
521
472
 
522
- /// Get all unique column values for a given column name.
473
+ /// Snapshot of the [`ViewConfig`] the currently-bound `View` was
474
+ /// constructed from. Returns `None` if no `View` has been created
475
+ /// yet (e.g., the post-`load`/pre-render window, or after a reset).
523
476
  ///
524
- /// Use the `.to_csv()` method, as I suspected copying this large string
525
- /// once was more efficient than copying many smaller strings, and
526
- /// string copying shows up frequently when doing performance analysis.
527
- ///
528
- /// TODO Does not work with expressions yet.
529
- ///
530
- /// # Arguments
531
- /// - `column` The name of the column (or expression).
532
- pub async fn get_column_values(&self, column: String) -> Result<Vec<String>, ApiError> {
533
- let expressions = Some(self.borrow().config.expressions.clone());
534
- let config = ViewConfigUpdate {
535
- group_by: Some(vec![column]),
536
- columns: Some(vec![]),
537
- expressions,
538
- ..ViewConfigUpdate::default()
539
- };
540
-
541
- let table = self.borrow().table.clone().unwrap();
542
- let view = table.view(Some(config.clone())).await?;
543
- let csv = view.to_csv(ViewWindow::default()).await?;
544
-
545
- ApiFuture::spawn(async move {
546
- view.delete().await?;
547
- Ok(())
548
- });
549
-
550
- let res = csv
551
- .lines()
552
- .map(|val| {
553
- if val.starts_with('\"') && val.ends_with('\"') {
554
- (val[1..val.len() - 1]).to_owned()
555
- } else {
556
- val.to_owned()
557
- }
558
- })
559
- .skip(2)
560
- .collect::<Vec<String>>();
561
- Ok(res)
477
+ /// Prefer this over [`Self::get_view_config`] when you need a
478
+ /// value consistent with what the active plugin is rendering.
479
+ /// `get_view_config` returns the live config, which is mutated
480
+ /// synchronously by [`Self::update_view_config`] ahead of the next
481
+ /// draw and so may temporarily disagree with the bound `View`.
482
+ pub fn get_rendered_view_config(&self) -> Option<Rc<ViewConfig>> {
483
+ self.borrow().view_sub.as_ref().map(|s| s.get_view_config())
562
484
  }
563
485
 
564
486
  pub fn set_update_column_defaults(
565
487
  &self,
566
488
  config_update: &mut ViewConfigUpdate,
567
- requirements: &ViewConfigRequirements,
489
+ config_static: &PluginStaticConfig,
568
490
  ) {
569
491
  use self::column_defaults_update::*;
570
492
  config_update.set_update_column_defaults(
571
493
  &self.metadata(),
572
494
  &self.get_view_config().columns,
573
- requirements,
495
+ config_static,
574
496
  )
575
497
  }
576
498
 
@@ -586,12 +508,42 @@ impl Session {
586
508
 
587
509
  if self.borrow_mut().config.apply_update(config_update) && self.0.borrow().is_clean {
588
510
  self.0.borrow_mut().is_clean = false;
511
+ // View config changed → cached stats are stale.
512
+ self.clear_column_stats();
589
513
  self.view_config_changed.emit(());
590
514
  }
591
515
 
592
516
  Ok(())
593
517
  }
594
518
 
519
+ /// Read the cached `ColumnStats` for a column. Returns `None` if no
520
+ /// fetch has populated this column yet (or the cache was just
521
+ /// cleared by a view-config change).
522
+ pub fn get_column_stats(&self, column_name: &str) -> Option<ColumnStats> {
523
+ self.column_stats.borrow().get(column_name).copied()
524
+ }
525
+
526
+ /// Insert a freshly-fetched `abs_max` for a column and notify
527
+ /// subscribers via [`SessionHandle::column_stats_changed`].
528
+ pub fn set_column_abs_max(&self, column_name: String, abs_max: f64) {
529
+ self.column_stats
530
+ .borrow_mut()
531
+ .entry(column_name)
532
+ .or_default()
533
+ .abs_max = Some(abs_max);
534
+ self.column_stats_changed.emit(());
535
+ }
536
+
537
+ /// Drop the entire stats cache. Called when the view config changes
538
+ /// (filter / group_by / etc.) so stats are re-fetched on next
539
+ /// schema query.
540
+ pub fn clear_column_stats(&self) {
541
+ if !self.column_stats.borrow().is_empty() {
542
+ self.column_stats.borrow_mut().clear();
543
+ self.column_stats_changed.emit(());
544
+ }
545
+ }
546
+
595
547
  /// In order to create a new view in this session, the session must first be
596
548
  /// validated to create a `ValidSession<'_>` guard.
597
549
  pub async fn validate(&self) -> Result<ValidSession<'_>, ApiError> {
@@ -643,19 +595,6 @@ impl Session {
643
595
  Ok(ValidSession(self, is_diff))
644
596
  }
645
597
 
646
- async fn flat_view(&self, flat: bool) -> ApiResult<View> {
647
- if flat {
648
- let table = self.borrow().table.clone().into_apierror()?;
649
- Ok(table.view(None).await?)
650
- } else {
651
- self.borrow()
652
- .view_sub
653
- .as_ref()
654
- .map(|x| x.get_view().clone())
655
- .into_apierror()
656
- }
657
- }
658
-
659
598
  fn update_stats(&self, stats: ViewStats) {
660
599
  self.borrow_mut().stats = Some(stats);
661
600
  if let Some(cb) = self.on_stats_changed.borrow().as_ref() {
@@ -772,9 +711,31 @@ impl Session {
772
711
  /// for passing as a Yew prop. Called by the root component whenever a
773
712
  /// session-related PubSub event fires.
774
713
  pub fn to_props(&self) -> SessionProps {
714
+ let column_stats = PtrEqRc::new(self.column_stats.borrow().clone());
775
715
  let data = self.borrow();
716
+
717
+ // Reuse memoized snapshots when the underlying value hasn't
718
+ // changed. PtrEq identity must be stable across `to_props()`
719
+ // calls triggered by *unrelated* pubsubs (e.g. our own
720
+ // `column_stats_changed`), or downstream effects keyed on
721
+ // these `PtrEqRc`s will spuriously refire.
722
+ let config = {
723
+ let mut cached = self.cached_config.borrow_mut();
724
+ if !matches!(&*cached, Some(c) if **c == data.config) {
725
+ *cached = Some(PtrEqRc::new(data.config.clone()));
726
+ }
727
+ cached.clone().unwrap()
728
+ };
729
+ let metadata = {
730
+ let mut cached = self.cached_metadata.borrow_mut();
731
+ if !matches!(&*cached, Some(m) if **m == data.metadata) {
732
+ *cached = Some(PtrEqRc::new(data.metadata.clone()));
733
+ }
734
+ cached.clone().unwrap()
735
+ };
736
+
776
737
  SessionProps {
777
- config: PtrEqRc::new(data.config.clone()),
738
+ config,
778
739
  stats: data.stats.clone(),
779
740
  has_table: if data.table.is_some() {
780
741
  Some(TableLoadState::Loaded)
@@ -785,7 +746,8 @@ impl Session {
785
746
  },
786
747
  error: data.error.clone(),
787
748
  title: data.title.clone(),
788
- metadata: PtrEqRc::new(data.metadata.clone()),
749
+ metadata,
750
+ column_stats,
789
751
  }
790
752
  }
791
753
  }