@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
|
@@ -13,9 +13,7 @@
|
|
|
13
13
|
use std::collections::HashSet;
|
|
14
14
|
use std::rc::Rc;
|
|
15
15
|
|
|
16
|
-
use perspective_client::config::{
|
|
17
|
-
ColumnType, WindowAggregate, WindowFrame, WindowSort, WindowSortDir, WindowSpec,
|
|
18
|
-
};
|
|
16
|
+
use perspective_client::config::{ColumnType, WindowFrame, WindowSort, WindowSortDir, WindowSpec};
|
|
19
17
|
use wasm_bindgen::JsCast;
|
|
20
18
|
use web_sys::{DragEvent, HtmlInputElement, MouseEvent};
|
|
21
19
|
use yew::prelude::*;
|
|
@@ -30,63 +28,33 @@ use crate::presentation::Presentation;
|
|
|
30
28
|
use crate::session::{Session, SessionMetadataRc};
|
|
31
29
|
use crate::utils::{AddListener, DragEffect, DragTarget, Subscription};
|
|
32
30
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
WindowAggregate::Last => "last",
|
|
44
|
-
WindowAggregate::Lag => "lag",
|
|
45
|
-
WindowAggregate::Lead => "lead",
|
|
46
|
-
WindowAggregate::Diff => "diff",
|
|
47
|
-
WindowAggregate::Rate => "rate",
|
|
48
|
-
WindowAggregate::Ema => "ema",
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
fn op_from_label(label: &str) -> Option<WindowAggregate> {
|
|
53
|
-
Some(match label {
|
|
54
|
-
"sum" => WindowAggregate::Sum,
|
|
55
|
-
"avg" => WindowAggregate::Avg,
|
|
56
|
-
"count" => WindowAggregate::Count,
|
|
57
|
-
"min" => WindowAggregate::Min,
|
|
58
|
-
"max" => WindowAggregate::Max,
|
|
59
|
-
"stddev" => WindowAggregate::Stddev,
|
|
60
|
-
"var" => WindowAggregate::Var,
|
|
61
|
-
"first" => WindowAggregate::First,
|
|
62
|
-
"last" => WindowAggregate::Last,
|
|
63
|
-
"lag" => WindowAggregate::Lag,
|
|
64
|
-
"lead" => WindowAggregate::Lead,
|
|
65
|
-
"diff" => WindowAggregate::Diff,
|
|
66
|
-
"rate" => WindowAggregate::Rate,
|
|
67
|
-
"ema" => WindowAggregate::Ema,
|
|
68
|
-
_ => return None,
|
|
69
|
-
})
|
|
31
|
+
/// The declared capabilities of one window aggregate, for a `source` column
|
|
32
|
+
/// type. Which controls an aggregate needs is the data model's to state - the
|
|
33
|
+
/// editor cannot infer it from a name it has never seen.
|
|
34
|
+
fn op_spec(
|
|
35
|
+
metadata: &SessionMetadataRc,
|
|
36
|
+
source: &str,
|
|
37
|
+
op: &str,
|
|
38
|
+
) -> Option<perspective_client::proto::WindowAggregateArgs> {
|
|
39
|
+
let ty = metadata.get_column_table_type(source)?;
|
|
40
|
+
metadata.get_window_aggregate(ty, op)
|
|
70
41
|
}
|
|
71
42
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
| WindowAggregate::Max
|
|
80
|
-
| WindowAggregate::Stddev
|
|
81
|
-
| WindowAggregate::Var
|
|
82
|
-
)
|
|
43
|
+
/// The editor's frame-type labels, as the `frames` a declaration lists.
|
|
44
|
+
fn frame_label(frame: &str) -> &'static str {
|
|
45
|
+
match frame {
|
|
46
|
+
"rows" => "Rows",
|
|
47
|
+
"range" => "Range",
|
|
48
|
+
_ => "Cumulative",
|
|
49
|
+
}
|
|
83
50
|
}
|
|
84
51
|
|
|
85
|
-
fn
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
52
|
+
fn frame_name(label: &str) -> &'static str {
|
|
53
|
+
match label {
|
|
54
|
+
"Rows" => "rows",
|
|
55
|
+
"Range" => "range",
|
|
56
|
+
_ => "cumulative",
|
|
57
|
+
}
|
|
90
58
|
}
|
|
91
59
|
|
|
92
60
|
fn is_orderable_for_range(ty: ColumnType) -> bool {
|
|
@@ -182,7 +150,7 @@ impl WindowDraft {
|
|
|
182
150
|
};
|
|
183
151
|
|
|
184
152
|
Self {
|
|
185
|
-
op:
|
|
153
|
+
op: spec.aggregate.clone(),
|
|
186
154
|
source: spec.column.clone(),
|
|
187
155
|
order_by: spec
|
|
188
156
|
.order_by
|
|
@@ -212,7 +180,7 @@ impl WindowDraft {
|
|
|
212
180
|
}
|
|
213
181
|
|
|
214
182
|
fn validate(&self, metadata: &SessionMetadataRc) -> Result<WindowSpec, String> {
|
|
215
|
-
let op =
|
|
183
|
+
let op = self.op.clone();
|
|
216
184
|
|
|
217
185
|
// Every slot takes true `Table` columns ONLY - expression aliases
|
|
218
186
|
// and other window columns would create dependency cycles (and
|
|
@@ -245,18 +213,13 @@ impl WindowDraft {
|
|
|
245
213
|
|
|
246
214
|
// Backstop for API-authored specs opened in the editor - the op
|
|
247
215
|
// menu only offers the feature-declared set, so this is
|
|
248
|
-
// unreachable from the UI.
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
.
|
|
252
|
-
.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return Err(format!(
|
|
256
|
-
"\"{}\" is not a supported window aggregate for this column",
|
|
257
|
-
op_label(op)
|
|
258
|
-
));
|
|
259
|
-
}
|
|
216
|
+
// unreachable from the UI. The declaration also supplies the
|
|
217
|
+
// controls this op takes, below.
|
|
218
|
+
let declared = metadata
|
|
219
|
+
.get_window_aggregate(source_ty, &op)
|
|
220
|
+
.ok_or_else(|| {
|
|
221
|
+
format!("\"{op}\" is not a supported window aggregate for this column")
|
|
222
|
+
})?;
|
|
260
223
|
|
|
261
224
|
// An EMPTY order slot is valid when the backend has a natural row
|
|
262
225
|
// order to fall back on (primary key order in the engine, `rowid`
|
|
@@ -300,11 +263,15 @@ impl WindowDraft {
|
|
|
300
263
|
|
|
301
264
|
// The numeric fields are typed and input-clamped to their domains,
|
|
302
265
|
// so no parse or range errors are reachable here.
|
|
303
|
-
let frame = if
|
|
266
|
+
let frame = if declared.frames.is_empty() {
|
|
267
|
+
None
|
|
268
|
+
} else {
|
|
269
|
+
let chosen = frame_name(&self.frame_type);
|
|
270
|
+
if !declared.frames.iter().any(|x| x == chosen) {
|
|
271
|
+
return Err(format!("\"{op}\" does not support a {chosen} frame"));
|
|
272
|
+
}
|
|
273
|
+
|
|
304
274
|
match self.frame_type.as_str() {
|
|
305
|
-
"Rows" | "Cumulative" if op == WindowAggregate::Rate => {
|
|
306
|
-
return Err("\"rate\" requires a range frame".to_string());
|
|
307
|
-
},
|
|
308
275
|
"Rows" => Some(WindowFrame::Rows(self.frame_rows)),
|
|
309
276
|
"Range" => {
|
|
310
277
|
// The natural-order fallback has no units, so `range`
|
|
@@ -323,15 +290,13 @@ impl WindowDraft {
|
|
|
323
290
|
},
|
|
324
291
|
_ => None,
|
|
325
292
|
}
|
|
326
|
-
} else {
|
|
327
|
-
None
|
|
328
293
|
};
|
|
329
294
|
|
|
330
295
|
// Emit `None` at the engine default so a spec saved without an
|
|
331
296
|
// explicit `offset` round-trips unchanged (the name-stripped
|
|
332
297
|
// change-detection baseline compares specs structurally).
|
|
333
|
-
let offset = (
|
|
334
|
-
let alpha =
|
|
298
|
+
let offset = (declared.offset && self.offset != 1).then_some(self.offset);
|
|
299
|
+
let alpha = declared.alpha.then_some(self.alpha);
|
|
335
300
|
|
|
336
301
|
let mut partition_by = self.partition_by.clone();
|
|
337
302
|
partition_by.retain(|col| !col.is_empty());
|
|
@@ -676,12 +641,10 @@ impl Component for WindowEditor {
|
|
|
676
641
|
})
|
|
677
642
|
.unwrap_or_default();
|
|
678
643
|
|
|
679
|
-
if !
|
|
680
|
-
.map(|op| available.contains(&op))
|
|
681
|
-
.unwrap_or_default()
|
|
644
|
+
if !available.iter().any(|x| x.name == self.draft.op)
|
|
682
645
|
&& let Some(first) = available.first()
|
|
683
646
|
{
|
|
684
|
-
self.draft.op =
|
|
647
|
+
self.draft.op = first.name.clone();
|
|
685
648
|
}
|
|
686
649
|
},
|
|
687
650
|
DragTarget::WindowOrderBy => self.draft.order_by = column,
|
|
@@ -698,11 +661,19 @@ impl Component for WindowEditor {
|
|
|
698
661
|
WindowEditorMsg::SetOp(op) => {
|
|
699
662
|
self.draft.op = op;
|
|
700
663
|
|
|
701
|
-
// Keep the frame coherent as the op
|
|
702
|
-
//
|
|
703
|
-
// rest).
|
|
704
|
-
if
|
|
705
|
-
self.draft.
|
|
664
|
+
// Keep the frame coherent as the op changes - an op that
|
|
665
|
+
// does not accept the current frame kind coerces to its
|
|
666
|
+
// first declared one (the dropdown omits the rest).
|
|
667
|
+
if let Some(declared) =
|
|
668
|
+
op_spec(&ctx.props().metadata, &self.draft.source, &self.draft.op)
|
|
669
|
+
&& !declared.frames.is_empty()
|
|
670
|
+
&& !declared
|
|
671
|
+
.frames
|
|
672
|
+
.iter()
|
|
673
|
+
.any(|x| x == frame_name(&self.draft.frame_type))
|
|
674
|
+
&& let Some(first) = declared.frames.first()
|
|
675
|
+
{
|
|
676
|
+
self.draft.frame_type = frame_label(first).to_string();
|
|
706
677
|
}
|
|
707
678
|
},
|
|
708
679
|
WindowEditorMsg::ClearSource => self.draft.source = String::default(),
|
|
@@ -745,7 +716,7 @@ impl Component for WindowEditor {
|
|
|
745
716
|
}
|
|
746
717
|
|
|
747
718
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
748
|
-
let
|
|
719
|
+
let declared = op_spec(&ctx.props().metadata, &self.draft.source, &self.draft.op);
|
|
749
720
|
|
|
750
721
|
// The op selector renders the FEATURE-DECLARED window aggregates
|
|
751
722
|
// for the source column's type, in the server's declared order -
|
|
@@ -763,7 +734,7 @@ impl Component for WindowEditor {
|
|
|
763
734
|
})
|
|
764
735
|
.unwrap_or_default()
|
|
765
736
|
.into_iter()
|
|
766
|
-
.map(|x| SelectItem::Option(
|
|
737
|
+
.map(|x| SelectItem::Option(x.name.clone()))
|
|
767
738
|
.collect(),
|
|
768
739
|
);
|
|
769
740
|
|
|
@@ -782,17 +753,16 @@ impl Component for WindowEditor {
|
|
|
782
753
|
};
|
|
783
754
|
|
|
784
755
|
// Frame type as a dropdown; like the op selector, invalid choices
|
|
785
|
-
// are omitted rather than disabled
|
|
786
|
-
// and `SetOp` already coerces the draft
|
|
756
|
+
// are omitted rather than disabled - the declared `frames` are the
|
|
757
|
+
// menu, and `SetOp` already coerces the draft into them.
|
|
787
758
|
let frame_types: Rc<Vec<SelectItem<String>>> = Rc::new(
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
.collect(),
|
|
759
|
+
declared
|
|
760
|
+
.as_ref()
|
|
761
|
+
.map(|x| x.frames.clone())
|
|
762
|
+
.unwrap_or_default()
|
|
763
|
+
.iter()
|
|
764
|
+
.map(|x| SelectItem::Option(frame_label(x).to_string()))
|
|
765
|
+
.collect(),
|
|
796
766
|
);
|
|
797
767
|
|
|
798
768
|
// Slots take true `Table` columns ONLY - expression aliases and
|
|
@@ -927,10 +897,12 @@ impl Component for WindowEditor {
|
|
|
927
897
|
</DragDropList<WindowEditor, PivotColumn, WindowPartitionByContext>>
|
|
928
898
|
};
|
|
929
899
|
|
|
930
|
-
let show_frame =
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
900
|
+
let show_frame = declared
|
|
901
|
+
.as_ref()
|
|
902
|
+
.map(|x| !x.frames.is_empty())
|
|
903
|
+
.unwrap_or_default();
|
|
904
|
+
let show_offset = declared.as_ref().map(|x| x.offset).unwrap_or_default();
|
|
905
|
+
let show_alpha = declared.as_ref().map(|x| x.alpha).unwrap_or_default();
|
|
934
906
|
|
|
935
907
|
html! {
|
|
936
908
|
<>
|
|
@@ -15,18 +15,12 @@ use std::collections::HashSet;
|
|
|
15
15
|
use serde::{Deserialize, Serialize};
|
|
16
16
|
use serde_json::Value;
|
|
17
17
|
|
|
18
|
-
use super::{KeyValueOpts, NumberSeriesStyleDefaultConfig};
|
|
18
|
+
use super::{CssKind, KeyValueOpts, NumberSeriesStyleDefaultConfig};
|
|
19
19
|
|
|
20
20
|
/// The full schema for one column at one point in time. Plugins may return
|
|
21
21
|
/// different schemas for the same column based on the column's current
|
|
22
22
|
/// stored value (e.g. to hide dependent fields), so this is re-queried on
|
|
23
23
|
/// every field update.
|
|
24
|
-
///
|
|
25
|
-
/// Each entry is a [`ControlSpec`]. Primitive variants carry their own
|
|
26
|
-
/// `key` (JSON storage key) inline; the sidebar UI label is supplied by
|
|
27
|
-
/// CSS via `--psp-label--<key>--content`. Composite variants render a
|
|
28
|
-
/// self-contained Yew component that supplies its own labels and owns a
|
|
29
|
-
/// fixed key namespace via [`ControlSpec::serialized_keys`].
|
|
30
24
|
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
31
25
|
pub struct ColumnConfigSchema {
|
|
32
26
|
pub fields: Vec<ControlSpec>,
|
|
@@ -93,33 +87,21 @@ pub enum ControlSpec {
|
|
|
93
87
|
key: String,
|
|
94
88
|
default: String,
|
|
95
89
|
},
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
ColorRange {
|
|
103
|
-
key_pos: String,
|
|
104
|
-
key_neg: String,
|
|
105
|
-
default_pos: String,
|
|
106
|
-
default_neg: String,
|
|
107
|
-
/// When `true`, the bar renders as a continuous gradient
|
|
108
|
-
/// (e.g. for `gradient` color modes); when `false`, the bar
|
|
109
|
-
/// renders as a hard pos/neg split. Currently only changes the
|
|
110
|
-
/// visual; pos/neg semantics are unchanged.
|
|
111
|
-
#[serde(default)]
|
|
112
|
-
is_gradient: bool,
|
|
90
|
+
Palette {
|
|
91
|
+
key: String,
|
|
92
|
+
default: String,
|
|
93
|
+
|
|
94
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
95
|
+
max: Option<usize>,
|
|
113
96
|
},
|
|
97
|
+
GradientStops {
|
|
98
|
+
key: String,
|
|
99
|
+
default: String,
|
|
114
100
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
/// styling.
|
|
101
|
+
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
|
102
|
+
discrete: bool,
|
|
103
|
+
},
|
|
119
104
|
DatetimeFormat,
|
|
120
|
-
/// Residual format-only widget for string columns — owns `format`
|
|
121
|
-
/// only. Pair with primitive `Enum` and `Color` fields for
|
|
122
|
-
/// `string_color_mode` + `color` to fully decompose string styling.
|
|
123
105
|
StringFormat,
|
|
124
106
|
NumberSeriesStyle {
|
|
125
107
|
default: NumberSeriesStyleDefaultConfig,
|
|
@@ -138,6 +120,90 @@ pub struct EnumVariant {
|
|
|
138
120
|
pub label: Option<String>,
|
|
139
121
|
}
|
|
140
122
|
|
|
123
|
+
/// One stop of a [`ControlSpec::GradientStops`] value in its in-memory
|
|
124
|
+
/// form: a `#rrggbb` color at `offset` ∈ `[0, 1]`.
|
|
125
|
+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
126
|
+
pub struct GradientStopSpec {
|
|
127
|
+
pub color: String,
|
|
128
|
+
pub offset: f64,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/// The [`ControlSpec::GradientStops`] canonical stop order: offsets
|
|
132
|
+
/// clamped to `[0, 1]` and rounded to 3 decimals, stops sorted stably
|
|
133
|
+
/// by offset.
|
|
134
|
+
pub fn canonicalize_gradient_stops(mut stops: Vec<GradientStopSpec>) -> Vec<GradientStopSpec> {
|
|
135
|
+
for stop in &mut stops {
|
|
136
|
+
stop.offset = (stop.offset.clamp(0.0, 1.0) * 1000.0).round() / 1000.0;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
stops.sort_by(|a, b| {
|
|
140
|
+
a.offset
|
|
141
|
+
.partial_cmp(&b.offset)
|
|
142
|
+
.unwrap_or(std::cmp::Ordering::Equal)
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
stops
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Fit `stops` to a `discrete` field's fixed pair: an over-length value
|
|
149
|
+
/// keeps only its two end colors, pinned to `0`/`1`.
|
|
150
|
+
pub fn discrete_pair(stops: Vec<GradientStopSpec>) -> Vec<GradientStopSpec> {
|
|
151
|
+
let stops = canonicalize_gradient_stops(stops);
|
|
152
|
+
match (stops.first(), stops.last()) {
|
|
153
|
+
(Some(first), Some(last)) if stops.len() > 2 => vec![
|
|
154
|
+
GradientStopSpec {
|
|
155
|
+
color: first.color.clone(),
|
|
156
|
+
offset: 0.0,
|
|
157
|
+
},
|
|
158
|
+
GradientStopSpec {
|
|
159
|
+
color: last.color.clone(),
|
|
160
|
+
offset: 1.0,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
163
|
+
_ => stops,
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
impl ColumnConfigSchema {
|
|
168
|
+
/// Canonicalize every CSS-valued default at schema ingest, dropping
|
|
169
|
+
/// (and logging) fields whose default fails its kind's reader.
|
|
170
|
+
pub fn canonicalize_defaults(mut self) -> Self {
|
|
171
|
+
self.fields.retain_mut(|spec| {
|
|
172
|
+
let (kind, key, default) = match spec {
|
|
173
|
+
ControlSpec::Color { key, default } => (CssKind::Color, key, default),
|
|
174
|
+
ControlSpec::Palette { key, default, .. } => (CssKind::Palette, key, default),
|
|
175
|
+
ControlSpec::GradientStops { key, default, .. } => {
|
|
176
|
+
(CssKind::Gradient, key, default)
|
|
177
|
+
},
|
|
178
|
+
_ => return true,
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
match kind.canonicalize(default) {
|
|
182
|
+
Ok(canonical) => {
|
|
183
|
+
*default = canonical;
|
|
184
|
+
true
|
|
185
|
+
},
|
|
186
|
+
Err(error) => {
|
|
187
|
+
tracing::error!("Dropping `{key}` — invalid schema default: {error}");
|
|
188
|
+
false
|
|
189
|
+
},
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
self
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/// The CSS kind of the control owning `key`, if it is CSS-valued.
|
|
197
|
+
pub fn css_kind_of(&self, key: &str) -> Option<CssKind> {
|
|
198
|
+
self.fields.iter().find_map(|spec| match spec {
|
|
199
|
+
ControlSpec::Color { key: k, .. } if k == key => Some(CssKind::Color),
|
|
200
|
+
ControlSpec::Palette { key: k, .. } if k == key => Some(CssKind::Palette),
|
|
201
|
+
ControlSpec::GradientStops { key: k, .. } if k == key => Some(CssKind::Gradient),
|
|
202
|
+
_ => None,
|
|
203
|
+
})
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
141
207
|
impl ControlSpec {
|
|
142
208
|
/// Top-level JSON keys this control owns when its value is serialized
|
|
143
209
|
/// into a column's config map. For primitives this is just `[key]`;
|
|
@@ -152,14 +218,13 @@ impl ControlSpec {
|
|
|
152
218
|
ControlSpec::Symbols { .. } => vec!["symbols"],
|
|
153
219
|
ControlSpec::NumberFormat => vec!["number_format"],
|
|
154
220
|
ControlSpec::AggregateDepth => vec!["aggregate_depth"],
|
|
155
|
-
ControlSpec::ColorRange {
|
|
156
|
-
key_pos, key_neg, ..
|
|
157
|
-
} => vec![key_pos.as_str(), key_neg.as_str()],
|
|
158
221
|
ControlSpec::Enum { key, .. }
|
|
159
222
|
| ControlSpec::Bool { key, .. }
|
|
160
223
|
| ControlSpec::Number { key, .. }
|
|
161
224
|
| ControlSpec::String { key, .. }
|
|
162
|
-
| ControlSpec::Color { key, .. }
|
|
225
|
+
| ControlSpec::Color { key, .. }
|
|
226
|
+
| ControlSpec::Palette { key, .. }
|
|
227
|
+
| ControlSpec::GradientStops { key, .. } => vec![key.as_str()],
|
|
163
228
|
}
|
|
164
229
|
}
|
|
165
230
|
}
|
|
@@ -168,12 +233,6 @@ impl ControlSpec {
|
|
|
168
233
|
/// declares which top-level keys the update is allowed to write
|
|
169
234
|
/// (`keys` — equivalent to the field's [`ControlSpec::serialized_keys`])
|
|
170
235
|
/// and a partial new sub-state (`value`).
|
|
171
|
-
///
|
|
172
|
-
/// Apply semantics: keys in `keys` are *cleared* from the column's config
|
|
173
|
-
/// map, then keys present in `value` are *inserted*. Defaults are
|
|
174
|
-
/// pre-stripped by the caller (typically via `skip_serializing_if`), so
|
|
175
|
-
/// "no value set for key K" means the schema default applies and K is
|
|
176
|
-
/// not serialized.
|
|
177
236
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
178
237
|
pub struct ColumnConfigFieldUpdate {
|
|
179
238
|
pub keys: Vec<String>,
|