@perspective-dev/viewer 4.5.1 → 5.0.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 (212) hide show
  1. package/dist/cdn/perspective-viewer.js +20 -1
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/monokai.css +1 -1
  9. package/dist/css/phosphor.css +1 -1
  10. package/dist/css/pro-dark.css +1 -1
  11. package/dist/css/pro.css +1 -1
  12. package/dist/css/solarized-dark.css +1 -1
  13. package/dist/css/solarized.css +1 -1
  14. package/dist/css/themes.css +1 -1
  15. package/dist/css/vaporwave.css +1 -1
  16. package/dist/esm/extensions.d.ts +22 -3
  17. package/dist/esm/perspective-viewer.cdn.d.ts +3 -0
  18. package/dist/esm/perspective-viewer.d.ts +9 -0
  19. package/dist/esm/perspective-viewer.inline.d.ts +3 -0
  20. package/dist/esm/perspective-viewer.inline.js +20 -1
  21. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  22. package/dist/esm/perspective-viewer.js +20 -1
  23. package/dist/esm/perspective-viewer.js.map +4 -4
  24. package/dist/esm/plugin.d.ts +71 -5
  25. package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
  26. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
  27. package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
  28. package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
  29. package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
  30. package/dist/esm/ts-rs/Layout.d.ts +11 -0
  31. package/dist/esm/ts-rs/OnUpdateData.d.ts +6 -0
  32. package/dist/esm/ts-rs/Orientation.d.ts +4 -0
  33. package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
  34. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +39 -0
  35. package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
  36. package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
  37. package/dist/esm/ts-rs/ViewOnUpdateResp.d.ts +4 -0
  38. package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -2
  39. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
  40. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
  41. package/dist/wasm/perspective-viewer.d.ts +213 -79
  42. package/dist/wasm/perspective-viewer.js +537 -189
  43. package/dist/wasm/perspective-viewer.wasm +0 -0
  44. package/dist/wasm/perspective-viewer.wasm.d.ts +36 -28
  45. package/dist/wasm/snippets/perspective-viewer-daeef4ceb066e6c7/inline0.js +6 -0
  46. package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline3.js → perspective-viewer-daeef4ceb066e6c7/inline4.js} +5 -0
  47. package/package.json +3 -2
  48. package/src/css/_column_dropdown.css +14 -0
  49. package/src/css/_context_menu.css +14 -0
  50. package/src/css/_dropdown_menu.css +14 -0
  51. package/src/css/_filter_dropdown.css +14 -0
  52. package/src/css/_function_dropdown.css +14 -0
  53. package/src/css/_viewer_bundle.css +41 -0
  54. package/src/css/column-settings-panel.css +1 -1
  55. package/src/css/containers/context-menu.css +101 -0
  56. package/src/css/panel-tab.css +193 -0
  57. package/src/css/plugin-selector.css +7 -10
  58. package/src/css/status-bar.css +75 -81
  59. package/src/css/viewer.css +181 -71
  60. package/src/rust/components/column_dropdown.rs +43 -33
  61. package/src/rust/components/column_selector/active_column.rs +7 -18
  62. package/src/rust/components/column_selector/aggregate_selector.rs +4 -10
  63. package/src/rust/components/column_selector/config_selector.rs +33 -84
  64. package/src/rust/components/column_selector/empty_column.rs +0 -3
  65. package/src/rust/components/column_selector/filter_column.rs +5 -18
  66. package/src/rust/components/column_selector/inactive_column.rs +3 -8
  67. package/src/rust/components/column_selector/invalid_column.rs +0 -4
  68. package/src/rust/components/column_selector/sort_column.rs +3 -6
  69. package/src/rust/components/column_selector.rs +5 -14
  70. package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +0 -3
  71. package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +0 -3
  72. package/src/rust/components/column_settings_sidebar.rs +0 -3
  73. package/src/rust/components/containers/dropdown_menu.rs +1 -3
  74. package/src/rust/components/containers/scroll_panel.rs +1 -3
  75. package/src/rust/components/containers/split_panel.rs +175 -66
  76. package/src/rust/components/containers/tab_list.rs +0 -3
  77. package/src/rust/components/context_menu.rs +130 -0
  78. package/src/rust/components/datetime_column_style.rs +0 -3
  79. package/src/rust/components/empty_row.rs +0 -3
  80. package/src/rust/components/expression_editor.rs +2 -3
  81. package/src/rust/components/filter_dropdown.rs +3 -3
  82. package/src/rust/components/font_loader.rs +2 -2
  83. package/src/rust/components/form/code_editor.rs +0 -3
  84. package/src/rust/components/form/debug.rs +3 -4
  85. package/src/rust/components/function_dropdown.rs +3 -3
  86. package/src/rust/components/global_filter_bar.rs +76 -0
  87. package/src/rust/components/main_panel/frame_theme.rs +186 -0
  88. package/src/rust/components/main_panel/msg.rs +62 -0
  89. package/src/rust/components/main_panel/presize.rs +220 -0
  90. package/src/rust/components/main_panel/reconcile.rs +224 -0
  91. package/src/rust/components/main_panel/render.rs +385 -0
  92. package/src/rust/components/main_panel/update.rs +184 -0
  93. package/src/rust/components/main_panel.rs +311 -71
  94. package/src/rust/components/mod.rs +4 -0
  95. package/src/rust/components/number_series_style.rs +0 -3
  96. package/src/rust/components/panel_menu.rs +486 -0
  97. package/src/rust/components/panel_tab.rs +505 -0
  98. package/src/rust/components/plugin_selector.rs +0 -3
  99. package/src/rust/components/plugin_tab.rs +1 -6
  100. package/src/rust/components/portal.rs +5 -2
  101. package/src/rust/components/render_warning.rs +0 -3
  102. package/src/rust/components/settings_panel.rs +13 -5
  103. package/src/rust/components/status_bar.rs +113 -180
  104. package/src/rust/components/status_indicator.rs +6 -9
  105. package/src/rust/components/string_column_style.rs +0 -3
  106. package/src/rust/components/style/mod.rs +65 -27
  107. package/src/rust/components/style/style_provider.rs +51 -16
  108. package/src/rust/components/style_controls/number_string_format.rs +1 -8
  109. package/src/rust/components/type_icon.rs +1 -4
  110. package/src/rust/components/viewer/filters.rs +133 -0
  111. package/src/rust/components/viewer/msg.rs +180 -0
  112. package/src/rust/components/viewer/panels.rs +326 -0
  113. package/src/rust/components/viewer/render.rs +243 -0
  114. package/src/rust/components/viewer/settings.rs +402 -0
  115. package/src/rust/components/viewer/snapshots.rs +134 -0
  116. package/src/rust/components/viewer/wiring.rs +505 -0
  117. package/src/rust/components/viewer.rs +243 -758
  118. package/src/rust/config/export_method.rs +1 -1
  119. package/src/rust/config/mod.rs +4 -0
  120. package/src/rust/config/options.rs +71 -0
  121. package/src/rust/config/viewer_config.rs +24 -2
  122. package/src/rust/config/workspace_config.rs +92 -0
  123. package/src/rust/custom_elements/copy_dropdown.rs +9 -3
  124. package/src/rust/custom_elements/debug_plugin.rs +20 -1
  125. package/src/rust/custom_elements/export_dropdown.rs +9 -3
  126. package/src/rust/custom_elements/viewer.rs +1015 -286
  127. package/src/rust/custom_events.rs +212 -61
  128. package/src/rust/js/mod.rs +2 -0
  129. package/src/rust/js/plugin.rs +297 -10
  130. package/src/rust/js/regular_layout.rs +258 -0
  131. package/src/rust/lib.rs +20 -2
  132. package/src/rust/presentation.rs +66 -33
  133. package/src/rust/queries/get_viewer_config.rs +29 -11
  134. package/src/rust/queries/plugin_column_styles.rs +2 -2
  135. package/src/rust/renderer/activate.rs +41 -14
  136. package/src/rust/renderer/dispatch.rs +436 -0
  137. package/src/rust/renderer/plugin_config.rs +495 -0
  138. package/src/rust/renderer/registry.rs +18 -6
  139. package/src/rust/renderer/render_timer.rs +29 -10
  140. package/src/rust/renderer.rs +350 -827
  141. package/src/rust/session/column_defaults_update.rs +26 -8
  142. package/src/rust/session/metadata.rs +11 -1
  143. package/src/rust/session/view_subscription.rs +30 -15
  144. package/src/rust/session.rs +753 -251
  145. package/src/rust/tasks/apply_global_filters.rs +89 -0
  146. package/src/rust/tasks/auto_pause.rs +171 -0
  147. package/src/rust/tasks/copy_export.rs +29 -19
  148. package/src/rust/tasks/create_panel.rs +243 -0
  149. package/src/rust/tasks/dismiss_render_warning.rs +3 -1
  150. package/src/rust/tasks/edit_expression.rs +4 -4
  151. package/src/rust/tasks/eject.rs +64 -23
  152. package/src/rust/tasks/mod.rs +28 -4
  153. package/src/rust/tasks/pipeline.rs +411 -0
  154. package/src/rust/tasks/presize_panels.rs +324 -0
  155. package/src/rust/tasks/reset_all.rs +22 -12
  156. package/src/rust/tasks/resize_observer.rs +124 -87
  157. package/src/rust/tasks/restore_and_render.rs +80 -86
  158. package/src/rust/tasks/restore_panel.rs +145 -0
  159. package/src/rust/tasks/send_column_config.rs +5 -2
  160. package/src/rust/tasks/send_plugin_config.rs +5 -2
  161. package/src/rust/tasks/set_edit_mode.rs +59 -0
  162. package/src/rust/tasks/sync_update_panels.rs +152 -0
  163. package/src/rust/tasks/table_lifecycle.rs +158 -0
  164. package/src/rust/tasks/update_theme.rs +56 -12
  165. package/src/rust/utils/completion.rs +56 -0
  166. package/src/rust/utils/custom_element.rs +5 -0
  167. package/src/rust/utils/debounce.rs +130 -7
  168. package/src/rust/utils/mod.rs +6 -0
  169. package/src/rust/utils/pending_effects.rs +62 -0
  170. package/src/rust/{components/style/local_style.rs → utils/spawn.rs} +17 -19
  171. package/src/rust/utils/weak_scope.rs +0 -10
  172. package/src/rust/workspace.rs +846 -0
  173. package/src/svg/active-indicator.svg +3 -0
  174. package/src/themes/botanical.css +7 -2
  175. package/src/themes/defaults.css +54 -2
  176. package/src/themes/dracula.css +8 -3
  177. package/src/themes/gruvbox-dark.css +7 -2
  178. package/src/themes/gruvbox.css +5 -2
  179. package/src/themes/icons.css +4 -2
  180. package/src/themes/monokai.css +7 -2
  181. package/src/themes/phosphor.css +7 -2
  182. package/src/themes/pro-dark.css +7 -2
  183. package/src/themes/pro.css +2 -1
  184. package/src/themes/solarized-dark.css +5 -2
  185. package/src/themes/solarized.css +5 -2
  186. package/src/themes/vaporwave.css +7 -2
  187. package/src/ts/bootstrap.ts +8 -7
  188. package/src/ts/extensions.ts +30 -2
  189. package/src/ts/perspective-viewer.ts +10 -0
  190. package/src/ts/plugin.ts +72 -5
  191. package/src/ts/ts-rs/ClientOptions.ts +7 -0
  192. package/src/ts/ts-rs/ExportMethod.ts +3 -0
  193. package/src/ts/ts-rs/ExportOptions.ts +8 -0
  194. package/src/ts/ts-rs/GetClientOptions.ts +7 -0
  195. package/src/ts/ts-rs/GetTableOptions.ts +7 -0
  196. package/src/ts/ts-rs/Layout.ts +9 -0
  197. package/src/ts/ts-rs/Orientation.ts +6 -0
  198. package/src/ts/ts-rs/PanelOptions.ts +7 -0
  199. package/src/ts/ts-rs/PanelViewerConfig.ts +15 -0
  200. package/src/ts/ts-rs/SplitLayout.ts +5 -0
  201. package/src/ts/ts-rs/TabLayout.ts +11 -0
  202. package/src/ts/ts-rs/ViewerConfig.ts +6 -2
  203. package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
  204. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
  205. package/tsconfig.json +1 -1
  206. package/src/rust/components/style/style_cache.rs +0 -136
  207. package/src/rust/tasks/intersection_observer.rs +0 -98
  208. package/src/rust/tasks/update_and_render.rs +0 -110
  209. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline0.js → perspective-viewer-daeef4ceb066e6c7/inline1.js} +0 -0
  210. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline1.js → perspective-viewer-daeef4ceb066e6c7/inline2.js} +0 -0
  211. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline2.js → perspective-viewer-daeef4ceb066e6c7/inline3.js} +0 -0
  212. /package/dist/wasm/snippets/{perspective-viewer-3cd58f0374935772/inline4.js → perspective-viewer-daeef4ceb066e6c7/inline5.js} +0 -0
@@ -0,0 +1,326 @@
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
+ //! Panel-lifecycle handlers: the [`Workspace`] panel set (add / duplicate /
14
+ //! close) and active-panel targeting. INVARIANT: app-initiated layout changes
15
+ //! mutate the `Workspace` FIRST, synchronously — `regular-layout` is
16
+ //! reconciled as a downstream consequence of the re-render, never the source
17
+ //! of truth (see `ClosePanel`).
18
+ //!
19
+ //! [`Workspace`]: crate::workspace::Workspace
20
+
21
+ use perspective_js::utils::*;
22
+ use yew::prelude::*;
23
+
24
+ use super::PerspectiveViewer;
25
+ use super::msg::PerspectiveViewerMsg::*;
26
+ use super::wiring::{
27
+ clear_active_callbacks, create_active_subscriptions, inject_active_callbacks,
28
+ subscribe_panel_titles,
29
+ };
30
+ use crate::config::{TableUpdate, ViewerConfigUpdate};
31
+ use crate::queries::*;
32
+ use crate::renderer::Renderer;
33
+ use crate::session::*;
34
+ use crate::tasks::*;
35
+ use crate::utils::{Completion, spawn_owned};
36
+ use crate::workspace::PanelId;
37
+
38
+ impl PerspectiveViewer {
39
+ pub(super) fn on_layout_changed(&mut self, ctx: &Context<Self>) -> bool {
40
+ self._title_subscriptions = subscribe_panel_titles(ctx);
41
+ self.resync_active(ctx);
42
+ true
43
+ }
44
+
45
+ /// Level-triggered binding sync: re-point the root's engine bindings at
46
+ /// the model's *current* active panel (or the detached empty engines at
47
+ /// zero panels) when they differ, by `Rc` identity. Catches model-side
48
+ /// activations that never dispatch `SetActivePanel`.
49
+ fn resync_active(&mut self, ctx: &Context<Self>) -> bool {
50
+ let session = ctx
51
+ .props()
52
+ .workspace
53
+ .active_session()
54
+ .unwrap_or_else(|| self.empty_session.clone());
55
+ let renderer = ctx
56
+ .props()
57
+ .workspace
58
+ .active_renderer()
59
+ .unwrap_or_else(|| self.empty_renderer.clone());
60
+
61
+ if session == self.active_session && renderer == self.active_renderer {
62
+ false
63
+ } else {
64
+ self.retarget_active(ctx, session, renderer);
65
+ true
66
+ }
67
+ }
68
+
69
+ pub(super) fn on_set_active_panel(
70
+ &mut self,
71
+ ctx: &Context<Self>,
72
+ id: String,
73
+ completion: Option<Completion>,
74
+ ) -> bool {
75
+ let id = PanelId::from(id);
76
+ let prev = ctx.props().workspace.active_id();
77
+ if prev.as_ref() == Some(&id) || !ctx.props().workspace.set_active(id.clone()) {
78
+ let resynced = self.resync_active(ctx);
79
+ if let Some(completion) = completion {
80
+ completion.resolve_after(async { Ok(()) });
81
+ }
82
+
83
+ resynced
84
+ } else {
85
+ let new_session = ctx
86
+ .props()
87
+ .workspace
88
+ .active_session()
89
+ .unwrap_or_else(|| self.empty_session.clone());
90
+
91
+ let new_renderer = ctx
92
+ .props()
93
+ .workspace
94
+ .active_renderer()
95
+ .unwrap_or_else(|| self.empty_renderer.clone());
96
+
97
+ self.retarget_active(ctx, new_session, new_renderer);
98
+ let mut nudges = Vec::new();
99
+ for pid in prev.iter().chain(std::iter::once(&id)) {
100
+ if let Some(panel) = ctx.props().workspace.panel(pid)
101
+ && panel.renderer.is_plugin_activated().unwrap_or(false)
102
+ {
103
+ nudges.push(activation_render(
104
+ panel.session.clone(),
105
+ panel.renderer.clone(),
106
+ ));
107
+ }
108
+ }
109
+
110
+ let run = async move {
111
+ futures::future::join_all(nudges)
112
+ .await
113
+ .into_iter()
114
+ .collect::<ApiResult<Vec<_>>>()?;
115
+ Ok(())
116
+ };
117
+
118
+ match completion {
119
+ Some(completion) => completion.resolve_after(run),
120
+ None => spawn_owned("set-active-panel", run),
121
+ }
122
+
123
+ true
124
+ }
125
+ }
126
+
127
+ /// Whole-element `restore`'s single commit: the `Workspace` already holds
128
+ /// the final panel set (models inserted, olds ejected) and the staged
129
+ /// layout. Re-subscribe the per-panel title wiring (the set changed
130
+ /// wholesale), activate the restored panel, and re-render — `MainPanel`'s
131
+ /// `rendered` pass applies the staged layout and mounts every cell in
132
+ /// this one commit.
133
+ pub(super) fn on_commit_workspace_restore(&mut self, ctx: &Context<Self>, id: String) -> bool {
134
+ self._title_subscriptions = subscribe_panel_titles(ctx);
135
+ self.on_set_active_panel(ctx, id, None);
136
+ true
137
+ }
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);
149
+ if was_active {
150
+ if let Some(next) = ctx.props().workspace.panel_ids().first().cloned() {
151
+ ctx.props().workspace.set_active(next);
152
+ }
153
+
154
+ let new_session = ctx
155
+ .props()
156
+ .workspace
157
+ .active_session()
158
+ .unwrap_or_else(|| self.empty_session.clone());
159
+ let new_renderer = ctx
160
+ .props()
161
+ .workspace
162
+ .active_renderer()
163
+ .unwrap_or_else(|| self.empty_renderer.clone());
164
+ self.retarget_active(ctx, new_session, new_renderer);
165
+ }
166
+
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
+ apply_global_filters(&ctx.props().workspace);
176
+ self._title_subscriptions = subscribe_panel_titles(ctx);
177
+ true
178
+ }
179
+
180
+ pub(super) fn on_duplicate_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
181
+ if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
182
+ let elem = ctx.props().elem.clone();
183
+ let presentation = ctx.props().presentation.clone();
184
+ let workspace = ctx.props().workspace.clone();
185
+ let notify = ctx.link().callback(|_: ()| LayoutChanged);
186
+ let activate = ctx.link().callback(|id| SetActivePanel(id, None));
187
+ ApiFuture::spawn(async move {
188
+ let config = panel
189
+ .renderer
190
+ .clone()
191
+ .with_lock(async {
192
+ get_viewer_config(&panel.session, &panel.renderer, &presentation).await
193
+ })
194
+ .await?;
195
+
196
+ let update = ViewerConfigUpdate::decode(&config.encode()?)?;
197
+ let client = panel.session.get_client();
198
+ let new_id = create_panel(
199
+ &elem,
200
+ &presentation,
201
+ &workspace,
202
+ &notify,
203
+ None,
204
+ update,
205
+ client,
206
+ )
207
+ .await?;
208
+
209
+ activate.emit(new_id.to_string());
210
+ Ok(())
211
+ });
212
+ }
213
+
214
+ false
215
+ }
216
+
217
+ pub(super) fn on_new_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
218
+ if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
219
+ let table_name = panel.session.get_table().map(|t| t.get_name().to_owned());
220
+ let elem = ctx.props().elem.clone();
221
+ let presentation = ctx.props().presentation.clone();
222
+ let workspace = ctx.props().workspace.clone();
223
+ let notify = ctx.link().callback(|_: ()| LayoutChanged);
224
+ let activate = ctx.link().callback(|id| SetActivePanel(id, None));
225
+ ApiFuture::spawn(async move {
226
+ let update = ViewerConfigUpdate {
227
+ table: table_name.map(TableUpdate::Update).unwrap_or_default(),
228
+ ..Default::default()
229
+ };
230
+
231
+ let client = panel.session.get_client();
232
+ let new_id = create_panel(
233
+ &elem,
234
+ &presentation,
235
+ &workspace,
236
+ &notify,
237
+ None,
238
+ update,
239
+ client,
240
+ )
241
+ .await?;
242
+
243
+ activate.emit(new_id.to_string());
244
+ Ok(())
245
+ });
246
+ }
247
+ false
248
+ }
249
+
250
+ /// `NewPanelFrom` — the context menu's "New" sub-menu: a fresh
251
+ /// (default-config) panel bound to the named `Table` on the named
252
+ /// `Client`, resolved from the `Workspace` loaded-clients registry.
253
+ pub(super) fn on_new_panel_from(
254
+ &mut self,
255
+ ctx: &Context<Self>,
256
+ client_name: String,
257
+ table: String,
258
+ ) -> bool {
259
+ let Some(client) = ctx
260
+ .props()
261
+ .workspace
262
+ .clients()
263
+ .into_iter()
264
+ .find(|c| c.get_name() == client_name)
265
+ else {
266
+ tracing::warn!("No loaded `Client` named \"{client_name}\"");
267
+ return false;
268
+ };
269
+
270
+ let elem = ctx.props().elem.clone();
271
+ let presentation = ctx.props().presentation.clone();
272
+ let workspace = ctx.props().workspace.clone();
273
+ let notify = ctx.link().callback(|_: ()| LayoutChanged);
274
+ let activate = ctx.link().callback(|id| SetActivePanel(id, None));
275
+ ApiFuture::spawn(async move {
276
+ let update = ViewerConfigUpdate {
277
+ table: TableUpdate::Update(table),
278
+ ..Default::default()
279
+ };
280
+
281
+ let new_id = create_panel(
282
+ &elem,
283
+ &presentation,
284
+ &workspace,
285
+ &notify,
286
+ None,
287
+ update,
288
+ Some(client),
289
+ )
290
+ .await?;
291
+
292
+ activate.emit(new_id.to_string());
293
+ Ok(())
294
+ });
295
+
296
+ false
297
+ }
298
+
299
+ /// Re-point the root's per-active engine wiring + snapshots from the
300
+ /// current active panel to a new one: clear the old panel's direct
301
+ /// callbacks, set up the new panel's callbacks + subscriptions, refresh
302
+ /// the snapshots, and reset the in-flight render counter (which tracked
303
+ /// the old panel).
304
+ fn retarget_active(&mut self, ctx: &Context<Self>, session: Session, renderer: Renderer) {
305
+ clear_active_callbacks(&self.active_session, &self.active_renderer);
306
+ inject_active_callbacks(ctx, &session, &renderer);
307
+ self._active_subscriptions = create_active_subscriptions(ctx, &session, &renderer);
308
+ self.session_props = session.to_props();
309
+ self.renderer_props = renderer.to_props(None);
310
+ self.update_count = session.in_flight_config_runs();
311
+ self.active_session = session;
312
+ self.active_renderer = renderer;
313
+ let presentation = ctx.props().presentation.clone();
314
+ let workspace = ctx.props().workspace.clone();
315
+ ApiFuture::spawn(async move {
316
+ let default_theme = presentation.get_default_theme_name().await;
317
+ if let Some(renderer) = workspace.active_renderer()
318
+ && let Some(theme) = renderer.theme().or(default_theme)
319
+ {
320
+ presentation.set_theme_name(Some(&theme)).await?;
321
+ }
322
+
323
+ Ok(())
324
+ });
325
+ }
326
+ }
@@ -0,0 +1,243 @@
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
+ //! The root component's `view()`: composition of the settings pane, the
14
+ //! column-settings drawer, and the multi-panel `MainPanel`.
15
+ //!
16
+ //! INVARIANT: the `SplitPanel` is ALWAYS rendered with `main_panel` as its
17
+ //! last (flex-fill) pane — toggling the settings sidebar only adds/removes the
18
+ //! leading pane, never reparents `main_panel` (a remount would tear down
19
+ //! `<regular-layout>` + the plugin `<slot>`s).
20
+
21
+ use yew::prelude::*;
22
+
23
+ use super::PerspectiveViewer;
24
+ use super::msg::PerspectiveViewerMsg::*;
25
+ use crate::components::column_settings_sidebar::ColumnSettingsPanel;
26
+ use crate::components::containers::split_panel::SplitPanel;
27
+ use crate::components::font_loader::FontLoader;
28
+ use crate::components::main_panel::MainPanel;
29
+ use crate::components::panel_menu::PanelCommand;
30
+ use crate::components::settings_panel::SettingsPanel;
31
+ use crate::components::style::StyleProvider;
32
+ use crate::queries::*;
33
+ use crate::session::TableLoadState;
34
+
35
+ impl PerspectiveViewer {
36
+ pub(super) fn render(&self, ctx: &Context<Self>) -> Html {
37
+ // The settings panel + status bar bind to the *active* panel's engines;
38
+ // `presentation` is element-level (shared).
39
+ let presentation = &ctx.props().presentation;
40
+ let renderer = &self.active_renderer;
41
+ let session = &self.active_session;
42
+
43
+ let is_settings_open = self.settings_open
44
+ && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
45
+
46
+ let mut class = classes!();
47
+ if !is_settings_open {
48
+ class.push("settings-closed");
49
+ }
50
+
51
+ if self.session_props.title.is_some() {
52
+ class.push("titled");
53
+ }
54
+
55
+ let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
56
+ locator: c,
57
+ sender: None,
58
+ toggle: true,
59
+ });
60
+
61
+ let on_column_settings_panel_resize = ctx
62
+ .link()
63
+ .callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
64
+
65
+ let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
66
+ let on_debug = ctx.link().callback(|_| ToggleDebug);
67
+ let selected_column = get_current_column_locator(
68
+ &self.presentation_props.open_column_settings,
69
+ renderer,
70
+ &self.session_props.config,
71
+ &self.session_props.metadata,
72
+ );
73
+
74
+ let selected_tab = self.presentation_props.open_column_settings.tab;
75
+ let plugin_name = self.renderer_props.plugin_name.clone();
76
+ let available_plugins = self.renderer_props.available_plugins.clone();
77
+ let has_table = self.session_props.has_table.clone();
78
+ let named_column_count = self.renderer_props.config.config_column_names.len();
79
+
80
+ let view_config = self.session_props.config.clone();
81
+ let drag_column = self.dragdrop_props.column.clone();
82
+ let metadata = self.session_props.metadata.clone();
83
+ let on_select_tab = ctx.link().callback(SettingsPanelTabChanged);
84
+ let on_auto_width = ctx.link().callback(SettingsPanelAutoWidth);
85
+ let settings_panel = if is_settings_open {
86
+ html! {
87
+ <SettingsPanel
88
+ on_close={on_close_settings}
89
+ on_resize={&self.on_resize}
90
+ on_select_column={on_open_expr_panel}
91
+ is_debug={self.debug_open}
92
+ {on_debug}
93
+ {plugin_name}
94
+ {available_plugins}
95
+ {has_table}
96
+ {named_column_count}
97
+ {view_config}
98
+ plugin_config={self.renderer_props.plugin_config.clone()}
99
+ {drag_column}
100
+ metadata={metadata.clone()}
101
+ open_column_settings={self.presentation_props.open_column_settings.clone()}
102
+ selected_theme={self.presentation_props.selected_theme.clone()}
103
+ selected_tab={self.settings_geometry.selected_tab}
104
+ auto_width={self.settings_geometry.auto_width}
105
+ on_dimensions_reset={&self.on_settings_panel_dimensions_reset}
106
+ {on_select_tab}
107
+ {on_auto_width}
108
+ {presentation}
109
+ {renderer}
110
+ {session}
111
+ />
112
+ }
113
+ } else {
114
+ html! { <></> }
115
+ };
116
+
117
+ let on_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
118
+ let on_select_tab = ctx.link().callback(ColumnSettingsTabChanged);
119
+ let column_settings_panel = html! {
120
+ if let Some(selected_column) = selected_column {
121
+ <SplitPanel
122
+ id="modal_panel"
123
+ reverse=true
124
+ initial_size={self.settings_geometry.column_settings_width_override}
125
+ on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
126
+ on_resize={on_column_settings_panel_resize}
127
+ >
128
+ <ColumnSettingsPanel
129
+ {selected_column}
130
+ {selected_tab}
131
+ on_close={self.on_close_column_settings.clone()}
132
+ width_override={self.settings_geometry.column_settings_width_override}
133
+ {on_select_tab}
134
+ plugin_name={self.renderer_props.plugin_name.clone()}
135
+ {metadata}
136
+ view_config={self.session_props.config.clone()}
137
+ column_stats={self.session_props.column_stats.clone()}
138
+ selected_theme={self.presentation_props.selected_theme.clone()}
139
+ {presentation}
140
+ {renderer}
141
+ {session}
142
+ />
143
+ <></>
144
+ </SplitPanel>
145
+ }
146
+ };
147
+
148
+ let on_reset = ctx.link().callback(|all| ResetPanel(None, all, None));
149
+ let main_panel = html! {
150
+ <MainPanel
151
+ {on_settings}
152
+ {on_reset}
153
+ on_activate_panel={ctx.link().callback(|id| SetActivePanel(id, None))}
154
+ on_close_panel={ctx.link().callback(|id| ClosePanel(id, None))}
155
+ on_panel_command={ctx.link().batch_callback(|(id, cmd): (String, PanelCommand)| {
156
+ match cmd {
157
+ PanelCommand::New => vec![NewPanel(id)],
158
+ PanelCommand::NewFrom { client, table } => {
159
+ vec![NewPanelFrom { client, table }]
160
+ },
161
+ PanelCommand::Duplicate => vec![DuplicatePanel(id)],
162
+ PanelCommand::Reset => vec![ResetPanel(Some(id), false, None)],
163
+ PanelCommand::ToggleMaster => vec![ToggleMaster(id)],
164
+ PanelCommand::Close => vec![ClosePanel(id, None)],
165
+ PanelCommand::Maximize | PanelCommand::Restore => vec![],
166
+ }
167
+ })}
168
+ session_props={self.session_props.clone()}
169
+ renderer_props={self.renderer_props.clone()}
170
+ presentation_props={self.presentation_props.clone()}
171
+ {is_settings_open}
172
+ update_count={self.update_count}
173
+ panel_ids={ctx.props().workspace.panel_ids()}
174
+ panel_titles={ctx.props()
175
+ .workspace
176
+ .panel_ids()
177
+ .iter()
178
+ .map(|id| {
179
+ let title = ctx
180
+ .props()
181
+ .workspace
182
+ .panel(id)
183
+ .and_then(|p| p.session.get_title())
184
+ .filter(|t| !t.is_empty());
185
+ (id.as_str().to_owned(), title)
186
+ })
187
+ .collect::<Vec<_>>()}
188
+ panel_themes={ctx.props()
189
+ .workspace
190
+ .panel_ids()
191
+ .iter()
192
+ .map(|id| {
193
+ (
194
+ id.as_str().to_owned(),
195
+ ctx.props().workspace.panel(id).and_then(|p| p.renderer.theme()),
196
+ )
197
+ })
198
+ .collect::<Vec<_>>()}
199
+ panel_masters={ctx.props().workspace.masters()}
200
+ workspace={ctx.props().workspace.clone()}
201
+ global_filters={self.global_filters.clone()}
202
+ on_remove_global_filter={ctx.link().callback(RemoveGlobalFilter)}
203
+ on_clear_global_filters={ctx.link().callback(|_| ClearGlobalFilters)}
204
+ {presentation}
205
+ {renderer}
206
+ {session}
207
+ />
208
+ };
209
+
210
+ let is_single_panel = if ctx.props().workspace.placed_count() == 1 {
211
+ "only-child"
212
+ } else {
213
+ ""
214
+ };
215
+
216
+ html! {
217
+ <StyleProvider root={ctx.props().elem.clone()}>
218
+ <div id="component_container" class={is_single_panel}>
219
+ <div id="layout_area">
220
+ <SplitPanel
221
+ id="app_panel"
222
+ reverse=true
223
+ skip_empty=true
224
+ deferred=true
225
+ size={self.settings_geometry.pane_width_override}
226
+ initial_size={self.settings_geometry.pane_width_override}
227
+ on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
228
+ on_resize={ctx.link().callback(|(x, _): (i32, i32)| SettingsDividerMove(x))}
229
+ on_resize_finished={ctx.link().callback(|_| SettingsDividerFinish)}
230
+ >
231
+ { settings_panel }
232
+ <div id="main_column_container">
233
+ { main_panel }
234
+ { column_settings_panel }
235
+ </div>
236
+ </SplitPanel>
237
+ </div>
238
+ </div>
239
+ <FontLoader ..self.fonts.clone() />
240
+ </StyleProvider>
241
+ }
242
+ }
243
+ }