import { LocalizedStrings } from "./../services/localizedStringsService"; import * as Utils from "./../util/utils"; import { Config } from "./../util/config"; export class DOMConsole { static get BASH_TEMPLATE () { return `
Terminal
`; } static get INPUT_CARET_TEMPLATE () { return `
`; } static get USER () { return 0; } static get INFO () { return 1; } static get ERR () { return 2; } static get INPUT () { return 3; } constructor (elementID, disableMarginTop = false) { this.disableMarginTop = disableMarginTop; this.input = null; this.cursorInterval = null; this.idleInterval = null; this.inputDiv = null; this.inputCMD = null; this.inputSpan = null; this.cursorRef = null; this.needInput = false; this.clearBtn = null; this.hideBtn = null; this.showBtn = null; this.termDiv = null; this.anyKey = false; let actualID = elementID; if (elementID[0] === "#") { actualID = elementID.substring(1); } this.parent = document.getElementById(actualID); this.setup(); this.inputListeners = []; this.hideInput(); this.pending_writes = []; this.last_clear = -1; } setup () { this._setupDom(); this._setupEvents(); } _setupEvents () { this.input.addEventListener("keydown", this.registerInput.bind(this)); this.clearBtn.addEventListener("click", this.clearBtnClick.bind(this)); this.hideBtn.addEventListener("click", this.hideBtnClick.bind(this)); this.showBtn.addEventListener("click", this.showBtnClick.bind(this)); } registerInput (event) { if (!this.needInput) { return; } const keyCode = event.which; if (keyCode === 13 || this.anyKey) { if (this.idleInterval != null) { clearInterval(this.idleInterval); this.idleInterval = null; } let text = this.input.value; text = text.replace("[\n\r]+", ""); this.notifyListeners(text); this._appendUserInput(text); this.input.value = ""; this.inputSpan.innerHTML = ""; this.currentLine = null; } } _setupDom () { const bashNode = document.createElement("div"); bashNode.classList.add("bash"); bashNode.innerHTML = DOMConsole.BASH_TEMPLATE; this.termDiv = bashNode.querySelector("#ivprog-term"); this.termDiv.classList.add("ivprog-term-div"); this.inputDiv = document.createElement("div"); this.inputDiv.id = "ivprog-terminal-inputdiv"; this.inputDiv.innerHTML = DOMConsole.INPUT_CARET_TEMPLATE; this.input = document.createElement("input"); this.input.setAttribute("name", "command"); this.input.setAttribute("value", ""); this.input.setAttribute("type", "text"); this.input.setAttribute("autocomplete", "off"); this.inputDiv.append(this.input); this.termDiv.append(this.inputDiv); bashNode.append(this.termDiv); this.parent.append(bashNode); this.inputCMD = this.inputDiv.querySelector("#cmd"); this.cursorRef = this.inputCMD.querySelector("#cursor"); this.inputSpan = this.inputCMD.querySelector("span"); this.clearBtn = bashNode.querySelector("#ivprog-console-clearbtn"); this.hideBtn = bashNode.querySelector("#ivprog-console-hidebtn"); this.showBtn = bashNode.querySelector("#ivprog-console-showbtn"); this._setupCursor(); //Jquery tooltips.... window .$(this.clearBtn) .popup({ content: LocalizedStrings.getUI("tooltip_terminal_clear") }); window .$(this.showBtn) .popup({ content: LocalizedStrings.getUI("tooltip_terminal_show") }); window .$(this.hideBtn) .popup({ content: LocalizedStrings.getUI("tooltip_terminal_hide") }); } _setupCursor () { this.inputCMD.addEventListener("click", this.blinkCaretAndFocus.bind(this)); //this.inputCMD.click(); this.input.addEventListener("keyup", this.updateSpanText.bind(this)); this.input.addEventListener("blur", this.stopBlinkCaret.bind(this)); } blinkCaretAndFocus () { if (this.cursorInterval != null) { return; } this.input.focus(); this.cursorInterval = window.setInterval(() => { if (this.cursorRef.style.visibility === "visible") { this.cursorRef.style.visibility = "hidden"; } else { this.cursorRef.style.visibility = "visible"; } }, 500); } updateSpanText () { this.inputSpan.innerHTML = this.input.value; if (this.idleInterval != null) window.clearInterval(this.idleInterval); this.scheduleNotify(); } stopBlinkCaret () { clearInterval(this.cursorInterval); this.cursorInterval = null; this.cursorRef.style.visibility = "visible"; } notifyListeners (text) { this.inputListeners.forEach((resolve) => resolve(text)); this.inputListeners.splice(0, this.inputListeners.length); this.hideInput(); this.anyKey = false; } writeRawHTML (text, channel) { this._appendTextLn(text, channel, false); } write (text, newLine = false) { this._appendText(text, DOMConsole.USER, newLine); } info (text) { this._appendTextLn(text, DOMConsole.INFO); } err (text) { this._appendTextLn(text, DOMConsole.ERR); } async _appendText (text, type, newLine = false) { // console.debug("Caling appendText"); const write_time = Date.now(); this.pending_writes.push(0); await Utils.sleep(5); this.pending_writes.pop(); if (this.last_clear >= write_time) { return; } if (this.currentLine == null) { const divClass = this.getClassForType(type); const textDiv = document.createElement("div"); textDiv.classList.add(divClass); this.termDiv.insertBefore(textDiv, this.inputDiv); this.currentLine = textDiv; } this.currentLine.innerHTML += this.getOutputText(text); if (newLine) { // console.debug("append newline"); this.currentLine = null; } this.scrollTerm(); } async _appendTextLn (text, type, filter = true) { const write_time = Date.now(); this.pending_writes.push(0); await Utils.sleep(5); this.pending_writes.pop(); if (this.last_clear >= write_time) { return; } const divClass = this.getClassForType(type); const textDiv = document.createElement("div"); textDiv.classList.add(divClass); if (filter) textDiv.innerHTML = this.getOutputText(text); else textDiv.innerHTML = `${text}`; if (this.currentLine != null && this.currentLine.innerHTML.length === 0) { this.termDiv.removeChild(this.currentLine); } this.termDiv.insertBefore(textDiv, this.inputDiv); this.currentLine = null; this.scrollTerm(); } async _appendUserInput (text) { const write_time = Date.now(); this.pending_writes.push(0); await Utils.sleep(5); this.pending_writes.pop(); if (this.last_clear >= write_time) { return; } const divClass = this.getClassForType(DOMConsole.INPUT); const textDiv = document.createElement("div"); textDiv.innerHTML = this.getUserInputText(text); textDiv.classList.add(divClass); this.termDiv.insertBefore(textDiv, this.inputDiv); this.currentLine = null; this.scrollTerm(); } getOutputText (text) { text = text.replace(/\s/g, " "); return `${text}`; } getUserInputText (text) { if (text.trim().length == 0) { text = " "; } return `${text}`; } scrollTerm () { //scrollIt(this.inputDiv.previousSibling,200); this.termDiv.scrollTop = this.termDiv.scrollHeight; } focus () { this.termDiv.style.display = "block"; // Is in draggable mode? if (!this.disableMarginTop && this.parent.style.top.length == 0) { this.parent.style.marginTop = "-160px"; } if (this.needInput) { this.showInput(); this.scheduleNotify(); } if (!Utils.isElementInViewport(this.termDiv)) this.termDiv.scrollIntoView(false); this.scrollTerm(); } hide () { if (this.needInput) { clearInterval(this.idleInterval); this.hideInput(); this.needInput = true; } // Is in draggable mode? if (!this.disableMarginTop && this.parent.style.top.length == 0) { this.parent.style.marginTop = "0"; } this.termDiv.style.display = "none"; } getClassForType (type) { switch (type) { case DOMConsole.INPUT: return "ivprog-term-userInput"; case DOMConsole.USER: return "ivprog-term-userText"; case DOMConsole.INFO: return "ivprog-term-info"; case DOMConsole.ERR: return "ivprog-term-error"; } } dispose () { this.input.removeEventListener("keyup", this.updateSpanText.bind(this)); this.input.removeEventListener("blur", this.stopBlinkCaret.bind(this)); this.input.removeEventListener("keydown", this.registerInput.bind(this)); this.inputCMD.removeEventListener( "click", this.blinkCaretAndFocus.bind(this) ); this.clearBtn.removeEventListener("click", this.clearBtnClick.bind(this)); this.hideBtn.removeEventListener("click", this.hideBtnClick.bind(this)); this.showBtn.removeEventListener("click", this.showBtnClick.bind(this)); this.input = null; this.inputCMD = null; this.inputDiv = null; this.termDiv = null; this.inputSpan = null; this.cursorRef = null; this.clearBtn = null; this.hideBtn = null; this.showBtn = null; this.currentLine = null; const cNode = this.parent.cloneNode(false); this.parent.parentNode.replaceChild(cNode, this.parent); if (this.cursorInterval != null) { clearInterval(this.cursorInterval); } if (this.idleInterval != null) { clearInterval(this.idleInterval); } } showInput () { this.needInput = true; this.inputDiv.style.display = "block"; this.inputCMD.click(); //this.inputCMD.scrollIntoView(); this.scrollTerm(); } hideInput () { this.needInput = false; this.inputDiv.style.display = " none"; clearInterval(this.cursorInterval); this.cursorInterval = null; } requestInput (anyKey = false) { const promise = new Promise((resolve, _) => { this.inputListeners.push(resolve); this.anyKey = anyKey; if (this.idleInterval == null) this.scheduleNotify(); this.showInput(); }); return promise; } sendOutput (text) { //console.debug(text); let output = "" + text; if (output.indexOf("\n") !== -1) { //console.debug("newline"); const outputList = output.split("\n"); let i = 0; for (; i < outputList.length - 1; i += 1) { //console.debug("newline write"); let t = outputList[i]; t = t.replace(/\t/g, " "); t = t.replace(/\s/g, " "); if (t.length !== 0) { this.write(t); } this.write("", true); } let t = outputList[i]; t = t.replace(/\t/g, " "); t = t.replace(/\s/g, " "); if (t.length != 0) this.write(t); } else { // console.debug("no newline"); output = output.replace(/\t/g, " "); output = output.replace(/\s/g, " "); if (output.length != 0) this.write(output); } } clearPendingWrites () { this.last_clear = Date.now(); } clear () { this.clearPendingWrites(); while (this.inputDiv.parentElement.childNodes.length > 1) { this.inputDiv.parentElement.removeChild( this.inputDiv.parentElement.firstChild ); } this.input.value = ""; this.inputSpan.innerHTML = ""; this.currentLine = null; } clearBtnClick () { this.clear(); } showBtnClick () { this.focus(); } hideBtnClick () { this.hide(); } notifyIdle () { this.info(LocalizedStrings.getMessage("awaiting_input_message")); this.inputCMD.click(); } scheduleNotify () { this.idleInterval = window.setInterval( this.notifyIdle.bind(this), Config.idle_input_interval ); } cancelPendingInputRequests () { this.inputListeners.forEach((resolve) => resolve("")); this.inputListeners.splice(0, this.inputListeners.length); if (this.idleInterval != null) { clearInterval(this.idleInterval); this.idleInterval = null; } this.input.value = ""; this.inputSpan.innerHTML = ""; this.currentLine = null; this.hideInput(); this.anyKey = false; } }