use std::any::TypeId;
use topcoat::context::Cx;
use super::state::current;
use crate::{
Page, RecordForm,
resource::{Action, Mounted, Places, Resource, mounted},
topcoat_compat::href,
};
pub fn panel(cx: &Cx) -> Option<String> {
current(cx).map(|panel| panel.realm.prefix.clone())
}
pub fn resource<R: Resource>(cx: &Cx) -> Option<String> {
registered::<R>(cx)
}
pub fn page<P: Page>(cx: &Cx) -> Option<String> {
registered::<P>(cx)
}
pub fn file(cx: &Cx, path: &str) -> String {
let signed = !path.contains(['?', '#'])
&& topcoat::context::try_app_context::<super::state::Panels>(cx).is_some_and(|panels| {
panels.0.iter().any(|panel| {
panel
.signed_roots
.iter()
.any(|root| path.starts_with(root.as_str()))
})
});
if signed {
crate::upload::signed::sign(cx, path, crate::upload::signed::now())
} else {
path.to_owned()
}
}
fn registered<T: 'static>(cx: &Cx) -> Option<String> {
current(cx).and_then(|panel| panel.urls.get(&TypeId::of::<T>()).cloned())
}
pub fn record<R: Resource>(cx: &Cx, record: &R::Model) -> Option<String> {
keyed::<R>(cx, record, Mounted::viewed).map(|(list, key)| row_view_url(&list, &key))
}
pub fn edit<R: Resource>(cx: &Cx, record: &R::Model) -> Option<String> {
keyed::<R>(cx, record, |_| <R::Form as RecordForm>::HAS_FORM)
.map(|(list, key)| row_edit_url(&list, &key))
}
pub fn delete<R: Resource>(cx: &Cx, record: &R::Model) -> Option<String> {
keyed::<R>(cx, record, |_| true).map(|(list, key)| delete_action_url(&list, &key))
}
pub fn create<R: Resource>(cx: &Cx) -> Option<String> {
mounted::<R>(cx)
.filter(|mounted| mounted.creates)
.map(|mounted| create_page_url(&mounted.url))
}
pub fn action<R: Resource, A: Action<R>>(cx: &Cx, record: &R::Model) -> Option<String> {
keyed::<R>(cx, record, |mounted| {
declares::<R, A>(mounted, Places::RECORD)
})
.map(|(list, key)| row_action_url(&list, &key, A::NAME))
}
pub fn bulk_action<R: Resource, A: Action<R>>(cx: &Cx) -> Option<String> {
mounted::<R>(cx)
.filter(|mounted| declares::<R, A>(mounted, Places::BULK))
.map(|mounted| bulk_action_url(&mounted.url, A::NAME))
}
fn keyed<R: Resource>(
cx: &Cx,
record: &R::Model,
serves: impl FnOnce(&Mounted<R>) -> bool,
) -> Option<(String, String)> {
let mounted = mounted::<R>(cx)?;
(mounted.table.is_addressable() && serves(&*mounted))
.then(|| (mounted.url.clone(), mounted.table.key_of(record)))
}
fn declares<R: Resource, A: Action<R>>(mounted: &Mounted<R>, places: Places) -> bool {
mounted
.actions
.find(A::NAME)
.is_some_and(|action| action.places.intersects(places))
}
pub(crate) const RECORD_ROUTE_PARAM: &str = "{id}";
pub(crate) const CREATE_ROUTE_SEGMENT: &str = "create";
pub(crate) const EXPORT_ROUTE_SEGMENT: &str = "export";
pub(crate) const IMPORT_ROUTE_SEGMENT: &str = "import";
pub(crate) const TEMPLATE_ROUTE_SEGMENT: &str = "template";
pub(crate) const OPTIONS_ROUTE_SEGMENT: &str = "options";
pub(crate) const EDIT_ROUTE_SEGMENT: &str = "edit";
pub(crate) const DELETE_ROUTE_SEGMENT: &str = "delete";
pub(crate) const BULK_DELETE_ROUTE_SEGMENT: &str = "bulk-delete";
pub(crate) const COLUMNS_ROUTE_SEGMENT: &str = "columns";
pub(crate) const ACTIONS_ROUTE_SEGMENT: &str = "actions";
pub(crate) const DASH_ROUTE_SEGMENT: &str = "-";
pub(crate) fn list_route(list_path: &str, segment: &str) -> String {
format!(
"{}/{DASH_ROUTE_SEGMENT}/{segment}",
list_path.trim_end_matches('/')
)
}
pub(crate) const ACTION_ROUTE_PARAM: &str = "{action}";
pub(crate) const RELATIONS_ROUTE_SEGMENT: &str = "relations";
pub(crate) const RELATION_ROUTE_PARAM: &str = "{relation}";
pub(crate) const RELATED_ROUTE_PARAM: &str = "{related}";
pub(crate) fn relation_url(list: &str, key: &str, slug: &str) -> String {
format!(
"{list}/{}/{DASH_ROUTE_SEGMENT}/{RELATIONS_ROUTE_SEGMENT}/{slug}",
href::encode_path_segment(key)
)
}
pub(crate) fn row_edit_url(prefix: &str, key: &str) -> String {
format!(
"{prefix}/{}/{EDIT_ROUTE_SEGMENT}",
href::encode_path_segment(key)
)
}
pub(crate) fn row_view_url(prefix: &str, key: &str) -> String {
format!("{prefix}/{}", href::encode_path_segment(key))
}
pub(crate) fn delete_action_url(prefix: &str, key: &str) -> String {
format!(
"{prefix}/{}/{DELETE_ROUTE_SEGMENT}",
href::encode_path_segment(key)
)
}
pub(crate) fn row_action_url(prefix: &str, key: &str, name: &str) -> String {
format!(
"{prefix}/{}/{DASH_ROUTE_SEGMENT}/{ACTIONS_ROUTE_SEGMENT}/{name}",
href::encode_path_segment(key)
)
}
pub(crate) fn bulk_action_url(list_path: &str, name: &str) -> String {
format!("{list_path}/{DASH_ROUTE_SEGMENT}/{ACTIONS_ROUTE_SEGMENT}/{name}")
}
pub(crate) fn action_options_url(url: &str, name: &str) -> String {
format!(
"{}/options",
bulk_action_url(url.trim_end_matches('/'), name)
)
}
pub(crate) fn create_page_url(list_path: &str) -> String {
list_route(list_path, CREATE_ROUTE_SEGMENT)
}
pub(crate) fn export_url(list_path: &str) -> String {
list_route(list_path, EXPORT_ROUTE_SEGMENT)
}
pub(crate) fn import_url(list_path: &str) -> String {
list_route(list_path, IMPORT_ROUTE_SEGMENT)
}
pub(crate) fn import_template_url(list_path: &str) -> String {
format!("{}/{TEMPLATE_ROUTE_SEGMENT}", import_url(list_path))
}
pub(crate) fn columns_url(list_path: &str) -> String {
list_route(list_path, COLUMNS_ROUTE_SEGMENT)
}
pub(crate) fn bulk_delete_url(list_path: &str) -> String {
list_route(list_path, BULK_DELETE_ROUTE_SEGMENT)
}
pub(crate) const RETURN_PARAM: &str = "return";
pub(crate) fn with_return(url: &str, target: &str) -> String {
format!("{url}?{RETURN_PARAM}={}", href::encode_query_value(target))
}