Skip to main content

tablo_core/
navigation.rs

1//! Sidebar navigation: [`NavigationItem`] and the [`NavTarget`] it points with.
2
3use topcoat::{context::Cx, icon::IconData, view::Attributes};
4
5/// The `href` and runtime-navigation attributes of a link to another panel page.
6pub(crate) fn runtime_link(cx: &Cx, href: &str) -> Attributes {
7    topcoat::runtime::link_attrs(cx, href.to_string(), topcoat::runtime::prefetch_mode(cx))
8}
9
10/// Where a sidebar entry points.
11#[derive(Clone, Default, PartialEq, Eq)]
12pub enum NavTarget {
13    /// No URL yet; the owning Panel resolves it.
14    #[default]
15    Derived,
16    /// An explicit URL.
17    Url(String),
18}
19
20impl NavTarget {
21    /// The URL this target names.
22    pub fn url(&self) -> Option<&str> {
23        match self {
24            Self::Derived => None,
25            Self::Url(url) => Some(url),
26        }
27    }
28}
29
30impl std::fmt::Debug for NavTarget {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        match self {
33            Self::Derived => f.write_str("Derived"),
34            Self::Url(url) => f.debug_tuple("Url").field(url).finish(),
35        }
36    }
37}
38
39/// A sidebar entry: a resource's, from its [`ResourceDef`](super::ResourceDef), or a `Page`'s.
40#[derive(Clone, Debug, Default)]
41pub struct NavigationItem {
42    pub label: String,
43    /// Where this entry points.
44    pub target: NavTarget,
45    /// Sort key for the sidebar.
46    pub order: i32,
47    /// The icon before the label, if any.
48    pub icon: Option<IconData>,
49}
50
51impl NavigationItem {
52    /// A sidebar entry at an explicit `url`.
53    pub fn at(label: impl Into<String>, url: impl Into<String>) -> Self {
54        Self {
55            label: label.into(),
56            target: NavTarget::Url(url.into()),
57            order: 0,
58            icon: None,
59        }
60    }
61
62    /// This entry with `icon` before its label.
63    #[must_use]
64    pub fn icon(mut self, icon: IconData) -> Self {
65        self.icon = Some(icon);
66        self
67    }
68
69    /// Resolve a [`NavTarget::Derived`] entry to `url`.
70    pub(crate) fn resolved(mut self, url: &str) -> Self {
71        if matches!(self.target, NavTarget::Derived) {
72            self.target = NavTarget::Url(url.to_string());
73        }
74        self
75    }
76
77    /// The URL this entry points at.
78    pub fn url(&self) -> Option<&str> {
79        self.target.url()
80    }
81
82    /// Whether this item is current for the given request path.
83    pub fn is_current_path(&self, current_path: &str) -> bool {
84        let Some(url) = self.url() else {
85            return false;
86        };
87        if current_path == url {
88            return true;
89        }
90        current_path
91            .strip_prefix(url)
92            .is_some_and(|rest| rest.starts_with('/'))
93    }
94}
95
96#[cfg(test)]
97mod tests;