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