@perspective-dev/viewer 4.5.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +20 -1
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/esm/extensions.d.ts +22 -3
- package/dist/esm/perspective-viewer.d.ts +9 -0
- package/dist/esm/perspective-viewer.inline.js +20 -1
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +20 -1
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +71 -5
- package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
- package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
- package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
- package/dist/esm/ts-rs/Layout.d.ts +11 -0
- package/dist/esm/ts-rs/Orientation.d.ts +4 -0
- package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +39 -0
- package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
- package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -2
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
- package/dist/wasm/perspective-viewer.d.ts +211 -77
- package/dist/wasm/perspective-viewer.js +528 -180
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-daeef4ceb066e6c7/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-daeef4ceb066e6c7/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-settings-panel.css +1 -1
- package/src/css/containers/context-menu.css +101 -0
- package/src/css/panel-tab.css +193 -0
- package/src/css/plugin-selector.css +7 -10
- package/src/css/status-bar.css +75 -81
- package/src/css/viewer.css +180 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +7 -18
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/config_selector.rs +33 -81
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +3 -8
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +5 -11
- package/src/rust/components/containers/split_panel.rs +175 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/form/debug.rs +3 -0
- package/src/rust/components/global_filter_bar.rs +76 -0
- package/src/rust/components/main_panel/frame_theme.rs +186 -0
- package/src/rust/components/main_panel/msg.rs +62 -0
- package/src/rust/components/main_panel/presize.rs +220 -0
- package/src/rust/components/main_panel/reconcile.rs +224 -0
- package/src/rust/components/main_panel/render.rs +385 -0
- package/src/rust/components/main_panel/update.rs +184 -0
- package/src/rust/components/main_panel.rs +311 -71
- package/src/rust/components/mod.rs +4 -0
- package/src/rust/components/panel_menu.rs +486 -0
- package/src/rust/components/panel_tab.rs +505 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/settings_panel.rs +13 -5
- package/src/rust/components/status_bar.rs +110 -178
- package/src/rust/components/status_indicator.rs +6 -9
- package/src/rust/components/style/mod.rs +5 -0
- package/src/rust/components/style/style_provider.rs +2 -0
- package/src/rust/components/viewer/filters.rs +133 -0
- package/src/rust/components/viewer/msg.rs +180 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +243 -0
- package/src/rust/components/viewer/settings.rs +402 -0
- package/src/rust/components/viewer/snapshots.rs +134 -0
- package/src/rust/components/viewer/wiring.rs +505 -0
- package/src/rust/components/viewer.rs +243 -756
- package/src/rust/config/export_method.rs +1 -1
- package/src/rust/config/mod.rs +4 -0
- package/src/rust/config/options.rs +71 -0
- package/src/rust/config/viewer_config.rs +24 -2
- package/src/rust/config/workspace_config.rs +92 -0
- package/src/rust/custom_elements/copy_dropdown.rs +3 -1
- package/src/rust/custom_elements/debug_plugin.rs +20 -1
- package/src/rust/custom_elements/export_dropdown.rs +3 -1
- package/src/rust/custom_elements/viewer.rs +1015 -286
- package/src/rust/custom_events.rs +212 -61
- package/src/rust/js/mod.rs +2 -0
- package/src/rust/js/plugin.rs +297 -10
- package/src/rust/js/regular_layout.rs +258 -0
- package/src/rust/lib.rs +20 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/get_viewer_config.rs +29 -11
- package/src/rust/queries/plugin_column_styles.rs +2 -2
- package/src/rust/renderer/activate.rs +41 -14
- package/src/rust/renderer/dispatch.rs +436 -0
- package/src/rust/renderer/plugin_config.rs +495 -0
- package/src/rust/renderer/registry.rs +18 -6
- package/src/rust/renderer/render_timer.rs +29 -10
- package/src/rust/renderer.rs +350 -837
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +753 -264
- package/src/rust/tasks/apply_global_filters.rs +89 -0
- package/src/rust/tasks/auto_pause.rs +171 -0
- package/src/rust/tasks/copy_export.rs +29 -19
- package/src/rust/tasks/create_panel.rs +243 -0
- package/src/rust/tasks/dismiss_render_warning.rs +3 -1
- package/src/rust/tasks/edit_expression.rs +4 -4
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +28 -4
- package/src/rust/tasks/pipeline.rs +411 -0
- package/src/rust/tasks/presize_panels.rs +324 -0
- package/src/rust/tasks/reset_all.rs +22 -12
- package/src/rust/tasks/resize_observer.rs +124 -87
- package/src/rust/tasks/restore_and_render.rs +80 -86
- package/src/rust/tasks/restore_panel.rs +145 -0
- package/src/rust/tasks/send_column_config.rs +5 -2
- package/src/rust/tasks/send_plugin_config.rs +5 -2
- package/src/rust/tasks/set_edit_mode.rs +59 -0
- package/src/rust/tasks/sync_update_panels.rs +152 -0
- package/src/rust/tasks/table_lifecycle.rs +158 -0
- package/src/rust/tasks/update_theme.rs +56 -12
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +130 -7
- package/src/rust/utils/mod.rs +6 -0
- package/src/rust/utils/pending_effects.rs +62 -0
- package/src/rust/utils/spawn.rs +33 -0
- package/src/rust/utils/weak_scope.rs +0 -10
- package/src/rust/workspace.rs +846 -0
- package/src/svg/active-indicator.svg +3 -0
- package/src/themes/botanical.css +5 -2
- package/src/themes/defaults.css +54 -2
- package/src/themes/dracula.css +6 -3
- package/src/themes/gruvbox-dark.css +5 -2
- package/src/themes/gruvbox.css +5 -2
- package/src/themes/icons.css +4 -2
- package/src/themes/monokai.css +5 -2
- package/src/themes/phosphor.css +5 -2
- package/src/themes/pro-dark.css +5 -2
- package/src/themes/pro.css +2 -1
- package/src/themes/solarized-dark.css +5 -2
- package/src/themes/solarized.css +5 -2
- package/src/themes/vaporwave.css +5 -2
- package/src/ts/bootstrap.ts +8 -7
- package/src/ts/extensions.ts +30 -2
- package/src/ts/perspective-viewer.ts +10 -0
- package/src/ts/plugin.ts +72 -5
- package/src/ts/ts-rs/ClientOptions.ts +7 -0
- package/src/ts/ts-rs/ExportMethod.ts +3 -0
- package/src/ts/ts-rs/ExportOptions.ts +8 -0
- package/src/ts/ts-rs/GetClientOptions.ts +7 -0
- package/src/ts/ts-rs/GetTableOptions.ts +7 -0
- package/src/ts/ts-rs/Layout.ts +9 -0
- package/src/ts/ts-rs/Orientation.ts +6 -0
- package/src/ts/ts-rs/PanelOptions.ts +7 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +15 -0
- package/src/ts/ts-rs/SplitLayout.ts +5 -0
- package/src/ts/ts-rs/TabLayout.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +6 -2
- package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
- package/src/rust/tasks/intersection_observer.rs +0 -98
- package/src/rust/tasks/update_and_render.rs +0 -110
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline0.js → perspective-viewer-daeef4ceb066e6c7/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-daeef4ceb066e6c7/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-daeef4ceb066e6c7/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-daeef4ceb066e6c7/inline5.js} +0 -0
|
@@ -0,0 +1,324 @@
|
|
|
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
|
+
//! Presize/resize sweeps over the [`Workspace`]'s visible panels, plus the DOM
|
|
14
|
+
//! geometry measurements they (and `MainPanel`'s `before-resize` presize)
|
|
15
|
+
//! depend on. These implement the presize-everywhere architecture: every
|
|
16
|
+
//! geometry change renders each plugin at its *target* box before the layout
|
|
17
|
+
//! commits, so content never lags its container.
|
|
18
|
+
|
|
19
|
+
use std::future::Future;
|
|
20
|
+
|
|
21
|
+
use futures::channel::oneshot::channel;
|
|
22
|
+
use futures::future::join_all;
|
|
23
|
+
use perspective_js::utils::*;
|
|
24
|
+
use wasm_bindgen::JsCast;
|
|
25
|
+
|
|
26
|
+
use crate::workspace::Workspace;
|
|
27
|
+
|
|
28
|
+
/// Resize every *visible* panel's plugin to its current cell box, concurrently.
|
|
29
|
+
/// Used after a layout change that does NOT emit `regular-layout-before-resize`
|
|
30
|
+
/// — settings-panel toggle (the outer SplitPanel) and maximize/minimize — so
|
|
31
|
+
/// the per-panel `ResizeObserver` (now scoped to drags) isn't needed to catch
|
|
32
|
+
/// them. Hidden panels (e.g. an unselected stacked tab → null `offset_parent`)
|
|
33
|
+
/// are skipped; they resize when revealed. `resize()` debounces per renderer.
|
|
34
|
+
pub async fn resize_visible_panels(workspace: &Workspace) {
|
|
35
|
+
let panels = workspace
|
|
36
|
+
.panels()
|
|
37
|
+
.into_iter()
|
|
38
|
+
.filter(|panel| {
|
|
39
|
+
// `offset_parent` is `None` for a `display:none` cell.
|
|
40
|
+
panel
|
|
41
|
+
.renderer
|
|
42
|
+
.active_plugin()
|
|
43
|
+
.and_then(|plugin| {
|
|
44
|
+
plugin
|
|
45
|
+
.unchecked_ref::<web_sys::HtmlElement>()
|
|
46
|
+
.offset_parent()
|
|
47
|
+
})
|
|
48
|
+
.is_some()
|
|
49
|
+
})
|
|
50
|
+
.collect::<Vec<_>>();
|
|
51
|
+
|
|
52
|
+
join_all(panels.iter().map(|p| p.renderer.resize())).await;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// The present closures collected from a presize sweep — the deferred
|
|
56
|
+
/// "reveal" half of the staged-presize protocol
|
|
57
|
+
/// ([`crate::renderer::Renderer::presize_with_dimensions`]). Call
|
|
58
|
+
/// [`Self::reveal`] in the same task as the layout commit the sweep
|
|
59
|
+
/// anticipated, so every panel's staged pixels and the geometry change
|
|
60
|
+
/// reach the screen in a single paint. The reveal runs through `Drop`, so
|
|
61
|
+
/// an error or early-return path between sweep and commit releases every
|
|
62
|
+
/// hold instead of stranding the displays — the pairing invariant is
|
|
63
|
+
/// carried by the value, not by convention.
|
|
64
|
+
#[derive(Default)]
|
|
65
|
+
pub struct StagedPresents(Vec<js_sys::Function>);
|
|
66
|
+
|
|
67
|
+
impl StagedPresents {
|
|
68
|
+
/// Reveal every staged frame now (a semantically-named drop; the
|
|
69
|
+
/// closures are lock-free blits, safe in any task).
|
|
70
|
+
pub fn reveal(self) {}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
impl From<Vec<js_sys::Function>> for StagedPresents {
|
|
74
|
+
fn from(presents: Vec<js_sys::Function>) -> Self {
|
|
75
|
+
Self(presents)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
impl Drop for StagedPresents {
|
|
80
|
+
fn drop(&mut self) {
|
|
81
|
+
for f in self.0.drain(..) {
|
|
82
|
+
let _ = f.call0(&wasm_bindgen::JsValue::NULL);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// The `(width, height)` factors each plugin's cell grows by when the settings
|
|
88
|
+
/// pane collapses. The plugin area (`#main_panel_container`) grows to the full
|
|
89
|
+
/// `#layout_area` width (the side settings pane is gone) AND to the full
|
|
90
|
+
/// `#main_column` height — because the status bar switches to
|
|
91
|
+
/// `position:absolute` when settings closes (see `status-bar.css`) and no
|
|
92
|
+
/// longer consumes column height. Each factor defaults to `1.0` if it can't be
|
|
93
|
+
/// measured.
|
|
94
|
+
fn settings_close_grow_ratios(elem: &web_sys::HtmlElement) -> (f64, f64) {
|
|
95
|
+
let measure = |sel: &str| -> Option<web_sys::DomRect> {
|
|
96
|
+
elem.shadow_root()?
|
|
97
|
+
.query_selector(sel)
|
|
98
|
+
.ok()?
|
|
99
|
+
.map(|e| e.get_bounding_client_rect())
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
fn ratio(full: Option<f64>, current: Option<f64>) -> f64 {
|
|
103
|
+
match (full, current) {
|
|
104
|
+
(Some(f), Some(c)) if c > 0.0 && f > c => f / c,
|
|
105
|
+
_ => 1.0,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let mpc = measure("#main_panel_container");
|
|
110
|
+
let width = ratio(
|
|
111
|
+
measure("#layout_area").map(|r| r.width()),
|
|
112
|
+
mpc.as_ref().map(|r| r.width()),
|
|
113
|
+
);
|
|
114
|
+
let height = ratio(
|
|
115
|
+
measure("#main_column").map(|r| r.height()),
|
|
116
|
+
mpc.as_ref().map(|r| r.height()),
|
|
117
|
+
);
|
|
118
|
+
(width, height)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/// Pre-size each visible plugin to its GROWN post-close cell *before* the
|
|
122
|
+
/// settings pane collapses, so the pane collapses into an already-correct
|
|
123
|
+
/// plugin — one clean resize instead of grow-then-resize. The cell grows in
|
|
124
|
+
/// BOTH width (the side settings pane is gone) and height (the status bar goes
|
|
125
|
+
/// floating).
|
|
126
|
+
pub async fn presize_visible_panels_grown(
|
|
127
|
+
workspace: &Workspace,
|
|
128
|
+
elem: &web_sys::HtmlElement,
|
|
129
|
+
) -> StagedPresents {
|
|
130
|
+
let (width_ratio, height_ratio) = settings_close_grow_ratios(elem);
|
|
131
|
+
presize_visible_panels_scaled(workspace, elem, width_ratio, height_ratio).await
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/// Pre-size each visible plugin to its SHRUNK post-open cell *before* the
|
|
135
|
+
/// settings pane mounts (P2), using the open-state geometry deltas cached at
|
|
136
|
+
/// the last close (`(layout_area.w − mpc.w, main_column.h − mpc.h)` — the pane
|
|
137
|
+
/// and divider width and the docked status-bar height, both stable across a
|
|
138
|
+
/// close/open cycle since the pane width persists in the override).
|
|
139
|
+
pub async fn presize_visible_panels_open(
|
|
140
|
+
workspace: &Workspace,
|
|
141
|
+
elem: &web_sys::HtmlElement,
|
|
142
|
+
delta_w: f64,
|
|
143
|
+
delta_h: f64,
|
|
144
|
+
) -> StagedPresents {
|
|
145
|
+
let Some(mpc) = shadow_rect(elem, "#main_panel_container") else {
|
|
146
|
+
return StagedPresents::default();
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
let (mpc_w0, mpc_h0) = (mpc.width(), mpc.height());
|
|
150
|
+
if mpc_w0 <= 0.0 || mpc_h0 <= 0.0 {
|
|
151
|
+
return StagedPresents::default();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
let width_ratio = (mpc_w0 - delta_w) / mpc_w0;
|
|
155
|
+
let height_ratio = (mpc_h0 - delta_h) / mpc_h0;
|
|
156
|
+
presize_visible_panels_scaled(workspace, elem, width_ratio, height_ratio).await
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Pre-size each visible plugin for a proposed settings-pane width (P1, the
|
|
160
|
+
/// deferred divider's pump). The grid distributes width proportionally, so the
|
|
161
|
+
/// target cells scale by the container's width ratio, derived arithmetically:
|
|
162
|
+
/// `mpc₁ = mpc₀ + (pane₀ − pane₁)`. Height is unaffected by the divider.
|
|
163
|
+
pub async fn presize_visible_panels_pane_width(
|
|
164
|
+
workspace: &Workspace,
|
|
165
|
+
elem: &web_sys::HtmlElement,
|
|
166
|
+
pane_width: f64,
|
|
167
|
+
) -> StagedPresents {
|
|
168
|
+
let Some(mpc) = shadow_rect(elem, "#main_panel_container") else {
|
|
169
|
+
return StagedPresents::default();
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let Some(pane) = shadow_rect(elem, "#app_panel > .split-panel-child") else {
|
|
173
|
+
return StagedPresents::default();
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
let mpc_w0 = mpc.width();
|
|
177
|
+
if mpc_w0 <= 0.0 {
|
|
178
|
+
return StagedPresents::default();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
let width_ratio = (mpc_w0 + (pane.width() - pane_width)) / mpc_w0;
|
|
182
|
+
presize_visible_panels_scaled(workspace, elem, width_ratio, 1.0).await
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// Shared presize core: render every visible plugin at its current cell box
|
|
186
|
+
/// scaled by `(width_ratio, height_ratio)` (track space), before the container
|
|
187
|
+
/// change those ratios anticipate.
|
|
188
|
+
async fn presize_visible_panels_scaled(
|
|
189
|
+
workspace: &Workspace,
|
|
190
|
+
elem: &web_sys::HtmlElement,
|
|
191
|
+
width_ratio: f64,
|
|
192
|
+
height_ratio: f64,
|
|
193
|
+
) -> StagedPresents {
|
|
194
|
+
if !width_ratio.is_finite()
|
|
195
|
+
|| !height_ratio.is_finite()
|
|
196
|
+
|| width_ratio <= 0.0
|
|
197
|
+
|| height_ratio <= 0.0
|
|
198
|
+
{
|
|
199
|
+
return StagedPresents::default();
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let mut last_chrome: Option<(f64, f64)> = None;
|
|
203
|
+
let targets = workspace
|
|
204
|
+
.panels()
|
|
205
|
+
.into_iter()
|
|
206
|
+
.filter_map(|panel| {
|
|
207
|
+
let plugin = panel.renderer.active_plugin()?;
|
|
208
|
+
let el = plugin.unchecked_ref::<web_sys::Element>();
|
|
209
|
+
el.unchecked_ref::<web_sys::HtmlElement>().offset_parent()?; // skip hidden
|
|
210
|
+
let plugin_box = el.get_bounding_client_rect();
|
|
211
|
+
let chrome = elem
|
|
212
|
+
.shadow_root()
|
|
213
|
+
.and_then(|r| {
|
|
214
|
+
r.query_selector(&format!(
|
|
215
|
+
"regular-layout-frame[name=\"{}\"]",
|
|
216
|
+
panel.id.as_str()
|
|
217
|
+
))
|
|
218
|
+
.ok()
|
|
219
|
+
.flatten()
|
|
220
|
+
})
|
|
221
|
+
.and_then(|frame| plugin_chrome(&frame, el));
|
|
222
|
+
|
|
223
|
+
last_chrome = chrome.or(last_chrome);
|
|
224
|
+
let (cw, ch) = last_chrome.unwrap_or(super::CHROME_FALLBACK);
|
|
225
|
+
let w = ((plugin_box.width() + cw) * width_ratio - cw).max(0.0);
|
|
226
|
+
let h = ((plugin_box.height() + ch) * height_ratio - ch).max(0.0);
|
|
227
|
+
Some((panel, w, h))
|
|
228
|
+
})
|
|
229
|
+
.collect::<Vec<_>>();
|
|
230
|
+
|
|
231
|
+
staged_presents_with_deadline(
|
|
232
|
+
workspace.effects().guard(),
|
|
233
|
+
async move {
|
|
234
|
+
join_all(
|
|
235
|
+
targets
|
|
236
|
+
.iter()
|
|
237
|
+
.map(|(p, w, h)| p.renderer.presize_with_dimensions(*w, *h)),
|
|
238
|
+
)
|
|
239
|
+
.await
|
|
240
|
+
.into_iter()
|
|
241
|
+
.filter_map(|r| r.ok().flatten())
|
|
242
|
+
.collect()
|
|
243
|
+
},
|
|
244
|
+
|| {},
|
|
245
|
+
)
|
|
246
|
+
.await
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/// Run `work` (a presize sweep producing staged present closures) against
|
|
250
|
+
/// the [`super::STAGING_DEADLINE_MS`] deadline. When `work` wins, its
|
|
251
|
+
/// presents are returned for the caller to reveal in its commit task. When
|
|
252
|
+
/// the deadline wins, an empty [`StagedPresents`] is returned (the caller's
|
|
253
|
+
/// commit proceeds); `work` then finishes late — `late` runs its cleanup and
|
|
254
|
+
/// the presents are revealed immediately, so a hold can never outlive its
|
|
255
|
+
/// prepare. The single consumer makes a double release inexpressible.
|
|
256
|
+
pub(crate) async fn staged_presents_with_deadline(
|
|
257
|
+
effect: crate::utils::EffectGuard,
|
|
258
|
+
work: impl Future<Output = Vec<js_sys::Function>> + 'static,
|
|
259
|
+
late: impl FnOnce() + 'static,
|
|
260
|
+
) -> StagedPresents {
|
|
261
|
+
let (done, on_done) = channel::<Vec<js_sys::Function>>();
|
|
262
|
+
ApiFuture::spawn(async move {
|
|
263
|
+
let _effect = effect;
|
|
264
|
+
let presents = work.await;
|
|
265
|
+
if let Err(presents) = done.send(presents) {
|
|
266
|
+
late();
|
|
267
|
+
StagedPresents::from(presents).reveal();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
Ok(())
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
let deadline = crate::utils::set_timeout(super::STAGING_DEADLINE_MS);
|
|
274
|
+
match futures::future::select(Box::pin(on_done), Box::pin(deadline)).await {
|
|
275
|
+
futures::future::Either::Left((Ok(presents), _)) => StagedPresents::from(presents),
|
|
276
|
+
_ => StagedPresents::default(),
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/// Measure a shadow-DOM descendant's border-box rect.
|
|
281
|
+
fn shadow_rect(elem: &web_sys::HtmlElement, sel: &str) -> Option<web_sys::DomRect> {
|
|
282
|
+
Some(
|
|
283
|
+
elem.shadow_root()?
|
|
284
|
+
.query_selector(sel)
|
|
285
|
+
.ok()??
|
|
286
|
+
.get_bounding_client_rect(),
|
|
287
|
+
)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/// The open-state geometry deltas cached for the next settings *open* (P2):
|
|
291
|
+
/// `(layout_area.w − mpc.w, main_column.h − mpc.h)`. Only meaningful while the
|
|
292
|
+
/// settings pane is open (i.e. measured at close time).
|
|
293
|
+
pub fn measure_settings_open_deltas(elem: &web_sys::HtmlElement) -> Option<(f64, f64)> {
|
|
294
|
+
let mpc = shadow_rect(elem, "#main_panel_container")?;
|
|
295
|
+
let layout_area = shadow_rect(elem, "#layout_area")?;
|
|
296
|
+
let main_column = shadow_rect(elem, "#main_column")?;
|
|
297
|
+
Some((
|
|
298
|
+
(layout_area.width() - mpc.width()).max(0.0),
|
|
299
|
+
(main_column.height() - mpc.height()).max(0.0),
|
|
300
|
+
))
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/// The frame chrome a plugin's grid cell has that the plugin itself doesn't
|
|
304
|
+
/// occupy, as `(width, height)` px: margin + border + titlebar. Measured live
|
|
305
|
+
/// from a `regular-layout-frame` and its plugin — robust to theme/CSS changes —
|
|
306
|
+
/// rather than hardcoded constants. `real_coordinates` reports the grid TRACK,
|
|
307
|
+
/// which equals the frame's *margin box* (`getBoundingClientRect` + margins);
|
|
308
|
+
/// the plugin fills the frame's container below its titlebar, so `track −
|
|
309
|
+
/// plugin` is exactly the chrome. Returns `None` if it can't be measured.
|
|
310
|
+
pub fn plugin_chrome(frame: &web_sys::Element, plugin: &web_sys::Element) -> Option<(f64, f64)> {
|
|
311
|
+
let style = web_sys::window()?.get_computed_style(frame).ok()??;
|
|
312
|
+
let px = |prop: &str| {
|
|
313
|
+
style
|
|
314
|
+
.get_property_value(prop)
|
|
315
|
+
.ok()
|
|
316
|
+
.and_then(|v| v.trim().trim_end_matches("px").parse::<f64>().ok())
|
|
317
|
+
.unwrap_or(0.0)
|
|
318
|
+
};
|
|
319
|
+
let frame_box = frame.get_bounding_client_rect();
|
|
320
|
+
let plugin_box = plugin.get_bounding_client_rect();
|
|
321
|
+
let width = frame_box.width() + px("margin-left") + px("margin-right") - plugin_box.width();
|
|
322
|
+
let height = frame_box.height() + px("margin-top") + px("margin-bottom") - plugin_box.height();
|
|
323
|
+
Some((width.max(0.0), height.max(0.0)))
|
|
324
|
+
}
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
//! presentation columns config / theme, then delegate to `restore_and_render`
|
|
15
15
|
//! to switch back to the default plugin and redraw.
|
|
16
16
|
|
|
17
|
-
use futures::channel::oneshot;
|
|
18
17
|
use perspective_client::clone;
|
|
19
18
|
use perspective_js::utils::ApiFuture;
|
|
20
19
|
|
|
20
|
+
use super::pipeline::RunOrigin;
|
|
21
21
|
use super::restore_and_render;
|
|
22
22
|
use crate::config::{
|
|
23
23
|
ColumnConfigUpdate, OptionalUpdate, PluginConfigUpdate, PluginUpdate, ViewerConfigUpdate,
|
|
@@ -32,8 +32,10 @@ use crate::session::{ResetOptions, Session};
|
|
|
32
32
|
/// per-column style maps.
|
|
33
33
|
/// - `all = true`: also clears expressions, per-column styles, and theme.
|
|
34
34
|
///
|
|
35
|
-
///
|
|
36
|
-
///
|
|
35
|
+
/// Returns the reset+redraw round-trip's future — the CALLER owns completion
|
|
36
|
+
/// (invariant I6: message handlers resolve their `Completion` only from run
|
|
37
|
+
/// futures like this one) and error, rather than this task spawning unowned
|
|
38
|
+
/// work.
|
|
37
39
|
///
|
|
38
40
|
/// Delegates plugin selection + draw to [`restore_and_render`], whose
|
|
39
41
|
/// two-pass restore guarantees the default plugin sees materialized
|
|
@@ -45,11 +47,10 @@ pub fn reset_all(
|
|
|
45
47
|
renderer: &Renderer,
|
|
46
48
|
presentation: &Presentation,
|
|
47
49
|
all: bool,
|
|
48
|
-
|
|
49
|
-
) {
|
|
50
|
+
) -> ApiFuture<()> {
|
|
50
51
|
presentation.set_open_column_settings(None);
|
|
51
52
|
clone!(session, renderer, presentation);
|
|
52
|
-
ApiFuture::
|
|
53
|
+
ApiFuture::new(async move {
|
|
53
54
|
session
|
|
54
55
|
.reset(ResetOptions {
|
|
55
56
|
config: true,
|
|
@@ -60,6 +61,10 @@ pub fn reset_all(
|
|
|
60
61
|
|
|
61
62
|
presentation.reset_available_themes(None).await;
|
|
62
63
|
if all {
|
|
64
|
+
// Clear this panel's own per-panel theme so it reverts to inheriting
|
|
65
|
+
// the (reset-to-default) host theme — `reset_theme` only resets the
|
|
66
|
+
// host, which an explicitly-themed panel would otherwise override.
|
|
67
|
+
renderer.set_theme(None);
|
|
63
68
|
presentation.reset_theme().await?;
|
|
64
69
|
}
|
|
65
70
|
|
|
@@ -86,12 +91,17 @@ pub fn reset_all(
|
|
|
86
91
|
..Default::default()
|
|
87
92
|
};
|
|
88
93
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
94
|
+
// `reset()` is a public element API — `Public` keeps its repaint
|
|
95
|
+
// affordance even on an already-default config.
|
|
96
|
+
restore_and_render(
|
|
97
|
+
&session,
|
|
98
|
+
&renderer,
|
|
99
|
+
&presentation,
|
|
100
|
+
RunOrigin::Public,
|
|
101
|
+
update,
|
|
102
|
+
async { Ok(()) },
|
|
103
|
+
)
|
|
104
|
+
.await?;
|
|
95
105
|
renderer.reset_changed.emit(());
|
|
96
106
|
Ok(())
|
|
97
107
|
})
|
|
@@ -10,127 +10,164 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
use
|
|
13
|
+
use futures::future::join_all;
|
|
14
|
+
use perspective_client::clone;
|
|
15
|
+
use perspective_js::utils::{ApiFuture, ApiResult};
|
|
14
16
|
use wasm_bindgen::JsCast;
|
|
15
17
|
use wasm_bindgen::prelude::*;
|
|
16
18
|
use web_sys::*;
|
|
17
|
-
use yew::prelude::*;
|
|
18
19
|
|
|
20
|
+
use super::update_theme::seed_default_themes;
|
|
19
21
|
use crate::components::viewer::{PerspectiveViewer, PerspectiveViewerMsg};
|
|
20
|
-
use crate::js
|
|
22
|
+
use crate::js::{ResizeObserver, ResizeObserverEntry};
|
|
21
23
|
use crate::presentation::Presentation;
|
|
22
|
-
use crate::renderer
|
|
24
|
+
use crate::renderer::Renderer;
|
|
23
25
|
use crate::root::Root;
|
|
24
|
-
use crate::session::
|
|
25
|
-
use crate::
|
|
26
|
+
use crate::session::TableLoadState;
|
|
27
|
+
use crate::workspace::Workspace;
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
struct SizeObserver {
|
|
28
30
|
elem: HtmlElement,
|
|
29
31
|
observer: ResizeObserver,
|
|
30
32
|
_callback: Closure<dyn FnMut(js_sys::Array)>,
|
|
31
33
|
}
|
|
32
34
|
|
|
33
|
-
impl
|
|
34
|
-
|
|
35
|
-
elem
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
.
|
|
44
|
-
|
|
45
|
-
|
|
35
|
+
impl SizeObserver {
|
|
36
|
+
fn new(elem: &HtmlElement, mut on_change: impl FnMut() + 'static) -> Self {
|
|
37
|
+
let mut width = elem.offset_width();
|
|
38
|
+
let mut height = elem.offset_height();
|
|
39
|
+
let target = elem.clone();
|
|
40
|
+
let _callback = Closure::new(move |entries: js_sys::Array| {
|
|
41
|
+
let is_visible = target
|
|
42
|
+
.offset_parent()
|
|
43
|
+
.map(|x| !x.is_null())
|
|
44
|
+
.unwrap_or(false);
|
|
45
|
+
for y in entries.iter() {
|
|
46
|
+
let entry: ResizeObserverEntry = y.unchecked_into();
|
|
47
|
+
let content = entry.content_rect();
|
|
48
|
+
let content_width = content.width().floor() as i32;
|
|
49
|
+
let content_height = content.height().floor() as i32;
|
|
50
|
+
let resized = width != content_width || height != content_height;
|
|
51
|
+
if resized && is_visible {
|
|
52
|
+
on_change();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
width = content_width;
|
|
56
|
+
height = content_height;
|
|
57
|
+
}
|
|
58
|
+
});
|
|
46
59
|
|
|
47
|
-
let mut state = ResizeObserverState {
|
|
48
|
-
elem: elem.clone(),
|
|
49
|
-
renderer: renderer.clone(),
|
|
50
|
-
session: session.clone(),
|
|
51
|
-
presentation: presentation.clone(),
|
|
52
|
-
width: elem.offset_width(),
|
|
53
|
-
height: elem.offset_height(),
|
|
54
|
-
on_resize,
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
let _callback = Closure::new(move |xs: js_sys::Array| state.on_resize(&xs));
|
|
58
60
|
let observer = ResizeObserver::new(_callback.as_ref().unchecked_ref::<js_sys::Function>());
|
|
59
61
|
observer.observe(elem);
|
|
60
62
|
Self {
|
|
61
63
|
elem: elem.clone(),
|
|
62
|
-
_callback,
|
|
63
64
|
observer,
|
|
65
|
+
_callback,
|
|
64
66
|
}
|
|
65
67
|
}
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
impl Drop for
|
|
70
|
+
impl Drop for SizeObserver {
|
|
69
71
|
fn drop(&mut self) {
|
|
70
72
|
self.observer.unobserve(&self.elem);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
session: Session,
|
|
79
|
-
presentation: Presentation,
|
|
80
|
-
width: i32,
|
|
81
|
-
height: i32,
|
|
82
|
-
on_resize: Callback<()>,
|
|
83
|
-
}
|
|
76
|
+
/// The host element's `ResizeObserver`: fans a host box change out to every
|
|
77
|
+
/// panel — a resize for drawn panels, a deferred first render for loaded
|
|
78
|
+
/// panels whose first draw was discarded while invisible.
|
|
79
|
+
pub struct ResizeObserverHandle(#[allow(dead_code)] SizeObserver);
|
|
84
80
|
|
|
85
|
-
impl
|
|
86
|
-
fn
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.
|
|
81
|
+
impl ResizeObserverHandle {
|
|
82
|
+
pub fn new(
|
|
83
|
+
elem: &HtmlElement,
|
|
84
|
+
workspace: &Workspace,
|
|
85
|
+
presentation: &Presentation,
|
|
86
|
+
root: &Root<PerspectiveViewer>,
|
|
87
|
+
) -> Self {
|
|
88
|
+
let on_resize = root
|
|
89
|
+
.borrow()
|
|
90
|
+
.as_ref()
|
|
91
|
+
.unwrap()
|
|
92
|
+
.callback(|()| PerspectiveViewerMsg::Resize);
|
|
93
|
+
|
|
94
|
+
clone!(workspace, presentation);
|
|
95
|
+
Self(SizeObserver::new(elem, move || {
|
|
96
|
+
clone!(workspace, presentation, on_resize);
|
|
97
|
+
ApiFuture::spawn_throttled(async move {
|
|
98
|
+
let mut plans = Vec::new();
|
|
99
|
+
for panel in workspace.panels() {
|
|
100
|
+
// Skip only a plugin that HAS DRAWN but is currently
|
|
101
|
+
// hidden (an unselected stacked tab → null
|
|
102
|
+
// `offset_parent`); it renders when revealed.
|
|
103
|
+
// `is_plugin_activated` is an explicit has-drawn flag,
|
|
104
|
+
// so an eagerly-MOUNTED but never-drawn plugin (its
|
|
105
|
+
// deferred first render was discarded by the
|
|
106
|
+
// `is_visible` gate) falls through to the
|
|
107
|
+
// `needs_render` branch below, which owes it a draw.
|
|
108
|
+
if panel.renderer.is_plugin_activated()?
|
|
109
|
+
&& let Some(plugin) = panel.renderer.active_plugin()
|
|
110
|
+
&& plugin
|
|
111
|
+
.unchecked_ref::<HtmlElement>()
|
|
112
|
+
.offset_parent()
|
|
113
|
+
.is_none()
|
|
114
|
+
{
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
114
117
|
|
|
118
|
+
let needs_render = !panel.renderer.is_plugin_activated()?
|
|
119
|
+
&& matches!(panel.session.has_table(), Some(TableLoadState::Loaded));
|
|
120
|
+
|
|
121
|
+
plans.push((panel, needs_render));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// For first mounts below: "stamp before restyle" — the
|
|
125
|
+
// plugin captures its `--psp-*` CSS at first draw.
|
|
126
|
+
if plans.iter().any(|(_, needs_render)| *needs_render) {
|
|
127
|
+
presentation.reset_attached();
|
|
128
|
+
seed_default_themes(&presentation, &workspace).await;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
join_all(plans.into_iter().map(|(panel, needs_render)| async move {
|
|
132
|
+
// A concurrent run (e.g. the `load()` that made
|
|
133
|
+
// `needs_render` true) is harmless: this run queues
|
|
134
|
+
// behind it, reconciles `Unchanged` with no state delta
|
|
135
|
+
// and dispatches nothing — never a duplicate draw.
|
|
115
136
|
if needs_render {
|
|
116
|
-
|
|
117
|
-
super::update_and_render(
|
|
118
|
-
&state.session,
|
|
119
|
-
&state.renderer,
|
|
120
|
-
Default::default(),
|
|
121
|
-
)?
|
|
122
|
-
.await?;
|
|
137
|
+
super::just_render(&panel.session, &panel.renderer)?.await?;
|
|
123
138
|
} else {
|
|
124
|
-
|
|
139
|
+
panel.renderer.resize().await?;
|
|
125
140
|
}
|
|
126
141
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
142
|
+
ApiResult::<()>::Ok(())
|
|
143
|
+
}))
|
|
144
|
+
.await
|
|
145
|
+
.into_iter()
|
|
146
|
+
.collect::<ApiResult<Vec<_>>>()?;
|
|
131
147
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
148
|
+
on_resize.emit(());
|
|
149
|
+
Ok(())
|
|
150
|
+
});
|
|
151
|
+
}))
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/// A per-panel [`ResizeObserver`] bound to a single panel's slotted plugin
|
|
156
|
+
/// element, resizing only that panel's [`Renderer`] when its box changes.
|
|
157
|
+
pub struct PanelResizeObserverHandle(#[allow(dead_code)] SizeObserver);
|
|
158
|
+
|
|
159
|
+
impl PanelResizeObserverHandle {
|
|
160
|
+
pub fn new(elem: &HtmlElement, renderer: &Renderer) -> Self {
|
|
161
|
+
clone!(renderer);
|
|
162
|
+
Self(SizeObserver::new(elem, move || {
|
|
163
|
+
clone!(renderer);
|
|
164
|
+
ApiFuture::spawn_throttled(async move {
|
|
165
|
+
if renderer.is_plugin_activated()? {
|
|
166
|
+
renderer.resize().await?;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
Ok(())
|
|
170
|
+
});
|
|
171
|
+
}))
|
|
135
172
|
}
|
|
136
173
|
}
|