1use std::rc::Rc;
14
15use futures::channel::oneshot::*;
16use perspective_js::utils::*;
17use wasm_bindgen::JsCast;
18use wasm_bindgen::prelude::*;
19use web_sys::{FocusEvent, KeyboardEvent};
20use yew::prelude::*;
21
22use super::containers::split_panel::SplitPanel;
23use super::font_loader::{FontLoader, FontLoaderProps, FontLoaderStatus};
24use super::style::{LocalStyle, StyleProvider};
25use crate::components::column_settings_sidebar::ColumnSettingsPanel;
26use crate::components::main_panel::MainPanel;
27use crate::components::settings_panel::{SelectedTab, SettingsPanel};
28use crate::config::*;
29use crate::css;
30use crate::js::JsPerspectiveViewerPlugin;
31use crate::presentation::{
32 ColumnLocator, ColumnSettingsTab, DragDropProps, Presentation, PresentationProps,
33};
34use crate::queries::*;
35use crate::renderer::{RendererProps, *};
36use crate::session::{SessionProps, *};
37use crate::tasks::*;
38use crate::utils::*;
39
40#[derive(Clone, Properties)]
41pub struct PerspectiveViewerProps {
42 pub elem: web_sys::HtmlElement,
44
45 pub session: Session,
47 pub renderer: Renderer,
48 pub presentation: Presentation,
49}
50
51impl PartialEq for PerspectiveViewerProps {
52 fn eq(&self, _rhs: &Self) -> bool {
53 false
54 }
55}
56
57#[derive(Debug)]
58pub enum PerspectiveViewerMsg {
59 ColumnSettingsPanelSizeUpdate(Option<i32>),
60 ColumnSettingsTabChanged(ColumnSettingsTab),
61 OpenColumnSettings {
62 locator: Option<ColumnLocator>,
63 sender: Option<Sender<()>>,
64 toggle: bool,
65 },
66 PreloadFontsUpdate,
67 Reset(bool, Option<Sender<()>>),
68 Resize,
69 SettingsPanelSizeUpdate(Option<i32>),
70 SettingsPanelTabChanged(SelectedTab),
71 SettingsPanelAutoWidth(f64),
72 ToggleDebug,
73 ToggleSettingsComplete(SettingsUpdate, Sender<()>),
74 ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
75 UpdateSession(Box<SessionProps>),
76 UpdateRenderer(Box<RendererProps>),
77 UpdatePresentation(Box<PresentationProps>),
78
79 UpdateSettingsOpen(bool),
82 UpdateIsWorkspace(bool),
83
84 UpdateColumnSettings(Box<crate::presentation::OpenColumnSettings>),
86 UpdateDragDrop(Box<DragDropProps>),
87
88 UpdateSessionStats(Option<ViewStats>, Option<TableLoadState>),
92
93 IncrementUpdateCount,
96 DecrementUpdateCount,
97}
98
99use PerspectiveViewerMsg::*;
100
101pub struct PerspectiveViewer {
102 _subscriptions: Vec<Subscription>,
103 column_settings_panel_width_override: Option<i32>,
104 debug_open: bool,
105 fonts: FontLoaderProps,
106 on_close_column_settings: Callback<()>,
107 on_rendered: Option<Sender<()>>,
108 on_resize: Rc<PubSub<()>>,
109 on_settings_panel_dimensions_reset: Rc<PubSub<()>>,
110 settings_open: bool,
111 settings_panel_width_override: Option<i32>,
112 settings_panel_selected_tab: SelectedTab,
113 settings_panel_auto_width: f64,
114
115 session_props: SessionProps,
119 renderer_props: RendererProps,
120 presentation_props: PresentationProps,
121 dragdrop_props: DragDropProps,
122
123 update_count: u32,
126
127 _shift_listeners: ShiftListeners,
132}
133
134struct 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
141impl 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
153fn 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 }
190}
191
192impl Component for PerspectiveViewer {
193 type Message = PerspectiveViewerMsg;
194 type Properties = PerspectiveViewerProps;
195
196 fn create(ctx: &Context<Self>) -> Self {
197 let elem = ctx.props().elem.clone();
198 let fonts = FontLoaderProps::new(&elem, ctx.link().callback(|()| PreloadFontsUpdate));
199 inject_engine_callbacks(ctx);
200 let subscriptions = create_subscriptions(ctx);
201 let session_props = ctx.props().session.to_props();
202 let renderer_props = ctx.props().renderer.to_props(None);
203 let presentation_props = ctx.props().presentation.to_props(PtrEqRc::new(vec![]));
204
205 let on_close_column_settings = ctx.link().callback(|_| OpenColumnSettings {
207 locator: None,
208 sender: None,
209 toggle: false,
210 });
211
212 {
216 let presentation = ctx.props().presentation.clone();
217 let cb = ctx.link().callback(move |themes: PtrEqRc<Vec<String>>| {
218 UpdatePresentation(Box::new(presentation.to_props(themes)))
219 });
220
221 let presentation = ctx.props().presentation.clone();
222 ApiFuture::spawn(async move {
223 let themes = presentation.get_available_themes().await?;
224 cb.emit(themes);
225 Ok(())
226 });
227 }
228
229 let shift_listeners = install_shift_listeners(elem);
230
231 Self {
232 _subscriptions: subscriptions,
233 column_settings_panel_width_override: None,
234 debug_open: false,
235 fonts,
236 on_close_column_settings,
237 on_rendered: None,
238 on_resize: Default::default(),
239 on_settings_panel_dimensions_reset: Default::default(),
240 settings_open: false,
241 settings_panel_width_override: None,
242 settings_panel_selected_tab: SelectedTab::default(),
243 settings_panel_auto_width: 0.0,
244 session_props,
245 renderer_props,
246 presentation_props,
247 dragdrop_props: DragDropProps::default(),
248 update_count: 0,
249 _shift_listeners: shift_listeners,
250 }
251 }
252
253 fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
254 match msg {
255 PreloadFontsUpdate => true,
256 Resize => {
257 self.on_resize.emit(());
258 false
259 },
260 Reset(all, sender) => {
261 reset_all(
262 &ctx.props().session,
263 &ctx.props().renderer,
264 &ctx.props().presentation,
265 all,
266 sender,
267 );
268 false
269 },
270 ToggleSettingsInit(Some(SettingsUpdate::Missing), None) => false,
271 ToggleSettingsInit(Some(SettingsUpdate::Missing), Some(resolve)) => {
272 resolve.send(Ok(JsValue::UNDEFINED)).unwrap();
273 false
274 },
275 ToggleSettingsInit(Some(SettingsUpdate::SetDefault), resolve) => {
276 self.init_toggle_settings_task(ctx, Some(false), resolve);
277 false
278 },
279 ToggleSettingsInit(Some(SettingsUpdate::Update(force)), resolve) => {
280 self.init_toggle_settings_task(ctx, Some(force), resolve);
281 false
282 },
283 ToggleSettingsInit(None, resolve) => {
284 self.init_toggle_settings_task(ctx, None, resolve);
285 false
286 },
287 ToggleSettingsComplete(SettingsUpdate::SetDefault, resolve) if self.settings_open => {
288 ctx.props().presentation.set_open_column_settings(None);
289 self.settings_open = false;
290 self.on_rendered = Some(resolve);
291 true
292 },
293 ToggleSettingsComplete(SettingsUpdate::Update(force), resolve)
294 if force != self.settings_open =>
295 {
296 ctx.props().presentation.set_open_column_settings(None);
297 self.settings_open = force;
298 self.on_rendered = Some(resolve);
299 true
300 },
301 ToggleSettingsComplete(_, resolve)
302 if matches!(self.fonts.get_status(), FontLoaderStatus::Finished) =>
303 {
304 if let Err(e) = resolve.send(()) {
305 tracing::error!("toggle settings failed {:?}", e);
306 }
307
308 false
309 },
310 ToggleSettingsComplete(_, resolve) => {
311 ctx.props().presentation.set_open_column_settings(None);
312 self.on_rendered = Some(resolve);
313 true
314 },
315 OpenColumnSettings {
316 locator,
317 sender,
318 toggle,
319 } => {
320 let mut open_column_settings = ctx.props().presentation.get_open_column_settings();
321 if locator == open_column_settings.locator {
322 if toggle {
323 ctx.props().presentation.set_open_column_settings(None);
324 }
325 } else {
326 open_column_settings.locator.clone_from(&locator);
327 open_column_settings.tab =
328 if matches!(locator, Some(ColumnLocator::NewExpression)) {
329 Some(ColumnSettingsTab::Attributes)
330 } else {
331 locator.as_ref().and_then(|x| {
332 x.name().map(|x| {
333 if self.session_props.is_column_active(x) {
334 ColumnSettingsTab::Style
335 } else {
336 ColumnSettingsTab::Attributes
337 }
338 })
339 })
340 };
341
342 ctx.props()
343 .presentation
344 .set_open_column_settings(Some(open_column_settings));
345
346 if locator.is_some() {
347 self.settings_panel_selected_tab = SelectedTab::Query;
348 }
349 }
350
351 if let Some(sender) = sender {
352 sender.send(()).unwrap();
353 }
354
355 true
356 },
357 SettingsPanelSizeUpdate(Some(x)) => {
358 self.settings_panel_width_override = Some(x);
359 false
360 },
361 SettingsPanelSizeUpdate(None) => {
362 self.settings_panel_width_override = None;
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 }
379 },
380 ColumnSettingsPanelSizeUpdate(Some(x)) => {
381 self.column_settings_panel_width_override = Some(x);
382 false
383 },
384 ColumnSettingsPanelSizeUpdate(None) => {
385 self.column_settings_panel_width_override = None;
386 false
387 },
388 ColumnSettingsTabChanged(tab) => {
389 let mut open_column_settings = ctx.props().presentation.get_open_column_settings();
390 open_column_settings.tab.clone_from(&Some(tab));
391 ctx.props()
392 .presentation
393 .set_open_column_settings(Some(open_column_settings));
394 true
395 },
396 ToggleDebug => {
397 self.debug_open = !self.debug_open;
398 clone!(ctx.props().renderer, ctx.props().session);
399 ApiFuture::spawn(async move {
400 renderer.draw(session.validate().await?.create_view()).await
401 });
402
403 true
404 },
405 UpdateSession(props) => {
406 let changed = *props != self.session_props;
407 self.session_props = *props;
408 changed
409 },
410 UpdateSessionStats(stats, has_table) => {
411 let changed =
412 stats != self.session_props.stats || has_table != self.session_props.has_table;
413 self.session_props.stats = stats;
414 self.session_props.has_table = has_table;
415 changed
416 },
417 UpdateRenderer(props) => {
418 let changed = *props != self.renderer_props;
419 self.renderer_props = *props;
420 changed
421 },
422 UpdatePresentation(props) => {
423 let changed = *props != self.presentation_props;
424 self.presentation_props = *props;
425 changed
426 },
427 UpdateSettingsOpen(open) => {
428 let changed = open != self.presentation_props.is_settings_open;
429 self.presentation_props.is_settings_open = open;
430 changed
431 },
432 UpdateIsWorkspace(is_workspace) => {
433 let changed = is_workspace != self.presentation_props.is_workspace;
434 self.presentation_props.is_workspace = is_workspace;
435 changed
436 },
437 UpdateColumnSettings(ocs) => {
438 let changed = *ocs != self.presentation_props.open_column_settings;
439 self.presentation_props.open_column_settings = *ocs;
440 changed
441 },
442 UpdateDragDrop(props) => {
443 let changed = *props != self.dragdrop_props;
444 self.dragdrop_props = *props;
445 changed
446 },
447 IncrementUpdateCount => {
448 self.update_count = self.update_count.saturating_add(1);
449 true
450 },
451 DecrementUpdateCount => {
452 self.update_count = self.update_count.saturating_sub(1);
453 true
454 },
455 }
456 }
457
458 fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
462 true
463 }
464
465 fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
468 if self.on_rendered.is_some()
469 && matches!(self.fonts.get_status(), FontLoaderStatus::Finished)
470 && self.on_rendered.take().unwrap().send(()).is_err()
471 {
472 tracing::warn!("Orphan render");
473 }
474 }
475
476 fn view(&self, ctx: &Context<Self>) -> Html {
477 let Self::Properties {
478 presentation,
479 renderer,
480 session,
481 ..
482 } = ctx.props();
483
484 let is_settings_open = self.settings_open
485 && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
486
487 let mut class = classes!();
488 if !is_settings_open {
489 class.push("settings-closed");
490 }
491
492 if self.session_props.title.is_some() {
493 class.push("titled");
494 }
495
496 let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
497 locator: c,
498 sender: None,
499 toggle: true,
500 });
501
502 let on_split_panel_resize = ctx
503 .link()
504 .callback(|(x, _)| SettingsPanelSizeUpdate(Some(x)));
505
506 let on_column_settings_panel_resize = ctx
507 .link()
508 .callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
509
510 let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
511 let on_debug = ctx.link().callback(|_| ToggleDebug);
512 let selected_column = get_current_column_locator(
513 &self.presentation_props.open_column_settings,
514 &ctx.props().renderer,
515 &self.session_props.config,
516 &self.session_props.metadata,
517 );
518
519 let selected_tab = self.presentation_props.open_column_settings.tab;
520 let plugin_name = self.renderer_props.plugin_name.clone();
521 let available_plugins = self.renderer_props.available_plugins.clone();
522 let has_table = self.session_props.has_table.clone();
523 let named_column_count = self.renderer_props.config.config_column_names.len();
524
525 let view_config = self.session_props.config.clone();
526 let drag_column = self.dragdrop_props.column.clone();
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);
530 let settings_panel = html! {
531 if is_settings_open {
532 <SettingsPanel
533 on_close={on_close_settings}
534 on_resize={&self.on_resize}
535 on_select_column={on_open_expr_panel}
536 is_debug={self.debug_open}
537 {on_debug}
538 {plugin_name}
539 {available_plugins}
540 {has_table}
541 {named_column_count}
542 {view_config}
543 plugin_config={self.renderer_props.plugin_config.clone()}
544 {drag_column}
545 metadata={metadata.clone()}
546 open_column_settings={self.presentation_props.open_column_settings.clone()}
547 selected_theme={self.presentation_props.selected_theme.clone()}
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}
553 {presentation}
554 {renderer}
555 {session}
556 />
557 }
558 };
559
560 let on_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
561 let on_select_tab = ctx.link().callback(ColumnSettingsTabChanged);
562 let column_settings_panel = html! {
563 if let Some(selected_column) = selected_column {
564 <SplitPanel
565 id="modal_panel"
566 reverse=true
567 initial_size={self.column_settings_panel_width_override}
568 on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
569 on_resize={on_column_settings_panel_resize}
570 >
571 <ColumnSettingsPanel
572 {selected_column}
573 {selected_tab}
574 on_close={self.on_close_column_settings.clone()}
575 width_override={self.column_settings_panel_width_override}
576 {on_select_tab}
577 plugin_name={self.renderer_props.plugin_name.clone()}
578 {metadata}
579 view_config={self.session_props.config.clone()}
580 column_stats={self.session_props.column_stats.clone()}
581 selected_theme={self.presentation_props.selected_theme.clone()}
582 {presentation}
583 {renderer}
584 {session}
585 />
586 <></>
587 </SplitPanel>
588 }
589 };
590
591 let on_reset = ctx.link().callback(|all| Reset(all, None));
592 let is_settings_open = self.settings_open
593 && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
594 let main_panel = html! {
595 <MainPanel
596 {on_settings}
597 {on_reset}
598 session_props={self.session_props.clone()}
599 renderer_props={self.renderer_props.clone()}
600 presentation_props={self.presentation_props.clone()}
601 {is_settings_open}
602 update_count={self.update_count}
603 {presentation}
604 {renderer}
605 {session}
606 />
607 };
608
609 html! {
610 <StyleProvider root={ctx.props().elem.clone()}>
611 <LocalStyle href={css!("viewer")} />
612 <div id="component_container">
613 if is_settings_open {
614 <SplitPanel
615 id="app_panel"
616 reverse=true
617 skip_empty=true
618 initial_size={self.settings_panel_width_override}
619 on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
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)}
629 >
630 { settings_panel }
631 <div id="main_column_container">
632 { main_panel }
633 { column_settings_panel }
634 </div>
635 </SplitPanel>
636 } else {
637 <div id="main_column_container">
638 { main_panel }
639 { column_settings_panel }
640 </div>
641 }
642 </div>
643 <FontLoader ..self.fonts.clone() />
644 </StyleProvider>
645 }
646 }
647
648 fn destroy(&mut self, _ctx: &Context<Self>) {}
649}
650
651impl PerspectiveViewer {
652 fn init_toggle_settings_task(
666 &mut self,
667 ctx: &Context<Self>,
668 force: Option<bool>,
669 sender: Option<Sender<ApiResult<JsValue>>>,
670 ) {
671 let is_open = ctx.props().presentation.is_settings_open();
672 ctx.props().presentation.set_settings_before_open(!is_open);
673 match force {
674 Some(force) if is_open == force => {
675 if let Some(sender) = sender {
676 sender.send(Ok(JsValue::UNDEFINED)).unwrap();
677 }
678 },
679 Some(_) | None => {
680 let force = !is_open;
681 let callback = ctx.link().callback(move |resolve| {
682 let update = SettingsUpdate::Update(force);
683 ToggleSettingsComplete(update, resolve)
684 });
685
686 clone!(
687 ctx.props().renderer,
688 ctx.props().session,
689 ctx.props().presentation
690 );
691
692 ApiFuture::spawn(async move {
693 let result = if session.js_get_table().is_some() {
694 renderer
695 .presize(force, {
696 let (sender, receiver) = channel::<()>();
697 async move {
698 callback.emit(sender);
699 presentation.set_settings_open(!is_open);
700 Ok(receiver.await?)
701 }
702 })
703 .await
704 } else {
705 let (sender, receiver) = channel::<()>();
706 callback.emit(sender);
707 presentation.set_settings_open(!is_open);
708 receiver.await?;
709 Ok(JsValue::UNDEFINED)
710 };
711
712 if let Some(sender) = sender {
713 let msg = result.ignore_view_delete();
714 sender
715 .send(msg.map(|x| x.unwrap_or(JsValue::UNDEFINED)))
716 .into_apierror()?;
717 };
718
719 Ok(JsValue::undefined())
720 });
721 },
722 };
723 }
724}
725
726fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
729 let session_props_sub = {
730 let session = ctx.props().session.clone();
731 let cb = ctx
732 .link()
733 .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
734
735 let s = &ctx.props().session;
736 let sub1 = s.table_loaded.add_notify_listener(&cb);
737 let sub2 = s.table_unloaded.add_notify_listener(&cb);
738 let sub3 = s.view_created.add_notify_listener(&cb);
739 let sub4 = s.view_config_changed.add_notify_listener(&cb);
740 let sub5 = s.title_changed.add_notify_listener(&cb);
741 let sub6 = s
742 .view_config_changed
743 .add_listener(ctx.link().callback(|_| IncrementUpdateCount));
744
745 let sub7 = s
746 .view_created
747 .add_listener(ctx.link().callback(|_| DecrementUpdateCount));
748
749 let sub8 = s.column_stats_changed.add_notify_listener(&cb);
754
755 vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7, sub8]
756 };
757
758 let renderer_props_sub = {
759 let renderer = ctx.props().renderer.clone();
760 let cb_plugin = ctx.link().callback({
761 let renderer = renderer.clone();
762 move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
763 });
764
765 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
778 let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
779 let sub2 = ctx
780 .props()
781 .renderer
782 .plugin_config_changed
783 .add_listener(cb_plugin_config);
784
785 vec![sub1, sub2]
786 };
787
788 let presentation_props_sub = {
789 let presentation = ctx.props().presentation.clone();
790 let cb_settings = ctx.link().callback(UpdateSettingsOpen);
791 let cb_theme = {
792 let pres = presentation.clone();
793 ctx.link()
794 .callback(move |(themes, _): (PtrEqRc<Vec<String>>, _)| {
795 UpdatePresentation(Box::new(pres.to_props(themes)))
796 })
797 };
798
799 let cb_column_settings = {
800 let pres = presentation.clone();
801 ctx.link().callback(move |_: (bool, Option<String>)| {
802 UpdateColumnSettings(Box::new(pres.get_open_column_settings()))
803 })
804 };
805
806 let sub1 = presentation.settings_open_changed.add_listener(cb_settings);
807 let sub2 = presentation.theme_config_updated.add_listener(cb_theme);
808 let sub3 = presentation
809 .column_settings_open_changed
810 .add_listener(cb_column_settings);
811
812 vec![sub1, sub2, sub3]
813 };
814
815 let dragdrop_props_sub = {
816 let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
817 let sub1 = ctx
818 .props()
819 .presentation
820 .drop_received
821 .add_notify_listener(&cb_clear);
822
823 vec![sub1]
824 };
825
826 let mut subscriptions = Vec::new();
827 subscriptions.extend(session_props_sub);
828 subscriptions.extend(renderer_props_sub);
829 subscriptions.extend(presentation_props_sub);
830 subscriptions.extend(dragdrop_props_sub);
831 subscriptions
832}
833
834fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
837 {
839 let session = ctx.props().session.clone();
840 let cb = ctx.link().callback(move |_: ()| {
841 UpdateSessionStats(session.get_table_stats(), session.has_table())
842 });
843
844 *ctx.props().session.on_stats_changed.borrow_mut() = Some(cb);
845 }
846
847 {
849 let session = ctx.props().session.clone();
850 let cb = ctx
851 .link()
852 .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
853
854 *ctx.props().session.on_table_errored.borrow_mut() = Some(cb);
855 }
856
857 {
860 clone!(
861 ctx.props().presentation,
862 ctx.props().renderer,
863 ctx.props().session
864 );
865
866 let cb = ctx.link().batch_callback(move |limits: RenderLimits| {
867 let mut msgs = vec![UpdateRenderer(Box::new(renderer.to_props(Some(limits))))];
868 if !limits.is_update {
869 let locator = get_current_column_locator(
870 &presentation.get_open_column_settings(),
871 &renderer,
872 &session.get_view_config(),
873 &session.metadata(),
874 );
875
876 msgs.push(OpenColumnSettings {
877 locator,
878 sender: None,
879 toggle: false,
880 });
881 }
882
883 msgs
884 });
885
886 *ctx.props().renderer.on_render_limits_changed.borrow_mut() = Some(cb);
887 }
888
889 {
891 let cb = ctx.link().callback(UpdateIsWorkspace);
892 *ctx.props()
893 .presentation
894 .on_is_workspace_changed
895 .borrow_mut() = Some(cb);
896 }
897
898 {
900 let presentation = ctx.props().presentation.clone();
901 let cb = ctx.link().callback(move |_: DragEffect| {
902 UpdateDragDrop(Box::new(presentation.drag_drop_props()))
903 });
904
905 *ctx.props().presentation.on_dragstart.borrow_mut() = Some(cb);
906 }
907
908 {
910 let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
911 *ctx.props().presentation.on_dragend.borrow_mut() = Some(cb);
912 }
913}