@perspective-dev/viewer 5.5.0 → 5.6.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 (201) hide show
  1. package/README.md +82 -3
  2. package/dist/cdn/perspective-viewer.js +9 -9
  3. package/dist/cdn/perspective-viewer.js.map +3 -3
  4. package/dist/css/blueprint.css +1 -1
  5. package/dist/css/botanical.css +1 -1
  6. package/dist/css/dracula.css +1 -1
  7. package/dist/css/eggplant.css +1 -1
  8. package/dist/css/gruvbox-dark.css +1 -1
  9. package/dist/css/gruvbox.css +1 -1
  10. package/dist/css/icons.css +1 -1
  11. package/dist/css/intl/ar.css +1 -1
  12. package/dist/css/intl/de.css +1 -1
  13. package/dist/css/intl/es.css +1 -1
  14. package/dist/css/intl/fr.css +1 -1
  15. package/dist/css/intl/ja.css +1 -1
  16. package/dist/css/intl/pt.css +1 -1
  17. package/dist/css/intl/zh.css +1 -1
  18. package/dist/css/intl.css +1 -1
  19. package/dist/css/ledger.css +1 -1
  20. package/dist/css/monokai.css +1 -1
  21. package/dist/css/nord.css +1 -1
  22. package/dist/css/phosphor.css +1 -1
  23. package/dist/css/pro-dark.css +1 -1
  24. package/dist/css/pro.css +1 -1
  25. package/dist/css/solarized-dark.css +1 -1
  26. package/dist/css/solarized.css +1 -1
  27. package/dist/css/themes.css +1 -1
  28. package/dist/css/vaporwave.css +1 -1
  29. package/dist/css/velvet.css +1 -1
  30. package/dist/docs/perspective-docs.json +1 -1
  31. package/dist/esm/column-format.js +2 -0
  32. package/dist/esm/column-format.js.map +7 -0
  33. package/dist/esm/extensions.d.ts +3 -13
  34. package/dist/esm/perspective-viewer.d.ts +1 -1
  35. package/dist/esm/perspective-viewer.js +9 -9
  36. package/dist/esm/perspective-viewer.js.map +3 -3
  37. package/dist/esm/plugin.d.ts +4 -1
  38. package/dist/esm/select-detail.d.ts +15 -0
  39. package/dist/esm/select-detail.js +2 -0
  40. package/dist/esm/select-detail.js.map +7 -0
  41. package/dist/esm/ts-rs/DerivedTableConfig.d.ts +35 -0
  42. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
  43. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +7 -6
  44. package/dist/esm/ts-rs/RestoreOptions.d.ts +3 -7
  45. package/dist/esm/ts-rs/TableRef.d.ts +9 -0
  46. package/dist/esm/ts-rs/ViewerConfig.d.ts +7 -6
  47. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -5
  48. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
  49. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +9 -0
  50. package/dist/wasm/perspective-viewer.d.ts +126 -35
  51. package/dist/wasm/perspective-viewer.js +320 -185
  52. package/dist/wasm/perspective-viewer.wasm +0 -0
  53. package/dist/wasm/perspective-viewer.wasm.d.ts +21 -17
  54. package/package.json +33 -20
  55. package/src/css/column-selector.css +205 -50
  56. package/src/css/ui/containers/context-menu.css +12 -1
  57. package/src/css/ui/containers/dropdown-menu-items.css +29 -0
  58. package/src/css/ui/containers/scroll-panel.css +4 -1
  59. package/src/rust/agent/client/protocol.rs +0 -89
  60. package/src/rust/agent/markdown/mod.rs +0 -238
  61. package/src/rust/agent/preamble.md +1 -0
  62. package/src/rust/agent/tools.rs +2 -16
  63. package/src/rust/components/column_selector/active_column.rs +15 -5
  64. package/src/rust/components/column_selector/add_expression_button.rs +9 -26
  65. package/src/rust/components/column_selector/config_selector.rs +34 -6
  66. package/src/rust/components/column_selector/derived_control.rs +229 -0
  67. package/src/rust/components/column_selector/expr_edit_button.rs +3 -2
  68. package/src/rust/components/column_selector/filter_column.rs +0 -2
  69. package/src/rust/components/column_selector/inactive_column.rs +4 -5
  70. package/src/rust/components/column_selector/pivot_column.rs +21 -1
  71. package/src/rust/components/column_selector/sort_column.rs +4 -3
  72. package/src/rust/components/column_selector.rs +21 -1
  73. package/src/rust/components/column_settings_sidebar/save_settings.rs +5 -3
  74. package/src/rust/components/column_settings_sidebar/style_tab.rs +1 -0
  75. package/src/rust/components/column_settings_sidebar.rs +25 -19
  76. package/src/rust/components/debug_panel.rs +38 -29
  77. package/src/rust/components/dragdrop_list.rs +8 -9
  78. package/src/rust/components/export_dropdown.rs +6 -0
  79. package/src/rust/components/main_panel/msg.rs +6 -4
  80. package/src/rust/components/main_panel/reconcile.rs +6 -1
  81. package/src/rust/components/main_panel/render.rs +70 -51
  82. package/src/rust/components/main_panel/update.rs +39 -1
  83. package/src/rust/components/main_panel.rs +97 -59
  84. package/src/rust/components/new_panel_menu.rs +69 -1
  85. package/src/rust/components/panel_menu.rs +63 -12
  86. package/src/rust/components/plugin_tab.rs +3 -2
  87. package/src/rust/components/render_warning.rs +2 -1
  88. package/src/rust/components/settings_panel.rs +4 -4
  89. package/src/rust/components/status_bar.rs +80 -21
  90. package/src/rust/components/status_indicator.rs +8 -1
  91. package/src/rust/components/viewer/filters.rs +15 -3
  92. package/src/rust/components/viewer/msg.rs +8 -0
  93. package/src/rust/components/viewer/panels.rs +44 -4
  94. package/src/rust/components/viewer/render.rs +1 -0
  95. package/src/rust/components/viewer/settings.rs +0 -22
  96. package/src/rust/components/viewer/snapshots.rs +2 -2
  97. package/src/rust/components/viewer/wiring.rs +18 -1
  98. package/src/rust/components/viewer.rs +47 -27
  99. package/src/rust/components/window_editor.rs +5 -4
  100. package/src/rust/config/column_config_schema.rs +38 -377
  101. package/src/rust/config/export_method.rs +24 -0
  102. package/src/rust/config/number_string_format.rs +0 -94
  103. package/src/rust/config/options.rs +3 -7
  104. package/src/rust/config/plugin_static_config.rs +0 -42
  105. package/src/rust/config/viewer_config.rs +47 -17
  106. package/src/rust/config/workspace_config.rs +158 -128
  107. package/src/rust/custom_elements/viewer.rs +189 -129
  108. package/src/rust/custom_events.rs +33 -18
  109. package/src/rust/derived.rs +266 -0
  110. package/src/rust/exprtk/tokenize.rs +0 -54
  111. package/src/rust/js/plugin.rs +1 -1
  112. package/src/rust/lib.rs +3 -0
  113. package/src/rust/presentation.rs +23 -4
  114. package/src/rust/queries/column_locator.rs +55 -48
  115. package/src/rust/queries/get_viewer_config.rs +1 -1
  116. package/src/rust/queries/plugin_column_styles.rs +22 -23
  117. package/src/rust/renderer/dispatch.rs +2 -0
  118. package/src/rust/renderer/plugin_config.rs +652 -487
  119. package/src/rust/renderer/props.rs +4 -0
  120. package/src/rust/renderer/registry.rs +2 -6
  121. package/src/rust/renderer/state.rs +205 -0
  122. package/src/rust/renderer.rs +201 -59
  123. package/src/rust/session/metadata.rs +35 -19
  124. package/src/rust/session/op_queue.rs +430 -0
  125. package/src/rust/session/panel_state.rs +327 -0
  126. package/src/rust/session/view_subscription.rs +42 -3
  127. package/src/rust/session.rs +931 -419
  128. package/src/rust/tasks/apply_global_filters.rs +49 -35
  129. package/src/rust/tasks/auto_pause.rs +22 -12
  130. package/src/rust/tasks/copy_export.rs +72 -0
  131. package/src/rust/tasks/create_panel.rs +46 -21
  132. package/src/rust/tasks/derive.rs +312 -0
  133. package/src/rust/tasks/mod.rs +6 -2
  134. package/src/rust/tasks/pipeline.rs +152 -89
  135. package/src/rust/tasks/reset_all.rs +31 -20
  136. package/src/rust/tasks/restore_derived.rs +200 -0
  137. package/src/rust/tasks/restore_panel.rs +153 -141
  138. package/src/rust/tasks/send_column_config.rs +56 -29
  139. package/src/rust/tasks/send_plugin_config.rs +52 -23
  140. package/src/rust/tasks/set_edit_mode.rs +33 -25
  141. package/src/rust/tasks/sync_update_panels.rs +40 -13
  142. package/src/rust/tasks/table_lifecycle.rs +70 -34
  143. package/src/rust/tasks/transactional_restore.rs +387 -0
  144. package/src/rust/tasks/update_theme.rs +45 -6
  145. package/src/rust/ui/containers/scroll_panel.rs +17 -10
  146. package/src/rust/ui/containers/split_panel.rs +0 -41
  147. package/src/rust/ui/context_menu.rs +157 -18
  148. package/src/rust/ui/font_loader.rs +23 -35
  149. package/src/rust/ui/form/mirrored_textarea.rs +0 -12
  150. package/src/rust/ui/form/multi_stop_gradient_selector.rs +0 -89
  151. package/src/rust/ui/form/palette_selector.rs +0 -51
  152. package/src/rust/ui/portal.rs +4 -10
  153. package/src/rust/utils/browser/mod.rs +2 -3
  154. package/src/{ts/perspective-viewer.inline.ts → rust/utils/browser/mouse.rs} +11 -10
  155. package/src/rust/utils/browser/request_animation_frame.rs +27 -8
  156. package/src/rust/utils/css.rs +0 -360
  157. package/src/rust/utils/{tests/mod.rs → in_flight.rs} +62 -2
  158. package/src/rust/utils/local_fonts.rs +0 -36
  159. package/src/rust/utils/mod.rs +4 -3
  160. package/src/rust/utils/modal_position.rs +15 -21
  161. package/src/rust/utils/pending_effects.rs +6 -30
  162. package/src/rust/utils/{browser/tests/mod.rs → plugin_host_state.rs} +31 -1
  163. package/src/rust/utils/pubsub.rs +0 -1
  164. package/src/rust/workspace.rs +187 -140
  165. package/src/svg/derived.svg +6 -0
  166. package/src/themes/icons.css +1 -0
  167. package/src/themes/intl/ar.css +10 -0
  168. package/src/themes/intl/de.css +10 -0
  169. package/src/themes/intl/es.css +10 -0
  170. package/src/themes/intl/fr.css +10 -0
  171. package/src/themes/intl/ja.css +10 -0
  172. package/src/themes/intl/pt.css +10 -0
  173. package/src/themes/intl/zh.css +10 -0
  174. package/src/themes/intl.css +10 -0
  175. package/src/themes/monokai.css +1 -1
  176. package/src/themes/vaporwave.css +1 -1
  177. package/src/ts/extensions.ts +3 -36
  178. package/src/ts/perspective-viewer.ts +1 -1
  179. package/src/ts/plugin.ts +4 -1
  180. package/src/{rust/utils/tests/request_animation_frame.rs → ts/select-detail.ts} +32 -26
  181. package/src/ts/ts-rs/DerivedTableConfig.ts +22 -0
  182. package/src/ts/ts-rs/ExportMethod.ts +1 -1
  183. package/src/ts/ts-rs/PanelViewerConfig.ts +7 -6
  184. package/src/ts/ts-rs/RestoreOptions.ts +3 -7
  185. package/src/ts/ts-rs/TableRef.ts +9 -0
  186. package/src/ts/ts-rs/ViewerConfig.ts +7 -6
  187. package/src/ts/ts-rs/ViewerConfigUpdate.ts +7 -5
  188. package/src/ts/ts-rs/WorkspaceConfig.ts +7 -1
  189. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -0
  190. package/dist/esm/perspective-viewer.inline.d.ts +0 -3
  191. package/dist/esm/perspective-viewer.inline.js +0 -21
  192. package/dist/esm/perspective-viewer.inline.js.map +0 -7
  193. package/src/rust/tasks/restore_and_render.rs +0 -107
  194. package/src/rust/utils/browser/tests/debounce.rs +0 -163
  195. package/src/rust/utils/tests/pubsub.rs +0 -95
  196. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline0.js +0 -0
  197. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline1.js +0 -0
  198. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline2.js +0 -0
  199. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline3.js +0 -0
  200. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline4.js +0 -0
  201. /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline5.js +0 -0
@@ -10,50 +10,64 @@
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::ViewConfigUpdate;
13
+ use std::rc::Rc;
14
14
 
15
- use super::apply_and_render;
16
- use crate::session::Session;
15
+ use futures::FutureExt;
16
+ use perspective_client::clone;
17
+ use perspective_js::utils::ApiFuture;
18
+
19
+ use super::pipeline::{RunCommit, render_run};
20
+ use super::transactional_restore::{commit_edit, prepare_overlay};
21
+ use crate::session::{OpKind, OverlayClause};
17
22
  use crate::utils::spawn_owned;
18
- use crate::workspace::{PanelId, Workspace};
23
+ use crate::workspace::{Panel, PanelId, Workspace};
19
24
 
20
- /// Stamp `session`'s transient global-filter overlay from the
21
- /// [`Workspace`]'s current set — EMPTY for master (filter-source) panels,
22
- /// the full set for details. Master immunity is enforced here and only
23
- /// here. Returns whether the overlay changed.
24
- ///
25
- /// Synchronous, so panel-creation paths can stamp BEFORE the panel's first
26
- /// locked bind — the initial render then picks the overlay up via its
27
- /// `ConfigSnapshot.effective`, with no second render and no unfiltered
28
- /// first paint.
29
- pub fn stamp_global_overlay(workspace: &Workspace, id: &PanelId, session: &Session) -> bool {
30
- let filters = if workspace.is_master(id) {
25
+ /// The element's global filter as broadcast to panel `id` — EMPTY for master
26
+ /// (filter-source) panels, the full set for details.
27
+ pub(crate) fn overlay_for(workspace: &Workspace, id: &PanelId) -> Rc<Vec<OverlayClause>> {
28
+ Rc::new(if workspace.is_master(id) {
31
29
  Vec::new()
32
30
  } else {
33
- workspace.global_filters()
34
- };
31
+ workspace.overlay()
32
+ })
33
+ }
34
+
35
+ /// Broadcast the [`Workspace`]'s current global filter to one panel, as an op
36
+ /// on ITS queue: the overlay is panel state, committed with the description of
37
+ /// the config it yields, like any other write.
38
+ pub fn broadcast_overlay(workspace: &Workspace, panel: &Panel) -> ApiFuture<()> {
39
+ let overlay = overlay_for(workspace, &panel.id);
40
+ clone!(panel.session, panel.renderer);
41
+ let ticket = panel.session.submit(OpKind::Overlay, move |_ctx| {
42
+ Box::pin(async move {
43
+ if *session.committed_overlay() == *overlay {
44
+ return Ok(None);
45
+ }
46
+
47
+ let bound = session.get_table().is_some();
48
+ let prepared = prepare_overlay(&session, &renderer, overlay).await?;
49
+ let committed = commit_edit(&session, &renderer, prepared);
50
+ Ok(if bound {
51
+ Some(render_run(session, renderer, RunCommit::Done(committed)).boxed_local())
52
+ } else {
53
+ None
54
+ })
55
+ })
56
+ });
35
57
 
36
- session.set_global_filter(filters)
58
+ ApiFuture::new(ticket.settle())
37
59
  }
38
60
 
39
- /// Re-stamp every panel's overlay and re-render those whose overlay CHANGED
40
- /// (details are usually non-active, so they need an explicit re-render
41
- /// rather than relying on a subscription; unchanged panels aren't touched
42
- /// at all).
61
+ /// Broadcast the global filter to every panel; panels whose overlay is
62
+ /// unchanged aren't touched at all.
43
63
  pub fn apply_global_filters(workspace: &Workspace) {
44
- for pid in workspace.panel_ids() {
45
- if let Some(panel) = workspace.panel(&pid)
46
- && stamp_global_overlay(workspace, &pid, &panel.session)
47
- {
48
- let effect = workspace.effects().guard();
49
- let session = panel.session.clone();
50
- let renderer = panel.renderer.clone();
51
- spawn_owned("apply-global-filters", async move {
52
- let _effect = effect;
53
- apply_and_render(&session, &renderer, ViewConfigUpdate::default())?.await?;
54
- Ok(())
55
- });
56
- }
64
+ for panel in workspace.panels() {
65
+ let effect = workspace.effects().guard();
66
+ let task = broadcast_overlay(workspace, &panel);
67
+ spawn_owned("apply-global-filters", async move {
68
+ let _effect = effect;
69
+ task.await
70
+ });
57
71
  }
58
72
  }
59
73
 
@@ -18,11 +18,12 @@ use wasm_bindgen::JsCast;
18
18
  use wasm_bindgen::prelude::*;
19
19
  use web_sys::*;
20
20
 
21
+ use super::transactional_restore::restore_in_place;
21
22
  use crate::config::ViewerConfigUpdate;
22
23
  use crate::js::*;
23
24
  use crate::presentation::Presentation;
24
25
  use crate::renderer::*;
25
- use crate::session::Session;
26
+ use crate::session::{OpKind, Session};
26
27
  use crate::utils::*;
27
28
  use crate::workspace::Workspace;
28
29
  use crate::*;
@@ -148,19 +149,28 @@ pub(crate) async fn set_panel_paused(
148
149
  ) -> ApiResult<()> {
149
150
  if visible {
150
151
  if session.set_pause(false) {
151
- let result = super::restore_and_render(
152
- session,
153
- renderer,
154
- presentation,
155
- super::RunOrigin::Internal,
156
- ViewerConfigUpdate::default(),
157
- async move { Ok(()) },
158
- )
159
- .await;
152
+ let ticket = session.submit(OpKind::Restore { update: None }, {
153
+ clone!(session, renderer, presentation);
154
+ move |_ctx| {
155
+ Box::pin(async move {
156
+ restore_in_place(
157
+ &session,
158
+ &renderer,
159
+ &presentation,
160
+ super::RunOrigin::Internal,
161
+ ViewerConfigUpdate::default(),
162
+ )
163
+ .await?;
164
+
165
+ Ok(None)
166
+ })
167
+ }
168
+ });
169
+
170
+ let result = ticket.settle().await;
160
171
 
161
172
  if let Err(e) = result.ignore_view_delete() {
162
- session.set_run_error(e.clone()).await?;
163
- return Err(e);
173
+ return renderer.fail(e);
164
174
  }
165
175
  }
166
176
  } else {
@@ -29,6 +29,7 @@ use crate::queries::{export_app, get_viewer_config};
29
29
  use crate::renderer::Renderer;
30
30
  use crate::session::Session;
31
31
  use crate::utils::*;
32
+ use crate::workspace::Workspace;
32
33
 
33
34
  fn tag_name_to_package(plugin: &JsPerspectiveViewerPlugin) -> String {
34
35
  let tag_name = plugin.unchecked_ref::<web_sys::HtmlElement>().tag_name();
@@ -107,10 +108,18 @@ pub async fn txt_as_jsvalue(
107
108
  }
108
109
 
109
110
  /// Generate a result `Blob` for all types of [`ExportMethod`].
111
+ /// The `window` (or a default one) with Arrow IPC body compression `codec`.
112
+ fn compressed(window: Option<ViewWindow>, codec: &str) -> Option<ViewWindow> {
113
+ let mut window = window.unwrap_or_default();
114
+ window.compression = Some(codec.to_owned());
115
+ Some(window)
116
+ }
117
+
110
118
  pub async fn export_method_to_blob(
111
119
  session: &Session,
112
120
  renderer: &Renderer,
113
121
  presentation: &Presentation,
122
+ workspace: &Workspace,
114
123
  method: ExportMethod,
115
124
  ) -> ApiResult<web_sys::Blob> {
116
125
  let viewport = renderer.get_selection();
@@ -152,6 +161,36 @@ pub async fn export_method_to_blob(
152
161
  ExportMethod::ArrowAll => crate::queries::arrow_as_jsvalue(session, true, None)
153
162
  .await?
154
163
  .as_blob(),
164
+ ExportMethod::ArrowLz4 => {
165
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "lz4"))
166
+ .await?
167
+ .as_blob()
168
+ },
169
+ ExportMethod::ArrowLz4Selected => {
170
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "lz4"))
171
+ .await?
172
+ .as_blob()
173
+ },
174
+ ExportMethod::ArrowLz4All => {
175
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "lz4"))
176
+ .await?
177
+ .as_blob()
178
+ },
179
+ ExportMethod::ArrowZstd => {
180
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "zstd"))
181
+ .await?
182
+ .as_blob()
183
+ },
184
+ ExportMethod::ArrowZstdSelected => {
185
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "zstd"))
186
+ .await?
187
+ .as_blob()
188
+ },
189
+ ExportMethod::ArrowZstdAll => {
190
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "zstd"))
191
+ .await?
192
+ .as_blob()
193
+ },
155
194
  ExportMethod::Html => html_as_jsvalue(session, renderer, presentation)
156
195
  .await?
157
196
  .as_blob(),
@@ -160,6 +199,7 @@ pub async fn export_method_to_blob(
160
199
  ExportMethod::JsonConfig => js_sys::JSON::stringify(
161
200
  &get_viewer_config(session, renderer, presentation)
162
201
  .await?
202
+ .map_table(|name| workspace.inline_table_ref(&name))
163
203
  .encode()?,
164
204
  )?
165
205
  .as_blob(),
@@ -171,6 +211,7 @@ pub async fn export_method_to_jsvalue(
171
211
  session: &Session,
172
212
  renderer: &Renderer,
173
213
  presentation: &Presentation,
214
+ workspace: &Workspace,
174
215
  method: ExportMethod,
175
216
  ) -> ApiResult<JsValue> {
176
217
  let viewport = renderer.get_selection();
@@ -212,6 +253,36 @@ pub async fn export_method_to_jsvalue(
212
253
  ExportMethod::ArrowAll => crate::queries::arrow_as_jsvalue(session, true, None)
213
254
  .await?
214
255
  .into(),
256
+ ExportMethod::ArrowLz4 => {
257
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "lz4"))
258
+ .await?
259
+ .into()
260
+ },
261
+ ExportMethod::ArrowLz4Selected => {
262
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "lz4"))
263
+ .await?
264
+ .into()
265
+ },
266
+ ExportMethod::ArrowLz4All => {
267
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "lz4"))
268
+ .await?
269
+ .into()
270
+ },
271
+ ExportMethod::ArrowZstd => {
272
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "zstd"))
273
+ .await?
274
+ .into()
275
+ },
276
+ ExportMethod::ArrowZstdSelected => {
277
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "zstd"))
278
+ .await?
279
+ .into()
280
+ },
281
+ ExportMethod::ArrowZstdAll => {
282
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "zstd"))
283
+ .await?
284
+ .into()
285
+ },
215
286
  ExportMethod::Html => html_as_jsvalue(session, renderer, presentation).await?,
216
287
  ExportMethod::Plugin if renderer.is_chart() => {
217
288
  png_as_jsvalue(session, renderer).await?.into()
@@ -219,6 +290,7 @@ pub async fn export_method_to_jsvalue(
219
290
  ExportMethod::Plugin => txt_as_jsvalue(session, renderer, viewport).await?.into(),
220
291
  ExportMethod::JsonConfig => get_viewer_config(session, renderer, presentation)
221
292
  .await?
293
+ .map_table(|name| workspace.inline_table_ref(&name))
222
294
  .encode()?,
223
295
  })
224
296
  }
@@ -17,7 +17,7 @@ use crate::config::*;
17
17
  use crate::custom_events::wire_panel_events;
18
18
  use crate::presentation::*;
19
19
  use crate::renderer::*;
20
- use crate::session::{MissingTable, ResetOptions, Session};
20
+ use crate::session::{Disposal, MissingTable, ResetOptions, Session};
21
21
  use crate::tasks::*;
22
22
  use crate::utils::*;
23
23
  use crate::workspace::{Panel, PanelId, PanelPhase, Workspace};
@@ -31,11 +31,18 @@ use crate::*;
31
31
  pub(crate) fn wire_panel_subs(
32
32
  elem: &HtmlElement,
33
33
  presentation: &Presentation,
34
+ workspace: &Workspace,
34
35
  session: &Session,
35
36
  renderer: &Renderer,
36
37
  ) -> Vec<Subscription> {
37
38
  let mut subs = vec![wire_panel_render_sub(session, renderer)];
38
- subs.extend(wire_panel_events(elem, session, renderer, presentation));
39
+ subs.extend(wire_panel_events(
40
+ elem,
41
+ session,
42
+ renderer,
43
+ presentation,
44
+ workspace,
45
+ ));
39
46
  subs
40
47
  }
41
48
 
@@ -97,7 +104,6 @@ pub(crate) async fn create_panel(
97
104
  Placement::Staged,
98
105
  );
99
106
 
100
- stamp_global_overlay(workspace, &id, &session);
101
107
  notify.emit(());
102
108
 
103
109
  ApiFuture::spawn({
@@ -123,12 +129,12 @@ pub(crate) async fn create_panel(
123
129
  update,
124
130
  crate::tasks::RestoreErrors::Publish,
125
131
  missing,
132
+ None,
126
133
  )
127
134
  .await;
128
135
 
129
- // Promote on completion AND error — a failed restore's error state
130
- // must become visible too.
131
- if workspace.promote(&id) {
136
+ let discarded = discard_rejected(workspace, &id, &session, &result).await?;
137
+ if discarded || workspace.promote(&id) {
132
138
  notify.emit(());
133
139
  }
134
140
 
@@ -136,6 +142,26 @@ pub(crate) async fn create_panel(
136
142
  Ok(id)
137
143
  }
138
144
 
145
+ /// Remove the panel a rejected CREATING restore was for.
146
+ pub(crate) async fn discard_rejected(
147
+ workspace: &Workspace,
148
+ id: &PanelId,
149
+ session: &Session,
150
+ result: &ApiResult<()>,
151
+ ) -> ApiResult<bool> {
152
+ if result.is_ok() || session.get_table().is_some() || session.pending_table().is_some() {
153
+ return Ok(false);
154
+ }
155
+
156
+ match workspace.remove_panel(id) {
157
+ Some(panel) => {
158
+ eject_panel(panel, Disposal::Resolve).await?;
159
+ Ok(true)
160
+ },
161
+ None => Ok(false),
162
+ }
163
+ }
164
+
139
165
  /// Where [`create_panel_model`] registers the new panel model.
140
166
  pub(crate) enum Placement {
141
167
  /// Into the placed panel set at [`PanelPhase::Placed`] — a live, visible
@@ -173,7 +199,7 @@ pub(crate) fn create_panel_model(
173
199
  let renderer = Renderer::new(elem);
174
200
  let id = id.unwrap_or_else(|| workspace.generate_id());
175
201
  renderer.set_slot_name(id.as_str());
176
- let subs = wire_panel_subs(elem, presentation, &session, &renderer);
202
+ let subs = wire_panel_subs(elem, presentation, workspace, &session, &renderer);
177
203
  let panel = Panel::new(id.clone(), session.clone(), renderer.clone(), subs);
178
204
  match placement {
179
205
  Placement::Placed => workspace.insert_panel(panel, PanelPhase::Placed),
@@ -182,7 +208,7 @@ pub(crate) fn create_panel_model(
182
208
  }
183
209
 
184
210
  update.settings = OptionalUpdate::Missing;
185
- renderer.set_theme(match &update.theme {
211
+ renderer.commit_theme(match &update.theme {
186
212
  OptionalUpdate::Update(theme) => Some(theme.clone()),
187
213
  _ => presentation.active_theme_name_sync(),
188
214
  });
@@ -213,7 +239,6 @@ pub(crate) fn place_reserved(
213
239
  has_table: bool,
214
240
  ) -> Option<Panel> {
215
241
  let panel = workspace.claim_reserved(has_table)?;
216
- stamp_global_overlay(workspace, &panel.id, &panel.session);
217
242
  notify.emit(());
218
243
  Some(panel)
219
244
  }
@@ -222,19 +247,19 @@ pub(crate) fn place_reserved(
222
247
  /// renderer (slot-scoped plugin + light-DOM cleanup) and eject its table.
223
248
  /// Shared by the root's `ClosePanel` handler and `restoreWorkspace`'s
224
249
  /// batch replacement of the pre-existing panel set.
225
- pub(crate) fn eject_panel(panel: Panel) -> ApiFuture<()> {
226
- let was_errored = panel.session.is_errored();
227
- let dispose_task = panel.renderer.dispose();
228
- let reset_task = panel.session.reset(ResetOptions {
229
- config: true,
230
- expressions: true,
231
- table: Some(session::TableIntermediateState::Ejected),
232
- ..ResetOptions::default()
233
- });
234
-
250
+ pub(crate) fn eject_panel(panel: Panel, disposal: Disposal) -> ApiFuture<()> {
251
+ panel.session.dispose(disposal);
235
252
  ApiFuture::new(async move {
236
- dispose_task.await?;
237
- match reset_task.await.ignore_view_delete() {
253
+ panel.renderer.dispose().await?;
254
+ let was_errored = panel.session.is_errored();
255
+ let reset = panel.session.reset(ResetOptions {
256
+ config: true,
257
+ expressions: true,
258
+ table: Some(session::TableIntermediateState::Ejected),
259
+ ..ResetOptions::default()
260
+ });
261
+
262
+ match reset.await.ignore_view_delete() {
238
263
  Err(_) if was_errored => Ok(()),
239
264
  Err(e) => Err(e),
240
265
  Ok(_) => Ok(()),