]> git.proxmox.com Git - ui/proxmox-yew-widget-toolkit.git/commitdiff
PersistentState: make storage choice more explicit
authorDominik Csapak <d.csapak@proxmox.com>
Tue, 19 Dec 2023 14:29:09 +0000 (15:29 +0100)
committerDominik Csapak <d.csapak@proxmox.com>
Tue, 19 Dec 2023 15:25:02 +0000 (16:25 +0100)
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 <d.csapak@proxmox.com>
src/state/loader.rs
src/state/mod.rs
src/state/persistent_state.rs

index 847f75d6411592af34bb18be2ee6b60a5621baee..eb5fb78982f593519a37f2007c7f3827f31e0ae4 100644 (file)
@@ -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<T> {
     loading: u64,
     state_id: Option<AttrValue>,
+    storage_location: StorageLocation,
     pub loader: Option<LoadCallback<T>>,
     pub data: Option<Result<Rc<T>, Error>>,
 }
@@ -31,7 +32,7 @@ impl<T: 'static + DeserializeOwned + Serialize> LoaderState<T> {
             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<T: 'static + DeserializeOwned + Serialize> LoaderState<T> {
 
         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<T: 'static + DeserializeOwned + Serialize> LoaderState<T> {
 /// - clnonable, shared state with change notifications.
 /// - stores load result as `Option<Result<Rc<T>, 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<T: 'static + DeserializeOwned + Serialize> Loader<T> {
             state_id: None,
             data: None,
             loader: None,
+            storage_location: StorageLocation::Session,
         };
         Self(SharedState::new(state))
     }
@@ -90,6 +92,18 @@ impl<T: 'static + DeserializeOwned + Serialize> Loader<T> {
         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<Loader<T>>) -> Self {
         let me = self.clone();
         match cb.into_event_callback() {
index 239bc75c21c03843267f2fb31a4dd8f757d19264..ead2d6756728aa73d30d3e5bc8295b9fdd21a0ef 100644 (file)
@@ -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<web_sys::Storage> {
     let window = match web_sys::window() {
@@ -92,35 +103,24 @@ pub fn local_storage() -> Option<web_sys::Storage> {
     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<web_sys::Storage> {
+    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<T: 'static + DeserializeOwned>(state_id: &str) -> Option<T> {
-    if let Some((state_id, store)) = resolve_state_id(state_id) {
+pub fn load_state<T: 'static + DeserializeOwned>(
+    state_id: &str,
+    storage: StorageLocation,
+) -> Option<T> {
+    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<T: 'static + DeserializeOwned>(state_id: &str) -> Option<T> {
     None
 }
 
-pub fn store_state<T: 'static + Serialize>(state_id: &str, data: &T) {
-    if let Some((state_id, store)) = resolve_state_id(state_id) {
+pub fn store_state<T: 'static + Serialize>(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!(
index df7641dc1cf435f3d0be17eb49d616103099393a..6d0d9f723c9b7d75d455b36c0d6b88fa10a11ae7 100644 (file)
@@ -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::<bool>::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::<bool>::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<T> {
+    storage: StorageLocation,
     state_id: String,
     data: T,
 }
@@ -30,7 +48,16 @@ impl<T> Deref for PersistentState<T> {
 }
 
 impl<T: 'static + Default + Serialize + DeserializeOwned> PersistentState<T> {
-    /// 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<T: 'static + Default + Serialize + DeserializeOwned> PersistentState<T> {
     ///
     /// 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.