use crate::error::{Result, StorageError};
use crate::local_storage::LocalStorage;
use crate::session_storage::SessionStorage;
use dioxus::hooks::*;
use dioxus_signals::*;
use serde::{de::DeserializeOwned, Serialize};
#[cfg(feature = "indexeddb")]
use dioxus_indexeddb::{Database, DatabaseConfig};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StorageBackend {
Local,
Session,
#[cfg(feature = "indexeddb")]
IndexedDb,
}
#[derive(Debug, Clone)]
pub struct StorageConfig {
pub backend: StorageBackend,
pub key_prefix: Option<String>,
}
impl StorageConfig {
pub fn local() -> Self {
Self {
backend: StorageBackend::Local,
key_prefix: None,
}
}
pub fn session() -> Self {
Self {
backend: StorageBackend::Session,
key_prefix: None,
}
}
#[cfg(feature = "indexeddb")]
pub fn indexed_db() -> Self {
Self {
backend: StorageBackend::IndexedDb,
key_prefix: None,
}
}
pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
self.key_prefix = Some(prefix.into());
self
}
}
#[derive(Debug, Clone)]
pub struct Storage {
config: StorageConfig,
}
impl Storage {
pub fn new(config: StorageConfig) -> Self {
Self { config }
}
fn full_key(&self, key: &str) -> String {
match &self.config.key_prefix {
Some(prefix) => format!("{}:{}", prefix, key),
None => key.to_string(),
}
}
pub fn get<T: DeserializeOwned>(&self, key: &str) -> Result<Option<T>> {
let key = self.full_key(key);
match self.config.backend {
StorageBackend::Local => LocalStorage::get(&key),
StorageBackend::Session => SessionStorage::get(&key),
#[cfg(feature = "indexeddb")]
StorageBackend::IndexedDb => {
panic!("Use get_async for IndexedDB backend")
}
}
}
pub fn set<T: Serialize>(&self, key: &str, value: &T) -> Result<()> {
let key = self.full_key(key);
match self.config.backend {
StorageBackend::Local => LocalStorage::set(&key, value),
StorageBackend::Session => SessionStorage::set(&key, value),
#[cfg(feature = "indexeddb")]
StorageBackend::IndexedDb => {
panic!("Use set_async for IndexedDB backend")
}
}
}
pub fn remove(&self, key: &str) -> Result<()> {
let key = self.full_key(key);
match self.config.backend {
StorageBackend::Local => LocalStorage::remove(&key),
StorageBackend::Session => SessionStorage::remove(&key),
#[cfg(feature = "indexeddb")]
StorageBackend::IndexedDb => {
panic!("Use remove_async for IndexedDB backend")
}
}
}
pub fn is_available(&self) -> bool {
match self.config.backend {
StorageBackend::Local => LocalStorage::is_available(),
StorageBackend::Session => SessionStorage::is_available(),
#[cfg(feature = "indexeddb")]
StorageBackend::IndexedDb => Database::is_available(),
}
}
}
pub fn use_storage(config: StorageConfig) -> Signal<Storage> {
use_signal(|| Storage::new(config))
}
pub fn use_storage_value<T: Serialize + DeserializeOwned + Clone>(
storage: &Storage,
key: impl Into<String>,
default: T,
) -> Signal<T> {
let key = key.into();
let storage = storage.clone();
let initial = storage.get::<T>(&key).ok().flatten().unwrap_or(default);
let signal = use_signal(|| initial);
{
let key = key.clone();
let storage = storage.clone();
use_effect(move || {
let value = signal.read().clone();
if let Err(e) = storage.set(&key, &value) {
log::warn!("Failed to save to storage: {}", e);
}
});
}
signal
}
#[cfg(feature = "indexeddb")]
pub fn _use_storage_db(_config: DatabaseConfig) -> Signal<Option<Database>> {
use_signal(|| None::<Database>)
}