commit 27f0d4e1baee72b04588d93a618cdc101bd1908b Author: Ward Truyen Date: Sat Sep 21 16:19:56 2024 +0200 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..447f0ff --- /dev/null +++ b/Dockerfile @@ -0,0 +1,8 @@ +FROM node:lts-alpine3.20 + +WORKDIR /app +COPY src/ . + +RUN npm install --global serve +EXPOSE 3000 +CMD [ "serve", "." ] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d6e6781 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +#A site for WTerminal diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..a6ef32a --- /dev/null +++ b/compose.yml @@ -0,0 +1,9 @@ +services: + sitewterminal: + build: . + labels: + - "traefik.http.routers.sitewillem.rule=Host(`willem.truyen.network`) && PathPrefix(`/`)" + - "traefik.http.routers.sitewillem.entrypoints=websecure" + - "traefik.http.routers.sitewillem.tls=true" + - "traefik.http.routers.sitewillem.tls.certresolver=myresolver" + - "traefik.enable=true" diff --git a/src/addon.html b/src/addon.html new file mode 100644 index 0000000..7de2e41 --- /dev/null +++ b/src/addon.html @@ -0,0 +1,38 @@ + + + + + WTerminal as addon + + + + + + + +
+ + Home + Static + Dropdown + Pong + Addon +
+ +
+

WTerminal as addon:

+

WTerminal can be used as a browser addon/plugin.
+ By using the addon you can have a WTerminal on every site you visit!

+ +

Visit addons.mozilla.org to get WTerminal + as a firefox addon.

+ +

Or download the source code here.

+
+ + + + + diff --git a/src/css/game.css b/src/css/game.css new file mode 100644 index 0000000..ef7e55e --- /dev/null +++ b/src/css/game.css @@ -0,0 +1,17 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +*/ +.game-area { + margin: 8px auto; + width: fit-content; +} + +.game-area p { + margin: 0 2px; +} + +canvas { + border: solid 2px #3b3b3b; + border-radius: 4px; + outline: none; +} diff --git a/src/css/main.css b/src/css/main.css new file mode 100644 index 0000000..cdf8f3c --- /dev/null +++ b/src/css/main.css @@ -0,0 +1,102 @@ +/* Author: Ward Truyen + * Version: 1.3.0 + */ + +html, +body { + margin: 0px; + padding: 0px; + background-image: linear-gradient(30deg, #73B2736F 70%, #73B2732F); + height: 100%; + display: flex; + flex-direction: column; +} + +main { + margin: auto; + width: 80%; + border: 2px solid darkgreen; + padding: 10px; + background-color: white; + box-shadow: 3px 3px 3px black; +} + +main > a, main > pre, main > p{ + margin-left: 1rem; +} + +h1 { + margin-top: 4px; + margin-bottom: 8px; + text-decoration: underline; +} + +header { + padding: 2px 16px 4px 16px; + /* width: 100%; */ + border-bottom: 1px solid black; + /* background-image: linear-gradient(#0000008f, #0000004f); */ + background-color: #80808080; + /* linear-gradient(#0000008f, #0000004f);*/ + /* margin-top: 16px; */ + text-shadow: 1px 1px 1px #0000004f; +} + +header a { + /* border: solid 1px black; */ + padding: 2px; + margin: 1px; +} + +footer { + padding: 8px 16px 4px 16px; + /* width: 100%; */ + border-top: 1px solid black; + /* background-color: #0000004f; */ + background-image: linear-gradient(#0000008f, #0000004f); + /* margin-top: 16px; */ + margin-top: auto; + text-shadow: 1px 1px 1px #0000004f; +} + +footer h1, +footer h2, +footer h3, +footer p { + display: inline; + margin: 0px; +} + +.float-right { + float: right; +} + +.text-red { + color: red; +} + +.button-red { + background-color: red; +} + +.button-black { + background-color: black; + color: white; +} + +html.theme-dark { + color-scheme: dark; + background-image: linear-gradient(-10deg, #1010206F 60%, #5050506F); + color: white; +} + +.theme-dark>body>main { + border: 1px solid darkgray; + background-color: black; + box-shadow: 3px 3px 5px #101010C0; +} + +.theme-dark>body>main>h1 { + text-shadow: 0px 0px 3px blue, 2px 2px 6px; +} + diff --git a/src/css/wterminal.css b/src/css/wterminal.css new file mode 100644 index 0000000..6d93b91 --- /dev/null +++ b/src/css/wterminal.css @@ -0,0 +1,166 @@ +/* Author: Ward Truyen +* Version: 1.1.0 +*/ + +.wterminal-background { + z-index: 9995; + position: fixed; + width: 100%; + height: 100%; + top: 0px; + left: 0px; + color: black; + line-height: normal; + visibility: hidden; + background: unset; + transition: all 0.2s ease-out 0s; +} + +.wterminal-background .wterminal-container{ + position: relative; + margin: 0px auto; + width: 70%; + border: 1px solid #888; + border-top: 0px solid grey; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + padding: 0px 4px 4px 4px; + background: linear-gradient(24deg, rgba(209,211,196,1) 20%, rgba(221,221,221,1) 50%, rgba(221,221,221,1) 70%, rgba(190,199,207,1) 90%); + box-shadow: 3px 3px 3px black; + text-align: left; + margin-top: -50%; +} + +.wterminal-container pre { + color: unset; + border: 2px solid #a0a0a0d0; + min-height: 20em; + max-height: 20em; + margin: 0px 0px 2px; + padding: 2px 4px 6px 4px; + background-color: #F0F0F0; + overflow-y: scroll; + font-family: Monospace, Incosolata, Courier; + font-size: 12px; + line-height: 1.08; + width: unset; +} + +.wterminal-output u{ + /* adds a nice fat blue underline to titles */ + text-decoration-color: #8cb4ff; + text-decoration-thickness: .15rem; + text-underline-offset: .1em; +} + +.wterminal-input { + padding: 1px 2px; + margin: 0px; + background-color: #F0F0F0; + color: black; + border: 1px solid #ccc; + border-radius: 2px; +} + +.wterminal-input:hover{ + background-color: #DDD; +} + +.wterminal-container input[type="submit"]:hover, +.wterminal-container button:hover{ + background-color: #DDD; + cursor: pointer; +} +.wterminal-container input, .wterminal-container label{ + line-height: unset; + display: unset; + /* width: unset; */ + height: unset; + margin: 0; + margin-left: 2px; +} +.wterminal-container input[type="submit"], +.wterminal-container button{ + width: unset; + height: unset; + margin: 0; + margin-left: 2px; + padding: 1px 4px; + color: black; + background-color: #eee; + border: 1px solid #aaa; + border-radius: 3px; + font-weight: normal; +} + +.wterminal-background.wterminal-visible{ + background-color: #0000008F; + backdrop-filter: blur(4px); + visibility: visible; + transition: background-color .1s ease-out 0s, backdrop-filter .1s ease-out 0s; +} + +.wterminal-visible .wterminal-container{ + margin-top: 0px; + transition: margin-top .2s ease-out 0s; +} + +.wterminal-container form{ + margin-left: 2px; +} + +/* Width */ +@media screen and (max-width: 480px) { + .wterminal-input{ + width: 40%; + } +} +@media screen and (min-width: 480px) and (max-width: 720px) { + .wterminal-input{ + width: 50%; + } +} +@media screen and (min-width: 720px) and (max-width: 1080px) { + .wterminal-input{ + width: 60%; + } +} +@media screen and (min-width: 1080px) { + .wterminal-input{ + width: 70%; + } +} + +/* Height */ +@media screen and (min-height: 512px) and (max-height: 1024px) { + .wterminal-background .wterminal-container .wterminal-output{ + min-height: 32em; + max-height: 32em; + } +} +@media screen and (min-height: 1024px) and (max-height: 1536px) { + .wterminal-background .wterminal-container .wterminal-output{ + min-height: 48em; + max-height: 48em; + } +} +@media screen and (min-height: 1536px) { + .wterminal-background .wterminal-container .wterminal-output{ + min-height: 64em; + max-height: 64em; + } +} + +.theme-dark .wterminal-background .wterminal-container{ + color: #F0F0F0; + background: #303030; +} + +.theme-dark .wterminal-container pre, +.theme-dark .wterminal-container button, +.theme-dark .wterminal-container input +{ + background-color: black; + /* color: #F0F0F0; */ + color: white; +} diff --git a/src/dropdown.html b/src/dropdown.html new file mode 100644 index 0000000..23fbfdf --- /dev/null +++ b/src/dropdown.html @@ -0,0 +1,57 @@ + + + + + WTerminal dropdown sample + + + + + + + + + + +
+ + Home + Static + Dropdown + Pong + Addon +
+ +
+

Dropdown terminal

+

WTerminal is installed on this site as a dropdown/quake style terminal.

+

How to use the terminal:

+ + +

How to add the terminal to a site:

+

Add the CSS and JavaScript files to your site header:
+ <link rel="stylesheet" href="css/wterminal.css" />
+ <script src="js/wterminal.js"></script>
+

+

Then add a script ellement with:
+ WTerminal.instalDropdownTerminal(); + Or + new WTerminal("dropdown", null, null); +

+ +
+ + + + + diff --git a/src/favicon.ico b/src/favicon.ico new file mode 100644 index 0000000..f9b5f37 Binary files /dev/null and b/src/favicon.ico differ diff --git a/src/img/kitty.jpg b/src/img/kitty.jpg new file mode 100644 index 0000000..2c08404 Binary files /dev/null and b/src/img/kitty.jpg differ diff --git a/src/index.html b/src/index.html new file mode 100644 index 0000000..b8b170d --- /dev/null +++ b/src/index.html @@ -0,0 +1,45 @@ + + + + + WTerminal home + + + + + + + +
+ + Home + Static + Dropdown + Pong + Addon +
+ +
+

WTerminal home:

+
*  _  .  _  _____ .----..----. .-.   .-..-..-. .-.  .--.  .-.   
+* | |/ \| |[_   _]| {__ | {)  }| .`-'. ||~|| .`| | / {} \ | |   
+* |  ,-,  |  | |  | {__ | .-. \| |\ /| || || |\  |/  /\  \| `--.
+* '-'   `-'  '-'  `----'`-' `-'`-' ` `-'`-'`-' `-'`-'  `-'`----'
+For terminal fun on the web.
+

The following pages show multiple ways that this terminal can be used.

+

Pages:

+ Static terminal
+ Dropdown terminal
+ Ping pong game + dropdown terminal
+ Browser addon
+ +

Testing:

+ terminal testing
+
+ + + + + diff --git a/src/js/game-pong-context2d.js b/src/js/game-pong-context2d.js new file mode 100644 index 0000000..8ccc812 --- /dev/null +++ b/src/js/game-pong-context2d.js @@ -0,0 +1,471 @@ +/* About: Pingpong for HTML, you can insert it in anny Div tagged with an id + * This is a simple elegant sample for how to work with a Canvas + */ +class PingPong_CTX2D { + static get DEBUG_PARENT_OBJECT() { return true; }; + static get AUTO_CONTINUE_ON_FOCUS() { return false; }; + static get SHOW_FPS_INTERVAL() { return 1000 / 4; }; // four times per second + + static get STATE_COUNTDOWN() { return 0; }; + static get STATE_PLAYING() { return 1; }; + static get STATE_ENDED() { return 2; }; + static get BALL_SIZE() { return 8; }; + static get SPEED_HUMAN() { return 180; }; + static get SPEED_CPU() { return 210; }; + + static get PADDLE_WIDTH() { return 10; }; + static get PADDLE_HEIGHT() { return 60; }; + static get PADDLE_MARGIN() { return 10; }; + + static get KEY_ARROW_UP() { return 'ArrowUp'; }; + static get KEY_ARROW_DOWN() { return 'ArrowDown'; }; + static get KEY_W() { return 'w'; }; + static get KEY_S() { return 's'; }; + static get KEY_ENTER() { return 'Enter'; }; + static get KEY_SPACEBAR() { return ' '; }; + static get KEY_ESCAPE() { return 'Escape'; }; + + animationRequestId = 0; + running = false; + + constructor(divId, width, height, zoom = 1, showFps = false) { + this.createCanvas(divId, width, height, zoom); + this.canvasEl.title = "Playing: PingPong"; + this.ctx = this.canvasEl.getContext("2d"); + this.ctx.textAlign = "center"; + + this.audioCtx = new AudioContext(); + this.sounds = []; + this.sounds[0] = new Audio('./snd/glass-knock.mp3'); + this.audioCtx.createMediaElementSource(this.sounds[0]).connect(this.audioCtx.destination); + this.sounds[1] = new Audio('./snd/short-success.mp3'); + this.audioCtx.createMediaElementSource(this.sounds[1]).connect(this.audioCtx.destination); + + this.showFps = showFps; + if (showFps) { + // add framecounter and fps variables and html + this.frameCounter = 0; + this.initTime = performance.now(); + const el = document.createElement('p'); + this.frameLabel = document.createElement('span'); + const floatRight = document.createElement('span'); + floatRight.style = "float: right;"; + this.fpsCounter = 0; + this.fpsLabel = document.createElement('span'); + el.appendChild(document.createTextNode('fps: ')); + el.appendChild(this.fpsLabel); + floatRight.appendChild(document.createTextNode('frame: ')); + floatRight.appendChild(this.frameLabel); + el.appendChild(floatRight); + this.divEl.appendChild(el); + } + + this.restartGame(); + this.canvasEl.addEventListener("keydown", (e) => this.onKeyDown(e)); + this.canvasEl.addEventListener("keyup", (e) => this.onKeyUp(e)); + this.canvasEl.addEventListener("blur", (e) => this.onBlur(e)); + this.canvasEl.addEventListener("focus", (e) => this.onFocus(e)); + + if (PingPong_CTX2D.DEBUG_PARENT_OBJECT) window.game = this; + if (typeof WTerminal === "function") { + WTerminal.terminalAddCommand("restartgame", (t) => this.terminalRestartGame(t)); + WTerminal.terminalAddCommand("printgame", (t) => t.printVar(this, "pong")); + WTerminal.printLn("new PingPong: @", divId, ' ', width, 'x', height, ':', zoom, ' showFps=', showFps); + } + + this.drawCanvas(); + } + + playSound(index) { + try { + const snd = this.sounds[index]; + snd.currentTime = 0; + snd.play(); + } catch (e) { + console.log(`Failed to play sound '${index}': ${e}}`) + } + } + + restartGame() { + this.scoreCpu = 0; + this.scoreHuman = 0; + this.newRound(); + } + + newRound() { + this.countDown = 3; // seconds; + this.gameState = PingPong_CTX2D.STATE_COUNTDOWN; + // randomize ball speed + let vx = 200 + Math.random() * 100; + let vy = -20 + Math.random() * 20; + if (Math.random() > 0.5) vx = - vx; + if (Math.random() > 0.5) vy = - vy; + // generate objects + this.ball = new Ball(this.width / 2 - PingPong_CTX2D.BALL_SIZE / 2, this.height / 2 - PingPong_CTX2D.BALL_SIZE / 2, + PingPong_CTX2D.BALL_SIZE, PingPong_CTX2D.BALL_SIZE, vx, vy); + this.human = new Paddle(PingPong_CTX2D.PADDLE_MARGIN, this.height / 2 - PingPong_CTX2D.PADDLE_HEIGHT / 2, + PingPong_CTX2D.PADDLE_WIDTH, PingPong_CTX2D.PADDLE_HEIGHT); + this.cpu = new Paddle(this.width - PingPong_CTX2D.PADDLE_MARGIN - PingPong_CTX2D.PADDLE_WIDTH, this.height / 2 - PingPong_CTX2D.PADDLE_HEIGHT / 2, + PingPong_CTX2D.PADDLE_WIDTH, PingPong_CTX2D.PADDLE_HEIGHT); + + this.prevNow = performance.now(); + } + + createCanvas(divId, width = 0, height = 0, zoom = 1) { + this.divEl = document.getElementById(divId); + if (this.divEl === null) throw new Error("elementId not found: " + divId); + while (this.divEl.firstChild) { + this.divEl.removeChild(this.divEl.lastChild); + } + this.canvasEl = this.divEl.appendChild(document.createElement("canvas")); + const c = this.canvasEl; + this.width = width; + this.height = height; + if (width > 0 && height > 0) { + c.style.width = width * zoom + 'px'; + c.style.height = height * zoom + 'px'; + c.width = width; + c.height = height; + } + c.tabIndex = 0; // improtant for keyboard focus! + // c.style.imageRendering = 'pixelated'; + } + + drawCanvas() { + if (this.showFps) { + this.frameCounter++; + this.fpsCounter++; + + const now = performance.now(); + const diff = now - this.initTime; + if (PingPong_CTX2D.SHOW_FPS_INTERVAL < diff || !this.running) { + this.initTime = now; + const seconds = diff / 1000; + const fps = this.fpsCounter / seconds; + this.fpsCounter = 0; + if (this.frameLabel) this.frameLabel.innerHTML = this.frameCounter; + if (this.fpsLabel) { + this.fpsLabel.innerHTML = Math.round((fps + Number.EPSILON) * 100) / 100; + if (!this.running) this.fpsLabel.innerHTML += " (not running)"; + } + } + } + const ctx = this.ctx; + ctx.clearRect(0, 0, this.width, this.height); + //# print score + const FONT_SIZE = this.width > 440 ? 24 : 16; + ctx.font = FONT_SIZE + "px serif"; + ctx.fillStyle = 'red'; + ctx.fillText(this.scoreHuman + " - " + this.scoreCpu, this.width / 2, FONT_SIZE * 2); + //# print count down + if (this.gameState == PingPong_CTX2D.STATE_COUNTDOWN) { + ctx.font = 2 * FONT_SIZE + "px serif"; + ctx.fillText(Math.ceil(this.countDown), this.width / 2, this.height / 2); + } + //# shadow + ctx.save(); + ctx.shadowColor = '#000000bf'; + ctx.shadowBlur = 3; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + //# draw ball + ctx.strokeStyle = 'black'; + ctx.fillStyle = 'orange'; + ctx.globalAlpha = 0.3; + ctx.beginPath(); + ctx.arc(this.ball.prevX + PingPong_CTX2D.BALL_SIZE / 2, this.ball.prevY + PingPong_CTX2D.BALL_SIZE / 2, PingPong_CTX2D.BALL_SIZE / 2, 0, Math.PI * 2, true); + ctx.fill(); + ctx.stroke(); + ctx.globalAlpha = 1; + ctx.beginPath(); + ctx.arc(this.ball.x + PingPong_CTX2D.BALL_SIZE / 2, this.ball.y + PingPong_CTX2D.BALL_SIZE / 2, PingPong_CTX2D.BALL_SIZE / 2, 0, Math.PI * 2, true); + ctx.fill(); + ctx.stroke(); + //# draw players paddle + ctx.fillStyle = 'green'; + ctx.fillRect(this.human.x, this.human.y, this.human.width, this.human.height); + ctx.strokeRect(this.human.x, this.human.y, this.human.width, this.human.height); + ctx.fillStyle = 'blue'; + ctx.fillRect(this.cpu.x, this.cpu.y, this.cpu.width, this.cpu.height); + ctx.strokeRect(this.cpu.x, this.cpu.y, this.cpu.width, this.cpu.height); + ctx.restore(); //end shadow + // unfocused & paused banner + if (!this.running) { + const x = this.width / 2; + const y = this.height / 2; + const y2 = y - FONT_SIZE / 2; + ctx.strokeStyle = 'black'; + const g = ctx.createLinearGradient(0, 0, this.width, 0); + g.addColorStop(0, '#404040a0'); + g.addColorStop(0.2, '#404040df'); + g.addColorStop(0.8, '#404040df'); + g.addColorStop(1, '#404040a0'); + ctx.fillStyle = g; + ctx.fillRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2); + ctx.strokeRect(0, y2 - 2, this.width + 1, FONT_SIZE + 5 * 2); + ctx.fillStyle = (this.gameState === PingPong_CTX2D.STATE_ENDED) ? 'red' : 'gray'; + ctx.font = 4 * FONT_SIZE + "px serif"; + let text = (this.gameState === PingPong_CTX2D.STATE_ENDED) ? "Score!" : "Paused"; + ctx.fillText(text, x, y - 2 * FONT_SIZE); + ctx.strokeText(text, x, y - 2 * FONT_SIZE); + ctx.fillStyle = this.isFocused ? 'goldenrod' : 'lightgray'; + ctx.font = FONT_SIZE + "px serif"; + text = this.isFocused ? `Press space or enter to ${(this.gameState === PingPong_CTX2D.STATE_ENDED) ? "start next round" : "continue"}.` : "Click here to continue. (unfocused)"; + ctx.strokeText(text, x, y2 + FONT_SIZE); + ctx.fillText(text, x, y2 + FONT_SIZE); + } + } + + updateCanvas() { + const now = performance.now(); + const timeDelta = (now - this.prevNow) / 1000; //timeDelta = (milli - milli) / toSeconds + this.prevNow = now; + //#state switch + if (this.gameState == PingPong_CTX2D.STATE_COUNTDOWN) { + this.human.move(timeDelta); + this.human.borderTopAndBottom(this.height); + + this.cpu.move(timeDelta); + this.cpu.borderTopAndBottom(this.height); + + this.countDown -= timeDelta;//PONG_UPDATE_INTERVAL; + if (this.countDown < 0) { + this.gameState = PingPong_CTX2D.STATE_PLAYING; + } + } else if (this.gameState == PingPong_CTX2D.STATE_PLAYING) { + //cpu actions + if (this.ball.y + this.ball.height / 2 < this.cpu.y + this.cpu.height / 2) { + this.cpu.vy = -PingPong_CTX2D.SPEED_CPU; + } else if (this.ball.y > this.cpu.y + this.cpu.height / 2) { + this.cpu.vy = PingPong_CTX2D.SPEED_CPU; + } else { + this.cpu.vy = 0; + } + + //move ball and paddles + this.ball.move(timeDelta); + if (this.ball.borderTopAndBottom(this.height)) { + this.playSound(0); + } + //Horizontal + if (this.ball.x < 0) { + this.win(0); + this.playSound(1); + } else if (this.ball.x > this.width - this.ball.width) { + this.win(1); + this.playSound(1); + } + + this.human.move(timeDelta); + this.human.borderTopAndBottom(this.height); + + this.cpu.move(timeDelta); + this.cpu.borderTopAndBottom(this.height); + + // collide ball vs paddles + if (this.human.x + this.human.width < this.ball.prevX && this.human.x + this.human.width > this.ball.x) { + // console.log("pass 1.1"); + if (this.human.y < this.ball.y + this.ball.height && this.human.y + this.human.height > this.ball.y) { + // console.log("pass 1.2"); + this.ball.vx = this.ball.vx * -1.05; + this.ball.x = this.human.x + this.human.width; + this.ball.vy += ((this.ball.height / 2 + this.ball.y) - (this.human.height / 2 + this.human.y)) * 10; + this.playSound(0); + } + } + + if (this.cpu.x < this.ball.x + this.ball.width && this.cpu.x > this.ball.prevX + this.ball.width) { + // console.log("pass 2.1"); + if (this.cpu.y < this.ball.y + this.ball.height && this.cpu.y + this.cpu.height > this.ball.y) { + // console.log("pass 2.2"); + this.ball.vx = this.ball.vx * -1.05; + this.ball.x = this.cpu.x - this.ball.width; + let temp = ((this.ball.height / 2 + this.ball.y) - (this.cpu.height / 2 + this.cpu.y)) * 10; + this.ball.vy += temp; + this.playSound(0); + } + } + } + this.drawCanvas(); + if (this.running) { // loop + this.animationRequestId = requestAnimationFrame(() => this.updateCanvas()); + } else { // not looping + this.animationRequestId = 0; + } + } + + startRunning(fn) { + if (this.animationRequestId != 0) cancelAnimationFrame(this.animationRequestId); + this.running = true; + this.prevNow = performance.now(); + if (this.showFps) { + this.initTime = performance.now(); + } + this.animationRequestId = requestAnimationFrame(fn); + } + + close() { + if (this.animationRequestId != 0) { + cancelAnimationFrame(this.animationRequestId); + this.animationRequestId = 0; + } + this.running = false; + } + + pausePlayGame() { + if (this.gameState == PingPong_CTX2D.STATE_ENDED) return; + this.running = !this.running; + if (this.running) { + this.startRunning(() => this.updateCanvas()); + } + } + + terminalPrintGame(term) { + term.printVar(this, "pong"); + } + + terminalRestartGame(term) { + term.terminalClose(); + this.restartGame(); + // this.canvasEl.focus(); + setTimeout(() => { this.canvasEl.focus(); this.pausePlayGame(); }, 200); + } + + win(winner) { + this.running = false; //this.stopRunning(); + this.gameState = PingPong_CTX2D.STATE_ENDED; + if (winner == 0) { + this.scoreCpu++; + if (typeof WTerminal === "function") WTerminal.printLn("CPU scored?!"); + } else { + this.scoreHuman++; + if (typeof WTerminal === "function") WTerminal.printLn("Human scored!"); + } + if (typeof WTerminal === "function") WTerminal.printLn("Scores: Human " + this.scoreHuman + " - " + this.scoreCpu + " CPU"); + } + + onKeyDown(e) { + if (e.key == PingPong_CTX2D.KEY_ESCAPE) { + if (this.running) this.pausePlayGame() + e.preventDefault(); + return false; + } + if (e.key == PingPong_CTX2D.KEY_ARROW_UP || e.key == PingPong_CTX2D.KEY_W) { + this.human.vy = -PingPong_CTX2D.SPEED_HUMAN; + e.preventDefault(); + return false; + } else if (e.key == PingPong_CTX2D.KEY_ARROW_DOWN || e.key == PingPong_CTX2D.KEY_S) { + this.human.vy = PingPong_CTX2D.SPEED_HUMAN; + e.preventDefault(); + return false; + } else if (e.key == PingPong_CTX2D.KEY_ENTER || e.key == PingPong_CTX2D.KEY_SPACEBAR) { + //# next round/pause/play + if (this.gameState == PingPong_CTX2D.STATE_ENDED) { + this.newRound(); + this.startRunning(() => this.updateCanvas()); + } else { + this.pausePlayGame(); + } + e.preventDefault(); + return false; + } + return true; + } + + onKeyUp(e) { + if (e.key == PingPong_CTX2D.KEY_ARROW_UP || e.key == PingPong_CTX2D.KEY_W) { + this.human.vy = 0; + e.preventDefault(); + return false; + } else if (e.key == PingPong_CTX2D.KEY_ARROW_DOWN || e.key == PingPong_CTX2D.KEY_S) { + this.human.vy = 0; + e.preventDefault(); + return false; + } + return true; + } + + onBlur() { + this.isFocused = false; + this.canvasEl.style.borderColor = null; + if (this.running) { + this.pausePlayGame(); + } else { + this.drawCanvas(); + } + } + + onFocus() { + this.isFocused = true; + this.canvasEl.style.borderColor = "red"; + if (!this.running && PingPong_CTX2D.AUTO_CONTINUE_ON_FOCUS) { + this.pausePlayGame(); + } else { + this.drawCanvas(); + } + } +} + +class Rectangle { + constructor(x, y, width, height) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +class Ball extends Rectangle { + constructor(x, y, width, height, vx, vy) { + super(x, y, width, height); + this.vx = vx; + this.vy = vy; + this.prevX = this.x; + this.prevY = this.y; + } + + move(timeDelta) { + this.prevX = this.x; + this.prevY = this.y; + this.x += this.vx * timeDelta; + this.y += this.vy * timeDelta; + } + + borderTopAndBottom(height) { + if (this.y < 0) { + this.y = 0; + this.vy = -this.vy; + return true; + } else if (this.y > height - this.height) { + this.y = height - this.height; + this.vy = - this.vy; + return true; + } + return false; + } +} + +class Paddle extends Rectangle { + constructor(x, y, width = 20, height = 20) { + super(x, y, width, height); + this.vy = 0; + } + + move(timeDelta) { + this.y += this.vy * timeDelta; + } + + borderTopAndBottom(height) { + if (this.y < 0) { + this.y = 0; + this.vy = 0; + } else if (this.y > height - this.height) { + this.y = height - this.height; + this.vy = 0; + } + } +} + +function startPingPong(divId, width = 480, height = 320, zoom = 1, showFps = true) { + return new PingPong_CTX2D(divId, width, height, zoom, showFps); +} diff --git a/src/js/static-terminal-buttons.js b/src/js/static-terminal-buttons.js new file mode 100644 index 0000000..3423943 --- /dev/null +++ b/src/js/static-terminal-buttons.js @@ -0,0 +1,55 @@ +const TERMINAL_NAME = "static"; + +window.addEventListener("load", + ()=>getWTerminal(TERMINAL_NAME).inputTextEl.focus()); + +function btn_terminalHelp() { + let term = getWTerminal(TERMINAL_NAME); + term.terminalCommand("help"); + term.inputTextEl.focus(); +} + +function btn_promptSample() { + let term = getWTerminal(TERMINAL_NAME); + term.printTitle("Prompt sample:"); + let age = prompt("Enter your age:", "404"); + term.printLn("Age-input: " + age); + let days = age * 365; + term.printLn(`Days alive: ${days}`); + let weeks = Math.round(days / 7); + term.printLn(`Weeks alive: ${weeks}`); + let adult = age >= 18; + term.printLn("Type: " + (adult ? "adult" : "minor")); + term.inputTextEl.focus(); +} + +function btn_kittySample() { + let term = getWTerminal(TERMINAL_NAME); + term.printTitle("Kitty sample:"); + term.printError("Error: Kitties are cute!"); + // term.printLn('Hello kitty!'); + let img = document.createElement("img"); + img.src = "img/kitty.jpg"; + term.printLn(img, "Hello kitty!"); + term.inputTextEl.focus(); +} + +function btn_printObjectSample() { + let term = getWTerminal(TERMINAL_NAME); + term.printTitle("print Object sample 1:"); + term.printVar("hello"); + term.printVar(3.1419); + term.printTitle("print Object sample 2:"); + let myObject = { hello: "World", number:100}; + myObject.InnerObject = { foo: "bar", myFunction: btn_printObjectSample} + myObject.array = [1,2,4,8]; + term.printVar(myObject, "sample"); + term.inputTextEl.focus(); +} + +function btn_terminalClear(){ + let term = getWTerminal(TERMINAL_NAME); + term.clearOutput(); + term.inputTextEl.focus(); +} + diff --git a/src/js/termext/ext-cookies.js b/src/js/termext/ext-cookies.js new file mode 100644 index 0000000..7fbf648 --- /dev/null +++ b/src/js/termext/ext-cookies.js @@ -0,0 +1,103 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: Cookie data managing +*/ + +if (WTerminal) { + const initTerminalCookieCommands = function() { + //getcookie + WTerminal.terminalAddCommand("getcookie", (term, argLine) => { + function getCookie(name, defaultValue = undefined) { + if (name === null || name === undefined || typeof name != "string" || name == '') { + console.log('error: cookie needs a name'); + return; + } + let pair = document.cookie.split(/; */).find((row) => row.startsWith(name + '=')) + //console.log("cookie pair: ", pair); + if (pair === undefined) + return defaultValue; + else + return pair.split('=')[1]; + } + return getCookie(...WTerminal.splitToArguments(argLine)); + }); + //setcookie + WTerminal.terminalAddCommand("setcookie", (term, argLine) => { + function setCookie(name, value = 1, days = 7) { + let date = new Date(); + date.setDate(date.getDate() + days); // add x days to date + document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; SameSite=strict; Secure"; + } + return setCookie(...WTerminal.splitToArguments(argLine)); + }); + //removecookies + WTerminal.terminalAddCommand("removecookies", function(term) { + function removeCookie(name) { + document.cookie = name + "=0; max-age=-1" + "; SameSite=strict; Secure"; + } + if (document.cookie == '') { + term.printError("No cookies found."); + return; + } + const cookies = document.cookie.split(/; */); + term.printLn("Cookies found: " + cookies.length); + cookies.forEach(c => { + const name = c.split('=')[0]; + term.printLn("removing: " + name); + removeCookie(name); + }); + }); + //cookies + WTerminal.terminalAddCommand("cookies", + function(term, argLine) { + if (document.cookie === '') { + if (!argLine.includes("-s")) term.printError("No cookies found.") + } else { + term.printList(document.cookie.split(/; */), false); + } + }, + function(term) { + term.printLn("Usage:"); + term.printLn(" cookies //Prints all cookies."); + term.printLn(" cookies -s //(silent)Prints only cookies, no error."); + }); + //doCookiesWork + WTerminal.terminalAddCommand("docookieswork", (term) => { + function getCookie(name, defaultValue = undefined) { + if (name === null || name === undefined || typeof name != "string" || name == '') { + console.log('error: cookie needs a name'); + return; + } + let pair = document.cookie.split(/; */).find((row) => row.startsWith(name + '=')) + //console.log("cookie pair: ", pair); + if (pair === undefined) + return defaultValue; + else + return pair.split('=')[1]; + } + function setCookie(name, value = 1, days = 7) { + let date = new Date(); + date.setDate(date.getDate() + days); // add x days to date + document.cookie = name + "=" + value + "; expires=" + date.toUTCString() + "; SameSite=strict; Secure"; + } + function removeCookie(name) { + document.cookie = name + "=0; max-age=-1" + "; SameSite=strict; Secure"; + } + const name = "testCookie"; + setCookie(name); + let itWorks = getCookie(name) !== undefined; + if (itWorks) { + removeCookie(name); + } + term.printLn(itWorks); + return itWorks; + }); + } + + //init + if (document.body) { + initTerminalCookieCommands(); + } else { + window.addEventListener("load", initTerminalCookieCommands); + } +} diff --git a/src/js/termext/ext-eval.js b/src/js/termext/ext-eval.js new file mode 100644 index 0000000..50dea3a --- /dev/null +++ b/src/js/termext/ext-eval.js @@ -0,0 +1,32 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds the eval command to the terminal. +*/ +{ + const evalHelp = function(term) { + term.printLn("Uses the function eval(string) on the argLine"); + } + const evalRun = function(term, argLine) { + try { + const result = eval(argLine); + term.printVar(result, '`' + argLine + '`'); + return result; + } catch (error) { + term.printError(`Eval error: \`${argLine}\` -> ${error.message}`); + } + }; + + const initTerminalEvalCommand = function() { + if (WTerminal === undefined) { //is WTerminal not available? + console.error("WTerminal is missing!"); + return; + } + WTerminal.terminalAddCommand("eval", evalRun, evalHelp); + }; + //init + if (document.body) { + initTerminalEvalCommand(); + } else { + window.addEventListener("load", initTerminalEvalCommand); + } +} diff --git a/src/js/termext/ext-featuretest.js b/src/js/termext/ext-featuretest.js new file mode 100644 index 0000000..dc4eb79 --- /dev/null +++ b/src/js/termext/ext-featuretest.js @@ -0,0 +1,42 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds the test command to the terminal. +* current test is to get local variables +*/ +{ + const help = function(term) { + term.printLn("Runs a test (nothing)."); + } + const run = function(term) { + term.printLn("Feature test warning: Under construction, can have unexpected results, errors and crashes."); + // todo: add test ... like throw errors and stuff + + // throw {name : "NotImplementedError", message : "too lazy to implement"}; + // throw new Error("too lazy to implement", "some name perhaps?"); + // class TerminalError extends Error{ + // constructor(msg, name="TerminalError"){ + // super(msg); + // this.name = name; + // } + // } + // throw new TerminalError("my message", "MyName"); + let num = 1; + num.toPrecision(500); + }; + + const addExtention = function() { + if (WTerminal === undefined) { //is WTerminal not available? + console.error("AddExtention Error: WTerminal is missing!"); + return; + } + WTerminal.terminalAddCommand("featuretest", run, help); + //add alias + WTerminal.terminalAddAlias("ft", "featuretest"); + }; + //init + if (document.body) { + addExtention(); + } else { + window.addEventListener("load", addExtention); + } +} diff --git a/src/js/termext/ext-passw.js b/src/js/termext/ext-passw.js new file mode 100644 index 0000000..0a6e563 --- /dev/null +++ b/src/js/termext/ext-passw.js @@ -0,0 +1,96 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds a password generatiing- and testing function +*/ +{ + + const generatePasswordHelp = function(term) { + term.printLn("Generates a new password and prints it out. But also copies it for you."); + } + const generatePassword = function(term, argLine) { + const UPPERCASE_LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const LOWERCASE_LETTERS = "abcdefghijklmnopqrstuvwxyz"; + const NUMBERS = "1234567890"; + const SPECIAL_CHARACTERS = "!@#$%^&*()_-+=,./<>\\|[]{}"; + const ALL_CHARACTERS = UPPERCASE_LETTERS + LOWERCASE_LETTERS + NUMBERS + SPECIAL_CHARACTERS; + let passwd = ""; + let length = 15; + for (let i = 0; i < length; i++) { + passwd += ALL_CHARACTERS.charAt(Math.floor(Math.random() * ALL_CHARACTERS.length)); + } + const el = document.createElement('span'); + el.innerText = passwd; + // el.style.fontSize = 'x-large'; + el.style.paddingLeft = '1em'; + term.printLn("Generated password(length=" + length + "):", el); + navigator.clipboard.writeText(passwd); + }; + + const isPasswordSafeHelp = function(term) { + term.printLn("Checks if a password is posted as breached by pwnedpasswords.com"); + }; + const isPasswordSafe = function(term, argLine) { + const sha1 = function(string) { + const buffer = new TextEncoder("utf-8").encode(string); + return crypto.subtle.digest("SHA-1", buffer).then(function(buffer) { + // Get the hex code + let hexCodes = []; + const padding = '00000000' + const view = new DataView(buffer); + for (let i = 0; i < view.byteLength; i += 4) { + // Using getUint32 reduces the number of iterations needed (we process 4 bytes each time) + const value = view.getUint32(i) + // toString(16) will give the hex representation of the number without padding + const stringValue = value.toString(16) + // We use concatenation and slice for padding + const paddedValue = (padding + stringValue).slice(-padding.length) + hexCodes.push(paddedValue); + } + // Join all the hex strings into one + return hexCodes.join(""); + }); + }; + const password = argLine; + term.printLn("Checking password, please wait..."); + + sha1(password).then((hash) => { + fetch("https://api.pwnedpasswords.com/range/" + hash.substr(0, 5)).then((response) => response.text()).then((response) => { + + const respLines = response.split('\n'); + const hashSub = hash.slice(5).toUpperCase(); + let isSafe = true; + // console.log('hash: ' + hashSub); + for (let i in respLines) { + // console.log(i+': '+respLines[i]) + if (respLines[i].substring(0, hashSub.length) == hashSub) { + isSafe = false; + break; + } + } + if (isSafe) { + term.printLn("Password is safe."); + } else { + term.printLn("Password has been breached."); + } + }).catch((err) => { + outputSafe.innerText = "Could not check if password is safe: " + err; + }); + }); + }; + + const initTerminalPasswCommands = function() { + if (WTerminal === undefined) { //is WTerminal not available? + console.error("WTerminal is missing!"); + return; + } + + WTerminal.terminalAddCommand("generatepassword", generatePassword, generatePasswordHelp); + WTerminal.terminalAddCommand("ispasswordsafe", isPasswordSafe, isPasswordSafeHelp); + }; + //init + if (document.body) { + initTerminalPasswCommands(); + } else { + window.addEventListener("load", initTerminalPasswCommands); + } +} diff --git a/src/js/termext/ext-popvar.js b/src/js/termext/ext-popvar.js new file mode 100644 index 0000000..421e141 --- /dev/null +++ b/src/js/termext/ext-popvar.js @@ -0,0 +1,164 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds the test command to the terminal. +* current test is to get local variables +*/ + +{// this code block hides the variables below from other scripts. + const runPopVar = function(term, argLine) { + const PAUSE_SYMBOL = "◼"; + const INTERVAL_TIME = "1000"; // 1000 == 1second + + class PopUpWindow { + static popupCounter = 0; + constructor(variableName, term) { + this.variableName = variableName; + this.printVar = term.printVar; + this._getObjType = term._getObjType; + this._printObject = term._printObject; + this.printLn = term.printLn; + this.options = {}; + let o = this.options; + let to = term.options; + o.printToConsoleLog = false; + o.tpo_unknownObjectPrint = to.tpo_unknownObjectPrint; + o.tpo_objectPrefix = to.tpo_objectPrefix; + o.tpo_specialPrefix = to.tpo_specialPrefix; + o.tpo_maxDepth = to.tpo_maxDepth; + o.tpo_innerMaxLength = to.tpo_innerMaxLength; + this.createPopup(); + this.printVariable(); + this.intervalId = setInterval(() => this.printVariable(), INTERVAL_TIME); + } + + createPopup() { + const t = 2 + PopUpWindow.popupCounter++; + let containerStyle = 'border: 1px solid black; z-index: 9990; position: absolute; background: #ffffffa0; border-radius: 2px; backdrop-filter: blur(3px); box-shadow: 3px 3px 3px #00000066;'; + containerStyle += ` top: ${t}em; left: ${t}em;`; + this.container = WTerminal.createElement('div', { style: containerStyle, title: this.variableName }); + const outputStyle = 'margin: 2px; font-family: Monospace, Incosolata, Courier; font-size: 12px; line-height: 1.05;';// overflow-y: scroll; max-height: ' + (window.innerHeight-80) +'px;'; + this.outputEl = WTerminal.createElement('pre', { style: outputStyle }); + this.btnPauseContinue = WTerminal.createElement('button', { title: "pause" }); + this.btnPauseContinue.innerHTML = PAUSE_SYMBOL; + this.btnPauseContinue.addEventListener("click", () => this.onPausePlay()); + let btnRefresh = WTerminal.createElement('button', { title: "refresh" }); + btnRefresh.innerHTML = '↻'; + btnRefresh.addEventListener("click", () => this.printVariable()); + let btnClose = WTerminal.createElement('button', { title: 'close' }); + btnClose.addEventListener("click", () => this.closePopup()); + btnClose.innerHTML = "✖"; + let headerDiv = WTerminal.createElement('div', { style: "border-bottom: 1px solid black; padding: 2px; background: #00000060" }); + headerDiv.appendChild(btnRefresh); + headerDiv.appendChild(this.btnPauseContinue); + headerDiv.appendChild(document.createTextNode(" Popvar " + PopUpWindow.popupCounter)); + let spanForClose = WTerminal.createElement('span', { style: "float: right;" }, btnClose); + headerDiv.appendChild(spanForClose); + this.container.appendChild(headerDiv); + this.container.appendChild(this.outputEl); + document.body.appendChild(this.container); + + headerDiv.onmousedown = (e) => this.startDrag(e); + } + + onPausePlay() { + if (this.intervalId === 0) { + this.intervalId = setInterval(() => this.printVariable(), INTERVAL_TIME); + this.printVariable(); + this.btnPauseContinue.innerHTML = PAUSE_SYMBOL; + this.btnPauseContinue.title = "pause"; + } else { + clearInterval(this.intervalId); + this.intervalId = 0; + this.btnPauseContinue.innerHTML = "►"; + this.btnPauseContinue.title = "play"; + } + } + + printVariable() { + const oldOutput = this.outputEl; + const outputStyle = 'margin: 2px; font-family: Monospace, Incosolata, Courier; font-size: 12px; line-height: 1.05;';// overflow-y: scroll; max-height: ' + (window.innerHeight-80) +'px;'; + this.outputEl = WTerminal.createElement('pre', { style: outputStyle }); + this.printVar(WTerminal.getGlobalVariable(this.variableName), this.variableName); + oldOutput.replaceWith(this.outputEl); + } + + closePopup() { + document.body.removeChild(this.container); + } + + startDrag(e) { + if (e.button !== 0) return; + e.preventDefault(); + this.pos3 = e.clientX; + this.pos4 = e.clientY; + document.onmouseup = () => this.endDrag(); + document.onmousemove = (e) => this.dragPopup(e); + } + + dragPopup(e) { + // e = e || window.event; + e.preventDefault(); + this.pos1 = this.pos3 - e.clientX; + this.pos2 = this.pos4 - e.clientY; + this.pos3 = e.clientX; + this.pos4 = e.clientY; + this.container.style.top = (this.container.offsetTop - this.pos2) + "px"; + this.container.style.left = (this.container.offsetLeft - this.pos1) + "px"; + } + + endDrag() { + document.onmouseup = null; + document.onmousemove = null; + } + };//class PopUpWindow + + if (globalThis === undefined) { + term.printError("Popvar error: Missing globalThis"); + } else if (argLine == '') { + term.printError("Popvar error: No name"); + } else { + // return new PopUpWindow(argLine, term); + new PopUpWindow(argLine, term); + } + }// runPopvar + + const initTerminalVariableCommands = function() { + const ext = { + popvar: { + run: runPopVar, + help: function(term) { + term.printLn("Creates a popup window with (refreshable) the given variable contents."); + term.printBold("Usage:"); + term.printLn("popvar VARIABLE_NAME //shows VARIABLE_NAME contents in a popup"); + term.printBold("Samples:"); + term.printLn("popvar terminal //Shows the terminal variable contents in a popup"); + } + }, + }; + + const aliases = { + pop: "popvar", + pt: "popvar terminal", + }; + + if (WTerminal === undefined) { //is WTerminal not available? + console.error("WTerminal is missing!"); + return; + } + //add commands in ext + for (let c of Object.keys(ext)) { + WTerminal.terminalAddCommand(c, ext[c].run, ext[c].help); + } + //add aliases + for (let c of Object.keys(aliases)) { + WTerminal.terminalAddAlias(c, aliases[c]); + } + }; + + //init + if (document.body) { + initTerminalVariableCommands(); + } else { + window.addEventListener("load", initTerminalVariableCommands); + } +} diff --git a/src/js/termext/ext-stresstest.js b/src/js/termext/ext-stresstest.js new file mode 100644 index 0000000..771b4b4 --- /dev/null +++ b/src/js/termext/ext-stresstest.js @@ -0,0 +1,40 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds the test command to the terminal. +* current test is to get local variables +*/ + +{// this code block hides the variables below from other scripts. + const initTerminalStressTestCommand = function() { + const help = function(term) { + term.printLn("Runs a stress test on the terminal."); + }; + const run = function(term, argLine) { + const testArgLine = function(str) { + term.printVar(WTerminal.splitToArguments(str), '`' + argLine + '`.splitToArguments()'); + }; + if (argLine != '') testArgLine(argLine); + term.terminalCommand("? && alias && const && option && date && time && uptime && starttime && echo OK", true); + term.terminalCommand("? help && help alias && ? const && ? option && ? date && ? time && ? uptime", true); + term.terminalCommand("thisterminal && gg", true); + term.terminalCommand('gdb && result .2 && result .children.0 && result .innerHTML', true); + term.terminalCommand('eval 10000+4*(1+4*10) + Math.PI', true); + term.terminalCommand('dovar alert "finished stresstest"', true); + // term.terminalCommand('dovar document.getElementById ' + TERMINAL_OUTPUT_ID + ' && result .innerHTML', true); + + return term.lastResult; + }; + //add command + if (WTerminal === undefined) { + console.error("WTerminal is missing!"); + return; + } + WTerminal.terminalAddCommand("stresstest", run, help); + }; + //init + if (document.body) { + initTerminalStressTestCommand(); + } else { + window.addEventListener("load", initTerminalStressTestCommand); + } +} diff --git a/src/js/termext/ext-timerdebug.js b/src/js/termext/ext-timerdebug.js new file mode 100644 index 0000000..7440d40 --- /dev/null +++ b/src/js/termext/ext-timerdebug.js @@ -0,0 +1,45 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds the test command to the terminal. +* current test is to get local variables +*/ +{ + const addExtention = function() { + const help = function(term) { + term.printLn("Installs a logger at setTimeout and setInterval."); + } + const run = function(term) { + //capture setTimeout + const oldSetTimeout = setTimeout; + setTimeout = function() { + const e = new Error('Just for stack trace'); + const result = oldSetTimeout.apply(this, arguments); + if (term) term.printLn(`New timeout ${result} registered.\n from: ${e.stack}`); + else console.log(`New timeout ${result} registered from: ${e.stack}`); + return result; + }; + //capture setInterval + const oldSetInterval = setInterval; + setInterval = function() { + const e = new Error('Just for stack trace'); + const result = oldSetInterval.apply(this, arguments); + if (term) term.printLn(`New interval ${result} registered.\n from: ${e.stack}`); + else console.log(`New interval ${result} registered from: ${e.stack}`); + return result; + } + term.printLn("activated"); + } + + if (WTerminal === undefined) { //is WTerminal not available? + console.error("WTerminal is missing!"); + return; + } + WTerminal.terminalAddCommand("timerdebug", run, help); + }; + //init + if (document.body) { + addExtention(); + } else { + window.addEventListener("load", addExtention); + } +} diff --git a/src/js/termext/ext-variables.js b/src/js/termext/ext-variables.js new file mode 100644 index 0000000..22225ed --- /dev/null +++ b/src/js/termext/ext-variables.js @@ -0,0 +1,348 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This adds the test command to the terminal. +* current test is to get local variables +*/ + +{// this code block hides the variables below from other scripts. + const initTerminalVariableCommands = function() { + const ext = + { + dovar: { + run: function(term, argLine) { + function _doFunction(gVar, functionKey, args) { + if (typeof gVar === "object" && typeof gVar[functionKey] === "function") { + // term.printVar(args, "do args"); + return gVar[functionKey](...args); + } else { + term.printError("Do error: " + functionKey + " is not a function"); + } + }; + /* To run a function of a Class (like document.getElementById(..)) + * We need the parent- /class-object from witch we call it from, to prevent errors (illegal acces) + * todo: ? --> use apply https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply + */ + if (globalThis === undefined) { + term.printError("Do error: Missing globalThis"); + } else if (argLine == '') { + term.printError("Do error: No name"); + } else { + let args = WTerminal.splitToArguments(argLine); + const verbal = args.includes("-v"); + if (verbal) { + args = args.filter(e => e !== '-v'); + } + const gName = args.shift(); //full global function name + args.forEach(e => e = WTerminal.stringToValue(e)); + let pName = gName.split('.'); //last object's parent name + let result; + if (pName.length == 1) { + result = _doFunction(globalThis, gName, args) + } else { + const lastName = pName.pop(); //last object name + result = _doFunction(WTerminal.getGlobalVariable(pName.join('.')), lastName, args); + } + if (verbal) term.printVar(result, gName + "(" + args.join(' ') + ")"); + return result; + } + }, + help: function(term) { + term.printLn("Runs given global function with arguments, and returns result."); + term.printBold("Usage:"); + term.printLn("dovar FUNCTION_NAME [ArgumentS] //Runs function FUNCTION_NAME with optional ArgumentS"); + term.printLn("dovar -v FUNCTION_NAME [ArgumentS] //Same as above but prints result too."); + term.printBold("Samples:"); + term.printLn("dovar window.open https://developer.mozilla.org //Runs function window.open() with the url as argument"); + term.printLn("dovar -v document.getElementById terminal-up //Runs function document.getElementById with terminal-up as argument") + term.printLn("dovar document.body.remove //Warning: removes all page content"); + } + }, + getvar: { + run: function(term, argLine) { + if (globalThis === undefined) { + term.printError("Getvar error: Missing globalThis"); + } else if (argLine == '') { + term.printError("Getvar error: Missing argument: VARIABLE_NAME"); + } else { + let result = {}; + const args = WTerminal.splitToArguments(argLine); + for (const gName of args) { + result[gName] = WTerminal.getGlobalVariable(gName); + } + const keys = Object.keys(result); + if (keys.length == 1) { + return result[keys[0]]; + } else { + return result; + } + } + }, + help: function(term) { + term.printLn("Gets a global variable and returns it."); + term.printLn("The returned result will be found in terminal.lastResult") + term.printBold("Usage:"); + term.printLn("getvar VARIABLE_NAME"); + term.printBold("Samples:"); + term.printLn("getvar terminal //Returns terminal"); + term.printLn("getvar terminal.version //Returns terminal.version"); + term.printLn("getvar var1 var2 var3 //Returns an object with var1 var2 and var3 keys and their global found value."); + term.printLn("getvar document //Returns global document object."); + term.printLn("getvar document.body.children //Returns a list of the children in the body."); + term.printLn("getvar document.head.children //Returns a list of the children in the head."); + } + }, + globals: { + run: function(term) { + if (globalThis !== undefined) { + term.printVar(globalThis, "globalThis"); + } else if (self !== undefined) { + term.printVar(self, "self"); + } else if (global !== undefined) { + term.printVar(global, "global"); + } else { + term.printError("Globals error: No globals!?"); + } + if (document !== undefined && globalThis.document !== document) term.printVar(document, "document"); + }, + help: function(term) { + term.printLn("Prints all global variables."); + } + }, + logvar: { + run: function(term, argLine) { + if (globalThis === undefined) { + term.printError("LogVar error: Missing globalThis"); + } else if (argLine == '') { + term.printError("LogVar error: Missing argument: VARIABLE_NAME"); + } else { + let result = {}; + let args = WTerminal.splitToArguments(argLine); + const returnResult = args.includes("-r"); + if (returnResult) { + args = args.filter(e => e !== '-r'); + } + for (const gName of args) { + result[gName] = WTerminal.getGlobalVariable(gName); + } + if (Object.keys(result).length == 1) { + result = result[Object.keys(result)[0]]; + } + // term.printVar(result); + console.log(result); + if (returnResult) return result; + } + }, + help: function(term) { + term.printLn("Like printvar but Logs variable(s) to console."); + } + }, + printvar: { + run: function(term, argLine) { + if (globalThis === undefined) { + term.printError("PrintVar error: Missing globalThis"); + } else if (argLine == '') { + term.printError("PrintVar error: Missing argument: VARIABLE_NAME"); + } else { + let result = {}; + let args = WTerminal.splitToArguments(argLine); + const returnResult = args.includes("-r"); + if (returnResult) { + args = args.filter(e => e !== '-r'); + } + for (const gName of args) { + result[gName] = WTerminal.getGlobalVariable(gName); + } + const keys = Object.keys(result); + if (keys.length == 1) { + result = result[keys[0]]; + term.printVar(result, keys[0]); + } else { + term.printVar(result); + } + if (returnResult) return result; + } + }, + help: function(term) { + term.printLn("Prints value of global variable."); + term.printBold("Usage:"); + term.printLn("printvar VARIABLE_NAME //Prints variable, returns nothing."); + term.printLn("printvar -r VARIABLE_NAME //Prints variable, returns variable."); + term.printBold("Samples:"); + term.printLn("printvar terminal //Prints terminal"); + term.printLn("printvar terminal.version //Prints terminal.version"); + term.printLn("printvar -r terminal.lastResult //Prints terminal.lastResult and returns it."); + term.printLn("printvar document //Prints global document object."); + term.printLn("printvar document.body.children //Prints body elements."); + term.printLn("printvar document.head.children //Prints head elements."); + } + }, + removevar: { + run: function(term, argLine) { + if (globalThis === undefined) { + term.printError("RemoveVar error: Missing globalThis"); + } else if (argLine == '') { + term.printError("RemoveVar error: Missing arguments"); + } else { + let args = WTerminal.splitToArguments(argLine); + const keys = Object.keys(globalThis); + const verbal = args.includes("-v"); + if (verbal) { + args = args.filter(e => e !== '-v'); + } + let count = 0; + if (args.includes("-a")) { + count = keys.length; + keys.forEach(key => { + if (verbal) term.printLn("removing: " + key) + delete globalThis[key]; + }); + term.printLn("Removed " + count + " keys.") + return; + } else if (args.includes("-n")) { + args = args.filter(e => e !== '-n'); + keys.forEach(key => { + if (globalThis[key] === null || globalThis[key] === undefined) { + if (verbal) term.printLn("removing: " + key) + delete globalThis[key]; + count++; + } + }); + } else if (args.includes("-f")) { + args = args.filter(e => e !== '-f'); + keys.forEach(key => { + if (globalThis[key] !== globalThis && + !(typeof globalThis[key] === "function" && globalThis[key].toString().includes("[native code]"))) { + if (verbal) term.printLn("removing: " + key) + delete globalThis[key]; + count++; + } + }); + } + while (args.length > 0) { + const gName = args.shift(); //full global function name + const pName = gName.split('.'); //last object's parent name + const lastName = pName.pop(); //last object name + let obj = globalThis; + if (pName.length > 0) { + obj = WTerminal.getGlobalVariable(pName.join('.')); //parent object + } + if (obj !== undefined && obj[lastName] !== undefined) { + if (verbal) term.printLn("removing: " + gName) + delete obj[lastName]; + count++; + } else { + if (obj === undefined) { + term.printError("Variable " + pName.join(".") + " = undefined") + } else { + term.printError("Variable " + gName + " = undefined"); + } + } + } + term.printLn("Removed " + (count > 1 ? (count + " keys.") : (count === 0 ? "nothing" : "1 key"))); + } + }, + help: function(term) { + term.printLn("Removes global variables."); + term.printBold("Usage:"); + term.printLn("removevar -v ... //Prints removing VARIABLE_NAME."); + term.printLn("removevar -f //Removes all global variables that are not a parent redefinition or native function."); + term.printLn("removevar -n //Removes all null or undefined global variables."); + term.printLn("removevar -a //Removes ALL global variables."); + term.printLn("removevar VARIABLE_NAMES //Removes the global variables provided.") + term.printBold("Samples:"); + term.printLn("removevar terminal //Removes the global variable terminal."); + term.printLn("removevar var1 var2 //Removes the 2 global variables var1 and var2."); + term.printLn("removevar terminal.history //Removes history from terminal."); + term.printLn("removevar -n terminal.history //Removes history from terminal, and all null or undefined global variables."); + term.printLn("removevar -n -v //Prints variable names of all null or undefined global variables it removes."); + } + }, + setvar: { + run: function(term, argLine) { + if (globalThis === undefined) { + term.printError("Setvar error: Missing globalThis"); + } else { + if (argLine == '') { + term.printError("Setvar error: no name"); + } else { + let args = WTerminal.splitToArguments(argLine); + for (const element of args) { + const keyValuePair = element.split("="); + if(keyValuePair.length < 2) throw new Error("missing arguements."); + const names = keyValuePair[0].split("."); + const value = WTerminal.stringToValue(keyValuePair[1]); + if (names.length == 1) { + globalThis[names[0]] = value; + term.printVar(globalThis[names[0]], keyValuePair[0]); + } else { + let obj = globalThis; + for (let i = 0; i < names.length - 1; i++) { + if (typeof obj[names[i]] === "object") obj = obj[names[i]]; + else { + let rem = names.length - 1 - i; + names.splice(names.length - rem, rem); + let name = names.join('.'); + term.printError(`Variable ${keyValuePair[0]} is not an object!`); + term.printVar(obj[names[i]], name); + return; + }; + } + obj[names[names.length - 1]] = value; + return obj[names[names.length - 1]]; + } + } + } + } + }, + help: function(term) { + term.printBold("Usage:"); + term.printLn("setvar NAME=VALUE //Sets VALUE to a global variable, NAME, and returns it."); + term.printBold("Samples:"); + term.printLn("setvar terminal //Sets terminal to undefined"); + term.printLn("setvar terminal.version=prehistoric //Sets terminal.version to string `prehistoric`"); + term.printLn("setvar myNumber=8 //Sets myNumber to number 8"); + term.printLn("setvar myNumber=(number)-8.1 //Sets myNumber to number -8.1"); + term.printLn("setvar var1 var2 var3 //Creates 3 undefined variables"); + term.printLn("setvar myName=\"Ward Truyen\" //Sets myName to string"); + term.printLn('setvar obj={"1":"test","2":true} //Sets obj to JSON.stringified object'); + term.printLn("setvar myFunc=(function)terminalPrintLn(\"Hello\");//Creates a function"); + term.printLn("setvar myVar=(global)myFunc //Sets(binds) myVar to the contents of myFunc"); + } + }, + }; + const aliases = { + run: "dovar", + pdo: "dovar -v ", + g: "getvar", + gg: "globals", + db: "printvar -r document.body.children", + dh: "printvar -r document.head.children", + gdb: "printvar -r document.body.children ", + gdh: "printvar -r document.head.children ", + result: "printvar -r terminal.lastResult", + error: "printvar terminal.lastError", + pv: "printvar -r ", + terminal: "printvar -r terminal", + // history: "printvar terminal.history", + }; + + //add commands in ext + if (WTerminal === undefined) { + console.error("WTerminal is missing!"); + return; + } + for (let c of Object.keys(ext)) { + WTerminal.terminalAddCommand(c, ext[c].run, ext[c].help); + } + //add aliases + for (let c of Object.keys(aliases)) { + WTerminal.terminalAddAlias(c, aliases[c]); + } + }; + //init + if (document.body) { + initTerminalVariableCommands(); + } else { + window.addEventListener("load", initTerminalVariableCommands); + } +} diff --git a/src/js/theme.js b/src/js/theme.js new file mode 100644 index 0000000..22aa963 --- /dev/null +++ b/src/js/theme.js @@ -0,0 +1,38 @@ +function toggleTheme() { + const theme = "theme-dark";//localStorage.getItem('theme'); + const root = document.querySelector(":root"); + root.classList.toggle(theme); + if (root.classList.contains(theme)) { + localStorage.setItem('theme', theme); + } else { + localStorage.setItem('theme', "theme-light"); + } +} + +function setTheme(themeName) { + console.log("setting theme: " + themeName); + const root = document.querySelector(":root"); + root.classList.add(themeName); + localStorage.setItem('theme', themeName); +} + +function detectTheme() { + const theme = localStorage.getItem('theme'); + if (theme === 'theme-dark' || theme === 'theme-light') { + setTheme(theme); + return; + } + + if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + setTheme('theme-dark'); + return; + } + + if (window.matchMedia('(prefers-color-scheme: light)').matches) { + setTheme('theme-light'); + return; + } + + setTheme('theme-light'); +} +detectTheme(); diff --git a/src/js/wterminal-autoextend.js b/src/js/wterminal-autoextend.js new file mode 100644 index 0000000..1e23a6f --- /dev/null +++ b/src/js/wterminal-autoextend.js @@ -0,0 +1,33 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: This auromaticly adds the other terminal extention scripts. +* THIS SCRIPT DOES NOT WORK FOR A BROWSER-ADDON/EXTENTION! +*/ + +{// this code block hides the variables below from other scripts. + const addScripts = function() { + // location of script files + const extentionScripts = [ + "js/termext/ext-variables.js", + "js/termext/ext-cookies.js", + "js/termext/ext-eval.js", + "js/termext/ext-timerdebug.js", + "js/termext/ext-stresstest.js", + "js/termext/ext-popvar.js", + "js/termext/ext-passw.js", + ]; + + for (let ext of extentionScripts) { + const element = document.createElement("script"); + // element.addAttribute("src", ext); + element.src = ext; + document.head.append(element); + } + }//--> addScripts = function() { + + if (document.body) { + addScripts(); + } else { + window.addEventListener("load", addScripts); + } +} diff --git a/src/js/wterminal.js b/src/js/wterminal.js new file mode 100644 index 0000000..aa969aa --- /dev/null +++ b/src/js/wterminal.js @@ -0,0 +1,1353 @@ +/* Author: Ward Truyen +* Version: 1.3.1 +* About: This started as a library of functions for output/printing to a 'terminal' +* But then the terminal got bigger and more fun! +*/ +class WTerminal { + //TERMINAL const + static get VERSION() { return "1.3.2"; }; // terminal version, duh. + //css & html element relations: + static get BACKGROUND_CLASS() { return "wterminal-background"; }; // blurred background div class, contains it all, hides it all. + static get CONTAINER_CLASS() { return "wterminal-container"; }; // container div class for all the terminal elements. + static get OUTPUT_CLASS() { return "wterminal-output"; }; // the class from the
 tag where we will to print to.
+  static get INPUT_CLASS() { return "wterminal-input"; }; //  class.
+  static get VISIBLE_CLASS() { return "wterminal-visible"; }; // the class that (by default) hides the terminal when set to the terminal-background div.
+  //globals (for fun experiments)
+  static get GLOBAL_LAST_RESULT() { return true; }; // when true: creates global terminal variable lastResult when a command reurns something
+  static get GLOBAL_LAST_ERROR() { return true; }; // when true: creates global terminal variable lastError when a command throws an error
+  static get GLOBAL_HISTORY() { return false; }; // when true: creates global terminal variable history and stores entered commands
+  //start up values: auto insert & logo print
+  static get AUTO_INSERT_GLOBAL_TEST_VARIABLES() { return false; }; // when true: adds extra global terminal variables for testing printVar (commands: gg, terminal)
+  static get AUTO_INSERT_DROPDOWN() { return false; }; // when true: automaticly inserts a hidden div containing the terminal in html.body
+  static get PRINT_LOGO() { return true; }; // when true: prints the WTerminal logo after terminal construction
+  //Options: open/close
+  static get KEY_OPEN() { return 'Backquote'; }; // 'Backquote' is the event.code to look for on keyDown to open the terminal.
+  static get KEY_OPEN_CTRL() { return false; }; // When true: ctrl-key must be pressed together with WTerminal.KEY_OPEN.
+  static get KEY_CLOSE() { return 'Escape'; }; // 'Escape' is the event.code to look for on keyDown to close the terminal.
+  static get KEY_HISTORY() { return 'ArrowUp'; }; // 'ArrowUp' is the event.code to look for on keyDown of the input-field to get previous command'
+  //Options: output
+  static get PRINT_TO_CONSOLE_LOG() { return false; }; // when true: printing logs to console too.
+  //Options: input
+  static get SLASH_COMMANDS() { return false; }; // when true: all the commands start with a forward slash.
+  static get INPUT_STRICT() { return true; }; // when true: input commands must strictly match.
+  static get PRINT_ALIAS_CHANGE() { return false; }; // when true: prints the change when an alias is used
+  static get PRINT_INNER_COMMANDS() { return false; }; // when true: prints the multiple-commands after && split
+  static get PRINT_COMMAND_RETURN() { return false; }; // when true: prints returned value of executed command, if anny
+  static get MAX_HISTORY() { return 32; }; // the maximum length of the history we keep
+  //Options: extensions
+  static get PRINT_ALIAS_ADD() { return false; }; // when true: prints anny added alias
+  static get PRINT_EXTENSION_ADD() { return false; }; // when true: prints anny extension command names that are added
+  //Options; }; TPO aka terminalPrintObject static get
+  static get TPO_UNKNOWN_OBJECT_PRINT() { return false; }; // when true and printVar detects an empty unkown object, then it prints prototype stuff
+  static get TPO_OBJECT_PREFIX() { return "|  "; }; // when printVar is printing keys of an object, this is added in front.
+  static get TPO_SPECIAL_PREFIX() { return " *" + WTerminal.TPO_OBJECT_PREFIX; }; // when printVar is printing special (keyless or HTMLElement) objects
+  static get TPO_MAX_DEPTH() { return 8; }; // when printVar is gooing this deep in objects it stops
+  static get TPO_INNER_MAX_LENGTH() { return 64; }; // when objects in objects are bigger than this it prints an empty code block { length=100  (too long) }
+
+  static terminals = {};
+
+  static createElement(tagName, tagAttributes, ...tagContents) {
+    const el = document.createElement(tagName);
+    if (typeof tagAttributes === 'object' && tagAttributes != null) {
+      for (let ta of Object.keys(tagAttributes)) {
+        el.setAttribute(ta, tagAttributes[ta]);
+      }
+    }
+    for (let tc of tagContents) {
+      if (typeof tc === "string") {
+        el.appendChild(document.createTextNode(tc))
+        continue;
+      } else if (typeof tc === 'object') {
+        if (tc instanceof HTMLElement) {
+          el.appendChild(tc)
+          continue;
+        }
+      }
+      el.appendChild(document.createTextNode(tc.toString()))
+    }
+    return el;
+  };
+
+  static splitToArguments(str) {
+    function _countChar(str, char) {
+      let index = str.indexOf(char);
+      let count = 0;
+      while (index != -1) {
+        count++;
+        index = str.indexOf(char, index + 1);
+      }
+      return count;
+    }
+    function _reconnectWordsByQuote(words, quoteCharacter) {
+      let quoteCounts = [];
+      for (let i = 0; i < words.length; i++) {
+        quoteCounts[i] = _countChar(words[i], quoteCharacter);
+      }
+      for (let i = 0; i < words.length; i++) {
+        quoteCounts[i] = quoteCounts[i] % 2;
+      }
+      let quotes = [];
+      let quotesIndex = 0;
+      let harvesting = false;
+      for (let i = 0; i < quoteCounts.length; i++) {
+        if (harvesting) {
+          quotes[quotesIndex] += " " + words[i];
+          if (quoteCounts[i] == 1) {
+            harvesting = false;
+            quotesIndex++;
+          }
+        } else {
+          if (quoteCounts[i] == 1) {
+            harvesting = true;
+            quotes[quotesIndex] = words[i];
+          }
+        }
+      }
+      for (let i = 0; i < quotes.length; i++) {
+        if (quotes[i].startsWith(quoteCharacter)) quotes[i] = quotes[i].replaceAll(quoteCharacter, '');
+      }
+      quotesIndex = 0;
+      let removing = false;
+      for (let i = quoteCounts.length - 1; i >= 0; i--) {
+        if (removing) {
+          words.splice(i, 1);
+          if (quoteCounts[i] == 1) {
+            removing = false;
+            words.splice(i, 0, quotes[quotesIndex]);//insert quote
+          }
+        } else {
+          if (quoteCounts[i] == 1) {
+            removing = true;
+            words.splice(i, 1);
+          }
+        }
+      }
+      return words;
+    }
+
+    let args = str.split(" ");
+    args = _reconnectWordsByQuote(args, '"');
+    args = _reconnectWordsByQuote(args, "'");
+    args = _reconnectWordsByQuote(args, '`');
+    return args;
+  };
+
+  static stringToValue(str) {
+    if (typeof str !== "string") {
+      throw new Error("StringToValue error: str must be a string!");
+    }
+    if (str === "true") return true;
+    else if (str === "false") return false;
+    else if (str.startsWith("(global)") || str.startsWith("(Global)")) {
+      return WTerminal.getGlobalVariable(str.substring(8));
+    } else if (str.startsWith("(number)") || str.startsWith("(Number)")) {
+      str = str.substring(8);
+      return str.includes(".") ? parseFloat(str) : parseInt(str);
+    } else if (str.startsWith("{")) {
+      str = str.replaceAll("'", '"'); //must have double quotes
+      // str = str.replaceAll(' ', ''); //should have 1 space-character behind each comma ... ? auto minify?
+      try {
+        return JSON.parse(str);
+      } catch (e) {
+        if (e instanceof SyntaxError) {
+          let err = new Error(e);
+          err.message = e.message + `\n\`${str}\``;
+          // err.data = str;
+          throw err;
+        }
+        throw new Error(e);
+      }
+    } else if (str.startsWith("(function)") || str.startsWith("(Function)")) {
+      return new Function(str.substring(10));
+    } else if (str.startsWith("'") || str.startsWith('"')) {
+      return str.substring(1, str.length - 1);
+    } else if (!isNaN(parseFloat(str)) && isFinite(str)) {
+      return str.includes(".") ? parseFloat(str) : parseInt(str);
+    } else {
+      return str;
+    }
+  }
+
+  static getGlobalVariable(gName) {
+    if (globalThis === undefined) {
+      throw new Error("Missing globalThis");
+    } else {
+      if (gName == '') {
+        throw new Error("Missing argument: VARIABLE_NAME");
+      } else {
+        const names = gName.split(".");
+        if (names.length == 1) {
+          return globalThis[gName];
+        } else {
+          let obj = globalThis;
+          for (let i = 0; i < names.length - 1; i++) {
+            const nobj = obj[names[i]]; // nobj is short for new object
+            if (typeof nobj === "object" && nobj !== null) obj = nobj;
+            else {
+              let rem = names.length - 1 - i;
+              names.splice(names.length - rem, rem);
+              let name = names.join('.');
+              throw new Error(name + " is " + nobj === null ? 'null' : 'not an object');
+            };
+          }
+          return obj[names[names.length - 1]];
+        }
+      }
+    }
+  };
+
+  constructor(name, locationId, options) {
+    // use name in static storage of terminals
+    this.name = name;
+    this.locationId = location;
+    WTerminal.terminals[name] = this;
+
+    //create terminal elements
+    const container = WTerminal.createElement('div', { class: WTerminal.CONTAINER_CLASS, title: "Terminal" });
+    const output = WTerminal.createElement('pre', { class: WTerminal.OUTPUT_CLASS, title: "Terminal output" });
+    const inputForm = WTerminal.createElement('form', { style: "display: inline;", onsubmit: "return false;" });
+    const inputLabel = WTerminal.createElement('label', null, "Input:");
+    const inputText = WTerminal.createElement('input', { class: WTerminal.INPUT_CLASS, title: "Terminal input", type: "text", name: WTerminal.INPUT_CLASS, placeholder: "help" });
+    const inputSubmit = WTerminal.createElement('input', { title: "Submit input", type: "submit", value: "Enter" });
+    const controls = WTerminal.createElement('span', { style: "float: right;" });
+    const btnScrollTop = WTerminal.createElement('button', { title: "Scroll to top" });//, "↑")
+    btnScrollTop.innerHTML = "↑";
+    const btnScrollBottom = WTerminal.createElement('button', { title: "Scroll to bottom" });//, "↓")
+    btnScrollBottom.innerHTML = "↓";
+    this.outputEl = output;
+    this.inputTextEl = inputText;
+
+    // this.inputTextEl.addEventListener("focus", (e)=>{
+    //   console.log("focus input");
+    // });
+    // insert submit function
+    this.onInputFormSubmit = function(event) {
+      event.stopPropagation();
+      this.submitTerminalInput();
+      return false;
+    };
+    inputForm.onsubmit = (e) => this.onInputFormSubmit(e);
+
+    // insert button functions
+    this.scrollToTop = function() {
+      this.outputEl.scrollTop = 0;
+      this.inputTextEl.focus();
+    }
+    btnScrollTop.onclick = () => this.scrollToTop();
+
+    this.scrollToBottom = function() {
+      this.outputEl.scrollTop = this.outputEl.scrollHeight;
+      this.inputTextEl.focus();
+    }
+    btnScrollBottom.onclick = () => this.scrollToBottom();
+
+    container.onclick = function(event) {
+      // clicking in the terminal should not close it
+      event.stopPropagation();
+      // return false;
+    };
+
+    // install shortcuts: up-history && close
+    this.onInputTextKeyDown = function(event) {
+      if (event.repeat == false) {
+        if (event.code == this.options.keyHistory) {
+          if (this.history instanceof Array) {
+            this.inputTextEl.value = this.history[0];
+            event.preventDefault();
+            return false;
+          }
+        }
+        if (event.code == this.options.keyClose && this.isTerminalOpen()) {
+          if (typeof this.backgroundEl !== "undefined") {
+            this.terminalClose();
+          }
+          event.preventDefault();
+          return false;
+        }
+      }
+    };
+    inputText.addEventListener("keydown", (e) => this.onInputTextKeyDown(e));
+
+    inputForm.appendChild(inputLabel);
+    inputForm.appendChild(inputText);
+    inputForm.appendChild(inputSubmit);
+    controls.appendChild(btnScrollTop);
+    controls.appendChild(btnScrollBottom);
+    container.appendChild(output);
+    container.appendChild(inputForm);
+    if (locationId === null) {    // use location to insert terminal in a div(string id) or dropdown (null)
+      const background = WTerminal.createElement('div', { class: WTerminal.BACKGROUND_CLASS, title: "Close terminal" });
+      this.backgroundEl = background;
+
+      const btnClose = WTerminal.createElement('button', { title: "Close terminal" });//, "✖")
+      btnClose.innerHTML = "✖";
+      btnClose.onclick = (e) => this.terminalClose();
+      this.onDocBodyKeyDown = function(event) {
+        //this.printLn('keydown.code ' + event.code);
+        //console.log('terminal keydown.code ' + event.code);
+        //this.printVar(event, "keydownEvent");
+        if (event.repeat == false) {
+          if (event.code == this.options.keyOpen && (this.options.keyOpenCtrl ? event.ctrlKey : !event.ctrlKey) && !this.isTerminalOpen()) {
+            this.terminalOpen();
+            event.preventDefault();
+            return false;
+          } else if (event.code == this.options.keyClose && this.isTerminalOpen()) {
+            if (typeof this.backgroundEl !== "undefined") {
+              this.terminalClose();
+            }
+            event.preventDefault();
+            return false;
+          }
+        }
+      };
+      document.body.addEventListener("keydown", (e) => this.onDocBodyKeyDown(e));
+
+      // close terminal events
+      this.onBackgroundClick = function(event) {
+        // clicking next to the terminal is closing it
+        if (this.isTerminalOpen()) {
+          this.terminalClose();
+          event.stopPropagation();
+        }
+      };
+      background.onclick = (e) => this.onBackgroundClick(e);
+
+      controls.appendChild(btnClose);
+      container.appendChild(controls);
+      background.appendChild(container);
+      document.body.appendChild(background);
+    } else {
+      let locEl = document.getElementById(locationId);
+
+      container.appendChild(controls);
+      locEl.appendChild(container);
+    }
+
+    // use function-options-var to overwrite default options todo: !!
+    this.options = {
+      //open/close
+      keyOpen: WTerminal.KEY_OPEN,
+      keyOpenCtrl: WTerminal.KEY_OPEN_CTRL,
+      keyClose: WTerminal.KEY_CLOSE,
+      keyHistory: WTerminal.KEY_HISTORY,
+      //output
+      printToConsoleLog: WTerminal.PRINT_TO_CONSOLE_LOG,
+      //input
+      slashCommands: WTerminal.SLASH_COMMANDS,
+      inputStrict: WTerminal.INPUT_STRICT,
+      printAliasChange: WTerminal.PRINT_ALIAS_CHANGE,
+      printInnerCommands: WTerminal.PRINT_INNER_COMMANDS,
+      printCommandReturn: WTerminal.PRINT_COMMAND_RETURN,
+      maxHistory: WTerminal.MAX_HISTORY,
+      //extensions
+      printExtensionAdd: WTerminal.PRINT_EXTENSION_ADD,
+      printAliasAdd: WTerminal.PRINT_ALIAS_ADD,
+      //TPO aka terminalPrintObject const
+      tpo_unknownObjectPrint: WTerminal.TPO_UNKNOWN_OBJECT_PRINT,
+      tpo_objectPrefix: WTerminal.TPO_OBJECT_PREFIX,
+      tpo_specialPrefix: WTerminal.TPO_SPECIAL_PREFIX,
+      tpo_maxDepth: WTerminal.TPO_MAX_DEPTH,
+      tpo_innerMaxLength: WTerminal.TPO_INNER_MAX_LENGTH,
+    };
+
+    // finish loading: Welcome prints
+    this.aliasExtensionList = {};
+    this.commandListExtension = {};
+    this.startupDate = new Date();
+    this.printLn(`WTerminal ${WTerminal.VERSION} initialized on `, this.startupDate);
+    if (WTerminal.PRINT_LOGO) {
+      this.printLn(" _  .  _  _____ .----..----. .-.   .-..-..-. .-.  .--.  .-.   ");
+      this.printLn("| |/ \\| |[_   _]| {__ | {)  }| .`-'. ||~|| .`| | / {} \\ | |   ");
+      this.printLn("|  ,-,  |  | |  | {__ | .-. \\| |\\ /| || || |\\  |/  /\\  \\| `--.");
+      this.printLn("'-'   `-'  '-'  `----'`-' `-'`-' ` `-'`-'`-' `-'`-'  `-'`----'");
+    }
+  }
+
+  //#region output 
+  print(...args) {
+    if (this.options.printToConsoleLog) {
+      console.log("terminalPrint: ", ...args);
+    }
+    for (let arg of args) {
+      if (arg instanceof HTMLElement) {
+        this.outputEl.appendChild(arg);
+      } else {
+        this.outputEl.appendChild(document.createTextNode(new String(arg)));
+      }
+    }
+    this.outputEl.scrollTop = this.outputEl.scrollHeight;
+  };
+  printLn(...args) {
+    if (this.options.printToConsoleLog) {
+      console.log("terminalPrintLn: ", ...args);
+    }
+    for (let arg of args) {
+      if (arg instanceof HTMLElement) {
+        this.outputEl.appendChild(arg);
+      } else {
+        this.outputEl.appendChild(document.createTextNode(new String(arg)));
+      }
+    }
+    this.outputEl.appendChild(document.createElement('br'));
+    this.outputEl.scrollTop = this.outputEl.scrollHeight;
+  };
+
+  clearOutput() {
+    if (this.options.printToConsoleLog) {
+      console.log("Terminal cleared");
+    }
+    this.outputEl.replaceChildren();
+    this.outputEl.scrollTop = this.outputEl.scrollHeight;
+  };
+  //#endregion
+
+  static print(...args) {
+    for (const tName in this.terminals) {
+      const t = this.terminals[tName];
+      t.print(...args)
+    }
+  }
+
+  static printLn(...args) {
+    for (const tName in this.terminals) {
+      const t = this.terminals[tName];
+      t.printLn(...args)
+    }
+  }
+
+  //#region extra-output
+  /* prints out bold */
+  printBold = function(text) {
+    this.printLn(WTerminal.createElement('b', null, text));
+  }
+  /* prints out underlined */
+  printTitle(title, useTags = true, char = "=") {
+    if (title && typeof title === "string" && title.length > 0) {
+      if (useTags == false) {
+        this.printLn(title);
+        let underline = "";
+        for (let i = 0; i < title.length; i++) {
+          underline += char;
+        }
+        this.printLn(underline);
+      } else {
+        this.printLn(WTerminal.createElement('u', null, WTerminal.createElement('b', null, title)));
+      }
+    }
+  };
+
+  /* prints out with red text */
+  printError(...args) {
+    this.printLn(WTerminal.createElement('span', { style: "color: red;" }, ...args));
+  };
+
+  printList(list, printKeys = true) {
+    if (typeof list !== "object") {
+      this.printError("printList error: Not a list");
+      return;
+    }
+    if (list === null) return;
+
+    const keys = Object.keys(list);
+    if (keys.length == 0) {
+      if (list.length !== undefined && list.length > 0) {
+        for (let i = 0; i < list.length; i++) {
+          const t = typeof (list[i]);
+          if (t === "undefined") {
+            this.printLn("undefined");
+          } else if (t === "string") {
+            this.printLn('`', list[i], '`');
+          } else if (t === "object" && list[i] === null) {
+            this.printLn("null");
+          } else {
+            this.printLn(list[i]);
+          }
+        }
+      }
+    } else if (printKeys) {
+      keys.forEach((key) => {
+        const t = typeof (list[key]);
+        if (t === "undefined") {
+          this.printLn(key, " = undefined");
+        } else if (t === "string") {
+          this.printLn(key, ' = `', list[key], '`');
+        } else if (t === "object" && list[key] === null) {
+          this.printLn(key, " = null");
+        } else {
+          this.printLn(key, " = ", list[key]);
+        }
+      });
+    } else {
+      keys.forEach((key) => {
+        const t = typeof (list[key]);
+        if (t === "undefined") {
+          this.printLn("undefined");
+        } else if (t === "string") {
+          this.printLn('`', list[key], '`');
+        } else if (t === "object" && obj === null) {
+          this.printLn("null");
+        } else {
+          this.printLn(list[key]);
+        }
+      });
+    }
+  };
+
+  /* prints out var/object content */
+  printVar(obj, name = "var", prefix = "") {
+    if (this.options.printToConsoleLog) {
+      console.log("terminalPrintVar: ", name, " = ", obj);
+    }
+    const t = typeof (obj);
+    if (t === "undefined") {
+      this.printLn(prefix, name, " = undefined");
+      return;
+    } else if (t === "string") {
+      this.printLn(prefix, name, ' = `', obj, '`');
+      return;
+    } else if (t === "object" && obj === null) {
+      this.printLn(prefix, name, " = null");
+      return;
+    } else if (t !== "object") {
+      if (t === "function") {
+        this.printLn(prefix, name, " = ", obj.toString());
+      } else if (t === "number" || t === "boolean") {
+        this.printLn(prefix, name, " = ", obj);
+      } else {
+        this.printLn(prefix, name, " = ", "(", typeof (obj), ") ", obj);
+      }
+      return;
+    }
+    this._printObject(obj, name, prefix);
+  };//-> function printVar
+
+  /* internal/private function: get Object Type */
+  static _getObjType(obj) {
+    let objType = typeof obj;
+    if (obj !== null) {
+      if (obj instanceof Date) {
+        objType += " Date";
+      } else if (obj instanceof Float32Array) {
+        objType += " Float32Array";
+      } else if (obj instanceof Float64Array) {
+        objType += " Float64Array";
+      } else {
+        try {
+          let className = Object.getPrototypeOf(obj).toString().replace('[', '').replace(']', '');
+          if (className == 'object Object' && typeof obj.constructor != 'undefined') {
+            objType += ' ' + obj.constructor.name;
+          } else {
+            objType = className;
+          }
+        } catch (e) {
+          if (obj instanceof Element) {
+            objType += " Element";
+          } else {
+            // this.printError("Bad prototype toString");
+            try {
+              if (className == 'object Object' && typeof obj.constructor != 'undefined') {
+                objType += ' ' + obj.constructor.name;
+              }
+            } catch { }
+          }
+        }
+      }
+    }
+    return objType;
+  }
+
+  /* internal/private function: print Object*/
+  _printObject(obj, name, prefix = "", lvl = 0) {
+    if (obj === null) {
+      this.printLn(prefix, name, " = null");
+    } else if (lvl > this.options.tpo_maxDepth) {
+      this.printLn(prefix, name, " = {} max depth reached(" + lvl + ")");
+    } else {
+      const keys = Object.keys(obj);
+      if (keys.length === 0) {
+        // special objects: no keys
+        if (obj instanceof Date) {
+          this.printLn(prefix, name, " = (Date)", obj.toString());
+        } else if (obj instanceof Array) {
+          this.printLn(prefix, name, " = []");
+        } else if (obj instanceof HTMLElement) {
+          this.printLn(prefix, name, " = (", WTerminal._getObjType(obj), "){");
+          this.printLn(prefix + this.options.tpo_specialPrefix, "tagName = ", obj.tagName);
+          if (obj.attributes.length != 0) {
+            let attr = {};
+            for (let i = 0; i < obj.attributes.length; i++) {
+              let a = obj.attributes[i];
+              if (a.value !== '') {
+                attr[a.name] = a.value;
+              }
+            }
+            // this.printLn(prefix + this.options.tpo_specialPrefix, "attributes = { ", attr, " }");
+            this.printVar(attr, "attributes", prefix + this.options.tpo_specialPrefix);
+          }
+          if (obj.children && obj.children.length > 0) {
+            this.printLn(prefix + this.options.tpo_specialPrefix, "childrenCount = ", obj.children.length);
+          }
+          this.printLn(prefix, "}");
+        } else if (obj instanceof Error) {
+          this.printLn(prefix, name, " = (", WTerminal._getObjType(obj), "){}");
+          const pre = prefix + this.options.tpo_specialPrefix;
+          this.printLn(pre, "message = ", obj.message);
+          this.printLn(pre, "name = ", obj.name);
+          try { this.printLn(pre, "fileName = ", obj.fileName); } catch { }
+          try { this.printLn(pre, "lineNumber = ", obj.lineNumber); } catch { }
+          try { this.printLn(pre, "columnNumber = ", obj.columnNumber); } catch { }
+          try { this.printLn(pre, "stack = ", obj.columnNumber); } catch { }
+        } else if (Object.getPrototypeOf(obj) == "[object Object]") {
+          this.printLn(prefix, name, " = {}");
+        } else {
+          // all properties hidden
+          this.printLn(prefix, name, " = (", WTerminal._getObjType(obj), "){}");
+          if (this.options.tpo_unknownObjectPrint) {
+            const pre = prefix + this.options.tpo_specialPrefix;
+            this.printLn(pre, "isSealed = ", Object.isSealed(obj))
+            this.printLn(pre, "isFrozen = ", Object.isFrozen(obj));
+            this.printLn(pre, "isExtensible = ", Object.isExtensible(obj));
+            this.printLn(pre, "prototype = ", Object.getPrototypeOf(obj));
+            this.printLn(pre, "prototype.prototype = ", Object.getPrototypeOf(Object.getPrototypeOf(obj)));
+            this.printVar(Object.getOwnPropertyNames(obj), ".propertyNames", pre);
+            this.printVar(Object.getOwnPropertyDescriptors(obj), "propertyDescriptors", pre);
+          }
+        }
+      } else {
+        // print keys of object
+        const isArray = obj instanceof Array || obj instanceof HTMLCollection;
+        if (isArray) {
+          if (obj instanceof Array) {
+            this.printLn(prefix, name, " = [ length: ", keys.length);
+          } else {
+            let t = "unknown Object";
+            try {
+              t = WTerminal._getObjType(obj);
+            } catch (e) { }
+            this.printLn(prefix, name, " = (", t, ")[ length: ", keys.length);
+          }
+        } else {
+          let t = "unknown Object";
+          try {
+            t = WTerminal._getObjType(obj);
+          } catch (e) { }
+          this.printLn(prefix, name, " = (", t, "){ length: ", keys.length);
+        }
+        if (lvl !== 0 && keys.length > this.options.tpo_innerMaxLength) {
+          this.printLn(prefix + this.options.tpo_objectPrefix, "(too long)");
+        } else {
+          const prefixIn = prefix + this.options.tpo_objectPrefix;
+          keys.forEach((key, index) => {
+            const type = typeof (obj[key]);
+            const position = isArray ? key : (index + ": " + key);
+            if (obj[key] === obj) {
+              this.printLn(prefixIn, position, " = (parent redefinition) ");
+            } else if (type === "function" || type === "undefined") {
+              this.printLn(prefixIn, position, " = (", type, ") ");
+            } else if (type === "boolean" || type === "number") {
+              this.printLn(prefixIn, position, ' = ', obj[key]);
+            } else if (type === "string") {
+              this.printLn(prefixIn, position, ' = `', obj[key], '`');
+            } else if (type === "object") {
+              this._printObject(obj[key], position, prefixIn, lvl + 1);
+            } else {
+              this.printLn(prefixIn, position, " = (", type, ") ", obj[key]);
+            }
+          });//-> forEach key
+        }
+        this.printLn(prefix, isArray ? "]" : "}");
+      }//-> if keys
+    }//-> if obj, lvl
+  }//-> function _printObject
+  //#endregion
+
+  submitTerminalInput() {
+    const input = this.inputTextEl.value;
+    if (input.length > 0) {
+      if (this.options.slashCommands && !input.startsWith("/")) {
+        //# if not a command: print
+        this.printLn(input);
+      } else {
+        //# if it's a command: execute
+        this.terminalCommand(this.options.slashCommands ? input.substring(1) : input);
+      }
+    }
+    this.inputTextEl.value = ""; //# clear the input field
+    this.inputTextEl.focus();
+    return false;  //# prevent 
from submitting + }; + + terminalClose() { + const terminalBackground = this.backgroundEl; + if (terminalBackground && terminalBackground.classList.contains(WTerminal.VISIBLE_CLASS)) { + terminalBackground.classList.remove(WTerminal.VISIBLE_CLASS); + } else if (typeof terminalBackground === "undefined") { + this.printError("This is not a dropdown terminal."); + } + }; + + terminalOpen() { + const terminalBackground = this.backgroundEl; + if (terminalBackground === null) return; + if (!terminalBackground.classList.contains(WTerminal.VISIBLE_CLASS)) { + terminalBackground.classList.add(WTerminal.VISIBLE_CLASS); + } + // this.inputTextEl.focus(); + this.outputEl.scrollTop = this.outputEl.scrollHeight; + setTimeout(() => this.inputTextEl.focus(), 100); + }; + + terminalOpenClose() { + const terminalBackground = this.backgroundEl; + if (terminalBackground === null) return; + if (!terminalBackground.classList.contains(WTerminal.VISIBLE_CLASS)) { + terminalBackground.classList.add(WTerminal.VISIBLE_CLASS); + // this.inputTextEl.focus(); + this.outputEl.scrollTop = this.outputEl.scrollHeight; + setInterval(() => this.inputTextEl.focus(), 100); + } else { + terminalBackground.classList.remove(WTerminal.VISIBLE_CLASS); + } + } + + isTerminalOpen() { + const terminalBackground = this.backgroundEl; + if (terminalBackground) + return terminalBackground.classList.contains(WTerminal.VISIBLE_CLASS); + return true; + }; + + terminalConst() { + const c = {}; + c.version = WTerminal.VERSION; + //css & html element relations: + c.backgroundClass = WTerminal.BACKGROUND_CLASS; + c.containerClass = WTerminal.CONTAINER_CLASS; + c.outputClass = WTerminal.OUTPUT_CLASS; + c.inputClass = WTerminal.INPUT_CLASS; + c.visibleClass = WTerminal.VISIBLE_CLASS; + //globals + c.globalLastResult = WTerminal.GLOBAL_LAST_RESULT; + c.globalLastError = WTerminal.GLOBAL_LAST_ERROR; + c.globalHistory = WTerminal.GLOBAL_HISTORY; + //auto insert + c.globalTestVariables = WTerminal.AUTO_INSERT_GLOBAL_TEST_VARIABLES; + c.autoInsertDropdown = WTerminal.AUTO_INSERT_DROPDOWN; + c.printLogo = WTerminal.PRINT_LOGO; + //Options: open/close + c.keyOpen = WTerminal.KEY_OPEN; + c.keyOpenCtrl = WTerminal.KEY_OPEN_CTRL; + c.keyClose = WTerminal.KEY_CLOSE; + c.keyHistory = WTerminal.KEY_HISTORY; + //Options: output + c.defaultPrintToConsoleLog = WTerminal.PRINT_TO_CONSOLE_LOG; + //Options: input + c.defaultSlashCommands = WTerminal.SLASH_COMMANDS; + c.defaultInputStrict = WTerminal.INPUT_STRICT; + c.defaultPrintAliasChange = WTerminal.PRINT_ALIAS_CHANGE; + c.defaultPrintInnerCommands = WTerminal.PRINT_INNER_COMMANDS; + c.defaultPrintCommandReturn = WTerminal.PRINT_COMMAND_RETURN; + c.defaultMaxHistory = WTerminal.MAX_HISTORY; + //Options: extensions + c.defaultPrintExtensionAdd = WTerminal.PRINT_EXTENSION_ADD; + c.defaultPrintAliasAdd = WTerminal.PRINT_ALIAS_ADD; + //Options: TPO aka terminalPrintObject const + c.tpo_defaultUnknownObjectPrint = WTerminal.TPO_UNKNOWN_OBJECT_PRINT; + c.tpo_defaultObjectPrefix = WTerminal.TPO_OBJECT_PREFIX; + c.tpo_defaultSpecialPrefix = WTerminal.TPO_SPECIAL_PREFIX; + c.tpo_defaultMaxDepth = WTerminal.TPO_MAX_DEPTH; + c.tpo_defaultInnerMaxLength = WTerminal.TPO_INNER_MAX_LENGTH; + return c; + } + + //#region input + /* List of standard aliases + replacement + */ + static aliasList = Object.freeze({ //freeze object to make it immutable/const-value + "?": "help", + "??": "help help", + cmdlist: "help", + print: "echo", + version: "const version ", + }); + + /* List of standard commands + functions + */ + static commandList = Object.freeze({ //freeze object to make it immutable/const-value + alias: { + run: function(term) { + term.printList(WTerminal.aliasList); + term.printList(term.aliasExtensionList); + }, + help: function(term) { + term.printBold("Usage:"); + term.printLn("alias //Prints all aliases"); + term.printBold("About alias usage:"); + term.printLn("An alias-name has no spaces, but the alias-value can have multiple spaces."); + term.printLn("When the terminal detects an alias-name as the first input word, it gets replaced to the alias-value."); + term.printLn("When the alias-value contains a space, then the arguments will be"); + term.printLn(" joined with the alias-value without a space joining them."); + term.printBold("Samples of using an alias:"); + term.printLn("? //Changes to: `help`"); + term.printLn("? clear //Changes to: `help clear`"); + term.printLn("version //Changes to: `const version ` (command, space at the end)"); + term.printLn("version clear //Changes to: `const version clear`"); + term.printLn("terminal //Changes to: `printvar -r terminal` (command, no space at the end)"); + } + }, + clear: { + run: function(term) { + term.clearOutput(); + }, + help: function(term) { + term.printLn("Clears terminal output."); + } + }, + close: { + run: function(term) { + term.terminalClose(); + }, + help: function(term) { + term.printLn("Closes dropdown terminal."); + } + }, + const: { + run: function(term, argLine) { + const c = term.terminalConst(); + + if (argLine == "") { + term.printVar(c, "const"); + return c; + } else if (c[argLine] !== undefined) { + term.printVar(c[argLine], argLine); + return c[argLine]; + } + }, + help: function(term) { + term.printBold("Usage:"); + term.printLn("const //Prints & returns all constant variables."); + term.printLn("const version //Prints & returns only the version constant."); + } + }, + date: { + run: function(term) { + const d = new Date().toDateString(); + term.printLn("The date is " + d); + return d; + }, + help: function(term) { + term.printLn("Returns the date."); + } + }, + echo: { + run: function(term, argLine) { + if (argLine.charAt(0) == '-') { + if (argLine.charAt(1) == 'r') { + term.printError(argLine.substring(3)); + return; + } else if (argLine.charAt(1) == 'g') { + term.printLn(WTerminal.createElement('span', { style: "color: green;" }, argLine.substring(3))); + return; + } else if (argLine.charAt(1) == 'b') { + term.printLn(WTerminal.createElement('span', { style: "color: blue;" }, argLine.substring(3))); + return; + } + } + term.printLn(argLine); + }, + help: function(term) { + term.printLn("Prints arguments."); + term.printBold("Usage:"); + term.printLn('echo hello //Prints "hello".'); + term.printLn('echo -r hello //Prints "hello", but in red.'); + term.printLn('echo -g hello //Prints "hello", but in green.'); + term.printLn('echo -b hello //Prints "hello", but in blue.'); + } + }, + height: { + run: function(term, argLine) { + if (argLine === "undefined") { + term.outputEl.style.minHeight = ''; // sample 40em (40 lines) + term.outputEl.style.maxHeight = ''; + } else if (argLine) { + term.outputEl.style.minHeight = argLine; // sample 40em (40 lines) + term.outputEl.style.maxHeight = argLine; + } else { + if (term.outputEl.style.minHeight) + term.printLn("height: " + term.outputEl.style.minHeight); + else + term.printLn("at original height."); + // term.printLn("height: " + getComputedStyle(term.outputEl).getPropertyValue('heiht')); + } + }, + help: function(term) { + term.printLn("prints or sets the height of the terminal-output element") + term.printLn("height // prints the terminal-output height") + term.printLn("height 40em // sets terminal-output height to 40 lines") + term.printLn("height undefined // sets terminal-output height to original value") + } + }, + help: { + run: function(term, argLine) { + if (argLine == '') { + if (term.backgroundEl) { + term.print(`Open the terminal with ${(term.options.keyOpenCtrl ? "CTRL + " : "")}the ${term.options.keyOpen}-key. `); + term.printLn(`Close the terminal with the ${term.options.keyClose}-key. `); + } + term.printLn(`Use the ${term.options.keyHistory}-key to get previous command. `) + term.printLn(`Type a command, optionally add some arguments, and press enter or click on submit.`); + term.printBold("Basic commands:"); + Object.keys(WTerminal.commandList).forEach((command, i) => { + if (i == 0) { + term.print(" " + command); + } else if (i % 10 == 0) { + term.printLn(", "); + term.print(" " + command); + } else { + term.print(", " + command); + } + }); + term.printLn(); + term.printBold("Extension commands:"); + Object.keys(term.commandListExtension).forEach((command, i) => { + if (i == 0) { + term.print(" " + command); + } else if (i % 10 == 0) { + term.printLn(", "); + term.print(" " + command); + } else { + term.print(", " + command); + } + }); + term.printLn(); + term.printBold("Using help:"); + term.printLn("help [COMMAND_NAME] //Prints help, optionally on a command"); + term.printBold("Samples of help:"); + term.printLn("help //Prints this help again."); + term.printLn("help help //Prints more help."); + term.printLn("help option //Prints help on the option command."); + term.printLn("help alias //Prints help on the alias command."); + } else { + if (WTerminal.commandList[argLine] !== undefined) { + WTerminal.commandList[argLine].help(term); + } else if (term.commandListExtension[argLine] !== undefined) { + if (typeof term.commandListExtension[argLine].help === "function") { + term.commandListExtension[argLine].help(term); + } else { + term.printLn("No help for command: \"" + argLine + "\""); + } + } else { + term.printError("Help error: Unknown command: \"" + argLine + "\""); + } + } + }, + help: function(term) { + term.printLn("Prints help about the terminal or given command."); + term.printBold("For terminal help use:"); + term.printLn("help"); + term.printBold("For help on a command use:"); + term.printLn("help commandName"); + term.printBold("Samples of help:"); + term.printLn("help //prints terminal help."); + term.printLn("help help //prints this help again."); + term.printLn("help option //prints help on the option command."); + term.printLn("help alias //prints help on the alias command."); + term.printBold("Advanced usage:") + term.printLn("Previous commands are stored in `terminal.history`") + term.printLn("Every command is a function, the result of executing this"); + term.printLn(" will be stored in the global variable `terminal.lastResult`."); + term.printLn("Multiple commands can be chained, separated by `&&`."); + term.printBold("Advanced samples:"); + term.printLn("const version && printvar terminal"); + term.printLn(" //Gets version value, automatically stores it in terminal.lastResult"); + term.printLn(" // then prints out the terminal variable (you will see lastResult in there)"); + term.printLn("printvar -r document.body.children && printvar -r terminal.lastResult.0.children"); + term.printLn(" //Get, print and return html body tag's children"); + term.printLn(" // && print out the children property of first element (of body)."); + term.printLn("gdb && result .0.children // does the same as above"); + } + }, + history: { + run: function(term) { + term.printList(term.history, "history"); + }, + help: function(term) { + term.printLn("prints this terminal's history"); + } + }, + option: { + run: function(term, argLine) { + if (argLine == '') { + term.printList(term.options); + } else { + let args = WTerminal.splitToArguments(argLine); + for (const element of args) { + if (!element.includes("=")) { + throw new Error("Option set error: argument is missing \"=\""); + } + const keyValuePair = element.split("="); + const name = keyValuePair[0]; + if (term.options[name] === undefined) { + throw new Error("Option set error: invalid option name `" + name + "`"); + } + term.options[name] = stringToValue(keyValuePair[1]); + } + return this.options; //todo: make clone for security? + } + }, + help: function(term) { + term.printBold("Usage:"); + term.printLn("option //Prints & returns all the options used."); + term.printLn("option [VARIABLE_NAME=VALUE] //Sets option VARIABLE_NAME to the following value."); + term.printBold("Samples:"); + term.printLn("option printAliasChange=true //Sets option printAliasChange to true."); + term.printLn("option printCommandReturn=true //Sets option printCommandReturn to true."); + term.printLn("option tpo_objectPrefix=\"# \" //Sets objectPrefix to `# `.") + } + }, + reload: { + run: function(term) { + location.reload(); + }, + help: function(term) { + term.printLn("Reloads page."); + } + }, + thisterminal: { + run: function(term) { + term.printVar(term, "terminal"); + }, + help: function(term) { + term.printLn("prints this terminal object") + } + }, + terminalnames: { + run: function(term) { + term.printList(Object.keys(WTerminal.terminals)); + }, + help: function(term) { + term.printLn("prints the names of all terminal objects") + } + }, + time: { + run: function(term) { + const d = new Date(); + const t = d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds(); + term.printLn("The time is " + t); + return t; + }, + help: function(term) { + term.printLn("Returns the current time."); + } + }, + uptime: { + run: function(term) { + const newDate = new Date(); + let diffTime = Math.abs(newDate - term.startupDate); + + const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24)); + if (diffDays > 0) { + term.print(diffDays + " days "); + diffTime -= diffDays * (1000 * 60 * 60 * 24); + } + + const diffHours = Math.floor(diffTime / (1000 * 60 * 60)); + if (diffHours > 0) { + term.print(diffHours + " hours "); + diffTime -= diffHours * (1000 * 60 * 60); + } + + const diffMinutes = Math.floor(diffTime / (1000 * 60)); + if (diffMinutes > 0) { + term.print(diffMinutes + " minutes "); + diffTime -= diffMinutes * (1000 * 60); + } + + const diffSeconds = Math.floor(diffTime / (1000)); + if (diffSeconds > 0) { + term.print(diffSeconds + " seconds "); + diffTime -= diffSeconds * (1000); + } + + if (diffTime > 0) { + term.print(diffTime + " milliseconds "); + } + term.printLn(); + }, + help: function(term) { + term.printLn("Prints how long the terminal has been up."); + } + }, + starttime: { + run: function(term) { + term.printLn("Terminal initialized on ", term.startupDate); + return term.startupDate; + }, + help: function(term) { + term.printLn("Returns when the terminal was started/initialized."); + } + }, + }); + + /* non static -> adds command to this terminal + */ + terminalAddComand(name, run, help) { + if (name === undefined || typeof name !== "string") { + this.printError("TerminalAddCommand error: Variable name must be a string."); + return; + } + if (name === '') { + this.printError("TerminalAddCommand error: Variable name can not be empty."); + return; + } + if (name.includes(' ')) { + this.printError("TerminalAddCommand error: Variable name can have spaces."); + return; + } + if (run === undefined || typeof run !== "function") { + this.printError("TerminalAddCommand error: Variable run must be a function."); + return; + } + if (help !== undefined && typeof run !== "function") { + this.printError("TerminalAddCommand error: Variable help must be a function or undefined."); + return; + } + if (this.options.printExtensionAdd) this.printLn("Command extension added: " + name); + this.commandListExtension[name] = { run: run, help: help }; + } + + /* static -> adds command to all terminals + */ + static terminalAddCommand(name, run, help) { + for (const tName in this.terminals) { + const t = this.terminals[tName]; + t.terminalAddComand(name, run, help); + } + } + + /* non static -> adds alias to this terminal + */ + terminalAddAlias(name, alias) { + if (typeof name !== "string") { + this.printError("AddAlias error: name must be a string."); + return false; + } + if (name.includes(' ')) { + this.printError("AddAlias error: name can not have spaces.") + return false; + } + if (typeof alias !== "string") { + this.printError("AddAlias error: alias must be a string."); + return false; + } + if (this.options.printAliasAdd) this.printLn(`Alias added: ${name} = \`${alias}\``); + this.aliasExtensionList[name] = alias; + return true; + } + + /* static -> adds alias to all terminals + */ + static terminalAddAlias(name, alias) { + for (const tName in this.terminals) { + const t = this.terminals[tName]; + t.terminalAddAlias(name, alias); + } + } + + /* Runs through a commandline searching for subCommands split by && + */ + terminalCommand(cmdLine, isSuperCommand = false) { + if (typeof cmdLine !== "string") { + this.printError("Error: Wrong cmdLine type: " + typeof cmdLine); + return; + } + if (cmdLine == '') return; + + cmdLine = cmdLine.trim(); + // print input + if (isSuperCommand) { + this.printLn(WTerminal.createElement('u', null, 'super-command#'), WTerminal.createElement('b', null, " ", cmdLine)); + } else { + let d = new Date(); + if (this.outputEl.innerHTML != '') this.print('\n'); + this.printLn(WTerminal.createElement('u', null, "(" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + ")#"), WTerminal.createElement('b', null, " ", cmdLine)); + } + // execute + let result; + try { + if (cmdLine.includes("&&")) { + // multiple commands + let commands = cmdLine.split("&&"); + // this.printList(commands); + for (let command of commands) { + command = command.trim(); + if (this.options.printInnerCommands) this.printLn(WTerminal.createElement("u", null, "inner-command#"), WTerminal.createElement("b", null, " ", command)); + result = this._terminalCommand(command); + } + } else { + // single command + result = this._terminalCommand(cmdLine); + } + } catch (e) { + // this.printError("Command error: ", e) + this.printError("Command error:"); + this.printVar(e, "error"); + this.lastError = e; + if (WTerminal.GLOBAL_LAST_ERROR) { + if (typeof globalThis.terminal !== "object") { + globalThis.terminal = {}; + } + globalThis.terminal.lastError = e; + } + // return e; + } + // keep a history + if (this.history === undefined || !(this.history instanceof Array)) { + this.history = []; + } + this.history.unshift(cmdLine); + if (this.history.length > this.options.maxHistory) { + this.history.splice(this.options.maxHistory); + } + if (WTerminal.GLOBAL_HISTORY) { + if (typeof globalThis.terminal !== "object") { + globalThis.terminal = {}; + } + if (globalThis.terminal.history === undefined || !(globalThis.terminal.history instanceof Array)) { + globalThis.terminal.history = []; + } + globalThis.terminal.history.unshift(cmdLine); + if (globalThis.terminal.history.length > this.options.maxHistory) { + globalThis.terminal.history.splice(this.options.maxHistory); + } + } + return result; + }; + + /* Only runs subcommands + * For internal use only + */ + _terminalCommand(cmdLine) { + if (cmdLine == '') return; + // get target values + const split = cmdLine.split(' '); + let command = split.shift(); + let argLine = split.join(' '); + + // not strict + if (!this.options.inputStrict) { + command = command.toLowerCase(); + } + // check for alias + let al; + if (WTerminal.aliasList[command]) { + al = WTerminal.aliasList[command]; + } else if (this.aliasExtensionList[command]) { + al = this.aliasExtensionList[command]; + } + if (al !== undefined) { + if (al.includes(" ")) { + //new command and argument + al = al.split(" "); + command = al.shift(); + argLine = (al.join(' ') + argLine).trim(); + } else { + //new command only + command = al; + } + if (this.options.printAliasChange) { + this.printLn("Alias found#", WTerminal.createElement('b', null, command + " " + argLine)); + } + } + // execute + let result; + if (WTerminal.commandList[command]) { + result = WTerminal.commandList[command].run(this, argLine); + } else if (this.commandListExtension[command]) { + result = this.commandListExtension[command].run(this, argLine); + } else { + this.printError("Error: Unknown command: \"" + command + "\""); + this.printLn("Try help"); + return; + } + // print result + if (this.options.printCommandReturn) { + this.printVar(result, `Result of \`${cmdLine}\``); + } + // store result + this.lastResult = result; + if (WTerminal.GLOBAL_LAST_RESULT) { + if (typeof globalThis.terminal !== "object" && typeof result !== "undefined") { + globalThis.terminal = {}; + } + if (typeof globalThis.terminal === 'object' && + (typeof globalThis.terminal.lastResult !== "undefined" || typeof result !== "undefined")) { + globalThis.terminal.lastResult = result; + } + } + return result; + }//-> function _terminalCommand + //#endregion + + /* installs a dropdown Terminal if it doesnt exist + */ + static instalDropdownTerminal() { + if (WTerminal.terminals.dropdown) return; + if (document.body) { + new WTerminal('dropdown', null, null); + } else { + window.addEventListener("load", () => new WTerminal('dropdown', null, null)); + } + } + + /* short for openDropdownTerminal + */ + static open() { + if (WTerminal.terminals.dropdown) { + WTerminal.terminals.dropdown.terminalOpen(); + } else { + if (confirm("Dropdown terminal is not available. Create terminal?")) { + instalDropdownTerminal(); + WTerminal.terminals.dropdown.terminalOpen(); + } + } + } + +}//-> class WTerminal + +//#region init +//Globals +if (WTerminal.AUTO_INSERT_GLOBAL_TEST_VARIABLES) { + globalThis.terminal = {}; + globalThis.terminal.version = WTerminal.VERSION; + globalThis.terminal.terminals = WTerminal.terminals; + globalThis.terminal.wterminal = WTerminal; + // terminal.options = options; + globalThis.terminal.testvars = {}; + const p = globalThis.terminal.testvars; + p.hello = "Hello World!"; + p.multilineStr = `one +two +three`; + p.emptyArray = []; + p.emptyObject = {}; + p.newArray = new Array(); + p.newDate = new Date(); + p.newSet = new Set(); + p.newMap = new Map(); + p.testArray1 = ["three", "two", "one"]; + p.testArray1 = ["three", 2, true, { 1: 1, '2': 2, 'three': 3 }]; + // p.testKeys = { 1:1, '2':2, 'three':3 };// 1 is a number. 2 is a string. => at runtime keys are always strings (even in an Array) +} + +if (WTerminal.AUTO_INSERT_DROPDOWN) { + WTerminal.instalDropdownTerminal(); +} +//#endregion + +/* get a registered WTerminal, preferrably by name, if not it returns the first of the list. + */ +const getWTerminal = function(name) { + if (name === undefined) for (const t in WTerminal.terminals) return WTerminal.terminals[t]; + return WTerminal.terminals[name]; +} diff --git a/src/pong.html b/src/pong.html new file mode 100644 index 0000000..773e3c3 --- /dev/null +++ b/src/pong.html @@ -0,0 +1,57 @@ + + + + + Sample: Ping pong + + + + + + + + + + + + +
+ + Home + Static + Dropdown + Pong + Addon +
+ +
+ + + +

Game sample: Ping pong

+ +
+ +

How to:
+ Use the arrow keys up and down to move, or [W] and [S].
+ Press spacebar or enter, to start a next round, or to pause/resume.

+

To restart the game use the buttons below. Or open the terminal, type "restartgame" and press enter.

+

To get more information about pong open the terminal, type "printgame" and press enter.
+ Or open the terminal, type "popvar game" and press enter (DEBUG_PARENT_OBJECT must be true for this to work).

+ + + + + + +
+ +
+

Author: Ward Truyen

+
+ + + diff --git a/src/snd/glass-knock.mp3 b/src/snd/glass-knock.mp3 new file mode 100644 index 0000000..52b49e5 Binary files /dev/null and b/src/snd/glass-knock.mp3 differ diff --git a/src/snd/short-success.mp3 b/src/snd/short-success.mp3 new file mode 100644 index 0000000..ff10122 Binary files /dev/null and b/src/snd/short-success.mp3 differ diff --git a/src/static-terminal.html b/src/static-terminal.html new file mode 100644 index 0000000..e87f5d4 --- /dev/null +++ b/src/static-terminal.html @@ -0,0 +1,61 @@ + + + + + WTerminal static sample + + + + + + + + + + + +
+ + Home + Static + Dropdown + Pong + Addon +
+ +
+ +

Static-terminal:

+

Below we have created a WTerminal, by using it's constructor with a name ("static") and + element-id("static-terminal") to generate a terminal as a 'static element on our page'.

+
+ +
+ Samples: + + + | + | + +
+

How to add the terminal to a site:

+

Add the CSS and JavaScript files to your site header:
+ <link rel="stylesheet" href="css/wterminal.css" />
+ <script src="js/wterminal.js"></script>
+

+

Where the terminal should be add a div element, with id "static-terminal":
+ <div id="static-terminal"></div> +

+

Finally add a script element with:
+ new WTerminal("static", "static-terminal", null); +

+
+ +
+

Author: Ward Truyen

+
+ + + diff --git a/src/test/asserts.html b/src/test/asserts.html new file mode 100644 index 0000000..0a1bf22 --- /dev/null +++ b/src/test/asserts.html @@ -0,0 +1,38 @@ + + + + + Test Asserts + + + + + + + + + + +
+ + WTerminal testing home +
+ +
+

Test Asserts:

+
+ + + +
+ +
+

Author: Ward Truyen

+
+ + + diff --git a/src/test/css/main.css b/src/test/css/main.css new file mode 100644 index 0000000..31eb115 --- /dev/null +++ b/src/test/css/main.css @@ -0,0 +1,99 @@ +/* Author: Ward Truyen + * Version: 1.3.0 + */ + +html, +body { + margin: 0px; + padding: 0px; + background: radial-gradient(ellipse at top, #8888ff, transparent), + radial-gradient(ellipse at bottom, #ad9fff, transparent); + height: 100%; + display: flex; + flex-direction: column; +} + +main { + margin: auto; + width: 80%; + border: 2px solid darkgreen; + padding: 10px; + background-color: white; + box-shadow: 3px 3px 3px black; +} + +h1 { + margin-top: 4px; + margin-bottom: 8px; + text-decoration: underline; +} + +header { + padding: 2px 16px 4px 16px; + /* width: 100%; */ + border-bottom: 1px solid black; + /* background-image: linear-gradient(#0000008f, #0000004f); */ + background-color: #80808080; + /* linear-gradient(#0000008f, #0000004f);*/ + /* margin-top: 16px; */ + text-shadow: 1px 1px 1px #0000004f; +} + +header a { + /* border: solid 1px black; */ + padding: 2px; + margin: 1px; +} + +footer { + padding: 8px 16px 4px 16px; + /* width: 100%; */ + border-top: 1px solid black; + /* background-color: #0000004f; */ + background-image: linear-gradient(#0000008f, #0000004f); + /* margin-top: 16px; */ + margin-top: auto; + text-shadow: 1px 1px 1px #0000004f; +} + +footer h1, +footer h2, +footer h3, +footer p { + display: inline; + margin: 0px; +} + +.float-right { + float: right; +} + +.text-red { + color: red; +} + +.button-red { + background-color: red; +} + +.button-black { + background-color: black; + color: white; +} + +html.theme-dark { + color-scheme: dark; + background-image: linear-gradient(-10deg, #1010206F 60%, #5050506F); + color: white; +} + +.theme-dark>body>main { + border: 1px solid darkgray; + background-color: black; + box-shadow: 3px 3px 5px #101010C0; +} + +.theme-dark>body>main>h1 { + text-shadow: 0px 0px 3px blue, 2px 2px 6px; +} + diff --git a/src/test/index.html b/src/test/index.html new file mode 100644 index 0000000..4323b90 --- /dev/null +++ b/src/test/index.html @@ -0,0 +1,38 @@ + + + + + WTerminal test home + + + + + + + + +
+ + WTerminal testing home + + +
+ +
+

Tests:

+
+ Test All
+
+ Test Asserts
+ Test WTerminal.splitToArguments
+
+

Others:

+ WTerminal site home +
+ +
+

Author: Ward Truyen

+
+ + + diff --git a/src/test/js/asserts.js b/src/test/js/asserts.js new file mode 100644 index 0000000..87fe3b6 --- /dev/null +++ b/src/test/js/asserts.js @@ -0,0 +1,74 @@ +/* Author: Ward Truyen +* Version: 1.0.0 +* About: A tool for testing purposes +*/ + +class Asserts { + static runTest(name, testFunction, term, pre = " ", printStacktraceAndStop = true) { + term.print(pre+name); + try { + testFunction(); + term.printLn(WTerminal.createElement("span", {style:"color: green;"}, " OK")); + } catch (e) { + term.printError(" failed: " + e.toString()); + if(printStacktraceAndStop){ + term.printLn(WTerminal.createElement("span", {style:"color: blue;"}, e.stack)); + throw e; + } + } + } + + /* Throws error when not true + */ + static isTrue(value) { + if (value === true) return; + throw new Error('assert isTrue failed'); + } + + /* Throws error when not false + */ + static isFalse(value) { + if (value === false) return; + throw new Error('assert isFalse failed'); + } + /* Throws error when not equal + */ + static isEqual(value1, value2) { + if (Asserts._equals(value1, value2)) return; + // if( typeof value1 === "object" || typeof value2 === "object") throw new Error('assert isEqual failed'); + throw new Error('assert isEqual failed\n' + value1 + " != " + value2); + } + + /* returns true when values/objects v1 and v2 are equal + */ + static _equals(v1, v2, _depth = 0) { + if (v1 === v2) return true; // easy peasy + let t = typeof v1 + if (t === "string" || v1 instanceof String && typeof v2 === "string" || v2 instanceof String) return v1.valueOf() === v2.valueOf(); // "Hello" equals to new String("Hello") + if (t !== typeof v2 || v1.constructor.name !== v2.constructor.name) return false; // must be of identical type + t += " " + v1.constructor.name; + switch (t) { + default: // Yet Unkown when i coded this? + throw new Error("Todo: checking of type " + t); + case 'boolean Boolean': + case 'number Number': + case 'object Date': + return v1.valueOf() === v2.valueOf(); + case 'object Array': + if (_depth > 4) throw Error("too deep"); + if (v1.length !== v2.length) return false; + for (let i = 0; i < v1.length; i++) { + if (Asserts._equals(v1[i], v2[i], _depth + 1) === false) return false; + } + return true; + case 'object Object': + if (_depth > 4) throw Error("too deep"); + const keys = Object.keys(v1); + if (keys.length != Object.keys(v2).length) return false; + for (let k in keys) { + if (Asserts._equals(v1[k], v2[k], _depth + 1) === false) return false; + } + return true; + }//-> switch (t) + }//-> static equals(v1, v2) +}//-> class Asserts diff --git a/src/test/js/test-asserts.js b/src/test/js/test-asserts.js new file mode 100644 index 0000000..687eba0 --- /dev/null +++ b/src/test/js/test-asserts.js @@ -0,0 +1,106 @@ +{ + const term = getWTerminal(); + term.printLn("Testing Asserts"); + //isTrue testing + Asserts.runTest(`Asserts.isTrue(true)`, + () => { + try { + Asserts.isTrue(true); + } catch (e) { + throw new Error("Assserts.isTrue(true) failed", e); + } + }, term); + Asserts.runTest(`Asserts.isTrue(false)`, + () => { + let throwsNoError = true; + try { + Asserts.isTrue(false); + } catch (e) { + throwsNoError = false; + } + if (throwsNoError) throw new Error("Assert.isTrue(false) does not throw Error"); + }, term); + // Asserts.runTest('This test should fail', ()=>{throw new Error("To fail test")}, term); + //isFalse testing + Asserts.runTest(`Asserts.isFalse(false)`, + () => { + try { + Asserts.isFalse(false); + } catch (e) { + throw new Error("Assserts.isFalse(false) failed", e); + } + }, term); + Asserts.runTest(`Asserts.isFalse(true)`, + () => { + let throwsNoError = true; + try { + Asserts.isFalse(true); + } catch (e) { + throwsNoError = false; + } + if (throwsNoError) throw new Error("Assert.isFalse(true) does not throw Error"); + }, term); + //isEqual testing + Asserts.runTest(`Asserts.isEqual(true, true)`, + () => { + try { + Asserts.isEqual(true, true); + } catch (e) { + throw new Error("Assserts.isEqual(true, true) failed", e); + } + }, term); + Asserts.runTest(`Asserts.isEqual(true, false)`, + () => { + let throwsNoError = true; + try { + Asserts.isEqual(true, false); + } catch (e) { + throwsNoError = false; + } + if (throwsNoError) throw new Error("Assert.isEqual(true, false) does not throw Error"); + }, term); + //_equals testing + Asserts.runTest(`Asserts.isFalse(Asserts._equals(null, 0))`, + () => Asserts.isFalse(Asserts._equals(null, 0)), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(null, undefined))`, + () => Asserts.isFalse(Asserts._equals(null, undefined)), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(false, true))`, + () => Asserts.isFalse(Asserts._equals(false, true)), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(false, 0))`, + () => Asserts.isFalse(Asserts._equals(false, 0)), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals(true, true))`, + () => Asserts.isTrue(Asserts._equals(true, true)), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(true, 1))`, + () => Asserts.isFalse(Asserts._equals(true, 1)), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(0, '0'))`, + () => Asserts.isFalse(Asserts._equals(0, '0')), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(0, 1))`, + () => Asserts.isFalse(Asserts._equals(0, 1)), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals(1, 1))`, + () => Asserts.isTrue(Asserts._equals(1, 1)), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(1, '1'))`, + () => Asserts.isFalse(Asserts._equals(1, '1')), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals(Math.PI, Math.PI))`, + () => Asserts.isTrue(Asserts._equals(Math.PI, Math.PI)), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals("Hello", "Hello"))`, + () => Asserts.isTrue(Asserts._equals("Hello", "Hello")), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals("Hello", new String("Hello")))`, + () => Asserts.isTrue(Asserts._equals("Hello", new String("Hello"))), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals("Hello", "hello"))`, + () => Asserts.isFalse(Asserts._equals("Hello", "hello")), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals(["Hello", 1], ["Hello", 1]))`, + () => Asserts.isTrue(Asserts._equals(["Hello", 1], ["Hello", 1])), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals(["Hello", 1], [1, "Hello"]))`, + () => Asserts.isFalse(Asserts._equals(["Hello", 1], [1, "Hello"])), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals({}, {}))`, + () => Asserts.isTrue(Asserts._equals({}, {})), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals({ a: "Hello", b: 1 }, { a: "Hello", b: 1 }))`, + () => Asserts.isTrue(Asserts._equals({ a: "Hello", b: 1 }, { a: "Hello", b: 1 })), term); + Asserts.runTest(`Asserts.isTrue(Asserts._equals({ a: "Hello", b: 1 }, { b: 1, a: "Hello" }))`, + () => Asserts.isTrue(Asserts._equals({ a: "Hello", b: 1 }, { b: 1, a: "Hello" })), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals({ a: "Hello", b: 1 }, { b: 1, a: "Hello", c: undefined }))`, + () => Asserts.isFalse(Asserts._equals({ a: "Hello", b: 1 }, { b: 1, a: "Hello", c: undefined })), term); + Asserts.runTest(`Asserts.isFalse(Asserts._equals({ a: "Hello", b: 1 }, { b: 1, a: "Hello", c: null }))`, + () => Asserts.isFalse(Asserts._equals({ a: "Hello", b: 1 }, { b: 1, a: "Hello", c: null })), term); + term.printLn("Asserts OK"); +} diff --git a/src/test/js/test-splitToArguments.js b/src/test/js/test-splitToArguments.js new file mode 100644 index 0000000..a5741c0 --- /dev/null +++ b/src/test/js/test-splitToArguments.js @@ -0,0 +1,20 @@ +{ + const term = getWTerminal(); + term.printLn("Testing WTerminal.splitToArguments"); + Asserts.runTest(`Asserts.isEqual(WTerminal.splitToArguments("hello world"), ["hello", "world"]);`, + () => Asserts.isEqual(WTerminal.splitToArguments("hello world"), ["hello", "world"]), term); + Asserts.runTest(`Asserts.isEqual(WTerminal.splitToArguments("hello 1"), ["hello", "1"])`, + () => Asserts.isEqual(WTerminal.splitToArguments("hello 1"), ["hello", "1"]), term); + Asserts.runTest(`Asserts.isEqual(WTerminal.splitToArguments('"hello world" 1'), ["hello world", "1"])`, + () => Asserts.isEqual(WTerminal.splitToArguments('"hello world" 1'), ["hello world", "1"]), term); + Asserts.runTest(`Asserts.isEqual(WTerminal.splitToArguments("'hello world' 1"), ["hello world", "1"])`, + () => Asserts.isEqual(WTerminal.splitToArguments("'hello world' 1"), ["hello world", "1"]), term); + // Asserts.runTest('This test should fail', ()=>{throw new Error("To fail test")}, term); + Asserts.runTest(`Asserts.isEqual(WTerminal.splitToArguments("\`hello world\` 1"), ["hello world", "1"])`, + () => Asserts.isEqual(WTerminal.splitToArguments("`hello world` 1"), ["hello world", "1"]), term); + Asserts.runTest(`Asserts.isEqual(WTerminal.splitToArguments("-rf /css/test.css myKitty.js 'my name with spaces'"), + ["-rf", "/css/test.css", "myKitty.js", "my name with spaces"])`, + () => Asserts.isEqual(WTerminal.splitToArguments("-rf /css/test.css myKitty.js 'my name with spaces'"), + ["-rf", "/css/test.css", "myKitty.js", "my name with spaces"]), term); + term.printLn("WTerminal.splitToArguments OK"); +} diff --git a/src/test/splitToArguments.html b/src/test/splitToArguments.html new file mode 100644 index 0000000..f0e7dd7 --- /dev/null +++ b/src/test/splitToArguments.html @@ -0,0 +1,38 @@ + + + + + Test WTerminal.splitToArguements + + + + + + + + + + +
+ + WTerminal testing home +
+ +
+

Test WTerminal.splitToArguements:

+
+ + + +
+ +
+

Author: Ward Truyen

+
+ + + diff --git a/src/test/test-all.html b/src/test/test-all.html new file mode 100644 index 0000000..063a27e --- /dev/null +++ b/src/test/test-all.html @@ -0,0 +1,39 @@ + + + + + Test all + + + + + + + + + + +
+ + WTerminal testing home +
+ +
+

Test all:

+
+ + + + +
+ + + + +