Skip to content

Commit

Permalink
Merge branch 'master' into platform-crate
Browse files Browse the repository at this point in the history
# Conflicts:
#	packages/yew/Cargo.toml
  • Loading branch information
futursolo committed Oct 19, 2022
2 parents f62a737 + 730f25c commit 95509cc
Show file tree
Hide file tree
Showing 34 changed files with 3,315 additions and 493 deletions.
14 changes: 3 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@ The most important tasks are outlined below.
To run all tests, use the following command:

```bash
cargo make tests
cargo make test-flow
```

### Browser tests

`cargo make tests` will automatically download Geckodriver to a temporary location if it isn't in the PATH.
`cargo make test` will automatically download Geckodriver to a temporary location if it isn't in the PATH.

Because Geckodriver looks for `firefox` in the path, if you use
FireFox Developer Edition, you may get an error, because Developer Editions
Expand All @@ -44,18 +44,10 @@ To fix this, either install the standard version of Firefox or symlink

The tests for the fetch service require a local [httpbin](https://httpbin.org/) server.
If you have [Docker](https://www.docker.com/) installed,
`cargo make tests` will automatically run httpbin in a container for you.
`cargo make test` will automatically run httpbin in a container for you.

Alternatively, you can set the `HTTPBIN_URL` environment variable to the URL you wish to run tests against.

### WebSocket service tests

The tests for the web-socket service require an echo server.
If you have [Docker](https://www.docker.com/) installed,
`cargo make tests` will automatically run an [echo server](https://hub.docker.com/r/jmalloc/echo-server) in a container for you.

Alternatively, you can set the `ECHO_SERVER_URL` environment variable to the URL you wish to run tests against.

### Macro tests

When adding or updating tests, please make sure to update the appropriate `stderr` file, which you can find [here](https://github.com/yewstack/yew/tree/master/packages/yew-macro/tests/macro) for the `html!` macro.
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div align="center">
<img src="https://yew.rs/img/logo.png" width="150" />
<a href="https://yew.rs/" target="_blank"><img src="https://yew.rs/img/logo.png" width="150" /></a>

<h1>Yew</h1>

Expand All @@ -20,7 +20,7 @@
<span> | </span>
<a href="https://yew.rs/docs/next/">Documentation (latest)</a>
<span> | </span>
<a href="https://github.com/yewstack/yew/tree/v0.18/examples">Examples</a>
<a href="https://github.com/yewstack/yew/tree/master/examples">Examples</a>
<span> | </span>
<a href="https://github.com/yewstack/yew/blob/master/CHANGELOG.md">Changelog</a>
<span> | </span>
Expand Down
10 changes: 10 additions & 0 deletions examples/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ As an example, check out the TodoMVC example here: <https://examples.yew.rs/todo

| Example | CT | Description |
| -------------------------------------------------- | -- | ---------------------------------------------------------------------------------------------------------------------------------- |
| [async_clock](async_clock) | S | Demonstrates the use of asynchronous tasks in a yew component. |
| [boids](boids) | S | Yew port of [Boids](https://en.wikipedia.org/wiki/Boids) |
| [contexts](contexts) | F | A technical demonstration of Context API.
| [communication_*](communication_child_to_parent) | S | A set of simple examples to demonstrate various communication patterns. |
Expand Down
12 changes: 12 additions & 0 deletions examples/async_clock/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "async_clock"
version = "0.0.1"
authors = ["Marcel Ibes <[email protected]>"]
edition = "2021"
license = "MIT OR Apache-2.0"

[dependencies]
yew = { path = "../../packages/yew", features = ["csr"] }
chrono = "0.4"
futures = "0.3"
gloo-net = "0.2"
20 changes: 20 additions & 0 deletions examples/async_clock/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Asynchronous coding in Yew

An example of using asynchronous tasks in a component. This example creates a clock in the background and
continuously awaits clock-ticks. When the clock updates the new time is sent to the UI component to display.
In parallel it fetches online jokes to make the clock more entertaining to watch.

Its main purpose is to demonstrate various ways of using async code in a yew component. It uses the following async
features:
- send_future
- send_stream
- spawn_local
- mpsc::unbounded channels

## Running

Run this application:

```bash
trunk serve --open
```
11 changes: 11 additions & 0 deletions examples/async_clock/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • Async Examples</title>
<link data-trunk rel="rust" />
<link data-trunk rel="sass" href="index.scss" />
</head>

<body></body>
</html>
42 changes: 42 additions & 0 deletions examples/async_clock/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
body {
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
font-size: 16pt;
}

.app {
display: flex;
justify-content: center;
flex-direction: row;
}

.time-display {
display: flex;
justify-content: center;
color: darkblue;
font-size: 24pt;
font-weight: bold;
margin-bottom: 15px;
}

.joke-display {
display: flex;
justify-content: center;
color: orangered;
max-width: 75%;
border-color: darkblue;
border-style: dashed;
border-width: 1px;
padding: 10px;
}

.fun-score-display {
font-style: italic;
}


.clock {
display: flex;
flex-direction: column;
row-gap: 15px;
align-items: center;
}
126 changes: 126 additions & 0 deletions examples/async_clock/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
use chrono::{DateTime, Local};
use futures::{FutureExt, StreamExt};
use services::compute_fun_score;
use yew::platform::pinned::mpsc::UnboundedSender;
use yew::{html, AttrValue, Component, Context, Html};

use crate::services::{emit_jokes, initialize_atomic_clocks, stream_time};

mod services;

/// The AsyncComponent displays the current time and some silly jokes. Its main purpose is to
/// demonstrate the use of async code in a yew component. It uses the following async features:
/// - send_future
/// - send_stream
/// - spawn_local
/// - mpsc::unbounded channels
pub struct AsyncComponent {
clock: Option<AttrValue>,
joke: Option<AttrValue>,
fun_score: Option<i16>,
fun_score_channel: UnboundedSender<AttrValue>,
}

pub enum Msg {
ClockInitialized(()),
ClockTicked(DateTime<Local>),
Joke(AttrValue),
FunScore(i16),
}

impl Component for AsyncComponent {
type Message = Msg;
type Properties = ();

fn create(ctx: &Context<Self>) -> Self {
// Demonstrate how we can send a message to the component when a future completes.
// This is the most straightforward way to use async code in a yew component.
let is_initialized = initialize_atomic_clocks();
ctx.link()
.send_future(is_initialized.map(Msg::ClockInitialized));

// The compute_fun_score launches a background task that is ready to compute the fun score
// from jokes that are delivered on this channel. The outcome of the computation is
// sent back to the component via the Msg::FunScore callback.
let fun_score_cb = ctx.link().callback(Msg::FunScore);
let fun_score_channel = compute_fun_score(fun_score_cb);

Self {
clock: None,
joke: None,
fun_score: None,
fun_score_channel,
}
}

fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ClockTicked(current_time) => {
// Update the clock display
self.clock = Some(AttrValue::from(current_time.to_rfc2822()));
}
Msg::ClockInitialized(_) => {
// Now that the clock is initialized, we can start the time stream.
self.clock = Some(AttrValue::from("Initialized"));

// The stream_time method returns a stream of time updates. We use send_stream to
// update the component with a Msg::ClockTicked message every time
// the stream produces a new value.
let time_steam = stream_time();
ctx.link().send_stream(time_steam.map(Msg::ClockTicked));

// In parallel we launch a background task that produces jokes to make the clock
// more fun to watch. The jokes are emitted back to the component
// throught the Msg::Joke callback.
let joke_cb = ctx.link().callback(Msg::Joke);
emit_jokes(joke_cb);
}
Msg::Joke(joke) => {
// Update the joke
self.joke = Some(joke.clone());

// Reset the fun score
self.fun_score = None;

// Send the joke to the background task that computes the fun score.
self.fun_score_channel
.send_now(joke)
.expect("failed to send joke");
}
Msg::FunScore(score) => {
self.fun_score = Some(score);
}
}
true
}

fn view(&self, _ctx: &Context<Self>) -> Html {
let display = self.clock.as_deref().unwrap_or("Loading...");
let joke = self.joke.as_deref().unwrap_or("Loading...");
let fun_score = self
.fun_score
.map(|score| format!("Fun score: {}", score))
.unwrap_or_else(|| "Computing...".to_string());

html! {
<div class="app">
<div class="clock">
<h2>{ "Asynchronous Examples" }</h2>
<div class="time-display">
{ display }
</div>
<div class="joke-display">
{ joke }
</div>
<div class="fun-score-display">
{ fun_score }
</div>
</div>
</div>
}
}
}

fn main() {
yew::Renderer::<AsyncComponent>::new().render();
}
60 changes: 60 additions & 0 deletions examples/async_clock/src/services.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
use std::time::Duration;

use chrono::{DateTime, Local};
use futures::{Stream, StreamExt};
use gloo_net::http::Request;
use yew::platform::pinned::mpsc::UnboundedSender;
use yew::platform::spawn_local;
use yew::platform::time::{interval, sleep};
use yew::{AttrValue, Callback};

const ONE_SEC: Duration = Duration::from_secs(1);
const TEN_SECS: Duration = Duration::from_secs(10);

/// Demonstration code to show how to use async code in a yew component.
pub async fn initialize_atomic_clocks() {
// aligning with atomic clocks :-)
sleep(ONE_SEC).await;
}

/// Returns a stream of time updates.
pub fn stream_time() -> impl Stream<Item = DateTime<Local>> {
interval(ONE_SEC).map(|_| Local::now())
}

/// Emit entertaining jokes every 10 seconds.
pub fn emit_jokes(joke_cb: Callback<AttrValue>) {
// Spawn a background task that will fetch a joke and send it to the component.
spawn_local(async move {
loop {
// Fetch the online joke
let fun_fact = Request::get("https://v2.jokeapi.dev/joke/Programming?format=txt")
.send()
.await
.unwrap()
.text()
.await
.unwrap();

// Emit it to the component
joke_cb.emit(AttrValue::from(fun_fact));
sleep(TEN_SECS).await;
}
});
}

/// Background task that computes the fun score from jokes that are delivered on the channel.
pub fn compute_fun_score(fun_score_cb: Callback<i16>) -> UnboundedSender<AttrValue> {
let (tx, mut rx) = yew::platform::pinned::mpsc::unbounded::<AttrValue>();

// Read endlessly from the UnboundedReceiver and compute the fun score.
spawn_local(async move {
while let Some(joke) = rx.next().await {
sleep(ONE_SEC).await;
let score = joke.len() as i16;
fun_score_cb.emit(score);
}
});

tx
}
9 changes: 8 additions & 1 deletion examples/todomvc/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,14 @@ impl Component for App {
self.state.filter = filter;
}
Msg::ToggleEdit(idx) => {
self.state.edit_value = self.state.entries[idx].description.clone();
let entry = self
.state
.entries
.iter()
.filter(|e| self.state.filter.fits(e))
.nth(idx)
.unwrap();
self.state.edit_value = entry.description.clone();
self.state.clear_all_edit();
self.state.toggle_edit(idx);
}
Expand Down
Loading

0 comments on commit 95509cc

Please sign in to comment.