Skip to main content

rustolio_web/hooks/
storage.rs

1//
2// SPDX-License-Identifier: MPL-2.0
3//
4// Copyright (c) 2026 Tobias Binnewies. All rights reserved.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at http://mozilla.org/MPL/2.0/.
9//
10
11use std::{rc::Rc, str::FromStr};
12
13use rustolio_utils::{
14    bytes::{
15        Bytes,
16        encoding::{decode_from_bytes, encode_to_bytes},
17    },
18    prelude::{Decode, Encode},
19};
20
21use crate::prelude::*;
22
23use super::SignalBase;
24
25#[derive(Debug, PartialEq, Eq)]
26pub struct StorageSignal<T> {
27    key: Signal<Rc<str>>,
28    signal: Signal<T>,
29}
30
31// Impl `Clone` and `Copy` manually to avoid requiring `T: Clone + Copy`
32impl<T> Clone for StorageSignal<T> {
33    fn clone(&self) -> Self {
34        *self
35    }
36}
37impl<T> Copy for StorageSignal<T> {}
38
39impl<T> SignalBase<T> for StorageSignal<T> {
40    fn base(&self) -> Signal<T> {
41        self.signal.base()
42    }
43}
44impl<T> SignalGetter<T> for StorageSignal<T> where T: Clone + 'static {}
45impl<T> SignalSetter<T> for StorageSignal<T> where T: PartialEq + 'static {}
46impl<T> SignalUpdater<T> for StorageSignal<T> where T: 'static {}
47
48impl<T> StorageSignal<T> {
49    /// Removed the stored key-value pair from local storage.
50    /// It will be rewritten on the next signal update.
51    ///
52    /// The current signal value stays the same.
53    pub fn remove(&self) -> rustolio_utils::Result<()> {
54        local_storage()?.delete(&self.key.peek())?;
55        Ok(())
56    }
57}
58
59impl<T> StorageSignal<T>
60where
61    T: ToString + FromStr + Clone + 'static,
62{
63    pub fn new(key: impl ToString, fallback: T) -> rustolio_utils::Result<Self> {
64        let key: Signal<Rc<str>> = Signal::new(key.to_string().into());
65
66        let stored_value = local_storage()?
67            .get(&key.peek())?
68            .and_then(|v| T::from_str(&v).ok())
69            .unwrap_or(fallback);
70
71        let signal = Signal::new(stored_value);
72        Effect::new_result(move || {
73            let key = key.peek();
74            let value = signal.value().to_string();
75            local_storage()?.set(&key, &value)?;
76            Ok(())
77        });
78
79        Ok(Self { signal, key })
80    }
81}
82
83impl<T> StorageSignal<T>
84where
85    T: Decode + Encode + Clone + 'static,
86{
87    pub fn new_encoded(key: impl ToString, fallback: T) -> rustolio_utils::Result<Self> {
88        let key: Signal<Rc<str>> = Signal::new(key.to_string().into());
89
90        let stored_value = local_storage()?
91            .get(&key.peek())?
92            .and_then(|v| decode_from_bytes(Bytes::from_owner(v.into_bytes())).ok())
93            .unwrap_or(fallback);
94
95        let signal = Signal::always_mutable(stored_value);
96        Effect::new_result(move || {
97            let key = key.peek();
98            let value = signal.value();
99            let value = encode_to_bytes(&value)?.into();
100            let value = unsafe { String::from_utf8_unchecked(value) };
101            local_storage()?.set(&key, &value)?;
102            Ok(())
103        });
104
105        Ok(Self { signal, key })
106    }
107}