@perspective-dev/viewer 4.4.0 → 4.5.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 +1 -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/phosphor.css +1 -0
- 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/bootstrap.d.ts +2 -1
- package/dist/esm/column-format.d.ts +51 -0
- package/dist/esm/extensions.d.ts +6 -0
- package/dist/esm/perspective-viewer.d.ts +4 -1
- package/dist/esm/perspective-viewer.inline.js +1 -2
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +1 -2
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/perspective-viewer.worker.d.ts +2 -0
- package/dist/esm/plugin.d.ts +21 -77
- package/dist/esm/ts-rs/ColumnSelectMode.d.ts +1 -0
- package/dist/esm/ts-rs/PluginStaticConfig.d.ts +77 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +39 -0
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
- package/dist/wasm/perspective-viewer.d.ts +88 -24
- package/dist/wasm/perspective-viewer.js +320 -151
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +22 -17
- package/package.json +24 -2
- package/src/css/column-selector.css +3 -2
- package/src/css/column-settings-panel.css +36 -6
- package/src/css/column-style.css +27 -2
- package/src/css/containers/scroll-panel.css +2 -1
- package/src/css/containers/tabs.css +8 -52
- package/src/css/dom/checkbox.css +0 -4
- package/src/css/form/code-editor.css +1 -0
- package/src/css/form/debug.css +3 -10
- package/src/css/plugin-selector.css +33 -0
- package/src/css/plugin-settings-panel.css +99 -0
- package/src/css/status-bar.css +1 -1
- package/src/css/viewer.css +65 -3
- package/src/rust/components/column_dropdown.rs +3 -1
- package/src/rust/components/column_selector/active_column.rs +13 -19
- package/src/rust/components/column_selector/config_selector.rs +20 -20
- package/src/rust/components/column_selector/filter_column.rs +14 -14
- package/src/rust/components/column_selector/inactive_column.rs +9 -15
- package/src/rust/components/column_selector/pivot_column.rs +7 -7
- package/src/rust/components/column_selector/sort_column.rs +7 -7
- package/src/rust/components/column_selector.rs +55 -37
- package/src/rust/components/column_settings_sidebar/style_tab/agg_depth_selector.rs +15 -7
- package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +394 -0
- package/src/rust/components/column_settings_sidebar/style_tab/symbol.rs +15 -6
- package/src/rust/components/column_settings_sidebar/style_tab.rs +267 -136
- package/src/rust/components/column_settings_sidebar.rs +43 -49
- package/src/rust/components/containers/dragdrop_list.rs +5 -5
- package/src/rust/components/containers/mod.rs +0 -1
- package/src/rust/components/containers/scroll_panel.rs +21 -7
- package/src/rust/components/containers/sidebar.rs +8 -6
- package/src/rust/components/containers/split_panel.rs +3 -3
- package/src/rust/components/containers/tab_list.rs +3 -9
- package/src/rust/components/copy_dropdown.rs +2 -3
- package/src/rust/components/datetime_column_style.rs +19 -81
- package/src/rust/components/editable_header.rs +2 -3
- package/src/rust/components/export_dropdown.rs +2 -3
- package/src/rust/components/expression_editor.rs +29 -17
- package/src/rust/components/filter_dropdown.rs +2 -1
- package/src/rust/components/form/color_range_selector.rs +14 -7
- package/src/rust/components/form/debug.rs +47 -37
- package/src/rust/components/main_panel.rs +24 -65
- package/src/rust/components/mod.rs +2 -1
- package/src/rust/components/number_series_style.rs +161 -0
- package/src/rust/components/plugin_tab.rs +221 -0
- package/src/rust/components/settings_panel.rs +181 -59
- package/src/rust/components/status_bar.rs +140 -173
- package/src/rust/components/status_indicator.rs +15 -22
- package/src/rust/components/string_column_style.rs +20 -82
- package/src/rust/components/style_controls/number_string_format.rs +14 -30
- package/src/rust/components/viewer.rs +92 -132
- package/src/rust/config/column_config_schema.rs +195 -0
- package/src/rust/config/columns_config.rs +4 -97
- package/src/rust/config/datetime_column_style.rs +0 -5
- package/src/rust/config/mod.rs +8 -2
- package/src/rust/config/number_series_style.rs +79 -0
- package/src/rust/config/plugin_static_config.rs +144 -0
- package/src/rust/config/string_column_style.rs +0 -5
- package/src/rust/config/viewer_config.rs +7 -8
- package/src/rust/custom_elements/copy_dropdown.rs +30 -18
- package/src/rust/custom_elements/debug_plugin.rs +5 -7
- package/src/rust/custom_elements/export_dropdown.rs +26 -18
- package/src/rust/custom_elements/viewer.rs +77 -77
- package/src/rust/custom_events.rs +181 -224
- package/src/rust/js/plugin.rs +45 -117
- package/src/rust/lib.rs +39 -5
- package/src/rust/presentation/drag_helpers.rs +206 -0
- package/src/rust/presentation/props.rs +8 -0
- package/src/rust/presentation.rs +256 -41
- package/src/rust/{tasks → queries}/column_locator.rs +17 -73
- package/src/rust/queries/column_values.rs +59 -0
- package/src/rust/{tasks → queries}/columns_iter_set.rs +11 -18
- package/src/rust/queries/exports.rs +96 -0
- package/src/rust/queries/fetch_column_stats.rs +94 -0
- package/src/rust/queries/get_viewer_config.rs +54 -0
- package/src/rust/queries/mod.rs +44 -0
- package/src/rust/queries/plugin_column_styles.rs +101 -0
- package/src/rust/{engines.rs → queries/validate_expression.rs} +26 -15
- package/src/rust/renderer/activate.rs +1 -0
- package/src/rust/renderer/limits.rs +9 -4
- package/src/rust/renderer/plugin_store.rs +12 -0
- package/src/rust/renderer/props.rs +28 -3
- package/src/rust/renderer/registry.rs +40 -15
- package/src/rust/renderer.rs +649 -55
- package/src/rust/session/column_defaults_update.rs +20 -28
- package/src/rust/session/drag_drop_update.rs +10 -10
- package/src/rust/session/metadata.rs +31 -16
- package/src/rust/session/props.rs +15 -6
- package/src/rust/session/view_subscription.rs +10 -0
- package/src/rust/session.rs +109 -147
- package/src/rust/tasks/copy_export.rs +178 -158
- package/src/rust/tasks/{structural.rs → dismiss_render_warning.rs} +20 -40
- package/src/rust/tasks/edit_expression.rs +68 -88
- package/src/rust/tasks/eject.rs +25 -22
- package/src/rust/tasks/intersection_observer.rs +8 -21
- package/src/rust/tasks/mod.rs +19 -21
- package/src/rust/tasks/reset_all.rs +78 -0
- package/src/rust/tasks/resize_observer.rs +11 -33
- package/src/rust/tasks/restore_and_render.rs +117 -89
- package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +38 -35
- package/src/rust/tasks/send_plugin_config.rs +32 -33
- package/src/rust/tasks/update_and_render.rs +66 -47
- package/src/rust/{components/containers/trap_door_panel.rs → tasks/update_theme.rs} +34 -33
- package/src/rust/tasks/validate_expression.rs +61 -0
- package/src/rust/utils/browser/selection.rs +4 -4
- package/src/rust/utils/mod.rs +0 -63
- package/src/svg/datagrid-select-row-tree.svg +13 -0
- package/src/svg/mega-menu-icons-density.svg +23 -0
- package/src/svg/mega-menu-icons-map-density.svg +24 -0
- package/src/svg/mega-menu-icons-map-line.svg +19 -0
- package/src/themes/botanical.css +27 -53
- package/src/themes/defaults.css +42 -36
- package/src/themes/dracula.css +36 -54
- package/src/themes/gruvbox-dark.css +39 -59
- package/src/themes/gruvbox.css +16 -28
- package/src/themes/icons.css +4 -18
- package/src/themes/intl/de.css +42 -6
- package/src/themes/intl/es.css +42 -6
- package/src/themes/intl/fr.css +42 -6
- package/src/themes/intl/ja.css +42 -6
- package/src/themes/intl/pt.css +42 -6
- package/src/themes/intl/zh.css +42 -6
- package/src/themes/intl.css +37 -4
- package/src/themes/monokai.css +45 -61
- package/src/themes/phosphor.css +175 -0
- package/src/themes/pro-dark.css +25 -34
- package/src/themes/solarized-dark.css +21 -36
- package/src/themes/solarized.css +13 -23
- package/src/themes/themes.css +1 -0
- package/src/themes/vaporwave.css +40 -74
- package/src/ts/bootstrap.ts +14 -3
- package/src/ts/column-format.ts +162 -0
- package/src/ts/extensions.ts +12 -1
- package/src/ts/perspective-viewer.ts +10 -1
- package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
- package/src/ts/plugin.ts +29 -105
- package/src/ts/ts-rs/{FormatUnit.ts → ColumnSelectMode.ts} +1 -1
- package/src/ts/ts-rs/PluginStaticConfig.ts +78 -0
- package/src/ts/ts-rs/ViewerConfig.ts +14 -0
- package/src/ts/ts-rs/ViewerConfigUpdate.ts +2 -3
- package/dist/esm/ts-rs/ColumnConfigValues.d.ts +0 -31
- package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +0 -1
- package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +0 -15
- package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +0 -18
- package/dist/esm/ts-rs/DatetimeColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/DatetimeFormatType.d.ts +0 -6
- package/dist/esm/ts-rs/FormatMode.d.ts +0 -1
- package/dist/esm/ts-rs/FormatUnit.d.ts +0 -1
- package/dist/esm/ts-rs/NumberBackgroundMode.d.ts +0 -1
- package/dist/esm/ts-rs/NumberForegroundMode.d.ts +0 -1
- package/dist/esm/ts-rs/PluginConfig.d.ts +0 -2
- package/dist/esm/ts-rs/RoundingMode.d.ts +0 -1
- package/dist/esm/ts-rs/RoundingPriority.d.ts +0 -1
- package/dist/esm/ts-rs/SignDisplay.d.ts +0 -1
- package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +0 -1
- package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +0 -6
- package/dist/esm/ts-rs/StringColorMode.d.ts +0 -1
- package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +0 -1
- package/dist/esm/ts-rs/UseGrouping.d.ts +0 -1
- package/src/rust/components/number_column_style.rs +0 -483
- package/src/rust/config/number_column_style.rs +0 -132
- package/src/rust/dragdrop.rs +0 -481
- package/src/rust/tasks/plugin_column_styles.rs +0 -98
- package/src/ts/ts-rs/ColumnConfigValues.ts +0 -14
- package/src/ts/ts-rs/CustomDatetimeFormat.ts +0 -3
- package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +0 -5
- package/src/ts/ts-rs/CustomNumberFormatConfig.ts +0 -8
- package/src/ts/ts-rs/DatetimeColorMode.ts +0 -3
- package/src/ts/ts-rs/DatetimeFormatType.ts +0 -8
- package/src/ts/ts-rs/FormatMode.ts +0 -3
- package/src/ts/ts-rs/NumberBackgroundMode.ts +0 -3
- package/src/ts/ts-rs/NumberForegroundMode.ts +0 -3
- package/src/ts/ts-rs/PluginConfig.ts +0 -4
- package/src/ts/ts-rs/RoundingMode.ts +0 -3
- package/src/ts/ts-rs/RoundingPriority.ts +0 -3
- package/src/ts/ts-rs/SignDisplay.ts +0 -3
- package/src/ts/ts-rs/SimpleDatetimeFormat.ts +0 -3
- package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +0 -4
- package/src/ts/ts-rs/StringColorMode.ts +0 -3
- package/src/ts/ts-rs/TrailingZeroDisplay.ts +0 -3
- package/src/ts/ts-rs/UseGrouping.ts +0 -3
- /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-68fef752754ffbc6 → perspective-viewer-39ab7da3ca157861}/inline4.js +0 -0
- /package/src/rust/{tasks → config}/export_method.rs +0 -0
- /package/src/rust/{tasks → queries}/export_app.rs +0 -0
- /package/src/rust/{tasks → queries}/is_invalid_drop.rs +0 -0
|
@@ -43,14 +43,11 @@ use super::style::LocalStyle;
|
|
|
43
43
|
use crate::components::column_dropdown::{ColumnDropDownElement, ColumnDropDownPortal};
|
|
44
44
|
use crate::components::containers::scroll_panel_item::ScrollPanelItem;
|
|
45
45
|
use crate::css;
|
|
46
|
-
use crate::
|
|
47
|
-
use crate::
|
|
46
|
+
use crate::presentation::{ColumnLocator, DragDropContainer, Presentation};
|
|
47
|
+
use crate::queries::{ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet};
|
|
48
48
|
use crate::renderer::*;
|
|
49
49
|
use crate::session::drag_drop_update::*;
|
|
50
50
|
use crate::session::*;
|
|
51
|
-
use crate::tasks::{
|
|
52
|
-
ActiveColumnState, ActiveColumnStateData, ColumnsIteratorSet, can_render_column_styles,
|
|
53
|
-
};
|
|
54
51
|
use crate::utils::*;
|
|
55
52
|
|
|
56
53
|
#[derive(Properties)]
|
|
@@ -77,11 +74,28 @@ pub struct ColumnSelectorProps {
|
|
|
77
74
|
// State
|
|
78
75
|
pub session: Session,
|
|
79
76
|
pub renderer: Renderer,
|
|
80
|
-
pub
|
|
77
|
+
pub presentation: Presentation,
|
|
81
78
|
|
|
82
79
|
/// Fires when this component is resized via the UI.
|
|
83
80
|
#[prop_or_default]
|
|
84
81
|
pub on_resize: Option<Rc<PubSub<()>>>,
|
|
82
|
+
|
|
83
|
+
/// Trap-door width pinned by the parent `SettingsPanel` so switching
|
|
84
|
+
/// tabs doesn't shrink the panel. Threaded into the inner
|
|
85
|
+
/// `ScrollPanel` as `initial_width`.
|
|
86
|
+
#[prop_or_default]
|
|
87
|
+
pub initial_width: f64,
|
|
88
|
+
|
|
89
|
+
/// Fires when the inner `ScrollPanel` measures its natural width.
|
|
90
|
+
/// Routed up to `SettingsPanel` which keeps the running max.
|
|
91
|
+
#[prop_or_default]
|
|
92
|
+
pub on_auto_width: Callback<f64>,
|
|
93
|
+
|
|
94
|
+
/// External "release the trap-door" signal from the outer settings
|
|
95
|
+
/// split-panel divider reset. Forwarded into `self.on_reset` so both
|
|
96
|
+
/// inner `ScrollPanel`s drop their cached `viewport_width`.
|
|
97
|
+
#[prop_or_default]
|
|
98
|
+
pub on_dimensions_reset: Option<Rc<PubSub<()>>>,
|
|
85
99
|
}
|
|
86
100
|
|
|
87
101
|
impl PartialEq for ColumnSelectorProps {
|
|
@@ -93,6 +107,7 @@ impl PartialEq for ColumnSelectorProps {
|
|
|
93
107
|
&& self.drag_column == rhs.drag_column
|
|
94
108
|
&& self.metadata == rhs.metadata
|
|
95
109
|
&& self.selected_theme == rhs.selected_theme
|
|
110
|
+
&& self.initial_width == rhs.initial_width
|
|
96
111
|
}
|
|
97
112
|
}
|
|
98
113
|
|
|
@@ -102,7 +117,6 @@ pub enum ColumnSelectorMsg {
|
|
|
102
117
|
/// from `ConfigSelector` after it mutates the view config.
|
|
103
118
|
Redraw,
|
|
104
119
|
HoverActiveIndex(Option<usize>),
|
|
105
|
-
SetWidth(f64),
|
|
106
120
|
Drop((String, DragTarget, DragEffect, usize)),
|
|
107
121
|
}
|
|
108
122
|
|
|
@@ -111,10 +125,9 @@ use ColumnSelectorMsg::*;
|
|
|
111
125
|
/// A `ColumnSelector` controls the `columns` field of the `ViewConfig`,
|
|
112
126
|
/// deriving its options from the table columns and `ViewConfig` expressions.
|
|
113
127
|
pub struct ColumnSelector {
|
|
114
|
-
_subscriptions:
|
|
128
|
+
_subscriptions: Vec<Subscription>,
|
|
115
129
|
drag_container: DragDropContainer,
|
|
116
130
|
column_dropdown: ColumnDropDownElement,
|
|
117
|
-
viewport_width: f64,
|
|
118
131
|
on_reset: Rc<PubSub<()>>,
|
|
119
132
|
}
|
|
120
133
|
|
|
@@ -124,12 +137,14 @@ impl Component for ColumnSelector {
|
|
|
124
137
|
|
|
125
138
|
fn create(ctx: &Context<Self>) -> Self {
|
|
126
139
|
let ColumnSelectorProps {
|
|
127
|
-
|
|
140
|
+
presentation,
|
|
141
|
+
session,
|
|
142
|
+
..
|
|
128
143
|
} = ctx.props();
|
|
129
144
|
|
|
130
145
|
let drop_sub = {
|
|
131
146
|
let cb = ctx.link().callback(ColumnSelectorMsg::Drop);
|
|
132
|
-
|
|
147
|
+
presentation.drop_received.add_listener(cb)
|
|
133
148
|
};
|
|
134
149
|
|
|
135
150
|
let drag_container = DragDropContainer::new(|| {}, {
|
|
@@ -138,28 +153,32 @@ impl Component for ColumnSelector {
|
|
|
138
153
|
});
|
|
139
154
|
|
|
140
155
|
let column_dropdown = ColumnDropDownElement::new(session.clone());
|
|
156
|
+
let on_reset: Rc<PubSub<()>> = Default::default();
|
|
157
|
+
let mut subscriptions = vec![drop_sub];
|
|
158
|
+
if let Some(outer_reset) = ctx.props().on_dimensions_reset.as_ref() {
|
|
159
|
+
let on_reset = on_reset.clone();
|
|
160
|
+
subscriptions.push(outer_reset.add_listener(move |()| on_reset.emit(())));
|
|
161
|
+
}
|
|
162
|
+
|
|
141
163
|
Self {
|
|
142
|
-
_subscriptions:
|
|
143
|
-
viewport_width: 0f64,
|
|
164
|
+
_subscriptions: subscriptions,
|
|
144
165
|
drag_container,
|
|
145
166
|
column_dropdown,
|
|
146
|
-
on_reset
|
|
167
|
+
on_reset,
|
|
147
168
|
}
|
|
148
169
|
}
|
|
149
170
|
|
|
150
171
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
151
172
|
match msg {
|
|
152
173
|
Redraw => true,
|
|
153
|
-
SetWidth(w) => {
|
|
154
|
-
self.viewport_width = w;
|
|
155
|
-
false
|
|
156
|
-
},
|
|
157
174
|
HoverActiveIndex(Some(to_index)) => ctx
|
|
158
175
|
.props()
|
|
159
|
-
.
|
|
176
|
+
.presentation
|
|
160
177
|
.notify_drag_enter(DragTarget::Active, to_index),
|
|
161
178
|
HoverActiveIndex(_) => {
|
|
162
|
-
ctx.props()
|
|
179
|
+
ctx.props()
|
|
180
|
+
.presentation
|
|
181
|
+
.notify_drag_leave(DragTarget::Active);
|
|
163
182
|
true
|
|
164
183
|
},
|
|
165
184
|
Drop((column, DragTarget::Active, DragEffect::Move(DragTarget::Active), index)) => {
|
|
@@ -170,7 +189,7 @@ impl Component for ColumnSelector {
|
|
|
170
189
|
.iter()
|
|
171
190
|
.position(|x| x.as_ref() == Some(&column));
|
|
172
191
|
|
|
173
|
-
let min_cols = ctx.props().renderer.metadata().
|
|
192
|
+
let min_cols = ctx.props().renderer.metadata().min_config_columns;
|
|
174
193
|
let is_to_empty = !config
|
|
175
194
|
.columns
|
|
176
195
|
.get(index)
|
|
@@ -247,7 +266,7 @@ impl Component for ColumnSelector {
|
|
|
247
266
|
let ColumnSelectorProps {
|
|
248
267
|
session,
|
|
249
268
|
renderer,
|
|
250
|
-
|
|
269
|
+
presentation,
|
|
251
270
|
..
|
|
252
271
|
} = ctx.props();
|
|
253
272
|
let metadata = &ctx.props().metadata;
|
|
@@ -271,18 +290,18 @@ impl Component for ColumnSelector {
|
|
|
271
290
|
};
|
|
272
291
|
|
|
273
292
|
let is_aggregated = config.is_aggregated();
|
|
274
|
-
let columns_iter = ColumnsIteratorSet::new(&config, metadata, renderer,
|
|
293
|
+
let columns_iter = ColumnsIteratorSet::new(&config, metadata, renderer, presentation);
|
|
275
294
|
let onselect = ctx.link().callback(|()| Redraw);
|
|
276
295
|
let ondragenter = ctx.link().callback(HoverActiveIndex);
|
|
277
296
|
let ondragover = Callback::from(|_event: DragEvent| _event.prevent_default());
|
|
278
297
|
let ondrop = Callback::from({
|
|
279
|
-
clone!(
|
|
280
|
-
move |event|
|
|
298
|
+
clone!(presentation);
|
|
299
|
+
move |event| presentation.notify_drop(&event)
|
|
281
300
|
});
|
|
282
301
|
|
|
283
302
|
let ondragend = Callback::from({
|
|
284
|
-
clone!(
|
|
285
|
-
move |_|
|
|
303
|
+
clone!(presentation);
|
|
304
|
+
move |_| presentation.notify_drag_end()
|
|
286
305
|
});
|
|
287
306
|
|
|
288
307
|
let mut active_classes = classes!("scrollable");
|
|
@@ -333,7 +352,7 @@ impl Component for ColumnSelector {
|
|
|
333
352
|
drag_column={ctx.props().drag_column.clone()}
|
|
334
353
|
metadata={metadata.clone()}
|
|
335
354
|
selected_theme={ctx.props().selected_theme.clone()}
|
|
336
|
-
{
|
|
355
|
+
{presentation}
|
|
337
356
|
{renderer}
|
|
338
357
|
{session}
|
|
339
358
|
/>
|
|
@@ -368,7 +387,7 @@ impl Component for ColumnSelector {
|
|
|
368
387
|
.and_then(|n| metadata.get_column_table_type(n))
|
|
369
388
|
.or_else(|| {
|
|
370
389
|
if matches!(name.state, ActiveColumnStateData::DragOver) {
|
|
371
|
-
|
|
390
|
+
presentation
|
|
372
391
|
.get_drag_column()
|
|
373
392
|
.and_then(|c| metadata.get_column_table_type(&c))
|
|
374
393
|
} else {
|
|
@@ -381,10 +400,8 @@ impl Component for ColumnSelector {
|
|
|
381
400
|
.map(|n| metadata.is_column_expression(n))
|
|
382
401
|
.unwrap_or(false);
|
|
383
402
|
|
|
384
|
-
let can_render_styles =
|
|
385
|
-
.get_name()
|
|
386
|
-
.and_then(|n| can_render_column_styles(renderer, &config, metadata, n).ok())
|
|
387
|
-
.unwrap_or(false);
|
|
403
|
+
let can_render_styles =
|
|
404
|
+
name.get_name().is_some() && renderer.can_render_column_styles();
|
|
388
405
|
|
|
389
406
|
let show_edit_btn = is_expression || can_render_styles;
|
|
390
407
|
let on_open_expr_panel = &ctx.props().on_open_expr_panel;
|
|
@@ -405,7 +422,7 @@ impl Component for ColumnSelector {
|
|
|
405
422
|
{ondragenter}
|
|
406
423
|
ondragend={&ondragend}
|
|
407
424
|
onselect={&onselect}
|
|
408
|
-
{
|
|
425
|
+
{presentation}
|
|
409
426
|
{renderer}
|
|
410
427
|
{session}
|
|
411
428
|
/>
|
|
@@ -435,7 +452,7 @@ impl Component for ColumnSelector {
|
|
|
435
452
|
onselect={&onselect}
|
|
436
453
|
ondragend={&ondragend}
|
|
437
454
|
on_open_expr_panel={&ctx.props().on_open_expr_panel}
|
|
438
|
-
{
|
|
455
|
+
{presentation}
|
|
439
456
|
{renderer}
|
|
440
457
|
{session}
|
|
441
458
|
/>
|
|
@@ -471,13 +488,14 @@ impl Component for ColumnSelector {
|
|
|
471
488
|
<div id="selected-columns" key="__active_columns__">
|
|
472
489
|
<ScrollPanel
|
|
473
490
|
id="active-columns"
|
|
491
|
+
omit_autosize_div={true}
|
|
474
492
|
class={active_classes}
|
|
475
493
|
dragover={ondragover}
|
|
476
494
|
dragenter={&self.drag_container.dragenter}
|
|
477
495
|
dragleave={&self.drag_container.dragleave}
|
|
478
496
|
viewport_ref={&self.drag_container.noderef}
|
|
479
|
-
initial_width={
|
|
480
|
-
on_auto_width={ctx.
|
|
497
|
+
initial_width={ctx.props().initial_width}
|
|
498
|
+
on_auto_width={ctx.props().on_auto_width.clone()}
|
|
481
499
|
drop={ondrop}
|
|
482
500
|
on_resize={&ctx.props().on_resize}
|
|
483
501
|
on_dimensions_reset={&self.on_reset}
|
|
@@ -14,17 +14,19 @@ use perspective_client::clone;
|
|
|
14
14
|
use yew::{Callback, Html, Properties, function_component, html};
|
|
15
15
|
|
|
16
16
|
use crate::components::form::number_field::NumberField;
|
|
17
|
-
use crate::config::
|
|
17
|
+
use crate::config::ColumnConfigFieldUpdate;
|
|
18
18
|
|
|
19
19
|
// ░░░█▀█░█▀▄░█▀█░█▀█░█▀▀░█▀▄░▀█▀░▀█▀░█▀▀░█▀▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
20
20
|
// ░░░█▀▀░█▀▄░█░█░█▀▀░█▀▀░█▀▄░░█░░░█░░█▀▀░▀▀█░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
21
21
|
// ░░░▀░░░▀░▀░▀▀▀░▀░░░▀▀▀░▀░▀░░▀░░▀▀▀░▀▀▀░▀▀▀░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
|
|
22
22
|
#[derive(Properties, PartialEq)]
|
|
23
23
|
pub struct AggregateDepthSelectorProps {
|
|
24
|
-
pub on_change: Callback<
|
|
24
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
25
25
|
pub value: u32,
|
|
26
26
|
pub group_by_depth: u32,
|
|
27
27
|
pub column_name: String,
|
|
28
|
+
#[prop_or_default]
|
|
29
|
+
pub keys: Vec<String>,
|
|
28
30
|
}
|
|
29
31
|
|
|
30
32
|
#[function_component]
|
|
@@ -36,12 +38,18 @@ pub fn AggregateDepthSelector(props: &AggregateDepthSelectorProps) -> Html {
|
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
let on_change = yew::use_callback(
|
|
39
|
-
(state.setter(), props.on_change.clone()),
|
|
41
|
+
(state.setter(), props.on_change.clone(), props.keys.clone()),
|
|
40
42
|
|x: Option<f64>, deps| {
|
|
41
|
-
|
|
42
|
-
deps.
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
let depth = x.unwrap_or_default() as u32;
|
|
44
|
+
deps.0.set(depth);
|
|
45
|
+
let mut value = serde_json::Map::new();
|
|
46
|
+
if depth > 0 {
|
|
47
|
+
value.insert("aggregate_depth".to_owned(), serde_json::json!(depth));
|
|
48
|
+
}
|
|
49
|
+
deps.1.emit(ColumnConfigFieldUpdate {
|
|
50
|
+
keys: deps.2.clone(),
|
|
51
|
+
value,
|
|
52
|
+
})
|
|
45
53
|
},
|
|
46
54
|
);
|
|
47
55
|
|
|
@@ -0,0 +1,394 @@
|
|
|
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
|
+
//! Schema-driven generic widgets for the Style tab. Each widget renders a
|
|
14
|
+
//! single primitive [`crate::config::ControlSpec`] variant and emits a
|
|
15
|
+
//! [`crate::config::ColumnConfigFieldUpdate`] on change. Built on top of
|
|
16
|
+
//! the existing form components ([`Select`], [`OptionalField`],
|
|
17
|
+
//! [`ColorSelector`]) so that they visually match the rich Yew widgets in
|
|
18
|
+
//! the same sidebar.
|
|
19
|
+
|
|
20
|
+
use std::rc::Rc;
|
|
21
|
+
|
|
22
|
+
use itertools::Itertools;
|
|
23
|
+
use serde_json::Value;
|
|
24
|
+
use wasm_bindgen::JsCast;
|
|
25
|
+
use web_sys::{HtmlInputElement, MouseEvent};
|
|
26
|
+
use yew::{Callback, Html, Properties, classes, function_component, html, use_callback};
|
|
27
|
+
|
|
28
|
+
use crate::components::containers::select::{Select, SelectItem};
|
|
29
|
+
use crate::components::form::color_range_selector::ColorRangeSelector;
|
|
30
|
+
use crate::components::form::color_selector::ColorSelector;
|
|
31
|
+
use crate::components::form::number_field::NumberField;
|
|
32
|
+
use crate::components::form::optional_field::OptionalField;
|
|
33
|
+
use crate::config::{ColumnConfigFieldUpdate, EnumVariant};
|
|
34
|
+
|
|
35
|
+
fn emit(on_change: &Callback<ColumnConfigFieldUpdate>, key: &str, value: Option<Value>) {
|
|
36
|
+
let mut map = serde_json::Map::new();
|
|
37
|
+
if let Some(v) = value {
|
|
38
|
+
map.insert(key.to_owned(), v);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
on_change.emit(ColumnConfigFieldUpdate {
|
|
42
|
+
keys: vec![key.to_owned()],
|
|
43
|
+
value: map,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
fn emit_color_range(
|
|
48
|
+
on_change: &Callback<ColumnConfigFieldUpdate>,
|
|
49
|
+
key_pos: &str,
|
|
50
|
+
key_neg: &str,
|
|
51
|
+
default_pos: &str,
|
|
52
|
+
default_neg: &str,
|
|
53
|
+
new_pos: &str,
|
|
54
|
+
new_neg: &str,
|
|
55
|
+
) {
|
|
56
|
+
let mut value = serde_json::Map::new();
|
|
57
|
+
if new_pos != default_pos {
|
|
58
|
+
value.insert(key_pos.to_owned(), Value::String(new_pos.to_owned()));
|
|
59
|
+
}
|
|
60
|
+
if new_neg != default_neg {
|
|
61
|
+
value.insert(key_neg.to_owned(), Value::String(new_neg.to_owned()));
|
|
62
|
+
}
|
|
63
|
+
on_change.emit(ColumnConfigFieldUpdate {
|
|
64
|
+
keys: vec![key_pos.to_owned(), key_neg.to_owned()],
|
|
65
|
+
value,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#[derive(Properties, PartialEq)]
|
|
70
|
+
pub struct EnumFieldProps {
|
|
71
|
+
pub field_key: String,
|
|
72
|
+
pub variants: Vec<EnumVariant>,
|
|
73
|
+
pub default: String,
|
|
74
|
+
pub current: Option<String>,
|
|
75
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
#[function_component]
|
|
79
|
+
pub fn EnumField(props: &EnumFieldProps) -> Html {
|
|
80
|
+
let selected = props
|
|
81
|
+
.current
|
|
82
|
+
.clone()
|
|
83
|
+
.unwrap_or_else(|| props.default.clone());
|
|
84
|
+
|
|
85
|
+
let checked = selected != props.default;
|
|
86
|
+
let values: Rc<Vec<SelectItem<String>>> = Rc::new(
|
|
87
|
+
props
|
|
88
|
+
.variants
|
|
89
|
+
.iter()
|
|
90
|
+
.map(|v| SelectItem::Option(v.value.clone()))
|
|
91
|
+
.collect_vec(),
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
let on_select = use_callback(
|
|
95
|
+
(
|
|
96
|
+
props.field_key.clone(),
|
|
97
|
+
props.default.clone(),
|
|
98
|
+
props.on_change.clone(),
|
|
99
|
+
),
|
|
100
|
+
|value: String, (key, default, on_change)| {
|
|
101
|
+
if value == *default {
|
|
102
|
+
emit(on_change, key, None);
|
|
103
|
+
} else {
|
|
104
|
+
emit(on_change, key, Some(Value::String(value)));
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
let on_reset = use_callback(
|
|
110
|
+
(props.field_key.clone(), props.on_change.clone()),
|
|
111
|
+
|_: MouseEvent, (key, on_change)| emit(on_change, key, None),
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
html! {
|
|
115
|
+
<div class="row">
|
|
116
|
+
<OptionalField label={props.field_key.clone()} on_check={on_reset} {checked}>
|
|
117
|
+
<Select<String> {values} {selected} {on_select} />
|
|
118
|
+
</OptionalField>
|
|
119
|
+
</div>
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[derive(Properties, PartialEq)]
|
|
124
|
+
pub struct BoolFieldProps {
|
|
125
|
+
pub field_key: String,
|
|
126
|
+
pub default: bool,
|
|
127
|
+
pub current: Option<bool>,
|
|
128
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
#[function_component]
|
|
132
|
+
pub fn BoolField(props: &BoolFieldProps) -> Html {
|
|
133
|
+
let current = props.current.unwrap_or(props.default);
|
|
134
|
+
let oninput = use_callback(
|
|
135
|
+
(
|
|
136
|
+
props.field_key.clone(),
|
|
137
|
+
props.default,
|
|
138
|
+
props.on_change.clone(),
|
|
139
|
+
),
|
|
140
|
+
|e: yew::events::InputEvent, (key, default, on_change)| {
|
|
141
|
+
let target: HtmlInputElement = e.target().unwrap().unchecked_into();
|
|
142
|
+
let next = target.checked();
|
|
143
|
+
if next == *default {
|
|
144
|
+
emit(on_change, key, None);
|
|
145
|
+
} else {
|
|
146
|
+
emit(on_change, key, Some(Value::Bool(next)));
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
let checked = current != props.default;
|
|
152
|
+
let on_reset = use_callback(
|
|
153
|
+
(props.field_key.clone(), props.on_change.clone()),
|
|
154
|
+
|_: MouseEvent, (key, on_change)| emit(on_change, key, None),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
html! {
|
|
158
|
+
<div class="row">
|
|
159
|
+
<OptionalField label={props.field_key.clone()} on_check={on_reset} {checked}>
|
|
160
|
+
<div class="bool-field-container">
|
|
161
|
+
<input
|
|
162
|
+
type="checkbox"
|
|
163
|
+
id={format!("{}-checkbox", props.field_key)}
|
|
164
|
+
checked={current}
|
|
165
|
+
{oninput}
|
|
166
|
+
/>
|
|
167
|
+
<label for={format!("{}-checkbox", props.field_key)} class="bool-field-desc">
|
|
168
|
+
{ if current { "Enabled" } else { "Disabled" } }
|
|
169
|
+
</label>
|
|
170
|
+
</div>
|
|
171
|
+
</OptionalField>
|
|
172
|
+
</div>
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
#[derive(Properties, PartialEq)]
|
|
177
|
+
pub struct NumberFieldPrimitiveProps {
|
|
178
|
+
pub field_key: String,
|
|
179
|
+
pub default: f64,
|
|
180
|
+
pub current: Option<f64>,
|
|
181
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
182
|
+
|
|
183
|
+
#[prop_or_default]
|
|
184
|
+
pub include: Option<bool>,
|
|
185
|
+
|
|
186
|
+
#[prop_or_default]
|
|
187
|
+
pub min: Option<f64>,
|
|
188
|
+
|
|
189
|
+
#[prop_or_default]
|
|
190
|
+
pub max: Option<f64>,
|
|
191
|
+
|
|
192
|
+
#[prop_or_default]
|
|
193
|
+
pub step: Option<f64>,
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
#[function_component]
|
|
197
|
+
pub fn NumberFieldPrimitive(props: &NumberFieldPrimitiveProps) -> Html {
|
|
198
|
+
let on_change_inner = use_callback(
|
|
199
|
+
(
|
|
200
|
+
props.field_key.clone(),
|
|
201
|
+
props.default,
|
|
202
|
+
props.on_change.clone(),
|
|
203
|
+
props.include,
|
|
204
|
+
),
|
|
205
|
+
|value: Option<f64>, (key, default, on_change, include)| match value {
|
|
206
|
+
Some(v) if include.unwrap_or_default() || v != *default => emit(
|
|
207
|
+
on_change,
|
|
208
|
+
key,
|
|
209
|
+
Some(
|
|
210
|
+
serde_json::Number::from_f64(v)
|
|
211
|
+
.map(Value::Number)
|
|
212
|
+
.unwrap_or(Value::Null),
|
|
213
|
+
),
|
|
214
|
+
),
|
|
215
|
+
None if include.unwrap_or_default() => emit(
|
|
216
|
+
on_change,
|
|
217
|
+
key,
|
|
218
|
+
Some(
|
|
219
|
+
serde_json::Number::from_f64(*default)
|
|
220
|
+
.map(Value::Number)
|
|
221
|
+
.unwrap_or(Value::Null),
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
_ => emit(on_change, key, None),
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
html! {
|
|
229
|
+
<NumberField
|
|
230
|
+
label={props.field_key.clone()}
|
|
231
|
+
current_value={props.current}
|
|
232
|
+
default={props.default}
|
|
233
|
+
min={props.min}
|
|
234
|
+
max={props.max}
|
|
235
|
+
step={props.step}
|
|
236
|
+
on_change={on_change_inner}
|
|
237
|
+
/>
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
#[derive(Properties, PartialEq)]
|
|
242
|
+
pub struct ColorRangeFieldProps {
|
|
243
|
+
pub field_key_pos: String,
|
|
244
|
+
pub field_key_neg: String,
|
|
245
|
+
pub default_pos: String,
|
|
246
|
+
pub default_neg: String,
|
|
247
|
+
pub current_pos: Option<String>,
|
|
248
|
+
pub current_neg: Option<String>,
|
|
249
|
+
pub is_gradient: bool,
|
|
250
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
#[function_component]
|
|
254
|
+
pub fn ColorRangeField(props: &ColorRangeFieldProps) -> Html {
|
|
255
|
+
let pos = props
|
|
256
|
+
.current_pos
|
|
257
|
+
.clone()
|
|
258
|
+
.unwrap_or_else(|| props.default_pos.clone());
|
|
259
|
+
let neg = props
|
|
260
|
+
.current_neg
|
|
261
|
+
.clone()
|
|
262
|
+
.unwrap_or_else(|| props.default_neg.clone());
|
|
263
|
+
let is_modified = (props.current_pos.is_some()
|
|
264
|
+
&& props.current_pos.as_deref() != Some(props.default_pos.as_str()))
|
|
265
|
+
|| (props.current_neg.is_some()
|
|
266
|
+
&& props.current_neg.as_deref() != Some(props.default_neg.as_str()));
|
|
267
|
+
|
|
268
|
+
// Multi-key emit: write whichever side(s) differ from default,
|
|
269
|
+
// clear the others. Mirrors the apply semantics of
|
|
270
|
+
// `ColumnConfigFieldUpdate { keys, value }` with both keys owned.
|
|
271
|
+
let on_pos_color = use_callback(
|
|
272
|
+
(
|
|
273
|
+
props.field_key_pos.clone(),
|
|
274
|
+
props.field_key_neg.clone(),
|
|
275
|
+
props.default_pos.clone(),
|
|
276
|
+
props.default_neg.clone(),
|
|
277
|
+
props.on_change.clone(),
|
|
278
|
+
neg.clone(),
|
|
279
|
+
),
|
|
280
|
+
|new_pos: String, (key_pos, key_neg, default_pos, default_neg, on_change, neg)| {
|
|
281
|
+
emit_color_range(
|
|
282
|
+
on_change,
|
|
283
|
+
key_pos,
|
|
284
|
+
key_neg,
|
|
285
|
+
default_pos,
|
|
286
|
+
default_neg,
|
|
287
|
+
&new_pos,
|
|
288
|
+
neg,
|
|
289
|
+
);
|
|
290
|
+
},
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
let on_neg_color = use_callback(
|
|
294
|
+
(
|
|
295
|
+
props.field_key_pos.clone(),
|
|
296
|
+
props.field_key_neg.clone(),
|
|
297
|
+
props.default_pos.clone(),
|
|
298
|
+
props.default_neg.clone(),
|
|
299
|
+
props.on_change.clone(),
|
|
300
|
+
pos.clone(),
|
|
301
|
+
),
|
|
302
|
+
|new_neg: String, (key_pos, key_neg, default_pos, default_neg, on_change, pos)| {
|
|
303
|
+
emit_color_range(
|
|
304
|
+
on_change,
|
|
305
|
+
key_pos,
|
|
306
|
+
key_neg,
|
|
307
|
+
default_pos,
|
|
308
|
+
default_neg,
|
|
309
|
+
pos,
|
|
310
|
+
&new_neg,
|
|
311
|
+
);
|
|
312
|
+
},
|
|
313
|
+
);
|
|
314
|
+
|
|
315
|
+
let on_reset = use_callback(
|
|
316
|
+
(
|
|
317
|
+
props.field_key_pos.clone(),
|
|
318
|
+
props.field_key_neg.clone(),
|
|
319
|
+
props.on_change.clone(),
|
|
320
|
+
),
|
|
321
|
+
|_: (), (key_pos, key_neg, on_change)| {
|
|
322
|
+
on_change.emit(ColumnConfigFieldUpdate {
|
|
323
|
+
keys: vec![key_pos.clone(), key_neg.clone()],
|
|
324
|
+
value: serde_json::Map::new(),
|
|
325
|
+
});
|
|
326
|
+
},
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
html! {
|
|
330
|
+
<div class="row">
|
|
331
|
+
<ColorRangeSelector
|
|
332
|
+
pos_class={classes!(props.field_key_pos.clone())}
|
|
333
|
+
neg_class={classes!(props.field_key_neg.clone())}
|
|
334
|
+
pos_color={pos}
|
|
335
|
+
neg_color={neg}
|
|
336
|
+
is_gradient={props.is_gradient}
|
|
337
|
+
{on_pos_color}
|
|
338
|
+
{on_neg_color}
|
|
339
|
+
{on_reset}
|
|
340
|
+
{is_modified}
|
|
341
|
+
/>
|
|
342
|
+
</div>
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
#[derive(Properties, PartialEq)]
|
|
347
|
+
pub struct ColorFieldProps {
|
|
348
|
+
pub field_key: String,
|
|
349
|
+
pub default: String,
|
|
350
|
+
pub current: Option<String>,
|
|
351
|
+
pub on_change: Callback<ColumnConfigFieldUpdate>,
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
#[function_component]
|
|
355
|
+
pub fn ColorField(props: &ColorFieldProps) -> Html {
|
|
356
|
+
let color = props
|
|
357
|
+
.current
|
|
358
|
+
.clone()
|
|
359
|
+
.unwrap_or_else(|| props.default.clone());
|
|
360
|
+
let is_modified =
|
|
361
|
+
props.current.as_deref() != Some(props.default.as_str()) && props.current.is_some();
|
|
362
|
+
|
|
363
|
+
let on_color = use_callback(
|
|
364
|
+
(
|
|
365
|
+
props.field_key.clone(),
|
|
366
|
+
props.default.clone(),
|
|
367
|
+
props.on_change.clone(),
|
|
368
|
+
),
|
|
369
|
+
|value: String, (key, default, on_change)| {
|
|
370
|
+
if value == *default {
|
|
371
|
+
emit(on_change, key, None);
|
|
372
|
+
} else {
|
|
373
|
+
emit(on_change, key, Some(Value::String(value)));
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
);
|
|
377
|
+
|
|
378
|
+
let on_reset = use_callback(
|
|
379
|
+
(props.field_key.clone(), props.on_change.clone()),
|
|
380
|
+
|_: (), (key, on_change)| emit(on_change, key, None),
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
html! {
|
|
384
|
+
<div class="row">
|
|
385
|
+
<ColorSelector
|
|
386
|
+
{color}
|
|
387
|
+
{on_color}
|
|
388
|
+
{on_reset}
|
|
389
|
+
{is_modified}
|
|
390
|
+
title={Some(format!("{}-label", props.field_key))}
|
|
391
|
+
/>
|
|
392
|
+
</div>
|
|
393
|
+
}
|
|
394
|
+
}
|