@perspective-dev/viewer 5.3.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.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/wasm/perspective-viewer.d.ts +16 -16
- package/dist/wasm/perspective-viewer.js +107 -103
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +16 -16
- package/package.json +1 -1
- package/src/css/column-settings-panel.css +19 -0
- package/src/css/column-style.css +82 -0
- package/src/css/containers/tabs.css +13 -3
- package/src/css/dom/select.css +19 -8
- 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 +5 -2
- package/src/rust/components/column_settings_sidebar/style_tab.rs +296 -237
- 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/plugin_tab.rs +125 -87
- package/src/rust/components/settings_panel.rs +1 -0
- 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/config/column_config_schema.rs +358 -27
- package/src/rust/config/datetime_column_style.rs +4 -21
- package/src/rust/config/number_string_format.rs +211 -18
- package/src/rust/presentation.rs +17 -0
- package/src/rust/queries/plugin_column_styles.rs +2 -2
- package/src/rust/renderer/dispatch.rs +34 -8
- package/src/rust/renderer/plugin_config.rs +55 -8
- package/src/themes/blueprint.css +190 -0
- package/src/themes/botanical.css +4 -17
- package/src/themes/defaults.css +2 -120
- package/src/themes/dracula.css +2 -14
- package/src/themes/eggplant.css +189 -0
- package/src/themes/gruvbox-dark.css +3 -15
- package/src/themes/gruvbox.css +1 -1
- package/src/themes/icons.css +1 -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 +2 -14
- package/src/themes/nord.css +186 -0
- package/src/themes/phosphor.css +2 -14
- package/src/themes/pro-dark.css +2 -16
- package/src/themes/pro.css +118 -0
- package/src/themes/solarized-dark.css +2 -16
- package/src/themes/themes.css +10 -5
- package/src/themes/vaporwave.css +2 -14
- package/src/themes/velvet.css +192 -0
- package/src/ts/column-format.ts +52 -2
- package/src/svg/dropdown-selector-light.svg +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-87bd21b215d0631a}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-87bd21b215d0631a}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-87bd21b215d0631a}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-87bd21b215d0631a}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-87bd21b215d0631a}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-39c3c3b28e3cbd48 → perspective-viewer-87bd21b215d0631a}/inline5.js +0 -0
|
@@ -15,6 +15,8 @@ mod misc_section;
|
|
|
15
15
|
mod style_section;
|
|
16
16
|
mod types;
|
|
17
17
|
|
|
18
|
+
use std::rc::Rc;
|
|
19
|
+
|
|
18
20
|
use perspective_client::config::ColumnType;
|
|
19
21
|
pub use types::*;
|
|
20
22
|
use yew::{Callback, Component, Properties, html};
|
|
@@ -27,6 +29,10 @@ pub struct CustomNumberFormatProps {
|
|
|
27
29
|
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
28
30
|
pub view_type: ColumnType,
|
|
29
31
|
pub column_name: String,
|
|
32
|
+
|
|
33
|
+
/// Resolved default values for this column under the active plugin.
|
|
34
|
+
pub defaults: Rc<NumberFormatDefaults>,
|
|
35
|
+
|
|
30
36
|
#[prop_or_default]
|
|
31
37
|
pub keys: Vec<String>,
|
|
32
38
|
}
|
|
@@ -147,25 +153,39 @@ impl Component for CustomNumberFormat {
|
|
|
147
153
|
self.config.minimum_integer_digits = val;
|
|
148
154
|
},
|
|
149
155
|
CustomNumberFormatMsg::FracChange(val) => {
|
|
156
|
+
let defaults = &ctx.props().defaults;
|
|
150
157
|
self.config.rounding_increment = None;
|
|
151
158
|
self.config.maximum_fraction_digits = val.map(|(_, val)| {
|
|
152
|
-
let min = self
|
|
159
|
+
let min = self
|
|
160
|
+
.config
|
|
161
|
+
.minimum_fraction_digits
|
|
162
|
+
.unwrap_or(defaults.fraction.0);
|
|
153
163
|
val.max(min)
|
|
154
164
|
});
|
|
155
165
|
|
|
156
166
|
self.config.minimum_fraction_digits = val.map(|(val, _)| {
|
|
157
|
-
let max = self
|
|
167
|
+
let max = self
|
|
168
|
+
.config
|
|
169
|
+
.maximum_fraction_digits
|
|
170
|
+
.unwrap_or(defaults.fraction.1);
|
|
158
171
|
val.min(max)
|
|
159
172
|
});
|
|
160
173
|
},
|
|
161
174
|
CustomNumberFormatMsg::SigChange(val) => {
|
|
175
|
+
let defaults = &ctx.props().defaults;
|
|
162
176
|
self.config.maximum_significant_digits = val.map(|(_, val)| {
|
|
163
|
-
let min = self
|
|
177
|
+
let min = self
|
|
178
|
+
.config
|
|
179
|
+
.minimum_significant_digits
|
|
180
|
+
.unwrap_or(defaults.significant.0);
|
|
164
181
|
val.max(min)
|
|
165
182
|
});
|
|
166
183
|
|
|
167
184
|
self.config.minimum_significant_digits = val.map(|(val, _)| {
|
|
168
|
-
let max = self
|
|
185
|
+
let max = self
|
|
186
|
+
.config
|
|
187
|
+
.maximum_significant_digits
|
|
188
|
+
.unwrap_or(defaults.significant.1);
|
|
169
189
|
val.min(max)
|
|
170
190
|
});
|
|
171
191
|
},
|
|
@@ -191,8 +211,7 @@ impl Component for CustomNumberFormat {
|
|
|
191
211
|
},
|
|
192
212
|
};
|
|
193
213
|
|
|
194
|
-
let
|
|
195
|
-
let filtered_config = self.config.clone().filter_default(is_float);
|
|
214
|
+
let filtered_config = self.config.clone().filter_default(&ctx.props().defaults);
|
|
196
215
|
let mut value = serde_json::Map::new();
|
|
197
216
|
if filtered_config != CustomNumberFormatConfig::default() {
|
|
198
217
|
value.insert(
|
|
@@ -216,20 +235,24 @@ impl Component for CustomNumberFormat {
|
|
|
216
235
|
|
|
217
236
|
impl CustomNumberFormat {
|
|
218
237
|
fn initialize(ctx: &yew::prelude::Context<Self>) -> Self {
|
|
219
|
-
let
|
|
238
|
+
let defaults = &ctx.props().defaults;
|
|
239
|
+
let mut config = ctx.props().restored_config.clone();
|
|
240
|
+
if config._style.is_none() && defaults.style != NumberFormatStyle::Decimal {
|
|
241
|
+
config._style = Some(defaults.style.clone());
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if config._notation.is_none() && defaults.notation != Notation::Standard {
|
|
245
|
+
config._notation = Some(defaults.notation.clone());
|
|
246
|
+
}
|
|
247
|
+
|
|
220
248
|
Self {
|
|
221
249
|
style: config
|
|
222
250
|
._style
|
|
223
251
|
.as_ref()
|
|
224
|
-
.map(
|
|
225
|
-
NumberFormatStyle::Decimal => NumberStyle::Decimal,
|
|
226
|
-
NumberFormatStyle::Currency(_) => NumberStyle::Currency,
|
|
227
|
-
NumberFormatStyle::Percent => NumberStyle::Percent,
|
|
228
|
-
NumberFormatStyle::Unit(_) => NumberStyle::Unit,
|
|
229
|
-
})
|
|
252
|
+
.map(NumberStyle::from)
|
|
230
253
|
.unwrap_or_default(),
|
|
254
|
+
notation: config._notation.as_ref().map(NotationName::from),
|
|
231
255
|
config,
|
|
232
|
-
notation: None,
|
|
233
256
|
}
|
|
234
257
|
}
|
|
235
258
|
}
|
|
@@ -15,7 +15,10 @@ use std::collections::HashSet;
|
|
|
15
15
|
use serde::{Deserialize, Serialize};
|
|
16
16
|
use serde_json::Value;
|
|
17
17
|
|
|
18
|
-
use super::{
|
|
18
|
+
use super::{
|
|
19
|
+
CssKind, CustomNumberFormatConfig, DatetimeFormatType, KeyValueOpts,
|
|
20
|
+
NumberSeriesStyleDefaultConfig,
|
|
21
|
+
};
|
|
19
22
|
|
|
20
23
|
/// The full schema for one column at one point in time. Plugins may return
|
|
21
24
|
/// different schemas for the same column based on the column's current
|
|
@@ -41,6 +44,21 @@ impl ColumnConfigSchema {
|
|
|
41
44
|
}
|
|
42
45
|
out
|
|
43
46
|
}
|
|
47
|
+
|
|
48
|
+
pub fn leaf_fields(&self) -> Vec<&ControlSpec> {
|
|
49
|
+
fn collect<'a>(fields: &'a [ControlSpec], out: &mut Vec<&'a ControlSpec>) {
|
|
50
|
+
for spec in fields {
|
|
51
|
+
match spec {
|
|
52
|
+
ControlSpec::Group { fields, .. } => collect(fields, out),
|
|
53
|
+
leaf => out.push(leaf),
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
let mut out = vec![];
|
|
59
|
+
collect(&self.fields, &mut out);
|
|
60
|
+
out
|
|
61
|
+
}
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
/// Discriminated union of widget kinds the viewer can render. Composite
|
|
@@ -101,7 +119,12 @@ pub enum ControlSpec {
|
|
|
101
119
|
#[serde(default, skip_serializing_if = "std::ops::Not::not")]
|
|
102
120
|
discrete: bool,
|
|
103
121
|
},
|
|
104
|
-
DatetimeFormat
|
|
122
|
+
DatetimeFormat {
|
|
123
|
+
/// Plugin-declared default `date_format`, shown by the editor in
|
|
124
|
+
/// unedited fields and elided from serialized configs.
|
|
125
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
126
|
+
default: Option<DatetimeFormatType>,
|
|
127
|
+
},
|
|
105
128
|
StringFormat,
|
|
106
129
|
NumberSeriesStyle {
|
|
107
130
|
default: NumberSeriesStyleDefaultConfig,
|
|
@@ -109,8 +132,19 @@ pub enum ControlSpec {
|
|
|
109
132
|
Symbols {
|
|
110
133
|
default: KeyValueOpts,
|
|
111
134
|
},
|
|
112
|
-
NumberFormat
|
|
135
|
+
NumberFormat {
|
|
136
|
+
/// Plugin-declared default format, keyed like `number_format`
|
|
137
|
+
/// itself.
|
|
138
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
139
|
+
default: Option<CustomNumberFormatConfig>,
|
|
140
|
+
},
|
|
113
141
|
AggregateDepth,
|
|
142
|
+
|
|
143
|
+
Group {
|
|
144
|
+
key: String,
|
|
145
|
+
#[serde(default)]
|
|
146
|
+
fields: Vec<ControlSpec>,
|
|
147
|
+
},
|
|
114
148
|
}
|
|
115
149
|
|
|
116
150
|
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
@@ -165,37 +199,90 @@ pub fn discrete_pair(stops: Vec<GradientStopSpec>) -> Vec<GradientStopSpec> {
|
|
|
165
199
|
}
|
|
166
200
|
|
|
167
201
|
impl ColumnConfigSchema {
|
|
202
|
+
pub fn canonicalize(self) -> Self {
|
|
203
|
+
self.canonicalize_defaults().group_format_controls()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
pub fn group_format_controls(mut self) -> Self {
|
|
207
|
+
fn is_format(spec: &ControlSpec) -> bool {
|
|
208
|
+
matches!(
|
|
209
|
+
spec,
|
|
210
|
+
ControlSpec::NumberFormat { .. }
|
|
211
|
+
| ControlSpec::DatetimeFormat { .. }
|
|
212
|
+
| ControlSpec::StringFormat
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
fn walk(fields: &mut Vec<ControlSpec>) {
|
|
217
|
+
for spec in fields.iter_mut() {
|
|
218
|
+
if let ControlSpec::Group { key, fields } = spec
|
|
219
|
+
&& key != "format"
|
|
220
|
+
{
|
|
221
|
+
walk(fields);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let first = fields.iter().position(is_format);
|
|
226
|
+
if let Some(first) = first {
|
|
227
|
+
let mut formats = vec![];
|
|
228
|
+
let mut i = first;
|
|
229
|
+
while i < fields.len() {
|
|
230
|
+
if is_format(&fields[i]) {
|
|
231
|
+
formats.push(fields.remove(i));
|
|
232
|
+
} else {
|
|
233
|
+
i += 1;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fields.insert(first, ControlSpec::Group {
|
|
238
|
+
key: "format".to_owned(),
|
|
239
|
+
fields: formats,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
walk(&mut self.fields);
|
|
245
|
+
self
|
|
246
|
+
}
|
|
247
|
+
|
|
168
248
|
/// Canonicalize every CSS-valued default at schema ingest, dropping
|
|
169
249
|
/// (and logging) fields whose default fails its kind's reader.
|
|
170
250
|
pub fn canonicalize_defaults(mut self) -> Self {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
},
|
|
186
|
-
Err(error) => {
|
|
187
|
-
tracing::error!("Dropping `{key}` — invalid schema default: {error}");
|
|
188
|
-
false
|
|
189
|
-
},
|
|
190
|
-
}
|
|
191
|
-
});
|
|
251
|
+
fn canonicalize_specs(fields: &mut Vec<ControlSpec>) {
|
|
252
|
+
fields.retain_mut(|spec| {
|
|
253
|
+
let (kind, key, default) = match spec {
|
|
254
|
+
ControlSpec::Group { fields, .. } => {
|
|
255
|
+
canonicalize_specs(fields);
|
|
256
|
+
return !fields.is_empty();
|
|
257
|
+
},
|
|
258
|
+
ControlSpec::Color { key, default } => (CssKind::Color, key, default),
|
|
259
|
+
ControlSpec::Palette { key, default, .. } => (CssKind::Palette, key, default),
|
|
260
|
+
ControlSpec::GradientStops { key, default, .. } => {
|
|
261
|
+
(CssKind::Gradient, key, default)
|
|
262
|
+
},
|
|
263
|
+
_ => return true,
|
|
264
|
+
};
|
|
192
265
|
|
|
266
|
+
match kind.canonicalize(default) {
|
|
267
|
+
Ok(canonical) => {
|
|
268
|
+
*default = canonical;
|
|
269
|
+
true
|
|
270
|
+
},
|
|
271
|
+
Err(error) => {
|
|
272
|
+
tracing::error!("Dropping `{key}` — invalid schema default: {error}");
|
|
273
|
+
false
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
canonicalize_specs(&mut self.fields);
|
|
193
280
|
self
|
|
194
281
|
}
|
|
195
282
|
|
|
196
283
|
/// The CSS kind of the control owning `key`, if it is CSS-valued.
|
|
197
284
|
pub fn css_kind_of(&self, key: &str) -> Option<CssKind> {
|
|
198
|
-
self.
|
|
285
|
+
self.leaf_fields().into_iter().find_map(|spec| match spec {
|
|
199
286
|
ControlSpec::Color { key: k, .. } if k == key => Some(CssKind::Color),
|
|
200
287
|
ControlSpec::Palette { key: k, .. } if k == key => Some(CssKind::Palette),
|
|
201
288
|
ControlSpec::GradientStops { key: k, .. } if k == key => Some(CssKind::Gradient),
|
|
@@ -212,11 +299,11 @@ impl ControlSpec {
|
|
|
212
299
|
/// `columns_config` blob passed to `plugin.restore()`.
|
|
213
300
|
pub fn serialized_keys(&self) -> Vec<&str> {
|
|
214
301
|
match self {
|
|
215
|
-
ControlSpec::DatetimeFormat => vec!["date_format"],
|
|
302
|
+
ControlSpec::DatetimeFormat { .. } => vec!["date_format"],
|
|
216
303
|
ControlSpec::StringFormat => vec!["format"],
|
|
217
304
|
ControlSpec::NumberSeriesStyle { .. } => vec!["chart_type", "stack"],
|
|
218
305
|
ControlSpec::Symbols { .. } => vec!["symbols"],
|
|
219
|
-
ControlSpec::NumberFormat => vec!["number_format"],
|
|
306
|
+
ControlSpec::NumberFormat { .. } => vec!["number_format"],
|
|
220
307
|
ControlSpec::AggregateDepth => vec!["aggregate_depth"],
|
|
221
308
|
ControlSpec::Enum { key, .. }
|
|
222
309
|
| ControlSpec::Bool { key, .. }
|
|
@@ -225,6 +312,9 @@ impl ControlSpec {
|
|
|
225
312
|
| ControlSpec::Color { key, .. }
|
|
226
313
|
| ControlSpec::Palette { key, .. }
|
|
227
314
|
| ControlSpec::GradientStops { key, .. } => vec![key.as_str()],
|
|
315
|
+
ControlSpec::Group { fields, .. } => {
|
|
316
|
+
fields.iter().flat_map(|f| f.serialized_keys()).collect()
|
|
317
|
+
},
|
|
228
318
|
}
|
|
229
319
|
}
|
|
230
320
|
}
|
|
@@ -252,3 +342,244 @@ pub fn filter_to_schema(
|
|
|
252
342
|
.map(|(k, v)| (k.clone(), v.clone()))
|
|
253
343
|
.collect()
|
|
254
344
|
}
|
|
345
|
+
|
|
346
|
+
#[cfg(test)]
|
|
347
|
+
mod tests {
|
|
348
|
+
use serde_json::json;
|
|
349
|
+
|
|
350
|
+
use super::*;
|
|
351
|
+
|
|
352
|
+
fn color(key: &str, default: &str) -> ControlSpec {
|
|
353
|
+
ControlSpec::Color {
|
|
354
|
+
key: key.to_owned(),
|
|
355
|
+
default: default.to_owned(),
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
fn flag(key: &str) -> ControlSpec {
|
|
360
|
+
ControlSpec::Bool {
|
|
361
|
+
key: key.to_owned(),
|
|
362
|
+
default: false,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
fn group(key: &str, fields: Vec<ControlSpec>) -> ControlSpec {
|
|
367
|
+
ControlSpec::Group {
|
|
368
|
+
key: key.to_owned(),
|
|
369
|
+
fields,
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
#[test]
|
|
374
|
+
fn group_deserializes_recursively() {
|
|
375
|
+
let schema: ColumnConfigSchema = serde_json::from_value(json!({
|
|
376
|
+
"fields": [{
|
|
377
|
+
"kind": "Group",
|
|
378
|
+
"key": "legend",
|
|
379
|
+
"fields": [
|
|
380
|
+
{ "kind": "Bool", "key": "legend_on", "default": false },
|
|
381
|
+
{
|
|
382
|
+
"kind": "Group",
|
|
383
|
+
"key": "inner",
|
|
384
|
+
"fields": [{ "kind": "Color", "key": "color", "default": "#ff0000" }]
|
|
385
|
+
}
|
|
386
|
+
]
|
|
387
|
+
}]
|
|
388
|
+
}))
|
|
389
|
+
.unwrap();
|
|
390
|
+
|
|
391
|
+
let keys = schema.active_keys();
|
|
392
|
+
assert_eq!(
|
|
393
|
+
keys,
|
|
394
|
+
HashSet::from(["legend_on".to_owned(), "color".to_owned()])
|
|
395
|
+
);
|
|
396
|
+
|
|
397
|
+
let leaves = schema.leaf_fields();
|
|
398
|
+
assert_eq!(leaves.len(), 2);
|
|
399
|
+
assert!(
|
|
400
|
+
leaves
|
|
401
|
+
.iter()
|
|
402
|
+
.all(|s| !matches!(s, ControlSpec::Group { .. }))
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
#[test]
|
|
407
|
+
fn grouped_schema_is_equivalent_to_flat() {
|
|
408
|
+
let flat = ColumnConfigSchema {
|
|
409
|
+
fields: vec![flag("stack"), color("color", "#0366d6")],
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
let grouped = ColumnConfigSchema {
|
|
413
|
+
fields: vec![group("series", vec![
|
|
414
|
+
flag("stack"),
|
|
415
|
+
color("color", "#0366d6"),
|
|
416
|
+
])],
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
assert_eq!(flat.active_keys(), grouped.active_keys());
|
|
420
|
+
assert_eq!(flat.css_kind_of("color"), grouped.css_kind_of("color"));
|
|
421
|
+
assert_eq!(flat.css_kind_of("stack"), grouped.css_kind_of("stack"));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
#[test]
|
|
425
|
+
fn format_controls_group_and_merge() {
|
|
426
|
+
let schema = ColumnConfigSchema {
|
|
427
|
+
fields: vec![
|
|
428
|
+
ControlSpec::NumberFormat { default: None },
|
|
429
|
+
flag("flag"),
|
|
430
|
+
ControlSpec::StringFormat,
|
|
431
|
+
],
|
|
432
|
+
}
|
|
433
|
+
.group_format_controls();
|
|
434
|
+
|
|
435
|
+
assert_eq!(schema.fields.len(), 2);
|
|
436
|
+
let ControlSpec::Group { key, fields } = &schema.fields[0] else {
|
|
437
|
+
panic!("expected format group first");
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
assert_eq!(key, "format");
|
|
441
|
+
assert!(matches!(fields[0], ControlSpec::NumberFormat { .. }));
|
|
442
|
+
assert!(matches!(fields[1], ControlSpec::StringFormat));
|
|
443
|
+
assert!(matches!(&schema.fields[1], ControlSpec::Bool { .. }));
|
|
444
|
+
|
|
445
|
+
assert_eq!(
|
|
446
|
+
schema.active_keys(),
|
|
447
|
+
HashSet::from([
|
|
448
|
+
"number_format".to_owned(),
|
|
449
|
+
"format".to_owned(),
|
|
450
|
+
"flag".to_owned()
|
|
451
|
+
])
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
#[test]
|
|
456
|
+
fn format_grouping_recurses_but_never_double_wraps() {
|
|
457
|
+
let schema = ColumnConfigSchema {
|
|
458
|
+
fields: vec![
|
|
459
|
+
group("format", vec![ControlSpec::NumberFormat { default: None }]),
|
|
460
|
+
group("styling", vec![flag("x"), ControlSpec::DatetimeFormat {
|
|
461
|
+
default: None,
|
|
462
|
+
}]),
|
|
463
|
+
],
|
|
464
|
+
}
|
|
465
|
+
.group_format_controls();
|
|
466
|
+
|
|
467
|
+
let ControlSpec::Group { key, fields } = &schema.fields[0] else {
|
|
468
|
+
panic!("expected group");
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
assert_eq!(key, "format");
|
|
472
|
+
assert!(matches!(fields[0], ControlSpec::NumberFormat { .. }));
|
|
473
|
+
|
|
474
|
+
let ControlSpec::Group { fields, .. } = &schema.fields[1] else {
|
|
475
|
+
panic!("expected group");
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
assert!(matches!(
|
|
479
|
+
&fields[1],
|
|
480
|
+
ControlSpec::Group { key, fields }
|
|
481
|
+
if key == "format" && matches!(fields[0], ControlSpec::DatetimeFormat { .. })
|
|
482
|
+
));
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
#[test]
|
|
486
|
+
fn format_controls_deserialize_without_default_payload() {
|
|
487
|
+
let schema: ColumnConfigSchema = serde_json::from_value(json!({
|
|
488
|
+
"fields": [{ "kind": "NumberFormat" }, { "kind": "DatetimeFormat" }]
|
|
489
|
+
}))
|
|
490
|
+
.unwrap();
|
|
491
|
+
|
|
492
|
+
assert!(matches!(&schema.fields[0], ControlSpec::NumberFormat {
|
|
493
|
+
default: None
|
|
494
|
+
}));
|
|
495
|
+
assert!(matches!(&schema.fields[1], ControlSpec::DatetimeFormat {
|
|
496
|
+
default: None
|
|
497
|
+
}));
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
#[test]
|
|
501
|
+
fn number_format_default_payload_deserializes_flattened_families() {
|
|
502
|
+
let schema: ColumnConfigSchema = serde_json::from_value(json!({
|
|
503
|
+
"fields": [{
|
|
504
|
+
"kind": "NumberFormat",
|
|
505
|
+
"default": {
|
|
506
|
+
"notation": "compact",
|
|
507
|
+
"compactDisplay": "short",
|
|
508
|
+
"minimumFractionDigits": 0,
|
|
509
|
+
"maximumFractionDigits": 1
|
|
510
|
+
}
|
|
511
|
+
}]
|
|
512
|
+
}))
|
|
513
|
+
.unwrap();
|
|
514
|
+
|
|
515
|
+
let ControlSpec::NumberFormat {
|
|
516
|
+
default: Some(default),
|
|
517
|
+
} = &schema.fields[0]
|
|
518
|
+
else {
|
|
519
|
+
panic!("expected NumberFormat with default");
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
assert_eq!(
|
|
523
|
+
default._notation,
|
|
524
|
+
Some(crate::config::Notation::Compact(
|
|
525
|
+
crate::config::CompactDisplay::Short
|
|
526
|
+
))
|
|
527
|
+
);
|
|
528
|
+
assert_eq!(default._style, None);
|
|
529
|
+
assert_eq!(default.minimum_fraction_digits, Some(0.));
|
|
530
|
+
assert_eq!(default.maximum_fraction_digits, Some(1.));
|
|
531
|
+
assert_eq!(
|
|
532
|
+
schema.active_keys(),
|
|
533
|
+
HashSet::from(["number_format".to_owned()])
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
#[test]
|
|
538
|
+
fn datetime_format_default_payload_deserializes_simple_arm() {
|
|
539
|
+
let schema: ColumnConfigSchema = serde_json::from_value(json!({
|
|
540
|
+
"fields": [{
|
|
541
|
+
"kind": "DatetimeFormat",
|
|
542
|
+
"default": { "dateStyle": "medium", "timeStyle": "disabled" }
|
|
543
|
+
}]
|
|
544
|
+
}))
|
|
545
|
+
.unwrap();
|
|
546
|
+
|
|
547
|
+
let ControlSpec::DatetimeFormat {
|
|
548
|
+
default: Some(DatetimeFormatType::Simple(simple)),
|
|
549
|
+
} = &schema.fields[0]
|
|
550
|
+
else {
|
|
551
|
+
panic!("expected DatetimeFormat with Simple default");
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
assert_eq!(
|
|
555
|
+
simple.date_style,
|
|
556
|
+
crate::config::SimpleDatetimeFormat::Medium
|
|
557
|
+
);
|
|
558
|
+
assert_eq!(
|
|
559
|
+
simple.time_style,
|
|
560
|
+
crate::config::SimpleDatetimeFormat::Disabled
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
#[test]
|
|
565
|
+
fn canonicalize_defaults_recurses_and_drops_empty_groups() {
|
|
566
|
+
let schema = ColumnConfigSchema {
|
|
567
|
+
fields: vec![
|
|
568
|
+
group("ok", vec![color("good", "RGB(255,0,0)"), flag("flag")]),
|
|
569
|
+
group("doomed", vec![color("bad", "not-a-color")]),
|
|
570
|
+
],
|
|
571
|
+
}
|
|
572
|
+
.canonicalize_defaults();
|
|
573
|
+
|
|
574
|
+
assert_eq!(schema.fields.len(), 1);
|
|
575
|
+
let ControlSpec::Group { key, fields } = &schema.fields[0] else {
|
|
576
|
+
panic!("expected group");
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
assert_eq!(key, "ok");
|
|
580
|
+
assert!(matches!(
|
|
581
|
+
&fields[0],
|
|
582
|
+
ControlSpec::Color { default, .. } if default == "#ff0000"
|
|
583
|
+
));
|
|
584
|
+
}
|
|
585
|
+
}
|
|
@@ -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
|
-
}
|