@perspective-dev/viewer 5.1.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 +9 -9
- 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/docs/perspective-docs.json +1 -0
- package/dist/esm/column-format.d.ts +19 -28
- package/dist/esm/perspective-viewer.d.ts +3 -0
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +10 -0
- package/dist/esm/providers.d.ts +79 -0
- package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
- package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
- package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
- package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
- package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
- package/dist/esm/ts-rs/Notation.d.ts +17 -0
- package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +42 -2
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
- package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
- package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
- package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
- package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
- package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
- package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/Unit.d.ts +1 -0
- package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
- package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +42 -2
- package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +63 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +3 -2
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +20 -9
- package/dist/wasm/perspective-viewer.d.ts +157 -41
- package/dist/wasm/perspective-viewer.js +452 -151
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +22 -17
- package/package.json +2 -1
- package/src/css/_viewer_bundle.css +1 -0
- package/src/css/column-selector.css +8 -0
- package/src/css/column-settings-panel.css +9 -5
- package/src/css/column-style.css +209 -31
- package/src/css/config-selector.css +38 -3
- package/src/css/dom/select.css +2 -1
- package/src/css/form/code-editor.css +6 -7
- package/src/css/form/mirrored-textarea.css +81 -0
- package/src/css/viewer.css +328 -0
- package/src/rust/agent/client/mod.rs +19 -0
- package/src/rust/agent/client/protocol.rs +320 -0
- package/src/rust/agent/client/transport.rs +287 -0
- package/src/rust/agent/client/turn.rs +174 -0
- package/src/rust/agent/config.rs +159 -0
- package/src/rust/agent/docs.rs +293 -0
- package/src/rust/agent/markdown/block.rs +509 -0
- package/src/rust/agent/markdown/entity.rs +52 -0
- package/src/rust/agent/markdown/events.rs +71 -0
- package/src/rust/agent/markdown/inline.rs +497 -0
- package/src/rust/agent/markdown/mod.rs +466 -0
- package/src/rust/agent/mod.rs +23 -0
- package/src/rust/agent/preamble.md +16 -0
- package/src/rust/agent/runtime.rs +201 -0
- package/src/rust/agent/slot.rs +236 -0
- package/src/rust/agent/tools.rs +1141 -0
- package/src/rust/components/chat_panel.rs +289 -0
- 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 +84 -7
- 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 +55 -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/containers/dragdrop_list.rs +37 -0
- 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 +29 -28
- package/src/rust/components/form/debug.rs +9 -5
- package/src/rust/components/form/mirrored_textarea.rs +165 -0
- package/src/rust/components/form/mod.rs +4 -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/mod.rs +2 -0
- package/src/rust/components/plugin_tab.rs +4 -28
- package/src/rust/components/settings_panel.rs +105 -22
- package/src/rust/components/viewer/msg.rs +46 -7
- package/src/rust/components/viewer/panels.rs +17 -20
- package/src/rust/components/viewer/render.rs +10 -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/components/window_editor.rs +75 -103
- package/src/rust/config/column_config_schema.rs +100 -41
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/datetime_column_style/custom.rs +17 -0
- package/src/rust/config/datetime_column_style/simple.rs +10 -0
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/number_string_format.rs +33 -0
- package/src/rust/config/options.rs +28 -0
- package/src/rust/config/plugin_static_config.rs +120 -5
- package/src/rust/config/viewer_config.rs +216 -3
- package/src/rust/config/workspace_config.rs +138 -10
- package/src/rust/custom_elements/viewer.rs +369 -119
- package/src/rust/custom_events.rs +43 -1
- package/src/rust/js/regular_layout.rs +3 -3
- package/src/rust/lib.rs +15 -0
- package/src/rust/presentation/column_locator.rs +19 -5
- package/src/rust/presentation.rs +200 -36
- package/src/rust/queries/column_locator.rs +74 -44
- package/src/rust/queries/get_viewer_config.rs +2 -1
- 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 +32 -7
- package/src/rust/session/metadata.rs +23 -21
- package/src/rust/session/props.rs +1 -0
- package/src/rust/session/replace_expression_update.rs +1 -0
- package/src/rust/session.rs +9 -6
- package/src/rust/tasks/create_panel.rs +46 -49
- package/src/rust/tasks/edit_expression.rs +19 -23
- package/src/rust/tasks/edit_window.rs +9 -11
- 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 +37 -22
- package/src/rust/tasks/set_edit_mode.rs +3 -1
- package/src/rust/tasks/sync_update_panels.rs +18 -24
- 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 +454 -199
- 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/intl/de.css +16 -0
- package/src/themes/intl/es.css +16 -0
- package/src/themes/intl/fr.css +16 -0
- package/src/themes/intl/ja.css +16 -0
- package/src/themes/intl/pt.css +16 -0
- package/src/themes/intl/zh.css +16 -0
- package/src/themes/intl.css +16 -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/column-format.ts +67 -46
- package/src/ts/perspective-viewer.ts +3 -0
- package/src/ts/plugin.ts +14 -0
- package/src/ts/providers.ts +96 -0
- package/src/ts/ts-rs/CompactDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyCode.ts +3 -0
- package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
- package/src/ts/ts-rs/CurrencySign.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
- package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
- package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
- package/src/ts/ts-rs/FormatMode.ts +3 -0
- package/src/ts/ts-rs/FormatUnit.ts +3 -0
- package/src/ts/ts-rs/Notation.ts +11 -0
- package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +48 -2
- package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
- package/src/ts/ts-rs/RestoreOptions.ts +20 -0
- package/src/ts/ts-rs/RoundingMode.ts +3 -0
- package/src/ts/ts-rs/RoundingPriority.ts +3 -0
- package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
- package/src/ts/ts-rs/SignDisplay.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
- package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
- package/src/ts/ts-rs/StringColorMode.ts +3 -0
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
- package/src/ts/ts-rs/Unit.ts +3 -0
- package/src/ts/ts-rs/UnitDisplay.ts +3 -0
- package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
- package/src/ts/ts-rs/UseGrouping.ts +3 -0
- package/src/ts/ts-rs/ViewerConfig.ts +48 -2
- package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +72 -2
- package/src/ts/ts-rs/WindowSpec.ts +3 -2
- package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +18 -9
- package/dist/esm/ts-rs/WindowAggregate.d.ts +0 -1
- package/src/rust/components/form/color_range_selector.rs +0 -137
- package/src/ts/ts-rs/WindowAggregate.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
|
@@ -150,6 +150,10 @@ fn dispatch_config_update(
|
|
|
150
150
|
renderer: &Renderer,
|
|
151
151
|
presentation: &Presentation,
|
|
152
152
|
) {
|
|
153
|
+
if session.get_table().is_none() && session.pending_table().is_none() {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
153
157
|
clone!(session, renderer, presentation);
|
|
154
158
|
let elem = elem.clone();
|
|
155
159
|
let tracker = session.clone();
|
|
@@ -218,7 +222,16 @@ pub fn wire_element_events(
|
|
|
218
222
|
|
|
219
223
|
let settings_sub = presentation.settings_open_changed.add_listener({
|
|
220
224
|
clone!(elem, presentation, workspace);
|
|
221
|
-
move |open: bool| {
|
|
225
|
+
move |(open, announce): (bool, bool)| {
|
|
226
|
+
// A silent toggle (`restore`/`restoreWorkspace`) is announced
|
|
227
|
+
// by its own view-config commit dispatch — emitting here too
|
|
228
|
+
// would double-fire `perspective-config-update`, the first
|
|
229
|
+
// carrying the intermediate config (see
|
|
230
|
+
// `PresentationHandle::settings_open_changed`).
|
|
231
|
+
if !announce {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
222
235
|
dispatch_event(&elem, "toggle-settings", open).unwrap();
|
|
223
236
|
if let Some(panel) = workspace.active_panel() {
|
|
224
237
|
dispatch_config_update(&elem, &panel.session, &panel.renderer, &presentation);
|
|
@@ -261,7 +274,36 @@ pub fn wire_element_events(
|
|
|
261
274
|
}
|
|
262
275
|
});
|
|
263
276
|
|
|
277
|
+
let layout_sub = workspace.layout_changed().add_listener({
|
|
278
|
+
clone!(elem);
|
|
279
|
+
move |panels: Vec<crate::workspace::PanelId>| {
|
|
280
|
+
let ids = panels
|
|
281
|
+
.iter()
|
|
282
|
+
.map(|id| JsValue::from_str(id.as_str()))
|
|
283
|
+
.collect::<js_sys::Array>();
|
|
284
|
+
|
|
285
|
+
let detail = js_sys::Object::new();
|
|
286
|
+
let _ = js_sys::Reflect::set(&detail, &JsValue::from_str("panels"), &ids);
|
|
287
|
+
dispatch_event(&elem, "layout-update", JsValue::from(detail)).unwrap();
|
|
288
|
+
}
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
let active_panel_sub = workspace.active_changed().add_listener({
|
|
292
|
+
clone!(elem);
|
|
293
|
+
move |active: Option<crate::workspace::PanelId>| {
|
|
294
|
+
let panel = active
|
|
295
|
+
.map(|id| JsValue::from_str(id.as_str()))
|
|
296
|
+
.unwrap_or(JsValue::NULL);
|
|
297
|
+
|
|
298
|
+
let detail = js_sys::Object::new();
|
|
299
|
+
let _ = js_sys::Reflect::set(&detail, &JsValue::from_str("panel"), &panel);
|
|
300
|
+
dispatch_event(&elem, "active-panel-update", JsValue::from(detail)).unwrap();
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
264
304
|
vec![
|
|
305
|
+
layout_sub,
|
|
306
|
+
active_panel_sub,
|
|
265
307
|
theme_sub,
|
|
266
308
|
before_settings_sub,
|
|
267
309
|
settings_sub,
|
|
@@ -235,9 +235,9 @@ impl Layout {
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
/// Rewrite every panel id through `f`, returning the remapped tree. Ids for
|
|
238
|
-
/// which `f` returns `None` are left unchanged. Used by
|
|
239
|
-
///
|
|
240
|
-
///
|
|
238
|
+
/// which `f` returns `None` are left unchanged. Used by `restoreWorkspace`,
|
|
239
|
+
/// which recreates panels under fresh (collision-free) ids and must point
|
|
240
|
+
/// the saved layout tree at them.
|
|
241
241
|
pub fn remap(&self, f: &impl Fn(&str) -> Option<String>) -> Layout {
|
|
242
242
|
match self {
|
|
243
243
|
Layout::Split(split) => Layout::Split(SplitLayout {
|
package/src/rust/lib.rs
CHANGED
|
@@ -31,6 +31,8 @@
|
|
|
31
31
|
// `SESSION_CONFIG_COHERENCE_PLAN.md`) — deny, don't warn.
|
|
32
32
|
#![deny(clippy::await_holding_refcell_ref)]
|
|
33
33
|
|
|
34
|
+
#[cfg(feature = "llm-agent")]
|
|
35
|
+
mod agent;
|
|
34
36
|
pub mod components;
|
|
35
37
|
pub mod config;
|
|
36
38
|
pub mod custom_elements;
|
|
@@ -86,22 +88,35 @@ import type {
|
|
|
86
88
|
|
|
87
89
|
export type * from "../../src/ts/ts-rs/ViewerConfig.d.ts";
|
|
88
90
|
export type * from "../../src/ts/ts-rs/ViewerConfigUpdate.d.ts";
|
|
91
|
+
export type * from "../../src/ts/ts-rs/ViewerConfigInitial.d.ts";
|
|
89
92
|
export type * from "../../src/ts/ts-rs/PluginStaticConfig.d.ts";
|
|
90
93
|
export type * from "../../src/ts/ts-rs/WorkspaceConfig.d.ts";
|
|
91
94
|
export type * from "../../src/ts/ts-rs/WorkspaceConfigUpdate.d.ts";
|
|
92
95
|
export type * from "../../src/ts/ts-rs/ExportMethod.d.ts";
|
|
93
96
|
export type * from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
|
97
|
+
export type * from "../../src/ts/ts-rs/RestoreOptions.d.ts";
|
|
98
|
+
export type * from "../../src/ts/ts-rs/SaveWorkspaceOptions.d.ts";
|
|
94
99
|
export type * from "../../src/ts/ts-rs/ClientOptions.d.ts";
|
|
95
100
|
export type * from "../../src/ts/ts-rs/ExportOptions.d.ts";
|
|
96
101
|
export type * from "../../src/ts/ts-rs/GetTableOptions.d.ts";
|
|
97
102
|
export type * from "../../src/ts/ts-rs/GetClientOptions.d.ts";
|
|
103
|
+
export type * from "../../src/ts/ts-rs/CustomNumberFormatConfig.d.ts";
|
|
104
|
+
export type * from "../../src/ts/ts-rs/NumberFormatStyle.d.ts";
|
|
105
|
+
export type * from "../../src/ts/ts-rs/Notation.d.ts";
|
|
106
|
+
export type * from "../../src/ts/ts-rs/DatetimeFormatType.d.ts";
|
|
107
|
+
export type * from "../../src/ts/ts-rs/StringColorMode.d.ts";
|
|
108
|
+
export type * from "../../src/ts/ts-rs/DatetimeColorMode.d.ts";
|
|
109
|
+
export type * from "../../src/ts/ts-rs/FormatMode.d.ts";
|
|
98
110
|
import type {GetTableOptions} from "../../src/ts/ts-rs/GetTableOptions.d.ts";
|
|
99
111
|
import type {PanelOptions} from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
|
112
|
+
import type {RestoreOptions} from "../../src/ts/ts-rs/RestoreOptions.d.ts";
|
|
113
|
+
import type {SaveWorkspaceOptions} from "../../src/ts/ts-rs/SaveWorkspaceOptions.d.ts";
|
|
100
114
|
import type {ExportOptions} from "../../src/ts/ts-rs/ExportOptions.d.ts";
|
|
101
115
|
import type {GetClientOptions} from "../../src/ts/ts-rs/GetClientOptions.d.ts";
|
|
102
116
|
import type {ClientOptions} from "../../src/ts/ts-rs/ClientOptions.d.ts";
|
|
103
117
|
import type {ViewerConfig} from "../../src/ts/ts-rs/ViewerConfig.d.ts";
|
|
104
118
|
import type {ViewerConfigUpdate} from "../../src/ts/ts-rs/ViewerConfigUpdate.d.ts";
|
|
119
|
+
import type {ViewerConfigInitial} from "../../src/ts/ts-rs/ViewerConfigInitial.d.ts";
|
|
105
120
|
import type {WorkspaceConfig} from "../../src/ts/ts-rs/WorkspaceConfig.d.ts";
|
|
106
121
|
import type {WorkspaceConfigUpdate} from "../../src/ts/ts-rs/WorkspaceConfigUpdate.d.ts";
|
|
107
122
|
"#;
|
|
@@ -66,6 +66,23 @@ impl ColumnLocator {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
/// What the column-settings drawer is opened on, as held in presentation
|
|
70
|
+
/// state: a column by name, or the not-yet-saved expression.
|
|
71
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
72
|
+
pub enum ColumnSettingsTarget {
|
|
73
|
+
Column(String),
|
|
74
|
+
NewExpression,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
impl ColumnSettingsTarget {
|
|
78
|
+
pub fn name(&self) -> Option<&String> {
|
|
79
|
+
match self {
|
|
80
|
+
Self::Column(name) => Some(name),
|
|
81
|
+
Self::NewExpression => None,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
69
86
|
#[derive(Debug, Default, Clone, Copy, PartialEq)]
|
|
70
87
|
|
|
71
88
|
pub enum ColumnSettingsTab {
|
|
@@ -83,16 +100,13 @@ impl Display for ColumnSettingsTab {
|
|
|
83
100
|
|
|
84
101
|
#[derive(Clone, Default, Debug, PartialEq)]
|
|
85
102
|
pub struct OpenColumnSettings {
|
|
86
|
-
pub
|
|
103
|
+
pub target: Option<ColumnSettingsTarget>,
|
|
87
104
|
pub tab: Option<ColumnSettingsTab>,
|
|
88
105
|
}
|
|
89
106
|
|
|
90
107
|
impl OpenColumnSettings {
|
|
91
108
|
pub fn name(&self) -> Option<String> {
|
|
92
|
-
self.
|
|
93
|
-
.as_ref()
|
|
94
|
-
.and_then(|l| l.name())
|
|
95
|
-
.map(|s| s.to_owned())
|
|
109
|
+
self.target.as_ref().and_then(|t| t.name()).cloned()
|
|
96
110
|
}
|
|
97
111
|
}
|
|
98
112
|
|
package/src/rust/presentation.rs
CHANGED
|
@@ -15,8 +15,8 @@ pub mod drag_helpers;
|
|
|
15
15
|
mod props;
|
|
16
16
|
mod sheets;
|
|
17
17
|
|
|
18
|
-
use std::cell::RefCell;
|
|
19
|
-
use std::collections::HashSet;
|
|
18
|
+
use std::cell::{Cell, RefCell};
|
|
19
|
+
use std::collections::{BTreeMap, HashSet};
|
|
20
20
|
use std::ops::Deref;
|
|
21
21
|
use std::rc::Rc;
|
|
22
22
|
|
|
@@ -27,10 +27,13 @@ use web_sys::*;
|
|
|
27
27
|
use yew::html::ImplicitClone;
|
|
28
28
|
use yew::prelude::*;
|
|
29
29
|
|
|
30
|
-
pub use self::column_locator::{
|
|
30
|
+
pub use self::column_locator::{
|
|
31
|
+
ColumnLocator, ColumnSettingsTab, ColumnSettingsTarget, ColumnTab, OpenColumnSettings,
|
|
32
|
+
};
|
|
31
33
|
use self::drag_helpers::DragTargetState;
|
|
32
34
|
pub use self::drag_helpers::{DragDropContainer, DragEndCallback};
|
|
33
35
|
pub use self::props::{DragDropProps, PresentationProps};
|
|
36
|
+
use crate::config::{CssKind, NamedValue, assign_palette_names};
|
|
34
37
|
use crate::utils::*;
|
|
35
38
|
|
|
36
39
|
#[derive(Clone, Debug)]
|
|
@@ -63,6 +66,12 @@ impl DragState {
|
|
|
63
66
|
pub struct PresentationHandle {
|
|
64
67
|
viewer_elem: HtmlElement,
|
|
65
68
|
|
|
69
|
+
/// The embedded LLM agent's shared model (runtime + chat transcript) —
|
|
70
|
+
/// carried here so the settings sidebar can render the chat tab without
|
|
71
|
+
/// threading a new prop chain from the element.
|
|
72
|
+
#[cfg(feature = "llm-agent")]
|
|
73
|
+
pub agent: crate::agent::AgentSlot,
|
|
74
|
+
|
|
66
75
|
/// The available themes as detected in the browser environment or set
|
|
67
76
|
/// explicitly when CORS prevents detection — a MEMO of a document
|
|
68
77
|
/// external, not component state. `None` until first parsed (detection
|
|
@@ -79,6 +88,18 @@ pub struct PresentationHandle {
|
|
|
79
88
|
/// concurrent `get_available_themes` calls await one parse instead of
|
|
80
89
|
/// racing their own.
|
|
81
90
|
theme_init: Mutex<()>,
|
|
91
|
+
|
|
92
|
+
/// Whether the host's theme was ever EXPLICITLY chosen — authored as a
|
|
93
|
+
/// `theme` attribute, or set by name through [`Self::set_theme_name`].
|
|
94
|
+
///
|
|
95
|
+
/// `false` means the host merely displays the registry default, which
|
|
96
|
+
/// [`Self::reset_themes`] is free to move; `true` pins the selection
|
|
97
|
+
/// until it leaves the registry. The `theme` attribute alone cannot
|
|
98
|
+
/// carry this, because `init` stamps it unconditionally so the document
|
|
99
|
+
/// cascade has a theme to match.
|
|
100
|
+
theme_selected: Cell<bool>,
|
|
101
|
+
|
|
102
|
+
palette: RefCell<BTreeMap<String, String>>,
|
|
82
103
|
is_settings_open: RefCell<bool>,
|
|
83
104
|
open_column_settings: RefCell<OpenColumnSettings>,
|
|
84
105
|
is_workspace: RefCell<Option<bool>>,
|
|
@@ -103,11 +124,20 @@ pub struct PresentationHandle {
|
|
|
103
124
|
/// dragged element from the shadow tree.
|
|
104
125
|
host_dragend: RefCell<Option<DragEndCallback>>,
|
|
105
126
|
|
|
127
|
+
source_dragend: RefCell<Option<(web_sys::EventTarget, DragEndCallback)>>,
|
|
128
|
+
|
|
106
129
|
/// IntersectionObserver-based fallback for the drag image, kept alive for
|
|
107
130
|
/// the duration of the drag.
|
|
108
131
|
drag_target: RefCell<Option<DragTargetState>>,
|
|
109
132
|
|
|
110
|
-
|
|
133
|
+
/// `(open, announce)` — `announce` says this toggle is the SOLE carrier
|
|
134
|
+
/// of the config change (a user gesture: toolbar, `toggleConfig`), so
|
|
135
|
+
/// the element must emit `toggle-settings` + a config-update for it. An
|
|
136
|
+
/// API restore's toggle is announced by its own view-config commit
|
|
137
|
+
/// dispatch instead — announcing here too would double-emit, the first
|
|
138
|
+
/// carrying the intermediate config (settings flipped, view fields not
|
|
139
|
+
/// yet committed).
|
|
140
|
+
pub settings_open_changed: PubSub<(bool, bool)>,
|
|
111
141
|
|
|
112
142
|
/// Injected callback from the root component, replacing the former
|
|
113
143
|
/// `is_workspace_changed: PubSub` field.
|
|
@@ -148,8 +178,12 @@ impl Presentation {
|
|
|
148
178
|
pub fn new(elem: &HtmlElement) -> Self {
|
|
149
179
|
let theme = Self(Rc::new(PresentationHandle {
|
|
150
180
|
viewer_elem: elem.clone(),
|
|
181
|
+
#[cfg(feature = "llm-agent")]
|
|
182
|
+
agent: Default::default(),
|
|
151
183
|
themes: Default::default(),
|
|
152
184
|
theme_init: Default::default(),
|
|
185
|
+
theme_selected: Cell::new(elem.get_attribute("theme").is_some()),
|
|
186
|
+
palette: Default::default(),
|
|
153
187
|
is_workspace: Default::default(),
|
|
154
188
|
settings_open_changed: Default::default(),
|
|
155
189
|
settings_before_open_changed: Default::default(),
|
|
@@ -165,6 +199,7 @@ impl Presentation {
|
|
|
165
199
|
on_dragstart: Default::default(),
|
|
166
200
|
on_dragend: Default::default(),
|
|
167
201
|
host_dragend: Default::default(),
|
|
202
|
+
source_dragend: Default::default(),
|
|
168
203
|
drag_target: Default::default(),
|
|
169
204
|
}));
|
|
170
205
|
|
|
@@ -230,13 +265,84 @@ impl Presentation {
|
|
|
230
265
|
}
|
|
231
266
|
}
|
|
232
267
|
|
|
233
|
-
|
|
234
|
-
|
|
268
|
+
/// See [`PresentationHandle::settings_open_changed`] for `announce`.
|
|
269
|
+
pub fn set_settings_open(&self, open: bool, announce: bool) {
|
|
270
|
+
self.settings_open_changed.emit((open, announce));
|
|
235
271
|
}
|
|
236
272
|
|
|
237
273
|
/// Sets the currently opened column settings. Emits an internal event on
|
|
238
274
|
/// change. Passing None is a shorthand for setting all fields to
|
|
239
275
|
/// None.
|
|
276
|
+
/// The workspace palette as last restored (canonical values).
|
|
277
|
+
pub fn palette(&self) -> BTreeMap<String, String> {
|
|
278
|
+
self.0.palette.borrow().clone()
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/// Replace the host palette: every previously-applied `--psp-user--*`
|
|
282
|
+
/// inline property is removed, then `palette` is applied.
|
|
283
|
+
pub fn set_palette(&self, palette: BTreeMap<String, String>) -> ApiResult<()> {
|
|
284
|
+
let style = self.0.viewer_elem.style();
|
|
285
|
+
for name in self.0.palette.borrow().keys() {
|
|
286
|
+
style.remove_property(name)?;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
for (name, value) in &palette {
|
|
290
|
+
style.set_property(name, value)?;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
*self.0.palette.borrow_mut() = palette;
|
|
294
|
+
Ok(())
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/// Pin `literal` into the restored palette, named by the same rules
|
|
298
|
+
/// as the derived set and applied to the host.
|
|
299
|
+
pub fn pin_style(&self, kind: CssKind, literal: &str) -> ApiResult<()> {
|
|
300
|
+
let Ok(value) = kind.canonicalize(literal) else {
|
|
301
|
+
return Ok(());
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
let current = self.palette();
|
|
305
|
+
let host = self.host_named_values(kind);
|
|
306
|
+
let set = assign_palette_names(¤t, &host, &[(kind, value)], &|name| {
|
|
307
|
+
self.resolve_css_var(name).is_some()
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
let style = self.0.viewer_elem.style();
|
|
311
|
+
for (name, value) in &set {
|
|
312
|
+
if !current.contains_key(name) {
|
|
313
|
+
style.set_property(name, value)?;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
*self.0.palette.borrow_mut() = set;
|
|
318
|
+
Ok(())
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/// The host's computed value for custom property `name` (inline
|
|
322
|
+
/// palette, then theme/page stylesheets), or `None` if undefined.
|
|
323
|
+
pub fn resolve_css_var(&self, name: &str) -> Option<String> {
|
|
324
|
+
read_custom_property(&self.0.viewer_elem, name)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// Theme/page-authored named values of `kind` on the host, canonical,
|
|
328
|
+
/// discovered by the contiguous-numbering walk `--psp-user--<kind>-1`,
|
|
329
|
+
/// `-2`, … up to the first undefined name.
|
|
330
|
+
pub fn host_named_values(&self, kind: CssKind) -> Vec<NamedValue> {
|
|
331
|
+
let mut out = vec![];
|
|
332
|
+
for n in 1.. {
|
|
333
|
+
let name = format!("{}{n}", kind.var_prefix());
|
|
334
|
+
let Some(raw) = self.resolve_css_var(&name) else {
|
|
335
|
+
break;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
if let Ok(value) = kind.canonicalize(&raw) {
|
|
339
|
+
out.push(NamedValue { name, value });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
out
|
|
344
|
+
}
|
|
345
|
+
|
|
240
346
|
pub fn set_open_column_settings(&self, settings: Option<OpenColumnSettings>) {
|
|
241
347
|
let settings = settings.unwrap_or_default();
|
|
242
348
|
if *(self.open_column_settings.borrow()) != settings {
|
|
@@ -307,22 +413,24 @@ impl Presentation {
|
|
|
307
413
|
index.and_then(|x| themes.get(x).cloned())
|
|
308
414
|
}
|
|
309
415
|
|
|
310
|
-
/// The
|
|
311
|
-
///
|
|
416
|
+
/// The theme a NEW panel is born with: the host's if it has one, else
|
|
417
|
+
/// the registry default. Synchronous, because panel creation is — the
|
|
418
|
+
/// registry fallback is `None` until the registry first parses, which
|
|
419
|
+
/// [`crate::tasks::seed_panel_theme`] fills in.
|
|
420
|
+
pub fn active_theme_name_sync(&self) -> Option<String> {
|
|
421
|
+
self.0
|
|
422
|
+
.viewer_elem
|
|
423
|
+
.get_attribute("theme")
|
|
424
|
+
.or_else(|| self.0.themes.borrow().as_ref()?.first().cloned())
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/// The registry default — the FIRST registered theme, which a panel or
|
|
428
|
+
/// host resolves to only when it has no theme of its own. `None` if no
|
|
312
429
|
/// themes exist.
|
|
313
430
|
pub async fn get_default_theme_name(&self) -> Option<String> {
|
|
314
431
|
self.get_available_themes().await.ok()?.first().cloned()
|
|
315
432
|
}
|
|
316
433
|
|
|
317
|
-
/// SYNC read of the registry default theme name, derived on demand from
|
|
318
|
-
/// the memoized registry — `None` until the registry first parses (or
|
|
319
|
-
/// when no themes exist). For synchronous stamping paths that must not
|
|
320
|
-
/// await registry init; prefer [`Self::get_default_theme_name`] where
|
|
321
|
-
/// awaiting is acceptable.
|
|
322
|
-
pub fn default_theme_name_sync(&self) -> Option<String> {
|
|
323
|
-
self.0.themes.borrow().as_ref()?.first().cloned()
|
|
324
|
-
}
|
|
325
|
-
|
|
326
434
|
fn set_theme_attribute(&self, theme: Option<&str>) -> ApiResult<()> {
|
|
327
435
|
if let Some(theme) = theme {
|
|
328
436
|
Ok(self.0.viewer_elem.set_attribute("theme", theme)?)
|
|
@@ -333,12 +441,39 @@ impl Presentation {
|
|
|
333
441
|
|
|
334
442
|
pub async fn reset_theme(&self) -> ApiResult<()> {
|
|
335
443
|
*self.0.is_workspace.borrow_mut() = None;
|
|
336
|
-
|
|
337
|
-
let default_theme = themes.first().map(|x| x.as_str());
|
|
338
|
-
self.set_theme_name(default_theme).await?;
|
|
444
|
+
self.set_theme_name(None).await?;
|
|
339
445
|
Ok(())
|
|
340
446
|
}
|
|
341
447
|
|
|
448
|
+
/// Adopt `themes` as the available set, KEEPING the host's theme unless
|
|
449
|
+
/// it was never explicitly chosen or has left the set — the only two
|
|
450
|
+
/// cases in which re-ordering the registry may move the viewer.
|
|
451
|
+
///
|
|
452
|
+
/// Always re-stamps; the caller publishes ([`Self::publish_theme_config`])
|
|
453
|
+
/// once its restyles have resolved, because the available list has
|
|
454
|
+
/// changed even when the selection has not.
|
|
455
|
+
///
|
|
456
|
+
/// @param themes the new set, or `None` to re-parse the document.
|
|
457
|
+
///
|
|
458
|
+
/// # Returns
|
|
459
|
+
/// The active theme after the change.
|
|
460
|
+
pub async fn reset_themes(&self, themes: Option<Vec<String>>) -> ApiResult<Option<String>> {
|
|
461
|
+
let selected = self
|
|
462
|
+
.0
|
|
463
|
+
.theme_selected
|
|
464
|
+
.get()
|
|
465
|
+
.then(|| self.0.viewer_elem.get_attribute("theme"))
|
|
466
|
+
.flatten();
|
|
467
|
+
|
|
468
|
+
self.reset_available_themes(themes).await;
|
|
469
|
+
let available = self.get_available_themes().await?;
|
|
470
|
+
let kept = selected.filter(|name| available.contains(name));
|
|
471
|
+
self.0.theme_selected.set(kept.is_some());
|
|
472
|
+
let active = kept.or_else(|| available.first().cloned());
|
|
473
|
+
self.set_theme_attribute(active.as_deref())?;
|
|
474
|
+
Ok(active)
|
|
475
|
+
}
|
|
476
|
+
|
|
342
477
|
/// Set the theme by name, or `None` for the default theme.
|
|
343
478
|
///
|
|
344
479
|
/// A NAMED theme's host attribute write is SYNCHRONOUS ("stamp with
|
|
@@ -360,6 +495,7 @@ impl Presentation {
|
|
|
360
495
|
/// # Returns
|
|
361
496
|
/// A `bool` indicating whether the internal state changed.
|
|
362
497
|
pub async fn set_theme_name(&self, theme: Option<&str>) -> ApiResult<bool> {
|
|
498
|
+
self.0.theme_selected.set(theme.is_some());
|
|
363
499
|
if let Some(theme) = theme {
|
|
364
500
|
if self.0.viewer_elem.get_attribute("theme").as_deref() == Some(theme) {
|
|
365
501
|
return Ok(false);
|
|
@@ -369,20 +505,27 @@ impl Presentation {
|
|
|
369
505
|
}
|
|
370
506
|
|
|
371
507
|
let themes = self.get_available_themes().await?;
|
|
372
|
-
|
|
373
|
-
themes.iter().position(|x| x == theme)
|
|
374
|
-
} else if !themes.is_empty() {
|
|
508
|
+
if theme.is_none() {
|
|
375
509
|
self.set_theme_attribute(themes.first().map(|x| x.as_str()))?;
|
|
376
|
-
|
|
377
|
-
} else {
|
|
378
|
-
self.set_theme_attribute(None)?;
|
|
379
|
-
None
|
|
380
|
-
};
|
|
510
|
+
}
|
|
381
511
|
|
|
382
|
-
self.theme_config_updated.emit((themes, index));
|
|
383
512
|
Ok(true)
|
|
384
513
|
}
|
|
385
514
|
|
|
515
|
+
/// Publish the `theme_config_updated` snapshot — the available themes
|
|
516
|
+
/// and the host's current selection — to the component tree.
|
|
517
|
+
pub async fn publish_theme_config(&self) -> ApiResult<()> {
|
|
518
|
+
let themes = self.get_available_themes().await?;
|
|
519
|
+
let index = self
|
|
520
|
+
.0
|
|
521
|
+
.viewer_elem
|
|
522
|
+
.get_attribute("theme")
|
|
523
|
+
.and_then(|active| themes.iter().position(|x| *x == active));
|
|
524
|
+
|
|
525
|
+
self.theme_config_updated.emit((themes, index));
|
|
526
|
+
Ok(())
|
|
527
|
+
}
|
|
528
|
+
|
|
386
529
|
/// Snapshot the drag state as a [`DragDropProps`] value for threading
|
|
387
530
|
/// through the component tree without PubSub subscriptions.
|
|
388
531
|
pub fn drag_drop_props(&self) -> DragDropProps {
|
|
@@ -419,6 +562,7 @@ impl Presentation {
|
|
|
419
562
|
|
|
420
563
|
pub fn set_drag_image(&self, event: &DragEvent) -> ApiResult<()> {
|
|
421
564
|
event.stop_propagation();
|
|
565
|
+
self.register_source_dragend(event)?;
|
|
422
566
|
if let Some(dt) = event.data_transfer() {
|
|
423
567
|
dt.set_drop_effect("move");
|
|
424
568
|
}
|
|
@@ -477,8 +621,7 @@ impl Presentation {
|
|
|
477
621
|
_ => None,
|
|
478
622
|
};
|
|
479
623
|
|
|
480
|
-
self.
|
|
481
|
-
*self.drag_state.borrow_mut() = DragState::NoDrag;
|
|
624
|
+
self.end_drag();
|
|
482
625
|
if let Some(action) = action {
|
|
483
626
|
self.drop_received.emit(action);
|
|
484
627
|
}
|
|
@@ -502,11 +645,15 @@ impl Presentation {
|
|
|
502
645
|
/// End the drag/drop action by resetting the state to default.
|
|
503
646
|
pub fn notify_drag_end(&self) {
|
|
504
647
|
if self.drag_state.borrow().is_drag_in_progress() {
|
|
505
|
-
self.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
648
|
+
self.end_drag();
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
fn end_drag(&self) {
|
|
653
|
+
self.drag_target.borrow_mut().take();
|
|
654
|
+
*self.drag_state.borrow_mut() = DragState::NoDrag;
|
|
655
|
+
if let Some(cb) = self.on_dragend.borrow().as_ref() {
|
|
656
|
+
cb.emit(());
|
|
510
657
|
}
|
|
511
658
|
}
|
|
512
659
|
|
|
@@ -514,6 +661,23 @@ impl Presentation {
|
|
|
514
661
|
/// element so that drag-end cleanup fires even when Yew re-renders
|
|
515
662
|
/// remove the original dragged element from the shadow DOM. The host
|
|
516
663
|
/// element is outside the virtual DOM and therefore stable.
|
|
664
|
+
fn register_source_dragend(&self, event: &DragEvent) -> ApiResult<()> {
|
|
665
|
+
let target = event.target().into_apierror()?;
|
|
666
|
+
if let Some((prev_target, prev)) = self.source_dragend.borrow_mut().take() {
|
|
667
|
+
let _ = prev_target
|
|
668
|
+
.remove_event_listener_with_callback("dragend", prev.as_ref().unchecked_ref());
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
let this = self.clone();
|
|
672
|
+
let closure = Closure::wrap(Box::new(move |_event: DragEvent| {
|
|
673
|
+
this.notify_drag_end();
|
|
674
|
+
}) as Box<dyn FnMut(DragEvent)>);
|
|
675
|
+
|
|
676
|
+
target.add_event_listener_with_callback("dragend", closure.as_ref().unchecked_ref())?;
|
|
677
|
+
*self.source_dragend.borrow_mut() = Some((target, closure));
|
|
678
|
+
Ok(())
|
|
679
|
+
}
|
|
680
|
+
|
|
517
681
|
fn register_host_dragend(&self) {
|
|
518
682
|
if let Some(prev) = self.host_dragend.borrow_mut().take() {
|
|
519
683
|
let _ = self
|
|
@@ -12,26 +12,29 @@
|
|
|
12
12
|
|
|
13
13
|
use perspective_client::config::ViewConfig;
|
|
14
14
|
|
|
15
|
-
use crate::presentation::{ColumnLocator, OpenColumnSettings};
|
|
15
|
+
use crate::presentation::{ColumnLocator, ColumnSettingsTarget, OpenColumnSettings};
|
|
16
16
|
use crate::renderer::Renderer;
|
|
17
17
|
use crate::session::SessionMetadata;
|
|
18
18
|
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
pub fn
|
|
19
|
+
/// Classify a column name against the session, config-first: the view
|
|
20
|
+
/// config is the commit of record for expressions and windows.
|
|
21
|
+
pub fn classify_column(
|
|
22
|
+
name: &str,
|
|
23
|
+
view_config: &ViewConfig,
|
|
22
24
|
metadata: &SessionMetadata,
|
|
23
|
-
name: Option<String>,
|
|
24
25
|
) -> Option<ColumnLocator> {
|
|
25
|
-
name.
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
26
|
+
if view_config.windows.contains_key(name) || metadata.is_column_window(name) {
|
|
27
|
+
Some(ColumnLocator::Window(name.to_owned()))
|
|
28
|
+
} else if view_config.expressions.contains_key(name) || metadata.is_column_expression(name) {
|
|
29
|
+
Some(ColumnLocator::Expression(name.to_owned()))
|
|
30
|
+
} else if metadata
|
|
31
|
+
.get_table_columns()
|
|
32
|
+
.is_some_and(|cols| cols.iter().any(|col| col == name))
|
|
33
|
+
{
|
|
34
|
+
Some(ColumnLocator::Table(name.to_owned()))
|
|
35
|
+
} else {
|
|
36
|
+
None
|
|
37
|
+
}
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
/// Gets a [`ColumnLocator`] for the current UI's column settings state,
|
|
@@ -43,35 +46,62 @@ pub fn get_current_column_locator(
|
|
|
43
46
|
open_column_settings: &OpenColumnSettings,
|
|
44
47
|
renderer: &Renderer,
|
|
45
48
|
view_config: &ViewConfig,
|
|
46
|
-
|
|
49
|
+
metadata: &SessionMetadata,
|
|
47
50
|
) -> Option<ColumnLocator> {
|
|
48
|
-
open_column_settings
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
51
|
+
match open_column_settings.target.as_ref()? {
|
|
52
|
+
ColumnSettingsTarget::NewExpression => Some(ColumnLocator::NewExpression),
|
|
53
|
+
ColumnSettingsTarget::Column(name) => {
|
|
54
|
+
let locator = classify_column(name, view_config, metadata)?;
|
|
55
|
+
match locator {
|
|
56
|
+
ColumnLocator::Table(_) => {
|
|
57
|
+
let in_columns = view_config.columns.iter().any(|maybe_col| {
|
|
58
|
+
maybe_col
|
|
59
|
+
.as_ref()
|
|
60
|
+
.map(|col| col == name)
|
|
61
|
+
.unwrap_or_default()
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
(in_columns && renderer.can_render_column_styles()).then_some(locator)
|
|
65
|
+
},
|
|
66
|
+
locator => Some(locator),
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
#[cfg(test)]
|
|
73
|
+
mod tests {
|
|
74
|
+
use std::collections::HashMap;
|
|
75
|
+
|
|
76
|
+
use perspective_client::config::Expressions;
|
|
77
|
+
|
|
78
|
+
use super::*;
|
|
79
|
+
|
|
80
|
+
fn config_with_expression(name: &str, expr: &str) -> ViewConfig {
|
|
81
|
+
ViewConfig {
|
|
82
|
+
expressions: Expressions(HashMap::from([(name.to_owned(), expr.to_owned())])),
|
|
83
|
+
..ViewConfig::default()
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#[test]
|
|
88
|
+
fn classifies_from_the_config_before_metadata_catches_up() {
|
|
89
|
+
let config = config_with_expression("x", "1 + 1");
|
|
90
|
+
let metadata = SessionMetadata::default();
|
|
91
|
+
assert_eq!(
|
|
92
|
+
classify_column("x", &config, &metadata),
|
|
93
|
+
Some(ColumnLocator::Expression("x".to_owned()))
|
|
94
|
+
);
|
|
95
|
+
assert_eq!(classify_column("nope", &config, &metadata), None);
|
|
96
|
+
}
|
|
61
97
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
})
|
|
72
|
-
.unwrap_or_default()
|
|
73
|
-
&& renderer.can_render_column_styles()
|
|
74
|
-
},
|
|
75
|
-
_ => true,
|
|
76
|
-
})
|
|
98
|
+
#[test]
|
|
99
|
+
fn unnamed_expressions_are_keyed_by_their_text() {
|
|
100
|
+
let config = config_with_expression("\"Sales\" + 1", "\"Sales\" + 1");
|
|
101
|
+
let metadata = SessionMetadata::default();
|
|
102
|
+
assert_eq!(
|
|
103
|
+
classify_column("\"Sales\" + 1", &config, &metadata),
|
|
104
|
+
Some(ColumnLocator::Expression("\"Sales\" + 1".to_owned()))
|
|
105
|
+
);
|
|
106
|
+
}
|
|
77
107
|
}
|