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