diff --git a/data/scripts/Terminal.js b/data/scripts/Terminal.js
new file mode 100644
index 0000000..87a35dc
--- /dev/null
+++ b/data/scripts/Terminal.js
@@ -0,0 +1,560 @@
+const TERM_X_MIN = 60;
+const TERM_Y_MIN = 10;
+// the following 2 values are empirical determined in chromium
+const TERM_CELL_HEIGHT = 24;
+const TERM_CELL_WIDTH = 10;
+
+const ANSI_NO = 0;
+const ANSI_ESC = 1;
+const ANSI_CSI1 = 2;
+const ANSI_CSI2 = 3;
+const ANSI_CSI3 = 4;
+const ANSI_CUU = 5;
+const ANSI_CUD = 6;
+const ANSI_CUF = 7;
+const ANSI_CUB = 8;
+const ANSI_CNL = 9;
+const ANSI_CPL = 10;
+const ANSI_CHA = 11;
+const ANSI_CUP = 12;
+const ANSI_ED = 13;
+const ANSI_EL = 14;
+const ANSI_SCP = 15;
+const ANSI_RCP = 16;
+const ANSI_DECTCEM = 17;
+
+var Terminal = function() {
+}
+Terminal.prototype.xSize;
+Terminal.prototype.ySize;
+Terminal.prototype.xPosition = 0;
+Terminal.prototype.yPosition = 0;
+Terminal.prototype.xPositionSaved = 0;
+Terminal.prototype.yPositionSaved = 0;
+Terminal.prototype.tickId;
+
+Terminal.prototype.staticNoShift = 4;
+Terminal.prototype.color = "white";
+Terminal.prototype.backgroundColor = "black";
+Terminal.prototype.bold = false;
+Terminal.prototype.displayCursor = true;
+
+Terminal.prototype.calculateSize = function(width, height) {
+ this.xSize = parseInt(width / (TERM_CELL_WIDTH));
+ if (this.xSize < TERM_X_MIN)
+ this.xSize = TERM_X_MIN;
+ this.ySize = parseInt(height / (TERM_CELL_HEIGHT));
+ if (this.ySize < TERM_Y_MIN)
+ this.ySize = TERM_Y_MIN;
+
+ console.log("Terminal resolution is: " + this.xSize + "x" + this.ySize);
+}
+Terminal.prototype.getBasicHTML = function() {
+ var code = "";
+ code += "
";
+ for (var i = 0; i < this.ySize; i++) {
+ code += "";
+ for (var j = 0; j < this.xSize; j++)
+ code += " | "
+ code += "
";
+ }
+ code += "
";
+ code += this.getInputHTML();
+ return code;
+}
+Terminal.prototype.getInputHTML = function() {
+ var code = "\
+ ";
+ return code;
+}
+Terminal.prototype.init = function() {
+ window.onkeypress = this.handleKeyPress;
+ window.onkeydown = this.handleKeyDown;
+ this.tickId = window.setInterval(this.tick, 10);
+ document.getElementById("input").focus();
+ this.cursorOn();
+}
+Terminal.prototype.tick = function() {
+
+}
+Terminal.prototype.globalLineFeed = function() {
+ for (var i = this.staticNoShift; i < this.ySize; i++) {
+ for (var j = 0; j < this.xSize; j++) {
+ var to = document.getElementsByClassName((i - 1) + "c" + j)[0];
+ var from = document.getElementsByClassName(i + "c" + j)[0];
+ to.innerHTML = form.innerHTML;
+ to.style.color = from.style.color;
+ to.style.fontWeight = from.style.fontWeight;
+ to.style.backgroundColor = from.style.backgroundColor;
+ }
+ }
+ for (var j = 0; j < this.xSize; j++) {
+ var to = document.getElementsByClassName((this.ySize - 1) + "c" + j)[0];
+ to.innerHTML = " ";
+ to.style.color = this.color;
+ to.style.backgroundColor = this.backgroundColor;
+ if (this.bold) {
+ to.style.fontWeight = "bold";
+ } else {
+ to.style.fontWeight = "normal";
+ }
+ }
+ if (--this.yPosition < 0)
+ this.yPosition = 0;
+}
+Terminal.prototype.cursorOff = function() {
+ var cell = document.getElementById("cursor");
+ if (cell != undefined) {
+ cell.style.borderColor = this.backgroundColor;
+ cell.id = undefined;
+ }
+}
+Terminal.prototype.cursorOn = function() {
+ var cell = document.getElementsByClassName(this.yPosition + "c" + this.xPosition)[0];
+ if (this.displayCursor) {
+ cell.style.borderColor = this.color;
+ cell.id = "cursor";
+ }
+}
+
+Terminal.prototype.normalOutputChar = function(char) {
+ this.cursorOff();
+ if (char == "\n") {
+ if (++this.yPosition >= this.ySize)
+ this.globalLineFeed();
+ this.xPosition = 0;
+ } else {
+ var cell = document.getElementsByClassName(this.yPosition + "c" + this.xPosition++)[0];
+ cell.innerHTML = char;
+ if (this.bold)
+ cell.style.fontWeight = "bold";
+ else
+ cell.style.fontWeight = "normal";
+ cell.style.color = this.color;
+ cell.style.backgroundColor = this.backgroundColor;
+ cell.style.boderColor = this.backgroundColor;
+
+ if (this.xPosition >= this.xSize) {
+ this.xPosition = 0;
+ this.yPosition++;
+ }
+ if (this.yPosition >= this.ySize) {
+ this.globalLineFeed();
+ }
+ }
+ this.cursorOn();
+}
+Terminal.prototype.normalOutput = function(text) {
+ for (var i = 0; i < text.length; i++) {
+ this.normalOutputChar(text.charAt(i));
+ }
+}
+Terminal.prototype.output = function(text) {
+ var tmp = "";
+ var param1 = "";
+ var param2 = "";
+ var state = ANSI_NO;
+ for (var i = 0; i < text.length; i++) {
+ switch(state) {
+ case ANSI_NO: // normal text
+ if (text[i] == "\033")
+ state = ANSI_ESC; // maybe a ANSI-sequence
+ else
+ this.normalOutputChar(text.charAt(i));
+ break;
+ case ANSI_ESC:
+ if (text[i] == "[")
+ state = ANSI_CSI1; // maybe a CSI-sequence
+ else {
+ this.normalOutputChar("\033");
+ state = ANSI_NO;
+ }
+ break;
+ case ANSI_CSI1:
+ if (isNumber(text[i])) {
+ param1 += text[i];
+ break;
+ }
+ if (param1.length > 0 && text[i] == ";") {
+ tmp = ";"
+ state = ANSI_CSI2;
+ break;
+ }
+ if (param1.length > 0) {
+ state = ANSI_CSI3;
+ break;
+ }
+ if (text[i] == "?") {
+ state = ANSI_DECTCEM;
+ break;
+ }
+ if (text[i] == "s") {
+ state = ANSI_SCP;
+ i--;
+ break;
+ }
+ if (text[i] == "u") {
+ state = ANSI_RCP;
+ i--;
+ break;
+ }
+ if (text[i] == "J") {
+ state = ANSI_ED;
+ i--;
+ if (param1.length == 0)
+ param1 = "0";
+ break;
+ }
+ this.normalOutput("\033[" + param1 + text[i])
+ param1 = "";
+ state = ANSI_NO;
+ break;
+ case ANSI_CSI2:
+ if (isNumber(text[i])) {
+ param2 += text[i];
+ break;
+ }
+ if (param2.length > 0) {
+ state = ANSI_CSI3;
+ break;
+ }
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ tmp = "";
+ param2 = "";
+ state = ANSI_NO;
+ break;
+ case ANSI_CSI3:
+ switch(text[i--]) {
+ case "A":
+ state = ANSI_CUU;
+ break;
+ case "B":
+ state = ANSI_CUD;
+ break;
+ case "C":
+ state = ANSI_CUF;
+ break;
+ case "D":
+ state = ANSI_CUB;
+ break;
+ case "E":
+ state = ANSI_CNL;
+ break;
+ case "F":
+ state = ANSI_CPL;
+ break;
+ case "G":
+ state = ANSI_CHA;
+ break;
+ case "H":
+ state = ANSI_CUP;
+ break;
+ case "J":
+ state = ANSI_ED;
+ break;
+ case "K":
+ state = ANSI_EL;
+ break;
+ default:
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[++i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ }
+ break;
+ case ANSI_CUU:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.yPosition -= parseInt(param1);
+ if (this.yPosition < 0)
+ this.yPosition = 0;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CUD:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.yPosition += parseInt(param1);
+ if (this.yPosition >= this.ySize)
+ this.yPosition = this.ySize - 1;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CUF:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.xPosition += parseInt(param1);
+ if (this.xPosition >= this.xSize)
+ this.xPosition = this.xSize - 1;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CUB:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.xPosition -= parseInt(param1);
+ if (this.xPosition < 0)
+ this.xPosition = 0;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CNL:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.xPosition = 0;
+ this.yPosition += parseInt(param1);
+ if (this.yPosition >= this.ySize)
+ this.yPosition = this.ySize - 1;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CPL:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.xPosition = 0;
+ this.yPosition -= parseInt(param1);
+ if (this.yPosition < 0)
+ this.yPosition = 0;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CHA:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.xPosition = parseInt(param1) - 1;
+ if (this.xPosition >= this.xSize)
+ this.xPosition = this.xSize - 1;
+ if (this.xPosition < 0)
+ this.xPosition = 0;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_CUP:
+ if (tmp == "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ this.cursorOff();
+ this.xPosition = parseInt(param2) - 1;
+ if (this.xPosition >= this.xSize)
+ this.xPosition = this.xSize - 1;
+ if (this.xPosition < 0)
+ this.xPosition = 0;
+ this.yPosition = parseInt(param1) - 1;
+ if (this.yPosition >= this.ySize)
+ this.yPosition = this.ySize - 1;
+ if (this.yPosition < 0)
+ this.yPosition = 0;
+ this.cursorOn();
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_ED:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ switch (parseInt(param1)) {
+ case 0:
+ var x = this.xPosition;
+ var y = this.yPosition;
+ var string = "";
+ for (var i = 0; i < this.xSize * (this.ySize - y - 1) + this.xSize - x; i++)
+ string += " ";
+ this.normalOutput(string);
+ this.xPosition = x;
+ this.yPosition = y;
+ break;
+ case 1:
+ var x = this.xPosition;
+ var y = this.yPosition;
+ var string = "";
+ for (var i = 0; i < this.xSize * (y - 1) + x; i++)
+ string += " ";
+ this.xPosition = 0;
+ this.yPosition = 0;
+ this.normalOutput(string);
+ this.xPosition = x;
+ this.yPosition = y;
+ break;
+ case 2:
+ var x = this.xPosition;
+ var y = this.yPosition;
+ var string = "";
+ for (var i = 0; i < this.xSize * this.ySize; i++)
+ string += " ";
+ this.xPosition = 0;
+ this.yPosition = 0;
+ this.normalOutput(string);
+ this.xPosition = x;
+ this.yPosition = y;
+ break;
+ default:
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ }
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_EL:
+ if (tmp != "") {
+ state = ANSI_NO;
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ }
+ switch (parseInt(param1)) {
+ case 0:
+ var x = this.xPosition;
+ var y = this.yPosition;
+ var string = "";
+ for (var i = 0; i < this.xSize - x; i++)
+ string += " ";
+ this.normalOutput(string);
+ this.xPosition = x;
+ this.yPosition = y;
+ break;
+ case 1:
+ var x = this.xPosition;
+ var string = "";
+ for (var i = 0; i < x; i++)
+ string += " ";
+ this.xPosition = 0;
+ this.normalOutput(string);
+ this.xPosition = x;
+ break;
+ case 2:
+ var x = this.xPosition;
+ var y = this.yPosition;
+ var string = "";
+ for (var i = 0; i < this.xSize; i++)
+ string += " ";
+ this.xPosition = 0;
+ this.normalOutput(string);
+ this.xPosition = x;
+ this.yPosition = y;
+ break;
+ default:
+ this.normalOutput("\033[" + param1 + tmp + param2 + text[i]);
+ }
+ state = ANSI_NO;
+ param1 = "";
+ param2 = "";
+ tmp = "";
+ break;
+ case ANSI_SCP:
+ state = ANSI_NO;
+ this.xPositionSaved = this.xPosition;
+ this.yPositionSaved = this.yPosition;
+ break;
+ case ANSI_RCP:
+ state = ANSI_NO;
+ this.xPosition = this.xPositionSaved;
+ this.yPosition = this.yPositionSaved;
+ break;
+ case ANSI_DECTCEM:
+ if (tmp.length < 3)
+ break;
+ if (tmp == "25l") {
+ this.displayCursor = false;
+ tmp = "";
+ state = ANSI_NO;
+ }
+ if (tmp == "25h") {
+ this.displayCursor = true;
+ tmp = "";
+ state = ANSI_NO;
+ }
+ break;
+ }
+ }
+ if (tmp.length || param1.length || param2.length)
+ this.normalOutput("\033[" + param1 + tmp + param2);
+}