@perspective-dev/viewer 5.4.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 (142) hide show
  1. package/dist/cdn/perspective-viewer.js +4 -4
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/blueprint.css +1 -1
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -1
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -1
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -1
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -1
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -1
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/perspective-viewer.d.ts +2 -0
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +4 -4
  34. package/dist/esm/perspective-viewer.js.map +2 -2
  35. package/dist/esm/plugin.d.ts +9 -0
  36. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  37. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
  38. package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
  39. package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
  40. package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
  41. package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
  42. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  43. package/dist/wasm/perspective-viewer.d.ts +102 -47
  44. package/dist/wasm/perspective-viewer.js +233 -142
  45. package/dist/wasm/perspective-viewer.wasm +0 -0
  46. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -18
  47. package/package.json +7 -7
  48. package/src/css/column-selector.css +11 -3
  49. package/src/css/column-style.css +162 -0
  50. package/src/css/config-selector.css +1 -1
  51. package/src/css/form/debug.css +9 -0
  52. package/src/css/status-bar.css +11 -0
  53. package/src/css/ui/containers/tabs.css +2 -1
  54. package/src/rust/agent/tools.rs +80 -7
  55. package/src/rust/components/column_selector/active_column.rs +9 -0
  56. package/src/rust/components/column_selector.rs +11 -0
  57. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  58. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  59. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +1 -1
  60. package/src/rust/components/column_settings_sidebar/style_tab.rs +68 -9
  61. package/src/rust/components/debug_panel.rs +107 -50
  62. package/src/rust/components/export_dropdown.rs +6 -0
  63. package/src/rust/components/main_panel/msg.rs +4 -0
  64. package/src/rust/components/main_panel.rs +14 -0
  65. package/src/rust/components/mod.rs +0 -1
  66. package/src/rust/components/plugin_tab.rs +57 -3
  67. package/src/rust/components/settings_panel.rs +6 -0
  68. package/src/rust/components/status_bar.rs +12 -3
  69. package/src/rust/components/status_indicator.rs +4 -0
  70. package/src/rust/components/viewer/filters.rs +2 -2
  71. package/src/rust/components/viewer/msg.rs +4 -0
  72. package/src/rust/components/viewer/panels.rs +6 -3
  73. package/src/rust/components/viewer/render.rs +1 -0
  74. package/src/rust/components/viewer/wiring.rs +14 -2
  75. package/src/rust/components/viewer.rs +2 -2
  76. package/src/rust/config/column_config_schema.rs +312 -8
  77. package/src/rust/config/datetime_column_style.rs +0 -11
  78. package/src/rust/config/export_method.rs +24 -0
  79. package/src/rust/config/mod.rs +0 -2
  80. package/src/rust/config/options.rs +50 -0
  81. package/src/rust/config/workspace_config.rs +69 -24
  82. package/src/rust/custom_elements/viewer.rs +214 -113
  83. package/src/rust/js/plugin.rs +3 -1
  84. package/src/rust/lib.rs +7 -3
  85. package/src/rust/queries/plugin_column_styles.rs +6 -5
  86. package/src/rust/renderer/plugin_config.rs +200 -41
  87. package/src/rust/renderer/plugin_store.rs +2 -1
  88. package/src/rust/renderer/props.rs +5 -0
  89. package/src/rust/renderer.rs +9 -0
  90. package/src/rust/session/metadata.rs +5 -10
  91. package/src/rust/session/props.rs +4 -0
  92. package/src/rust/session.rs +273 -195
  93. package/src/rust/tasks/copy_export.rs +67 -0
  94. package/src/rust/tasks/create_panel.rs +16 -13
  95. package/src/rust/tasks/pipeline.rs +26 -8
  96. package/src/rust/tasks/restore_panel.rs +129 -30
  97. package/src/rust/tasks/send_column_config.rs +3 -0
  98. package/src/rust/tasks/sync_update_panels.rs +148 -68
  99. package/src/rust/tasks/table_lifecycle.rs +7 -1
  100. package/src/rust/ui/containers/tab_list.rs +1 -1
  101. package/src/rust/ui/form/intl_label.rs +4 -23
  102. package/src/rust/ui/portal.rs +1 -0
  103. package/src/rust/utils/css_vars.rs +13 -0
  104. package/src/rust/{config/datetime_column_style/color_mode.rs → utils/in_flight.rs} +47 -35
  105. package/src/rust/utils/local_fonts.rs +395 -0
  106. package/src/rust/utils/mod.rs +4 -0
  107. package/src/rust/utils/pending_effects.rs +6 -30
  108. package/src/rust/workspace.rs +12 -0
  109. package/src/svg/status_pending.svg +3 -0
  110. package/src/themes/icons.css +1 -0
  111. package/src/themes/intl/ar.css +20 -7
  112. package/src/themes/intl/de.css +20 -7
  113. package/src/themes/intl/es.css +20 -7
  114. package/src/themes/intl/fr.css +20 -7
  115. package/src/themes/intl/ja.css +20 -7
  116. package/src/themes/intl/pt.css +20 -7
  117. package/src/themes/intl/zh.css +20 -7
  118. package/src/themes/intl.css +20 -7
  119. package/src/themes/monokai.css +1 -1
  120. package/src/themes/vaporwave.css +1 -1
  121. package/src/ts/perspective-viewer.ts +2 -0
  122. package/src/ts/plugin.ts +9 -0
  123. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  124. package/src/ts/ts-rs/ExportMethod.ts +1 -1
  125. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  126. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  127. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  128. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  129. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  130. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  131. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  132. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  133. package/src/rust/components/string_column_style.rs +0 -120
  134. package/src/rust/config/string_column_style.rs +0 -135
  135. package/src/ts/ts-rs/FormatMode.ts +0 -3
  136. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  137. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline0.js +0 -0
  138. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline1.js +0 -0
  139. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline2.js +0 -0
  140. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline3.js +0 -0
  141. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline4.js +0 -0
  142. /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline5.js +0 -0
@@ -107,6 +107,13 @@ pub async fn txt_as_jsvalue(
107
107
  }
108
108
 
109
109
  /// Generate a result `Blob` for all types of [`ExportMethod`].
110
+ /// The `window` (or a default one) with Arrow IPC body compression `codec`.
111
+ fn compressed(window: Option<ViewWindow>, codec: &str) -> Option<ViewWindow> {
112
+ let mut window = window.unwrap_or_default();
113
+ window.compression = Some(codec.to_owned());
114
+ Some(window)
115
+ }
116
+
110
117
  pub async fn export_method_to_blob(
111
118
  session: &Session,
112
119
  renderer: &Renderer,
@@ -152,6 +159,36 @@ pub async fn export_method_to_blob(
152
159
  ExportMethod::ArrowAll => crate::queries::arrow_as_jsvalue(session, true, None)
153
160
  .await?
154
161
  .as_blob(),
162
+ ExportMethod::ArrowLz4 => {
163
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "lz4"))
164
+ .await?
165
+ .as_blob()
166
+ },
167
+ ExportMethod::ArrowLz4Selected => {
168
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "lz4"))
169
+ .await?
170
+ .as_blob()
171
+ },
172
+ ExportMethod::ArrowLz4All => {
173
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "lz4"))
174
+ .await?
175
+ .as_blob()
176
+ },
177
+ ExportMethod::ArrowZstd => {
178
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "zstd"))
179
+ .await?
180
+ .as_blob()
181
+ },
182
+ ExportMethod::ArrowZstdSelected => {
183
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "zstd"))
184
+ .await?
185
+ .as_blob()
186
+ },
187
+ ExportMethod::ArrowZstdAll => {
188
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "zstd"))
189
+ .await?
190
+ .as_blob()
191
+ },
155
192
  ExportMethod::Html => html_as_jsvalue(session, renderer, presentation)
156
193
  .await?
157
194
  .as_blob(),
@@ -212,6 +249,36 @@ pub async fn export_method_to_jsvalue(
212
249
  ExportMethod::ArrowAll => crate::queries::arrow_as_jsvalue(session, true, None)
213
250
  .await?
214
251
  .into(),
252
+ ExportMethod::ArrowLz4 => {
253
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "lz4"))
254
+ .await?
255
+ .into()
256
+ },
257
+ ExportMethod::ArrowLz4Selected => {
258
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "lz4"))
259
+ .await?
260
+ .into()
261
+ },
262
+ ExportMethod::ArrowLz4All => {
263
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "lz4"))
264
+ .await?
265
+ .into()
266
+ },
267
+ ExportMethod::ArrowZstd => {
268
+ crate::queries::arrow_as_jsvalue(session, false, compressed(None, "zstd"))
269
+ .await?
270
+ .into()
271
+ },
272
+ ExportMethod::ArrowZstdSelected => {
273
+ crate::queries::arrow_as_jsvalue(session, false, compressed(viewport, "zstd"))
274
+ .await?
275
+ .into()
276
+ },
277
+ ExportMethod::ArrowZstdAll => {
278
+ crate::queries::arrow_as_jsvalue(session, true, compressed(None, "zstd"))
279
+ .await?
280
+ .into()
281
+ },
215
282
  ExportMethod::Html => html_as_jsvalue(session, renderer, presentation).await?,
216
283
  ExportMethod::Plugin if renderer.is_chart() => {
217
284
  png_as_jsvalue(session, renderer).await?.into()
@@ -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::{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};
@@ -76,6 +76,7 @@ fn wire_panel_render_sub(session: &Session, renderer: &Renderer) -> Subscription
76
76
  /// one. `theme` is stripped (element-level, not per-panel) and `client` —
77
77
  /// or the element's default client when `None` — is bound so the config's
78
78
  /// `table` resolves against it.
79
+ #[allow(clippy::too_many_arguments)]
79
80
  pub(crate) async fn create_panel(
80
81
  elem: &HtmlElement,
81
82
  presentation: &Presentation,
@@ -84,6 +85,7 @@ pub(crate) async fn create_panel(
84
85
  id: Option<PanelId>,
85
86
  config: ViewerConfigInitial,
86
87
  client: Option<perspective_client::Client>,
88
+ missing: MissingTable,
87
89
  ) -> ApiResult<PanelId> {
88
90
  let (id, session, renderer, update) = create_panel_model(
89
91
  elem,
@@ -120,6 +122,7 @@ pub(crate) async fn create_panel(
120
122
  RestoreMode::Fresh,
121
123
  update,
122
124
  crate::tasks::RestoreErrors::Publish,
125
+ missing,
123
126
  )
124
127
  .await;
125
128
 
@@ -219,19 +222,19 @@ pub(crate) fn place_reserved(
219
222
  /// renderer (slot-scoped plugin + light-DOM cleanup) and eject its table.
220
223
  /// Shared by the root's `ClosePanel` handler and `restoreWorkspace`'s
221
224
  /// batch replacement of the pre-existing panel set.
222
- pub(crate) fn eject_panel(panel: Panel) -> ApiFuture<()> {
223
- let was_errored = panel.session.is_errored();
224
- let dispose_task = panel.renderer.dispose();
225
- let reset_task = panel.session.reset(ResetOptions {
226
- config: true,
227
- expressions: true,
228
- table: Some(session::TableIntermediateState::Ejected),
229
- ..ResetOptions::default()
230
- });
231
-
225
+ pub(crate) fn eject_panel(panel: Panel, disposal: Disposal) -> ApiFuture<()> {
226
+ panel.session.mark_disposed(disposal);
232
227
  ApiFuture::new(async move {
233
- dispose_task.await?;
234
- match reset_task.await.ignore_view_delete() {
228
+ panel.renderer.dispose().await?;
229
+ let was_errored = panel.session.is_errored();
230
+ let reset = panel.session.reset(ResetOptions {
231
+ config: true,
232
+ expressions: true,
233
+ table: Some(session::TableIntermediateState::Ejected),
234
+ ..ResetOptions::default()
235
+ });
236
+
237
+ match reset.await.ignore_view_delete() {
235
238
  Err(_) if was_errored => Ok(()),
236
239
  Err(e) => Err(e),
237
240
  Ok(_) => Ok(()),
@@ -22,7 +22,7 @@ use yew::prelude::*;
22
22
  use crate::config::{ColumnConfigUpdate, PluginConfigUpdate};
23
23
  use crate::presentation::Presentation;
24
24
  use crate::renderer::{RenderContext, Renderer};
25
- use crate::session::{BindDisposition, Session};
25
+ use crate::session::{BindDisposition, Disposal, Session};
26
26
  use crate::utils::RenderGuard;
27
27
 
28
28
  /// Snapshot → validate → bind → cache + pin the [`RenderContext`]. The core
@@ -227,6 +227,13 @@ pub(crate) async fn locked_run(
227
227
  renderer
228
228
  .clone()
229
229
  .render_task(|guard| async move {
230
+ if let Some(disposal) = session.disposal() {
231
+ return match (disposal, spec.origin) {
232
+ (Disposal::Reject, RunOrigin::Public) => Err(ApiError::new("Panel disposed")),
233
+ _ => Ok(()),
234
+ };
235
+ }
236
+
230
237
  renderer.mount_active_plugin()?;
231
238
  if let Some(task) = spec.task {
232
239
  task.await?;
@@ -251,6 +258,18 @@ pub(crate) async fn locked_run(
251
258
  let view_config_snapshot = session.get_view_config().clone();
252
259
  let plugin_config_changed =
253
260
  renderer.update_plugin_config(&view_config_snapshot, spec.plugin_config)?;
261
+ let plugin_config_snapshot = renderer.get_plugin_config();
262
+ let plugin_update =
263
+ JsValue::from_serde_ext(&plugin_config_snapshot).unwrap_or(JsValue::NULL);
264
+
265
+ let plugin_restored = plugin_config_changed || plugin_swapped;
266
+ if plugin_restored {
267
+ let columns_config = renderer
268
+ .all_columns_configs_materialized(&view_config_snapshot, &session)
269
+ .await;
270
+ plugin.restore(&plugin_update, Some(&columns_config))?;
271
+ }
272
+
254
273
  let columns_config_changed = renderer.update_columns_configs(
255
274
  &view_config_snapshot,
256
275
  &session,
@@ -258,17 +277,16 @@ pub(crate) async fn locked_run(
258
277
  )?;
259
278
 
260
279
  let changed = plugin_config_changed || columns_config_changed;
261
- if changed || plugin_swapped {
262
- let plugin_config_snapshot = renderer.get_plugin_config();
263
- let plugin_update =
264
- JsValue::from_serde_ext(&plugin_config_snapshot).unwrap_or(JsValue::NULL);
280
+ if columns_config_changed {
265
281
  let columns_config = renderer
266
282
  .all_columns_configs_materialized(&view_config_snapshot, &session)
267
283
  .await;
268
284
  plugin.restore(&plugin_update, Some(&columns_config))?;
269
- if plugin_config_changed {
270
- renderer.plugin_config_changed.emit(plugin_config_snapshot);
271
- }
285
+ renderer.columns_config_changed.emit(columns_config);
286
+ }
287
+
288
+ if plugin_config_changed {
289
+ renderer.plugin_config_changed.emit(plugin_config_snapshot);
272
290
  }
273
291
 
274
292
  if spec
@@ -16,7 +16,9 @@ use perspective_client::utils::PerspectiveResultExt;
16
16
  use crate::config::*;
17
17
  use crate::presentation::Presentation;
18
18
  use crate::renderer::Renderer;
19
- use crate::session::{ResetOptions, Session};
19
+ use crate::session::{
20
+ LoadGuard, MissingTable, ResetOptions, Session, TableIntermediateState, probe_table,
21
+ };
20
22
  use crate::tasks::*;
21
23
  use crate::workspace::Workspace;
22
24
  use crate::*;
@@ -38,27 +40,107 @@ pub(crate) enum RestoreErrors {
38
40
  Suppress,
39
41
  }
40
42
 
43
+ /// The client to bind and the opened `Table`, or `None` when pending.
44
+ type Probed = (
45
+ perspective_client::Client,
46
+ Option<perspective_client::Table>,
47
+ );
48
+
49
+ /// Resolve and probe the client hosting `name` without touching the
50
+ /// session's own binding.
51
+ async fn probe(
52
+ session: &Session,
53
+ workspace: &Workspace,
54
+ name: &str,
55
+ missing: MissingTable,
56
+ ) -> ApiResult<Probed> {
57
+ let current = session.get_client();
58
+ let resolved = workspace
59
+ .resolve_client_for_table(name, current.as_ref())
60
+ .await;
61
+
62
+ let client = resolved.or(current).into_apierror()?;
63
+ let table = probe_table(&client, name, missing).await?;
64
+ Ok((client, table))
65
+ }
66
+
67
+ /// Bind an unbound or same-named session to `name`.
41
68
  pub(crate) async fn bind_table_task(
42
69
  session: &Session,
43
70
  workspace: &Workspace,
44
71
  name: String,
72
+ missing: MissingTable,
45
73
  ) -> ApiResult<()> {
46
- let current = session.get_client();
47
- if let Some(client) = workspace
48
- .resolve_client_for_table(&name, current.as_ref())
49
- .await
74
+ if session
75
+ .get_table()
76
+ .is_some_and(|t| t.get_name() == name.as_str())
50
77
  {
51
- session.set_client(client);
78
+ return Ok(());
79
+ }
80
+
81
+ let (client, table) = probe(session, workspace, &name, missing).await?;
82
+ session.set_client(client);
83
+ match table {
84
+ Some(table) => session.bind_table(table).await?,
85
+ None => session.pend_table(name).await?,
86
+ }
87
+
88
+ session.commit_table_defaults();
89
+ Ok(())
90
+ }
91
+
92
+ /// Rebind a session to `name`, probing the incoming table before the
93
+ /// outgoing binding is dropped and replaying `load`'s journal over the
94
+ /// incoming table's defaults.
95
+ async fn rebind_table_task(
96
+ session: &Session,
97
+ renderer: &Renderer,
98
+ workspace: &Workspace,
99
+ name: String,
100
+ missing: MissingTable,
101
+ load: &LoadGuard,
102
+ ) -> ApiResult<()> {
103
+ let probed = probe(session, workspace, &name, missing).await;
104
+ let journal = load.claim();
105
+ let (client, table) = probed?;
106
+ let Some(journal) = journal else {
107
+ return Ok(());
108
+ };
109
+
110
+ session
111
+ .reset(ResetOptions {
112
+ config: true,
113
+ expressions: true,
114
+ stats: true,
115
+ table: Some(TableIntermediateState::Reloaded),
116
+ })
117
+ .await?;
118
+
119
+ session.set_client(client);
120
+ match table {
121
+ Some(table) => session.bind_table(table).await?,
122
+ None => session.pend_table(name).await?,
52
123
  }
53
124
 
54
- session.set_table(name).await?;
55
125
  session.commit_table_defaults();
126
+ for delta in journal {
127
+ session.commit_view_config(delta)?;
128
+ }
129
+
130
+ session.commit_table_defaults();
131
+ if session.get_table().is_none()
132
+ && let Some(plugin) = renderer.active_plugin()
133
+ {
134
+ plugin.clear().await?;
135
+ }
136
+
56
137
  Ok(())
57
138
  }
58
139
 
59
140
  /// Apply a [`ViewerConfigUpdate`] to a single panel and re-draw — the one
60
141
  /// pipeline shared by `restorePanel` (an existing panel), `restoreWorkspace`,
61
142
  /// and `addPanel` (both fresh panels).
143
+ #[allow(clippy::too_many_arguments)]
62
144
  pub(crate) async fn restore_panel(
63
145
  session: &Session,
64
146
  renderer: &Renderer,
@@ -67,6 +149,7 @@ pub(crate) async fn restore_panel(
67
149
  mode: RestoreMode,
68
150
  mut update: ViewerConfigUpdate,
69
151
  errors: RestoreErrors,
152
+ missing: MissingTable,
70
153
  ) -> ApiResult<()> {
71
154
  let active = matches!(mode, RestoreMode::Existing { active: true });
72
155
  let fresh = matches!(mode, RestoreMode::Fresh);
@@ -100,21 +183,24 @@ pub(crate) async fn restore_panel(
100
183
  let rollback =
101
184
  matches!(errors, RestoreErrors::Suppress).then(|| session.get_view_config().clone());
102
185
 
103
- let table_changed = !fresh
104
- && matches!(&update.table, OptionalUpdate::Update(name)
105
- if session.get_table().map(|t| t.get_name() != name.as_str()).unwrap_or(true));
186
+ let binding_before = (
187
+ session.get_table().map(|t| t.get_name().to_owned()),
188
+ session.pending_table(),
189
+ );
106
190
 
107
- let errored_recovery =
108
- session.is_errored() && matches!(&update.table, OptionalUpdate::Update(_));
109
-
110
- let reset = (table_changed || errored_recovery).then(|| {
111
- session.reset(ResetOptions {
112
- config: true,
113
- expressions: true,
114
- stats: true,
115
- ..ResetOptions::default()
116
- })
117
- });
191
+ let load = match &update.table {
192
+ OptionalUpdate::Update(name)
193
+ if session.is_errored()
194
+ || (!fresh
195
+ && session
196
+ .get_table()
197
+ .map(|t| t.get_name() != name.as_str())
198
+ .unwrap_or(true)) =>
199
+ {
200
+ Some(session.begin_pending_load())
201
+ },
202
+ _ => None,
203
+ };
118
204
 
119
205
  let result = restore_and_render(
120
206
  session,
@@ -123,27 +209,40 @@ pub(crate) async fn restore_panel(
123
209
  RunOrigin::Public,
124
210
  update.clone(),
125
211
  {
126
- clone!(session, update.table, workspace);
212
+ clone!(session, renderer, update.table, workspace, load);
127
213
  async move {
128
- if let OptionalUpdate::Update(name) = table {
129
- if let Some(reset) = reset {
130
- reset.await?;
131
- }
214
+ let OptionalUpdate::Update(name) = table else {
215
+ return Ok(());
216
+ };
132
217
 
133
- bind_table_task(&session, &workspace, name).await?;
218
+ match &load {
219
+ Some(load) => {
220
+ rebind_table_task(&session, &renderer, &workspace, name, missing, load)
221
+ .await
222
+ },
223
+ None => bind_table_task(&session, &workspace, name, missing).await,
134
224
  }
135
-
136
- Ok(())
137
225
  }
138
226
  },
139
227
  )
140
228
  .await;
141
229
 
230
+ if let Some(load) = &load {
231
+ load.close();
232
+ }
233
+
142
234
  if let Err(e) = &result {
143
235
  match errors {
144
236
  RestoreErrors::Publish => session.set_error(false, e.clone()).await?,
145
237
  RestoreErrors::Suppress => {
146
- if let Some(config) = rollback {
238
+ let binding_after = (
239
+ session.get_table().map(|t| t.get_name().to_owned()),
240
+ session.pending_table(),
241
+ );
242
+
243
+ if let Some(config) = rollback
244
+ && binding_after == binding_before
245
+ {
147
246
  session.commit_view_config(config.into()).unwrap_or_log();
148
247
  }
149
248
  },
@@ -51,6 +51,9 @@ pub fn send_column_config(
51
51
  renderer
52
52
  .update_lazy(async move { Ok(session.get_view_with_dimensions()) })
53
53
  .await?;
54
+ renderer
55
+ .columns_config_changed
56
+ .emit(columns_configs.clone());
54
57
  renderer.column_style_changed.emit(columns_configs);
55
58
  Ok(())
56
59
  })