perspective_viewer/components/
status_indicator.rs1use 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#[derive(PartialEq, Properties)]
28pub struct StatusIndicatorProps {
29 pub renderer: Renderer,
30 pub session: Session,
31
32 pub update_count: u32,
34 pub session_props: SessionProps,
35 pub render_error: Option<RenderError>,
36}
37
38#[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}