Skip to content
This repository was archived by the owner on Oct 22, 2021. It is now read-only.

Commit bc66a57

Browse files
committed
✨ Feat: Terminal tabs
See #260
1 parent 790fe97 commit bc66a57

File tree

6 files changed

+207
-45
lines changed

6 files changed

+207
-45
lines changed

src/_boot.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ app.on('ready', () => {
163163
};
164164
tty.ondisconnected = () => {
165165
signale.error("Lost connection to frontend");
166+
Object.keys(extraTtys).forEach(key => {
167+
if (extraTtys[key] !== null) {
168+
extraTtys[key].close();
169+
extraTtys[key] = null;
170+
}
171+
});
166172
signale.watch("Waiting for frontend connection...");
167173
};
168174

@@ -213,7 +219,8 @@ app.on('ready', () => {
213219
});
214220
signale.success(`New terminal back-end initialized at ${port}`);
215221
term.onclosed = (code, signal) => {
216-
tty.ondisconnected = () => {};
222+
term.ondisconnected = () => {};
223+
term.wss.close();
217224
signale.complete(`TTY exited at ${port}`, code, signal);
218225
extraTtys[term.port] = null;
219226
delete term;

src/_renderer.js

Lines changed: 105 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ initUI = () => {
169169
document.body.innerHTML += `<section class="mod_column" id="mod_column_left">
170170
<h3 class="title"><p>PANEL</p><p>SYSTEM</p></h3>
171171
</section>
172-
<section id="main_shell" class="greeting" style="height:0%;width:0%;opacity:0;margin-bottom:30vh;">
172+
<section id="main_shell" style="height:0%;width:0%;opacity:0;margin-bottom:30vh;">
173173
<h3 class="title" style="opacity:0;"><p>TERMINAL</p><p>MAIN SHELL</p></h3>
174174
<h1 id="main_shell_greeting"></h1>
175175
</section>
@@ -272,15 +272,32 @@ initGreeter = () => {
272272
setTimeout(() => {
273273
greeter.remove();
274274
setTimeout(() => {
275-
shellContainer.innerHTML += `<pre id="terminal"></pre>`;
276-
window.term = new Terminal({
277-
role: "client",
278-
parentId: "terminal",
279-
port: window.settings.port || 3000
280-
});
275+
shellContainer.innerHTML += `
276+
<ul id="main_shell_tabs">
277+
<li id="shell_tab0" onclick="window.focusShellTab(0);" class="active">MAIN SHELL</li>
278+
<li id="shell_tab1" onclick="window.focusShellTab(1);">EMPTY</li>
279+
<li id="shell_tab2" onclick="window.focusShellTab(2);">EMPTY</li>
280+
<li id="shell_tab3" onclick="window.focusShellTab(3);">EMPTY</li>
281+
<li id="shell_tab4" onclick="window.focusShellTab(4);">EMPTY</li>
282+
</ul>
283+
<div id="main_shell_innercontainer">
284+
<pre id="terminal0" class="active"></pre>
285+
<pre id="terminal1"></pre>
286+
<pre id="terminal2"></pre>
287+
<pre id="terminal3"></pre>
288+
<pre id="terminal4"></pre>
289+
</div>`;
290+
window.term = {
291+
0: new Terminal({
292+
role: "client",
293+
parentId: "terminal0",
294+
port: window.settings.port || 3000
295+
})
296+
};
297+
window.currentTerm = 0;
281298
// Prevent losing hardware keyboard focus on the terminal when using touch keyboard
282299
window.onmouseup = (e) => {
283-
window.term.term.focus();
300+
window.term[window.currentTerm].term.focus();
284301
};
285302

286303
window.fsDisp = new FilesystemDisplay({
@@ -298,6 +315,8 @@ initGreeter = () => {
298315
};
299316

300317
window.themeChanger = (theme) => {
318+
window.focusShellTab(0);
319+
301320
let src = path.join(themesDir, theme+".json" || settings.theme+".json");
302321
// Always get fresh theme files
303322
delete require.cache[src];
@@ -309,30 +328,33 @@ window.themeChanger = (theme) => {
309328
for (let i; i < 99999; i++) {
310329
clearInterval(i);
311330
}
312-
window.term.socket.close();
313-
delete window.term;
331+
window.term[window.currentTerm].socket.close();
332+
delete window.term[window.currentTerm];
314333
delete window.mods;
315334
delete window.fsDisp;
316335

317-
document.getElementById("terminal").innerHTML = "";
336+
document.getElementById("terminal0").innerHTML = "";
318337
document.querySelectorAll(".mod_column").forEach((e) => {
319338
e.setAttribute("class", "mod_column");
320339
});
321340
document.querySelectorAll(".mod_column > div").forEach(e => {e.remove()});
322341
document.querySelectorAll("div.smoothie-chart-tooltip").forEach(e => {e.remove()});
323342

324-
window.term = new Terminal({
325-
role: "client",
326-
parentId: "terminal",
327-
port: window.settings.port || 3000
328-
});
343+
window.term = {
344+
0: new Terminal({
345+
role: "client",
346+
parentId: "terminal0",
347+
port: window.settings.port || 3000
348+
})
349+
};
350+
window.currentTerm = 0;
329351
initMods();
330352
window.fsDisp = new FilesystemDisplay({
331353
parentId: "filesystem"
332354
});
333355

334356
setTimeout(() => {
335-
window.term.fit();
357+
window.term[window.currentTerm].fit();
336358
}, 2700);
337359
};
338360

@@ -344,6 +366,67 @@ window.remakeKeyboard = (layout) => {
344366
});
345367
};
346368

369+
window.focusShellTab = (number) => {
370+
if (number !== window.currentTerm && window.term[number]) {
371+
window.currentTerm = number;
372+
373+
document.querySelectorAll(`ul#main_shell_tabs > li:not(:nth-child(${number+1}))`).forEach(e => {
374+
e.setAttribute("class", "");
375+
});
376+
document.getElementById("shell_tab"+number).setAttribute("class", "active");
377+
378+
document.querySelectorAll(`div#main_shell_innercontainer > pre:not(:nth-child(${number+1}))`).forEach(e => {
379+
e.setAttribute("class", "");
380+
});
381+
document.getElementById("terminal"+number).setAttribute("class", "active");
382+
383+
window.term[number].fit();
384+
window.term[number].term.focus();
385+
window.term[number].resendCWD();
386+
} else if (number > 0 && number <= 4 && window.term[number] !== null) {
387+
window.term[number] = null;
388+
389+
document.getElementById("shell_tab"+number).innerText = "LOADING...";
390+
ipc.send("ttyspawn", "true");
391+
ipc.once("ttyspawn-reply", (e, r) => {
392+
if (r.startsWith("ERROR")) {
393+
document.getElementById("shell_tab"+number).innerText = "ERROR";
394+
} else if (r.startsWith("SUCCESS")) {
395+
let port = Number(r.substr(9));
396+
397+
window.term[number] = new Terminal({
398+
role: "client",
399+
parentId: "terminal"+number,
400+
port
401+
});
402+
403+
window.term[number].onclose = e => {
404+
document.getElementById("shell_tab"+number).innerText = "EMPTY";
405+
document.getElementById("terminal"+number).innerHTML = "";
406+
delete window.term[number];
407+
window.focusShellTab(0);
408+
};
409+
410+
document.getElementById("shell_tab"+number).innerText = "::"+port;
411+
setTimeout(() => {
412+
window.focusShellTab(number);
413+
}, 500);
414+
}
415+
});
416+
}
417+
};
418+
419+
// Global keyboard shortcuts
420+
const globalShortcut = electron.remote.globalShortcut;
421+
globalShortcut.register("CommandOrControl+Tab", () => {
422+
if (window.currentTerm <= 3) {
423+
window.focusShellTab(window.currentTerm+1);
424+
} else {
425+
window.focusShellTab(0);
426+
}
427+
});
428+
429+
347430
// Prevent showing menu, exiting fullscreen or app with keyboard shortcuts
348431
window.onkeydown = e => {
349432
if (e.key === "Alt") {
@@ -362,3 +445,8 @@ window.onkeydown = e => {
362445

363446
// Fix double-tap zoom on touchscreens
364447
require('electron').webFrame.setVisualZoomLevelLimits(1, 1);
448+
449+
// Resize terminal with window
450+
window.onresize = () => {
451+
window.term[window.currentTerm].fit();
452+
}

src/assets/css/main_shell.css

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ section#main_shell {
44
padding: 0.74vh;
55
border: 0.18vh solid rgba(var(--color_r), var(--color_g), var(--color_b), 0.5);
66
border-radius: 0.278vh;
7-
transition: width .5s cubic-bezier(0.4, 0, 1, 1), height .5s cubic-bezier(0.4, 0, 1, 1);
8-
}
97

10-
section#main_shell.greeting {
118
display: flex;
9+
flex-direction: column;
10+
align-items: flex-start;
11+
justify-content: flex-start;
12+
overflow: hidden;
13+
14+
transition: width .5s cubic-bezier(0.4, 0, 1, 1), height .5s cubic-bezier(0.4, 0, 1, 1);
1215
}
1316

1417
h1#main_shell_greeting {
@@ -35,13 +38,65 @@ section#main_shell > h3.title > p {
3538
width: 49.8%;
3639
}
3740

38-
section#main_shell pre {
41+
ul#main_shell_tabs {
42+
margin: 0;
43+
margin-top: -0.70vh;
44+
margin-left: -0.74vh;
45+
padding: 0;
46+
width: calc(100% + 1.48vh);
47+
display: flex;
48+
box-sizing: border-box;
49+
border-bottom: 0.18vh solid rgba(var(--color_r), var(--color_g), var(--color_b), 0.5);
50+
border-top-left-radius: 0.278vh;
51+
border-top-right-radius: 0.278vh;
52+
flex-direction: row;
53+
align-items: center;
54+
justify-content: space-evenly;
55+
flex-wrap: nowrap;
56+
overflow: hidden;
57+
}
58+
59+
ul#main_shell_tabs > li {
60+
cursor: pointer;
61+
display: block;
62+
font-family: var(--font_main);
63+
font-weight: normal;
64+
width: 100%;
65+
padding-top: 0.7vh;
66+
padding-bottom: 0.4vh;
67+
text-align: center;
68+
box-sizing: border-box;
69+
70+
background: var(--color_light_black);
71+
}
72+
ul#main_shell_tabs > li:not(:first-child) {
73+
border-left: 0.18vh solid rgba(var(--color_r), var(--color_g), var(--color_b), 0.5);
74+
}
75+
76+
ul#main_shell_tabs > li.active {
77+
background: rgb(var(--color_r), var(--color_g), var(--color_b));
78+
color: var(--color_light_black);
79+
font-weight: bold;
80+
}
81+
82+
div#main_shell_innercontainer, div#main_shell_innercontainer pre {
3983
height: 100%;
4084
width: 100%;
4185
margin: 0vh;
4286
overflow: hidden;
4387
}
4488

89+
div#main_shell_innercontainer pre {
90+
z-index: -999;
91+
opacity: 0;
92+
position: absolute;
93+
}
94+
95+
div#main_shell_innercontainer pre.active {
96+
z-index: inherit;
97+
opacity: 1;
98+
}
99+
45100
.terminal .xterm-viewport {
46101
overflow: hidden;
47102
cursor: default;

src/classes/filesystem.class.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ class FilesystemDisplay {
5555
<h2 id="fs_disp_error">CANNOT ACCESS CURRENT WORKING DIRECTORY</h2>`;
5656
};
5757

58-
window.term.oncwdchange = (cwd) => {
58+
window.term[window.currentTerm].oncwdchange = (cwd) => {
5959
if (cwd) {
60+
if (this._fsWatcher) {
61+
this._fsWatcher.close();
62+
}
6063
if (cwd.startsWith("FALLBACK |-- ")) {
6164
this.readFS(cwd.slice(13));
6265
this._noTracking = true;
@@ -210,9 +213,9 @@ class FilesystemDisplay {
210213
hidden = " hidden";
211214
}
212215

213-
let cmd = `window.term.write('\\'${e.name}'\\')`;
216+
let cmd = `window.term[window.currentTerm].write('\\'${e.name}'\\')`;
214217
if (e.type === "dir" || e.type === "up" || e.type.endsWith("Dir")) {
215-
cmd = `window.term.writelr('cd \\'${e.name.replace("\\", "\\\\")}\\'')`;
218+
cmd = `window.term[window.currentTerm].writelr('cd \\'${e.name.replace("\\", "\\\\")}\\'')`;
216219
}
217220

218221
if (e.type === "up" && this._noTracking) {
@@ -229,7 +232,7 @@ class FilesystemDisplay {
229232
cmd = `window.remakeKeyboard('${e.name.slice(0, -5)}')`;
230233
}
231234
if (e.type === "edex-settings" && process.env.editor) {
232-
cmd = `window.term.writelr('${process.env.editor} \\'${e.name.slice(0, -5)}\\'')`;
235+
cmd = `window.term[window.currentTerm].writelr('${process.env.editor} \\'${e.name.slice(0, -5)}\\'')`;
233236
}
234237

235238
let icon = "";

src/classes/keyboard.class.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -195,13 +195,13 @@ class Keyboard {
195195
break;
196196
}
197197
} else if (cmd === "\n") {
198-
term.writelr("");
199-
} else if (cmd === ctrlseq[19] && window.term.term.hasSelection()) {
200-
window.term.clipboard.copy();
201-
} else if (cmd === ctrlseq[20] && window.term.clipboard.didCopy) {
202-
window.term.clipboard.paste();
198+
window.term[window.currentTerm].writelr("");
199+
} else if (cmd === ctrlseq[19] && window.term[window.currentTerm].term.hasSelection()) {
200+
window.term[window.currentTerm].clipboard.copy();
201+
} else if (cmd === ctrlseq[20] && window.term[window.currentTerm].clipboard.didCopy) {
202+
window.term[window.currentTerm].clipboard.paste();
203203
} else {
204-
term.write(cmd);
204+
window.term[window.currentTerm].write(cmd);
205205
}
206206
};
207207

@@ -226,7 +226,7 @@ class Keyboard {
226226
});
227227

228228
// Keep focus on the terminal
229-
term.term.focus();
229+
window.term[window.currentTerm].term.focus();
230230
e.preventDefault();
231231
};
232232
key.onmouseup = () => {
@@ -265,7 +265,7 @@ class Keyboard {
265265
pressKey(key);
266266

267267
// Keep focus on the terminal
268-
term.term.focus();
268+
window.term[window.currentTerm].term.focus();
269269
e.preventDefault();
270270
};
271271
key.onmouseup = (e) => {
@@ -291,7 +291,7 @@ class Keyboard {
291291
}, 100);
292292
};
293293
}
294-
294+
295295
// See #229
296296
key.onmouseleave = () => {
297297
clearTimeout(key.holdTimeout);

0 commit comments

Comments
 (0)