@perspective-dev/viewer 5.0.0 → 5.2.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 (213) hide show
  1. package/dist/cdn/perspective-viewer.js +9 -9
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -1
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/docs/perspective-docs.json +1 -0
  24. package/dist/esm/column-format.d.ts +19 -28
  25. package/dist/esm/perspective-viewer.d.ts +2 -0
  26. package/dist/esm/perspective-viewer.inline.js +8 -8
  27. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  28. package/dist/esm/perspective-viewer.js +9 -9
  29. package/dist/esm/perspective-viewer.js.map +4 -4
  30. package/dist/esm/providers.d.ts +79 -0
  31. package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
  32. package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
  33. package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
  34. package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
  35. package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
  36. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
  37. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
  38. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
  39. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
  40. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
  41. package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
  42. package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
  43. package/dist/esm/ts-rs/Notation.d.ts +17 -0
  44. package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
  45. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +43 -1
  46. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
  47. package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
  48. package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
  49. package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
  50. package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
  51. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
  52. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
  53. package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
  54. package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
  55. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
  56. package/dist/esm/ts-rs/Unit.d.ts +1 -0
  57. package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
  58. package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
  59. package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
  60. package/dist/esm/ts-rs/ViewerConfig.d.ts +43 -1
  61. package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
  62. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +72 -0
  63. package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
  64. package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
  65. package/dist/esm/ts-rs/WindowSpec.d.ts +47 -0
  66. package/dist/esm/ts-rs/Windows.d.ts +10 -0
  67. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +7 -6
  68. package/dist/wasm/perspective-viewer.d.ts +222 -26
  69. package/dist/wasm/perspective-viewer.js +378 -125
  70. package/dist/wasm/perspective-viewer.wasm +0 -0
  71. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -15
  72. package/package.json +2 -1
  73. package/src/css/_viewer_bundle.css +1 -0
  74. package/src/css/column-selector.css +9 -0
  75. package/src/css/column-settings-panel.css +67 -2
  76. package/src/css/column-style.css +4 -3
  77. package/src/css/config-selector.css +60 -4
  78. package/src/css/containers/context-menu.css +12 -6
  79. package/src/css/containers/tabs.css +2 -2
  80. package/src/css/form/code-editor.css +2 -3
  81. package/src/css/form/mirrored-textarea.css +81 -0
  82. package/src/css/viewer.css +336 -0
  83. package/src/rust/agent/client/mod.rs +19 -0
  84. package/src/rust/agent/client/protocol.rs +320 -0
  85. package/src/rust/agent/client/transport.rs +287 -0
  86. package/src/rust/agent/client/turn.rs +174 -0
  87. package/src/rust/agent/config.rs +159 -0
  88. package/src/rust/agent/docs.rs +293 -0
  89. package/src/rust/agent/markdown/block.rs +509 -0
  90. package/src/rust/agent/markdown/entity.rs +52 -0
  91. package/src/rust/agent/markdown/events.rs +71 -0
  92. package/src/rust/agent/markdown/inline.rs +497 -0
  93. package/src/rust/agent/markdown/mod.rs +466 -0
  94. package/src/rust/agent/mod.rs +23 -0
  95. package/src/rust/agent/preamble.md +16 -0
  96. package/src/rust/agent/runtime.rs +201 -0
  97. package/src/rust/agent/slot.rs +236 -0
  98. package/src/rust/agent/tools.rs +1110 -0
  99. package/src/rust/components/chat_panel.rs +289 -0
  100. package/src/rust/components/column_selector/active_column.rs +21 -27
  101. package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
  102. package/src/rust/components/column_selector/config_selector.rs +83 -8
  103. package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
  104. package/src/rust/components/column_selector/inactive_column.rs +8 -1
  105. package/src/rust/components/column_selector.rs +27 -3
  106. package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
  107. package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
  108. package/src/rust/components/column_settings_sidebar.rs +163 -16
  109. package/src/rust/components/containers/dragdrop_list.rs +110 -3
  110. package/src/rust/components/containers/sidebar.rs +99 -22
  111. package/src/rust/components/containers/split_panel.rs +6 -0
  112. package/src/rust/components/editable_header.rs +2 -0
  113. package/src/rust/components/form/code_editor.rs +14 -23
  114. package/src/rust/components/form/debug.rs +8 -5
  115. package/src/rust/components/form/mirrored_textarea.rs +165 -0
  116. package/src/rust/components/form/mod.rs +1 -0
  117. package/src/rust/components/main_panel.rs +5 -0
  118. package/src/rust/components/mod.rs +3 -0
  119. package/src/rust/components/panel_menu.rs +7 -0
  120. package/src/rust/components/panel_tab.rs +5 -0
  121. package/src/rust/components/portal.rs +7 -0
  122. package/src/rust/components/settings_panel.rs +80 -2
  123. package/src/rust/components/viewer/msg.rs +2 -0
  124. package/src/rust/components/viewer/panels.rs +9 -14
  125. package/src/rust/components/viewer/render.rs +14 -0
  126. package/src/rust/components/viewer/settings.rs +33 -1
  127. package/src/rust/components/viewer.rs +2 -0
  128. package/src/rust/components/window_editor.rs +985 -0
  129. package/src/rust/config/datetime_column_style/custom.rs +17 -0
  130. package/src/rust/config/datetime_column_style/simple.rs +10 -0
  131. package/src/rust/config/number_string_format.rs +33 -0
  132. package/src/rust/config/options.rs +18 -0
  133. package/src/rust/config/plugin_static_config.rs +120 -5
  134. package/src/rust/config/viewer_config.rs +220 -1
  135. package/src/rust/config/workspace_config.rs +7 -6
  136. package/src/rust/custom_elements/viewer.rs +209 -18
  137. package/src/rust/custom_events.rs +29 -0
  138. package/src/rust/lib.rs +17 -1
  139. package/src/rust/presentation/column_locator.rs +16 -1
  140. package/src/rust/presentation.rs +8 -0
  141. package/src/rust/queries/columns_iter_set.rs +7 -1
  142. package/src/rust/queries/get_viewer_config.rs +5 -1
  143. package/src/rust/renderer/dispatch.rs +85 -58
  144. package/src/rust/renderer.rs +34 -1
  145. package/src/rust/session/column_defaults_update.rs +18 -0
  146. package/src/rust/session/drag_drop_update.rs +18 -0
  147. package/src/rust/session/metadata.rs +64 -9
  148. package/src/rust/session/replace_expression_update.rs +2 -0
  149. package/src/rust/session.rs +7 -5
  150. package/src/rust/tasks/create_panel.rs +18 -8
  151. package/src/rust/tasks/edit_window.rs +155 -0
  152. package/src/rust/tasks/mod.rs +2 -0
  153. package/src/rust/tasks/restore_panel.rs +34 -17
  154. package/src/rust/tasks/sync_update_panels.rs +7 -23
  155. package/src/rust/utils/browser/dragdrop.rs +12 -0
  156. package/src/rust/utils/browser/tests/debounce.rs +49 -0
  157. package/src/rust/utils/debounce.rs +76 -27
  158. package/src/rust/workspace.rs +112 -20
  159. package/src/svg/pin-icon.svg +5 -0
  160. package/src/themes/icons.css +1 -0
  161. package/src/themes/intl/de.css +23 -0
  162. package/src/themes/intl/es.css +23 -0
  163. package/src/themes/intl/fr.css +23 -0
  164. package/src/themes/intl/ja.css +23 -0
  165. package/src/themes/intl/pt.css +23 -0
  166. package/src/themes/intl/zh.css +23 -0
  167. package/src/themes/intl.css +26 -1
  168. package/src/ts/column-format.ts +67 -46
  169. package/src/ts/perspective-viewer.ts +2 -0
  170. package/src/ts/providers.ts +96 -0
  171. package/src/ts/ts-rs/CompactDisplay.ts +3 -0
  172. package/src/ts/ts-rs/CurrencyCode.ts +3 -0
  173. package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
  174. package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
  175. package/src/ts/ts-rs/CurrencySign.ts +3 -0
  176. package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
  177. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
  178. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
  179. package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
  180. package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
  181. package/src/ts/ts-rs/FormatMode.ts +3 -0
  182. package/src/ts/ts-rs/FormatUnit.ts +3 -0
  183. package/src/ts/ts-rs/Notation.ts +11 -0
  184. package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
  185. package/src/ts/ts-rs/PanelViewerConfig.ts +48 -1
  186. package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
  187. package/src/ts/ts-rs/RestoreOptions.ts +20 -0
  188. package/src/ts/ts-rs/RoundingMode.ts +3 -0
  189. package/src/ts/ts-rs/RoundingPriority.ts +3 -0
  190. package/src/ts/ts-rs/SignDisplay.ts +3 -0
  191. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
  192. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
  193. package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
  194. package/src/ts/ts-rs/StringColorMode.ts +3 -0
  195. package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
  196. package/src/ts/ts-rs/Unit.ts +3 -0
  197. package/src/ts/ts-rs/UnitDisplay.ts +3 -0
  198. package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
  199. package/src/ts/ts-rs/UseGrouping.ts +3 -0
  200. package/src/ts/ts-rs/ViewerConfig.ts +48 -1
  201. package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
  202. package/src/ts/ts-rs/ViewerConfigUpdate.ts +81 -2
  203. package/src/ts/ts-rs/WindowSort.ts +9 -0
  204. package/src/ts/ts-rs/WindowSortDir.ts +8 -0
  205. package/src/ts/ts-rs/WindowSpec.ts +47 -0
  206. package/src/ts/ts-rs/Windows.ts +10 -0
  207. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -6
  208. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline0.js +0 -0
  209. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline1.js +0 -0
  210. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline2.js +0 -0
  211. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline3.js +0 -0
  212. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline4.js +0 -0
  213. /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline5.js +0 -0
@@ -43,6 +43,10 @@ pub struct InactiveColumnProps {
43
43
  #[prop_or_default]
44
44
  pub is_expression: bool,
45
45
 
46
+ /// Whether this column is a window column.
47
+ #[prop_or_default]
48
+ pub is_window: bool,
49
+
46
50
  /// Session metadata snapshot — threaded from `SessionProps`.
47
51
  pub metadata: SessionMetadataRc,
48
52
 
@@ -72,6 +76,7 @@ impl PartialEq for InactiveColumnProps {
72
76
  && self.name == rhs.name
73
77
  && self.is_editing == rhs.is_editing
74
78
  && self.is_expression == rhs.is_expression
79
+ && self.is_window == rhs.is_window
75
80
  && self.metadata == rhs.metadata
76
81
  && self.view_config == rhs.view_config
77
82
  }
@@ -148,6 +153,7 @@ impl Component for InactiveColumn {
148
153
  .callback(|event: MouseEvent| MouseEnter(event.which() == 0));
149
154
 
150
155
  let is_expression = ctx.props().is_expression;
156
+ let is_window = ctx.props().is_window;
151
157
 
152
158
  let mut is_active_class = ctx.props().renderer.metadata().select_mode.css();
153
159
  is_active_class.push("shift-alt-icon");
@@ -179,7 +185,8 @@ impl Component for InactiveColumn {
179
185
  name={ctx.props().name.clone()}
180
186
  on_open_expr_panel={&ctx.props().on_open_expr_panel}
181
187
  {is_expression}
182
- is_disabled={!is_expression}
188
+ {is_window}
189
+ is_disabled={!(is_expression || is_window)}
183
190
  is_editing={ctx.props().is_editing}
184
191
  />
185
192
  </div>
@@ -13,6 +13,7 @@
13
13
  mod active_column;
14
14
  mod add_expression_button;
15
15
  mod aggregate_selector;
16
+ mod column_selector_column_row;
16
17
  mod config_selector;
17
18
  mod empty_column;
18
19
  mod expr_edit_button;
@@ -25,6 +26,7 @@ mod sort_column;
25
26
  use std::iter::*;
26
27
  use std::rc::Rc;
27
28
 
29
+ pub use column_selector_column_row::*;
28
30
  pub use empty_column::*;
29
31
  pub use invalid_column::*;
30
32
  use perspective_client::config::ViewConfig;
@@ -40,6 +42,7 @@ use super::containers::scroll_panel::*;
40
42
  use super::containers::split_panel::{Orientation, SplitPanel};
41
43
  use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPortal};
42
44
  use crate::components::containers::scroll_panel_item::ScrollPanelItem;
45
+ use crate::config::PluginStaticConfig;
43
46
  use crate::presentation::{ColumnLocator, DragDropContainer, Presentation};
44
47
  use crate::queries::{ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet};
45
48
  use crate::renderer::*;
@@ -59,6 +62,10 @@ pub struct ColumnSelectorProps {
59
62
  /// Value props threaded from root's `SessionProps` / `RendererProps`.
60
63
  pub has_table: Option<TableLoadState>,
61
64
  pub named_column_count: usize,
65
+
66
+ /// The ACTIVE plugin's declared contract — see the identically named
67
+ /// prop on `SettingsPanelProps`.
68
+ pub plugin_static_config: Rc<PluginStaticConfig>,
62
69
  pub view_config: PtrEqRc<ViewConfig>,
63
70
  pub drag_column: Option<String>,
64
71
 
@@ -101,6 +108,7 @@ impl PartialEq for ColumnSelectorProps {
101
108
  self.selected_column == rhs.selected_column
102
109
  && self.has_table == rhs.has_table
103
110
  && self.named_column_count == rhs.named_column_count
111
+ && self.plugin_static_config == rhs.plugin_static_config
104
112
  && self.view_config == rhs.view_config
105
113
  && self.drag_column == rhs.drag_column
106
114
  && self.metadata == rhs.metadata
@@ -344,6 +352,7 @@ impl Component for ColumnSelector {
344
352
  drag_column={ctx.props().drag_column.clone()}
345
353
  metadata={metadata.clone()}
346
354
  selected_theme={ctx.props().selected_theme.clone()}
355
+ plugin_static_config={ctx.props().plugin_static_config.clone()}
347
356
  {presentation}
348
357
  {renderer}
349
358
  {session}
@@ -367,7 +376,9 @@ impl Component for ColumnSelector {
367
376
  let column_dropdown = self.column_dropdown.clone();
368
377
  let is_editing = matches!(
369
378
  &ctx.props().selected_column,
370
- Some(ColumnLocator::Table(x)) | Some(ColumnLocator::Expression(x))
379
+ Some(ColumnLocator::Table(x))
380
+ | Some(ColumnLocator::Expression(x))
381
+ | Some(ColumnLocator::Window(x))
371
382
  if x == &key );
372
383
 
373
384
  // Compute metadata-derived props here so that changes to
@@ -392,10 +403,15 @@ impl Component for ColumnSelector {
392
403
  .map(|n| metadata.is_column_expression(n))
393
404
  .unwrap_or(false);
394
405
 
406
+ let is_window = name
407
+ .get_name()
408
+ .map(|n| metadata.is_column_window(n))
409
+ .unwrap_or(false);
410
+
395
411
  let can_render_styles =
396
412
  name.get_name().is_some() && renderer.can_render_column_styles();
397
413
 
398
- let show_edit_btn = is_expression || can_render_styles;
414
+ let show_edit_btn = is_expression || is_window || can_render_styles;
399
415
  let on_open_expr_panel = &ctx.props().on_open_expr_panel;
400
416
  html_nested! {
401
417
  <ScrollPanelItem {key} {size_hint}>
@@ -405,6 +421,7 @@ impl Component for ColumnSelector {
405
421
  {is_aggregated}
406
422
  {is_editing}
407
423
  {is_expression}
424
+ {is_window}
408
425
  {show_edit_btn}
409
426
  {col_type}
410
427
  view_config={config.clone()}
@@ -425,12 +442,18 @@ impl Component for ColumnSelector {
425
442
 
426
443
  let mut inactive_children: Vec<_> = columns_iter
427
444
  .expression()
445
+ .chain(columns_iter.window())
428
446
  .chain(columns_iter.inactive())
429
447
  .enumerate()
430
448
  .map(|(idx, vc)| {
431
449
  let selected_column = ctx.props().selected_column.as_ref();
432
- let is_editing = matches!(selected_column, Some(ColumnLocator::Expression(x)) if x.as_str() == vc.name);
450
+ let is_editing = matches!(
451
+ selected_column,
452
+ Some(ColumnLocator::Expression(x)) | Some(ColumnLocator::Window(x))
453
+ if x.as_str() == vc.name
454
+ );
433
455
  let is_expression = metadata.is_column_expression(vc.name);
456
+ let is_window = metadata.is_column_window(vc.name);
434
457
  html_nested! {
435
458
  <ScrollPanelItem key={vc.name} size_hint=28.0>
436
459
  <InactiveColumn
@@ -439,6 +462,7 @@ impl Component for ColumnSelector {
439
462
  name={vc.name.to_owned()}
440
463
  {is_editing}
441
464
  {is_expression}
465
+ {is_window}
442
466
  view_config={config.clone()}
443
467
  metadata={metadata.clone()}
444
468
  onselect={&onselect}
@@ -326,7 +326,8 @@ pub fn StyleTab(props: &StyleTabProps) -> Html {
326
326
  },
327
327
  };
328
328
 
329
- Some(html! { <fieldset class="style-control">{ component }</fieldset> })
329
+ let key = format!("{}::{}", props.column_name, keys.join("+"));
330
+ Some(html! { <fieldset class="style-control" {key}>{ component }</fieldset> })
330
331
  })
331
332
  .collect_vec()
332
333
  })
@@ -0,0 +1,34 @@
1
+ // ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2
+ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
3
+ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
4
+ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
5
+ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
6
+ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7
+ // ┃ Copyright (c) 2017, the Perspective Authors. ┃
8
+ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9
+ // ┃ This file is part of the Perspective library, distributed under the terms ┃
10
+ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
+ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
+
13
+ use yew::prelude::*;
14
+
15
+ use super::save_settings::{SaveSettings, SaveSettingsProps};
16
+ use crate::components::window_editor::{WindowEditor, WindowEditorProps};
17
+
18
+ #[derive(PartialEq, Properties, Clone)]
19
+ pub struct WindowTabProps {
20
+ pub editor: WindowEditorProps,
21
+ pub save_section: SaveSettingsProps,
22
+ }
23
+
24
+ #[function_component]
25
+ pub fn WindowTab(p: &WindowTabProps) -> Html {
26
+ html! {
27
+ <div id="window-tab">
28
+ <div class="tab-section" id="window-editor-section">
29
+ <WindowEditor ..p.editor.clone() />
30
+ </div>
31
+ <div class="tab-section"><SaveSettings ..p.save_section.clone() /></div>
32
+ </div>
33
+ }
34
+ }
@@ -13,12 +13,13 @@ mod attributes_tab;
13
13
 
14
14
  mod save_settings;
15
15
  pub(crate) mod style_tab;
16
+ mod window_tab;
16
17
 
17
18
  use std::rc::Rc;
18
19
 
19
20
  use derivative::Derivative;
20
21
  use itertools::Itertools;
21
- use perspective_client::config::{ColumnType, Expression, ViewConfig};
22
+ use perspective_client::config::{ColumnType, Expression, ViewConfig, WindowSpec};
22
23
  use perspective_client::utils::PerspectiveResultExt;
23
24
  use yew::{Callback, Component, Html, Properties, html, props};
24
25
 
@@ -27,15 +28,19 @@ use self::style_tab::StyleTabProps;
27
28
  use crate::components::column_settings_sidebar::attributes_tab::AttributesTab;
28
29
  use crate::components::column_settings_sidebar::save_settings::SaveSettingsProps;
29
30
  use crate::components::column_settings_sidebar::style_tab::StyleTab;
31
+ use crate::components::column_settings_sidebar::window_tab::{WindowTab, WindowTabProps};
30
32
  use crate::components::containers::sidebar::Sidebar;
31
33
  use crate::components::containers::tab_list::TabList;
32
34
  use crate::components::editable_header::EditableHeaderProps;
33
35
  use crate::components::expression_editor::ExpressionEditorProps;
34
36
  use crate::components::type_icon::TypeIconType;
37
+ use crate::components::window_editor::WindowEditorProps;
35
38
  use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation};
36
39
  use crate::renderer::Renderer;
37
40
  use crate::session::{Session, SessionMetadataRc};
38
- use crate::tasks::{delete_expr, save_expr, update_expr};
41
+ use crate::tasks::{
42
+ delete_expr, delete_window, save_expr, save_window, update_expr, update_window,
43
+ };
39
44
  use crate::utils::PtrEqRc;
40
45
 
41
46
  #[derive(Clone, Derivative, Properties)]
@@ -47,9 +52,22 @@ pub struct ColumnSettingsPanelProps {
47
52
  pub width_override: Option<i32>,
48
53
  pub on_select_tab: Callback<ColumnSettingsTab>,
49
54
 
50
- /// Active plugin name threaded as a value prop so that plugin changes
51
- /// trigger re-initialization via `changed()` rather than a PubSub
52
- /// `render_limits_changed` subscription.
55
+ /// Shared trap-door width across the drawer's Style/Attributes/Window
56
+ /// tabs.
57
+ #[prop_or_default]
58
+ pub auto_width: f64,
59
+
60
+ #[prop_or_default]
61
+ pub on_auto_width: Callback<f64>,
62
+
63
+ /// Whether the drawer is pinned into the layout.
64
+ #[prop_or_default]
65
+ pub is_pinned: bool,
66
+
67
+ #[prop_or_default]
68
+ pub on_toggle_pin: Callback<()>,
69
+
70
+ /// Active plugin name.
53
71
  pub plugin_name: Option<String>,
54
72
 
55
73
  /// Session metadata snapshot — threaded from `SessionProps`.
@@ -75,8 +93,10 @@ pub struct ColumnSettingsPanelProps {
75
93
  pub session: Session,
76
94
  }
77
95
 
78
- impl PartialEq for ColumnSettingsPanelProps {
79
- fn eq(&self, other: &Self) -> bool {
96
+ impl ColumnSettingsPanelProps {
97
+ /// Everything EXCEPT the trap-door `auto_width`: the props whose change
98
+ /// invalidates the drafts that `initialize` rebuilds.
99
+ fn identity_eq(&self, other: &Self) -> bool {
80
100
  self.selected_column == other.selected_column
81
101
  && self.selected_tab == other.selected_tab
82
102
  && self.plugin_name == other.plugin_name
@@ -87,12 +107,21 @@ impl PartialEq for ColumnSettingsPanelProps {
87
107
  }
88
108
  }
89
109
 
110
+ impl PartialEq for ColumnSettingsPanelProps {
111
+ fn eq(&self, other: &Self) -> bool {
112
+ self.identity_eq(other)
113
+ && self.auto_width == other.auto_width
114
+ && self.is_pinned == other.is_pinned
115
+ }
116
+ }
117
+
90
118
  #[derive(Debug)]
91
119
  pub enum ColumnSettingsPanelMsg {
92
120
  SetExprValue(Rc<String>),
93
121
  SetExprValid(bool),
94
122
  SetHeaderValue(Option<String>),
95
123
  SetHeaderValid(bool),
124
+ SetWindowValue(Option<WindowSpec>),
96
125
  SetSelectedTab((usize, ColumnSettingsTab)),
97
126
  OnSaveAttributes(()),
98
127
  OnResetAttributes(()),
@@ -117,6 +146,8 @@ pub struct ColumnSettingsPanel {
117
146
  reset_enabled: bool,
118
147
  save_count: u8,
119
148
  save_enabled: bool,
149
+ initial_window_value: Option<WindowSpec>,
150
+ window_value: Option<WindowSpec>,
120
151
  tabs: Vec<ColumnSettingsTab>,
121
152
  }
122
153
 
@@ -138,6 +169,8 @@ impl Component for ColumnSettingsPanel {
138
169
  reset_count: 0,
139
170
  column_name: "".to_owned(),
140
171
  maybe_ty: None,
172
+ initial_window_value: None,
173
+ window_value: None,
141
174
  tabs: vec![],
142
175
  on_input: Callback::default(),
143
176
  on_save: Callback::default(),
@@ -149,12 +182,15 @@ impl Component for ColumnSettingsPanel {
149
182
  }
150
183
 
151
184
  fn changed(&mut self, ctx: &yew::prelude::Context<Self>, old_props: &Self::Properties) -> bool {
152
- if ctx.props() != old_props {
185
+ // Only reached when props are unequal. Re-`initialize` (which wipes
186
+ // in-progress expression/window drafts) only on IDENTITY changes -
187
+ // a trap-door `auto_width` change re-renders the `Sidebar` sizer
188
+ // alone.
189
+ if !ctx.props().identity_eq(old_props) {
153
190
  self.initialize(ctx);
154
- true
155
- } else {
156
- false
157
191
  }
192
+
193
+ true
158
194
  }
159
195
 
160
196
  fn update(&mut self, ctx: &yew::prelude::Context<Self>, msg: Self::Message) -> bool {
@@ -187,6 +223,15 @@ impl Component for ColumnSettingsPanel {
187
223
  self.save_enabled_effect();
188
224
  true
189
225
  },
226
+ ColumnSettingsPanelMsg::SetWindowValue(val) => {
227
+ if self.window_value != val {
228
+ self.window_value = val;
229
+ self.reset_enabled = true;
230
+ true
231
+ } else {
232
+ false
233
+ }
234
+ },
190
235
  ColumnSettingsPanelMsg::SetSelectedTab((_, val)) => {
191
236
  let rerender = ctx.props().selected_tab != Some(val);
192
237
  ctx.props().on_select_tab.emit(val);
@@ -195,19 +240,58 @@ impl Component for ColumnSettingsPanel {
195
240
  ColumnSettingsPanelMsg::OnResetAttributes(()) => {
196
241
  self.header_value.clone_from(&self.initial_header_value);
197
242
  self.expr_value.clone_from(&self.initial_expr_value);
243
+ self.window_value.clone_from(&self.initial_window_value);
198
244
  self.save_enabled = false;
199
245
  self.reset_enabled = false;
200
246
  self.reset_count += 1;
201
247
  true
202
248
  },
203
249
  ColumnSettingsPanelMsg::OnSaveAttributes(()) => {
250
+ if matches!(ctx.props().selected_tab, Some(ColumnSettingsTab::Window)) {
251
+ if let Some(spec) = self.window_value.clone() {
252
+ let name = self
253
+ .header_value
254
+ .clone()
255
+ .unwrap_or_else(|| self.column_name.clone());
256
+ match &ctx.props().selected_column {
257
+ ColumnLocator::Window(old_name) => update_window(
258
+ &ctx.props().session,
259
+ &ctx.props().renderer,
260
+ &ctx.props().presentation,
261
+ old_name.clone(),
262
+ name,
263
+ spec.clone(),
264
+ ),
265
+ _ => {
266
+ if let Err(err) = save_window(
267
+ &ctx.props().session,
268
+ &ctx.props().renderer,
269
+ &ctx.props().presentation,
270
+ name,
271
+ spec.clone(),
272
+ ) {
273
+ tracing::warn!("{}", err);
274
+ }
275
+ },
276
+ }
277
+
278
+ self.initial_window_value = Some(spec);
279
+ self.initial_header_value.clone_from(&self.header_value);
280
+ self.save_enabled = false;
281
+ self.reset_enabled = false;
282
+ self.save_count += 1;
283
+ }
284
+
285
+ return true;
286
+ }
287
+
204
288
  let new_expr = Expression::new(
205
289
  self.header_value.clone().map(|s| s.into()),
206
290
  (*(self.expr_value)).clone().into(),
207
291
  );
208
292
 
209
293
  match &ctx.props().selected_column {
210
- ColumnLocator::Table(_) => {
294
+ ColumnLocator::Table(_) | ColumnLocator::Window(_) => {
211
295
  tracing::error!("Tried to save non-expression column!")
212
296
  },
213
297
  ColumnLocator::Expression(name) => update_expr(
@@ -244,6 +328,13 @@ impl Component for ColumnSettingsPanel {
244
328
  &self.column_name,
245
329
  )
246
330
  .unwrap_or_log();
331
+ } else if ctx.props().selected_column.is_saved_window() {
332
+ delete_window(
333
+ &ctx.props().session,
334
+ &ctx.props().renderer,
335
+ &self.column_name,
336
+ )
337
+ .unwrap_or_log();
247
338
  }
248
339
 
249
340
  ctx.props().on_close.emit(());
@@ -253,15 +344,24 @@ impl Component for ColumnSettingsPanel {
253
344
  }
254
345
 
255
346
  fn view(&self, ctx: &yew::prelude::Context<Self>) -> Html {
347
+ let is_window_tab = matches!(ctx.props().selected_tab, Some(ColumnSettingsTab::Window));
348
+
349
+ let header_placeholder = if is_window_tab {
350
+ Rc::new(self.column_name.clone())
351
+ } else {
352
+ self.expr_value.clone()
353
+ };
354
+
256
355
  let header_props = props!(EditableHeaderProps {
257
356
  initial_value: self.initial_header_value.clone(),
258
- placeholder: self.expr_value.clone(),
357
+ placeholder: header_placeholder,
259
358
  reset_count: self.reset_count,
260
- editable: ctx.props().selected_column.is_expr()
359
+ editable: (ctx.props().selected_column.is_expr()
261
360
  && matches!(
262
361
  ctx.props().selected_tab,
263
362
  Some(ColumnSettingsTab::Attributes)
264
- ),
363
+ ))
364
+ || (ctx.props().selected_column.is_window_editable() && is_window_tab),
265
365
  update_on_input: true,
266
366
  icon_type: self
267
367
  .maybe_ty
@@ -324,7 +424,26 @@ impl Component for ColumnSettingsPanel {
324
424
 
325
425
  let attrs_tab = AttributesTabProps {
326
426
  expr_editor,
327
- save_section,
427
+ save_section: save_section.clone(),
428
+ };
429
+
430
+ let window_changed = self.window_value != self.initial_window_value
431
+ || self.header_value != self.initial_header_value;
432
+ let window_tab = WindowTabProps {
433
+ editor: WindowEditorProps {
434
+ metadata: ctx.props().metadata.clone(),
435
+ initial: self.initial_window_value.clone(),
436
+ on_change: ctx.link().callback(ColumnSettingsPanelMsg::SetWindowValue),
437
+ reset_count: self.reset_count,
438
+ presentation: ctx.props().presentation.clone(),
439
+ session: ctx.props().session.clone(),
440
+ selected_theme: ctx.props().selected_theme.clone(),
441
+ },
442
+ save_section: SaveSettingsProps {
443
+ save_enabled: self.window_value.is_some() && window_changed && self.header_valid,
444
+ show_danger_zone: ctx.props().selected_column.is_saved_window(),
445
+ ..save_section
446
+ },
328
447
  };
329
448
 
330
449
  let style_tab = StyleTabProps {
@@ -342,6 +461,7 @@ impl Component for ColumnSettingsPanel {
342
461
 
343
462
  let tab_children = self.tabs.iter().map(|tab| match tab {
344
463
  ColumnSettingsTab::Attributes => html! { <AttributesTab ..attrs_tab.clone() /> },
464
+ ColumnSettingsTab::Window => html! { <WindowTab ..window_tab.clone() /> },
345
465
  ColumnSettingsTab::Style => html! { <StyleTab ..style_tab.clone() /> },
346
466
  });
347
467
 
@@ -358,6 +478,10 @@ impl Component for ColumnSettingsPanel {
358
478
  on_close={ctx.props().on_close.clone()}
359
479
  id_prefix="column_settings"
360
480
  width_override={ctx.props().width_override}
481
+ auto_width={ctx.props().auto_width}
482
+ on_auto_width={ctx.props().on_auto_width.clone()}
483
+ is_pinned={ctx.props().is_pinned}
484
+ on_toggle_pin={Some(ctx.props().on_toggle_pin.clone())}
361
485
  selected_tab={selected_tab_idx}
362
486
  {header_props}
363
487
  >
@@ -403,6 +527,16 @@ impl ColumnSettingsPanel {
403
527
  .metadata
404
528
  .locator_view_type(&ctx.props().selected_column);
405
529
 
530
+ // Specs are unnamed (the `windows` map key names them, and the
531
+ // editable header owns naming in the UI), so drafts and saved specs
532
+ // compare directly.
533
+ let initial_window_value = ctx
534
+ .props()
535
+ .selected_column
536
+ .name()
537
+ .and_then(|name| ctx.props().view_config.windows.get(name))
538
+ .cloned();
539
+
406
540
  let tabs = {
407
541
  let mut tabs = vec![];
408
542
  let is_new_expr = ctx.props().selected_column.is_new_expr();
@@ -424,6 +558,17 @@ impl ColumnSettingsPanel {
424
558
  tabs.push(ColumnSettingsTab::Attributes);
425
559
  }
426
560
 
561
+ let supports_windows = ctx
562
+ .props()
563
+ .metadata
564
+ .get_features()
565
+ .map(|x| x.has_window_aggregates())
566
+ .unwrap_or_default();
567
+
568
+ if ctx.props().selected_column.is_window_editable() && supports_windows {
569
+ tabs.push(ColumnSettingsTab::Window);
570
+ }
571
+
427
572
  tabs
428
573
  };
429
574
 
@@ -440,6 +585,8 @@ impl ColumnSettingsPanel {
440
585
  header_value: initial_header_value.clone(),
441
586
  initial_header_value,
442
587
  maybe_ty,
588
+ window_value: initial_window_value.clone(),
589
+ initial_window_value,
443
590
  tabs,
444
591
  header_valid: true,
445
592
  on_input,