@perspective-dev/viewer 4.5.2 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer.js +20 -1
- package/dist/cdn/perspective-viewer.js.map +4 -4
- package/dist/css/botanical.css +1 -1
- package/dist/css/dracula.css +1 -1
- package/dist/css/gruvbox-dark.css +1 -1
- package/dist/css/gruvbox.css +1 -1
- package/dist/css/icons.css +1 -1
- package/dist/css/monokai.css +1 -1
- package/dist/css/phosphor.css +1 -1
- package/dist/css/pro-dark.css +1 -1
- package/dist/css/pro.css +1 -1
- package/dist/css/solarized-dark.css +1 -1
- package/dist/css/solarized.css +1 -1
- package/dist/css/themes.css +1 -1
- package/dist/css/vaporwave.css +1 -1
- package/dist/esm/extensions.d.ts +22 -3
- package/dist/esm/perspective-viewer.d.ts +9 -0
- package/dist/esm/perspective-viewer.inline.js +20 -1
- package/dist/esm/perspective-viewer.inline.js.map +4 -4
- package/dist/esm/perspective-viewer.js +20 -1
- package/dist/esm/perspective-viewer.js.map +4 -4
- package/dist/esm/plugin.d.ts +71 -5
- package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
- package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
- package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
- package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
- package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
- package/dist/esm/ts-rs/Layout.d.ts +11 -0
- package/dist/esm/ts-rs/Orientation.d.ts +4 -0
- package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
- package/dist/esm/ts-rs/PanelViewerConfig.d.ts +39 -0
- package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
- package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
- package/dist/esm/ts-rs/ViewerConfig.d.ts +6 -2
- package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
- package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
- package/dist/wasm/perspective-viewer.d.ts +211 -77
- package/dist/wasm/perspective-viewer.js +528 -180
- package/dist/wasm/perspective-viewer.wasm +0 -0
- package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
- package/dist/wasm/snippets/perspective-viewer-daeef4ceb066e6c7/inline0.js +6 -0
- package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-daeef4ceb066e6c7/inline4.js} +5 -0
- package/package.json +3 -2
- package/src/css/_context_menu.css +14 -0
- package/src/css/column-settings-panel.css +1 -1
- package/src/css/containers/context-menu.css +101 -0
- package/src/css/panel-tab.css +193 -0
- package/src/css/plugin-selector.css +7 -10
- package/src/css/status-bar.css +75 -81
- package/src/css/viewer.css +180 -70
- package/src/rust/components/column_dropdown.rs +40 -30
- package/src/rust/components/column_selector/active_column.rs +7 -18
- package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
- package/src/rust/components/column_selector/config_selector.rs +33 -81
- package/src/rust/components/column_selector/filter_column.rs +5 -15
- package/src/rust/components/column_selector/inactive_column.rs +3 -8
- package/src/rust/components/column_selector/sort_column.rs +3 -6
- package/src/rust/components/column_selector.rs +5 -11
- package/src/rust/components/containers/split_panel.rs +175 -62
- package/src/rust/components/context_menu.rs +130 -0
- package/src/rust/components/form/debug.rs +3 -0
- package/src/rust/components/global_filter_bar.rs +76 -0
- package/src/rust/components/main_panel/frame_theme.rs +186 -0
- package/src/rust/components/main_panel/msg.rs +62 -0
- package/src/rust/components/main_panel/presize.rs +220 -0
- package/src/rust/components/main_panel/reconcile.rs +224 -0
- package/src/rust/components/main_panel/render.rs +385 -0
- package/src/rust/components/main_panel/update.rs +184 -0
- package/src/rust/components/main_panel.rs +311 -71
- package/src/rust/components/mod.rs +4 -0
- package/src/rust/components/panel_menu.rs +486 -0
- package/src/rust/components/panel_tab.rs +505 -0
- package/src/rust/components/plugin_tab.rs +1 -1
- package/src/rust/components/settings_panel.rs +13 -5
- package/src/rust/components/status_bar.rs +110 -178
- package/src/rust/components/status_indicator.rs +6 -9
- package/src/rust/components/style/mod.rs +5 -0
- package/src/rust/components/style/style_provider.rs +2 -0
- package/src/rust/components/viewer/filters.rs +133 -0
- package/src/rust/components/viewer/msg.rs +180 -0
- package/src/rust/components/viewer/panels.rs +326 -0
- package/src/rust/components/viewer/render.rs +243 -0
- package/src/rust/components/viewer/settings.rs +402 -0
- package/src/rust/components/viewer/snapshots.rs +134 -0
- package/src/rust/components/viewer/wiring.rs +505 -0
- package/src/rust/components/viewer.rs +243 -756
- package/src/rust/config/export_method.rs +1 -1
- package/src/rust/config/mod.rs +4 -0
- package/src/rust/config/options.rs +71 -0
- package/src/rust/config/viewer_config.rs +24 -2
- package/src/rust/config/workspace_config.rs +92 -0
- package/src/rust/custom_elements/copy_dropdown.rs +3 -1
- package/src/rust/custom_elements/debug_plugin.rs +20 -1
- package/src/rust/custom_elements/export_dropdown.rs +3 -1
- package/src/rust/custom_elements/viewer.rs +1015 -286
- package/src/rust/custom_events.rs +212 -61
- package/src/rust/js/mod.rs +2 -0
- package/src/rust/js/plugin.rs +297 -10
- package/src/rust/js/regular_layout.rs +258 -0
- package/src/rust/lib.rs +20 -1
- package/src/rust/presentation.rs +66 -33
- package/src/rust/queries/get_viewer_config.rs +29 -11
- package/src/rust/queries/plugin_column_styles.rs +2 -2
- package/src/rust/renderer/activate.rs +41 -14
- package/src/rust/renderer/dispatch.rs +436 -0
- package/src/rust/renderer/plugin_config.rs +495 -0
- package/src/rust/renderer/registry.rs +18 -6
- package/src/rust/renderer/render_timer.rs +29 -10
- package/src/rust/renderer.rs +350 -837
- package/src/rust/session/column_defaults_update.rs +26 -8
- package/src/rust/session/view_subscription.rs +30 -15
- package/src/rust/session.rs +753 -264
- package/src/rust/tasks/apply_global_filters.rs +89 -0
- package/src/rust/tasks/auto_pause.rs +171 -0
- package/src/rust/tasks/copy_export.rs +29 -19
- package/src/rust/tasks/create_panel.rs +243 -0
- package/src/rust/tasks/dismiss_render_warning.rs +3 -1
- package/src/rust/tasks/edit_expression.rs +4 -4
- package/src/rust/tasks/eject.rs +64 -23
- package/src/rust/tasks/mod.rs +28 -4
- package/src/rust/tasks/pipeline.rs +411 -0
- package/src/rust/tasks/presize_panels.rs +324 -0
- package/src/rust/tasks/reset_all.rs +22 -12
- package/src/rust/tasks/resize_observer.rs +124 -87
- package/src/rust/tasks/restore_and_render.rs +80 -86
- package/src/rust/tasks/restore_panel.rs +145 -0
- package/src/rust/tasks/send_column_config.rs +5 -2
- package/src/rust/tasks/send_plugin_config.rs +5 -2
- package/src/rust/tasks/set_edit_mode.rs +59 -0
- package/src/rust/tasks/sync_update_panels.rs +152 -0
- package/src/rust/tasks/table_lifecycle.rs +158 -0
- package/src/rust/tasks/update_theme.rs +56 -12
- package/src/rust/utils/completion.rs +56 -0
- package/src/rust/utils/custom_element.rs +5 -0
- package/src/rust/utils/debounce.rs +130 -7
- package/src/rust/utils/mod.rs +6 -0
- package/src/rust/utils/pending_effects.rs +62 -0
- package/src/rust/utils/spawn.rs +33 -0
- package/src/rust/utils/weak_scope.rs +0 -10
- package/src/rust/workspace.rs +846 -0
- package/src/svg/active-indicator.svg +3 -0
- package/src/themes/botanical.css +5 -2
- package/src/themes/defaults.css +54 -2
- package/src/themes/dracula.css +6 -3
- package/src/themes/gruvbox-dark.css +5 -2
- package/src/themes/gruvbox.css +5 -2
- package/src/themes/icons.css +4 -2
- package/src/themes/monokai.css +5 -2
- package/src/themes/phosphor.css +5 -2
- package/src/themes/pro-dark.css +5 -2
- package/src/themes/pro.css +2 -1
- package/src/themes/solarized-dark.css +5 -2
- package/src/themes/solarized.css +5 -2
- package/src/themes/vaporwave.css +5 -2
- package/src/ts/bootstrap.ts +8 -7
- package/src/ts/extensions.ts +30 -2
- package/src/ts/perspective-viewer.ts +10 -0
- package/src/ts/plugin.ts +72 -5
- package/src/ts/ts-rs/ClientOptions.ts +7 -0
- package/src/ts/ts-rs/ExportMethod.ts +3 -0
- package/src/ts/ts-rs/ExportOptions.ts +8 -0
- package/src/ts/ts-rs/GetClientOptions.ts +7 -0
- package/src/ts/ts-rs/GetTableOptions.ts +7 -0
- package/src/ts/ts-rs/Layout.ts +9 -0
- package/src/ts/ts-rs/Orientation.ts +6 -0
- package/src/ts/ts-rs/PanelOptions.ts +7 -0
- package/src/ts/ts-rs/PanelViewerConfig.ts +15 -0
- package/src/ts/ts-rs/SplitLayout.ts +5 -0
- package/src/ts/ts-rs/TabLayout.ts +11 -0
- package/src/ts/ts-rs/ViewerConfig.ts +6 -2
- package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
- package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
- package/src/rust/tasks/intersection_observer.rs +0 -98
- package/src/rust/tasks/update_and_render.rs +0 -110
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline0.js → perspective-viewer-daeef4ceb066e6c7/inline1.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-daeef4ceb066e6c7/inline2.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-daeef4ceb066e6c7/inline3.js} +0 -0
- /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-daeef4ceb066e6c7/inline5.js} +0 -0
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
use perspective_client::config::Filter;
|
|
13
14
|
use wasm_bindgen_futures::spawn_local;
|
|
14
15
|
use web_sys::*;
|
|
15
16
|
use yew::prelude::*;
|
|
@@ -18,6 +19,7 @@ use super::status_indicator::StatusIndicator;
|
|
|
18
19
|
use crate::components::containers::select::*;
|
|
19
20
|
use crate::components::copy_dropdown::CopyDropDownMenu;
|
|
20
21
|
use crate::components::export_dropdown::ExportDropDownMenu;
|
|
22
|
+
use crate::components::global_filter_bar::GlobalFilterBar;
|
|
21
23
|
use crate::components::portal::PortalModal;
|
|
22
24
|
use crate::components::status_bar_counter::StatusBarRowsCounter;
|
|
23
25
|
use crate::components::style::StyleSurface;
|
|
@@ -28,6 +30,7 @@ use crate::renderer::*;
|
|
|
28
30
|
use crate::session::*;
|
|
29
31
|
use crate::tasks::*;
|
|
30
32
|
use crate::utils::*;
|
|
33
|
+
use crate::workspace::Workspace;
|
|
31
34
|
use crate::*;
|
|
32
35
|
|
|
33
36
|
#[derive(Clone, Properties)]
|
|
@@ -38,10 +41,6 @@ pub struct StatusBarProps {
|
|
|
38
41
|
/// Fired when the reset button is clicked.
|
|
39
42
|
pub on_reset: Callback<bool>,
|
|
40
43
|
|
|
41
|
-
/// Fires when the settings button is clicked
|
|
42
|
-
#[prop_or_default]
|
|
43
|
-
pub on_settings: Option<Callback<()>>,
|
|
44
|
-
|
|
45
44
|
/// Snapshots threaded from root. Component reads `has_table`, `stats`,
|
|
46
45
|
/// `error`, `title` from session_props; `selected_theme`,
|
|
47
46
|
/// `available_themes`, `is_workspace` from presentation_props.
|
|
@@ -56,10 +55,25 @@ pub struct StatusBarProps {
|
|
|
56
55
|
/// In-flight render counter, threaded to `StatusIndicator`.
|
|
57
56
|
pub update_count: u32,
|
|
58
57
|
|
|
58
|
+
/// Element-level global filters (fed by master/detail selection); rendered
|
|
59
|
+
/// as removable chips between the row stats and the menu icons.
|
|
60
|
+
pub global_filters: Vec<Filter>,
|
|
61
|
+
|
|
62
|
+
/// Remove the global filter at this index (a chip's ×).
|
|
63
|
+
pub on_remove_global_filter: Callback<usize>,
|
|
64
|
+
|
|
65
|
+
/// Clear all global filters (the "Clear" affordance).
|
|
66
|
+
pub on_clear_global_filters: Callback<()>,
|
|
67
|
+
|
|
59
68
|
// State
|
|
60
69
|
pub session: Session,
|
|
61
70
|
pub renderer: Renderer,
|
|
62
71
|
pub presentation: Presentation,
|
|
72
|
+
|
|
73
|
+
/// The multi-panel model, so a theme change can restyle EVERY panel (not
|
|
74
|
+
/// just the active one this status bar targets) — non-active panels that
|
|
75
|
+
/// inherit the host theme otherwise render stale CSS until they redraw.
|
|
76
|
+
pub workspace: Workspace,
|
|
63
77
|
}
|
|
64
78
|
|
|
65
79
|
impl PartialEq for StatusBarProps {
|
|
@@ -69,6 +83,7 @@ impl PartialEq for StatusBarProps {
|
|
|
69
83
|
&& self.presentation_props == other.presentation_props
|
|
70
84
|
&& self.is_settings_open == other.is_settings_open
|
|
71
85
|
&& self.update_count == other.update_count
|
|
86
|
+
&& self.global_filters == other.global_filters
|
|
72
87
|
}
|
|
73
88
|
}
|
|
74
89
|
|
|
@@ -78,25 +93,17 @@ pub enum StatusBarMsg {
|
|
|
78
93
|
Copy,
|
|
79
94
|
CloseExport,
|
|
80
95
|
CloseCopy,
|
|
81
|
-
Noop,
|
|
82
96
|
Eject,
|
|
83
97
|
SetTheme(String),
|
|
84
98
|
ResetTheme,
|
|
85
99
|
PointerEvent(web_sys::PointerEvent),
|
|
86
|
-
TitleInputEvent,
|
|
87
|
-
TitleChangeEvent,
|
|
88
100
|
}
|
|
89
101
|
|
|
90
102
|
/// A toolbar with buttons, and `Table` & `View` status information.
|
|
91
103
|
pub struct StatusBar {
|
|
92
104
|
copy_ref: NodeRef,
|
|
93
105
|
export_ref: NodeRef,
|
|
94
|
-
input_ref: NodeRef,
|
|
95
106
|
statusbar_ref: NodeRef,
|
|
96
|
-
/// Local title tracks the live `<input>` value before the user commits the
|
|
97
|
-
/// change (blur / Enter). Reset to the prop value whenever the prop
|
|
98
|
-
/// changes.
|
|
99
|
-
title: Option<String>,
|
|
100
107
|
copy_target: Option<HtmlElement>,
|
|
101
108
|
export_target: Option<HtmlElement>,
|
|
102
109
|
}
|
|
@@ -105,127 +112,76 @@ impl Component for StatusBar {
|
|
|
105
112
|
type Message = StatusBarMsg;
|
|
106
113
|
type Properties = StatusBarProps;
|
|
107
114
|
|
|
108
|
-
fn create(
|
|
115
|
+
fn create(_ctx: &Context<Self>) -> Self {
|
|
109
116
|
Self {
|
|
110
117
|
copy_ref: NodeRef::default(),
|
|
111
118
|
export_ref: NodeRef::default(),
|
|
112
|
-
input_ref: NodeRef::default(),
|
|
113
119
|
statusbar_ref: NodeRef::default(),
|
|
114
|
-
title: ctx.props().session_props.title.clone(),
|
|
115
120
|
copy_target: None,
|
|
116
121
|
export_target: None,
|
|
117
122
|
}
|
|
118
123
|
}
|
|
119
124
|
|
|
120
|
-
fn changed(&mut self, ctx: &Context<Self>, old_props: &Self::Properties) -> bool {
|
|
121
|
-
// Keep the local title in sync with the prop whenever the session title
|
|
122
|
-
// changes externally (e.g. restore() call) or the settings panel opens /
|
|
123
|
-
// closes (which resets the input element).
|
|
124
|
-
if ctx.props().session_props.title != old_props.session_props.title
|
|
125
|
-
|| ctx.props().is_settings_open != old_props.is_settings_open
|
|
126
|
-
{
|
|
127
|
-
self.title = ctx.props().session_props.title.clone();
|
|
128
|
-
}
|
|
129
|
-
true
|
|
130
|
-
}
|
|
131
|
-
|
|
132
125
|
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
self.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
|
|
184
|
-
let title = elem.value();
|
|
185
|
-
let title = if title.trim().is_empty() {
|
|
186
|
-
None
|
|
187
|
-
} else {
|
|
188
|
-
Some(title)
|
|
189
|
-
};
|
|
190
|
-
|
|
191
|
-
self.title = title;
|
|
192
|
-
true
|
|
193
|
-
},
|
|
194
|
-
StatusBarMsg::TitleChangeEvent => {
|
|
195
|
-
let elem = self.input_ref.cast::<HtmlInputElement>().into_apierror()?;
|
|
196
|
-
let title = elem.value();
|
|
197
|
-
let title = if title.trim().is_empty() {
|
|
198
|
-
None
|
|
199
|
-
} else {
|
|
200
|
-
Some(title)
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
ctx.props().session.set_title(title);
|
|
204
|
-
false
|
|
205
|
-
},
|
|
206
|
-
StatusBarMsg::PointerEvent(event) => {
|
|
207
|
-
if event.target().map(JsValue::from)
|
|
208
|
-
== self.statusbar_ref.cast::<HtmlElement>().map(JsValue::from)
|
|
209
|
-
{
|
|
210
|
-
ctx.props().presentation.statusbar_pointer_event.emit(event);
|
|
211
|
-
}
|
|
126
|
+
match msg {
|
|
127
|
+
StatusBarMsg::Reset(event) => {
|
|
128
|
+
let all = event.shift_key();
|
|
129
|
+
ctx.props().on_reset.emit(all);
|
|
130
|
+
false
|
|
131
|
+
},
|
|
132
|
+
StatusBarMsg::ResetTheme => {
|
|
133
|
+
update_theme(
|
|
134
|
+
&ctx.props().renderer,
|
|
135
|
+
&ctx.props().presentation,
|
|
136
|
+
&ctx.props().workspace,
|
|
137
|
+
None,
|
|
138
|
+
);
|
|
139
|
+
true
|
|
140
|
+
},
|
|
141
|
+
StatusBarMsg::SetTheme(theme_name) => {
|
|
142
|
+
update_theme(
|
|
143
|
+
&ctx.props().renderer,
|
|
144
|
+
&ctx.props().presentation,
|
|
145
|
+
&ctx.props().workspace,
|
|
146
|
+
Some(theme_name),
|
|
147
|
+
);
|
|
148
|
+
false
|
|
149
|
+
},
|
|
150
|
+
StatusBarMsg::Export => {
|
|
151
|
+
self.export_target = self.export_ref.cast::<HtmlElement>();
|
|
152
|
+
true
|
|
153
|
+
},
|
|
154
|
+
StatusBarMsg::Copy => {
|
|
155
|
+
self.copy_target = self.copy_ref.cast::<HtmlElement>();
|
|
156
|
+
true
|
|
157
|
+
},
|
|
158
|
+
StatusBarMsg::CloseExport => {
|
|
159
|
+
self.export_target = None;
|
|
160
|
+
true
|
|
161
|
+
},
|
|
162
|
+
StatusBarMsg::CloseCopy => {
|
|
163
|
+
self.copy_target = None;
|
|
164
|
+
true
|
|
165
|
+
},
|
|
166
|
+
StatusBarMsg::Eject => {
|
|
167
|
+
ctx.props().presentation.on_eject.emit(());
|
|
168
|
+
false
|
|
169
|
+
},
|
|
170
|
+
StatusBarMsg::PointerEvent(event) => {
|
|
171
|
+
if event.target().map(JsValue::from)
|
|
172
|
+
== self.statusbar_ref.cast::<HtmlElement>().map(JsValue::from)
|
|
173
|
+
{
|
|
174
|
+
ctx.props().presentation.statusbar_pointer_event.emit(event);
|
|
175
|
+
}
|
|
212
176
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
})();
|
|
217
|
-
r.unwrap_or_else(|e| {
|
|
218
|
-
web_sys::console::warn_1(&e.into());
|
|
219
|
-
Default::default()
|
|
220
|
-
})
|
|
177
|
+
false
|
|
178
|
+
},
|
|
179
|
+
}
|
|
221
180
|
}
|
|
222
181
|
|
|
223
182
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
224
183
|
let Self::Properties {
|
|
225
|
-
|
|
226
|
-
renderer,
|
|
227
|
-
session,
|
|
228
|
-
..
|
|
184
|
+
renderer, session, ..
|
|
229
185
|
} = ctx.props();
|
|
230
186
|
|
|
231
187
|
let has_table = ctx.props().session_props.has_table.clone();
|
|
@@ -234,12 +190,8 @@ impl Component for StatusBar {
|
|
|
234
190
|
let title = &ctx.props().session_props.title;
|
|
235
191
|
|
|
236
192
|
let mut is_updating_class_name = classes!();
|
|
237
|
-
if title.is_some() {
|
|
238
|
-
is_updating_class_name.push("titled");
|
|
239
|
-
};
|
|
240
|
-
|
|
241
193
|
if !is_settings_open {
|
|
242
|
-
is_updating_class_name.push(
|
|
194
|
+
is_updating_class_name.push("settings-closed");
|
|
243
195
|
};
|
|
244
196
|
|
|
245
197
|
if !matches!(has_table, Some(TableLoadState::Loaded)) {
|
|
@@ -247,34 +199,31 @@ impl Component for StatusBar {
|
|
|
247
199
|
}
|
|
248
200
|
|
|
249
201
|
// TODO Memoizing these would reduce some vdom diffing later on
|
|
250
|
-
let onblur = ctx.link().callback(|_| StatusBarMsg::Noop);
|
|
251
202
|
let onclose = ctx.link().callback(|_| StatusBarMsg::Eject);
|
|
252
203
|
let onpointerdown = ctx.link().callback(StatusBarMsg::PointerEvent);
|
|
253
204
|
let onexport = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Export);
|
|
254
205
|
let oncopy = ctx.link().callback(|_: MouseEvent| StatusBarMsg::Copy);
|
|
255
206
|
let onreset = ctx.link().callback(StatusBarMsg::Reset);
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|| is_errored
|
|
276
|
-
|| is_settings_open
|
|
277
|
-
|| presentation.is_active(&self.input_ref.cast::<Element>());
|
|
207
|
+
|
|
208
|
+
// Project only the *active* panel's plugin toolbar into the shared status
|
|
209
|
+
// bar. Each panel's toolbar slots into `statusbar-extra-{its-panel-id}`
|
|
210
|
+
// (see datagrid `toolbar.ts`); the active panel's id comes from the
|
|
211
|
+
// active renderer this status bar is bound to.
|
|
212
|
+
let extra_slot = ctx
|
|
213
|
+
.props()
|
|
214
|
+
.renderer
|
|
215
|
+
.slot_name()
|
|
216
|
+
.map(|id| format!("statusbar-extra-{id}"))
|
|
217
|
+
.unwrap_or_else(|| "statusbar-extra".to_owned());
|
|
218
|
+
let is_menu = matches!(has_table, Some(TableLoadState::Loaded)) && is_settings_open;
|
|
219
|
+
// The editable title moved to the per-panel `<PanelTab>` headers; the
|
|
220
|
+
// status bar no longer hosts an `<input>`, so its `is_active` factor is
|
|
221
|
+
// gone. `is_title` now only gates the row counter.
|
|
222
|
+
let is_title =
|
|
223
|
+
is_menu || ctx.props().presentation_props.is_workspace || title.is_some() || is_errored;
|
|
224
|
+
|
|
225
|
+
let is_settings =
|
|
226
|
+
!matches!(has_table, Some(TableLoadState::Loaded)) || is_errored || is_settings_open;
|
|
278
227
|
|
|
279
228
|
let on_copy_select = {
|
|
280
229
|
let props = ctx.props().clone();
|
|
@@ -344,26 +293,18 @@ impl Component for StatusBar {
|
|
|
344
293
|
update_count={ctx.props().update_count}
|
|
345
294
|
session_props={ctx.props().session_props.clone()}
|
|
346
295
|
/>
|
|
347
|
-
if is_title {
|
|
348
|
-
<label
|
|
349
|
-
class="input-sizer"
|
|
350
|
-
data-value={self.title.clone().unwrap_or_default()}
|
|
351
|
-
>
|
|
352
|
-
<input
|
|
353
|
-
ref={&self.input_ref}
|
|
354
|
-
placeholder=""
|
|
355
|
-
value={self.title.clone().unwrap_or_default()}
|
|
356
|
-
size="10"
|
|
357
|
-
{onblur}
|
|
358
|
-
{onchange}
|
|
359
|
-
{oninput}
|
|
360
|
-
/>
|
|
361
|
-
<span id="status-bar-placeholder" />
|
|
362
|
-
</label>
|
|
363
|
-
}
|
|
364
296
|
if is_title {
|
|
365
297
|
<StatusBarRowsCounter stats={ctx.props().session_props.stats.clone()} />
|
|
366
298
|
}
|
|
299
|
+
// Global-filter chips sit between the row stats and the
|
|
300
|
+
// menu icons (rendered only when there are filters).
|
|
301
|
+
if !ctx.props().global_filters.is_empty() {
|
|
302
|
+
<GlobalFilterBar
|
|
303
|
+
filters={ctx.props().global_filters.clone()}
|
|
304
|
+
on_remove={ctx.props().on_remove_global_filter.clone()}
|
|
305
|
+
on_clear={ctx.props().on_clear_global_filters.clone()}
|
|
306
|
+
/>
|
|
307
|
+
}
|
|
367
308
|
<div id="spacer" />
|
|
368
309
|
if is_menu {
|
|
369
310
|
<div id="menu-bar" class="section">
|
|
@@ -373,7 +314,7 @@ impl Component for StatusBar {
|
|
|
373
314
|
on_change={ctx.link().callback(StatusBarMsg::SetTheme)}
|
|
374
315
|
on_reset={ctx.link().callback(|_| StatusBarMsg::ResetTheme)}
|
|
375
316
|
/>
|
|
376
|
-
<div id="plugin-settings"><slot name=
|
|
317
|
+
<div id="plugin-settings"><slot name={extra_slot} /></div>
|
|
377
318
|
<span class="hover-target">
|
|
378
319
|
<span id="reset" class="button" onmousedown={&onreset}>
|
|
379
320
|
<span class="icon shift-alt-icon" />
|
|
@@ -402,14 +343,7 @@ impl Component for StatusBar {
|
|
|
402
343
|
</span>
|
|
403
344
|
</div>
|
|
404
345
|
}
|
|
405
|
-
if
|
|
406
|
-
<div
|
|
407
|
-
id="settings_button"
|
|
408
|
-
class="noselect"
|
|
409
|
-
onmousedown={x.reform(|_| ())}
|
|
410
|
-
>
|
|
411
|
-
<span class="icon" />
|
|
412
|
-
</div>
|
|
346
|
+
if !is_settings_open {
|
|
413
347
|
<div id="close_button" class="noselect" onmousedown={onclose}>
|
|
414
348
|
<span class="icon" />
|
|
415
349
|
</div>
|
|
@@ -441,18 +375,16 @@ impl Component for StatusBar {
|
|
|
441
375
|
</PortalModal>
|
|
442
376
|
</>
|
|
443
377
|
}
|
|
444
|
-
} else
|
|
378
|
+
} else {
|
|
379
|
+
// Settings closed + loaded: no docked status bar. The open-settings
|
|
380
|
+
// affordance lives on the `PanelTab`s; only the (default-hidden)
|
|
381
|
+
// eject button floats here.
|
|
445
382
|
let class = classes!(is_updating_class_name, "floating");
|
|
446
383
|
html! {
|
|
447
384
|
<div id={ctx.props().id.clone()} {class}>
|
|
448
|
-
<div id="settings_button" class="noselect" onmousedown={x.reform(|_| ())}>
|
|
449
|
-
<span class="icon" />
|
|
450
|
-
</div>
|
|
451
385
|
<div id="close_button" class="noselect" onmousedown={&onclose} />
|
|
452
386
|
</div>
|
|
453
387
|
}
|
|
454
|
-
} else {
|
|
455
|
-
html! {}
|
|
456
388
|
}
|
|
457
389
|
}
|
|
458
390
|
}
|
|
@@ -17,18 +17,20 @@ use yew::prelude::*;
|
|
|
17
17
|
|
|
18
18
|
use crate::renderer::Renderer;
|
|
19
19
|
use crate::session::{Session, SessionProps, TableLoadState};
|
|
20
|
+
use crate::tasks::apply_and_render;
|
|
20
21
|
use crate::utils::*;
|
|
21
22
|
|
|
22
23
|
/// Value-prop version: no PubSub subscriptions, no reducer.
|
|
23
24
|
/// The parent (`StatusBar`) re-renders this component whenever
|
|
24
25
|
/// `session_props.error/has_table/stats` or `update_count` change (via
|
|
25
|
-
/// root's `
|
|
26
|
-
/// messages).
|
|
26
|
+
/// root's `UpdateInFlight` / `UpdateSession` messages).
|
|
27
27
|
#[derive(PartialEq, Properties)]
|
|
28
28
|
pub struct StatusIndicatorProps {
|
|
29
29
|
pub renderer: Renderer,
|
|
30
30
|
pub session: Session,
|
|
31
|
-
/// Number of in-flight
|
|
31
|
+
/// Number of in-flight CONFIG-DRIVEN runs (>0 → "updating" spinner) —
|
|
32
|
+
/// a level-triggered snapshot of `Session::in_flight_config_runs`
|
|
33
|
+
/// (RAII-settled; see `UPDATE_COUNT_REGRESSION_PLAN.md`).
|
|
32
34
|
pub update_count: u32,
|
|
33
35
|
/// Snapshot of session value props — read for `error`, `has_table`,
|
|
34
36
|
/// `stats` to derive the icon state.
|
|
@@ -81,12 +83,7 @@ pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
|
|
|
81
83
|
match &state {
|
|
82
84
|
StatusIconState::Errored(..) => {
|
|
83
85
|
session.reconnect().await?;
|
|
84
|
-
|
|
85
|
-
session.update_view_config(cfg)?;
|
|
86
|
-
renderer.apply_pending_plugin()?;
|
|
87
|
-
renderer
|
|
88
|
-
.draw(session.validate().await?.create_view())
|
|
89
|
-
.await?;
|
|
86
|
+
apply_and_render(session, renderer, ViewConfigUpdate::default())?.await?;
|
|
90
87
|
},
|
|
91
88
|
StatusIconState::Normal => {
|
|
92
89
|
session.status_indicator_clicked.emit(());
|
|
@@ -58,6 +58,9 @@ fn function_dropdown_bundle() -> &'static str {
|
|
|
58
58
|
fn dropdown_menu_bundle() -> &'static str {
|
|
59
59
|
include_str!(concat!(env!("OUT_DIR"), "/css/_dropdown_menu.css"))
|
|
60
60
|
}
|
|
61
|
+
fn context_menu_bundle() -> &'static str {
|
|
62
|
+
include_str!(concat!(env!("OUT_DIR"), "/css/_context_menu.css"))
|
|
63
|
+
}
|
|
61
64
|
|
|
62
65
|
thread_local! {
|
|
63
66
|
static VIEWER_SHEET: web_sys::CssStyleSheet = make_sheet(viewer_bundle());
|
|
@@ -65,6 +68,7 @@ thread_local! {
|
|
|
65
68
|
static FILTER_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(filter_dropdown_bundle());
|
|
66
69
|
static FUNCTION_DROPDOWN_SHEET: web_sys::CssStyleSheet = make_sheet(function_dropdown_bundle());
|
|
67
70
|
static DROPDOWN_MENU_SHEET: web_sys::CssStyleSheet = make_sheet(dropdown_menu_bundle());
|
|
71
|
+
static CONTEXT_MENU_SHEET: web_sys::CssStyleSheet = make_sheet(context_menu_bundle());
|
|
68
72
|
}
|
|
69
73
|
|
|
70
74
|
fn make_sheet(css: &str) -> web_sys::CssStyleSheet {
|
|
@@ -81,5 +85,6 @@ pub(crate) fn surface_sheet(surface: StyleSurface) -> web_sys::CssStyleSheet {
|
|
|
81
85
|
StyleSurface::FilterDropdown => FILTER_DROPDOWN_SHEET.with(|x| x.clone()),
|
|
82
86
|
StyleSurface::FunctionDropdown => FUNCTION_DROPDOWN_SHEET.with(|x| x.clone()),
|
|
83
87
|
StyleSurface::DropdownMenu => DROPDOWN_MENU_SHEET.with(|x| x.clone()),
|
|
88
|
+
StyleSurface::ContextMenu => CONTEXT_MENU_SHEET.with(|x| x.clone()),
|
|
84
89
|
}
|
|
85
90
|
}
|
|
@@ -0,0 +1,133 @@
|
|
|
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
|
+
//! Master/detail cross-filtering handlers. The filter set itself is OWNED BY
|
|
14
|
+
//! THE [`Workspace`] model (fed by master-panel selections via the
|
|
15
|
+
//! `perspective-global-filter` listener in [`super::wiring`], surfaced as
|
|
16
|
+
//! chips in the `StatusBar`); these handlers mutate it and run the
|
|
17
|
+
//! [`apply_global_filters`] task. Re-renders flow through the workspace's
|
|
18
|
+
//! `filters_changed` PubSub → `UpdateGlobalFilters` snapshot refresh.
|
|
19
|
+
//!
|
|
20
|
+
//! [`Workspace`]: crate::workspace::Workspace
|
|
21
|
+
|
|
22
|
+
use yew::prelude::*;
|
|
23
|
+
|
|
24
|
+
use super::PerspectiveViewer;
|
|
25
|
+
use super::msg::MasterSelection;
|
|
26
|
+
use crate::tasks::*;
|
|
27
|
+
use crate::workspace::PanelId;
|
|
28
|
+
|
|
29
|
+
impl PerspectiveViewer {
|
|
30
|
+
pub(super) fn on_toggle_master(&mut self, ctx: &Context<Self>, id: String) -> bool {
|
|
31
|
+
let id = PanelId::from(id);
|
|
32
|
+
let is_master = ctx.props().workspace.toggle_master(&id);
|
|
33
|
+
// Drive the panel's plugin into (out of) a selection mode so a
|
|
34
|
+
// master emits `perspective-global-filter` selections via the
|
|
35
|
+
// plugin's built-in select mechanism. No-op for plugins without
|
|
36
|
+
// an `edit_mode` (e.g. charts, which select intrinsically).
|
|
37
|
+
if let Some(panel) = ctx.props().workspace.panel(&id) {
|
|
38
|
+
let mode = if is_master {
|
|
39
|
+
"SELECT_ROW_TREE"
|
|
40
|
+
} else {
|
|
41
|
+
"READ_ONLY"
|
|
42
|
+
};
|
|
43
|
+
set_edit_mode(&panel.session, &panel.renderer, mode);
|
|
44
|
+
}
|
|
45
|
+
// Demoting a master back to detail retracts (only) its own
|
|
46
|
+
// contribution — other masters' clauses and any restored bucket
|
|
47
|
+
// survive. Promotion needs no filter mutation: the re-stamp below
|
|
48
|
+
// grants the new master immunity (and details keep filtering).
|
|
49
|
+
if !is_master {
|
|
50
|
+
ctx.props().workspace.clear_contribution(&id);
|
|
51
|
+
}
|
|
52
|
+
apply_global_filters(&ctx.props().workspace);
|
|
53
|
+
// Re-render regardless of a filter change — the context menu's
|
|
54
|
+
// "Master"/"Detail" label reads the toggled state.
|
|
55
|
+
true
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
pub(super) fn on_master_contribution(
|
|
59
|
+
&mut self,
|
|
60
|
+
ctx: &Context<Self>,
|
|
61
|
+
panel_id: String,
|
|
62
|
+
selection: Option<MasterSelection>,
|
|
63
|
+
) -> bool {
|
|
64
|
+
// A master's selection state REPLACES its per-panel contribution to
|
|
65
|
+
// the element-level global filter set (`None` = deselected = clears
|
|
66
|
+
// it) — then surfaces in the bar and applies to the details. The
|
|
67
|
+
// re-render arrives via `filters_changed` → `UpdateGlobalFilters`.
|
|
68
|
+
// Contributions are keyed by the ORIGINATING panel, so removing them
|
|
69
|
+
// from the bar can clear its selection state. Non-master events
|
|
70
|
+
// (plain detail-panel selections/clicks) don't broadcast.
|
|
71
|
+
let workspace = &ctx.props().workspace;
|
|
72
|
+
let id = PanelId::from(panel_id);
|
|
73
|
+
if !workspace.is_master(&id) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let filters = match selection {
|
|
78
|
+
None => Vec::new(),
|
|
79
|
+
Some(selection) => {
|
|
80
|
+
// Broadcast only the selection-DERIVED clauses: the event's
|
|
81
|
+
// filters embed the master's own stored `filter` field (a
|
|
82
|
+
// select detail's configs extend the panel config), which
|
|
83
|
+
// must not leak into the details — subtract it. (Live `Ref`
|
|
84
|
+
// guard — scope it to this statement.)
|
|
85
|
+
let own = workspace
|
|
86
|
+
.panel(&id)
|
|
87
|
+
.map(|p| p.session.get_view_config().filter.clone())
|
|
88
|
+
.unwrap_or_default();
|
|
89
|
+
|
|
90
|
+
let derived = selection
|
|
91
|
+
.filters
|
|
92
|
+
.into_iter()
|
|
93
|
+
.filter(|x| !own.contains(x))
|
|
94
|
+
.collect::<Vec<_>>();
|
|
95
|
+
|
|
96
|
+
if !derived.is_empty() {
|
|
97
|
+
derived
|
|
98
|
+
} else {
|
|
99
|
+
// Nothing derivable from the event's configs — fall back
|
|
100
|
+
// to the clicked-cell clause if the detail carried one,
|
|
101
|
+
// else leave the current contribution untouched.
|
|
102
|
+
match selection.cell_fallback {
|
|
103
|
+
Some(x) => vec![x],
|
|
104
|
+
None => return false,
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
workspace.set_contribution(&id, filters);
|
|
111
|
+
apply_global_filters(workspace);
|
|
112
|
+
false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pub(super) fn on_remove_global_filter(&mut self, ctx: &Context<Self>, index: usize) -> bool {
|
|
116
|
+
let workspace = &ctx.props().workspace;
|
|
117
|
+
let origins = workspace.remove_global_filter(index);
|
|
118
|
+
apply_global_filters(workspace);
|
|
119
|
+
// The chip's clause is gone — the master selection(s) that produced
|
|
120
|
+
// it must not stay highlighted, implying a filter that no longer
|
|
121
|
+
// exists. (Restored/unattributed clauses have no origin — no-op.)
|
|
122
|
+
clear_master_selections(workspace, origins);
|
|
123
|
+
false
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pub(super) fn on_clear_global_filters(&mut self, ctx: &Context<Self>) -> bool {
|
|
127
|
+
let workspace = &ctx.props().workspace;
|
|
128
|
+
let origins = workspace.clear_global_filters();
|
|
129
|
+
apply_global_filters(workspace);
|
|
130
|
+
clear_master_selections(workspace, origins);
|
|
131
|
+
false
|
|
132
|
+
}
|
|
133
|
+
}
|