@perspective-dev/viewer 4.4.1 → 4.5.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 (225) hide show
  1. package/dist/cdn/perspective-viewer.js +1 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -1
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/esm/bootstrap.d.ts +2 -1
  24. package/dist/esm/column-format.d.ts +51 -0
  25. package/dist/esm/extensions.d.ts +2 -0
  26. package/dist/esm/perspective-viewer.d.ts +3 -1
  27. package/dist/esm/perspective-viewer.inline.js +1 -2
  28. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  29. package/dist/esm/perspective-viewer.js +1 -2
  30. package/dist/esm/perspective-viewer.js.map +4 -4
  31. package/dist/esm/perspective-viewer.worker.d.ts +2 -0
  32. package/dist/esm/plugin.d.ts +16 -72
  33. package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
  34. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
  35. package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -3
  36. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
  37. package/dist/wasm/perspective-viewer.d.ts +77 -18
  38. package/dist/wasm/perspective-viewer.js +293 -144
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
  41. package/package.json +24 -2
  42. package/src/css/column-selector.css +3 -2
  43. package/src/css/column-settings-panel.css +35 -6
  44. package/src/css/column-style.css +27 -2
  45. package/src/css/containers/scroll-panel.css +2 -1
  46. package/src/css/containers/tabs.css +8 -52
  47. package/src/css/dom/checkbox.css +0 -4
  48. package/src/css/form/code-editor.css +1 -0
  49. package/src/css/form/debug.css +3 -10
  50. package/src/css/plugin-selector.css +33 -0
  51. package/src/css/plugin-settings-panel.css +99 -0
  52. package/src/css/viewer.css +65 -3
  53. package/src/rust/components/column_dropdown.rs +3 -1
  54. package/src/rust/components/column_selector/active_column.rs +13 -19
  55. package/src/rust/components/column_selector/config_selector.rs +20 -20
  56. package/src/rust/components/column_selector/filter_column.rs +14 -14
  57. package/src/rust/components/column_selector/inactive_column.rs +9 -15
  58. package/src/rust/components/column_selector/pivot_column.rs +7 -7
  59. package/src/rust/components/column_selector/sort_column.rs +7 -7
  60. package/src/rust/components/column_selector.rs +55 -37
  61. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
  62. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +394 -0
  63. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
  64. package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
  65. package/src/rust/components/column_settings_sidebar.rs +43 -49
  66. package/src/rust/components/containers/dragdrop_list.rs +5 -5
  67. package/src/rust/components/containers/mod.rs +0 -1
  68. package/src/rust/components/containers/scroll_panel.rs +21 -7
  69. package/src/rust/components/containers/sidebar.rs +8 -6
  70. package/src/rust/components/containers/split_panel.rs +3 -3
  71. package/src/rust/components/containers/tab_list.rs +3 -9
  72. package/src/rust/components/copy_dropdown.rs +2 -3
  73. package/src/rust/components/datetime_column_style.rs +19 -81
  74. package/src/rust/components/editable_header.rs +2 -3
  75. package/src/rust/components/export_dropdown.rs +2 -3
  76. package/src/rust/components/expression_editor.rs +29 -17
  77. package/src/rust/components/filter_dropdown.rs +2 -1
  78. package/src/rust/components/form/color_range_selector.rs +14 -7
  79. package/src/rust/components/form/debug.rs +47 -37
  80. package/src/rust/components/main_panel.rs +24 -65
  81. package/src/rust/components/mod.rs +2 -1
  82. package/src/rust/components/number_series_style.rs +161 -0
  83. package/src/rust/components/plugin_tab.rs +221 -0
  84. package/src/rust/components/settings_panel.rs +181 -59
  85. package/src/rust/components/status_bar.rs +140 -173
  86. package/src/rust/components/status_indicator.rs +15 -22
  87. package/src/rust/components/string_column_style.rs +20 -82
  88. package/src/rust/components/style_controls/number_string_format.rs +14 -30
  89. package/src/rust/components/viewer.rs +92 -131
  90. package/src/rust/config/column_config_schema.rs +195 -0
  91. package/src/rust/config/columns_config.rs +4 -97
  92. package/src/rust/config/datetime_column_style.rs +0 -5
  93. package/src/rust/config/mod.rs +8 -2
  94. package/src/rust/config/number_series_style.rs +79 -0
  95. package/src/rust/config/plugin_static_config.rs +144 -0
  96. package/src/rust/config/string_column_style.rs +0 -5
  97. package/src/rust/config/viewer_config.rs +5 -6
  98. package/src/rust/custom_elements/copy_dropdown.rs +30 -18
  99. package/src/rust/custom_elements/debug_plugin.rs +1 -3
  100. package/src/rust/custom_elements/export_dropdown.rs +26 -18
  101. package/src/rust/custom_elements/viewer.rs +62 -73
  102. package/src/rust/custom_events.rs +181 -224
  103. package/src/rust/js/plugin.rs +45 -117
  104. package/src/rust/lib.rs +34 -5
  105. package/src/rust/presentation/drag_helpers.rs +206 -0
  106. package/src/rust/presentation/props.rs +8 -0
  107. package/src/rust/presentation.rs +256 -41
  108. package/src/rust/{tasks → queries}/column_locator.rs +17 -73
  109. package/src/rust/queries/column_values.rs +59 -0
  110. package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
  111. package/src/rust/queries/exports.rs +96 -0
  112. package/src/rust/queries/fetch_column_stats.rs +94 -0
  113. package/src/rust/queries/get_viewer_config.rs +54 -0
  114. package/src/rust/queries/mod.rs +44 -0
  115. package/src/rust/queries/plugin_column_styles.rs +101 -0
  116. package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
  117. package/src/rust/renderer/activate.rs +1 -0
  118. package/src/rust/renderer/limits.rs +9 -4
  119. package/src/rust/renderer/plugin_store.rs +12 -0
  120. package/src/rust/renderer/props.rs +28 -3
  121. package/src/rust/renderer/registry.rs +40 -15
  122. package/src/rust/renderer.rs +640 -51
  123. package/src/rust/session/column_defaults_update.rs +20 -28
  124. package/src/rust/session/drag_drop_update.rs +10 -10
  125. package/src/rust/session/metadata.rs +31 -16
  126. package/src/rust/session/props.rs +15 -6
  127. package/src/rust/session/view_subscription.rs +10 -0
  128. package/src/rust/session.rs +109 -147
  129. package/src/rust/tasks/copy_export.rs +178 -158
  130. package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
  131. package/src/rust/tasks/edit_expression.rs +68 -88
  132. package/src/rust/tasks/eject.rs +25 -22
  133. package/src/rust/tasks/intersection_observer.rs +8 -21
  134. package/src/rust/tasks/mod.rs +19 -21
  135. package/src/rust/tasks/reset_all.rs +78 -0
  136. package/src/rust/tasks/resize_observer.rs +11 -33
  137. package/src/rust/tasks/restore_and_render.rs +117 -90
  138. package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +38 -35
  139. package/src/rust/tasks/send_plugin_config.rs +32 -33
  140. package/src/rust/tasks/update_and_render.rs +66 -47
  141. package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
  142. package/src/rust/tasks/validate_expression.rs +61 -0
  143. package/src/rust/utils/browser/selection.rs +4 -4
  144. package/src/rust/utils/mod.rs +0 -63
  145. package/src/svg/mega-menu-icons-density.svg +23 -0
  146. package/src/svg/mega-menu-icons-map-density.svg +24 -0
  147. package/src/svg/mega-menu-icons-map-line.svg +19 -0
  148. package/src/themes/botanical.css +27 -53
  149. package/src/themes/defaults.css +24 -36
  150. package/src/themes/dracula.css +36 -54
  151. package/src/themes/gruvbox-dark.css +39 -59
  152. package/src/themes/gruvbox.css +16 -28
  153. package/src/themes/icons.css +3 -0
  154. package/src/themes/intl/de.css +42 -6
  155. package/src/themes/intl/es.css +42 -6
  156. package/src/themes/intl/fr.css +42 -6
  157. package/src/themes/intl/ja.css +42 -6
  158. package/src/themes/intl/pt.css +42 -6
  159. package/src/themes/intl/zh.css +42 -6
  160. package/src/themes/intl.css +37 -4
  161. package/src/themes/monokai.css +45 -61
  162. package/src/themes/phosphor.css +20 -29
  163. package/src/themes/pro-dark.css +25 -34
  164. package/src/themes/solarized-dark.css +21 -36
  165. package/src/themes/solarized.css +13 -23
  166. package/src/themes/vaporwave.css +40 -74
  167. package/src/ts/bootstrap.ts +14 -3
  168. package/src/ts/column-format.ts +162 -0
  169. package/src/ts/extensions.ts +4 -0
  170. package/src/ts/perspective-viewer.ts +9 -1
  171. package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
  172. package/src/ts/plugin.ts +25 -101
  173. package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
  174. package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
  175. package/src/ts/ts-rs/ViewerConfig.ts +1 -2
  176. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
  177. package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
  178. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
  179. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
  180. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
  181. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  182. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
  183. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  184. package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
  185. package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
  186. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
  187. package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
  188. package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
  189. package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
  190. package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
  191. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
  192. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
  193. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  194. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
  195. package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
  196. package/src/rust/components/number_column_style.rs +0 -491
  197. package/src/rust/config/number_column_style.rs +0 -136
  198. package/src/rust/dragdrop.rs +0 -481
  199. package/src/rust/tasks/plugin_column_styles.rs +0 -98
  200. package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
  201. package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
  202. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
  203. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
  204. package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
  205. package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
  206. package/src/ts/ts-rs/FormatMode.ts +0 -3
  207. package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
  208. package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
  209. package/src/ts/ts-rs/PluginConfig.ts +0 -4
  210. package/src/ts/ts-rs/RoundingMode.ts +0 -3
  211. package/src/ts/ts-rs/RoundingPriority.ts +0 -3
  212. package/src/ts/ts-rs/SignDisplay.ts +0 -3
  213. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
  214. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
  215. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  216. package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
  217. package/src/ts/ts-rs/UseGrouping.ts +0 -3
  218. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline0.js +0 -0
  219. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline1.js +0 -0
  220. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline2.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline3.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline4.js +0 -0
  223. /package/src/rust/{tasks → config}/export_method.rs +0 -0
  224. /package/src/rust/{tasks → queries}/export_app.rs +0 -0
  225. /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
@@ -21,9 +21,9 @@ use crate::components::copy_dropdown::CopyDropDownMenu;
21
21
  use crate::components::export_dropdown::ExportDropDownMenu;
22
22
  use crate::components::portal::PortalModal;
23
23
  use crate::components::status_bar_counter::StatusBarRowsCounter;
24
- use crate::custom_events::CustomEvents;
24
+ use crate::config::*;
25
25
  use crate::js::*;
26
- use crate::presentation::Presentation;
26
+ use crate::presentation::{Presentation, PresentationProps};
27
27
  use crate::renderer::*;
28
28
  use crate::session::*;
29
29
  use crate::tasks::*;
@@ -42,27 +42,21 @@ pub struct StatusBarProps {
42
42
  #[prop_or_default]
43
43
  pub on_settings: Option<Callback<()>>,
44
44
 
45
- // Value props threaded from the root's `SessionProps`.
46
- // Using these avoids PubSub subscriptions for table_loaded / table_errored.
47
- pub has_table: Option<TableLoadState>,
48
- pub is_errored: bool,
49
- pub stats: Option<ViewStats>,
50
- /// In-flight render counter and full error, threaded to `StatusIndicator`.
51
- pub update_count: u32,
52
- pub error: Option<TableErrorState>,
53
- /// Title string from session — threaded to avoid title_changed
54
- /// subscription.
55
- pub title: Option<String>,
56
- /// Theme state from presentation — threaded to avoid theme_config_updated /
57
- /// visibility_changed subscriptions.
45
+ /// Snapshots threaded from root. Component reads `has_table`, `stats`,
46
+ /// `error`, `title` from session_props; `selected_theme`,
47
+ /// `available_themes`, `is_workspace` from presentation_props.
48
+ pub session_props: SessionProps,
49
+ pub presentation_props: PresentationProps,
50
+
51
+ /// Derived from root: `settings_open && has_table_loaded`. Used
52
+ /// here to drive the title-input enabled state and the theme picker
53
+ /// visibility.
58
54
  pub is_settings_open: bool,
59
- pub selected_theme: Option<String>,
60
- pub available_themes: PtrEqRc<Vec<String>>,
61
- /// Whether this viewer is hosted inside a `<perspective-workspace>`.
62
- pub is_workspace: bool,
55
+
56
+ /// In-flight render counter, threaded to `StatusIndicator`.
57
+ pub update_count: u32,
63
58
 
64
59
  // State
65
- pub custom_events: CustomEvents,
66
60
  pub session: Session,
67
61
  pub renderer: Renderer,
68
62
  pub presentation: Presentation,
@@ -71,48 +65,10 @@ pub struct StatusBarProps {
71
65
  impl PartialEq for StatusBarProps {
72
66
  fn eq(&self, other: &Self) -> bool {
73
67
  self.id == other.id
74
- && self.has_table == other.has_table
75
- && self.is_errored == other.is_errored
76
- && self.stats == other.stats
77
- && self.update_count == other.update_count
78
- && self.error == other.error
79
- && self.title == other.title
68
+ && self.session_props == other.session_props
69
+ && self.presentation_props == other.presentation_props
80
70
  && self.is_settings_open == other.is_settings_open
81
- && self.selected_theme == other.selected_theme
82
- && self.available_themes == other.available_themes
83
- && self.is_workspace == other.is_workspace
84
- }
85
- }
86
-
87
- impl HasCustomEvents for StatusBarProps {
88
- fn custom_events(&self) -> &CustomEvents {
89
- &self.custom_events
90
- }
91
- }
92
-
93
- impl HasPresentation for StatusBarProps {
94
- fn presentation(&self) -> &Presentation {
95
- &self.presentation
96
- }
97
- }
98
-
99
- impl HasRenderer for StatusBarProps {
100
- fn renderer(&self) -> &Renderer {
101
- &self.renderer
102
- }
103
- }
104
-
105
- impl HasSession for StatusBarProps {
106
- fn session(&self) -> &Session {
107
- &self.session
108
- }
109
- }
110
-
111
- impl StateProvider for StatusBarProps {
112
- type State = StatusBarProps;
113
-
114
- fn clone_state(&self) -> Self::State {
115
- self.clone()
71
+ && self.update_count == other.update_count
116
72
  }
117
73
  }
118
74
 
@@ -155,7 +111,7 @@ impl Component for StatusBar {
155
111
  export_ref: NodeRef::default(),
156
112
  input_ref: NodeRef::default(),
157
113
  statusbar_ref: NodeRef::default(),
158
- title: ctx.props().title.clone(),
114
+ title: ctx.props().session_props.title.clone(),
159
115
  copy_target: None,
160
116
  export_target: None,
161
117
  }
@@ -165,119 +121,117 @@ impl Component for StatusBar {
165
121
  // Keep the local title in sync with the prop whenever the session title
166
122
  // changes externally (e.g. restore() call) or the settings panel opens /
167
123
  // closes (which resets the input element).
168
- if ctx.props().title != old_props.title
124
+ if ctx.props().session_props.title != old_props.session_props.title
169
125
  || ctx.props().is_settings_open != old_props.is_settings_open
170
126
  {
171
- self.title = ctx.props().title.clone();
127
+ self.title = ctx.props().session_props.title.clone();
172
128
  }
173
129
  true
174
130
  }
175
131
 
176
132
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
177
- maybe_log_or_default!(Ok(match msg {
178
- StatusBarMsg::Reset(event) => {
179
- let all = event.shift_key();
180
- ctx.props().on_reset.emit(all);
181
- false
182
- },
183
- StatusBarMsg::ResetTheme => {
184
- let presentation = ctx.props().presentation.clone();
185
- let session = ctx.props().session.clone();
186
- let renderer = ctx.props().renderer.clone();
187
- ApiFuture::spawn(async move {
188
- presentation.reset_theme().await?;
189
- let view = session.get_view().into_apierror()?;
190
- renderer.restyle_all(&view).await
191
- });
192
- true
193
- },
194
- StatusBarMsg::SetTheme(theme_name) => {
195
- let presentation = ctx.props().presentation.clone();
196
- let session = ctx.props().session.clone();
197
- let renderer = ctx.props().renderer.clone();
198
- ApiFuture::spawn(async move {
199
- presentation.set_theme_name(Some(&theme_name)).await?;
200
- let view = session.get_view().into_apierror()?;
201
- renderer.restyle_all(&view).await
202
- });
203
-
204
- false
205
- },
206
- StatusBarMsg::Export => {
207
- self.export_target = self.export_ref.cast::<HtmlElement>();
208
- true
209
- },
210
- StatusBarMsg::Copy => {
211
- self.copy_target = self.copy_ref.cast::<HtmlElement>();
212
- true
213
- },
214
- StatusBarMsg::CloseExport => {
215
- self.export_target = None;
216
- true
217
- },
218
- StatusBarMsg::CloseCopy => {
219
- self.copy_target = None;
220
- true
221
- },
222
- StatusBarMsg::Eject => {
223
- ctx.props().presentation().on_eject.emit(());
224
- false
225
- },
226
- StatusBarMsg::Noop => {
227
- self.title = ctx.props().title.clone();
228
- true
229
- },
230
- StatusBarMsg::TitleInputEvent => {
231
- let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
232
- let title = elem.value();
233
- let title = if title.trim().is_empty() {
234
- None
235
- } else {
236
- Some(title)
237
- };
238
-
239
- self.title = title;
240
- true
241
- },
242
- StatusBarMsg::TitleChangeEvent => {
243
- let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
244
- let title = elem.value();
245
- let title = if title.trim().is_empty() {
246
- None
247
- } else {
248
- Some(title)
249
- };
250
-
251
- ctx.props().session().set_title(title);
252
- false
253
- },
254
- StatusBarMsg::PointerEvent(event) => {
255
- if event.target().map(JsValue::from)
256
- == self.statusbar_ref.cast::<HtmlElement>().map(JsValue::from)
257
- {
258
- ctx.props()
259
- .custom_events()
260
- .dispatch_event(format!("statusbar-{}", event.type_()).as_str(), &event)?;
261
- }
262
-
263
- false
264
- },
265
- }))
133
+ let r: ApiResult<bool> = (|| {
134
+ Ok(match msg {
135
+ StatusBarMsg::Reset(event) => {
136
+ let all = event.shift_key();
137
+ ctx.props().on_reset.emit(all);
138
+ false
139
+ },
140
+ StatusBarMsg::ResetTheme => {
141
+ update_theme(
142
+ &ctx.props().session,
143
+ &ctx.props().renderer,
144
+ &ctx.props().presentation,
145
+ None,
146
+ );
147
+ true
148
+ },
149
+ StatusBarMsg::SetTheme(theme_name) => {
150
+ update_theme(
151
+ &ctx.props().session,
152
+ &ctx.props().renderer,
153
+ &ctx.props().presentation,
154
+ Some(theme_name),
155
+ );
156
+ false
157
+ },
158
+ StatusBarMsg::Export => {
159
+ self.export_target = self.export_ref.cast::<HtmlElement>();
160
+ true
161
+ },
162
+ StatusBarMsg::Copy => {
163
+ self.copy_target = self.copy_ref.cast::<HtmlElement>();
164
+ true
165
+ },
166
+ StatusBarMsg::CloseExport => {
167
+ self.export_target = None;
168
+ true
169
+ },
170
+ StatusBarMsg::CloseCopy => {
171
+ self.copy_target = None;
172
+ true
173
+ },
174
+ StatusBarMsg::Eject => {
175
+ ctx.props().presentation.on_eject.emit(());
176
+ false
177
+ },
178
+ StatusBarMsg::Noop => {
179
+ self.title = ctx.props().session_props.title.clone();
180
+ true
181
+ },
182
+ StatusBarMsg::TitleInputEvent => {
183
+ let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
184
+ let title = elem.value();
185
+ let title = if title.trim().is_empty() {
186
+ None
187
+ } else {
188
+ Some(title)
189
+ };
190
+
191
+ self.title = title;
192
+ true
193
+ },
194
+ StatusBarMsg::TitleChangeEvent => {
195
+ let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
196
+ let title = elem.value();
197
+ let title = if title.trim().is_empty() {
198
+ None
199
+ } else {
200
+ Some(title)
201
+ };
202
+
203
+ ctx.props().session.set_title(title);
204
+ false
205
+ },
206
+ StatusBarMsg::PointerEvent(event) => {
207
+ if event.target().map(JsValue::from)
208
+ == self.statusbar_ref.cast::<HtmlElement>().map(JsValue::from)
209
+ {
210
+ ctx.props().presentation.statusbar_pointer_event.emit(event);
211
+ }
212
+
213
+ false
214
+ },
215
+ })
216
+ })();
217
+ r.unwrap_or_else(|e| {
218
+ web_sys::console::warn_1(&e.into());
219
+ Default::default()
220
+ })
266
221
  }
267
222
 
268
223
  fn view(&self, ctx: &Context<Self>) -> Html {
269
224
  let Self::Properties {
270
- custom_events,
271
225
  presentation,
272
226
  renderer,
273
227
  session,
274
228
  ..
275
229
  } = ctx.props();
276
230
 
277
- let has_table = ctx.props().has_table.clone();
278
- let is_errored = ctx.props().is_errored;
231
+ let has_table = ctx.props().session_props.has_table.clone();
232
+ let is_errored = ctx.props().session_props.is_errored();
279
233
  let is_settings_open = ctx.props().is_settings_open;
280
- let title = &ctx.props().title;
234
+ let title = &ctx.props().session_props.title;
281
235
 
282
236
  let mut is_updating_class_name = classes!();
283
237
  if title.is_some() {
@@ -310,13 +264,13 @@ impl Component for StatusBar {
310
264
  let is_menu = matches!(has_table, Some(TableLoadState::Loaded))
311
265
  && ctx.props().on_settings.as_ref().is_none();
312
266
  let is_title = is_menu
313
- || ctx.props().is_workspace
267
+ || ctx.props().presentation_props.is_workspace
314
268
  || title.is_some()
315
269
  || is_errored
316
270
  || presentation.is_active(&self.input_ref.cast::<Element>());
317
271
 
318
272
  let is_settings = title.is_some()
319
- || ctx.props().is_workspace
273
+ || ctx.props().presentation_props.is_workspace
320
274
  || !matches!(has_table, Some(TableLoadState::Loaded))
321
275
  || is_errored
322
276
  || is_settings_open
@@ -330,12 +284,21 @@ impl Component for StatusBar {
330
284
  let link = link.clone();
331
285
  spawn_local(async move {
332
286
  let mime = x.method.mimetype(x.is_chart);
333
- let task = props.export_method_to_blob(x.method);
287
+ let task = export_method_to_blob(
288
+ &props.session,
289
+ &props.renderer,
290
+ &props.presentation,
291
+ x.method,
292
+ );
334
293
  let result = copy_to_clipboard(task, mime).await;
335
- crate::maybe_log!({
294
+ let r = (|| -> ApiResult<()> {
336
295
  result?;
337
296
  link.send_message(StatusBarMsg::CloseCopy);
338
- })
297
+ Ok(())
298
+ })();
299
+ if let Err(e) = r {
300
+ web_sys::console::warn_1(&e.into());
301
+ }
339
302
  })
340
303
  })
341
304
  };
@@ -347,8 +310,15 @@ impl Component for StatusBar {
347
310
  if !x.name.is_empty() {
348
311
  clone!(props, link);
349
312
  spawn_local(async move {
350
- let val = props.export_method_to_blob(x.method).await.unwrap();
351
- let is_chart = props.renderer().is_chart();
313
+ let val = export_method_to_blob(
314
+ &props.session,
315
+ &props.renderer,
316
+ &props.presentation,
317
+ x.method,
318
+ )
319
+ .await
320
+ .unwrap();
321
+ let is_chart = props.renderer.is_chart();
352
322
  download(&x.as_filename(is_chart), &val).unwrap();
353
323
  link.send_message(StatusBarMsg::CloseExport);
354
324
  })
@@ -370,13 +340,10 @@ impl Component for StatusBar {
370
340
  {onpointerdown}
371
341
  >
372
342
  <StatusIndicator
373
- {custom_events}
374
343
  {renderer}
375
344
  {session}
376
345
  update_count={ctx.props().update_count}
377
- error={ctx.props().error.clone()}
378
- has_table={ctx.props().has_table.clone()}
379
- stats={ctx.props().stats.clone()}
346
+ session_props={ctx.props().session_props.clone()}
380
347
  />
381
348
  if is_title {
382
349
  <label
@@ -396,14 +363,14 @@ impl Component for StatusBar {
396
363
  </label>
397
364
  }
398
365
  if is_title {
399
- <StatusBarRowsCounter stats={ctx.props().stats.clone()} />
366
+ <StatusBarRowsCounter stats={ctx.props().session_props.stats.clone()} />
400
367
  }
401
368
  <div id="spacer" />
402
369
  if is_menu {
403
370
  <div id="menu-bar" class="section">
404
371
  <ThemeSelector
405
- theme={ctx.props().selected_theme.clone()}
406
- themes={ctx.props().available_themes.clone()}
372
+ theme={ctx.props().presentation_props.selected_theme.clone()}
373
+ themes={ctx.props().presentation_props.available_themes.clone()}
407
374
  on_change={ctx.link().callback(StatusBarMsg::SetTheme)}
408
375
  on_reset={ctx.link().callback(|_| StatusBarMsg::ResetTheme)}
409
376
  />
@@ -454,7 +421,7 @@ impl Component for StatusBar {
454
421
  target={self.copy_target.clone()}
455
422
  own_focus=true
456
423
  on_close={on_close_copy}
457
- theme={ctx.props().selected_theme.clone().unwrap_or_default()}
424
+ theme={ctx.props().presentation_props.selected_theme.clone().unwrap_or_default()}
458
425
  >
459
426
  <CopyDropDownMenu renderer={renderer.clone()} callback={on_copy_select} />
460
427
  </PortalModal>
@@ -463,7 +430,7 @@ impl Component for StatusBar {
463
430
  target={self.export_target.clone()}
464
431
  own_focus=true
465
432
  on_close={on_close_export}
466
- theme={ctx.props().selected_theme.clone().unwrap_or_default()}
433
+ theme={ctx.props().presentation_props.selected_theme.clone().unwrap_or_default()}
467
434
  >
468
435
  <ExportDropDownMenu
469
436
  renderer={renderer.clone()}
@@ -12,32 +12,27 @@
12
12
 
13
13
  use perspective_client::config::ViewConfigUpdate;
14
14
  use perspective_js::utils::ApiError;
15
- use wasm_bindgen::JsValue;
16
15
  use web_sys::*;
17
16
  use yew::prelude::*;
18
17
 
19
- use crate::custom_events::CustomEvents;
20
18
  use crate::renderer::Renderer;
21
- use crate::session::{Session, TableErrorState, TableLoadState, ViewStats};
19
+ use crate::session::{Session, SessionProps, TableLoadState};
22
20
  use crate::utils::*;
23
21
 
24
22
  /// Value-prop version: no PubSub subscriptions, no reducer.
25
23
  /// The parent (`StatusBar`) re-renders this component whenever
26
- /// `update_count`, `error`, or `stats` change (via root's
27
- /// `IncrementUpdateCount` / `DecrementUpdateCount` / `UpdateSession` messages).
24
+ /// `session_props.error/has_table/stats` or `update_count` change (via
25
+ /// root's `IncrementUpdateCount` / `DecrementUpdateCount` / `UpdateSession`
26
+ /// messages).
28
27
  #[derive(PartialEq, Properties)]
29
28
  pub struct StatusIndicatorProps {
30
- pub custom_events: CustomEvents,
31
29
  pub renderer: Renderer,
32
30
  pub session: Session,
33
31
  /// Number of in-flight renders (>0 → "updating" spinner).
34
32
  pub update_count: u32,
35
- /// Full error state (if any), used for the error dialog and reconnect.
36
- pub error: Option<TableErrorState>,
37
- /// Whether a table has been loaded.
38
- pub has_table: Option<TableLoadState>,
39
- /// Row/column statistics — used to distinguish "loading" from "connected".
40
- pub stats: Option<ViewStats>,
33
+ /// Snapshot of session value props read for `error`, `has_table`,
34
+ /// `stats` to derive the icon state.
35
+ pub session_props: SessionProps,
41
36
  }
42
37
 
43
38
  /// An indicator component which displays the current status of the perspective
@@ -46,19 +41,22 @@ pub struct StatusIndicatorProps {
46
41
  #[function_component]
47
42
  pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
48
43
  let has_table_cells = props
44
+ .session_props
49
45
  .stats
50
46
  .as_ref()
51
47
  .and_then(|s| s.num_table_cells)
52
48
  .is_some();
53
49
 
54
- let state = if let Some(err) = &props.error {
50
+ let state = if let Some(err) = &props.session_props.error {
55
51
  StatusIconState::Errored(
56
52
  err.message(),
57
53
  err.stacktrace(),
58
54
  err.kind(),
59
55
  err.is_reconnect(),
60
56
  )
61
- } else if !has_table_cells && matches!(props.has_table, Some(TableLoadState::Loading)) {
57
+ } else if !has_table_cells
58
+ && matches!(props.session_props.has_table, Some(TableLoadState::Loading))
59
+ {
62
60
  StatusIconState::Loading
63
61
  } else if props.update_count > 0 {
64
62
  StatusIconState::Updating
@@ -78,13 +76,8 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
78
76
  };
79
77
 
80
78
  let onclick = use_async_callback(
81
- (
82
- props.session.clone(),
83
- props.renderer.clone(),
84
- props.custom_events.clone(),
85
- state.clone(),
86
- ),
87
- async move |_: MouseEvent, (session, renderer, custom_events, state)| {
79
+ (props.session.clone(), props.renderer.clone(), state.clone()),
80
+ async move |_: MouseEvent, (session, renderer, state)| {
88
81
  match &state {
89
82
  StatusIconState::Errored(..) => {
90
83
  session.reconnect().await?;
@@ -96,7 +89,7 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
96
89
  .await?;
97
90
  },
98
91
  StatusIconState::Normal => {
99
- custom_events.dispatch_event("status-indicator-click", JsValue::UNDEFINED)?;
92
+ session.status_indicator_clicked.emit(());
100
93
  },
101
94
  _ => {},
102
95
  };