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