621 lines
25 KiB
JavaScript
621 lines
25 KiB
JavaScript
// 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); |