Files
XCDesktop/electron/main.ts

218 lines
6.1 KiB
TypeScript

import { app, BrowserWindow, shell, ipcMain, dialog, nativeTheme, globalShortcut, clipboard } from 'electron';
import path from 'path';
import { fileURLToPath, pathToFileURL } from 'url';
import fs from 'fs';
import log from 'electron-log';
import { generatePdf } from './services/pdfGenerator';
import { selectHtmlFile } from './services/htmlImport';
import { electronState } from './state';
log.initialize();
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
process.env.NOTEBOOK_ROOT = path.join(app.getPath('documents'), 'XCDesktop');
if (!fs.existsSync(process.env.NOTEBOOK_ROOT)) {
try {
fs.mkdirSync(process.env.NOTEBOOK_ROOT, { recursive: true });
} catch (err) {
log.error('Failed to create notebook directory:', err);
}
}
electronState.setDevelopment(!app.isPackaged);
let lastClipboardText = '';
function startClipboardWatcher() {
lastClipboardText = clipboard.readText();
setInterval(() => {
try {
const currentText = clipboard.readText();
if (currentText && currentText !== lastClipboardText) {
lastClipboardText = currentText;
log.info('Clipboard changed, syncing to remote');
const win = electronState.getMainWindow();
if (win) {
win.webContents.send('remote-clipboard-auto-sync', currentText);
}
}
} catch (e) {
// ignore
}
}, 1000);
}
async function createWindow() {
const initialSymbolColor = nativeTheme.shouldUseDarkColors ? '#ffffff' : '#000000';
const mainWindow = new BrowserWindow({
width: 1280,
height: 800,
minWidth: 1600,
minHeight: 900,
autoHideMenuBar: true,
titleBarStyle: 'hidden',
titleBarOverlay: {
color: '#00000000',
symbolColor: initialSymbolColor,
height: 32,
},
webPreferences: {
nodeIntegration: false,
contextIsolation: true,
sandbox: false,
webviewTag: true,
preload: path.join(__dirname, 'preload.cjs'),
},
});
electronState.setMainWindow(mainWindow);
mainWindow.setMenu(null);
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
if (url.startsWith('http:') || url.startsWith('https:')) {
shell.openExternal(url);
return { action: 'deny' };
}
return { action: 'allow' };
});
if (electronState.isDevelopment()) {
log.info('Loading development URL...');
try {
await mainWindow.loadURL('http://localhost:5173');
} catch (e) {
log.error('Failed to load dev URL. Make sure npm run electron:dev is used.', e);
}
mainWindow.webContents.openDevTools();
} else {
log.info(`Loading production URL with port ${electronState.getServerPort()}...`);
await mainWindow.loadURL(`http://localhost:${electronState.getServerPort()}`);
}
}
ipcMain.handle('export-pdf', async (event, title, htmlContent) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (!win) return { success: false, error: 'No window found' };
try {
const { filePath } = await dialog.showSaveDialog(win, {
title: '导出 PDF',
defaultPath: `${title}.pdf`,
filters: [{ name: 'PDF Files', extensions: ['pdf'] }]
});
if (!filePath) return { success: false, canceled: true };
if (!htmlContent) {
throw new Error('No HTML content provided for PDF export');
}
const pdfData = await generatePdf(htmlContent);
fs.writeFileSync(filePath, pdfData);
return { success: true, filePath };
} catch (error: any) {
log.error('Export PDF failed:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('select-html-file', async (event) => {
const win = BrowserWindow.fromWebContents(event.sender);
return selectHtmlFile(win);
});
ipcMain.handle('update-titlebar-buttons', async (event, symbolColor: string) => {
const win = BrowserWindow.fromWebContents(event.sender);
if (win) {
win.setTitleBarOverlay({ symbolColor });
return { success: true };
}
return { success: false };
});
ipcMain.handle('clipboard-read-text', async () => {
try {
const text = clipboard.readText();
return { success: true, text };
} catch (error: any) {
log.error('Clipboard read failed:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('clipboard-write-text', async (event, text: string) => {
try {
clipboard.writeText(text);
return { success: true };
} catch (error: any) {
log.error('Clipboard write failed:', error);
return { success: false, error: error.message };
}
});
async function startServer() {
if (electronState.isDevelopment()) {
log.info('In dev mode, assuming external servers are running.');
return;
}
const serverPath = path.join(__dirname, '../dist-api/server.js');
const serverUrl = pathToFileURL(serverPath).href;
log.info(`Starting internal server from: ${serverPath}`);
try {
const serverModule = await import(serverUrl);
if (serverModule.startServer) {
const port = await serverModule.startServer();
electronState.setServerPort(port);
log.info(`Internal server started successfully on port ${port}`);
} else {
log.warn('startServer function not found in server module, using default port 3001');
}
} catch (e) {
log.error('Failed to start internal server:', e);
}
}
app.whenReady().then(async () => {
await startServer();
await createWindow();
startClipboardWatcher();
globalShortcut.register('CommandOrControl+Shift+C', () => {
log.info('Global shortcut: sync clipboard to remote');
const win = electronState.getMainWindow();
if (win) {
win.webContents.send('remote-clipboard-sync-to-remote');
}
});
globalShortcut.register('CommandOrControl+Shift+V', () => {
log.info('Global shortcut: sync clipboard from remote');
const win = electronState.getMainWindow();
if (win) {
win.webContents.send('remote-clipboard-sync-from-remote');
}
});
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on('window-all-closed', () => {
globalShortcut.unregisterAll();
if (process.platform !== 'darwin') {
app.quit();
}
});