import { LocalizedStrings } from "./../services/localizedStringsService"; import { isElementInViewport } 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 = ''; } } _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) { this._appendText(text, DOMConsole.USER); } info (text) { this._appendText(text, DOMConsole.INFO); } err (text) { this._appendText(text, DOMConsole.ERR); } _appendText (text, type) { const write_time = Date.now(); const pending_write = setTimeout(() => { this.pending_writes.shift(); if(this.last_clear >= write_time) { return; } const divClass = this.getClassForType(type); const textDiv = document.createElement('div'); textDiv.classList.add(divClass); textDiv.innerHTML = this.getOutputText(text); this.termDiv.insertBefore(textDiv, this.inputDiv); this.scrollTerm(); }, 5); this.pending_writes.push(pending_write); } _appendUserInput (text) { const write_time = Date.now(); const pending_write = setTimeout(() => { this.pending_writes.shift(); 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.scrollTerm(); }, 5); this.pending_writes.push(pending_write); } getOutputText (text) { if(text.trim().length == 0) { text = " "; } 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(!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; 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 (callback, anyKey = false) { this.inputListeners.push(callback); this.anyKey = anyKey; if(this.idleInterval == null) this.scheduleNotify(); this.showInput(); } sendOutput (text) { const output = ""+text; output.split("\n").forEach(t => { t = t.replace(/\t/g,'  '); t = t.replace(/\s/g," "); this.write(t) }); } clearPendingWrites () { this.last_clear = Date.now(); for(const id in this.pending_writes) { clearTimeout(id); } } clear () { this.clearPendingWrites(); this.pending_writes = []; while(this.inputDiv.parentElement.childNodes.length > 1) { this.inputDiv.parentElement.removeChild(this.inputDiv.parentElement.firstChild); } this.input.value = ""; this.inputSpan.innerHTML = ''; } 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); } }