@perspective-dev/viewer 4.3.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 (227) 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 -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/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/perspective-viewer.inline.js +2 -2
  23. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  24. package/dist/esm/perspective-viewer.js +2 -2
  25. package/dist/esm/perspective-viewer.js.map +4 -4
  26. package/dist/wasm/perspective-viewer.d.ts +57 -53
  27. package/dist/wasm/perspective-viewer.js +190 -165
  28. package/dist/wasm/perspective-viewer.wasm +0 -0
  29. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  30. package/package.json +7 -5
  31. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  32. package/src/css/column-dropdown.css +109 -0
  33. package/src/{less/column-selector.less → css/column-selector.css} +160 -158
  34. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  35. package/src/{less/column-style.less → css/column-style.css} +52 -66
  36. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  37. package/src/{less/config-selector.less → css/config-selector.css} +151 -135
  38. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  39. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  40. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  41. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  42. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  43. package/src/css/dom/checkbox.css +102 -0
  44. package/src/css/dom/scrollbar.css +35 -0
  45. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  46. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  47. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  48. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  49. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  50. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  51. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  52. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  53. package/src/css/plugin-selector.css +261 -0
  54. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  55. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  56. package/src/css/type-icon.css +116 -0
  57. package/src/{less/viewer.less → css/viewer.css} +112 -146
  58. package/src/rust/components/column_dropdown.rs +229 -119
  59. package/src/rust/components/column_selector/active_column.rs +81 -62
  60. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  61. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  62. package/src/rust/components/column_selector/config_selector.rs +315 -199
  63. package/src/rust/components/column_selector/empty_column.rs +2 -2
  64. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  65. package/src/rust/components/column_selector/filter_column.rs +37 -26
  66. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  67. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  68. package/src/rust/components/column_selector/pivot_column.rs +11 -5
  69. package/src/rust/components/column_selector/sort_column.rs +23 -13
  70. package/src/rust/components/column_selector.rs +163 -84
  71. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  72. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  73. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  77. package/src/rust/components/column_settings_sidebar.rs +91 -53
  78. package/src/rust/components/containers/dragdrop_list.rs +2 -1
  79. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  80. package/src/rust/components/containers/split_panel.rs +1 -0
  81. package/src/rust/components/containers/tab_list.rs +1 -1
  82. package/src/rust/components/copy_dropdown.rs +7 -28
  83. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  84. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  85. package/src/rust/components/datetime_column_style.rs +4 -2
  86. package/src/rust/components/editable_header.rs +7 -4
  87. package/src/rust/components/empty_row.rs +1 -1
  88. package/src/rust/components/export_dropdown.rs +4 -30
  89. package/src/rust/components/expression_editor.rs +19 -10
  90. package/src/rust/components/filter_dropdown.rs +246 -102
  91. package/src/rust/components/font_loader.rs +11 -28
  92. package/src/rust/components/form/code_editor.rs +17 -2
  93. package/src/rust/components/form/color_range_selector.rs +19 -6
  94. package/src/rust/components/form/debug.rs +30 -13
  95. package/src/rust/components/function_dropdown.rs +186 -113
  96. package/src/rust/components/main_panel.rs +71 -89
  97. package/src/rust/components/mod.rs +1 -1
  98. package/src/rust/components/modal.rs +7 -1
  99. package/src/rust/components/number_column_style.rs +22 -7
  100. package/src/rust/components/plugin_selector.rs +34 -102
  101. package/src/rust/components/portal.rs +274 -0
  102. package/src/rust/components/render_warning.rs +72 -123
  103. package/src/rust/components/settings_panel.rs +115 -11
  104. package/src/rust/components/status_bar.rs +222 -98
  105. package/src/rust/components/status_bar_counter.rs +8 -20
  106. package/src/rust/components/status_indicator.rs +64 -114
  107. package/src/rust/components/string_column_style.rs +2 -2
  108. package/src/rust/components/style/style_cache.rs +5 -1
  109. package/src/rust/components/viewer.rs +391 -39
  110. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  111. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  112. package/src/rust/custom_elements/mod.rs +0 -7
  113. package/src/rust/custom_elements/modal.rs +7 -103
  114. package/src/rust/custom_elements/viewer.rs +99 -35
  115. package/src/rust/custom_events.rs +23 -2
  116. package/src/rust/dragdrop.rs +149 -10
  117. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  118. package/src/rust/js/plugin.rs +1 -1
  119. package/src/rust/lib.rs +5 -4
  120. package/src/rust/presentation/props.rs +39 -0
  121. package/src/rust/presentation/sheets.rs +3 -3
  122. package/src/rust/presentation.rs +44 -8
  123. package/src/rust/renderer/limits.rs +32 -3
  124. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  125. package/src/rust/renderer.rs +83 -9
  126. package/src/rust/session/column_defaults_update.rs +1 -1
  127. package/src/rust/session/metadata.rs +23 -2
  128. package/src/rust/session/props.rs +178 -0
  129. package/src/rust/session.rs +124 -117
  130. package/src/rust/tasks/column_locator.rs +133 -0
  131. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  132. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  133. package/src/rust/{model → tasks}/eject.rs +2 -2
  134. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  135. package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
  136. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  137. package/src/rust/tasks/mod.rs +52 -0
  138. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  139. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  140. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  141. package/src/rust/tasks/structural.rs +53 -0
  142. package/src/rust/utils/mod.rs +4 -0
  143. package/src/rust/utils/modal_position.rs +110 -0
  144. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  145. package/src/rust/utils/pubsub.rs +11 -1
  146. package/src/svg/bg-pattern.png +0 -0
  147. package/src/svg/close-icon.svg +1 -1
  148. package/src/svg/expression.svg +1 -1
  149. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  150. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  151. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  152. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  153. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  154. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  155. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  156. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  157. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  158. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  159. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  160. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  161. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  162. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  163. package/src/svg/radio-hover.svg +1 -1
  164. package/src/svg/radio-off.svg +1 -1
  165. package/src/svg/radio-on.svg +1 -1
  166. package/src/themes/botanical.css +157 -0
  167. package/src/themes/defaults.css +139 -0
  168. package/src/themes/dracula.css +233 -0
  169. package/src/themes/gruvbox-dark.css +255 -0
  170. package/src/themes/gruvbox.css +134 -0
  171. package/src/themes/icons.css +124 -0
  172. package/src/themes/intl/de.css +102 -0
  173. package/src/themes/intl/es.css +102 -0
  174. package/src/themes/intl/fr.css +102 -0
  175. package/src/themes/intl/ja.css +102 -0
  176. package/src/themes/intl/pt.css +102 -0
  177. package/src/themes/intl/zh.css +102 -0
  178. package/src/themes/intl.css +102 -0
  179. package/src/themes/monokai.css +233 -0
  180. package/src/themes/pro-dark.css +158 -0
  181. package/src/themes/{themes.less → pro.css} +17 -21
  182. package/src/themes/solarized-dark.css +135 -0
  183. package/src/themes/solarized.css +95 -0
  184. package/src/themes/themes.css +22 -0
  185. package/src/themes/vaporwave.css +256 -0
  186. package/dist/css/variables.css +0 -0
  187. package/src/less/column-dropdown.less +0 -95
  188. package/src/less/dom/checkbox.less +0 -100
  189. package/src/less/plugin-selector.less +0 -183
  190. package/src/less/type-icon.less +0 -68
  191. package/src/rust/components/error_message.rs +0 -56
  192. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  193. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  194. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  195. package/src/rust/model/column_locator.rs +0 -82
  196. package/src/rust/model/is_invalid_drop.rs +0 -36
  197. package/src/rust/model/mod.rs +0 -100
  198. package/src/rust/model/reset_all.rs +0 -38
  199. package/src/rust/model/structural.rs +0 -244
  200. package/src/themes/botanical.less +0 -142
  201. package/src/themes/dracula.less +0 -101
  202. package/src/themes/gruvbox-dark.less +0 -116
  203. package/src/themes/gruvbox.less +0 -152
  204. package/src/themes/icons.less +0 -130
  205. package/src/themes/intl/de.less +0 -102
  206. package/src/themes/intl/es.less +0 -102
  207. package/src/themes/intl/fr.less +0 -102
  208. package/src/themes/intl/ja.less +0 -102
  209. package/src/themes/intl/pt.less +0 -102
  210. package/src/themes/intl/zh.less +0 -102
  211. package/src/themes/intl.less +0 -102
  212. package/src/themes/monokai.less +0 -107
  213. package/src/themes/pro-dark.less +0 -147
  214. package/src/themes/pro.less +0 -186
  215. package/src/themes/solarized-dark.less +0 -78
  216. package/src/themes/solarized.less +0 -102
  217. package/src/themes/vaporwave.less +0 -145
  218. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  219. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  220. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  223. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  224. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  225. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  226. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  227. /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
+ }