@perspective-dev/viewer 4.4.1 → 4.5.1
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 -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/bootstrap.d.ts +2 -1
- package/dist/esm/column-format.d.ts +51 -0
- package/dist/esm/extensions.d.ts +2 -0
- package/dist/esm/perspective-viewer.d.ts +3 -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 +16 -72
- 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 +6 -3
- package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +7 -4
- package/dist/wasm/perspective-viewer.d.ts +77 -18
- package/dist/wasm/perspective-viewer.js +302 -148
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +20 -15
- package/package.json +24 -2
- package/src/css/column-selector.css +3 -2
- package/src/css/column-settings-panel.css +44 -9
- package/src/css/column-style.css +35 -2
- package/src/css/containers/scroll-panel.css +2 -1
- package/src/css/containers/tabs.css +8 -52
- package/src/css/dom/checkbox.css +2 -6
- 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/viewer.css +143 -3
- package/src/rust/components/column_dropdown.rs +3 -1
- package/src/rust/components/column_selector/active_column.rs +16 -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 +10 -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 +395 -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 +44 -49
- package/src/rust/components/containers/dragdrop_list.rs +32 -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 +17 -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 +141 -174
- 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 +169 -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 +5 -6
- package/src/rust/custom_elements/copy_dropdown.rs +30 -18
- package/src/rust/custom_elements/debug_plugin.rs +1 -3
- package/src/rust/custom_elements/export_dropdown.rs +26 -18
- package/src/rust/custom_elements/viewer.rs +62 -73
- package/src/rust/custom_events.rs +181 -224
- package/src/rust/js/plugin.rs +45 -117
- package/src/rust/lib.rs +34 -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 +703 -60
- 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 +98 -0
- package/src/rust/tasks/resize_observer.rs +11 -33
- package/src/rust/tasks/restore_and_render.rs +128 -90
- package/src/rust/tasks/{get_viewer_config.rs → send_column_config.rs} +39 -35
- package/src/rust/tasks/send_plugin_config.rs +33 -33
- package/src/rust/tasks/update_and_render.rs +75 -49
- 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/checkbox-checked-icon.svg +1 -1
- package/src/svg/checkbox-unchecked-icon.svg +1 -1
- 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 +24 -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 +5 -0
- package/src/themes/intl/de.css +43 -6
- package/src/themes/intl/es.css +43 -6
- package/src/themes/intl/fr.css +43 -6
- package/src/themes/intl/ja.css +43 -6
- package/src/themes/intl/pt.css +43 -6
- package/src/themes/intl/zh.css +43 -6
- package/src/themes/intl.css +38 -4
- package/src/themes/monokai.css +45 -61
- package/src/themes/phosphor.css +20 -29
- 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/vaporwave.css +40 -74
- package/src/ts/bootstrap.ts +14 -3
- package/src/ts/column-format.ts +162 -0
- package/src/ts/extensions.ts +4 -0
- package/src/ts/perspective-viewer.ts +9 -1
- package/src/{rust/components/column_settings_sidebar/style_tab/stub.rs → ts/perspective-viewer.worker.ts} +2 -22
- package/src/ts/plugin.ts +25 -101
- 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 +1 -2
- 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 -491
- package/src/rust/config/number_column_style.rs +0 -136
- 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-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline0.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline1.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline2.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/inline3.js +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-d924246f0b4a3dce → perspective-viewer-3cd58f0374935772}/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
|
@@ -14,22 +14,24 @@ use std::rc::Rc;
|
|
|
14
14
|
|
|
15
15
|
use futures::channel::oneshot::*;
|
|
16
16
|
use perspective_js::utils::*;
|
|
17
|
+
use wasm_bindgen::JsCast;
|
|
17
18
|
use wasm_bindgen::prelude::*;
|
|
19
|
+
use web_sys::{FocusEvent, KeyboardEvent};
|
|
18
20
|
use yew::prelude::*;
|
|
19
21
|
|
|
20
22
|
use super::containers::split_panel::SplitPanel;
|
|
21
23
|
use super::font_loader::{FontLoader, FontLoaderProps, FontLoaderStatus};
|
|
22
|
-
use super::form::debug::DebugPanel;
|
|
23
24
|
use super::style::{LocalStyle, StyleProvider};
|
|
24
25
|
use crate::components::column_settings_sidebar::ColumnSettingsPanel;
|
|
25
26
|
use crate::components::main_panel::MainPanel;
|
|
26
|
-
use crate::components::settings_panel::SettingsPanel;
|
|
27
|
+
use crate::components::settings_panel::{SelectedTab, SettingsPanel};
|
|
27
28
|
use crate::config::*;
|
|
28
29
|
use crate::css;
|
|
29
|
-
use crate::custom_events::CustomEvents;
|
|
30
|
-
use crate::dragdrop::{DragDropProps, *};
|
|
31
30
|
use crate::js::JsPerspectiveViewerPlugin;
|
|
32
|
-
use crate::presentation::{
|
|
31
|
+
use crate::presentation::{
|
|
32
|
+
ColumnLocator, ColumnSettingsTab, DragDropProps, Presentation, PresentationProps,
|
|
33
|
+
};
|
|
34
|
+
use crate::queries::*;
|
|
33
35
|
use crate::renderer::{RendererProps, *};
|
|
34
36
|
use crate::session::{SessionProps, *};
|
|
35
37
|
use crate::tasks::*;
|
|
@@ -41,8 +43,6 @@ pub struct PerspectiveViewerProps {
|
|
|
41
43
|
pub elem: web_sys::HtmlElement,
|
|
42
44
|
|
|
43
45
|
/// State
|
|
44
|
-
pub custom_events: CustomEvents,
|
|
45
|
-
pub dragdrop: DragDrop,
|
|
46
46
|
pub session: Session,
|
|
47
47
|
pub renderer: Renderer,
|
|
48
48
|
pub presentation: Presentation,
|
|
@@ -54,44 +54,6 @@ impl PartialEq for PerspectiveViewerProps {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
impl HasCustomEvents for PerspectiveViewerProps {
|
|
58
|
-
fn custom_events(&self) -> &CustomEvents {
|
|
59
|
-
&self.custom_events
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
impl HasDragDrop for PerspectiveViewerProps {
|
|
64
|
-
fn dragdrop(&self) -> &DragDrop {
|
|
65
|
-
&self.dragdrop
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
impl HasPresentation for PerspectiveViewerProps {
|
|
70
|
-
fn presentation(&self) -> &Presentation {
|
|
71
|
-
&self.presentation
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
impl HasRenderer for PerspectiveViewerProps {
|
|
76
|
-
fn renderer(&self) -> &Renderer {
|
|
77
|
-
&self.renderer
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
impl HasSession for PerspectiveViewerProps {
|
|
82
|
-
fn session(&self) -> &Session {
|
|
83
|
-
&self.session
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
impl StateProvider for PerspectiveViewerProps {
|
|
88
|
-
type State = PerspectiveViewerProps;
|
|
89
|
-
|
|
90
|
-
fn clone_state(&self) -> Self::State {
|
|
91
|
-
self.clone()
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
57
|
#[derive(Debug)]
|
|
96
58
|
pub enum PerspectiveViewerMsg {
|
|
97
59
|
ColumnSettingsPanelSizeUpdate(Option<i32>),
|
|
@@ -105,6 +67,8 @@ pub enum PerspectiveViewerMsg {
|
|
|
105
67
|
Reset(bool, Option<Sender<()>>),
|
|
106
68
|
Resize,
|
|
107
69
|
SettingsPanelSizeUpdate(Option<i32>),
|
|
70
|
+
SettingsPanelTabChanged(SelectedTab),
|
|
71
|
+
SettingsPanelAutoWidth(f64),
|
|
108
72
|
ToggleDebug,
|
|
109
73
|
ToggleSettingsComplete(SettingsUpdate, Sender<()>),
|
|
110
74
|
ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
|
|
@@ -142,8 +106,11 @@ pub struct PerspectiveViewer {
|
|
|
142
106
|
on_close_column_settings: Callback<()>,
|
|
143
107
|
on_rendered: Option<Sender<()>>,
|
|
144
108
|
on_resize: Rc<PubSub<()>>,
|
|
109
|
+
on_settings_panel_dimensions_reset: Rc<PubSub<()>>,
|
|
145
110
|
settings_open: bool,
|
|
146
111
|
settings_panel_width_override: Option<i32>,
|
|
112
|
+
settings_panel_selected_tab: SelectedTab,
|
|
113
|
+
settings_panel_auto_width: f64,
|
|
147
114
|
|
|
148
115
|
/// Value-semantic state snapshots (Step 4 scaffold).
|
|
149
116
|
/// Populated by `UpdateSession` / `UpdateRenderer` / `UpdatePresentation` /
|
|
@@ -156,6 +123,70 @@ pub struct PerspectiveViewer {
|
|
|
156
123
|
/// Counts in-flight renders (incremented on `view_config_changed`,
|
|
157
124
|
/// decremented on `view_created`). Threaded to `StatusIndicator`.
|
|
158
125
|
update_count: u32,
|
|
126
|
+
|
|
127
|
+
/// Window listeners that toggle the `.shift-active` class on the host
|
|
128
|
+
/// element while the Shift key is held, making Shift-modified affordances
|
|
129
|
+
/// (e.g. inactive column add, active column remove, status-bar reset)
|
|
130
|
+
/// visually discoverable. Stored so the closures outlive `create`.
|
|
131
|
+
_shift_listeners: ShiftListeners,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
struct ShiftListeners {
|
|
135
|
+
elem: web_sys::HtmlElement,
|
|
136
|
+
keydown: Closure<dyn FnMut(KeyboardEvent)>,
|
|
137
|
+
keyup: Closure<dyn FnMut(KeyboardEvent)>,
|
|
138
|
+
blur: Closure<dyn FnMut(FocusEvent)>,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
impl Drop for ShiftListeners {
|
|
142
|
+
fn drop(&mut self) {
|
|
143
|
+
let win = global::window();
|
|
144
|
+
let _ = win
|
|
145
|
+
.remove_event_listener_with_callback("keydown", self.keydown.as_ref().unchecked_ref());
|
|
146
|
+
let _ =
|
|
147
|
+
win.remove_event_listener_with_callback("keyup", self.keyup.as_ref().unchecked_ref());
|
|
148
|
+
let _ = win.remove_event_listener_with_callback("blur", self.blur.as_ref().unchecked_ref());
|
|
149
|
+
let _ = self.elem.class_list().remove_1("shift-active");
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fn install_shift_listeners(elem: web_sys::HtmlElement) -> ShiftListeners {
|
|
154
|
+
let keydown = {
|
|
155
|
+
let elem = elem.clone();
|
|
156
|
+
Closure::wrap(Box::new(move |event: KeyboardEvent| {
|
|
157
|
+
if event.key() == "Shift" {
|
|
158
|
+
let _ = elem.class_list().add_1("shift-active");
|
|
159
|
+
}
|
|
160
|
+
}) as Box<dyn FnMut(KeyboardEvent)>)
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
let keyup = {
|
|
164
|
+
let elem = elem.clone();
|
|
165
|
+
Closure::wrap(Box::new(move |event: KeyboardEvent| {
|
|
166
|
+
if event.key() == "Shift" {
|
|
167
|
+
let _ = elem.class_list().remove_1("shift-active");
|
|
168
|
+
}
|
|
169
|
+
}) as Box<dyn FnMut(KeyboardEvent)>)
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
let blur = {
|
|
173
|
+
let elem = elem.clone();
|
|
174
|
+
Closure::wrap(Box::new(move |_: FocusEvent| {
|
|
175
|
+
let _ = elem.class_list().remove_1("shift-active");
|
|
176
|
+
}) as Box<dyn FnMut(FocusEvent)>)
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
let win = global::window();
|
|
180
|
+
let _ = win.add_event_listener_with_callback("keydown", keydown.as_ref().unchecked_ref());
|
|
181
|
+
let _ = win.add_event_listener_with_callback("keyup", keyup.as_ref().unchecked_ref());
|
|
182
|
+
let _ = win.add_event_listener_with_callback("blur", blur.as_ref().unchecked_ref());
|
|
183
|
+
|
|
184
|
+
ShiftListeners {
|
|
185
|
+
elem,
|
|
186
|
+
keydown,
|
|
187
|
+
keyup,
|
|
188
|
+
blur,
|
|
189
|
+
}
|
|
159
190
|
}
|
|
160
191
|
|
|
161
192
|
impl Component for PerspectiveViewer {
|
|
@@ -195,6 +226,8 @@ impl Component for PerspectiveViewer {
|
|
|
195
226
|
});
|
|
196
227
|
}
|
|
197
228
|
|
|
229
|
+
let shift_listeners = install_shift_listeners(elem);
|
|
230
|
+
|
|
198
231
|
Self {
|
|
199
232
|
_subscriptions: subscriptions,
|
|
200
233
|
column_settings_panel_width_override: None,
|
|
@@ -203,13 +236,17 @@ impl Component for PerspectiveViewer {
|
|
|
203
236
|
on_close_column_settings,
|
|
204
237
|
on_rendered: None,
|
|
205
238
|
on_resize: Default::default(),
|
|
239
|
+
on_settings_panel_dimensions_reset: Default::default(),
|
|
206
240
|
settings_open: false,
|
|
207
241
|
settings_panel_width_override: None,
|
|
242
|
+
settings_panel_selected_tab: SelectedTab::default(),
|
|
243
|
+
settings_panel_auto_width: 0.0,
|
|
208
244
|
session_props,
|
|
209
245
|
renderer_props,
|
|
210
246
|
presentation_props,
|
|
211
247
|
dragdrop_props: DragDropProps::default(),
|
|
212
248
|
update_count: 0,
|
|
249
|
+
_shift_listeners: shift_listeners,
|
|
213
250
|
}
|
|
214
251
|
}
|
|
215
252
|
|
|
@@ -221,43 +258,13 @@ impl Component for PerspectiveViewer {
|
|
|
221
258
|
false
|
|
222
259
|
},
|
|
223
260
|
Reset(all, sender) => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
ctx.props().renderer,
|
|
227
|
-
ctx.props().
|
|
228
|
-
|
|
261
|
+
reset_all(
|
|
262
|
+
&ctx.props().session,
|
|
263
|
+
&ctx.props().renderer,
|
|
264
|
+
&ctx.props().presentation,
|
|
265
|
+
all,
|
|
266
|
+
sender,
|
|
229
267
|
);
|
|
230
|
-
|
|
231
|
-
ApiFuture::spawn(async move {
|
|
232
|
-
session
|
|
233
|
-
.reset(ResetOptions {
|
|
234
|
-
config: true,
|
|
235
|
-
expressions: all,
|
|
236
|
-
..ResetOptions::default()
|
|
237
|
-
})
|
|
238
|
-
.await?;
|
|
239
|
-
let columns_config = if all {
|
|
240
|
-
presentation.reset_columns_configs();
|
|
241
|
-
None
|
|
242
|
-
} else {
|
|
243
|
-
Some(presentation.all_columns_configs())
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
renderer.reset(columns_config.as_ref()).await?;
|
|
247
|
-
presentation.reset_available_themes(None).await;
|
|
248
|
-
if all {
|
|
249
|
-
presentation.reset_theme().await?;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
let result = renderer.draw(session.validate().await?.create_view()).await;
|
|
253
|
-
if let Some(sender) = sender {
|
|
254
|
-
sender.send(()).unwrap();
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
renderer.reset_changed.emit(());
|
|
258
|
-
result
|
|
259
|
-
});
|
|
260
|
-
|
|
261
268
|
false
|
|
262
269
|
},
|
|
263
270
|
ToggleSettingsInit(Some(SettingsUpdate::Missing), None) => false,
|
|
@@ -335,6 +342,10 @@ impl Component for PerspectiveViewer {
|
|
|
335
342
|
ctx.props()
|
|
336
343
|
.presentation
|
|
337
344
|
.set_open_column_settings(Some(open_column_settings));
|
|
345
|
+
|
|
346
|
+
if locator.is_some() {
|
|
347
|
+
self.settings_panel_selected_tab = SelectedTab::Query;
|
|
348
|
+
}
|
|
338
349
|
}
|
|
339
350
|
|
|
340
351
|
if let Some(sender) = sender {
|
|
@@ -349,7 +360,22 @@ impl Component for PerspectiveViewer {
|
|
|
349
360
|
},
|
|
350
361
|
SettingsPanelSizeUpdate(None) => {
|
|
351
362
|
self.settings_panel_width_override = None;
|
|
352
|
-
|
|
363
|
+
self.settings_panel_auto_width = 0.0;
|
|
364
|
+
self.on_settings_panel_dimensions_reset.emit(());
|
|
365
|
+
true
|
|
366
|
+
},
|
|
367
|
+
SettingsPanelTabChanged(tab) => {
|
|
368
|
+
let changed = tab != self.settings_panel_selected_tab;
|
|
369
|
+
self.settings_panel_selected_tab = tab;
|
|
370
|
+
changed
|
|
371
|
+
},
|
|
372
|
+
SettingsPanelAutoWidth(w) => {
|
|
373
|
+
if w > self.settings_panel_auto_width {
|
|
374
|
+
self.settings_panel_auto_width = w;
|
|
375
|
+
true
|
|
376
|
+
} else {
|
|
377
|
+
false
|
|
378
|
+
}
|
|
353
379
|
},
|
|
354
380
|
ColumnSettingsPanelSizeUpdate(Some(x)) => {
|
|
355
381
|
self.column_settings_panel_width_override = Some(x);
|
|
@@ -449,8 +475,6 @@ impl Component for PerspectiveViewer {
|
|
|
449
475
|
|
|
450
476
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
451
477
|
let Self::Properties {
|
|
452
|
-
custom_events,
|
|
453
|
-
dragdrop,
|
|
454
478
|
presentation,
|
|
455
479
|
renderer,
|
|
456
480
|
session,
|
|
@@ -470,7 +494,7 @@ impl Component for PerspectiveViewer {
|
|
|
470
494
|
}
|
|
471
495
|
|
|
472
496
|
let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
|
|
473
|
-
locator:
|
|
497
|
+
locator: c,
|
|
474
498
|
sender: None,
|
|
475
499
|
toggle: true,
|
|
476
500
|
});
|
|
@@ -496,17 +520,13 @@ impl Component for PerspectiveViewer {
|
|
|
496
520
|
let plugin_name = self.renderer_props.plugin_name.clone();
|
|
497
521
|
let available_plugins = self.renderer_props.available_plugins.clone();
|
|
498
522
|
let has_table = self.session_props.has_table.clone();
|
|
499
|
-
let named_column_count = self
|
|
500
|
-
.renderer_props
|
|
501
|
-
.requirements
|
|
502
|
-
.names
|
|
503
|
-
.as_ref()
|
|
504
|
-
.map(|n| n.len())
|
|
505
|
-
.unwrap_or(0);
|
|
523
|
+
let named_column_count = self.renderer_props.config.config_column_names.len();
|
|
506
524
|
|
|
507
525
|
let view_config = self.session_props.config.clone();
|
|
508
526
|
let drag_column = self.dragdrop_props.column.clone();
|
|
509
527
|
let metadata = self.session_props.metadata.clone();
|
|
528
|
+
let on_select_tab = ctx.link().callback(SettingsPanelTabChanged);
|
|
529
|
+
let on_auto_width = ctx.link().callback(SettingsPanelAutoWidth);
|
|
510
530
|
let settings_panel = html! {
|
|
511
531
|
if is_settings_open {
|
|
512
532
|
<SettingsPanel
|
|
@@ -520,11 +540,16 @@ impl Component for PerspectiveViewer {
|
|
|
520
540
|
{has_table}
|
|
521
541
|
{named_column_count}
|
|
522
542
|
{view_config}
|
|
543
|
+
plugin_config={self.renderer_props.plugin_config.clone()}
|
|
523
544
|
{drag_column}
|
|
524
545
|
metadata={metadata.clone()}
|
|
525
546
|
open_column_settings={self.presentation_props.open_column_settings.clone()}
|
|
526
547
|
selected_theme={self.presentation_props.selected_theme.clone()}
|
|
527
|
-
{
|
|
548
|
+
selected_tab={self.settings_panel_selected_tab}
|
|
549
|
+
auto_width={self.settings_panel_auto_width}
|
|
550
|
+
on_dimensions_reset={&self.on_settings_panel_dimensions_reset}
|
|
551
|
+
{on_select_tab}
|
|
552
|
+
{on_auto_width}
|
|
528
553
|
{presentation}
|
|
529
554
|
{renderer}
|
|
530
555
|
{session}
|
|
@@ -552,8 +577,8 @@ impl Component for PerspectiveViewer {
|
|
|
552
577
|
plugin_name={self.renderer_props.plugin_name.clone()}
|
|
553
578
|
{metadata}
|
|
554
579
|
view_config={self.session_props.config.clone()}
|
|
580
|
+
column_stats={self.session_props.column_stats.clone()}
|
|
555
581
|
selected_theme={self.presentation_props.selected_theme.clone()}
|
|
556
|
-
{custom_events}
|
|
557
582
|
{presentation}
|
|
558
583
|
{renderer}
|
|
559
584
|
{session}
|
|
@@ -564,43 +589,23 @@ impl Component for PerspectiveViewer {
|
|
|
564
589
|
};
|
|
565
590
|
|
|
566
591
|
let on_reset = ctx.link().callback(|all| Reset(all, None));
|
|
567
|
-
let render_limits = self.renderer_props.render_limits;
|
|
568
|
-
let has_table = self.session_props.has_table.clone();
|
|
569
|
-
let is_errored = self.session_props.error.is_some();
|
|
570
|
-
let stats = self.session_props.stats.clone();
|
|
571
|
-
let update_count = self.update_count;
|
|
572
|
-
let error = self.session_props.error.clone();
|
|
573
592
|
let is_settings_open = self.settings_open
|
|
574
593
|
&& matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
|
|
575
|
-
let title = self.session_props.title.clone();
|
|
576
|
-
let selected_theme = self.presentation_props.selected_theme.clone();
|
|
577
|
-
let available_themes = self.presentation_props.available_themes.clone();
|
|
578
594
|
let main_panel = html! {
|
|
579
595
|
<MainPanel
|
|
580
596
|
{on_settings}
|
|
581
597
|
{on_reset}
|
|
582
|
-
{
|
|
583
|
-
{
|
|
584
|
-
{
|
|
585
|
-
{stats}
|
|
586
|
-
{update_count}
|
|
587
|
-
{error}
|
|
598
|
+
session_props={self.session_props.clone()}
|
|
599
|
+
renderer_props={self.renderer_props.clone()}
|
|
600
|
+
presentation_props={self.presentation_props.clone()}
|
|
588
601
|
{is_settings_open}
|
|
589
|
-
{
|
|
590
|
-
{selected_theme}
|
|
591
|
-
{available_themes}
|
|
592
|
-
is_workspace={self.presentation_props.is_workspace}
|
|
593
|
-
{custom_events}
|
|
602
|
+
update_count={self.update_count}
|
|
594
603
|
{presentation}
|
|
595
604
|
{renderer}
|
|
596
605
|
{session}
|
|
597
606
|
/>
|
|
598
607
|
};
|
|
599
608
|
|
|
600
|
-
let debug_panel = html! {
|
|
601
|
-
if self.debug_open { <DebugPanel {presentation} {renderer} {session} /> }
|
|
602
|
-
};
|
|
603
|
-
|
|
604
609
|
html! {
|
|
605
610
|
<StyleProvider root={ctx.props().elem.clone()}>
|
|
606
611
|
<LocalStyle href={css!("viewer")} />
|
|
@@ -612,10 +617,16 @@ impl Component for PerspectiveViewer {
|
|
|
612
617
|
skip_empty=true
|
|
613
618
|
initial_size={self.settings_panel_width_override}
|
|
614
619
|
on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
|
|
615
|
-
on_resize={
|
|
616
|
-
|
|
620
|
+
on_resize={{
|
|
621
|
+
let size_cb = on_split_panel_resize.clone();
|
|
622
|
+
let resize_cb = resize_callback(&ctx.props().session, &ctx.props().renderer);
|
|
623
|
+
move |x| {
|
|
624
|
+
size_cb.emit(x);
|
|
625
|
+
resize_cb.emit(());
|
|
626
|
+
}
|
|
627
|
+
}}
|
|
628
|
+
on_resize_finished={resize_callback(&ctx.props().session, &ctx.props().renderer)}
|
|
617
629
|
>
|
|
618
|
-
{ debug_panel }
|
|
619
630
|
{ settings_panel }
|
|
620
631
|
<div id="main_column_container">
|
|
621
632
|
{ main_panel }
|
|
@@ -735,17 +746,43 @@ fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
|
|
|
735
746
|
.view_created
|
|
736
747
|
.add_listener(ctx.link().callback(|_| DecrementUpdateCount));
|
|
737
748
|
|
|
738
|
-
|
|
749
|
+
// Stats fetch resolution (populates session.column_stats) triggers
|
|
750
|
+
// a fresh `SessionProps` so `column_stats` reaches downstream
|
|
751
|
+
// components and the StyleTab re-queries the schema with the
|
|
752
|
+
// new value.
|
|
753
|
+
let sub8 = s.column_stats_changed.add_notify_listener(&cb);
|
|
754
|
+
|
|
755
|
+
vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8]
|
|
739
756
|
};
|
|
740
757
|
|
|
741
758
|
let renderer_props_sub = {
|
|
742
759
|
let renderer = ctx.props().renderer.clone();
|
|
743
760
|
let cb_plugin = ctx.link().callback({
|
|
761
|
+
let renderer = renderer.clone();
|
|
744
762
|
move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
|
|
745
763
|
});
|
|
746
764
|
|
|
765
|
+
// Re-snapshot RendererProps when the plugin_config bucket
|
|
766
|
+
// changes (in-tab edit via `send_plugin_config`, JSON paste via
|
|
767
|
+
// `restore_and_render`, full clear via `reset_all` with
|
|
768
|
+
// `all=true`). Without this, `RendererProps.plugin_config`
|
|
769
|
+
// would stay frozen at its construct-time value and `PluginTab`
|
|
770
|
+
// would render stale.
|
|
771
|
+
let cb_plugin_config = ctx.link().callback({
|
|
772
|
+
let renderer = renderer.clone();
|
|
773
|
+
move |_: serde_json::Map<String, serde_json::Value>| {
|
|
774
|
+
UpdateRenderer(Box::new(renderer.to_props(None)))
|
|
775
|
+
}
|
|
776
|
+
});
|
|
777
|
+
|
|
747
778
|
let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
|
|
748
|
-
|
|
779
|
+
let sub2 = ctx
|
|
780
|
+
.props()
|
|
781
|
+
.renderer
|
|
782
|
+
.plugin_config_changed
|
|
783
|
+
.add_listener(cb_plugin_config);
|
|
784
|
+
|
|
785
|
+
vec![sub1, sub2]
|
|
749
786
|
};
|
|
750
787
|
|
|
751
788
|
let presentation_props_sub = {
|
|
@@ -779,7 +816,7 @@ fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
|
|
|
779
816
|
let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
|
|
780
817
|
let sub1 = ctx
|
|
781
818
|
.props()
|
|
782
|
-
.
|
|
819
|
+
.presentation
|
|
783
820
|
.drop_received
|
|
784
821
|
.add_notify_listener(&cb_clear);
|
|
785
822
|
|
|
@@ -858,19 +895,19 @@ fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
|
|
|
858
895
|
.borrow_mut() = Some(cb);
|
|
859
896
|
}
|
|
860
897
|
|
|
861
|
-
//
|
|
898
|
+
// Drag/drop: on_dragstart (post-merge: lives on Presentation)
|
|
862
899
|
{
|
|
863
|
-
let
|
|
864
|
-
let cb = ctx
|
|
865
|
-
.
|
|
866
|
-
|
|
900
|
+
let presentation = ctx.props().presentation.clone();
|
|
901
|
+
let cb = ctx.link().callback(move |_: DragEffect| {
|
|
902
|
+
UpdateDragDrop(Box::new(presentation.drag_drop_props()))
|
|
903
|
+
});
|
|
867
904
|
|
|
868
|
-
*ctx.props().
|
|
905
|
+
*ctx.props().presentation.on_dragstart.borrow_mut() = Some(cb);
|
|
869
906
|
}
|
|
870
907
|
|
|
871
|
-
//
|
|
908
|
+
// Drag/drop: on_dragend
|
|
872
909
|
{
|
|
873
910
|
let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
|
|
874
|
-
*ctx.props().
|
|
911
|
+
*ctx.props().presentation.on_dragend.borrow_mut() = Some(cb);
|
|
875
912
|
}
|
|
876
913
|
}
|
|
@@ -0,0 +1,195 @@
|
|
|
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::collections::HashSet;
|
|
14
|
+
|
|
15
|
+
use serde::{Deserialize, Serialize};
|
|
16
|
+
use serde_json::Value;
|
|
17
|
+
|
|
18
|
+
use super::{KeyValueOpts, NumberSeriesStyleDefaultConfig};
|
|
19
|
+
|
|
20
|
+
/// The full schema for one column at one point in time. Plugins may return
|
|
21
|
+
/// different schemas for the same column based on the column's current
|
|
22
|
+
/// stored value (e.g. to hide dependent fields), so this is re-queried on
|
|
23
|
+
/// every field update.
|
|
24
|
+
///
|
|
25
|
+
/// Each entry is a [`ControlSpec`]. Primitive variants carry their own
|
|
26
|
+
/// `key` (JSON storage key) inline; the sidebar UI label is supplied by
|
|
27
|
+
/// CSS via `--psp-label--<key>--content`. Composite variants render a
|
|
28
|
+
/// self-contained Yew component that supplies its own labels and owns a
|
|
29
|
+
/// fixed key namespace via [`ControlSpec::serialized_keys`].
|
|
30
|
+
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
|
|
31
|
+
pub struct ColumnConfigSchema {
|
|
32
|
+
pub fields: Vec<ControlSpec>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl ColumnConfigSchema {
|
|
36
|
+
/// Union of every JSON key any control in this schema knows how to
|
|
37
|
+
/// read or write. Used to build the schema-filtered view of
|
|
38
|
+
/// `columns_config` passed to `plugin.restore()` — keys not in this
|
|
39
|
+
/// set are "ghost" state from a different plugin and stay invisible
|
|
40
|
+
/// to the active one.
|
|
41
|
+
pub fn active_keys(&self) -> HashSet<String> {
|
|
42
|
+
let mut out = HashSet::new();
|
|
43
|
+
for spec in &self.fields {
|
|
44
|
+
for k in spec.serialized_keys() {
|
|
45
|
+
out.insert(k.to_string());
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
out
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/// Discriminated union of widget kinds the viewer can render. Composite
|
|
53
|
+
/// variants wrap an existing rich Yew component and carry only the
|
|
54
|
+
/// component's `*DefaultConfig`. Primitive variants render generic scalar
|
|
55
|
+
/// widgets and carry their own `key` inline; the visible label is
|
|
56
|
+
/// resolved at CSS time via `--psp-label--<key>--content`.
|
|
57
|
+
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
58
|
+
#[serde(tag = "kind")]
|
|
59
|
+
pub enum ControlSpec {
|
|
60
|
+
Enum {
|
|
61
|
+
key: String,
|
|
62
|
+
variants: Vec<EnumVariant>,
|
|
63
|
+
default: String,
|
|
64
|
+
},
|
|
65
|
+
Bool {
|
|
66
|
+
key: String,
|
|
67
|
+
default: bool,
|
|
68
|
+
},
|
|
69
|
+
Number {
|
|
70
|
+
key: String,
|
|
71
|
+
default: f64,
|
|
72
|
+
|
|
73
|
+
/// If `true`, always serialize this values even if it is the default.
|
|
74
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
75
|
+
include: Option<bool>,
|
|
76
|
+
|
|
77
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
78
|
+
min: Option<f64>,
|
|
79
|
+
|
|
80
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
81
|
+
max: Option<f64>,
|
|
82
|
+
|
|
83
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
84
|
+
step: Option<f64>,
|
|
85
|
+
},
|
|
86
|
+
String {
|
|
87
|
+
key: String,
|
|
88
|
+
default: String,
|
|
89
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
90
|
+
placeholder: Option<String>,
|
|
91
|
+
},
|
|
92
|
+
Color {
|
|
93
|
+
key: String,
|
|
94
|
+
default: String,
|
|
95
|
+
},
|
|
96
|
+
/// Paired pos/neg color picker rendered as a single horizontal
|
|
97
|
+
/// gradient/range bar. Used to expose the existing
|
|
98
|
+
/// [`crate::components::form::color_range_selector::ColorRangeSelector`]
|
|
99
|
+
/// widget at primitive granularity. Owns two top-level keys
|
|
100
|
+
/// (`key_pos` + `key_neg`); the visible label is derived from
|
|
101
|
+
/// `key_pos`.
|
|
102
|
+
ColorRange {
|
|
103
|
+
key_pos: String,
|
|
104
|
+
key_neg: String,
|
|
105
|
+
default_pos: String,
|
|
106
|
+
default_neg: String,
|
|
107
|
+
/// When `true`, the bar renders as a continuous gradient
|
|
108
|
+
/// (e.g. for `gradient` color modes); when `false`, the bar
|
|
109
|
+
/// renders as a hard pos/neg split. Currently only changes the
|
|
110
|
+
/// visual; pos/neg semantics are unchanged.
|
|
111
|
+
#[serde(default)]
|
|
112
|
+
is_gradient: bool,
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/// Residual format-only widget for datetime columns — owns
|
|
116
|
+
/// `date_format` only. Pair with primitive `Enum` and `Color` fields
|
|
117
|
+
/// for `datetime_color_mode` + `color` to fully decompose datetime
|
|
118
|
+
/// styling.
|
|
119
|
+
DatetimeFormat,
|
|
120
|
+
/// Residual format-only widget for string columns — owns `format`
|
|
121
|
+
/// only. Pair with primitive `Enum` and `Color` fields for
|
|
122
|
+
/// `string_color_mode` + `color` to fully decompose string styling.
|
|
123
|
+
StringFormat,
|
|
124
|
+
NumberSeriesStyle {
|
|
125
|
+
default: NumberSeriesStyleDefaultConfig,
|
|
126
|
+
},
|
|
127
|
+
Symbols {
|
|
128
|
+
default: KeyValueOpts,
|
|
129
|
+
},
|
|
130
|
+
NumberFormat,
|
|
131
|
+
AggregateDepth,
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
|
|
135
|
+
pub struct EnumVariant {
|
|
136
|
+
pub value: String,
|
|
137
|
+
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
138
|
+
pub label: Option<String>,
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
impl ControlSpec {
|
|
142
|
+
/// Top-level JSON keys this control owns when its value is serialized
|
|
143
|
+
/// into a column's config map. For primitives this is just `[key]`;
|
|
144
|
+
/// for composites it's the set of fields the wrapped sub-struct
|
|
145
|
+
/// flattens. Used by [`ColumnConfigSchema::active_keys`] to filter the
|
|
146
|
+
/// `columns_config` blob passed to `plugin.restore()`.
|
|
147
|
+
pub fn serialized_keys(&self) -> Vec<&str> {
|
|
148
|
+
match self {
|
|
149
|
+
ControlSpec::DatetimeFormat => vec!["date_format"],
|
|
150
|
+
ControlSpec::StringFormat => vec!["format"],
|
|
151
|
+
ControlSpec::NumberSeriesStyle { .. } => vec!["chart_type", "stack"],
|
|
152
|
+
ControlSpec::Symbols { .. } => vec!["symbols"],
|
|
153
|
+
ControlSpec::NumberFormat => vec!["number_format"],
|
|
154
|
+
ControlSpec::AggregateDepth => vec!["aggregate_depth"],
|
|
155
|
+
ControlSpec::ColorRange {
|
|
156
|
+
key_pos, key_neg, ..
|
|
157
|
+
} => vec![key_pos.as_str(), key_neg.as_str()],
|
|
158
|
+
ControlSpec::Enum { key, .. }
|
|
159
|
+
| ControlSpec::Bool { key, .. }
|
|
160
|
+
| ControlSpec::Number { key, .. }
|
|
161
|
+
| ControlSpec::String { key, .. }
|
|
162
|
+
| ControlSpec::Color { key, .. } => vec![key.as_str()],
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/// One UI-emitted change to a single schema field. The emitting widget
|
|
168
|
+
/// declares which top-level keys the update is allowed to write
|
|
169
|
+
/// (`keys` — equivalent to the field's [`ControlSpec::serialized_keys`])
|
|
170
|
+
/// and a partial new sub-state (`value`).
|
|
171
|
+
///
|
|
172
|
+
/// Apply semantics: keys in `keys` are *cleared* from the column's config
|
|
173
|
+
/// map, then keys present in `value` are *inserted*. Defaults are
|
|
174
|
+
/// pre-stripped by the caller (typically via `skip_serializing_if`), so
|
|
175
|
+
/// "no value set for key K" means the schema default applies and K is
|
|
176
|
+
/// not serialized.
|
|
177
|
+
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
178
|
+
pub struct ColumnConfigFieldUpdate {
|
|
179
|
+
pub keys: Vec<String>,
|
|
180
|
+
pub value: serde_json::Map<String, Value>,
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/// Filter a per-column config map to only the keys advertised by the
|
|
184
|
+
/// active plugin's schema. Foreign keys (left over from a previous plugin)
|
|
185
|
+
/// stay in the unfiltered presentation state but never reach `restore()`.
|
|
186
|
+
pub fn filter_to_schema(
|
|
187
|
+
config: &serde_json::Map<String, Value>,
|
|
188
|
+
active_keys: &HashSet<String>,
|
|
189
|
+
) -> serde_json::Map<String, Value> {
|
|
190
|
+
config
|
|
191
|
+
.iter()
|
|
192
|
+
.filter(|(k, _)| active_keys.contains(k.as_str()))
|
|
193
|
+
.map(|(k, v)| (k.clone(), v.clone()))
|
|
194
|
+
.collect()
|
|
195
|
+
}
|