The project has lived only as a folder on the Mac Studio and, briefly, as a transfer tarball. This is the first commit: the Express server and its web front end, the Capacitor iOS and Android wrappers, and the release and backup scripts. The repo is the source, not a backup of the machine. Left out on purpose, by the .gitignore files that were already in the tree: node_modules, build output, mobile/www (regenerated from server/public on every build), and server/data, which holds the live checklists and belongs on the NAS rather than in git. Every credential is excluded too — server/.env, the Play upload keystore and its properties file, the App Store Connect key and ios-signing.config. The one secret that was not in an ignored file was the NAS SSH password, hard-coded in build-deploy.sh and grepped back out of it by backup-to-drive.sh. Both now read it from the login keychain (service checklist-nas, account Christian), falling back to NAS_PASS in the environment, so rotating it is one keychain entry rather than a history rewrite. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
126 lines
3.5 KiB
JavaScript
126 lines
3.5 KiB
JavaScript
const { app, BrowserWindow, Menu, dialog, ipcMain } = require('electron');
|
|
const path = require('path');
|
|
const fs = require('fs/promises');
|
|
const APP_VERSION = require('./package.json').version;
|
|
|
|
let mainWindow;
|
|
|
|
function createWindow() {
|
|
mainWindow = new BrowserWindow({
|
|
width: 1200,
|
|
height: 800,
|
|
show: false, // avoid white flash; show when ready
|
|
backgroundColor: '#121212',
|
|
webPreferences: {
|
|
nodeIntegration: true,
|
|
contextIsolation: false
|
|
},
|
|
icon: path.join(__dirname, 'build', 'icon.ico')
|
|
});
|
|
|
|
// Use absolute path so packaged app can load index.html reliably
|
|
mainWindow.loadFile(path.join(__dirname, 'index.html'));
|
|
|
|
buildMenu();
|
|
|
|
mainWindow.once('ready-to-show', () => {
|
|
mainWindow.show();
|
|
});
|
|
|
|
// Open DevTools in development (comment out for production)
|
|
// mainWindow.webContents.openDevTools();
|
|
}
|
|
|
|
ipcMain.handle('export-checklists', async (event, data) => {
|
|
try {
|
|
const parentWindow = BrowserWindow.fromWebContents(event.sender);
|
|
const { canceled, filePath } = await dialog.showSaveDialog(parentWindow, {
|
|
title: 'Save checklists',
|
|
defaultPath: path.join(app.getPath('documents'), 'recurring-checklists.json'),
|
|
filters: [{ name: 'JSON Files', extensions: ['json'] }]
|
|
});
|
|
|
|
if (canceled || !filePath) {
|
|
return { canceled: true };
|
|
}
|
|
|
|
await fs.writeFile(filePath, data, 'utf-8');
|
|
return { filePath };
|
|
} catch (error) {
|
|
return { error: error.message };
|
|
}
|
|
});
|
|
|
|
ipcMain.handle('import-checklists', async (event) => {
|
|
try {
|
|
const parentWindow = BrowserWindow.fromWebContents(event.sender);
|
|
const { canceled, filePaths } = await dialog.showOpenDialog(parentWindow, {
|
|
title: 'Open checklist file',
|
|
defaultPath: app.getPath('documents'),
|
|
filters: [{ name: 'JSON Files', extensions: ['json'] }],
|
|
properties: ['openFile']
|
|
});
|
|
|
|
if (canceled || !filePaths || filePaths.length === 0) {
|
|
return { canceled: true };
|
|
}
|
|
|
|
const filePath = filePaths[0];
|
|
const data = await fs.readFile(filePath, 'utf-8');
|
|
return { filePath, data };
|
|
} catch (error) {
|
|
return { error: error.message };
|
|
}
|
|
});
|
|
|
|
function buildMenu() {
|
|
const template = [
|
|
{
|
|
label: 'Help',
|
|
submenu: [
|
|
{
|
|
label: 'About',
|
|
click: () => {
|
|
dialog.showMessageBox({
|
|
type: 'info',
|
|
title: 'About',
|
|
message: `Recurring Checklist — Build ${APP_VERSION}`,
|
|
detail: [
|
|
`Version ${APP_VERSION}`,
|
|
'',
|
|
'Copyright & Non-Distribution Disclaimer',
|
|
'',
|
|
'© Christian Bridges. All rights reserved.',
|
|
'',
|
|
'This material is protected by copyright law. No part of this work may be copied, reproduced, distributed, shared, published, uploaded, or transmitted in any form or by any means—electronic, mechanical, or otherwise—without the prior written permission of Christian Bridges.',
|
|
'',
|
|
'Unauthorized use, duplication, or distribution of this material is strictly prohibited.'
|
|
].join('\n')
|
|
});
|
|
}
|
|
}
|
|
]
|
|
}
|
|
];
|
|
|
|
const menu = Menu.buildFromTemplate(template);
|
|
Menu.setApplicationMenu(menu);
|
|
}
|
|
|
|
app.whenReady().then(() => {
|
|
createWindow();
|
|
|
|
app.on('activate', () => {
|
|
if (BrowserWindow.getAllWindows().length === 0) {
|
|
createWindow();
|
|
}
|
|
});
|
|
});
|
|
|
|
app.on('window-all-closed', () => {
|
|
if (process.platform !== 'darwin') {
|
|
app.quit();
|
|
}
|
|
});
|
|
|