mirror of
https://gitee.com/tuxmonkey/Helix
synced 2026-10-23 10:42:20 +08:00
172 lines
5.5 KiB
JavaScript
172 lines
5.5 KiB
JavaScript
|
|
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
||
|
|
const path = require('path');
|
||
|
|
const fs = require('fs');
|
||
|
|
const { exec } = require('child_process');
|
||
|
|
|
||
|
|
let mainWindow;
|
||
|
|
const settingsPath = path.join(app.getPath('userData'), 'settings.json');
|
||
|
|
|
||
|
|
function loadSettings() {
|
||
|
|
try {
|
||
|
|
if (fs.existsSync(settingsPath)) {
|
||
|
|
return JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));
|
||
|
|
}
|
||
|
|
} catch (e) { /* ignore */ }
|
||
|
|
return { language: 'zh-CN' };
|
||
|
|
}
|
||
|
|
|
||
|
|
function saveSettings(settings) {
|
||
|
|
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), 'utf-8');
|
||
|
|
}
|
||
|
|
|
||
|
|
function createWindow() {
|
||
|
|
mainWindow = new BrowserWindow({
|
||
|
|
width: 1200,
|
||
|
|
height: 800,
|
||
|
|
minWidth: 800,
|
||
|
|
minHeight: 600,
|
||
|
|
frame: false,
|
||
|
|
backgroundColor: '#f5f5f7',
|
||
|
|
show: false,
|
||
|
|
webPreferences: {
|
||
|
|
preload: path.join(__dirname, 'preload.js'),
|
||
|
|
contextIsolation: true,
|
||
|
|
nodeIntegration: false,
|
||
|
|
sandbox: false
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
mainWindow.once('ready-to-show', () => {
|
||
|
|
mainWindow.show();
|
||
|
|
});
|
||
|
|
|
||
|
|
mainWindow.loadFile('src/index.html');
|
||
|
|
}
|
||
|
|
|
||
|
|
function runGitCommand(repoPath, args) {
|
||
|
|
return new Promise((resolve, reject) => {
|
||
|
|
exec(`git ${args.join(' ')}`, { cwd: repoPath, maxBuffer: 10 * 1024 * 1024 }, (err, stdout, stderr) => {
|
||
|
|
if (err) {
|
||
|
|
reject({ message: err.message, stderr: stderr || '' });
|
||
|
|
} else {
|
||
|
|
resolve(stdout.trim());
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
ipcMain.handle('settings:load', () => loadSettings());
|
||
|
|
ipcMain.handle('settings:save', (event, settings) => saveSettings(settings));
|
||
|
|
|
||
|
|
ipcMain.handle('git:log', async (event, repoPath) => {
|
||
|
|
try {
|
||
|
|
const format = '--format=%H||%h||%an||%ae||%ad||%s||%D||%P';
|
||
|
|
const output = await runGitCommand(repoPath, ['log', '-50', format, '--date=relative', '--all', '--topo-order']);
|
||
|
|
if (!output) return [];
|
||
|
|
return output.split('\n').map(line => {
|
||
|
|
const [hash, shortHash, author, email, date, subject, refs, parents] = line.split('||');
|
||
|
|
return { hash, shortHash, author, email, date, subject, refs, parents };
|
||
|
|
});
|
||
|
|
} catch (e) { return []; }
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:status', async (event, repoPath) => {
|
||
|
|
try {
|
||
|
|
const output = await runGitCommand(repoPath, ['status', '--porcelain', '-b']);
|
||
|
|
const lines = output ? output.split('\n') : [];
|
||
|
|
const branchLine = lines[0] || '';
|
||
|
|
const branch = branchLine.replace('## ', '').split('...')[0].replace(/ \[.*\]/, '').trim();
|
||
|
|
const files = lines.slice(1).filter(Boolean).map(line => ({
|
||
|
|
status: line.substring(0, 2).trim(),
|
||
|
|
filename: line.substring(3).trim()
|
||
|
|
}));
|
||
|
|
return { branch, files };
|
||
|
|
} catch (e) { return { branch: '', files: [] }; }
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:branches', async (event, repoPath) => {
|
||
|
|
try {
|
||
|
|
const output = await runGitCommand(repoPath, ['branch', '--format=%(refname:short)||%(objectname:short)||%(upstream:short)||%(committerdate:relative)']);
|
||
|
|
if (!output) return [];
|
||
|
|
return output.split('\n').map(line => {
|
||
|
|
const isCurrent = line.startsWith('*');
|
||
|
|
const clean = line.replace(/^\* /, '');
|
||
|
|
const [name, hash, upstream, date] = clean.split('||');
|
||
|
|
return { name, hash, upstream, date, isCurrent };
|
||
|
|
});
|
||
|
|
} catch (e) { return []; }
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:diff', async (event, repoPath, hash) => {
|
||
|
|
try {
|
||
|
|
const args = hash ? ['show', '--stat', '--format=', hash] : ['diff', '--stat', 'HEAD'];
|
||
|
|
return await runGitCommand(repoPath, args);
|
||
|
|
} catch (e) { return ''; }
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:unstaged-diff', async (event, repoPath) => {
|
||
|
|
try { return await runGitCommand(repoPath, ['diff', '--stat']); } catch (e) { return ''; }
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:staged-diff', async (event, repoPath) => {
|
||
|
|
try { return await runGitCommand(repoPath, ['diff', '--cached', '--stat']); } catch (e) { return ''; }
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:stage', async (event, repoPath, files) => {
|
||
|
|
return runGitCommand(repoPath, ['add', ...files]);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:unstage', async (event, repoPath, files) => {
|
||
|
|
return runGitCommand(repoPath, ['reset', 'HEAD', '--', ...files]);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:commit', async (event, repoPath, message) => {
|
||
|
|
return runGitCommand(repoPath, ['commit', '-m', message]);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:checkout', async (event, repoPath, branch) => {
|
||
|
|
return runGitCommand(repoPath, ['checkout', branch]);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:create-branch', async (event, repoPath, name) => {
|
||
|
|
return runGitCommand(repoPath, ['branch', name]);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:pull', async (event, repoPath) => {
|
||
|
|
return runGitCommand(repoPath, ['pull']);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:push', async (event, repoPath) => {
|
||
|
|
return runGitCommand(repoPath, ['push']);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:fetch', async (event, repoPath) => {
|
||
|
|
return runGitCommand(repoPath, ['fetch', '--all']);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('git:discard', async (event, repoPath, file) => {
|
||
|
|
return runGitCommand(repoPath, ['checkout', '--', file]);
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('dialog:open-repo', async () => {
|
||
|
|
const result = await dialog.showOpenDialog(mainWindow, {
|
||
|
|
properties: ['openDirectory'],
|
||
|
|
title: 'Open Git Repository'
|
||
|
|
});
|
||
|
|
if (result.canceled || result.filePaths.length === 0) return null;
|
||
|
|
return result.filePaths[0];
|
||
|
|
});
|
||
|
|
|
||
|
|
ipcMain.handle('window:minimize', () => mainWindow.minimize());
|
||
|
|
ipcMain.handle('window:maximize', () => {
|
||
|
|
if (mainWindow.isMaximized()) mainWindow.unmaximize();
|
||
|
|
else mainWindow.maximize();
|
||
|
|
});
|
||
|
|
ipcMain.handle('window:close', () => mainWindow.close());
|
||
|
|
|
||
|
|
app.whenReady().then(createWindow);
|
||
|
|
app.on('window-all-closed', () => app.quit());
|
||
|
|
app.on('activate', () => {
|
||
|
|
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||
|
|
});
|