terrazzo-terminal 0.2.7

A simple web-based terminal emulator built on Terrazzo.
use std::sync::OnceLock;

use terrazzo::autoclone;
use terrazzo::html;
use terrazzo::prelude::*;
use terrazzo::template;
use terrazzo::widgets::more_event::MoreEvent as _;
use wasm_bindgen::JsCast;
use wasm_bindgen_futures::spawn_local;
use web_sys::HtmlElement;
use web_sys::HtmlInputElement;

use self::diagnostics::Instrument as _;
use self::diagnostics::info;
use self::diagnostics::warn;
use crate::assets::icons;
use crate::tiles::ui::show_tiles;

terrazzo_css::import_style!(style, "login.scss");

#[autoclone]
#[html]
#[template]
pub fn login(#[signal] mut logged_in: LoggedInStatus) -> XElement {
    match logged_in {
        LoggedInStatus::Login => show_tiles(),
        LoggedInStatus::Logout => div(
            key = "login",
            class = style::LOGIN,
            img(class = style::KEY_ICON, src = icons::key_icon()),
            input(
                r#type = "password",
                after_render = |password: &Element| {
                    let password: &HtmlElement = password.dyn_ref().or_throw("password");
                    let () = password.focus().or_throw("password focus");
                },
                change = move |ev: web_sys::Event| {
                    let Ok(password): Result<HtmlInputElement, _> = ev
                        .current_target_element("The password field")
                        .map_err(|error| warn!("{error}"))
                    else {
                        return;
                    };

                    let login_task = async move {
                        autoclone!(logged_in_mut);
                        match crate::api::client::login::login(Some(&password.value())).await {
                            Ok(()) => logged_in_mut.set(LoggedInStatus::Login),
                            Err(error) => warn!("{error}"),
                        }
                    };
                    spawn_local(login_task.in_current_span());
                },
            ),
        ),
        LoggedInStatus::Unknown => {
            let login_task = async move {
                autoclone!(logged_in_mut);
                match crate::api::client::login::login(None).await {
                    Ok(()) => logged_in_mut.set(LoggedInStatus::Login),
                    Err(error) => {
                        logged_in_mut.set(LoggedInStatus::Logout);
                        info!("Authentication is required: {error}")
                    }
                }
            };
            spawn_local(login_task.in_current_span());
            div(key = "login-pending", class = style::LOGIN)
        }
    }
}

pub fn logged_in() -> XSignal<LoggedInStatus> {
    static LOGGED_IN: OnceLock<XSignal<LoggedInStatus>> = OnceLock::new();
    LOGGED_IN
        .get_or_init(|| XSignal::new("logged-in", LoggedInStatus::Unknown))
        .clone()
}

#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)]
pub enum LoggedInStatus {
    Login,
    Logout,

    #[default]
    Unknown,
}