Expand electron-tests into full modular app source with tray+window and production-ready structure

This commit is contained in:
OpenClaw Bot 2026-03-14 17:17:47 +00:00
parent 0800b06ae3
commit ef73b13262
10 changed files with 182 additions and 93 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
dist/
*.log
.DS_Store

28
README.md Normal file
View File

@ -0,0 +1,28 @@
# electron-tests
Electron tray/window test app for CI/CD validation.
## Features
- Tray icon + context menu
- Main desktop window
- Simple status UI showing runtime versions
## Run locally
```bash
npm ci
npm start
```
## Build AppImage
```bash
npm run build:linux:amd64
npm run build:linux:arm64
```
Artifacts are produced via CI workflows:
- `electron-linux-amd64`
- `electron-linux-arm64`

View File

@ -1,20 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Electron Tray Demo</title>
<style>
body { font-family: Inter, system-ui, Arial, sans-serif; margin: 24px; }
h1 { margin-bottom: 8px; }
.card { border: 1px solid #ddd; border-radius: 12px; padding: 16px; }
</style>
</head>
<body>
<h1>Electron Tray Demo</h1>
<div class="card">
<p>This is a test desktop window for the Electron tray application.</p>
<p>Close window to hide to tray, use tray menu to show it again.</p>
</div>
</body>
</html>

70
main.js
View File

@ -1,70 +0,0 @@
const { app, BrowserWindow, Menu, Tray } = require('electron');
const path = require('path');
let tray = null;
let mainWindow = null;
function createWindow() {
mainWindow = new BrowserWindow({
width: 700,
height: 420,
title: 'Electron Tray Demo',
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
},
});
mainWindow.loadFile(path.join(__dirname, 'index.html'));
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault();
mainWindow.hide();
}
});
}
function createTray() {
const iconPath = path.join(__dirname, 'assets', 'trayTemplate.png');
tray = new Tray(iconPath);
tray.setToolTip('Electron Tray Demo');
const contextMenu = Menu.buildFromTemplate([
{
label: 'Show Window',
click: () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
},
},
{
label: 'Quit',
click: () => {
app.isQuiting = true;
app.quit();
},
},
]);
tray.setContextMenu(contextMenu);
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});
}
app.whenReady().then(() => {
createWindow();
createTray();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) createWindow();
});
});
app.on('window-all-closed', () => {
// keep tray app alive on Linux/macOS
});

View File

@ -2,7 +2,7 @@
"name": "electron-tray-demo", "name": "electron-tray-demo",
"version": "1.0.0", "version": "1.0.0",
"description": "Electron tray demo app", "description": "Electron tray demo app",
"main": "main.js", "main": "src/main/index.js",
"author": "marksaitis", "author": "marksaitis",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
@ -18,8 +18,7 @@
"appId": "io.swissline.electrontraydemo", "appId": "io.swissline.electrontraydemo",
"productName": "electron_tray_demo", "productName": "electron_tray_demo",
"files": [ "files": [
"main.js", "src/**/*",
"index.html",
"assets/**/*", "assets/**/*",
"package.json" "package.json"
], ],

86
src/main/index.js Normal file
View File

@ -0,0 +1,86 @@
const { app, BrowserWindow, Menu, Tray, nativeImage } = require('electron');
const path = require('path');
let tray = null;
let mainWindow = null;
function createMainWindow() {
mainWindow = new BrowserWindow({
width: 900,
height: 560,
minWidth: 720,
minHeight: 420,
title: 'Electron Test App',
backgroundColor: '#0f172a',
webPreferences: {
preload: path.join(__dirname, '..', 'preload', 'index.js'),
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
},
});
mainWindow.loadFile(path.join(__dirname, '..', 'renderer', 'index.html'));
mainWindow.on('close', (event) => {
if (!app.isQuiting) {
event.preventDefault();
mainWindow.hide();
}
});
}
function buildTrayMenu() {
return Menu.buildFromTemplate([
{
label: 'Show window',
click: () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
},
},
{
type: 'separator',
},
{
label: 'Quit',
click: () => {
app.isQuiting = true;
app.quit();
},
},
]);
}
function createTray() {
const iconPath = path.join(__dirname, '..', '..', 'assets', 'trayTemplate.png');
const icon = nativeImage.createFromPath(iconPath);
tray = new Tray(icon);
tray.setToolTip('Electron Test');
tray.setContextMenu(buildTrayMenu());
tray.on('double-click', () => {
if (mainWindow) {
mainWindow.show();
mainWindow.focus();
}
});
}
app.whenReady().then(() => {
createMainWindow();
createTray();
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow();
} else if (mainWindow) {
mainWindow.show();
}
});
});
app.on('window-all-closed', () => {
// keep tray app alive by default
});

6
src/preload/index.js Normal file
View File

@ -0,0 +1,6 @@
const { contextBridge } = require('electron');
contextBridge.exposeInMainWorld('electronTest', {
appName: 'Electron Test',
runtime: process.versions,
});

28
src/renderer/index.html Normal file
View File

@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Electron Test</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<main class="page">
<h1>Electron Test App</h1>
<p class="muted">Tray + Window demo used for CI/CD validation.</p>
<section class="card">
<h2>Status</h2>
<ul>
<li>Window launched ✅</li>
<li>Tray icon active ✅</li>
<li>Platform: <span id="platform">...</span></li>
<li>Electron: <span id="electron">...</span></li>
<li>Node: <span id="node">...</span></li>
<li>Chrome: <span id="chrome">...</span></li>
</ul>
</section>
</main>
<script src="./renderer.js"></script>
</body>
</html>

6
src/renderer/renderer.js Normal file
View File

@ -0,0 +1,6 @@
const versions = (window.electronTest && window.electronTest.runtime) || {};
document.getElementById('platform').textContent = navigator.userAgentData?.platform || navigator.platform || 'unknown';
document.getElementById('electron').textContent = versions.electron || 'unknown';
document.getElementById('node').textContent = versions.node || 'unknown';
document.getElementById('chrome').textContent = versions.chrome || 'unknown';

22
src/renderer/styles.css Normal file
View File

@ -0,0 +1,22 @@
* { box-sizing: border-box; }
body {
margin: 0;
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
color: #e2e8f0;
background: radial-gradient(circle at top left, #1e293b, #0f172a 55%);
}
.page {
max-width: 980px;
margin: 0 auto;
padding: 24px;
}
h1 { margin: 0 0 8px; font-size: 32px; }
.muted { opacity: .8; margin: 0 0 16px; }
.card {
border: 1px solid #334155;
border-radius: 16px;
padding: 16px;
background: rgba(30, 41, 59, 0.5);
}
ul { margin: 0; padding-left: 20px; }
li { margin: 8px 0; }