| <!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> |
|
|