use std::{any::TypeId, collections::HashMap, sync::Arc};
use topcoat::context::{Cx, try_context};
use super::{
relations::Child,
shell::{Brand, ShellAssets},
};
use crate::{
auth::{CurrentRealm, Realm},
navigation::NavigationItem,
page::MountedPage,
resource::Mounts,
upload::InstalledUploader,
};
#[derive(Clone)]
pub(crate) struct NavEntry {
pub(crate) item: NavigationItem,
pub(crate) visible: Arc<dyn Fn(&Cx) -> bool + Send + Sync>,
pub(crate) resource_list: Option<ResourceTrail>,
pub(crate) badge: Option<BadgeFn>,
pub(crate) area: Option<AreaSlot>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct AreaSlot {
pub(crate) area: usize,
pub(crate) tenant: bool,
}
#[derive(Debug, Clone)]
pub(crate) struct AreaNav {
pub(crate) url: String,
pub(crate) label: crate::Text,
}
#[derive(Debug, Clone)]
pub(crate) struct ResourceTrail {
pub(crate) list: String,
pub(crate) viewed: bool,
pub(crate) pages: Vec<(String, crate::Text)>,
}
#[derive(Clone)]
pub(crate) struct Searchable {
pub(crate) label: crate::Text,
pub(crate) list: String,
pub(crate) visible: fn(&Cx) -> bool,
pub(crate) search: SearchFn,
}
pub(crate) type SearchFn =
for<'a> fn(
&'a Cx,
&'a [String],
usize,
) -> crate::BoxFuture<'a, topcoat::Result<Vec<crate::resource::SearchHit>>>;
pub(crate) type BadgeFn =
Arc<dyn for<'a> Fn(&'a Cx) -> crate::BoxFuture<'a, Option<String>> + Send + Sync>;
pub(crate) struct PanelState {
pub(crate) realm: Arc<Realm>,
pub(crate) nav_items: Vec<NavEntry>,
pub(crate) areas: Vec<AreaNav>,
pub(crate) brand: Option<Brand>,
pub(crate) dark_mode: bool,
pub(crate) environment: Option<super::Environment>,
pub(crate) shell_assets: Option<ShellAssets>,
pub(crate) children: HashMap<TypeId, Child>,
pub(crate) mounts: Arc<Mounts>,
pub(crate) root_redirect: Option<String>,
pub(crate) user_menu: Vec<NavigationItem>,
pub(crate) banners: Vec<super::banner::BannerFn>,
pub(crate) uploads: Option<InstalledUploader>,
pub(crate) served_paths: Vec<String>,
pub(crate) signed_roots: Vec<String>,
pub(crate) urls: HashMap<TypeId, String>,
pub(crate) mounted_pages: HashMap<TypeId, Arc<MountedPage>>,
pub(crate) endpoints: Vec<String>,
pub(crate) searches: Vec<Searchable>,
pub(crate) limits: crate::Limits,
pub(crate) commit_hooks: Vec<super::commit_hook::CommitHookFn>,
}
pub(crate) fn under_prefix(prefix: &str, path: &str) -> bool {
path.strip_prefix(prefix)
.is_some_and(|rest| rest.is_empty() || rest.starts_with(['/', '?']))
}
impl PanelState {
pub(crate) fn area_at(&self, path: &str) -> Option<usize> {
self.areas
.iter()
.position(|area| under_prefix(&area.url, path))
}
}
#[derive(Default, Clone)]
pub(crate) struct Panels(pub(crate) Vec<Arc<PanelState>>);
impl Panels {
pub(crate) fn by_prefix(&self, prefix: &str) -> Option<&Arc<PanelState>> {
self.0.iter().find(|panel| panel.realm.prefix == prefix)
}
pub(crate) fn declaring<'a>(
&'a self,
path: &'a str,
) -> impl Iterator<Item = &'a Arc<PanelState>> {
let bare = |path: &'a str| path.strip_suffix('/').unwrap_or(path);
self.0.iter().filter(move |panel| {
panel
.endpoints
.iter()
.any(|endpoint| bare(endpoint) == bare(path))
})
}
}
#[derive(Clone)]
pub(crate) struct CurrentPanel(pub(crate) Arc<PanelState>);
pub(crate) fn current(cx: &Cx) -> Option<&Arc<PanelState>> {
try_context::<CurrentPanel>(cx).map(|CurrentPanel(panel)| panel)
}
pub(crate) fn enter(cx: &Cx, panel: &Arc<PanelState>) -> Cx {
let cx = crate::i18n::entered(cx, &panel.realm.catalog);
cx.with_many((
CurrentPanel(Arc::clone(panel)),
CurrentRealm(Arc::clone(&panel.realm)),
panel.limits,
super::list::ShownList::default(),
))
}
pub(crate) fn uploader(cx: &Cx) -> Option<&InstalledUploader> {
current(cx)?.uploads.as_ref()
}
pub(crate) fn panels(cx: &Cx) -> Option<&Panels> {
try_context::<Panels>(cx)
}