Skip to content

Commit d58dd29

Browse files
committed
refactor(examples): split rx_state into multiple examples
This makes each example show one particular thing, which is much better than before.
1 parent 2941f77 commit d58dd29

32 files changed

+338
-90
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.perseus/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "perseus-example-freezing-and-thawing"
3+
version = "0.3.2"
4+
edition = "2018"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] }
10+
sycamore = "0.7"
11+
serde = { version = "1", features = ["derive"] }
12+
serde_json = "1"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Freezing and Thawing Example
2+
3+
This example shows how state freezing and state thawing work with Perseus' reactive state platform.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use perseus::{ErrorPages, Html};
2+
use sycamore::view;
3+
4+
pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
5+
let mut error_pages = ErrorPages::new(|url, status, err, _| {
6+
view! {
7+
p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) }
8+
}
9+
});
10+
error_pages.add_page(404, |_, _, _, _| {
11+
view! {
12+
p { "Page not found." }
13+
}
14+
});
15+
16+
error_pages
17+
}

examples/rx_state/src/global_state.rs renamed to examples/core/freezing_and_thawing/src/global_state.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,6 @@ pub struct AppState {
1212
#[perseus::autoserde(global_build_state)]
1313
pub async fn get_build_state() -> RenderFnResult<AppState> {
1414
Ok(AppState {
15-
test: "Hello from the global state build process!".to_string(),
15+
test: "Hello World!".to_string(),
1616
})
1717
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
mod error_pages;
2+
mod global_state;
3+
mod templates;
4+
5+
use perseus::define_app;
6+
define_app! {
7+
templates: [
8+
crate::templates::index::get_template::<G>(),
9+
crate::templates::about::get_template::<G>()
10+
],
11+
error_pages: crate::error_pages::get_error_pages(),
12+
global_state_creator: crate::global_state::get_global_state_creator()
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use perseus::state::Freeze;
2+
use perseus::{Html, Template};
3+
use sycamore::prelude::*;
4+
5+
use crate::global_state::AppStateRx;
6+
7+
#[perseus::template_rx(AboutPage)]
8+
pub fn about_page(_: (), global_state: AppStateRx) -> View<G> {
9+
let test = global_state.test;
10+
// This is not part of our data model, we do NOT want the frozen app synchronized as part of our page's state, it should be separate
11+
let frozen_app = Signal::new(String::new());
12+
let render_ctx = perseus::get_render_ctx!();
13+
14+
view! {
15+
p { (test.get()) }
16+
17+
// When the user visits this and then comes back, they'll still be able to see their username (the previous state will be retrieved from the global state automatically)
18+
a(href = "") { "Index" }
19+
br()
20+
21+
// We'll let the user freeze from here to demonstrate that the frozen state also navigates back to the last route
22+
button(on:click = cloned!(frozen_app, render_ctx => move |_| {
23+
frozen_app.set(render_ctx.freeze());
24+
})) { "Freeze!" }
25+
p { (frozen_app.get()) }
26+
}
27+
}
28+
29+
pub fn get_template<G: Html>() -> Template<G> {
30+
Template::new("about").template(about_page)
31+
}

examples/rx_state/src/index.rs renamed to examples/core/freezing_and_thawing/src/templates/index.rs

+11-9
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,30 @@ use sycamore::prelude::*;
44

55
use crate::global_state::AppStateRx;
66

7-
// We define a normal `struct` and then use `make_rx` (which derives `Serialize`, `Deserialize`, and `Clone` automatically)
8-
// This will generate a new `struct` called `IndexPropsRx` (as we asked it to), in which every field is made reactive with a `Signal`
97
#[perseus::make_rx(IndexPropsRx)]
108
pub struct IndexProps {
11-
pub username: String,
9+
username: String,
1210
}
1311

14-
// This special macro (normally we'd use `template(IndexProps)`) converts the state we generate elsewhere to a reactive version
15-
// We need to tell it the name of the unreactive properties we created to start with (unfortunately the compiler isn't smart enough to figure that out yet)
16-
// This will also add our reactive properties to the global state store, and, if they're already there, it'll use the existing one
1712
#[perseus::template_rx(IndexPage)]
18-
pub fn index_page(IndexPropsRx { username }: IndexPropsRx, global_state: AppStateRx) -> View<G> {
13+
pub fn index_page(state: IndexPropsRx, global_state: AppStateRx) -> View<G> {
14+
let username = state.username;
1915
let username_2 = username.clone(); // This is necessary until Sycamore's new reactive primitives are released
20-
let frozen_app = Signal::new(String::new()); // This is not part of our data model, so it's not part of the state properties (everything else should be though)
16+
let test = global_state.test;
17+
let test_2 = test.clone();
18+
19+
// This is not part of our data model, we do NOT want the frozen app synchronized as part of our page's state, it should be separate
20+
let frozen_app = Signal::new(String::new());
2121
let frozen_app_2 = frozen_app.clone();
2222
let frozen_app_3 = frozen_app.clone();
2323
let render_ctx = perseus::get_render_ctx!();
2424

2525
view! {
26+
// For demonstration, we'll let the user modify the page's state and the global state arbitrarily
2627
p { (format!("Greetings, {}!", username.get())) }
2728
input(bind:value = username_2, placeholder = "Username")
28-
p { (global_state.test.get()) }
29+
p { (test.get()) }
30+
input(bind:value = test_2, placeholder = "Global state")
2931

3032
// When the user visits this and then comes back, they'll still be able to see their username (the previous state will be retrieved from the global state automatically)
3133
a(href = "about") { "About" }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod about;
2+
pub mod index;

examples/core/idb_freezing/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.perseus/

examples/core/idb_freezing/Cargo.toml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "perseus-example-idb-freezing"
3+
version = "0.3.2"
4+
edition = "2018"
5+
6+
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
7+
8+
[dependencies]
9+
perseus = { path = "../../../packages/perseus", features = [ "hydrate", "idb-freezing" ] }
10+
sycamore = "0.7"
11+
serde = { version = "1", features = ["derive"] }
12+
serde_json = "1"
13+
wasm-bindgen-futures = "0.4"

examples/core/idb_freezing/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# IndexedDB Freezing Example
2+
3+
This example shows how Perseus can supprot freezing state to IndexedDB and retrieving it from there later, which is the mechanism of state freezing that many apps will use. This is also the basis of Perseus' HSR system.
4+
5+
Notably, this requires the `wasm-bindgen-futures` package and the `idb-freezing` feature enabled on the `perseus` package.

examples/core/idb_freezing/index.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
</head>
7+
<body>
8+
<div id="root"></div>
9+
</body>
10+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use perseus::{ErrorPages, Html};
2+
use sycamore::view;
3+
4+
pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
5+
let mut error_pages = ErrorPages::new(|url, status, err, _| {
6+
view! {
7+
p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) }
8+
}
9+
});
10+
error_pages.add_page(404, |_, _, _, _| {
11+
view! {
12+
p { "Page not found." }
13+
}
14+
});
15+
16+
error_pages
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use perseus::{state::GlobalStateCreator, RenderFnResult};
2+
3+
pub fn get_global_state_creator() -> GlobalStateCreator {
4+
GlobalStateCreator::new().build_state_fn(get_build_state)
5+
}
6+
7+
#[perseus::make_rx(AppStateRx)]
8+
pub struct AppState {
9+
pub test: String,
10+
}
11+
12+
#[perseus::autoserde(global_build_state)]
13+
pub async fn get_build_state() -> RenderFnResult<AppState> {
14+
Ok(AppState {
15+
test: "Hello World!".to_string(),
16+
})
17+
}

examples/core/idb_freezing/src/lib.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
mod error_pages;
2+
mod global_state;
3+
mod templates;
4+
5+
use perseus::define_app;
6+
define_app! {
7+
templates: [
8+
crate::templates::index::get_template::<G>(),
9+
crate::templates::about::get_template::<G>()
10+
],
11+
error_pages: crate::error_pages::get_error_pages(),
12+
global_state_creator: crate::global_state::get_global_state_creator()
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use perseus::state::{Freeze, IdbFrozenStateStore};
2+
use perseus::{Html, Template};
3+
use sycamore::prelude::*;
4+
5+
use crate::global_state::AppStateRx;
6+
7+
#[perseus::template_rx(AboutPage)]
8+
pub fn about_page(_: (), global_state: AppStateRx) -> View<G> {
9+
let test = global_state.test;
10+
// This is not part of our data model
11+
let freeze_status = Signal::new(String::new());
12+
let render_ctx = perseus::get_render_ctx!();
13+
14+
view! {
15+
p { (test.get()) }
16+
17+
// When the user visits this and then comes back, they'll still be able to see their username (the previous state will be retrieved from the global state automatically)
18+
a(href = "") { "Index" }
19+
br()
20+
21+
// We'll let the user freeze from here to demonstrate that the frozen state also navigates back to the last route
22+
button(on:click = cloned!(freeze_status, render_ctx => move |_|
23+
// The IndexedDB API is asynchronous, so we'll spawn a future
24+
wasm_bindgen_futures::spawn_local(cloned!(render_ctx, freeze_status => async move {
25+
// We do this here (rather than when we get the render context) so that it's updated whenever we press the button
26+
let frozen_state = render_ctx.freeze();
27+
let idb_store = match IdbFrozenStateStore::new().await {
28+
Ok(idb_store) => idb_store,
29+
Err(_) => {
30+
freeze_status.set("Error.".to_string());
31+
return;
32+
}
33+
};
34+
match idb_store.set(&frozen_state).await {
35+
Ok(_) => freeze_status.set("Saved.".to_string()),
36+
Err(_) => freeze_status.set("Error.".to_string())
37+
};
38+
}))
39+
)) { "Freeze to IndexedDB" }
40+
p { (freeze_status.get()) }
41+
}
42+
}
43+
44+
pub fn get_template<G: Html>() -> Template<G> {
45+
Template::new("about").template(about_page)
46+
}

examples/rx_state/src/idb.rs renamed to examples/core/idb_freezing/src/templates/index.rs

+25-12
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,35 @@ use perseus::state::{Freeze, IdbFrozenStateStore, PageThawPrefs, ThawPrefs};
22
use perseus::{Html, RenderFnResultWithCause, Template};
33
use sycamore::prelude::*;
44

5-
#[perseus::make_rx(TestPropsRx)]
6-
pub struct TestProps {
7-
pub username: String,
5+
use crate::global_state::AppStateRx;
6+
7+
#[perseus::make_rx(IndexPropsRx)]
8+
pub struct IndexProps {
9+
username: String,
810
}
911

10-
#[perseus::template_rx(IdbPage)]
11-
pub fn idb_page(TestPropsRx { username }: TestPropsRx) -> View<G> {
12+
#[perseus::template_rx(IndexPage)]
13+
pub fn index_page(state: IndexPropsRx, global_state: AppStateRx) -> View<G> {
14+
let username = state.username;
1215
let username_2 = username.clone(); // This is necessary until Sycamore's new reactive primitives are released
13-
let render_ctx = perseus::get_render_ctx!(); // We get the render context out here, it's not accessible in the future
14-
let freeze_status = Signal::new(String::new()); // This isn't part of the template's data model, it's just here for demonstration
16+
let test = global_state.test;
17+
let test_2 = test.clone();
18+
19+
// This is not part of our data model
20+
let freeze_status = Signal::new(String::new());
1521
let thaw_status = Signal::new(String::new());
22+
let render_ctx = perseus::get_render_ctx!();
1623

1724
view! {
25+
// For demonstration, we'll let the user modify the page's state and the global state arbitrarily
1826
p { (format!("Greetings, {}!", username.get())) }
1927
input(bind:value = username_2, placeholder = "Username")
28+
p { (test.get()) }
29+
input(bind:value = test_2, placeholder = "Global state")
2030

2131
// When the user visits this and then comes back, they'll still be able to see their username (the previous state will be retrieved from the global state automatically)
2232
a(href = "about") { "About" }
23-
a(href = "") { "Index" }
33+
br()
2434

2535
button(on:click = cloned!(freeze_status, render_ctx => move |_|
2636
// The IndexedDB API is asynchronous, so we'll spawn a future
@@ -76,14 +86,17 @@ pub fn idb_page(TestPropsRx { username }: TestPropsRx) -> View<G> {
7686
}
7787

7888
pub fn get_template<G: Html>() -> Template<G> {
79-
Template::new("idb")
89+
Template::new("index")
8090
.build_state_fn(get_build_state)
81-
.template(idb_page)
91+
.template(index_page)
8292
}
8393

8494
#[perseus::autoserde(build_state)]
85-
pub async fn get_build_state(_path: String, _locale: String) -> RenderFnResultWithCause<TestProps> {
86-
Ok(TestProps {
95+
pub async fn get_build_state(
96+
_path: String,
97+
_locale: String,
98+
) -> RenderFnResultWithCause<IndexProps> {
99+
Ok(IndexProps {
87100
username: "".to_string(),
88101
})
89102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod about;
2+
pub mod index;

examples/core/rx_state/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.perseus/

examples/rx_state/Cargo.toml renamed to examples/core/rx_state/Cargo.toml

+1-4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@ edition = "2018"
66
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
77

88
[dependencies]
9-
perseus = { path = "../../packages/perseus", features = [ "hydrate", "idb-freezing" ] }
9+
perseus = { path = "../../../packages/perseus", features = [ "hydrate" ] }
1010
sycamore = "0.7"
1111
serde = { version = "1", features = ["derive"] }
1212
serde_json = "1"
13-
ureq = "2"
14-
reqwasm = "0.4"
15-
wasm-bindgen-futures = "0.4"

examples/core/rx_state/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# Reactive State Example
2+
3+
This example shows some of the power of Perseus' reactive state platform by extending the usual basic Perseus app to show how reactive state can persist between pages through the page state store.

examples/core/rx_state/index.html

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
6+
</head>
7+
<body>
8+
<div id="root"></div>
9+
</body>
10+
</html>
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
use perseus::{ErrorPages, Html};
2+
use sycamore::view;
3+
4+
pub fn get_error_pages<G: Html>() -> ErrorPages<G> {
5+
let mut error_pages = ErrorPages::new(|url, status, err, _| {
6+
view! {
7+
p { (format!("An error with HTTP code {} occurred at '{}': '{}'.", status, url, err)) }
8+
}
9+
});
10+
error_pages.add_page(404, |_, _, _, _| {
11+
view! {
12+
p { "Page not found." }
13+
}
14+
});
15+
16+
error_pages
17+
}

examples/core/rx_state/src/lib.rs

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
mod error_pages;
2+
mod templates;
3+
4+
use perseus::define_app;
5+
define_app! {
6+
templates: [
7+
crate::templates::index::get_template::<G>(),
8+
crate::templates::about::get_template::<G>()
9+
],
10+
error_pages: crate::error_pages::get_error_pages()
11+
}

0 commit comments

Comments
 (0)