use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{Document, Element, HtmlInputElement, HtmlTextAreaElement, Storage, Window};
pub(crate) fn window() -> Result<Window, JsValue> {
web_sys::window().ok_or_else(|| JsValue::from_str("no window — wrong execution context"))
}
pub(crate) fn document() -> Result<Document, JsValue> {
window()?
.document()
.ok_or_else(|| JsValue::from_str("no document — wrong execution context"))
}
pub(crate) fn session_storage() -> Result<Option<Storage>, JsValue> {
window()?.session_storage()
}
pub(crate) fn by_id(id: &str) -> Option<Element> {
document().ok()?.get_element_by_id(id)
}
pub(crate) fn input_by_id(id: &str) -> Option<HtmlInputElement> {
by_id(id)?.dyn_into::<HtmlInputElement>().ok()
}
pub(crate) fn textarea_by_id(id: &str) -> Option<HtmlTextAreaElement> {
by_id(id)?.dyn_into::<HtmlTextAreaElement>().ok()
}
#[derive(Clone, Copy)]
pub(crate) enum Msg {
Error,
Muted,
Accent,
}
impl Msg {
fn css_var(self) -> &'static str {
match self {
Msg::Error => "--error",
Msg::Muted => "--muted",
Msg::Accent => "--accent",
}
}
}
pub(crate) fn msg_span(kind: Msg, text: &str) -> String {
let style = format!("color:var({})", kind.css_var());
maud::html! { span style=(style) { (text) } }.into_string()
}
pub(crate) fn swap_inner(id: &str, html: &str) {
if let Some(el) = by_id(id) {
el.set_inner_html(html);
}
}
pub(crate) fn swap_outer(id: &str, html: &str) {
if let Some(el) = by_id(id) {
el.set_outer_html(html);
}
}
pub(crate) fn append_html(id: &str, html: &str) {
if let Some(el) = by_id(id) {
let _ = el.insert_adjacent_html("beforeend", html);
}
}
pub(crate) fn remove(id: &str) {
if let Some(el) = by_id(id) {
el.remove();
}
}
pub(crate) fn scroll_to_bottom(id: &str) {
if let Some(el) = by_id(id) {
el.set_scroll_top(el.scroll_height());
}
}
pub(crate) fn scroll_to_bottom_soon(id: &str) {
scroll_to_bottom(id);
let Ok(win) = window() else { return };
for delay in [60, 350] {
let id = id.to_string();
let cb = Closure::once_into_js(move || scroll_to_bottom(&id));
let _ = win.set_timeout_with_callback_and_timeout_and_arguments_0(
cb.unchecked_ref(),
delay,
);
}
}
pub(crate) fn mark_ready() {
if let Ok(doc) = document() {
if let Some(el) = doc.document_element() {
let _ = el.set_attribute("data-lh-ready", "1");
}
}
}
pub(crate) fn set_status(message: &str, is_error: bool) {
let Some(doc) = web_sys::window().and_then(|w| w.document()) else {
return;
};
if let Some(el) = doc.get_element_by_id("system-status") {
el.remove();
}
if message.is_empty() {
return;
}
let Some(transcript) = doc.get_element_by_id("transcript") else {
return;
};
let cls = if is_error { "system-status err" } else { "system-status" };
let _ = transcript.insert_adjacent_html(
"beforeend",
&format!(
"<div id=\"system-status\" class=\"{cls}\">{}</div>",
html_escape(message)
),
);
scroll_to_bottom("transcript");
}
fn html_escape(s: &str) -> String {
s.replace('&', "&").replace('<', "<").replace('>', ">")
}