From 7db924bfd9be1682061769fa17a7734a6891e548 Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Tue, 19 Dec 2023 15:29:09 +0100 Subject: [PATCH] PersistentState: make storage choice more explicit by introducing the StorageLocation enum, and add a 'with_location' method to specify the location (new defaults to local). This make the 'begin with * to determine session storage' hack unnecessary. This also adds methods to set the location for the Loader struct. Signed-off-by: Dominik Csapak --- src/state/loader.rs | 28 +++++++++++++++----- src/state/mod.rs | 50 +++++++++++++++++------------------ src/state/persistent_state.rs | 36 ++++++++++++++++++++++--- 3 files changed, 78 insertions(+), 36 deletions(-) diff --git a/src/state/loader.rs b/src/state/loader.rs index 847f75d..eb5fb78 100644 --- a/src/state/loader.rs +++ b/src/state/loader.rs @@ -9,17 +9,18 @@ use yew::prelude::*; use crate::prelude::*; use crate::props::{IntoLoadCallback, LoadCallback}; -use crate::state::{ - SharedState, SharedStateObserver, SharedStateReadGuard, SharedStateWriteGuard, -}; +use crate::state::{SharedState, SharedStateObserver, SharedStateReadGuard, SharedStateWriteGuard}; use crate::widget::{error_message, Button, Fa}; +use super::StorageLocation; + /// Shared HTTP load state /// /// This struct stores the state (loading) and the result of the load. pub struct LoaderState { loading: u64, state_id: Option, + storage_location: StorageLocation, pub loader: Option>, pub data: Option, Error>>, } @@ -31,7 +32,7 @@ impl LoaderState { None => return, }; - if let Some(data) = super::load_state(state_id) { + if let Some(data) = super::load_state(state_id, self.storage_location) { self.data = Some(Ok(Rc::new(data))); } } @@ -44,10 +45,10 @@ impl LoaderState { match &self.data { Some(Ok(data)) => { - super::store_state(state_id, data); + super::store_state(state_id, data, self.storage_location); } _ => { - super::delete_state(state_id); + super::delete_state(state_id, self.storage_location); } } } @@ -58,7 +59,7 @@ impl LoaderState { /// - clnonable, shared state with change notifications. /// - stores load result as `Option, Error>>`. /// - tracks load state `self.loading()`. -/// - ability to cache result in local storage by setting `state_id`. +/// - ability to cache result in local (default) or session storage by setting `state_id`. /// - helper to simplify renderering `self.render`. /// #[derive(Derivative)] @@ -73,6 +74,7 @@ impl Loader { state_id: None, data: None, loader: None, + storage_location: StorageLocation::Session, }; Self(SharedState::new(state)) } @@ -90,6 +92,18 @@ impl Loader { me.load_from_cache(); } + /// Method to set the [StorageLocation] + pub fn set_storage_location(&mut self, storage_location: StorageLocation) { + let mut me = self.write(); + me.storage_location = storage_location; + } + + /// Builder style method to set the [StorageLocation] + pub fn storage(mut self, storage_location: StorageLocation) -> Self { + self.set_storage_location(storage_location); + self + } + pub fn on_change(mut self, cb: impl IntoEventCallback>) -> Self { let me = self.clone(); match cb.into_event_callback() { diff --git a/src/state/mod.rs b/src/state/mod.rs index 239bc75..ead2d67 100644 --- a/src/state/mod.rs +++ b/src/state/mod.rs @@ -42,6 +42,17 @@ pub use language::{ get_available_languages, set_available_languages, Language, LanguageInfo, LanguageObserver, }; +/// Where the state should be saved +#[derive(Clone, Copy)] +pub enum StorageLocation { + /// saved in the browser local storage + /// https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage + Local, + /// saved in the browser session storage + /// https://developer.mozilla.org/en-US/docs/Web/API/Window/sessionStorage + Session, +} + /// Helper function to get the window session [Storage](web_sys::Storage) pub fn session_storage() -> Option { let window = match web_sys::window() { @@ -92,35 +103,24 @@ pub fn local_storage() -> Option { Some(store) } -fn resolve_state_id(state_id: &str) -> Option<(&str, web_sys::Storage)> { - let (use_session_storage, state_id) = if let Some(state_id) = state_id.strip_prefix("*") { - (true, state_id) - } else { - (false, state_id) - }; - - let store = if use_session_storage { - match session_storage() { - Some(store) => store, - None => return None, - } - } else { - match local_storage() { - Some(store) => store, - None => return None, - } - }; - Some((state_id, store)) +fn resolve_storage(storage: StorageLocation) -> Option { + match storage { + StorageLocation::Local => local_storage(), + StorageLocation::Session => session_storage(), + } } -pub fn delete_state(state_id: &str) { - if let Some((state_id, store)) = resolve_state_id(state_id) { +pub fn delete_state(state_id: &str, storage: StorageLocation) { + if let Some(store) = resolve_storage(storage) { let _ = store.delete(state_id); } } -pub fn load_state(state_id: &str) -> Option { - if let Some((state_id, store)) = resolve_state_id(state_id) { +pub fn load_state( + state_id: &str, + storage: StorageLocation, +) -> Option { + if let Some(store) = resolve_storage(storage) { if let Ok(Some(item_str)) = store.get_item(state_id) { if let Ok(data) = serde_json::from_str(&item_str) { return Some(data); @@ -130,8 +130,8 @@ pub fn load_state(state_id: &str) -> Option { None } -pub fn store_state(state_id: &str, data: &T) { - if let Some((state_id, store)) = resolve_state_id(state_id) { +pub fn store_state(state_id: &str, data: &T, storage: StorageLocation) { + if let Some(store) = resolve_storage(storage) { let item_str = serde_json::to_string(data).unwrap(); match store.set_item(state_id, &item_str) { Err(err) => log::error!( diff --git a/src/state/persistent_state.rs b/src/state/persistent_state.rs index df7641d..6d0d9f7 100644 --- a/src/state/persistent_state.rs +++ b/src/state/persistent_state.rs @@ -1,6 +1,8 @@ use serde::{de::DeserializeOwned, Serialize}; use std::ops::Deref; +use super::StorageLocation; + /// Helper to store data persitently using window local [Storage](web_sys::Storage) /// /// Usage: @@ -8,6 +10,7 @@ use std::ops::Deref; /// ``` /// # fn test() { /// use pwt::state::PersistentState; +/// use pwt::state::StorageLocation; /// /// let mut state = PersistentState::::new("my-storage-key-name"); /// @@ -16,7 +19,22 @@ use std::ops::Deref; /// state.update(true); // update the value /// # } /// ``` +/// +/// in session storage instead of local storage: +/// ``` +/// # fn test() { +/// use pwt::state::PersistentState; +/// use pwt::state::StorageLocation; +/// +/// let mut state = PersistentState::::with_location("my-storage-key-name", StorageLocation::Session); +/// +/// let cunnent_value: bool = *state; // acess value with Deref +/// +/// state.update(true); // update the value +/// # } +/// ``` pub struct PersistentState { + storage: StorageLocation, state_id: String, data: T, } @@ -30,7 +48,16 @@ impl Deref for PersistentState { } impl PersistentState { - /// Create a new instance, using 'state_id' as storage key. + /// Create a new instance, using 'state_id' as storage key in the + /// local storage. + /// + /// See [Self::with_location] for details. + pub fn new(state_id: &str) -> Self { + Self::with_location(state_id, StorageLocation::Local) + } + + /// Create a new instance, using 'state_id' as storage key from the given + /// [StorageLocation] /// /// This automatically loads data from the storage. /// @@ -38,23 +65,24 @@ impl PersistentState { /// /// Any errors are logged and ignored. Returns the default value /// in case of errors. - pub fn new(state_id: &str) -> Self { + pub fn with_location(state_id: &str, storage: StorageLocation) -> Self { let mut me = Self { state_id: state_id.into(), data: T::default(), + storage, }; me.load(); me } fn load(&mut self) { - if let Some(data) = super::load_state(&self.state_id) { + if let Some(data) = super::load_state(&self.state_id, self.storage) { self.data = data; } } fn store(&self) { - super::store_state(&self.state_id, &self.data) + super::store_state(&self.state_id, &self.data, self.storage) } /// Update data and write the new value back to the storage. -- 2.39.5