@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
@@ -10,249 +10,206 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use std::cell::RefCell;
14
- use std::ops::Deref;
15
- use std::rc::Rc;
13
+ //! Wire engine PubSub fanout to JavaScript `CustomEvent` dispatch on the host
14
+ //! element. [`wire_custom_events`] is the single subscription site; every
15
+ //! `perspective-*` `CustomEvent` originates from a PubSub fire on `Session`,
16
+ //! `Renderer`, or `Presentation`.
16
17
 
17
18
  use perspective_client::{ViewWindow, clone};
18
19
  use perspective_js::json;
20
+ use perspective_js::utils::{ApiResult, JsValueSerdeExt};
19
21
  use wasm_bindgen::prelude::*;
20
22
  use web_sys::*;
21
- use yew::html::ImplicitClone;
22
23
 
23
- use crate::config::*;
24
24
  use crate::js::JsPerspectiveViewerPlugin;
25
25
  use crate::presentation::Presentation;
26
- use crate::renderer::*;
26
+ use crate::queries::get_viewer_config;
27
+ use crate::renderer::{ColumnConfigMap, Renderer};
27
28
  use crate::session::Session;
28
- use crate::tasks::*;
29
- use crate::utils::*;
30
- use crate::*;
31
-
32
- /// A collection of [`Subscription`]s which should trigger an event on the
33
- /// JavaScript Custom Element as a [`CustomEvent`]. There are no public methods
34
- /// on `CustomElements`, but when it is `drop()` the Custom Element will no
35
- /// longer dispatch events such as `"perspective-config-change"`.
36
- #[derive(Clone)]
37
- pub struct CustomEvents(Rc<(CustomEventsDataRc, [Subscription; 8])>);
38
-
39
- impl ImplicitClone for CustomEvents {}
40
-
41
- impl PartialEq for CustomEvents {
42
- fn eq(&self, other: &Self) -> bool {
43
- Rc::ptr_eq(&self.0, &other.0)
44
- }
45
- }
46
-
47
- #[derive(Clone)]
48
- struct CustomEventsDataRc(Rc<CustomEventsData>);
49
-
50
- impl Deref for CustomEventsDataRc {
51
- type Target = CustomEventsData;
52
-
53
- fn deref(&self) -> &CustomEventsData {
54
- &self.0
55
- }
29
+ use crate::utils::{AddListener, Subscription};
30
+
31
+ /// Dispatch a JS `CustomEvent` named `perspective-{name}` on `elem`.
32
+ fn dispatch_event<T: Into<JsValue>>(elem: &HtmlElement, name: &str, event: T) -> ApiResult<()> {
33
+ let event_init = web_sys::CustomEventInit::new();
34
+ event_init.set_detail(&event.into());
35
+ let event = web_sys::CustomEvent::new_with_event_init_dict(
36
+ format!("perspective-{}", name).as_str(),
37
+ &event_init,
38
+ )?;
39
+
40
+ elem.dispatch_event(&event)?;
41
+ Ok(())
56
42
  }
57
43
 
58
- struct CustomEventsData {
59
- elem: HtmlElement,
60
- session: Session,
61
- renderer: Renderer,
62
- presentation: Presentation,
63
- last_dispatched: RefCell<Option<ViewerConfig>>,
44
+ fn dispatch_column_settings_open_changed(
45
+ elem: &HtmlElement,
46
+ open: bool,
47
+ column_name: Option<String>,
48
+ ) {
49
+ let event_init = web_sys::CustomEventInit::new();
50
+ event_init.set_detail(&JsValue::from(
51
+ json!({"open": open, "column_name": column_name}),
52
+ ));
53
+ let event = web_sys::CustomEvent::new_with_event_init_dict(
54
+ "perspective-toggle-column-settings",
55
+ &event_init,
56
+ );
57
+
58
+ elem.dispatch_event(&event.unwrap()).unwrap();
64
59
  }
65
60
 
66
- impl HasPresentation for CustomEventsData {
67
- fn presentation(&self) -> &Presentation {
68
- &self.presentation
69
- }
70
- }
71
-
72
- impl HasRenderer for CustomEventsData {
73
- fn renderer(&self) -> &Renderer {
74
- &self.renderer
75
- }
76
- }
61
+ fn dispatch_plugin_changed(elem: &HtmlElement, plugin: &JsPerspectiveViewerPlugin) {
62
+ let event_init = web_sys::CustomEventInit::new();
63
+ event_init.set_detail(plugin);
64
+ let event =
65
+ web_sys::CustomEvent::new_with_event_init_dict("perspective-plugin-update", &event_init);
77
66
 
78
- impl HasSession for CustomEventsData {
79
- fn session(&self) -> &Session {
80
- &self.session
81
- }
67
+ elem.dispatch_event(&event.unwrap()).unwrap();
82
68
  }
83
69
 
84
- impl CustomEvents {
85
- pub fn new(
86
- elem: &HtmlElement,
87
- session: &Session,
88
- renderer: &Renderer,
89
- presentation: &Presentation,
90
- ) -> Self {
91
- let data = CustomEventsDataRc(Rc::new(CustomEventsData {
92
- elem: elem.clone(),
93
- session: session.clone(),
94
- renderer: renderer.clone(),
95
- presentation: presentation.clone(),
96
- last_dispatched: Default::default(),
97
- }));
98
-
99
- let theme_sub = presentation.theme_config_updated.add_listener({
100
- clone!(data);
101
- move |_| data.clone().dispatch_config_update()
102
- });
103
-
104
- let settings_sub = presentation.settings_open_changed.add_listener({
105
- clone!(data);
106
- move |open: bool| {
107
- data.dispatch_event("toggle-settings", open).unwrap();
108
- data.clone().dispatch_config_update();
109
- }
110
- });
111
-
112
- let before_settings_sub = presentation.settings_before_open_changed.add_listener({
113
- clone!(data);
114
- move |open: bool| {
115
- data.dispatch_event("toggle-settings-before", open).unwrap();
116
- // data.clone().dispatch_config_update();
117
- }
118
- });
119
-
120
- let column_settings_sub = presentation.column_settings_open_changed.add_listener({
121
- clone!(data);
122
- move |(open, column_name)| {
123
- data.dispatch_column_settings_open_changed(open, column_name);
124
- // column_settings is ethereal; do not change the config
125
- }
126
- });
127
-
128
- let plugin_sub = renderer.plugin_changed.add_listener({
129
- clone!(data);
130
- move |plugin| {
131
- data.dispatch_plugin_changed(&plugin);
132
- data.clone().dispatch_config_update();
133
- }
134
- });
70
+ /// Per-element memoized config-change dispatcher. Reads/writes the dedup
71
+ /// cell on `presentation.last_dispatched_config` so each viewer instance
72
+ /// has its own cache — without this, a second viewer reloading the same
73
+ /// table the first viewer used would have its initial `config-update`
74
+ /// suppressed.
75
+ fn dispatch_config_update(
76
+ elem: &HtmlElement,
77
+ session: &Session,
78
+ renderer: &Renderer,
79
+ presentation: &Presentation,
80
+ ) {
81
+ clone!(session, renderer, presentation);
82
+ let elem = elem.clone();
83
+ perspective_js::utils::ApiFuture::spawn(async move {
84
+ let viewer_config = get_viewer_config(&session, &renderer, &presentation).await?;
85
+ if viewer_config.view_config != Default::default()
86
+ && Some(&viewer_config) != presentation.last_dispatched_config.borrow().as_ref()
87
+ {
88
+ let json_config = JsValue::from_serde_ext(&viewer_config)?;
89
+ let event_init = web_sys::CustomEventInit::new();
90
+ event_init.set_detail(&json_config);
91
+ let event = web_sys::CustomEvent::new_with_event_init_dict(
92
+ "perspective-config-update",
93
+ &event_init,
94
+ );
95
+
96
+ *presentation.last_dispatched_config.borrow_mut() = Some(viewer_config);
97
+ elem.dispatch_event(&event.unwrap()).unwrap();
98
+ }
135
99
 
136
- let view_sub = session.view_created.add_listener({
137
- clone!(data);
138
- move |_| data.clone().dispatch_config_update()
139
- });
140
-
141
- let title_sub = session.title_changed.add_listener({
142
- clone!(data);
143
- move |_| data.clone().dispatch_config_update()
144
- });
145
-
146
- let unload_sub = session.table_unloaded.add_listener({
147
- clone!(data);
148
- move |x: bool| {
149
- if !x {
150
- data.clone()
151
- .dispatch_event("table-delete-before", JsValue::UNDEFINED)
152
- .unwrap();
153
- } else {
154
- data.clone()
155
- .dispatch_event("table-delete", JsValue::UNDEFINED)
156
- .unwrap()
157
- }
158
- }
159
- });
160
-
161
- Self(Rc::new((data, [
162
- theme_sub,
163
- before_settings_sub,
164
- settings_sub,
165
- column_settings_sub,
166
- plugin_sub,
167
- view_sub,
168
- title_sub,
169
- unload_sub,
170
- ])))
171
- }
172
-
173
- pub fn dispatch_column_style_changed(&self, config: &JsValue) -> ApiResult<()> {
174
- self.dispatch_event("column-style-change", config)?;
175
- self.0.0.clone().dispatch_config_update();
176
100
  Ok(())
177
- }
178
-
179
- pub fn dispatch_select(&self, view_window: Option<&ViewWindow>) -> ApiResult<()> {
180
- self.dispatch_event("select", &serde_wasm_bindgen::to_value(&view_window)?)?;
181
- self.0.0.clone().dispatch_config_update();
182
- Ok(())
183
- }
184
-
185
- pub fn dispatch_event<T>(&self, name: &str, event: T) -> ApiResult<()>
186
- where
187
- T: Into<JsValue>,
188
- {
189
- self.0.0.dispatch_event(name, event)
190
- }
191
-
192
- pub fn dispatch_raw_event(&self, event: &web_sys::CustomEvent) -> ApiResult<bool> {
193
- self.0.0.elem.dispatch_event(event).map_err(|e| e.into())
194
- }
101
+ });
195
102
  }
196
103
 
197
- impl CustomEventsDataRc {
198
- pub fn dispatch_event<T>(&self, name: &str, event: T) -> ApiResult<()>
199
- where
200
- T: Into<JsValue>,
201
- {
202
- let event_init = web_sys::CustomEventInit::new();
203
- event_init.set_detail(&event.into());
204
- let event = web_sys::CustomEvent::new_with_event_init_dict(
205
- format!("perspective-{}", name).as_str(),
206
- &event_init,
207
- )?;
208
-
209
- self.elem.dispatch_event(&event)?;
210
- Ok(())
211
- }
212
-
213
- fn dispatch_column_settings_open_changed(&self, open: bool, column_name: Option<String>) {
214
- let event_init = web_sys::CustomEventInit::new();
215
- event_init.set_detail(&JsValue::from(
216
- json!( {"open": open, "column_name": column_name} ),
217
- ));
218
- let event = web_sys::CustomEvent::new_with_event_init_dict(
219
- "perspective-toggle-column-settings",
220
- &event_init,
221
- );
222
-
223
- self.elem.dispatch_event(&event.unwrap()).unwrap();
224
- }
225
-
226
- fn dispatch_plugin_changed(&self, plugin: &JsPerspectiveViewerPlugin) {
227
- let event_init = web_sys::CustomEventInit::new();
228
- event_init.set_detail(plugin);
229
- let event = web_sys::CustomEvent::new_with_event_init_dict(
230
- "perspective-plugin-update",
231
- &event_init,
232
- );
233
-
234
- self.elem.dispatch_event(&event.unwrap()).unwrap();
235
- }
236
-
237
- fn dispatch_config_update(self) {
238
- ApiFuture::spawn(async move {
239
- let viewer_config = self.get_viewer_config().await?;
240
- if viewer_config.view_config != Default::default()
241
- && Some(&viewer_config) != self.last_dispatched.borrow().as_ref()
242
- {
243
- let json_config = JsValue::from_serde_ext(&viewer_config)?;
244
- let event_init = web_sys::CustomEventInit::new();
245
- event_init.set_detail(&json_config);
246
- let event = web_sys::CustomEvent::new_with_event_init_dict(
247
- "perspective-config-update",
248
- &event_init,
249
- );
250
-
251
- *self.last_dispatched.borrow_mut() = Some(viewer_config);
252
- self.elem.dispatch_event(&event.unwrap()).unwrap();
104
+ /// Wire PubSub channels on `session`, `renderer`, and `presentation` to the
105
+ /// `perspective-*` `CustomEvent` set on `elem`. The returned
106
+ /// `Vec<Subscription>` must be kept alive for the lifetime of the element;
107
+ /// dropping it detaches all listeners.
108
+ pub fn wire_custom_events(
109
+ elem: &HtmlElement,
110
+ session: &Session,
111
+ renderer: &Renderer,
112
+ presentation: &Presentation,
113
+ ) -> Vec<Subscription> {
114
+ let theme_sub = presentation.theme_config_updated.add_listener({
115
+ clone!(elem, session, renderer, presentation);
116
+ move |_| dispatch_config_update(&elem, &session, &renderer, &presentation)
117
+ });
118
+
119
+ let settings_sub = presentation.settings_open_changed.add_listener({
120
+ clone!(elem, session, renderer, presentation);
121
+ move |open: bool| {
122
+ dispatch_event(&elem, "toggle-settings", open).unwrap();
123
+ dispatch_config_update(&elem, &session, &renderer, &presentation);
124
+ }
125
+ });
126
+
127
+ let before_settings_sub = presentation.settings_before_open_changed.add_listener({
128
+ clone!(elem);
129
+ move |open: bool| {
130
+ dispatch_event(&elem, "toggle-settings-before", open).unwrap();
131
+ }
132
+ });
133
+
134
+ let column_settings_sub = presentation.column_settings_open_changed.add_listener({
135
+ clone!(elem);
136
+ move |(open, column_name)| {
137
+ dispatch_column_settings_open_changed(&elem, open, column_name);
138
+ // column_settings is ethereal; do not change the config
139
+ }
140
+ });
141
+
142
+ let plugin_sub = renderer.plugin_changed.add_listener({
143
+ clone!(elem, session, renderer, presentation);
144
+ move |plugin| {
145
+ dispatch_plugin_changed(&elem, &plugin);
146
+ dispatch_config_update(&elem, &session, &renderer, &presentation);
147
+ }
148
+ });
149
+
150
+ let view_sub = session.view_created.add_listener({
151
+ clone!(elem, session, renderer, presentation);
152
+ move |_| dispatch_config_update(&elem, &session, &renderer, &presentation)
153
+ });
154
+
155
+ let title_sub = session.title_changed.add_listener({
156
+ clone!(elem, session, renderer, presentation);
157
+ move |_| dispatch_config_update(&elem, &session, &renderer, &presentation)
158
+ });
159
+
160
+ let unload_sub = session.table_unloaded.add_listener({
161
+ clone!(elem);
162
+ move |x: bool| {
163
+ if !x {
164
+ dispatch_event(&elem, "table-delete-before", JsValue::UNDEFINED).unwrap();
165
+ } else {
166
+ dispatch_event(&elem, "table-delete", JsValue::UNDEFINED).unwrap()
253
167
  }
254
-
255
- Ok(())
256
- });
257
- }
168
+ }
169
+ });
170
+
171
+ let select_sub = renderer.selection_changed.add_listener({
172
+ clone!(elem, session, renderer, presentation);
173
+ move |window: Option<ViewWindow>| {
174
+ let detail = JsValue::from_serde_ext(&window).unwrap();
175
+ dispatch_event(&elem, "select", &detail).unwrap();
176
+ dispatch_config_update(&elem, &session, &renderer, &presentation);
177
+ }
178
+ });
179
+
180
+ let column_style_sub = renderer.column_style_changed.add_listener({
181
+ clone!(elem, session, renderer, presentation);
182
+ move |cfg: ColumnConfigMap| {
183
+ let detail = JsValue::from_serde_ext(&cfg).unwrap();
184
+ dispatch_event(&elem, "column-style-change", &detail).unwrap();
185
+ dispatch_config_update(&elem, &session, &renderer, &presentation);
186
+ }
187
+ });
188
+
189
+ let status_click_sub = session.status_indicator_clicked.add_listener({
190
+ clone!(elem);
191
+ move |_| dispatch_event(&elem, "status-indicator-click", JsValue::UNDEFINED).unwrap()
192
+ });
193
+
194
+ let statusbar_ptr_sub = presentation.statusbar_pointer_event.add_listener({
195
+ clone!(elem);
196
+ move |event: PointerEvent| {
197
+ dispatch_event(&elem, &format!("statusbar-{}", event.type_()), &event).unwrap();
198
+ }
199
+ });
200
+
201
+ vec![
202
+ theme_sub,
203
+ before_settings_sub,
204
+ settings_sub,
205
+ column_settings_sub,
206
+ plugin_sub,
207
+ view_sub,
208
+ title_sub,
209
+ unload_sub,
210
+ select_sub,
211
+ column_style_sub,
212
+ status_click_sub,
213
+ statusbar_ptr_sub,
214
+ ]
258
215
  }
@@ -10,12 +10,12 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use perspective_client::config::GroupRollupMode;
13
+ use perspective_js::JsViewWindow;
14
14
  use perspective_js::utils::*;
15
- use serde::*;
16
15
  use wasm_bindgen::prelude::*;
17
16
 
18
- use crate::presentation::ColumnConfigMap;
17
+ use crate::config::PluginStaticConfig;
18
+ use crate::renderer::ColumnConfigMap;
19
19
 
20
20
  /// Perspective FFI
21
21
  #[wasm_bindgen]
@@ -25,55 +25,31 @@ extern "C" {
25
25
  #[derive(Clone)]
26
26
  pub type JsPerspectiveViewer;
27
27
 
28
- #[wasm_bindgen(method)]
29
- pub fn get_model(this: &JsPerspectiveViewer) -> JsValue;
30
-
31
28
  #[derive(Clone)]
32
29
  pub type JsPerspectiveViewerPlugin;
33
30
 
34
- #[wasm_bindgen(method, getter)]
35
- pub fn name(this: &JsPerspectiveViewerPlugin) -> String;
36
-
37
- #[wasm_bindgen(method, getter)]
38
- pub fn category(this: &JsPerspectiveViewerPlugin) -> Option<String>;
39
-
40
- #[wasm_bindgen(method, getter)]
41
- pub fn max_columns(this: &JsPerspectiveViewerPlugin) -> Option<usize>;
42
-
43
- #[wasm_bindgen(method, getter)]
44
- pub fn max_cells(this: &JsPerspectiveViewerPlugin) -> Option<usize>;
45
-
46
- // TODO This can be an internal property
47
- #[wasm_bindgen(method, getter)]
48
- pub fn render_warning(this: &JsPerspectiveViewerPlugin) -> Option<bool>;
49
-
50
- #[wasm_bindgen(method, setter)]
51
- pub fn set_render_warning(this: &JsPerspectiveViewerPlugin, val: bool);
52
-
53
- #[wasm_bindgen(method, getter)]
54
- pub fn select_mode(this: &JsPerspectiveViewerPlugin) -> JsValue;
55
-
56
- #[wasm_bindgen(method, getter)]
57
- pub fn min_config_columns(this: &JsPerspectiveViewerPlugin) -> Option<usize>;
58
-
59
- #[wasm_bindgen(method, getter)]
60
- pub fn config_column_names(this: &JsPerspectiveViewerPlugin) -> Option<js_sys::Array>;
61
-
62
- #[wasm_bindgen(method, getter)]
63
- pub fn priority(this: &JsPerspectiveViewerPlugin) -> Option<i32>;
64
-
65
- #[wasm_bindgen(method, getter)]
66
- pub fn group_rollups(this: &JsPerspectiveViewerPlugin) -> Option<js_sys::Array>;
31
+ #[derive(Clone)]
32
+ pub type JsPluginStaticConfig;
67
33
 
68
- /// Don't call this method directly. Instead, call the corresponding method on the PluginColumnStyles model.
69
- #[wasm_bindgen(method, catch)]
70
- pub fn can_render_column_styles(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>) -> ApiResult<bool>;
34
+ /// The static configuration of the plugin which defines the basic
35
+ /// integration with `perspective-viewer`. Called once per plugin at
36
+ /// registration time and cached the result must be stable for
37
+ /// the lifetime of the application.
38
+ #[wasm_bindgen(method)]
39
+ pub fn get_static_config(this: &JsPerspectiveViewerPlugin) -> JsPluginStaticConfig;
71
40
 
72
- #[wasm_bindgen(method, catch)]
73
- pub fn column_style_controls(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>) -> ApiResult<JsValue>;
41
+ /// Returns the per-column schema describing which controls to render
42
+ /// in the sidebar Style tab and the keys each control owns in the
43
+ /// column's persisted config map. `column_stats` carries cached
44
+ /// per-column numeric stats (currently `{ abs_max?: number }`);
45
+ /// fields are populated lazily and may be missing on the first
46
+ /// call — the view re-renders and re-queries the schema once the
47
+ /// async fetch resolves.
48
+ #[wasm_bindgen(method, catch, js_name = column_config_schema)]
49
+ pub fn _column_config_schema(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>, column_name: &str, current_value: &JsValue, view_config: &JsValue, column_stats: &JsValue) -> ApiResult<JsValue>;
74
50
 
75
- #[wasm_bindgen(method, catch)]
76
- pub fn save(this: &JsPerspectiveViewerPlugin) -> ApiResult<JsValue>;
51
+ #[wasm_bindgen(method, catch, js_name = plugin_config_schema)]
52
+ pub fn _plugin_config_schema(this: &JsPerspectiveViewerPlugin, view_config: &JsValue) -> ApiResult<JsValue>;
77
53
 
78
54
  #[wasm_bindgen(method, js_name=restore, catch)]
79
55
  pub fn _restore(this: &JsPerspectiveViewerPlugin, token: &JsValue, columns_config: &JsValue) -> ApiResult<()>;
@@ -81,11 +57,17 @@ extern "C" {
81
57
  #[wasm_bindgen(method)]
82
58
  pub fn delete(this: &JsPerspectiveViewerPlugin);
83
59
 
60
+ #[wasm_bindgen(method)]
61
+ pub fn restyle(
62
+ this: &JsPerspectiveViewerPlugin,
63
+ );
64
+
84
65
  #[wasm_bindgen(method, catch)]
85
- pub async fn restyle(
66
+ pub async fn render(
86
67
  this: &JsPerspectiveViewerPlugin,
87
- view: perspective_js::View
88
- ) -> ApiResult<JsValue>;
68
+ view: perspective_js::View,
69
+ viewport: Option<JsViewWindow>,
70
+ ) -> ApiResult<web_sys::Blob>;
89
71
 
90
72
  #[wasm_bindgen(method, catch)]
91
73
  pub async fn draw(
@@ -113,7 +95,21 @@ extern "C" {
113
95
 
114
96
  }
115
97
 
98
+ impl From<JsPluginStaticConfig> for PluginStaticConfig {
99
+ fn from(value: JsPluginStaticConfig) -> Self {
100
+ value.into_serde_ext().expect("Invalid plugin config")
101
+ }
102
+ }
103
+
116
104
  impl JsPerspectiveViewerPlugin {
105
+ /// Read and deserialize the plugin's static config. Should only
106
+ /// be called once per plugin (at registration time); cache the
107
+ /// result and read fields off the cached value rather than
108
+ /// reaching back through the FFI.
109
+ pub fn read_static_config(&self) -> PluginStaticConfig {
110
+ self.get_static_config().into()
111
+ }
112
+
117
113
  pub fn restore(
118
114
  &self,
119
115
  token: &JsValue,
@@ -123,71 +119,3 @@ impl JsPerspectiveViewerPlugin {
123
119
  self._restore(token, &columns_config)
124
120
  }
125
121
  }
126
-
127
- #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
128
- #[serde(rename_all = "camelCase")]
129
- pub enum ColumnSelectMode {
130
- #[default]
131
- Toggle,
132
- Select,
133
- }
134
-
135
- impl ColumnSelectMode {
136
- pub fn css(&self) -> yew::Classes {
137
- match self {
138
- Self::Toggle => yew::classes!("toggle-mode", "is_column_active"),
139
- Self::Select => yew::classes!("select-mode", "is_column_active"),
140
- }
141
- }
142
- }
143
-
144
- #[derive(Clone, Debug, Default, PartialEq)]
145
- pub struct ViewConfigRequirements {
146
- pub min: Option<usize>,
147
- pub names: Option<Vec<String>>,
148
- pub mode: ColumnSelectMode,
149
- pub max_columns: Option<usize>,
150
- pub max_cells: Option<usize>,
151
- pub name: String,
152
- pub render_warning: bool,
153
- group_rollups: Option<Vec<GroupRollupMode>>,
154
- }
155
-
156
- impl ViewConfigRequirements {
157
- pub fn is_swap(&self, index: usize) -> bool {
158
- self.names
159
- .as_ref()
160
- .map(|x| index < x.len() - 1)
161
- .unwrap_or(false)
162
- }
163
-
164
- pub fn get_group_rollups(&self, rollup_features: &[GroupRollupMode]) -> Vec<GroupRollupMode> {
165
- self.group_rollups
166
- .clone()
167
- .map(|x| {
168
- x.into_iter()
169
- .filter(|y| rollup_features.is_empty() || rollup_features.contains(y))
170
- .collect()
171
- })
172
- .unwrap_or_default()
173
- }
174
- }
175
-
176
- impl JsPerspectiveViewerPlugin {
177
- pub fn get_requirements(&self) -> ApiResult<ViewConfigRequirements> {
178
- Ok(ViewConfigRequirements {
179
- min: self.min_config_columns(),
180
- mode: self.select_mode().into_serde_ext()?,
181
- names: self
182
- .config_column_names()
183
- .map(|x| x.into_serde_ext().unwrap()),
184
- max_columns: self.max_columns(),
185
- max_cells: self.max_cells(),
186
- name: self.name(),
187
- render_warning: self.render_warning().unwrap_or(true),
188
- group_rollups: self
189
- .group_rollups()
190
- .map(|x| x.into_serde_ext::<Vec<GroupRollupMode>>().unwrap()),
191
- })
192
- }
193
- }