@perspective-dev/viewer 5.5.0 → 5.6.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/README.md +82 -3
- package/dist/cdn/perspective-viewer.js +9 -9
- package/dist/cdn/perspective-viewer.js.map +3 -3
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/ar.css +1 -1
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/column-format.js +2 -0
- package/dist/esm/column-format.js.map +7 -0
- package/dist/esm/extensions.d.ts +3 -13
- package/dist/esm/perspective-viewer.d.ts +1 -1
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +3 -3
- package/dist/esm/plugin.d.ts +4 -1
- package/dist/esm/select-detail.d.ts +15 -0
- package/dist/esm/select-detail.js +2 -0
- package/dist/esm/select-detail.js.map +7 -0
- package/dist/esm/ts-rs/DerivedTableConfig.d.ts +35 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +7 -6
- package/dist/esm/ts-rs/RestoreOptions.d.ts +3 -7
- package/dist/esm/ts-rs/TableRef.d.ts +9 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +7 -6
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -5
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +9 -0
- package/dist/wasm/perspective-viewer.d.ts +126 -35
- package/dist/wasm/perspective-viewer.js +320 -185
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +21 -17
- package/package.json +33 -20
- package/src/css/column-selector.css +205 -50
- package/src/css/ui/containers/context-menu.css +12 -1
- package/src/css/ui/containers/dropdown-menu-items.css +29 -0
- package/src/css/ui/containers/scroll-panel.css +4 -1
- package/src/rust/agent/client/protocol.rs +0 -89
- package/src/rust/agent/markdown/mod.rs +0 -238
- package/src/rust/agent/preamble.md +1 -0
- package/src/rust/agent/tools.rs +2 -16
- package/src/rust/components/column_selector/active_column.rs +15 -5
- package/src/rust/components/column_selector/add_expression_button.rs +9 -26
- package/src/rust/components/column_selector/config_selector.rs +34 -6
- package/src/rust/components/column_selector/derived_control.rs +229 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +3 -2
- package/src/rust/components/column_selector/filter_column.rs +0 -2
- package/src/rust/components/column_selector/inactive_column.rs +4 -5
- package/src/rust/components/column_selector/pivot_column.rs +21 -1
- package/src/rust/components/column_selector/sort_column.rs +4 -3
- package/src/rust/components/column_selector.rs +21 -1
- package/src/rust/components/column_settings_sidebar/save_settings.rs +5 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +1 -0
- package/src/rust/components/column_settings_sidebar.rs +25 -19
- package/src/rust/components/debug_panel.rs +38 -29
- package/src/rust/components/dragdrop_list.rs +8 -9
- package/src/rust/components/export_dropdown.rs +6 -0
- package/src/rust/components/main_panel/msg.rs +6 -4
- package/src/rust/components/main_panel/reconcile.rs +6 -1
- package/src/rust/components/main_panel/render.rs +70 -51
- package/src/rust/components/main_panel/update.rs +39 -1
- package/src/rust/components/main_panel.rs +97 -59
- package/src/rust/components/new_panel_menu.rs +69 -1
- package/src/rust/components/panel_menu.rs +63 -12
- package/src/rust/components/plugin_tab.rs +3 -2
- package/src/rust/components/render_warning.rs +2 -1
- package/src/rust/components/settings_panel.rs +4 -4
- package/src/rust/components/status_bar.rs +80 -21
- package/src/rust/components/status_indicator.rs +8 -1
- package/src/rust/components/viewer/filters.rs +15 -3
- package/src/rust/components/viewer/msg.rs +8 -0
- package/src/rust/components/viewer/panels.rs +44 -4
- package/src/rust/components/viewer/render.rs +1 -0
- package/src/rust/components/viewer/settings.rs +0 -22
- package/src/rust/components/viewer/snapshots.rs +2 -2
- package/src/rust/components/viewer/wiring.rs +18 -1
- package/src/rust/components/viewer.rs +47 -27
- package/src/rust/components/window_editor.rs +5 -4
- package/src/rust/config/column_config_schema.rs +38 -377
- package/src/rust/config/export_method.rs +24 -0
- package/src/rust/config/number_string_format.rs +0 -94
- package/src/rust/config/options.rs +3 -7
- package/src/rust/config/plugin_static_config.rs +0 -42
- package/src/rust/config/viewer_config.rs +47 -17
- package/src/rust/config/workspace_config.rs +158 -128
- package/src/rust/custom_elements/viewer.rs +189 -129
- package/src/rust/custom_events.rs +33 -18
- package/src/rust/derived.rs +266 -0
- package/src/rust/exprtk/tokenize.rs +0 -54
- package/src/rust/js/plugin.rs +1 -1
- package/src/rust/lib.rs +3 -0
- package/src/rust/presentation.rs +23 -4
- package/src/rust/queries/column_locator.rs +55 -48
- package/src/rust/queries/get_viewer_config.rs +1 -1
- package/src/rust/queries/plugin_column_styles.rs +22 -23
- package/src/rust/renderer/dispatch.rs +2 -0
- package/src/rust/renderer/plugin_config.rs +652 -487
- package/src/rust/renderer/props.rs +4 -0
- package/src/rust/renderer/registry.rs +2 -6
- package/src/rust/renderer/state.rs +205 -0
- package/src/rust/renderer.rs +201 -59
- package/src/rust/session/metadata.rs +35 -19
- package/src/rust/session/op_queue.rs +430 -0
- package/src/rust/session/panel_state.rs +327 -0
- package/src/rust/session/view_subscription.rs +42 -3
- package/src/rust/session.rs +931 -419
- package/src/rust/tasks/apply_global_filters.rs +49 -35
- package/src/rust/tasks/auto_pause.rs +22 -12
- package/src/rust/tasks/copy_export.rs +72 -0
- package/src/rust/tasks/create_panel.rs +46 -21
- package/src/rust/tasks/derive.rs +312 -0
- package/src/rust/tasks/mod.rs +6 -2
- package/src/rust/tasks/pipeline.rs +152 -89
- package/src/rust/tasks/reset_all.rs +31 -20
- package/src/rust/tasks/restore_derived.rs +200 -0
- package/src/rust/tasks/restore_panel.rs +153 -141
- package/src/rust/tasks/send_column_config.rs +56 -29
- package/src/rust/tasks/send_plugin_config.rs +52 -23
- package/src/rust/tasks/set_edit_mode.rs +33 -25
- package/src/rust/tasks/sync_update_panels.rs +40 -13
- package/src/rust/tasks/table_lifecycle.rs +70 -34
- package/src/rust/tasks/transactional_restore.rs +387 -0
- package/src/rust/tasks/update_theme.rs +45 -6
- package/src/rust/ui/containers/scroll_panel.rs +17 -10
- package/src/rust/ui/containers/split_panel.rs +0 -41
- package/src/rust/ui/context_menu.rs +157 -18
- package/src/rust/ui/font_loader.rs +23 -35
- package/src/rust/ui/form/mirrored_textarea.rs +0 -12
- package/src/rust/ui/form/multi_stop_gradient_selector.rs +0 -89
- package/src/rust/ui/form/palette_selector.rs +0 -51
- package/src/rust/ui/portal.rs +4 -10
- package/src/rust/utils/browser/mod.rs +2 -3
- package/src/{ts/perspective-viewer.inline.ts → rust/utils/browser/mouse.rs} +11 -10
- package/src/rust/utils/browser/request_animation_frame.rs +27 -8
- package/src/rust/utils/css.rs +0 -360
- package/src/rust/utils/{tests/mod.rs → in_flight.rs} +62 -2
- package/src/rust/utils/local_fonts.rs +0 -36
- package/src/rust/utils/mod.rs +4 -3
- package/src/rust/utils/modal_position.rs +15 -21
- package/src/rust/utils/pending_effects.rs +6 -30
- package/src/rust/utils/{browser/tests/mod.rs → plugin_host_state.rs} +31 -1
- package/src/rust/utils/pubsub.rs +0 -1
- package/src/rust/workspace.rs +187 -140
- package/src/svg/derived.svg +6 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/ar.css +10 -0
- package/src/themes/intl/de.css +10 -0
- package/src/themes/intl/es.css +10 -0
- package/src/themes/intl/fr.css +10 -0
- package/src/themes/intl/ja.css +10 -0
- package/src/themes/intl/pt.css +10 -0
- package/src/themes/intl/zh.css +10 -0
- package/src/themes/intl.css +10 -0
- package/src/themes/monokai.css +1 -1
- package/src/themes/vaporwave.css +1 -1
- package/src/ts/extensions.ts +3 -36
- package/src/ts/perspective-viewer.ts +1 -1
- package/src/ts/plugin.ts +4 -1
- package/src/{rust/utils/tests/request_animation_frame.rs → ts/select-detail.ts} +32 -26
- package/src/ts/ts-rs/DerivedTableConfig.ts +22 -0
- package/src/ts/ts-rs/ExportMethod.ts +1 -1
- package/src/ts/ts-rs/PanelViewerConfig.ts +7 -6
- package/src/ts/ts-rs/RestoreOptions.ts +3 -7
- package/src/ts/ts-rs/TableRef.ts +9 -0
- package/src/ts/ts-rs/ViewerConfig.ts +7 -6
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +7 -5
- package/src/ts/ts-rs/WorkspaceConfig.ts +7 -1
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -0
- package/dist/esm/perspective-viewer.inline.d.ts +0 -3
- package/dist/esm/perspective-viewer.inline.js +0 -21
- package/dist/esm/perspective-viewer.inline.js.map +0 -7
- package/src/rust/tasks/restore_and_render.rs +0 -107
- package/src/rust/utils/browser/tests/debounce.rs +0 -163
- package/src/rust/utils/tests/pubsub.rs +0 -95
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline5.js +0 -0
|
@@ -15,6 +15,7 @@ use std::rc::Rc;
|
|
|
15
15
|
use crate::config::PluginStaticConfig;
|
|
16
16
|
use crate::renderer::limits::RenderLimits;
|
|
17
17
|
use crate::renderer::plugin_config::ColumnConfigMap;
|
|
18
|
+
use crate::renderer::state::RenderError;
|
|
18
19
|
use crate::utils::PtrEqRc;
|
|
19
20
|
|
|
20
21
|
/// Value-semantic snapshot of the renderer state read by components.
|
|
@@ -55,6 +56,9 @@ pub struct RendererProps {
|
|
|
55
56
|
/// Snapshot of the active plugin's `columns_config` bucket, re-read by
|
|
56
57
|
/// `Renderer::to_props` on `columns_config_changed` and `plugin_changed`.
|
|
57
58
|
pub columns_config: PtrEqRc<ColumnConfigMap>,
|
|
59
|
+
|
|
60
|
+
/// The failure of the last draw, which stands until the next commit.
|
|
61
|
+
pub render_error: Option<RenderError>,
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
impl RendererProps {
|
|
@@ -43,7 +43,8 @@ pub impl LocalKey<Rc<RefCell<Vec<PluginRecord>>>> {
|
|
|
43
43
|
let mut elements = vec![];
|
|
44
44
|
for plugin in plugins.borrow().iter() {
|
|
45
45
|
let element = create_plugin(&plugin.tag_name);
|
|
46
|
-
// let style =
|
|
46
|
+
// let style =
|
|
47
|
+
// element.unchecked_ref::<HtmlElement>().style();
|
|
47
48
|
// style.set_property("position", "absolute")?;
|
|
48
49
|
// style.set_property("top", "0")?;
|
|
49
50
|
// style.set_property("right", "0")?;
|
|
@@ -147,11 +148,6 @@ pub impl LocalKey<Rc<RefCell<Vec<PluginRecord>>>> {
|
|
|
147
148
|
});
|
|
148
149
|
});
|
|
149
150
|
}
|
|
150
|
-
|
|
151
|
-
#[cfg(test)]
|
|
152
|
-
fn reset(&'static self) {
|
|
153
|
-
self.with(|plugins| plugins.borrow_mut().clear());
|
|
154
|
-
}
|
|
155
151
|
}
|
|
156
152
|
|
|
157
153
|
fn register_default() {
|
|
@@ -0,0 +1,205 @@
|
|
|
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::RefCell;
|
|
14
|
+
use std::collections::HashMap;
|
|
15
|
+
use std::rc::Rc;
|
|
16
|
+
|
|
17
|
+
use perspective_js::utils::ApiError;
|
|
18
|
+
|
|
19
|
+
use super::plugin_config::PluginScopedConfig;
|
|
20
|
+
use crate::config::{ColumnConfigFieldUpdate, PluginStaticConfig};
|
|
21
|
+
|
|
22
|
+
/// How a panel is drawn, as one immutable value replaced whole.
|
|
23
|
+
#[derive(Clone, Default)]
|
|
24
|
+
pub struct RendererState {
|
|
25
|
+
/// The selected plugin, or `None` until the first run selects one.
|
|
26
|
+
pub plugin: Option<PluginRef>,
|
|
27
|
+
|
|
28
|
+
/// Every plugin's own `plugin_config` and `columns_config`, keyed by
|
|
29
|
+
/// plugin name.
|
|
30
|
+
pub buckets: Rc<HashMap<String, PluginScopedConfig>>,
|
|
31
|
+
|
|
32
|
+
/// This panel's concrete theme name.
|
|
33
|
+
pub theme: Option<String>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// A plugin selection: its index in the plugin store and its static config.
|
|
37
|
+
#[derive(Clone)]
|
|
38
|
+
pub struct PluginRef {
|
|
39
|
+
pub idx: usize,
|
|
40
|
+
pub static_config: Rc<PluginStaticConfig>,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
impl RendererState {
|
|
44
|
+
pub fn with_theme(&self, theme: Option<String>) -> Self {
|
|
45
|
+
Self {
|
|
46
|
+
theme,
|
|
47
|
+
..self.clone()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
pub fn with_plugin(&self, plugin: PluginRef) -> Self {
|
|
52
|
+
Self {
|
|
53
|
+
plugin: Some(plugin),
|
|
54
|
+
..self.clone()
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// This state with no plugin selected and every bucket forgotten.
|
|
59
|
+
pub fn without_plugins(&self) -> Self {
|
|
60
|
+
Self {
|
|
61
|
+
plugin: None,
|
|
62
|
+
buckets: Rc::default(),
|
|
63
|
+
..self.clone()
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/// The named plugin's bucket, empty when it has never been written.
|
|
68
|
+
pub fn bucket(&self, name: &str) -> PluginScopedConfig {
|
|
69
|
+
self.buckets.get(name).cloned().unwrap_or_default()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/// This state with the named plugin's bucket replaced.
|
|
73
|
+
pub fn with_bucket(&self, name: &str, bucket: PluginScopedConfig) -> Self {
|
|
74
|
+
let mut buckets = (*self.buckets).clone();
|
|
75
|
+
buckets.insert(name.to_owned(), bucket);
|
|
76
|
+
Self {
|
|
77
|
+
buckets: Rc::new(buckets),
|
|
78
|
+
..self.clone()
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// A failure to draw a valid committed state.
|
|
84
|
+
#[derive(Clone)]
|
|
85
|
+
pub struct RenderError(pub ApiError);
|
|
86
|
+
|
|
87
|
+
impl PartialEq for RenderError {
|
|
88
|
+
fn eq(&self, other: &Self) -> bool {
|
|
89
|
+
self.0.to_string() == other.0.to_string()
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
impl std::fmt::Debug for RenderError {
|
|
94
|
+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
95
|
+
f.debug_tuple("RenderError")
|
|
96
|
+
.field(&self.0.to_string())
|
|
97
|
+
.finish()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/// How the last draw of the committed state went, which any commit resets.
|
|
102
|
+
#[derive(Clone, Default)]
|
|
103
|
+
pub enum RenderOutcome {
|
|
104
|
+
#[default]
|
|
105
|
+
Never,
|
|
106
|
+
Ok,
|
|
107
|
+
Failed(RenderError),
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// A UI edit of the renderer's state that is queued but not yet committed.
|
|
111
|
+
#[derive(Clone)]
|
|
112
|
+
pub enum StagedEdit {
|
|
113
|
+
/// One plugin-level settings field of the selected plugin.
|
|
114
|
+
PluginField(ColumnConfigFieldUpdate),
|
|
115
|
+
|
|
116
|
+
/// One style field of one column, for the selected plugin.
|
|
117
|
+
ColumnField {
|
|
118
|
+
column: String,
|
|
119
|
+
update: ColumnConfigFieldUpdate,
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
/// Keys merged into the selected plugin's `plugin_config`.
|
|
123
|
+
PluginConfig(serde_json::Map<String, serde_json::Value>),
|
|
124
|
+
Theme(Option<String>),
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// The renderer's staged edits, in the order they were made.
|
|
128
|
+
#[derive(Default)]
|
|
129
|
+
pub(super) struct StagedEdits {
|
|
130
|
+
next_id: std::cell::Cell<u64>,
|
|
131
|
+
edits: RefCell<Vec<(u64, StagedEdit)>>,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
impl StagedEdits {
|
|
135
|
+
pub fn stage(self: &Rc<Self>, edit: StagedEdit) -> Staged {
|
|
136
|
+
let id = self.next_id.get();
|
|
137
|
+
self.next_id.set(id + 1);
|
|
138
|
+
self.edits.borrow_mut().push((id, edit));
|
|
139
|
+
Staged {
|
|
140
|
+
id,
|
|
141
|
+
edits: Rc::downgrade(self),
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
pub fn pending(&self) -> Vec<StagedEdit> {
|
|
146
|
+
self.edits.borrow().iter().map(|x| x.1.clone()).collect()
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// Keeps one [`StagedEdit`] visible to the UI until its op leaves the queue.
|
|
151
|
+
pub struct Staged {
|
|
152
|
+
id: u64,
|
|
153
|
+
edits: std::rc::Weak<StagedEdits>,
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
impl Drop for Staged {
|
|
157
|
+
fn drop(&mut self) {
|
|
158
|
+
if let Some(edits) = self.edits.upgrade() {
|
|
159
|
+
edits.edits.borrow_mut().retain(|x| x.0 != self.id);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// `bucket` with every pending bucket edit applied, in the order made.
|
|
165
|
+
pub(super) fn project_bucket(
|
|
166
|
+
mut bucket: PluginScopedConfig,
|
|
167
|
+
pending: Vec<StagedEdit>,
|
|
168
|
+
) -> PluginScopedConfig {
|
|
169
|
+
for edit in pending {
|
|
170
|
+
match edit {
|
|
171
|
+
StagedEdit::PluginField(update) => {
|
|
172
|
+
for key in &update.keys {
|
|
173
|
+
match update.value.get(key) {
|
|
174
|
+
Some(value) => {
|
|
175
|
+
bucket.plugin.insert(key.clone(), value.clone());
|
|
176
|
+
},
|
|
177
|
+
None => {
|
|
178
|
+
bucket.plugin.remove(key);
|
|
179
|
+
},
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
StagedEdit::ColumnField { column, update } => {
|
|
184
|
+
let entry = bucket.columns.entry(column.clone()).or_default();
|
|
185
|
+
for key in &update.keys {
|
|
186
|
+
entry.remove(key);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (key, value) in update.value {
|
|
190
|
+
if update.keys.contains(&key) {
|
|
191
|
+
entry.insert(key, value);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if entry.is_empty() {
|
|
196
|
+
bucket.columns.remove(&column);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
StagedEdit::PluginConfig(map) => bucket.plugin.extend(map),
|
|
200
|
+
StagedEdit::Theme(_) => {},
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
bucket
|
|
205
|
+
}
|
package/src/rust/renderer.rs
CHANGED
|
@@ -25,6 +25,7 @@ mod plugin_store;
|
|
|
25
25
|
mod props;
|
|
26
26
|
mod registry;
|
|
27
27
|
mod render_timer;
|
|
28
|
+
mod state;
|
|
28
29
|
|
|
29
30
|
use std::cell::{Cell, RefCell};
|
|
30
31
|
use std::collections::HashMap;
|
|
@@ -32,7 +33,7 @@ use std::ops::Deref;
|
|
|
32
33
|
use std::rc::Rc;
|
|
33
34
|
|
|
34
35
|
use perspective_client::ViewWindow;
|
|
35
|
-
use perspective_js::utils::{ApiFuture, ApiResult, JsValueSerdeExt};
|
|
36
|
+
use perspective_js::utils::{ApiError, ApiFuture, ApiResult, JsValueSerdeExt};
|
|
36
37
|
use wasm_bindgen::prelude::*;
|
|
37
38
|
use web_sys::*;
|
|
38
39
|
use yew::html::ImplicitClone;
|
|
@@ -40,11 +41,16 @@ use yew::prelude::*;
|
|
|
40
41
|
|
|
41
42
|
use self::activate::*;
|
|
42
43
|
pub use self::limits::RenderLimits;
|
|
43
|
-
pub use self::plugin_config::{
|
|
44
|
+
pub use self::plugin_config::{
|
|
45
|
+
ColumnConfigMap, ColumnRole, PluginScopedConfig, PreparedRenderer, ValidatedColumnsConfig,
|
|
46
|
+
ValidatedPluginConfig,
|
|
47
|
+
};
|
|
44
48
|
use self::plugin_store::*;
|
|
45
49
|
pub use self::props::RendererProps;
|
|
46
50
|
pub use self::registry::*;
|
|
47
51
|
use self::render_timer::*;
|
|
52
|
+
pub use self::state::{PluginRef, RenderError, RendererState, Staged, StagedEdit};
|
|
53
|
+
use self::state::{RenderOutcome, StagedEdits, project_bucket};
|
|
48
54
|
use crate::config::*;
|
|
49
55
|
use crate::js::plugin::*;
|
|
50
56
|
use crate::utils::*;
|
|
@@ -97,6 +103,13 @@ impl Drop for ContextPin {
|
|
|
97
103
|
|
|
98
104
|
/// Immutable state
|
|
99
105
|
pub struct RendererData {
|
|
106
|
+
state: RefCell<Rc<RendererState>>,
|
|
107
|
+
outcome: RefCell<RenderOutcome>,
|
|
108
|
+
|
|
109
|
+
/// Injected callback from the root component, fired when a render failure
|
|
110
|
+
/// is raised or cleared.
|
|
111
|
+
pub on_render_failure_changed: RefCell<Option<Callback<()>>>,
|
|
112
|
+
staged: Rc<StagedEdits>,
|
|
100
113
|
plugin_data: RefCell<RendererMutData>,
|
|
101
114
|
draw_lock: DebounceMutex,
|
|
102
115
|
pub plugin_changed: PubSub<JsPerspectiveViewerPlugin>,
|
|
@@ -151,20 +164,12 @@ pub struct RendererData {
|
|
|
151
164
|
/// coexist there.
|
|
152
165
|
slot_name: RefCell<Option<String>>,
|
|
153
166
|
|
|
154
|
-
/// This panel's theme name — CONCRETE, resolved once at creation from
|
|
155
|
-
/// the config's `theme`, else the host's, else the registry default.
|
|
156
|
-
/// There is no "unthemed panel" that resolves against live element
|
|
157
|
-
/// state at draw time: a registry re-ordering must never repaint a
|
|
158
|
-
/// panel, so nothing but an explicit write may change this. `None` only
|
|
159
|
-
/// while no themes exist at all.
|
|
160
|
-
theme: RefCell<Option<String>>,
|
|
161
|
-
|
|
162
167
|
/// Whether the active plugin has completed a draw. An EXPLICIT flag —
|
|
163
168
|
/// not inferred from DOM connectedness — because plugin elements may be
|
|
164
169
|
/// mounted eagerly (at panel creation / draw start, before the view
|
|
165
170
|
/// query resolves), so "in the DOM" no longer implies "has rendered".
|
|
166
171
|
/// Set by a successful `draw_view`; cleared on plugin swap
|
|
167
|
-
/// (`
|
|
172
|
+
/// (`activate_committed_plugin`), `dispose` and `delete`.
|
|
168
173
|
has_drawn: Cell<bool>,
|
|
169
174
|
|
|
170
175
|
/// The effective theme stamped at the active plugin's last `--psp-*`
|
|
@@ -210,12 +215,9 @@ pub struct RendererData {
|
|
|
210
215
|
/// Mutable state
|
|
211
216
|
pub struct RendererMutData {
|
|
212
217
|
viewer_elem: HtmlElement,
|
|
213
|
-
metadata: Rc<PluginStaticConfig>,
|
|
214
218
|
plugin_store: PluginStore,
|
|
215
|
-
plugins_idx: Option<usize>,
|
|
216
219
|
timer: MovingWindowRenderTimer,
|
|
217
220
|
selection: Option<ViewWindow>,
|
|
218
|
-
plugin_states: HashMap<String, PluginScopedConfig>,
|
|
219
221
|
}
|
|
220
222
|
|
|
221
223
|
/// The state object responsible for the active [`JsPerspectiveViewerPlugin`].
|
|
@@ -250,14 +252,15 @@ impl Renderer {
|
|
|
250
252
|
pub fn new(viewer_elem: &HtmlElement) -> Self {
|
|
251
253
|
let draw_lock = DebounceMutex::default();
|
|
252
254
|
Self(Rc::new(RendererData {
|
|
255
|
+
state: RefCell::default(),
|
|
256
|
+
outcome: RefCell::default(),
|
|
257
|
+
on_render_failure_changed: RefCell::default(),
|
|
258
|
+
staged: Rc::default(),
|
|
253
259
|
plugin_data: RefCell::new(RendererMutData {
|
|
254
260
|
viewer_elem: viewer_elem.clone(),
|
|
255
|
-
metadata: Rc::new(PluginStaticConfig::default()),
|
|
256
261
|
plugin_store: PluginStore::default(),
|
|
257
|
-
plugins_idx: None,
|
|
258
262
|
selection: None,
|
|
259
263
|
timer: MovingWindowRenderTimer::default(),
|
|
260
|
-
plugin_states: HashMap::default(),
|
|
261
264
|
}),
|
|
262
265
|
geometry_slot: draw_lock.slot(),
|
|
263
266
|
geometry_cmd: Cell::new(None),
|
|
@@ -274,7 +277,6 @@ impl Renderer {
|
|
|
274
277
|
presized_box: Cell::new(None),
|
|
275
278
|
on_render_limits_changed: Default::default(),
|
|
276
279
|
slot_name: Default::default(),
|
|
277
|
-
theme: Default::default(),
|
|
278
280
|
has_drawn: Cell::new(false),
|
|
279
281
|
captured_theme: Default::default(),
|
|
280
282
|
cached_context: Default::default(),
|
|
@@ -297,26 +299,83 @@ impl Renderer {
|
|
|
297
299
|
self.0.slot_name.borrow().clone()
|
|
298
300
|
}
|
|
299
301
|
|
|
300
|
-
///
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
pub fn set_theme(&self, name: Option<String>) {
|
|
304
|
-
*self.0.theme.borrow_mut() = name;
|
|
302
|
+
/// Write this panel's concrete theme name.
|
|
303
|
+
pub fn commit_theme(&self, name: Option<String>) {
|
|
304
|
+
self.swap(self.state().with_theme(name));
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
///
|
|
308
|
-
pub fn
|
|
309
|
-
self.0.
|
|
307
|
+
/// The committed state, which is what a running step reads.
|
|
308
|
+
pub fn state(&self) -> Rc<RendererState> {
|
|
309
|
+
self.0.state.borrow().clone()
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
pub(crate) fn swap(&self, next: RendererState) {
|
|
313
|
+
*self.0.state.borrow_mut() = Rc::new(next);
|
|
314
|
+
self.set_outcome(RenderOutcome::Never);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/// The failure of the last draw, which stands until the next commit.
|
|
318
|
+
pub fn failure(&self) -> Option<RenderError> {
|
|
319
|
+
match &*self.0.outcome.borrow() {
|
|
320
|
+
RenderOutcome::Failed(error) => Some(error.clone()),
|
|
321
|
+
_ => None,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/// Record that the draw of the committed state failed.
|
|
326
|
+
pub fn fail(&self, error: ApiError) -> ApiResult<()> {
|
|
327
|
+
self.set_outcome(RenderOutcome::Failed(RenderError(error.clone())));
|
|
328
|
+
Err(error)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/// Record that the draw of the committed state landed.
|
|
332
|
+
pub fn landed(&self) {
|
|
333
|
+
self.set_outcome(RenderOutcome::Ok);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
fn set_outcome(&self, next: RenderOutcome) {
|
|
337
|
+
let was_failed = self.failure().is_some();
|
|
338
|
+
let is_failed = matches!(next, RenderOutcome::Failed(_));
|
|
339
|
+
*self.0.outcome.borrow_mut() = next;
|
|
340
|
+
if was_failed != is_failed
|
|
341
|
+
&& let Some(cb) = self.0.on_render_failure_changed.borrow().as_ref()
|
|
342
|
+
{
|
|
343
|
+
cb.emit(());
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/// Show `edit` to the UI until the returned guard is dropped.
|
|
348
|
+
pub fn stage(&self, edit: StagedEdit) -> Staged {
|
|
349
|
+
self.0.staged.stage(edit)
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
fn projected_bucket(&self, name: &str) -> PluginScopedConfig {
|
|
353
|
+
project_bucket(self.state().bucket(name), self.0.staged.pending())
|
|
310
354
|
}
|
|
311
355
|
|
|
312
|
-
/// [`Self::
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
pub fn set_theme_stamped(&self, theme: Option<String>) {
|
|
316
|
-
self.set_theme(theme);
|
|
356
|
+
/// [`Self::commit_theme`] plus a synchronous [`Self::stamp_theme`].
|
|
357
|
+
pub fn commit_theme_stamped(&self, theme: Option<String>) {
|
|
358
|
+
self.commit_theme(theme);
|
|
317
359
|
self.stamp_theme(None);
|
|
318
360
|
}
|
|
319
361
|
|
|
362
|
+
/// This panel's theme name, as the UI sees it: the committed theme, or the
|
|
363
|
+
/// latest pending pick.
|
|
364
|
+
pub fn theme(&self) -> Option<String> {
|
|
365
|
+
let pending = self
|
|
366
|
+
.0
|
|
367
|
+
.staged
|
|
368
|
+
.pending()
|
|
369
|
+
.into_iter()
|
|
370
|
+
.filter_map(|edit| match edit {
|
|
371
|
+
StagedEdit::Theme(theme) => Some(theme),
|
|
372
|
+
_ => None,
|
|
373
|
+
})
|
|
374
|
+
.next_back();
|
|
375
|
+
|
|
376
|
+
pending.unwrap_or_else(|| self.state().theme.clone())
|
|
377
|
+
}
|
|
378
|
+
|
|
320
379
|
/// Whether the active plugin's captured `--psp-*` CSS is STALE — the
|
|
321
380
|
/// effective theme differs from the one stamped at the plugin's last
|
|
322
381
|
/// CSS capture (first paint / last restyle).
|
|
@@ -376,17 +435,24 @@ impl Renderer {
|
|
|
376
435
|
plugin.delete();
|
|
377
436
|
}
|
|
378
437
|
self.plugin_data.borrow().viewer_elem.set_inner_text("");
|
|
379
|
-
let
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
438
|
+
let viewer_elem = self.plugin_data.borrow().viewer_elem.clone();
|
|
439
|
+
*self.plugin_data.borrow_mut() = RendererMutData {
|
|
440
|
+
viewer_elem,
|
|
441
|
+
plugin_store: PluginStore::default(),
|
|
442
|
+
selection: None,
|
|
443
|
+
timer: MovingWindowRenderTimer::default(),
|
|
444
|
+
};
|
|
384
445
|
|
|
446
|
+
self.swap(self.state().without_plugins());
|
|
385
447
|
Ok(())
|
|
386
448
|
}
|
|
387
449
|
|
|
388
450
|
pub fn metadata(&self) -> Rc<PluginStaticConfig> {
|
|
389
|
-
self.
|
|
451
|
+
self.state()
|
|
452
|
+
.plugin
|
|
453
|
+
.as_ref()
|
|
454
|
+
.map(|plugin| plugin.static_config.clone())
|
|
455
|
+
.unwrap_or_default()
|
|
390
456
|
}
|
|
391
457
|
|
|
392
458
|
pub fn is_chart(&self) -> bool {
|
|
@@ -438,9 +504,9 @@ impl Renderer {
|
|
|
438
504
|
/// permanently pins the per-renderer store to the built-in `Debug`
|
|
439
505
|
/// (init-order race, surfaces on Safari).
|
|
440
506
|
pub fn active_plugin(&self) -> Option<JsPerspectiveViewerPlugin> {
|
|
441
|
-
// Bail on `plugins_idx` BEFORE touching `plugin_store`, so an
|
|
442
|
-
// renderer never snapshots the registry.
|
|
443
|
-
let idx = self.
|
|
507
|
+
// Bail on `plugins_idx` BEFORE touching `plugin_store`, so an
|
|
508
|
+
// unselected renderer never snapshots the registry.
|
|
509
|
+
let idx = self.selected_idx()?;
|
|
444
510
|
self.0.borrow_mut().plugin_store.plugins().get(idx).cloned()
|
|
445
511
|
}
|
|
446
512
|
|
|
@@ -488,6 +554,30 @@ impl Renderer {
|
|
|
488
554
|
/// empty panel frame. NOTE eager mounting is exactly why
|
|
489
555
|
/// [`Self::is_plugin_activated`] must be an explicit has-drawn flag, not
|
|
490
556
|
/// DOM-connectedness.
|
|
557
|
+
/// Rename this renderer's slot (and its mounted light-DOM elements'
|
|
558
|
+
/// `slot` attributes) ahead of an eject whose slot name a replacement
|
|
559
|
+
/// panel reuses, so the async [`Self::dispose`] sweep cannot remove the
|
|
560
|
+
/// successor's elements.
|
|
561
|
+
pub fn retire_slot(&self) {
|
|
562
|
+
let Some(slot) = self.slot_name() else {
|
|
563
|
+
return;
|
|
564
|
+
};
|
|
565
|
+
|
|
566
|
+
let viewer = self.0.borrow().viewer_elem.clone();
|
|
567
|
+
let slots = [slot.clone(), format!("statusbar-extra-{slot}")];
|
|
568
|
+
let children = viewer.children();
|
|
569
|
+
for idx in 0..children.length() {
|
|
570
|
+
if let Some(el) = children.item(idx)
|
|
571
|
+
&& let Some(s) = el.get_attribute("slot")
|
|
572
|
+
&& slots.contains(&s)
|
|
573
|
+
{
|
|
574
|
+
let _ = el.set_attribute("slot", &format!("{s}-retired"));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
self.set_slot_name(&format!("{slot}-retired"));
|
|
579
|
+
}
|
|
580
|
+
|
|
491
581
|
pub fn mount_active_plugin(&self) -> ApiResult<()> {
|
|
492
582
|
if let Some(plugin) = self.active_plugin() {
|
|
493
583
|
let viewer_elem = self.0.borrow().viewer_elem.clone();
|
|
@@ -556,7 +646,7 @@ impl Renderer {
|
|
|
556
646
|
|
|
557
647
|
let idx = self.find_plugin_idx(name)?;
|
|
558
648
|
let changed = !matches!(
|
|
559
|
-
self.
|
|
649
|
+
self.selected_idx(),
|
|
560
650
|
Some(selected_idx) if selected_idx == idx
|
|
561
651
|
);
|
|
562
652
|
|
|
@@ -574,6 +664,24 @@ impl Renderer {
|
|
|
574
664
|
}
|
|
575
665
|
}
|
|
576
666
|
|
|
667
|
+
/// Reject a [`PluginUpdate`] naming a plugin that is not registered.
|
|
668
|
+
pub fn check_plugin_update(&self, update: &PluginUpdate) -> ApiResult<()> {
|
|
669
|
+
if let PluginUpdate::Update(name) = update
|
|
670
|
+
&& self.find_plugin_idx(name).is_none()
|
|
671
|
+
{
|
|
672
|
+
let known = self
|
|
673
|
+
.get_all_plugin_configs()
|
|
674
|
+
.iter()
|
|
675
|
+
.map(|c| format!("\"{}\"", c.name))
|
|
676
|
+
.collect::<Vec<_>>()
|
|
677
|
+
.join(", ");
|
|
678
|
+
|
|
679
|
+
return Err(format!("Unknown plugin \"{name}\"; expected one of {known}").into());
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
Ok(())
|
|
683
|
+
}
|
|
684
|
+
|
|
577
685
|
/// Commit a plugin selection previously resolved by
|
|
578
686
|
/// [`Self::resolve_plugin_update`]. COMMAND — call only from inside a
|
|
579
687
|
/// locked draw task, so the swap lands atomically with the view rebuild
|
|
@@ -586,7 +694,7 @@ impl Renderer {
|
|
|
586
694
|
let idx = match idx {
|
|
587
695
|
Some(idx) => idx,
|
|
588
696
|
None => {
|
|
589
|
-
if self.
|
|
697
|
+
if self.selected_idx().is_none() {
|
|
590
698
|
let name = PLUGIN_REGISTRY.default_plugin_name();
|
|
591
699
|
let idx = self
|
|
592
700
|
.find_plugin_idx(&name)
|
|
@@ -600,7 +708,7 @@ impl Renderer {
|
|
|
600
708
|
};
|
|
601
709
|
|
|
602
710
|
let changed = !matches!(
|
|
603
|
-
self.
|
|
711
|
+
self.selected_idx(),
|
|
604
712
|
Some(selected_idx) if selected_idx == idx
|
|
605
713
|
);
|
|
606
714
|
|
|
@@ -616,12 +724,6 @@ impl Renderer {
|
|
|
616
724
|
/// reset the per-plugin render-warning flag, and fire
|
|
617
725
|
/// `plugin_changed`.
|
|
618
726
|
fn commit_plugin_idx(&self, idx: usize) -> ApiResult<()> {
|
|
619
|
-
// The newly-selected plugin element has not drawn (a swap keeps the
|
|
620
|
-
// OLD plugin mounted until the new one's draw lands) — and has
|
|
621
|
-
// captured no CSS.
|
|
622
|
-
self.0.has_drawn.set(false);
|
|
623
|
-
self.0.captured_theme.borrow_mut().take();
|
|
624
|
-
self.borrow_mut().plugins_idx = Some(idx);
|
|
625
727
|
let config = self
|
|
626
728
|
.0
|
|
627
729
|
.borrow_mut()
|
|
@@ -631,22 +733,46 @@ impl Renderer {
|
|
|
631
733
|
.cloned()
|
|
632
734
|
.ok_or("No Plugin")?;
|
|
633
735
|
|
|
634
|
-
self.
|
|
736
|
+
self.swap(self.state().with_plugin(PluginRef {
|
|
737
|
+
idx,
|
|
738
|
+
static_config: config,
|
|
739
|
+
}));
|
|
740
|
+
|
|
741
|
+
self.activate_committed_plugin()
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/// The `static_config` of the plugin at store index `idx`, for a
|
|
745
|
+
/// [`PluginRef`] committed by a transaction rather than by
|
|
746
|
+
/// [`Self::commit_plugin`].
|
|
747
|
+
pub fn plugin_ref(&self, idx: usize) -> ApiResult<PluginRef> {
|
|
748
|
+
let static_config = self
|
|
749
|
+
.0
|
|
750
|
+
.borrow_mut()
|
|
751
|
+
.plugin_store
|
|
752
|
+
.plugin_configs()
|
|
753
|
+
.get(idx)
|
|
754
|
+
.cloned()
|
|
755
|
+
.ok_or("No Plugin")?;
|
|
756
|
+
|
|
757
|
+
Ok(PluginRef { idx, static_config })
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/// Bring the plugin ELEMENT in line with a just-committed selection: the
|
|
761
|
+
/// newly-selected element has drawn nothing and captured no CSS, takes its
|
|
762
|
+
/// own stored bucket, and is announced.
|
|
763
|
+
pub fn activate_committed_plugin(&self) -> ApiResult<()> {
|
|
764
|
+
// The newly-selected plugin element has not drawn (a swap keeps the
|
|
765
|
+
// OLD plugin mounted until the new one's draw lands) — and has
|
|
766
|
+
// captured no CSS.
|
|
767
|
+
self.0.has_drawn.set(false);
|
|
768
|
+
self.0.captured_theme.borrow_mut().take();
|
|
635
769
|
self.0.render_warning.set(true);
|
|
636
|
-
// `commit_plugin_idx` is called *by* the selection path, so it must use
|
|
637
|
-
// the pure query (never `ensure_plugin_selected`, which would recurse
|
|
638
|
-
// through `commit_plugin`). `plugins_idx` was just set above.
|
|
639
770
|
let plugin: JsPerspectiveViewerPlugin = self.active_plugin().ok_or("No Plugin")?;
|
|
640
771
|
|
|
641
772
|
// Push the newly-activated plugin's stored bucket through
|
|
642
773
|
// `plugin.restore` so the swap immediately reflects any
|
|
643
774
|
// viewer-owned per-column and plugin-level config.
|
|
644
|
-
let bucket = self
|
|
645
|
-
.borrow()
|
|
646
|
-
.plugin_states
|
|
647
|
-
.get(&config.name)
|
|
648
|
-
.cloned()
|
|
649
|
-
.unwrap_or_default();
|
|
775
|
+
let bucket = self.state().bucket(&self.metadata().name);
|
|
650
776
|
let token = JsValue::from_serde_ext(&bucket.plugin).unwrap_or(JsValue::NULL);
|
|
651
777
|
if let Err(e) = plugin.restore(&token, Some(&bucket.columns)) {
|
|
652
778
|
tracing::warn!("plugin.restore on swap failed: {:?}", e);
|
|
@@ -656,6 +782,20 @@ impl Renderer {
|
|
|
656
782
|
Ok(())
|
|
657
783
|
}
|
|
658
784
|
|
|
785
|
+
/// The committed plugin selection's index into the plugin store.
|
|
786
|
+
pub fn committed_plugin_idx(&self) -> Option<usize> {
|
|
787
|
+
self.selected_idx()
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/// The COMMITTED panel theme, pending theme edits excluded.
|
|
791
|
+
pub fn committed_theme(&self) -> Option<String> {
|
|
792
|
+
self.state().theme.clone()
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
fn selected_idx(&self) -> Option<usize> {
|
|
796
|
+
self.state().plugin.as_ref().map(|plugin| plugin.idx)
|
|
797
|
+
}
|
|
798
|
+
|
|
659
799
|
pub fn render_timer(&self) -> MovingWindowRenderTimer {
|
|
660
800
|
self.0.borrow().timer.clone()
|
|
661
801
|
}
|
|
@@ -710,6 +850,7 @@ impl Renderer {
|
|
|
710
850
|
is_chart,
|
|
711
851
|
plugin_config,
|
|
712
852
|
columns_config,
|
|
853
|
+
render_error: self.failure(),
|
|
713
854
|
}
|
|
714
855
|
} else {
|
|
715
856
|
RendererProps {
|
|
@@ -720,6 +861,7 @@ impl Renderer {
|
|
|
720
861
|
is_chart: false,
|
|
721
862
|
plugin_config: PtrEqRc::default(),
|
|
722
863
|
columns_config: PtrEqRc::default(),
|
|
864
|
+
render_error: self.failure(),
|
|
723
865
|
}
|
|
724
866
|
}
|
|
725
867
|
}
|