| | <!DOCTYPE html> |
| | <html lang="en"> |
| | <head> |
| | <meta charset="utf-8" /> |
| | <meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
| | <title>Enhanced Terminal</title> |
| | <link rel="stylesheet" href="https://unpkg.com/xterm@4.11.0/css/xterm.css" /> |
| | <style> |
| | html { |
| | font-family: Arial, sans-serif; |
| | } |
| | body { |
| | background-color: #1e1e1e; |
| | color: white; |
| | margin: 0; |
| | padding: 0; |
| | display: flex; |
| | flex-direction: column; |
| | height: 100vh; |
| | } |
| | #status-bar { |
| | background-color: #333; |
| | color: #fff; |
| | padding: 5px 10px; |
| | display: flex; |
| | justify-content: space-between; |
| | align-items: center; |
| | font-size: small; |
| | } |
| | #terminal-container { |
| | flex: 1; |
| | display: flex; |
| | flex-direction: column; |
| | padding: 10px; |
| | } |
| | #terminal { |
| | flex: 1; |
| | width: 100%; |
| | height: 100%; |
| | border: 1px solid #444; |
| | border-radius: 5px; |
| | overflow: hidden; |
| | } |
| | |
| | ::-webkit-scrollbar { |
| | width: 12px; |
| | height: 12px; |
| | } |
| | ::-webkit-scrollbar-track { |
| | background: #2e2e2e; |
| | border-radius: 10px; |
| | } |
| | ::-webkit-scrollbar-thumb { |
| | background-color: #555; |
| | border-radius: 10px; |
| | border: 3px solid #2e2e2e; |
| | } |
| | ::-webkit-scrollbar-thumb:hover { |
| | background-color: #777; |
| | } |
| | .link-container { |
| | text-align: right; |
| | font-size: small; |
| | margin-top: 5px; |
| | } |
| | .link-container a { |
| | color: #1e90ff; |
| | text-decoration: none; |
| | } |
| | .link-container a:hover { |
| | text-decoration: underline; |
| | } |
| | </style> |
| | </head> |
| | <body> |
| | <div id="status-bar"> |
| | <span>Status: <span id="status">connecting...</span></span> |
| | <span id="resize-status"></span> |
| | </div> |
| | <div id="terminal-container"> |
| | <div id="terminal"></div> |
| | <div class="link-container"> |
| | <a href="https://www.youtube.com/watch?v=o-YBDTqX_ZU">Get Rickrolled</a> | |
| | <a href="https://github.com">GitHub</a> |
| | </div> |
| | </div> |
| | <script src="https://unpkg.com/xterm@4.11.0/lib/xterm.js"></script> |
| | <script src="https://unpkg.com/xterm-addon-fit@0.5.0/lib/xterm-addon-fit.js"></script> |
| | <script src="https://unpkg.com/xterm-addon-web-links@0.4.0/lib/xterm-addon-web-links.js"></script> |
| | <script src="https://unpkg.com/xterm-addon-search@0.8.0/lib/xterm-addon-search.js"></script> |
| | <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.min.js"></script> |
| | <script> |
| | const term = new Terminal({ |
| | cursorBlink: true, |
| | macOptionIsMeta: true, |
| | scrollback: true, |
| | }); |
| | const fit = new FitAddon.FitAddon(); |
| | term.loadAddon(fit); |
| | term.loadAddon(new WebLinksAddon.WebLinksAddon()); |
| | term.loadAddon(new SearchAddon.SearchAddon()); |
| | |
| | term.open(document.getElementById("terminal")); |
| | fit.fit(); |
| | term.resize(15, 50); |
| | console.log(`size: ${term.cols} columns, ${term.rows} rows`); |
| | fit.fit(); |
| | term.writeln("Welcome to AR-server"); |
| | term.writeln(""); |
| | term.writeln("You can copy with ctrl+shift+x"); |
| | term.writeln("You can paste with ctrl+shift+v"); |
| | term.writeln(''); |
| | |
| | term.onData((data) => { |
| | console.log("browser terminal received new data:", data); |
| | socket.emit("pty-input", { input: data }); |
| | }); |
| | |
| | const socket = io.connect("/pty"); |
| | const status = document.getElementById("status"); |
| | |
| | socket.on("pty-output", function (data) { |
| | console.log("new output received from server:", data.output); |
| | term.write(data.output); |
| | }); |
| | |
| | socket.on("connect", () => { |
| | fitToscreen(); |
| | status.innerHTML = '<span style="background-color: lightgreen; padding: 2px 4px; border-radius: 3px;">connected</span>'; |
| | }); |
| | |
| | socket.on("disconnect", () => { |
| | status.innerHTML = '<span style="background-color: #ff8383; padding: 2px 4px; border-radius: 3px;">disconnected</span>'; |
| | }); |
| | |
| | function fitToscreen() { |
| | fit.fit(); |
| | const dims = { cols: term.cols, rows: term.rows }; |
| | console.log("sending new dimensions to server's pty", dims); |
| | socket.emit("resize", dims); |
| | document.getElementById("resize-status").textContent = `Resized to: ${dims.cols}x${dims.rows}`; |
| | } |
| | |
| | function debounce(func, wait_ms) { |
| | let timeout; |
| | return function (...args) { |
| | const context = this; |
| | clearTimeout(timeout); |
| | timeout = setTimeout(() => func.apply(context, args), wait_ms); |
| | }; |
| | } |
| | |
| | |
| | |
| | |
| | function customKeyEventHandler(e) { |
| | if (e.type !== "keydown") { |
| | return true; |
| | } |
| | if (e.ctrlKey && e.shiftKey) { |
| | const key = e.key.toLowerCase(); |
| | if (key === "v") { |
| | |
| | navigator.clipboard.readText().then((toPaste) => { |
| | socket.emit("pty-input", { input: toPaste }); |
| | }); |
| | |
| | |
| | e.preventDefault(); |
| | |
| | return false; |
| | } else if (key === "c" || key === "x") { |
| | |
| | const toCopy = term.getSelection(); |
| | if (toCopy) { |
| | navigator.clipboard.writeText(toCopy).then(() => { |
| | term.focus(); |
| | }); |
| | } |
| | return false; |
| | } |
| | } |
| | return true; |
| | } |
| | |
| | const wait_ms = 50; |
| | window.onresize = debounce(fitToscreen, wait_ms); |
| | term.attachCustomKeyEventHandler(customKeyEventHandler); |
| | </script> |
| | </body> |
| | </html> |
| |
|