@perspective-dev/viewer 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -0
  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/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/esm/extensions.d.ts +32 -1
  23. package/dist/esm/perspective-viewer.d.ts +1 -0
  24. package/dist/esm/perspective-viewer.inline.js +2 -2
  25. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  26. package/dist/esm/perspective-viewer.js +2 -2
  27. package/dist/esm/perspective-viewer.js.map +4 -4
  28. package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
  29. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +2 -0
  30. package/dist/wasm/perspective-viewer.d.ts +57 -53
  31. package/dist/wasm/perspective-viewer.js +197 -164
  32. package/dist/wasm/perspective-viewer.wasm +0 -0
  33. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  34. package/package.json +9 -6
  35. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  36. package/src/css/column-dropdown.css +109 -0
  37. package/src/{less/column-selector.less → css/column-selector.css} +161 -159
  38. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  39. package/src/{less/column-style.less → css/column-style.css} +52 -66
  40. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  41. package/src/css/config-selector.css +441 -0
  42. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  43. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  44. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  45. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  46. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  47. package/src/css/dom/checkbox.css +102 -0
  48. package/src/css/dom/scrollbar.css +35 -0
  49. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  50. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  51. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  52. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  53. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  54. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  55. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  56. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  57. package/src/css/plugin-selector.css +261 -0
  58. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  59. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  60. package/src/css/type-icon.css +116 -0
  61. package/src/{less/viewer.less → css/viewer.css} +112 -146
  62. package/src/rust/components/column_dropdown.rs +229 -119
  63. package/src/rust/components/column_selector/active_column.rs +81 -62
  64. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  65. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  66. package/src/rust/components/column_selector/config_selector.rs +374 -185
  67. package/src/rust/components/column_selector/empty_column.rs +2 -2
  68. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  69. package/src/rust/components/column_selector/filter_column.rs +37 -26
  70. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  71. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  72. package/src/rust/components/column_selector/pivot_column.rs +21 -10
  73. package/src/rust/components/column_selector/sort_column.rs +23 -13
  74. package/src/rust/components/column_selector.rs +189 -100
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  77. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  79. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  80. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  81. package/src/rust/components/column_settings_sidebar.rs +91 -53
  82. package/src/rust/components/containers/dragdrop_list.rs +29 -7
  83. package/src/rust/components/containers/scroll_panel.rs +8 -1
  84. package/src/rust/components/containers/select.rs +3 -3
  85. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  86. package/src/rust/components/containers/split_panel.rs +3 -2
  87. package/src/rust/components/containers/tab_list.rs +1 -1
  88. package/src/rust/components/copy_dropdown.rs +7 -28
  89. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  90. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  91. package/src/rust/components/datetime_column_style.rs +4 -2
  92. package/src/rust/components/editable_header.rs +7 -4
  93. package/src/rust/components/empty_row.rs +1 -1
  94. package/src/rust/components/export_dropdown.rs +4 -30
  95. package/src/rust/components/expression_editor.rs +19 -10
  96. package/src/rust/components/filter_dropdown.rs +246 -102
  97. package/src/rust/components/font_loader.rs +11 -28
  98. package/src/rust/components/form/code_editor.rs +17 -2
  99. package/src/rust/components/form/color_range_selector.rs +19 -6
  100. package/src/rust/components/form/debug.rs +30 -13
  101. package/src/rust/components/function_dropdown.rs +186 -113
  102. package/src/rust/components/main_panel.rs +71 -89
  103. package/src/rust/components/mod.rs +1 -1
  104. package/src/rust/components/modal.rs +7 -1
  105. package/src/rust/components/number_column_style.rs +22 -7
  106. package/src/rust/components/plugin_selector.rs +34 -92
  107. package/src/rust/components/portal.rs +274 -0
  108. package/src/rust/components/render_warning.rs +72 -123
  109. package/src/rust/components/settings_panel.rs +115 -11
  110. package/src/rust/components/status_bar.rs +222 -98
  111. package/src/rust/components/status_bar_counter.rs +8 -20
  112. package/src/rust/components/status_indicator.rs +64 -111
  113. package/src/rust/components/string_column_style.rs +2 -2
  114. package/src/rust/components/style/style_cache.rs +5 -1
  115. package/src/rust/components/viewer.rs +391 -39
  116. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  117. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  118. package/src/rust/custom_elements/mod.rs +0 -7
  119. package/src/rust/custom_elements/modal.rs +7 -103
  120. package/src/rust/custom_elements/viewer.rs +99 -35
  121. package/src/rust/custom_events.rs +23 -2
  122. package/src/rust/dragdrop.rs +149 -10
  123. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  124. package/src/rust/js/plugin.rs +20 -1
  125. package/src/rust/lib.rs +5 -4
  126. package/src/rust/presentation/props.rs +39 -0
  127. package/src/rust/presentation/sheets.rs +3 -3
  128. package/src/rust/presentation.rs +44 -8
  129. package/src/rust/renderer/limits.rs +32 -3
  130. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  131. package/src/rust/renderer/registry.rs +8 -1
  132. package/src/rust/renderer.rs +83 -9
  133. package/src/rust/session/column_defaults_update.rs +18 -0
  134. package/src/rust/session/metadata.rs +23 -2
  135. package/src/rust/session/props.rs +178 -0
  136. package/src/rust/session/replace_expression_update.rs +1 -0
  137. package/src/rust/session.rs +124 -117
  138. package/src/rust/tasks/column_locator.rs +133 -0
  139. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  140. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  141. package/src/rust/{model → tasks}/eject.rs +2 -2
  142. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  143. package/src/rust/{model → tasks}/intersection_observer.rs +22 -4
  144. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  145. package/src/rust/tasks/mod.rs +52 -0
  146. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  147. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  148. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  149. package/src/rust/tasks/structural.rs +53 -0
  150. package/src/rust/utils/mod.rs +4 -0
  151. package/src/rust/utils/modal_position.rs +110 -0
  152. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  153. package/src/rust/utils/pubsub.rs +11 -1
  154. package/src/svg/bg-pattern.png +0 -0
  155. package/src/svg/close-icon.svg +1 -1
  156. package/src/svg/expression.svg +1 -1
  157. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  158. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  159. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  160. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  161. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  162. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  163. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  164. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  165. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  166. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  167. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  168. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  169. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  170. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  171. package/src/svg/radio-hover.svg +1 -1
  172. package/src/svg/radio-off.svg +1 -1
  173. package/src/svg/radio-on.svg +1 -1
  174. package/src/themes/botanical.css +157 -0
  175. package/src/themes/defaults.css +139 -0
  176. package/src/themes/dracula.css +233 -0
  177. package/src/themes/gruvbox-dark.css +255 -0
  178. package/src/themes/gruvbox.css +134 -0
  179. package/src/themes/icons.css +124 -0
  180. package/src/themes/intl/de.css +102 -0
  181. package/src/themes/intl/es.css +102 -0
  182. package/src/themes/intl/fr.css +102 -0
  183. package/src/themes/intl/ja.css +102 -0
  184. package/src/themes/intl/pt.css +102 -0
  185. package/src/themes/intl/zh.css +102 -0
  186. package/src/themes/intl.css +102 -0
  187. package/src/themes/monokai.css +233 -0
  188. package/src/themes/pro-dark.css +158 -0
  189. package/src/themes/{themes.less → pro.css} +17 -20
  190. package/src/themes/solarized-dark.css +135 -0
  191. package/src/themes/solarized.css +95 -0
  192. package/src/themes/themes.css +22 -0
  193. package/src/themes/vaporwave.css +256 -0
  194. package/src/ts/extensions.ts +73 -2
  195. package/src/ts/perspective-viewer.ts +1 -0
  196. package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
  197. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -1
  198. package/tsconfig.json +1 -0
  199. package/dist/css/variables.css +0 -0
  200. package/src/less/column-dropdown.less +0 -95
  201. package/src/less/config-selector.less +0 -363
  202. package/src/less/dom/checkbox.less +0 -100
  203. package/src/less/plugin-selector.less +0 -183
  204. package/src/less/type-icon.less +0 -68
  205. package/src/rust/components/error_message.rs +0 -56
  206. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  207. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  208. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  209. package/src/rust/model/column_locator.rs +0 -82
  210. package/src/rust/model/is_invalid_drop.rs +0 -36
  211. package/src/rust/model/mod.rs +0 -100
  212. package/src/rust/model/reset_all.rs +0 -38
  213. package/src/rust/model/structural.rs +0 -244
  214. package/src/themes/dracula.less +0 -101
  215. package/src/themes/gruvbox-dark.less +0 -116
  216. package/src/themes/gruvbox.less +0 -152
  217. package/src/themes/icons.less +0 -130
  218. package/src/themes/intl/de.less +0 -102
  219. package/src/themes/intl/es.less +0 -102
  220. package/src/themes/intl/fr.less +0 -102
  221. package/src/themes/intl/ja.less +0 -102
  222. package/src/themes/intl/pt.less +0 -102
  223. package/src/themes/intl/zh.less +0 -102
  224. package/src/themes/intl.less +0 -102
  225. package/src/themes/monokai.less +0 -107
  226. package/src/themes/pro-dark.less +0 -147
  227. package/src/themes/pro.less +0 -186
  228. package/src/themes/solarized-dark.less +0 -78
  229. package/src/themes/solarized.less +0 -102
  230. package/src/themes/vaporwave.less +0 -145
  231. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  232. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  233. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  234. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  235. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  236. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  237. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  238. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  239. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  240. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
@@ -25,16 +25,17 @@ use crate::components::column_settings_sidebar::ColumnSettingsPanel;
25
25
  use crate::components::main_panel::MainPanel;
26
26
  use crate::components::settings_panel::SettingsPanel;
27
27
  use crate::config::*;
28
+ use crate::css;
28
29
  use crate::custom_events::CustomEvents;
29
- use crate::dragdrop::*;
30
- use crate::model::*;
31
- use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation};
32
- use crate::renderer::*;
33
- use crate::session::*;
30
+ use crate::dragdrop::{DragDropProps, *};
31
+ use crate::js::JsPerspectiveViewerPlugin;
32
+ use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation, PresentationProps};
33
+ use crate::renderer::{RendererProps, *};
34
+ use crate::session::{SessionProps, *};
35
+ use crate::tasks::*;
34
36
  use crate::utils::*;
35
- use crate::{PerspectiveProperties, css};
36
37
 
37
- #[derive(Clone, Properties, PerspectiveProperties!)]
38
+ #[derive(Clone, Properties)]
38
39
  pub struct PerspectiveViewerProps {
39
40
  /// The light DOM element this component will render to.
40
41
  pub elem: web_sys::HtmlElement,
@@ -53,9 +54,41 @@ impl PartialEq for PerspectiveViewerProps {
53
54
  }
54
55
  }
55
56
 
56
- impl PerspectiveViewerProps {
57
- fn is_title(&self) -> bool {
58
- self.session.get_title().is_some()
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()
59
92
  }
60
93
  }
61
94
 
@@ -75,12 +108,34 @@ pub enum PerspectiveViewerMsg {
75
108
  ToggleDebug,
76
109
  ToggleSettingsComplete(SettingsUpdate, Sender<()>),
77
110
  ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
111
+ UpdateSession(Box<SessionProps>),
112
+ UpdateRenderer(Box<RendererProps>),
113
+ UpdatePresentation(Box<PresentationProps>),
114
+
115
+ /// Update only `is_settings_open` in the presentation snapshot without
116
+ /// touching `available_themes` (which requires async data).
117
+ UpdateSettingsOpen(bool),
118
+ UpdateIsWorkspace(bool),
119
+
120
+ /// Update only `open_column_settings` in the presentation snapshot.
121
+ UpdateColumnSettings(Box<crate::presentation::OpenColumnSettings>),
122
+ UpdateDragDrop(Box<DragDropProps>),
123
+
124
+ /// Update only stats-related fields of `session_props` without touching
125
+ /// `config`. This prevents `stats_changed` events (e.g. from `reset()`)
126
+ /// from propagating a freshly-cleared config to the column selector.
127
+ UpdateSessionStats(Option<ViewStats>, Option<TableLoadState>),
128
+
129
+ /// Increment/decrement the in-flight render counter threaded to
130
+ /// `StatusIndicator` so it can show the "updating" spinner.
131
+ IncrementUpdateCount,
132
+ DecrementUpdateCount,
78
133
  }
79
134
 
80
135
  use PerspectiveViewerMsg::*;
81
136
 
82
137
  pub struct PerspectiveViewer {
83
- _subscriptions: [Subscription; 1],
138
+ _subscriptions: Vec<Subscription>,
84
139
  column_settings_panel_width_override: Option<i32>,
85
140
  debug_open: bool,
86
141
  fonts: FontLoaderProps,
@@ -89,6 +144,18 @@ pub struct PerspectiveViewer {
89
144
  on_resize: Rc<PubSub<()>>,
90
145
  settings_open: bool,
91
146
  settings_panel_width_override: Option<i32>,
147
+
148
+ /// Value-semantic state snapshots (Step 4 scaffold).
149
+ /// Populated by `UpdateSession` / `UpdateRenderer` / `UpdatePresentation` /
150
+ /// `UpdateDragDrop` messages dispatched from async engine tasks.
151
+ session_props: SessionProps,
152
+ renderer_props: RendererProps,
153
+ presentation_props: PresentationProps,
154
+ dragdrop_props: DragDropProps,
155
+
156
+ /// Counts in-flight renders (incremented on `view_config_changed`,
157
+ /// decremented on `view_created`). Threaded to `StatusIndicator`.
158
+ update_count: u32,
92
159
  }
93
160
 
94
161
  impl Component for PerspectiveViewer {
@@ -98,36 +165,38 @@ impl Component for PerspectiveViewer {
98
165
  fn create(ctx: &Context<Self>) -> Self {
99
166
  let elem = ctx.props().elem.clone();
100
167
  let fonts = FontLoaderProps::new(&elem, ctx.link().callback(|()| PreloadFontsUpdate));
168
+ inject_engine_callbacks(ctx);
169
+ let subscriptions = create_subscriptions(ctx);
170
+ let session_props = ctx.props().session.to_props();
171
+ let renderer_props = ctx.props().renderer.to_props(None);
172
+ let presentation_props = ctx.props().presentation.to_props(PtrEqRc::new(vec![]));
101
173
 
102
- let session_sub = {
103
- let props = ctx.props().clone();
104
- let callback = ctx.link().batch_callback(move |(update, _)| {
105
- if update {
106
- vec![]
107
- } else {
108
- let locator = props.get_current_column_locator();
109
- vec![OpenColumnSettings {
110
- locator,
111
- sender: None,
112
- toggle: false,
113
- }]
114
- }
115
- });
116
-
117
- ctx.props()
118
- .renderer
119
- .render_limits_changed
120
- .add_listener(callback)
121
- };
122
-
174
+ // Memoized callback for column settings drawer
123
175
  let on_close_column_settings = ctx.link().callback(|_| OpenColumnSettings {
124
176
  locator: None,
125
177
  sender: None,
126
178
  toggle: false,
127
179
  });
128
180
 
181
+ // Kick off an initial async theme fetch so that `available_themes` is
182
+ // populated even if `theme_config_updated` fires before the PubSub
183
+ // subscription is registered.
184
+ {
185
+ let presentation = ctx.props().presentation.clone();
186
+ let cb = ctx.link().callback(move |themes: PtrEqRc<Vec<String>>| {
187
+ UpdatePresentation(Box::new(presentation.to_props(themes)))
188
+ });
189
+
190
+ let presentation = ctx.props().presentation.clone();
191
+ ApiFuture::spawn(async move {
192
+ let themes = presentation.get_available_themes().await?;
193
+ cb.emit(themes);
194
+ Ok(())
195
+ });
196
+ }
197
+
129
198
  Self {
130
- _subscriptions: [session_sub],
199
+ _subscriptions: subscriptions,
131
200
  column_settings_panel_width_override: None,
132
201
  debug_open: false,
133
202
  fonts,
@@ -136,6 +205,11 @@ impl Component for PerspectiveViewer {
136
205
  on_resize: Default::default(),
137
206
  settings_open: false,
138
207
  settings_panel_width_override: None,
208
+ session_props,
209
+ renderer_props,
210
+ presentation_props,
211
+ dragdrop_props: DragDropProps::default(),
212
+ update_count: 0,
139
213
  }
140
214
  }
141
215
 
@@ -250,7 +324,7 @@ impl Component for PerspectiveViewer {
250
324
  } else {
251
325
  locator.as_ref().and_then(|x| {
252
326
  x.name().map(|x| {
253
- if ctx.props().session.is_column_active(x) {
327
+ if self.session_props.is_column_active(x) {
254
328
  ColumnSettingsTab::Style
255
329
  } else {
256
330
  ColumnSettingsTab::Attributes
@@ -303,6 +377,56 @@ impl Component for PerspectiveViewer {
303
377
 
304
378
  true
305
379
  },
380
+ UpdateSession(props) => {
381
+ let changed = *props != self.session_props;
382
+ self.session_props = *props;
383
+ changed
384
+ },
385
+ UpdateSessionStats(stats, has_table) => {
386
+ let changed =
387
+ stats != self.session_props.stats || has_table != self.session_props.has_table;
388
+ self.session_props.stats = stats;
389
+ self.session_props.has_table = has_table;
390
+ changed
391
+ },
392
+ UpdateRenderer(props) => {
393
+ let changed = *props != self.renderer_props;
394
+ self.renderer_props = *props;
395
+ changed
396
+ },
397
+ UpdatePresentation(props) => {
398
+ let changed = *props != self.presentation_props;
399
+ self.presentation_props = *props;
400
+ changed
401
+ },
402
+ UpdateSettingsOpen(open) => {
403
+ let changed = open != self.presentation_props.is_settings_open;
404
+ self.presentation_props.is_settings_open = open;
405
+ changed
406
+ },
407
+ UpdateIsWorkspace(is_workspace) => {
408
+ let changed = is_workspace != self.presentation_props.is_workspace;
409
+ self.presentation_props.is_workspace = is_workspace;
410
+ changed
411
+ },
412
+ UpdateColumnSettings(ocs) => {
413
+ let changed = *ocs != self.presentation_props.open_column_settings;
414
+ self.presentation_props.open_column_settings = *ocs;
415
+ changed
416
+ },
417
+ UpdateDragDrop(props) => {
418
+ let changed = *props != self.dragdrop_props;
419
+ self.dragdrop_props = *props;
420
+ changed
421
+ },
422
+ IncrementUpdateCount => {
423
+ self.update_count = self.update_count.saturating_add(1);
424
+ true
425
+ },
426
+ DecrementUpdateCount => {
427
+ self.update_count = self.update_count.saturating_sub(1);
428
+ true
429
+ },
306
430
  }
307
431
  }
308
432
 
@@ -334,13 +458,15 @@ impl Component for PerspectiveViewer {
334
458
  ..
335
459
  } = ctx.props();
336
460
 
337
- let is_settings_open = self.settings_open && ctx.props().session.has_table();
461
+ let is_settings_open = self.settings_open
462
+ && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
463
+
338
464
  let mut class = classes!();
339
465
  if !is_settings_open {
340
466
  class.push("settings-closed");
341
467
  }
342
468
 
343
- if ctx.props().is_title() {
469
+ if self.session_props.title.is_some() {
344
470
  class.push("titled");
345
471
  }
346
472
 
@@ -360,8 +486,28 @@ impl Component for PerspectiveViewer {
360
486
 
361
487
  let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
362
488
  let on_debug = ctx.link().callback(|_| ToggleDebug);
363
- let selected_column = ctx.props().get_current_column_locator();
364
- let selected_tab = ctx.props().presentation.get_open_column_settings().tab;
489
+ let selected_column = get_current_column_locator(
490
+ &self.presentation_props.open_column_settings,
491
+ &ctx.props().renderer,
492
+ &self.session_props.config,
493
+ &self.session_props.metadata,
494
+ );
495
+
496
+ let selected_tab = self.presentation_props.open_column_settings.tab;
497
+ let plugin_name = self.renderer_props.plugin_name.clone();
498
+ let available_plugins = self.renderer_props.available_plugins.clone();
499
+ let has_table = self.session_props.has_table.clone();
500
+ let named_column_count = self
501
+ .renderer_props
502
+ .requirements
503
+ .names
504
+ .as_ref()
505
+ .map(|n| n.len())
506
+ .unwrap_or(0);
507
+
508
+ let view_config = self.session_props.config.clone();
509
+ let drag_column = self.dragdrop_props.column.clone();
510
+ let metadata = self.session_props.metadata.clone();
365
511
  let settings_panel = html! {
366
512
  if is_settings_open {
367
513
  <SettingsPanel
@@ -370,6 +516,15 @@ impl Component for PerspectiveViewer {
370
516
  on_select_column={on_open_expr_panel}
371
517
  is_debug={self.debug_open}
372
518
  {on_debug}
519
+ {plugin_name}
520
+ {available_plugins}
521
+ {has_table}
522
+ {named_column_count}
523
+ {view_config}
524
+ {drag_column}
525
+ metadata={metadata.clone()}
526
+ open_column_settings={self.presentation_props.open_column_settings.clone()}
527
+ selected_theme={self.presentation_props.selected_theme.clone()}
373
528
  {dragdrop}
374
529
  {presentation}
375
530
  {renderer}
@@ -395,6 +550,10 @@ impl Component for PerspectiveViewer {
395
550
  on_close={self.on_close_column_settings.clone()}
396
551
  width_override={self.column_settings_panel_width_override}
397
552
  {on_select_tab}
553
+ plugin_name={self.renderer_props.plugin_name.clone()}
554
+ {metadata}
555
+ view_config={self.session_props.config.clone()}
556
+ selected_theme={self.presentation_props.selected_theme.clone()}
398
557
  {custom_events}
399
558
  {presentation}
400
559
  {renderer}
@@ -405,8 +564,38 @@ impl Component for PerspectiveViewer {
405
564
  }
406
565
  };
407
566
 
567
+ let on_reset = ctx.link().callback(|all| Reset(all, None));
568
+ let render_limits = self.renderer_props.render_limits;
569
+ let has_table = self.session_props.has_table.clone();
570
+ let is_errored = self.session_props.error.is_some();
571
+ let stats = self.session_props.stats.clone();
572
+ let update_count = self.update_count;
573
+ let error = self.session_props.error.clone();
574
+ let is_settings_open = self.settings_open
575
+ && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
576
+ let title = self.session_props.title.clone();
577
+ let selected_theme = self.presentation_props.selected_theme.clone();
578
+ let available_themes = self.presentation_props.available_themes.clone();
408
579
  let main_panel = html! {
409
- <MainPanel {on_settings} {custom_events} {presentation} {renderer} {session} />
580
+ <MainPanel
581
+ {on_settings}
582
+ {on_reset}
583
+ {render_limits}
584
+ {has_table}
585
+ {is_errored}
586
+ {stats}
587
+ {update_count}
588
+ {error}
589
+ {is_settings_open}
590
+ {title}
591
+ {selected_theme}
592
+ {available_themes}
593
+ is_workspace={self.presentation_props.is_workspace}
594
+ {custom_events}
595
+ {presentation}
596
+ {renderer}
597
+ {session}
598
+ />
410
599
  };
411
600
 
412
601
  let debug_panel = html! {
@@ -523,3 +712,166 @@ impl PerspectiveViewer {
523
712
  };
524
713
  }
525
714
  }
715
+
716
+ /// Subscribe to PubSub events that still have non-root subscribers and
717
+ /// therefore cannot yet be replaced with direct callbacks.
718
+ fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
719
+ let session_props_sub = {
720
+ let session = ctx.props().session.clone();
721
+ let cb = ctx
722
+ .link()
723
+ .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
724
+
725
+ let s = &ctx.props().session;
726
+ let sub1 = s.table_loaded.add_notify_listener(&cb);
727
+ let sub2 = s.table_unloaded.add_notify_listener(&cb);
728
+ let sub3 = s.view_created.add_notify_listener(&cb);
729
+ let sub4 = s.view_config_changed.add_notify_listener(&cb);
730
+ let sub5 = s.title_changed.add_notify_listener(&cb);
731
+ let sub6 = s
732
+ .view_config_changed
733
+ .add_listener(ctx.link().callback(|_| IncrementUpdateCount));
734
+
735
+ let sub7 = s
736
+ .view_created
737
+ .add_listener(ctx.link().callback(|_| DecrementUpdateCount));
738
+
739
+ vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7]
740
+ };
741
+
742
+ let renderer_props_sub = {
743
+ let renderer = ctx.props().renderer.clone();
744
+ let cb_plugin = ctx.link().callback({
745
+ move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
746
+ });
747
+
748
+ let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
749
+ vec![sub1]
750
+ };
751
+
752
+ let presentation_props_sub = {
753
+ let presentation = ctx.props().presentation.clone();
754
+ let cb_settings = ctx.link().callback(UpdateSettingsOpen);
755
+ let cb_theme = {
756
+ let pres = presentation.clone();
757
+ ctx.link()
758
+ .callback(move |(themes, _): (PtrEqRc<Vec<String>>, _)| {
759
+ UpdatePresentation(Box::new(pres.to_props(themes)))
760
+ })
761
+ };
762
+
763
+ let cb_column_settings = {
764
+ let pres = presentation.clone();
765
+ ctx.link().callback(move |_: (bool, Option<String>)| {
766
+ UpdateColumnSettings(Box::new(pres.get_open_column_settings()))
767
+ })
768
+ };
769
+
770
+ let sub1 = presentation.settings_open_changed.add_listener(cb_settings);
771
+ let sub2 = presentation.theme_config_updated.add_listener(cb_theme);
772
+ let sub3 = presentation
773
+ .column_settings_open_changed
774
+ .add_listener(cb_column_settings);
775
+
776
+ vec![sub1, sub2, sub3]
777
+ };
778
+
779
+ let dragdrop_props_sub = {
780
+ let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
781
+ let sub1 = ctx
782
+ .props()
783
+ .dragdrop
784
+ .drop_received
785
+ .add_notify_listener(&cb_clear);
786
+
787
+ vec![sub1]
788
+ };
789
+
790
+ let mut subscriptions = Vec::new();
791
+ subscriptions.extend(session_props_sub);
792
+ subscriptions.extend(renderer_props_sub);
793
+ subscriptions.extend(presentation_props_sub);
794
+ subscriptions.extend(dragdrop_props_sub);
795
+ subscriptions
796
+ }
797
+
798
+ /// Inject direct callbacks into the engine handles, replacing PubSub fields
799
+ /// that were exclusively consumed by the root component.
800
+ fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
801
+ // Session: on_stats_changed
802
+ {
803
+ let session = ctx.props().session.clone();
804
+ let cb = ctx.link().callback(move |_: ()| {
805
+ UpdateSessionStats(session.get_table_stats(), session.has_table())
806
+ });
807
+
808
+ *ctx.props().session.on_stats_changed.borrow_mut() = Some(cb);
809
+ }
810
+
811
+ // Session: on_table_errored
812
+ {
813
+ let session = ctx.props().session.clone();
814
+ let cb = ctx
815
+ .link()
816
+ .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
817
+
818
+ *ctx.props().session.on_table_errored.borrow_mut() = Some(cb);
819
+ }
820
+
821
+ // Renderer: on_render_limits_changed (combines UpdateRenderer + column
822
+ // locator recheck that were previously two separate PubSub subscriptions).
823
+ {
824
+ clone!(
825
+ ctx.props().presentation,
826
+ ctx.props().renderer,
827
+ ctx.props().session
828
+ );
829
+
830
+ let cb = ctx.link().batch_callback(move |limits: RenderLimits| {
831
+ let mut msgs = vec![UpdateRenderer(Box::new(renderer.to_props(Some(limits))))];
832
+ if !limits.is_update {
833
+ let locator = get_current_column_locator(
834
+ &presentation.get_open_column_settings(),
835
+ &renderer,
836
+ &session.get_view_config(),
837
+ &session.metadata(),
838
+ );
839
+
840
+ msgs.push(OpenColumnSettings {
841
+ locator,
842
+ sender: None,
843
+ toggle: false,
844
+ });
845
+ }
846
+
847
+ msgs
848
+ });
849
+
850
+ *ctx.props().renderer.on_render_limits_changed.borrow_mut() = Some(cb);
851
+ }
852
+
853
+ // Presentation: on_is_workspace_changed
854
+ {
855
+ let cb = ctx.link().callback(UpdateIsWorkspace);
856
+ *ctx.props()
857
+ .presentation
858
+ .on_is_workspace_changed
859
+ .borrow_mut() = Some(cb);
860
+ }
861
+
862
+ // DragDrop: on_dragstart
863
+ {
864
+ let dragdrop = ctx.props().dragdrop.clone();
865
+ let cb = ctx
866
+ .link()
867
+ .callback(move |_: DragEffect| UpdateDragDrop(Box::new(dragdrop.to_props())));
868
+
869
+ *ctx.props().dragdrop.on_dragstart.borrow_mut() = Some(cb);
870
+ }
871
+
872
+ // DragDrop: on_dragend
873
+ {
874
+ let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
875
+ *ctx.props().dragdrop.on_dragend.borrow_mut() = Some(cb);
876
+ }
877
+ }