@perspective-dev/viewer 4.2.0 → 4.4.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 (240) hide show
  1. package/dist/cdn/perspective-viewer.js +2 -2
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -0
  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/pro-dark.css +1 -1
  17. package/dist/css/pro.css +1 -1
  18. package/dist/css/solarized-dark.css +1 -1
  19. package/dist/css/solarized.css +1 -1
  20. package/dist/css/themes.css +1 -1
  21. package/dist/css/vaporwave.css +1 -1
  22. package/dist/esm/extensions.d.ts +32 -1
  23. package/dist/esm/perspective-viewer.d.ts +1 -0
  24. package/dist/esm/perspective-viewer.inline.js +2 -2
  25. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  26. package/dist/esm/perspective-viewer.js +2 -2
  27. package/dist/esm/perspective-viewer.js.map +4 -4
  28. package/dist/esm/ts-rs/GroupRollupMode.d.ts +1 -0
  29. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +2 -0
  30. package/dist/wasm/perspective-viewer.d.ts +57 -53
  31. package/dist/wasm/perspective-viewer.js +197 -164
  32. package/dist/wasm/perspective-viewer.wasm +0 -0
  33. package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
  34. package/package.json +9 -6
  35. package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
  36. package/src/css/column-dropdown.css +109 -0
  37. package/src/{less/column-selector.less → css/column-selector.css} +161 -159
  38. package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
  39. package/src/{less/column-style.less → css/column-style.css} +52 -66
  40. package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
  41. package/src/css/config-selector.css +441 -0
  42. package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
  43. package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
  44. package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
  45. package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
  46. package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
  47. package/src/css/dom/checkbox.css +102 -0
  48. package/src/css/dom/scrollbar.css +35 -0
  49. package/src/{less/dom/select.less → css/dom/select.css} +17 -18
  50. package/src/{less/empty-column.less → css/empty-column.css} +19 -18
  51. package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
  52. package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
  53. package/src/{less/filter-item.less → css/filter-item.css} +16 -15
  54. package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
  55. package/src/{less/form/debug.less → css/form/debug.css} +19 -18
  56. package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
  57. package/src/css/plugin-selector.css +261 -0
  58. package/src/{less/render-warning.less → css/render-warning.css} +18 -17
  59. package/src/{less/status-bar.less → css/status-bar.css} +156 -144
  60. package/src/css/type-icon.css +116 -0
  61. package/src/{less/viewer.less → css/viewer.css} +112 -146
  62. package/src/rust/components/column_dropdown.rs +229 -119
  63. package/src/rust/components/column_selector/active_column.rs +81 -62
  64. package/src/rust/components/column_selector/add_expression_button.rs +1 -0
  65. package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
  66. package/src/rust/components/column_selector/config_selector.rs +374 -185
  67. package/src/rust/components/column_selector/empty_column.rs +2 -2
  68. package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
  69. package/src/rust/components/column_selector/filter_column.rs +37 -26
  70. package/src/rust/components/column_selector/inactive_column.rs +41 -29
  71. package/src/rust/components/column_selector/invalid_column.rs +7 -18
  72. package/src/rust/components/column_selector/pivot_column.rs +21 -10
  73. package/src/rust/components/column_selector/sort_column.rs +23 -13
  74. package/src/rust/components/column_selector.rs +189 -100
  75. package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
  76. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
  77. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
  78. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
  79. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
  80. package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
  81. package/src/rust/components/column_settings_sidebar.rs +91 -53
  82. package/src/rust/components/containers/dragdrop_list.rs +29 -7
  83. package/src/rust/components/containers/scroll_panel.rs +8 -1
  84. package/src/rust/components/containers/select.rs +3 -3
  85. package/src/rust/components/containers/sidebar_close_button.rs +1 -1
  86. package/src/rust/components/containers/split_panel.rs +3 -2
  87. package/src/rust/components/containers/tab_list.rs +1 -1
  88. package/src/rust/components/copy_dropdown.rs +7 -28
  89. package/src/rust/components/datetime_column_style/custom.rs +2 -2
  90. package/src/rust/components/datetime_column_style/simple.rs +2 -2
  91. package/src/rust/components/datetime_column_style.rs +4 -2
  92. package/src/rust/components/editable_header.rs +7 -4
  93. package/src/rust/components/empty_row.rs +1 -1
  94. package/src/rust/components/export_dropdown.rs +4 -30
  95. package/src/rust/components/expression_editor.rs +19 -10
  96. package/src/rust/components/filter_dropdown.rs +246 -102
  97. package/src/rust/components/font_loader.rs +11 -28
  98. package/src/rust/components/form/code_editor.rs +17 -2
  99. package/src/rust/components/form/color_range_selector.rs +19 -6
  100. package/src/rust/components/form/debug.rs +30 -13
  101. package/src/rust/components/function_dropdown.rs +186 -113
  102. package/src/rust/components/main_panel.rs +71 -89
  103. package/src/rust/components/mod.rs +1 -1
  104. package/src/rust/components/modal.rs +7 -1
  105. package/src/rust/components/number_column_style.rs +22 -7
  106. package/src/rust/components/plugin_selector.rs +34 -92
  107. package/src/rust/components/portal.rs +274 -0
  108. package/src/rust/components/render_warning.rs +72 -123
  109. package/src/rust/components/settings_panel.rs +115 -11
  110. package/src/rust/components/status_bar.rs +222 -98
  111. package/src/rust/components/status_bar_counter.rs +8 -20
  112. package/src/rust/components/status_indicator.rs +64 -111
  113. package/src/rust/components/string_column_style.rs +2 -2
  114. package/src/rust/components/style/style_cache.rs +5 -1
  115. package/src/rust/components/viewer.rs +391 -39
  116. package/src/rust/custom_elements/copy_dropdown.rs +102 -21
  117. package/src/rust/custom_elements/export_dropdown.rs +102 -20
  118. package/src/rust/custom_elements/mod.rs +0 -7
  119. package/src/rust/custom_elements/modal.rs +7 -103
  120. package/src/rust/custom_elements/viewer.rs +99 -35
  121. package/src/rust/custom_events.rs +23 -2
  122. package/src/rust/dragdrop.rs +149 -10
  123. package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
  124. package/src/rust/js/plugin.rs +20 -1
  125. package/src/rust/lib.rs +5 -4
  126. package/src/rust/presentation/props.rs +39 -0
  127. package/src/rust/presentation/sheets.rs +3 -3
  128. package/src/rust/presentation.rs +44 -8
  129. package/src/rust/renderer/limits.rs +32 -3
  130. package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
  131. package/src/rust/renderer/registry.rs +8 -1
  132. package/src/rust/renderer.rs +83 -9
  133. package/src/rust/session/column_defaults_update.rs +18 -0
  134. package/src/rust/session/metadata.rs +23 -2
  135. package/src/rust/session/props.rs +178 -0
  136. package/src/rust/session/replace_expression_update.rs +1 -0
  137. package/src/rust/session.rs +124 -117
  138. package/src/rust/tasks/column_locator.rs +133 -0
  139. package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
  140. package/src/rust/{model → tasks}/edit_expression.rs +34 -10
  141. package/src/rust/{model → tasks}/eject.rs +2 -2
  142. package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
  143. package/src/rust/{model → tasks}/intersection_observer.rs +22 -4
  144. package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
  145. package/src/rust/tasks/mod.rs +52 -0
  146. package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
  147. package/src/rust/{model → tasks}/resize_observer.rs +39 -6
  148. package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
  149. package/src/rust/tasks/structural.rs +53 -0
  150. package/src/rust/utils/mod.rs +4 -0
  151. package/src/rust/utils/modal_position.rs +110 -0
  152. package/src/rust/utils/ptr_eq_rc.rs +74 -0
  153. package/src/rust/utils/pubsub.rs +11 -1
  154. package/src/svg/bg-pattern.png +0 -0
  155. package/src/svg/close-icon.svg +1 -1
  156. package/src/svg/expression.svg +1 -1
  157. package/src/svg/mega-menu-icons-candlestick.svg +1 -1
  158. package/src/svg/mega-menu-icons-datagrid.svg +1 -2
  159. package/src/svg/mega-menu-icons-heatmap.svg +1 -1
  160. package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
  161. package/src/svg/mega-menu-icons-ohlc.svg +1 -1
  162. package/src/svg/mega-menu-icons-sunburst.svg +1 -1
  163. package/src/svg/mega-menu-icons-treemap.svg +1 -1
  164. package/src/svg/mega-menu-icons-x-bar.svg +1 -1
  165. package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
  166. package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
  167. package/src/svg/mega-menu-icons-y-area.svg +1 -1
  168. package/src/svg/mega-menu-icons-y-bar.svg +1 -1
  169. package/src/svg/mega-menu-icons-y-line.svg +1 -1
  170. package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
  171. package/src/svg/radio-hover.svg +1 -1
  172. package/src/svg/radio-off.svg +1 -1
  173. package/src/svg/radio-on.svg +1 -1
  174. package/src/themes/botanical.css +157 -0
  175. package/src/themes/defaults.css +139 -0
  176. package/src/themes/dracula.css +233 -0
  177. package/src/themes/gruvbox-dark.css +255 -0
  178. package/src/themes/gruvbox.css +134 -0
  179. package/src/themes/icons.css +124 -0
  180. package/src/themes/intl/de.css +102 -0
  181. package/src/themes/intl/es.css +102 -0
  182. package/src/themes/intl/fr.css +102 -0
  183. package/src/themes/intl/ja.css +102 -0
  184. package/src/themes/intl/pt.css +102 -0
  185. package/src/themes/intl/zh.css +102 -0
  186. package/src/themes/intl.css +102 -0
  187. package/src/themes/monokai.css +233 -0
  188. package/src/themes/pro-dark.css +158 -0
  189. package/src/themes/{themes.less → pro.css} +17 -20
  190. package/src/themes/solarized-dark.css +135 -0
  191. package/src/themes/solarized.css +95 -0
  192. package/src/themes/themes.css +22 -0
  193. package/src/themes/vaporwave.css +256 -0
  194. package/src/ts/extensions.ts +73 -2
  195. package/src/ts/perspective-viewer.ts +1 -0
  196. package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
  197. package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -1
  198. package/tsconfig.json +1 -0
  199. package/dist/css/variables.css +0 -0
  200. package/src/less/column-dropdown.less +0 -95
  201. package/src/less/config-selector.less +0 -363
  202. package/src/less/dom/checkbox.less +0 -100
  203. package/src/less/plugin-selector.less +0 -183
  204. package/src/less/type-icon.less +0 -68
  205. package/src/rust/components/error_message.rs +0 -56
  206. package/src/rust/custom_elements/column_dropdown.rs +0 -123
  207. package/src/rust/custom_elements/filter_dropdown.rs +0 -179
  208. package/src/rust/custom_elements/function_dropdown.rs +0 -115
  209. package/src/rust/model/column_locator.rs +0 -82
  210. package/src/rust/model/is_invalid_drop.rs +0 -36
  211. package/src/rust/model/mod.rs +0 -100
  212. package/src/rust/model/reset_all.rs +0 -38
  213. package/src/rust/model/structural.rs +0 -244
  214. package/src/themes/dracula.less +0 -101
  215. package/src/themes/gruvbox-dark.less +0 -116
  216. package/src/themes/gruvbox.less +0 -152
  217. package/src/themes/icons.less +0 -130
  218. package/src/themes/intl/de.less +0 -102
  219. package/src/themes/intl/es.less +0 -102
  220. package/src/themes/intl/fr.less +0 -102
  221. package/src/themes/intl/ja.less +0 -102
  222. package/src/themes/intl/pt.less +0 -102
  223. package/src/themes/intl/zh.less +0 -102
  224. package/src/themes/intl.less +0 -102
  225. package/src/themes/monokai.less +0 -107
  226. package/src/themes/pro-dark.less +0 -147
  227. package/src/themes/pro.less +0 -186
  228. package/src/themes/solarized-dark.less +0 -78
  229. package/src/themes/solarized.less +0 -102
  230. package/src/themes/vaporwave.less +0 -145
  231. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
  232. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
  233. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
  234. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
  235. /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
  236. /package/src/rust/{model → tasks}/copy_export.rs +0 -0
  237. /package/src/rust/{model → tasks}/export_app.rs +0 -0
  238. /package/src/rust/{model → tasks}/export_method.rs +0 -0
  239. /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
  240. /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
@@ -26,18 +26,30 @@ use crate::components::number_column_style::NumberColumnStyle;
26
26
  use crate::components::string_column_style::StringColumnStyle;
27
27
  use crate::components::style_controls::CustomNumberFormat;
28
28
  use crate::custom_events::CustomEvents;
29
- use crate::model::*;
30
29
  use crate::presentation::Presentation;
31
30
  use crate::renderer::Renderer;
32
31
  use crate::session::Session;
33
- use crate::*;
32
+ use crate::tasks::{
33
+ HasCustomEvents, HasPresentation, HasRenderer, HasSession, SendPluginConfig,
34
+ get_column_style_control_options,
35
+ };
36
+ use crate::utils::PtrEqRc;
34
37
 
35
- #[derive(Clone, PartialEq, Properties, PerspectiveProperties!)]
38
+ #[derive(Clone, PartialEq, Properties)]
36
39
  pub struct StyleTabProps {
37
40
  pub ty: Option<ColumnType>,
38
41
  pub column_name: String,
39
42
  pub group_by_depth: u32,
40
43
 
44
+ /// View config snapshot — threaded from parent.
45
+ pub view_config: PtrEqRc<perspective_client::config::ViewConfig>,
46
+
47
+ /// Session metadata snapshot — threaded from parent.
48
+ pub metadata: PtrEqRc<crate::session::SessionMetadata>,
49
+
50
+ /// Selected theme name, threaded for PortalModal consumers.
51
+ pub selected_theme: Option<String>,
52
+
41
53
  // State
42
54
  pub custom_events: CustomEvents,
43
55
  pub presentation: Presentation,
@@ -45,123 +57,152 @@ pub struct StyleTabProps {
45
57
  pub session: Session,
46
58
  }
47
59
 
60
+ impl HasCustomEvents for StyleTabProps {
61
+ fn custom_events(&self) -> &CustomEvents {
62
+ &self.custom_events
63
+ }
64
+ }
65
+
66
+ impl HasPresentation for StyleTabProps {
67
+ fn presentation(&self) -> &Presentation {
68
+ &self.presentation
69
+ }
70
+ }
71
+
72
+ impl HasRenderer for StyleTabProps {
73
+ fn renderer(&self) -> &Renderer {
74
+ &self.renderer
75
+ }
76
+ }
77
+
78
+ impl HasSession for StyleTabProps {
79
+ fn session(&self) -> &Session {
80
+ &self.session
81
+ }
82
+ }
83
+
48
84
  #[function_component]
49
85
  pub fn StyleTab(props: &StyleTabProps) -> Html {
50
- let config = props.presentation().get_columns_config(&props.column_name);
86
+ let config = props.presentation.get_columns_config(&props.column_name);
51
87
  let on_change = yew::use_callback(
52
- (props.clone_state(), props.column_name.clone()),
88
+ (props.clone(), props.column_name.clone()),
53
89
  |config, (state, column_name)| {
54
90
  state.send_plugin_config(column_name, config);
55
91
  },
56
92
  );
57
93
 
58
- let components = props
59
- .get_column_style_control_options(&props.column_name)
60
- .map(|opts| {
61
- let mut components = vec![];
62
- if !props.session().get_view_config().group_by.is_empty() {
63
- let aggregate_depth = config.as_ref().map(|x| x.aggregate_depth as f64);
64
- components.push(("Aggregate Depth", html! {
65
- <AggregateDepthSelector
66
- group_by_depth={props.group_by_depth}
67
- on_change={on_change.clone()}
68
- column_name={props.column_name.to_owned()}
69
- value={aggregate_depth.unwrap_or_default() as u32}
70
- />
71
- }));
72
- }
73
-
74
- if let Some(default_config) = opts.datagrid_number_style {
75
- let config = config
76
- .as_ref()
77
- .map(|config| config.datagrid_number_style.clone());
78
-
79
- components.push(("Number Styles", html! {
80
- <NumberColumnStyle
81
- column_name={props.column_name.clone()}
82
- {config}
83
- {default_config}
84
- on_change={on_change.clone()}
85
- session={props.session()}
86
- />
87
- }));
88
- }
89
- if let Some(default_config) = opts.datagrid_string_style {
90
- let config = config
91
- .as_ref()
92
- .map(|config| config.datagrid_string_style.clone());
93
-
94
- components.push(("String Styles", html! {
95
- <StringColumnStyle {config} {default_config} on_change={on_change.clone()} />
96
- }));
97
- }
98
-
99
- if let Some(default_config) = opts.datagrid_datetime_style {
100
- let config = config
101
- .as_ref()
102
- .map(|config| config.datagrid_datetime_style.clone());
103
-
104
- let enable_time_config = props.ty.unwrap() == ColumnType::Datetime;
105
- components.push(("Datetime Styles", html! {
106
- <DatetimeColumnStyle
107
- {enable_time_config}
108
- {config}
109
- {default_config}
110
- on_change={on_change.clone()}
111
- />
112
- }))
113
- }
114
-
115
- if let Some(default_config) = opts.symbols {
116
- let restored_config = config
117
- .as_ref()
118
- .map(|config| config.symbols.clone())
119
- .unwrap_or_default();
120
-
121
- components.push(("Symbols", html! {
122
- <SymbolStyle
123
- {default_config}
124
- {restored_config}
125
- on_change={on_change.clone()}
126
- column_name={props.column_name.clone()}
127
- session={props.session().clone()}
128
- />
129
- }))
130
- }
131
-
132
- if opts.number_string_format.unwrap_or_default() {
133
- let restored_config = config
134
- .as_ref()
135
- .and_then(|config| config.number_format.clone())
136
- .unwrap_or_default();
137
-
138
- components.push(("Number Formatting", html! {
139
- <CustomNumberFormat
140
- {restored_config}
141
- on_change={on_change.clone()}
142
- view_type={props.ty.unwrap()}
143
- column_name={props.column_name.clone()}
144
- />
145
- }));
146
- }
147
-
148
- components
149
- .into_iter()
150
- .map(|(_title, component)| {
151
- html! {
152
- <fieldset class="style-control">
153
- // <legend >{ title }</legend>
154
- { component }
155
- </fieldset>
156
- }
157
- })
158
- .collect_vec()
159
- })
160
- .unwrap_or_else(|error| {
161
- vec![html! {
162
- <Stub message="Could not render column styles" error={Some(format!("{error:?}"))} />
163
- }]
164
- });
94
+ let components = get_column_style_control_options(
95
+ &props.renderer,
96
+ &props.view_config,
97
+ &props.metadata,
98
+ &props.column_name,
99
+ )
100
+ .map(|opts| {
101
+ let mut components = vec![];
102
+ if !props.view_config.group_by.is_empty() {
103
+ let aggregate_depth = config.as_ref().map(|x| x.aggregate_depth as f64);
104
+ components.push(("Aggregate Depth", html! {
105
+ <AggregateDepthSelector
106
+ group_by_depth={props.group_by_depth}
107
+ on_change={on_change.clone()}
108
+ column_name={props.column_name.to_owned()}
109
+ value={aggregate_depth.unwrap_or_default() as u32}
110
+ />
111
+ }));
112
+ }
113
+
114
+ if let Some(default_config) = opts.datagrid_number_style {
115
+ let config = config
116
+ .as_ref()
117
+ .map(|config| config.datagrid_number_style.clone());
118
+
119
+ components.push(("Number Styles", html! {
120
+ <NumberColumnStyle
121
+ column_name={props.column_name.clone()}
122
+ {config}
123
+ {default_config}
124
+ on_change={on_change.clone()}
125
+ session={props.session.clone()}
126
+ />
127
+ }));
128
+ }
129
+ if let Some(default_config) = opts.datagrid_string_style {
130
+ let config = config
131
+ .as_ref()
132
+ .map(|config| config.datagrid_string_style.clone());
133
+
134
+ components.push(("String Styles", html! {
135
+ <StringColumnStyle {config} {default_config} on_change={on_change.clone()} />
136
+ }));
137
+ }
138
+
139
+ if let Some(default_config) = opts.datagrid_datetime_style {
140
+ let config = config
141
+ .as_ref()
142
+ .map(|config| config.datagrid_datetime_style.clone());
143
+
144
+ let enable_time_config = props.ty.unwrap() == ColumnType::Datetime;
145
+ components.push(("Datetime Styles", html! {
146
+ <DatetimeColumnStyle
147
+ {enable_time_config}
148
+ {config}
149
+ {default_config}
150
+ on_change={on_change.clone()}
151
+ />
152
+ }))
153
+ }
154
+
155
+ if let Some(default_config) = opts.symbols {
156
+ let restored_config = config
157
+ .as_ref()
158
+ .map(|config| config.symbols.clone())
159
+ .unwrap_or_default();
160
+
161
+ components.push(("Symbols", html! {
162
+ <SymbolStyle
163
+ {default_config}
164
+ {restored_config}
165
+ on_change={on_change.clone()}
166
+ column_name={props.column_name.clone()}
167
+ selected_theme={props.selected_theme.clone()}
168
+ session={props.session.clone()}
169
+ />
170
+ }))
171
+ }
172
+
173
+ if opts.number_string_format.unwrap_or_default() {
174
+ let restored_config = config
175
+ .as_ref()
176
+ .and_then(|config| config.number_format.clone())
177
+ .unwrap_or_default();
178
+
179
+ components.push(("Number Formatting", html! {
180
+ <CustomNumberFormat
181
+ {restored_config}
182
+ on_change={on_change.clone()}
183
+ view_type={props.ty.unwrap()}
184
+ column_name={props.column_name.clone()}
185
+ />
186
+ }));
187
+ }
188
+
189
+ components
190
+ .into_iter()
191
+ .map(|(_title, component)| {
192
+ html! {
193
+ <fieldset class="style-control">
194
+ // <legend >{ title }</legend>
195
+ { component }
196
+ </fieldset>
197
+ }
198
+ })
199
+ .collect_vec()
200
+ })
201
+ .unwrap_or_else(|error| {
202
+ vec![html! {
203
+ <Stub message="Could not render column styles" error={Some(format!("{error:?}"))} />
204
+ }]
205
+ });
165
206
 
166
207
  html! {
167
208
  <div id="style-tab">
@@ -18,7 +18,7 @@ use std::rc::Rc;
18
18
 
19
19
  use derivative::Derivative;
20
20
  use itertools::Itertools;
21
- use perspective_client::config::{ColumnType, Expression};
21
+ use perspective_client::config::{ColumnType, Expression, ViewConfig};
22
22
  use perspective_client::utils::PerspectiveResultExt;
23
23
  use yew::{Callback, Component, Html, Properties, html, props};
24
24
 
@@ -34,14 +34,17 @@ use crate::components::expression_editor::ExpressionEditorProps;
34
34
  use crate::components::style::LocalStyle;
35
35
  use crate::components::type_icon::TypeIconType;
36
36
  use crate::custom_events::CustomEvents;
37
- use crate::model::*;
38
37
  use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation};
39
38
  use crate::renderer::Renderer;
40
- use crate::session::Session;
41
- use crate::utils::*;
39
+ use crate::session::{Session, SessionMetadataRc};
40
+ use crate::tasks::{
41
+ EditExpression, HasCustomEvents, HasPresentation, HasRenderer, HasSession,
42
+ can_render_column_styles, locator_name_or_default, locator_view_type,
43
+ };
44
+ use crate::utils::PtrEqRc;
42
45
  use crate::*;
43
46
 
44
- #[derive(Clone, Derivative, Properties, PerspectiveProperties!)]
47
+ #[derive(Clone, Derivative, Properties)]
45
48
  #[derivative(Debug)]
46
49
  pub struct ColumnSettingsPanelProps {
47
50
  pub selected_column: ColumnLocator,
@@ -50,6 +53,20 @@ pub struct ColumnSettingsPanelProps {
50
53
  pub width_override: Option<i32>,
51
54
  pub on_select_tab: Callback<ColumnSettingsTab>,
52
55
 
56
+ /// Active plugin name threaded as a value prop so that plugin changes
57
+ /// trigger re-initialization via `changed()` rather than a PubSub
58
+ /// `render_limits_changed` subscription.
59
+ pub plugin_name: Option<String>,
60
+
61
+ /// Session metadata snapshot — threaded from `SessionProps`.
62
+ pub metadata: SessionMetadataRc,
63
+
64
+ /// View config snapshot — threaded from `SessionProps`.
65
+ pub view_config: PtrEqRc<ViewConfig>,
66
+
67
+ /// Selected theme name, threaded for PortalModal consumers.
68
+ pub selected_theme: Option<String>,
69
+
53
70
  // State
54
71
  #[derivative(Debug = "ignore")]
55
72
  pub custom_events: CustomEvents,
@@ -66,7 +83,36 @@ pub struct ColumnSettingsPanelProps {
66
83
 
67
84
  impl PartialEq for ColumnSettingsPanelProps {
68
85
  fn eq(&self, other: &Self) -> bool {
69
- self.selected_column == other.selected_column && self.selected_tab == other.selected_tab
86
+ self.selected_column == other.selected_column
87
+ && self.selected_tab == other.selected_tab
88
+ && self.plugin_name == other.plugin_name
89
+ && self.metadata == other.metadata
90
+ && self.view_config == other.view_config
91
+ && self.selected_theme == other.selected_theme
92
+ }
93
+ }
94
+
95
+ impl HasCustomEvents for ColumnSettingsPanelProps {
96
+ fn custom_events(&self) -> &CustomEvents {
97
+ &self.custom_events
98
+ }
99
+ }
100
+
101
+ impl HasPresentation for ColumnSettingsPanelProps {
102
+ fn presentation(&self) -> &Presentation {
103
+ &self.presentation
104
+ }
105
+ }
106
+
107
+ impl HasRenderer for ColumnSettingsPanelProps {
108
+ fn renderer(&self) -> &Renderer {
109
+ &self.renderer
110
+ }
111
+ }
112
+
113
+ impl HasSession for ColumnSettingsPanelProps {
114
+ fn session(&self) -> &Session {
115
+ &self.session
70
116
  }
71
117
  }
72
118
 
@@ -80,7 +126,6 @@ pub enum ColumnSettingsPanelMsg {
80
126
  OnSaveAttributes(()),
81
127
  OnResetAttributes(()),
82
128
  OnDelete(()),
83
- SessionUpdated(bool),
84
129
  }
85
130
 
86
131
  #[derive(Derivative)]
@@ -102,9 +147,6 @@ pub struct ColumnSettingsPanel {
102
147
  save_count: u8,
103
148
  save_enabled: bool,
104
149
  tabs: Vec<ColumnSettingsTab>,
105
-
106
- #[derivative(Debug = "ignore")]
107
- _session_sub: Option<Subscription>,
108
150
  }
109
151
 
110
152
  impl Component for ColumnSettingsPanel {
@@ -112,18 +154,7 @@ impl Component for ColumnSettingsPanel {
112
154
  type Properties = ColumnSettingsPanelProps;
113
155
 
114
156
  fn create(ctx: &yew::prelude::Context<Self>) -> Self {
115
- let session_cb = ctx
116
- .link()
117
- .callback(|(is_update, _)| ColumnSettingsPanelMsg::SessionUpdated(is_update));
118
-
119
- let session_sub = ctx
120
- .props()
121
- .renderer
122
- .render_limits_changed
123
- .add_listener(session_cb);
124
-
125
157
  let mut this = Self {
126
- _session_sub: Some(session_sub),
127
158
  initial_expr_value: Rc::default(),
128
159
  expr_value: Rc::default(),
129
160
  expr_valid: false,
@@ -233,14 +264,6 @@ impl Component for ColumnSettingsPanel {
233
264
  ctx.props().on_close.emit(());
234
265
  true
235
266
  },
236
- ColumnSettingsPanelMsg::SessionUpdated(is_update) => {
237
- if !is_update {
238
- self.initialize(ctx);
239
- true
240
- } else {
241
- false
242
- }
243
- },
244
267
  }
245
268
  }
246
269
 
@@ -264,6 +287,7 @@ impl Component for ColumnSettingsPanel {
264
287
  ColumnSettingsPanelMsg::SetHeaderValid(valid),
265
288
  ]
266
289
  }),
290
+ metadata: ctx.props().metadata.clone(),
267
291
  session: &ctx.props().session
268
292
  });
269
293
 
@@ -274,13 +298,28 @@ impl Component for ColumnSettingsPanel {
274
298
  alias: ctx.props().selected_column.name().cloned(),
275
299
  disabled: !ctx.props().selected_column.is_expr(),
276
300
  reset_count: self.reset_count,
301
+ metadata: ctx.props().metadata.clone(),
302
+ selected_theme: ctx.props().selected_theme.clone(),
277
303
  session: &ctx.props().session
278
304
  });
279
305
 
280
306
  let disable_delete = ctx
281
307
  .props()
282
- .session
283
- .is_locator_active(&ctx.props().selected_column);
308
+ .selected_column
309
+ .name()
310
+ .map(|name| {
311
+ let config = &ctx.props().view_config;
312
+ config.columns.iter().any(|maybe_col| {
313
+ maybe_col
314
+ .as_ref()
315
+ .map(|col| col == name)
316
+ .unwrap_or_default()
317
+ }) || config.group_by.iter().any(|col| col == name)
318
+ || config.split_by.iter().any(|col| col == name)
319
+ || config.filter.iter().any(|col| col.column() == name)
320
+ || config.sort.iter().any(|col| &col.0 == name)
321
+ })
322
+ .unwrap_or_default();
284
323
 
285
324
  let save_section = SaveSettingsProps {
286
325
  save_enabled: self.save_enabled,
@@ -302,15 +341,18 @@ impl Component for ColumnSettingsPanel {
302
341
  save_section,
303
342
  };
304
343
 
305
- let style_tab = props!(StyleTabProps {
344
+ let style_tab = StyleTabProps {
306
345
  ty: self.maybe_ty,
307
346
  column_name: self.column_name.clone(),
308
- group_by_depth: ctx.props().session.get_view_config().group_by.len() as u32,
309
- custom_events: ctx.props().custom_events(),
310
- presentation: ctx.props().presentation(),
311
- renderer: ctx.props().renderer(),
312
- session: ctx.props().session()
313
- });
347
+ group_by_depth: ctx.props().view_config.group_by.len() as u32,
348
+ view_config: ctx.props().view_config.clone(),
349
+ metadata: ctx.props().metadata.clone(),
350
+ selected_theme: ctx.props().selected_theme.clone(),
351
+ custom_events: ctx.props().custom_events.clone(),
352
+ presentation: ctx.props().presentation.clone(),
353
+ renderer: ctx.props().renderer.clone(),
354
+ session: ctx.props().session.clone(),
355
+ };
314
356
 
315
357
  let tab_children = self.tabs.iter().map(|tab| match tab {
316
358
  ColumnSettingsTab::Attributes => html! { <AttributesTab ..attrs_tab.clone() /> },
@@ -356,15 +398,12 @@ impl ColumnSettingsPanel {
356
398
  }
357
399
 
358
400
  fn initialize(&mut self, ctx: &yew::prelude::Context<Self>) {
359
- let column_name = ctx
360
- .props()
361
- .session
362
- .locator_name_or_default(&ctx.props().selected_column);
401
+ let column_name =
402
+ locator_name_or_default(&ctx.props().metadata, &ctx.props().selected_column);
363
403
 
364
404
  let initial_expr_value = ctx
365
405
  .props()
366
- .session
367
- .metadata()
406
+ .metadata
368
407
  .get_expression_by_alias(&column_name)
369
408
  .unwrap_or_default();
370
409
 
@@ -372,19 +411,19 @@ impl ColumnSettingsPanel {
372
411
  let initial_header_value =
373
412
  (*initial_expr_value != column_name).then_some(column_name.clone());
374
413
 
375
- let maybe_ty = ctx
376
- .props()
377
- .session()
378
- .locator_view_type(&ctx.props().selected_column);
414
+ let maybe_ty = locator_view_type(&ctx.props().metadata, &ctx.props().selected_column);
379
415
 
380
416
  let tabs = {
381
417
  let mut tabs = vec![];
382
418
  let is_new_expr = ctx.props().selected_column.is_new_expr();
383
419
  let show_styles = !is_new_expr
384
- && ctx
385
- .props()
386
- .can_render_column_styles(&column_name)
387
- .unwrap_or_default();
420
+ && can_render_column_styles(
421
+ &ctx.props().renderer,
422
+ &ctx.props().view_config,
423
+ &ctx.props().metadata,
424
+ &column_name,
425
+ )
426
+ .unwrap_or_default();
388
427
 
389
428
  if !is_new_expr && show_styles {
390
429
  tabs.push(ColumnSettingsTab::Style);
@@ -414,7 +453,6 @@ impl ColumnSettingsPanel {
414
453
  on_input,
415
454
  on_save,
416
455
  on_validate,
417
- _session_sub: self._session_sub.take(),
418
456
  ..*self
419
457
  }
420
458
  }
@@ -14,12 +14,14 @@ use std::collections::HashSet;
14
14
  use std::marker::PhantomData;
15
15
 
16
16
  use derivative::Derivative;
17
+ use perspective_client::proto::ColumnType;
17
18
  use web_sys::*;
18
19
  use yew::html::Scope;
19
20
  use yew::prelude::*;
20
21
 
22
+ use crate::components::column_dropdown::ColumnDropDownElement;
21
23
  use crate::components::column_selector::{EmptyColumn, InPlaceColumn, InvalidColumn};
22
- use crate::custom_elements::ColumnDropDownElement;
24
+ use crate::components::type_icon::TypeIcon;
23
25
  use crate::dragdrop::*;
24
26
  use crate::utils::DragTarget;
25
27
 
@@ -32,12 +34,16 @@ where
32
34
  <U as Component>::Properties: DragDropListItemProps,
33
35
  {
34
36
  pub parent: Scope<T>,
37
+
35
38
  pub dragdrop: DragDrop,
36
39
  pub name: &'static str,
37
40
  pub column_dropdown: ColumnDropDownElement,
38
41
  pub exclude: HashSet<String>,
39
42
  pub children: ChildrenWithProps<U>,
40
43
 
44
+ #[prop_or_default]
45
+ pub disabled: bool,
46
+
41
47
  #[prop_or_default]
42
48
  pub is_dragover: Option<(
43
49
  usize,
@@ -59,6 +65,7 @@ where
59
65
  && self.children == other.children
60
66
  && self.allow_duplicates == other.allow_duplicates
61
67
  && self.is_dragover == other.is_dragover
68
+ && self.disabled == other.disabled
62
69
  }
63
70
  }
64
71
 
@@ -283,20 +290,35 @@ where
283
290
  let column_dropdown = ctx.props().column_dropdown.clone();
284
291
  let exclude = ctx.props().exclude.clone();
285
292
  let on_select = ctx.props().parent.callback(V::create);
293
+ let class = classes!("rrow");
294
+ let is_enabled = true;
295
+
286
296
  html! {
287
- <div ref={&self.elem} class="rrow">
297
+ <div ref={&self.elem} {class}>
288
298
  <div
289
299
  id={ctx.props().name}
290
- ondragover={dragover}
291
- ondragenter={drag_container.dragenter}
292
- ondragleave={drag_container.dragleave}
300
+ ondragover={is_enabled.then_some(dragover)}
301
+ ondragenter={is_enabled.then_some(drag_container.dragenter)}
302
+ ondragleave={is_enabled.then_some(drag_container.dragleave)}
293
303
  ref={drag_container.noderef}
294
- ondrop={drop}
304
+ ondrop={is_enabled.then_some(drop)}
295
305
  >
296
306
  <div class="psp-text-field">
297
307
  <ul class="psp-text-field__input" for={ctx.props().name}>
298
308
  { columns_html }
299
- if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
309
+ if ctx.props().disabled && ctx.props().is_dragover.is_none() {
310
+ <div class="pivot-column">
311
+ <div class="pivot-column-border pivot-column-total">
312
+ <span class="drag-handle icon" />
313
+ <TypeIcon ty={ColumnType::Integer} />
314
+ <span class="column_name">{ "TOTAL" }</span>
315
+ </div>
316
+ <span
317
+ class="toggle-mode is_column_active"
318
+ onmousedown={ctx.props().parent.callback(move |_| V::close(0))}
319
+ />
320
+ </div>
321
+ } else if ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag) {
300
322
  <EmptyColumn {column_dropdown} {exclude} {on_select} />
301
323
  } else if invalid_drag {
302
324
  <InvalidColumn />
@@ -34,6 +34,9 @@ pub struct ScrollPanelProps {
34
34
  #[prop_or_default]
35
35
  pub viewport_ref: Option<NodeRef>,
36
36
 
37
+ #[prop_or_default]
38
+ pub initial_width: Option<f64>,
39
+
37
40
  #[prop_or_default]
38
41
  pub class: Classes,
39
42
 
@@ -52,6 +55,9 @@ pub struct ScrollPanelProps {
52
55
  #[prop_or_default]
53
56
  pub on_resize: Option<Rc<PubSub<()>>>,
54
57
 
58
+ #[prop_or_default]
59
+ pub on_auto_width: Callback<f64>,
60
+
55
61
  #[prop_or_default]
56
62
  pub on_dimensions_reset: Option<Rc<PubSub<()>>>,
57
63
 
@@ -125,7 +131,7 @@ impl Component for ScrollPanel {
125
131
  Self {
126
132
  viewport_ref: Default::default(),
127
133
  viewport_height: 0f64,
128
- viewport_width: 0f64,
134
+ viewport_width: ctx.props().initial_width.unwrap_or_default(),
129
135
  content_window: None,
130
136
  needs_rerender: true,
131
137
  total_height,
@@ -150,6 +156,7 @@ impl Component for ScrollPanel {
150
156
 
151
157
  self.viewport_height = rect.height() - 8.0;
152
158
  self.viewport_width = self.viewport_width.max(rect.width() - 6.0);
159
+ ctx.props().on_auto_width.emit(self.viewport_width);
153
160
  re_render
154
161
  },
155
162
  ScrollPanelMsg::CalculateWindowContent => self.calculate_window_content(ctx),