@perspective-dev/viewer 5.5.1 → 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/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 +115 -35
- package/dist/wasm/perspective-viewer.js +314 -190
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +21 -17
- package/package.json +31 -18
- 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/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 +74 -24
- package/src/rust/components/status_indicator.rs +8 -1
- package/src/rust/components/viewer/filters.rs +13 -1
- package/src/rust/components/viewer/msg.rs +4 -0
- package/src/rust/components/viewer/panels.rs +41 -1
- 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 +14 -0
- package/src/rust/components/viewer.rs +45 -25
- package/src/rust/components/window_editor.rs +5 -4
- package/src/rust/config/column_config_schema.rs +38 -377
- 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 +187 -131
- 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 +30 -9
- 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 +900 -395
- 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 +5 -0
- package/src/rust/tasks/create_panel.rs +34 -9
- package/src/rust/tasks/derive.rs +312 -0
- package/src/rust/tasks/mod.rs +6 -2
- package/src/rust/tasks/pipeline.rs +145 -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 +39 -12
- 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/local_fonts.rs +0 -36
- package/src/rust/utils/mod.rs +2 -3
- package/src/rust/utils/modal_position.rs +15 -21
- 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/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/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/mod.rs +0 -14
- package/src/rust/utils/tests/pubsub.rs +0 -95
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-6813b568219c6763 → perspective-viewer-d0d525977a1acba2}/inline5.js +0 -0
|
@@ -10,50 +10,64 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
use
|
|
13
|
+
use std::rc::Rc;
|
|
14
14
|
|
|
15
|
-
use
|
|
16
|
-
use
|
|
15
|
+
use futures::FutureExt;
|
|
16
|
+
use perspective_client::clone;
|
|
17
|
+
use perspective_js::utils::ApiFuture;
|
|
18
|
+
|
|
19
|
+
use super::pipeline::{RunCommit, render_run};
|
|
20
|
+
use super::transactional_restore::{commit_edit, prepare_overlay};
|
|
21
|
+
use crate::session::{OpKind, OverlayClause};
|
|
17
22
|
use crate::utils::spawn_owned;
|
|
18
|
-
use crate::workspace::{PanelId, Workspace};
|
|
23
|
+
use crate::workspace::{Panel, PanelId, Workspace};
|
|
19
24
|
|
|
20
|
-
///
|
|
21
|
-
///
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
///
|
|
25
|
-
/// Synchronous, so panel-creation paths can stamp BEFORE the panel's first
|
|
26
|
-
/// locked bind — the initial render then picks the overlay up via its
|
|
27
|
-
/// `ConfigSnapshot.effective`, with no second render and no unfiltered
|
|
28
|
-
/// first paint.
|
|
29
|
-
pub fn stamp_global_overlay(workspace: &Workspace, id: &PanelId, session: &Session) -> bool {
|
|
30
|
-
let filters = if workspace.is_master(id) {
|
|
25
|
+
/// The element's global filter as broadcast to panel `id` — EMPTY for master
|
|
26
|
+
/// (filter-source) panels, the full set for details.
|
|
27
|
+
pub(crate) fn overlay_for(workspace: &Workspace, id: &PanelId) -> Rc<Vec<OverlayClause>> {
|
|
28
|
+
Rc::new(if workspace.is_master(id) {
|
|
31
29
|
Vec::new()
|
|
32
30
|
} else {
|
|
33
|
-
workspace.
|
|
34
|
-
}
|
|
31
|
+
workspace.overlay()
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Broadcast the [`Workspace`]'s current global filter to one panel, as an op
|
|
36
|
+
/// on ITS queue: the overlay is panel state, committed with the description of
|
|
37
|
+
/// the config it yields, like any other write.
|
|
38
|
+
pub fn broadcast_overlay(workspace: &Workspace, panel: &Panel) -> ApiFuture<()> {
|
|
39
|
+
let overlay = overlay_for(workspace, &panel.id);
|
|
40
|
+
clone!(panel.session, panel.renderer);
|
|
41
|
+
let ticket = panel.session.submit(OpKind::Overlay, move |_ctx| {
|
|
42
|
+
Box::pin(async move {
|
|
43
|
+
if *session.committed_overlay() == *overlay {
|
|
44
|
+
return Ok(None);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
let bound = session.get_table().is_some();
|
|
48
|
+
let prepared = prepare_overlay(&session, &renderer, overlay).await?;
|
|
49
|
+
let committed = commit_edit(&session, &renderer, prepared);
|
|
50
|
+
Ok(if bound {
|
|
51
|
+
Some(render_run(session, renderer, RunCommit::Done(committed)).boxed_local())
|
|
52
|
+
} else {
|
|
53
|
+
None
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
});
|
|
35
57
|
|
|
36
|
-
|
|
58
|
+
ApiFuture::new(ticket.settle())
|
|
37
59
|
}
|
|
38
60
|
|
|
39
|
-
///
|
|
40
|
-
///
|
|
41
|
-
/// rather than relying on a subscription; unchanged panels aren't touched
|
|
42
|
-
/// at all).
|
|
61
|
+
/// Broadcast the global filter to every panel; panels whose overlay is
|
|
62
|
+
/// unchanged aren't touched at all.
|
|
43
63
|
pub fn apply_global_filters(workspace: &Workspace) {
|
|
44
|
-
for
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
{
|
|
48
|
-
let
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
spawn_owned("apply-global-filters", async move {
|
|
52
|
-
let _effect = effect;
|
|
53
|
-
apply_and_render(&session, &renderer, ViewConfigUpdate::default())?.await?;
|
|
54
|
-
Ok(())
|
|
55
|
-
});
|
|
56
|
-
}
|
|
64
|
+
for panel in workspace.panels() {
|
|
65
|
+
let effect = workspace.effects().guard();
|
|
66
|
+
let task = broadcast_overlay(workspace, &panel);
|
|
67
|
+
spawn_owned("apply-global-filters", async move {
|
|
68
|
+
let _effect = effect;
|
|
69
|
+
task.await
|
|
70
|
+
});
|
|
57
71
|
}
|
|
58
72
|
}
|
|
59
73
|
|
|
@@ -18,11 +18,12 @@ use wasm_bindgen::JsCast;
|
|
|
18
18
|
use wasm_bindgen::prelude::*;
|
|
19
19
|
use web_sys::*;
|
|
20
20
|
|
|
21
|
+
use super::transactional_restore::restore_in_place;
|
|
21
22
|
use crate::config::ViewerConfigUpdate;
|
|
22
23
|
use crate::js::*;
|
|
23
24
|
use crate::presentation::Presentation;
|
|
24
25
|
use crate::renderer::*;
|
|
25
|
-
use crate::session::Session;
|
|
26
|
+
use crate::session::{OpKind, Session};
|
|
26
27
|
use crate::utils::*;
|
|
27
28
|
use crate::workspace::Workspace;
|
|
28
29
|
use crate::*;
|
|
@@ -148,19 +149,28 @@ pub(crate) async fn set_panel_paused(
|
|
|
148
149
|
) -> ApiResult<()> {
|
|
149
150
|
if visible {
|
|
150
151
|
if session.set_pause(false) {
|
|
151
|
-
let
|
|
152
|
-
session,
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
152
|
+
let ticket = session.submit(OpKind::Restore { update: None }, {
|
|
153
|
+
clone!(session, renderer, presentation);
|
|
154
|
+
move |_ctx| {
|
|
155
|
+
Box::pin(async move {
|
|
156
|
+
restore_in_place(
|
|
157
|
+
&session,
|
|
158
|
+
&renderer,
|
|
159
|
+
&presentation,
|
|
160
|
+
super::RunOrigin::Internal,
|
|
161
|
+
ViewerConfigUpdate::default(),
|
|
162
|
+
)
|
|
163
|
+
.await?;
|
|
164
|
+
|
|
165
|
+
Ok(None)
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
let result = ticket.settle().await;
|
|
160
171
|
|
|
161
172
|
if let Err(e) = result.ignore_view_delete() {
|
|
162
|
-
|
|
163
|
-
return Err(e);
|
|
173
|
+
return renderer.fail(e);
|
|
164
174
|
}
|
|
165
175
|
}
|
|
166
176
|
} else {
|
|
@@ -29,6 +29,7 @@ use crate::queries::{export_app, get_viewer_config};
|
|
|
29
29
|
use crate::renderer::Renderer;
|
|
30
30
|
use crate::session::Session;
|
|
31
31
|
use crate::utils::*;
|
|
32
|
+
use crate::workspace::Workspace;
|
|
32
33
|
|
|
33
34
|
fn tag_name_to_package(plugin: &JsPerspectiveViewerPlugin) -> String {
|
|
34
35
|
let tag_name = plugin.unchecked_ref::<web_sys::HtmlElement>().tag_name();
|
|
@@ -118,6 +119,7 @@ pub async fn export_method_to_blob(
|
|
|
118
119
|
session: &Session,
|
|
119
120
|
renderer: &Renderer,
|
|
120
121
|
presentation: &Presentation,
|
|
122
|
+
workspace: &Workspace,
|
|
121
123
|
method: ExportMethod,
|
|
122
124
|
) -> ApiResult<web_sys::Blob> {
|
|
123
125
|
let viewport = renderer.get_selection();
|
|
@@ -197,6 +199,7 @@ pub async fn export_method_to_blob(
|
|
|
197
199
|
ExportMethod::JsonConfig => js_sys::JSON::stringify(
|
|
198
200
|
&get_viewer_config(session, renderer, presentation)
|
|
199
201
|
.await?
|
|
202
|
+
.map_table(|name| workspace.inline_table_ref(&name))
|
|
200
203
|
.encode()?,
|
|
201
204
|
)?
|
|
202
205
|
.as_blob(),
|
|
@@ -208,6 +211,7 @@ pub async fn export_method_to_jsvalue(
|
|
|
208
211
|
session: &Session,
|
|
209
212
|
renderer: &Renderer,
|
|
210
213
|
presentation: &Presentation,
|
|
214
|
+
workspace: &Workspace,
|
|
211
215
|
method: ExportMethod,
|
|
212
216
|
) -> ApiResult<JsValue> {
|
|
213
217
|
let viewport = renderer.get_selection();
|
|
@@ -286,6 +290,7 @@ pub async fn export_method_to_jsvalue(
|
|
|
286
290
|
ExportMethod::Plugin => txt_as_jsvalue(session, renderer, viewport).await?.into(),
|
|
287
291
|
ExportMethod::JsonConfig => get_viewer_config(session, renderer, presentation)
|
|
288
292
|
.await?
|
|
293
|
+
.map_table(|name| workspace.inline_table_ref(&name))
|
|
289
294
|
.encode()?,
|
|
290
295
|
})
|
|
291
296
|
}
|
|
@@ -31,11 +31,18 @@ use crate::*;
|
|
|
31
31
|
pub(crate) fn wire_panel_subs(
|
|
32
32
|
elem: &HtmlElement,
|
|
33
33
|
presentation: &Presentation,
|
|
34
|
+
workspace: &Workspace,
|
|
34
35
|
session: &Session,
|
|
35
36
|
renderer: &Renderer,
|
|
36
37
|
) -> Vec<Subscription> {
|
|
37
38
|
let mut subs = vec![wire_panel_render_sub(session, renderer)];
|
|
38
|
-
subs.extend(wire_panel_events(
|
|
39
|
+
subs.extend(wire_panel_events(
|
|
40
|
+
elem,
|
|
41
|
+
session,
|
|
42
|
+
renderer,
|
|
43
|
+
presentation,
|
|
44
|
+
workspace,
|
|
45
|
+
));
|
|
39
46
|
subs
|
|
40
47
|
}
|
|
41
48
|
|
|
@@ -97,7 +104,6 @@ pub(crate) async fn create_panel(
|
|
|
97
104
|
Placement::Staged,
|
|
98
105
|
);
|
|
99
106
|
|
|
100
|
-
stamp_global_overlay(workspace, &id, &session);
|
|
101
107
|
notify.emit(());
|
|
102
108
|
|
|
103
109
|
ApiFuture::spawn({
|
|
@@ -123,12 +129,12 @@ pub(crate) async fn create_panel(
|
|
|
123
129
|
update,
|
|
124
130
|
crate::tasks::RestoreErrors::Publish,
|
|
125
131
|
missing,
|
|
132
|
+
None,
|
|
126
133
|
)
|
|
127
134
|
.await;
|
|
128
135
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if workspace.promote(&id) {
|
|
136
|
+
let discarded = discard_rejected(workspace, &id, &session, &result).await?;
|
|
137
|
+
if discarded || workspace.promote(&id) {
|
|
132
138
|
notify.emit(());
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -136,6 +142,26 @@ pub(crate) async fn create_panel(
|
|
|
136
142
|
Ok(id)
|
|
137
143
|
}
|
|
138
144
|
|
|
145
|
+
/// Remove the panel a rejected CREATING restore was for.
|
|
146
|
+
pub(crate) async fn discard_rejected(
|
|
147
|
+
workspace: &Workspace,
|
|
148
|
+
id: &PanelId,
|
|
149
|
+
session: &Session,
|
|
150
|
+
result: &ApiResult<()>,
|
|
151
|
+
) -> ApiResult<bool> {
|
|
152
|
+
if result.is_ok() || session.get_table().is_some() || session.pending_table().is_some() {
|
|
153
|
+
return Ok(false);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
match workspace.remove_panel(id) {
|
|
157
|
+
Some(panel) => {
|
|
158
|
+
eject_panel(panel, Disposal::Resolve).await?;
|
|
159
|
+
Ok(true)
|
|
160
|
+
},
|
|
161
|
+
None => Ok(false),
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
139
165
|
/// Where [`create_panel_model`] registers the new panel model.
|
|
140
166
|
pub(crate) enum Placement {
|
|
141
167
|
/// Into the placed panel set at [`PanelPhase::Placed`] — a live, visible
|
|
@@ -173,7 +199,7 @@ pub(crate) fn create_panel_model(
|
|
|
173
199
|
let renderer = Renderer::new(elem);
|
|
174
200
|
let id = id.unwrap_or_else(|| workspace.generate_id());
|
|
175
201
|
renderer.set_slot_name(id.as_str());
|
|
176
|
-
let subs = wire_panel_subs(elem, presentation, &session, &renderer);
|
|
202
|
+
let subs = wire_panel_subs(elem, presentation, workspace, &session, &renderer);
|
|
177
203
|
let panel = Panel::new(id.clone(), session.clone(), renderer.clone(), subs);
|
|
178
204
|
match placement {
|
|
179
205
|
Placement::Placed => workspace.insert_panel(panel, PanelPhase::Placed),
|
|
@@ -182,7 +208,7 @@ pub(crate) fn create_panel_model(
|
|
|
182
208
|
}
|
|
183
209
|
|
|
184
210
|
update.settings = OptionalUpdate::Missing;
|
|
185
|
-
renderer.
|
|
211
|
+
renderer.commit_theme(match &update.theme {
|
|
186
212
|
OptionalUpdate::Update(theme) => Some(theme.clone()),
|
|
187
213
|
_ => presentation.active_theme_name_sync(),
|
|
188
214
|
});
|
|
@@ -213,7 +239,6 @@ pub(crate) fn place_reserved(
|
|
|
213
239
|
has_table: bool,
|
|
214
240
|
) -> Option<Panel> {
|
|
215
241
|
let panel = workspace.claim_reserved(has_table)?;
|
|
216
|
-
stamp_global_overlay(workspace, &panel.id, &panel.session);
|
|
217
242
|
notify.emit(());
|
|
218
243
|
Some(panel)
|
|
219
244
|
}
|
|
@@ -223,7 +248,7 @@ pub(crate) fn place_reserved(
|
|
|
223
248
|
/// Shared by the root's `ClosePanel` handler and `restoreWorkspace`'s
|
|
224
249
|
/// batch replacement of the pre-existing panel set.
|
|
225
250
|
pub(crate) fn eject_panel(panel: Panel, disposal: Disposal) -> ApiFuture<()> {
|
|
226
|
-
panel.session.
|
|
251
|
+
panel.session.dispose(disposal);
|
|
227
252
|
ApiFuture::new(async move {
|
|
228
253
|
panel.renderer.dispose().await?;
|
|
229
254
|
let was_errored = panel.session.is_errored();
|
|
@@ -0,0 +1,312 @@
|
|
|
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
|
+
//! Create a derived `Table` from a panel's view, and release a panel back to
|
|
14
|
+
//! the `Table` its own derived one reads.
|
|
15
|
+
|
|
16
|
+
use perspective_client::config::{ViewConfig, ViewConfigUpdate};
|
|
17
|
+
use perspective_client::{Client, DeleteOptions, TableData, TableInitOptions};
|
|
18
|
+
use perspective_js::utils::*;
|
|
19
|
+
|
|
20
|
+
use crate::config::{DerivedTableConfig, OptionalUpdate, TableRef, ViewerConfigUpdate};
|
|
21
|
+
use crate::derived::{DerivedSource, DerivedTable, suggest_name};
|
|
22
|
+
use crate::presentation::Presentation;
|
|
23
|
+
use crate::queries::fetch_hosted_tables;
|
|
24
|
+
use crate::renderer::Renderer;
|
|
25
|
+
use crate::session::{MissingTable, Session, probe_table};
|
|
26
|
+
use crate::tasks::{RestoreErrors, RestoreMode, restore_panel};
|
|
27
|
+
use crate::utils::PtrEqRc;
|
|
28
|
+
use crate::workspace::Workspace;
|
|
29
|
+
|
|
30
|
+
/// Whether this panel's `Client` can derive a `Table` from a `View`.
|
|
31
|
+
pub fn can_derive(session: &Session) -> bool {
|
|
32
|
+
session
|
|
33
|
+
.metadata()
|
|
34
|
+
.get_features()
|
|
35
|
+
.is_some_and(|x| x.view_derivations)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// Rewrite an inline derived-table definition in `update.table` to a concrete
|
|
39
|
+
/// hosted name, rebinding to a structurally identical live derivation where
|
|
40
|
+
/// one exists and building (or pending, per `missing`) the rest.
|
|
41
|
+
pub async fn resolve_table_ref(
|
|
42
|
+
workspace: &Workspace,
|
|
43
|
+
update: &mut ViewerConfigUpdate,
|
|
44
|
+
missing: MissingTable,
|
|
45
|
+
) -> ApiResult<()> {
|
|
46
|
+
if !matches!(&update.table, OptionalUpdate::Update(TableRef::Derived(_))) {
|
|
47
|
+
return Ok(());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let OptionalUpdate::Update(table_ref) =
|
|
51
|
+
std::mem::replace(&mut update.table, OptionalUpdate::Missing)
|
|
52
|
+
else {
|
|
53
|
+
return Ok(());
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
let mut levels = vec![];
|
|
57
|
+
let mut cursor = table_ref;
|
|
58
|
+
let base = loop {
|
|
59
|
+
match cursor {
|
|
60
|
+
TableRef::Name(name) => break name,
|
|
61
|
+
TableRef::Derived(config) => {
|
|
62
|
+
levels.push(config.view_config);
|
|
63
|
+
cursor = config.table;
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
let mut pending = workspace.derived_pending();
|
|
69
|
+
let mut taken = fetch_hosted_tables(workspace)
|
|
70
|
+
.await
|
|
71
|
+
.into_iter()
|
|
72
|
+
.flat_map(|(_, tables)| tables)
|
|
73
|
+
.collect::<std::collections::BTreeSet<_>>();
|
|
74
|
+
|
|
75
|
+
taken.extend(pending.keys().cloned());
|
|
76
|
+
let mut parent = base;
|
|
77
|
+
for view_config in levels.into_iter().rev() {
|
|
78
|
+
let config = DerivedTableConfig {
|
|
79
|
+
table: parent.clone(),
|
|
80
|
+
view_config,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
if let Some(existing) = workspace.derived_find_structural(&config) {
|
|
84
|
+
parent = existing;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if let Some(existing) = pending.iter().find(|(_, x)| **x == config) {
|
|
89
|
+
parent = existing.0.clone();
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
let name = suggest_name(
|
|
94
|
+
&taken,
|
|
95
|
+
&PtrEqRc::new(config.view_config.clone()),
|
|
96
|
+
&Some(config.table.clone()),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
taken.insert(name.clone());
|
|
100
|
+
let built = match workspace
|
|
101
|
+
.resolve_client_for_table(&config.table, None)
|
|
102
|
+
.await
|
|
103
|
+
{
|
|
104
|
+
Some(client) => {
|
|
105
|
+
build_derived(
|
|
106
|
+
&client,
|
|
107
|
+
workspace,
|
|
108
|
+
&name,
|
|
109
|
+
&config.table,
|
|
110
|
+
&config.view_config,
|
|
111
|
+
)
|
|
112
|
+
.await?
|
|
113
|
+
},
|
|
114
|
+
None => false,
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if !built {
|
|
118
|
+
if matches!(missing, MissingTable::Error) {
|
|
119
|
+
return Err(ApiError::new(format!("Unknown table \"{}\"", config.table)));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pending.insert(name.clone(), config);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
parent = name;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
workspace.set_derived_pending(pending);
|
|
129
|
+
update.table = OptionalUpdate::Update(TableRef::Name(parent));
|
|
130
|
+
Ok(())
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/// Build a derived `Table` named `name` from `parent`'s `view_config`, and
|
|
134
|
+
/// register it on `workspace`.
|
|
135
|
+
pub async fn build_derived(
|
|
136
|
+
client: &Client,
|
|
137
|
+
workspace: &Workspace,
|
|
138
|
+
name: &str,
|
|
139
|
+
parent: &str,
|
|
140
|
+
view_config: &ViewConfig,
|
|
141
|
+
) -> ApiResult<bool> {
|
|
142
|
+
let Some(parent_table) = probe_table(client, parent, MissingTable::Pend).await? else {
|
|
143
|
+
return Ok(false);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// An empty `columns` in a derived DEFINITION means "all columns" (the
|
|
147
|
+
// engine's absent-columns default), not a key-only view — `ViewConfig`'s
|
|
148
|
+
// serde default materializes `[]` for definitions that omit the key.
|
|
149
|
+
let mut update: ViewConfigUpdate = view_config.clone().into();
|
|
150
|
+
if update.columns.as_ref().is_none_or(|x| x.is_empty()) {
|
|
151
|
+
update.columns = None;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let view = parent_table.view(Some(update)).await?;
|
|
155
|
+
let derived = match client
|
|
156
|
+
.table(TableData::View(view.clone()), TableInitOptions {
|
|
157
|
+
name: Some(name.to_owned()),
|
|
158
|
+
..TableInitOptions::default()
|
|
159
|
+
})
|
|
160
|
+
.await
|
|
161
|
+
{
|
|
162
|
+
Ok(derived) => derived,
|
|
163
|
+
Err(err) => {
|
|
164
|
+
view.delete().await?;
|
|
165
|
+
return Err(err.into());
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
workspace.register_derived(
|
|
170
|
+
name.to_owned(),
|
|
171
|
+
DerivedTable::new(
|
|
172
|
+
parent.to_owned(),
|
|
173
|
+
view_config.clone(),
|
|
174
|
+
DerivedSource::Restored(view),
|
|
175
|
+
derived,
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
Ok(true)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/// Promote the panel's current `View` to a derived `Table` named `name`,
|
|
183
|
+
/// consuming that `View` and rebinding the panel to the result.
|
|
184
|
+
pub async fn create_derived(
|
|
185
|
+
session: &Session,
|
|
186
|
+
renderer: &Renderer,
|
|
187
|
+
presentation: &Presentation,
|
|
188
|
+
workspace: &Workspace,
|
|
189
|
+
name: String,
|
|
190
|
+
) -> ApiResult<()> {
|
|
191
|
+
if !can_derive(session) {
|
|
192
|
+
return Err("This data source cannot derive a Table from a View".into());
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let client = session.get_client().ok_or("`load()` has not been called")?;
|
|
196
|
+
let table = session.get_table().ok_or("`load()` has not been called")?;
|
|
197
|
+
let parent = table.get_name().to_owned();
|
|
198
|
+
let plugin = renderer.metadata().name.clone();
|
|
199
|
+
let sub = session
|
|
200
|
+
.take_view_sub()
|
|
201
|
+
.ok_or("Panel has no View to derive from")?;
|
|
202
|
+
|
|
203
|
+
let view_config = (*sub.get_view_config()).clone();
|
|
204
|
+
let derived = match client
|
|
205
|
+
.table(TableData::View(sub.get_view().clone()), TableInitOptions {
|
|
206
|
+
name: Some(name.clone()),
|
|
207
|
+
..TableInitOptions::default()
|
|
208
|
+
})
|
|
209
|
+
.await
|
|
210
|
+
{
|
|
211
|
+
Ok(derived) => derived,
|
|
212
|
+
Err(err) => {
|
|
213
|
+
session.put_view_sub(sub);
|
|
214
|
+
return Err(err.into());
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
// The promoted `View` now feeds the derived `Table`, not this panel —
|
|
219
|
+
// silence its Session-bound stats/update channels, or every PARENT tick
|
|
220
|
+
// keeps writing the parent's stats over the panel's.
|
|
221
|
+
if let Err(err) = sub.quiesce().await {
|
|
222
|
+
session.put_view_sub(sub);
|
|
223
|
+
derived.delete(DeleteOptions::default()).await?;
|
|
224
|
+
return Err(err);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
workspace.register_derived(
|
|
228
|
+
name.clone(),
|
|
229
|
+
DerivedTable::new(parent, view_config, DerivedSource::Panel(sub), derived),
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
let update = ViewerConfigUpdate {
|
|
233
|
+
table: OptionalUpdate::Update(name.clone().into()),
|
|
234
|
+
plugin: OptionalUpdate::Update(plugin),
|
|
235
|
+
..ViewerConfigUpdate::default()
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
let restored = restore_panel(
|
|
239
|
+
session,
|
|
240
|
+
renderer,
|
|
241
|
+
presentation,
|
|
242
|
+
workspace,
|
|
243
|
+
RestoreMode::Existing { active: true },
|
|
244
|
+
update,
|
|
245
|
+
RestoreErrors::Publish,
|
|
246
|
+
MissingTable::Error,
|
|
247
|
+
None,
|
|
248
|
+
)
|
|
249
|
+
.await;
|
|
250
|
+
|
|
251
|
+
if restored.is_err()
|
|
252
|
+
&& let Some(derived) = workspace.take_derived(&name)
|
|
253
|
+
{
|
|
254
|
+
derived.delete().await?;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
restored
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/// Rebind the panel to the `Table` its derived one reads, deleting that
|
|
261
|
+
/// derivation when `delete` and nothing else consumes it.
|
|
262
|
+
pub async fn release_derived(
|
|
263
|
+
session: &Session,
|
|
264
|
+
renderer: &Renderer,
|
|
265
|
+
presentation: &Presentation,
|
|
266
|
+
workspace: &Workspace,
|
|
267
|
+
delete: bool,
|
|
268
|
+
) -> ApiResult<()> {
|
|
269
|
+
let table = session.get_table().ok_or("Panel has no table")?;
|
|
270
|
+
let name = table.get_name().to_owned();
|
|
271
|
+
let config = workspace
|
|
272
|
+
.derived_config(&name)
|
|
273
|
+
.ok_or("Panel is not bound to a derived table")?;
|
|
274
|
+
|
|
275
|
+
let update = ViewerConfigUpdate {
|
|
276
|
+
table: OptionalUpdate::Update(config.table.clone().into()),
|
|
277
|
+
view_config: config.view_config.into(),
|
|
278
|
+
..ViewerConfigUpdate::default()
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
restore_panel(
|
|
282
|
+
session,
|
|
283
|
+
renderer,
|
|
284
|
+
presentation,
|
|
285
|
+
workspace,
|
|
286
|
+
RestoreMode::Existing { active: true },
|
|
287
|
+
update,
|
|
288
|
+
RestoreErrors::Publish,
|
|
289
|
+
MissingTable::Error,
|
|
290
|
+
None,
|
|
291
|
+
)
|
|
292
|
+
.await?;
|
|
293
|
+
|
|
294
|
+
if delete {
|
|
295
|
+
delete_derived(workspace, &name).await?;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
Ok(())
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// Delete a derived `Table` that nothing consumes.
|
|
302
|
+
pub async fn delete_derived(workspace: &Workspace, name: &str) -> ApiResult<()> {
|
|
303
|
+
let consumers = workspace.derived_consumers(name);
|
|
304
|
+
if consumers > 0 {
|
|
305
|
+
return Err(format!("`{name}` is still read by {consumers} panels or tables").into());
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
match workspace.take_derived(name) {
|
|
309
|
+
Some(derived) => derived.delete().await,
|
|
310
|
+
None => Err(format!("`{name}` is not a derived table").into()),
|
|
311
|
+
}
|
|
312
|
+
}
|
package/src/rust/tasks/mod.rs
CHANGED
|
@@ -25,6 +25,7 @@ mod apply_global_filters;
|
|
|
25
25
|
mod auto_pause;
|
|
26
26
|
mod copy_export;
|
|
27
27
|
mod create_panel;
|
|
28
|
+
mod derive;
|
|
28
29
|
mod dismiss_render_warning;
|
|
29
30
|
mod edit_expression;
|
|
30
31
|
mod edit_window;
|
|
@@ -33,13 +34,14 @@ mod pipeline;
|
|
|
33
34
|
mod presize_panels;
|
|
34
35
|
mod reset_all;
|
|
35
36
|
mod resize_observer;
|
|
36
|
-
mod
|
|
37
|
+
mod restore_derived;
|
|
37
38
|
mod restore_panel;
|
|
38
39
|
mod send_column_config;
|
|
39
40
|
mod send_plugin_config;
|
|
40
41
|
mod set_edit_mode;
|
|
41
42
|
mod sync_update_panels;
|
|
42
43
|
mod table_lifecycle;
|
|
44
|
+
mod transactional_restore;
|
|
43
45
|
mod update_theme;
|
|
44
46
|
mod validate_expression;
|
|
45
47
|
|
|
@@ -57,6 +59,7 @@ pub use self::apply_global_filters::*;
|
|
|
57
59
|
pub use self::auto_pause::*;
|
|
58
60
|
pub use self::copy_export::*;
|
|
59
61
|
pub(crate) use self::create_panel::*;
|
|
62
|
+
pub use self::derive::*;
|
|
60
63
|
pub use self::dismiss_render_warning::*;
|
|
61
64
|
pub use self::edit_expression::*;
|
|
62
65
|
pub use self::edit_window::*;
|
|
@@ -65,12 +68,13 @@ pub use self::pipeline::*;
|
|
|
65
68
|
pub use self::presize_panels::*;
|
|
66
69
|
pub use self::reset_all::*;
|
|
67
70
|
pub use self::resize_observer::*;
|
|
68
|
-
pub use self::
|
|
71
|
+
pub use self::restore_derived::*;
|
|
69
72
|
pub(crate) use self::restore_panel::*;
|
|
70
73
|
pub use self::send_column_config::*;
|
|
71
74
|
pub use self::send_plugin_config::*;
|
|
72
75
|
pub use self::set_edit_mode::*;
|
|
73
76
|
pub(crate) use self::sync_update_panels::*;
|
|
74
77
|
pub(crate) use self::table_lifecycle::*;
|
|
78
|
+
pub(crate) use self::transactional_restore::{commit_and_render_locked, prepare};
|
|
75
79
|
pub use self::update_theme::*;
|
|
76
80
|
pub use self::validate_expression::*;
|