mirror of https://github.com/yewstack/yew
Remove context & job agent (#2295)
* Remove context and job agent. * Revert "Remove context and job agent." This reverts commit408f6fcdf0
. * Revert "Revert "Remove context and job agent."" This reverts commit44a42dfb31
. * Update example. * Fix docs. * Fix test. * Rename examples. * Fix examples & docs. * Update website/docs/concepts/function-components/custom-hooks.mdx Co-authored-by: Muhammad Hamza <muhammadhamza1311@gmail.com> * Examples in alphabetical order. Co-authored-by: Muhammad Hamza <muhammadhamza1311@gmail.com>
This commit is contained in:
parent
b761487bac
commit
7d52858d01
|
@ -8,7 +8,9 @@ members = [
|
||||||
"packages/yew-router-macro",
|
"packages/yew-router-macro",
|
||||||
|
|
||||||
# Examples
|
# Examples
|
||||||
|
"examples/agents",
|
||||||
"examples/boids",
|
"examples/boids",
|
||||||
|
"examples/contexts",
|
||||||
"examples/counter",
|
"examples/counter",
|
||||||
"examples/dyn_create_destroy_apps",
|
"examples/dyn_create_destroy_apps",
|
||||||
"examples/file_upload",
|
"examples/file_upload",
|
||||||
|
@ -19,14 +21,11 @@ members = [
|
||||||
"examples/js_callback",
|
"examples/js_callback",
|
||||||
"examples/keyed_list",
|
"examples/keyed_list",
|
||||||
"examples/mount_point",
|
"examples/mount_point",
|
||||||
"examples/multi_thread",
|
|
||||||
"examples/nested_list",
|
"examples/nested_list",
|
||||||
"examples/node_refs",
|
"examples/node_refs",
|
||||||
"examples/password_strength",
|
"examples/password_strength",
|
||||||
"examples/portals",
|
"examples/portals",
|
||||||
"examples/pub_sub",
|
|
||||||
"examples/router",
|
"examples/router",
|
||||||
"examples/store",
|
|
||||||
"examples/timer",
|
"examples/timer",
|
||||||
"examples/todomvc",
|
"examples/todomvc",
|
||||||
"examples/two_apps",
|
"examples/two_apps",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "multi_thread"
|
name = "agents"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
|
authors = ["Denis Kolodin <deniskolodin@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
|
@ -0,0 +1,18 @@
|
||||||
|
# Warning
|
||||||
|
|
||||||
|
The agents example is a conceptual WIP and is currently blocked on [a future Trunk feature](https://github.com/thedodd/trunk/issues/46)
|
||||||
|
|
||||||
|
There is an alternate agent example [here](https://github.com/yewstack/yew/tree/master/examples/web_worker_fib).
|
||||||
|
|
||||||
|
|
||||||
|
# Multi-Thread Example
|
||||||
|
|
||||||
|
[](https://examples.yew.rs/agents)
|
||||||
|
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
Uses an [Agent] that runs in a [Web Worker].
|
||||||
|
|
||||||
|
[agent]: https://yew.rs/docs/concepts/agents/
|
||||||
|
[web worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
|
|
@ -1,4 +1,4 @@
|
||||||
fn main() {
|
fn main() {
|
||||||
wasm_logger::init(wasm_logger::Config::default());
|
wasm_logger::init(wasm_logger::Config::default());
|
||||||
yew::start_app::<multi_thread::Model>();
|
yew::start_app::<agents::Model>();
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use multi_thread::native_worker::Worker;
|
use agents::native_worker::Worker;
|
||||||
use yew_agent::Threaded;
|
use yew_agent::Threaded;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
|
@ -0,0 +1,49 @@
|
||||||
|
pub mod native_worker;
|
||||||
|
|
||||||
|
use yew::{html, Component, Context, Html};
|
||||||
|
use yew_agent::{Bridge, Bridged};
|
||||||
|
|
||||||
|
pub enum Msg {
|
||||||
|
SendToWorker,
|
||||||
|
DataReceived,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Model {
|
||||||
|
worker: Box<dyn Bridge<native_worker::Worker>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Model {
|
||||||
|
type Message = Msg;
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
let link = ctx.link();
|
||||||
|
let callback = link.callback(|_| Msg::DataReceived);
|
||||||
|
let worker = native_worker::Worker::bridge(callback);
|
||||||
|
|
||||||
|
Self { worker }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
||||||
|
match msg {
|
||||||
|
Msg::SendToWorker => {
|
||||||
|
self.worker.send(native_worker::Request::GetDataFromServer);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Msg::DataReceived => {
|
||||||
|
log::info!("DataReceived");
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<nav class="menu">
|
||||||
|
<button onclick={ctx.link().callback(|_| Msg::SendToWorker)}>{ "Send to Thread" }</button>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "pub_sub"
|
name = "contexts"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
|
@ -0,0 +1,10 @@
|
||||||
|
# Context Example
|
||||||
|
|
||||||
|
[](https://examples.yew.rs/contexts)
|
||||||
|
|
||||||
|
This is currently a technical demonstration of Context API.
|
||||||
|
|
||||||
|
## Concepts
|
||||||
|
|
||||||
|
The example has two components, which communicates through a context
|
||||||
|
as opposed to the traditional method using component links.
|
|
@ -2,7 +2,7 @@
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<title>Yew • Pub Sub</title>
|
<title>Yew • Context</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body></body>
|
<body></body>
|
|
@ -0,0 +1,23 @@
|
||||||
|
mod msg_ctx;
|
||||||
|
mod producer;
|
||||||
|
mod subscriber;
|
||||||
|
|
||||||
|
use producer::Producer;
|
||||||
|
use subscriber::Subscriber;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use msg_ctx::MessageProvider;
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Model() -> Html {
|
||||||
|
html! {
|
||||||
|
<MessageProvider>
|
||||||
|
<Producer />
|
||||||
|
<Subscriber />
|
||||||
|
</MessageProvider>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
yew::start_app::<Model>();
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
pub struct Message {
|
||||||
|
pub inner: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reducible for Message {
|
||||||
|
type Action = String;
|
||||||
|
|
||||||
|
fn reduce(self: Rc<Self>, action: Self::Action) -> Rc<Self> {
|
||||||
|
Message { inner: action }.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type MessageContext = UseReducerHandle<Message>;
|
||||||
|
|
||||||
|
#[derive(Properties, Debug, PartialEq)]
|
||||||
|
pub struct MessageProviderProps {
|
||||||
|
#[prop_or_default]
|
||||||
|
pub children: Children,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn MessageProvider(props: &MessageProviderProps) -> Html {
|
||||||
|
let msg = use_reducer(|| Message {
|
||||||
|
inner: "No message yet.".to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<ContextProvider<MessageContext> context={msg}>
|
||||||
|
{props.children.clone()}
|
||||||
|
</ContextProvider<MessageContext>>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use super::msg_ctx::MessageContext;
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Producer() -> Html {
|
||||||
|
let msg_ctx = use_context::<MessageContext>().unwrap();
|
||||||
|
|
||||||
|
let onclick = Callback::from(move |_| msg_ctx.dispatch("Message Received.".to_string()));
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<button {onclick}>
|
||||||
|
{"PRESS ME"}
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
use super::msg_ctx::MessageContext;
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Subscriber() -> Html {
|
||||||
|
let msg_ctx = use_context::<MessageContext>().unwrap();
|
||||||
|
|
||||||
|
let message = msg_ctx.inner.to_owned();
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<h1>{ message }</h1>
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,18 +0,0 @@
|
||||||
# Warning
|
|
||||||
|
|
||||||
The multi-thread example is a conceptual WIP and is currently blocked on [a future Trunk feature](https://github.com/thedodd/trunk/issues/46)
|
|
||||||
|
|
||||||
There is an alternate multi-thread example [here](https://github.com/yewstack/yew/tree/master/examples/web_worker_fib).
|
|
||||||
|
|
||||||
|
|
||||||
# Multi-Thread Example
|
|
||||||
|
|
||||||
[](https://examples.yew.rs/multi_thread)
|
|
||||||
|
|
||||||
|
|
||||||
## Concepts
|
|
||||||
|
|
||||||
Uses an [Agent] that runs in a [Web Worker].
|
|
||||||
|
|
||||||
[agent]: https://yew.rs/docs/concepts/agents/
|
|
||||||
[web worker]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
|
|
|
@ -1,61 +0,0 @@
|
||||||
use gloo_timers::callback::Interval;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use yew_agent::{Agent, AgentLink, Context, HandlerId};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Request {
|
|
||||||
GetDataFromServer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Response {
|
|
||||||
DataFetched,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
Updating,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Worker {
|
|
||||||
link: AgentLink<Worker>,
|
|
||||||
_interval: Interval,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Agent for Worker {
|
|
||||||
type Reach = Context<Self>;
|
|
||||||
type Message = Msg;
|
|
||||||
type Input = Request;
|
|
||||||
type Output = Response;
|
|
||||||
|
|
||||||
fn create(link: AgentLink<Self>) -> Self {
|
|
||||||
let duration = 3;
|
|
||||||
|
|
||||||
let interval = {
|
|
||||||
let link = link.clone();
|
|
||||||
Interval::new(duration, move || link.send_message(Msg::Updating))
|
|
||||||
};
|
|
||||||
|
|
||||||
Self {
|
|
||||||
link,
|
|
||||||
_interval: interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) {
|
|
||||||
match msg {
|
|
||||||
Msg::Updating => {
|
|
||||||
log::info!("Tick...");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(&mut self, msg: Self::Input, who: HandlerId) {
|
|
||||||
log::info!("Request: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
Request::GetDataFromServer => {
|
|
||||||
// TODO fetch actual data
|
|
||||||
self.link.respond(who, Response::DataFetched);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
use gloo_timers::callback::Interval;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use yew_agent::{Agent, AgentLink, HandlerId, Job};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Request {
|
|
||||||
GetDataFromServer,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Response {
|
|
||||||
DataFetched,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
Initialized,
|
|
||||||
Updating,
|
|
||||||
DataFetched,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Worker {
|
|
||||||
link: AgentLink<Worker>,
|
|
||||||
_interval: Interval,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Agent for Worker {
|
|
||||||
type Reach = Job<Self>;
|
|
||||||
type Message = Msg;
|
|
||||||
type Input = Request;
|
|
||||||
type Output = Response;
|
|
||||||
|
|
||||||
fn create(link: AgentLink<Self>) -> Self {
|
|
||||||
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,
|
|
||||||
_interval: interval,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) {
|
|
||||||
match msg {
|
|
||||||
Msg::Initialized => {
|
|
||||||
log::info!("Initialized!");
|
|
||||||
}
|
|
||||||
Msg::Updating => {
|
|
||||||
log::info!("Tick...");
|
|
||||||
}
|
|
||||||
Msg::DataFetched => {
|
|
||||||
log::info!("Data was fetched");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(&mut self, msg: Self::Input, who: HandlerId) {
|
|
||||||
log::info!("Request: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
Request::GetDataFromServer => {
|
|
||||||
// TODO fetch actual data
|
|
||||||
self.link.respond(who, Response::DataFetched);
|
|
||||||
self.link.send_message(Msg::DataFetched);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
pub mod context;
|
|
||||||
pub mod job;
|
|
||||||
pub mod native_worker;
|
|
||||||
|
|
||||||
use yew::{html, Component, Context, Html};
|
|
||||||
use yew_agent::{Bridge, Bridged};
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
SendToWorker,
|
|
||||||
SendToJob,
|
|
||||||
SendToContext,
|
|
||||||
DataReceived,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Model {
|
|
||||||
worker: Box<dyn Bridge<native_worker::Worker>>,
|
|
||||||
job: Box<dyn Bridge<job::Worker>>,
|
|
||||||
context: Box<dyn Bridge<context::Worker>>,
|
|
||||||
context_2: Box<dyn Bridge<context::Worker>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Model {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
|
||||||
let link = ctx.link();
|
|
||||||
let callback = link.callback(|_| Msg::DataReceived);
|
|
||||||
let worker = native_worker::Worker::bridge(callback);
|
|
||||||
|
|
||||||
let callback = link.callback(|_| Msg::DataReceived);
|
|
||||||
let job = job::Worker::bridge(callback);
|
|
||||||
|
|
||||||
let callback = link.callback(|_| Msg::DataReceived);
|
|
||||||
let context = context::Worker::bridge(callback);
|
|
||||||
|
|
||||||
let callback = link.callback(|_| Msg::DataReceived);
|
|
||||||
let context_2 = context::Worker::bridge(callback);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
worker,
|
|
||||||
job,
|
|
||||||
context,
|
|
||||||
context_2,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::SendToWorker => {
|
|
||||||
self.worker.send(native_worker::Request::GetDataFromServer);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Msg::SendToJob => {
|
|
||||||
self.job.send(job::Request::GetDataFromServer);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Msg::SendToContext => {
|
|
||||||
self.context.send(context::Request::GetDataFromServer);
|
|
||||||
self.context_2.send(context::Request::GetDataFromServer);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Msg::DataReceived => {
|
|
||||||
log::info!("DataReceived");
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
<nav class="menu">
|
|
||||||
<button onclick={ctx.link().callback(|_| Msg::SendToWorker)}>{ "Send to Thread" }</button>
|
|
||||||
<button onclick={ctx.link().callback(|_| Msg::SendToJob)}>{ "Send to Job" }</button>
|
|
||||||
<button onclick={ctx.link().callback(|_| Msg::SendToContext)}>{ "Send to Context" }</button>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
# Pub Sub Example
|
|
||||||
|
|
||||||
[](https://examples.yew.rs/pub_sub)
|
|
||||||
|
|
||||||
This is currently a technical demonstration of agents.
|
|
||||||
|
|
||||||
## Concepts
|
|
||||||
|
|
||||||
The example has two components, which communicate through a "broker" agent
|
|
||||||
as opposed to the traditional method using component links.
|
|
||||||
|
|
||||||
## Improvements
|
|
||||||
|
|
||||||
As it stands, this example uses a great amount of code to do very little.
|
|
||||||
The concept should be applied to a more elaborate use case.
|
|
||||||
|
|
||||||
This could also be merged into the [nested_list](../nested_list) example to remove the need for `WeakComponentLink`.
|
|
|
@ -1,47 +0,0 @@
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use yew_agent::{Agent, AgentLink, Context, HandlerId};
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum Request {
|
|
||||||
EventBusMsg(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct EventBus {
|
|
||||||
link: AgentLink<EventBus>,
|
|
||||||
subscribers: HashSet<HandlerId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Agent for EventBus {
|
|
||||||
type Reach = Context<Self>;
|
|
||||||
type Message = ();
|
|
||||||
type Input = Request;
|
|
||||||
type Output = String;
|
|
||||||
|
|
||||||
fn create(link: AgentLink<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
link,
|
|
||||||
subscribers: HashSet::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) {}
|
|
||||||
|
|
||||||
fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) {
|
|
||||||
match msg {
|
|
||||||
Request::EventBusMsg(s) => {
|
|
||||||
for sub in self.subscribers.iter() {
|
|
||||||
self.link.respond(*sub, s.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connected(&mut self, id: HandlerId) {
|
|
||||||
self.subscribers.insert(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disconnected(&mut self, id: HandlerId) {
|
|
||||||
self.subscribers.remove(&id);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
mod event_bus;
|
|
||||||
mod producer;
|
|
||||||
mod subscriber;
|
|
||||||
|
|
||||||
use producer::Producer;
|
|
||||||
use subscriber::Subscriber;
|
|
||||||
use yew::{html, Component, Context, Html};
|
|
||||||
|
|
||||||
pub struct Model;
|
|
||||||
|
|
||||||
impl Component for Model {
|
|
||||||
type Message = ();
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<Producer />
|
|
||||||
<Subscriber />
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
yew::start_app::<Model>();
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
use crate::event_bus::{EventBus, Request};
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yew_agent::{Dispatched, Dispatcher};
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
Clicked,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Producer {
|
|
||||||
event_bus: Dispatcher<EventBus>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Producer {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
event_bus: EventBus::dispatcher(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::Clicked => {
|
|
||||||
self.event_bus
|
|
||||||
.send(Request::EventBusMsg("Message received".to_owned()));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
html! {
|
|
||||||
<button onclick={ctx.link().callback(|_| Msg::Clicked)}>
|
|
||||||
{ "PRESS ME" }
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
use super::event_bus::EventBus;
|
|
||||||
use yew::{html, Component, Context, Html};
|
|
||||||
use yew_agent::{Bridge, Bridged};
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
NewMessage(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Subscriber {
|
|
||||||
message: String,
|
|
||||||
_producer: Box<dyn Bridge<EventBus>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Subscriber {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
message: "No message yet.".to_owned(),
|
|
||||||
_producer: EventBus::bridge(ctx.link().callback(Msg::NewMessage)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::NewMessage(s) => {
|
|
||||||
self.message = s;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
|
||||||
html! {
|
|
||||||
<h1>{ &self.message }</h1>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
[package]
|
|
||||||
name = "store"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["Michał Kawalec <michal@monad.cat>"]
|
|
||||||
edition = "2018"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
yew = { path = "../../packages/yew" }
|
|
||||||
yew-agent = { path = "../../packages/yew-agent" }
|
|
||||||
wasm-bindgen = "0.2"
|
|
||||||
gloo-console = "0.2"
|
|
||||||
|
|
||||||
[dependencies.web-sys]
|
|
||||||
version = "0.3"
|
|
||||||
features = [
|
|
||||||
"HtmlInputElement",
|
|
||||||
]
|
|
|
@ -1,16 +0,0 @@
|
||||||
# Store Example
|
|
||||||
|
|
||||||
[](https://examples.yew.rs/store)
|
|
||||||
|
|
||||||
A timeline of posts that can be independently updated.
|
|
||||||
|
|
||||||
## Concepts
|
|
||||||
|
|
||||||
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/yew_agent/latest/
|
|
|
@ -1,9 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Yew • Store</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body></body>
|
|
||||||
</html>
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod posts;
|
|
|
@ -1,76 +0,0 @@
|
||||||
use std::collections::HashMap;
|
|
||||||
use yew_agent::utils::store::{Store, StoreWrapper};
|
|
||||||
use yew_agent::AgentLink;
|
|
||||||
|
|
||||||
pub type PostId = u32;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum PostRequest {
|
|
||||||
Create(String),
|
|
||||||
Update(PostId, String),
|
|
||||||
Remove(PostId),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum Action {
|
|
||||||
SetPost(Option<PostId>, String),
|
|
||||||
RemovePost(PostId),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PostStore {
|
|
||||||
pub posts: HashMap<PostId, String>,
|
|
||||||
// Stores can have private state too
|
|
||||||
id_counter: PostId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Store for PostStore {
|
|
||||||
type Action = Action;
|
|
||||||
type Input = PostRequest;
|
|
||||||
|
|
||||||
fn new() -> Self {
|
|
||||||
let mut posts = HashMap::new();
|
|
||||||
|
|
||||||
// We insert one post to show the initial send of state
|
|
||||||
// when a bridge is opened.
|
|
||||||
posts.insert(0, "Magic first post".to_owned());
|
|
||||||
|
|
||||||
PostStore {
|
|
||||||
posts,
|
|
||||||
id_counter: 1,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(&self, link: AgentLink<StoreWrapper<Self>>, msg: Self::Input) {
|
|
||||||
match msg {
|
|
||||||
PostRequest::Create(text) => {
|
|
||||||
link.send_message(Action::SetPost(None, text));
|
|
||||||
}
|
|
||||||
PostRequest::Update(id, text) => {
|
|
||||||
link.send_message(Action::SetPost(Some(id), text));
|
|
||||||
}
|
|
||||||
PostRequest::Remove(id) => {
|
|
||||||
link.send_message(Action::RemovePost(id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reduce(&mut self, msg: Self::Action) {
|
|
||||||
match msg {
|
|
||||||
Action::SetPost(id, text) => {
|
|
||||||
let id = id.unwrap_or_else(|| self.next_id());
|
|
||||||
self.posts.insert(id, text);
|
|
||||||
}
|
|
||||||
Action::RemovePost(id) => {
|
|
||||||
self.posts.remove(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PostStore {
|
|
||||||
fn next_id(&mut self) -> PostId {
|
|
||||||
let tmp = self.id_counter;
|
|
||||||
self.id_counter += 1;
|
|
||||||
tmp
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
mod agents;
|
|
||||||
mod post;
|
|
||||||
mod text_input;
|
|
||||||
|
|
||||||
use agents::posts::{PostId, PostRequest, PostStore};
|
|
||||||
use gloo_console as console;
|
|
||||||
use post::Post;
|
|
||||||
use text_input::TextInput;
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yew_agent::utils::store::{Bridgeable, ReadOnly, StoreWrapper};
|
|
||||||
use yew_agent::Bridge;
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
CreatePost(String),
|
|
||||||
PostStoreMsg(ReadOnly<PostStore>),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Model {
|
|
||||||
post_ids: Vec<PostId>,
|
|
||||||
post_store: Box<dyn Bridge<StoreWrapper<PostStore>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Model {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
|
||||||
let callback = ctx.link().callback(Msg::PostStoreMsg);
|
|
||||||
Self {
|
|
||||||
post_ids: Vec::new(),
|
|
||||||
post_store: PostStore::bridge(callback),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::CreatePost(text) => {
|
|
||||||
self.post_store.send(PostRequest::Create(text));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
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.
|
|
||||||
console::log!("Received update");
|
|
||||||
|
|
||||||
let state = state.borrow();
|
|
||||||
if state.posts.len() != self.post_ids.len() {
|
|
||||||
self.post_ids = state.posts.keys().copied().collect();
|
|
||||||
self.post_ids.sort_unstable();
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
html! {
|
|
||||||
<>
|
|
||||||
<TextInput value="New post" onsubmit={ctx.link().callback(Msg::CreatePost)} />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{ for self.post_ids.iter().map(|&id| html!{ <Post key={id} {id} /> }) }
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn main() {
|
|
||||||
yew::start_app::<Model>();
|
|
||||||
}
|
|
|
@ -1,85 +0,0 @@
|
||||||
use crate::agents::posts::{PostId, PostRequest, PostStore};
|
|
||||||
use crate::text_input::TextInput;
|
|
||||||
use yew::prelude::*;
|
|
||||||
use yew_agent::utils::store::{Bridgeable, ReadOnly, StoreWrapper};
|
|
||||||
use yew_agent::Bridge;
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
UpdateText(String),
|
|
||||||
Delete,
|
|
||||||
PostStore(ReadOnly<PostStore>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, Clone, PartialEq)]
|
|
||||||
pub struct Props {
|
|
||||||
pub id: PostId,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Post {
|
|
||||||
id: PostId,
|
|
||||||
text: Option<String>,
|
|
||||||
post_store: Box<dyn Bridge<StoreWrapper<PostStore>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for Post {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = Props;
|
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
|
||||||
let callback = ctx.link().callback(Msg::PostStore);
|
|
||||||
Self {
|
|
||||||
id: ctx.props().id,
|
|
||||||
text: None,
|
|
||||||
post_store: PostStore::bridge(callback),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::UpdateText(text) => {
|
|
||||||
self.post_store.send(PostRequest::Update(self.id, text));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Msg::Delete => {
|
|
||||||
self.post_store.send(PostRequest::Remove(self.id));
|
|
||||||
false
|
|
||||||
}
|
|
||||||
Msg::PostStore(state) => {
|
|
||||||
let state = state.borrow();
|
|
||||||
|
|
||||||
// Only update if the post changed.
|
|
||||||
if let Some(text) = state.posts.get(&self.id) {
|
|
||||||
if self.text.as_ref().map(|it| *it != *text).unwrap_or(false) {
|
|
||||||
self.text = Some(text.clone());
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
|
||||||
self.id = ctx.props().id;
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
let text = self.text.as_deref().unwrap_or("<pending>");
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<div>
|
|
||||||
<h2>{ format!("Post #{}", self.id) }</h2>
|
|
||||||
<p>{text}</p>
|
|
||||||
|
|
||||||
<TextInput value={text.to_owned()} onsubmit={ctx.link().callback(Msg::UpdateText)} />
|
|
||||||
<button onclick={ctx.link().callback(|_| Msg::Delete)}>
|
|
||||||
{ "Delete" }
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
use web_sys::HtmlInputElement;
|
|
||||||
use yew::prelude::*;
|
|
||||||
|
|
||||||
pub enum Msg {
|
|
||||||
Submit(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Properties, Clone, PartialEq)]
|
|
||||||
pub struct Props {
|
|
||||||
pub value: String,
|
|
||||||
pub onsubmit: Callback<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TextInput {
|
|
||||||
text: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for TextInput {
|
|
||||||
type Message = Msg;
|
|
||||||
type Properties = Props;
|
|
||||||
|
|
||||||
fn create(ctx: &Context<Self>) -> Self {
|
|
||||||
Self {
|
|
||||||
text: ctx.props().value.clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
match msg {
|
|
||||||
Msg::Submit(text) => {
|
|
||||||
ctx.props().onsubmit.emit(text);
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn changed(&mut self, ctx: &Context<Self>) -> bool {
|
|
||||||
self.text = ctx.props().value.clone();
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
let onkeydown = ctx.link().batch_callback(|e: KeyboardEvent| {
|
|
||||||
e.stop_propagation();
|
|
||||||
if e.key() == "Enter" {
|
|
||||||
let input: HtmlInputElement = e.target_unchecked_into();
|
|
||||||
let value = input.value();
|
|
||||||
input.set_value("");
|
|
||||||
Some(Msg::Submit(value))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
html! {
|
|
||||||
<input
|
|
||||||
placeholder={ctx.props().value.clone()}
|
|
||||||
type="text"
|
|
||||||
{onkeydown}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,15 +2,12 @@
|
||||||
|
|
||||||
mod hooks;
|
mod hooks;
|
||||||
mod link;
|
mod link;
|
||||||
mod local;
|
|
||||||
mod pool;
|
mod pool;
|
||||||
pub mod utils;
|
|
||||||
mod worker;
|
mod worker;
|
||||||
|
|
||||||
pub use hooks::{use_bridge, UseBridgeHandle};
|
pub use hooks::{use_bridge, UseBridgeHandle};
|
||||||
pub use link::AgentLink;
|
pub use link::AgentLink;
|
||||||
pub(crate) use link::*;
|
pub(crate) use link::*;
|
||||||
pub use local::{Context, Job};
|
|
||||||
pub(crate) use pool::*;
|
pub(crate) use pool::*;
|
||||||
pub use pool::{Dispatched, Dispatcher};
|
pub use pool::{Dispatched, Dispatcher};
|
||||||
pub use worker::{Private, Public, Threaded};
|
pub use worker::{Private, Public, Threaded};
|
||||||
|
|
|
@ -1,141 +0,0 @@
|
||||||
use super::*;
|
|
||||||
use anymap2::{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());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Create a single instance in the current thread.
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct Context<AGN> {
|
|
||||||
_agent: PhantomData<AGN>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN> Discoverer for Context<AGN>
|
|
||||||
where
|
|
||||||
AGN: Agent,
|
|
||||||
{
|
|
||||||
type Agent = AGN;
|
|
||||||
|
|
||||||
fn spawn_or_join(callback: Option<Callback<AGN::Output>>) -> Box<dyn Bridge<AGN>> {
|
|
||||||
let mut scope_to_init = None;
|
|
||||||
let bridge = LOCAL_AGENTS_POOL.with(|pool| {
|
|
||||||
let mut pool = pool.borrow_mut();
|
|
||||||
match pool.entry::<LocalAgent<AGN>>() {
|
|
||||||
anymap2::Entry::Occupied(mut entry) => entry.get_mut().create_bridge(callback),
|
|
||||||
anymap2::Entry::Vacant(entry) => {
|
|
||||||
let scope = AgentScope::<AGN>::new();
|
|
||||||
let launched = LocalAgent::new(&scope);
|
|
||||||
let responder = SlabResponder {
|
|
||||||
slab: launched.slab(),
|
|
||||||
};
|
|
||||||
scope_to_init = Some((scope, responder));
|
|
||||||
entry.insert(launched).create_bridge(callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if let Some((scope, responder)) = scope_to_init {
|
|
||||||
let agent_link = AgentLink::connect(&scope, responder);
|
|
||||||
let upd = AgentLifecycleEvent::Create(agent_link);
|
|
||||||
scope.send(upd);
|
|
||||||
}
|
|
||||||
let upd = AgentLifecycleEvent::Connected(bridge.id);
|
|
||||||
bridge.scope.send(upd);
|
|
||||||
Box::new(bridge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SlabResponder<AGN: Agent> {
|
|
||||||
slab: Shared<Slab<Option<Callback<AGN::Output>>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Responder<AGN> for SlabResponder<AGN> {
|
|
||||||
fn respond(&self, id: HandlerId, output: AGN::Output) {
|
|
||||||
locate_callback_and_respond::<AGN>(&self.slab, id, output);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Dispatchable for Context<AGN> {}
|
|
||||||
|
|
||||||
struct ContextBridge<AGN: Agent> {
|
|
||||||
scope: AgentScope<AGN>,
|
|
||||||
id: HandlerId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Bridge<AGN> for ContextBridge<AGN> {
|
|
||||||
fn send(&mut self, msg: AGN::Input) {
|
|
||||||
let upd = AgentLifecycleEvent::Input(msg, self.id);
|
|
||||||
self.scope.send(upd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Drop for ContextBridge<AGN> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let terminate_worker = LOCAL_AGENTS_POOL.with(|pool| {
|
|
||||||
let mut pool = pool.borrow_mut();
|
|
||||||
let terminate_worker = {
|
|
||||||
if let Some(launched) = pool.get_mut::<LocalAgent<AGN>>() {
|
|
||||||
launched.remove_bridge(self)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if terminate_worker {
|
|
||||||
pool.remove::<LocalAgent<AGN>>();
|
|
||||||
}
|
|
||||||
|
|
||||||
terminate_worker
|
|
||||||
});
|
|
||||||
|
|
||||||
let upd = AgentLifecycleEvent::Disconnected(self.id);
|
|
||||||
self.scope.send(upd);
|
|
||||||
|
|
||||||
if terminate_worker {
|
|
||||||
let upd = AgentLifecycleEvent::Destroy;
|
|
||||||
self.scope.send(upd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct LocalAgent<AGN: Agent> {
|
|
||||||
scope: AgentScope<AGN>,
|
|
||||||
slab: SharedOutputSlab<AGN>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> LocalAgent<AGN> {
|
|
||||||
pub fn new(scope: &AgentScope<AGN>) -> Self {
|
|
||||||
let slab = Rc::new(RefCell::new(Slab::new()));
|
|
||||||
LocalAgent {
|
|
||||||
scope: scope.clone(),
|
|
||||||
slab,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slab(&self) -> SharedOutputSlab<AGN> {
|
|
||||||
self.slab.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_bridge(&mut self, callback: Option<Callback<AGN::Output>>) -> ContextBridge<AGN> {
|
|
||||||
let respondable = callback.is_some();
|
|
||||||
let mut slab = self.slab.borrow_mut();
|
|
||||||
let id: usize = slab.insert(callback);
|
|
||||||
let id = HandlerId::new(id, respondable);
|
|
||||||
ContextBridge {
|
|
||||||
scope: self.scope.clone(),
|
|
||||||
id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_bridge(&mut self, bridge: &ContextBridge<AGN>) -> Last {
|
|
||||||
let mut slab = self.slab.borrow_mut();
|
|
||||||
let _ = slab.remove(bridge.id.raw_id());
|
|
||||||
slab.is_empty()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
use super::*;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use yew::callback::Callback;
|
|
||||||
|
|
||||||
const SINGLETON_ID: HandlerId = HandlerId(0, true);
|
|
||||||
|
|
||||||
/// Create an instance in the current thread.
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct Job<AGN> {
|
|
||||||
_agent: PhantomData<AGN>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN> Discoverer for Job<AGN>
|
|
||||||
where
|
|
||||||
AGN: Agent,
|
|
||||||
{
|
|
||||||
type Agent = AGN;
|
|
||||||
|
|
||||||
fn spawn_or_join(callback: Option<Callback<AGN::Output>>) -> Box<dyn Bridge<AGN>> {
|
|
||||||
let callback = callback.expect("Callback required for Job");
|
|
||||||
let scope = AgentScope::<AGN>::new();
|
|
||||||
let responder = CallbackResponder { callback };
|
|
||||||
let agent_link = AgentLink::connect(&scope, responder);
|
|
||||||
let upd = AgentLifecycleEvent::Create(agent_link);
|
|
||||||
scope.send(upd);
|
|
||||||
let upd = AgentLifecycleEvent::Connected(SINGLETON_ID);
|
|
||||||
scope.send(upd);
|
|
||||||
let bridge = JobBridge { scope };
|
|
||||||
Box::new(bridge)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct JobBridge<AGN: Agent> {
|
|
||||||
scope: AgentScope<AGN>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Bridge<AGN> for JobBridge<AGN> {
|
|
||||||
fn send(&mut self, msg: AGN::Input) {
|
|
||||||
let upd = AgentLifecycleEvent::Input(msg, SINGLETON_ID);
|
|
||||||
self.scope.send(upd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Drop for JobBridge<AGN> {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let upd = AgentLifecycleEvent::Disconnected(SINGLETON_ID);
|
|
||||||
self.scope.send(upd);
|
|
||||||
let upd = AgentLifecycleEvent::Destroy;
|
|
||||||
self.scope.send(upd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct CallbackResponder<AGN: Agent> {
|
|
||||||
callback: Callback<AGN::Output>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<AGN: Agent> Responder<AGN> for CallbackResponder<AGN> {
|
|
||||||
fn respond(&self, id: HandlerId, output: AGN::Output) {
|
|
||||||
assert_eq!(id.raw_id(), SINGLETON_ID.raw_id());
|
|
||||||
self.callback.emit(output);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
mod context;
|
|
||||||
mod job;
|
|
||||||
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
pub use context::Context;
|
|
||||||
pub use job::Job;
|
|
|
@ -1 +0,0 @@
|
||||||
pub mod store;
|
|
|
@ -1,162 +0,0 @@
|
||||||
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::prelude::*;
|
|
||||||
|
|
||||||
/// A functional state wrapper, enforcing a unidirectional
|
|
||||||
/// data flow and consistent state to the observers.
|
|
||||||
///
|
|
||||||
/// `handle_input` receives incoming messages from components,
|
|
||||||
/// `reduce` applies changes to the state
|
|
||||||
///
|
|
||||||
/// The state is sent once whenever a bridge is opened and then once
|
|
||||||
/// for each `Action` sent by the `handle_input` function. This means
|
|
||||||
/// the initial state of the store must be valid for the consumers.
|
|
||||||
///
|
|
||||||
/// Once created with a first bridge, a Store will never be destroyed
|
|
||||||
/// for the lifetime of the application.
|
|
||||||
pub trait Store: Sized + 'static {
|
|
||||||
/// Messages instructing the store to do somethin
|
|
||||||
type Input;
|
|
||||||
/// State updates to be consumed by `reduce`
|
|
||||||
type Action;
|
|
||||||
|
|
||||||
/// Create a new Store
|
|
||||||
fn new() -> Self;
|
|
||||||
|
|
||||||
/// Receives messages from components and other agents. Use the `link`
|
|
||||||
/// to send actions to itself in order to notify `reduce` once your
|
|
||||||
/// operation completes. This is the place to do side effects, like
|
|
||||||
/// talking to the server, or asking the user for input.
|
|
||||||
///
|
|
||||||
/// Note that you can look at the state of your Store, but you
|
|
||||||
/// cannot modify it here. If you want to modify it, send a Message
|
|
||||||
/// to the reducer
|
|
||||||
fn handle_input(&self, link: AgentLink<StoreWrapper<Self>>, msg: Self::Input);
|
|
||||||
|
|
||||||
/// A pure function, with no side effects. Receives a message,
|
|
||||||
/// and applies it to the state as it sees fit.
|
|
||||||
fn reduce(&mut self, msg: Self::Action);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Hides the full context Agent from a Store and does
|
|
||||||
/// the boring data wrangling logic
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct StoreWrapper<S: Store> {
|
|
||||||
/// Currently subscribed components and agents
|
|
||||||
pub handlers: HashSet<HandlerId>,
|
|
||||||
/// Link to itself so Store::handle_input can send actions to reducer
|
|
||||||
pub link: AgentLink<Self>,
|
|
||||||
|
|
||||||
/// The actual Store
|
|
||||||
pub state: Shared<S>,
|
|
||||||
|
|
||||||
/// A circular dispatcher to itself so the store is not removed
|
|
||||||
pub self_dispatcher: Dispatcher<Self>,
|
|
||||||
}
|
|
||||||
|
|
||||||
type Shared<T> = Rc<RefCell<T>>;
|
|
||||||
|
|
||||||
/// A wrapper ensuring state observers can only
|
|
||||||
/// borrow the state immutably
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ReadOnly<S> {
|
|
||||||
state: Shared<S>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S> ReadOnly<S> {
|
|
||||||
/// Allow only immutable borrows to the underlying data
|
|
||||||
pub fn borrow(&self) -> impl Deref<Target = S> + '_ {
|
|
||||||
self.state.borrow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This is a wrapper, intended to be used as an opaque
|
|
||||||
/// machinery allowing the Store to do it's things.
|
|
||||||
impl<S: Store> Agent for StoreWrapper<S> {
|
|
||||||
type Reach = Context<Self>;
|
|
||||||
type Message = S::Action;
|
|
||||||
type Input = S::Input;
|
|
||||||
type Output = ReadOnly<S>;
|
|
||||||
|
|
||||||
fn create(link: AgentLink<Self>) -> Self {
|
|
||||||
let state = Rc::new(RefCell::new(S::new()));
|
|
||||||
let handlers = HashSet::new();
|
|
||||||
|
|
||||||
// Link to self to never go out of scope
|
|
||||||
let self_dispatcher = Self::dispatcher();
|
|
||||||
|
|
||||||
StoreWrapper {
|
|
||||||
handlers,
|
|
||||||
link,
|
|
||||||
state,
|
|
||||||
self_dispatcher,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, msg: Self::Message) {
|
|
||||||
{
|
|
||||||
self.state.borrow_mut().reduce(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
for handler in self.handlers.iter() {
|
|
||||||
self.link.respond(
|
|
||||||
*handler,
|
|
||||||
ReadOnly {
|
|
||||||
state: self.state.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connected(&mut self, id: HandlerId) {
|
|
||||||
self.handlers.insert(id);
|
|
||||||
self.link.respond(
|
|
||||||
id,
|
|
||||||
ReadOnly {
|
|
||||||
state: self.state.clone(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(&mut self, msg: Self::Input, _id: HandlerId) {
|
|
||||||
self.state.borrow().handle_input(self.link.clone(), msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn disconnected(&mut self, id: HandlerId) {
|
|
||||||
self.handlers.remove(&id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This instance is quite unfortunate, as the Rust compiler
|
|
||||||
// does not support mutually exclusive trait bounds (https://github.com/rust-lang/rust/issues/51774),
|
|
||||||
// we have to create a new trait with the same function as in the original one.
|
|
||||||
|
|
||||||
/// Allows us to communicate with a store
|
|
||||||
pub trait Bridgeable: Sized + 'static {
|
|
||||||
/// A wrapper for the store we want to bridge to,
|
|
||||||
/// which serves as a communication intermediary
|
|
||||||
type Wrapper: Agent;
|
|
||||||
|
|
||||||
/// Creates a messaging bridge between a worker and the component.
|
|
||||||
fn bridge(
|
|
||||||
callback: Callback<<Self::Wrapper as Agent>::Output>,
|
|
||||||
) -> Box<dyn Bridge<Self::Wrapper>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Implementation of bridge creation
|
|
||||||
impl<T> Bridgeable for T
|
|
||||||
where
|
|
||||||
T: Store,
|
|
||||||
{
|
|
||||||
/// The hiding wrapper
|
|
||||||
type Wrapper = StoreWrapper<T>;
|
|
||||||
|
|
||||||
fn bridge(
|
|
||||||
callback: Callback<<Self::Wrapper as Agent>::Output>,
|
|
||||||
) -> Box<dyn Bridge<Self::Wrapper>> {
|
|
||||||
<Self::Wrapper as Agent>::Reach::spawn_or_join(Some(callback))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,8 +11,7 @@ yew-agent = { path = "../../packages/yew-agent/" }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
boolinator = "2.4"
|
boolinator = "2.4"
|
||||||
derive_more = "0.99"
|
derive_more = "0.99"
|
||||||
gloo-events = "0.1"
|
gloo = "0.5"
|
||||||
gloo-utils = "0.1"
|
|
||||||
js-sys = "0.3"
|
js-sys = "0.3"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
wasm-bindgen-futures = "0.4"
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
|
@ -1,50 +0,0 @@
|
||||||
//! Agent types that compile to be used by website code snippets
|
|
||||||
|
|
||||||
use yew_agent::{Agent, AgentLink, Context, HandlerId};
|
|
||||||
|
|
||||||
pub struct EventBus;
|
|
||||||
|
|
||||||
impl Agent for EventBus {
|
|
||||||
type Reach = Context<Self>;
|
|
||||||
type Message = ();
|
|
||||||
type Input = ();
|
|
||||||
type Output = String;
|
|
||||||
|
|
||||||
fn create(_link: AgentLink<Self>) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) {
|
|
||||||
// impl
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(&mut self, _msg: Self::Input, _id: HandlerId) {
|
|
||||||
// impl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum WorkerMsg {
|
|
||||||
Process,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MyWorker;
|
|
||||||
|
|
||||||
impl Agent for MyWorker {
|
|
||||||
type Reach = Context<Self>;
|
|
||||||
|
|
||||||
type Message = ();
|
|
||||||
type Input = WorkerMsg;
|
|
||||||
type Output = ();
|
|
||||||
|
|
||||||
fn create(_link: AgentLink<Self>) -> Self {
|
|
||||||
Self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _msg: Self::Message) {
|
|
||||||
// impl
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_input(&mut self, _msg: Self::Input, _id: HandlerId) {
|
|
||||||
// impl
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,4 +1,3 @@
|
||||||
pub mod agents;
|
|
||||||
pub mod tutorial;
|
pub mod tutorial;
|
||||||
|
|
||||||
include!(concat!(env!("OUT_DIR"), "/website_tests.rs"));
|
include!(concat!(env!("OUT_DIR"), "/website_tests.rs"));
|
||||||
|
|
|
@ -26,7 +26,7 @@ pub struct ModalProps {
|
||||||
|
|
||||||
#[function_component(Modal)]
|
#[function_component(Modal)]
|
||||||
fn modal(props: &ModalProps) -> Html {
|
fn modal(props: &ModalProps) -> Html {
|
||||||
let modal_host = gloo_utils::document()
|
let modal_host = gloo::utils::document()
|
||||||
.get_element_by_id("modal_host")
|
.get_element_by_id("modal_host")
|
||||||
.expect("a #modal_host element");
|
.expect("a #modal_host element");
|
||||||
|
|
||||||
|
|
|
@ -6,13 +6,7 @@ description: "Yew's Actor System"
|
||||||
import useBaseUrl from "@docusaurus/useBaseUrl";
|
import useBaseUrl from "@docusaurus/useBaseUrl";
|
||||||
import ThemedImage from '@theme/ThemedImage';
|
import ThemedImage from '@theme/ThemedImage';
|
||||||
|
|
||||||
Agents are similar to Angular's [Services](https://angular.io/guide/architecture-services)
|
Agents are a way to offload tasks to web workers or achieve inter-tab communication(WIP).
|
||||||
\(but without dependency injection\), and provide Yew with an
|
|
||||||
[Actor Model](https://en.wikipedia.org/wiki/Actor_model). Agents can be used to route messages
|
|
||||||
between components independently of where they sit in the component hierarchy, or they can be used
|
|
||||||
to create shared state between different components. Agents can also be used to offload
|
|
||||||
computationally expensive tasks from the main thread which renders the UI. There is also planned
|
|
||||||
support for using agents to allow Yew applications to communicate across tabs \(in the future\).
|
|
||||||
|
|
||||||
In order for agents to run concurrently, Yew uses
|
In order for agents to run concurrently, Yew uses
|
||||||
[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).
|
[web-workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers).
|
||||||
|
@ -37,19 +31,14 @@ The code can be found in the <desc> tag of the svgs.
|
||||||
|
|
||||||
### Reaches
|
### Reaches
|
||||||
|
|
||||||
* Context - There will exist at most one instance of a Context Agent at any given time. Bridges will
|
* Public - There will exist at most one instance of a Public Agent at any given time. Bridges will
|
||||||
spawn or connect to an already spawned agent on the UI thread. This can be used to coordinate
|
spawn or connect to an already spawned agent in a web worker.
|
||||||
state between components or other agents. When no bridges are connected to this agent, the agent
|
When no bridges are connected to this agent, the agent will disappear.
|
||||||
will disappear.
|
|
||||||
|
|
||||||
* Job - Spawn a new agent on the UI thread for every new bridge. This is good for moving shared but
|
* Private - Spawn a new agent in a web worker for every new bridge. This is good for moving shared but
|
||||||
independent behavior that communicates with the browser out of components. \(TODO verify\) When
|
independent behavior that communicates with the browser out of components. \(TODO verify\) When
|
||||||
the task is done, the agent will disappear.
|
the task is done, the agent will disappear.
|
||||||
|
|
||||||
* Public - Same as Context, but runs on its own web worker.
|
|
||||||
|
|
||||||
* Private - Same as Job, but runs on its own web worker.
|
|
||||||
|
|
||||||
* Global \(WIP\)
|
* Global \(WIP\)
|
||||||
|
|
||||||
## Communication between Agents and Components
|
## Communication between Agents and Components
|
||||||
|
@ -66,13 +55,11 @@ A dispatcher allows uni-directional communication between a component and an age
|
||||||
|
|
||||||
## Overhead
|
## Overhead
|
||||||
|
|
||||||
Agents that use web workers \(i.e. Private and Public\) will incur a serialization overhead on the
|
Agents use web workers \(i.e. Private and Public\). They incur a serialization overhead on the
|
||||||
messages they send and receive. They use [bincode](https://github.com/servo/bincode) to communicate
|
messages they send and receive. Agents use [bincode](https://github.com/servo/bincode) to communicate
|
||||||
with other threads, so the cost is substantially higher than just calling a function. Unless the
|
with other threads, so the cost is substantially higher than just calling a function.
|
||||||
cost of computation will outweigh the cost of message passing, you should use agents running on the
|
|
||||||
UI thread \(i.e. Job or Context\).
|
|
||||||
|
|
||||||
## Further reading
|
## Further reading
|
||||||
|
|
||||||
* The [pub\_sub](https://github.com/yewstack/yew/tree/master/examples/pub_sub) example shows how
|
* The [multi\_thread](https://github.com/yewstack/yew/tree/master/examples/multi_thread) example shows how
|
||||||
components can use agents to communicate with each other.
|
components can send message to and receive message from agents.
|
||||||
|
|
|
@ -5,84 +5,114 @@ description: "Defining your own Hooks "
|
||||||
|
|
||||||
## Defining custom Hooks
|
## Defining custom Hooks
|
||||||
|
|
||||||
Component's stateful logic can be extracted into usable function by creating custom Hooks.
|
Component's stateful logic can be extracted into usable function by creating custom Hooks
|
||||||
|
|
||||||
|
Consider that we wish to create an event listener that listens to an event on the `window`
|
||||||
|
object.
|
||||||
|
|
||||||
Consider that we have a component which subscribes to an agent and displays the messages sent to it.
|
|
||||||
```rust
|
```rust
|
||||||
use yew::{function_component, html, use_effect, use_state, Callback};
|
use yew::prelude::*;
|
||||||
use yew_agent::Bridged;
|
use gloo::events::EventListener;
|
||||||
// EventBus is an implementation yew_agent::Agent
|
use gloo::utils::window;
|
||||||
use website_test::agents::EventBus;
|
use std::mem::drop;
|
||||||
|
|
||||||
|
|
||||||
#[function_component(ShowMessages)]
|
#[function_component(ShowStorageChanged)]
|
||||||
pub fn show_messages() -> Html {
|
pub fn show_storage_changed() -> Html {
|
||||||
let state = use_state(Vec::new);
|
let state_storage_changed = use_state(|| false);
|
||||||
|
|
||||||
{
|
{
|
||||||
let state = state.clone();
|
let state_storage_changed = state_storage_changed.clone();
|
||||||
use_effect(move || {
|
use_effect(|| {
|
||||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
let listener = EventListener::new(&window(), "storage", move |_| state_storage_changed.set(true));
|
||||||
let mut messages = (*state).clone();
|
|
||||||
messages.push(msg);
|
|
||||||
state.set(messages)
|
|
||||||
}));
|
|
||||||
|
|
||||||
|| drop(producer)
|
move || { drop(listener); }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let output = state.iter().map(|it| html! { <p>{ it }</p> });
|
html! { <div>{"Storage Event Fired: "}{*state_storage_changed}</div> }
|
||||||
html! { <div>{ for output }</div> }
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
There's one problem with this code: the logic can't be reused by another component.
|
There's one problem with this code: the logic can't be reused by another component.
|
||||||
If we build another component which keeps track of the messages, instead of copying the code we can move the logic into a custom hook.
|
If we build another component which keeps track of the an event,
|
||||||
|
instead of copying the code we can move the logic into a custom hook.
|
||||||
|
|
||||||
We'll start by creating a new function called `use_subscribe`.
|
We'll start by creating a new function called `use_event`.
|
||||||
The `use_` prefix conventionally denotes that a function is a hook.
|
The `use_` prefix conventionally denotes that a function is a hook.
|
||||||
This function will take no arguments and return `Rc<RefCell<Vec<String>>>`.
|
This function will take an event target, a event type and a callback.
|
||||||
```rust
|
```rust
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use web_sys::{Event, EventTarget};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use gloo::events::EventListener;
|
||||||
|
|
||||||
fn use_subscribe() -> Rc<RefCell<Vec<String>>> {
|
pub fn use_event<E, F>(target: &EventTarget, event_type: E, callback: F)
|
||||||
|
where
|
||||||
|
E: Into<Cow<'static, str>>,
|
||||||
|
F: Fn(&Event) + 'static,
|
||||||
|
{
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This is a simple hook which can be created by combining other hooks. For this example, we'll two pre-defined hooks.
|
This is a simple hook which can be created by using built-in hooks. For this example, we'll use the `use_effect_with_deps` hook,
|
||||||
We'll use `use_state` hook to store the `Vec` for messages, so they persist between component re-renders.
|
which subscribes to the dependencies so an event listener can be recreated when hook arguments change.
|
||||||
We'll also use `use_effect` to subscribe to the `EventBus` `Agent` so the subscription can be tied to component's lifecycle.
|
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use std::collections::HashSet;
|
use yew::prelude::*;
|
||||||
use yew::{use_effect, use_state, Callback};
|
use web_sys::{Event, EventTarget};
|
||||||
use yew_agent::Bridged;
|
use std::borrow::Cow;
|
||||||
// EventBus is an implementation yew_agent::Agent
|
use std::rc::Rc;
|
||||||
use website_test::agents::EventBus;
|
use gloo::events::EventListener;
|
||||||
|
|
||||||
fn use_subscribe() -> Vec<String> {
|
pub fn use_event<E, F>(target: &EventTarget, event_type: E, callback: F)
|
||||||
let state = use_state(Vec::new);
|
where
|
||||||
|
E: Into<Cow<'static, str>>,
|
||||||
|
F: Fn(&Event) + 'static,
|
||||||
|
{
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct EventDependents {
|
||||||
|
target: EventTarget,
|
||||||
|
event_type: Cow<'static, str>,
|
||||||
|
callback: Rc<dyn Fn(&Event)>,
|
||||||
|
}
|
||||||
|
|
||||||
let effect_state = state.clone();
|
#[allow(clippy::vtable_address_comparisons)]
|
||||||
|
impl PartialEq for EventDependents {
|
||||||
|
fn eq(&self, rhs: &Self) -> bool {
|
||||||
|
self.target == rhs.target
|
||||||
|
&& self.event_type == rhs.event_type
|
||||||
|
&& Rc::ptr_eq(&self.callback, &rhs.callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
use_effect(move || {
|
let deps = EventDependents {
|
||||||
let producer = EventBus::bridge(Callback::from(move |msg| {
|
target: target.clone(),
|
||||||
let mut messages = (*effect_state).clone();
|
event_type: event_type.into(),
|
||||||
messages.push(msg);
|
callback: Rc::new(callback) as Rc<dyn Fn(&Event)>,
|
||||||
effect_state.set(messages)
|
};
|
||||||
}));
|
|
||||||
|| drop(producer)
|
|
||||||
});
|
|
||||||
|
|
||||||
(*state).clone()
|
use_effect_with_deps(
|
||||||
|
|deps| {
|
||||||
|
let EventDependents {
|
||||||
|
target,
|
||||||
|
event_type,
|
||||||
|
callback,
|
||||||
|
} = deps.clone();
|
||||||
|
|
||||||
|
let listener = EventListener::new(&target, event_type, move |e| {
|
||||||
|
callback(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
move || {
|
||||||
|
drop(listener);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deps,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already
|
Although this approach works in almost all cases, it can't be used to write primitive hooks like the pre-defined hooks we've been using already.
|
||||||
|
|
||||||
### Writing primitive hooks
|
View the docs on [docs.rs](https://docs.rs/yew) for documentation and `hooks` directory to see implementations of pre-defined hooks.
|
||||||
|
|
||||||
`use_hook` function is used to write such hooks. View the docs on [docs.rs](https://docs.rs/yew/0.18.0/yew-functional/use_hook.html) for the documentation
|
|
||||||
and `hooks` directory to see implementations of pre-defined hooks.
|
|
||||||
|
|
|
@ -68,36 +68,26 @@ This hook requires the state object to implement `PartialEq`.
|
||||||
`use_ref` is used for obtaining an immutable reference to a value.
|
`use_ref` is used for obtaining an immutable reference to a value.
|
||||||
Its state persists across renders.
|
Its state persists across renders.
|
||||||
|
|
||||||
`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as
|
`use_ref` can be useful for keeping things in scope for the lifetime of the component, so long as
|
||||||
you don't store a clone of the resulting `Rc` anywhere that outlives the component.
|
you don't store a clone of the resulting `Rc` anywhere that outlives the component.
|
||||||
|
|
||||||
If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref).
|
If you need a mutable reference, consider using [`use_mut_ref`](#use_mut_ref).
|
||||||
If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state).
|
If you need the component to be re-rendered on state change, consider using [`use_state`](#use_state).
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
// EventBus is an implementation of yew_agent::Agent
|
|
||||||
use website_test::agents::EventBus;
|
|
||||||
use yew::{function_component, html, use_ref, use_state, Callback};
|
use yew::{function_component, html, use_ref, use_state, Callback};
|
||||||
use yew_agent::Bridged;
|
|
||||||
|
|
||||||
#[function_component(UseRef)]
|
#[function_component(UseRef)]
|
||||||
fn ref_hook() -> Html {
|
fn ref_hook() -> Html {
|
||||||
let greeting = use_state(|| "No one has greeted me yet!".to_owned());
|
let message = use_ref(|| "Some Expensive State.".to_string());
|
||||||
|
|
||||||
{
|
|
||||||
let greeting = greeting.clone();
|
|
||||||
use_ref(|| EventBus::bridge(Callback::from(move |msg| {
|
|
||||||
greeting.set(msg);
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
html! {
|
html! {
|
||||||
<div>
|
<div>
|
||||||
<span>{ (*greeting).clone() }</span>
|
<span>{ (*message).clone() }</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## `use_mut_ref`
|
## `use_mut_ref`
|
||||||
`use_mut_ref` is used for obtaining a mutable reference to a value.
|
`use_mut_ref` is used for obtaining a mutable reference to a value.
|
||||||
|
@ -122,7 +112,7 @@ fn mut_ref_hook() -> Html {
|
||||||
let message_count = use_mut_ref(|| 0);
|
let message_count = use_mut_ref(|| 0);
|
||||||
|
|
||||||
let onclick = Callback::from(move |_| {
|
let onclick = Callback::from(move |_| {
|
||||||
let window = gloo_utils::window();
|
let window = gloo::utils::window();
|
||||||
|
|
||||||
if *message_count.borrow_mut() > 3 {
|
if *message_count.borrow_mut() > 3 {
|
||||||
window.alert_with_message("Message limit reached").unwrap();
|
window.alert_with_message("Message limit reached").unwrap();
|
||||||
|
@ -316,10 +306,10 @@ fn effect() -> Html {
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
use_effect(move || {
|
use_effect(move || {
|
||||||
// Make a call to DOM API after component is rendered
|
// Make a call to DOM API after component is rendered
|
||||||
gloo_utils::document().set_title(&format!("You clicked {} times", *counter));
|
gloo::utils::document().set_title(&format!("You clicked {} times", *counter));
|
||||||
|
|
||||||
// Perform the cleanup
|
// Perform the cleanup
|
||||||
|| gloo_utils::document().set_title("You clicked 0 times")
|
|| gloo::utils::document().set_title("You clicked 0 times")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
let onclick = {
|
let onclick = {
|
||||||
|
|
|
@ -17,7 +17,7 @@ used as a `Html` value using `VRef`:
|
||||||
```rust
|
```rust
|
||||||
use web_sys::{Element, Node};
|
use web_sys::{Element, Node};
|
||||||
use yew::{Component, Context, html, Html};
|
use yew::{Component, Context, html, Html};
|
||||||
use gloo_utils::document;
|
use gloo::utils::document;
|
||||||
|
|
||||||
struct Comp;
|
struct Comp;
|
||||||
|
|
||||||
|
@ -161,45 +161,6 @@ impl Component for MyComponent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
|
||||||
|
|
||||||
</TabItem>
|
|
||||||
<TabItem value="Agent Handler" label="Agent Handler">
|
|
||||||
|
|
||||||
```rust
|
|
||||||
use yew::{html, Component, Context, Html};
|
|
||||||
use yew_agent::{Dispatcher, Dispatched};
|
|
||||||
use website_test::agents::{MyWorker, WorkerMsg};
|
|
||||||
|
|
||||||
struct MyComponent {
|
|
||||||
worker: Dispatcher<MyWorker>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Component for MyComponent {
|
|
||||||
type Message = WorkerMsg;
|
|
||||||
type Properties = ();
|
|
||||||
|
|
||||||
fn create(_ctx: &Context<Self>) -> Self {
|
|
||||||
MyComponent {
|
|
||||||
worker: MyWorker::dispatcher(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
|
|
||||||
self.worker.send(msg);
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
|
||||||
// Create a callback from a worker to handle it in another context
|
|
||||||
let click_callback = ctx.link().callback(|_| WorkerMsg::Process);
|
|
||||||
html! {
|
|
||||||
<button onclick={click_callback}>
|
|
||||||
{ "Click me!" }
|
|
||||||
</button>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
</TabItem>
|
</TabItem>
|
||||||
|
|
|
@ -630,7 +630,7 @@ use yew::{
|
||||||
Component, Context, Html, NodeRef,
|
Component, Context, Html, NodeRef,
|
||||||
};
|
};
|
||||||
|
|
||||||
use gloo_events::EventListener;
|
use gloo::events::EventListener;
|
||||||
|
|
||||||
pub struct Comp {
|
pub struct Comp {
|
||||||
my_div: NodeRef,
|
my_div: NodeRef,
|
||||||
|
@ -693,4 +693,4 @@ component is about to be destroyed as the `EventListener` has a `drop` implement
|
||||||
which will remove the event listener from the element.
|
which will remove the event listener from the element.
|
||||||
|
|
||||||
For more information on `EventListener`, see the
|
For more information on `EventListener`, see the
|
||||||
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).
|
[gloo_events docs.rs](https://docs.rs/gloo-events/0.1.1/gloo_events/struct.EventListener.html).
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
title: "From 0.1.0 to 0.2.0"
|
||||||
|
---
|
||||||
|
|
||||||
|
The `Context` and `Job` Agents have been removed in favour of Yew's Context API.
|
||||||
|
|
||||||
|
You can see the updated [`pub_sub`](https://github.com/yewstack/yew/tree/master/examples/pub_sub) example about how to use Context API.
|
||||||
|
|
||||||
|
For users of `yew_agent::utils::store`, you may switch to third party solutions like: [Yewdux](https://github.com/intendednull/yewdux) or [Bounce](https://github.com/futursolo/bounce).
|
Loading…
Reference in New Issue