@perspective-dev/viewer 5.2.0 → 5.3.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 +2 -2
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/perspective-viewer.d.ts +1 -0
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +2 -2
- package/dist/esm/perspective-viewer.js +4 -4
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +10 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +13 -3
- package/dist/wasm/perspective-viewer.d.ts +43 -36
- package/dist/wasm/perspective-viewer.js +194 -136
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +19 -17
- package/package.json +1 -1
- package/src/css/column-settings-panel.css +9 -5
- package/src/css/column-style.css +209 -31
- package/src/css/dom/select.css +2 -1
- package/src/css/form/code-editor.css +4 -4
- package/src/rust/agent/tools.rs +48 -17
- package/src/rust/components/column_selector/active_column.rs +2 -2
- package/src/rust/components/column_selector/add_expression_button.rs +3 -3
- package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
- package/src/rust/components/column_selector/config_selector.rs +8 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
- package/src/rust/components/column_selector/inactive_column.rs +2 -2
- package/src/rust/components/column_selector.rs +48 -4
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
- package/src/rust/components/column_settings_sidebar/style_tab.rs +262 -225
- package/src/rust/components/column_settings_sidebar.rs +88 -69
- package/src/rust/components/editable_header.rs +85 -144
- package/src/rust/components/expression_editor.rs +9 -16
- package/src/rust/components/form/code_editor.rs +16 -6
- package/src/rust/components/form/debug.rs +1 -0
- package/src/rust/components/form/mod.rs +3 -1
- package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
- package/src/rust/components/form/named_value_picker.rs +86 -0
- package/src/rust/components/form/palette_selector.rs +440 -0
- package/src/rust/components/main_panel/msg.rs +2 -2
- package/src/rust/components/main_panel/reconcile.rs +4 -4
- package/src/rust/components/plugin_tab.rs +4 -28
- package/src/rust/components/settings_panel.rs +25 -20
- package/src/rust/components/viewer/msg.rs +46 -7
- package/src/rust/components/viewer/panels.rs +11 -9
- package/src/rust/components/viewer/render.rs +8 -3
- package/src/rust/components/viewer/settings.rs +163 -23
- package/src/rust/components/viewer/snapshots.rs +46 -21
- package/src/rust/components/viewer/wiring.rs +14 -25
- package/src/rust/components/viewer.rs +12 -8
- package/src/rust/config/column_config_schema.rs +100 -41
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/options.rs +10 -0
- package/src/rust/config/viewer_config.rs +49 -55
- package/src/rust/config/workspace_config.rs +132 -5
- package/src/rust/custom_elements/viewer.rs +214 -155
- package/src/rust/custom_events.rs +14 -1
- package/src/rust/js/regular_layout.rs +3 -3
- package/src/rust/lib.rs +2 -0
- package/src/rust/presentation/column_locator.rs +19 -5
- package/src/rust/presentation.rs +192 -36
- package/src/rust/queries/column_locator.rs +74 -44
- package/src/rust/queries/get_viewer_config.rs +0 -3
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +82 -0
- package/src/rust/queries/plugin_column_styles.rs +6 -2
- package/src/rust/renderer/dispatch.rs +16 -4
- package/src/rust/renderer/plugin_config.rs +309 -47
- package/src/rust/renderer.rs +15 -42
- package/src/rust/session/column_defaults_update.rs +15 -8
- package/src/rust/session/metadata.rs +5 -1
- package/src/rust/session/props.rs +1 -0
- package/src/rust/session.rs +9 -6
- package/src/rust/tasks/create_panel.rs +44 -57
- package/src/rust/tasks/edit_expression.rs +19 -23
- package/src/rust/tasks/edit_window.rs +8 -10
- package/src/rust/tasks/eject.rs +4 -1
- package/src/rust/tasks/pipeline.rs +2 -2
- package/src/rust/tasks/presize_panels.rs +44 -5
- package/src/rust/tasks/reset_all.rs +4 -4
- package/src/rust/tasks/restore_and_render.rs +17 -44
- package/src/rust/tasks/restore_panel.rs +11 -13
- package/src/rust/tasks/set_edit_mode.rs +3 -1
- package/src/rust/tasks/sync_update_panels.rs +11 -1
- package/src/rust/tasks/update_theme.rs +23 -18
- package/src/rust/utils/css_vars.rs +26 -0
- package/src/rust/utils/mod.rs +2 -0
- package/src/rust/utils/spawn.rs +1 -1
- package/src/rust/workspace.rs +435 -272
- package/src/svg/lock.svg +7 -0
- package/src/themes/botanical.css +10 -0
- package/src/themes/defaults.css +12 -0
- package/src/themes/dracula.css +17 -0
- package/src/themes/gruvbox-dark.css +20 -0
- package/src/themes/gruvbox.css +10 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/monokai.css +22 -0
- package/src/themes/phosphor.css +5 -0
- package/src/themes/pro-dark.css +10 -0
- package/src/themes/solarized-dark.css +7 -0
- package/src/themes/solarized.css +7 -0
- package/src/themes/vaporwave.css +20 -0
- package/src/ts/perspective-viewer.ts +1 -0
- package/src/ts/plugin.ts +14 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +1 -1
- package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +1 -1
- package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +11 -3
- package/src/rust/components/form/color_range_selector.rs +0 -137
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
|
@@ -187,7 +187,7 @@ impl SessionMetadata {
|
|
|
187
187
|
}
|
|
188
188
|
|
|
189
189
|
pub fn get_expression_columns(&self) -> impl Iterator<Item = &'_ String> {
|
|
190
|
-
try {
|
|
190
|
+
let mut columns = try {
|
|
191
191
|
self.as_ref()?
|
|
192
192
|
.expr_meta
|
|
193
193
|
.as_ref()?
|
|
@@ -197,6 +197,10 @@ impl SessionMetadata {
|
|
|
197
197
|
}
|
|
198
198
|
.into_iter()
|
|
199
199
|
.flatten()
|
|
200
|
+
.collect::<Vec<_>>();
|
|
201
|
+
|
|
202
|
+
columns.sort_unstable();
|
|
203
|
+
columns.into_iter()
|
|
200
204
|
}
|
|
201
205
|
|
|
202
206
|
/// Returns the full original expression `String` for an expression alias.
|
package/src/rust/session.rs
CHANGED
|
@@ -719,9 +719,11 @@ impl Session {
|
|
|
719
719
|
config_static: &PluginStaticConfig,
|
|
720
720
|
) {
|
|
721
721
|
use self::column_defaults_update::*;
|
|
722
|
+
let config = self.get_view_config();
|
|
722
723
|
config_update.set_update_column_defaults(
|
|
723
724
|
&self.metadata(),
|
|
724
|
-
&
|
|
725
|
+
&config,
|
|
726
|
+
&config.columns,
|
|
725
727
|
config_static,
|
|
726
728
|
)
|
|
727
729
|
}
|
|
@@ -737,7 +739,11 @@ impl Session {
|
|
|
737
739
|
config_static: &PluginStaticConfig,
|
|
738
740
|
) {
|
|
739
741
|
use self::column_defaults_update::*;
|
|
740
|
-
config_update.set_update_rollup_defaults(
|
|
742
|
+
config_update.set_update_rollup_defaults(
|
|
743
|
+
&self.metadata(),
|
|
744
|
+
&self.get_view_config(),
|
|
745
|
+
config_static,
|
|
746
|
+
)
|
|
741
747
|
}
|
|
742
748
|
|
|
743
749
|
/// Apply a `ViewConfigUpdate` to the live config — the ONLY view-config
|
|
@@ -889,7 +895,7 @@ impl Session {
|
|
|
889
895
|
.pending_dispatches
|
|
890
896
|
.set(self.0.pending_dispatches.get() + 1);
|
|
891
897
|
let session = self.clone();
|
|
892
|
-
ApiFuture::
|
|
898
|
+
ApiFuture::spawn_named("config-update-dispatch", async move {
|
|
893
899
|
let result = fut.await;
|
|
894
900
|
let remaining = session.0.pending_dispatches.get() - 1;
|
|
895
901
|
session.0.pending_dispatches.set(remaining);
|
|
@@ -1165,9 +1171,6 @@ impl Session {
|
|
|
1165
1171
|
let old = self.borrow_mut().view_sub.take();
|
|
1166
1172
|
ApiFuture::spawn(old.delete());
|
|
1167
1173
|
self.borrow_mut().view_sub = Some(sub);
|
|
1168
|
-
// REBUILD: a `View` was genuinely constructed and bound — the one
|
|
1169
|
-
// place `view_created` may fire, and the one place the `FreshView`
|
|
1170
|
-
// full-draw witness is minted from a rebuild.
|
|
1171
1174
|
self.view_created.emit(());
|
|
1172
1175
|
self.commit_reconciled.emit(());
|
|
1173
1176
|
match self.get_view() {
|
|
@@ -20,7 +20,7 @@ use crate::renderer::*;
|
|
|
20
20
|
use crate::session::{ResetOptions, Session};
|
|
21
21
|
use crate::tasks::*;
|
|
22
22
|
use crate::utils::*;
|
|
23
|
-
use crate::workspace::{Panel, PanelId, Workspace};
|
|
23
|
+
use crate::workspace::{Panel, PanelId, PanelPhase, Workspace};
|
|
24
24
|
use crate::*;
|
|
25
25
|
|
|
26
26
|
/// Build the full set of subscriptions a [`Panel`] owns for its lifetime: its
|
|
@@ -70,29 +70,19 @@ fn wire_panel_render_sub(session: &Session, renderer: &Renderer) -> Subscription
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/// Create a new independent panel (own `Session` + `Renderer` + id) and
|
|
73
|
-
/// restore `
|
|
74
|
-
/// create-if-missing upsert
|
|
75
|
-
///
|
|
76
|
-
///
|
|
77
|
-
///
|
|
78
|
-
///
|
|
79
|
-
///
|
|
80
|
-
/// A `table` is OPTIONAL here: an update with none creates a deferred
|
|
81
|
-
/// panel that a later `load()` binds, which is the pre-existing
|
|
82
|
-
/// `restore`-then-`load` contract and the same state the reserved
|
|
83
|
-
/// `load()` path produces. The routes where a table-less panel WOULD be
|
|
84
|
-
/// permanently blank require one in their own argument types instead —
|
|
85
|
-
/// `addPanel`'s [`ViewerConfigInitial`], `WorkspaceConfigUpdate`'s
|
|
86
|
-
/// `panels` entries, and the agent's `add_panel` — so the guarantee sits
|
|
87
|
-
/// at the boundary that owns it rather than here, which would sweep
|
|
88
|
-
/// `restore`'s patch in with them.
|
|
73
|
+
/// restore `config` into it. Shared by `addPanel` and `restore`'s
|
|
74
|
+
/// create-if-missing upsert. `id` is the panel's id — provided when
|
|
75
|
+
/// restoring into a specific named slot, or `None` to generate a fresh
|
|
76
|
+
/// one. `theme` is stripped (element-level, not per-panel) and `client` —
|
|
77
|
+
/// or the element's default client when `None` — is bound so the config's
|
|
78
|
+
/// `table` resolves against it.
|
|
89
79
|
pub(crate) async fn create_panel(
|
|
90
80
|
elem: &HtmlElement,
|
|
91
81
|
presentation: &Presentation,
|
|
92
82
|
workspace: &Workspace,
|
|
93
83
|
notify: &Callback<()>,
|
|
94
84
|
id: Option<PanelId>,
|
|
95
|
-
|
|
85
|
+
config: ViewerConfigInitial,
|
|
96
86
|
client: Option<perspective_client::Client>,
|
|
97
87
|
) -> ApiResult<PanelId> {
|
|
98
88
|
let (id, session, renderer, update) = create_panel_model(
|
|
@@ -100,36 +90,25 @@ pub(crate) async fn create_panel(
|
|
|
100
90
|
presentation,
|
|
101
91
|
workspace,
|
|
102
92
|
id,
|
|
103
|
-
|
|
93
|
+
config.into(),
|
|
104
94
|
client,
|
|
105
|
-
Placement::
|
|
95
|
+
Placement::Staged,
|
|
106
96
|
);
|
|
107
97
|
|
|
108
98
|
stamp_global_overlay(workspace, &id, &session);
|
|
109
|
-
// STAGE the panel (model-first): `MainPanel` renders its plugin slot
|
|
110
|
-
// into a hidden pre-sized wrapper — not a layout cell — so the restore
|
|
111
|
-
// below completes its first draw invisibly at (near-)final size. The
|
|
112
|
-
// promote below then re-renders, and `MainPanel::reconcile` inserts an
|
|
113
|
-
// ALREADY-DRAWN panel: the one layout transition reveals content,
|
|
114
|
-
// never a blank frame.
|
|
115
|
-
workspace.stage_panel(&id);
|
|
116
99
|
notify.emit(());
|
|
117
100
|
|
|
118
|
-
|
|
119
|
-
// loading — the pre-staging progressive reveal) rather than leaving
|
|
120
|
-
// the layout without feedback. Races the completion promote below on
|
|
121
|
-
// the staged flag; only the winner re-renders.
|
|
122
|
-
{
|
|
101
|
+
ApiFuture::spawn({
|
|
123
102
|
clone!(workspace, id, notify);
|
|
124
|
-
|
|
103
|
+
async move {
|
|
125
104
|
set_timeout(STAGING_DEADLINE_MS).await?;
|
|
126
|
-
if workspace.
|
|
105
|
+
if workspace.promote(&id) {
|
|
127
106
|
notify.emit(());
|
|
128
107
|
}
|
|
129
108
|
|
|
130
109
|
Ok(())
|
|
131
|
-
}
|
|
132
|
-
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
133
112
|
|
|
134
113
|
// A fresh panel is never the active one, so it needs no `root` for the
|
|
135
114
|
// (active-only) settings-sidebar sequencing.
|
|
@@ -146,7 +125,7 @@ pub(crate) async fn create_panel(
|
|
|
146
125
|
|
|
147
126
|
// Promote on completion AND error — a failed restore's error state
|
|
148
127
|
// must become visible too.
|
|
149
|
-
if workspace.
|
|
128
|
+
if workspace.promote(&id) {
|
|
150
129
|
notify.emit(());
|
|
151
130
|
}
|
|
152
131
|
|
|
@@ -156,10 +135,17 @@ pub(crate) async fn create_panel(
|
|
|
156
135
|
|
|
157
136
|
/// Where [`create_panel_model`] registers the new panel model.
|
|
158
137
|
pub(crate) enum Placement {
|
|
159
|
-
/// Into the placed panel set
|
|
160
|
-
///
|
|
138
|
+
/// Into the placed panel set at [`PanelPhase::Placed`] — a live, visible
|
|
139
|
+
/// panel from the start (`restoreWorkspace` mounts panels directly at
|
|
140
|
+
/// their saved layout positions).
|
|
161
141
|
Placed,
|
|
162
142
|
|
|
143
|
+
/// Into the placed panel set at [`PanelPhase::Staging`] — withheld from
|
|
144
|
+
/// the layout while its first draw completes in the hidden staging
|
|
145
|
+
/// wrapper, then promoted ([`Workspace::promote`]) by restore completion
|
|
146
|
+
/// or the [`STAGING_DEADLINE_MS`] deadline, whichever wins.
|
|
147
|
+
Staged,
|
|
148
|
+
|
|
163
149
|
/// Into the reservation slot ([`Workspace::reserve_panel`]) — a pending
|
|
164
150
|
/// `load()`'s first panel, held out of the placed set until it is
|
|
165
151
|
/// claimed ([`place_reserved`]) or discarded.
|
|
@@ -180,32 +166,23 @@ pub(crate) fn create_panel_model(
|
|
|
180
166
|
client: Option<perspective_client::Client>,
|
|
181
167
|
placement: Placement,
|
|
182
168
|
) -> (PanelId, Session, Renderer, ViewerConfigUpdate) {
|
|
183
|
-
// Authored-theme boot: the FIRST panel of an empty element with no explicit
|
|
184
|
-
// theme adopts the element's `theme` attribute (a one-time initial
|
|
185
|
-
// selection; the attribute is otherwise an active-panel mirror). Captured
|
|
186
|
-
// before `insert_panel` makes the workspace non-empty.
|
|
187
|
-
let boot_theme = (workspace.is_empty() && matches!(update.theme, OptionalUpdate::Missing))
|
|
188
|
-
.then(|| elem.get_attribute("theme"))
|
|
189
|
-
.flatten();
|
|
190
|
-
|
|
191
169
|
let session = Session::new();
|
|
192
170
|
let renderer = Renderer::new(elem);
|
|
193
171
|
let id = id.unwrap_or_else(|| workspace.generate_id());
|
|
194
172
|
renderer.set_slot_name(id.as_str());
|
|
195
|
-
renderer.set_default_theme(presentation.default_theme_name_sync());
|
|
196
173
|
let subs = wire_panel_subs(elem, presentation, &session, &renderer);
|
|
197
174
|
let panel = Panel::new(id.clone(), session.clone(), renderer.clone(), subs);
|
|
198
175
|
match placement {
|
|
199
|
-
Placement::Placed => workspace.insert_panel(panel),
|
|
176
|
+
Placement::Placed => workspace.insert_panel(panel, PanelPhase::Placed),
|
|
177
|
+
Placement::Staged => workspace.insert_panel(panel, PanelPhase::Staging),
|
|
200
178
|
Placement::Reserved => workspace.reserve_panel(panel),
|
|
201
179
|
}
|
|
202
180
|
|
|
203
181
|
update.settings = OptionalUpdate::Missing;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
}
|
|
182
|
+
renderer.set_theme(match &update.theme {
|
|
183
|
+
OptionalUpdate::Update(theme) => Some(theme.clone()),
|
|
184
|
+
_ => presentation.active_theme_name_sync(),
|
|
185
|
+
});
|
|
209
186
|
|
|
210
187
|
update.theme = OptionalUpdate::Missing;
|
|
211
188
|
if let Some(client) = client.or_else(|| workspace.default_client()) {
|
|
@@ -221,8 +198,18 @@ pub(crate) fn create_panel_model(
|
|
|
221
198
|
(id, session, renderer, update)
|
|
222
199
|
}
|
|
223
200
|
|
|
224
|
-
|
|
225
|
-
|
|
201
|
+
/// Claim the pending `load()` reservation into the placed set
|
|
202
|
+
/// ([`Workspace::claim_reserved`]), stamping the global-filter overlay and
|
|
203
|
+
/// notifying the root. `has_table` is whether the claimant carried a
|
|
204
|
+
/// `table`, recorded atomically with the claim — pass `true` when `load()`
|
|
205
|
+
/// itself places its reservation (a `Table` payload, or error surfacing),
|
|
206
|
+
/// which is not a restore claim and must never arm the epilogue's eviction.
|
|
207
|
+
pub(crate) fn place_reserved(
|
|
208
|
+
workspace: &Workspace,
|
|
209
|
+
notify: &Callback<()>,
|
|
210
|
+
has_table: bool,
|
|
211
|
+
) -> Option<Panel> {
|
|
212
|
+
let panel = workspace.claim_reserved(has_table)?;
|
|
226
213
|
stamp_global_overlay(workspace, &panel.id, &panel.session);
|
|
227
214
|
notify.emit(());
|
|
228
215
|
Some(panel)
|
|
@@ -230,7 +217,7 @@ pub(crate) fn place_reserved(workspace: &Workspace, notify: &Callback<()>) -> Op
|
|
|
230
217
|
|
|
231
218
|
/// Tear down a [`Panel`] already removed from the [`Workspace`]: dispose its
|
|
232
219
|
/// renderer (slot-scoped plugin + light-DOM cleanup) and eject its table.
|
|
233
|
-
/// Shared by the root's `ClosePanel` handler and
|
|
220
|
+
/// Shared by the root's `ClosePanel` handler and `restoreWorkspace`'s
|
|
234
221
|
/// batch replacement of the pre-existing panel set.
|
|
235
222
|
pub(crate) fn eject_panel(panel: Panel) -> ApiFuture<()> {
|
|
236
223
|
let was_errored = panel.session.is_errored();
|
|
@@ -18,14 +18,22 @@ use perspective_client::clone;
|
|
|
18
18
|
use perspective_client::config::{Expression, ViewConfigUpdate};
|
|
19
19
|
|
|
20
20
|
use super::apply_and_render;
|
|
21
|
-
use crate::presentation::{
|
|
21
|
+
use crate::presentation::{
|
|
22
|
+
ColumnSettingsTab, ColumnSettingsTarget, OpenColumnSettings, Presentation,
|
|
23
|
+
};
|
|
22
24
|
use crate::renderer::Renderer;
|
|
23
25
|
use crate::session::Session;
|
|
24
26
|
use crate::*;
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
fn reopen(presentation: &Presentation, name: String) {
|
|
29
|
+
presentation.set_open_column_settings(Some(OpenColumnSettings {
|
|
30
|
+
target: Some(ColumnSettingsTarget::Column(name)),
|
|
31
|
+
tab: Some(ColumnSettingsTab::Attributes),
|
|
32
|
+
}));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/// Replace the expression at `old_name` with `new_expr` and re-render,
|
|
36
|
+
/// moving the column-settings drawer onto the renamed expression.
|
|
29
37
|
pub fn update_expr(
|
|
30
38
|
session: &Session,
|
|
31
39
|
renderer: &Renderer,
|
|
@@ -39,25 +47,21 @@ pub fn update_expr(
|
|
|
39
47
|
.to_props()
|
|
40
48
|
.create_replace_expression_update(&old_name, &new_expr);
|
|
41
49
|
|
|
42
|
-
apply_and_render(&session, &renderer, update)
|
|
43
|
-
presentation.
|
|
44
|
-
|
|
45
|
-
tab: Some(ColumnSettingsTab::Attributes),
|
|
46
|
-
}));
|
|
47
|
-
|
|
50
|
+
let run = apply_and_render(&session, &renderer, update)?;
|
|
51
|
+
reopen(&presentation, new_expr.name.to_string());
|
|
52
|
+
run.await?;
|
|
48
53
|
Ok(())
|
|
49
54
|
});
|
|
50
55
|
}
|
|
51
56
|
|
|
52
|
-
/// Insert `expr` into the session's expressions and re-render
|
|
53
|
-
///
|
|
57
|
+
/// Insert `expr` into the session's expressions and re-render, moving the
|
|
58
|
+
/// column-settings drawer onto the new expression.
|
|
54
59
|
pub fn save_expr(
|
|
55
60
|
session: &Session,
|
|
56
61
|
renderer: &Renderer,
|
|
57
62
|
presentation: &Presentation,
|
|
58
63
|
expr: Expression,
|
|
59
64
|
) -> ApiResult<()> {
|
|
60
|
-
let presentation = presentation.clone();
|
|
61
65
|
let expr_name: String = expr.name.clone().into();
|
|
62
66
|
let task = {
|
|
63
67
|
let mut serde_exprs = session.get_view_config().expressions.clone();
|
|
@@ -68,16 +72,8 @@ pub fn save_expr(
|
|
|
68
72
|
})
|
|
69
73
|
}?;
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
presentation.set_open_column_settings(Some(OpenColumnSettings {
|
|
74
|
-
locator: Some(ColumnLocator::Expression(expr_name)),
|
|
75
|
-
tab: Some(ColumnSettingsTab::Attributes),
|
|
76
|
-
}));
|
|
77
|
-
|
|
78
|
-
Ok(())
|
|
79
|
-
});
|
|
80
|
-
|
|
75
|
+
reopen(presentation, expr_name);
|
|
76
|
+
ApiFuture::spawn(task);
|
|
81
77
|
Ok(())
|
|
82
78
|
}
|
|
83
79
|
|
|
@@ -14,14 +14,16 @@ use perspective_client::clone;
|
|
|
14
14
|
use perspective_client::config::{Filter, ViewConfigUpdate, WindowSpec};
|
|
15
15
|
|
|
16
16
|
use super::apply_and_render;
|
|
17
|
-
use crate::presentation::{
|
|
17
|
+
use crate::presentation::{
|
|
18
|
+
ColumnSettingsTab, ColumnSettingsTarget, OpenColumnSettings, Presentation,
|
|
19
|
+
};
|
|
18
20
|
use crate::renderer::Renderer;
|
|
19
21
|
use crate::session::Session;
|
|
20
22
|
use crate::*;
|
|
21
23
|
|
|
22
24
|
fn reopen(presentation: &Presentation, name: String) {
|
|
23
25
|
presentation.set_open_column_settings(Some(OpenColumnSettings {
|
|
24
|
-
|
|
26
|
+
target: Some(ColumnSettingsTarget::Column(name)),
|
|
25
27
|
tab: Some(ColumnSettingsTab::Window),
|
|
26
28
|
}));
|
|
27
29
|
}
|
|
@@ -36,7 +38,6 @@ pub fn save_window(
|
|
|
36
38
|
name: String,
|
|
37
39
|
spec: WindowSpec,
|
|
38
40
|
) -> ApiResult<()> {
|
|
39
|
-
let presentation = presentation.clone();
|
|
40
41
|
let task = {
|
|
41
42
|
let mut windows = session.get_view_config().windows.clone();
|
|
42
43
|
windows.insert(name.clone(), spec);
|
|
@@ -46,12 +47,8 @@ pub fn save_window(
|
|
|
46
47
|
})
|
|
47
48
|
}?;
|
|
48
49
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
reopen(&presentation, name);
|
|
52
|
-
Ok(())
|
|
53
|
-
});
|
|
54
|
-
|
|
50
|
+
reopen(presentation, name);
|
|
51
|
+
ApiFuture::spawn(task);
|
|
55
52
|
Ok(())
|
|
56
53
|
}
|
|
57
54
|
|
|
@@ -134,8 +131,9 @@ pub fn update_window(
|
|
|
134
131
|
update
|
|
135
132
|
};
|
|
136
133
|
|
|
137
|
-
apply_and_render(&session, &renderer, update)
|
|
134
|
+
let run = apply_and_render(&session, &renderer, update)?;
|
|
138
135
|
reopen(&presentation, new_name);
|
|
136
|
+
run.await?;
|
|
139
137
|
Ok(())
|
|
140
138
|
});
|
|
141
139
|
}
|
package/src/rust/tasks/eject.rs
CHANGED
|
@@ -23,11 +23,14 @@ use crate::workspace::Workspace;
|
|
|
23
23
|
/// Tear down the entire viewer: dispose EVERY panel's engines (emit
|
|
24
24
|
/// `table_unloaded`, destroy the renderer under its own draw lock, eject the
|
|
25
25
|
/// session), then drop the Yew root once. Fans out over all panels — not just
|
|
26
|
-
/// the seed — so panels added via `addPanel` or
|
|
26
|
+
/// the seed — so panels added via `addPanel` or `restoreWorkspace` don't
|
|
27
27
|
/// leak their `View`/`Table` + plugin when the element is deleted or ejected.
|
|
28
28
|
pub fn delete_all<T: Component>(workspace: &Workspace, root: &Root<T>) -> ApiFuture<()> {
|
|
29
29
|
clone!(workspace, root);
|
|
30
30
|
ApiFuture::new(async move {
|
|
31
|
+
// A staged-but-unapplied `restoreWorkspace` layout tree must not
|
|
32
|
+
// outlive the panels it names.
|
|
33
|
+
workspace.take_pending_layout();
|
|
31
34
|
let panels = workspace
|
|
32
35
|
.take_reserved()
|
|
33
36
|
.into_iter()
|
|
@@ -285,12 +285,12 @@ pub(crate) async fn locked_run(
|
|
|
285
285
|
|
|
286
286
|
let view_config_snapshot = session.get_view_config().clone();
|
|
287
287
|
let plugin_config_changed =
|
|
288
|
-
renderer.update_plugin_config(&view_config_snapshot, spec.plugin_config)
|
|
288
|
+
renderer.update_plugin_config(&view_config_snapshot, spec.plugin_config)?;
|
|
289
289
|
let columns_config_changed = renderer.update_columns_configs(
|
|
290
290
|
&view_config_snapshot,
|
|
291
291
|
&session,
|
|
292
292
|
spec.columns_config,
|
|
293
|
-
)
|
|
293
|
+
)?;
|
|
294
294
|
|
|
295
295
|
let changed = plugin_config_changed || columns_config_changed;
|
|
296
296
|
if changed || plugin_swapped {
|
|
@@ -131,11 +131,14 @@ pub async fn presize_visible_panels_grown(
|
|
|
131
131
|
presize_visible_panels_scaled(workspace, elem, width_ratio, height_ratio).await
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
/// Pre-size each visible plugin to its SHRUNK
|
|
135
|
-
///
|
|
136
|
-
/// the
|
|
137
|
-
///
|
|
138
|
-
///
|
|
134
|
+
/// Pre-size each visible plugin to its cell SHRUNK by `(delta_w, delta_h)` —
|
|
135
|
+
/// GROWN when a delta is negative — *before* the layout change commits. Used
|
|
136
|
+
/// by the settings-pane open (P2, deltas `(layout_area.w − mpc.w,
|
|
137
|
+
/// main_column.h − mpc.h)` cached at the last close — the pane and divider
|
|
138
|
+
/// width and the docked status-bar height, both stable across a close/open
|
|
139
|
+
/// cycle since the pane width persists in the override) and by the
|
|
140
|
+
/// column-settings pin toggle and docked-drawer mount/unmount (signed delta
|
|
141
|
+
/// measured live at toggle time).
|
|
139
142
|
pub async fn presize_visible_panels_open(
|
|
140
143
|
workspace: &Workspace,
|
|
141
144
|
elem: &web_sys::HtmlElement,
|
|
@@ -199,6 +202,7 @@ async fn presize_visible_panels_scaled(
|
|
|
199
202
|
return StagedPresents::default();
|
|
200
203
|
}
|
|
201
204
|
|
|
205
|
+
let mpc = shadow_rect(elem, "#main_panel_container");
|
|
202
206
|
let mut last_chrome: Option<(f64, f64)> = None;
|
|
203
207
|
let targets = workspace
|
|
204
208
|
.panels()
|
|
@@ -222,6 +226,23 @@ async fn presize_visible_panels_scaled(
|
|
|
222
226
|
|
|
223
227
|
last_chrome = chrome.or(last_chrome);
|
|
224
228
|
let (cw, ch) = last_chrome.unwrap_or(super::CHROME_FALLBACK);
|
|
229
|
+
|
|
230
|
+
// Per axis, chrome can never exceed the container's
|
|
231
|
+
// non-plugin remainder: with no sibling panel on that axis,
|
|
232
|
+
// `mpc − box` IS the exact chrome, while the frame-margin
|
|
233
|
+
// measurement overcounts (margins that overlap container
|
|
234
|
+
// padding) — the source of a ~2px target miss that pushed
|
|
235
|
+
// every commit past the plugin transport's ±0.5px resize
|
|
236
|
+
// dedupe, costing an extra corrective render. With siblings
|
|
237
|
+
// the remainder is large and the measured chrome wins.
|
|
238
|
+
let (cw, ch) = match &mpc {
|
|
239
|
+
Some(mpc) => (
|
|
240
|
+
cw.min((mpc.width() - plugin_box.width()).max(0.0)),
|
|
241
|
+
ch.min((mpc.height() - plugin_box.height()).max(0.0)),
|
|
242
|
+
),
|
|
243
|
+
None => (cw, ch),
|
|
244
|
+
};
|
|
245
|
+
|
|
225
246
|
let w = ((plugin_box.width() + cw) * width_ratio - cw).max(0.0);
|
|
226
247
|
let h = ((plugin_box.height() + ch) * height_ratio - ch).max(0.0);
|
|
227
248
|
Some((panel, w, h))
|
|
@@ -300,6 +321,24 @@ pub fn measure_settings_open_deltas(elem: &web_sys::HtmlElement) -> Option<(f64,
|
|
|
300
321
|
))
|
|
301
322
|
}
|
|
302
323
|
|
|
324
|
+
/// The signed `#main_panel_container` width delta a column-settings pin
|
|
325
|
+
/// toggle will cause: the docked `#modal_panel`'s flex box spans its drawer
|
|
326
|
+
/// child plus the overlapping divider (`drawer.right − divider.left`, the
|
|
327
|
+
/// same span in both modes since `.pinned` reparents nothing) — PINNING
|
|
328
|
+
/// takes it from the main column (positive, shrink), UNPINNING releases it
|
|
329
|
+
/// back (negative, grow). `None` when the drawer isn't mounted.
|
|
330
|
+
pub fn measure_column_settings_pin_delta(
|
|
331
|
+
elem: &web_sys::HtmlElement,
|
|
332
|
+
is_pinned: bool,
|
|
333
|
+
) -> Option<f64> {
|
|
334
|
+
let drawer = shadow_rect(elem, "#modal_panel > .split-panel-child:first-child")?;
|
|
335
|
+
let divider_left = shadow_rect(elem, "#modal_panel > .split-panel-divider")
|
|
336
|
+
.map(|r| r.left())
|
|
337
|
+
.unwrap_or_else(|| drawer.left());
|
|
338
|
+
let width = (drawer.right() - divider_left).max(0.0);
|
|
339
|
+
Some(if is_pinned { -width } else { width })
|
|
340
|
+
}
|
|
341
|
+
|
|
303
342
|
/// The frame chrome a plugin's grid cell has that the plugin itself doesn't
|
|
304
343
|
/// occupy, as `(width, height)` px: margin + border + titlebar. Measured live
|
|
305
344
|
/// from a `regular-layout-frame` and its plugin — robust to theme/CSS changes —
|
|
@@ -61,11 +61,11 @@ pub fn reset_all(
|
|
|
61
61
|
|
|
62
62
|
presentation.reset_available_themes(None).await;
|
|
63
63
|
if all {
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
renderer.set_theme(None);
|
|
64
|
+
// Put this panel back on the registry default CONCRETELY —
|
|
65
|
+
// `reset_theme` only resets the host, which an explicitly-themed
|
|
66
|
+
// panel would otherwise keep overriding.
|
|
68
67
|
presentation.reset_theme().await?;
|
|
68
|
+
renderer.set_theme(presentation.get_default_theme_name().await);
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
// For `all = true`, route the bucket clears through `restore_and_render`'s
|
|
@@ -18,6 +18,7 @@ use futures::Future;
|
|
|
18
18
|
use perspective_client::clone;
|
|
19
19
|
|
|
20
20
|
use super::pipeline::{RunOrigin, RunSpec, locked_run};
|
|
21
|
+
use super::update_theme::seed_panel_theme;
|
|
21
22
|
use crate::config::{OptionalUpdate, ViewerConfigUpdate};
|
|
22
23
|
use crate::presentation::Presentation;
|
|
23
24
|
use crate::renderer::Renderer;
|
|
@@ -70,11 +71,6 @@ pub fn restore_and_render(
|
|
|
70
71
|
session.set_title(None);
|
|
71
72
|
}
|
|
72
73
|
|
|
73
|
-
// Mirror a config-carried theme onto the host attribute (the shared
|
|
74
|
-
// chrome). The restyle a theme change requires is owned by the
|
|
75
|
-
// MUTATION SITES (`restorePanel`'s own-theme tail, the theme-picker
|
|
76
|
-
// task), not here — this run's own draw below already stamps the new
|
|
77
|
-
// effective theme before the plugin's first style read.
|
|
78
74
|
match theme_name {
|
|
79
75
|
OptionalUpdate::SetDefault => {
|
|
80
76
|
let current_name = presentation.get_selected_theme_name().await;
|
|
@@ -83,59 +79,31 @@ pub fn restore_and_render(
|
|
|
83
79
|
}
|
|
84
80
|
},
|
|
85
81
|
OptionalUpdate::Update(x) => {
|
|
86
|
-
// No pre-resolution gate: `set_theme_name` stamps the host
|
|
87
|
-
// attribute SYNCHRONOUSLY before its registry await and
|
|
88
|
-
// no-ops on literal equality itself. The old
|
|
89
|
-
// `get_selected_theme_name().await` guard was precisely
|
|
90
|
-
// the former-theme window on a cold registry — the host
|
|
91
|
-
// held the old attribute until stylesheet parsing (and
|
|
92
|
-
// everything queued behind it) resolved.
|
|
93
82
|
presentation.set_theme_name(Some(&x)).await?;
|
|
94
83
|
},
|
|
95
84
|
_ => {},
|
|
96
85
|
};
|
|
97
86
|
|
|
98
|
-
// Resolve the target plugin here (pure — needed now for
|
|
99
|
-
// `set_update_column_defaults`), but COMMIT it only inside the locked
|
|
100
|
-
// run below, atomically with the view rebind it belongs to. No swap
|
|
101
|
-
// intent may exist outside that run: an unrelated run that wins the
|
|
102
|
-
// lock first (e.g. a `table_updated` redraw) must observe either the
|
|
103
|
-
// fully-old or fully-new world, never a staged half of this one.
|
|
104
87
|
let resolved_plugin = renderer.resolve_plugin_update(&plugin);
|
|
105
88
|
if let Some((_, metadata)) = &resolved_plugin {
|
|
106
89
|
session.set_update_column_defaults(&mut view_config, metadata);
|
|
107
90
|
} else {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
session.set_update_rollup_defaults(&mut view_config, &
|
|
91
|
+
let metadata = if renderer.active_plugin().is_none() {
|
|
92
|
+
renderer
|
|
93
|
+
.resolve_plugin_update(&OptionalUpdate::SetDefault)
|
|
94
|
+
.map(|(_, metadata)| metadata)
|
|
95
|
+
.unwrap_or_else(|| renderer.metadata())
|
|
96
|
+
} else {
|
|
97
|
+
renderer.metadata()
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
session.set_update_rollup_defaults(&mut view_config, &metadata);
|
|
118
101
|
}
|
|
119
102
|
|
|
120
103
|
let plugin_idx = resolved_plugin.map(|(idx, _)| idx);
|
|
121
|
-
|
|
122
|
-
// The config COMMIT: synchronous, validated, atomic (I1/I4). Under
|
|
123
|
-
// I2/I3 committing before the lock is safe — whichever queued run
|
|
124
|
-
// snapshots next picks it up, and this run's own snapshot (taken
|
|
125
|
-
// inside the lock below) can only be this commit or fresher.
|
|
126
104
|
session.commit_view_config(view_config)?;
|
|
127
|
-
|
|
128
|
-
// Spinner accounting (RAII): held to the end of this restore —
|
|
129
|
-
// INCLUDING the deferred-draw exit (no table yet → no
|
|
130
|
-
// `bind_snapshot`), which under the old edge-counted scheme
|
|
131
|
-
// stranded the `StatusIndicator` spinner permanently.
|
|
132
105
|
let _run_token = session.begin_config_run();
|
|
133
|
-
|
|
134
|
-
// Awaits theme-registry init, so the stamp below can never observe a
|
|
135
|
-
// pre-init (empty) theme set on a cold first load. Seeds this
|
|
136
|
-
// panel's renderer default-theme cache, which every locked draw
|
|
137
|
-
// stamps the effective theme from.
|
|
138
|
-
renderer.set_default_theme(presentation.get_default_theme_name().await);
|
|
106
|
+
seed_panel_theme(&presentation, &renderer).await;
|
|
139
107
|
locked_run(&session, &renderer, RunSpec {
|
|
140
108
|
origin,
|
|
141
109
|
plugin_idx,
|
|
@@ -146,6 +114,11 @@ pub fn restore_and_render(
|
|
|
146
114
|
})
|
|
147
115
|
.await?;
|
|
148
116
|
|
|
117
|
+
if renderer.needs_restyle() {
|
|
118
|
+
renderer.restyle_all().await?;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
presentation.publish_theme_config().await?;
|
|
149
122
|
Ok(())
|
|
150
123
|
})
|
|
151
124
|
}
|