// renderer.js window.addEventListener('DOMContentLoaded', async () => { // Elemente holen const folderList = document.getElementById('folderList'); const addBtn = document.getElementById('addFolderBtn'); const titleEl = document.getElementById('currentTitle'); const contentList = document.getElementById('contentList'); const panel = document.querySelector('.flex-1.p-4.overflow-y-auto'); // Farben für Sky-Mode const DAY_COLOR = [173, 216, 230]; const NIGHT_COLOR = [0, 0, 50]; function lerpColor(c1, c2, t) { return c1.map((v, i) => Math.round(v + t * (c2[i] - v))); } function getTimeFactor() { const now = new Date(); const md = now.getHours() * 60 + now.getMinutes(); if (md < 4 * 60) return 0; if (md < 8 * 60) return (md - 4*60) / (4*60); if (md < 16 * 60) return 1; if (md < 20 * 60) return 1 - ((md - 16*60) / (4*60)); return 0; } function updateBackground() { const factor = getTimeFactor(); const [r,g,b] = lerpColor(NIGHT_COLOR, DAY_COLOR, factor); panel.style.backgroundColor = `rgb(${r}, ${g}, ${b})`; } let skyIntervalId, titleIntervalId; function applySkyMode(enabled) { document.body.classList.toggle('sky-mode', enabled); clearInterval(skyIntervalId); clearInterval(titleIntervalId); if (enabled) { updateBackground(); skyIntervalId = setInterval(updateBackground, 60_000); function updateTitleColor() { const hour = new Date().getHours(); if (hour >= 18 || hour < 6) { titleEl.style.color = '#fff'; } else { titleEl.style.color = ''; } } updateTitleColor(); titleIntervalId = setInterval(updateTitleColor, 60_000); } else { panel.style.backgroundColor = ''; titleEl.style.color = ''; } } const initialSky = await window.settingsAPI.getSkyMode(); applySkyMode(initialSky); window.addEventListener('skymode-changed', e => applySkyMode(e.detail)); function basename(fullPath) { 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() { const folders = await window.electronAPI.getFolders(); const selected = await window.electronAPI.getSelected(); folderList.innerHTML = ''; folders.forEach(folderObj => { const folder = folderObj.path; const isMonitoring = folderObj.monitoring; const li = document.createElement('li'); li.className = [ 'flex items-center justify-between px-3 py-2 rounded cursor-pointer', selected && folder === selected.path ? 'selected' : '' ].join(' '); // beide Buttons schon im Template unter „right“ li.innerHTML = `
${basename(folder)}
`; // jetzt Listener setzen const pauseBtn = li.querySelector('.pause-play-btn'); pauseBtn.addEventListener('click', async e => { e.stopPropagation(); await window.electronAPI.setMonitoring(folderObj, !isMonitoring); await renderSidebar(); }); const removeBtn = li.querySelector('.remove-btn'); removeBtn.addEventListener('click', async e => { e.stopPropagation(); // ... dein Remove-Logic hier ... }); // Kontextmenü / Click zum Select li.addEventListener('contextmenu', e => { e.preventDefault(); window.electronAPI.showFolderContextMenu(folderObj.path); }); li.addEventListener('click', async e => { if (e.target.closest('.pause-play-btn, .remove-btn')) return; await window.electronAPI.setSelected(folderObj); await renderSidebar(); await renderContent(folderObj); }); folderList.appendChild(li); li.addEventListener('contextmenu', e => { e.preventDefault(); window.electronAPI.showFolderContextMenu(folderObj.path); }); li.addEventListener('click', async e => { if (e.target.closest('.remove-btn')) return; await window.electronAPI.setSelected(folderObj); await renderSidebar(); await renderContent(folderObj); }); li.querySelector('.remove-btn').addEventListener('click', async e => { e.stopPropagation(); const count = await window.electronAPI.getCommitCount(folderObj); const hasUnstaged= await window.electronAPI.hasDiffs(folderObj); const skipPrompt = await window.settingsAPI.getSkipPrompt(); if (count === 1 && !hasUnstaged) { if (skipPrompt) { await window.electronAPI.removeGitFolder(folderObj); } else { const ok = confirm( 'Dieser Ordner hat nur einen Initial-Commit und keine Änderungen.\n' + 'Möchtest du das gesamte Git-Repository (den .git-Ordner) löschen?' ); if (ok) { await window.electronAPI.removeGitFolder(folderObj); } else { return; } } } await window.electronAPI.removeFolder(folderObj); await renderSidebar(); const all = await window.electronAPI.getFolders(); if (all.length === 0) { titleEl.textContent = 'No folder selected'; contentList.innerHTML = ''; } else { const idxOld = folders.findIndex(f => f.path === folderObj.path); let idxNew = Math.max(0, idxOld - 1); const pick = all[idxNew]; await window.electronAPI.setSelected(pick); await renderSidebar(); await renderContent(pick); } }); folderList.appendChild(li); }); } const folderTitleDrop = document.getElementById('folderTitleDrop'); const folderTitleArrow = document.getElementById('folderTitleArrow'); const folderHierarchyDropdown = document.getElementById('folderHierarchyDropdown'); let isDropdownOpen = false; folderTitleDrop.addEventListener('click', async () => { if (isDropdownOpen) { closeDropdown(); return; } const selected = await window.electronAPI.getSelected(); if (!selected || !selected.path) return; // Show loading text folderHierarchyDropdown.textContent = 'Lade Verzeichnis…'; folderHierarchyDropdown.classList.remove('hidden'); folderTitleArrow.classList.add('open'); isDropdownOpen = true; // Lade Tree const tree = await window.electronAPI.getFolderTree(selected.path); folderHierarchyDropdown.textContent = renderFolderTreeAscii(tree, '.', ''); }); function closeDropdown() { folderHierarchyDropdown.classList.add('hidden'); folderTitleArrow.classList.remove('open'); isDropdownOpen = false; } // ASCII-Baum: wie bei ChatGPT! function renderFolderTreeAscii(tree, prefix = '', indent = '') { if (!Array.isArray(tree)) return ''; let result = ''; const lastIdx = tree.length - 1; tree.forEach((node, i) => { const isLast = i === lastIdx; const pointer = isLast ? '└── ' : '├── '; if (node.type === 'dir') { result += `${indent}${pointer}${node.name}/\n`; const newIndent = indent + (isLast ? ' ' : '│ '); result += renderFolderTreeAscii(node.children, '', newIndent); } else { result += `${indent}${pointer}${node.name}\n`; } }); return result; } window.addEventListener('repo-updated', () => { closeDropdown(); }); async function renderContent(folderObj) { closeDropdown(); const folder = folderObj.path; titleEl.textContent = folder; const { head, commits } = await window.electronAPI.getCommits(folderObj); contentList.innerHTML = commits.map(c => `
  • ${c.hash} ${new Date(c.date).toLocaleString()}
    ${c.message}
    
            
  • `).join(''); // Diff-Buttons prüfen und ggf. deaktivieren contentList.querySelectorAll('.diff-btn').forEach(async btn => { const hash = btn.dataset.hash; const diffText = await window.electronAPI.diffCommit(folderObj, hash); if (!diffText.trim()) { btn.disabled = true; btn.classList.add('disabled'); } }); // Diff-Toggle & Highlighting contentList.querySelectorAll('.diff-btn').forEach(btn => { btn.addEventListener('click', async () => { const li = btn.closest('li'); const hash = btn.dataset.hash; const svg = btn.querySelector('svg'); const container = li.querySelector('.diff-container'); const pre = container.querySelector('pre'); if (!pre.innerHTML.trim()) { // fetch und HTML-Snippet bauen const diff = await window.electronAPI.diffCommit(folderObj, hash); const escaped = diff .replace(/&/g, '&') .replace(//g, '>'); pre.innerHTML = escaped .split('\n') .map(line => { const cls = line.startsWith('+') ? 'diff-line addition' : line.startsWith('-') ? 'diff-line deletion' : 'diff-line'; return `
    ${line}
    `; }) .join(''); } const isOpen = container.classList.toggle('open'); if (isOpen) { container.style.maxHeight = container.scrollHeight + 'px'; } else { container.style.maxHeight = '0'; } svg.classList.toggle('open', isOpen); }); }); // Snapshot-Button contentList.querySelectorAll('.snapshot-btn').forEach(btn => { btn.addEventListener('click', async () => { const hash = btn.dataset.hash; try { const savedPath = await window.electronAPI.snapshotCommit(folderObj, hash); if (savedPath) { alert(`Snapshot gespeichert unter:\n${savedPath}`); } } catch (err) { console.error(err); alert('Snapshot fehlgeschlagen'); } }); }); // Checkout-Button contentList.querySelectorAll('.checkout-btn').forEach(btn => { btn.addEventListener('click', async () => { const hash = btn.dataset.hash; await window.electronAPI.checkoutCommit(folderObj, hash); await renderContent(folderObj); }); }); // Copy-Diff-Button contentList.querySelectorAll('.diff-container').forEach(container => { const btn = container.querySelector('.copy-diff-btn'); const pre = container.querySelector('pre'); const originalSVG = btn.innerHTML; const checkSVG = ` `; btn.addEventListener('click', () => { navigator.clipboard.writeText(pre.textContent) .then(() => { btn.innerHTML = checkSVG; setTimeout(() => { btn.innerHTML = originalSVG; }, 1000); }) .catch(err => { console.error('Clipboard write failed', err); }); }); }); const currentEl = contentList.querySelector('li.current-commit'); if (currentEl) { currentEl.scrollIntoView({ behavior: 'smooth', block: 'center' }); } } await renderSidebar(); const initial = await window.electronAPI.getSelected(); if (initial) await renderContent(initial); addBtn.addEventListener('click', async () => { await window.electronAPI.addFolder(); await renderSidebar(); const sel = await window.electronAPI.getSelected(); if (sel) await renderContent(sel); }); // Repo-Update Handling jetzt mit Lookup für folderObj window.addEventListener('repo-updated', async e => { const obj = await getFolderObjByPath(e.detail); if (obj) renderContent(obj); }); titleEl.addEventListener('contextmenu', e => { e.preventDefault(); if (titleEl.textContent !== 'No folder selected') { window.electronAPI.showFolderContextMenu(titleEl.textContent); } }); const commitBtn = document.getElementById('commitBtn'); commitBtn.addEventListener('click', async () => { const folderObj = await window.electronAPI.getSelected(); if (!folderObj || !folderObj.path) { alert('Kein Ordner ausgewählt!'); return; } const message = 'test' commitBtn.disabled = true; commitBtn.textContent = 'Committing…'; const result = await window.electronAPI.commitCurrentFolder(folderObj, message); if (result.success) { alert('Commit erfolgreich!'); await renderContent(folderObj); } else { alert('Commit fehlgeschlagen:\n' + result.error); } commitBtn.disabled = false; commitBtn.textContent = 'Commit'; }); }); /* const folderTitleDrop = document.getElementById('folderTitleDrop'); const folderTitleArrow = document.getElementById('folderTitleArrow'); const folderHierarchyDropdown = document.getElementById('folderHierarchyDropdown'); const titleEl = document.getElementById('currentTitle'); let isDropdownOpen = false; folderTitleDrop.addEventListener('click', async () => { if (isDropdownOpen) { closeDropdown(); return; } const selected = await window.electronAPI.getSelected(); if (!selected || !selected.path) return; // Lade die aktuelle Ordnerhierarchie folderHierarchyDropdown.innerHTML = '
    Lade Verzeichnis...
    '; folderHierarchyDropdown.classList.remove('hidden'); folderHierarchyDropdown.classList.add('open'); folderTitleArrow.classList.add('open'); isDropdownOpen = true; const tree = await window.electronAPI.getFolderTree(selected.path); folderHierarchyDropdown.innerHTML = renderFolderTree(tree); }); function closeDropdown() { folderHierarchyDropdown.classList.add('hidden'); folderHierarchyDropdown.classList.remove('open'); folderTitleArrow.classList.remove('open'); isDropdownOpen = false; } // KEIN automatisches Schließen beim Klick außerhalb! function renderFolderTree(tree, level = 0) { if (!Array.isArray(tree)) return ''; return `'; } */