use std::{future::Future, pin::Pin, rc::Rc};
use vertigo_macro::{AutoJsJson, store};
use crate::{
Context, Css, DomNode, Instant, InstantType, JsJson, WebsocketMessage,
computed::{DropResource, get_dependencies, struct_mut::ValueMut},
css::get_css_manager,
dev::{
FutureBox,
command::{LocationSetMode, LocationTarget},
},
driver_module::{
api::{api_browser_command, api_location, api_server_handler, api_timers, api_websocket},
dom::get_driver_dom,
utils::futures_spawn::spawn_local,
},
fetch::request_builder::{RequestBody, RequestBuilder},
};
use super::api::DomAccess;
pub const VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER: &str = "%%VERTIGO_PUBLIC_BUILD_PATH%%";
pub const VERTIGO_MOUNT_POINT_PLACEHOLDER: &str = "%%VERTIGO_MOUNT_POINT%%";
#[derive(AutoJsJson, Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum FetchMethod {
GET,
HEAD,
POST,
PUT,
DELETE,
CONNECT,
OPTIONS,
TRACE,
PATCH,
}
impl FetchMethod {
pub fn to_str(&self) -> String {
match self {
Self::GET => "GET",
Self::HEAD => "HEAD",
Self::POST => "POST",
Self::PUT => "PUT",
Self::DELETE => "DELETE",
Self::CONNECT => "CONNECT",
Self::OPTIONS => "OPTIONS",
Self::TRACE => "TRACE",
Self::PATCH => "PATCH",
}
.into()
}
}
type Executable = dyn Fn(Pin<Box<dyn Future<Output = ()> + 'static>>);
pub type FetchResult = Result<(u32, RequestBody), String>;
#[store]
pub fn get_driver() -> Rc<Driver> {
let spawn_executor = {
Rc::new(move |fut: Pin<Box<dyn Future<Output = ()> + 'static>>| {
spawn_local(fut);
})
};
let subscribe = get_dependencies().hooks.on_after_transaction(move || {
get_driver_dom().flush_dom_changes();
});
Rc::new(Driver {
spawn_executor,
_subscribe: subscribe,
subscription: ValueMut::new(None),
})
}
pub fn transaction<R, F: FnOnce(&Context) -> R>(f: F) -> R {
get_driver().transaction(f)
}
pub struct Driver {
spawn_executor: Rc<Executable>,
_subscribe: DropResource,
subscription: ValueMut<Option<DomNode>>,
}
impl Driver {
pub(crate) fn set_root(&self, root_view: DomNode) {
self.subscription.set(Some(root_view));
}
pub fn cookie_get(&self, cname: &str) -> String {
api_browser_command().cookie_get(cname.into())
}
pub fn cookie_get_json(&self, cname: &str) -> JsJson {
api_browser_command().cookie_json_get(cname.into())
}
pub fn cookie_set(&self, cname: &str, cvalue: &str, expires_in: u64) {
api_browser_command().cookie_set(cname.into(), cvalue.into(), expires_in);
}
pub fn cookie_set_json(&self, cname: &str, cvalue: JsJson, expires_in: u64) {
api_browser_command().cookie_json_set(cname.into(), cvalue, expires_in);
}
pub fn history_back(&self) {
api_browser_command().history_back();
}
pub fn history_replace(&self, new_url: &str) {
api_location().push_location(LocationTarget::History, LocationSetMode::Replace, new_url);
}
#[must_use]
pub fn set_interval(&self, time: u32, func: impl Fn() + 'static) -> DropResource {
api_timers().interval(time, func)
}
pub fn now(&self) -> Instant {
Instant::now()
}
pub fn utc_now(&self) -> InstantType {
api_browser_command().get_date_now()
}
pub fn timezone_offset(&self) -> i32 {
api_browser_command().timezone_offset()
}
#[must_use]
pub fn request_get(&self, url: impl Into<String>) -> RequestBuilder {
RequestBuilder::get(url)
}
#[must_use]
pub fn request_post(&self, url: impl Into<String>) -> RequestBuilder {
RequestBuilder::post(url)
}
#[must_use]
pub fn sleep(&self, time: u32) -> FutureBox<()> {
let (sender, future) = FutureBox::new();
api_timers().set_timeout_and_detach(time, move || {
sender.publish(());
});
future
}
pub fn get_random(&self, min: u32, max: u32) -> u32 {
api_browser_command().get_random(min, max)
}
pub fn get_random_from<K: Clone>(&self, list: &[K]) -> Option<K> {
let len = list.len();
if len < 1 {
return None;
}
let max_index = len - 1;
let index = self.get_random(0, max_index as u32);
Some(list[index as usize].clone())
}
#[must_use]
pub fn websocket<F: Fn(WebsocketMessage) + 'static>(
&self,
host: impl Into<String>,
callback: F,
) -> DropResource {
api_websocket().websocket(host, callback)
}
pub fn spawn(&self, future: impl Future<Output = ()> + 'static) {
let future = Box::pin(future);
let spawn_executor = self.spawn_executor.clone();
spawn_executor(future);
}
pub fn transaction<R, F: FnOnce(&Context) -> R>(&self, func: F) -> R {
get_dependencies().transaction(func)
}
pub fn dom_access(&self) -> DomAccess {
DomAccess::default()
}
pub fn on_after_transaction(&self, callback: impl Fn() + 'static) -> DropResource {
get_dependencies().hooks.on_after_transaction(callback)
}
pub fn is_browser(&self) -> bool {
api_browser_command().is_browser()
}
pub fn is_server(&self) -> bool {
!self.is_browser()
}
pub fn env(&self, name: impl Into<String>) -> Option<String> {
let name = name.into();
api_browser_command().get_env(name)
}
pub fn public_build_path(&self, path: impl Into<String>) -> String {
let path = path.into();
if self.is_browser() {
if let Some(public_path) = self.env("vertigo-public-path") {
path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, &public_path)
} else {
path.replace(VERTIGO_PUBLIC_BUILD_PATH_PLACEHOLDER, "/build")
}
} else {
path
}
}
pub fn route_to_public(&self, path: impl Into<String>) -> String {
let path = path.into();
if self.is_browser() {
let mount_point = self
.env("vertigo-mount-point")
.unwrap_or_else(|| "/".to_string());
if mount_point != "/" {
[mount_point, path].concat()
} else {
path
}
} else {
[VERTIGO_MOUNT_POINT_PLACEHOLDER, &path].concat()
}
}
pub fn route_from_public(&self, path: impl Into<String>) -> String {
let path: String = path.into();
if api_browser_command().is_browser() {
let mount_point = api_browser_command()
.get_env("vertigo-mount-point")
.unwrap_or_else(|| "/".to_string());
if mount_point != "/" {
path.trim_start_matches(&mount_point).to_string()
} else {
path
}
} else {
path
}
}
pub fn plains(&self, callback: impl Fn(&str) -> Option<String> + 'static) {
api_server_handler().plains(callback);
}
pub fn set_status(&self, status: u16) {
if self.is_server() {
api_browser_command().set_status(status);
}
}
pub fn class_name_for(&self, css: &Css) -> String {
get_css_manager().get_class_name(css)
}
pub fn register_bundle(&self, bundle: impl Into<String>) {
get_css_manager().register_bundle(bundle.into())
}
}