@perspective-dev/viewer 4.4.1 → 4.5.1

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 (227) 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 -1
  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 +2 -0
  26. package/dist/esm/perspective-viewer.d.ts +3 -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 +16 -72
  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 +6 -3
  36. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
  37. package/dist/wasm/perspective-viewer.d.ts +77 -18
  38. package/dist/wasm/perspective-viewer.js +302 -148
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
  41. package/package.json +24 -2
  42. package/src/css/column-selector.css +3 -2
  43. package/src/css/column-settings-panel.css +44 -9
  44. package/src/css/column-style.css +35 -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 +2 -6
  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/viewer.css +143 -3
  53. package/src/rust/components/column_dropdown.rs +3 -1
  54. package/src/rust/components/column_selector/active_column.rs +16 -19
  55. package/src/rust/components/column_selector/config_selector.rs +20 -20
  56. package/src/rust/components/column_selector/filter_column.rs +14 -14
  57. package/src/rust/components/column_selector/inactive_column.rs +10 -15
  58. package/src/rust/components/column_selector/pivot_column.rs +7 -7
  59. package/src/rust/components/column_selector/sort_column.rs +7 -7
  60. package/src/rust/components/column_selector.rs +55 -37
  61. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
  62. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +395 -0
  63. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
  64. package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
  65. package/src/rust/components/column_settings_sidebar.rs +44 -49
  66. package/src/rust/components/containers/dragdrop_list.rs +32 -5
  67. package/src/rust/components/containers/mod.rs +0 -1
  68. package/src/rust/components/containers/scroll_panel.rs +21 -7
  69. package/src/rust/components/containers/sidebar.rs +8 -6
  70. package/src/rust/components/containers/split_panel.rs +3 -3
  71. package/src/rust/components/containers/tab_list.rs +3 -9
  72. package/src/rust/components/copy_dropdown.rs +2 -3
  73. package/src/rust/components/datetime_column_style.rs +19 -81
  74. package/src/rust/components/editable_header.rs +17 -3
  75. package/src/rust/components/export_dropdown.rs +2 -3
  76. package/src/rust/components/expression_editor.rs +29 -17
  77. package/src/rust/components/filter_dropdown.rs +2 -1
  78. package/src/rust/components/form/color_range_selector.rs +14 -7
  79. package/src/rust/components/form/debug.rs +47 -37
  80. package/src/rust/components/main_panel.rs +24 -65
  81. package/src/rust/components/mod.rs +2 -1
  82. package/src/rust/components/number_series_style.rs +161 -0
  83. package/src/rust/components/plugin_tab.rs +221 -0
  84. package/src/rust/components/settings_panel.rs +181 -59
  85. package/src/rust/components/status_bar.rs +141 -174
  86. package/src/rust/components/status_indicator.rs +15 -22
  87. package/src/rust/components/string_column_style.rs +20 -82
  88. package/src/rust/components/style_controls/number_string_format.rs +14 -30
  89. package/src/rust/components/viewer.rs +169 -132
  90. package/src/rust/config/column_config_schema.rs +195 -0
  91. package/src/rust/config/columns_config.rs +4 -97
  92. package/src/rust/config/datetime_column_style.rs +0 -5
  93. package/src/rust/config/mod.rs +8 -2
  94. package/src/rust/config/number_series_style.rs +79 -0
  95. package/src/rust/config/plugin_static_config.rs +144 -0
  96. package/src/rust/config/string_column_style.rs +0 -5
  97. package/src/rust/config/viewer_config.rs +5 -6
  98. package/src/rust/custom_elements/copy_dropdown.rs +30 -18
  99. package/src/rust/custom_elements/debug_plugin.rs +1 -3
  100. package/src/rust/custom_elements/export_dropdown.rs +26 -18
  101. package/src/rust/custom_elements/viewer.rs +62 -73
  102. package/src/rust/custom_events.rs +181 -224
  103. package/src/rust/js/plugin.rs +45 -117
  104. package/src/rust/lib.rs +34 -5
  105. package/src/rust/presentation/drag_helpers.rs +206 -0
  106. package/src/rust/presentation/props.rs +8 -0
  107. package/src/rust/presentation.rs +256 -41
  108. package/src/rust/{tasks → queries}/column_locator.rs +17 -73
  109. package/src/rust/queries/column_values.rs +59 -0
  110. package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
  111. package/src/rust/queries/exports.rs +96 -0
  112. package/src/rust/queries/fetch_column_stats.rs +94 -0
  113. package/src/rust/queries/get_viewer_config.rs +54 -0
  114. package/src/rust/queries/mod.rs +44 -0
  115. package/src/rust/queries/plugin_column_styles.rs +101 -0
  116. package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
  117. package/src/rust/renderer/activate.rs +1 -0
  118. package/src/rust/renderer/limits.rs +9 -4
  119. package/src/rust/renderer/plugin_store.rs +12 -0
  120. package/src/rust/renderer/props.rs +28 -3
  121. package/src/rust/renderer/registry.rs +40 -15
  122. package/src/rust/renderer.rs +703 -60
  123. package/src/rust/session/column_defaults_update.rs +20 -28
  124. package/src/rust/session/drag_drop_update.rs +10 -10
  125. package/src/rust/session/metadata.rs +31 -16
  126. package/src/rust/session/props.rs +15 -6
  127. package/src/rust/session/view_subscription.rs +10 -0
  128. package/src/rust/session.rs +109 -147
  129. package/src/rust/tasks/copy_export.rs +178 -158
  130. package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
  131. package/src/rust/tasks/edit_expression.rs +68 -88
  132. package/src/rust/tasks/eject.rs +25 -22
  133. package/src/rust/tasks/intersection_observer.rs +8 -21
  134. package/src/rust/tasks/mod.rs +19 -21
  135. package/src/rust/tasks/reset_all.rs +98 -0
  136. package/src/rust/tasks/resize_observer.rs +11 -33
  137. package/src/rust/tasks/restore_and_render.rs +128 -90
  138. package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +39 -35
  139. package/src/rust/tasks/send_plugin_config.rs +33 -33
  140. package/src/rust/tasks/update_and_render.rs +75 -49
  141. package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
  142. package/src/rust/tasks/validate_expression.rs +61 -0
  143. package/src/rust/utils/browser/selection.rs +4 -4
  144. package/src/rust/utils/mod.rs +0 -63
  145. package/src/svg/checkbox-checked-icon.svg +1 -1
  146. package/src/svg/checkbox-unchecked-icon.svg +1 -1
  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 +24 -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 +5 -0
  156. package/src/themes/intl/de.css +43 -6
  157. package/src/themes/intl/es.css +43 -6
  158. package/src/themes/intl/fr.css +43 -6
  159. package/src/themes/intl/ja.css +43 -6
  160. package/src/themes/intl/pt.css +43 -6
  161. package/src/themes/intl/zh.css +43 -6
  162. package/src/themes/intl.css +38 -4
  163. package/src/themes/monokai.css +45 -61
  164. package/src/themes/phosphor.css +20 -29
  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/vaporwave.css +40 -74
  169. package/src/ts/bootstrap.ts +14 -3
  170. package/src/ts/column-format.ts +162 -0
  171. package/src/ts/extensions.ts +4 -0
  172. package/src/ts/perspective-viewer.ts +9 -1
  173. package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
  174. package/src/ts/plugin.ts +25 -101
  175. package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
  176. package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
  177. package/src/ts/ts-rs/ViewerConfig.ts +1 -2
  178. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
  179. package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
  180. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
  181. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
  182. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
  183. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  184. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
  185. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  186. package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
  187. package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
  188. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
  189. package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
  190. package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
  191. package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
  192. package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
  193. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
  194. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
  195. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  196. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
  197. package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
  198. package/src/rust/components/number_column_style.rs +0 -491
  199. package/src/rust/config/number_column_style.rs +0 -136
  200. package/src/rust/dragdrop.rs +0 -481
  201. package/src/rust/tasks/plugin_column_styles.rs +0 -98
  202. package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
  203. package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
  204. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
  205. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
  206. package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
  207. package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
  208. package/src/ts/ts-rs/FormatMode.ts +0 -3
  209. package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
  210. package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
  211. package/src/ts/ts-rs/PluginConfig.ts +0 -4
  212. package/src/ts/ts-rs/RoundingMode.ts +0 -3
  213. package/src/ts/ts-rs/RoundingPriority.ts +0 -3
  214. package/src/ts/ts-rs/SignDisplay.ts +0 -3
  215. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
  216. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
  217. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  218. package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
  219. package/src/ts/ts-rs/UseGrouping.ts +0 -3
  220. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline0.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline1.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline2.js +0 -0
  223. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline3.js +0 -0
  224. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline4.js +0 -0
  225. /package/src/rust/{tasks → config}/export_method.rs +0 -0
  226. /package/src/rust/{tasks → queries}/export_app.rs +0 -0
  227. /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
@@ -10,32 +10,11 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use perspective_client::config::{ColumnType, ViewConfig};
13
+ use perspective_client::config::ViewConfig;
14
14
 
15
- use super::HasSession;
16
- use super::plugin_column_styles::can_render_column_styles;
17
15
  use crate::presentation::{ColumnLocator, OpenColumnSettings};
18
16
  use crate::renderer::Renderer;
19
17
  use crate::session::SessionMetadata;
20
- use crate::tasks::{HasPresentation, HasRenderer, PluginColumnStyles};
21
-
22
- /// Returns the column name for a locator, generating a default for new
23
- /// expressions.
24
- pub fn locator_name_or_default(metadata: &SessionMetadata, locator: &ColumnLocator) -> String {
25
- match locator {
26
- ColumnLocator::Table(s) | ColumnLocator::Expression(s) => s.clone(),
27
- ColumnLocator::NewExpression => metadata.make_new_column_name(None),
28
- }
29
- }
30
-
31
- /// Returns the view type for a locator's column, if available.
32
- pub fn locator_view_type(
33
- metadata: &SessionMetadata,
34
- locator: &ColumnLocator,
35
- ) -> Option<ColumnType> {
36
- let name = locator.name().cloned().unwrap_or_default();
37
- metadata.get_column_view_type(name.as_str())
38
- }
39
18
 
40
19
  /// Returns a [`ColumnLocator`] for a given column name, or [`None`] if no
41
20
  /// such column exists.
@@ -57,17 +36,20 @@ pub fn get_column_locator(
57
36
 
58
37
  /// Gets a [`ColumnLocator`] for the current UI's column settings state,
59
38
  /// or [`None`] if it is not currently active.
39
+ ///
40
+ /// Table columns only have a useful sidebar (the Style tab)
41
+ /// when they're in `view_config.columns`.
60
42
  pub fn get_current_column_locator(
61
43
  open_column_settings: &OpenColumnSettings,
62
44
  renderer: &Renderer,
63
45
  view_config: &ViewConfig,
64
- metadata: &SessionMetadata,
46
+ _metadata: &SessionMetadata,
65
47
  ) -> Option<ColumnLocator> {
66
48
  open_column_settings
67
49
  .locator
68
50
  .clone()
69
51
  .filter(|locator| match locator {
70
- ColumnLocator::Table(name) => {
52
+ ColumnLocator::Table(_name) => {
71
53
  locator
72
54
  .name()
73
55
  .map(|name| {
@@ -76,58 +58,20 @@ pub fn get_current_column_locator(
76
58
  .as_ref()
77
59
  .map(|col| col == name)
78
60
  .unwrap_or_default()
79
- }) || view_config.group_by.iter().any(|col| col == name)
80
- || view_config.split_by.iter().any(|col| col == name)
81
- || view_config.filter.iter().any(|col| col.column() == name)
82
- || view_config.sort.iter().any(|col| &col.0 == name)
61
+
62
+ // }) || view_config.group_by.iter().any(|col|
63
+ // col == name) ||
64
+ // view_config.split_by.iter().any(|col| col ==
65
+ // name) ||
66
+ // view_config.filter.iter().any(|col| col.column()
67
+ // == name) ||
68
+ // view_config.sort.iter().any(|col| &col.0 == name)
69
+ // })
70
+ })
83
71
  })
84
72
  .unwrap_or_default()
85
- && can_render_column_styles(renderer, view_config, metadata, name)
86
- .unwrap_or_default()
73
+ && renderer.can_render_column_styles()
87
74
  },
88
75
  _ => true,
89
76
  })
90
77
  }
91
-
92
- /// Trait facade — delegates to standalone functions above.
93
- pub trait ColumnLocatorExt: HasSession {
94
- fn locator_name_or_default(&self, locator: &ColumnLocator) -> String {
95
- locator_name_or_default(&self.session().metadata(), locator)
96
- }
97
-
98
- fn is_locator_active(&self, locator: &ColumnLocator) -> bool {
99
- locator
100
- .name()
101
- .map(|name| self.session().to_props().is_column_active(name))
102
- .unwrap_or_default()
103
- }
104
-
105
- fn locator_view_type(&self, locator: &ColumnLocator) -> Option<ColumnType> {
106
- locator_view_type(&self.session().metadata(), locator)
107
- }
108
-
109
- fn get_column_locator(&self, name: Option<String>) -> Option<ColumnLocator> {
110
- get_column_locator(&self.session().metadata(), name)
111
- }
112
- }
113
-
114
- impl<T: HasSession> ColumnLocatorExt for T {}
115
-
116
- /// Trait facade for `get_current_column_locator`.
117
- pub trait ColumnLocatorCurrentExt:
118
- HasPresentation + HasRenderer + HasSession + PluginColumnStyles
119
- {
120
- fn get_current_column_locator(&self) -> Option<ColumnLocator> {
121
- get_current_column_locator(
122
- &self.presentation().get_open_column_settings(),
123
- self.renderer(),
124
- &self.session().get_view_config(),
125
- &self.session().metadata(),
126
- )
127
- }
128
- }
129
-
130
- impl<T: HasPresentation + HasRenderer + HasSession + PluginColumnStyles> ColumnLocatorCurrentExt
131
- for T
132
- {
133
- }
@@ -0,0 +1,59 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use perspective_client::config::ViewConfigUpdate;
14
+ use perspective_client::{ViewWindow, clone};
15
+ use perspective_js::utils::{ApiError, ApiFuture};
16
+
17
+ use crate::session::Session;
18
+
19
+ /// Get all unique column values for a given column name.
20
+ ///
21
+ /// Use the `.to_csv()` method, as I suspected copying this large string
22
+ /// once was more efficient than copying many smaller strings, and
23
+ /// string copying shows up frequently when doing performance analysis.
24
+ ///
25
+ /// TODO Does not work with expressions yet.
26
+ pub async fn get_column_values(session: &Session, column: String) -> Result<Vec<String>, ApiError> {
27
+ let expressions = Some(session.get_view_config().expressions.clone());
28
+ let config = ViewConfigUpdate {
29
+ group_by: Some(vec![column]),
30
+ columns: Some(vec![]),
31
+ expressions,
32
+ ..ViewConfigUpdate::default()
33
+ };
34
+
35
+ let table = session
36
+ .get_table()
37
+ .ok_or_else(|| ApiError::from("No table set"))?;
38
+ let view = table.view(Some(config.clone())).await?;
39
+ let csv = view.to_csv(ViewWindow::default()).await?;
40
+
41
+ clone!(view);
42
+ ApiFuture::spawn(async move {
43
+ view.delete().await?;
44
+ Ok(())
45
+ });
46
+
47
+ let res = csv
48
+ .lines()
49
+ .map(|val| {
50
+ if val.starts_with('\"') && val.ends_with('\"') {
51
+ (val[1..val.len() - 1]).to_owned()
52
+ } else {
53
+ val.to_owned()
54
+ }
55
+ })
56
+ .skip(2)
57
+ .collect::<Vec<String>>();
58
+ Ok(res)
59
+ }
@@ -16,11 +16,10 @@ use itertools::Itertools;
16
16
  use perspective_client::config::*;
17
17
 
18
18
  use super::is_invalid_columns_column;
19
- use crate::dragdrop::*;
19
+ use crate::presentation::Presentation;
20
20
  use crate::renderer::*;
21
21
  use crate::session::SessionMetadata;
22
22
  use crate::utils::DragTarget;
23
- use crate::*;
24
23
 
25
24
  /// The possible states of a column (row) in the active columns list, including
26
25
  /// the `Option<String>` label type.
@@ -76,10 +75,10 @@ impl<'a> ColumnsIteratorSet<'a> {
76
75
  config: &'a ViewConfig,
77
76
  metadata: &'a SessionMetadata,
78
77
  renderer: &'a Renderer,
79
- dragdrop: &DragDrop,
78
+ presentation: &Presentation,
80
79
  ) -> ColumnsIteratorSet<'a> {
81
- let is_dragover_column = dragdrop.is_dragover(DragTarget::Active);
82
- let named_columns = renderer.metadata().names.clone().unwrap_or_default();
80
+ let is_dragover_column = presentation.is_dragover(DragTarget::Active);
81
+ let named_columns = renderer.metadata().config_column_names.clone();
83
82
  ColumnsIteratorSet {
84
83
  config,
85
84
  renderer,
@@ -92,12 +91,7 @@ impl<'a> ColumnsIteratorSet<'a> {
92
91
  /// Generate an iterator for active columns, which are represented as
93
92
  /// `Option` for dragover and missing columns.
94
93
  pub fn active(&'a self) -> impl Iterator<Item = ActiveColumnState> + 'a {
95
- let min_cols = self
96
- .renderer
97
- .metadata()
98
- .names
99
- .as_ref()
100
- .map_or(0, |x| x.len());
94
+ let min_cols = self.renderer.metadata().config_column_names.len();
101
95
 
102
96
  let last_col = self.config.columns.last().and_then(|x| x.as_ref());
103
97
  let has_blank_tail = last_col.is_none()
@@ -122,7 +116,7 @@ impl<'a> ColumnsIteratorSet<'a> {
122
116
  .position(|x| x.as_ref() == Some(from_column));
123
117
 
124
118
  let is_from_required = from_index
125
- .and_then(|x| self.renderer.metadata().min.map(|y| x < y))
119
+ .and_then(|x| self.renderer.metadata().min_config_columns.map(|y| x < y))
126
120
  .unwrap_or_default();
127
121
 
128
122
  let is_from_swap = from_index
@@ -131,7 +125,7 @@ impl<'a> ColumnsIteratorSet<'a> {
131
125
 
132
126
  let is_swap_invalid = is_invalid_columns_column(
133
127
  self.config,
134
- self.renderer.metadata().min,
128
+ self.renderer.metadata().min_config_columns,
135
129
  from_column,
136
130
  *to_index,
137
131
  );
@@ -254,12 +248,11 @@ impl<'a> ColumnsIteratorSet<'a> {
254
248
  &'a self,
255
249
  values: impl Iterator<Item = &'a String>,
256
250
  ) -> impl Iterator<Item = OrderedColumn<'a>> {
257
- let is_drag_active = maybe! {
251
+ let is_drag_active: Option<bool> = try {
258
252
  let dragover_col = self.is_dragover_column.as_ref();
259
253
  let cols = &self.config.columns;
260
254
  let (_, drag_name) = dragover_col?;
261
- let is_drag_active = cols.iter().flatten().any(|x| x == drag_name);
262
- Some(is_drag_active)
255
+ cols.iter().flatten().any(|x| x == drag_name)
263
256
  };
264
257
 
265
258
  let col_set = self.config.columns.iter().collect::<HashSet<_>>();
@@ -282,11 +275,11 @@ impl<'a> ColumnsIteratorSet<'a> {
282
275
  let dragover_col = self.is_dragover_column.as_ref();
283
276
  let cols = &self.config.columns;
284
277
  let is_active = col_set.contains(&Some(name.to_string())); // cols.iter().flatten().any(|x| x == name);
285
- let is_swap_over = maybe! {
278
+ let is_swap_over: Option<bool> = try {
286
279
  let (drop_index, _) = dragover_col?;
287
280
  let is_swap = self.renderer.metadata().is_swap(*drop_index);
288
281
  let is_over = cols.get(*drop_index)?.as_ref()? == name;
289
- Some(is_swap && is_over && !is_drag_active?)
282
+ is_swap && is_over && !is_drag_active?
290
283
  };
291
284
 
292
285
  if !is_active || is_swap_over.unwrap_or_default() {
@@ -0,0 +1,96 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ //! Pure read-only `View` → bytes/string derivations. `flat = true` means
14
+ //! "ignore the active view config and re-fetch the un-pivoted table view";
15
+ //! `flat = false` means "use the session's bound view".
16
+
17
+ use perspective_client::{View, ViewWindow};
18
+ use perspective_js::utils::{ApiError, ApiResult};
19
+ use wasm_bindgen::JsCast;
20
+
21
+ use crate::session::Session;
22
+
23
+ async fn flat_view(session: &Session, flat: bool) -> ApiResult<View> {
24
+ if flat {
25
+ let table = session
26
+ .get_table()
27
+ .ok_or_else(|| ApiError::from("No table set"))?;
28
+ Ok(table.view(None).await?)
29
+ } else {
30
+ session
31
+ .get_view()
32
+ .ok_or_else(|| ApiError::from("No view created"))
33
+ }
34
+ }
35
+
36
+ pub async fn arrow_as_vec(
37
+ session: &Session,
38
+ flat: bool,
39
+ window: Option<ViewWindow>,
40
+ ) -> Result<Vec<u8>, ApiError> {
41
+ Ok(flat_view(session, flat)
42
+ .await?
43
+ .to_arrow(window.unwrap_or_default())
44
+ .await?
45
+ .into())
46
+ }
47
+
48
+ pub async fn arrow_as_jsvalue(
49
+ session: &Session,
50
+ flat: bool,
51
+ window: Option<ViewWindow>,
52
+ ) -> Result<js_sys::ArrayBuffer, ApiError> {
53
+ let arrow = flat_view(session, flat)
54
+ .await?
55
+ .to_arrow(window.unwrap_or_default())
56
+ .await?;
57
+ Ok(js_sys::Uint8Array::from(&arrow[..])
58
+ .buffer()
59
+ .unchecked_into())
60
+ }
61
+
62
+ pub async fn ndjson_as_jsvalue(
63
+ session: &Session,
64
+ flat: bool,
65
+ window: Option<ViewWindow>,
66
+ ) -> Result<js_sys::JsString, ApiError> {
67
+ let json: String = flat_view(session, flat)
68
+ .await?
69
+ .to_ndjson(window.unwrap_or_default())
70
+ .await?;
71
+
72
+ Ok(json.into())
73
+ }
74
+
75
+ pub async fn json_as_jsvalue(
76
+ session: &Session,
77
+ flat: bool,
78
+ window: Option<ViewWindow>,
79
+ ) -> Result<js_sys::Object, ApiError> {
80
+ let json: String = flat_view(session, flat)
81
+ .await?
82
+ .to_columns_string(window.unwrap_or_default())
83
+ .await?;
84
+
85
+ Ok(js_sys::JSON::parse(&json)?.unchecked_into())
86
+ }
87
+
88
+ pub async fn csv_as_jsvalue(
89
+ session: &Session,
90
+ flat: bool,
91
+ window: Option<ViewWindow>,
92
+ ) -> Result<js_sys::JsString, ApiError> {
93
+ let window = window.unwrap_or_default();
94
+ let csv = flat_view(session, flat).await?.to_csv(window).await;
95
+ Ok(csv.map(js_sys::JsString::from)?)
96
+ }
@@ -0,0 +1,94 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ //! Async dispatch task for per-column numeric stats.
14
+ //!
15
+ //! `resolve_abs_max` is the cache-or-fetch core: hit the
16
+ //! [`Session`] stats cache first, fall back to `View::get_min_max`
17
+ //! for numeric columns on miss, write-through on success.
18
+ //!
19
+ //! `fetch_column_abs_max` is the fire-and-forget pre-warm wrapper used
20
+ //! by the StyleTab on column-settings panel open so the gradient
21
+ //! defaults are ready before the user can trigger any UI update.
22
+
23
+ use perspective_client::View;
24
+ use perspective_client::config::{ColumnType, Scalar};
25
+ use perspective_js::utils::ApiFuture;
26
+
27
+ use crate::session::{Session, SessionMetadata};
28
+
29
+ fn scalar_to_f64(s: &Scalar) -> Option<f64> {
30
+ match s {
31
+ Scalar::Float(x) => Some(*x),
32
+ _ => None,
33
+ }
34
+ }
35
+
36
+ /// Extract `abs_max = max(|min|, |max|)` from a `(Scalar, Scalar)`
37
+ /// `View::get_min_max` result. Returns `None` for non-numeric scalars.
38
+ pub fn min_max_to_abs_max(min: &Scalar, max: &Scalar) -> Option<f64> {
39
+ let min = scalar_to_f64(min)?;
40
+ let max = scalar_to_f64(max)?;
41
+ Some(min.abs().max(max.abs()))
42
+ }
43
+
44
+ /// `true` if the column's view type is `Integer` or `Float`. Used to
45
+ /// skip the `View::get_min_max` round trip for non-numeric columns
46
+ /// (which would either error or return non-`Float` scalars).
47
+ pub fn is_numeric_column(metadata: &SessionMetadata, col_name: &str) -> bool {
48
+ matches!(
49
+ metadata.get_column_view_type(col_name),
50
+ Some(ColumnType::Integer | ColumnType::Float)
51
+ )
52
+ }
53
+
54
+ /// Resolve the cached `abs_max` for `col_name`; on miss, await
55
+ /// `View::get_min_max` for numeric columns, populate the cache, and
56
+ /// return the value. Non-numeric columns and missing views return
57
+ /// `None` without a fetch.
58
+ pub async fn resolve_abs_max(
59
+ session: &Session,
60
+ metadata: &SessionMetadata,
61
+ view: Option<&View>,
62
+ col_name: &str,
63
+ ) -> Option<f64> {
64
+ if let Some(stats) = session.get_column_stats(col_name)
65
+ && let Some(v) = stats.abs_max
66
+ {
67
+ return Some(v);
68
+ }
69
+ if !is_numeric_column(metadata, col_name) {
70
+ return None;
71
+ }
72
+
73
+ let view = view?;
74
+ let (min, max) = view.get_min_max(col_name.to_string()).await.ok()?;
75
+ let v = min_max_to_abs_max(&min, &max)?;
76
+ session.set_column_abs_max(col_name.to_string(), v);
77
+ Some(v)
78
+ }
79
+
80
+ /// Fire-and-forget pre-warm of the stats cache. Spawns
81
+ /// [`resolve_abs_max`] on the active session's view; the result is
82
+ /// discarded — the side effect of populating
83
+ /// [`Session::set_column_abs_max`] is the goal. Used by the
84
+ /// `StyleTab`'s `use_effect_with` to warm gradient defaults before
85
+ /// the user can trigger a `plugin.restore`.
86
+ pub fn fetch_column_abs_max(session: &Session, column_name: String) {
87
+ let session = session.clone();
88
+ ApiFuture::spawn(async move {
89
+ let metadata = session.metadata().clone();
90
+ let view = session.get_view();
91
+ resolve_abs_max(&session, &metadata, view.as_ref(), &column_name).await;
92
+ Ok::<_, perspective_js::utils::ApiError>(())
93
+ });
94
+ }
@@ -0,0 +1,54 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use crate::config::*;
14
+ use crate::presentation::Presentation;
15
+ use crate::renderer::Renderer;
16
+ use crate::session::Session;
17
+ use crate::*;
18
+
19
+ /// Compose the current [`ViewerConfig`] from across the engine handles.
20
+ ///
21
+ /// The `view_config` field is read from the snapshot the currently-bound
22
+ /// `View` was constructed from, so it is consistent with what the active
23
+ /// plugin is rendering. Falls back to the live session config if no
24
+ /// `View` exists yet.
25
+ pub async fn get_viewer_config(
26
+ session: &Session,
27
+ renderer: &Renderer,
28
+ presentation: &Presentation,
29
+ ) -> ApiResult<ViewerConfig> {
30
+ let version = config::API_VERSION.to_string();
31
+ let view_config = if let Some(rendered) = session.get_rendered_view_config() {
32
+ (*rendered).clone()
33
+ } else {
34
+ session.get_view_config().clone()
35
+ };
36
+ let settings = presentation.is_settings_open();
37
+ let plugin = renderer.metadata().name.clone();
38
+ let plugin_config = renderer.get_plugin_config();
39
+ let theme = presentation.get_selected_theme_name().await;
40
+ let title = session.get_title();
41
+ let table = session.get_table().map(|x| x.get_name().to_owned());
42
+ let columns_config = renderer.all_columns_configs();
43
+ Ok(ViewerConfig {
44
+ version,
45
+ plugin,
46
+ title,
47
+ plugin_config,
48
+ columns_config,
49
+ settings,
50
+ table,
51
+ view_config,
52
+ theme,
53
+ })
54
+ }
@@ -0,0 +1,44 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ //! Read-only async utilities across the engine handles.
14
+ //!
15
+ //! Each function in this module reads from one or more of [`Session`],
16
+ //! [`Renderer`], [`Presentation`] and produces a derived value without
17
+ //! mutating engine state. Compare [`crate::tasks`], which holds the
18
+ //! state-mutating async business logic dispatched from user actions.
19
+ //!
20
+ //! [`Session`]: crate::session::Session
21
+ //! [`Renderer`]: crate::renderer::Renderer
22
+ //! [`Presentation`]: crate::presentation::Presentation
23
+
24
+ mod column_locator;
25
+ mod column_values;
26
+ mod columns_iter_set;
27
+ pub mod export_app;
28
+ mod exports;
29
+ mod fetch_column_stats;
30
+ mod get_viewer_config;
31
+ mod is_invalid_drop;
32
+ mod plugin_column_styles;
33
+ mod validate_expression;
34
+
35
+ pub use self::column_locator::*;
36
+ pub use self::column_values::*;
37
+ pub use self::columns_iter_set::*;
38
+ pub use self::export_app::*;
39
+ pub use self::exports::*;
40
+ pub use self::fetch_column_stats::*;
41
+ pub use self::get_viewer_config::*;
42
+ pub use self::is_invalid_drop::*;
43
+ pub use self::plugin_column_styles::*;
44
+ pub use self::validate_expression::*;
@@ -0,0 +1,101 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use itertools::Itertools;
14
+ use perspective_client::config::ViewConfig;
15
+ use perspective_js::utils::{ApiResult, JsValueSerdeExt};
16
+ use serde::Serialize;
17
+
18
+ use crate::config::ColumnConfigSchema;
19
+ use crate::renderer::Renderer;
20
+ use crate::session::SessionMetadata;
21
+
22
+ /// Stats payload passed to `plugin.column_config_schema` as the
23
+ /// `column_stats` arg. The caller (e.g. the StyleTab) owns the value —
24
+ /// typically populated by `fetch_column_abs_max` resolving into a
25
+ /// component-local `use_state`. Missing when no fetch has resolved yet.
26
+ #[derive(Default, Serialize)]
27
+ struct ColumnStats {
28
+ #[serde(skip_serializing_if = "Option::is_none")]
29
+ abs_max: Option<f64>,
30
+ }
31
+
32
+ /// Queries the active plugin for its plugin-scoped
33
+ /// [`ColumnConfigSchema`]. Mirrors [`get_column_config_schema`] but
34
+ /// drops the column-specific args — the schema describes the shape
35
+ /// of the plugin's own config bucket on the renderer.
36
+ pub fn get_plugin_config_schema(
37
+ renderer: &Renderer,
38
+ view_config: &ViewConfig,
39
+ ) -> ApiResult<ColumnConfigSchema> {
40
+ let plugin = renderer.get_active_plugin()?;
41
+ let view_config_js =
42
+ wasm_bindgen::JsValue::from_serde_ext(view_config).unwrap_or(wasm_bindgen::JsValue::NULL);
43
+ let raw = plugin._plugin_config_schema(&view_config_js)?;
44
+ serde_wasm_bindgen::from_value(raw).map_err(|e| e.into())
45
+ }
46
+
47
+ /// Queries the active plugin for the per-column [`ColumnConfigSchema`].
48
+ ///
49
+ /// `current_value` is the column's existing flat JSON config (if any);
50
+ /// plugins use it to dynamically gate fields based on prior state.
51
+ /// `abs_max` is the caller-owned numeric stat (typically a Yew
52
+ /// `use_state` populated by an in-flight `fetch_column_abs_max` task);
53
+ /// `None` means the fetch has not yet resolved and gradient defaults
54
+ /// fall back to 0.
55
+ pub fn get_column_config_schema(
56
+ renderer: &Renderer,
57
+ view_config: &ViewConfig,
58
+ metadata: &SessionMetadata,
59
+ column_name: &str,
60
+ current_value: Option<&serde_json::Map<String, serde_json::Value>>,
61
+ abs_max: Option<f64>,
62
+ ) -> ApiResult<ColumnConfigSchema> {
63
+ let plugin = renderer.get_active_plugin()?;
64
+ let plugin_config = renderer.metadata();
65
+ let names = &plugin_config.config_column_names;
66
+ let group = view_config
67
+ .columns
68
+ .iter()
69
+ .find_position(|maybe_s| maybe_s.as_deref() == Some(column_name))
70
+ .and_then(|(idx, _)| names.get(idx))
71
+ .map(|s| s.as_str());
72
+
73
+ let view_type = if let Some(x) = metadata.get_column_view_type(column_name) {
74
+ x
75
+ } else {
76
+ return Ok(ColumnConfigSchema { fields: vec![] });
77
+ };
78
+
79
+ // Route through serde_json so maps serialize as plain JS objects
80
+ // rather than `Map` instances — plugin code accesses these with
81
+ // property syntax, not `.get()`.
82
+ let current_js = wasm_bindgen::JsValue::from_serde_ext(&current_value)
83
+ .unwrap_or(wasm_bindgen::JsValue::NULL);
84
+ let view_config_js =
85
+ wasm_bindgen::JsValue::from_serde_ext(view_config).unwrap_or(wasm_bindgen::JsValue::NULL);
86
+
87
+ let stats = ColumnStats { abs_max };
88
+ let stats_js =
89
+ wasm_bindgen::JsValue::from_serde_ext(&stats).unwrap_or(wasm_bindgen::JsValue::NULL);
90
+
91
+ let raw = plugin._column_config_schema(
92
+ &view_type.to_string(),
93
+ group,
94
+ column_name,
95
+ &current_js,
96
+ &view_config_js,
97
+ &stats_js,
98
+ )?;
99
+
100
+ serde_wasm_bindgen::from_value(raw).map_err(|e| e.into())
101
+ }