1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//! [`Banner`]: a strip the app shows above every page of a panel, such as a failed payment.
use std::future::Future;
use topcoat::{
Result,
context::Cx,
view::{BoxView, View, ViewExt, view},
};
use crate::topcoat_compat::async_page;
/// A strip above every page of a panel, under the bar that names whom a user acts as, installed
/// with [`Panel::banner`](crate::Panel::banner): what the signed-in user must see wherever they
/// are, such as a failed payment or an expiring trial.
///
/// ```rust
/// use tablo_core::{Banner, Panel, auth};
/// use topcoat::{
/// Result,
/// context::Cx,
/// view::{View, view},
/// };
///
/// struct PaymentFailed;
///
/// impl Banner for PaymentFailed {
/// async fn render(cx: &Cx) -> Result<Option<impl View>> {
/// // A real banner reads the signed-in user's billing state here.
/// let failed = auth::signed_in(cx) && false;
/// Ok(failed.then(|| view! { <div role="alert">"Your last payment failed."</div> }))
/// }
/// }
///
/// Panel::new("app").banner::<PaymentFailed>();
/// ```
pub trait Banner: Send + Sync + 'static {
/// The strip for this request, or `None` to show nothing. It is asked on every page the
/// panel's shell frames, signed in or not, and an error fails the page.
fn render(cx: &Cx) -> impl Future<Output = Result<Option<impl View>>> + Send;
}
/// A [`Banner`] with its type erased: what the shell renders.
pub(crate) type BannerFn = for<'a> fn(&'a Cx) -> BoxView<'a>;
/// `B`'s strip, or nothing.
pub(crate) fn erased<B: Banner>(cx: &Cx) -> BoxView<'_> {
async_page(async move {
Ok(match B::render(cx).await? {
Some(strip) => strip.boxed(),
None => view! {}.boxed(),
})
})
}