@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
|
@@ -12,17 +12,17 @@
|
|
|
12
12
|
|
|
13
13
|
use std::rc::Rc;
|
|
14
14
|
|
|
15
|
-
use futures::
|
|
15
|
+
use futures::FutureExt;
|
|
16
16
|
use perspective_client::config::ViewConfigUpdate;
|
|
17
17
|
use perspective_client::{View, clone};
|
|
18
18
|
use perspective_js::utils::*;
|
|
19
19
|
use wasm_bindgen::JsValue;
|
|
20
20
|
use yew::prelude::*;
|
|
21
21
|
|
|
22
|
-
use
|
|
22
|
+
use super::transactional_restore::{commit_edit, prepare_edit};
|
|
23
23
|
use crate::presentation::Presentation;
|
|
24
24
|
use crate::renderer::{RenderContext, Renderer};
|
|
25
|
-
use crate::session::{BindDisposition, Session};
|
|
25
|
+
use crate::session::{BindDisposition, BindingEffects, Disposal, EditDelta, OpKind, Session};
|
|
26
26
|
use crate::utils::RenderGuard;
|
|
27
27
|
|
|
28
28
|
/// Snapshot → validate → bind → cache + pin the [`RenderContext`]. The core
|
|
@@ -35,15 +35,6 @@ pub async fn bind_snapshot(
|
|
|
35
35
|
session: &Session,
|
|
36
36
|
renderer: &Renderer,
|
|
37
37
|
) -> ApiResult<(BindDisposition, Option<crate::renderer::ContextPin>)> {
|
|
38
|
-
// A `load()` is mid-classification (see [`Session::pending_load`]): DEFER
|
|
39
|
-
// rather than bind the incoming config against the still-bound outgoing
|
|
40
|
-
// table. Holds the last painted frame; the `load()` run performs the one
|
|
41
|
-
// reconciling bind once its payload resolves — the single visible
|
|
42
|
-
// transition, correct in whichever future (Table/Client) materializes.
|
|
43
|
-
if session.has_pending_load() {
|
|
44
|
-
return Ok((BindDisposition::Deferred, None));
|
|
45
|
-
}
|
|
46
|
-
|
|
47
38
|
let snap = session.snapshot(guard);
|
|
48
39
|
let validated = session.validate_snapshot(guard, snap).await?;
|
|
49
40
|
let disposition = session.bind_view(guard, validated).await?;
|
|
@@ -132,34 +123,73 @@ pub fn apply_and_render(
|
|
|
132
123
|
}
|
|
133
124
|
|
|
134
125
|
/// [`apply_and_render`] plus a plugin selection (resolved via
|
|
135
|
-
/// [`Renderer::resolve_plugin_update`])
|
|
136
|
-
///
|
|
137
|
-
/// `Renderer` where a concurrent draw could observe it.
|
|
126
|
+
/// [`Renderer::resolve_plugin_update`]), committed in the SAME swap as the
|
|
127
|
+
/// view config — in the op's step, never by the detached render.
|
|
138
128
|
pub fn update_plugin_and_render(
|
|
139
129
|
session: &Session,
|
|
140
130
|
renderer: &Renderer,
|
|
141
131
|
update: ViewConfigUpdate,
|
|
142
132
|
plugin_idx: Option<usize>,
|
|
143
133
|
) -> ApiResult<ApiFuture<()>> {
|
|
144
|
-
session.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
134
|
+
session.check_edit(&update)?;
|
|
135
|
+
let kind = OpKind::Edit {
|
|
136
|
+
delta: EditDelta::View(Box::new(update.clone())),
|
|
137
|
+
swaps_plugin: plugin_idx.is_some(),
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
let ticket = session.submit(kind, {
|
|
141
|
+
clone!(session, renderer);
|
|
142
|
+
move |_ctx| {
|
|
143
|
+
Box::pin(async move {
|
|
144
|
+
let prepared = prepare_edit(&session, &renderer, update, plugin_idx).await?;
|
|
145
|
+
let committed = commit_edit(&session, &renderer, prepared);
|
|
146
|
+
Ok(Some(
|
|
147
|
+
render_run(session, renderer, RunCommit::Done(committed)).boxed_local(),
|
|
148
|
+
))
|
|
149
|
+
})
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
Ok(ApiFuture::new(ticket.settle()))
|
|
153
154
|
}
|
|
154
155
|
|
|
155
156
|
/// Re-render from the current commit without applying an update.
|
|
156
157
|
pub fn just_render(session: &Session, renderer: &Renderer) -> ApiResult<ApiFuture<()>> {
|
|
157
158
|
clone!(session, renderer);
|
|
158
159
|
Ok(ApiFuture::new(async move {
|
|
159
|
-
render_run(session, renderer, None).await
|
|
160
|
+
render_run(session, renderer, RunCommit::None).await
|
|
160
161
|
}))
|
|
161
162
|
}
|
|
162
163
|
|
|
164
|
+
/// What a transactional restore committed (see
|
|
165
|
+
/// [`super::transactional_restore`]): the run has to bring the plugin element
|
|
166
|
+
/// and the paint in line with it.
|
|
167
|
+
pub(crate) struct Committed {
|
|
168
|
+
/// A plugin selection was committed; its element must be activated.
|
|
169
|
+
pub activate: bool,
|
|
170
|
+
|
|
171
|
+
/// ...and it REPLACED an earlier selection (a first selection is not a
|
|
172
|
+
/// swap, and does not force a `plugin.restore`).
|
|
173
|
+
pub plugin_swapped: bool,
|
|
174
|
+
pub plugin_config_changed: bool,
|
|
175
|
+
pub columns_config_changed: bool,
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/// The commit a locked run renders.
|
|
179
|
+
pub(crate) enum RunCommit {
|
|
180
|
+
/// Whatever is committed (a repaint, an activation, a resize).
|
|
181
|
+
None,
|
|
182
|
+
|
|
183
|
+
/// A UI edit its op's step ALREADY committed; the plugin receives the final
|
|
184
|
+
/// buckets in ONE `plugin.restore`.
|
|
185
|
+
Done(Committed),
|
|
186
|
+
|
|
187
|
+
/// A restore's commit, run first thing under the lock — a restore may
|
|
188
|
+
/// REBIND, and under the lock no render of the outgoing state can bind a
|
|
189
|
+
/// `View` over the incoming table.
|
|
190
|
+
Deferred(Box<dyn FnOnce() -> (Committed, BindingEffects)>),
|
|
191
|
+
}
|
|
192
|
+
|
|
163
193
|
/// Everything that varies between locked render runs, consumed by
|
|
164
194
|
/// [`locked_run`] — the ONE lock-body composition. `Default` is the
|
|
165
195
|
/// host-internal UI-commit run: `Internal` origin, no plugin swap, no
|
|
@@ -168,21 +198,8 @@ pub(crate) struct RunSpec {
|
|
|
168
198
|
/// Who initiated this run (see [`RunOrigin`]).
|
|
169
199
|
pub origin: RunOrigin,
|
|
170
200
|
|
|
171
|
-
///
|
|
172
|
-
|
|
173
|
-
pub plugin_idx: Option<usize>,
|
|
174
|
-
|
|
175
|
-
/// Plugin-level bucket update, applied inside the run after the bind
|
|
176
|
-
/// (so strip-on-write sees fresh schemas).
|
|
177
|
-
pub plugin_config: PluginConfigUpdate,
|
|
178
|
-
|
|
179
|
-
/// Per-column bucket update; same timing as `plugin_config`.
|
|
180
|
-
pub columns_config: ColumnConfigUpdate,
|
|
181
|
-
|
|
182
|
-
/// Pre-bind hook awaited inside the lock (table binding, resets) —
|
|
183
|
-
/// BEFORE the error guard, so a task that recovers the session (e.g.
|
|
184
|
-
/// `restorePanel`'s errored-recovery reset) unblocks its own run.
|
|
185
|
-
pub task: Option<LocalBoxFuture<'static, ApiResult<()>>>,
|
|
201
|
+
/// What this run renders — see [`RunCommit`].
|
|
202
|
+
pub commit: RunCommit,
|
|
186
203
|
|
|
187
204
|
/// When set, plugin dispatch is skipped while the host element is not
|
|
188
205
|
/// visible (`Presentation::is_visible`) — the `restore` family's gate
|
|
@@ -196,10 +213,7 @@ impl Default for RunSpec {
|
|
|
196
213
|
fn default() -> Self {
|
|
197
214
|
Self {
|
|
198
215
|
origin: RunOrigin::Internal,
|
|
199
|
-
|
|
200
|
-
plugin_config: PluginConfigUpdate::Missing,
|
|
201
|
-
columns_config: ColumnConfigUpdate::Missing,
|
|
202
|
-
task: None,
|
|
216
|
+
commit: RunCommit::None,
|
|
203
217
|
presentation: None,
|
|
204
218
|
}
|
|
205
219
|
}
|
|
@@ -208,7 +222,7 @@ impl Default for RunSpec {
|
|
|
208
222
|
/// One locked, witnessed, snapshot-consuming render run — the SINGLE lock
|
|
209
223
|
/// body shared by every config-driven run (`apply_and_render` &co. via
|
|
210
224
|
/// [`render_run`]'s tail, the `restore` family via
|
|
211
|
-
/// [`super::
|
|
225
|
+
/// [`super::transactional_restore`]): the restore's commit → eager mount →
|
|
212
226
|
/// plugin-swap commit → theme stamp → error guard → [`bind_snapshot`]
|
|
213
227
|
/// (gated on a bound `Table`, else `Deferred` — the config is already
|
|
214
228
|
/// committed, so the eventual `load()` run binds from it) → bucket updates
|
|
@@ -216,7 +230,7 @@ impl Default for RunSpec {
|
|
|
216
230
|
///
|
|
217
231
|
/// A pre-existing session error skips the run for `Internal` origins and
|
|
218
232
|
/// fails it for `Public` ones (the caller asked and must hear the failure —
|
|
219
|
-
/// I6). Returns the RAW run result; error-reporting policy (`
|
|
233
|
+
/// I6). Returns the RAW run result; error-reporting policy (`Renderer::fail`
|
|
220
234
|
/// vs. propagate) belongs to the caller.
|
|
221
235
|
pub(crate) async fn locked_run(
|
|
222
236
|
session: &Session,
|
|
@@ -226,16 +240,68 @@ pub(crate) async fn locked_run(
|
|
|
226
240
|
clone!(session, renderer);
|
|
227
241
|
renderer
|
|
228
242
|
.clone()
|
|
229
|
-
.render_task(|guard|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
243
|
+
.render_task(|guard| run_locked(guard, session, renderer, spec))
|
|
244
|
+
.await
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// [`locked_run`]'s body, for a caller that already holds the draw lock.
|
|
248
|
+
pub(crate) async fn run_locked(
|
|
249
|
+
guard: RenderGuard,
|
|
250
|
+
session: Session,
|
|
251
|
+
renderer: Renderer,
|
|
252
|
+
spec: RunSpec,
|
|
253
|
+
) -> ApiResult<()> {
|
|
254
|
+
{
|
|
255
|
+
{
|
|
256
|
+
if let Some(disposal) = session.disposal() {
|
|
257
|
+
return match (disposal, spec.origin) {
|
|
258
|
+
(Disposal::Reject, RunOrigin::Public) => Err(ApiError::new("Panel disposed")),
|
|
259
|
+
_ => Ok(()),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let committed = match spec.commit {
|
|
264
|
+
RunCommit::Done(committed) => Some(committed),
|
|
265
|
+
RunCommit::Deferred(commit) => {
|
|
266
|
+
let (committed, binding) = commit();
|
|
267
|
+
let rebound = binding.rebound();
|
|
268
|
+
session.finish_binding(binding).await?;
|
|
269
|
+
if rebound
|
|
270
|
+
&& session.get_table().is_none()
|
|
271
|
+
&& let Some(plugin) = renderer.active_plugin()
|
|
272
|
+
{
|
|
273
|
+
plugin.clear().await?;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
Some(committed)
|
|
277
|
+
},
|
|
278
|
+
RunCommit::None => None,
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
if !committed.as_ref().is_some_and(|c| c.activate) {
|
|
282
|
+
renderer.mount_active_plugin()?;
|
|
233
283
|
}
|
|
234
284
|
|
|
235
|
-
let plugin_swapped =
|
|
285
|
+
let plugin_swapped = match &committed {
|
|
286
|
+
Some(committed) => {
|
|
287
|
+
if committed.activate {
|
|
288
|
+
renderer.activate_committed_plugin()?;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
committed.plugin_swapped
|
|
292
|
+
},
|
|
293
|
+
None => {
|
|
294
|
+
renderer.ensure_plugin_selected()?;
|
|
295
|
+
false
|
|
296
|
+
},
|
|
297
|
+
};
|
|
236
298
|
let plugin = renderer.active_plugin().ok_or("No Plugin")?;
|
|
237
299
|
renderer.stamp_theme(Some(&plugin));
|
|
238
|
-
|
|
300
|
+
let blocking = session
|
|
301
|
+
.get_error()
|
|
302
|
+
.or_else(|| renderer.failure().map(|x| x.0));
|
|
303
|
+
|
|
304
|
+
if let Some(error) = blocking {
|
|
239
305
|
return match spec.origin {
|
|
240
306
|
RunOrigin::Public => Err(error),
|
|
241
307
|
RunOrigin::Internal => Ok(()),
|
|
@@ -248,39 +314,35 @@ pub(crate) async fn locked_run(
|
|
|
248
314
|
(BindDisposition::Deferred, None)
|
|
249
315
|
};
|
|
250
316
|
|
|
251
|
-
let view_config_snapshot = session.
|
|
252
|
-
let
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
JsValue::from_serde_ext(&plugin_config_snapshot).unwrap_or(JsValue::NULL);
|
|
257
|
-
|
|
258
|
-
let plugin_restored = plugin_config_changed || plugin_swapped;
|
|
259
|
-
if plugin_restored {
|
|
260
|
-
let columns_config = renderer
|
|
261
|
-
.all_columns_configs_materialized(&view_config_snapshot, &session)
|
|
262
|
-
.await;
|
|
263
|
-
plugin.restore(&plugin_update, Some(&columns_config))?;
|
|
264
|
-
}
|
|
317
|
+
let view_config_snapshot = session.committed_view_config().clone();
|
|
318
|
+
let changed = match committed {
|
|
319
|
+
Some(committed) => {
|
|
320
|
+
let changed =
|
|
321
|
+
committed.plugin_config_changed || committed.columns_config_changed;
|
|
265
322
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
)?;
|
|
271
|
-
|
|
272
|
-
let changed = plugin_config_changed || columns_config_changed;
|
|
273
|
-
if columns_config_changed {
|
|
274
|
-
let columns_config = renderer
|
|
275
|
-
.all_columns_configs_materialized(&view_config_snapshot, &session)
|
|
276
|
-
.await;
|
|
277
|
-
plugin.restore(&plugin_update, Some(&columns_config))?;
|
|
278
|
-
renderer.columns_config_changed.emit(columns_config);
|
|
279
|
-
}
|
|
323
|
+
if changed || plugin_swapped {
|
|
324
|
+
let plugin_config = renderer.committed_plugin_config();
|
|
325
|
+
let plugin_update =
|
|
326
|
+
JsValue::from_serde_ext(&plugin_config).unwrap_or(JsValue::NULL);
|
|
280
327
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
328
|
+
let columns_config = renderer
|
|
329
|
+
.all_columns_configs_materialized(&view_config_snapshot, &session)
|
|
330
|
+
.await;
|
|
331
|
+
|
|
332
|
+
plugin.restore(&plugin_update, Some(&columns_config))?;
|
|
333
|
+
if committed.columns_config_changed {
|
|
334
|
+
renderer.columns_config_changed.emit(columns_config);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if committed.plugin_config_changed {
|
|
338
|
+
renderer.plugin_config_changed.emit(plugin_config);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
changed
|
|
343
|
+
},
|
|
344
|
+
None => false,
|
|
345
|
+
};
|
|
284
346
|
|
|
285
347
|
if spec
|
|
286
348
|
.presentation
|
|
@@ -291,22 +353,23 @@ pub(crate) async fn locked_run(
|
|
|
291
353
|
dispatch_bound(&guard, &renderer, disposition, changed, spec.origin).await?;
|
|
292
354
|
}
|
|
293
355
|
|
|
356
|
+
renderer.landed();
|
|
294
357
|
Ok(())
|
|
295
|
-
}
|
|
296
|
-
|
|
358
|
+
}
|
|
359
|
+
}
|
|
297
360
|
}
|
|
298
361
|
|
|
299
362
|
/// [`locked_run`]'s host-internal tail: a failed RUN sets error state (with
|
|
300
363
|
/// the reset-reconnect affordance); the committed config is NOT rolled back
|
|
301
364
|
/// (I4). Cancellation by a superseding run ("View already deleted") is not
|
|
302
365
|
/// a failure.
|
|
303
|
-
async fn render_run(
|
|
366
|
+
pub(super) async fn render_run(
|
|
304
367
|
session: Session,
|
|
305
368
|
renderer: Renderer,
|
|
306
|
-
|
|
369
|
+
commit: RunCommit,
|
|
307
370
|
) -> ApiResult<()> {
|
|
308
371
|
let spec = RunSpec {
|
|
309
|
-
|
|
372
|
+
commit,
|
|
310
373
|
..RunSpec::default()
|
|
311
374
|
};
|
|
312
375
|
|
|
@@ -314,7 +377,7 @@ async fn render_run(
|
|
|
314
377
|
.await
|
|
315
378
|
.ignore_view_delete()
|
|
316
379
|
{
|
|
317
|
-
Err(e) =>
|
|
380
|
+
Err(e) => renderer.fail(e),
|
|
318
381
|
Ok(_) => Ok(()),
|
|
319
382
|
}
|
|
320
383
|
}
|
|
@@ -11,20 +11,20 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
//! Cross-engine reset orchestration: reset session config, optionally clear
|
|
14
|
-
//! presentation columns config / theme, then delegate to
|
|
15
|
-
//! to switch back to the default plugin and redraw.
|
|
14
|
+
//! presentation columns config / theme, then delegate to a transactional
|
|
15
|
+
//! restore to switch back to the default plugin and redraw.
|
|
16
16
|
|
|
17
17
|
use perspective_client::clone;
|
|
18
|
-
use perspective_js::utils::ApiFuture;
|
|
18
|
+
use perspective_js::utils::{ApiFuture, ApiResult};
|
|
19
19
|
|
|
20
20
|
use super::pipeline::RunOrigin;
|
|
21
|
-
use super::
|
|
21
|
+
use super::transactional_restore::restore_in_place;
|
|
22
22
|
use crate::config::{
|
|
23
23
|
ColumnConfigUpdate, OptionalUpdate, PluginConfigUpdate, PluginUpdate, ViewerConfigUpdate,
|
|
24
24
|
};
|
|
25
25
|
use crate::presentation::Presentation;
|
|
26
26
|
use crate::renderer::Renderer;
|
|
27
|
-
use crate::session::{ResetOptions, Session};
|
|
27
|
+
use crate::session::{OpKind, ResetOptions, Session};
|
|
28
28
|
|
|
29
29
|
/// Reset the viewer's `ViewerConfig` to the default.
|
|
30
30
|
///
|
|
@@ -37,7 +37,7 @@ use crate::session::{ResetOptions, Session};
|
|
|
37
37
|
/// futures like this one) and error, rather than this task spawning unowned
|
|
38
38
|
/// work.
|
|
39
39
|
///
|
|
40
|
-
/// Delegates plugin selection + draw to [`
|
|
40
|
+
/// Delegates plugin selection + draw to [`restore_in_place`], whose
|
|
41
41
|
/// two-pass restore guarantees the default plugin sees materialized
|
|
42
42
|
/// `columns_config` / `plugin_config` on its first draw — fixing a race
|
|
43
43
|
/// where the raw post-reset bucket would reach the plugin before
|
|
@@ -49,8 +49,27 @@ pub fn reset_all(
|
|
|
49
49
|
all: bool,
|
|
50
50
|
) -> ApiFuture<()> {
|
|
51
51
|
presentation.set_open_column_settings(None);
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
let ticket = session.submit(OpKind::Restore { update: None }, {
|
|
53
|
+
clone!(session, renderer, presentation);
|
|
54
|
+
move |_ctx| {
|
|
55
|
+
Box::pin(async move {
|
|
56
|
+
reset_all_step(&session, &renderer, &presentation, all).await?;
|
|
57
|
+
Ok(None)
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
ApiFuture::new(ticket.settle())
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// The body of [`reset_all`], run as one op on the panel's queue.
|
|
66
|
+
async fn reset_all_step(
|
|
67
|
+
session: &Session,
|
|
68
|
+
renderer: &Renderer,
|
|
69
|
+
presentation: &Presentation,
|
|
70
|
+
all: bool,
|
|
71
|
+
) -> ApiResult<()> {
|
|
72
|
+
{
|
|
54
73
|
session
|
|
55
74
|
.reset(ResetOptions {
|
|
56
75
|
config: true,
|
|
@@ -65,10 +84,10 @@ pub fn reset_all(
|
|
|
65
84
|
// `reset_theme` only resets the host, which an explicitly-themed
|
|
66
85
|
// panel would otherwise keep overriding.
|
|
67
86
|
presentation.reset_theme().await?;
|
|
68
|
-
renderer.
|
|
87
|
+
renderer.commit_theme(presentation.get_default_theme_name().await);
|
|
69
88
|
}
|
|
70
89
|
|
|
71
|
-
// For `all = true`, route the bucket clears through
|
|
90
|
+
// For `all = true`, route the bucket clears through the restore's
|
|
72
91
|
// `update_*` paths as `SetDefault`. This guarantees the materialized
|
|
73
92
|
// restore fires even when the user is already on the default plugin
|
|
74
93
|
// (no plugin_swap signal), since `SetDefault` reports the bucket as
|
|
@@ -93,16 +112,8 @@ pub fn reset_all(
|
|
|
93
112
|
|
|
94
113
|
// `reset()` is a public element API — `Public` keeps its repaint
|
|
95
114
|
// affordance even on an already-default config.
|
|
96
|
-
|
|
97
|
-
&session,
|
|
98
|
-
&renderer,
|
|
99
|
-
&presentation,
|
|
100
|
-
RunOrigin::Public,
|
|
101
|
-
update,
|
|
102
|
-
async { Ok(()) },
|
|
103
|
-
)
|
|
104
|
-
.await?;
|
|
115
|
+
restore_in_place(session, renderer, presentation, RunOrigin::Public, update).await?;
|
|
105
116
|
renderer.reset_changed.emit(());
|
|
106
117
|
Ok(())
|
|
107
|
-
}
|
|
118
|
+
}
|
|
108
119
|
}
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
//! Reconcile the workspace's derived `Table`s with a restored
|
|
14
|
+
//! `derived_tables` map.
|
|
15
|
+
|
|
16
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
17
|
+
|
|
18
|
+
use perspective_js::utils::*;
|
|
19
|
+
|
|
20
|
+
use crate::config::{DerivedTableConfig, OptionalUpdate, ViewerConfigUpdate};
|
|
21
|
+
use crate::derived::topo_order;
|
|
22
|
+
use crate::presentation::Presentation;
|
|
23
|
+
use crate::session::MissingTable;
|
|
24
|
+
use crate::tasks::{RestoreErrors, RestoreMode, build_derived, restore_panel};
|
|
25
|
+
use crate::workspace::Workspace;
|
|
26
|
+
|
|
27
|
+
/// Build every derivation in `configs` that is not already reusable,
|
|
28
|
+
/// parents-first, deferring any whose parent is not yet hosted.
|
|
29
|
+
pub async fn apply_derived_tables(
|
|
30
|
+
workspace: &Workspace,
|
|
31
|
+
configs: BTreeMap<String, DerivedTableConfig>,
|
|
32
|
+
) -> ApiResult<()> {
|
|
33
|
+
let order = topo_order(&configs).map_err(ApiError::new)?;
|
|
34
|
+
let reuse = workspace.derived_reusable(&order, &configs);
|
|
35
|
+
|
|
36
|
+
// Delete the stale generations children-first BEFORE building: a
|
|
37
|
+
// non-reusable name's old registration otherwise blocks its parent's
|
|
38
|
+
// redefinition (the parent's consumer count includes the stale child,
|
|
39
|
+
// and the engine refuses to delete a table the child's view reads).
|
|
40
|
+
for name in order.iter().rev() {
|
|
41
|
+
if reuse.contains(name) || !workspace.is_derived(name) {
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let consumers = workspace.derived_consumers(name);
|
|
46
|
+
if consumers > 0 {
|
|
47
|
+
return Err(ApiError::new(format!(
|
|
48
|
+
"Cannot redefine `{name}` while {consumers} panels or tables read it"
|
|
49
|
+
)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if let Some(stale) = workspace.take_derived(name) {
|
|
53
|
+
stale.delete().await?;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let mut deferred = BTreeMap::new();
|
|
58
|
+
for name in &order {
|
|
59
|
+
let config = &configs[name];
|
|
60
|
+
if reuse.contains(name) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
if deferred.contains_key(&config.table) {
|
|
65
|
+
deferred.insert(name.clone(), config.clone());
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let Some(client) = workspace
|
|
70
|
+
.resolve_client_for_table(&config.table, None)
|
|
71
|
+
.await
|
|
72
|
+
else {
|
|
73
|
+
deferred.insert(name.clone(), config.clone());
|
|
74
|
+
continue;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
if !build_derived(&client, workspace, name, &config.table, &config.view_config).await? {
|
|
78
|
+
deferred.insert(name.clone(), config.clone());
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
workspace.set_derived_pending(deferred);
|
|
83
|
+
Ok(())
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Delete every registered derivation absent from `keep`, children first.
|
|
87
|
+
pub async fn prune_derived(workspace: &Workspace, keep: &BTreeSet<String>) -> ApiResult<()> {
|
|
88
|
+
let mut doomed = workspace
|
|
89
|
+
.derived_names()
|
|
90
|
+
.into_iter()
|
|
91
|
+
.filter(|name| !keep.contains(name))
|
|
92
|
+
.collect::<Vec<_>>();
|
|
93
|
+
|
|
94
|
+
doomed.sort_by_key(|name| std::cmp::Reverse(workspace.derived_ancestors(name).len()));
|
|
95
|
+
for name in doomed {
|
|
96
|
+
if workspace.derived_consumers(&name) > 0 {
|
|
97
|
+
tracing::warn!("Keeping derived table \"{name}\", which is still read");
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if let Some(derived) = workspace.take_derived(&name)
|
|
102
|
+
&& let Err(err) = derived.delete().await
|
|
103
|
+
{
|
|
104
|
+
tracing::warn!("Failed to delete derived table \"{name}\": {err}");
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
Ok(())
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Retry the derivations whose parent was not hosted when they were restored.
|
|
112
|
+
pub async fn resume_derived(workspace: &Workspace) -> ApiResult<()> {
|
|
113
|
+
let pending = workspace.derived_pending();
|
|
114
|
+
if pending.is_empty() {
|
|
115
|
+
return Ok(());
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
let mut configs = workspace.derived_configs();
|
|
119
|
+
configs.extend(pending);
|
|
120
|
+
apply_derived_tables(workspace, configs).await
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/// Tear down the element's ENTIRE derived state (the "all" reset): release
|
|
124
|
+
/// every panel bound or pending to a derived (or pending-derived) table to
|
|
125
|
+
/// its root ancestor, clear the pending registry, and delete every
|
|
126
|
+
/// registered derivation children-first.
|
|
127
|
+
pub async fn purge_derived(workspace: &Workspace, presentation: &Presentation) -> ApiResult<()> {
|
|
128
|
+
let pending = workspace.derived_pending();
|
|
129
|
+
if workspace.derived_names().is_empty() && pending.is_empty() {
|
|
130
|
+
return Ok(());
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let parent_of = |name: &str| {
|
|
134
|
+
workspace
|
|
135
|
+
.derived_config(name)
|
|
136
|
+
.map(|x| x.table)
|
|
137
|
+
.or_else(|| pending.get(name).map(|x| x.table.clone()))
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
let root_of = |name: &str| {
|
|
141
|
+
let mut seen = BTreeSet::new();
|
|
142
|
+
let mut cursor = name.to_owned();
|
|
143
|
+
while seen.insert(cursor.clone()) {
|
|
144
|
+
match parent_of(&cursor) {
|
|
145
|
+
Some(parent) => cursor = parent,
|
|
146
|
+
None => break,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
cursor
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
let active = workspace.active_id();
|
|
154
|
+
let mut rebinds = Vec::new();
|
|
155
|
+
for panel in workspace.panels() {
|
|
156
|
+
let Some(bound) = panel
|
|
157
|
+
.session
|
|
158
|
+
.get_table()
|
|
159
|
+
.map(|x| x.get_name().to_owned())
|
|
160
|
+
.or_else(|| panel.session.pending_table())
|
|
161
|
+
else {
|
|
162
|
+
continue;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if parent_of(&bound).is_none() {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
let update = ViewerConfigUpdate {
|
|
170
|
+
table: OptionalUpdate::Update(root_of(&bound).into()),
|
|
171
|
+
..ViewerConfigUpdate::default()
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
let is_active = active.as_ref() == Some(&panel.id);
|
|
175
|
+
let presentation = presentation.clone();
|
|
176
|
+
let workspace = workspace.clone();
|
|
177
|
+
rebinds.push(async move {
|
|
178
|
+
restore_panel(
|
|
179
|
+
&panel.session,
|
|
180
|
+
&panel.renderer,
|
|
181
|
+
&presentation,
|
|
182
|
+
&workspace,
|
|
183
|
+
RestoreMode::Existing { active: is_active },
|
|
184
|
+
update,
|
|
185
|
+
RestoreErrors::Publish,
|
|
186
|
+
MissingTable::Pend,
|
|
187
|
+
None,
|
|
188
|
+
)
|
|
189
|
+
.await
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
futures::future::join_all(rebinds)
|
|
194
|
+
.await
|
|
195
|
+
.into_iter()
|
|
196
|
+
.collect::<ApiResult<Vec<_>>>()?;
|
|
197
|
+
|
|
198
|
+
workspace.set_derived_pending(BTreeMap::new());
|
|
199
|
+
prune_derived(workspace, &BTreeSet::new()).await
|
|
200
|
+
}
|