@perspective-dev/viewer 4.1.1 → 4.3.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 (62) 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/solarized.css +1 -1
  8. package/dist/css/themes.css +1 -1
  9. package/dist/css/vaporwave.css +1 -1
  10. package/dist/esm/extensions.d.ts +32 -1
  11. package/dist/esm/perspective-viewer.d.ts +1 -0
  12. package/dist/esm/perspective-viewer.inline.js +2 -2
  13. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  14. package/dist/esm/perspective-viewer.js +2 -2
  15. package/dist/esm/perspective-viewer.js.map +4 -4
  16. package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
  17. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +2 -0
  18. package/dist/wasm/perspective-viewer.d.ts +1403 -1319
  19. package/dist/wasm/perspective-viewer.js +3531 -3268
  20. package/dist/wasm/perspective-viewer.wasm +0 -0
  21. package/dist/wasm/perspective-viewer.wasm.d.ts +111 -99
  22. package/package.json +4 -3
  23. package/src/less/column-selector.less +2 -2
  24. package/src/less/config-selector.less +66 -4
  25. package/src/rust/components/column_dropdown.rs +8 -8
  26. package/src/rust/components/column_selector/config_selector.rs +102 -29
  27. package/src/rust/components/column_selector/filter_column.rs +12 -12
  28. package/src/rust/components/column_selector/pivot_column.rs +12 -7
  29. package/src/rust/components/column_selector.rs +27 -17
  30. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +5 -5
  31. package/src/rust/components/column_settings_sidebar.rs +2 -4
  32. package/src/rust/components/containers/dragdrop_list.rs +32 -10
  33. package/src/rust/components/containers/scroll_panel.rs +8 -1
  34. package/src/rust/components/containers/select.rs +9 -9
  35. package/src/rust/components/containers/split_panel.rs +2 -2
  36. package/src/rust/components/filter_dropdown.rs +8 -8
  37. package/src/rust/components/function_dropdown.rs +8 -8
  38. package/src/rust/components/modal.rs +4 -4
  39. package/src/rust/components/plugin_selector.rs +15 -5
  40. package/src/rust/components/status_indicator.rs +3 -0
  41. package/src/rust/custom_elements/filter_dropdown.rs +4 -4
  42. package/src/rust/custom_elements/viewer.rs +2 -7
  43. package/src/rust/js/plugin.rs +19 -0
  44. package/src/rust/model/copy_export.rs +2 -1
  45. package/src/rust/model/intersection_observer.rs +3 -1
  46. package/src/rust/presentation/sheets.rs +4 -1
  47. package/src/rust/presentation.rs +4 -4
  48. package/src/rust/renderer/registry.rs +8 -1
  49. package/src/rust/session/column_defaults_update.rs +18 -0
  50. package/src/rust/session/replace_expression_update.rs +1 -0
  51. package/src/themes/botanical.less +142 -0
  52. package/src/themes/themes.less +2 -1
  53. package/src/ts/extensions.ts +73 -2
  54. package/src/ts/perspective-viewer.ts +1 -0
  55. package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
  56. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -1
  57. package/tsconfig.json +1 -0
  58. /package/dist/wasm/snippets/{perspective-viewer-11a3c51b6310ee99 → perspective-viewer-d729f682ba5c19df}/inline0.js +0 -0
  59. /package/dist/wasm/snippets/{perspective-viewer-11a3c51b6310ee99/inline2.js → perspective-viewer-d729f682ba5c19df/inline1.js} +0 -0
  60. /package/dist/wasm/snippets/{perspective-viewer-11a3c51b6310ee99/inline1.js → perspective-viewer-d729f682ba5c19df/inline2.js} +0 -0
  61. /package/dist/wasm/snippets/{perspective-viewer-11a3c51b6310ee99 → perspective-viewer-d729f682ba5c19df}/inline3.js +0 -0
  62. /package/dist/wasm/snippets/{perspective-viewer-11a3c51b6310ee99 → perspective-viewer-d729f682ba5c19df}/inline4.js +0 -0
@@ -514,18 +514,18 @@ impl FilterColumnProps {
514
514
  }
515
515
  };
516
516
 
517
- if let Some(input) = filter_input {
518
- if &input != filter_column.term() {
519
- *filter_column.term_mut() = input;
520
- let update = ViewConfigUpdate {
521
- filter: Some(filters),
522
- ..ViewConfigUpdate::default()
523
- };
524
-
525
- self.update_and_render(update)
526
- .map(ApiFuture::spawn)
527
- .unwrap_or_log();
528
- }
517
+ if let Some(input) = filter_input
518
+ && &input != filter_column.term()
519
+ {
520
+ *filter_column.term_mut() = input;
521
+ let update = ViewConfigUpdate {
522
+ filter: Some(filters),
523
+ ..ViewConfigUpdate::default()
524
+ };
525
+
526
+ self.update_and_render(update)
527
+ .map(ApiFuture::spawn)
528
+ .unwrap_or_log();
529
529
  }
530
530
  }
531
531
  }
@@ -26,11 +26,15 @@ pub struct PivotColumnProps {
26
26
  /// Column name.
27
27
  pub column: String,
28
28
 
29
+ #[prop_or_default]
30
+ pub column_type: Option<ColumnType>,
31
+
29
32
  /// The drag starte of this column, if applicable.
30
33
  pub action: DragTarget,
31
34
 
32
35
  // State
33
- pub session: Session,
36
+ #[prop_or_default]
37
+ pub opt_session: Option<Session>,
34
38
  pub dragdrop: DragDrop,
35
39
  }
36
40
 
@@ -74,12 +78,13 @@ impl Component for PivotColumn {
74
78
  move |_event| dragdrop.notify_drag_end()
75
79
  });
76
80
 
77
- let col_type = ctx
78
- .props()
79
- .session
80
- .metadata()
81
- .get_column_table_type(&ctx.props().column)
82
- .unwrap_or(ColumnType::Integer);
81
+ let col_type = ctx.props().column_type.unwrap_or_else(|| {
82
+ ctx.props()
83
+ .opt_session
84
+ .as_ref()
85
+ .and_then(|x| x.metadata().get_column_table_type(&ctx.props().column))
86
+ .unwrap_or(ColumnType::Integer)
87
+ });
83
88
 
84
89
  html! {
85
90
  <div
@@ -28,6 +28,7 @@ use std::rc::Rc;
28
28
  pub use empty_column::*;
29
29
  pub use invalid_column::*;
30
30
  use perspective_js::utils::ApiFuture;
31
+ pub use pivot_column::*;
31
32
  use web_sys::*;
32
33
  use yew::prelude::*;
33
34
 
@@ -77,6 +78,7 @@ pub enum ColumnSelectorMsg {
77
78
  TableLoaded,
78
79
  ViewCreated,
79
80
  HoverActiveIndex(Option<usize>),
81
+ SetWidth(f64),
80
82
  Drag(DragEffect),
81
83
  DragEnd,
82
84
  Drop((String, DragTarget, DragEffect, usize)),
@@ -91,6 +93,7 @@ pub struct ColumnSelector {
91
93
  named_row_count: usize,
92
94
  drag_container: DragDropContainer,
93
95
  column_dropdown: ColumnDropDownElement,
96
+ viewport_width: f64,
94
97
  on_reset: Rc<PubSub<()>>,
95
98
  }
96
99
 
@@ -147,6 +150,7 @@ impl Component for ColumnSelector {
147
150
  Self {
148
151
  _subscriptions: [table_sub, view_sub, drop_sub, drag_sub, dragend_sub],
149
152
  named_row_count,
153
+ viewport_width: 0f64,
150
154
  drag_container,
151
155
  column_dropdown,
152
156
  on_reset: Default::default(),
@@ -157,6 +161,10 @@ impl Component for ColumnSelector {
157
161
  match msg {
158
162
  Drag(DragEffect::Move(DragTarget::Active)) => false,
159
163
  Drag(_) | DragEnd | TableLoaded => true,
164
+ SetWidth(w) => {
165
+ self.viewport_width = w;
166
+ false
167
+ },
160
168
  ViewCreated => {
161
169
  let named = maybe! {
162
170
  let plugin =
@@ -355,11 +363,7 @@ impl Component for ColumnSelector {
355
363
  })
356
364
  .collect();
357
365
 
358
- let size = if !inactive_children.is_empty() {
359
- 56.0
360
- } else {
361
- 28.0
362
- };
366
+ let size = 28.0;
363
367
 
364
368
  let add_column = if ctx
365
369
  .props()
@@ -389,8 +393,8 @@ impl Component for ColumnSelector {
389
393
  inactive_children.insert(0, add_column);
390
394
  }
391
395
 
392
- let selected_columns = html! {
393
- <div id="selected-columns">
396
+ let mut selected_columns = vec![html! {
397
+ <div id="selected-columns" key="__active_columns__">
394
398
  <ScrollPanel
395
399
  id="active-columns"
396
400
  class={active_classes}
@@ -398,13 +402,27 @@ impl Component for ColumnSelector {
398
402
  dragenter={&self.drag_container.dragenter}
399
403
  dragleave={&self.drag_container.dragleave}
400
404
  viewport_ref={&self.drag_container.noderef}
405
+ initial_width={self.viewport_width}
406
+ on_auto_width={ctx.link().callback(ColumnSelectorMsg::SetWidth)}
401
407
  drop={ondrop}
402
408
  on_resize={&ctx.props().on_resize}
403
409
  on_dimensions_reset={&self.on_reset}
404
410
  children={std::iter::once(config_selector).chain(active_columns).collect::<Vec<_>>()}
405
411
  />
406
412
  </div>
407
- };
413
+ }];
414
+
415
+ if !inactive_children.is_empty() {
416
+ selected_columns.push(html! {
417
+ <ScrollPanel
418
+ id="sub-columns"
419
+ key="__sub_columns__"
420
+ on_resize={&ctx.props().on_resize}
421
+ on_dimensions_reset={&self.on_reset}
422
+ children={inactive_children}
423
+ />
424
+ })
425
+ }
408
426
 
409
427
  html! {
410
428
  <>
@@ -415,15 +433,7 @@ impl Component for ColumnSelector {
415
433
  skip_empty=true
416
434
  orientation={Orientation::Vertical}
417
435
  >
418
- { selected_columns }
419
- if !inactive_children.is_empty() {
420
- <ScrollPanel
421
- id="sub-columns"
422
- on_resize={&ctx.props().on_resize}
423
- on_dimensions_reset={&self.on_reset}
424
- children={inactive_children}
425
- />
426
- }
436
+ { for selected_columns }
427
437
  </SplitPanel>
428
438
  </>
429
439
  }
@@ -55,15 +55,15 @@ pub fn row_selector(props: &RowSelectorProps) -> Html {
55
55
 
56
56
  let inner = if props.selected_row.is_none() || props.focused {
57
57
  let mut pairs = props.pairs.clone();
58
- if let Some(ref rowval) = props.selected_row {
59
- if let Some((i, _)) = pairs.iter().find_position(|pair| {
58
+ if let Some(ref rowval) = props.selected_row
59
+ && let Some((i, _)) = pairs.iter().find_position(|pair| {
60
60
  pair.key
61
61
  .as_ref()
62
62
  .map(|keyval| keyval == rowval)
63
63
  .unwrap_or_default()
64
- }) {
65
- pairs.remove(i);
66
- }
64
+ })
65
+ {
66
+ pairs.remove(i);
67
67
  }
68
68
 
69
69
  let exclude: HashSet<_> = pairs
@@ -156,7 +156,7 @@ impl Component for ColumnSettingsPanel {
156
156
  }
157
157
 
158
158
  fn update(&mut self, ctx: &yew::prelude::Context<Self>, msg: Self::Message) -> bool {
159
- let result = match msg {
159
+ match msg {
160
160
  ColumnSettingsPanelMsg::SetExprValue(val) => {
161
161
  if self.expr_value != val {
162
162
  self.expr_value = val;
@@ -241,9 +241,7 @@ impl Component for ColumnSettingsPanel {
241
241
  false
242
242
  }
243
243
  },
244
- };
245
-
246
- result
244
+ }
247
245
  }
248
246
 
249
247
  fn view(&self, ctx: &yew::prelude::Context<Self>) -> Html {
@@ -14,11 +14,13 @@ use std::collections::HashSet;
14
14
  use std::marker::PhantomData;
15
15
 
16
16
  use derivative::Derivative;
17
+ use perspective_client::proto::ColumnType;
17
18
  use web_sys::*;
18
19
  use yew::html::Scope;
19
20
  use yew::prelude::*;
20
21
 
21
22
  use crate::components::column_selector::{EmptyColumn, InPlaceColumn, InvalidColumn};
23
+ use crate::components::type_icon::TypeIcon;
22
24
  use crate::custom_elements::ColumnDropDownElement;
23
25
  use crate::dragdrop::*;
24
26
  use crate::utils::DragTarget;
@@ -32,12 +34,16 @@ where
32
34
  <U as Component>::Properties: DragDropListItemProps,
33
35
  {
34
36
  pub parent: Scope<T>,
37
+
35
38
  pub dragdrop: DragDrop,
36
39
  pub name: &'static str,
37
40
  pub column_dropdown: ColumnDropDownElement,
38
41
  pub exclude: HashSet<String>,
39
42
  pub children: ChildrenWithProps<U>,
40
43
 
44
+ #[prop_or_default]
45
+ pub disabled: bool,
46
+
41
47
  #[prop_or_default]
42
48
  pub is_dragover: Option<(
43
49
  usize,
@@ -59,6 +65,7 @@ where
59
65
  && self.children == other.children
60
66
  && self.allow_duplicates == other.allow_duplicates
61
67
  && self.is_dragover == other.is_dragover
68
+ && self.disabled == other.disabled
62
69
  }
63
70
  }
64
71
 
@@ -213,10 +220,11 @@ where
213
220
  .position(|x| x.1.1.as_ref().unwrap().props.get_item() == *column);
214
221
 
215
222
  valid_duplicate_drag = is_duplicate.is_some() && !ctx.props().allow_duplicates;
216
- if let Some(duplicate) = is_duplicate {
217
- if !is_append && (!ctx.props().allow_duplicates || is_self_move) {
218
- columns.remove(duplicate);
219
- }
223
+ if let Some(duplicate) = is_duplicate
224
+ && !is_append
225
+ && (!ctx.props().allow_duplicates || is_self_move)
226
+ {
227
+ columns.remove(duplicate);
220
228
  }
221
229
 
222
230
  // If inserting into the middle of the list, use
@@ -282,20 +290,34 @@ where
282
290
  let column_dropdown = ctx.props().column_dropdown.clone();
283
291
  let exclude = ctx.props().exclude.clone();
284
292
  let on_select = ctx.props().parent.callback(V::create);
293
+ let class = classes!("rrow");
294
+ let is_enabled = true;
295
+
285
296
  html! {
286
- <div ref={&self.elem} class="rrow">
297
+ <div ref={&self.elem} {class}>
287
298
  <div
288
299
  id={ctx.props().name}
289
- ondragover={dragover}
290
- ondragenter={drag_container.dragenter}
291
- ondragleave={drag_container.dragleave}
300
+ ondragover={is_enabled.then_some(dragover)}
301
+ ondragenter={is_enabled.then_some(drag_container.dragenter)}
302
+ ondragleave={is_enabled.then_some(drag_container.dragleave)}
292
303
  ref={drag_container.noderef}
293
- ondrop={drop}
304
+ ondrop={is_enabled.then_some(drop)}
294
305
  >
295
306
  <div class="psp-text-field">
296
307
  <ul class="psp-text-field__input" for={ctx.props().name}>
297
308
  { columns_html }
298
- if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
309
+ if ctx.props().disabled && ctx.props().is_dragover.is_none() {
310
+ <div class="pivot-column">
311
+ <div class="pivot-column-border pivot-column-total">
312
+ <TypeIcon ty={ColumnType::Integer} />
313
+ <span class="column_name">{ "TOTAL" }</span>
314
+ </div>
315
+ <span
316
+ class="toggle-mode is_column_active"
317
+ onmousedown={ctx.props().parent.callback(move |_| V::close(0))}
318
+ />
319
+ </div>
320
+ } else if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
299
321
  <EmptyColumn {column_dropdown} {exclude} {on_select} />
300
322
  } else if invalid_drag {
301
323
  <InvalidColumn />
@@ -34,6 +34,9 @@ pub struct ScrollPanelProps {
34
34
  #[prop_or_default]
35
35
  pub viewport_ref: Option<NodeRef>,
36
36
 
37
+ #[prop_or_default]
38
+ pub initial_width: Option<f64>,
39
+
37
40
  #[prop_or_default]
38
41
  pub class: Classes,
39
42
 
@@ -52,6 +55,9 @@ pub struct ScrollPanelProps {
52
55
  #[prop_or_default]
53
56
  pub on_resize: Option<Rc<PubSub<()>>>,
54
57
 
58
+ #[prop_or_default]
59
+ pub on_auto_width: Callback<f64>,
60
+
55
61
  #[prop_or_default]
56
62
  pub on_dimensions_reset: Option<Rc<PubSub<()>>>,
57
63
 
@@ -125,7 +131,7 @@ impl Component for ScrollPanel {
125
131
  Self {
126
132
  viewport_ref: Default::default(),
127
133
  viewport_height: 0f64,
128
- viewport_width: 0f64,
134
+ viewport_width: ctx.props().initial_width.unwrap_or_default(),
129
135
  content_window: None,
130
136
  needs_rerender: true,
131
137
  total_height,
@@ -150,6 +156,7 @@ impl Component for ScrollPanel {
150
156
 
151
157
  self.viewport_height = rect.height() - 8.0;
152
158
  self.viewport_width = self.viewport_width.max(rect.width() - 6.0);
159
+ ctx.props().on_auto_width.emit(self.viewport_width);
153
160
  re_render
154
161
  },
155
162
  ScrollPanelMsg::CalculateWindowContent => self.calculate_window_content(ctx),
@@ -100,12 +100,12 @@ where
100
100
  ctx.props().on_select.emit(self.selected.clone());
101
101
  return true;
102
102
  }
103
- } else if code.as_str() == "ArrowDown" {
104
- if let Some(x) = find_nth(idx + 1, &ctx.props().values) {
105
- self.selected = x.clone();
106
- ctx.props().on_select.emit(self.selected.clone());
107
- return true;
108
- }
103
+ } else if code.as_str() == "ArrowDown"
104
+ && let Some(x) = find_nth(idx + 1, &ctx.props().values)
105
+ {
106
+ self.selected = x.clone();
107
+ ctx.props().on_select.emit(self.selected.clone());
108
+ return true;
109
109
  }
110
110
  }
111
111
 
@@ -220,9 +220,9 @@ where
220
220
  };
221
221
 
222
222
  let value = if ctx.props().is_autosize {
223
- self.selected.to_string()
223
+ Some(self.selected.to_string())
224
224
  } else {
225
- "".to_owned()
225
+ None
226
226
  };
227
227
 
228
228
  html! {
@@ -230,7 +230,7 @@ where
230
230
  <label>
231
231
  { ctx.props().label.as_ref().map(|x| x.to_string()).unwrap_or_default() }
232
232
  </label>
233
- <div class={wrapper_class} data-value={value.clone()}>{ select }</div>
233
+ <div class={wrapper_class} data-value={value}>{ select }</div>
234
234
  } else {
235
235
  <div class={wrapper_class} data-value={value}>{ select }</div>
236
236
  }
@@ -201,11 +201,11 @@ impl Component for SplitPanel {
201
201
  let count = iter.len();
202
202
  let contents = html! {
203
203
  <>
204
- <LocalStyle key=0 href={css!("containers/split-panel")} />
204
+ <LocalStyle href={css!("containers/split-panel")} />
205
205
  for (i, x) in iter {
206
206
  if i == 0 {
207
207
  if count == 1 {
208
- <key={i}>
208
+ <key=0>
209
209
  {x}
210
210
  </>
211
211
  } else {
@@ -87,19 +87,19 @@ impl Component for FilterDropDown {
87
87
  },
88
88
  FilterDropDownMsg::ItemDown => {
89
89
  self.selected += 1;
90
- if let Some(ref values) = self.values {
91
- if self.selected >= values.len() {
92
- self.selected = 0;
93
- }
90
+ if let Some(ref values) = self.values
91
+ && self.selected >= values.len()
92
+ {
93
+ self.selected = 0;
94
94
  };
95
95
 
96
96
  true
97
97
  },
98
98
  FilterDropDownMsg::ItemUp => {
99
- if let Some(ref values) = self.values {
100
- if self.selected < 1 {
101
- self.selected = values.len();
102
- }
99
+ if let Some(ref values) = self.values
100
+ && self.selected < 1
101
+ {
102
+ self.selected = values.len();
103
103
  };
104
104
 
105
105
  self.selected -= 1;
@@ -88,19 +88,19 @@ impl Component for FunctionDropDown {
88
88
  },
89
89
  FunctionDropDownMsg::ItemDown => {
90
90
  self.selected += 1;
91
- if let Some(ref values) = self.values {
92
- if self.selected >= values.len() {
93
- self.selected = 0;
94
- }
91
+ if let Some(ref values) = self.values
92
+ && self.selected >= values.len()
93
+ {
94
+ self.selected = 0;
95
95
  };
96
96
 
97
97
  true
98
98
  },
99
99
  FunctionDropDownMsg::ItemUp => {
100
- if let Some(ref values) = self.values {
101
- if self.selected < 1 {
102
- self.selected = values.len();
103
- }
100
+ if let Some(ref values) = self.values
101
+ && self.selected < 1
102
+ {
103
+ self.selected = values.len();
104
104
  }
105
105
 
106
106
  self.selected -= 1;
@@ -82,10 +82,10 @@ where
82
82
  true
83
83
  },
84
84
  ModalMsg::SubMsg(msg) => {
85
- if let Some(child) = &ctx.props().child {
86
- if let Some(link) = child.props.weak_link().borrow().as_ref() {
87
- link.send_message(msg);
88
- }
85
+ if let Some(child) = &ctx.props().child
86
+ && let Some(link) = child.props.weak_link().borrow().as_ref()
87
+ {
88
+ link.send_message(msg);
89
89
  }
90
90
 
91
91
  false
@@ -83,11 +83,21 @@ impl Component for PluginSelector {
83
83
  let metadata =
84
84
  renderer.get_next_plugin_metadata(&PluginUpdate::Update(plugin_name));
85
85
 
86
- let mut update = ViewConfigUpdate::default();
87
- session.set_update_column_defaults(
88
- &mut update,
89
- metadata.as_ref().unwrap_or(&*renderer.metadata()),
90
- );
86
+ let prev_metadata = renderer.metadata();
87
+ let requirements = metadata.as_ref().unwrap_or(&*prev_metadata);
88
+ let rollup_features = session
89
+ .metadata()
90
+ .get_features()
91
+ .map(|x| x.get_group_rollup_modes())
92
+ .unwrap();
93
+
94
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
95
+ let mut update = ViewConfigUpdate {
96
+ group_rollup_mode: group_rollups.first().cloned(),
97
+ ..ViewConfigUpdate::default()
98
+ };
99
+
100
+ session.set_update_column_defaults(&mut update, requirements);
91
101
 
92
102
  if let Ok(task) = ctx.props().update_and_render(update) {
93
103
  ApiFuture::spawn(task);
@@ -164,6 +164,9 @@ impl Reducible for StatusIconState {
164
164
  StatusIconStateAction::Increment | StatusIconStateAction::Decrement,
165
165
  ) => StatusIconState::Loading,
166
166
  (_, StatusIconStateAction::Increment) => Self::Updating(1),
167
+ (StatusIconState::Errored(x, y, z), _) => {
168
+ StatusIconState::Errored(x.clone(), y.clone(), z)
169
+ },
167
170
  (_, StatusIconStateAction::Decrement) => StatusIconState::Normal,
168
171
  };
169
172
 
@@ -93,10 +93,10 @@ impl FilterDropDownElement {
93
93
  FilterDropDownMsg::SetValues(values),
94
94
  ]);
95
95
 
96
- if let Some(x) = self.target.borrow().clone() {
97
- if !self.modal.is_open() {
98
- ApiFuture::spawn(self.modal.clone().open(x, None))
99
- }
96
+ if let Some(x) = self.target.borrow().clone()
97
+ && !self.modal.is_open()
98
+ {
99
+ ApiFuture::spawn(self.modal.clone().open(x, None))
100
100
  }
101
101
  }
102
102
  },
@@ -77,9 +77,6 @@ pub struct PerspectiveViewerElement {
77
77
  _subscriptions: Rc<[Subscription; 2]>,
78
78
  }
79
79
 
80
- // derive_model!( Renderer, Root, Session, Presentation for
81
- // PerspectiveViewerElement);
82
-
83
80
  impl CustomElementMetadata for PerspectiveViewerElement {
84
81
  const CUSTOM_ELEMENT_NAME: &'static str = "perspective-viewer";
85
82
  const STATICS: &'static [&'static str] = ["registerPlugin", "getExprTKCommands"].as_slice();
@@ -939,10 +936,8 @@ impl PerspectiveViewerElement {
939
936
  .cloned();
940
937
 
941
938
  changed = presentation.set_theme_name(reset_theme.as_deref()).await? || changed;
942
- if changed {
943
- if let Some(view) = session.get_view() {
944
- return renderer.restyle_all(&view).await;
945
- }
939
+ if changed && let Some(view) = session.get_view() {
940
+ return renderer.restyle_all(&view).await;
946
941
  }
947
942
 
948
943
  Ok(JsValue::UNDEFINED)
@@ -10,6 +10,7 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ use perspective_client::config::GroupRollupMode;
13
14
  use perspective_js::utils::*;
14
15
  use serde::*;
15
16
  use wasm_bindgen::prelude::*;
@@ -61,6 +62,9 @@ extern "C" {
61
62
  #[wasm_bindgen(method, getter)]
62
63
  pub fn priority(this: &JsPerspectiveViewerPlugin) -> Option<i32>;
63
64
 
65
+ #[wasm_bindgen(method, getter)]
66
+ pub fn group_rollups(this: &JsPerspectiveViewerPlugin) -> Option<js_sys::Array>;
67
+
64
68
  /// Don't call this method directly. Instead, call the corresponding method on the PluginColumnStyles model.
65
69
  #[wasm_bindgen(method, catch)]
66
70
  pub fn can_render_column_styles(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>) -> ApiResult<bool>;
@@ -146,6 +150,7 @@ pub struct ViewConfigRequirements {
146
150
  pub max_cells: Option<usize>,
147
151
  pub name: String,
148
152
  pub render_warning: bool,
153
+ group_rollups: Option<Vec<GroupRollupMode>>,
149
154
  }
150
155
 
151
156
  impl ViewConfigRequirements {
@@ -155,6 +160,17 @@ impl ViewConfigRequirements {
155
160
  .map(|x| index < x.len() - 1)
156
161
  .unwrap_or(false)
157
162
  }
163
+
164
+ pub fn get_group_rollups(&self, rollup_features: &[GroupRollupMode]) -> Vec<GroupRollupMode> {
165
+ self.group_rollups
166
+ .clone()
167
+ .map(|x| {
168
+ x.into_iter()
169
+ .filter(|y| rollup_features.is_empty() || rollup_features.contains(y))
170
+ .collect()
171
+ })
172
+ .unwrap_or_default()
173
+ }
158
174
  }
159
175
 
160
176
  impl JsPerspectiveViewerPlugin {
@@ -169,6 +185,9 @@ impl JsPerspectiveViewerPlugin {
169
185
  max_cells: self.max_cells(),
170
186
  name: self.name(),
171
187
  render_warning: self.render_warning().unwrap_or(true),
188
+ group_rollups: self
189
+ .group_rollups()
190
+ .map(|x| x.into_serde_ext::<Vec<GroupRollupMode>>().unwrap()),
172
191
  })
173
192
  }
174
193
  }
@@ -12,6 +12,7 @@
12
12
 
13
13
  use std::collections::HashSet;
14
14
 
15
+ use base64::prelude::*;
15
16
  use futures::join;
16
17
  use itertools::Itertools;
17
18
  use perspective_client::ViewWindow;
@@ -59,7 +60,7 @@ pub trait CopyExportModel:
59
60
  let mut config = config?;
60
61
  config.settings = false;
61
62
  let js_config = serde_json::to_string(&config)?;
62
- let html = export_app::render(&base64::encode(arrow), &js_config, &plugins);
63
+ let html = export_app::render(&BASE64_STANDARD.encode(arrow), &js_config, &plugins);
63
64
  Ok(js_sys::JsString::from(html.trim()).into())
64
65
  }
65
66
 
@@ -38,8 +38,9 @@ impl IntersectionObserverHandle {
38
38
  ) -> Self {
39
39
  clone!(session, renderer, presentation);
40
40
  let _callback = Closure::new(move |xs: js_sys::Array| {
41
+ // https://stackoverflow.com/questions/53862160/intersectionobserver-multiple-entries
41
42
  let intersect = xs
42
- .get(0)
43
+ .pop()
43
44
  .unchecked_into::<IntersectionObserverEntry>()
44
45
  .is_intersecting();
45
46
 
@@ -49,6 +50,7 @@ impl IntersectionObserverHandle {
49
50
  session,
50
51
  renderer,
51
52
  };
53
+
52
54
  ApiFuture::spawn(state.set_pause(intersect));
53
55
  });
54
56
 
@@ -46,7 +46,10 @@ fn fill_rule_theme_names(
46
46
  if property == "--theme-name" {
47
47
  let name = style.get_property_value("--theme-name")?;
48
48
  let trimmed = name.trim();
49
- themes.push(trimmed[1..trimmed.len() - 1].to_owned());
49
+ let theme = &trimmed[1..trimmed.len() - 1];
50
+ if themes.iter().find(|x| x == &theme).is_none() {
51
+ themes.push(theme.to_owned());
52
+ }
50
53
  }
51
54
  }
52
55
  }