Added lolcat command
This commit is contained in:
parent
1e1bdf5089
commit
915be2cb99
17 changed files with 146 additions and 60 deletions
|
|
@ -8,6 +8,7 @@ import { ANSI, HOSTNAME, USERNAME } from "../../../config/apps/terminal.config.j
|
|||
import CommandsManager from "../../../features/apps/terminal/commands.js";
|
||||
import { removeFromArray } from "../../../features/_utils/array.utils.js";
|
||||
import Stream from "../../../features/apps/terminal/stream.js";
|
||||
import { formatError } from "../../../features/apps/terminal/_utils/terminal.utils.js";
|
||||
|
||||
/**
|
||||
* @param {import("../../windows/WindowView.jsx").windowProps} props
|
||||
|
|
@ -60,7 +61,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
});
|
||||
};
|
||||
|
||||
const connectStream = (stream) => {
|
||||
const connectStream = (stream, pipes) => {
|
||||
setStream(stream);
|
||||
setStreamFocused(false);
|
||||
|
||||
|
|
@ -73,8 +74,23 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
let lastOutput = null;
|
||||
|
||||
stream.on(Stream.EVENT_NAMES.NEW, (text) => {
|
||||
lastOutput = text;
|
||||
setStreamOutput(text);
|
||||
let output = text;
|
||||
pipes.forEach((pipe) => {
|
||||
if (output instanceof Stream)
|
||||
return;
|
||||
|
||||
// Output from the previous command gets added as an argument for the next command
|
||||
output = handleInput(output ? `${pipe} ${output}` : pipe);
|
||||
});
|
||||
|
||||
if (output instanceof Stream) {
|
||||
stream.stop();
|
||||
promptOutput(ANSI.fg.red + "Stream failed");
|
||||
return;
|
||||
}
|
||||
|
||||
lastOutput = output;
|
||||
setStreamOutput(output);
|
||||
});
|
||||
|
||||
stream.on(Stream.EVENT_NAMES.STOP, () => {
|
||||
|
|
@ -92,6 +108,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
const handleInput = (value) => {
|
||||
const rawInputValueStart = value.indexOf(" ") + 1;
|
||||
const rawInputValue = rawInputValueStart <= 0 ? "" : value.substr(rawInputValueStart);
|
||||
const timestamp = Date.now();
|
||||
value = value.trim();
|
||||
|
||||
if (value === "")
|
||||
|
|
@ -109,7 +126,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
const command = CommandsManager.find(commandName);
|
||||
|
||||
if (!command)
|
||||
return CommandsManager.formatError(commandName, "Command not found");
|
||||
return formatError(commandName, "Command not found");
|
||||
|
||||
// Get options
|
||||
const options = [];
|
||||
|
|
@ -145,10 +162,10 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
|
||||
// Check usage
|
||||
if (command.requireArgs && args.length === 0)
|
||||
return CommandsManager.formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 argument`);
|
||||
return formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 argument`);
|
||||
|
||||
if (command.requireOptions && options.length === 0)
|
||||
return CommandsManager.formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`);
|
||||
return formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`);
|
||||
|
||||
// Execute command
|
||||
let response = null;
|
||||
|
|
@ -165,17 +182,18 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
rawInputValue,
|
||||
options,
|
||||
exit,
|
||||
inputs
|
||||
inputs,
|
||||
timestamp
|
||||
});
|
||||
|
||||
if (response == null)
|
||||
return CommandsManager.formatError(commandName, "Command failed");
|
||||
return formatError(commandName, "Command failed");
|
||||
|
||||
if (!response.blank)
|
||||
return response;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
return CommandsManager.formatError(commandName, "Command failed");
|
||||
return formatError(commandName, "Command failed");
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -190,17 +208,21 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }) {
|
|||
setHistoryIndex(0);
|
||||
|
||||
// Piping is used to chain commands
|
||||
const segments = value.split(" | ");
|
||||
const pipes = value.split(" | ");
|
||||
|
||||
let output = null;
|
||||
segments.forEach((segment) => {
|
||||
pipes.forEach((pipe) => {
|
||||
if (output instanceof Stream)
|
||||
return;
|
||||
|
||||
// Output from the previous command gets added as an argument for the next command
|
||||
output = handleInput(output ? `${segment} ${output}` : segment);
|
||||
output = handleInput(output ? `${pipe} ${output}` : pipe);
|
||||
removeFromArray(pipe, pipes);
|
||||
});
|
||||
|
||||
if (output) {
|
||||
if (output instanceof Stream) {
|
||||
connectStream(output);
|
||||
connectStream(output, pipes);
|
||||
} else {
|
||||
promptOutput(`${output}\n`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -93,31 +93,31 @@
|
|||
cursor: text;
|
||||
}
|
||||
|
||||
.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); }
|
||||
.ansi-blue-fg { color: var(--blue-b); }
|
||||
.ansi-magenta-fg { color: var(--purple-b); }
|
||||
.ansi-cyan-fg { color: var(--cyan-b); }
|
||||
.ansi-white-fg { color: var(--grey-a); }
|
||||
.ansi-black-fg { color: var(--dark-grey-c); }
|
||||
.ansi-red-fg { color: var(--red-a); }
|
||||
.ansi-green-fg { color: var(--green-a); }
|
||||
.ansi-yellow-fg { color: var(--yellow-a); }
|
||||
.ansi-blue-fg { color: var(--blue-a); }
|
||||
.ansi-magenta-fg { color: var(--purple-a); }
|
||||
.ansi-cyan-fg { color: var(--cyan-a); }
|
||||
.ansi-white-fg { color: var(--foreground-color-a); }
|
||||
|
||||
.ansi-bright-black-fg { color: var(--dark-grey-d); }
|
||||
.ansi-bright-black-fg { color: var(--dark-grey-c); }
|
||||
.ansi-bright-red-fg { color: var(--red-a); }
|
||||
.ansi-bright-green-fg { color: var(--green-a); }
|
||||
.ansi-bright-yellow-fg { color: var(--yellow-a); }
|
||||
.ansi-bright-blue-fg { color: var(--blue-a); }
|
||||
.ansi-bright-magenta-fg { color: var(--purple-a); }
|
||||
.ansi-bright-cyan-fg { color: var(--cyan-a); }
|
||||
.ansi-bright-white-fg { color: var(--white-a); }
|
||||
.ansi-bright-white-fg { color: var(--foreground-color-a); }
|
||||
|
||||
.ansi-black-bg { background-color: var(--dark-grey-d); }
|
||||
.ansi-black-bg { background-color: var(--dark-grey-c); }
|
||||
.ansi-red-bg { background-color: var(--red-a); }
|
||||
.ansi-green-bg { background-color: var(--green-a); }
|
||||
.ansi-yellow-bg { background-color: var(--yellow-a); }
|
||||
.ansi-blue-bg { background-color: var(--blue-a); }
|
||||
.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-white-bg { background-color: var(--foreground-color-a); }
|
||||
|
||||
.ansi-dim { opacity: 0.65; }
|
||||
19
src/features/apps/terminal/_utils/terminal.utils.js
Normal file
19
src/features/apps/terminal/_utils/terminal.utils.js
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { ANSI } from "../../../../config/apps/terminal.config.js";
|
||||
|
||||
/**
|
||||
* @param {string} commandName
|
||||
* @param {string} error
|
||||
* @returns {string}
|
||||
*/
|
||||
export function formatError(commandName, error) {
|
||||
return `${ANSI.fg.red}${commandName}: ${error}${ANSI.reset}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} string
|
||||
* @returns {string}
|
||||
*/
|
||||
export function removeAnsi(string) {
|
||||
// eslint-disable-next-line no-control-regex
|
||||
return string.replace(/\u001b\[([0-9]+)m/gm, "");
|
||||
}
|
||||
|
|
@ -1,4 +1,3 @@
|
|||
import { ANSI } from "../../../config/apps/terminal.config.js";
|
||||
import Command from "./command.js";
|
||||
|
||||
let commands = [];
|
||||
|
|
@ -55,13 +54,4 @@ 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}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
export const cat = new Command()
|
||||
.setRequireArgs(true)
|
||||
|
|
@ -14,7 +14,7 @@ export const cat = new Command()
|
|||
const file = currentDirectory.findFile(name, extension);
|
||||
|
||||
if (!file)
|
||||
return CommandsManager.formatError(this.name, `${args[0]}: No such file`);
|
||||
return formatError(this.name, `${args[0]}: No such file`);
|
||||
|
||||
if (file.content) {
|
||||
if (!options.includes("e")) {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
export const cd = new Command()
|
||||
.setManual({
|
||||
|
|
@ -12,7 +12,7 @@ export const cd = new Command()
|
|||
const destination = currentDirectory.navigate(path);
|
||||
|
||||
if (!destination)
|
||||
return CommandsManager.formatError(this.name, `${args[0]}: No such file or directory`);
|
||||
return formatError(this.name, `${args[0]}: No such file or directory`);
|
||||
|
||||
setCurrentDirectory(destination);
|
||||
return { blank: true };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ANSI } from "../../../../config/apps/terminal.config.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
|
|
@ -18,10 +19,10 @@ export const help = new Command()
|
|||
const command = CommandsManager.find(commandName);
|
||||
|
||||
if (!command)
|
||||
return CommandsManager.formatError(this.name, `${commandName}: Command not found`);
|
||||
return formatError(this.name, `${commandName}: Command not found`);
|
||||
|
||||
if (!command.manual?.purpose)
|
||||
return CommandsManager.formatError(this.name, `${commandName}: No manual found`);
|
||||
return formatError(this.name, `${commandName}: No manual found`);
|
||||
|
||||
return command.manual.purpose;
|
||||
});
|
||||
52
src/features/apps/terminal/commands/lolcat.js
Normal file
52
src/features/apps/terminal/commands/lolcat.js
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
import { ANSI } from "../../../../config/apps/terminal.config.js";
|
||||
import { removeAnsi } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
|
||||
const COLUMN_WIDTH = 5;
|
||||
const ROW_OFFSET = 2;
|
||||
|
||||
const RAINBOW = [
|
||||
ANSI.fg.red,
|
||||
ANSI.fg.yellow,
|
||||
ANSI.fg.green,
|
||||
ANSI.fg.cyan,
|
||||
ANSI.fg.blue,
|
||||
ANSI.fg.magenta,
|
||||
];
|
||||
|
||||
export const lolcat = new Command()
|
||||
.setManual({
|
||||
purpose: "Display text with a rainbow effect"
|
||||
})
|
||||
.setExecute(function(args, { rawInputValue, timestamp }) {
|
||||
let rows = removeAnsi(rawInputValue).split("\n");
|
||||
const offset = timestamp / 100;
|
||||
|
||||
rows = rows.map((row, index) => {
|
||||
const columns = [];
|
||||
|
||||
const rowIndex = index + offset;
|
||||
const rowOffset = COLUMN_WIDTH - ((ROW_OFFSET * rowIndex) % COLUMN_WIDTH);
|
||||
let rainbowIndex = Math.floor(rowIndex / (COLUMN_WIDTH / ROW_OFFSET));
|
||||
|
||||
const addColumn = (start, end) => {
|
||||
const column = row.substring(start, end);
|
||||
const color = RAINBOW[rainbowIndex++ % RAINBOW.length];
|
||||
columns.push(color + column);
|
||||
};
|
||||
|
||||
if (rowOffset > 0)
|
||||
addColumn(0, rowOffset);
|
||||
|
||||
for (let i = rowOffset; i < row.length; i += COLUMN_WIDTH + 1) {
|
||||
addColumn(i, i + COLUMN_WIDTH + 1);
|
||||
}
|
||||
|
||||
if (row.length === 0)
|
||||
return "";
|
||||
|
||||
return columns.join("");
|
||||
});
|
||||
|
||||
return rows.join("\n");
|
||||
});
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { ANSI } from "../../../../config/apps/terminal.config.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
export const ls = new Command()
|
||||
.setManual({
|
||||
|
|
@ -16,7 +16,7 @@ export const ls = new Command()
|
|||
}
|
||||
|
||||
if (!directory)
|
||||
return CommandsManager.formatError(this.name, `Cannot access '${args[0]}': No such file or directory`);
|
||||
return 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);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
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 CommandsManager.formatError(this.name, "*** No rule to make target 'love'. Stop.");
|
||||
return formatError(this.name, "*** No rule to make target 'love'. Stop.");
|
||||
});
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
import { ANSI } from "../../../../config/apps/terminal.config.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
|
|
@ -33,12 +34,12 @@ export const man = new Command()
|
|||
const command = CommandsManager.find(commandName);
|
||||
|
||||
if (!command)
|
||||
return `${this.name}: ${commandName}: Command not found`;
|
||||
return formatError(this.name, `${commandName}: Command not found`);
|
||||
|
||||
const manual = command.manual;
|
||||
|
||||
if (!manual)
|
||||
return `${this.name}: ${commandName}: No manual found`;
|
||||
return formatError(this.name, `${commandName}: No manual found`);
|
||||
|
||||
const formatText = (text) => {
|
||||
const lines = text.split("\n").map((line) => " ".repeat(MARGIN) + line);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
export const rm = new Command()
|
||||
.setRequireArgs(true)
|
||||
|
|
@ -12,7 +12,7 @@ export const rm = new Command()
|
|||
const file = currentDirectory.findFile(name, extension);
|
||||
|
||||
if (!file)
|
||||
return CommandsManager.formatError(this.name, `${args[0]}: No such file`);
|
||||
return formatError(this.name, `${args[0]}: No such file`);
|
||||
|
||||
file.delete();
|
||||
return { blank: true };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
export const rmdir = new Command()
|
||||
.setRequireArgs(true)
|
||||
|
|
@ -11,7 +11,7 @@ export const rmdir = new Command()
|
|||
const folder = currentDirectory.findSubFolder(name);
|
||||
|
||||
if (!folder)
|
||||
return CommandsManager.formatError(this.name, `${args[0]}: No such directory`);
|
||||
return formatError(this.name, `${args[0]}: No such directory`);
|
||||
|
||||
folder.delete();
|
||||
return { blank: true };
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
import Stream from "../stream.js";
|
||||
|
||||
const ANIMATION_SPEED = 1.25;
|
||||
|
|
@ -193,7 +193,7 @@ export const sl = new Command()
|
|||
wagonCount = parseInt(inputs.w);
|
||||
|
||||
if (!wagonCount || wagonCount < 0) {
|
||||
return CommandsManager.formatError(this.name, "Please specify a valid amount of wagons");
|
||||
return formatError(this.name, "Please specify a valid amount of wagons");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { VirtualFile } from "../../../virtual-drive/file/virtualFile.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
export const touch = new Command()
|
||||
.setRequireArgs(true)
|
||||
|
|
@ -12,7 +12,7 @@ export const touch = new Command()
|
|||
})
|
||||
.setExecute(function(args, { currentDirectory }) {
|
||||
if (args[0] === "girls\\" && args[1] === "boo**")
|
||||
return CommandsManager.formatError(this.name, "Cannot touch 'girls boo**': Permission denied");
|
||||
return formatError(this.name, "Cannot touch 'girls boo**': Permission denied");
|
||||
|
||||
const { name, extension } = VirtualFile.convertId(args[0]);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { ANSI } from "../../../../config/apps/terminal.config.js";
|
||||
import { formatError } from "../_utils/terminal.utils.js";
|
||||
import Command from "../command.js";
|
||||
import CommandsManager from "../commands.js";
|
||||
|
||||
|
|
@ -12,10 +13,10 @@ export const whatis = new Command()
|
|||
const command = CommandsManager.find(commandName);
|
||||
|
||||
if (!command)
|
||||
return CommandsManager.formatError(this.name, `${commandName}: Command not found`);
|
||||
return formatError(this.name, `${commandName}: Command not found`);
|
||||
|
||||
if (!command.manual?.purpose)
|
||||
return CommandsManager.formatError(this.name, `${commandName}: No information found`);
|
||||
return formatError(this.name, `${commandName}: No information found`);
|
||||
|
||||
return `${commandName} - ${ANSI.fg.green}${command.manual.purpose}`;
|
||||
});
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
:root {
|
||||
--white-a: #fff;
|
||||
--white-a: hsl(210, 54%, 95%);
|
||||
--pink-a: #ff9ff3;
|
||||
--pink-b: #f368e0;
|
||||
--yellow-a: #feca57;
|
||||
|
|
@ -16,8 +16,8 @@
|
|||
--blue-b: #2e86de;
|
||||
--purple-a: #5f27cd;
|
||||
--purple-b: #341f97;
|
||||
--grey-a: #c8d6e5;
|
||||
--grey-b: #8395a7;
|
||||
--grey-a: hsl(210, 36%, 85%);
|
||||
--grey-b: hsl(210, 18%, 60%);
|
||||
--dark-grey-a: hsl(211, 14%, 40%);
|
||||
--dark-grey-b: hsl(var(--background-color-a-hsl));
|
||||
--dark-grey-c: hsl(var(--background-color-c-hsl));
|
||||
|
|
|
|||
Loading…
Reference in a new issue