@perspective-dev/viewer 4.5.2 → 5.1.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 +20 -1
- 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/esm/extensions.d.ts +22 -3
- package/dist/esm/perspective-viewer.d.ts +9 -0
- package/dist/esm/perspective-viewer.inline.js +20 -1
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +20 -1
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +71 -5
- package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
- package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
- package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
- package/dist/esm/ts-rs/Layout.d.ts +11 -0
- package/dist/esm/ts-rs/Orientation.d.ts +4 -0
- package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +41 -0
- package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
- package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +8 -2
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +9 -0
- package/dist/esm/ts-rs/WindowAggregate.d.ts +1 -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 +46 -0
- package/dist/esm/ts-rs/Windows.d.ts +10 -0
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
- package/dist/wasm/perspective-viewer.d.ts +299 -78
- package/dist/wasm/perspective-viewer.js +544 -186
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-8a8c7d5d696b43ae/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-8a8c7d5d696b43ae/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-selector.css +1 -0
- package/src/css/column-settings-panel.css +68 -3
- package/src/css/column-style.css +4 -3
- package/src/css/config-selector.css +22 -1
- package/src/css/containers/context-menu.css +107 -0
- package/src/css/containers/tabs.css +2 -2
- package/src/css/panel-tab.css +193 -0
- package/src/css/plugin-selector.css +7 -10
- package/src/css/status-bar.css +75 -81
- package/src/css/viewer.css +188 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +28 -45
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
- package/src/rust/components/column_selector/config_selector.rs +40 -82
- package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +11 -9
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +25 -14
- 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 +73 -3
- package/src/rust/components/containers/sidebar.rs +99 -22
- package/src/rust/components/containers/split_panel.rs +181 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/editable_header.rs +2 -0
- package/src/rust/components/form/debug.rs +3 -0
- package/src/rust/components/global_filter_bar.rs +76 -0
- package/src/rust/components/main_panel/frame_theme.rs +186 -0
- package/src/rust/components/main_panel/msg.rs +62 -0
- package/src/rust/components/main_panel/presize.rs +220 -0
- package/src/rust/components/main_panel/reconcile.rs +224 -0
- package/src/rust/components/main_panel/render.rs +385 -0
- package/src/rust/components/main_panel/update.rs +184 -0
- package/src/rust/components/main_panel.rs +316 -71
- package/src/rust/components/mod.rs +5 -0
- package/src/rust/components/panel_menu.rs +493 -0
- package/src/rust/components/panel_tab.rs +510 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/portal.rs +7 -0
- package/src/rust/components/settings_panel.rs +13 -5
- package/src/rust/components/status_bar.rs +110 -178
- package/src/rust/components/status_indicator.rs +6 -9
- package/src/rust/components/style/mod.rs +5 -0
- package/src/rust/components/style/style_provider.rs +2 -0
- package/src/rust/components/viewer/filters.rs +133 -0
- package/src/rust/components/viewer/msg.rs +182 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +255 -0
- package/src/rust/components/viewer/settings.rs +434 -0
- package/src/rust/components/viewer/snapshots.rs +134 -0
- package/src/rust/components/viewer/wiring.rs +505 -0
- package/src/rust/components/viewer.rs +245 -756
- package/src/rust/components/window_editor.rs +1013 -0
- package/src/rust/config/export_method.rs +1 -1
- package/src/rust/config/mod.rs +4 -0
- package/src/rust/config/options.rs +71 -0
- package/src/rust/config/viewer_config.rs +24 -2
- package/src/rust/config/workspace_config.rs +92 -0
- package/src/rust/custom_elements/copy_dropdown.rs +3 -1
- package/src/rust/custom_elements/debug_plugin.rs +20 -1
- package/src/rust/custom_elements/export_dropdown.rs +3 -1
- package/src/rust/custom_elements/viewer.rs +1015 -286
- package/src/rust/custom_events.rs +212 -61
- package/src/rust/js/mod.rs +2 -0
- package/src/rust/js/plugin.rs +297 -10
- package/src/rust/js/regular_layout.rs +258 -0
- package/src/rust/lib.rs +24 -2
- package/src/rust/presentation/column_locator.rs +16 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/columns_iter_set.rs +7 -1
- package/src/rust/queries/get_viewer_config.rs +29 -11
- package/src/rust/queries/plugin_column_styles.rs +2 -2
- package/src/rust/renderer/activate.rs +41 -14
- package/src/rust/renderer/dispatch.rs +463 -0
- package/src/rust/renderer/plugin_config.rs +495 -0
- package/src/rust/renderer/registry.rs +18 -6
- package/src/rust/renderer/render_timer.rs +29 -10
- package/src/rust/renderer.rs +384 -838
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/drag_drop_update.rs +18 -0
- package/src/rust/session/metadata.rs +66 -9
- package/src/rust/session/replace_expression_update.rs +1 -0
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +755 -264
- package/src/rust/tasks/apply_global_filters.rs +89 -0
- package/src/rust/tasks/auto_pause.rs +171 -0
- package/src/rust/tasks/copy_export.rs +29 -19
- package/src/rust/tasks/create_panel.rs +243 -0
- package/src/rust/tasks/dismiss_render_warning.rs +3 -1
- package/src/rust/tasks/edit_expression.rs +4 -4
- package/src/rust/tasks/edit_window.rs +155 -0
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +30 -4
- package/src/rust/tasks/pipeline.rs +411 -0
- package/src/rust/tasks/presize_panels.rs +324 -0
- package/src/rust/tasks/reset_all.rs +22 -12
- package/src/rust/tasks/resize_observer.rs +124 -87
- package/src/rust/tasks/restore_and_render.rs +80 -86
- package/src/rust/tasks/restore_panel.rs +145 -0
- package/src/rust/tasks/send_column_config.rs +5 -2
- package/src/rust/tasks/send_plugin_config.rs +5 -2
- package/src/rust/tasks/set_edit_mode.rs +59 -0
- package/src/rust/tasks/sync_update_panels.rs +152 -0
- package/src/rust/tasks/table_lifecycle.rs +158 -0
- package/src/rust/tasks/update_theme.rs +56 -12
- package/src/rust/utils/browser/dragdrop.rs +12 -0
- package/src/rust/utils/browser/tests/debounce.rs +49 -0
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +189 -17
- package/src/rust/utils/mod.rs +6 -0
- package/src/rust/utils/pending_effects.rs +62 -0
- package/src/rust/utils/spawn.rs +33 -0
- package/src/rust/utils/weak_scope.rs +0 -10
- package/src/rust/workspace.rs +846 -0
- package/src/svg/active-indicator.svg +3 -0
- package/src/svg/pin-icon.svg +5 -0
- package/src/themes/botanical.css +5 -2
- package/src/themes/defaults.css +54 -2
- package/src/themes/dracula.css +6 -3
- package/src/themes/gruvbox-dark.css +5 -2
- package/src/themes/gruvbox.css +5 -2
- package/src/themes/icons.css +5 -2
- package/src/themes/intl/de.css +7 -0
- package/src/themes/intl/es.css +7 -0
- package/src/themes/intl/fr.css +7 -0
- package/src/themes/intl/ja.css +7 -0
- package/src/themes/intl/pt.css +7 -0
- package/src/themes/intl/zh.css +7 -0
- package/src/themes/intl.css +10 -1
- package/src/themes/monokai.css +5 -2
- package/src/themes/phosphor.css +5 -2
- package/src/themes/pro-dark.css +5 -2
- package/src/themes/pro.css +2 -1
- package/src/themes/solarized-dark.css +5 -2
- package/src/themes/solarized.css +5 -2
- package/src/themes/vaporwave.css +5 -2
- package/src/ts/bootstrap.ts +8 -7
- package/src/ts/extensions.ts +30 -2
- package/src/ts/perspective-viewer.ts +10 -0
- package/src/ts/plugin.ts +72 -5
- package/src/ts/ts-rs/ClientOptions.ts +7 -0
- package/src/ts/ts-rs/ExportMethod.ts +3 -0
- package/src/ts/ts-rs/ExportOptions.ts +8 -0
- package/src/ts/ts-rs/GetClientOptions.ts +7 -0
- package/src/ts/ts-rs/GetTableOptions.ts +7 -0
- package/src/ts/ts-rs/Layout.ts +9 -0
- package/src/ts/ts-rs/Orientation.ts +6 -0
- package/src/ts/ts-rs/PanelOptions.ts +7 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +16 -0
- package/src/ts/ts-rs/SplitLayout.ts +5 -0
- package/src/ts/ts-rs/TabLayout.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +7 -2
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +9 -0
- package/src/ts/ts-rs/WindowAggregate.ts +3 -0
- 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 +46 -0
- package/src/ts/ts-rs/Windows.ts +10 -0
- package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
- package/src/rust/tasks/intersection_observer.rs +0 -98
- package/src/rust/tasks/update_and_render.rs +0 -110
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline0.js → perspective-viewer-8a8c7d5d696b43ae/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-8a8c7d5d696b43ae/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-8a8c7d5d696b43ae/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-8a8c7d5d696b43ae/inline5.js} +0 -0
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
13
|
use perspective_client::config::*;
|
|
14
|
-
use perspective_js::utils::ApiFuture;
|
|
15
14
|
use web_sys::*;
|
|
16
15
|
use yew::prelude::*;
|
|
17
16
|
|
|
@@ -20,6 +19,7 @@ use crate::components::type_icon::TypeIcon;
|
|
|
20
19
|
use crate::presentation::Presentation;
|
|
21
20
|
use crate::renderer::*;
|
|
22
21
|
use crate::session::*;
|
|
22
|
+
use crate::tasks::apply_and_render;
|
|
23
23
|
use crate::utils::*;
|
|
24
24
|
|
|
25
25
|
#[derive(Properties)]
|
|
@@ -86,11 +86,8 @@ impl Component for SortColumn {
|
|
|
86
86
|
|
|
87
87
|
let session = ctx.props().session.clone();
|
|
88
88
|
let renderer = ctx.props().renderer.clone();
|
|
89
|
-
if
|
|
90
|
-
|
|
91
|
-
renderer.apply_pending_plugin()?;
|
|
92
|
-
renderer.draw(session.validate().await?.create_view()).await
|
|
93
|
-
});
|
|
89
|
+
if let Ok(task) = apply_and_render(&session, &renderer, update) {
|
|
90
|
+
spawn_owned("sort-column", task);
|
|
94
91
|
}
|
|
95
92
|
|
|
96
93
|
false
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
mod active_column;
|
|
14
14
|
mod add_expression_button;
|
|
15
15
|
mod aggregate_selector;
|
|
16
|
+
mod column_selector_column_row;
|
|
16
17
|
mod config_selector;
|
|
17
18
|
mod empty_column;
|
|
18
19
|
mod expr_edit_button;
|
|
@@ -25,10 +26,10 @@ mod sort_column;
|
|
|
25
26
|
use std::iter::*;
|
|
26
27
|
use std::rc::Rc;
|
|
27
28
|
|
|
29
|
+
pub use column_selector_column_row::*;
|
|
28
30
|
pub use empty_column::*;
|
|
29
31
|
pub use invalid_column::*;
|
|
30
32
|
use perspective_client::config::ViewConfig;
|
|
31
|
-
use perspective_js::utils::ApiFuture;
|
|
32
33
|
pub use pivot_column::*;
|
|
33
34
|
use web_sys::*;
|
|
34
35
|
use yew::prelude::*;
|
|
@@ -46,6 +47,7 @@ use crate::queries::{ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSe
|
|
|
46
47
|
use crate::renderer::*;
|
|
47
48
|
use crate::session::drag_drop_update::*;
|
|
48
49
|
use crate::session::*;
|
|
50
|
+
use crate::tasks::apply_and_render;
|
|
49
51
|
use crate::utils::*;
|
|
50
52
|
|
|
51
53
|
#[derive(Properties)]
|
|
@@ -218,11 +220,8 @@ impl Component for ColumnSelector {
|
|
|
218
220
|
|
|
219
221
|
let session = ctx.props().session.clone();
|
|
220
222
|
let renderer = ctx.props().renderer.clone();
|
|
221
|
-
if
|
|
222
|
-
|
|
223
|
-
renderer.apply_pending_plugin()?;
|
|
224
|
-
renderer.draw(session.validate().await?.create_view()).await
|
|
225
|
-
});
|
|
223
|
+
if let Ok(task) = apply_and_render(&session, &renderer, update) {
|
|
224
|
+
spawn_owned("column-selector", task);
|
|
226
225
|
}
|
|
227
226
|
}
|
|
228
227
|
|
|
@@ -246,11 +245,8 @@ impl Component for ColumnSelector {
|
|
|
246
245
|
|
|
247
246
|
let session = ctx.props().session.clone();
|
|
248
247
|
let renderer = ctx.props().renderer.clone();
|
|
249
|
-
if
|
|
250
|
-
|
|
251
|
-
renderer.apply_pending_plugin()?;
|
|
252
|
-
renderer.draw(session.validate().await?.create_view()).await
|
|
253
|
-
});
|
|
248
|
+
if let Ok(task) = apply_and_render(&session, &renderer, update) {
|
|
249
|
+
spawn_owned("column-selector", task);
|
|
254
250
|
}
|
|
255
251
|
|
|
256
252
|
true
|
|
@@ -373,7 +369,9 @@ impl Component for ColumnSelector {
|
|
|
373
369
|
let column_dropdown = self.column_dropdown.clone();
|
|
374
370
|
let is_editing = matches!(
|
|
375
371
|
&ctx.props().selected_column,
|
|
376
|
-
Some(ColumnLocator::Table(x))
|
|
372
|
+
Some(ColumnLocator::Table(x))
|
|
373
|
+
| Some(ColumnLocator::Expression(x))
|
|
374
|
+
| Some(ColumnLocator::Window(x))
|
|
377
375
|
if x == &key );
|
|
378
376
|
|
|
379
377
|
// Compute metadata-derived props here so that changes to
|
|
@@ -398,10 +396,15 @@ impl Component for ColumnSelector {
|
|
|
398
396
|
.map(|n| metadata.is_column_expression(n))
|
|
399
397
|
.unwrap_or(false);
|
|
400
398
|
|
|
399
|
+
let is_window = name
|
|
400
|
+
.get_name()
|
|
401
|
+
.map(|n| metadata.is_column_window(n))
|
|
402
|
+
.unwrap_or(false);
|
|
403
|
+
|
|
401
404
|
let can_render_styles =
|
|
402
405
|
name.get_name().is_some() && renderer.can_render_column_styles();
|
|
403
406
|
|
|
404
|
-
let show_edit_btn = is_expression || can_render_styles;
|
|
407
|
+
let show_edit_btn = is_expression || is_window || can_render_styles;
|
|
405
408
|
let on_open_expr_panel = &ctx.props().on_open_expr_panel;
|
|
406
409
|
html_nested! {
|
|
407
410
|
<ScrollPanelItem {key} {size_hint}>
|
|
@@ -411,6 +414,7 @@ impl Component for ColumnSelector {
|
|
|
411
414
|
{is_aggregated}
|
|
412
415
|
{is_editing}
|
|
413
416
|
{is_expression}
|
|
417
|
+
{is_window}
|
|
414
418
|
{show_edit_btn}
|
|
415
419
|
{col_type}
|
|
416
420
|
view_config={config.clone()}
|
|
@@ -431,12 +435,18 @@ impl Component for ColumnSelector {
|
|
|
431
435
|
|
|
432
436
|
let mut inactive_children: Vec<_> = columns_iter
|
|
433
437
|
.expression()
|
|
438
|
+
.chain(columns_iter.window())
|
|
434
439
|
.chain(columns_iter.inactive())
|
|
435
440
|
.enumerate()
|
|
436
441
|
.map(|(idx, vc)| {
|
|
437
442
|
let selected_column = ctx.props().selected_column.as_ref();
|
|
438
|
-
let is_editing = matches!(
|
|
443
|
+
let is_editing = matches!(
|
|
444
|
+
selected_column,
|
|
445
|
+
Some(ColumnLocator::Expression(x)) | Some(ColumnLocator::Window(x))
|
|
446
|
+
if x.as_str() == vc.name
|
|
447
|
+
);
|
|
439
448
|
let is_expression = metadata.is_column_expression(vc.name);
|
|
449
|
+
let is_window = metadata.is_column_window(vc.name);
|
|
440
450
|
html_nested! {
|
|
441
451
|
<ScrollPanelItem key={vc.name} size_hint=28.0>
|
|
442
452
|
<InactiveColumn
|
|
@@ -445,6 +455,7 @@ impl Component for ColumnSelector {
|
|
|
445
455
|
name={vc.name.to_owned()}
|
|
446
456
|
{is_editing}
|
|
447
457
|
{is_expression}
|
|
458
|
+
{is_window}
|
|
448
459
|
view_config={config.clone()}
|
|
449
460
|
metadata={metadata.clone()}
|
|
450
461
|
onselect={&onselect}
|
|
@@ -326,7 +326,8 @@ pub fn StyleTab(props: &StyleTabProps) -> Html {
|
|
|
326
326
|
},
|
|
327
327
|
};
|
|
328
328
|
|
|
329
|
-
|
|
329
|
+
let key = format!("{}::{}", props.column_name, keys.join("+"));
|
|
330
|
+
Some(html! { <fieldset class="style-control" {key}>{ component }</fieldset> })
|
|
330
331
|
})
|
|
331
332
|
.collect_vec()
|
|
332
333
|
})
|
|
@@ -0,0 +1,34 @@
|
|
|
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 yew::prelude::*;
|
|
14
|
+
|
|
15
|
+
use super::save_settings::{SaveSettings, SaveSettingsProps};
|
|
16
|
+
use crate::components::window_editor::{WindowEditor, WindowEditorProps};
|
|
17
|
+
|
|
18
|
+
#[derive(PartialEq, Properties, Clone)]
|
|
19
|
+
pub struct WindowTabProps {
|
|
20
|
+
pub editor: WindowEditorProps,
|
|
21
|
+
pub save_section: SaveSettingsProps,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[function_component]
|
|
25
|
+
pub fn WindowTab(p: &WindowTabProps) -> Html {
|
|
26
|
+
html! {
|
|
27
|
+
<div id="window-tab">
|
|
28
|
+
<div class="tab-section" id="window-editor-section">
|
|
29
|
+
<WindowEditor ..p.editor.clone() />
|
|
30
|
+
</div>
|
|
31
|
+
<div class="tab-section"><SaveSettings ..p.save_section.clone() /></div>
|
|
32
|
+
</div>
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -13,12 +13,13 @@ mod attributes_tab;
|
|
|
13
13
|
|
|
14
14
|
mod save_settings;
|
|
15
15
|
pub(crate) mod style_tab;
|
|
16
|
+
mod window_tab;
|
|
16
17
|
|
|
17
18
|
use std::rc::Rc;
|
|
18
19
|
|
|
19
20
|
use derivative::Derivative;
|
|
20
21
|
use itertools::Itertools;
|
|
21
|
-
use perspective_client::config::{ColumnType, Expression, ViewConfig};
|
|
22
|
+
use perspective_client::config::{ColumnType, Expression, ViewConfig, WindowSpec};
|
|
22
23
|
use perspective_client::utils::PerspectiveResultExt;
|
|
23
24
|
use yew::{Callback, Component, Html, Properties, html, props};
|
|
24
25
|
|
|
@@ -27,15 +28,19 @@ use self::style_tab::StyleTabProps;
|
|
|
27
28
|
use crate::components::column_settings_sidebar::attributes_tab::AttributesTab;
|
|
28
29
|
use crate::components::column_settings_sidebar::save_settings::SaveSettingsProps;
|
|
29
30
|
use crate::components::column_settings_sidebar::style_tab::StyleTab;
|
|
31
|
+
use crate::components::column_settings_sidebar::window_tab::{WindowTab, WindowTabProps};
|
|
30
32
|
use crate::components::containers::sidebar::Sidebar;
|
|
31
33
|
use crate::components::containers::tab_list::TabList;
|
|
32
34
|
use crate::components::editable_header::EditableHeaderProps;
|
|
33
35
|
use crate::components::expression_editor::ExpressionEditorProps;
|
|
34
36
|
use crate::components::type_icon::TypeIconType;
|
|
37
|
+
use crate::components::window_editor::WindowEditorProps;
|
|
35
38
|
use crate::presentation::{ColumnLocator, ColumnSettingsTab, Presentation};
|
|
36
39
|
use crate::renderer::Renderer;
|
|
37
40
|
use crate::session::{Session, SessionMetadataRc};
|
|
38
|
-
use crate::tasks::{
|
|
41
|
+
use crate::tasks::{
|
|
42
|
+
delete_expr, delete_window, save_expr, save_window, update_expr, update_window,
|
|
43
|
+
};
|
|
39
44
|
use crate::utils::PtrEqRc;
|
|
40
45
|
|
|
41
46
|
#[derive(Clone, Derivative, Properties)]
|
|
@@ -47,9 +52,22 @@ pub struct ColumnSettingsPanelProps {
|
|
|
47
52
|
pub width_override: Option<i32>,
|
|
48
53
|
pub on_select_tab: Callback<ColumnSettingsTab>,
|
|
49
54
|
|
|
50
|
-
///
|
|
51
|
-
///
|
|
52
|
-
|
|
55
|
+
/// Shared trap-door width across the drawer's Style/Attributes/Window
|
|
56
|
+
/// tabs.
|
|
57
|
+
#[prop_or_default]
|
|
58
|
+
pub auto_width: f64,
|
|
59
|
+
|
|
60
|
+
#[prop_or_default]
|
|
61
|
+
pub on_auto_width: Callback<f64>,
|
|
62
|
+
|
|
63
|
+
/// Whether the drawer is pinned into the layout.
|
|
64
|
+
#[prop_or_default]
|
|
65
|
+
pub is_pinned: bool,
|
|
66
|
+
|
|
67
|
+
#[prop_or_default]
|
|
68
|
+
pub on_toggle_pin: Callback<()>,
|
|
69
|
+
|
|
70
|
+
/// Active plugin name.
|
|
53
71
|
pub plugin_name: Option<String>,
|
|
54
72
|
|
|
55
73
|
/// Session metadata snapshot — threaded from `SessionProps`.
|
|
@@ -75,8 +93,10 @@ pub struct ColumnSettingsPanelProps {
|
|
|
75
93
|
pub session: Session,
|
|
76
94
|
}
|
|
77
95
|
|
|
78
|
-
impl
|
|
79
|
-
|
|
96
|
+
impl ColumnSettingsPanelProps {
|
|
97
|
+
/// Everything EXCEPT the trap-door `auto_width`: the props whose change
|
|
98
|
+
/// invalidates the drafts that `initialize` rebuilds.
|
|
99
|
+
fn identity_eq(&self, other: &Self) -> bool {
|
|
80
100
|
self.selected_column == other.selected_column
|
|
81
101
|
&& self.selected_tab == other.selected_tab
|
|
82
102
|
&& self.plugin_name == other.plugin_name
|
|
@@ -87,12 +107,21 @@ impl PartialEq for ColumnSettingsPanelProps {
|
|
|
87
107
|
}
|
|
88
108
|
}
|
|
89
109
|
|
|
110
|
+
impl PartialEq for ColumnSettingsPanelProps {
|
|
111
|
+
fn eq(&self, other: &Self) -> bool {
|
|
112
|
+
self.identity_eq(other)
|
|
113
|
+
&& self.auto_width == other.auto_width
|
|
114
|
+
&& self.is_pinned == other.is_pinned
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
90
118
|
#[derive(Debug)]
|
|
91
119
|
pub enum ColumnSettingsPanelMsg {
|
|
92
120
|
SetExprValue(Rc<String>),
|
|
93
121
|
SetExprValid(bool),
|
|
94
122
|
SetHeaderValue(Option<String>),
|
|
95
123
|
SetHeaderValid(bool),
|
|
124
|
+
SetWindowValue(Option<WindowSpec>),
|
|
96
125
|
SetSelectedTab((usize, ColumnSettingsTab)),
|
|
97
126
|
OnSaveAttributes(()),
|
|
98
127
|
OnResetAttributes(()),
|
|
@@ -117,6 +146,8 @@ pub struct ColumnSettingsPanel {
|
|
|
117
146
|
reset_enabled: bool,
|
|
118
147
|
save_count: u8,
|
|
119
148
|
save_enabled: bool,
|
|
149
|
+
initial_window_value: Option<WindowSpec>,
|
|
150
|
+
window_value: Option<WindowSpec>,
|
|
120
151
|
tabs: Vec<ColumnSettingsTab>,
|
|
121
152
|
}
|
|
122
153
|
|
|
@@ -138,6 +169,8 @@ impl Component for ColumnSettingsPanel {
|
|
|
138
169
|
reset_count: 0,
|
|
139
170
|
column_name: "".to_owned(),
|
|
140
171
|
maybe_ty: None,
|
|
172
|
+
initial_window_value: None,
|
|
173
|
+
window_value: None,
|
|
141
174
|
tabs: vec![],
|
|
142
175
|
on_input: Callback::default(),
|
|
143
176
|
on_save: Callback::default(),
|
|
@@ -149,12 +182,15 @@ impl Component for ColumnSettingsPanel {
|
|
|
149
182
|
}
|
|
150
183
|
|
|
151
184
|
fn changed(&mut self, ctx: &yew::prelude::Context<Self>, old_props: &Self::Properties) -> bool {
|
|
152
|
-
|
|
185
|
+
// Only reached when props are unequal. Re-`initialize` (which wipes
|
|
186
|
+
// in-progress expression/window drafts) only on IDENTITY changes -
|
|
187
|
+
// a trap-door `auto_width` change re-renders the `Sidebar` sizer
|
|
188
|
+
// alone.
|
|
189
|
+
if !ctx.props().identity_eq(old_props) {
|
|
153
190
|
self.initialize(ctx);
|
|
154
|
-
true
|
|
155
|
-
} else {
|
|
156
|
-
false
|
|
157
191
|
}
|
|
192
|
+
|
|
193
|
+
true
|
|
158
194
|
}
|
|
159
195
|
|
|
160
196
|
fn update(&mut self, ctx: &yew::prelude::Context<Self>, msg: Self::Message) -> bool {
|
|
@@ -187,6 +223,15 @@ impl Component for ColumnSettingsPanel {
|
|
|
187
223
|
self.save_enabled_effect();
|
|
188
224
|
true
|
|
189
225
|
},
|
|
226
|
+
ColumnSettingsPanelMsg::SetWindowValue(val) => {
|
|
227
|
+
if self.window_value != val {
|
|
228
|
+
self.window_value = val;
|
|
229
|
+
self.reset_enabled = true;
|
|
230
|
+
true
|
|
231
|
+
} else {
|
|
232
|
+
false
|
|
233
|
+
}
|
|
234
|
+
},
|
|
190
235
|
ColumnSettingsPanelMsg::SetSelectedTab((_, val)) => {
|
|
191
236
|
let rerender = ctx.props().selected_tab != Some(val);
|
|
192
237
|
ctx.props().on_select_tab.emit(val);
|
|
@@ -195,19 +240,58 @@ impl Component for ColumnSettingsPanel {
|
|
|
195
240
|
ColumnSettingsPanelMsg::OnResetAttributes(()) => {
|
|
196
241
|
self.header_value.clone_from(&self.initial_header_value);
|
|
197
242
|
self.expr_value.clone_from(&self.initial_expr_value);
|
|
243
|
+
self.window_value.clone_from(&self.initial_window_value);
|
|
198
244
|
self.save_enabled = false;
|
|
199
245
|
self.reset_enabled = false;
|
|
200
246
|
self.reset_count += 1;
|
|
201
247
|
true
|
|
202
248
|
},
|
|
203
249
|
ColumnSettingsPanelMsg::OnSaveAttributes(()) => {
|
|
250
|
+
if matches!(ctx.props().selected_tab, Some(ColumnSettingsTab::Window)) {
|
|
251
|
+
if let Some(spec) = self.window_value.clone() {
|
|
252
|
+
let name = self
|
|
253
|
+
.header_value
|
|
254
|
+
.clone()
|
|
255
|
+
.unwrap_or_else(|| self.column_name.clone());
|
|
256
|
+
match &ctx.props().selected_column {
|
|
257
|
+
ColumnLocator::Window(old_name) => update_window(
|
|
258
|
+
&ctx.props().session,
|
|
259
|
+
&ctx.props().renderer,
|
|
260
|
+
&ctx.props().presentation,
|
|
261
|
+
old_name.clone(),
|
|
262
|
+
name,
|
|
263
|
+
spec.clone(),
|
|
264
|
+
),
|
|
265
|
+
_ => {
|
|
266
|
+
if let Err(err) = save_window(
|
|
267
|
+
&ctx.props().session,
|
|
268
|
+
&ctx.props().renderer,
|
|
269
|
+
&ctx.props().presentation,
|
|
270
|
+
name,
|
|
271
|
+
spec.clone(),
|
|
272
|
+
) {
|
|
273
|
+
tracing::warn!("{}", err);
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
self.initial_window_value = Some(spec);
|
|
279
|
+
self.initial_header_value.clone_from(&self.header_value);
|
|
280
|
+
self.save_enabled = false;
|
|
281
|
+
self.reset_enabled = false;
|
|
282
|
+
self.save_count += 1;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
return true;
|
|
286
|
+
}
|
|
287
|
+
|
|
204
288
|
let new_expr = Expression::new(
|
|
205
289
|
self.header_value.clone().map(|s| s.into()),
|
|
206
290
|
(*(self.expr_value)).clone().into(),
|
|
207
291
|
);
|
|
208
292
|
|
|
209
293
|
match &ctx.props().selected_column {
|
|
210
|
-
ColumnLocator::Table(_) => {
|
|
294
|
+
ColumnLocator::Table(_) | ColumnLocator::Window(_) => {
|
|
211
295
|
tracing::error!("Tried to save non-expression column!")
|
|
212
296
|
},
|
|
213
297
|
ColumnLocator::Expression(name) => update_expr(
|
|
@@ -244,6 +328,13 @@ impl Component for ColumnSettingsPanel {
|
|
|
244
328
|
&self.column_name,
|
|
245
329
|
)
|
|
246
330
|
.unwrap_or_log();
|
|
331
|
+
} else if ctx.props().selected_column.is_saved_window() {
|
|
332
|
+
delete_window(
|
|
333
|
+
&ctx.props().session,
|
|
334
|
+
&ctx.props().renderer,
|
|
335
|
+
&self.column_name,
|
|
336
|
+
)
|
|
337
|
+
.unwrap_or_log();
|
|
247
338
|
}
|
|
248
339
|
|
|
249
340
|
ctx.props().on_close.emit(());
|
|
@@ -253,15 +344,24 @@ impl Component for ColumnSettingsPanel {
|
|
|
253
344
|
}
|
|
254
345
|
|
|
255
346
|
fn view(&self, ctx: &yew::prelude::Context<Self>) -> Html {
|
|
347
|
+
let is_window_tab = matches!(ctx.props().selected_tab, Some(ColumnSettingsTab::Window));
|
|
348
|
+
|
|
349
|
+
let header_placeholder = if is_window_tab {
|
|
350
|
+
Rc::new(self.column_name.clone())
|
|
351
|
+
} else {
|
|
352
|
+
self.expr_value.clone()
|
|
353
|
+
};
|
|
354
|
+
|
|
256
355
|
let header_props = props!(EditableHeaderProps {
|
|
257
356
|
initial_value: self.initial_header_value.clone(),
|
|
258
|
-
placeholder:
|
|
357
|
+
placeholder: header_placeholder,
|
|
259
358
|
reset_count: self.reset_count,
|
|
260
|
-
editable: ctx.props().selected_column.is_expr()
|
|
359
|
+
editable: (ctx.props().selected_column.is_expr()
|
|
261
360
|
&& matches!(
|
|
262
361
|
ctx.props().selected_tab,
|
|
263
362
|
Some(ColumnSettingsTab::Attributes)
|
|
264
|
-
)
|
|
363
|
+
))
|
|
364
|
+
|| (ctx.props().selected_column.is_window_editable() && is_window_tab),
|
|
265
365
|
update_on_input: true,
|
|
266
366
|
icon_type: self
|
|
267
367
|
.maybe_ty
|
|
@@ -324,7 +424,26 @@ impl Component for ColumnSettingsPanel {
|
|
|
324
424
|
|
|
325
425
|
let attrs_tab = AttributesTabProps {
|
|
326
426
|
expr_editor,
|
|
327
|
-
save_section,
|
|
427
|
+
save_section: save_section.clone(),
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
let window_changed = self.window_value != self.initial_window_value
|
|
431
|
+
|| self.header_value != self.initial_header_value;
|
|
432
|
+
let window_tab = WindowTabProps {
|
|
433
|
+
editor: WindowEditorProps {
|
|
434
|
+
metadata: ctx.props().metadata.clone(),
|
|
435
|
+
initial: self.initial_window_value.clone(),
|
|
436
|
+
on_change: ctx.link().callback(ColumnSettingsPanelMsg::SetWindowValue),
|
|
437
|
+
reset_count: self.reset_count,
|
|
438
|
+
presentation: ctx.props().presentation.clone(),
|
|
439
|
+
session: ctx.props().session.clone(),
|
|
440
|
+
selected_theme: ctx.props().selected_theme.clone(),
|
|
441
|
+
},
|
|
442
|
+
save_section: SaveSettingsProps {
|
|
443
|
+
save_enabled: self.window_value.is_some() && window_changed && self.header_valid,
|
|
444
|
+
show_danger_zone: ctx.props().selected_column.is_saved_window(),
|
|
445
|
+
..save_section
|
|
446
|
+
},
|
|
328
447
|
};
|
|
329
448
|
|
|
330
449
|
let style_tab = StyleTabProps {
|
|
@@ -342,6 +461,7 @@ impl Component for ColumnSettingsPanel {
|
|
|
342
461
|
|
|
343
462
|
let tab_children = self.tabs.iter().map(|tab| match tab {
|
|
344
463
|
ColumnSettingsTab::Attributes => html! { <AttributesTab ..attrs_tab.clone() /> },
|
|
464
|
+
ColumnSettingsTab::Window => html! { <WindowTab ..window_tab.clone() /> },
|
|
345
465
|
ColumnSettingsTab::Style => html! { <StyleTab ..style_tab.clone() /> },
|
|
346
466
|
});
|
|
347
467
|
|
|
@@ -358,6 +478,10 @@ impl Component for ColumnSettingsPanel {
|
|
|
358
478
|
on_close={ctx.props().on_close.clone()}
|
|
359
479
|
id_prefix="column_settings"
|
|
360
480
|
width_override={ctx.props().width_override}
|
|
481
|
+
auto_width={ctx.props().auto_width}
|
|
482
|
+
on_auto_width={ctx.props().on_auto_width.clone()}
|
|
483
|
+
is_pinned={ctx.props().is_pinned}
|
|
484
|
+
on_toggle_pin={Some(ctx.props().on_toggle_pin.clone())}
|
|
361
485
|
selected_tab={selected_tab_idx}
|
|
362
486
|
{header_props}
|
|
363
487
|
>
|
|
@@ -403,6 +527,16 @@ impl ColumnSettingsPanel {
|
|
|
403
527
|
.metadata
|
|
404
528
|
.locator_view_type(&ctx.props().selected_column);
|
|
405
529
|
|
|
530
|
+
// Specs are unnamed (the `windows` map key names them, and the
|
|
531
|
+
// editable header owns naming in the UI), so drafts and saved specs
|
|
532
|
+
// compare directly.
|
|
533
|
+
let initial_window_value = ctx
|
|
534
|
+
.props()
|
|
535
|
+
.selected_column
|
|
536
|
+
.name()
|
|
537
|
+
.and_then(|name| ctx.props().view_config.windows.get(name))
|
|
538
|
+
.cloned();
|
|
539
|
+
|
|
406
540
|
let tabs = {
|
|
407
541
|
let mut tabs = vec![];
|
|
408
542
|
let is_new_expr = ctx.props().selected_column.is_new_expr();
|
|
@@ -424,6 +558,17 @@ impl ColumnSettingsPanel {
|
|
|
424
558
|
tabs.push(ColumnSettingsTab::Attributes);
|
|
425
559
|
}
|
|
426
560
|
|
|
561
|
+
let supports_windows = ctx
|
|
562
|
+
.props()
|
|
563
|
+
.metadata
|
|
564
|
+
.get_features()
|
|
565
|
+
.map(|x| x.has_window_aggregates())
|
|
566
|
+
.unwrap_or_default();
|
|
567
|
+
|
|
568
|
+
if ctx.props().selected_column.is_window_editable() && supports_windows {
|
|
569
|
+
tabs.push(ColumnSettingsTab::Window);
|
|
570
|
+
}
|
|
571
|
+
|
|
427
572
|
tabs
|
|
428
573
|
};
|
|
429
574
|
|
|
@@ -440,6 +585,8 @@ impl ColumnSettingsPanel {
|
|
|
440
585
|
header_value: initial_header_value.clone(),
|
|
441
586
|
initial_header_value,
|
|
442
587
|
maybe_ty,
|
|
588
|
+
window_value: initial_window_value.clone(),
|
|
589
|
+
initial_window_value,
|
|
443
590
|
tabs,
|
|
444
591
|
header_valid: true,
|
|
445
592
|
on_input,
|
|
@@ -52,6 +52,21 @@ where
|
|
|
52
52
|
|
|
53
53
|
#[prop_or_default]
|
|
54
54
|
pub allow_duplicates: bool,
|
|
55
|
+
|
|
56
|
+
/// Single-slot mode: the list holds at most one item, a dragover
|
|
57
|
+
/// preview REPLACES the current item rather than inserting beside it,
|
|
58
|
+
/// and the trailing `EmptyColumn` autocomplete renders only while the
|
|
59
|
+
/// slot is empty.
|
|
60
|
+
#[prop_or_default]
|
|
61
|
+
pub single_slot: bool,
|
|
62
|
+
|
|
63
|
+
/// The in-flight drag is INVALID for this list (a parent-defined rule,
|
|
64
|
+
/// e.g. the window editor's Table-columns-only slots): the dragover
|
|
65
|
+
/// preview is suppressed and the invalid-X overlay renders instead,
|
|
66
|
+
/// like a duplicate drag over `group_by`/`split_by`. The parent's drop
|
|
67
|
+
/// handler is still responsible for ignoring the drop itself.
|
|
68
|
+
#[prop_or_default]
|
|
69
|
+
pub is_invalid: bool,
|
|
55
70
|
}
|
|
56
71
|
|
|
57
72
|
impl<T, U> PartialEq for DragDropListProps<T, U>
|
|
@@ -66,6 +81,8 @@ where
|
|
|
66
81
|
&& self.allow_duplicates == other.allow_duplicates
|
|
67
82
|
&& self.is_dragover == other.is_dragover
|
|
68
83
|
&& self.disabled == other.disabled
|
|
84
|
+
&& self.single_slot == other.single_slot
|
|
85
|
+
&& self.is_invalid == other.is_invalid
|
|
69
86
|
}
|
|
70
87
|
}
|
|
71
88
|
|
|
@@ -202,7 +219,51 @@ where
|
|
|
202
219
|
let invalid_drag: bool;
|
|
203
220
|
let mut valid_duplicate_drag = false;
|
|
204
221
|
|
|
205
|
-
let columns_html = {
|
|
222
|
+
let columns_html = if ctx.props().single_slot {
|
|
223
|
+
invalid_drag = ctx.props().is_invalid && ctx.props().is_dragover.is_some();
|
|
224
|
+
|
|
225
|
+
// Dragging the slot's own pill over its own slot is a no-op
|
|
226
|
+
// move - keep showing the pill instead of the drop preview
|
|
227
|
+
// (mirrors the multi-column branch's `is_self_move` handling).
|
|
228
|
+
let is_self_move = ctx
|
|
229
|
+
.props()
|
|
230
|
+
.presentation
|
|
231
|
+
.get_drag_target()
|
|
232
|
+
.map(|x| V::is_self_move(x))
|
|
233
|
+
.unwrap_or_default();
|
|
234
|
+
|
|
235
|
+
let close = ctx.props().parent.callback(|_| V::close(0));
|
|
236
|
+
let dragenter = ctx.props().parent.callback({
|
|
237
|
+
let container_noderef = container_noderef.clone();
|
|
238
|
+
move |event: DragEvent| {
|
|
239
|
+
event.stop_propagation();
|
|
240
|
+
event.prevent_default();
|
|
241
|
+
if event.related_target().is_none()
|
|
242
|
+
&& let Some(elem) = container_noderef.cast::<HtmlElement>()
|
|
243
|
+
{
|
|
244
|
+
let _ = elem.dataset().set("safaridragleave", "true");
|
|
245
|
+
}
|
|
246
|
+
V::dragenter(0)
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
if ctx.props().is_dragover.is_some() && !is_self_move && !invalid_drag {
|
|
251
|
+
html! {
|
|
252
|
+
<div class="pivot-column" ondragenter={dragenter}>
|
|
253
|
+
<div class="config-drop" />
|
|
254
|
+
</div>
|
|
255
|
+
}
|
|
256
|
+
} else if let Some(column) = ctx.props().children.iter().next() {
|
|
257
|
+
html! {
|
|
258
|
+
<div class="pivot-column" ondragenter={dragenter}>
|
|
259
|
+
{ Html::from(column) }
|
|
260
|
+
<span class="row_close" onmousedown={close} />
|
|
261
|
+
</div>
|
|
262
|
+
}
|
|
263
|
+
} else {
|
|
264
|
+
html! {}
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
206
267
|
let mut columns = ctx
|
|
207
268
|
.props()
|
|
208
269
|
.children
|
|
@@ -211,7 +272,10 @@ where
|
|
|
211
272
|
.enumerate()
|
|
212
273
|
.collect::<Vec<_>>();
|
|
213
274
|
|
|
214
|
-
invalid_drag = if
|
|
275
|
+
invalid_drag = if ctx.props().is_invalid && ctx.props().is_dragover.is_some() {
|
|
276
|
+
// Parent-defined invalidity: no preview, X overlay only.
|
|
277
|
+
true
|
|
278
|
+
} else if let Some((x, column)) = &ctx.props().is_dragover {
|
|
215
279
|
let index = *x;
|
|
216
280
|
let is_append = index == columns.len();
|
|
217
281
|
let is_self_move = ctx
|
|
@@ -314,6 +378,12 @@ where
|
|
|
314
378
|
.collect::<Html>()
|
|
315
379
|
};
|
|
316
380
|
|
|
381
|
+
let show_empty = if ctx.props().single_slot {
|
|
382
|
+
ctx.props().children.is_empty() && ctx.props().is_dragover.is_none()
|
|
383
|
+
} else {
|
|
384
|
+
ctx.props().is_dragover.is_none() | (!invalid_drag && valid_duplicate_drag)
|
|
385
|
+
};
|
|
386
|
+
|
|
317
387
|
let column_dropdown = ctx.props().column_dropdown.clone();
|
|
318
388
|
let exclude = ctx.props().exclude.clone();
|
|
319
389
|
let on_select = ctx.props().parent.callback(V::create);
|
|
@@ -345,7 +415,7 @@ where
|
|
|
345
415
|
onmousedown={ctx.props().parent.callback(move |_| V::close(0))}
|
|
346
416
|
/>
|
|
347
417
|
</div>
|
|
348
|
-
} else if
|
|
418
|
+
} else if show_empty {
|
|
349
419
|
<EmptyColumn {column_dropdown} {exclude} {on_select} />
|
|
350
420
|
} else if invalid_drag {
|
|
351
421
|
<InvalidColumn />
|