@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,221 @@
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
+ //! Plugin-scoped settings tab. Mirrors `style_tab` but operates on the
14
+ //! active plugin's `save()`/`restore()` token rather than a per-column
15
+ //! config map. The schema comes from `plugin.plugin_config_schema()`;
16
+ //! field updates are dispatched through `tasks::send_plugin_config`.
17
+
18
+ use itertools::Itertools;
19
+ use perspective_client::config::ViewConfig;
20
+ use yew::prelude::*;
21
+
22
+ use crate::components::column_settings_sidebar::style_tab::primitive_field::{
23
+ BoolField, ColorField, ColorRangeField, EnumField, NumberFieldPrimitive,
24
+ };
25
+ use crate::components::style::LocalStyle;
26
+ use crate::config::ControlSpec;
27
+ use crate::css;
28
+ use crate::queries::get_plugin_config_schema;
29
+ use crate::renderer::Renderer;
30
+ use crate::session::Session;
31
+ use crate::tasks::send_plugin_config;
32
+ use crate::utils::PtrEqRc;
33
+
34
+ #[derive(Clone, PartialEq, Properties)]
35
+ pub struct PluginTabProps {
36
+ /// View config snapshot — passed to the plugin schema callback in
37
+ /// case the plugin wants to gate fields based on it.
38
+ pub view_config: PtrEqRc<ViewConfig>,
39
+
40
+ /// Active plugin's `plugin_config` bucket — threaded as a value
41
+ /// snapshot from `RendererProps`. Changes on every mutation path
42
+ /// that fires `plugin_config_changed` (in-tab edit,
43
+ /// `restore_and_render` JSON paste, `reset_all` with `all=true`)
44
+ /// AND on plugin switch (the active bucket is keyed by plugin
45
+ /// name, so `to_props()` produces a fresh `Rc` after
46
+ /// `commit_plugin_idx`). PluginTab is a pure function of this
47
+ /// prop — no `Renderer::get_plugin_config()` reads against the
48
+ /// interior-mutable handle.
49
+ pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
50
+
51
+ // State
52
+ pub renderer: Renderer,
53
+ pub session: Session,
54
+ }
55
+
56
+ #[function_component]
57
+ pub fn PluginTab(props: &PluginTabProps) -> Html {
58
+ // Memoize the JS-side `plugin_config_schema` call. The schema is a
59
+ // function of (active plugin, current plugin_config values,
60
+ // view_config); each of those arrives as a prop so the deps tuple
61
+ // uses cheap pointer-equality / value-equality. Yew re-runs the
62
+ // closure only when one of them actually changed, so the JS
63
+ // round-trip doesn't fire on unrelated re-renders.
64
+ //
65
+ // The closure captures `renderer` to dispatch `_plugin_config_schema`
66
+ // through the active plugin handle, but resolves it via the props
67
+ // at call time so the schema query is bound to the same atomic
68
+ // snapshot the rendered controls read from. No race window between
69
+ // a plugin swap and the schema fetch — both observe the same
70
+ // `RendererProps` value.
71
+ let schema = {
72
+ let renderer = props.renderer.clone();
73
+ let view_config = props.view_config.clone();
74
+ use_memo(
75
+ (props.plugin_config.clone(), props.view_config.clone()),
76
+ move |_| match get_plugin_config_schema(&renderer, &view_config) {
77
+ Ok(schema) => schema.fields,
78
+ Err(error) => {
79
+ tracing::error!("{}", error);
80
+ vec![]
81
+ },
82
+ },
83
+ )
84
+ };
85
+
86
+ let on_change = {
87
+ let session = props.session.clone();
88
+ let renderer = props.renderer.clone();
89
+ yew::Callback::from(move |update: crate::config::ColumnConfigFieldUpdate| {
90
+ // `send_plugin_config` emits `plugin_config_changed`,
91
+ // which the root component's subscription
92
+ // (`create_subscriptions`) turns into an `UpdateRenderer`
93
+ // dispatch carrying a fresh `RendererProps`. Yew's prop
94
+ // diff propagates the new `plugin_config` into this
95
+ // component automatically — no manual revision bump.
96
+ send_plugin_config(&session, &renderer, update);
97
+ })
98
+ };
99
+
100
+ let raw_config = &*props.plugin_config;
101
+ let components = schema
102
+ .iter()
103
+ .cloned()
104
+ .filter_map(|spec| {
105
+ let component = match spec {
106
+ ControlSpec::Enum {
107
+ key,
108
+ variants,
109
+ default,
110
+ } => {
111
+ let current = raw_config
112
+ .get(&key)
113
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
114
+ html! {
115
+ <EnumField
116
+ field_key={key}
117
+ {variants}
118
+ {default}
119
+ {current}
120
+ on_change={on_change.clone()}
121
+ />
122
+ }
123
+ },
124
+ ControlSpec::Bool { key, default } => {
125
+ let current = raw_config.get(&key).and_then(|v| v.as_bool());
126
+ html! {
127
+ <BoolField
128
+ field_key={key}
129
+ {default}
130
+ {current}
131
+ on_change={on_change.clone()}
132
+ />
133
+ }
134
+ },
135
+ ControlSpec::Color { key, default } => {
136
+ let current = raw_config
137
+ .get(&key)
138
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
139
+ html! {
140
+ <ColorField
141
+ field_key={key}
142
+ {default}
143
+ {current}
144
+ on_change={on_change.clone()}
145
+ />
146
+ }
147
+ },
148
+ ControlSpec::ColorRange {
149
+ key_pos,
150
+ key_neg,
151
+ default_pos,
152
+ default_neg,
153
+ is_gradient,
154
+ } => {
155
+ let current_pos = raw_config
156
+ .get(&key_pos)
157
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
158
+ let current_neg = raw_config
159
+ .get(&key_neg)
160
+ .and_then(|v| v.as_str().map(|s| s.to_string()));
161
+ html! {
162
+ <ColorRangeField
163
+ field_key_pos={key_pos}
164
+ field_key_neg={key_neg}
165
+ {default_pos}
166
+ {default_neg}
167
+ {current_pos}
168
+ {current_neg}
169
+ {is_gradient}
170
+ on_change={on_change.clone()}
171
+ />
172
+ }
173
+ },
174
+ ControlSpec::Number {
175
+ key,
176
+ default,
177
+ min,
178
+ max,
179
+ step,
180
+ include,
181
+ } => {
182
+ let current = raw_config.get(&key).and_then(|v| v.as_f64());
183
+ html! {
184
+ <NumberFieldPrimitive
185
+ field_key={key}
186
+ {default}
187
+ {current}
188
+ {min}
189
+ {max}
190
+ {step}
191
+ {include}
192
+ on_change={on_change.clone()}
193
+ />
194
+ }
195
+ },
196
+ // Column-scoped variants don't apply to
197
+ // plugin-level config; drop silently.
198
+ ControlSpec::AggregateDepth
199
+ | ControlSpec::NumberSeriesStyle { .. }
200
+ | ControlSpec::DatetimeFormat
201
+ | ControlSpec::StringFormat
202
+ | ControlSpec::Symbols { .. }
203
+ | ControlSpec::NumberFormat
204
+ | ControlSpec::String { .. } => {
205
+ return None;
206
+ },
207
+ };
208
+
209
+ Some(html! { <fieldset class="style-control">{ component }</fieldset> })
210
+ })
211
+ .collect_vec();
212
+
213
+ html! {
214
+ <div id="plugin-tab" class="sidebar_column scrollable">
215
+ <LocalStyle href={css!("column-style")} />
216
+ <LocalStyle href={css!("plugin-settings-panel")} />
217
+ <LocalStyle href={css!("containers/tabs")} />
218
+ <div id="plugin-config-container" class="tab-section">{ components }</div>
219
+ </div>
220
+ }
221
+ }
@@ -18,21 +18,22 @@ use yew::prelude::*;
18
18
 
19
19
  use super::column_selector::ColumnSelector;
20
20
  use super::plugin_selector::PluginSelector;
21
+ use super::plugin_tab::PluginTab;
21
22
  use crate::components::containers::sidebar_close_button::SidebarCloseButton;
23
+ use crate::components::form::debug::DebugPanel;
22
24
  use crate::config::PluginUpdate;
23
- use crate::dragdrop::*;
24
25
  use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
25
26
  use crate::renderer::*;
26
27
  use crate::session::column_defaults_update::*;
27
28
  use crate::session::*;
28
- use crate::tasks::can_render_column_styles;
29
+ use crate::tasks::update_and_render;
29
30
  use crate::utils::*;
30
31
 
31
32
  #[derive(Clone, Properties)]
32
33
  pub struct SettingsPanelProps {
33
34
  pub on_close: Callback<()>,
34
35
  pub on_resize: Rc<PubSub<()>>,
35
- pub on_select_column: Callback<ColumnLocator>,
36
+ pub on_select_column: Callback<Option<ColumnLocator>>,
36
37
  pub on_debug: Callback<()>,
37
38
  pub is_debug: bool,
38
39
 
@@ -42,12 +43,20 @@ pub struct SettingsPanelProps {
42
43
  pub has_table: Option<TableLoadState>,
43
44
  pub named_column_count: usize,
44
45
  pub view_config: PtrEqRc<ViewConfig>,
46
+
47
+ /// Snapshot of the active plugin's `plugin_config` bucket, threaded
48
+ /// from `RendererProps`. Forwarded into `PluginTab` so the tab is
49
+ /// prop-driven instead of reading `Renderer` directly.
50
+ pub plugin_config: PtrEqRc<serde_json::Map<String, serde_json::Value>>,
51
+
45
52
  /// Column currently being dragged (if any) — threaded to show drag
46
53
  /// highlights without per-component `DragDrop` PubSub subscriptions.
47
54
  pub drag_column: Option<String>,
55
+
48
56
  /// Cloned session metadata snapshot — threaded from `SessionProps`
49
57
  /// so that metadata changes trigger re-renders via prop diffing.
50
58
  pub metadata: SessionMetadataRc,
59
+
51
60
  /// Snapshot of the column-settings sidebar state — threaded from
52
61
  /// `PresentationProps` so that open/close triggers re-renders.
53
62
  pub open_column_settings: OpenColumnSettings,
@@ -55,8 +64,29 @@ pub struct SettingsPanelProps {
55
64
  /// Selected theme name, threaded for PortalModal consumers.
56
65
  pub selected_theme: Option<String>,
57
66
 
67
+ /// Controlled: the currently selected tab. Lifted to `PerspectiveViewer`
68
+ /// so that messages like `OpenColumnSettings` can revert the tab without
69
+ /// the panel owning the state.
70
+ pub selected_tab: SelectedTab,
71
+
72
+ /// Controlled: the running max of measured tab widths. Lifted so that
73
+ /// `SettingsPanelSizeUpdate(None)` (divider reset) can clear it.
74
+ pub auto_width: f64,
75
+
76
+ /// Callback invoked when the user clicks a tab.
77
+ pub on_select_tab: Callback<SelectedTab>,
78
+
79
+ /// Callback invoked by tab subtrees reporting their natural width.
80
+ pub on_auto_width: Callback<f64>,
81
+
82
+ /// Fires when the outer split-panel divider is reset; threaded into
83
+ /// `ColumnSelector` so its inner `ScrollPanel` can drop its persistent
84
+ /// `viewport_width` and re-measure honestly. Without this, the
85
+ /// `auto_width` reset in `PerspectiveViewer` rebounds immediately as
86
+ /// the ScrollPanel republishes its stale cached width.
87
+ pub on_dimensions_reset: Rc<PubSub<()>>,
88
+
58
89
  /// State
59
- pub dragdrop: DragDrop,
60
90
  pub session: Session,
61
91
  pub renderer: Renderer,
62
92
  pub presentation: Presentation,
@@ -70,17 +100,27 @@ impl PartialEq for SettingsPanelProps {
70
100
  && self.has_table == rhs.has_table
71
101
  && self.named_column_count == rhs.named_column_count
72
102
  && self.view_config == rhs.view_config
103
+ && self.plugin_config == rhs.plugin_config
73
104
  && self.drag_column == rhs.drag_column
74
105
  && self.metadata == rhs.metadata
75
106
  && self.open_column_settings == rhs.open_column_settings
76
107
  && self.selected_theme == rhs.selected_theme
108
+ && self.selected_tab == rhs.selected_tab
109
+ && self.auto_width == rhs.auto_width
77
110
  }
78
111
  }
79
112
 
113
+ #[derive(Debug, PartialEq, Clone, Copy, Default)]
114
+ pub enum SelectedTab {
115
+ #[default]
116
+ Query,
117
+ Plugin,
118
+ Debug,
119
+ }
120
+
80
121
  #[function_component]
81
122
  pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
82
123
  let SettingsPanelProps {
83
- dragdrop,
84
124
  presentation,
85
125
  renderer,
86
126
  session,
@@ -91,7 +131,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
91
131
  let locator = props.open_column_settings.locator.clone();
92
132
  let config = &props.view_config;
93
133
  locator.filter(|locator| match locator {
94
- ColumnLocator::Table(name) => {
134
+ ColumnLocator::Table(_name) => {
95
135
  locator
96
136
  .name()
97
137
  .map(|n| {
@@ -103,8 +143,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
103
143
  || config.sort.iter().any(|col| &col.0 == n)
104
144
  })
105
145
  .unwrap_or_default()
106
- && can_render_column_styles(&props.renderer, config, &props.metadata, name)
107
- .unwrap_or_default()
146
+ && props.renderer.can_render_column_styles()
108
147
  },
109
148
  _ => true,
110
149
  })
@@ -112,48 +151,88 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
112
151
 
113
152
  let plugin_name = props.plugin_name.clone();
114
153
  let available_plugins = props.available_plugins.clone();
154
+ let selected = props.selected_tab;
155
+
156
+ // Shared trap-door width across tabs. Each tab subtree measures its
157
+ // natural width and feeds the result back through `on_auto_width`;
158
+ // the parent keeps the running max so a tab switch never shrinks the
159
+ // panel, and clears it on divider reset.
160
+ let width = props.auto_width;
161
+ let on_auto_width = props.on_auto_width.clone();
115
162
 
116
- // Dispatch callback: captures engine handles, constructs config update, renders
163
+ // Dispatch callback: captures engine handles, constructs config update,
164
+ // hands the apply+draw work to `tasks::update_and_render`.
117
165
  let on_select_plugin = {
118
166
  clone!(renderer, session, presentation);
119
167
  let session_metadata = props.metadata.clone();
168
+ let view_config = props.view_config.clone();
120
169
  Callback::from(move |plugin_name: String| {
121
- if !session.is_errored() {
122
- let metadata =
123
- renderer.get_next_plugin_metadata(&PluginUpdate::Update(plugin_name));
124
-
125
- let prev_metadata = renderer.metadata();
126
- let requirements = metadata.as_ref().unwrap_or(&*prev_metadata);
127
- let rollup_features = session_metadata
128
- .get_features()
129
- .map(|x| x.get_group_rollup_modes())
130
- .unwrap();
131
-
132
- let group_rollups = requirements.get_group_rollups(&rollup_features);
133
- let mut update = ViewConfigUpdate {
134
- group_rollup_mode: group_rollups.first().cloned(),
135
- ..ViewConfigUpdate::default()
136
- };
137
-
138
- update.set_update_column_defaults(
139
- &session_metadata,
140
- &session.get_view_config().columns,
141
- requirements,
142
- );
143
-
144
- if session.update_view_config(update).is_ok() {
145
- clone!(renderer, session);
146
- ApiFuture::spawn(async move {
147
- renderer.apply_pending_plugin()?;
148
- renderer.draw(session.validate().await?.create_view()).await
149
- });
150
- }
151
-
152
- presentation.set_open_column_settings(None);
170
+ if session.is_errored() {
171
+ return;
172
+ }
173
+ let metadata = renderer.get_next_plugin_metadata(&PluginUpdate::Update(plugin_name));
174
+ let prev_metadata = renderer.metadata();
175
+ let plugin_config = metadata.as_deref().unwrap_or(&*prev_metadata);
176
+ let rollup_features = session_metadata
177
+ .get_features()
178
+ .map(|x| x.get_group_rollup_modes())
179
+ .unwrap();
180
+
181
+ let group_rollups = plugin_config.get_group_rollups(&rollup_features);
182
+ let mut update = ViewConfigUpdate {
183
+ group_rollup_mode: group_rollups.first().cloned(),
184
+ ..ViewConfigUpdate::default()
185
+ };
186
+
187
+ update.set_update_column_defaults(
188
+ &session_metadata,
189
+ &view_config.columns,
190
+ plugin_config,
191
+ );
192
+
193
+ if let Ok(task) = update_and_render(&session, &renderer, update) {
194
+ ApiFuture::spawn(task);
153
195
  }
196
+
197
+ presentation.set_open_column_settings(None);
154
198
  })
155
199
  };
156
200
 
201
+ let cb1 = props.on_select_column.clone();
202
+ let set_debug = use_callback(
203
+ props.on_select_tab.clone(),
204
+ move |_: PointerEvent, on_select_tab| {
205
+ on_select_tab.emit(SelectedTab::Debug);
206
+ cb1.emit(None)
207
+ },
208
+ );
209
+
210
+ let cb2 = props.on_select_column.clone();
211
+ let set_plugin = use_callback(
212
+ props.on_select_tab.clone(),
213
+ move |_: PointerEvent, on_select_tab| {
214
+ on_select_tab.emit(SelectedTab::Plugin);
215
+ cb2.emit(None)
216
+ },
217
+ );
218
+
219
+ let set_query = use_callback(
220
+ props.on_select_tab.clone(),
221
+ |_: PointerEvent, on_select_tab| on_select_tab.emit(SelectedTab::Query),
222
+ );
223
+
224
+ let tab_class = |l_tab: SelectedTab, r_tab: SelectedTab| {
225
+ if l_tab == r_tab {
226
+ "settings_tab selected_tab"
227
+ } else {
228
+ "settings_tab"
229
+ }
230
+ };
231
+
232
+ let on_open_expr_panel = use_callback(props.on_select_column.clone(), |c, on_select| {
233
+ on_select.emit(Some(c))
234
+ });
235
+
157
236
  html! {
158
237
  <div id="settings_panel" class="sidebar_column noselect split-panel orient-vertical">
159
238
  if selected_column.is_none() {
@@ -162,25 +241,68 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
162
241
  on_close_sidebar={&props.on_close.clone()}
163
242
  />
164
243
  }
165
- <SidebarCloseButton
166
- id={if props.is_debug {"debug_close_button"} else {"debug_open_button"}}
167
- on_close_sidebar={&props.on_debug}
168
- />
169
- <PluginSelector {plugin_name} {available_plugins} {on_select_plugin} />
170
- <ColumnSelector
171
- on_resize={&props.on_resize}
172
- on_open_expr_panel={&props.on_select_column}
173
- {selected_column}
174
- has_table={props.has_table.clone()}
175
- named_column_count={props.named_column_count}
176
- view_config={props.view_config.clone()}
177
- drag_column={props.drag_column.clone()}
178
- metadata={props.metadata.clone()}
179
- selected_theme={props.selected_theme.clone()}
180
- {dragdrop}
181
- renderer={renderer.clone()}
182
- session={session.clone()}
244
+ <PluginSelector
245
+ {plugin_name}
246
+ {available_plugins}
247
+ {on_select_plugin}
183
248
  />
249
+ <div id="settings_tab_bar" class="settings_tab_bar_scroll_offset">
250
+ <div
251
+ id="query_tabbar_tab"
252
+ class={tab_class(selected, SelectedTab::Query)}
253
+ onpointerdown={set_query}
254
+ />
255
+ <div
256
+ id="plugin_tabbar_tab"
257
+ class={tab_class(selected, SelectedTab::Plugin)}
258
+ onpointerdown={set_plugin}
259
+ />
260
+ <div
261
+ id="debug_tabbar_tab"
262
+ class={tab_class(selected, SelectedTab::Debug)}
263
+ onpointerdown={set_debug}
264
+ />
265
+ </div>
266
+ if selected == SelectedTab::Query {
267
+ <ColumnSelector
268
+ on_resize={&props.on_resize}
269
+ {on_open_expr_panel}
270
+ {selected_column}
271
+ has_table={props.has_table.clone()}
272
+ named_column_count={props.named_column_count}
273
+ view_config={props.view_config.clone()}
274
+ drag_column={props.drag_column.clone()}
275
+ metadata={props.metadata.clone()}
276
+ selected_theme={props.selected_theme.clone()}
277
+ presentation={presentation.clone()}
278
+ renderer={renderer.clone()}
279
+ session={session.clone()}
280
+ initial_width={width}
281
+ on_auto_width={on_auto_width.clone()}
282
+ on_dimensions_reset={&props.on_dimensions_reset}
283
+ />
284
+ } else if selected == SelectedTab::Plugin {
285
+ <PluginTab
286
+ view_config={props.view_config.clone()}
287
+ plugin_config={props.plugin_config.clone()}
288
+ renderer={renderer.clone()}
289
+ session={session.clone()}
290
+ // initial_width={width}
291
+ // on_auto_width={on_auto_width.clone()}
292
+ />
293
+ } else {
294
+ <DebugPanel
295
+ {presentation}
296
+ {renderer}
297
+ {session}
298
+ initial_width={width}
299
+ on_auto_width={on_auto_width.clone()}
300
+ />
301
+ }
302
+ // Sibling sizer keeps the panel width pinned across tab
303
+ // switches; lives outside the tab-body so it survives the
304
+ // tab subtree's unmount.
305
+ <div class="scroll-panel-auto-width" style={format!("width:{}px", width)} />
184
306
  </div>
185
307
  }
186
308
  }