@perspective-dev/viewer 5.4.0 → 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.
- package/dist/cdn/perspective-viewer.js +4 -4
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/ar.css +1 -1
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/perspective-viewer.d.ts +2 -0
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +3 -3
- package/dist/esm/perspective-viewer.js +4 -4
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +9 -0
- package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
- package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
- package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
- package/dist/wasm/perspective-viewer.d.ts +91 -47
- package/dist/wasm/perspective-viewer.js +216 -136
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -18
- package/package.json +5 -5
- package/src/css/column-selector.css +11 -3
- package/src/css/column-style.css +162 -0
- package/src/css/config-selector.css +1 -1
- package/src/css/form/debug.css +9 -0
- package/src/css/status-bar.css +11 -0
- package/src/css/ui/containers/tabs.css +2 -1
- package/src/rust/agent/tools.rs +80 -7
- package/src/rust/components/column_selector/active_column.rs +9 -0
- package/src/rust/components/column_selector.rs +11 -0
- package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
- package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +68 -9
- package/src/rust/components/debug_panel.rs +107 -50
- package/src/rust/components/main_panel/msg.rs +4 -0
- package/src/rust/components/main_panel.rs +14 -0
- package/src/rust/components/mod.rs +0 -1
- package/src/rust/components/plugin_tab.rs +57 -3
- package/src/rust/components/settings_panel.rs +6 -0
- package/src/rust/components/status_indicator.rs +4 -0
- package/src/rust/components/viewer/panels.rs +3 -0
- package/src/rust/components/viewer/render.rs +1 -0
- package/src/rust/components/viewer/wiring.rs +10 -1
- package/src/rust/config/column_config_schema.rs +312 -8
- package/src/rust/config/datetime_column_style.rs +0 -11
- package/src/rust/config/mod.rs +0 -2
- package/src/rust/config/options.rs +50 -0
- package/src/rust/config/workspace_config.rs +69 -24
- package/src/rust/custom_elements/viewer.rs +194 -97
- package/src/rust/js/plugin.rs +3 -1
- package/src/rust/lib.rs +7 -3
- package/src/rust/queries/plugin_column_styles.rs +6 -5
- package/src/rust/renderer/plugin_config.rs +200 -41
- package/src/rust/renderer/plugin_store.rs +2 -1
- package/src/rust/renderer/props.rs +5 -0
- package/src/rust/renderer.rs +9 -0
- package/src/rust/session/props.rs +4 -0
- package/src/rust/session.rs +151 -80
- package/src/rust/tasks/create_panel.rs +4 -1
- package/src/rust/tasks/pipeline.rs +18 -7
- package/src/rust/tasks/restore_panel.rs +129 -30
- package/src/rust/tasks/send_column_config.rs +3 -0
- package/src/rust/tasks/sync_update_panels.rs +147 -67
- package/src/rust/tasks/table_lifecycle.rs +7 -1
- package/src/rust/ui/containers/tab_list.rs +1 -1
- package/src/rust/ui/form/intl_label.rs +4 -23
- package/src/rust/ui/portal.rs +1 -0
- package/src/rust/utils/css_vars.rs +13 -0
- package/src/rust/utils/local_fonts.rs +395 -0
- package/src/rust/utils/mod.rs +2 -0
- package/src/rust/workspace.rs +12 -0
- package/src/svg/status_pending.svg +3 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/ar.css +20 -7
- package/src/themes/intl/de.css +20 -7
- package/src/themes/intl/es.css +20 -7
- package/src/themes/intl/fr.css +20 -7
- package/src/themes/intl/ja.css +20 -7
- package/src/themes/intl/pt.css +20 -7
- package/src/themes/intl/zh.css +20 -7
- package/src/themes/intl.css +20 -7
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/plugin.ts +9 -0
- package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
- package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
- package/src/ts/ts-rs/GetViewOptions.ts +7 -0
- package/src/ts/ts-rs/RestoreOptions.ts +7 -1
- package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/src/rust/components/string_column_style.rs +0 -120
- package/src/rust/config/datetime_column_style/color_mode.rs +0 -62
- package/src/rust/config/string_column_style.rs +0 -135
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
package/src/rust/session.rs
CHANGED
|
@@ -152,6 +152,51 @@ impl Deref for SessionHandle {
|
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
+
/// What an un-hosted `table` name means to a bind, per the `wait_for_table`
|
|
156
|
+
/// option.
|
|
157
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
158
|
+
pub enum MissingTable {
|
|
159
|
+
/// A hard error that leaves the panel as it was.
|
|
160
|
+
Error,
|
|
161
|
+
|
|
162
|
+
/// The panel waits unbound and binds when the name is hosted.
|
|
163
|
+
Pend,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
impl MissingTable {
|
|
167
|
+
/// Decode the `wait_for_table` option (absent = `false` = `Error`).
|
|
168
|
+
pub fn from_wait(wait_for_table: Option<bool>) -> Self {
|
|
169
|
+
if wait_for_table.unwrap_or_default() {
|
|
170
|
+
Self::Pend
|
|
171
|
+
} else {
|
|
172
|
+
Self::Error
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Open `name` on `client`, returning `Ok(None)` when the name is not hosted
|
|
178
|
+
/// and `missing` is [`MissingTable::Pend`].
|
|
179
|
+
pub(crate) async fn probe_table(
|
|
180
|
+
client: &Client,
|
|
181
|
+
name: &str,
|
|
182
|
+
missing: MissingTable,
|
|
183
|
+
) -> ApiResult<Option<perspective_client::Table>> {
|
|
184
|
+
match client.open_table(name.to_owned()).await {
|
|
185
|
+
Ok(table) => Ok(Some(table)),
|
|
186
|
+
Err(e) => {
|
|
187
|
+
let hosted = client.get_hosted_table_names().await.unwrap_or_default();
|
|
188
|
+
if hosted.iter().any(|n| n == name) {
|
|
189
|
+
return Err(e.into());
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
match missing {
|
|
193
|
+
MissingTable::Pend => Ok(None),
|
|
194
|
+
MissingTable::Error => Err(ApiError::new(format!("Unknown table \"{name}\""))),
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
155
200
|
/// Mutable state for `Session`.
|
|
156
201
|
#[derive(Default)]
|
|
157
202
|
pub struct SessionData {
|
|
@@ -309,6 +354,7 @@ impl Session {
|
|
|
309
354
|
/// `Session`.
|
|
310
355
|
pub fn reset(&self, options: ResetOptions) -> impl Future<Output = ApiResult<()>> + use<> {
|
|
311
356
|
let view = self.0.borrow_mut().view_sub.take();
|
|
357
|
+
let had_table = self.borrow().table.is_some();
|
|
312
358
|
let err = self.get_error();
|
|
313
359
|
self.borrow_mut().error = None;
|
|
314
360
|
if options.stats {
|
|
@@ -328,11 +374,13 @@ impl Session {
|
|
|
328
374
|
self.borrow_mut().is_loading = false;
|
|
329
375
|
self.borrow_mut().table = None;
|
|
330
376
|
self.borrow_mut().pending_table = None;
|
|
377
|
+
self.borrow_mut().metadata = SessionMetadata::default();
|
|
331
378
|
},
|
|
332
379
|
Some(TableIntermediateState::Reloaded) => {
|
|
333
380
|
self.borrow_mut().is_loading = true;
|
|
334
381
|
self.borrow_mut().table = None;
|
|
335
382
|
self.borrow_mut().pending_table = None;
|
|
383
|
+
self.borrow_mut().metadata = SessionMetadata::default();
|
|
336
384
|
},
|
|
337
385
|
_ => {
|
|
338
386
|
self.borrow_mut().is_loading = false;
|
|
@@ -353,7 +401,7 @@ impl Session {
|
|
|
353
401
|
let session = self.clone();
|
|
354
402
|
async move {
|
|
355
403
|
let res = view.delete().await;
|
|
356
|
-
if options.table.is_some() {
|
|
404
|
+
if options.table.is_some() && had_table {
|
|
357
405
|
session.table_unloaded.emit(true)
|
|
358
406
|
}
|
|
359
407
|
|
|
@@ -410,6 +458,8 @@ impl Session {
|
|
|
410
458
|
Some(TableLoadState::Loaded)
|
|
411
459
|
} else if data.is_loading {
|
|
412
460
|
Some(TableLoadState::Loading)
|
|
461
|
+
} else if data.pending_table.is_some() {
|
|
462
|
+
Some(TableLoadState::Pending)
|
|
413
463
|
} else {
|
|
414
464
|
None
|
|
415
465
|
}
|
|
@@ -470,10 +520,8 @@ impl Session {
|
|
|
470
520
|
/// `table_name` is unique per `Client`, so if this value has not changed,
|
|
471
521
|
/// `Session::set_table` does nothing and returns `Ok(false)`.
|
|
472
522
|
///
|
|
473
|
-
/// A name
|
|
474
|
-
///
|
|
475
|
-
/// `Ok(false)`; the element's hosted-tables subscription
|
|
476
|
-
/// (`tasks::table_lifecycle`) re-runs the bind when the table is created.
|
|
523
|
+
/// A name no loaded client hosts pends ([`MissingTable::Pend`]) and
|
|
524
|
+
/// returns `Ok(false)` until the table lifecycle re-runs the bind.
|
|
477
525
|
pub async fn set_table(&self, table_name: String) -> ApiResult<bool> {
|
|
478
526
|
if Some(table_name.as_str()) == self.0.borrow().table.as_ref().map(|x| x.get_name()) {
|
|
479
527
|
self.0.borrow_mut().pending_table = None;
|
|
@@ -481,60 +529,68 @@ impl Session {
|
|
|
481
529
|
}
|
|
482
530
|
|
|
483
531
|
let client = self.0.borrow().client.clone().into_apierror()?;
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
// open failure by the hosted set, not the error's text.
|
|
489
|
-
let hosted = client.get_hosted_table_names().await.unwrap_or_default();
|
|
490
|
-
if hosted.iter().any(|n| n == &table_name) {
|
|
491
|
-
return Err(e.into());
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
self.0.borrow_mut().pending_table = Some(table_name);
|
|
495
|
-
return Ok(false);
|
|
532
|
+
match probe_table(&client, &table_name, MissingTable::Pend).await? {
|
|
533
|
+
Some(table) => match self.bind_table(table).await {
|
|
534
|
+
Ok(()) => Ok(true),
|
|
535
|
+
Err(err) => self.set_error(false, err).await.map(|_| false),
|
|
496
536
|
},
|
|
497
|
-
|
|
537
|
+
None => {
|
|
538
|
+
self.pend_table(table_name).await?;
|
|
539
|
+
Ok(false)
|
|
540
|
+
},
|
|
541
|
+
}
|
|
542
|
+
}
|
|
498
543
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
544
|
+
/// Bind an opened `Table`, replacing any previous binding and announcing
|
|
545
|
+
/// `table_loaded`.
|
|
546
|
+
pub(crate) async fn bind_table(&self, table: perspective_client::Table) -> ApiResult<()> {
|
|
547
|
+
let metadata = SessionMetadata::from_table(&table).await?;
|
|
548
|
+
let client = table.get_client();
|
|
549
|
+
let on_error = self.on_table_errored.borrow().clone();
|
|
550
|
+
let session = self.clone();
|
|
551
|
+
let poll_loop = LocalPollLoop::new(move |(message, reconnect): (ApiError, _)| {
|
|
552
|
+
session.borrow_mut().error = Some(TableErrorState(message, reconnect));
|
|
553
|
+
if let Some(cb) = &on_error {
|
|
554
|
+
cb.emit(());
|
|
555
|
+
}
|
|
556
|
+
if let Some(sub) = session.borrow_mut().view_sub.take() {
|
|
557
|
+
sub.dismiss();
|
|
558
|
+
}
|
|
512
559
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
let _callback_id = client
|
|
517
|
-
.on_error(Box::new(move |message: ClientError, reconnect| {
|
|
518
|
-
let poll_loop = poll_loop.clone();
|
|
519
|
-
async move {
|
|
520
|
-
poll_loop.poll((message.into(), reconnect)).await;
|
|
521
|
-
Ok(())
|
|
522
|
-
}
|
|
523
|
-
}))
|
|
524
|
-
.await?;
|
|
560
|
+
Ok(JsValue::UNDEFINED)
|
|
561
|
+
});
|
|
525
562
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
563
|
+
let _callback_id = client
|
|
564
|
+
.on_error(Box::new(move |message: ClientError, reconnect| {
|
|
565
|
+
let poll_loop = poll_loop.clone();
|
|
566
|
+
async move {
|
|
567
|
+
poll_loop.poll((message.into(), reconnect)).await;
|
|
568
|
+
Ok(())
|
|
569
|
+
}
|
|
570
|
+
}))
|
|
571
|
+
.await?;
|
|
572
|
+
|
|
573
|
+
let sub = self.borrow_mut().view_sub.take();
|
|
574
|
+
self.borrow_mut().metadata = metadata;
|
|
575
|
+
self.borrow_mut().table = Some(table);
|
|
576
|
+
self.borrow_mut().pending_table = None;
|
|
577
|
+
self.borrow_mut().is_loading = false;
|
|
578
|
+
self.borrow_mut().last_validated_expressions = None;
|
|
579
|
+
sub.delete().await?;
|
|
580
|
+
self.table_loaded.emit(());
|
|
581
|
+
Ok(())
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/// Record `name` as pending, dropping any bound `Table` so nothing binds
|
|
585
|
+
/// against the outgoing table while the name waits for a host.
|
|
586
|
+
pub(crate) async fn pend_table(&self, name: String) -> ApiResult<()> {
|
|
587
|
+
let sub = self.borrow_mut().view_sub.take();
|
|
588
|
+
self.borrow_mut().table = None;
|
|
589
|
+
self.borrow_mut().metadata = SessionMetadata::default();
|
|
590
|
+
self.borrow_mut().pending_table = Some(name);
|
|
591
|
+
self.borrow_mut().is_loading = false;
|
|
592
|
+
self.borrow_mut().last_validated_expressions = None;
|
|
593
|
+
sub.delete().await
|
|
538
594
|
}
|
|
539
595
|
|
|
540
596
|
pub async fn set_error(&self, reset_table: bool, err: ApiError) -> ApiResult<()> {
|
|
@@ -1094,31 +1150,7 @@ impl Session {
|
|
|
1094
1150
|
.clone()
|
|
1095
1151
|
.ok_or("`restore()` called before `load()`")?;
|
|
1096
1152
|
|
|
1097
|
-
|
|
1098
|
-
// virtual server api. Snapshot-local — never written back.
|
|
1099
|
-
let mut view_config = (*effective).clone();
|
|
1100
|
-
for col in view_config
|
|
1101
|
-
.columns
|
|
1102
|
-
.iter()
|
|
1103
|
-
.flatten()
|
|
1104
|
-
.chain(view_config.sort.iter().map(|x| &x.0))
|
|
1105
|
-
{
|
|
1106
|
-
if !view_config.aggregates.contains_key(col.as_str()) {
|
|
1107
|
-
let agg = self
|
|
1108
|
-
.metadata()
|
|
1109
|
-
.get_column_aggregates(col.as_str())
|
|
1110
|
-
.and_then(|mut aggs| aggs.next())
|
|
1111
|
-
.into_apierror();
|
|
1112
|
-
|
|
1113
|
-
match agg {
|
|
1114
|
-
Err(_) => {
|
|
1115
|
-
tracing::warn!("No default aggregate for column '{}' found, skipping", col)
|
|
1116
|
-
},
|
|
1117
|
-
Ok(agg) => _ = view_config.aggregates.insert(col.to_string(), agg),
|
|
1118
|
-
};
|
|
1119
|
-
}
|
|
1120
|
-
}
|
|
1121
|
-
|
|
1153
|
+
let view_config = self.with_default_aggregates(&effective);
|
|
1122
1154
|
let view = table.view(Some(view_config.into())).await?;
|
|
1123
1155
|
let view_schema = view.schema().await?;
|
|
1124
1156
|
self.metadata_mut().update_view_schema(&view_schema)?;
|
|
@@ -1150,6 +1182,43 @@ impl Session {
|
|
|
1150
1182
|
}
|
|
1151
1183
|
}
|
|
1152
1184
|
|
|
1185
|
+
/// The engine config for a `View` built from `effective`, per-column
|
|
1186
|
+
/// default aggregates filled in as a courtesy to the virtual server API.
|
|
1187
|
+
fn with_default_aggregates(&self, effective: &ViewConfig) -> ViewConfig {
|
|
1188
|
+
let mut view_config = effective.clone();
|
|
1189
|
+
for col in view_config
|
|
1190
|
+
.columns
|
|
1191
|
+
.iter()
|
|
1192
|
+
.flatten()
|
|
1193
|
+
.chain(view_config.sort.iter().map(|x| &x.0))
|
|
1194
|
+
{
|
|
1195
|
+
if !view_config.aggregates.contains_key(col.as_str()) {
|
|
1196
|
+
let agg = self
|
|
1197
|
+
.metadata()
|
|
1198
|
+
.get_column_aggregates(col.as_str())
|
|
1199
|
+
.and_then(|mut aggs| aggs.next())
|
|
1200
|
+
.into_apierror();
|
|
1201
|
+
|
|
1202
|
+
match agg {
|
|
1203
|
+
Err(_) => {
|
|
1204
|
+
tracing::warn!("No default aggregate for column '{}' found, skipping", col)
|
|
1205
|
+
},
|
|
1206
|
+
Ok(agg) => _ = view_config.aggregates.insert(col.to_string(), agg),
|
|
1207
|
+
};
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
view_config
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
/// Build a caller-owned `View` from the effective config outside the
|
|
1215
|
+
/// render pipeline, with no subscription and no interaction with pause.
|
|
1216
|
+
pub async fn create_detached_view(&self) -> ApiResult<View> {
|
|
1217
|
+
let table = self.borrow().table.clone().ok_or("No `Table` set")?;
|
|
1218
|
+
let view_config = self.with_default_aggregates(&self.effective_view_config());
|
|
1219
|
+
Ok(table.view(Some(view_config.into())).await?)
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1153
1222
|
/// Record a failed pipeline run: error state plus a reconnect affordance
|
|
1154
1223
|
/// that resets the config (the error screen's reset button). Replaces
|
|
1155
1224
|
/// the old `validate()` error path — the committed config is NOT rolled
|
|
@@ -1239,6 +1308,8 @@ impl Session {
|
|
|
1239
1308
|
Some(TableLoadState::Loaded)
|
|
1240
1309
|
} else if data.is_loading {
|
|
1241
1310
|
Some(TableLoadState::Loading)
|
|
1311
|
+
} else if data.pending_table.is_some() {
|
|
1312
|
+
Some(TableLoadState::Pending)
|
|
1242
1313
|
} else {
|
|
1243
1314
|
None
|
|
1244
1315
|
},
|
|
@@ -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::{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
|
|
|
@@ -251,6 +251,18 @@ pub(crate) async fn locked_run(
|
|
|
251
251
|
let view_config_snapshot = session.get_view_config().clone();
|
|
252
252
|
let plugin_config_changed =
|
|
253
253
|
renderer.update_plugin_config(&view_config_snapshot, spec.plugin_config)?;
|
|
254
|
+
let plugin_config_snapshot = renderer.get_plugin_config();
|
|
255
|
+
let plugin_update =
|
|
256
|
+
JsValue::from_serde_ext(&plugin_config_snapshot).unwrap_or(JsValue::NULL);
|
|
257
|
+
|
|
258
|
+
let plugin_restored = plugin_config_changed || plugin_swapped;
|
|
259
|
+
if plugin_restored {
|
|
260
|
+
let columns_config = renderer
|
|
261
|
+
.all_columns_configs_materialized(&view_config_snapshot, &session)
|
|
262
|
+
.await;
|
|
263
|
+
plugin.restore(&plugin_update, Some(&columns_config))?;
|
|
264
|
+
}
|
|
265
|
+
|
|
254
266
|
let columns_config_changed = renderer.update_columns_configs(
|
|
255
267
|
&view_config_snapshot,
|
|
256
268
|
&session,
|
|
@@ -258,17 +270,16 @@ pub(crate) async fn locked_run(
|
|
|
258
270
|
)?;
|
|
259
271
|
|
|
260
272
|
let changed = plugin_config_changed || columns_config_changed;
|
|
261
|
-
if
|
|
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);
|
|
273
|
+
if columns_config_changed {
|
|
265
274
|
let columns_config = renderer
|
|
266
275
|
.all_columns_configs_materialized(&view_config_snapshot, &session)
|
|
267
276
|
.await;
|
|
268
277
|
plugin.restore(&plugin_update, Some(&columns_config))?;
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
278
|
+
renderer.columns_config_changed.emit(columns_config);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if plugin_config_changed {
|
|
282
|
+
renderer.plugin_config_changed.emit(plugin_config_snapshot);
|
|
272
283
|
}
|
|
273
284
|
|
|
274
285
|
if spec
|
|
@@ -16,7 +16,7 @@ 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::{MissingTable, ResetOptions, Session, TableIntermediateState, probe_table};
|
|
20
20
|
use crate::tasks::*;
|
|
21
21
|
use crate::workspace::Workspace;
|
|
22
22
|
use crate::*;
|
|
@@ -38,27 +38,107 @@ pub(crate) enum RestoreErrors {
|
|
|
38
38
|
Suppress,
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/// The client to bind and the opened `Table`, or `None` when pending.
|
|
42
|
+
type Probed = (
|
|
43
|
+
perspective_client::Client,
|
|
44
|
+
Option<perspective_client::Table>,
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
/// Resolve and probe the client hosting `name` without touching the
|
|
48
|
+
/// session's own binding.
|
|
49
|
+
async fn probe(
|
|
50
|
+
session: &Session,
|
|
51
|
+
workspace: &Workspace,
|
|
52
|
+
name: &str,
|
|
53
|
+
missing: MissingTable,
|
|
54
|
+
) -> ApiResult<Probed> {
|
|
55
|
+
let current = session.get_client();
|
|
56
|
+
let resolved = workspace
|
|
57
|
+
.resolve_client_for_table(name, current.as_ref())
|
|
58
|
+
.await;
|
|
59
|
+
|
|
60
|
+
let client = resolved.or(current).into_apierror()?;
|
|
61
|
+
let table = probe_table(&client, name, missing).await?;
|
|
62
|
+
Ok((client, table))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// Bind an unbound or same-named session to `name`.
|
|
41
66
|
pub(crate) async fn bind_table_task(
|
|
42
67
|
session: &Session,
|
|
43
68
|
workspace: &Workspace,
|
|
44
69
|
name: String,
|
|
70
|
+
missing: MissingTable,
|
|
45
71
|
) -> ApiResult<()> {
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
.
|
|
49
|
-
.await
|
|
72
|
+
if session
|
|
73
|
+
.get_table()
|
|
74
|
+
.is_some_and(|t| t.get_name() == name.as_str())
|
|
50
75
|
{
|
|
51
|
-
|
|
76
|
+
return Ok(());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let (client, table) = probe(session, workspace, &name, missing).await?;
|
|
80
|
+
session.set_client(client);
|
|
81
|
+
match table {
|
|
82
|
+
Some(table) => session.bind_table(table).await?,
|
|
83
|
+
None => session.pend_table(name).await?,
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
session.commit_table_defaults();
|
|
87
|
+
Ok(())
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/// Rebind a session to `name`, probing the incoming table before the
|
|
91
|
+
/// outgoing binding is dropped and replaying the journal opened as
|
|
92
|
+
/// `generation` over the incoming table's defaults.
|
|
93
|
+
async fn rebind_table_task(
|
|
94
|
+
session: &Session,
|
|
95
|
+
renderer: &Renderer,
|
|
96
|
+
workspace: &Workspace,
|
|
97
|
+
name: String,
|
|
98
|
+
missing: MissingTable,
|
|
99
|
+
generation: u32,
|
|
100
|
+
) -> ApiResult<()> {
|
|
101
|
+
let probed = probe(session, workspace, &name, missing).await;
|
|
102
|
+
let journal = session.take_pending_load(generation);
|
|
103
|
+
let (client, table) = probed?;
|
|
104
|
+
let Some(journal) = journal else {
|
|
105
|
+
return Ok(());
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
session
|
|
109
|
+
.reset(ResetOptions {
|
|
110
|
+
config: true,
|
|
111
|
+
expressions: true,
|
|
112
|
+
stats: true,
|
|
113
|
+
table: Some(TableIntermediateState::Reloaded),
|
|
114
|
+
})
|
|
115
|
+
.await?;
|
|
116
|
+
|
|
117
|
+
session.set_client(client);
|
|
118
|
+
match table {
|
|
119
|
+
Some(table) => session.bind_table(table).await?,
|
|
120
|
+
None => session.pend_table(name).await?,
|
|
52
121
|
}
|
|
53
122
|
|
|
54
|
-
session.set_table(name).await?;
|
|
55
123
|
session.commit_table_defaults();
|
|
124
|
+
for delta in journal {
|
|
125
|
+
session.commit_view_config(delta)?;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
session.commit_table_defaults();
|
|
129
|
+
if session.get_table().is_none()
|
|
130
|
+
&& let Some(plugin) = renderer.active_plugin()
|
|
131
|
+
{
|
|
132
|
+
plugin.clear().await?;
|
|
133
|
+
}
|
|
134
|
+
|
|
56
135
|
Ok(())
|
|
57
136
|
}
|
|
58
137
|
|
|
59
138
|
/// Apply a [`ViewerConfigUpdate`] to a single panel and re-draw — the one
|
|
60
139
|
/// pipeline shared by `restorePanel` (an existing panel), `restoreWorkspace`,
|
|
61
140
|
/// and `addPanel` (both fresh panels).
|
|
141
|
+
#[allow(clippy::too_many_arguments)]
|
|
62
142
|
pub(crate) async fn restore_panel(
|
|
63
143
|
session: &Session,
|
|
64
144
|
renderer: &Renderer,
|
|
@@ -67,6 +147,7 @@ pub(crate) async fn restore_panel(
|
|
|
67
147
|
mode: RestoreMode,
|
|
68
148
|
mut update: ViewerConfigUpdate,
|
|
69
149
|
errors: RestoreErrors,
|
|
150
|
+
missing: MissingTable,
|
|
70
151
|
) -> ApiResult<()> {
|
|
71
152
|
let active = matches!(mode, RestoreMode::Existing { active: true });
|
|
72
153
|
let fresh = matches!(mode, RestoreMode::Fresh);
|
|
@@ -100,21 +181,24 @@ pub(crate) async fn restore_panel(
|
|
|
100
181
|
let rollback =
|
|
101
182
|
matches!(errors, RestoreErrors::Suppress).then(|| session.get_view_config().clone());
|
|
102
183
|
|
|
103
|
-
let
|
|
104
|
-
|
|
105
|
-
|
|
184
|
+
let binding_before = (
|
|
185
|
+
session.get_table().map(|t| t.get_name().to_owned()),
|
|
186
|
+
session.pending_table(),
|
|
187
|
+
);
|
|
106
188
|
|
|
107
|
-
let
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
189
|
+
let generation = match &update.table {
|
|
190
|
+
OptionalUpdate::Update(name)
|
|
191
|
+
if session.is_errored()
|
|
192
|
+
|| (!fresh
|
|
193
|
+
&& session
|
|
194
|
+
.get_table()
|
|
195
|
+
.map(|t| t.get_name() != name.as_str())
|
|
196
|
+
.unwrap_or(true)) =>
|
|
197
|
+
{
|
|
198
|
+
Some(session.begin_pending_load())
|
|
199
|
+
},
|
|
200
|
+
_ => None,
|
|
201
|
+
};
|
|
118
202
|
|
|
119
203
|
let result = restore_and_render(
|
|
120
204
|
session,
|
|
@@ -123,27 +207,42 @@ pub(crate) async fn restore_panel(
|
|
|
123
207
|
RunOrigin::Public,
|
|
124
208
|
update.clone(),
|
|
125
209
|
{
|
|
126
|
-
clone!(session, update.table, workspace);
|
|
210
|
+
clone!(session, renderer, update.table, workspace);
|
|
127
211
|
async move {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
}
|
|
212
|
+
let OptionalUpdate::Update(name) = table else {
|
|
213
|
+
return Ok(());
|
|
214
|
+
};
|
|
132
215
|
|
|
133
|
-
|
|
216
|
+
match generation {
|
|
217
|
+
Some(generation) => {
|
|
218
|
+
rebind_table_task(
|
|
219
|
+
&session, &renderer, &workspace, name, missing, generation,
|
|
220
|
+
)
|
|
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(generation) = generation {
|
|
231
|
+
session.take_pending_load(generation);
|
|
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
|
-
|
|
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
|
})
|