@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
@@ -14,22 +14,24 @@ use std::rc::Rc;
14
14
 
15
15
  use futures::channel::oneshot::*;
16
16
  use perspective_js::utils::*;
17
+ use wasm_bindgen::JsCast;
17
18
  use wasm_bindgen::prelude::*;
19
+ use web_sys::{FocusEvent, KeyboardEvent};
18
20
  use yew::prelude::*;
19
21
 
20
22
  use super::containers::split_panel::SplitPanel;
21
23
  use super::font_loader::{FontLoader, FontLoaderProps, FontLoaderStatus};
22
- use super::form::debug::DebugPanel;
23
24
  use super::style::{LocalStyle, StyleProvider};
24
25
  use crate::components::column_settings_sidebar::ColumnSettingsPanel;
25
26
  use crate::components::main_panel::MainPanel;
26
- use crate::components::settings_panel::SettingsPanel;
27
+ use crate::components::settings_panel::{SelectedTab, SettingsPanel};
27
28
  use crate::config::*;
28
29
  use crate::css;
29
- use crate::custom_events::CustomEvents;
30
- use crate::dragdrop::{DragDropProps, *};
31
30
  use crate::js::JsPerspectiveViewerPlugin;
32
- use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation, PresentationProps};
31
+ use crate::presentation::{
32
+ ColumnLocator, ColumnSettingsTab, DragDropProps, Presentation, PresentationProps,
33
+ };
34
+ use crate::queries::*;
33
35
  use crate::renderer::{RendererProps, *};
34
36
  use crate::session::{SessionProps, *};
35
37
  use crate::tasks::*;
@@ -41,8 +43,6 @@ pub struct PerspectiveViewerProps {
41
43
  pub elem: web_sys::HtmlElement,
42
44
 
43
45
  /// State
44
- pub custom_events: CustomEvents,
45
- pub dragdrop: DragDrop,
46
46
  pub session: Session,
47
47
  pub renderer: Renderer,
48
48
  pub presentation: Presentation,
@@ -54,44 +54,6 @@ impl PartialEq for PerspectiveViewerProps {
54
54
  }
55
55
  }
56
56
 
57
- impl HasCustomEvents for PerspectiveViewerProps {
58
- fn custom_events(&self) -> &CustomEvents {
59
- &self.custom_events
60
- }
61
- }
62
-
63
- impl HasDragDrop for PerspectiveViewerProps {
64
- fn dragdrop(&self) -> &DragDrop {
65
- &self.dragdrop
66
- }
67
- }
68
-
69
- impl HasPresentation for PerspectiveViewerProps {
70
- fn presentation(&self) -> &Presentation {
71
- &self.presentation
72
- }
73
- }
74
-
75
- impl HasRenderer for PerspectiveViewerProps {
76
- fn renderer(&self) -> &Renderer {
77
- &self.renderer
78
- }
79
- }
80
-
81
- impl HasSession for PerspectiveViewerProps {
82
- fn session(&self) -> &Session {
83
- &self.session
84
- }
85
- }
86
-
87
- impl StateProvider for PerspectiveViewerProps {
88
- type State = PerspectiveViewerProps;
89
-
90
- fn clone_state(&self) -> Self::State {
91
- self.clone()
92
- }
93
- }
94
-
95
57
  #[derive(Debug)]
96
58
  pub enum PerspectiveViewerMsg {
97
59
  ColumnSettingsPanelSizeUpdate(Option<i32>),
@@ -105,6 +67,8 @@ pub enum PerspectiveViewerMsg {
105
67
  Reset(bool, Option<Sender<()>>),
106
68
  Resize,
107
69
  SettingsPanelSizeUpdate(Option<i32>),
70
+ SettingsPanelTabChanged(SelectedTab),
71
+ SettingsPanelAutoWidth(f64),
108
72
  ToggleDebug,
109
73
  ToggleSettingsComplete(SettingsUpdate, Sender<()>),
110
74
  ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
@@ -142,8 +106,11 @@ pub struct PerspectiveViewer {
142
106
  on_close_column_settings: Callback<()>,
143
107
  on_rendered: Option<Sender<()>>,
144
108
  on_resize: Rc<PubSub<()>>,
109
+ on_settings_panel_dimensions_reset: Rc<PubSub<()>>,
145
110
  settings_open: bool,
146
111
  settings_panel_width_override: Option<i32>,
112
+ settings_panel_selected_tab: SelectedTab,
113
+ settings_panel_auto_width: f64,
147
114
 
148
115
  /// Value-semantic state snapshots (Step 4 scaffold).
149
116
  /// Populated by `UpdateSession` / `UpdateRenderer` / `UpdatePresentation` /
@@ -156,6 +123,70 @@ pub struct PerspectiveViewer {
156
123
  /// Counts in-flight renders (incremented on `view_config_changed`,
157
124
  /// decremented on `view_created`). Threaded to `StatusIndicator`.
158
125
  update_count: u32,
126
+
127
+ /// Window listeners that toggle the `.shift-active` class on the host
128
+ /// element while the Shift key is held, making Shift-modified affordances
129
+ /// (e.g. inactive column add, active column remove, status-bar reset)
130
+ /// visually discoverable. Stored so the closures outlive `create`.
131
+ _shift_listeners: ShiftListeners,
132
+ }
133
+
134
+ struct ShiftListeners {
135
+ elem: web_sys::HtmlElement,
136
+ keydown: Closure<dyn FnMut(KeyboardEvent)>,
137
+ keyup: Closure<dyn FnMut(KeyboardEvent)>,
138
+ blur: Closure<dyn FnMut(FocusEvent)>,
139
+ }
140
+
141
+ impl Drop for ShiftListeners {
142
+ fn drop(&mut self) {
143
+ let win = global::window();
144
+ let _ = win
145
+ .remove_event_listener_with_callback("keydown", self.keydown.as_ref().unchecked_ref());
146
+ let _ =
147
+ win.remove_event_listener_with_callback("keyup", self.keyup.as_ref().unchecked_ref());
148
+ let _ = win.remove_event_listener_with_callback("blur", self.blur.as_ref().unchecked_ref());
149
+ let _ = self.elem.class_list().remove_1("shift-active");
150
+ }
151
+ }
152
+
153
+ fn install_shift_listeners(elem: web_sys::HtmlElement) -> ShiftListeners {
154
+ let keydown = {
155
+ let elem = elem.clone();
156
+ Closure::wrap(Box::new(move |event: KeyboardEvent| {
157
+ if event.key() == "Shift" {
158
+ let _ = elem.class_list().add_1("shift-active");
159
+ }
160
+ }) as Box<dyn FnMut(KeyboardEvent)>)
161
+ };
162
+
163
+ let keyup = {
164
+ let elem = elem.clone();
165
+ Closure::wrap(Box::new(move |event: KeyboardEvent| {
166
+ if event.key() == "Shift" {
167
+ let _ = elem.class_list().remove_1("shift-active");
168
+ }
169
+ }) as Box<dyn FnMut(KeyboardEvent)>)
170
+ };
171
+
172
+ let blur = {
173
+ let elem = elem.clone();
174
+ Closure::wrap(Box::new(move |_: FocusEvent| {
175
+ let _ = elem.class_list().remove_1("shift-active");
176
+ }) as Box<dyn FnMut(FocusEvent)>)
177
+ };
178
+
179
+ let win = global::window();
180
+ let _ = win.add_event_listener_with_callback("keydown", keydown.as_ref().unchecked_ref());
181
+ let _ = win.add_event_listener_with_callback("keyup", keyup.as_ref().unchecked_ref());
182
+ let _ = win.add_event_listener_with_callback("blur", blur.as_ref().unchecked_ref());
183
+
184
+ ShiftListeners {
185
+ elem,
186
+ keydown,
187
+ keyup,
188
+ blur,
189
+ }
159
190
  }
160
191
 
161
192
  impl Component for PerspectiveViewer {
@@ -195,6 +226,8 @@ impl Component for PerspectiveViewer {
195
226
  });
196
227
  }
197
228
 
229
+ let shift_listeners = install_shift_listeners(elem);
230
+
198
231
  Self {
199
232
  _subscriptions: subscriptions,
200
233
  column_settings_panel_width_override: None,
@@ -203,13 +236,17 @@ impl Component for PerspectiveViewer {
203
236
  on_close_column_settings,
204
237
  on_rendered: None,
205
238
  on_resize: Default::default(),
239
+ on_settings_panel_dimensions_reset: Default::default(),
206
240
  settings_open: false,
207
241
  settings_panel_width_override: None,
242
+ settings_panel_selected_tab: SelectedTab::default(),
243
+ settings_panel_auto_width: 0.0,
208
244
  session_props,
209
245
  renderer_props,
210
246
  presentation_props,
211
247
  dragdrop_props: DragDropProps::default(),
212
248
  update_count: 0,
249
+ _shift_listeners: shift_listeners,
213
250
  }
214
251
  }
215
252
 
@@ -221,43 +258,13 @@ impl Component for PerspectiveViewer {
221
258
  false
222
259
  },
223
260
  Reset(all, sender) => {
224
- ctx.props().presentation.set_open_column_settings(None);
225
- clone!(
226
- ctx.props().renderer,
227
- ctx.props().session,
228
- ctx.props().presentation
261
+ reset_all(
262
+ &ctx.props().session,
263
+ &ctx.props().renderer,
264
+ &ctx.props().presentation,
265
+ all,
266
+ sender,
229
267
  );
230
-
231
- ApiFuture::spawn(async move {
232
- session
233
- .reset(ResetOptions {
234
- config: true,
235
- expressions: all,
236
- ..ResetOptions::default()
237
- })
238
- .await?;
239
- let columns_config = if all {
240
- presentation.reset_columns_configs();
241
- None
242
- } else {
243
- Some(presentation.all_columns_configs())
244
- };
245
-
246
- renderer.reset(columns_config.as_ref()).await?;
247
- presentation.reset_available_themes(None).await;
248
- if all {
249
- presentation.reset_theme().await?;
250
- }
251
-
252
- let result = renderer.draw(session.validate().await?.create_view()).await;
253
- if let Some(sender) = sender {
254
- sender.send(()).unwrap();
255
- }
256
-
257
- renderer.reset_changed.emit(());
258
- result
259
- });
260
-
261
268
  false
262
269
  },
263
270
  ToggleSettingsInit(Some(SettingsUpdate::Missing), None) => false,
@@ -335,6 +342,10 @@ impl Component for PerspectiveViewer {
335
342
  ctx.props()
336
343
  .presentation
337
344
  .set_open_column_settings(Some(open_column_settings));
345
+
346
+ if locator.is_some() {
347
+ self.settings_panel_selected_tab = SelectedTab::Query;
348
+ }
338
349
  }
339
350
 
340
351
  if let Some(sender) = sender {
@@ -349,7 +360,22 @@ impl Component for PerspectiveViewer {
349
360
  },
350
361
  SettingsPanelSizeUpdate(None) => {
351
362
  self.settings_panel_width_override = None;
352
- false
363
+ self.settings_panel_auto_width = 0.0;
364
+ self.on_settings_panel_dimensions_reset.emit(());
365
+ true
366
+ },
367
+ SettingsPanelTabChanged(tab) => {
368
+ let changed = tab != self.settings_panel_selected_tab;
369
+ self.settings_panel_selected_tab = tab;
370
+ changed
371
+ },
372
+ SettingsPanelAutoWidth(w) => {
373
+ if w > self.settings_panel_auto_width {
374
+ self.settings_panel_auto_width = w;
375
+ true
376
+ } else {
377
+ false
378
+ }
353
379
  },
354
380
  ColumnSettingsPanelSizeUpdate(Some(x)) => {
355
381
  self.column_settings_panel_width_override = Some(x);
@@ -449,8 +475,6 @@ impl Component for PerspectiveViewer {
449
475
 
450
476
  fn view(&self, ctx: &Context<Self>) -> Html {
451
477
  let Self::Properties {
452
- custom_events,
453
- dragdrop,
454
478
  presentation,
455
479
  renderer,
456
480
  session,
@@ -470,7 +494,7 @@ impl Component for PerspectiveViewer {
470
494
  }
471
495
 
472
496
  let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
473
- locator: Some(c),
497
+ locator: c,
474
498
  sender: None,
475
499
  toggle: true,
476
500
  });
@@ -496,17 +520,13 @@ impl Component for PerspectiveViewer {
496
520
  let plugin_name = self.renderer_props.plugin_name.clone();
497
521
  let available_plugins = self.renderer_props.available_plugins.clone();
498
522
  let has_table = self.session_props.has_table.clone();
499
- let named_column_count = self
500
- .renderer_props
501
- .requirements
502
- .names
503
- .as_ref()
504
- .map(|n| n.len())
505
- .unwrap_or(0);
523
+ let named_column_count = self.renderer_props.config.config_column_names.len();
506
524
 
507
525
  let view_config = self.session_props.config.clone();
508
526
  let drag_column = self.dragdrop_props.column.clone();
509
527
  let metadata = self.session_props.metadata.clone();
528
+ let on_select_tab = ctx.link().callback(SettingsPanelTabChanged);
529
+ let on_auto_width = ctx.link().callback(SettingsPanelAutoWidth);
510
530
  let settings_panel = html! {
511
531
  if is_settings_open {
512
532
  <SettingsPanel
@@ -520,11 +540,16 @@ impl Component for PerspectiveViewer {
520
540
  {has_table}
521
541
  {named_column_count}
522
542
  {view_config}
543
+ plugin_config={self.renderer_props.plugin_config.clone()}
523
544
  {drag_column}
524
545
  metadata={metadata.clone()}
525
546
  open_column_settings={self.presentation_props.open_column_settings.clone()}
526
547
  selected_theme={self.presentation_props.selected_theme.clone()}
527
- {dragdrop}
548
+ selected_tab={self.settings_panel_selected_tab}
549
+ auto_width={self.settings_panel_auto_width}
550
+ on_dimensions_reset={&self.on_settings_panel_dimensions_reset}
551
+ {on_select_tab}
552
+ {on_auto_width}
528
553
  {presentation}
529
554
  {renderer}
530
555
  {session}
@@ -552,8 +577,8 @@ impl Component for PerspectiveViewer {
552
577
  plugin_name={self.renderer_props.plugin_name.clone()}
553
578
  {metadata}
554
579
  view_config={self.session_props.config.clone()}
580
+ column_stats={self.session_props.column_stats.clone()}
555
581
  selected_theme={self.presentation_props.selected_theme.clone()}
556
- {custom_events}
557
582
  {presentation}
558
583
  {renderer}
559
584
  {session}
@@ -564,43 +589,23 @@ impl Component for PerspectiveViewer {
564
589
  };
565
590
 
566
591
  let on_reset = ctx.link().callback(|all| Reset(all, None));
567
- let render_limits = self.renderer_props.render_limits;
568
- let has_table = self.session_props.has_table.clone();
569
- let is_errored = self.session_props.error.is_some();
570
- let stats = self.session_props.stats.clone();
571
- let update_count = self.update_count;
572
- let error = self.session_props.error.clone();
573
592
  let is_settings_open = self.settings_open
574
593
  && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
575
- let title = self.session_props.title.clone();
576
- let selected_theme = self.presentation_props.selected_theme.clone();
577
- let available_themes = self.presentation_props.available_themes.clone();
578
594
  let main_panel = html! {
579
595
  <MainPanel
580
596
  {on_settings}
581
597
  {on_reset}
582
- {render_limits}
583
- {has_table}
584
- {is_errored}
585
- {stats}
586
- {update_count}
587
- {error}
598
+ session_props={self.session_props.clone()}
599
+ renderer_props={self.renderer_props.clone()}
600
+ presentation_props={self.presentation_props.clone()}
588
601
  {is_settings_open}
589
- {title}
590
- {selected_theme}
591
- {available_themes}
592
- is_workspace={self.presentation_props.is_workspace}
593
- {custom_events}
602
+ update_count={self.update_count}
594
603
  {presentation}
595
604
  {renderer}
596
605
  {session}
597
606
  />
598
607
  };
599
608
 
600
- let debug_panel = html! {
601
- if self.debug_open { <DebugPanel {presentation} {renderer} {session} /> }
602
- };
603
-
604
609
  html! {
605
610
  <StyleProvider root={ctx.props().elem.clone()}>
606
611
  <LocalStyle href={css!("viewer")} />
@@ -612,10 +617,16 @@ impl Component for PerspectiveViewer {
612
617
  skip_empty=true
613
618
  initial_size={self.settings_panel_width_override}
614
619
  on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
615
- on_resize={on_split_panel_resize.clone()}
616
- on_resize_finished={ctx.props().render_callback()}
620
+ on_resize={{
621
+ let size_cb = on_split_panel_resize.clone();
622
+ let resize_cb = resize_callback(&ctx.props().session, &ctx.props().renderer);
623
+ move |x| {
624
+ size_cb.emit(x);
625
+ resize_cb.emit(());
626
+ }
627
+ }}
628
+ on_resize_finished={resize_callback(&ctx.props().session, &ctx.props().renderer)}
617
629
  >
618
- { debug_panel }
619
630
  { settings_panel }
620
631
  <div id="main_column_container">
621
632
  { main_panel }
@@ -735,17 +746,43 @@ fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
735
746
  .view_created
736
747
  .add_listener(ctx.link().callback(|_| DecrementUpdateCount));
737
748
 
738
- vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7]
749
+ // Stats fetch resolution (populates session.column_stats) triggers
750
+ // a fresh `SessionProps` so `column_stats` reaches downstream
751
+ // components and the StyleTab re-queries the schema with the
752
+ // new value.
753
+ let sub8 = s.column_stats_changed.add_notify_listener(&cb);
754
+
755
+ vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8]
739
756
  };
740
757
 
741
758
  let renderer_props_sub = {
742
759
  let renderer = ctx.props().renderer.clone();
743
760
  let cb_plugin = ctx.link().callback({
761
+ let renderer = renderer.clone();
744
762
  move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
745
763
  });
746
764
 
765
+ // Re-snapshot RendererProps when the plugin_config bucket
766
+ // changes (in-tab edit via `send_plugin_config`, JSON paste via
767
+ // `restore_and_render`, full clear via `reset_all` with
768
+ // `all=true`). Without this, `RendererProps.plugin_config`
769
+ // would stay frozen at its construct-time value and `PluginTab`
770
+ // would render stale.
771
+ let cb_plugin_config = ctx.link().callback({
772
+ let renderer = renderer.clone();
773
+ move |_: serde_json::Map<String, serde_json::Value>| {
774
+ UpdateRenderer(Box::new(renderer.to_props(None)))
775
+ }
776
+ });
777
+
747
778
  let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
748
- vec![sub1]
779
+ let sub2 = ctx
780
+ .props()
781
+ .renderer
782
+ .plugin_config_changed
783
+ .add_listener(cb_plugin_config);
784
+
785
+ vec![sub1, sub2]
749
786
  };
750
787
 
751
788
  let presentation_props_sub = {
@@ -779,7 +816,7 @@ fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
779
816
  let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
780
817
  let sub1 = ctx
781
818
  .props()
782
- .dragdrop
819
+ .presentation
783
820
  .drop_received
784
821
  .add_notify_listener(&cb_clear);
785
822
 
@@ -858,19 +895,19 @@ fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
858
895
  .borrow_mut() = Some(cb);
859
896
  }
860
897
 
861
- // DragDrop: on_dragstart
898
+ // Drag/drop: on_dragstart (post-merge: lives on Presentation)
862
899
  {
863
- let dragdrop = ctx.props().dragdrop.clone();
864
- let cb = ctx
865
- .link()
866
- .callback(move |_: DragEffect| UpdateDragDrop(Box::new(dragdrop.to_props())));
900
+ let presentation = ctx.props().presentation.clone();
901
+ let cb = ctx.link().callback(move |_: DragEffect| {
902
+ UpdateDragDrop(Box::new(presentation.drag_drop_props()))
903
+ });
867
904
 
868
- *ctx.props().dragdrop.on_dragstart.borrow_mut() = Some(cb);
905
+ *ctx.props().presentation.on_dragstart.borrow_mut() = Some(cb);
869
906
  }
870
907
 
871
- // DragDrop: on_dragend
908
+ // Drag/drop: on_dragend
872
909
  {
873
910
  let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
874
- *ctx.props().dragdrop.on_dragend.borrow_mut() = Some(cb);
911
+ *ctx.props().presentation.on_dragend.borrow_mut() = Some(cb);
875
912
  }
876
913
  }
@@ -0,0 +1,195 @@
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 std::collections::HashSet;
14
+
15
+ use serde::{Deserialize, Serialize};
16
+ use serde_json::Value;
17
+
18
+ use super::{KeyValueOpts, NumberSeriesStyleDefaultConfig};
19
+
20
+ /// The full schema for one column at one point in time. Plugins may return
21
+ /// different schemas for the same column based on the column's current
22
+ /// stored value (e.g. to hide dependent fields), so this is re-queried on
23
+ /// every field update.
24
+ ///
25
+ /// Each entry is a [`ControlSpec`]. Primitive variants carry their own
26
+ /// `key` (JSON storage key) inline; the sidebar UI label is supplied by
27
+ /// CSS via `--psp-label--<key>--content`. Composite variants render a
28
+ /// self-contained Yew component that supplies its own labels and owns a
29
+ /// fixed key namespace via [`ControlSpec::serialized_keys`].
30
+ #[derive(Clone, Debug, Default, Deserialize, Serialize)]
31
+ pub struct ColumnConfigSchema {
32
+ pub fields: Vec<ControlSpec>,
33
+ }
34
+
35
+ impl ColumnConfigSchema {
36
+ /// Union of every JSON key any control in this schema knows how to
37
+ /// read or write. Used to build the schema-filtered view of
38
+ /// `columns_config` passed to `plugin.restore()` — keys not in this
39
+ /// set are "ghost" state from a different plugin and stay invisible
40
+ /// to the active one.
41
+ pub fn active_keys(&self) -> HashSet<String> {
42
+ let mut out = HashSet::new();
43
+ for spec in &self.fields {
44
+ for k in spec.serialized_keys() {
45
+ out.insert(k.to_string());
46
+ }
47
+ }
48
+ out
49
+ }
50
+ }
51
+
52
+ /// Discriminated union of widget kinds the viewer can render. Composite
53
+ /// variants wrap an existing rich Yew component and carry only the
54
+ /// component's `*DefaultConfig`. Primitive variants render generic scalar
55
+ /// widgets and carry their own `key` inline; the visible label is
56
+ /// resolved at CSS time via `--psp-label--<key>--content`.
57
+ #[derive(Clone, Debug, Deserialize, Serialize)]
58
+ #[serde(tag = "kind")]
59
+ pub enum ControlSpec {
60
+ Enum {
61
+ key: String,
62
+ variants: Vec<EnumVariant>,
63
+ default: String,
64
+ },
65
+ Bool {
66
+ key: String,
67
+ default: bool,
68
+ },
69
+ Number {
70
+ key: String,
71
+ default: f64,
72
+
73
+ /// If `true`, always serialize this values even if it is the default.
74
+ #[serde(default, skip_serializing_if = "Option::is_none")]
75
+ include: Option<bool>,
76
+
77
+ #[serde(default, skip_serializing_if = "Option::is_none")]
78
+ min: Option<f64>,
79
+
80
+ #[serde(default, skip_serializing_if = "Option::is_none")]
81
+ max: Option<f64>,
82
+
83
+ #[serde(default, skip_serializing_if = "Option::is_none")]
84
+ step: Option<f64>,
85
+ },
86
+ String {
87
+ key: String,
88
+ default: String,
89
+ #[serde(default, skip_serializing_if = "Option::is_none")]
90
+ placeholder: Option<String>,
91
+ },
92
+ Color {
93
+ key: String,
94
+ default: String,
95
+ },
96
+ /// Paired pos/neg color picker rendered as a single horizontal
97
+ /// gradient/range bar. Used to expose the existing
98
+ /// [`crate::components::form::color_range_selector::ColorRangeSelector`]
99
+ /// widget at primitive granularity. Owns two top-level keys
100
+ /// (`key_pos` + `key_neg`); the visible label is derived from
101
+ /// `key_pos`.
102
+ ColorRange {
103
+ key_pos: String,
104
+ key_neg: String,
105
+ default_pos: String,
106
+ default_neg: String,
107
+ /// When `true`, the bar renders as a continuous gradient
108
+ /// (e.g. for `gradient` color modes); when `false`, the bar
109
+ /// renders as a hard pos/neg split. Currently only changes the
110
+ /// visual; pos/neg semantics are unchanged.
111
+ #[serde(default)]
112
+ is_gradient: bool,
113
+ },
114
+
115
+ /// Residual format-only widget for datetime columns — owns
116
+ /// `date_format` only. Pair with primitive `Enum` and `Color` fields
117
+ /// for `datetime_color_mode` + `color` to fully decompose datetime
118
+ /// styling.
119
+ DatetimeFormat,
120
+ /// Residual format-only widget for string columns — owns `format`
121
+ /// only. Pair with primitive `Enum` and `Color` fields for
122
+ /// `string_color_mode` + `color` to fully decompose string styling.
123
+ StringFormat,
124
+ NumberSeriesStyle {
125
+ default: NumberSeriesStyleDefaultConfig,
126
+ },
127
+ Symbols {
128
+ default: KeyValueOpts,
129
+ },
130
+ NumberFormat,
131
+ AggregateDepth,
132
+ }
133
+
134
+ #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
135
+ pub struct EnumVariant {
136
+ pub value: String,
137
+ #[serde(default, skip_serializing_if = "Option::is_none")]
138
+ pub label: Option<String>,
139
+ }
140
+
141
+ impl ControlSpec {
142
+ /// Top-level JSON keys this control owns when its value is serialized
143
+ /// into a column's config map. For primitives this is just `[key]`;
144
+ /// for composites it's the set of fields the wrapped sub-struct
145
+ /// flattens. Used by [`ColumnConfigSchema::active_keys`] to filter the
146
+ /// `columns_config` blob passed to `plugin.restore()`.
147
+ pub fn serialized_keys(&self) -> Vec<&str> {
148
+ match self {
149
+ ControlSpec::DatetimeFormat => vec!["date_format"],
150
+ ControlSpec::StringFormat => vec!["format"],
151
+ ControlSpec::NumberSeriesStyle { .. } => vec!["chart_type", "stack"],
152
+ ControlSpec::Symbols { .. } => vec!["symbols"],
153
+ ControlSpec::NumberFormat => vec!["number_format"],
154
+ ControlSpec::AggregateDepth => vec!["aggregate_depth"],
155
+ ControlSpec::ColorRange {
156
+ key_pos, key_neg, ..
157
+ } => vec![key_pos.as_str(), key_neg.as_str()],
158
+ ControlSpec::Enum { key, .. }
159
+ | ControlSpec::Bool { key, .. }
160
+ | ControlSpec::Number { key, .. }
161
+ | ControlSpec::String { key, .. }
162
+ | ControlSpec::Color { key, .. } => vec![key.as_str()],
163
+ }
164
+ }
165
+ }
166
+
167
+ /// One UI-emitted change to a single schema field. The emitting widget
168
+ /// declares which top-level keys the update is allowed to write
169
+ /// (`keys` — equivalent to the field's [`ControlSpec::serialized_keys`])
170
+ /// and a partial new sub-state (`value`).
171
+ ///
172
+ /// Apply semantics: keys in `keys` are *cleared* from the column's config
173
+ /// map, then keys present in `value` are *inserted*. Defaults are
174
+ /// pre-stripped by the caller (typically via `skip_serializing_if`), so
175
+ /// "no value set for key K" means the schema default applies and K is
176
+ /// not serialized.
177
+ #[derive(Clone, Debug, Deserialize, Serialize)]
178
+ pub struct ColumnConfigFieldUpdate {
179
+ pub keys: Vec<String>,
180
+ pub value: serde_json::Map<String, Value>,
181
+ }
182
+
183
+ /// Filter a per-column config map to only the keys advertised by the
184
+ /// active plugin's schema. Foreign keys (left over from a previous plugin)
185
+ /// stay in the unfiltered presentation state but never reach `restore()`.
186
+ pub fn filter_to_schema(
187
+ config: &serde_json::Map<String, Value>,
188
+ active_keys: &HashSet<String>,
189
+ ) -> serde_json::Map<String, Value> {
190
+ config
191
+ .iter()
192
+ .filter(|(k, _)| active_keys.contains(k.as_str()))
193
+ .map(|(k, v)| (k.clone(), v.clone()))
194
+ .collect()
195
+ }