configurable themes

This commit is contained in:
Amrit Subramanian 2025-10-03 16:18:18 -04:00
parent 7c1a97ea58
commit 020d8ba82d
4 changed files with 374 additions and 10 deletions

View file

@ -19,6 +19,7 @@
<div class="sidebar">
<div class="sidebar-header">
<h1 class="sidebar-title">FleetCode</h1>
<button id="open-settings" class="settings-btn" title="Settings">⚙️</button>
</div>
<div class="sidebar-content">
@ -169,6 +170,57 @@
</div>
</div>
<!-- Settings Modal -->
<div id="settings-modal" class="modal-overlay hidden">
<div class="modal">
<h2 class="modal-title">Terminal Settings</h2>
<div class="form-group">
<label class="form-label">Theme</label>
<select id="settings-theme" class="form-select">
<option value="macos-light">macOS Terminal (Light)</option>
<option value="macos-dark">macOS Terminal (Dark)</option>
<option value="solarized-dark">Solarized Dark</option>
<option value="dracula">Dracula</option>
<option value="one-dark">One Dark</option>
<option value="github-dark">GitHub Dark</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Font Family</label>
<select id="settings-font-family" class="form-select">
<option value="Menlo, Monaco, 'Courier New', monospace">Menlo</option>
<option value="'SF Mono', Monaco, 'Courier New', monospace">SF Mono</option>
<option value="Monaco, 'Courier New', monospace">Monaco</option>
<option value="'Courier New', Courier, monospace">Courier New</option>
<option value="'Source Code Pro', monospace">Source Code Pro</option>
<option value="'Fira Code', monospace">Fira Code</option>
<option value="'JetBrains Mono', monospace">JetBrains Mono</option>
<option value="Consolas, monospace">Consolas</option>
</select>
</div>
<div class="form-group">
<label class="form-label">Font Size</label>
<input type="number" id="settings-font-size" class="form-input" min="8" max="32" placeholder="11" />
</div>
<div class="form-group">
<label class="flex items-center space-x-2 cursor-pointer">
<input type="checkbox" id="settings-cursor-blink" class="form-checkbox" />
<span class="text-sm text-gray-300">Cursor Blink</span>
</label>
</div>
<div class="btn-group">
<button id="cancel-settings" class="btn-secondary">Cancel</button>
<button id="reset-settings" class="btn-secondary">Reset to Default</button>
<button id="save-settings" class="btn-primary">Save</button>
</div>
</div>
</div>
<script>
require('./dist/renderer.js');
</script>

28
main.ts
View file

@ -212,7 +212,10 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
let dataBuffer = "";
ptyProcess.onData((data) => {
// Only send data if window still exists and is not destroyed
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("session-output", sessionId, data);
}
// Detect when terminal is ready
if (!terminalReady) {
@ -309,7 +312,10 @@ ipcMain.on("reopen-session", (event, sessionId: string) => {
let dataBuffer = "";
ptyProcess.onData((data) => {
// Only send data if window still exists and is not destroyed
if (mainWindow && !mainWindow.isDestroyed()) {
mainWindow.webContents.send("session-output", sessionId, data);
}
// Detect when terminal is ready and auto-run agent
if (!terminalReady) {
@ -389,6 +395,15 @@ ipcMain.on("rename-session", (_event, sessionId: string, newName: string) => {
}
});
// Terminal settings handlers
ipcMain.handle("get-terminal-settings", () => {
return (store as any).get("terminalSettings");
});
ipcMain.handle("save-terminal-settings", (_event, settings: any) => {
(store as any).set("terminalSettings", settings);
});
// MCP Server management functions
async function listMcpServers() {
try {
@ -529,6 +544,19 @@ const createWindow = () => {
const sessions = getPersistedSessions();
mainWindow.webContents.send("load-persisted-sessions", sessions);
});
// Clean up PTY processes when window is closed
mainWindow.on("closed", () => {
// Kill all active PTY processes
activePtyProcesses.forEach((ptyProcess, sessionId) => {
try {
ptyProcess.kill();
} catch (error) {
console.error(`Error killing PTY for session ${sessionId}:`, error);
}
});
activePtyProcesses.clear();
});
};
app.whenReady().then(() => {

View file

@ -40,19 +40,199 @@ interface McpServer {
type?: "stdio" | "sse";
}
interface TerminalSettings {
fontFamily: string;
fontSize: number;
theme: string; // Theme preset name
cursorBlink: boolean;
}
interface ThemeColors {
background: string;
foreground: string;
cursor?: string;
cursorAccent?: string;
selection?: string;
black?: string;
red?: string;
green?: string;
yellow?: string;
blue?: string;
magenta?: string;
cyan?: string;
white?: string;
brightBlack?: string;
brightRed?: string;
brightGreen?: string;
brightYellow?: string;
brightBlue?: string;
brightMagenta?: string;
brightCyan?: string;
brightWhite?: string;
}
// Theme presets
const THEME_PRESETS: Record<string, ThemeColors> = {
"macos-light": {
background: "#ffffff",
foreground: "#000000",
cursor: "#000000",
selection: "#b4d5fe",
black: "#000000",
red: "#c23621",
green: "#25bc24",
yellow: "#adad27",
blue: "#492ee1",
magenta: "#d338d3",
cyan: "#33bbc8",
white: "#cbcccd",
brightBlack: "#818383",
brightRed: "#fc391f",
brightGreen: "#31e722",
brightYellow: "#eaec23",
brightBlue: "#5833ff",
brightMagenta: "#f935f8",
brightCyan: "#14f0f0",
brightWhite: "#e9ebeb",
},
"macos-dark": {
background: "#000000",
foreground: "#ffffff",
cursor: "#ffffff",
selection: "#4d4d4d",
black: "#000000",
red: "#c23621",
green: "#25bc24",
yellow: "#adad27",
blue: "#492ee1",
magenta: "#d338d3",
cyan: "#33bbc8",
white: "#cbcccd",
brightBlack: "#818383",
brightRed: "#fc391f",
brightGreen: "#31e722",
brightYellow: "#eaec23",
brightBlue: "#5833ff",
brightMagenta: "#f935f8",
brightCyan: "#14f0f0",
brightWhite: "#e9ebeb",
},
"solarized-dark": {
background: "#002b36",
foreground: "#839496",
cursor: "#839496",
selection: "#073642",
black: "#073642",
red: "#dc322f",
green: "#859900",
yellow: "#b58900",
blue: "#268bd2",
magenta: "#d33682",
cyan: "#2aa198",
white: "#eee8d5",
brightBlack: "#002b36",
brightRed: "#cb4b16",
brightGreen: "#586e75",
brightYellow: "#657b83",
brightBlue: "#839496",
brightMagenta: "#6c71c4",
brightCyan: "#93a1a1",
brightWhite: "#fdf6e3",
},
"dracula": {
background: "#282a36",
foreground: "#f8f8f2",
cursor: "#f8f8f2",
selection: "#44475a",
black: "#21222c",
red: "#ff5555",
green: "#50fa7b",
yellow: "#f1fa8c",
blue: "#bd93f9",
magenta: "#ff79c6",
cyan: "#8be9fd",
white: "#f8f8f2",
brightBlack: "#6272a4",
brightRed: "#ff6e6e",
brightGreen: "#69ff94",
brightYellow: "#ffffa5",
brightBlue: "#d6acff",
brightMagenta: "#ff92df",
brightCyan: "#a4ffff",
brightWhite: "#ffffff",
},
"one-dark": {
background: "#282c34",
foreground: "#abb2bf",
cursor: "#528bff",
selection: "#3e4451",
black: "#282c34",
red: "#e06c75",
green: "#98c379",
yellow: "#e5c07b",
blue: "#61afef",
magenta: "#c678dd",
cyan: "#56b6c2",
white: "#abb2bf",
brightBlack: "#5c6370",
brightRed: "#e06c75",
brightGreen: "#98c379",
brightYellow: "#e5c07b",
brightBlue: "#61afef",
brightMagenta: "#c678dd",
brightCyan: "#56b6c2",
brightWhite: "#ffffff",
},
"github-dark": {
background: "#0d1117",
foreground: "#c9d1d9",
cursor: "#58a6ff",
selection: "#163c61",
black: "#484f58",
red: "#ff7b72",
green: "#3fb950",
yellow: "#d29922",
blue: "#58a6ff",
magenta: "#bc8cff",
cyan: "#39c5cf",
white: "#b1bac4",
brightBlack: "#6e7681",
brightRed: "#ffa198",
brightGreen: "#56d364",
brightYellow: "#e3b341",
brightBlue: "#79c0ff",
brightMagenta: "#d2a8ff",
brightCyan: "#56d4dd",
brightWhite: "#f0f6fc",
},
};
// Detect system theme
function getSystemTheme(): "light" | "dark" {
return window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
// Default settings - macOS Terminal matching system theme
const DEFAULT_SETTINGS: TerminalSettings = {
fontFamily: "Menlo, Monaco, 'Courier New', monospace",
fontSize: 11,
theme: getSystemTheme() === "dark" ? "macos-dark" : "macos-light",
cursorBlink: false,
};
const sessions = new Map<string, Session>();
let activeSessionId: string | null = null;
let mcpServers: McpServer[] = [];
let terminalSettings: TerminalSettings = { ...DEFAULT_SETTINGS };
function createTerminalUI(sessionId: string) {
const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"];
const term = new Terminal({
cursorBlink: true,
fontSize: 14,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
theme: {
background: "#000000",
foreground: "#ffffff",
},
cursorBlink: terminalSettings.cursorBlink,
fontSize: terminalSettings.fontSize,
fontFamily: terminalSettings.fontFamily,
theme: themeColors,
});
const fitAddon = new FitAddon();
@ -986,3 +1166,99 @@ ipcRenderer.on("mcp-servers-updated", (_event, servers: McpServer[]) => {
// Load MCP servers on startup
loadMcpServers();
// Settings Modal handling
const settingsModal = document.getElementById("settings-modal");
const openSettingsBtn = document.getElementById("open-settings");
const cancelSettingsBtn = document.getElementById("cancel-settings");
const resetSettingsBtn = document.getElementById("reset-settings");
const saveSettingsBtn = document.getElementById("save-settings");
const settingsTheme = document.getElementById("settings-theme") as HTMLSelectElement;
const settingsFontFamily = document.getElementById("settings-font-family") as HTMLSelectElement;
const settingsFontSize = document.getElementById("settings-font-size") as HTMLInputElement;
const settingsCursorBlink = document.getElementById("settings-cursor-blink") as HTMLInputElement;
// Load saved settings on startup
async function loadSettings() {
const savedSettings = await ipcRenderer.invoke("get-terminal-settings");
if (savedSettings) {
terminalSettings = { ...DEFAULT_SETTINGS, ...savedSettings };
}
}
// Populate settings form
function populateSettingsForm() {
// Set theme
settingsTheme.value = terminalSettings.theme;
// Set font family - match against dropdown options
const fontOptions = Array.from(settingsFontFamily.options);
const matchingOption = fontOptions.find(opt => opt.value === terminalSettings.fontFamily);
if (matchingOption) {
settingsFontFamily.value = matchingOption.value;
} else {
// Default to first option (Menlo) if no match
settingsFontFamily.selectedIndex = 0;
}
settingsFontSize.value = terminalSettings.fontSize.toString();
settingsCursorBlink.checked = terminalSettings.cursorBlink;
}
// Apply settings to all existing terminals
function applySettingsToAllTerminals() {
const themeColors = THEME_PRESETS[terminalSettings.theme] || THEME_PRESETS["macos-dark"];
sessions.forEach((session) => {
if (session.terminal) {
session.terminal.options.fontFamily = terminalSettings.fontFamily;
session.terminal.options.fontSize = terminalSettings.fontSize;
session.terminal.options.cursorBlink = terminalSettings.cursorBlink;
session.terminal.options.theme = themeColors;
// Refresh terminal to apply changes
if (session.fitAddon) {
session.fitAddon.fit();
}
}
});
}
// Open settings modal
openSettingsBtn?.addEventListener("click", () => {
populateSettingsForm();
settingsModal?.classList.remove("hidden");
});
// Cancel settings
cancelSettingsBtn?.addEventListener("click", () => {
settingsModal?.classList.add("hidden");
});
// Reset settings to default
resetSettingsBtn?.addEventListener("click", () => {
terminalSettings = { ...DEFAULT_SETTINGS };
populateSettingsForm();
});
// Save settings
saveSettingsBtn?.addEventListener("click", async () => {
// Read values from form
terminalSettings.theme = settingsTheme.value;
terminalSettings.fontFamily = settingsFontFamily.value || DEFAULT_SETTINGS.fontFamily;
terminalSettings.fontSize = parseInt(settingsFontSize.value) || DEFAULT_SETTINGS.fontSize;
terminalSettings.cursorBlink = settingsCursorBlink.checked;
// Save to electron-store
await ipcRenderer.invoke("save-terminal-settings", terminalSettings);
// Apply to all existing terminals
applySettingsToAllTerminals();
// Close modal
settingsModal?.classList.add("hidden");
});
// Load settings on startup
loadSettings();

View file

@ -9,13 +9,17 @@
}
.sidebar-header {
@apply p-4 border-b border-gray-700;
@apply p-4 border-b border-gray-700 flex items-center justify-between;
}
.sidebar-title {
@apply text-white font-semibold text-lg;
}
.settings-btn {
@apply text-gray-400 hover:text-white hover:bg-gray-700 text-lg w-8 h-8 flex items-center justify-center rounded transition-all;
}
.sidebar-content {
@apply flex-1 overflow-y-auto;
}
@ -177,6 +181,10 @@
@apply w-4 h-4 rounded border-gray-600 bg-gray-700 text-blue-600 focus:ring-blue-500 focus:ring-2 cursor-pointer;
}
.color-input {
@apply bg-gray-700 border border-gray-600 rounded px-2 py-1 h-10 w-20 cursor-pointer;
}
.btn-secondary {
@apply bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded text-sm font-medium transition;
}