@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
|
@@ -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
|
}
|
package/src/rust/presentation.rs
CHANGED
|
@@ -104,6 +104,8 @@ pub struct PresentationHandle {
|
|
|
104
104
|
open_column_settings: RefCell<OpenColumnSettings>,
|
|
105
105
|
is_workspace: RefCell<Option<bool>>,
|
|
106
106
|
|
|
107
|
+
collapsed_control_groups: RefCell<HashSet<String>>,
|
|
108
|
+
|
|
107
109
|
/// Drag/drop in-progress state. Empty (`NoDrag`) when no user drag is
|
|
108
110
|
/// active. Mutated by `notify_drag_*` / `notify_drop`; read by component
|
|
109
111
|
/// CSS-class derivations (`is_dragover`, `get_drag_column`).
|
|
@@ -191,6 +193,7 @@ impl Presentation {
|
|
|
191
193
|
on_is_workspace_changed: Default::default(),
|
|
192
194
|
is_settings_open: Default::default(),
|
|
193
195
|
open_column_settings: Default::default(),
|
|
196
|
+
collapsed_control_groups: Default::default(),
|
|
194
197
|
theme_config_updated: PubSub::default(),
|
|
195
198
|
on_eject: PubSub::default(),
|
|
196
199
|
statusbar_pointer_event: PubSub::default(),
|
|
@@ -357,6 +360,20 @@ impl Presentation {
|
|
|
357
360
|
self.open_column_settings.borrow().deref().clone()
|
|
358
361
|
}
|
|
359
362
|
|
|
363
|
+
pub fn is_control_group_collapsed(&self, key: &str) -> bool {
|
|
364
|
+
self.collapsed_control_groups.borrow().contains(key)
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
pub fn set_control_group_collapsed(&self, key: &str, collapsed: bool) {
|
|
368
|
+
if collapsed {
|
|
369
|
+
self.collapsed_control_groups
|
|
370
|
+
.borrow_mut()
|
|
371
|
+
.insert(key.to_owned());
|
|
372
|
+
} else {
|
|
373
|
+
self.collapsed_control_groups.borrow_mut().remove(key);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
360
377
|
async fn init(self) -> ApiResult<()> {
|
|
361
378
|
self.set_theme_attribute(self.get_selected_theme_name().await.as_deref())
|
|
362
379
|
}
|
|
@@ -42,7 +42,7 @@ pub fn get_plugin_config_schema(
|
|
|
42
42
|
wasm_bindgen::JsValue::from_serde_ext(view_config).unwrap_or(wasm_bindgen::JsValue::NULL);
|
|
43
43
|
let raw = plugin._plugin_config_schema(&view_config_js)?;
|
|
44
44
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
45
|
-
.map(|schema| schema.
|
|
45
|
+
.map(|schema| schema.canonicalize())
|
|
46
46
|
.map_err(|e| e.into())
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -100,6 +100,6 @@ pub fn get_column_config_schema(
|
|
|
100
100
|
)?;
|
|
101
101
|
|
|
102
102
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
103
|
-
.map(|schema| schema.
|
|
103
|
+
.map(|schema| schema.canonicalize())
|
|
104
104
|
.map_err(|e| e.into())
|
|
105
105
|
}
|
|
@@ -165,10 +165,8 @@ impl Renderer {
|
|
|
165
165
|
width: f64,
|
|
166
166
|
height: f64,
|
|
167
167
|
) -> ApiResult<Option<js_sys::Function>> {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
self.0.presize_pending.set(self.0.presize_pending.get() - 1);
|
|
171
|
-
result
|
|
168
|
+
let _pending = PresizePendingGuard::increment(self);
|
|
169
|
+
self.presize_inner(None, width, height).await
|
|
172
170
|
}
|
|
173
171
|
|
|
174
172
|
/// Pre-size AND pre-position the plugin to its target layout bo.
|
|
@@ -179,10 +177,8 @@ impl Renderer {
|
|
|
179
177
|
width: f64,
|
|
180
178
|
height: f64,
|
|
181
179
|
) -> ApiResult<Option<js_sys::Function>> {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
self.0.presize_pending.set(self.0.presize_pending.get() - 1);
|
|
185
|
-
result
|
|
180
|
+
let _pending = PresizePendingGuard::increment(self);
|
|
181
|
+
self.presize_inner(Some((dx, dy)), width, height).await
|
|
186
182
|
}
|
|
187
183
|
|
|
188
184
|
async fn presize_inner(
|
|
@@ -374,7 +370,14 @@ impl Renderer {
|
|
|
374
370
|
.debounce_with(|guard| async move {
|
|
375
371
|
set_timeout(timer.get_throttle()).await?;
|
|
376
372
|
if self.0.presize_pending.get() > 0 {
|
|
373
|
+
// Record the drop like the hidden-panel path
|
|
374
|
+
// (`wire_panel_render_sub`) does — a silently
|
|
375
|
+
// discarded update otherwise leaves the plugin's
|
|
376
|
+
// retained frame stale until the NEXT data tick,
|
|
377
|
+
// or forever if none comes. `draw_view` /
|
|
378
|
+
// `activation_render` consume the marker.
|
|
377
379
|
tracing::debug!("Update skipped, presize pending");
|
|
380
|
+
self.set_data_stale();
|
|
378
381
|
return Ok(());
|
|
379
382
|
}
|
|
380
383
|
|
|
@@ -473,3 +476,26 @@ impl Renderer {
|
|
|
473
476
|
self.draw_lock.clone()
|
|
474
477
|
}
|
|
475
478
|
}
|
|
479
|
+
|
|
480
|
+
/// RAII increment of [`RendererData::presize_pending`], the counter that
|
|
481
|
+
/// gates [`Renderer::update_lazy`] dispatches. Decrementing on `Drop`
|
|
482
|
+
/// (not on the happy path) makes the release unconditional — a presize
|
|
483
|
+
/// future cancelled mid-await can never strand the counter above zero,
|
|
484
|
+
/// which would silently discard every subsequent debounced update for
|
|
485
|
+
/// the panel's lifetime.
|
|
486
|
+
struct PresizePendingGuard(Renderer);
|
|
487
|
+
|
|
488
|
+
impl PresizePendingGuard {
|
|
489
|
+
fn increment(renderer: &Renderer) -> Self {
|
|
490
|
+
let cell = &renderer.0.presize_pending;
|
|
491
|
+
cell.set(cell.get() + 1);
|
|
492
|
+
Self(renderer.clone())
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
impl Drop for PresizePendingGuard {
|
|
497
|
+
fn drop(&mut self) {
|
|
498
|
+
let cell = &self.0.0.presize_pending;
|
|
499
|
+
cell.set(cell.get() - 1);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
@@ -133,7 +133,7 @@ impl Renderer {
|
|
|
133
133
|
else {
|
|
134
134
|
continue;
|
|
135
135
|
};
|
|
136
|
-
let needs_warm = schema.
|
|
136
|
+
let needs_warm = schema.leaf_fields().into_iter().any(|f| {
|
|
137
137
|
matches!(
|
|
138
138
|
f,
|
|
139
139
|
ControlSpec::Number {
|
|
@@ -164,7 +164,7 @@ impl Renderer {
|
|
|
164
164
|
continue;
|
|
165
165
|
};
|
|
166
166
|
|
|
167
|
-
for field in
|
|
167
|
+
for field in schema.leaf_fields() {
|
|
168
168
|
let ControlSpec::Number {
|
|
169
169
|
key,
|
|
170
170
|
default,
|
|
@@ -380,7 +380,7 @@ impl Renderer {
|
|
|
380
380
|
let view_config_js = JsValue::from_serde_ext(view_config).unwrap_or(JsValue::NULL);
|
|
381
381
|
let raw = plugin._plugin_config_schema(&view_config_js)?;
|
|
382
382
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
383
|
-
.map(|schema| schema.
|
|
383
|
+
.map(|schema| schema.canonicalize())
|
|
384
384
|
.map_err(|e| e.into())
|
|
385
385
|
}
|
|
386
386
|
|
|
@@ -429,7 +429,7 @@ impl Renderer {
|
|
|
429
429
|
)?;
|
|
430
430
|
|
|
431
431
|
serde_wasm_bindgen::from_value::<ColumnConfigSchema>(raw)
|
|
432
|
-
.map(|schema| schema.
|
|
432
|
+
.map(|schema| schema.canonicalize())
|
|
433
433
|
.map_err(|e| e.into())
|
|
434
434
|
}
|
|
435
435
|
|
|
@@ -470,9 +470,9 @@ impl Renderer {
|
|
|
470
470
|
))));
|
|
471
471
|
}
|
|
472
472
|
|
|
473
|
+
let leaves = s.leaf_fields();
|
|
473
474
|
map.retain(|key, value| {
|
|
474
|
-
let is_default =
|
|
475
|
-
.fields
|
|
475
|
+
let is_default = leaves
|
|
476
476
|
.iter()
|
|
477
477
|
.any(|spec| matches_declared_default(spec, key, value));
|
|
478
478
|
if is_default {
|
|
@@ -567,9 +567,9 @@ fn strip_default_values(
|
|
|
567
567
|
schema: &ColumnConfigSchema,
|
|
568
568
|
map: &mut serde_json::Map<String, serde_json::Value>,
|
|
569
569
|
) {
|
|
570
|
+
let leaves = schema.leaf_fields();
|
|
570
571
|
map.retain(|key, value| {
|
|
571
|
-
!
|
|
572
|
-
.fields
|
|
572
|
+
!leaves
|
|
573
573
|
.iter()
|
|
574
574
|
.any(|spec| matches_declared_default(spec, key, value))
|
|
575
575
|
});
|
|
@@ -702,6 +702,53 @@ mod tests {
|
|
|
702
702
|
));
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
+
#[test]
|
|
706
|
+
fn strip_default_values_sees_through_groups() {
|
|
707
|
+
let leaves = vec![
|
|
708
|
+
ControlSpec::Bool {
|
|
709
|
+
key: "flag".to_owned(),
|
|
710
|
+
default: false,
|
|
711
|
+
},
|
|
712
|
+
ControlSpec::Number {
|
|
713
|
+
key: "size".to_owned(),
|
|
714
|
+
default: 3.0,
|
|
715
|
+
include: None,
|
|
716
|
+
min: None,
|
|
717
|
+
max: None,
|
|
718
|
+
step: None,
|
|
719
|
+
},
|
|
720
|
+
];
|
|
721
|
+
|
|
722
|
+
let flat = ColumnConfigSchema {
|
|
723
|
+
fields: leaves.clone(),
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
let grouped = ColumnConfigSchema {
|
|
727
|
+
fields: vec![ControlSpec::Group {
|
|
728
|
+
key: "section".to_owned(),
|
|
729
|
+
fields: leaves,
|
|
730
|
+
}],
|
|
731
|
+
};
|
|
732
|
+
|
|
733
|
+
let src = json!({ "flag": false, "size": 4.0, "foreign": 1 })
|
|
734
|
+
.as_object()
|
|
735
|
+
.unwrap()
|
|
736
|
+
.clone();
|
|
737
|
+
|
|
738
|
+
let mut a = src.clone();
|
|
739
|
+
let mut b = src;
|
|
740
|
+
strip_default_values(&flat, &mut a);
|
|
741
|
+
strip_default_values(&grouped, &mut b);
|
|
742
|
+
assert_eq!(a, b);
|
|
743
|
+
assert_eq!(
|
|
744
|
+
a,
|
|
745
|
+
json!({ "size": 4.0, "foreign": 1 })
|
|
746
|
+
.as_object()
|
|
747
|
+
.unwrap()
|
|
748
|
+
.clone()
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
|
|
705
752
|
#[test]
|
|
706
753
|
fn normalize_css_values_canonicalizes_and_reports() {
|
|
707
754
|
let schema = ColumnConfigSchema {
|