forked from phenom/healer-man
81 lines
3.5 KiB
TypeScript
81 lines
3.5 KiB
TypeScript
import type { CSSProperties } from "react";
|
|
import {
|
|
loginKeyboardRows,
|
|
type LoginField,
|
|
type LoginInputSnapshot,
|
|
type LoginKeyDefinition,
|
|
} from "../app/loginInputStore";
|
|
import type { CompanionCommand } from "../platform/dualScreenSync";
|
|
import { BrandMark, FrontSurface } from "./FrontSurface";
|
|
|
|
function passwordPreview(length: number): string {
|
|
if (!length) return "Password";
|
|
return `${"•".repeat(Math.min(length, 18))}${length > 18 ? "…" : ""}`;
|
|
}
|
|
|
|
function keyLabel(key: LoginKeyDefinition, activeField: LoginField): string {
|
|
if (key.kind === "field") return activeField === "username" ? "Password" : "Username";
|
|
return key.label;
|
|
}
|
|
|
|
export function LoginVirtualKeyboard({ snapshot, notice, send }: {
|
|
snapshot: LoginInputSnapshot;
|
|
notice: string;
|
|
send: (command: CompanionCommand) => void;
|
|
}) {
|
|
const rows = loginKeyboardRows(snapshot.keyboardMode);
|
|
const chooseField = (field: LoginField) => send({ name: "selectLoginField", field });
|
|
|
|
return (
|
|
<FrontSurface bottom className="companion-panel companion-login companion-login-keyboard" ariaLabel="Controller login keyboard">
|
|
<header><BrandMark compact /><b>CONTROLLER KEYBOARD</b></header>
|
|
<main>
|
|
<div className="login-keyboard-fields" role="group" aria-label="Login fields">
|
|
<button
|
|
type="button"
|
|
className={snapshot.activeField === "username" ? "is-active" : ""}
|
|
aria-pressed={snapshot.activeField === "username"}
|
|
onClick={() => chooseField("username")}
|
|
>
|
|
<small>Username</small><strong>{snapshot.username || "Select to enter"}</strong>
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={snapshot.activeField === "password" ? "is-active" : ""}
|
|
aria-pressed={snapshot.activeField === "password"}
|
|
onClick={() => chooseField("password")}
|
|
>
|
|
<small>Password</small><strong>{passwordPreview(snapshot.passwordLength)}</strong>
|
|
</button>
|
|
</div>
|
|
<div className="login-keyboard-grid" role="group" aria-label={`${snapshot.keyboardMode} virtual keyboard`}>
|
|
{rows.flat().map((key) => {
|
|
const isAction = ["login", "create", "offline"].includes(key.kind);
|
|
const unavailableForUsername = snapshot.activeField === "username"
|
|
&& key.kind === "character"
|
|
&& !/^[A-Za-z0-9_]$/.test(key.value ?? "");
|
|
return (
|
|
<button
|
|
key={key.id}
|
|
type="button"
|
|
className={`login-keyboard-key login-keyboard-key--${key.kind} ${snapshot.selectedKeyId === key.id ? "is-controller-selected" : ""} ${unavailableForUsername ? "is-unavailable" : ""}`}
|
|
style={{ gridColumn: `span ${key.span ?? 1}` } as CSSProperties}
|
|
aria-label={key.ariaLabel}
|
|
aria-current={snapshot.selectedKeyId === key.id ? "true" : undefined}
|
|
disabled={isAction && snapshot.busy !== null}
|
|
onClick={() => send({ name: "pressLoginKey", keyId: key.id })}
|
|
>
|
|
{keyLabel(key, snapshot.activeField)}
|
|
</button>
|
|
);
|
|
})}
|
|
</div>
|
|
</main>
|
|
<footer>
|
|
<span><b>D-Pad</b> Move <b>A</b> Type</span>
|
|
<strong>{snapshot.busy ? `${snapshot.busy === "create" ? "Creating account" : "Opening gateway"}…` : notice || `${snapshot.activeField === "username" ? "Username" : "Password"} selected`}</strong>
|
|
</footer>
|
|
</FrontSurface>
|
|
);
|
|
}
|