Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add storage hook #17

Merged
merged 117 commits into from
Apr 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
567ae82
add storage hook
ealmloff Sep 23, 2023
0c3f4bd
save work
esimkowitz Oct 4, 2023
5ae1da2
swap back to async_broadcast
esimkowitz Oct 4, 2023
e963c80
remove comments
esimkowitz Oct 4, 2023
0e69cd5
save work
esimkowitz Oct 5, 2023
0616011
save work
esimkowitz Oct 6, 2023
856c860
it works!
esimkowitz Oct 7, 2023
f0e7fdc
remove unnecessary dependency
esimkowitz Oct 7, 2023
48e7b4d
remove unused imports
esimkowitz Oct 7, 2023
0157bd1
add localstorage
esimkowitz Oct 9, 2023
d957fb3
add unsubscribe
esimkowitz Oct 9, 2023
1778184
Merge branch 'storage-v4' into storage-v5
esimkowitz Oct 9, 2023
0d015b4
save work
esimkowitz Oct 9, 2023
f6d7e29
change name
esimkowitz Oct 9, 2023
68dcbfb
Break subscriber out
esimkowitz Oct 10, 2023
321ff7b
break out subscribe
esimkowitz Oct 10, 2023
06d1576
update example
esimkowitz Oct 10, 2023
577a41a
small refactoring
esimkowitz Oct 10, 2023
59df763
update sample
esimkowitz Oct 10, 2023
3bb8937
change UsePersistent to UseStorageEntry, use everywhere
esimkowitz Oct 10, 2023
d966cef
more refactoring
esimkowitz Oct 10, 2023
84e4c2f
Remove custom UseStorageEntryRef
esimkowitz Oct 12, 2023
2e33853
remove StorageEntryMut in favor of with_mut
esimkowitz Oct 12, 2023
8ba7c46
add clone and copy derives
esimkowitz Oct 12, 2023
4a84603
save work
esimkowitz Oct 13, 2023
84c47f2
potential signal-centric alternative
esimkowitz Oct 13, 2023
e72df4e
save
esimkowitz Oct 13, 2023
e0a929d
it works!
esimkowitz Oct 13, 2023
856a860
fix persistence
esimkowitz Oct 13, 2023
1d7884f
use_effect instead of use_subscribe
esimkowitz Oct 13, 2023
eaa787c
fix use_effect
esimkowitz Oct 14, 2023
2f83149
Merge pull request #1 from esimkowitz/storage-v4.1
esimkowitz Oct 14, 2023
1e4b0e6
working on ssr
esimkowitz Oct 14, 2023
d6ae6e9
refactoring for devex
esimkowitz Oct 15, 2023
f4299c9
add comments
esimkowitz Oct 15, 2023
cb703af
add more comments
esimkowitz Oct 15, 2023
32eeac7
format
esimkowitz Oct 15, 2023
d40b47c
remove unnecessary constraints
esimkowitz Oct 15, 2023
640d45a
change name
esimkowitz Oct 15, 2023
5579de2
fix ssr
esimkowitz Oct 16, 2023
7658e0a
expose getters for channel and key in storageentry
esimkowitz Oct 16, 2023
b4f0c56
format
esimkowitz Oct 16, 2023
65d3c4a
update to use watch channels
esimkowitz Oct 24, 2023
483ec12
fix cargo
esimkowitz Oct 24, 2023
f7b5fb0
simplify synced save
esimkowitz Oct 24, 2023
07d2c93
add more comments, fix unsubscribe
esimkowitz Oct 24, 2023
4b4c0f2
split storage desktop and web examples
esimkowitz Oct 24, 2023
5a95fc0
Merge pull request #2 from esimkowitz/storage-v4.2
esimkowitz Oct 24, 2023
9917de4
remove semicolon
esimkowitz Oct 24, 2023
97d84e6
typo
esimkowitz Oct 24, 2023
25300cb
remove dashmap due to wasm compatibility issues
esimkowitz Oct 24, 2023
0470435
add comments
esimkowitz Oct 24, 2023
8b74f37
adding filesystem and memory stores
esimkowitz Oct 26, 2023
5de9137
more comments
esimkowitz Oct 26, 2023
270e5d5
Updating storage desktop example to add new window
esimkowitz Oct 26, 2023
ab764a8
Remove notify crate
esimkowitz Oct 26, 2023
e5aaf73
fix typo
esimkowitz Oct 26, 2023
dcff702
Merge pull request #3 from esimkowitz/storage-v4.3
esimkowitz Oct 26, 2023
1c7e1f6
add more comments
esimkowitz Oct 26, 2023
fcd4abc
make log info into trace
esimkowitz Oct 26, 2023
474433c
fix memory map
esimkowitz Oct 28, 2023
016c925
Update in-memory to use Any instead of serialize
esimkowitz Oct 28, 2023
ca9b45a
Merge pull request #1 from esimkowitz/storage-v4
ealmloff Oct 28, 2023
a35bdc1
add clone bound to the web storage subscribe method
ealmloff Oct 28, 2023
48f5737
fix clippy
ealmloff Oct 28, 2023
69c6c0a
add non-hook versions of storage
ealmloff Nov 1, 2023
cb1eecf
add new_ prefix to the non-hook storage functions
ealmloff Nov 2, 2023
2b2e9a9
fix storage example import
ealmloff Nov 6, 2023
c29a1c3
add use_storage example
ealmloff Nov 6, 2023
f0ae975
add example to new_storage
ealmloff Nov 6, 2023
1fb405c
fix some lints
ealmloff Nov 6, 2023
7a99a3e
remove storage from the default features
ealmloff Nov 6, 2023
22c03cb
update some out of date readmes
ealmloff Nov 8, 2023
178aa78
export new_ variants of hooks
ealmloff Nov 8, 2023
2e2c00d
log serialization errors
ealmloff Nov 8, 2023
72d2346
fix formatting
ealmloff Nov 8, 2023
9d79bb9
add storage to the tested features
ealmloff Nov 8, 2023
0edb165
fix dioxus storage tests
ealmloff Nov 9, 2023
63fe35e
only enable desktop features for clippy
ealmloff Nov 9, 2023
9e4e53b
fix formatting
ealmloff Nov 9, 2023
cf79ae7
feat: Dioxus 0.5 support
marc2332 Jan 23, 2024
137b414
updated dioxus
marc2332 Jan 25, 2024
cde6452
fix use_clipboard
marc2332 Jan 26, 2024
191edf0
fix use_clipboard again
marc2332 Jan 26, 2024
73e2d0c
better error handling of use_clipboard
marc2332 Jan 26, 2024
d084129
fmt
marc2332 Jan 26, 2024
7b4dbe3
fix
marc2332 Jan 26, 2024
6791b1d
fixing it blindly
marc2332 Jan 26, 2024
b79c38d
chore: Bump dx
marc2332 Feb 6, 2024
a5ebe58
Remove unwrap
marc2332 Feb 6, 2024
791a21f
chore: Fixes
marc2332 Feb 6, 2024
074c27a
derive copy and clean up
marc2332 Feb 9, 2024
ce5ea55
fix
marc2332 Feb 9, 2024
4424071
fix
marc2332 Feb 9, 2024
1b35f9d
empty
marc2332 Feb 9, 2024
42adeb4
fix use_geolocation
marc2332 Feb 9, 2024
f330cdb
fix
marc2332 Feb 9, 2024
2e111cd
bump dx
marc2332 Feb 18, 2024
e8d3b67
bump dx
marc2332 Feb 24, 2024
ddcb7a1
Merge branch 'master' into feat/dioxus-0.5
marc2332 Feb 24, 2024
569e9b2
fixes
marc2332 Feb 24, 2024
8279239
bump dx
marc2332 Mar 6, 2024
0fe58a6
bump dx
marc2332 Mar 9, 2024
4d8ae33
0.5
marc2332 Mar 15, 2024
33f6fce
bump dx, fmt, clippy
marc2332 Mar 19, 2024
fd64567
nuke use_rw and fix use_geolocation
marc2332 Mar 19, 2024
bc29d7c
clippy
marc2332 Mar 19, 2024
a07270f
clippy
marc2332 Mar 19, 2024
e1ad3ab
Merge branch 'master' into storage
ealmloff Mar 27, 2024
0fca567
move storage into the workspace
ealmloff Mar 27, 2024
eca4ad0
Merge branch 'pr/marc2332/23' into storage
ealmloff Mar 27, 2024
e3ad85b
revert channel changes
ealmloff Mar 28, 2024
a8e4f99
fix dioxus storage
ealmloff Mar 28, 2024
a94f61b
Merge branch 'master' into storage
ealmloff Mar 28, 2024
3a27bdf
fix formatting
ealmloff Mar 28, 2024
25e6b2a
address comments
ealmloff Mar 29, 2024
fa7871c
remove unnessicary lock
ealmloff Apr 1, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[workspace]
resolver = "2"
members = [
"std",
"examples/*",
]
[workspace.dependencies]
dioxus-std = { path = "./std" }
dioxus = { version = "0.5" }
dioxus-web = { version = "0.5" }
dioxus-desktop = { version = "0.5" }
[workspace]
resolver = "2"
members = [
"std",
"examples/*",
]

[workspace.dependencies]
dioxus-std = { path = "./std" }
dioxus = { version = "0.5" }
dioxus-web = { version = "0.5" }
dioxus-desktop = { version = "0.5" }
5 changes: 4 additions & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,8 @@ Learn how to use the `i18n` abstraction.
### [`channel`](./channel/)
Learn how to use the `channel` abstraction.

### [`storage`](./storage/)
Learn how to use the `storage` abstraction.

### [`clipboard`](./clipboard/)
Learn how to use the `clipboard` abstraction.
Learn how to use the `clipboard` abstraction.
12 changes: 12 additions & 0 deletions examples/storage/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "storage-desktop"
version = "0.1.0"
edition = "2021"

[dependencies]
dioxus-std = { workspace = true, features = ["storage"] }
dioxus = { workspace = true, features = ["router"] }

[features]
web = ["dioxus/web"]
desktop = ["dioxus/desktop"]
8 changes: 8 additions & 0 deletions examples/storage/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# use_persistent (Desktop)


Use persistent allows you to create data that will persist across sessions. This example will teach you how to use the `use_persistant` hook.

Run:

```dioxus serve --platform desktop```
102 changes: 102 additions & 0 deletions examples/storage/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
use dioxus::prelude::*;
use dioxus_router::prelude::*;
use dioxus_std::storage::*;

fn main() {
dioxus_std::storage::set_dir!();
launch(app);
}

fn app() -> Element {
rsx! {
Router::<Route> {}
}
}

#[derive(Routable, Clone)]
#[rustfmt::skip]
enum Route {
#[layout(Footer)]
#[route("/")]
Page1 {},
#[route("/page2")]
Page2 {},
}

#[component]
fn Footer() -> Element {
let new_window = {
#[cfg(feature = "desktop")]
{
let window = dioxus::desktop::use_window();
rsx! {
div {
button {
onclick: move |_| {
let dom = VirtualDom::new(app);
window.new_window(dom, Default::default());
},
"New Window"
}
}
}
}
#[cfg(not(feature = "desktop"))]
{
rsx! {
div {}
}
}
};

rsx! {
div {
Outlet::<Route> { }

p {
"----"
}

{new_window}

nav {
ul {
li { Link { to: Route::Page1 {}, "Page1" } }
li { Link { to: Route::Page2 {}, "Page2" } }
}
}
}
}
}

#[component]
fn Page1() -> Element {
rsx!("Home")
}

#[component]
fn Page2() -> Element {
let mut count_session = use_singleton_persistent(|| 0);
let mut count_local = use_synced_storage::<LocalStorage, i32>("synced".to_string(), || 0);

rsx!(
div {
button {
onclick: move |_| {
*count_session.write() += 1;
},
"Click me!"
},
"I persist for the current session. Clicked {count_session} times"
}
div {
button {
onclick: move |_| {
*count_local.write() += 1;
},
"Click me!"
},
"I persist across all sessions. Clicked {count_local} times"
}
)
}
34 changes: 33 additions & 1 deletion std/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,24 @@ i18n = [
# Non Shared
"dep:unic-langid",
]
storage = [
# Shared
"dep:rustc-hash",
"dep:postcard",
"dep:once_cell",
"dep:dioxus-signals",
"dep:tokio",
"dep:yazi",
"web-sys/StorageEvent",
"dep:serde",
"dep:futures-util",

# WASM
"dep:wasm-bindgen",

# Not WASM
"dep:directories",
]

# CI testing
wasm-testing = ["geolocation", "color_scheme", "utils", "i18n"]
Expand All @@ -75,7 +93,7 @@ notify-rust = { version = "4.8.0", optional = true }
uuid = { version = "1.3.2", optional = true }
async-broadcast = { version = "0.5.1", optional = true }

# Used by: geolocation
# Used by: geolocation, storage
futures = { version = "0.3.28", features = ["std"], optional = true }
futures-util = { version = "0.3.28", optional = true }

Expand All @@ -84,6 +102,16 @@ serde = { version = "1.0.163", optional = true }
serde_json = { version = "1.0.96", optional = true }
unic-langid = { version = "0.9.1", features = ["serde"], optional = true }

# Used by: storage
rustc-hash = { version = "1.1.0", optional = true }
postcard = { version = "1.0.2", features = ["use-std"], optional = true }
once_cell = { version = "1.17.0", optional = true }
dioxus-signals = { version = "0.5.0-alpha.2", features = [
"serialize",
], optional = true }
tokio = { version = "1.33.0", features = ["sync"], optional = true }
yazi = { version = "0.1.4", optional = true }
tracing = "0.1.40"

# # # # # # # # #
# Windows Deps. #
Expand Down Expand Up @@ -113,6 +141,10 @@ js-sys = "0.3.62"
uuid = { version = "1.3.2", features = ["js"] }


[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
# Used by: storage
directories = { version = "4.0.1", optional = true }

# # # # #
# Docs. #
# # # # #
Expand Down
18 changes: 12 additions & 6 deletions std/src/geolocation/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
mod core;
mod platform;
mod use_geolocation;

pub use self::core::*;
pub use self::use_geolocation::*;
cfg_if::cfg_if! {
if #[cfg(any(windows, target_family = "wasm"))] {
pub mod core;
pub mod platform;
pub mod use_geolocation;
pub use self::core::*;
pub use self::use_geolocation::*;
}
else {
compile_error!("The geolocation module is not supported on this platform.");
}
}
6 changes: 6 additions & 0 deletions std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,9 @@ cfg_if::cfg_if! {
pub mod clipboard;
}
}

cfg_if::cfg_if! {
if #[cfg(feature = "storage")] {
pub mod storage;
}
}
128 changes: 128 additions & 0 deletions std/src/storage/client_storage/fs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use crate::storage::{StorageChannelPayload, StorageSubscription};
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::collections::HashMap;
use std::io::Write;
use std::sync::{OnceLock, RwLock};
use tokio::sync::watch::{channel, Receiver};

use crate::storage::{serde_to_string, try_serde_from_string, StorageBacking, StorageSubscriber};

#[doc(hidden)]
/// Sets the directory where the storage files are located.
pub fn set_directory(path: std::path::PathBuf) {
LOCATION.set(path).unwrap();
}

#[doc(hidden)]
pub fn set_dir_name(name: &str) {
set_directory(
directories::BaseDirs::new()
.unwrap()
.data_local_dir()
.join(name),
)
}

/// The location where the storage files are located.
static LOCATION: OnceLock<std::path::PathBuf> = OnceLock::new();

/// Set a value in the configured storage location using the key as the file name.
fn set<T: Serialize>(key: String, value: &T) {
let as_str = serde_to_string(value);
let path = LOCATION
.get()
.expect("Call the set_dir macro before accessing persistant data");
std::fs::create_dir_all(path).unwrap();
let file_path = path.join(key);
let mut file = std::fs::File::create(file_path).unwrap();
file.write_all(as_str.as_bytes()).unwrap();
}

/// Get a value from the configured storage location using the key as the file name.
fn get<T: DeserializeOwned>(key: &str) -> Option<T> {
let path = LOCATION
.get()
.expect("Call the set_dir macro before accessing persistant data")
.join(key);
let s = std::fs::read_to_string(path).ok()?;
try_serde_from_string(&s)
}

#[derive(Clone)]
pub struct LocalStorage;

impl StorageBacking for LocalStorage {
type Key = String;

fn set<T: Serialize + Send + Sync + Clone + 'static>(key: String, value: &T) {
let key_clone = key.clone();
let value_clone = (*value).clone();
set(key, value);

// If the subscriptions map is not initialized, we don't need to notify any subscribers.
if let Some(subscriptions) = SUBSCRIPTIONS.get() {
let read_binding = subscriptions.read().unwrap();
if let Some(subscription) = read_binding.get(&key_clone) {
subscription
.tx
.send(StorageChannelPayload::new(value_clone))
.unwrap();
}
}
}

fn get<T: DeserializeOwned>(key: &String) -> Option<T> {
get(key)
}
}

// Note that this module contains an optimization that differs from the web version. Dioxus Desktop runs all windows in
// the same thread, meaning that we can just directly notify the subscribers via the same channels, rather than using the
// storage event listener.
impl StorageSubscriber<LocalStorage> for LocalStorage {
fn subscribe<T: DeserializeOwned + Send + Sync + Clone + 'static>(
key: &<LocalStorage as StorageBacking>::Key,
) -> Receiver<StorageChannelPayload> {
// Initialize the subscriptions map if it hasn't been initialized yet.
let subscriptions = SUBSCRIPTIONS.get_or_init(|| RwLock::new(HashMap::new()));

// Check if the subscription already exists. If it does, return the existing subscription's channel.
// If it doesn't, create a new subscription and return its channel.
let read_binding = subscriptions.read().unwrap();
match read_binding.get(key) {
Some(subscription) => subscription.tx.subscribe(),
None => {
drop(read_binding);
let (tx, rx) = channel::<StorageChannelPayload>(StorageChannelPayload::default());
let subscription = StorageSubscription::new::<LocalStorage, T>(tx, key.clone());

subscriptions
.write()
.unwrap()
.insert(key.clone(), subscription);
rx
}
}
}

fn unsubscribe(key: &<LocalStorage as StorageBacking>::Key) {
tracing::trace!("Unsubscribing from \"{}\"", key);

// Fail silently if unsubscribe is called but the subscriptions map isn't initialized yet.
if let Some(subscriptions) = SUBSCRIPTIONS.get() {
let read_binding = subscriptions.read().unwrap();

// If the subscription exists, remove it from the subscriptions map.
if read_binding.contains_key(key) {
tracing::trace!("Found entry for \"{}\"", key);
drop(read_binding);
subscriptions.write().unwrap().remove(key);
}
}
}
}

/// A map of all the channels that are currently subscribed to and the getters for the corresponding storage entry.
/// This gets initialized lazily.
static SUBSCRIPTIONS: OnceLock<RwLock<HashMap<String, StorageSubscription>>> = OnceLock::new();
Loading
Loading