Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | import { app, BrowserWindow, dialog, ipcMain, screen } from 'electron';
import * as path from 'path';
import { ServerManager } from './server-manager.js';
import { WindowStateStore } from './window-state.js';
let mainWindow: BrowserWindow | null = null;
const serverManager = new ServerManager();
const windowState = new WindowStateStore();
// Shutdown coordination
let isQuitting = false;
/**
* Wrap an async operation with a timeout. If the operation doesn't complete
* within the timeout, the onTimeout callback is called and the promise resolves.
*/
async function withAppTimeout<T>(
operation: Promise<T>,
timeoutMs: number,
onTimeout: () => void
): Promise<T | void> {
return Promise.race([
operation,
new Promise<void>((resolve) => {
setTimeout(() => {
onTimeout();
resolve();
}, timeoutMs);
}),
]);
}
app.whenReady().then(async () => {
// Check prerequisites
const prereq = serverManager.checkPrerequisites();
if (!prereq.ok) {
dialog.showErrorBox('Setup Required', prereq.error!);
app.quit();
return;
}
// Windows: Auto-install mehr in WSL if needed
if (prereq.needsInstall) {
try {
// Show progress dialog
const progressWindow = new BrowserWindow({
width: 400,
height: 150,
frame: false,
resizable: false,
alwaysOnTop: true,
webPreferences: { nodeIntegration: false, contextIsolation: true },
});
progressWindow.loadURL(
`data:text/html,<html><body style="font-family:system-ui;display:flex;flex-direction:column;align-items:center;justify-content:center;height:100vh;margin:0;background:#1e1e2e;color:#cdd6f4"><div style="margin-bottom:12px">Installing mehr in WSL...</div><div style="width:80%;height:4px;background:#313244;border-radius:2px;overflow:hidden"><div style="width:100%;height:100%;background:#89b4fa;animation:progress 2s ease-in-out infinite"></div></div><style>@keyframes progress{0%,100%{transform:translateX(-100%)}50%{transform:translateX(100%)}}</style></body></html>`
);
await serverManager.installMehrInWSL();
progressWindow.close();
} catch (err) {
dialog.showErrorBox('Installation Failed', String(err));
app.quit();
return;
}
}
// Start server in global mode
try {
const port = await serverManager.startGlobal();
createWindow(port);
} catch (err) {
dialog.showErrorBox('Failed to Start', String(err));
app.quit();
}
});
function createWindow(port: number): void {
const state = windowState.load();
const displays = screen.getAllDisplays();
// Find saved display or fall back to primary
const targetDisplay = state
? displays.find((d) => d.id === state.displayId) || screen.getPrimaryDisplay()
: screen.getPrimaryDisplay();
// Restore bounds if display still exists, otherwise use display workArea
const displayStillExists = state && displays.some((d) => d.id === state.displayId);
const bounds = displayStillExists
? { x: state.x, y: state.y, width: state.width, height: state.height }
: targetDisplay.workArea;
mainWindow = new BrowserWindow({
...bounds,
show: false, // Prevent flash while positioning
title: 'Mehrhof',
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
// Show window after ready (maximize on first launch or if was maximized)
mainWindow.once('ready-to-show', () => {
if (!state || state.isMaximized) {
mainWindow!.maximize();
}
mainWindow!.show();
});
// Save window state on close
mainWindow.on('close', () => {
if (!mainWindow) return;
const currentBounds = mainWindow.getBounds();
const currentDisplay = screen.getDisplayMatching(currentBounds);
windowState.save({
...currentBounds,
isMaximized: mainWindow.isMaximized(),
displayId: currentDisplay.id,
});
});
// Load the live server (global mode shows project picker)
mainWindow.loadURL(`http://localhost:${port}`);
}
// IPC: Native folder picker for adding new projects
ipcMain.handle('open-folder', async () => {
const result = await dialog.showOpenDialog(mainWindow!, {
properties: ['openDirectory'],
title: 'Select Project Folder',
});
return result.canceled ? null : result.filePaths[0];
});
// Graceful shutdown with timeout protection
app.on('before-quit', async (e) => {
if (isQuitting) return; // Already shutting down
isQuitting = true;
e.preventDefault();
await withAppTimeout(
serverManager.stop(),
3000, // 3 second timeout
() => console.log('[shutdown] Server stop timed out, forcing quit')
);
// Use app.exit() to terminate immediately without re-triggering before-quit
app.exit(0);
});
app.on('window-all-closed', async () => {
if (isQuitting) return; // Already handled by before-quit
isQuitting = true;
await withAppTimeout(
serverManager.stop(),
3000,
() => console.log('[shutdown] Server stop timed out')
);
if (process.platform !== 'darwin') {
app.exit(0);
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0 && mainWindow === null) {
// Re-launch would need to restart server - for now just quit
app.quit();
}
});
|