@perspective-dev/viewer 4.5.2 → 5.1.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/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/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 +41 -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 +8 -2
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +9 -0
- package/dist/esm/ts-rs/WindowAggregate.d.ts +1 -0
- package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
- package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +46 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- 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 +299 -78
- package/dist/wasm/perspective-viewer.js +544 -186
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-8a8c7d5d696b43ae/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-8a8c7d5d696b43ae/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-selector.css +1 -0
- package/src/css/column-settings-panel.css +68 -3
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +22 -1
- package/src/css/containers/context-menu.css +107 -0
- package/src/css/containers/tabs.css +2 -2
- 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 +188 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +28 -45
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +40 -82
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +11 -9
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +25 -14
- package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
- package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
- package/src/rust/components/column_settings_sidebar.rs +163 -16
- package/src/rust/components/containers/dragdrop_list.rs +73 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +181 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/editable_header.rs +2 -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 +316 -71
- package/src/rust/components/mod.rs +5 -0
- package/src/rust/components/panel_menu.rs +493 -0
- package/src/rust/components/panel_tab.rs +510 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/portal.rs +7 -0
- 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 +182 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +255 -0
- package/src/rust/components/viewer/settings.rs +434 -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 +245 -756
- package/src/rust/components/window_editor.rs +1013 -0
- 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 +24 -2
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/columns_iter_set.rs +7 -1
- 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 +463 -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 +384 -838
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +66 -9
- package/src/rust/session/replace_expression_update.rs +1 -0
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +755 -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/edit_window.rs +155 -0
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +30 -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/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +189 -17
- 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/svg/pin-icon.svg +5 -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 +5 -2
- package/src/themes/intl/de.css +7 -0
- package/src/themes/intl/es.css +7 -0
- package/src/themes/intl/fr.css +7 -0
- package/src/themes/intl/ja.css +7 -0
- package/src/themes/intl/pt.css +7 -0
- package/src/themes/intl/zh.css +7 -0
- package/src/themes/intl.css +10 -1
- 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 +16 -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 +7 -2
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +9 -0
- package/src/ts/ts-rs/WindowAggregate.ts +3 -0
- package/src/ts/ts-rs/WindowSort.ts +9 -0
- package/src/ts/ts-rs/WindowSortDir.ts +8 -0
- package/src/ts/ts-rs/WindowSpec.ts +46 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- 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-8a8c7d5d696b43ae/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-8a8c7d5d696b43ae/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-8a8c7d5d696b43ae/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-8a8c7d5d696b43ae/inline5.js} +0 -0
|
@@ -0,0 +1,510 @@
|
|
|
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
|
+
use yew::prelude::*;
|
|
21
|
+
|
|
22
|
+
#[wasm_bindgen(inline_js = r#"
|
|
23
|
+
export function define_panel_tab(name) {
|
|
24
|
+
if (!customElements.get(name)) {
|
|
25
|
+
customElements.define(name, class extends HTMLElement {});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
"#)]
|
|
29
|
+
extern "C" {
|
|
30
|
+
#[wasm_bindgen(js_name = "define_panel_tab")]
|
|
31
|
+
fn define_panel_tab(name: &str);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/// The tag the tab host is created as (a custom element, so it can own a
|
|
35
|
+
/// ShadowRoot for its contents).
|
|
36
|
+
const TAB_TAG: &str = "perspective-viewer-tab";
|
|
37
|
+
|
|
38
|
+
thread_local! {
|
|
39
|
+
/// Whether the `<perspective-viewer-tab>` custom element has been defined
|
|
40
|
+
/// (once per page; WASM is single-threaded).
|
|
41
|
+
static ELEMENT_DEFINED: Cell<bool> = const { Cell::new(false) };
|
|
42
|
+
|
|
43
|
+
/// The shared, constructed stylesheet adopted into every tab's ShadowRoot.
|
|
44
|
+
/// Built once from `panel-tab.css`; a constructed `CSSStyleSheet` is cheap
|
|
45
|
+
/// and safe to adopt into arbitrarily many roots.
|
|
46
|
+
static TAB_SHEET: CssStyleSheet = {
|
|
47
|
+
let sheet = CssStyleSheet::new().unwrap();
|
|
48
|
+
sheet.replace_sync(include_str!("../../css/panel-tab.css"))
|
|
49
|
+
.unwrap();
|
|
50
|
+
sheet
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/// Define the `<perspective-viewer-tab>` custom element once. It needs no
|
|
55
|
+
/// lifecycle callbacks — it exists only to own a ShadowRoot into which each
|
|
56
|
+
/// tab's contents are rendered (so the tab's structure CSS is encapsulated in
|
|
57
|
+
/// its `adoptedStyleSheets` rather than injected into `document.head`).
|
|
58
|
+
///
|
|
59
|
+
/// The host stays a *light-DOM* child of the viewer, so the per-panel `theme`
|
|
60
|
+
/// attr is still matched by the document theme rules (`perspective-viewer
|
|
61
|
+
/// [theme="X"]`); the `--psp-*` they set are inherited across the shadow
|
|
62
|
+
/// boundary into the tab's contents.
|
|
63
|
+
fn ensure_custom_element() {
|
|
64
|
+
ELEMENT_DEFINED.with(|defined| {
|
|
65
|
+
if !defined.get() {
|
|
66
|
+
define_panel_tab(TAB_TAG);
|
|
67
|
+
defined.set(true);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// Adopt the shared tab stylesheet into `shadow_root.adoptedStyleSheets`
|
|
73
|
+
/// (idempotent per root). Mirrors `StyleProvider`'s adopt-by-`Reflect`
|
|
74
|
+
/// approach.
|
|
75
|
+
fn adopt_sheet(shadow_root: &Element) {
|
|
76
|
+
let sheets = js_sys::Reflect::get(shadow_root.as_ref(), &"adoptedStyleSheets".into())
|
|
77
|
+
.unwrap()
|
|
78
|
+
.unchecked_into::<js_sys::Array>();
|
|
79
|
+
|
|
80
|
+
TAB_SHEET.with(|sheet| {
|
|
81
|
+
let sheet_val: &JsValue = sheet.as_ref();
|
|
82
|
+
if sheets.index_of(sheet_val, 0) < 0 {
|
|
83
|
+
sheets.push(sheet_val);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/// A panel's titlebar tab. The host is a `<perspective-viewer-tab>` custom
|
|
89
|
+
/// element mounted in the viewer's **light DOM** and forwarded into its
|
|
90
|
+
/// `<regular-layout-frame>` titlebar via a `<slot name="tab-{id}" slot="tab">`
|
|
91
|
+
/// (rendered by `MainPanel`); the tab's *contents* live in the host's
|
|
92
|
+
/// **ShadowRoot**, whose `adoptedStyleSheets` carry the structure CSS.
|
|
93
|
+
///
|
|
94
|
+
/// Keeping the host in the light DOM — like the plugin — is what lets
|
|
95
|
+
/// *document* theme CSS (`perspective-viewer [theme="X"]`) reach it, so each
|
|
96
|
+
/// panel's chrome is themed by the native cascade instead of a runtime-inlined
|
|
97
|
+
/// string; the `--psp-*` those rules set are inherited across the shadow
|
|
98
|
+
/// boundary into the contents. The host carries `part="tab"`, which
|
|
99
|
+
/// `<regular-layout-frame>`'s pointer handler treats as a drag handle
|
|
100
|
+
/// (regular-layout@>=0.6.0's custom-tab contract); the title is
|
|
101
|
+
/// `pointer-events:none` so clicks fall through to the host (like the built-in
|
|
102
|
+
/// `<regular-layout-tab>`).
|
|
103
|
+
#[derive(Properties, PartialEq)]
|
|
104
|
+
pub struct PanelTabProps {
|
|
105
|
+
/// The `<perspective-viewer>` host element; the tab is attached here as a
|
|
106
|
+
/// light-DOM child (mirrors `renderer::activate`'s plugin mount).
|
|
107
|
+
pub viewer: HtmlElement,
|
|
108
|
+
|
|
109
|
+
/// The panel id. The tab is assigned to `slot="tab-{panel_id}"`.
|
|
110
|
+
pub panel_id: String,
|
|
111
|
+
|
|
112
|
+
/// The panel's title; falls back to the id when `None`.
|
|
113
|
+
pub title: Option<String>,
|
|
114
|
+
|
|
115
|
+
/// This panel's effective theme. Reflected onto the host's `theme`
|
|
116
|
+
/// attribute so the document theme rules (`perspective-viewer [theme="X"]`)
|
|
117
|
+
/// theme the tab per-panel via the native cascade.
|
|
118
|
+
pub theme: Option<String>,
|
|
119
|
+
|
|
120
|
+
/// `true` when this is the active panel (toolbar target / selected tab);
|
|
121
|
+
/// drives the active-tab styling.
|
|
122
|
+
pub active: bool,
|
|
123
|
+
|
|
124
|
+
/// `true` when this panel is *visible* — the front (selected) tab of its
|
|
125
|
+
/// stack, or a lone panel. Hidden panels are those at an unselected index
|
|
126
|
+
/// of a tab stack. Independent of `active`: every stack has a visible
|
|
127
|
+
/// panel, but only one panel in the whole layout is active.
|
|
128
|
+
pub visible: bool,
|
|
129
|
+
|
|
130
|
+
/// `true` when this panel is a master (filter-source) panel — shows the
|
|
131
|
+
/// broadcast badge to the left of the close button.
|
|
132
|
+
pub is_master: bool,
|
|
133
|
+
|
|
134
|
+
/// `true` when this is the host viewer's ONLY panel (one plugin child).
|
|
135
|
+
/// Reflected on the host as a `single`/`multi` class — the same
|
|
136
|
+
/// panel-count CSS hook `Renderer::stamp_active` stamps on the plugin —
|
|
137
|
+
/// e.g. the caret is hidden on a lone tab (see panel-tab.css).
|
|
138
|
+
pub single: bool,
|
|
139
|
+
|
|
140
|
+
/// `false` for a lone panel (which can't be closed to zero) — hides the
|
|
141
|
+
/// close button.
|
|
142
|
+
pub closable: bool,
|
|
143
|
+
|
|
144
|
+
/// `true` when the settings sidebar is open. When closed, the tab renders
|
|
145
|
+
/// an open-settings button *in place of* the close button — the only
|
|
146
|
+
/// affordance for opening the settings panel (there is deliberately none
|
|
147
|
+
/// at zero panels).
|
|
148
|
+
pub is_settings_open: bool,
|
|
149
|
+
|
|
150
|
+
/// `false` for a lone panel — suppresses the tab rearrange-drag.
|
|
151
|
+
/// `<regular-layout-frame>` arms a drag from any `part="tab"` pointerdown,
|
|
152
|
+
/// but a lone panel has nowhere to drop, so the host `pointerdown` handler
|
|
153
|
+
/// stops the event before it reaches the frame (see `create`).
|
|
154
|
+
pub draggable: bool,
|
|
155
|
+
|
|
156
|
+
/// Select this panel in the layout (brings its frame forward within a stack
|
|
157
|
+
/// and activates it). Wired by `MainPanel` to `RegularLayout::select`.
|
|
158
|
+
pub on_select: Callback<String>,
|
|
159
|
+
|
|
160
|
+
/// Remove this panel from the layout. Wired by `MainPanel` to the root
|
|
161
|
+
/// `ClosePanel` message (which mutates the `Workspace` model first, then
|
|
162
|
+
/// syncs the slave `regular-layout` — NOT `RegularLayout::remove_panel`
|
|
163
|
+
/// directly; see the app-initiated-layout-change invariant).
|
|
164
|
+
pub on_close: Callback<String>,
|
|
165
|
+
|
|
166
|
+
/// Open the settings sidebar targeting this panel. Wired by `MainPanel` to
|
|
167
|
+
/// select this panel in the layout, activate it (so the sidebar binds its
|
|
168
|
+
/// engines), then toggle the sidebar open.
|
|
169
|
+
pub on_open_settings: Callback<String>,
|
|
170
|
+
|
|
171
|
+
/// Open the panel context menu at `(client_x, client_y)`. Wired here on the
|
|
172
|
+
/// tab host because the tab's content is a `create_portal` subtree, so its
|
|
173
|
+
/// events don't reach the frame's main-tree `oncontextmenu` (unlike the
|
|
174
|
+
/// imperatively-mounted plugin body).
|
|
175
|
+
pub on_context_menu: Callback<(String, f64, f64)>,
|
|
176
|
+
|
|
177
|
+
/// Commit a new title for this panel. Wired by `MainPanel` to this panel's
|
|
178
|
+
/// own [`Session::set_title`](crate::session::Session::set_title).
|
|
179
|
+
/// `(panel_id, new_title)`; `None` clears it back to the id fallback.
|
|
180
|
+
pub on_rename: Callback<(String, Option<String>)>,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Max gap (ms) between two tab pointerdowns to count as a double-click.
|
|
184
|
+
const DBLCLICK_MS: f64 = 400.0;
|
|
185
|
+
|
|
186
|
+
/// Shown in place of the title when a panel has no explicit title (rather than
|
|
187
|
+
/// falling back to the table / plugin name).
|
|
188
|
+
const TITLE_PLACEHOLDER: &str = "untitled";
|
|
189
|
+
|
|
190
|
+
pub enum PanelTabMsg {
|
|
191
|
+
/// A pointerdown on the tab host. Selects the panel; and when it's the
|
|
192
|
+
/// second within [`DBLCLICK_MS`], enters title-edit mode. Carries the
|
|
193
|
+
/// event `timeStamp` (ms). We synthesize the double-click from pointerdown
|
|
194
|
+
/// because `<regular-layout-frame>` calls `setPointerCapture` +
|
|
195
|
+
/// `preventDefault` on the tab's pointerdown (to arm a drag), which
|
|
196
|
+
/// suppresses the browser's synthesized `click`/`dblclick` — so a native
|
|
197
|
+
/// `dblclick` listener never fires on the tab.
|
|
198
|
+
PointerDown(f64),
|
|
199
|
+
Close,
|
|
200
|
+
/// The tab's open-settings button (shown while the settings sidebar is
|
|
201
|
+
/// closed, in the close button's place).
|
|
202
|
+
OpenSettings,
|
|
203
|
+
ContextMenu(f64, f64),
|
|
204
|
+
/// Track the live `<input>` value while editing (drives the auto-sizer).
|
|
205
|
+
EditInput(String),
|
|
206
|
+
/// Commit the edited title to the panel's session (blur / Enter).
|
|
207
|
+
CommitEdit,
|
|
208
|
+
/// Abandon the edit, restoring the previous title (Escape).
|
|
209
|
+
CancelEdit,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
pub struct PanelTab {
|
|
213
|
+
host: HtmlElement,
|
|
214
|
+
/// The host's open ShadowRoot; the tab's contents are portaled here (stored
|
|
215
|
+
/// as `Element` for `create_portal`, matching `PortalModal`).
|
|
216
|
+
shadow_root: Element,
|
|
217
|
+
/// `pointerdown` listener on the host (kept alive); selects this panel and
|
|
218
|
+
/// synthesizes double-click-to-edit (see [`PanelTabMsg::PointerDown`]).
|
|
219
|
+
_pointerdown: Closure<dyn FnMut(PointerEvent)>,
|
|
220
|
+
/// `contextmenu` listener on the host (kept alive); opens the panel menu.
|
|
221
|
+
_contextmenu: Closure<dyn FnMut(MouseEvent)>,
|
|
222
|
+
/// Current `draggable` prop, shared into the `pointerdown` closure so a
|
|
223
|
+
/// prop change (lone ⇄ multi panel) takes effect without re-creating it.
|
|
224
|
+
draggable: Rc<Cell<bool>>,
|
|
225
|
+
/// `timeStamp` (ms) of the last host pointerdown, for synthesizing
|
|
226
|
+
/// double-clicks. `NEG_INFINITY` until the first pointerdown.
|
|
227
|
+
last_pointerdown: f64,
|
|
228
|
+
/// `true` while the title is being edited (renders an `<input>`).
|
|
229
|
+
editing: bool,
|
|
230
|
+
/// Live edited value; controls the `<input>` and the auto-sizer width.
|
|
231
|
+
edit_value: String,
|
|
232
|
+
/// The edit `<input>`, for focusing on edit entry.
|
|
233
|
+
input_ref: NodeRef,
|
|
234
|
+
/// Focus + select-all the input on the next render after entering edit
|
|
235
|
+
/// mode.
|
|
236
|
+
focus_pending: bool,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
impl PanelTab {
|
|
240
|
+
fn slot_name(panel_id: &str) -> String {
|
|
241
|
+
format!("tab-{panel_id}")
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// Enter title-edit mode, seeding the input with the *real* title (empty
|
|
245
|
+
/// when `None`, not the id fallback shown in display mode). Ignores
|
|
246
|
+
/// re-entry so a click while editing doesn't clobber the in-progress
|
|
247
|
+
/// edit. Returns whether a re-render is needed.
|
|
248
|
+
fn begin_edit(&mut self, ctx: &Context<Self>) -> bool {
|
|
249
|
+
if self.editing {
|
|
250
|
+
return false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
self.editing = true;
|
|
254
|
+
self.focus_pending = true;
|
|
255
|
+
self.edit_value = ctx.props().title.clone().unwrap_or_default();
|
|
256
|
+
true
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/// Reflect `active`/`visible`/`single` onto the host (not a vnode — so
|
|
260
|
+
/// its class is set imperatively). `visible` marks the front (selected)
|
|
261
|
+
/// tab of every stack, not just the single active panel; `single`/`multi`
|
|
262
|
+
/// reflect the host viewer's panel count. The shadow CSS keys off
|
|
263
|
+
/// `:host(.active)` / `:host(.visible)` / `:host(.single)`.
|
|
264
|
+
fn sync_class(&self, active: bool, visible: bool, single: bool) {
|
|
265
|
+
let mut class = Vec::with_capacity(3);
|
|
266
|
+
if active {
|
|
267
|
+
class.push("active");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if visible {
|
|
271
|
+
class.push("visible");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
class.push(if single { "single" } else { "multi" });
|
|
275
|
+
let _ = self.host.set_attribute("class", &class.join(" "));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
impl Component for PanelTab {
|
|
280
|
+
type Message = PanelTabMsg;
|
|
281
|
+
type Properties = PanelTabProps;
|
|
282
|
+
|
|
283
|
+
fn create(ctx: &Context<Self>) -> Self {
|
|
284
|
+
ensure_custom_element();
|
|
285
|
+
|
|
286
|
+
let host: HtmlElement = global::document()
|
|
287
|
+
.create_element(TAB_TAG)
|
|
288
|
+
.unwrap()
|
|
289
|
+
.unchecked_into();
|
|
290
|
+
|
|
291
|
+
host.set_attribute("part", "tab").unwrap();
|
|
292
|
+
host.set_attribute("slot", &Self::slot_name(&ctx.props().panel_id))
|
|
293
|
+
.unwrap();
|
|
294
|
+
|
|
295
|
+
let init = ShadowRootInit::new(ShadowRootMode::Open);
|
|
296
|
+
let shadow_root = host
|
|
297
|
+
.shadow_root()
|
|
298
|
+
.unwrap_or_else(|| host.attach_shadow(&init).unwrap())
|
|
299
|
+
.unchecked_into::<Element>();
|
|
300
|
+
|
|
301
|
+
adopt_sheet(&shadow_root);
|
|
302
|
+
let link = ctx.link().clone();
|
|
303
|
+
let draggable = Rc::new(Cell::new(ctx.props().draggable));
|
|
304
|
+
let drag_flag = draggable.clone();
|
|
305
|
+
let pointerdown = Closure::wrap(Box::new(move |event: PointerEvent| {
|
|
306
|
+
if !drag_flag.get() {
|
|
307
|
+
event.stop_propagation();
|
|
308
|
+
event.prevent_default();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
let ts = event.unchecked_ref::<Event>().time_stamp();
|
|
312
|
+
link.send_message(PanelTabMsg::PointerDown(ts));
|
|
313
|
+
}) as Box<dyn FnMut(PointerEvent)>);
|
|
314
|
+
let _ = host
|
|
315
|
+
.add_event_listener_with_callback("pointerdown", pointerdown.as_ref().unchecked_ref());
|
|
316
|
+
|
|
317
|
+
let link = ctx.link().clone();
|
|
318
|
+
let contextmenu = Closure::wrap(Box::new(move |event: MouseEvent| {
|
|
319
|
+
// Shift+right-click passes through to the native browser menu.
|
|
320
|
+
if event.shift_key() {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
event.prevent_default();
|
|
325
|
+
event.stop_propagation();
|
|
326
|
+
link.send_message(PanelTabMsg::ContextMenu(
|
|
327
|
+
event.client_x() as f64,
|
|
328
|
+
event.client_y() as f64,
|
|
329
|
+
));
|
|
330
|
+
}) as Box<dyn FnMut(MouseEvent)>);
|
|
331
|
+
let _ = host
|
|
332
|
+
.add_event_listener_with_callback("contextmenu", contextmenu.as_ref().unchecked_ref());
|
|
333
|
+
|
|
334
|
+
Self {
|
|
335
|
+
host,
|
|
336
|
+
shadow_root,
|
|
337
|
+
_pointerdown: pointerdown,
|
|
338
|
+
_contextmenu: contextmenu,
|
|
339
|
+
draggable,
|
|
340
|
+
last_pointerdown: f64::NEG_INFINITY,
|
|
341
|
+
editing: false,
|
|
342
|
+
edit_value: String::new(),
|
|
343
|
+
input_ref: NodeRef::default(),
|
|
344
|
+
focus_pending: false,
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
fn changed(&mut self, ctx: &Context<Self>, old: &Self::Properties) -> bool {
|
|
349
|
+
if ctx.props().panel_id != old.panel_id {
|
|
350
|
+
let _ = self
|
|
351
|
+
.host
|
|
352
|
+
.set_attribute("slot", &Self::slot_name(&ctx.props().panel_id));
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
self.draggable.set(ctx.props().draggable);
|
|
356
|
+
true
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
360
|
+
let id = ctx.props().panel_id.clone();
|
|
361
|
+
match msg {
|
|
362
|
+
PanelTabMsg::PointerDown(ts) => {
|
|
363
|
+
ctx.props().on_select.emit(id);
|
|
364
|
+
let is_double = ts - self.last_pointerdown <= DBLCLICK_MS;
|
|
365
|
+
self.last_pointerdown = ts;
|
|
366
|
+
is_double && self.begin_edit(ctx)
|
|
367
|
+
},
|
|
368
|
+
PanelTabMsg::Close => {
|
|
369
|
+
ctx.props().on_close.emit(id);
|
|
370
|
+
false
|
|
371
|
+
},
|
|
372
|
+
PanelTabMsg::OpenSettings => {
|
|
373
|
+
ctx.props().on_open_settings.emit(id);
|
|
374
|
+
false
|
|
375
|
+
},
|
|
376
|
+
PanelTabMsg::ContextMenu(x, y) => {
|
|
377
|
+
ctx.props().on_context_menu.emit((id, x, y));
|
|
378
|
+
false
|
|
379
|
+
},
|
|
380
|
+
PanelTabMsg::EditInput(value) => {
|
|
381
|
+
self.edit_value = value;
|
|
382
|
+
true
|
|
383
|
+
},
|
|
384
|
+
PanelTabMsg::CommitEdit => {
|
|
385
|
+
if !self.editing {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
self.editing = false;
|
|
390
|
+
let value = self.edit_value.trim();
|
|
391
|
+
let title = (!value.is_empty()).then(|| value.to_owned());
|
|
392
|
+
ctx.props().on_rename.emit((id, title));
|
|
393
|
+
true
|
|
394
|
+
},
|
|
395
|
+
PanelTabMsg::CancelEdit => {
|
|
396
|
+
if !self.editing {
|
|
397
|
+
return false;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
self.editing = false;
|
|
401
|
+
true
|
|
402
|
+
},
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
407
|
+
let on_close = ctx.link().callback(|e: PointerEvent| {
|
|
408
|
+
e.stop_propagation();
|
|
409
|
+
PanelTabMsg::Close
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
let on_open_settings = ctx.link().callback(|e: PointerEvent| {
|
|
413
|
+
e.stop_propagation();
|
|
414
|
+
PanelTabMsg::OpenSettings
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
let title_html = if self.editing {
|
|
418
|
+
let oninput = ctx.link().callback(|e: InputEvent| {
|
|
419
|
+
let value = e
|
|
420
|
+
.target()
|
|
421
|
+
.map(|t| t.unchecked_into::<HtmlInputElement>().value())
|
|
422
|
+
.unwrap_or_default();
|
|
423
|
+
PanelTabMsg::EditInput(value)
|
|
424
|
+
});
|
|
425
|
+
let onblur = ctx.link().callback(|_: FocusEvent| PanelTabMsg::CommitEdit);
|
|
426
|
+
let onkeydown = ctx
|
|
427
|
+
.link()
|
|
428
|
+
.batch_callback(|e: KeyboardEvent| match e.key().as_str() {
|
|
429
|
+
"Enter" => vec![PanelTabMsg::CommitEdit],
|
|
430
|
+
"Escape" => vec![PanelTabMsg::CancelEdit],
|
|
431
|
+
_ => vec![],
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
let onpointerdown = ctx.link().batch_callback(|e: PointerEvent| {
|
|
435
|
+
e.stop_propagation();
|
|
436
|
+
Vec::<PanelTabMsg>::new()
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
html! {
|
|
440
|
+
<label class="psp-tab-title input-sizer" data-value={self.edit_value.clone()}>
|
|
441
|
+
<input
|
|
442
|
+
ref={self.input_ref.clone()}
|
|
443
|
+
value={self.edit_value.clone()}
|
|
444
|
+
{oninput}
|
|
445
|
+
{onblur}
|
|
446
|
+
{onkeydown}
|
|
447
|
+
{onpointerdown}
|
|
448
|
+
/>
|
|
449
|
+
</label>
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
// The panel's explicit title, else a placeholder (NOT the table /
|
|
453
|
+
// plugin name) rendered in the inactive color (see panel-tab.css
|
|
454
|
+
// `.psp-tab-title.placeholder`).
|
|
455
|
+
match ctx.props().title.clone().filter(|t| !t.is_empty()) {
|
|
456
|
+
Some(title) => html! { <span class="psp-tab-title">{ title }</span> },
|
|
457
|
+
None => html! {
|
|
458
|
+
<span class="psp-tab-title placeholder">{ TITLE_PLACEHOLDER }</span>
|
|
459
|
+
},
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
let content = html! {
|
|
464
|
+
<>
|
|
465
|
+
<span class="psp-tab-caret" />
|
|
466
|
+
// <span class="psp-tab-grip" />
|
|
467
|
+
{ title_html }
|
|
468
|
+
if ctx.props().is_master { <span class="psp-tab-master" /> }
|
|
469
|
+
if !ctx.props().is_settings_open {
|
|
470
|
+
<button class="psp-tab-settings" onpointerdown={on_open_settings} />
|
|
471
|
+
} else if ctx.props().closable {
|
|
472
|
+
<button class="psp-tab-close" onpointerdown={on_close} />
|
|
473
|
+
}
|
|
474
|
+
</>
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
yew::create_portal(content, self.shadow_root.clone())
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
|
|
481
|
+
self.sync_class(ctx.props().active, ctx.props().visible, ctx.props().single);
|
|
482
|
+
match &ctx.props().theme {
|
|
483
|
+
Some(theme) => {
|
|
484
|
+
let _ = self.host.set_attribute("theme", theme);
|
|
485
|
+
},
|
|
486
|
+
None => {
|
|
487
|
+
let _ = self.host.remove_attribute("theme");
|
|
488
|
+
},
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if !self.host.is_connected() {
|
|
492
|
+
let _ = ctx.props().viewer.append_child(&self.host);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Focus + select-all once, on entry to edit mode.
|
|
496
|
+
if self.focus_pending {
|
|
497
|
+
self.focus_pending = false;
|
|
498
|
+
if let Some(input) = self.input_ref.cast::<HtmlInputElement>() {
|
|
499
|
+
let _ = input.focus();
|
|
500
|
+
input.select();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
fn destroy(&mut self, ctx: &Context<Self>) {
|
|
506
|
+
if self.host.is_connected() {
|
|
507
|
+
let _ = ctx.props().viewer.remove_child(&self.host);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
@@ -87,7 +87,7 @@ pub fn PluginTab(props: &PluginTabProps) -> Html {
|
|
|
87
87
|
yew::Callback::from(move |update: crate::config::ColumnConfigFieldUpdate| {
|
|
88
88
|
// `send_plugin_config` emits `plugin_config_changed`,
|
|
89
89
|
// which the root component's subscription
|
|
90
|
-
// (`
|
|
90
|
+
// (`create_active_subscriptions`) turns into an `UpdateRenderer`
|
|
91
91
|
// dispatch carrying a fresh `RendererProps`. Yew's prop
|
|
92
92
|
// diff propagates the new `plugin_config` into this
|
|
93
93
|
// component automatically — no manual revision bump.
|
|
@@ -170,6 +170,13 @@ impl Component for PortalModal {
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
172
|
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
|
|
173
|
+
// The host element (`tag_name`) and its adopted sheet (`surface`) are
|
|
174
|
+
// fixed at `create` — a swap at the same vdom position must be keyed
|
|
175
|
+
// so Yew recreates the component (see `PanelMenu`'s menu→picker
|
|
176
|
+
// stages).
|
|
177
|
+
debug_assert_eq!(ctx.props().tag_name, old_props.tag_name);
|
|
178
|
+
debug_assert_eq!(ctx.props().surface, old_props.surface);
|
|
179
|
+
|
|
173
180
|
let new_target = &ctx.props().target;
|
|
174
181
|
let old_target = &old_props.target;
|
|
175
182
|
|
|
@@ -26,7 +26,7 @@ use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
|
|
|
26
26
|
use crate::renderer::*;
|
|
27
27
|
use crate::session::column_defaults_update::*;
|
|
28
28
|
use crate::session::*;
|
|
29
|
-
use crate::tasks::
|
|
29
|
+
use crate::tasks::update_plugin_and_render;
|
|
30
30
|
use crate::utils::*;
|
|
31
31
|
|
|
32
32
|
#[derive(Clone, Properties)]
|
|
@@ -161,7 +161,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
161
161
|
let on_auto_width = props.on_auto_width.clone();
|
|
162
162
|
|
|
163
163
|
// Dispatch callback: captures engine handles, constructs config update,
|
|
164
|
-
// hands the apply+draw work to `tasks::
|
|
164
|
+
// hands the apply+draw work to `tasks::pipeline`.
|
|
165
165
|
let on_select_plugin = {
|
|
166
166
|
clone!(renderer, session, presentation);
|
|
167
167
|
let session_metadata = props.metadata.clone();
|
|
@@ -170,9 +170,16 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
170
170
|
if session.is_errored() {
|
|
171
171
|
return;
|
|
172
172
|
}
|
|
173
|
-
|
|
173
|
+
// Pure resolve — the swap itself is committed inside the locked
|
|
174
|
+
// draw task by `update_plugin_and_render`, never staged on the
|
|
175
|
+
// `Renderer` where a concurrent draw could observe it.
|
|
176
|
+
let resolved_plugin =
|
|
177
|
+
renderer.resolve_plugin_update(&PluginUpdate::Update(plugin_name));
|
|
174
178
|
let prev_metadata = renderer.metadata();
|
|
175
|
-
let plugin_config =
|
|
179
|
+
let plugin_config = resolved_plugin
|
|
180
|
+
.as_ref()
|
|
181
|
+
.map(|(_, metadata)| &**metadata)
|
|
182
|
+
.unwrap_or(&*prev_metadata);
|
|
176
183
|
let rollup_features = session_metadata
|
|
177
184
|
.get_features()
|
|
178
185
|
.map(|x| x.get_group_rollup_modes())
|
|
@@ -190,7 +197,8 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
190
197
|
plugin_config,
|
|
191
198
|
);
|
|
192
199
|
|
|
193
|
-
|
|
200
|
+
let plugin_idx = resolved_plugin.map(|(idx, _)| idx);
|
|
201
|
+
if let Ok(task) = update_plugin_and_render(&session, &renderer, update, plugin_idx) {
|
|
194
202
|
ApiFuture::spawn(task);
|
|
195
203
|
}
|
|
196
204
|
|