forked from tools/josh
1
0
Fork 0
josh/src/js/terminal.ts

292 lines
7.9 KiB
TypeScript
Raw Normal View History

import {addOnLoad, asciiHeaderHtml, moveCaretToEndOf, q} from "./shared.js";
import {FileSystem} from "./fs.js";
import {Commands} from "./commands.js";
export class Terminal {
2019-10-21 02:25:42 +02:00
private readonly terminal: HTMLElement;
private readonly input: HTMLElement;
private readonly output: HTMLElement;
private readonly prefixDiv: HTMLElement;
private readonly inputHistory: InputHistory;
private readonly fileSystem: FileSystem;
private readonly commands: Commands;
2019-10-26 16:08:46 +02:00
private readonly users: User[];
private _currentUser: User | undefined;
2019-10-21 02:25:42 +02:00
private isLoggedIn: boolean;
constructor(terminal: HTMLElement, input: HTMLElement, output: HTMLElement, prefixDiv: HTMLElement) {
this.terminal = terminal;
this.input = input;
this.output = output;
this.prefixDiv = prefixDiv;
2019-10-26 16:08:46 +02:00
this.users = [
new User("felix", "password", "Why are you logged in on <i>my</i> account?"),
new User("root", "root", "Wait how did you get here?")
];
this._currentUser = this.users.find(it => it.name === "felix");
if (this._currentUser === undefined)
throw "Could not find user `felix`.";
2019-10-21 02:25:42 +02:00
this.isLoggedIn = true;
this.inputHistory = new InputHistory();
this.fileSystem = new FileSystem();
this.commands = new Commands(this, this.fileSystem);
this.terminal.addEventListener("click", this.onclick.bind(this));
this.terminal.addEventListener("keypress", this.onkeypress.bind(this));
this.terminal.addEventListener("keydown", this.onkeydown.bind(this));
2018-11-28 21:29:28 +01:00
this.reset();
2019-10-21 02:25:42 +02:00
this.input.focus();
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
get inputText(): string {
return this.input.innerHTML
2018-11-28 22:41:59 +01:00
.replaceAll(/<br>/, "");
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
set inputText(inputText: string) {
this.input.innerHTML = inputText;
}
get outputText(): string {
return this.output.innerHTML;
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
set outputText(outputText: string) {
this.output.innerHTML = outputText;
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
get prefixText(): string {
return this.prefixDiv.innerHTML;
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
set prefixText(prefixText: string) {
this.prefixDiv.innerHTML = prefixText;
2018-11-28 19:51:48 +01:00
}
2019-10-26 16:08:46 +02:00
get currentUser(): User | undefined {
2019-10-21 02:25:42 +02:00
return this._currentUser;
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
static generateHeader(): string {
return "" +
2018-11-30 17:38:33 +01:00
`${asciiHeaderHtml}
2018-11-28 19:51:48 +01:00
2018-11-29 01:08:58 +01:00
Student MSc Computer Science <span class="smallScreenOnly">
</span>@ <a href="https://www.tudelft.nl/en/">TU Delft</a>, the Netherlands
<span class="wideScreenOnly">${(new Date()).toISOString()}
</span>
2019-06-10 15:36:03 +02:00
Type "<a href="#" onclick="run('help');">help</a>" for help.
2018-11-28 19:51:48 +01:00
2018-11-29 01:08:58 +01:00
`.trimLines();
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
generatePrefix(): string {
if (!this.isLoggedIn) {
if (this._currentUser === undefined)
2018-11-29 16:10:02 +01:00
return "login as: ";
2019-10-21 02:25:42 +02:00
else
2019-10-26 16:08:46 +02:00
return `Password for ${this._currentUser.name}@fwdekker.com: `;
2018-11-29 16:10:02 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-26 16:08:46 +02:00
if (this._currentUser === undefined)
throw "User is logged in as undefined.";
return `${this._currentUser.name}@fwdekker.com <span style="color: green;">${this.fileSystem.pwd}</span>&gt; `;
2018-11-29 16:10:02 +01:00
}
2018-11-28 19:51:48 +01:00
2018-11-29 16:10:02 +01:00
clear() {
this.outputText = "";
2018-11-28 19:51:48 +01:00
}
2019-10-26 13:09:34 +02:00
private reset() {
2019-10-21 02:25:42 +02:00
this.fileSystem.reset();
2018-11-28 19:51:48 +01:00
2018-11-28 21:29:28 +01:00
this.outputText = Terminal.generateHeader();
2018-11-28 22:23:11 +01:00
this.prefixText = this.generatePrefix();
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 02:25:42 +02:00
private continueLogin(input: string) {
2019-10-26 16:08:46 +02:00
if (this.isLoggedIn)
throw "`continueLogin` is called while user is already logged in.";
const user = this._currentUser;
if (user === undefined) {
this.outputText += `${this.prefixText}${input.trim()}\n`;
this._currentUser = this.users.find(it => it.name === input.trim());
if (this._currentUser === undefined)
this._currentUser = new User(input.trim(), "temp", "temp");
2018-11-29 16:19:50 +01:00
2019-10-21 02:25:42 +02:00
this.input.classList.add("terminalCurrentFocusInputHidden");
2018-11-29 16:10:02 +01:00
} else {
2018-11-29 16:19:50 +01:00
this.outputText += `${this.prefixText}\n`;
2019-10-26 16:08:46 +02:00
if (this.users.find(it => it.name === user.name) && input === user.password) {
2019-10-21 02:25:42 +02:00
this.isLoggedIn = true;
2018-11-29 18:28:37 +01:00
this.outputText += Terminal.generateHeader();
2018-11-29 16:10:02 +01:00
} else {
2019-10-21 02:25:42 +02:00
this._currentUser = undefined;
2018-11-29 18:28:37 +01:00
this.outputText += "Access denied\n";
2018-11-29 16:10:02 +01:00
}
2018-11-29 16:19:50 +01:00
2019-10-21 02:25:42 +02:00
this.input.classList.remove("terminalCurrentFocusInputHidden");
2018-11-29 16:10:02 +01:00
}
}
logOut() {
2019-10-21 02:25:42 +02:00
this._currentUser = undefined;
this.isLoggedIn = false;
this.inputHistory.clear();
2019-10-26 13:09:34 +02:00
this.fileSystem.reset();
2018-11-29 16:10:02 +01:00
}
2019-02-09 14:37:36 +01:00
ignoreInput() {
this.outputText += `${this.prefixText}${this.inputText}\n`;
this.prefixText = this.generatePrefix();
this.inputText = "";
}
2019-10-21 02:25:42 +02:00
processInput(input: string) {
2018-11-29 16:10:02 +01:00
this.inputText = "";
2019-10-21 02:25:42 +02:00
if (!this.isLoggedIn) {
2018-11-29 16:10:02 +01:00
this.continueLogin(input);
} else {
2018-11-29 16:19:50 +01:00
this.outputText += `${this.prefixText}${input}\n`;
2019-10-21 02:25:42 +02:00
this.inputHistory.addEntry(input);
2018-11-29 16:10:02 +01:00
2019-10-21 17:07:16 +02:00
const output = this.commands.execute(input.trim());
2019-10-21 02:25:42 +02:00
if (output !== "")
2018-11-29 16:10:02 +01:00
this.outputText += output + `\n`;
}
this.prefixText = this.generatePrefix();
}
2019-10-21 02:25:42 +02:00
private onclick() {
this.input.focus();
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
2019-10-21 17:07:16 +02:00
private onkeypress(event: KeyboardEvent) {
2019-10-21 02:25:42 +02:00
switch (event.key.toLowerCase()) {
2018-11-28 22:41:59 +01:00
case "enter":
this.processInput(this.inputText.replaceAll(/&nbsp;/, " "));
2019-10-21 02:25:42 +02:00
event.preventDefault();
2018-11-28 21:29:28 +01:00
break;
}
2018-11-28 19:51:48 +01:00
}
2019-10-21 17:07:16 +02:00
private onkeydown(event: KeyboardEvent) {
2019-10-21 02:25:42 +02:00
switch (event.key.toLowerCase()) {
2018-11-28 22:41:59 +01:00
case "arrowup":
2019-10-21 02:25:42 +02:00
this.inputText = this.inputHistory.previousEntry();
window.setTimeout(() => moveCaretToEndOf(this.input), 0);
2018-11-28 21:29:28 +01:00
break;
2018-11-28 22:41:59 +01:00
case "arrowdown":
2019-10-21 02:25:42 +02:00
this.inputText = this.inputHistory.nextEntry();
window.setTimeout(() => moveCaretToEndOf(this.input), 0);
2018-11-29 00:31:43 +01:00
break;
2019-10-21 16:37:20 +02:00
case "tab":
event.preventDefault();
break;
2019-02-09 14:37:36 +01:00
case "c":
2019-10-21 02:25:42 +02:00
if (event.ctrlKey) {
2019-02-09 14:37:36 +01:00
this.ignoreInput();
2019-10-21 02:25:42 +02:00
event.preventDefault();
2019-02-09 14:37:36 +01:00
}
break;
2018-11-28 21:29:28 +01:00
}
2018-11-28 19:51:48 +01:00
}
2018-11-28 21:29:28 +01:00
}
2018-11-29 16:10:02 +01:00
class InputHistory {
2019-10-21 02:25:42 +02:00
private history: string[];
private index: number;
2018-11-29 16:10:02 +01:00
constructor() {
2019-10-21 17:07:16 +02:00
this.history = [];
this.index = -1;
2018-11-29 16:10:02 +01:00
}
addEntry(entry: string) {
2019-06-10 15:31:46 +02:00
if (entry.trim() !== "")
2019-10-21 02:25:42 +02:00
this.history.unshift(entry);
2019-10-21 02:25:42 +02:00
this.index = -1;
2018-11-29 16:10:02 +01:00
}
clear() {
2019-10-21 02:25:42 +02:00
this.history = [];
this.index = -1;
2018-11-29 16:10:02 +01:00
}
2019-10-21 02:25:42 +02:00
getEntry(index: number): string {
2019-06-10 15:31:46 +02:00
if (index >= 0)
2019-10-21 02:25:42 +02:00
return this.history[index];
2019-06-10 15:31:46 +02:00
else
2018-11-29 16:10:02 +01:00
return "";
}
2019-10-21 02:25:42 +02:00
nextEntry(): string {
this.index--;
if (this.index < -1)
this.index = -1;
2018-11-29 16:10:02 +01:00
2019-10-21 02:25:42 +02:00
return this.getEntry(this.index);
2018-11-29 16:10:02 +01:00
}
2019-10-21 02:25:42 +02:00
previousEntry(): string {
this.index++;
if (this.index >= this.history.length)
this.index = this.history.length - 1;
2018-11-29 16:10:02 +01:00
2019-10-21 02:25:42 +02:00
return this.getEntry(this.index);
2018-11-29 16:10:02 +01:00
}
}
2019-10-26 16:08:46 +02:00
class User {
readonly name: string;
readonly password: string;
readonly description: string;
constructor(name: string, password: string, description: string) {
this.name = name;
this.password = password;
this.description = description;
}
}
2018-11-28 21:29:28 +01:00
2019-10-21 02:25:42 +02:00
export let terminal: Terminal;
2018-11-28 21:29:28 +01:00
addOnLoad(() => {
terminal = new Terminal(
2018-11-28 22:41:59 +01:00
q("#terminal"),
q("#terminalCurrentFocusInput"),
q("#terminalOutput"),
q("#terminalCurrentPrefix")
2018-11-28 21:29:28 +01:00
);
2019-10-21 02:25:42 +02:00
// @ts-ignore: Force definition
window.relToAbs = (filename: string) => terminal.fileSystem.pwd + filename;
// @ts-ignore: Force definition
window.run = (command: string) => terminal.processInput(command);
2018-11-28 22:41:59 +01:00
terminal.processInput("ls");
2018-11-28 19:51:48 +01:00
});