Skip to main content

perspective_viewer/components/
status_indicator.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_client::config::ViewConfigUpdate;
14use perspective_js::utils::ApiError;
15use web_sys::*;
16use yew::prelude::*;
17
18use crate::renderer::{RenderError, Renderer};
19use crate::session::{Session, SessionProps, TableLoadState};
20use crate::tasks::apply_and_render;
21use crate::utils::*;
22
23/// Value-prop version: no PubSub subscriptions, no reducer.
24/// The parent (`StatusBar`) re-renders this component whenever
25/// `session_props.error/has_table/has_table_cells` or `update_count` change
26/// (via root's `UpdateInFlight` / `UpdateSession` messages).
27#[derive(PartialEq, Properties)]
28pub struct StatusIndicatorProps {
29    pub renderer: Renderer,
30    pub session: Session,
31
32    /// TODO(texodus): remove this
33    pub update_count: u32,
34    pub session_props: SessionProps,
35    pub render_error: Option<RenderError>,
36}
37
38/// An indicator component which displays the current status of the perspective
39/// server as an icon. This indicator also functions as a button to invoke the
40/// reconnect callback when in an error state.
41#[function_component]
42pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
43    let has_table_cells = props.session_props.has_table_cells;
44    let state = if let Some(err) = &props.session_props.error {
45        StatusIconState::Errored(
46            err.message(),
47            err.stacktrace(),
48            err.kind(),
49            err.is_reconnect(),
50        )
51    } else if let Some(RenderError(err)) = &props.render_error {
52        StatusIconState::Errored(err.message(), err.stacktrace(), err.kind(), true)
53    } else if !has_table_cells
54        && matches!(props.session_props.has_table, Some(TableLoadState::Loading))
55    {
56        StatusIconState::Loading
57    } else if matches!(props.session_props.has_table, Some(TableLoadState::Pending)) {
58        StatusIconState::Pending
59    } else if props.update_count > 0 {
60        StatusIconState::Updating
61    } else if has_table_cells {
62        StatusIconState::Normal
63    } else {
64        StatusIconState::Uninitialized
65    };
66
67    let class_name = match &state {
68        StatusIconState::Errored(_, _, _, true) => "errored",
69        StatusIconState::Errored(_, _, _, false) => "errored disabled",
70        StatusIconState::Normal => "connected",
71        StatusIconState::Updating => "updating",
72        StatusIconState::Loading => "loading",
73        StatusIconState::Pending => "pending",
74        StatusIconState::Uninitialized => "uninitialized",
75    };
76
77    let onclick = use_async_callback(
78        (props.session.clone(), props.renderer.clone(), state.clone()),
79        async move |_: MouseEvent, (session, renderer, state)| {
80            match &state {
81                StatusIconState::Errored(..) => {
82                    if renderer.failure().is_some() {
83                        session.reset_failed_config().await?;
84                    }
85
86                    session.reconnect().await?;
87                    apply_and_render(session, renderer, ViewConfigUpdate::default())?.await?;
88                },
89                StatusIconState::Normal => {
90                    session.status_indicator_clicked.emit(());
91                },
92                _ => {},
93            };
94
95            Ok::<_, ApiError>(())
96        },
97    );
98
99    html! {
100        <>
101            <div class="section">
102                <div id="status_reconnect" class={class_name} {onclick}>
103                    <span id="status" class={class_name} />
104                    <span id="status_updating" class={class_name} />
105                </div>
106                if let StatusIconState::Errored(err, stack, kind, _) = &state {
107                    <div class="error-dialog">
108                        <div class="error-dialog-message">{ format!("{} {}", kind, err) }</div>
109                        <div class="error-dialog-stack">{ stack }</div>
110                    </div>
111                }
112            </div>
113        </>
114    }
115}
116
117#[derive(Clone, Debug, PartialEq)]
118enum StatusIconState {
119    Loading,
120    Pending,
121    Updating,
122    Errored(String, String, &'static str, bool),
123    Normal,
124    Uninitialized,
125}