Refactor and cleanup codebase (#1842)

* remove yew-dsl

* remove yew-components

* remove yew-services

* remove yew::format

* fix CI

* move yew::agent to yew-agent crate

* move yew-functional to yew

plus a couple of misc fixes

* move futures and neqassign out of yewtil

* move yewtil::store to yew_agent, remove yewtil

* formatting

* fix tests

* update docs

* use `rustwasm` gloo repo instead of mine

* add docs

* use rustwasm/gloo repo

* remove unused file

* fix Makefile.toml

* Fix issues after rebase

* Apply suggestions from code review (part 1)

Co-authored-by: mc1098 <m.cripps1@uni.brighton.ac.uk>

* Apply suggestions from code review (part 2)

* move `#[function_component(_)]` tests

missed those before

* Apply suggestions from code review

Co-authored-by: mc1098 <m.cripps1@uni.brighton.ac.uk>

Co-authored-by: mc1098 <m.cripps1@uni.brighton.ac.uk>
This commit is contained in:
Muhammad Hamza 2021-07-18 18:57:09 +05:00 committed by GitHub
parent 91ab14bfd3
commit 2412a68bee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
208 changed files with 551 additions and 7912 deletions

View File

@ -41,12 +41,6 @@ jobs:
command: clippy
args: --all-targets -- -D warnings
- name: Run clippy - yew with all features
if: always()
run: |
cd packages/yew
cargo clippy --all-targets --features "cbor msgpack toml yaml" -- -D warnings
check_examples:
name: Check Examples
runs-on: ubuntu-latest
@ -110,20 +104,11 @@ jobs:
- name: Run doctest - yew with features
run: |
cd packages/yew
cargo test --doc --features "doc_test wasm_test yaml msgpack cbor toml"
cargo test --doc --features "doc_test wasm_test"
integration_tests:
name: Integration Tests on ${{ matrix.toolchain }}
runs-on: ubuntu-latest
services:
httpbin:
image: kennethreitz/httpbin@sha256:599fe5e5073102dbb0ee3dbb65f049dab44fa9fc251f6835c9990f8fb196a72b
ports:
- 8080:80
echo_server:
image: jmalloc/echo-server@sha256:c461e7e54d947a8777413aaf9c624b4ad1f1bac5d8272475da859ae82c1abd7d
ports:
- 8081:8080
strategy:
matrix:
@ -163,20 +148,6 @@ jobs:
cd packages/yew-router
wasm-pack test --chrome --firefox --headless
- name: Run tests - yew-functional
run: |
cd packages/yew-functional
wasm-pack test --chrome --firefox --headless
- name: Run tests - yew-services
env:
HTTPBIN_URL: "http://localhost:8080"
ECHO_SERVER_URL: "ws://localhost:8081"
run: |
cd packages/yew-services
wasm-pack test --chrome --firefox --headless -- --features "wasm_test httpbin_test echo_server_test"
unit_tests:
name: Unit Tests on ${{ matrix.toolchain }}
runs-on: ubuntu-latest

View File

@ -1,31 +1,18 @@
[workspace]
members = [
"packages/yew",
"packages/yew-components",
"packages/yew-macro",
"packages/yew-services",
"packages/yew-validation",
"packages/yew-agent",
# Router
"packages/yew-router",
"packages/yew-router-macro",
# Function components
"packages/yew-functional",
"packages/yew-functional-macro",
# Utilities
"packages/yewtil",
"packages/yewtil-macro",
# dsl
"packages/yew-dsl",
# Examples
"examples/boids",
"examples/counter",
"examples/crm",
"examples/dashboard",
"examples/dyn_create_destroy_apps",
"examples/file_upload",
"examples/futures",

View File

@ -31,9 +31,8 @@ As an example, check out the TodoMVC example here: <https://examples.yew.rs/todo
| [boids](boids) | Yew port of [Boids](https://en.wikipedia.org/wiki/Boids) |
| [counter](counter) | Simple counter which can be incremented and decremented |
| [crm](crm) | Shallow customer relationship management tool |
| [dashboard](dashboard) | Uses the `fetch` and `websocket` services to load external data |
| [dyn_create_destroy_apps](dyn_create_destroy_apps) | Uses the function `start_app_in_element` and the `AppHandle` struct to dynamically create and delete Yew apps |
| [file_upload](file_upload) | Uses the `reader` service to read the content of user uploaded files |
| [file_upload](file_upload) | Uses the `gloo::file` to read the content of user uploaded files |
| [futures](futures) | Demonstrates how you can use futures and async code with Yew. Features a Markdown renderer. |
| [game_of_life](game_of_life) | Implementation of [Conway's Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) |
| [inner_html](inner_html) | Embeds an external document as raw HTML by manually managing the element |

View File

@ -12,4 +12,4 @@ getrandom = { version = "0.2", features = ["js"] }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
gloo = { git = "https://github.com/rustwasm/gloo/" }

View File

@ -17,17 +17,15 @@ trunk serve --release
## Concepts
The example uses [`IntervalService`] to drive the game loop.
The example uses [`gloo::timers`](https://gloo-rs.web.app/docs/timer) implementation of `setInterval` to drive the Yew game loop.
## Improvements
- Add the possibility to switch the behaviour from flocking to scattering by inverting the cohesion rule so that boids avoid each other.
This should also invert the color adaption to restore some variety.
- Add keyboard shortcuts (using the `KeyboardService`) for the actions.
- Add keyboard shortcuts for the actions.
- Make it possible to hide the settings panel entirely
- Bigger boids should accelerate slower than smaller ones
- Share settings by encoding them into the URL
- Resize the boids when "Spacing" is changed.
The setting should then also be renamed to something like "Size".
[`intervalservice`]: https://docs.rs/yew-services/latest/yew_services/struct.IntervalService.html

View File

@ -1,6 +1,5 @@
use gloo::storage::{LocalStorage, Storage};
use serde::{Deserialize, Serialize};
use yew::format::Json;
use yew_services::storage::{Area, StorageService};
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct Settings {
@ -31,27 +30,15 @@ impl Settings {
const KEY: &'static str = "yew.boids.settings";
pub fn load() -> Self {
StorageService::new(Area::Local)
.ok()
.and_then(|storage| {
storage
.restore::<Json<anyhow::Result<Settings>>>(Self::KEY)
.0
.ok()
})
.unwrap_or_default()
LocalStorage::get(Self::KEY).unwrap_or_default()
}
pub fn remove() {
if let Ok(mut storage) = StorageService::new(Area::Local) {
storage.remove(Self::KEY);
}
LocalStorage::delete(Self::KEY);
}
pub fn store(&self) {
if let Ok(mut storage) = StorageService::new(Area::Local) {
storage.store(Self::KEY, Json(self))
}
let _ = LocalStorage::set(Self::KEY, self);
}
}
impl Default for Settings {

View File

@ -1,9 +1,8 @@
use crate::boid::Boid;
use crate::math::Vector2D;
use crate::settings::Settings;
use std::time::Duration;
use gloo::timers::callback::Interval;
use yew::{html, Component, ComponentLink, Html, Properties, ShouldRender};
use yew_services::interval::{IntervalService, IntervalTask};
pub const SIZE: Vector2D = Vector2D::new(1600.0, 1000.0);
@ -26,7 +25,7 @@ pub struct Simulation {
props: Props,
link: ComponentLink<Self>,
boids: Vec<Boid>,
interval_task: IntervalTask,
interval: Interval,
}
impl Component for Simulation {
type Message = Msg;
@ -38,16 +37,18 @@ impl Component for Simulation {
.map(|_| Boid::new_random(settings))
.collect();
let interval_task = IntervalService::spawn(
Duration::from_millis(settings.tick_interval_ms),
link.callback(|_| Msg::Tick),
);
let interval = {
let link = link.clone();
Interval::new(settings.tick_interval_ms as u32, move || {
link.send_message(Msg::Tick)
})
};
Self {
props,
link,
boids,
interval_task,
interval,
}
}
@ -86,10 +87,12 @@ impl Component for Simulation {
if settings.tick_interval_ms != self.props.settings.tick_interval_ms {
// as soon as the previous task is dropped it is cancelled.
// We don't need to worry about manually stopping it.
self.interval_task = IntervalService::spawn(
Duration::from_millis(settings.tick_interval_ms),
self.link.callback(|_| Msg::Tick),
);
self.interval = {
let link = self.link.clone();
Interval::new(settings.tick_interval_ms as u32, move || {
link.send_message(Msg::Tick)
})
}
}
self.props = props;

View File

@ -8,4 +8,5 @@ license = "MIT OR Apache-2.0"
[dependencies]
js-sys = "0.3"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
weblog = "0.3"
wasm-bindgen = "0.2"

View File

@ -1,6 +1,6 @@
use js_sys::Date;
use weblog::console_log;
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yew_services::ConsoleService;
// Define the possible messages which can be sent to the component
pub enum Msg {
@ -25,12 +25,12 @@ impl Component for Model {
match msg {
Msg::Increment => {
self.value += 1;
ConsoleService::log("plus one"); // Will output a string to the browser console
console_log!("plus one"); // Will output a string to the browser console
true // Return true to cause the displayed change to update
}
Msg::Decrement => {
self.value -= 1;
ConsoleService::log("minus one");
console_log!("minus one");
true
}
}

View File

@ -9,4 +9,5 @@ license = "MIT OR Apache-2.0"
serde = "1"
serde_derive = "1"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
gloo = { git = "https://github.com/rustwasm/gloo/" }

View File

@ -14,7 +14,7 @@ For a much more sophisticated approach check out [`yew-router`](https://yew.rs/c
One major flaw with the implementation used by this example is that the scenes aren't tied to the URL.
Reloading the page always brings the user back to the initial scene.
The example also uses the [`StorageService`](https://docs.rs/yew-services/latest/yew_services/struct.StorageService.html)
The example also uses the [`gloo::storage`](https://gloo-rs.web.app/docs/storage)
to persist the clients across sessions.
## Improvements

View File

@ -1,9 +1,7 @@
use add_client::AddClientForm;
use gloo::storage::{LocalStorage, Storage};
use serde::{Deserialize, Serialize};
use yew::format::Json;
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yew_services::storage::Area;
use yew_services::{DialogService, StorageService};
mod add_client;
@ -46,7 +44,6 @@ pub enum Msg {
pub struct Model {
link: ComponentLink<Self>,
storage: StorageService,
clients: Vec<Client>,
scene: Scene,
}
@ -56,12 +53,9 @@ impl Component for Model {
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let storage = StorageService::new(Area::Local).expect("storage was disabled by the user");
let Json(clients) = storage.restore(KEY);
let clients = clients.ok().unwrap_or_else(Vec::new);
let clients = LocalStorage::get(KEY).unwrap_or_else(|_| Vec::new());
Self {
link,
storage,
clients,
scene: Scene::ClientsList,
}
@ -75,14 +69,14 @@ impl Component for Model {
}
Msg::AddClient(client) => {
self.clients.push(client);
self.storage.store(KEY, Json(&self.clients));
LocalStorage::set(KEY, &self.clients).expect("failed to set");
// we only need to re-render if we're currently displaying the clients
matches!(self.scene, Scene::ClientsList)
}
Msg::ClearClients => {
if DialogService::confirm("Do you really want to clear the data?") {
if gloo::dialogs::confirm("Do you really want to clear the data?") {
self.clients.clear();
self.storage.remove(KEY);
LocalStorage::delete(KEY);
true
} else {
false

View File

@ -1,13 +0,0 @@
[package]
name = "dashboard"
version = "0.1.0"
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
[dependencies]
anyhow = "1"
serde = "1"
serde_derive = "1"
yew = { path = "../../packages/yew", features = ["toml"] }
yew-services = { path = "../../packages/yew-services" }

View File

@ -1,31 +0,0 @@
# Dashboard Example
[![Demo](https://img.shields.io/website?label=demo&url=https%3A%2F%2Fexamples.yew.rs%2Fdashboard)](https://examples.yew.rs/dashboard)
This example uses `FetchService` and `WebSocketService` to load external data.
## Special requirements for WebSocket
This is not a requirement for the example as a whole, only for the parts relating to the WebSocket service.
The frontend assumes there's a WebSocket server listening at `ws://localhost:9001/`.
The [`server`](server) directory contains a very basic WebSocket server that can be used for this purpose.
Run the following commands to start it:
```bash
# assuming you're in the same directory as this README file.
cd server
cargo run
```
The server just echoes all the data it receives back to the client and logs the message to the console.
## Improvements
- Handle errors by showing them to the user (at the very least they should be logged)
- Create a `cargo-make` task to run the example along with the WebSocket server
The example is called "dashboard" but it doesn't look or act like one.
It could be changed to be more like a dashboard.

View File

@ -1,12 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Yew • Dashboard</title>
<link data-trunk rel="copy-file" href="static/data.json" />
<link data-trunk rel="copy-file" href="static/data.toml" />
</head>
<body></body>
</html>

View File

@ -1,11 +0,0 @@
[package]
name = "server"
version = "0.1.0"
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
[dependencies]
tungstenite = "0.10.1"
[workspace]

View File

@ -1,19 +0,0 @@
use std::net::TcpListener;
use std::thread::spawn;
use tungstenite::server::accept;
fn main() {
let server = TcpListener::bind("127.0.0.1:9001").unwrap();
for stream in server.incoming() {
spawn(move || {
let mut websocket = accept(stream.unwrap()).unwrap();
loop {
let msg = websocket.read_message().unwrap();
println!("Received: {}", msg);
if msg.is_binary() || msg.is_text() {
websocket.write_message(msg).unwrap();
}
}
});
}
}

View File

@ -1,224 +0,0 @@
use anyhow::Error;
use serde_derive::{Deserialize, Serialize};
use yew::format::{Json, Nothing, Toml};
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yew_services::fetch::{FetchService, FetchTask, Request, Response};
use yew_services::websocket::{WebSocketService, WebSocketStatus, WebSocketTask};
type AsBinary = bool;
pub enum Format {
Json,
Toml,
}
pub enum WsAction {
Connect,
SendData(AsBinary),
Disconnect,
Lost,
}
pub enum Msg {
FetchData(Format, AsBinary),
WsAction(WsAction),
FetchReady(Result<DataFromFile, Error>),
WsReady(Result<WsResponse, Error>),
}
impl From<WsAction> for Msg {
fn from(action: WsAction) -> Self {
Msg::WsAction(action)
}
}
/// This type is used to parse data from `./static/data.json` file and
/// have to correspond the data layout from that file.
#[derive(Deserialize, Debug)]
pub struct DataFromFile {
value: u32,
}
/// This type is used as a request which sent to websocket connection.
#[derive(Serialize, Debug)]
struct WsRequest {
value: u32,
}
/// This type is an expected response from a websocket connection.
#[derive(Deserialize, Debug)]
pub struct WsResponse {
value: u32,
}
pub struct Model {
link: ComponentLink<Model>,
data: Option<u32>,
_ft: Option<FetchTask>,
ws: Option<WebSocketTask>,
}
impl Model {
fn view_data(&self) -> Html {
if let Some(value) = self.data {
html! {
<p>{ value }</p>
}
} else {
html! {
<p>{ "Data hasn't fetched yet." }</p>
}
}
}
fn fetch_json(&mut self, binary: AsBinary) -> yew_services::fetch::FetchTask {
let callback = self.link.batch_callback(
move |response: Response<Json<Result<DataFromFile, Error>>>| {
let (meta, Json(data)) = response.into_parts();
println!("META: {:?}, {:?}", meta, data);
if meta.status.is_success() {
Some(Msg::FetchReady(data))
} else {
None // FIXME: Handle this error accordingly.
}
},
);
let request = Request::get("/data.json").body(Nothing).unwrap();
if binary {
FetchService::fetch_binary(request, callback).unwrap()
} else {
FetchService::fetch(request, callback).unwrap()
}
}
pub fn fetch_toml(&mut self, binary: AsBinary) -> yew_services::fetch::FetchTask {
let callback = self.link.batch_callback(
move |response: Response<Toml<Result<DataFromFile, Error>>>| {
let (meta, Toml(data)) = response.into_parts();
println!("META: {:?}, {:?}", meta, data);
if meta.status.is_success() {
Some(Msg::FetchReady(data))
} else {
None // FIXME: Handle this error accordingly.
}
},
);
let request = Request::get("/data.toml").body(Nothing).unwrap();
if binary {
FetchService::fetch_binary(request, callback).unwrap()
} else {
FetchService::fetch(request, callback).unwrap()
}
}
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
data: None,
_ft: None,
ws: None,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::FetchData(format, binary) => {
let task = match format {
Format::Json => self.fetch_json(binary),
Format::Toml => self.fetch_toml(binary),
};
self._ft = Some(task);
true
}
Msg::WsAction(action) => match action {
WsAction::Connect => {
let callback = self.link.callback(|Json(data)| Msg::WsReady(data));
let notification = self.link.batch_callback(|status| match status {
WebSocketStatus::Opened => None,
WebSocketStatus::Closed | WebSocketStatus::Error => {
Some(WsAction::Lost.into())
}
});
let task =
WebSocketService::connect("ws://localhost:9001/", callback, notification)
.unwrap();
self.ws = Some(task);
true
}
WsAction::SendData(binary) => {
let request = WsRequest { value: 321 };
if binary {
self.ws.as_mut().unwrap().send_binary(Json(&request));
} else {
self.ws.as_mut().unwrap().send(Json(&request));
}
false
}
WsAction::Disconnect => {
self.ws.take();
true
}
WsAction::Lost => {
self.ws = None;
true
}
},
Msg::FetchReady(response) => {
self.data = response.map(|data| data.value).ok();
true
}
Msg::WsReady(response) => {
self.data = response.map(|data| data.value).ok();
true
}
}
}
fn change(&mut self, _: Self::Properties) -> ShouldRender {
false
}
fn view(&self) -> Html {
html! {
<div>
<nav class="menu">
<button onclick=self.link.callback(|_| Msg::FetchData(Format::Json, false))>
{ "Fetch Data" }
</button>
<button onclick=self.link.callback(|_| Msg::FetchData(Format::Json, true))>
{ "Fetch Data [binary]" }
</button>
<button onclick=self.link.callback(|_| Msg::FetchData(Format::Toml, false))>
{ "Fetch Data [toml]" }
</button>
{ self.view_data() }
<button disabled=self.ws.is_some()
onclick=self.link.callback(|_| WsAction::Connect)>
{ "Connect To WebSocket" }
</button>
<button disabled=self.ws.is_none()
onclick=self.link.callback(|_| WsAction::SendData(false))>
{ "Send To WebSocket" }
</button>
<button disabled=self.ws.is_none()
onclick=self.link.callback(|_| WsAction::SendData(true))>
{ "Send To WebSocket [binary]" }
</button>
<button disabled=self.ws.is_none()
onclick=self.link.callback(|_| WsAction::Disconnect)>
{ "Close WebSocket connection" }
</button>
</nav>
</div>
}
}
}
fn main() {
yew::start_app::<Model>();
}

View File

@ -1,3 +0,0 @@
{
"value": 123
}

View File

@ -1 +0,0 @@
value = 567

View File

@ -8,9 +8,10 @@ license = "MIT OR Apache-2.0"
[dependencies]
js-sys = "0.3"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
slab = "0.4.3"
gloo = "0.2"
wasm-bindgen = "0.2"
weblog = "0.3"
[dependencies.web-sys]
version = "0.3.50"

View File

@ -1,14 +1,10 @@
use std::time::Duration;
use gloo::timers::callback::Interval;
use yew::prelude::*;
use yew_services::{
interval::{IntervalService, IntervalTask},
ConsoleService,
};
pub struct CounterModel {
counter: usize,
props: CounterProps,
_interval_task: IntervalTask,
_interval: Interval,
}
#[derive(Clone, Properties)]
@ -27,14 +23,11 @@ impl Component for CounterModel {
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
// Create a Tick message every second
let interval_task = IntervalService::spawn(
Duration::from_secs(1),
link.callback(|()| Self::Message::Tick),
);
let interval = Interval::new(1, move || link.send_message(Self::Message::Tick));
Self {
counter: 0,
props,
_interval_task: interval_task,
_interval: interval,
}
}
@ -73,6 +66,6 @@ impl Component for CounterModel {
}
fn destroy(&mut self) {
ConsoleService::log("CounterModel app destroyed");
weblog::console_log!("CounterModel app destroyed");
}
}

View File

@ -8,4 +8,8 @@ license = "MIT OR Apache-2.0"
[dependencies]
js-sys = "0.3"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
gloo = "0.2"
[dependencies.web-sys]
version = "0.3"
features = ["File"]

View File

@ -7,7 +7,7 @@ The contents of the selected file are then rendered to the page either as a whol
## Concepts
Demonstrates the use of `ReaderService`.
Demonstrates reading from files in Yew with the help of [`gloo::file`](https://gloo-rs.web.app/docs/file).
## Improvements

View File

@ -1,20 +1,22 @@
use yew::{html, ChangeData, Component, ComponentLink, Html, ShouldRender};
use yew_services::reader::{File, FileChunk, FileData, ReaderService, ReaderTask};
use gloo::file::callbacks::FileReader;
use gloo::file::File;
type Chunks = bool;
pub enum Msg {
Loaded(FileData),
Chunk(Option<FileChunk>),
Loaded(String, String),
LoadedBytes(String, Vec<u8>),
Files(Vec<File>, Chunks),
ToggleByChunks,
ToggleReadBytes,
}
pub struct Model {
link: ComponentLink<Model>,
tasks: Vec<ReaderTask>,
readers: Vec<FileReader>,
files: Vec<String>,
by_chunks: bool,
read_bytes: bool,
}
impl Component for Model {
@ -24,44 +26,54 @@ impl Component for Model {
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
tasks: vec![],
readers: vec![],
files: vec![],
by_chunks: false,
read_bytes: false,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Loaded(file) => {
let info = format!("file: {:?}", file);
Msg::Loaded(file_name, data) => {
let info = format!("file_name: {}, data: {:?}", file_name, data);
self.files.push(info);
true
}
Msg::Chunk(Some(chunk)) => {
let info = format!("chunk: {:?}", chunk);
Msg::LoadedBytes(file_name, data) => {
let info = format!("file_name: {}, data: {:?}", file_name, data);
self.files.push(info);
true
}
Msg::Files(files, chunks) => {
Msg::Files(files, bytes) => {
for file in files.into_iter() {
let task = {
if chunks {
let callback = self.link.callback(Msg::Chunk);
ReaderService::read_file_by_chunks(file, callback, 10).unwrap()
let file_name = file.name();
let link = self.link.clone();
if bytes {
gloo::file::callbacks::read_as_bytes(&file, move |res| {
link.send_message(Msg::LoadedBytes(
file_name,
res.expect("failed to read file"),
))
})
} else {
let callback = self.link.callback(Msg::Loaded);
ReaderService::read_file(file, callback).unwrap()
gloo::file::callbacks::read_as_text(&file, move |res| {
link.send_message(Msg::Loaded(
file_name,
res.unwrap_or_else(|e| e.to_string()),
))
})
}
};
self.tasks.push(task);
self.readers.push(task);
}
true
}
Msg::ToggleByChunks => {
self.by_chunks = !self.by_chunks;
Msg::ToggleReadBytes => {
self.read_bytes = !self.read_bytes;
true
}
_ => false,
}
}
@ -70,7 +82,7 @@ impl Component for Model {
}
fn view(&self) -> Html {
let flag = self.by_chunks;
let flag = self.read_bytes;
html! {
<div>
<div>
@ -81,7 +93,8 @@ impl Component for Model {
let files = js_sys::try_iter(&files)
.unwrap()
.unwrap()
.map(|v| File::from(v.unwrap()));
.map(|v| web_sys::File::from(v.unwrap()))
.map(File::from);
result.extend(files);
}
Msg::Files(result, flag)
@ -89,8 +102,8 @@ impl Component for Model {
/>
</div>
<div>
<label>{ "By chunks" }</label>
<input type="checkbox" checked=flag onclick=self.link.callback(|_| Msg::ToggleByChunks) />
<label>{ "Read bytes" }</label>
<input type="checkbox" checked=flag onclick=self.link.callback(|_| Msg::ToggleReadBytes) />
</div>
<ul>
{ for self.files.iter().map(|f| Self::view_file(f)) }

View File

@ -10,8 +10,6 @@ pulldown-cmark = { version = "0.8", default-features = false }
wasm-bindgen = "0.2"
wasm-bindgen-futures = "0.4"
yew = { path = "../../packages/yew" }
yewtil = { path = "../../packages/yewtil", features = ["future"] }
yew-services = { path = "../../packages/yew-services" }
[dependencies.web-sys]
version = "0.3"

View File

@ -7,7 +7,6 @@ use wasm_bindgen::JsCast;
use wasm_bindgen_futures::JsFuture;
use web_sys::{Request, RequestInit, RequestMode, Response};
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yewtil::future::LinkFuture;
mod markdown;

View File

@ -15,4 +15,4 @@ log = "0.4"
rand = "0.8"
wasm-logger = "0.2"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
gloo = "0.2"

View File

@ -15,5 +15,5 @@ trunk serve --release
## Concepts
Uses `IntervalService` to automatically step the simulation.
Logs to the console using the [`log`](https://crates.io/crates/log) crate with the [`wasm_logger`](https://crates.io/crates/wasm-logger) adapter.
- Uses [`gloo_timer`](https://gloo-rs.web.app/docs/timer) to automatically step the simulation.
- Logs to the console using the [`weblog`](https://crates.io/crates/weblog) crate.

View File

@ -1,8 +1,7 @@
use cell::Cellule;
use gloo::timers::callback::Interval;
use rand::Rng;
use std::time::Duration;
use yew::{classes, html, Component, ComponentLink, Html, ShouldRender};
use yew_services::interval::{IntervalService, IntervalTask};
mod cell;
@ -22,7 +21,7 @@ pub struct Model {
cellules: Vec<Cellule>,
cellules_width: usize,
cellules_height: usize,
_task: IntervalTask,
_interval: Interval,
}
impl Model {
@ -108,7 +107,7 @@ impl Component for Model {
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let callback = link.callback(|_| Msg::Tick);
let task = IntervalService::spawn(Duration::from_millis(200), callback);
let interval = Interval::new(200, move || callback.emit(()));
let (cellules_width, cellules_height) = (53, 40);
@ -118,7 +117,7 @@ impl Component for Model {
cellules: vec![Cellule::new_dead(); cellules_width * cellules_height],
cellules_width,
cellules_height,
_task: task,
_interval: interval,
}
}

View File

@ -7,7 +7,6 @@ license = "MIT OR Apache-2.0"
[dependencies]
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
[dependencies.web-sys]
version = "0.3"

View File

@ -8,4 +8,3 @@ license = "MIT OR Apache-2.0"
[dependencies]
wasm-bindgen = "0.2"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }

View File

@ -13,5 +13,3 @@ log = "0.4"
rand = "0.8"
wasm-logger = "0.2"
yew = { path = "../../packages/yew" }
yewtil = { path = "../../packages/yewtil" }
yew-services = { path = "../../packages/yew-services" }

View File

@ -1,8 +1,8 @@
use instant::Instant;
use person::PersonType;
use yew::prelude::*;
use yew::utils::NeqAssign;
use yew::web_sys::HtmlElement;
use yewtil::NeqAssign;
mod person;
mod random;

View File

@ -1,7 +1,7 @@
use crate::random;
use std::rc::Rc;
use yew::utils::NeqAssign;
use yew::{html, Component, ComponentLink, Html, Properties, ShouldRender};
use yewtil::NeqAssign;
use fake::faker::address::raw::*;
use fake::faker::name::raw::*;

View File

@ -8,7 +8,6 @@ license = "MIT OR Apache-2.0"
[dependencies]
wasm-bindgen = "0.2"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
[dependencies.web-sys]
version = "0.3"

View File

@ -10,4 +10,5 @@ log = "0.4"
serde = { version = "1.0", features = ["derive"] }
wasm-logger = "0.2"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
yew-agent = { path = "../../packages/yew-agent" }
gloo = "0.2"

View File

@ -1,5 +1,5 @@
use multi_thread::native_worker::Worker;
use yew::agent::Threaded;
use yew_agent::Threaded;
fn main() {
wasm_logger::init(wasm_logger::Config::default());

View File

@ -1,7 +1,6 @@
use gloo::timers::callback::Interval;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use yew::worker::{Agent, AgentLink, Context, HandlerId};
use yew_services::interval::{IntervalService, IntervalTask};
use yew_agent::{Agent, AgentLink, Context, HandlerId};
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
@ -19,7 +18,7 @@ pub enum Msg {
pub struct Worker {
link: AgentLink<Worker>,
_task: IntervalTask,
_interval: Interval,
}
impl Agent for Worker {
@ -29,11 +28,16 @@ impl Agent for Worker {
type Output = Response;
fn create(link: AgentLink<Self>) -> Self {
let duration = Duration::from_secs(3);
let callback = link.callback(|_| Msg::Updating);
let duration = 3;
let interval = {
let link = link.clone();
Interval::new(duration, move || link.send_message(Msg::Updating))
};
Self {
link,
_task: IntervalService::spawn(duration, callback),
_interval: interval,
}
}

View File

@ -1,7 +1,6 @@
use gloo::timers::callback::Interval;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use yew::worker::{Agent, AgentLink, HandlerId, Job};
use yew_services::interval::{IntervalService, IntervalTask};
use yew_agent::{Agent, AgentLink, HandlerId, Job};
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
@ -21,7 +20,7 @@ pub enum Msg {
pub struct Worker {
link: AgentLink<Worker>,
_task: IntervalTask,
_interval: Interval,
}
impl Agent for Worker {
@ -31,13 +30,17 @@ impl Agent for Worker {
type Output = Response;
fn create(link: AgentLink<Self>) -> Self {
let duration = Duration::from_secs(3);
let callback = link.callback(|_| Msg::Updating);
let duration = 3;
let interval = {
let link = link.clone();
Interval::new(duration, move || link.send_message(Msg::Updating))
};
link.send_message(Msg::Initialized);
Self {
link,
_task: IntervalService::spawn(duration, callback),
_interval: interval,
}
}

View File

@ -2,8 +2,8 @@ pub mod context;
pub mod job;
pub mod native_worker;
use yew::worker::{Bridge, Bridged};
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yew_agent::{Bridge, Bridged};
pub enum Msg {
SendToWorker,

View File

@ -1,7 +1,6 @@
use gloo::timers::callback::Interval;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use yew::worker::{Agent, AgentLink, HandlerId, Public};
use yew_services::interval::{IntervalService, IntervalTask};
use yew_agent::{Agent, AgentLink, HandlerId, Public};
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {
@ -19,7 +18,7 @@ pub enum Msg {
pub struct Worker {
link: AgentLink<Worker>,
_task: IntervalTask,
_interval: Interval,
}
impl Agent for Worker {
@ -29,11 +28,15 @@ impl Agent for Worker {
type Output = Response;
fn create(link: AgentLink<Self>) -> Self {
let duration = Duration::from_secs(3);
let callback = link.callback(|_| Msg::Updating);
let duration = 3;
let interval = {
let link = link.clone();
Interval::new(duration, move || link.send_message(Msg::Updating))
};
Self {
link,
_task: IntervalService::spawn(duration, callback),
_interval: interval,
}
}

View File

@ -9,4 +9,3 @@ license = "MIT OR Apache-2.0"
log = "0.4"
wasm-logger = "0.2"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }

View File

@ -8,4 +8,3 @@ license = "MIT OR Apache-2.0"
[dependencies]
yew = { path = "../../packages/yew" }
web-sys = { version = "0.3", features = ["HtmlElement", "HtmlInputElement", "Node"] }
yew-services = { path = "../../packages/yew-services" }

View File

@ -9,3 +9,4 @@ log = "0.4"
serde = { version = "1.0", features = ["derive"] }
wasm-logger = "0.2.0"
yew = { path = "../../packages/yew" }
yew-agent = { path = "../../packages/yew-agent" }

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use yew::worker::*;
use yew_agent::{Agent, AgentLink, Context, HandlerId};
#[derive(Serialize, Deserialize, Debug)]
pub enum Request {

View File

@ -1,6 +1,6 @@
use crate::event_bus::{EventBus, Request};
use yew::agent::{Dispatched, Dispatcher};
use yew::prelude::*;
use yew_agent::{Dispatched, Dispatcher};
pub enum Msg {
Clicked,

View File

@ -1,6 +1,6 @@
use super::event_bus::EventBus;
use yew::agent::Bridged;
use yew::{html, Bridge, Component, ComponentLink, Html, ShouldRender};
use yew::{html, Component, ComponentLink, Html, ShouldRender};
use yew_agent::{Bridge, Bridged};
pub enum Msg {
NewMessage(String),

View File

@ -13,7 +13,6 @@ rand = { version = "0.8", features = ["small_rng"] }
wasm-logger = "0.2"
yew = { path = "../../packages/yew" }
yew-router = { path = "../../packages/yew-router" }
yewtil = { path = "../../packages/yewtil" }
yew-services = { path = "../../packages/yew-services" }
serde = { version = "1.0", features = ["derive"] }
lazy_static = "1.4.0"
gloo = "0.2"

View File

@ -21,7 +21,6 @@ Content generation can take up quite a bit of time in debug builds.
This example involves many different parts, here are just the Yew specific things:
- Uses [`yew-router`] to render and switch between multiple pages.
- Uses [`IntervalService`] for the [`ProgressDelay`](src/components/progress_delay.rs) component.
The example automatically adapts to the `--public-url` value passed to Trunk.
This allows it to be hosted on any path, not just at the root.
@ -44,5 +43,4 @@ Take a look at [`PublicUrlSwitch`](src/switch.rs) for the implementation.
- Home (`/`) should include links to the post list and the author introduction
- Detect sub-path from `--public-url` value passed to Trunk. See: thedodd/trunk#51
[`intervalservice`]: https://docs.rs/yew-services/latest/yew_services/struct.IntervalService.html
[`yew-router`]: https://docs.rs/yew-router/latest/yew_router/

View File

@ -1,5 +1,5 @@
use yew::prelude::*;
use yewtil::NeqAssign;
use yew::utils::NeqAssign;
const ELLIPSIS: &str = "\u{02026}";

View File

@ -1,8 +1,7 @@
use gloo::timers::callback::Interval;
use instant::Instant;
use std::time::Duration;
use yew::prelude::*;
use yew_services::interval::{IntervalService, IntervalTask};
use yewtil::NeqAssign;
use yew::utils::NeqAssign;
const RESOLUTION: u64 = 500;
const MIN_INTERVAL_MS: u64 = 50;
@ -21,7 +20,7 @@ pub struct Props {
pub struct ProgressDelay {
props: Props,
_task: IntervalTask,
_interval: Interval,
start: Instant,
value: f64,
}
@ -31,13 +30,10 @@ impl Component for ProgressDelay {
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
let interval = (props.duration_ms / RESOLUTION).min(MIN_INTERVAL_MS);
let task = IntervalService::spawn(
Duration::from_millis(interval),
link.callback(|_| Msg::Tick),
);
let interval = Interval::new(interval as u32, move || link.send_message(Msg::Tick));
Self {
props,
_task: task,
_interval: interval,
start: Instant::now(),
value: 0.0,
}

View File

@ -7,5 +7,6 @@ license = "MIT OR Apache-2.0"
[dependencies]
yew = { path = "../../packages/yew" }
yewtil = { path = "../../packages/yewtil" }
yew-services = { path = "../../packages/yew-services" }
yew-agent = { path = "../../packages/yew-agent" }
weblog = "0.3"
wasm-bindgen = "0.2"

View File

@ -6,11 +6,11 @@ A timeline of posts that can be independently updated.
## Concepts
Uses the [`yewtil::store`] API to keep track of posts.
Uses the [`yew_agent`] API to keep track of posts.
## Improvements
- This example desperately needs some styling.
- Posts should persist across sessions.
[`yewtil::store`]: https://docs.rs/yewtil/latest/yewtil/store/index.html
[`yewtil::store`]: https://docs.rs/yew_agent/latest/

View File

@ -1,6 +1,6 @@
use std::collections::HashMap;
use yew::agent::AgentLink;
use yewtil::store::{Store, StoreWrapper};
use yew_agent::utils::store::{Store, StoreWrapper};
use yew_agent::AgentLink;
pub type PostId = u32;
@ -19,7 +19,6 @@ pub enum Action {
pub struct PostStore {
pub posts: HashMap<PostId, String>,
// Stores can have private state too
id_counter: PostId,
}

View File

@ -5,9 +5,10 @@ mod text_input;
use agents::posts::{PostId, PostStore, Request};
use post::Post;
use text_input::TextInput;
use weblog::console_log;
use yew::prelude::*;
use yew_services::ConsoleService;
use yewtil::store::{Bridgeable, ReadOnly, StoreWrapper};
use yew_agent::utils::store::{Bridgeable, ReadOnly, StoreWrapper};
use yew_agent::Bridge;
pub enum Msg {
CreatePost(String),
@ -42,7 +43,7 @@ impl Component for Model {
Msg::PostStoreMsg(state) => {
// We can see this is logged once before we click any button.
// The state of the store is sent when we open a bridge.
ConsoleService::log("Received update");
console_log!("Received update");
let state = state.borrow();
if state.posts.len() != self.post_ids.len() {

View File

@ -1,8 +1,9 @@
use crate::agents::posts::{PostId, PostStore, Request};
use crate::text_input::TextInput;
use yew::prelude::*;
use yewtil::store::{Bridgeable, ReadOnly, StoreWrapper};
use yewtil::NeqAssign;
use yew::utils::NeqAssign;
use yew_agent::utils::store::{Bridgeable, ReadOnly, StoreWrapper};
use yew_agent::Bridge;
pub enum Msg {
UpdateText(String),

View File

@ -8,4 +8,6 @@ license = "MIT OR Apache-2.0"
[dependencies]
yew = { path = "../../packages/yew" }
js-sys = "0.3"
yew-services = { path = "../../packages/yew-services" }
gloo = "0.2"
weblog = "0.3"
wasm-bindgen = "0.2"

View File

@ -6,13 +6,10 @@ This is a technical demonstration for how to use timeouts and intervals.
## Concepts
The example mainly demonstrates the use of [`TimeoutService`] and [`IntervalService`]
but also makes use of some more advanced [`ConsoleService`] features.
The example mainly demonstrates the use of [`gloo_timer`](https://gloo-rs.web.app/docs/timer) and
[`gloo_console_timer`](https://gloo-rs.web.app/docs/console-timer) but also makes use of some
more advanced web console features.
## Improvements
- Apply the concept to something more fun than just a dry technical demonstration
[`timeoutservice`]: https://docs.rs/yew-services/latest/yew_services/struct.TimeoutService.html
[`intervalservice`]: https://docs.rs/yew-services/latest/yew_services/struct.IntervalService.html
[`consoleservice`]: https://docs.rs/yew-services/latest/yew_services/struct.ConsoleService.html

View File

@ -1,7 +1,7 @@
use std::time::Duration;
use yew::{html, Callback, Component, ComponentLink, Html, ShouldRender};
use yew_services::interval::{IntervalService, IntervalTask};
use yew_services::{ConsoleService, Task, TimeoutService};
use gloo::console_timer::ConsoleTimer;
use gloo::timers::callback::{Interval, Timeout};
use weblog::*;
use yew::{html, Component, ComponentLink, Html, ShouldRender};
pub enum Msg {
StartTimeout,
@ -14,10 +14,12 @@ pub enum Msg {
pub struct Model {
link: ComponentLink<Self>,
job: Option<Box<dyn Task>>,
time: String,
messages: Vec<&'static str>,
_standalone: (IntervalTask, IntervalTask),
_standalone: (Interval, Interval),
interval: Option<Interval>,
timeout: Option<Timeout>,
console_timer: Option<ConsoleTimer<'static>>,
}
impl Model {
@ -25,6 +27,11 @@ impl Model {
let date = js_sys::Date::new_0();
String::from(date.to_locale_time_string("en-US"))
}
fn cancel(&mut self) {
self.timeout = None;
self.interval = None;
}
}
impl Component for Model {
@ -32,74 +39,80 @@ impl Component for Model {
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let standalone_handle = IntervalService::spawn(
Duration::from_secs(10),
// This callback doesn't send any message to a scope
Callback::from(|_| {
ConsoleService::debug("Example of a standalone callback.");
}),
);
let standalone_handle =
Interval::new(10, || console_debug!("Example of a standalone callback."));
let clock_handle =
IntervalService::spawn(Duration::from_secs(1), link.callback(|_| Msg::UpdateTime));
let clock_handle = {
let link = link.clone();
Interval::new(1, move || link.send_message(Msg::UpdateTime))
};
Self {
link,
job: None,
time: Model::get_current_time(),
messages: Vec::new(),
_standalone: (standalone_handle, clock_handle),
interval: None,
timeout: None,
console_timer: None,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::StartTimeout => {
let handle = TimeoutService::spawn(
Duration::from_secs(3),
self.link.callback(|_| Msg::Done),
);
self.job = Some(Box::new(handle));
let handle = {
let link = self.link.clone();
Timeout::new(3, move || link.send_message(Msg::Done))
};
self.timeout = Some(handle);
self.messages.clear();
ConsoleService::clear();
console_clear!();
self.messages.push("Timer started!");
ConsoleService::time_named("Timer");
self.console_timer = Some(ConsoleTimer::new("Timer"));
true
}
Msg::StartInterval => {
let handle = IntervalService::spawn(
Duration::from_secs(1),
self.link.callback(|_| Msg::Tick),
);
self.job = Some(Box::new(handle));
let handle = {
let link = self.link.clone();
Interval::new(1, move || link.send_message(Msg::Tick))
};
self.interval = Some(handle);
self.messages.clear();
ConsoleService::clear();
console_clear!();
self.messages.push("Interval started!");
true
}
Msg::Cancel => {
self.job = None;
self.cancel();
self.messages.push("Canceled!");
ConsoleService::warn("Canceled!");
console_warn!("Canceled!");
true
}
Msg::Done => {
self.job = None;
self.cancel();
self.messages.push("Done!");
ConsoleService::group();
ConsoleService::info("Done!");
ConsoleService::time_named_end("Timer");
ConsoleService::group_end();
// todo weblog
// ConsoleService::group();
console_info!("Done!");
if let Some(timer) = self.console_timer.take() {
drop(timer);
}
// todo weblog
// ConsoleService::group_end();
true
}
Msg::Tick => {
self.messages.push("Tick...");
ConsoleService::count_named("Tick");
// todo weblog
// ConsoleService::count_named("Tick");
true
}
Msg::UpdateTime => {
@ -114,7 +127,7 @@ impl Component for Model {
}
fn view(&self) -> Html {
let has_job = self.job.is_some();
let has_job = self.timeout.is_some() || self.interval.is_some();
html! {
<>
<div id="buttons">

View File

@ -11,4 +11,4 @@ strum_macros = "0.21"
serde = "1"
serde_derive = "1"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
gloo = { git = "https://github.com/rustwasm/gloo/" }

View File

@ -9,7 +9,7 @@ including: all entries, entered text and chosen filter.
## Concepts
- Uses [`StorageService`] to persist the state
- Uses [`gloo_storage`](https://gloo-rs.web.app/docs/storage) to persist the state
- [`Refs`] are used to manipulate DOM elements after they're rendered (to automatically focus input fields for instance)
## Improvements
@ -17,5 +17,4 @@ including: all entries, entered text and chosen filter.
- Use `yew-router` for the hash based routing
- Clean up the code
[`storageservice`]: https://docs.rs/yew-services/latest/yew_services/struct.StorageService.html
[`refs`]: https://yew.rs/concepts/components/refs/

View File

@ -1,10 +1,9 @@
use gloo::storage::{LocalStorage, Storage};
use state::{Entry, Filter, State};
use strum::IntoEnumIterator;
use yew::format::Json;
use yew::web_sys::HtmlInputElement as InputElement;
use yew::{classes, html, Component, ComponentLink, Html, InputData, NodeRef, ShouldRender};
use yew::{events::KeyboardEvent, Classes};
use yew_services::storage::{Area, StorageService};
mod state;
@ -26,7 +25,6 @@ pub enum Msg {
pub struct Model {
link: ComponentLink<Self>,
storage: StorageService,
state: State,
focus_ref: NodeRef,
}
@ -36,14 +34,7 @@ impl Component for Model {
type Properties = ();
fn create(_props: Self::Properties, link: ComponentLink<Self>) -> Self {
let storage = StorageService::new(Area::Local).expect("storage was disabled by the user");
let entries = {
if let Json(Ok(restored_model)) = storage.restore(KEY) {
restored_model
} else {
Vec::new()
}
};
let entries = LocalStorage::get(KEY).unwrap_or_else(|_| Vec::new());
let state = State {
entries,
filter: Filter::All,
@ -53,7 +44,6 @@ impl Component for Model {
let focus_ref = NodeRef::default();
Self {
link,
storage,
state,
focus_ref,
}
@ -113,7 +103,7 @@ impl Component for Model {
}
}
}
self.storage.store(KEY, Json(&self.state.entries));
LocalStorage::set(KEY, &self.state.entries).expect("failed to set");
true
}

View File

@ -7,4 +7,3 @@ license = "MIT OR Apache-2.0"
[dependencies]
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }

View File

@ -9,7 +9,7 @@ license = "MIT OR Apache-2.0"
js-sys = "0.3"
wasm-bindgen = "0.2"
yew = { path = "../../packages/yew" }
yew-services = { path = "../../packages/yew-services" }
gloo-render = { git = "https://github.com/rustwasm/gloo/" }
[dependencies.web-sys]
version = "0.3"

View File

@ -1,8 +1,7 @@
use gloo_render::{request_animation_frame, AnimationFrame};
use wasm_bindgen::JsCast;
use web_sys::{HtmlCanvasElement, WebGlRenderingContext as GL};
use yew::{html, Component, ComponentLink, Html, NodeRef, ShouldRender};
use yew_services::render::RenderTask;
use yew_services::RenderService;
pub enum Msg {
Render(f64),
@ -12,7 +11,7 @@ pub struct Model {
gl: Option<GL>,
link: ComponentLink<Self>,
node_ref: NodeRef,
_render_loop: Option<RenderTask>,
_render_loop: Option<AnimationFrame>,
}
impl Component for Model {
@ -51,8 +50,10 @@ impl Component for Model {
if first_render {
// The callback to request animation frame is passed a time value which can be used for
// rendering motion independent of the framerate which may vary.
let render_frame = self.link.callback(Msg::Render);
let handle = RenderService::request_animation_frame(render_frame);
let handle = {
let link = self.link.clone();
request_animation_frame(move |time| link.send_message(Msg::Render(time)))
};
// A reference to the handle must be stored, otherwise it is dropped and the render won't
// occur.
@ -127,8 +128,10 @@ impl Model {
gl.draw_arrays(GL::TRIANGLES, 0, 6);
let render_frame = self.link.callback(Msg::Render);
let handle = RenderService::request_animation_frame(render_frame);
let handle = {
let link = self.link.clone();
request_animation_frame(move |time| link.send_message(Msg::Render(time)))
};
// A reference to the new handle must be retained for the next render to run.
self._render_loop = Some(handle);

View File

@ -0,0 +1,24 @@
[package]
name = "yew-agent"
version = "0.1.0"
authors = ["Hamza <muhammadhamza1311@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anymap = "0.12"
bincode = { version = "1" }
js-sys = "0.3"
log = "0.4"
serde = { version = "1.0", features = ["derive"] }
slab = "0.4"
wasm-bindgen = "0.2"
yew = { path = "../yew" }
wasm-bindgen-futures = "0.4"
[dependencies.web-sys]
version = "0.3"
features = [
"Worker"
]

View File

@ -3,6 +3,7 @@
mod link;
mod local;
mod pool;
pub mod utils;
mod worker;
pub use link::AgentLink;
@ -12,10 +13,10 @@ pub(crate) use pool::*;
pub use pool::{Dispatched, Dispatcher};
pub use worker::{Private, Public, Threaded};
use crate::callback::Callback;
use serde::{Deserialize, Serialize};
use std::fmt;
use std::ops::{Deref, DerefMut};
use yew::callback::Callback;
/// Declares the behavior of the agent.
pub trait Agent: Sized + 'static {

View File

@ -1,10 +1,12 @@
use super::*;
use crate::callback::Callback;
use crate::html::ImplicitClone;
use crate::scheduler::{self, Runnable, Shared};
use std::cell::RefCell;
use std::fmt;
use std::future::Future;
use std::rc::Rc;
use wasm_bindgen_futures::spawn_local;
use yew::callback::Callback;
use yew::html::ImplicitClone;
use yew::scheduler::{self, Runnable, Shared};
/// Defines communication from Worker to Consumers
pub(crate) trait Responder<AGN: Agent> {
@ -66,6 +68,68 @@ impl<AGN: Agent> AgentLink<AGN> {
};
closure.into()
}
/// This method creates a [`Callback`] which returns a Future which
/// returns a message to be sent back to the agent
///
/// # Panics
/// If the future panics, then the promise will not resolve, and
/// will leak.
pub fn callback_future<FN, FU, IN, M>(&self, function: FN) -> Callback<IN>
where
M: Into<AGN::Message>,
FU: Future<Output = M> + 'static,
FN: Fn(IN) -> FU + 'static,
{
let link = self.clone();
let closure = move |input: IN| {
let future: FU = function(input);
link.send_future(future);
};
closure.into()
}
/// This method creates a [`Callback`] from [`FnOnce`] which returns a Future
/// which returns a message to be sent back to the agent.
///
/// # Panics
/// If the future panics, then the promise will not resolve, and
/// will leak.
pub fn callback_future_once<FN, FU, IN, M>(&self, function: FN) -> Callback<IN>
where
M: Into<AGN::Message>,
FU: Future<Output = M> + 'static,
FN: FnOnce(IN) -> FU + 'static,
{
let link = self.clone();
let closure = move |input: IN| {
let future: FU = function(input);
link.send_future(future);
};
Callback::once(closure)
}
/// This method processes a Future that returns a message and sends it back to the agent.
///
/// # Panics
/// If the future panics, then the promise will not resolve, and will leak.
pub fn send_future<F, M>(&self, future: F)
where
M: Into<AGN::Message>,
F: Future<Output = M> + 'static,
{
let link: AgentLink<AGN> = self.clone();
let js_future = async move {
let message: AGN::Message = future.await.into();
let cb = link.callback(|m: AGN::Message| m);
cb.emit(message);
};
spawn_local(js_future);
}
}
impl<AGN: Agent> fmt::Debug for AgentLink<AGN> {

View File

@ -1,11 +1,11 @@
use super::*;
use crate::callback::Callback;
use crate::scheduler::Shared;
use anymap::{self, AnyMap};
use slab::Slab;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use yew::callback::Callback;
use yew::scheduler::Shared;
thread_local! {
static LOCAL_AGENTS_POOL: RefCell<AnyMap> = RefCell::new(AnyMap::new());

View File

@ -1,6 +1,6 @@
use super::*;
use crate::callback::Callback;
use std::marker::PhantomData;
use yew::callback::Callback;
const SINGLETON_ID: HandlerId = HandlerId(0, true);

View File

@ -1,7 +1,7 @@
use super::*;
use crate::scheduler::Shared;
use log::warn;
use slab::Slab;
use yew::scheduler::Shared;
pub(crate) type Last = bool;

View File

@ -0,0 +1 @@
pub mod store;

View File

@ -1,8 +1,8 @@
use crate::{Agent, AgentLink, Bridge, Context, Discoverer, Dispatched, Dispatcher, HandlerId};
use std::cell::RefCell;
use std::collections::HashSet;
use std::ops::Deref;
use std::rc::Rc;
use yew::agent::{Agent, AgentLink, Context, Discoverer, Dispatcher, HandlerId};
use yew::prelude::*;
/// A functional state wrapper, enforcing a unidirectional

View File

@ -6,7 +6,6 @@ pub use private::Private;
pub use public::Public;
use super::*;
use crate::utils;
use js_sys::{Array, Reflect, Uint8Array};
use serde::{Deserialize, Serialize};
use wasm_bindgen::{closure::Closure, JsCast, JsValue};
@ -72,7 +71,7 @@ where
}
fn worker_new(name_of_resource: &str, is_module: bool) -> Worker {
let origin = utils::origin().unwrap();
let origin = yew::utils::origin().unwrap();
let script_url = format!("{}/{}", origin, name_of_resource);
let wasm_url = format!("{}/{}", origin, name_of_resource.replace(".js", "_bg.wasm"));
let array = Array::new();

View File

@ -1,10 +1,10 @@
use super::*;
use crate::callback::Callback;
use queue::Queue;
use std::fmt;
use std::marker::PhantomData;
use std::sync::atomic::{AtomicUsize, Ordering};
use web_sys::Worker;
use yew::callback::Callback;
thread_local! {
static QUEUE: Queue<usize> = Queue::new();

View File

@ -1,7 +1,5 @@
use super::WorkerExt;
use super::*;
use crate::callback::Callback;
use crate::scheduler::Shared;
use anymap::{self, AnyMap};
use queue::Queue;
use slab::Slab;
@ -11,6 +9,8 @@ use std::fmt;
use std::marker::PhantomData;
use std::rc::Rc;
use web_sys::Worker;
use yew::callback::Callback;
use yew::scheduler::Shared;
thread_local! {
static REMOTE_AGENTS_POOL: RefCell<AnyMap> = RefCell::new(AnyMap::new());

View File

@ -1,17 +0,0 @@
[package]
name = "yew-components"
version = "0.3.0"
edition = "2018"
authors = ["Yew Maintainers <maintainers@yew.rs>"]
repository = "https://github.com/yewstack/yew"
homepage = "https://github.com/yewstack/yew"
documentation = "https://docs.rs/yew-components/"
license = "MIT OR Apache-2.0"
keywords = ["web", "asmjs", "webasm", "javascript"]
categories = ["gui", "web-programming"]
description = "A collection of community-created Yew components"
[dependencies]
wasm-bindgen = "0.2.60"
web-sys = "0.3"
yew = { version = "^0.18", path = "../yew" }

View File

@ -1,8 +0,0 @@
//! This crate contains useful Yew components.
//! At this moment it only includes typed `Select`.
#[doc(hidden)]
pub mod select;
#[doc(inline)]
pub use self::select::Select;

View File

@ -1,211 +0,0 @@
//! This module contains the implementation of the `Select` component.
use web_sys::HtmlSelectElement;
use yew::callback::Callback;
use yew::html::{ChangeData, Component, ComponentLink, Html, NodeRef, ShouldRender};
use yew::{html, Properties};
/// An alternative to the HTML `<select>` tag.
///
/// The display of options is handled by the `ToString` implementation on their
/// type.
///
/// # Example
///
/// ```
///# use std::fmt;
///# use yew::{Html, Component, ComponentLink, html};
///# use yew_components::Select;
/// #[derive(PartialEq, Clone)]
/// enum Scene {
/// First,
/// Second,
/// }
///# struct Model { link: ComponentLink<Self> };
///# impl Component for Model {
///# type Message = ();type Properties = ();
///# fn create(props: Self::Properties,link: ComponentLink<Self>) -> Self {unimplemented!()}
///# fn update(&mut self,msg: Self::Message) -> bool {unimplemented!()}
///# fn change(&mut self, _: Self::Properties) -> bool {unimplemented!()}
///# fn view(&self) -> Html {unimplemented!()}}
/// impl fmt::Display for Scene {
/// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// match self {
/// Scene::First => write!(f, "{}", "First"),
/// Scene::Second => write!(f, "{}", "Second"),
/// }
/// }
/// }
///
/// fn view(link: ComponentLink<Model>) -> Html {
/// let scenes = vec![Scene::First, Scene::Second];
/// html! {
/// <Select<Scene> options=scenes on_change=link.callback(|_| ()) />
/// }
/// }
/// ```
///
/// # Properties
///
/// Only the `on_change` property is mandatory. Other (optional) properties
/// are `selected`, `disabled`, `options`, `class`, `id`, and `placeholder`.
#[derive(Debug)]
pub struct Select<T: ToString + PartialEq + Clone + 'static> {
props: Props<T>,
select_ref: NodeRef,
link: ComponentLink<Self>,
}
/// Messages sent internally as part of the select component
#[derive(Debug)]
pub enum Msg {
/// Sent when the user selects a new option.
Selected(Option<usize>),
}
/// Properties of the `Select` component.
#[derive(PartialEq, Clone, Properties, Debug)]
pub struct Props<T: Clone> {
/// Initially selected value.
#[prop_or_default]
pub selected: Option<T>,
/// Whether or not the selector should be disabled.
#[prop_or_default]
pub disabled: bool,
/// A vector of options which the end user can choose from.
#[prop_or_default]
pub options: Vec<T>,
/// Classes to be applied to the `<select>` tag
#[prop_or_default]
pub class: String,
/// The ID for the `<select>` tag
#[prop_or_default]
pub id: String,
/// Placeholder value, shown at the top as a disabled option
#[prop_or(String::from(""))]
pub placeholder: String,
/// A callback which is called when the value of the `<select>` changes.
pub on_change: Callback<T>,
}
impl<T> Component for Select<T>
where
T: ToString + PartialEq + Clone + 'static,
{
type Message = Msg;
type Properties = Props<T>;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
props,
select_ref: NodeRef::default(),
link,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::Selected(value) => {
if let Some(idx) = value {
let item = self.props.options.get(idx - 1);
if let Some(value) = item {
self.props.on_change.emit(value.clone());
}
}
}
}
true
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if self.props.selected != props.selected {
if let Some(select) = self.select_ref.cast::<HtmlSelectElement>() {
let val = props
.selected
.as_ref()
.map(|v| v.to_string())
.unwrap_or_default();
select.set_value(&val);
}
}
self.props = props;
true
}
fn view(&self) -> Html {
let selected = self.props.selected.as_ref();
let view_option = |value: &T| {
let flag = selected == Some(value);
html! {
<option value=value.to_string() selected=flag>{ value.to_string() }</option>
}
};
html! {
<select
ref=self.select_ref.clone()
id=self.props.id.clone()
class=self.props.class.clone()
disabled=self.props.disabled
onchange=self.on_change()
>
<option value="" disabled=true selected=selected.is_none()>
{ self.props.placeholder.clone() }
</option>
{ for self.props.options.iter().map(view_option) }
</select>
}
}
}
impl<T> Select<T>
where
T: ToString + PartialEq + Clone + 'static,
{
fn on_change(&self) -> Callback<ChangeData> {
self.link.callback(|event| match event {
ChangeData::Select(elem) => {
let value = elem.selected_index();
Msg::Selected(Some(value as usize))
}
_ => unreachable!(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn can_create_select() {
let on_change = Callback::<u8>::default();
html! {
<Select<u8> on_change=on_change />
};
}
#[test]
fn can_create_select_with_class() {
let on_change = Callback::<u8>::default();
html! {
<Select<u8> on_change=on_change class="form-control" />
};
}
#[test]
fn can_create_select_with_id() {
let on_change = Callback::<u8>::default();
html! {
<Select<u8> on_change=on_change id="test-select" />
};
}
#[test]
fn can_create_select_with_placeholder() {
let on_change = Callback::<u8>::default();
html! {
<Select<u8> on_change=on_change placeholder="--Please choose an option--" />
};
}
}

View File

@ -1,11 +0,0 @@
[package]
name = "yew-dsl"
version = "0.1.0"
authors = ["Teymour Aldridge <teymour.aldridge@icloud.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
yew = {path="../yew"}

View File

@ -1,69 +0,0 @@
//! yew_dsl provides an Rust-based syntax for creating DOM elements.
//! It provides five basic functions with which you should be able to create complex layouts
//! (these are `tag`, `comp`, `text`, `populated_list` and `list`).
pub use crate::vcomp::VCompProducer;
use crate::vlist::VListProducer;
pub use crate::vtag::VTagProducer;
pub use crate::vtext::VTextProducer;
use yew::virtual_dom::VNode;
use yew::Component;
mod vcomp;
mod vlist;
mod vtag;
mod vtext;
use std::cell::RefCell;
use std::rc::Rc;
use yew::html::Scope;
/// A `ScopeHolder` contains a reference to the scope of the parent component.
type ScopeHolder<PARENT> = Rc<RefCell<Option<Scope<PARENT>>>>;
/// `BoxedVNodeProducer` is a wrapper around a function which produces a `VNode`.
pub struct BoxedVNodeProducer<COMP: Component>(Box<dyn FnOnce(ScopeHolder<COMP>) -> VNode>);
impl<COMP: Component> BoxedVNodeProducer<COMP> {
fn wrap(f: impl FnOnce(ScopeHolder<COMP>) -> VNode + 'static) -> Self {
BoxedVNodeProducer(Box::new(f))
}
fn execute(self, scope: &ScopeHolder<COMP>) -> VNode {
(self.0)(scope.clone())
}
pub fn build(self) -> VNode {
let scope = ScopeHolder::default();
self.execute(&scope)
}
}
impl<COMP: Component> From<BoxedVNodeProducer<COMP>> for VNode {
fn from(value: BoxedVNodeProducer<COMP>) -> VNode {
value.build()
}
}
/// Creates HTML tags (e.g. 'span', 'div', etc).
pub fn tag<COMP: Component>(tag: &'static str) -> VTagProducer<COMP> {
VTagProducer::new(tag)
}
/// Creates child components.
pub fn comp<COMP: Component, CHILD: Component>(props: CHILD::Properties) -> VCompProducer<COMP> {
VCompProducer::new::<CHILD>(props)
}
/// Creates text nodes.
pub fn text<COMP: Component, TEXT: Into<String> + 'static>(text: TEXT) -> VTextProducer {
VTextProducer::new::<TEXT>(text)
}
/// Creates new lists populatated with the data supplied to the function.
pub fn populated_list<COMP: Component>(list: Vec<BoxedVNodeProducer<COMP>>) -> VListProducer<COMP> {
VListProducer::populated_new(list)
}
/// Creates new (empty) lists.
pub fn list<COMP: Component>() -> VListProducer<COMP> {
VListProducer::new()
}

View File

@ -1,24 +0,0 @@
use crate::BoxedVNodeProducer;
use yew::virtual_dom::VComp;
use yew::{Component, NodeRef};
use crate::ScopeHolder;
/// `VCompProducer` returns instances of virtual components. It implements the `From` trait
/// for `BoxedVNodeProducer` through which it can be used to return virtual nodes.
pub struct VCompProducer<COMP: Component>(Box<dyn FnOnce(ScopeHolder<COMP>) -> VComp>);
impl<COMP: Component> VCompProducer<COMP> {
pub fn new<CHILD: Component>(props: CHILD::Properties) -> Self {
// TODO: allow getting the NodeRef as a parameter somewhere.
VCompProducer(Box::new(move |_| {
VComp::new::<CHILD>(props, NodeRef::default(), None)
}))
}
}
impl<COMP: Component> From<VCompProducer<COMP>> for BoxedVNodeProducer<COMP> {
fn from(vcomp_prod: VCompProducer<COMP>) -> Self {
BoxedVNodeProducer::wrap(move |scope| (vcomp_prod.0)(scope).into())
}
}

View File

@ -1,42 +0,0 @@
use crate::BoxedVNodeProducer;
use yew::virtual_dom::VList;
use yew::Component;
///
pub struct VListProducer<COMP: Component> {
children: Vec<BoxedVNodeProducer<COMP>>,
}
impl<COMP: Component> Default for VListProducer<COMP> {
fn default() -> Self {
VListProducer::<COMP> { children: vec![] }
}
}
impl<COMP: Component> VListProducer<COMP> {
pub fn new() -> Self {
VListProducer::<COMP> { children: vec![] }
}
pub fn child<T: Into<BoxedVNodeProducer<COMP>>>(mut self, child: T) -> Self {
self.children.push(child.into());
self
}
pub fn populated_new(children: Vec<BoxedVNodeProducer<COMP>>) -> Self {
VListProducer::<COMP> { children }
}
}
impl<COMP: Component> From<VListProducer<COMP>> for BoxedVNodeProducer<COMP> {
fn from(vlist_prod: VListProducer<COMP>) -> Self {
BoxedVNodeProducer(Box::new(move |scope| {
let mut vlist = VList::new();
for child in vlist_prod.children {
let child = child.execute(&scope);
vlist.add_child(child);
}
vlist.into()
}))
}
}

View File

@ -1,77 +0,0 @@
use crate::BoxedVNodeProducer;
use crate::ScopeHolder;
use yew::virtual_dom::{Listener, VTag};
use yew::{Classes, Component};
pub struct Effect<T, COMP: Component>(Box<dyn FnOnce(T, &ScopeHolder<COMP>) -> T>);
impl<T, COMP: Component> Effect<T, COMP> {
fn new(f: impl FnOnce(T, &ScopeHolder<COMP>) -> T + 'static) -> Self {
Effect::<T, COMP>(Box::new(f))
}
}
pub struct VTagProducer<COMP: Component> {
tag_type: &'static str,
effects: Vec<Effect<VTag, COMP>>,
}
impl<COMP: Component> VTagProducer<COMP> {
pub fn new(tag_type: &'static str) -> Self {
VTagProducer::<COMP> {
tag_type,
effects: vec![],
}
}
// TODO, consider making this T: Into<VNode> - The whole dsl doesn't need to be lazy.
// - although being generic over an additional argument that is either () OR Scope is problematic.
pub fn child<T: Into<BoxedVNodeProducer<COMP>> + 'static>(mut self, child: T) -> Self {
let effect = Effect::new(move |mut vtag: VTag, scope: &ScopeHolder<COMP>| {
let child = child.into().execute(scope);
vtag.add_child(child);
vtag
});
self.effects.push(effect);
self
}
pub fn attribute(mut self, name: &'static str, value: String) -> Self {
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
vtag.add_attribute(name, value);
vtag
});
self.effects.push(effect);
self
}
pub fn listener(mut self, listener: std::rc::Rc<dyn Listener>) -> Self {
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
vtag.add_listener(listener);
vtag
});
self.effects.push(effect);
self
}
pub fn classes(mut self, classes: Classes) -> Self {
let effect = Effect::new(move |mut vtag: VTag, _scope: &ScopeHolder<COMP>| {
vtag.add_attribute("class", classes.to_string());
vtag
});
self.effects.push(effect);
self
}
}
impl<COMP: Component> From<VTagProducer<COMP>> for BoxedVNodeProducer<COMP> {
fn from(vtag_prod: VTagProducer<COMP>) -> Self {
BoxedVNodeProducer::wrap(move |scope| {
let mut vtag = VTag::new(vtag_prod.tag_type);
for effect in vtag_prod.effects.into_iter() {
vtag = (effect.0)(vtag, &scope)
}
vtag.into()
})
}
}

View File

@ -1,18 +0,0 @@
use crate::BoxedVNodeProducer;
use yew::virtual_dom::VText;
use yew::Component;
/// A wrapper around a function which produces `VText` nodes.
pub struct VTextProducer(Box<dyn FnOnce() -> VText>);
impl VTextProducer {
pub fn new<TEXT: Into<String> + 'static>(text: TEXT) -> Self {
VTextProducer(Box::new(move || VText::new(text.into())))
}
}
impl<COMP: Component> From<VTextProducer> for BoxedVNodeProducer<COMP> {
fn from(vtext_prod: VTextProducer) -> Self {
BoxedVNodeProducer::wrap(move |_scope| (vtext_prod.0)().into())
}
}

View File

@ -1,22 +0,0 @@
[package]
name = "yew-functional-macro"
version = "0.1.0"
authors = ["Hamza <muhammadhamza1311@gmail.com>"]
edition = "2018"
license = "MIT OR Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.6"
quote = "1.0"
syn = { version = "1.0.11", features = ["full"] }
# testing
[dev-dependencies]
rustversion = "1.0"
trybuild = "1.0"
yew = { path = "../yew" }
yew-functional = { path = "../yew-functional" }

View File

@ -1,9 +0,0 @@
[tasks.test]
clear = true
toolchain = "1.51"
command = "cargo"
args = ["test"]
[tasks.test-overwrite]
extend = "test"
env = { TRYBUILD = "overwrite" }

View File

@ -1,25 +0,0 @@
[package]
name = "yew-functional"
version = "0.13.0"
authors = ["ZainlessBrombie <zainredirect@gmail.com>"]
edition = "2018"
repository = "https://github.com/yewstack/yew"
homepage = "https://github.com/yewstack/yew"
license = "MIT OR Apache-2.0"
keywords = ["web", "wasm", "frontend", "webasm", "webassembly"]
categories = ["gui", "web-programming", "wasm"]
description = "A framework for making client-side single-page apps"
[dev-dependencies]
wasm-bindgen-test = "0.3.9"
web-sys = "0.3.36"
yew = { path = "../yew" }
log = "0.4"
wasm-logger = "0.2"
yew-services = { path = "../yew-services" }
[dependencies]
yew = { path = "../yew" }
yew-functional-macro = { path = "../yew-functional-macro" }
wasm-bindgen = "0.2.60"
scoped-tls-hkt = "0.1.2"

View File

@ -1,4 +0,0 @@
[tasks.test]
extend = "core::wasm-pack-base"
command = "wasm-pack"
args = ["test", "@@split(YEW_TEST_FLAGS, )"]

View File

@ -1,13 +0,0 @@
pub(crate) fn obtain_result() -> String {
yew::utils::document()
.get_element_by_id("result")
.expect("No result found. Most likely, the application crashed and burned")
.inner_html()
}
pub(crate) fn obtain_result_by_id(id: &str) -> String {
yew::utils::document()
.get_element_by_id(id)
.expect("No result found. Most likely, the application crashed and burned")
.inner_html()
}

View File

@ -4,12 +4,9 @@ use syn::parse::{Parse, ParseStream};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::Comma;
use syn::{
parse_macro_input, Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type,
Visibility,
};
use syn::{Attribute, Block, FnArg, Generics, Ident, Item, ItemFn, ReturnType, Type, Visibility};
struct FunctionComponent {
pub struct FunctionComponent {
block: Box<Block>,
props_type: Box<Type>,
arg: FnArg,
@ -142,7 +139,7 @@ impl Parse for FunctionComponent {
}
}
struct FunctionComponentName {
pub struct FunctionComponentName {
component_name: Ident,
}
@ -158,20 +155,7 @@ impl Parse for FunctionComponentName {
}
}
#[proc_macro_attribute]
pub fn function_component(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as FunctionComponent);
let attr = parse_macro_input!(attr as FunctionComponentName);
function_component_impl(attr, item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}
fn function_component_impl(
pub fn function_component_impl(
name: FunctionComponentName,
component: FunctionComponent,
) -> syn::Result<TokenStream> {
@ -212,7 +196,7 @@ fn function_component_impl(
_marker: ::std::marker::PhantomData<(#phantom_generics)>,
}
impl #impl_generics ::yew_functional::FunctionProvider for #function_name #ty_generics #where_clause {
impl #impl_generics ::yew::functional::FunctionProvider for #function_name #ty_generics #where_clause {
type TProps = #props_type;
fn run(#arg) -> #ret_type {
@ -222,7 +206,7 @@ fn function_component_impl(
#(#attrs)*
#[allow(type_alias_bounds)]
#vis type #component_name #impl_generics = ::yew_functional::FunctionComponent<#function_name #ty_generics>;
#vis type #component_name #impl_generics = ::yew::functional::FunctionComponent<#function_name #ty_generics>;
};
Ok(quoted)

View File

@ -57,11 +57,13 @@
mod classes;
mod derive_props;
mod function_component;
mod html_tree;
mod props;
mod stringify;
use derive_props::DerivePropsInput;
use function_component::{function_component_impl, FunctionComponent, FunctionComponentName};
use html_tree::{HtmlRoot, HtmlRootVNode};
use proc_macro::TokenStream;
use quote::ToTokens;
@ -126,3 +128,16 @@ pub fn classes(input: TokenStream) -> TokenStream {
let classes = parse_macro_input!(input as classes::Classes);
TokenStream::from(classes.into_token_stream())
}
#[proc_macro_attribute]
pub fn function_component(
attr: proc_macro::TokenStream,
item: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let item = parse_macro_input!(item as FunctionComponent);
let attr = parse_macro_input!(attr as FunctionComponentName);
function_component_impl(attr, item)
.unwrap_or_else(|err| err.to_compile_error())
.into()
}

Some files were not shown because too many files have changed in this diff Show More