1use std::{future::Future, pin::Pin, rc::Rc};
2use vertigo_macro::{store, AutoJsJson};
3
4use crate::{
5 computed::{get_dependencies, struct_mut::ValueMut, DropResource},
6 css::get_css_manager,
7 dev::{
8 command::{LocationSetMode, LocationTarget},
9 FutureBox,
10 },
11 driver_module::{
12 api::{api_browser_command, api_location, api_server_handler, api_timers, api_websocket},
13 dom::get_driver_dom,
14 utils::futures_spawn::spawn_local,
15 },
16 fetch::request_builder::{RequestBody, RequestBuilder},
17 Context, Css, DomNode, Instant, InstantType, JsJson, WebsocketMessage,
18};
19
20use super::api::DomAccess;
21
22pub const VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER: &str = "%%VERTIGO_PUBLIC_BUILD_PATH%%";
24
25pub const VERTIGO_MOUNT_POINT_PLACEHOLDER: &str = "%%VERTIGO_MOUNT_POINT%%";
27
28#[derive(AutoJsJson, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
29pub enum FetchMethod {
30 GET,
31 HEAD,
32 POST,
33 PUT,
34 DELETE,
35 CONNECT,
36 OPTIONS,
37 TRACE,
38 PATCH,
39}
40
41impl FetchMethod {
42 pub fn to_str(&self) -> String {
43 match self {
44 Self::GET => "GET",
45 Self::HEAD => "HEAD",
46 Self::POST => "POST",
47 Self::PUT => "PUT",
48 Self::DELETE => "DELETE",
49 Self::CONNECT => "CONNECT",
50 Self::OPTIONS => "OPTIONS",
51 Self::TRACE => "TRACE",
52 Self::PATCH => "PATCH",
53 }
54 .into()
55 }
56}
57
58type Executable = dyn Fn(Pin<Box<dyn Future<Output = ()> + 'static>>);
59
60pub type FetchResult = Result<(u32, RequestBody), String>;
66
67#[store]
75pub fn get_driver() -> Rc<Driver> {
76 let spawn_executor = {
77 Rc::new(move |fut: Pin<Box<dyn Future<Output = ()> + 'static>>| {
78 spawn_local(fut);
79 })
80 };
81
82 let subscribe = get_dependencies().hooks.on_after_transaction(move || {
83 get_driver_dom().flush_dom_changes();
84 });
85
86 Rc::new(Driver {
87 spawn_executor,
88 _subscribe: subscribe,
89 subscription: ValueMut::new(None),
90 })
91}
92
93pub fn transaction<R, F: FnOnce(&Context) -> R>(f: F) -> R {
95 get_driver().transaction(f)
96}
97
98pub struct Driver {
100 spawn_executor: Rc<Executable>,
101 _subscribe: DropResource,
102 subscription: ValueMut<Option<DomNode>>,
103}
104
105impl Driver {
106 pub(crate) fn set_root(&self, root_view: DomNode) {
107 self.subscription.set(Some(root_view));
108 }
109
110 pub fn cookie_get(&self, cname: &str) -> String {
112 api_browser_command().cookie_get(cname.into())
113 }
114
115 pub fn cookie_get_json(&self, cname: &str) -> JsJson {
117 api_browser_command().cookie_json_get(cname.into())
118 }
119
120 pub fn cookie_set(&self, cname: &str, cvalue: &str, expires_in: u64) {
122 api_browser_command().cookie_set(cname.into(), cvalue.into(), expires_in);
123 }
124
125 pub fn cookie_set_json(&self, cname: &str, cvalue: JsJson, expires_in: u64) {
127 api_browser_command().cookie_json_set(cname.into(), cvalue, expires_in);
128 }
129
130 pub fn history_back(&self) {
132 api_browser_command().history_back();
133 }
134
135 pub fn history_replace(&self, new_url: &str) {
137 api_location().push_location(LocationTarget::History, LocationSetMode::Replace, new_url);
138 }
139
140 #[must_use]
142 pub fn set_interval(&self, time: u32, func: impl Fn() + 'static) -> DropResource {
143 api_timers().interval(time, func)
144 }
145
146 pub fn now(&self) -> Instant {
148 Instant::now()
149 }
150
151 pub fn utc_now(&self) -> InstantType {
153 api_browser_command().get_date_now()
154 }
155
156 pub fn timezone_offset(&self) -> i32 {
160 api_browser_command().timezone_offset()
161 }
162
163 #[must_use]
165 pub fn request_get(&self, url: impl Into<String>) -> RequestBuilder {
166 RequestBuilder::get(url)
167 }
168
169 #[must_use]
171 pub fn request_post(&self, url: impl Into<String>) -> RequestBuilder {
172 RequestBuilder::post(url)
173 }
174
175 #[must_use]
176 pub fn sleep(&self, time: u32) -> FutureBox<()> {
177 let (sender, future) = FutureBox::new();
178
179 api_timers().set_timeout_and_detach(time, move || {
180 sender.publish(());
181 });
182
183 future
184 }
185
186 pub fn get_random(&self, min: u32, max: u32) -> u32 {
187 api_browser_command().get_random(min, max)
188 }
189
190 pub fn get_random_from<K: Clone>(&self, list: &[K]) -> Option<K> {
191 let len = list.len();
192
193 if len < 1 {
194 return None;
195 }
196
197 let max_index = len - 1;
198
199 let index = self.get_random(0, max_index as u32);
200 Some(list[index as usize].clone())
201 }
202
203 #[must_use]
205 pub fn websocket<F: Fn(WebsocketMessage) + 'static>(
206 &self,
207 host: impl Into<String>,
208 callback: F,
209 ) -> DropResource {
210 api_websocket().websocket(host, callback)
211 }
212
213 pub fn spawn(&self, future: impl Future<Output = ()> + 'static) {
215 let future = Box::pin(future);
216 let spawn_executor = self.spawn_executor.clone();
217 spawn_executor(future);
218 }
219
220 pub fn transaction<R, F: FnOnce(&Context) -> R>(&self, func: F) -> R {
223 get_dependencies().transaction(func)
224 }
225
226 pub fn dom_access(&self) -> DomAccess {
228 DomAccess::default()
229 }
230
231 pub fn on_after_transaction(&self, callback: impl Fn() + 'static) -> DropResource {
233 get_dependencies().hooks.on_after_transaction(callback)
234 }
235
236 pub fn is_browser(&self) -> bool {
248 api_browser_command().is_browser()
249 }
250
251 pub fn is_server(&self) -> bool {
252 !self.is_browser()
253 }
254
255 pub fn env(&self, name: impl Into<String>) -> Option<String> {
257 let name = name.into();
258 api_browser_command().get_env(name)
259 }
260
261 pub fn public_build_path(&self, path: impl Into<String>) -> String {
263 let path = path.into();
264 if self.is_browser() {
265 if let Some(public_path) = self.env("vertigo-public-path") {
267 path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, &public_path)
268 } else {
269 path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, "/build")
271 }
272 } else {
273 path
275 }
276 }
277
278 pub fn route_to_public(&self, path: impl Into<String>) -> String {
280 let path = path.into();
281 if self.is_browser() {
282 let mount_point = self
284 .env("vertigo-mount-point")
285 .unwrap_or_else(|| "/".to_string());
286 if mount_point != "/" {
287 [mount_point, path].concat()
288 } else {
289 path
290 }
291 } else {
292 [VERTIGO_MOUNT_POINT_PLACEHOLDER, &path].concat()
294 }
295 }
296
297 pub fn route_from_public(&self, path: impl Into<String>) -> String {
299 let path: String = path.into();
300
301 if api_browser_command().is_browser() {
302 let mount_point = api_browser_command()
304 .get_env("vertigo-mount-point")
305 .unwrap_or_else(|| "/".to_string());
306 if mount_point != "/" {
307 path.trim_start_matches(&mount_point).to_string()
308 } else {
309 path
310 }
311 } else {
312 path
314 }
315 }
316
317 pub fn plains(&self, callback: impl Fn(&str) -> Option<String> + 'static) {
333 api_server_handler().plains(callback);
334 }
335
336 pub fn set_status(&self, status: u16) {
344 if self.is_server() {
345 api_browser_command().set_status(status);
346 }
347 }
348
349 pub fn class_name_for(&self, css: &Css) -> String {
353 get_css_manager().get_class_name(css)
354 }
355
356 pub fn register_bundle(&self, bundle: impl Into<String>) {
360 get_css_manager().register_bundle(bundle.into())
361 }
362}