@perspective-dev/viewer 5.3.1 → 5.5.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 (291) hide show
  1. package/dist/cdn/perspective-viewer.js +6 -6
  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 -0
  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/extensions.d.ts +0 -5
  31. package/dist/esm/perspective-viewer.d.ts +2 -0
  32. package/dist/esm/perspective-viewer.inline.js +8 -8
  33. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  34. package/dist/esm/perspective-viewer.js +5 -5
  35. package/dist/esm/perspective-viewer.js.map +2 -2
  36. package/dist/esm/plugin.d.ts +9 -0
  37. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  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/ViewOnUpdateResp.d.ts +1 -1
  43. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  44. package/dist/wasm/perspective-viewer.d.ts +137 -104
  45. package/dist/wasm/perspective-viewer.js +337 -269
  46. package/dist/wasm/perspective-viewer.wasm +0 -0
  47. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -30
  48. package/package.json +7 -7
  49. package/src/css/_context_menu.css +1 -1
  50. package/src/css/_dropdown_menu.css +1 -1
  51. package/src/css/_viewer_bundle.css +7 -8
  52. package/src/css/column-dropdown.css +0 -3
  53. package/src/css/column-selector.css +18 -7
  54. package/src/css/column-settings-panel.css +0 -225
  55. package/src/css/column-style.css +175 -0
  56. package/src/css/config-selector.css +19 -11
  57. package/src/css/filter-dropdown.css +5 -5
  58. package/src/css/form/debug.css +37 -4
  59. package/src/css/function-dropdown.css +5 -5
  60. package/src/css/panel-tab.css +23 -0
  61. package/src/css/plugin-selector.css +5 -5
  62. package/src/css/render-warning.css +1 -1
  63. package/src/css/status-bar.css +63 -21
  64. package/src/css/type-icon.css +1 -3
  65. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  66. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  67. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  68. package/src/css/{containers → ui/containers}/tabs.css +2 -1
  69. package/src/css/viewer.css +32 -26
  70. package/src/rust/agent/tools.rs +80 -7
  71. package/src/rust/components/chat_panel.rs +1 -1
  72. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  73. package/src/rust/components/column_dropdown.rs +2 -2
  74. package/src/rust/components/column_selector/active_column.rs +24 -9
  75. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  76. package/src/rust/components/column_selector/config_selector.rs +2 -2
  77. package/src/rust/components/column_selector/filter_column.rs +8 -5
  78. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  79. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  80. package/src/rust/components/column_selector/sort_column.rs +7 -4
  81. package/src/rust/components/column_selector.rs +14 -3
  82. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  83. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  84. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  85. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +16 -18
  86. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  87. package/src/rust/components/column_settings_sidebar/style_tab.rs +70 -12
  88. package/src/rust/components/column_settings_sidebar.rs +3 -4
  89. package/src/rust/components/copy_dropdown.rs +1 -1
  90. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  91. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  92. package/src/rust/components/datetime_column_style.rs +1 -2
  93. package/src/rust/components/{form/debug.rs → debug_panel.rs} +112 -55
  94. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  95. package/src/rust/components/export_dropdown.rs +1 -1
  96. package/src/rust/components/expression_editor.rs +1 -1
  97. package/src/rust/components/filter_dropdown.rs +2 -2
  98. package/src/rust/components/function_dropdown.rs +2 -2
  99. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  100. package/src/rust/components/main_panel/msg.rs +4 -0
  101. package/src/rust/components/main_panel/reconcile.rs +9 -4
  102. package/src/rust/components/main_panel/render.rs +62 -11
  103. package/src/rust/components/main_panel/update.rs +39 -10
  104. package/src/rust/components/main_panel.rs +40 -2
  105. package/src/rust/components/mod.rs +5 -8
  106. package/src/rust/components/new_panel_menu.rs +97 -0
  107. package/src/rust/components/number_series_style.rs +2 -3
  108. package/src/rust/components/panel_menu.rs +61 -87
  109. package/src/rust/components/panel_tab.rs +100 -43
  110. package/src/rust/components/plugin_tab.rs +58 -4
  111. package/src/rust/components/render_warning.rs +2 -2
  112. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  113. package/src/rust/components/settings_panel.rs +8 -2
  114. package/src/rust/components/status_bar.rs +113 -19
  115. package/src/rust/components/status_indicator.rs +9 -14
  116. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  117. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  118. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  119. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  120. package/src/rust/components/viewer/msg.rs +43 -30
  121. package/src/rust/components/viewer/panels.rs +53 -19
  122. package/src/rust/components/viewer/render.rs +16 -13
  123. package/src/rust/components/viewer/settings.rs +137 -68
  124. package/src/rust/components/viewer/snapshots.rs +5 -10
  125. package/src/rust/components/viewer/wiring.rs +11 -22
  126. package/src/rust/components/viewer.rs +15 -10
  127. package/src/rust/components/window_editor.rs +8 -9
  128. package/src/rust/config/column_config_schema.rs +312 -33
  129. package/src/rust/config/datetime_column_style.rs +0 -11
  130. package/src/rust/config/mod.rs +0 -4
  131. package/src/rust/config/options.rs +50 -0
  132. package/src/rust/config/workspace_config.rs +70 -24
  133. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  134. package/src/rust/custom_elements/mod.rs +0 -3
  135. package/src/rust/custom_elements/viewer.rs +194 -115
  136. package/src/rust/js/plugin.rs +3 -87
  137. package/src/rust/lib.rs +19 -18
  138. package/src/rust/presentation/column_locator.rs +3 -7
  139. package/src/rust/presentation.rs +68 -38
  140. package/src/rust/queries/hosted_tables.rs +38 -0
  141. package/src/rust/queries/mod.rs +2 -0
  142. package/src/rust/queries/palette_set.rs +1 -1
  143. package/src/rust/queries/plugin_column_styles.rs +6 -5
  144. package/src/rust/renderer/activate.rs +1 -3
  145. package/src/rust/renderer/dispatch.rs +83 -22
  146. package/src/rust/renderer/limits.rs +24 -5
  147. package/src/rust/renderer/plugin_config.rs +201 -41
  148. package/src/rust/renderer/plugin_store.rs +2 -1
  149. package/src/rust/renderer/props.rs +7 -1
  150. package/src/rust/renderer.rs +23 -27
  151. package/src/rust/session/props.rs +8 -3
  152. package/src/rust/session/view_subscription.rs +19 -6
  153. package/src/rust/session.rs +189 -150
  154. package/src/rust/tasks/create_panel.rs +5 -2
  155. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  156. package/src/rust/tasks/eject.rs +1 -0
  157. package/src/rust/tasks/pipeline.rs +18 -67
  158. package/src/rust/tasks/presize_panels.rs +24 -1
  159. package/src/rust/tasks/restore_and_render.rs +0 -17
  160. package/src/rust/tasks/restore_panel.rs +129 -30
  161. package/src/rust/tasks/send_column_config.rs +4 -1
  162. package/src/rust/tasks/send_plugin_config.rs +1 -1
  163. package/src/rust/tasks/set_edit_mode.rs +1 -1
  164. package/src/rust/tasks/sync_update_panels.rs +147 -67
  165. package/src/rust/tasks/table_lifecycle.rs +7 -1
  166. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  167. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  168. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  169. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  170. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  171. package/src/rust/{components → ui}/containers/select.rs +0 -6
  172. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  173. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  174. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  175. package/src/rust/{components → ui}/context_menu.rs +8 -13
  176. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  177. package/src/rust/{components → ui}/font_loader.rs +0 -26
  178. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  179. package/src/rust/{config/datetime_column_style/color_mode.rs → ui/form/intl_label.rs} +42 -42
  180. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  181. package/src/rust/{components → ui}/form/mod.rs +1 -10
  182. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  183. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  184. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  185. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  186. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  187. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  188. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  189. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  190. package/src/rust/ui/mod.rs +56 -0
  191. package/src/rust/ui/modal.rs +61 -0
  192. package/src/rust/{components → ui}/portal.rs +175 -79
  193. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  194. package/src/rust/utils/completion.rs +0 -6
  195. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  196. package/src/rust/utils/css_vars.rs +13 -0
  197. package/src/rust/utils/local_fonts.rs +395 -0
  198. package/src/rust/utils/mod.rs +4 -0
  199. package/src/rust/workspace.rs +47 -0
  200. package/src/svg/boolean-type.svg +3 -2
  201. package/src/svg/checkbox-checked-icon.svg +4 -7
  202. package/src/svg/checkbox-hover.svg +3 -3
  203. package/src/svg/checkbox-off.svg +2 -3
  204. package/src/svg/checkbox-on.svg +3 -3
  205. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  206. package/src/svg/datagrid-select-column.svg +8 -15
  207. package/src/svg/datagrid-select-region.svg +8 -15
  208. package/src/svg/datagrid-select-row-tree.svg +7 -12
  209. package/src/svg/datagrid-select-row.svg +8 -13
  210. package/src/svg/date-type.svg +4 -23
  211. package/src/svg/drawer-tab-hover.svg +4 -6
  212. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  213. package/src/svg/drawer-tab-invert.svg +3 -5
  214. package/src/svg/drawer-tab.svg +5 -5
  215. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  216. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  217. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  218. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  219. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  220. package/src/svg/new-panel-icon.svg +4 -0
  221. package/src/svg/number-type.svg +3 -3
  222. package/src/svg/radio-hover.svg +3 -3
  223. package/src/svg/radio-off.svg +3 -3
  224. package/src/svg/radio-on.svg +3 -3
  225. package/src/svg/status_ok.svg +3 -26
  226. package/src/svg/status_pending.svg +3 -0
  227. package/src/svg/string-type.svg +4 -8
  228. package/src/svg/theme-icon.svg +5 -5
  229. package/src/themes/blueprint.css +0 -57
  230. package/src/themes/botanical.css +0 -36
  231. package/src/themes/defaults.css +1 -24
  232. package/src/themes/dracula.css +0 -58
  233. package/src/themes/eggplant.css +0 -58
  234. package/src/themes/gruvbox-dark.css +0 -65
  235. package/src/themes/gruvbox.css +1 -39
  236. package/src/themes/icons.css +2 -8
  237. package/src/themes/intl/ar.css +198 -0
  238. package/src/themes/intl/de.css +63 -40
  239. package/src/themes/intl/es.css +58 -35
  240. package/src/themes/intl/fr.css +57 -34
  241. package/src/themes/intl/ja.css +59 -36
  242. package/src/themes/intl/pt.css +54 -31
  243. package/src/themes/intl/zh.css +65 -42
  244. package/src/themes/intl.css +34 -8
  245. package/src/themes/ledger.css +1 -59
  246. package/src/themes/monokai.css +0 -51
  247. package/src/themes/nord.css +0 -55
  248. package/src/themes/phosphor.css +0 -48
  249. package/src/themes/pro-dark.css +0 -37
  250. package/src/themes/pro.css +3 -23
  251. package/src/themes/solarized-dark.css +0 -31
  252. package/src/themes/solarized.css +1 -24
  253. package/src/themes/vaporwave.css +0 -44
  254. package/src/themes/velvet.css +0 -60
  255. package/src/ts/extensions.ts +0 -13
  256. package/src/ts/perspective-viewer.ts +2 -0
  257. package/src/ts/plugin.ts +9 -0
  258. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  259. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  260. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  261. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  262. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  263. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  264. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  265. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  266. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  267. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  268. package/src/css/plugin-settings-panel.css +0 -171
  269. package/src/rust/components/modal.rs +0 -177
  270. package/src/rust/components/string_column_style.rs +0 -121
  271. package/src/rust/config/string_column_style.rs +0 -135
  272. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  273. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  274. package/src/rust/custom_elements/modal.rs +0 -299
  275. package/src/ts/ts-rs/FormatMode.ts +0 -3
  276. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  277. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
  278. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
  279. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
  280. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
  281. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
  282. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
  283. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  284. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  285. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  286. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  287. /package/src/css/{dom → ui/dom}/select.css +0 -0
  288. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  289. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  290. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  291. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -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::{ResetOptions, TableLoadState};
41
+ use crate::session::{MissingTable, ResetOptions, TableLoadState};
42
42
  use crate::tasks::*;
43
43
  use crate::utils::*;
44
44
  use crate::workspace::{Panel, PanelId, Workspace};
@@ -61,11 +61,18 @@ extern "C" {
61
61
  #[wasm_bindgen(typescript_type = "PanelOptions")]
62
62
  pub type JsPanelOptions;
63
63
 
64
- /// `restore()` options dict
65
- /// (`{ panel?: string, suppress_errors?: boolean }`).
64
+ /// The `restore()` options dict.
66
65
  #[wasm_bindgen(typescript_type = "RestoreOptions")]
67
66
  pub type JsRestoreOptions;
68
67
 
68
+ /// The `restoreWorkspace()` options dict.
69
+ #[wasm_bindgen(typescript_type = "RestoreWorkspaceOptions")]
70
+ pub type JsRestoreWorkspaceOptions;
71
+
72
+ /// The `addPanel()` options dict.
73
+ #[wasm_bindgen(typescript_type = "AddPanelOptions")]
74
+ pub type JsAddPanelOptions;
75
+
69
76
  /// `addPanel()` argument: a new panel's initial config — `table`
70
77
  /// REQUIRED.
71
78
  #[wasm_bindgen(typescript_type = "ViewerConfigInitial")]
@@ -84,9 +91,9 @@ extern "C" {
84
91
  #[wasm_bindgen(typescript_type = "GetClientOptions")]
85
92
  pub type JsGetClientOptions;
86
93
 
87
- /// `restoreWorkspace()` argument: a workspace config update.
88
- #[wasm_bindgen(typescript_type = "WorkspaceConfigUpdate")]
89
- pub type JsWorkspaceConfigUpdate;
94
+ /// `getView` options dict (`{ mode?: GetViewMode, panel?: string }`).
95
+ #[wasm_bindgen(typescript_type = "GetViewOptions")]
96
+ pub type JsGetViewOptions;
90
97
 
91
98
  /// `saveWorkspace()` options dict (`{ full_palette?: boolean }`).
92
99
  #[wasm_bindgen(typescript_type = "SaveWorkspaceOptions")]
@@ -121,14 +128,15 @@ struct ResizeDimensions {
121
128
  /// falling back to `Default` on absence or a malformed argument (matching the
122
129
  /// `ResizeOptions` precedent — an options bag is a best-effort convenience,
123
130
  /// not a hard-validated payload).
124
- fn parse_options<T, U>(options: Option<T>) -> U
131
+ fn parse_options<T, U>(options: Option<T>) -> ApiResult<U>
125
132
  where
126
133
  T: Into<JsValue>,
127
134
  U: Default + for<'a> serde::Deserialize<'a>,
128
135
  {
129
- options
130
- .and_then(|o| o.into_serde_ext().ok())
131
- .unwrap_or_default()
136
+ match options {
137
+ None => Ok(U::default()),
138
+ Some(x) => Ok(x.into_serde_ext()?),
139
+ }
132
140
  }
133
141
 
134
142
  /// The `<perspective-viewer>` custom element.
@@ -196,6 +204,28 @@ impl PerspectiveViewerElement {
196
204
  })
197
205
  }
198
206
 
207
+ /// Re-stamp the global-filter overlay on every panel not in `fresh` and
208
+ /// re-render those whose overlay changed.
209
+ async fn rerender_retained_overlays(&self, fresh: &[PanelId]) -> ApiResult<()> {
210
+ let retained = self
211
+ .workspace
212
+ .panels()
213
+ .into_iter()
214
+ .filter(|panel| !fresh.contains(&panel.id))
215
+ .filter(|panel| stamp_global_overlay(&self.workspace, &panel.id, &panel.session))
216
+ .collect::<Vec<_>>();
217
+
218
+ join_all(retained.iter().map(|panel| async move {
219
+ apply_and_render(&panel.session, &panel.renderer, ViewConfigUpdate::default())?.await?;
220
+ Ok(())
221
+ }))
222
+ .await
223
+ .into_iter()
224
+ .collect::<ApiResult<Vec<_>>>()?;
225
+
226
+ Ok(())
227
+ }
228
+
199
229
  fn layout_element(&self) -> Option<RegularLayout> {
200
230
  self.elem
201
231
  .shadow_root()?
@@ -512,31 +542,13 @@ impl PerspectiveViewerElement {
512
542
  .expect("just-reserved panel is present")
513
543
  });
514
544
 
515
- // Carrying `Some(notify)` marks this load as the reservation's
516
- // owner — the only call that may place or discard it below.
517
545
  (panel, Some(self.layout_changed_notify()))
518
546
  },
519
547
  };
520
548
 
521
- // A `Table` payload targets this panel's engines; a `Client` registers
522
- // inertly against it. Selecting the panel here (not at construction)
523
- // keeps the registry race safe — by `load()` time real plugins have
524
- // registered.
525
549
  let session = panel.session;
526
550
  let renderer = panel.renderer;
527
-
528
- // Open the pending-load window SYNCHRONOUSLY, at the call site — this
529
- // is what fixes the ordering. The payload's RESET disposition (a
530
- // `Table` resets the view; a `Client` does not) is unknown until the
531
- // promise resolves, but the window's POSITION on the config-commit
532
- // stream is fixed NOW. A `restore()` a caller fires immediately after
533
- // this unawaited `load()` (the React prop-binding pattern, which has
534
- // no async ordering guarantees) commits INTO this window's journal and
535
- // is replayed over the reset base if the payload proves to be a
536
- // `Table` — so a moved-async reset can no longer clobber a later
537
- // commit. See `SESSION_CONFIG_COHERENCE_PLAN.md`.
538
551
  let generation = session.begin_pending_load();
539
-
540
552
  clone!(self.workspace, self.presentation);
541
553
  Ok(ApiFuture::new_throttled(async move {
542
554
  let _effect = effect;
@@ -734,7 +746,11 @@ impl PerspectiveViewerElement {
734
746
  /// await viewer.eject({client: "remote"});
735
747
  /// ```
736
748
  pub fn eject(&mut self, options: Option<JsClientOptions>) -> ApiFuture<()> {
737
- let ClientOptions { client } = parse_options(options);
749
+ let ClientOptions { client } = match parse_options(options) {
750
+ Ok(x) => x,
751
+ Err(x) => return ApiFuture::new_err(x),
752
+ };
753
+
738
754
  // Default target: the active panel's client, or — when the active panel
739
755
  // is unbound (`load(Client)` is now inert) — the default client.
740
756
  let Some(target) = client
@@ -770,32 +786,59 @@ impl PerspectiveViewerElement {
770
786
  eject_client_panels(&self.workspace, &self.root, target, ids)
771
787
  }
772
788
 
773
- /// Get the underlying [`View`] for this viewer.
789
+ /// Get a [`View`] for a panel of this viewer, as currently configured by
790
+ /// the user.
774
791
  ///
775
- /// Use this method to get promgrammatic access to the [`View`] as currently
776
- /// configured by the user, for e.g. serializing as an
777
- /// [Apache Arrow](https://arrow.apache.org/) before passing to another
778
- /// library.
792
+ /// # Arguments
779
793
  ///
780
- /// The [`View`] returned by this method is owned by the
781
- /// [`PerspectiveViewerElement`] and may be _invalidated_ by
782
- /// [`View::delete`] at any time. Plugins which rely on this [`View`] for
783
- /// their [`HTMLPerspectiveViewerPluginElement::draw`] implementations
784
- /// should treat this condition as a _cancellation_ by silently aborting on
785
- /// "View already deleted" errors from method calls.
794
+ /// - `mode` - which [`View`] to return:
795
+ /// - `"live"` (default) - the panel's own [`View`], which the viewer
796
+ /// replaces on every config change and deletes when auto-paused or
797
+ /// [`PerspectiveViewerElement::delete`]d, so calls on it may fail with
798
+ /// "View already deleted", it must never be deleted by the caller, and
799
+ /// it rejects with `No View for panel "<name>"` while none is bound
800
+ /// (auto-paused or not yet rendered).
801
+ /// - `"clone"` - a new [`View`] built from the panel's effective config
802
+ /// (global filter included), independent of the render lifecycle, which
803
+ /// the caller owns and must [`View::delete`].
804
+ /// - `"auto"` - `"live"` when the panel has a bound [`View`], else
805
+ /// `"clone"`.
806
+ /// - `panel` - the target panel, or the active panel when omitted.
807
+ ///
808
+ /// Plugins which rely on the live [`View`] for their
809
+ /// [`HTMLPerspectiveViewerPluginElement::draw`] implementations should
810
+ /// treat "View already deleted" as a _cancellation_ by silently aborting.
786
811
  ///
787
812
  /// # JavaScript Examples
788
813
  ///
789
814
  /// ```javascript
790
815
  /// const view = await viewer.getView();
791
816
  /// ```
817
+ ///
818
+ /// Export every panel without borrowing the viewer's own [`View`]:
819
+ ///
820
+ /// ```javascript
821
+ /// for (const panel of viewer.getPanelNames()) {
822
+ /// const view = await viewer.getView({ panel, mode: "clone" });
823
+ /// const arrow = await view.to_arrow();
824
+ /// await view.delete();
825
+ /// }
826
+ /// ```
792
827
  #[wasm_bindgen]
793
- pub fn getView(&self, options: Option<JsPanelOptions>) -> ApiFuture<View> {
794
- let PanelOptions { panel: name } = parse_options(options);
828
+ pub fn getView(&self, options: Option<JsGetViewOptions>) -> ApiFuture<View> {
795
829
  let this = self.clone();
796
830
  ApiFuture::new(async move {
831
+ let GetViewOptions { mode, panel: name } = parse_options(options)?;
797
832
  let panel = this.resolve_panel(name)?;
798
- Ok(panel.session.get_view().ok_or("No table set")?.into())
833
+ match (mode.unwrap_or_default(), panel.session.get_view()) {
834
+ (GetViewMode::Live | GetViewMode::Auto, Some(view)) => Ok(view.into()),
835
+ (GetViewMode::Live, None) => {
836
+ Err(format!("No View for panel \"{}\"", panel.id.as_str()).into())
837
+ },
838
+ (GetViewMode::Clone, _) | (GetViewMode::Auto, None) => {
839
+ Ok(panel.session.create_detached_view().await?.into())
840
+ },
841
+ }
799
842
  })
800
843
  }
801
844
 
@@ -806,9 +849,9 @@ impl PerspectiveViewerElement {
806
849
  /// [`View::get_config`])
807
850
  #[wasm_bindgen]
808
851
  pub fn getViewConfig(&self, options: Option<JsPanelOptions>) -> ApiFuture<JsViewConfig> {
809
- let PanelOptions { panel: name } = parse_options(options);
810
852
  let this = self.clone();
811
853
  ApiFuture::new(async move {
854
+ let PanelOptions { panel: name } = parse_options(options)?;
812
855
  let panel = this.resolve_panel(name)?;
813
856
  let config = if let Some(ctx) = panel.renderer.render_context() {
814
857
  (*ctx.view_config).clone()
@@ -839,12 +882,12 @@ impl PerspectiveViewerElement {
839
882
  /// ```
840
883
  #[wasm_bindgen]
841
884
  pub fn getTable(&self, options: Option<JsGetTableOptions>) -> ApiFuture<Table> {
842
- let GetTableOptions {
843
- wait: wait_for_table,
844
- panel: name,
845
- } = parse_options(options);
846
885
  let this = self.clone();
847
886
  ApiFuture::new(async move {
887
+ let GetTableOptions {
888
+ wait: wait_for_table,
889
+ panel: name,
890
+ } = parse_options(options)?;
848
891
  let panel = this.resolve_panel(name)?;
849
892
  if !wait_for_table.unwrap_or_default()
850
893
  && let Some(ctx) = panel.renderer.render_context()
@@ -884,12 +927,12 @@ impl PerspectiveViewerElement {
884
927
  &self,
885
928
  options: Option<JsGetClientOptions>,
886
929
  ) -> ApiFuture<perspective_js::Client> {
887
- let GetClientOptions {
888
- wait: wait_for_client,
889
- panel: name,
890
- } = parse_options(options);
891
930
  let this = self.clone();
892
931
  ApiFuture::new(async move {
932
+ let GetClientOptions {
933
+ wait: wait_for_client,
934
+ panel: name,
935
+ } = parse_options(options)?;
893
936
  let panel = this.resolve_panel(name)?;
894
937
  if !wait_for_client.unwrap_or_default()
895
938
  && let Some(ctx) = panel.renderer.render_context()
@@ -920,7 +963,7 @@ impl PerspectiveViewerElement {
920
963
  /// ```
921
964
  #[wasm_bindgen]
922
965
  pub fn getRenderStats(&self, options: Option<JsPanelOptions>) -> ApiResult<JsValue> {
923
- let PanelOptions { panel: name } = parse_options(options);
966
+ let PanelOptions { panel: name } = parse_options(options)?;
924
967
  let panel = self.resolve_panel(name)?;
925
968
  Ok(JsValue::from_serde_ext(
926
969
  &panel.renderer.render_timer().get_stats(),
@@ -1018,12 +1061,13 @@ impl PerspectiveViewerElement {
1018
1061
  /// - `options.panel` - The panel to target, or the active panel when
1019
1062
  /// omitted.
1020
1063
  /// - `options.suppress_errors` - when `true`, a failed restore only rejects
1021
- /// the returned `Promise`; the error is NOT committed to the viewer's
1022
- /// visible error state and the session remains usable. The view config is
1023
- /// rolled back to its pre-call value, so a rejected patch cannot re-merge
1024
- /// into a later restore. Element-level state the call already applied
1025
- /// (theme, title, a plugin swap) is NOT undone — restore a known-good
1026
- /// config to recover those exactly.
1064
+ /// the returned `Promise` and rolls the view config back, leaving the
1065
+ /// viewer's visible error state and any element-level state the call
1066
+ /// already applied (theme, title, plugin, a successful `table` change)
1067
+ /// untouched.
1068
+ /// - `options.wait_for_table` - when `true`, a `table` no loaded client
1069
+ /// hosts yet leaves the panel empty and pending until the table is
1070
+ /// created, instead of the default error.
1027
1071
  ///
1028
1072
  /// # JavaScript Examples
1029
1073
  ///
@@ -1045,20 +1089,23 @@ impl PerspectiveViewerElement {
1045
1089
  update: JsViewerConfigUpdate,
1046
1090
  options: Option<JsRestoreOptions>,
1047
1091
  ) -> JsVoidPromise {
1048
- let RestoreOptions {
1049
- panel: name,
1050
- suppress_errors,
1051
- } = parse_options(options);
1052
-
1053
- let errors = if suppress_errors.unwrap_or_default() {
1054
- RestoreErrors::Suppress
1055
- } else {
1056
- RestoreErrors::Publish
1057
- };
1058
-
1059
1092
  let effect = self.workspace.effects().guard();
1060
1093
  let this = self.clone();
1061
1094
  let fut = ApiFuture::new_throttled(async move {
1095
+ let RestoreOptions {
1096
+ panel: name,
1097
+ suppress_errors,
1098
+ wait_for_table,
1099
+ } = parse_options(options)?;
1100
+
1101
+ let errors = if suppress_errors.unwrap_or_default() {
1102
+ RestoreErrors::Suppress
1103
+ } else {
1104
+ RestoreErrors::Publish
1105
+ };
1106
+
1107
+ let missing = MissingTable::from_wait(wait_for_table);
1108
+
1062
1109
  let _effect = effect;
1063
1110
  let id = name.map(PanelId::from);
1064
1111
  let mut update = ViewerConfigUpdate::decode(&update)?;
@@ -1092,19 +1139,6 @@ impl PerspectiveViewerElement {
1092
1139
  };
1093
1140
 
1094
1141
  if !matches!(settings, OptionalUpdate::Missing) {
1095
- // Through `ToggleSettingsInit` — the SAME full choreography
1096
- // the toolbar toggle drives (presize every visible plugin
1097
- // to its post-toggle box, then the exactness-finalizer
1098
- // resize) — NOT the bare `ToggleSettingsComplete` leaf,
1099
- // which only re-renders the pane. The pane is the outer
1100
- // `SplitPanel` (no `before-resize` event) and the host box
1101
- // is unchanged, so a leaf-only toggle left every canvas
1102
- // plugin CSS-stretched at its old backing size.
1103
- //
1104
- // No `set_settings_before_open` here: `is_settings_open` is
1105
- // Init's toggle-vs-no-op DISPATCH state, so pre-writing the
1106
- // target makes every call resolve as a no-op. Init owns the
1107
- // write, as it does for the toolbar and `toggleConfig`.
1108
1142
  let (sender, receiver) = channel::<ApiResult<JsValue>>();
1109
1143
  this.root.borrow().as_ref().into_apierror()?.send_message(
1110
1144
  PerspectiveViewerMsg::ToggleSettingsInit(Some(settings), false, Some(sender)),
@@ -1123,6 +1157,7 @@ impl PerspectiveViewerElement {
1123
1157
  RestoreMode::Existing { active },
1124
1158
  update,
1125
1159
  errors,
1160
+ missing,
1126
1161
  )
1127
1162
  .await
1128
1163
  },
@@ -1135,6 +1170,7 @@ impl PerspectiveViewerElement {
1135
1170
  RestoreMode::Existing { active: true },
1136
1171
  update,
1137
1172
  errors,
1173
+ missing,
1138
1174
  )
1139
1175
  .await
1140
1176
  },
@@ -1147,6 +1183,7 @@ impl PerspectiveViewerElement {
1147
1183
  id,
1148
1184
  *config,
1149
1185
  None,
1186
+ missing,
1150
1187
  )
1151
1188
  .await?;
1152
1189
  Ok(())
@@ -1157,25 +1194,47 @@ impl PerspectiveViewerElement {
1157
1194
  js_sys::Promise::from(fut).unchecked_into()
1158
1195
  }
1159
1196
 
1160
- /// Restore the ENTIRE element from a [`WorkspaceConfigUpdate`]
1161
- /// (`{version, active?, layout, panels, ...}`) —
1162
- /// the multi-panel counterpart of [`Self::restore`]. Every existing panel
1163
- /// is replaced by the `panels` entries, and the layout tree + master/detail
1164
- /// cross-filter state re-applied. Unlike [`Self::restore`], this never
1165
- /// falls back to the single-panel path.
1197
+ /// Apply a [`WorkspaceConfigUpdate`] to the element — the multi-panel
1198
+ /// counterpart of [`Self::restore`] and like it a field-wise update, where
1199
+ /// an absent field is unchanged, `null` resets and a value replaces.
1200
+ ///
1201
+ /// # Arguments
1202
+ ///
1203
+ /// - `update` - The workspace config update: a [`Self::saveWorkspace`]
1204
+ /// token replaces every panel (`panels` is the whole set), while a token
1205
+ /// without `panels` keeps them and `layout`, `active` and `masters` name
1206
+ /// them by their existing ids.
1207
+ /// - `options.wait_for_table` - when `true`, a panel whose `table` no
1208
+ /// loaded client hosts yet is created empty and pending until the table
1209
+ /// is created, instead of the default error.
1166
1210
  ///
1167
1211
  /// # JavaScript Examples
1168
1212
  ///
1169
1213
  /// ```javascript
1170
1214
  /// await viewer.restoreWorkspace(await otherViewer.saveWorkspace());
1171
1215
  /// ```
1172
- pub fn restoreWorkspace(&self, update: JsWorkspaceConfigUpdate) -> JsVoidPromise {
1173
- let update: JsViewerConfigUpdate = update.unchecked_into();
1216
+ ///
1217
+ /// Cross-filter the current panels without re-creating them:
1218
+ ///
1219
+ /// ```javascript
1220
+ /// await viewer.restoreWorkspace({ global_filters: [["Region", "==", "West"]] });
1221
+ /// ```
1222
+ pub fn restoreWorkspace(
1223
+ &self,
1224
+ update: JsWorkspaceConfigUpdate,
1225
+ options: Option<JsRestoreWorkspaceOptions>,
1226
+ ) -> JsVoidPromise {
1174
1227
  let effect = self.workspace.effects().guard();
1175
1228
  let this = self.clone();
1176
1229
  let fut = ApiFuture::new(async move {
1177
1230
  let _effect = effect;
1231
+ let RestoreWorkspaceOptions { wait_for_table } = parse_options(options)?;
1232
+ let missing = MissingTable::from_wait(wait_for_table);
1178
1233
  let (contents, eject_tasks) = sync_update_panels(&this, update)?;
1234
+ let fresh = contents
1235
+ .iter()
1236
+ .map(|(id, ..)| id.clone())
1237
+ .collect::<Vec<_>>();
1179
1238
  let results = join_all(contents.into_iter().map(|(id, session, renderer, config)| {
1180
1239
  let presentation = this.presentation.clone();
1181
1240
  let workspace = this.workspace.clone();
@@ -1189,6 +1248,7 @@ impl PerspectiveViewerElement {
1189
1248
  RestoreMode::Fresh,
1190
1249
  config,
1191
1250
  crate::tasks::RestoreErrors::Publish,
1251
+ missing,
1192
1252
  )
1193
1253
  .await?;
1194
1254
  if workspace.is_master(&id) {
@@ -1201,6 +1261,7 @@ impl PerspectiveViewerElement {
1201
1261
  .await;
1202
1262
 
1203
1263
  results.into_iter().collect::<ApiResult<Vec<_>>>()?;
1264
+ this.rerender_retained_overlays(&fresh).await?;
1204
1265
  join_all(eject_tasks)
1205
1266
  .await
1206
1267
  .into_iter()
@@ -1264,9 +1325,9 @@ impl PerspectiveViewerElement {
1264
1325
  /// });
1265
1326
  /// ```
1266
1327
  pub fn save(&self, options: Option<JsPanelOptions>) -> JsViewerConfigPromise {
1267
- let PanelOptions { panel: name } = parse_options(options);
1268
1328
  let this = self.clone();
1269
1329
  let fut = ApiFuture::new(async move {
1330
+ let PanelOptions { panel: name } = parse_options(options)?;
1270
1331
  this.workspace.effects().settle().await;
1271
1332
  let panel = this.resolve_panel(name)?;
1272
1333
  let viewer_config = panel
@@ -1300,9 +1361,12 @@ impl PerspectiveViewerElement {
1300
1361
  &self,
1301
1362
  options: Option<JsSaveWorkspaceOptions>,
1302
1363
  ) -> JsWorkspaceConfigPromise {
1303
- let SaveWorkspaceOptions { full_palette } = parse_options(options);
1304
1364
  let this = self.clone();
1305
- let fut = ApiFuture::new(Self::workspace_config(this, full_palette.unwrap_or(false)));
1365
+ let fut = ApiFuture::new(async move {
1366
+ let SaveWorkspaceOptions { full_palette } = parse_options(options)?;
1367
+ Self::workspace_config(this, full_palette.unwrap_or(false)).await
1368
+ });
1369
+
1306
1370
  js_sys::Promise::from(fut).unchecked_into()
1307
1371
  }
1308
1372
 
@@ -1321,13 +1385,13 @@ impl PerspectiveViewerElement {
1321
1385
  /// })
1322
1386
  /// ```
1323
1387
  pub fn download(&self, options: Option<JsExportOptions>) -> ApiFuture<()> {
1324
- let ExportOptions {
1325
- method,
1326
- panel: name,
1327
- } = parse_options(options);
1328
- let method = method.map(|m| JsString::from(m.as_str()));
1329
1388
  let this = self.clone();
1330
1389
  ApiFuture::new_throttled(async move {
1390
+ let ExportOptions {
1391
+ method,
1392
+ panel: name,
1393
+ } = parse_options(options)?;
1394
+ let method = method.map(|m| JsString::from(m.as_str()));
1331
1395
  let method = if let Some(method) = method
1332
1396
  .map(|x| x.unchecked_into())
1333
1397
  .map(serde_wasm_bindgen::from_value)
@@ -1368,13 +1432,14 @@ impl PerspectiveViewerElement {
1368
1432
  /// const data = await viewer.export("plugin");
1369
1433
  /// ```
1370
1434
  pub fn export(&self, options: Option<JsExportOptions>) -> ApiFuture<JsValue> {
1371
- let ExportOptions {
1372
- method,
1373
- panel: name,
1374
- } = parse_options(options);
1375
- let method = method.map(|m| JsString::from(m.as_str()));
1376
1435
  let this = self.clone();
1377
1436
  ApiFuture::new(async move {
1437
+ let ExportOptions {
1438
+ method,
1439
+ panel: name,
1440
+ } = parse_options(options)?;
1441
+
1442
+ let method = method.map(|m| JsString::from(m.as_str()));
1378
1443
  let method = if let Some(method) = method
1379
1444
  .map(|x| x.unchecked_into())
1380
1445
  .map(serde_wasm_bindgen::from_value)
@@ -1406,13 +1471,14 @@ impl PerspectiveViewerElement {
1406
1471
  /// })
1407
1472
  /// ```
1408
1473
  pub fn copy(&self, options: Option<JsExportOptions>) -> ApiFuture<()> {
1409
- let ExportOptions {
1410
- method,
1411
- panel: name,
1412
- } = parse_options(options);
1413
- let method = method.map(|m| JsString::from(m.as_str()));
1414
1474
  let this = self.clone();
1415
1475
  ApiFuture::new_throttled(async move {
1476
+ let ExportOptions {
1477
+ method,
1478
+ panel: name,
1479
+ } = parse_options(options)?;
1480
+
1481
+ let method = method.map(|m| JsString::from(m.as_str()));
1416
1482
  let method = if let Some(method) = method
1417
1483
  .map(|x| x.unchecked_into())
1418
1484
  .map(serde_wasm_bindgen::from_value)
@@ -1451,11 +1517,11 @@ impl PerspectiveViewerElement {
1451
1517
  /// await viewer.reset(true, {panel: "p1"}); // just "p1", + expressions
1452
1518
  /// ```
1453
1519
  pub fn reset(&self, reset_all: Option<bool>, options: Option<JsPanelOptions>) -> ApiFuture<()> {
1454
- let PanelOptions { panel: name } = parse_options(options);
1455
1520
  let effect = self.workspace.effects().guard();
1456
1521
  let this = self.clone();
1457
1522
  let all = reset_all.unwrap_or_default();
1458
1523
  ApiFuture::new_throttled(async move {
1524
+ let PanelOptions { panel: name } = parse_options(options)?;
1459
1525
  let _effect = effect;
1460
1526
  let (completion, receiver) = Completion::new();
1461
1527
  {
@@ -1633,7 +1699,7 @@ impl PerspectiveViewerElement {
1633
1699
  /// region of the named panel, or the active panel when `panel` is omitted.
1634
1700
  #[wasm_bindgen]
1635
1701
  pub fn getSelection(&self, options: Option<JsPanelOptions>) -> ApiResult<Option<JsViewWindow>> {
1636
- let PanelOptions { panel: name } = parse_options(options);
1702
+ let PanelOptions { panel: name } = parse_options(options)?;
1637
1703
  let panel = self.resolve_panel(name)?;
1638
1704
  Ok(panel.renderer.get_selection().map(|x| x.into()))
1639
1705
  }
@@ -1646,7 +1712,7 @@ impl PerspectiveViewerElement {
1646
1712
  window: Option<JsViewWindow>,
1647
1713
  options: Option<JsPanelOptions>,
1648
1714
  ) -> ApiResult<()> {
1649
- let PanelOptions { panel: name } = parse_options(options);
1715
+ let PanelOptions { panel: name } = parse_options(options)?;
1650
1716
  let window = window.map(|x| x.into_serde_ext()).transpose()?;
1651
1717
  self.resolve_panel(name)?.renderer.set_selection(window);
1652
1718
  Ok(())
@@ -1657,7 +1723,7 @@ impl PerspectiveViewerElement {
1657
1723
  /// `panel` is omitted.
1658
1724
  #[wasm_bindgen]
1659
1725
  pub fn getEditPort(&self, options: Option<JsPanelOptions>) -> ApiResult<f64> {
1660
- let PanelOptions { panel: name } = parse_options(options);
1726
+ let PanelOptions { panel: name } = parse_options(options)?;
1661
1727
  let panel = self.resolve_panel(name)?;
1662
1728
  let edit_port = if let Some(ctx) = panel.renderer.render_context() {
1663
1729
  ctx.edit_port
@@ -1873,13 +1939,25 @@ impl PerspectiveViewerElement {
1873
1939
  ///
1874
1940
  /// The element-level `settings` field does not exist on the argument
1875
1941
  /// type (it is shared across the element, not per-panel).
1942
+ ///
1943
+ /// # Arguments
1944
+ ///
1945
+ /// - `config` - The new panel's initial config, whose `table` is required.
1946
+ /// - `options.wait_for_table` - when `true`, a `table` no loaded client
1947
+ /// hosts yet creates the panel empty and pending until the table is
1948
+ /// created, instead of the default rejection.
1876
1949
  #[wasm_bindgen]
1877
- pub fn addPanel(&self, config: JsViewerConfigInitial) -> ApiFuture<JsValue> {
1950
+ pub fn addPanel(
1951
+ &self,
1952
+ config: JsViewerConfigInitial,
1953
+ options: Option<JsAddPanelOptions>,
1954
+ ) -> ApiFuture<JsValue> {
1878
1955
  clone!(self.elem, self.presentation, self.workspace);
1879
1956
  let effect = workspace.effects().guard();
1880
1957
  let notify = self.layout_changed_notify();
1881
1958
  ApiFuture::new(async move {
1882
1959
  let _effect = effect;
1960
+ let AddPanelOptions { wait_for_table } = parse_options(options)?;
1883
1961
  let config = ViewerConfigInitial::decode(&config)?;
1884
1962
  let id = create_panel(
1885
1963
  &elem,
@@ -1889,6 +1967,7 @@ impl PerspectiveViewerElement {
1889
1967
  None,
1890
1968
  config,
1891
1969
  None,
1970
+ MissingTable::from_wait(wait_for_table),
1892
1971
  )
1893
1972
  .await?;
1894
1973
  Ok(JsValue::from_str(id.as_str()))
@@ -1978,10 +2057,10 @@ impl PerspectiveViewerElement {
1978
2057
  column_name: String,
1979
2058
  options: Option<JsPanelOptions>,
1980
2059
  ) -> ApiFuture<()> {
1981
- let PanelOptions { panel: name } = parse_options(options);
1982
2060
  let effect = self.workspace.effects().guard();
1983
2061
  let this = self.clone();
1984
2062
  ApiFuture::new_throttled(async move {
2063
+ let PanelOptions { panel: name } = parse_options(options)?;
1985
2064
  let _effect = effect;
1986
2065
  let panel = this.resolve_panel(name)?;
1987
2066
  let was_active = this.workspace.active_id().as_ref() == Some(&panel.id);