1use crate::{
2 css::css_manager::CssManager,
3 fetch::request_builder::{RequestBody, RequestBuilder},
4 Context, Css, Dependencies, DropResource, FutureBox, Instant, JsJson, WebsocketMessage,
5};
6use std::cell::RefCell;
7use std::{future::Future, pin::Pin, rc::Rc};
8
9use crate::driver_module::dom::DriverDom;
10use crate::{driver_module::api::ApiImport, driver_module::utils::futures_spawn::spawn_local};
11
12use super::api::DomAccess;
13
14pub const VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER: &str = "%%VERTIGO_PUBLIC_BUILD_PATH%%";
16
17pub const VERTIGO_MOUNT_POINT_PLACEHOLDER: &str = "%%VERTIGO_MOUNT_POINT%%";
19
20#[derive(Debug, Clone, Copy)]
21pub enum FetchMethod {
22 GET,
23 HEAD,
24 POST,
25 PUT,
26 DELETE,
27 CONNECT,
28 OPTIONS,
29 TRACE,
30 PATCH,
31}
32
33impl FetchMethod {
34 pub fn to_str(&self) -> String {
35 match self {
36 Self::GET => "GET",
37 Self::HEAD => "HEAD",
38 Self::POST => "POST",
39 Self::PUT => "PUT",
40 Self::DELETE => "DELETE",
41 Self::CONNECT => "CONNECT",
42 Self::OPTIONS => "OPTIONS",
43 Self::TRACE => "TRACE",
44 Self::PATCH => "PATCH",
45 }
46 .into()
47 }
48}
49
50type Executable = dyn Fn(Pin<Box<dyn Future<Output = ()> + 'static>>);
51type PlainHandler = dyn Fn(&str) -> Option<String>;
52
53pub struct DriverInner {
54 pub(crate) api: ApiImport,
55 pub(crate) dependencies: &'static Dependencies,
56 pub(crate) css_manager: CssManager,
57 pub(crate) dom: &'static DriverDom,
58 spawn_executor: Rc<Executable>,
59 _subscribe: DropResource,
60 _plains_handler: RefCell<Option<Rc<PlainHandler>>>,
61}
62
63impl DriverInner {
64 pub fn new() -> &'static Self {
65 let dependencies: &'static Dependencies = Box::leak(Box::default());
66
67 let api = ApiImport::default();
68
69 let spawn_executor = {
70 let api = api.clone();
71
72 Rc::new(move |fut: Pin<Box<dyn Future<Output = ()> + 'static>>| {
73 spawn_local(api.clone(), fut);
74 })
75 };
76
77 let dom = DriverDom::new(&api);
78 let css_manager = {
79 let driver_dom = dom;
80 CssManager::new(move |selector, value| driver_dom.insert_css(selector, value))
81 };
82
83 let subscribe = dependencies.hooks.on_after_transaction(move || {
84 dom.flush_dom_changes();
85 });
86
87 Box::leak(Box::new(DriverInner {
88 api,
89 dependencies,
90 css_manager,
91 dom,
92 spawn_executor,
93 _subscribe: subscribe,
94 _plains_handler: RefCell::new(None),
95 }))
96 }
97}
98
99pub type FetchResult = Result<(u32, RequestBody), String>;
105
106#[derive(Clone, Copy)]
108pub struct Driver {
109 pub(crate) inner: &'static DriverInner,
110}
111
112impl Default for Driver {
113 fn default() -> Self {
114 let driver = DriverInner::new();
115
116 Driver { inner: driver }
117 }
118}
119
120impl Driver {
121 pub fn cookie_get(&self, cname: &str) -> String {
123 self.inner.api.cookie_get(cname)
124 }
125
126 pub fn cookie_get_json(&self, cname: &str) -> JsJson {
128 self.inner.api.cookie_get_json(cname)
129 }
130
131 pub fn cookie_set(&self, cname: &str, cvalue: &str, expires_in: u64) {
133 self.inner.api.cookie_set(cname, cvalue, expires_in)
134 }
135
136 pub fn cookie_set_json(&self, cname: &str, cvalue: JsJson, expires_in: u64) {
138 self.inner.api.cookie_set_json(cname, cvalue, expires_in)
139 }
140
141 pub fn history_back(&self) {
143 self.inner.api.history_back();
144 }
145
146 pub fn history_replace(&self, new_url: &str) {
148 self.inner.api.replace_history_location(new_url)
149 }
150
151 #[must_use]
153 pub fn set_interval(&self, time: u32, func: impl Fn() + 'static) -> DropResource {
154 self.inner.api.interval_set(time, func)
155 }
156
157 pub fn now(&self) -> Instant {
159 Instant::now(self.inner.api.clone())
160 }
161
162 pub fn utc_now(&self) -> i64 {
164 self.inner.api.utc_now()
165 }
166
167 pub fn timezone_offset(&self) -> i32 {
171 self.inner.api.timezone_offset()
172 }
173
174 #[must_use]
176 pub fn request_get(&self, url: impl Into<String>) -> RequestBuilder {
177 RequestBuilder::get(url)
178 }
179
180 #[must_use]
182 pub fn request_post(&self, url: impl Into<String>) -> RequestBuilder {
183 RequestBuilder::post(url)
184 }
185
186 #[must_use]
187 pub fn sleep(&self, time: u32) -> FutureBox<()> {
188 let (sender, future) = FutureBox::new();
189 self.inner.api.set_timeout_and_detach(time, move || {
190 sender.publish(());
191 });
192
193 future
194 }
195
196 pub fn get_random(&self, min: u32, max: u32) -> u32 {
197 self.inner.api.get_random(min, max)
198 }
199
200 pub fn get_random_from<K: Clone>(&self, list: &[K]) -> Option<K> {
201 let len = list.len();
202
203 if len < 1 {
204 return None;
205 }
206
207 let max_index = len - 1;
208
209 let index = self.get_random(0, max_index as u32);
210 Some(list[index as usize].clone())
211 }
212
213 #[must_use]
215 pub fn websocket<F: Fn(WebsocketMessage) + 'static>(
216 &self,
217 host: impl Into<String>,
218 callback: F,
219 ) -> DropResource {
220 self.inner.api.websocket(host, callback)
221 }
222
223 pub fn spawn(&self, future: impl Future<Output = ()> + 'static) {
225 let future = Box::pin(future);
226 let spawn_executor = self.inner.spawn_executor.clone();
227 spawn_executor(future);
228 }
229
230 pub fn transaction<R, F: FnOnce(&Context) -> R>(&self, func: F) -> R {
233 self.inner.dependencies.transaction(func)
234 }
235
236 pub fn dom_access(&self) -> DomAccess {
238 self.inner.api.dom_access()
239 }
240
241 pub fn on_after_transaction(&self, callback: impl Fn() + 'static) -> DropResource {
243 self.inner.dependencies.hooks.on_after_transaction(callback)
244 }
245
246 pub fn is_browser(&self) -> bool {
258 self.inner.api.is_browser()
259 }
260
261 pub fn is_server(&self) -> bool {
262 !self.is_browser()
263 }
264
265 pub fn env(&self, name: impl Into<String>) -> Option<String> {
267 let name = name.into();
268 self.inner.api.get_env(name)
269 }
270
271 pub fn public_build_path(&self, path: impl Into<String>) -> String {
273 let path = path.into();
274 if self.is_browser() {
275 if let Some(public_path) = self.env("vertigo-public-path") {
277 path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, &public_path)
278 } else {
279 path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, "/build")
281 }
282 } else {
283 path
285 }
286 }
287
288 pub fn route_to_public(&self, path: impl Into<String>) -> String {
290 let path = path.into();
291 if self.is_browser() {
292 let mount_point = self
294 .env("vertigo-mount-point")
295 .unwrap_or_else(|| "/".to_string());
296 if mount_point != "/" {
297 [mount_point, path].concat()
298 } else {
299 path
300 }
301 } else {
302 [VERTIGO_MOUNT_POINT_PLACEHOLDER, &path].concat()
304 }
305 }
306
307 pub fn route_from_public(&self, path: impl Into<String>) -> String {
309 let path: String = path.into();
310 self.inner.api.route_from_public(path)
311 }
312
313 pub fn plains(&mut self, callback: impl Fn(&str) -> Option<String> + 'static) {
329 let mut mut_plains = self.inner._plains_handler.borrow_mut();
330 *mut_plains = Some(Rc::new(callback));
331 }
332
333 pub fn try_get_plain(&self) {
336 if self.is_server() {
337 let url = self.inner.api.get_history_location();
338 match self.inner._plains_handler.try_borrow() {
339 Ok(callback_ref) => {
340 if let Some(callback) = callback_ref.as_deref() {
341 if let Some(body) = callback(&url) {
342 self.inner.api.plain_response(body)
343 }
344 }
345 }
346 Err(err) => log::error!("Error invoking plains: {err}"),
347 }
348 } else {
349 log::info!("Browser mode, not invoking try_get_plain");
350 }
351 }
352
353 pub fn set_status(&self, status: u16) {
361 if self.is_server() {
362 self.inner.api.set_status(status);
363 }
364 }
365
366 pub fn class_name_for(&mut self, css: &Css) -> String {
370 self.inner.css_manager.get_class_name(css)
371 }
372
373 pub fn register_bundle(&self, bundle: impl Into<String>) {
377 self.inner.css_manager.register_bundle(bundle.into())
378 }
379}