use std::marker::PhantomData;
use std::sync::{Arc, RwLock};
use frunk::{HCons, HNil, hlist::HList};
use maud::{Markup, html};
use crate::{
app::App,
capability::{
ApplyHooks, CapStore, Capability, FoldRegistrarHooks, apply_registrar_hook,
mount_with_hooks,
},
tag::Tagged,
traits::{
add::{AddCapability, CapTagAbsent},
get::GetByTag,
replace::MapByTag,
},
};
pub struct SlotTag;
pub struct HeadSlotTag;
pub struct TopbarItemsSlotTag;
pub struct RightSidebarSlotTag;
#[derive(Clone, Debug, Default)]
pub struct SlotCtx {
pub name: Option<String>,
pub role: Option<String>,
pub is_superuser: bool,
pub is_staff: bool,
}
#[derive(Clone)]
pub struct ShellChrome {
pub head: Markup,
pub topbar_items: Markup,
pub right_sidebar: Markup,
}
impl Default for ShellChrome {
fn default() -> Self {
Self {
head: html! {
title { (document_title()) }
},
topbar_items: Markup::default(),
right_sidebar: Markup::default(),
}
}
}
pub trait RenderSlot {
fn render_slot(&self, ctx: &SlotCtx) -> Markup;
}
pub struct SlotOf<Kind, T> {
_kind: PhantomData<Kind>,
_slot: PhantomData<fn() -> T>,
}
impl<Kind, T> Clone for SlotOf<Kind, T> {
fn clone(&self) -> Self {
*self
}
}
impl<Kind, T> Copy for SlotOf<Kind, T> {}
impl<Kind, T> Default for SlotOf<Kind, T> {
fn default() -> Self {
Self::new()
}
}
impl<Kind, T> SlotOf<Kind, T> {
pub const fn new() -> Self {
Self {
_kind: PhantomData,
_slot: PhantomData,
}
}
}
pub trait SlotBucket {
fn push(chrome: &mut ShellChrome, markup: Markup);
}
impl SlotBucket for HeadSlotTag {
fn push(chrome: &mut ShellChrome, markup: Markup) {
chrome.head = html! {
(chrome.head.clone())
(markup)
};
}
}
impl SlotBucket for TopbarItemsSlotTag {
fn push(chrome: &mut ShellChrome, markup: Markup) {
chrome.topbar_items = html! {
(chrome.topbar_items.clone())
(markup)
};
}
}
impl SlotBucket for RightSidebarSlotTag {
fn push(chrome: &mut ShellChrome, markup: Markup) {
chrome.right_sidebar = html! {
(chrome.right_sidebar.clone())
(markup)
};
}
}
pub trait FoldSlots {
fn fold_chrome(&self, ctx: &SlotCtx) -> ShellChrome;
}
impl FoldSlots for HNil {
fn fold_chrome(&self, _: &SlotCtx) -> ShellChrome {
ShellChrome::default()
}
}
impl<Tag, Kind, T, Tail> FoldSlots for HCons<Tagged<Tag, SlotOf<Kind, T>>, Tail>
where
Kind: SlotBucket,
T: RenderSlot + Default,
Tail: FoldSlots,
{
fn fold_chrome(&self, ctx: &SlotCtx) -> ShellChrome {
let mut chrome = self.tail.fold_chrome(ctx);
Kind::push(&mut chrome, T::default().render_slot(ctx));
chrome
}
}
pub trait FoldChrome: Send + Sync {
fn fold(&self, ctx: &SlotCtx) -> ShellChrome;
}
impl<Slots> FoldChrome for SlotCapability<Slots>
where
Slots: FoldSlots + Send + Sync,
{
fn fold(&self, ctx: &SlotCtx) -> ShellChrome {
self.fold_chrome(ctx)
}
}
pub type SharedChromeFolder = Arc<dyn FoldChrome>;
#[derive(Clone)]
pub struct SlotCapability<Slots> {
pub slots: Slots,
}
type SlotCapabilityAfterAdd<Tag, Kind, T, Slots> =
SlotCapability<HCons<Tagged<Tag, SlotOf<Kind, T>>, Slots>>;
impl SlotCapability<HNil> {
pub fn new() -> Self {
Self { slots: HNil }
}
}
impl Default for SlotCapability<HNil> {
fn default() -> Self {
Self::new()
}
}
impl<Slots> SlotCapability<Slots> {
pub fn add<Tag, Kind, T>(self) -> SlotCapabilityAfterAdd<Tag, Kind, T, Slots>
where
Slots: HList,
Kind: SlotBucket,
T: RenderSlot + Default + 'static,
{
SlotCapability {
slots: HCons {
head: Tagged::new(SlotOf::new()),
tail: self.slots,
},
}
}
pub fn get_slot<Tag, Index>(&self) -> &<Slots as GetByTag<Tag, Index>>::Value
where
Slots: GetByTag<Tag, Index>,
{
self.slots.get_by_tag()
}
pub fn replace_slot<Tag, Index, NewValue>(
self,
f: impl FnOnce(<Slots as MapByTag<Tag, NewValue, Index>>::OldValue) -> NewValue,
) -> SlotCapability<<Slots as MapByTag<Tag, NewValue, Index>>::Output>
where
Slots: MapByTag<Tag, NewValue, Index>,
{
SlotCapability {
slots: self.slots.map_by_tag(f),
}
}
pub fn fold_chrome(&self, ctx: &SlotCtx) -> ShellChrome
where
Slots: FoldSlots,
{
self.slots.fold_chrome(ctx)
}
pub fn fold(&self, ctx: &SlotCtx) -> ShellChrome
where
Slots: FoldSlots,
{
self.fold_chrome(ctx)
}
}
pub trait SlotRegistrar<T>: Sized {
type Output;
fn register_slots(self, cap: SlotCapability<T>) -> SlotCapability<Self::Output>;
}
pub type SlotCap<Hooks, Items> = CapStore<SlotTag, Hooks, Items>;
impl<Hooks, Items> SlotCap<Hooks, Items> {
pub fn resolve_hooks(
self,
) -> SlotCap<HNil, <Hooks as FoldRegistrarHooks<SlotTag, Items>>::Output>
where
Hooks: FoldRegistrarHooks<SlotTag, Items>,
{
CapStore::with_items(self.hooks.fold_registrar_hooks(self.items))
}
}
apply_registrar_hook! {
capability: SlotCapability;
trait: SlotRegistrar;
method: register_slots;
field: slots;
proof: crate::capability::SlotHookProof;
tag: SlotTag;
}
impl<Hooks, Items> Capability for SlotCap<Hooks, Items>
where
Hooks: ApplyHooks<Items>,
<Hooks as ApplyHooks<Items>>::Output: FoldSlots + Send + Sync + 'static,
{
type Value = SharedChromeFolder;
type Output = Tagged<SlotTag, SharedChromeFolder>;
type Hooks = Hooks;
type Items = Items;
fn mount(self) -> Self::Output {
mount_with_hooks(self, |items| {
Arc::new(SlotCapability { slots: items }) as SharedChromeFolder
})
}
}
static DOCUMENT_TITLE: RwLock<String> = RwLock::new(String::new());
pub fn document_title() -> String {
DOCUMENT_TITLE
.read()
.ok()
.map(|g| g.clone())
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "Lariv".into())
}
pub fn set_document_title(title: impl Into<String>) {
if let Ok(mut guard) = DOCUMENT_TITLE.write() {
*guard = title.into();
}
}
pub struct CoreTitleTag;
#[derive(Default)]
pub struct CoreTitle;
impl RenderSlot for CoreTitle {
fn render_slot(&self, _ctx: &SlotCtx) -> Markup {
let title = document_title();
html! {
title { (title) }
}
}
}
type DefaultSlots = HCons<Tagged<CoreTitleTag, SlotOf<HeadSlotTag, CoreTitle>>, HNil>;
pub fn with_slots<L, Proof>(app: App<L>) -> App<HCons<SlotCap<HNil, DefaultSlots>, L>>
where
L: HList + CapTagAbsent<SlotTag, Proof>,
{
app.add_capability(CapStore::with_items(
SlotCapability::new()
.add::<CoreTitleTag, HeadSlotTag, CoreTitle>()
.slots,
))
}