mod actions;
mod build;
mod detail;
mod forms;
pub(crate) mod gate;
mod headers;
mod list;
mod pages;
mod register;
mod relations;
mod search;
mod shell;
pub(crate) mod state;
#[cfg(test)]
pub(crate) mod test_support;
pub mod url;
mod write;
use std::path::PathBuf;
use topcoat::{asset::Asset, font::Font, router::LayoutRenderFn};
#[cfg(test)]
pub(crate) use self::search::TABLE_SEARCH_PATH;
pub use self::{build::RouterBuilderPanelExt, gate::can_list, shell::Brand};
use self::{
build::is_directory_pattern,
register::{PageRegistration, Registration, ResourceRegistration},
shell::ShellAssets,
};
pub(crate) use self::{build::route_path, forms::parse_form_body, gate::panel_prefix};
use crate::{
DeclarationError, DeclarationErrorKind, Page,
resource::{Resource, ResourceDef},
};
pub fn wired_table<R: Resource>(
cx: &topcoat::context::Cx,
) -> topcoat::Result<crate::table::Table<R::Model>> {
let resource = crate::resource::require_mounted::<R>(cx)?;
Ok(self::list::wire_table_actions(cx, &resource, false))
}
pub fn navigation<R: Resource>(cx: &topcoat::context::Cx) -> Option<crate::NavigationItem> {
topcoat::context::try_app_context::<crate::resource::MountScope>(cx)?;
crate::resource::mounted::<R>(cx).map(|resource| resource.navigation.clone())
}
pub struct Panel {
prefix: String,
shell_assets: Option<ShellAssets>,
brand: Option<Brand>,
dark_mode: Option<bool>,
layout: Option<LayoutRenderFn>,
registrations: Vec<Box<dyn Registration>>,
frame_ancestors: Option<String>,
configuration_errors: Vec<DeclarationError>,
uploads: Option<crate::upload::InstalledUploader>,
served_dirs: Vec<(String, PathBuf)>,
login_hint: Option<String>,
auth: crate::auth::Auth,
}
impl Panel {
pub fn new(prefix: impl Into<String>) -> Self {
let raw = prefix.into();
let trimmed = raw.trim().trim_matches('/').to_string();
let prefix = if trimmed.is_empty() {
"/admin".to_string()
} else {
format!("/{trimmed}")
};
let configuration_errors = prefix
.trim_matches('/')
.split('/')
.filter_map(|segment| build::validate_route_segment("panel prefix", segment).err())
.map(DeclarationError::panel)
.collect();
Self {
prefix,
shell_assets: None,
brand: None,
dark_mode: None,
layout: None,
registrations: Vec::new(),
frame_ancestors: Some(headers::DEFAULT_FRAME_ANCESTORS.to_string()),
configuration_errors,
uploads: None,
served_dirs: Vec::new(),
login_hint: None,
auth: crate::auth::Auth::default(),
}
}
pub fn prefix(&self) -> &str {
&self.prefix
}
pub fn uploads(mut self, uploader: impl crate::Uploader) -> Self {
self.uploads = Some(crate::upload::InstalledUploader::new(uploader));
self
}
pub fn serve_dir(mut self, path: impl Into<String>, dir: impl Into<PathBuf>) -> Self {
let path = path.into();
if !is_directory_pattern(&path) {
self.configuration_errors.push(DeclarationError::panel(
DeclarationErrorKind::ServeDirWithoutCatchAll { path: path.clone() },
));
}
self.served_dirs.push((path, dir.into()));
self
}
pub fn shell_assets(mut self, stylesheet: Asset, font: Font) -> Self {
self.shell_assets = Some(ShellAssets { stylesheet, font });
self
}
pub fn resource<R: Resource>(self) -> Self {
self.resource_with::<R>(|def| def)
}
pub fn resource_with<R: Resource>(
mut self,
customize: impl FnOnce(ResourceDef<R>) -> ResourceDef<R> + Send + 'static,
) -> Self {
self.registrations
.push(Box::new(ResourceRegistration::<R>(Box::new(customize))));
self
}
pub fn page<P: Page>(mut self) -> Self {
self.registrations.push(Box::new(PageRegistration::<P> {
home: false,
_page: std::marker::PhantomData,
}));
self
}
pub fn home<P: Page>(mut self) -> Self {
self.registrations.push(Box::new(PageRegistration::<P> {
home: true,
_page: std::marker::PhantomData,
}));
self
}
pub fn layout(mut self, render: LayoutRenderFn) -> Self {
self.layout = Some(render);
self
}
pub fn brand(mut self, brand: Brand) -> Self {
self.brand = Some(brand);
self
}
pub fn frame_ancestors(mut self, ancestors: impl Into<String>) -> Self {
self.frame_ancestors = Some(ancestors.into());
self
}
pub fn without_frame_ancestors(mut self) -> Self {
self.frame_ancestors = None;
self
}
pub fn dark_mode(mut self, enabled: bool) -> Self {
self.dark_mode = Some(enabled);
self
}
pub fn auth(mut self, auth: crate::auth::Auth) -> Self {
self.auth = auth;
self
}
pub fn login_hint(mut self, hint: impl Into<String>) -> Self {
self.login_hint = Some(hint.into());
self
}
}
enum Root {
Redirect(String),
Home,
}
#[cfg(test)]
mod tests;