mirror of
https://github.com/adoyle0/leptos-use.git
synced 2025-01-23 09:09:21 -05:00
Delete existing storage/ in favour of use_storage.rs
This commit is contained in:
parent
4538097ab1
commit
bdeadba508
5 changed files with 0 additions and 646 deletions
|
@ -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::*;
|
|
|
@ -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 [<use_ $storage_name _storage>]<T, D>(
|
|
||||||
key: &str,
|
|
||||||
defaults: D,
|
|
||||||
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
|
|
||||||
where
|
|
||||||
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
|
|
||||||
D: Into<MaybeRwSignal<T>>,
|
|
||||||
T: Clone,
|
|
||||||
{
|
|
||||||
[<use_ $storage_name _storage_with_options>](key, defaults, UseSpecificStorageOptions::default())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Version of
|
|
||||||
#[$simple_func]
|
|
||||||
/// that accepts [`UseSpecificStorageOptions`]. See
|
|
||||||
#[$simple_func]
|
|
||||||
/// for how to use.
|
|
||||||
pub fn [<use_ $storage_name _storage_with_options>]<T, D>(
|
|
||||||
key: &str,
|
|
||||||
defaults: D,
|
|
||||||
options: UseSpecificStorageOptions<T>,
|
|
||||||
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
|
|
||||||
where
|
|
||||||
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
|
|
||||||
D: Into<MaybeRwSignal<T>>,
|
|
||||||
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<T> {
|
|
||||||
/// 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<dyn Fn(UseStorageError)>,
|
|
||||||
|
|
||||||
/// Debounce or throttle the writing to storage whenever the value changes.
|
|
||||||
filter: FilterOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for UseSpecificStorageOptions<T> {
|
|
||||||
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<T> UseSpecificStorageOptions<T> {
|
|
||||||
pub fn into_storage_options(self, storage_type: StorageType) -> UseStorageOptions<T> {
|
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -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`]
|
|
||||||
);
|
|
|
@ -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`]
|
|
||||||
);
|
|
|
@ -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<T>, WriteSignal<T>, 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<MyState>
|
|
||||||
///
|
|
||||||
/// // bind bool.
|
|
||||||
/// let (flag, set_flag, remove_flag) = use_storage("my-flag", true); // returns Signal<bool>
|
|
||||||
///
|
|
||||||
/// // bind number
|
|
||||||
/// let (count, set_count, _) = use_storage("my-count", 0); // returns Signal<i32>
|
|
||||||
///
|
|
||||||
/// // 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<T>` 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::<MyState>::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<T, D>(key: &str, defaults: D) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
|
|
||||||
where
|
|
||||||
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
|
|
||||||
D: Into<MaybeRwSignal<T>>,
|
|
||||||
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<T, D>(
|
|
||||||
key: &str,
|
|
||||||
defaults: D,
|
|
||||||
options: UseStorageOptions<T>,
|
|
||||||
) -> (Signal<T>, WriteSignal<T>, impl Fn() + Clone)
|
|
||||||
where
|
|
||||||
for<'de> T: Serialize + Deserialize<'de> + Clone + 'static,
|
|
||||||
D: Into<MaybeRwSignal<T>>,
|
|
||||||
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<dyn Fn()> = Rc::new(|| {});
|
|
||||||
} else {
|
|
||||||
let storage = storage_type.into_storage();
|
|
||||||
|
|
||||||
let remove: Rc<dyn Fn()> = 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<StorageEventDetail>| -> Option<T> {
|
|
||||||
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<StorageEventDetail>| {
|
|
||||||
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<String>,
|
|
||||||
pub old_value: Option<String>,
|
|
||||||
pub new_value: Option<String>,
|
|
||||||
pub storage_area: Option<web_sys::Storage>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<web_sys::StorageEvent> 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<web_sys::CustomEvent> 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::<web_sys::Storage>().ok())
|
|
||||||
.unwrap_or_default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<StorageEventDetail> 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<String> {
|
|
||||||
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<E = ()> {
|
|
||||||
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<T> {
|
|
||||||
/// 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<dyn Fn(UseStorageError)>,
|
|
||||||
|
|
||||||
/// Debounce or throttle the writing to storage whenever the value changes.
|
|
||||||
pub(crate) filter: FilterOptions,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> Default for UseStorageOptions<T> {
|
|
||||||
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<T> UseStorageOptions<T> {
|
|
||||||
filter_builder_methods!(
|
|
||||||
/// the serializing and storing into storage
|
|
||||||
filter
|
|
||||||
);
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue