/** * @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 += `
`; } const html = ``; 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] }' n’est pas reconnu en tant que commande interne ou externe, un programme exécutable ou un fichier de commandes. Tapez la commande help pour afficher la liste des commandes disponibles.`; } else { if (commandObj.responseType === "list" && Array.isArray(commandObj.value)) { html = ""; } 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) => `${h}`).join(""); const tdsHtml = rows .map((r) => `${r.map((rtd) => `${rtd}`).join("")}`) .join(""); html = `${thsHtml}${tdsHtml}
`; } else if (commandObj.responseType === "code") { html = `
${commandObj.value.join("\n")}
`; } } 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 = `
`; 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); });