@perspective-dev/viewer 5.1.0 → 5.3.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 +9 -9
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.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/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/monokai.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/docs/perspective-docs.json +1 -0
- package/dist/esm/column-format.d.ts +19 -28
- package/dist/esm/perspective-viewer.d.ts +3 -0
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +10 -0
- package/dist/esm/providers.d.ts +79 -0
- package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
- package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
- package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
- package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
- package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
- package/dist/esm/ts-rs/Notation.d.ts +17 -0
- package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +42 -2
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
- package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
- package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
- package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
- package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
- package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
- package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/Unit.d.ts +1 -0
- package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
- package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +42 -2
- package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +63 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +3 -2
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +20 -9
- package/dist/wasm/perspective-viewer.d.ts +157 -41
- package/dist/wasm/perspective-viewer.js +452 -151
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +22 -17
- package/package.json +2 -1
- package/src/css/_viewer_bundle.css +1 -0
- package/src/css/column-selector.css +8 -0
- package/src/css/column-settings-panel.css +9 -5
- package/src/css/column-style.css +209 -31
- package/src/css/config-selector.css +38 -3
- package/src/css/dom/select.css +2 -1
- package/src/css/form/code-editor.css +6 -7
- package/src/css/form/mirrored-textarea.css +81 -0
- package/src/css/viewer.css +328 -0
- package/src/rust/agent/client/mod.rs +19 -0
- package/src/rust/agent/client/protocol.rs +320 -0
- package/src/rust/agent/client/transport.rs +287 -0
- package/src/rust/agent/client/turn.rs +174 -0
- package/src/rust/agent/config.rs +159 -0
- package/src/rust/agent/docs.rs +293 -0
- package/src/rust/agent/markdown/block.rs +509 -0
- package/src/rust/agent/markdown/entity.rs +52 -0
- package/src/rust/agent/markdown/events.rs +71 -0
- package/src/rust/agent/markdown/inline.rs +497 -0
- package/src/rust/agent/markdown/mod.rs +466 -0
- package/src/rust/agent/mod.rs +23 -0
- package/src/rust/agent/preamble.md +16 -0
- package/src/rust/agent/runtime.rs +201 -0
- package/src/rust/agent/slot.rs +236 -0
- package/src/rust/agent/tools.rs +1141 -0
- package/src/rust/components/chat_panel.rs +289 -0
- 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 +84 -7
- 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 +55 -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 +262 -225
- package/src/rust/components/column_settings_sidebar.rs +88 -69
- package/src/rust/components/containers/dragdrop_list.rs +37 -0
- 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 +29 -28
- package/src/rust/components/form/debug.rs +9 -5
- package/src/rust/components/form/mirrored_textarea.rs +165 -0
- package/src/rust/components/form/mod.rs +4 -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/mod.rs +2 -0
- package/src/rust/components/plugin_tab.rs +4 -28
- package/src/rust/components/settings_panel.rs +105 -22
- package/src/rust/components/viewer/msg.rs +46 -7
- package/src/rust/components/viewer/panels.rs +17 -20
- package/src/rust/components/viewer/render.rs +10 -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/components/window_editor.rs +75 -103
- package/src/rust/config/column_config_schema.rs +100 -41
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/datetime_column_style/custom.rs +17 -0
- package/src/rust/config/datetime_column_style/simple.rs +10 -0
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/number_string_format.rs +33 -0
- package/src/rust/config/options.rs +28 -0
- package/src/rust/config/plugin_static_config.rs +120 -5
- package/src/rust/config/viewer_config.rs +216 -3
- package/src/rust/config/workspace_config.rs +138 -10
- package/src/rust/custom_elements/viewer.rs +369 -119
- package/src/rust/custom_events.rs +43 -1
- package/src/rust/js/regular_layout.rs +3 -3
- package/src/rust/lib.rs +15 -0
- package/src/rust/presentation/column_locator.rs +19 -5
- package/src/rust/presentation.rs +200 -36
- package/src/rust/queries/column_locator.rs +74 -44
- package/src/rust/queries/get_viewer_config.rs +2 -1
- 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 +16 -4
- package/src/rust/renderer/plugin_config.rs +309 -47
- package/src/rust/renderer.rs +15 -42
- package/src/rust/session/column_defaults_update.rs +32 -7
- package/src/rust/session/metadata.rs +23 -21
- package/src/rust/session/props.rs +1 -0
- package/src/rust/session/replace_expression_update.rs +1 -0
- package/src/rust/session.rs +9 -6
- package/src/rust/tasks/create_panel.rs +46 -49
- package/src/rust/tasks/edit_expression.rs +19 -23
- package/src/rust/tasks/edit_window.rs +9 -11
- 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 +37 -22
- package/src/rust/tasks/set_edit_mode.rs +3 -1
- package/src/rust/tasks/sync_update_panels.rs +18 -24
- 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 +454 -199
- package/src/svg/lock.svg +7 -0
- package/src/themes/botanical.css +10 -0
- package/src/themes/defaults.css +12 -0
- package/src/themes/dracula.css +17 -0
- package/src/themes/gruvbox-dark.css +20 -0
- package/src/themes/gruvbox.css +10 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/de.css +16 -0
- package/src/themes/intl/es.css +16 -0
- package/src/themes/intl/fr.css +16 -0
- package/src/themes/intl/ja.css +16 -0
- package/src/themes/intl/pt.css +16 -0
- package/src/themes/intl/zh.css +16 -0
- package/src/themes/intl.css +16 -0
- package/src/themes/monokai.css +22 -0
- package/src/themes/phosphor.css +5 -0
- package/src/themes/pro-dark.css +10 -0
- package/src/themes/solarized-dark.css +7 -0
- package/src/themes/solarized.css +7 -0
- package/src/themes/vaporwave.css +20 -0
- package/src/ts/column-format.ts +67 -46
- package/src/ts/perspective-viewer.ts +3 -0
- package/src/ts/plugin.ts +14 -0
- package/src/ts/providers.ts +96 -0
- package/src/ts/ts-rs/CompactDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyCode.ts +3 -0
- package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
- package/src/ts/ts-rs/CurrencySign.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
- package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
- package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
- package/src/ts/ts-rs/FormatMode.ts +3 -0
- package/src/ts/ts-rs/FormatUnit.ts +3 -0
- package/src/ts/ts-rs/Notation.ts +11 -0
- package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +48 -2
- package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
- package/src/ts/ts-rs/RestoreOptions.ts +20 -0
- package/src/ts/ts-rs/RoundingMode.ts +3 -0
- package/src/ts/ts-rs/RoundingPriority.ts +3 -0
- package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
- package/src/ts/ts-rs/SignDisplay.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
- package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
- package/src/ts/ts-rs/StringColorMode.ts +3 -0
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
- package/src/ts/ts-rs/Unit.ts +3 -0
- package/src/ts/ts-rs/UnitDisplay.ts +3 -0
- package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
- package/src/ts/ts-rs/UseGrouping.ts +3 -0
- package/src/ts/ts-rs/ViewerConfig.ts +48 -2
- package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +72 -2
- package/src/ts/ts-rs/WindowSpec.ts +3 -2
- package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +18 -9
- package/dist/esm/ts-rs/WindowAggregate.d.ts +0 -1
- package/src/rust/components/form/color_range_selector.rs +0 -137
- package/src/ts/ts-rs/WindowAggregate.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
|
@@ -29,6 +29,8 @@ use wasm_bindgen_futures::JsFuture;
|
|
|
29
29
|
use web_sys::HtmlElement;
|
|
30
30
|
use yew::Callback;
|
|
31
31
|
|
|
32
|
+
#[cfg(feature = "llm-agent")]
|
|
33
|
+
use crate::agent::AgentRuntime;
|
|
32
34
|
use crate::components::viewer::{PerspectiveViewerMsg, PerspectiveViewerProps};
|
|
33
35
|
use crate::config::*;
|
|
34
36
|
use crate::custom_events::*;
|
|
@@ -59,6 +61,16 @@ extern "C" {
|
|
|
59
61
|
#[wasm_bindgen(typescript_type = "PanelOptions")]
|
|
60
62
|
pub type JsPanelOptions;
|
|
61
63
|
|
|
64
|
+
/// `restore()` options dict
|
|
65
|
+
/// (`{ panel?: string, suppress_errors?: boolean }`).
|
|
66
|
+
#[wasm_bindgen(typescript_type = "RestoreOptions")]
|
|
67
|
+
pub type JsRestoreOptions;
|
|
68
|
+
|
|
69
|
+
/// `addPanel()` argument: a new panel's initial config — `table`
|
|
70
|
+
/// REQUIRED.
|
|
71
|
+
#[wasm_bindgen(typescript_type = "ViewerConfigInitial")]
|
|
72
|
+
pub type JsViewerConfigInitial;
|
|
73
|
+
|
|
62
74
|
/// `download`/`export`/`copy` options dict
|
|
63
75
|
/// (`{ method?: ExportMethod, panel?: string }`).
|
|
64
76
|
#[wasm_bindgen(typescript_type = "ExportOptions")]
|
|
@@ -72,11 +84,15 @@ extern "C" {
|
|
|
72
84
|
#[wasm_bindgen(typescript_type = "GetClientOptions")]
|
|
73
85
|
pub type JsGetClientOptions;
|
|
74
86
|
|
|
75
|
-
/// `restoreWorkspace()` argument: a
|
|
87
|
+
/// `restoreWorkspace()` argument: a workspace config update.
|
|
76
88
|
#[wasm_bindgen(typescript_type = "WorkspaceConfigUpdate")]
|
|
77
89
|
pub type JsWorkspaceConfigUpdate;
|
|
78
90
|
|
|
79
|
-
/// `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.
|
|
80
96
|
#[wasm_bindgen(typescript_type = "Promise<WorkspaceConfig>")]
|
|
81
97
|
pub type JsWorkspaceConfigPromise;
|
|
82
98
|
|
|
@@ -189,7 +205,8 @@ impl PerspectiveViewerElement {
|
|
|
189
205
|
.map(|el| el.unchecked_into())
|
|
190
206
|
}
|
|
191
207
|
|
|
192
|
-
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;
|
|
193
210
|
let mut panels: std::collections::BTreeMap<String, PanelViewerConfig> = Default::default();
|
|
194
211
|
for id in &this.workspace.panel_ids() {
|
|
195
212
|
let panel = this.workspace.panel(id).into_apierror()?;
|
|
@@ -204,6 +221,26 @@ impl PerspectiveViewerElement {
|
|
|
204
221
|
panels.insert(id.as_str().to_owned(), config.panel);
|
|
205
222
|
}
|
|
206
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
|
+
|
|
207
244
|
let active = this
|
|
208
245
|
.presentation
|
|
209
246
|
.is_settings_open()
|
|
@@ -228,6 +265,7 @@ impl PerspectiveViewerElement {
|
|
|
228
265
|
.iter()
|
|
229
266
|
.map(|id| id.as_str().to_owned())
|
|
230
267
|
.collect(),
|
|
268
|
+
palette,
|
|
231
269
|
})?)
|
|
232
270
|
}
|
|
233
271
|
}
|
|
@@ -509,7 +547,7 @@ impl PerspectiveViewerElement {
|
|
|
509
547
|
renderer
|
|
510
548
|
.clone()
|
|
511
549
|
.render_task(|guard| async move {
|
|
512
|
-
|
|
550
|
+
seed_panel_theme(&presentation, &renderer).await;
|
|
513
551
|
renderer.stamp_theme(None);
|
|
514
552
|
let jstable = JsFuture::from(promise)
|
|
515
553
|
.await
|
|
@@ -524,7 +562,7 @@ impl PerspectiveViewerElement {
|
|
|
524
562
|
};
|
|
525
563
|
|
|
526
564
|
if let Some(notify) = ¬ify {
|
|
527
|
-
place_reserved(&workspace, notify);
|
|
565
|
+
place_reserved(&workspace, notify, true);
|
|
528
566
|
}
|
|
529
567
|
|
|
530
568
|
let _plugin = renderer.ensure_plugin_selected()?;
|
|
@@ -582,7 +620,35 @@ impl PerspectiveViewerElement {
|
|
|
582
620
|
// commits already applied live (`commit_view_config`).
|
|
583
621
|
let owned_window = session.take_pending_load(generation).is_some();
|
|
584
622
|
let discard = if owned_window && notify.is_some() {
|
|
585
|
-
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
|
+
}
|
|
586
652
|
} else {
|
|
587
653
|
None
|
|
588
654
|
};
|
|
@@ -601,13 +667,19 @@ impl PerspectiveViewerElement {
|
|
|
601
667
|
Err(e) => {
|
|
602
668
|
session.take_pending_load(generation);
|
|
603
669
|
if let Some(notify) = ¬ify {
|
|
604
|
-
place_reserved(&workspace, notify);
|
|
670
|
+
place_reserved(&workspace, notify, true);
|
|
605
671
|
}
|
|
606
672
|
|
|
607
673
|
session.set_error(false, e.clone()).await?;
|
|
608
674
|
Err(e)
|
|
609
675
|
},
|
|
610
|
-
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
|
+
},
|
|
611
683
|
Ok(None) => Ok(()),
|
|
612
684
|
}
|
|
613
685
|
}))
|
|
@@ -919,15 +991,18 @@ impl PerspectiveViewerElement {
|
|
|
919
991
|
/// optional `{panel}` selector.
|
|
920
992
|
///
|
|
921
993
|
/// If `panel` names no existing panel, a NEW panel is created with that id
|
|
922
|
-
/// and the config restored into it (an upsert)
|
|
923
|
-
/// [`Self::addPanel`]
|
|
924
|
-
///
|
|
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`.
|
|
925
1000
|
///
|
|
926
1001
|
/// On an empty element with a pending [`Self::load`] whose payload is not
|
|
927
1002
|
/// yet classified, the active-target form (no `panel`) instead claims and
|
|
928
1003
|
/// restores into that load's reserved first panel — see [`Self::load`].
|
|
929
1004
|
///
|
|
930
|
-
/// This restores a SINGLE panel; a
|
|
1005
|
+
/// This restores a SINGLE panel; a workspace config (with a `panels`
|
|
931
1006
|
/// map) must be applied via [`Self::restoreWorkspace`] — its `panels` /
|
|
932
1007
|
/// `layout` keys are ignored here.
|
|
933
1008
|
///
|
|
@@ -940,7 +1015,15 @@ impl PerspectiveViewerElement {
|
|
|
940
1015
|
///
|
|
941
1016
|
/// - `update` - The config to restore to, as returned by [`Self::save`] in
|
|
942
1017
|
/// either "json", "string" or "arraybuffer" format.
|
|
943
|
-
/// - `
|
|
1018
|
+
/// - `options.panel` - The panel to target, or the active panel when
|
|
1019
|
+
/// omitted.
|
|
1020
|
+
/// - `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.
|
|
944
1027
|
///
|
|
945
1028
|
/// # JavaScript Examples
|
|
946
1029
|
///
|
|
@@ -960,73 +1043,113 @@ impl PerspectiveViewerElement {
|
|
|
960
1043
|
pub fn restore(
|
|
961
1044
|
&self,
|
|
962
1045
|
update: JsViewerConfigUpdate,
|
|
963
|
-
options: Option<
|
|
1046
|
+
options: Option<JsRestoreOptions>,
|
|
964
1047
|
) -> JsVoidPromise {
|
|
965
|
-
let
|
|
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
|
+
|
|
966
1059
|
let effect = self.workspace.effects().guard();
|
|
967
1060
|
let this = self.clone();
|
|
968
1061
|
let fut = ApiFuture::new_throttled(async move {
|
|
969
1062
|
let _effect = effect;
|
|
970
1063
|
let id = name.map(PanelId::from);
|
|
971
|
-
let update = ViewerConfigUpdate::decode(&update)?;
|
|
972
|
-
|
|
1064
|
+
let mut update = ViewerConfigUpdate::decode(&update)?;
|
|
1065
|
+
let settings = std::mem::replace(&mut update.settings, OptionalUpdate::Missing);
|
|
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()) {
|
|
973
1074
|
// An existing (or the active) panel — update it in place.
|
|
974
1075
|
Some(panel) => {
|
|
975
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
|
+
};
|
|
1093
|
+
|
|
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>>();
|
|
1109
|
+
this.root.borrow().as_ref().into_apierror()?.send_message(
|
|
1110
|
+
PerspectiveViewerMsg::ToggleSettingsInit(Some(settings), false, Some(sender)),
|
|
1111
|
+
);
|
|
1112
|
+
|
|
1113
|
+
receiver.await.map_err(|_| ApiError::new("Cancelled"))??;
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
match target {
|
|
1117
|
+
Target::Existing { panel, active } => {
|
|
976
1118
|
restore_panel(
|
|
977
1119
|
&panel.session,
|
|
978
1120
|
&panel.renderer,
|
|
979
1121
|
&this.presentation,
|
|
980
1122
|
&this.workspace,
|
|
981
|
-
Some(&this.root),
|
|
982
1123
|
RestoreMode::Existing { active },
|
|
983
1124
|
update,
|
|
1125
|
+
errors,
|
|
984
1126
|
)
|
|
985
1127
|
.await
|
|
986
1128
|
},
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
.
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
RestoreMode::Existing { active: true },
|
|
1012
|
-
update,
|
|
1013
|
-
)
|
|
1014
|
-
.await
|
|
1015
|
-
},
|
|
1016
|
-
None => {
|
|
1017
|
-
create_panel(
|
|
1018
|
-
&this.elem,
|
|
1019
|
-
&this.presentation,
|
|
1020
|
-
&this.workspace,
|
|
1021
|
-
¬ify,
|
|
1022
|
-
id,
|
|
1023
|
-
update,
|
|
1024
|
-
None,
|
|
1025
|
-
)
|
|
1026
|
-
.await?;
|
|
1027
|
-
Ok(())
|
|
1028
|
-
},
|
|
1029
|
-
}
|
|
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(())
|
|
1030
1153
|
},
|
|
1031
1154
|
}
|
|
1032
1155
|
});
|
|
@@ -1034,8 +1157,8 @@ impl PerspectiveViewerElement {
|
|
|
1034
1157
|
js_sys::Promise::from(fut).unchecked_into()
|
|
1035
1158
|
}
|
|
1036
1159
|
|
|
1037
|
-
/// Restore the ENTIRE element from a
|
|
1038
|
-
///
|
|
1160
|
+
/// Restore the ENTIRE element from a [`WorkspaceConfigUpdate`]
|
|
1161
|
+
/// (`{version, active?, layout, panels, ...}`) —
|
|
1039
1162
|
/// the multi-panel counterpart of [`Self::restore`]. Every existing panel
|
|
1040
1163
|
/// is replaced by the `panels` entries, and the layout tree + master/detail
|
|
1041
1164
|
/// cross-filter state re-applied. Unlike [`Self::restore`], this never
|
|
@@ -1063,9 +1186,9 @@ impl PerspectiveViewerElement {
|
|
|
1063
1186
|
&renderer,
|
|
1064
1187
|
&presentation,
|
|
1065
1188
|
&workspace,
|
|
1066
|
-
None,
|
|
1067
1189
|
RestoreMode::Fresh,
|
|
1068
1190
|
config,
|
|
1191
|
+
crate::tasks::RestoreErrors::Publish,
|
|
1069
1192
|
)
|
|
1070
1193
|
.await?;
|
|
1071
1194
|
if workspace.is_master(&id) {
|
|
@@ -1144,6 +1267,7 @@ impl PerspectiveViewerElement {
|
|
|
1144
1267
|
let PanelOptions { panel: name } = parse_options(options);
|
|
1145
1268
|
let this = self.clone();
|
|
1146
1269
|
let fut = ApiFuture::new(async move {
|
|
1270
|
+
this.workspace.effects().settle().await;
|
|
1147
1271
|
let panel = this.resolve_panel(name)?;
|
|
1148
1272
|
let viewer_config = panel
|
|
1149
1273
|
.renderer
|
|
@@ -1159,11 +1283,12 @@ impl PerspectiveViewerElement {
|
|
|
1159
1283
|
js_sys::Promise::from(fut).unchecked_into()
|
|
1160
1284
|
}
|
|
1161
1285
|
|
|
1162
|
-
/// Save the ENTIRE element to a
|
|
1163
|
-
/// (`{version, active?, layout, panels, ...}`) — the
|
|
1164
|
-
/// counterpart of [`Self::save`]. Unlike [`Self::save`]
|
|
1165
|
-
/// single `ViewerConfig` for one panel), this ALWAYS
|
|
1166
|
-
///
|
|
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`].
|
|
1167
1292
|
///
|
|
1168
1293
|
/// # JavaScript Examples
|
|
1169
1294
|
///
|
|
@@ -1171,9 +1296,13 @@ impl PerspectiveViewerElement {
|
|
|
1171
1296
|
/// const token = await viewer.saveWorkspace();
|
|
1172
1297
|
/// await viewer.restoreWorkspace(token);
|
|
1173
1298
|
/// ```
|
|
1174
|
-
pub fn saveWorkspace(
|
|
1299
|
+
pub fn saveWorkspace(
|
|
1300
|
+
&self,
|
|
1301
|
+
options: Option<JsSaveWorkspaceOptions>,
|
|
1302
|
+
) -> JsWorkspaceConfigPromise {
|
|
1303
|
+
let SaveWorkspaceOptions { full_palette } = parse_options(options);
|
|
1175
1304
|
let this = self.clone();
|
|
1176
|
-
let fut = ApiFuture::new(Self::workspace_config(this));
|
|
1305
|
+
let fut = ApiFuture::new(Self::workspace_config(this, full_palette.unwrap_or(false)));
|
|
1177
1306
|
js_sys::Promise::from(fut).unchecked_into()
|
|
1178
1307
|
}
|
|
1179
1308
|
|
|
@@ -1303,9 +1432,10 @@ impl PerspectiveViewerElement {
|
|
|
1303
1432
|
/// Reset a panel's `ViewerConfig` to its data-relative default.
|
|
1304
1433
|
///
|
|
1305
1434
|
/// Without a `panel`, this is ELEMENT-LEVEL: EVERY panel is reset and the
|
|
1306
|
-
/// cross-filter overlay cleared (symmetric with
|
|
1307
|
-
/// [`Self::
|
|
1308
|
-
/// 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.
|
|
1309
1439
|
///
|
|
1310
1440
|
/// # Arguments
|
|
1311
1441
|
///
|
|
@@ -1556,17 +1686,15 @@ impl PerspectiveViewerElement {
|
|
|
1556
1686
|
/// ```
|
|
1557
1687
|
#[wasm_bindgen]
|
|
1558
1688
|
pub fn restyleElement(&self) -> ApiFuture<JsValue> {
|
|
1559
|
-
clone!(self.workspace
|
|
1689
|
+
clone!(self.workspace);
|
|
1560
1690
|
let effect = workspace.effects().guard();
|
|
1561
1691
|
ApiFuture::new(async move {
|
|
1562
1692
|
let _effect = effect;
|
|
1563
|
-
let default = presentation.get_default_theme_name().await;
|
|
1564
1693
|
for panel in workspace
|
|
1565
1694
|
.panel_ids()
|
|
1566
1695
|
.into_iter()
|
|
1567
1696
|
.filter_map(|id| workspace.panel(&id))
|
|
1568
1697
|
{
|
|
1569
|
-
panel.renderer.set_default_theme(default.clone());
|
|
1570
1698
|
panel.renderer.restyle_all().await?;
|
|
1571
1699
|
}
|
|
1572
1700
|
|
|
@@ -1574,6 +1702,21 @@ impl PerspectiveViewerElement {
|
|
|
1574
1702
|
})
|
|
1575
1703
|
}
|
|
1576
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
|
+
|
|
1577
1720
|
/// Set the available theme names available in the status bar UI.
|
|
1578
1721
|
///
|
|
1579
1722
|
/// Calling [`Self::resetThemes`] may cause the current theme to switch,
|
|
@@ -1594,46 +1737,41 @@ impl PerspectiveViewerElement {
|
|
|
1594
1737
|
let effect = workspace.effects().guard();
|
|
1595
1738
|
ApiFuture::new(async move {
|
|
1596
1739
|
let _effect = effect;
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
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
|
+
};
|
|
1602
1747
|
|
|
1603
|
-
let
|
|
1604
|
-
presentation.
|
|
1748
|
+
let previous = presentation.active_theme_name_sync();
|
|
1749
|
+
let active = presentation.reset_themes(themes).await?;
|
|
1605
1750
|
let available = presentation.get_available_themes().await?;
|
|
1606
|
-
let reset_theme = available
|
|
1607
|
-
.iter()
|
|
1608
|
-
.find(|y| theme_name.as_ref() == Some(y))
|
|
1609
|
-
.cloned();
|
|
1610
|
-
|
|
1611
|
-
presentation.set_theme_name(reset_theme.as_deref()).await?;
|
|
1612
|
-
let new_default = presentation.get_default_theme_name().await;
|
|
1613
1751
|
for panel in workspace
|
|
1614
1752
|
.panel_ids()
|
|
1615
1753
|
.into_iter()
|
|
1616
1754
|
.filter_map(|id| workspace.panel(&id))
|
|
1617
1755
|
{
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
//
|
|
1621
|
-
//
|
|
1622
|
-
//
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
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
|
+
}
|
|
1634
1771
|
}
|
|
1635
1772
|
}
|
|
1636
1773
|
|
|
1774
|
+
presentation.publish_theme_config().await?;
|
|
1637
1775
|
Ok(JsValue::UNDEFINED)
|
|
1638
1776
|
})
|
|
1639
1777
|
}
|
|
@@ -1688,7 +1826,7 @@ impl PerspectiveViewerElement {
|
|
|
1688
1826
|
let force = force.map(SettingsUpdate::Update);
|
|
1689
1827
|
let (sender, receiver) = channel::<ApiResult<wasm_bindgen::JsValue>>();
|
|
1690
1828
|
root.borrow().as_ref().into_apierror()?.send_message(
|
|
1691
|
-
PerspectiveViewerMsg::ToggleSettingsInit(force, Some(sender)),
|
|
1829
|
+
PerspectiveViewerMsg::ToggleSettingsInit(force, true, Some(sender)),
|
|
1692
1830
|
);
|
|
1693
1831
|
|
|
1694
1832
|
receiver.await.map_err(|_| JsValue::from("Cancelled"))?
|
|
@@ -1726,27 +1864,30 @@ impl PerspectiveViewerElement {
|
|
|
1726
1864
|
}
|
|
1727
1865
|
|
|
1728
1866
|
/// Add a new, independent panel to this viewer's layout, rendering the
|
|
1729
|
-
/// supplied [`
|
|
1730
|
-
///
|
|
1731
|
-
///
|
|
1732
|
-
///
|
|
1733
|
-
///
|
|
1734
|
-
///
|
|
1867
|
+
/// supplied [`ViewerConfigInitial`] into it. Unlike [`Self::restore`]'s
|
|
1868
|
+
/// update-shaped argument, a new panel has no prior state, so `table`
|
|
1869
|
+
/// is REQUIRED — a table-less call rejects before the layout is
|
|
1870
|
+
/// touched. The panel uses the default [`perspective_client::Client`]
|
|
1871
|
+
/// (the first passed to [`Self::load`]) to resolve its `table`. Returns
|
|
1872
|
+
/// the generated panel id.
|
|
1873
|
+
///
|
|
1874
|
+
/// The element-level `settings` field does not exist on the argument
|
|
1875
|
+
/// type (it is shared across the element, not per-panel).
|
|
1735
1876
|
#[wasm_bindgen]
|
|
1736
|
-
pub fn addPanel(&self,
|
|
1877
|
+
pub fn addPanel(&self, config: JsViewerConfigInitial) -> ApiFuture<JsValue> {
|
|
1737
1878
|
clone!(self.elem, self.presentation, self.workspace);
|
|
1738
1879
|
let effect = workspace.effects().guard();
|
|
1739
1880
|
let notify = self.layout_changed_notify();
|
|
1740
1881
|
ApiFuture::new(async move {
|
|
1741
1882
|
let _effect = effect;
|
|
1742
|
-
let
|
|
1883
|
+
let config = ViewerConfigInitial::decode(&config)?;
|
|
1743
1884
|
let id = create_panel(
|
|
1744
1885
|
&elem,
|
|
1745
1886
|
&presentation,
|
|
1746
1887
|
&workspace,
|
|
1747
1888
|
¬ify,
|
|
1748
1889
|
None,
|
|
1749
|
-
|
|
1890
|
+
config,
|
|
1750
1891
|
None,
|
|
1751
1892
|
)
|
|
1752
1893
|
.await?;
|
|
@@ -1844,7 +1985,12 @@ impl PerspectiveViewerElement {
|
|
|
1844
1985
|
let _effect = effect;
|
|
1845
1986
|
let panel = this.resolve_panel(name)?;
|
|
1846
1987
|
let was_active = this.workspace.active_id().as_ref() == Some(&panel.id);
|
|
1847
|
-
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
|
+
};
|
|
1848
1994
|
if !was_active {
|
|
1849
1995
|
this.root.borrow().as_ref().into_apierror()?.send_message(
|
|
1850
1996
|
PerspectiveViewerMsg::SetActivePanel(panel.id.as_str().to_owned(), None),
|
|
@@ -1854,7 +2000,7 @@ impl PerspectiveViewerElement {
|
|
|
1854
2000
|
let (sender, receiver) = channel::<()>();
|
|
1855
2001
|
this.root.borrow().as_ref().into_apierror()?.send_message(
|
|
1856
2002
|
PerspectiveViewerMsg::OpenColumnSettings {
|
|
1857
|
-
|
|
2003
|
+
target,
|
|
1858
2004
|
sender: Some(sender),
|
|
1859
2005
|
toggle: was_active,
|
|
1860
2006
|
},
|
|
@@ -1864,3 +2010,107 @@ impl PerspectiveViewerElement {
|
|
|
1864
2010
|
})
|
|
1865
2011
|
}
|
|
1866
2012
|
}
|
|
2013
|
+
|
|
2014
|
+
#[cfg(feature = "llm-agent")]
|
|
2015
|
+
#[wasm_bindgen]
|
|
2016
|
+
impl PerspectiveViewerElement {
|
|
2017
|
+
/// Configure the embedded LLM agent (see `prompt()`), replacing any prior
|
|
2018
|
+
/// configuration and conversation.
|
|
2019
|
+
///
|
|
2020
|
+
/// The agent core is provider-agnostic: one OpenAI-chat-completions
|
|
2021
|
+
/// protocol over primitive connection fields. Exactly one of
|
|
2022
|
+
/// `config.url` or `config.engine` is required; the `providers` presets
|
|
2023
|
+
/// exported by this package are plain spreadable collections of these
|
|
2024
|
+
/// fields (`{...providers.anthropic, apiKey}`).
|
|
2025
|
+
///
|
|
2026
|
+
/// - `config.url` - a full chat-completions endpoint URL (any
|
|
2027
|
+
/// OpenAI-compatible service: Anthropic/Gemini compatibility endpoints,
|
|
2028
|
+
/// LM Studio, Ollama, OpenRouter, a proxy...).
|
|
2029
|
+
/// - `config.engine` - an in-page engine object with an OpenAI-compatible
|
|
2030
|
+
/// `chat.completions.create(request)` method (e.g. WebLLM's `MLCEngine`);
|
|
2031
|
+
/// mutually exclusive with `url`.
|
|
2032
|
+
/// - `config.headers` - extra request headers, sent verbatim.
|
|
2033
|
+
/// - `config.apiKey` - sugar for the `Authorization: Bearer` header.
|
|
2034
|
+
/// - `config.model` - model id sent with each request; local servers and
|
|
2035
|
+
/// engines generally answer with whatever model is loaded.
|
|
2036
|
+
/// - `config.name` - cosmetic label for the chat badge (presets set this).
|
|
2037
|
+
/// - `config.systemPrompt` - extra system-prompt context appended to the
|
|
2038
|
+
/// agent's built-in instructions.
|
|
2039
|
+
/// - `config.maxTurns` - max model turns (tool-call rounds + the final
|
|
2040
|
+
/// answer) per `prompt()` call. Defaults to 16.
|
|
2041
|
+
/// - `config.docs` - the agent metadata bundle, which supplies the
|
|
2042
|
+
/// `search_docs` corpus and the rich tool parameter schemas: the packaged
|
|
2043
|
+
/// `dist/docs/perspective-docs.json` asset as a parsed object (`import
|
|
2044
|
+
/// docs from "…json" with { type: "json" }`), a `fetch()` `Response`, an
|
|
2045
|
+
/// `ArrayBuffer`, a JSON string, or a `Promise` of any of those — and/or
|
|
2046
|
+
/// an inline array of `{title?, text}` entries for host data definitions.
|
|
2047
|
+
/// Omitted, `search_docs` searches an empty corpus and the parameter
|
|
2048
|
+
/// schemas degrade to permissive objects.
|
|
2049
|
+
/// - `config.systemRole` - where the preamble (plus `systemPrompt`) is
|
|
2050
|
+
/// placed: `"system"` (default) or `"user"`. Some engines refuse a system
|
|
2051
|
+
/// message alongside `tools` because they substitute their own — WebLLM's
|
|
2052
|
+
/// Hermes function calling throws `CustomSystemPromptError` on ANY system
|
|
2053
|
+
/// message — and those need `"user"`, which folds the same text into the
|
|
2054
|
+
/// opening user turn.
|
|
2055
|
+
/// - `config.entitlements` - access grants limiting which tools the agent
|
|
2056
|
+
/// is offered (and may call): any of `"read_view"`, `"configure_view"`,
|
|
2057
|
+
/// `"manage_layout"`, `"read_docs"`, `"read_data"`. Omitted, all but
|
|
2058
|
+
/// `"read_data"` are granted; `["read_view", "read_docs"]` yields a
|
|
2059
|
+
/// read-only agent.
|
|
2060
|
+
///
|
|
2061
|
+
/// # JavaScript Examples
|
|
2062
|
+
///
|
|
2063
|
+
/// ```javascript
|
|
2064
|
+
/// import { providers } from "@perspective-dev/viewer";
|
|
2065
|
+
///
|
|
2066
|
+
/// viewer.agentConfig({
|
|
2067
|
+
/// ...providers.anthropic,
|
|
2068
|
+
/// apiKey: "sk-ant-...",
|
|
2069
|
+
/// docs: fetch(
|
|
2070
|
+
/// "node_modules/@perspective-dev/viewer/dist/docs/perspective-docs.json",
|
|
2071
|
+
/// ),
|
|
2072
|
+
/// });
|
|
2073
|
+
/// ```
|
|
2074
|
+
#[wasm_bindgen(js_name = "agentConfig")]
|
|
2075
|
+
pub fn agent_config(&self, config: JsValue) -> ApiResult<()> {
|
|
2076
|
+
let runtime = AgentRuntime::new(&config, self.clone())?;
|
|
2077
|
+
self.presentation.agent.configure(runtime);
|
|
2078
|
+
Ok(())
|
|
2079
|
+
}
|
|
2080
|
+
|
|
2081
|
+
/// Run one conversational turn of the embedded LLM agent (configured via
|
|
2082
|
+
/// `agentConfig()`), resolving with the agent's final text response after
|
|
2083
|
+
/// any tool calls have been applied to this element. Turns share a
|
|
2084
|
+
/// conversation history (and the chat sidebar's transcript) until
|
|
2085
|
+
/// `agentReset()`; a call made while a turn is already running rejects.
|
|
2086
|
+
/// Tool activity is emitted as `perspective-agent-tool` CustomEvents on
|
|
2087
|
+
/// this element.
|
|
2088
|
+
///
|
|
2089
|
+
/// # JavaScript Examples
|
|
2090
|
+
///
|
|
2091
|
+
/// ```javascript
|
|
2092
|
+
/// await viewer.agentPrompt("Show me sales by region as a bar chart");
|
|
2093
|
+
/// ```
|
|
2094
|
+
#[wasm_bindgen(js_name = "agentPrompt")]
|
|
2095
|
+
pub fn agent_prompt(&self, prompt: String) -> js_sys::Promise {
|
|
2096
|
+
let presentation = self.presentation.clone();
|
|
2097
|
+
let fut = ApiFuture::new(async move {
|
|
2098
|
+
Ok(JsValue::from(presentation.agent.run_prompt(prompt).await?))
|
|
2099
|
+
});
|
|
2100
|
+
|
|
2101
|
+
js_sys::Promise::from(fut)
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
/// Clear the agent's conversation (history and chat transcript), keeping
|
|
2105
|
+
/// its configuration. Cancels any in-flight turn.
|
|
2106
|
+
#[wasm_bindgen(js_name = "agentReset")]
|
|
2107
|
+
pub fn agent_reset(&self) -> js_sys::Promise {
|
|
2108
|
+
let presentation = self.presentation.clone();
|
|
2109
|
+
let fut = ApiFuture::new(async move {
|
|
2110
|
+
presentation.agent.reset().await;
|
|
2111
|
+
Ok(JsValue::UNDEFINED)
|
|
2112
|
+
});
|
|
2113
|
+
|
|
2114
|
+
js_sys::Promise::from(fut)
|
|
2115
|
+
}
|
|
2116
|
+
}
|