@perspective-dev/viewer 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/esm/perspective-viewer.inline.js +2 -2
  23. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  24. package/dist/esm/perspective-viewer.js +2 -2
  25. package/dist/esm/perspective-viewer.js.map +4 -4
  26. package/dist/wasm/perspective-viewer.d.ts +57 -53
  27. package/dist/wasm/perspective-viewer.js +190 -165
  28. package/dist/wasm/perspective-viewer.wasm +0 -0
  29. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  30. package/package.json +7 -5
  31. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  32. package/src/css/column-dropdown.css +109 -0
  33. package/src/{less/column-selector.less → css/column-selector.css} +160 -158
  34. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  35. package/src/{less/column-style.less → css/column-style.css} +52 -66
  36. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  37. package/src/{less/config-selector.less → css/config-selector.css} +151 -135
  38. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  39. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  40. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  41. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  42. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  43. package/src/css/dom/checkbox.css +102 -0
  44. package/src/css/dom/scrollbar.css +35 -0
  45. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  46. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  47. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  48. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  49. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  50. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  51. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  52. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  53. package/src/css/plugin-selector.css +261 -0
  54. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  55. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  56. package/src/css/type-icon.css +116 -0
  57. package/src/{less/viewer.less → css/viewer.css} +112 -146
  58. package/src/rust/components/column_dropdown.rs +229 -119
  59. package/src/rust/components/column_selector/active_column.rs +81 -62
  60. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  61. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  62. package/src/rust/components/column_selector/config_selector.rs +315 -199
  63. package/src/rust/components/column_selector/empty_column.rs +2 -2
  64. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  65. package/src/rust/components/column_selector/filter_column.rs +37 -26
  66. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  67. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  68. package/src/rust/components/column_selector/pivot_column.rs +11 -5
  69. package/src/rust/components/column_selector/sort_column.rs +23 -13
  70. package/src/rust/components/column_selector.rs +163 -84
  71. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  72. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  73. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  77. package/src/rust/components/column_settings_sidebar.rs +91 -53
  78. package/src/rust/components/containers/dragdrop_list.rs +2 -1
  79. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  80. package/src/rust/components/containers/split_panel.rs +1 -0
  81. package/src/rust/components/containers/tab_list.rs +1 -1
  82. package/src/rust/components/copy_dropdown.rs +7 -28
  83. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  84. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  85. package/src/rust/components/datetime_column_style.rs +4 -2
  86. package/src/rust/components/editable_header.rs +7 -4
  87. package/src/rust/components/empty_row.rs +1 -1
  88. package/src/rust/components/export_dropdown.rs +4 -30
  89. package/src/rust/components/expression_editor.rs +19 -10
  90. package/src/rust/components/filter_dropdown.rs +246 -102
  91. package/src/rust/components/font_loader.rs +11 -28
  92. package/src/rust/components/form/code_editor.rs +17 -2
  93. package/src/rust/components/form/color_range_selector.rs +19 -6
  94. package/src/rust/components/form/debug.rs +30 -13
  95. package/src/rust/components/function_dropdown.rs +186 -113
  96. package/src/rust/components/main_panel.rs +71 -89
  97. package/src/rust/components/mod.rs +1 -1
  98. package/src/rust/components/modal.rs +7 -1
  99. package/src/rust/components/number_column_style.rs +22 -7
  100. package/src/rust/components/plugin_selector.rs +34 -102
  101. package/src/rust/components/portal.rs +274 -0
  102. package/src/rust/components/render_warning.rs +72 -123
  103. package/src/rust/components/settings_panel.rs +115 -11
  104. package/src/rust/components/status_bar.rs +222 -98
  105. package/src/rust/components/status_bar_counter.rs +8 -20
  106. package/src/rust/components/status_indicator.rs +64 -114
  107. package/src/rust/components/string_column_style.rs +2 -2
  108. package/src/rust/components/style/style_cache.rs +5 -1
  109. package/src/rust/components/viewer.rs +391 -39
  110. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  111. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  112. package/src/rust/custom_elements/mod.rs +0 -7
  113. package/src/rust/custom_elements/modal.rs +7 -103
  114. package/src/rust/custom_elements/viewer.rs +99 -35
  115. package/src/rust/custom_events.rs +23 -2
  116. package/src/rust/dragdrop.rs +149 -10
  117. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  118. package/src/rust/js/plugin.rs +1 -1
  119. package/src/rust/lib.rs +5 -4
  120. package/src/rust/presentation/props.rs +39 -0
  121. package/src/rust/presentation/sheets.rs +3 -3
  122. package/src/rust/presentation.rs +44 -8
  123. package/src/rust/renderer/limits.rs +32 -3
  124. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  125. package/src/rust/renderer.rs +83 -9
  126. package/src/rust/session/column_defaults_update.rs +1 -1
  127. package/src/rust/session/metadata.rs +23 -2
  128. package/src/rust/session/props.rs +178 -0
  129. package/src/rust/session.rs +124 -117
  130. package/src/rust/tasks/column_locator.rs +133 -0
  131. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  132. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  133. package/src/rust/{model → tasks}/eject.rs +2 -2
  134. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  135. package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
  136. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  137. package/src/rust/tasks/mod.rs +52 -0
  138. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  139. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  140. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  141. package/src/rust/tasks/structural.rs +53 -0
  142. package/src/rust/utils/mod.rs +4 -0
  143. package/src/rust/utils/modal_position.rs +110 -0
  144. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  145. package/src/rust/utils/pubsub.rs +11 -1
  146. package/src/svg/bg-pattern.png +0 -0
  147. package/src/svg/close-icon.svg +1 -1
  148. package/src/svg/expression.svg +1 -1
  149. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  150. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  151. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  152. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  153. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  154. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  155. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  156. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  157. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  158. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  159. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  160. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  161. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  162. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  163. package/src/svg/radio-hover.svg +1 -1
  164. package/src/svg/radio-off.svg +1 -1
  165. package/src/svg/radio-on.svg +1 -1
  166. package/src/themes/botanical.css +157 -0
  167. package/src/themes/defaults.css +139 -0
  168. package/src/themes/dracula.css +233 -0
  169. package/src/themes/gruvbox-dark.css +255 -0
  170. package/src/themes/gruvbox.css +134 -0
  171. package/src/themes/icons.css +124 -0
  172. package/src/themes/intl/de.css +102 -0
  173. package/src/themes/intl/es.css +102 -0
  174. package/src/themes/intl/fr.css +102 -0
  175. package/src/themes/intl/ja.css +102 -0
  176. package/src/themes/intl/pt.css +102 -0
  177. package/src/themes/intl/zh.css +102 -0
  178. package/src/themes/intl.css +102 -0
  179. package/src/themes/monokai.css +233 -0
  180. package/src/themes/pro-dark.css +158 -0
  181. package/src/themes/{themes.less → pro.css} +17 -21
  182. package/src/themes/solarized-dark.css +135 -0
  183. package/src/themes/solarized.css +95 -0
  184. package/src/themes/themes.css +22 -0
  185. package/src/themes/vaporwave.css +256 -0
  186. package/dist/css/variables.css +0 -0
  187. package/src/less/column-dropdown.less +0 -95
  188. package/src/less/dom/checkbox.less +0 -100
  189. package/src/less/plugin-selector.less +0 -183
  190. package/src/less/type-icon.less +0 -68
  191. package/src/rust/components/error_message.rs +0 -56
  192. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  193. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  194. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  195. package/src/rust/model/column_locator.rs +0 -82
  196. package/src/rust/model/is_invalid_drop.rs +0 -36
  197. package/src/rust/model/mod.rs +0 -100
  198. package/src/rust/model/reset_all.rs +0 -38
  199. package/src/rust/model/structural.rs +0 -244
  200. package/src/themes/botanical.less +0 -142
  201. package/src/themes/dracula.less +0 -101
  202. package/src/themes/gruvbox-dark.less +0 -116
  203. package/src/themes/gruvbox.less +0 -152
  204. package/src/themes/icons.less +0 -130
  205. package/src/themes/intl/de.less +0 -102
  206. package/src/themes/intl/es.less +0 -102
  207. package/src/themes/intl/fr.less +0 -102
  208. package/src/themes/intl/ja.less +0 -102
  209. package/src/themes/intl/pt.less +0 -102
  210. package/src/themes/intl/zh.less +0 -102
  211. package/src/themes/intl.less +0 -102
  212. package/src/themes/monokai.less +0 -107
  213. package/src/themes/pro-dark.less +0 -147
  214. package/src/themes/pro.less +0 -186
  215. package/src/themes/solarized-dark.less +0 -78
  216. package/src/themes/solarized.less +0 -102
  217. package/src/themes/vaporwave.less +0 -145
  218. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  219. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  220. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  223. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  224. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  225. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  226. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  227. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
@@ -0,0 +1,178 @@
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 perspective_client::config::*;
14
+
15
+ use crate::js::plugin::ViewConfigRequirements;
16
+ use crate::session::column_defaults_update::ViewConfigUpdateExt;
17
+ use crate::session::drag_drop_update::ViewConfigExt as DragDropExt;
18
+ use crate::session::metadata::SessionMetadataRc;
19
+ use crate::session::replace_expression_update::ViewConfigExt as ReplaceExprExt;
20
+ use crate::session::{TableErrorState, ViewStats};
21
+ use crate::utils::*;
22
+
23
+ #[derive(Clone, Debug, PartialEq, Default)]
24
+ pub enum TableLoadState {
25
+ #[default]
26
+ Loaded,
27
+ Loading,
28
+ }
29
+
30
+ /// Value-semantic snapshot of the session state read by the root component.
31
+ ///
32
+ /// This does not hold any async handles (`Table`, `View`, `Client`). Those
33
+ /// live inside `Session(Rc<SessionHandle>)` and are accessed directly when
34
+ /// needed by async tasks.
35
+ #[derive(Clone, Debug, PartialEq, Default)]
36
+ pub struct SessionProps {
37
+ /// The current `ViewConfig` driving the active `View`.
38
+ pub config: PtrEqRc<ViewConfig>,
39
+
40
+ /// Row/column statistics for the status bar.
41
+ pub stats: Option<ViewStats>,
42
+
43
+ /// `true` if a `Table` has been loaded into this session.
44
+ pub has_table: Option<TableLoadState>,
45
+
46
+ /// Non-`None` when the session is in an error state (e.g. table load
47
+ /// failure or client disconnection).
48
+ pub error: Option<TableErrorState>,
49
+
50
+ /// Optional title string set via `restore({ title: "..." })`.
51
+ pub title: Option<String>,
52
+
53
+ /// Cloned snapshot of `SessionMetadata` at the time of the last
54
+ /// `to_props()` call. Components read column types, features,
55
+ /// expression info, etc. from this snapshot instead of borrowing
56
+ /// `Session`'s `RefCell` directly.
57
+ pub metadata: SessionMetadataRc,
58
+ }
59
+
60
+ impl SessionProps {
61
+ /// Returns `true` if the session is in any error state.
62
+ pub fn is_errored(&self) -> bool {
63
+ self.error.is_some()
64
+ }
65
+
66
+ /// Returns `true` if the error state represents a reconnectable
67
+ /// disconnection rather than a fatal failure.
68
+ pub fn is_reconnect(&self) -> bool {
69
+ self.error
70
+ .as_ref()
71
+ .map(|x| x.is_reconnect())
72
+ .unwrap_or_default()
73
+ }
74
+
75
+ /// Returns `true` if `name` appears in any active config slot (columns,
76
+ /// group-by, split-by, filter, or sort).
77
+ pub fn is_column_active(&self, name: &str) -> bool {
78
+ self.config.columns.iter().any(|maybe_col| {
79
+ maybe_col
80
+ .as_ref()
81
+ .map(|col| col == name)
82
+ .unwrap_or_default()
83
+ }) || self.config.group_by.iter().any(|col| col == name)
84
+ || self.config.split_by.iter().any(|col| col == name)
85
+ || self.config.filter.iter().any(|col| col.column() == name)
86
+ || self.config.sort.iter().any(|col| col.0 == name)
87
+ }
88
+
89
+ /// Returns `true` if the expression column `name` is referenced by any
90
+ /// part of the current view config.
91
+ pub fn is_column_expression_in_use(&self, name: &str) -> bool {
92
+ self.config.is_column_expression_in_use(name)
93
+ }
94
+
95
+ fn all_columns(&self) -> Vec<String> {
96
+ self.metadata
97
+ .get_table_columns()
98
+ .into_iter()
99
+ .flatten()
100
+ .cloned()
101
+ .collect()
102
+ }
103
+
104
+ /// Build a [`ViewConfigUpdate`] that applies a drag-drop of `column`
105
+ /// into the given `drop` target at `index`.
106
+ pub fn create_drag_drop_update(
107
+ &self,
108
+ column: String,
109
+ index: usize,
110
+ drop: DragTarget,
111
+ drag: DragEffect,
112
+ requirements: &ViewConfigRequirements,
113
+ ) -> ViewConfigUpdate {
114
+ let col_type = self
115
+ .metadata
116
+ .get_column_table_type(column.as_str())
117
+ .unwrap();
118
+
119
+ self.config.create_drag_drop_update(
120
+ column,
121
+ col_type,
122
+ index,
123
+ drop,
124
+ drag,
125
+ requirements,
126
+ self.metadata.get_features().unwrap(),
127
+ )
128
+ }
129
+
130
+ /// Populate `config_update` with default column settings (aggregates,
131
+ /// etc.) based on the current metadata and plugin requirements.
132
+ pub fn set_update_column_defaults(
133
+ &self,
134
+ config_update: &mut ViewConfigUpdate,
135
+ requirements: &ViewConfigRequirements,
136
+ ) {
137
+ config_update.set_update_column_defaults(
138
+ &self.metadata,
139
+ &self.all_columns().into_iter().map(Some).collect::<Vec<_>>(),
140
+ requirements,
141
+ )
142
+ }
143
+
144
+ /// Build a [`ViewConfigUpdate`] that replaces `old_expr_name` with
145
+ /// `new_expr` in every config slot where it appears.
146
+ pub fn create_replace_expression_update(
147
+ &self,
148
+ old_expr_name: &str,
149
+ new_expr: &Expression<'static>,
150
+ ) -> ViewConfigUpdate {
151
+ let old_expr_val = self
152
+ .metadata
153
+ .get_expression_by_alias(old_expr_name)
154
+ .unwrap();
155
+
156
+ let old_expr = Expression::new(Some(old_expr_name.into()), old_expr_val.into());
157
+ self.config
158
+ .create_replace_expression_update(&old_expr, new_expr)
159
+ }
160
+
161
+ /// Build a [`ViewConfigUpdate`] that renames `old_expr_name` to
162
+ /// `new_expr_name` (or clears the alias if `None`), keeping the
163
+ /// expression body unchanged.
164
+ pub fn create_rename_expression_update(
165
+ &self,
166
+ old_expr_name: String,
167
+ new_expr_name: Option<String>,
168
+ ) -> ViewConfigUpdate {
169
+ let old_expr_val = self
170
+ .metadata
171
+ .get_expression_by_alias(&old_expr_name)
172
+ .unwrap();
173
+ let old_expr = Expression::new(Some(old_expr_name.into()), old_expr_val.clone().into());
174
+ let new_expr = Expression::new(new_expr_name.map(|n| n.into()), old_expr_val.into());
175
+ self.config
176
+ .create_replace_expression_update(&old_expr, &new_expr)
177
+ }
178
+ }
@@ -10,10 +10,11 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- mod column_defaults_update;
14
- mod drag_drop_update;
13
+ pub(crate) mod column_defaults_update;
14
+ pub(crate) mod drag_drop_update;
15
15
  mod metadata;
16
- mod replace_expression_update;
16
+ mod props;
17
+ pub(crate) mod replace_expression_update;
17
18
  mod view_subscription;
18
19
 
19
20
  use std::cell::{Ref, RefCell};
@@ -30,9 +31,9 @@ use wasm_bindgen::prelude::*;
30
31
  use yew::html::ImplicitClone;
31
32
  use yew::prelude::*;
32
33
 
33
- pub use self::metadata::MetadataRef;
34
34
  use self::metadata::*;
35
- use self::replace_expression_update::*;
35
+ pub use self::metadata::{MetadataRef, SessionMetadata, SessionMetadataRc};
36
+ pub use self::props::{SessionProps, TableLoadState};
36
37
  pub use self::view_subscription::ViewStats;
37
38
  use self::view_subscription::*;
38
39
  use crate::js::plugin::*;
@@ -44,12 +45,19 @@ pub struct SessionHandle {
44
45
  session_data: RefCell<SessionData>,
45
46
  pub table_updated: PubSub<()>,
46
47
  pub table_loaded: PubSub<()>,
47
- pub table_errored: PubSub<ApiError>,
48
48
  pub table_unloaded: PubSub<bool>,
49
49
  pub view_created: PubSub<()>,
50
50
  pub view_config_changed: PubSub<()>,
51
- pub stats_changed: PubSub<Option<ViewStats>>,
52
51
  pub title_changed: PubSub<Option<String>>,
52
+
53
+ /// Injected callback from the root component, replacing the former
54
+ /// `stats_changed: PubSub` field. Fires when view stats are updated.
55
+ pub on_stats_changed: RefCell<Option<Callback<()>>>,
56
+
57
+ /// Injected callback from the root component, replacing the former
58
+ /// `table_errored: PubSub` field. Fires when an error is set on the
59
+ /// session (table load failure, client disconnect, invalid config, etc.).
60
+ pub on_table_errored: RefCell<Option<Callback<()>>>,
53
61
  }
54
62
 
55
63
  impl Deref for SessionHandle {
@@ -70,6 +78,7 @@ pub struct SessionData {
70
78
  config: ViewConfig,
71
79
  view_sub: Option<ViewSubscription>,
72
80
  stats: Option<ViewStats>,
81
+ is_loading: bool,
73
82
  is_clean: bool,
74
83
  is_paused: bool,
75
84
  error: Option<TableErrorState>,
@@ -79,6 +88,45 @@ pub struct SessionData {
79
88
  #[derive(Clone)]
80
89
  pub struct TableErrorState(ApiError, Option<ReconnectCallback>);
81
90
 
91
+ impl PartialEq for TableErrorState {
92
+ fn eq(&self, other: &Self) -> bool {
93
+ self.0.to_string() == other.0.to_string()
94
+ }
95
+ }
96
+
97
+ impl std::fmt::Debug for TableErrorState {
98
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99
+ f.debug_tuple("TableErrorState")
100
+ .field(&self.0.to_string())
101
+ .finish()
102
+ }
103
+ }
104
+
105
+ impl TableErrorState {
106
+ pub fn message(&self) -> String {
107
+ self.0.message()
108
+ }
109
+
110
+ pub fn stacktrace(&self) -> String {
111
+ self.0.stacktrace()
112
+ }
113
+
114
+ pub fn kind(&self) -> &'static str {
115
+ self.0.kind()
116
+ }
117
+
118
+ pub fn is_reconnect(&self) -> bool {
119
+ self.1.is_some()
120
+ }
121
+ }
122
+
123
+ #[derive(Debug, Default)]
124
+ pub enum TableIntermediateState {
125
+ #[default]
126
+ Ejected,
127
+ Reloaded,
128
+ }
129
+
82
130
  /// Options for [`Session::reset`]
83
131
  #[derive(Default)]
84
132
  pub struct ResetOptions {
@@ -86,7 +134,7 @@ pub struct ResetOptions {
86
134
  pub expressions: bool,
87
135
 
88
136
  /// Reset the [`Table`]
89
- pub table: bool,
137
+ pub table: Option<TableIntermediateState>,
90
138
 
91
139
  /// Reset the [`ViewConfig`]
92
140
  pub config: bool,
@@ -125,15 +173,15 @@ impl Session {
125
173
  Self(Rc::default())
126
174
  }
127
175
 
128
- pub fn metadata(&self) -> MetadataRef<'_> {
176
+ pub(crate) fn metadata(&self) -> MetadataRef<'_> {
129
177
  std::cell::Ref::map(self.borrow(), |x| &x.metadata)
130
178
  }
131
179
 
132
- pub fn metadata_mut(&self) -> MetadataMutRef<'_> {
180
+ pub(crate) fn metadata_mut(&self) -> MetadataMutRef<'_> {
133
181
  std::cell::RefMut::map(self.borrow_mut(), |x| &mut x.metadata)
134
182
  }
135
183
 
136
- pub fn get_title(&self) -> Option<String> {
184
+ pub(crate) fn get_title(&self) -> Option<String> {
137
185
  self.borrow().title.clone()
138
186
  }
139
187
 
@@ -160,16 +208,27 @@ impl Session {
160
208
  self.borrow_mut().config.reset(options.expressions);
161
209
  }
162
210
 
163
- if options.table {
164
- self.borrow_mut().table = None;
165
- self.borrow_mut().metadata = SessionMetadata::default();
166
- }
211
+ match options.table {
212
+ Some(TableIntermediateState::Ejected) => {
213
+ self.borrow_mut().is_loading = false;
214
+ self.borrow_mut().table = None;
215
+ self.borrow_mut().metadata = SessionMetadata::default();
216
+ },
217
+ Some(TableIntermediateState::Reloaded) => {
218
+ self.borrow_mut().is_loading = true;
219
+ self.borrow_mut().table = None;
220
+ self.borrow_mut().metadata = SessionMetadata::default();
221
+ },
222
+ _ => {
223
+ self.borrow_mut().is_loading = false;
224
+ },
225
+ };
167
226
 
168
227
  let table_unloaded = self.table_unloaded.callback();
169
228
  self.borrow_mut().is_clean = false;
170
229
  async move {
171
230
  let res = view.delete().await;
172
- if options.table {
231
+ if options.table.is_some() {
173
232
  table_unloaded.emit(true)
174
233
  }
175
234
 
@@ -177,8 +236,15 @@ impl Session {
177
236
  }
178
237
  }
179
238
 
180
- pub fn has_table(&self) -> bool {
181
- self.borrow().table.is_some()
239
+ pub(crate) fn has_table(&self) -> Option<TableLoadState> {
240
+ let data = self.borrow();
241
+ if data.table.is_some() {
242
+ Some(TableLoadState::Loaded)
243
+ } else if data.is_loading {
244
+ Some(TableLoadState::Loading)
245
+ } else {
246
+ None
247
+ }
182
248
  }
183
249
 
184
250
  pub fn get_table(&self) -> Option<perspective_client::Table> {
@@ -222,11 +288,13 @@ impl Session {
222
288
  match SessionMetadata::from_table(&table).await {
223
289
  Ok(metadata) => {
224
290
  let client = table.get_client();
225
- let set_error = self.table_errored.as_boxfn();
291
+ let on_error = self.on_table_errored.borrow().clone();
226
292
  let session = self.clone();
227
293
  let poll_loop = LocalPollLoop::new(move |(message, reconnect): (ApiError, _)| {
228
- set_error(message.clone());
229
294
  session.borrow_mut().error = Some(TableErrorState(message, reconnect));
295
+ if let Some(cb) = &on_error {
296
+ cb.emit(());
297
+ }
230
298
  if let Some(sub) = session.borrow_mut().view_sub.take() {
231
299
  sub.dismiss();
232
300
  }
@@ -247,6 +315,7 @@ impl Session {
247
315
  let sub = self.borrow_mut().view_sub.take();
248
316
  self.borrow_mut().metadata = metadata;
249
317
  self.borrow_mut().table = Some(table);
318
+ self.borrow_mut().is_loading = false;
250
319
  self.borrow_mut().is_clean = false;
251
320
  sub.delete().await?;
252
321
  self.table_loaded.emit(());
@@ -286,7 +355,10 @@ impl Session {
286
355
  })),
287
356
  ));
288
357
 
289
- self.table_errored.emit(err.clone());
358
+ if let Some(cb) = self.on_table_errored.borrow().as_ref() {
359
+ cb.emit(());
360
+ }
361
+
290
362
  let sub = self.borrow_mut().view_sub.take();
291
363
  if reset_table {
292
364
  self.borrow_mut().metadata = SessionMetadata::default();
@@ -324,31 +396,19 @@ impl Session {
324
396
  Some(perspective_js::Table::from(self.borrow().table.clone()?).into())
325
397
  }
326
398
 
327
- pub fn js_get_view(&self) -> Option<JsValue> {
328
- let view = self.borrow().view_sub.as_ref()?.get_view().clone();
329
- Some(perspective_js::View::from(view).into())
330
- }
331
-
332
- pub fn is_errored(&self) -> bool {
399
+ pub(crate) fn is_errored(&self) -> bool {
333
400
  self.borrow().error.is_some()
334
401
  }
335
402
 
336
- pub fn get_error(&self) -> Option<ApiError> {
403
+ pub(crate) fn get_error(&self) -> Option<ApiError> {
337
404
  self.borrow().error.as_ref().map(|x| x.0.clone())
338
405
  }
339
406
 
340
- pub fn is_reconnect(&self) -> bool {
341
- self.borrow()
342
- .error
343
- .as_ref()
344
- .map(|x| x.1.is_some())
345
- .unwrap_or_default()
346
- }
347
-
348
407
  pub async fn reconnect(&self) -> ApiResult<()> {
349
408
  let err = self.borrow().error.clone();
350
409
  if let Some(TableErrorState(_, Some(reconnect))) = err {
351
410
  reconnect().await?;
411
+ self.borrow_mut().is_loading = false;
352
412
  self.borrow_mut().error = None;
353
413
  self.borrow_mut().is_clean = false;
354
414
  self.borrow_mut().view_sub = None;
@@ -358,82 +418,6 @@ impl Session {
358
418
  Ok(())
359
419
  }
360
420
 
361
- pub fn is_column_expression_in_use(&self, name: &str) -> bool {
362
- self.borrow().config.is_column_expression_in_use(name)
363
- }
364
-
365
- /// Is this column currently being used or not
366
- pub fn is_column_active(&self, name: &str) -> bool {
367
- let config = Ref::map(self.borrow(), |x| &x.config);
368
- config.columns.iter().any(|maybe_col| {
369
- maybe_col
370
- .as_ref()
371
- .map(|col| col == name)
372
- .unwrap_or_default()
373
- }) || config.group_by.iter().any(|col| col == name)
374
- || config.split_by.iter().any(|col| col == name)
375
- || config.filter.iter().any(|col| col.column() == name)
376
- || config.sort.iter().any(|col| col.0 == name)
377
- }
378
-
379
- pub fn create_drag_drop_update(
380
- &self,
381
- column: String,
382
- index: usize,
383
- drop: DragTarget,
384
- drag: DragEffect,
385
- requirements: &ViewConfigRequirements,
386
- ) -> ViewConfigUpdate {
387
- use self::drag_drop_update::*;
388
- let col_type = self
389
- .metadata()
390
- .get_column_table_type(column.as_str())
391
- .unwrap();
392
-
393
- self.get_view_config().create_drag_drop_update(
394
- column,
395
- col_type,
396
- index,
397
- drop,
398
- drag,
399
- requirements,
400
- self.metadata().get_features().unwrap(),
401
- )
402
- }
403
-
404
- /// An async task which replaces a `column` aliased expression with another.
405
- pub async fn create_replace_expression_update(
406
- &self,
407
- old_expr_name: &str,
408
- new_expr: &Expression<'static>,
409
- ) -> ViewConfigUpdate {
410
- let old_expr_val = self
411
- .metadata()
412
- .get_expression_by_alias(old_expr_name)
413
- .unwrap();
414
-
415
- let old_expr = Expression::new(Some(old_expr_name.into()), old_expr_val.into());
416
-
417
- use self::replace_expression_update::*;
418
- self.get_view_config()
419
- .create_replace_expression_update(&old_expr, new_expr)
420
- }
421
-
422
- pub async fn create_rename_expression_update(
423
- &self,
424
- old_expr_name: String,
425
- new_expr_name: Option<String>,
426
- ) -> ViewConfigUpdate {
427
- let old_expr_val = self
428
- .metadata()
429
- .get_expression_by_alias(&old_expr_name)
430
- .expect_throw(&format!("Unable to get expr with name {old_expr_name}"));
431
- let old_expr = Expression::new(Some(old_expr_name.into()), old_expr_val.clone().into());
432
- let new_expr = Expression::new(new_expr_name.map(|n| n.into()), old_expr_val.into());
433
- self.get_view_config()
434
- .create_replace_expression_update(&old_expr, &new_expr)
435
- }
436
-
437
421
  /// Validate an expression string and marshall the results.
438
422
  pub async fn validate_expr(
439
423
  &self,
@@ -527,7 +511,7 @@ impl Session {
527
511
  .map(|sub| sub.get_view().clone())
528
512
  }
529
513
 
530
- pub fn get_table_stats(&self) -> Option<ViewStats> {
514
+ pub(crate) fn get_table_stats(&self) -> Option<ViewStats> {
531
515
  self.borrow().stats.clone()
532
516
  }
533
517
 
@@ -585,7 +569,7 @@ impl Session {
585
569
  use self::column_defaults_update::*;
586
570
  config_update.set_update_column_defaults(
587
571
  &self.metadata(),
588
- &self.all_columns().into_iter().map(Some).collect::<Vec<_>>(),
572
+ &self.get_view_config().columns,
589
573
  requirements,
590
574
  )
591
575
  }
@@ -600,7 +584,7 @@ impl Session {
600
584
  return Err(ApiError::new(x.0.clone()));
601
585
  }
602
586
 
603
- if self.borrow_mut().config.apply_update(config_update) {
587
+ if self.borrow_mut().config.apply_update(config_update) && self.0.borrow().is_clean {
604
588
  self.0.borrow_mut().is_clean = false;
605
589
  self.view_config_changed.emit(());
606
590
  }
@@ -673,8 +657,10 @@ impl Session {
673
657
  }
674
658
 
675
659
  fn update_stats(&self, stats: ViewStats) {
676
- self.borrow_mut().stats = Some(stats.clone());
677
- self.stats_changed.emit(Some(stats));
660
+ self.borrow_mut().stats = Some(stats);
661
+ if let Some(cb) = self.on_stats_changed.borrow().as_ref() {
662
+ cb.emit(());
663
+ }
678
664
  }
679
665
 
680
666
  fn all_columns(&self) -> Vec<String> {
@@ -781,6 +767,27 @@ impl Session {
781
767
  std::mem::swap(&mut is_clean, &mut self.0.borrow_mut().is_clean);
782
768
  is_clean
783
769
  }
770
+
771
+ /// Snapshot the current session state as a [`SessionProps`] value suitable
772
+ /// for passing as a Yew prop. Called by the root component whenever a
773
+ /// session-related PubSub event fires.
774
+ pub fn to_props(&self) -> SessionProps {
775
+ let data = self.borrow();
776
+ SessionProps {
777
+ config: PtrEqRc::new(data.config.clone()),
778
+ stats: data.stats.clone(),
779
+ has_table: if data.table.is_some() {
780
+ Some(TableLoadState::Loaded)
781
+ } else if data.is_loading {
782
+ Some(TableLoadState::Loading)
783
+ } else {
784
+ None
785
+ },
786
+ error: data.error.clone(),
787
+ title: data.title.clone(),
788
+ metadata: PtrEqRc::new(data.metadata.clone()),
789
+ }
790
+ }
784
791
  }
785
792
 
786
793
  /// A newtype wrapper which only provides `create_view()`