fix(ui): render the connectivity status bar message properly
This commit is contained in:
parent
1fd587a840
commit
875c667313
3 changed files with 163 additions and 22 deletions
|
|
@ -121,19 +121,6 @@ impl Component for StatusBanner {
|
|||
});
|
||||
let p = intro.get().value();
|
||||
|
||||
let content = rect()
|
||||
.horizontal()
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.child(Icon::new(issue.icon()).size(15.).color(Color::WHITE))
|
||||
.child(
|
||||
label()
|
||||
.text(issue.message())
|
||||
.font_size(12.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.color(Color::WHITE),
|
||||
);
|
||||
|
||||
let close = issue.closeable().then(|| {
|
||||
rect()
|
||||
.position(Position::new_absolute().right(10.))
|
||||
|
|
@ -147,8 +134,12 @@ impl Component for StatusBanner {
|
|||
dismissed.write().insert(issue);
|
||||
})
|
||||
.child(Icon::new(IconType::XClose).size(14.).color(Color::WHITE))
|
||||
.into_element()
|
||||
});
|
||||
|
||||
// The icon+message row is laid out directly in the bar rather than in a
|
||||
// nested auto-sized wrapper: a transparent rect with no explicit size
|
||||
// measures to nothing here, which is what was swallowing the message.
|
||||
rect()
|
||||
.width(Size::window_percent(100.))
|
||||
.height(Size::px(BAR_HEIGHT))
|
||||
|
|
@ -156,14 +147,20 @@ impl Component for StatusBanner {
|
|||
.layer(Layer::OverlayLevel(u8::MAX - 20))
|
||||
.background(issue.background())
|
||||
.opacity(p)
|
||||
.horizontal()
|
||||
.main_align(Alignment::Center)
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(8.)
|
||||
.padding(Gaps::new_symmetric(0., 40.))
|
||||
.child(Icon::new(issue.icon()).size(15.).color(Color::WHITE))
|
||||
.child(
|
||||
rect()
|
||||
.width(Size::fill())
|
||||
.height(Size::fill())
|
||||
.center()
|
||||
.padding(Gaps::new_symmetric(0., 16.))
|
||||
.child(content)
|
||||
.maybe_child(close),
|
||||
label()
|
||||
.text(issue.message())
|
||||
.font_size(12.)
|
||||
.font_weight(FontWeight::MEDIUM)
|
||||
.max_lines(1)
|
||||
.color(Color::WHITE),
|
||||
)
|
||||
.maybe_child(close)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use freya::router::RouterContext;
|
|||
use oneclient_db::console::{ConsoleQueryResult, run_console_query};
|
||||
|
||||
use oneclient_auth::preview_samples;
|
||||
use oneclient_net::status::{self, ServiceStatus};
|
||||
|
||||
use crate::components::{Button, Icon, IconType, TextInput, login_dialog, toggle};
|
||||
use crate::hooks::use_dispatch;
|
||||
|
|
@ -76,6 +77,11 @@ impl Component for Debug {
|
|||
vec![LauncherUpdateSimulator.into_element()],
|
||||
))
|
||||
.child(divider())
|
||||
.child(section(
|
||||
"Connectivity Status Bar",
|
||||
vec![StatusBarSimulator.into_element()],
|
||||
))
|
||||
.child(divider())
|
||||
.child(section(
|
||||
"Auth Error Guidance",
|
||||
vec![AuthGuidancePreview.into_element()],
|
||||
|
|
@ -468,6 +474,114 @@ impl Component for LauncherUpdateSimulator {
|
|||
}
|
||||
}
|
||||
|
||||
/// Pins `oneclient_net::status` so the connectivity banner can be looked at
|
||||
/// without unplugging anything. Each preset is one banner the bar knows how to
|
||||
/// draw; the bar itself picks the first active issue.
|
||||
#[derive(PartialEq)]
|
||||
struct StatusBarSimulator;
|
||||
|
||||
const STATUS_PRESETS: [(&str, IconType, ServiceStatus); 4] = [
|
||||
(
|
||||
"No internet",
|
||||
IconType::Globe01,
|
||||
ServiceStatus {
|
||||
online: false,
|
||||
mc_auth_up: false,
|
||||
polyfrost_up: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"Minecraft auth down",
|
||||
IconType::AlertTriangle,
|
||||
ServiceStatus {
|
||||
online: true,
|
||||
mc_auth_up: false,
|
||||
polyfrost_up: true,
|
||||
},
|
||||
),
|
||||
(
|
||||
"Polyfrost down",
|
||||
IconType::AlertCircle,
|
||||
ServiceStatus {
|
||||
online: true,
|
||||
mc_auth_up: true,
|
||||
polyfrost_up: false,
|
||||
},
|
||||
),
|
||||
(
|
||||
"Both services down",
|
||||
IconType::AlertTriangle,
|
||||
ServiceStatus {
|
||||
online: true,
|
||||
mc_auth_up: false,
|
||||
polyfrost_up: false,
|
||||
},
|
||||
),
|
||||
];
|
||||
|
||||
impl Component for StatusBarSimulator {
|
||||
fn render(&self) -> impl IntoElement {
|
||||
let mut pinned = use_state(status::forced);
|
||||
|
||||
let mut row = rect().horizontal().width(Size::fill()).spacing(12.);
|
||||
for (text, icon, forced) in STATUS_PRESETS {
|
||||
row = row.child(
|
||||
Button::new()
|
||||
.secondary()
|
||||
.child(Icon::new(icon).size(16.))
|
||||
.text(text)
|
||||
.on_press(move |_| {
|
||||
status::force(Some(forced));
|
||||
pinned.set(Some(forced));
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
let current = *pinned.read();
|
||||
let state_text = match current {
|
||||
Some(_) => "Pinned. Probing still runs, but its result is not published.".to_string(),
|
||||
None => "Not pinned; showing the real probe result.".to_string(),
|
||||
};
|
||||
|
||||
rect()
|
||||
.vertical()
|
||||
.width(Size::fill())
|
||||
.spacing(10.)
|
||||
.child(
|
||||
label()
|
||||
.text("Forces what the connectivity prober reports, so the real status bar renders its banner. Dismissing a banner is remembered until the issue clears.")
|
||||
.font_size(13.)
|
||||
.color(colors::fg_secondary()),
|
||||
)
|
||||
.child(row)
|
||||
.child(
|
||||
rect()
|
||||
.horizontal()
|
||||
.width(Size::fill())
|
||||
.cross_align(Alignment::Center)
|
||||
.spacing(12.)
|
||||
.child(
|
||||
Button::new()
|
||||
.primary()
|
||||
.child(Icon::new(IconType::RefreshCw01).size(16.))
|
||||
.text("Clear + recheck")
|
||||
.enabled(current.is_some())
|
||||
.on_press(move |_| {
|
||||
status::force(None);
|
||||
pinned.set(None);
|
||||
}),
|
||||
)
|
||||
.child(
|
||||
label()
|
||||
.text(state_text)
|
||||
.font_size(13.)
|
||||
.color(colors::fg_secondary()),
|
||||
),
|
||||
)
|
||||
.into_element()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
struct AuthGuidancePreview;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::OnceLock;
|
||||
use std::sync::{OnceLock, RwLock};
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -34,6 +34,9 @@ struct Connectivity {
|
|||
rx: watch::Receiver<ServiceStatus>,
|
||||
notify: Notify,
|
||||
started: AtomicBool,
|
||||
/// Debug-only pin. While set, probe results are recorded but never
|
||||
/// published, so the forced status survives a recheck.
|
||||
forced: RwLock<Option<ServiceStatus>>,
|
||||
}
|
||||
|
||||
fn connectivity() -> &'static Connectivity {
|
||||
|
|
@ -45,10 +48,34 @@ fn connectivity() -> &'static Connectivity {
|
|||
rx,
|
||||
notify: Notify::new(),
|
||||
started: AtomicBool::new(false),
|
||||
forced: RwLock::new(None),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Pins the reported status to `status`, or releases the pin with `None` and
|
||||
/// re-probes. Exposed for the debug page: the connectivity banner is otherwise
|
||||
/// only reachable by actually taking a service down.
|
||||
pub fn force(status: Option<ServiceStatus>) {
|
||||
let conn = connectivity();
|
||||
*conn.forced.write().unwrap_or_else(|e| e.into_inner()) = status;
|
||||
|
||||
match status {
|
||||
Some(status) => {
|
||||
let _ = conn.tx.send(status);
|
||||
}
|
||||
None => request_recheck(),
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn forced() -> Option<ServiceStatus> {
|
||||
*connectivity()
|
||||
.forced
|
||||
.read()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
pub fn subscribe() -> watch::Receiver<ServiceStatus> {
|
||||
connectivity().rx.clone()
|
||||
}
|
||||
|
|
@ -78,7 +105,10 @@ pub fn start(client: RequestClient) {
|
|||
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
let _ = conn.tx.send(check_service_status(&client).await);
|
||||
let probed = check_service_status(&client).await;
|
||||
if forced().is_none() {
|
||||
let _ = conn.tx.send(probed);
|
||||
}
|
||||
conn.notify.notified().await;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue