@perspective-dev/viewer 4.4.1 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/dist/cdn/perspective-viewer.js +1 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -1
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/esm/bootstrap.d.ts +2 -1
  24. package/dist/esm/column-format.d.ts +51 -0
  25. package/dist/esm/extensions.d.ts +2 -0
  26. package/dist/esm/perspective-viewer.d.ts +3 -1
  27. package/dist/esm/perspective-viewer.inline.js +1 -2
  28. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  29. package/dist/esm/perspective-viewer.js +1 -2
  30. package/dist/esm/perspective-viewer.js.map +4 -4
  31. package/dist/esm/perspective-viewer.worker.d.ts +2 -0
  32. package/dist/esm/plugin.d.ts +16 -72
  33. package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
  34. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
  35. package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -3
  36. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
  37. package/dist/wasm/perspective-viewer.d.ts +77 -18
  38. package/dist/wasm/perspective-viewer.js +293 -144
  39. package/dist/wasm/perspective-viewer.wasm +0 -0
  40. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
  41. package/package.json +24 -2
  42. package/src/css/column-selector.css +3 -2
  43. package/src/css/column-settings-panel.css +35 -6
  44. package/src/css/column-style.css +27 -2
  45. package/src/css/containers/scroll-panel.css +2 -1
  46. package/src/css/containers/tabs.css +8 -52
  47. package/src/css/dom/checkbox.css +0 -4
  48. package/src/css/form/code-editor.css +1 -0
  49. package/src/css/form/debug.css +3 -10
  50. package/src/css/plugin-selector.css +33 -0
  51. package/src/css/plugin-settings-panel.css +99 -0
  52. package/src/css/viewer.css +65 -3
  53. package/src/rust/components/column_dropdown.rs +3 -1
  54. package/src/rust/components/column_selector/active_column.rs +13 -19
  55. package/src/rust/components/column_selector/config_selector.rs +20 -20
  56. package/src/rust/components/column_selector/filter_column.rs +14 -14
  57. package/src/rust/components/column_selector/inactive_column.rs +9 -15
  58. package/src/rust/components/column_selector/pivot_column.rs +7 -7
  59. package/src/rust/components/column_selector/sort_column.rs +7 -7
  60. package/src/rust/components/column_selector.rs +55 -37
  61. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
  62. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +394 -0
  63. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
  64. package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
  65. package/src/rust/components/column_settings_sidebar.rs +43 -49
  66. package/src/rust/components/containers/dragdrop_list.rs +5 -5
  67. package/src/rust/components/containers/mod.rs +0 -1
  68. package/src/rust/components/containers/scroll_panel.rs +21 -7
  69. package/src/rust/components/containers/sidebar.rs +8 -6
  70. package/src/rust/components/containers/split_panel.rs +3 -3
  71. package/src/rust/components/containers/tab_list.rs +3 -9
  72. package/src/rust/components/copy_dropdown.rs +2 -3
  73. package/src/rust/components/datetime_column_style.rs +19 -81
  74. package/src/rust/components/editable_header.rs +2 -3
  75. package/src/rust/components/export_dropdown.rs +2 -3
  76. package/src/rust/components/expression_editor.rs +29 -17
  77. package/src/rust/components/filter_dropdown.rs +2 -1
  78. package/src/rust/components/form/color_range_selector.rs +14 -7
  79. package/src/rust/components/form/debug.rs +47 -37
  80. package/src/rust/components/main_panel.rs +24 -65
  81. package/src/rust/components/mod.rs +2 -1
  82. package/src/rust/components/number_series_style.rs +161 -0
  83. package/src/rust/components/plugin_tab.rs +221 -0
  84. package/src/rust/components/settings_panel.rs +181 -59
  85. package/src/rust/components/status_bar.rs +140 -173
  86. package/src/rust/components/status_indicator.rs +15 -22
  87. package/src/rust/components/string_column_style.rs +20 -82
  88. package/src/rust/components/style_controls/number_string_format.rs +14 -30
  89. package/src/rust/components/viewer.rs +92 -131
  90. package/src/rust/config/column_config_schema.rs +195 -0
  91. package/src/rust/config/columns_config.rs +4 -97
  92. package/src/rust/config/datetime_column_style.rs +0 -5
  93. package/src/rust/config/mod.rs +8 -2
  94. package/src/rust/config/number_series_style.rs +79 -0
  95. package/src/rust/config/plugin_static_config.rs +144 -0
  96. package/src/rust/config/string_column_style.rs +0 -5
  97. package/src/rust/config/viewer_config.rs +5 -6
  98. package/src/rust/custom_elements/copy_dropdown.rs +30 -18
  99. package/src/rust/custom_elements/debug_plugin.rs +1 -3
  100. package/src/rust/custom_elements/export_dropdown.rs +26 -18
  101. package/src/rust/custom_elements/viewer.rs +62 -73
  102. package/src/rust/custom_events.rs +181 -224
  103. package/src/rust/js/plugin.rs +45 -117
  104. package/src/rust/lib.rs +34 -5
  105. package/src/rust/presentation/drag_helpers.rs +206 -0
  106. package/src/rust/presentation/props.rs +8 -0
  107. package/src/rust/presentation.rs +256 -41
  108. package/src/rust/{tasks → queries}/column_locator.rs +17 -73
  109. package/src/rust/queries/column_values.rs +59 -0
  110. package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
  111. package/src/rust/queries/exports.rs +96 -0
  112. package/src/rust/queries/fetch_column_stats.rs +94 -0
  113. package/src/rust/queries/get_viewer_config.rs +54 -0
  114. package/src/rust/queries/mod.rs +44 -0
  115. package/src/rust/queries/plugin_column_styles.rs +101 -0
  116. package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
  117. package/src/rust/renderer/activate.rs +1 -0
  118. package/src/rust/renderer/limits.rs +9 -4
  119. package/src/rust/renderer/plugin_store.rs +12 -0
  120. package/src/rust/renderer/props.rs +28 -3
  121. package/src/rust/renderer/registry.rs +40 -15
  122. package/src/rust/renderer.rs +640 -51
  123. package/src/rust/session/column_defaults_update.rs +20 -28
  124. package/src/rust/session/drag_drop_update.rs +10 -10
  125. package/src/rust/session/metadata.rs +31 -16
  126. package/src/rust/session/props.rs +15 -6
  127. package/src/rust/session/view_subscription.rs +10 -0
  128. package/src/rust/session.rs +109 -147
  129. package/src/rust/tasks/copy_export.rs +178 -158
  130. package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
  131. package/src/rust/tasks/edit_expression.rs +68 -88
  132. package/src/rust/tasks/eject.rs +25 -22
  133. package/src/rust/tasks/intersection_observer.rs +8 -21
  134. package/src/rust/tasks/mod.rs +19 -21
  135. package/src/rust/tasks/reset_all.rs +78 -0
  136. package/src/rust/tasks/resize_observer.rs +11 -33
  137. package/src/rust/tasks/restore_and_render.rs +117 -90
  138. package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +38 -35
  139. package/src/rust/tasks/send_plugin_config.rs +32 -33
  140. package/src/rust/tasks/update_and_render.rs +66 -47
  141. package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
  142. package/src/rust/tasks/validate_expression.rs +61 -0
  143. package/src/rust/utils/browser/selection.rs +4 -4
  144. package/src/rust/utils/mod.rs +0 -63
  145. package/src/svg/mega-menu-icons-density.svg +23 -0
  146. package/src/svg/mega-menu-icons-map-density.svg +24 -0
  147. package/src/svg/mega-menu-icons-map-line.svg +19 -0
  148. package/src/themes/botanical.css +27 -53
  149. package/src/themes/defaults.css +24 -36
  150. package/src/themes/dracula.css +36 -54
  151. package/src/themes/gruvbox-dark.css +39 -59
  152. package/src/themes/gruvbox.css +16 -28
  153. package/src/themes/icons.css +3 -0
  154. package/src/themes/intl/de.css +42 -6
  155. package/src/themes/intl/es.css +42 -6
  156. package/src/themes/intl/fr.css +42 -6
  157. package/src/themes/intl/ja.css +42 -6
  158. package/src/themes/intl/pt.css +42 -6
  159. package/src/themes/intl/zh.css +42 -6
  160. package/src/themes/intl.css +37 -4
  161. package/src/themes/monokai.css +45 -61
  162. package/src/themes/phosphor.css +20 -29
  163. package/src/themes/pro-dark.css +25 -34
  164. package/src/themes/solarized-dark.css +21 -36
  165. package/src/themes/solarized.css +13 -23
  166. package/src/themes/vaporwave.css +40 -74
  167. package/src/ts/bootstrap.ts +14 -3
  168. package/src/ts/column-format.ts +162 -0
  169. package/src/ts/extensions.ts +4 -0
  170. package/src/ts/perspective-viewer.ts +9 -1
  171. package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
  172. package/src/ts/plugin.ts +25 -101
  173. package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
  174. package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
  175. package/src/ts/ts-rs/ViewerConfig.ts +1 -2
  176. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
  177. package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
  178. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
  179. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
  180. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
  181. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  182. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
  183. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  184. package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
  185. package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
  186. package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
  187. package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
  188. package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
  189. package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
  190. package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
  191. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
  192. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
  193. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  194. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
  195. package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
  196. package/src/rust/components/number_column_style.rs +0 -491
  197. package/src/rust/config/number_column_style.rs +0 -136
  198. package/src/rust/dragdrop.rs +0 -481
  199. package/src/rust/tasks/plugin_column_styles.rs +0 -98
  200. package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
  201. package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
  202. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
  203. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
  204. package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
  205. package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
  206. package/src/ts/ts-rs/FormatMode.ts +0 -3
  207. package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
  208. package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
  209. package/src/ts/ts-rs/PluginConfig.ts +0 -4
  210. package/src/ts/ts-rs/RoundingMode.ts +0 -3
  211. package/src/ts/ts-rs/RoundingPriority.ts +0 -3
  212. package/src/ts/ts-rs/SignDisplay.ts +0 -3
  213. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
  214. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
  215. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  216. package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
  217. package/src/ts/ts-rs/UseGrouping.ts +0 -3
  218. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline0.js +0 -0
  219. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline1.js +0 -0
  220. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline2.js +0 -0
  221. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline3.js +0 -0
  222. /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-39ab7da3ca157861}/inline4.js +0 -0
  223. /package/src/rust/{tasks → config}/export_method.rs +0 -0
  224. /package/src/rust/{tasks → queries}/export_app.rs +0 -0
  225. /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
@@ -0,0 +1,195 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use std::collections::HashSet;
14
+
15
+ use serde::{Deserialize, Serialize};
16
+ use serde_json::Value;
17
+
18
+ use super::{KeyValueOpts, NumberSeriesStyleDefaultConfig};
19
+
20
+ /// The full schema for one column at one point in time. Plugins may return
21
+ /// different schemas for the same column based on the column's current
22
+ /// stored value (e.g. to hide dependent fields), so this is re-queried on
23
+ /// every field update.
24
+ ///
25
+ /// Each entry is a [`ControlSpec`]. Primitive variants carry their own
26
+ /// `key` (JSON storage key) inline; the sidebar UI label is supplied by
27
+ /// CSS via `--psp-label--<key>--content`. Composite variants render a
28
+ /// self-contained Yew component that supplies its own labels and owns a
29
+ /// fixed key namespace via [`ControlSpec::serialized_keys`].
30
+ #[derive(Clone, Debug, Default, Deserialize, Serialize)]
31
+ pub struct ColumnConfigSchema {
32
+ pub fields: Vec<ControlSpec>,
33
+ }
34
+
35
+ impl ColumnConfigSchema {
36
+ /// Union of every JSON key any control in this schema knows how to
37
+ /// read or write. Used to build the schema-filtered view of
38
+ /// `columns_config` passed to `plugin.restore()` — keys not in this
39
+ /// set are "ghost" state from a different plugin and stay invisible
40
+ /// to the active one.
41
+ pub fn active_keys(&self) -> HashSet<String> {
42
+ let mut out = HashSet::new();
43
+ for spec in &self.fields {
44
+ for k in spec.serialized_keys() {
45
+ out.insert(k.to_string());
46
+ }
47
+ }
48
+ out
49
+ }
50
+ }
51
+
52
+ /// Discriminated union of widget kinds the viewer can render. Composite
53
+ /// variants wrap an existing rich Yew component and carry only the
54
+ /// component's `*DefaultConfig`. Primitive variants render generic scalar
55
+ /// widgets and carry their own `key` inline; the visible label is
56
+ /// resolved at CSS time via `--psp-label--<key>--content`.
57
+ #[derive(Clone, Debug, Deserialize, Serialize)]
58
+ #[serde(tag = "kind")]
59
+ pub enum ControlSpec {
60
+ Enum {
61
+ key: String,
62
+ variants: Vec<EnumVariant>,
63
+ default: String,
64
+ },
65
+ Bool {
66
+ key: String,
67
+ default: bool,
68
+ },
69
+ Number {
70
+ key: String,
71
+ default: f64,
72
+
73
+ /// If `true`, always serialize this values even if it is the default.
74
+ #[serde(default, skip_serializing_if = "Option::is_none")]
75
+ include: Option<bool>,
76
+
77
+ #[serde(default, skip_serializing_if = "Option::is_none")]
78
+ min: Option<f64>,
79
+
80
+ #[serde(default, skip_serializing_if = "Option::is_none")]
81
+ max: Option<f64>,
82
+
83
+ #[serde(default, skip_serializing_if = "Option::is_none")]
84
+ step: Option<f64>,
85
+ },
86
+ String {
87
+ key: String,
88
+ default: String,
89
+ #[serde(default, skip_serializing_if = "Option::is_none")]
90
+ placeholder: Option<String>,
91
+ },
92
+ Color {
93
+ key: String,
94
+ default: String,
95
+ },
96
+ /// Paired pos/neg color picker rendered as a single horizontal
97
+ /// gradient/range bar. Used to expose the existing
98
+ /// [`crate::components::form::color_range_selector::ColorRangeSelector`]
99
+ /// widget at primitive granularity. Owns two top-level keys
100
+ /// (`key_pos` + `key_neg`); the visible label is derived from
101
+ /// `key_pos`.
102
+ ColorRange {
103
+ key_pos: String,
104
+ key_neg: String,
105
+ default_pos: String,
106
+ default_neg: String,
107
+ /// When `true`, the bar renders as a continuous gradient
108
+ /// (e.g. for `gradient` color modes); when `false`, the bar
109
+ /// renders as a hard pos/neg split. Currently only changes the
110
+ /// visual; pos/neg semantics are unchanged.
111
+ #[serde(default)]
112
+ is_gradient: bool,
113
+ },
114
+
115
+ /// Residual format-only widget for datetime columns — owns
116
+ /// `date_format` only. Pair with primitive `Enum` and `Color` fields
117
+ /// for `datetime_color_mode` + `color` to fully decompose datetime
118
+ /// styling.
119
+ DatetimeFormat,
120
+ /// Residual format-only widget for string columns — owns `format`
121
+ /// only. Pair with primitive `Enum` and `Color` fields for
122
+ /// `string_color_mode` + `color` to fully decompose string styling.
123
+ StringFormat,
124
+ NumberSeriesStyle {
125
+ default: NumberSeriesStyleDefaultConfig,
126
+ },
127
+ Symbols {
128
+ default: KeyValueOpts,
129
+ },
130
+ NumberFormat,
131
+ AggregateDepth,
132
+ }
133
+
134
+ #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
135
+ pub struct EnumVariant {
136
+ pub value: String,
137
+ #[serde(default, skip_serializing_if = "Option::is_none")]
138
+ pub label: Option<String>,
139
+ }
140
+
141
+ impl ControlSpec {
142
+ /// Top-level JSON keys this control owns when its value is serialized
143
+ /// into a column's config map. For primitives this is just `[key]`;
144
+ /// for composites it's the set of fields the wrapped sub-struct
145
+ /// flattens. Used by [`ColumnConfigSchema::active_keys`] to filter the
146
+ /// `columns_config` blob passed to `plugin.restore()`.
147
+ pub fn serialized_keys(&self) -> Vec<&str> {
148
+ match self {
149
+ ControlSpec::DatetimeFormat => vec!["date_format"],
150
+ ControlSpec::StringFormat => vec!["format"],
151
+ ControlSpec::NumberSeriesStyle { .. } => vec!["chart_type", "stack"],
152
+ ControlSpec::Symbols { .. } => vec!["symbols"],
153
+ ControlSpec::NumberFormat => vec!["number_format"],
154
+ ControlSpec::AggregateDepth => vec!["aggregate_depth"],
155
+ ControlSpec::ColorRange {
156
+ key_pos, key_neg, ..
157
+ } => vec![key_pos.as_str(), key_neg.as_str()],
158
+ ControlSpec::Enum { key, .. }
159
+ | ControlSpec::Bool { key, .. }
160
+ | ControlSpec::Number { key, .. }
161
+ | ControlSpec::String { key, .. }
162
+ | ControlSpec::Color { key, .. } => vec![key.as_str()],
163
+ }
164
+ }
165
+ }
166
+
167
+ /// One UI-emitted change to a single schema field. The emitting widget
168
+ /// declares which top-level keys the update is allowed to write
169
+ /// (`keys` — equivalent to the field's [`ControlSpec::serialized_keys`])
170
+ /// and a partial new sub-state (`value`).
171
+ ///
172
+ /// Apply semantics: keys in `keys` are *cleared* from the column's config
173
+ /// map, then keys present in `value` are *inserted*. Defaults are
174
+ /// pre-stripped by the caller (typically via `skip_serializing_if`), so
175
+ /// "no value set for key K" means the schema default applies and K is
176
+ /// not serialized.
177
+ #[derive(Clone, Debug, Deserialize, Serialize)]
178
+ pub struct ColumnConfigFieldUpdate {
179
+ pub keys: Vec<String>,
180
+ pub value: serde_json::Map<String, Value>,
181
+ }
182
+
183
+ /// Filter a per-column config map to only the keys advertised by the
184
+ /// active plugin's schema. Foreign keys (left over from a previous plugin)
185
+ /// stay in the unfiltered presentation state but never reach `restore()`.
186
+ pub fn filter_to_schema(
187
+ config: &serde_json::Map<String, Value>,
188
+ active_keys: &HashSet<String>,
189
+ ) -> serde_json::Map<String, Value> {
190
+ config
191
+ .iter()
192
+ .filter(|(k, _)| active_keys.contains(k.as_str()))
193
+ .map(|(k, v)| (k.clone(), v.clone()))
194
+ .collect()
195
+ }
@@ -10,105 +10,12 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use std::collections::HashMap;
13
+ //! Auxiliary types used by `ColumnConfigSchema`'s composite control
14
+ //! variants. Per-column persisted state itself is opaque
15
+ //! `serde_json::Map<String, serde_json::Value>`; see
16
+ //! [`crate::presentation::ColumnConfigMap`].
14
17
 
15
18
  use serde::{Deserialize, Serialize};
16
- use ts_rs::TS;
17
-
18
- use super::{
19
- CustomNumberFormatConfig, DatetimeColumnStyleConfig, DatetimeColumnStyleDefaultConfig,
20
- NumberColumnStyleConfig, NumberColumnStyleDefaultConfig, StringColumnStyleConfig,
21
- StringColumnStyleDefaultConfig,
22
- };
23
-
24
- fn is_zero(x: &u32) -> bool {
25
- x == &0
26
- }
27
-
28
- /// The value de/serialized and stored in the viewer config.
29
- /// Also passed to the plugin via `plugin.save()`.
30
- #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize, TS)]
31
- pub struct ColumnConfigValues {
32
- #[serde(default)]
33
- #[serde(skip_serializing_if = "HashMap::is_empty")]
34
- pub symbols: HashMap<String, String>,
35
-
36
- #[serde(flatten)]
37
- pub datagrid_number_style: NumberColumnStyleConfig,
38
-
39
- #[serde(flatten)]
40
- pub datagrid_string_style: StringColumnStyleConfig,
41
-
42
- #[serde(flatten)]
43
- pub datagrid_datetime_style: DatetimeColumnStyleConfig,
44
-
45
- #[serde(default)]
46
- #[serde(skip_serializing_if = "Option::is_none")]
47
- pub number_format: Option<CustomNumberFormatConfig>,
48
-
49
- #[serde(default)]
50
- #[serde(skip_serializing_if = "is_zero")]
51
- pub aggregate_depth: u32,
52
- }
53
-
54
- #[derive(Debug)]
55
- pub enum ColumnConfigValueUpdate {
56
- DatagridNumberStyle(Option<NumberColumnStyleConfig>),
57
- DatagridStringStyle(Option<StringColumnStyleConfig>),
58
- DatagridDatetimeStyle(Option<DatetimeColumnStyleConfig>),
59
- Symbols(Option<HashMap<String, String>>),
60
- CustomNumberStringFormat(Option<CustomNumberFormatConfig>),
61
- AggregateDepth(u32),
62
- }
63
-
64
- impl ColumnConfigValues {
65
- pub fn update(self, update: ColumnConfigValueUpdate) -> Self {
66
- match update {
67
- ColumnConfigValueUpdate::DatagridNumberStyle(update) => Self {
68
- datagrid_number_style: update.unwrap_or_default(),
69
- ..self
70
- },
71
- ColumnConfigValueUpdate::DatagridStringStyle(update) => Self {
72
- datagrid_string_style: update.unwrap_or_default(),
73
- ..self
74
- },
75
- ColumnConfigValueUpdate::DatagridDatetimeStyle(update) => Self {
76
- datagrid_datetime_style: update.unwrap_or_default(),
77
- ..self
78
- },
79
- ColumnConfigValueUpdate::Symbols(update) => Self {
80
- symbols: update.unwrap_or_default(),
81
- ..self
82
- },
83
- ColumnConfigValueUpdate::CustomNumberStringFormat(update) => Self {
84
- number_format: update.filter(|x| x != &CustomNumberFormatConfig::default()),
85
- ..self
86
- },
87
- ColumnConfigValueUpdate::AggregateDepth(aggregate_depth) => Self {
88
- aggregate_depth,
89
- ..self
90
- },
91
- }
92
- }
93
-
94
- pub fn is_empty(&self) -> bool {
95
- self == &Self::default()
96
- }
97
- }
98
-
99
- /// The controls returned by plugin.column_style_controls.
100
- ///
101
- /// This is a way to fill out default values for the given controls.
102
- /// If a control is not given, it will not be rendered. I would like to
103
- /// eventually show these as inactive values.
104
- #[derive(Serialize, Deserialize, PartialEq, Debug, Default)]
105
- pub struct ColumnStyleOpts {
106
- pub datagrid_number_style: Option<NumberColumnStyleDefaultConfig>,
107
- pub datagrid_string_style: Option<StringColumnStyleDefaultConfig>,
108
- pub datagrid_datetime_style: Option<DatetimeColumnStyleDefaultConfig>,
109
- pub symbols: Option<KeyValueOpts>,
110
- pub number_string_format: Option<bool>,
111
- }
112
19
 
113
20
  #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy)]
114
21
  #[serde(rename_all = "snake_case")]
@@ -92,8 +92,3 @@ impl Default for DatetimeColumnStyleConfig {
92
92
  }
93
93
  }
94
94
  }
95
-
96
- #[derive(Clone, Default, Deserialize, Eq, PartialEq, Serialize, Debug)]
97
- pub struct DatetimeColumnStyleDefaultConfig {
98
- pub color: String,
99
- }
@@ -13,19 +13,25 @@
13
13
  //! A collection of (de-)serializable structs which capture the application
14
14
  //! state, suitable for persistence, history, etc. features.
15
15
 
16
+ mod column_config_schema;
16
17
  mod columns_config;
17
18
  mod datetime_column_style;
19
+ mod export_method;
18
20
  mod kvpair;
19
- mod number_column_style;
21
+ mod number_series_style;
20
22
  mod number_string_format;
23
+ mod plugin_static_config;
21
24
  mod string_column_style;
22
25
  pub mod view_config;
23
26
  mod viewer_config;
24
27
 
28
+ pub use column_config_schema::*;
25
29
  pub use columns_config::*;
26
30
  pub use datetime_column_style::*;
27
- pub use number_column_style::*;
31
+ pub use export_method::*;
32
+ pub use number_series_style::*;
28
33
  pub use number_string_format::*;
34
+ pub use plugin_static_config::*;
29
35
  pub use string_column_style::*;
30
36
  pub use view_config::*;
31
37
  pub use viewer_config::*;
@@ -0,0 +1,79 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use serde::{Deserialize, Serialize};
14
+ use strum::{Display, EnumIter};
15
+ use ts_rs::TS;
16
+
17
+ /// Render glyph for a numeric aggregate in the Y Bar plugin. Serialized as
18
+ /// a lowercase string to match the plugin's runtime lookup (which does a
19
+ /// case-insensitive `chart_type` match against the same literal set).
20
+ #[derive(
21
+ Clone, Copy, Debug, Default, Deserialize, Display, EnumIter, Eq, PartialEq, Serialize, TS,
22
+ )]
23
+ pub enum ChartType {
24
+ #[default]
25
+ #[serde(rename = "bar")]
26
+ Bar,
27
+
28
+ #[serde(rename = "line")]
29
+ Line,
30
+
31
+ #[serde(rename = "scatter")]
32
+ Scatter,
33
+
34
+ #[serde(rename = "area")]
35
+ Area,
36
+ }
37
+
38
+ impl ChartType {
39
+ pub fn is_default(&self) -> bool {
40
+ *self == Self::Bar
41
+ }
42
+
43
+ /// Glyphs for which `stack` is a meaningful option. Line/Scatter never
44
+ /// stack; Bar/Area stack by default but can be opted out per column.
45
+ pub fn supports_stack(&self) -> bool {
46
+ matches!(self, Self::Bar | Self::Area)
47
+ }
48
+ }
49
+
50
+ /// Per-column render-style config for numeric aggregates in series charts
51
+ /// (currently Y Bar). Stored flat in the column's
52
+ /// `serde_json::Map<String, serde_json::Value>`, so the JSON shape at the
53
+ /// viewer boundary is `{ "chart_type": "line", "stack": false }`.
54
+ ///
55
+ /// Default `Bar` + `None` stack serializes as an empty object so plugins
56
+ /// that never touch these fields don't pay any JSON overhead.
57
+ #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq, TS)]
58
+ pub struct NumberSeriesStyleConfig {
59
+ #[serde(default)]
60
+ #[serde(skip_serializing_if = "ChartType::is_default")]
61
+ pub chart_type: ChartType,
62
+
63
+ /// Stack override. `None` means "use the glyph default"
64
+ /// (Bar/Area stack, Line/Scatter don't). `Some(false)` on a Bar/Area
65
+ /// forces non-stacking.
66
+ #[serde(default)]
67
+ #[serde(skip_serializing_if = "Option::is_none")]
68
+ pub stack: Option<bool>,
69
+ }
70
+
71
+ /// Defaults-only shape carried by `ControlSpec::NumberSeriesStyle`. The
72
+ /// presence of this control in a plugin's `column_config_schema` is the
73
+ /// signal for the sidebar to render the chart-type picker.
74
+ #[derive(Serialize, Deserialize, Clone, Default, Debug, PartialEq)]
75
+ pub struct NumberSeriesStyleDefaultConfig {
76
+ pub chart_type: ChartType,
77
+ #[serde(default)]
78
+ pub stack: Option<bool>,
79
+ }
@@ -0,0 +1,144 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use perspective_client::config::GroupRollupMode;
14
+ use serde::Deserialize;
15
+ use ts_rs::TS;
16
+
17
+ #[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq, TS)]
18
+ #[serde(rename_all = "camelCase")]
19
+ pub enum ColumnSelectMode {
20
+ #[default]
21
+ Toggle,
22
+ Select,
23
+ }
24
+
25
+ impl ColumnSelectMode {
26
+ pub fn css(&self) -> yew::Classes {
27
+ match self {
28
+ Self::Toggle => yew::classes!("toggle-mode", "is_column_active"),
29
+ Self::Select => yew::classes!("select-mode", "is_column_active"),
30
+ }
31
+ }
32
+ }
33
+
34
+ /// Static, immutable configuration for a plugin.
35
+ ///
36
+ /// Returned once per plugin from `get_static_config()` at registration
37
+ /// time and cached in [`crate::renderer::PluginRecord`]. Consumers
38
+ /// (renderer, session, queries, components) read these fields off the
39
+ /// renderer's active-plugin metadata rather than calling back into JS.
40
+ ///
41
+ /// `<perspective-viewer>` reads this exactly once per plugin (at
42
+ /// `registerPlugin` time) and caches it for the lifetime of the
43
+ /// application. The result must be stable; do not mutate any field
44
+ /// after registration.
45
+ #[derive(Clone, Debug, Default, Deserialize, PartialEq, TS)]
46
+ pub struct PluginStaticConfig {
47
+ /// The unique key for this plugin. Used as the `plugin` field in a
48
+ /// `ViewerConfig` and as the display name key in the
49
+ /// `<perspective-viewer>` UI.
50
+ pub name: String,
51
+
52
+ /// Category in the plugin picker menu.
53
+ #[serde(default)]
54
+ #[ts(as = "Option<_>")]
55
+ #[ts(optional)]
56
+ pub category: Option<String>,
57
+
58
+ /// Soft limit on the number of columns the plugin will render.
59
+ /// Triggers the "Rendering N of M" warning when the view exceeds
60
+ /// this value (until dismissed).
61
+ #[serde(default)]
62
+ #[ts(as = "Option<_>")]
63
+ #[ts(optional)]
64
+ pub max_columns: Option<usize>,
65
+
66
+ /// Soft limit on the number of cells (rows × columns) the plugin
67
+ /// will render. Triggers the "Rendering N of M" warning when the view
68
+ /// exceeds this value (until dismissed).
69
+ #[serde(default)]
70
+ #[ts(as = "Option<_>")]
71
+ #[ts(optional)]
72
+ pub max_cells: Option<usize>,
73
+
74
+ /// Column add/remove behavior. `"select"` exclusively selects the
75
+ /// added column, removing other columns. `"toggle"` toggles the
76
+ /// column on or off based on its current state, leaving other
77
+ /// columns alone.
78
+ #[serde(default)]
79
+ #[ts(as = "Option<_>")]
80
+ #[ts(optional)]
81
+ pub select_mode: ColumnSelectMode,
82
+
83
+ /// Minimum number of columns the plugin requires to render. Mostly
84
+ /// affects drag/drop and column-remove button behavior. `undefined`
85
+ /// is treated identically to `1`.
86
+ #[serde(default)]
87
+ #[ts(as = "Option<_>")]
88
+ #[ts(optional)]
89
+ pub min_config_columns: Option<usize>,
90
+
91
+ /// Named column slots. Named columns have replace/swap behavior in
92
+ /// drag/drop rather than insert. The length must be at least
93
+ /// `min_config_columns`.
94
+ #[serde(default)]
95
+ #[ts(as = "Option<_>")]
96
+ #[ts(optional)]
97
+ pub config_column_names: Vec<String>,
98
+
99
+ /// Group-rollup modes the plugin accepts, in preference order.
100
+ /// The first entry that matches a feature flag becomes the default.
101
+ #[serde(default)]
102
+ #[ts(as = "Option<_>")]
103
+ #[ts(optional)]
104
+ pub group_rollup_modes: Option<Vec<GroupRollupMode>>,
105
+
106
+ /// Plugin load priority. Higher numbers win; ties resolve in
107
+ /// registration order. The highest-priority plugin is loaded by
108
+ /// default unless `restore({ plugin })` overrides it.
109
+ #[serde(default)]
110
+ #[ts(as = "Option<_>")]
111
+ #[ts(optional)]
112
+ pub priority: Option<i32>,
113
+
114
+ /// Whether this plugin opts into per-column style controls in the
115
+ /// settings sidebar. When `true`, the StyleTab is shown for active
116
+ /// columns and the plugin's `column_config_schema` is queried for
117
+ /// the per-column field set. When `false` or omitted, no StyleTab
118
+ /// is shown.
119
+ #[serde(default)]
120
+ #[ts(as = "Option<_>")]
121
+ #[ts(optional)]
122
+ pub can_render_column_styles: bool,
123
+ }
124
+
125
+ impl PluginStaticConfig {
126
+ /// `true` if dropping a column at `index` should swap with the
127
+ /// column already there rather than insert. Only the named slots
128
+ /// (`config_column_names[..len()-1]`) participate in swap behaviour;
129
+ /// the trailing unnamed tail inserts.
130
+ pub fn is_swap(&self, index: usize) -> bool {
131
+ !self.config_column_names.is_empty() && index < self.config_column_names.len() - 1
132
+ }
133
+
134
+ pub fn get_group_rollups(&self, rollup_features: &[GroupRollupMode]) -> Vec<GroupRollupMode> {
135
+ self.group_rollup_modes
136
+ .clone()
137
+ .map(|x| {
138
+ x.into_iter()
139
+ .filter(|y| rollup_features.is_empty() || rollup_features.contains(y))
140
+ .collect()
141
+ })
142
+ .unwrap_or_default()
143
+ }
144
+ }
@@ -133,8 +133,3 @@ pub struct StringColumnStyleConfig {
133
133
  #[serde(default)]
134
134
  pub color: Option<String>,
135
135
  }
136
-
137
- #[derive(Debug, Clone, Default, Deserialize, Eq, PartialEq, Serialize)]
138
- pub struct StringColumnStyleDefaultConfig {
139
- pub color: String,
140
- }
@@ -10,7 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- use std::collections::HashMap;
14
13
  use std::ops::Deref;
15
14
  use std::sync::LazyLock;
16
15
 
@@ -21,8 +20,7 @@ use serde_json::Value;
21
20
  use ts_rs::TS;
22
21
  use wasm_bindgen::prelude::*;
23
22
 
24
- use super::ColumnConfigValues;
25
- use crate::presentation::ColumnConfigMap;
23
+ use crate::renderer::ColumnConfigMap;
26
24
 
27
25
  /// The state of an entire `custom_elements::PerspectiveViewerElement` component
28
26
  /// and its `Plugin`.
@@ -32,7 +30,7 @@ pub struct ViewerConfig<V: TS = String> {
32
30
  pub version: V,
33
31
  pub columns_config: ColumnConfigMap,
34
32
  pub plugin: String,
35
- pub plugin_config: Value,
33
+ pub plugin_config: serde_json::Map<String, Value>,
36
34
  pub settings: bool,
37
35
  pub table: Option<String>,
38
36
  pub theme: Option<String>,
@@ -106,7 +104,7 @@ pub struct ViewerConfigUpdate {
106
104
  #[serde(default)]
107
105
  #[ts(as = "Option<_>")]
108
106
  #[ts(optional)]
109
- pub plugin_config: Option<PluginConfig>,
107
+ pub plugin_config: PluginConfigUpdate,
110
108
 
111
109
  #[serde(default)]
112
110
  #[ts(as = "Option<_>")]
@@ -162,7 +160,8 @@ pub type ThemeUpdate = OptionalUpdate<String>;
162
160
  pub type TitleUpdate = OptionalUpdate<String>;
163
161
  pub type TableUpdate = OptionalUpdate<String>;
164
162
  pub type VersionUpdate = OptionalUpdate<String>;
165
- pub type ColumnConfigUpdate = OptionalUpdate<HashMap<String, ColumnConfigValues>>;
163
+ pub type ColumnConfigUpdate = OptionalUpdate<ColumnConfigMap>;
164
+ pub type PluginConfigUpdate = OptionalUpdate<serde_json::Map<String, Value>>;
166
165
 
167
166
  /// Handles `{}` when included as a field with `#[serde(default)]`.
168
167
  impl<T: Clone> Default for OptionalUpdate<T> {