use super::state::{TimedAlert, ToastState, ALERTS};
use anyhow::Error;
use rate_ui::shared_object::{DataChanged, SharedObject};
use rate_ui::widget::{Context, NotificationHandler, Widget, WidgetRuntime};
use std::time::Duration;
use timeago::Formatter;
use yew::{html, Html};
const REFRESH_INTERVAL: u64 = 1_000;
const RETAIN_INTERVAL: u64 = 5_600;
pub type AlertToast = WidgetRuntime<AlertToastWidget>;
pub struct AlertToastWidget {
formatter: Formatter,
alerts: SharedObject<ToastState>,
}
impl Default for AlertToastWidget {
fn default() -> Self {
Self {
formatter: Default::default(),
alerts: ALERTS.with(SharedObject::clone),
}
}
}
#[derive(Clone)]
pub enum Msg {
Hide(usize),
TimerRefresh,
}
impl Widget for AlertToastWidget {
type Event = Msg;
type Tag = ();
type Properties = ();
type Meta = ();
fn init(&mut self, ctx: &mut Context<Self>) {
self.alerts.subscribe(ctx);
self.reschedule(ctx);
}
fn on_event(&mut self, event: Self::Event, ctx: &mut Context<Self>) {
let mut state = self.alerts.write();
match event {
Msg::Hide(idx) => {
state.alerts.remove(idx);
ctx.redraw();
}
Msg::TimerRefresh => {
let deadline = js_sys::Date::now() as u64 - RETAIN_INTERVAL;
state.alerts.retain(|alert| alert.ms > deadline);
drop(state);
self.reschedule(ctx);
ctx.redraw();
}
}
}
fn view(&self, ctx: &Context<Self>) -> Html {
let state = self.alerts.read();
let timestamp = Duration::from_millis(js_sys::Date::now() as u64);
html! {
<div class="position-fixed bottom-0 end-0 p-3" style="z-index: 50000">
<div class="toast-container">
{ for state.alerts.iter().enumerate().take(10).map(|(idx, msg)| self.render_alert(idx, msg, timestamp, ctx)) }
</div>
</div>
}
}
}
impl AlertToastWidget {
fn render_alert(
&self,
idx: usize,
item: &TimedAlert,
from: Duration,
ctx: &Context<Self>,
) -> Html {
let ago = self
.formatter
.convert(from - Duration::from_millis(item.ms));
html! {
<div class="toast show">
<div class="toast-header">
<strong class="me-auto">{ &item.origin }</strong>
<small>{ ago }</small>
<button type="button" class="btn-close"
onclick=ctx.event(Msg::Hide(idx))
></button>
</div>
<div class="toast-body">{ &item.message }</div>
</div>
}
}
fn reschedule(&mut self, ctx: &mut Context<Self>) {
let state = self.alerts.read();
if !state.alerts.is_empty() {
ctx.schedule(REFRESH_INTERVAL, Msg::TimerRefresh);
} else {
ctx.unschedule();
}
}
}
impl NotificationHandler<DataChanged<ToastState>> for AlertToastWidget {
fn handle(
&mut self,
_event: DataChanged<ToastState>,
ctx: &mut Context<Self>,
) -> Result<(), Error> {
self.reschedule(ctx);
ctx.redraw();
Ok(())
}
}