@perspective-dev/viewer 4.2.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 -0
- 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/extensions.d.ts +32 -1
- package/dist/esm/perspective-viewer.d.ts +1 -0
- 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/esm/ts-rs/GroupRollupMode.d.ts +1 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +2 -0
- package/dist/wasm/perspective-viewer.d.ts +57 -53
- package/dist/wasm/perspective-viewer.js +197 -164
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +17 -18
- package/package.json +9 -6
- 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} +161 -159
- 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/css/config-selector.css +441 -0
- 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 +374 -185
- 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 +21 -10
- package/src/rust/components/column_selector/sort_column.rs +23 -13
- package/src/rust/components/column_selector.rs +189 -100
- 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 +29 -7
- package/src/rust/components/containers/scroll_panel.rs +8 -1
- package/src/rust/components/containers/select.rs +3 -3
- package/src/rust/components/containers/sidebar_close_button.rs +1 -1
- package/src/rust/components/containers/split_panel.rs +3 -2
- 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 -92
- 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 -111
- 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 +20 -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/registry.rs +8 -1
- package/src/rust/renderer.rs +83 -9
- package/src/rust/session/column_defaults_update.rs +18 -0
- package/src/rust/session/metadata.rs +23 -2
- package/src/rust/session/props.rs +178 -0
- package/src/rust/session/replace_expression_update.rs +1 -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 +22 -4
- 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 -20
- 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/src/ts/extensions.ts +73 -2
- package/src/ts/perspective-viewer.ts +1 -0
- package/src/ts/ts-rs/GroupRollupMode.ts +3 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -1
- package/tsconfig.json +1 -0
- package/dist/css/variables.css +0 -0
- package/src/less/column-dropdown.less +0 -95
- package/src/less/config-selector.less +0 -363
- 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/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-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → perspective-viewer-68fef752754ffbc6}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-1586156e058be573 → 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
|
@@ -10,153 +10,263 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
use std::cell::RefCell;
|
|
14
|
+
use std::collections::HashSet;
|
|
15
|
+
use std::rc::Rc;
|
|
16
|
+
|
|
17
|
+
use perspective_client::clone;
|
|
18
|
+
use perspective_client::config::Expression;
|
|
13
19
|
use web_sys::*;
|
|
20
|
+
use yew::html::ImplicitClone;
|
|
14
21
|
use yew::prelude::*;
|
|
15
22
|
|
|
16
23
|
use super::column_selector::InPlaceColumn;
|
|
17
|
-
use super::
|
|
18
|
-
use crate::
|
|
24
|
+
use super::portal::PortalModal;
|
|
25
|
+
use crate::session::Session;
|
|
26
|
+
use crate::utils::*;
|
|
27
|
+
use crate::*;
|
|
19
28
|
|
|
20
29
|
static CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/css/column-dropdown.css"));
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
pub
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
31
|
+
/// Shared state for the column dropdown, updated imperatively.
|
|
32
|
+
#[derive(Default)]
|
|
33
|
+
pub struct ColumnDropDownState {
|
|
34
|
+
pub values: Vec<InPlaceColumn>,
|
|
35
|
+
pub selected: usize,
|
|
36
|
+
pub width: f64,
|
|
37
|
+
pub on_select: Option<Callback<InPlaceColumn>>,
|
|
38
|
+
pub target: Option<HtmlElement>,
|
|
39
|
+
pub no_results: bool,
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
/// A clonable handle for the column dropdown shared state.
|
|
43
|
+
#[derive(Clone)]
|
|
44
|
+
pub struct ColumnDropDownElement {
|
|
45
|
+
state: Rc<RefCell<ColumnDropDownState>>,
|
|
46
|
+
session: Session,
|
|
47
|
+
notify: Rc<PubSub<()>>,
|
|
40
48
|
}
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
on_select: Option<Callback<InPlaceColumn>>,
|
|
50
|
+
impl PartialEq for ColumnDropDownElement {
|
|
51
|
+
fn eq(&self, other: &Self) -> bool {
|
|
52
|
+
Rc::ptr_eq(&self.state, &other.state)
|
|
53
|
+
}
|
|
47
54
|
}
|
|
48
55
|
|
|
49
|
-
impl
|
|
50
|
-
type Message = ColumnDropDownMsg;
|
|
51
|
-
type Properties = ColumnDropDownProps;
|
|
56
|
+
impl ImplicitClone for ColumnDropDownElement {}
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
58
|
+
impl ColumnDropDownElement {
|
|
59
|
+
pub fn new(session: Session) -> Self {
|
|
55
60
|
Self {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
on_select: None,
|
|
61
|
+
state: Default::default(),
|
|
62
|
+
session,
|
|
63
|
+
notify: Rc::default(),
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
fn
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
false
|
|
85
|
-
},
|
|
86
|
-
}
|
|
87
|
-
} else {
|
|
88
|
-
console::error_1(&"No Values".into());
|
|
89
|
-
false
|
|
90
|
-
}
|
|
91
|
-
},
|
|
92
|
-
ColumnDropDownMsg::ItemDown => {
|
|
93
|
-
self.selected += 1;
|
|
94
|
-
if let Some(ref values) = self.values
|
|
95
|
-
&& self.selected >= values.len()
|
|
96
|
-
{
|
|
97
|
-
self.selected = 0;
|
|
98
|
-
}
|
|
67
|
+
pub fn autocomplete(
|
|
68
|
+
&self,
|
|
69
|
+
target: HtmlInputElement,
|
|
70
|
+
exclude: HashSet<String>,
|
|
71
|
+
callback: Callback<InPlaceColumn>,
|
|
72
|
+
) -> Option<()> {
|
|
73
|
+
let input = target.value();
|
|
74
|
+
let metadata = self.session.metadata();
|
|
75
|
+
let mut values: Vec<InPlaceColumn> = vec![];
|
|
76
|
+
let small_input = input.to_lowercase();
|
|
77
|
+
for col in metadata.get_table_columns()? {
|
|
78
|
+
if !exclude.contains(col) && col.to_lowercase().contains(&small_input) {
|
|
79
|
+
values.push(InPlaceColumn::Column(col.to_owned()));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for col in self.session.metadata().get_expression_columns() {
|
|
84
|
+
if !exclude.contains(col) && col.to_lowercase().contains(&small_input) {
|
|
85
|
+
values.push(InPlaceColumn::Column(col.to_owned()));
|
|
86
|
+
}
|
|
87
|
+
}
|
|
99
88
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
89
|
+
clone!(self.state, self.session, self.notify);
|
|
90
|
+
let target_elem: HtmlElement = target.clone().into();
|
|
91
|
+
let width = target.get_bounding_client_rect().width();
|
|
92
|
+
ApiFuture::spawn(async move {
|
|
93
|
+
if !exclude.contains(&input) {
|
|
94
|
+
let is_expr = session.validate_expr(&input).await?.is_none();
|
|
95
|
+
if is_expr {
|
|
96
|
+
values.push(InPlaceColumn::Expression(Expression::new(
|
|
97
|
+
None,
|
|
98
|
+
input.into(),
|
|
99
|
+
)));
|
|
107
100
|
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let no_results = values.is_empty();
|
|
104
|
+
{
|
|
105
|
+
let mut s = state.borrow_mut();
|
|
106
|
+
s.values = values;
|
|
107
|
+
s.selected = 0;
|
|
108
|
+
s.width = width;
|
|
109
|
+
s.on_select = Some(callback);
|
|
110
|
+
s.target = Some(target_elem);
|
|
111
|
+
s.no_results = no_results;
|
|
112
|
+
}
|
|
113
|
+
notify.emit(());
|
|
114
|
+
Ok(())
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
Some(())
|
|
118
|
+
}
|
|
108
119
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
120
|
+
pub fn item_select(&self) {
|
|
121
|
+
let state = self.state.borrow();
|
|
122
|
+
if let Some(value) = state.values.get(state.selected)
|
|
123
|
+
&& let Some(ref cb) = state.on_select
|
|
124
|
+
{
|
|
125
|
+
cb.emit(value.clone());
|
|
112
126
|
}
|
|
113
127
|
}
|
|
114
128
|
|
|
115
|
-
fn
|
|
116
|
-
|
|
129
|
+
pub fn item_down(&self) {
|
|
130
|
+
let mut state = self.state.borrow_mut();
|
|
131
|
+
state.selected += 1;
|
|
132
|
+
if state.selected >= state.values.len() {
|
|
133
|
+
state.selected = 0;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
drop(state);
|
|
137
|
+
self.notify.emit(());
|
|
117
138
|
}
|
|
118
139
|
|
|
119
|
-
fn
|
|
120
|
-
let
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
140
|
+
pub fn item_up(&self) {
|
|
141
|
+
let mut state = self.state.borrow_mut();
|
|
142
|
+
if state.selected < 1 {
|
|
143
|
+
state.selected = state.values.len();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
state.selected -= 1;
|
|
147
|
+
drop(state);
|
|
148
|
+
self.notify.emit(());
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
pub fn hide(&self) -> ApiResult<()> {
|
|
152
|
+
self.state.borrow_mut().target = None;
|
|
153
|
+
self.notify.emit(());
|
|
154
|
+
Ok(())
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// A portal component that renders the column dropdown. Should be placed in
|
|
159
|
+
/// the view of the component that creates the `ColumnDropDownElement`.
|
|
160
|
+
#[derive(Properties, PartialEq)]
|
|
161
|
+
pub struct ColumnDropDownPortalProps {
|
|
162
|
+
pub element: ColumnDropDownElement,
|
|
163
|
+
pub theme: String,
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
pub struct ColumnDropDownPortal {
|
|
167
|
+
_sub: Subscription,
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
impl Component for ColumnDropDownPortal {
|
|
171
|
+
type Message = ();
|
|
172
|
+
type Properties = ColumnDropDownPortalProps;
|
|
173
|
+
|
|
174
|
+
fn create(ctx: &Context<Self>) -> Self {
|
|
175
|
+
let link = ctx.link().clone();
|
|
176
|
+
let sub = ctx
|
|
177
|
+
.props()
|
|
178
|
+
.element
|
|
179
|
+
.notify
|
|
180
|
+
.add_listener(move |()| link.send_message(()));
|
|
181
|
+
Self { _sub: sub }
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fn update(&mut self, _ctx: &Context<Self>, _msg: ()) -> bool {
|
|
185
|
+
true
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
189
|
+
let state = ctx.props().element.state.borrow();
|
|
190
|
+
let target = state.target.clone();
|
|
191
|
+
let on_close = {
|
|
192
|
+
let element = ctx.props().element.clone();
|
|
193
|
+
Callback::from(move |()| {
|
|
194
|
+
let _ = element.hide();
|
|
195
|
+
})
|
|
153
196
|
};
|
|
154
197
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
198
|
+
if target.is_some() {
|
|
199
|
+
let values = state.values.clone();
|
|
200
|
+
let selected = state.selected;
|
|
201
|
+
let width = state.width;
|
|
202
|
+
let on_select = state.on_select.clone();
|
|
203
|
+
drop(state);
|
|
159
204
|
|
|
160
|
-
|
|
205
|
+
html! {
|
|
206
|
+
<PortalModal
|
|
207
|
+
tag_name="perspective-dropdown"
|
|
208
|
+
{target}
|
|
209
|
+
own_focus=false
|
|
210
|
+
{on_close}
|
|
211
|
+
theme={ctx.props().theme.clone()}
|
|
212
|
+
>
|
|
213
|
+
<ColumnDropDownView {values} {selected} {width} {on_select} />
|
|
214
|
+
</PortalModal>
|
|
215
|
+
}
|
|
216
|
+
} else {
|
|
217
|
+
html! {}
|
|
218
|
+
}
|
|
161
219
|
}
|
|
162
220
|
}
|
|
221
|
+
|
|
222
|
+
/// Pure view component for the column dropdown content.
|
|
223
|
+
#[derive(Properties, PartialEq)]
|
|
224
|
+
struct ColumnDropDownViewProps {
|
|
225
|
+
values: Vec<InPlaceColumn>,
|
|
226
|
+
selected: usize,
|
|
227
|
+
width: f64,
|
|
228
|
+
on_select: Option<Callback<InPlaceColumn>>,
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
#[function_component]
|
|
232
|
+
fn ColumnDropDownView(props: &ColumnDropDownViewProps) -> Html {
|
|
233
|
+
let body = html! {
|
|
234
|
+
if !props.values.is_empty() {
|
|
235
|
+
{ for props.values
|
|
236
|
+
.iter()
|
|
237
|
+
.enumerate()
|
|
238
|
+
.map(|(idx, value)| {
|
|
239
|
+
let click = props.on_select.as_ref().unwrap().reform({
|
|
240
|
+
let value = value.clone();
|
|
241
|
+
move |_: MouseEvent| value.clone()
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
let row = match value {
|
|
245
|
+
InPlaceColumn::Column(col) => html! {
|
|
246
|
+
<span>{ col }</span>
|
|
247
|
+
},
|
|
248
|
+
InPlaceColumn::Expression(col) => html! {
|
|
249
|
+
<span id="add-expression"><span class="icon" />{ col.name.clone() }</span>
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
html! {
|
|
254
|
+
if idx == props.selected {
|
|
255
|
+
<span onmousedown={click} class="selected">{ row }</span>
|
|
256
|
+
} else {
|
|
257
|
+
<span onmousedown={click}>{ row }</span>
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}) }
|
|
261
|
+
} else {
|
|
262
|
+
<span class="no-results" />
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
let position = format!(
|
|
267
|
+
":host{{min-width:{}px;max-width:{}px}}",
|
|
268
|
+
props.width, props.width
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
html! { <><style>{ CSS }</style><style>{ position }</style>{ body }</> }
|
|
272
|
+
}
|
|
@@ -13,7 +13,6 @@
|
|
|
13
13
|
use std::collections::HashSet;
|
|
14
14
|
|
|
15
15
|
use perspective_client::config::*;
|
|
16
|
-
use perspective_client::utils::PerspectiveResultExt;
|
|
17
16
|
use perspective_js::utils::ApiFuture;
|
|
18
17
|
use web_sys::*;
|
|
19
18
|
use yew::prelude::*;
|
|
@@ -21,19 +20,18 @@ use yew::prelude::*;
|
|
|
21
20
|
use super::InPlaceColumn;
|
|
22
21
|
use super::aggregate_selector::*;
|
|
23
22
|
use super::expr_edit_button::*;
|
|
24
|
-
use crate::
|
|
23
|
+
use crate::components::column_dropdown::ColumnDropDownElement;
|
|
25
24
|
use crate::components::column_selector::{EmptyColumn, InvalidColumn};
|
|
26
25
|
use crate::components::type_icon::TypeIcon;
|
|
27
|
-
use crate::custom_elements::ColumnDropDownElement;
|
|
28
26
|
use crate::dragdrop::*;
|
|
29
27
|
use crate::js::plugin::*;
|
|
30
|
-
use crate::model::*;
|
|
31
28
|
use crate::presentation::ColumnLocator;
|
|
32
29
|
use crate::renderer::*;
|
|
33
30
|
use crate::session::*;
|
|
31
|
+
use crate::tasks::*;
|
|
34
32
|
use crate::utils::*;
|
|
35
33
|
|
|
36
|
-
#[derive(Clone, Properties
|
|
34
|
+
#[derive(Clone, Properties)]
|
|
37
35
|
pub struct ActiveColumnProps {
|
|
38
36
|
/// The column's index in the list.
|
|
39
37
|
pub idx: usize,
|
|
@@ -64,6 +62,27 @@ pub struct ActiveColumnProps {
|
|
|
64
62
|
/// Is this column's expression/config side panel open?
|
|
65
63
|
pub is_editing: bool,
|
|
66
64
|
|
|
65
|
+
/// Whether this column is an expression column. Computed by the parent
|
|
66
|
+
/// so that changes to session metadata trigger a re-render via prop diff.
|
|
67
|
+
#[prop_or_default]
|
|
68
|
+
pub is_expression: bool,
|
|
69
|
+
|
|
70
|
+
/// Whether the expression/config edit button should be shown. Computed
|
|
71
|
+
/// by the parent (`is_expression || can_render_column_styles`).
|
|
72
|
+
#[prop_or_default]
|
|
73
|
+
pub show_edit_btn: bool,
|
|
74
|
+
|
|
75
|
+
/// The resolved table column type, if available. Computed by the parent
|
|
76
|
+
/// from session metadata so that metadata updates trigger re-renders.
|
|
77
|
+
#[prop_or_default]
|
|
78
|
+
pub col_type: Option<ColumnType>,
|
|
79
|
+
|
|
80
|
+
/// Session metadata snapshot — threaded from `SessionProps`.
|
|
81
|
+
pub metadata: SessionMetadataRc,
|
|
82
|
+
|
|
83
|
+
/// View config snapshot — threaded from parent as a value prop.
|
|
84
|
+
pub view_config: PtrEqRc<ViewConfig>,
|
|
85
|
+
|
|
67
86
|
/// State
|
|
68
87
|
pub session: Session,
|
|
69
88
|
pub dragdrop: DragDrop,
|
|
@@ -71,8 +90,16 @@ pub struct ActiveColumnProps {
|
|
|
71
90
|
}
|
|
72
91
|
|
|
73
92
|
impl PartialEq for ActiveColumnProps {
|
|
74
|
-
fn eq(&self,
|
|
75
|
-
|
|
93
|
+
fn eq(&self, rhs: &Self) -> bool {
|
|
94
|
+
self.idx == rhs.idx
|
|
95
|
+
&& self.name == rhs.name
|
|
96
|
+
&& self.is_aggregated == rhs.is_aggregated
|
|
97
|
+
&& self.is_editing == rhs.is_editing
|
|
98
|
+
&& self.is_expression == rhs.is_expression
|
|
99
|
+
&& self.show_edit_btn == rhs.show_edit_btn
|
|
100
|
+
&& self.col_type == rhs.col_type
|
|
101
|
+
&& self.metadata == rhs.metadata
|
|
102
|
+
&& self.view_config == rhs.view_config
|
|
76
103
|
}
|
|
77
104
|
}
|
|
78
105
|
|
|
@@ -121,7 +148,7 @@ impl Component for ActiveColumn {
|
|
|
121
148
|
is_render
|
|
122
149
|
},
|
|
123
150
|
New(InPlaceColumn::Column(col)) => {
|
|
124
|
-
let mut view_config = ctx.props().
|
|
151
|
+
let mut view_config = (*ctx.props().view_config).clone();
|
|
125
152
|
if ctx.props().idx >= view_config.columns.len() {
|
|
126
153
|
view_config.columns.push(Some(col));
|
|
127
154
|
} else {
|
|
@@ -133,15 +160,21 @@ impl Component for ActiveColumn {
|
|
|
133
160
|
..ViewConfigUpdate::default()
|
|
134
161
|
};
|
|
135
162
|
|
|
136
|
-
|
|
137
|
-
.
|
|
138
|
-
.
|
|
139
|
-
.
|
|
163
|
+
{
|
|
164
|
+
let session = ctx.props().session.clone();
|
|
165
|
+
let renderer = ctx.props().renderer.clone();
|
|
166
|
+
if session.update_view_config(update).is_ok() {
|
|
167
|
+
ApiFuture::spawn(async move {
|
|
168
|
+
renderer.apply_pending_plugin()?;
|
|
169
|
+
renderer.draw(session.validate().await?.create_view()).await
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
140
173
|
|
|
141
174
|
true
|
|
142
175
|
},
|
|
143
176
|
New(InPlaceColumn::Expression(col)) => {
|
|
144
|
-
let mut view_config = ctx.props().
|
|
177
|
+
let mut view_config = (*ctx.props().view_config).clone();
|
|
145
178
|
if ctx.props().idx >= view_config.columns.len() {
|
|
146
179
|
view_config.columns.push(Some(col.name.as_ref().to_owned()));
|
|
147
180
|
} else {
|
|
@@ -155,10 +188,16 @@ impl Component for ActiveColumn {
|
|
|
155
188
|
..ViewConfigUpdate::default()
|
|
156
189
|
};
|
|
157
190
|
|
|
158
|
-
|
|
159
|
-
.
|
|
160
|
-
.
|
|
161
|
-
.
|
|
191
|
+
{
|
|
192
|
+
let session = ctx.props().session.clone();
|
|
193
|
+
let renderer = ctx.props().renderer.clone();
|
|
194
|
+
if session.update_view_config(update).is_ok() {
|
|
195
|
+
ApiFuture::spawn(async move {
|
|
196
|
+
renderer.apply_pending_plugin()?;
|
|
197
|
+
renderer.draw(session.validate().await?.create_view()).await
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
162
201
|
|
|
163
202
|
true
|
|
164
203
|
},
|
|
@@ -234,7 +273,7 @@ impl Component for ActiveColumn {
|
|
|
234
273
|
})
|
|
235
274
|
.collect();
|
|
236
275
|
|
|
237
|
-
let col_type = ctx.props().
|
|
276
|
+
let col_type = ctx.props().col_type;
|
|
238
277
|
match (name, col_type) {
|
|
239
278
|
((label, ColumnState::Empty), _) => {
|
|
240
279
|
classes.push("empty-named");
|
|
@@ -242,8 +281,7 @@ impl Component for ActiveColumn {
|
|
|
242
281
|
let on_select = ctx.link().callback(ActiveColumnMsg::New);
|
|
243
282
|
let exclude = ctx
|
|
244
283
|
.props()
|
|
245
|
-
.
|
|
246
|
-
.get_view_config()
|
|
284
|
+
.view_config
|
|
247
285
|
.columns
|
|
248
286
|
.iter()
|
|
249
287
|
.flatten()
|
|
@@ -292,7 +330,7 @@ impl Component for ActiveColumn {
|
|
|
292
330
|
}))
|
|
293
331
|
};
|
|
294
332
|
|
|
295
|
-
let ondragend = &ctx.props().ondragend.reform(|_|
|
|
333
|
+
let ondragend = &ctx.props().ondragend.reform(|_| ());
|
|
296
334
|
let ondragstart = ctx.link().callback({
|
|
297
335
|
let event_name = name.to_owned();
|
|
298
336
|
let dragdrop = ctx.props().dragdrop.clone();
|
|
@@ -312,17 +350,12 @@ impl Component for ActiveColumn {
|
|
|
312
350
|
.link()
|
|
313
351
|
.callback(|event: MouseEvent| MouseEnter(event.which() == 0));
|
|
314
352
|
|
|
315
|
-
let is_expression = ctx.props().
|
|
353
|
+
let is_expression = ctx.props().is_expression;
|
|
354
|
+
let show_edit_btn = ctx.props().show_edit_btn;
|
|
316
355
|
let mut class = ctx.props().renderer.metadata().mode.css();
|
|
317
356
|
if is_required {
|
|
318
357
|
class.push("required");
|
|
319
358
|
};
|
|
320
|
-
|
|
321
|
-
let can_render_styles = ctx
|
|
322
|
-
.props()
|
|
323
|
-
.can_render_column_styles(&name)
|
|
324
|
-
.unwrap_or_default();
|
|
325
|
-
let show_edit_btn = is_expression || can_render_styles;
|
|
326
359
|
html! {
|
|
327
360
|
<div
|
|
328
361
|
class={outer_classes}
|
|
@@ -342,11 +375,14 @@ impl Component for ActiveColumn {
|
|
|
342
375
|
{ondragend}
|
|
343
376
|
>
|
|
344
377
|
<div class="column-selector-column-border">
|
|
378
|
+
<span class="drag-handle icon" />
|
|
345
379
|
<TypeIcon ty={col_type} />
|
|
346
380
|
if ctx.props().is_aggregated {
|
|
347
381
|
<AggregateSelector
|
|
348
382
|
column={name.clone()}
|
|
349
383
|
aggregate={ctx.props().get_aggregate(&name)}
|
|
384
|
+
view_config={ctx.props().view_config.clone()}
|
|
385
|
+
metadata={ctx.props().metadata.clone()}
|
|
350
386
|
renderer={&ctx.props().renderer}
|
|
351
387
|
session={&ctx.props().session}
|
|
352
388
|
/>
|
|
@@ -359,14 +395,13 @@ impl Component for ActiveColumn {
|
|
|
359
395
|
if !ctx.props().is_aggregated {
|
|
360
396
|
<span class="column-selector--spacer" />
|
|
361
397
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
}
|
|
398
|
+
<ExprEditButton
|
|
399
|
+
name={name.clone()}
|
|
400
|
+
on_open_expr_panel={&ctx.props().on_open_expr_panel}
|
|
401
|
+
{is_expression}
|
|
402
|
+
is_disabled={!show_edit_btn}
|
|
403
|
+
is_editing={ctx.props().is_editing}
|
|
404
|
+
/>
|
|
370
405
|
</div>
|
|
371
406
|
</div>
|
|
372
407
|
</div>
|
|
@@ -390,27 +425,6 @@ impl Component for ActiveColumn {
|
|
|
390
425
|
}
|
|
391
426
|
|
|
392
427
|
impl ActiveColumnProps {
|
|
393
|
-
fn get_name(&self, defn: &ActiveColumnState) -> Option<String> {
|
|
394
|
-
match &defn.state {
|
|
395
|
-
ActiveColumnStateData::DragOver => Some(self.dragdrop.get_drag_column().unwrap()),
|
|
396
|
-
ActiveColumnStateData::Column(name) => Some(name.to_owned()),
|
|
397
|
-
ActiveColumnStateData::Required => None,
|
|
398
|
-
ActiveColumnStateData::Invalid => None,
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
fn get_table_type(&self, defn: &ActiveColumnState) -> Option<ColumnType> {
|
|
403
|
-
self.get_name(defn)
|
|
404
|
-
.as_ref()
|
|
405
|
-
.and_then(|x| self.session.metadata().get_column_table_type(x))
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
fn _get_view_type(&self, defn: &ActiveColumnState) -> Option<ColumnType> {
|
|
409
|
-
self.get_name(defn)
|
|
410
|
-
.as_ref()
|
|
411
|
-
.and_then(|x| self.session.metadata().get_column_view_type(x))
|
|
412
|
-
}
|
|
413
|
-
|
|
414
428
|
/// Remove an active column from `columns`, or alternatively make this
|
|
415
429
|
/// column the only column in `columns` if the shift key is set (via the
|
|
416
430
|
/// `shift` flag).
|
|
@@ -420,7 +434,7 @@ impl ActiveColumnProps {
|
|
|
420
434
|
/// with respect to `columns`.
|
|
421
435
|
/// - `shift` whether to toggle or select this column.
|
|
422
436
|
fn deactivate_column(&self, name: String, shift: bool) {
|
|
423
|
-
let mut columns = self.
|
|
437
|
+
let mut columns = self.view_config.columns.clone();
|
|
424
438
|
let max_cols = self
|
|
425
439
|
.renderer
|
|
426
440
|
.metadata()
|
|
@@ -457,7 +471,7 @@ impl ActiveColumnProps {
|
|
|
457
471
|
}
|
|
458
472
|
|
|
459
473
|
fn get_aggregate(&self, name: &str) -> Option<Aggregate> {
|
|
460
|
-
self.
|
|
474
|
+
self.view_config.aggregates.get(name).cloned()
|
|
461
475
|
}
|
|
462
476
|
|
|
463
477
|
fn apply_columns(&self, columns: Vec<Option<String>>) {
|
|
@@ -466,8 +480,13 @@ impl ActiveColumnProps {
|
|
|
466
480
|
..ViewConfigUpdate::default()
|
|
467
481
|
};
|
|
468
482
|
|
|
469
|
-
self.
|
|
470
|
-
.
|
|
471
|
-
.
|
|
483
|
+
if self.session.update_view_config(config).is_ok() {
|
|
484
|
+
let session = self.session.clone();
|
|
485
|
+
let renderer = self.renderer.clone();
|
|
486
|
+
ApiFuture::spawn(async move {
|
|
487
|
+
renderer.apply_pending_plugin()?;
|
|
488
|
+
renderer.draw(session.validate().await?.create_view()).await
|
|
489
|
+
});
|
|
490
|
+
}
|
|
472
491
|
}
|
|
473
492
|
}
|