Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f820e40
chore(packet-sender): remove
JavierRibaldelRio Jun 15, 2026
6aefa81
yay: new deprecated package with critical issuess
JavierRibaldelRio Jun 15, 2026
0f47e6e
feat: selector mode
JavierRibaldelRio Jun 15, 2026
02c6a4b
feat: not show log window at blcu
JavierRibaldelRio Jun 15, 2026
a39284f
feat: enhance selection window
JavierRibaldelRio Jun 15, 2026
a6043f8
fix: wait before opening testing view
JavierRibaldelRio Jun 15, 2026
c7d6115
feat: return to selector button
JavierRibaldelRio Jun 15, 2026
28b1eb5
docs(electron): remove orphan comment
JavierRibaldelRio Jun 16, 2026
4b90356
chore(packet-sender): remove
JavierRibaldelRio Jun 15, 2026
3b453cc
yay: new deprecated package with critical issuess
JavierRibaldelRio Jun 15, 2026
143a00b
feat: selector mode
JavierRibaldelRio Jun 15, 2026
5b6200a
feat: not show log window at blcu
JavierRibaldelRio Jun 15, 2026
1328109
feat: enhance selection window
JavierRibaldelRio Jun 15, 2026
976155d
fix: wait before opening testing view
JavierRibaldelRio Jun 15, 2026
75acd70
feat: return to selector button
JavierRibaldelRio Jun 15, 2026
8f536ed
docs(electron): remove orphan comment
JavierRibaldelRio Jun 16, 2026
ac5e142
Merge branch 'control-station/multiple-select-app' of https://github.…
JavierRibaldelRio Jun 16, 2026
e47561f
fix(electron): solve close testing view and open again bug
JavierRibaldelRio Jun 16, 2026
c8e60d3
fix(electron): remove timer to start front
JavierRibaldelRio Jun 16, 2026
5d9c285
docs: update readme
JavierRibaldelRio Jun 16, 2026
9f47192
Merge branch 'develop' into control-station/multiple-select-app
Humanoidear Jun 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions electron-app/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ dist-ssr
build
binaries
renderer
!renderer/
renderer/testing-view/
renderer/flashing-view/
!renderer/mode-selector/
!renderer/mode-selector/**
out
*.local

Expand Down
172 changes: 65 additions & 107 deletions electron-app/main.js
Original file line number Diff line number Diff line change
@@ -1,121 +1,79 @@
/**
* @module main
* @description Main entry point for the Electron application.
* Handles application lifecycle, initialization, and cleanup of processes and windows.
*
* Orchestrates application lifecycle and initialization through modular components:
* - initialization: Config, IPC, and process cleanup
* - modeSelector: Mode selection UI and main window creation
* - updater: Auto-update functionality
* - lifecycle: App lifecycle event handling
*/

import { app, BrowserWindow, dialog, screen } from "electron";
import pkg from "electron-updater";
import { getConfigManager } from "./src/config/configInstance.js";
import { setupIpcHandlers } from "./src/ipc/handlers.js";
import { startBackend, stopBackend } from "./src/processes/backend.js";
import { startBlcuProgramming, stopBlcuProgramming } from "./src/processes/blcuProgramming.js";
import { app, BrowserWindow, screen } from "electron";
import {
handleSelectorFallback,
initializeApp,
setupLifecycleHandlers,
setupUpdater,
showModeSelector,
} from "./src/app/index.js";
import { stopBackend } from "./src/processes/backend.js";
import { stopBlcuProgramming } from "./src/processes/blcuProgramming.js";
import { logger } from "./src/utils/logger.js";
import { createLogWindow } from "./src/windows/logWindow.js";
import { createWindow } from "./src/windows/mainWindow.js";

const { autoUpdater } = pkg;

// Setup IPC handlers for renderer process communication
setupIpcHandlers();

// App lifecycle: wait for Electron to be ready
/**
* Initializes the application when Electron is ready.
* Orchestrates startup sequence:
* 1. Initialize config and IPC
* 2. Show mode selector
* 3. Setup auto-updater
* 4. Setup lifecycle handlers
*/
app.whenReady().then(async () => {
// Get the screen width and height
// Only can be used inside app.whenReady()
const { width: screenWidth, height: screenHeight } =
screen.getPrimaryDisplay().workAreaSize;

// Initialize ConfigManager and ensure config exists BEFORE starting backend
logger.electron.header("Initializing configuration...");
// Get ConfigManager instance (creates config from template if needed)
await getConfigManager();
logger.electron.header("Configuration ready");

const logWindow = createLogWindow(screenWidth, screenHeight);

// Start backend process
try {
await startBackend(logWindow);
logger.electron.header("Backend process spawned");
} catch (error) {
// Start backend already shows these errors
}

try {
await startBlcuProgramming(logWindow);
logger.electron.header("BLCU programming process spawned");
} catch (error) {
logger.electron.error("Failed to start BLCU programming:", error);
}

// Create main application window
const mainWindow = createWindow(screenWidth, screenHeight);
mainWindow.maximize();

logger.electron.header("Main application window created");

// Updater setup
if (!app.isPackaged) {
autoUpdater.forceDevUpdateConfig = true;
}

autoUpdater.logger = {
info: (message) => logger.electron.info(message),
error: (message) => logger.electron.error(message),
warn: (message) => logger.electron.warning(message),
debug: (message) => logger.electron.debug(message),
};

// Check for updates
autoUpdater.checkForUpdates();

// Handle update downloaded event
autoUpdater.on("update-downloaded", (info) => {
dialog
.showMessageBox({
type: "info",
title: "Update Ready",
message: `Version ${info.version} has been downloaded. Restart now to install?`,
buttons: ["Restart", "Later"],
})
.then((result) => {
if (result.response === 0) {
autoUpdater.quitAndInstall();
}
});
});

// Handle macOS app activation (reopen window when dock icon clicked)
app.on("activate", () => {
// Only create window if no windows exist
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
// Initialize configuration, IPC, and cleanup
await initializeApp();

// Get screen dimensions for window creation
const { width: screenWidth, height: screenHeight } = screen.getPrimaryDisplay().workAreaSize;

// Register return-to-selector handler before starting the app.
app.on("return-to-selector", async () => {
try {
logger.electron.info("Returning to selector mode...");
await Promise.all([stopBackend(), stopBlcuProgramming()]);

const existingWindows = BrowserWindow.getAllWindows().filter((window) => !window.isDestroyed());
existingWindows.forEach((window) => window.hide());

const { width: selectorWidth, height: selectorHeight } = screen.getPrimaryDisplay().workAreaSize;
await showModeSelector(selectorWidth, selectorHeight);

existingWindows.forEach((window) => {
if (!window.isDestroyed()) {
window.close();
}
});
} catch (error) {
logger.electron.error("Failed to return to selector:", error);
}
});

// Show mode selector and get user choice
try {
await showModeSelector(screenWidth, screenHeight);
} catch (error) {
logger.electron.error("Mode selector failed:", error);
await handleSelectorFallback(screenWidth, screenHeight);
}
});
});

// Handle window close behavior
app.on("window-all-closed", () => {
// On macOS, keep app running even when all windows are closed
if (process.platform !== "darwin") {
// Quit app on other platforms when all windows are closed
// Setup auto-updater
setupUpdater();

// Setup application lifecycle handlers (window-all-closed, before-quit, activate, exceptions)
setupLifecycleHandlers();
} catch (error) {
logger.electron.error("Failed to initialize application:", error);
app.quit();
}
});

// Cleanup before app quits
app.on("before-quit", (e) => {
e.preventDefault();
Promise.all([stopBackend(), stopBlcuProgramming()])
.catch((error) => logger.electron.error("Error during shutdown:", error))
.finally(() => app.exit());
});

// Handle uncaught exceptions globally
process.on("uncaughtException", (error) => {
// Log error to console
logger.electron.error("Uncaught exception:", error);
// Show error dialog to user
dialog.showErrorBox("Error", error.message);
});
6 changes: 4 additions & 2 deletions electron-app/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hyperloop-control-station",
"version": "1.0.0",
"version": "11.1.0",
"description": "Hyperloop UPV Control Station",
"main": "main.js",
"type": "module",
Expand Down Expand Up @@ -112,7 +112,9 @@
"icon": "icons/512x512.png",
"category": "Utility",
"artifactName": "${productName}-${version}-linux-${arch}.${ext}",
"executableArgs": ["--no-sandbox"]
"executableArgs": [
"--no-sandbox"
]
}
}
}
7 changes: 7 additions & 0 deletions electron-app/preload.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ contextBridge.exposeInMainWorld("electronAPI", {
selectFolder: () => ipcRenderer.invoke("select-folder"),
// Open a folder path in the OS file explorer
openFolder: (path) => ipcRenderer.invoke("open-folder", path),
// Get the application version from the main process
getAppVersion: () => ipcRenderer.invoke("get-app-version"),
// Set initial mode (used by mode selector renderer)
setInitialMode: (mode) => {
ipcRenderer.send("mode-selected", mode);
return Promise.resolve();
},
// Receive log message from backend
onLog: (callback) => {
const listener = (_event, value) => callback(value);
Expand Down
Loading
Loading