@perspective-dev/viewer 5.3.1 → 5.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 (255) hide show
  1. package/dist/cdn/perspective-viewer.js +6 -6
  2. package/dist/cdn/perspective-viewer.js.map +2 -2
  3. package/dist/css/blueprint.css +1 -1
  4. package/dist/css/botanical.css +1 -1
  5. package/dist/css/dracula.css +1 -1
  6. package/dist/css/eggplant.css +1 -1
  7. package/dist/css/gruvbox-dark.css +1 -1
  8. package/dist/css/gruvbox.css +1 -1
  9. package/dist/css/icons.css +1 -1
  10. package/dist/css/intl/ar.css +1 -0
  11. package/dist/css/intl/de.css +1 -1
  12. package/dist/css/intl/es.css +1 -1
  13. package/dist/css/intl/fr.css +1 -1
  14. package/dist/css/intl/ja.css +1 -1
  15. package/dist/css/intl/pt.css +1 -1
  16. package/dist/css/intl/zh.css +1 -1
  17. package/dist/css/intl.css +1 -1
  18. package/dist/css/ledger.css +1 -1
  19. package/dist/css/monokai.css +1 -1
  20. package/dist/css/nord.css +1 -1
  21. package/dist/css/phosphor.css +1 -1
  22. package/dist/css/pro-dark.css +1 -1
  23. package/dist/css/pro.css +1 -1
  24. package/dist/css/solarized-dark.css +1 -1
  25. package/dist/css/solarized.css +1 -1
  26. package/dist/css/themes.css +1 -1
  27. package/dist/css/vaporwave.css +1 -1
  28. package/dist/css/velvet.css +1 -1
  29. package/dist/docs/perspective-docs.json +1 -1
  30. package/dist/esm/extensions.d.ts +0 -5
  31. package/dist/esm/perspective-viewer.inline.js +9 -9
  32. package/dist/esm/perspective-viewer.inline.js.map +3 -3
  33. package/dist/esm/perspective-viewer.js +5 -5
  34. package/dist/esm/perspective-viewer.js.map +2 -2
  35. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  36. package/dist/wasm/perspective-viewer.d.ts +62 -73
  37. package/dist/wasm/perspective-viewer.js +226 -238
  38. package/dist/wasm/perspective-viewer.wasm +0 -0
  39. package/dist/wasm/perspective-viewer.wasm.d.ts +18 -28
  40. package/package.json +7 -7
  41. package/src/css/_context_menu.css +1 -1
  42. package/src/css/_dropdown_menu.css +1 -1
  43. package/src/css/_viewer_bundle.css +7 -8
  44. package/src/css/column-dropdown.css +0 -3
  45. package/src/css/column-selector.css +8 -5
  46. package/src/css/column-settings-panel.css +0 -225
  47. package/src/css/column-style.css +13 -0
  48. package/src/css/config-selector.css +18 -10
  49. package/src/css/filter-dropdown.css +5 -5
  50. package/src/css/form/debug.css +28 -4
  51. package/src/css/function-dropdown.css +5 -5
  52. package/src/css/panel-tab.css +23 -0
  53. package/src/css/plugin-selector.css +5 -5
  54. package/src/css/render-warning.css +1 -1
  55. package/src/css/status-bar.css +52 -21
  56. package/src/css/type-icon.css +1 -3
  57. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  58. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  59. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  60. package/src/css/viewer.css +32 -26
  61. package/src/rust/components/chat_panel.rs +1 -1
  62. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  63. package/src/rust/components/column_dropdown.rs +2 -2
  64. package/src/rust/components/column_selector/active_column.rs +15 -9
  65. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  66. package/src/rust/components/column_selector/config_selector.rs +2 -2
  67. package/src/rust/components/column_selector/filter_column.rs +8 -5
  68. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  69. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  70. package/src/rust/components/column_selector/sort_column.rs +7 -4
  71. package/src/rust/components/column_selector.rs +3 -3
  72. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  73. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +15 -17
  74. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  75. package/src/rust/components/column_settings_sidebar/style_tab.rs +4 -5
  76. package/src/rust/components/column_settings_sidebar.rs +3 -4
  77. package/src/rust/components/copy_dropdown.rs +1 -1
  78. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  79. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  80. package/src/rust/components/datetime_column_style.rs +1 -2
  81. package/src/rust/components/{form/debug.rs → debug_panel.rs} +5 -5
  82. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  83. package/src/rust/components/export_dropdown.rs +1 -1
  84. package/src/rust/components/expression_editor.rs +1 -1
  85. package/src/rust/components/filter_dropdown.rs +2 -2
  86. package/src/rust/components/function_dropdown.rs +2 -2
  87. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  88. package/src/rust/components/main_panel/reconcile.rs +9 -4
  89. package/src/rust/components/main_panel/render.rs +62 -11
  90. package/src/rust/components/main_panel/update.rs +39 -10
  91. package/src/rust/components/main_panel.rs +26 -2
  92. package/src/rust/components/mod.rs +5 -7
  93. package/src/rust/components/new_panel_menu.rs +97 -0
  94. package/src/rust/components/number_series_style.rs +2 -3
  95. package/src/rust/components/panel_menu.rs +61 -87
  96. package/src/rust/components/panel_tab.rs +100 -43
  97. package/src/rust/components/plugin_tab.rs +1 -1
  98. package/src/rust/components/render_warning.rs +2 -2
  99. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  100. package/src/rust/components/settings_panel.rs +2 -2
  101. package/src/rust/components/status_bar.rs +113 -19
  102. package/src/rust/components/status_indicator.rs +5 -14
  103. package/src/rust/components/string_column_style.rs +1 -2
  104. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  105. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  106. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  107. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  108. package/src/rust/components/viewer/msg.rs +43 -30
  109. package/src/rust/components/viewer/panels.rs +50 -19
  110. package/src/rust/components/viewer/render.rs +15 -13
  111. package/src/rust/components/viewer/settings.rs +137 -68
  112. package/src/rust/components/viewer/snapshots.rs +5 -10
  113. package/src/rust/components/viewer/wiring.rs +1 -21
  114. package/src/rust/components/viewer.rs +15 -10
  115. package/src/rust/components/window_editor.rs +8 -9
  116. package/src/rust/config/column_config_schema.rs +2 -27
  117. package/src/rust/config/mod.rs +0 -2
  118. package/src/rust/config/workspace_config.rs +2 -1
  119. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  120. package/src/rust/custom_elements/mod.rs +0 -3
  121. package/src/rust/custom_elements/viewer.rs +0 -18
  122. package/src/rust/js/plugin.rs +0 -86
  123. package/src/rust/lib.rs +12 -15
  124. package/src/rust/presentation/column_locator.rs +3 -7
  125. package/src/rust/presentation.rs +68 -38
  126. package/src/rust/queries/hosted_tables.rs +38 -0
  127. package/src/rust/queries/mod.rs +2 -0
  128. package/src/rust/queries/palette_set.rs +1 -1
  129. package/src/rust/renderer/activate.rs +1 -3
  130. package/src/rust/renderer/dispatch.rs +83 -22
  131. package/src/rust/renderer/limits.rs +24 -5
  132. package/src/rust/renderer/plugin_config.rs +1 -0
  133. package/src/rust/renderer/props.rs +2 -1
  134. package/src/rust/renderer.rs +14 -27
  135. package/src/rust/session/props.rs +4 -3
  136. package/src/rust/session/view_subscription.rs +19 -6
  137. package/src/rust/session.rs +38 -70
  138. package/src/rust/tasks/create_panel.rs +1 -1
  139. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  140. package/src/rust/tasks/eject.rs +1 -0
  141. package/src/rust/tasks/pipeline.rs +0 -60
  142. package/src/rust/tasks/presize_panels.rs +24 -1
  143. package/src/rust/tasks/restore_and_render.rs +0 -17
  144. package/src/rust/tasks/send_column_config.rs +1 -1
  145. package/src/rust/tasks/send_plugin_config.rs +1 -1
  146. package/src/rust/tasks/set_edit_mode.rs +1 -1
  147. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  148. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  149. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  150. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  151. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  152. package/src/rust/{components → ui}/containers/select.rs +0 -6
  153. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  154. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  155. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  156. package/src/rust/{components → ui}/context_menu.rs +8 -13
  157. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  158. package/src/rust/{components → ui}/font_loader.rs +0 -26
  159. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  160. package/src/rust/ui/form/intl_label.rs +81 -0
  161. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  162. package/src/rust/{components → ui}/form/mod.rs +1 -10
  163. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  164. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  165. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  166. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  167. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  168. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  169. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  170. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  171. package/src/rust/ui/mod.rs +56 -0
  172. package/src/rust/ui/modal.rs +61 -0
  173. package/src/rust/{components → ui}/portal.rs +174 -79
  174. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  175. package/src/rust/utils/completion.rs +0 -6
  176. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  177. package/src/rust/utils/mod.rs +2 -0
  178. package/src/rust/workspace.rs +35 -0
  179. package/src/svg/boolean-type.svg +3 -2
  180. package/src/svg/checkbox-checked-icon.svg +4 -7
  181. package/src/svg/checkbox-hover.svg +3 -3
  182. package/src/svg/checkbox-off.svg +2 -3
  183. package/src/svg/checkbox-on.svg +3 -3
  184. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  185. package/src/svg/datagrid-select-column.svg +8 -15
  186. package/src/svg/datagrid-select-region.svg +8 -15
  187. package/src/svg/datagrid-select-row-tree.svg +7 -12
  188. package/src/svg/datagrid-select-row.svg +8 -13
  189. package/src/svg/date-type.svg +4 -23
  190. package/src/svg/drawer-tab-hover.svg +4 -6
  191. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  192. package/src/svg/drawer-tab-invert.svg +3 -5
  193. package/src/svg/drawer-tab.svg +5 -5
  194. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  195. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  196. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  197. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  198. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  199. package/src/svg/new-panel-icon.svg +4 -0
  200. package/src/svg/number-type.svg +3 -3
  201. package/src/svg/radio-hover.svg +3 -3
  202. package/src/svg/radio-off.svg +3 -3
  203. package/src/svg/radio-on.svg +3 -3
  204. package/src/svg/status_ok.svg +3 -26
  205. package/src/svg/string-type.svg +4 -8
  206. package/src/svg/theme-icon.svg +5 -5
  207. package/src/themes/blueprint.css +0 -57
  208. package/src/themes/botanical.css +0 -36
  209. package/src/themes/defaults.css +1 -24
  210. package/src/themes/dracula.css +0 -58
  211. package/src/themes/eggplant.css +0 -58
  212. package/src/themes/gruvbox-dark.css +0 -65
  213. package/src/themes/gruvbox.css +1 -39
  214. package/src/themes/icons.css +1 -8
  215. package/src/themes/intl/ar.css +185 -0
  216. package/src/themes/intl/de.css +45 -35
  217. package/src/themes/intl/es.css +40 -30
  218. package/src/themes/intl/fr.css +39 -29
  219. package/src/themes/intl/ja.css +41 -31
  220. package/src/themes/intl/pt.css +37 -27
  221. package/src/themes/intl/zh.css +47 -37
  222. package/src/themes/intl.css +16 -3
  223. package/src/themes/ledger.css +1 -59
  224. package/src/themes/monokai.css +0 -51
  225. package/src/themes/nord.css +0 -55
  226. package/src/themes/phosphor.css +0 -48
  227. package/src/themes/pro-dark.css +0 -37
  228. package/src/themes/pro.css +3 -23
  229. package/src/themes/solarized-dark.css +0 -31
  230. package/src/themes/solarized.css +1 -24
  231. package/src/themes/vaporwave.css +0 -44
  232. package/src/themes/velvet.css +0 -60
  233. package/src/ts/extensions.ts +0 -13
  234. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  235. package/src/css/plugin-settings-panel.css +0 -171
  236. package/src/rust/components/modal.rs +0 -177
  237. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  238. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  239. package/src/rust/custom_elements/modal.rs +0 -299
  240. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline0.js +0 -0
  241. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline1.js +0 -0
  242. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline2.js +0 -0
  243. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline3.js +0 -0
  244. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline4.js +0 -0
  245. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-adc6f3f79141112b}/inline5.js +0 -0
  246. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  247. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  248. /package/src/css/{containers → ui/containers}/tabs.css +0 -0
  249. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  250. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  251. /package/src/css/{dom → ui/dom}/select.css +0 -0
  252. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  253. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  254. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  255. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -10,25 +10,27 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
+ use std::rc::Rc;
14
+
13
15
  use perspective_client::config::Filter;
14
16
  use wasm_bindgen_futures::spawn_local;
15
17
  use web_sys::*;
16
18
  use yew::prelude::*;
17
19
 
18
20
  use super::status_indicator::StatusIndicator;
19
- use crate::components::containers::select::*;
20
21
  use crate::components::copy_dropdown::CopyDropDownMenu;
21
22
  use crate::components::export_dropdown::ExportDropDownMenu;
22
23
  use crate::components::global_filter_bar::GlobalFilterBar;
23
- use crate::components::portal::PortalModal;
24
- use crate::components::status_bar_counter::StatusBarRowsCounter;
24
+ use crate::components::new_panel_menu::{HostedTables, NewPanelMenu, NewPanelPick, PanelLabels};
25
25
  use crate::components::style::StyleSurface;
26
26
  use crate::config::*;
27
27
  use crate::js::*;
28
28
  use crate::presentation::{Presentation, PresentationProps};
29
+ use crate::queries::fetch_hosted_tables;
29
30
  use crate::renderer::*;
30
31
  use crate::session::*;
31
32
  use crate::tasks::*;
33
+ use crate::ui::{PortalModal, Select, SelectItem};
32
34
  use crate::utils::*;
33
35
  use crate::workspace::Workspace;
34
36
  use crate::*;
@@ -41,9 +43,12 @@ pub struct StatusBarProps {
41
43
  /// Fired when the reset button is clicked.
42
44
  pub on_reset: Callback<bool>,
43
45
 
44
- /// Snapshots threaded from root. Component reads `has_table`, `stats`,
45
- /// `error`, `title` from session_props; `selected_theme`,
46
- /// `available_themes`, `is_workspace` from presentation_props.
46
+ /// The left-anchored "New" action, fired with the [`NewPanelMenu`] pick.
47
+ pub on_new_panel: Callback<NewPanelPick>,
48
+
49
+ /// Snapshots threaded from root. Component reads `has_table`,
50
+ /// `has_table_cells`, `error` from session_props; `selected_theme`,
51
+ /// `available_themes` from presentation_props.
47
52
  pub session_props: SessionProps,
48
53
  pub presentation_props: PresentationProps,
49
54
 
@@ -89,6 +94,18 @@ impl PartialEq for StatusBarProps {
89
94
 
90
95
  pub enum StatusBarMsg {
91
96
  Reset(MouseEvent),
97
+
98
+ /// The "New" button: fetch the hosted-table listing, then open the
99
+ /// dropdown.
100
+ NewPanel,
101
+
102
+ /// The listing resolved: open the dropdown anchored at the button, if
103
+ /// the generation is still the newest.
104
+ OpenNewPanel(u32, HostedTables),
105
+ CloseNewPanel,
106
+
107
+ /// A pick in the "New" dropdown.
108
+ NewPanelPick(NewPanelPick),
92
109
  Export,
93
110
  Copy,
94
111
  CloseExport,
@@ -103,9 +120,17 @@ pub enum StatusBarMsg {
103
120
  pub struct StatusBar {
104
121
  copy_ref: NodeRef,
105
122
  export_ref: NodeRef,
123
+ new_ref: NodeRef,
106
124
  statusbar_ref: NodeRef,
107
125
  copy_target: Option<HtmlElement>,
108
126
  export_target: Option<HtmlElement>,
127
+
128
+ /// The "New" dropdown's anchor and listings while it is open.
129
+ new_menu: Option<(HtmlElement, HostedTables, PanelLabels)>,
130
+
131
+ /// Bumped per "New" click; a resolving fetch opens only if it is still
132
+ /// the newest one.
133
+ new_generation: u32,
109
134
  }
110
135
 
111
136
  impl Component for StatusBar {
@@ -116,9 +141,12 @@ impl Component for StatusBar {
116
141
  Self {
117
142
  copy_ref: NodeRef::default(),
118
143
  export_ref: NodeRef::default(),
144
+ new_ref: NodeRef::default(),
119
145
  statusbar_ref: NodeRef::default(),
120
146
  copy_target: None,
121
147
  export_target: None,
148
+ new_menu: None,
149
+ new_generation: 0,
122
150
  }
123
151
  }
124
152
 
@@ -129,6 +157,54 @@ impl Component for StatusBar {
129
157
  ctx.props().on_reset.emit(all);
130
158
  false
131
159
  },
160
+ StatusBarMsg::NewPanel => {
161
+ self.new_generation = self.new_generation.wrapping_add(1);
162
+ let generation = self.new_generation;
163
+ let workspace = ctx.props().workspace.clone();
164
+ let link = ctx.link().clone();
165
+ ApiFuture::spawn(async move {
166
+ let tables = fetch_hosted_tables(&workspace).await;
167
+ link.send_message(StatusBarMsg::OpenNewPanel(generation, Rc::new(tables)));
168
+ Ok(())
169
+ });
170
+
171
+ false
172
+ },
173
+ StatusBarMsg::OpenNewPanel(generation, tables) => {
174
+ if generation != self.new_generation {
175
+ return false;
176
+ }
177
+
178
+ match self.new_ref.cast::<HtmlElement>() {
179
+ Some(target) => {
180
+ let workspace = &ctx.props().workspace;
181
+ let panels = Rc::new(
182
+ workspace
183
+ .panel_ids()
184
+ .into_iter()
185
+ .filter_map(|id| {
186
+ let panel = workspace.panel(&id)?;
187
+ let title = panel.session.get_title().filter(|t| !t.is_empty());
188
+ Some((id.as_str().to_owned(), title))
189
+ })
190
+ .collect::<Vec<_>>(),
191
+ );
192
+
193
+ self.new_menu = Some((target, tables, panels));
194
+ true
195
+ },
196
+ None => false,
197
+ }
198
+ },
199
+ StatusBarMsg::CloseNewPanel => {
200
+ self.new_menu = None;
201
+ true
202
+ },
203
+ StatusBarMsg::NewPanelPick(pick) => {
204
+ ctx.props().on_new_panel.emit(pick);
205
+ self.new_menu = None;
206
+ true
207
+ },
132
208
  StatusBarMsg::ResetTheme => {
133
209
  update_theme(
134
210
  &ctx.props().renderer,
@@ -187,7 +263,6 @@ impl Component for StatusBar {
187
263
  let has_table = ctx.props().session_props.has_table.clone();
188
264
  let is_errored = ctx.props().session_props.is_errored();
189
265
  let is_settings_open = ctx.props().is_settings_open;
190
- let title = &ctx.props().session_props.title;
191
266
 
192
267
  let mut is_updating_class_name = classes!();
193
268
  if !is_settings_open {
@@ -204,6 +279,9 @@ impl Component for StatusBar {
204
279
  let onexport = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);
205
280
  let oncopy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);
206
281
  let onreset = ctx.link().callback(StatusBarMsg::Reset);
282
+ let onnew = ctx.link().callback(|_: MouseEvent| StatusBarMsg::NewPanel);
283
+ let on_new_select = ctx.link().callback(StatusBarMsg::NewPanelPick);
284
+ let on_close_new = ctx.link().callback(|_| StatusBarMsg::CloseNewPanel);
207
285
 
208
286
  // Project only the *active* panel's plugin toolbar into the shared status
209
287
  // bar. Each panel's toolbar slots into `statusbar-extra-{its-panel-id}`
@@ -216,12 +294,7 @@ impl Component for StatusBar {
216
294
  .map(|id| format!("statusbar-extra-{id}"))
217
295
  .unwrap_or_else(|| "statusbar-extra".to_owned());
218
296
  let is_menu = matches!(has_table, Some(TableLoadState::Loaded)) && is_settings_open;
219
- // The editable title moved to the per-panel `<PanelTab>` headers; the
220
- // status bar no longer hosts an `<input>`, so its `is_active` factor is
221
- // gone. `is_title` now only gates the row counter.
222
- let is_title =
223
- is_menu || ctx.props().presentation_props.is_workspace || title.is_some() || is_errored;
224
-
297
+ let is_panel_bar = !ctx.props().workspace.clients().is_empty();
225
298
  let is_settings =
226
299
  !matches!(has_table, Some(TableLoadState::Loaded)) || is_errored || is_settings_open;
227
300
 
@@ -293,11 +366,16 @@ impl Component for StatusBar {
293
366
  update_count={ctx.props().update_count}
294
367
  session_props={ctx.props().session_props.clone()}
295
368
  />
296
- if is_title {
297
- <StatusBarRowsCounter stats={ctx.props().session_props.stats.clone()} />
369
+ if is_panel_bar {
370
+ <div id="panel-bar" class="section">
371
+ <span ref={&self.new_ref} class="hover-target" onmousedown={onnew}>
372
+ <span id="new_panel" class="button">
373
+ <span class="icon" />
374
+ <span class="icon-label" />
375
+ </span>
376
+ </span>
377
+ </div>
298
378
  }
299
- // Global-filter chips sit between the row stats and the
300
- // menu icons (rendered only when there are filters).
301
379
  if !ctx.props().global_filters.is_empty() {
302
380
  <GlobalFilterBar
303
381
  filters={ctx.props().global_filters.clone()}
@@ -349,9 +427,25 @@ impl Component for StatusBar {
349
427
  </div>
350
428
  }
351
429
  </div>
430
+ <PortalModal
431
+ tag_name="perspective-new-panel-menu"
432
+ sheet={StyleSurface::DropdownMenu.sheet()}
433
+ target={self.new_menu.as_ref().map(|(target, ..)| target.clone())}
434
+ own_focus=true
435
+ on_close={on_close_new}
436
+ theme={ctx.props().presentation_props.selected_theme.clone().unwrap_or_default()}
437
+ >
438
+ if let Some((_, tables, panels)) = &self.new_menu {
439
+ <NewPanelMenu
440
+ tables={Some(tables.clone())}
441
+ panels={panels.clone()}
442
+ callback={on_new_select}
443
+ />
444
+ }
445
+ </PortalModal>
352
446
  <PortalModal
353
447
  tag_name="perspective-copy-menu"
354
- surface={StyleSurface::DropdownMenu}
448
+ sheet={StyleSurface::DropdownMenu.sheet()}
355
449
  target={self.copy_target.clone()}
356
450
  own_focus=true
357
451
  on_close={on_close_copy}
@@ -361,7 +455,7 @@ impl Component for StatusBar {
361
455
  </PortalModal>
362
456
  <PortalModal
363
457
  tag_name="perspective-export-menu"
364
- surface={StyleSurface::DropdownMenu}
458
+ sheet={StyleSurface::DropdownMenu.sheet()}
365
459
  target={self.export_target.clone()}
366
460
  own_focus=true
367
461
  on_close={on_close_export}
@@ -22,18 +22,15 @@ use crate::utils::*;
22
22
 
23
23
  /// Value-prop version: no PubSub subscriptions, no reducer.
24
24
  /// The parent (`StatusBar`) re-renders this component whenever
25
- /// `session_props.error/has_table/stats` or `update_count` change (via
26
- /// root's `UpdateInFlight` / `UpdateSession` messages).
25
+ /// `session_props.error/has_table/has_table_cells` or `update_count` change
26
+ /// (via root's `UpdateInFlight` / `UpdateSession` messages).
27
27
  #[derive(PartialEq, Properties)]
28
28
  pub struct StatusIndicatorProps {
29
29
  pub renderer: Renderer,
30
30
  pub session: Session,
31
- /// Number of in-flight CONFIG-DRIVEN runs (>0 → "updating" spinner) —
32
- /// a level-triggered snapshot of `Session::in_flight_config_runs`
33
- /// (RAII-settled; see `UPDATE_COUNT_REGRESSION_PLAN.md`).
31
+
32
+ /// TODO(texodus): remove this
34
33
  pub update_count: u32,
35
- /// Snapshot of session value props — read for `error`, `has_table`,
36
- /// `stats` to derive the icon state.
37
34
  pub session_props: SessionProps,
38
35
  }
39
36
 
@@ -42,13 +39,7 @@ pub struct StatusIndicatorProps {
42
39
  /// reconnect callback when in an error state.
43
40
  #[function_component]
44
41
  pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
45
- let has_table_cells = props
46
- .session_props
47
- .stats
48
- .as_ref()
49
- .and_then(|s| s.num_table_cells)
50
- .is_some();
51
-
42
+ let has_table_cells = props.session_props.has_table_cells;
52
43
  let state = if let Some(err) = &props.session_props.error {
53
44
  StatusIconState::Errored(
54
45
  err.message(),
@@ -12,9 +12,8 @@
12
12
 
13
13
  use yew::prelude::*;
14
14
 
15
- use super::modal::{ModalLink, SetModalLink};
16
- use crate::components::form::select_enum_field::SelectEnumField;
17
15
  use crate::config::*;
16
+ use crate::ui::{ModalLink, SelectEnumField, SetModalLink};
18
17
  use crate::utils::WeakScope;
19
18
 
20
19
  /// Format-only widget for [`String`] columns. Renders the `format` enum
@@ -25,15 +25,44 @@
25
25
  //!
26
26
  //! ```ignore
27
27
  //! html! {
28
- //! <StyleProvider root={host} surface={StyleSurface::Viewer}>
28
+ //! <StyleProvider root={host} sheet={StyleSurface::Viewer.sheet()}>
29
29
  //! <h1>{ "I am styled!" }</h1>
30
30
  //! </StyleProvider>
31
31
  //! }
32
32
  //! ```
33
33
 
34
- mod style_provider;
34
+ /// The CSS bundle a shadow tree adopts; each variant maps to one build-time
35
+ /// `src/css/_*.css` entrypoint.
36
+ #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
37
+ pub enum StyleSurface {
38
+ /// The `<perspective-viewer>` ShadowRoot.
39
+ #[default]
40
+ Viewer,
41
+ /// Column-name completion dropdown (inverted color scheme).
42
+ ColumnDropdown,
43
+ /// Filter-value completion dropdown.
44
+ FilterDropdown,
45
+ /// Expression function-completion dropdown.
46
+ FunctionDropdown,
47
+ /// Copy / Export menu (non-inverted color scheme).
48
+ DropdownMenu,
49
+ /// Panel context menu (right-click a panel).
50
+ ContextMenu,
51
+ }
35
52
 
36
- pub use style_provider::{StyleProvider, StyleSurface};
53
+ impl StyleSurface {
54
+ /// The shared, lazily constructed `CSSStyleSheet` for this surface.
55
+ pub fn sheet(self) -> web_sys::CssStyleSheet {
56
+ match self {
57
+ StyleSurface::Viewer => VIEWER_SHEET.with(|x| x.clone()),
58
+ StyleSurface::ColumnDropdown => COLUMN_DROPDOWN_SHEET.with(|x| x.clone()),
59
+ StyleSurface::FilterDropdown => FILTER_DROPDOWN_SHEET.with(|x| x.clone()),
60
+ StyleSurface::FunctionDropdown => FUNCTION_DROPDOWN_SHEET.with(|x| x.clone()),
61
+ StyleSurface::DropdownMenu => DROPDOWN_MENU_SHEET.with(|x| x.clone()),
62
+ StyleSurface::ContextMenu => CONTEXT_MENU_SHEET.with(|x| x.clone()),
63
+ }
64
+ }
65
+ }
37
66
 
38
67
  /// The viewer-surface bundle: every `src/css` file except the floating-popup
39
68
  /// sheets. Adopted into the `<perspective-viewer>` ShadowRoot.
@@ -76,15 +105,3 @@ fn make_sheet(css: &str) -> web_sys::CssStyleSheet {
76
105
  sheet.replace_sync(css).unwrap();
77
106
  sheet
78
107
  }
79
-
80
- /// The memoized, shared `CSSStyleSheet` for a given [`StyleSurface`].
81
- pub(crate) fn surface_sheet(surface: StyleSurface) -> web_sys::CssStyleSheet {
82
- match surface {
83
- StyleSurface::Viewer => VIEWER_SHEET.with(|x| x.clone()),
84
- StyleSurface::ColumnDropdown => COLUMN_DROPDOWN_SHEET.with(|x| x.clone()),
85
- StyleSurface::FilterDropdown => FILTER_DROPDOWN_SHEET.with(|x| x.clone()),
86
- StyleSurface::FunctionDropdown => FUNCTION_DROPDOWN_SHEET.with(|x| x.clone()),
87
- StyleSurface::DropdownMenu => DROPDOWN_MENU_SHEET.with(|x| x.clone()),
88
- StyleSurface::ContextMenu => CONTEXT_MENU_SHEET.with(|x| x.clone()),
89
- }
90
- }
@@ -17,15 +17,13 @@ use perspective_client::config::ColumnType;
17
17
  use yew::html;
18
18
 
19
19
  use super::CustomNumberFormat;
20
- use crate::components::containers::select::{Select, SelectItem};
21
- use crate::components::form::number_field::NumberField;
22
- use crate::components::form::number_range_field::NumberRangeField;
23
- use crate::components::form::optional_field::OptionalField;
24
- use crate::components::form::select_enum_field::SelectEnumField;
25
20
  use crate::components::style_controls::CustomNumberFormatMsg;
26
21
  use crate::config::{
27
22
  ROUNDING_INCREMENTS, RoundingIncrement, RoundingMode, RoundingPriority, TrailingZeroDisplay,
28
23
  };
24
+ use crate::ui::{
25
+ NumberField, NumberRangeField, OptionalField, Select, SelectEnumField, SelectItem,
26
+ };
29
27
 
30
28
  impl CustomNumberFormat {
31
29
  pub fn digits_section(&self, ctx: &yew::prelude::Context<Self>) -> yew::prelude::Html {
@@ -13,9 +13,9 @@
13
13
  use yew::html;
14
14
 
15
15
  use super::CustomNumberFormat;
16
- use crate::components::form::select_enum_field::SelectEnumField;
17
16
  use crate::components::style_controls::{CustomNumberFormatMsg, NotationName};
18
17
  use crate::config::*;
18
+ use crate::ui::SelectEnumField;
19
19
 
20
20
  impl CustomNumberFormat {
21
21
  pub fn misc_section(&self, ctx: &yew::prelude::Context<Self>) -> yew::prelude::Html {
@@ -13,9 +13,9 @@
13
13
  use yew::html;
14
14
 
15
15
  use super::CustomNumberFormat;
16
- use crate::components::form::select_enum_field::SelectEnumField;
17
16
  use crate::components::style_controls::{CustomNumberFormatMsg, NumberStyle};
18
17
  use crate::config::*;
18
+ use crate::ui::SelectEnumField;
19
19
 
20
20
  impl CustomNumberFormat {
21
21
  pub fn style_section(&self, ctx: &yew::prelude::Context<Self>) -> yew::prelude::Html {
@@ -26,7 +26,7 @@ use crate::presentation::{
26
26
  ColumnSettingsTab, ColumnSettingsTarget, DragDropProps, PresentationProps,
27
27
  };
28
28
  use crate::renderer::RendererProps;
29
- use crate::session::{SessionProps, TableLoadState, ViewStats};
29
+ use crate::session::{SessionProps, TableLoadState};
30
30
  use crate::utils::Completion;
31
31
 
32
32
  /// The filter-bearing payload of a master panel's selection or click event
@@ -45,9 +45,31 @@ pub struct MasterSelection {
45
45
  pub cell_fallback: Option<Filter>,
46
46
  }
47
47
 
48
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
49
+ pub enum Divider {
50
+ Settings,
51
+ ColumnSettings,
52
+ }
53
+
54
+ impl Divider {
55
+ /// The shadow-DOM selector of the divider's resizable pane (pane 0 of
56
+ /// its `SplitPanel`; the flex-fill pane renders bare).
57
+ pub fn pane_selector(self) -> &'static str {
58
+ match self {
59
+ Self::Settings => "#app_panel > .split-panel-child",
60
+ Self::ColumnSettings => "#modal_panel > .split-panel-child",
61
+ }
62
+ }
63
+ }
64
+
65
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
66
+ pub enum PaneTarget {
67
+ Width(i32),
68
+ Natural,
69
+ }
70
+
48
71
  #[derive(Debug)]
49
72
  pub enum PerspectiveViewerMsg {
50
- ColumnSettingsPanelSizeUpdate(Option<i32>),
51
73
  ColumnSettingsPanelAutoWidth(f64),
52
74
  ToggleColumnSettingsPin,
53
75
  ToggleColumnSettingsPinComplete(Sender<()>),
@@ -84,12 +106,18 @@ pub enum PerspectiveViewerMsg {
84
106
  /// nudge runs complete (invariant I6).
85
107
  SetActivePanel(String, Option<Completion>),
86
108
 
87
- /// The named panel's frame was closed (removed from the layout); remove it
88
- /// from the workspace and dispose its engines. The `Completion` resolves
89
- /// `removePanel()` after the eject's teardown run completes (invariant
90
- /// I6) — carrying any teardown error, which was previously dropped.
109
+ /// Close the named panel: remove it from the workspace now but keep its
110
+ /// engines parked until [`Self::PanelClosed`] reports the layout commit
111
+ /// that reclaims its cell. The `Completion` resolves `removePanel()`
112
+ /// after the eject's teardown run completes (invariant I6) — carrying
113
+ /// any teardown error.
91
114
  ClosePanel(String, Option<Completion>),
92
115
 
116
+ /// `MainPanel` saw the named panel leave the `regular-layout` tree, so
117
+ /// dispose its parked panel, or close outright one the workspace still
118
+ /// holds.
119
+ PanelClosed(String),
120
+
93
121
  /// `restoreWorkspace` finished replacing the panel set in the
94
122
  /// `Workspace` (new models inserted, old panels ejected, layout staged):
95
123
  /// activate the named panel, re-subscribe the per-panel wiring, and
@@ -130,26 +158,10 @@ pub enum PerspectiveViewerMsg {
130
158
  /// Some panel's title changed (any panel, via `_title_subscriptions`);
131
159
  /// re-render so the tab titles refresh.
132
160
  TitlesChanged,
133
- SettingsPanelSizeUpdate(Option<i32>),
134
-
135
- /// The settings-pane divider proposed a new pane width (per pointermove,
136
- /// from the *deferred* `SplitPanel` — it has NOT been applied). Feeds the
137
- /// latest-wins presize pump (`PRESIZE_EVERYWHERE_PLAN.md` P1): geometry
138
- /// commits only after every visible panel has rendered at its target.
139
- SettingsDividerMove(i32),
140
-
141
- /// Run one pump iteration: presize all visible panels at the newest
142
- /// proposed pane width, then commit it.
143
- SettingsDividerPump,
144
-
145
- /// Presize for this pane width completed — commit it (the deferred
146
- /// `SplitPanel`'s controlled `size`), then pump again if a newer target
147
- /// arrived meanwhile.
148
- SettingsDividerCommit(i32),
149
-
150
- /// Divider drag ended: reactively finalize every visible panel at its
151
- /// exact settled cell (debounced no-op when the presizes were exact).
152
- SettingsDividerFinish,
161
+ DividerMove(Divider, PaneTarget),
162
+ DividerPump(Divider),
163
+ DividerCommit(Divider, PaneTarget),
164
+ DividerFinish(Divider),
153
165
  SettingsPanelTabChanged(SelectedTab),
154
166
  SettingsPanelAutoWidth(f64),
155
167
  ToggleDebug,
@@ -203,10 +215,11 @@ pub enum PerspectiveViewerMsg {
203
215
  UpdateColumnSettingsCommit(Sender<()>),
204
216
  UpdateDragDrop(Box<DragDropProps>),
205
217
 
206
- /// Update only stats-related fields of `session_props` without touching
207
- /// `config`. This prevents `stats_changed` events (e.g. from `reset()`)
208
- /// from propagating a freshly-cleared config to the column selector.
209
- UpdateSessionStats(Option<ViewStats>, Option<TableLoadState>),
218
+ /// Update only the stats-derived fields of `session_props`
219
+ /// (`has_table_cells`, `has_table`) without touching `config`. This
220
+ /// prevents `stats_changed` events (e.g. from `reset()`) from propagating
221
+ /// a freshly-cleared config to the column selector.
222
+ UpdateSessionStats(bool, Option<TableLoadState>),
210
223
 
211
224
  /// Refresh the root's render snapshot of the `Workspace`-owned global
212
225
  /// filter set (dispatched by its `filters_changed` PubSub).
@@ -33,7 +33,7 @@ use crate::renderer::Renderer;
33
33
  use crate::session::*;
34
34
  use crate::tasks::*;
35
35
  use crate::utils::{Completion, spawn_owned};
36
- use crate::workspace::PanelId;
36
+ use crate::workspace::{Panel, PanelId};
37
37
 
38
38
  impl PerspectiveViewer {
39
39
  pub(super) fn on_layout_changed(&mut self, ctx: &Context<Self>) -> bool {
@@ -136,16 +136,11 @@ impl PerspectiveViewer {
136
136
  true
137
137
  }
138
138
 
139
- pub(super) fn on_close_panel(
140
- &mut self,
141
- ctx: &Context<Self>,
142
- id: String,
143
- completion: Option<Completion>,
144
- ) -> bool {
145
- let id = PanelId::from(id);
146
-
147
- let was_active = ctx.props().workspace.active_id().as_ref() == Some(&id);
148
- let removed = ctx.props().workspace.remove_panel(&id);
139
+ /// Remove `id` from the workspace and return the detached [`Panel`] with
140
+ /// its engines still alive, or `None` for an unknown id.
141
+ fn detach_panel(&mut self, ctx: &Context<Self>, id: &PanelId) -> Option<Panel> {
142
+ let was_active = ctx.props().workspace.active_id().as_ref() == Some(id);
143
+ let removed = ctx.props().workspace.remove_panel(id)?;
149
144
  if was_active {
150
145
  if let Some(next) = ctx.props().workspace.panel_ids().first().cloned() {
151
146
  ctx.props().workspace.set_active(next);
@@ -164,19 +159,55 @@ impl PerspectiveViewer {
164
159
  self.retarget_active(ctx, new_session, new_renderer);
165
160
  }
166
161
 
167
- let eject = removed.map(eject_panel);
168
- match (eject, completion) {
169
- (Some(eject), Some(completion)) => completion.resolve_after(eject),
170
- (Some(eject), None) => spawn_owned("close-panel", eject),
171
- (None, Some(completion)) => completion.resolve_after(async { Ok(()) }),
172
- (None, None) => {},
173
- }
174
-
175
162
  apply_global_filters(&ctx.props().workspace);
176
163
  self._title_subscriptions = subscribe_panel_titles(ctx);
164
+ Some(removed)
165
+ }
166
+
167
+ pub(super) fn on_close_panel(
168
+ &mut self,
169
+ ctx: &Context<Self>,
170
+ id: String,
171
+ completion: Option<Completion>,
172
+ ) -> bool {
173
+ let id = PanelId::from(id);
174
+ let Some(removed) = self.detach_panel(ctx, &id) else {
175
+ if let Some(completion) = completion {
176
+ completion.resolve_after(async { Ok(()) });
177
+ }
178
+
179
+ return false;
180
+ };
181
+
182
+ ctx.props().workspace.stash_closing(removed);
183
+ if let Some(completion) = completion {
184
+ self.pending_closes.insert(id, completion);
185
+ }
186
+
177
187
  true
178
188
  }
179
189
 
190
+ pub(super) fn on_panel_closed(&mut self, ctx: &Context<Self>, id: String) -> bool {
191
+ let id = PanelId::from(id);
192
+ if let Some(panel) = ctx.props().workspace.take_closing(&id) {
193
+ let eject = eject_panel(panel);
194
+ match self.pending_closes.remove(&id) {
195
+ Some(completion) => completion.resolve_after(eject),
196
+ None => spawn_owned("close-panel", eject),
197
+ }
198
+
199
+ return false;
200
+ }
201
+
202
+ match self.detach_panel(ctx, &id) {
203
+ Some(panel) => {
204
+ spawn_owned("close-panel", eject_panel(panel));
205
+ true
206
+ },
207
+ None => false,
208
+ }
209
+ }
210
+
180
211
  pub(super) fn on_duplicate_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
181
212
  if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
182
213
  let elem = ctx.props().elem.clone();