Skip to main content

perspective_viewer/components/
main_panel.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13use futures::channel::oneshot::*;
14use perspective_js::utils::*;
15use wasm_bindgen::prelude::*;
16use yew::prelude::*;
17
18use super::render_warning::RenderWarning;
19use super::status_bar::StatusBar;
20use crate::PerspectiveProperties;
21use crate::custom_events::CustomEvents;
22use crate::presentation::Presentation;
23use crate::renderer::*;
24use crate::session::*;
25use crate::utils::*;
26
27#[derive(Clone, Properties, PerspectiveProperties!)]
28pub struct MainPanelProps {
29    pub on_settings: Callback<()>,
30
31    /// State
32    pub custom_events: CustomEvents,
33    pub session: Session,
34    pub renderer: Renderer,
35    pub presentation: Presentation,
36}
37
38impl PartialEq for MainPanelProps {
39    fn eq(&self, _rhs: &Self) -> bool {
40        false
41    }
42}
43
44impl MainPanelProps {
45    fn is_title(&self) -> bool {
46        self.session.get_title().is_some()
47    }
48}
49
50#[derive(Debug)]
51pub enum MainPanelMsg {
52    Reset(bool, Option<Sender<()>>),
53    RenderLimits(Option<(usize, usize, Option<usize>, Option<usize>)>),
54    PointerEvent(web_sys::PointerEvent),
55    Error,
56}
57
58pub struct MainPanel {
59    _subscriptions: [Subscription; 2],
60    dimensions: Option<(usize, usize, Option<usize>, Option<usize>)>,
61    main_panel_ref: NodeRef,
62}
63
64impl Component for MainPanel {
65    type Message = MainPanelMsg;
66    type Properties = MainPanelProps;
67
68    fn create(ctx: &Context<Self>) -> Self {
69        let session_sub = {
70            let callback = ctx.link().callback(move |(_, render_limits)| {
71                MainPanelMsg::RenderLimits(Some(render_limits))
72            });
73
74            ctx.props()
75                .renderer
76                .render_limits_changed
77                .add_listener(callback)
78        };
79
80        let error_sub = ctx
81            .props()
82            .session
83            .table_errored
84            .add_listener(ctx.link().callback(|_| MainPanelMsg::Error));
85
86        Self {
87            _subscriptions: [session_sub, error_sub],
88            dimensions: None,
89            main_panel_ref: NodeRef::default(),
90        }
91    }
92
93    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
94        match msg {
95            MainPanelMsg::Error => true,
96            MainPanelMsg::Reset(all, sender) => {
97                ctx.props().presentation.set_open_column_settings(None);
98
99                clone!(
100                    ctx.props().renderer,
101                    ctx.props().session,
102                    ctx.props().presentation
103                );
104
105                ApiFuture::spawn(async move {
106                    session
107                        .reset(ResetOptions {
108                            config: true,
109                            expressions: all,
110                            ..ResetOptions::default()
111                        })
112                        .await?;
113                    let columns_config = if all {
114                        presentation.reset_columns_configs();
115                        None
116                    } else {
117                        Some(presentation.all_columns_configs())
118                    };
119
120                    renderer.reset(columns_config.as_ref()).await?;
121                    presentation.reset_available_themes(None).await;
122                    if all {
123                        presentation.reset_theme().await?;
124                    }
125
126                    let result = renderer.draw(session.validate().await?.create_view()).await;
127                    if let Some(sender) = sender {
128                        sender.send(()).unwrap();
129                    }
130
131                    renderer.reset_changed.emit(());
132                    result
133                });
134
135                false
136            },
137
138            MainPanelMsg::RenderLimits(dimensions) => {
139                if self.dimensions != dimensions {
140                    self.dimensions = dimensions;
141                    true
142                } else {
143                    false
144                }
145            },
146
147            MainPanelMsg::PointerEvent(event) => {
148                if event.target().map(JsValue::from)
149                    == self
150                        .main_panel_ref
151                        .cast::<web_sys::HtmlElement>()
152                        .map(JsValue::from)
153                {
154                    ctx.props()
155                        .custom_events
156                        .dispatch_event(format!("statusbar-{}", event.type_()).as_str(), &event)
157                        .unwrap();
158                }
159
160                false
161            },
162        }
163    }
164
165    fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
166        true
167    }
168
169    fn view(&self, ctx: &Context<Self>) -> Html {
170        let Self::Properties {
171            custom_events,
172            presentation,
173            renderer,
174            session,
175            ..
176        } = ctx.props();
177
178        let is_settings_open =
179            ctx.props().presentation.is_settings_open() && ctx.props().session.has_table();
180
181        let on_settings = (!is_settings_open).then(|| ctx.props().on_settings.clone());
182
183        let mut class = classes!();
184        if !is_settings_open {
185            class.push("settings-closed");
186        }
187
188        if ctx.props().is_title() {
189            class.push("titled");
190        }
191
192        let on_reset = ctx.link().callback(|all| MainPanelMsg::Reset(all, None));
193        let pointerdown = ctx.link().callback(MainPanelMsg::PointerEvent);
194        html! {
195            <div id="main_column">
196                <StatusBar
197                    id="status_bar"
198                    {on_settings}
199                    on_reset={on_reset.clone()}
200                    {custom_events}
201                    {presentation}
202                    {renderer}
203                    {session}
204                />
205                <div
206                    id="main_panel_container"
207                    ref={self.main_panel_ref.clone()}
208                    {class}
209                    onpointerdown={pointerdown}
210                >
211                    <RenderWarning {renderer} {session} dimensions={self.dimensions} />
212                    <slot />
213                </div>
214            </div>
215        }
216    }
217
218    fn destroy(&mut self, _ctx: &Context<Self>) {}
219}