diff --git a/renderer/app.js b/renderer/app.js index abb6904..42ea938 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -20,6 +20,10 @@ let queueJobs = []; // { id, file, fileName, hoster, status, bytesUploaded, byte let _jobIndexById = new Map(); // id -> job (O(1) lookup) let _jobIndexByUploadId = new Map(); // uploadId -> job let selectedJobIds = new Set(); +let _sessionTotalBytes = 0; // Total bytes ever added to queue this session +let _sessionUploadedBytes = 0; // Bytes fully uploaded this session (done jobs) +let _sessionTrackedJobs = new Set(); // Job IDs already counted for totalBytes +let _sessionDoneJobs = new Set(); // Job IDs already counted for uploadedBytes let queueSortState = { key: 'filename', direction: 'asc' }; // History state @@ -919,6 +923,24 @@ function showContextMenu(x, y) { const startItem = menu.querySelector('[data-action="start-selected"]'); if (startItem) startItem.textContent = n > 1 ? `Ausgewählte starten (${n})` : 'Ausgewählte starten'; + // Dynamic "cancel hoster" items + const cancelSep = menu.querySelector('.ctx-hoster-cancel-sep'); + const cancelContainer = menu.querySelector('.ctx-hoster-cancel-items'); + const activeHosters = [...new Set(queueJobs.filter(j => j.status === 'uploading' || j.status === 'queued' || j.status === 'retrying' || j.status === 'getting-server' || j.status === 'preview').map(j => j.hoster))]; + cancelContainer.innerHTML = ''; + if (activeHosters.length > 0) { + cancelSep.style.display = ''; + activeHosters.forEach(h => { + const item = document.createElement('div'); + item.className = 'ctx-item ctx-item-danger'; + item.dataset.action = `cancel-hoster:${h}`; + item.textContent = `${h} abbrechen`; + cancelContainer.appendChild(item); + }); + } else { + cancelSep.style.display = 'none'; + } + menu.style.display = 'block'; const menuX = Math.min(x, window.innerWidth - menu.offsetWidth - 5); menu.style.left = menuX + 'px'; @@ -1116,6 +1138,24 @@ async function handleContextAction(action) { } else if (action === 'always-on-top') { alwaysOnTopState = !alwaysOnTopState; await window.api.setAlwaysOnTop(alwaysOnTopState); + } else if (action.startsWith('cancel-hoster:')) { + const hoster = action.replace('cancel-hoster:', ''); + const jobIds = []; + for (const job of queueJobs) { + if (job.hoster === hoster && (job.status === 'uploading' || job.status === 'queued' || job.status === 'retrying' || job.status === 'getting-server' || job.status === 'preview')) { + jobIds.push(job.id); + // Mark queued/preview jobs as error immediately + if (job.status === 'queued' || job.status === 'preview') { + job.status = 'error'; + job.error = 'Hoster abgebrochen'; + } + } + } + // Cancel active uploads via IPC + if (jobIds.length > 0) await window.api.cancelSelectedJobs(jobIds); + renderQueueTable(); + updateStatusBar(); + updateQueueActionButtons(); } else if (action.startsWith('shutdown-')) { const mode = action.replace('shutdown-', ''); await window.api.setShutdownAfterFinish(mode); @@ -1302,6 +1342,11 @@ function handleProgress(data) { job.status = data.status; job.bytesUploaded = data.bytesUploaded || 0; job.bytesTotal = data.bytesTotal || job.bytesTotal; + // Track session total bytes (survives removeFromQueueOnDone) + if (job.bytesTotal > 0 && !_sessionTrackedJobs.has(job.id)) { + _sessionTotalBytes += job.bytesTotal; + _sessionTrackedJobs.add(job.id); + } job.speedKbs = data.speedKbs || 0; job.elapsed = data.elapsed || 0; job.remaining = data.remaining || 0; @@ -1317,6 +1362,12 @@ function handleProgress(data) { maybeAddSessionFile(job); + // Track session uploaded bytes (survives removeFromQueueOnDone) + if (job.status === 'done' && !_sessionDoneJobs.has(job.id)) { + _sessionUploadedBytes += job.bytesTotal || 0; + _sessionDoneJobs.add(job.id); + } + // Remove finished jobs from queue immediately if setting is enabled if (job.status === 'done' && config.globalSettings && config.globalSettings.removeFromQueueOnDone) { removeJobFromIndex(job); @@ -1541,21 +1592,6 @@ function maybeAddSessionFile(job) { } } - if (job.status === 'error') { - const errorText = `[Fehler] ${job.error || ''}`; - if (!sessionFilesData.some((row) => row.isError && row.filename === job.fileName && row.host === job.hoster && row.link === errorText)) { - sessionFilesData.push({ - date: dt.text, - dateTs: dt.ts, - filename: job.fileName || '', - host: job.hoster || '', - link: errorText, - isError: true, - order: sessionFilesData.length - }); - renderRecentUploadsPanel(); - } - } } function applySummaryResults(summary) { @@ -1637,15 +1673,26 @@ function updateStatusBar() { document.getElementById('sbState').textContent = stateText; document.getElementById('sbSpeed').textContent = formatSpeed(lastUploadStats.globalSpeedKbs || 0); - const uploadedSize = Math.max(0, stats.totalSize - stats.remainingSize); - document.getElementById('sbTotal').textContent = `${formatSize(uploadedSize)} / ${formatSize(stats.totalSize)}`; + // Session-based bytes: survive removeFromQueueOnDone + // Uploaded = done jobs (session) + in-progress bytes still in queue + let inProgressBytes = 0; + for (const job of queueJobs) { + if (job.status === 'uploading' || job.status === 'getting-server' || job.status === 'retrying') { + inProgressBytes += job.bytesUploaded || 0; + } + } + const uploadedSize = _sessionUploadedBytes + inProgressBytes; + const totalSize = Math.max(stats.totalSize, _sessionTotalBytes); + document.getElementById('sbTotal').textContent = `${formatSize(uploadedSize)} / ${formatSize(totalSize)}`; document.getElementById('sbEta').textContent = `ETA ${etaSeconds > 0 ? formatTime(etaSeconds) : '--:--'}`; document.getElementById('sbConnections').textContent = `Connections: ${lastUploadStats.activeJobs || 0}`; document.getElementById('sbQueueCount').textContent = `Total: ${stats.total}`; document.getElementById('sbRemainingCount').textContent = `Remaining: ${stats.remaining}`; document.getElementById('sbInProgressCount').textContent = `In Progress: ${stats.inProgress}`; - document.getElementById('sbDoneCount').textContent = `Done: ${stats.done}`; - document.getElementById('sbErrorCount').textContent = `Error: ${stats.errors}`; + const sessionDone = sessionFilesData.filter(r => !r.isError).length; + const sessionErrors = sessionFilesData.filter(r => r.isError).length; + document.getElementById('sbDoneCount').textContent = `Done: ${sessionDone}`; + document.getElementById('sbErrorCount').textContent = `Error: ${sessionErrors}`; } // --- Health Check --- diff --git a/renderer/index.html b/renderer/index.html index 19ff2d8..157d622 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -265,6 +265,8 @@