@perspective-dev/viewer 5.5.0 → 5.6.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/README.md +82 -3
- package/dist/cdn/perspective-viewer.js +9 -9
- package/dist/cdn/perspective-viewer.js.map +3 -3
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.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/ar.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/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.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/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/column-format.js +2 -0
- package/dist/esm/column-format.js.map +7 -0
- package/dist/esm/extensions.d.ts +3 -13
- package/dist/esm/perspective-viewer.d.ts +1 -1
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +3 -3
- package/dist/esm/plugin.d.ts +4 -1
- package/dist/esm/select-detail.d.ts +15 -0
- package/dist/esm/select-detail.js +2 -0
- package/dist/esm/select-detail.js.map +7 -0
- package/dist/esm/ts-rs/DerivedTableConfig.d.ts +35 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +7 -6
- package/dist/esm/ts-rs/RestoreOptions.d.ts +3 -7
- package/dist/esm/ts-rs/TableRef.d.ts +9 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +7 -6
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -5
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +9 -0
- package/dist/wasm/perspective-viewer.d.ts +126 -35
- package/dist/wasm/perspective-viewer.js +320 -185
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +21 -17
- package/package.json +33 -20
- package/src/css/column-selector.css +205 -50
- package/src/css/ui/containers/context-menu.css +12 -1
- package/src/css/ui/containers/dropdown-menu-items.css +29 -0
- package/src/css/ui/containers/scroll-panel.css +4 -1
- package/src/rust/agent/client/protocol.rs +0 -89
- package/src/rust/agent/markdown/mod.rs +0 -238
- package/src/rust/agent/preamble.md +1 -0
- package/src/rust/agent/tools.rs +2 -16
- package/src/rust/components/column_selector/active_column.rs +15 -5
- package/src/rust/components/column_selector/add_expression_button.rs +9 -26
- package/src/rust/components/column_selector/config_selector.rs +34 -6
- package/src/rust/components/column_selector/derived_control.rs +229 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +3 -2
- package/src/rust/components/column_selector/filter_column.rs +0 -2
- package/src/rust/components/column_selector/inactive_column.rs +4 -5
- package/src/rust/components/column_selector/pivot_column.rs +21 -1
- package/src/rust/components/column_selector/sort_column.rs +4 -3
- package/src/rust/components/column_selector.rs +21 -1
- package/src/rust/components/column_settings_sidebar/save_settings.rs +5 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +1 -0
- package/src/rust/components/column_settings_sidebar.rs +25 -19
- package/src/rust/components/debug_panel.rs +38 -29
- package/src/rust/components/dragdrop_list.rs +8 -9
- package/src/rust/components/export_dropdown.rs +6 -0
- package/src/rust/components/main_panel/msg.rs +6 -4
- package/src/rust/components/main_panel/reconcile.rs +6 -1
- package/src/rust/components/main_panel/render.rs +70 -51
- package/src/rust/components/main_panel/update.rs +39 -1
- package/src/rust/components/main_panel.rs +97 -59
- package/src/rust/components/new_panel_menu.rs +69 -1
- package/src/rust/components/panel_menu.rs +63 -12
- package/src/rust/components/plugin_tab.rs +3 -2
- package/src/rust/components/render_warning.rs +2 -1
- package/src/rust/components/settings_panel.rs +4 -4
- package/src/rust/components/status_bar.rs +80 -21
- package/src/rust/components/status_indicator.rs +8 -1
- package/src/rust/components/viewer/filters.rs +15 -3
- package/src/rust/components/viewer/msg.rs +8 -0
- package/src/rust/components/viewer/panels.rs +44 -4
- package/src/rust/components/viewer/render.rs +1 -0
- package/src/rust/components/viewer/settings.rs +0 -22
- package/src/rust/components/viewer/snapshots.rs +2 -2
- package/src/rust/components/viewer/wiring.rs +18 -1
- package/src/rust/components/viewer.rs +47 -27
- package/src/rust/components/window_editor.rs +5 -4
- package/src/rust/config/column_config_schema.rs +38 -377
- package/src/rust/config/export_method.rs +24 -0
- package/src/rust/config/number_string_format.rs +0 -94
- package/src/rust/config/options.rs +3 -7
- package/src/rust/config/plugin_static_config.rs +0 -42
- package/src/rust/config/viewer_config.rs +47 -17
- package/src/rust/config/workspace_config.rs +158 -128
- package/src/rust/custom_elements/viewer.rs +189 -129
- package/src/rust/custom_events.rs +33 -18
- package/src/rust/derived.rs +266 -0
- package/src/rust/exprtk/tokenize.rs +0 -54
- package/src/rust/js/plugin.rs +1 -1
- package/src/rust/lib.rs +3 -0
- package/src/rust/presentation.rs +23 -4
- package/src/rust/queries/column_locator.rs +55 -48
- package/src/rust/queries/get_viewer_config.rs +1 -1
- package/src/rust/queries/plugin_column_styles.rs +22 -23
- package/src/rust/renderer/dispatch.rs +2 -0
- package/src/rust/renderer/plugin_config.rs +652 -487
- package/src/rust/renderer/props.rs +4 -0
- package/src/rust/renderer/registry.rs +2 -6
- package/src/rust/renderer/state.rs +205 -0
- package/src/rust/renderer.rs +201 -59
- package/src/rust/session/metadata.rs +35 -19
- package/src/rust/session/op_queue.rs +430 -0
- package/src/rust/session/panel_state.rs +327 -0
- package/src/rust/session/view_subscription.rs +42 -3
- package/src/rust/session.rs +931 -419
- package/src/rust/tasks/apply_global_filters.rs +49 -35
- package/src/rust/tasks/auto_pause.rs +22 -12
- package/src/rust/tasks/copy_export.rs +72 -0
- package/src/rust/tasks/create_panel.rs +46 -21
- package/src/rust/tasks/derive.rs +312 -0
- package/src/rust/tasks/mod.rs +6 -2
- package/src/rust/tasks/pipeline.rs +152 -89
- package/src/rust/tasks/reset_all.rs +31 -20
- package/src/rust/tasks/restore_derived.rs +200 -0
- package/src/rust/tasks/restore_panel.rs +153 -141
- package/src/rust/tasks/send_column_config.rs +56 -29
- package/src/rust/tasks/send_plugin_config.rs +52 -23
- package/src/rust/tasks/set_edit_mode.rs +33 -25
- package/src/rust/tasks/sync_update_panels.rs +40 -13
- package/src/rust/tasks/table_lifecycle.rs +70 -34
- package/src/rust/tasks/transactional_restore.rs +387 -0
- package/src/rust/tasks/update_theme.rs +45 -6
- package/src/rust/ui/containers/scroll_panel.rs +17 -10
- package/src/rust/ui/containers/split_panel.rs +0 -41
- package/src/rust/ui/context_menu.rs +157 -18
- package/src/rust/ui/font_loader.rs +23 -35
- package/src/rust/ui/form/mirrored_textarea.rs +0 -12
- package/src/rust/ui/form/multi_stop_gradient_selector.rs +0 -89
- package/src/rust/ui/form/palette_selector.rs +0 -51
- package/src/rust/ui/portal.rs +4 -10
- package/src/rust/utils/browser/mod.rs +2 -3
- package/src/{ts/perspective-viewer.inline.ts → rust/utils/browser/mouse.rs} +11 -10
- package/src/rust/utils/browser/request_animation_frame.rs +27 -8
- package/src/rust/utils/css.rs +0 -360
- package/src/rust/utils/{tests/mod.rs → in_flight.rs} +62 -2
- package/src/rust/utils/local_fonts.rs +0 -36
- package/src/rust/utils/mod.rs +4 -3
- package/src/rust/utils/modal_position.rs +15 -21
- package/src/rust/utils/pending_effects.rs +6 -30
- package/src/rust/utils/{browser/tests/mod.rs → plugin_host_state.rs} +31 -1
- package/src/rust/utils/pubsub.rs +0 -1
- package/src/rust/workspace.rs +187 -140
- package/src/svg/derived.svg +6 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/ar.css +10 -0
- package/src/themes/intl/de.css +10 -0
- package/src/themes/intl/es.css +10 -0
- package/src/themes/intl/fr.css +10 -0
- package/src/themes/intl/ja.css +10 -0
- package/src/themes/intl/pt.css +10 -0
- package/src/themes/intl/zh.css +10 -0
- package/src/themes/intl.css +10 -0
- package/src/themes/monokai.css +1 -1
- package/src/themes/vaporwave.css +1 -1
- package/src/ts/extensions.ts +3 -36
- package/src/ts/perspective-viewer.ts +1 -1
- package/src/ts/plugin.ts +4 -1
- package/src/{rust/utils/tests/request_animation_frame.rs → ts/select-detail.ts} +32 -26
- package/src/ts/ts-rs/DerivedTableConfig.ts +22 -0
- package/src/ts/ts-rs/ExportMethod.ts +1 -1
- package/src/ts/ts-rs/PanelViewerConfig.ts +7 -6
- package/src/ts/ts-rs/RestoreOptions.ts +3 -7
- package/src/ts/ts-rs/TableRef.ts +9 -0
- package/src/ts/ts-rs/ViewerConfig.ts +7 -6
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +7 -5
- package/src/ts/ts-rs/WorkspaceConfig.ts +7 -1
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -0
- package/dist/esm/perspective-viewer.inline.d.ts +0 -3
- package/dist/esm/perspective-viewer.inline.js +0 -21
- package/dist/esm/perspective-viewer.inline.js.map +0 -7
- package/src/rust/tasks/restore_and_render.rs +0 -107
- package/src/rust/utils/browser/tests/debounce.rs +0 -163
- package/src/rust/utils/tests/pubsub.rs +0 -95
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-ea71a3990cad35d7 → perspective-viewer-d0d525977a1acba2}/inline5.js +0 -0
|
@@ -103,8 +103,8 @@ fn dispatch_column_settings_open_changed(
|
|
|
103
103
|
event_init.set_detail(&JsValue::from(
|
|
104
104
|
json!({"open": open, "column_name": column_name}),
|
|
105
105
|
));
|
|
106
|
-
// Bubble so the event still reaches the host (and any public listeners)
|
|
107
|
-
// though it's dispatched on a panel's plugin below it.
|
|
106
|
+
// Bubble so the event still reaches the host (and any public listeners)
|
|
107
|
+
// even though it's dispatched on a panel's plugin below it.
|
|
108
108
|
event_init.set_bubbles(true);
|
|
109
109
|
let event = web_sys::CustomEvent::new_with_event_init_dict(
|
|
110
110
|
"perspective-toggle-column-settings",
|
|
@@ -116,8 +116,8 @@ fn dispatch_column_settings_open_changed(
|
|
|
116
116
|
// viewer hosts every panel's plugin under one element, and each datagrid
|
|
117
117
|
// listens for this event on itself — so dispatching on the host (which the
|
|
118
118
|
// event bubbles to) would highlight EVERY datagrid. The column settings
|
|
119
|
-
// apply to the active panel, so scope the highlight to its plugin; fall
|
|
120
|
-
// to the host if none is drawn.
|
|
119
|
+
// apply to the active panel, so scope the highlight to its plugin; fall
|
|
120
|
+
// back to the host if none is drawn.
|
|
121
121
|
let target: web_sys::EventTarget = workspace
|
|
122
122
|
.active_id()
|
|
123
123
|
.and_then(|id| workspace.panel(&id))
|
|
@@ -149,19 +149,21 @@ fn dispatch_config_update(
|
|
|
149
149
|
session: &Session,
|
|
150
150
|
renderer: &Renderer,
|
|
151
151
|
presentation: &Presentation,
|
|
152
|
+
workspace: &Workspace,
|
|
152
153
|
) {
|
|
153
154
|
if session.get_table().is_none() && session.pending_table().is_none() {
|
|
154
155
|
return;
|
|
155
156
|
}
|
|
156
157
|
|
|
157
|
-
clone!(session, renderer, presentation);
|
|
158
|
+
clone!(session, renderer, presentation, workspace);
|
|
158
159
|
let elem = elem.clone();
|
|
159
160
|
let tracker = session.clone();
|
|
160
161
|
tracker.track_dispatch(async move {
|
|
161
162
|
let viewer_config = renderer
|
|
162
163
|
.clone()
|
|
163
164
|
.with_lock(async { get_viewer_config(&session, &renderer, &presentation).await })
|
|
164
|
-
.await
|
|
165
|
+
.await?
|
|
166
|
+
.map_table(|name| workspace.inline_table_ref(&name));
|
|
165
167
|
|
|
166
168
|
if viewer_config.view_config != Default::default()
|
|
167
169
|
&& Some(&viewer_config) != session.last_dispatched_config.borrow().as_deref()
|
|
@@ -215,7 +217,13 @@ pub fn wire_element_events(
|
|
|
215
217
|
clone!(elem, presentation, workspace);
|
|
216
218
|
move |_| {
|
|
217
219
|
if let Some(panel) = workspace.active_panel() {
|
|
218
|
-
dispatch_config_update(
|
|
220
|
+
dispatch_config_update(
|
|
221
|
+
&elem,
|
|
222
|
+
&panel.session,
|
|
223
|
+
&panel.renderer,
|
|
224
|
+
&presentation,
|
|
225
|
+
&workspace,
|
|
226
|
+
);
|
|
219
227
|
}
|
|
220
228
|
}
|
|
221
229
|
});
|
|
@@ -234,7 +242,13 @@ pub fn wire_element_events(
|
|
|
234
242
|
|
|
235
243
|
dispatch_event(&elem, "toggle-settings", open).unwrap();
|
|
236
244
|
if let Some(panel) = workspace.active_panel() {
|
|
237
|
-
dispatch_config_update(
|
|
245
|
+
dispatch_config_update(
|
|
246
|
+
&elem,
|
|
247
|
+
&panel.session,
|
|
248
|
+
&panel.renderer,
|
|
249
|
+
&presentation,
|
|
250
|
+
&workspace,
|
|
251
|
+
);
|
|
238
252
|
}
|
|
239
253
|
}
|
|
240
254
|
});
|
|
@@ -328,12 +342,13 @@ pub fn wire_panel_events(
|
|
|
328
342
|
session: &Session,
|
|
329
343
|
renderer: &Renderer,
|
|
330
344
|
presentation: &Presentation,
|
|
345
|
+
workspace: &Workspace,
|
|
331
346
|
) -> Vec<Subscription> {
|
|
332
347
|
let plugin_sub = renderer.plugin_changed.add_listener({
|
|
333
|
-
clone!(elem, session, renderer, presentation);
|
|
348
|
+
clone!(elem, session, renderer, presentation, workspace);
|
|
334
349
|
move |plugin| {
|
|
335
350
|
dispatch_plugin_changed(&elem, &renderer, &plugin);
|
|
336
|
-
dispatch_config_update(&elem, &session, &renderer, &presentation);
|
|
351
|
+
dispatch_config_update(&elem, &session, &renderer, &presentation, &workspace);
|
|
337
352
|
}
|
|
338
353
|
});
|
|
339
354
|
|
|
@@ -343,13 +358,13 @@ pub fn wire_panel_events(
|
|
|
343
358
|
// paused-`load()` case). The dispatcher dedups, so REBUILD's pairing
|
|
344
359
|
// with `view_created` can't double-fire it.
|
|
345
360
|
let view_sub = session.commit_reconciled.add_listener({
|
|
346
|
-
clone!(elem, session, renderer, presentation);
|
|
347
|
-
move |_| dispatch_config_update(&elem, &session, &renderer, &presentation)
|
|
361
|
+
clone!(elem, session, renderer, presentation, workspace);
|
|
362
|
+
move |_| dispatch_config_update(&elem, &session, &renderer, &presentation, &workspace)
|
|
348
363
|
});
|
|
349
364
|
|
|
350
365
|
let title_sub = session.title_changed.add_listener({
|
|
351
|
-
clone!(elem, session, renderer, presentation);
|
|
352
|
-
move |_| dispatch_config_update(&elem, &session, &renderer, &presentation)
|
|
366
|
+
clone!(elem, session, renderer, presentation, workspace);
|
|
367
|
+
move |_| dispatch_config_update(&elem, &session, &renderer, &presentation, &workspace)
|
|
353
368
|
});
|
|
354
369
|
|
|
355
370
|
let unload_sub = session.table_unloaded.add_listener({
|
|
@@ -365,22 +380,22 @@ pub fn wire_panel_events(
|
|
|
365
380
|
});
|
|
366
381
|
|
|
367
382
|
let select_sub = renderer.selection_changed.add_listener({
|
|
368
|
-
clone!(elem, session, renderer, presentation);
|
|
383
|
+
clone!(elem, session, renderer, presentation, workspace);
|
|
369
384
|
move |window: Option<ViewWindow>| {
|
|
370
385
|
let detail = JsValue::from_serde_ext(&window).unwrap();
|
|
371
386
|
annotate_panel(&detail, &renderer);
|
|
372
387
|
let _ = dispatch_panel_event(&elem, &renderer, "select", &detail);
|
|
373
|
-
dispatch_config_update(&elem, &session, &renderer, &presentation);
|
|
388
|
+
dispatch_config_update(&elem, &session, &renderer, &presentation, &workspace);
|
|
374
389
|
}
|
|
375
390
|
});
|
|
376
391
|
|
|
377
392
|
let column_style_sub = renderer.column_style_changed.add_listener({
|
|
378
|
-
clone!(elem, session, renderer, presentation);
|
|
393
|
+
clone!(elem, session, renderer, presentation, workspace);
|
|
379
394
|
move |cfg: ColumnConfigMap| {
|
|
380
395
|
let detail = JsValue::from_serde_ext(&cfg).unwrap();
|
|
381
396
|
annotate_panel(&detail, &renderer);
|
|
382
397
|
let _ = dispatch_panel_event(&elem, &renderer, "column-style-change", &detail);
|
|
383
|
-
dispatch_config_update(&elem, &session, &renderer, &presentation);
|
|
398
|
+
dispatch_config_update(&elem, &session, &renderer, &presentation, &workspace);
|
|
384
399
|
}
|
|
385
400
|
});
|
|
386
401
|
|
|
@@ -0,0 +1,266 @@
|
|
|
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
|
+
use std::collections::{BTreeMap, BTreeSet};
|
|
14
|
+
|
|
15
|
+
use perspective_client::config::ViewConfig;
|
|
16
|
+
use perspective_client::{DeleteOptions, Table, View};
|
|
17
|
+
use perspective_js::utils::*;
|
|
18
|
+
|
|
19
|
+
use crate::config::DerivedTableConfig;
|
|
20
|
+
use crate::session::ViewSubscription;
|
|
21
|
+
use crate::utils::PtrEqRc;
|
|
22
|
+
|
|
23
|
+
/// The `View` a derived `Table` reads, which is a panel's own subscription when
|
|
24
|
+
/// the derivation consumed it and a bare `View` when a restore rebuilt it.
|
|
25
|
+
pub enum DerivedSource {
|
|
26
|
+
Panel(ViewSubscription),
|
|
27
|
+
Restored(View),
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
impl DerivedSource {
|
|
31
|
+
async fn delete(self) -> ApiResult<()> {
|
|
32
|
+
match self {
|
|
33
|
+
Self::Panel(sub) => sub.delete().await,
|
|
34
|
+
Self::Restored(view) => Ok(view.delete().await?),
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// A live derived `Table` and the `View` it reads.
|
|
40
|
+
pub struct DerivedTable {
|
|
41
|
+
parent: String,
|
|
42
|
+
view_config: ViewConfig,
|
|
43
|
+
source: DerivedSource,
|
|
44
|
+
table: Table,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
impl DerivedTable {
|
|
48
|
+
pub fn new(
|
|
49
|
+
parent: String,
|
|
50
|
+
view_config: ViewConfig,
|
|
51
|
+
source: DerivedSource,
|
|
52
|
+
table: Table,
|
|
53
|
+
) -> Self {
|
|
54
|
+
Self {
|
|
55
|
+
parent,
|
|
56
|
+
view_config,
|
|
57
|
+
source,
|
|
58
|
+
table,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
pub fn parent(&self) -> &str {
|
|
63
|
+
&self.parent
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
pub fn view_config(&self) -> &ViewConfig {
|
|
67
|
+
&self.view_config
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
pub fn table(&self) -> &Table {
|
|
71
|
+
&self.table
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/// The persisted definition of this derivation.
|
|
75
|
+
pub fn config(&self) -> DerivedTableConfig {
|
|
76
|
+
DerivedTableConfig {
|
|
77
|
+
table: self.parent.clone(),
|
|
78
|
+
view_config: self.view_config.clone(),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// Whether this derivation was built from `config`, and so may be reused.
|
|
83
|
+
pub fn matches(&self, config: &DerivedTableConfig) -> bool {
|
|
84
|
+
self.parent == config.table && self.view_config == config.view_config
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/// Delete the `Table` before the `View` it reads, which the engine
|
|
88
|
+
/// requires in that order.
|
|
89
|
+
pub async fn delete(self) -> ApiResult<()> {
|
|
90
|
+
self.table.delete(DeleteOptions::default()).await?;
|
|
91
|
+
self.source.delete().await
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/// The workspace's derived `Table`s: those live, and those whose parent is not
|
|
96
|
+
/// yet hosted.
|
|
97
|
+
#[derive(Default)]
|
|
98
|
+
pub struct DerivedRegistry {
|
|
99
|
+
live: BTreeMap<String, DerivedTable>,
|
|
100
|
+
pending: BTreeMap<String, DerivedTableConfig>,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
impl DerivedRegistry {
|
|
104
|
+
pub fn get(&self, name: &str) -> Option<&DerivedTable> {
|
|
105
|
+
self.live.get(name)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
pub fn contains(&self, name: &str) -> bool {
|
|
109
|
+
self.live.contains_key(name)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
pub fn insert(&mut self, name: String, table: DerivedTable) {
|
|
113
|
+
self.pending.remove(&name);
|
|
114
|
+
self.live.insert(name, table);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pub fn remove(&mut self, name: &str) -> Option<DerivedTable> {
|
|
118
|
+
self.pending.remove(name);
|
|
119
|
+
self.live.remove(name)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
pub fn names(&self) -> Vec<String> {
|
|
123
|
+
self.live.keys().cloned().collect()
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// The name of a live derivation built from `config`, regardless of what
|
|
127
|
+
/// it is named, for inline-definition restores to rebind to.
|
|
128
|
+
pub fn find_structural(&self, config: &DerivedTableConfig) -> Option<String> {
|
|
129
|
+
self.live
|
|
130
|
+
.iter()
|
|
131
|
+
.find(|(_, table)| table.matches(config))
|
|
132
|
+
.map(|(name, _)| name.clone())
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// The definitions waiting on a parent that is not yet hosted.
|
|
136
|
+
pub fn pending(&self) -> BTreeMap<String, DerivedTableConfig> {
|
|
137
|
+
self.pending.clone()
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
pub fn set_pending(&mut self, pending: BTreeMap<String, DerivedTableConfig>) {
|
|
141
|
+
self.pending = pending;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// Every derivation's persisted definition, live or pending, for
|
|
145
|
+
/// `saveWorkspace`.
|
|
146
|
+
pub fn configs(&self) -> BTreeMap<String, DerivedTableConfig> {
|
|
147
|
+
let mut out = self.pending.clone();
|
|
148
|
+
out.extend(
|
|
149
|
+
self.live
|
|
150
|
+
.iter()
|
|
151
|
+
.map(|(name, table)| (name.clone(), table.config())),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
out
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// The names whose `parent` is `name`.
|
|
158
|
+
pub fn children(&self, name: &str) -> Vec<String> {
|
|
159
|
+
self.live
|
|
160
|
+
.iter()
|
|
161
|
+
.filter(|(_, table)| table.parent == name)
|
|
162
|
+
.map(|(child, _)| child.clone())
|
|
163
|
+
.chain(
|
|
164
|
+
self.pending
|
|
165
|
+
.iter()
|
|
166
|
+
.filter(|(_, config)| config.table == name)
|
|
167
|
+
.map(|(child, _)| child.clone()),
|
|
168
|
+
)
|
|
169
|
+
.collect()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// `name`'s ancestors, nearest first, stopping at the first non-derived
|
|
173
|
+
/// `Table`.
|
|
174
|
+
pub fn ancestors(&self, name: &str) -> Vec<String> {
|
|
175
|
+
let mut out = vec![];
|
|
176
|
+
let mut seen = BTreeSet::new();
|
|
177
|
+
let mut cursor = name.to_owned();
|
|
178
|
+
while let Some(entry) = self.live.get(&cursor) {
|
|
179
|
+
if !seen.insert(cursor.clone()) {
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
out.push(entry.parent.clone());
|
|
184
|
+
cursor = entry.parent.clone();
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
out
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Order `configs` parents-first, or report the names forming a cycle.
|
|
192
|
+
pub fn topo_order(configs: &BTreeMap<String, DerivedTableConfig>) -> Result<Vec<String>, String> {
|
|
193
|
+
let mut pending = configs.keys().cloned().collect::<BTreeSet<_>>();
|
|
194
|
+
let mut out = Vec::with_capacity(configs.len());
|
|
195
|
+
while !pending.is_empty() {
|
|
196
|
+
let ready = pending
|
|
197
|
+
.iter()
|
|
198
|
+
.filter(|name| {
|
|
199
|
+
let parent = &configs[*name].table;
|
|
200
|
+
!pending.contains(parent)
|
|
201
|
+
})
|
|
202
|
+
.cloned()
|
|
203
|
+
.collect::<Vec<_>>();
|
|
204
|
+
|
|
205
|
+
if ready.is_empty() {
|
|
206
|
+
let cycle = pending.into_iter().collect::<Vec<_>>().join("`, `");
|
|
207
|
+
return Err(format!(
|
|
208
|
+
"`derived_tables` has a dependency cycle among `{cycle}`"
|
|
209
|
+
));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
for name in ready {
|
|
213
|
+
pending.remove(&name);
|
|
214
|
+
out.push(name);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
Ok(out)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/// The subset of `order` that may be reused from `registry`, which requires
|
|
222
|
+
/// every ancestor within `configs` to be reusable too.
|
|
223
|
+
pub fn reusable(
|
|
224
|
+
order: &[String],
|
|
225
|
+
configs: &BTreeMap<String, DerivedTableConfig>,
|
|
226
|
+
registry: &DerivedRegistry,
|
|
227
|
+
) -> BTreeSet<String> {
|
|
228
|
+
let mut out = BTreeSet::new();
|
|
229
|
+
for name in order {
|
|
230
|
+
let config = &configs[name];
|
|
231
|
+
let parent_ok = !configs.contains_key(&config.table) || out.contains(&config.table);
|
|
232
|
+
if parent_ok
|
|
233
|
+
&& registry
|
|
234
|
+
.get(name)
|
|
235
|
+
.is_some_and(|table| table.matches(config))
|
|
236
|
+
{
|
|
237
|
+
out.insert(name.clone());
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
out
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/// A `<table> GROUP BY <cols>`-style name that collides with nothing in
|
|
245
|
+
/// `taken`.
|
|
246
|
+
pub fn suggest_name(
|
|
247
|
+
taken: &BTreeSet<String>,
|
|
248
|
+
config: &PtrEqRc<ViewConfig>,
|
|
249
|
+
table: &Option<String>,
|
|
250
|
+
) -> String {
|
|
251
|
+
let table_name = table.as_deref().unwrap_or("...");
|
|
252
|
+
let name = if config.group_by.is_empty() {
|
|
253
|
+
table_name.to_owned()
|
|
254
|
+
} else {
|
|
255
|
+
format!("{} GROUP BY {}", table_name, config.group_by.join(", "))
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
if taken.contains(&name) {
|
|
259
|
+
(1..)
|
|
260
|
+
.map(|n| format!("{name} {n}"))
|
|
261
|
+
.find(|name| !taken.contains(name))
|
|
262
|
+
.unwrap()
|
|
263
|
+
} else {
|
|
264
|
+
name
|
|
265
|
+
}
|
|
266
|
+
}
|
|
@@ -151,57 +151,3 @@ pub fn tokenize(input: &str) -> Vec<Token<'_>> {
|
|
|
151
151
|
|
|
152
152
|
tokens
|
|
153
153
|
}
|
|
154
|
-
|
|
155
|
-
#[cfg(test)]
|
|
156
|
-
mod tests {
|
|
157
|
-
use wasm_bindgen_test::*;
|
|
158
|
-
|
|
159
|
-
use super::*;
|
|
160
|
-
|
|
161
|
-
#[wasm_bindgen_test]
|
|
162
|
-
fn test_simple() {
|
|
163
|
-
let s = "123 abc 'hello' \"Sales\"";
|
|
164
|
-
assert_eq!(tokenize(s), vec![
|
|
165
|
-
Literal("123"),
|
|
166
|
-
Whitespace(" "),
|
|
167
|
-
Symbol("abc"),
|
|
168
|
-
Whitespace(" "),
|
|
169
|
-
Column("'hello'"),
|
|
170
|
-
Whitespace(" "),
|
|
171
|
-
Literal("\"Sales\"")
|
|
172
|
-
]);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
#[wasm_bindgen_test]
|
|
176
|
-
fn test_complex_string() {
|
|
177
|
-
let s = "'this is 'a \"test of\" strings";
|
|
178
|
-
assert_eq!(tokenize(s), vec![
|
|
179
|
-
Column("'this is '"),
|
|
180
|
-
Symbol("a"),
|
|
181
|
-
Whitespace(" "),
|
|
182
|
-
Literal("\"test of\""),
|
|
183
|
-
Whitespace(" "),
|
|
184
|
-
Symbol("strings"),
|
|
185
|
-
]);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
#[wasm_bindgen_test]
|
|
189
|
-
fn test_comment_newline() {
|
|
190
|
-
let s = "// Title\n1 + 2";
|
|
191
|
-
assert_eq!(tokenize(s), vec![
|
|
192
|
-
Comment("// Title"),
|
|
193
|
-
Break("\n"),
|
|
194
|
-
Literal("1"),
|
|
195
|
-
Whitespace(" "),
|
|
196
|
-
Operator("+"),
|
|
197
|
-
Whitespace(" "),
|
|
198
|
-
Literal("2"),
|
|
199
|
-
]);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
#[wasm_bindgen_test]
|
|
203
|
-
fn test_escape_strings() {
|
|
204
|
-
let s = "'test\\/'";
|
|
205
|
-
assert_eq!(tokenize(s), vec![Literal("'test\\/'"),]);
|
|
206
|
-
}
|
|
207
|
-
}
|
package/src/rust/js/plugin.rs
CHANGED
|
@@ -51,7 +51,7 @@ extern "C" {
|
|
|
51
51
|
/// call — the view re-renders and re-queries the schema once the
|
|
52
52
|
/// async fetch resolves.
|
|
53
53
|
#[wasm_bindgen(method, catch, js_name = column_config_schema)]
|
|
54
|
-
pub fn _column_config_schema(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>, column_name: &str, current_value: &JsValue, view_config: &JsValue,
|
|
54
|
+
pub fn _column_config_schema(this: &JsPerspectiveViewerPlugin, view_type: &str, group: Option<&str>, column_name: &str, current_value: &JsValue, view_config: &JsValue, plugin_config: &JsValue, role: &str) -> ApiResult<JsValue>;
|
|
55
55
|
|
|
56
56
|
/// The plugin-level settings schema as it applies to `current_value`, the
|
|
57
57
|
/// `plugin_config` state the schema should describe.
|
package/src/rust/lib.rs
CHANGED
|
@@ -34,6 +34,7 @@ pub mod components;
|
|
|
34
34
|
pub mod config;
|
|
35
35
|
pub mod custom_elements;
|
|
36
36
|
mod custom_events;
|
|
37
|
+
mod derived;
|
|
37
38
|
pub mod exprtk;
|
|
38
39
|
mod js;
|
|
39
40
|
mod presentation;
|
|
@@ -89,6 +90,8 @@ export type * from "../../src/ts/ts-rs/ViewerConfigUpdate.d.ts";
|
|
|
89
90
|
export type * from "../../src/ts/ts-rs/ViewerConfigInitial.d.ts";
|
|
90
91
|
export type * from "../../src/ts/ts-rs/PluginStaticConfig.d.ts";
|
|
91
92
|
export type * from "../../src/ts/ts-rs/WorkspaceConfig.d.ts";
|
|
93
|
+
export type * from "../../src/ts/ts-rs/TableRef.d.ts";
|
|
94
|
+
export type * from "../../src/ts/ts-rs/DerivedTableConfig.d.ts";
|
|
92
95
|
export type * from "../../src/ts/ts-rs/WorkspaceConfigUpdate.d.ts";
|
|
93
96
|
export type * from "../../src/ts/ts-rs/ExportMethod.d.ts";
|
|
94
97
|
export type * from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
package/src/rust/presentation.rs
CHANGED
|
@@ -233,6 +233,8 @@ impl Presentation {
|
|
|
233
233
|
self.viewer_elem
|
|
234
234
|
.toggle_attribute_with_force("settings", opt)
|
|
235
235
|
.unwrap();
|
|
236
|
+
|
|
237
|
+
crate::utils::stamp_plugin_host_state_all(&self.viewer_elem);
|
|
236
238
|
}
|
|
237
239
|
|
|
238
240
|
pub fn is_settings_open(&self) -> bool {
|
|
@@ -652,10 +654,12 @@ impl Presentation {
|
|
|
652
654
|
_ => None,
|
|
653
655
|
};
|
|
654
656
|
|
|
655
|
-
self.
|
|
657
|
+
self.reset_drag_state();
|
|
656
658
|
if let Some(action) = action {
|
|
657
659
|
self.drop_received.emit(action);
|
|
658
660
|
}
|
|
661
|
+
|
|
662
|
+
self.emit_drag_end();
|
|
659
663
|
}
|
|
660
664
|
|
|
661
665
|
/// Start the drag/drop action with the name of the column being dragged.
|
|
@@ -681,11 +685,26 @@ impl Presentation {
|
|
|
681
685
|
}
|
|
682
686
|
|
|
683
687
|
fn end_drag(&self) {
|
|
688
|
+
self.reset_drag_state();
|
|
689
|
+
self.emit_drag_end();
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
fn reset_drag_state(&self) {
|
|
684
693
|
self.drag_target.borrow_mut().take();
|
|
685
694
|
*self.drag_state.borrow_mut() = DragState::NoDrag;
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/// Notify the root one microtask late, so a drop's config edit (itself
|
|
698
|
+
/// notified through a microtask) lands in the same render batch.
|
|
699
|
+
fn emit_drag_end(&self) {
|
|
700
|
+
let emit = self.on_dragend.borrow().clone();
|
|
701
|
+
ApiFuture::spawn(async move {
|
|
702
|
+
if let Some(cb) = emit {
|
|
703
|
+
cb.emit(());
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
Ok(())
|
|
707
|
+
});
|
|
689
708
|
}
|
|
690
709
|
|
|
691
710
|
/// Register a `dragend` listener on the host `<perspective-viewer>`
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
use perspective_client::config::ViewConfig;
|
|
13
|
+
use perspective_client::config::{ColumnType, ViewConfig};
|
|
14
14
|
|
|
15
15
|
use crate::presentation::{ColumnLocator, ColumnSettingsTarget, OpenColumnSettings};
|
|
16
16
|
use crate::renderer::Renderer;
|
|
@@ -37,11 +37,62 @@ pub fn classify_column(
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
/// Whether `name` is one of `view_config`'s pivots, whose own raw values a
|
|
41
|
+
/// plugin draws as a header label or axis tick and therefore formats with its
|
|
42
|
+
/// TABLE type rather than an aggregate's view type.
|
|
43
|
+
pub fn is_pivot_column(name: &str, view_config: &ViewConfig) -> bool {
|
|
44
|
+
view_config.group_by.iter().any(|col| col == name)
|
|
45
|
+
|| view_config.split_by.iter().any(|col| col == name)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// Whether `name` is configured AS a pivot: a pivot that is also in `columns`
|
|
49
|
+
/// is configured as that aggregate instead, so its pivot copy is not
|
|
50
|
+
/// separately configurable.
|
|
51
|
+
pub fn is_pivot_only_column(name: &str, view_config: &ViewConfig) -> bool {
|
|
52
|
+
is_pivot_column(name, view_config)
|
|
53
|
+
&& !view_config
|
|
54
|
+
.columns
|
|
55
|
+
.iter()
|
|
56
|
+
.any(|col| col.as_deref() == Some(name))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// Whether the active plugin offers `name` any column config, gating every
|
|
60
|
+
/// affordance that opens the Style tab.
|
|
61
|
+
// TODO This hardcodes the set of types a plugin offers a pivot a format control
|
|
62
|
+
// for, instead of asking it. `get_column_config_schema` answers exactly
|
|
63
|
+
// (`!schema.leaf_fields().is_empty()`) but costs a plugin call per pivot pill
|
|
64
|
+
// per render; switch to it if pills ever outgrow a handful, or if a plugin's
|
|
65
|
+
// pivot controls stop being format-only.
|
|
66
|
+
pub fn has_column_config(
|
|
67
|
+
name: &str,
|
|
68
|
+
view_config: &ViewConfig,
|
|
69
|
+
metadata: &SessionMetadata,
|
|
70
|
+
renderer: &Renderer,
|
|
71
|
+
) -> bool {
|
|
72
|
+
if !renderer.can_render_column_styles() {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if view_config
|
|
77
|
+
.columns
|
|
78
|
+
.iter()
|
|
79
|
+
.any(|col| col.as_deref() == Some(name))
|
|
80
|
+
{
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
is_pivot_column(name, view_config)
|
|
85
|
+
&& matches!(
|
|
86
|
+
metadata.get_column_table_type(name),
|
|
87
|
+
Some(ColumnType::Integer | ColumnType::Float | ColumnType::Date | ColumnType::Datetime)
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
40
91
|
/// Gets a [`ColumnLocator`] for the current UI's column settings state,
|
|
41
92
|
/// or [`None`] if it is not currently active.
|
|
42
93
|
///
|
|
43
|
-
/// Table columns only have a useful sidebar (the Style tab)
|
|
44
|
-
///
|
|
94
|
+
/// Table columns only have a useful sidebar (the Style tab) when they're in
|
|
95
|
+
/// `view_config.columns` or are a pivot.
|
|
45
96
|
pub fn get_current_column_locator(
|
|
46
97
|
open_column_settings: &OpenColumnSettings,
|
|
47
98
|
renderer: &Renderer,
|
|
@@ -54,54 +105,10 @@ pub fn get_current_column_locator(
|
|
|
54
105
|
let locator = classify_column(name, view_config, metadata)?;
|
|
55
106
|
match locator {
|
|
56
107
|
ColumnLocator::Table(_) => {
|
|
57
|
-
|
|
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)
|
|
108
|
+
has_column_config(name, view_config, metadata, renderer).then_some(locator)
|
|
65
109
|
},
|
|
66
110
|
locator => Some(locator),
|
|
67
111
|
}
|
|
68
112
|
},
|
|
69
113
|
}
|
|
70
114
|
}
|
|
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
|
-
}
|
|
97
|
-
|
|
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
|
-
}
|
|
107
|
-
}
|
|
@@ -26,7 +26,7 @@ pub async fn get_viewer_config(
|
|
|
26
26
|
session: &Session,
|
|
27
27
|
renderer: &Renderer,
|
|
28
28
|
presentation: &Presentation,
|
|
29
|
-
) -> ApiResult<ViewerConfig
|
|
29
|
+
) -> ApiResult<ViewerConfig<String, String>> {
|
|
30
30
|
let version = config::API_VERSION.to_string();
|
|
31
31
|
let mut view_config = if let Some(rendered) = session.get_rendered_view_config() {
|
|
32
32
|
(*rendered).clone()
|