perspective_viewer/components/
status_indicator.rs1use perspective_client::config::ViewConfigUpdate;
14use web_sys::*;
15use yew::prelude::*;
16
17use crate::model::UpdateAndRender;
18use crate::renderer::*;
19use crate::session::*;
20use crate::utils::*;
21use crate::*;
22
23#[derive(Clone, Default, Debug, PartialEq)]
24enum StatusIconState {
25 Loading,
26 Updating(u32),
27 Errored(String, String, &'static str),
28 Normal,
29
30 #[default]
31 Unititialized,
32}
33
34#[derive(Clone, Debug)]
35enum StatusIconStateAction {
36 Increment,
37 Decrement,
38 Load(Option<ViewStats>),
39 SetError(ApiError),
40}
41
42impl Reducible for StatusIconState {
43 type Action = StatusIconStateAction;
44
45 fn reduce(self: std::rc::Rc<Self>, action: Self::Action) -> std::rc::Rc<Self> {
46 let new_status = match (&*self, action.clone()) {
47 (StatusIconState::Updating(x), StatusIconStateAction::Increment) => {
48 Self::Updating(x + 1)
49 },
50 (StatusIconState::Updating(x), StatusIconStateAction::Decrement) if *x > 1 => {
51 Self::Updating(x - 1)
52 },
53 (_, StatusIconStateAction::Load(stats)) => {
54 if stats.and_then(|x| x.num_table_cells).is_some() {
55 StatusIconState::Normal
56 } else {
57 Self::Loading
58 }
59 },
60 (_, StatusIconStateAction::SetError(e)) => {
61 Self::Errored(e.message(), e.stacktrace(), e.kind())
62 },
63 (
64 StatusIconState::Loading,
65 StatusIconStateAction::Increment | StatusIconStateAction::Decrement,
66 ) => StatusIconState::Loading,
67 (_, StatusIconStateAction::Increment) => Self::Updating(1),
68 (_, StatusIconStateAction::Decrement) => StatusIconState::Normal,
69 };
70
71 new_status.into()
72 }
73}
74
75#[extend::ext]
76impl<T: Reducible + 'static> UseReducerDispatcher<T> {
77 fn callback<U>(&self, action: impl Fn(U) -> T::Action + 'static) -> Callback<U> {
78 let dispatcher = self.clone();
79 Callback::from(move |event| dispatcher.dispatch(action(event)))
80 }
81}
82
83#[derive(Clone, Properties, PartialEq)]
84pub struct StatusIndicatorProps {
85 pub session: Session,
86 pub renderer: Renderer,
87}
88
89derive_model!(Session, Renderer for StatusIndicatorProps);
90
91#[function_component]
95pub fn StatusIndicator(props: &StatusIndicatorProps) -> Html {
96 let state = use_reducer_eq(|| {
97 if let Some(err) = props.session.get_error() {
98 StatusIconState::Errored(err.message(), err.stacktrace(), err.kind())
99 } else {
100 StatusIconState::Normal
101 }
102 });
103
104 use_effect_with(
105 (props.session.clone(), state.dispatcher()),
106 |(session, set_state)| {
107 let subs = [
108 session
109 .table_errored
110 .add_listener(set_state.callback(StatusIconStateAction::SetError)),
111 session
112 .stats_changed
113 .add_listener(set_state.callback(StatusIconStateAction::Load)),
114 session
115 .view_config_changed
116 .add_listener(set_state.callback(|_| StatusIconStateAction::Increment)),
117 session
118 .view_created
119 .add_listener(set_state.callback(|_| StatusIconStateAction::Decrement)),
120 ];
121
122 move || drop(subs)
123 },
124 );
125
126 let onclick = use_async_callback(
127 (props.clone(), state.clone()),
128 async move |_: MouseEvent, (props, state)| {
129 if let StatusIconState::Errored(..) = &**state {
130 props.session.reconnect().await?;
131 let cfg = ViewConfigUpdate::default();
132 props.update_and_render(cfg)?.await?;
133 }
134
135 Ok::<_, ApiError>(())
136 },
137 );
138
139 let class_name = match (&*state, props.session.is_reconnect()) {
140 (StatusIconState::Errored(..), true) => "errored",
141 (StatusIconState::Errored(..), false) => "errored disabled",
142 (StatusIconState::Normal, _) => "connected",
143 (StatusIconState::Updating(_), _) => "updating",
144 (StatusIconState::Loading, _) => "loading",
145 (StatusIconState::Unititialized, _) => "uninitialized",
146 };
147
148 html! {
149 <>
150 <div class="section">
151 <div id="status_reconnect" class={class_name} {onclick}>
152 <span id="status" class={class_name} />
153 <span id="status_updating" class={class_name} />
154 </div>
155 if let StatusIconState::Errored(err, stack, kind) = &*state {
156 <div class="error-dialog">
157 <div class="error-dialog-message">{ format!("{} {}", kind, err) }</div>
158 <div class="error-dialog-stack">{ stack }</div>
159 </div>
160 }
161 </div>
162 </>
163 }
164}