@perspective-dev/viewer 4.0.0 → 4.1.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 (184) 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/dracula.css +1 -1
  4. package/dist/css/gruvbox-dark.css +1 -1
  5. package/dist/css/gruvbox.css +1 -1
  6. package/dist/css/icons.css +1 -1
  7. package/dist/css/monokai.css +1 -1
  8. package/dist/css/pro-dark.css +1 -1
  9. package/dist/css/pro.css +1 -1
  10. package/dist/css/solarized-dark.css +1 -1
  11. package/dist/css/solarized.css +1 -1
  12. package/dist/css/themes.css +1 -1
  13. package/dist/css/vaporwave.css +1 -1
  14. package/dist/esm/extensions.d.ts +23 -2
  15. package/dist/esm/perspective-viewer.d.ts +2 -7
  16. package/dist/esm/perspective-viewer.inline.js +2 -2
  17. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  18. package/dist/esm/perspective-viewer.js +2 -2
  19. package/dist/esm/perspective-viewer.js.map +4 -4
  20. package/dist/esm/plugin.d.ts +1 -1
  21. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +1 -0
  22. package/dist/wasm/perspective-viewer.d.ts +218 -46
  23. package/dist/wasm/perspective-viewer.js +1251 -762
  24. package/dist/wasm/perspective-viewer.wasm +0 -0
  25. package/dist/wasm/perspective-viewer.wasm.d.ts +38 -19
  26. package/package.json +1 -1
  27. package/src/less/containers/scroll-panel.less +0 -1
  28. package/src/less/plugin-selector.less +15 -5
  29. package/src/less/status-bar.less +75 -27
  30. package/src/less/viewer.less +140 -58
  31. package/src/rust/components/column_dropdown.rs +21 -21
  32. package/src/rust/components/column_selector/active_column.rs +131 -120
  33. package/src/rust/components/column_selector/add_expression_button.rs +5 -0
  34. package/src/rust/components/column_selector/aggregate_selector.rs +8 -4
  35. package/src/rust/components/column_selector/config_selector.rs +170 -161
  36. package/src/rust/components/column_selector/empty_column.rs +16 -11
  37. package/src/rust/components/column_selector/{expression_toolbar.rs → expr_edit_button.rs} +7 -0
  38. package/src/rust/components/column_selector/filter_column.rs +195 -194
  39. package/src/rust/components/column_selector/inactive_column.rs +82 -67
  40. package/src/rust/components/column_selector/pivot_column.rs +16 -11
  41. package/src/rust/components/column_selector/sort_column.rs +9 -7
  42. package/src/rust/components/column_selector.rs +42 -37
  43. package/src/rust/components/column_settings_sidebar/save_settings.rs +3 -1
  44. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +58 -0
  45. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +6 -6
  46. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +2 -94
  47. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +111 -0
  48. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +3 -3
  49. package/src/rust/components/column_settings_sidebar/style_tab.rs +23 -83
  50. package/src/rust/components/{column_settings_sidebar/sidebar.rs → column_settings_sidebar.rs} +198 -171
  51. package/src/rust/components/containers/dragdrop_list.rs +20 -20
  52. package/src/rust/components/containers/dropdown_menu.rs +4 -6
  53. package/src/rust/components/containers/mod.rs +1 -4
  54. package/src/rust/components/containers/scroll_panel.rs +80 -80
  55. package/src/rust/components/containers/scroll_panel_item.rs +36 -36
  56. package/src/rust/components/containers/select.rs +46 -44
  57. package/src/rust/components/containers/sidebar.rs +3 -19
  58. package/src/rust/components/{column_settings_sidebar/style_tab/symbol/symbol_config.rs → containers/sidebar_close_button.rs} +15 -9
  59. package/src/rust/components/containers/split_panel.rs +212 -200
  60. package/src/rust/components/containers/tab_list.rs +11 -11
  61. package/src/rust/components/copy_dropdown.rs +22 -25
  62. package/src/rust/components/datetime_column_style/custom.rs +19 -19
  63. package/src/rust/components/datetime_column_style/simple.rs +13 -14
  64. package/src/rust/components/datetime_column_style.rs +75 -76
  65. package/src/rust/components/editable_header.rs +18 -14
  66. package/src/rust/components/empty_row.rs +5 -5
  67. package/src/rust/components/export_dropdown.rs +42 -42
  68. package/src/rust/components/expression_editor.rs +25 -19
  69. package/src/rust/components/filter_dropdown.rs +22 -22
  70. package/src/rust/components/font_loader.rs +11 -9
  71. package/src/rust/components/form/code_editor.rs +106 -105
  72. package/src/rust/components/form/color_range_selector.rs +14 -12
  73. package/src/rust/components/form/color_selector.rs +3 -1
  74. package/src/rust/components/form/debug.rs +95 -94
  75. package/src/rust/components/form/highlight.rs +5 -3
  76. package/src/rust/components/form/mod.rs +3 -2
  77. package/src/rust/components/form/optional_field.rs +2 -2
  78. package/src/rust/components/form/{select_field.rs → select_enum_field.rs} +1 -46
  79. package/src/rust/components/form/select_value_field.rs +64 -0
  80. package/src/rust/components/function_dropdown.rs +21 -21
  81. package/src/rust/components/main_panel.rs +219 -0
  82. package/src/rust/components/mod.rs +6 -6
  83. package/src/rust/components/modal.rs +42 -42
  84. package/src/rust/components/number_column_style.rs +34 -88
  85. package/src/rust/components/plugin_selector.rs +22 -25
  86. package/src/rust/components/render_warning.rs +9 -6
  87. package/src/rust/components/settings_panel.rs +82 -0
  88. package/src/rust/components/status_bar.rs +250 -146
  89. package/src/rust/components/status_bar_counter.rs +26 -119
  90. package/src/rust/components/status_indicator.rs +95 -79
  91. package/src/rust/components/string_column_style.rs +45 -45
  92. package/src/rust/components/style/style_provider.rs +1 -15
  93. package/src/rust/components/style_controls/number_string_format/digits_section.rs +1 -1
  94. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  95. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  96. package/src/rust/components/style_controls/number_string_format.rs +45 -46
  97. package/src/rust/components/type_icon.rs +14 -11
  98. package/src/rust/components/viewer.rs +241 -384
  99. package/src/rust/config/columns_config.rs +2 -2
  100. package/src/rust/config/datetime_column_style.rs +1 -6
  101. package/src/rust/config/mod.rs +1 -0
  102. package/src/rust/config/number_column_style.rs +0 -6
  103. package/src/rust/config/number_string_format.rs +27 -4
  104. package/src/rust/config/viewer_config.rs +27 -167
  105. package/src/rust/custom_elements/copy_dropdown.rs +14 -6
  106. package/src/rust/custom_elements/export_dropdown.rs +15 -7
  107. package/src/rust/custom_elements/filter_dropdown.rs +4 -4
  108. package/src/rust/custom_elements/mod.rs +3 -0
  109. package/src/rust/custom_elements/viewer.rs +353 -161
  110. package/src/rust/custom_events.rs +55 -32
  111. package/src/rust/dragdrop.rs +4 -24
  112. package/src/rust/exprtk/cursor.rs +10 -1
  113. package/src/rust/exprtk/mod.rs +2 -0
  114. package/src/rust/exprtk/tokenize.rs +20 -3
  115. package/src/rust/js/clipboard.rs +2 -2
  116. package/src/rust/js/mimetype.rs +2 -7
  117. package/src/rust/js/mod.rs +0 -1
  118. package/src/rust/js/plugin.rs +7 -0
  119. package/src/rust/lib.rs +18 -5
  120. package/src/rust/model/column_locator.rs +82 -0
  121. package/src/rust/model/columns_iter_set.rs +1 -0
  122. package/src/rust/model/copy_export.rs +50 -14
  123. package/src/rust/model/edit_expression.rs +2 -5
  124. package/src/rust/model/eject.rs +41 -0
  125. package/src/rust/model/export_app.rs +3 -2
  126. package/src/rust/model/get_viewer_config.rs +4 -28
  127. package/src/rust/model/intersection_observer.rs +20 -8
  128. package/src/rust/model/mod.rs +11 -4
  129. package/src/rust/model/plugin_column_styles.rs +0 -31
  130. package/src/rust/model/reset_all.rs +38 -0
  131. package/src/rust/model/resize_observer.rs +34 -7
  132. package/src/rust/model/restore_and_render.rs +12 -7
  133. package/src/rust/{utils/scope.rs → model/send_plugin_config.rs} +32 -35
  134. package/src/rust/model/structural.rs +194 -23
  135. package/src/rust/model/update_and_render.rs +14 -4
  136. package/src/rust/{model/create_col.rs → presentation/column_locator.rs} +73 -42
  137. package/src/rust/{utils/wasm_abi.rs → presentation/sheets.rs} +54 -40
  138. package/src/rust/presentation.rs +60 -119
  139. package/src/rust/renderer/activate.rs +20 -5
  140. package/src/rust/renderer/limits.rs +0 -149
  141. package/src/rust/renderer/render_timer.rs +1 -1
  142. package/src/rust/renderer.rs +34 -18
  143. package/src/rust/root.rs +50 -0
  144. package/src/rust/session/column_defaults_update.rs +4 -4
  145. package/src/rust/session/drag_drop_update.rs +1 -1
  146. package/src/rust/session/metadata.rs +3 -17
  147. package/src/rust/session/replace_expression_update.rs +1 -2
  148. package/src/rust/session.rs +162 -82
  149. package/src/rust/utils/browser/blob.rs +16 -2
  150. package/src/rust/utils/browser/download.rs +1 -0
  151. package/src/rust/{components/column_settings_sidebar/mod.rs → utils/browser/dragdrop.rs} +14 -5
  152. package/src/rust/utils/browser/mod.rs +8 -4
  153. package/src/rust/utils/browser/selection.rs +5 -0
  154. package/src/rust/utils/custom_element.rs +28 -13
  155. package/src/rust/utils/datetime.rs +5 -0
  156. package/src/rust/utils/debounce.rs +7 -1
  157. package/src/rust/utils/hooks/use_async_callback.rs +7 -17
  158. package/src/rust/utils/mod.rs +28 -40
  159. package/src/rust/utils/number_format.rs +6 -5
  160. package/src/rust/utils/pubsub.rs +15 -10
  161. package/src/rust/utils/weak_scope.rs +11 -1
  162. package/src/svg/bookmark-icon.svg +4 -0
  163. package/src/svg/drag-handle copy.svg +10 -0
  164. package/src/svg/drawer-tab-hover.svg +5 -7
  165. package/src/svg/drawer-tab-invert-hover.svg +4 -8
  166. package/src/svg/drawer-tab-invert.svg +4 -7
  167. package/src/svg/drawer-tab.svg +4 -6
  168. package/src/svg/status_ok.svg +24 -24
  169. package/src/ts/extensions.ts +51 -3
  170. package/src/ts/perspective-viewer.ts +2 -14
  171. package/src/ts/plugin.ts +1 -1
  172. package/src/ts/ts-rs/ViewerConfigUpdate.ts +1 -1
  173. package/src/rust/components/column_settings_sidebar/style_tab/column_style.rs +0 -177
  174. package/src/rust/components/containers/tests/mod.rs +0 -11
  175. package/src/rust/components/containers/tests/split_panel.rs +0 -91
  176. package/src/rust/js/testing.rs +0 -149
  177. package/src/rust/utils/tee.rs +0 -88
  178. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline0.js +0 -0
  179. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline1.js +0 -0
  180. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline2.js +0 -0
  181. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline3.js +0 -0
  182. /package/dist/wasm/snippets/{perspective-viewer-c69283f6f62a5f14 → perspective-viewer-0d326a25c1022412}/inline4.js +0 -0
  183. /package/src/rust/components/{style_controls.rs → style_controls/mod.rs} +0 -0
  184. /package/src/rust/{components/containers → config}/kvpair.rs +0 -0
@@ -17,7 +17,6 @@ use std::ops::{Deref, DerefMut};
17
17
  use perspective_client::config::*;
18
18
  use perspective_js::apierror;
19
19
 
20
- use crate::components::viewer::ColumnLocator;
21
20
  use crate::*;
22
21
 
23
22
  struct SessionViewExpressionMetadata {
@@ -46,6 +45,9 @@ impl DerefMut for SessionMetadata {
46
45
  }
47
46
  }
48
47
 
48
+ pub type MetadataRef<'a> = std::cell::Ref<'a, SessionMetadata>;
49
+ pub type MetadataMutRef<'a> = std::cell::RefMut<'a, SessionMetadata>;
50
+
49
51
  /// TODO the multiple `Option` types could probably be merged since they are
50
52
  /// populated within an async lock
51
53
  #[derive(Default)]
@@ -193,22 +195,6 @@ impl SessionMetadata {
193
195
  is_expr.unwrap_or_default()
194
196
  }
195
197
 
196
- /// This function will find a currently existing column. If you want to
197
- /// create a new expression column, use ColumnLocator::Expr(None)
198
- pub fn get_column_locator(&self, name: Option<String>) -> Option<ColumnLocator> {
199
- name.and_then(|name| {
200
- self.as_ref().and_then(|meta| {
201
- if self.is_column_expression(&name) {
202
- Some(ColumnLocator::Expression(name))
203
- } else {
204
- meta.column_names
205
- .iter()
206
- .find_map(|n| (n == &name).then_some(ColumnLocator::Table(name.clone())))
207
- }
208
- })
209
- })
210
- }
211
-
212
198
  /// Creates a new column name by appending a numeral corresponding to the
213
199
  /// number of columns with that name.
214
200
  pub fn make_new_column_name(&self, col: Option<&str>) -> String {
@@ -37,8 +37,7 @@ pub impl ViewConfig {
37
37
  sort,
38
38
  filter,
39
39
  aggregates,
40
- filter_op: _,
41
- group_by_depth: _,
40
+ ..
42
41
  } = self.clone();
43
42
 
44
43
  let expressions = expressions
@@ -21,47 +21,49 @@ use std::collections::HashSet;
21
21
  use std::future::Future;
22
22
  use std::ops::Deref;
23
23
  use std::rc::Rc;
24
- use std::sync::Arc;
25
24
 
26
25
  use perspective_client::config::*;
27
- use perspective_client::{ClientError, ReconnectCallback, View, ViewWindow};
26
+ use perspective_client::{Client, ClientError, ReconnectCallback, View, ViewWindow};
28
27
  use perspective_js::apierror;
29
28
  use perspective_js::utils::*;
30
29
  use wasm_bindgen::prelude::*;
31
30
  use yew::html::ImplicitClone;
32
31
  use yew::prelude::*;
33
32
 
33
+ pub use self::metadata::MetadataRef;
34
34
  use self::metadata::*;
35
35
  use self::replace_expression_update::*;
36
36
  pub use self::view_subscription::ViewStats;
37
37
  use self::view_subscription::*;
38
- use crate::dragdrop::*;
39
38
  use crate::js::plugin::*;
40
39
  use crate::utils::*;
41
40
 
42
- /// The `Session` struct is the principal interface to the Perspective engine,
43
- /// the `Table` and `View` objects for this viewer, and all associated state
44
- /// including the `ViewConfig`.
45
- #[derive(Clone, Default)]
46
- pub struct Session(Arc<SessionHandle>);
47
-
48
- impl ImplicitClone for Session {}
49
-
50
41
  /// Immutable state for `Session`.
51
42
  #[derive(Default)]
52
43
  pub struct SessionHandle {
53
44
  session_data: RefCell<SessionData>,
54
45
  pub table_updated: PubSub<()>,
55
46
  pub table_loaded: PubSub<()>,
47
+ pub table_errored: PubSub<ApiError>,
48
+ pub table_unloaded: PubSub<bool>,
56
49
  pub view_created: PubSub<()>,
57
50
  pub view_config_changed: PubSub<()>,
58
51
  pub stats_changed: PubSub<Option<ViewStats>>,
59
- pub table_errored: PubSub<ApiError>,
52
+ pub title_changed: PubSub<Option<String>>,
53
+ }
54
+
55
+ impl Deref for SessionHandle {
56
+ type Target = RefCell<SessionData>;
57
+
58
+ fn deref(&self) -> &Self::Target {
59
+ &self.session_data
60
+ }
60
61
  }
61
62
 
62
63
  /// Mutable state for `Session`.
63
64
  #[derive(Default)]
64
65
  pub struct SessionData {
66
+ client: Option<perspective_client::Client>,
65
67
  table: Option<perspective_client::Table>,
66
68
  metadata: SessionMetadata,
67
69
  old_config: Option<ViewConfig>,
@@ -71,11 +73,36 @@ pub struct SessionData {
71
73
  is_clean: bool,
72
74
  is_paused: bool,
73
75
  error: Option<TableErrorState>,
76
+ title: Option<String>,
74
77
  }
75
78
 
76
79
  #[derive(Clone)]
77
80
  pub struct TableErrorState(ApiError, Option<ReconnectCallback>);
78
81
 
82
+ /// Options for [`Session::reset`]
83
+ #[derive(Default)]
84
+ pub struct ResetOptions {
85
+ /// Reset user defined expressions
86
+ pub expressions: bool,
87
+
88
+ /// Reset the [`Table`]
89
+ pub table: bool,
90
+
91
+ /// Reset the [`ViewConfig`]
92
+ pub config: bool,
93
+
94
+ /// Manually reset the [`ViewStats`]
95
+ pub stats: bool,
96
+ }
97
+
98
+ /// The `Session` struct is the principal interface to the Perspective engine,
99
+ /// the `Table` and `View` objects for this viewer, and all associated state
100
+ /// including the `ViewConfig`.
101
+ #[derive(Clone)]
102
+ pub struct Session(Rc<SessionHandle>);
103
+
104
+ impl ImplicitClone for Session {}
105
+
79
106
  impl Deref for Session {
80
107
  type Target = SessionHandle;
81
108
 
@@ -86,22 +113,18 @@ impl Deref for Session {
86
113
 
87
114
  impl PartialEq for Session {
88
115
  fn eq(&self, other: &Self) -> bool {
89
- Arc::ptr_eq(&self.0, &other.0)
116
+ Rc::ptr_eq(&self.0, &other.0)
90
117
  }
91
118
  }
92
119
 
93
- impl Deref for SessionHandle {
94
- type Target = RefCell<SessionData>;
95
-
96
- fn deref(&self) -> &Self::Target {
97
- &self.session_data
120
+ impl Session {
121
+ /// Uses [`Self::new`] instead of [`Default`] to prevent accidental
122
+ /// instantiation in props/etc.
123
+ #[allow(clippy::new_without_default)]
124
+ pub fn new() -> Self {
125
+ Self(Rc::default())
98
126
  }
99
- }
100
127
 
101
- pub type MetadataRef<'a> = std::cell::Ref<'a, SessionMetadata>;
102
- pub type MetadataMutRef<'a> = std::cell::RefMut<'a, SessionMetadata>;
103
-
104
- impl Session {
105
128
  pub fn metadata(&self) -> MetadataRef<'_> {
106
129
  std::cell::Ref::map(self.borrow(), |x| &x.metadata)
107
130
  }
@@ -110,40 +133,48 @@ impl Session {
110
133
  std::cell::RefMut::map(self.borrow_mut(), |x| &mut x.metadata)
111
134
  }
112
135
 
113
- pub fn invalidate(&self) {
114
- self.borrow_mut().error = None;
115
- self.borrow_mut().is_clean = false;
116
- self.borrow_mut().view_sub = None;
136
+ pub fn get_title(&self) -> Option<String> {
137
+ self.borrow().title.clone()
117
138
  }
118
139
 
119
- /// Reset this `Session`'s `View` state, but preserve the `Table`.
120
- ///
121
- /// # Arguments
122
- /// - `reset_expressions` Whether to reset the `expressions` property.
123
- pub fn reset(&self, reset_expressions: bool) -> impl Future<Output = ApiResult<()>> + use<> {
124
- self.borrow_mut().is_clean = false;
125
- let view = self.0.borrow_mut().view_sub.take();
126
- self.borrow_mut().view_sub = None;
127
- self.borrow_mut().config.reset(reset_expressions);
128
- let err = self.get_error();
129
- async move {
130
- let res = view.delete().await;
131
- if let Some(err) = err { Err(err) } else { res }
132
- }
140
+ pub fn set_title(&self, title: Option<String>) {
141
+ let new_title = title.filter(|x| !x.is_empty());
142
+ self.borrow_mut().title.clone_from(&new_title);
143
+ self.title_changed.emit(new_title);
133
144
  }
134
145
 
135
146
  /// Reset this (presumably shared) `Session` to its initial state, returning
136
147
  /// a bool indicating whether this `Session` had a table which was
137
148
  /// deleted. TODO Table should be an immutable constructor parameter to
138
149
  /// `Session`.
139
- pub async fn delete(&self) -> ApiResult<()> {
150
+ pub fn reset(&self, options: ResetOptions) -> impl Future<Output = ApiResult<()>> + use<> {
140
151
  self.borrow_mut().is_clean = false;
141
- self.borrow_mut().config.reset(true);
142
- self.borrow_mut().metadata = SessionMetadata::default();
143
- self.borrow_mut().table = None;
144
- let view = self.borrow_mut().view_sub.take();
145
- view.delete().await?;
146
- Ok(())
152
+ let view = self.0.borrow_mut().view_sub.take();
153
+ let err = self.get_error();
154
+ self.borrow_mut().error = None;
155
+ if options.stats {
156
+ self.update_stats(ViewStats::default());
157
+ }
158
+
159
+ if options.config {
160
+ self.borrow_mut().config.reset(options.expressions);
161
+ }
162
+
163
+ if options.table {
164
+ self.borrow_mut().table = None;
165
+ self.borrow_mut().metadata = SessionMetadata::default();
166
+ }
167
+
168
+ let table_unloaded = self.table_unloaded.callback();
169
+ self.borrow_mut().is_clean = false;
170
+ async move {
171
+ let res = view.delete().await;
172
+ if options.table {
173
+ table_unloaded.emit(true)
174
+ }
175
+
176
+ if let Some(err) = err { Err(err) } else { res }
177
+ }
147
178
  }
148
179
 
149
180
  pub fn has_table(&self) -> bool {
@@ -154,10 +185,40 @@ impl Session {
154
185
  self.borrow().table.clone()
155
186
  }
156
187
 
188
+ pub fn set_client(&self, client: Client) -> bool {
189
+ if Some(&client) != self.borrow().client.as_ref() {
190
+ self.borrow_mut().client = Some(client);
191
+ self.borrow_mut().table = None;
192
+ true
193
+ } else {
194
+ false
195
+ }
196
+ }
197
+
198
+ pub fn get_client(&self) -> Option<Client> {
199
+ self.borrow().client.clone()
200
+ }
201
+
157
202
  /// Reset this `Session`'s state with a new `Table`. Implicitly clears the
158
203
  /// `ViewSubscription`, which will need to be re-initialized later via
159
204
  /// `create_view()`.
160
- pub async fn set_table(&self, table: perspective_client::Table) -> ApiResult<JsValue> {
205
+ ///
206
+ /// # Arguments
207
+ ///
208
+ /// - `table_name` The name of the `Table` to load, which must exist on the
209
+ /// loaded `Client`.
210
+ ///
211
+ /// # Returns
212
+ ///
213
+ /// `table_name` is unique per `Client`, so if this value has not changed,
214
+ /// `Session::set_table` does nothing and returns `Ok(false)`.
215
+ pub async fn set_table(&self, table_name: String) -> ApiResult<bool> {
216
+ if Some(table_name.as_str()) == self.0.borrow().table.as_ref().map(|x| x.get_name()) {
217
+ return Ok(false);
218
+ }
219
+
220
+ let client = self.0.borrow().client.clone().into_apierror()?;
221
+ let table = client.open_table(table_name.clone()).await?;
161
222
  match SessionMetadata::from_table(&table).await {
162
223
  Ok(metadata) => {
163
224
  let client = table.get_client();
@@ -186,19 +247,31 @@ impl Session {
186
247
  let sub = self.borrow_mut().view_sub.take();
187
248
  self.borrow_mut().metadata = metadata;
188
249
  self.borrow_mut().table = Some(table);
250
+ self.borrow_mut().is_clean = false;
189
251
  sub.delete().await?;
190
252
  self.table_loaded.emit(());
191
- Ok(JsValue::UNDEFINED)
253
+ Ok(true)
192
254
  },
193
- Err(err) => self.set_error(false, err).await.map(|_| JsValue::UNDEFINED),
255
+ Err(err) => self.set_error(false, err).await.map(|_| false),
256
+ }
257
+ }
258
+
259
+ pub fn update_column_defaults(&self, requirements: &ViewConfigRequirements) {
260
+ if self.borrow().config.columns.is_empty() {
261
+ let mut update = ViewConfigUpdate::default();
262
+ self.set_update_column_defaults(&mut update, requirements);
263
+ self.borrow_mut().config.apply_update(update);
194
264
  }
195
265
  }
196
266
 
197
267
  pub async fn set_error(&self, reset_table: bool, err: ApiError) -> ApiResult<()> {
198
268
  let session = self.clone();
199
269
  let poll_loop = LocalPollLoop::new(move |()| {
200
- session.invalidate();
201
- ApiFuture::spawn(session.reset(true));
270
+ ApiFuture::spawn(session.reset(ResetOptions {
271
+ config: true,
272
+ expressions: true,
273
+ ..ResetOptions::default()
274
+ }));
202
275
  Ok(JsValue::UNDEFINED)
203
276
  });
204
277
 
@@ -240,7 +313,7 @@ impl Session {
240
313
 
241
314
  pub async fn await_table(&self) -> ApiResult<()> {
242
315
  if self.js_get_table().is_none() {
243
- self.table_loaded.listen_once().await?;
316
+ self.table_loaded.read_next().await?;
244
317
  let _ = self.js_get_table().ok_or("No table set")?;
245
318
  }
246
319
 
@@ -279,6 +352,7 @@ impl Session {
279
352
  self.borrow_mut().error = None;
280
353
  self.borrow_mut().is_clean = false;
281
354
  self.borrow_mut().view_sub = None;
355
+ self.table_loaded.emit(());
282
356
  }
283
357
 
284
358
  Ok(())
@@ -457,7 +531,7 @@ impl Session {
457
531
  self.borrow().stats.clone()
458
532
  }
459
533
 
460
- pub fn get_view_config(&self) -> Ref<ViewConfig> {
534
+ pub fn get_view_config(&'_ self) -> Ref<'_, ViewConfig> {
461
535
  Ref::map(self.borrow(), |x| &x.config)
462
536
  }
463
537
 
@@ -511,7 +585,7 @@ impl Session {
511
585
  use self::column_defaults_update::*;
512
586
  config_update.set_update_column_defaults(
513
587
  &self.metadata(),
514
- &self.borrow().config.columns,
588
+ &self.all_columns().into_iter().map(Some).collect::<Vec<_>>(),
515
589
  requirements,
516
590
  )
517
591
  }
@@ -534,15 +608,6 @@ impl Session {
534
608
  Ok(())
535
609
  }
536
610
 
537
- pub fn reset_stats(&self) {
538
- self.update_stats(ViewStats::default());
539
- }
540
-
541
- #[cfg(test)]
542
- pub fn set_stats(&self, stats: ViewStats) {
543
- self.update_stats(stats)
544
- }
545
-
546
611
  /// In order to create a new view in this session, the session must first be
547
612
  /// validated to create a `ValidSession<'_>` guard.
548
613
  pub async fn validate(&self) -> Result<ValidSession<'_>, ApiError> {
@@ -555,8 +620,11 @@ impl Session {
555
620
  if let Err(err) = self.validate_view_config().await {
556
621
  let session = self.clone();
557
622
  let poll_loop = LocalPollLoop::new(move |()| {
558
- session.invalidate();
559
- ApiFuture::spawn(session.reset(true));
623
+ ApiFuture::spawn(session.reset(ResetOptions {
624
+ config: true,
625
+ expressions: true,
626
+ ..ResetOptions::default()
627
+ }));
560
628
  Ok(JsValue::UNDEFINED)
561
629
  });
562
630
 
@@ -574,7 +642,12 @@ impl Session {
574
642
  if let Some(config) = old {
575
643
  self.borrow_mut().config = config;
576
644
  } else {
577
- self.reset(true).await?;
645
+ self.reset(ResetOptions {
646
+ config: true,
647
+ expressions: true,
648
+ ..ResetOptions::default()
649
+ })
650
+ .await?;
578
651
  }
579
652
 
580
653
  return Err(err);
@@ -604,16 +677,18 @@ impl Session {
604
677
  self.stats_changed.emit(Some(stats));
605
678
  }
606
679
 
607
- async fn validate_view_config(&self) -> ApiResult<()> {
608
- let mut config = self.borrow().config.clone();
609
- let table_columns = self
610
- .metadata()
680
+ fn all_columns(&self) -> Vec<String> {
681
+ self.metadata()
611
682
  .get_table_columns()
612
683
  .into_iter()
613
684
  .flatten()
614
685
  .cloned()
615
- .collect::<Vec<String>>();
686
+ .collect()
687
+ }
616
688
 
689
+ async fn validate_view_config(&self) -> ApiResult<()> {
690
+ let mut config = self.borrow().config.clone();
691
+ let table_columns = self.all_columns();
617
692
  let all_columns: HashSet<String> = table_columns.iter().cloned().collect();
618
693
  let mut view_columns: HashSet<&str> = HashSet::new();
619
694
  let table = self
@@ -711,17 +786,17 @@ impl Session {
711
786
  /// A newtype wrapper which only provides `create_view()`
712
787
  pub struct ValidSession<'a>(&'a Session, bool);
713
788
 
714
- impl<'a> ValidSession<'a> {
789
+ impl ValidSession<'_> {
715
790
  /// Set a new `View` (derived from this `Session`'s `Table`), and create the
716
791
  /// `update()` subscription, consuming this `ValidSession<'_>` and returning
717
792
  /// the original `&Session`.
718
- pub async fn create_view(&self) -> Result<&'a Session, ApiError> {
793
+ pub async fn create_view(&self) -> Result<Option<View>, ApiError> {
719
794
  if !self.0.reset_clean() && !self.0.borrow().is_paused {
720
795
  if !self.1 {
721
796
  let config = self.0.borrow().config.clone();
722
797
  if let Some(sub) = &mut self.0.borrow_mut().view_sub.as_mut() {
723
798
  sub.update_view_config(Rc::new(config));
724
- return Ok(self.0);
799
+ return Ok(Some(sub.get_view().clone()));
725
800
  }
726
801
  }
727
802
 
@@ -747,11 +822,16 @@ impl<'a> ValidSession<'a> {
747
822
  .0
748
823
  .metadata()
749
824
  .get_column_aggregates(col.as_str())
750
- .into_apierror()?
751
- .next()
752
- .into_apierror()?;
753
-
754
- let _ = view_config.aggregates.insert(col.to_string(), agg);
825
+ .and_then(|mut aggs| aggs.next())
826
+ .into_apierror();
827
+
828
+ match agg {
829
+ Err(_) => tracing::warn!(
830
+ "No default aggregate for column '{}' found, skipping",
831
+ col
832
+ ),
833
+ Ok(agg) => _ = view_config.aggregates.insert(col.to_string(), agg),
834
+ };
755
835
  }
756
836
  }
757
837
 
@@ -781,7 +861,7 @@ impl<'a> ValidSession<'a> {
781
861
  self.0.borrow_mut().view_sub = Some(sub);
782
862
  }
783
863
 
784
- Ok(self.0)
864
+ Ok(self.0.get_view())
785
865
  }
786
866
  }
787
867
 
@@ -11,10 +11,13 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  use perspective_js::utils::ApiResult;
14
+ use wasm_bindgen::JsValue;
14
15
 
16
+ /// An extension method for converting various [`js_sys`] and [`web_sys`] types
17
+ /// to [`web_sys::Blob`].
15
18
  pub trait AsBlob {
16
- /// Standardized conversions from common `wasm_bindgen` types to
17
- /// `js_sys::Blob`, which is commonly necessary to provide data to a
19
+ /// Standardized conversions from common [`wasm_bindgen`] types to
20
+ /// [`web_sys::Blob`], which is commonly necessary to provide data to a
18
21
  /// download or clipboard action.
19
22
  fn as_blob(&self) -> ApiResult<web_sys::Blob>;
20
23
  }
@@ -26,6 +29,17 @@ impl AsBlob for js_sys::ArrayBuffer {
26
29
  }
27
30
  }
28
31
 
32
+ impl AsBlob for JsValue {
33
+ fn as_blob(&self) -> ApiResult<web_sys::Blob> {
34
+ let array = std::iter::once(self).collect::<js_sys::Array>();
35
+ let options = web_sys::BlobPropertyBag::new();
36
+ options.set_type("text/plain");
37
+ Ok(web_sys::Blob::new_with_str_sequence_and_options(
38
+ &array, &options,
39
+ )?)
40
+ }
41
+ }
42
+
29
43
  impl AsBlob for js_sys::JsString {
30
44
  fn as_blob(&self) -> ApiResult<web_sys::Blob> {
31
45
  let array = std::iter::once(self).collect::<js_sys::Array>();
@@ -13,6 +13,7 @@
13
13
  use perspective_js::utils::{global, *};
14
14
  use wasm_bindgen::JsCast;
15
15
 
16
+ /// Given a blob, invoke the Browser's download popup.
16
17
  pub fn download(name: &str, value: &web_sys::Blob) -> ApiResult<()> {
17
18
  let element: web_sys::HtmlElement = global::document().create_element("a")?.unchecked_into();
18
19
  let blob_url = web_sys::Url::create_object_url_with_blob(value)?;
@@ -9,9 +9,18 @@
9
9
  // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
- mod attributes_tab;
13
- mod save_settings;
14
- mod sidebar;
15
- mod style_tab;
16
12
 
17
- pub use sidebar::*;
13
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
14
+ pub enum DragTarget {
15
+ Active,
16
+ GroupBy,
17
+ SplitBy,
18
+ Sort,
19
+ Filter,
20
+ }
21
+
22
+ #[derive(Clone, Copy, Debug, Eq, PartialEq)]
23
+ pub enum DragEffect {
24
+ Copy,
25
+ Move(DragTarget),
26
+ }
@@ -10,15 +10,19 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ //! Utilities specific to Browser-only JavaScript extensions.
14
+
13
15
  mod blob;
14
16
  mod download;
17
+ mod dragdrop;
15
18
  mod request_animation_frame;
16
19
  mod selection;
17
20
 
18
21
  #[cfg(test)]
19
22
  mod tests;
20
23
 
21
- pub use blob::*;
22
- pub use download::*;
23
- pub use request_animation_frame::*;
24
- pub use selection::*;
24
+ pub use self::blob::*;
25
+ pub use self::download::*;
26
+ pub use self::dragdrop::*;
27
+ pub use self::request_animation_frame::*;
28
+ pub use self::selection::*;
@@ -19,8 +19,13 @@ use crate::*;
19
19
  /// but `Deref` makes them fall through, so it is important that this method
20
20
  /// be called on the correct struct type!
21
21
  pub trait CaretPosition {
22
+ /// Select the entired clip
22
23
  fn select_all(&self) -> ApiResult<()>;
24
+
25
+ /// Set the caret to a specific offset.
23
26
  fn set_caret_position(&self, offset: usize) -> ApiResult<()>;
27
+
28
+ /// Get the caret's offset (if it has focus)
24
29
  fn get_caret_position(&self) -> Option<u32>;
25
30
  }
26
31
 
@@ -10,20 +10,9 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use wasm_bindgen::prelude::*;
14
-
15
- pub trait CustomElementMetadata {
16
- const CUSTOM_ELEMENT_NAME: &'static str;
17
- const STATICS: &'static [&'static str] = [].as_slice();
18
- const TYPE_NAME: &'static str = std::any::type_name::<Self>();
13
+ //! Utilities for building JavaScript Custom Elements with Rust.
19
14
 
20
- fn struct_name() -> &'static str {
21
- match &Self::TYPE_NAME.rfind(':') {
22
- Some(pos) => &Self::TYPE_NAME[pos + 1..],
23
- None => Self::TYPE_NAME,
24
- }
25
- }
26
- }
15
+ use wasm_bindgen::prelude::*;
27
16
 
28
17
  #[wasm_bindgen(inline_js = r#"
29
18
  export function bootstrap(psp, name, clsname, statics) {
@@ -75,6 +64,32 @@ extern "C" {
75
64
  fn js_bootstrap(psp: &JsValue, name: &str, cls: &str, statics: js_sys::Array) -> JsValue;
76
65
  }
77
66
 
67
+ /// A trait which allows the [`define_web_component`] method to create a
68
+ /// Custom Element (which must by definition inherit from `HTMLElement`) from
69
+ /// a `[wasm_bindgen]` annotated Rust struct (for which this trait is
70
+ /// implemented).
71
+ pub trait CustomElementMetadata {
72
+ /// The name of the element to register.
73
+ const CUSTOM_ELEMENT_NAME: &'static str;
74
+
75
+ /// [optional] The names of the methods which should be static on the
76
+ /// element.
77
+ const STATICS: &'static [&'static str] = [].as_slice();
78
+
79
+ /// [optional] The name of the Rust struct.
80
+ const TYPE_NAME: &'static str = std::any::type_name::<Self>();
81
+
82
+ /// [optional] A custom implementation of struct name to class name.
83
+ #[must_use]
84
+ fn struct_name() -> &'static str {
85
+ match &Self::TYPE_NAME.rfind(':') {
86
+ Some(pos) => &Self::TYPE_NAME[pos + 1..],
87
+ None => Self::TYPE_NAME,
88
+ }
89
+ }
90
+ }
91
+
92
+ /// Register the Custom Element globally.
78
93
  pub fn define_web_component<T: CustomElementMetadata>(module: &JsValue) {
79
94
  js_bootstrap(
80
95
  module,
@@ -10,6 +10,9 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ //! Utility functions for dealing with datetime types.
14
+ //! TODO These methods should use JavaSript `Intl`.
15
+
13
16
  use chrono::{DateTime, FixedOffset, NaiveDateTime, TimeZone, Utc};
14
17
  use perspective_js::utils::*;
15
18
  use wasm_bindgen::prelude::*;
@@ -27,6 +30,7 @@ fn get_local_tz() -> FixedOffset {
27
30
  FixedOffset::west_opt(js_sys::Date::new(&0.into()).get_timezone_offset() as i32 * 60).unwrap()
28
31
  }
29
32
 
33
+ /// Convert POSIX timestamp to a formatted string.
30
34
  pub fn posix_to_utc_str(x: f64) -> ApiResult<String> {
31
35
  let tz = get_local_tz();
32
36
  if x > 0_f64 {
@@ -42,6 +46,7 @@ pub fn posix_to_utc_str(x: f64) -> ApiResult<String> {
42
46
  }
43
47
  }
44
48
 
49
+ /// Convert a `String` datetime representation to a POSIX timestamp.
45
50
  pub fn str_to_utc_posix(val: &str) -> Result<f64, ApiError> {
46
51
  let tz = get_local_tz();
47
52
  let posix = NaiveDateTime::parse_from_str(val, input_value_format(val)?)?;