@perspective-dev/viewer 4.5.2 → 5.0.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 +20 -1
- 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/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/esm/extensions.d.ts +22 -3
- package/dist/esm/perspective-viewer.d.ts +9 -0
- package/dist/esm/perspective-viewer.inline.js +20 -1
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +20 -1
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +71 -5
- package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
- package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
- package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
- package/dist/esm/ts-rs/Layout.d.ts +11 -0
- package/dist/esm/ts-rs/Orientation.d.ts +4 -0
- package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +39 -0
- package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
- package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -2
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
- package/dist/wasm/perspective-viewer.d.ts +211 -77
- package/dist/wasm/perspective-viewer.js +528 -180
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-daeef4ceb066e6c7/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-daeef4ceb066e6c7/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-settings-panel.css +1 -1
- package/src/css/containers/context-menu.css +101 -0
- package/src/css/panel-tab.css +193 -0
- package/src/css/plugin-selector.css +7 -10
- package/src/css/status-bar.css +75 -81
- package/src/css/viewer.css +180 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +7 -18
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/config_selector.rs +33 -81
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +3 -8
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +5 -11
- package/src/rust/components/containers/split_panel.rs +175 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/form/debug.rs +3 -0
- package/src/rust/components/global_filter_bar.rs +76 -0
- package/src/rust/components/main_panel/frame_theme.rs +186 -0
- package/src/rust/components/main_panel/msg.rs +62 -0
- package/src/rust/components/main_panel/presize.rs +220 -0
- package/src/rust/components/main_panel/reconcile.rs +224 -0
- package/src/rust/components/main_panel/render.rs +385 -0
- package/src/rust/components/main_panel/update.rs +184 -0
- package/src/rust/components/main_panel.rs +311 -71
- package/src/rust/components/mod.rs +4 -0
- package/src/rust/components/panel_menu.rs +486 -0
- package/src/rust/components/panel_tab.rs +505 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/settings_panel.rs +13 -5
- package/src/rust/components/status_bar.rs +110 -178
- package/src/rust/components/status_indicator.rs +6 -9
- package/src/rust/components/style/mod.rs +5 -0
- package/src/rust/components/style/style_provider.rs +2 -0
- package/src/rust/components/viewer/filters.rs +133 -0
- package/src/rust/components/viewer/msg.rs +180 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +243 -0
- package/src/rust/components/viewer/settings.rs +402 -0
- package/src/rust/components/viewer/snapshots.rs +134 -0
- package/src/rust/components/viewer/wiring.rs +505 -0
- package/src/rust/components/viewer.rs +243 -756
- package/src/rust/config/export_method.rs +1 -1
- package/src/rust/config/mod.rs +4 -0
- package/src/rust/config/options.rs +71 -0
- package/src/rust/config/viewer_config.rs +24 -2
- package/src/rust/config/workspace_config.rs +92 -0
- package/src/rust/custom_elements/copy_dropdown.rs +3 -1
- package/src/rust/custom_elements/debug_plugin.rs +20 -1
- package/src/rust/custom_elements/export_dropdown.rs +3 -1
- package/src/rust/custom_elements/viewer.rs +1015 -286
- package/src/rust/custom_events.rs +212 -61
- package/src/rust/js/mod.rs +2 -0
- package/src/rust/js/plugin.rs +297 -10
- package/src/rust/js/regular_layout.rs +258 -0
- package/src/rust/lib.rs +20 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/get_viewer_config.rs +29 -11
- package/src/rust/queries/plugin_column_styles.rs +2 -2
- package/src/rust/renderer/activate.rs +41 -14
- package/src/rust/renderer/dispatch.rs +436 -0
- package/src/rust/renderer/plugin_config.rs +495 -0
- package/src/rust/renderer/registry.rs +18 -6
- package/src/rust/renderer/render_timer.rs +29 -10
- package/src/rust/renderer.rs +350 -837
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +753 -264
- package/src/rust/tasks/apply_global_filters.rs +89 -0
- package/src/rust/tasks/auto_pause.rs +171 -0
- package/src/rust/tasks/copy_export.rs +29 -19
- package/src/rust/tasks/create_panel.rs +243 -0
- package/src/rust/tasks/dismiss_render_warning.rs +3 -1
- package/src/rust/tasks/edit_expression.rs +4 -4
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +28 -4
- package/src/rust/tasks/pipeline.rs +411 -0
- package/src/rust/tasks/presize_panels.rs +324 -0
- package/src/rust/tasks/reset_all.rs +22 -12
- package/src/rust/tasks/resize_observer.rs +124 -87
- package/src/rust/tasks/restore_and_render.rs +80 -86
- package/src/rust/tasks/restore_panel.rs +145 -0
- package/src/rust/tasks/send_column_config.rs +5 -2
- package/src/rust/tasks/send_plugin_config.rs +5 -2
- package/src/rust/tasks/set_edit_mode.rs +59 -0
- package/src/rust/tasks/sync_update_panels.rs +152 -0
- package/src/rust/tasks/table_lifecycle.rs +158 -0
- package/src/rust/tasks/update_theme.rs +56 -12
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +130 -7
- package/src/rust/utils/mod.rs +6 -0
- package/src/rust/utils/pending_effects.rs +62 -0
- package/src/rust/utils/spawn.rs +33 -0
- package/src/rust/utils/weak_scope.rs +0 -10
- package/src/rust/workspace.rs +846 -0
- package/src/svg/active-indicator.svg +3 -0
- package/src/themes/botanical.css +5 -2
- package/src/themes/defaults.css +54 -2
- package/src/themes/dracula.css +6 -3
- package/src/themes/gruvbox-dark.css +5 -2
- package/src/themes/gruvbox.css +5 -2
- package/src/themes/icons.css +4 -2
- package/src/themes/monokai.css +5 -2
- package/src/themes/phosphor.css +5 -2
- package/src/themes/pro-dark.css +5 -2
- package/src/themes/pro.css +2 -1
- package/src/themes/solarized-dark.css +5 -2
- package/src/themes/solarized.css +5 -2
- package/src/themes/vaporwave.css +5 -2
- package/src/ts/bootstrap.ts +8 -7
- package/src/ts/extensions.ts +30 -2
- package/src/ts/perspective-viewer.ts +10 -0
- package/src/ts/plugin.ts +72 -5
- package/src/ts/ts-rs/ClientOptions.ts +7 -0
- package/src/ts/ts-rs/ExportMethod.ts +3 -0
- package/src/ts/ts-rs/ExportOptions.ts +8 -0
- package/src/ts/ts-rs/GetClientOptions.ts +7 -0
- package/src/ts/ts-rs/GetTableOptions.ts +7 -0
- package/src/ts/ts-rs/Layout.ts +9 -0
- package/src/ts/ts-rs/Orientation.ts +6 -0
- package/src/ts/ts-rs/PanelOptions.ts +7 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +15 -0
- package/src/ts/ts-rs/SplitLayout.ts +5 -0
- package/src/ts/ts-rs/TabLayout.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +6 -2
- package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
- package/src/rust/tasks/intersection_observer.rs +0 -98
- package/src/rust/tasks/update_and_render.rs +0 -110
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline0.js → perspective-viewer-daeef4ceb066e6c7/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-daeef4ceb066e6c7/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-daeef4ceb066e6c7/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-daeef4ceb066e6c7/inline5.js} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use perspective_client::config::ViewConfigUpdate;
|
|
14
|
+
|
|
15
|
+
use super::apply_and_render;
|
|
16
|
+
use crate::session::Session;
|
|
17
|
+
use crate::utils::spawn_owned;
|
|
18
|
+
use crate::workspace::{PanelId, Workspace};
|
|
19
|
+
|
|
20
|
+
/// Stamp `session`'s transient global-filter overlay from the
|
|
21
|
+
/// [`Workspace`]'s current set — EMPTY for master (filter-source) panels,
|
|
22
|
+
/// the full set for details. Master immunity is enforced here and only
|
|
23
|
+
/// here. Returns whether the overlay changed.
|
|
24
|
+
///
|
|
25
|
+
/// Synchronous, so panel-creation paths can stamp BEFORE the panel's first
|
|
26
|
+
/// locked bind — the initial render then picks the overlay up via its
|
|
27
|
+
/// `ConfigSnapshot.effective`, with no second render and no unfiltered
|
|
28
|
+
/// first paint.
|
|
29
|
+
pub fn stamp_global_overlay(workspace: &Workspace, id: &PanelId, session: &Session) -> bool {
|
|
30
|
+
let filters = if workspace.is_master(id) {
|
|
31
|
+
Vec::new()
|
|
32
|
+
} else {
|
|
33
|
+
workspace.global_filters()
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
session.set_global_filter(filters)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Re-stamp every panel's overlay and re-render those whose overlay CHANGED
|
|
40
|
+
/// (details are usually non-active, so they need an explicit re-render
|
|
41
|
+
/// rather than relying on a subscription; unchanged panels aren't touched
|
|
42
|
+
/// at all).
|
|
43
|
+
pub fn apply_global_filters(workspace: &Workspace) {
|
|
44
|
+
for pid in workspace.panel_ids() {
|
|
45
|
+
if let Some(panel) = workspace.panel(&pid)
|
|
46
|
+
&& stamp_global_overlay(workspace, &pid, &panel.session)
|
|
47
|
+
{
|
|
48
|
+
let effect = workspace.effects().guard();
|
|
49
|
+
let session = panel.session.clone();
|
|
50
|
+
let renderer = panel.renderer.clone();
|
|
51
|
+
spawn_owned("apply-global-filters", async move {
|
|
52
|
+
let _effect = effect;
|
|
53
|
+
apply_and_render(&session, &renderer, ViewConfigUpdate::default())?.await?;
|
|
54
|
+
Ok(())
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Clear the ORIGINATING master panels' visible selection state (row
|
|
61
|
+
/// highlights, pinned tooltips) after their contributed clauses are removed
|
|
62
|
+
/// from the `GlobalFilterBar` (chip × / "Clear") — a selection visual must
|
|
63
|
+
/// not outlive the filter it produced. The plugin's OPTIONAL `deselect()` is
|
|
64
|
+
/// SILENT (no selection events), so no `MasterSelect` echo can re-mutate the
|
|
65
|
+
/// filter set; plugins without one (e.g. `Debug`) no-op. Each call runs
|
|
66
|
+
/// under its panel's draw lock — implementations may redraw (see the
|
|
67
|
+
/// call-discipline contract on `js/plugin.rs`). A closed panel's id is
|
|
68
|
+
/// skipped.
|
|
69
|
+
pub fn clear_master_selections(workspace: &Workspace, origins: Vec<PanelId>) {
|
|
70
|
+
for id in origins {
|
|
71
|
+
if let Some(panel) = workspace.panel(&id) {
|
|
72
|
+
let renderer = panel.renderer.clone();
|
|
73
|
+
spawn_owned("clear-master-selection", async move {
|
|
74
|
+
let r = renderer.clone();
|
|
75
|
+
renderer
|
|
76
|
+
.render_task(|_guard| async move {
|
|
77
|
+
// Pure query: a never-drawn panel has no plugin and
|
|
78
|
+
// no selection to clear.
|
|
79
|
+
if let Some(plugin) = r.active_plugin() {
|
|
80
|
+
plugin.deselect().await?;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
Ok(())
|
|
84
|
+
})
|
|
85
|
+
.await
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use std::cell::Cell;
|
|
14
|
+
use std::rc::Rc;
|
|
15
|
+
|
|
16
|
+
use perspective_js::utils::global;
|
|
17
|
+
use wasm_bindgen::JsCast;
|
|
18
|
+
use wasm_bindgen::prelude::*;
|
|
19
|
+
use web_sys::*;
|
|
20
|
+
|
|
21
|
+
use crate::config::ViewerConfigUpdate;
|
|
22
|
+
use crate::js::*;
|
|
23
|
+
use crate::presentation::Presentation;
|
|
24
|
+
use crate::renderer::*;
|
|
25
|
+
use crate::session::Session;
|
|
26
|
+
use crate::utils::*;
|
|
27
|
+
use crate::workspace::Workspace;
|
|
28
|
+
use crate::*;
|
|
29
|
+
|
|
30
|
+
/// The `auto-pause` driver: pauses every panel's [`Session`] (deleting its
|
|
31
|
+
/// `ViewSubscription`, which stops `table_updated` at the source) whenever
|
|
32
|
+
/// the host element cannot be seen, and resumes with a re-render when it
|
|
33
|
+
/// can again. "Cannot be seen" is the conjunction of TWO independent
|
|
34
|
+
/// signals, either of which alone would miss real cases:
|
|
35
|
+
///
|
|
36
|
+
/// - An `IntersectionObserver` on the host element (scrolled out of the
|
|
37
|
+
/// viewport, `display: none`, disconnected). This CANNOT observe
|
|
38
|
+
/// browser-tab backgrounding — intersection entries are only generated
|
|
39
|
+
/// during the document's rendering steps, which are suspended while hidden,
|
|
40
|
+
/// and the element's viewport intersection doesn't change on a tab switch
|
|
41
|
+
/// anyway.
|
|
42
|
+
/// - A `document` `visibilitychange` listener (tab backgrounded, window
|
|
43
|
+
/// minimized/occluded). Without it, a fast-updating table kept dispatching
|
|
44
|
+
/// update-redraws whose draws hang on the suspended rAF — the
|
|
45
|
+
/// background-tab reactivation stall.
|
|
46
|
+
pub struct AutoPauseHandle {
|
|
47
|
+
elem: HtmlElement,
|
|
48
|
+
observer: IntersectionObserver,
|
|
49
|
+
_callback: Closure<dyn FnMut(js_sys::Array)>,
|
|
50
|
+
_visibility: Closure<dyn Fn(JsValue)>,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
impl AutoPauseHandle {
|
|
54
|
+
pub fn new(elem: &HtmlElement, presentation: &Presentation, workspace: &Workspace) -> Self {
|
|
55
|
+
let state = Rc::new(AutoPauseState {
|
|
56
|
+
intersecting: Cell::new(true),
|
|
57
|
+
doc_visible: Cell::new(!global::document().hidden()),
|
|
58
|
+
presentation: presentation.clone(),
|
|
59
|
+
workspace: workspace.clone(),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
let _callback = Closure::new({
|
|
63
|
+
clone!(state);
|
|
64
|
+
move |xs: js_sys::Array| {
|
|
65
|
+
let intersect = xs
|
|
66
|
+
.pop()
|
|
67
|
+
.unchecked_into::<IntersectionObserverEntry>()
|
|
68
|
+
.is_intersecting();
|
|
69
|
+
|
|
70
|
+
state.intersecting.set(intersect);
|
|
71
|
+
state.apply();
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
let func = _callback.as_ref().unchecked_ref::<js_sys::Function>();
|
|
76
|
+
let observer = IntersectionObserver::new(func);
|
|
77
|
+
observer.observe(elem);
|
|
78
|
+
|
|
79
|
+
let _visibility = Closure::new({
|
|
80
|
+
clone!(state);
|
|
81
|
+
move |_: JsValue| {
|
|
82
|
+
state.doc_visible.set(!global::document().hidden());
|
|
83
|
+
state.apply();
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
global::document()
|
|
88
|
+
.add_event_listener_with_callback(
|
|
89
|
+
"visibilitychange",
|
|
90
|
+
_visibility.as_ref().unchecked_ref(),
|
|
91
|
+
)
|
|
92
|
+
.unwrap();
|
|
93
|
+
|
|
94
|
+
Self {
|
|
95
|
+
elem: elem.clone(),
|
|
96
|
+
observer,
|
|
97
|
+
_callback,
|
|
98
|
+
_visibility,
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl Drop for AutoPauseHandle {
|
|
104
|
+
fn drop(&mut self) {
|
|
105
|
+
self.observer.unobserve(&self.elem);
|
|
106
|
+
let _ = global::document().remove_event_listener_with_callback(
|
|
107
|
+
"visibilitychange",
|
|
108
|
+
self._visibility.as_ref().unchecked_ref(),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
struct AutoPauseState {
|
|
114
|
+
intersecting: Cell<bool>,
|
|
115
|
+
doc_visible: Cell<bool>,
|
|
116
|
+
presentation: Presentation,
|
|
117
|
+
workspace: Workspace,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
impl AutoPauseState {
|
|
121
|
+
fn apply(self: &Rc<Self>) {
|
|
122
|
+
let visible = self.intersecting.get() && self.doc_visible.get();
|
|
123
|
+
clone!(self.workspace, self.presentation);
|
|
124
|
+
ApiFuture::spawn(async move {
|
|
125
|
+
let panels = workspace.panels();
|
|
126
|
+
futures::future::join_all(panels.iter().map(|panel| {
|
|
127
|
+
let presentation = presentation.clone();
|
|
128
|
+
async move {
|
|
129
|
+
set_panel_paused(&panel.session, &panel.renderer, &presentation, visible).await
|
|
130
|
+
}
|
|
131
|
+
}))
|
|
132
|
+
.await
|
|
133
|
+
.into_iter()
|
|
134
|
+
.collect::<ApiResult<Vec<_>>>()?;
|
|
135
|
+
|
|
136
|
+
Ok(())
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// Apply one panel's pause/resume transition. Shared by the
|
|
142
|
+
/// [`AutoPauseState`] fan-out and `setAutoPause(false)`'s force-resume.
|
|
143
|
+
pub(crate) async fn set_panel_paused(
|
|
144
|
+
session: &Session,
|
|
145
|
+
renderer: &Renderer,
|
|
146
|
+
presentation: &Presentation,
|
|
147
|
+
visible: bool,
|
|
148
|
+
) -> ApiResult<()> {
|
|
149
|
+
if visible {
|
|
150
|
+
if session.set_pause(false) {
|
|
151
|
+
let result = super::restore_and_render(
|
|
152
|
+
session,
|
|
153
|
+
renderer,
|
|
154
|
+
presentation,
|
|
155
|
+
super::RunOrigin::Internal,
|
|
156
|
+
ViewerConfigUpdate::default(),
|
|
157
|
+
async move { Ok(()) },
|
|
158
|
+
)
|
|
159
|
+
.await;
|
|
160
|
+
|
|
161
|
+
if let Err(e) = result.ignore_view_delete() {
|
|
162
|
+
session.set_run_error(e.clone()).await?;
|
|
163
|
+
return Err(e);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
session.set_pause(true);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
Ok(())
|
|
171
|
+
}
|
|
@@ -66,34 +66,44 @@ pub async fn html_as_jsvalue(
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
/// Render the current view as a `.png` `Blob` via the active plugin's
|
|
69
|
-
/// `render` method (typically only available for chart plugins).
|
|
69
|
+
/// `render` method (typically only available for chart plugins). Chart
|
|
70
|
+
/// plugins implement `render` as a full internal `draw` + snapshot, so the
|
|
71
|
+
/// call must hold the renderer's draw lock like any other draw (see
|
|
72
|
+
/// [`crate::js::plugin`]) or it would interleave with a concurrent
|
|
73
|
+
/// `table_updated` redraw.
|
|
70
74
|
pub async fn png_as_jsvalue(session: &Session, renderer: &Renderer) -> ApiResult<web_sys::Blob> {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
renderer
|
|
76
|
+
.clone()
|
|
77
|
+
.render_task(|_guard| async move {
|
|
78
|
+
let plugin = renderer.ensure_plugin_selected()?;
|
|
79
|
+
let view: perspective_client::View = session
|
|
80
|
+
.get_view()
|
|
81
|
+
.ok_or(ApiError::from(ApiErrorType::NoTableError))?;
|
|
82
|
+
|
|
83
|
+
plugin.render(view.into(), None).await
|
|
84
|
+
})
|
|
85
|
+
.await
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
/// Render the current view as a `.txt` `Blob` via the active plugin's
|
|
81
|
-
/// `render` method (typically used for the datagrid).
|
|
89
|
+
/// `render` method (typically used for the datagrid). Locked for the same
|
|
90
|
+
/// reason as [`png_as_jsvalue`].
|
|
82
91
|
pub async fn txt_as_jsvalue(
|
|
83
92
|
session: &Session,
|
|
84
93
|
renderer: &Renderer,
|
|
85
94
|
viewport: Option<ViewWindow>,
|
|
86
95
|
) -> ApiResult<web_sys::Blob> {
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
.
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
renderer
|
|
97
|
+
.clone()
|
|
98
|
+
.render_task(|_guard| async move {
|
|
99
|
+
let plugin = renderer.ensure_plugin_selected()?;
|
|
100
|
+
let view: perspective_client::View = session
|
|
101
|
+
.get_view()
|
|
102
|
+
.ok_or(ApiError::from(ApiErrorType::NoTableError))?;
|
|
103
|
+
|
|
104
|
+
plugin.render(view.into(), viewport.map(|x| x.into())).await
|
|
105
|
+
})
|
|
106
|
+
.await
|
|
97
107
|
}
|
|
98
108
|
|
|
99
109
|
/// Generate a result `Blob` for all types of [`ExportMethod`].
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use web_sys::HtmlElement;
|
|
14
|
+
use yew::Callback;
|
|
15
|
+
|
|
16
|
+
use crate::config::*;
|
|
17
|
+
use crate::custom_events::wire_panel_events;
|
|
18
|
+
use crate::presentation::*;
|
|
19
|
+
use crate::renderer::*;
|
|
20
|
+
use crate::session::{ResetOptions, Session};
|
|
21
|
+
use crate::tasks::*;
|
|
22
|
+
use crate::utils::*;
|
|
23
|
+
use crate::workspace::{Panel, PanelId, Workspace};
|
|
24
|
+
use crate::*;
|
|
25
|
+
|
|
26
|
+
/// Build the full set of subscriptions a [`Panel`] owns for its lifetime: its
|
|
27
|
+
/// redraw subscription plus its custom-event fanout ([`wire_panel_events`]).
|
|
28
|
+
/// Shared by the seed (element constructor) and every [`create_panel_model`]
|
|
29
|
+
/// panel, so all panels wire identically and every panel — not just the seed —
|
|
30
|
+
/// dispatches its own `perspective-*` events (see C6).
|
|
31
|
+
pub(crate) fn wire_panel_subs(
|
|
32
|
+
elem: &HtmlElement,
|
|
33
|
+
presentation: &Presentation,
|
|
34
|
+
session: &Session,
|
|
35
|
+
renderer: &Renderer,
|
|
36
|
+
) -> Vec<Subscription> {
|
|
37
|
+
let mut subs = vec![wire_panel_render_sub(session, renderer)];
|
|
38
|
+
subs.extend(wire_panel_events(elem, session, renderer, presentation));
|
|
39
|
+
subs
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Wire a panel's data-refresh subscription: when its [`Session`]'s table emits
|
|
43
|
+
/// an update, redraw its [`Renderer`]. The returned [`Subscription`] must be
|
|
44
|
+
/// owned for the panel's lifetime (see [`Panel`]).
|
|
45
|
+
///
|
|
46
|
+
/// Hidden panels (an unslotted tab-stack panel, a `display: none` host)
|
|
47
|
+
/// don't redraw — a full charts dispatch per update is a fetch + render
|
|
48
|
+
/// nobody can see. The dropped update is recorded on the [`Renderer`]
|
|
49
|
+
/// instead, and the activation nudge (`tasks::pipeline::activation_render`)
|
|
50
|
+
/// consumes the marker to catch the panel up with ONE `plugin.update`.
|
|
51
|
+
fn wire_panel_render_sub(session: &Session, renderer: &Renderer) -> Subscription {
|
|
52
|
+
session.table_updated.add_listener({
|
|
53
|
+
clone!(renderer, session);
|
|
54
|
+
move |_| {
|
|
55
|
+
if renderer.is_plugin_hidden() {
|
|
56
|
+
renderer.set_data_stale();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
clone!(renderer, session);
|
|
61
|
+
ApiFuture::spawn(async move {
|
|
62
|
+
renderer
|
|
63
|
+
.update_lazy(async move { Ok(session.get_view()) })
|
|
64
|
+
.await
|
|
65
|
+
.ignore_view_delete()
|
|
66
|
+
.map(|_| ())
|
|
67
|
+
})
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Create a new independent panel (own `Session` + `Renderer` + id) from a
|
|
73
|
+
/// `ViewerConfigUpdate`, mount and draw it, and return its id. Shared by
|
|
74
|
+
/// `addPanel`, whole-element `restore`, and `restorePanel`'s create-if-missing
|
|
75
|
+
/// path. `id` is the panel's id — provided when restoring into a specific named
|
|
76
|
+
/// slot, or `None` to generate a fresh one. `settings`/`theme` are stripped
|
|
77
|
+
/// (element-level, not per-panel) and `client` — or the element's default
|
|
78
|
+
/// client when `None` — is bound so the config's `table` resolves against it.
|
|
79
|
+
pub(crate) async fn create_panel(
|
|
80
|
+
elem: &HtmlElement,
|
|
81
|
+
presentation: &Presentation,
|
|
82
|
+
workspace: &Workspace,
|
|
83
|
+
notify: &Callback<()>,
|
|
84
|
+
id: Option<PanelId>,
|
|
85
|
+
update: ViewerConfigUpdate,
|
|
86
|
+
client: Option<perspective_client::Client>,
|
|
87
|
+
) -> ApiResult<PanelId> {
|
|
88
|
+
let (id, session, renderer, update) = create_panel_model(
|
|
89
|
+
elem,
|
|
90
|
+
presentation,
|
|
91
|
+
workspace,
|
|
92
|
+
id,
|
|
93
|
+
update,
|
|
94
|
+
client,
|
|
95
|
+
Placement::Placed,
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
stamp_global_overlay(workspace, &id, &session);
|
|
99
|
+
// STAGE the panel (model-first): `MainPanel` renders its plugin slot
|
|
100
|
+
// into a hidden pre-sized wrapper — not a layout cell — so the restore
|
|
101
|
+
// below completes its first draw invisibly at (near-)final size. The
|
|
102
|
+
// promote below then re-renders, and `MainPanel::reconcile` inserts an
|
|
103
|
+
// ALREADY-DRAWN panel: the one layout transition reveals content,
|
|
104
|
+
// never a blank frame.
|
|
105
|
+
workspace.stage_panel(&id);
|
|
106
|
+
notify.emit(());
|
|
107
|
+
|
|
108
|
+
// The staging deadline: a slow restore promotes EARLY (placed, still
|
|
109
|
+
// loading — the pre-staging progressive reveal) rather than leaving
|
|
110
|
+
// the layout without feedback. Races the completion promote below on
|
|
111
|
+
// the staged flag; only the winner re-renders.
|
|
112
|
+
{
|
|
113
|
+
clone!(workspace, id, notify);
|
|
114
|
+
ApiFuture::spawn(async move {
|
|
115
|
+
set_timeout(STAGING_DEADLINE_MS).await?;
|
|
116
|
+
if workspace.clear_staged(&id) {
|
|
117
|
+
notify.emit(());
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
Ok(())
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// A fresh panel is never the active one, so it needs no `root` for the
|
|
125
|
+
// (active-only) settings-sidebar sequencing.
|
|
126
|
+
let result = restore_panel(
|
|
127
|
+
&session,
|
|
128
|
+
&renderer,
|
|
129
|
+
presentation,
|
|
130
|
+
workspace,
|
|
131
|
+
None,
|
|
132
|
+
RestoreMode::Fresh,
|
|
133
|
+
update,
|
|
134
|
+
)
|
|
135
|
+
.await;
|
|
136
|
+
|
|
137
|
+
// Promote on completion AND error — a failed restore's error state
|
|
138
|
+
// must become visible too.
|
|
139
|
+
if workspace.clear_staged(&id) {
|
|
140
|
+
notify.emit(());
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
result?;
|
|
144
|
+
Ok(id)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/// Where [`create_panel_model`] registers the new panel model.
|
|
148
|
+
pub(crate) enum Placement {
|
|
149
|
+
/// Into the placed panel set ([`Workspace::insert_panel`]) — a live,
|
|
150
|
+
/// visible panel from the start.
|
|
151
|
+
Placed,
|
|
152
|
+
|
|
153
|
+
/// Into the reservation slot ([`Workspace::reserve_panel`]) — a pending
|
|
154
|
+
/// `load()`'s first panel, held out of the placed set until it is
|
|
155
|
+
/// claimed ([`place_reserved`]) or discarded.
|
|
156
|
+
Reserved,
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// The synchronous *model* half of [`create_panel`]: build and register a new
|
|
160
|
+
/// panel's engine handles (own `Session` + `Renderer` + id) in the
|
|
161
|
+
/// [`Workspace`] (per `placement`), apply-and-strip the element-level config
|
|
162
|
+
/// fields (`settings`/`theme`), and bind `client` (falling back to the default
|
|
163
|
+
/// client). `id` is the panel's id, or `None` to generate a fresh one.
|
|
164
|
+
pub(crate) fn create_panel_model(
|
|
165
|
+
elem: &HtmlElement,
|
|
166
|
+
presentation: &Presentation,
|
|
167
|
+
workspace: &Workspace,
|
|
168
|
+
id: Option<PanelId>,
|
|
169
|
+
mut update: ViewerConfigUpdate,
|
|
170
|
+
client: Option<perspective_client::Client>,
|
|
171
|
+
placement: Placement,
|
|
172
|
+
) -> (PanelId, Session, Renderer, ViewerConfigUpdate) {
|
|
173
|
+
// Authored-theme boot: the FIRST panel of an empty element with no explicit
|
|
174
|
+
// theme adopts the element's `theme` attribute (a one-time initial
|
|
175
|
+
// selection; the attribute is otherwise an active-panel mirror). Captured
|
|
176
|
+
// before `insert_panel` makes the workspace non-empty.
|
|
177
|
+
let boot_theme = (workspace.is_empty() && matches!(update.theme, OptionalUpdate::Missing))
|
|
178
|
+
.then(|| elem.get_attribute("theme"))
|
|
179
|
+
.flatten();
|
|
180
|
+
|
|
181
|
+
let session = Session::new();
|
|
182
|
+
let renderer = Renderer::new(elem);
|
|
183
|
+
let id = id.unwrap_or_else(|| workspace.generate_id());
|
|
184
|
+
renderer.set_slot_name(id.as_str());
|
|
185
|
+
renderer.set_default_theme(presentation.default_theme_name_sync());
|
|
186
|
+
let subs = wire_panel_subs(elem, presentation, &session, &renderer);
|
|
187
|
+
let panel = Panel::new(id.clone(), session.clone(), renderer.clone(), subs);
|
|
188
|
+
match placement {
|
|
189
|
+
Placement::Placed => workspace.insert_panel(panel),
|
|
190
|
+
Placement::Reserved => workspace.reserve_panel(panel),
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
update.settings = OptionalUpdate::Missing;
|
|
194
|
+
if let OptionalUpdate::Update(theme) = &update.theme {
|
|
195
|
+
renderer.set_theme(Some(theme.clone()));
|
|
196
|
+
} else if let Some(theme) = boot_theme {
|
|
197
|
+
renderer.set_theme(Some(theme));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
update.theme = OptionalUpdate::Missing;
|
|
201
|
+
if let Some(client) = client.or_else(|| workspace.default_client()) {
|
|
202
|
+
workspace.register_client(client.clone());
|
|
203
|
+
session.set_client(client);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if let Some((idx, _)) = renderer.resolve_plugin_update(&update.plugin) {
|
|
207
|
+
let _ = renderer.commit_plugin(Some(idx));
|
|
208
|
+
let _ = renderer.mount_active_plugin();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
(id, session, renderer, update)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
pub(crate) fn place_reserved(workspace: &Workspace, notify: &Callback<()>) -> Option<Panel> {
|
|
215
|
+
let panel = workspace.claim_reserved()?;
|
|
216
|
+
stamp_global_overlay(workspace, &panel.id, &panel.session);
|
|
217
|
+
notify.emit(());
|
|
218
|
+
Some(panel)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// Tear down a [`Panel`] already removed from the [`Workspace`]: dispose its
|
|
222
|
+
/// renderer (slot-scoped plugin + light-DOM cleanup) and eject its table.
|
|
223
|
+
/// Shared by the root's `ClosePanel` handler and whole-element `restore`'s
|
|
224
|
+
/// batch replacement of the pre-existing panel set.
|
|
225
|
+
pub(crate) fn eject_panel(panel: Panel) -> ApiFuture<()> {
|
|
226
|
+
let was_errored = panel.session.is_errored();
|
|
227
|
+
let dispose_task = panel.renderer.dispose();
|
|
228
|
+
let reset_task = panel.session.reset(ResetOptions {
|
|
229
|
+
config: true,
|
|
230
|
+
expressions: true,
|
|
231
|
+
table: Some(session::TableIntermediateState::Ejected),
|
|
232
|
+
..ResetOptions::default()
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
ApiFuture::new(async move {
|
|
236
|
+
dispose_task.await?;
|
|
237
|
+
match reset_task.await.ignore_view_delete() {
|
|
238
|
+
Err(_) if was_errored => Ok(()),
|
|
239
|
+
Err(e) => Err(e),
|
|
240
|
+
Ok(_) => Ok(()),
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
}
|
|
@@ -27,7 +27,9 @@ pub fn dismiss_render_warning_callback(session: &Session, renderer: &Renderer) -
|
|
|
27
27
|
clone!(session, renderer);
|
|
28
28
|
ApiFuture::spawn(async move {
|
|
29
29
|
renderer.disable_active_plugin_render_warning();
|
|
30
|
-
renderer
|
|
30
|
+
renderer
|
|
31
|
+
.update_lazy(async move { Ok(session.get_view()) })
|
|
32
|
+
.await
|
|
31
33
|
});
|
|
32
34
|
})
|
|
33
35
|
}
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
use perspective_client::clone;
|
|
18
18
|
use perspective_client::config::{Expression, ViewConfigUpdate};
|
|
19
19
|
|
|
20
|
-
use super::
|
|
20
|
+
use super::apply_and_render;
|
|
21
21
|
use crate::presentation::{ColumnLocator, ColumnSettingsTab, OpenColumnSettings, Presentation};
|
|
22
22
|
use crate::renderer::Renderer;
|
|
23
23
|
use crate::session::Session;
|
|
@@ -39,7 +39,7 @@ pub fn update_expr(
|
|
|
39
39
|
.to_props()
|
|
40
40
|
.create_replace_expression_update(&old_name, &new_expr);
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
apply_and_render(&session, &renderer, update)?.await?;
|
|
43
43
|
presentation.set_open_column_settings(Some(OpenColumnSettings {
|
|
44
44
|
locator: Some(ColumnLocator::Expression(new_expr.name.to_string())),
|
|
45
45
|
tab: Some(ColumnSettingsTab::Attributes),
|
|
@@ -62,7 +62,7 @@ pub fn save_expr(
|
|
|
62
62
|
let task = {
|
|
63
63
|
let mut serde_exprs = session.get_view_config().expressions.clone();
|
|
64
64
|
serde_exprs.insert(&expr);
|
|
65
|
-
|
|
65
|
+
apply_and_render(session, renderer, ViewConfigUpdate {
|
|
66
66
|
expressions: Some(serde_exprs),
|
|
67
67
|
..Default::default()
|
|
68
68
|
})
|
|
@@ -90,7 +90,7 @@ pub fn delete_expr(session: &Session, renderer: &Renderer, expr_name: &str) -> A
|
|
|
90
90
|
..ViewConfigUpdate::default()
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
let task =
|
|
93
|
+
let task = apply_and_render(session, renderer, config)?;
|
|
94
94
|
ApiFuture::spawn(task);
|
|
95
95
|
Ok(())
|
|
96
96
|
}
|