#![allow(non_snake_case)] use dioxus::prelude::*; use dioxus_components::{Button, ButtonSize, ButtonVariant, Control, ControlKind, Prompt}; use dioxus_icons::lucide::{Bell, Bot, Check, Copy, LogIn, Mic, Moon, Power, Settings, Shield, Sun, Wifi}; use crate::{read_hub_state, sleep_ms, start_login}; fn copy_to_clipboard(text: &str) { let mut literal = String::from('"'); for c in text.chars() { match c { '"' => literal.push_str("\\\""), '\\' => literal.push_str("\\\\"), '\n' => literal.push_str("\\n"), '\r' => literal.push_str("\\r"), '\t' => literal.push_str("\\t"), c if c.is_control() => literal.push_str(&format!("\\u{:04x}", c as u32)), c => literal.push(c), } } literal.push('"'); let _ = document::eval(&format!("navigator.clipboard.writeText({literal})")); } const APP_CSS: &str = r#" html, body { width: 100%; height: 100%; margin: 0; background: #171D18; color: #F2F4EA; font-family: "Segoe UI", "IBM Plex Sans", "Helvetica Neue", sans-serif; } #main, #main > div { width: 100%; height: 100%; } .mme-hub { /* Palette: dark defaults; light overrides below. Values match the mize token table. */ --bg: #171D18; --surface: #242D25; --surface-2: #2C362D; --text: #F2F4EA; --text-2: #AAB5A4; --border: #3A463A; --accent: #D7E47D; --accent-text: #D7E47D; --success: #B9D987; --info: #B9E4EB; --warning: #FFD77F; --danger: #FF9B8E; --on-accent: #171D18; --focus-ring: var(--accent); --tile-size: 6.75rem; --tile-gap: 0.65rem; --prompt-gap: 2.25rem; display: flex; width: 100%; height: 100%; box-sizing: border-box; flex-direction: column; align-items: stretch; padding: 1rem 1.25rem 1.5rem; gap: var(--prompt-gap); overflow: auto; background: var(--bg); color: var(--text); } .mme-hub[data-theme="light"] { --bg: #F7F8F4; --surface: #FFFFFF; --surface-2: #EFF2EA; --text: #20251C; --text-2: #66705E; --border: #DFE4D9; --accent: #FEC251; --accent-text: #79500F; --success: #526F32; --info: #376777; --warning: #A66B13; --danger: #B94D42; --on-accent: #79500F; } .mme-topbar { display: flex; flex-shrink: 0; align-items: center; gap: 0.65rem; width: 100%; } .mme-topbar-prompt { flex: 1 1 auto; min-width: 0; } .mme-topbar-actions { display: flex; flex-shrink: 0; align-items: center; gap: 0.5rem; } .mme-login-banner { display: flex; flex-shrink: 0; flex-direction: column; gap: 0.55rem; padding: 0.75rem 0.9rem; border: 1px solid var(--border); border-radius: 0.375rem; background: var(--surface); color: var(--text); } .mme-login-banner-label { color: var(--text-2); font-size: 0.75rem; letter-spacing: 0.02em; text-transform: uppercase; } .mme-login-banner-row { display: flex; align-items: center; gap: 0.65rem; min-width: 0; } .mme-login-banner-code { flex: 1 1 auto; min-width: 0; color: var(--accent-text); font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace; font-size: 1.25rem; font-weight: 600; letter-spacing: 0.08em; } .mme-login-banner-url { flex: 1 1 auto; min-width: 0; color: var(--info); font-size: 0.8125rem; word-break: break-all; } .mme-tiles { display: grid; flex: 1 1 auto; align-content: start; justify-content: start; justify-items: center; gap: var(--tile-gap); grid-template-columns: repeat(3, var(--tile-size)); } .mme-hub-tile { width: var(--tile-size); height: var(--tile-size); } @media (max-width: 720px) { .mme-hub { --tile-size: 5.75rem; --prompt-gap: 1.35rem; } .mme-topbar { flex-wrap: wrap; } .mme-topbar-actions { width: 100%; justify-content: flex-end; } .mme-tiles { grid-template-columns: repeat(2, var(--tile-size)); } } "#; pub fn launch() { dioxus::LaunchBuilder::new() .with_cfg( dioxus::desktop::Config::new() .with_menu(None) .with_window( dioxus::desktop::WindowBuilder::new().with_title("mme"), ), ) .launch(App); } /// Isolated so hub re-renders never diff `document::Style` props. #[component] fn AppStyle() -> Element { rsx! { document::Style { {APP_CSS} } } } #[component] fn App() -> Element { rsx! { AppStyle {} document::Title { "mme" } Hub {} } } #[component] fn Hub() -> Element { let mut prompt = use_signal(String::new); let mut mic_on = use_signal(|| false); let mut wifi_on = use_signal(|| true); let mut shield_on = use_signal(|| true); let mut dark = use_signal(|| true); let mut bot_armed = use_signal(|| false); let initial = read_hub_state().unwrap_or_default(); let mut logged_in = use_signal(|| initial.logged_in); let mut logging_in = use_signal(|| initial.logging_in); let mut login_url = use_signal(|| initial.login_url.clone()); let mut login_code = use_signal(|| initial.login_code.clone()); use_future(move || async move { loop { sleep_ms(200).await; let Ok(hub) = read_hub_state() else { continue; }; // Only write signals when values change — avoids re-rendering the // whole hub (and its nested document::Style tags) every tick. if logged_in() != hub.logged_in { logged_in.set(hub.logged_in); } if logging_in() != hub.logging_in { logging_in.set(hub.logging_in); } if login_url() != hub.login_url { login_url.set(hub.login_url.clone()); } if login_code() != hub.login_code { login_code.set(hub.login_code.clone()); } } }); let theme = if dark() { "dark" } else { "light" }; let show_banner = logging_in() && (login_url().is_some() || login_code().is_some()); rsx! { div { class: "mme-hub", "data-theme": theme, div { class: "mme-topbar", div { class: "mme-topbar-prompt", Prompt { value: prompt(), placeholder: "Ask or run a command…".to_string(), oninput: move |e: FormEvent| prompt.set(e.value()), } } div { class: "mme-topbar-actions", Button { variant: ButtonVariant::Outline, onclick: move |_| dark.set(!dark()), if dark() { Moon {} } else { Sun {} } if dark() { "Dark" } else { "Light" } } if logged_in() { Button { variant: ButtonVariant::Outline, onclick: move |_| {}, Check {} "Logged in" } } else if logging_in() { Button { variant: ButtonVariant::Outline, onclick: move |_| {}, LogIn {} "Waiting…" } } else { Button { variant: ButtonVariant::Primary, onclick: move |_| start_login(), LogIn {} "PPC Login" } } } } if show_banner { div { class: "mme-login-banner", span { class: "mme-login-banner-label", "PPC device login" } if let Some(code) = login_code() { div { class: "mme-login-banner-row", span { class: "mme-login-banner-code", "{code}" } Button { variant: ButtonVariant::Outline, size: ButtonSize::Sm, onclick: move |_| copy_to_clipboard(&code), Copy {} "Copy code" } } } if let Some(url) = login_url() { div { class: "mme-login-banner-row", span { class: "mme-login-banner-url", "{url}" } Button { variant: ButtonVariant::Outline, size: ButtonSize::Sm, onclick: move |_| copy_to_clipboard(&url), Copy {} "Copy link" } } } } } div { class: "mme-tiles", div { class: "mme-hub-tile", Control { name: "Mic".to_string(), kind: ControlKind::Toggle, enabled: mic_on(), confirm: Some("Enable microphone access for voice input?".into()), on_enabled_change: move |v| mic_on.set(v), Mic {} } } div { class: "mme-hub-tile", Control { name: "Alerts".to_string(), kind: ControlKind::Action, onclick: move |_| {}, Bell {} } } div { class: "mme-hub-tile", Control { name: "Network".to_string(), kind: ControlKind::Toggle, enabled: wifi_on(), on_enabled_change: move |v| wifi_on.set(v), Wifi {} } } div { class: "mme-hub-tile", Control { name: "Agent".to_string(), kind: ControlKind::Toggle, enabled: bot_armed(), confirm: Some("Arm the agent for autonomous actions?".into()), on_enabled_change: move |v| bot_armed.set(v), Bot {} } } div { class: "mme-hub-tile", Control { name: "Status".to_string(), kind: ControlKind::Display, value: Some(if bot_armed() { "armed".into() } else { "idle".into() }), Power {} } } div { class: "mme-hub-tile", Control { name: "Shield".to_string(), kind: ControlKind::Toggle, enabled: shield_on(), confirm: Some("Disable shield? This turns off local protections.".into()), on_enabled_change: move |v| shield_on.set(v), Shield {} } } div { class: "mme-hub-tile", Control { name: "Settings".to_string(), kind: ControlKind::Action, onclick: move |_| {}, Settings {} } } } } } }