diff --git a/ReleaseNotes.md b/ReleaseNotes.md index 668f08e..8777288 100644 --- a/ReleaseNotes.md +++ b/ReleaseNotes.md @@ -1,9 +1,25 @@ -## Version 0.1.5 +## Version 0.1.6 --- Release highlights: -* Added correct icon -* made runner start and stop the same button which just toggles. +* Added extra tasks +* Modified the terminal to use [Xterm.js](https://github.com/xtermjs/xterm.js) --- + +# Extra tasks - Clean + +It is now possible to run dotnet clean against each loaded application. + +# Xterm.js + +The terminal has now been replaced with Node-PTY and Xterm.JS. + +These two technologies are what powers the integrated terminal in many IDE's and editors such as VS-Code. + +This now provides a much richer log output along with performance improvements and the ability to view a terminal in full screen. + + + + \ No newline at end of file diff --git a/app/Components/runner/Runner.js b/app/Components/runner/Runner.js index f20cf63..c936442 100644 --- a/app/Components/runner/Runner.js +++ b/app/Components/runner/Runner.js @@ -1,4 +1,12 @@ -const { killDotnetProcessAsync, startDotnetProcess } = require('../../tasks'); +const { killDotnetProcessAsync, startDotnetProcess, startCleanProcess } = require('../../tasks'); +const Terminal = require('xterm').Terminal; +const fit = require('xterm/lib/addons/fit/fit'); +const fullScreen = require('xterm/lib/addons/fullscreen/fullscreen'); +const debounce = require('../../utils/debounce'); + +Terminal.applyAddon(fit); +Terminal.applyAddon(fullScreen); + const WebComponentBase = require('../WebComponentBase'); module.exports = class RunnerElement extends WebComponentBase { @@ -14,6 +22,8 @@ module.exports = class RunnerElement extends WebComponentBase { this._name = ''; + this._terminalProcess; + this.setState(RunnerElement.states.stopped); } @@ -34,10 +44,48 @@ module.exports = class RunnerElement extends WebComponentBase { this.setState(this.state); const clearLog = shadow.querySelector('.clear-log'); + const clean = shadow.querySelector('.clean'); + const full = shadow.querySelector('.full'); + const terminal = shadow.querySelector('.terminals'); clearLog.addEventListener('click', () => this.clearData()); + clean.addEventListener('click', () => this.clean()); + + terminal.addEventListener('click', (e) => { + if (e.target.classList.contains('full-screen')) { + terminal.classList.remove('full-screen'); + this._terminalProcess.fit(); + } + }); + + full.addEventListener('click', () => { + terminal.classList.add('full-screen'); + + this.resize(); + }); + + this._terminalProcess = new Terminal(); + this._terminalProcess.setOption('disableStdin', true); + this._terminalProcess.setOption('fontFamily', "Consolas, 'Courier New', monospace"); + + this._terminalProcess.open(shadow.querySelector('.terminals')); shadow.querySelector('.action').addEventListener('click', this._onToggle.bind(this)); + + // Terminal wont fit itself on resize. + window.addEventListener('resize', debounce(this.resize.bind(this), { + delay: 100, + executeOnFirstRun: true + })); + + this.resize(); + } + + resize() { + this._terminalProcess.fit(); + + if (this._runningProccess) + this._runningProccess.resize(this._terminalProcess.cols, this._terminalProcess.rows); } _enableAction() { @@ -67,6 +115,20 @@ module.exports = class RunnerElement extends WebComponentBase { } } + _enableClean() { + if (!this.shadowRoot) + return; + + this.shadowRoot.querySelector('.clean').removeAttribute('disabled'); + } + + _disableClean() { + if (!this.shadowRoot) + return; + + this.shadowRoot.querySelector('.clean').setAttribute('disabled', 'disabled'); + } + onStart() { if (this.state === RunnerElement.states.running || this.state === RunnerElement.states.starting) return; @@ -75,43 +137,43 @@ module.exports = class RunnerElement extends WebComponentBase { this.setState(RunnerElement.states.starting); - this._runningProccess = startDotnetProcess(this.cwd, true, this.runCommandArguments); + this._terminalProcess.writeln("Starting..."); - this._runningProccess.on('close', () => { + this._runningProccess = startDotnetProcess(this.cwd, true, this.runCommandArguments, this._terminalProcess.cols, this._terminalProcess.rows); + + this._runningProccess.on('exit', () => { this.setState(RunnerElement.states.stopped); this._runningProccess = undefined; }); - this._runningProccess.stdout.on('data', (d) => this.onData(d.toString())); - this._runningProccess.stderr.on('data', (d) => this.onData(d.toString(), true)); - } + this._runningProccess.once('data', () => this.setState(RunnerElement.states.running)); - onData(d, errorData) { - if (this.state === RunnerElement.states.starting) - this.setState(RunnerElement.states.running); + this._runningProccess.on('data', (d) => { + this._terminalProcess.write(d); + }); + } - const el = document.createElement('span'); - const terminal = this.shadowRoot.querySelector('.terminal'); + clean() { + if (this.state === RunnerElement.states.running || this.state === RunnerElement.states.starting) + return; - el.classList.add('log-item'); + this._runningProccess = startCleanProcess(this.cwd); - if (errorData) - el.classList.add('error'); - - el.textContent = d; + this._runningProccess.on('exit', () => { + this.setState(RunnerElement.states.stopped); - terminal.appendChild(el); + this._runningProccess = undefined; + }); - terminal.scrollTop = terminal.scrollHeight; + this._runningProccess.on('data', (d) => { + this.setState(RunnerElement.states.running); + this._terminalProcess.write(d); + }); } clearData() { - const terminal = this.shadowRoot.querySelector('.terminal'); - - while(terminal.firstChild) { - terminal.removeChild(terminal.firstChild); - } + this._terminalProcess.clear(); } onTerminate() { @@ -136,6 +198,7 @@ module.exports = class RunnerElement extends WebComponentBase { setState(state) { this._disableAction(); + this._disableClean(); this.state = state; @@ -166,6 +229,7 @@ module.exports = class RunnerElement extends WebComponentBase { stateEl.textContent = 'Stopped'; stateEl.className = 'state badge badge-secondary'; this._enableAction(); + this._enableClean(); actionBtn.textContent = 'Start'; break; } diff --git a/app/Components/runner/runner.css b/app/Components/runner/runner.css index 6966643..41232d7 100644 --- a/app/Components/runner/runner.css +++ b/app/Components/runner/runner.css @@ -9,19 +9,42 @@ span.badge { padding: 5px; } -.terminal { - background-color: rgb(0, 36, 81); - color: #cccccc; - white-space: pre-line; +:host(:hover) .full-screen-toggle { + opacity: 1; +} + +.terminals { border: 1px solid rgba(128, 128, 128, 0.35); - border-radius: 4px; - padding: 5px; - word-break: break-word; - max-height: 200px; - overflow: auto; - flex: 1; } .log-item.error { color: red; -} \ No newline at end of file +} + +.terminals.full-screen { + position: absolute; + top: 0px; + bottom: 0px; + left: 0px; + right: 0px; + z-index: 200; +} + +.terminals.full-screen::before { + content: 'Close'; + display: block; + position: absolute; + top: 0px; + z-index: 5; + right: 20px; + color: white; + cursor: pointer; + border-radius: 3px; + border: 1px solid white; + padding: 5px; + margin: 3px; +} + +.terminals { + position: relative; +} diff --git a/app/Components/runner/runner.html b/app/Components/runner/runner.html index f9d42e9..0ec4f21 100644 --- a/app/Components/runner/runner.html +++ b/app/Components/runner/runner.html @@ -1,10 +1,22 @@ + + + +