Skip to content

Commit 839a796

Browse files
authored
Function Router Example (#2494)
* Move example from #2453. * Add implementation note. * Update Implementation Note.
1 parent e2b91ca commit 839a796

26 files changed

Lines changed: 1842 additions & 0 deletions

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ members = [
1515
"examples/dyn_create_destroy_apps",
1616
"examples/file_upload",
1717
"examples/function_memory_game",
18+
"examples/function_router",
1819
"examples/function_todomvc",
1920
"examples/futures",
2021
"examples/game_of_life",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
[package]
2+
name = "function_router"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "MIT OR Apache-2.0"
6+
7+
[dependencies]
8+
lipsum = "0.8"
9+
log = "0.4"
10+
rand = { version = "0.8", features = ["small_rng"] }
11+
yew = { path = "../../packages/yew" }
12+
yew-router = { path = "../../packages/yew-router" }
13+
serde = { version = "1.0", features = ["derive"] }
14+
lazy_static = "1.4.0"
15+
gloo-timers = "0.2"
16+
17+
[target.'cfg(target_arch = "wasm32")'.dependencies]
18+
getrandom = { version = "0.2", features = ["js"] }
19+
instant = { version = "0.1", features = ["wasm-bindgen"] }
20+
wasm-logger = "0.2"
21+
22+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
23+
instant = { version = "0.1" }

examples/function_router/README.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# Function Router Example
2+
3+
This is identical to the router example, but written in function
4+
components.
5+
6+
[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Ffunction_router)](https://examples.yew.rs/function_router)
7+
8+
A blog all about yew.
9+
The best way to figure out what this example is about is to just open it up.
10+
It's mobile friendly too!
11+
12+
## Running
13+
14+
While not strictly necessary, this example should be built in release mode:
15+
16+
```bash
17+
trunk serve --release
18+
```
19+
20+
Content generation can take up quite a bit of time in debug builds.
21+
22+
## Concepts
23+
24+
This example involves many different parts, here are just the Yew specific things:
25+
26+
- Uses [`yew-router`] to render and switch between multiple pages.
27+
28+
The example automatically adapts to the `--public-url` value passed to Trunk.
29+
This allows it to be hosted on any path, not just at the root.
30+
For example, our demo is hosted at [/router](https://examples.yew.rs/router).
31+
32+
This is achieved by adding `<base data-trunk-public-url />` to the [index.html](index.html) file.
33+
Trunk rewrites this tag to contain the value passed to `--public-url` which can then be retrieved at runtime.
34+
Take a look at [`Route`](src/main.rs) for the implementation.
35+
36+
## Improvements
37+
38+
- Use a special image component which shows a progress bar until the image is loaded.
39+
- Scroll back to the top after switching route
40+
- Run content generation in a dedicated web worker
41+
- Use longer Markov chains to achieve more coherent results
42+
- Make images deterministic (the same seed should produce the same images)
43+
- Show posts by the author on their page
44+
(this is currently impossible because we need to find post seeds which in turn generate the author's seed)
45+
- Show other posts at the end of a post ("continue reading")
46+
- Home (`/`) should include links to the post list and the author introduction
47+
- Detect sub-path from `--public-url` value passed to Trunk. See: thedodd/trunk#51
48+
49+
[`yew-router`]: https://docs.rs/yew-router/latest/yew_router/
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
allergenics
2+
archaeology
3+
austria
4+
berries
5+
birds
6+
color
7+
conservation
8+
cosmology
9+
culture
10+
europe
11+
evergreens
12+
fleshy
13+
france
14+
guides
15+
horticulture
16+
ireland
17+
landscaping
18+
medicine
19+
music
20+
poison
21+
religion
22+
rome
23+
rust
24+
scotland
25+
seeds
26+
spain
27+
taxonomy
28+
toxics
29+
tradition
30+
trees
31+
wasm
32+
wood
33+
woodworking
34+
yew
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
ald
2+
ber
3+
fe
4+
ger
5+
jo
6+
jus
7+
kas
8+
lix
9+
lu
10+
mon
11+
mour
12+
nas
13+
ridge
14+
ry
15+
si
16+
star
17+
tey
18+
tim
19+
tin
20+
yew

examples/function_router/data/yew.txt

Lines changed: 317 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
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" />
6+
7+
<title>Yew • Function Router</title>
8+
<base data-trunk-public-url />
9+
<link
10+
rel="stylesheet"
11+
href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css"
12+
/>
13+
<link data-trunk rel="sass" href="index.scss" />
14+
</head>
15+
16+
<body></body>
17+
</html>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
.hero {
2+
&.has-background {
3+
position: relative;
4+
overflow: hidden;
5+
}
6+
7+
&-background {
8+
position: absolute;
9+
object-fit: cover;
10+
object-position: bottom;
11+
width: 100%;
12+
height: 100%;
13+
14+
&.is-transparent {
15+
opacity: 0.3;
16+
}
17+
}
18+
}
19+
20+
.burger {
21+
background-color: transparent;
22+
border: none;
23+
}
24+
25+
.navbar-brand {
26+
align-items: center;
27+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use yew::prelude::*;
2+
use yew_router::prelude::*;
3+
4+
use crate::components::nav::Nav;
5+
use crate::pages::{
6+
author::Author, author_list::AuthorList, home::Home, page_not_found::PageNotFound, post::Post,
7+
post_list::PostList,
8+
};
9+
10+
#[derive(Routable, PartialEq, Clone, Debug)]
11+
pub enum Route {
12+
#[at("/posts/:id")]
13+
Post { id: u32 },
14+
#[at("/posts")]
15+
Posts,
16+
#[at("/authors/:id")]
17+
Author { id: u32 },
18+
#[at("/authors")]
19+
Authors,
20+
#[at("/")]
21+
Home,
22+
#[not_found]
23+
#[at("/404")]
24+
NotFound,
25+
}
26+
27+
#[function_component]
28+
pub fn App() -> Html {
29+
html! {
30+
<BrowserRouter>
31+
<Nav />
32+
33+
<main>
34+
<Switch<Route> render={Switch::render(switch)} />
35+
</main>
36+
<footer class="footer">
37+
<div class="content has-text-centered">
38+
{ "Powered by " }
39+
<a href="https://yew.rs">{ "Yew" }</a>
40+
{ " using " }
41+
<a href="https://bulma.io">{ "Bulma" }</a>
42+
{ " and images from " }
43+
<a href="https://unsplash.com">{ "Unsplash" }</a>
44+
</div>
45+
</footer>
46+
</BrowserRouter>
47+
}
48+
}
49+
50+
#[cfg(not(target_arch = "wasm32"))]
51+
mod arch_native {
52+
use super::*;
53+
54+
use yew::virtual_dom::AttrValue;
55+
use yew_router::history::{AnyHistory, History, MemoryHistory};
56+
57+
use std::collections::HashMap;
58+
59+
#[derive(Properties, PartialEq, Debug)]
60+
pub struct ServerAppProps {
61+
pub url: AttrValue,
62+
pub queries: HashMap<String, String>,
63+
}
64+
65+
#[function_component]
66+
pub fn ServerApp(props: &ServerAppProps) -> Html {
67+
let history = AnyHistory::from(MemoryHistory::new());
68+
history
69+
.push_with_query(&*props.url, &props.queries)
70+
.unwrap();
71+
72+
html! {
73+
<Router history={history}>
74+
<Nav />
75+
76+
<main>
77+
<Switch<Route> render={Switch::render(switch)} />
78+
</main>
79+
<footer class="footer">
80+
<div class="content has-text-centered">
81+
{ "Powered by " }
82+
<a href="https://yew.rs">{ "Yew" }</a>
83+
{ " using " }
84+
<a href="https://bulma.io">{ "Bulma" }</a>
85+
{ " and images from " }
86+
<a href="https://unsplash.com">{ "Unsplash" }</a>
87+
</div>
88+
</footer>
89+
</Router>
90+
}
91+
}
92+
}
93+
94+
#[cfg(not(target_arch = "wasm32"))]
95+
pub use arch_native::*;
96+
97+
fn switch(routes: &Route) -> Html {
98+
match routes.clone() {
99+
Route::Post { id } => {
100+
html! { <Post seed={id} /> }
101+
}
102+
Route::Posts => {
103+
html! { <PostList /> }
104+
}
105+
Route::Author { id } => {
106+
html! { <Author seed={id} /> }
107+
}
108+
Route::Authors => {
109+
html! { <AuthorList /> }
110+
}
111+
Route::Home => {
112+
html! { <Home /> }
113+
}
114+
Route::NotFound => {
115+
html! { <PageNotFound /> }
116+
}
117+
}
118+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
use std::rc::Rc;
2+
3+
use crate::{content::Author, generator::Generated, Route};
4+
use yew::prelude::*;
5+
use yew_router::prelude::*;
6+
7+
#[derive(Clone, Debug, PartialEq, Properties)]
8+
pub struct Props {
9+
pub seed: u32,
10+
}
11+
12+
#[derive(PartialEq, Debug)]
13+
pub struct AuthorState {
14+
pub inner: Author,
15+
}
16+
17+
impl Reducible for AuthorState {
18+
type Action = u32;
19+
20+
fn reduce(self: Rc<Self>, action: u32) -> Rc<Self> {
21+
Self {
22+
inner: Author::generate_from_seed(action),
23+
}
24+
.into()
25+
}
26+
}
27+
28+
#[function_component]
29+
pub fn AuthorCard(props: &Props) -> Html {
30+
let seed = props.seed;
31+
32+
let author = use_reducer_eq(|| AuthorState {
33+
inner: Author::generate_from_seed(seed),
34+
});
35+
36+
{
37+
let author_dispatcher = author.dispatcher();
38+
use_effect_with_deps(
39+
move |seed| {
40+
author_dispatcher.dispatch(*seed);
41+
42+
|| {}
43+
},
44+
seed,
45+
);
46+
}
47+
48+
let author = &author.inner;
49+
50+
html! {
51+
<div class="card">
52+
<div class="card-content">
53+
<div class="media">
54+
<div class="media-left">
55+
<figure class="image is-128x128">
56+
<img alt="Author's profile picture" src={author.image_url.clone()} />
57+
</figure>
58+
</div>
59+
<div class="media-content">
60+
<p class="title is-3">{ &author.name }</p>
61+
<p>
62+
{ "I like " }
63+
<b>{ author.keywords.join(", ") }</b>
64+
</p>
65+
</div>
66+
</div>
67+
</div>
68+
<footer class="card-footer">
69+
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.seed }}>
70+
{ "Profile" }
71+
</Link<Route>>
72+
</footer>
73+
</div>
74+
}
75+
}

0 commit comments

Comments
 (0)