mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
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:
parent
91ab14bfd3
commit
2412a68bee
31
.github/workflows/pull-request.yml
vendored
31
.github/workflows/pull-request.yml
vendored
@ -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
|
||||
|
||||
15
Cargo.toml
15
Cargo.toml
@ -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",
|
||||
|
||||
@ -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 |
|
||||
|
||||
@ -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/" }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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/" }
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" }
|
||||
@ -1,31 +0,0 @@
|
||||
# Dashboard Example
|
||||
|
||||
[](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.
|
||||
@ -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>
|
||||
@ -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]
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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>();
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
{
|
||||
"value": 123
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
value = 567
|
||||
@ -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"
|
||||
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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)) }
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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::*;
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use yew::prelude::*;
|
||||
use yewtil::NeqAssign;
|
||||
use yew::utils::NeqAssign;
|
||||
|
||||
const ELLIPSIS: &str = "\u{02026}";
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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/" }
|
||||
|
||||
@ -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/
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -7,4 +7,3 @@ license = "MIT OR Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
yew = { path = "../../packages/yew" }
|
||||
yew-services = { path = "../../packages/yew-services" }
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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);
|
||||
|
||||
24
packages/yew-agent/Cargo.toml
Normal file
24
packages/yew-agent/Cargo.toml
Normal 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"
|
||||
]
|
||||
@ -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 {
|
||||
@ -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> {
|
||||
@ -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());
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
1
packages/yew-agent/src/utils/mod.rs
Normal file
1
packages/yew-agent/src/utils/mod.rs
Normal file
@ -0,0 +1 @@
|
||||
pub mod store;
|
||||
@ -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
|
||||
@ -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();
|
||||
@ -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();
|
||||
@ -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());
|
||||
@ -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" }
|
||||
@ -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;
|
||||
@ -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--" />
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -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"}
|
||||
@ -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()
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}))
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -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())
|
||||
}
|
||||
}
|
||||
@ -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" }
|
||||
@ -1,9 +0,0 @@
|
||||
[tasks.test]
|
||||
clear = true
|
||||
toolchain = "1.51"
|
||||
command = "cargo"
|
||||
args = ["test"]
|
||||
|
||||
[tasks.test-overwrite]
|
||||
extend = "test"
|
||||
env = { TRYBUILD = "overwrite" }
|
||||
@ -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"
|
||||
@ -1,4 +0,0 @@
|
||||
[tasks.test]
|
||||
extend = "core::wasm-pack-base"
|
||||
command = "wasm-pack"
|
||||
args = ["test", "@@split(YEW_TEST_FLAGS, )"]
|
||||
@ -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()
|
||||
}
|
||||
@ -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)
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user