perspective_viewer/components/
column_selector.rs1mod active_column;
14mod add_expression_button;
15mod aggregate_selector;
16mod config_selector;
17mod empty_column;
18mod expression_toolbar;
19mod filter_column;
20mod inactive_column;
21mod invalid_column;
22mod pivot_column;
23mod sort_column;
24
25use std::iter::*;
26use std::rc::Rc;
27
28pub use empty_column::*;
29pub use invalid_column::*;
30use web_sys::*;
31use yew::prelude::*;
32
33use self::active_column::*;
34use self::add_expression_button::AddExpressionButton;
35use self::config_selector::ConfigSelector;
36use self::inactive_column::*;
37use super::containers::scroll_panel::*;
38use super::containers::split_panel::{Orientation, SplitPanel};
39use super::style::LocalStyle;
40use super::viewer::ColumnLocator;
41use crate::components::containers::scroll_panel_item::ScrollPanelItem;
42use crate::custom_elements::ColumnDropDownElement;
43use crate::dragdrop::*;
44use crate::model::*;
45use crate::presentation::Presentation;
46use crate::renderer::*;
47use crate::session::*;
48use crate::utils::*;
49use crate::*;
50
51#[derive(Properties)]
52pub struct ColumnSelectorProps {
53 pub session: Session,
54 pub renderer: Renderer,
55 pub dragdrop: DragDrop,
56 pub presentation: Presentation,
57
58 pub on_open_expr_panel: Callback<ColumnLocator>,
59
60 pub selected_column: Option<ColumnLocator>,
62
63 #[prop_or_default]
64 pub on_resize: Option<Rc<PubSub<()>>>,
65
66 #[prop_or_default]
67 pub on_dimensions_reset: Option<Rc<PubSub<()>>>,
68}
69
70derive_model!(DragDrop, Renderer, Session for ColumnSelectorProps);
71
72impl PartialEq for ColumnSelectorProps {
73 fn eq(&self, rhs: &Self) -> bool {
74 self.selected_column == rhs.selected_column
75 }
76}
77
78#[derive(Debug)]
79pub enum ColumnSelectorMsg {
80 TableLoaded,
81 ViewCreated,
82 HoverActiveIndex(Option<usize>),
83 Drag(DragEffect),
84 DragEnd,
85 Drop((String, DragTarget, DragEffect, usize)),
86}
87
88use ColumnSelectorMsg::*;
89
90pub struct ColumnSelector {
93 _subscriptions: [Subscription; 5],
94 named_row_count: usize,
95 drag_container: DragDropContainer,
96 column_dropdown: ColumnDropDownElement,
97 on_reset: Rc<PubSub<()>>,
98}
99
100impl Component for ColumnSelector {
101 type Message = ColumnSelectorMsg;
102 type Properties = ColumnSelectorProps;
103
104 fn create(ctx: &Context<Self>) -> Self {
105 let table_sub = {
106 let cb = ctx.link().callback(|_| ColumnSelectorMsg::TableLoaded);
107 ctx.props().session.table_loaded.add_listener(cb)
108 };
109
110 let view_sub = {
111 let cb = ctx.link().callback(|_| ColumnSelectorMsg::ViewCreated);
112 ctx.props().session.view_created.add_listener(cb)
113 };
114
115 let drop_sub = {
116 let cb = ctx.link().callback(ColumnSelectorMsg::Drop);
117 ctx.props().dragdrop.drop_received.add_listener(cb)
118 };
119
120 let drag_sub = {
121 let cb = ctx.link().callback(ColumnSelectorMsg::Drag);
122 ctx.props().dragdrop.dragstart_received.add_listener(cb)
123 };
124
125 let dragend_sub = {
126 let cb = ctx.link().callback(|_| ColumnSelectorMsg::DragEnd);
127 ctx.props().dragdrop.dragend_received.add_listener(cb)
128 };
129
130 let named = maybe! {
131 let plugin =
132 ctx.props().renderer.get_active_plugin().ok()?;
133
134 Some(plugin.config_column_names()?.length() as usize)
135 };
136
137 let named_row_count = named.unwrap_or_default();
138 let drag_container = DragDropContainer::new(|| {}, {
139 let link = ctx.link().clone();
140 move || link.send_message(ColumnSelectorMsg::HoverActiveIndex(None))
141 });
142
143 let column_dropdown = ColumnDropDownElement::new(ctx.props().session.clone());
144 Self {
145 _subscriptions: [table_sub, view_sub, drop_sub, drag_sub, dragend_sub],
146 named_row_count,
147 drag_container,
148 column_dropdown,
149 on_reset: Default::default(),
150 }
151 }
152
153 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
154 match msg {
155 Drag(DragEffect::Move(DragTarget::Active)) => false,
156 Drag(_) | DragEnd | TableLoaded => true,
157 ViewCreated => {
158 let named = maybe! {
159 let plugin =
160 ctx.props().renderer.get_active_plugin().ok()?;
161
162 Some(plugin.config_column_names()?.length() as usize)
163 };
164
165 self.named_row_count = named.unwrap_or_default();
166 true
167 },
168 HoverActiveIndex(Some(to_index)) => ctx
169 .props()
170 .dragdrop
171 .notify_drag_enter(DragTarget::Active, to_index),
172 HoverActiveIndex(_) => {
173 ctx.props().dragdrop.notify_drag_leave(DragTarget::Active);
174 true
175 },
176 Drop((column, DragTarget::Active, DragEffect::Move(DragTarget::Active), index)) => {
177 if !ctx.props().is_invalid_columns_column(&column, index) {
178 let update = ctx.props().session.create_drag_drop_update(
179 column,
180 index,
181 DragTarget::Active,
182 DragEffect::Move(DragTarget::Active),
183 &ctx.props().renderer.metadata(),
184 );
185
186 if let Ok(task) = ctx.props().update_and_render(update) {
187 ApiFuture::spawn(task);
188 }
189 }
190
191 true
192 },
193 Drop((column, DragTarget::Active, effect, index)) => {
194 let update = ctx.props().session.create_drag_drop_update(
195 column,
196 index,
197 DragTarget::Active,
198 effect,
199 &ctx.props().renderer.metadata(),
200 );
201
202 if let Ok(task) = ctx.props().update_and_render(update) {
203 ApiFuture::spawn(task);
204 }
205
206 true
207 },
208 Drop((_, _, DragEffect::Move(DragTarget::Active), _)) => true,
209 Drop((..)) => true,
210 }
211 }
212
213 fn view(&self, ctx: &Context<Self>) -> Html {
214 let config = ctx.props().session.get_view_config();
215
216 let is_aggregated = config.is_aggregated();
217 let columns_iter = ctx.props().column_selector_iter_set(&config);
218 let onselect = ctx.link().callback(|()| ViewCreated);
219 let ondragenter = ctx.link().callback(HoverActiveIndex);
220 let ondragover = Callback::from(|_event: DragEvent| _event.prevent_default());
221 let ondrop = Callback::from({
222 let dragdrop = ctx.props().dragdrop.clone();
223 move |event| dragdrop.notify_drop(&event)
224 });
225
226 let ondragend = Callback::from({
227 let dragdrop = ctx.props().dragdrop.clone();
228 move |_| dragdrop.notify_drag_end()
229 });
230
231 let mut active_classes = classes!();
232 if ctx.props().dragdrop.get_drag_column().is_some() {
233 active_classes.push("dragdrop-highlight");
234 };
235
236 if is_aggregated {
237 active_classes.push("is-aggregated");
238 }
239
240 let size_hint = 28.0f64.mul_add(
241 (config.group_by.len()
242 + config.split_by.len()
243 + config.filter.len()
244 + config.sort.len()) as f64,
245 ctx.props()
246 .session
247 .metadata()
248 .get_features()
249 .map(|x| {
250 let mut y = 0.0;
251 if !x.filter_ops.is_empty() {
252 y += 1.0;
253 }
254
255 if x.group_by {
256 y += 1.0;
257 }
258
259 if x.split_by {
260 y += 1.0;
261 }
262
263 if x.sort {
264 y += 1.0;
265 }
266
267 y * 55.0
268 })
269 .unwrap_or_default(),
270 );
271
272 let config_selector = html_nested! {
273 <ScrollPanelItem key="config_selector" {size_hint}>
274 <ConfigSelector
275 dragdrop={&ctx.props().dragdrop}
276 session={&ctx.props().session}
277 renderer={&ctx.props().renderer}
278 onselect={onselect.clone()}
279 ondragenter={ctx.link().callback(|()| ViewCreated)}
280 />
281 </ScrollPanelItem>
282 };
283
284 let mut named_count = self.named_row_count;
285 let mut active_columns: Vec<_> = columns_iter
286 .active()
287 .enumerate()
288 .map(|(idx, name)| {
289 let ondragenter = ondragenter.reform(move |_| Some(idx));
290 let size_hint = if named_count > 0 { 50.0 } else { 28.0 };
291 named_count = named_count.saturating_sub(1);
292 let key = name
293 .get_name()
294 .map(|x| x.to_owned())
295 .unwrap_or_else(|| format!("__auto_{idx}__"));
296
297 let column_dropdown = self.column_dropdown.clone();
298 let is_editing = matches!(
299 &ctx.props().selected_column,
300 Some(ColumnLocator::Table(x)) | Some(ColumnLocator::Expression(x))
301 if x == &key );
302
303 let on_open_expr_panel = &ctx.props().on_open_expr_panel;
304 html_nested! {
305 <ScrollPanelItem {key} {size_hint}>
306 <ActiveColumn
307 {column_dropdown}
308 {idx}
309 {is_aggregated}
310 {is_editing}
311 {name}
312 {on_open_expr_panel}
313 dragdrop={&ctx.props().dragdrop}
314 session={&ctx.props().session}
315 renderer={&ctx.props().renderer}
316 presentation={&ctx.props().presentation}
317 {ondragenter}
318 ondragend={&ondragend}
319 onselect={&onselect}
320 />
321 </ScrollPanelItem>
322 }
323 })
324 .collect();
325
326 let mut inactive_children: Vec<_> = columns_iter
327 .expression()
328 .chain(columns_iter.inactive())
329 .enumerate()
330 .map(|(idx, vc)| {
331 let selected_column = ctx.props().selected_column.as_ref();
332 let is_editing = matches!(selected_column, Some(ColumnLocator::Expression(x)) if x.as_str() == vc.name);
333 html_nested! {
334 <ScrollPanelItem key={vc.name} size_hint=28.0>
335 <InactiveColumn
336 {idx}
337 visible={vc.is_visible}
338 name={vc.name.to_owned()}
339 dragdrop={&ctx.props().dragdrop}
340 session={&ctx.props().session}
341 renderer={&ctx.props().renderer}
342 presentation={&ctx.props().presentation}
343 {is_editing}
344 onselect={&onselect}
345 ondragend={&ondragend}
346 on_open_expr_panel={&ctx.props().on_open_expr_panel}
347 />
348 </ScrollPanelItem>
349 }
350 })
351 .collect();
352
353 let size = if !inactive_children.is_empty() {
354 56.0
355 } else {
356 28.0
357 };
358
359 let add_column = if ctx
360 .props()
361 .session
362 .metadata()
363 .get_features()
364 .unwrap()
365 .expressions
366 {
367 html_nested! {
368 <ScrollPanelItem key="__add_expression__" size_hint={size}>
369 <AddExpressionButton
370 on_open_expr_panel={&ctx.props().on_open_expr_panel}
371 selected_column={ctx.props().selected_column.clone()}
372 />
373 </ScrollPanelItem>
374 }
375 } else {
376 html_nested! {
377 <ScrollPanelItem key="__add_expression__" size_hint=0_f64><span /></ScrollPanelItem>
378 }
379 };
380
381 if inactive_children.is_empty() {
382 active_columns.push(add_column)
383 } else {
384 inactive_children.insert(0, add_column);
385 }
386
387 let selected_columns = html! {
388 <div id="selected-columns">
389 <ScrollPanel
390 id="active-columns"
391 class={active_classes}
392 dragover={ondragover}
393 dragenter={&self.drag_container.dragenter}
394 dragleave={&self.drag_container.dragleave}
395 viewport_ref={&self.drag_container.noderef}
396 drop={ondrop}
397 on_resize={&ctx.props().on_resize}
398 on_dimensions_reset={&self.on_reset}
399 children={std::iter::once(config_selector).chain(active_columns).collect::<Vec<_>>()}
400 />
401 </div>
402 };
403
404 html! {
405 <>
406 <LocalStyle href={css!("column-selector")} />
407 <SplitPanel
408 no_wrap=true
409 on_reset={self.on_reset.callback()}
410 skip_empty=true
411 orientation={Orientation::Vertical}
412 >
413 { selected_columns }
414 if !inactive_children.is_empty() {
415 <ScrollPanel
416 id="sub-columns"
417 on_resize={&ctx.props().on_resize}
418 on_dimensions_reset={&self.on_reset}
419 children={inactive_children}
420 />
421 }
422 </SplitPanel>
423 </>
424 }
425 }
426}