diff --git a/renderer/App.jsx b/renderer/App.jsx
new file mode 100644
index 0000000..6491792
--- /dev/null
+++ b/renderer/App.jsx
@@ -0,0 +1,104 @@
+import React, { useState, useEffect } from 'react';
+import Settings from './Settings.jsx';
+
+export default function App() {
+ const [folder, setFolder] = useState('');
+ const [repoName, setRepoName] = useState('');
+ const [platform, setPlatform] = useState('github');
+ const [status, setStatus] = useState('');
+ const [showSettings, setShowSettings] = useState(false);
+ const [branches, setBranches] = useState([]);
+ const [selectedBranch, setSelectedBranch] = useState('master');
+ const [logs, setLogs] = useState([]);
+ const [progress, setProgress] = useState(0);
+
+ async function selectFolder() {
+ const selected = await window.electronAPI.selectFolder();
+ if (selected) setFolder(selected);
+ // Branches laden
+ if (selected) {
+ const branchList = await window.electronAPI.getBranches({ folder: selected });
+ setBranches(branchList);
+ if (branchList.includes('master')) setSelectedBranch('master');
+ }
+ }
+
+ async function createRepoHandler() {
+ if (!repoName) return alert('Repo Name required!');
+ setStatus('Creating repository...');
+ const result = await window.electronAPI.createRepo({ name: repoName, platform });
+ setStatus(result ? 'Repository created!' : 'Failed to create repository.');
+ }
+
+ async function pushProjectHandler() {
+ if (!folder) return alert('Select a project folder first!');
+ setStatus('Pushing project...');
+ setProgress(0);
+ const onProgress = (p) => setProgress(p); // Callback für Fortschritt
+ const result = await window.electronAPI.pushProject({ folder, branch: selectedBranch, onProgress });
+ setStatus(result ? 'Project pushed!' : 'Failed to push project.');
+ if (result) {
+ const logList = await window.electronAPI.getCommitLogs({ folder });
+ setLogs(logList);
+ }
+ }
+
+ return (
+
+
Git Manager GUI - High-End
+
+ {showSettings &&
}
+
+
+
+
+
+
+
+
+ {folder}
+
+
+
+
+
+
+
+
+ setRepoName(e.target.value)} />
+
+
+
+
+
+
+
+
+
+
+ Status: {status}
+
+
+
+
Commit Logs:
+
+ {logs.map((log, i) => (
+ - {log}
+ ))}
+
+
+
+ );
+}
diff --git a/renderer/Settings.jsx b/renderer/Settings.jsx
new file mode 100644
index 0000000..0c5450e
--- /dev/null
+++ b/renderer/Settings.jsx
@@ -0,0 +1,41 @@
+import React, { useState, useEffect } from 'react';
+
+export default function Settings() {
+ const [githubToken, setGithubToken] = useState('');
+ const [giteaToken, setGiteaToken] = useState('');
+ const [giteaURL, setGiteaURL] = useState('');
+
+ useEffect(() => {
+ window.electronAPI.loadCredentials().then(data => {
+ if (data) {
+ setGithubToken(data.githubToken || '');
+ setGiteaToken(data.giteaToken || '');
+ setGiteaURL(data.giteaURL || '');
+ }
+ });
+ }, []);
+
+ const save = () => {
+ window.electronAPI.saveCredentials({ githubToken, giteaToken, giteaURL });
+ alert('Settings saved securely!');
+ }
+
+ return (
+
+
Settings
+
+
+ setGithubToken(e.target.value)} />
+
+
+
+ setGiteaToken(e.target.value)} />
+
+
+
+ setGiteaURL(e.target.value)} />
+
+
+
+ );
+}
diff --git a/renderer/index.html b/renderer/index.html
new file mode 100644
index 0000000..cf0be02
--- /dev/null
+++ b/renderer/index.html
@@ -0,0 +1,89 @@
+
+
+
+
+ Git Manager Explorer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Preview
+
Select a file to preview
+
+
+
+
Commit Logs
+
No commits yet.
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/renderer/index.js b/renderer/index.js
new file mode 100644
index 0000000..63a54d1
--- /dev/null
+++ b/renderer/index.js
@@ -0,0 +1,7 @@
+import React from 'react';
+import { createRoot } from 'react-dom/client';
+import App from './App.jsx';
+
+const container = document.getElementById('root');
+const root = createRoot(container);
+root.render();
diff --git a/renderer/renderer.js b/renderer/renderer.js
new file mode 100644
index 0000000..891e681
--- /dev/null
+++ b/renderer/renderer.js
@@ -0,0 +1,621 @@
+// renderer.js — Explorer with repo drag-export and folder upload+git push + progress UI
+const $ = id => document.getElementById(id);
+
+let selectedFolder = null;
+let giteaCache = {}; // cache repo contents
+
+function setStatus(txt) { const s = $('status'); if (s) s.innerText = txt || ''; }
+
+/* -------------------------
+ Small dynamic progress UI (created if not present)
+ ------------------------- */
+function ensureProgressUI() {
+ if (document.getElementById('folderProgressContainer')) return;
+ const container = document.createElement('div');
+ container.id = 'folderProgressContainer';
+ container.style.position = 'fixed';
+ container.style.left = '50%';
+ container.style.top = '12px';
+ container.style.transform = 'translateX(-50%)';
+ container.style.zIndex = '10000';
+ container.style.width = '480px';
+ container.style.maxWidth = '90%';
+ container.style.padding = '6px';
+ container.style.background = 'rgba(20,20,30,0.95)';
+ container.style.borderRadius = '8px';
+ container.style.boxShadow = '0 6px 18px rgba(0,0,0,0.45)';
+ container.style.color = '#fff';
+ container.style.fontFamily = 'sans-serif';
+ container.style.display = 'none';
+
+ const text = document.createElement('div');
+ text.id = 'folderProgressText';
+ text.style.marginBottom = '6px';
+ text.style.fontSize = '13px';
+ container.appendChild(text);
+
+ const barWrap = document.createElement('div');
+ barWrap.style.width = '100%';
+ barWrap.style.height = '10px';
+ barWrap.style.background = '#333';
+ barWrap.style.borderRadius = '6px';
+ barWrap.style.overflow = 'hidden';
+
+ const bar = document.createElement('div');
+ bar.id = 'folderProgressBar';
+ bar.style.width = '0%';
+ bar.style.height = '100%';
+ bar.style.background = '#4caf50';
+ bar.style.transition = 'width 150ms linear';
+
+ barWrap.appendChild(bar);
+ container.appendChild(barWrap);
+ document.body.appendChild(container);
+}
+
+function showProgress(percent, text) {
+ ensureProgressUI();
+ const container = document.getElementById('folderProgressContainer');
+ const bar = document.getElementById('folderProgressBar');
+ const txt = document.getElementById('folderProgressText');
+ txt.innerText = text || '';
+ bar.style.width = `${percent}%`;
+ container.style.display = 'block';
+}
+
+function hideProgress() {
+ const container = document.getElementById('folderProgressContainer');
+ if (container) container.style.display = 'none';
+}
+
+/* -------------------------
+ Initialization & wiring
+ ------------------------- */
+async function init() {
+ const creds = await window.electronAPI.loadCredentials();
+ if (creds) {
+ if ($('githubToken')) $('githubToken').value = creds.githubToken || '';
+ if ($('giteaToken')) $('giteaToken').value = creds.giteaToken || '';
+ if ($('giteaURL')) $('giteaURL').value = creds.giteaURL || '';
+ }
+
+ if ($('btnLoadGiteaRepos')) $('btnLoadGiteaRepos').addEventListener('click', loadGiteaRepos);
+ if ($('btnSelectFolder')) $('btnSelectFolder').addEventListener('click', selectLocalFolder);
+ if ($('btnPush')) $('btnPush').addEventListener('click', pushLocalFolder);
+ if ($('btnCreateRepo')) $('btnCreateRepo').addEventListener('click', createRepoHandler);
+
+ if ($('btnSettings')) $('btnSettings').addEventListener('click', () => $('settingsModal').classList.remove('hidden'));
+ if ($('btnCloseSettings')) $('btnCloseSettings').addEventListener('click', () => $('settingsModal').classList.add('hidden'));
+ if ($('btnSaveSettings')) $('btnSaveSettings').addEventListener('click', saveSettings);
+
+ // global drag-over/drop: prevent default
+ document.addEventListener('dragover', e => e.preventDefault());
+ document.addEventListener('drop', e => e.preventDefault());
+
+ // subscribe to progress events
+ window.electronAPI.onFolderUploadProgress((payload) => {
+ const { processed, total, percent } = payload;
+ showProgress(percent, `Upload: ${processed}/${total} (${percent}%)`);
+ if (processed >= total) setTimeout(hideProgress, 600);
+ });
+ window.electronAPI.onFolderDownloadProgress((payload) => {
+ const { processed, total, percent } = payload;
+ showProgress(percent, `Download: ${processed}/${total} (${percent}%)`);
+ if (processed >= total) setTimeout(hideProgress, 600);
+ });
+
+ window.electronAPI.onPushProgress(p => setStatus('Pushing... ' + p + '%'));
+ ensureProgressUI();
+}
+
+/* -------------------------
+ Settings / buttons
+ ------------------------- */
+async function saveSettings() {
+ const data = { githubToken: $('githubToken')?.value, giteaToken: $('giteaToken')?.value, giteaURL: $('giteaURL')?.value };
+ const r = await window.electronAPI.saveCredentials(data);
+ setStatus(r.ok ? 'Settings saved' : 'Save failed: ' + r.error);
+ $('settingsModal').classList.add('hidden');
+}
+
+async function loadGiteaRepos() {
+ setStatus('Loading Gitea repos...');
+ const creds = await window.electronAPI.loadCredentials();
+ if (!creds || !creds.giteaToken || !creds.giteaURL) { setStatus('Set Gitea token & URL in Settings'); return; }
+ const res = await window.electronAPI.listGiteaRepos({ token: creds.giteaToken, url: creds.giteaURL });
+ if (!res.ok) { setStatus('Failed to load repos: ' + res.error); return; }
+ renderGiteaRoots(res.repos);
+ setStatus(`Loaded ${res.repos.length} repos`);
+}
+
+async function selectLocalFolder() {
+ const folder = await window.electronAPI.selectFolder();
+ if (!folder) return;
+ selectedFolder = folder;
+ setStatus('Selected local folder: ' + folder);
+ await refreshLocalTree(folder);
+ await loadBranches(folder);
+ await loadCommitLogs(folder);
+}
+
+async function pushLocalFolder() {
+ if (!selectedFolder) return alert('Select local folder first');
+ const branch = $('branchSelect')?.value || 'main';
+ setStatus('Pushing...');
+ const res = await window.electronAPI.pushProject({ folder: selectedFolder, branch, repoName: $('repoName')?.value, platform: $('platform')?.value });
+ setStatus(res.ok ? 'Push succeeded' : 'Push failed: ' + res.error);
+ if (res.ok) loadCommitLogs(selectedFolder);
+}
+
+async function createRepoHandler() {
+ const name = $('repoName')?.value?.trim();
+ const platform = $('platform')?.value;
+ const license = $('licenseSelect')?.value || '';
+ const autoInit = $('createReadme')?.checked;
+
+ if (!name) return alert('Repo name required');
+ setStatus('Creating repo...');
+ const res = await window.electronAPI.createRepo({ name, platform, license, autoInit });
+ setStatus(res.ok ? 'Repo created' : 'Create failed: ' + res.error);
+}
+
+/* -------------------------
+ Render Gitea roots + draggable/droppable nodes
+ ------------------------- */
+function renderGiteaRoots(repos) {
+ const tree = $('fileTree');
+ const prev = tree.querySelector('[data-is-gitea-root="1"]');
+ if (prev) {
+ const next = prev.nextSibling;
+ prev.remove();
+ if (next && next.classList && next.classList.contains('gitea-children-wrapper')) next.remove();
+ }
+
+ const groot = document.createElement('div');
+ groot.className = 'file-node folder';
+ groot.dataset.isGiteaRoot = '1';
+ groot.innerText = 'Gitea Repositories';
+ groot.style.fontWeight = '800';
+ const wrapper = document.createElement('div');
+ wrapper.className = 'gitea-children-wrapper';
+ wrapper.style.marginLeft = '6px';
+
+ repos.forEach(repo => {
+ let owner = (repo.owner && (repo.owner.login || repo.owner.username)) || null;
+ let repoName = repo.name;
+ if (!owner && repo.full_name && repo.full_name.includes('/')) {
+ const parts = repo.full_name.split('/');
+ owner = parts[0]; repoName = parts[1];
+ }
+
+ const rdiv = document.createElement('div');
+ rdiv.className = 'file-node folder';
+ rdiv.innerText = (repo.full_name || `${owner}/${repoName}`);
+ rdiv.dataset.giteaOwner = owner;
+ rdiv.dataset.giteaRepo = repoName;
+ rdiv.dataset.giteaClone = repo.clone_url || repo.clone_url_ssh || (repo.html_url ? `${repo.html_url}.git` : '');
+ rdiv.style.paddingLeft = '8px';
+ rdiv.draggable = true;
+
+ // dragstart
+ rdiv.addEventListener('dragstart', async (ev) => {
+ ev.preventDefault();
+ setStatus(`Preparing download for ${owner}/${repoName} ...`);
+ showProgress(0, `Preparing ${owner}/${repoName}...`);
+ const res = await window.electronAPI.prepareDownloadDrag({ owner, repo: repoName, path: '' });
+ if (!res.ok) { setStatus('Prepare failed: ' + res.error); hideProgress(); return; }
+ const tempPath = res.tempPath;
+ window.electronAPI.startNativeDrag(tempPath);
+ setStatus('Drag started — drop to desktop or file manager.');
+ hideProgress();
+ });
+
+ // drop
+ rdiv.addEventListener('dragover', (ev) => { ev.preventDefault(); rdiv.classList.add('drag-target'); });
+ rdiv.addEventListener('dragleave', () => { rdiv.classList.remove('drag-target'); });
+
+ rdiv.addEventListener('drop', async (ev) => {
+ ev.preventDefault();
+ rdiv.classList.remove('drag-target');
+ const dt = ev.dataTransfer;
+ if (!dt || !dt.files || dt.files.length === 0) {
+ setStatus('No files/folders dropped.');
+ return;
+ }
+ const paths = Array.from(dt.files).map(f => f.path);
+ for (const p of paths) {
+ setStatus(`Uploading ${p} to ${owner}/${repoName} ...`);
+ showProgress(0, `Uploading ${ppathBasename(p)} → ${owner}/${repoName}`);
+ const res = await window.electronAPI.uploadAndPush({ localFolder: p, owner, repo: repoName, destPath: '', cloneUrl: rdiv.dataset.giteaClone, branch: 'main' });
+ if (!res.ok) setStatus('Upload failed: ' + res.error);
+ else setStatus(res.usedGit ? `Upload+Push OK (git used)` : `Upload OK (API used)`);
+ hideProgress();
+ }
+ try { await expandGiteaDir(rdiv, owner, repoName, rdiv.dataset.giteaPath || ''); } catch (_) {}
+ });
+
+ // Click - FIXED: ref: 'main' explicitly added
+ rdiv.addEventListener('click', async (ev) => {
+ ev.stopPropagation();
+ if (rdiv._expanded) {
+ const next = rdiv.nextSibling;
+ if (next && next.classList && next.classList.contains('gitea-children')) next.remove();
+ rdiv._expanded = false;
+ return;
+ }
+ setStatus(`Loading ${owner}/${repoName}...`);
+ const res = await window.electronAPI.getGiteaRepoContents({ owner, repo: repoName, path: '', ref: 'main' });
+ if (!res.ok) { setStatus('Failed to load repo: ' + res.error); return; }
+ const cont = document.createElement('div');
+ cont.className = 'gitea-children';
+ cont.style.marginLeft = '12px';
+ (res.items || []).forEach(item => {
+ const node = document.createElement('div');
+ node.className = 'file-node ' + (item.type === 'dir' ? 'folder' : 'file');
+ node.innerText = item.name;
+ node.dataset.giteaOwner = owner;
+ node.dataset.giteaRepo = repoName;
+ node.dataset.giteaPath = item.path;
+ node.style.paddingLeft = '8px';
+
+ node.addEventListener('dragover', e => e.preventDefault());
+ node.addEventListener('drop', async (e) => {
+ e.preventDefault();
+ const dt = e.dataTransfer;
+ if (!dt || !dt.files || dt.files.length === 0) { setStatus('No files dropped'); return; }
+ const dropped = Array.from(dt.files).map(f => f.path);
+ for (const p of dropped) {
+ setStatus(`Uploading ${p} to ${owner}/${repoName}/${item.path} ...`);
+ showProgress(0, `Uploading ${ppathBasename(p)} → ${owner}/${repoName}/${item.path}`);
+ const res = await window.electronAPI.uploadAndPush({ localFolder: p, owner, repo: repoName, destPath: item.path, cloneUrl: rdiv.dataset.giteaClone, branch: 'main' });
+ if (!res.ok) setStatus('Upload failed: ' + res.error);
+ else setStatus(res.usedGit ? `Upload+Push OK (git used)` : `Upload OK (API used)`);
+ hideProgress();
+ }
+ await expandGiteaDir(node, owner, repoName, item.path);
+ });
+
+ node.addEventListener('click', async (e) => {
+ e.stopPropagation();
+ if (item.type === 'dir') await expandGiteaDir(node, owner, repoName, item.path);
+ else await previewGiteaFile(owner, repoName, item.path);
+ document.querySelectorAll('.file-node').forEach(n => n.classList.remove('active'));
+ node.classList.add('active');
+ });
+
+ node.addEventListener('contextmenu', (ev) => {
+ ev.preventDefault();
+ showGiteaContextMenu(node, ev.clientX, ev.clientY);
+ });
+
+ cont.appendChild(node);
+ });
+ rdiv.after(cont);
+ rdiv._expanded = true;
+ setStatus('');
+ });
+
+ // Context menu for Repo
+ rdiv.addEventListener('contextmenu', (ev) => {
+ ev.preventDefault();
+ showRepoContextMenu(rdiv, ev.clientX, ev.clientY, owner, repoName, rdiv.dataset.giteaClone);
+ });
+
+ wrapper.appendChild(rdiv);
+ });
+
+ tree.prepend(groot);
+ groot.after(wrapper);
+}
+
+function ppathBasename(p) {
+ try { return p.split(/[\\/]/).pop(); } catch (_) { return p; }
+}
+
+/* Expand dir / render children / preview */
+async function expandGiteaDir(nodeEl, owner, repo, dirPath) {
+ if (nodeEl._expanded) {
+ const next = nodeEl.nextSibling;
+ if (next && next.classList && next.classList.contains('gitea-sub')) next.remove();
+ nodeEl._expanded = false;
+ return;
+ }
+ const key = `${owner}/${repo}:${dirPath}`;
+ const ref = 'main'; // FIXED: Always use 'main'
+ if (giteaCache[key]) { renderGiteaChildren(nodeEl, giteaCache[key], owner, repo); nodeEl._expanded = true; return; }
+ setStatus(`Loading ${dirPath}...`);
+ const res = await window.electronAPI.getGiteaRepoContents({ owner, repo, path: dirPath, ref });
+ if (!res.ok) { setStatus('Failed: ' + res.error); return; }
+ giteaCache[key] = res.items;
+ renderGiteaChildren(nodeEl, res.items, owner, repo);
+ nodeEl._expanded = true;
+ setStatus('');
+}
+
+function renderGiteaChildren(parentNode, items, owner, repo) {
+ const container = document.createElement('div');
+ container.className = 'gitea-sub';
+ container.style.marginLeft = '12px';
+ items.forEach(item => {
+ const el = document.createElement('div');
+ el.className = 'file-node ' + (item.type === 'dir' ? 'folder' : 'file');
+ el.innerText = item.name;
+ el.dataset.giteaOwner = owner;
+ el.dataset.giteaRepo = repo;
+ el.dataset.giteaPath = item.path;
+ el.style.paddingLeft = '8px';
+ el.addEventListener('click', async (ev) => {
+ ev.stopPropagation();
+ if (item.type === 'dir') await expandGiteaDir(el, owner, repo, item.path);
+ else await previewGiteaFile(owner, repo, item.path);
+ document.querySelectorAll('.file-node').forEach(n => n.classList.remove('active'));
+ el.classList.add('active');
+ });
+
+ el.addEventListener('dragover', e => e.preventDefault());
+ el.addEventListener('drop', async (e) => {
+ e.preventDefault();
+ const paths = Array.from(e.dataTransfer.files).map(f => f.path);
+ for (const p of paths) {
+ setStatus(`Uploading ${p} to ${owner}/${repo}/${item.path} ...`);
+ showProgress(0, `Uploading ${ppathBasename(p)} → ${owner}/${repo}/${item.path}`);
+ const res = await window.electronAPI.uploadAndPush({ localFolder: p, owner, repo, destPath: item.path, cloneUrl: parentNode.previousSibling?.dataset?.giteaClone, branch: 'main' });
+ if (!res.ok) setStatus('Upload failed: ' + res.error);
+ else setStatus(res.usedGit ? `Upload+Push OK (git used)` : `Upload OK (API used)`);
+ hideProgress();
+ }
+ await expandGiteaDir(el, owner, repo, item.path);
+ });
+
+ el.addEventListener('contextmenu', (ev) => {
+ ev.preventDefault();
+ showGiteaContextMenu(el, ev.clientX, ev.clientY);
+ });
+
+ container.appendChild(el);
+ });
+ parentNode.after(container);
+}
+
+async function previewGiteaFile(owner, repo, filePath) {
+ setStatus(`Loading file ${filePath}...`);
+ const res = await window.electronAPI.getGiteaFileContent({ owner, repo, path: filePath, ref: 'main' });
+ if (!res.ok) { setStatus('Failed: ' + res.error); $('previewTitle').innerText = filePath; $('previewContent').innerText = ''; return; }
+ $('previewTitle').innerText = `${owner}/${repo}:${filePath}`;
+ $('previewContent').innerText = res.content;
+ setStatus('');
+}
+
+/* Local tree */
+async function refreshLocalTree(folder) {
+ const res = await window.electronAPI.getFileTree({ folder, exclude: ['node_modules'], maxDepth: 5 });
+ const container = $('fileTree');
+ const existingLocal = container.querySelector('[data-local-root="1"]');
+ if (existingLocal) {
+ const next = existingLocal.nextSibling;
+ existingLocal.remove();
+ if (next && next.classList && next.classList.contains('local-children')) next.remove();
+ }
+ if (!res.ok) { setStatus('Local tree error: ' + res.error); return; }
+ const root = document.createElement('div');
+ root.className = 'file-node folder';
+ root.dataset.localRoot = '1';
+ root.innerText = 'Local: ' + folder.split(/[\\/]/).pop();
+ root.style.fontWeight = '800';
+ root.addEventListener('click', () => {
+ const next = root.nextSibling;
+ if (next && next.classList && next.classList.contains('local-children')) next.classList.toggle('hidden');
+ });
+ const wrapper = document.createElement('div');
+ wrapper.className = 'local-children';
+ wrapper.style.marginLeft = '8px';
+ function build(node, parent) {
+ const el = document.createElement('div');
+ el.className = 'file-node ' + (node.isDirectory ? 'folder' : 'file');
+ el.innerText = node.name;
+ el.dataset.path = node.path;
+ el.style.paddingLeft = (node.depth * 10 + 8) + 'px';
+ el.addEventListener('click', async (ev) => {
+ ev.stopPropagation();
+ if (!node.isDirectory) {
+ const r = await window.electronAPI.readFile({ path: node.path });
+ if (r.ok) { $('previewTitle').innerText = node.path; $('previewContent').innerText = r.content; }
+ else setStatus('Read failed: ' + r.error);
+ }
+ });
+ parent.appendChild(el);
+ if (node.children && node.children.length) node.children.forEach(c => build(c, parent));
+ }
+ res.tree.forEach(n => build(n, wrapper));
+ const tree = $('fileTree');
+ tree.appendChild(root);
+ root.after(wrapper);
+}
+
+/* Branches & commits */
+async function loadBranches(folder) {
+ const res = await window.electronAPI.getBranches({ folder });
+ const sel = $('branchSelect');
+ sel.innerHTML = '';
+ if (res.ok) res.branches.forEach(b => sel.appendChild(new Option(b, b)));
+ else sel.appendChild(new Option('main','main'));
+}
+async function loadCommitLogs(folder) {
+ const res = await window.electronAPI.getCommitLogs({ folder });
+ const container = $('logs');
+ container.innerHTML = '';
+ if (!res.ok) return container.innerText = 'No logs or error: ' + res.error;
+ res.logs.forEach(l => { const d = document.createElement('div'); d.innerText = l; container.appendChild(d); });
+}
+
+/* Context menu */
+function showGiteaContextMenu(node, x, y) {
+ const old = document.getElementById('ctxMenu');
+ if (old) old.remove();
+ const menu = document.createElement('div');
+ menu.id = 'ctxMenu';
+ menu.style.position = 'fixed';
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+ menu.style.background = '#1b1b2a';
+ menu.style.border = '1px solid #333';
+ menu.style.padding = '6px';
+ menu.style.zIndex = '9999';
+ menu.style.borderRadius = '6px';
+ menu.style.color = '#fff';
+
+ const owner = node.dataset.giteaOwner;
+ const repo = node.dataset.giteaRepo;
+ const filePath = node.dataset.giteaPath || '';
+
+ if (node.classList.contains('file')) {
+ const dl = document.createElement('div'); dl.innerText = 'Download File'; dl.style.padding = '6px'; dl.style.cursor = 'pointer';
+ dl.onclick = async () => {
+ const res = await window.electronAPI.downloadGiteaFile({ owner, repo, path: filePath });
+ setStatus(res.ok ? `Saved to ${res.savedTo}` : 'Download failed: ' + res.error);
+ menu.remove();
+ };
+ menu.appendChild(dl);
+ }
+
+ if (node.classList.contains('folder')) {
+ const dlf = document.createElement('div'); dlf.innerText = 'Download Folder (recursive)'; dlf.style.padding = '6px'; dlf.style.cursor = 'pointer';
+ dlf.onclick = async () => {
+ setStatus(`Choose local folder to save ${owner}/${repo}/${filePath} ...`);
+ showProgress(0, 'Starting download...');
+ const res = await window.electronAPI.downloadGiteaFolder({ owner, repo, path: filePath });
+ setStatus(res.ok ? `Folder downloaded to ${res.savedTo}` : 'Folder download failed: ' + res.error);
+ hideProgress();
+ menu.remove();
+ };
+ menu.appendChild(dlf);
+ }
+
+ const up = document.createElement('div'); up.innerText = 'Upload Local Folder Here'; up.style.padding = '6px'; up.style.cursor = 'pointer';
+ up.onclick = async () => {
+ const folderSel = await window.electronAPI.selectFolder();
+ if (!folderSel) { setStatus('No folder selected'); menu.remove(); return; }
+ setStatus(`Uploading local folder ${folderSel} to ${owner}/${repo}/${filePath} ...`);
+ showProgress(0, 'Starting upload...');
+ const res = await window.electronAPI.uploadAndPush({ localFolder: folderSel, owner, repo, destPath: filePath, cloneUrl: node.dataset.giteaClone, branch: 'main' });
+ setStatus(res.ok ? (res.usedGit ? 'Folder uploaded and pushed (git used)' : 'Folder uploaded (API)') : 'Upload failed: ' + res.error);
+ hideProgress();
+ menu.remove();
+ if (node.classList.contains('folder')) await expandGiteaDir(node, owner, repo, filePath);
+ };
+ menu.appendChild(up);
+
+ if (node.classList.contains('file')) {
+ const upf = document.createElement('div'); upf.innerText = 'Upload & Overwrite File'; upf.style.padding = '6px'; upf.style.cursor = 'pointer';
+ upf.onclick = async () => {
+ const sel = await window.electronAPI.selectFile();
+ if (!sel.ok || !sel.files || sel.files.length === 0) { setStatus('No file selected'); menu.remove(); return; }
+ const files = sel.files;
+ setStatus(`Uploading ${files.length} file(s) to ${owner}/${repo}/${filePath} ...`);
+ showProgress(0, 'Uploading files...');
+ const res = await window.electronAPI.uploadGiteaFile({ localPath: files, owner, repo, destPath: filePath, message: 'Upload via GUI', branch: 'main' });
+ setStatus(res.ok ? 'Upload complete' : 'Upload failed: ' + res.error);
+ hideProgress();
+ menu.remove();
+ if (node.classList.contains('folder')) await expandGiteaDir(node, owner, repo, filePath);
+ };
+ menu.appendChild(upf);
+ }
+
+ document.body.appendChild(menu);
+ document.addEventListener('click', function handler() { menu.remove(); document.removeEventListener('click', handler); });
+}
+
+/* Context menu for Repository Root */
+function showRepoContextMenu(repoEl, x, y, owner, repoName, cloneUrl) {
+ const old = document.getElementById('ctxMenu');
+ if (old) old.remove();
+
+ const menu = document.createElement('div');
+ menu.id = 'ctxMenu';
+ menu.style.position = 'fixed';
+ menu.style.left = x + 'px';
+ menu.style.top = y + 'px';
+ menu.style.background = '#1b1b2a';
+ menu.style.border = '1px solid #333';
+ menu.style.padding = '6px';
+ menu.style.zIndex = '9999';
+ menu.style.borderRadius = '6px';
+ menu.style.color = '#fff';
+
+ // Option 1: Download Repository
+ const dlRepo = document.createElement('div');
+ dlRepo.innerText = 'Download Repository';
+ dlRepo.style.padding = '6px';
+ dlRepo.style.cursor = 'pointer';
+ dlRepo.style.borderBottom = '1px solid #333';
+ dlRepo.onclick = async () => {
+ setStatus(`Downloading repository ${owner}/${repoName}...`);
+ showProgress(0, 'Starting download...');
+ const res = await window.electronAPI.downloadGiteaFolder({ owner, repo: repoName, path: '' });
+ setStatus(res.ok ? `Repository downloaded to ${res.savedTo}` : 'Download failed: ' + res.error);
+ hideProgress();
+ menu.remove();
+ };
+ menu.appendChild(dlRepo);
+
+ // Option 2: Upload Folder to Repository
+ const upRepo = document.createElement('div');
+ upRepo.innerText = 'Upload Folder to Repository';
+ upRepo.style.padding = '6px';
+ upRepo.style.cursor = 'pointer';
+ upRepo.style.borderBottom = '1px solid #333';
+ upRepo.onclick = async () => {
+ const folderSel = await window.electronAPI.selectFolder();
+ if (!folderSel) { setStatus('No folder selected'); menu.remove(); return; }
+ setStatus(`Uploading local folder to ${owner}/${repoName} ...`);
+ showProgress(0, 'Starting upload...');
+ const res = await window.electronAPI.uploadAndPush({ localFolder: folderSel, owner, repo: repoName, destPath: '', cloneUrl: cloneUrl, branch: 'main' });
+ setStatus(res.ok ? (res.usedGit ? 'Upload & Push successful (git)' : 'Upload successful (API)') : 'Upload failed: ' + res.error);
+ hideProgress();
+ menu.remove();
+ if (repoEl._expanded) {
+ const next = repoEl.nextSibling;
+ if (next && next.classList && next.classList.contains('gitea-children')) {
+ next.remove();
+ repoEl._expanded = false;
+ repoEl.click(); // Re-expand
+ }
+ }
+ };
+ menu.appendChild(upRepo);
+
+ // Option 3: Delete Repository
+ const delRepo = document.createElement('div');
+ delRepo.innerText = 'Delete Repository';
+ delRepo.style.padding = '6px';
+ delRepo.style.cursor = 'pointer';
+ delRepo.style.color = '#ff5555';
+ delRepo.style.fontWeight = 'bold';
+ delRepo.onclick = async () => {
+ if (!confirm(`Are you sure you want to DELETE ${owner}/${repoName}? This cannot be undone.`)) {
+ menu.remove();
+ return;
+ }
+ setStatus(`Deleting repository ${owner}/${repoName}...`);
+ const res = await window.electronAPI.deleteGiteaRepo({ owner, repo: repoName });
+ if (res.ok) {
+ setStatus('Repository deleted successfully');
+ repoEl.remove();
+ } else {
+ setStatus('Delete failed: ' + res.error);
+ }
+ menu.remove();
+ };
+ menu.appendChild(delRepo);
+
+ document.body.appendChild(menu);
+ const closeHandler = () => {
+ menu.remove();
+ document.removeEventListener('click', closeHandler);
+ };
+ setTimeout(() => document.addEventListener('click', closeHandler), 10);
+}
+
+/* Start */
+window.addEventListener('DOMContentLoaded', init);
\ No newline at end of file
diff --git a/renderer/style.css b/renderer/style.css
new file mode 100644
index 0000000..544648c
--- /dev/null
+++ b/renderer/style.css
@@ -0,0 +1,53 @@
+/* Dark Explorer Style */
+:root {
+ --bg: #141423;
+ --panel: #1f1f2f;
+ --muted: #9aa0b4;
+ --accent: #00bcd4;
+ --card: #242438;
+ --panel-2: #2b2b3d;
+}
+
+html,body { height:100%; margin:0; background:var(--bg); color:#e6e6e6; font-family: "Segoe UI", Roboto, Arial; }
+#toolbar { display:flex; align-items:center; gap:8px; padding:10px; background:var(--panel); border-bottom:1px solid #2f2f4f; }
+#toolbar button, #toolbar select { background:var(--accent); color:#022; border:none; padding:6px 10px; border-radius:6px; cursor:pointer; }
+#toolbar .status { margin-left:8px; color:var(--muted); font-weight:600; }
+
+#main { display:flex; height: calc(100vh - 52px); gap:10px; padding:12px; box-sizing:border-box; }
+aside#explorerPanel { width:320px; background:var(--panel-2); border-radius:8px; padding:10px; overflow:auto; box-shadow: 0 6px 18px rgba(0,0,0,0.6); }
+#fileTree { font-family: monospace; font-size:13px; }
+
+#detailsPanel { flex:1; display:flex; flex-direction:column; gap:12px; overflow:auto; }
+.card { background:var(--card); padding:12px; border-radius:8px; box-shadow: 0 6px 18px rgba(0,0,0,0.6); }
+input, select { background:#222231; color:#e6e6e6; border:1px solid #3a3a5a; padding:6px; border-radius:6px; width:100%; box-sizing:border-box; }
+
+/* FIX: Styling für Checkboxen, damit sie nicht die volle Breite einnehmen */
+input[type="checkbox"] {
+ width: auto;
+ margin-right: 8px;
+ vertical-align: middle;
+ cursor: pointer;
+}
+
+.file-node { padding:6px 8px; border-radius:6px; margin:2px 0; }
+.file-node:hover { background: rgba(255,255,255,0.02); cursor:pointer; }
+.file-node.folder { font-weight:700; color:#cfefff; }
+.file-node.file { color:#dfe7ff; }
+
+.preview-content { background:#0f1020; color:#dcecff; padding:10px; border-radius:6px; height:260px; overflow:auto; white-space:pre-wrap; font-family: Consolas, Monaco, monospace; }
+
+#logs { max-height:220px; overflow:auto; font-family:monospace; font-size:13px; background:#0b0b12; padding:8px; border-radius:6px; }
+#logs div { padding:6px; border-bottom:1px solid #1b1b2a; }
+
+.modal { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; background:rgba(0,0,0,0.6); }
+.modalContent { width:420px; padding:16px; }
+.modal.hidden { display:none; }
+
+.context-menu {
+ font-family: sans-serif;
+ font-size: 13px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.5);
+}
+.context-menu div:hover {
+ background-color: #555;
+}
\ No newline at end of file