good work
This commit is contained in:
+345
-83
@@ -1,4 +1,4 @@
|
|||||||
export type LineColor = 'green' | 'amber' | 'red' | 'dim';
|
export type LineColor = "green" | "amber" | "red" | "dim";
|
||||||
|
|
||||||
export interface BootLine {
|
export interface BootLine {
|
||||||
text: string;
|
text: string;
|
||||||
@@ -9,96 +9,358 @@ export interface BootLine {
|
|||||||
|
|
||||||
export const BOOT_SEQUENCE: BootLine[] = [
|
export const BOOT_SEQUENCE: BootLine[] = [
|
||||||
// Phase 1 - BIOS POST
|
// Phase 1 - BIOS POST
|
||||||
{ text: 'AWARD BIOS v6.9.420 (c) 2024 Stift15 Systems Ltd.', color: 'dim', delay: 300 },
|
{
|
||||||
{ text: 'Performing Power-On Self Test...', color: 'dim', delay: 200 },
|
text: "AWARD BIOS v6.9.420 (c) 2024 Stift15 Systems Ltd.",
|
||||||
{ text: '', color: 'dim', delay: 100 },
|
color: "dim",
|
||||||
{ text: 'CPU: AMD Ryzen 9 9950X "GamerFuel Edition" @ 5.7 GHz .......... OK', color: 'green', delay: 150 },
|
delay: 300,
|
||||||
{ text: 'RAM: 65536 MB DDR5-6000 ....................................... OK', color: 'green', delay: 120 },
|
},
|
||||||
{ text: 'GPU: NVIDIA RTX 5090 "Mortgage Edition" ........................ OK', color: 'green', delay: 120 },
|
{ text: "Performing Power-On Self Test...", color: "dim", delay: 200 },
|
||||||
{ text: 'RGB Subsystem .................................................. SYNCED (mood: aggressive)', color: 'green', delay: 100 },
|
{ text: "", color: "dim", delay: 100 },
|
||||||
{ text: 'Gamer Chair Lumbar Support ..................................... RECLINED', color: 'green', delay: 100 },
|
{
|
||||||
{ text: '', color: 'dim', delay: 80 },
|
text: 'CPU: AMD Ryzen 9 9950X "GamerFuel Edition" @ 5.7 GHz .......... OK',
|
||||||
{ text: 'Detecting boot devices...', color: 'dim', delay: 400 },
|
color: "green",
|
||||||
{ text: 'Boot device: /dev/sda1 (labeled "DO_NOT_DELETE_MOM")', color: 'dim', delay: 300 },
|
delay: 150,
|
||||||
{ text: '', color: 'dim', delay: 200 },
|
},
|
||||||
|
{
|
||||||
|
text: "RAM: 65536 MB DDR5-6000 ....................................... OK",
|
||||||
|
color: "green",
|
||||||
|
delay: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: 'GPU: NVIDIA RTX 5090 "Mortgage Edition" ........................ OK',
|
||||||
|
color: "green",
|
||||||
|
delay: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "RGB Subsystem .................................................. SYNCED (mood: aggressive)",
|
||||||
|
color: "green",
|
||||||
|
delay: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "Gamer Chair Lumbar Support ..................................... RECLINED",
|
||||||
|
color: "green",
|
||||||
|
delay: 100,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 80 },
|
||||||
|
{ text: "Detecting boot devices...", color: "dim", delay: 400 },
|
||||||
|
{
|
||||||
|
text: 'Boot device: /dev/sda1 (labeled "DO_NOT_DELETE_MOM")',
|
||||||
|
color: "dim",
|
||||||
|
delay: 300,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 200 },
|
||||||
|
|
||||||
// Phase 2 - Kernel Boot
|
// Phase 2 - Kernel Boot
|
||||||
{ text: 'Loading GRUB 2.12 ...', color: 'green', delay: 300 },
|
{ text: "Loading GRUB 2.12 ...", color: "green", delay: 300 },
|
||||||
{ text: 'kernel: Linux 6.9.0-gamer-generic loading...', color: 'green', delay: 200 },
|
{
|
||||||
{ text: '[ 0.000000] Command line: root=/dev/sda1 ro quiet splash=off fps_unlock=true', color: 'dim', delay: 80 },
|
text: "kernel: Linux 6.9.0-gamer-generic loading...",
|
||||||
{ text: '[ 0.004200] Initializing gaming subsystems...', color: 'dim', delay: 80 },
|
color: "green",
|
||||||
{ text: '[ 0.013370] RGB controller mapped to /dev/rgb0', color: 'dim', delay: 60 },
|
delay: 200,
|
||||||
{ text: '[ 0.024601] Registering thermal zone: "CPU Package" (target: yes)', color: 'dim', delay: 60 },
|
},
|
||||||
{ text: '[ 0.031337] Loading module: gamer_posture.ko', color: 'dim', delay: 60 },
|
{
|
||||||
{ text: '[ 0.042069] Mounting /dev/fridge (read-only, sadness mode)', color: 'dim', delay: 60 },
|
text: "[ 0.000000] Command line: root=/dev/sda1 ro quiet splash=off fps_unlock=true",
|
||||||
{ text: '', color: 'dim', delay: 200 },
|
color: "dim",
|
||||||
|
delay: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 0.004200] Initializing gaming subsystems...",
|
||||||
|
color: "dim",
|
||||||
|
delay: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 0.013370] RGB controller mapped to /dev/rgb0",
|
||||||
|
color: "dim",
|
||||||
|
delay: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '[ 0.024601] Registering thermal zone: "CPU Package" (target: yes)',
|
||||||
|
color: "dim",
|
||||||
|
delay: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 0.031337] Loading module: gamer_posture.ko",
|
||||||
|
color: "dim",
|
||||||
|
delay: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 0.042069] Mounting /dev/fridge (read-only, sadness mode)",
|
||||||
|
color: "dim",
|
||||||
|
delay: 60,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 200 },
|
||||||
|
|
||||||
// Phase 3 - Services Starting
|
// Phase 3 - Services Starting
|
||||||
{ text: '[ OK ] Started Gamer Posture Reminder Service', color: 'green', delay: 150 },
|
{
|
||||||
{ text: '[ OK ] Started Mountain Dew Level Monitor', color: 'green', delay: 120 },
|
text: "[ OK ] Started Gamer Posture Reminder Service",
|
||||||
{ text: '[ OK ] Started Discord Rich Presence Daemon', color: 'green', delay: 120 },
|
color: "green",
|
||||||
{ text: '[ OK ] Started Mechanical Keyboard Click Amplifier', color: 'green', delay: 100 },
|
delay: 150,
|
||||||
{ text: '[ OK ] Started Rage Quit Prevention System (v0.1-beta)', color: 'green', delay: 100 },
|
},
|
||||||
{ text: '[WARN] Hot Pocket Proximity Sensor returned NaN', color: 'amber', delay: 300 },
|
{
|
||||||
{ text: '[ OK ] Started Frank\'s "Temporary" Minecraft Server (uptime: 847 days)', color: 'green', delay: 150 },
|
text: "[ OK ] Started Mountain Dew Level Monitor",
|
||||||
{ text: '[ OK ] Started nginx web server on port 80', color: 'green', delay: 100 },
|
color: "green",
|
||||||
{ text: '[ OK ] Started Node.js application server on port 3000', color: 'green', delay: 100 },
|
delay: 120,
|
||||||
{ text: '[WARN] server.js has mass of 2.1 GB (node_modules singularity detected)', color: 'amber', delay: 400 },
|
},
|
||||||
{ text: '[WARN] Gravitational pull from node_modules is affecting nearby files', color: 'amber', delay: 200 },
|
{
|
||||||
{ text: '', color: 'dim', delay: 300 },
|
text: "[ OK ] Started Teamspeak Rich Presence Daemon",
|
||||||
|
color: "green",
|
||||||
|
delay: 120,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ OK ] Started Mechanical Keyboard Click Amplifier",
|
||||||
|
color: "green",
|
||||||
|
delay: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ OK ] Started Rage Quit Prevention System (v0.1-beta)",
|
||||||
|
color: "green",
|
||||||
|
delay: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[WARN] Hot Pocket Proximity Sensor returned NaN",
|
||||||
|
color: "amber",
|
||||||
|
delay: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '[ OK ] Started Frank\'s "Temporary" Minecraft Server (uptime: 847 days)',
|
||||||
|
color: "green",
|
||||||
|
delay: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ OK ] Started nginx web server on port 80",
|
||||||
|
color: "green",
|
||||||
|
delay: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ OK ] Started Node.js application server on port 3000",
|
||||||
|
color: "green",
|
||||||
|
delay: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[WARN] server.js has mass of 2.1 GB (node_modules singularity detected)",
|
||||||
|
color: "amber",
|
||||||
|
delay: 400,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[WARN] Gravitational pull from node_modules is affecting nearby files",
|
||||||
|
color: "amber",
|
||||||
|
delay: 200,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 300 },
|
||||||
|
|
||||||
// Phase 4 - The Crash
|
// Phase 4 - The Crash
|
||||||
{ text: '[FAIL] HTTP Health Check: GET /api/status returned... nothing', color: 'red', delay: 600 },
|
{
|
||||||
{ text: '[FAIL] The backend is not responding.', color: 'red', delay: 400 },
|
text: "[FAIL] HTTP Health Check: GET /api/status returned... nothing",
|
||||||
{ text: '[FAIL] The backend has never responded. To anything. Ever.', color: 'red', delay: 500 },
|
color: "red",
|
||||||
{ text: '', color: 'dim', delay: 200 },
|
delay: 600,
|
||||||
{ text: '[WARN] Attempting emergency restart...', color: 'amber', delay: 800 },
|
},
|
||||||
{ text: '[ 4.040404] Process \'server.js\' invoked OOM killer', color: 'red', delay: 300 },
|
{ text: "[FAIL] The backend is not responding.", color: "red", delay: 400 },
|
||||||
{ text: '[ 4.040405] OOM killer selected process \'stift15-game-server\' (adj 1000)', color: 'red', delay: 150 },
|
{
|
||||||
{ text: '[ 4.040406] Out of memory: Killed process 1337 (stift15-game-server)', color: 'red', delay: 150 },
|
text: "[FAIL] The backend has never responded. To anything. Ever.",
|
||||||
{ text: '[ 4.040407] Reason: Frank downloaded the entire Steam Workshop into /tmp', color: 'red', delay: 300 },
|
color: "red",
|
||||||
{ text: '', color: 'dim', delay: 200 },
|
delay: 500,
|
||||||
{ text: '[FAIL] Emergency restart failed. Exit code: 418 (I\'m a teapot)', color: 'red', delay: 600 },
|
},
|
||||||
{ text: '', color: 'dim', delay: 800 },
|
{ text: "", color: "dim", delay: 200 },
|
||||||
|
{
|
||||||
|
text: "[WARN] Attempting emergency restart...",
|
||||||
|
color: "amber",
|
||||||
|
delay: 800,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 4.040404] Process 'server.js' invoked OOM killer",
|
||||||
|
color: "red",
|
||||||
|
delay: 300,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 4.040405] OOM killer selected process 'stift15-game-server' (adj 1000)",
|
||||||
|
color: "red",
|
||||||
|
delay: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 4.040406] Out of memory: Killed process 1337 (stift15-game-server)",
|
||||||
|
color: "red",
|
||||||
|
delay: 150,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "[ 4.040407] Reason: Frank downloaded the entire Steam Workshop into /tmp",
|
||||||
|
color: "red",
|
||||||
|
delay: 300,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 200 },
|
||||||
|
{
|
||||||
|
text: "[FAIL] Emergency restart failed. Exit code: 418 (I'm a teapot)",
|
||||||
|
color: "red",
|
||||||
|
delay: 600,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 800 },
|
||||||
|
|
||||||
// Phase 5 - The 503 Error (typewriter lines)
|
// Phase 5 - The 503 Error (typewriter lines)
|
||||||
{ text: '============================================================', color: 'red', delay: 400, typewriter: true },
|
{
|
||||||
{ text: ' ERROR 503 - SERVICE UNAVAILABLE', color: 'red', delay: 100, typewriter: true },
|
text: "============================================================",
|
||||||
{ text: '============================================================', color: 'red', delay: 100, typewriter: true },
|
color: "red",
|
||||||
{ text: '', color: 'dim', delay: 200 },
|
delay: 400,
|
||||||
{ text: ' The server hosting the Stift15 gaming group has encountered', color: 'green', delay: 60, typewriter: true },
|
typewriter: true,
|
||||||
{ text: ' a fatal error and is currently taking a rage-quit break.', color: 'green', delay: 60, typewriter: true },
|
},
|
||||||
{ text: '', color: 'dim', delay: 300 },
|
{
|
||||||
{ text: ' DIAGNOSTIC SUMMARY:', color: 'amber', delay: 200, typewriter: true },
|
text: " ERROR 503 - SERVICE UNAVAILABLE",
|
||||||
{ text: ' - Last stable connection: "lol"', color: 'green', delay: 80, typewriter: true },
|
color: "red",
|
||||||
{ text: ' - Uptime before crash: 4h 20m 69s', color: 'green', delay: 80, typewriter: true },
|
delay: 100,
|
||||||
{ text: ' - Packets lost: all of them', color: 'green', delay: 80, typewriter: true },
|
typewriter: true,
|
||||||
{ text: ' - Root cause: someone git pushed node_modules to prod', color: 'green', delay: 80, typewriter: true },
|
},
|
||||||
{ text: ' - Secondary cause: Frank', color: 'green', delay: 80, typewriter: true },
|
{
|
||||||
{ text: ' - Tertiary cause: also Frank', color: 'green', delay: 80, typewriter: true },
|
text: "============================================================",
|
||||||
{ text: ' - Frank\'s response: "works on my machine"', color: 'green', delay: 80, typewriter: true },
|
color: "red",
|
||||||
{ text: '', color: 'dim', delay: 300 },
|
delay: 100,
|
||||||
{ text: ' RECOMMENDED ACTIONS:', color: 'amber', delay: 200, typewriter: true },
|
typewriter: true,
|
||||||
{ text: ' 1. Touch grass (estimated ETA: never)', color: 'green', delay: 80, typewriter: true },
|
},
|
||||||
{ text: ' 2. Check if Frank pushed to main again', color: 'green', delay: 80, typewriter: true },
|
{ text: "", color: "dim", delay: 200 },
|
||||||
{ text: ' 3. Blame the lag', color: 'green', delay: 80, typewriter: true },
|
{
|
||||||
{ text: ' 4. Alt+F4 your expectations', color: 'green', delay: 80, typewriter: true },
|
text: " The server hosting Stift15.de has encountered",
|
||||||
{ text: ' 5. Have you tried turning Frank off and on again?', color: 'green', delay: 80, typewriter: true },
|
color: "green",
|
||||||
{ text: '', color: 'dim', delay: 300 },
|
delay: 60,
|
||||||
{ text: ' If this error persists, please scream into the void or', color: 'green', delay: 60, typewriter: true },
|
typewriter: true,
|
||||||
{ text: ' message the server admin, who is also screaming into the void.', color: 'green', delay: 60, typewriter: true },
|
},
|
||||||
{ text: '', color: 'dim', delay: 400 },
|
{
|
||||||
{ text: '============================================================', color: 'red', delay: 100, typewriter: true },
|
text: " a fatal error and is currently taking a rage-quit break.",
|
||||||
{ text: ' SERVER ADMIN NOTE:', color: 'amber', delay: 200, typewriter: true },
|
color: "green",
|
||||||
{ text: ' No, I will not fix this at 3 AM. I have work tomorrow.', color: 'green', delay: 60, typewriter: true },
|
delay: 60,
|
||||||
{ text: ' The server can stay dead. It builds character.', color: 'green', delay: 60, typewriter: true },
|
typewriter: true,
|
||||||
{ text: ' Also Frank still owes me for last month\'s hosting.', color: 'green', delay: 60, typewriter: true },
|
},
|
||||||
{ text: '============================================================', color: 'red', delay: 100, typewriter: true },
|
{ text: "", color: "dim", delay: 300 },
|
||||||
{ text: '', color: 'dim', delay: 1500 },
|
{
|
||||||
|
text: " DIAGNOSTIC SUMMARY:",
|
||||||
|
color: "amber",
|
||||||
|
delay: 200,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: ' - Last stable connection: "lol"',
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " - Uptime before crash: 4h 20m 69s",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " - Packets lost: all of them",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " - Root cause: someone git pushed node_modules to prod",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " - Secondary cause: Frank",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " - Tertiary cause: also Frank",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: ' - Frank\'s response: "works on my machine"',
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 300 },
|
||||||
|
{
|
||||||
|
text: " RECOMMENDED ACTIONS:",
|
||||||
|
color: "amber",
|
||||||
|
delay: 200,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " 1. Touch grass (estimated ETA: never)",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " 2. Check if Frank pushed to main again",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{ text: " 3. Blame the lag", color: "green", delay: 80, typewriter: true },
|
||||||
|
{
|
||||||
|
text: " 4. Alt+F4 your expectations",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " 5. Have you tried turning Frank off and on again?",
|
||||||
|
color: "green",
|
||||||
|
delay: 80,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 300 },
|
||||||
|
{
|
||||||
|
text: " If this error persists, please scream into the void or",
|
||||||
|
color: "green",
|
||||||
|
delay: 60,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " message the server admin, who is also screaming into the void.",
|
||||||
|
color: "green",
|
||||||
|
delay: 60,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 400 },
|
||||||
|
{
|
||||||
|
text: "============================================================",
|
||||||
|
color: "red",
|
||||||
|
delay: 100,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " SERVER ADMIN NOTE:",
|
||||||
|
color: "amber",
|
||||||
|
delay: 200,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " No, I will not fix this at 3 AM. I have work tomorrow.",
|
||||||
|
color: "green",
|
||||||
|
delay: 60,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " The server can stay dead. It builds character.",
|
||||||
|
color: "green",
|
||||||
|
delay: 60,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: " Also Frank still owes me for last month's hosting.",
|
||||||
|
color: "green",
|
||||||
|
delay: 60,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: "============================================================",
|
||||||
|
color: "red",
|
||||||
|
delay: 100,
|
||||||
|
typewriter: true,
|
||||||
|
},
|
||||||
|
{ text: "", color: "dim", delay: 1500 },
|
||||||
|
|
||||||
// Phase 6 - Reconnect
|
// Phase 6 - Reconnect
|
||||||
{ text: '> Reconnecting in 3 seconds...', color: 'dim', delay: 0 },
|
{ text: "> Reconnecting in 3 seconds...", color: "dim", delay: 0 },
|
||||||
{ text: '> ...', color: 'dim', delay: 1000 },
|
{ text: "> ...", color: "dim", delay: 1000 },
|
||||||
{ text: '> ...', color: 'dim', delay: 1000 },
|
{ text: "> ...", color: "dim", delay: 1000 },
|
||||||
{ text: '> CONNECTION ESTABLISHED', color: 'green', delay: 1000 },
|
{ text: "> CONNECTION ESTABLISHED", color: "green", delay: 1000 },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export function CommandPrompt({ onReboot, onBrick }: CommandPromptProps) {
|
|||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
const [history, setHistory] = useState<string[]>([]);
|
const [history, setHistory] = useState<string[]>([]);
|
||||||
const [historyIndex, setHistoryIndex] = useState(-1);
|
const [historyIndex, setHistoryIndex] = useState(-1);
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const bottomRef = useRef<HTMLDivElement>(null);
|
const bottomRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const scrollToBottom = useCallback(() => {
|
const scrollToBottom = useCallback(() => {
|
||||||
@@ -29,9 +29,13 @@ export function CommandPrompt({ onReboot, onBrick }: CommandPromptProps) {
|
|||||||
|
|
||||||
useEffect(scrollToBottom, [outputLines.length, scrollToBottom]);
|
useEffect(scrollToBottom, [outputLines.length, scrollToBottom]);
|
||||||
|
|
||||||
// Focus container on mount
|
// Keep input focused
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
containerRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const focusInput = useCallback(() => {
|
||||||
|
inputRef.current?.focus();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const executeCommand = useCallback(
|
const executeCommand = useCallback(
|
||||||
@@ -82,12 +86,11 @@ export function CommandPrompt({ onReboot, onBrick }: CommandPromptProps) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent) => {
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
executeCommand(input);
|
executeCommand(input);
|
||||||
setInput('');
|
setInput('');
|
||||||
} else if (e.key === 'Backspace') {
|
setHistoryIndex(-1);
|
||||||
setInput((prev) => prev.slice(0, -1));
|
|
||||||
} else if (e.key === 'ArrowUp') {
|
} else if (e.key === 'ArrowUp') {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (history.length === 0) return;
|
if (history.length === 0) return;
|
||||||
@@ -105,8 +108,6 @@ export function CommandPrompt({ onReboot, onBrick }: CommandPromptProps) {
|
|||||||
setHistoryIndex(newIndex);
|
setHistoryIndex(newIndex);
|
||||||
setInput(history[newIndex]);
|
setInput(history[newIndex]);
|
||||||
}
|
}
|
||||||
} else if (e.key.length === 1 && !e.ctrlKey && !e.metaKey) {
|
|
||||||
setInput((prev) => prev + e.key);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[input, executeCommand, history, historyIndex]
|
[input, executeCommand, history, historyIndex]
|
||||||
@@ -121,11 +122,8 @@ export function CommandPrompt({ onReboot, onBrick }: CommandPromptProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
className="absolute inset-0 z-0 overflow-y-auto p-4 font-mono text-sm outline-none cursor-text"
|
||||||
className="absolute inset-0 z-0 overflow-y-auto p-4 font-mono text-sm outline-none"
|
onClick={focusInput}
|
||||||
tabIndex={0}
|
|
||||||
onKeyDown={handleKeyDown}
|
|
||||||
onClick={() => containerRef.current?.focus()}
|
|
||||||
>
|
>
|
||||||
{outputLines.map((line, i) => (
|
{outputLines.map((line, i) => (
|
||||||
<div
|
<div
|
||||||
@@ -137,13 +135,27 @@ export function CommandPrompt({ onReboot, onBrick }: CommandPromptProps) {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Input line */}
|
{/* Input line */}
|
||||||
<div className="text-term-green whitespace-pre leading-[1.4]">
|
<div className="text-term-green whitespace-pre leading-[1.4] flex items-center">
|
||||||
{PROMPT}
|
<span>{PROMPT}</span>
|
||||||
{input}
|
<span>{input}</span>
|
||||||
<span
|
<span
|
||||||
className="inline-block w-[0.6em] h-[1em] bg-term-green ml-0.5 align-middle"
|
className="inline-block w-[0.6em] h-[1em] bg-term-green ml-0.5"
|
||||||
style={{ animation: 'blink 1s step-end infinite' }}
|
style={{ animation: 'blink 1s step-end infinite' }}
|
||||||
/>
|
/>
|
||||||
|
{/* Hidden native input for reliable focus and mobile keyboard support */}
|
||||||
|
<input
|
||||||
|
ref={inputRef}
|
||||||
|
type="text"
|
||||||
|
value={input}
|
||||||
|
onChange={(e) => setInput(e.target.value)}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onBlur={focusInput}
|
||||||
|
className="absolute opacity-0 w-0 h-0"
|
||||||
|
autoCapitalize="off"
|
||||||
|
autoCorrect="off"
|
||||||
|
autoComplete="off"
|
||||||
|
spellCheck={false}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div ref={bottomRef} />
|
<div ref={bottomRef} />
|
||||||
|
|||||||
Reference in New Issue
Block a user