From 1e1bdf50898cf8bcccc232b8170ebea363f18c93 Mon Sep 17 00:00:00 2001 From: Prozilla Date: Thu, 2 May 2024 22:48:32 +0200 Subject: [PATCH] Updated terminal app --- src/components/apps/terminal/Terminal.jsx | 17 ++++++++++++----- .../apps/terminal/Terminal.module.css | 4 +++- src/config/apps/terminal.config.js | 3 +++ src/features/apps/terminal/command.js | 8 ++++++++ src/features/apps/terminal/commands.js | 10 ++++++++++ src/features/apps/terminal/commands/cat.js | 7 ++++--- src/features/apps/terminal/commands/cd.js | 6 +++--- src/features/apps/terminal/commands/clear.js | 1 - src/features/apps/terminal/commands/compgen.js | 3 +++ src/features/apps/terminal/commands/dir.js | 2 +- src/features/apps/terminal/commands/echo.js | 2 +- src/features/apps/terminal/commands/exit.js | 2 +- src/features/apps/terminal/commands/fortune.js | 2 +- src/features/apps/terminal/commands/help.js | 9 +++++---- src/features/apps/terminal/commands/hostname.js | 2 +- src/features/apps/terminal/commands/ls.js | 7 ++++--- src/features/apps/terminal/commands/make.js | 3 ++- src/features/apps/terminal/commands/man.js | 14 +++++++++----- src/features/apps/terminal/commands/mkdir.js | 4 ++-- src/features/apps/terminal/commands/neofetch.js | 2 +- src/features/apps/terminal/commands/pwd.js | 2 +- src/features/apps/terminal/commands/reboot.js | 2 +- src/features/apps/terminal/commands/reload.js | 6 ++---- src/features/apps/terminal/commands/rev.js | 4 ++-- src/features/apps/terminal/commands/rm.js | 5 +++-- src/features/apps/terminal/commands/rmdir.js | 5 +++-- src/features/apps/terminal/commands/sl.js | 11 ++++++----- src/features/apps/terminal/commands/touch.js | 9 +++++---- src/features/apps/terminal/commands/uptime.js | 2 +- src/features/apps/terminal/commands/whatis.js | 7 ++++--- src/hooks/_utils/keyboard.js | 16 ++++++++++++---- 31 files changed, 114 insertions(+), 63 deletions(-) diff --git a/src/components/apps/terminal/Terminal.jsx b/src/components/apps/terminal/Terminal.jsx index f038471..862ff94 100644 --- a/src/components/apps/terminal/Terminal.jsx +++ b/src/components/apps/terminal/Terminal.jsx @@ -37,6 +37,13 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) { setStreamFocused(true); }, [streamFocused, streamOutput, streamRef]); + useEffect(() => { + if (!inputRef.current || !active) + return; + + inputRef.current.focus(); + }, [inputRef, active]); + const prefix = `${ANSI.fg.cyan + USERNAME}@${HOSTNAME + ANSI.reset}:` + `${ANSI.fg.blue + (currentDirectory.root ? "/" : currentDirectory.path) + ANSI.reset}$ `; @@ -102,7 +109,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) { const command = CommandsManager.find(commandName); if (!command) - return `${commandName}: Command not found`; + return CommandsManager.formatError(commandName, "Command not found"); // Get options const options = []; @@ -138,10 +145,10 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) { // Check usage if (command.requireArgs && args.length === 0) - return `${commandName}: Incorrect usage: ${commandName} requires at least 1 argument`; + return CommandsManager.formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 argument`); if (command.requireOptions && options.length === 0) - return `${commandName}: Incorrect usage: ${commandName} requires at least 1 option`; + return CommandsManager.formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`); // Execute command let response = null; @@ -162,13 +169,13 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) { }); if (response == null) - return `${commandName}: Command failed`; + return CommandsManager.formatError(commandName, "Command failed"); if (!response.blank) return response; } catch (error) { console.error(error); - return `${commandName}: Command failed`; + return CommandsManager.formatError(commandName, "Command failed"); } }; diff --git a/src/components/apps/terminal/Terminal.module.css b/src/components/apps/terminal/Terminal.module.css index da93a36..d8eb5e8 100644 --- a/src/components/apps/terminal/Terminal.module.css +++ b/src/components/apps/terminal/Terminal.module.css @@ -93,7 +93,7 @@ cursor: text; } -.ansi-black-fg { color: var(--dark-grey-e); } +.ansi-black-fg { color: var(--dark-grey-e); } .ansi-red-fg { color: var(--red-b); } .ansi-green-fg { color: var(--green-b); } .ansi-yellow-fg { color: var(--yellow-b); } @@ -119,3 +119,5 @@ .ansi-magenta-bg { background-color: var(--purple-a); } .ansi-cyan-bg { background-color: var(--cyan-a); } .ansi-white-bg { background-color: var(--white-a); } + +.ansi-dim { opacity: 0.65; } \ No newline at end of file diff --git a/src/config/apps/terminal.config.js b/src/config/apps/terminal.config.js index f5680df..823c6cb 100644 --- a/src/config/apps/terminal.config.js +++ b/src/config/apps/terminal.config.js @@ -15,6 +15,9 @@ export const ANSI = { }, bg: { + }, + decoration: { + dim: "\u001b[2m", }, reset: "\u001b[0m", }; \ No newline at end of file diff --git a/src/features/apps/terminal/command.js b/src/features/apps/terminal/command.js index 75a773a..fb9d3eb 100644 --- a/src/features/apps/terminal/command.js +++ b/src/features/apps/terminal/command.js @@ -42,6 +42,14 @@ export default class Command { */ setName(name) { this.name = name; + + if (!this.manual?.usage) { + if (!this.manual) + this.manual = {}; + + this.manual.usage = name; + } + return this; } diff --git a/src/features/apps/terminal/commands.js b/src/features/apps/terminal/commands.js index 307a9d6..004e62f 100644 --- a/src/features/apps/terminal/commands.js +++ b/src/features/apps/terminal/commands.js @@ -1,3 +1,4 @@ +import { ANSI } from "../../../config/apps/terminal.config.js"; import Command from "./command.js"; let commands = []; @@ -54,4 +55,13 @@ export default class CommandsManager { loadCommands(); CommandsManager.COMMANDS = commands; } + + /** + * @param {string} commandName + * @param {string} error + * @returns {string} + */ + static formatError(commandName, error) { + return `${ANSI.fg.red}${commandName}: ${error}${ANSI.reset}`; + } } \ No newline at end of file diff --git a/src/features/apps/terminal/commands/cat.js b/src/features/apps/terminal/commands/cat.js index 402929b..b2510ad 100644 --- a/src/features/apps/terminal/commands/cat.js +++ b/src/features/apps/terminal/commands/cat.js @@ -1,19 +1,20 @@ import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js"; import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const cat = new Command() .setRequireArgs(true) .setManual({ purpose: "Concetenate files and display on the terminal screen", - usage: "cat [OPTION]... [FILE]...", - description: "Concetenate FILE(s) to standard output." + usage: "cat [options] [files]", + description: "Concetenate files to standard output." }) .setExecute(function(args, { currentDirectory, options }) { const { name, extension } = VirtualFile.convertId(args[0]); const file = currentDirectory.findFile(name, extension); if (!file) - return `${this.name}: ${args[0]}: No such file`; + return CommandsManager.formatError(this.name, `${args[0]}: No such file`); if (file.content) { if (!options.includes("e")) { diff --git a/src/features/apps/terminal/commands/cd.js b/src/features/apps/terminal/commands/cd.js index 2d4e039..28b9385 100644 --- a/src/features/apps/terminal/commands/cd.js +++ b/src/features/apps/terminal/commands/cd.js @@ -1,9 +1,10 @@ import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const cd = new Command() .setManual({ purpose: "Change the current directory", - usage: "cd path", + usage: "cd [PATH]", description: "Change working directory to given path (the home directory by default)." }) .setExecute(function(args, { currentDirectory, setCurrentDirectory }) { @@ -11,9 +12,8 @@ export const cd = new Command() const destination = currentDirectory.navigate(path); if (!destination) - return `${this.name}: ${args[0]}: No such file or directory`; + return CommandsManager.formatError(this.name, `${args[0]}: No such file or directory`); - console.log(destination); setCurrentDirectory(destination); return { blank: true }; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/clear.js b/src/features/apps/terminal/commands/clear.js index 7f3d8e2..8d46381 100644 --- a/src/features/apps/terminal/commands/clear.js +++ b/src/features/apps/terminal/commands/clear.js @@ -3,7 +3,6 @@ import Command from "../command.js"; export const clear = new Command() .setManual({ purpose: "Clear terminal screen", - usage: "clear", }) .setExecute(function(args, { pushHistory }) { pushHistory({ diff --git a/src/features/apps/terminal/commands/compgen.js b/src/features/apps/terminal/commands/compgen.js index 3c50c57..043b1f0 100644 --- a/src/features/apps/terminal/commands/compgen.js +++ b/src/features/apps/terminal/commands/compgen.js @@ -2,6 +2,9 @@ import Command from "../command.js"; import CommandsManager from "../commands.js"; export const compgen = new Command() + .setManual({ + purpose: "Display a list of all commands" + }) .setRequireOptions(true) .setExecute(function(args, { options }) { if (options.includes("c")) { diff --git a/src/features/apps/terminal/commands/dir.js b/src/features/apps/terminal/commands/dir.js index 611af40..66ebb5b 100644 --- a/src/features/apps/terminal/commands/dir.js +++ b/src/features/apps/terminal/commands/dir.js @@ -4,7 +4,7 @@ export const dir = new Command() .setManual({ purpose: "List all directories in the current directory" }) - .setExecute((args, { currentDirectory }) => { + .setExecute(function(args, { currentDirectory }) { const folderNames = currentDirectory.subFolders.map((folder) => folder.id); if (folderNames.length === 0) diff --git a/src/features/apps/terminal/commands/echo.js b/src/features/apps/terminal/commands/echo.js index aa587eb..31cf8e4 100644 --- a/src/features/apps/terminal/commands/echo.js +++ b/src/features/apps/terminal/commands/echo.js @@ -4,6 +4,6 @@ export const echo = new Command() .setManual({ purpose: "Display text on the terminal screen" }) - .setExecute((args, { rawInputValue }) => { + .setExecute(function(args, { rawInputValue }) { return rawInputValue; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/exit.js b/src/features/apps/terminal/commands/exit.js index 90a6841..5a61c2b 100644 --- a/src/features/apps/terminal/commands/exit.js +++ b/src/features/apps/terminal/commands/exit.js @@ -4,7 +4,7 @@ export const exit = new Command() .setManual({ purpose: "Quit terminal interface" }) - .setExecute((args, { exit }) => { + .setExecute(function(args, { exit }) { exit(); return { blank: true }; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/fortune.js b/src/features/apps/terminal/commands/fortune.js index 6945b60..50eac79 100644 --- a/src/features/apps/terminal/commands/fortune.js +++ b/src/features/apps/terminal/commands/fortune.js @@ -58,6 +58,6 @@ export const fortune = new Command() .setManual({ purpose: "Tell fortune" }) - .setExecute(() => { + .setExecute(function() { return randomFromArray(FORTUNES); }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/help.js b/src/features/apps/terminal/commands/help.js index 406baee..2004607 100644 --- a/src/features/apps/terminal/commands/help.js +++ b/src/features/apps/terminal/commands/help.js @@ -1,12 +1,13 @@ +import { ANSI } from "../../../../config/apps/terminal.config.js"; import Command from "../command.js"; import CommandsManager from "../commands.js"; export const help = new Command() - .setExecute((args) => { + .setExecute(function(args) { if (args.length === 0) { return CommandsManager.COMMANDS.map((command) => { if (command.manual?.purpose) { - return `${command.name} - ${command.manual.purpose}`; + return `${command.name} - ${ANSI.fg.green}${ANSI.decoration.dim}${command.manual.purpose}${ANSI.reset}`; } else { return command.name; } @@ -17,10 +18,10 @@ export const help = new Command() const command = CommandsManager.find(commandName); if (!command) - return `${this.name}: ${commandName}: Command not found`; + return CommandsManager.formatError(this.name, `${commandName}: Command not found`); if (!command.manual?.purpose) - return `${this.name}: ${commandName}: No manual found`; + return CommandsManager.formatError(this.name, `${commandName}: No manual found`); return command.manual.purpose; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/hostname.js b/src/features/apps/terminal/commands/hostname.js index 8dffc0f..e7c7ab6 100644 --- a/src/features/apps/terminal/commands/hostname.js +++ b/src/features/apps/terminal/commands/hostname.js @@ -4,6 +4,6 @@ export const hostname = new Command() .setManual({ purpose: "Display the hostname" }) - .setExecute((args, { hostname }) => { + .setExecute(function(args, { hostname }) { return hostname; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/ls.js b/src/features/apps/terminal/commands/ls.js index f61bd99..2ee34e3 100644 --- a/src/features/apps/terminal/commands/ls.js +++ b/src/features/apps/terminal/commands/ls.js @@ -1,11 +1,12 @@ import { ANSI } from "../../../../config/apps/terminal.config.js"; import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const ls = new Command() .setManual({ purpose: "List directory contents", - usage: "ls [OPTION]... [FILE]...", - description: "List information about the FILEs (the current directory by default)." + usage: "ls [options] [files]", + description: "List information about directories or files (the current directory by default)." }) .setExecute(function(args, { currentDirectory }) { let directory = currentDirectory; @@ -15,7 +16,7 @@ export const ls = new Command() } if (!directory) - return `${this.name}: Cannot access '${args[0]}': No such file or directory`; + return CommandsManager.formatError(this.name, `Cannot access '${args[0]}': No such file or directory`); const folderNames = directory.subFolders.map((folder) => `${ANSI.fg.blue}${folder.id}${ANSI.reset}`); const fileNames = directory.files.map((file) => file.id); diff --git a/src/features/apps/terminal/commands/make.js b/src/features/apps/terminal/commands/make.js index 4e13102..1cb9187 100644 --- a/src/features/apps/terminal/commands/make.js +++ b/src/features/apps/terminal/commands/make.js @@ -1,8 +1,9 @@ import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const make = new Command() .setRequireArgs(true) .setExecute(function(args) { if (args[0] === "love") - return `${this.name}: *** No rule to make target 'love'. Stop.`; + return CommandsManager.formatError(this.name, "*** No rule to make target 'love'. Stop."); }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/man.js b/src/features/apps/terminal/commands/man.js index f6a270f..3d14fb2 100644 --- a/src/features/apps/terminal/commands/man.js +++ b/src/features/apps/terminal/commands/man.js @@ -1,3 +1,4 @@ +import { ANSI } from "../../../../config/apps/terminal.config.js"; import Command from "../command.js"; import CommandsManager from "../commands.js"; @@ -7,8 +8,8 @@ export const man = new Command() .setRequireArgs(true) .setManual({ purpose: "Show system reference manuals", - usage: "man [OPTION]... page\n" - + "man -k [OPTION]... regexp", + usage: "man [options] page\n" + + "man -k [options] regexp", description: "Each page arguments given to man is normally the name of a command.\n" + "The manual page associated with this command is then found and displayed.", options: { @@ -47,7 +48,7 @@ export const man = new Command() const sections = [["NAME"]]; if (manual.purpose) { - sections[0].push(formatText(`${commandName} - ${command.manual.purpose}`)); + sections[0].push(formatText(`${commandName} - ${ANSI.decoration.dim}${ANSI.fg.yellow}${command.manual.purpose}${ANSI.reset}`)); } else { sections[0].push(formatText(commandName)); } @@ -70,10 +71,13 @@ export const man = new Command() sections.push([ "OPTIONS", formatText(Object.entries(manual.options).map(([key, value]) => { - return `${key} ${value}`; + return `${key} ${ANSI.decoration.dim}${ANSI.fg.yellow}${value}${ANSI.reset}`; }).join("\n")) ]); } - return sections.map((section) => section.join("\n")).join("\n\n"); + return sections.map((section) => { + section[0] = ANSI.fg.yellow + section[0] + ANSI.reset; + return section.join("\n"); + }).join("\n\n"); }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/mkdir.js b/src/features/apps/terminal/commands/mkdir.js index 8df0690..6427328 100644 --- a/src/features/apps/terminal/commands/mkdir.js +++ b/src/features/apps/terminal/commands/mkdir.js @@ -2,10 +2,10 @@ import Command from "../command.js"; export const mkdir = new Command() .setManual({ - purpose: "Create directory" + purpose: "Create a directory" }) .setRequireArgs(true) - .setExecute((args, { currentDirectory }) => { + .setExecute(function(args, { currentDirectory }) { const name = args[0]; if (currentDirectory.findSubFolder(name)) diff --git a/src/features/apps/terminal/commands/neofetch.js b/src/features/apps/terminal/commands/neofetch.js index db15d51..cb870af 100644 --- a/src/features/apps/terminal/commands/neofetch.js +++ b/src/features/apps/terminal/commands/neofetch.js @@ -10,7 +10,7 @@ export const neofetch = new Command() .setManual({ purpose: "Fetch system information" }) - .setExecute((args, { username, hostname }) => { + .setExecute(function(args, { username, hostname }) { const leftColumn = ANSI_ASCII_LOGO.split("\n"); const rightColumnWidth = username.length + hostname.length + 1; diff --git a/src/features/apps/terminal/commands/pwd.js b/src/features/apps/terminal/commands/pwd.js index 6193479..51d5457 100644 --- a/src/features/apps/terminal/commands/pwd.js +++ b/src/features/apps/terminal/commands/pwd.js @@ -4,7 +4,7 @@ export const pwd = new Command() .setManual({ purpose: "Display path of the current directory" }) - .setExecute((args, { currentDirectory }) => { + .setExecute(function(args, { currentDirectory }) { if (currentDirectory.root) { return "/"; } else { diff --git a/src/features/apps/terminal/commands/reboot.js b/src/features/apps/terminal/commands/reboot.js index 12ca09c..549386f 100644 --- a/src/features/apps/terminal/commands/reboot.js +++ b/src/features/apps/terminal/commands/reboot.js @@ -5,7 +5,7 @@ export const reboot = new Command() .setManual({ purpose: "Reboot the system" }) - .setExecute(() => { + .setExecute(function() { reloadViewport(); return { blank: true }; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/reload.js b/src/features/apps/terminal/commands/reload.js index 8d46fcb..ffa71fe 100644 --- a/src/features/apps/terminal/commands/reload.js +++ b/src/features/apps/terminal/commands/reload.js @@ -3,11 +3,9 @@ import CommandsManager from "../commands.js"; export const reload = new Command() .setManual({ - purpose: "Reload terminal commands", - usage: "reload", + purpose: "Reload the terminal", }) - .setExecute(() => { + .setExecute(function() { CommandsManager.reload(); - return { blank: true }; }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/rev.js b/src/features/apps/terminal/commands/rev.js index 4197366..7346eab 100644 --- a/src/features/apps/terminal/commands/rev.js +++ b/src/features/apps/terminal/commands/rev.js @@ -2,8 +2,8 @@ import Command from "../command.js"; export const rev = new Command() .setManual({ - purpose: "Reverses text." + purpose: "Display the reverse of a text" }) - .setExecute((args, { rawInputValue }) => { + .setExecute(function(args, { rawInputValue }) { return rawInputValue.split("").reverse().join(""); }); \ No newline at end of file diff --git a/src/features/apps/terminal/commands/rm.js b/src/features/apps/terminal/commands/rm.js index 2ea971f..d455e01 100644 --- a/src/features/apps/terminal/commands/rm.js +++ b/src/features/apps/terminal/commands/rm.js @@ -1,17 +1,18 @@ import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js"; import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const rm = new Command() .setRequireArgs(true) .setManual({ purpose: "Remove a file" }) - .setExecute((args, { currentDirectory }) => { + .setExecute(function(args, { currentDirectory }) { const { name, extension } = VirtualFile.convertId(args[0]); const file = currentDirectory.findFile(name, extension); if (!file) - return `${this.name}: ${args[0]}: No such file`; + return CommandsManager.formatError(this.name, `${args[0]}: No such file`); file.delete(); return { blank: true }; diff --git a/src/features/apps/terminal/commands/rmdir.js b/src/features/apps/terminal/commands/rmdir.js index 6df7ea9..04eebc4 100644 --- a/src/features/apps/terminal/commands/rmdir.js +++ b/src/features/apps/terminal/commands/rmdir.js @@ -1,16 +1,17 @@ import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const rmdir = new Command() .setRequireArgs(true) .setManual({ purpose: "Remove a directory" }) - .setExecute((args, { currentDirectory }) => { + .setExecute(function(args, { currentDirectory }) { const name = args[0]; const folder = currentDirectory.findSubFolder(name); if (!folder) - return `${this.name}: ${args[0]}: No such directory`; + return CommandsManager.formatError(this.name, `${args[0]}: No such directory`); folder.delete(); return { blank: true }; diff --git a/src/features/apps/terminal/commands/sl.js b/src/features/apps/terminal/commands/sl.js index c4be859..5a6e590 100644 --- a/src/features/apps/terminal/commands/sl.js +++ b/src/features/apps/terminal/commands/sl.js @@ -1,4 +1,5 @@ import Command from "../command.js"; +import CommandsManager from "../commands.js"; import Stream from "../stream.js"; const ANIMATION_SPEED = 1.25; @@ -173,11 +174,11 @@ function getLocomotive(frame, wagonCount = 1) { export const sl = new Command() .setManual({ - purpose: "Displays animations aimed to correct users who accidentally enter sl instead of ls. SL stands for Steam Locomotive.", + purpose: "Show animations aimed to correct users who accidentally enter sl instead of ls. SL stands for Steam Locomotive.", usage: "sl\n" - + "sl -w [NUMBER]", + + "sl -w number", options: { - "-w": "Set the amount of wagons (defaults to 1)" + "-w number": "Set the amount of wagons (defaults to 1)" } }) .addOption({ @@ -185,14 +186,14 @@ export const sl = new Command() long: "wagons", isInput: true }) - .setExecute((args, { inputs }) => { + .setExecute(function(args, { inputs }) { let wagonCount = 1; if (inputs?.w) { wagonCount = parseInt(inputs.w); if (!wagonCount || wagonCount < 0) { - return `${this.name}: Please specify a valid amount of wagons`; + return CommandsManager.formatError(this.name, "Please specify a valid amount of wagons"); } } diff --git a/src/features/apps/terminal/commands/touch.js b/src/features/apps/terminal/commands/touch.js index 50ef012..021ddfe 100644 --- a/src/features/apps/terminal/commands/touch.js +++ b/src/features/apps/terminal/commands/touch.js @@ -1,17 +1,18 @@ import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js"; import Command from "../command.js"; +import CommandsManager from "../commands.js"; export const touch = new Command() .setRequireArgs(true) .setManual({ - purpose: "Change file timestamps", - usage: "touch [OPTION]... FILE...", + purpose: "Changes file timestamps", + usage: "touch [options] files", description: "Update the access and modification times of each FILE to the current time.\n\n" - + "A FILE argument that does not exist is created empty." + + "A file argument that does not exist is created empty." }) .setExecute(function(args, { currentDirectory }) { if (args[0] === "girls\\" && args[1] === "boo**") - return `${this.name}: Cannot touch 'girls boo**': Permission denied`; + return CommandsManager.formatError(this.name, "Cannot touch 'girls boo**': Permission denied"); const { name, extension } = VirtualFile.convertId(args[0]); diff --git a/src/features/apps/terminal/commands/uptime.js b/src/features/apps/terminal/commands/uptime.js index 6e5fe4e..f2a0cd0 100644 --- a/src/features/apps/terminal/commands/uptime.js +++ b/src/features/apps/terminal/commands/uptime.js @@ -5,7 +5,7 @@ import Command from "../command.js"; export const uptime = new Command() .setManual({ - purpose: "Displays the current uptime of the system" + purpose: "Display the current uptime of the system" }) .setExecute(() => { return `Uptime: ${formatRelativeTime(START_DATE, 2, false)}`; diff --git a/src/features/apps/terminal/commands/whatis.js b/src/features/apps/terminal/commands/whatis.js index 06c2224..5f726c4 100644 --- a/src/features/apps/terminal/commands/whatis.js +++ b/src/features/apps/terminal/commands/whatis.js @@ -1,3 +1,4 @@ +import { ANSI } from "../../../../config/apps/terminal.config.js"; import Command from "../command.js"; import CommandsManager from "../commands.js"; @@ -11,10 +12,10 @@ export const whatis = new Command() const command = CommandsManager.find(commandName); if (!command) - return `${this.name}: ${commandName}: Command not found`; + return CommandsManager.formatError(this.name, `${commandName}: Command not found`); if (!command.manual?.purpose) - return `${this.name}: ${commandName}: No information found`; + return CommandsManager.formatError(this.name, `${commandName}: No information found`); - return `${commandName} - ${command.manual.purpose}`; + return `${commandName} - ${ANSI.fg.green}${command.manual.purpose}`; }); \ No newline at end of file diff --git a/src/hooks/_utils/keyboard.js b/src/hooks/_utils/keyboard.js index 1696e03..024751e 100644 --- a/src/hooks/_utils/keyboard.js +++ b/src/hooks/_utils/keyboard.js @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { removeFromArray } from "../../features/_utils/array.utils.js"; /** @@ -31,16 +31,22 @@ export function useKeyboardListener({ onKeyDown, onKeyUp }) { export function useShortcuts({ options, shortcuts, useCategories = true }) { const [activeKeys, setActiveKeys] = useState([]); - const checkShortcuts = (event, allowExecution = true) => { + const checkShortcuts = useCallback((event, allowExecution = true) => { if (!shortcuts) return; + const keys = [...activeKeys]; + + // Prevent alt-tabbing from messing with alt key registration + if (keys.includes("Alt") && !event.altKey) + removeFromArray("Alt", keys); + const checkGroup = (group, category) => { for (const [name, shortcut] of Object.entries(group)) { let active = true; shortcut.forEach((key) => { - if (!activeKeys.includes(key) && event.key !== key) + if (!keys.includes(key) && event.key !== key) return active = false; }); @@ -68,7 +74,9 @@ export function useShortcuts({ options, shortcuts, useCategories = true }) { } else { checkGroup(shortcuts); } - }; + + setActiveKeys(keys); + }, [activeKeys, options, shortcuts, useCategories]); const onKeyDown = (event) => { const isRepeated = activeKeys.includes(event.key);