Initial Commit
This commit is contained in:
151
frontend/src/app.rs
Normal file
151
frontend/src/app.rs
Normal file
@ -0,0 +1,151 @@
|
||||
use crate::page;
|
||||
use common::{ClientMessage, ServerMessage};
|
||||
use seed::app::orders::OrdersContainer;
|
||||
use seed::prelude::*;
|
||||
use seed::{log, window};
|
||||
use seed_router::Router;
|
||||
use std::collections::VecDeque;
|
||||
|
||||
pub type AppOrders = OrdersContainer<Msg, Model, Vec<Node<Msg>>>;
|
||||
|
||||
/// Delays between successive attempts to reconnect in case the socket breaks. In seconds.
|
||||
const TIMEOUT_CONNECT_DELAYS: &[u32] = &[2, 5, 10, 10, 10, 20, 30, 60, 120, 300];
|
||||
|
||||
pub struct Model {
|
||||
page: Pages,
|
||||
send_queue: VecDeque<ClientMessage>,
|
||||
socket: WebSocket,
|
||||
ws_url: String,
|
||||
timeout_count: usize,
|
||||
}
|
||||
|
||||
#[derive(Router)]
|
||||
pub enum Pages {
|
||||
#[page("404", NotFound)]
|
||||
NotFound(page::not_found::Model),
|
||||
|
||||
#[page("info", Info)]
|
||||
Info(page::info::Model),
|
||||
|
||||
#[page("lights", Lights)]
|
||||
Lights(page::lights::Model),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PageMsg {
|
||||
NotFound(page::not_found::Msg),
|
||||
Info(page::info::Msg),
|
||||
Lights(page::lights::Msg),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Msg {
|
||||
Page(PageMsg),
|
||||
|
||||
SendMessage(ClientMessage),
|
||||
FlushMessageQueue,
|
||||
|
||||
// Global
|
||||
Connect,
|
||||
SocketOpened(),
|
||||
SocketClosed(CloseEvent),
|
||||
SocketError(),
|
||||
SocketMessage(WebSocketMessage),
|
||||
}
|
||||
|
||||
pub fn init(url: Url, orders: &mut impl Orders<Msg>) -> Model {
|
||||
orders.subscribe(Msg::SendMessage);
|
||||
|
||||
let location = window().location();
|
||||
let host = location.host().expect("Failed to get hostname");
|
||||
let ws_protocol = match location.protocol().ok().as_deref() {
|
||||
Some("http:") => "ws",
|
||||
_ => "wss",
|
||||
};
|
||||
|
||||
let ws_url = format!("{ws_protocol}://{host}/api/ws");
|
||||
|
||||
Model {
|
||||
page: Pages::from_url(url, &mut orders.proxy(Msg::Page))
|
||||
.unwrap_or(Pages::NotFound(Default::default())),
|
||||
send_queue: Default::default(),
|
||||
socket: open_socket(&ws_url, orders),
|
||||
ws_url,
|
||||
timeout_count: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn open_socket(url: &str, orders: &mut impl Orders<Msg>) -> WebSocket {
|
||||
WebSocket::builder(url, orders)
|
||||
.on_open(Msg::SocketOpened)
|
||||
.on_close(Msg::SocketClosed)
|
||||
.on_error(Msg::SocketError)
|
||||
.on_message(Msg::SocketMessage)
|
||||
.build_and_open()
|
||||
.expect("failed to open websocket")
|
||||
}
|
||||
|
||||
pub fn update(msg: Msg, model: &mut Model, orders: &mut AppOrders) {
|
||||
#[cfg(debug_assertions)]
|
||||
log!(format!("{msg:?}"));
|
||||
|
||||
match msg {
|
||||
Msg::Page(msg) => model.page.update(msg, &mut orders.proxy(Msg::Page)),
|
||||
Msg::FlushMessageQueue => {
|
||||
while let Some(message) = model.send_queue.pop_front() {
|
||||
let serialized = ron::to_string(&message).unwrap();
|
||||
|
||||
if let Err(e) = model.socket.send_text(serialized) {
|
||||
model.send_queue.push_front(message);
|
||||
log!(e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
Msg::Connect => {
|
||||
model.socket = open_socket(&model.ws_url, orders);
|
||||
}
|
||||
Msg::SendMessage(message) => {
|
||||
model.send_queue.push_back(message);
|
||||
orders.send_msg(Msg::FlushMessageQueue);
|
||||
}
|
||||
Msg::SocketOpened() => {
|
||||
model.timeout_count = 0;
|
||||
orders.send_msg(Msg::FlushMessageQueue);
|
||||
}
|
||||
Msg::SocketClosed(_event) => {
|
||||
let timeout_sec = TIMEOUT_CONNECT_DELAYS[model.timeout_count];
|
||||
let timeout_ms = timeout_sec * 1000;
|
||||
orders.perform_cmd(cmds::timeout(timeout_ms, || Msg::Connect));
|
||||
|
||||
log!(format!(
|
||||
"Socket closed, trying to reconnect in {timeout_sec} seconds"
|
||||
));
|
||||
|
||||
model.timeout_count = TIMEOUT_CONNECT_DELAYS.len().min(model.timeout_count + 1);
|
||||
}
|
||||
Msg::SocketError() => {}
|
||||
Msg::SocketMessage(message) => {
|
||||
if let Err(e) = handle_ws_msg(message, orders) {
|
||||
log!(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_ws_msg(message: WebSocketMessage, orders: &mut impl Orders<Msg>) -> anyhow::Result<()> {
|
||||
let message = message.text().map_err(|e| anyhow::format_err!("{e:?}"))?;
|
||||
let message: ServerMessage = ron::from_str(&message)?;
|
||||
orders.notify(message);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn view(model: &Model) -> Vec<Node<Msg>> {
|
||||
vec![model.page.view().map_msg(Msg::Page)]
|
||||
//match &model.page {
|
||||
// Pages::NotFound => vec![h1!["Not Found"]],
|
||||
// Pages::InfoScreen => vec![div![C![C.info_box], raw![&model.info_page]]],
|
||||
// Pages::Lights(page) => vec![],
|
||||
//}
|
||||
}
|
||||
Reference in New Issue
Block a user