@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
@@ -13,8 +13,7 @@
13
13
  use std::collections::HashSet;
14
14
  use std::rc::Rc;
15
15
 
16
- use perspective_client::config::*;
17
- use perspective_client::utils::PerspectiveResultExt;
16
+ use perspective_client::config::{ViewConfig, *};
18
17
  use perspective_js::utils::ApiFuture;
19
18
  use yew::prelude::*;
20
19
 
@@ -22,23 +21,38 @@ use super::InPlaceColumn;
22
21
  use super::filter_column::*;
23
22
  use super::pivot_column::*;
24
23
  use super::sort_column::*;
24
+ use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPortal};
25
25
  use crate::components::containers::dragdrop_list::*;
26
+ use crate::components::containers::select::{Select, SelectItem};
27
+ use crate::components::filter_dropdown::{FilterDropDownElement, FilterDropDownPortal};
26
28
  use crate::components::style::LocalStyle;
27
- use crate::custom_elements::{ColumnDropDownElement, FilterDropDownElement};
29
+ use crate::css;
28
30
  use crate::dragdrop::*;
29
- use crate::model::*;
30
31
  use crate::renderer::*;
32
+ use crate::session::drag_drop_update::*;
31
33
  use crate::session::*;
32
34
  use crate::utils::*;
33
- use crate::{PerspectiveProperties, css};
34
35
 
35
- #[derive(Properties, PerspectiveProperties!)]
36
+ #[derive(Clone, Properties)]
36
37
  pub struct ConfigSelectorProps {
37
38
  pub onselect: Callback<()>,
38
39
 
39
40
  #[prop_or_default]
40
41
  pub ondragenter: Callback<()>,
41
42
 
43
+ /// Current view config threaded as a value prop so that config changes
44
+ /// (group_by, sort, filter, etc.) trigger re-renders via normal prop
45
+ /// diffing rather than a PubSub `view_created` subscription.
46
+ pub view_config: PtrEqRc<ViewConfig>,
47
+ /// Column currently being dragged — threaded to show `dragdrop-highlight`
48
+ /// without subscribing to `dragstart_received`/`dragend_received`.
49
+ pub drag_column: Option<String>,
50
+ /// Session metadata snapshot — threaded from `SessionProps`.
51
+ pub metadata: SessionMetadataRc,
52
+
53
+ /// Selected theme name, threaded for PortalModal consumers.
54
+ pub selected_theme: Option<String>,
55
+
42
56
  // State
43
57
  pub session: Session,
44
58
  pub renderer: Renderer,
@@ -46,30 +60,31 @@ pub struct ConfigSelectorProps {
46
60
  }
47
61
 
48
62
  impl PartialEq for ConfigSelectorProps {
49
- fn eq(&self, _other: &Self) -> bool {
50
- false
63
+ fn eq(&self, other: &Self) -> bool {
64
+ self.view_config == other.view_config
65
+ && self.drag_column == other.drag_column
66
+ && self.metadata == other.metadata
67
+ && self.selected_theme == other.selected_theme
51
68
  }
52
69
  }
53
70
 
54
71
  #[derive(Debug)]
55
72
  pub enum ConfigSelectorMsg {
56
- DragStart,
57
- DragEnd,
58
73
  DragOver(usize, DragTarget),
59
74
  DragLeave(DragTarget),
60
75
  Drop(String, DragTarget, DragEffect, usize),
61
76
  Close(usize, DragTarget),
62
77
  SetFilterValue(usize, String),
63
78
  TransposePivots,
64
- ViewCreated,
65
79
  New(DragTarget, InPlaceColumn),
80
+ UpdateGroupRollupMode(GroupRollupMode),
66
81
  }
67
82
 
68
83
  #[derive(Clone)]
69
84
  pub struct ConfigSelector {
70
85
  filter_dropdown: FilterDropDownElement,
71
86
  column_dropdown: ColumnDropDownElement,
72
- _subscriptions: [Rc<Subscription>; 4],
87
+ _subscriptions: [Rc<Subscription>; 1],
73
88
  }
74
89
 
75
90
  impl Component for ConfigSelector {
@@ -77,12 +92,6 @@ impl Component for ConfigSelector {
77
92
  type Properties = ConfigSelectorProps;
78
93
 
79
94
  fn create(ctx: &Context<Self>) -> Self {
80
- let cb = ctx.link().callback(|_| ConfigSelectorMsg::DragStart);
81
- let drag_sub = Rc::new(ctx.props().dragdrop.dragstart_received.add_listener(cb));
82
-
83
- let cb = ctx.link().callback(|_| ConfigSelectorMsg::DragEnd);
84
- let dragend_sub = Rc::new(ctx.props().dragdrop.dragend_received.add_listener(cb));
85
-
86
95
  let cb = ctx
87
96
  .link()
88
97
  .callback(|x: (String, DragTarget, DragEffect, usize)| {
@@ -90,12 +99,9 @@ impl Component for ConfigSelector {
90
99
  });
91
100
  let drop_sub = Rc::new(ctx.props().dragdrop.drop_received.add_listener(cb));
92
101
 
93
- let cb = ctx.link().callback(|_| ConfigSelectorMsg::ViewCreated);
94
- let view_sub = Rc::new(ctx.props().session.view_created.add_listener(cb));
95
-
96
102
  let filter_dropdown = FilterDropDownElement::new(ctx.props().session.clone());
97
103
  let column_dropdown = ColumnDropDownElement::new(ctx.props().session.clone());
98
- let _subscriptions = [drop_sub, view_sub, drag_sub, dragend_sub];
104
+ let _subscriptions = [drop_sub];
99
105
  Self {
100
106
  filter_dropdown,
101
107
  column_dropdown,
@@ -105,8 +111,6 @@ impl Component for ConfigSelector {
105
111
 
106
112
  fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
107
113
  match msg {
108
- ConfigSelectorMsg::DragStart | ConfigSelectorMsg::ViewCreated => true,
109
- ConfigSelectorMsg::DragEnd => true,
110
114
  ConfigSelectorMsg::DragOver(index, action) => {
111
115
  let should_render = ctx.props().dragdrop.notify_drag_enter(action, index);
112
116
  if should_render {
@@ -119,7 +123,7 @@ impl Component for ConfigSelector {
119
123
  true
120
124
  },
121
125
  ConfigSelectorMsg::Close(index, DragTarget::Sort) => {
122
- let mut sort = ctx.props().session.get_view_config().sort.clone();
126
+ let mut sort = ctx.props().view_config.sort.clone();
123
127
  sort.remove(index);
124
128
  let sort = Some(sort);
125
129
  let config = ViewConfigUpdate {
@@ -127,59 +131,121 @@ impl Component for ConfigSelector {
127
131
  ..ViewConfigUpdate::default()
128
132
  };
129
133
 
130
- ctx.props()
131
- .update_and_render(config)
132
- .map(ApiFuture::spawn)
133
- .unwrap_or_log();
134
+ {
135
+ let session = ctx.props().session.clone();
136
+ let renderer = ctx.props().renderer.clone();
137
+ if session.update_view_config(config).is_ok() {
138
+ ApiFuture::spawn(async move {
139
+ renderer.apply_pending_plugin()?;
140
+ renderer.draw(session.validate().await?.create_view()).await
141
+ });
142
+ }
143
+ }
134
144
 
135
145
  ctx.props().onselect.emit(());
136
146
  false
137
147
  },
138
- ConfigSelectorMsg::Close(index, DragTarget::GroupBy) => {
139
- let mut group_by = ctx.props().session.get_view_config().group_by.clone();
140
- group_by.remove(index);
148
+ ConfigSelectorMsg::UpdateGroupRollupMode(mode) => {
141
149
  let config = ViewConfigUpdate {
142
- group_by: Some(group_by),
150
+ group_rollup_mode: Some(mode),
143
151
  ..ViewConfigUpdate::default()
144
152
  };
145
153
 
146
- ctx.props()
147
- .update_and_render(config)
148
- .map(ApiFuture::spawn)
149
- .unwrap_or_log();
154
+ {
155
+ let session = ctx.props().session.clone();
156
+ let renderer = ctx.props().renderer.clone();
157
+ if session.update_view_config(config).is_ok() {
158
+ ApiFuture::spawn(async move {
159
+ renderer.apply_pending_plugin()?;
160
+ renderer.draw(session.validate().await?.create_view()).await
161
+ });
162
+ }
163
+ }
150
164
 
151
- ctx.props().onselect.emit(());
152
165
  false
153
166
  },
167
+ ConfigSelectorMsg::Close(index, DragTarget::GroupBy) => {
168
+ if ctx.props().view_config.group_rollup_mode == GroupRollupMode::Total {
169
+ let requirements = ctx.props().renderer.metadata();
170
+
171
+ let rollup_features = ctx
172
+ .props()
173
+ .metadata
174
+ .get_features()
175
+ .map(|x| x.get_group_rollup_modes())
176
+ .unwrap();
177
+
178
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
179
+
180
+ ctx.link()
181
+ .send_message(ConfigSelectorMsg::UpdateGroupRollupMode(
182
+ group_rollups.first().cloned().unwrap(),
183
+ ));
184
+ false
185
+ } else {
186
+ let mut group_by = ctx.props().view_config.group_by.clone();
187
+ group_by.remove(index);
188
+ let config = ViewConfigUpdate {
189
+ group_by: Some(group_by),
190
+ ..ViewConfigUpdate::default()
191
+ };
192
+
193
+ {
194
+ let session = ctx.props().session.clone();
195
+ let renderer = ctx.props().renderer.clone();
196
+ if session.update_view_config(config).is_ok() {
197
+ ApiFuture::spawn(async move {
198
+ renderer.apply_pending_plugin()?;
199
+ renderer.draw(session.validate().await?.create_view()).await
200
+ });
201
+ }
202
+ }
203
+
204
+ ctx.props().onselect.emit(());
205
+ false
206
+ }
207
+ },
154
208
  ConfigSelectorMsg::Close(index, DragTarget::SplitBy) => {
155
- let mut split_by = ctx.props().session.get_view_config().split_by.clone();
209
+ let mut split_by = ctx.props().view_config.split_by.clone();
156
210
  split_by.remove(index);
157
211
  let config = ViewConfigUpdate {
158
212
  split_by: Some(split_by),
159
213
  ..ViewConfigUpdate::default()
160
214
  };
161
215
 
162
- ctx.props()
163
- .update_and_render(config)
164
- .map(ApiFuture::spawn)
165
- .unwrap_or_log();
216
+ {
217
+ let session = ctx.props().session.clone();
218
+ let renderer = ctx.props().renderer.clone();
219
+ if session.update_view_config(config).is_ok() {
220
+ ApiFuture::spawn(async move {
221
+ renderer.apply_pending_plugin()?;
222
+ renderer.draw(session.validate().await?.create_view()).await
223
+ });
224
+ }
225
+ }
166
226
 
167
227
  ctx.props().onselect.emit(());
168
228
  false
169
229
  },
170
230
  ConfigSelectorMsg::Close(index, DragTarget::Filter) => {
171
231
  self.filter_dropdown.hide().unwrap();
172
- let mut filter = ctx.props().session.get_view_config().filter.clone();
232
+ let mut filter = ctx.props().view_config.filter.clone();
173
233
  filter.remove(index);
174
234
  let config = ViewConfigUpdate {
175
235
  filter: Some(filter),
176
236
  ..ViewConfigUpdate::default()
177
237
  };
178
238
 
179
- ctx.props()
180
- .update_and_render(config)
181
- .map(ApiFuture::spawn)
182
- .unwrap_or_log();
239
+ {
240
+ let session = ctx.props().session.clone();
241
+ let renderer = ctx.props().renderer.clone();
242
+ if session.update_view_config(config).is_ok() {
243
+ ApiFuture::spawn(async move {
244
+ renderer.apply_pending_plugin()?;
245
+ renderer.draw(session.validate().await?.create_view()).await
246
+ });
247
+ }
248
+ }
183
249
 
184
250
  ctx.props().onselect.emit(());
185
251
  false
@@ -188,18 +254,31 @@ impl Component for ConfigSelector {
188
254
  ConfigSelectorMsg::Drop(column, action, effect, index)
189
255
  if action != DragTarget::Active =>
190
256
  {
191
- let update = ctx.props().session.create_drag_drop_update(
257
+ let col_type = ctx
258
+ .props()
259
+ .metadata
260
+ .get_column_table_type(column.as_str())
261
+ .unwrap();
262
+ let update = ctx.props().view_config.create_drag_drop_update(
192
263
  column,
264
+ col_type,
193
265
  index,
194
266
  action,
195
267
  effect,
196
268
  &ctx.props().renderer.metadata(),
269
+ ctx.props().metadata.get_features().unwrap(),
197
270
  );
198
271
 
199
- ctx.props()
200
- .update_and_render(update)
201
- .map(ApiFuture::spawn)
202
- .unwrap_or_log();
272
+ {
273
+ let session = ctx.props().session.clone();
274
+ let renderer = ctx.props().renderer.clone();
275
+ if session.update_view_config(update).is_ok() {
276
+ ApiFuture::spawn(async move {
277
+ renderer.apply_pending_plugin()?;
278
+ renderer.draw(session.validate().await?.create_view()).await
279
+ });
280
+ }
281
+ }
203
282
 
204
283
  ctx.props().onselect.emit(());
205
284
  false
@@ -211,7 +290,7 @@ impl Component for ConfigSelector {
211
290
  },
212
291
  ConfigSelectorMsg::Drop(..) => false,
213
292
  ConfigSelectorMsg::TransposePivots => {
214
- let mut view_config = ctx.props().session.get_view_config().clone();
293
+ let mut view_config = (*ctx.props().view_config).clone();
215
294
  std::mem::swap(&mut view_config.group_by, &mut view_config.split_by);
216
295
 
217
296
  let update = ViewConfigUpdate {
@@ -220,15 +299,22 @@ impl Component for ConfigSelector {
220
299
  ..ViewConfigUpdate::default()
221
300
  };
222
301
 
223
- ctx.props()
224
- .update_and_render(update)
225
- .map(ApiFuture::spawn)
226
- .unwrap_or_log();
302
+ {
303
+ let session = ctx.props().session.clone();
304
+ let renderer = ctx.props().renderer.clone();
305
+ if session.update_view_config(update).is_ok() {
306
+ ApiFuture::spawn(async move {
307
+ renderer.apply_pending_plugin()?;
308
+ renderer.draw(session.validate().await?.create_view()).await
309
+ });
310
+ }
311
+ }
227
312
  ctx.props().onselect.emit(());
228
313
  false
229
314
  },
315
+
230
316
  ConfigSelectorMsg::SetFilterValue(index, input) => {
231
- let mut filter = ctx.props().session.get_view_config().filter.clone();
317
+ let mut filter = ctx.props().view_config.filter.clone();
232
318
 
233
319
  // TODO Can't special case these - need to make this part of the
234
320
  // Features API.
@@ -258,47 +344,65 @@ impl Component for ConfigSelector {
258
344
  }
259
345
  };
260
346
 
261
- ctx.props()
262
- .update_and_render(update)
263
- .map(ApiFuture::spawn)
264
- .unwrap_or_log();
347
+ {
348
+ let session = ctx.props().session.clone();
349
+ let renderer = ctx.props().renderer.clone();
350
+ if session.update_view_config(update).is_ok() {
351
+ ApiFuture::spawn(async move {
352
+ renderer.apply_pending_plugin()?;
353
+ renderer.draw(session.validate().await?.create_view()).await
354
+ });
355
+ }
356
+ }
265
357
 
266
358
  false
267
359
  },
268
360
  ConfigSelectorMsg::New(DragTarget::GroupBy, InPlaceColumn::Column(col)) => {
269
- let mut view_config = ctx.props().session.get_view_config().clone();
361
+ let mut view_config = (*ctx.props().view_config).clone();
270
362
  view_config.group_by.push(col);
271
363
  let update = ViewConfigUpdate {
272
364
  group_by: Some(view_config.group_by),
273
365
  ..ViewConfigUpdate::default()
274
366
  };
275
367
 
276
- ctx.props()
277
- .update_and_render(update)
278
- .map(ApiFuture::spawn)
279
- .unwrap_or_log();
368
+ {
369
+ let session = ctx.props().session.clone();
370
+ let renderer = ctx.props().renderer.clone();
371
+ if session.update_view_config(update).is_ok() {
372
+ ApiFuture::spawn(async move {
373
+ renderer.apply_pending_plugin()?;
374
+ renderer.draw(session.validate().await?.create_view()).await
375
+ });
376
+ }
377
+ }
280
378
 
281
379
  ctx.props().onselect.emit(());
282
380
  false
283
381
  },
284
382
  ConfigSelectorMsg::New(DragTarget::SplitBy, InPlaceColumn::Column(col)) => {
285
- let mut view_config = ctx.props().session.get_view_config().clone();
383
+ let mut view_config = (*ctx.props().view_config).clone();
286
384
  view_config.split_by.push(col);
287
385
  let update = ViewConfigUpdate {
288
386
  split_by: Some(view_config.split_by),
289
387
  ..ViewConfigUpdate::default()
290
388
  };
291
389
 
292
- ctx.props()
293
- .update_and_render(update)
294
- .map(ApiFuture::spawn)
295
- .unwrap_or_log();
390
+ {
391
+ let session = ctx.props().session.clone();
392
+ let renderer = ctx.props().renderer.clone();
393
+ if session.update_view_config(update).is_ok() {
394
+ ApiFuture::spawn(async move {
395
+ renderer.apply_pending_plugin()?;
396
+ renderer.draw(session.validate().await?.create_view()).await
397
+ });
398
+ }
399
+ }
296
400
 
297
401
  ctx.props().onselect.emit(());
298
402
  false
299
403
  },
300
404
  ConfigSelectorMsg::New(DragTarget::Filter, InPlaceColumn::Column(column)) => {
301
- let mut view_config = ctx.props().session.get_view_config().clone();
405
+ let mut view_config = (*ctx.props().view_config).clone();
302
406
  let op = ctx.props().default_op(column.as_str()).unwrap_or_default();
303
407
  view_config.filter.push(Filter::new(
304
408
  &column,
@@ -311,32 +415,44 @@ impl Component for ConfigSelector {
311
415
  ..ViewConfigUpdate::default()
312
416
  };
313
417
 
314
- ctx.props()
315
- .update_and_render(update)
316
- .map(ApiFuture::spawn)
317
- .unwrap_or_log();
418
+ {
419
+ let session = ctx.props().session.clone();
420
+ let renderer = ctx.props().renderer.clone();
421
+ if session.update_view_config(update).is_ok() {
422
+ ApiFuture::spawn(async move {
423
+ renderer.apply_pending_plugin()?;
424
+ renderer.draw(session.validate().await?.create_view()).await
425
+ });
426
+ }
427
+ }
318
428
 
319
429
  ctx.props().onselect.emit(());
320
430
  false
321
431
  },
322
432
  ConfigSelectorMsg::New(DragTarget::Sort, InPlaceColumn::Column(col)) => {
323
- let mut view_config = ctx.props().session.get_view_config().clone();
433
+ let mut view_config = (*ctx.props().view_config).clone();
324
434
  view_config.sort.push(Sort(col, SortDir::Asc));
325
435
  let update = ViewConfigUpdate {
326
436
  sort: Some(view_config.sort),
327
437
  ..ViewConfigUpdate::default()
328
438
  };
329
439
 
330
- ctx.props()
331
- .update_and_render(update)
332
- .map(ApiFuture::spawn)
333
- .unwrap_or_log();
440
+ {
441
+ let session = ctx.props().session.clone();
442
+ let renderer = ctx.props().renderer.clone();
443
+ if session.update_view_config(update).is_ok() {
444
+ ApiFuture::spawn(async move {
445
+ renderer.apply_pending_plugin()?;
446
+ renderer.draw(session.validate().await?.create_view()).await
447
+ });
448
+ }
449
+ }
334
450
 
335
451
  ctx.props().onselect.emit(());
336
452
  false
337
453
  },
338
454
  ConfigSelectorMsg::New(DragTarget::GroupBy, InPlaceColumn::Expression(col)) => {
339
- let mut view_config = ctx.props().session.get_view_config().clone();
455
+ let mut view_config = (*ctx.props().view_config).clone();
340
456
  view_config.group_by.push(col.name.as_ref().to_owned());
341
457
  view_config.expressions.insert(&col);
342
458
  let update = ViewConfigUpdate {
@@ -345,16 +461,22 @@ impl Component for ConfigSelector {
345
461
  ..ViewConfigUpdate::default()
346
462
  };
347
463
 
348
- ctx.props()
349
- .update_and_render(update)
350
- .map(ApiFuture::spawn)
351
- .unwrap_or_log();
464
+ {
465
+ let session = ctx.props().session.clone();
466
+ let renderer = ctx.props().renderer.clone();
467
+ if session.update_view_config(update).is_ok() {
468
+ ApiFuture::spawn(async move {
469
+ renderer.apply_pending_plugin()?;
470
+ renderer.draw(session.validate().await?.create_view()).await
471
+ });
472
+ }
473
+ }
352
474
 
353
475
  ctx.props().onselect.emit(());
354
476
  false
355
477
  },
356
478
  ConfigSelectorMsg::New(DragTarget::SplitBy, InPlaceColumn::Expression(col)) => {
357
- let mut view_config = ctx.props().session.get_view_config().clone();
479
+ let mut view_config = (*ctx.props().view_config).clone();
358
480
  view_config.split_by.push(col.name.as_ref().to_owned());
359
481
  view_config.expressions.insert(&col);
360
482
  let update = ViewConfigUpdate {
@@ -363,16 +485,22 @@ impl Component for ConfigSelector {
363
485
  ..ViewConfigUpdate::default()
364
486
  };
365
487
 
366
- ctx.props()
367
- .update_and_render(update)
368
- .map(ApiFuture::spawn)
369
- .unwrap_or_log();
488
+ {
489
+ let session = ctx.props().session.clone();
490
+ let renderer = ctx.props().renderer.clone();
491
+ if session.update_view_config(update).is_ok() {
492
+ ApiFuture::spawn(async move {
493
+ renderer.apply_pending_plugin()?;
494
+ renderer.draw(session.validate().await?.create_view()).await
495
+ });
496
+ }
497
+ }
370
498
 
371
499
  ctx.props().onselect.emit(());
372
500
  false
373
501
  },
374
502
  ConfigSelectorMsg::New(DragTarget::Filter, InPlaceColumn::Expression(col)) => {
375
- let mut view_config = ctx.props().session.get_view_config().clone();
503
+ let mut view_config = (*ctx.props().view_config).clone();
376
504
  let column = col.name.as_ref();
377
505
  view_config.filter.push(Filter::new(
378
506
  column,
@@ -389,16 +517,22 @@ impl Component for ConfigSelector {
389
517
  ..ViewConfigUpdate::default()
390
518
  };
391
519
 
392
- ctx.props()
393
- .update_and_render(update)
394
- .map(ApiFuture::spawn)
395
- .unwrap_or_log();
520
+ {
521
+ let session = ctx.props().session.clone();
522
+ let renderer = ctx.props().renderer.clone();
523
+ if session.update_view_config(update).is_ok() {
524
+ ApiFuture::spawn(async move {
525
+ renderer.apply_pending_plugin()?;
526
+ renderer.draw(session.validate().await?.create_view()).await
527
+ });
528
+ }
529
+ }
396
530
 
397
531
  ctx.props().onselect.emit(());
398
532
  false
399
533
  },
400
534
  ConfigSelectorMsg::New(DragTarget::Sort, InPlaceColumn::Expression(col)) => {
401
- let mut view_config = ctx.props().session.get_view_config().clone();
535
+ let mut view_config = (*ctx.props().view_config).clone();
402
536
  view_config
403
537
  .sort
404
538
  .push(Sort(col.name.as_ref().to_owned(), SortDir::Asc));
@@ -409,10 +543,16 @@ impl Component for ConfigSelector {
409
543
  ..ViewConfigUpdate::default()
410
544
  };
411
545
 
412
- ctx.props()
413
- .update_and_render(update)
414
- .map(ApiFuture::spawn)
415
- .unwrap_or_log();
546
+ {
547
+ let session = ctx.props().session.clone();
548
+ let renderer = ctx.props().renderer.clone();
549
+ if session.update_view_config(update).is_ok() {
550
+ ApiFuture::spawn(async move {
551
+ renderer.apply_pending_plugin()?;
552
+ renderer.draw(session.validate().await?.create_view()).await
553
+ });
554
+ }
555
+ }
416
556
 
417
557
  ctx.props().onselect.emit(());
418
558
  false
@@ -434,124 +574,163 @@ impl Component for ConfigSelector {
434
574
  session,
435
575
  ..
436
576
  } = ctx.props();
437
- let config = session.get_view_config();
577
+ let config = &ctx.props().view_config;
438
578
  let transpose = ctx.link().callback(|_| ConfigSelectorMsg::TransposePivots);
439
579
  let column_dropdown = self.column_dropdown.clone();
440
- let class = if dragdrop.get_drag_column().is_some() {
441
- "dragdrop-highlight"
442
- } else {
443
- ""
444
- };
580
+ let mut class = classes!();
581
+
582
+ if ctx.props().drag_column.is_some() {
583
+ class.push("dragdrop-highlight");
584
+ }
585
+
586
+ if config.group_rollup_mode == GroupRollupMode::Total {
587
+ class.push("group-rollup-mode-total");
588
+ }
445
589
 
446
590
  let dragend = Callback::from({
447
591
  let dragdrop = dragdrop.clone();
448
592
  move |_event| dragdrop.notify_drag_end()
449
593
  });
450
594
 
451
- let metadata = session.metadata();
595
+ let metadata = &ctx.props().metadata;
452
596
  let features = metadata.get_features().unwrap();
597
+ let requirements = renderer.metadata();
598
+ let on_group_rollup_mode = ctx
599
+ .link()
600
+ .callback(ConfigSelectorMsg::UpdateGroupRollupMode);
601
+
602
+ let rollup_features = metadata
603
+ .get_features()
604
+ .map(|x| x.get_group_rollup_modes())
605
+ .unwrap();
606
+
607
+ let group_rollups = requirements.get_group_rollups(&rollup_features);
453
608
 
454
609
  html! {
455
- <div slot="top_panel" id="top_panel" {class} ondragend={dragend}>
456
- <LocalStyle href={css!("config-selector")} />
457
- if !config.group_by.is_empty() && config.split_by.is_empty() {
458
- <span
459
- id="transpose_button"
460
- class="rrow centered"
461
- title="Transpose Pivots"
462
- onmousedown={transpose.clone()}
463
- />
464
- }
465
- if features.group_by {
466
- <GroupBySelector
467
- name="group_by"
468
- parent={ctx.link().clone()}
469
- column_dropdown={column_dropdown.clone()}
470
- exclude={config.group_by.iter().cloned().collect::<HashSet<_>>()}
471
- is_dragover={ctx.props().dragdrop.is_dragover(DragTarget::GroupBy)}
472
- {dragdrop}
473
- >
474
- { for config.group_by.iter().map(|group_by| {
610
+ <>
611
+ <div slot="top_panel" id="top_panel" {class} ondragend={dragend}>
612
+ <LocalStyle href={css!("config-selector")} />
613
+ <div class="pivot_controls">
614
+ if group_rollups.len() > 1 {
615
+ <Select<GroupRollupMode>
616
+ id="group_rollup_mode_selector"
617
+ wrapper_class="group_rollup_wrapper"
618
+ is_autosize=true
619
+ values={Rc::new(
620
+ group_rollups
621
+ .iter()
622
+ .map(|x| SelectItem::Option(*x))
623
+ .collect(),
624
+ )}
625
+ selected={config.group_rollup_mode}
626
+ on_select={on_group_rollup_mode}
627
+ />
628
+ }
629
+ if !config.group_by.is_empty() && config.split_by.is_empty() {
630
+ <span
631
+ id="transpose_button"
632
+ class="rrow centered"
633
+ title="Transpose Pivots"
634
+ onmousedown={transpose.clone()}
635
+ />
636
+ }
637
+ </div>
638
+ if features.group_by {
639
+ <GroupBySelector
640
+ name="group_by"
641
+ disabled={config.group_rollup_mode == GroupRollupMode::Total}
642
+ parent={ctx.link().clone()}
643
+ column_dropdown={column_dropdown.clone()}
644
+ exclude={config.group_by.iter().cloned().collect::<HashSet<_>>()}
645
+ is_dragover={ctx.props().dragdrop.is_dragover(DragTarget::GroupBy)}
646
+ {dragdrop}
647
+ >
648
+ { for config.group_by.iter().map(|group_by| {
475
649
  html_nested! {
476
650
  <PivotColumn
477
651
  action={DragTarget::GroupBy}
478
652
  column={group_by.clone()}
653
+ metadata={metadata.clone()}
479
654
  {dragdrop}
480
- {session}
655
+ opt_session={session}
481
656
  >
482
657
  </PivotColumn>
483
658
  }
484
659
  }) }
485
- </GroupBySelector>
486
- }
487
- if features.split_by {
488
- if !config.split_by.is_empty() {
489
- <span
490
- id="transpose_button"
491
- class="rrow centered"
492
- title="Transpose Pivots"
493
- onmousedown={transpose}
494
- />
660
+ </GroupBySelector>
495
661
  }
496
- <SplitBySelector
497
- name="split_by"
498
- parent={ctx.link().clone()}
499
- column_dropdown={column_dropdown.clone()}
500
- exclude={config.split_by.iter().cloned().collect::<HashSet<_>>()}
501
- is_dragover={dragdrop.is_dragover(DragTarget::SplitBy)}
502
- {dragdrop}
503
- >
504
- { for config.split_by.iter().map(|split_by| {
662
+ if features.split_by {
663
+ if !config.split_by.is_empty() {
664
+ <div class="pivot_controls">
665
+ <span
666
+ id="transpose_button"
667
+ class="rrow centered"
668
+ title="Transpose Pivots"
669
+ onmousedown={transpose}
670
+ />
671
+ </div>
672
+ }
673
+ <SplitBySelector
674
+ name="split_by"
675
+ parent={ctx.link().clone()}
676
+ column_dropdown={column_dropdown.clone()}
677
+ exclude={config.split_by.iter().cloned().collect::<HashSet<_>>()}
678
+ is_dragover={dragdrop.is_dragover(DragTarget::SplitBy)}
679
+ {dragdrop}
680
+ >
681
+ { for config.split_by.iter().map(|split_by| {
505
682
  html_nested! {
506
683
  <PivotColumn
507
684
  action={ DragTarget::SplitBy }
508
685
  column={ split_by.clone() }
509
-
510
- {dragdrop}
511
- {session}>
686
+ metadata={metadata.clone()}
687
+ {dragdrop}
688
+ opt_session={session}>
512
689
  </PivotColumn>
513
690
  }
514
691
  }) }
515
- </SplitBySelector>
516
- }
517
- if features.sort {
518
- <SortSelector
519
- name="sort"
520
- allow_duplicates=true
521
- parent={ctx.link().clone()}
522
- column_dropdown={column_dropdown.clone()}
523
- exclude={config.sort.iter().map(|x| x.0.clone()).collect::<HashSet<_>>()}
524
- is_dragover={dragdrop.is_dragover(DragTarget::Sort).map(|(index, name)| {
692
+ </SplitBySelector>
693
+ }
694
+ if features.sort {
695
+ <SortSelector
696
+ name="sort"
697
+ allow_duplicates=true
698
+ parent={ctx.link().clone()}
699
+ column_dropdown={column_dropdown.clone()}
700
+ exclude={config.sort.iter().map(|x| x.0.clone()).collect::<HashSet<_>>()}
701
+ is_dragover={dragdrop.is_dragover(DragTarget::Sort).map(|(index, name)| {
525
702
  (index, Sort(name, SortDir::Asc))
526
703
  })}
527
- {dragdrop}
528
- >
529
- { for config.sort.iter().enumerate().map(|(idx, sort)| {
704
+ {dragdrop}
705
+ >
706
+ { for config.sort.iter().enumerate().map(|(idx, sort)| {
530
707
  html_nested! {
531
708
  <SortColumn
532
709
  idx={ idx }
533
710
  sort={ sort.clone() }
711
+ view_config={config.clone()}
712
+ metadata={metadata.clone()}
534
713
  {dragdrop}
535
714
  {renderer}
536
715
  {session}>
537
716
  </SortColumn>
538
717
  }
539
718
  }) }
540
- </SortSelector>
541
- }
542
- if !features.filter_ops.is_empty() {
543
- <FilterSelector
544
- name="filter"
545
- allow_duplicates=true
546
- parent={ctx.link().clone()}
547
- {column_dropdown}
548
- exclude={config.filter.iter().map(|x| x.column().to_string()).collect::<HashSet<_>>()}
549
- is_dragover={dragdrop.is_dragover(DragTarget::Filter).map(|(index, name)| {
719
+ </SortSelector>
720
+ }
721
+ if !features.filter_ops.is_empty() {
722
+ <FilterSelector
723
+ name="filter"
724
+ allow_duplicates=true
725
+ parent={ctx.link().clone()}
726
+ {column_dropdown}
727
+ exclude={config.filter.iter().map(|x| x.column().to_string()).collect::<HashSet<_>>()}
728
+ is_dragover={dragdrop.is_dragover(DragTarget::Filter).map(|(index, name)| {
550
729
  (index, Filter::new(&name, "", FilterTerm::Scalar(Scalar::Null)))
551
730
  })}
552
- {dragdrop}
553
- >
554
- { for config.filter.iter().enumerate().map(|(idx, filter)| {
731
+ {dragdrop}
732
+ >
733
+ { for config.filter.iter().enumerate().map(|(idx, filter)| {
555
734
  let filter_keydown = ctx.link()
556
735
  .callback(move |txt| ConfigSelectorMsg::SetFilterValue(idx, txt));
557
736
 
@@ -561,24 +740,34 @@ impl Component for ConfigSelector {
561
740
  filter_dropdown={ &self.filter_dropdown }
562
741
  filter={ filter.clone() }
563
742
  on_keydown={ filter_keydown }
743
+ view_config={config.clone()}
744
+ metadata={metadata.clone()}
564
745
  {dragdrop}
565
746
  {renderer}
566
747
  {session}>
567
748
  </FilterColumn>
568
749
  }
569
750
  }) }
570
- </FilterSelector>
571
- }
572
- </div>
751
+ </FilterSelector>
752
+ }
753
+ </div>
754
+ <ColumnDropDownPortal
755
+ element={self.column_dropdown.clone()}
756
+ theme={ctx.props().selected_theme.clone().unwrap_or_default()}
757
+ />
758
+ <FilterDropDownPortal
759
+ element={self.filter_dropdown.clone()}
760
+ theme={ctx.props().selected_theme.clone().unwrap_or_default()}
761
+ />
762
+ </>
573
763
  }
574
764
  }
575
765
  }
576
766
 
577
767
  impl ConfigSelectorProps {
578
768
  fn default_op(&self, column: &str) -> Option<String> {
579
- let metadata = self.session.metadata();
580
- let features = metadata.get_features()?;
581
- let col_type = metadata.get_column_table_type(column)?;
769
+ let features = self.metadata.get_features()?;
770
+ let col_type = self.metadata.get_column_table_type(column)?;
582
771
  let first = features.default_op(col_type)?;
583
772
  Some(first.to_string())
584
773
  }