Skip to content

Commit 80e0473

Browse files
authored
Merge pull request #17 from ste2425/xterm
Xterm integration
2 parents 4f9f05b + 90e5e48 commit 80e0473

File tree

14 files changed

+903
-75
lines changed

14 files changed

+903
-75
lines changed

ReleaseNotes.md

Lines changed: 19 additions & 3 deletions
Large diffs are not rendered by default.

app/Components/runner/Runner.js

Lines changed: 87 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,12 @@
1-
const { killDotnetProcessAsync, startDotnetProcess } = require('../../tasks');
1+
const { killDotnetProcessAsync, startDotnetProcess, startCleanProcess } = require('../../tasks');
2+
const Terminal = require('xterm').Terminal;
3+
const fit = require('xterm/lib/addons/fit/fit');
4+
const fullScreen = require('xterm/lib/addons/fullscreen/fullscreen');
5+
const debounce = require('../../utils/debounce');
6+
7+
Terminal.applyAddon(fit);
8+
Terminal.applyAddon(fullScreen);
9+
210
const WebComponentBase = require('../WebComponentBase');
311

412
module.exports = class RunnerElement extends WebComponentBase {
@@ -14,6 +22,8 @@ module.exports = class RunnerElement extends WebComponentBase {
1422

1523
this._name = '';
1624

25+
this._terminalProcess;
26+
1727
this.setState(RunnerElement.states.stopped);
1828
}
1929

@@ -34,10 +44,48 @@ module.exports = class RunnerElement extends WebComponentBase {
3444
this.setState(this.state);
3545

3646
const clearLog = shadow.querySelector('.clear-log');
47+
const clean = shadow.querySelector('.clean');
48+
const full = shadow.querySelector('.full');
49+
const terminal = shadow.querySelector('.terminals');
3750

3851
clearLog.addEventListener('click', () => this.clearData());
52+
clean.addEventListener('click', () => this.clean());
53+
54+
terminal.addEventListener('click', (e) => {
55+
if (e.target.classList.contains('full-screen')) {
56+
terminal.classList.remove('full-screen');
57+
this._terminalProcess.fit();
58+
}
59+
});
60+
61+
full.addEventListener('click', () => {
62+
terminal.classList.add('full-screen');
63+
64+
this.resize();
65+
});
66+
67+
this._terminalProcess = new Terminal();
68+
this._terminalProcess.setOption('disableStdin', true);
69+
this._terminalProcess.setOption('fontFamily', "Consolas, 'Courier New', monospace");
70+
71+
this._terminalProcess.open(shadow.querySelector('.terminals'));
3972

4073
shadow.querySelector('.action').addEventListener('click', this._onToggle.bind(this));
74+
75+
// Terminal wont fit itself on resize.
76+
window.addEventListener('resize', debounce(this.resize.bind(this), {
77+
delay: 100,
78+
executeOnFirstRun: true
79+
}));
80+
81+
this.resize();
82+
}
83+
84+
resize() {
85+
this._terminalProcess.fit();
86+
87+
if (this._runningProccess)
88+
this._runningProccess.resize(this._terminalProcess.cols, this._terminalProcess.rows);
4189
}
4290

4391
_enableAction() {
@@ -67,6 +115,20 @@ module.exports = class RunnerElement extends WebComponentBase {
67115
}
68116
}
69117

118+
_enableClean() {
119+
if (!this.shadowRoot)
120+
return;
121+
122+
this.shadowRoot.querySelector('.clean').removeAttribute('disabled');
123+
}
124+
125+
_disableClean() {
126+
if (!this.shadowRoot)
127+
return;
128+
129+
this.shadowRoot.querySelector('.clean').setAttribute('disabled', 'disabled');
130+
}
131+
70132
onStart() {
71133
if (this.state === RunnerElement.states.running || this.state === RunnerElement.states.starting)
72134
return;
@@ -75,43 +137,43 @@ module.exports = class RunnerElement extends WebComponentBase {
75137

76138
this.setState(RunnerElement.states.starting);
77139

78-
this._runningProccess = startDotnetProcess(this.cwd, true, this.runCommandArguments);
140+
this._terminalProcess.writeln("Starting...");
79141

80-
this._runningProccess.on('close', () => {
142+
this._runningProccess = startDotnetProcess(this.cwd, true, this.runCommandArguments, this._terminalProcess.cols, this._terminalProcess.rows);
143+
144+
this._runningProccess.on('exit', () => {
81145
this.setState(RunnerElement.states.stopped);
82146

83147
this._runningProccess = undefined;
84148
});
85149

86-
this._runningProccess.stdout.on('data', (d) => this.onData(d.toString()));
87-
this._runningProccess.stderr.on('data', (d) => this.onData(d.toString(), true));
88-
}
150+
this._runningProccess.once('data', () => this.setState(RunnerElement.states.running));
89151

90-
onData(d, errorData) {
91-
if (this.state === RunnerElement.states.starting)
92-
this.setState(RunnerElement.states.running);
152+
this._runningProccess.on('data', (d) => {
153+
this._terminalProcess.write(d);
154+
});
155+
}
93156

94-
const el = document.createElement('span');
95-
const terminal = this.shadowRoot.querySelector('.terminal');
157+
clean() {
158+
if (this.state === RunnerElement.states.running || this.state === RunnerElement.states.starting)
159+
return;
96160

97-
el.classList.add('log-item');
161+
this._runningProccess = startCleanProcess(this.cwd);
98162

99-
if (errorData)
100-
el.classList.add('error');
101-
102-
el.textContent = d;
163+
this._runningProccess.on('exit', () => {
164+
this.setState(RunnerElement.states.stopped);
103165

104-
terminal.appendChild(el);
166+
this._runningProccess = undefined;
167+
});
105168

106-
terminal.scrollTop = terminal.scrollHeight;
169+
this._runningProccess.on('data', (d) => {
170+
this.setState(RunnerElement.states.running);
171+
this._terminalProcess.write(d);
172+
});
107173
}
108174

109175
clearData() {
110-
const terminal = this.shadowRoot.querySelector('.terminal');
111-
112-
while(terminal.firstChild) {
113-
terminal.removeChild(terminal.firstChild);
114-
}
176+
this._terminalProcess.clear();
115177
}
116178

117179
onTerminate() {
@@ -136,6 +198,7 @@ module.exports = class RunnerElement extends WebComponentBase {
136198

137199
setState(state) {
138200
this._disableAction();
201+
this._disableClean();
139202

140203
this.state = state;
141204

@@ -166,6 +229,7 @@ module.exports = class RunnerElement extends WebComponentBase {
166229
stateEl.textContent = 'Stopped';
167230
stateEl.className = 'state badge badge-secondary';
168231
this._enableAction();
232+
this._enableClean();
169233
actionBtn.textContent = 'Start';
170234
break;
171235
}

app/Components/runner/runner.css

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,42 @@ span.badge {
99
padding: 5px;
1010
}
1111

12-
.terminal {
13-
background-color: rgb(0, 36, 81);
14-
color: #cccccc;
15-
white-space: pre-line;
12+
:host(:hover) .full-screen-toggle {
13+
opacity: 1;
14+
}
15+
16+
.terminals {
1617
border: 1px solid rgba(128, 128, 128, 0.35);
17-
border-radius: 4px;
18-
padding: 5px;
19-
word-break: break-word;
20-
max-height: 200px;
21-
overflow: auto;
22-
flex: 1;
2318
}
2419

2520
.log-item.error {
2621
color: red;
27-
}
22+
}
23+
24+
.terminals.full-screen {
25+
position: absolute;
26+
top: 0px;
27+
bottom: 0px;
28+
left: 0px;
29+
right: 0px;
30+
z-index: 200;
31+
}
32+
33+
.terminals.full-screen::before {
34+
content: 'Close';
35+
display: block;
36+
position: absolute;
37+
top: 0px;
38+
z-index: 5;
39+
right: 20px;
40+
color: white;
41+
cursor: pointer;
42+
border-radius: 3px;
43+
border: 1px solid white;
44+
padding: 5px;
45+
margin: 3px;
46+
}
47+
48+
.terminals {
49+
position: relative;
50+
}

app/Components/runner/runner.html

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
11
<link rel="stylesheet" href="../../../node_modules/bootstrap-css-only/css/bootstrap.css">
2+
<link rel="stylesheet" href="../../../node_modules/xterm/dist/xterm.css" />
3+
<link rel="stylesheet" href="../../../node_modules/xterm/lib/addons/fullscreen/fullscreen.css" />
4+
<!--
5+
This ensures the style is applied when xterm attempts to check size of terminal container.
6+
-->
7+
<style>
8+
.terminals:not(.full-screen) {
9+
max-height: 200px;
10+
}
11+
</style>
212
<div class="d-flex align-items-center justify-content-between flex-row">
313
<h1 class="name"></h1>
414

515
<div class="btn-group">
616
<button class="action btn btn-sm btn-primary">Start</button>
17+
<button class="full btn btn-sm btn-dark">Full Screen</button>
718
<drop-down class="group-left">
19+
<button class="clean btn-sm">Clean</button>
820
<button class="clear-log btn-sm">Clear Log</button>
921
</drop-down>
1022
</div>
1123
</div>
1224
<div class="d-flex justify-content-between align-items-center pb-2">
1325
<span class="state badge"></span>
1426
</div>
15-
<div class="terminal"></div>
27+
<div class="terminals">
28+
</div>

app/DotnetRunnerApp.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const { BrowserWindow, Menu } = require('electron');
1+
const { BrowserWindow, Menu, ipcMain } = require('electron');
22
const { getApplications } = require('./data/applicationStore');
33
const { shell } = require('electron');
44
const ipcMessages = require('./ipcMessages');
@@ -82,27 +82,33 @@ module.exports = class DotnetRunnerApp {
8282
{
8383
label: 'Options',
8484
submenu: [{
85+
id: 'config-apps',
8586
label: 'Configure Applicaions',
8687
click: this._preferencesOnCLick.bind(this)
8788
}, {
8889
label: 'Preferences',
8990
click: this._displayPreferences.bind(this)
9091
}]
9192
}, {
93+
id: 'tasks',
9294
label: 'Tasks',
9395
submenu: [{
96+
id: 'start-all',
9497
label: 'Start all',
9598
accelerator: 'Ctrl+s',
9699
click: this._startAllApps.bind(this)
97100
}, {
101+
id: 'stop-all',
98102
label: 'Stop all',
99103
accelerator: 'Ctrl+Shift+S',
100104
click: this._stopAllApps.bind(this)
101105
}, {
106+
id: 'clear-all',
102107
label: 'Clear all',
103108
accelerator: 'Ctrl+Shift+C',
104109
click: this._clearAllApps.bind(this)
105110
}, {
111+
id: 'purge-all',
106112
label: 'Purge',
107113
accelerator: 'Ctrl+Shift+P',
108114
click: this._purge.bind(this)
@@ -142,15 +148,24 @@ module.exports = class DotnetRunnerApp {
142148
}
143149

144150
_startAllApps() {
151+
this._disableTasks();
152+
this._disableConfigAppMenu();
145153
this._sendMessage(ipcMessages.startAllApplications);
154+
ipcMain.once(ipcMessages.taskComplete, this._onTaskComplete.bind(this));
146155
}
147156

148157
_stopAllApps() {
158+
this._disableTasks();
159+
this._disableConfigAppMenu();
149160
this._sendMessage(ipcMessages.stopAllApplications);
161+
ipcMain.once(ipcMessages.taskComplete, this._onTaskComplete.bind(this));
150162
}
151163

152164
_clearAllApps() {
165+
this._disableTasks();
166+
this._disableConfigAppMenu();
153167
this._sendMessage(ipcMessages.clearAllApplicationLogs);
168+
ipcMain.once(ipcMessages.taskComplete, this._onTaskComplete.bind(this));
154169
}
155170

156171
_purge() {
@@ -161,6 +176,43 @@ module.exports = class DotnetRunnerApp {
161176
this._mainWindow.send(message);
162177
}
163178

179+
_disableTasks() {
180+
const menu = this._menu.getApplicationMenu().getMenuItemById('tasks');
181+
182+
if (!menu || !menu.submenu || !menu.submenu.items)
183+
return;
184+
185+
menu.submenu.items.forEach(x => x.enabled = false);
186+
}
187+
188+
_enableTasks() {
189+
const menu = this._menu.getApplicationMenu().getMenuItemById('tasks');
190+
191+
if (!menu || !menu.submenu || !menu.submenu.items)
192+
return;
193+
194+
menu.submenu.items.forEach(x => x.enabled = true);
195+
}
196+
197+
_disableConfigAppMenu() {
198+
const menu = this._menu.getApplicationMenu().getMenuItemById('config-apps') || {};
199+
200+
menu.enabled = false;
201+
}
202+
203+
_enableConfigAppMenu() {
204+
const menu = this._menu.getApplicationMenu().getMenuItemById('config-apps') || {};
205+
206+
menu.enabled = true;
207+
}
208+
209+
_onTaskComplete(e, task) {
210+
if (task === ipcMessages.startAllApplications || task === ipcMessages.stopAllApplications || task === ipcMessages.clearAllApplicationLogs)
211+
this._enableConfigAppMenu();
212+
213+
this._enableTasks();
214+
}
215+
164216
/**
165217
* @returns {Menu}
166218
*/

app/SplashScreenApp.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ module.exports = class SplashScreenApp {
7272
show: false
7373
});
7474

75-
7675
this._splashWindow
7776
.loadFile('app/browserWindows/splashScreen/splashScreen.html');
7877

0 commit comments

Comments
 (0)