@perspective-dev/viewer 5.2.0 → 5.3.1
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 +8 -8
- package/dist/cdn/perspective-viewer.js.map +3 -3
- package/dist/css/blueprint.css +1 -0
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -0
- 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/ledger.css +1 -0
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -0
- 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 -0
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/column-format.d.ts +16 -3
- package/dist/esm/perspective-viewer.d.ts +1 -0
- package/dist/esm/perspective-viewer.inline.js +9 -9
- package/dist/esm/perspective-viewer.inline.js.map +3 -3
- package/dist/esm/perspective-viewer.js +8 -8
- package/dist/esm/perspective-viewer.js.map +3 -3
- package/dist/esm/plugin.d.ts +10 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +1 -1
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +13 -3
- package/dist/wasm/perspective-viewer.d.ts +43 -36
- package/dist/wasm/perspective-viewer.js +198 -136
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +19 -17
- package/package.json +1 -1
- package/src/css/column-settings-panel.css +28 -5
- package/src/css/column-style.css +291 -31
- package/src/css/containers/tabs.css +13 -3
- package/src/css/dom/select.css +21 -9
- package/src/css/form/code-editor.css +4 -4
- package/src/css/plugin-selector.css +1 -0
- package/src/css/plugin-settings-panel.css +72 -0
- package/src/css/viewer.css +0 -5
- package/src/rust/agent/tools.rs +53 -19
- package/src/rust/components/column_selector/active_column.rs +2 -2
- package/src/rust/components/column_selector/add_expression_button.rs +3 -3
- package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
- package/src/rust/components/column_selector/config_selector.rs +8 -0
- package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
- package/src/rust/components/column_selector/inactive_column.rs +2 -2
- package/src/rust/components/column_selector.rs +48 -4
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
- package/src/rust/components/column_settings_sidebar/style_tab.rs +330 -234
- package/src/rust/components/column_settings_sidebar.rs +88 -69
- package/src/rust/components/containers/control_group.rs +91 -0
- package/src/rust/components/containers/mod.rs +1 -0
- package/src/rust/components/containers/select.rs +8 -2
- package/src/rust/components/datetime_column_style/simple.rs +15 -4
- package/src/rust/components/datetime_column_style.rs +50 -10
- package/src/rust/components/editable_header.rs +85 -144
- package/src/rust/components/expression_editor.rs +9 -16
- package/src/rust/components/form/code_editor.rs +16 -6
- package/src/rust/components/form/debug.rs +1 -0
- package/src/rust/components/form/mod.rs +3 -1
- package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
- package/src/rust/components/form/named_value_picker.rs +86 -0
- package/src/rust/components/form/palette_selector.rs +440 -0
- package/src/rust/components/main_panel/msg.rs +2 -2
- package/src/rust/components/main_panel/reconcile.rs +4 -4
- package/src/rust/components/plugin_tab.rs +126 -112
- package/src/rust/components/settings_panel.rs +26 -20
- package/src/rust/components/status_bar.rs +0 -1
- package/src/rust/components/style_controls/number_string_format/digits_section.rs +22 -10
- package/src/rust/components/style_controls/number_string_format/misc_section.rs +6 -2
- package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -0
- package/src/rust/components/style_controls/number_string_format/types.rs +24 -0
- package/src/rust/components/style_controls/number_string_format.rs +37 -14
- package/src/rust/components/viewer/msg.rs +46 -7
- package/src/rust/components/viewer/panels.rs +11 -9
- package/src/rust/components/viewer/render.rs +8 -3
- package/src/rust/components/viewer/settings.rs +163 -23
- package/src/rust/components/viewer/snapshots.rs +46 -21
- package/src/rust/components/viewer/wiring.rs +14 -25
- package/src/rust/components/viewer.rs +12 -8
- package/src/rust/config/column_config_schema.rs +435 -45
- package/src/rust/config/css_scale.rs +882 -0
- package/src/rust/config/datetime_column_style.rs +4 -21
- package/src/rust/config/mod.rs +2 -0
- package/src/rust/config/number_string_format.rs +211 -18
- package/src/rust/config/options.rs +10 -0
- package/src/rust/config/viewer_config.rs +49 -55
- package/src/rust/config/workspace_config.rs +132 -5
- package/src/rust/custom_elements/viewer.rs +214 -155
- package/src/rust/custom_events.rs +14 -1
- package/src/rust/js/regular_layout.rs +3 -3
- package/src/rust/lib.rs +2 -0
- package/src/rust/presentation/column_locator.rs +19 -5
- package/src/rust/presentation.rs +209 -36
- package/src/rust/queries/column_locator.rs +74 -44
- package/src/rust/queries/get_viewer_config.rs +0 -3
- package/src/rust/queries/mod.rs +2 -0
- package/src/rust/queries/palette_set.rs +82 -0
- package/src/rust/queries/plugin_column_styles.rs +6 -2
- package/src/rust/renderer/dispatch.rs +50 -12
- package/src/rust/renderer/plugin_config.rs +362 -53
- package/src/rust/renderer.rs +15 -42
- package/src/rust/session/column_defaults_update.rs +15 -8
- package/src/rust/session/metadata.rs +5 -1
- package/src/rust/session/props.rs +1 -0
- package/src/rust/session.rs +9 -6
- package/src/rust/tasks/create_panel.rs +44 -57
- package/src/rust/tasks/edit_expression.rs +19 -23
- package/src/rust/tasks/edit_window.rs +8 -10
- package/src/rust/tasks/eject.rs +4 -1
- package/src/rust/tasks/pipeline.rs +2 -2
- package/src/rust/tasks/presize_panels.rs +44 -5
- package/src/rust/tasks/reset_all.rs +4 -4
- package/src/rust/tasks/restore_and_render.rs +17 -44
- package/src/rust/tasks/restore_panel.rs +11 -13
- package/src/rust/tasks/set_edit_mode.rs +3 -1
- package/src/rust/tasks/sync_update_panels.rs +11 -1
- package/src/rust/tasks/update_theme.rs +23 -18
- package/src/rust/utils/css_vars.rs +26 -0
- package/src/rust/utils/mod.rs +2 -0
- package/src/rust/utils/spawn.rs +1 -1
- package/src/rust/workspace.rs +435 -272
- package/src/svg/lock.svg +7 -0
- package/src/themes/blueprint.css +190 -0
- package/src/themes/botanical.css +14 -17
- package/src/themes/defaults.css +4 -110
- package/src/themes/dracula.css +19 -14
- package/src/themes/eggplant.css +189 -0
- package/src/themes/gruvbox-dark.css +23 -15
- package/src/themes/gruvbox.css +11 -1
- package/src/themes/icons.css +2 -2
- package/src/themes/intl/de.css +23 -1
- package/src/themes/intl/es.css +23 -1
- package/src/themes/intl/fr.css +23 -1
- package/src/themes/intl/ja.css +23 -1
- package/src/themes/intl/pt.css +23 -1
- package/src/themes/intl/zh.css +23 -1
- package/src/themes/intl.css +26 -4
- package/src/themes/ledger.css +195 -0
- package/src/themes/monokai.css +24 -14
- package/src/themes/nord.css +186 -0
- package/src/themes/phosphor.css +7 -14
- package/src/themes/pro-dark.css +12 -16
- package/src/themes/pro.css +118 -0
- package/src/themes/solarized-dark.css +9 -16
- package/src/themes/solarized.css +7 -0
- package/src/themes/themes.css +10 -5
- package/src/themes/vaporwave.css +22 -14
- package/src/themes/velvet.css +192 -0
- package/src/ts/column-format.ts +52 -2
- package/src/ts/perspective-viewer.ts +1 -0
- package/src/ts/plugin.ts +14 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +1 -1
- package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +1 -1
- package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +11 -3
- package/src/rust/components/form/color_range_selector.rs +0 -137
- package/src/svg/dropdown-selector-light.svg +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-02e461f3d7062b26 → perspective-viewer-87bd21b215d0631a}/inline5.js +0 -0
|
@@ -41,10 +41,6 @@ impl Default for DatetimeFormatType {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
impl DatetimeFormatType {
|
|
44
|
-
fn is_simple(&self) -> bool {
|
|
45
|
-
self == &Self::Simple(SimpleDatetimeStyleConfig::default())
|
|
46
|
-
}
|
|
47
|
-
|
|
48
44
|
pub fn time_zone(&self) -> &Option<String> {
|
|
49
45
|
match self {
|
|
50
46
|
DatetimeFormatType::Custom(x) => &x.time_zone,
|
|
@@ -62,12 +58,13 @@ impl DatetimeFormatType {
|
|
|
62
58
|
|
|
63
59
|
/// A model for the JSON serialized style configuration for a column of type
|
|
64
60
|
/// `datetime`.
|
|
65
|
-
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize, TS)]
|
|
61
|
+
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize, TS)]
|
|
66
62
|
// #[derive(WasmDescribe!, FromWasmAbi!)]
|
|
67
63
|
pub struct DatetimeColumnStyleConfig {
|
|
68
64
|
#[serde(default)]
|
|
69
|
-
#[serde(skip_serializing_if = "
|
|
70
|
-
|
|
65
|
+
#[serde(skip_serializing_if = "Option::is_none")]
|
|
66
|
+
#[ts(optional, as = "Option<_>")]
|
|
67
|
+
pub date_format: Option<DatetimeFormatType>,
|
|
71
68
|
|
|
72
69
|
#[serde(default)]
|
|
73
70
|
#[serde(skip_serializing_if = "DatetimeColorMode::is_none")]
|
|
@@ -78,17 +75,3 @@ pub struct DatetimeColumnStyleConfig {
|
|
|
78
75
|
#[ts(skip)]
|
|
79
76
|
pub color: Option<String>,
|
|
80
77
|
}
|
|
81
|
-
|
|
82
|
-
impl Default for DatetimeColumnStyleConfig {
|
|
83
|
-
fn default() -> Self {
|
|
84
|
-
Self {
|
|
85
|
-
date_format: DatetimeFormatType::Simple(SimpleDatetimeStyleConfig {
|
|
86
|
-
time_zone: Default::default(),
|
|
87
|
-
date_style: SimpleDatetimeFormat::Short,
|
|
88
|
-
time_style: SimpleDatetimeFormat::Medium,
|
|
89
|
-
}),
|
|
90
|
-
datetime_color_mode: Default::default(),
|
|
91
|
-
color: Default::default(),
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
package/src/rust/config/mod.rs
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
mod column_config_schema;
|
|
17
17
|
mod columns_config;
|
|
18
|
+
mod css_scale;
|
|
18
19
|
mod datetime_column_style;
|
|
19
20
|
mod export_method;
|
|
20
21
|
mod kvpair;
|
|
@@ -29,6 +30,7 @@ mod workspace_config;
|
|
|
29
30
|
|
|
30
31
|
pub use column_config_schema::*;
|
|
31
32
|
pub use columns_config::*;
|
|
33
|
+
pub use css_scale::*;
|
|
32
34
|
pub use datetime_column_style::*;
|
|
33
35
|
pub use export_method::*;
|
|
34
36
|
pub use number_series_style::*;
|
|
@@ -255,13 +255,106 @@ pub struct CustomNumberFormatConfig {
|
|
|
255
255
|
pub sign_display: Option<SignDisplay>,
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
+
/// The active plugin's `NumberFormat` default overlaid on the built-in
|
|
259
|
+
/// `number_format` values.
|
|
260
|
+
#[derive(Clone, Debug, PartialEq)]
|
|
261
|
+
pub struct NumberFormatDefaults {
|
|
262
|
+
pub is_float: bool,
|
|
263
|
+
pub style: NumberFormatStyle,
|
|
264
|
+
pub notation: Notation,
|
|
265
|
+
pub minimum_integer_digits: f64,
|
|
266
|
+
pub fraction: (f64, f64),
|
|
267
|
+
pub significant: (f64, f64),
|
|
268
|
+
pub rounding_priority: RoundingPriority,
|
|
269
|
+
pub rounding_mode: RoundingMode,
|
|
270
|
+
pub trailing_zero_display: TrailingZeroDisplay,
|
|
271
|
+
pub use_grouping: UseGrouping,
|
|
272
|
+
pub sign_display: SignDisplay,
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
impl NumberFormatDefaults {
|
|
276
|
+
pub fn builtin(is_float: bool) -> Self {
|
|
277
|
+
Self {
|
|
278
|
+
is_float,
|
|
279
|
+
style: NumberFormatStyle::default(),
|
|
280
|
+
notation: Notation::default(),
|
|
281
|
+
minimum_integer_digits: 1.,
|
|
282
|
+
fraction: if is_float { (2., 2.) } else { (0., 0.) },
|
|
283
|
+
significant: (1., 21.),
|
|
284
|
+
rounding_priority: RoundingPriority::default(),
|
|
285
|
+
rounding_mode: RoundingMode::default(),
|
|
286
|
+
trailing_zero_display: TrailingZeroDisplay::default(),
|
|
287
|
+
use_grouping: UseGrouping::default(),
|
|
288
|
+
sign_display: SignDisplay::default(),
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// Overlay a plugin-declared partial default onto [`Self::builtin`].
|
|
293
|
+
pub fn resolve(spec_default: Option<&CustomNumberFormatConfig>, is_float: bool) -> Self {
|
|
294
|
+
let mut out = Self::builtin(is_float);
|
|
295
|
+
let Some(d) = spec_default else {
|
|
296
|
+
return out;
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
if let Some(style) = &d._style {
|
|
300
|
+
out.style = style.clone();
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if let Some(notation) = &d._notation {
|
|
304
|
+
out.notation = notation.clone();
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if let Some(v) = d.minimum_integer_digits {
|
|
308
|
+
out.minimum_integer_digits = v;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if let Some(v) = d.minimum_fraction_digits {
|
|
312
|
+
out.fraction.0 = v;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if let Some(v) = d.maximum_fraction_digits {
|
|
316
|
+
out.fraction.1 = v;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if let Some(v) = d.minimum_significant_digits {
|
|
320
|
+
out.significant.0 = v;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if let Some(v) = d.maximum_significant_digits {
|
|
324
|
+
out.significant.1 = v;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if let Some(v) = d.rounding_priority {
|
|
328
|
+
out.rounding_priority = v;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if let Some(v) = d.rounding_mode {
|
|
332
|
+
out.rounding_mode = v;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if let Some(v) = d.trailing_zero_display {
|
|
336
|
+
out.trailing_zero_display = v;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
if let Some(v) = d.use_grouping {
|
|
340
|
+
out.use_grouping = v;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if let Some(v) = d.sign_display {
|
|
344
|
+
out.sign_display = v;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
out
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
258
351
|
impl CustomNumberFormatConfig {
|
|
259
|
-
pub fn filter_default(self,
|
|
260
|
-
let (frac_min, frac_max) =
|
|
352
|
+
pub fn filter_default(self, defaults: &NumberFormatDefaults) -> Self {
|
|
353
|
+
let (frac_min, frac_max) = defaults.fraction;
|
|
261
354
|
let rounding_increment = self.rounding_increment;
|
|
262
355
|
let use_grouping = self
|
|
263
356
|
.use_grouping
|
|
264
|
-
.filter(|val| *val !=
|
|
357
|
+
.filter(|val| *val != defaults.use_grouping);
|
|
265
358
|
|
|
266
359
|
let mut minimum_fraction_digits =
|
|
267
360
|
self.minimum_fraction_digits.filter(|val| *val != frac_min);
|
|
@@ -269,7 +362,7 @@ impl CustomNumberFormatConfig {
|
|
|
269
362
|
let mut maximum_fraction_digits =
|
|
270
363
|
self.maximum_fraction_digits.filter(|val| *val != frac_max);
|
|
271
364
|
|
|
272
|
-
let mut show_frac = is_float
|
|
365
|
+
let mut show_frac = defaults.is_float
|
|
273
366
|
&& (minimum_fraction_digits.is_some()
|
|
274
367
|
|| maximum_fraction_digits.is_some()
|
|
275
368
|
|| use_grouping.is_some()
|
|
@@ -277,7 +370,7 @@ impl CustomNumberFormatConfig {
|
|
|
277
370
|
self._style,
|
|
278
371
|
Some(NumberFormatStyle::Percent | NumberFormatStyle::Unit(_))
|
|
279
372
|
))
|
|
280
|
-
|| !is_float && matches!(self._style, Some(NumberFormatStyle::Currency(_)));
|
|
373
|
+
|| !defaults.is_float && matches!(self._style, Some(NumberFormatStyle::Currency(_)));
|
|
281
374
|
|
|
282
375
|
// Rounding increment does not work unless `minimum_fraction_digits`
|
|
283
376
|
// and `maximum_fraction_digits` are set to 0.
|
|
@@ -287,39 +380,139 @@ impl CustomNumberFormatConfig {
|
|
|
287
380
|
maximum_fraction_digits = Some(0.);
|
|
288
381
|
}
|
|
289
382
|
|
|
290
|
-
let minimum_significant_digits = self
|
|
291
|
-
|
|
383
|
+
let minimum_significant_digits = self
|
|
384
|
+
.minimum_significant_digits
|
|
385
|
+
.filter(|val| *val != defaults.significant.0);
|
|
386
|
+
|
|
387
|
+
let maximum_significant_digits = self
|
|
388
|
+
.maximum_significant_digits
|
|
389
|
+
.filter(|val| *val != defaults.significant.1);
|
|
390
|
+
|
|
292
391
|
let show_sig = minimum_significant_digits.is_some() || maximum_significant_digits.is_some();
|
|
293
392
|
Self {
|
|
294
|
-
_style: self
|
|
295
|
-
|
|
296
|
-
.
|
|
297
|
-
|
|
393
|
+
_style: self._style.filter(|style| *style != defaults.style),
|
|
394
|
+
minimum_integer_digits: self
|
|
395
|
+
.minimum_integer_digits
|
|
396
|
+
.filter(|val| *val != defaults.minimum_integer_digits),
|
|
298
397
|
minimum_fraction_digits: show_frac
|
|
299
398
|
.then_some(minimum_fraction_digits.unwrap_or(frac_min)),
|
|
300
399
|
maximum_fraction_digits: show_frac
|
|
301
400
|
.then_some(maximum_fraction_digits.unwrap_or(frac_max)),
|
|
302
401
|
minimum_significant_digits: show_sig
|
|
303
|
-
.then_some(minimum_significant_digits.unwrap_or(
|
|
402
|
+
.then_some(minimum_significant_digits.unwrap_or(defaults.significant.0)),
|
|
304
403
|
maximum_significant_digits: show_sig
|
|
305
|
-
.then_some(
|
|
404
|
+
.then_some(maximum_significant_digits.unwrap_or(defaults.significant.1)),
|
|
306
405
|
rounding_priority: self
|
|
307
406
|
.rounding_priority
|
|
308
|
-
.filter(|val| *val !=
|
|
407
|
+
.filter(|val| *val != defaults.rounding_priority),
|
|
309
408
|
rounding_increment,
|
|
310
409
|
rounding_mode: self
|
|
311
410
|
.rounding_mode
|
|
312
|
-
.filter(|val| *val !=
|
|
411
|
+
.filter(|val| *val != defaults.rounding_mode),
|
|
313
412
|
trailing_zero_display: self
|
|
314
413
|
.trailing_zero_display
|
|
315
|
-
.filter(|val| *val !=
|
|
414
|
+
.filter(|val| *val != defaults.trailing_zero_display),
|
|
316
415
|
_notation: self
|
|
317
416
|
._notation
|
|
318
|
-
.filter(|notation|
|
|
417
|
+
.filter(|notation| *notation != defaults.notation),
|
|
319
418
|
use_grouping,
|
|
320
419
|
sign_display: self
|
|
321
420
|
.sign_display
|
|
322
|
-
.filter(|val| *val !=
|
|
421
|
+
.filter(|val| *val != defaults.sign_display),
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
#[cfg(test)]
|
|
427
|
+
mod tests {
|
|
428
|
+
use super::*;
|
|
429
|
+
|
|
430
|
+
fn sig(min: f64, max: f64) -> CustomNumberFormatConfig {
|
|
431
|
+
CustomNumberFormatConfig {
|
|
432
|
+
minimum_significant_digits: Some(min),
|
|
433
|
+
maximum_significant_digits: Some(max),
|
|
434
|
+
..Default::default()
|
|
323
435
|
}
|
|
324
436
|
}
|
|
437
|
+
|
|
438
|
+
#[test]
|
|
439
|
+
fn resolve_overlays_partial_default_on_builtin() {
|
|
440
|
+
let spec = CustomNumberFormatConfig {
|
|
441
|
+
_notation: Some(Notation::Compact(CompactDisplay::Short)),
|
|
442
|
+
minimum_fraction_digits: Some(0.),
|
|
443
|
+
maximum_fraction_digits: Some(1.),
|
|
444
|
+
..Default::default()
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
let defaults = NumberFormatDefaults::resolve(Some(&spec), true);
|
|
448
|
+
assert_eq!(defaults.notation, Notation::Compact(CompactDisplay::Short));
|
|
449
|
+
assert_eq!(defaults.fraction, (0., 1.));
|
|
450
|
+
assert_eq!(defaults.style, NumberFormatStyle::Decimal);
|
|
451
|
+
assert_eq!(defaults.significant, (1., 21.));
|
|
452
|
+
assert_eq!(
|
|
453
|
+
NumberFormatDefaults::resolve(None, true),
|
|
454
|
+
NumberFormatDefaults::builtin(true)
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
#[test]
|
|
459
|
+
fn filter_preserves_max_significant_digits() {
|
|
460
|
+
let filtered = sig(1., 5.).filter_default(&NumberFormatDefaults::builtin(true));
|
|
461
|
+
assert_eq!(filtered.minimum_significant_digits, Some(1.));
|
|
462
|
+
assert_eq!(filtered.maximum_significant_digits, Some(5.));
|
|
463
|
+
|
|
464
|
+
let filtered = sig(3., 21.).filter_default(&NumberFormatDefaults::builtin(true));
|
|
465
|
+
assert_eq!(filtered.minimum_significant_digits, Some(3.));
|
|
466
|
+
assert_eq!(filtered.maximum_significant_digits, Some(21.));
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
#[test]
|
|
470
|
+
fn filter_elides_values_equal_to_resolved_defaults() {
|
|
471
|
+
let defaults = NumberFormatDefaults {
|
|
472
|
+
fraction: (0., 1.),
|
|
473
|
+
notation: Notation::Compact(CompactDisplay::Short),
|
|
474
|
+
..NumberFormatDefaults::builtin(true)
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
let config = CustomNumberFormatConfig {
|
|
478
|
+
_notation: Some(Notation::Compact(CompactDisplay::Short)),
|
|
479
|
+
minimum_fraction_digits: Some(0.),
|
|
480
|
+
maximum_fraction_digits: Some(1.),
|
|
481
|
+
..Default::default()
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
assert_eq!(
|
|
485
|
+
config.filter_default(&defaults),
|
|
486
|
+
CustomNumberFormatConfig::default()
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
#[test]
|
|
491
|
+
fn filter_serializes_builtin_values_under_override() {
|
|
492
|
+
let defaults = NumberFormatDefaults {
|
|
493
|
+
notation: Notation::Compact(CompactDisplay::Short),
|
|
494
|
+
..NumberFormatDefaults::builtin(true)
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
let config = CustomNumberFormatConfig {
|
|
498
|
+
_notation: Some(Notation::Standard),
|
|
499
|
+
..Default::default()
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
let filtered = config.filter_default(&defaults);
|
|
503
|
+
assert_eq!(filtered._notation, Some(Notation::Standard));
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
#[test]
|
|
507
|
+
fn filter_bakes_fraction_digits_for_percent_style() {
|
|
508
|
+
let config = CustomNumberFormatConfig {
|
|
509
|
+
_style: Some(NumberFormatStyle::Percent),
|
|
510
|
+
..Default::default()
|
|
511
|
+
};
|
|
512
|
+
|
|
513
|
+
let filtered = config.filter_default(&NumberFormatDefaults::builtin(true));
|
|
514
|
+
assert_eq!(filtered._style, Some(NumberFormatStyle::Percent));
|
|
515
|
+
assert_eq!(filtered.minimum_fraction_digits, Some(2.));
|
|
516
|
+
assert_eq!(filtered.maximum_fraction_digits, Some(2.));
|
|
517
|
+
}
|
|
325
518
|
}
|
|
@@ -87,3 +87,13 @@ pub struct GetClientOptions {
|
|
|
87
87
|
#[ts(optional)]
|
|
88
88
|
pub panel: Option<String>,
|
|
89
89
|
}
|
|
90
|
+
|
|
91
|
+
/// Options for the `saveWorkspace()` method.
|
|
92
|
+
#[derive(Deserialize, Default, TS)]
|
|
93
|
+
pub struct SaveWorkspaceOptions {
|
|
94
|
+
/// When `true`, the emitted `palette` is the full set the element
|
|
95
|
+
/// holds rather than only the values the panels reference.
|
|
96
|
+
#[serde(default)]
|
|
97
|
+
#[ts(optional)]
|
|
98
|
+
pub full_palette: Option<bool>,
|
|
99
|
+
}
|
|
@@ -24,7 +24,7 @@ use crate::renderer::ColumnConfigMap;
|
|
|
24
24
|
|
|
25
25
|
/// The state of an entire `custom_elements::PerspectiveViewerElement` component
|
|
26
26
|
/// and its `Plugin`: the element-level `settings` flag plus the per-panel
|
|
27
|
-
/// [`PanelViewerConfig`]. The split exists so the
|
|
27
|
+
/// [`PanelViewerConfig`]. The split exists so the workspace config format
|
|
28
28
|
/// can serialize panel entries *without* a `settings` key (it is element-level
|
|
29
29
|
/// state there, carried by the top-level `active` field instead), while the
|
|
30
30
|
/// single-panel format flattens back to the legacy shape.
|
|
@@ -39,7 +39,7 @@ pub struct ViewerConfig<V: TS = String> {
|
|
|
39
39
|
|
|
40
40
|
/// The per-panel state of a [`ViewerConfig`] — everything except the
|
|
41
41
|
/// element-level `settings` flag. This is the `panels` entry type of the
|
|
42
|
-
///
|
|
42
|
+
/// workspace config format.
|
|
43
43
|
#[derive(Debug, Default, Serialize, PartialEq, TS)]
|
|
44
44
|
pub struct PanelViewerConfig<V: TS = String> {
|
|
45
45
|
/// The `@perspective-dev/viewer` version that wrote this config,
|
|
@@ -315,15 +315,53 @@ impl From<ViewerConfigInitial> for ViewerConfigUpdate {
|
|
|
315
315
|
}
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
318
|
+
/// The rejection every panel-creating route without a `table` resolves to.
|
|
319
|
+
pub const CREATE_REQUIRES_TABLE: &str = "Cannot create a panel without a `table` — `load()` a \
|
|
320
|
+
`Client` and include `table` in the config, or use \
|
|
321
|
+
`addPanel()`";
|
|
322
|
+
|
|
323
|
+
fn down<T: Clone>(value: OptionalUpdate<T>) -> Option<T> {
|
|
324
|
+
match value {
|
|
325
|
+
OptionalUpdate::Update(value) => Some(value),
|
|
326
|
+
OptionalUpdate::Missing | OptionalUpdate::SetDefault => None,
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
impl TryFrom<ViewerConfigUpdate> for ViewerConfigInitial {
|
|
331
|
+
type Error = ApiError;
|
|
332
|
+
|
|
333
|
+
fn try_from(value: ViewerConfigUpdate) -> Result<Self, Self::Error> {
|
|
334
|
+
// Exhaustive (no `..`) on purpose — the same drift alarm as the
|
|
335
|
+
// `From<ViewerConfigInitial>` impl above. `settings` is element-level
|
|
336
|
+
// state, stripped by `restore()` before resolution; discarded here.
|
|
337
|
+
let ViewerConfigUpdate {
|
|
338
|
+
version,
|
|
339
|
+
plugin,
|
|
340
|
+
title,
|
|
341
|
+
table,
|
|
342
|
+
theme,
|
|
343
|
+
settings: _settings,
|
|
344
|
+
plugin_config,
|
|
345
|
+
columns_config,
|
|
346
|
+
view_config,
|
|
347
|
+
} = value;
|
|
348
|
+
|
|
349
|
+
let OptionalUpdate::Update(table) = table else {
|
|
350
|
+
return Err(ApiError::new(CREATE_REQUIRES_TABLE));
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
Ok(Self {
|
|
354
|
+
table,
|
|
355
|
+
version: down(version),
|
|
356
|
+
plugin: down(plugin),
|
|
357
|
+
title: down(title),
|
|
358
|
+
theme: down(theme),
|
|
359
|
+
plugin_config: down(plugin_config),
|
|
360
|
+
columns_config: down(columns_config),
|
|
361
|
+
view_config,
|
|
362
|
+
})
|
|
363
|
+
}
|
|
364
|
+
}
|
|
327
365
|
|
|
328
366
|
impl std::fmt::Display for ViewerConfigUpdate {
|
|
329
367
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
@@ -360,50 +398,6 @@ pub type VersionUpdate = OptionalUpdate<String>;
|
|
|
360
398
|
pub type ColumnConfigUpdate = OptionalUpdate<ColumnConfigMap>;
|
|
361
399
|
pub type PluginConfigUpdate = OptionalUpdate<serde_json::Map<String, Value>>;
|
|
362
400
|
|
|
363
|
-
#[cfg(test)]
|
|
364
|
-
mod tests {
|
|
365
|
-
use super::*;
|
|
366
|
-
|
|
367
|
-
#[test]
|
|
368
|
-
fn initial_requires_a_table() {
|
|
369
|
-
let json = serde_json::json!({ "group_by": ["State"] });
|
|
370
|
-
let err = serde_json::from_value::<ViewerConfigInitial>(json).unwrap_err();
|
|
371
|
-
assert!(format!("{err}").contains("table"));
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/// The creation → patch direction is the only one that exists (see the
|
|
375
|
-
/// note above the `From` impl); a `table` becomes a concrete update and
|
|
376
|
-
/// the element-level `settings` is not carried.
|
|
377
|
-
#[test]
|
|
378
|
-
fn initial_widens_to_an_update() {
|
|
379
|
-
let json = serde_json::json!({
|
|
380
|
-
"table": "superstore",
|
|
381
|
-
"plugin": "Datagrid",
|
|
382
|
-
"group_by": ["State"],
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
let initial: ViewerConfigInitial = serde_json::from_value(json).unwrap();
|
|
386
|
-
let update = ViewerConfigUpdate::from(initial);
|
|
387
|
-
assert!(matches!(&update.table, OptionalUpdate::Update(x) if x == "superstore"));
|
|
388
|
-
assert!(matches!(&update.settings, OptionalUpdate::Missing));
|
|
389
|
-
assert!(matches!(&update.plugin, OptionalUpdate::Update(x) if x == "Datagrid"));
|
|
390
|
-
assert_eq!(
|
|
391
|
-
update.view_config.group_by.as_deref(),
|
|
392
|
-
Some(&["State".to_owned()][..])
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/// A table-less patch is a legitimate creation input now that
|
|
397
|
-
/// `create_panel` takes an update — the deferred panel a `load()`
|
|
398
|
-
/// binds. Nothing in this module may reject it.
|
|
399
|
-
#[test]
|
|
400
|
-
fn a_table_less_update_is_representable() {
|
|
401
|
-
let json = serde_json::json!({ "group_by": ["State"] });
|
|
402
|
-
let update: ViewerConfigUpdate = serde_json::from_value(json).unwrap();
|
|
403
|
-
assert!(matches!(&update.table, OptionalUpdate::Missing));
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
|
|
407
401
|
/// Handles `{}` when included as a field with `#[serde(default)]`.
|
|
408
402
|
impl<T: Clone> Default for OptionalUpdate<T> {
|
|
409
403
|
fn default() -> Self {
|
|
@@ -14,9 +14,9 @@ use std::collections::BTreeMap;
|
|
|
14
14
|
|
|
15
15
|
use perspective_client::config::Filter;
|
|
16
16
|
|
|
17
|
-
use crate::config::{PanelViewerConfig, ViewerConfigInitial};
|
|
17
|
+
use crate::config::{CssKind, PanelViewerConfig, ViewerConfigInitial};
|
|
18
18
|
|
|
19
|
-
/// The
|
|
19
|
+
/// The workspace config format (`{version, active?, layout, panels}`) —
|
|
20
20
|
/// the multi-panel counterpart of the single-panel [`ViewerConfig`] — as
|
|
21
21
|
/// emitted by [`PerspectiveViewerElement::save`].
|
|
22
22
|
///
|
|
@@ -56,9 +56,17 @@ pub struct WorkspaceConfig {
|
|
|
56
56
|
#[ts(as = "Option<_>")]
|
|
57
57
|
#[ts(optional)]
|
|
58
58
|
pub masters: Vec<String>,
|
|
59
|
+
|
|
60
|
+
/// Named color-scale definitions shared by every panel: CSS custom
|
|
61
|
+
/// property name (`--psp-user--<kind>-<name>`) → canonical CSS
|
|
62
|
+
/// value.
|
|
63
|
+
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
|
64
|
+
#[ts(as = "Option<_>")]
|
|
65
|
+
#[ts(optional)]
|
|
66
|
+
pub palette: BTreeMap<String, String>,
|
|
59
67
|
}
|
|
60
68
|
|
|
61
|
-
/// The parse target of a
|
|
69
|
+
/// The parse target of a workspace config in
|
|
62
70
|
/// [`PerspectiveViewerElement::restoreWorkspace`]. Mirrors
|
|
63
71
|
/// [`WorkspaceConfig`], but `panels` entries are [`ViewerConfigInitial`]s —
|
|
64
72
|
/// every entry creates a NEW panel, so `table` is required by type (a
|
|
@@ -79,15 +87,134 @@ pub struct WorkspaceConfigUpdate {
|
|
|
79
87
|
/// The element-level global (master/detail cross-) filters to re-apply as
|
|
80
88
|
/// a transient overlay on every DETAIL panel. Restored as one
|
|
81
89
|
/// unattributed bucket: the next selection on any master replaces it.
|
|
90
|
+
/// `Option` so an explicit `undefined` property deserializes as
|
|
91
|
+
/// `None` like an absent key (also `masters` / `palette` below).
|
|
82
92
|
#[serde(default)]
|
|
83
93
|
#[ts(as = "Option<_>")]
|
|
84
94
|
#[ts(optional)]
|
|
85
|
-
pub global_filters: Vec<Filter
|
|
95
|
+
pub global_filters: Option<Vec<Filter>>,
|
|
86
96
|
|
|
87
97
|
/// The master (filter-source) panels, by saved `panels` key. An id not in
|
|
88
98
|
/// `panels` warns and is dropped.
|
|
89
99
|
#[serde(default)]
|
|
90
100
|
#[ts(as = "Option<_>")]
|
|
91
101
|
#[ts(optional)]
|
|
92
|
-
pub masters: Vec<String
|
|
102
|
+
pub masters: Option<Vec<String>>,
|
|
103
|
+
|
|
104
|
+
/// Named color-scale definitions to apply to the host (see
|
|
105
|
+
/// [`WorkspaceConfig::palette`]), replacing any previously restored
|
|
106
|
+
/// palette.
|
|
107
|
+
#[serde(default)]
|
|
108
|
+
#[ts(as = "Option<_>")]
|
|
109
|
+
#[ts(optional)]
|
|
110
|
+
pub palette: Option<BTreeMap<String, String>>,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/// Validate a restored palette map: each key's `--psp-user--<kind>-`
|
|
114
|
+
/// prefix selects the reader that canonicalizes its value.
|
|
115
|
+
pub fn validate_palette(
|
|
116
|
+
palette: BTreeMap<String, String>,
|
|
117
|
+
) -> Result<BTreeMap<String, String>, String> {
|
|
118
|
+
palette
|
|
119
|
+
.into_iter()
|
|
120
|
+
.map(|(name, value)| {
|
|
121
|
+
let kind = CssKind::of_var(&name).ok_or_else(|| {
|
|
122
|
+
format!(
|
|
123
|
+
"`palette` key `{name}` must start with `--psp-user--gradient-`, \
|
|
124
|
+
`--psp-user--palette-` or `--psp-user--color-`"
|
|
125
|
+
)
|
|
126
|
+
})?;
|
|
127
|
+
|
|
128
|
+
let canonical = kind
|
|
129
|
+
.canonicalize(&value)
|
|
130
|
+
.map_err(|error| format!("`palette[\"{name}\"]`: {error}"))?;
|
|
131
|
+
|
|
132
|
+
Ok((name, canonical))
|
|
133
|
+
})
|
|
134
|
+
.collect()
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
#[cfg(test)]
|
|
138
|
+
mod tests {
|
|
139
|
+
use serde_json::json;
|
|
140
|
+
|
|
141
|
+
use super::*;
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn palette_serializes_only_when_present() {
|
|
145
|
+
let config = WorkspaceConfig {
|
|
146
|
+
version: "x".to_owned(),
|
|
147
|
+
active: None,
|
|
148
|
+
layout: None,
|
|
149
|
+
panels: BTreeMap::new(),
|
|
150
|
+
global_filters: vec![],
|
|
151
|
+
masters: vec![],
|
|
152
|
+
palette: BTreeMap::new(),
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
assert_eq!(
|
|
156
|
+
serde_json::to_value(&config).unwrap(),
|
|
157
|
+
json!({ "version": "x", "layout": null, "panels": {} })
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
let mut palette = BTreeMap::new();
|
|
161
|
+
palette.insert("--psp-user--color-hot".to_owned(), "#ff0000".to_owned());
|
|
162
|
+
|
|
163
|
+
let config = WorkspaceConfig { palette, ..config };
|
|
164
|
+
assert_eq!(
|
|
165
|
+
serde_json::to_value(&config).unwrap(),
|
|
166
|
+
json!({
|
|
167
|
+
"version": "x",
|
|
168
|
+
"layout": null,
|
|
169
|
+
"panels": {},
|
|
170
|
+
"palette": { "--psp-user--color-hot": "#ff0000" },
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
#[test]
|
|
176
|
+
fn update_palette_defaults_empty_and_validates_by_prefix() {
|
|
177
|
+
let update: WorkspaceConfigUpdate =
|
|
178
|
+
serde_json::from_value(json!({ "panels": {} })).unwrap();
|
|
179
|
+
assert!(update.palette.is_none());
|
|
180
|
+
|
|
181
|
+
let update: WorkspaceConfigUpdate = serde_json::from_value(json!({
|
|
182
|
+
"panels": {},
|
|
183
|
+
"palette": {
|
|
184
|
+
"--psp-user--gradient-1": "linear-gradient(#000, #fff)",
|
|
185
|
+
"--psp-user--palette-warm": "linear-gradient(90deg, RGB(255,0,0), #ff0)",
|
|
186
|
+
"--psp-user--color-hot": "#F00",
|
|
187
|
+
},
|
|
188
|
+
}))
|
|
189
|
+
.unwrap();
|
|
190
|
+
|
|
191
|
+
let valid = validate_palette(update.palette.unwrap()).unwrap();
|
|
192
|
+
assert_eq!(
|
|
193
|
+
valid.get("--psp-user--gradient-1").unwrap(),
|
|
194
|
+
"linear-gradient(to right, #000000 0%, #ffffff 100%)"
|
|
195
|
+
);
|
|
196
|
+
assert_eq!(
|
|
197
|
+
valid.get("--psp-user--palette-warm").unwrap(),
|
|
198
|
+
"linear-gradient(to right, #ff0000, #ffff00)"
|
|
199
|
+
);
|
|
200
|
+
assert_eq!(valid.get("--psp-user--color-hot").unwrap(), "#ff0000");
|
|
201
|
+
|
|
202
|
+
let bad = |name: &str, value: &str| {
|
|
203
|
+
let mut map = BTreeMap::new();
|
|
204
|
+
map.insert(name.to_owned(), value.to_owned());
|
|
205
|
+
validate_palette(map).unwrap_err()
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
assert!(bad("--psp-user--other-1", "#ff0000").contains("--psp-user--other-1"));
|
|
209
|
+
assert!(bad("--psp-charts--gradient", "#ff0000").contains("must start with"));
|
|
210
|
+
assert!(
|
|
211
|
+
bad(
|
|
212
|
+
"--psp-user--palette-1",
|
|
213
|
+
"linear-gradient(#000 0%, #fff 100%)"
|
|
214
|
+
)
|
|
215
|
+
.contains("--psp-user--palette-1")
|
|
216
|
+
);
|
|
217
|
+
assert!(bad("--psp-user--gradient-1", "#ff0000").contains("linear-gradient"));
|
|
218
|
+
assert!(bad("--psp-user--color-1", "red").contains("red"));
|
|
219
|
+
}
|
|
93
220
|
}
|