Update from Git Manager GUI
This commit is contained in:
104
renderer/App.jsx
Normal file
104
renderer/App.jsx
Normal file
@@ -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 (
|
||||||
|
<div style={{ padding: 20 }}>
|
||||||
|
<h1>Git Manager GUI - High-End</h1>
|
||||||
|
<button onClick={() => setShowSettings(!showSettings)}>Settings</button>
|
||||||
|
{showSettings && <Settings />}
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<label>Platform:</label>
|
||||||
|
<select value={platform} onChange={e => setPlatform(e.target.value)}>
|
||||||
|
<option value="github">GitHub</option>
|
||||||
|
<option value="gitea">Gitea</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<button onClick={selectFolder}>Select Project Folder</button>
|
||||||
|
<span style={{ marginLeft: 10 }}>{folder}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<label>Branch:</label>
|
||||||
|
<select value={selectedBranch} onChange={e => setSelectedBranch(e.target.value)}>
|
||||||
|
{branches.map(b => <option key={b} value={b}>{b}</option>)}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Repository Name"
|
||||||
|
value={repoName}
|
||||||
|
onChange={e => setRepoName(e.target.value)} />
|
||||||
|
<button onClick={createRepoHandler} style={{ marginLeft: 10 }}>Create Repo</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<button onClick={pushProjectHandler}>Push / Update Project</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 10 }}>
|
||||||
|
<label>Progress:</label>
|
||||||
|
<progress value={progress} max="100" style={{ width: '100%' }} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<strong>Status: </strong>{status}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20 }}>
|
||||||
|
<h3>Commit Logs:</h3>
|
||||||
|
<ul>
|
||||||
|
{logs.map((log, i) => (
|
||||||
|
<li key={i}>{log}</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
41
renderer/Settings.jsx
Normal file
41
renderer/Settings.jsx
Normal file
@@ -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 (
|
||||||
|
<div style={{ padding: 20 }}>
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<div>
|
||||||
|
<label>GitHub Token:</label>
|
||||||
|
<input type="password" value={githubToken} onChange={e => setGithubToken(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Gitea Token:</label>
|
||||||
|
<input type="password" value={giteaToken} onChange={e => setGiteaToken(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label>Gitea URL:</label>
|
||||||
|
<input type="text" value={giteaURL} onChange={e => setGiteaURL(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
<button onClick={save} style={{ marginTop: 10 }}>Save</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
89
renderer/index.html
Normal file
89
renderer/index.html
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Git Manager Explorer</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app">
|
||||||
|
<div id="toolbar">
|
||||||
|
<button id="btnSettings">Settings</button>
|
||||||
|
<button id="btnSelectFolder">Select Folder</button>
|
||||||
|
<button id="btnPush">Push / Update</button>
|
||||||
|
<select id="platform"><option value="github">GitHub</option><option value="gitea">Gitea</option></select>
|
||||||
|
<span id="status" class="status"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="main">
|
||||||
|
<aside id="explorerPanel" class="card">
|
||||||
|
<h3>Project Files</h3>
|
||||||
|
<div id="fileTree" class="file-tree"></div>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<section id="detailsPanel">
|
||||||
|
<div id="repoDetails" class="card">
|
||||||
|
<div><label>Branch:</label><select id="branchSelect"></select></div>
|
||||||
|
<div><label>Repository Name:</label><input id="repoName" type="text" placeholder="repo-name"></div>
|
||||||
|
|
||||||
|
<!-- NEU: License Auswahl -->
|
||||||
|
<div style="margin-bottom: 5px;">
|
||||||
|
<label for="licenseSelect">License:</label>
|
||||||
|
<select id="licenseSelect">
|
||||||
|
<option value="">No License</option>
|
||||||
|
<option value="MIT">MIT License</option>
|
||||||
|
<option value="Apache-2.0">Apache License 2.0</option>
|
||||||
|
<option value="GPL-3.0">GNU General Public License v3.0</option>
|
||||||
|
<option value="BSD-3-Clause">BSD 3-Clause "New" or "Revised" License</option>
|
||||||
|
<option value="BSD-2-Clause">BSD 2-Clause "Simplified" License</option>
|
||||||
|
<option value="LGPL-3.0">GNU Lesser General Public License v3.0</option>
|
||||||
|
<option value="MPL-2.0">Mozilla Public License 2.0</option>
|
||||||
|
<option value="Unlicense">The Unlicense</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NEU: README Checkbox -->
|
||||||
|
<div style="margin-bottom: 10px;">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="createReadme" checked>
|
||||||
|
Initialize Repository with a README
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div><button id="btnCreateRepo">Create Repo</button></div>
|
||||||
|
<div style="margin-top:10px;"><button id="btnLoadGiteaRepos">Load My Gitea Repos</button>
|
||||||
|
<div id="giteaRepoContainer"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="previewArea" class="card">
|
||||||
|
<h3 id="previewTitle">Preview</h3>
|
||||||
|
<pre id="previewContent" class="preview-content">Select a file to preview</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="commitLogs" class="card">
|
||||||
|
<h3>Commit Logs</h3>
|
||||||
|
<div id="logs">No commits yet.</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Settings Modal -->
|
||||||
|
<div id="settingsModal" class="modal hidden">
|
||||||
|
<div class="modalContent card">
|
||||||
|
<h2>Settings</h2>
|
||||||
|
<label>GitHub Token</label><input id="githubToken" type="password">
|
||||||
|
<label>Gitea Token</label><input id="giteaToken" type="password">
|
||||||
|
<label>Gitea URL</label><input id="giteaURL" type="text" placeholder="https://gitea.example.com">
|
||||||
|
<div style="margin-top:10px;">
|
||||||
|
<button id="btnSaveSettings">Save</button>
|
||||||
|
<button id="btnCloseSettings">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="./renderer.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
renderer/index.js
Normal file
7
renderer/index.js
Normal file
@@ -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(<App />);
|
||||||
621
renderer/renderer.js
Normal file
621
renderer/renderer.js
Normal file
@@ -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);
|
||||||
53
renderer/style.css
Normal file
53
renderer/style.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user