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 perspective_js::utils::*;
14use wasm_bindgen::prelude::*;
15use yew::prelude::*;
16
17use super::render_warning::RenderWarning;
18use super::status_bar::StatusBar;
19use crate::custom_events::CustomEvents;
20use crate::presentation::Presentation;
21use crate::renderer::limits::RenderLimits;
22use crate::renderer::*;
23use crate::session::{Session, TableErrorState, TableLoadState, ViewStats};
24use crate::utils::*;
25
26#[derive(Clone, Properties)]
27pub struct MainPanelProps {
28    pub on_settings: Callback<()>,
29
30    /// Reset callback forwarded from the root component.  Fired when the user
31    /// clicks the reset button; `bool` is `true` for a full reset (expressions
32    /// + column configs), `false` for config-only.
33    pub on_reset: Callback<bool>,
34
35    /// Render-limit dimensions forwarded from the root's `RendererProps`.
36    /// `Some` when the active plugin is capping the rendered row/column count;
37    /// `None` when no limits are active (e.g. after a plugin change).
38    pub render_limits: Option<RenderLimits>,
39
40    /// Value props from root's `SessionProps`, threaded to `StatusBar` /
41    /// `StatusIndicator`.
42    pub has_table: Option<TableLoadState>,
43    pub is_errored: bool,
44    pub stats: Option<ViewStats>,
45    pub update_count: u32,
46    pub error: Option<TableErrorState>,
47    pub title: Option<String>,
48
49    /// Value props from root's `PresentationProps`, threaded to `StatusBar`.
50    pub is_settings_open: bool,
51    pub selected_theme: Option<String>,
52    pub available_themes: PtrEqRc<Vec<String>>,
53    pub is_workspace: bool,
54
55    /// State
56    pub custom_events: CustomEvents,
57    pub session: Session,
58    pub renderer: Renderer,
59    pub presentation: Presentation,
60}
61
62impl PartialEq for MainPanelProps {
63    fn eq(&self, rhs: &Self) -> bool {
64        self.has_table == rhs.has_table
65            && self.is_errored == rhs.is_errored
66            && self.stats == rhs.stats
67            && self.update_count == rhs.update_count
68            && self.error == rhs.error
69            && self.title == rhs.title
70            && self.is_settings_open == rhs.is_settings_open
71            && self.selected_theme == rhs.selected_theme
72            && self.available_themes == rhs.available_themes
73            && self.is_workspace == rhs.is_workspace
74            && self.render_limits == rhs.render_limits
75    }
76}
77
78impl MainPanelProps {
79    fn is_title(&self) -> bool {
80        self.title.is_some()
81    }
82}
83
84#[derive(Debug)]
85pub enum MainPanelMsg {
86    PointerEvent(web_sys::PointerEvent),
87}
88
89pub struct MainPanel {
90    main_panel_ref: NodeRef,
91}
92
93impl Component for MainPanel {
94    type Message = MainPanelMsg;
95    type Properties = MainPanelProps;
96
97    fn create(_ctx: &Context<Self>) -> Self {
98        Self {
99            main_panel_ref: NodeRef::default(),
100        }
101    }
102
103    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
104        match msg {
105            MainPanelMsg::PointerEvent(event) => {
106                if event.target().map(JsValue::from)
107                    == self
108                        .main_panel_ref
109                        .cast::<web_sys::HtmlElement>()
110                        .map(JsValue::from)
111                {
112                    ctx.props()
113                        .custom_events
114                        .dispatch_event(format!("statusbar-{}", event.type_()).as_str(), &event)
115                        .unwrap();
116                }
117
118                false
119            },
120        }
121    }
122
123    fn changed(&mut self, _ctx: &Context<Self>, _old: &Self::Properties) -> bool {
124        true
125    }
126
127    fn view(&self, ctx: &Context<Self>) -> Html {
128        let Self::Properties {
129            custom_events,
130            presentation,
131            renderer,
132            session,
133            ..
134        } = ctx.props();
135
136        let is_settings_open = ctx.props().is_settings_open
137            && matches!(ctx.props().has_table, Some(TableLoadState::Loaded));
138
139        let on_settings = (!is_settings_open).then(|| ctx.props().on_settings.clone());
140
141        let mut class = classes!();
142        if !is_settings_open {
143            class.push("settings-closed");
144        }
145
146        if ctx.props().is_title() {
147            class.push("titled");
148        }
149
150        let pointerdown = ctx.link().callback(MainPanelMsg::PointerEvent);
151        let on_dismiss_warning = {
152            clone!(renderer, session);
153            Callback::from(move |_: ()| {
154                clone!(renderer, session);
155                ApiFuture::spawn(async move {
156                    renderer.disable_active_plugin_render_warning();
157                    let view_task = session.get_view();
158                    renderer.update(view_task).await
159                });
160            })
161        };
162
163        html! {
164            <div id="main_column">
165                <StatusBar
166                    id="status_bar"
167                    {on_settings}
168                    on_reset={ctx.props().on_reset.clone()}
169                    has_table={ctx.props().has_table.clone()}
170                    is_errored={ctx.props().is_errored}
171                    stats={ctx.props().stats.clone()}
172                    update_count={ctx.props().update_count}
173                    error={ctx.props().error.clone()}
174                    title={ctx.props().title.clone()}
175                    is_settings_open={ctx.props().is_settings_open}
176                    selected_theme={ctx.props().selected_theme.clone()}
177                    available_themes={ctx.props().available_themes.clone()}
178                    is_workspace={ctx.props().is_workspace}
179                    {custom_events}
180                    {presentation}
181                    {renderer}
182                    {session}
183                />
184                <div
185                    id="main_panel_container"
186                    ref={self.main_panel_ref.clone()}
187                    {class}
188                    onpointerdown={pointerdown}
189                >
190                    <RenderWarning
191                        on_dismiss={on_dismiss_warning}
192                        dimensions={ctx.props().render_limits}
193                    />
194                    <slot />
195                </div>
196            </div>
197        }
198    }
199
200    fn destroy(&mut self, _ctx: &Context<Self>) {}
201}