@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
|
@@ -17,7 +17,7 @@ use wasm_bindgen::prelude::*;
|
|
|
17
17
|
use crate::components::viewer::PerspectiveViewerMsg;
|
|
18
18
|
use crate::config::*;
|
|
19
19
|
use crate::renderer::*;
|
|
20
|
-
use crate::session::Session;
|
|
20
|
+
use crate::session::{Disposal, Session};
|
|
21
21
|
use crate::tasks::*;
|
|
22
22
|
use crate::workspace::PanelId;
|
|
23
23
|
use crate::*;
|
|
@@ -34,6 +34,7 @@ extern "C" {
|
|
|
34
34
|
pub type SyncUpdatePanelsResult = (
|
|
35
35
|
Vec<(PanelId, Session, Renderer, ViewerConfigUpdate)>,
|
|
36
36
|
std::vec::Vec<perspective_js::utils::ApiFuture<()>>,
|
|
37
|
+
Option<std::collections::BTreeMap<String, crate::config::DerivedTableConfig>>,
|
|
37
38
|
);
|
|
38
39
|
|
|
39
40
|
/// Synchronously apply a `WorkspaceConfigUpdate` to the element's models
|
|
@@ -47,6 +48,7 @@ pub fn sync_update_panels(
|
|
|
47
48
|
active,
|
|
48
49
|
layout,
|
|
49
50
|
panels,
|
|
51
|
+
derived_tables,
|
|
50
52
|
global_filters,
|
|
51
53
|
masters,
|
|
52
54
|
palette,
|
|
@@ -75,12 +77,28 @@ pub fn sync_update_panels(
|
|
|
75
77
|
// zero-panel empty stage — the former table-less fallback panel was
|
|
76
78
|
// exactly the blank-panel state this type exists to preclude.
|
|
77
79
|
Some(panels) => {
|
|
80
|
+
// Eject the outgoing set FIRST so each saved id is free to be
|
|
81
|
+
// reused — panels keep their SAVED ids (`saveWorkspace` writes
|
|
82
|
+
// them back; `active`/`masters` and user configs address panels
|
|
83
|
+
// by these names across the round-trip).
|
|
84
|
+
if let Some(panel) = this.workspace.take_reserved() {
|
|
85
|
+
panel.renderer.retire_slot();
|
|
86
|
+
eject_tasks.push(eject_panel(panel, Disposal::Reject));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for old in &old_ids {
|
|
90
|
+
if let Some(panel) = this.workspace.remove_panel(old) {
|
|
91
|
+
panel.renderer.retire_slot();
|
|
92
|
+
eject_tasks.push(eject_panel(panel, Disposal::Reject));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
for (saved_id, config) in panels {
|
|
79
97
|
let (fresh, session, renderer, config) = create_panel_model(
|
|
80
98
|
&this.elem,
|
|
81
99
|
&this.presentation,
|
|
82
100
|
&this.workspace,
|
|
83
|
-
|
|
101
|
+
Some(PanelId::from(saved_id.as_str())),
|
|
84
102
|
config.into(),
|
|
85
103
|
None,
|
|
86
104
|
Placement::Placed,
|
|
@@ -89,16 +107,6 @@ pub fn sync_update_panels(
|
|
|
89
107
|
id_map.insert(saved_id, fresh.as_str().to_owned());
|
|
90
108
|
contents.push((fresh, session, renderer, config));
|
|
91
109
|
}
|
|
92
|
-
|
|
93
|
-
if let Some(panel) = this.workspace.take_reserved() {
|
|
94
|
-
eject_tasks.push(eject_panel(panel));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
for old in &old_ids {
|
|
98
|
-
if let Some(panel) = this.workspace.remove_panel(old) {
|
|
99
|
-
eject_tasks.push(eject_panel(panel));
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
110
|
},
|
|
103
111
|
None => {
|
|
104
112
|
for id in &old_ids {
|
|
@@ -119,6 +127,25 @@ pub fn sync_update_panels(
|
|
|
119
127
|
if !replace {
|
|
120
128
|
this.workspace.layout_staged().emit(());
|
|
121
129
|
}
|
|
130
|
+
} else if replace && contents.len() > 1 {
|
|
131
|
+
// No saved tree: synthesize the equal-split default so a
|
|
132
|
+
// multi-panel replace still mounts in ONE layout commit —
|
|
133
|
+
// per-cell `insertPanel` commits are async, and a commit landing
|
|
134
|
+
// between two inserts drops the later one.
|
|
135
|
+
this.workspace
|
|
136
|
+
.set_pending_layout(crate::js::Layout::Split(crate::js::SplitLayout {
|
|
137
|
+
orientation: crate::js::Orientation::Horizontal,
|
|
138
|
+
sizes: vec![1.0 / contents.len() as f64; contents.len()],
|
|
139
|
+
children: contents
|
|
140
|
+
.iter()
|
|
141
|
+
.map(|(id, ..)| {
|
|
142
|
+
crate::js::Layout::Tab(crate::js::TabLayout {
|
|
143
|
+
tabs: vec![id.as_str().to_owned()],
|
|
144
|
+
selected: Some(0),
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
.collect(),
|
|
148
|
+
}));
|
|
122
149
|
}
|
|
123
150
|
|
|
124
151
|
// Phase 3 — the single visible commit
|
|
@@ -222,5 +249,5 @@ pub fn sync_update_panels(
|
|
|
222
249
|
OptionalUpdate::Update(filters) => this.workspace.set_global_filters(filters),
|
|
223
250
|
}
|
|
224
251
|
|
|
225
|
-
Ok((contents, eject_tasks))
|
|
252
|
+
Ok((contents, eject_tasks, derived_tables))
|
|
226
253
|
}
|
|
@@ -19,8 +19,8 @@ use perspective_js::utils::{ApiFuture, ApiResult, LocalPollLoop};
|
|
|
19
19
|
use wasm_bindgen::JsValue;
|
|
20
20
|
|
|
21
21
|
use super::pipeline::RunOrigin;
|
|
22
|
-
use super::
|
|
23
|
-
use crate::config::ViewerConfigUpdate;
|
|
22
|
+
use super::restore_panel::{Rebind, RestoreErrors, RestoreMode, restore_panel_step};
|
|
23
|
+
use crate::config::{OptionalUpdate, ViewerConfigUpdate};
|
|
24
24
|
use crate::presentation::Presentation;
|
|
25
25
|
use crate::utils::{AddListener, Subscription};
|
|
26
26
|
use crate::workspace::{Panel, Workspace};
|
|
@@ -86,6 +86,10 @@ pub(crate) async fn sweep_table_bindings(
|
|
|
86
86
|
workspace: &Workspace,
|
|
87
87
|
presentation: &Presentation,
|
|
88
88
|
) -> ApiResult<()> {
|
|
89
|
+
if let Err(err) = super::resume_derived(workspace).await {
|
|
90
|
+
tracing::warn!("Failed to resume derived tables: {err}");
|
|
91
|
+
}
|
|
92
|
+
|
|
89
93
|
for panel in workspace.panels() {
|
|
90
94
|
if let Some(name) = panel.session.get_table().map(|t| t.get_name().to_owned())
|
|
91
95
|
&& let Some(client) = panel.session.get_client()
|
|
@@ -104,6 +108,7 @@ pub(crate) async fn sweep_table_bindings(
|
|
|
104
108
|
}
|
|
105
109
|
}
|
|
106
110
|
|
|
111
|
+
workspace.hosted_tables_changed().emit(());
|
|
107
112
|
Ok(())
|
|
108
113
|
}
|
|
109
114
|
|
|
@@ -113,52 +118,83 @@ pub(crate) async fn sweep_table_bindings(
|
|
|
113
118
|
/// sweep's observation suspends its CURRENT state or no-ops.
|
|
114
119
|
async fn suspend_panel(panel: &Panel) -> ApiResult<()> {
|
|
115
120
|
clone!(panel.session, panel.renderer);
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
let ticket = panel.session.submit(
|
|
122
|
+
crate::session::OpKind::Restore { update: None },
|
|
123
|
+
move |_ctx| {
|
|
124
|
+
Box::pin(async move {
|
|
125
|
+
renderer
|
|
126
|
+
.clone()
|
|
127
|
+
.render_task(|_guard| async move {
|
|
128
|
+
if let Some(reset) = session.suspend_table() {
|
|
129
|
+
reset.await?;
|
|
130
|
+
if let Some(plugin) = renderer.active_plugin() {
|
|
131
|
+
plugin.clear().await?;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
Ok(())
|
|
136
|
+
})
|
|
137
|
+
.await?;
|
|
138
|
+
|
|
139
|
+
Ok(None)
|
|
140
|
+
})
|
|
141
|
+
},
|
|
142
|
+
);
|
|
125
143
|
|
|
126
|
-
|
|
127
|
-
})
|
|
128
|
-
.await
|
|
144
|
+
ticket.settle().await
|
|
129
145
|
}
|
|
130
146
|
|
|
131
|
-
/// Complete a PENDING panel's bind
|
|
132
|
-
///
|
|
133
|
-
///
|
|
134
|
-
///
|
|
147
|
+
/// Complete a PENDING panel's bind, as the restore it is: the pending name
|
|
148
|
+
/// re-derived inside the op (capture-free), the client federated exactly as a
|
|
149
|
+
/// `restore` would, and the config and buckets committed as INTENT while the
|
|
150
|
+
/// table was away validated against the table that arrived.
|
|
135
151
|
async fn bind_pending(
|
|
136
152
|
panel: &Panel,
|
|
137
153
|
workspace: &Workspace,
|
|
138
154
|
presentation: &Presentation,
|
|
139
155
|
) -> ApiResult<()> {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
ViewerConfigUpdate::default(),
|
|
146
|
-
{
|
|
147
|
-
clone!(panel.session, workspace);
|
|
148
|
-
async move {
|
|
156
|
+
clone!(panel.session, panel.renderer, workspace, presentation);
|
|
157
|
+
let ticket = panel.session.submit(
|
|
158
|
+
crate::session::OpKind::Restore { update: None },
|
|
159
|
+
move |ctx| {
|
|
160
|
+
Box::pin(async move {
|
|
149
161
|
let Some(name) = session.pending_table() else {
|
|
150
|
-
return Ok(
|
|
162
|
+
return Ok(None);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
let update = ViewerConfigUpdate {
|
|
166
|
+
table: OptionalUpdate::Update(name.into()),
|
|
167
|
+
plugin_config: OptionalUpdate::Update(renderer.committed_plugin_config()),
|
|
168
|
+
columns_config: OptionalUpdate::Update(renderer.committed_columns_configs()),
|
|
169
|
+
..ViewerConfigUpdate::default()
|
|
151
170
|
};
|
|
152
171
|
|
|
153
|
-
|
|
172
|
+
let result = restore_panel_step(
|
|
173
|
+
&ctx,
|
|
154
174
|
&session,
|
|
175
|
+
&renderer,
|
|
176
|
+
&presentation,
|
|
155
177
|
&workspace,
|
|
156
|
-
|
|
178
|
+
RestoreMode::Existing { active: false },
|
|
179
|
+
Rebind::Complete,
|
|
180
|
+
RunOrigin::Internal,
|
|
181
|
+
update,
|
|
182
|
+
RestoreErrors::Publish,
|
|
157
183
|
crate::session::MissingTable::Pend,
|
|
158
184
|
)
|
|
159
|
-
.await
|
|
160
|
-
|
|
185
|
+
.await;
|
|
186
|
+
|
|
187
|
+
if let Err(e) = &result
|
|
188
|
+
&& session.pending_table().is_some()
|
|
189
|
+
{
|
|
190
|
+
let _ = renderer.fail(e.clone());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
result?;
|
|
194
|
+
Ok(None)
|
|
195
|
+
})
|
|
161
196
|
},
|
|
162
|
-
)
|
|
163
|
-
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
ticket.settle().await
|
|
164
200
|
}
|
|
@@ -0,0 +1,387 @@
|
|
|
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
|
+
//! A restore as a TRANSACTION: [`prepare`] makes every check that can fail —
|
|
14
|
+
//! and waits on every round trip — without writing anything; [`commit`] then
|
|
15
|
+
//! replaces the panel's state in ONE swap and cannot fail.
|
|
16
|
+
|
|
17
|
+
use std::rc::Rc;
|
|
18
|
+
|
|
19
|
+
use perspective_client::config::ViewConfigUpdate;
|
|
20
|
+
use perspective_js::utils::*;
|
|
21
|
+
|
|
22
|
+
use super::pipeline::{Committed, RunCommit, RunOrigin, RunSpec, locked_run, run_locked};
|
|
23
|
+
use crate::config::{OptionalUpdate, SettingsUpdate, ThemeUpdate, ViewerConfigUpdate};
|
|
24
|
+
use crate::presentation::Presentation;
|
|
25
|
+
use crate::renderer::{
|
|
26
|
+
PluginRef, PreparedRenderer, Renderer, ValidatedColumnsConfig, ValidatedPluginConfig,
|
|
27
|
+
};
|
|
28
|
+
use crate::session::{
|
|
29
|
+
BindPlan, BindingEffects, OverlayClause, PreparedView, Session, ViewDefaults,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/// A restore that has passed every check and only awaits [`commit`].
|
|
33
|
+
pub(crate) struct Prepared {
|
|
34
|
+
state: PreparedState,
|
|
35
|
+
effects: Effects,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// The panel state a [`Prepared`] restore commits.
|
|
39
|
+
struct PreparedState {
|
|
40
|
+
view: PreparedView,
|
|
41
|
+
|
|
42
|
+
/// The plugin selection to commit, when the restore ends on a plugin other
|
|
43
|
+
/// than the one selected (or selects the first).
|
|
44
|
+
plugin: Option<PluginRef>,
|
|
45
|
+
|
|
46
|
+
/// The name of the plugin the restore ends on — whose bucket the validated
|
|
47
|
+
/// bucket updates belong to.
|
|
48
|
+
target_name: String,
|
|
49
|
+
plugin_config: ValidatedPluginConfig,
|
|
50
|
+
columns_config: ValidatedColumnsConfig,
|
|
51
|
+
theme: Option<Option<String>>,
|
|
52
|
+
title: Option<Option<String>>,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// What a restore does to the ELEMENT rather than the panel — applied once the
|
|
56
|
+
/// restore can no longer be rejected, as part of rendering it.
|
|
57
|
+
pub(crate) struct Effects {
|
|
58
|
+
settings: SettingsUpdate,
|
|
59
|
+
host_theme: ThemeUpdate,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Every fallible and asynchronous part of a restore.
|
|
63
|
+
pub(crate) async fn prepare(
|
|
64
|
+
session: &Session,
|
|
65
|
+
renderer: &Renderer,
|
|
66
|
+
presentation: &Presentation,
|
|
67
|
+
active: bool,
|
|
68
|
+
plan: BindPlan,
|
|
69
|
+
overlay: Option<Rc<Vec<OverlayClause>>>,
|
|
70
|
+
update: ViewerConfigUpdate,
|
|
71
|
+
) -> ApiResult<Prepared> {
|
|
72
|
+
let ViewerConfigUpdate {
|
|
73
|
+
plugin,
|
|
74
|
+
plugin_config,
|
|
75
|
+
columns_config,
|
|
76
|
+
settings,
|
|
77
|
+
theme,
|
|
78
|
+
title,
|
|
79
|
+
view_config,
|
|
80
|
+
..
|
|
81
|
+
} = update;
|
|
82
|
+
|
|
83
|
+
renderer.check_plugin_update(&plugin)?;
|
|
84
|
+
let named = renderer.resolve_plugin_update(&plugin);
|
|
85
|
+
let (target, swap_idx) = renderer.resolve_target(&plugin)?;
|
|
86
|
+
let rebinds = !matches!(plan, BindPlan::Keep);
|
|
87
|
+
let defaults = match &named {
|
|
88
|
+
Some((_, metadata)) => ViewDefaults::Swap(metadata),
|
|
89
|
+
None if rebinds && matches!(plugin, OptionalUpdate::Update(_)) => {
|
|
90
|
+
ViewDefaults::Swap(&target.static_config)
|
|
91
|
+
},
|
|
92
|
+
None => ViewDefaults::Rollup(&target.static_config),
|
|
93
|
+
};
|
|
94
|
+
let view = session
|
|
95
|
+
.prepare_view(plan, view_config, defaults, overlay)
|
|
96
|
+
.await?;
|
|
97
|
+
let (plugin_config, columns_config) = renderer.prepare_buckets(
|
|
98
|
+
&target,
|
|
99
|
+
session,
|
|
100
|
+
view.config(),
|
|
101
|
+
view.view_schema(),
|
|
102
|
+
rebinds,
|
|
103
|
+
plugin_config,
|
|
104
|
+
columns_config,
|
|
105
|
+
)?;
|
|
106
|
+
|
|
107
|
+
let panel_theme = match &theme {
|
|
108
|
+
OptionalUpdate::Update(theme) => Some(Some(theme.clone())),
|
|
109
|
+
OptionalUpdate::SetDefault => Some(presentation.get_default_theme_name().await),
|
|
110
|
+
OptionalUpdate::Missing if renderer.committed_theme().is_none() => {
|
|
111
|
+
Some(presentation.get_default_theme_name().await).filter(|x| x.is_some())
|
|
112
|
+
},
|
|
113
|
+
OptionalUpdate::Missing => None,
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let title = match title {
|
|
117
|
+
OptionalUpdate::Update(title) => Some(Some(title).filter(|x| !x.is_empty())),
|
|
118
|
+
OptionalUpdate::SetDefault => Some(None),
|
|
119
|
+
OptionalUpdate::Missing => None,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
Ok(Prepared {
|
|
123
|
+
state: PreparedState {
|
|
124
|
+
view,
|
|
125
|
+
plugin: swap_idx.map(|idx| renderer.plugin_ref(idx)).transpose()?,
|
|
126
|
+
target_name: target.static_config.name.clone(),
|
|
127
|
+
plugin_config,
|
|
128
|
+
columns_config,
|
|
129
|
+
theme: panel_theme,
|
|
130
|
+
title,
|
|
131
|
+
},
|
|
132
|
+
effects: Effects {
|
|
133
|
+
settings,
|
|
134
|
+
host_theme: if active {
|
|
135
|
+
theme
|
|
136
|
+
} else {
|
|
137
|
+
OptionalUpdate::Missing
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// A UI edit as the transaction it is: the view-config `update` — and the
|
|
144
|
+
/// plugin swap `plugin_idx`, when the control made one — validated against what
|
|
145
|
+
/// the ops AHEAD of it left committed, including the server's `describe`.
|
|
146
|
+
pub(crate) async fn prepare_edit(
|
|
147
|
+
session: &Session,
|
|
148
|
+
renderer: &Renderer,
|
|
149
|
+
update: ViewConfigUpdate,
|
|
150
|
+
plugin_idx: Option<usize>,
|
|
151
|
+
) -> ApiResult<Prepared> {
|
|
152
|
+
let selected = renderer.committed_plugin_idx();
|
|
153
|
+
let (target, swap_idx) = match plugin_idx.filter(|idx| Some(*idx) != selected) {
|
|
154
|
+
Some(idx) => (renderer.target_at(idx)?, Some(idx)),
|
|
155
|
+
None => renderer.resolve_target(&OptionalUpdate::Missing)?,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
let view = session
|
|
159
|
+
.prepare_view(BindPlan::Keep, update, ViewDefaults::AsGiven, None)
|
|
160
|
+
.await?
|
|
161
|
+
.projected();
|
|
162
|
+
|
|
163
|
+
edit_of(renderer, view, &target.static_config.name, swap_idx)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/// A new global-filter overlay as the transaction it is: the committed config
|
|
167
|
+
/// re-described with the clauses this panel's table honors.
|
|
168
|
+
pub(crate) async fn prepare_overlay(
|
|
169
|
+
session: &Session,
|
|
170
|
+
renderer: &Renderer,
|
|
171
|
+
overlay: Rc<Vec<OverlayClause>>,
|
|
172
|
+
) -> ApiResult<Prepared> {
|
|
173
|
+
let (target, swap_idx) = renderer.resolve_target(&OptionalUpdate::Missing)?;
|
|
174
|
+
let view = session
|
|
175
|
+
.prepare_view(
|
|
176
|
+
BindPlan::Keep,
|
|
177
|
+
ViewConfigUpdate::default(),
|
|
178
|
+
ViewDefaults::AsGiven,
|
|
179
|
+
Some(overlay),
|
|
180
|
+
)
|
|
181
|
+
.await?;
|
|
182
|
+
|
|
183
|
+
edit_of(renderer, view, &target.static_config.name, swap_idx)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
fn edit_of(
|
|
187
|
+
renderer: &Renderer,
|
|
188
|
+
view: PreparedView,
|
|
189
|
+
target_name: &str,
|
|
190
|
+
swap_idx: Option<usize>,
|
|
191
|
+
) -> ApiResult<Prepared> {
|
|
192
|
+
Ok(Prepared {
|
|
193
|
+
state: PreparedState {
|
|
194
|
+
view,
|
|
195
|
+
plugin: swap_idx.map(|idx| renderer.plugin_ref(idx)).transpose()?,
|
|
196
|
+
target_name: target_name.to_owned(),
|
|
197
|
+
plugin_config: ValidatedPluginConfig::Missing,
|
|
198
|
+
columns_config: ValidatedColumnsConfig::Missing,
|
|
199
|
+
theme: None,
|
|
200
|
+
title: None,
|
|
201
|
+
},
|
|
202
|
+
effects: Effects {
|
|
203
|
+
settings: OptionalUpdate::Missing,
|
|
204
|
+
host_theme: OptionalUpdate::Missing,
|
|
205
|
+
},
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/// Commit a [`prepare_edit`] NOW, in the op's step — so the write is the
|
|
210
|
+
/// drain's, and the render that follows may be detached.
|
|
211
|
+
pub(crate) fn commit_edit(session: &Session, renderer: &Renderer, prepared: Prepared) -> Committed {
|
|
212
|
+
let (committed, binding) = commit(session, renderer, prepared.state);
|
|
213
|
+
debug_assert!(!binding.rebound());
|
|
214
|
+
binding.forget();
|
|
215
|
+
committed
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// Commit and render a [`Prepared`] restore under a draw lock the caller
|
|
219
|
+
/// ALREADY holds (`load()`, which holds it across its payload's arrival).
|
|
220
|
+
pub(crate) async fn commit_and_render_locked(
|
|
221
|
+
guard: crate::utils::RenderGuard,
|
|
222
|
+
session: &Session,
|
|
223
|
+
renderer: &Renderer,
|
|
224
|
+
Prepared { state, .. }: Prepared,
|
|
225
|
+
) -> ApiResult<()> {
|
|
226
|
+
let commit = {
|
|
227
|
+
let (session, renderer) = (session.clone(), renderer.clone());
|
|
228
|
+
Box::new(move || commit(&session, &renderer, state))
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
run_locked(guard, session.clone(), renderer.clone(), RunSpec {
|
|
232
|
+
origin: RunOrigin::Public,
|
|
233
|
+
commit: RunCommit::Deferred(commit),
|
|
234
|
+
presentation: None,
|
|
235
|
+
})
|
|
236
|
+
.await
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/// Commit a [`Prepared`] restore to both engines, with no `await` between.
|
|
240
|
+
fn commit(
|
|
241
|
+
session: &Session,
|
|
242
|
+
renderer: &Renderer,
|
|
243
|
+
prepared: PreparedState,
|
|
244
|
+
) -> (Committed, BindingEffects) {
|
|
245
|
+
let PreparedState {
|
|
246
|
+
view,
|
|
247
|
+
plugin,
|
|
248
|
+
target_name,
|
|
249
|
+
plugin_config,
|
|
250
|
+
columns_config,
|
|
251
|
+
theme,
|
|
252
|
+
title,
|
|
253
|
+
} = prepared;
|
|
254
|
+
|
|
255
|
+
let had_plugin = renderer.active_plugin().is_some();
|
|
256
|
+
let activate = plugin.is_some();
|
|
257
|
+
let binding = session.commit_view(view, title);
|
|
258
|
+
let changed = renderer.commit(PreparedRenderer {
|
|
259
|
+
plugin,
|
|
260
|
+
target_name,
|
|
261
|
+
plugin_config,
|
|
262
|
+
columns_config,
|
|
263
|
+
theme,
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
let committed = Committed {
|
|
267
|
+
activate,
|
|
268
|
+
plugin_swapped: activate && had_plugin,
|
|
269
|
+
plugin_config_changed: changed.plugin_config,
|
|
270
|
+
columns_config_changed: changed.columns_config,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
(committed, binding)
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/// A restore that leaves the table binding alone, for the host's own re-renders
|
|
277
|
+
/// (a reset, an auto-pause resume): to be called only from a running op's step.
|
|
278
|
+
pub(crate) async fn restore_in_place(
|
|
279
|
+
session: &Session,
|
|
280
|
+
renderer: &Renderer,
|
|
281
|
+
presentation: &Presentation,
|
|
282
|
+
origin: RunOrigin,
|
|
283
|
+
update: ViewerConfigUpdate,
|
|
284
|
+
) -> ApiResult<()> {
|
|
285
|
+
let prepared = prepare(
|
|
286
|
+
session,
|
|
287
|
+
renderer,
|
|
288
|
+
presentation,
|
|
289
|
+
false,
|
|
290
|
+
BindPlan::Keep,
|
|
291
|
+
None,
|
|
292
|
+
update,
|
|
293
|
+
)
|
|
294
|
+
.await?;
|
|
295
|
+
commit_and_render(session, renderer, presentation, origin, prepared)
|
|
296
|
+
.await
|
|
297
|
+
.result
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Whether a restore that failed had ALREADY committed — what tells a rejection
|
|
301
|
+
/// (the panel is as it was) from a failure to render valid state.
|
|
302
|
+
pub(crate) struct Outcome {
|
|
303
|
+
pub committed: bool,
|
|
304
|
+
pub result: ApiResult<()>,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/// Commit a [`Prepared`] restore and render it: element-level effects, then the
|
|
308
|
+
/// locked run, whose first act is the commit — under the draw lock, so no
|
|
309
|
+
/// render of the outgoing state can bind a `View` over the incoming table.
|
|
310
|
+
pub(crate) async fn commit_and_render(
|
|
311
|
+
session: &Session,
|
|
312
|
+
renderer: &Renderer,
|
|
313
|
+
presentation: &Presentation,
|
|
314
|
+
origin: RunOrigin,
|
|
315
|
+
Prepared { state, effects }: Prepared,
|
|
316
|
+
) -> Outcome {
|
|
317
|
+
let Effects {
|
|
318
|
+
settings,
|
|
319
|
+
host_theme,
|
|
320
|
+
} = effects;
|
|
321
|
+
|
|
322
|
+
let did_commit = std::rc::Rc::new(std::cell::Cell::new(false));
|
|
323
|
+
let result = render(
|
|
324
|
+
session,
|
|
325
|
+
renderer,
|
|
326
|
+
presentation,
|
|
327
|
+
origin,
|
|
328
|
+
settings,
|
|
329
|
+
host_theme,
|
|
330
|
+
{
|
|
331
|
+
let (session, renderer, did_commit) =
|
|
332
|
+
(session.clone(), renderer.clone(), did_commit.clone());
|
|
333
|
+
Box::new(move || {
|
|
334
|
+
did_commit.set(true);
|
|
335
|
+
commit(&session, &renderer, state)
|
|
336
|
+
})
|
|
337
|
+
},
|
|
338
|
+
)
|
|
339
|
+
.await;
|
|
340
|
+
|
|
341
|
+
Outcome {
|
|
342
|
+
committed: did_commit.get(),
|
|
343
|
+
result,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#[allow(clippy::too_many_arguments)]
|
|
348
|
+
async fn render(
|
|
349
|
+
session: &Session,
|
|
350
|
+
renderer: &Renderer,
|
|
351
|
+
presentation: &Presentation,
|
|
352
|
+
origin: RunOrigin,
|
|
353
|
+
settings: SettingsUpdate,
|
|
354
|
+
host_theme: ThemeUpdate,
|
|
355
|
+
commit: Box<dyn FnOnce() -> (Committed, BindingEffects)>,
|
|
356
|
+
) -> ApiResult<()> {
|
|
357
|
+
if let OptionalUpdate::Update(x) = settings {
|
|
358
|
+
presentation.set_settings_attribute(x);
|
|
359
|
+
presentation.set_settings_before_open(x);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
match host_theme {
|
|
363
|
+
OptionalUpdate::SetDefault => {
|
|
364
|
+
if presentation.get_selected_theme_name().await.is_some() {
|
|
365
|
+
presentation.set_theme_name(None).await?;
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
OptionalUpdate::Update(x) => {
|
|
369
|
+
presentation.set_theme_name(Some(&x)).await?;
|
|
370
|
+
},
|
|
371
|
+
OptionalUpdate::Missing => {},
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
locked_run(session, renderer, RunSpec {
|
|
375
|
+
origin,
|
|
376
|
+
commit: RunCommit::Deferred(commit),
|
|
377
|
+
presentation: Some(presentation.clone()),
|
|
378
|
+
})
|
|
379
|
+
.await?;
|
|
380
|
+
|
|
381
|
+
if renderer.needs_restyle() {
|
|
382
|
+
renderer.restyle_all().await?;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
presentation.publish_theme_config().await?;
|
|
386
|
+
Ok(())
|
|
387
|
+
}
|