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.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; } 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) { 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}`; 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) t = " " this.write(t, 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," "); 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; } }