perspective_viewer/components/viewer/msg.rs
1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors. ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13//! The root component's message protocol. One flat enum — the `update()` match
14//! in `viewer.rs` is its dispatch table; handler bodies live in the sibling
15//! domain modules ([`super::panels`], [`super::settings`], [`super::filters`],
16//! [`super::snapshots`]).
17
18use futures::channel::oneshot::Sender;
19use perspective_client::config::Filter;
20use perspective_js::utils::ApiResult;
21use wasm_bindgen::JsValue;
22
23use crate::components::settings_panel::SelectedTab;
24use crate::config::*;
25use crate::presentation::{
26 ColumnSettingsTab, ColumnSettingsTarget, DragDropProps, PresentationProps,
27};
28use crate::renderer::RendererProps;
29use crate::session::{SessionProps, TableLoadState};
30use crate::utils::Completion;
31
32/// The filter-bearing payload of a master panel's selection or click event
33/// (`MasterContribution`), as decoded by the host listeners in
34/// [`super::wiring`].
35#[derive(Debug)]
36pub struct MasterSelection {
37 /// The event's filter clauses (`insertFilters` for the select-detail
38 /// family, `config.filter` for clicks) — BEFORE the master's own stored
39 /// filters are subtracted (see `on_master_contribution`).
40 pub filters: Vec<Filter>,
41
42 /// A synthesized clicked-cell `[column, "==", value]` clause, used when
43 /// `filters` derives to nothing — e.g. a FLAT (un-grouped) datagrid
44 /// master, whose clicks carry no group-by path to filter on.
45 pub cell_fallback: Option<Filter>,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum Divider {
50 Settings,
51 ColumnSettings,
52}
53
54impl Divider {
55 /// The shadow-DOM selector of the divider's resizable pane (pane 0 of
56 /// its `SplitPanel`; the flex-fill pane renders bare).
57 pub fn pane_selector(self) -> &'static str {
58 match self {
59 Self::Settings => "#app_panel > .split-panel-child",
60 Self::ColumnSettings => "#modal_panel > .split-panel-child",
61 }
62 }
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub enum PaneTarget {
67 Width(i32),
68 Natural,
69}
70
71#[derive(Debug)]
72pub enum PerspectiveViewerMsg {
73 ColumnSettingsPanelAutoWidth(f64),
74 ToggleColumnSettingsPin,
75 ToggleColumnSettingsPinComplete(Sender<()>),
76 ColumnSettingsTabChanged(ColumnSettingsTab),
77 OpenColumnSettings {
78 target: Option<ColumnSettingsTarget>,
79 sender: Option<Sender<()>>,
80 toggle: bool,
81 },
82 PreloadFontsUpdate,
83
84 /// Element-level reset (the public `reset()` API): reset EVERY panel and
85 /// clear the cross-filter overlay, symmetric with
86 /// `saveWorkspace`/`restoreWorkspace`. The `bool` also clears
87 /// expressions/column settings.
88 Reset(bool, Option<Completion>),
89
90 /// Reset ONLY the named panel — or the active panel when `None` — to its
91 /// default `ViewerConfig` (the toolbar Reset button, the context menu's
92 /// "Reset" command, and the public `resetPanel()` API). The `bool` is
93 /// `Reset`'s expressions flag (toolbar shift-click); the `Completion`
94 /// resolves the `resetPanel()` promise after the reset's run completes
95 /// (invariant I6).
96 ResetPanel(Option<String>, bool, Option<Completion>),
97 Resize,
98
99 /// The set of layout panels changed (added/removed); re-render so the
100 /// layout host reconciles its `<regular-layout>` cells.
101 LayoutChanged,
102
103 /// Make the named panel active: re-target the settings panel + status bar
104 /// (and the root's session/renderer subscriptions) to its engines. The
105 /// `Completion` resolves `setActivePanel()` after the activation-chrome
106 /// nudge runs complete (invariant I6).
107 SetActivePanel(String, Option<Completion>),
108
109 /// Close the named panel: remove it from the workspace now but keep its
110 /// engines parked until [`Self::PanelClosed`] reports the layout commit
111 /// that reclaims its cell. The `Completion` resolves `removePanel()`
112 /// after the eject's teardown run completes (invariant I6) — carrying
113 /// any teardown error.
114 ClosePanel(String, Option<Completion>),
115
116 /// `MainPanel` saw the named panel leave the `regular-layout` tree, so
117 /// dispose its parked panel, or close outright one the workspace still
118 /// holds.
119 PanelClosed(String),
120
121 /// `restoreWorkspace` finished replacing the panel set in the
122 /// `Workspace` (new models inserted, old panels ejected, layout staged):
123 /// activate the named panel, re-subscribe the per-panel wiring, and
124 /// re-render — the SINGLE visible commit of the restore.
125 CommitWorkspaceRestore(String),
126
127 /// Duplicate the named panel: snapshot its config into a new independent
128 /// panel appended to the layout.
129 DuplicatePanel(String),
130
131 /// New panel: a fresh (default-config) panel bound to the named panel's
132 /// table (from the default client).
133 NewPanel(String),
134
135 /// New panel bound to the named `Table` on the named `Client` (the
136 /// context menu's "New" sub-menu). The `Client` is resolved by name from
137 /// the `Workspace` loaded-clients registry.
138 NewPanelFrom {
139 client: String,
140 table: String,
141 },
142
143 /// Toggle the named panel's master/detail (filter-source) role.
144 ToggleMaster(String),
145
146 /// A master panel's selection state, from EITHER host listener
147 /// (`perspective-global-filter` select/deselect or `perspective-click`):
148 /// `Some` REPLACES that panel's global-filter contribution, `None`
149 /// (deselect) clears it. Non-master sources are ignored by the handler.
150 MasterContribution(String, Option<MasterSelection>),
151
152 /// Remove the global filter at this index (GlobalFilterBar chip ×).
153 RemoveGlobalFilter(usize),
154
155 /// Clear all global filters (GlobalFilterBar "Clear").
156 ClearGlobalFilters,
157
158 /// Some panel's title changed (any panel, via `_title_subscriptions`);
159 /// re-render so the tab titles refresh.
160 TitlesChanged,
161 DividerMove(Divider, PaneTarget),
162 DividerPump(Divider),
163 DividerCommit(Divider, PaneTarget),
164 DividerFinish(Divider),
165 SettingsPanelTabChanged(SelectedTab),
166 SettingsPanelAutoWidth(f64),
167 ToggleDebug,
168
169 /// The toggle choreography's INTERNAL completion leaf: flip the pane
170 /// and resolve on the render commit. Never send this to toggle
171 /// settings from outside `settings.rs` — it skips the presize/resize
172 /// sweep (the pane's `SplitPanel` emits no `before-resize` and the
173 /// host box is unchanged, so nothing else resizes the plugins), which
174 /// leaves canvas plugins CSS-stretched at their old backing size. API
175 /// entry points send [`Self::ToggleSettingsInit`].
176 ToggleSettingsComplete(SettingsUpdate, Sender<()>),
177
178 /// Toggle (or force) the settings pane with the FULL choreography:
179 /// presize every visible plugin to its post-toggle box, commit the
180 /// pane, then the exactness-finalizer resize. The `Sender` resolves
181 /// after the sweep. The one settings-toggle entry point for both the
182 /// toolbar and the element API (`toggleConfig`, `restore({settings})`,
183 /// `restoreWorkspace`).
184 ///
185 /// The `bool` is `announce`: `true` when this toggle is the SOLE
186 /// carrier of the config change (a user gesture — toolbar,
187 /// `toggleConfig`), which emits `toggle-settings` + one
188 /// `perspective-config-update`; `false` for the `restore` family,
189 /// whose own view-config commit dispatch announces the settings field
190 /// — one API call, one config-update.
191 ToggleSettingsInit(
192 Option<SettingsUpdate>,
193 bool,
194 Option<Sender<ApiResult<JsValue>>>,
195 ),
196 UpdateSession(Box<SessionProps>),
197 UpdateRenderer(Box<RendererProps>),
198 UpdatePresentation(Box<PresentationProps>),
199
200 /// Update only `is_settings_open` in the presentation snapshot without
201 /// touching `available_themes` (which requires async data).
202 UpdateSettingsOpen(bool),
203 UpdateIsWorkspace(bool),
204
205 /// Update only `open_column_settings` in the presentation snapshot.
206 /// Handled in `settings.rs` (not `snapshots.rs`): a docked-drawer
207 /// mount/unmount defers the snapshot behind a presize sweep.
208 UpdateColumnSettings(Box<crate::presentation::OpenColumnSettings>),
209
210 /// Every visible panel has rendered at its post-transition box — NOW
211 /// apply the newest deferred `open_column_settings` target (the
212 /// latest-wins slot, not a copy captured at sweep spawn); the `Sender`
213 /// resolves on the render commit so the staged presents reveal in the
214 /// same paint (mirrors `ToggleSettingsComplete`).
215 UpdateColumnSettingsCommit(Sender<()>),
216 UpdateDragDrop(Box<DragDropProps>),
217
218 /// Update only the stats-derived fields of `session_props`
219 /// (`has_table_cells`, `has_table`) without touching `config`. This
220 /// prevents `stats_changed` events (e.g. from `reset()`) from propagating
221 /// a freshly-cleared config to the column selector.
222 UpdateSessionStats(bool, Option<TableLoadState>),
223
224 /// Refresh the root's render snapshot of the `Workspace`-owned global
225 /// filter set (dispatched by its `filters_changed` PubSub).
226 UpdateGlobalFilters,
227
228 /// The active panel's in-flight config-run count changed. LEVEL-
229 /// triggered: the payload is the ABSOLUTE count (RAII-settled — see
230 /// `Session::begin_config_run`), which the handler ASSIGNS to
231 /// `update_count`; there is no delta arithmetic to drift. Threaded to
232 /// `StatusIndicator` as the "updating" spinner.
233 UpdateInFlight(u32),
234}