1
0
This commit is contained in:
2025-05-23 22:42:01 +02:00
parent 5b180b3b95
commit b41740568b
4 changed files with 339 additions and 221 deletions

249
main.js
View File

@@ -47,6 +47,7 @@ function createWindow() {
return win; return win;
} }
// Settings-Fenster // Settings-Fenster
let settingsWin; let settingsWin;
function openSettings(win) { function openSettings(win) {
@@ -101,6 +102,87 @@ async function initGitRepo(folder) {
} }
} }
// Map für Monitoring-Watcher (nicht repoWatchers!)
const monitoringWatchers = new Map();
function startMonitoringWatcher(folderPath, win) {
// Nicht mehrfach starten
if (monitoringWatchers.has(folderPath)) return;
const watcher = chokidar.watch(folderPath, {
ignored: /(^|[\/\\])\..|node_modules|\.git/, // ignoriert .git und .dot-Dateien + node_modules
ignoreInitial: true,
persistent: true,
depth: 99, // Rekursiv
awaitWriteFinish: {
stabilityThreshold: 300,
pollInterval: 100
}
});
// TODO: Optionale .gitignore Logik nachrüsten
watcher.on('all', async (event, changedPath) => {
debug(`[MONITOR] ${event} in ${changedPath}`);
// Hier einfach auto-commit Funktion rufen:
await autoCommit(folderPath, `[auto] ${event} ${path.relative(folderPath, changedPath)}`);
// Repo-UI aktualisieren:
win.webContents.send('repo-updated', folderPath);
});
monitoringWatchers.set(folderPath, watcher);
debug(`[MONITOR] Watcher aktiv für ${folderPath}`);
}
function stopMonitoringWatcher(folderPath) {
const watcher = monitoringWatchers.get(folderPath);
if (watcher) {
watcher.close();
monitoringWatchers.delete(folderPath);
debug(`[MONITOR] Watcher gestoppt für ${folderPath}`);
}
}
async function autoCommit(folder, message) {
const git = simpleGit(folder);
const status = await git.status();
if (
status.not_added.length === 0 &&
status.created.length === 0 &&
status.deleted.length === 0 &&
status.modified.length === 0 &&
status.renamed.length === 0
) {
debug('Auto-Commit: Keine Änderungen zum committen.');
return false;
}
// Der Rest wie in commit-current-folder
let currentBranch = null;
try {
currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();
debug(`[autoCommit] Aktueller Branch: ${currentBranch}`);
} catch {
debug('[autoCommit] HEAD ist detached.');
}
if (!currentBranch || currentBranch === 'HEAD') {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupBranch = `backup-master-${timestamp}`;
const branches = await git.branchLocal();
if (branches.all.includes('master')) {
await git.branch(['-m', 'master', backupBranch]);
debug(`[autoCommit] Alter master in ${backupBranch} umbenannt.`);
}
await git.checkout(['-b', 'master']);
debug('[autoCommit] Neuer master-Branch erstellt und ausgecheckt.');
}
await git.add(['-A']);
debug('[autoCommit] Alle Änderungen gestaged.');
await git.commit(message || '[auto]');
debug('[autoCommit] Commit erfolgreich erstellt.');
return true;
}
app.whenReady().then(() => { app.whenReady().then(() => {
const win = createWindow(); const win = createWindow();
@@ -118,22 +200,39 @@ app.whenReady().then(() => {
]); ]);
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
// 1) Beim Start bereits gespeicherte Ordner überwachen // 1) Beim Start bereits gespeicherte Ordner überwachen und monitoren
const folders = store.get('folders'); const folders = store.get('folders') || [];
folders.forEach(folder => { folders.forEach(folderObj => {
// nur watchen, wenn .git existiert if (fs.existsSync(path.join(folderObj.path, '.git', 'refs', 'heads', 'master'))) {
if (fs.existsSync(path.join(folder, '.git', 'refs', 'heads', 'master'))) { watchRepo(folderObj.path, win);
watchRepo(folder, win); }
if (folderObj.monitoring) {
startMonitoringWatcher(folderObj.path, win);
} }
}); });
// 2) IPC-Handler // 2) IPC-Handler
ipcMain.handle('get-selected', () => {
const folders = store.get('folders') || [];
const selectedPath = store.get('selected');
return folders.find(f => f.path === selectedPath) || null;
});
ipcMain.handle('set-selected', (_e, folderObjOrPath) => {
// Akzeptiert sowohl String (legacy) als auch Objekt:
const folderPath = typeof folderObjOrPath === 'string'
? folderObjOrPath
: folderObjOrPath.path;
store.set('selected', folderPath);
const folders = store.get('folders') || [];
return folders.find(f => f.path === folderPath) || null;
});
// Liste aller Folders // Liste aller Folders
ipcMain.handle('get-folders', () => store.get('folders')); ipcMain.handle('get-folders', () => store.get('folders'));
// Ordner hinzufügen: Open-Dialog, init, Store-Update, watchen
// Ordner hinzufügen: Open-Dialog, init, Store-Update, watchen, monitoren
ipcMain.handle('add-folder', async () => { ipcMain.handle('add-folder', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({ const { canceled, filePaths } = await dialog.showOpenDialog({
properties: ['openDirectory'] properties: ['openDirectory']
@@ -142,6 +241,24 @@ app.whenReady().then(() => {
return store.get('folders'); return store.get('folders');
} }
const newFolder = filePaths[0]; const newFolder = filePaths[0];
await initGitRepo(newFolder);
let folders = store.get('folders') || [];
let folderObj = folders.find(f => f.path === newFolder);
if (!folderObj) {
folderObj = { path: newFolder, monitoring: true };
folders.push(folderObj);
store.set('folders', folders);
}
store.set('selected', newFolder);
watchRepo(newFolder, win);
startMonitoringWatcher(newFolder, win);
return store.get('folders');
});
/*
ipcMain.handle('add-folder', async () => {
const { canceled, filePaths } = await dialog.showOpenDialog({ properties: ['openDirectory'] });
if (canceled || !filePaths[0]) return store.get('folders');
const newFolder = filePaths[0];
// Repo initialisieren // Repo initialisieren
await initGitRepo(newFolder); await initGitRepo(newFolder);
@@ -158,8 +275,27 @@ app.whenReady().then(() => {
return store.get('folders'); return store.get('folders');
}); });
*/
// Ordner entfernen: Watcher schließen, Store-Update // Ordner entfernen: Watcher schließen, Store-Update
ipcMain.handle('remove-folder', (_e, folderObj) => {
const folders = store.get('folders') || [];
const updated = folders.filter(f => f.path !== folderObj.path);
store.set('folders', updated);
if (store.get('selected') === folderObj.path) store.set('selected', null);
stopMonitoringWatcher(folderObj.path);
const watcher = repoWatchers.get(folderObj.path);
if (watcher) watcher.close(), repoWatchers.delete(folderObj.path);
return updated;
});
/*
ipcMain.handle('get-selected', () => store.get('selected'));
ipcMain.handle('set-selected', (_e, folderPath) => {
store.set('selected', folderPath);
return folderPath;
});
*/
/*
ipcMain.handle('remove-folder', (_e, folder) => { ipcMain.handle('remove-folder', (_e, folder) => {
const watcher = repoWatchers.get(folder); const watcher = repoWatchers.get(folder);
if (watcher) { if (watcher) {
@@ -173,41 +309,45 @@ app.whenReady().then(() => {
} }
return updated; return updated;
}); });
*/
// Zähle Commits // Zähle Commits
ipcMain.handle('get-commit-count', async (_e, folder) => { ipcMain.handle('get-commit-count', async (_e, folderObj) => {
const git = simpleGit(folder); const git = simpleGit(folderObj.path);
const log = await git.log(); const log = await git.log();
return log.total; // Anzahl der Commits return log.total; // Anzahl der Commits
}); });
// Prüfe, ob es ungestagte Änderungen gibt // Prüfe, ob es ungestagte Änderungen gibt
ipcMain.handle('has-diffs', async (_e, folder) => { ipcMain.handle('has-diffs', async (_e, folderObj) => {
const git = simpleGit(folder); const git = simpleGit(folderObj.path);
const status = await git.status(); const status = await git.status();
// modified, not_added, deleted, etc. // modified, not_added, deleted, etc.
return status.files.length > 0; return status.files.length > 0;
}); });
// Entferne das .git-Verzeichnis // Entferne das .git-Verzeichnis
ipcMain.handle('remove-git-folder', async (_e, folder) => { ipcMain.handle('remove-git-folder', async (_e, folderObj) => {
const gitDir = path.join(folder, '.git'); const gitDir = path.join(folderObj.path, '.git');
if (fs.existsSync(gitDir)) { if (fs.existsSync(gitDir)) {
await fs.promises.rm(gitDir, { recursive: true, force: true }); await fs.promises.rm(gitDir, { recursive: true, force: true });
} }
return; return;
}); });
/*
// Selected // Selected
ipcMain.handle('get-selected', () => store.get('selected')); ipcMain.handle('get-selected', () => store.get('selected'));
ipcMain.handle('set-selected', (_e, folder) => { ipcMain.handle('set-selected', (_e, folder) => {
store.set('selected', folder); store.set('selected', folder);
return folder; return folder;
}); });
*/
// Commits holen // Commits holen
ipcMain.handle('get-commits', async (_e, folder) => { ipcMain.handle('get-commits', async (_e, folderObj) => {
const git = simpleGit(folder); const git = simpleGit(folderObj.path);
// alle Commits holen // alle Commits holen
const log = await git.log(['--all']); const log = await git.log(['--all']);
// aktuellen HEADHash ermitteln // aktuellen HEADHash ermitteln
@@ -224,40 +364,40 @@ app.whenReady().then(() => {
}); });
// Diff // Diff
ipcMain.handle('diff-commit', async (_e, folder, hash) => { ipcMain.handle('diff-commit', async (_e, folderObj, hash) => {
const git = simpleGit(folder); const git = simpleGit(folderObj.path);
return git.diff([`${hash}^!`]); return git.diff([`${hash}^!`]);
}); });
// Revert // Revert
ipcMain.handle('revert-commit', async (_e, folder, hash) => { ipcMain.handle('revert-commit', async (_e, folderObj, hash) => {
const git = simpleGit(folder); const git = simpleGit(folderObj.path);
await git.revert(hash, ['--no-edit']); await git.revert(hash, ['--no-edit']);
}); });
/** /**
* Checkt das Arbeitsverzeichnis auf exakt den Zustand von `hash` aus. * Checkt das Arbeitsverzeichnis auf exakt den Zustand von `hash` aus.
*/ */
ipcMain.handle('checkout-commit', async (_e, folder, hash) => { ipcMain.handle('checkout-commit', async (_e, folderObj, hash) => {
const git = simpleGit(folder); const git = simpleGit(folderObj.path);
// clean mode: alle lokalen Veränderungen verwerfen // clean mode: alle lokalen Veränderungen verwerfen
await git.checkout([hash, '--force']); await git.checkout([hash, '--force']);
}); });
// Snapshot // Snapshot
ipcMain.handle('snapshot-commit', async (_e, folder, hash) => { ipcMain.handle('snapshot-commit', async (_e, folderObj, hash) => {
const { canceled, filePaths } = await dialog.showOpenDialog({ const { canceled, filePaths } = await dialog.showOpenDialog({
title: 'Ordner auswählen zum Speichern des Snapshots', title: 'Ordner auswählen zum Speichern des Snapshots',
properties: ['openDirectory'] properties: ['openDirectory']
}); });
if (canceled || !filePaths[0]) return; if (canceled || !filePaths[0]) return;
const outDir = filePaths[0]; const outDir = filePaths[0];
const baseName = path.basename(folder); const baseName = path.basename(folderObj.path);
const filePath = path.join(outDir, `${baseName}-${hash}.zip`); const filePath = path.join(outDir, `${baseName}-${hash}.zip`);
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
exec( exec(
`git -C "${folder}" archive --format zip --output "${filePath}" ${hash}`, `git -C "${folderObj.path}" archive --format zip --output "${filePath}" ${hash}`,
err => err ? reject(err) : resolve(filePath) err => err ? reject(err) : resolve(filePath)
); );
}); });
@@ -277,38 +417,58 @@ app.whenReady().then(() => {
ipcMain.handle('set-skip-git-prompt', (_e,val) => store.set('skipGitPrompt', val)); ipcMain.handle('set-skip-git-prompt', (_e,val) => store.set('skipGitPrompt', val));
ipcMain.handle('commit-current-folder', async (_e, folder, message) => {
ipcMain.handle('commit-current-folder', async (_e, folderObj, message) => {
folder = folderObj.path;
try { try {
debug(`Commit-Vorgang für ${folder} gestartet…`); debug(`Commit-Vorgang für ${folder} gestartet…`);
const git = simpleGit(folder); const git = simpleGit(folder);
// 1) BranchStatus prüfen // Prüfe: Gibt es was zu committen?
const status = await git.status();
if (
status.not_added.length === 0 &&
status.created.length === 0 &&
status.deleted.length === 0 &&
status.modified.length === 0 &&
status.renamed.length === 0
) {
debug('Nichts zu committen.');
return { success: false, error: 'Nichts zu committen.' };
}
// HEAD-Status prüfen
let currentBranch = null; let currentBranch = null;
try { try {
currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim(); currentBranch = (await git.revparse(['--abbrev-ref', 'HEAD'])).trim();
debug(`Aktueller Branch: ${currentBranch}`); debug(`Aktueller Branch: ${currentBranch}`);
} catch { } catch (err) {
debug('HEAD ist detached.'); debug('HEAD ist detached.');
} }
// 2) Falls detached, alten master sichern und neuen anlegen // Falls detached, **jetzt erst** alten Branch umbenennen und neuen master erzeugen
if (!currentBranch || currentBranch === 'HEAD') { if (!currentBranch || currentBranch === 'HEAD') {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const backupBranch = `backup-master-${timestamp}`; const backupBranch = `backup-master-${timestamp}`;
const branches = await git.branchLocal();
// Alten master umbenennen (nur falls vorhanden!)
const branches = await git.branchLocal();
if (branches.all.includes('master')) { if (branches.all.includes('master')) {
await git.branch(['-m', 'master', backupBranch]); await git.branch(['-m', 'master', backupBranch]);
debug(`Alter master ${backupBranch}`); debug(`Alter master-Branch wurde in ${backupBranch} umbenannt.`);
} }
// Neuer master-Branch
await git.checkout(['-b', 'master']); await git.checkout(['-b', 'master']);
debug('Neuer master-Branch erstellt.'); debug('Neuer master-Branch erstellt und ausgecheckt.');
} }
// 3) Commit & Push await git.add(['-A']);
await git.add(['-A']); debug('git add -A'); debug('Alle Änderungen gestaged.');
await git.commit(message || 'test'); debug('git commit'); await git.commit(message || 'test');
await git.push(['-u','origin','master']); debug('git push'); debug('Commit erfolgreich erstellt.');
// Push hier ggf. noch auskommentiert lassen
return { success: true }; return { success: true };
} catch (err) { } catch (err) {
@@ -317,8 +477,21 @@ app.whenReady().then(() => {
} }
}); });
ipcMain.handle('set-monitoring', async (_e, folderPath, monitoring) => {
let folders = store.get('folders') || [];
folders = folders.map(f =>
f.path === folderPath ? { ...f, monitoring } : f
);
store.set('folders', folders);
debug(`[STORE] Monitoring für ${folderPath}: ${monitoring}`);
// Monitoring-Watcher starten/stoppen → gleich mehr dazu
if (monitoring) {
startMonitoringWatcher(folderPath, win);
} else {
stopMonitoringWatcher(folderPath);
}
return monitoring;
});
// … Ende der IPC-Handler … // … Ende der IPC-Handler …

View File

@@ -31,6 +31,7 @@
"dependencies": { "dependencies": {
"chokidar": "^4.0.3", "chokidar": "^4.0.3",
"electron-store": "^8.2.0", "electron-store": "^8.2.0",
"ignore": "^7.0.4",
"simple-git": "^3.20.0", "simple-git": "^3.20.0",
"suncalc": "^1.9.0" "suncalc": "^1.9.0"
}, },

View File

@@ -10,23 +10,20 @@ contextBridge.exposeInMainWorld('settingsAPI', {
contextBridge.exposeInMainWorld('electronAPI', { contextBridge.exposeInMainWorld('electronAPI', {
getFolders: () => ipcRenderer.invoke('get-folders'), getFolders: () => ipcRenderer.invoke('get-folders'),
addFolder: () => ipcRenderer.invoke('add-folder'), addFolder: () => ipcRenderer.invoke('add-folder'),
removeFolder: folder => ipcRenderer.invoke('remove-folder', folder), removeFolder: folderObj=> ipcRenderer.invoke('remove-folder', folderObj),
getSelected: () => ipcRenderer.invoke('get-selected'), getSelected: () => ipcRenderer.invoke('get-selected'),
setSelected: folder => ipcRenderer.invoke('set-selected', folder), setSelected: folderObj=> ipcRenderer.invoke('set-selected', folderObj),
listFolder: folder => ipcRenderer.invoke('list-folder', folder), getCommits: folderObj=> ipcRenderer.invoke('get-commits', folderObj),
getCommits: folder => ipcRenderer.invoke('get-commits', folder), diffCommit: (folderObj, hash) => ipcRenderer.invoke('diff-commit', folderObj, hash),
diffCommit: (folder, hash) => ipcRenderer.invoke('diff-commit', folder, hash), revertCommit: (folderObj, hash) => ipcRenderer.invoke('revert-commit', folderObj, hash),
revertCommit: (folder, hash) => ipcRenderer.invoke('revert-commit', folder, hash), snapshotCommit: (folderObj, hash) => ipcRenderer.invoke('snapshot-commit', folderObj, hash),
snapshotCommit: (folder, hash) => ipcRenderer.invoke('snapshot-commit', folder, hash), checkoutCommit: (folderObj, hash) => ipcRenderer.invoke('checkout-commit', folderObj, hash),
checkoutCommit: (folder, hash) => ipcRenderer.invoke('checkout-commit', folder, hash), getCommitCount: folderObj=> ipcRenderer.invoke('get-commit-count', folderObj),
getCommitCount: folder => ipcRenderer.invoke('get-commit-count', folder), hasDiffs: folderObj=> ipcRenderer.invoke('has-diffs', folderObj),
hasDiffs: folder => ipcRenderer.invoke('has-diffs', folder), removeGitFolder:folderObj=> ipcRenderer.invoke('remove-git-folder', folderObj),
removeGitFolder: folder => ipcRenderer.invoke('remove-git-folder', folder), commitCurrentFolder: (folderObj, message) => ipcRenderer.invoke('commit-current-folder', folderObj, message),
getSkipPrompt: () => ipcRenderer.invoke('get-skip-git-prompt'),
setSkipPrompt: val => ipcRenderer.invoke('set-skip-git-prompt', val),
showFolderContextMenu: folderPath => ipcRenderer.send('show-folder-context-menu', folderPath),
commitCurrentFolder: (folder, message) => ipcRenderer.invoke('commit-current-folder', folder, message),
showFolderContextMenu: folderPath => ipcRenderer.send('show-folder-context-menu', folderPath), showFolderContextMenu: folderPath => ipcRenderer.send('show-folder-context-menu', folderPath),
setMonitoring: (folderObj, monitoring) => ipcRenderer.invoke('set-monitoring', folderObj.path, monitoring),
}); });
ipcRenderer.on('repo-updated', (_e, folder) => { ipcRenderer.on('repo-updated', (_e, folder) => {

View File

@@ -8,18 +8,13 @@ window.addEventListener('DOMContentLoaded', async () => {
const contentList = document.getElementById('contentList'); const contentList = document.getElementById('contentList');
const panel = document.querySelector('.flex-1.p-4.overflow-y-auto'); const panel = document.querySelector('.flex-1.p-4.overflow-y-auto');
// Farben für Sky-Mode
// 1) Baby-Blau und Nacht-Blau als RGB-Arrays
const DAY_COLOR = [173, 216, 230]; const DAY_COLOR = [173, 216, 230];
const NIGHT_COLOR = [0, 0, 50]; const NIGHT_COLOR = [0, 0, 50];
// 2) Linearer Interpolator
function lerpColor(c1, c2, t) { function lerpColor(c1, c2, t) {
return c1.map((v, i) => Math.round(v + t * (c2[i] - v))); return c1.map((v, i) => Math.round(v + t * (c2[i] - v)));
} }
// 3) Entscheider für den Zeit-Factor
function getTimeFactor() { function getTimeFactor() {
const now = new Date(); const now = new Date();
const md = now.getHours() * 60 + now.getMinutes(); const md = now.getHours() * 60 + now.getMinutes();
@@ -29,29 +24,20 @@ window.addEventListener('DOMContentLoaded', async () => {
if (md < 20 * 60) return 1 - ((md - 16*60) / (4*60)); if (md < 20 * 60) return 1 - ((md - 16*60) / (4*60));
return 0; return 0;
} }
// 4) setzt die Hintergrundfarbe
function updateBackground() { function updateBackground() {
const factor = getTimeFactor(); const factor = getTimeFactor();
const [r,g,b] = lerpColor(NIGHT_COLOR, DAY_COLOR, factor); const [r,g,b] = lerpColor(NIGHT_COLOR, DAY_COLOR, factor);
panel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; panel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`;
} }
// 5) applySkyMode-Funktion
let skyIntervalId, titleIntervalId; let skyIntervalId, titleIntervalId;
function applySkyMode(enabled) { function applySkyMode(enabled) {
document.body.classList.toggle('sky-mode', enabled); document.body.classList.toggle('sky-mode', enabled);
// alte Intervalle löschen
clearInterval(skyIntervalId); clearInterval(skyIntervalId);
clearInterval(titleIntervalId); clearInterval(titleIntervalId);
if (enabled) { if (enabled) {
// Hintergrund updaten
updateBackground(); updateBackground();
skyIntervalId = setInterval(updateBackground, 60_000); skyIntervalId = setInterval(updateBackground, 60_000);
// Titel-Farbe je nach Uhrzeit
function updateTitleColor() { function updateTitleColor() {
const hour = new Date().getHours(); const hour = new Date().getHours();
if (hour >= 18 || hour < 6) { if (hour >= 18 || hour < 6) {
@@ -63,36 +49,38 @@ window.addEventListener('DOMContentLoaded', async () => {
updateTitleColor(); updateTitleColor();
titleIntervalId = setInterval(updateTitleColor, 60_000); titleIntervalId = setInterval(updateTitleColor, 60_000);
} else { } else {
// Sky-Mode aus → zurücksetzen
panel.style.backgroundColor = ''; panel.style.backgroundColor = '';
titleEl.style.color = ''; titleEl.style.color = '';
} }
} }
// 6) initial anwenden und auf Event lauschen
const initialSky = await window.settingsAPI.getSkyMode(); const initialSky = await window.settingsAPI.getSkyMode();
applySkyMode(initialSky); applySkyMode(initialSky);
window.addEventListener('skymode-changed', e => applySkyMode(e.detail)); window.addEventListener('skymode-changed', e => applySkyMode(e.detail));
// … restliches Rendering wie gehabt …
function basename(fullPath) { function basename(fullPath) {
return fullPath.replace(/.*[\\/]/, ''); return fullPath.replace(/.*[\\/]/, '');
} }
// Utility für FolderObj-Suche per Path
async function getFolderObjByPath(path) {
const folders = await window.electronAPI.getFolders();
return folders.find(f => f.path === path) || null;
}
async function renderSidebar() { async function renderSidebar() {
const folders = await window.electronAPI.getFolders(); const folders = await window.electronAPI.getFolders();
const selected = await window.electronAPI.getSelected(); const selected = await window.electronAPI.getSelected();
folderList.innerHTML = ''; folderList.innerHTML = '';
folders.forEach(folder => { folders.forEach(folderObj => {
// 1) li anlegen, selected-Klasse statt bg-[#fecdd3] const folder = folderObj.path;
const isMonitoring = folderObj.monitoring;
const li = document.createElement('li'); const li = document.createElement('li');
li.className = [ li.className = [
'flex items-center justify-between px-3 py-2 rounded cursor-pointer', 'flex items-center justify-between px-3 py-2 rounded cursor-pointer',
folder === selected ? 'selected' : '' selected && folder === selected.path ? 'selected' : ''
].join(' '); ].join(' ');
// 2) inneres Markup, ohne Hard-Coded-Farben
li.innerHTML = ` li.innerHTML = `
<div class="flex items-center space-x-2 overflow-hidden"> <div class="flex items-center space-x-2 overflow-hidden">
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
@@ -113,57 +101,61 @@ async function renderSidebar() {
</button> </button>
`; `;
// rechter Mausklick in der Sidebar // play/pause Button korrekt initialisieren
const pausePlayBtn = document.createElement('button');
pausePlayBtn.className = 'pause-play-btn ml-2';
pausePlayBtn.innerHTML = isMonitoring ? '⏸️' : '▶️';
pausePlayBtn.title = isMonitoring ? 'Monitoring pausieren' : 'Monitoring starten';
pausePlayBtn.addEventListener('click', async e => {
e.stopPropagation();
await window.electronAPI.setMonitoring(folderObj, !isMonitoring);
await renderSidebar();
});
li.appendChild(pausePlayBtn);
li.addEventListener('contextmenu', e => { li.addEventListener('contextmenu', e => {
e.preventDefault(); e.preventDefault();
window.electronAPI.showFolderContextMenu(folder); window.electronAPI.showFolderContextMenu(folderObj.path);
}); });
// 3) Klick auf li (Select)
li.addEventListener('click', async e => { li.addEventListener('click', async e => {
if (e.target.closest('.remove-btn')) return; if (e.target.closest('.remove-btn')) return;
await window.electronAPI.setSelected(folder); await window.electronAPI.setSelected(folderObj);
await renderSidebar(); await renderSidebar();
await renderContent(folder); await renderContent(folderObj);
}); });
// 4) Remove-Button
li.querySelector('.remove-btn').addEventListener('click', async e => { li.querySelector('.remove-btn').addEventListener('click', async e => {
e.stopPropagation(); e.stopPropagation();
const count = await window.electronAPI.getCommitCount(folderObj);
// Commit-Count / Diffs / SkipPrompt-Logik wie gehabt const hasUnstaged= await window.electronAPI.hasDiffs(folderObj);
const count = await window.electronAPI.getCommitCount(folder);
const hasUnstaged= await window.electronAPI.hasDiffs(folder);
const skipPrompt = await window.settingsAPI.getSkipPrompt(); const skipPrompt = await window.settingsAPI.getSkipPrompt();
if (count === 1 && !hasUnstaged) { if (count === 1 && !hasUnstaged) {
if (skipPrompt) { if (skipPrompt) {
await window.electronAPI.removeGitFolder(folder); await window.electronAPI.removeGitFolder(folderObj);
} else { } else {
const ok = confirm( const ok = confirm(
'Dieser Ordner hat nur einen Initial-Commit und keine Änderungen.\n' + 'Dieser Ordner hat nur einen Initial-Commit und keine Änderungen.\n' +
'Möchtest du das gesamte Git-Repository (den .git-Ordner) löschen?' 'Möchtest du das gesamte Git-Repository (den .git-Ordner) löschen?'
); );
if (ok) { if (ok) {
await window.electronAPI.removeGitFolder(folder); await window.electronAPI.removeGitFolder(folderObj);
} else { } else {
return; return;
} }
} }
} }
// 5) aus Sidebar/Store entfernen await window.electronAPI.removeFolder(folderObj);
await window.electronAPI.removeFolder(folder);
await renderSidebar(); await renderSidebar();
// 6) neue Selektion
const all = await window.electronAPI.getFolders(); const all = await window.electronAPI.getFolders();
if (all.length === 0) { if (all.length === 0) {
titleEl.textContent = 'No folder selected'; titleEl.textContent = 'No folder selected';
contentList.innerHTML = ''; contentList.innerHTML = '';
} else { } else {
// entfernten Index vorher merken, dann neuen auswählen const idxOld = folders.findIndex(f => f.path === folderObj.path);
const idxOld = folders.indexOf(folder);
let idxNew = Math.max(0, idxOld - 1); let idxNew = Math.max(0, idxOld - 1);
const pick = all[idxNew]; const pick = all[idxNew];
await window.electronAPI.setSelected(pick); await window.electronAPI.setSelected(pick);
@@ -176,11 +168,10 @@ async function renderSidebar() {
}); });
} }
async function renderContent(folder) { async function renderContent(folderObj) {
const currentFolder = folder; const folder = folderObj.path;
titleEl.textContent = folder; titleEl.textContent = folder;
const { head, commits } = await window.electronAPI.getCommits(folder); const { head, commits } = await window.electronAPI.getCommits(folderObj);
contentList.innerHTML = commits.map(c => ` contentList.innerHTML = commits.map(c => `
<li class="w-full p-3 mb-2 bg-white border border-gray-200 rounded shadow-sm <li class="w-full p-3 mb-2 bg-white border border-gray-200 rounded shadow-sm
@@ -220,21 +211,10 @@ async function renderSidebar() {
</svg> </svg>
Jump Here Jump Here
</button> </button>
<!-- Revert-Button -->
<!--<button class="revert-btn flex items-center px-2 py-1 text-xs border rounded hover:bg-gray-100" data-hash="${c.hash}">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-1" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M15 12H3m12 0l-4-4m4 4l-4 4"/>
</svg>
Revert
</button>-->
</div> </div>
<div class="diff-container relative"> <div class="diff-container relative">
<!-- copy-Button oben rechts -->
<button class="copy-diff-btn absolute top-1 right-1 p-1 border rounded hover:bg-gray-100 flex items-center justify-center"> <button class="copy-diff-btn absolute top-1 right-1 p-1 border rounded hover:bg-gray-100 flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<!-- Copy-Icon: -->
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h6a2 2 0 012 2v2m0 4h2a2 2 0 002-2v-6a2 2 0 00-2-2h-6a2 2 0 00-2 2v2" /> d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h6a2 2 0 012 2v2m0 4h2a2 2 0 002-2v-6a2 2 0 00-2-2h-6a2 2 0 00-2 2v2" />
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
@@ -246,10 +226,10 @@ async function renderSidebar() {
</li> </li>
`).join(''); `).join('');
// Erst mal alle Diff-Buttons prüfen und ggf. deaktivieren // Diff-Buttons prüfen und ggf. deaktivieren
contentList.querySelectorAll('.diff-btn').forEach(async btn => { contentList.querySelectorAll('.diff-btn').forEach(async btn => {
const hash = btn.dataset.hash; const hash = btn.dataset.hash;
const diffText = await window.electronAPI.diffCommit(folder, hash); const diffText = await window.electronAPI.diffCommit(folderObj, hash);
if (!diffText.trim()) { if (!diffText.trim()) {
btn.disabled = true; btn.disabled = true;
btn.classList.add('disabled'); btn.classList.add('disabled');
@@ -267,7 +247,7 @@ async function renderSidebar() {
if (!pre.innerHTML.trim()) { if (!pre.innerHTML.trim()) {
// fetch und HTML-Snippet bauen // fetch und HTML-Snippet bauen
const diff = await window.electronAPI.diffCommit(folder, hash); const diff = await window.electronAPI.diffCommit(folderObj, hash);
const escaped = diff const escaped = diff
.replace(/&/g, '&amp;') .replace(/&/g, '&amp;')
.replace(/</g, '&lt;') .replace(/</g, '&lt;')
@@ -300,8 +280,7 @@ async function renderSidebar() {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
const hash = btn.dataset.hash; const hash = btn.dataset.hash;
try { try {
// verwende jetzt currentFolder statt einer undefinierten Variable const savedPath = await window.electronAPI.snapshotCommit(folderObj, hash);
const savedPath = await window.electronAPI.snapshotCommit(currentFolder, hash);
if (savedPath) { if (savedPath) {
alert(`Snapshot gespeichert unter:\n${savedPath}`); alert(`Snapshot gespeichert unter:\n${savedPath}`);
} }
@@ -312,37 +291,20 @@ async function renderSidebar() {
}); });
}); });
// Revert-Button
contentList.querySelectorAll('.revert-btn').forEach(btn => {
btn.addEventListener('click', async () => {
const hash = btn.dataset.hash;
if (confirm(`Commit ${hash} wirklich revertieren?`)) {
await window.electronAPI.revertCommit(folder, hash);
await renderContent(folder);
}
});
});
// Checkout-Button // Checkout-Button
contentList.querySelectorAll('.checkout-btn').forEach(btn => { contentList.querySelectorAll('.checkout-btn').forEach(btn => {
btn.addEventListener('click', async () => { btn.addEventListener('click', async () => {
const hash = btn.dataset.hash; const hash = btn.dataset.hash;
//if (!confirm(`Jump here? Ungestagte Änderungen werden verworfen.`)) return; await window.electronAPI.checkoutCommit(folderObj, hash);
// currentFolder hast du oben in renderContent gespeichert await renderContent(folderObj);
await window.electronAPI.checkoutCommit(currentFolder, hash);
await renderContent(currentFolder);
}); });
}); });
const currentEl = contentList.querySelector('li.current-commit');
// Copy-Diff-Button // Copy-Diff-Button
contentList.querySelectorAll('.diff-container').forEach(container => { contentList.querySelectorAll('.diff-container').forEach(container => {
const btn = container.querySelector('.copy-diff-btn'); const btn = container.querySelector('.copy-diff-btn');
const pre = container.querySelector('pre'); const pre = container.querySelector('pre');
// speicher die ursprüngliche SVG
const originalSVG = btn.innerHTML; const originalSVG = btn.innerHTML;
// erstelle die Checkmark-SVG
const checkSVG = ` const checkSVG = `
<svg xmlns="http://www.w3.org/2000/svg" <svg xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4 text-green-600" class="h-4 w-4 text-green-600"
@@ -351,13 +313,10 @@ async function renderSidebar() {
d="M5 13l4 4L19 7"/> d="M5 13l4 4L19 7"/>
</svg> </svg>
`; `;
btn.addEventListener('click', () => { btn.addEventListener('click', () => {
navigator.clipboard.writeText(pre.textContent) navigator.clipboard.writeText(pre.textContent)
.then(() => { .then(() => {
// Icon tauschen
btn.innerHTML = checkSVG; btn.innerHTML = checkSVG;
// nach 1s wieder zurück
setTimeout(() => { setTimeout(() => {
btn.innerHTML = originalSVG; btn.innerHTML = originalSVG;
}, 1000); }, 1000);
@@ -368,8 +327,8 @@ async function renderSidebar() {
}); });
}); });
const currentEl = contentList.querySelector('li.current-commit');
if (currentEl) { if (currentEl) {
// weich scrollen und zentriert in den Viewport bringen
currentEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); currentEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
} }
} }
@@ -378,7 +337,6 @@ async function renderSidebar() {
const initial = await window.electronAPI.getSelected(); const initial = await window.electronAPI.getSelected();
if (initial) await renderContent(initial); if (initial) await renderContent(initial);
// Add-Folder
addBtn.addEventListener('click', async () => { addBtn.addEventListener('click', async () => {
await window.electronAPI.addFolder(); await window.electronAPI.addFolder();
await renderSidebar(); await renderSidebar();
@@ -386,10 +344,12 @@ async function renderSidebar() {
if (sel) await renderContent(sel); if (sel) await renderContent(sel);
}); });
// repo-updated, contextmenus usw. // Repo-Update Handling jetzt mit Lookup für folderObj
window.addEventListener('repo-updated', e => { window.addEventListener('repo-updated', async e => {
if (titleEl.textContent === e.detail) renderContent(e.detail); const obj = await getFolderObjByPath(e.detail);
if (obj) renderContent(obj);
}); });
titleEl.addEventListener('contextmenu', e => { titleEl.addEventListener('contextmenu', e => {
e.preventDefault(); e.preventDefault();
if (titleEl.textContent !== 'No folder selected') { if (titleEl.textContent !== 'No folder selected') {
@@ -397,40 +357,27 @@ async function renderSidebar() {
} }
}); });
const commitBtn = document.getElementById('commitBtn'); const commitBtn = document.getElementById('commitBtn');
commitBtn.addEventListener('click', async () => { commitBtn.addEventListener('click', async () => {
// 1) Welcher Ordner ist gerade ausgewählt? const folderObj = await window.electronAPI.getSelected();
const folder = await window.electronAPI.getSelected(); if (!folderObj || !folderObj.path) {
if (!folder) {
alert('Kein Ordner ausgewählt!'); alert('Kein Ordner ausgewählt!');
return; return;
} }
// 2) Nachricht abfragen
const message = 'test' const message = 'test'
alert('Commit-Button geklickt!');
// 3) UI blockieren
commitBtn.disabled = true; commitBtn.disabled = true;
commitBtn.textContent = 'Committing…'; commitBtn.textContent = 'Committing…';
// 4) IPCAufruf const result = await window.electronAPI.commitCurrentFolder(folderObj, message);
const result = await window.electronAPI.commitCurrentFolder(folder, message);
console.log('[renderer] commit result:', result);
// 5) Ergebnis anzeigen und Liste neu laden
if (result.success) { if (result.success) {
alert('Commit erfolgreich!'); alert('Commit erfolgreich!');
await renderContent(folder); await renderContent(folderObj);
} else { } else {
alert('Commit fehlgeschlagen:\n' + result.error); alert('Commit fehlgeschlagen:\n' + result.error);
} }
// 6) UI wieder freigeben
commitBtn.disabled = false; commitBtn.disabled = false;
commitBtn.textContent = 'Commit'; commitBtn.textContent = 'Commit';
}); });
}); });