From 2f473f7c97739487fdd5bb8438d8d8e5a8b0b4bc Mon Sep 17 00:00:00 2001 From: Evan Almloff Date: Thu, 1 Jun 2023 11:03:44 -0500 Subject: [PATCH] make serde optional --- packages/router/Cargo.toml | 8 +- packages/router/examples/simple_routes.rs | 3 +- packages/router/src/components/router.rs | 11 +- packages/router/src/history/web.rs | 292 ++++++++++++++++----- packages/router/src/history/web_history.rs | 53 +++- packages/router/src/history/web_scroll.rs | 8 +- packages/router/src/router_cfg.rs | 3 +- 7 files changed, 289 insertions(+), 89 deletions(-) diff --git a/packages/router/Cargo.toml b/packages/router/Cargo.toml index 58bbc034e..fb92d0888 100644 --- a/packages/router/Cargo.toml +++ b/packages/router/Cargo.toml @@ -16,17 +16,19 @@ dioxus = { path="../dioxus" } dioxus-router-macro = { path="../router-macro" } gloo = { version = "0.8.0", optional = true } log = "0.4.17" -serde = { version = "1.0.163", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"], optional = true } thiserror = "1.0.37" url = "2.3.1" wasm-bindgen = { version = "0.2.86", optional = true } web-sys = { version = "0.3.60", optional = true, features = ["ScrollRestoration"] } -gloo-utils = { version = "0.1.6", optional = true, features = ["serde"] } +js-sys = { version = "0.3.63", optional = true } +gloo-utils = { version = "0.1.6", optional = true } [features] default = ["web"] wasm_test = [] -web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils"] +serde = ["dep:serde", "gloo-utils/serde"] +web = ["gloo", "web-sys", "wasm-bindgen", "gloo-utils", "js-sys"] [dev-dependencies] dioxus = { path = "../dioxus" } diff --git a/packages/router/examples/simple_routes.rs b/packages/router/examples/simple_routes.rs index d87a1fe3c..c620b81a4 100644 --- a/packages/router/examples/simple_routes.rs +++ b/packages/router/examples/simple_routes.rs @@ -2,7 +2,6 @@ use dioxus::prelude::*; use dioxus_router::prelude::*; -use serde::{Deserialize, Serialize}; use std::str::FromStr; fn main() { @@ -128,7 +127,7 @@ fn Route3(cx: Scope, dynamic: String) -> Element { } #[rustfmt::skip] -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Routable)] +#[derive(Clone, Debug, PartialEq, Routable)] enum Route { // Nests with parameters have types taken from child routes #[nest("/user/:user_id")] diff --git a/packages/router/src/components/router.rs b/packages/router/src/components/router.rs index ba515abb9..a5299d08a 100644 --- a/packages/router/src/components/router.rs +++ b/packages/router/src/components/router.rs @@ -1,6 +1,5 @@ use dioxus::prelude::*; use log::error; -use serde::{de::DeserializeOwned, Serialize}; use std::{cell::RefCell, str::FromStr}; use crate::{ @@ -14,7 +13,7 @@ pub struct RouterCfg { config: RefCell>>, } -impl Default for RouterCfg +impl Default for RouterCfg where ::Err: std::fmt::Display, { @@ -35,7 +34,7 @@ impl From> for RouterCfg { /// The props for [`GenericRouter`]. #[derive(Props)] -pub struct GenericRouterProps +pub struct GenericRouterProps where ::Err: std::fmt::Display, { @@ -43,7 +42,7 @@ where config: RouterCfg, } -impl PartialEq for GenericRouterProps +impl PartialEq for GenericRouterProps where ::Err: std::fmt::Display, { @@ -54,9 +53,7 @@ where } /// A component that renders the current route. -pub fn GenericRouter( - cx: Scope>, -) -> Element +pub fn GenericRouter(cx: Scope>) -> Element where ::Err: std::fmt::Display, { diff --git a/packages/router/src/history/web.rs b/packages/router/src/history/web.rs index 9f4ccb486..da8f4952f 100644 --- a/packages/router/src/history/web.rs +++ b/packages/router/src/history/web.rs @@ -1,7 +1,7 @@ use std::sync::{Arc, Mutex}; use gloo::{console::error, events::EventListener, render::AnimationFrame}; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use wasm_bindgen::JsValue; use web_sys::{window, History, ScrollRestoration, Window}; use crate::routable::Routable; @@ -12,7 +12,20 @@ use super::{ HistoryProvider, }; -fn update_scroll(window: &Window, history: &History) { +#[cfg(not(feature = "serde"))] +#[allow(clippy::extra_unused_type_parameters)] +fn update_scroll(window: &Window, history: &History) { + let scroll = ScrollPosition::of_window(window); + if let Err(err) = replace_state_with_url(history, &[scroll.x, scroll.y], None) { + error!(err); + } +} + +#[cfg(feature = "serde")] +fn update_scroll( + window: &Window, + history: &History, +) { if let Some(WebHistoryState { state, .. }) = get_current::>(history) { let scroll = ScrollPosition::of_window(window); let state = WebHistoryState { state, scroll }; @@ -22,7 +35,8 @@ fn update_scroll(window: &Window, hi } } -#[derive(Deserialize, Serialize)] +#[cfg(feature = "serde")] +#[derive(serde::Deserialize, serde::Serialize)] struct WebHistoryState { state: R, scroll: ScrollPosition, @@ -40,7 +54,7 @@ struct WebHistoryState { /// /// Application developers are responsible for not rendering the router if the prefix is not present /// in the URL. Otherwise, if a router navigation is triggered, the prefix will be added. -pub struct WebHistory { +pub struct WebHistory { do_scroll_restoration: bool, history: History, listener_navigation: Option, @@ -52,7 +66,8 @@ pub struct WebHistory { phantom: std::marker::PhantomData, } -impl Default for WebHistory +#[cfg(not(feature = "serde"))] +impl Default for WebHistory where ::Err: std::fmt::Display, { @@ -61,12 +76,82 @@ where } } -impl WebHistory { +#[cfg(feature = "serde")] +impl Default for WebHistory +where + ::Err: std::fmt::Display, + R: serde::Serialize + serde::de::DeserializeOwned, +{ + fn default() -> Self { + Self::new(None, true) + } +} + +impl WebHistory { + #[cfg(not(feature = "serde"))] /// Create a new [`WebHistory`]. /// /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history /// state. It'll also set the browsers scroll restoration to `manual`. - pub fn new(prefix: Option, do_scroll_restoration: bool) -> Self + fn new(prefix: Option, do_scroll_restoration: bool) -> Self + where + ::Err: std::fmt::Display, + { + let w = window().expect("access to `window`"); + let h = w.history().expect("`window` has access to `history`"); + let document = w.document().expect("`window` has access to `document`"); + + let myself = Self::new_inner( + prefix, + do_scroll_restoration, + EventListener::new(&document, "scroll", move |_| { + update_scroll::(&w, &h); + }), + ); + + let current_route = myself.current_route(); + let current_url = current_route.to_string(); + let state = myself.create_state(current_route); + let _ = replace_state_with_url(&myself.history, &state, Some(¤t_url)); + + myself + } + + #[cfg(feature = "serde")] + /// Create a new [`WebHistory`]. + /// + /// If `do_scroll_restoration` is [`true`], [`WebHistory`] will take control of the history + /// state. It'll also set the browsers scroll restoration to `manual`. + fn new(prefix: Option, do_scroll_restoration: bool) -> Self + where + ::Err: std::fmt::Display, + R: serde::Serialize + serde::de::DeserializeOwned, + { + let w = window().expect("access to `window`"); + let h = w.history().expect("`window` has access to `history`"); + let document = w.document().expect("`window` has access to `document`"); + + let myself = Self::new_inner( + prefix, + do_scroll_restoration, + EventListener::new(&document, "scroll", move |_| { + update_scroll::(&w, &h); + }), + ); + + let current_route = myself.current_route(); + let current_url = current_route.to_string(); + let state = myself.create_state(current_route); + let _ = replace_state_with_url(&myself.history, &state, Some(¤t_url)); + + myself + } + + fn new_inner( + prefix: Option, + do_scroll_restoration: bool, + event_listener: EventListener, + ) -> Self where ::Err: std::fmt::Display, { @@ -78,18 +163,12 @@ impl WebHistory { history .set_scroll_restoration(ScrollRestoration::Manual) .expect("`history` can set scroll restoration"); - let w = window.clone(); - let h = history.clone(); - let document = w.document().expect("`window` has access to `document`"); - - Some(EventListener::new(&document, "scroll", move |_| { - update_scroll::(&w, &h); - })) + Some(event_listener) } false => None, }; - let myself = Self { + Self { do_scroll_restoration, history, listener_navigation: None, @@ -98,26 +177,75 @@ impl WebHistory { prefix, window, phantom: Default::default(), - }; - - let current_route = myself.current_route(); - let current_url = current_route.to_string(); - let state = myself.create_state(current_route); - let _ = replace_state_with_url(&myself.history, &state, Some(¤t_url)); - - myself + } } - fn create_state(&self, state: R) -> WebHistoryState { - let scroll = self - .do_scroll_restoration + fn scroll_pos(&self) -> ScrollPosition { + self.do_scroll_restoration .then(|| ScrollPosition::of_window(&self.window)) - .unwrap_or_default(); + .unwrap_or_default() + } + + #[cfg(not(feature = "serde"))] + fn create_state(&self, _state: R) -> [f64; 2] { + let scroll = self.scroll_pos(); + [scroll.x, scroll.y] + } + + #[cfg(feature = "serde")] + fn create_state(&self, state: R) -> WebHistoryState { + let scroll = self.scroll_pos(); WebHistoryState { state, scroll } } } -impl HistoryProvider for WebHistory +impl WebHistory +where + ::Err: std::fmt::Display, +{ + fn route_from_location(&self) -> R { + R::from_str( + &self + .window + .location() + .pathname() + .unwrap_or_else(|_| String::from("/")), + ) + .unwrap_or_else(|err| panic!("{}", err)) + } + + fn full_path(&self, state: &R) -> String { + match &self.prefix { + None => format!("{state}"), + Some(prefix) => format!("{prefix}{state}"), + } + } + + fn handle_nav(&self, result: Result<(), JsValue>) { + match result { + Ok(_) => { + if self.do_scroll_restoration { + self.window.scroll_to_with_x_and_y(0.0, 0.0) + } + } + Err(e) => error!("failed to change state: ", e), + } + } + + fn navigate_external(&mut self, url: String) -> bool { + match self.window.location().set_href(&url) { + Ok(_) => true, + Err(e) => { + error!("failed to navigate to external url (", url, "): ", e); + false + } + } + } +} + +#[cfg(feature = "serde")] +impl HistoryProvider + for WebHistory where ::Err: std::fmt::Display, { @@ -126,14 +254,7 @@ where // Try to get the route from the history state Some(route) => route.state, // If that fails, get the route from the current URL - None => R::from_str( - &self - .window - .location() - .pathname() - .unwrap_or_else(|_| String::from("/")), - ) - .unwrap_or_else(|err| panic!("{}", err)), + None => self.route_from_location(), } } @@ -154,23 +275,11 @@ where } fn push(&mut self, state: R) { - let path = match &self.prefix { - None => format!("{state}"), - Some(prefix) => format!("{prefix}{state}"), - }; + let path = self.full_path(&state); let state = self.create_state(state); - let nav = push_state_and_url(&self.history, &state, path); - - match nav { - Ok(_) => { - if self.do_scroll_restoration { - self.window.scroll_to_with_x_and_y(0.0, 0.0) - } - } - Err(e) => error!("failed to push state: ", e), - } + self.handle_nav(push_state_and_url(&self.history, &state, path)); } fn replace(&mut self, state: R) { @@ -181,26 +290,11 @@ where let state = self.create_state(state); - let nav = replace_state_with_url(&self.history, &state, Some(&path)); - - match nav { - Ok(_) => { - if self.do_scroll_restoration { - self.window.scroll_to_with_x_and_y(0.0, 0.0) - } - } - Err(e) => error!("failed to replace state:", e), - } + self.handle_nav(replace_state_with_url(&self.history, &state, Some(&path))); } fn external(&mut self, url: String) -> bool { - match self.window.location().set_href(&url) { - Ok(_) => true, - Err(e) => { - error!("failed to navigate to external url (", url, "): ", e); - false - } - } + self.navigate_external(url) } fn updater(&mut self, callback: std::sync::Arc) { @@ -220,3 +314,67 @@ where })); } } + +#[cfg(not(feature = "serde"))] +impl HistoryProvider for WebHistory +where + ::Err: std::fmt::Display, +{ + fn current_route(&self) -> R { + self.route_from_location() + } + + fn current_prefix(&self) -> Option { + self.prefix.clone() + } + + fn go_back(&mut self) { + if let Err(e) = self.history.back() { + error!("failed to go back: ", e) + } + } + + fn go_forward(&mut self) { + if let Err(e) = self.history.forward() { + error!("failed to go forward: ", e) + } + } + + fn push(&mut self, state: R) { + let path = self.full_path(&state); + + let state: [f64; 2] = self.create_state(state); + self.handle_nav(push_state_and_url(&self.history, &state, path)); + } + + fn replace(&mut self, state: R) { + let path = match &self.prefix { + None => format!("{state}"), + Some(prefix) => format!("{prefix}{state}"), + }; + + let state = self.create_state(state); + self.handle_nav(replace_state_with_url(&self.history, &state, Some(&path))); + } + + fn external(&mut self, url: String) -> bool { + self.navigate_external(url) + } + + fn updater(&mut self, callback: std::sync::Arc) { + let w = self.window.clone(); + let h = self.history.clone(); + let s = self.listener_animation_frame.clone(); + let d = self.do_scroll_restoration; + + self.listener_navigation = Some(EventListener::new(&self.window, "popstate", move |_| { + (*callback)(); + if d { + let mut s = s.lock().expect("unpoisoned scroll mutex"); + if let Some([x, y]) = get_current(&h) { + *s = Some(ScrollPosition { x, y }.scroll_to(w.clone())); + } + } + })); + } +} diff --git a/packages/router/src/history/web_history.rs b/packages/router/src/history/web_history.rs index 711c1b251..25a7063b4 100644 --- a/packages/router/src/history/web_history.rs +++ b/packages/router/src/history/web_history.rs @@ -1,10 +1,24 @@ use gloo::console::error; +#[cfg(feature = "serde")] use gloo_utils::format::JsValueSerdeExt; -use serde::{de::DeserializeOwned, Serialize}; use wasm_bindgen::JsValue; use web_sys::History; -pub(crate) fn replace_state_with_url( +#[cfg(not(feature = "serde"))] +pub(crate) fn replace_state_with_url( + history: &History, + value: &[f64; 2], + url: Option<&str>, +) -> Result<(), JsValue> { + let position = js_sys::Array::new(); + position.push(&JsValue::from(value[0])); + position.push(&JsValue::from(value[1])); + + history.replace_state_with_url(&position, "", url) +} + +#[cfg(feature = "serde")] +pub(crate) fn replace_state_with_url( history: &History, value: &V, url: Option<&str>, @@ -14,7 +28,21 @@ pub(crate) fn replace_state_with_url( history.replace_state_with_url(&position, "", url) } -pub(crate) fn push_state_and_url( +#[cfg(not(feature = "serde"))] +pub(crate) fn push_state_and_url( + history: &History, + value: &[f64; 2], + url: String, +) -> Result<(), JsValue> { + let position = js_sys::Array::new(); + position.push(&JsValue::from(value[0])); + position.push(&JsValue::from(value[1])); + + history.push_state_with_url(&position, "", Some(&url)) +} + +#[cfg(feature = "serde")] +pub(crate) fn push_state_and_url( history: &History, value: &V, url: String, @@ -24,7 +52,8 @@ pub(crate) fn push_state_and_url( history.push_state_with_url(&position, "", Some(&url)) } -pub(crate) fn get_current(history: &History) -> Option { +#[cfg(feature = "serde")] +pub(crate) fn get_current(history: &History) -> Option { let state = history.state(); if let Err(err) = &state { error!(err); @@ -37,3 +66,19 @@ pub(crate) fn get_current(history: &History) -> Option { deserialized.ok() }) } + +#[cfg(not(feature = "serde"))] +pub(crate) fn get_current(history: &History) -> Option<[f64; 2]> { + use wasm_bindgen::JsCast; + + let state = history.state(); + if let Err(err) = &state { + error!(err); + } + state.ok().and_then(|state| { + let state = state.dyn_into::().ok()?; + let x = state.get(0).as_f64()?; + let y = state.get(1).as_f64()?; + Some([x, y]) + }) +} diff --git a/packages/router/src/history/web_scroll.rs b/packages/router/src/history/web_scroll.rs index 09a43bbf2..4e5d8318f 100644 --- a/packages/router/src/history/web_scroll.rs +++ b/packages/router/src/history/web_scroll.rs @@ -1,11 +1,11 @@ use gloo::render::{request_animation_frame, AnimationFrame}; -use serde::{Deserialize, Serialize}; use web_sys::Window; -#[derive(Clone, Copy, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Copy, Debug, Default)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] pub(crate) struct ScrollPosition { - x: f64, - y: f64, + pub x: f64, + pub y: f64, } impl ScrollPosition { diff --git a/packages/router/src/router_cfg.rs b/packages/router/src/router_cfg.rs index 96792d031..a11bb31a7 100644 --- a/packages/router/src/router_cfg.rs +++ b/packages/router/src/router_cfg.rs @@ -2,7 +2,6 @@ use crate::contexts::router::RoutingCallback; use crate::history::HistoryProvider; use crate::routable::Routable; use dioxus::prelude::*; -use serde::{de::DeserializeOwned, Serialize}; use crate::prelude::*; @@ -52,7 +51,7 @@ pub struct RouterConfiguration { pub on_update: Option>, } -impl Default for RouterConfiguration +impl Default for RouterConfiguration where ::Err: std::fmt::Display, {