AnyBot/mobile/screen.html

110 lines
6.4 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="zh-Hant">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, user-scalable=no">
<meta name="theme-color" content="#16181d">
<title>Bot 的電腦</title>
<link rel="icon" href="icon.svg">
<style>
:root { --bg: #16181d; --bar: #1f2228; --line: #2e323a; --text: #eceef2; --dim: #9aa1ad; --accent: #4f8cff; --warn: #e5a13a; --ok: #3fb67a; }
* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--bg); color: var(--text); font: 15px/1.4 -apple-system, BlinkMacSystemFont, "PingFang TC", "Noto Sans TC", sans-serif; overflow: hidden; }
body { display: flex; flex-direction: column; height: 100dvh; }
header { display: flex; align-items: center; gap: 8px; padding: 8px 12px; padding-top: max(8px, env(safe-area-inset-top)); background: var(--bar); border-bottom: 1px solid var(--line); }
header .name { font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
header .state { color: var(--dim); font-size: 13px; white-space: nowrap; display: flex; align-items: center; gap: 6px; }
.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--dim); flex: none; }
.dot.live { background: var(--ok); }
.dot.hold { background: var(--warn); }
.grow { flex: 1; min-width: 0; display: flex; flex-direction: column; }
button { font: inherit; color: var(--text); background: #2b2f37; border: 1px solid var(--line); border-radius: 8px; padding: 6px 12px; cursor: pointer; white-space: nowrap; }
button.main { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }
button.icon { padding: 6px 10px; }
#screen { flex: 1; min-height: 0; position: relative; background: #000; }
#screen > div { background: #000 !important; }
#msg { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; text-align: center; padding: 24px; color: var(--dim); pointer-events: none; }
#type { display: none; gap: 6px; padding: 8px 12px; padding-bottom: max(8px, env(safe-area-inset-bottom)); background: var(--bar); border-top: 1px solid var(--line); flex-wrap: wrap; }
#type.on { display: flex; }
#type input { flex: 1 1 160px; min-width: 0; font: inherit; color: var(--text); background: #111318; border: 1px solid var(--line); border-radius: 8px; padding: 7px 10px; }
@media (max-width: 420px) { header .hint { display: none; } }
</style>
</head>
<body>
<header>
<button class="icon" id="back" title="返回">←</button>
<div class="grow"><div class="name" id="name">Bot 的電腦</div><div class="state"><span class="dot" id="dot"></span><span id="state">連線中…</span></div></div>
<button id="kb" title="打字(任何語言)">打字</button>
<button class="main" id="take">接管</button>
</header>
<div id="screen"><div id="msg">連線中…</div></div>
<div id="type">
<input id="text" placeholder="要打的字(中文也可以),按送出" autocomplete="off" enterkeyhint="send">
<button class="main" id="send">送出</button>
<button data-k="Return">Enter</button><button data-k="Escape">Esc</button><button data-k="Tab">Tab</button><button data-k="BackSpace">⌫</button>
</div>
<script type="module">
import RFB from "./novnc.js";
// #host=…&name=…&ticket=… (the app's link) or #host=… with this site's sign-in (the phone page);
// #lan=1 on an AnyBot's own phone page. &control=1 starts with control (a bot asked for help).
const q = new URLSearchParams(location.hash.slice(1));
const name = q.get("name") || "Bot 的電腦";
const token = localStorage.getItem("anybot.token") || "";
const wsBase = (location.protocol === "https:" ? "wss://" : "ws://") + location.host;
const url = q.get("lan") === "1"
? `${wsBase}/api/vnc?t=${encodeURIComponent(token)}`
: `${wsBase}/relay/vnc/${encodeURIComponent(q.get("host") || "")}?` + (q.get("ticket") ? "ticket=" + encodeURIComponent(q.get("ticket")) : "t=" + encodeURIComponent(token));
const $ = (id) => document.getElementById(id);
document.title = name;
$("name").textContent = name;
let rfb = null, ws = null, control = q.get("control") === "1", tries = 0, connected = false;
const KEYS = { Return: 0xff0d, Escape: 0xff1b, Tab: 0xff09, BackSpace: 0xff08 };
function state(text, kind) { $("state").textContent = text; $("dot").className = "dot" + (kind ? " " + kind : ""); }
function message(text) { $("msg").textContent = text; $("msg").style.display = text ? "" : "none"; }
function showControl() {
$("take").textContent = control ? "交還給 Bot" : "接管";
$("take").className = control ? "" : "main";
$("kb").style.display = control ? "" : "none";
if (!control) $("type").classList.remove("on");
if (rfb) { rfb.viewOnly = !control; if (control) rfb.focus(); }
if (connected) state(control ? "你在操作(Bot 會等你停下)" : "即時觀看", control ? "hold" : "live");
}
function connect() {
message("連線中…"); state("連線中…");
ws = new WebSocket(url);
rfb = new RFB($("screen"), ws, { shared: true });
rfb.scaleViewport = true;
rfb.resizeSession = false;
rfb.qualityLevel = 6;
rfb.compressionLevel = 2;
rfb.showDotCursor = true;
rfb.viewOnly = !control;
rfb.addEventListener("connect", () => { tries = 0; connected = true; message(""); showControl(); });
rfb.addEventListener("disconnect", () => {
rfb = null; connected = false;
tries += 1;
state("連線中斷,正在重新連線…");
message(tries > 3 ? "連不上這台電腦。它的 AnyBot 可能關了,或連結已過期:回到 AnyBot 再打開一次。" : "連線中斷,正在重新連線…");
setTimeout(connect, Math.min(1000 * tries, 8000));
});
}
$("take").onclick = () => { control = !control; showControl(); };
$("kb").onclick = () => { $("type").classList.toggle("on"); if ($("type").classList.contains("on")) $("text").focus(); };
// Typed text goes over the same connection as words (any language), pasted in the bots' computer.
function send() { const t = $("text").value; if (!t || !ws || ws.readyState !== 1) return; ws.send(t); $("text").value = ""; }
$("send").onclick = send;
$("text").addEventListener("keydown", (e) => { if (e.key === "Enter" && !e.isComposing) { e.preventDefault(); send(); } });
document.querySelectorAll("[data-k]").forEach((b) => b.onclick = () => { if (rfb) rfb.sendKey(KEYS[b.dataset.k], b.dataset.k); });
$("back").onclick = () => { if (history.length > 1) history.back(); else window.close(); };
showControl();
connect();
</script>
</body>
</html>