@perspective-dev/viewer 5.2.0 → 5.3.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.
- package/dist/cdn/perspective-viewer.js +8 -8
- package/dist/cdn/perspective-viewer.js.map +3 -3
- package/dist/css/blueprint.css +1 -0
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -0
- 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/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 -0
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -0
- 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 -0
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/column-format.d.ts +16 -3
- package/dist/esm/perspective-viewer.d.ts +1 -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 +8 -8
- package/dist/esm/perspective-viewer.js.map +3 -3
- package/dist/esm/plugin.d.ts +10 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +13 -3
- package/dist/wasm/perspective-viewer.d.ts +43 -36
- package/dist/wasm/perspective-viewer.js +198 -136
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +19 -17
- package/package.json +1 -1
- package/src/css/column-settings-panel.css +28 -5
- package/src/css/column-style.css +291 -31
- package/src/css/containers/tabs.css +13 -3
- package/src/css/dom/select.css +21 -9
- package/src/css/form/code-editor.css +4 -4
- package/src/css/plugin-selector.css +1 -0
- package/src/css/plugin-settings-panel.css +72 -0
- package/src/css/viewer.css +0 -5
- package/src/rust/agent/tools.rs +53 -19
- package/src/rust/components/column_selector/active_column.rs +2 -2
- package/src/rust/components/column_selector/add_expression_button.rs +3 -3
- package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
- package/src/rust/components/column_selector/config_selector.rs +8 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
- package/src/rust/components/column_selector/inactive_column.rs +2 -2
- package/src/rust/components/column_selector.rs +48 -4
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
- package/src/rust/components/column_settings_sidebar/style_tab.rs +330 -234
- package/src/rust/components/column_settings_sidebar.rs +88 -69
- package/src/rust/components/containers/control_group.rs +91 -0
- package/src/rust/components/containers/mod.rs +1 -0
- package/src/rust/components/containers/select.rs +8 -2
- package/src/rust/components/datetime_column_style/simple.rs +15 -4
- package/src/rust/components/datetime_column_style.rs +50 -10
- package/src/rust/components/editable_header.rs +85 -144
- package/src/rust/components/expression_editor.rs +9 -16
- package/src/rust/components/form/code_editor.rs +16 -6
- package/src/rust/components/form/debug.rs +1 -0
- package/src/rust/components/form/mod.rs +3 -1
- package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
- package/src/rust/components/form/named_value_picker.rs +86 -0
- package/src/rust/components/form/palette_selector.rs +440 -0
- package/src/rust/components/main_panel/msg.rs +2 -2
- package/src/rust/components/main_panel/reconcile.rs +4 -4
- package/src/rust/components/plugin_tab.rs +126 -112
- package/src/rust/components/settings_panel.rs +26 -20
- package/src/rust/components/status_bar.rs +0 -1
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +22 -10
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +6 -2
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -0
- package/src/rust/components/style_controls/number_string_format/types.rs +24 -0
- package/src/rust/components/style_controls/number_string_format.rs +37 -14
- package/src/rust/components/viewer/msg.rs +46 -7
- package/src/rust/components/viewer/panels.rs +11 -9
- package/src/rust/components/viewer/render.rs +8 -3
- package/src/rust/components/viewer/settings.rs +163 -23
- package/src/rust/components/viewer/snapshots.rs +46 -21
- package/src/rust/components/viewer/wiring.rs +14 -25
- package/src/rust/components/viewer.rs +12 -8
- package/src/rust/config/column_config_schema.rs +435 -45
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/datetime_column_style.rs +4 -21
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/number_string_format.rs +211 -18
- package/src/rust/config/options.rs +10 -0
- package/src/rust/config/viewer_config.rs +49 -55
- package/src/rust/config/workspace_config.rs +132 -5
- package/src/rust/custom_elements/viewer.rs +214 -155
- package/src/rust/custom_events.rs +14 -1
- package/src/rust/js/regular_layout.rs +3 -3
- package/src/rust/lib.rs +2 -0
- package/src/rust/presentation/column_locator.rs +19 -5
- package/src/rust/presentation.rs +209 -36
- package/src/rust/queries/column_locator.rs +74 -44
- package/src/rust/queries/get_viewer_config.rs +0 -3
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +82 -0
- package/src/rust/queries/plugin_column_styles.rs +6 -2
- package/src/rust/renderer/dispatch.rs +50 -12
- package/src/rust/renderer/plugin_config.rs +362 -53
- package/src/rust/renderer.rs +15 -42
- package/src/rust/session/column_defaults_update.rs +15 -8
- package/src/rust/session/metadata.rs +5 -1
- package/src/rust/session/props.rs +1 -0
- package/src/rust/session.rs +9 -6
- package/src/rust/tasks/create_panel.rs +44 -57
- package/src/rust/tasks/edit_expression.rs +19 -23
- package/src/rust/tasks/edit_window.rs +8 -10
- package/src/rust/tasks/eject.rs +4 -1
- package/src/rust/tasks/pipeline.rs +2 -2
- package/src/rust/tasks/presize_panels.rs +44 -5
- package/src/rust/tasks/reset_all.rs +4 -4
- package/src/rust/tasks/restore_and_render.rs +17 -44
- package/src/rust/tasks/restore_panel.rs +11 -13
- package/src/rust/tasks/set_edit_mode.rs +3 -1
- package/src/rust/tasks/sync_update_panels.rs +11 -1
- package/src/rust/tasks/update_theme.rs +23 -18
- package/src/rust/utils/css_vars.rs +26 -0
- package/src/rust/utils/mod.rs +2 -0
- package/src/rust/utils/spawn.rs +1 -1
- package/src/rust/workspace.rs +435 -272
- package/src/svg/lock.svg +7 -0
- package/src/themes/blueprint.css +190 -0
- package/src/themes/botanical.css +14 -17
- package/src/themes/defaults.css +4 -110
- package/src/themes/dracula.css +19 -14
- package/src/themes/eggplant.css +189 -0
- package/src/themes/gruvbox-dark.css +23 -15
- package/src/themes/gruvbox.css +11 -1
- package/src/themes/icons.css +2 -2
- package/src/themes/intl/de.css +23 -1
- package/src/themes/intl/es.css +23 -1
- package/src/themes/intl/fr.css +23 -1
- package/src/themes/intl/ja.css +23 -1
- package/src/themes/intl/pt.css +23 -1
- package/src/themes/intl/zh.css +23 -1
- package/src/themes/intl.css +26 -4
- package/src/themes/ledger.css +195 -0
- package/src/themes/monokai.css +24 -14
- package/src/themes/nord.css +186 -0
- package/src/themes/phosphor.css +7 -14
- package/src/themes/pro-dark.css +12 -16
- package/src/themes/pro.css +118 -0
- package/src/themes/solarized-dark.css +9 -16
- package/src/themes/solarized.css +7 -0
- package/src/themes/themes.css +10 -5
- package/src/themes/vaporwave.css +22 -14
- package/src/themes/velvet.css +192 -0
- package/src/ts/column-format.ts +52 -2
- package/src/ts/perspective-viewer.ts +1 -0
- package/src/ts/plugin.ts +14 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +1 -1
- package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +1 -1
- package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +11 -3
- package/src/rust/components/form/color_range_selector.rs +0 -137
- package/src/svg/dropdown-selector-light.svg +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline5.js +0 -0
|
@@ -84,11 +84,15 @@ extern "C" {
|
|
|
84
84
|
#[wasm_bindgen(typescript_type = "GetClientOptions")]
|
|
85
85
|
pub type JsGetClientOptions;
|
|
86
86
|
|
|
87
|
-
/// `restoreWorkspace()` argument: a
|
|
87
|
+
/// `restoreWorkspace()` argument: a workspace config update.
|
|
88
88
|
#[wasm_bindgen(typescript_type = "WorkspaceConfigUpdate")]
|
|
89
89
|
pub type JsWorkspaceConfigUpdate;
|
|
90
90
|
|
|
91
|
-
/// `saveWorkspace()`
|
|
91
|
+
/// `saveWorkspace()` options dict (`{ full_palette?: boolean }`).
|
|
92
|
+
#[wasm_bindgen(typescript_type = "SaveWorkspaceOptions")]
|
|
93
|
+
pub type JsSaveWorkspaceOptions;
|
|
94
|
+
|
|
95
|
+
/// `saveWorkspace()` return: a workspace config.
|
|
92
96
|
#[wasm_bindgen(typescript_type = "Promise<WorkspaceConfig>")]
|
|
93
97
|
pub type JsWorkspaceConfigPromise;
|
|
94
98
|
|
|
@@ -201,7 +205,8 @@ impl PerspectiveViewerElement {
|
|
|
201
205
|
.map(|el| el.unchecked_into())
|
|
202
206
|
}
|
|
203
207
|
|
|
204
|
-
async fn workspace_config(this: Self) -> ApiResult<JsValue> {
|
|
208
|
+
async fn workspace_config(this: Self, full_palette: bool) -> ApiResult<JsValue> {
|
|
209
|
+
this.workspace.effects().settle().await;
|
|
205
210
|
let mut panels: std::collections::BTreeMap<String, PanelViewerConfig> = Default::default();
|
|
206
211
|
for id in &this.workspace.panel_ids() {
|
|
207
212
|
let panel = this.workspace.panel(id).into_apierror()?;
|
|
@@ -216,6 +221,26 @@ impl PerspectiveViewerElement {
|
|
|
216
221
|
panels.insert(id.as_str().to_owned(), config.panel);
|
|
217
222
|
}
|
|
218
223
|
|
|
224
|
+
let mut palette = palette_set(&this.workspace, &this.presentation);
|
|
225
|
+
let mut referenced = std::collections::BTreeSet::new();
|
|
226
|
+
for (id, item) in styles_in_use(&this.workspace) {
|
|
227
|
+
let Some(name) = palette_name_for(&palette, item.kind, &item.literal) else {
|
|
228
|
+
continue;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if let Some(entry) = panels
|
|
232
|
+
.get_mut(id.as_str())
|
|
233
|
+
.and_then(|panel| panel.columns_config.get_mut(&item.column))
|
|
234
|
+
{
|
|
235
|
+
entry.insert(item.key, serde_json::Value::String(format_var_ref(&name)));
|
|
236
|
+
referenced.insert(name);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if !full_palette {
|
|
241
|
+
palette.retain(|name, _| referenced.contains(name));
|
|
242
|
+
}
|
|
243
|
+
|
|
219
244
|
let active = this
|
|
220
245
|
.presentation
|
|
221
246
|
.is_settings_open()
|
|
@@ -240,6 +265,7 @@ impl PerspectiveViewerElement {
|
|
|
240
265
|
.iter()
|
|
241
266
|
.map(|id| id.as_str().to_owned())
|
|
242
267
|
.collect(),
|
|
268
|
+
palette,
|
|
243
269
|
})?)
|
|
244
270
|
}
|
|
245
271
|
}
|
|
@@ -521,7 +547,7 @@ impl PerspectiveViewerElement {
|
|
|
521
547
|
renderer
|
|
522
548
|
.clone()
|
|
523
549
|
.render_task(|guard| async move {
|
|
524
|
-
|
|
550
|
+
seed_panel_theme(&presentation, &renderer).await;
|
|
525
551
|
renderer.stamp_theme(None);
|
|
526
552
|
let jstable = JsFuture::from(promise)
|
|
527
553
|
.await
|
|
@@ -536,7 +562,7 @@ impl PerspectiveViewerElement {
|
|
|
536
562
|
};
|
|
537
563
|
|
|
538
564
|
if let Some(notify) = ¬ify {
|
|
539
|
-
place_reserved(&workspace, notify);
|
|
565
|
+
place_reserved(&workspace, notify, true);
|
|
540
566
|
}
|
|
541
567
|
|
|
542
568
|
let _plugin = renderer.ensure_plugin_selected()?;
|
|
@@ -594,7 +620,35 @@ impl PerspectiveViewerElement {
|
|
|
594
620
|
// commits already applied live (`commit_view_config`).
|
|
595
621
|
let owned_window = session.take_pending_load(generation).is_some();
|
|
596
622
|
let discard = if owned_window && notify.is_some() {
|
|
597
|
-
workspace.take_reserved()
|
|
623
|
+
match workspace.take_reserved() {
|
|
624
|
+
Some(panel) => Some((panel, None)),
|
|
625
|
+
// The one-shot claim read: a table-less
|
|
626
|
+
// `restore` claimed the reservation, and no
|
|
627
|
+
// table has bound nor is pending — evict the
|
|
628
|
+
// panel `CREATE_REQUIRES_TABLE` forbids.
|
|
629
|
+
None if workspace
|
|
630
|
+
.resolve_claim()
|
|
631
|
+
.is_some_and(|has_table| !has_table)
|
|
632
|
+
&& session.get_table().is_none()
|
|
633
|
+
&& session.pending_table().is_none() =>
|
|
634
|
+
{
|
|
635
|
+
let evicted = renderer
|
|
636
|
+
.slot_name()
|
|
637
|
+
.map(PanelId::from)
|
|
638
|
+
.and_then(|id| workspace.remove_panel(&id));
|
|
639
|
+
|
|
640
|
+
if evicted.is_some()
|
|
641
|
+
&& let Some(notify) = ¬ify
|
|
642
|
+
{
|
|
643
|
+
notify.emit(());
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
evicted.map(|panel| {
|
|
647
|
+
(panel, Some(ApiError::new(CREATE_REQUIRES_TABLE)))
|
|
648
|
+
})
|
|
649
|
+
},
|
|
650
|
+
None => None,
|
|
651
|
+
}
|
|
598
652
|
} else {
|
|
599
653
|
None
|
|
600
654
|
};
|
|
@@ -613,13 +667,19 @@ impl PerspectiveViewerElement {
|
|
|
613
667
|
Err(e) => {
|
|
614
668
|
session.take_pending_load(generation);
|
|
615
669
|
if let Some(notify) = ¬ify {
|
|
616
|
-
place_reserved(&workspace, notify);
|
|
670
|
+
place_reserved(&workspace, notify, true);
|
|
617
671
|
}
|
|
618
672
|
|
|
619
673
|
session.set_error(false, e.clone()).await?;
|
|
620
674
|
Err(e)
|
|
621
675
|
},
|
|
622
|
-
Ok(Some(panel)) =>
|
|
676
|
+
Ok(Some((panel, error))) => {
|
|
677
|
+
eject_panel(panel).await?;
|
|
678
|
+
match error {
|
|
679
|
+
Some(e) => Err(e),
|
|
680
|
+
None => Ok(()),
|
|
681
|
+
}
|
|
682
|
+
},
|
|
623
683
|
Ok(None) => Ok(()),
|
|
624
684
|
}
|
|
625
685
|
}))
|
|
@@ -931,18 +991,18 @@ impl PerspectiveViewerElement {
|
|
|
931
991
|
/// optional `{panel}` selector.
|
|
932
992
|
///
|
|
933
993
|
/// If `panel` names no existing panel, a NEW panel is created with that id
|
|
934
|
-
/// and the config restored into it (an upsert).
|
|
935
|
-
/// the
|
|
936
|
-
///
|
|
937
|
-
///
|
|
938
|
-
///
|
|
939
|
-
///
|
|
994
|
+
/// and the config restored into it (an upsert). Creation REQUIRES a
|
|
995
|
+
/// `table` — the same rule [`Self::addPanel`] enforces in its argument
|
|
996
|
+
/// type — and a would-create call without one REJECTS before any state
|
|
997
|
+
/// (including `settings`) is applied: with no panel to target and no
|
|
998
|
+
/// `table`, the patch has no data arrival path. In particular, on an
|
|
999
|
+
/// element with zero panels every `restore` must carry a `table`.
|
|
940
1000
|
///
|
|
941
1001
|
/// On an empty element with a pending [`Self::load`] whose payload is not
|
|
942
1002
|
/// yet classified, the active-target form (no `panel`) instead claims and
|
|
943
1003
|
/// restores into that load's reserved first panel — see [`Self::load`].
|
|
944
1004
|
///
|
|
945
|
-
/// This restores a SINGLE panel; a
|
|
1005
|
+
/// This restores a SINGLE panel; a workspace config (with a `panels`
|
|
946
1006
|
/// map) must be applied via [`Self::restoreWorkspace`] — its `panels` /
|
|
947
1007
|
/// `layout` keys are ignored here.
|
|
948
1008
|
///
|
|
@@ -990,10 +1050,6 @@ impl PerspectiveViewerElement {
|
|
|
990
1050
|
suppress_errors,
|
|
991
1051
|
} = parse_options(options);
|
|
992
1052
|
|
|
993
|
-
// `suppress_errors` failures reject the returned Promise WITHOUT
|
|
994
|
-
// committing to the viewer's visible error state — for programmatic
|
|
995
|
-
// callers (the agent's `set_view_config`) whose failed patches are
|
|
996
|
-
// caller feedback, not user-facing faults.
|
|
997
1053
|
let errors = if suppress_errors.unwrap_or_default() {
|
|
998
1054
|
RestoreErrors::Suppress
|
|
999
1055
|
} else {
|
|
@@ -1006,43 +1062,59 @@ impl PerspectiveViewerElement {
|
|
|
1006
1062
|
let _effect = effect;
|
|
1007
1063
|
let id = name.map(PanelId::from);
|
|
1008
1064
|
let mut update = ViewerConfigUpdate::decode(&update)?;
|
|
1009
|
-
|
|
1010
|
-
// `settings` is ELEMENT-level chrome, not panel state — it
|
|
1011
|
-
// rides in this object only because the legacy single-panel
|
|
1012
|
-
// `restore()` has always carried it. Apply it HERE and hand
|
|
1013
|
-
// the panel pipeline a purely per-panel update: the
|
|
1014
|
-
// panel-CREATING branch below converts to
|
|
1015
|
-
// `ViewerConfigInitial`, which has no `settings` field at
|
|
1016
|
-
// all, so a `restore()` against an empty element — the first
|
|
1017
|
-
// call every embedding makes — would otherwise drop it
|
|
1018
|
-
// silently. Applied BEFORE the panel work so the plugin
|
|
1019
|
-
// draws once, already at its final size.
|
|
1020
|
-
// BOTH halves move together, or the flag and the chrome
|
|
1021
|
-
// disagree: `set_settings_before_open` writes the persisted
|
|
1022
|
-
// `is_settings_open` (what `save()` reports) and the host
|
|
1023
|
-
// `settings` attribute, while `ToggleSettingsComplete`
|
|
1024
|
-
// performs the component's own toggle. `restore_and_render`
|
|
1025
|
-
// used to do the first for the panel-UPDATING path only —
|
|
1026
|
-
// which the creating path never reaches, since
|
|
1027
|
-
// `ViewerConfigInitial` has no `settings` field to carry.
|
|
1028
1065
|
let settings = std::mem::replace(&mut update.settings, OptionalUpdate::Missing);
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1066
|
+
enum Target {
|
|
1067
|
+
Existing { panel: Panel, active: bool },
|
|
1068
|
+
Claimed(Panel),
|
|
1069
|
+
Create(Box<ViewerConfigInitial>),
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
let notify = this.layout_changed_notify();
|
|
1073
|
+
let target = match this.workspace.panel_or_active(id.as_ref()) {
|
|
1074
|
+
// An existing (or the active) panel — update it in place.
|
|
1075
|
+
Some(panel) => {
|
|
1076
|
+
let active = this.workspace.active_id().as_ref() == Some(&panel.id);
|
|
1077
|
+
Target::Existing { panel, active }
|
|
1078
|
+
},
|
|
1079
|
+
None => {
|
|
1080
|
+
let has_table = matches!(&update.table, OptionalUpdate::Update(_));
|
|
1081
|
+
match id
|
|
1082
|
+
.is_none()
|
|
1083
|
+
.then(|| place_reserved(&this.workspace, ¬ify, has_table))
|
|
1084
|
+
.flatten()
|
|
1085
|
+
{
|
|
1086
|
+
Some(panel) => Target::Claimed(panel),
|
|
1087
|
+
None => Target::Create(Box::new(ViewerConfigInitial::try_from(
|
|
1088
|
+
std::mem::take(&mut update),
|
|
1089
|
+
)?)),
|
|
1090
|
+
}
|
|
1091
|
+
},
|
|
1092
|
+
};
|
|
1033
1093
|
|
|
1034
|
-
|
|
1094
|
+
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
|
+
let (sender, receiver) = channel::<ApiResult<JsValue>>();
|
|
1035
1109
|
this.root.borrow().as_ref().into_apierror()?.send_message(
|
|
1036
|
-
PerspectiveViewerMsg::
|
|
1110
|
+
PerspectiveViewerMsg::ToggleSettingsInit(Some(settings), false, Some(sender)),
|
|
1037
1111
|
);
|
|
1038
1112
|
|
|
1039
|
-
receiver.await.
|
|
1113
|
+
receiver.await.map_err(|_| ApiError::new("Cancelled"))??;
|
|
1040
1114
|
}
|
|
1041
1115
|
|
|
1042
|
-
match
|
|
1043
|
-
|
|
1044
|
-
Some(panel) => {
|
|
1045
|
-
let active = this.workspace.active_id().as_ref() == Some(&panel.id);
|
|
1116
|
+
match target {
|
|
1117
|
+
Target::Existing { panel, active } => {
|
|
1046
1118
|
restore_panel(
|
|
1047
1119
|
&panel.session,
|
|
1048
1120
|
&panel.renderer,
|
|
@@ -1054,61 +1126,30 @@ impl PerspectiveViewerElement {
|
|
|
1054
1126
|
)
|
|
1055
1127
|
.await
|
|
1056
1128
|
},
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
.
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
update,
|
|
1082
|
-
errors,
|
|
1083
|
-
)
|
|
1084
|
-
.await
|
|
1085
|
-
},
|
|
1086
|
-
None => {
|
|
1087
|
-
// The panel-creating upsert. `restore`'s
|
|
1088
|
-
// argument is a PATCH, so a `table` is
|
|
1089
|
-
// optional here as it is everywhere else on
|
|
1090
|
-
// this path: an update carrying none creates
|
|
1091
|
-
// a deferred panel, which the next `load()`
|
|
1092
|
-
// binds (it targets `active_panel()` before
|
|
1093
|
-
// reserving one of its own). The
|
|
1094
|
-
// table-required rule belongs to the
|
|
1095
|
-
// CREATION types — `addPanel`,
|
|
1096
|
-
// `restoreWorkspace`'s `panels`, the agent's
|
|
1097
|
-
// `add_panel` — which enforce it in their
|
|
1098
|
-
// own signatures.
|
|
1099
|
-
create_panel(
|
|
1100
|
-
&this.elem,
|
|
1101
|
-
&this.presentation,
|
|
1102
|
-
&this.workspace,
|
|
1103
|
-
¬ify,
|
|
1104
|
-
id,
|
|
1105
|
-
update,
|
|
1106
|
-
None,
|
|
1107
|
-
)
|
|
1108
|
-
.await?;
|
|
1109
|
-
Ok(())
|
|
1110
|
-
},
|
|
1111
|
-
}
|
|
1129
|
+
Target::Claimed(panel) => {
|
|
1130
|
+
restore_panel(
|
|
1131
|
+
&panel.session,
|
|
1132
|
+
&panel.renderer,
|
|
1133
|
+
&this.presentation,
|
|
1134
|
+
&this.workspace,
|
|
1135
|
+
RestoreMode::Existing { active: true },
|
|
1136
|
+
update,
|
|
1137
|
+
errors,
|
|
1138
|
+
)
|
|
1139
|
+
.await
|
|
1140
|
+
},
|
|
1141
|
+
Target::Create(config) => {
|
|
1142
|
+
create_panel(
|
|
1143
|
+
&this.elem,
|
|
1144
|
+
&this.presentation,
|
|
1145
|
+
&this.workspace,
|
|
1146
|
+
¬ify,
|
|
1147
|
+
id,
|
|
1148
|
+
*config,
|
|
1149
|
+
None,
|
|
1150
|
+
)
|
|
1151
|
+
.await?;
|
|
1152
|
+
Ok(())
|
|
1112
1153
|
},
|
|
1113
1154
|
}
|
|
1114
1155
|
});
|
|
@@ -1116,8 +1157,8 @@ impl PerspectiveViewerElement {
|
|
|
1116
1157
|
js_sys::Promise::from(fut).unchecked_into()
|
|
1117
1158
|
}
|
|
1118
1159
|
|
|
1119
|
-
/// Restore the ENTIRE element from a
|
|
1120
|
-
///
|
|
1160
|
+
/// Restore the ENTIRE element from a [`WorkspaceConfigUpdate`]
|
|
1161
|
+
/// (`{version, active?, layout, panels, ...}`) —
|
|
1121
1162
|
/// the multi-panel counterpart of [`Self::restore`]. Every existing panel
|
|
1122
1163
|
/// is replaced by the `panels` entries, and the layout tree + master/detail
|
|
1123
1164
|
/// cross-filter state re-applied. Unlike [`Self::restore`], this never
|
|
@@ -1226,6 +1267,7 @@ impl PerspectiveViewerElement {
|
|
|
1226
1267
|
let PanelOptions { panel: name } = parse_options(options);
|
|
1227
1268
|
let this = self.clone();
|
|
1228
1269
|
let fut = ApiFuture::new(async move {
|
|
1270
|
+
this.workspace.effects().settle().await;
|
|
1229
1271
|
let panel = this.resolve_panel(name)?;
|
|
1230
1272
|
let viewer_config = panel
|
|
1231
1273
|
.renderer
|
|
@@ -1241,11 +1283,12 @@ impl PerspectiveViewerElement {
|
|
|
1241
1283
|
js_sys::Promise::from(fut).unchecked_into()
|
|
1242
1284
|
}
|
|
1243
1285
|
|
|
1244
|
-
/// Save the ENTIRE element to a
|
|
1245
|
-
/// (`{version, active?, layout, panels, ...}`) — the
|
|
1246
|
-
/// counterpart of [`Self::save`]. Unlike [`Self::save`]
|
|
1247
|
-
/// single `ViewerConfig` for one panel), this ALWAYS
|
|
1248
|
-
///
|
|
1286
|
+
/// Save the ENTIRE element to a [`WorkspaceConfig`]
|
|
1287
|
+
/// (`{version, active?, layout, panels, palette?, ...}`) — the
|
|
1288
|
+
/// multi-panel counterpart of [`Self::save`]. Unlike [`Self::save`]
|
|
1289
|
+
/// (which emits a single `ViewerConfig` for one panel), this ALWAYS
|
|
1290
|
+
/// emits the workspace format, restorable via
|
|
1291
|
+
/// [`Self::restoreWorkspace`].
|
|
1249
1292
|
///
|
|
1250
1293
|
/// # JavaScript Examples
|
|
1251
1294
|
///
|
|
@@ -1253,9 +1296,13 @@ impl PerspectiveViewerElement {
|
|
|
1253
1296
|
/// const token = await viewer.saveWorkspace();
|
|
1254
1297
|
/// await viewer.restoreWorkspace(token);
|
|
1255
1298
|
/// ```
|
|
1256
|
-
pub fn saveWorkspace(
|
|
1299
|
+
pub fn saveWorkspace(
|
|
1300
|
+
&self,
|
|
1301
|
+
options: Option<JsSaveWorkspaceOptions>,
|
|
1302
|
+
) -> JsWorkspaceConfigPromise {
|
|
1303
|
+
let SaveWorkspaceOptions { full_palette } = parse_options(options);
|
|
1257
1304
|
let this = self.clone();
|
|
1258
|
-
let fut = ApiFuture::new(Self::workspace_config(this));
|
|
1305
|
+
let fut = ApiFuture::new(Self::workspace_config(this, full_palette.unwrap_or(false)));
|
|
1259
1306
|
js_sys::Promise::from(fut).unchecked_into()
|
|
1260
1307
|
}
|
|
1261
1308
|
|
|
@@ -1385,9 +1432,10 @@ impl PerspectiveViewerElement {
|
|
|
1385
1432
|
/// Reset a panel's `ViewerConfig` to its data-relative default.
|
|
1386
1433
|
///
|
|
1387
1434
|
/// Without a `panel`, this is ELEMENT-LEVEL: EVERY panel is reset and the
|
|
1388
|
-
/// cross-filter overlay cleared (symmetric with
|
|
1389
|
-
/// [`Self::
|
|
1390
|
-
/// reset — the other panels and the overlay are left
|
|
1435
|
+
/// cross-filter overlay cleared (symmetric with
|
|
1436
|
+
/// [`Self::saveWorkspace`] / [`Self::restoreWorkspace`]). With `{panel}`,
|
|
1437
|
+
/// only that panel is reset — the other panels and the overlay are left
|
|
1438
|
+
/// untouched.
|
|
1391
1439
|
///
|
|
1392
1440
|
/// # Arguments
|
|
1393
1441
|
///
|
|
@@ -1638,17 +1686,15 @@ impl PerspectiveViewerElement {
|
|
|
1638
1686
|
/// ```
|
|
1639
1687
|
#[wasm_bindgen]
|
|
1640
1688
|
pub fn restyleElement(&self) -> ApiFuture<JsValue> {
|
|
1641
|
-
clone!(self.workspace
|
|
1689
|
+
clone!(self.workspace);
|
|
1642
1690
|
let effect = workspace.effects().guard();
|
|
1643
1691
|
ApiFuture::new(async move {
|
|
1644
1692
|
let _effect = effect;
|
|
1645
|
-
let default = presentation.get_default_theme_name().await;
|
|
1646
1693
|
for panel in workspace
|
|
1647
1694
|
.panel_ids()
|
|
1648
1695
|
.into_iter()
|
|
1649
1696
|
.filter_map(|id| workspace.panel(&id))
|
|
1650
1697
|
{
|
|
1651
|
-
panel.renderer.set_default_theme(default.clone());
|
|
1652
1698
|
panel.renderer.restyle_all().await?;
|
|
1653
1699
|
}
|
|
1654
1700
|
|
|
@@ -1656,6 +1702,21 @@ impl PerspectiveViewerElement {
|
|
|
1656
1702
|
})
|
|
1657
1703
|
}
|
|
1658
1704
|
|
|
1705
|
+
#[wasm_bindgen]
|
|
1706
|
+
pub fn getThemes(&self) -> ApiFuture<JsValue> {
|
|
1707
|
+
clone!(self.presentation);
|
|
1708
|
+
ApiFuture::new(async move {
|
|
1709
|
+
let x = presentation
|
|
1710
|
+
.get_available_themes()
|
|
1711
|
+
.await?
|
|
1712
|
+
.iter()
|
|
1713
|
+
.cloned()
|
|
1714
|
+
.collect::<Vec<_>>();
|
|
1715
|
+
|
|
1716
|
+
Ok(JsValue::from(x))
|
|
1717
|
+
})
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1659
1720
|
/// Set the available theme names available in the status bar UI.
|
|
1660
1721
|
///
|
|
1661
1722
|
/// Calling [`Self::resetThemes`] may cause the current theme to switch,
|
|
@@ -1676,46 +1737,41 @@ impl PerspectiveViewerElement {
|
|
|
1676
1737
|
let effect = workspace.effects().guard();
|
|
1677
1738
|
ApiFuture::new(async move {
|
|
1678
1739
|
let _effect = effect;
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1740
|
+
// `None` (re-parse the document) must survive the conversion —
|
|
1741
|
+
// mapping BEFORE defaulting is what keeps that branch reachable
|
|
1742
|
+
// from JavaScript at all.
|
|
1743
|
+
let themes: Option<Vec<String>> = match themes {
|
|
1744
|
+
None => None,
|
|
1745
|
+
Some(themes) => themes.iter().map(|x| x.as_string()).collect(),
|
|
1746
|
+
};
|
|
1684
1747
|
|
|
1685
|
-
let
|
|
1686
|
-
presentation.
|
|
1748
|
+
let previous = presentation.active_theme_name_sync();
|
|
1749
|
+
let active = presentation.reset_themes(themes).await?;
|
|
1687
1750
|
let available = presentation.get_available_themes().await?;
|
|
1688
|
-
let reset_theme = available
|
|
1689
|
-
.iter()
|
|
1690
|
-
.find(|y| theme_name.as_ref() == Some(y))
|
|
1691
|
-
.cloned();
|
|
1692
|
-
|
|
1693
|
-
presentation.set_theme_name(reset_theme.as_deref()).await?;
|
|
1694
|
-
let new_default = presentation.get_default_theme_name().await;
|
|
1695
1751
|
for panel in workspace
|
|
1696
1752
|
.panel_ids()
|
|
1697
1753
|
.into_iter()
|
|
1698
1754
|
.filter_map(|id| workspace.panel(&id))
|
|
1699
1755
|
{
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
//
|
|
1703
|
-
//
|
|
1704
|
-
//
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
panel.renderer.restyle_all().await?;
|
|
1756
|
+
let theme = panel.renderer.theme();
|
|
1757
|
+
|
|
1758
|
+
// A panel follows the host only when it was TRACKING it (it
|
|
1759
|
+
// holds the host's previous theme, which is how every panel
|
|
1760
|
+
// born without an explicit one starts — and what keeps the
|
|
1761
|
+
// active panel and the host in agreement), or when its own
|
|
1762
|
+
// theme has left the registry. A panel explicitly set to a
|
|
1763
|
+
// different, still-available theme is untouched: re-ordering
|
|
1764
|
+
// alone must repaint nothing.
|
|
1765
|
+
let stale = theme.as_ref().is_none_or(|x| !available.contains(x));
|
|
1766
|
+
if (stale || theme == previous) && theme != active {
|
|
1767
|
+
panel.renderer.set_theme(active.clone());
|
|
1768
|
+
if panel.renderer.needs_restyle() {
|
|
1769
|
+
panel.renderer.restyle_all().await?;
|
|
1770
|
+
}
|
|
1716
1771
|
}
|
|
1717
1772
|
}
|
|
1718
1773
|
|
|
1774
|
+
presentation.publish_theme_config().await?;
|
|
1719
1775
|
Ok(JsValue::UNDEFINED)
|
|
1720
1776
|
})
|
|
1721
1777
|
}
|
|
@@ -1770,7 +1826,7 @@ impl PerspectiveViewerElement {
|
|
|
1770
1826
|
let force = force.map(SettingsUpdate::Update);
|
|
1771
1827
|
let (sender, receiver) = channel::<ApiResult<wasm_bindgen::JsValue>>();
|
|
1772
1828
|
root.borrow().as_ref().into_apierror()?.send_message(
|
|
1773
|
-
PerspectiveViewerMsg::ToggleSettingsInit(force, Some(sender)),
|
|
1829
|
+
PerspectiveViewerMsg::ToggleSettingsInit(force, true, Some(sender)),
|
|
1774
1830
|
);
|
|
1775
1831
|
|
|
1776
1832
|
receiver.await.map_err(|_| JsValue::from("Cancelled"))?
|
|
@@ -1824,8 +1880,6 @@ impl PerspectiveViewerElement {
|
|
|
1824
1880
|
let notify = self.layout_changed_notify();
|
|
1825
1881
|
ApiFuture::new(async move {
|
|
1826
1882
|
let _effect = effect;
|
|
1827
|
-
// The table-required guarantee is HERE, in the argument type
|
|
1828
|
-
// and this decode — `create_panel` itself takes a patch.
|
|
1829
1883
|
let config = ViewerConfigInitial::decode(&config)?;
|
|
1830
1884
|
let id = create_panel(
|
|
1831
1885
|
&elem,
|
|
@@ -1833,7 +1887,7 @@ impl PerspectiveViewerElement {
|
|
|
1833
1887
|
&workspace,
|
|
1834
1888
|
¬ify,
|
|
1835
1889
|
None,
|
|
1836
|
-
config
|
|
1890
|
+
config,
|
|
1837
1891
|
None,
|
|
1838
1892
|
)
|
|
1839
1893
|
.await?;
|
|
@@ -1931,7 +1985,12 @@ impl PerspectiveViewerElement {
|
|
|
1931
1985
|
let _effect = effect;
|
|
1932
1986
|
let panel = this.resolve_panel(name)?;
|
|
1933
1987
|
let was_active = this.workspace.active_id().as_ref() == Some(&panel.id);
|
|
1934
|
-
let
|
|
1988
|
+
let target = {
|
|
1989
|
+
let config = panel.session.get_view_config();
|
|
1990
|
+
let metadata = panel.session.metadata();
|
|
1991
|
+
classify_column(&column_name, &config, &metadata)
|
|
1992
|
+
.map(|_| crate::presentation::ColumnSettingsTarget::Column(column_name))
|
|
1993
|
+
};
|
|
1935
1994
|
if !was_active {
|
|
1936
1995
|
this.root.borrow().as_ref().into_apierror()?.send_message(
|
|
1937
1996
|
PerspectiveViewerMsg::SetActivePanel(panel.id.as_str().to_owned(), None),
|
|
@@ -1941,7 +2000,7 @@ impl PerspectiveViewerElement {
|
|
|
1941
2000
|
let (sender, receiver) = channel::<()>();
|
|
1942
2001
|
this.root.borrow().as_ref().into_apierror()?.send_message(
|
|
1943
2002
|
PerspectiveViewerMsg::OpenColumnSettings {
|
|
1944
|
-
|
|
2003
|
+
target,
|
|
1945
2004
|
sender: Some(sender),
|
|
1946
2005
|
toggle: was_active,
|
|
1947
2006
|
},
|
|
@@ -150,6 +150,10 @@ fn dispatch_config_update(
|
|
|
150
150
|
renderer: &Renderer,
|
|
151
151
|
presentation: &Presentation,
|
|
152
152
|
) {
|
|
153
|
+
if session.get_table().is_none() && session.pending_table().is_none() {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
153
157
|
clone!(session, renderer, presentation);
|
|
154
158
|
let elem = elem.clone();
|
|
155
159
|
let tracker = session.clone();
|
|
@@ -218,7 +222,16 @@ pub fn wire_element_events(
|
|
|
218
222
|
|
|
219
223
|
let settings_sub = presentation.settings_open_changed.add_listener({
|
|
220
224
|
clone!(elem, presentation, workspace);
|
|
221
|
-
move |open: bool| {
|
|
225
|
+
move |(open, announce): (bool, bool)| {
|
|
226
|
+
// A silent toggle (`restore`/`restoreWorkspace`) is announced
|
|
227
|
+
// by its own view-config commit dispatch — emitting here too
|
|
228
|
+
// would double-fire `perspective-config-update`, the first
|
|
229
|
+
// carrying the intermediate config (see
|
|
230
|
+
// `PresentationHandle::settings_open_changed`).
|
|
231
|
+
if !announce {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
222
235
|
dispatch_event(&elem, "toggle-settings", open).unwrap();
|
|
223
236
|
if let Some(panel) = workspace.active_panel() {
|
|
224
237
|
dispatch_config_update(&elem, &panel.session, &panel.renderer, &presentation);
|
|
@@ -235,9 +235,9 @@ impl Layout {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/// Rewrite every panel id through `f`, returning the remapped tree. Ids for
|
|
238
|
-
/// which `f` returns `None` are left unchanged. Used by
|
|
239
|
-
///
|
|
240
|
-
///
|
|
238
|
+
/// which `f` returns `None` are left unchanged. Used by `restoreWorkspace`,
|
|
239
|
+
/// which recreates panels under fresh (collision-free) ids and must point
|
|
240
|
+
/// the saved layout tree at them.
|
|
241
241
|
pub fn remap(&self, f: &impl Fn(&str) -> Option<String>) -> Layout {
|
|
242
242
|
match self {
|
|
243
243
|
Layout::Split(split) => Layout::Split(SplitLayout {
|
package/src/rust/lib.rs
CHANGED
|
@@ -95,6 +95,7 @@ export type * from "../../src/ts/ts-rs/WorkspaceConfigUpdate.d.ts";
|
|
|
95
95
|
export type * from "../../src/ts/ts-rs/ExportMethod.d.ts";
|
|
96
96
|
export type * from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
|
97
97
|
export type * from "../../src/ts/ts-rs/RestoreOptions.d.ts";
|
|
98
|
+
export type * from "../../src/ts/ts-rs/SaveWorkspaceOptions.d.ts";
|
|
98
99
|
export type * from "../../src/ts/ts-rs/ClientOptions.d.ts";
|
|
99
100
|
export type * from "../../src/ts/ts-rs/ExportOptions.d.ts";
|
|
100
101
|
export type * from "../../src/ts/ts-rs/GetTableOptions.d.ts";
|
|
@@ -109,6 +110,7 @@ export type * from "../../src/ts/ts-rs/FormatMode.d.ts";
|
|
|
109
110
|
import type {GetTableOptions} from "../../src/ts/ts-rs/GetTableOptions.d.ts";
|
|
110
111
|
import type {PanelOptions} from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
|
111
112
|
import type {RestoreOptions} from "../../src/ts/ts-rs/RestoreOptions.d.ts";
|
|
113
|
+
import type {SaveWorkspaceOptions} from "../../src/ts/ts-rs/SaveWorkspaceOptions.d.ts";
|
|
112
114
|
import type {ExportOptions} from "../../src/ts/ts-rs/ExportOptions.d.ts";
|
|
113
115
|
import type {GetClientOptions} from "../../src/ts/ts-rs/GetClientOptions.d.ts";
|
|
114
116
|
import type {ClientOptions} from "../../src/ts/ts-rs/ClientOptions.d.ts";
|