1use std::{future::Future, pin::Pin, rc::Rc};
2use vertigo_macro::{AutoJsJson, store};
3
4use crate::{
5 Context, Css, DomNode, DropResource, Instant, InstantType, JsJson, WebsocketMessage,
6 css::get_css_manager,
7 dev::{
8 FutureBox,
9 command::{LocationSetMode, LocationTarget},
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 struct_mut::ValueMut,
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 = crate::reactive::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]
167 pub fn request_get(&self, url: impl Into<String>) -> RequestBuilder {
168 RequestBuilder::get(url)
169 }
170
171 #[must_use]
173 pub fn request_post(&self, url: impl Into<String>) -> RequestBuilder {
174 RequestBuilder::post(url)
175 }
176
177 #[must_use]
179 pub fn request_patch(&self, url: impl Into<String>) -> RequestBuilder {
180 RequestBuilder::patch(url)
181 }
182
183 #[must_use]
185 pub fn request_put(&self, url: impl Into<String>) -> RequestBuilder {
186 RequestBuilder::put(url)
187 }
188
189 #[must_use]
191 pub fn request_delete(&self, url: impl Into<String>) -> RequestBuilder {
192 RequestBuilder::delete(url)
193 }
194
195 #[must_use]
196 pub fn sleep(&self, time: u32) -> FutureBox<()> {
197 let (sender, future) = FutureBox::new();
198
199 api_timers().set_timeout_and_detach(time, move || {
200 sender.publish(());
201 });
202
203 future
204 }
205
206 pub fn get_random(&self, min: u32, max: u32) -> u32 {
207 api_browser_command().get_random(min, max)
208 }
209
210 pub fn get_random_from<K: Clone>(&self, list: &[K]) -> Option<K> {
211 let len = list.len();
212
213 if len < 1 {
214 return None;
215 }
216
217 let max_index = len - 1;
218
219 let index = self.get_random(0, max_index as u32);
220 Some(list[index as usize].clone())
221 }
222
223 #[must_use]
225 pub fn websocket<F: Fn(WebsocketMessage) + 'static>(
226 &self,
227 host: impl Into<String>,
228 callback: F,
229 ) -> DropResource {
230 api_websocket().websocket(host, callback)
231 }
232
233 pub fn spawn(&self, future: impl Future<Output = ()> + 'static) {
235 let future = Box::pin(future);
236 let spawn_executor = self.spawn_executor.clone();
237 spawn_executor(future);
238 }
239
240 pub fn transaction<R, F: FnOnce(&Context) -> R>(&self, func: F) -> R {
243 crate::reactive::transaction(func)
244 }
245
246 pub fn dom_access(&self) -> DomAccess {
248 DomAccess::default()
249 }
250
251 pub fn on_after_transaction(&self, callback: impl Fn() + 'static) -> DropResource {
253 crate::reactive::on_after_transaction(callback)
254 }
255
256 pub fn is_browser(&self) -> bool {
268 api_browser_command().is_browser()
269 }
270
271 pub fn is_server(&self) -> bool {
272 !self.is_browser()
273 }
274
275 pub fn env(&self, name: impl Into<String>) -> Option<String> {
277 let name = name.into();
278 api_browser_command().get_env(name)
279 }
280
281 pub fn public_build_path(&self, path: impl Into<String>) -> String {
283 let path = path.into();
284 if self.is_browser() {
285 if let Some(public_path) = self.env("vertigo-public-path") {
287 path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, &public_path)
288 } else {
289 path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, "/build")
291 }
292 } else {
293 path
295 }
296 }
297
298 pub fn route_to_public(&self, path: impl Into<String>) -> String {
300 let path = path.into();
301 if self.is_browser() {
302 let mount_point = self
304 .env("vertigo-mount-point")
305 .unwrap_or_else(|| "/".to_string());
306 if mount_point != "/" {
307 [mount_point, path].concat()
308 } else {
309 path
310 }
311 } else {
312 [VERTIGO_MOUNT_POINT_PLACEHOLDER, &path].concat()
314 }
315 }
316
317 pub fn route_from_public(&self, path: impl Into<String>) -> String {
319 let path: String = path.into();
320
321 if api_browser_command().is_browser() {
322 let mount_point = api_browser_command()
324 .get_env("vertigo-mount-point")
325 .unwrap_or_else(|| "/".to_string());
326 if mount_point != "/" {
327 path.trim_start_matches(&mount_point).to_string()
328 } else {
329 path
330 }
331 } else {
332 path
334 }
335 }
336
337 pub fn plains(&self, callback: impl Fn(&str) -> Option<String> + 'static) {
353 api_server_handler().plains(callback);
354 }
355
356 pub fn set_status(&self, status: u16) {
364 if self.is_server() {
365 api_browser_command().set_status(status);
366 }
367 }
368
369 pub fn class_name_for(&self, css: &Css) -> String {
373 get_css_manager().get_class_name(css)
374 }
375
376 pub fn register_bundle(&self, bundle: impl Into<String>) {
380 get_css_manager().register_bundle(bundle.into())
381 }
382}