@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
|
@@ -10,31 +10,43 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
//! The root `<perspective-viewer>` Yew component: state, lifecycle, and the
|
|
14
|
+
//! message dispatch table. Handler bodies live in the domain modules —
|
|
15
|
+
//! [`panels`] (workspace panel lifecycle + active targeting), [`settings`]
|
|
16
|
+
//! (settings sidebar + divider presize pump), [`filters`] (master/detail
|
|
17
|
+
//! cross-filter), [`snapshots`] (value-semantic props plumbing) — with engine
|
|
18
|
+
//! wiring in [`wiring`] and `view()` in [`render`]. (The panel context menu +
|
|
19
|
+
//! pickers + maximize live in `MainPanel`/`PanelMenu`, which own the layout
|
|
20
|
+
//! element.)
|
|
21
|
+
|
|
22
|
+
mod filters;
|
|
23
|
+
mod msg;
|
|
24
|
+
mod panels;
|
|
25
|
+
mod render;
|
|
26
|
+
mod settings;
|
|
27
|
+
mod snapshots;
|
|
28
|
+
mod wiring;
|
|
29
|
+
|
|
13
30
|
use std::rc::Rc;
|
|
14
31
|
|
|
15
|
-
use futures::channel::oneshot
|
|
32
|
+
use futures::channel::oneshot::Sender;
|
|
33
|
+
use perspective_client::config::Filter;
|
|
16
34
|
use perspective_js::utils::*;
|
|
17
35
|
use wasm_bindgen::JsCast;
|
|
18
36
|
use wasm_bindgen::prelude::*;
|
|
19
|
-
use web_sys::{FocusEvent, KeyboardEvent};
|
|
20
37
|
use yew::prelude::*;
|
|
21
38
|
|
|
22
|
-
use
|
|
23
|
-
use
|
|
24
|
-
use
|
|
25
|
-
use
|
|
26
|
-
use
|
|
27
|
-
use crate::
|
|
28
|
-
use crate::config::*;
|
|
29
|
-
use crate::js::JsPerspectiveViewerPlugin;
|
|
30
|
-
use crate::presentation::{
|
|
31
|
-
ColumnLocator, ColumnSettingsTab, DragDropProps, Presentation, PresentationProps,
|
|
32
|
-
};
|
|
33
|
-
use crate::queries::*;
|
|
39
|
+
pub use self::msg::PerspectiveViewerMsg;
|
|
40
|
+
use self::msg::PerspectiveViewerMsg::*;
|
|
41
|
+
use self::settings::SettingsGeometry;
|
|
42
|
+
use self::wiring::*;
|
|
43
|
+
use super::font_loader::{FontLoaderProps, FontLoaderStatus};
|
|
44
|
+
use crate::presentation::{Presentation, PresentationProps};
|
|
34
45
|
use crate::renderer::{RendererProps, *};
|
|
35
46
|
use crate::session::{SessionProps, *};
|
|
36
47
|
use crate::tasks::*;
|
|
37
48
|
use crate::utils::*;
|
|
49
|
+
use crate::workspace::Workspace;
|
|
38
50
|
|
|
39
51
|
#[derive(Clone, Properties)]
|
|
40
52
|
pub struct PerspectiveViewerProps {
|
|
@@ -42,9 +54,12 @@ pub struct PerspectiveViewerProps {
|
|
|
42
54
|
pub elem: web_sys::HtmlElement,
|
|
43
55
|
|
|
44
56
|
/// State
|
|
45
|
-
pub session: Session,
|
|
46
|
-
pub renderer: Renderer,
|
|
47
57
|
pub presentation: Presentation,
|
|
58
|
+
|
|
59
|
+
/// The multi-panel model — the source of truth for engine state. The
|
|
60
|
+
/// component derives the active panel's `Session`/`Renderer` via
|
|
61
|
+
/// `workspace.active_*()` (see `create`), and renders a cell per panel.
|
|
62
|
+
pub workspace: Workspace,
|
|
48
63
|
}
|
|
49
64
|
|
|
50
65
|
impl PartialEq for PerspectiveViewerProps {
|
|
@@ -53,63 +68,47 @@ impl PartialEq for PerspectiveViewerProps {
|
|
|
53
68
|
}
|
|
54
69
|
}
|
|
55
70
|
|
|
56
|
-
#[derive(Debug)]
|
|
57
|
-
pub enum PerspectiveViewerMsg {
|
|
58
|
-
ColumnSettingsPanelSizeUpdate(Option<i32>),
|
|
59
|
-
ColumnSettingsTabChanged(ColumnSettingsTab),
|
|
60
|
-
OpenColumnSettings {
|
|
61
|
-
locator: Option<ColumnLocator>,
|
|
62
|
-
sender: Option<Sender<()>>,
|
|
63
|
-
toggle: bool,
|
|
64
|
-
},
|
|
65
|
-
PreloadFontsUpdate,
|
|
66
|
-
Reset(bool, Option<Sender<()>>),
|
|
67
|
-
Resize,
|
|
68
|
-
SettingsPanelSizeUpdate(Option<i32>),
|
|
69
|
-
SettingsPanelTabChanged(SelectedTab),
|
|
70
|
-
SettingsPanelAutoWidth(f64),
|
|
71
|
-
ToggleDebug,
|
|
72
|
-
ToggleSettingsComplete(SettingsUpdate, Sender<()>),
|
|
73
|
-
ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
|
|
74
|
-
UpdateSession(Box<SessionProps>),
|
|
75
|
-
UpdateRenderer(Box<RendererProps>),
|
|
76
|
-
UpdatePresentation(Box<PresentationProps>),
|
|
77
|
-
|
|
78
|
-
/// Update only `is_settings_open` in the presentation snapshot without
|
|
79
|
-
/// touching `available_themes` (which requires async data).
|
|
80
|
-
UpdateSettingsOpen(bool),
|
|
81
|
-
UpdateIsWorkspace(bool),
|
|
82
|
-
|
|
83
|
-
/// Update only `open_column_settings` in the presentation snapshot.
|
|
84
|
-
UpdateColumnSettings(Box<crate::presentation::OpenColumnSettings>),
|
|
85
|
-
UpdateDragDrop(Box<DragDropProps>),
|
|
86
|
-
|
|
87
|
-
/// Update only stats-related fields of `session_props` without touching
|
|
88
|
-
/// `config`. This prevents `stats_changed` events (e.g. from `reset()`)
|
|
89
|
-
/// from propagating a freshly-cleared config to the column selector.
|
|
90
|
-
UpdateSessionStats(Option<ViewStats>, Option<TableLoadState>),
|
|
91
|
-
|
|
92
|
-
/// Increment/decrement the in-flight render counter threaded to
|
|
93
|
-
/// `StatusIndicator` so it can show the "updating" spinner.
|
|
94
|
-
IncrementUpdateCount,
|
|
95
|
-
DecrementUpdateCount,
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
use PerspectiveViewerMsg::*;
|
|
99
|
-
|
|
100
71
|
pub struct PerspectiveViewer {
|
|
101
72
|
_subscriptions: Vec<Subscription>,
|
|
102
|
-
|
|
73
|
+
|
|
74
|
+
/// Per-active-panel subscriptions; dropped + recreated on `SetActivePanel`.
|
|
75
|
+
_active_subscriptions: Vec<Subscription>,
|
|
76
|
+
|
|
77
|
+
/// One `title_changed` subscription per panel (all panels, not just
|
|
78
|
+
/// active), so a title change on any panel re-renders the tab titles.
|
|
79
|
+
/// Recreated when the panel set changes (`LayoutChanged`/`ClosePanel`).
|
|
80
|
+
_title_subscriptions: Vec<Subscription>,
|
|
81
|
+
|
|
82
|
+
/// The active panel's engine handles — what the settings panel + status bar
|
|
83
|
+
/// bind to. Re-targeted on `SetActivePanel`. Fall back to
|
|
84
|
+
/// `empty_session`/`empty_renderer` when the element has zero panels.
|
|
85
|
+
active_session: Session,
|
|
86
|
+
active_renderer: Renderer,
|
|
87
|
+
|
|
88
|
+
/// Detached "empty" engine handles (never inserted into the `Workspace`,
|
|
89
|
+
/// never mounted). Used as the `active_*` fallback when there are zero
|
|
90
|
+
/// panels, so the settings/status chrome always has a `Session`+`Renderer`
|
|
91
|
+
/// to bind to — and, being tableless, renders an inert empty state.
|
|
92
|
+
empty_session: Session,
|
|
93
|
+
empty_renderer: Renderer,
|
|
103
94
|
debug_open: bool,
|
|
104
95
|
fonts: FontLoaderProps,
|
|
105
96
|
on_close_column_settings: Callback<()>,
|
|
106
|
-
on_rendered:
|
|
97
|
+
on_rendered: Vec<Sender<()>>,
|
|
107
98
|
on_resize: Rc<PubSub<()>>,
|
|
108
99
|
on_settings_panel_dimensions_reset: Rc<PubSub<()>>,
|
|
109
100
|
settings_open: bool,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
101
|
+
|
|
102
|
+
/// Render snapshot of the `Workspace`-owned global filter set (see
|
|
103
|
+
/// `Workspace::global_filters`); refreshed by `UpdateGlobalFilters` via
|
|
104
|
+
/// the `filters_changed` subscription. The status-bar chips render from
|
|
105
|
+
/// this when non-empty.
|
|
106
|
+
global_filters: Vec<Filter>,
|
|
107
|
+
|
|
108
|
+
/// The settings sidebar's geometry state (pane/drawer width overrides,
|
|
109
|
+
/// divider presize pump, open-state deltas cache) — see
|
|
110
|
+
/// [`settings::SettingsGeometry`].
|
|
111
|
+
settings_geometry: SettingsGeometry,
|
|
113
112
|
|
|
114
113
|
/// Value-semantic state snapshots (Step 4 scaffold).
|
|
115
114
|
/// Populated by `UpdateSession` / `UpdateRenderer` / `UpdatePresentation` /
|
|
@@ -117,10 +116,12 @@ pub struct PerspectiveViewer {
|
|
|
117
116
|
session_props: SessionProps,
|
|
118
117
|
renderer_props: RendererProps,
|
|
119
118
|
presentation_props: PresentationProps,
|
|
120
|
-
dragdrop_props: DragDropProps,
|
|
119
|
+
dragdrop_props: crate::presentation::DragDropProps,
|
|
121
120
|
|
|
122
|
-
///
|
|
123
|
-
///
|
|
121
|
+
/// The active panel's in-flight config-run count — a LEVEL-triggered
|
|
122
|
+
/// snapshot of `Session::in_flight_config_runs` (RAII-settled;
|
|
123
|
+
/// assigned by `UpdateInFlight`, re-read on retarget). Threaded to
|
|
124
|
+
/// `StatusIndicator` as the "updating" spinner.
|
|
124
125
|
update_count: u32,
|
|
125
126
|
|
|
126
127
|
/// Window listeners that toggle the `.shift-active` class on the host
|
|
@@ -128,64 +129,13 @@ pub struct PerspectiveViewer {
|
|
|
128
129
|
/// (e.g. inactive column add, active column remove, status-bar reset)
|
|
129
130
|
/// visually discoverable. Stored so the closures outlive `create`.
|
|
130
131
|
_shift_listeners: ShiftListeners,
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
struct ShiftListeners {
|
|
134
|
-
elem: web_sys::HtmlElement,
|
|
135
|
-
keydown: Closure<dyn FnMut(KeyboardEvent)>,
|
|
136
|
-
keyup: Closure<dyn FnMut(KeyboardEvent)>,
|
|
137
|
-
blur: Closure<dyn FnMut(FocusEvent)>,
|
|
138
|
-
}
|
|
139
132
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
win.remove_event_listener_with_callback("keyup", self.keyup.as_ref().unchecked_ref());
|
|
147
|
-
let _ = win.remove_event_listener_with_callback("blur", self.blur.as_ref().unchecked_ref());
|
|
148
|
-
let _ = self.elem.class_list().remove_1("shift-active");
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
fn install_shift_listeners(elem: web_sys::HtmlElement) -> ShiftListeners {
|
|
153
|
-
let keydown = {
|
|
154
|
-
let elem = elem.clone();
|
|
155
|
-
Closure::wrap(Box::new(move |event: KeyboardEvent| {
|
|
156
|
-
if event.key() == "Shift" {
|
|
157
|
-
let _ = elem.class_list().add_1("shift-active");
|
|
158
|
-
}
|
|
159
|
-
}) as Box<dyn FnMut(KeyboardEvent)>)
|
|
160
|
-
};
|
|
161
|
-
|
|
162
|
-
let keyup = {
|
|
163
|
-
let elem = elem.clone();
|
|
164
|
-
Closure::wrap(Box::new(move |event: KeyboardEvent| {
|
|
165
|
-
if event.key() == "Shift" {
|
|
166
|
-
let _ = elem.class_list().remove_1("shift-active");
|
|
167
|
-
}
|
|
168
|
-
}) as Box<dyn FnMut(KeyboardEvent)>)
|
|
169
|
-
};
|
|
170
|
-
|
|
171
|
-
let blur = {
|
|
172
|
-
let elem = elem.clone();
|
|
173
|
-
Closure::wrap(Box::new(move |_: FocusEvent| {
|
|
174
|
-
let _ = elem.class_list().remove_1("shift-active");
|
|
175
|
-
}) as Box<dyn FnMut(FocusEvent)>)
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
let win = global::window();
|
|
179
|
-
let _ = win.add_event_listener_with_callback("keydown", keydown.as_ref().unchecked_ref());
|
|
180
|
-
let _ = win.add_event_listener_with_callback("keyup", keyup.as_ref().unchecked_ref());
|
|
181
|
-
let _ = win.add_event_listener_with_callback("blur", blur.as_ref().unchecked_ref());
|
|
182
|
-
|
|
183
|
-
ShiftListeners {
|
|
184
|
-
elem,
|
|
185
|
-
keydown,
|
|
186
|
-
keyup,
|
|
187
|
-
blur,
|
|
188
|
-
}
|
|
133
|
+
/// `perspective-global-filter` + `perspective-click` listeners on the host
|
|
134
|
+
/// (master/detail). Both route a master panel's selection state to
|
|
135
|
+
/// `MasterContribution`. Kept alive here, attached once in `rendered`.
|
|
136
|
+
_global_filter_listener: Closure<dyn FnMut(web_sys::Event)>,
|
|
137
|
+
_master_click_listener: Closure<dyn FnMut(web_sys::Event)>,
|
|
138
|
+
master_listeners_attached: bool,
|
|
189
139
|
}
|
|
190
140
|
|
|
191
141
|
impl Component for PerspectiveViewer {
|
|
@@ -195,13 +145,26 @@ impl Component for PerspectiveViewer {
|
|
|
195
145
|
fn create(ctx: &Context<Self>) -> Self {
|
|
196
146
|
let elem = ctx.props().elem.clone();
|
|
197
147
|
let fonts = FontLoaderProps::new(&elem, ctx.link().callback(|()| PreloadFontsUpdate));
|
|
198
|
-
|
|
199
|
-
let
|
|
200
|
-
let
|
|
201
|
-
|
|
148
|
+
let empty_session = Session::new();
|
|
149
|
+
let empty_renderer = Renderer::new(&elem);
|
|
150
|
+
let active_session = ctx
|
|
151
|
+
.props()
|
|
152
|
+
.workspace
|
|
153
|
+
.active_session()
|
|
154
|
+
.unwrap_or_else(|| empty_session.clone());
|
|
155
|
+
let active_renderer = ctx
|
|
156
|
+
.props()
|
|
157
|
+
.workspace
|
|
158
|
+
.active_renderer()
|
|
159
|
+
.unwrap_or_else(|| empty_renderer.clone());
|
|
160
|
+
inject_shared_callbacks(ctx);
|
|
161
|
+
inject_active_callbacks(ctx, &active_session, &active_renderer);
|
|
162
|
+
let subscriptions = create_shared_subscriptions(ctx);
|
|
163
|
+
let active_subscriptions =
|
|
164
|
+
create_active_subscriptions(ctx, &active_session, &active_renderer);
|
|
165
|
+
let session_props = active_session.to_props();
|
|
166
|
+
let renderer_props = active_renderer.to_props(None);
|
|
202
167
|
let presentation_props = ctx.props().presentation.to_props(PtrEqRc::new(vec![]));
|
|
203
|
-
|
|
204
|
-
// Memoized callback for column settings drawer
|
|
205
168
|
let on_close_column_settings = ctx.link().callback(|_| OpenColumnSettings {
|
|
206
169
|
locator: None,
|
|
207
170
|
sender: None,
|
|
@@ -226,231 +189,171 @@ impl Component for PerspectiveViewer {
|
|
|
226
189
|
}
|
|
227
190
|
|
|
228
191
|
let shift_listeners = install_shift_listeners(elem);
|
|
192
|
+
let global_filter_listener = wiring::global_filter_listener(ctx);
|
|
193
|
+
let master_click_listener = wiring::master_click_listener(ctx);
|
|
229
194
|
|
|
230
195
|
Self {
|
|
231
196
|
_subscriptions: subscriptions,
|
|
232
|
-
|
|
197
|
+
_global_filter_listener: global_filter_listener,
|
|
198
|
+
_master_click_listener: master_click_listener,
|
|
199
|
+
master_listeners_attached: false,
|
|
200
|
+
_active_subscriptions: active_subscriptions,
|
|
201
|
+
_title_subscriptions: subscribe_panel_titles(ctx),
|
|
202
|
+
active_session,
|
|
203
|
+
active_renderer,
|
|
204
|
+
empty_session,
|
|
205
|
+
empty_renderer,
|
|
233
206
|
debug_open: false,
|
|
234
207
|
fonts,
|
|
235
208
|
on_close_column_settings,
|
|
236
|
-
on_rendered:
|
|
209
|
+
on_rendered: Vec::new(),
|
|
237
210
|
on_resize: Default::default(),
|
|
238
211
|
on_settings_panel_dimensions_reset: Default::default(),
|
|
239
212
|
settings_open: false,
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
settings_panel_auto_width: 0.0,
|
|
213
|
+
global_filters: Vec::new(),
|
|
214
|
+
settings_geometry: Default::default(),
|
|
243
215
|
session_props,
|
|
244
216
|
renderer_props,
|
|
245
217
|
presentation_props,
|
|
246
|
-
dragdrop_props:
|
|
218
|
+
dragdrop_props: Default::default(),
|
|
247
219
|
update_count: 0,
|
|
248
220
|
_shift_listeners: shift_listeners,
|
|
249
221
|
}
|
|
250
222
|
}
|
|
251
223
|
|
|
224
|
+
/// The protocol dispatch table: every arm is a one-line call into a domain
|
|
225
|
+
/// module (see the module doc). Trivial self-describing arms stay inline.
|
|
252
226
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
253
227
|
match msg {
|
|
254
228
|
PreloadFontsUpdate => true,
|
|
229
|
+
TitlesChanged => true,
|
|
255
230
|
Resize => {
|
|
256
231
|
self.on_resize.emit(());
|
|
257
232
|
false
|
|
258
233
|
},
|
|
259
|
-
Reset(all,
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
},
|
|
286
|
-
ToggleSettingsComplete(SettingsUpdate::SetDefault, resolve) if self.settings_open => {
|
|
287
|
-
ctx.props().presentation.set_open_column_settings(None);
|
|
288
|
-
self.settings_open = false;
|
|
289
|
-
self.on_rendered = Some(resolve);
|
|
290
|
-
true
|
|
291
|
-
},
|
|
292
|
-
ToggleSettingsComplete(SettingsUpdate::Update(force), resolve)
|
|
293
|
-
if force != self.settings_open =>
|
|
294
|
-
{
|
|
295
|
-
ctx.props().presentation.set_open_column_settings(None);
|
|
296
|
-
self.settings_open = force;
|
|
297
|
-
self.on_rendered = Some(resolve);
|
|
298
|
-
true
|
|
299
|
-
},
|
|
300
|
-
ToggleSettingsComplete(_, resolve)
|
|
301
|
-
if matches!(self.fonts.get_status(), FontLoaderStatus::Finished) =>
|
|
302
|
-
{
|
|
303
|
-
if let Err(e) = resolve.send(()) {
|
|
304
|
-
tracing::error!("toggle settings failed {:?}", e);
|
|
234
|
+
Reset(all, completion) => {
|
|
235
|
+
// Element-level reset (symmetric with `save`, which fans out
|
|
236
|
+
// over all panels): drop the element-level global-filter SET
|
|
237
|
+
// first — otherwise a detail panel re-applies it right after
|
|
238
|
+
// the reset — then reset EVERY panel. Master/detail ROLES are
|
|
239
|
+
// layout state (like the panel arrangement), not panel
|
|
240
|
+
// config, so they survive. The `Completion`
|
|
241
|
+
// resolves only after ALL panels' reset runs complete
|
|
242
|
+
// (invariant I6 — previously it rode the active panel only,
|
|
243
|
+
// and the other panels' runs were unowned).
|
|
244
|
+
let workspace = &ctx.props().workspace;
|
|
245
|
+
let origins = workspace.clear_global_filters();
|
|
246
|
+
clear_master_selections(workspace, origins);
|
|
247
|
+
let mut tasks = Vec::new();
|
|
248
|
+
for (id, panel) in workspace
|
|
249
|
+
.panel_ids()
|
|
250
|
+
.into_iter()
|
|
251
|
+
.filter_map(|id| workspace.panel(&id).map(|p| (id, p)))
|
|
252
|
+
{
|
|
253
|
+
stamp_global_overlay(workspace, &id, &panel.session);
|
|
254
|
+
tasks.push(reset_all(
|
|
255
|
+
&panel.session,
|
|
256
|
+
&panel.renderer,
|
|
257
|
+
&ctx.props().presentation,
|
|
258
|
+
all,
|
|
259
|
+
));
|
|
305
260
|
}
|
|
306
261
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
} => {
|
|
319
|
-
let mut open_column_settings = ctx.props().presentation.get_open_column_settings();
|
|
320
|
-
if locator == open_column_settings.locator {
|
|
321
|
-
if toggle {
|
|
322
|
-
ctx.props().presentation.set_open_column_settings(None);
|
|
323
|
-
}
|
|
324
|
-
} else {
|
|
325
|
-
open_column_settings.locator.clone_from(&locator);
|
|
326
|
-
open_column_settings.tab =
|
|
327
|
-
if matches!(locator, Some(ColumnLocator::NewExpression)) {
|
|
328
|
-
Some(ColumnSettingsTab::Attributes)
|
|
329
|
-
} else {
|
|
330
|
-
locator.as_ref().and_then(|x| {
|
|
331
|
-
x.name().map(|x| {
|
|
332
|
-
if self.session_props.is_column_active(x) {
|
|
333
|
-
ColumnSettingsTab::Style
|
|
334
|
-
} else {
|
|
335
|
-
ColumnSettingsTab::Attributes
|
|
336
|
-
}
|
|
337
|
-
})
|
|
338
|
-
})
|
|
339
|
-
};
|
|
340
|
-
|
|
341
|
-
ctx.props()
|
|
342
|
-
.presentation
|
|
343
|
-
.set_open_column_settings(Some(open_column_settings));
|
|
344
|
-
|
|
345
|
-
if locator.is_some() {
|
|
346
|
-
self.settings_panel_selected_tab = SelectedTab::Query;
|
|
347
|
-
}
|
|
262
|
+
let run = async move {
|
|
263
|
+
futures::future::join_all(tasks)
|
|
264
|
+
.await
|
|
265
|
+
.into_iter()
|
|
266
|
+
.collect::<ApiResult<Vec<_>>>()?;
|
|
267
|
+
Ok(())
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
match completion {
|
|
271
|
+
Some(completion) => completion.resolve_after(run),
|
|
272
|
+
None => spawn_owned("reset", run),
|
|
348
273
|
}
|
|
349
274
|
|
|
350
|
-
if let Some(sender) = sender {
|
|
351
|
-
sender.send(()).unwrap();
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
true
|
|
355
|
-
},
|
|
356
|
-
SettingsPanelSizeUpdate(Some(x)) => {
|
|
357
|
-
self.settings_panel_width_override = Some(x);
|
|
358
275
|
false
|
|
359
276
|
},
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
277
|
+
ResetPanel(id, all, sender) => {
|
|
278
|
+
// Panel-scoped reset (toolbar / context menu / `resetPanel()`
|
|
279
|
+
// API): reset ONLY the target panel's config. The
|
|
280
|
+
// element-level cross-filter overlay is workspace state, not
|
|
281
|
+
// panel config, so it's deliberately left in place — the
|
|
282
|
+
// rebuilt view re-applies it via `effective_view_config`,
|
|
283
|
+
// keeping a detail panel consistent with the rest of the
|
|
284
|
+
// workspace. A dangling id no-ops (dropping `sender` rejects
|
|
285
|
+
// the API promise).
|
|
286
|
+
let id = id.map(crate::workspace::PanelId::from);
|
|
287
|
+
if let Some(panel) = ctx.props().workspace.panel_or_active(id.as_ref()) {
|
|
288
|
+
let run = reset_all(
|
|
289
|
+
&panel.session,
|
|
290
|
+
&panel.renderer,
|
|
291
|
+
&ctx.props().presentation,
|
|
292
|
+
all,
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
match sender {
|
|
296
|
+
Some(completion) => completion.resolve_after(run),
|
|
297
|
+
None => spawn_owned("reset-panel", run),
|
|
298
|
+
}
|
|
377
299
|
}
|
|
378
|
-
|
|
379
|
-
ColumnSettingsPanelSizeUpdate(Some(x)) => {
|
|
380
|
-
self.column_settings_panel_width_override = Some(x);
|
|
381
|
-
false
|
|
382
|
-
},
|
|
383
|
-
ColumnSettingsPanelSizeUpdate(None) => {
|
|
384
|
-
self.column_settings_panel_width_override = None;
|
|
300
|
+
|
|
385
301
|
false
|
|
386
302
|
},
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
true
|
|
403
|
-
},
|
|
404
|
-
UpdateSession(props) => {
|
|
405
|
-
let changed = *props != self.session_props;
|
|
406
|
-
self.session_props = *props;
|
|
407
|
-
changed
|
|
408
|
-
},
|
|
409
|
-
UpdateSessionStats(stats, has_table) => {
|
|
410
|
-
let changed =
|
|
411
|
-
stats != self.session_props.stats || has_table != self.session_props.has_table;
|
|
412
|
-
self.session_props.stats = stats;
|
|
413
|
-
self.session_props.has_table = has_table;
|
|
414
|
-
changed
|
|
415
|
-
},
|
|
416
|
-
UpdateRenderer(props) => {
|
|
417
|
-
let changed = *props != self.renderer_props;
|
|
418
|
-
self.renderer_props = *props;
|
|
419
|
-
changed
|
|
420
|
-
},
|
|
421
|
-
UpdatePresentation(props) => {
|
|
422
|
-
let changed = *props != self.presentation_props;
|
|
423
|
-
self.presentation_props = *props;
|
|
424
|
-
changed
|
|
425
|
-
},
|
|
426
|
-
UpdateSettingsOpen(open) => {
|
|
427
|
-
let changed = open != self.presentation_props.is_settings_open;
|
|
428
|
-
self.presentation_props.is_settings_open = open;
|
|
429
|
-
changed
|
|
430
|
-
},
|
|
431
|
-
UpdateIsWorkspace(is_workspace) => {
|
|
432
|
-
let changed = is_workspace != self.presentation_props.is_workspace;
|
|
433
|
-
self.presentation_props.is_workspace = is_workspace;
|
|
434
|
-
changed
|
|
435
|
-
},
|
|
436
|
-
UpdateColumnSettings(ocs) => {
|
|
437
|
-
let changed = *ocs != self.presentation_props.open_column_settings;
|
|
438
|
-
self.presentation_props.open_column_settings = *ocs;
|
|
439
|
-
changed
|
|
440
|
-
},
|
|
441
|
-
UpdateDragDrop(props) => {
|
|
442
|
-
let changed = *props != self.dragdrop_props;
|
|
443
|
-
self.dragdrop_props = *props;
|
|
444
|
-
changed
|
|
303
|
+
|
|
304
|
+
// Panel lifecycle (`panels.rs`)
|
|
305
|
+
LayoutChanged => self.on_layout_changed(ctx),
|
|
306
|
+
SetActivePanel(id, completion) => self.on_set_active_panel(ctx, id, completion),
|
|
307
|
+
ClosePanel(id, completion) => self.on_close_panel(ctx, id, completion),
|
|
308
|
+
CommitWorkspaceRestore(id) => self.on_commit_workspace_restore(ctx, id),
|
|
309
|
+
DuplicatePanel(id) => self.on_duplicate_panel(ctx, id),
|
|
310
|
+
NewPanel(id) => self.on_new_panel(ctx, id),
|
|
311
|
+
NewPanelFrom { client, table } => self.on_new_panel_from(ctx, client, table),
|
|
312
|
+
|
|
313
|
+
// Master/detail cross-filter (`filters.rs`)
|
|
314
|
+
ToggleMaster(id) => self.on_toggle_master(ctx, id),
|
|
315
|
+
MasterContribution(panel, selection) => {
|
|
316
|
+
self.on_master_contribution(ctx, panel, selection)
|
|
445
317
|
},
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
318
|
+
RemoveGlobalFilter(index) => self.on_remove_global_filter(ctx, index),
|
|
319
|
+
ClearGlobalFilters => self.on_clear_global_filters(ctx),
|
|
320
|
+
|
|
321
|
+
// Settings sidebar + divider pump + column settings (`settings.rs`)
|
|
322
|
+
ToggleSettingsInit(update, resolve) => {
|
|
323
|
+
self.on_toggle_settings_init(ctx, update, resolve)
|
|
449
324
|
},
|
|
450
|
-
|
|
451
|
-
self.
|
|
452
|
-
true
|
|
325
|
+
ToggleSettingsComplete(update, resolve) => {
|
|
326
|
+
self.on_toggle_settings_complete(ctx, update, resolve)
|
|
453
327
|
},
|
|
328
|
+
SettingsPanelSizeUpdate(x) => self.on_settings_panel_size_update(x),
|
|
329
|
+
SettingsDividerMove(w) => self.on_settings_divider_move(ctx, w),
|
|
330
|
+
SettingsDividerPump => self.on_settings_divider_pump(ctx),
|
|
331
|
+
SettingsDividerCommit(w) => self.on_settings_divider_commit(ctx, w),
|
|
332
|
+
SettingsDividerFinish => self.on_settings_divider_finish(ctx),
|
|
333
|
+
SettingsPanelTabChanged(tab) => self.on_settings_panel_tab_changed(tab),
|
|
334
|
+
SettingsPanelAutoWidth(w) => self.on_settings_panel_auto_width(w),
|
|
335
|
+
OpenColumnSettings {
|
|
336
|
+
locator,
|
|
337
|
+
sender,
|
|
338
|
+
toggle,
|
|
339
|
+
} => self.on_open_column_settings(ctx, locator, sender, toggle),
|
|
340
|
+
ColumnSettingsPanelSizeUpdate(x) => self.on_column_settings_panel_size_update(x),
|
|
341
|
+
ColumnSettingsPanelAutoWidth(w) => self.on_column_settings_panel_auto_width(w),
|
|
342
|
+
ToggleColumnSettingsPin => self.on_toggle_column_settings_pin(),
|
|
343
|
+
ColumnSettingsTabChanged(tab) => self.on_column_settings_tab_changed(ctx, tab),
|
|
344
|
+
ToggleDebug => self.on_toggle_debug(ctx),
|
|
345
|
+
|
|
346
|
+
// Value-semantic snapshot plumbing (`snapshots.rs`)
|
|
347
|
+
UpdateSession(props) => self.on_update_session(*props),
|
|
348
|
+
UpdateSessionStats(stats, has_table) => self.on_update_session_stats(stats, has_table),
|
|
349
|
+
UpdateGlobalFilters => self.on_update_global_filters(ctx),
|
|
350
|
+
UpdateRenderer(props) => self.on_update_renderer(*props),
|
|
351
|
+
UpdatePresentation(props) => self.on_update_presentation(ctx, *props),
|
|
352
|
+
UpdateSettingsOpen(open) => self.on_update_settings_open(open),
|
|
353
|
+
UpdateIsWorkspace(is_workspace) => self.on_update_is_workspace(is_workspace),
|
|
354
|
+
UpdateColumnSettings(ocs) => self.on_update_column_settings(*ocs),
|
|
355
|
+
UpdateDragDrop(props) => self.on_update_dragdrop(*props),
|
|
356
|
+
UpdateInFlight(count) => self.on_update_in_flight(count),
|
|
454
357
|
}
|
|
455
358
|
}
|
|
456
359
|
|
|
@@ -463,449 +366,35 @@ impl Component for PerspectiveViewer {
|
|
|
463
366
|
|
|
464
367
|
/// On rendered call notify_resize(). This also triggers any registered
|
|
465
368
|
/// async callbacks to the Custom Element API.
|
|
466
|
-
fn rendered(&mut self,
|
|
467
|
-
|
|
369
|
+
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
|
|
370
|
+
// Attach the master/detail selection listeners once (the host element
|
|
371
|
+
// is stable for this component's lifetime).
|
|
372
|
+
if !self.master_listeners_attached {
|
|
373
|
+
let _ = ctx.props().elem.add_event_listener_with_callback(
|
|
374
|
+
"perspective-global-filter",
|
|
375
|
+
self._global_filter_listener.as_ref().unchecked_ref(),
|
|
376
|
+
);
|
|
377
|
+
let _ = ctx.props().elem.add_event_listener_with_callback(
|
|
378
|
+
"perspective-click",
|
|
379
|
+
self._master_click_listener.as_ref().unchecked_ref(),
|
|
380
|
+
);
|
|
381
|
+
self.master_listeners_attached = true;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if !self.on_rendered.is_empty()
|
|
468
385
|
&& matches!(self.fonts.get_status(), FontLoaderStatus::Finished)
|
|
469
|
-
&& self.on_rendered.take().unwrap().send(()).is_err()
|
|
470
386
|
{
|
|
471
|
-
|
|
387
|
+
for resolve in self.on_rendered.drain(..) {
|
|
388
|
+
if resolve.send(()).is_err() {
|
|
389
|
+
tracing::warn!("Orphan render");
|
|
390
|
+
}
|
|
391
|
+
}
|
|
472
392
|
}
|
|
473
393
|
}
|
|
474
394
|
|
|
475
395
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
476
|
-
|
|
477
|
-
presentation,
|
|
478
|
-
renderer,
|
|
479
|
-
session,
|
|
480
|
-
..
|
|
481
|
-
} = ctx.props();
|
|
482
|
-
|
|
483
|
-
let is_settings_open = self.settings_open
|
|
484
|
-
&& matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
|
|
485
|
-
|
|
486
|
-
let mut class = classes!();
|
|
487
|
-
if !is_settings_open {
|
|
488
|
-
class.push("settings-closed");
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
if self.session_props.title.is_some() {
|
|
492
|
-
class.push("titled");
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
|
|
496
|
-
locator: c,
|
|
497
|
-
sender: None,
|
|
498
|
-
toggle: true,
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
let on_split_panel_resize = ctx
|
|
502
|
-
.link()
|
|
503
|
-
.callback(|(x, _)| SettingsPanelSizeUpdate(Some(x)));
|
|
504
|
-
|
|
505
|
-
let on_column_settings_panel_resize = ctx
|
|
506
|
-
.link()
|
|
507
|
-
.callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
|
|
508
|
-
|
|
509
|
-
let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
|
|
510
|
-
let on_debug = ctx.link().callback(|_| ToggleDebug);
|
|
511
|
-
let selected_column = get_current_column_locator(
|
|
512
|
-
&self.presentation_props.open_column_settings,
|
|
513
|
-
&ctx.props().renderer,
|
|
514
|
-
&self.session_props.config,
|
|
515
|
-
&self.session_props.metadata,
|
|
516
|
-
);
|
|
517
|
-
|
|
518
|
-
let selected_tab = self.presentation_props.open_column_settings.tab;
|
|
519
|
-
let plugin_name = self.renderer_props.plugin_name.clone();
|
|
520
|
-
let available_plugins = self.renderer_props.available_plugins.clone();
|
|
521
|
-
let has_table = self.session_props.has_table.clone();
|
|
522
|
-
let named_column_count = self.renderer_props.config.config_column_names.len();
|
|
523
|
-
|
|
524
|
-
let view_config = self.session_props.config.clone();
|
|
525
|
-
let drag_column = self.dragdrop_props.column.clone();
|
|
526
|
-
let metadata = self.session_props.metadata.clone();
|
|
527
|
-
let on_select_tab = ctx.link().callback(SettingsPanelTabChanged);
|
|
528
|
-
let on_auto_width = ctx.link().callback(SettingsPanelAutoWidth);
|
|
529
|
-
let settings_panel = html! {
|
|
530
|
-
if is_settings_open {
|
|
531
|
-
<SettingsPanel
|
|
532
|
-
on_close={on_close_settings}
|
|
533
|
-
on_resize={&self.on_resize}
|
|
534
|
-
on_select_column={on_open_expr_panel}
|
|
535
|
-
is_debug={self.debug_open}
|
|
536
|
-
{on_debug}
|
|
537
|
-
{plugin_name}
|
|
538
|
-
{available_plugins}
|
|
539
|
-
{has_table}
|
|
540
|
-
{named_column_count}
|
|
541
|
-
{view_config}
|
|
542
|
-
plugin_config={self.renderer_props.plugin_config.clone()}
|
|
543
|
-
{drag_column}
|
|
544
|
-
metadata={metadata.clone()}
|
|
545
|
-
open_column_settings={self.presentation_props.open_column_settings.clone()}
|
|
546
|
-
selected_theme={self.presentation_props.selected_theme.clone()}
|
|
547
|
-
selected_tab={self.settings_panel_selected_tab}
|
|
548
|
-
auto_width={self.settings_panel_auto_width}
|
|
549
|
-
on_dimensions_reset={&self.on_settings_panel_dimensions_reset}
|
|
550
|
-
{on_select_tab}
|
|
551
|
-
{on_auto_width}
|
|
552
|
-
{presentation}
|
|
553
|
-
{renderer}
|
|
554
|
-
{session}
|
|
555
|
-
/>
|
|
556
|
-
}
|
|
557
|
-
};
|
|
558
|
-
|
|
559
|
-
let on_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
|
|
560
|
-
let on_select_tab = ctx.link().callback(ColumnSettingsTabChanged);
|
|
561
|
-
let column_settings_panel = html! {
|
|
562
|
-
if let Some(selected_column) = selected_column {
|
|
563
|
-
<SplitPanel
|
|
564
|
-
id="modal_panel"
|
|
565
|
-
reverse=true
|
|
566
|
-
initial_size={self.column_settings_panel_width_override}
|
|
567
|
-
on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
|
|
568
|
-
on_resize={on_column_settings_panel_resize}
|
|
569
|
-
>
|
|
570
|
-
<ColumnSettingsPanel
|
|
571
|
-
{selected_column}
|
|
572
|
-
{selected_tab}
|
|
573
|
-
on_close={self.on_close_column_settings.clone()}
|
|
574
|
-
width_override={self.column_settings_panel_width_override}
|
|
575
|
-
{on_select_tab}
|
|
576
|
-
plugin_name={self.renderer_props.plugin_name.clone()}
|
|
577
|
-
{metadata}
|
|
578
|
-
view_config={self.session_props.config.clone()}
|
|
579
|
-
column_stats={self.session_props.column_stats.clone()}
|
|
580
|
-
selected_theme={self.presentation_props.selected_theme.clone()}
|
|
581
|
-
{presentation}
|
|
582
|
-
{renderer}
|
|
583
|
-
{session}
|
|
584
|
-
/>
|
|
585
|
-
<></>
|
|
586
|
-
</SplitPanel>
|
|
587
|
-
}
|
|
588
|
-
};
|
|
589
|
-
|
|
590
|
-
let on_reset = ctx.link().callback(|all| Reset(all, None));
|
|
591
|
-
let is_settings_open = self.settings_open
|
|
592
|
-
&& matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
|
|
593
|
-
let main_panel = html! {
|
|
594
|
-
<MainPanel
|
|
595
|
-
{on_settings}
|
|
596
|
-
{on_reset}
|
|
597
|
-
session_props={self.session_props.clone()}
|
|
598
|
-
renderer_props={self.renderer_props.clone()}
|
|
599
|
-
presentation_props={self.presentation_props.clone()}
|
|
600
|
-
{is_settings_open}
|
|
601
|
-
update_count={self.update_count}
|
|
602
|
-
{presentation}
|
|
603
|
-
{renderer}
|
|
604
|
-
{session}
|
|
605
|
-
/>
|
|
606
|
-
};
|
|
607
|
-
|
|
608
|
-
html! {
|
|
609
|
-
<StyleProvider root={ctx.props().elem.clone()}>
|
|
610
|
-
<div id="component_container">
|
|
611
|
-
if is_settings_open {
|
|
612
|
-
<SplitPanel
|
|
613
|
-
id="app_panel"
|
|
614
|
-
reverse=true
|
|
615
|
-
skip_empty=true
|
|
616
|
-
initial_size={self.settings_panel_width_override}
|
|
617
|
-
on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
|
|
618
|
-
on_resize={{
|
|
619
|
-
let size_cb = on_split_panel_resize.clone();
|
|
620
|
-
let resize_cb = resize_callback(&ctx.props().session, &ctx.props().renderer);
|
|
621
|
-
move |x| {
|
|
622
|
-
size_cb.emit(x);
|
|
623
|
-
resize_cb.emit(());
|
|
624
|
-
}
|
|
625
|
-
}}
|
|
626
|
-
on_resize_finished={resize_callback(&ctx.props().session, &ctx.props().renderer)}
|
|
627
|
-
>
|
|
628
|
-
{ settings_panel }
|
|
629
|
-
<div id="main_column_container">
|
|
630
|
-
{ main_panel }
|
|
631
|
-
{ column_settings_panel }
|
|
632
|
-
</div>
|
|
633
|
-
</SplitPanel>
|
|
634
|
-
} else {
|
|
635
|
-
<div id="main_column_container">
|
|
636
|
-
{ main_panel }
|
|
637
|
-
{ column_settings_panel }
|
|
638
|
-
</div>
|
|
639
|
-
}
|
|
640
|
-
</div>
|
|
641
|
-
<FontLoader ..self.fonts.clone() />
|
|
642
|
-
</StyleProvider>
|
|
643
|
-
}
|
|
396
|
+
self.render(ctx)
|
|
644
397
|
}
|
|
645
398
|
|
|
646
399
|
fn destroy(&mut self, _ctx: &Context<Self>) {}
|
|
647
400
|
}
|
|
648
|
-
|
|
649
|
-
impl PerspectiveViewer {
|
|
650
|
-
/// Toggle the settings, or force the settings panel either open (true) or
|
|
651
|
-
/// closed (false) explicitly. In order to reduce apparent
|
|
652
|
-
/// screen-shear, `toggle_settings()` uses a somewhat complex render
|
|
653
|
-
/// order: it first resize the plugin's `<div>` without moving it,
|
|
654
|
-
/// using `overflow: hidden` to hide the extra draw area; then,
|
|
655
|
-
/// after the _async_ drawing of the plugin is complete, it will send a
|
|
656
|
-
/// message to complete the toggle action and re-render the element with
|
|
657
|
-
/// the settings removed.
|
|
658
|
-
///
|
|
659
|
-
/// # Arguments
|
|
660
|
-
/// * `force` - Whether to explicitly set the settings panel state to
|
|
661
|
-
/// Open/Close (`Some(true)`/`Some(false)`), or to just toggle the current
|
|
662
|
-
/// state (`None`).
|
|
663
|
-
fn init_toggle_settings_task(
|
|
664
|
-
&mut self,
|
|
665
|
-
ctx: &Context<Self>,
|
|
666
|
-
force: Option<bool>,
|
|
667
|
-
sender: Option<Sender<ApiResult<JsValue>>>,
|
|
668
|
-
) {
|
|
669
|
-
let is_open = ctx.props().presentation.is_settings_open();
|
|
670
|
-
ctx.props().presentation.set_settings_before_open(!is_open);
|
|
671
|
-
match force {
|
|
672
|
-
Some(force) if is_open == force => {
|
|
673
|
-
if let Some(sender) = sender {
|
|
674
|
-
sender.send(Ok(JsValue::UNDEFINED)).unwrap();
|
|
675
|
-
}
|
|
676
|
-
},
|
|
677
|
-
Some(_) | None => {
|
|
678
|
-
let force = !is_open;
|
|
679
|
-
let callback = ctx.link().callback(move |resolve| {
|
|
680
|
-
let update = SettingsUpdate::Update(force);
|
|
681
|
-
ToggleSettingsComplete(update, resolve)
|
|
682
|
-
});
|
|
683
|
-
|
|
684
|
-
clone!(
|
|
685
|
-
ctx.props().renderer,
|
|
686
|
-
ctx.props().session,
|
|
687
|
-
ctx.props().presentation
|
|
688
|
-
);
|
|
689
|
-
|
|
690
|
-
ApiFuture::spawn(async move {
|
|
691
|
-
let result = if session.js_get_table().is_some() {
|
|
692
|
-
renderer
|
|
693
|
-
.presize(force, {
|
|
694
|
-
let (sender, receiver) = channel::<()>();
|
|
695
|
-
async move {
|
|
696
|
-
callback.emit(sender);
|
|
697
|
-
presentation.set_settings_open(!is_open);
|
|
698
|
-
Ok(receiver.await?)
|
|
699
|
-
}
|
|
700
|
-
})
|
|
701
|
-
.await
|
|
702
|
-
} else {
|
|
703
|
-
let (sender, receiver) = channel::<()>();
|
|
704
|
-
callback.emit(sender);
|
|
705
|
-
presentation.set_settings_open(!is_open);
|
|
706
|
-
receiver.await?;
|
|
707
|
-
Ok(JsValue::UNDEFINED)
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
if let Some(sender) = sender {
|
|
711
|
-
let msg = result.ignore_view_delete();
|
|
712
|
-
sender
|
|
713
|
-
.send(msg.map(|x| x.unwrap_or(JsValue::UNDEFINED)))
|
|
714
|
-
.into_apierror()?;
|
|
715
|
-
};
|
|
716
|
-
|
|
717
|
-
Ok(JsValue::undefined())
|
|
718
|
-
});
|
|
719
|
-
},
|
|
720
|
-
};
|
|
721
|
-
}
|
|
722
|
-
}
|
|
723
|
-
|
|
724
|
-
/// Subscribe to PubSub events that still have non-root subscribers and
|
|
725
|
-
/// therefore cannot yet be replaced with direct callbacks.
|
|
726
|
-
fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
|
|
727
|
-
let session_props_sub = {
|
|
728
|
-
let session = ctx.props().session.clone();
|
|
729
|
-
let cb = ctx
|
|
730
|
-
.link()
|
|
731
|
-
.callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
|
|
732
|
-
|
|
733
|
-
let s = &ctx.props().session;
|
|
734
|
-
let sub1 = s.table_loaded.add_notify_listener(&cb);
|
|
735
|
-
let sub2 = s.table_unloaded.add_notify_listener(&cb);
|
|
736
|
-
let sub3 = s.view_created.add_notify_listener(&cb);
|
|
737
|
-
let sub4 = s.view_config_changed.add_notify_listener(&cb);
|
|
738
|
-
let sub5 = s.title_changed.add_notify_listener(&cb);
|
|
739
|
-
let sub6 = s
|
|
740
|
-
.view_config_changed
|
|
741
|
-
.add_listener(ctx.link().callback(|_| IncrementUpdateCount));
|
|
742
|
-
|
|
743
|
-
let sub7 = s
|
|
744
|
-
.view_created
|
|
745
|
-
.add_listener(ctx.link().callback(|_| DecrementUpdateCount));
|
|
746
|
-
|
|
747
|
-
// Stats fetch resolution (populates session.column_stats) triggers
|
|
748
|
-
// a fresh `SessionProps` so `column_stats` reaches downstream
|
|
749
|
-
// components and the StyleTab re-queries the schema with the
|
|
750
|
-
// new value.
|
|
751
|
-
let sub8 = s.column_stats_changed.add_notify_listener(&cb);
|
|
752
|
-
|
|
753
|
-
vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8]
|
|
754
|
-
};
|
|
755
|
-
|
|
756
|
-
let renderer_props_sub = {
|
|
757
|
-
let renderer = ctx.props().renderer.clone();
|
|
758
|
-
let cb_plugin = ctx.link().callback({
|
|
759
|
-
let renderer = renderer.clone();
|
|
760
|
-
move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
|
|
761
|
-
});
|
|
762
|
-
|
|
763
|
-
// Re-snapshot RendererProps when the plugin_config bucket
|
|
764
|
-
// changes (in-tab edit via `send_plugin_config`, JSON paste via
|
|
765
|
-
// `restore_and_render`, full clear via `reset_all` with
|
|
766
|
-
// `all=true`). Without this, `RendererProps.plugin_config`
|
|
767
|
-
// would stay frozen at its construct-time value and `PluginTab`
|
|
768
|
-
// would render stale.
|
|
769
|
-
let cb_plugin_config = ctx.link().callback({
|
|
770
|
-
let renderer = renderer.clone();
|
|
771
|
-
move |_: serde_json::Map<String, serde_json::Value>| {
|
|
772
|
-
UpdateRenderer(Box::new(renderer.to_props(None)))
|
|
773
|
-
}
|
|
774
|
-
});
|
|
775
|
-
|
|
776
|
-
let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
|
|
777
|
-
let sub2 = ctx
|
|
778
|
-
.props()
|
|
779
|
-
.renderer
|
|
780
|
-
.plugin_config_changed
|
|
781
|
-
.add_listener(cb_plugin_config);
|
|
782
|
-
|
|
783
|
-
vec![sub1, sub2]
|
|
784
|
-
};
|
|
785
|
-
|
|
786
|
-
let presentation_props_sub = {
|
|
787
|
-
let presentation = ctx.props().presentation.clone();
|
|
788
|
-
let cb_settings = ctx.link().callback(UpdateSettingsOpen);
|
|
789
|
-
let cb_theme = {
|
|
790
|
-
let pres = presentation.clone();
|
|
791
|
-
ctx.link()
|
|
792
|
-
.callback(move |(themes, _): (PtrEqRc<Vec<String>>, _)| {
|
|
793
|
-
UpdatePresentation(Box::new(pres.to_props(themes)))
|
|
794
|
-
})
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
let cb_column_settings = {
|
|
798
|
-
let pres = presentation.clone();
|
|
799
|
-
ctx.link().callback(move |_: (bool, Option<String>)| {
|
|
800
|
-
UpdateColumnSettings(Box::new(pres.get_open_column_settings()))
|
|
801
|
-
})
|
|
802
|
-
};
|
|
803
|
-
|
|
804
|
-
let sub1 = presentation.settings_open_changed.add_listener(cb_settings);
|
|
805
|
-
let sub2 = presentation.theme_config_updated.add_listener(cb_theme);
|
|
806
|
-
let sub3 = presentation
|
|
807
|
-
.column_settings_open_changed
|
|
808
|
-
.add_listener(cb_column_settings);
|
|
809
|
-
|
|
810
|
-
vec![sub1, sub2, sub3]
|
|
811
|
-
};
|
|
812
|
-
|
|
813
|
-
let dragdrop_props_sub = {
|
|
814
|
-
let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
|
|
815
|
-
let sub1 = ctx
|
|
816
|
-
.props()
|
|
817
|
-
.presentation
|
|
818
|
-
.drop_received
|
|
819
|
-
.add_notify_listener(&cb_clear);
|
|
820
|
-
|
|
821
|
-
vec![sub1]
|
|
822
|
-
};
|
|
823
|
-
|
|
824
|
-
let mut subscriptions = Vec::new();
|
|
825
|
-
subscriptions.extend(session_props_sub);
|
|
826
|
-
subscriptions.extend(renderer_props_sub);
|
|
827
|
-
subscriptions.extend(presentation_props_sub);
|
|
828
|
-
subscriptions.extend(dragdrop_props_sub);
|
|
829
|
-
subscriptions
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/// Inject direct callbacks into the engine handles, replacing PubSub fields
|
|
833
|
-
/// that were exclusively consumed by the root component.
|
|
834
|
-
fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
|
|
835
|
-
// Session: on_stats_changed
|
|
836
|
-
{
|
|
837
|
-
let session = ctx.props().session.clone();
|
|
838
|
-
let cb = ctx.link().callback(move |_: ()| {
|
|
839
|
-
UpdateSessionStats(session.get_table_stats(), session.has_table())
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
*ctx.props().session.on_stats_changed.borrow_mut() = Some(cb);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// Session: on_table_errored
|
|
846
|
-
{
|
|
847
|
-
let session = ctx.props().session.clone();
|
|
848
|
-
let cb = ctx
|
|
849
|
-
.link()
|
|
850
|
-
.callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
|
|
851
|
-
|
|
852
|
-
*ctx.props().session.on_table_errored.borrow_mut() = Some(cb);
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
// Renderer: on_render_limits_changed (combines UpdateRenderer + column
|
|
856
|
-
// locator recheck that were previously two separate PubSub subscriptions).
|
|
857
|
-
{
|
|
858
|
-
clone!(
|
|
859
|
-
ctx.props().presentation,
|
|
860
|
-
ctx.props().renderer,
|
|
861
|
-
ctx.props().session
|
|
862
|
-
);
|
|
863
|
-
|
|
864
|
-
let cb = ctx.link().batch_callback(move |limits: RenderLimits| {
|
|
865
|
-
let mut msgs = vec![UpdateRenderer(Box::new(renderer.to_props(Some(limits))))];
|
|
866
|
-
if !limits.is_update {
|
|
867
|
-
let locator = get_current_column_locator(
|
|
868
|
-
&presentation.get_open_column_settings(),
|
|
869
|
-
&renderer,
|
|
870
|
-
&session.get_view_config(),
|
|
871
|
-
&session.metadata(),
|
|
872
|
-
);
|
|
873
|
-
|
|
874
|
-
msgs.push(OpenColumnSettings {
|
|
875
|
-
locator,
|
|
876
|
-
sender: None,
|
|
877
|
-
toggle: false,
|
|
878
|
-
});
|
|
879
|
-
}
|
|
880
|
-
|
|
881
|
-
msgs
|
|
882
|
-
});
|
|
883
|
-
|
|
884
|
-
*ctx.props().renderer.on_render_limits_changed.borrow_mut() = Some(cb);
|
|
885
|
-
}
|
|
886
|
-
|
|
887
|
-
// Presentation: on_is_workspace_changed
|
|
888
|
-
{
|
|
889
|
-
let cb = ctx.link().callback(UpdateIsWorkspace);
|
|
890
|
-
*ctx.props()
|
|
891
|
-
.presentation
|
|
892
|
-
.on_is_workspace_changed
|
|
893
|
-
.borrow_mut() = Some(cb);
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
// Drag/drop: on_dragstart (post-merge: lives on Presentation)
|
|
897
|
-
{
|
|
898
|
-
let presentation = ctx.props().presentation.clone();
|
|
899
|
-
let cb = ctx.link().callback(move |_: DragEffect| {
|
|
900
|
-
UpdateDragDrop(Box::new(presentation.drag_drop_props()))
|
|
901
|
-
});
|
|
902
|
-
|
|
903
|
-
*ctx.props().presentation.on_dragstart.borrow_mut() = Some(cb);
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
// Drag/drop: on_dragend
|
|
907
|
-
{
|
|
908
|
-
let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
|
|
909
|
-
*ctx.props().presentation.on_dragend.borrow_mut() = Some(cb);
|
|
910
|
-
}
|
|
911
|
-
}
|