@perspective-dev/viewer 5.4.0 → 5.5.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 +4 -4
- package/dist/cdn/perspective-viewer.js.map +2 -2
- package/dist/css/blueprint.css +1 -1
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/eggplant.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/ar.css +1 -1
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/ledger.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/nord.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/css/velvet.css +1 -1
- package/dist/docs/perspective-docs.json +1 -1
- package/dist/esm/perspective-viewer.d.ts +2 -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 +4 -4
- package/dist/esm/perspective-viewer.js.map +2 -2
- package/dist/esm/plugin.d.ts +9 -0
- package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -1
- package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
- package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
- package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
- package/dist/wasm/perspective-viewer.d.ts +102 -47
- package/dist/wasm/perspective-viewer.js +233 -142
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -18
- package/package.json +7 -7
- package/src/css/column-selector.css +11 -3
- package/src/css/column-style.css +162 -0
- package/src/css/config-selector.css +1 -1
- package/src/css/form/debug.css +9 -0
- package/src/css/status-bar.css +11 -0
- package/src/css/ui/containers/tabs.css +2 -1
- package/src/rust/agent/tools.rs +80 -7
- package/src/rust/components/column_selector/active_column.rs +9 -0
- package/src/rust/components/column_selector.rs +11 -0
- package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
- package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +68 -9
- package/src/rust/components/debug_panel.rs +107 -50
- package/src/rust/components/export_dropdown.rs +6 -0
- package/src/rust/components/main_panel/msg.rs +4 -0
- package/src/rust/components/main_panel.rs +14 -0
- package/src/rust/components/mod.rs +0 -1
- package/src/rust/components/plugin_tab.rs +57 -3
- package/src/rust/components/settings_panel.rs +6 -0
- package/src/rust/components/status_bar.rs +12 -3
- package/src/rust/components/status_indicator.rs +4 -0
- package/src/rust/components/viewer/filters.rs +2 -2
- package/src/rust/components/viewer/msg.rs +4 -0
- package/src/rust/components/viewer/panels.rs +6 -3
- package/src/rust/components/viewer/render.rs +1 -0
- package/src/rust/components/viewer/wiring.rs +14 -2
- package/src/rust/components/viewer.rs +2 -2
- package/src/rust/config/column_config_schema.rs +312 -8
- package/src/rust/config/datetime_column_style.rs +0 -11
- package/src/rust/config/export_method.rs +24 -0
- package/src/rust/config/mod.rs +0 -2
- package/src/rust/config/options.rs +50 -0
- package/src/rust/config/workspace_config.rs +69 -24
- package/src/rust/custom_elements/viewer.rs +214 -113
- package/src/rust/js/plugin.rs +3 -1
- package/src/rust/lib.rs +7 -3
- package/src/rust/queries/plugin_column_styles.rs +6 -5
- package/src/rust/renderer/plugin_config.rs +200 -41
- package/src/rust/renderer/plugin_store.rs +2 -1
- package/src/rust/renderer/props.rs +5 -0
- package/src/rust/renderer.rs +9 -0
- package/src/rust/session/metadata.rs +5 -10
- package/src/rust/session/props.rs +4 -0
- package/src/rust/session.rs +273 -195
- package/src/rust/tasks/copy_export.rs +67 -0
- package/src/rust/tasks/create_panel.rs +16 -13
- package/src/rust/tasks/pipeline.rs +26 -8
- package/src/rust/tasks/restore_panel.rs +129 -30
- package/src/rust/tasks/send_column_config.rs +3 -0
- package/src/rust/tasks/sync_update_panels.rs +148 -68
- package/src/rust/tasks/table_lifecycle.rs +7 -1
- package/src/rust/ui/containers/tab_list.rs +1 -1
- package/src/rust/ui/form/intl_label.rs +4 -23
- package/src/rust/ui/portal.rs +1 -0
- package/src/rust/utils/css_vars.rs +13 -0
- package/src/rust/{config/datetime_column_style/color_mode.rs → utils/in_flight.rs} +47 -35
- package/src/rust/utils/local_fonts.rs +395 -0
- package/src/rust/utils/mod.rs +4 -0
- package/src/rust/utils/pending_effects.rs +6 -30
- package/src/rust/workspace.rs +12 -0
- package/src/svg/status_pending.svg +3 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/ar.css +20 -7
- package/src/themes/intl/de.css +20 -7
- package/src/themes/intl/es.css +20 -7
- package/src/themes/intl/fr.css +20 -7
- package/src/themes/intl/ja.css +20 -7
- package/src/themes/intl/pt.css +20 -7
- package/src/themes/intl/zh.css +20 -7
- package/src/themes/intl.css +20 -7
- package/src/themes/monokai.css +1 -1
- package/src/themes/vaporwave.css +1 -1
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/plugin.ts +9 -0
- package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
- package/src/ts/ts-rs/ExportMethod.ts +1 -1
- package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
- package/src/ts/ts-rs/GetViewOptions.ts +7 -0
- package/src/ts/ts-rs/RestoreOptions.ts +7 -1
- package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/src/rust/components/string_column_style.rs +0 -120
- package/src/rust/config/string_column_style.rs +0 -135
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-adc6f3f79141112b → perspective-viewer-6813b568219c6763}/inline5.js +0 -0
package/src/rust/lib.rs
CHANGED
|
@@ -93,25 +93,29 @@ export type * from "../../src/ts/ts-rs/WorkspaceConfigUpdate.d.ts";
|
|
|
93
93
|
export type * from "../../src/ts/ts-rs/ExportMethod.d.ts";
|
|
94
94
|
export type * from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
|
95
95
|
export type * from "../../src/ts/ts-rs/RestoreOptions.d.ts";
|
|
96
|
+
export type * from "../../src/ts/ts-rs/RestoreWorkspaceOptions.d.ts";
|
|
97
|
+
export type * from "../../src/ts/ts-rs/AddPanelOptions.d.ts";
|
|
96
98
|
export type * from "../../src/ts/ts-rs/SaveWorkspaceOptions.d.ts";
|
|
97
99
|
export type * from "../../src/ts/ts-rs/ClientOptions.d.ts";
|
|
98
100
|
export type * from "../../src/ts/ts-rs/ExportOptions.d.ts";
|
|
99
101
|
export type * from "../../src/ts/ts-rs/GetTableOptions.d.ts";
|
|
100
102
|
export type * from "../../src/ts/ts-rs/GetClientOptions.d.ts";
|
|
103
|
+
export type * from "../../src/ts/ts-rs/GetViewOptions.d.ts";
|
|
104
|
+
export type * from "../../src/ts/ts-rs/GetViewMode.d.ts";
|
|
101
105
|
export type * from "../../src/ts/ts-rs/CustomNumberFormatConfig.d.ts";
|
|
102
106
|
export type * from "../../src/ts/ts-rs/NumberFormatStyle.d.ts";
|
|
103
107
|
export type * from "../../src/ts/ts-rs/Notation.d.ts";
|
|
104
108
|
export type * from "../../src/ts/ts-rs/DatetimeFormatType.d.ts";
|
|
105
|
-
export type * from "../../src/ts/ts-rs/StringColorMode.d.ts";
|
|
106
|
-
export type * from "../../src/ts/ts-rs/DatetimeColorMode.d.ts";
|
|
107
|
-
export type * from "../../src/ts/ts-rs/FormatMode.d.ts";
|
|
108
109
|
|
|
109
110
|
import type {GetTableOptions} from "../../src/ts/ts-rs/GetTableOptions.d.ts";
|
|
110
111
|
import type {PanelOptions} from "../../src/ts/ts-rs/PanelOptions.d.ts";
|
|
111
112
|
import type {RestoreOptions} from "../../src/ts/ts-rs/RestoreOptions.d.ts";
|
|
113
|
+
import type {RestoreWorkspaceOptions} from "../../src/ts/ts-rs/RestoreWorkspaceOptions.d.ts";
|
|
114
|
+
import type {AddPanelOptions} from "../../src/ts/ts-rs/AddPanelOptions.d.ts";
|
|
112
115
|
import type {SaveWorkspaceOptions} from "../../src/ts/ts-rs/SaveWorkspaceOptions.d.ts";
|
|
113
116
|
import type {ExportOptions} from "../../src/ts/ts-rs/ExportOptions.d.ts";
|
|
114
117
|
import type {GetClientOptions} from "../../src/ts/ts-rs/GetClientOptions.d.ts";
|
|
118
|
+
import type {GetViewOptions} from "../../src/ts/ts-rs/GetViewOptions.d.ts";
|
|
115
119
|
import type {ClientOptions} from "../../src/ts/ts-rs/ClientOptions.d.ts";
|
|
116
120
|
import type {ViewerConfig} from "../../src/ts/ts-rs/ViewerConfig.d.ts";
|
|
117
121
|
import type {ViewerConfigUpdate} from "../../src/ts/ts-rs/ViewerConfigUpdate.d.ts";
|
|
@@ -29,18 +29,19 @@ struct ColumnStats {
|
|
|
29
29
|
abs_max: Option<f64>,
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
/// Queries the active plugin for its plugin-scoped
|
|
33
|
-
///
|
|
34
|
-
/// drops the column-specific args — the schema describes the shape
|
|
35
|
-
/// of the plugin's own config bucket on the renderer.
|
|
32
|
+
/// Queries the active plugin for its plugin-scoped [`ColumnConfigSchema`] as
|
|
33
|
+
/// it applies to `current_value`.
|
|
36
34
|
pub fn get_plugin_config_schema(
|
|
37
35
|
renderer: &Renderer,
|
|
38
36
|
view_config: &ViewConfig,
|
|
37
|
+
current_value: Option<&serde_json::Map<String, serde_json::Value>>,
|
|
39
38
|
) -> ApiResult<ColumnConfigSchema> {
|
|
40
39
|
let plugin = renderer.ensure_plugin_selected()?;
|
|
41
40
|
let view_config_js =
|
|
42
41
|
wasm_bindgen::JsValue::from_serde_ext(view_config).unwrap_or(wasm_bindgen::JsValue::NULL);
|
|
43
|
-
let
|
|
42
|
+
let current_js = wasm_bindgen::JsValue::from_serde_ext(¤t_value)
|
|
43
|
+
.unwrap_or(wasm_bindgen::JsValue::NULL);
|
|
44
|
+
let raw = plugin._plugin_config_schema(&view_config_js, ¤t_js)?;
|
|
44
45
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
45
46
|
.map(|schema| schema.canonicalize())
|
|
46
47
|
.map_err(|e| e.into())
|
|
@@ -247,10 +247,16 @@ impl Renderer {
|
|
|
247
247
|
{
|
|
248
248
|
let active = schema.active_keys();
|
|
249
249
|
cfg.retain(|k, _| active.contains(k));
|
|
250
|
-
let errors =
|
|
250
|
+
let errors = normalize_values(&schema, &mut cfg);
|
|
251
251
|
if let Some((key, error)) = errors.first() {
|
|
252
|
+
let ty = session
|
|
253
|
+
.metadata()
|
|
254
|
+
.get_column_view_type(&col)
|
|
255
|
+
.map(|t| format!(" for a {t} column"))
|
|
256
|
+
.unwrap_or_default();
|
|
257
|
+
|
|
252
258
|
return Err(ApiError::from(JsValue::from_str(&format!(
|
|
253
|
-
"Invalid `columns_config[\"{col}\"].{key}
|
|
259
|
+
"Invalid `columns_config[\"{col}\"].{key}`{ty}: {error}"
|
|
254
260
|
))));
|
|
255
261
|
}
|
|
256
262
|
|
|
@@ -305,7 +311,7 @@ impl Renderer {
|
|
|
305
311
|
&column_name,
|
|
306
312
|
current_value.as_ref(),
|
|
307
313
|
) {
|
|
308
|
-
for (key, error) in
|
|
314
|
+
for (key, error) in normalize_values(&schema, &mut update.value) {
|
|
309
315
|
tracing::error!("Dropping `{column_name}`.`{key}`: {error}");
|
|
310
316
|
}
|
|
311
317
|
|
|
@@ -329,16 +335,25 @@ impl Renderer {
|
|
|
329
335
|
entry.clone()
|
|
330
336
|
};
|
|
331
337
|
|
|
332
|
-
|
|
338
|
+
// The schema AFTER the update decides which keys survive and, for a
|
|
339
|
+
// key whose control kind follows another key (`fg_color` reads as a
|
|
340
|
+
// color, gradient or palette depending on `fg_mode`), whether the
|
|
341
|
+
// value still parses under its new kind. A value that no longer
|
|
342
|
+
// does is dropped here, so `save()` never emits a config that
|
|
343
|
+
// `restore()` would reject.
|
|
344
|
+
let after = self
|
|
333
345
|
.query_column_config_schema(view_config, session, &column_name, Some(&next))
|
|
334
|
-
.ok()
|
|
335
|
-
.map(|schema| schema.active_keys());
|
|
346
|
+
.ok();
|
|
336
347
|
|
|
337
348
|
let mut st = self.borrow_mut();
|
|
338
349
|
let bucket = st.plugin_states.entry(n).or_default();
|
|
339
350
|
if let Some(entry) = bucket.columns.get_mut(&column_name) {
|
|
340
|
-
if let Some(
|
|
351
|
+
if let Some(schema) = &after {
|
|
352
|
+
let active = schema.active_keys();
|
|
341
353
|
entry.retain(|k, _| active.contains(k));
|
|
354
|
+
for (key, error) in normalize_values(schema, entry) {
|
|
355
|
+
tracing::warn!("Dropping `{column_name}`.`{key}` after a mode change: {error}");
|
|
356
|
+
}
|
|
342
357
|
}
|
|
343
358
|
|
|
344
359
|
if entry.is_empty() {
|
|
@@ -371,15 +386,18 @@ impl Renderer {
|
|
|
371
386
|
}
|
|
372
387
|
}
|
|
373
388
|
|
|
374
|
-
/// Synchronously query the active plugin's
|
|
375
|
-
///
|
|
389
|
+
/// Synchronously query the active plugin's [`ColumnConfigSchema`] as it
|
|
390
|
+
/// applies to `current_value`, the plugin-config state the schema should
|
|
391
|
+
/// describe.
|
|
376
392
|
fn query_plugin_config_schema(
|
|
377
393
|
&self,
|
|
378
394
|
view_config: &ViewConfig,
|
|
395
|
+
current_value: Option<&serde_json::Map<String, serde_json::Value>>,
|
|
379
396
|
) -> ApiResult<ColumnConfigSchema> {
|
|
380
397
|
let plugin = self.ensure_plugin_selected()?;
|
|
381
398
|
let view_config_js = JsValue::from_serde_ext(view_config).unwrap_or(JsValue::NULL);
|
|
382
|
-
let
|
|
399
|
+
let current_js = JsValue::from_serde_ext(¤t_value).unwrap_or(JsValue::NULL);
|
|
400
|
+
let raw = plugin._plugin_config_schema(&view_config_js, ¤t_js)?;
|
|
383
401
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
384
402
|
.map(|schema| schema.canonicalize())
|
|
385
403
|
.map_err(|e| e.into())
|
|
@@ -434,12 +452,9 @@ impl Renderer {
|
|
|
434
452
|
.map_err(|e| e.into())
|
|
435
453
|
}
|
|
436
454
|
|
|
437
|
-
///
|
|
438
|
-
///
|
|
439
|
-
///
|
|
440
|
-
/// is cleared rather than the default being stored literally.
|
|
441
|
-
/// Keys absent from the incoming map are left alone (merge
|
|
442
|
-
/// semantics for the non-default subset).
|
|
455
|
+
/// Merge an update into the active plugin's plugin-level config map,
|
|
456
|
+
/// clearing entries equal to the schema default and dropping keys the
|
|
457
|
+
/// resulting schema no longer advertises.
|
|
443
458
|
pub fn update_plugin_config(
|
|
444
459
|
&self,
|
|
445
460
|
view_config: &ViewConfig,
|
|
@@ -449,28 +464,42 @@ impl Renderer {
|
|
|
449
464
|
return Ok(false);
|
|
450
465
|
};
|
|
451
466
|
|
|
452
|
-
let schema = self.query_plugin_config_schema(view_config).ok();
|
|
453
|
-
let mut st = self.borrow_mut();
|
|
454
|
-
let bucket = st.plugin_states.entry(n).or_default();
|
|
455
467
|
match update {
|
|
456
468
|
OptionalUpdate::SetDefault => {
|
|
469
|
+
let mut st = self.borrow_mut();
|
|
470
|
+
let bucket = st.plugin_states.entry(n).or_default();
|
|
457
471
|
let changed = !bucket.plugin.is_empty();
|
|
458
472
|
bucket.plugin.clear();
|
|
459
473
|
Ok(changed)
|
|
460
474
|
},
|
|
461
475
|
OptionalUpdate::Missing => Ok(false),
|
|
462
476
|
OptionalUpdate::Update(mut map) => {
|
|
463
|
-
let mut
|
|
477
|
+
let mut merged = self.get_plugin_config();
|
|
478
|
+
for (k, v) in &map {
|
|
479
|
+
merged.insert(k.clone(), v.clone());
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
let schema = self
|
|
483
|
+
.query_plugin_config_schema(view_config, Some(&merged))
|
|
484
|
+
.ok();
|
|
485
|
+
|
|
486
|
+
let mut active = None;
|
|
464
487
|
if let Some(s) = &schema {
|
|
465
|
-
let
|
|
466
|
-
map.retain(|k, _|
|
|
467
|
-
|
|
488
|
+
let keys = s.active_keys();
|
|
489
|
+
map.retain(|k, _| keys.contains(k));
|
|
490
|
+
active = Some(keys);
|
|
491
|
+
let errors = normalize_values(s, &mut map);
|
|
468
492
|
if let Some((key, error)) = errors.first() {
|
|
469
493
|
return Err(ApiError::from(JsValue::from_str(&format!(
|
|
470
494
|
"Invalid `plugin_config.{key}`: {error}"
|
|
471
495
|
))));
|
|
472
496
|
}
|
|
497
|
+
}
|
|
473
498
|
|
|
499
|
+
let mut st = self.borrow_mut();
|
|
500
|
+
let bucket = st.plugin_states.entry(n).or_default();
|
|
501
|
+
let mut changed = false;
|
|
502
|
+
if let Some(s) = &schema {
|
|
474
503
|
let leaves = s.leaf_fields();
|
|
475
504
|
map.retain(|key, value| {
|
|
476
505
|
let is_default = leaves
|
|
@@ -494,12 +523,19 @@ impl Renderer {
|
|
|
494
523
|
}
|
|
495
524
|
}
|
|
496
525
|
|
|
526
|
+
if let Some(active) = active {
|
|
527
|
+
let before = bucket.plugin.len();
|
|
528
|
+
bucket.plugin.retain(|k, _| active.contains(k));
|
|
529
|
+
changed |= bucket.plugin.len() != before;
|
|
530
|
+
}
|
|
531
|
+
|
|
497
532
|
Ok(changed)
|
|
498
533
|
},
|
|
499
534
|
}
|
|
500
535
|
}
|
|
501
536
|
|
|
502
|
-
/// Apply a single schema-field update from the plugin-settings UI
|
|
537
|
+
/// Apply a single schema-field update from the plugin-settings UI,
|
|
538
|
+
/// dropping keys the resulting schema no longer advertises.
|
|
503
539
|
pub fn update_plugin_config_field(
|
|
504
540
|
&self,
|
|
505
541
|
view_config: &ViewConfig,
|
|
@@ -509,12 +545,28 @@ impl Renderer {
|
|
|
509
545
|
return false;
|
|
510
546
|
};
|
|
511
547
|
|
|
512
|
-
|
|
513
|
-
|
|
548
|
+
let mut next = self.get_plugin_config();
|
|
549
|
+
for k in &update.keys {
|
|
550
|
+
match update.value.get(k) {
|
|
551
|
+
Some(v) => {
|
|
552
|
+
next.insert(k.clone(), v.clone());
|
|
553
|
+
},
|
|
554
|
+
None => {
|
|
555
|
+
next.remove(k);
|
|
556
|
+
},
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
let schema = self
|
|
561
|
+
.query_plugin_config_schema(view_config, Some(&next))
|
|
562
|
+
.ok();
|
|
563
|
+
|
|
564
|
+
if let Some(schema) = &schema {
|
|
565
|
+
for (key, error) in normalize_values(schema, &mut update.value) {
|
|
514
566
|
tracing::error!("Dropping `plugin_config`.`{key}`: {error}");
|
|
515
567
|
}
|
|
516
568
|
|
|
517
|
-
strip_default_values(
|
|
569
|
+
strip_default_values(schema, &mut update.value);
|
|
518
570
|
}
|
|
519
571
|
|
|
520
572
|
let mut st = self.borrow_mut();
|
|
@@ -532,28 +584,48 @@ impl Renderer {
|
|
|
532
584
|
}
|
|
533
585
|
}
|
|
534
586
|
|
|
587
|
+
if let Some(schema) = &schema {
|
|
588
|
+
let active = schema.active_keys();
|
|
589
|
+
let before = bucket.plugin.len();
|
|
590
|
+
bucket.plugin.retain(|k, _| active.contains(k));
|
|
591
|
+
changed |= bucket.plugin.len() != before;
|
|
592
|
+
}
|
|
593
|
+
|
|
535
594
|
changed
|
|
536
595
|
}
|
|
537
596
|
}
|
|
538
597
|
|
|
539
|
-
|
|
598
|
+
/// Canonicalize every CSS-valued key of `map` and check every `Enum`-valued
|
|
599
|
+
/// key against its variants, removing and reporting the keys that fail.
|
|
600
|
+
/// The schema is the one queried for `map`'s own values, so an `Enum`'s
|
|
601
|
+
/// variants are exactly the values the column's type accepts.
|
|
602
|
+
fn normalize_values(
|
|
540
603
|
schema: &ColumnConfigSchema,
|
|
541
604
|
map: &mut serde_json::Map<String, serde_json::Value>,
|
|
542
605
|
) -> Vec<(String, String)> {
|
|
543
606
|
let mut errors = vec![];
|
|
544
607
|
for (key, value) in map.iter_mut() {
|
|
545
|
-
let Some(kind) = schema.css_kind_of(key)
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
Some(src) => kind.canonicalize(src),
|
|
551
|
-
None => Err(format!("expected a CSS string, got `{value}`")),
|
|
552
|
-
};
|
|
608
|
+
if let Some(kind) = schema.css_kind_of(key) {
|
|
609
|
+
let canonical = match value.as_str() {
|
|
610
|
+
Some(src) => kind.canonicalize(src),
|
|
611
|
+
None => Err(format!("expected a CSS string, got `{value}`")),
|
|
612
|
+
};
|
|
553
613
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
614
|
+
match canonical {
|
|
615
|
+
Ok(canonical) => *value = Value::String(canonical),
|
|
616
|
+
Err(error) => errors.push((key.clone(), error)),
|
|
617
|
+
}
|
|
618
|
+
} else if let Some(variants) = schema.enum_variants_of(key) {
|
|
619
|
+
let accepted = variants.iter().any(|v| value.as_str() == Some(&v.value));
|
|
620
|
+
if !accepted {
|
|
621
|
+
let expected = variants
|
|
622
|
+
.iter()
|
|
623
|
+
.map(|v| format!("`{}`", v.value))
|
|
624
|
+
.collect::<Vec<_>>()
|
|
625
|
+
.join(", ");
|
|
626
|
+
|
|
627
|
+
errors.push((key.clone(), format!("{value} is not one of {expected}")));
|
|
628
|
+
}
|
|
557
629
|
}
|
|
558
630
|
}
|
|
559
631
|
|
|
@@ -580,6 +652,21 @@ fn matches_declared_default(spec: &ControlSpec, key: &str, value: &Value) -> boo
|
|
|
580
652
|
match spec {
|
|
581
653
|
ControlSpec::Enum {
|
|
582
654
|
key: k, default, ..
|
|
655
|
+
}
|
|
656
|
+
| ControlSpec::Font {
|
|
657
|
+
key: k, default, ..
|
|
658
|
+
} if k == key => value.as_str() == Some(default.as_str()),
|
|
659
|
+
ControlSpec::Font {
|
|
660
|
+
size: Some(size), ..
|
|
661
|
+
} if size.key == key => value.as_f64() == Some(size.default),
|
|
662
|
+
ControlSpec::Font { bold, italic, .. } => [bold, italic]
|
|
663
|
+
.into_iter()
|
|
664
|
+
.flatten()
|
|
665
|
+
.any(|toggle| toggle.key == key && value.as_bool() == Some(toggle.default)),
|
|
666
|
+
ControlSpec::Alignment {
|
|
667
|
+
key: k,
|
|
668
|
+
default: Some(default),
|
|
669
|
+
..
|
|
583
670
|
} if k == key => value.as_str() == Some(default.as_str()),
|
|
584
671
|
ControlSpec::Bool {
|
|
585
672
|
key: k, default, ..
|
|
@@ -703,6 +790,35 @@ mod tests {
|
|
|
703
790
|
));
|
|
704
791
|
}
|
|
705
792
|
|
|
793
|
+
#[test]
|
|
794
|
+
fn alignment_strips_only_a_declared_default() {
|
|
795
|
+
let schema = ColumnConfigSchema {
|
|
796
|
+
fields: vec![
|
|
797
|
+
ControlSpec::Alignment {
|
|
798
|
+
key: "align".to_owned(),
|
|
799
|
+
default: None,
|
|
800
|
+
corners: false,
|
|
801
|
+
},
|
|
802
|
+
ControlSpec::Alignment {
|
|
803
|
+
key: "legend_anchor".to_owned(),
|
|
804
|
+
default: Some(Alignment::TopRight),
|
|
805
|
+
corners: true,
|
|
806
|
+
},
|
|
807
|
+
],
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
let mut map = json!({ "align": "center", "legend_anchor": "top-right" })
|
|
811
|
+
.as_object()
|
|
812
|
+
.unwrap()
|
|
813
|
+
.clone();
|
|
814
|
+
|
|
815
|
+
strip_default_values(&schema, &mut map);
|
|
816
|
+
assert_eq!(
|
|
817
|
+
map,
|
|
818
|
+
json!({ "align": "center" }).as_object().unwrap().clone()
|
|
819
|
+
);
|
|
820
|
+
}
|
|
821
|
+
|
|
706
822
|
#[test]
|
|
707
823
|
fn strip_default_values_sees_through_groups() {
|
|
708
824
|
let leaves = vec![
|
|
@@ -751,7 +867,50 @@ mod tests {
|
|
|
751
867
|
}
|
|
752
868
|
|
|
753
869
|
#[test]
|
|
754
|
-
fn
|
|
870
|
+
fn normalize_values_rejects_enum_values_outside_the_variants() {
|
|
871
|
+
let schema = ColumnConfigSchema {
|
|
872
|
+
fields: vec![ControlSpec::Group {
|
|
873
|
+
key: "color".to_owned(),
|
|
874
|
+
fields: vec![ControlSpec::Enum {
|
|
875
|
+
key: "fg_mode".to_owned(),
|
|
876
|
+
default: "color".to_owned(),
|
|
877
|
+
variants: vec![
|
|
878
|
+
EnumVariant {
|
|
879
|
+
value: "disabled".to_owned(),
|
|
880
|
+
label: None,
|
|
881
|
+
},
|
|
882
|
+
EnumVariant {
|
|
883
|
+
value: "color".to_owned(),
|
|
884
|
+
label: None,
|
|
885
|
+
},
|
|
886
|
+
],
|
|
887
|
+
}],
|
|
888
|
+
}],
|
|
889
|
+
};
|
|
890
|
+
|
|
891
|
+
let mut map = json!({ "fg_mode": "color", "other": "series" })
|
|
892
|
+
.as_object()
|
|
893
|
+
.unwrap()
|
|
894
|
+
.clone();
|
|
895
|
+
|
|
896
|
+
assert!(normalize_values(&schema, &mut map).is_empty());
|
|
897
|
+
assert_eq!(map.len(), 2);
|
|
898
|
+
|
|
899
|
+
let mut map = json!({ "fg_mode": "series" }).as_object().unwrap().clone();
|
|
900
|
+
let errors = normalize_values(&schema, &mut map);
|
|
901
|
+
assert_eq!(errors.len(), 1);
|
|
902
|
+
assert_eq!(errors[0].0, "fg_mode");
|
|
903
|
+
assert_eq!(errors[0].1, "\"series\" is not one of `disabled`, `color`");
|
|
904
|
+
assert!(map.is_empty());
|
|
905
|
+
|
|
906
|
+
let mut map = json!({ "fg_mode": 3 }).as_object().unwrap().clone();
|
|
907
|
+
let errors = normalize_values(&schema, &mut map);
|
|
908
|
+
assert_eq!(errors.len(), 1);
|
|
909
|
+
assert!(map.is_empty());
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
#[test]
|
|
913
|
+
fn normalize_values_canonicalizes_and_reports() {
|
|
755
914
|
let schema = ColumnConfigSchema {
|
|
756
915
|
fields: vec![
|
|
757
916
|
ControlSpec::Color {
|
|
@@ -786,7 +945,7 @@ mod tests {
|
|
|
786
945
|
.unwrap()
|
|
787
946
|
.clone();
|
|
788
947
|
|
|
789
|
-
let errors =
|
|
948
|
+
let errors = normalize_values(&schema, &mut map);
|
|
790
949
|
assert_eq!(errors.len(), 1);
|
|
791
950
|
assert_eq!(errors[0].0, "gradient");
|
|
792
951
|
assert_eq!(
|
|
@@ -26,7 +26,8 @@ pub struct PluginStore {
|
|
|
26
26
|
|
|
27
27
|
impl PluginStore {
|
|
28
28
|
fn init_lazy(&mut self) {
|
|
29
|
-
|
|
29
|
+
let plugins = PLUGIN_REGISTRY.create_plugins();
|
|
30
|
+
self.plugins = Some(plugins);
|
|
30
31
|
self.plugin_configs = Some(PLUGIN_REGISTRY.plugin_configs());
|
|
31
32
|
self.plugin_records = Some(PLUGIN_REGISTRY.available_plugin_names_by_category());
|
|
32
33
|
}
|
|
@@ -14,6 +14,7 @@ use std::rc::Rc;
|
|
|
14
14
|
|
|
15
15
|
use crate::config::PluginStaticConfig;
|
|
16
16
|
use crate::renderer::limits::RenderLimits;
|
|
17
|
+
use crate::renderer::plugin_config::ColumnConfigMap;
|
|
17
18
|
use crate::utils::PtrEqRc;
|
|
18
19
|
|
|
19
20
|
/// Value-semantic snapshot of the renderer state read by components.
|
|
@@ -50,6 +51,10 @@ pub struct RendererProps {
|
|
|
50
51
|
/// the interior-mutable handle, no manual PubSub subscription, no
|
|
51
52
|
/// race window between `plugins_idx` swap and the schema query.
|
|
52
53
|
pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
|
|
54
|
+
|
|
55
|
+
/// Snapshot of the active plugin's `columns_config` bucket, re-read by
|
|
56
|
+
/// `Renderer::to_props` on `columns_config_changed` and `plugin_changed`.
|
|
57
|
+
pub columns_config: PtrEqRc<ColumnConfigMap>,
|
|
53
58
|
}
|
|
54
59
|
|
|
55
60
|
impl RendererProps {
|
package/src/rust/renderer.rs
CHANGED
|
@@ -112,6 +112,11 @@ pub struct RendererData {
|
|
|
112
112
|
/// plugin's bucket.
|
|
113
113
|
pub plugin_config_changed: PubSub<serde_json::Map<String, serde_json::Value>>,
|
|
114
114
|
|
|
115
|
+
/// Fires after the active plugin's per-column bucket changes by any path
|
|
116
|
+
/// (edit, restore or reset), unlike `column_style_changed` which covers
|
|
117
|
+
/// edits only.
|
|
118
|
+
pub columns_config_changed: PubSub<ColumnConfigMap>,
|
|
119
|
+
|
|
115
120
|
/// `true` while the active plugin's "rendering N of M" warning is
|
|
116
121
|
/// dismissable.
|
|
117
122
|
pub render_warning: Cell<bool>,
|
|
@@ -263,6 +268,7 @@ impl Renderer {
|
|
|
263
268
|
selection_changed: Default::default(),
|
|
264
269
|
column_style_changed: Default::default(),
|
|
265
270
|
plugin_config_changed: Default::default(),
|
|
271
|
+
columns_config_changed: Default::default(),
|
|
266
272
|
render_warning: Cell::new(true),
|
|
267
273
|
presize_pending: Cell::new(0),
|
|
268
274
|
presized_box: Cell::new(None),
|
|
@@ -695,6 +701,7 @@ impl Renderer {
|
|
|
695
701
|
.into();
|
|
696
702
|
|
|
697
703
|
let plugin_config = PtrEqRc::new(self.get_plugin_config());
|
|
704
|
+
let columns_config = PtrEqRc::new(self.all_columns_configs());
|
|
698
705
|
RendererProps {
|
|
699
706
|
plugin_name,
|
|
700
707
|
config,
|
|
@@ -702,6 +709,7 @@ impl Renderer {
|
|
|
702
709
|
available_plugins,
|
|
703
710
|
is_chart,
|
|
704
711
|
plugin_config,
|
|
712
|
+
columns_config,
|
|
705
713
|
}
|
|
706
714
|
} else {
|
|
707
715
|
RendererProps {
|
|
@@ -711,6 +719,7 @@ impl Renderer {
|
|
|
711
719
|
available_plugins: PtrEqRc::new(vec![]),
|
|
712
720
|
is_chart: false,
|
|
713
721
|
plugin_config: PtrEqRc::default(),
|
|
722
|
+
columns_config: PtrEqRc::default(),
|
|
714
723
|
}
|
|
715
724
|
}
|
|
716
725
|
}
|
|
@@ -92,7 +92,7 @@ impl SessionMetadata {
|
|
|
92
92
|
&mut self,
|
|
93
93
|
view_schema: &HashMap<String, ColumnType>,
|
|
94
94
|
) -> ApiResult<()> {
|
|
95
|
-
self.as_mut().
|
|
95
|
+
self.as_mut().ok_or("No `Table` set")?.view_schema = Some(view_schema.clone());
|
|
96
96
|
Ok(())
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -115,7 +115,7 @@ impl SessionMetadata {
|
|
|
115
115
|
Some((name.clone(), dtype))
|
|
116
116
|
})
|
|
117
117
|
.collect();
|
|
118
|
-
self.as_mut().
|
|
118
|
+
self.as_mut().ok_or("No `Table` set")?.window_schema = window_schema;
|
|
119
119
|
Ok(())
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -151,16 +151,11 @@ impl SessionMetadata {
|
|
|
151
151
|
)));
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
-
let
|
|
155
|
-
|
|
156
|
-
.unwrap()
|
|
157
|
-
.expr_meta
|
|
158
|
-
.take()
|
|
159
|
-
.map(|x| x.edited)
|
|
160
|
-
.unwrap_or_default();
|
|
154
|
+
let state = self.as_mut().ok_or("No `Table` set")?;
|
|
155
|
+
let mut edited = state.expr_meta.take().map(|x| x.edited).unwrap_or_default();
|
|
161
156
|
|
|
162
157
|
edited.retain(|k, _| valid_recs.expression_alias.contains_key(k));
|
|
163
|
-
|
|
158
|
+
state.expr_meta = Some(SessionViewExpressionMetadata {
|
|
164
159
|
expressions: valid_recs.clone(),
|
|
165
160
|
edited,
|
|
166
161
|
});
|
|
@@ -22,11 +22,15 @@ use crate::session::replace_expression_update::ViewConfigExt as ReplaceExprExt;
|
|
|
22
22
|
use crate::session::{ColumnStats, TableErrorState};
|
|
23
23
|
use crate::utils::*;
|
|
24
24
|
|
|
25
|
+
/// The panel's table binding as the status indicator and chrome see it:
|
|
26
|
+
/// bound, a `load()` in flight, or a named table awaiting its host (see
|
|
27
|
+
/// `Session::pending_table`).
|
|
25
28
|
#[derive(Clone, Debug, PartialEq, Default)]
|
|
26
29
|
pub enum TableLoadState {
|
|
27
30
|
#[default]
|
|
28
31
|
Loaded,
|
|
29
32
|
Loading,
|
|
33
|
+
Pending,
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
/// Value-semantic snapshot of the session state read by the root component.
|