sim/ui/src/api.rs

97 lines
2.8 KiB
Rust
Raw Normal View History

use gloo_net::http::Request;
use gloo_timers::future::TimeoutFuture;
use leptos::task::spawn_local;
use serde::de::DeserializeOwned;
use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::JsCast;
use wasm_bindgen::prelude::Closure;
use web_sys::{MessageEvent, WebSocket};
thread_local! {
static SOCKET: RefCell<Option<WebSocket>> = const { RefCell::new(None) };
}
/// Opens the live socket and calls `on_state` with each pushed snapshot; reconnects after a drop.
pub fn connect(on_state: impl Fn(String) + 'static) {
let Some(loc) = web_sys::window().map(|w| w.location()) else {
return;
};
let proto = if loc.protocol().is_ok_and(|p| p == "https:") {
"wss"
} else {
"ws"
};
let Ok(host) = loc.host() else { return };
open(format!("{proto}://{host}/ws"), Rc::new(on_state));
}
fn open(url: String, on_state: Rc<dyn Fn(String)>) {
let Ok(ws) = WebSocket::new(&url) else { return };
let cb = on_state.clone();
let onmessage = Closure::<dyn FnMut(MessageEvent)>::new(move |e: MessageEvent| {
if let Some(text) = e.data().as_string() {
cb(text);
}
});
ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
onmessage.forget();
let url2 = url.clone();
let onclose = Closure::<dyn FnMut()>::new(move || {
SOCKET.with(|s| *s.borrow_mut() = None);
let (url, on_state) = (url2.clone(), on_state.clone());
spawn_local(async move {
TimeoutFuture::new(1000).await;
open(url, on_state);
});
});
ws.set_onclose(Some(onclose.as_ref().unchecked_ref()));
onclose.forget();
SOCKET.with(|s| *s.borrow_mut() = Some(ws));
}
pub async fn get_json<T: DeserializeOwned>(url: &str) -> Option<T> {
Request::get(url).send().await.ok()?.json().await.ok()
}
pub async fn get_text(url: &str) -> Option<String> {
Request::get(url).send().await.ok()?.text().await.ok()
}
/// Returns (success, response body).
pub async fn post(url: &str, body: String) -> Option<(bool, String)> {
let r = Request::post(url).body(body).ok()?.send().await.ok()?;
Some((r.ok(), r.text().await.ok()?))
}
pub fn edit(cmd: String) {
let sent = SOCKET.with(|s| {
s.borrow()
.as_ref()
.filter(|w| w.ready_state() == WebSocket::OPEN)
.is_some_and(|w| w.send_with_str(&cmd).is_ok())
});
if !sent {
spawn_local(async move {
let _ = post("/api/edit", cmd).await;
});
}
}
fn storage() -> Option<web_sys::Storage> {
web_sys::window()?.local_storage().ok().flatten()
}
pub fn vim_pref() -> bool {
storage()
.and_then(|s| s.get_item("vim").ok().flatten())
.is_none_or(|v| v != "0")
}
pub fn set_vim_pref(on: bool) {
if let Some(s) = storage() {
let _ = s.set_item("vim", if on { "1" } else { "0" });
}
}