@perspective-dev/viewer 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (240) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -0
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/esm/extensions.d.ts +32 -1
  23. package/dist/esm/perspective-viewer.d.ts +1 -0
  24. package/dist/esm/perspective-viewer.inline.js +2 -2
  25. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  26. package/dist/esm/perspective-viewer.js +2 -2
  27. package/dist/esm/perspective-viewer.js.map +4 -4
  28. package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
  29. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +2 -0
  30. package/dist/wasm/perspective-viewer.d.ts +57 -53
  31. package/dist/wasm/perspective-viewer.js +197 -164
  32. package/dist/wasm/perspective-viewer.wasm +0 -0
  33. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  34. package/package.json +9 -6
  35. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  36. package/src/css/column-dropdown.css +109 -0
  37. package/src/{less/column-selector.less → css/column-selector.css} +161 -159
  38. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  39. package/src/{less/column-style.less → css/column-style.css} +52 -66
  40. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  41. package/src/css/config-selector.css +441 -0
  42. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  43. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  44. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  45. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  46. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  47. package/src/css/dom/checkbox.css +102 -0
  48. package/src/css/dom/scrollbar.css +35 -0
  49. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  50. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  51. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  52. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  53. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  54. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  55. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  56. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  57. package/src/css/plugin-selector.css +261 -0
  58. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  59. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  60. package/src/css/type-icon.css +116 -0
  61. package/src/{less/viewer.less → css/viewer.css} +112 -146
  62. package/src/rust/components/column_dropdown.rs +229 -119
  63. package/src/rust/components/column_selector/active_column.rs +81 -62
  64. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  65. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  66. package/src/rust/components/column_selector/config_selector.rs +374 -185
  67. package/src/rust/components/column_selector/empty_column.rs +2 -2
  68. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  69. package/src/rust/components/column_selector/filter_column.rs +37 -26
  70. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  71. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  72. package/src/rust/components/column_selector/pivot_column.rs +21 -10
  73. package/src/rust/components/column_selector/sort_column.rs +23 -13
  74. package/src/rust/components/column_selector.rs +189 -100
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  77. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  79. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  80. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  81. package/src/rust/components/column_settings_sidebar.rs +91 -53
  82. package/src/rust/components/containers/dragdrop_list.rs +29 -7
  83. package/src/rust/components/containers/scroll_panel.rs +8 -1
  84. package/src/rust/components/containers/select.rs +3 -3
  85. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  86. package/src/rust/components/containers/split_panel.rs +3 -2
  87. package/src/rust/components/containers/tab_list.rs +1 -1
  88. package/src/rust/components/copy_dropdown.rs +7 -28
  89. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  90. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  91. package/src/rust/components/datetime_column_style.rs +4 -2
  92. package/src/rust/components/editable_header.rs +7 -4
  93. package/src/rust/components/empty_row.rs +1 -1
  94. package/src/rust/components/export_dropdown.rs +4 -30
  95. package/src/rust/components/expression_editor.rs +19 -10
  96. package/src/rust/components/filter_dropdown.rs +246 -102
  97. package/src/rust/components/font_loader.rs +11 -28
  98. package/src/rust/components/form/code_editor.rs +17 -2
  99. package/src/rust/components/form/color_range_selector.rs +19 -6
  100. package/src/rust/components/form/debug.rs +30 -13
  101. package/src/rust/components/function_dropdown.rs +186 -113
  102. package/src/rust/components/main_panel.rs +71 -89
  103. package/src/rust/components/mod.rs +1 -1
  104. package/src/rust/components/modal.rs +7 -1
  105. package/src/rust/components/number_column_style.rs +22 -7
  106. package/src/rust/components/plugin_selector.rs +34 -92
  107. package/src/rust/components/portal.rs +274 -0
  108. package/src/rust/components/render_warning.rs +72 -123
  109. package/src/rust/components/settings_panel.rs +115 -11
  110. package/src/rust/components/status_bar.rs +222 -98
  111. package/src/rust/components/status_bar_counter.rs +8 -20
  112. package/src/rust/components/status_indicator.rs +64 -111
  113. package/src/rust/components/string_column_style.rs +2 -2
  114. package/src/rust/components/style/style_cache.rs +5 -1
  115. package/src/rust/components/viewer.rs +391 -39
  116. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  117. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  118. package/src/rust/custom_elements/mod.rs +0 -7
  119. package/src/rust/custom_elements/modal.rs +7 -103
  120. package/src/rust/custom_elements/viewer.rs +99 -35
  121. package/src/rust/custom_events.rs +23 -2
  122. package/src/rust/dragdrop.rs +149 -10
  123. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  124. package/src/rust/js/plugin.rs +20 -1
  125. package/src/rust/lib.rs +5 -4
  126. package/src/rust/presentation/props.rs +39 -0
  127. package/src/rust/presentation/sheets.rs +3 -3
  128. package/src/rust/presentation.rs +44 -8
  129. package/src/rust/renderer/limits.rs +32 -3
  130. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  131. package/src/rust/renderer/registry.rs +8 -1
  132. package/src/rust/renderer.rs +83 -9
  133. package/src/rust/session/column_defaults_update.rs +18 -0
  134. package/src/rust/session/metadata.rs +23 -2
  135. package/src/rust/session/props.rs +178 -0
  136. package/src/rust/session/replace_expression_update.rs +1 -0
  137. package/src/rust/session.rs +124 -117
  138. package/src/rust/tasks/column_locator.rs +133 -0
  139. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  140. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  141. package/src/rust/{model → tasks}/eject.rs +2 -2
  142. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  143. package/src/rust/{model → tasks}/intersection_observer.rs +22 -4
  144. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  145. package/src/rust/tasks/mod.rs +52 -0
  146. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  147. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  148. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  149. package/src/rust/tasks/structural.rs +53 -0
  150. package/src/rust/utils/mod.rs +4 -0
  151. package/src/rust/utils/modal_position.rs +110 -0
  152. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  153. package/src/rust/utils/pubsub.rs +11 -1
  154. package/src/svg/bg-pattern.png +0 -0
  155. package/src/svg/close-icon.svg +1 -1
  156. package/src/svg/expression.svg +1 -1
  157. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  158. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  159. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  160. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  161. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  162. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  163. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  164. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  165. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  166. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  167. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  168. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  169. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  170. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  171. package/src/svg/radio-hover.svg +1 -1
  172. package/src/svg/radio-off.svg +1 -1
  173. package/src/svg/radio-on.svg +1 -1
  174. package/src/themes/botanical.css +157 -0
  175. package/src/themes/defaults.css +139 -0
  176. package/src/themes/dracula.css +233 -0
  177. package/src/themes/gruvbox-dark.css +255 -0
  178. package/src/themes/gruvbox.css +134 -0
  179. package/src/themes/icons.css +124 -0
  180. package/src/themes/intl/de.css +102 -0
  181. package/src/themes/intl/es.css +102 -0
  182. package/src/themes/intl/fr.css +102 -0
  183. package/src/themes/intl/ja.css +102 -0
  184. package/src/themes/intl/pt.css +102 -0
  185. package/src/themes/intl/zh.css +102 -0
  186. package/src/themes/intl.css +102 -0
  187. package/src/themes/monokai.css +233 -0
  188. package/src/themes/pro-dark.css +158 -0
  189. package/src/themes/{themes.less → pro.css} +17 -20
  190. package/src/themes/solarized-dark.css +135 -0
  191. package/src/themes/solarized.css +95 -0
  192. package/src/themes/themes.css +22 -0
  193. package/src/themes/vaporwave.css +256 -0
  194. package/src/ts/extensions.ts +73 -2
  195. package/src/ts/perspective-viewer.ts +1 -0
  196. package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
  197. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -1
  198. package/tsconfig.json +1 -0
  199. package/dist/css/variables.css +0 -0
  200. package/src/less/column-dropdown.less +0 -95
  201. package/src/less/config-selector.less +0 -363
  202. package/src/less/dom/checkbox.less +0 -100
  203. package/src/less/plugin-selector.less +0 -183
  204. package/src/less/type-icon.less +0 -68
  205. package/src/rust/components/error_message.rs +0 -56
  206. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  207. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  208. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  209. package/src/rust/model/column_locator.rs +0 -82
  210. package/src/rust/model/is_invalid_drop.rs +0 -36
  211. package/src/rust/model/mod.rs +0 -100
  212. package/src/rust/model/reset_all.rs +0 -38
  213. package/src/rust/model/structural.rs +0 -244
  214. package/src/themes/dracula.less +0 -101
  215. package/src/themes/gruvbox-dark.less +0 -116
  216. package/src/themes/gruvbox.less +0 -152
  217. package/src/themes/icons.less +0 -130
  218. package/src/themes/intl/de.less +0 -102
  219. package/src/themes/intl/es.less +0 -102
  220. package/src/themes/intl/fr.less +0 -102
  221. package/src/themes/intl/ja.less +0 -102
  222. package/src/themes/intl/pt.less +0 -102
  223. package/src/themes/intl/zh.less +0 -102
  224. package/src/themes/intl.less +0 -102
  225. package/src/themes/monokai.less +0 -107
  226. package/src/themes/pro-dark.less +0 -147
  227. package/src/themes/pro.less +0 -186
  228. package/src/themes/solarized-dark.less +0 -78
  229. package/src/themes/solarized.less +0 -102
  230. package/src/themes/vaporwave.less +0 -145
  231. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  232. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  233. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  234. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  235. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  236. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  237. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  238. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  239. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  240. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
package/src/rust/lib.rs CHANGED
@@ -16,7 +16,6 @@
16
16
  // Required by yew's `html` macro.
17
17
  #![recursion_limit = "1024"]
18
18
  #![feature(const_type_name)]
19
- #![feature(macro_metavar_expr)]
20
19
  #![feature(iter_intersperse)]
21
20
  #![feature(stmt_expr_attributes)]
22
21
  #![feature(try_blocks)]
@@ -39,11 +38,12 @@ pub mod exprtk;
39
38
  mod js;
40
39
  mod root;
41
40
 
42
- #[doc(hidden)]
43
- pub mod model;
41
+ pub mod engines;
44
42
  mod presentation;
45
43
  mod renderer;
46
44
  mod session;
45
+ #[doc(hidden)]
46
+ pub mod tasks;
47
47
  pub mod utils;
48
48
 
49
49
  #[macro_use]
@@ -67,6 +67,7 @@ import type {
67
67
  ColumnWindow,
68
68
  ViewWindow,
69
69
  OnUpdateOptions,
70
+ JoinOptions,
70
71
  UpdateOptions,
71
72
  DeleteOptions,
72
73
  ViewConfigUpdate,
@@ -106,8 +107,8 @@ pub fn js_init() {
106
107
  pub fn bootstrap_web_components(psp: &JsValue) {
107
108
  define_web_component::<PerspectiveViewerElement>(psp);
108
109
  define_web_component::<PerspectiveDebugPluginElement>(psp);
109
- define_web_component::<ExportDropDownMenuElement>(psp);
110
110
  define_web_component::<CopyDropDownMenuElement>(psp);
111
+ define_web_component::<ExportDropDownMenuElement>(psp);
111
112
  }
112
113
 
113
114
  /// Defining the web components needs an extern struct to reference the
@@ -0,0 +1,39 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use crate::presentation::OpenColumnSettings;
14
+ use crate::utils::PtrEqRc;
15
+
16
+ /// Value-semantic snapshot of the presentation/UI state used by the root
17
+ /// component to drive `is_settings_open`, `selected_theme`, and
18
+ /// `available_themes` into child components via plain props.
19
+ ///
20
+ /// The `HtmlElement` handle, async theme-detection machinery, column-settings
21
+ /// state, and per-column config live in `PresentationEngine` and are not
22
+ /// passed as props.
23
+ #[derive(Clone, Debug, PartialEq, Default)]
24
+ pub struct PresentationProps {
25
+ /// Whether the settings panel is currently open.
26
+ pub is_settings_open: bool,
27
+
28
+ /// Detected theme names, in discovery order.
29
+ pub available_themes: PtrEqRc<Vec<String>>,
30
+
31
+ /// The currently selected theme name, if any theme is active.
32
+ pub selected_theme: Option<String>,
33
+
34
+ /// Snapshot of the currently opened column-settings sidebar state.
35
+ pub open_column_settings: OpenColumnSettings,
36
+
37
+ /// Whether this viewer is hosted inside a `<perspective-workspace>`.
38
+ pub is_workspace: bool,
39
+ }
@@ -21,7 +21,7 @@ macro_rules! iter_index {
21
21
  }
22
22
 
23
23
  /// Search the document's `styleSheets` for rules which apply to `elem` and
24
- /// provide the `--theme-name` CSS custom property.
24
+ /// provide the `--psp-theme-name` CSS custom property.
25
25
  pub fn get_theme_names(elem: &HtmlElement) -> Result<Vec<String>, JsValue> {
26
26
  let sheets = global::document().style_sheets();
27
27
  let mut themes: Vec<String> = vec![];
@@ -43,8 +43,8 @@ fn fill_rule_theme_names(
43
43
  let style = rule.style();
44
44
  let x = (0..style.length()).map(|x| style.item(x));
45
45
  for property in x {
46
- if property == "--theme-name" {
47
- let name = style.get_property_value("--theme-name")?;
46
+ if property == "--psp-theme-name" {
47
+ let name = style.get_property_value("--psp-theme-name")?;
48
48
  let trimmed = name.trim();
49
49
  let theme = &trimmed[1..trimmed.len() - 1];
50
50
  if themes.iter().find(|x| x == &theme).is_none() {
@@ -11,6 +11,7 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  mod column_locator;
14
+ mod props;
14
15
  mod sheets;
15
16
 
16
17
  use std::cell::RefCell;
@@ -20,11 +21,12 @@ use std::rc::Rc;
20
21
 
21
22
  use async_lock::Mutex;
22
23
  use perspective_js::utils::{ApiFuture, ApiResult};
23
- use wasm_bindgen::prelude::*;
24
24
  use web_sys::*;
25
25
  use yew::html::ImplicitClone;
26
+ use yew::prelude::*;
26
27
 
27
28
  pub use self::column_locator::{ColumnLocator, ColumnSettingsTab, ColumnTab, OpenColumnSettings};
29
+ pub use self::props::PresentationProps;
28
30
  use crate::config::{ColumnConfigUpdate, ColumnConfigValueUpdate, ColumnConfigValues};
29
31
  use crate::utils::*;
30
32
 
@@ -48,11 +50,13 @@ pub struct PresentationHandle {
48
50
  columns_config: RefCell<ColumnConfigMap>,
49
51
  is_workspace: RefCell<Option<bool>>,
50
52
  pub settings_open_changed: PubSub<bool>,
53
+
54
+ /// Injected callback from the root component, replacing the former
55
+ /// `is_workspace_changed: PubSub` field.
56
+ pub on_is_workspace_changed: RefCell<Option<Callback<bool>>>,
51
57
  pub settings_before_open_changed: PubSub<bool>,
52
58
  pub column_settings_open_changed: PubSub<(bool, Option<String>)>,
53
- pub column_settings_updated: PubSub<JsValue>,
54
- pub theme_config_updated: PubSub<(Rc<Vec<String>>, Option<usize>)>,
55
- pub visibility_changed: PubSub<bool>,
59
+ pub theme_config_updated: PubSub<(PtrEqRc<Vec<String>>, Option<usize>)>,
56
60
  pub on_eject: PubSub<()>,
57
61
  }
58
62
 
@@ -86,13 +90,12 @@ impl Presentation {
86
90
  settings_open_changed: Default::default(),
87
91
  settings_before_open_changed: Default::default(),
88
92
  column_settings_open_changed: Default::default(),
89
- column_settings_updated: Default::default(),
93
+ on_is_workspace_changed: Default::default(),
90
94
  columns_config: Default::default(),
91
95
  is_settings_open: Default::default(),
92
96
  open_column_settings: Default::default(),
93
97
  theme_config_updated: PubSub::default(),
94
98
  on_eject: PubSub::default(),
95
- visibility_changed: PubSub::default(),
96
99
  }));
97
100
 
98
101
  ApiFuture::spawn(theme.clone().init());
@@ -110,6 +113,13 @@ impl Presentation {
110
113
  elem.is_some() && &self.viewer_elem.shadow_root().unwrap().active_element() == elem
111
114
  }
112
115
 
116
+ pub fn reset_attached(&self) {
117
+ *self.0.is_workspace.borrow_mut() = None;
118
+ if let Some(cb) = self.on_is_workspace_changed.borrow().as_ref() {
119
+ cb.emit(self.get_is_workspace());
120
+ }
121
+ }
122
+
113
123
  pub fn get_is_workspace(&self) -> bool {
114
124
  if self.is_workspace.borrow().is_none() {
115
125
  if !self.viewer_elem.is_connected() {
@@ -174,7 +184,7 @@ impl Presentation {
174
184
  /// Get the available theme names from the browser environment by parsing
175
185
  /// readable stylesheets. This method is memoized - the state can be
176
186
  /// flushed by calling `reset()`.
177
- pub async fn get_available_themes(&self) -> ApiResult<Rc<Vec<String>>> {
187
+ pub async fn get_available_themes(&self) -> ApiResult<PtrEqRc<Vec<String>>> {
178
188
  let mut data = self.0.theme_data.lock().await;
179
189
  if data.themes.is_none() {
180
190
  await_dom_loaded().await?;
@@ -203,7 +213,9 @@ impl Presentation {
203
213
  changed
204
214
  }
205
215
 
206
- pub async fn get_selected_theme_config(&self) -> ApiResult<(Rc<Vec<String>>, Option<usize>)> {
216
+ pub async fn get_selected_theme_config(
217
+ &self,
218
+ ) -> ApiResult<(PtrEqRc<Vec<String>>, Option<usize>)> {
207
219
  let themes = self.get_available_themes().await?;
208
220
  let name = self.0.viewer_elem.get_attribute("theme");
209
221
  let index = name
@@ -308,4 +320,28 @@ impl Presentation {
308
320
  config.insert(column_name, update);
309
321
  }
310
322
  }
323
+
324
+ /// Snapshot the current presentation state as a [`PresentationProps`]
325
+ /// value suitable for passing as a Yew prop. Called by the root component
326
+ /// whenever a presentation-related PubSub event fires.
327
+ ///
328
+ /// `available_themes` must be provided by the caller because theme
329
+ /// detection is async and therefore not available synchronously here.
330
+ pub fn to_props(&self, available_themes: PtrEqRc<Vec<String>>) -> PresentationProps {
331
+ let theme_attr = self.0.viewer_elem.get_attribute("theme");
332
+ let selected_theme = theme_attr.as_deref().and_then(|name| {
333
+ available_themes
334
+ .iter()
335
+ .find(|x| x.as_str() == name)
336
+ .cloned()
337
+ });
338
+
339
+ PresentationProps {
340
+ is_settings_open: self.is_settings_open(),
341
+ available_themes,
342
+ selected_theme,
343
+ open_column_settings: self.get_open_column_settings(),
344
+ is_workspace: self.get_is_workspace(),
345
+ }
346
+ }
311
347
  }
@@ -14,15 +14,38 @@ use perspective_js::utils::ApiError;
14
14
 
15
15
  use crate::js::plugin::*;
16
16
 
17
+ /// The row/column limits computed for the current view and plugin
18
+ /// configuration.
19
+ #[derive(Clone, Copy, Debug, Default, PartialEq)]
20
+ pub struct RenderLimits {
21
+ /// Whether this render was an incremental update (`true`) or a full
22
+ /// draw (`false`).
23
+ pub is_update: bool,
24
+ /// Total number of columns in the view.
25
+ pub num_cols: usize,
26
+ /// Total number of rows in the view.
27
+ pub num_rows: usize,
28
+ /// Maximum number of columns the plugin will render, if capped.
29
+ pub max_cols: Option<usize>,
30
+ /// Maximum number of rows the plugin will render, if capped.
31
+ pub max_rows: Option<usize>,
32
+ }
33
+
17
34
  pub async fn get_row_and_col_limits(
18
35
  view: &perspective_client::View,
19
36
  plugin_metadata: &ViewConfigRequirements,
20
- ) -> Result<(usize, usize, Option<usize>, Option<usize>), ApiError> {
37
+ ) -> Result<RenderLimits, ApiError> {
21
38
  let dimensions = view.dimensions().await?;
22
39
  let num_cols = dimensions.num_view_columns as usize;
23
40
  let num_rows = dimensions.num_view_rows as usize;
24
41
  match (plugin_metadata.max_columns, plugin_metadata.render_warning) {
25
- (Some(_), false) => Ok((num_cols, num_rows, None, None)),
42
+ (Some(_), false) => Ok(RenderLimits {
43
+ is_update: false,
44
+ num_cols,
45
+ num_rows,
46
+ max_cols: None,
47
+ max_rows: None,
48
+ }),
26
49
  (max_columns, _) => {
27
50
  let schema = view.schema().await?;
28
51
  let keys = schema.keys();
@@ -45,7 +68,13 @@ pub async fn get_row_and_col_limits(
45
68
  .ceil() as usize
46
69
  });
47
70
 
48
- Ok((num_cols, num_rows, max_cols, max_rows))
71
+ Ok(RenderLimits {
72
+ is_update: false,
73
+ num_cols,
74
+ num_rows,
75
+ max_cols,
76
+ max_rows,
77
+ })
49
78
  },
50
79
  }
51
80
  }
@@ -10,26 +10,25 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- @mixin scrollbar {
14
- &:hover {
15
- --fix: ;
16
- }
13
+ use crate::js::plugin::ViewConfigRequirements;
14
+ use crate::renderer::limits::RenderLimits;
15
+ use crate::utils::PtrEqRc;
17
16
 
18
- &::-webkit-scrollbar-thumb {
19
- border-radius: 2px;
20
- border: 2px solid transparent;
21
- box-shadow: inset 0px 0px 0 4px var(--inactive--color);
22
- }
17
+ /// Value-semantic snapshot of the renderer state read by components.
18
+ ///
19
+ /// The actual plugin JS objects, draw lock, and render timer live in
20
+ /// `RendererEngine` and are not passed as props.
21
+ #[derive(Clone, Debug, PartialEq, Default)]
22
+ pub struct RendererProps {
23
+ /// Name of the currently active plugin (e.g. `"Datagrid"`).
24
+ pub plugin_name: Option<String>,
23
25
 
24
- &:hover::-webkit-scrollbar-thumb {
25
- border: 1px solid transparent;
26
- box-shadow: inset 0px 0px 0 4px var(--inactive--color);
27
- }
26
+ /// Column count / config requirements reported by the active plugin.
27
+ pub requirements: ViewConfigRequirements,
28
28
 
29
- &::-webkit-scrollbar,
30
- &::-webkit-scrollbar-corner {
31
- background-color: transparent;
32
- width: 6px;
33
- height: 6px;
34
- }
29
+ /// Most recently emitted render limits, if any.
30
+ pub render_limits: Option<RenderLimits>,
31
+
32
+ /// Names of all registered plugins, in registration order.
33
+ pub available_plugins: PtrEqRc<Vec<String>>,
35
34
  }
@@ -102,7 +102,14 @@ pub impl LocalKey<Rc<RefCell<Vec<PluginRecord>>>> {
102
102
  .unwrap_or_else(|| "Custom".to_owned()),
103
103
  priority: plugin_inst.priority().unwrap_or_default(),
104
104
  };
105
+
105
106
  let mut plugins = plugin.borrow_mut();
107
+ if let Some(first) = plugins.first()
108
+ && first.tag_name.as_str() == "perspective-viewer-plugin"
109
+ {
110
+ plugins.clear();
111
+ }
112
+
106
113
  plugins.push(record);
107
114
  plugins.sort_by(|a, b| Ord::cmp(&b.priority, &a.priority));
108
115
  });
@@ -121,7 +128,7 @@ fn register_default() {
121
128
  name: "Debug".to_owned(),
122
129
  category: "Custom".to_owned(),
123
130
  tag_name: "perspective-viewer-plugin".to_owned(),
124
- priority: 0,
131
+ priority: -1,
125
132
  })
126
133
  }
127
134
  })
@@ -18,8 +18,9 @@
18
18
  //! references throughout the application.
19
19
 
20
20
  mod activate;
21
- mod limits;
21
+ pub mod limits;
22
22
  mod plugin_store;
23
+ mod props;
23
24
  mod registry;
24
25
  mod render_timer;
25
26
 
@@ -38,10 +39,13 @@ use perspective_js::utils::ApiResult;
38
39
  use wasm_bindgen::prelude::*;
39
40
  use web_sys::*;
40
41
  use yew::html::ImplicitClone;
42
+ use yew::prelude::*;
41
43
 
42
44
  use self::activate::*;
45
+ pub use self::limits::RenderLimits;
43
46
  use self::limits::*;
44
47
  use self::plugin_store::*;
48
+ pub use self::props::RendererProps;
45
49
  pub use self::registry::*;
46
50
  use self::render_timer::*;
47
51
  use crate::config::*;
@@ -54,9 +58,13 @@ pub struct RendererData {
54
58
  plugin_data: RefCell<RendererMutData>,
55
59
  draw_lock: DebounceMutex,
56
60
  pub plugin_changed: PubSub<JsPerspectiveViewerPlugin>,
57
- pub render_limits_changed: PubSub<(bool, RenderLimits)>,
58
61
  pub style_changed: PubSub<()>,
59
62
  pub reset_changed: PubSub<()>,
63
+
64
+ /// Injected callback from the root component, replacing the former
65
+ /// `render_limits_changed: PubSub` field. Fires after every draw/update
66
+ /// with the computed render limits.
67
+ pub on_render_limits_changed: RefCell<Option<Callback<RenderLimits>>>,
60
68
  }
61
69
 
62
70
  /// Mutable state
@@ -70,8 +78,6 @@ pub struct RendererMutData {
70
78
  pending_plugin: Option<usize>,
71
79
  }
72
80
 
73
- type RenderLimits = (usize, usize, Option<usize>, Option<usize>);
74
-
75
81
  /// The state object responsible for the active [`JsPerspectiveViewerPlugin`].
76
82
  #[derive(Clone)]
77
83
  pub struct Renderer(Rc<RendererData>);
@@ -120,9 +126,9 @@ impl Renderer {
120
126
  }),
121
127
  draw_lock: Default::default(),
122
128
  plugin_changed: Default::default(),
123
- render_limits_changed: Default::default(),
124
129
  style_changed: Default::default(),
125
130
  reset_changed: Default::default(),
131
+ on_render_limits_changed: Default::default(),
126
132
  }))
127
133
  }
128
134
 
@@ -322,6 +328,31 @@ impl Renderer {
322
328
  .await
323
329
  }
324
330
 
331
+ pub async fn resize_with_dimensions(&self, width: f64, height: f64) -> ApiResult<()> {
332
+ let draw_mutex = self.draw_lock();
333
+ let timer = self.render_timer();
334
+ draw_mutex
335
+ .debounce(async {
336
+ set_timeout(timer.get_throttle()).await?;
337
+ let plugin = self.get_active_plugin()?;
338
+ let main_panel: &web_sys::HtmlElement = plugin.unchecked_ref();
339
+ let rect = main_panel.get_bounding_client_rect();
340
+ if (height - rect.height()).abs() > 0.5 || (width - rect.width()).abs() > 0.5 {
341
+ let new_width = format!("{}px", width);
342
+ let new_height = format!("{}px", height);
343
+ main_panel.style().set_property("width", &new_width)?;
344
+ main_panel.style().set_property("height", &new_height)?;
345
+ let result = plugin.resize().await;
346
+ main_panel.style().set_property("width", "")?;
347
+ main_panel.style().set_property("height", "")?;
348
+ result?;
349
+ }
350
+
351
+ Ok(())
352
+ })
353
+ .await
354
+ }
355
+
325
356
  /// This will take a future which _should_ create a new view and then will
326
357
  /// draw it. As the `session` closure is asynchronous, it can be cancelled
327
358
  /// by returning `None`.
@@ -367,14 +398,17 @@ impl Renderer {
367
398
  async fn draw_view(&self, view: &perspective_client::View, is_update: bool) -> ApiResult<()> {
368
399
  let plugin = self.get_active_plugin()?;
369
400
  let meta = self.metadata().clone();
370
- let limits = get_row_and_col_limits(view, &meta).await?;
371
- self.render_limits_changed.emit((is_update, limits));
401
+ let mut limits = get_row_and_col_limits(view, &meta).await?;
402
+ limits.is_update = is_update;
403
+ if let Some(cb) = self.0.on_render_limits_changed.borrow().as_ref() {
404
+ cb.emit(limits);
405
+ }
372
406
  let viewer_elem = &self.0.borrow().viewer_elem.clone();
373
407
  if is_update {
374
- let task = plugin.update(view.clone().into(), limits.2, limits.3, false);
408
+ let task = plugin.update(view.clone().into(), limits.max_cols, limits.max_rows, false);
375
409
  activate_plugin(viewer_elem, &plugin, task).await?;
376
410
  } else {
377
- let task = plugin.draw(view.clone().into(), limits.2, limits.3, false);
411
+ let task = plugin.draw(view.clone().into(), limits.max_cols, limits.max_rows, false);
378
412
  activate_plugin(viewer_elem, &plugin, task).await?;
379
413
  }
380
414
 
@@ -477,3 +511,43 @@ fn make_short_name(name: &str) -> String {
477
511
  .filter(|x| x.is_alphabetic())
478
512
  .collect()
479
513
  }
514
+
515
+ impl Renderer {
516
+ /// Snapshot the current renderer state as a [`RendererProps`] value
517
+ /// suitable for passing as a Yew prop. Called by the root component
518
+ /// whenever a renderer-related PubSub event fires.
519
+ pub fn to_props(&self, render_limits: Option<RenderLimits>) -> RendererProps {
520
+ // Guard: don't touch the PluginStore if no plugin has been explicitly
521
+ // selected yet. Calling `get_active_plugin()` or `get_all_plugins()`
522
+ // triggers `PluginStore::init_lazy()`, which snapshots the
523
+ // PLUGIN_REGISTRY. If this happens during component `create()` —
524
+ // before JavaScript has called `registerPlugin()` — the cache will
525
+ // only contain the default Debug plugin and custom plugins registered
526
+ // later will never be found.
527
+ let has_plugin = self.0.borrow().plugins_idx.is_some();
528
+ if has_plugin {
529
+ let plugin_name = self.get_active_plugin().ok().map(|p| p.name());
530
+ let requirements = self.metadata().clone();
531
+ let available_plugins = self
532
+ .get_all_plugins()
533
+ .into_iter()
534
+ .map(|p| p.name())
535
+ .collect::<Vec<_>>()
536
+ .into();
537
+
538
+ RendererProps {
539
+ plugin_name,
540
+ requirements,
541
+ render_limits,
542
+ available_plugins,
543
+ }
544
+ } else {
545
+ RendererProps {
546
+ plugin_name: None,
547
+ requirements: ViewConfigRequirements::default(),
548
+ render_limits,
549
+ available_plugins: PtrEqRc::new(vec![]),
550
+ }
551
+ }
552
+ }
553
+ }
@@ -31,6 +31,24 @@ pub impl ViewConfigUpdate {
31
31
  columns: &[Option<String>],
32
32
  requirements: &ViewConfigRequirements,
33
33
  ) {
34
+ let rollup_features = metadata
35
+ .get_features()
36
+ .map(|x| x.get_group_rollup_modes())
37
+ .unwrap_or_default();
38
+
39
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
40
+ if !group_rollups.contains(
41
+ self.group_rollup_mode
42
+ .as_ref()
43
+ .unwrap_or(&GroupRollupMode::Rollup),
44
+ ) {
45
+ self.group_rollup_mode = group_rollups.first().cloned();
46
+ tracing::debug!(
47
+ "Setting plugin-advised rollup mode {:?}",
48
+ self.group_rollup_mode
49
+ );
50
+ }
51
+
34
52
  if let (
35
53
  None,
36
54
  ViewConfigRequirements {
@@ -17,8 +17,10 @@ use std::ops::{Deref, DerefMut};
17
17
  use perspective_client::config::*;
18
18
  use perspective_js::apierror;
19
19
 
20
+ use crate::utils::PtrEqRc;
20
21
  use crate::*;
21
22
 
23
+ #[derive(Clone, PartialEq)]
22
24
  struct SessionViewExpressionMetadata {
23
25
  edited: HashMap<String, String>,
24
26
  expressions: perspective_client::ExprValidationResult,
@@ -28,9 +30,17 @@ struct SessionViewExpressionMetadata {
28
30
  /// do so without `async`. It must be recreated by any `async` method which
29
31
  /// changes the `View` and may temporarily be out-of-sync with the
30
32
  /// `View`/`ViewConfig`.
31
- #[derive(Default)]
33
+ #[derive(Clone, Default, PartialEq)]
32
34
  pub struct SessionMetadata(Option<SessionMetadataState>);
33
35
 
36
+ pub type SessionMetadataRc = PtrEqRc<SessionMetadata>;
37
+
38
+ impl std::fmt::Debug for SessionMetadata {
39
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40
+ f.write_str("SessionMetadata { .. }")
41
+ }
42
+ }
43
+
34
44
  impl Deref for SessionMetadata {
35
45
  type Target = Option<SessionMetadataState>;
36
46
 
@@ -50,7 +60,7 @@ pub type MetadataMutRef<'a> = std::cell::RefMut<'a, SessionMetadata>;
50
60
 
51
61
  /// TODO the multiple `Option` types could probably be merged since they are
52
62
  /// populated within an async lock
53
- #[derive(Default)]
63
+ #[derive(Clone, Default, PartialEq)]
54
64
  pub struct SessionMetadataState {
55
65
  features: perspective_client::Features,
56
66
  column_names: Vec<String>,
@@ -118,6 +128,17 @@ impl SessionMetadata {
118
128
 
119
129
  /// Returns the unique column names in this session that are expression
120
130
  /// columns.
131
+ pub fn get_expression_schema(&self) -> Option<&HashMap<String, ColumnType>> {
132
+ Some(
133
+ &self
134
+ .as_ref()?
135
+ .expr_meta
136
+ .as_ref()?
137
+ .expressions
138
+ .expression_schema,
139
+ )
140
+ }
141
+
121
142
  pub fn get_expression_columns(&self) -> impl Iterator<Item = &'_ String> {
122
143
  maybe!(Some(
123
144
  self.as_ref()?