perspective_viewer/components/
status_indicator.rs1use perspective_client::config::ViewConfigUpdate;
14use perspective_js::utils::ApiError;
15use wasm_bindgen::JsValue;
16use web_sys::*;
17use yew::prelude::*;
18
19use crate::custom_events::CustomEvents;
20use crate::renderer::Renderer;
21use crate::session::{Session, TableErrorState, TableLoadState, ViewStats};
22use crate::utils::*;
23
24#[derive(PartialEq, Properties)]
29pub struct StatusIndicatorProps {
30 pub custom_events: CustomEvents,
31 pub renderer: Renderer,
32 pub session: Session,
33 pub update_count: u32,
35 pub error: Option<TableErrorState>,
37 pub has_table: Option<TableLoadState>,
39 pub stats: Option<ViewStats>,
41}
42
43#[function_component]
47pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
48 let has_table_cells = props
49 .stats
50 .as_ref()
51 .and_then(|s| s.num_table_cells)
52 .is_some();
53
54 let state = if let Some(err) = &props.error {
55 StatusIconState::Errored(
56 err.message(),
57 err.stacktrace(),
58 err.kind(),
59 err.is_reconnect(),
60 )
61 } else if !has_table_cells && matches!(props.has_table, Some(TableLoadState::Loading)) {
62 StatusIconState::Loading
63 } else if props.update_count > 0 {
64 StatusIconState::Updating
65 } else if has_table_cells {
66 StatusIconState::Normal
67 } else {
68 StatusIconState::Uninitialized
69 };
70
71 let class_name = match &state {
72 StatusIconState::Errored(_, _, _, true) => "errored",
73 StatusIconState::Errored(_, _, _, false) => "errored disabled",
74 StatusIconState::Normal => "connected",
75 StatusIconState::Updating => "updating",
76 StatusIconState::Loading => "loading",
77 StatusIconState::Uninitialized => "uninitialized",
78 };
79
80 let onclick = use_async_callback(
81 (
82 props.session.clone(),
83 props.renderer.clone(),
84 props.custom_events.clone(),
85 state.clone(),
86 ),
87 async move |_: MouseEvent, (session, renderer, custom_events, state)| {
88 match &state {
89 StatusIconState::Errored(..) => {
90 session.reconnect().await?;
91 let cfg = ViewConfigUpdate::default();
92 session.update_view_config(cfg)?;
93 renderer.apply_pending_plugin()?;
94 renderer
95 .draw(session.validate().await?.create_view())
96 .await?;
97 },
98 StatusIconState::Normal => {
99 custom_events.dispatch_event("status-indicator-click", JsValue::UNDEFINED)?;
100 },
101 _ => {},
102 };
103
104 Ok::<_, ApiError>(())
105 },
106 );
107
108 html! {
109 <>
110 <div class="section">
111 <div id="status_reconnect" class={class_name} {onclick}>
112 <span id="status" class={class_name} />
113 <span id="status_updating" class={class_name} />
114 </div>
115 if let StatusIconState::Errored(err, stack, kind, _) = &state {
116 <div class="error-dialog">
117 <div class="error-dialog-message">{ format!("{} {}", kind, err) }</div>
118 <div class="error-dialog-stack">{ stack }</div>
119 </div>
120 }
121 </div>
122 </>
123 }
124}
125
126#[derive(Clone, Debug, PartialEq)]
127enum StatusIconState {
128 Loading,
129 Updating,
130 Errored(String, String, &'static str, bool),
131 Normal,
132 Uninitialized,
133}