To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit 3cb95dc2 authored by kchung's avatar kchung 🚴🏻
Browse files

FEA: added integrated terminal

parent 59178a0f
......@@ -28,3 +28,9 @@ npm run dev
```
Now, you can access to your slidese from your browser on localhost, port 3000 (localhost:3000)
## Integrate terminal inside of the slides
You can add terminal inside of the slides. To accomplish this, you need to install dependencies in `ifd-theme/terminal/client` and `ifd-theme/terminal/server` by running ```npm install```.
Then, before using terminal, please run the server side application using ```npm start```
// TerminalUI.js
import { Terminal } from "xterm";
import "xterm/css/xterm.css";
export class TerminalUI {
constructor(socket) {
this.terminal = new Terminal();
/* You can make your terminals colorful :) */
this.terminal.setOption("theme", {
background: "#202B33",
foreground: "#F5F8FA"
});
this.socket = socket;
}
/**
* Attach event listeners for terminal UI and socket.io client
*/
startListening() {
this.terminal.onData(data => this.sendInput(data));
this.socket.on("output", data => {
// When there is data from PTY on server, print that on Terminal.
this.write(data);
});
}
/**
* Print something to terminal UI.
*/
write(text) {
this.terminal.write(text);
}
/**
* Utility function to print new line on terminal.
*/
prompt() {
this.terminal.write(`\r\n$ `);
}
/**
* Send whatever you type in Terminal UI to PTY process in server.
* @param {*} input Input to send to server
*/
sendInput(input) {
this.socket.emit("input", input);
}
/**
*
* @param {HTMLElement} container HTMLElement where xterm can attach terminal ui instance.
*/
attachTo(container) {
this.terminal.open(container);
// Default text to display on terminal.
//this.terminal.write("Terminal Connected");
//this.terminal.write("");
//this.prompt();
}
clear() {
this.terminal.clear();
}
}
// index.js
import { TerminalUI } from "./TerminalUI";
import io from "socket.io-client";
// IMPORTANT: Make sure you replace this address with your server address.
const serverAddress = "http://localhost:8080";
//Server sandbox available at https://codesandbox.io/s/web-terminal-tutorial-server-g2ihu
function connectToSocket(serverAddress) {
return new Promise(res => {
const socket = io(serverAddress);
res(socket);
});
}
function startTerminal(container, socket) {
// Create an xterm.js instance (TerminalUI class is a wrapper with some utils. Check that file for info.)
const terminal = new TerminalUI(socket);
console.log(terminal);
// Attach created terminal to a DOM element.
terminal.attachTo(container);
// When terminal attached to DOM, start listening for input, output events.
// Check TerminalUI startListening() function for details.
terminal.startListening();
}
function start() {
const container = document.getElementById("terminal-container");
// Connect to socket and when it is available, start terminal.
connectToSocket(serverAddress).then(socket => {
startTerminal(container, socket);
});
}
// Better to start on DOMContentLoaded. So, we know terminal-container is loaded
start();
This diff is collapsed.
{
"name": "web-terminal-turorial-client",
"version": "1.0.0",
"description": "",
"main": "index.html",
"scripts": {
"start": "parcel index.html --open",
"build": "parcel build index.html"
},
"dependencies": {
"socket.io-client": "2.3.0",
"xterm": "4.2.0"
},
"devDependencies": {
"@babel/core": "7.2.0",
"parcel-bundler": "^1.6.1"
},
"keywords": []
}
body {
font-family: sans-serif;
}
// PTYService.js
const os = require("os");
const pty = require("node-pty");
class PTY {
constructor(socket) {
// Setting default terminals based on user os
this.shell = os.platform() === "win32" ? "powershell.exe" : "bash";
this.ptyProcess = null;
this.socket = socket;
// Initialize PTY process.
this.startPtyProcess();
}
/**
* Spawn an instance of pty with a selected shell.
*/
startPtyProcess() {
this.ptyProcess = pty.spawn(this.shell, [], {
name: "xterm-color",
cwd: './', // Which path should terminal start
env: process.env // Pass environment variables
});
// Add a "data" event listener.
this.ptyProcess.on("data", data => {
// Whenever terminal generates any data, send that output to socket.io client to display on UI
this.sendToClient(data);
});
}
/**
* Use this function to send in the input to Pseudo Terminal process.
* @param {*} data Input from user like command sent from terminal UI
*/
write(data) {
this.ptyProcess.write(data);
}
sendToClient(data) {
// Emit data to socket.io client in an event "output"
this.socket.emit("output", data);
}
}
module.exports = PTY;
// SocketService.js
// Manage Socket.IO server
const socketIO = require("socket.io");
const PTYService = require("./PTYService");
class SocketService {
constructor() {
this.socket = null;
this.pty = null;
}
attachServer(server) {
if (!server) {
throw new Error("Server not found...");
}
const io = socketIO(server);
console.log("Created socket server. Waiting for client connection.");
// "connection" event happens when any client connects to this io instance.
io.on("connection", socket => {
console.log("Client connect to socket.", socket.id);
this.socket = socket;
// Just logging when socket disconnects.
this.socket.on("disconnect", () => {
console.log("Disconnected Socket: ", socket.id);
});
// Create a new pty service when client connects.
this.pty = new PTYService(this.socket);
// Attach any event listeners which runs if any event is triggered from socket.io client
// For now, we are only adding "input" event, where client sends the strings you type on terminal UI.
this.socket.on("input", input => {
//Runs this event function socket receives "input" events from socket.io client
this.pty.write(input);
});
});
}
}
module.exports = SocketService;
// index.js
const http = require("http");
const SocketService = require("./SocketService");
/*
Create Server from http module.
If you use other packages like express, use something like,
const app = require("express")();
const server = require("http").Server(app);
*/
const server = http.createServer((req, res) => {
res.write("Terminal Server Running.");
res.end();
});
const port = 8080;
server.listen(port, function() {
console.log("Server listening on : ", port);
const socketService = new SocketService();
socketService.attachServer(server);
});
This diff is collapsed.
{
"name": "web-terminal-tutorial-server",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "nodemon index.js localhost 8080"
},
"dependencies": {
"node-pty": "0.9.0",
"socket.io": "2.3.0"
},
"devDependencies": {
"nodemon": "1.18.4"
},
"keywords": []
}
......@@ -57,7 +57,7 @@
<h1>Slide with Logos at the Top</h1>
<div class="description">
<hr/>
Descriptions
<div id="terminal-container"></div>
</div>
</div>
</section>
......@@ -73,5 +73,6 @@
</div>
</div>
<script type="module" src="/main.js"></script>
<script type="module" src="./ifd-theme/terminal/client/index.js"></script>
</body>
</html>
{
"name": "reveal-vite",
"version": "0.0.0",
"lockfileVersion": 2,
"lockfileVersion": 1,
"requires": true,
"packages": {
"": {
"version": "0.0.0",
"devDependencies": {
"reveal.js": "^4.1.0",
"vite": "^2.1.5"
}
},
"node_modules/colorette": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
"dev": true
},
"node_modules/esbuild": {
"version": "0.9.7",
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.9.7.tgz",
"integrity": "sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
"dev": true
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/is-core-module": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz",
"integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==",
"dev": true,
"dependencies": {
"has": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/nanoid": {
"version": "3.1.22",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.22.tgz",
"integrity": "sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==",
"dev": true,
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/path-parse": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
"integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
"dev": true
},
"node_modules/postcss": {
"version": "8.2.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.2.12.tgz",
"integrity": "sha512-BJnGT5+0q2tzvs6oQfnY2NpEJ7rIXNfBnZtQOKCIsweeWXBXeDd5k31UgTdS3d/c02ouspufn37mTaHWkJyzMQ==",
"dev": true,
"dependencies": {
"colorette": "^1.2.2",
"nanoid": "^3.1.22",
"source-map": "^0.6.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
}
},
"node_modules/resolve": {
"version": "1.20.0",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz",
"integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==",
"dev": true,
"dependencies": {
"is-core-module": "^2.2.0",
"path-parse": "^1.0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/reveal.js": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/reveal.js/-/reveal.js-4.1.0.tgz",
"integrity": "sha512-lYOMhxSWgq5jtgspF7eRL0d2rBnHO/VGZ4/qp46yu+eGoWqJkYHEuepEXzx71M8MI6Rf8HBYWaTnKi5uHWcU1Q==",
"dev": true,
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/rollup": {
"version": "2.45.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.45.2.tgz",
"integrity": "sha512-kRRU7wXzFHUzBIv0GfoFFIN3m9oteY4uAsKllIpQDId5cfnkWF2J130l+27dzDju0E6MScKiV0ZM5Bw8m4blYQ==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.1"
}
},
"node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/vite": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/vite/-/vite-2.2.1.tgz",
"integrity": "sha512-KIqK90EoJJpuqE86Y9DSkZjFNGgsyZX/4I1xENIitLRd3hgRtOlIGCJYrNnBD9/eqipz0OroAiIj9/R1JcOdFA==",
"dev": true,
"dependencies": {
"esbuild": "^0.9.3",
"postcss": "^8.2.1",
"resolve": "^1.19.0",
"rollup": "^2.38.5"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": ">=12.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.1"
}
}
},
"dependencies": {
"colorette": {
"version": "1.2.2",
......
export default {
resolve: {
alias: {
"socket.io-client": "socket.io-client/dist/socket.io.js",
},
},
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment