// use crate::utils::{CloneableFnWithArg, FilterOptions}; // use crate::{ // filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions, // ThrottleOptions, WatchOptions, WatchPausableReturn, // }; // use default_struct_builder::DefaultBuilder; // use js_sys::Reflect; // use leptos::*; // use serde::{Deserialize, Serialize}; // use serde_json::Error; // use std::time::Duration; // use wasm_bindgen::{JsCast, JsValue}; // // 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 `(Signal, WriteSignal, Fn())`. // /// // /// ``` // /// # 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(cx: Scope) -> impl IntoView { // /// // bind struct. Must be serializable. // /// let (state, set_state, _) = use_storage( // /// cx, // /// "my-state", // /// MyState { // /// hello: "hi".to_string(), // /// greeting: "Hello".to_string() // /// }, // /// ); // returns Signal // /// // /// // bind bool. // /// let (flag, set_flag, remove_flag) = use_storage(cx, "my-flag", true); // returns Signal // /// // /// // bind number // /// let (count, set_count, _) = use_storage(cx, "my-count", 0); // returns Signal // /// // /// // bind string with SessionStorage // /// let (id, set_id, _) = use_storage_with_options( // /// cx, // /// "my-id", // /// "some_string_id".to_string(), // /// UseStorageOptions::default().storage_type(StorageType::Session), // /// ); // /// # view! { cx, } // /// # } // /// ``` // /// // /// ## 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(cx, "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( // /// cx, // /// "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(cx: Scope) -> impl IntoView { // /// let (state, set_state) = use_storage_with_options( // /// cx, // /// "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! { cx, } // /// # } // /// ``` // /// // /// ## Filter Storage Write // /// // /// You can specify `debounce` and `throttle` filter options for writing to storage. // /// // /// ## See also // /// // /// // /// * [`use_local_storage`] // /// * [`use_session_storage`] // #[doc(cfg(feature = "storage"))] // pub fn use_storage( // cx: Scope, // 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(cx, 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( // cx: Scope, // 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) = create_signal(cx, defaults.get_untracked()); // // let storage = storage_type.into_storage(); // // let remove = match storage { // Ok(Some(storage)) => { // let on_err = on_error.clone(); // // let store = storage.clone(); // let k = key.to_string(); // // let write = move |v: &Option| { // if let Some(v) = v { // match serde_json::to_string(&v) { // Ok(ref serialized) => match store.get_item(&k) { // Ok(old_value) => { // if old_value.as_ref() != Some(serialized) { // if let Err(e) = store.set_item(&k, &serialized) { // on_err(UseStorageError::StorageAccessError(e)); // } else { // let mut event_init = web_sys::CustomEventInit::new(); // event_init.detail( // &StorageEventDetail { // key: Some(k.clone()), // old_value, // new_value: Some(serialized.clone()), // storage_area: Some(store.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_err.clone()(UseStorageError::StorageAccessError(e)); // } // }, // Err(e) => { // on_err.clone()(UseStorageError::SerializationError(e)); // } // } // } else if let Err(e) = store.remove_item(&k) { // on_err(UseStorageError::StorageAccessError(e)); // } // }; // // let store = storage.clone(); // let on_err = on_error.clone(); // let k = key.to_string(); // let def = defaults.clone(); // // let read = move |event_detail: Option| -> Option { // let raw_init = match serde_json::to_string(&def.get_untracked()) { // Ok(serialized) => Some(serialized), // Err(e) => { // on_err.clone()(UseStorageError::DefaultSerializationError(e)); // None // } // }; // // let raw_value = if let Some(event_detail) = event_detail { // event_detail.new_value // } else { // match store.get_item(&k) { // Ok(raw_value) => match raw_value { // Some(raw_value) => { // Some(merge_defaults(&raw_value, &def.get_untracked())) // } // None => raw_init.clone(), // }, // Err(e) => { // on_err.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_err.clone()(UseStorageError::SerializationError(e)); // None // } // }, // None => { // if let Some(raw_init) = &raw_init { // if write_defaults { // if let Err(e) = store.set_item(&k, raw_init) { // on_err(UseStorageError::StorageAccessError(e)); // } // } // } // // Some(def.get_untracked()) // } // } // }; // // let WatchPausableReturn { // pause: pause_watch, // resume: resume_watch, // .. // } = watch_pausable_with_options( // cx, // data, // move |data, _, _| write.clone()(data), // WatchOptions::default().immediate(true).filter(filter), // ); // // let k = key.to_string(); // // let update = move |event_detail: Option| { // if let Some(event_detail) = &event_detail { // if event_detail.storage_area != Some(storage) { // return; // } // // match &event_detail.key { // None => { // set_data(Some(defaults.get_untracked())); // return; // } // Some(event_key) => { // if event_key != &k { // return; // } // } // }; // } // // pause_watch(); // // set_data(read(event_detail.clone())); // // if event_detail.is_some() { // // use timeout to avoid inifinite loop // let resume = resume_watch.clone(); // let _ = set_timeout_with_handle(resume, Duration::ZERO); // } else { // resume_watch(); // } // }; // // let upd = update.clone(); // let update_from_custom_event = // move |event: web_sys::CustomEvent| upd.clone()(Some(event.into())); // // let upd = update.clone(); // let update_from_storage_event = // move |event: web_sys::StorageEvent| upd.clone()(Some(event.into())); // // if listen_to_storage_changes { // let _ = use_event_listener(cx, window(), ev::storage, update_from_storage_event); // let _ = use_event_listener( // cx, // window(), // ev::Custom::new(CUSTOM_STORAGE_EVENT_NAME), // update_from_custom_event, // ); // } // // update(None); // // move || storage.remove_item(&k) // } // Err(e) => { // on_error(UseStorageError::NoStorage(e)); // move || {} // } // _ => { // // do nothing // move || {} // } // }; // // (data, set_data, 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)` // storage_type: StorageType, // /// 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: Box>, // // /// Debounce or throttle the writing to storage whenever the value changes. // 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: Box::new(|_| ()), // filter: Default::default(), // } // } // } // // impl UseStorageOptions { // filter_builder_methods!( // /// the serializing and storing into storage // filter // ); // } // // /// Local or session storage or a custom store that is a `web_sys::Storage`. // #[doc(cfg(feature = "storage"))] // #[derive(Default)] // pub enum StorageType { // #[default] // Local, // Session, // Custom(web_sys::Storage), // } // // impl StorageType { // pub fn into_storage(self) -> Result, JsValue> { // match self { // StorageType::Local => window().local_storage(), // StorageType::Session => window().session_storage(), // StorageType::Custom(storage) => Ok(Some(storage)), // } // } // }