Added cmatrix command
This commit is contained in:
parent
9cad04edcd
commit
3bcb70ab95
7 changed files with 158 additions and 40 deletions
|
|
@ -151,6 +151,7 @@ export default function Ansi(props: { children?: string | undefined; linkify?: b
|
||||||
"code",
|
"code",
|
||||||
{ className },
|
{ className },
|
||||||
ansiToJSON(children ?? "", useClasses ?? false).map(
|
ansiToJSON(children ?? "", useClasses ?? false).map(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
convertBundleIntoReact.bind(null, linkify ?? false, useClasses ?? false)
|
convertBundleIntoReact.bind(null, linkify ?? false, useClasses ?? false)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,10 @@
|
||||||
|
|
||||||
.Terminal p, .Terminal pre {
|
.Terminal p, .Terminal pre {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
min-height: 1.25rem;
|
height: 1.25rem;
|
||||||
|
line-height: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
letter-spacing: inherit;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Prefix {
|
.Prefix {
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,44 @@
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { MouseEventHandler, useEffect, useRef, useState } from "react";
|
||||||
import styles from "./Terminal.module.css";
|
import styles from "./Terminal.module.css";
|
||||||
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
|
import { useVirtualRoot } from "../../../hooks/virtual-drive/virtualRootContext";
|
||||||
import { clamp } from "../../../features/math/clamp";
|
import { clamp } from "../../../features/math/clamp";
|
||||||
import { OutputLine } from "./OutputLine";
|
import { OutputLine } from "./OutputLine";
|
||||||
import { InputLine } from "./InputLine";
|
import { InputLine } from "./InputLine";
|
||||||
import { ANSI, HOSTNAME, USERNAME } from "../../../config/apps/terminal.config";
|
import { ANSI, HOSTNAME, USERNAME, WELCOME_MESSAGE } from "../../../config/apps/terminal.config";
|
||||||
import CommandsManager from "../../../features/apps/terminal/commands";
|
import CommandsManager from "../../../features/apps/terminal/commands";
|
||||||
import { removeFromArray } from "../../../features/_utils/array.utils";
|
import { removeFromArray } from "../../../features/_utils/array.utils";
|
||||||
import Stream from "../../../features/apps/terminal/stream";
|
import Stream from "../../../features/apps/terminal/stream";
|
||||||
import { formatError } from "../../../features/apps/terminal/_utils/terminal.utils";
|
import { formatError } from "../../../features/apps/terminal/_utils/terminal.utils";
|
||||||
import { WindowProps } from "../../windows/WindowView";
|
import { WindowProps } from "../../windows/WindowView";
|
||||||
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
|
import { VirtualFolder } from "../../../features/virtual-drive/folder/virtualFolder";
|
||||||
|
import { CommandResponse } from "../../../features/apps/terminal/command";
|
||||||
|
import { APP_NAMES } from "../../../config/apps.config";
|
||||||
|
|
||||||
interface TerminalProps extends WindowProps {
|
interface TerminalProps extends WindowProps {
|
||||||
startPath: string;
|
startPath: string;
|
||||||
input: string;
|
input: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HistoryEntry {
|
||||||
|
text: string;
|
||||||
|
isInput: boolean;
|
||||||
|
value?: string;
|
||||||
|
clear?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export function Terminal({ startPath, input, setTitle, close: exit, active }: TerminalProps) {
|
export function Terminal({ startPath, input, setTitle, close: exit, active }: TerminalProps) {
|
||||||
const [inputKey, setInputKey] = useState(0);
|
const [inputKey, setInputKey] = useState(0);
|
||||||
const [inputValue, setInputValue] = useState(input ?? "");
|
const [inputValue, setInputValue] = useState(input ?? "");
|
||||||
const [history, setHistory] = useState([]);
|
const [history, setHistory] = useState<HistoryEntry[]>([{
|
||||||
|
text: WELCOME_MESSAGE.replace("$APP_NAME", APP_NAMES.TERMINAL),
|
||||||
|
isInput: false,
|
||||||
|
}]);
|
||||||
const virtualRoot = useVirtualRoot();
|
const virtualRoot = useVirtualRoot();
|
||||||
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate(startPath ?? "~"));
|
const [currentDirectory, setCurrentDirectory] = useState(virtualRoot.navigate(startPath ?? "~"));
|
||||||
const inputRef = useRef(null);
|
const inputRef = useRef(null);
|
||||||
const [historyIndex, setHistoryIndex] = useState(0);
|
const [historyIndex, setHistoryIndex] = useState(0);
|
||||||
const [stream, setStream] = useState(null);
|
const [stream, setStream] = useState<Stream>(null);
|
||||||
const [streamOutput, setStreamOutput] = useState(null);
|
const [streamOutput, setStreamOutput] = useState<string>(null);
|
||||||
const streamRef = useRef(null);
|
const streamRef = useRef(null);
|
||||||
const [streamFocused, setStreamFocused] = useState(false);
|
const [streamFocused, setStreamFocused] = useState(false);
|
||||||
|
|
||||||
|
|
@ -38,7 +50,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
if (streamFocused || streamRef.current == null || streamOutput == null)
|
if (streamFocused || streamRef.current == null || streamOutput == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
streamRef.current.scrollTop = streamRef.current.scrollHeight;
|
(streamRef.current as unknown as HTMLDivElement).scrollTop = (streamRef.current as unknown as HTMLDivElement).scrollHeight;
|
||||||
setStreamFocused(true);
|
setStreamFocused(true);
|
||||||
}, [streamFocused, streamOutput, streamRef]);
|
}, [streamFocused, streamOutput, streamRef]);
|
||||||
|
|
||||||
|
|
@ -46,48 +58,48 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
if (!inputRef.current || !active)
|
if (!inputRef.current || !active)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
inputRef.current.focus();
|
(inputRef.current as unknown as HTMLInputElement).focus();
|
||||||
}, [inputRef, active]);
|
}, [inputRef, active]);
|
||||||
|
|
||||||
const prefix = `${ANSI.fg.cyan + USERNAME}@${HOSTNAME + ANSI.reset}:`
|
const prefix = `${ANSI.fg.cyan + USERNAME}@${HOSTNAME + ANSI.reset}:`
|
||||||
+ `${ANSI.fg.blue + (currentDirectory.root ? "/" : currentDirectory.path) + ANSI.reset}$ `;
|
+ `${ANSI.fg.blue + (currentDirectory.root ? "/" : currentDirectory.path) + ANSI.reset}$ `;
|
||||||
|
|
||||||
const updatedHistory = history;
|
const updatedHistory = history;
|
||||||
const pushHistory = (entry) => {
|
const pushHistory = (entry: HistoryEntry) => {
|
||||||
updatedHistory.push(entry);
|
updatedHistory.push(entry);
|
||||||
setHistory(updatedHistory);
|
setHistory(updatedHistory);
|
||||||
};
|
};
|
||||||
|
|
||||||
const promptOutput = (text) => {
|
const promptOutput = (text: string) => {
|
||||||
pushHistory({
|
pushHistory({
|
||||||
text,
|
text,
|
||||||
isInput: false
|
isInput: false
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const connectStream = (stream, pipes) => {
|
const connectStream = (stream: Stream, pipes: string[]) => {
|
||||||
setStream(stream);
|
setStream(stream);
|
||||||
setStreamFocused(false);
|
setStreamFocused(false);
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
if (active && (event.ctrlKey || event.metaKey) && event.key === "c") {
|
if (active && (event.ctrlKey || event.metaKey) && event.key === "c") {
|
||||||
stream.stop();
|
stream.stop();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastOutput = null;
|
let lastOutput: CommandResponse = null;
|
||||||
|
|
||||||
stream.on(Stream.EVENT_NAMES.new, (text) => {
|
stream.on(Stream.EVENT_NAMES.new, (text: string) => {
|
||||||
let output = text;
|
let output: CommandResponse = text;
|
||||||
pipes.forEach((pipe) => {
|
pipes.forEach((pipe) => {
|
||||||
if (output instanceof Stream)
|
if (output instanceof Stream)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Output from the previous command gets added as an argument for the next command
|
// Output from the previous command gets added as an argument for the next command
|
||||||
output = handleInput(output ? `${pipe} ${output}` : pipe);
|
output = handleInput(output ? `${pipe} ${output as string}` : pipe);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (output instanceof Stream) {
|
if ((output as unknown) instanceof Stream) {
|
||||||
stream.stop();
|
stream.stop();
|
||||||
promptOutput(ANSI.fg.red + "Stream failed");
|
promptOutput(ANSI.fg.red + "Stream failed");
|
||||||
return;
|
return;
|
||||||
|
|
@ -100,7 +112,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
stream.on(Stream.EVENT_NAMES.stop, () => {
|
stream.on(Stream.EVENT_NAMES.stop, () => {
|
||||||
document.removeEventListener("keydown", onKeyDown);
|
document.removeEventListener("keydown", onKeyDown);
|
||||||
|
|
||||||
promptOutput(lastOutput);
|
promptOutput(lastOutput as string);
|
||||||
|
|
||||||
setStream(null);
|
setStream(null);
|
||||||
setStreamOutput(null);
|
setStreamOutput(null);
|
||||||
|
|
@ -109,7 +121,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
document.addEventListener("keydown", onKeyDown);
|
document.addEventListener("keydown", onKeyDown);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInput = (value) => {
|
const handleInput = (value: string): CommandResponse => {
|
||||||
const rawInputValueStart = value.indexOf(" ") + 1;
|
const rawInputValueStart = value.indexOf(" ") + 1;
|
||||||
const rawInputValue = rawInputValueStart <= 0 ? "" : value.substr(rawInputValueStart);
|
const rawInputValue = rawInputValueStart <= 0 ? "" : value.substr(rawInputValueStart);
|
||||||
const timestamp = Date.now();
|
const timestamp = Date.now();
|
||||||
|
|
@ -133,10 +145,10 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
return formatError(commandName, "Command not found");
|
return formatError(commandName, "Command not found");
|
||||||
|
|
||||||
// Get options
|
// Get options
|
||||||
const options = [];
|
const options: string[] = [];
|
||||||
const inputs = {};
|
const inputs = {};
|
||||||
args.filter((arg) => arg.startsWith("-")).forEach((option, index) => {
|
args.filter((arg: string) => arg.startsWith("-")).forEach((option: string) => {
|
||||||
const addOption = (key) => {
|
const addOption = (key: string) => {
|
||||||
if (options.includes(key))
|
if (options.includes(key))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -156,7 +168,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
addOption(longOption);
|
addOption(longOption);
|
||||||
} else {
|
} else {
|
||||||
const shortOptions = option.substring(1).split("");
|
const shortOptions = option.substring(1).split("");
|
||||||
shortOptions.forEach((shortOption) => {
|
shortOptions.forEach((shortOption: string) => {
|
||||||
addOption(shortOption);
|
addOption(shortOption);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -172,7 +184,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
return formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`);
|
return formatError(commandName, `Incorrect usage: ${commandName} requires at least 1 option`);
|
||||||
|
|
||||||
// Execute command
|
// Execute command
|
||||||
let response = null;
|
let response: CommandResponse = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
response = command.execute(args, {
|
response = command.execute(args, {
|
||||||
|
|
@ -193,7 +205,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
if (response == null)
|
if (response == null)
|
||||||
return formatError(commandName, "Command failed");
|
return formatError(commandName, "Command failed");
|
||||||
|
|
||||||
if (!response.blank)
|
if (!(response as { blank: boolean }).blank)
|
||||||
return response;
|
return response;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|
@ -201,7 +213,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const submitInput = (value) => {
|
const submitInput = (value: string) => {
|
||||||
pushHistory({
|
pushHistory({
|
||||||
text: prefix + value,
|
text: prefix + value,
|
||||||
isInput: true,
|
isInput: true,
|
||||||
|
|
@ -236,7 +248,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateHistoryIndex = (delta) => {
|
const updateHistoryIndex = (delta: number) => {
|
||||||
const inputHistory = history.filter(({ isInput }) => isInput);
|
const inputHistory = history.filter(({ isInput }) => isInput);
|
||||||
const index = clamp(historyIndex + delta, 0, inputHistory.length);
|
const index = clamp(historyIndex + delta, 0, inputHistory.length);
|
||||||
|
|
||||||
|
|
@ -257,8 +269,8 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
setHistoryIndex(index);
|
setHistoryIndex(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onKeyDown = (event) => {
|
const onKeyDown = (event: KeyboardEvent) => {
|
||||||
const value = event.target.value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
const { key } = event;
|
const { key } = event;
|
||||||
|
|
||||||
if (key === "Enter") {
|
if (key === "Enter") {
|
||||||
|
|
@ -275,8 +287,8 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChange = (event) => {
|
const onChange = (event: KeyboardEvent) => {
|
||||||
const value = event.target.value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
return setInputValue(value);
|
return setInputValue(value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -294,7 +306,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onMouseDown = (event) => {
|
const onMouseDown = (event: { button: number; preventDefault: () => void; }) => {
|
||||||
if (event.button === 2) {
|
if (event.button === 2) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
|
|
@ -306,7 +318,7 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onContextMenu = (event) => {
|
const onContextMenu = (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -315,11 +327,11 @@ export function Terminal({ startPath, input, setTitle, close: exit, active }: Te
|
||||||
ref={streamRef}
|
ref={streamRef}
|
||||||
className={styles.Terminal}
|
className={styles.Terminal}
|
||||||
onMouseDown={onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
onContextMenu={onContextMenu}
|
onContextMenu={onContextMenu as unknown as MouseEventHandler}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
if (window.getSelection().toString() === "") {
|
if (window.getSelection().toString() === "") {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
inputRef.current?.focus();
|
(inputRef.current as HTMLInputElement)?.focus();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,7 @@ export const ANSI = {
|
||||||
dim: "\u001b[2m",
|
dim: "\u001b[2m",
|
||||||
},
|
},
|
||||||
reset: "\u001b[0m",
|
reset: "\u001b[0m",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WELCOME_MESSAGE = `${ANSI.fg.cyan + ANSI.decoration.dim}$APP_NAME - Made by Prozilla${ANSI.reset}`
|
||||||
|
+ `\n${ANSI.decoration.dim}Type 'help' for a list of commands.${ANSI.reset}\n`;
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ type Option = {
|
||||||
isInput: boolean
|
isInput: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CommandResponse = string | { blank: boolean } | void | Stream;
|
||||||
|
|
||||||
type Execute = (args?: string[], options?: {
|
type Execute = (args?: string[], options?: {
|
||||||
promptOutput?: Function,
|
promptOutput?: Function,
|
||||||
pushHistory?: Function,
|
pushHistory?: Function,
|
||||||
|
|
@ -21,7 +23,7 @@ type Execute = (args?: string[], options?: {
|
||||||
exit?: Function,
|
exit?: Function,
|
||||||
inputs?: Record<string, string>;
|
inputs?: Record<string, string>;
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
}) => string | { blank: boolean } | void | Stream;
|
}) => CommandResponse;
|
||||||
|
|
||||||
type Manual = {
|
type Manual = {
|
||||||
purpose?: string,
|
purpose?: string,
|
||||||
|
|
|
||||||
97
src/features/apps/terminal/commands/cmatrix.ts
Normal file
97
src/features/apps/terminal/commands/cmatrix.ts
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
import { ANSI } from "../../../../config/apps/terminal.config";
|
||||||
|
import { randomFromArray, removeFromArray } from "../../../_utils/array.utils";
|
||||||
|
import { randomRange } from "../../../math/random";
|
||||||
|
import Vector2 from "../../../math/vector2";
|
||||||
|
import Command from "../command";
|
||||||
|
import Stream from "../stream";
|
||||||
|
|
||||||
|
const ANIMATION_SPEED = 1.25;
|
||||||
|
const SCREEN_WIDTH = 75;
|
||||||
|
const SCREEN_HEIGHT = 20;
|
||||||
|
const CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.*\\/()#@&$!?%°:<>[]";
|
||||||
|
|
||||||
|
const PARTICLES = {
|
||||||
|
framesBetweenSpawn: 30,
|
||||||
|
fallSpeed: 1,
|
||||||
|
minSize: 5,
|
||||||
|
maxSize: 25
|
||||||
|
};
|
||||||
|
|
||||||
|
type Particle = {
|
||||||
|
position: Vector2,
|
||||||
|
size: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
let particles: Particle[] = [];
|
||||||
|
|
||||||
|
function generateScreen(frame: number): string {
|
||||||
|
// Spawn new particles
|
||||||
|
if (frame % PARTICLES.framesBetweenSpawn) {
|
||||||
|
const newParticle: Particle = {
|
||||||
|
position: new Vector2(randomRange(0, SCREEN_WIDTH), SCREEN_HEIGHT).round(),
|
||||||
|
size: Math.round(randomRange(PARTICLES.minSize, PARTICLES.maxSize))
|
||||||
|
};
|
||||||
|
particles.push(newParticle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create screen
|
||||||
|
const screen: string[][] = [];
|
||||||
|
for (let y = 0; y < SCREEN_HEIGHT; y++) {
|
||||||
|
const row: string[] = [];
|
||||||
|
for (let x = 0; x < SCREEN_WIDTH; x++) {
|
||||||
|
row.push(" ");
|
||||||
|
}
|
||||||
|
screen.push(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move and render particles
|
||||||
|
particles.forEach((particle) => {
|
||||||
|
particle.position.y -= PARTICLES.fallSpeed;
|
||||||
|
|
||||||
|
// Remove offscreen particles
|
||||||
|
if (particle.position.y + particle.size <= 0 || particle.position.x >= SCREEN_WIDTH)
|
||||||
|
return removeFromArray(particle, particles);
|
||||||
|
|
||||||
|
for (let i = 0; i < particle.size; i++) {
|
||||||
|
const character = randomFromArray(CHARACTERS.split(""));
|
||||||
|
let color = i == 0 ? ANSI.fg.white : ANSI.fg.green;
|
||||||
|
|
||||||
|
if (i > particle.size / 2)
|
||||||
|
color = ANSI.fg.green + ANSI.decoration.dim;
|
||||||
|
|
||||||
|
const positionX = particle.position.x;
|
||||||
|
const positionY = particle.position.y + i;
|
||||||
|
|
||||||
|
if (positionX < SCREEN_WIDTH && positionY < SCREEN_HEIGHT && positionY > 0) {
|
||||||
|
screen[positionY][positionX] = color + character + ANSI.reset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return screen.map((row) => row.join("")).reverse().join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cmatrix = new Command()
|
||||||
|
.setManual({
|
||||||
|
purpose: "Show a scrolling 'Matrix' like screen",
|
||||||
|
usage: "cmatrix",
|
||||||
|
})
|
||||||
|
.setExecute(function() {
|
||||||
|
const stream = new Stream();
|
||||||
|
particles = [];
|
||||||
|
|
||||||
|
let frame = 0;
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
const text = generateScreen(frame);
|
||||||
|
stream.send(text);
|
||||||
|
frame++;
|
||||||
|
}, 100 / ANIMATION_SPEED);
|
||||||
|
|
||||||
|
stream.on(Stream.EVENT_NAMES.stop, () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.start();
|
||||||
|
|
||||||
|
return stream;
|
||||||
|
});
|
||||||
|
|
@ -136,7 +136,7 @@ const EXTRA_WAGONS = [
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
function getLocomotive(frame, wagonCount = 1) {
|
function generateLocomotive(frame: number, wagonCount = 1) {
|
||||||
const smokeHeight = LOCOMOTIVE_SMOKE[0].length;
|
const smokeHeight = LOCOMOTIVE_SMOKE[0].length;
|
||||||
const locomotiveHeight = LOCOMOTIVE_TOP.length + LOCOMOTIVE_BOTTOM[0].length;
|
const locomotiveHeight = LOCOMOTIVE_TOP.length + LOCOMOTIVE_BOTTOM[0].length;
|
||||||
const wagonHeight = COAL_WAGON.length;
|
const wagonHeight = COAL_WAGON.length;
|
||||||
|
|
@ -193,7 +193,7 @@ export const sl = new Command()
|
||||||
wagonCount = parseInt(inputs.w);
|
wagonCount = parseInt(inputs.w);
|
||||||
|
|
||||||
if (!wagonCount || wagonCount < 0) {
|
if (!wagonCount || wagonCount < 0) {
|
||||||
return formatError(this.name, "Please specify a valid amount of wagons");
|
return formatError((this as Command).name, "Please specify a valid amount of wagons");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +201,7 @@ export const sl = new Command()
|
||||||
|
|
||||||
let frame = 0;
|
let frame = 0;
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
const text = getLocomotive(frame, wagonCount);
|
const text = generateLocomotive(frame, wagonCount);
|
||||||
stream.send(text);
|
stream.send(text);
|
||||||
frame++;
|
frame++;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue