@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
@@ -16,9 +16,9 @@ use perspective_client::config::Expression;
16
16
  use web_sys::*;
17
17
  use yew::prelude::*;
18
18
 
19
+ use crate::components::column_dropdown::ColumnDropDownElement;
19
20
  use crate::components::style::LocalStyle;
20
21
  use crate::css;
21
- use crate::custom_elements::ColumnDropDownElement;
22
22
 
23
23
  #[derive(Properties)]
24
24
  pub struct EmptyColumnProps {
@@ -38,7 +38,7 @@ impl PartialEq for EmptyColumnProps {
38
38
  }
39
39
  }
40
40
 
41
- #[derive(Clone, Debug)]
41
+ #[derive(Clone, Debug, PartialEq)]
42
42
  pub enum InPlaceColumn {
43
43
  Column(String),
44
44
  Expression(Expression<'static>),
@@ -27,6 +27,10 @@ pub struct ExprEditButtonProps {
27
27
 
28
28
  /// Is the expression/config panel open?
29
29
  pub is_editing: bool,
30
+
31
+ /// Is the expression/config panel enabled? If not, show an invisible
32
+ /// square in the same dimensions (so the layout does not jump around).
33
+ pub is_disabled: bool,
30
34
  }
31
35
 
32
36
  /// A button that goes into a column-list for a custom expression
@@ -42,11 +46,13 @@ pub fn ExprEditButton(p: &ExprEditButtonProps) -> Html {
42
46
  p.on_open_expr_panel.emit(name)
43
47
  });
44
48
 
45
- let class = if p.is_editing {
49
+ let class = if p.is_disabled {
50
+ "expression-edit-button disabled"
51
+ } else if p.is_editing {
46
52
  "expression-edit-button is-editing"
47
53
  } else {
48
54
  "expression-edit-button"
49
55
  };
50
56
 
51
- html! { <span {onmousedown} {class} /> }
57
+ html! { <span {onmousedown} {class}><span class="icon" /></span> }
52
58
  }
@@ -15,7 +15,6 @@ use std::rc::Rc;
15
15
 
16
16
  use chrono::{Datelike, NaiveDate, TimeZone, Utc};
17
17
  use perspective_client::config::*;
18
- use perspective_client::utils::PerspectiveResultExt;
19
18
  use perspective_js::utils::ApiFuture;
20
19
  use wasm_bindgen::JsCast;
21
20
  use web_sys::*;
@@ -23,23 +22,27 @@ use yew::prelude::*;
23
22
 
24
23
  use crate::components::containers::dragdrop_list::*;
25
24
  use crate::components::containers::select::*;
25
+ use crate::components::filter_dropdown::FilterDropDownElement;
26
26
  use crate::components::style::LocalStyle;
27
27
  use crate::components::type_icon::TypeIcon;
28
- use crate::custom_elements::*;
29
28
  use crate::dragdrop::*;
30
- use crate::model::*;
31
29
  use crate::renderer::*;
32
30
  use crate::session::*;
33
31
  use crate::utils::*;
34
- use crate::*;
32
+ use crate::{css, maybe};
35
33
 
36
- #[derive(Clone, Properties, PerspectiveProperties!)]
34
+ #[derive(Clone, Properties)]
37
35
  pub struct FilterColumnProps {
38
36
  pub filter: Filter,
39
37
  pub idx: usize,
40
38
  pub filter_dropdown: FilterDropDownElement,
41
39
  pub on_keydown: Callback<String>,
42
40
 
41
+ /// Session metadata snapshot — threaded from `SessionProps`.
42
+ pub metadata: SessionMetadataRc,
43
+ /// Current view config threaded as a value prop.
44
+ pub view_config: PtrEqRc<ViewConfig>,
45
+
43
46
  // State
44
47
  pub session: Session,
45
48
  pub renderer: Renderer,
@@ -48,7 +51,11 @@ pub struct FilterColumnProps {
48
51
 
49
52
  impl PartialEq for FilterColumnProps {
50
53
  fn eq(&self, rhs: &Self) -> bool {
51
- self.idx == rhs.idx && self.filter == rhs.filter && self.on_keydown == rhs.on_keydown
54
+ self.idx == rhs.idx
55
+ && self.filter == rhs.filter
56
+ && self.on_keydown == rhs.on_keydown
57
+ && self.metadata == rhs.metadata
58
+ && self.view_config == rhs.view_config
52
59
  }
53
60
  }
54
61
 
@@ -95,7 +102,7 @@ impl Component for FilterColumn {
95
102
 
96
103
  this.filter_ops = Rc::new(
97
104
  maybe! {
98
- Some(get_filter_ops(ctx.props().session(), col_type?)?
105
+ Some(get_filter_ops(&ctx.props().metadata, col_type?)?
99
106
  .into_iter()
100
107
  .map(SelectItem::Option)
101
108
  .collect::<Vec<_>>())
@@ -183,7 +190,7 @@ impl Component for FilterColumn {
183
190
  changed = true;
184
191
  self.filter_ops = Rc::new(
185
192
  maybe! {
186
- Some(get_filter_ops(&ctx.props().session, col_type?)?
193
+ Some(get_filter_ops(&ctx.props().metadata, col_type?)?
187
194
  .into_iter()
188
195
  .map(SelectItem::Option)
189
196
  .collect::<Vec<_>>())
@@ -204,11 +211,7 @@ impl Component for FilterColumn {
204
211
  let idx = ctx.props().idx;
205
212
  let filter = ctx.props().filter.clone();
206
213
  let column = filter.column().to_owned();
207
- let col_type = ctx
208
- .props()
209
- .session
210
- .metadata()
211
- .get_column_table_type(&column);
214
+ let col_type = ctx.props().metadata.get_column_table_type(&column);
212
215
  let select = ctx.link().callback(FilterColumnMsg::FilterOpSelect);
213
216
  let noderef = &self.input_ref;
214
217
  let input = ctx.link().callback({
@@ -347,6 +350,7 @@ impl Component for FilterColumn {
347
350
  >
348
351
  <LocalStyle href={css!("filter-item")} />
349
352
  <div class="pivot-column-border">
353
+ <span class="drag-handle icon" />
350
354
  // <TypeIcon ty={ColumnType::String} />
351
355
  <TypeIcon ty={final_col_type} />
352
356
  <span class="column_name">{ filter.column().to_owned() }</span>
@@ -377,8 +381,7 @@ impl Component for FilterColumn {
377
381
  }
378
382
 
379
383
  /// Get the allowed `FilterOp`s for this filter.
380
- fn get_filter_ops(session: &Session, col_type: ColumnType) -> Option<Vec<String>> {
381
- let metadata = session.metadata();
384
+ fn get_filter_ops(metadata: &SessionMetadata, col_type: ColumnType) -> Option<Vec<String>> {
382
385
  let features = metadata.get_features()?;
383
386
  features
384
387
  .filter_ops
@@ -404,9 +407,7 @@ impl FilterColumnProps {
404
407
 
405
408
  /// Get this filter's type, e.g. the type of the column.
406
409
  fn get_filter_type(&self, filter: &Filter) -> Option<ColumnType> {
407
- self.session
408
- .metadata()
409
- .get_column_table_type(filter.column())
410
+ self.metadata.get_column_table_type(filter.column())
410
411
  }
411
412
 
412
413
  // Get the string value, suitable for the `value` field of a `FilterColumns`'s
@@ -442,7 +443,7 @@ impl FilterColumnProps {
442
443
  /// # Arguments
443
444
  /// - `op` The new `FilterOp`.
444
445
  fn update_filter_op(&self, idx: usize, op: String) {
445
- let mut filter = self.session.get_view_config().filter.clone();
446
+ let mut filter = self.view_config.filter.clone();
446
447
  let filter_column = &mut filter.get_mut(idx).expect("Filter on no column");
447
448
  *filter_column.op_mut() = op;
448
449
  let update = ViewConfigUpdate {
@@ -450,9 +451,14 @@ impl FilterColumnProps {
450
451
  ..ViewConfigUpdate::default()
451
452
  };
452
453
 
453
- self.update_and_render(update)
454
- .map(ApiFuture::spawn)
455
- .unwrap_or_log();
454
+ if self.session.update_view_config(update).is_ok() {
455
+ let session = self.session.clone();
456
+ let renderer = self.renderer.clone();
457
+ ApiFuture::spawn(async move {
458
+ renderer.apply_pending_plugin()?;
459
+ renderer.draw(session.validate().await?.create_view()).await
460
+ });
461
+ }
456
462
  }
457
463
 
458
464
  /// Update the filter value from the string input read from the DOM.
@@ -460,7 +466,7 @@ impl FilterColumnProps {
460
466
  /// # Arguments
461
467
  /// - `val` The new filter value.
462
468
  fn update_filter_input(&self, val: String) {
463
- let mut filters = self.session.get_view_config().filter.clone();
469
+ let mut filters = self.view_config.filter.clone();
464
470
  let filter_column = &mut filters.get_mut(self.idx).expect("Filter on no column");
465
471
 
466
472
  // TODO This belongs in the Features API.
@@ -523,9 +529,14 @@ impl FilterColumnProps {
523
529
  ..ViewConfigUpdate::default()
524
530
  };
525
531
 
526
- self.update_and_render(update)
527
- .map(ApiFuture::spawn)
528
- .unwrap_or_log();
532
+ if self.session.update_view_config(update).is_ok() {
533
+ let session = self.session.clone();
534
+ let renderer = self.renderer.clone();
535
+ ApiFuture::spawn(async move {
536
+ renderer.apply_pending_plugin()?;
537
+ renderer.draw(session.validate().await?.create_view()).await
538
+ });
539
+ }
529
540
  }
530
541
  }
531
542
  }
@@ -12,7 +12,6 @@
12
12
 
13
13
  use itertools::Itertools;
14
14
  use perspective_client::config::*;
15
- use perspective_client::utils::PerspectiveResultExt;
16
15
  use perspective_js::utils::ApiFuture;
17
16
  use web_sys::*;
18
17
  use yew::prelude::*;
@@ -21,14 +20,12 @@ use super::expr_edit_button::*;
21
20
  use crate::components::type_icon::TypeIcon;
22
21
  use crate::dragdrop::*;
23
22
  use crate::js::plugin::*;
24
- use crate::model::*;
25
23
  use crate::presentation::ColumnLocator;
26
24
  use crate::renderer::*;
27
25
  use crate::session::*;
28
26
  use crate::utils::*;
29
- use crate::*;
30
27
 
31
- #[derive(Clone, Properties, PerspectiveProperties!)]
28
+ #[derive(Clone, Properties)]
32
29
  pub struct InactiveColumnProps {
33
30
  /// This column's index in its list.
34
31
  pub idx: usize,
@@ -42,6 +39,18 @@ pub struct InactiveColumnProps {
42
39
  /// Is the expression/config panel open for this column?
43
40
  pub is_editing: bool,
44
41
 
42
+ /// Whether this column is an expression column. Computed by the parent
43
+ /// so that changes to session metadata trigger a re-render via prop diff.
44
+ #[prop_or_default]
45
+ pub is_expression: bool,
46
+
47
+ /// Session metadata snapshot — threaded from `SessionProps`.
48
+ pub metadata: SessionMetadataRc,
49
+
50
+ /// View config snapshot — threaded from parent so we avoid
51
+ /// `session.get_view_config()` calls.
52
+ pub view_config: PtrEqRc<ViewConfig>,
53
+
45
54
  /// `dragend` event`.
46
55
  pub ondragend: Callback<()>,
47
56
 
@@ -58,11 +67,14 @@ pub struct InactiveColumnProps {
58
67
  }
59
68
 
60
69
  impl PartialEq for InactiveColumnProps {
61
- /// Equality for `InactiveColumnProps` determines when it should re-render,
62
- /// which is only when it has changed.
63
- /// TODO Aggregates
64
- fn eq(&self, _rhs: &Self) -> bool {
65
- false
70
+ fn eq(&self, rhs: &Self) -> bool {
71
+ self.idx == rhs.idx
72
+ && self.visible == rhs.visible
73
+ && self.name == rhs.name
74
+ && self.is_editing == rhs.is_editing
75
+ && self.is_expression == rhs.is_expression
76
+ && self.metadata == rhs.metadata
77
+ && self.view_config == rhs.view_config
66
78
  }
67
79
  }
68
80
 
@@ -112,8 +124,7 @@ impl Component for InactiveColumn {
112
124
  fn view(&self, ctx: &Context<Self>) -> Html {
113
125
  let col_type = ctx
114
126
  .props()
115
- .session
116
- .metadata()
127
+ .metadata
117
128
  .get_column_table_type(&ctx.props().name)
118
129
  .unwrap_or(ColumnType::String);
119
130
 
@@ -128,7 +139,7 @@ impl Component for InactiveColumn {
128
139
  move |event: DragEvent| {
129
140
  dragdrop.set_drag_image(&event).unwrap();
130
141
  dragdrop.notify_drag_start(event_name.to_string(), DragEffect::Copy);
131
- MouseLeave(false)
142
+ MouseLeave(true)
132
143
  }
133
144
  });
134
145
 
@@ -137,11 +148,7 @@ impl Component for InactiveColumn {
137
148
  .link()
138
149
  .callback(|event: MouseEvent| MouseEnter(event.which() == 0));
139
150
 
140
- let is_expression = ctx
141
- .props()
142
- .session
143
- .metadata()
144
- .is_column_expression(&ctx.props().name);
151
+ let is_expression = ctx.props().is_expression;
145
152
 
146
153
  let is_active_class = ctx.props().renderer.metadata().mode.css();
147
154
  let mut class = classes!("column-selector-column");
@@ -164,17 +171,17 @@ impl Component for InactiveColumn {
164
171
  {ondragend}
165
172
  >
166
173
  <div class="column-selector-column-border">
174
+ <span class="drag-handle icon" />
167
175
  <TypeIcon ty={col_type} />
168
176
  <span class="column_name">{ ctx.props().name.clone() }</span>
169
177
  <span class="column-selector--spacer" />
170
- if is_expression {
171
- <ExprEditButton
172
- name={ctx.props().name.clone()}
173
- on_open_expr_panel={&ctx.props().on_open_expr_panel}
174
- is_expression=true
175
- is_editing={ctx.props().is_editing}
176
- />
177
- }
178
+ <ExprEditButton
179
+ name={ctx.props().name.clone()}
180
+ on_open_expr_panel={&ctx.props().on_open_expr_panel}
181
+ {is_expression}
182
+ is_disabled={!is_expression}
183
+ is_editing={ctx.props().is_editing}
184
+ />
178
185
  </div>
179
186
  </div>
180
187
  </div>
@@ -191,7 +198,7 @@ impl InactiveColumnProps {
191
198
  /// with respect to `columns`.
192
199
  /// - `shift` whether to toggle or select this column.
193
200
  pub fn activate_column(&self, name: String, shift: bool) {
194
- let mut columns = self.session.get_view_config().columns.clone();
201
+ let mut columns = self.view_config.columns.clone();
195
202
  let max_cols = self
196
203
  .renderer
197
204
  .metadata()
@@ -229,8 +236,13 @@ impl InactiveColumnProps {
229
236
  ..ViewConfigUpdate::default()
230
237
  };
231
238
 
232
- self.update_and_render(config)
233
- .map(ApiFuture::spawn)
234
- .unwrap_or_log();
239
+ if self.session.update_view_config(config).is_ok() {
240
+ let session = self.session.clone();
241
+ let renderer = self.renderer.clone();
242
+ ApiFuture::spawn(async move {
243
+ renderer.apply_pending_plugin()?;
244
+ renderer.draw(session.validate().await?.create_view()).await
245
+ });
246
+ }
235
247
  }
236
248
  }
@@ -15,23 +15,12 @@ use yew::prelude::*;
15
15
  use crate::components::style::LocalStyle;
16
16
  use crate::css;
17
17
 
18
- #[derive(Default)]
19
- pub struct InvalidColumn {}
20
-
21
- impl Component for InvalidColumn {
22
- type Message = ();
23
- type Properties = ();
24
-
25
- fn create(_ctx: &Context<Self>) -> Self {
26
- Self::default()
27
- }
28
-
29
- fn view(&self, _ctx: &Context<Self>) -> Html {
30
- html! {
31
- <div class="pivot-column column-empty column-invalid">
32
- <LocalStyle href={css!("empty-column")} />
33
- <div class="column-invalid-input" />
34
- </div>
35
- }
18
+ #[function_component(InvalidColumn)]
19
+ pub fn invalid_column() -> Html {
20
+ html! {
21
+ <div class="pivot-column column-empty column-invalid">
22
+ <LocalStyle href={css!("empty-column")} />
23
+ <div class="column-invalid-input" />
24
+ </div>
36
25
  }
37
26
  }
@@ -19,24 +19,33 @@ use crate::components::type_icon::TypeIcon;
19
19
  use crate::dragdrop::*;
20
20
  use crate::session::*;
21
21
  use crate::utils::*;
22
- use crate::*;
23
22
 
24
- #[derive(Properties, PerspectiveProperties!)]
23
+ #[derive(Properties)]
25
24
  pub struct PivotColumnProps {
26
25
  /// Column name.
27
26
  pub column: String,
28
27
 
28
+ #[prop_or_default]
29
+ pub column_type: Option<ColumnType>,
30
+
29
31
  /// The drag starte of this column, if applicable.
30
32
  pub action: DragTarget,
31
33
 
34
+ /// Session metadata snapshot — threaded from `SessionProps`.
35
+ #[prop_or_default]
36
+ pub metadata: Option<SessionMetadataRc>,
37
+
32
38
  // State
33
- pub session: Session,
39
+ #[prop_or_default]
40
+ pub opt_session: Option<Session>,
34
41
  pub dragdrop: DragDrop,
35
42
  }
36
43
 
37
44
  impl PartialEq for PivotColumnProps {
38
45
  fn eq(&self, other: &Self) -> bool {
39
- self.column == other.column && self.action == other.action
46
+ self.column == other.column
47
+ && self.action == other.action
48
+ && self.metadata == other.metadata
40
49
  }
41
50
  }
42
51
 
@@ -74,12 +83,13 @@ impl Component for PivotColumn {
74
83
  move |_event| dragdrop.notify_drag_end()
75
84
  });
76
85
 
77
- let col_type = ctx
78
- .props()
79
- .session
80
- .metadata()
81
- .get_column_table_type(&ctx.props().column)
82
- .unwrap_or(ColumnType::Integer);
86
+ let col_type = ctx.props().column_type.unwrap_or_else(|| {
87
+ ctx.props()
88
+ .metadata
89
+ .as_ref()
90
+ .and_then(|x| x.get_column_table_type(&ctx.props().column))
91
+ .unwrap_or(ColumnType::Integer)
92
+ });
83
93
 
84
94
  html! {
85
95
  <div
@@ -89,6 +99,7 @@ impl Component for PivotColumn {
89
99
  ondragend={dragend}
90
100
  >
91
101
  <div class="pivot-column-border">
102
+ <span class="drag-handle icon" />
92
103
  <TypeIcon ty={col_type} />
93
104
  <span class="column_name">{ ctx.props().column.clone() }</span>
94
105
  </div>
@@ -11,7 +11,6 @@
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
13
  use perspective_client::config::*;
14
- use perspective_client::utils::PerspectiveResultExt;
15
14
  use perspective_js::utils::ApiFuture;
16
15
  use web_sys::*;
17
16
  use yew::prelude::*;
@@ -19,17 +18,21 @@ use yew::prelude::*;
19
18
  use crate::components::containers::dragdrop_list::*;
20
19
  use crate::components::type_icon::TypeIcon;
21
20
  use crate::dragdrop::*;
22
- use crate::model::*;
23
21
  use crate::renderer::*;
24
22
  use crate::session::*;
25
23
  use crate::utils::*;
26
- use crate::*;
27
24
 
28
- #[derive(Properties, PerspectiveProperties!)]
25
+ #[derive(Properties)]
29
26
  pub struct SortColumnProps {
30
27
  pub sort: Sort,
31
28
  pub idx: usize,
32
29
 
30
+ /// Session metadata snapshot — threaded from `SessionProps`.
31
+ pub metadata: SessionMetadataRc,
32
+
33
+ /// Current view config — threaded as a value prop.
34
+ pub view_config: PtrEqRc<ViewConfig>,
35
+
33
36
  // State
34
37
  pub session: Session,
35
38
  pub renderer: Renderer,
@@ -38,7 +41,10 @@ pub struct SortColumnProps {
38
41
 
39
42
  impl PartialEq for SortColumnProps {
40
43
  fn eq(&self, other: &Self) -> bool {
41
- self.sort == other.sort && self.idx == other.idx
44
+ self.sort == other.sort
45
+ && self.idx == other.idx
46
+ && self.metadata == other.metadata
47
+ && self.view_config == other.view_config
42
48
  }
43
49
  }
44
50
 
@@ -69,8 +75,8 @@ impl Component for SortColumn {
69
75
  fn update(&mut self, ctx: &Context<Self>, msg: SortColumnMsg) -> bool {
70
76
  match msg {
71
77
  SortColumnMsg::SortDirClick(shift_key) => {
72
- let is_split = ctx.props().session.get_view_config().split_by.is_empty();
73
- let mut sort = ctx.props().session.get_view_config().sort.clone();
78
+ let is_split = ctx.props().view_config.split_by.is_empty();
79
+ let mut sort = ctx.props().view_config.sort.clone();
74
80
  let sort_column = &mut sort.get_mut(ctx.props().idx).expect("Sort on no column");
75
81
  sort_column.1 = sort_column.1.cycle(!is_split, shift_key);
76
82
  let update = ViewConfigUpdate {
@@ -78,10 +84,14 @@ impl Component for SortColumn {
78
84
  ..ViewConfigUpdate::default()
79
85
  };
80
86
 
81
- ctx.props()
82
- .update_and_render(update)
83
- .map(ApiFuture::spawn)
84
- .unwrap_or_log();
87
+ let session = ctx.props().session.clone();
88
+ let renderer = ctx.props().renderer.clone();
89
+ if session.update_view_config(update).is_ok() {
90
+ ApiFuture::spawn(async move {
91
+ renderer.apply_pending_plugin()?;
92
+ renderer.draw(session.validate().await?.create_view()).await
93
+ });
94
+ }
85
95
 
86
96
  false
87
97
  },
@@ -110,8 +120,7 @@ impl Component for SortColumn {
110
120
 
111
121
  let col_type = ctx
112
122
  .props()
113
- .session
114
- .metadata()
123
+ .metadata
115
124
  .get_column_table_type(&ctx.props().sort.0.to_owned())
116
125
  .unwrap_or(ColumnType::Integer);
117
126
 
@@ -123,6 +132,7 @@ impl Component for SortColumn {
123
132
  ondragend={dragend}
124
133
  >
125
134
  <div class="pivot-column-border">
135
+ <span class="drag-handle icon" />
126
136
  <TypeIcon ty={col_type} />
127
137
  // <TypeIcon ty={ColumnType::String} />
128
138
  <span class="column_name">{ ctx.props().sort.0.to_owned() }</span>