Monorepo for Aesthetic.Computer aesthetic.computer
at main 166 lines 5.1 kB view raw
1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Aesthetic Computer Shell</title> 7 <link rel="stylesheet" href="../node_modules/@xterm/xterm/css/xterm.css"> 8 <style> 9 * { margin: 0; padding: 0; box-sizing: border-box; } 10 html, body { 11 width: 100%; 12 height: 100%; 13 overflow: hidden; 14 background: #0a0a12; 15 } 16 #terminal-container { 17 width: 100%; 18 height: calc(100% - 64px); /* Leave space at top */ 19 margin-top: 64px; /* Push terminal down */ 20 padding: 8px; 21 } 22 .xterm { height: 100%; } 23 .xterm-viewport { padding-top: 0 !important; } 24 </style> 25</head> 26<body> 27 <div id="terminal-container"></div> 28 29 <script> 30 // With nodeIntegration enabled, we can use require() directly 31 const { Terminal } = require('@xterm/xterm'); 32 const { FitAddon } = require('@xterm/addon-fit'); 33 const { WebglAddon } = require('@xterm/addon-webgl'); 34 const { ipcRenderer } = require('electron'); 35 36 const terminalContainer = document.getElementById('terminal-container'); 37 38 // Initialize terminal with performance optimizations 39 const term = new Terminal({ 40 cursorBlink: true, 41 cursorStyle: 'block', 42 fontSize: 14, 43 fontFamily: "'SF Mono', 'Fira Code', 'Cascadia Code', monospace", 44 theme: { 45 background: '#0a0a12', 46 foreground: '#eee', 47 cursor: '#f0f', 48 cursorAccent: '#0a0a12', 49 selectionBackground: 'rgba(255, 0, 255, 0.3)', 50 black: '#1a1a2e', 51 red: '#ff5555', 52 green: '#50fa7b', 53 yellow: '#f1fa8c', 54 blue: '#6272a4', 55 magenta: '#ff79c6', 56 cyan: '#8be9fd', 57 white: '#f8f8f2', 58 }, 59 allowTransparency: false, // Disabled for WebGL performance 60 scrollback: 5000, 61 fastScrollModifier: 'alt', 62 fastScrollSensitivity: 5, 63 }); 64 65 const fitAddon = new FitAddon(); 66 term.loadAddon(fitAddon); 67 term.open(terminalContainer); 68 69 // Load WebGL addon for GPU-accelerated rendering 70 try { 71 const webglAddon = new WebglAddon(); 72 webglAddon.onContextLoss(() => { 73 webglAddon.dispose(); 74 }); 75 term.loadAddon(webglAddon); 76 console.log('[shell] WebGL renderer enabled'); 77 } catch (e) { 78 console.warn('[shell] WebGL not available, using canvas renderer'); 79 } 80 81 // Fit after a short delay to ensure container is sized 82 setTimeout(() => fitAddon.fit(), 50); 83 84 // Resize handling 85 const resizeObserver = new ResizeObserver(() => { 86 fitAddon.fit(); 87 ipcRenderer.send('pty-resize', term.cols, term.rows); 88 }); 89 resizeObserver.observe(terminalContainer); 90 91 // Connect to PTY 92 async function initShell() { 93 try { 94 // Check Docker 95 const dockerOk = await ipcRenderer.invoke('check-docker'); 96 if (!dockerOk) { 97 term.writeln('\x1b[31mError: Docker not running\x1b[0m'); 98 return; 99 } 100 101 // Check if container is running 102 const containerRunning = await ipcRenderer.invoke('check-container'); 103 if (!containerRunning) { 104 // Check if container exists but is stopped 105 const containerExists = await ipcRenderer.invoke('check-container-exists'); 106 if (containerExists) { 107 term.writeln('\x1b[33mStarting existing container...\x1b[0m'); 108 await ipcRenderer.invoke('start-existing-container'); 109 } else { 110 term.writeln('\x1b[33mCreating devcontainer...\x1b[0m'); 111 await ipcRenderer.invoke('start-container'); 112 } 113 } 114 115 // Connect PTY 116 const connected = await ipcRenderer.invoke('connect-pty'); 117 if (!connected) { 118 term.writeln('\x1b[31mFailed to connect to shell\x1b[0m'); 119 return; 120 } 121 122 // PTY data → terminal 123 ipcRenderer.on('pty-data', (event, data) => { 124 term.write(data); 125 }); 126 127 // Terminal input → PTY 128 term.onData((data) => { 129 ipcRenderer.send('pty-input', data); 130 }); 131 132 // PTY exit 133 ipcRenderer.on('pty-exit', (event, code) => { 134 term.writeln(`\r\n\x1b[33mShell exited with code ${code}\x1b[0m`); 135 }); 136 137 // Send initial resize 138 ipcRenderer.send('pty-resize', term.cols, term.rows); 139 140 // Auto-start emacs after a brief delay for shell to initialize 141 setTimeout(() => { 142 ipcRenderer.send('pty-input', 'ac-aesthetic\n'); 143 }, 500); 144 145 } catch (err) { 146 term.writeln(`\x1b[31mError: ${err.message}\x1b[0m`); 147 } 148 } 149 150 // Expose reboot function globally for easy access 151 window.rebootApp = async () => { 152 console.log('Requesting app reboot...'); 153 try { 154 const result = await ipcRenderer.invoke('app-reboot'); 155 console.log('Reboot initiated:', result); 156 return result; 157 } catch (err) { 158 console.error('Reboot failed:', err); 159 throw err; 160 } 161 }; 162 163 initShell(); 164 </script> 165</body> 166</html>