lockwatch/client/src/main.rs
finga afa1d19dd8 Exchange data via websockets
Use websockets to send binary messages to the server. The server logs
the time taken by the client, as well as the time calculated with the
received messages sent by the client.
2022-10-02 21:14:32 +02:00

155 lines
4.5 KiB
Rust

use futures::{
stream::{SplitSink, SplitStream},
SinkExt, StreamExt,
};
use gloo_net::websocket::{futures::WebSocket, Message};
use gloo_timers::callback::Interval;
use instant::Instant;
use log::debug;
use std::sync::Arc;
use tokio::sync::Mutex;
use wasm_bindgen_futures::spawn_local;
use yew::prelude::*;
use yew_router::prelude::*;
#[derive(Clone, Debug, Eq, PartialEq, Routable)]
pub enum Route {
#[at("/")]
LockWatch,
#[not_found]
#[at("/404")]
NotFound,
}
struct WebSocketHandle {
writer: Arc<Mutex<SplitSink<WebSocket, Message>>>,
_reader: Arc<Mutex<SplitStream<WebSocket>>>,
}
impl WebSocketHandle {
fn connect(url: &str) -> Self {
debug!("connecting to websocket `{}`", url);
let ws = WebSocket::open(url)
.unwrap_or_else(|_| panic!("could not connect to websocket `{}`", url));
let (writer, reader) = ws.split();
Self {
writer: Arc::new(Mutex::new(writer)),
_reader: Arc::new(Mutex::new(reader)),
}
}
fn send(&self, msg: client::Message) {
let writer = self.writer.clone();
spawn_local(async move {
writer
.lock()
.await
.send(Message::Bytes(
bincode::serialize(&msg).expect("could not serialize websocket message"),
))
.await
.expect("could not send websocket message");
});
}
}
enum Msg {
Start,
Stop,
UpdateDuration,
}
struct LockWatch {
websocket: WebSocketHandle,
duration: Option<Instant>,
duration_handle: Option<Interval>,
}
impl LockWatch {
fn get_duration(&self) -> String {
self.duration.map_or_else(
|| "00:00:00.000".to_string(),
|instant| {
let elapsed = instant.elapsed();
format!(
"{:02}:{:02}:{:02}.{:03}",
elapsed.as_secs() / 60 / 60,
(elapsed.as_secs() / 60) % 60,
elapsed.as_secs() % 60,
elapsed.as_millis() % 1_000
)
},
)
}
}
impl Component for LockWatch {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self {
websocket: WebSocketHandle::connect("ws://localhost:3000/api/ws"),
duration: None,
duration_handle: None,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::Start => {
self.duration = Some(Instant::now());
self.websocket.send(client::Message::Start);
self.duration_handle = {
let link = ctx.link().clone();
Some(Interval::new(1, move || {
link.send_message(Msg::UpdateDuration);
}))
};
}
Msg::Stop => {
self.duration_handle = None;
self.websocket.send(client::Message::Stop);
let duration = self.duration.expect("could not get duration").elapsed();
self.websocket.send(client::Message::Duration(duration));
}
Msg::UpdateDuration => {}
}
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
html! {
<section class="section">
<div class="container">
<h1 class="title">{ "LockWatch" }</h1>
<div class="block is-size-1">
{ &self.get_duration() }
</div>
<div class="block">
if self.duration_handle.is_some() {
<button class="button is-danger" onclick={ctx.link().callback(|_| Msg::Stop)}>{ "Stop" }</button>
} else {
if self.duration.is_some() {
<button class="button" disabled=true>{ "Start" }</button>
} else {
<button class="button is-primary" onclick={ctx.link().callback(|_| Msg::Start)}>{ "Start" }</button>
}
}
</div>
</div>
</section>
}
}
}
fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
console_error_panic_hook::set_once();
yew::start_app::<LockWatch>();
}