@perspective-dev/viewer 5.3.1 → 5.5.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 (291) 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.d.ts +2 -0
  32. package/dist/esm/perspective-viewer.inline.js +8 -8
  33. package/dist/esm/perspective-viewer.inline.js.map +2 -2
  34. package/dist/esm/perspective-viewer.js +5 -5
  35. package/dist/esm/perspective-viewer.js.map +2 -2
  36. package/dist/esm/plugin.d.ts +9 -0
  37. package/dist/esm/ts-rs/AddPanelOptions.d.ts +9 -0
  38. package/dist/esm/ts-rs/GetViewMode.d.ts +4 -0
  39. package/dist/esm/ts-rs/GetViewOptions.d.ts +8 -0
  40. package/dist/esm/ts-rs/RestoreOptions.d.ts +6 -0
  41. package/dist/esm/ts-rs/RestoreWorkspaceOptions.d.ts +9 -0
  42. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +1 -1
  43. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +30 -22
  44. package/dist/wasm/perspective-viewer.d.ts +137 -104
  45. package/dist/wasm/perspective-viewer.js +337 -269
  46. package/dist/wasm/perspective-viewer.wasm +0 -0
  47. package/dist/wasm/perspective-viewer.wasm.d.ts +20 -30
  48. package/package.json +7 -7
  49. package/src/css/_context_menu.css +1 -1
  50. package/src/css/_dropdown_menu.css +1 -1
  51. package/src/css/_viewer_bundle.css +7 -8
  52. package/src/css/column-dropdown.css +0 -3
  53. package/src/css/column-selector.css +18 -7
  54. package/src/css/column-settings-panel.css +0 -225
  55. package/src/css/column-style.css +175 -0
  56. package/src/css/config-selector.css +19 -11
  57. package/src/css/filter-dropdown.css +5 -5
  58. package/src/css/form/debug.css +37 -4
  59. package/src/css/function-dropdown.css +5 -5
  60. package/src/css/panel-tab.css +23 -0
  61. package/src/css/plugin-selector.css +5 -5
  62. package/src/css/render-warning.css +1 -1
  63. package/src/css/status-bar.css +63 -21
  64. package/src/css/type-icon.css +1 -3
  65. package/src/css/{containers → ui/containers}/context-menu.css +22 -16
  66. package/src/css/ui/containers/dropdown-menu-items.css +66 -0
  67. package/src/css/{containers → ui/containers}/dropdown-menu.css +8 -53
  68. package/src/css/{containers → ui/containers}/tabs.css +2 -1
  69. package/src/css/viewer.css +32 -26
  70. package/src/rust/agent/tools.rs +80 -7
  71. package/src/rust/components/chat_panel.rs +1 -1
  72. package/src/rust/components/{form/code_editor.rs → code_editor.rs} +4 -2
  73. package/src/rust/components/column_dropdown.rs +2 -2
  74. package/src/rust/components/column_selector/active_column.rs +24 -9
  75. package/src/rust/components/column_selector/aggregate_selector.rs +1 -1
  76. package/src/rust/components/column_selector/config_selector.rs +2 -2
  77. package/src/rust/components/column_selector/filter_column.rs +8 -5
  78. package/src/rust/components/column_selector/inactive_column.rs +4 -2
  79. package/src/rust/components/column_selector/pivot_column.rs +4 -3
  80. package/src/rust/components/column_selector/sort_column.rs +7 -4
  81. package/src/rust/components/column_selector.rs +14 -3
  82. package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +1 -1
  83. package/src/rust/components/column_settings_sidebar/style_tab/alignment_field.rs +90 -0
  84. package/src/rust/components/column_settings_sidebar/style_tab/font_field.rs +267 -0
  85. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +16 -18
  86. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +1 -1
  87. package/src/rust/components/column_settings_sidebar/style_tab.rs +70 -12
  88. package/src/rust/components/column_settings_sidebar.rs +3 -4
  89. package/src/rust/components/copy_dropdown.rs +1 -1
  90. package/src/rust/components/datetime_column_style/custom.rs +1 -3
  91. package/src/rust/components/datetime_column_style/simple.rs +1 -2
  92. package/src/rust/components/datetime_column_style.rs +1 -2
  93. package/src/rust/components/{form/debug.rs → debug_panel.rs} +112 -55
  94. package/src/rust/components/{containers/dragdrop_list.rs → dragdrop_list.rs} +2 -10
  95. package/src/rust/components/export_dropdown.rs +1 -1
  96. package/src/rust/components/expression_editor.rs +1 -1
  97. package/src/rust/components/filter_dropdown.rs +2 -2
  98. package/src/rust/components/function_dropdown.rs +2 -2
  99. package/src/rust/components/main_panel/frame_theme.rs +3 -59
  100. package/src/rust/components/main_panel/msg.rs +4 -0
  101. package/src/rust/components/main_panel/reconcile.rs +9 -4
  102. package/src/rust/components/main_panel/render.rs +62 -11
  103. package/src/rust/components/main_panel/update.rs +39 -10
  104. package/src/rust/components/main_panel.rs +40 -2
  105. package/src/rust/components/mod.rs +5 -8
  106. package/src/rust/components/new_panel_menu.rs +97 -0
  107. package/src/rust/components/number_series_style.rs +2 -3
  108. package/src/rust/components/panel_menu.rs +61 -87
  109. package/src/rust/components/panel_tab.rs +100 -43
  110. package/src/rust/components/plugin_tab.rs +58 -4
  111. package/src/rust/components/render_warning.rs +2 -2
  112. package/src/rust/components/{status_bar_counter.rs → rows_counter.rs} +10 -10
  113. package/src/rust/components/settings_panel.rs +8 -2
  114. package/src/rust/components/status_bar.rs +113 -19
  115. package/src/rust/components/status_indicator.rs +9 -14
  116. package/src/rust/components/{style/mod.rs → style.rs} +32 -15
  117. package/src/rust/components/style_controls/number_string_format/digits_section.rs +3 -5
  118. package/src/rust/components/style_controls/number_string_format/misc_section.rs +1 -1
  119. package/src/rust/components/style_controls/number_string_format/style_section.rs +1 -1
  120. package/src/rust/components/viewer/msg.rs +43 -30
  121. package/src/rust/components/viewer/panels.rs +53 -19
  122. package/src/rust/components/viewer/render.rs +16 -13
  123. package/src/rust/components/viewer/settings.rs +137 -68
  124. package/src/rust/components/viewer/snapshots.rs +5 -10
  125. package/src/rust/components/viewer/wiring.rs +11 -22
  126. package/src/rust/components/viewer.rs +15 -10
  127. package/src/rust/components/window_editor.rs +8 -9
  128. package/src/rust/config/column_config_schema.rs +312 -33
  129. package/src/rust/config/datetime_column_style.rs +0 -11
  130. package/src/rust/config/mod.rs +0 -4
  131. package/src/rust/config/options.rs +50 -0
  132. package/src/rust/config/workspace_config.rs +70 -24
  133. package/src/rust/custom_elements/debug_plugin.rs +1 -10
  134. package/src/rust/custom_elements/mod.rs +0 -3
  135. package/src/rust/custom_elements/viewer.rs +194 -115
  136. package/src/rust/js/plugin.rs +3 -87
  137. package/src/rust/lib.rs +19 -18
  138. package/src/rust/presentation/column_locator.rs +3 -7
  139. package/src/rust/presentation.rs +68 -38
  140. package/src/rust/queries/hosted_tables.rs +38 -0
  141. package/src/rust/queries/mod.rs +2 -0
  142. package/src/rust/queries/palette_set.rs +1 -1
  143. package/src/rust/queries/plugin_column_styles.rs +6 -5
  144. package/src/rust/renderer/activate.rs +1 -3
  145. package/src/rust/renderer/dispatch.rs +83 -22
  146. package/src/rust/renderer/limits.rs +24 -5
  147. package/src/rust/renderer/plugin_config.rs +201 -41
  148. package/src/rust/renderer/plugin_store.rs +2 -1
  149. package/src/rust/renderer/props.rs +7 -1
  150. package/src/rust/renderer.rs +23 -27
  151. package/src/rust/session/props.rs +8 -3
  152. package/src/rust/session/view_subscription.rs +19 -6
  153. package/src/rust/session.rs +189 -150
  154. package/src/rust/tasks/create_panel.rs +5 -2
  155. package/src/rust/tasks/dismiss_render_warning.rs +1 -1
  156. package/src/rust/tasks/eject.rs +1 -0
  157. package/src/rust/tasks/pipeline.rs +18 -67
  158. package/src/rust/tasks/presize_panels.rs +24 -1
  159. package/src/rust/tasks/restore_and_render.rs +0 -17
  160. package/src/rust/tasks/restore_panel.rs +129 -30
  161. package/src/rust/tasks/send_column_config.rs +4 -1
  162. package/src/rust/tasks/send_plugin_config.rs +1 -1
  163. package/src/rust/tasks/set_edit_mode.rs +1 -1
  164. package/src/rust/tasks/sync_update_panels.rs +147 -67
  165. package/src/rust/tasks/table_lifecycle.rs +7 -1
  166. package/src/rust/{components → ui}/containers/control_group.rs +25 -3
  167. package/src/rust/{components → ui}/containers/dropdown_menu.rs +5 -2
  168. package/src/rust/{components → ui}/containers/mod.rs +0 -4
  169. package/src/rust/{components → ui}/containers/scroll_panel.rs +0 -22
  170. package/src/rust/{components → ui}/containers/scroll_panel_item.rs +0 -13
  171. package/src/rust/{components → ui}/containers/select.rs +0 -6
  172. package/src/rust/{components → ui}/containers/sidebar.rs +5 -32
  173. package/src/rust/{components → ui}/containers/split_panel.rs +8 -80
  174. package/src/rust/{components → ui}/containers/tab_list.rs +15 -9
  175. package/src/rust/{components → ui}/context_menu.rs +8 -13
  176. package/src/rust/{presentation/drag_helpers.rs → ui/dragdrop.rs} +26 -31
  177. package/src/rust/{components → ui}/font_loader.rs +0 -26
  178. package/src/rust/{components → ui}/form/color_selector.rs +4 -2
  179. package/src/rust/{config/datetime_column_style/color_mode.rs → ui/form/intl_label.rs} +42 -42
  180. package/src/rust/{components → ui}/form/mirrored_textarea.rs +0 -53
  181. package/src/rust/{components → ui}/form/mod.rs +1 -10
  182. package/src/rust/{components → ui}/form/multi_stop_gradient_selector.rs +4 -5
  183. package/src/rust/{components → ui}/form/named_value_picker.rs +1 -6
  184. package/src/rust/{components → ui}/form/number_field.rs +1 -1
  185. package/src/rust/{components → ui}/form/number_range_field.rs +1 -1
  186. package/src/rust/{components → ui}/form/optional_field.rs +3 -1
  187. package/src/rust/{components → ui}/form/palette_selector.rs +4 -5
  188. package/src/rust/{components → ui}/form/select_enum_field.rs +2 -2
  189. package/src/rust/{components → ui}/form/select_value_field.rs +2 -2
  190. package/src/rust/ui/mod.rs +56 -0
  191. package/src/rust/ui/modal.rs +61 -0
  192. package/src/rust/{components → ui}/portal.rs +175 -79
  193. package/src/rust/{components/style/style_provider.rs → ui/style.rs} +4 -34
  194. package/src/rust/utils/completion.rs +0 -6
  195. package/src/rust/{config/css_scale.rs → utils/css.rs} +38 -35
  196. package/src/rust/utils/css_vars.rs +13 -0
  197. package/src/rust/utils/local_fonts.rs +395 -0
  198. package/src/rust/utils/mod.rs +4 -0
  199. package/src/rust/workspace.rs +47 -0
  200. package/src/svg/boolean-type.svg +3 -2
  201. package/src/svg/checkbox-checked-icon.svg +4 -7
  202. package/src/svg/checkbox-hover.svg +3 -3
  203. package/src/svg/checkbox-off.svg +2 -3
  204. package/src/svg/checkbox-on.svg +3 -3
  205. package/src/svg/checkbox-unchecked-icon.svg +3 -3
  206. package/src/svg/datagrid-select-column.svg +8 -15
  207. package/src/svg/datagrid-select-region.svg +8 -15
  208. package/src/svg/datagrid-select-row-tree.svg +7 -12
  209. package/src/svg/datagrid-select-row.svg +8 -13
  210. package/src/svg/date-type.svg +4 -23
  211. package/src/svg/drawer-tab-hover.svg +4 -6
  212. package/src/svg/drawer-tab-invert-hover.svg +5 -5
  213. package/src/svg/drawer-tab-invert.svg +3 -5
  214. package/src/svg/drawer-tab.svg +5 -5
  215. package/src/svg/mega-menu-icons-candlestick.svg +5 -37
  216. package/src/svg/mega-menu-icons-ohlc.svg +4 -39
  217. package/src/svg/mega-menu-icons-sunburst.svg +4 -38
  218. package/src/svg/mega-menu-icons-treemap.svg +3 -43
  219. package/src/svg/mega-menu-icons-y-scatter.svg +12 -72
  220. package/src/svg/new-panel-icon.svg +4 -0
  221. package/src/svg/number-type.svg +3 -3
  222. package/src/svg/radio-hover.svg +3 -3
  223. package/src/svg/radio-off.svg +3 -3
  224. package/src/svg/radio-on.svg +3 -3
  225. package/src/svg/status_ok.svg +3 -26
  226. package/src/svg/status_pending.svg +3 -0
  227. package/src/svg/string-type.svg +4 -8
  228. package/src/svg/theme-icon.svg +5 -5
  229. package/src/themes/blueprint.css +0 -57
  230. package/src/themes/botanical.css +0 -36
  231. package/src/themes/defaults.css +1 -24
  232. package/src/themes/dracula.css +0 -58
  233. package/src/themes/eggplant.css +0 -58
  234. package/src/themes/gruvbox-dark.css +0 -65
  235. package/src/themes/gruvbox.css +1 -39
  236. package/src/themes/icons.css +2 -8
  237. package/src/themes/intl/ar.css +198 -0
  238. package/src/themes/intl/de.css +63 -40
  239. package/src/themes/intl/es.css +58 -35
  240. package/src/themes/intl/fr.css +57 -34
  241. package/src/themes/intl/ja.css +59 -36
  242. package/src/themes/intl/pt.css +54 -31
  243. package/src/themes/intl/zh.css +65 -42
  244. package/src/themes/intl.css +34 -8
  245. package/src/themes/ledger.css +1 -59
  246. package/src/themes/monokai.css +0 -51
  247. package/src/themes/nord.css +0 -55
  248. package/src/themes/phosphor.css +0 -48
  249. package/src/themes/pro-dark.css +0 -37
  250. package/src/themes/pro.css +3 -23
  251. package/src/themes/solarized-dark.css +0 -31
  252. package/src/themes/solarized.css +1 -24
  253. package/src/themes/vaporwave.css +0 -44
  254. package/src/themes/velvet.css +0 -60
  255. package/src/ts/extensions.ts +0 -13
  256. package/src/ts/perspective-viewer.ts +2 -0
  257. package/src/ts/plugin.ts +9 -0
  258. package/src/ts/ts-rs/AddPanelOptions.ts +10 -0
  259. package/src/ts/ts-rs/{DatetimeColorMode.ts → GetViewMode.ts} +4 -1
  260. package/src/ts/ts-rs/GetViewOptions.ts +7 -0
  261. package/src/ts/ts-rs/RestoreOptions.ts +7 -1
  262. package/src/ts/ts-rs/RestoreWorkspaceOptions.ts +10 -0
  263. package/src/ts/ts-rs/ViewOnUpdateResp.ts +1 -1
  264. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +31 -20
  265. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
  266. package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
  267. package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
  268. package/src/css/plugin-settings-panel.css +0 -171
  269. package/src/rust/components/modal.rs +0 -177
  270. package/src/rust/components/string_column_style.rs +0 -121
  271. package/src/rust/config/string_column_style.rs +0 -135
  272. package/src/rust/custom_elements/copy_dropdown.rs +0 -219
  273. package/src/rust/custom_elements/export_dropdown.rs +0 -216
  274. package/src/rust/custom_elements/modal.rs +0 -299
  275. package/src/ts/ts-rs/FormatMode.ts +0 -3
  276. package/src/ts/ts-rs/StringColorMode.ts +0 -3
  277. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline0.js +0 -0
  278. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline1.js +0 -0
  279. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline2.js +0 -0
  280. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline3.js +0 -0
  281. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline4.js +0 -0
  282. /package/dist/wasm/snippets/{perspective-viewer-87bd21b215d0631a → perspective-viewer-ea71a3990cad35d7}/inline5.js +0 -0
  283. /package/src/css/{containers → ui/containers}/scroll-panel.css +0 -0
  284. /package/src/css/{containers → ui/containers}/split-panel.css +0 -0
  285. /package/src/css/{dom → ui/dom}/checkbox.css +0 -0
  286. /package/src/css/{dom → ui/dom}/scrollbar.css +0 -0
  287. /package/src/css/{dom → ui/dom}/select.css +0 -0
  288. /package/src/css/{form → ui/form}/mirrored-textarea.css +0 -0
  289. /package/src/rust/components/{form → code_editor}/highlight.rs +0 -0
  290. /package/src/rust/{components → ui}/containers/sidebar_close_button.rs +0 -0
  291. /package/src/rust/{components → ui}/form/number_input.rs +0 -0
@@ -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();
@@ -204,6 +235,7 @@ impl PerspectiveViewer {
204
235
  None,
205
236
  update,
206
237
  client,
238
+ crate::session::MissingTable::Error,
207
239
  )
208
240
  .await?;
209
241
 
@@ -238,6 +270,7 @@ impl PerspectiveViewer {
238
270
  None,
239
271
  ViewerConfigInitial::new(table_name),
240
272
  client,
273
+ crate::session::MissingTable::Error,
241
274
  )
242
275
  .await?;
243
276
 
@@ -282,6 +315,7 @@ impl PerspectiveViewer {
282
315
  None,
283
316
  ViewerConfigInitial::new(table),
284
317
  Some(client),
318
+ crate::session::MissingTable::Error,
285
319
  )
286
320
  .await?;
287
321
 
@@ -22,15 +22,15 @@ use yew::prelude::*;
22
22
 
23
23
  use super::PerspectiveViewer;
24
24
  use super::msg::PerspectiveViewerMsg::*;
25
+ use super::msg::{Divider, PaneTarget};
25
26
  use crate::components::column_settings_sidebar::ColumnSettingsPanel;
26
- use crate::components::containers::split_panel::SplitPanel;
27
- use crate::components::font_loader::FontLoader;
28
27
  use crate::components::main_panel::MainPanel;
29
28
  use crate::components::panel_menu::PanelCommand;
30
29
  use crate::components::settings_panel::SettingsPanel;
31
- use crate::components::style::StyleProvider;
30
+ use crate::components::style::StyleSurface;
32
31
  use crate::queries::*;
33
32
  use crate::session::TableLoadState;
33
+ use crate::ui::{FontLoader, SplitPanel, StyleProvider};
34
34
 
35
35
  impl PerspectiveViewer {
36
36
  pub(super) fn render(&self, ctx: &Context<Self>) -> Html {
@@ -58,10 +58,6 @@ impl PerspectiveViewer {
58
58
  toggle: true,
59
59
  });
60
60
 
61
- let on_column_settings_panel_resize = ctx
62
- .link()
63
- .callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
64
-
65
61
  let on_close_settings = ctx
66
62
  .link()
67
63
  .callback(|()| ToggleSettingsInit(None, true, None));
@@ -99,6 +95,7 @@ impl PerspectiveViewer {
99
95
  plugin_static_config={self.renderer_props.config.clone()}
100
96
  {view_config}
101
97
  plugin_config={self.renderer_props.plugin_config.clone()}
98
+ columns_config={self.renderer_props.columns_config.clone()}
102
99
  {drag_column}
103
100
  metadata={metadata.clone()}
104
101
  open_column_settings={self.presentation_props.open_column_settings.clone()}
@@ -135,9 +132,12 @@ impl PerspectiveViewer {
135
132
  id="modal_panel"
136
133
  class={classes!(is_pinned.then_some("pinned"))}
137
134
  reverse=true
135
+ deferred={is_pinned}
136
+ size={self.settings_geometry.column_settings_width_override}
138
137
  initial_size={self.settings_geometry.column_settings_width_override}
139
- on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
140
- on_resize={on_column_settings_panel_resize}
138
+ on_reset={ctx.link().callback(|_| DividerMove(Divider::ColumnSettings, PaneTarget::Natural))}
139
+ on_resize={ctx.link().callback(|(x, _): (i32, i32)| DividerMove(Divider::ColumnSettings, PaneTarget::Width(x)))}
140
+ on_resize_finished={ctx.link().callback(|_| DividerFinish(Divider::ColumnSettings))}
141
141
  >
142
142
  <ColumnSettingsPanel
143
143
  {selected_column}
@@ -171,6 +171,7 @@ impl PerspectiveViewer {
171
171
  {on_reset}
172
172
  on_activate_panel={ctx.link().callback(|id| SetActivePanel(id, None))}
173
173
  on_close_panel={ctx.link().callback(|id| ClosePanel(id, None))}
174
+ on_panel_closed={ctx.link().callback(PanelClosed)}
174
175
  on_panel_command={ctx.link().batch_callback(|(id, cmd): (String, PanelCommand)| {
175
176
  match cmd {
176
177
  PanelCommand::New => vec![NewPanel(id)],
@@ -178,6 +179,7 @@ impl PerspectiveViewer {
178
179
  vec![NewPanelFrom { client, table }]
179
180
  },
180
181
  PanelCommand::Duplicate => vec![DuplicatePanel(id)],
182
+ PanelCommand::NewFromPanel(source) => vec![DuplicatePanel(source)],
181
183
  PanelCommand::Reset => vec![ResetPanel(Some(id), false, None)],
182
184
  PanelCommand::ToggleMaster => vec![ToggleMaster(id)],
183
185
  PanelCommand::Close => vec![ClosePanel(id, None)],
@@ -233,8 +235,9 @@ impl PerspectiveViewer {
233
235
  };
234
236
 
235
237
  html! {
236
- <StyleProvider root={ctx.props().elem.clone()}>
238
+ <StyleProvider root={ctx.props().elem.clone()} sheet={StyleSurface::Viewer.sheet()}>
237
239
  <div id="component_container" class={is_single_panel}>
240
+ <slot name="modal" />
238
241
  <div id="layout_area">
239
242
  <SplitPanel
240
243
  id="app_panel"
@@ -243,9 +246,9 @@ impl PerspectiveViewer {
243
246
  deferred=true
244
247
  size={self.settings_geometry.pane_width_override}
245
248
  initial_size={self.settings_geometry.pane_width_override}
246
- on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
247
- on_resize={ctx.link().callback(|(x, _): (i32, i32)| SettingsDividerMove(x))}
248
- on_resize_finished={ctx.link().callback(|_| SettingsDividerFinish)}
249
+ on_reset={ctx.link().callback(|_| DividerMove(Divider::Settings, PaneTarget::Natural))}
250
+ on_resize={ctx.link().callback(|(x, _): (i32, i32)| DividerMove(Divider::Settings, PaneTarget::Width(x)))}
251
+ on_resize_finished={ctx.link().callback(|_| DividerFinish(Divider::Settings))}
249
252
  >
250
253
  { settings_panel }
251
254
  <div id="main_column_container">
@@ -10,11 +10,6 @@
10
10
  // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11
11
  // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
12
 
13
- //! Settings-sidebar handlers: the open/close toggle (with its presize
14
- //! choreography), the deferred divider's latest-wins presize pump
15
- //! (`PRESIZE_EVERYWHERE_PLAN.md` P1/P2), and the column-settings drawer. The
16
- //! presize sweeps themselves live in [`crate::tasks`] (`presize_panels`).
17
-
18
13
  use futures::channel::oneshot::{Sender, channel};
19
14
  use perspective_js::utils::*;
20
15
  use wasm_bindgen::prelude::*;
@@ -22,12 +17,13 @@ use yew::prelude::*;
22
17
 
23
18
  use super::PerspectiveViewer;
24
19
  use super::msg::PerspectiveViewerMsg::*;
25
- use crate::components::font_loader::FontLoaderStatus;
20
+ use super::msg::{Divider, PaneTarget};
26
21
  use crate::components::settings_panel::SelectedTab;
27
22
  use crate::config::*;
28
23
  use crate::presentation::{ColumnSettingsTab, ColumnSettingsTarget, OpenColumnSettings};
29
24
  use crate::queries::get_current_column_locator;
30
25
  use crate::tasks::*;
26
+ use crate::ui::FontLoaderStatus;
31
27
 
32
28
  /// The settings sidebar's geometry state, folded into one field on
33
29
  /// [`PerspectiveViewer`]: the pane/drawer width overrides, the divider
@@ -44,11 +40,9 @@ pub(super) struct SettingsGeometry {
44
40
  /// High-water-mark auto width reported by the settings panel.
45
41
  pub auto_width: f64,
46
42
 
47
- /// Latest-wins presize pump for the (deferred) settings divider: the
48
- /// newest proposed pane width not yet presized, and whether a pump
49
- /// iteration is in flight. See `SettingsDividerMove`.
50
- pub divider_target: Option<i32>,
51
- pub divider_pumping: bool,
43
+ pub settings_divider: DividerPump,
44
+
45
+ pub column_settings_divider: DividerPump,
52
46
 
53
47
  /// Open-state geometry deltas `(layout_area.w − mpc.w, main_column.h −
54
48
  /// mpc.h)`, cached at settings *close* time — used to presize panels to
@@ -85,6 +79,29 @@ pub(super) struct SettingsGeometry {
85
79
  pub column_settings_docked_width: Option<f64>,
86
80
  }
87
81
 
82
+ #[derive(Default)]
83
+ pub(super) struct DividerPump {
84
+ pub target: Option<PaneTarget>,
85
+ pub pumping: bool,
86
+ pub finish_pending: bool,
87
+ }
88
+
89
+ impl SettingsGeometry {
90
+ fn pump_mut(&mut self, divider: Divider) -> &mut DividerPump {
91
+ match divider {
92
+ Divider::Settings => &mut self.settings_divider,
93
+ Divider::ColumnSettings => &mut self.column_settings_divider,
94
+ }
95
+ }
96
+
97
+ fn is_divider_deferred(&self, divider: Divider) -> bool {
98
+ match divider {
99
+ Divider::Settings => true,
100
+ Divider::ColumnSettings => self.column_settings_pinned,
101
+ }
102
+ }
103
+ }
104
+
88
105
  impl PerspectiveViewer {
89
106
  pub(super) fn on_toggle_settings_init(
90
107
  &mut self,
@@ -257,84 +274,143 @@ impl PerspectiveViewer {
257
274
  };
258
275
  }
259
276
 
260
- pub(super) fn on_settings_panel_size_update(&mut self, x: Option<i32>) -> bool {
261
- match x {
262
- Some(x) => {
263
- self.settings_geometry.pane_width_override = Some(x);
264
- false
277
+ fn apply_pane_target(&mut self, divider: Divider, target: PaneTarget) -> bool {
278
+ let deferred = self.settings_geometry.is_divider_deferred(divider);
279
+ match (divider, target) {
280
+ (Divider::Settings, PaneTarget::Width(w)) => {
281
+ self.settings_geometry.pane_width_override = Some(w);
282
+ true
265
283
  },
266
- None => {
284
+ (Divider::Settings, PaneTarget::Natural) => {
267
285
  self.settings_geometry.pane_width_override = None;
268
286
  self.settings_geometry.auto_width = 0.0;
269
287
  self.on_settings_panel_dimensions_reset.emit(());
270
288
  true
271
289
  },
290
+ (Divider::ColumnSettings, PaneTarget::Width(w)) => {
291
+ self.settings_geometry.column_settings_width_override = Some(w);
292
+ deferred
293
+ },
294
+ (Divider::ColumnSettings, PaneTarget::Natural) => {
295
+ self.settings_geometry.column_settings_width_override = None;
296
+ self.settings_geometry.column_settings_auto_width = 0.0;
297
+ true
298
+ },
272
299
  }
273
300
  }
274
301
 
275
- pub(super) fn on_settings_divider_move(
302
+ pub(super) fn on_divider_move(
276
303
  &mut self,
277
304
  ctx: &Context<Self>,
278
- pane_width: i32,
305
+ divider: Divider,
306
+ target: PaneTarget,
279
307
  ) -> bool {
308
+ if !self.settings_geometry.is_divider_deferred(divider) {
309
+ return self.apply_pane_target(divider, target);
310
+ }
311
+
280
312
  // Latest-wins: overwrite the pending target; start the pump if
281
313
  // idle. Intermediate targets that arrive while a presize is in
282
314
  // flight are dropped (mirrors `PresizeQueue`'s single queued
283
315
  // slot) — the pane tracks the pointer at content-render rate.
284
- self.settings_geometry.divider_target = Some(pane_width);
285
- if !self.settings_geometry.divider_pumping {
286
- self.settings_geometry.divider_pumping = true;
287
- ctx.link().send_message(SettingsDividerPump);
316
+ let pump = self.settings_geometry.pump_mut(divider);
317
+ pump.target = Some(target);
318
+ if !pump.pumping {
319
+ pump.pumping = true;
320
+ ctx.link().send_message(DividerPump(divider));
288
321
  }
289
322
 
290
323
  false
291
324
  }
292
325
 
293
- pub(super) fn on_settings_divider_pump(&mut self, ctx: &Context<Self>) -> bool {
294
- if let Some(pane_width) = self.settings_geometry.divider_target.take() {
295
- let workspace = ctx.props().workspace.clone();
296
- let elem = ctx.props().elem.clone();
297
- let link = ctx.link().clone();
298
- ApiFuture::spawn(async move {
299
- let presents =
300
- presize_visible_panels_pane_width(&workspace, &elem, pane_width as f64).await;
301
- link.send_message(SettingsDividerCommit(pane_width));
302
- // Same task as the commit's re-render (whether Yew drained it
303
- // synchronously inside `send_message` or deferred it to a
304
- // microtask): the staged reveal and the pane-width geometry
305
- // land in one paint.
306
- presents.reveal();
307
- Ok(())
308
- });
309
- } else {
310
- self.settings_geometry.divider_pumping = false;
311
- }
326
+ pub(super) fn on_divider_pump(&mut self, ctx: &Context<Self>, divider: Divider) -> bool {
327
+ let workspace = ctx.props().workspace.clone();
328
+ let pump = self.settings_geometry.pump_mut(divider);
329
+ let Some(target) = pump.target.take() else {
330
+ pump.pumping = false;
331
+ if std::mem::take(&mut pump.finish_pending) {
332
+ ApiFuture::spawn(async move {
333
+ resize_visible_panels(&workspace).await;
334
+ Ok(())
335
+ });
336
+ }
337
+
338
+ return false;
339
+ };
340
+
341
+ let elem = ctx.props().elem.clone();
342
+ let pane_width = match target {
343
+ PaneTarget::Width(w) => Some(w as f64),
344
+ PaneTarget::Natural => measure_pane_natural_width(&elem, divider.pane_selector()),
345
+ };
346
+
347
+ let link = ctx.link().clone();
348
+ ApiFuture::spawn(async move {
349
+ let presents = match pane_width {
350
+ Some(pane_width) => {
351
+ presize_visible_panels_pane_width(
352
+ &workspace,
353
+ &elem,
354
+ divider.pane_selector(),
355
+ pane_width,
356
+ )
357
+ .await
358
+ },
359
+ None => StagedPresents::default(),
360
+ };
361
+
362
+ link.send_message(DividerCommit(divider, target));
363
+ presents.reveal();
364
+ Ok(())
365
+ });
312
366
 
313
367
  false
314
368
  }
315
369
 
316
- pub(super) fn on_settings_divider_commit(
370
+ pub(super) fn on_divider_commit(
317
371
  &mut self,
318
372
  ctx: &Context<Self>,
319
- pane_width: i32,
373
+ divider: Divider,
374
+ target: PaneTarget,
320
375
  ) -> bool {
321
376
  // Every visible panel has rendered at (approximately) its
322
377
  // target box — NOW move the geometry: the re-render below
323
- // applies this width to the deferred `SplitPanel`'s controlled
378
+ // applies this target to the deferred `SplitPanel`'s controlled
324
379
  // `size`, in the same task as the presizes' inline clears.
325
- self.settings_geometry.pane_width_override = Some(pane_width);
326
- ctx.link().send_message(SettingsDividerPump);
380
+ self.apply_pane_target(divider, target);
381
+ if target == PaneTarget::Natural {
382
+ self.settings_geometry.pump_mut(divider).finish_pending = true;
383
+ }
384
+
385
+ ctx.link().send_message(DividerPump(divider));
327
386
  true
328
387
  }
329
388
 
330
- pub(super) fn on_settings_divider_finish(&mut self, ctx: &Context<Self>) -> bool {
389
+ pub(super) fn on_divider_finish(&mut self, ctx: &Context<Self>, divider: Divider) -> bool {
390
+ if !self.settings_geometry.is_divider_deferred(divider) {
391
+ return false;
392
+ }
393
+
394
+ let pump = self.settings_geometry.pump_mut(divider);
395
+ if pump.pumping {
396
+ pump.finish_pending = true;
397
+ } else {
398
+ let workspace = ctx.props().workspace.clone();
399
+ ApiFuture::spawn(async move {
400
+ resize_visible_panels(&workspace).await;
401
+ Ok(())
402
+ });
403
+ }
404
+
405
+ false
406
+ }
407
+
408
+ fn resize_for_ratchet(&self, ctx: &Context<Self>) {
331
409
  let workspace = ctx.props().workspace.clone();
332
410
  ApiFuture::spawn(async move {
333
411
  resize_visible_panels(&workspace).await;
334
412
  Ok(())
335
413
  });
336
-
337
- false
338
414
  }
339
415
 
340
416
  pub(super) fn on_settings_panel_tab_changed(&mut self, tab: SelectedTab) -> bool {
@@ -343,9 +419,10 @@ impl PerspectiveViewer {
343
419
  changed
344
420
  }
345
421
 
346
- pub(super) fn on_settings_panel_auto_width(&mut self, w: f64) -> bool {
422
+ pub(super) fn on_settings_panel_auto_width(&mut self, ctx: &Context<Self>, w: f64) -> bool {
347
423
  if w > self.settings_geometry.auto_width {
348
424
  self.settings_geometry.auto_width = w;
425
+ self.resize_for_ratchet(ctx);
349
426
  true
350
427
  } else {
351
428
  false
@@ -397,19 +474,17 @@ impl PerspectiveViewer {
397
474
  true
398
475
  }
399
476
 
400
- pub(super) fn on_column_settings_panel_size_update(&mut self, x: Option<i32>) -> bool {
401
- self.settings_geometry.column_settings_width_override = x;
402
- if x.is_none() {
403
- self.settings_geometry.column_settings_auto_width = 0.0;
404
- true
405
- } else {
406
- false
407
- }
408
- }
409
-
410
- pub(super) fn on_column_settings_panel_auto_width(&mut self, w: f64) -> bool {
477
+ pub(super) fn on_column_settings_panel_auto_width(
478
+ &mut self,
479
+ ctx: &Context<Self>,
480
+ w: f64,
481
+ ) -> bool {
411
482
  if w > self.settings_geometry.column_settings_auto_width {
412
483
  self.settings_geometry.column_settings_auto_width = w;
484
+ if self.settings_geometry.column_settings_pinned {
485
+ self.resize_for_ratchet(ctx);
486
+ }
487
+
413
488
  true
414
489
  } else {
415
490
  false
@@ -561,12 +636,6 @@ impl PerspectiveViewer {
561
636
  true
562
637
  }
563
638
 
564
- /// Toggling the debug tab re-renders the settings panel only — the
565
- /// `DebugPanel` populates itself (`get_viewer_config` on mount +
566
- /// change subscriptions), so no plugin dispatch is owed. The old
567
- /// `just_render` here relied on the pre-amendment unconditional
568
- /// `Unchanged → update` arm and repainted the plugin as a side effect
569
- /// (`PLUGIN_DRAW_INVARIANT_PLAN.md` amendment, migration 2).
570
639
  pub(super) fn on_toggle_debug(&mut self, _ctx: &Context<Self>) -> bool {
571
640
  self.debug_open = !self.debug_open;
572
641
  true
@@ -22,7 +22,7 @@ use yew::prelude::*;
22
22
  use super::PerspectiveViewer;
23
23
  use crate::presentation::{DragDropProps, PresentationProps};
24
24
  use crate::renderer::RendererProps;
25
- use crate::session::{SessionProps, TableLoadState, ViewStats};
25
+ use crate::session::{SessionProps, TableLoadState};
26
26
  use crate::tasks::resize_visible_panels;
27
27
 
28
28
  impl PerspectiveViewer {
@@ -59,12 +59,12 @@ impl PerspectiveViewer {
59
59
 
60
60
  pub(super) fn on_update_session_stats(
61
61
  &mut self,
62
- stats: Option<ViewStats>,
62
+ has_table_cells: bool,
63
63
  has_table: Option<TableLoadState>,
64
64
  ) -> bool {
65
- let changed =
66
- stats != self.session_props.stats || has_table != self.session_props.has_table;
67
- self.session_props.stats = stats;
65
+ let changed = has_table_cells != self.session_props.has_table_cells
66
+ || has_table != self.session_props.has_table;
67
+ self.session_props.has_table_cells = has_table_cells;
68
68
  self.session_props.has_table = has_table;
69
69
  changed
70
70
  }
@@ -146,11 +146,6 @@ impl PerspectiveViewer {
146
146
  changed
147
147
  }
148
148
 
149
- /// LEVEL-triggered spinner count: ASSIGN the absolute in-flight
150
- /// config-run count from the session's RAII accounting — any missed or
151
- /// reordered notification is corrected by the next one, unlike the
152
- /// edge-counted increment/decrement pair this replaces (see
153
- /// `UPDATE_COUNT_REGRESSION_PLAN.md`).
154
149
  pub(super) fn on_update_in_flight(&mut self, count: u32) -> bool {
155
150
  let changed = count != self.update_count;
156
151
  self.update_count = count;
@@ -280,28 +280,14 @@ pub(super) fn create_active_subscriptions(
280
280
 
281
281
  let sub1 = session.table_loaded.add_notify_listener(&cb);
282
282
  let sub2 = session.table_unloaded.add_notify_listener(&cb);
283
- // Snapshot refresh on every RECONCILED commit (SKIP/REUSE/paused
284
- // included), not just `View` construction.
285
283
  let sub3 = session.commit_reconciled.add_notify_listener(&cb);
286
284
  let sub4 = session.view_config_changed.add_notify_listener(&cb);
287
285
  let sub5 = session.title_changed.add_notify_listener(&cb);
288
-
289
- // The `StatusIndicator` spinner count. LEVEL-triggered: the payload
290
- // is the ABSOLUTE in-flight config-run count (RAII-settled by
291
- // `ConfigRunToken`), which the handler ASSIGNS — replacing the
292
- // edge-counted `view_config_changed`+1/`view_created`−1 pair, whose
293
- // pairing the pipeline does not guarantee (stuck-spinner
294
- // regression — see `UPDATE_COUNT_REGRESSION_PLAN.md`).
295
286
  let sub6 = session
296
287
  .run_state_changed
297
288
  .add_listener(ctx.link().callback(UpdateInFlight));
298
289
 
299
- // Stats fetch resolution (populates session.column_stats) triggers
300
- // a fresh `SessionProps` so `column_stats` reaches downstream
301
- // components and the StyleTab re-queries the schema with the
302
- // new value.
303
290
  let sub7 = session.column_stats_changed.add_notify_listener(&cb);
304
-
305
291
  vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7]
306
292
  };
307
293
 
@@ -311,12 +297,6 @@ pub(super) fn create_active_subscriptions(
311
297
  move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
312
298
  });
313
299
 
314
- // Re-snapshot RendererProps when the plugin_config bucket
315
- // changes (in-tab edit via `send_plugin_config`, JSON paste via
316
- // `restore_and_render`, full clear via `reset_all` with
317
- // `all=true`). Without this, `RendererProps.plugin_config`
318
- // would stay frozen at its construct-time value and `PluginTab`
319
- // would render stale.
320
300
  let cb_plugin_config = ctx.link().callback({
321
301
  let renderer = renderer.clone();
322
302
  move |_: serde_json::Map<String, serde_json::Value>| {
@@ -324,12 +304,21 @@ pub(super) fn create_active_subscriptions(
324
304
  }
325
305
  });
326
306
 
307
+ let cb_columns_config = ctx.link().callback({
308
+ let renderer = renderer.clone();
309
+ move |_: ColumnConfigMap| UpdateRenderer(Box::new(renderer.to_props(None)))
310
+ });
311
+
327
312
  let sub1 = renderer.plugin_changed.add_listener(cb_plugin);
328
313
  let sub2 = renderer
329
314
  .plugin_config_changed
330
315
  .add_listener(cb_plugin_config);
331
316
 
332
- vec![sub1, sub2]
317
+ let sub3 = renderer
318
+ .columns_config_changed
319
+ .add_listener(cb_columns_config);
320
+
321
+ vec![sub1, sub2, sub3]
333
322
  };
334
323
 
335
324
  let mut subscriptions = Vec::new();
@@ -413,7 +402,7 @@ pub(super) fn inject_active_callbacks(
413
402
  let s = session.clone();
414
403
  let cb = ctx
415
404
  .link()
416
- .callback(move |_: ()| UpdateSessionStats(s.get_table_stats(), s.has_table()));
405
+ .callback(move |_: ()| UpdateSessionStats(s.has_table_cells(), s.has_table()));
417
406
 
418
407
  *session.on_stats_changed.borrow_mut() = Some(cb);
419
408
  }