fleetcode/renderer.ts
Amrit Subramanian 66e0c96b75 refactor styles
2025-10-02 18:33:22 -04:00

201 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { ipcRenderer } from "electron";
import { Terminal } from "xterm";
import { FitAddon } from "xterm-addon-fit";
interface Session {
id: string;
terminal: Terminal;
fitAddon: FitAddon;
element: HTMLDivElement;
name: string;
}
const sessions = new Map<string, Session>();
let activeSessionId: string | null = null;
function createSession(sessionId: string, name: string) {
const term = new Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
theme: {
background: "#000000",
foreground: "#ffffff",
},
});
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
const sessionElement = document.createElement("div");
sessionElement.className = "session-wrapper";
sessionElement.id = `session-${sessionId}`;
const container = document.getElementById("session-container");
if (container) {
container.appendChild(sessionElement);
}
term.open(sessionElement);
fitAddon.fit();
term.onData((data) => {
ipcRenderer.send("session-input", sessionId, data);
});
const session: Session = {
id: sessionId,
terminal: term,
fitAddon,
element: sessionElement,
name,
};
sessions.set(sessionId, session);
// Add to sidebar
addToSidebar(sessionId, name);
// Add tab
addTab(sessionId, name);
// Switch to this session
switchToSession(sessionId);
// Handle resize
const resizeHandler = () => {
if (activeSessionId === sessionId) {
fitAddon.fit();
ipcRenderer.send("session-resize", sessionId, term.cols, term.rows);
}
};
window.addEventListener("resize", resizeHandler);
return session;
}
function addToSidebar(sessionId: string, name: string) {
const list = document.getElementById("session-list");
if (!list) return;
const item = document.createElement("div");
item.id = `sidebar-${sessionId}`;
item.className = "session-list-item";
item.innerHTML = `
<span class="truncate">${name}</span>
<button class="session-close-btn" data-id="${sessionId}">×</button>
`;
item.addEventListener("click", (e) => {
if (!(e.target as HTMLElement).classList.contains("session-close-btn")) {
switchToSession(sessionId);
}
});
const closeBtn = item.querySelector(".session-close-btn");
closeBtn?.addEventListener("click", (e) => {
e.stopPropagation();
closeSession(sessionId);
});
list.appendChild(item);
}
function addTab(sessionId: string, name: string) {
const tabsContainer = document.getElementById("tabs");
if (!tabsContainer) return;
const tab = document.createElement("div");
tab.id = `tab-${sessionId}`;
tab.className = "tab";
tab.innerHTML = `
<span class="tab-name">${name}</span>
<button class="tab-close-btn" data-id="${sessionId}">×</button>
`;
tab.addEventListener("click", (e) => {
if (!(e.target as HTMLElement).classList.contains("tab-close-btn")) {
switchToSession(sessionId);
}
});
const closeBtn = tab.querySelector(".tab-close-btn");
closeBtn?.addEventListener("click", (e) => {
e.stopPropagation();
closeSession(sessionId);
});
tabsContainer.appendChild(tab);
}
function switchToSession(sessionId: string) {
// Hide all sessions
sessions.forEach((session, id) => {
session.element.classList.remove("active");
document.getElementById(`tab-${id}`)?.classList.remove("active");
document.getElementById(`sidebar-${id}`)?.classList.remove("active");
});
// Show active session
const session = sessions.get(sessionId);
if (session) {
session.element.classList.add("active");
document.getElementById(`tab-${sessionId}`)?.classList.add("active");
document.getElementById(`sidebar-${sessionId}`)?.classList.add("active");
activeSessionId = sessionId;
// Focus and resize
session.terminal.focus();
setTimeout(() => {
session.fitAddon.fit();
ipcRenderer.send("session-resize", sessionId, session.terminal.cols, session.terminal.rows);
}, 0);
}
}
function closeSession(sessionId: string) {
const session = sessions.get(sessionId);
if (!session) return;
// Remove from UI
session.element.remove();
document.getElementById(`tab-${sessionId}`)?.remove();
document.getElementById(`sidebar-${sessionId}`)?.remove();
// Dispose terminal
session.terminal.dispose();
sessions.delete(sessionId);
// Close in main process
ipcRenderer.send("close-session", sessionId);
// Switch to another session
if (activeSessionId === sessionId) {
const remainingSessions = Array.from(sessions.keys());
if (remainingSessions.length > 0) {
switchToSession(remainingSessions[0]);
} else {
activeSessionId = null;
}
}
}
// Handle session output
ipcRenderer.on("session-output", (_event, sessionId: string, data: string) => {
const session = sessions.get(sessionId);
if (session) {
session.terminal.write(data);
}
});
// Handle session created
ipcRenderer.on("session-created", (_event, sessionId: string) => {
const name = `Session ${sessions.size + 1}`;
createSession(sessionId, name);
});
// New session button
document.getElementById("new-session")?.addEventListener("click", () => {
ipcRenderer.send("create-session");
});