From bdeadba508eb9e035e9f9e61e0e2d255e492e2ce Mon Sep 17 00:00:00 2001 From: Joshua McQuistan Date: Fri, 27 Oct 2023 12:39:58 +0100 Subject: [PATCH] Delete existing storage/ in favour of use_storage.rs --- src/storage/mod.rs | 8 - src/storage/shared.rs | 96 ------ src/storage/use_local_storage.rs | 22 -- src/storage/use_session_storage.rs | 22 -- src/storage/use_storage.rs | 498 ----------------------------- 5 files changed, 646 deletions(-) delete mode 100644 src/storage/mod.rs delete mode 100644 src/storage/shared.rs delete mode 100644 src/storage/use_local_storage.rs delete mode 100644 src/storage/use_session_storage.rs delete mode 100644 src/storage/use_storage.rs diff --git a/src/storage/mod.rs b/src/storage/mod.rs deleted file mode 100644 index bc0844b..0000000 --- a/src/storage/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod shared; -mod use_local_storage; -mod use_session_storage; -mod use_storage; - -pub use use_local_storage::*; -pub use use_session_storage::*; -pub use use_storage::*; diff --git a/src/storage/shared.rs b/src/storage/shared.rs deleted file mode 100644 index bd9ef93..0000000 --- a/src/storage/shared.rs +++ /dev/null @@ -1,96 +0,0 @@ -use crate::filter_builder_methods; -use crate::storage::{StorageType, UseStorageError, UseStorageOptions}; -use crate::utils::{DebounceOptions, FilterOptions, ThrottleOptions}; -use default_struct_builder::DefaultBuilder; -use leptos::*; -use std::rc::Rc; - -macro_rules! use_specific_storage { - ($(#[$outer:meta])* - $storage_name:ident - #[$simple_func:meta] - ) => { - paste! { - $(#[$outer])* - pub fn []( - key: &str, - defaults: D, - ) -> (Signal, WriteSignal, impl Fn() + Clone) - where - for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, - T: Clone, - { - [](key, defaults, UseSpecificStorageOptions::default()) - } - - /// Version of - #[$simple_func] - /// that accepts [`UseSpecificStorageOptions`]. See - #[$simple_func] - /// for how to use. - pub fn []( - key: &str, - defaults: D, - options: UseSpecificStorageOptions, - ) -> (Signal, WriteSignal, impl Fn() + Clone) - where - for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, - T: Clone, - { - use_storage_with_options(key, defaults, options.into_storage_options(StorageType::[<$storage_name:camel>])) - } - } - }; -} - -pub(crate) use use_specific_storage; - -/// Options for [`use_local_storage_with_options`]. -// #[doc(cfg(feature = "storage"))] -#[derive(DefaultBuilder)] -pub struct UseSpecificStorageOptions { - /// Listen to changes to this storage key from somewhere else. Defaults to true. - listen_to_storage_changes: bool, - /// If no value for the give key is found in the storage, write it. Defaults to true. - write_defaults: bool, - /// Takes the serialized (json) stored value and the default value and returns a merged version. - /// Defaults to simply returning the stored value. - merge_defaults: fn(&str, &T) -> String, - /// Optional callback whenever an error occurs. The callback takes an argument of type [`UseStorageError`]. - on_error: Rc, - - /// Debounce or throttle the writing to storage whenever the value changes. - filter: FilterOptions, -} - -impl Default for UseSpecificStorageOptions { - fn default() -> Self { - Self { - listen_to_storage_changes: true, - write_defaults: true, - merge_defaults: |stored_value, _default_value| stored_value.to_string(), - on_error: Rc::new(|_| ()), - filter: Default::default(), - } - } -} - -impl UseSpecificStorageOptions { - pub fn into_storage_options(self, storage_type: StorageType) -> UseStorageOptions { - UseStorageOptions { - storage_type, - listen_to_storage_changes: self.listen_to_storage_changes, - write_defaults: self.write_defaults, - merge_defaults: self.merge_defaults, - on_error: self.on_error, - filter: self.filter, - } - } - - filter_builder_methods!( - /// the serializing and storing into storage - filter - ); -} diff --git a/src/storage/use_local_storage.rs b/src/storage/use_local_storage.rs deleted file mode 100644 index 3ed7716..0000000 --- a/src/storage/use_local_storage.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::core::MaybeRwSignal; -use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions}; -use crate::storage::{use_storage_with_options, StorageType}; -use leptos::*; -use paste::paste; -use serde::{Deserialize, Serialize}; - -use_specific_storage!( - /// Reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) - /// - /// ## Usage - /// - /// Please refer to [`use_storage`] - /// - /// ## See also - /// - /// * [`use_storage`] - /// * [`use_session_storage`] - // #[doc(cfg(feature = "storage"))] - local - /// [`use_local_storage`] -); diff --git a/src/storage/use_session_storage.rs b/src/storage/use_session_storage.rs deleted file mode 100644 index dca9b95..0000000 --- a/src/storage/use_session_storage.rs +++ /dev/null @@ -1,22 +0,0 @@ -use crate::core::MaybeRwSignal; -use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions}; -use crate::storage::{use_storage_with_options, StorageType}; -use leptos::*; -use paste::paste; -use serde::{Deserialize, Serialize}; - -use_specific_storage!( - /// Reactive [SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage) - /// - /// ## Usage - /// - /// Please refer to [`use_storage`] - /// - /// ## See also - /// - /// * [`use_storage`] - /// * [`use_local_storage`] - // #[doc(cfg(feature = "storage"))] - session - /// [`use_session_storage`] -); diff --git a/src/storage/use_storage.rs b/src/storage/use_storage.rs deleted file mode 100644 index 7122882..0000000 --- a/src/storage/use_storage.rs +++ /dev/null @@ -1,498 +0,0 @@ -#![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))] - -use crate::core::MaybeRwSignal; -use crate::utils::FilterOptions; -use crate::{ - filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions, - ThrottleOptions, WatchOptions, WatchPausableReturn, -}; -use cfg_if::cfg_if; -use default_struct_builder::DefaultBuilder; -use js_sys::Reflect; -use leptos::*; -use serde::{Deserialize, Serialize}; -use serde_json::Error; -use std::rc::Rc; -use std::time::Duration; -use wasm_bindgen::{JsCast, JsValue}; - -pub use crate::core::StorageType; - -const CUSTOM_STORAGE_EVENT_NAME: &str = "leptos-use-storage"; - -/// Reactive [LocalStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage) / [SessionStorage](https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage). -/// -/// ## Demo -/// -/// [Link to Demo](https://github.com/Synphonyte/leptos-use/tree/main/examples/use_storage) -/// -/// ## Usage -/// -/// It returns a triplet `(read_signal, write_signal, delete_from_storage_func)` of type `(ReadSignal, WriteSignal, Fn())`. -/// -/// Values are (de-)serialized to/from JSON using [`serde`](https://serde.rs/). -/// -/// ``` -/// # use leptos::*; -/// # use leptos_use::storage::{StorageType, use_storage, use_storage_with_options, UseStorageOptions}; -/// # use serde::{Deserialize, Serialize}; -/// # -/// #[derive(Serialize, Deserialize, Clone)] -/// pub struct MyState { -/// pub hello: String, -/// pub greeting: String, -/// } -/// -/// # pub fn Demo() -> impl IntoView { -/// // bind struct. Must be serializable. -/// let (state, set_state, _) = use_storage( -/// "my-state", -/// MyState { -/// hello: "hi".to_string(), -/// greeting: "Hello".to_string() -/// }, -/// ); // returns Signal -/// -/// // bind bool. -/// let (flag, set_flag, remove_flag) = use_storage("my-flag", true); // returns Signal -/// -/// // bind number -/// let (count, set_count, _) = use_storage("my-count", 0); // returns Signal -/// -/// // bind string with SessionStorage -/// let (id, set_id, _) = use_storage_with_options( -/// "my-id", -/// "some_string_id".to_string(), -/// UseStorageOptions::default().storage_type(StorageType::Session), -/// ); -/// # view! { } -/// # } -/// ``` -/// -/// ## Merge Defaults -/// -/// By default, [`use_storage`] will use the value from storage if it is present and ignores the default value. -/// Be aware that when you add more properties to the default value, the key might be `None` -/// (in the case of an `Option` field) if client's storage does not have that key -/// or deserialization might fail altogether. -/// -/// Let's say you had a struct `MyState` that has been saved to storage -/// -/// ```ignore -/// #[derive(Serialize, Deserialize, Clone)] -/// struct MyState { -/// hello: String, -/// } -/// -/// let (state, .. ) = use_storage("my-state", MyState { hello: "hello" }); -/// ``` -/// -/// Now, in a newer version you added a field `greeting` to `MyState`. -/// -/// ```ignore -/// #[derive(Serialize, Deserialize, Clone)] -/// struct MyState { -/// hello: String, -/// greeting: String, -/// } -/// -/// let (state, .. ) = use_storage( -/// "my-state", -/// MyState { hello: "hi", greeting: "whatsup" }, -/// ); // fails to deserialize -> default value -/// ``` -/// -/// This will fail to deserialize the stored string `{"hello": "hello"}` because it has no field `greeting`. -/// Hence it just uses the new default value provided and the previously saved value is lost. -/// -/// To mitigate that you can provide a `merge_defaults` option. This is a pure function pointer -/// that takes the serialized (to json) stored value and the default value as arguments -/// and should return the serialized merged value. -/// -/// ``` -/// # use leptos::*; -/// # use leptos_use::storage::{use_storage_with_options, UseStorageOptions}; -/// # use serde::{Deserialize, Serialize}; -/// # -/// #[derive(Serialize, Deserialize, Clone)] -/// pub struct MyState { -/// pub hello: String, -/// pub greeting: String, -/// } -/// # -/// # pub fn Demo() -> impl IntoView { -/// let (state, set_state, _) = use_storage_with_options( -/// "my-state", -/// MyState { -/// hello: "hi".to_string(), -/// greeting: "Hello".to_string() -/// }, -/// UseStorageOptions::::default().merge_defaults(|stored_value, default_value| { -/// if stored_value.contains(r#""greeting":"#) { -/// stored_value.to_string() -/// } else { -/// // add "greeting": "Hello" to the string -/// stored_value.replace("}", &format!(r#""greeting": "{}"}}"#, default_value.greeting)) -/// } -/// }), -/// ); -/// # -/// # view! { } -/// # } -/// ``` -/// -/// ## Filter Storage Write -/// -/// You can specify `debounce` or `throttle` options for limiting writes to storage. -/// -/// ## Server-Side Rendering -/// -/// On the server this falls back to a `create_signal(default)` and an empty remove function. -/// -/// ## See also -/// -/// * [`use_local_storage`] -/// * [`use_session_storage`] -// #[doc(cfg(feature = "storage"))] -pub fn use_storage(key: &str, defaults: D) -> (Signal, WriteSignal, impl Fn() + Clone) -where - for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, - T: Clone, -{ - use_storage_with_options(key, defaults, UseStorageOptions::default()) -} - -/// Version of [`use_storage`] that accepts [`UseStorageOptions`]. See [`use_storage`] for how to use. -// #[doc(cfg(feature = "storage"))] -pub fn use_storage_with_options( - key: &str, - defaults: D, - options: UseStorageOptions, -) -> (Signal, WriteSignal, impl Fn() + Clone) -where - for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, - T: Clone, -{ - let defaults = defaults.into(); - - let UseStorageOptions { - storage_type, - listen_to_storage_changes, - write_defaults, - merge_defaults, - on_error, - filter, - } = options; - - let (data, set_data) = defaults.into_signal(); - - let raw_init = data.get_untracked(); - - cfg_if! { if #[cfg(feature = "ssr")] { - let remove: Rc = Rc::new(|| {}); - } else { - let storage = storage_type.into_storage(); - - let remove: Rc = match storage { - Ok(Some(storage)) => { - let write = { - let on_error = on_error.clone(); - let storage = storage.clone(); - let key = key.to_string(); - - Rc::new(move |v: &T| { - match serde_json::to_string(&v) { - Ok(ref serialized) => match storage.get_item(&key) { - Ok(old_value) => { - if old_value.as_ref() != Some(serialized) { - if let Err(e) = storage.set_item(&key, serialized) { - on_error(UseStorageError::StorageAccessError(e)); - } else { - let mut event_init = web_sys::CustomEventInit::new(); - event_init.detail( - &StorageEventDetail { - key: Some(key.clone()), - old_value, - new_value: Some(serialized.clone()), - storage_area: Some(storage.clone()), - } - .into(), - ); - - // importantly this should _not_ be a StorageEvent since those cannot - // be constructed with a non-built-in storage area - let _ = window().dispatch_event( - &web_sys::CustomEvent::new_with_event_init_dict( - CUSTOM_STORAGE_EVENT_NAME, - &event_init, - ) - .expect("Failed to create CustomEvent"), - ); - } - } - } - Err(e) => { - on_error.clone()(UseStorageError::StorageAccessError(e)); - } - }, - Err(e) => { - on_error.clone()(UseStorageError::SerializationError(e)); - } - } - }) - }; - - let read = { - let storage = storage.clone(); - let on_error = on_error.clone(); - let key = key.to_string(); - let raw_init = raw_init.clone(); - - Rc::new( - move |event_detail: Option| -> Option { - let serialized_init = match serde_json::to_string(&raw_init) { - Ok(serialized) => Some(serialized), - Err(e) => { - on_error.clone()(UseStorageError::DefaultSerializationError(e)); - None - } - }; - - let raw_value = if let Some(event_detail) = event_detail { - event_detail.new_value - } else { - match storage.get_item(&key) { - Ok(raw_value) => match raw_value { - Some(raw_value) => Some(merge_defaults(&raw_value, &raw_init)), - None => serialized_init.clone(), - }, - Err(e) => { - on_error.clone()(UseStorageError::StorageAccessError(e)); - None - } - } - }; - - match raw_value { - Some(raw_value) => match serde_json::from_str(&raw_value) { - Ok(v) => Some(v), - Err(e) => { - on_error.clone()(UseStorageError::SerializationError(e)); - None - } - }, - None => { - if let Some(serialized_init) = &serialized_init { - if write_defaults { - if let Err(e) = storage.set_item(&key, serialized_init) { - on_error(UseStorageError::StorageAccessError(e)); - } - } - } - - Some(raw_init.clone()) - } - } - }, - ) - }; - - let WatchPausableReturn { - pause: pause_watch, - resume: resume_watch, - .. - } = watch_pausable_with_options( - move || data.get(), - move |data, _, _| Rc::clone(&write)(data), - WatchOptions::default().filter(filter), - ); - - let update = { - let key = key.to_string(); - let storage = storage.clone(); - let raw_init = raw_init.clone(); - - Rc::new(move |event_detail: Option| { - if let Some(event_detail) = &event_detail { - if event_detail.storage_area != Some(storage.clone()) { - return; - } - - match &event_detail.key { - None => { - set_data.set(raw_init.clone()); - return; - } - Some(event_key) => { - if event_key != &key { - return; - } - } - }; - } - - pause_watch(); - - if let Some(value) = read(event_detail.clone()) { - set_data.set(value); - } - - if event_detail.is_some() { - // use timeout to avoid infinite loop - let resume = resume_watch.clone(); - let _ = set_timeout_with_handle(resume, Duration::ZERO); - } else { - resume_watch(); - } - }) - }; - - let update_from_custom_event = { - let update = Rc::clone(&update); - - move |event: web_sys::CustomEvent| { - let update = Rc::clone(&update); - queue_microtask(move || update(Some(event.into()))) - } - }; - - let update_from_storage_event = { - let update = Rc::clone(&update); - - move |event: web_sys::StorageEvent| update(Some(event.into())) - }; - - if listen_to_storage_changes { - let _ = use_event_listener(window(), ev::storage, update_from_storage_event); - let _ = use_event_listener( - window(), - ev::Custom::new(CUSTOM_STORAGE_EVENT_NAME), - update_from_custom_event, - ); - } - - update(None); - - let k = key.to_string(); - - Rc::new(move || { - let _ = storage.remove_item(&k); - }) - } - Err(e) => { - on_error(UseStorageError::NoStorage(e)); - Rc::new(move || {}) - } - _ => { - // do nothing - Rc::new(move || {}) - } - }; - }} - - (data, set_data, move || remove()) -} - -#[derive(Clone)] -pub struct StorageEventDetail { - pub key: Option, - pub old_value: Option, - pub new_value: Option, - pub storage_area: Option, -} - -impl From for StorageEventDetail { - fn from(event: web_sys::StorageEvent) -> Self { - Self { - key: event.key(), - old_value: event.old_value(), - new_value: event.new_value(), - storage_area: event.storage_area(), - } - } -} - -impl From for StorageEventDetail { - fn from(event: web_sys::CustomEvent) -> Self { - let detail = event.detail(); - Self { - key: get_optional_string(&detail, "key"), - old_value: get_optional_string(&detail, "oldValue"), - new_value: get_optional_string(&detail, "newValue"), - storage_area: Reflect::get(&detail, &"storageArea".into()) - .map(|v| v.dyn_into::().ok()) - .unwrap_or_default(), - } - } -} - -impl From for JsValue { - fn from(event: StorageEventDetail) -> Self { - let obj = js_sys::Object::new(); - - let _ = Reflect::set(&obj, &"key".into(), &event.key.into()); - let _ = Reflect::set(&obj, &"oldValue".into(), &event.old_value.into()); - let _ = Reflect::set(&obj, &"newValue".into(), &event.new_value.into()); - let _ = Reflect::set(&obj, &"storageArea".into(), &event.storage_area.into()); - - obj.into() - } -} - -fn get_optional_string(v: &JsValue, key: &str) -> Option { - Reflect::get(v, &key.into()) - .map(|v| v.as_string()) - .unwrap_or_default() -} - -/// Error type for use_storage_with_options -// #[doc(cfg(feature = "storage"))] -pub enum UseStorageError { - NoStorage(JsValue), - StorageAccessError(JsValue), - CustomStorageAccessError(E), - SerializationError(Error), - DefaultSerializationError(Error), -} - -/// Options for [`use_storage_with_options`]. -// #[doc(cfg(feature = "storage"))] -#[derive(DefaultBuilder)] -pub struct UseStorageOptions { - /// Type of storage. Can be `Local` (default), `Session` or `Custom(web_sys::Storage)` - pub(crate) storage_type: StorageType, - /// Listen to changes to this storage key from somewhere else. Defaults to true. - pub(crate) listen_to_storage_changes: bool, - /// If no value for the give key is found in the storage, write it. Defaults to true. - pub(crate) write_defaults: bool, - /// Takes the serialized (json) stored value and the default value and returns a merged version. - /// Defaults to simply returning the stored value. - pub(crate) merge_defaults: fn(&str, &T) -> String, - /// Optional callback whenever an error occurs. The callback takes an argument of type [`UseStorageError`]. - pub(crate) on_error: Rc, - - /// Debounce or throttle the writing to storage whenever the value changes. - pub(crate) filter: FilterOptions, -} - -impl Default for UseStorageOptions { - fn default() -> Self { - Self { - storage_type: Default::default(), - listen_to_storage_changes: true, - write_defaults: true, - merge_defaults: |stored_value, _default_value| stored_value.to_string(), - on_error: Rc::new(|_| ()), - filter: Default::default(), - } - } -} - -impl UseStorageOptions { - filter_builder_methods!( - /// the serializing and storing into storage - filter - ); -}