1use std::borrow::Cow;
2
3use crate::history::{AnyHistory, History, HistoryError, HistoryResult};
4use crate::query::ToQuery;
5use crate::routable::Routable;
6
7pub type NavigationError = HistoryError;
8pub type NavigationResult<T> = HistoryResult<T>;
9
10#[derive(Debug, PartialEq, Eq, Clone, Copy)]
12pub enum NavigatorKind {
13 Browser,
15 Hash,
17 Memory,
19}
20
21#[derive(Debug, PartialEq, Clone)]
23pub struct Navigator {
24 inner: AnyHistory,
25 basename: Option<String>,
26}
27
28impl Navigator {
29 pub(crate) fn new(history: AnyHistory, basename: Option<String>) -> Self {
30 Self {
31 inner: history,
32 basename,
33 }
34 }
35
36 pub fn basename(&self) -> Option<&str> {
38 self.basename.as_deref()
39 }
40
41 pub fn back(&self) {
43 self.go(-1);
44 }
45
46 pub fn forward(&self) {
48 self.go(1);
49 }
50
51 pub fn go(&self, delta: isize) {
55 self.inner.go(delta);
56 }
57
58 pub fn push<R>(&self, route: &R)
60 where
61 R: Routable,
62 {
63 self.inner.push(self.prefix_basename(&route.to_path()));
64 }
65
66 pub fn replace<R>(&self, route: &R)
68 where
69 R: Routable,
70 {
71 self.inner.replace(self.prefix_basename(&route.to_path()));
72 }
73
74 pub fn push_with_state<R, T>(&self, route: &R, state: T)
76 where
77 R: Routable,
78 T: 'static,
79 {
80 self.inner
81 .push_with_state(self.prefix_basename(&route.to_path()), state);
82 }
83
84 pub fn replace_with_state<R, T>(&self, route: &R, state: T)
86 where
87 R: Routable,
88 T: 'static,
89 {
90 self.inner
91 .replace_with_state(self.prefix_basename(&route.to_path()), state);
92 }
93
94 pub fn push_with_query<R, Q>(&self, route: &R, query: Q) -> Result<(), Q::Error>
96 where
97 R: Routable,
98 Q: ToQuery,
99 {
100 self.inner
101 .push_with_query(self.prefix_basename(&route.to_path()), query)
102 }
103
104 pub fn replace_with_query<R, Q>(&self, route: &R, query: Q) -> Result<(), Q::Error>
106 where
107 R: Routable,
108 Q: ToQuery,
109 {
110 self.inner
111 .replace_with_query(self.prefix_basename(&route.to_path()), query)
112 }
113
114 pub fn push_with_query_and_state<R, Q, T>(
116 &self,
117 route: &R,
118 query: Q,
119 state: T,
120 ) -> Result<(), Q::Error>
121 where
122 R: Routable,
123 Q: ToQuery,
124 T: 'static,
125 {
126 self.inner
127 .push_with_query_and_state(self.prefix_basename(&route.to_path()), query, state)
128 }
129
130 pub fn replace_with_query_and_state<R, Q, T>(
132 &self,
133 route: &R,
134 query: Q,
135 state: T,
136 ) -> Result<(), Q::Error>
137 where
138 R: Routable,
139 Q: ToQuery,
140 T: 'static,
141 {
142 self.inner.replace_with_query_and_state(
143 self.prefix_basename(&route.to_path()),
144 query,
145 state,
146 )
147 }
148
149 pub fn kind(&self) -> NavigatorKind {
151 match &self.inner {
152 AnyHistory::Browser(_) => NavigatorKind::Browser,
153 AnyHistory::Hash(_) => NavigatorKind::Hash,
154 AnyHistory::Memory(_) => NavigatorKind::Memory,
155 }
156 }
157
158 pub(crate) fn prefix_basename<'a>(&self, route_s: &'a str) -> Cow<'a, str> {
159 match self.basename() {
160 Some(base) => {
161 if base.is_empty() && route_s.is_empty() {
162 Cow::from("/")
163 } else {
164 Cow::from(format!("{base}{route_s}"))
165 }
166 }
167 None => route_s.into(),
168 }
169 }
170
171 pub(crate) fn strip_basename<'a>(&self, path: Cow<'a, str>) -> Cow<'a, str> {
172 match self.basename() {
173 Some(m) => {
174 let mut path = path
175 .strip_prefix(m)
176 .map(|m| Cow::from(m.to_owned()))
177 .unwrap_or(path);
178
179 if !path.starts_with('/') {
180 path = format!("/{m}").into();
181 }
182
183 path
184 }
185 None => path,
186 }
187 }
188}