From e55f253e8dc8974f7393e32258810527929a5cca Mon Sep 17 00:00:00 2001 From: Maccesch Date: Sat, 15 Jul 2023 16:48:29 +0100 Subject: [PATCH] use `MaybeRwSignal` where applicable. Fixes #4 --- .fleet/run.json | 8 +- CHANGELOG.md | 38 ++++- README.md | 2 +- src/core/maybe_rw_signal.rs | 16 ++- src/storage/shared.rs | 8 +- src/storage/use_local_storage.rs | 1 + src/storage/use_session_storage.rs | 1 + src/storage/use_storage.rs | 223 +++++++++++++++-------------- src/use_color_mode.rs | 26 ++-- src/use_cycle_list.rs | 2 +- src/use_drop_zone.rs | 8 +- src/use_favicon.rs | 15 +- 12 files changed, 197 insertions(+), 151 deletions(-) diff --git a/.fleet/run.json b/.fleet/run.json index c6a052e..4e4ffb5 100644 --- a/.fleet/run.json +++ b/.fleet/run.json @@ -3,7 +3,13 @@ { "type": "cargo", "name": "Tests", - "cargoArgs": ["test", "--all-features"], + "cargoArgs": ["test", "--features", "math,storage,docs"], + }, + { + "type": "cargo", + "name": "Clippy", + "cargoArgs": ["+nightly", "clippy", "--features", "math,storage,docs", "--tests", "--", "-D", "warnings"], + "workingDir": "./", }, ] diff --git a/CHANGELOG.md b/CHANGELOG.md index e95c47a..bbcf635 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.5.0] - 2023-07-15 +## [Unreleased] - + +### Braking Changes 🛠 + +- The following functions now accept a `MaybeRwSignal` as their initial/default value which means + you can use a synchronized `RwSignal` in those places. + - `use_color_mode` + - `use_cycle_list` + - `use_favicon` + - `use_storage` + - `use_local_storage` + - `use_session_storage` +- Instead of returning `ReadSignal`, the following functions now return `Signal`. + - `use_color_mode` + - `use_favicon` + - `use_storage` + - `use_local_storage` + - `use_session_storage` + +### Fixes 🍕 + +- `use_drop_zone` now uses `.get_untracked()` in event handlers + +## [0.5.0] - 2023-07-15 ### New Functions 🚀 @@ -26,8 +49,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.4.0] - 2023-07-03 ### Braking Changes 🛠 + - Required `leptos` version is now 0.4 -- Following the changes in `leptos` there is no longer a `stable` crate feature required in order to use this library with a stable toolchain. +- Following the changes in `leptos` there is no longer a `stable` crate feature required in order to use this library + with a stable toolchain. If you want to use it with a nightly toolchain you have to enable the `nightly` feature only on `leptos` directly. No change is required for `leptos-use` itself. @@ -61,17 +86,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.3.0] - 2023-06-13 ### Braking Changes 🛠 + - `use_event_listener` no longer returns a `Box` but a `impl Fn() + Clone` ### Changes 🔥 - You can now specify a `&str` or `Signal` with CSS selectors wherever a node ref is accepted - Callbacks of the following functions no longer require `Clone` - - `use_resize_observer` - - `use_intersection_observer` + - `use_resize_observer` + - `use_intersection_observer` - These functions now also accept multiple target elements in addition to a single one: - - `use_resize_observer` - - `use_intersection_observer` + - `use_resize_observer` + - `use_intersection_observer` ### New Functions 🚀 diff --git a/README.md b/README.md index 8470b62..64f47aa 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ To run all tests run cargo test --all-features ``` -## Book +### Book First you need to install diff --git a/src/core/maybe_rw_signal.rs b/src/core/maybe_rw_signal.rs index 54aa9f9..f380221 100644 --- a/src/core/maybe_rw_signal.rs +++ b/src/core/maybe_rw_signal.rs @@ -77,14 +77,20 @@ impl From<&str> for MaybeRwSignal { } impl MaybeRwSignal { - pub fn to_signal(&self, cx: Scope) -> (Signal, WriteSignal) { + pub fn into_signal(self, cx: Scope) -> (Signal, WriteSignal) { match self { Self::DynamicRead(s) => { - // TODO : this feels hacky - let (_, w) = create_signal(cx, s.get_untracked()); - (*s, w) + let (r, w) = create_signal(cx, s.get_untracked()); + + create_effect(cx, move |_| { + w.update(move |w| { + *w = s.get(); + }); + }); + + (r.into(), w) } - Self::DynamicRw(r, w) => (*r, *w), + Self::DynamicRw(r, w) => (r, w), Self::Static(v) => { let (r, w) = create_signal(cx, v.clone()); (Signal::from(r), w) diff --git a/src/storage/shared.rs b/src/storage/shared.rs index 434334c..5704251 100644 --- a/src/storage/shared.rs +++ b/src/storage/shared.rs @@ -15,10 +15,10 @@ macro_rules! use_specific_storage { cx: Scope, key: &str, defaults: D, - ) -> (ReadSignal, WriteSignal, impl Fn() + Clone) + ) -> (Signal, WriteSignal, impl Fn() + Clone) where for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, + D: Into>, T: Clone, { [](cx, key, defaults, UseSpecificStorageOptions::default()) @@ -34,10 +34,10 @@ macro_rules! use_specific_storage { key: &str, defaults: D, options: UseSpecificStorageOptions, - ) -> (ReadSignal, WriteSignal, impl Fn() + Clone) + ) -> (Signal, WriteSignal, impl Fn() + Clone) where for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, + D: Into>, T: Clone, { use_storage_with_options(cx, key, defaults, options.into_storage_options(StorageType::[<$storage_name:camel>])) diff --git a/src/storage/use_local_storage.rs b/src/storage/use_local_storage.rs index 80c7822..3ed7716 100644 --- a/src/storage/use_local_storage.rs +++ b/src/storage/use_local_storage.rs @@ -1,3 +1,4 @@ +use crate::core::MaybeRwSignal; use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions}; use crate::storage::{use_storage_with_options, StorageType}; use leptos::*; diff --git a/src/storage/use_session_storage.rs b/src/storage/use_session_storage.rs index dd8cfa8..dca9b95 100644 --- a/src/storage/use_session_storage.rs +++ b/src/storage/use_session_storage.rs @@ -1,3 +1,4 @@ +use crate::core::MaybeRwSignal; use crate::storage::shared::{use_specific_storage, UseSpecificStorageOptions}; use crate::storage::{use_storage_with_options, StorageType}; use leptos::*; diff --git a/src/storage/use_storage.rs b/src/storage/use_storage.rs index d517ccf..2f72857 100644 --- a/src/storage/use_storage.rs +++ b/src/storage/use_storage.rs @@ -1,5 +1,6 @@ #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports, dead_code))] +use crate::core::MaybeRwSignal; use crate::utils::{CloneableFn, CloneableFnWithArg, FilterOptions}; use crate::{ filter_builder_methods, use_event_listener, watch_pausable_with_options, DebounceOptions, @@ -160,10 +161,10 @@ pub fn use_storage( cx: Scope, key: &str, defaults: D, -) -> (ReadSignal, WriteSignal, impl Fn() + Clone) +) -> (Signal, WriteSignal, impl Fn() + Clone) where for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, + D: Into>, T: Clone, { use_storage_with_options(cx, key, defaults, UseStorageOptions::default()) @@ -176,10 +177,10 @@ pub fn use_storage_with_options( key: &str, defaults: D, options: UseStorageOptions, -) -> (ReadSignal, WriteSignal, impl Fn() + Clone) +) -> (Signal, WriteSignal, impl Fn() + Clone) where for<'de> T: Serialize + Deserialize<'de> + Clone + 'static, - D: Into>, + D: Into>, T: Clone, { let defaults = defaults.into(); @@ -193,7 +194,9 @@ where filter, } = options; - let (data, set_data) = create_signal(cx, defaults.get_untracked()); + let (data, set_data) = defaults.into_signal(cx); + + let raw_init = data.get(); cfg_if! { if #[cfg(feature = "ssr")] { let remove: Box = Box::new(|| {}); @@ -202,101 +205,102 @@ where let remove: Box = match storage { Ok(Some(storage)) => { - let on_err = on_error.clone(); + let write = { + let on_error = on_error.clone(); + let storage = storage.clone(); + let key = key.to_string(); - let store = storage.clone(); - let k = key.to_string(); + 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(), + ); - let write = move |v: &T| { - 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"), - ); + // 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_err.clone()(UseStorageError::StorageAccessError(e)); + on_error.clone()(UseStorageError::SerializationError(e)); } - }, - Err(e) => { - on_err.clone()(UseStorageError::SerializationError(e)); } } }; - let store = storage.clone(); - let on_err = on_error.clone(); - let k = key.to_string(); - let def = defaults.clone(); + let read = { + let storage = storage.clone(); + let on_error = on_error.clone(); + let key = key.to_string(); + let raw_init = raw_init.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 - } - }; + 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 store.get_item(&k) { - Ok(raw_value) => match raw_value { - Some(raw_value) => { - Some(merge_defaults(&raw_value, &def.get_untracked())) + 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 } - 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)); + 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(def.get_untracked()) + Some(raw_init) + } } } }; @@ -312,40 +316,43 @@ where WatchOptions::default().filter(filter), ); - let k = key.to_string(); - let store = storage.clone(); + let update = { + let key = key.to_string(); + let storage = storage.clone(); + let raw_init = raw_init.clone(); - let update = move |event_detail: Option| { - if let Some(event_detail) = &event_detail { - if event_detail.storage_area != Some(store) { - return; - } - - match &event_detail.key { - None => { - set_data.set(defaults.get_untracked()); + move |event_detail: Option| { + if let Some(event_detail) = &event_detail { + if event_detail.storage_area != Some(storage) { return; } - Some(event_key) => { - if event_key != &k { + + match &event_detail.key { + None => { + set_data.set(raw_init); return; } - } - }; - } + Some(event_key) => { + if event_key != &key { + return; + } + } + }; + } - pause_watch(); + pause_watch(); - if let Some(value) = read(event_detail.clone()) { - set_data.set(value); - } + if let Some(value) = read(event_detail.clone()) { + set_data.set(value); + } - 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(); + 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(); + } } }; diff --git a/src/use_color_mode.rs b/src/use_color_mode.rs index 6af1c06..4582ab6 100644 --- a/src/use_color_mode.rs +++ b/src/use_color_mode.rs @@ -1,4 +1,4 @@ -use crate::core::ElementMaybeSignal; +use crate::core::{ElementMaybeSignal, MaybeRwSignal}; #[cfg(feature = "storage")] use crate::storage::{use_storage_with_options, UseStorageOptions}; #[cfg(feature = "storage")] @@ -248,7 +248,7 @@ where UseColorModeReturn { mode, set_mode: set_store, - store: store.into(), + store, set_store, system, state, @@ -269,20 +269,21 @@ pub enum ColorMode { cfg_if! { if #[cfg(feature = "storage")] { fn get_store_signal( cx: Scope, - initial_value: MaybeSignal, + initial_value: MaybeRwSignal, storage_signal: Option>, storage_key: &str, storage_enabled: bool, storage: StorageType, listen_to_storage_changes: bool, - ) -> (ReadSignal, WriteSignal) { + ) -> (Signal, WriteSignal) { if let Some(storage_signal) = storage_signal { - storage_signal.split() + let (store, set_store) = storage_signal.split(); + (store.into(), set_store) } else if storage_enabled { let (store, set_store, _) = use_storage_with_options( cx, storage_key, - initial_value.get_untracked(), + initial_value, UseStorageOptions::default() .listen_to_storage_changes(listen_to_storage_changes) .storage_type(storage), @@ -290,23 +291,24 @@ cfg_if! { if #[cfg(feature = "storage")] { (store, set_store) } else { - create_signal(cx, initial_value.get_untracked()) + initial_value.into_signal(cx) } } } else { fn get_store_signal( cx: Scope, - initial_value: MaybeSignal, + initial_value: MaybeRwSignal, storage_signal: Option>, _storage_key: &String, _storage_enabled: bool, _storage: StorageType, _listen_to_storage_changes: bool, - ) -> (ReadSignal, WriteSignal) { + ) -> (Signal, WriteSignal) { if let Some(storage_signal) = storage_signal { - storage_signal.split() + let (store, set_store) = storage_signal.split(); + (store.into(), set_store) } else { - create_signal(cx, initial_value.get_untracked()) + initial_value.into_signal(cx) } } }} @@ -368,7 +370,7 @@ where /// Initial value of the color mode. Defaults to `"Auto"`. #[builder(into)] - initial_value: MaybeSignal, + initial_value: MaybeRwSignal, /// Custom modes that you plan to use as `ColorMode::Custom(x)`. Defaults to `vec![]`. custom_modes: Vec, diff --git a/src/use_cycle_list.rs b/src/use_cycle_list.rs index a4d16c9..5142887 100644 --- a/src/use_cycle_list.rs +++ b/src/use_cycle_list.rs @@ -84,7 +84,7 @@ where } }; - let (state, set_state) = get_initial_value().to_signal(cx); + let (state, set_state) = get_initial_value().into_signal(cx); let index = { let list = list.clone(); diff --git a/src/use_drop_zone.rs b/src/use_drop_zone.rs index a6d2067..93a6dec 100644 --- a/src/use_drop_zone.rs +++ b/src/use_drop_zone.rs @@ -100,7 +100,7 @@ where update_files(&event); on_enter(UseDropZoneEvent { - files: files.get(), + files: files.get_untracked(), event, }); }); @@ -109,7 +109,7 @@ where event.prevent_default(); update_files(&event); on_over(UseDropZoneEvent { - files: files.get(), + files: files.get_untracked(), event, }); }); @@ -124,7 +124,7 @@ where update_files(&event); on_leave(UseDropZoneEvent { - files: files.get(), + files: files.get_untracked(), event, }); }); @@ -137,7 +137,7 @@ where update_files(&event); on_drop(UseDropZoneEvent { - files: files.get(), + files: files.get_untracked(), event, }); }); diff --git a/src/use_favicon.rs b/src/use_favicon.rs index 18c2b47..7d7aa53 100644 --- a/src/use_favicon.rs +++ b/src/use_favicon.rs @@ -1,5 +1,6 @@ #![cfg_attr(feature = "ssr", allow(unused_variables, unused_imports))] +use crate::core::MaybeRwSignal; use crate::watch; use cfg_if::cfg_if; use default_struct_builder::DefaultBuilder; @@ -59,7 +60,7 @@ use wasm_bindgen::JsCast; /// ## Server-Side Rendering /// /// On the server only the signals work but no favicon will be changed obviously. -pub fn use_favicon(cx: Scope) -> (ReadSignal>, WriteSignal>) { +pub fn use_favicon(cx: Scope) -> (Signal>, WriteSignal>) { use_favicon_with_options(cx, UseFaviconOptions::default()) } @@ -67,18 +68,14 @@ pub fn use_favicon(cx: Scope) -> (ReadSignal>, WriteSignal