@perspective-dev/viewer 5.5.0 → 5.5.1

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 (44) hide show
  1. package/dist/cdn/perspective-viewer.js +1 -1
  2. package/dist/cdn/perspective-viewer.js.map +1 -1
  3. package/dist/css/monokai.css +1 -1
  4. package/dist/css/themes.css +1 -1
  5. package/dist/css/vaporwave.css +1 -1
  6. package/dist/docs/perspective-docs.json +1 -1
  7. package/dist/esm/perspective-viewer.inline.js +3 -3
  8. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  9. package/dist/esm/perspective-viewer.js +1 -1
  10. package/dist/esm/perspective-viewer.js.map +1 -1
  11. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
  12. package/dist/wasm/perspective-viewer.d.ts +27 -16
  13. package/dist/wasm/perspective-viewer.js +112 -101
  14. package/dist/wasm/perspective-viewer.wasm +0 -0
  15. package/dist/wasm/perspective-viewer.wasm.d.ts +16 -16
  16. package/package.json +7 -7
  17. package/src/rust/components/export_dropdown.rs +6 -0
  18. package/src/rust/components/status_bar.rs +12 -3
  19. package/src/rust/components/viewer/filters.rs +2 -2
  20. package/src/rust/components/viewer/msg.rs +4 -0
  21. package/src/rust/components/viewer/panels.rs +3 -3
  22. package/src/rust/components/viewer/wiring.rs +4 -1
  23. package/src/rust/components/viewer.rs +2 -2
  24. package/src/rust/config/export_method.rs +24 -0
  25. package/src/rust/custom_elements/viewer.rs +21 -17
  26. package/src/rust/session/metadata.rs +5 -10
  27. package/src/rust/session.rs +123 -116
  28. package/src/rust/tasks/copy_export.rs +67 -0
  29. package/src/rust/tasks/create_panel.rs +13 -13
  30. package/src/rust/tasks/pipeline.rs +8 -1
  31. package/src/rust/tasks/restore_panel.rs +15 -15
  32. package/src/rust/tasks/sync_update_panels.rs +3 -3
  33. package/src/rust/utils/in_flight.rs +74 -0
  34. package/src/rust/utils/mod.rs +2 -0
  35. package/src/rust/utils/pending_effects.rs +6 -30
  36. package/src/themes/monokai.css +1 -1
  37. package/src/themes/vaporwave.css +1 -1
  38. package/src/ts/ts-rs/ExportMethod.ts +1 -1
  39. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-6813b568219c6763}/inline0.js +0 -0
  40. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-6813b568219c6763}/inline1.js +0 -0
  41. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-6813b568219c6763}/inline2.js +0 -0
  42. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-6813b568219c6763}/inline3.js +0 -0
  43. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-6813b568219c6763}/inline4.js +0 -0
  44. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-6813b568219c6763}/inline5.js +0 -0
@@ -157,22 +157,22 @@ export const virtualdataslice_setIntegerCol: (a: number, b: number, c: number, d
157
157
  export const virtualdataslice_setStringCol: (a: number, b: number, c: number, d: number, e: number, f: number, g: number) => void;
158
158
  export const virtualserver_handleRequest: (a: number, b: number, c: number) => number;
159
159
  export const virtualserver_new: (a: number, b: number) => void;
160
- export const __wasm_bindgen_func_elem_18885: (a: number, b: number) => void;
161
- export const __wasm_bindgen_func_elem_4479: (a: number, b: number) => void;
162
- export const __wasm_bindgen_func_elem_19596: (a: number, b: number) => void;
163
- export const __wasm_bindgen_func_elem_20506: (a: number, b: number) => void;
164
- export const __wasm_bindgen_func_elem_25319: (a: number, b: number) => void;
165
- export const __wasm_bindgen_func_elem_33477: (a: number, b: number, c: number, d: number) => void;
166
- export const __wasm_bindgen_func_elem_19005: (a: number, b: number, c: number) => void;
167
- export const __wasm_bindgen_func_elem_7877: (a: number, b: number, c: number) => void;
168
- export const __wasm_bindgen_func_elem_7878: (a: number, b: number, c: number) => void;
169
- export const __wasm_bindgen_func_elem_7879: (a: number, b: number, c: number) => void;
170
- export const __wasm_bindgen_func_elem_7875: (a: number, b: number, c: number) => void;
171
- export const __wasm_bindgen_func_elem_19601: (a: number, b: number, c: number) => void;
172
- export const __wasm_bindgen_func_elem_25336: (a: number, b: number, c: number) => void;
173
- export const __wasm_bindgen_func_elem_21545: (a: number, b: number) => number;
174
- export const __wasm_bindgen_func_elem_19600: (a: number, b: number) => void;
175
- export const __wasm_bindgen_func_elem_21546: (a: number, b: number) => void;
160
+ export const __wasm_bindgen_func_elem_18919: (a: number, b: number) => void;
161
+ export const __wasm_bindgen_func_elem_4482: (a: number, b: number) => void;
162
+ export const __wasm_bindgen_func_elem_19628: (a: number, b: number) => void;
163
+ export const __wasm_bindgen_func_elem_20538: (a: number, b: number) => void;
164
+ export const __wasm_bindgen_func_elem_25347: (a: number, b: number) => void;
165
+ export const __wasm_bindgen_func_elem_33507: (a: number, b: number, c: number, d: number) => void;
166
+ export const __wasm_bindgen_func_elem_19039: (a: number, b: number, c: number) => void;
167
+ export const __wasm_bindgen_func_elem_7893: (a: number, b: number, c: number) => void;
168
+ export const __wasm_bindgen_func_elem_7894: (a: number, b: number, c: number) => void;
169
+ export const __wasm_bindgen_func_elem_7895: (a: number, b: number, c: number) => void;
170
+ export const __wasm_bindgen_func_elem_7891: (a: number, b: number, c: number) => void;
171
+ export const __wasm_bindgen_func_elem_19633: (a: number, b: number, c: number) => void;
172
+ export const __wasm_bindgen_func_elem_25364: (a: number, b: number, c: number) => void;
173
+ export const __wasm_bindgen_func_elem_21577: (a: number, b: number) => number;
174
+ export const __wasm_bindgen_func_elem_19632: (a: number, b: number) => void;
175
+ export const __wasm_bindgen_func_elem_21578: (a: number, b: number) => void;
176
176
  export const __wbindgen_export: (a: number, b: number) => number;
177
177
  export const __wbindgen_export2: (a: number, b: number, c: number, d: number) => number;
178
178
  export const __wbindgen_export3: (a: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@perspective-dev/viewer",
3
- "version": "5.5.0",
3
+ "version": "5.5.1",
4
4
  "description": "The `<perspective-viewer>` Custom Element, frontend for Perspective",
5
5
  "keywords": [
6
6
  "perspective",
@@ -56,12 +56,12 @@
56
56
  "dependencies": {
57
57
  "pro_self_extracting_wasm": "0.0.9",
58
58
  "regular-layout": "=0.6.1",
59
- "@perspective-dev/client": "^5.5.0"
59
+ "@perspective-dev/client": "^5.5.1"
60
60
  },
61
61
  "devDependencies": {
62
62
  "lightningcss": "^1.29.0",
63
- "@playwright/test": "=1.58.0",
64
- "@playwright/experimental-ct-react": "=1.58.0",
63
+ "@playwright/test": "=1.62.0",
64
+ "@playwright/experimental-ct-react": "=1.62.0",
65
65
  "@types/react": ">17 <20",
66
66
  "@types/node": ">=22",
67
67
  "@types/react-dom": ">17 <20",
@@ -70,9 +70,9 @@
70
70
  "typedoc": "^0.28.7",
71
71
  "typescript": ">=5 <6",
72
72
  "zx": ">=8 <9",
73
- "@perspective-dev/metadata": "^5.5.0",
74
- "@perspective-dev/esbuild-plugin": "^5.5.0",
75
- "@perspective-dev/test": "^5.5.0"
73
+ "@perspective-dev/test": "^5.5.1",
74
+ "@perspective-dev/esbuild-plugin": "^5.5.1",
75
+ "@perspective-dev/metadata": "^5.5.1"
76
76
  },
77
77
  "scripts": {
78
78
  "build": "node ./build.mjs",
@@ -100,6 +100,8 @@ fn get_menu_items(name: &str, is_chart: bool) -> Vec<ExportDropDownMenuItem> {
100
100
  ExportMethod::Json.new_file(name, is_chart),
101
101
  ExportMethod::Ndjson.new_file(name, is_chart),
102
102
  ExportMethod::Arrow.new_file(name, is_chart),
103
+ ExportMethod::ArrowLz4.new_file(name, is_chart),
104
+ ExportMethod::ArrowZstd.new_file(name, is_chart),
103
105
  ExportMethod::Html.new_file(name, is_chart),
104
106
  ExportMethod::Plugin.new_file(name, is_chart),
105
107
  ]
@@ -109,6 +111,8 @@ fn get_menu_items(name: &str, is_chart: bool) -> Vec<ExportDropDownMenuItem> {
109
111
  ExportMethod::Json.new_file(name, is_chart),
110
112
  ExportMethod::Ndjson.new_file(name, is_chart),
111
113
  ExportMethod::Arrow.new_file(name, is_chart),
114
+ ExportMethod::ArrowLz4.new_file(name, is_chart),
115
+ ExportMethod::ArrowZstd.new_file(name, is_chart),
112
116
  ExportMethod::Html.new_file(name, is_chart),
113
117
  ]
114
118
  },
@@ -118,6 +122,8 @@ fn get_menu_items(name: &str, is_chart: bool) -> Vec<ExportDropDownMenuItem> {
118
122
  ExportMethod::JsonAll.new_file(name, is_chart),
119
123
  ExportMethod::NdjsonAll.new_file(name, is_chart),
120
124
  ExportMethod::ArrowAll.new_file(name, is_chart),
125
+ ExportMethod::ArrowLz4All.new_file(name, is_chart),
126
+ ExportMethod::ArrowZstdAll.new_file(name, is_chart),
121
127
  ]),
122
128
  ExportDropDownMenuItem::OptGroup("Config".into(), vec![
123
129
  ExportMethod::JsonConfig.new_file(name, is_chart),
@@ -276,10 +276,19 @@ impl Component for StatusBar {
276
276
  // TODO Memoizing these would reduce some vdom diffing later on
277
277
  let onclose = ctx.link().callback(|_| StatusBarMsg::Eject);
278
278
  let onpointerdown = ctx.link().callback(StatusBarMsg::PointerEvent);
279
- let onexport = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);
280
- let oncopy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);
279
+ let onexport = ctx.link().callback(|event: MouseEvent| {
280
+ event.prevent_default();
281
+ StatusBarMsg::Export
282
+ });
283
+ let oncopy = ctx.link().callback(|event: MouseEvent| {
284
+ event.prevent_default();
285
+ StatusBarMsg::Copy
286
+ });
281
287
  let onreset = ctx.link().callback(StatusBarMsg::Reset);
282
- let onnew = ctx.link().callback(|_: MouseEvent| StatusBarMsg::NewPanel);
288
+ let onnew = ctx.link().callback(|event: MouseEvent| {
289
+ event.prevent_default();
290
+ StatusBarMsg::NewPanel
291
+ });
283
292
  let on_new_select = ctx.link().callback(StatusBarMsg::NewPanelPick);
284
293
  let on_close_new = ctx.link().callback(|_| StatusBarMsg::CloseNewPanel);
285
294
 
@@ -97,10 +97,10 @@ impl PerspectiveViewer {
97
97
  derived
98
98
  } else {
99
99
  // Nothing derivable from the event's configs — fall back
100
- // to the clicked-cell clause if the detail carried one,
101
- // else leave the current contribution untouched.
100
+ // to the clicked-cell clause if the detail carried one.
102
101
  match selection.cell_fallback {
103
102
  Some(x) => vec![x],
103
+ None if selection.clear_if_underivable => Vec::new(),
104
104
  None => return false,
105
105
  }
106
106
  }
@@ -43,6 +43,10 @@ pub struct MasterSelection {
43
43
  /// `filters` derives to nothing — e.g. a FLAT (un-grouped) datagrid
44
44
  /// master, whose clicks carry no group-by path to filter on.
45
45
  pub cell_fallback: Option<Filter>,
46
+
47
+ /// Whether an event deriving no clause and carrying no `cell_fallback`
48
+ /// clears the panel's contribution rather than leaving it untouched.
49
+ pub clear_if_underivable: bool,
46
50
  }
47
51
 
48
52
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -190,7 +190,7 @@ impl PerspectiveViewer {
190
190
  pub(super) fn on_panel_closed(&mut self, ctx: &Context<Self>, id: String) -> bool {
191
191
  let id = PanelId::from(id);
192
192
  if let Some(panel) = ctx.props().workspace.take_closing(&id) {
193
- let eject = eject_panel(panel);
193
+ let eject = eject_panel(panel, Disposal::Reject);
194
194
  match self.pending_closes.remove(&id) {
195
195
  Some(completion) => completion.resolve_after(eject),
196
196
  None => spawn_owned("close-panel", eject),
@@ -201,7 +201,7 @@ impl PerspectiveViewer {
201
201
 
202
202
  match self.detach_panel(ctx, &id) {
203
203
  Some(panel) => {
204
- spawn_owned("close-panel", eject_panel(panel));
204
+ spawn_owned("close-panel", eject_panel(panel, Disposal::Reject));
205
205
  true
206
206
  },
207
207
  None => false,
@@ -337,7 +337,7 @@ impl PerspectiveViewer {
337
337
  self._active_subscriptions = create_active_subscriptions(ctx, &session, &renderer);
338
338
  self.session_props = session.to_props();
339
339
  self.renderer_props = renderer.to_props(None);
340
- self.update_count = session.in_flight_config_runs();
340
+ self.update_count = session.config_runs.count();
341
341
  self.active_session = session;
342
342
  self.active_renderer = renderer;
343
343
  let presentation = ctx.props().presentation.clone();
@@ -150,6 +150,7 @@ pub(super) fn global_filter_listener(
150
150
  let selection = selected.then(|| MasterSelection {
151
151
  cell_fallback: cell_fallback(&row, &column_names),
152
152
  filters: insert_filters,
153
+ clear_if_underivable: true,
153
154
  });
154
155
 
155
156
  cb.emit((panel, selection));
@@ -210,6 +211,7 @@ pub(super) fn master_click_listener(
210
211
  let selection = MasterSelection {
211
212
  cell_fallback: cell_fallback(&row, &column_names),
212
213
  filters: config.map(|x| x.filter).unwrap_or_default(),
214
+ clear_if_underivable: false,
213
215
  };
214
216
 
215
217
  cb.emit((panel, Some(selection)));
@@ -284,7 +286,8 @@ pub(super) fn create_active_subscriptions(
284
286
  let sub4 = session.view_config_changed.add_notify_listener(&cb);
285
287
  let sub5 = session.title_changed.add_notify_listener(&cb);
286
288
  let sub6 = session
287
- .run_state_changed
289
+ .config_runs
290
+ .changed()
288
291
  .add_listener(ctx.link().callback(UpdateInFlight));
289
292
 
290
293
  let sub7 = session.column_stats_changed.add_notify_listener(&cb);
@@ -124,8 +124,8 @@ pub struct PerspectiveViewer {
124
124
  dragdrop_props: crate::presentation::DragDropProps,
125
125
 
126
126
  /// The active panel's in-flight config-run count — a LEVEL-triggered
127
- /// snapshot of `Session::in_flight_config_runs` (RAII-settled;
128
- /// assigned by `UpdateInFlight`, re-read on retarget). Threaded to
127
+ /// snapshot of `Session::config_runs` (RAII-settled; assigned by
128
+ /// `UpdateInFlight`, re-read on retarget). Threaded to
129
129
  /// `StatusIndicator` as the "updating" spinner.
130
130
  update_count: u32,
131
131
 
@@ -69,6 +69,24 @@ pub enum ExportMethod {
69
69
  #[serde(rename = "arrow-all")]
70
70
  ArrowAll,
71
71
 
72
+ #[serde(rename = "arrow-lz4")]
73
+ ArrowLz4,
74
+
75
+ #[serde(rename = "arrow-lz4-selected")]
76
+ ArrowLz4Selected,
77
+
78
+ #[serde(rename = "arrow-lz4-all")]
79
+ ArrowLz4All,
80
+
81
+ #[serde(rename = "arrow-zstd")]
82
+ ArrowZstd,
83
+
84
+ #[serde(rename = "arrow-zstd-selected")]
85
+ ArrowZstdSelected,
86
+
87
+ #[serde(rename = "arrow-zstd-all")]
88
+ ArrowZstdAll,
89
+
72
90
  #[serde(rename = "json-config")]
73
91
  JsonConfig,
74
92
  }
@@ -94,6 +112,12 @@ impl ExportMethod {
94
112
  Self::CsvSelected => ".selected.csv",
95
113
  Self::JsonSelected => ".selected.json",
96
114
  Self::ArrowSelected => ".selected.arrow",
115
+ Self::ArrowLz4 => ".lz4.arrow",
116
+ Self::ArrowLz4Selected => ".selected.lz4.arrow",
117
+ Self::ArrowLz4All => ".all.lz4.arrow",
118
+ Self::ArrowZstd => ".zstd.arrow",
119
+ Self::ArrowZstdSelected => ".selected.zstd.arrow",
120
+ Self::ArrowZstdAll => ".all.zstd.arrow",
97
121
  Self::Ndjson => ".ndjson",
98
122
  Self::NdjsonAll => ".all.ndjson",
99
123
  Self::NdjsonSelected => ".selected.ndjson",
@@ -38,7 +38,7 @@ use crate::js::*;
38
38
  use crate::presentation::*;
39
39
  use crate::queries::*;
40
40
  use crate::root::Root;
41
- use crate::session::{MissingTable, ResetOptions, TableLoadState};
41
+ use crate::session::{Disposal, MissingTable, ResetOptions, TableLoadState};
42
42
  use crate::tasks::*;
43
43
  use crate::utils::*;
44
44
  use crate::workspace::{Panel, PanelId, Workspace};
@@ -548,14 +548,19 @@ impl PerspectiveViewerElement {
548
548
 
549
549
  let session = panel.session;
550
550
  let renderer = panel.renderer;
551
- let generation = session.begin_pending_load();
551
+ let load = session.begin_pending_load();
552
552
  clone!(self.workspace, self.presentation);
553
553
  Ok(ApiFuture::new_throttled(async move {
554
554
  let _effect = effect;
555
+ enum Discard {
556
+ Unclaimed(Panel),
557
+ Evicted(Panel),
558
+ }
559
+
555
560
  renderer.set_throttle(None);
556
561
  let _run_token = session.begin_config_run();
557
562
  let result = {
558
- clone!(session, renderer, workspace, notify);
563
+ clone!(session, renderer, workspace, notify, load);
559
564
  renderer
560
565
  .clone()
561
566
  .render_task(|guard| async move {
@@ -569,7 +574,7 @@ impl PerspectiveViewerElement {
569
574
  try_from_js_option::<perspective_js::Table>(jstable.clone())
570
575
  {
571
576
  tracing::warn!("{}", DEPRECATED_TABLE_MESSAGE);
572
- let Some(journal) = session.take_pending_load(generation) else {
577
+ let Some(journal) = load.claim() else {
573
578
  return Ok(None);
574
579
  };
575
580
 
@@ -630,10 +635,10 @@ impl PerspectiveViewerElement {
630
635
  // window is discarded (not replayed): a `Client`
631
636
  // performs no reset, and any racing `restore`'s
632
637
  // commits already applied live (`commit_view_config`).
633
- let owned_window = session.take_pending_load(generation).is_some();
638
+ let owned_window = load.claim().is_some();
634
639
  let discard = if owned_window && notify.is_some() {
635
640
  match workspace.take_reserved() {
636
- Some(panel) => Some((panel, None)),
641
+ Some(panel) => Some(Discard::Unclaimed(panel)),
637
642
  // The one-shot claim read: a table-less
638
643
  // `restore` claimed the reservation, and no
639
644
  // table has bound nor is pending — evict the
@@ -655,9 +660,7 @@ impl PerspectiveViewerElement {
655
660
  notify.emit(());
656
661
  }
657
662
 
658
- evicted.map(|panel| {
659
- (panel, Some(ApiError::new(CREATE_REQUIRES_TABLE)))
660
- })
663
+ evicted.map(Discard::Evicted)
661
664
  },
662
665
  None => None,
663
666
  }
@@ -668,7 +671,7 @@ impl PerspectiveViewerElement {
668
671
  workspace.set_default_client(client.get_client().clone());
669
672
  Ok(discard)
670
673
  } else {
671
- session.take_pending_load(generation);
674
+ load.close();
672
675
  Err(ApiError::new("Invalid argument"))
673
676
  }
674
677
  })
@@ -677,7 +680,7 @@ impl PerspectiveViewerElement {
677
680
 
678
681
  match result {
679
682
  Err(e) => {
680
- session.take_pending_load(generation);
683
+ load.close();
681
684
  if let Some(notify) = &notify {
682
685
  place_reserved(&workspace, notify, true);
683
686
  }
@@ -685,12 +688,13 @@ impl PerspectiveViewerElement {
685
688
  session.set_error(false, e.clone()).await?;
686
689
  Err(e)
687
690
  },
688
- Ok(Some((panel, error))) => {
689
- eject_panel(panel).await?;
690
- match error {
691
- Some(e) => Err(e),
692
- None => Ok(()),
693
- }
691
+ Ok(Some(Discard::Unclaimed(panel))) => {
692
+ eject_panel(panel, Disposal::Reject).await?;
693
+ Ok(())
694
+ },
695
+ Ok(Some(Discard::Evicted(panel))) => {
696
+ eject_panel(panel, Disposal::Resolve).await?;
697
+ Err(ApiError::new(CREATE_REQUIRES_TABLE))
694
698
  },
695
699
  Ok(None) => Ok(()),
696
700
  }
@@ -92,7 +92,7 @@ impl SessionMetadata {
92
92
  &mut self,
93
93
  view_schema: &HashMap<String, ColumnType>,
94
94
  ) -> ApiResult<()> {
95
- self.as_mut().unwrap().view_schema = Some(view_schema.clone());
95
+ self.as_mut().ok_or("No `Table` set")?.view_schema = Some(view_schema.clone());
96
96
  Ok(())
97
97
  }
98
98
 
@@ -115,7 +115,7 @@ impl SessionMetadata {
115
115
  Some((name.clone(), dtype))
116
116
  })
117
117
  .collect();
118
- self.as_mut().unwrap().window_schema = window_schema;
118
+ self.as_mut().ok_or("No `Table` set")?.window_schema = window_schema;
119
119
  Ok(())
120
120
  }
121
121
 
@@ -151,16 +151,11 @@ impl SessionMetadata {
151
151
  )));
152
152
  }
153
153
 
154
- let mut edited = self
155
- .as_mut()
156
- .unwrap()
157
- .expr_meta
158
- .take()
159
- .map(|x| x.edited)
160
- .unwrap_or_default();
154
+ let state = self.as_mut().ok_or("No `Table` set")?;
155
+ let mut edited = state.expr_meta.take().map(|x| x.edited).unwrap_or_default();
161
156
 
162
157
  edited.retain(|k, _| valid_recs.expression_alias.contains_key(k));
163
- self.as_mut().unwrap().expr_meta = Some(SessionViewExpressionMetadata {
158
+ state.expr_meta = Some(SessionViewExpressionMetadata {
164
159
  expressions: valid_recs.clone(),
165
160
  edited,
166
161
  });