use crate::i18n::Text;
use crate::screen::ScreenDef;
pub struct Shell;
impl Shell {
pub fn site() -> SiteShell {
SiteShell::default()
}
}
#[derive(Default)]
pub struct SiteShell {
pub(crate) header: SiteHeader,
pub(crate) footer: SiteFooter,
}
impl SiteShell {
pub fn header(mut self, build: impl FnOnce(SiteHeader) -> SiteHeader) -> Self {
self.header = build(self.header);
self
}
pub fn footer(mut self, build: impl FnOnce(SiteFooter) -> SiteFooter) -> Self {
self.footer = build(self.footer);
self
}
pub(crate) fn linked_screens(&self) -> impl Iterator<Item = &'static str> + '_ {
self.footer.columns.iter().flat_map(|c| &c.links).filter_map(|l| match l.target {
FooterTarget::Screen(s) => Some(s),
FooterTarget::Url(_) => None,
})
}
pub(crate) fn bad_urls(&self) -> impl Iterator<Item = &'static str> + '_ {
self.footer.columns.iter().flat_map(|c| &c.links).filter_map(|l| match l.target {
FooterTarget::Url(u) if !safe_url(u) => Some(u),
_ => None,
})
}
}
fn safe_url(url: &str) -> bool {
(url.starts_with('/') && !url.starts_with("//"))
|| ["https://", "http://", "mailto:"].iter().any(|p| url.starts_with(p))
}
#[derive(Default)]
pub struct SiteHeader {
pub(crate) sign_in: bool,
pub(crate) language: bool,
pub(crate) theme: bool,
}
impl SiteHeader {
pub fn sign_in(mut self) -> Self {
self.sign_in = true;
self
}
pub fn language(mut self) -> Self {
self.language = true;
self
}
pub fn theme(mut self) -> Self {
self.theme = true;
self
}
}
#[derive(Default)]
pub struct SiteFooter {
pub(crate) columns: Vec<FooterColumn>,
pub(crate) text: Option<Text>,
}
impl SiteFooter {
pub fn column(mut self, title: impl Into<Text>, build: impl FnOnce(FooterColumn) -> FooterColumn) -> Self {
self.columns.push(build(FooterColumn { title: title.into(), links: Vec::new() }));
self
}
pub fn text(mut self, text: impl Into<Text>) -> Self {
self.text = Some(text.into());
self
}
}
pub(crate) enum FooterTarget {
Screen(&'static str),
Url(&'static str),
}
pub(crate) struct FooterLink {
pub(crate) label: Text,
pub(crate) target: FooterTarget,
}
pub struct FooterColumn {
pub(crate) title: Text,
pub(crate) links: Vec<FooterLink>,
}
impl FooterColumn {
pub fn link<S: ScreenDef>(mut self, label: impl Into<Text>, _screen: S) -> Self {
const { assert!(S::META.route.is_some(), "a footer link must point to a screen that has a route") };
self.links.push(FooterLink { label: label.into(), target: FooterTarget::Screen(S::META.name) });
self
}
pub fn url(mut self, label: impl Into<Text>, href: &'static str) -> Self {
self.links.push(FooterLink { label: label.into(), target: FooterTarget::Url(href) });
self
}
}