init code for the trainer app

This commit is contained in:
Cipher Vance
2026-01-25 09:56:41 -06:00
parent b29d7481e7
commit 9eab5ed98b
47 changed files with 13572 additions and 25 deletions

292
electron/main.cjs Normal file
View File

@@ -0,0 +1,292 @@
const { app, BrowserWindow, protocol, ipcMain } = require('electron');
const path = require('path');
app.commandLine.appendSwitch('enable-experimental-web-platform-features');
app.commandLine.appendSwitch('enable-web-bluetooth');
app.commandLine.appendSwitch('enable-features', 'WebBluetooth');
if (process.platform === 'linux') {
app.commandLine.appendSwitch('disable-gpu-sandbox');
app.commandLine.appendSwitch('no-sandbox');
app.commandLine.appendSwitch('disable-seccomp-filter-sandbox');
app.commandLine.appendSwitch('enable-logging');
app.commandLine.appendSwitch('v', '1');
}
const isDev = process.env.ELECTRON_IS_DEV === '1';
let mainWindow;
let deepLinkUrl = null;
let bluetoothSelectCallback = null;
let discoveredDevices = new Map();
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on('second-instance', (event, commandLine) => {
if (mainWindow) {
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
const url = commandLine.find(arg => arg.startsWith('rideaware://'));
if (url) {
handleDeepLink(url);
}
}
});
app.whenReady().then(() => {
if (!isDev) {
protocol.registerFileProtocol('rideaware', (request, callback) => {
callback({ path: '' });
});
}
createWindow();
app.on('open-url', (event, url) => {
event.preventDefault();
handleDeepLink(url);
});
if (deepLinkUrl) {
handleDeepLink(deepLinkUrl);
deepLinkUrl = null;
}
});
}
app.on('open-url', (event, url) => {
event.preventDefault();
if (mainWindow) {
handleDeepLink(url);
} else {
deepLinkUrl = url;
}
});
if (process.platform !== 'darwin') {
const url = process.argv.find(arg => arg.startsWith('rideaware://'));
if (url) {
deepLinkUrl = url;
}
}
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 900,
minWidth: 800,
minHeight: 600,
webPreferences: {
preload: path.join(__dirname, 'preload.cjs'),
nodeIntegration: false,
contextIsolation: true,
enableBlinkFeatures: 'WebBluetooth',
webSecurity: true,
sandbox: process.platform !== 'linux',
},
backgroundColor: '#0a0a0a',
show: false,
});
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission) => {
if (permission === 'bluetooth') {
return true;
}
return false;
});
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, callback) => {
if (permission === 'bluetooth') {
callback(true);
} else {
callback(false);
}
});
mainWindow.once('ready-to-show', () => {
mainWindow.show();
});
if (isDev) {
mainWindow.loadURL('http://localhost:5173');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}
mainWindow.on('closed', () => {
mainWindow = null;
});
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
require('electron').shell.openExternal(url);
return { action: 'deny' };
});
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
event.preventDefault();
bluetoothSelectCallback = callback;
deviceList.forEach((device) => {
discoveredDevices.set(device.deviceId, {
id: device.deviceId,
name: device.deviceName || 'Unknown Device',
});
});
const devices = Array.from(discoveredDevices.values());
mainWindow.webContents.send('bluetooth:devices-updated', devices);
});
}
ipcMain.on('bluetooth:select-device', (event, deviceId) => {
if (bluetoothSelectCallback) {
bluetoothSelectCallback(deviceId);
bluetoothSelectCallback = null;
}
});
ipcMain.on('bluetooth:cancel-selection', () => {
if (bluetoothSelectCallback) {
bluetoothSelectCallback('');
bluetoothSelectCallback = null;
}
discoveredDevices.clear();
});
ipcMain.on('bluetooth:clear-devices', () => {
discoveredDevices.clear();
});
function handleDeepLink(urlString) {
if (!mainWindow) return;
try {
const parsedUrl = new URL(urlString);
if (parsedUrl.protocol === 'rideaware:') {
const action = parsedUrl.hostname;
const params = {};
parsedUrl.searchParams.forEach((value, key) => {
params[key] = value;
});
mainWindow.webContents.send('deep-link', {
action,
params,
});
if (mainWindow.isMinimized()) mainWindow.restore();
mainWindow.focus();
}
} catch (error) {
console.error('Failed to parse deep link:', error);
}
}
ipcMain.handle('get-version', () => {
return app.getVersion();
});
ipcMain.handle('get-platform', () => {
return process.platform;
});
ipcMain.handle('bluetooth:check-availability', async () => {
return {
available: true,
platform: process.platform,
flags: {
webBluetooth: app.commandLine.hasSwitch('enable-web-bluetooth'),
experimentalFeatures: app.commandLine.hasSwitch('enable-experimental-web-platform-features'),
}
};
});
ipcMain.handle('bluetooth:check-system', async () => {
const { exec } = require('child_process');
const { promisify } = require('util');
const execAsync = promisify(exec);
const result = {
platform: process.platform,
available: false,
adapter: null,
error: null,
guidance: []
};
try {
if (process.platform === 'linux') {
try {
const { stdout } = await execAsync('systemctl is-active bluetooth');
result.available = stdout.trim() === 'active';
if (result.available) {
try {
const { stdout: hciOutput } = await execAsync('hciconfig 2>&1');
result.adapter = hciOutput.includes('UP RUNNING') ? 'enabled' : 'disabled';
if (!hciOutput.includes('UP RUNNING')) {
result.guidance.push('Bluetooth adapter is not powered on. Try: sudo hciconfig hci0 up');
}
} catch {
result.adapter = 'unknown';
result.guidance.push('Unable to check Bluetooth adapter status. Install bluez-utils if not present.');
}
} else {
result.guidance.push('Bluetooth service is not running. Start it with: sudo systemctl start bluetooth');
}
} catch {
result.available = false;
result.guidance.push('Bluetooth service not found. Install bluez package.');
}
} else if (process.platform === 'darwin') {
result.available = true;
result.adapter = 'system';
} else if (process.platform === 'win32') {
result.available = true;
result.adapter = 'system';
}
} catch (error) {
result.error = error.message;
}
return result;
});
ipcMain.handle('minimize-window', () => {
if (mainWindow) mainWindow.minimize();
});
ipcMain.handle('maximize-window', () => {
if (mainWindow) {
if (mainWindow.isMaximized()) {
mainWindow.unmaximize();
} else {
mainWindow.maximize();
}
}
});
ipcMain.handle('close-window', () => {
if (mainWindow) mainWindow.close();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});

27
electron/preload.cjs Normal file
View File

@@ -0,0 +1,27 @@
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
getVersion: () => ipcRenderer.invoke('get-version'),
getPlatform: () => ipcRenderer.invoke('get-platform'),
onDeepLink: (callback) => {
ipcRenderer.on('deep-link', (event, data) => callback(data));
},
minimizeWindow: () => ipcRenderer.invoke('minimize-window'),
maximizeWindow: () => ipcRenderer.invoke('maximize-window'),
closeWindow: () => ipcRenderer.invoke('close-window'),
bluetooth: {
checkAvailability: () => ipcRenderer.invoke('bluetooth:check-availability'),
checkSystem: () => ipcRenderer.invoke('bluetooth:check-system'),
onDevicesUpdated: (callback) => {
ipcRenderer.on('bluetooth:devices-updated', (event, devices) => callback(devices));
},
selectDevice: (deviceId) => ipcRenderer.send('bluetooth:select-device', deviceId),
cancelSelection: () => ipcRenderer.send('bluetooth:cancel-selection'),
clearDevices: () => ipcRenderer.send('bluetooth:clear-devices'),
},
isElectron: true,
});