Skip to main content

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