@perspective-dev/viewer 4.5.2 → 5.1.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/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/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 +41 -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 +8 -2
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +9 -0
- package/dist/esm/ts-rs/WindowAggregate.d.ts +1 -0
- package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
- package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +46 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- 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 +299 -78
- package/dist/wasm/perspective-viewer.js +544 -186
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-8a8c7d5d696b43ae/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-8a8c7d5d696b43ae/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-selector.css +1 -0
- package/src/css/column-settings-panel.css +68 -3
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +22 -1
- package/src/css/containers/context-menu.css +107 -0
- package/src/css/containers/tabs.css +2 -2
- 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 +188 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +28 -45
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +40 -82
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +11 -9
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +25 -14
- package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
- package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
- package/src/rust/components/column_settings_sidebar.rs +163 -16
- package/src/rust/components/containers/dragdrop_list.rs +73 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +181 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/editable_header.rs +2 -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 +316 -71
- package/src/rust/components/mod.rs +5 -0
- package/src/rust/components/panel_menu.rs +493 -0
- package/src/rust/components/panel_tab.rs +510 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/portal.rs +7 -0
- 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 +182 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +255 -0
- package/src/rust/components/viewer/settings.rs +434 -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 +245 -756
- package/src/rust/components/window_editor.rs +1013 -0
- 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 +24 -2
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/columns_iter_set.rs +7 -1
- 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 +463 -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 +384 -838
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +66 -9
- package/src/rust/session/replace_expression_update.rs +1 -0
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +755 -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/edit_window.rs +155 -0
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +30 -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/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +189 -17
- 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/svg/pin-icon.svg +5 -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 +5 -2
- package/src/themes/intl/de.css +7 -0
- package/src/themes/intl/es.css +7 -0
- package/src/themes/intl/fr.css +7 -0
- package/src/themes/intl/ja.css +7 -0
- package/src/themes/intl/pt.css +7 -0
- package/src/themes/intl/zh.css +7 -0
- package/src/themes/intl.css +10 -1
- 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 +16 -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 +7 -2
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +9 -0
- package/src/ts/ts-rs/WindowAggregate.ts +3 -0
- package/src/ts/ts-rs/WindowSort.ts +9 -0
- package/src/ts/ts-rs/WindowSortDir.ts +8 -0
- package/src/ts/ts-rs/WindowSpec.ts +46 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- 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-8a8c7d5d696b43ae/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-8a8c7d5d696b43ae/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-8a8c7d5d696b43ae/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-8a8c7d5d696b43ae/inline5.js} +0 -0
|
@@ -0,0 +1,224 @@
|
|
|
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::rendered` reconcile: attach the layout listeners once, apply a
|
|
14
|
+
//! `restore`-staged layout tree, and `insertPanel`/`removePanel` cells
|
|
15
|
+
//! against `panel_ids`. Purely STRUCTURAL — it never touches plugin
|
|
16
|
+
//! rendering or paint-affecting plugin attributes (those belong to locked
|
|
17
|
+
//! draw dispatches; see the note at the end of `reconcile`).
|
|
18
|
+
|
|
19
|
+
use perspective_js::utils::JsValueSerdeExt;
|
|
20
|
+
use wasm_bindgen::prelude::*;
|
|
21
|
+
use yew::prelude::*;
|
|
22
|
+
|
|
23
|
+
use super::MainPanel;
|
|
24
|
+
use crate::js::RegularLayout;
|
|
25
|
+
|
|
26
|
+
/// Layout interaction tuning constants, applied via
|
|
27
|
+
/// [`RegularLayout::restore_physics`] when the layout element mounts.
|
|
28
|
+
#[derive(serde::Serialize)]
|
|
29
|
+
#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
|
|
30
|
+
struct LayoutPhysics {
|
|
31
|
+
grid_divider_size: f64,
|
|
32
|
+
split_edge_tolerance: f64,
|
|
33
|
+
split_root_edge_tolerance: f64,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const LAYOUT_PHYSICS: LayoutPhysics = LayoutPhysics {
|
|
37
|
+
grid_divider_size: 6.0,
|
|
38
|
+
split_edge_tolerance: 0.33,
|
|
39
|
+
split_root_edge_tolerance: 0.1,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
impl MainPanel {
|
|
43
|
+
/// Size each STAGED panel's hidden wrapper (`.psp-staging` — see
|
|
44
|
+
/// `MainPanel::render` and `Workspace::stage_panel`) to its PREDICTED
|
|
45
|
+
/// post-insert cell: an equal share of the layout box's width (the
|
|
46
|
+
/// reconcile insert splits the root horizontally with equal
|
|
47
|
+
/// redistribution) minus the frame-chrome fallback the presize sweep
|
|
48
|
+
/// uses. Prediction only — the staged first draw renders at these
|
|
49
|
+
/// dimensions, and any delta from the real committed cell is
|
|
50
|
+
/// reconciled by the post-commit reactive resize (free when exact,
|
|
51
|
+
/// via the charts transport's dims-unchanged guard). Imperative
|
|
52
|
+
/// because it MEASURES the committed layout box, which a `view()`
|
|
53
|
+
/// style prop cannot.
|
|
54
|
+
pub(super) fn size_staging_wrappers(&self) {
|
|
55
|
+
let Some(root) = self.main_panel_ref.cast::<web_sys::Element>() else {
|
|
56
|
+
return;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
let Ok(wrappers) = root.query_selector_all(".psp-staging") else {
|
|
60
|
+
return;
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if wrappers.length() == 0 {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
let Some(layout) = self.layout_ref.cast::<web_sys::HtmlElement>() else {
|
|
68
|
+
return;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
let stage = layout.get_bounding_client_rect();
|
|
72
|
+
let shares = (self.inserted.len() + wrappers.length() as usize).max(1) as f64;
|
|
73
|
+
let (chrome_w, chrome_h) = crate::tasks::CHROME_FALLBACK;
|
|
74
|
+
let width = (stage.width() / shares - chrome_w).max(0.0);
|
|
75
|
+
let height = (stage.height() - chrome_h).max(0.0);
|
|
76
|
+
for i in 0..wrappers.length() {
|
|
77
|
+
if let Some(node) = wrappers.get(i)
|
|
78
|
+
&& let Some(el) = node.dyn_ref::<web_sys::HtmlElement>()
|
|
79
|
+
{
|
|
80
|
+
let _ = el.style().set_property("width", &format!("{width}px"));
|
|
81
|
+
let _ = el.style().set_property("height", &format!("{height}px"));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Reconcile the `<regular-layout>` grid against `panel_ids`: `insertPanel`
|
|
87
|
+
/// newly-rendered cells (flipping each from `display:none` to a visible
|
|
88
|
+
/// grid cell) and `removePanel` cells whose panels are gone.
|
|
89
|
+
pub(super) fn reconcile(&mut self, ctx: &Context<Self>) {
|
|
90
|
+
// The `<regular-layout>` element is rendered unconditionally and so is
|
|
91
|
+
// stable for this component's lifetime — zero panels is just another
|
|
92
|
+
// count (empty insert loop; the retain below removes the last cell,
|
|
93
|
+
// collapsing the layout tree to a childless split). Do NOT reset
|
|
94
|
+
// `listener_target`/`inserted` on empty: the listeners stay attached
|
|
95
|
+
// to the persistent element.
|
|
96
|
+
let Some(el) = self.layout_ref.cast::<web_sys::HtmlElement>() else {
|
|
97
|
+
return;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Attach the close-detection + active-panel-sync listeners once per
|
|
101
|
+
// ELEMENT (the `<regular-layout>` element is keyed into a fully-keyed
|
|
102
|
+
// sibling list, so Yew reuses one instance for this MainPanel's
|
|
103
|
+
// lifetime), and configure resize physics — `GRID_DIVIDER_SIZE` is the
|
|
104
|
+
// divider hit tolerance (0 by default → not resizable). The cell-edge
|
|
105
|
+
// band it grabs must be left uncovered: each `.rl-panel` carries a
|
|
106
|
+
// `margin` (viewer.css) so pointerdowns there reach `regular-layout`
|
|
107
|
+
// instead of the frame. Comparing the ELEMENT (not a latched bool)
|
|
108
|
+
// self-heals an instance swap: a fresh element boots an EMPTY layout
|
|
109
|
+
// tree with no listeners, so everything must be re-bound and every
|
|
110
|
+
// placed panel re-inserted (see `listener_target`).
|
|
111
|
+
if self.listener_target.as_ref() != Some(&el) {
|
|
112
|
+
if self.listener_target.is_some() {
|
|
113
|
+
self.inserted.clear();
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let _ = el.add_event_listener_with_callback(
|
|
117
|
+
RegularLayout::UPDATE_EVENT,
|
|
118
|
+
self._layout_update_listener.as_ref().unchecked_ref(),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
let _ = el.add_event_listener_with_callback(
|
|
122
|
+
RegularLayout::SELECT_EVENT,
|
|
123
|
+
self._layout_select_listener.as_ref().unchecked_ref(),
|
|
124
|
+
);
|
|
125
|
+
|
|
126
|
+
let _ = el.add_event_listener_with_callback(
|
|
127
|
+
RegularLayout::BEFORE_RESIZE_EVENT,
|
|
128
|
+
self._layout_before_resize_listener.as_ref().unchecked_ref(),
|
|
129
|
+
);
|
|
130
|
+
|
|
131
|
+
if let Ok(physics) = JsValue::from_serde_ext(&LAYOUT_PHYSICS) {
|
|
132
|
+
el.unchecked_ref::<RegularLayout>()
|
|
133
|
+
.restore_physics(&physics);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
self.listener_target = Some(el.clone());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let layout: RegularLayout = el.unchecked_into();
|
|
140
|
+
let panel_ids = &ctx.props().panel_ids;
|
|
141
|
+
|
|
142
|
+
// Whole-element restore stages its saved layout tree on the Workspace
|
|
143
|
+
// (the model; regular-layout is a slave view). Apply it here, BEFORE
|
|
144
|
+
// the insert reconcile, and seed `inserted` from its panel names — so
|
|
145
|
+
// restored panels mount directly at their saved positions in ONE
|
|
146
|
+
// layout commit, never transiting the synthetic equal-split inserts
|
|
147
|
+
// below.
|
|
148
|
+
if let Some(tree) = ctx.props().workspace.take_pending_layout()
|
|
149
|
+
&& let Ok(js) = JsValue::from_serde_ext(&tree)
|
|
150
|
+
{
|
|
151
|
+
layout.restore_sync(&js);
|
|
152
|
+
for name in tree.slot_names() {
|
|
153
|
+
if !self.inserted.contains(&name) {
|
|
154
|
+
self.inserted.push(name);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Insert cells that are newly present.
|
|
160
|
+
for id in panel_ids {
|
|
161
|
+
let name = id.as_str();
|
|
162
|
+
if self.inserted.iter().any(|n| n == name) {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// STAGED panels are withheld from the layout: their first draw
|
|
167
|
+
// is completing in the hidden staging wrapper (see
|
|
168
|
+
// `Workspace::stage_panel`). The promote re-render clears the
|
|
169
|
+
// flag, and this loop then inserts the already-drawn panel.
|
|
170
|
+
if ctx.props().workspace.is_staged(id) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Already placed in the layout tree (the layout is the placement
|
|
175
|
+
// source of truth — e.g. a restored tree naming a panel this
|
|
176
|
+
// component hasn't tracked yet): record it, never re-split it.
|
|
177
|
+
if layout.contains_panel(name) {
|
|
178
|
+
self.inserted.push(name.to_owned());
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Insert at this panel's index as a split (orientation = `true` →
|
|
183
|
+
// horizontal) rather than the default (path `[]`), which would
|
|
184
|
+
// *stack* into the root tab-layout — a stack only renders its
|
|
185
|
+
// selected tab, and without frame chrome there's no tab-bar to
|
|
186
|
+
// reach the others. Splitting keeps every panel visible side-by-side.
|
|
187
|
+
let index = self.inserted.len();
|
|
188
|
+
let path = JsValue::from(js_sys::Array::of1(&JsValue::from_f64(index as f64)));
|
|
189
|
+
let _ = layout.insert_panel(name, path, JsValue::from_bool(true));
|
|
190
|
+
self.inserted.push(name.to_owned());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Remove cells whose panels are gone.
|
|
194
|
+
self.inserted.retain(|name| {
|
|
195
|
+
if panel_ids.iter().any(|id| id.as_str() == name) {
|
|
196
|
+
true
|
|
197
|
+
} else {
|
|
198
|
+
let _ = layout.remove_panel(name);
|
|
199
|
+
false
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// A removed panel can't stay "maximized" (regular-layout drops the
|
|
204
|
+
// maximize stylesheet when its panel leaves the layout).
|
|
205
|
+
if let Some(m) = &self.maximized
|
|
206
|
+
&& !panel_ids.iter().any(|id| id.as_str() == m.as_str())
|
|
207
|
+
{
|
|
208
|
+
self.maximized = None;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// NOTE: neither the plugin `theme` attribute nor the `active` class
|
|
212
|
+
// is managed here. This pass is an async render — mutating
|
|
213
|
+
// paint-affecting plugin state from it splits the change and the
|
|
214
|
+
// plugin DOM it styles across two paints (the datagrid's "wrong-row
|
|
215
|
+
// EDIT" artifact), and inferring "needs restyle" from DOM state here
|
|
216
|
+
// raced in-flight locked runs (it once captured a pre-rebuild `View`
|
|
217
|
+
// and restyled it after its deletion). Both stamps are applied by
|
|
218
|
+
// `Renderer::stamp_active`/`stamp_theme` INSIDE locked plugin
|
|
219
|
+
// dispatches, atomic with the draw ("stamp before draw"); theme
|
|
220
|
+
// CHANGES are restyled by their mutation sites (the theme-picker
|
|
221
|
+
// task, `restorePanel`, `resetThemes`, and the root's default-theme
|
|
222
|
+
// fan-out in `snapshots.rs::on_update_presentation`).
|
|
223
|
+
}
|
|
224
|
+
}
|
|
@@ -0,0 +1,385 @@
|
|
|
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::view`: the shared status bar, the `<regular-layout>` cells (one
|
|
14
|
+
//! forwarding `<slot>` wrapper per panel), the per-panel `<PanelTab>`s, and the
|
|
15
|
+
//! cursor-anchored `<PanelMenu>`.
|
|
16
|
+
|
|
17
|
+
use wasm_bindgen::prelude::*;
|
|
18
|
+
use yew::prelude::*;
|
|
19
|
+
|
|
20
|
+
use super::{MainPanel, MainPanelMsg, MainPanelProps};
|
|
21
|
+
use crate::components::panel_menu::PanelMenu;
|
|
22
|
+
use crate::components::panel_tab::PanelTab;
|
|
23
|
+
use crate::components::render_warning::RenderWarning;
|
|
24
|
+
use crate::components::status_bar::StatusBar;
|
|
25
|
+
use crate::js::RegularLayout;
|
|
26
|
+
use crate::tasks::dismiss_render_warning_callback;
|
|
27
|
+
use crate::workspace::PanelId;
|
|
28
|
+
|
|
29
|
+
/// Format a string as a CSS string literal (double-quoted, with `\` and `"`
|
|
30
|
+
/// escaped) — for use as a `--regular-layout-<id>--title` custom-property
|
|
31
|
+
/// value.
|
|
32
|
+
fn css_string(s: &str) -> String {
|
|
33
|
+
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
|
|
34
|
+
format!("\"{escaped}\"")
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
impl MainPanel {
|
|
38
|
+
pub(super) fn render(&self, ctx: &Context<Self>) -> Html {
|
|
39
|
+
let MainPanelProps {
|
|
40
|
+
presentation,
|
|
41
|
+
renderer,
|
|
42
|
+
session,
|
|
43
|
+
..
|
|
44
|
+
} = ctx.props();
|
|
45
|
+
|
|
46
|
+
let is_settings_open = ctx.props().is_settings_open;
|
|
47
|
+
let mut class = classes!();
|
|
48
|
+
if !is_settings_open {
|
|
49
|
+
class.push("settings-closed");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if ctx.props().is_title() {
|
|
53
|
+
class.push("titled");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let pointerdown = ctx.link().callback(MainPanelMsg::PointerEvent);
|
|
57
|
+
let on_dismiss_warning = dismiss_render_warning_callback(session, renderer);
|
|
58
|
+
|
|
59
|
+
// Each panel's plugin is mounted in the viewer's light DOM under its
|
|
60
|
+
// panel-id slot (see `renderer::activate`). Render one `<regular-layout>`
|
|
61
|
+
// cell per panel: a concrete `<div name=…>` wrapper (which
|
|
62
|
+
// `regular-layout` grid-places via its `::slotted([name])` rule — that
|
|
63
|
+
// only matches *direct*, non-`<slot>` children, hence the wrapper) whose
|
|
64
|
+
// inner forwarding `<slot>` projects the light-DOM plugin into the placed
|
|
65
|
+
// cell. The `<regular-layout>` element is ALWAYS rendered — at zero
|
|
66
|
+
// panels it is merely empty (its tree collapses to a childless split,
|
|
67
|
+
// and it paints nothing of its own), which keeps the stage CSS
|
|
68
|
+
// (viewer.css keys the stage background + inset bleed on the element)
|
|
69
|
+
// and the attached listeners alive across empty↔populated transitions.
|
|
70
|
+
let plugin_area = {
|
|
71
|
+
// The active panel (whose engines the status bar/settings target) is
|
|
72
|
+
// the one the active renderer is bound to.
|
|
73
|
+
let active_slot = renderer.slot_name();
|
|
74
|
+
// Panel-count chrome (`single`/`multi`, closable, draggable)
|
|
75
|
+
let placed_count = ctx.props().workspace.placed_count();
|
|
76
|
+
let cells = ctx
|
|
77
|
+
.props()
|
|
78
|
+
.panel_ids
|
|
79
|
+
.iter()
|
|
80
|
+
.filter(|id| !ctx.props().workspace.is_staged(id))
|
|
81
|
+
.map(|id| {
|
|
82
|
+
let name = id.as_str().to_owned();
|
|
83
|
+
let mut class = classes!("rl-panel");
|
|
84
|
+
if placed_count > 1 {
|
|
85
|
+
if active_slot.as_deref() == Some(name.as_str()) {
|
|
86
|
+
class.push("active");
|
|
87
|
+
}
|
|
88
|
+
} else {
|
|
89
|
+
// Lone panel: can't be closed to zero — hide the close button
|
|
90
|
+
// (see viewer.css) and skip the active outline.
|
|
91
|
+
class.push("single");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Activate this panel on pointerdown anywhere in it (body or
|
|
95
|
+
// titlebar), so *working* in a panel — not only clicking its tab
|
|
96
|
+
// — targets it with the shared settings/toolbar. regular-layout
|
|
97
|
+
// emits `regular-layout-select` on tab clicks only, which left
|
|
98
|
+
// body interaction unable to change the active panel. Tab clicks
|
|
99
|
+
// still also fire `-select`, but that dispatch is async (after
|
|
100
|
+
// `restore`) so it lands after this synchronous handler — within a
|
|
101
|
+
// stack, switching tabs still resolves to the clicked panel.
|
|
102
|
+
// Re-activating the already-active panel is a no-op upstream.
|
|
103
|
+
let on_activate = ctx.props().on_activate_panel.clone();
|
|
104
|
+
let activate_name = name.clone();
|
|
105
|
+
let onpointerdown = Callback::from(move |_: web_sys::PointerEvent| {
|
|
106
|
+
on_activate.emit(activate_name.clone());
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Right-click anywhere in the panel opens the context menu. This
|
|
110
|
+
// is handled by the imperative `contextmenu` listener on the
|
|
111
|
+
// panel container (see `_contextmenu_listener`), NOT a Yew
|
|
112
|
+
// `oncontextmenu` here: the plugin body is light-DOM attached by
|
|
113
|
+
// the renderer, so Yew's delegated handler — which walks the vdom
|
|
114
|
+
// — never matches a right-click on it, leaving the native menu.
|
|
115
|
+
|
|
116
|
+
// `<regular-layout-frame>` gives each panel a titlebar (its
|
|
117
|
+
// `slot="tab"`) and a `::part(container)`. Two forwarding `<slot>`s
|
|
118
|
+
// project our light-DOM nodes in: `name` → the plugin (into the
|
|
119
|
+
// container), and `tab-<name>` (with `slot="tab"`) → this panel's
|
|
120
|
+
// `<PanelTab>` (into the titlebar). Both targets live in the
|
|
121
|
+
// viewer's light DOM, so the slots — being in the viewer's shadow —
|
|
122
|
+
// collect them and forward them one level deeper into the frame.
|
|
123
|
+
//
|
|
124
|
+
// The frame no longer carries the theme: the per-panel theme is
|
|
125
|
+
// applied directly to the light-DOM plugin + tab (each stamped with
|
|
126
|
+
// a `theme` attribute) via the `perspective-viewer [theme="X"]`
|
|
127
|
+
// document rules, so it themes through the native cascade instead
|
|
128
|
+
// of a scraped/inlined `--psp-*` block.
|
|
129
|
+
html! {
|
|
130
|
+
<regular-layout-frame
|
|
131
|
+
name={name.clone()}
|
|
132
|
+
key={name.clone()}
|
|
133
|
+
{class}
|
|
134
|
+
{onpointerdown}
|
|
135
|
+
>
|
|
136
|
+
<slot name={name.clone()} />
|
|
137
|
+
<slot name={format!("tab-{name}")} slot="tab" />
|
|
138
|
+
</regular-layout-frame>
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Per-panel tab titles: regular-layout's tabs render their label from
|
|
143
|
+
// a `--regular-layout-<id>--title` custom property (a CSS string). We
|
|
144
|
+
// set one per titled panel on the `<regular-layout>` host; they
|
|
145
|
+
// inherit into each frame/tab's shadow.
|
|
146
|
+
let title_style = ctx
|
|
147
|
+
.props()
|
|
148
|
+
.panel_titles
|
|
149
|
+
.iter()
|
|
150
|
+
.filter_map(|(id, title)| {
|
|
151
|
+
title
|
|
152
|
+
.as_ref()
|
|
153
|
+
.map(|t| format!("--regular-layout-{id}--title:{};", css_string(t)))
|
|
154
|
+
})
|
|
155
|
+
.collect::<String>();
|
|
156
|
+
|
|
157
|
+
// One `<PanelTab>` per panel, mounted in the viewer's light DOM and
|
|
158
|
+
// forwarded into its frame's titlebar by the `tab-<id>` slot above.
|
|
159
|
+
// Rendered as siblings of `<regular-layout>` (their DOM placement is
|
|
160
|
+
// irrelevant — each portals itself onto the viewer host). `on_select`
|
|
161
|
+
// drives regular-layout's own `select` (the `regular-layout-select`
|
|
162
|
+
// listener picks up activation).
|
|
163
|
+
let viewer_elem = ctx.props().presentation.viewer_elem().clone();
|
|
164
|
+
// A lone panel can't be closed to zero, and has nowhere to be
|
|
165
|
+
// dragged — so it's neither closable nor draggable; its tab also
|
|
166
|
+
// carries the `single` panel-count class (which e.g. hides the
|
|
167
|
+
// caret).
|
|
168
|
+
let closable = placed_count > 1;
|
|
169
|
+
let draggable = closable;
|
|
170
|
+
let single = !closable;
|
|
171
|
+
let on_select = {
|
|
172
|
+
let layout_ref = self.layout_ref.clone();
|
|
173
|
+
Callback::from(move |id: String| {
|
|
174
|
+
if let Some(el) = layout_ref.cast::<web_sys::HtmlElement>() {
|
|
175
|
+
let _ = el.unchecked_ref::<RegularLayout>().select(&id);
|
|
176
|
+
}
|
|
177
|
+
})
|
|
178
|
+
};
|
|
179
|
+
// Close routes through the root `ClosePanel` (the same channel the
|
|
180
|
+
// context-menu "Close" uses), NOT `RegularLayout::remove_panel`.
|
|
181
|
+
// Closing is an APP-initiated state change, so it must mutate the
|
|
182
|
+
// `Workspace` (the source of truth) FIRST and dispose the panel
|
|
183
|
+
// synchronously; `MainPanel`'s reconcile then removes the cell from
|
|
184
|
+
// `regular-layout` on the re-render. Driving the layout first — and
|
|
185
|
+
// discovering the close via the `regular-layout-update` DOM event —
|
|
186
|
+
// ran the layout-update resize fan-out over a still-present-but-
|
|
187
|
+
// doomed panel, spawning an (uncancellable) draw that then raced its
|
|
188
|
+
// disposal ("restore() called before load()").
|
|
189
|
+
let on_close = ctx.props().on_close_panel.clone();
|
|
190
|
+
// Commit an in-tab title edit to that panel's own session — the same
|
|
191
|
+
// `title_changed` → `TitlesChanged` refresh loop that used to be
|
|
192
|
+
// driven by the status bar re-renders the tab with the new title.
|
|
193
|
+
let on_rename = {
|
|
194
|
+
let workspace = ctx.props().workspace.clone();
|
|
195
|
+
Callback::from(move |(id, title): (String, Option<String>)| {
|
|
196
|
+
if let Some(panel) = workspace.panel(&PanelId::from(id.as_str())) {
|
|
197
|
+
panel.session.set_title(title);
|
|
198
|
+
}
|
|
199
|
+
})
|
|
200
|
+
};
|
|
201
|
+
// The tab's open-settings button (the ONLY open-settings
|
|
202
|
+
// affordance — the status bar's button is gone): select the panel
|
|
203
|
+
// in the layout, activate it, then toggle the sidebar open. All
|
|
204
|
+
// three are synchronous root-message sends, so activation lands
|
|
205
|
+
// before the toggle and the sidebar binds the clicked panel's
|
|
206
|
+
// engines.
|
|
207
|
+
let on_open_settings = {
|
|
208
|
+
let on_select = on_select.clone();
|
|
209
|
+
let on_activate = ctx.props().on_activate_panel.clone();
|
|
210
|
+
let on_settings = ctx.props().on_settings.clone();
|
|
211
|
+
Callback::from(move |id: String| {
|
|
212
|
+
on_select.emit(id.clone());
|
|
213
|
+
on_activate.emit(id);
|
|
214
|
+
on_settings.emit(());
|
|
215
|
+
})
|
|
216
|
+
};
|
|
217
|
+
let tabs = ctx
|
|
218
|
+
.props()
|
|
219
|
+
.panel_ids
|
|
220
|
+
.iter()
|
|
221
|
+
.filter(|id| !ctx.props().workspace.is_staged(id))
|
|
222
|
+
.map(|id| {
|
|
223
|
+
let name = id.as_str().to_owned();
|
|
224
|
+
let title = ctx
|
|
225
|
+
.props()
|
|
226
|
+
.panel_titles
|
|
227
|
+
.iter()
|
|
228
|
+
.find(|(pid, _)| pid == &name)
|
|
229
|
+
.and_then(|(_, t)| t.clone());
|
|
230
|
+
let active = active_slot.as_deref() == Some(name.as_str());
|
|
231
|
+
let visible = !self.hidden_tabs.contains(name.as_str());
|
|
232
|
+
let is_master = ctx.props().panel_masters.contains(id);
|
|
233
|
+
let theme = ctx.props().effective_panel_theme(&name);
|
|
234
|
+
|
|
235
|
+
html! {
|
|
236
|
+
<PanelTab
|
|
237
|
+
key={name.clone()}
|
|
238
|
+
viewer={viewer_elem.clone()}
|
|
239
|
+
panel_id={name.clone()}
|
|
240
|
+
{title}
|
|
241
|
+
{theme}
|
|
242
|
+
{active}
|
|
243
|
+
{visible}
|
|
244
|
+
{is_master}
|
|
245
|
+
{single}
|
|
246
|
+
{closable}
|
|
247
|
+
{is_settings_open}
|
|
248
|
+
{draggable}
|
|
249
|
+
on_select={on_select.clone()}
|
|
250
|
+
on_close={on_close.clone()}
|
|
251
|
+
on_open_settings={on_open_settings.clone()}
|
|
252
|
+
on_context_menu={ctx.link().callback(|(id, x, y): (String, f64, f64)| MainPanelMsg::ContextMenu(Some(id), x, y))}
|
|
253
|
+
on_rename={on_rename.clone()}
|
|
254
|
+
/>
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
.collect::<Vec<_>>();
|
|
258
|
+
|
|
259
|
+
// STAGED panels: the plugin `<slot>` is delivered OUTSIDE the
|
|
260
|
+
// layout into a hidden wrapper, sized to the predicted
|
|
261
|
+
// post-insert cell by `size_staging_wrappers`. `visibility:
|
|
262
|
+
// hidden` (not `display: none`) so the plugin keeps a real box
|
|
263
|
+
// and a non-null `offsetParent` — first draws, redraw
|
|
264
|
+
// subscriptions and every observer (the host-level
|
|
265
|
+
// `AutoPauseHandle`/`ResizeObserverHandle` gates, the drag
|
|
266
|
+
// `ResizeObserver`s) see it exactly as they would a visible
|
|
267
|
+
// panel. No tab is rendered while staged.
|
|
268
|
+
let staging = ctx
|
|
269
|
+
.props()
|
|
270
|
+
.panel_ids
|
|
271
|
+
.iter()
|
|
272
|
+
.filter(|id| ctx.props().workspace.is_staged(id))
|
|
273
|
+
.map(|id| {
|
|
274
|
+
let name = id.as_str().to_owned();
|
|
275
|
+
html! {
|
|
276
|
+
<div
|
|
277
|
+
class="psp-staging"
|
|
278
|
+
data-panel={name.clone()}
|
|
279
|
+
key={format!("staging-{name}")}
|
|
280
|
+
>
|
|
281
|
+
<slot {name} />
|
|
282
|
+
</div>
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// ONE flat FULLY-KEYED list: `<regular-layout>` + tabs + staging
|
|
287
|
+
// wrappers. Keying every sibling (the layout element included)
|
|
288
|
+
// routes Yew's list diff through `apply_keyed`, which matches by
|
|
289
|
+
// key — the layout element is REUSED across renders regardless of
|
|
290
|
+
// how the tab/staging population shifts around it. The prior
|
|
291
|
+
// shape (`<regular-layout>` + nested `{tabs}`/`{staging}` lists,
|
|
292
|
+
// layout unkeyed) took `apply_unkeyed`, whose back-to-front
|
|
293
|
+
// positional pairing REPLACED the layout element whenever the
|
|
294
|
+
// trailing lists changed length — orphaning its committed tree
|
|
295
|
+
// and attached listeners, and silently re-booting the layout
|
|
296
|
+
// from `EMPTY_PANEL` (see `listener_target`).
|
|
297
|
+
std::iter::once(html! {
|
|
298
|
+
<regular-layout
|
|
299
|
+
key="__regular_layout__"
|
|
300
|
+
ref={self.layout_ref.clone()}
|
|
301
|
+
style={title_style}
|
|
302
|
+
>
|
|
303
|
+
{ for cells }
|
|
304
|
+
</regular-layout>
|
|
305
|
+
})
|
|
306
|
+
.chain(tabs)
|
|
307
|
+
.chain(staging)
|
|
308
|
+
.collect::<Html>()
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
// The cursor-anchored panel command menu — a body-mounted
|
|
312
|
+
// `PortalModal` (like the Export/Copy pickers it spawns), so its
|
|
313
|
+
// placement in this subtree is irrelevant. Themed by the TARGET
|
|
314
|
+
// panel's effective theme — its own, else the registry default —
|
|
315
|
+
// not the active/host theme (the target-less stage menu also takes
|
|
316
|
+
// the registry default). Maximize/Restore are handled locally;
|
|
317
|
+
// other commands forward to the root.
|
|
318
|
+
let panel_menu = self
|
|
319
|
+
.context_menu
|
|
320
|
+
.as_ref()
|
|
321
|
+
.map(|(x, y, id)| {
|
|
322
|
+
let theme = match id {
|
|
323
|
+
Some(id) => ctx.props().effective_panel_theme(id),
|
|
324
|
+
None => ctx
|
|
325
|
+
.props()
|
|
326
|
+
.presentation_props
|
|
327
|
+
.available_themes
|
|
328
|
+
.first()
|
|
329
|
+
.cloned(),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
let maximized = id
|
|
333
|
+
.as_deref()
|
|
334
|
+
.is_some_and(|id| self.maximized.as_deref() == Some(id));
|
|
335
|
+
|
|
336
|
+
html! {
|
|
337
|
+
<PanelMenu
|
|
338
|
+
x={*x}
|
|
339
|
+
y={*y}
|
|
340
|
+
panel_id={id.clone()}
|
|
341
|
+
workspace={ctx.props().workspace.clone()}
|
|
342
|
+
presentation={ctx.props().presentation.clone()}
|
|
343
|
+
{theme}
|
|
344
|
+
{maximized}
|
|
345
|
+
on_command={ctx.link().callback(MainPanelMsg::Command)}
|
|
346
|
+
on_close={ctx.link().callback(|_| MainPanelMsg::CloseContextMenu)}
|
|
347
|
+
/>
|
|
348
|
+
}
|
|
349
|
+
})
|
|
350
|
+
.unwrap_or_default();
|
|
351
|
+
|
|
352
|
+
html! {
|
|
353
|
+
<div id="main_column">
|
|
354
|
+
<StatusBar
|
|
355
|
+
id="status_bar"
|
|
356
|
+
on_reset={ctx.props().on_reset.clone()}
|
|
357
|
+
session_props={ctx.props().session_props.clone()}
|
|
358
|
+
presentation_props={ctx.props().presentation_props.clone()}
|
|
359
|
+
is_settings_open={ctx.props().is_settings_open}
|
|
360
|
+
update_count={ctx.props().update_count}
|
|
361
|
+
global_filters={ctx.props().global_filters.clone()}
|
|
362
|
+
on_remove_global_filter={ctx.props().on_remove_global_filter.clone()}
|
|
363
|
+
on_clear_global_filters={ctx.props().on_clear_global_filters.clone()}
|
|
364
|
+
{presentation}
|
|
365
|
+
{renderer}
|
|
366
|
+
{session}
|
|
367
|
+
workspace={ctx.props().workspace.clone()}
|
|
368
|
+
/>
|
|
369
|
+
<div
|
|
370
|
+
id="main_panel_container"
|
|
371
|
+
ref={self.main_panel_ref.clone()}
|
|
372
|
+
{class}
|
|
373
|
+
onpointerdown={pointerdown}
|
|
374
|
+
>
|
|
375
|
+
<RenderWarning
|
|
376
|
+
on_dismiss={on_dismiss_warning}
|
|
377
|
+
dimensions={ctx.props().renderer_props.render_limits}
|
|
378
|
+
/>
|
|
379
|
+
{ plugin_area }
|
|
380
|
+
</div>
|
|
381
|
+
{ panel_menu }
|
|
382
|
+
</div>
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
}
|