@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
@@ -190,16 +190,21 @@ impl MainPanel {
190
190
  self.inserted.push(name.to_owned());
191
191
  }
192
192
 
193
- // Remove cells whose panels are gone.
193
+ let mut pending = std::mem::take(&mut self.pending_removals);
194
194
  self.inserted.retain(|name| {
195
195
  if panel_ids.iter().any(|id| id.as_str() == name) {
196
- true
197
- } else {
196
+ return true;
197
+ }
198
+
199
+ if pending.insert(name.clone()) {
198
200
  let _ = layout.remove_panel(name);
199
- false
200
201
  }
202
+
203
+ true
201
204
  });
202
205
 
206
+ self.pending_removals = pending;
207
+
203
208
  // A removed panel can't stay "maximized" (regular-layout drops the
204
209
  // maximize stylesheet when its panel leaves the layout).
205
210
  if let Some(m) = &self.maximized
@@ -18,7 +18,8 @@ use wasm_bindgen::prelude::*;
18
18
  use yew::prelude::*;
19
19
 
20
20
  use super::{MainPanel, MainPanelMsg, MainPanelProps};
21
- use crate::components::panel_menu::PanelMenu;
21
+ use crate::components::new_panel_menu::NewPanelPick;
22
+ use crate::components::panel_menu::{PanelCommand, PanelMenu};
22
23
  use crate::components::panel_tab::PanelTab;
23
24
  use crate::components::render_warning::RenderWarning;
24
25
  use crate::components::status_bar::StatusBar;
@@ -73,14 +74,26 @@ impl MainPanel {
73
74
  let active_slot = renderer.slot_name();
74
75
  // Panel-count chrome (`single`/`multi`, closable, draggable)
75
76
  let placed_count = ctx.props().workspace.placed_count();
77
+ let closing = self
78
+ .inserted
79
+ .iter()
80
+ .filter(|name| !ctx.props().panel_ids.iter().any(|id| id.as_str() == *name))
81
+ .map(|name| PanelId::from(name.as_str()))
82
+ .collect::<Vec<_>>();
76
83
  let cells = ctx
77
84
  .props()
78
85
  .panel_ids
79
86
  .iter()
80
87
  .filter(|id| !ctx.props().workspace.is_staged(id))
81
- .map(|id| {
88
+ .map(|id| (id, false))
89
+ .chain(closing.iter().map(|id| (id, true)))
90
+ .map(|(id, is_closing)| {
82
91
  let name = id.as_str().to_owned();
83
92
  let mut class = classes!("rl-panel");
93
+ if is_closing {
94
+ class.push("closing");
95
+ }
96
+
84
97
  if placed_count > 1 {
85
98
  if active_slot.as_deref() == Some(name.as_str()) {
86
99
  class.push("active");
@@ -103,7 +116,9 @@ impl MainPanel {
103
116
  let on_activate = ctx.props().on_activate_panel.clone();
104
117
  let activate_name = name.clone();
105
118
  let onpointerdown = Callback::from(move |_: web_sys::PointerEvent| {
106
- on_activate.emit(activate_name.clone());
119
+ if !is_closing {
120
+ on_activate.emit(activate_name.clone());
121
+ }
107
122
  });
108
123
 
109
124
  // Right-click anywhere in the panel opens the context menu. This
@@ -214,23 +229,41 @@ impl MainPanel {
214
229
  on_settings.emit(());
215
230
  })
216
231
  };
232
+ let closing_tabs = closing.iter().filter_map(|id| {
233
+ let panel = ctx.props().workspace.closing_panel(id)?;
234
+ let title = panel.session.get_title().filter(|t| !t.is_empty());
235
+ let theme = panel.renderer.theme().or_else(|| {
236
+ ctx.props()
237
+ .presentation_props
238
+ .available_themes
239
+ .first()
240
+ .cloned()
241
+ });
242
+ Some((id, panel.session, title, theme, true))
243
+ });
217
244
  let tabs = ctx
218
245
  .props()
219
246
  .panel_ids
220
247
  .iter()
221
248
  .filter(|id| !ctx.props().workspace.is_staged(id))
222
- .map(|id| {
223
- let name = id.as_str().to_owned();
249
+ .filter_map(|id| {
250
+ let panel = ctx.props().workspace.panel(id)?;
251
+ let name = id.as_str();
224
252
  let title = ctx
225
253
  .props()
226
254
  .panel_titles
227
255
  .iter()
228
- .find(|(pid, _)| pid == &name)
256
+ .find(|(pid, _)| pid == name)
229
257
  .and_then(|(_, t)| t.clone());
230
- let active = active_slot.as_deref() == Some(name.as_str());
258
+ let theme = ctx.props().effective_panel_theme(name);
259
+ Some((id, panel.session, title, theme, false))
260
+ })
261
+ .chain(closing_tabs)
262
+ .map(|(id, session, title, theme, is_closing)| {
263
+ let name = id.as_str().to_owned();
264
+ let active = !is_closing && active_slot.as_deref() == Some(name.as_str());
231
265
  let visible = !self.hidden_tabs.contains(name.as_str());
232
- let is_master = ctx.props().panel_masters.contains(id);
233
- let theme = ctx.props().effective_panel_theme(&name);
266
+ let is_master = !is_closing && ctx.props().panel_masters.contains(id);
234
267
 
235
268
  html! {
236
269
  <PanelTab
@@ -238,14 +271,16 @@ impl MainPanel {
238
271
  viewer={viewer_elem.clone()}
239
272
  panel_id={name.clone()}
240
273
  {title}
274
+ {session}
241
275
  {theme}
242
276
  {active}
243
277
  {visible}
244
278
  {is_master}
245
279
  {single}
246
- {closable}
280
+ closable={closable && !is_closing}
247
281
  {is_settings_open}
248
- {draggable}
282
+ draggable={draggable && !is_closing}
283
+ closing={is_closing}
249
284
  on_select={on_select.clone()}
250
285
  on_close={on_close.clone()}
251
286
  on_open_settings={on_open_settings.clone()}
@@ -349,11 +384,27 @@ impl MainPanel {
349
384
  })
350
385
  .unwrap_or_default();
351
386
 
387
+ let on_new_panel = {
388
+ let on_command = ctx.props().on_panel_command.clone();
389
+ let id = renderer.slot_name().unwrap_or_default();
390
+ Callback::from(move |pick: NewPanelPick| {
391
+ let cmd = match pick {
392
+ NewPanelPick::FromTable { client, table } => {
393
+ PanelCommand::NewFrom { client, table }
394
+ },
395
+ NewPanelPick::FromPanel(source) => PanelCommand::NewFromPanel(source),
396
+ };
397
+
398
+ on_command.emit((id.clone(), cmd));
399
+ })
400
+ };
401
+
352
402
  html! {
353
403
  <div id="main_column">
354
404
  <StatusBar
355
405
  id="status_bar"
356
406
  on_reset={ctx.props().on_reset.clone()}
407
+ {on_new_panel}
357
408
  session_props={ctx.props().session_props.clone()}
358
409
  presentation_props={ctx.props().presentation_props.clone()}
359
410
  is_settings_open={ctx.props().is_settings_open}
@@ -22,6 +22,7 @@ use yew::prelude::*;
22
22
  use super::MainPanel;
23
23
  use crate::components::panel_menu::PanelCommand;
24
24
  use crate::js::{Layout, RegularLayout};
25
+ use crate::renderer::SUBPIXEL_EPSILON;
25
26
  use crate::tasks::resize_callback;
26
27
 
27
28
  impl MainPanel {
@@ -42,11 +43,9 @@ impl MainPanel {
42
43
  false
43
44
  }
44
45
 
46
+ /// Report closed every panel this commit dropped from the layout, and
47
+ /// re-render so its frame leaves the DOM in the commit's paint.
45
48
  pub(super) fn on_layout_updated(&mut self, ctx: &Context<Self>) -> bool {
46
- // A panel still tracked in `inserted` but no longer present in
47
- // the layout (calculatePath → null) was removed by the layout —
48
- // i.e. its frame's close button was pressed. Report it for
49
- // disposal.
50
49
  let Some(el) = self.layout_ref.cast::<web_sys::HtmlElement>() else {
51
50
  return false;
52
51
  };
@@ -68,10 +67,14 @@ impl MainPanel {
68
67
  }
69
68
  });
70
69
 
70
+ let closed_any = !closed.is_empty();
71
71
  for id in closed {
72
- ctx.props().on_close_panel.emit(id);
72
+ self.pending_removals.remove(&id);
73
+ ctx.props().on_panel_closed.emit(id);
73
74
  }
74
75
 
76
+ let reissue = self.forget_dropped_removals(&layout);
77
+
75
78
  // A layout change (divider drag, insert, restore) reflows the
76
79
  // grid cells without resizing the host, so the host
77
80
  // `ResizeObserver` never fires. Resize every activated panel's
@@ -92,6 +95,19 @@ impl MainPanel {
92
95
  if let Some(panel) = ctx.props().workspace.panel(id)
93
96
  && panel.renderer.is_plugin_activated().unwrap_or(false)
94
97
  {
98
+ if let Some((w, h)) = panel.renderer.take_presized_box()
99
+ && let Some(plugin) = panel.renderer.active_plugin()
100
+ {
101
+ let rect = plugin
102
+ .unchecked_ref::<web_sys::Element>()
103
+ .get_bounding_client_rect();
104
+ if (rect.width() - w).abs() <= SUBPIXEL_EPSILON
105
+ && (rect.height() - h).abs() <= SUBPIXEL_EPSILON
106
+ {
107
+ continue;
108
+ }
109
+ }
110
+
95
111
  resize_callback(&panel.session, &panel.renderer).emit(());
96
112
  }
97
113
  }
@@ -111,10 +127,27 @@ impl MainPanel {
111
127
  self.hidden_tabs = hidden;
112
128
  true
113
129
  } else {
114
- false
130
+ closed_any || reissue
115
131
  }
116
132
  }
117
133
 
134
+ /// Forget every issued removal whose panel survived the commit, so the
135
+ /// next `reconcile` re-issues it.
136
+ fn forget_dropped_removals(&mut self, layout: &RegularLayout) -> bool {
137
+ let dropped = self
138
+ .pending_removals
139
+ .iter()
140
+ .filter(|id| layout.contains_panel(id))
141
+ .cloned()
142
+ .collect::<Vec<_>>();
143
+ let reissue = !dropped.is_empty();
144
+ for id in dropped {
145
+ self.pending_removals.remove(&id);
146
+ }
147
+
148
+ reissue
149
+ }
150
+
118
151
  pub(super) fn on_tab_selected(&self, ctx: &Context<Self>, name: String) -> bool {
119
152
  ctx.props().on_activate_panel.emit(name);
120
153
  false
@@ -169,10 +202,6 @@ impl MainPanel {
169
202
 
170
203
  self.maximized = None;
171
204
  },
172
- // The stage menu (no target panel) offers ONLY `NewFrom`, whose
173
- // handler resolves the client/table purely from the loaded-clients
174
- // registry — the panel id is unused there, so the empty id never
175
- // reaches a panel lookup.
176
205
  cmd => ctx
177
206
  .props()
178
207
  .on_panel_command
@@ -61,10 +61,14 @@ pub struct MainPanelProps {
61
61
  /// active panel.
62
62
  pub on_activate_panel: Callback<String>,
63
63
 
64
- /// Fired with a panel id when its frame's close button removes it from the
65
- /// layout, so the root can dispose the panel.
64
+ /// Fired with a panel id from a tab × (an app-initiated close): the root
65
+ /// removes it from the model at once and parks its engines.
66
66
  pub on_close_panel: Callback<String>,
67
67
 
68
+ /// Fired with a panel id once the `regular-layout` tree no longer holds
69
+ /// it, so the root can eject the parked panel.
70
+ pub on_panel_closed: Callback<String>,
71
+
68
72
  /// Fired with `(panel id, command)` when the panel context menu selects a
69
73
  /// command the root executes (New/Duplicate/Reset/ToggleMaster/Close).
70
74
  /// Maximize/Restore are handled HERE (this component owns the layout
@@ -203,6 +207,10 @@ pub struct MainPanel {
203
207
  /// every listener, and nothing ever noticed.
204
208
  listener_target: Option<web_sys::HtmlElement>,
205
209
 
210
+ /// Ids gone from the model whose `removePanel` has been issued to the
211
+ /// layout and not yet committed.
212
+ pending_removals: HashSet<String>,
213
+
206
214
  /// Per-panel `ResizeObserver`s, keyed by panel id, each observing that
207
215
  /// panel's slotted plugin element and resizing only that panel's
208
216
  /// `Renderer`. Bound in `BeforeResize` for the DRAGGED panel only (it is
@@ -347,6 +355,7 @@ impl Component for MainPanel {
347
355
  main_panel_ref: NodeRef::default(),
348
356
  layout_ref: NodeRef::default(),
349
357
  inserted: Vec::new(),
358
+ pending_removals: HashSet::new(),
350
359
  _layout_update_listener: listener,
351
360
  _layout_select_listener: select_listener,
352
361
  _layout_before_resize_listener: before_resize_listener,
@@ -376,6 +385,7 @@ impl Component for MainPanel {
376
385
  }
377
386
 
378
387
  fn changed(&mut self, ctx: &Context<Self>, old: &Self::Properties) -> bool {
388
+ self.close_unstaged_panels(ctx, old);
379
389
  ctx.props() != old
380
390
  }
381
391
 
@@ -403,3 +413,17 @@ impl Component for MainPanel {
403
413
 
404
414
  fn destroy(&mut self, _ctx: &Context<Self>) {}
405
415
  }
416
+
417
+ impl MainPanel {
418
+ /// Report closed every panel gone from the model that the layout never
419
+ /// held, since it has no commit to wait for.
420
+ fn close_unstaged_panels(&self, ctx: &Context<Self>, old: &MainPanelProps) {
421
+ for id in &old.panel_ids {
422
+ if !ctx.props().panel_ids.contains(id)
423
+ && !self.inserted.iter().any(|n| n == id.as_str())
424
+ {
425
+ ctx.props().on_panel_closed.emit(id.as_str().to_owned());
426
+ }
427
+ }
428
+ }
429
+ }
@@ -16,34 +16,32 @@
16
16
 
17
17
  #[cfg(feature = "llm-agent")]
18
18
  pub mod chat_panel;
19
+ pub mod code_editor;
19
20
  pub mod column_dropdown;
20
21
  pub mod column_selector;
21
22
  pub mod column_settings_sidebar;
22
- pub mod containers;
23
- pub mod context_menu;
24
23
  pub mod copy_dropdown;
25
24
  pub mod datetime_column_style;
25
+ pub mod debug_panel;
26
+ pub mod dragdrop_list;
26
27
  pub mod editable_header;
27
28
  pub mod empty_row;
28
29
  pub mod export_dropdown;
29
30
  pub mod expression_editor;
30
31
  pub mod filter_dropdown;
31
- pub mod font_loader;
32
- pub mod form;
33
32
  pub mod function_dropdown;
34
33
  pub mod global_filter_bar;
35
34
  pub mod main_panel;
36
- pub mod modal;
35
+ pub mod new_panel_menu;
37
36
  pub mod number_series_style;
38
37
  pub mod panel_menu;
39
38
  pub mod panel_tab;
40
39
  pub mod plugin_selector;
41
40
  pub mod plugin_tab;
42
- pub mod portal;
43
41
  pub mod render_warning;
42
+ pub mod rows_counter;
44
43
  pub mod settings_panel;
45
44
  pub mod status_bar;
46
- pub mod status_bar_counter;
47
45
  pub mod status_indicator;
48
46
  pub mod string_column_style;
49
47
  pub mod style;
@@ -0,0 +1,97 @@
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 std::rc::Rc;
14
+
15
+ use yew::prelude::*;
16
+
17
+ /// A pick in the "New" menu: a fresh panel on a hosted `Table`, or a copy of
18
+ /// an existing panel (the same operation as the context menu's "Duplicate").
19
+ #[derive(Clone, PartialEq)]
20
+ pub enum NewPanelPick {
21
+ FromTable { client: String, table: String },
22
+ FromPanel(String),
23
+ }
24
+
25
+ /// `(client name, its hosted table names)` per loaded client — the resolved
26
+ /// output of `queries::fetch_hosted_tables`.
27
+ pub type HostedTables = Rc<Vec<(String, Vec<String>)>>;
28
+
29
+ /// `(panel id, title)` per panel, in layout-insertion order.
30
+ pub type PanelLabels = Rc<Vec<(String, Option<String>)>>;
31
+
32
+ #[derive(Properties, PartialEq)]
33
+ pub struct NewPanelMenuProps {
34
+ /// `None` while the hosted-table fetch is in flight.
35
+ pub tables: Option<HostedTables>,
36
+ pub panels: PanelLabels,
37
+ pub callback: Callback<NewPanelPick>,
38
+ }
39
+
40
+ /// The "New" menu body shared by the status bar dropdown and the context
41
+ /// menu's "New" flyout.
42
+ #[function_component]
43
+ pub fn NewPanelMenu(props: &NewPanelMenuProps) -> Html {
44
+ let pick = |value: NewPanelPick| props.callback.reform(move |_: MouseEvent| value.clone());
45
+
46
+ let tables = match &props.tables {
47
+ None => html! { <span class="no-results">{ "Loading..." }</span> },
48
+ Some(clients) if clients.iter().all(|(_, tables)| tables.is_empty()) => {
49
+ html! { <span class="no-results">{ "No tables" }</span> }
50
+ },
51
+ Some(clients) => {
52
+ let multi = clients.len() > 1;
53
+ clients
54
+ .iter()
55
+ .map(|(client, tables)| {
56
+ let rows = tables.iter().map(|table| {
57
+ let onmousedown = pick(NewPanelPick::FromTable {
58
+ client: client.clone(),
59
+ table: table.clone(),
60
+ });
61
+ html! { <span class="dropdown-menu-item" {onmousedown}>{ table }</span> }
62
+ });
63
+
64
+ html! {
65
+ <>
66
+ if multi { <span class="dropdown-group-sublabel">{ client }</span> }
67
+ { for rows }
68
+ </>
69
+ }
70
+ })
71
+ .collect::<Html>()
72
+ },
73
+ };
74
+
75
+ let panels = if props.panels.is_empty() {
76
+ html! { <span class="no-results">{ "No panels" }</span> }
77
+ } else {
78
+ props
79
+ .panels
80
+ .iter()
81
+ .map(|(id, title)| {
82
+ let onmousedown = pick(NewPanelPick::FromPanel(id.clone()));
83
+ let label = title.clone().unwrap_or_else(|| id.clone());
84
+ html! { <span class="dropdown-menu-item" {onmousedown}>{ label }</span> }
85
+ })
86
+ .collect::<Html>()
87
+ };
88
+
89
+ html! {
90
+ <div class="new-panel-menu">
91
+ <span class="dropdown-group-label" data-label="new-from-table" />
92
+ <div class="dropdown-group-container">{ tables }</div>
93
+ <span class="dropdown-group-label" data-label="new-from-panel" />
94
+ <div class="dropdown-group-container">{ panels }</div>
95
+ </div>
96
+ }
97
+ }
@@ -13,9 +13,8 @@
13
13
  use web_sys::{HtmlInputElement, InputEvent};
14
14
  use yew::prelude::*;
15
15
 
16
- use super::modal::{ModalLink, SetModalLink};
17
- use crate::components::form::select_enum_field::SelectEnumField;
18
16
  use crate::config::*;
17
+ use crate::ui::{IntlLabel, ModalLink, SelectEnumField, SetModalLink};
19
18
  use crate::utils::WeakScope;
20
19
 
21
20
  #[derive(Properties)]
@@ -113,7 +112,7 @@ impl Component for NumberSeriesStyle {
113
112
  });
114
113
  html! {
115
114
  <div class="row">
116
- <label id="stack-label" />
115
+ <IntlLabel name="stack" />
117
116
  <input type="checkbox" id="stack-checkbox" {checked} {oninput} />
118
117
  </div>
119
118
  }