Compare commits

...

6 Commits

Author SHA1 Message Date
Administrator
7749699830 release: v3.3.58 2026-06-09 06:02:16 +02:00
Administrator
de371e56a3 fix(ui): hydrate missing file sizes for queued/waiting rows + show '...' fallback 2026-06-09 06:01:42 +02:00
Administrator
b93617ace9 release: v3.3.57 2026-06-09 05:03:05 +02:00
Administrator
82e0163d3f fix(ui): 'Ausgewählte starten' during active upload also accepts preview jobs 2026-06-09 05:02:28 +02:00
Administrator
05fae3209d release: v3.3.56 2026-06-09 04:58:22 +02:00
Administrator
0f72478a2e fix(ui): remove batch summary modal at end of upload 2026-06-09 04:57:54 +02:00
3 changed files with 56 additions and 29 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "3.3.55", "version": "3.3.58",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously", "description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js", "main": "main.js",
"scripts": { "scripts": {

View File

@ -99,6 +99,7 @@ async function init() {
syncSelectedUploadHosters(); syncSelectedUploadHosters();
restoreQueueStateFromConfig(); restoreQueueStateFromConfig();
await _autoDeduplicateFromLog(); await _autoDeduplicateFromLog();
_hydrateMissingJobSizes();
renderHosterSummary(); renderHosterSummary();
renderHosterModal(); renderHosterModal();
renderSettings(); renderSettings();
@ -997,12 +998,54 @@ function scheduleStatusChangeUpdate() {
}); });
} }
function _hydrateMissingJobSizes(jobsLike) {
if (!window.api || !window.api.getFileSizes) return;
const paths = [];
const seen = new Set();
const source = Array.isArray(jobsLike) ? jobsLike : queueJobs;
for (const j of source) {
if (!j || !j.file) continue;
if (j.bytesTotal && j.bytesTotal > 0) continue;
if (seen.has(j.file)) continue;
seen.add(j.file);
paths.push(j.file);
}
if (paths.length === 0) return;
window.api.getFileSizes(paths).then((sizeMap) => {
if (!sizeMap || typeof sizeMap !== 'object') return;
let changed = false;
for (const j of queueJobs) {
if (sizeMap[j.file] && (!j.bytesTotal || j.bytesTotal === 0)) {
j.bytesTotal = sizeMap[j.file];
changed = true;
}
}
for (const f of selectedFiles) {
if (sizeMap[f.path] && (!f.size || f.size === 0)) f.size = sizeMap[f.path];
}
if (changed) {
_queueStatsCache = null;
if (typeof renderQueueTable === 'function') renderQueueTable();
if (typeof updateStatusBar === 'function') updateStatusBar();
}
}).catch(() => {});
}
function _formatUploadedSize(job) {
const bt = job.bytesTotal || 0;
const bu = job.bytesUploaded || 0;
const s = job.status;
if (s === 'preview') return bt > 0 ? formatSize(bt) : '...';
if (s === 'queued' || s === 'getting-server' || s === 'retrying') {
return bt > 0 ? `${formatSize(bu)} / ${formatSize(bt)}` : '...';
}
return `${formatSize(bu)} / ${formatSize(bt)}`;
}
function buildRowHtml(job) { function buildRowHtml(job) {
const statusClass = `status-${job.status}`; const statusClass = `status-${job.status}`;
const rowClass = `queue-row ${statusClass}${selectedJobIds.has(job.id) ? ' selected' : ''}`; const rowClass = `queue-row ${statusClass}${selectedJobIds.has(job.id) ? ' selected' : ''}`;
const uploadedSize = job.status === 'preview' const uploadedSize = _formatUploadedSize(job);
? (job.bytesTotal > 0 ? formatSize(job.bytesTotal) : '...')
: `${formatSize(job.bytesUploaded)} / ${formatSize(job.bytesTotal)}`;
const statusText = getStatusText(job); const statusText = getStatusText(job);
const elapsed = formatTime(job.elapsed); const elapsed = formatTime(job.elapsed);
const remaining = formatTime(job.remaining); const remaining = formatTime(job.remaining);
@ -1032,9 +1075,7 @@ function buildRowHtml(job) {
// In-place update of a single row's cells (avoids full innerHTML rebuild) // In-place update of a single row's cells (avoids full innerHTML rebuild)
function _updateRowInPlace(tr, job) { function _updateRowInPlace(tr, job) {
const statusClass = `status-${job.status}`; const statusClass = `status-${job.status}`;
const uploadedSize = job.status === 'preview' const uploadedSize = _formatUploadedSize(job);
? (job.bytesTotal > 0 ? formatSize(job.bytesTotal) : '...')
: `${formatSize(job.bytesUploaded)} / ${formatSize(job.bytesTotal)}`;
const statusText = getStatusText(job); const statusText = getStatusText(job);
const elapsed = formatTime(job.elapsed); const elapsed = formatTime(job.elapsed);
const remaining = formatTime(job.remaining); const remaining = formatTime(job.remaining);
@ -1728,6 +1769,7 @@ async function startUpload() {
if (uploading) return; if (uploading) return;
uploading = true; // set immediately to prevent double-click race uploading = true; // set immediately to prevent double-click race
updateQueueActionButtons(); updateQueueActionButtons();
_hydrateMissingJobSizes();
const hosters = getSelectedHosters(); const hosters = getSelectedHosters();
if (queueJobs.length === 0 && selectedFiles.length > 0) { if (queueJobs.length === 0 && selectedFiles.length > 0) {
@ -1800,10 +1842,13 @@ function _markSkippedJobs(result) {
async function startSelectedUpload() { async function startSelectedUpload() {
if (uploading) { if (uploading) {
// Batch already running — add selected jobs (queued/error/aborted/skipped) to running batch _hydrateMissingJobSizes();
// Upload-manager has duplicate protection (skips jobs already tracked) const addable = queueJobs.filter(j => selectedJobIds.has(j.id) && isStartableQueueStatus(j.status));
const addable = queueJobs.filter(j => selectedJobIds.has(j.id) && ['queued', 'error', 'aborted', 'skipped'].includes(j.status)); if (addable.length === 0) {
if (addable.length > 0) { if (selectedJobIds.size > 0) showCopyToast('Keine startbaren Jobs ausgewählt (alle laufen schon oder sind fertig).');
return;
}
{
addable.forEach(j => { addable.forEach(j => {
j.status = 'queued'; j.error = null; j.result = null; j.status = 'queued'; j.error = null; j.result = null;
j.bytesUploaded = 0; j.speedKbs = 0; j.progress = 0; j.uploadId = null; j.bytesUploaded = 0; j.speedKbs = 0; j.progress = 0; j.uploadId = null;
@ -1848,7 +1893,6 @@ async function startSelectedUpload() {
} }
return; return;
} }
return;
} }
uploading = true; // set immediately to prevent double-click race uploading = true; // set immediately to prevent double-click race
updateQueueActionButtons(); updateQueueActionButtons();
@ -2108,7 +2152,6 @@ function handleBatchDone(summary) {
lastUploadStats = { state: 'idle', globalSpeedKbs: 0, totalBytes: lastUploadStats.totalBytes, elapsed: lastUploadStats.elapsed, activeJobs: 0 }; lastUploadStats = { state: 'idle', globalSpeedKbs: 0, totalBytes: lastUploadStats.totalBytes, elapsed: lastUploadStats.elapsed, activeJobs: 0 };
updateStatusBar(); updateStatusBar();
_maybeShowBatchSummary(summary);
_refreshSessionFailedSnapshot(); _refreshSessionFailedSnapshot();
} }

View File

@ -342,22 +342,6 @@
</div> </div>
</div> </div>
<div class="modal" id="batchSummaryModal" style="display:none">
<div class="modal-content" style="max-width:680px">
<div class="modal-header">
<h2>Batch-Zusammenfassung</h2>
<button class="icon-btn" id="batchSummaryClose" aria-label="Schließen">&times;</button>
</div>
<div class="modal-body">
<div id="batchSummaryList"></div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" id="batchSummaryRetryTransient">Transiente erneut hochladen</button>
<button class="btn btn-primary" id="batchSummaryRetryAll">Alle Fehler erneut versuchen</button>
</div>
</div>
</div>
<script src="../lib/queue-prune.js"></script> <script src="../lib/queue-prune.js"></script>
<script src="../lib/queue-dedup.js"></script> <script src="../lib/queue-dedup.js"></script>
<script src="../lib/log-mode.js"></script> <script src="../lib/log-mode.js"></script>