@perspective-dev/viewer 4.3.0 → 4.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +2 -2
- 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/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/perspective-viewer.inline.js +2 -2
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +2 -2
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/wasm/perspective-viewer.d.ts +57 -53
- package/dist/wasm/perspective-viewer.js +190 -165
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
- package/package.json +7 -5
- package/src/{less/aggregate-selector.less → css/aggregate-selector.css} +23 -20
- package/src/css/column-dropdown.css +109 -0
- package/src/{less/column-selector.less → css/column-selector.css} +160 -158
- package/src/{less/column-settings-panel.less → css/column-settings-panel.css} +69 -59
- package/src/{less/column-style.less → css/column-style.css} +52 -66
- package/src/{less/column-symbol-attributes.less → css/column-symbol-attributes.css} +15 -14
- package/src/{less/config-selector.less → css/config-selector.css} +151 -135
- package/src/{less/containers/dropdown-menu.less → css/containers/dropdown-menu.css} +20 -19
- package/src/{less/containers/pairs-list.less → css/containers/pairs-list.css} +13 -12
- package/src/{themes/variables.less → css/containers/scroll-panel.css} +25 -22
- package/src/{less/containers/split-panel.less → css/containers/split-panel.css} +15 -14
- package/src/{less/containers/tabs.less → css/containers/tabs.css} +17 -19
- package/src/css/dom/checkbox.css +102 -0
- package/src/css/dom/scrollbar.css +35 -0
- package/src/{less/dom/select.less → css/dom/select.css} +17 -18
- package/src/{less/empty-column.less → css/empty-column.css} +19 -18
- package/src/{less/expression-editor.less → css/expression-editor.css} +19 -18
- package/src/{less/filter-dropdown.less → css/filter-dropdown.css} +12 -11
- package/src/{less/filter-item.less → css/filter-item.css} +16 -15
- package/src/{less/form/code-editor.less → css/form/code-editor.css} +26 -30
- package/src/{less/form/debug.less → css/form/debug.css} +19 -18
- package/src/{less/function-dropdown.less → css/function-dropdown.css} +12 -11
- package/src/css/plugin-selector.css +261 -0
- package/src/{less/render-warning.less → css/render-warning.css} +18 -17
- package/src/{less/status-bar.less → css/status-bar.css} +156 -144
- package/src/css/type-icon.css +116 -0
- package/src/{less/viewer.less → css/viewer.css} +112 -146
- package/src/rust/components/column_dropdown.rs +229 -119
- package/src/rust/components/column_selector/active_column.rs +81 -62
- package/src/rust/components/column_selector/add_expression_button.rs +1 -0
- package/src/rust/components/column_selector/aggregate_selector.rs +25 -15
- package/src/rust/components/column_selector/config_selector.rs +315 -199
- package/src/rust/components/column_selector/empty_column.rs +2 -2
- package/src/rust/components/column_selector/expr_edit_button.rs +8 -2
- package/src/rust/components/column_selector/filter_column.rs +37 -26
- package/src/rust/components/column_selector/inactive_column.rs +41 -29
- package/src/rust/components/column_selector/invalid_column.rs +7 -18
- package/src/rust/components/column_selector/pivot_column.rs +11 -5
- package/src/rust/components/column_selector/sort_column.rs +23 -13
- package/src/rust/components/column_selector.rs +163 -84
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/row_selector.rs +1 -1
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs.rs +3 -2
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_pairs_item.rs +3 -2
- package/src/rust/components/column_settings_sidebar/style_tab/symbol/symbol_selector.rs +2 -3
- package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +7 -1
- package/src/rust/components/column_settings_sidebar/style_tab.rs +153 -112
- package/src/rust/components/column_settings_sidebar.rs +91 -53
- package/src/rust/components/containers/dragdrop_list.rs +2 -1
- package/src/rust/components/containers/sidebar_close_button.rs +1 -1
- package/src/rust/components/containers/split_panel.rs +1 -0
- package/src/rust/components/containers/tab_list.rs +1 -1
- package/src/rust/components/copy_dropdown.rs +7 -28
- package/src/rust/components/datetime_column_style/custom.rs +2 -2
- package/src/rust/components/datetime_column_style/simple.rs +2 -2
- package/src/rust/components/datetime_column_style.rs +4 -2
- package/src/rust/components/editable_header.rs +7 -4
- package/src/rust/components/empty_row.rs +1 -1
- package/src/rust/components/export_dropdown.rs +4 -30
- package/src/rust/components/expression_editor.rs +19 -10
- package/src/rust/components/filter_dropdown.rs +246 -102
- package/src/rust/components/font_loader.rs +11 -28
- package/src/rust/components/form/code_editor.rs +17 -2
- package/src/rust/components/form/color_range_selector.rs +19 -6
- package/src/rust/components/form/debug.rs +30 -13
- package/src/rust/components/function_dropdown.rs +186 -113
- package/src/rust/components/main_panel.rs +71 -89
- package/src/rust/components/mod.rs +1 -1
- package/src/rust/components/modal.rs +7 -1
- package/src/rust/components/number_column_style.rs +22 -7
- package/src/rust/components/plugin_selector.rs +34 -102
- package/src/rust/components/portal.rs +274 -0
- package/src/rust/components/render_warning.rs +72 -123
- package/src/rust/components/settings_panel.rs +115 -11
- package/src/rust/components/status_bar.rs +222 -98
- package/src/rust/components/status_bar_counter.rs +8 -20
- package/src/rust/components/status_indicator.rs +64 -114
- package/src/rust/components/string_column_style.rs +2 -2
- package/src/rust/components/style/style_cache.rs +5 -1
- package/src/rust/components/viewer.rs +391 -39
- package/src/rust/custom_elements/copy_dropdown.rs +102 -21
- package/src/rust/custom_elements/export_dropdown.rs +102 -20
- package/src/rust/custom_elements/mod.rs +0 -7
- package/src/rust/custom_elements/modal.rs +7 -103
- package/src/rust/custom_elements/viewer.rs +99 -35
- package/src/rust/custom_events.rs +23 -2
- package/src/rust/dragdrop.rs +149 -10
- package/src/{less/containers/scroll-panel.less → rust/engines.rs} +15 -13
- package/src/rust/js/plugin.rs +1 -1
- package/src/rust/lib.rs +5 -4
- package/src/rust/presentation/props.rs +39 -0
- package/src/rust/presentation/sheets.rs +3 -3
- package/src/rust/presentation.rs +44 -8
- package/src/rust/renderer/limits.rs +32 -3
- package/src/{less/dom/scrollbar.less → rust/renderer/props.rs} +18 -19
- package/src/rust/renderer.rs +83 -9
- package/src/rust/session/column_defaults_update.rs +1 -1
- package/src/rust/session/metadata.rs +23 -2
- package/src/rust/session/props.rs +178 -0
- package/src/rust/session.rs +124 -117
- package/src/rust/tasks/column_locator.rs +133 -0
- package/src/rust/{model → tasks}/columns_iter_set.rs +14 -23
- package/src/rust/{model → tasks}/edit_expression.rs +34 -10
- package/src/rust/{model → tasks}/eject.rs +2 -2
- package/src/rust/{model → tasks}/get_viewer_config.rs +0 -11
- package/src/rust/{model → tasks}/intersection_observer.rs +19 -3
- package/src/{less/containers/radio-list.less → rust/tasks/is_invalid_drop.rs} +21 -14
- package/src/rust/tasks/mod.rs +52 -0
- package/src/rust/{model → tasks}/plugin_column_styles.rs +69 -46
- package/src/rust/{model → tasks}/resize_observer.rs +39 -6
- package/src/rust/{model → tasks}/send_plugin_config.rs +1 -1
- package/src/rust/tasks/structural.rs +53 -0
- package/src/rust/utils/mod.rs +4 -0
- package/src/rust/utils/modal_position.rs +110 -0
- package/src/rust/utils/ptr_eq_rc.rs +74 -0
- package/src/rust/utils/pubsub.rs +11 -1
- package/src/svg/bg-pattern.png +0 -0
- package/src/svg/close-icon.svg +1 -1
- package/src/svg/expression.svg +1 -1
- package/src/svg/mega-menu-icons-candlestick.svg +1 -1
- package/src/svg/mega-menu-icons-datagrid.svg +1 -2
- package/src/svg/mega-menu-icons-heatmap.svg +1 -1
- package/src/svg/mega-menu-icons-map-scatter.svg +1 -1
- package/src/svg/mega-menu-icons-ohlc.svg +1 -1
- package/src/svg/mega-menu-icons-sunburst.svg +1 -1
- package/src/svg/mega-menu-icons-treemap.svg +1 -1
- package/src/svg/mega-menu-icons-x-bar.svg +1 -1
- package/src/svg/mega-menu-icons-x-y-line.svg +1 -1
- package/src/svg/mega-menu-icons-x-y-scatter.svg +1 -1
- package/src/svg/mega-menu-icons-y-area.svg +1 -1
- package/src/svg/mega-menu-icons-y-bar.svg +1 -1
- package/src/svg/mega-menu-icons-y-line.svg +1 -1
- package/src/svg/mega-menu-icons-y-scatter.svg +1 -1
- package/src/svg/radio-hover.svg +1 -1
- package/src/svg/radio-off.svg +1 -1
- package/src/svg/radio-on.svg +1 -1
- package/src/themes/botanical.css +157 -0
- package/src/themes/defaults.css +139 -0
- package/src/themes/dracula.css +233 -0
- package/src/themes/gruvbox-dark.css +255 -0
- package/src/themes/gruvbox.css +134 -0
- package/src/themes/icons.css +124 -0
- package/src/themes/intl/de.css +102 -0
- package/src/themes/intl/es.css +102 -0
- package/src/themes/intl/fr.css +102 -0
- package/src/themes/intl/ja.css +102 -0
- package/src/themes/intl/pt.css +102 -0
- package/src/themes/intl/zh.css +102 -0
- package/src/themes/intl.css +102 -0
- package/src/themes/monokai.css +233 -0
- package/src/themes/pro-dark.css +158 -0
- package/src/themes/{themes.less → pro.css} +17 -21
- package/src/themes/solarized-dark.css +135 -0
- package/src/themes/solarized.css +95 -0
- package/src/themes/themes.css +22 -0
- package/src/themes/vaporwave.css +256 -0
- package/dist/css/variables.css +0 -0
- package/src/less/column-dropdown.less +0 -95
- package/src/less/dom/checkbox.less +0 -100
- package/src/less/plugin-selector.less +0 -183
- package/src/less/type-icon.less +0 -68
- package/src/rust/components/error_message.rs +0 -56
- package/src/rust/custom_elements/column_dropdown.rs +0 -123
- package/src/rust/custom_elements/filter_dropdown.rs +0 -179
- package/src/rust/custom_elements/function_dropdown.rs +0 -115
- package/src/rust/model/column_locator.rs +0 -82
- package/src/rust/model/is_invalid_drop.rs +0 -36
- package/src/rust/model/mod.rs +0 -100
- package/src/rust/model/reset_all.rs +0 -38
- package/src/rust/model/structural.rs +0 -244
- package/src/themes/botanical.less +0 -142
- package/src/themes/dracula.less +0 -101
- package/src/themes/gruvbox-dark.less +0 -116
- package/src/themes/gruvbox.less +0 -152
- package/src/themes/icons.less +0 -130
- package/src/themes/intl/de.less +0 -102
- package/src/themes/intl/es.less +0 -102
- package/src/themes/intl/fr.less +0 -102
- package/src/themes/intl/ja.less +0 -102
- package/src/themes/intl/pt.less +0 -102
- package/src/themes/intl/zh.less +0 -102
- package/src/themes/intl.less +0 -102
- package/src/themes/monokai.less +0 -107
- package/src/themes/pro-dark.less +0 -147
- package/src/themes/pro.less +0 -186
- package/src/themes/solarized-dark.less +0 -78
- package/src/themes/solarized.less +0 -102
- package/src/themes/vaporwave.less +0 -145
- /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d729f682ba5c19df → perspective-viewer-68fef752754ffbc6}/inline4.js +0 -0
- /package/src/rust/{model → tasks}/copy_export.rs +0 -0
- /package/src/rust/{model → tasks}/export_app.rs +0 -0
- /package/src/rust/{model → tasks}/export_method.rs +0 -0
- /package/src/rust/{model → tasks}/restore_and_render.rs +0 -0
- /package/src/rust/{model → tasks}/update_and_render.rs +0 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
|
2
|
+
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
|
3
|
+
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
|
4
|
+
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
|
5
|
+
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
|
6
|
+
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
|
7
|
+
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
|
8
|
+
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
|
9
|
+
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
|
10
|
+
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
|
+
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
|
+
|
|
13
|
+
use std::cell::Cell;
|
|
14
|
+
use std::rc::Rc;
|
|
15
|
+
|
|
16
|
+
use perspective_js::utils::global;
|
|
17
|
+
use wasm_bindgen::JsCast;
|
|
18
|
+
use wasm_bindgen::prelude::*;
|
|
19
|
+
use web_sys::*;
|
|
20
|
+
use yew::prelude::*;
|
|
21
|
+
|
|
22
|
+
use crate::components::modal::ModalOrientation;
|
|
23
|
+
use crate::components::style::StyleProvider;
|
|
24
|
+
use crate::utils::*;
|
|
25
|
+
|
|
26
|
+
#[derive(Properties, PartialEq)]
|
|
27
|
+
pub struct PortalModalProps {
|
|
28
|
+
pub children: Children,
|
|
29
|
+
|
|
30
|
+
/// The element to position relative to. `None` means closed.
|
|
31
|
+
pub target: Option<HtmlElement>,
|
|
32
|
+
|
|
33
|
+
/// Whether the portal manages its own focus and closes on blur.
|
|
34
|
+
#[prop_or(true)]
|
|
35
|
+
pub own_focus: bool,
|
|
36
|
+
|
|
37
|
+
/// Called when the portal closes (blur, etc).
|
|
38
|
+
#[prop_or_default]
|
|
39
|
+
pub on_close: Callback<()>,
|
|
40
|
+
|
|
41
|
+
pub tag_name: &'static str,
|
|
42
|
+
|
|
43
|
+
pub theme: String,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
pub enum PortalModalMsg {
|
|
47
|
+
Reposition,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pub struct PortalModal {
|
|
51
|
+
host: HtmlElement,
|
|
52
|
+
shadow_root: Element,
|
|
53
|
+
top: f64,
|
|
54
|
+
left: f64,
|
|
55
|
+
visible: bool,
|
|
56
|
+
rev_vert: ModalOrientation,
|
|
57
|
+
anchor: Rc<Cell<ModalAnchor>>,
|
|
58
|
+
_blur_closure: Option<Closure<dyn FnMut(FocusEvent)>>,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
impl PortalModal {
|
|
62
|
+
fn attach_to_body(&self) {
|
|
63
|
+
if !self.host.is_connected() {
|
|
64
|
+
let _ = global::body().append_child(&self.host);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
fn detach_from_body(&mut self) {
|
|
69
|
+
if self.host.is_connected() {
|
|
70
|
+
let _ = global::body().remove_child(&self.host);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if let Some(closure) = self._blur_closure.as_ref() {
|
|
74
|
+
self.host
|
|
75
|
+
.remove_event_listener_with_callback("blur", closure.as_ref().unchecked_ref())
|
|
76
|
+
.unwrap()
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
self._blur_closure = None;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
fn position_against_target(&mut self, target: &HtmlElement) {
|
|
83
|
+
let target_rect = target.get_bounding_client_rect();
|
|
84
|
+
let height = target_rect.height();
|
|
85
|
+
let width = target_rect.width();
|
|
86
|
+
let top = target_rect.top();
|
|
87
|
+
let left = target_rect.left();
|
|
88
|
+
|
|
89
|
+
if !self.visible {
|
|
90
|
+
// First pass: position at default anchor, invisible
|
|
91
|
+
self.top = top + height - 1.0;
|
|
92
|
+
self.left = left;
|
|
93
|
+
self.visible = false;
|
|
94
|
+
} else {
|
|
95
|
+
// Second pass: compute actual anchor and reposition
|
|
96
|
+
let anchor = calc_relative_position(&self.host, top, left, height, width);
|
|
97
|
+
self.anchor.set(anchor);
|
|
98
|
+
let modal_rect = self.host.get_bounding_client_rect();
|
|
99
|
+
let (new_top, new_left) = calc_anchor_position(anchor, &target_rect, &modal_rect);
|
|
100
|
+
self.top = new_top;
|
|
101
|
+
self.left = new_left;
|
|
102
|
+
self.rev_vert.set(anchor.is_rev_vert());
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
fn setup_blur_handler(&mut self, ctx: &Context<Self>) {
|
|
107
|
+
let on_close = {
|
|
108
|
+
let target = ctx.props().target.clone();
|
|
109
|
+
ctx.props().on_close.reform(move |_| {
|
|
110
|
+
if let Some(target) = &target {
|
|
111
|
+
target.class_list().remove_1("modal-target").unwrap();
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
let closure = Closure::wrap(Box::new(move |_: FocusEvent| {
|
|
117
|
+
on_close.emit(());
|
|
118
|
+
}) as Box<dyn FnMut(FocusEvent)>);
|
|
119
|
+
|
|
120
|
+
let _ = self
|
|
121
|
+
.host
|
|
122
|
+
.add_event_listener_with_callback("blur", closure.as_ref().unchecked_ref());
|
|
123
|
+
|
|
124
|
+
self._blur_closure = Some(closure);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
impl Component for PortalModal {
|
|
129
|
+
type Message = PortalModalMsg;
|
|
130
|
+
type Properties = PortalModalProps;
|
|
131
|
+
|
|
132
|
+
fn create(ctx: &Context<Self>) -> Self {
|
|
133
|
+
let host: HtmlElement = global::document()
|
|
134
|
+
.create_element(ctx.props().tag_name)
|
|
135
|
+
.unwrap()
|
|
136
|
+
.unchecked_into();
|
|
137
|
+
|
|
138
|
+
host.style().set_property("position", "fixed").unwrap();
|
|
139
|
+
host.style().set_property("z-index", "10000").unwrap();
|
|
140
|
+
let init = ShadowRootInit::new(ShadowRootMode::Open);
|
|
141
|
+
let shadow_root = if let Some(elem) = host.shadow_root() {
|
|
142
|
+
elem
|
|
143
|
+
} else {
|
|
144
|
+
host.attach_shadow(&init).unwrap()
|
|
145
|
+
}
|
|
146
|
+
.unchecked_into::<Element>();
|
|
147
|
+
|
|
148
|
+
Self {
|
|
149
|
+
host,
|
|
150
|
+
shadow_root,
|
|
151
|
+
top: 0.0,
|
|
152
|
+
left: 0.0,
|
|
153
|
+
visible: false,
|
|
154
|
+
rev_vert: Default::default(),
|
|
155
|
+
anchor: Default::default(),
|
|
156
|
+
_blur_closure: None,
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
161
|
+
match msg {
|
|
162
|
+
PortalModalMsg::Reposition => {
|
|
163
|
+
self.visible = true;
|
|
164
|
+
true
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
|
|
170
|
+
let new_target = &ctx.props().target;
|
|
171
|
+
let old_target = &old_props.target;
|
|
172
|
+
|
|
173
|
+
match (old_target, new_target, self._blur_closure.as_ref()) {
|
|
174
|
+
(None, Some(_), Some(closure)) => {
|
|
175
|
+
self.visible = false;
|
|
176
|
+
self.host
|
|
177
|
+
.remove_event_listener_with_callback("blur", closure.as_ref().unchecked_ref())
|
|
178
|
+
.unwrap();
|
|
179
|
+
|
|
180
|
+
self._blur_closure = None;
|
|
181
|
+
},
|
|
182
|
+
(None, Some(_), None) => {
|
|
183
|
+
self.visible = false;
|
|
184
|
+
self._blur_closure = None;
|
|
185
|
+
},
|
|
186
|
+
(Some(_), None, _) => {
|
|
187
|
+
self.detach_from_body();
|
|
188
|
+
return true;
|
|
189
|
+
},
|
|
190
|
+
_ => {},
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
true
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
197
|
+
let target = &ctx.props().target;
|
|
198
|
+
if target.is_none() {
|
|
199
|
+
return html! {};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
let opacity = if self.visible { "" } else { ";opacity:0" };
|
|
203
|
+
let css = format!(
|
|
204
|
+
":host{{top:{}px;left:{}px{}}}",
|
|
205
|
+
self.top, self.left, opacity
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
let portal_content = html! {
|
|
209
|
+
<>
|
|
210
|
+
<style>{ css }</style>
|
|
211
|
+
<ContextProvider<ModalOrientation> context={self.rev_vert.clone()}>
|
|
212
|
+
<StyleProvider root={self.host.clone()}>
|
|
213
|
+
{ for ctx.props().children.iter() }
|
|
214
|
+
</StyleProvider>
|
|
215
|
+
</ContextProvider<ModalOrientation>>
|
|
216
|
+
</>
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
yew::create_portal(portal_content, self.shadow_root.clone())
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
fn rendered(&mut self, ctx: &Context<Self>, _first_render: bool) {
|
|
223
|
+
if let Some(target) = &ctx.props().target {
|
|
224
|
+
if !self.host.is_connected() {
|
|
225
|
+
let theme = ctx.props().theme.as_str();
|
|
226
|
+
self.host.set_attribute("theme", theme).unwrap();
|
|
227
|
+
|
|
228
|
+
// First render with a target: attach to body, position invisible
|
|
229
|
+
self.position_against_target(target);
|
|
230
|
+
self.attach_to_body();
|
|
231
|
+
|
|
232
|
+
// Propagate theme from target
|
|
233
|
+
if let Some(theme) = target.get_attribute("theme") {
|
|
234
|
+
let _ = self.host.set_attribute("theme", &theme);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
target.class_list().add_1("modal-target").unwrap();
|
|
238
|
+
|
|
239
|
+
if ctx.props().own_focus {
|
|
240
|
+
self.host.set_attribute("tabindex", "0").unwrap();
|
|
241
|
+
self.setup_blur_handler(ctx);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Schedule second positioning pass
|
|
245
|
+
let link = ctx.link().clone();
|
|
246
|
+
wasm_bindgen_futures::spawn_local(async move {
|
|
247
|
+
request_animation_frame().await;
|
|
248
|
+
link.send_message(PortalModalMsg::Reposition);
|
|
249
|
+
});
|
|
250
|
+
} else if self.visible {
|
|
251
|
+
// Second pass: reposition with correct anchor
|
|
252
|
+
self.position_against_target(target);
|
|
253
|
+
|
|
254
|
+
if ctx.props().own_focus && self._blur_closure.is_some() {
|
|
255
|
+
let _ = self.host.focus();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fn destroy(&mut self, ctx: &Context<Self>) {
|
|
262
|
+
if let Some(target) = &ctx.props().target {
|
|
263
|
+
target.class_list().remove_1("modal-target").unwrap();
|
|
264
|
+
if target.get_attribute("theme").is_some() {
|
|
265
|
+
let _ = self.host.remove_attribute("theme");
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
let event = CustomEvent::new("-perspective-close-expression").unwrap();
|
|
269
|
+
let _ = target.dispatch_event(&event);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
self.detach_from_body();
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -13,144 +13,93 @@
|
|
|
13
13
|
use yew::prelude::*;
|
|
14
14
|
|
|
15
15
|
use super::style::LocalStyle;
|
|
16
|
-
use crate::
|
|
17
|
-
use crate::renderer
|
|
18
|
-
use crate::session::*;
|
|
19
|
-
use crate::*;
|
|
16
|
+
use crate::css;
|
|
17
|
+
use crate::renderer::limits::RenderLimits;
|
|
20
18
|
|
|
21
|
-
#[derive(Properties,
|
|
19
|
+
#[derive(Properties, PartialEq)]
|
|
22
20
|
pub struct RenderWarningProps {
|
|
23
|
-
|
|
24
|
-
pub dimensions: Option<(usize, usize, Option<usize>, Option<usize>)>,
|
|
21
|
+
pub dimensions: Option<RenderLimits>,
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
pub
|
|
23
|
+
/// Called when the user clicks "Render all points". The parent disables
|
|
24
|
+
/// the render warning on the active plugin and re-draws.
|
|
25
|
+
pub on_dismiss: Callback<()>,
|
|
29
26
|
}
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
pub enum RenderWarningMsg {
|
|
38
|
-
DismissWarning,
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
pub struct RenderWarning {
|
|
42
|
-
col_warn: Option<(usize, usize)>,
|
|
43
|
-
row_warn: Option<(usize, usize)>,
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
impl RenderWarning {
|
|
47
|
-
fn update_warnings(&mut self, ctx: &Context<Self>) {
|
|
48
|
-
if let Some((num_cols, num_rows, max_cols, max_rows)) = ctx.props().dimensions {
|
|
49
|
-
let count = num_cols * num_rows;
|
|
50
|
-
if max_cols.is_some_and(|x| x < num_cols) {
|
|
51
|
-
self.col_warn = Some((max_cols.unwrap(), num_cols));
|
|
52
|
-
} else {
|
|
53
|
-
self.col_warn = None;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if max_rows.is_some_and(|x| x < num_rows) {
|
|
57
|
-
self.row_warn = Some((num_cols * max_rows.unwrap(), count));
|
|
58
|
-
} else {
|
|
59
|
-
self.row_warn = None;
|
|
60
|
-
}
|
|
28
|
+
#[function_component(RenderWarning)]
|
|
29
|
+
pub fn render_warning(props: &RenderWarningProps) -> Html {
|
|
30
|
+
let dimensions = props.dimensions;
|
|
31
|
+
let (col_warn, row_warn) = if let Some(limits) = dimensions {
|
|
32
|
+
let col_warn = if limits.max_cols.is_some_and(|x| x < limits.num_cols) {
|
|
33
|
+
Some((limits.max_cols.unwrap(), limits.num_cols))
|
|
61
34
|
} else {
|
|
62
|
-
|
|
63
|
-
self.row_warn = None;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
impl Component for RenderWarning {
|
|
69
|
-
type Message = RenderWarningMsg;
|
|
70
|
-
type Properties = RenderWarningProps;
|
|
71
|
-
|
|
72
|
-
fn create(ctx: &Context<Self>) -> Self {
|
|
73
|
-
let mut elem = Self {
|
|
74
|
-
col_warn: None,
|
|
75
|
-
row_warn: None,
|
|
35
|
+
None
|
|
76
36
|
};
|
|
77
37
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
38
|
+
let row_warn = if limits.max_rows.is_some_and(|x| x < limits.num_rows) {
|
|
39
|
+
Some((
|
|
40
|
+
limits.num_cols * limits.max_rows.unwrap(),
|
|
41
|
+
limits.num_cols * limits.num_rows,
|
|
42
|
+
))
|
|
43
|
+
} else {
|
|
44
|
+
None
|
|
45
|
+
};
|
|
81
46
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
47
|
+
(col_warn, row_warn)
|
|
48
|
+
} else {
|
|
49
|
+
(None, None)
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
if col_warn.is_some() || row_warn.is_some() {
|
|
53
|
+
let warning = match (col_warn, row_warn) {
|
|
54
|
+
(Some((x, y)), Some((a, b))) => html! {
|
|
55
|
+
<span style="white-space: nowrap">
|
|
56
|
+
{ "Rendering" }
|
|
57
|
+
{ render_pair(x, y) }
|
|
58
|
+
{ "of columns and" }
|
|
59
|
+
{ render_pair(a, b) }
|
|
60
|
+
{ "of points." }
|
|
61
|
+
</span>
|
|
62
|
+
},
|
|
63
|
+
(Some((x, y)), None) => html! {
|
|
64
|
+
<span style="white-space: nowrap">
|
|
65
|
+
{ "Rendering" }
|
|
66
|
+
{ render_pair(x, y) }
|
|
67
|
+
{ "of columns." }
|
|
68
|
+
</span>
|
|
69
|
+
},
|
|
70
|
+
(None, Some((x, y))) => html! {
|
|
71
|
+
<span style="white-space: nowrap">
|
|
72
|
+
{ "Rendering" }
|
|
73
|
+
{ render_pair(x, y) }
|
|
74
|
+
{ "of points." }
|
|
75
|
+
</span>
|
|
91
76
|
},
|
|
77
|
+
_ => html! { <div /> },
|
|
92
78
|
};
|
|
93
|
-
true
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
fn changed(&mut self, ctx: &Context<Self>, _old: &Self::Properties) -> bool {
|
|
97
|
-
self.update_warnings(ctx);
|
|
98
|
-
true
|
|
99
|
-
}
|
|
100
79
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
80
|
+
let on_dismiss = props.on_dismiss.clone();
|
|
81
|
+
let onclick = Callback::from(move |_: MouseEvent| on_dismiss.emit(()));
|
|
82
|
+
html! {
|
|
83
|
+
<>
|
|
84
|
+
<LocalStyle href={css!("render-warning")} />
|
|
85
|
+
<div
|
|
86
|
+
class="plugin_information plugin_information--warning"
|
|
87
|
+
id="plugin_information--size"
|
|
88
|
+
>
|
|
89
|
+
<span class="plugin_information__icon" />
|
|
90
|
+
<span class="plugin_information__text" id="plugin_information_count">
|
|
91
|
+
{ warning }
|
|
111
92
|
</span>
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
{ "Rendering" }
|
|
116
|
-
{ render_pair(x, y) }
|
|
117
|
-
{ "of columns." }
|
|
118
|
-
</span>
|
|
119
|
-
},
|
|
120
|
-
(None, Some((x, y))) => html! {
|
|
121
|
-
<span style="white-space: nowrap">
|
|
122
|
-
{ "Rendering" }
|
|
123
|
-
{ render_pair(x, y) }
|
|
124
|
-
{ "of points." }
|
|
125
|
-
</span>
|
|
126
|
-
},
|
|
127
|
-
_ => html! { <div /> },
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
let onclick = ctx.link().callback(|_| RenderWarningMsg::DismissWarning);
|
|
131
|
-
|
|
132
|
-
html! {
|
|
133
|
-
<>
|
|
134
|
-
<LocalStyle href={css!("render-warning")} />
|
|
135
|
-
<div
|
|
136
|
-
class="plugin_information plugin_information--warning"
|
|
137
|
-
id="plugin_information--size"
|
|
138
|
-
>
|
|
139
|
-
<span class="plugin_information__icon" />
|
|
140
|
-
<span class="plugin_information__text" id="plugin_information_count">
|
|
141
|
-
{ warning }
|
|
93
|
+
<span class="plugin_information__actions">
|
|
94
|
+
<span class="plugin_information__action" onmousedown={onclick}>
|
|
95
|
+
{ "Render all points" }
|
|
142
96
|
</span>
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
</span>
|
|
147
|
-
</span>
|
|
148
|
-
</div>
|
|
149
|
-
</>
|
|
150
|
-
}
|
|
151
|
-
} else {
|
|
152
|
-
html! {}
|
|
97
|
+
</span>
|
|
98
|
+
</div>
|
|
99
|
+
</>
|
|
153
100
|
}
|
|
101
|
+
} else {
|
|
102
|
+
html! {}
|
|
154
103
|
}
|
|
155
104
|
}
|
|
156
105
|
|
|
@@ -12,20 +12,23 @@
|
|
|
12
12
|
|
|
13
13
|
use std::rc::Rc;
|
|
14
14
|
|
|
15
|
+
use perspective_client::config::{ViewConfig, ViewConfigUpdate};
|
|
16
|
+
use perspective_js::utils::ApiFuture;
|
|
15
17
|
use yew::prelude::*;
|
|
16
18
|
|
|
17
19
|
use super::column_selector::ColumnSelector;
|
|
18
20
|
use super::plugin_selector::PluginSelector;
|
|
19
|
-
use crate::PerspectiveProperties;
|
|
20
21
|
use crate::components::containers::sidebar_close_button::SidebarCloseButton;
|
|
22
|
+
use crate::config::PluginUpdate;
|
|
21
23
|
use crate::dragdrop::*;
|
|
22
|
-
use crate::
|
|
23
|
-
use crate::presentation::{ColumnLocator, Presentation};
|
|
24
|
+
use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
|
|
24
25
|
use crate::renderer::*;
|
|
26
|
+
use crate::session::column_defaults_update::*;
|
|
25
27
|
use crate::session::*;
|
|
28
|
+
use crate::tasks::can_render_column_styles;
|
|
26
29
|
use crate::utils::*;
|
|
27
30
|
|
|
28
|
-
#[derive(Clone, Properties
|
|
31
|
+
#[derive(Clone, Properties)]
|
|
29
32
|
pub struct SettingsPanelProps {
|
|
30
33
|
pub on_close: Callback<()>,
|
|
31
34
|
pub on_resize: Rc<PubSub<()>>,
|
|
@@ -33,6 +36,25 @@ pub struct SettingsPanelProps {
|
|
|
33
36
|
pub on_debug: Callback<()>,
|
|
34
37
|
pub is_debug: bool,
|
|
35
38
|
|
|
39
|
+
/// Value props threaded from the root's `RendererProps` / `SessionProps`.
|
|
40
|
+
pub plugin_name: Option<String>,
|
|
41
|
+
pub available_plugins: PtrEqRc<Vec<String>>,
|
|
42
|
+
pub has_table: Option<TableLoadState>,
|
|
43
|
+
pub named_column_count: usize,
|
|
44
|
+
pub view_config: PtrEqRc<ViewConfig>,
|
|
45
|
+
/// Column currently being dragged (if any) — threaded to show drag
|
|
46
|
+
/// highlights without per-component `DragDrop` PubSub subscriptions.
|
|
47
|
+
pub drag_column: Option<String>,
|
|
48
|
+
/// Cloned session metadata snapshot — threaded from `SessionProps`
|
|
49
|
+
/// so that metadata changes trigger re-renders via prop diffing.
|
|
50
|
+
pub metadata: SessionMetadataRc,
|
|
51
|
+
/// Snapshot of the column-settings sidebar state — threaded from
|
|
52
|
+
/// `PresentationProps` so that open/close triggers re-renders.
|
|
53
|
+
pub open_column_settings: OpenColumnSettings,
|
|
54
|
+
|
|
55
|
+
/// Selected theme name, threaded for PortalModal consumers.
|
|
56
|
+
pub selected_theme: Option<String>,
|
|
57
|
+
|
|
36
58
|
/// State
|
|
37
59
|
pub dragdrop: DragDrop,
|
|
38
60
|
pub session: Session,
|
|
@@ -41,8 +63,17 @@ pub struct SettingsPanelProps {
|
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
impl PartialEq for SettingsPanelProps {
|
|
44
|
-
fn eq(&self,
|
|
45
|
-
|
|
66
|
+
fn eq(&self, rhs: &Self) -> bool {
|
|
67
|
+
self.is_debug == rhs.is_debug
|
|
68
|
+
&& self.plugin_name == rhs.plugin_name
|
|
69
|
+
&& self.available_plugins == rhs.available_plugins
|
|
70
|
+
&& self.has_table == rhs.has_table
|
|
71
|
+
&& self.named_column_count == rhs.named_column_count
|
|
72
|
+
&& self.view_config == rhs.view_config
|
|
73
|
+
&& self.drag_column == rhs.drag_column
|
|
74
|
+
&& self.metadata == rhs.metadata
|
|
75
|
+
&& self.open_column_settings == rhs.open_column_settings
|
|
76
|
+
&& self.selected_theme == rhs.selected_theme
|
|
46
77
|
}
|
|
47
78
|
}
|
|
48
79
|
|
|
@@ -55,7 +86,74 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
55
86
|
session,
|
|
56
87
|
..
|
|
57
88
|
} = &props;
|
|
58
|
-
|
|
89
|
+
|
|
90
|
+
let selected_column = {
|
|
91
|
+
let locator = props.open_column_settings.locator.clone();
|
|
92
|
+
let config = &props.view_config;
|
|
93
|
+
locator.filter(|locator| match locator {
|
|
94
|
+
ColumnLocator::Table(name) => {
|
|
95
|
+
locator
|
|
96
|
+
.name()
|
|
97
|
+
.map(|n| {
|
|
98
|
+
config.columns.iter().any(|maybe_col| {
|
|
99
|
+
maybe_col.as_ref().map(|col| col == n).unwrap_or_default()
|
|
100
|
+
}) || config.group_by.iter().any(|col| col == n)
|
|
101
|
+
|| config.split_by.iter().any(|col| col == n)
|
|
102
|
+
|| config.filter.iter().any(|col| col.column() == n)
|
|
103
|
+
|| config.sort.iter().any(|col| &col.0 == n)
|
|
104
|
+
})
|
|
105
|
+
.unwrap_or_default()
|
|
106
|
+
&& can_render_column_styles(&props.renderer, config, &props.metadata, name)
|
|
107
|
+
.unwrap_or_default()
|
|
108
|
+
},
|
|
109
|
+
_ => true,
|
|
110
|
+
})
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
let plugin_name = props.plugin_name.clone();
|
|
114
|
+
let available_plugins = props.available_plugins.clone();
|
|
115
|
+
|
|
116
|
+
// Dispatch callback: captures engine handles, constructs config update, renders
|
|
117
|
+
let on_select_plugin = {
|
|
118
|
+
clone!(renderer, session, presentation);
|
|
119
|
+
let session_metadata = props.metadata.clone();
|
|
120
|
+
Callback::from(move |plugin_name: String| {
|
|
121
|
+
if !session.is_errored() {
|
|
122
|
+
let metadata =
|
|
123
|
+
renderer.get_next_plugin_metadata(&PluginUpdate::Update(plugin_name));
|
|
124
|
+
|
|
125
|
+
let prev_metadata = renderer.metadata();
|
|
126
|
+
let requirements = metadata.as_ref().unwrap_or(&*prev_metadata);
|
|
127
|
+
let rollup_features = session_metadata
|
|
128
|
+
.get_features()
|
|
129
|
+
.map(|x| x.get_group_rollup_modes())
|
|
130
|
+
.unwrap();
|
|
131
|
+
|
|
132
|
+
let group_rollups = requirements.get_group_rollups(&rollup_features);
|
|
133
|
+
let mut update = ViewConfigUpdate {
|
|
134
|
+
group_rollup_mode: group_rollups.first().cloned(),
|
|
135
|
+
..ViewConfigUpdate::default()
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
update.set_update_column_defaults(
|
|
139
|
+
&session_metadata,
|
|
140
|
+
&session.get_view_config().columns,
|
|
141
|
+
requirements,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if session.update_view_config(update).is_ok() {
|
|
145
|
+
clone!(renderer, session);
|
|
146
|
+
ApiFuture::spawn(async move {
|
|
147
|
+
renderer.apply_pending_plugin()?;
|
|
148
|
+
renderer.draw(session.validate().await?.create_view()).await
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
presentation.set_open_column_settings(None);
|
|
153
|
+
}
|
|
154
|
+
})
|
|
155
|
+
};
|
|
156
|
+
|
|
59
157
|
html! {
|
|
60
158
|
<div id="settings_panel" class="sidebar_column noselect split-panel orient-vertical">
|
|
61
159
|
if selected_column.is_none() {
|
|
@@ -68,14 +166,20 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
|
|
|
68
166
|
id={if props.is_debug {"debug_close_button"} else {"debug_open_button"}}
|
|
69
167
|
on_close_sidebar={&props.on_debug}
|
|
70
168
|
/>
|
|
71
|
-
<PluginSelector {
|
|
169
|
+
<PluginSelector {plugin_name} {available_plugins} {on_select_plugin} />
|
|
72
170
|
<ColumnSelector
|
|
73
171
|
on_resize={&props.on_resize}
|
|
74
172
|
on_open_expr_panel={&props.on_select_column}
|
|
75
|
-
|
|
173
|
+
{selected_column}
|
|
174
|
+
has_table={props.has_table.clone()}
|
|
175
|
+
named_column_count={props.named_column_count}
|
|
176
|
+
view_config={props.view_config.clone()}
|
|
177
|
+
drag_column={props.drag_column.clone()}
|
|
178
|
+
metadata={props.metadata.clone()}
|
|
179
|
+
selected_theme={props.selected_theme.clone()}
|
|
76
180
|
{dragdrop}
|
|
77
|
-
{renderer}
|
|
78
|
-
{session}
|
|
181
|
+
renderer={renderer.clone()}
|
|
182
|
+
session={session.clone()}
|
|
79
183
|
/>
|
|
80
184
|
</div>
|
|
81
185
|
}
|