terminal-cv/src/app.js
2022-03-20 18:21:10 +01:00

249 lines
7.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @typedef Command
* @property {string} command
* @property {string} responseType
* @property {string?} value
* @property {string[]?} headers
* @property {string[]?} rows
*/
/**
* @type {Command[]} commands
*/
import commands from "./resources/commands.json";
import { getCV, pif, rmRf, setDarkMode } from "./custom-comands";
import { dragElement } from "./draggable";
// Tableau contenant les commandes (utile pour la complétion des commandes)
let commandsList = [];
commands.forEach((c) => {
commandsList.push(c.command);
});
// Commandes qui nécessitent un traitement JS
const customCommands = ["clear", "dark", "light", "get cv"];
commandsList = commandsList.concat(customCommands);
// Commandes 'easter eggs' non disponibles à l'autocomplétion
const hiddenCommands = ["pif", "rm -rf /"];
// Ajout de la possibilité de déplacer la fenêtre pour les PC
if (window.innerWidth > 1024) {
dragElement(document.querySelector(".terminal"));
}
// Tableau contenant l'historique des commandes
const commandsHistory = [];
let historyMode = false;
let historyIndex = -1;
const terminalBody = document.querySelector(".terminal__body");
// Ajout de la ligne par défaut
addNewLine();
// Easter egg de décembre, ajout de flocons de neige
const now = new Date();
if (now.getMonth() === 11) {
let htmlFlakes = "";
for (let i = 0; i < 6; i++) {
htmlFlakes += `<div class="snowflake">❅</div><div class="snowflake">❆</div>`;
}
const html = `<div class="snowflakes" aria-hidden="true">${htmlFlakes}</div>`;
document.body.append(stringToDom(html));
}
/**
* Retourne le HTML de la réponse pour une commande donnée
* @param {string} command
*/
function getDomForCommand(command) {
const commandObj = commands.find((el) => el.command === command);
let html = "";
if (commandObj === undefined) {
html = `'${
command.split(" ")[0]
}' nest pas reconnu en tant que commande interne ou externe, un programme exécutable ou un fichier de commandes. Tapez la commande <code>help</code> pour afficher la liste des commandes disponibles.`;
} else {
if (commandObj.responseType === "list" && Array.isArray(commandObj.value)) {
html = "<ul>";
html += commandObj.value.map((s) => `<li>${s}</li>`).join("");
html += "</ul>";
} else if (commandObj.responseType === "text") {
html = commandObj.value;
} else if (commandObj.responseType === "table") {
const headers = commandObj.headers;
const rows = commandObj.rows;
const thsHtml = headers.map((h) => `<th>${h}</th>`).join("");
const tdsHtml = rows
.map((r) => `<tr>${r.map((rtd) => `<td>${rtd}</td>`).join("")}</tr>`)
.join("");
html = `<table><thead><tr>${thsHtml}</tr></thead><tbody>${tdsHtml}</tbody></table>`;
} else if (commandObj.responseType === "code") {
html = `<pre>${commandObj.value.join("\n")}</pre>`;
}
}
return html;
}
/**
* Ajoute une nouvelle ligne input de commande et désactive la précédente.
* @param {string|null} previousUid uid de la ligne précédente.
*/
function addNewLine(previousUid = null) {
const uid = Math.random().toString(36).replace("0.", "");
// terminal__line
const terminalLineEl = document.createElement("div");
terminalLineEl.classList.add("terminal__line");
// terminal__response
const terminalResponseEl = document.createElement("div");
terminalResponseEl.classList.add("terminal__response");
terminalResponseEl.id = `response-${uid}`;
// input text
const inputEl = document.createElement("input");
inputEl.type = "text";
inputEl.id = `input-${uid}`;
inputEl.autocapitalize = "off";
inputEl.dataset.uid = uid;
inputEl.dataset.active = "1"; // Utile pour le focus
inputEl.addEventListener("keydown", onCommandInput);
terminalLineEl.appendChild(inputEl);
if (previousUid) {
const previousInputEl = document.getElementById(previousUid);
if (previousInputEl) {
previousInputEl.setAttribute("disabled", "true");
previousInputEl.removeEventListener("keydown", onCommandInput);
delete previousInputEl.dataset.active;
}
}
document.getElementById("terminal").appendChild(terminalLineEl);
document.getElementById("terminal").appendChild(terminalResponseEl);
inputEl.focus(); // Ajoute le focus dès la création du champs
}
/**
* Gère le keydown sur l'input de la commande.
* @param e
*/
function onCommandInput(e) {
const commandValue = e.target.value.trim().toLowerCase();
if (e.keyCode === 13) {
// ENTER
if (commandValue !== "") {
historyMode = false;
const idResponse = `response-${e.target.dataset.uid}`;
const responseEl = document.getElementById(idResponse);
let html;
if (
hiddenCommands.includes(commandValue) ||
customCommands.includes(commandValue)
) {
html = handleCustomCommands(commandValue);
} else {
html = getDomForCommand(commandValue);
}
if (responseEl) {
responseEl.innerHTML = html;
commandsHistory.push(commandValue);
addNewLine(e.target.id);
}
}
} else if (e.keyCode === 9) {
// TAB
e.preventDefault();
if (commandValue === "") {
this.value = "help";
} else {
const matchingCommand = commandsList.find((c) =>
c.startsWith(commandValue)
);
if (matchingCommand) {
this.value = matchingCommand;
}
}
historyMode = false;
} else if (e.keyCode === 38 || e.keyCode === 40) {
// UP / DOWN
// Gestion de l'historique
if (commandsHistory.length > 0) {
if (historyMode === false) {
historyIndex = commandsHistory.length - 1;
} else {
if (e.keyCode === 38 && historyIndex !== 0) {
// UP
historyIndex--;
} else if (
e.keyCode === 40 &&
historyIndex !== commandsHistory.length - 1
) {
historyIndex++;
}
}
this.value = commandsHistory[historyIndex];
}
historyMode = true;
}
}
/**
* Permet de gérer les commandes cachées (non proposées dans l'autocomplétion)
* @param {string} command
* @returns {string|void} Html à afficher dans la réponse de la commande
*/
function handleCustomCommands(command) {
switch (command) {
case "pif":
pif();
return "C'est la fête !";
case "light":
if (document.body.classList.length === 0)
return "Vous êtes déjà en mode clair";
setDarkMode(false);
return "Vous êtes maintenant en mode clair.";
case "dark":
if (document.body.classList.length === 1)
return "Vous êtes déjà en mode sombre";
setDarkMode(true);
return "Vous êtes maintenant en mode sombre.";
case "get cv":
getCV();
return "Le CV va être téléchargé.";
case "rm -rf /":
rmRf();
return "w4dhIHZhIFDDiVRFUiAh";
case "clear":
terminalBody.innerHTML = `<div id="terminal"></div>`;
return;
}
}
/**
* Convert HTML to DOM object
* @param html
* @returns {DocumentFragment}
*/
function stringToDom(html) {
return document.createRange().createContextualFragment(html);
}
// ------------------------------------------------------------------------------------
// EVENT LISTENNER
// ------------------------------------------------------------------------------------
// Ajout du focus sur l'input même si on clique sur le body (pour garder le curseur)
document.body.addEventListener("click", function (e) {
if (e.target.tagName !== "INPUT") {
const activeInput = document.querySelector("input[data-active]");
activeInput.focus();
}
});
document.querySelector(".fake-close").addEventListener("click", function (e) {
const terminalEl = document.querySelector(".terminal");
terminalEl.parentElement.removeChild(terminalEl);
});