@perspective-dev/viewer 5.0.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +9 -9
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/intl/de.css +1 -1
- package/dist/css/intl/es.css +1 -1
- package/dist/css/intl/fr.css +1 -1
- package/dist/css/intl/ja.css +1 -1
- package/dist/css/intl/pt.css +1 -1
- package/dist/css/intl/zh.css +1 -1
- package/dist/css/intl.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/docs/perspective-docs.json +1 -0
- package/dist/esm/column-format.d.ts +19 -28
- package/dist/esm/perspective-viewer.d.ts +2 -0
- package/dist/esm/perspective-viewer.inline.js +8 -8
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +9 -9
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/providers.d.ts +79 -0
- package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
- package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
- package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
- package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
- package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
- package/dist/esm/ts-rs/Notation.d.ts +17 -0
- package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +43 -1
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
- package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
- package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
- package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
- package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
- package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
- package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/Unit.d.ts +1 -0
- package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
- package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
- package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +43 -1
- package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +72 -0
- package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
- package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
- package/dist/esm/ts-rs/WindowSpec.d.ts +47 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +7 -6
- package/dist/wasm/perspective-viewer.d.ts +222 -26
- package/dist/wasm/perspective-viewer.js +378 -125
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +18 -15
- package/package.json +2 -1
- package/src/css/_viewer_bundle.css +1 -0
- package/src/css/column-selector.css +9 -0
- package/src/css/column-settings-panel.css +67 -2
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +60 -4
- package/src/css/containers/context-menu.css +12 -6
- package/src/css/containers/tabs.css +2 -2
- package/src/css/form/code-editor.css +2 -3
- package/src/css/form/mirrored-textarea.css +81 -0
- package/src/css/viewer.css +336 -0
- package/src/rust/agent/client/mod.rs +19 -0
- package/src/rust/agent/client/protocol.rs +320 -0
- package/src/rust/agent/client/transport.rs +287 -0
- package/src/rust/agent/client/turn.rs +174 -0
- package/src/rust/agent/config.rs +159 -0
- package/src/rust/agent/docs.rs +293 -0
- package/src/rust/agent/markdown/block.rs +509 -0
- package/src/rust/agent/markdown/entity.rs +52 -0
- package/src/rust/agent/markdown/events.rs +71 -0
- package/src/rust/agent/markdown/inline.rs +497 -0
- package/src/rust/agent/markdown/mod.rs +466 -0
- package/src/rust/agent/mod.rs +23 -0
- package/src/rust/agent/preamble.md +16 -0
- package/src/rust/agent/runtime.rs +201 -0
- package/src/rust/agent/slot.rs +236 -0
- package/src/rust/agent/tools.rs +1110 -0
- package/src/rust/components/chat_panel.rs +289 -0
- package/src/rust/components/column_selector/active_column.rs +21 -27
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +83 -8
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/inactive_column.rs +8 -1
- package/src/rust/components/column_selector.rs +27 -3
- package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
- package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
- package/src/rust/components/column_settings_sidebar.rs +163 -16
- package/src/rust/components/containers/dragdrop_list.rs +110 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +6 -0
- package/src/rust/components/editable_header.rs +2 -0
- package/src/rust/components/form/code_editor.rs +14 -23
- package/src/rust/components/form/debug.rs +8 -5
- package/src/rust/components/form/mirrored_textarea.rs +165 -0
- package/src/rust/components/form/mod.rs +1 -0
- package/src/rust/components/main_panel.rs +5 -0
- package/src/rust/components/mod.rs +3 -0
- package/src/rust/components/panel_menu.rs +7 -0
- package/src/rust/components/panel_tab.rs +5 -0
- package/src/rust/components/portal.rs +7 -0
- package/src/rust/components/settings_panel.rs +80 -2
- package/src/rust/components/viewer/msg.rs +2 -0
- package/src/rust/components/viewer/panels.rs +9 -14
- package/src/rust/components/viewer/render.rs +14 -0
- package/src/rust/components/viewer/settings.rs +33 -1
- package/src/rust/components/viewer.rs +2 -0
- package/src/rust/components/window_editor.rs +985 -0
- package/src/rust/config/datetime_column_style/custom.rs +17 -0
- package/src/rust/config/datetime_column_style/simple.rs +10 -0
- package/src/rust/config/number_string_format.rs +33 -0
- package/src/rust/config/options.rs +18 -0
- package/src/rust/config/plugin_static_config.rs +120 -5
- package/src/rust/config/viewer_config.rs +220 -1
- package/src/rust/config/workspace_config.rs +7 -6
- package/src/rust/custom_elements/viewer.rs +209 -18
- package/src/rust/custom_events.rs +29 -0
- package/src/rust/lib.rs +17 -1
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +8 -0
- package/src/rust/queries/columns_iter_set.rs +7 -1
- package/src/rust/queries/get_viewer_config.rs +5 -1
- package/src/rust/renderer/dispatch.rs +85 -58
- package/src/rust/renderer.rs +34 -1
- package/src/rust/session/column_defaults_update.rs +18 -0
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +64 -9
- package/src/rust/session/replace_expression_update.rs +2 -0
- package/src/rust/session.rs +7 -5
- package/src/rust/tasks/create_panel.rs +18 -8
- package/src/rust/tasks/edit_window.rs +155 -0
- package/src/rust/tasks/mod.rs +2 -0
- package/src/rust/tasks/restore_panel.rs +34 -17
- package/src/rust/tasks/sync_update_panels.rs +7 -23
- package/src/rust/utils/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/debounce.rs +76 -27
- package/src/rust/workspace.rs +112 -20
- package/src/svg/pin-icon.svg +5 -0
- package/src/themes/icons.css +1 -0
- package/src/themes/intl/de.css +23 -0
- package/src/themes/intl/es.css +23 -0
- package/src/themes/intl/fr.css +23 -0
- package/src/themes/intl/ja.css +23 -0
- package/src/themes/intl/pt.css +23 -0
- package/src/themes/intl/zh.css +23 -0
- package/src/themes/intl.css +26 -1
- package/src/ts/column-format.ts +67 -46
- package/src/ts/perspective-viewer.ts +2 -0
- package/src/ts/providers.ts +96 -0
- package/src/ts/ts-rs/CompactDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyCode.ts +3 -0
- package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
- package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
- package/src/ts/ts-rs/CurrencySign.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
- package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
- package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
- package/src/ts/ts-rs/FormatMode.ts +3 -0
- package/src/ts/ts-rs/FormatUnit.ts +3 -0
- package/src/ts/ts-rs/Notation.ts +11 -0
- package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +48 -1
- package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
- package/src/ts/ts-rs/RestoreOptions.ts +20 -0
- package/src/ts/ts-rs/RoundingMode.ts +3 -0
- package/src/ts/ts-rs/RoundingPriority.ts +3 -0
- package/src/ts/ts-rs/SignDisplay.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
- package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
- package/src/ts/ts-rs/StringColorMode.ts +3 -0
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
- package/src/ts/ts-rs/Unit.ts +3 -0
- package/src/ts/ts-rs/UnitDisplay.ts +3 -0
- package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
- package/src/ts/ts-rs/UseGrouping.ts +3 -0
- package/src/ts/ts-rs/ViewerConfig.ts +48 -1
- package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +81 -2
- package/src/ts/ts-rs/WindowSort.ts +9 -0
- package/src/ts/ts-rs/WindowSortDir.ts +8 -0
- package/src/ts/ts-rs/WindowSpec.ts +47 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +7 -6
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline4.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-daeef4ceb066e6c7 → perspective-viewer-02e461f3d7062b26}/inline5.js +0 -0
|
@@ -25,7 +25,7 @@ use web_sys::*;
|
|
|
25
25
|
|
|
26
26
|
use super::activate::*;
|
|
27
27
|
use super::limits::*;
|
|
28
|
-
use super::{ContextPin, RenderContext, Renderer, SUBPIXEL_EPSILON};
|
|
28
|
+
use super::{ContextPin, GeometryCmd, RenderContext, Renderer, SUBPIXEL_EPSILON};
|
|
29
29
|
use crate::js::plugin::*;
|
|
30
30
|
use crate::session::FreshView;
|
|
31
31
|
use crate::utils::*;
|
|
@@ -133,19 +133,8 @@ impl Renderer {
|
|
|
133
133
|
}
|
|
134
134
|
|
|
135
135
|
pub async fn resize(&self) -> ApiResult<()> {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
draw_mutex
|
|
139
|
-
.debounce_with(|_guard| async move {
|
|
140
|
-
set_timeout(timer.get_throttle()).await?;
|
|
141
|
-
let Some(jsplugin) = self.active_plugin() else {
|
|
142
|
-
return Ok(());
|
|
143
|
-
};
|
|
144
|
-
self.stamp_active(&jsplugin);
|
|
145
|
-
jsplugin.resize().await?;
|
|
146
|
-
Ok(())
|
|
147
|
-
})
|
|
148
|
-
.await
|
|
136
|
+
self.0.geometry_cmd.set(Some(GeometryCmd::Measure));
|
|
137
|
+
self.geometry_task().await.map(|_| ())
|
|
149
138
|
}
|
|
150
139
|
|
|
151
140
|
/// Public one-shot resize to explicit dimensions (`viewer.resize({
|
|
@@ -190,62 +179,100 @@ impl Renderer {
|
|
|
190
179
|
width: f64,
|
|
191
180
|
height: f64,
|
|
192
181
|
) -> ApiResult<Option<js_sys::Function>> {
|
|
193
|
-
|
|
194
|
-
|
|
182
|
+
self.0.geometry_cmd.set(Some(GeometryCmd::Presize {
|
|
183
|
+
translate,
|
|
184
|
+
width,
|
|
185
|
+
height,
|
|
186
|
+
}));
|
|
187
|
+
|
|
188
|
+
self.geometry_task().await
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Run the LATEST queued [`GeometryCmd`] on the geometry debounce slot.
|
|
192
|
+
async fn geometry_task(&self) -> ApiResult<Option<js_sys::Function>> {
|
|
193
|
+
let timer = self.render_timer();
|
|
194
|
+
self.0
|
|
195
|
+
.geometry_slot
|
|
196
|
+
.clone()
|
|
195
197
|
.debounce_with(|_guard| async move {
|
|
196
|
-
let Some(
|
|
198
|
+
let Some(cmd) = self.0.geometry_cmd.take() else {
|
|
197
199
|
return Ok(None);
|
|
198
200
|
};
|
|
199
201
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
202
|
+
match cmd {
|
|
203
|
+
GeometryCmd::Measure => {
|
|
204
|
+
set_timeout(timer.get_throttle()).await?;
|
|
205
|
+
let Some(jsplugin) = self.active_plugin() else {
|
|
206
|
+
return Ok(None);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
self.stamp_active(&jsplugin);
|
|
210
|
+
jsplugin.resize().await?;
|
|
211
|
+
Ok(None)
|
|
212
|
+
},
|
|
213
|
+
GeometryCmd::Presize {
|
|
214
|
+
translate,
|
|
215
|
+
width,
|
|
216
|
+
height,
|
|
217
|
+
} => self.run_presize(translate, width, height).await,
|
|
211
218
|
}
|
|
219
|
+
})
|
|
220
|
+
.await
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async fn run_presize(
|
|
224
|
+
&self,
|
|
225
|
+
translate: Option<(f64, f64)>,
|
|
226
|
+
width: f64,
|
|
227
|
+
height: f64,
|
|
228
|
+
) -> ApiResult<Option<js_sys::Function>> {
|
|
229
|
+
let Some(plugin) = self.active_plugin() else {
|
|
230
|
+
return Ok(None);
|
|
231
|
+
};
|
|
212
232
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
233
|
+
self.stamp_active(&plugin);
|
|
234
|
+
self.clear_presize()?;
|
|
235
|
+
if plugin.capabilities().presize {
|
|
236
|
+
return plugin.presize(width, height).await;
|
|
237
|
+
}
|
|
217
238
|
|
|
218
|
-
|
|
219
|
-
|
|
239
|
+
let main_panel: &web_sys::HtmlElement = plugin.unchecked_ref();
|
|
240
|
+
let rect = main_panel.get_bounding_client_rect();
|
|
241
|
+
let size_changed = (height - rect.height()).abs() > SUBPIXEL_EPSILON
|
|
242
|
+
|| (width - rect.width()).abs() > SUBPIXEL_EPSILON;
|
|
220
243
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
.style()
|
|
227
|
-
.set_property("height", &format!("{height}px"))?;
|
|
228
|
-
}
|
|
244
|
+
let (dx, dy) = translate.unwrap_or((0.0, 0.0));
|
|
245
|
+
let moved = dx.abs() > SUBPIXEL_EPSILON || dy.abs() > SUBPIXEL_EPSILON;
|
|
246
|
+
if !size_changed && !moved {
|
|
247
|
+
return Ok(None);
|
|
248
|
+
}
|
|
229
249
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
250
|
+
if size_changed {
|
|
251
|
+
main_panel
|
|
252
|
+
.style()
|
|
253
|
+
.set_property("width", &format!("{width}px"))?;
|
|
254
|
+
main_panel
|
|
255
|
+
.style()
|
|
256
|
+
.set_property("height", &format!("{height}px"))?;
|
|
257
|
+
}
|
|
235
258
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
259
|
+
if moved {
|
|
260
|
+
main_panel
|
|
261
|
+
.style()
|
|
262
|
+
.set_property("transform", &format!("translate({dx}px, {dy}px)"))?;
|
|
263
|
+
}
|
|
242
264
|
|
|
243
|
-
|
|
244
|
-
|
|
265
|
+
if size_changed {
|
|
266
|
+
let result = plugin.resize().await;
|
|
267
|
+
if translate.is_none() {
|
|
268
|
+
main_panel.style().set_property("width", "")?;
|
|
269
|
+
main_panel.style().set_property("height", "")?;
|
|
270
|
+
}
|
|
245
271
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
272
|
+
result?;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
Ok(None)
|
|
249
276
|
}
|
|
250
277
|
|
|
251
278
|
/// Remove the inline presize styles applied by [`Self::presize_with_box`].
|
package/src/rust/renderer.rs
CHANGED
|
@@ -53,6 +53,22 @@ use crate::utils::*;
|
|
|
53
53
|
/// presize paths.
|
|
54
54
|
pub(crate) const SUBPIXEL_EPSILON: f64 = 0.5;
|
|
55
55
|
|
|
56
|
+
/// One queued geometry task for the [`RendererData::geometry_slot`] runner.
|
|
57
|
+
/// `Measure` re-measures the plugin's live box (reactive resize); `Presize`
|
|
58
|
+
/// renders an anticipated box ahead of a layout commit. A later command
|
|
59
|
+
/// subsumes an earlier unconsumed one: `Measure` reflects whatever geometry
|
|
60
|
+
/// has committed by run time, and `Presize` anticipates the NEXT commit —
|
|
61
|
+
/// callers issue them in commit order.
|
|
62
|
+
#[derive(Clone, Copy)]
|
|
63
|
+
pub(crate) enum GeometryCmd {
|
|
64
|
+
Measure,
|
|
65
|
+
Presize {
|
|
66
|
+
translate: Option<(f64, f64)>,
|
|
67
|
+
width: f64,
|
|
68
|
+
height: f64,
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
/// Everything a plugin may read back during its render, all belonging to
|
|
57
73
|
/// one snapshot (invariant I5). Built once per bound `View` by the render
|
|
58
74
|
/// pipeline and CACHED on the `Renderer`; pinning it for a run is an `Rc`
|
|
@@ -104,6 +120,20 @@ pub struct RendererData {
|
|
|
104
120
|
/// [`Renderer::presize_with_box`] presize calls.
|
|
105
121
|
presize_pending: Cell<u32>,
|
|
106
122
|
|
|
123
|
+
/// Debounce slot for geometry tasks (`resize` / presize), separate from
|
|
124
|
+
/// the default slot data-update redraws coalesce on. Invariant: a
|
|
125
|
+
/// geometry request must never resolve via a parked DATA task — a
|
|
126
|
+
/// pending update's redraw renders at the worker's current dimensions
|
|
127
|
+
/// and cannot subsume a size change (the drag-during-updates permanent
|
|
128
|
+
/// skew).
|
|
129
|
+
geometry_slot: DebounceSlot,
|
|
130
|
+
|
|
131
|
+
/// Latest-wins parameter cell for the geometry slot, consumed by the
|
|
132
|
+
/// runner AT RUN TIME under the lock. Parameters live here rather than
|
|
133
|
+
/// in runner captures so a parked runner can never act on a stale
|
|
134
|
+
/// target when later calls coalesced onto it.
|
|
135
|
+
geometry_cmd: Cell<Option<GeometryCmd>>,
|
|
136
|
+
|
|
107
137
|
/// Fires after every draw/update with the computed render limits.
|
|
108
138
|
pub on_render_limits_changed: RefCell<Option<Callback<RenderLimits>>>,
|
|
109
139
|
|
|
@@ -221,6 +251,7 @@ impl Deref for RendererData {
|
|
|
221
251
|
|
|
222
252
|
impl Renderer {
|
|
223
253
|
pub fn new(viewer_elem: &HtmlElement) -> Self {
|
|
254
|
+
let draw_lock = DebounceMutex::default();
|
|
224
255
|
Self(Rc::new(RendererData {
|
|
225
256
|
plugin_data: RefCell::new(RendererMutData {
|
|
226
257
|
viewer_elem: viewer_elem.clone(),
|
|
@@ -231,7 +262,9 @@ impl Renderer {
|
|
|
231
262
|
timer: MovingWindowRenderTimer::default(),
|
|
232
263
|
plugin_states: HashMap::default(),
|
|
233
264
|
}),
|
|
234
|
-
|
|
265
|
+
geometry_slot: draw_lock.slot(),
|
|
266
|
+
geometry_cmd: Cell::new(None),
|
|
267
|
+
draw_lock,
|
|
235
268
|
plugin_changed: Default::default(),
|
|
236
269
|
style_changed: Default::default(),
|
|
237
270
|
reset_changed: Default::default(),
|
|
@@ -52,6 +52,24 @@ pub impl ViewConfigUpdate {
|
|
|
52
52
|
self.group_rollup_mode
|
|
53
53
|
);
|
|
54
54
|
}
|
|
55
|
+
|
|
56
|
+
let split_rollup_features = metadata
|
|
57
|
+
.get_features()
|
|
58
|
+
.map(|x| x.get_split_rollup_modes())
|
|
59
|
+
.unwrap_or_default();
|
|
60
|
+
|
|
61
|
+
let split_rollups = config_static.get_split_rollups(&split_rollup_features);
|
|
62
|
+
if !split_rollups.contains(
|
|
63
|
+
self.split_rollup_mode
|
|
64
|
+
.as_ref()
|
|
65
|
+
.unwrap_or(&SplitRollupMode::Flat),
|
|
66
|
+
) {
|
|
67
|
+
self.split_rollup_mode = split_rollups.first().cloned();
|
|
68
|
+
tracing::debug!(
|
|
69
|
+
"Setting plugin-advised split rollup mode {:?}",
|
|
70
|
+
self.split_rollup_mode
|
|
71
|
+
);
|
|
72
|
+
}
|
|
55
73
|
}
|
|
56
74
|
|
|
57
75
|
/// Appends additional columns to the `columns` field of this
|
|
@@ -96,9 +96,27 @@ pub impl ViewConfig {
|
|
|
96
96
|
config.filter.retain(|x| x.column() != column);
|
|
97
97
|
update.filter = Some(config.filter.clone());
|
|
98
98
|
},
|
|
99
|
+
DragEffect::Move(
|
|
100
|
+
DragTarget::WindowSource
|
|
101
|
+
| DragTarget::WindowOrderBy
|
|
102
|
+
| DragTarget::WindowPartitionBy,
|
|
103
|
+
) => {
|
|
104
|
+
// Staged origins (window-editor pills) have nothing to remove
|
|
105
|
+
// HERE - the editor's draft is not part of the view config
|
|
106
|
+
// this update mutates. The open `WindowEditor` observes the
|
|
107
|
+
// same `drop_received` event and clears the origin slot in
|
|
108
|
+
// its draft, completing the move.
|
|
109
|
+
},
|
|
99
110
|
}
|
|
100
111
|
|
|
101
112
|
match drop {
|
|
113
|
+
DragTarget::WindowSource
|
|
114
|
+
| DragTarget::WindowOrderBy
|
|
115
|
+
| DragTarget::WindowPartitionBy => {
|
|
116
|
+
// Unreachable: staged drops never build config updates
|
|
117
|
+
// (`DragTarget::is_staged` is excluded by every committing
|
|
118
|
+
// `drop_received` subscriber).
|
|
119
|
+
},
|
|
102
120
|
DragTarget::Active => {
|
|
103
121
|
if !is_swap_to_after_last {
|
|
104
122
|
if is_to_swap || is_from_required {
|
|
@@ -69,6 +69,7 @@ pub struct SessionMetadataState {
|
|
|
69
69
|
edit_port: f64,
|
|
70
70
|
view_schema: Option<HashMap<String, ColumnType>>,
|
|
71
71
|
expr_meta: Option<SessionViewExpressionMetadata>,
|
|
72
|
+
window_schema: HashMap<String, ColumnType>,
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
impl SessionMetadata {
|
|
@@ -95,6 +96,41 @@ impl SessionMetadata {
|
|
|
95
96
|
Ok(())
|
|
96
97
|
}
|
|
97
98
|
|
|
99
|
+
/// Records the pre-aggregation types of the config's window columns, so
|
|
100
|
+
/// `get_column_table_type` resolves them like any other derived column.
|
|
101
|
+
pub(super) fn update_windows(
|
|
102
|
+
&mut self,
|
|
103
|
+
windows: &perspective_client::config::Windows,
|
|
104
|
+
) -> ApiResult<()> {
|
|
105
|
+
let window_schema = windows
|
|
106
|
+
.iter()
|
|
107
|
+
.filter_map(|(name, w)| {
|
|
108
|
+
let source = self.get_column_table_type(&w.column)?;
|
|
109
|
+
let dtype = self
|
|
110
|
+
.get_window_aggregate(source, &w.aggregate)
|
|
111
|
+
.and_then(|spec| spec.result_type)
|
|
112
|
+
.and_then(|ty| ColumnType::try_from(ty).ok())
|
|
113
|
+
.unwrap_or(source);
|
|
114
|
+
|
|
115
|
+
Some((name.clone(), dtype))
|
|
116
|
+
})
|
|
117
|
+
.collect();
|
|
118
|
+
self.as_mut().unwrap().window_schema = window_schema;
|
|
119
|
+
Ok(())
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// The declaration for one window aggregate over a `source` column type.
|
|
123
|
+
pub fn get_window_aggregate(
|
|
124
|
+
&self,
|
|
125
|
+
source: ColumnType,
|
|
126
|
+
name: &str,
|
|
127
|
+
) -> Option<perspective_client::proto::WindowAggregateArgs> {
|
|
128
|
+
self.get_features()?
|
|
129
|
+
.get_window_aggregates(source)
|
|
130
|
+
.into_iter()
|
|
131
|
+
.find(|x| x.name == name)
|
|
132
|
+
}
|
|
133
|
+
|
|
98
134
|
/// Whether `view_schema` has been populated by a prior successful
|
|
99
135
|
/// `create_view`. Used by `Session::create_view` to force a full
|
|
100
136
|
/// build on first run even when `is_clean` happens to be true, so
|
|
@@ -212,6 +248,19 @@ impl SessionMetadata {
|
|
|
212
248
|
self.as_ref().map(|meta| &meta.column_names)
|
|
213
249
|
}
|
|
214
250
|
|
|
251
|
+
pub fn is_column_window(&self, name: &str) -> bool {
|
|
252
|
+
self.as_ref()
|
|
253
|
+
.map(|meta| meta.window_schema.contains_key(name))
|
|
254
|
+
.unwrap_or_default()
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
pub fn get_window_columns(&self) -> impl Iterator<Item = &'_ String> {
|
|
258
|
+
self.as_ref()
|
|
259
|
+
.map(|meta| meta.window_schema.keys())
|
|
260
|
+
.into_iter()
|
|
261
|
+
.flatten()
|
|
262
|
+
}
|
|
263
|
+
|
|
215
264
|
pub fn is_column_expression(&self, name: &str) -> bool {
|
|
216
265
|
let is_expr: Option<bool> = try {
|
|
217
266
|
self.as_ref()?
|
|
@@ -253,14 +302,18 @@ impl SessionMetadata {
|
|
|
253
302
|
pub fn get_column_table_type(&self, name: &str) -> Option<ColumnType> {
|
|
254
303
|
try {
|
|
255
304
|
let meta = self.as_ref()?;
|
|
256
|
-
meta.table_schema
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
305
|
+
meta.table_schema
|
|
306
|
+
.get(name)
|
|
307
|
+
.cloned()
|
|
308
|
+
.or_else(|| {
|
|
309
|
+
meta.expr_meta
|
|
310
|
+
.as_ref()?
|
|
311
|
+
.expressions
|
|
312
|
+
.expression_schema
|
|
313
|
+
.get(name)
|
|
314
|
+
.cloned()
|
|
315
|
+
})
|
|
316
|
+
.or_else(|| meta.window_schema.get(name).cloned())?
|
|
264
317
|
}
|
|
265
318
|
}
|
|
266
319
|
|
|
@@ -282,7 +335,9 @@ impl SessionMetadata {
|
|
|
282
335
|
/// expressions.
|
|
283
336
|
pub fn locator_name_or_default(&self, locator: &ColumnLocator) -> String {
|
|
284
337
|
match locator {
|
|
285
|
-
ColumnLocator::Table(s) | ColumnLocator::Expression(s)
|
|
338
|
+
ColumnLocator::Table(s) | ColumnLocator::Expression(s) | ColumnLocator::Window(s) => {
|
|
339
|
+
s.clone()
|
|
340
|
+
},
|
|
286
341
|
ColumnLocator::NewExpression => self.make_new_column_name(None),
|
|
287
342
|
}
|
|
288
343
|
}
|
package/src/rust/session.rs
CHANGED
|
@@ -806,11 +806,11 @@ impl Session {
|
|
|
806
806
|
}
|
|
807
807
|
|
|
808
808
|
/// SYNC name validation for a candidate config (I4): every referenced
|
|
809
|
-
/// column must be a table column or an expression
|
|
810
|
-
/// candidate itself (syntactic presence — server-side
|
|
811
|
-
/// run property, not a commit property). Skipped
|
|
812
|
-
/// yet: the config rides along until `load()`
|
|
813
|
-
/// surfaces any residual error on that run.
|
|
809
|
+
/// column must be a table column, or an expression or window column
|
|
810
|
+
/// present in the candidate itself (syntactic presence — server-side
|
|
811
|
+
/// compilability is a run property, not a commit property). Skipped
|
|
812
|
+
/// when no table is bound yet: the config rides along until `load()`
|
|
813
|
+
/// binds one, and the engine surfaces any residual error on that run.
|
|
814
814
|
fn validate_names(&self, config: &ViewConfig) -> ApiResult<()> {
|
|
815
815
|
let table_columns = self.all_columns();
|
|
816
816
|
if table_columns.is_empty() {
|
|
@@ -819,6 +819,7 @@ impl Session {
|
|
|
819
819
|
|
|
820
820
|
let mut allowed: HashSet<&str> = table_columns.iter().map(|x| x.as_str()).collect();
|
|
821
821
|
allowed.extend(config.expressions.0.keys().map(|x| x.as_str()));
|
|
822
|
+
allowed.extend(config.windows.keys().map(|x| x.as_str()));
|
|
822
823
|
let named = config
|
|
823
824
|
.columns
|
|
824
825
|
.iter()
|
|
@@ -1144,6 +1145,7 @@ impl Session {
|
|
|
1144
1145
|
let view = table.view(Some(view_config.into())).await?;
|
|
1145
1146
|
let view_schema = view.schema().await?;
|
|
1146
1147
|
self.metadata_mut().update_view_schema(&view_schema)?;
|
|
1148
|
+
self.metadata_mut().update_windows(&effective.windows)?;
|
|
1147
1149
|
let on_stats = Callback::from({
|
|
1148
1150
|
let this = self.clone();
|
|
1149
1151
|
move |stats| this.update_stats(stats)
|
|
@@ -69,13 +69,23 @@ fn wire_panel_render_sub(session: &Session, renderer: &Renderer) -> Subscription
|
|
|
69
69
|
})
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
-
/// Create a new independent panel (own `Session` + `Renderer` + id)
|
|
73
|
-
/// `
|
|
74
|
-
///
|
|
75
|
-
///
|
|
76
|
-
///
|
|
77
|
-
///
|
|
78
|
-
///
|
|
72
|
+
/// Create a new independent panel (own `Session` + `Renderer` + id) and
|
|
73
|
+
/// restore `update` into it. Shared by `addPanel`, `restore`'s
|
|
74
|
+
/// create-if-missing upsert, and `restorePanel`. `id` is the panel's id —
|
|
75
|
+
/// provided when restoring into a specific named slot, or `None` to
|
|
76
|
+
/// generate a fresh one. `theme` is stripped (element-level, not
|
|
77
|
+
/// per-panel) and `client` — or the element's default client when `None` —
|
|
78
|
+
/// is bound so the config's `table` resolves against it.
|
|
79
|
+
///
|
|
80
|
+
/// A `table` is OPTIONAL here: an update with none creates a deferred
|
|
81
|
+
/// panel that a later `load()` binds, which is the pre-existing
|
|
82
|
+
/// `restore`-then-`load` contract and the same state the reserved
|
|
83
|
+
/// `load()` path produces. The routes where a table-less panel WOULD be
|
|
84
|
+
/// permanently blank require one in their own argument types instead —
|
|
85
|
+
/// `addPanel`'s [`ViewerConfigInitial`], `WorkspaceConfigUpdate`'s
|
|
86
|
+
/// `panels` entries, and the agent's `add_panel` — so the guarantee sits
|
|
87
|
+
/// at the boundary that owns it rather than here, which would sweep
|
|
88
|
+
/// `restore`'s patch in with them.
|
|
79
89
|
pub(crate) async fn create_panel(
|
|
80
90
|
elem: &HtmlElement,
|
|
81
91
|
presentation: &Presentation,
|
|
@@ -128,9 +138,9 @@ pub(crate) async fn create_panel(
|
|
|
128
138
|
&renderer,
|
|
129
139
|
presentation,
|
|
130
140
|
workspace,
|
|
131
|
-
None,
|
|
132
141
|
RestoreMode::Fresh,
|
|
133
142
|
update,
|
|
143
|
+
crate::tasks::RestoreErrors::Publish,
|
|
134
144
|
)
|
|
135
145
|
.await;
|
|
136
146
|
|
|
@@ -0,0 +1,155 @@
|
|
|
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 perspective_client::clone;
|
|
14
|
+
use perspective_client::config::{Filter, ViewConfigUpdate, WindowSpec};
|
|
15
|
+
|
|
16
|
+
use super::apply_and_render;
|
|
17
|
+
use crate::presentation::{ColumnLocator, ColumnSettingsTab, OpenColumnSettings, Presentation};
|
|
18
|
+
use crate::renderer::Renderer;
|
|
19
|
+
use crate::session::Session;
|
|
20
|
+
use crate::*;
|
|
21
|
+
|
|
22
|
+
fn reopen(presentation: &Presentation, name: String) {
|
|
23
|
+
presentation.set_open_column_settings(Some(OpenColumnSettings {
|
|
24
|
+
locator: Some(ColumnLocator::Window(name)),
|
|
25
|
+
tab: Some(ColumnSettingsTab::Window),
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// Insert `spec` into the session's windows as `name` (replacing any spec
|
|
30
|
+
/// under the same key) and re-render. After the render completes, opens the
|
|
31
|
+
/// column-settings drawer on the new window column.
|
|
32
|
+
pub fn save_window(
|
|
33
|
+
session: &Session,
|
|
34
|
+
renderer: &Renderer,
|
|
35
|
+
presentation: &Presentation,
|
|
36
|
+
name: String,
|
|
37
|
+
spec: WindowSpec,
|
|
38
|
+
) -> ApiResult<()> {
|
|
39
|
+
let presentation = presentation.clone();
|
|
40
|
+
let task = {
|
|
41
|
+
let mut windows = session.get_view_config().windows.clone();
|
|
42
|
+
windows.insert(name.clone(), spec);
|
|
43
|
+
apply_and_render(session, renderer, ViewConfigUpdate {
|
|
44
|
+
windows: Some(windows),
|
|
45
|
+
..Default::default()
|
|
46
|
+
})
|
|
47
|
+
}?;
|
|
48
|
+
|
|
49
|
+
ApiFuture::spawn(async move {
|
|
50
|
+
task.await?;
|
|
51
|
+
reopen(&presentation, name);
|
|
52
|
+
Ok(())
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
Ok(())
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Replace the window at `old_name` with `spec` and re-render. A rename
|
|
59
|
+
/// rewrites every config reference to the old name - `columns`, `group_by`,
|
|
60
|
+
/// `split_by`, `sort`, `filter` and the `aggregates` key - so usage follows
|
|
61
|
+
/// the column, as expression renames do.
|
|
62
|
+
pub fn update_window(
|
|
63
|
+
session: &Session,
|
|
64
|
+
renderer: &Renderer,
|
|
65
|
+
presentation: &Presentation,
|
|
66
|
+
old_name: String,
|
|
67
|
+
new_name: String,
|
|
68
|
+
spec: WindowSpec,
|
|
69
|
+
) {
|
|
70
|
+
clone!(session, renderer, presentation);
|
|
71
|
+
ApiFuture::spawn(async move {
|
|
72
|
+
let update = {
|
|
73
|
+
let config = session.get_view_config().clone();
|
|
74
|
+
let mut windows = config.windows.clone();
|
|
75
|
+
windows.remove(&old_name);
|
|
76
|
+
windows.insert(new_name.clone(), spec);
|
|
77
|
+
|
|
78
|
+
let rename = |col: &String| -> String {
|
|
79
|
+
if col == &old_name {
|
|
80
|
+
new_name.clone()
|
|
81
|
+
} else {
|
|
82
|
+
col.clone()
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
let mut update = ViewConfigUpdate {
|
|
87
|
+
windows: Some(windows),
|
|
88
|
+
..Default::default()
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
if old_name != new_name {
|
|
92
|
+
update.columns = Some(
|
|
93
|
+
config
|
|
94
|
+
.columns
|
|
95
|
+
.iter()
|
|
96
|
+
.map(|col| col.as_ref().map(rename))
|
|
97
|
+
.collect(),
|
|
98
|
+
);
|
|
99
|
+
update.group_by = Some(config.group_by.iter().map(&rename).collect());
|
|
100
|
+
update.split_by = Some(config.split_by.iter().map(&rename).collect());
|
|
101
|
+
update.sort = Some(
|
|
102
|
+
config
|
|
103
|
+
.sort
|
|
104
|
+
.iter()
|
|
105
|
+
.map(|sort| {
|
|
106
|
+
let mut sort = sort.clone();
|
|
107
|
+
sort.0 = rename(&sort.0);
|
|
108
|
+
sort
|
|
109
|
+
})
|
|
110
|
+
.collect(),
|
|
111
|
+
);
|
|
112
|
+
update.filter = Some(
|
|
113
|
+
config
|
|
114
|
+
.filter
|
|
115
|
+
.iter()
|
|
116
|
+
.map(|filter| {
|
|
117
|
+
if filter.column() == old_name {
|
|
118
|
+
Filter::new(new_name.as_str(), filter.op(), filter.term().clone())
|
|
119
|
+
} else {
|
|
120
|
+
filter.clone()
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
.collect(),
|
|
124
|
+
);
|
|
125
|
+
update.aggregates = Some(
|
|
126
|
+
config
|
|
127
|
+
.aggregates
|
|
128
|
+
.iter()
|
|
129
|
+
.map(|(col, agg)| (rename(col), agg.clone()))
|
|
130
|
+
.collect(),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
update
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
apply_and_render(&session, &renderer, update)?.await?;
|
|
138
|
+
reopen(&presentation, new_name);
|
|
139
|
+
Ok(())
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/// Remove the window named `name` from the session and re-render.
|
|
144
|
+
pub fn delete_window(session: &Session, renderer: &Renderer, name: &str) -> ApiResult<()> {
|
|
145
|
+
let mut windows = session.get_view_config().windows.clone();
|
|
146
|
+
windows.remove(name);
|
|
147
|
+
let config = ViewConfigUpdate {
|
|
148
|
+
windows: Some(windows),
|
|
149
|
+
..ViewConfigUpdate::default()
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
let task = apply_and_render(session, renderer, config)?;
|
|
153
|
+
ApiFuture::spawn(task);
|
|
154
|
+
Ok(())
|
|
155
|
+
}
|
package/src/rust/tasks/mod.rs
CHANGED
|
@@ -27,6 +27,7 @@ mod copy_export;
|
|
|
27
27
|
mod create_panel;
|
|
28
28
|
mod dismiss_render_warning;
|
|
29
29
|
mod edit_expression;
|
|
30
|
+
mod edit_window;
|
|
30
31
|
mod eject;
|
|
31
32
|
mod pipeline;
|
|
32
33
|
mod presize_panels;
|
|
@@ -58,6 +59,7 @@ pub use self::copy_export::*;
|
|
|
58
59
|
pub(crate) use self::create_panel::*;
|
|
59
60
|
pub use self::dismiss_render_warning::*;
|
|
60
61
|
pub use self::edit_expression::*;
|
|
62
|
+
pub use self::edit_window::*;
|
|
61
63
|
pub use self::eject::*;
|
|
62
64
|
pub use self::pipeline::*;
|
|
63
65
|
pub use self::presize_panels::*;
|