feat: add Electron desktop application support
- Add electron/ directory with main process, preload script, and types - Add system tray integration and window management - Add IPC communication for app info, external links, server controls - Add useElectron React hooks for Next.js integration - Add build scripts for Windows, macOS, and Linux - Add development scripts for running Electron with Next.js Ref: #149
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
# OmniRoute Electron Desktop App
|
||||
|
||||
This directory contains the Electron desktop application wrapper for OmniRoute.
|
||||
|
||||
## Structure
|
||||
|
||||
```
|
||||
electron/
|
||||
├── main.js # Main process (window management, IPC)
|
||||
├── preload.js # Preload script (secure bridge to renderer)
|
||||
├── package.json # Electron-specific dependencies
|
||||
├── types.d.ts # TypeScript definitions
|
||||
└── assets/ # Application icons and resources
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. Build the Next.js app first:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. Install Electron dependencies:
|
||||
```bash
|
||||
cd electron
|
||||
npm install
|
||||
```
|
||||
|
||||
### Running in Development
|
||||
|
||||
1. Start the Next.js development server:
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
2. In another terminal, start Electron:
|
||||
```bash
|
||||
cd electron
|
||||
npm run dev
|
||||
```
|
||||
|
||||
### Running in Production Mode
|
||||
|
||||
1. Build Next.js in standalone mode:
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
2. Start Electron:
|
||||
```bash
|
||||
cd electron
|
||||
npm start
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
### Build for Current Platform
|
||||
```bash
|
||||
cd electron
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Build for Specific Platforms
|
||||
|
||||
```bash
|
||||
# Windows
|
||||
npm run build:win
|
||||
|
||||
# macOS
|
||||
npm run build:mac
|
||||
|
||||
# Linux
|
||||
npm run build:linux
|
||||
```
|
||||
|
||||
## Output
|
||||
|
||||
Built applications are placed in `dist-electron/`:
|
||||
- Windows: `.exe` installer (NSIS)
|
||||
- macOS: `.dmg` installer
|
||||
- Linux: `.AppImage`
|
||||
|
||||
## Features
|
||||
|
||||
- **System Tray Integration**: Minimize to tray, quick actions
|
||||
- **Native Notifications**: Desktop notifications
|
||||
- **Window Management**: Minimize, maximize, close
|
||||
- **Auto-start**: Option to launch on system startup
|
||||
- **Offline Support**: Local server bundled with the app
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
The Electron app respects these environment variables:
|
||||
- `OMNIROUTE_PORT`: Server port (default: 20128)
|
||||
- `NODE_ENV`: Set to 'production' for production builds
|
||||
|
||||
### Custom Icon
|
||||
|
||||
Place your icons in `assets/`:
|
||||
- `icon.ico` - Windows icon (256x256)
|
||||
- `icon.icns` - macOS icon bundle
|
||||
- `icon.png` - Linux/general use (512x512)
|
||||
- `tray-icon.png` - System tray icon (16x16 or 32x32)
|
||||
|
||||
## IPC Channels
|
||||
|
||||
| Channel | Direction | Description |
|
||||
|---------|-----------|-------------|
|
||||
| `get-app-info` | Renderer → Main | Get app name, version, platform |
|
||||
| `open-external` | Renderer → Main | Open URL in default browser |
|
||||
| `get-data-dir` | Renderer → Main | Get data directory path |
|
||||
| `restart-server` | Renderer → Main | Restart the internal server |
|
||||
| `server-status` | Main → Renderer | Server status updates |
|
||||
| `port-changed` | Main → Renderer | Port change notifications |
|
||||
|
||||
## Security
|
||||
|
||||
- `contextIsolation: true` - Isolates renderer from Node.js
|
||||
- `nodeIntegration: false` - No direct Node.js access in renderer
|
||||
- Preload script validates IPC channels
|
||||
- No remote code execution
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### App Won't Start
|
||||
|
||||
1. Check if port 20128 is available
|
||||
2. Check logs in the console
|
||||
3. Verify the build output exists
|
||||
|
||||
### White Screen
|
||||
|
||||
1. Verify Next.js build exists
|
||||
2. Check the server URL in main.js
|
||||
3. Check for console errors
|
||||
|
||||
### Build Fails
|
||||
|
||||
1. Ensure you have build tools installed:
|
||||
- Windows: Visual Studio Build Tools
|
||||
- macOS: Xcode Command Line Tools
|
||||
- Linux: build-essential, libsecret-1-dev
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
@@ -0,0 +1,282 @@
|
||||
/**
|
||||
* OmniRoute Electron Desktop App - Main Process
|
||||
*
|
||||
* This is the entry point for the Electron desktop application.
|
||||
* It manages the main window, system tray, and IPC communication.
|
||||
*/
|
||||
|
||||
const { app, BrowserWindow, ipcMain, Tray, Menu, nativeImage, shell } = require('electron');
|
||||
const path = require('path');
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs');
|
||||
|
||||
// Environment detection
|
||||
const isDev = process.env.NODE_ENV === 'development' || !app.isPackaged;
|
||||
const isProduction = !isDev;
|
||||
|
||||
// Paths
|
||||
const APP_PATH = app.getAppPath();
|
||||
const RESOURCES_PATH = isProduction ? process.resourcesPath : APP_PATH;
|
||||
const NEXT_SERVER_PATH = path.join(RESOURCES_PATH, 'app');
|
||||
|
||||
// State
|
||||
let mainWindow = null;
|
||||
let tray = null;
|
||||
let nextServer = null;
|
||||
let serverPort = 20128;
|
||||
|
||||
// Server URL
|
||||
const getServerUrl = () => `http://localhost:${serverPort}`;
|
||||
|
||||
/**
|
||||
* Create the main application window
|
||||
*/
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1400,
|
||||
height: 900,
|
||||
minWidth: 1024,
|
||||
minHeight: 700,
|
||||
title: 'OmniRoute',
|
||||
icon: path.join(RESOURCES_PATH, 'assets', 'icon.png'),
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
webSecurity: true,
|
||||
},
|
||||
show: false,
|
||||
backgroundColor: '#0a0a0a',
|
||||
titleBarStyle: 'hiddenInset',
|
||||
trafficLightPosition: { x: 16, y: 16 },
|
||||
});
|
||||
|
||||
// Load the Next.js app
|
||||
if (isDev) {
|
||||
mainWindow.loadURL(getServerUrl());
|
||||
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
} else {
|
||||
mainWindow.loadURL(getServerUrl());
|
||||
}
|
||||
|
||||
// Show window when ready
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
// Handle external links
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
shell.openExternal(url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
// Handle window close
|
||||
mainWindow.on('close', (event) => {
|
||||
if (!app.isQuitting) {
|
||||
event.preventDefault();
|
||||
mainWindow.hide();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow = null;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create system tray icon
|
||||
*/
|
||||
function createTray() {
|
||||
const iconPath = path.join(RESOURCES_PATH, 'assets', 'tray-icon.png');
|
||||
let icon;
|
||||
|
||||
try {
|
||||
icon = nativeImage.createFromPath(iconPath);
|
||||
if (icon.isEmpty()) {
|
||||
icon = nativeImage.createEmpty();
|
||||
}
|
||||
} catch {
|
||||
icon = nativeImage.createEmpty();
|
||||
}
|
||||
|
||||
tray = new Tray(icon);
|
||||
|
||||
const contextMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'Open OmniRoute',
|
||||
click: () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: 'Open Dashboard',
|
||||
click: () => {
|
||||
shell.openExternal(getServerUrl());
|
||||
},
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Server Port',
|
||||
submenu: [
|
||||
{ label: `Port: ${serverPort}`, enabled: false },
|
||||
{ type: 'separator' },
|
||||
{ label: '20128', click: () => changePort(20128) },
|
||||
{ label: '3000', click: () => changePort(3000) },
|
||||
{ label: '8080', click: () => changePort(8080) },
|
||||
],
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: 'Quit',
|
||||
click: () => {
|
||||
app.isQuitting = true;
|
||||
app.quit();
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
tray.setToolTip('OmniRoute');
|
||||
tray.setContextMenu(contextMenu);
|
||||
|
||||
tray.on('double-click', () => {
|
||||
if (mainWindow) {
|
||||
mainWindow.show();
|
||||
mainWindow.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the server port
|
||||
*/
|
||||
function changePort(port) {
|
||||
if (port === serverPort) return;
|
||||
serverPort = port;
|
||||
if (mainWindow) {
|
||||
mainWindow.loadURL(getServerUrl());
|
||||
}
|
||||
createTray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start the Next.js server (production mode)
|
||||
*/
|
||||
function startNextServer() {
|
||||
if (isDev) {
|
||||
console.log('Development mode: Connect to existing Next.js server');
|
||||
return;
|
||||
}
|
||||
|
||||
const serverScript = path.join(NEXT_SERVER_PATH, 'server.js');
|
||||
|
||||
if (!fs.existsSync(serverScript)) {
|
||||
console.error('Server script not found:', serverScript);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('Starting Next.js server...');
|
||||
|
||||
nextServer = spawn('node', [serverScript], {
|
||||
cwd: NEXT_SERVER_PATH,
|
||||
env: {
|
||||
...process.env,
|
||||
PORT: String(serverPort),
|
||||
NODE_ENV: 'production',
|
||||
},
|
||||
stdio: 'inherit',
|
||||
});
|
||||
|
||||
nextServer.on('error', (err) => {
|
||||
console.error('Failed to start server:', err);
|
||||
});
|
||||
|
||||
nextServer.on('exit', (code) => {
|
||||
console.log('Server exited with code:', code);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the Next.js server
|
||||
*/
|
||||
function stopNextServer() {
|
||||
if (nextServer) {
|
||||
nextServer.kill();
|
||||
nextServer = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* IPC Handlers
|
||||
*/
|
||||
function setupIpcHandlers() {
|
||||
// Get app info
|
||||
ipcMain.handle('get-app-info', () => ({
|
||||
name: app.getName(),
|
||||
version: app.getVersion(),
|
||||
platform: process.platform,
|
||||
isDev,
|
||||
port: serverPort,
|
||||
}));
|
||||
|
||||
// Open external URL
|
||||
ipcMain.handle('open-external', (event, url) => {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
// Get data directory
|
||||
ipcMain.handle('get-data-dir', () => {
|
||||
const home = require('os').homedir();
|
||||
return path.join(home, '.omniroute');
|
||||
});
|
||||
|
||||
// Restart server
|
||||
ipcMain.handle('restart-server', async () => {
|
||||
stopNextServer();
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
startNextServer();
|
||||
return { success: true };
|
||||
});
|
||||
}
|
||||
|
||||
// App lifecycle events
|
||||
app.whenReady().then(() => {
|
||||
startNextServer();
|
||||
createWindow();
|
||||
createTray();
|
||||
setupIpcHandlers();
|
||||
|
||||
// macOS: recreate window when dock icon clicked
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
} else if (mainWindow) {
|
||||
mainWindow.show();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Quit when all windows are closed (except on macOS)
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
// Clean up before quit
|
||||
app.on('before-quit', () => {
|
||||
app.isQuitting = true;
|
||||
stopNextServer();
|
||||
});
|
||||
|
||||
// Handle uncaught exceptions
|
||||
process.on('uncaughtException', (error) => {
|
||||
console.error('Uncaught Exception:', error);
|
||||
});
|
||||
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
console.error('Unhandled Rejection:', reason);
|
||||
});
|
||||
Generated
+5278
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"name": "omniroute-desktop",
|
||||
"version": "1.0.0",
|
||||
"description": "OmniRoute Desktop Application",
|
||||
"main": "main.js",
|
||||
"author": "OmniRoute Team",
|
||||
"license": "MIT",
|
||||
"homepage": "https://omniroute.online",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dev": "electron . --no-sandbox",
|
||||
"build": "electron-builder",
|
||||
"build:win": "electron-builder --win",
|
||||
"build:mac": "electron-builder --mac",
|
||||
"build:linux": "electron-builder --linux",
|
||||
"pack": "electron-builder --dir"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"electron": "^33.0.0",
|
||||
"electron-builder": "^25.1.8"
|
||||
},
|
||||
"build": {
|
||||
"appId": "online.omniroute.desktop",
|
||||
"productName": "OmniRoute",
|
||||
"copyright": "Copyright © 2025 OmniRoute",
|
||||
"directories": {
|
||||
"output": "dist-electron",
|
||||
"buildResources": "assets"
|
||||
},
|
||||
"files": [
|
||||
"main.js",
|
||||
"preload.js",
|
||||
"package.json"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
"from": "../.next/standalone",
|
||||
"to": "app",
|
||||
"filter": ["**/*"]
|
||||
},
|
||||
{
|
||||
"from": "../.next/static",
|
||||
"to": "app/.next/static",
|
||||
"filter": ["**/*"]
|
||||
},
|
||||
{
|
||||
"from": "../public",
|
||||
"to": "app/public",
|
||||
"filter": ["**/*"]
|
||||
}
|
||||
],
|
||||
"win": {
|
||||
"target": [
|
||||
{
|
||||
"target": "nsis",
|
||||
"arch": ["x64"]
|
||||
}
|
||||
],
|
||||
"icon": "assets/icon.ico"
|
||||
},
|
||||
"mac": {
|
||||
"target": [
|
||||
{
|
||||
"target": "dmg",
|
||||
"arch": ["x64", "arm64"]
|
||||
}
|
||||
],
|
||||
"icon": "assets/icon.icns",
|
||||
"category": "public.app-category.productivity"
|
||||
},
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": ["x64"]
|
||||
}
|
||||
],
|
||||
"icon": "assets/icons",
|
||||
"category": "Utility"
|
||||
},
|
||||
"nsis": {
|
||||
"oneClick": false,
|
||||
"allowToChangeInstallationDirectory": true,
|
||||
"createDesktopShortcut": true,
|
||||
"createStartMenuShortcut": true,
|
||||
"installerIcon": "assets/icon.ico",
|
||||
"uninstallerIcon": "assets/icon.ico"
|
||||
},
|
||||
"dmg": {
|
||||
"contents": [
|
||||
{
|
||||
"x": 130,
|
||||
"y": 220
|
||||
},
|
||||
{
|
||||
"x": 410,
|
||||
"y": 220,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
/**
|
||||
* OmniRoute Electron Desktop App - Preload Script
|
||||
*
|
||||
* This script runs in a separate context before the web page loads.
|
||||
* It provides a secure bridge between the renderer process (Next.js app)
|
||||
* and the main process (Electron).
|
||||
*
|
||||
* Security: Uses contextIsolation: true for maximum security.
|
||||
*/
|
||||
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
// Valid IPC channels for security
|
||||
const VALID_CHANNELS = {
|
||||
invoke: [
|
||||
'get-app-info',
|
||||
'open-external',
|
||||
'get-data-dir',
|
||||
'restart-server',
|
||||
],
|
||||
send: [
|
||||
'window-minimize',
|
||||
'window-maximize',
|
||||
'window-close',
|
||||
],
|
||||
receive: [
|
||||
'server-status',
|
||||
'port-changed',
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Expose a limited API to the renderer process
|
||||
*/
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
/**
|
||||
* Get application information
|
||||
* @returns {Promise<{name: string, version: string, platform: string, isDev: boolean, port: number}>}
|
||||
*/
|
||||
getAppInfo: () => ipcRenderer.invoke('get-app-info'),
|
||||
|
||||
/**
|
||||
* Open an external URL in the default browser
|
||||
* @param {string} url - The URL to open
|
||||
*/
|
||||
openExternal: (url) => ipcRenderer.invoke('open-external', url),
|
||||
|
||||
/**
|
||||
* Get the data directory path
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
getDataDir: () => ipcRenderer.invoke('get-data-dir'),
|
||||
|
||||
/**
|
||||
* Restart the server
|
||||
* @returns {Promise<{success: boolean}>}
|
||||
*/
|
||||
restartServer: () => ipcRenderer.invoke('restart-server'),
|
||||
|
||||
/**
|
||||
* Minimize the window
|
||||
*/
|
||||
minimizeWindow: () => ipcRenderer.send('window-minimize'),
|
||||
|
||||
/**
|
||||
* Maximize/unmaximize the window
|
||||
*/
|
||||
maximizeWindow: () => ipcRenderer.send('window-maximize'),
|
||||
|
||||
/**
|
||||
* Close the window
|
||||
*/
|
||||
closeWindow: () => ipcRenderer.send('window-close'),
|
||||
|
||||
/**
|
||||
* Listen for server status updates
|
||||
* @param {function} callback - Callback function
|
||||
*/
|
||||
onServerStatus: (callback) => {
|
||||
ipcRenderer.on('server-status', (event, data) => callback(data));
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove server status listener
|
||||
*/
|
||||
removeServerStatusListener: () => {
|
||||
ipcRenderer.removeAllListeners('server-status');
|
||||
},
|
||||
|
||||
/**
|
||||
* Listen for port changes
|
||||
* @param {function} callback - Callback function
|
||||
*/
|
||||
onPortChanged: (callback) => {
|
||||
ipcRenderer.on('port-changed', (event, port) => callback(port));
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove port changed listener
|
||||
*/
|
||||
removePortChangedListener: () => {
|
||||
ipcRenderer.removeAllListeners('port-changed');
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if running in Electron
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isElectron: true,
|
||||
|
||||
/**
|
||||
* Get the platform
|
||||
* @returns {string}
|
||||
*/
|
||||
platform: process.platform,
|
||||
});
|
||||
|
||||
/**
|
||||
* Type definition for the exposed API (for TypeScript support)
|
||||
* This can be referenced in the Next.js app for type safety.
|
||||
*/
|
||||
// declare global {
|
||||
// interface Window {
|
||||
// electronAPI: {
|
||||
// getAppInfo: () => Promise<AppInfo>;
|
||||
// openExternal: (url: string) => Promise<void>;
|
||||
// getDataDir: () => Promise<string>;
|
||||
// restartServer: () => Promise<{success: boolean}>;
|
||||
// minimizeWindow: () => void;
|
||||
// maximizeWindow: () => void;
|
||||
// closeWindow: () => void;
|
||||
// onServerStatus: (callback: (data: any) => void) => void;
|
||||
// removeServerStatusListener: () => void;
|
||||
// onPortChanged: (callback: (port: number) => void) => void;
|
||||
// removePortChangedListener: () => void;
|
||||
// isElectron: boolean;
|
||||
// platform: string;
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
Vendored
+88
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* OmniRoute Electron Types
|
||||
*
|
||||
* TypeScript definitions for the Electron API exposed to the renderer process.
|
||||
*/
|
||||
|
||||
export interface AppInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
platform: 'win32' | 'darwin' | 'linux';
|
||||
isDev: boolean;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export interface ElectronAPI {
|
||||
/**
|
||||
* Get application information
|
||||
*/
|
||||
getAppInfo(): Promise<AppInfo>;
|
||||
|
||||
/**
|
||||
* Open an external URL in the default browser
|
||||
*/
|
||||
openExternal(url: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Get the data directory path
|
||||
*/
|
||||
getDataDir(): Promise<string>;
|
||||
|
||||
/**
|
||||
* Restart the server
|
||||
*/
|
||||
restartServer(): Promise<{ success: boolean }>;
|
||||
|
||||
/**
|
||||
* Minimize the window
|
||||
*/
|
||||
minimizeWindow(): void;
|
||||
|
||||
/**
|
||||
* Maximize/unmaximize the window
|
||||
*/
|
||||
maximizeWindow(): void;
|
||||
|
||||
/**
|
||||
* Close the window
|
||||
*/
|
||||
closeWindow(): void;
|
||||
|
||||
/**
|
||||
* Listen for server status updates
|
||||
*/
|
||||
onServerStatus(callback: (data: { status: string; port: number }) => void): void;
|
||||
|
||||
/**
|
||||
* Remove server status listener
|
||||
*/
|
||||
removeServerStatusListener(): void;
|
||||
|
||||
/**
|
||||
* Listen for port changes
|
||||
*/
|
||||
onPortChanged(callback: (port: number) => void): void;
|
||||
|
||||
/**
|
||||
* Remove port changed listener
|
||||
*/
|
||||
removePortChangedListener(): void;
|
||||
|
||||
/**
|
||||
* Check if running in Electron
|
||||
*/
|
||||
isElectron: boolean;
|
||||
|
||||
/**
|
||||
* Get the platform
|
||||
*/
|
||||
platform: 'win32' | 'darwin' | 'linux';
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: ElectronAPI;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
Generated
+503
-20
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "omniroute",
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.3",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "omniroute",
|
||||
"version": "1.6.2",
|
||||
"version": "1.6.3",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
@@ -53,6 +53,8 @@
|
||||
"@types/node": "^25.2.3",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"husky": "^9.1.7",
|
||||
@@ -61,7 +63,8 @@
|
||||
"tailwindcss": "^4",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.0"
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"wait-on": "^9.0.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0 <24.0.0"
|
||||
@@ -366,6 +369,13 @@
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@epic-web/invariant": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@epic-web/invariant/-/invariant-1.0.0.tgz",
|
||||
"integrity": "sha512-lrTPqgvfFQtR/eY/qkIzp98OGdNJu0m5ji3q/nJI8v3SXkRKEnWiOxMmbvcSoAIzv/cGiuvRy57k4suKQSAdwA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
|
||||
@@ -1004,6 +1014,60 @@
|
||||
"tslib": "^2.8.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/address": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/address/-/address-5.1.1.tgz",
|
||||
"integrity": "sha512-A+po2d/dVoY7cYajycYI43ZbYMXukuopIsqCjh5QzsBCipDtdofHntljDlpccMjIfTy6UOkg+5KPriwYch2bXA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^11.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/formula": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-3.0.2.tgz",
|
||||
"integrity": "sha512-hY5YPNXzw1He7s0iqkRQi+uMGh383CGdyyIGYtB+W5N3KHPXoqychklvHhKCC9M3Xtv0OCs/IHw+r4dcHtBYWw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@hapi/hoek": {
|
||||
"version": "11.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-11.0.7.tgz",
|
||||
"integrity": "sha512-HV5undWkKzcB4RZUusqOpcgxOaq6VOAH7zhhIr2g3G8NF/MlFO75SjOr2NfuSx0Mh40+1FqCkagKLJRykUWoFQ==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@hapi/pinpoint": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.1.tgz",
|
||||
"integrity": "sha512-EKQmr16tM8s16vTT3cA5L0kZZcTMU5DUOZTuvpnY738m+jyP3JIUj+Mm1xc1rsLkGBQ/gVnfKYPwOmPg1tUR4Q==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@hapi/tlds": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/tlds/-/tlds-1.1.6.tgz",
|
||||
"integrity": "sha512-xdi7A/4NZokvV0ewovme3aUO5kQhW9pQ2YD1hRqZGhhSi5rBv4usHYidVocXSi9eihYsznZxLtAiEYYUL6VBGw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@hapi/topo": {
|
||||
"version": "6.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-6.0.2.tgz",
|
||||
"integrity": "sha512-KR3rD5inZbGMrHmgPxsJ9dbi6zEK+C3ZwUwTa+eMwWLz7oijWUTWD2pMSNNYJAU6Qq+65NkxXjqHr/7LM2Xkqg==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/hoek": "^11.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanfs/core": {
|
||||
"version": "0.19.1",
|
||||
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
|
||||
@@ -2281,7 +2345,7 @@
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz",
|
||||
"integrity": "sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright": "1.58.2"
|
||||
@@ -3011,7 +3075,7 @@
|
||||
"version": "19.2.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
|
||||
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
@@ -3918,6 +3982,13 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/asynckit": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/atomic-sleep": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
|
||||
@@ -3953,6 +4024,18 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.13.6",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.6.tgz",
|
||||
"integrity": "sha512-ChTCHMouEe2kn713WHbQGcuYrr6fXTBiu460OTwWrWob16g1bXn4vtz07Ope7ewMozJAnEquLk5lWQWtBig9DQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.11",
|
||||
"form-data": "^4.0.5",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/axobject-query": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
|
||||
@@ -4337,6 +4420,94 @@
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/cliui/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui/node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
|
||||
@@ -4372,6 +4543,19 @@
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"delayed-stream": "~1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "14.0.3",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz",
|
||||
@@ -4389,6 +4573,47 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/concurrently": {
|
||||
"version": "9.2.1",
|
||||
"resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz",
|
||||
"integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "4.1.2",
|
||||
"rxjs": "7.8.2",
|
||||
"shell-quote": "1.8.3",
|
||||
"supports-color": "8.1.1",
|
||||
"tree-kill": "1.2.2",
|
||||
"yargs": "17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"conc": "dist/bin/concurrently.js",
|
||||
"concurrently": "dist/bin/concurrently.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/concurrently/node_modules/supports-color": {
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
|
||||
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"has-flag": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/supports-color?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/content-disposition": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz",
|
||||
@@ -4436,6 +4661,24 @@
|
||||
"node": ">=6.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-env": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-10.1.0.tgz",
|
||||
"integrity": "sha512-GsYosgnACZTADcmEyJctkJIoqAhHjttw7RsFrVoJNXbsWWqaq6Ym+7kZjq6mS45O0jij6vtiReppKQEtqWy6Dw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@epic-web/invariant": "^1.0.0",
|
||||
"cross-spawn": "^7.0.6"
|
||||
},
|
||||
"bin": {
|
||||
"cross-env": "dist/bin/cross-env.js",
|
||||
"cross-env-shell": "dist/bin/cross-env-shell.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
@@ -4455,7 +4698,7 @@
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
@@ -4785,6 +5028,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/delayed-stream": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/depd": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
|
||||
@@ -5859,6 +6112,46 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data": {
|
||||
"version": "4.0.5",
|
||||
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
|
||||
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"asynckit": "^0.4.0",
|
||||
"combined-stream": "^1.0.8",
|
||||
"es-set-tostringtag": "^2.1.0",
|
||||
"hasown": "^2.0.2",
|
||||
"mime-types": "^2.1.12"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-db": {
|
||||
"version": "1.52.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/form-data/node_modules/mime-types": {
|
||||
"version": "2.1.35",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mime-db": "1.52.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/forwarded": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
|
||||
@@ -5958,6 +6251,16 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz",
|
||||
@@ -7034,6 +7337,25 @@
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/joi": {
|
||||
"version": "18.0.2",
|
||||
"resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz",
|
||||
"integrity": "sha512-RuCOQMIt78LWnktPoeBL0GErkNaJPTBGcYuyaBvUOQSpcpcLfWrHPPihYdOGbV5pam9VTWbeoF7TsGiHugcjGA==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@hapi/address": "^5.1.1",
|
||||
"@hapi/formula": "^3.0.2",
|
||||
"@hapi/hoek": "^11.0.7",
|
||||
"@hapi/pinpoint": "^2.0.1",
|
||||
"@hapi/tlds": "^1.1.1",
|
||||
"@hapi/topo": "^6.0.2",
|
||||
"@standard-schema/spec": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
}
|
||||
},
|
||||
"node_modules/jose": {
|
||||
"version": "6.1.3",
|
||||
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
|
||||
@@ -7506,6 +7828,13 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash": {
|
||||
"version": "4.17.23",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz",
|
||||
"integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.merge": {
|
||||
"version": "4.6.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||
@@ -7953,17 +8282,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/next-intl/node_modules/@swc/helpers": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.19.tgz",
|
||||
"integrity": "sha512-QamiFeIK3txNjgUTNppE6MiG3p7TdninpZu0E0PbqVh1a9FNLT2FRhisaa4NcaX52XVhA5l7Pk58Ft7Sqi/2sA==",
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next/node_modules/postcss": {
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
@@ -8508,7 +8826,7 @@
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.58.2.tgz",
|
||||
"integrity": "sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.58.2"
|
||||
@@ -8527,7 +8845,7 @@
|
||||
"version": "1.58.2",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.58.2.tgz",
|
||||
"integrity": "sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
@@ -8686,6 +9004,13 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/proxy-from-env": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/pump": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
|
||||
@@ -8839,6 +9164,7 @@
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/react-redux": {
|
||||
@@ -8988,6 +9314,16 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
@@ -9127,6 +9463,16 @@
|
||||
"queue-microtask": "^1.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/rxjs": {
|
||||
"version": "7.8.2",
|
||||
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
|
||||
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-array-concat": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
|
||||
@@ -9443,6 +9789,19 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.8.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
|
||||
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 0.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/side-channel": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
|
||||
@@ -10084,6 +10443,16 @@
|
||||
"node": ">=0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"tree-kill": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ts-api-utils": {
|
||||
"version": "2.4.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz",
|
||||
@@ -10290,7 +10659,7 @@
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"devOptional": true,
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
@@ -10523,6 +10892,26 @@
|
||||
"d3-timer": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/wait-on": {
|
||||
"version": "9.0.4",
|
||||
"resolved": "https://registry.npmjs.org/wait-on/-/wait-on-9.0.4.tgz",
|
||||
"integrity": "sha512-k8qrgfwrPVJXTeFY8tl6BxVHiclK11u72DVKhpybHfUL/K6KM4bdyK9EhIVYGytB5MJe/3lq4Tf0hrjM+pvJZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "^1.13.5",
|
||||
"joi": "^18.0.2",
|
||||
"lodash": "^4.17.23",
|
||||
"minimist": "^1.2.8",
|
||||
"rxjs": "^7.8.2"
|
||||
},
|
||||
"bin": {
|
||||
"wait-on": "bin/wait-on"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
@@ -10734,6 +11123,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
@@ -10757,6 +11156,90 @@
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/yargs/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
+9
-1
@@ -48,6 +48,11 @@
|
||||
"build:cli": "node scripts/prepublish.mjs",
|
||||
"start": "node scripts/run-next.mjs start",
|
||||
"lint": "eslint .",
|
||||
"electron:dev": "concurrently \"npm run dev\" \"wait-on http://localhost:20128 && cd electron && npm run dev\"",
|
||||
"electron:build": "npm run build && cd electron && npm run build",
|
||||
"electron:build:win": "npm run build && cd electron && npm run build:win",
|
||||
"electron:build:mac": "npm run build && cd electron && npm run build:mac",
|
||||
"electron:build:linux": "npm run build && cd electron && npm run build:linux",
|
||||
"test": "node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
"test:unit": "node --import tsx/esm --test tests/unit/*.test.mjs",
|
||||
"test:plan3": "node --test tests/unit/plan3-p0.test.mjs",
|
||||
@@ -98,6 +103,8 @@
|
||||
"@types/node": "^25.2.3",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"concurrently": "^9.2.1",
|
||||
"cross-env": "^10.1.0",
|
||||
"eslint": "^9.39.2",
|
||||
"eslint-config-next": "16.1.6",
|
||||
"husky": "^9.1.7",
|
||||
@@ -106,7 +113,8 @@
|
||||
"tailwindcss": "^4",
|
||||
"tsx": "^4.21.0",
|
||||
"typescript": "^5.9.3",
|
||||
"typescript-eslint": "^8.56.0"
|
||||
"typescript-eslint": "^8.56.0",
|
||||
"wait-on": "^9.0.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,jsx,ts,tsx,mjs}": [
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
|
||||
/**
|
||||
* Check if the app is running in Electron
|
||||
*/
|
||||
export function useIsElectron(): boolean {
|
||||
const [isElectron, setIsElectron] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsElectron(typeof window !== 'undefined' && window.electronAPI?.isElectron === true);
|
||||
}, []);
|
||||
|
||||
return isElectron;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Electron app information
|
||||
*/
|
||||
export function useElectronAppInfo() {
|
||||
const [appInfo, setAppInfo] = useState<{
|
||||
name: string;
|
||||
version: string;
|
||||
platform: string;
|
||||
isDev: boolean;
|
||||
port: number;
|
||||
} | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || !window.electronAPI) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
window.electronAPI
|
||||
.getAppInfo()
|
||||
.then((info) => {
|
||||
setAppInfo(info);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch((err) => {
|
||||
setError(err);
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { appInfo, loading, error };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the data directory path
|
||||
*/
|
||||
export function useDataDir() {
|
||||
const [dataDir, setDataDir] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window === 'undefined' || !window.electronAPI) {
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
window.electronAPI
|
||||
.getDataDir()
|
||||
.then((dir) => {
|
||||
setDataDir(dir);
|
||||
setLoading(false);
|
||||
})
|
||||
.catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
}, []);
|
||||
|
||||
return { dataDir, loading };
|
||||
}
|
||||
|
||||
/**
|
||||
* Window controls for Electron
|
||||
*/
|
||||
export function useWindowControls() {
|
||||
const isElectron = useIsElectron();
|
||||
|
||||
const minimize = useCallback(() => {
|
||||
if (isElectron && window.electronAPI) {
|
||||
window.electronAPI.minimizeWindow();
|
||||
}
|
||||
}, [isElectron]);
|
||||
|
||||
const maximize = useCallback(() => {
|
||||
if (isElectron && window.electronAPI) {
|
||||
window.electronAPI.maximizeWindow();
|
||||
}
|
||||
}, [isElectron]);
|
||||
|
||||
const close = useCallback(() => {
|
||||
if (isElectron && window.electronAPI) {
|
||||
window.electronAPI.closeWindow();
|
||||
}
|
||||
}, [isElectron]);
|
||||
|
||||
return {
|
||||
isElectron,
|
||||
minimize,
|
||||
maximize,
|
||||
close,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Open external URL in default browser
|
||||
*/
|
||||
export function useOpenExternal() {
|
||||
const isElectron = useIsElectron();
|
||||
|
||||
const openExternal = useCallback(
|
||||
async (url: string) => {
|
||||
if (isElectron && window.electronAPI) {
|
||||
await window.electronAPI.openExternal(url);
|
||||
} else {
|
||||
window.open(url, '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
},
|
||||
[isElectron]
|
||||
);
|
||||
|
||||
return { openExternal };
|
||||
}
|
||||
|
||||
/**
|
||||
* Server controls for Electron
|
||||
*/
|
||||
export function useServerControls() {
|
||||
const isElectron = useIsElectron();
|
||||
const [restarting, setRestarting] = useState(false);
|
||||
|
||||
const restart = useCallback(async () => {
|
||||
if (!isElectron || !window.electronAPI) {
|
||||
return { success: false };
|
||||
}
|
||||
|
||||
setRestarting(true);
|
||||
try {
|
||||
const result = await window.electronAPI.restartServer();
|
||||
return result;
|
||||
} finally {
|
||||
setRestarting(false);
|
||||
}
|
||||
}, [isElectron]);
|
||||
|
||||
return {
|
||||
isElectron,
|
||||
restart,
|
||||
restarting,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for server status updates
|
||||
*/
|
||||
export function useServerStatus(onStatus: (status: { status: string; port: number }) => void) {
|
||||
const isElectron = useIsElectron();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectron || !window.electronAPI) return;
|
||||
|
||||
window.electronAPI.onServerStatus(onStatus);
|
||||
|
||||
return () => {
|
||||
window.electronAPI.removeServerStatusListener();
|
||||
};
|
||||
}, [isElectron, onStatus]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen for port changes
|
||||
*/
|
||||
export function usePortChanged(onPortChanged: (port: number) => void) {
|
||||
const isElectron = useIsElectron();
|
||||
|
||||
useEffect(() => {
|
||||
if (!isElectron || !window.electronAPI) return;
|
||||
|
||||
window.electronAPI.onPortChanged(onPortChanged);
|
||||
|
||||
return () => {
|
||||
window.electronAPI.removePortChangedListener();
|
||||
};
|
||||
}, [isElectron, onPortChanged]);
|
||||
}
|
||||
Reference in New Issue
Block a user