@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,186 @@
|
|
|
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
|
+
//! `MainPanel::stamp_frame_themes`: mirror each panel's effective theme
|
|
14
|
+
//! *background* onto its `<regular-layout-frame>`.
|
|
15
|
+
//!
|
|
16
|
+
//! The frames live in the viewer's shadow DOM, so the document theme rules
|
|
17
|
+
//! (`perspective-viewer [theme="X"]`) can never match them — the
|
|
18
|
+
//! `--psp--background-color` their `::part(container)` background resolves
|
|
19
|
+
//! (viewer.css) inherits down from the viewer host, i.e. always the *host*
|
|
20
|
+
//! theme. A frame is normally covered by its slotted plugin (which paints its
|
|
21
|
+
//! own per-panel themed background), but while the plugin is missing — not yet
|
|
22
|
+
//! mounted, first draw pending, or torn down — the host-theme container shows
|
|
23
|
+
//! through a panel themed differently, as a visible artifact.
|
|
24
|
+
//!
|
|
25
|
+
//! There is no native cascade channel from the light-DOM plugin to the frame
|
|
26
|
+
//! (siblings in the flattened tree), so the value is mirrored imperatively:
|
|
27
|
+
//! each panel's *plugin element* — light-DOM, slotted under the panel's slot,
|
|
28
|
+
//! mounted eagerly at panel creation (`create_panel_model` /
|
|
29
|
+
//! `Renderer::mount_active_plugin`) — is where the document theme rules
|
|
30
|
+
//! actually resolve, so its computed `--psp--background-color` is copied onto
|
|
31
|
+
//! the frame's inline style, where the frame's shadow parts inherit it. The
|
|
32
|
+
//! plugin is the ONLY sound source element:
|
|
33
|
+
//!
|
|
34
|
+
//! - An owned hidden probe child of the viewer does NOT work: an *unslotted*
|
|
35
|
+
//! light-DOM child of a shadow host is outside the flattened tree, and
|
|
36
|
+
//! `getComputedStyle` returns all-empty for it even though the document rules
|
|
37
|
+
//! match it syntactically (selector matching is DOM-tree; style computation
|
|
38
|
+
//! is flat-tree).
|
|
39
|
+
//! - The `<perspective-viewer-tab>` is slotted, but stamps its own `theme` attr
|
|
40
|
+
//! in `PanelTab::rendered` — an unordered sibling lifecycle relative to this
|
|
41
|
+
//! pass (the theme-stamp-lag class of bug).
|
|
42
|
+
//! - The plugin's `theme` attr, by contrast, is stamped *synchronously at the
|
|
43
|
+
//! mutation sites* (`update_theme`, `restorePanel`, …) before the events that
|
|
44
|
+
//! schedule this render, so it is current when this pass reads it.
|
|
45
|
+
//!
|
|
46
|
+
//! A read is trusted ONLY when the plugin's stamped attr equals the theme
|
|
47
|
+
//! being mirrored: a freshly-created panel's plugin is mounted *unstamped*
|
|
48
|
+
//! (the attr first lands inside its first locked dispatch), and reading it
|
|
49
|
+
//! early would silently mirror the HOST theme's values. Trusted reads seed
|
|
50
|
+
//! [`MainPanel::theme_backgrounds`], the fallback for frames with no readable
|
|
51
|
+
//! plugin (an unregistered plugin name stays lazily unmounted; plugin-switch
|
|
52
|
+
//! and teardown transients). A frame with neither leaves the pass gate
|
|
53
|
+
//! unlatched, so it retries on subsequent renders until the dispatch stamp
|
|
54
|
+
//! lands (the render that follows that dispatch's `update_count` bump).
|
|
55
|
+
//!
|
|
56
|
+
//! Unlike the plugin `theme`/`active` stamps (see the NOTE in [`reconcile`]),
|
|
57
|
+
//! the frame is pure viewer chrome — no plugin dispatch reads it — so
|
|
58
|
+
//! stamping it from this async `rendered` pass cannot split a plugin draw
|
|
59
|
+
//! across paints, and it needs no lock.
|
|
60
|
+
|
|
61
|
+
use wasm_bindgen::prelude::*;
|
|
62
|
+
use yew::prelude::*;
|
|
63
|
+
|
|
64
|
+
use super::MainPanel;
|
|
65
|
+
use crate::utils::PtrEqRc;
|
|
66
|
+
use crate::workspace::PanelId;
|
|
67
|
+
|
|
68
|
+
/// The custom property mirrored onto each frame: what viewer.css's
|
|
69
|
+
/// `.rl-panel::part(container)` background resolves.
|
|
70
|
+
const BACKGROUND_VAR: &str = "--psp--background-color";
|
|
71
|
+
|
|
72
|
+
/// The inputs the frame backgrounds were last computed from; re-mirroring is
|
|
73
|
+
/// skipped while these are unchanged, so the forced style recalcs of
|
|
74
|
+
/// `getComputedStyle` don't run on every render (e.g. the per-update
|
|
75
|
+
/// `update_count` renders of a streaming table). `available_themes` is part of
|
|
76
|
+
/// the key because theme CSS registering late changes the *computed* value
|
|
77
|
+
/// under an unchanged theme name.
|
|
78
|
+
pub(super) type FrameThemeSnapshot = (
|
|
79
|
+
Vec<PanelId>,
|
|
80
|
+
Vec<(String, Option<String>)>,
|
|
81
|
+
PtrEqRc<Vec<String>>,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
impl MainPanel {
|
|
85
|
+
/// Mirror each panel's effective theme background (its own theme, else the
|
|
86
|
+
/// registry default — the same fallback the tab/menu use) onto its
|
|
87
|
+
/// `<regular-layout-frame>`'s inline `--psp--background-color`, read off
|
|
88
|
+
/// the panel's stamped plugin element (else the theme-keyed cache of prior
|
|
89
|
+
/// reads). A panel with no resolvable theme has the property removed,
|
|
90
|
+
/// falling back to the inherited host-theme value.
|
|
91
|
+
pub(super) fn stamp_frame_themes(&mut self, ctx: &Context<Self>) {
|
|
92
|
+
let Some(layout) = self.layout_ref.cast::<web_sys::Element>() else {
|
|
93
|
+
return;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
let props = ctx.props();
|
|
97
|
+
let snapshot: FrameThemeSnapshot = (
|
|
98
|
+
props.panel_ids.clone(),
|
|
99
|
+
props.panel_themes.clone(),
|
|
100
|
+
props.presentation_props.available_themes.clone(),
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if self.stamped_frame_themes.as_ref() == Some(&snapshot) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// A theme-registry change can re-value an unchanged theme name, so
|
|
108
|
+
// cached reads from the previous registry are unsound.
|
|
109
|
+
if let Some((_, _, prev_themes)) = &self.stamped_frame_themes
|
|
110
|
+
&& prev_themes != &snapshot.2
|
|
111
|
+
{
|
|
112
|
+
self.theme_backgrounds.clear();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let viewer = props.presentation.viewer_elem().clone();
|
|
116
|
+
let mut complete = true;
|
|
117
|
+
let children = layout.children();
|
|
118
|
+
for i in 0..children.length() {
|
|
119
|
+
let Some(frame) = children.item(i) else {
|
|
120
|
+
continue;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if !frame
|
|
124
|
+
.tag_name()
|
|
125
|
+
.eq_ignore_ascii_case("regular-layout-frame")
|
|
126
|
+
{
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
let Some(name) = frame.get_attribute("name") else {
|
|
131
|
+
continue;
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
let theme = props.effective_panel_theme(&name);
|
|
135
|
+
|
|
136
|
+
let background = theme.as_ref().and_then(|theme| {
|
|
137
|
+
let fresh = read_plugin_background(&viewer, &name, theme);
|
|
138
|
+
if let Some(color) = &fresh {
|
|
139
|
+
self.theme_backgrounds.insert(theme.clone(), color.clone());
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
let value = fresh.or_else(|| self.theme_backgrounds.get(theme).cloned());
|
|
143
|
+
complete &= value.is_some();
|
|
144
|
+
value
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
let style = frame.unchecked_ref::<web_sys::HtmlElement>().style();
|
|
148
|
+
match background {
|
|
149
|
+
Some(color) => {
|
|
150
|
+
let _ = style.set_property(BACKGROUND_VAR, &color);
|
|
151
|
+
},
|
|
152
|
+
None => {
|
|
153
|
+
let _ = style.remove_property(BACKGROUND_VAR);
|
|
154
|
+
},
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Latch only a fully-resolved pass; an unresolved frame (unmounted or
|
|
159
|
+
// not-yet-stamped plugin) retries each render until its dispatch
|
|
160
|
+
// stamp lands.
|
|
161
|
+
self.stamped_frame_themes = complete.then_some(snapshot);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// Read the computed [`BACKGROUND_VAR`] off `slot`'s plugin element — the
|
|
166
|
+
/// viewer light-DOM child mounted under exactly that slot name (the tab and
|
|
167
|
+
/// toolbar use `tab-`/`statusbar-extra-` prefixed slots). Requires the
|
|
168
|
+
/// plugin's `theme` attr to already equal `theme` — during a plugin switch
|
|
169
|
+
/// two elements briefly share the slot, and a freshly-mounted plugin is not
|
|
170
|
+
/// yet stamped; both are disambiguated by the attr check.
|
|
171
|
+
fn read_plugin_background(viewer: &web_sys::Element, slot: &str, theme: &str) -> Option<String> {
|
|
172
|
+
let children = viewer.children();
|
|
173
|
+
let plugin = (0..children.length())
|
|
174
|
+
.filter_map(|i| children.item(i))
|
|
175
|
+
.filter(|el| el.get_attribute("slot").as_deref() == Some(slot))
|
|
176
|
+
.find(|el| el.get_attribute("theme").as_deref() == Some(theme))?;
|
|
177
|
+
|
|
178
|
+
let value = web_sys::window()?
|
|
179
|
+
.get_computed_style(&plugin)
|
|
180
|
+
.ok()??
|
|
181
|
+
.get_property_value(BACKGROUND_VAR)
|
|
182
|
+
.ok()?;
|
|
183
|
+
|
|
184
|
+
let value = value.trim();
|
|
185
|
+
(!value.is_empty()).then(|| value.to_owned())
|
|
186
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use crate::components::panel_menu::PanelCommand;
|
|
14
|
+
|
|
15
|
+
#[derive(Debug)]
|
|
16
|
+
pub enum MainPanelMsg {
|
|
17
|
+
PointerEvent(web_sys::PointerEvent),
|
|
18
|
+
|
|
19
|
+
/// The `Workspace` staged-panel set changed (`staged_changed` PubSub —
|
|
20
|
+
/// see `Workspace::stage_panel`): re-render so cells/tabs/wrappers
|
|
21
|
+
/// reflect it and `reconcile` inserts a promoted panel. Subscribed on
|
|
22
|
+
/// MainPanel's OWN scope — the root's `LayoutChanged` delivery proved
|
|
23
|
+
/// unreliable from element-API task contexts (promoted panels stranded
|
|
24
|
+
/// in their hidden wrappers), while this component's own messages
|
|
25
|
+
/// deliver in every observed context.
|
|
26
|
+
StagedChanged,
|
|
27
|
+
|
|
28
|
+
/// The `<regular-layout>` tree changed (fired by its
|
|
29
|
+
/// `regular-layout-update` event). Used to detect panels removed from
|
|
30
|
+
/// the layout (e.g. a frame's close button) so they can be disposed.
|
|
31
|
+
LayoutUpdated,
|
|
32
|
+
|
|
33
|
+
/// A tab was selected (fired by `regular-layout-select`, `detail.name`);
|
|
34
|
+
/// the selected panel becomes the active panel. This is
|
|
35
|
+
/// regular-layout's own selection signal, so it resolves the correct
|
|
36
|
+
/// panel within a stack.
|
|
37
|
+
TabSelected(String),
|
|
38
|
+
|
|
39
|
+
/// The layout is about to resize its cells (fired by the cancelable
|
|
40
|
+
/// `regular-layout-before-resize`). Carries the event so its
|
|
41
|
+
/// `PresizeDetail` can pre-size each panel's plugin to its target box
|
|
42
|
+
/// before the layout commits — avoiding the post-commit resize
|
|
43
|
+
/// shear/clip. The handler (`onbeforeresize` closure) has already
|
|
44
|
+
/// `preventDefault()`-ed it.
|
|
45
|
+
BeforeResize(web_sys::Event),
|
|
46
|
+
|
|
47
|
+
/// A right-click opened the panel context menu (target panel id, client
|
|
48
|
+
/// x, y). Fired by the imperative `contextmenu` listener on the panel
|
|
49
|
+
/// container — see `_contextmenu_listener` — and by each `PanelTab`.
|
|
50
|
+
/// `Some(id)` activates that panel and opens its
|
|
51
|
+
/// [`PanelMenu`](crate::components::panel_menu::PanelMenu) at the
|
|
52
|
+
/// cursor; `None` is the EMPTY stage (zero panels) — a target-less menu
|
|
53
|
+
/// offering only "New", whose items create the first panel.
|
|
54
|
+
ContextMenu(Option<String>, f64, f64),
|
|
55
|
+
|
|
56
|
+
/// Dismiss the panel context menu (the menu session ended).
|
|
57
|
+
CloseContextMenu,
|
|
58
|
+
|
|
59
|
+
/// The open context menu selected a command. Maximize/Restore act on the
|
|
60
|
+
/// layout here; the rest forward to `on_panel_command`.
|
|
61
|
+
Command(PanelCommand),
|
|
62
|
+
}
|
|
@@ -0,0 +1,220 @@
|
|
|
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
|
+
//! The `MainPanelMsg::BeforeResize` handler: pre-size every panel's plugin to
|
|
14
|
+
//! its *target* cell box before the `<regular-layout>` commit, so content is
|
|
15
|
+
//! ready ahead of the geometry change (no post-commit shear/clip).
|
|
16
|
+
|
|
17
|
+
use std::collections::HashMap;
|
|
18
|
+
|
|
19
|
+
use futures::future::join_all;
|
|
20
|
+
use perspective_client::utils::PerspectiveResultExt;
|
|
21
|
+
use perspective_js::utils::JsValueSerdeExt;
|
|
22
|
+
use wasm_bindgen::prelude::*;
|
|
23
|
+
use yew::prelude::*;
|
|
24
|
+
|
|
25
|
+
use super::MainPanel;
|
|
26
|
+
use crate::ApiFuture;
|
|
27
|
+
use crate::js::{PresizeDetail, RegularLayout};
|
|
28
|
+
use crate::renderer::Renderer;
|
|
29
|
+
use crate::tasks::PanelResizeObserverHandle;
|
|
30
|
+
use crate::workspace::PanelId;
|
|
31
|
+
|
|
32
|
+
fn set_tab_presize(tab: &web_sys::HtmlElement, dx: f64, dy: f64) {
|
|
33
|
+
if dx.abs() > crate::renderer::SUBPIXEL_EPSILON || dy.abs() > crate::renderer::SUBPIXEL_EPSILON
|
|
34
|
+
{
|
|
35
|
+
let _ = tab
|
|
36
|
+
.style()
|
|
37
|
+
.set_property("transform", &format!("translate({dx}px, {dy}px)"));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Remove the inline presize translate applied by [`set_tab_presize`] (a no-op
|
|
42
|
+
/// when none was set). Cleared in the SAME synchronous run as the plugin clear
|
|
43
|
+
/// and `resumeResize`, so the tab and its plugin land at the committed grid
|
|
44
|
+
/// position together with no intermediate paint.
|
|
45
|
+
fn clear_tab_presize(tab: &web_sys::HtmlElement) {
|
|
46
|
+
let _ = tab.style().remove_property("transform");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[derive(serde::Deserialize)]
|
|
50
|
+
struct PresizePath {
|
|
51
|
+
#[serde(with = "serde_wasm_bindgen::preserve")]
|
|
52
|
+
view_window: JsValue,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// The current screen origin of a frame's grid TRACK (its margin box — the
|
|
56
|
+
/// coordinate space `real_coordinates` reports), measured live from the frame.
|
|
57
|
+
/// `target_track − current_track` is the presize translate delta: the plugin's
|
|
58
|
+
/// offset *within* its track is constant across a layout transition, so the
|
|
59
|
+
/// track delta is exactly the plugin delta (see `Renderer::presize_with_box`).
|
|
60
|
+
fn frame_track_origin(frame: &web_sys::Element) -> Option<(f64, f64)> {
|
|
61
|
+
let style = web_sys::window()?.get_computed_style(frame).ok()??;
|
|
62
|
+
let px = |prop: &str| {
|
|
63
|
+
style
|
|
64
|
+
.get_property_value(prop)
|
|
65
|
+
.ok()
|
|
66
|
+
.and_then(|v| v.trim().trim_end_matches("px").parse::<f64>().ok())
|
|
67
|
+
.unwrap_or(0.0)
|
|
68
|
+
};
|
|
69
|
+
let rect = frame.get_bounding_client_rect();
|
|
70
|
+
Some((
|
|
71
|
+
rect.left() - px("margin-left"),
|
|
72
|
+
rect.top() - px("margin-top"),
|
|
73
|
+
))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
impl MainPanel {
|
|
77
|
+
pub(super) fn on_before_resize(&mut self, ctx: &Context<Self>, event: web_sys::Event) -> bool {
|
|
78
|
+
let Some(el) = self.layout_ref.cast::<web_sys::HtmlElement>() else {
|
|
79
|
+
return false;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
let layout: RegularLayout = el.unchecked_into();
|
|
83
|
+
let detail = event
|
|
84
|
+
.unchecked_ref::<web_sys::CustomEvent>()
|
|
85
|
+
.detail()
|
|
86
|
+
.unchecked_into::<PresizeDetail>();
|
|
87
|
+
|
|
88
|
+
let paths: HashMap<String, PresizePath> = detail
|
|
89
|
+
.calculate_presize_paths()
|
|
90
|
+
.into_serde_ext()
|
|
91
|
+
.unwrap_or_default();
|
|
92
|
+
|
|
93
|
+
// Bind a drag ResizeObserver on the panel being dragged: it's
|
|
94
|
+
// the one present in the layout but EXCLUDED from the presize
|
|
95
|
+
// paths. A STAGED panel is absent from the layout for a different
|
|
96
|
+
// reason than the dragged panel — never bind the per-drag observer
|
|
97
|
+
// to its staging wrapper.
|
|
98
|
+
for id in &ctx.props().panel_ids {
|
|
99
|
+
let name = id.as_str();
|
|
100
|
+
if !paths.contains_key(name)
|
|
101
|
+
&& !self.panel_resize_observers.contains_key(name)
|
|
102
|
+
&& !ctx.props().workspace.is_staged(id)
|
|
103
|
+
&& let Some(panel) = ctx.props().workspace.panel(id)
|
|
104
|
+
&& let Some(plugin) = panel.renderer.active_plugin()
|
|
105
|
+
{
|
|
106
|
+
let plugin_el = plugin.unchecked_ref::<web_sys::HtmlElement>();
|
|
107
|
+
self.panel_resize_observers.insert(
|
|
108
|
+
name.to_owned(),
|
|
109
|
+
PanelResizeObserverHandle::new(plugin_el, &panel.renderer),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
let workspace = ctx.props().workspace.clone();
|
|
115
|
+
let viewer = ctx.props().presentation.viewer_elem().clone();
|
|
116
|
+
let effect = workspace.effects().guard();
|
|
117
|
+
ApiFuture::spawn(async move {
|
|
118
|
+
let _effect = effect;
|
|
119
|
+
let mut targets: Vec<(Renderer, f64, f64, f64, f64)> = Vec::new();
|
|
120
|
+
let mut tab_targets: Vec<(web_sys::HtmlElement, f64, f64)> = Vec::new();
|
|
121
|
+
let mut last_chrome: Option<(f64, f64)> = None;
|
|
122
|
+
for (name, path) in &paths {
|
|
123
|
+
let cell: web_sys::DomRect =
|
|
124
|
+
layout.real_coordinates(&path.view_window).unchecked_into();
|
|
125
|
+
|
|
126
|
+
// Only presize panels with a drawn plugin. Critically,
|
|
127
|
+
// `insertPanel`/`removePanel` *also* fire `before-resize`
|
|
128
|
+
// (they `restore()` through the presize queue), and a
|
|
129
|
+
// just-inserted panel has no plugin yet — presizing it
|
|
130
|
+
// would stall on the draw lock and `resumeResize` would
|
|
131
|
+
// never fire, freezing the layout mid-mutation. (Mirrors
|
|
132
|
+
// the old workspace's `if (!plugin_box) continue`.)
|
|
133
|
+
if let Some(panel) = workspace.panel(&PanelId::from(name.as_str()))
|
|
134
|
+
&& panel.renderer.is_plugin_activated().unwrap_or(false)
|
|
135
|
+
&& let Some(plugin) = panel.renderer.active_plugin()
|
|
136
|
+
{
|
|
137
|
+
let plugin_el = plugin.unchecked_ref::<web_sys::Element>();
|
|
138
|
+
let frame = layout
|
|
139
|
+
.unchecked_ref::<web_sys::Element>()
|
|
140
|
+
.query_selector(&format!("regular-layout-frame[name=\"{name}\"]"))
|
|
141
|
+
.ok()
|
|
142
|
+
.flatten();
|
|
143
|
+
let chrome = frame
|
|
144
|
+
.as_ref()
|
|
145
|
+
.and_then(|frame| crate::tasks::plugin_chrome(frame, plugin_el));
|
|
146
|
+
last_chrome = chrome.or(last_chrome);
|
|
147
|
+
let (cw, ch) = last_chrome.unwrap_or(crate::tasks::CHROME_FALLBACK);
|
|
148
|
+
let width = (cell.width() - cw).max(0.0);
|
|
149
|
+
let height = (cell.height() - ch).max(0.0);
|
|
150
|
+
let (dx, dy) = frame
|
|
151
|
+
.as_ref()
|
|
152
|
+
.and_then(frame_track_origin)
|
|
153
|
+
.map(|(x, y)| (cell.left() - x, cell.top() - y))
|
|
154
|
+
.unwrap_or((0.0, 0.0));
|
|
155
|
+
targets.push((panel.renderer, dx, dy, width, height));
|
|
156
|
+
|
|
157
|
+
if let Some(tab) = viewer
|
|
158
|
+
.query_selector(&format!("perspective-viewer-tab[slot=\"tab-{name}\"]"))
|
|
159
|
+
.ok()
|
|
160
|
+
.flatten()
|
|
161
|
+
{
|
|
162
|
+
tab_targets.push((tab.unchecked_into::<web_sys::HtmlElement>(), dx, dy));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
let renderers = targets.iter().map(|(r, ..)| r.clone()).collect::<Vec<_>>();
|
|
168
|
+
let tabs = tab_targets
|
|
169
|
+
.iter()
|
|
170
|
+
.map(|(t, ..)| t.clone())
|
|
171
|
+
.collect::<Vec<_>>();
|
|
172
|
+
for (tab, dx, dy) in &tab_targets {
|
|
173
|
+
set_tab_presize(tab, *dx, *dy);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
let presents = crate::tasks::staged_presents_with_deadline(
|
|
177
|
+
workspace.effects().guard(),
|
|
178
|
+
async move {
|
|
179
|
+
join_all(
|
|
180
|
+
targets
|
|
181
|
+
.iter()
|
|
182
|
+
.map(|(r, dx, dy, w, h)| r.presize_with_box(*dx, *dy, *w, *h)),
|
|
183
|
+
)
|
|
184
|
+
.await
|
|
185
|
+
.into_iter()
|
|
186
|
+
.filter_map(|r| r.ok().flatten())
|
|
187
|
+
.collect()
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
let renderers = renderers.clone();
|
|
191
|
+
let tabs = tabs.clone();
|
|
192
|
+
move || {
|
|
193
|
+
for renderer in &renderers {
|
|
194
|
+
let _ = renderer.clear_presize();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
for tab in &tabs {
|
|
198
|
+
clear_tab_presize(tab);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
)
|
|
203
|
+
.await;
|
|
204
|
+
|
|
205
|
+
for renderer in &renderers {
|
|
206
|
+
renderer.clear_presize().unwrap_or_log();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
for tab in &tabs {
|
|
210
|
+
clear_tab_presize(tab);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
presents.reveal();
|
|
214
|
+
layout.resume_resize();
|
|
215
|
+
Ok(())
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
false
|
|
219
|
+
}
|
|
220
|
+
}
|