TerminalHomepage/js/main.js
2023-01-16 15:28:11 +01:00

217 lines
7.4 KiB
JavaScript

const user = { id: crypto.randomUUID(), name: "", history: [] };
let pretext = "root@baipyr.us:~# ";
// Initiating variables
const length = pretext.length+1;
let startPosition = length;
let cursorPosition = 0;
let cursorYOffset = 7;
let history = {index: 0, list: []};
// Initiating constants
const tbDiv = document.getElementById("textbox");
const textIn = document.getElementById("input");
const textCur = document.getElementById("current");
const cursor = document.getElementById("cursor");
// Initial cursor offset
cursor.style.transform = `translate(${length-1}ch,${2.222*7-0.4}ch)`;
// Handle user input
textIn.oninput = () => {
const text = textCur.textContent;
// If cursor at end of text
if (cursorPosition === text.length)
textCur.textContent += textIn.value;
else {
// Insert input at cursor position
const left = text.substring(0, cursorPosition);
const right = text.substring(cursorPosition, text.length);
textCur.textContent = left + textIn.value + right;
}
// Increment by input length
cursorPosition += textIn.value.length;
textIn.value = "";
updateCursor();
};
textIn.focus();
// If no ranged selection was made, focus on input field
document.addEventListener("selectionchange", e => {
if (e.target !== textIn) {
const selection = window.getSelection();
if (selection.type === "Caret")
textIn.focus();
}
});
document.addEventListener("keydown", async e => {
const text = textCur.textContent;
switch (e.key) {
case "Backspace":
// Ein Zeichen nach links löschen
if (cursorPosition === 0)
return;
if (cursorPosition <= textCur.textContent.length-1) {
const a = text.substring(0, cursorPosition-1);
const b = text.substring(cursorPosition, text.length);
textCur.textContent=a+b;
} else
textCur.textContent = text.substring(0,text.length-1);
cursorPosition--;
updateCursor();
break;
case "ArrowLeft":
// Ein Zeichen nach links
if (cursorPosition > 0) {
cursorPosition--;
updateCursor();
}
break;
case "ArrowRight":
// Ein Zeichen nach reckts
if (cursorPosition < textCur.textContent.length) {
cursorPosition++;
updateCursor();
}
break;
case "Delete":
// Ein Zeichen nach rechts löschen
if (cursorPosition >= textCur.textContent.length)
return;
if (cursorPosition === 0 && textCur.textContent.length === 0)
return;
const a = text.substring(0, cursorPosition);
const b = text.substring(cursorPosition+1, text.length);
textCur.textContent=a+b;
break;
case "Enter":
// Befehl absenden / Zeilenumbruch
tbDiv.removeChild(textIn);
tbDiv.removeChild(textCur);
const inputSet = new Set(text.split(''));
if (text !== "" || inputSet.has(" ") && inputSet.size === 1) {
history.list.push(text);
history.index = history.list.length;
}
// Fix current input to page
tbDiv.appendChild(createText(text));
tbDiv.innerHTML += `<br>`;
// Run command and return output
let commandOutput = await runCommand(text);
if (commandOutput !== "") {
// If output is given, display it as text with formatting
tbDiv.append(createText(commandOutput, false))
tbDiv.innerHTML += `<br>`;
// Offset cursor by at least one line, and as many more as there are line breaks
cursorYOffset += commandOutput.split("<br>").length;
}
// Add new pretext to terminal
tbDiv.innerHTML += pretext;
// Add elements on top of div
tbDiv.appendChild(textCur);
tbDiv.appendChild(textIn);
// Reset variables
textCur.textContent = "";
cursorPosition = 0;
cursorYOffset++;
updateCursor();
break;
case "ArrowUp":
// Einen Befehl zurück in der History
if (history.index > 0) {
history.index--;
textCur.textContent = history.list[history.index];
cursorPosition = textCur.textContent.length;
}
updateCursor();
break;
case "ArrowDown":
// Einen Befehl nach vorne in der History
if (history.index < history.list.length-1) {
history.index++;
textCur.textContent = history.list[history.index];
cursorPosition = textCur.textContent.length;
} else if (history.index < history.list.length) {
history.index++;
textCur.textContent = "";
cursorPosition = 0;
}
updateCursor();
break;
case "Tab":
// Autocomplete
break;
}
});
// Create an 'a' element with input string as its contents
function createText(str, safe=true) {
const link = document.createElement("a");
link.style.color = "lightgrey";
link.style.whiteSpace = "pre";
// No "HTML injection"
if (safe)
link.textContent = str;
// "Anything goes" and keep formatting
else
link.innerHTML = str;
return link;
}
// Run input as command if possible
async function runCommand(input) {
// Return on no input
if (input === "")
return "";
let output = "";
const lowerIn = input.toLowerCase();
// Go through properties of window
for (const func in window) {
const splits = func.split("cmd_");
// If property is prefixed with 'cmd_' (a 'command', so an executable function)
if (splits.length === 2) {
const name = splits[1];
const lowerNm = name.toLowerCase();
// If command is called without parameters
if (lowerIn === lowerNm) {
if (window[func].constructor.name === "AsnycFunction")
output = await window[func]();
else
output = window[func]();
break;
// If command is called with parameters
} else if (lowerIn.startsWith(lowerNm + " ")) {
// Parameters always follow the command name after first space
const params = input.split(" ").filter((e,i)=>i!==0);
if (window[func].constructor.name === "AsnycFunction")
output = await window[func](params);
else
output = window[func](params);
break;
}
}
}
// Standard output:
if (output === "" && !lowerIn.startsWith("clear"))
output = `${input.split(" ")[0]}: command not found`;
// Return command output
return output;
}
// Update cursor position based on coordinates
function updateCursor() {
let cursor = document.getElementById("cursor");
cursor.style.transform = `translate(${startPosition+cursorPosition-1}ch, ${2.222*cursorYOffset-0.4}ch)`;
}
// Toggle cursor visibility
setInterval(()=>{
cursor.style.opacity = !parseInt(cursor.style.opacity)+0;
},1000);