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 if let Err(e) = resolve.send(()) {
298 tracing::error!("toggle settings failed {:?}", e);
299 }
300
301 false
302 },
303 ToggleSettingsComplete(_, resolve) => {
304 ctx.props().presentation.set_open_column_settings(None);
305 self.on_rendered = Some(resolve);
306 true
307 },
308 OpenColumnSettings {
309 locator,
310 sender,
311 toggle,
312 } => {
313 let mut open_column_settings = ctx.props().presentation.get_open_column_settings();
314 if locator == open_column_settings.locator {
315 if toggle {
316 ctx.props().presentation.set_open_column_settings(None);
317 }
318 } else {
319 open_column_settings.locator.clone_from(&locator);
320 open_column_settings.tab =
321 if matches!(locator, Some(ColumnLocator::NewExpression)) {
322 Some(ColumnSettingsTab::Attributes)
323 } else {
324 locator.as_ref().and_then(|x| {
325 x.name().map(|x| {
326 if self.session_props.is_column_active(x) {
327 ColumnSettingsTab::Style
328 } else {
329 ColumnSettingsTab::Attributes
330 }
331 })
332 })
333 };
334
335 ctx.props()
336 .presentation
337 .set_open_column_settings(Some(open_column_settings));
338 }
339
340 if let Some(sender) = sender {
341 sender.send(()).unwrap();
342 }
343
344 true
345 },
346 SettingsPanelSizeUpdate(Some(x)) => {
347 self.settings_panel_width_override = Some(x);
348 false
349 },
350 SettingsPanelSizeUpdate(None) => {
351 self.settings_panel_width_override = None;
352 false
353 },
354 ColumnSettingsPanelSizeUpdate(Some(x)) => {
355 self.column_settings_panel_width_override = Some(x);
356 false
357 },
358 ColumnSettingsPanelSizeUpdate(None) => {
359 self.column_settings_panel_width_override = None;
360 false
361 },
362 ColumnSettingsTabChanged(tab) => {
363 let mut open_column_settings = ctx.props().presentation.get_open_column_settings();
364 open_column_settings.tab.clone_from(&Some(tab));
365 ctx.props()
366 .presentation
367 .set_open_column_settings(Some(open_column_settings));
368 true
369 },
370 ToggleDebug => {
371 self.debug_open = !self.debug_open;
372 clone!(ctx.props().renderer, ctx.props().session);
373 ApiFuture::spawn(async move {
374 renderer.draw(session.validate().await?.create_view()).await
375 });
376
377 true
378 },
379 UpdateSession(props) => {
380 let changed = *props != self.session_props;
381 self.session_props = *props;
382 changed
383 },
384 UpdateSessionStats(stats, has_table) => {
385 let changed =
386 stats != self.session_props.stats || has_table != self.session_props.has_table;
387 self.session_props.stats = stats;
388 self.session_props.has_table = has_table;
389 changed
390 },
391 UpdateRenderer(props) => {
392 let changed = *props != self.renderer_props;
393 self.renderer_props = *props;
394 changed
395 },
396 UpdatePresentation(props) => {
397 let changed = *props != self.presentation_props;
398 self.presentation_props = *props;
399 changed
400 },
401 UpdateSettingsOpen(open) => {
402 let changed = open != self.presentation_props.is_settings_open;
403 self.presentation_props.is_settings_open = open;
404 changed
405 },
406 UpdateIsWorkspace(is_workspace) => {
407 let changed = is_workspace != self.presentation_props.is_workspace;
408 self.presentation_props.is_workspace = is_workspace;
409 changed
410 },
411 UpdateColumnSettings(ocs) => {
412 let changed = *ocs != self.presentation_props.open_column_settings;
413 self.presentation_props.open_column_settings = *ocs;
414 changed
415 },
416 UpdateDragDrop(props) => {
417 let changed = *props != self.dragdrop_props;
418 self.dragdrop_props = *props;
419 changed
420 },
421 IncrementUpdateCount => {
422 self.update_count = self.update_count.saturating_add(1);
423 true
424 },
425 DecrementUpdateCount => {
426 self.update_count = self.update_count.saturating_sub(1);
427 true
428 },
429 }
430 }
431
432 fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
436 true
437 }
438
439 fn rendered(&mut self, _ctx: &Context<Self>, _first_render: bool) {
442 if self.on_rendered.is_some()
443 && matches!(self.fonts.get_status(), FontLoaderStatus::Finished)
444 && self.on_rendered.take().unwrap().send(()).is_err()
445 {
446 tracing::warn!("Orphan render");
447 }
448 }
449
450 fn view(&self, ctx: &Context<Self>) -> Html {
451 let Self::Properties {
452 custom_events,
453 dragdrop,
454 presentation,
455 renderer,
456 session,
457 ..
458 } = ctx.props();
459
460 let is_settings_open = self.settings_open
461 && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
462
463 let mut class = classes!();
464 if !is_settings_open {
465 class.push("settings-closed");
466 }
467
468 if self.session_props.title.is_some() {
469 class.push("titled");
470 }
471
472 let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
473 locator: Some(c),
474 sender: None,
475 toggle: true,
476 });
477
478 let on_split_panel_resize = ctx
479 .link()
480 .callback(|(x, _)| SettingsPanelSizeUpdate(Some(x)));
481
482 let on_column_settings_panel_resize = ctx
483 .link()
484 .callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
485
486 let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
487 let on_debug = ctx.link().callback(|_| ToggleDebug);
488 let selected_column = get_current_column_locator(
489 &self.presentation_props.open_column_settings,
490 &ctx.props().renderer,
491 &self.session_props.config,
492 &self.session_props.metadata,
493 );
494
495 let selected_tab = self.presentation_props.open_column_settings.tab;
496 let plugin_name = self.renderer_props.plugin_name.clone();
497 let available_plugins = self.renderer_props.available_plugins.clone();
498 let has_table = self.session_props.has_table.clone();
499 let named_column_count = self
500 .renderer_props
501 .requirements
502 .names
503 .as_ref()
504 .map(|n| n.len())
505 .unwrap_or(0);
506
507 let view_config = self.session_props.config.clone();
508 let drag_column = self.dragdrop_props.column.clone();
509 let metadata = self.session_props.metadata.clone();
510 let settings_panel = html! {
511 if is_settings_open {
512 <SettingsPanel
513 on_close={on_close_settings}
514 on_resize={&self.on_resize}
515 on_select_column={on_open_expr_panel}
516 is_debug={self.debug_open}
517 {on_debug}
518 {plugin_name}
519 {available_plugins}
520 {has_table}
521 {named_column_count}
522 {view_config}
523 {drag_column}
524 metadata={metadata.clone()}
525 open_column_settings={self.presentation_props.open_column_settings.clone()}
526 selected_theme={self.presentation_props.selected_theme.clone()}
527 {dragdrop}
528 {presentation}
529 {renderer}
530 {session}
531 />
532 }
533 };
534
535 let on_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
536 let on_select_tab = ctx.link().callback(ColumnSettingsTabChanged);
537 let column_settings_panel = html! {
538 if let Some(selected_column) = selected_column {
539 <SplitPanel
540 id="modal_panel"
541 reverse=true
542 initial_size={self.column_settings_panel_width_override}
543 on_reset={ctx.link().callback(|_| ColumnSettingsPanelSizeUpdate(None))}
544 on_resize={on_column_settings_panel_resize}
545 >
546 <ColumnSettingsPanel
547 {selected_column}
548 {selected_tab}
549 on_close={self.on_close_column_settings.clone()}
550 width_override={self.column_settings_panel_width_override}
551 {on_select_tab}
552 plugin_name={self.renderer_props.plugin_name.clone()}
553 {metadata}
554 view_config={self.session_props.config.clone()}
555 selected_theme={self.presentation_props.selected_theme.clone()}
556 {custom_events}
557 {presentation}
558 {renderer}
559 {session}
560 />
561 <></>
562 </SplitPanel>
563 }
564 };
565
566 let on_reset = ctx.link().callback(|all| Reset(all, None));
567 let render_limits = self.renderer_props.render_limits;
568 let has_table = self.session_props.has_table.clone();
569 let is_errored = self.session_props.error.is_some();
570 let stats = self.session_props.stats.clone();
571 let update_count = self.update_count;
572 let error = self.session_props.error.clone();
573 let is_settings_open = self.settings_open
574 && matches!(self.session_props.has_table, Some(TableLoadState::Loaded));
575 let title = self.session_props.title.clone();
576 let selected_theme = self.presentation_props.selected_theme.clone();
577 let available_themes = self.presentation_props.available_themes.clone();
578 let main_panel = html! {
579 <MainPanel
580 {on_settings}
581 {on_reset}
582 {render_limits}
583 {has_table}
584 {is_errored}
585 {stats}
586 {update_count}
587 {error}
588 {is_settings_open}
589 {title}
590 {selected_theme}
591 {available_themes}
592 is_workspace={self.presentation_props.is_workspace}
593 {custom_events}
594 {presentation}
595 {renderer}
596 {session}
597 />
598 };
599
600 let debug_panel = html! {
601 if self.debug_open { <DebugPanel {presentation} {renderer} {session} /> }
602 };
603
604 html! {
605 <StyleProvider root={ctx.props().elem.clone()}>
606 <LocalStyle href={css!("viewer")} />
607 <div id="component_container">
608 if is_settings_open {
609 <SplitPanel
610 id="app_panel"
611 reverse=true
612 skip_empty=true
613 initial_size={self.settings_panel_width_override}
614 on_reset={ctx.link().callback(|_| SettingsPanelSizeUpdate(None))}
615 on_resize={on_split_panel_resize.clone()}
616 on_resize_finished={ctx.props().render_callback()}
617 >
618 { debug_panel }
619 { settings_panel }
620 <div id="main_column_container">
621 { main_panel }
622 { column_settings_panel }
623 </div>
624 </SplitPanel>
625 } else {
626 <div id="main_column_container">
627 { main_panel }
628 { column_settings_panel }
629 </div>
630 }
631 </div>
632 <FontLoader ..self.fonts.clone() />
633 </StyleProvider>
634 }
635 }
636
637 fn destroy(&mut self, _ctx: &Context<Self>) {}
638}
639
640impl PerspectiveViewer {
641 fn init_toggle_settings_task(
655 &mut self,
656 ctx: &Context<Self>,
657 force: Option<bool>,
658 sender: Option<Sender<ApiResult<JsValue>>>,
659 ) {
660 let is_open = ctx.props().presentation.is_settings_open();
661 ctx.props().presentation.set_settings_before_open(!is_open);
662 match force {
663 Some(force) if is_open == force => {
664 if let Some(sender) = sender {
665 sender.send(Ok(JsValue::UNDEFINED)).unwrap();
666 }
667 },
668 Some(_) | None => {
669 let force = !is_open;
670 let callback = ctx.link().callback(move |resolve| {
671 let update = SettingsUpdate::Update(force);
672 ToggleSettingsComplete(update, resolve)
673 });
674
675 clone!(
676 ctx.props().renderer,
677 ctx.props().session,
678 ctx.props().presentation
679 );
680
681 ApiFuture::spawn(async move {
682 let result = if session.js_get_table().is_some() {
683 renderer
684 .presize(force, {
685 let (sender, receiver) = channel::<()>();
686 async move {
687 callback.emit(sender);
688 presentation.set_settings_open(!is_open);
689 Ok(receiver.await?)
690 }
691 })
692 .await
693 } else {
694 let (sender, receiver) = channel::<()>();
695 callback.emit(sender);
696 presentation.set_settings_open(!is_open);
697 receiver.await?;
698 Ok(JsValue::UNDEFINED)
699 };
700
701 if let Some(sender) = sender {
702 let msg = result.ignore_view_delete();
703 sender
704 .send(msg.map(|x| x.unwrap_or(JsValue::UNDEFINED)))
705 .into_apierror()?;
706 };
707
708 Ok(JsValue::undefined())
709 });
710 },
711 };
712 }
713}
714
715fn create_subscriptions(ctx: &Context<PerspectiveViewer>) -> Vec<Subscription> {
718 let session_props_sub = {
719 let session = ctx.props().session.clone();
720 let cb = ctx
721 .link()
722 .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
723
724 let s = &ctx.props().session;
725 let sub1 = s.table_loaded.add_notify_listener(&cb);
726 let sub2 = s.table_unloaded.add_notify_listener(&cb);
727 let sub3 = s.view_created.add_notify_listener(&cb);
728 let sub4 = s.view_config_changed.add_notify_listener(&cb);
729 let sub5 = s.title_changed.add_notify_listener(&cb);
730 let sub6 = s
731 .view_config_changed
732 .add_listener(ctx.link().callback(|_| IncrementUpdateCount));
733
734 let sub7 = s
735 .view_created
736 .add_listener(ctx.link().callback(|_| DecrementUpdateCount));
737
738 vec![sub1, sub2, sub3, sub4, sub5, sub6, sub7]
739 };
740
741 let renderer_props_sub = {
742 let renderer = ctx.props().renderer.clone();
743 let cb_plugin = ctx.link().callback({
744 move |_: JsPerspectiveViewerPlugin| UpdateRenderer(Box::new(renderer.to_props(None)))
745 });
746
747 let sub1 = ctx.props().renderer.plugin_changed.add_listener(cb_plugin);
748 vec![sub1]
749 };
750
751 let presentation_props_sub = {
752 let presentation = ctx.props().presentation.clone();
753 let cb_settings = ctx.link().callback(UpdateSettingsOpen);
754 let cb_theme = {
755 let pres = presentation.clone();
756 ctx.link()
757 .callback(move |(themes, _): (PtrEqRc<Vec<String>>, _)| {
758 UpdatePresentation(Box::new(pres.to_props(themes)))
759 })
760 };
761
762 let cb_column_settings = {
763 let pres = presentation.clone();
764 ctx.link().callback(move |_: (bool, Option<String>)| {
765 UpdateColumnSettings(Box::new(pres.get_open_column_settings()))
766 })
767 };
768
769 let sub1 = presentation.settings_open_changed.add_listener(cb_settings);
770 let sub2 = presentation.theme_config_updated.add_listener(cb_theme);
771 let sub3 = presentation
772 .column_settings_open_changed
773 .add_listener(cb_column_settings);
774
775 vec![sub1, sub2, sub3]
776 };
777
778 let dragdrop_props_sub = {
779 let cb_clear = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
780 let sub1 = ctx
781 .props()
782 .dragdrop
783 .drop_received
784 .add_notify_listener(&cb_clear);
785
786 vec![sub1]
787 };
788
789 let mut subscriptions = Vec::new();
790 subscriptions.extend(session_props_sub);
791 subscriptions.extend(renderer_props_sub);
792 subscriptions.extend(presentation_props_sub);
793 subscriptions.extend(dragdrop_props_sub);
794 subscriptions
795}
796
797fn inject_engine_callbacks(ctx: &Context<PerspectiveViewer>) {
800 {
802 let session = ctx.props().session.clone();
803 let cb = ctx.link().callback(move |_: ()| {
804 UpdateSessionStats(session.get_table_stats(), session.has_table())
805 });
806
807 *ctx.props().session.on_stats_changed.borrow_mut() = Some(cb);
808 }
809
810 {
812 let session = ctx.props().session.clone();
813 let cb = ctx
814 .link()
815 .callback(move |_: ()| UpdateSession(Box::new(session.to_props())));
816
817 *ctx.props().session.on_table_errored.borrow_mut() = Some(cb);
818 }
819
820 {
823 clone!(
824 ctx.props().presentation,
825 ctx.props().renderer,
826 ctx.props().session
827 );
828
829 let cb = ctx.link().batch_callback(move |limits: RenderLimits| {
830 let mut msgs = vec![UpdateRenderer(Box::new(renderer.to_props(Some(limits))))];
831 if !limits.is_update {
832 let locator = get_current_column_locator(
833 &presentation.get_open_column_settings(),
834 &renderer,
835 &session.get_view_config(),
836 &session.metadata(),
837 );
838
839 msgs.push(OpenColumnSettings {
840 locator,
841 sender: None,
842 toggle: false,
843 });
844 }
845
846 msgs
847 });
848
849 *ctx.props().renderer.on_render_limits_changed.borrow_mut() = Some(cb);
850 }
851
852 {
854 let cb = ctx.link().callback(UpdateIsWorkspace);
855 *ctx.props()
856 .presentation
857 .on_is_workspace_changed
858 .borrow_mut() = Some(cb);
859 }
860
861 {
863 let dragdrop = ctx.props().dragdrop.clone();
864 let cb = ctx
865 .link()
866 .callback(move |_: DragEffect| UpdateDragDrop(Box::new(dragdrop.to_props())));
867
868 *ctx.props().dragdrop.on_dragstart.borrow_mut() = Some(cb);
869 }
870
871 {
873 let cb = ctx.link().callback(|_: ()| UpdateDragDrop(Box::default()));
874 *ctx.props().dragdrop.on_dragend.borrow_mut() = Some(cb);
875 }
876}