@perspective-dev/viewer 4.3.0 → 4.4.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 (243) 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/phosphor.css +1 -0
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/esm/extensions.d.ts +4 -0
  24. package/dist/esm/perspective-viewer.d.ts +1 -0
  25. package/dist/esm/perspective-viewer.inline.js +2 -2
  26. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  27. package/dist/esm/perspective-viewer.js +2 -2
  28. package/dist/esm/perspective-viewer.js.map +4 -4
  29. package/dist/esm/plugin.d.ts +9 -9
  30. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +1 -1
  31. package/dist/esm/ts-rs/ViewerConfig.d.ts +36 -0
  32. package/dist/wasm/perspective-viewer.d.ts +68 -59
  33. package/dist/wasm/perspective-viewer.js +216 -171
  34. package/dist/wasm/perspective-viewer.wasm +0 -0
  35. package/dist/wasm/perspective-viewer.wasm.d.ts +19 -20
  36. package/package.json +7 -5
  37. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  38. package/src/css/column-dropdown.css +109 -0
  39. package/src/{less/column-selector.less → css/column-selector.css} +160 -158
  40. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +70 -59
  41. package/src/{less/column-style.less → css/column-style.css} +52 -66
  42. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  43. package/src/{less/config-selector.less → css/config-selector.css} +151 -135
  44. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  45. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  46. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  47. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  48. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  49. package/src/css/dom/checkbox.css +102 -0
  50. package/src/css/dom/scrollbar.css +35 -0
  51. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  52. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  53. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  54. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  55. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  56. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  57. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  58. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  59. package/src/css/plugin-selector.css +261 -0
  60. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  61. package/src/{less/status-bar.less → css/status-bar.css} +157 -145
  62. package/src/css/type-icon.css +116 -0
  63. package/src/{less/viewer.less → css/viewer.css} +112 -146
  64. package/src/rust/components/column_dropdown.rs +229 -119
  65. package/src/rust/components/column_selector/active_column.rs +81 -62
  66. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  67. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  68. package/src/rust/components/column_selector/config_selector.rs +315 -199
  69. package/src/rust/components/column_selector/empty_column.rs +2 -2
  70. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  71. package/src/rust/components/column_selector/filter_column.rs +37 -26
  72. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  73. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  74. package/src/rust/components/column_selector/pivot_column.rs +11 -5
  75. package/src/rust/components/column_selector/sort_column.rs +23 -13
  76. package/src/rust/components/column_selector.rs +163 -84
  77. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  79. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  80. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  81. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  82. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  83. package/src/rust/components/column_settings_sidebar.rs +91 -53
  84. package/src/rust/components/containers/dragdrop_list.rs +2 -1
  85. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  86. package/src/rust/components/containers/split_panel.rs +1 -0
  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 +30 -7
  106. package/src/rust/components/plugin_selector.rs +34 -102
  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 -114
  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 -40
  116. package/src/rust/config/number_column_style.rs +5 -1
  117. package/src/rust/config/viewer_config.rs +2 -2
  118. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  119. package/src/rust/custom_elements/debug_plugin.rs +4 -4
  120. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  121. package/src/rust/custom_elements/mod.rs +0 -7
  122. package/src/rust/custom_elements/modal.rs +7 -103
  123. package/src/rust/custom_elements/viewer.rs +114 -39
  124. package/src/rust/custom_events.rs +23 -2
  125. package/src/rust/dragdrop.rs +149 -10
  126. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  127. package/src/rust/js/plugin.rs +1 -1
  128. package/src/rust/lib.rs +10 -4
  129. package/src/rust/presentation/props.rs +39 -0
  130. package/src/rust/presentation/sheets.rs +3 -3
  131. package/src/rust/presentation.rs +44 -8
  132. package/src/rust/renderer/limits.rs +32 -3
  133. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  134. package/src/rust/renderer.rs +93 -14
  135. package/src/rust/session/column_defaults_update.rs +1 -1
  136. package/src/rust/session/metadata.rs +23 -2
  137. package/src/rust/session/props.rs +178 -0
  138. package/src/rust/session.rs +124 -117
  139. package/src/rust/tasks/column_locator.rs +133 -0
  140. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  141. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  142. package/src/rust/{model → tasks}/eject.rs +2 -2
  143. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  144. package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
  145. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  146. package/src/rust/tasks/mod.rs +52 -0
  147. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  148. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  149. package/src/rust/{model → tasks}/restore_and_render.rs +4 -3
  150. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  151. package/src/rust/tasks/structural.rs +53 -0
  152. package/src/rust/utils/mod.rs +4 -0
  153. package/src/rust/utils/modal_position.rs +110 -0
  154. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  155. package/src/rust/utils/pubsub.rs +11 -1
  156. package/src/svg/bg-pattern.png +0 -0
  157. package/src/svg/close-icon.svg +1 -1
  158. package/src/svg/datagrid-select-row-tree.svg +13 -0
  159. package/src/svg/expression.svg +1 -1
  160. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  161. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  162. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  163. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  164. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  165. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  166. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  167. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  168. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  169. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  170. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  171. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  172. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  173. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  174. package/src/svg/radio-hover.svg +1 -1
  175. package/src/svg/radio-off.svg +1 -1
  176. package/src/svg/radio-on.svg +1 -1
  177. package/src/themes/botanical.css +157 -0
  178. package/src/themes/defaults.css +157 -0
  179. package/src/themes/dracula.css +233 -0
  180. package/src/themes/gruvbox-dark.css +255 -0
  181. package/src/themes/gruvbox.css +134 -0
  182. package/src/themes/icons.css +107 -0
  183. package/src/themes/intl/de.css +102 -0
  184. package/src/themes/intl/es.css +102 -0
  185. package/src/themes/intl/fr.css +102 -0
  186. package/src/themes/intl/ja.css +102 -0
  187. package/src/themes/intl/pt.css +102 -0
  188. package/src/themes/intl/zh.css +102 -0
  189. package/src/themes/intl.css +102 -0
  190. package/src/themes/monokai.css +233 -0
  191. package/src/themes/phosphor.css +184 -0
  192. package/src/themes/pro-dark.css +158 -0
  193. package/src/themes/{themes.less → pro.css} +17 -21
  194. package/src/themes/solarized-dark.css +135 -0
  195. package/src/themes/solarized.css +95 -0
  196. package/src/themes/themes.css +23 -0
  197. package/src/themes/vaporwave.css +256 -0
  198. package/src/ts/extensions.ts +8 -1
  199. package/src/ts/perspective-viewer.ts +1 -0
  200. package/src/ts/plugin.ts +9 -9
  201. package/src/ts/ts-rs/NumberForegroundMode.ts +1 -1
  202. package/src/ts/ts-rs/ViewerConfig.ts +15 -0
  203. package/dist/css/variables.css +0 -0
  204. package/src/less/column-dropdown.less +0 -95
  205. package/src/less/dom/checkbox.less +0 -100
  206. package/src/less/plugin-selector.less +0 -183
  207. package/src/less/type-icon.less +0 -68
  208. package/src/rust/components/error_message.rs +0 -56
  209. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  210. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  211. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  212. package/src/rust/model/column_locator.rs +0 -82
  213. package/src/rust/model/is_invalid_drop.rs +0 -36
  214. package/src/rust/model/mod.rs +0 -100
  215. package/src/rust/model/reset_all.rs +0 -38
  216. package/src/rust/model/structural.rs +0 -244
  217. package/src/themes/botanical.less +0 -142
  218. package/src/themes/dracula.less +0 -101
  219. package/src/themes/gruvbox-dark.less +0 -116
  220. package/src/themes/gruvbox.less +0 -152
  221. package/src/themes/icons.less +0 -130
  222. package/src/themes/intl/de.less +0 -102
  223. package/src/themes/intl/es.less +0 -102
  224. package/src/themes/intl/fr.less +0 -102
  225. package/src/themes/intl/ja.less +0 -102
  226. package/src/themes/intl/pt.less +0 -102
  227. package/src/themes/intl/zh.less +0 -102
  228. package/src/themes/intl.less +0 -102
  229. package/src/themes/monokai.less +0 -107
  230. package/src/themes/pro-dark.less +0 -147
  231. package/src/themes/pro.less +0 -186
  232. package/src/themes/solarized-dark.less +0 -78
  233. package/src/themes/solarized.less +0 -102
  234. package/src/themes/vaporwave.less +0 -145
  235. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline0.js +0 -0
  236. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline1.js +0 -0
  237. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline2.js +0 -0
  238. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline3.js +0 -0
  239. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-d924246f0b4a3dce}/inline4.js +0 -0
  240. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  241. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  242. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  243. /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
 
@@ -220,7 +294,6 @@ impl Component for PerspectiveViewer {
220
294
  ToggleSettingsComplete(_, resolve)
221
295
  if matches!(self.fonts.get_status(), FontLoaderStatus::Finished) =>
222
296
  {
223
- ctx.props().presentation.set_open_column_settings(None);
224
297
  if let Err(e) = resolve.send(()) {
225
298
  tracing::error!("toggle settings failed {:?}", e);
226
299
  }
@@ -250,7 +323,7 @@ impl Component for PerspectiveViewer {
250
323
  } else {
251
324
  locator.as_ref().and_then(|x| {
252
325
  x.name().map(|x| {
253
- if ctx.props().session.is_column_active(x) {
326
+ if self.session_props.is_column_active(x) {
254
327
  ColumnSettingsTab::Style
255
328
  } else {
256
329
  ColumnSettingsTab::Attributes
@@ -303,6 +376,56 @@ impl Component for PerspectiveViewer {
303
376
 
304
377
  true
305
378
  },
379
+ UpdateSession(props) => {
380
+ let changed = *props != self.session_props;
381
+ self.session_props = *props;
382
+ changed
383
+ },
384
+ UpdateSessionStats(stats, has_table) => {
385
+ let changed =
386
+ stats != self.session_props.stats || has_table != self.session_props.has_table;
387
+ self.session_props.stats = stats;
388
+ self.session_props.has_table = has_table;
389
+ changed
390
+ },
391
+ UpdateRenderer(props) => {
392
+ let changed = *props != self.renderer_props;
393
+ self.renderer_props = *props;
394
+ changed
395
+ },
396
+ UpdatePresentation(props) => {
397
+ let changed = *props != self.presentation_props;
398
+ self.presentation_props = *props;
399
+ changed
400
+ },
401
+ UpdateSettingsOpen(open) => {
402
+ let changed = open != self.presentation_props.is_settings_open;
403
+ self.presentation_props.is_settings_open = open;
404
+ changed
405
+ },
406
+ UpdateIsWorkspace(is_workspace) => {
407
+ let changed = is_workspace != self.presentation_props.is_workspace;
408
+ self.presentation_props.is_workspace = is_workspace;
409
+ changed
410
+ },
411
+ UpdateColumnSettings(ocs) => {
412
+ let changed = *ocs != self.presentation_props.open_column_settings;
413
+ self.presentation_props.open_column_settings = *ocs;
414
+ changed
415
+ },
416
+ UpdateDragDrop(props) => {
417
+ let changed = *props != self.dragdrop_props;
418
+ self.dragdrop_props = *props;
419
+ changed
420
+ },
421
+ IncrementUpdateCount => {
422
+ self.update_count = self.update_count.saturating_add(1);
423
+ true
424
+ },
425
+ DecrementUpdateCount => {
426
+ self.update_count = self.update_count.saturating_sub(1);
427
+ true
428
+ },
306
429
  }
307
430
  }
308
431
 
@@ -334,13 +457,15 @@ impl Component for PerspectiveViewer {
334
457
  ..
335
458
  } = ctx.props();
336
459
 
337
- let is_settings_open = self.settings_open && ctx.props().session.has_table();
460
+ let is_settings_open = self.settings_open
461
+ && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
462
+
338
463
  let mut class = classes!();
339
464
  if !is_settings_open {
340
465
  class.push("settings-closed");
341
466
  }
342
467
 
343
- if ctx.props().is_title() {
468
+ if self.session_props.title.is_some() {
344
469
  class.push("titled");
345
470
  }
346
471
 
@@ -360,8 +485,28 @@ impl Component for PerspectiveViewer {
360
485
 
361
486
  let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
362
487
  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;
488
+ let selected_column = get_current_column_locator(
489
+ &self.presentation_props.open_column_settings,
490
+ &ctx.props().renderer,
491
+ &self.session_props.config,
492
+ &self.session_props.metadata,
493
+ );
494
+
495
+ let selected_tab = self.presentation_props.open_column_settings.tab;
496
+ let plugin_name = self.renderer_props.plugin_name.clone();
497
+ let available_plugins = self.renderer_props.available_plugins.clone();
498
+ 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);
506
+
507
+ let view_config = self.session_props.config.clone();
508
+ let drag_column = self.dragdrop_props.column.clone();
509
+ let metadata = self.session_props.metadata.clone();
365
510
  let settings_panel = html! {
366
511
  if is_settings_open {
367
512
  <SettingsPanel
@@ -370,6 +515,15 @@ impl Component for PerspectiveViewer {
370
515
  on_select_column={on_open_expr_panel}
371
516
  is_debug={self.debug_open}
372
517
  {on_debug}
518
+ {plugin_name}
519
+ {available_plugins}
520
+ {has_table}
521
+ {named_column_count}
522
+ {view_config}
523
+ {drag_column}
524
+ metadata={metadata.clone()}
525
+ open_column_settings={self.presentation_props.open_column_settings.clone()}
526
+ selected_theme={self.presentation_props.selected_theme.clone()}
373
527
  {dragdrop}
374
528
  {presentation}
375
529
  {renderer}
@@ -395,6 +549,10 @@ impl Component for PerspectiveViewer {
395
549
  on_close={self.on_close_column_settings.clone()}
396
550
  width_override={self.column_settings_panel_width_override}
397
551
  {on_select_tab}
552
+ plugin_name={self.renderer_props.plugin_name.clone()}
553
+ {metadata}
554
+ view_config={self.session_props.config.clone()}
555
+ selected_theme={self.presentation_props.selected_theme.clone()}
398
556
  {custom_events}
399
557
  {presentation}
400
558
  {renderer}
@@ -405,8 +563,38 @@ impl Component for PerspectiveViewer {
405
563
  }
406
564
  };
407
565
 
566
+ 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
+ let is_settings_open = self.settings_open
574
+ && 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();
408
578
  let main_panel = html! {
409
- <MainPanel {on_settings} {custom_events} {presentation} {renderer} {session} />
579
+ <MainPanel
580
+ {on_settings}
581
+ {on_reset}
582
+ {render_limits}
583
+ {has_table}
584
+ {is_errored}
585
+ {stats}
586
+ {update_count}
587
+ {error}
588
+ {is_settings_open}
589
+ {title}
590
+ {selected_theme}
591
+ {available_themes}
592
+ is_workspace={self.presentation_props.is_workspace}
593
+ {custom_events}
594
+ {presentation}
595
+ {renderer}
596
+ {session}
597
+ />
410
598
  };
411
599
 
412
600
  let debug_panel = html! {
@@ -523,3 +711,166 @@ impl PerspectiveViewer {
523
711
  };
524
712
  }
525
713
  }
714
+
715
+ /// Subscribe to PubSub events that still have non-root subscribers and
716
+ /// therefore cannot yet be replaced with direct callbacks.
717
+ fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
718
+ let session_props_sub = {
719
+ let session = ctx.props().session.clone();
720
+ let cb = ctx
721
+ .link()
722
+ .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
723
+
724
+ let s = &ctx.props().session;
725
+ let sub1 = s.table_loaded.add_notify_listener(&cb);
726
+ let sub2 = s.table_unloaded.add_notify_listener(&cb);
727
+ let sub3 = s.view_created.add_notify_listener(&cb);
728
+ let sub4 = s.view_config_changed.add_notify_listener(&cb);
729
+ let sub5 = s.title_changed.add_notify_listener(&cb);
730
+ let sub6 = s
731
+ .view_config_changed
732
+ .add_listener(ctx.link().callback(|_| IncrementUpdateCount));
733
+
734
+ let sub7 = s
735
+ .view_created
736
+ .add_listener(ctx.link().callback(|_| DecrementUpdateCount));
737
+
738
+ vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7]
739
+ };
740
+
741
+ let renderer_props_sub = {
742
+ let renderer = ctx.props().renderer.clone();
743
+ let cb_plugin = ctx.link().callback({
744
+ move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
745
+ });
746
+
747
+ let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
748
+ vec![sub1]
749
+ };
750
+
751
+ let presentation_props_sub = {
752
+ let presentation = ctx.props().presentation.clone();
753
+ let cb_settings = ctx.link().callback(UpdateSettingsOpen);
754
+ let cb_theme = {
755
+ let pres = presentation.clone();
756
+ ctx.link()
757
+ .callback(move |(themes, _): (PtrEqRc<Vec<String>>, _)| {
758
+ UpdatePresentation(Box::new(pres.to_props(themes)))
759
+ })
760
+ };
761
+
762
+ let cb_column_settings = {
763
+ let pres = presentation.clone();
764
+ ctx.link().callback(move |_: (bool, Option<String>)| {
765
+ UpdateColumnSettings(Box::new(pres.get_open_column_settings()))
766
+ })
767
+ };
768
+
769
+ let sub1 = presentation.settings_open_changed.add_listener(cb_settings);
770
+ let sub2 = presentation.theme_config_updated.add_listener(cb_theme);
771
+ let sub3 = presentation
772
+ .column_settings_open_changed
773
+ .add_listener(cb_column_settings);
774
+
775
+ vec![sub1, sub2, sub3]
776
+ };
777
+
778
+ let dragdrop_props_sub = {
779
+ let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
780
+ let sub1 = ctx
781
+ .props()
782
+ .dragdrop
783
+ .drop_received
784
+ .add_notify_listener(&cb_clear);
785
+
786
+ vec![sub1]
787
+ };
788
+
789
+ let mut subscriptions = Vec::new();
790
+ subscriptions.extend(session_props_sub);
791
+ subscriptions.extend(renderer_props_sub);
792
+ subscriptions.extend(presentation_props_sub);
793
+ subscriptions.extend(dragdrop_props_sub);
794
+ subscriptions
795
+ }
796
+
797
+ /// Inject direct callbacks into the engine handles, replacing PubSub fields
798
+ /// that were exclusively consumed by the root component.
799
+ fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
800
+ // Session: on_stats_changed
801
+ {
802
+ let session = ctx.props().session.clone();
803
+ let cb = ctx.link().callback(move |_: ()| {
804
+ UpdateSessionStats(session.get_table_stats(), session.has_table())
805
+ });
806
+
807
+ *ctx.props().session.on_stats_changed.borrow_mut() = Some(cb);
808
+ }
809
+
810
+ // Session: on_table_errored
811
+ {
812
+ let session = ctx.props().session.clone();
813
+ let cb = ctx
814
+ .link()
815
+ .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
816
+
817
+ *ctx.props().session.on_table_errored.borrow_mut() = Some(cb);
818
+ }
819
+
820
+ // Renderer: on_render_limits_changed (combines UpdateRenderer + column
821
+ // locator recheck that were previously two separate PubSub subscriptions).
822
+ {
823
+ clone!(
824
+ ctx.props().presentation,
825
+ ctx.props().renderer,
826
+ ctx.props().session
827
+ );
828
+
829
+ let cb = ctx.link().batch_callback(move |limits: RenderLimits| {
830
+ let mut msgs = vec![UpdateRenderer(Box::new(renderer.to_props(Some(limits))))];
831
+ if !limits.is_update {
832
+ let locator = get_current_column_locator(
833
+ &presentation.get_open_column_settings(),
834
+ &renderer,
835
+ &session.get_view_config(),
836
+ &session.metadata(),
837
+ );
838
+
839
+ msgs.push(OpenColumnSettings {
840
+ locator,
841
+ sender: None,
842
+ toggle: false,
843
+ });
844
+ }
845
+
846
+ msgs
847
+ });
848
+
849
+ *ctx.props().renderer.on_render_limits_changed.borrow_mut() = Some(cb);
850
+ }
851
+
852
+ // Presentation: on_is_workspace_changed
853
+ {
854
+ let cb = ctx.link().callback(UpdateIsWorkspace);
855
+ *ctx.props()
856
+ .presentation
857
+ .on_is_workspace_changed
858
+ .borrow_mut() = Some(cb);
859
+ }
860
+
861
+ // DragDrop: on_dragstart
862
+ {
863
+ let dragdrop = ctx.props().dragdrop.clone();
864
+ let cb = ctx
865
+ .link()
866
+ .callback(move |_: DragEffect| UpdateDragDrop(Box::new(dragdrop.to_props())));
867
+
868
+ *ctx.props().dragdrop.on_dragstart.borrow_mut() = Some(cb);
869
+ }
870
+
871
+ // DragDrop: on_dragend
872
+ {
873
+ let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
874
+ *ctx.props().dragdrop.on_dragend.borrow_mut() = Some(cb);
875
+ }
876
+ }
@@ -29,6 +29,9 @@ pub enum NumberForegroundMode {
29
29
 
30
30
  #[serde(rename = "bar")]
31
31
  Bar,
32
+
33
+ #[serde(rename = "label-bar")]
34
+ LabelBar,
32
35
  }
33
36
 
34
37
  impl FromStr for NumberForegroundMode {
@@ -38,6 +41,7 @@ impl FromStr for NumberForegroundMode {
38
41
  match s {
39
42
  "color" => Ok(Self::Color),
40
43
  "bar" => Ok(Self::Bar),
44
+ "label-bar" => Ok(Self::LabelBar),
41
45
  x => Err(format!("Unknown NumberForegroundMode::{x}")),
42
46
  }
43
47
  }
@@ -53,7 +57,7 @@ impl NumberForegroundMode {
53
57
  }
54
58
 
55
59
  pub fn needs_gradient(&self) -> bool {
56
- *self == Self::Bar
60
+ *self == Self::Bar || *self == Self::LabelBar
57
61
  }
58
62
  }
59
63
 
@@ -26,9 +26,9 @@ use crate::presentation::ColumnConfigMap;
26
26
 
27
27
  /// The state of an entire `custom_elements::PerspectiveViewerElement` component
28
28
  /// and its `Plugin`.
29
- #[derive(Debug, Default, Serialize, PartialEq)]
29
+ #[derive(Debug, Default, Serialize, PartialEq, TS)]
30
30
  #[serde(deny_unknown_fields)]
31
- pub struct ViewerConfig<V = String> {
31
+ pub struct ViewerConfig<V: TS = String> {
32
32
  pub version: V,
33
33
  pub columns_config: ColumnConfigMap,
34
34
  pub plugin: String,