umbral_admin/branding.rs
1//! Admin chrome the developer can rebrand at plugin-build time —
2//! site title, site description, and brand color.
3//!
4//! Stored on `AdminPlugin` during construction (chainable builders),
5//! sealed into the global [`BRANDING`] cell at `Plugin::routes()`
6//! time, and exposed to every template as the globals `site_title`,
7//! `site_description`, and `brand_color`. The wrapper template
8//! injects a `<style>` overriding `--primary` when a brand color is
9//! set so the entire theme tints accordingly.
10
11use std::sync::OnceLock;
12
13#[derive(Debug, Clone)]
14pub struct AdminBranding {
15 pub site_title: String,
16 pub site_description: String,
17 pub brand_color: String,
18 /// Gap 107: admin base path (default `/admin`). Surfaced to
19 /// templates as the `admin_base` Jinja global so cross-page
20 /// links and HTMX targets resolve under whatever prefix
21 /// `AdminPlugin::at()` configured.
22 pub base_path: String,
23 /// gaps2 #33 — whether the "restore last changelist" feature is
24 /// active. When `true` (default), `/admin/` redirects the user to
25 /// the last-visited changelist URL stored in
26 /// `admin_user_pref.preferences.last_path`, and the "Home" breadcrumb
27 /// link carries `?dashboard=1` so the dashboard is reachable in one
28 /// click. When `false`, the index always renders the dashboard and
29 /// the changelist handler stops writing `last_path` (no dead data).
30 pub restore_last_path: bool,
31 /// gaps3 #67 — the version string in the sidebar and on the login page.
32 ///
33 /// `None` hides it entirely (`AdminPlugin::show_version(false)`). The default is
34 /// umbral's OWN version, read from `CARGO_PKG_VERSION` at compile time — the
35 /// templates used to hardcode the literal `v0.0.1`, which had been wrong since
36 /// 0.0.2 and would have gone on being wrong forever.
37 ///
38 /// An app that would rather advertise ITS version than the framework's sets its own
39 /// string: `AdminPlugin::default().version(concat!("MyShop v", env!("CARGO_PKG_VERSION")))`.
40 /// Whose version an admin should show is a product decision, not ours.
41 pub version_label: Option<String>,
42}
43
44/// umbral's own version — the crate version of `umbral-admin`, which tracks the
45/// workspace. Not a literal, so it cannot go stale.
46pub fn umbral_version_label() -> String {
47 format!("umbral v{}", env!("CARGO_PKG_VERSION"))
48}
49
50impl Default for AdminBranding {
51 fn default() -> Self {
52 Self {
53 site_title: "umbral admin".to_string(),
54 site_description: String::new(),
55 brand_color: String::new(),
56 base_path: "/admin".to_string(),
57 restore_last_path: true,
58 version_label: Some(umbral_version_label()),
59 }
60 }
61}
62
63/// Per-process branding. Sealed once at `Plugin::routes()` time;
64/// subsequent attempts to set it are silent no-ops, matching
65/// `App::build`'s "build once" expectation.
66pub(crate) static BRANDING: OnceLock<AdminBranding> = OnceLock::new();
67
68/// Read the active branding. Falls back to defaults if `routes()`
69/// hasn't sealed the value (test harnesses, ad-hoc renders).
70pub(crate) fn current() -> AdminBranding {
71 BRANDING.get().cloned().unwrap_or_default()
72}