release: Multi-Hoster-Upload 2.1.10

This commit is contained in:
Sucukdeluxe
2026-08-11 18:32:05 +02:00
parent e8eea5b5de
commit 191fe92d44
6 changed files with 76 additions and 38 deletions
+1 -1
View File
@@ -8,7 +8,7 @@ Multi-Hoster-Upload is a Windows desktop application for sending file batches to
Download the current Setup or Portable build from the [latest GitHub release](https://github.com/Sucukdeluxe/Multi-Hoster-Upload/releases/latest). Download the current Setup or Portable build from the [latest GitHub release](https://github.com/Sucukdeluxe/Multi-Hoster-Upload/releases/latest).
The latest public release is version 2.1.9. Use the release page for the executables and the full English changelog. The latest public release is version 2.1.10. Use the release page for the executables and the full English changelog.
## Features ## Features
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "2.1.9", "version": "2.1.10",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "2.1.9", "version": "2.1.10",
"dependencies": { "dependencies": {
"chokidar": "^3.6.0", "chokidar": "^3.6.0",
"undici": "^7.29.0", "undici": "^7.29.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "2.1.9", "version": "2.1.10",
"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": {
+23 -26
View File
@@ -1563,40 +1563,37 @@ function suppressPreviewKeysStillSelected(keys) {
// Build preview jobs from selected files x selected hosters (before upload starts) // Build preview jobs from selected files x selected hosters (before upload starts)
function buildQueuePreview() { function buildQueuePreview() {
const hosters = getSelectedHosters(); const hosters = getSelectedHosters();
if (hosters.length === 0) {
queueJobs = queueJobs.filter(j => j.status !== 'preview');
rebuildJobIndex();
renderQueueTable();
persistQueueStateSoon();
return;
}
// Remove old preview jobs
queueJobs = queueJobs.filter(j => j.status !== 'preview'); queueJobs = queueJobs.filter(j => j.status !== 'preview');
// Build a Set for fast existence checks if (hosters.length > 0) {
const existingKeys = new Set(); const existingKeys = new Set();
for (const j of queueJobs) { for (const j of queueJobs) {
existingKeys.add(`${j.file}|${j.hoster}`); existingKeys.add(`${j.file}|${j.hoster}`);
} }
for (const file of selectedFiles) { for (const file of selectedFiles) {
for (const hoster of hosters) { for (const hoster of hosters) {
const key = `${file.path}|${hoster}`; const key = `${file.path}|${hoster}`;
if (!existingKeys.has(key) && !_completedUploadKeys.has(key) && !_suppressedPreviewKeys.has(key)) { if (!existingKeys.has(key) && !_completedUploadKeys.has(key) && !_suppressedPreviewKeys.has(key)) {
const job = { const job = {
id: `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, id: `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
file: file.path, fileName: file.name, hoster, file: file.path, fileName: file.name, hoster,
status: 'preview', bytesUploaded: 0, bytesTotal: file.size || 0, status: 'preview', bytesUploaded: 0, bytesTotal: file.size || 0,
speedKbs: 0, elapsed: 0, remaining: 0, speedKbs: 0, elapsed: 0, remaining: 0,
error: null, result: null, attempt: 0, maxAttempts: 0, link: '' error: null, result: null, attempt: 0, maxAttempts: 0, link: ''
}; };
queueJobs.push(job); queueJobs.push(job);
existingKeys.add(key); existingKeys.add(key);
}
} }
} }
} }
rebuildJobIndex(); rebuildJobIndex();
_queueStatsCache = null;
renderQueueTable(); renderQueueTable();
updateStatusBar();
updateStatsPanel();
persistQueueStateSoon(); persistQueueStateSoon();
} }
+14 -6
View File
@@ -2518,13 +2518,18 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
} }
.view-sidebar-badge { .view-sidebar-badge {
height: 22px;
min-width: 22px; min-width: 22px;
padding: 2px 6px; display: inline-grid;
place-items: center;
padding: 0 7px;
border-radius: 999px; border-radius: 999px;
background: rgba(255, 255, 255, .055); background: var(--accent);
color: var(--text-dim); color: var(--accent-ink);
font-size: 10px; font-size: 12px;
font-weight: 700;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center; text-align: center;
} }
@@ -3768,11 +3773,14 @@ input[type="checkbox"] {
display: grid; display: grid;
} }
#upload-view .view-sidebar-copy, #upload-view .view-sidebar-copy {
#upload-view .view-sidebar-badge {
display: block; display: block;
} }
#upload-view .view-sidebar-badge {
display: grid;
}
#upload-view .upload-sidebar-lower { #upload-view .upload-sidebar-lower {
min-width: 0; min-width: 0;
gap: 10px; gap: 10px;
+35 -2
View File
@@ -313,7 +313,7 @@ setTimeout(async () => {
await wc.debugger.sendCommand('Input.dispatchDragEvent', { type: 'drop', ...populatedDropPoint, data: dragData }); await wc.debugger.sendCommand('Input.dispatchDragEvent', { type: 'drop', ...populatedDropPoint, data: dragData });
await waitUntil(() => wc.executeJavaScript('document.getElementById("hosterModal")?.style.display === "flex"')); await waitUntil(() => wc.executeJavaScript('document.getElementById("hosterModal")?.style.display === "flex"'));
populatedDropState = await wc.executeJavaScript('(() => ({ modal: document.getElementById("hosterModal")?.style.display, paths: _pendingFiles.map(file => file.path) }))()'); populatedDropState = await wc.executeJavaScript('(() => ({ modal: document.getElementById("hosterModal")?.style.display, paths: _pendingFiles.map(file => file.path) }))()');
await wc.executeJavaScript('cancelHosterModal(); selectedFiles = []; queueJobs = []; rebuildJobIndex(); updateUploadView(); renderQueueTable();'); await wc.executeJavaScript('cancelHosterModal(); selectedFiles = []; queueJobs = []; rebuildJobIndex(); _queueStatsCache = null; updateUploadView(); renderQueueTable(); updateStatusBar();');
} finally { } finally {
if (wc.debugger.isAttached()) wc.debugger.detach(); if (wc.debugger.isAttached()) wc.debugger.detach();
try { fs.unlinkSync(populatedDropFixture); } catch {} try { fs.unlinkSync(populatedDropFixture); } catch {}
@@ -337,7 +337,7 @@ setTimeout(async () => {
await wc.debugger.sendCommand('Input.dispatchDragEvent', { type: 'drop', ...duplicateDropPoint, data: dragData }); await wc.debugger.sendCommand('Input.dispatchDragEvent', { type: 'drop', ...duplicateDropPoint, data: dragData });
await new Promise(resolve => setTimeout(resolve, 100)); await new Promise(resolve => setTimeout(resolve, 100));
duplicateDropState = await wc.executeJavaScript('(() => ({ modal: document.getElementById("hosterModal")?.style.display, pending: _pendingFiles.length, toast: document.getElementById("copyToast")?.textContent, shown: document.getElementById("copyToast")?.classList.contains("show") }))()'); duplicateDropState = await wc.executeJavaScript('(() => ({ modal: document.getElementById("hosterModal")?.style.display, pending: _pendingFiles.length, toast: document.getElementById("copyToast")?.textContent, shown: document.getElementById("copyToast")?.classList.contains("show") }))()');
await wc.executeJavaScript('selectedFiles = []; queueJobs = []; rebuildJobIndex(); updateUploadView(); renderQueueTable();'); await wc.executeJavaScript('selectedFiles = []; queueJobs = []; rebuildJobIndex(); _queueStatsCache = null; updateUploadView(); renderQueueTable(); updateStatusBar();');
} finally { } finally {
if (wc.debugger.isAttached()) wc.debugger.detach(); if (wc.debugger.isAttached()) wc.debugger.detach();
try { fs.unlinkSync(duplicateDropFixture); } catch {} try { fs.unlinkSync(duplicateDropFixture); } catch {}
@@ -374,6 +374,39 @@ setTimeout(async () => {
const initialTelemetryValues = await wc.executeJavaScript('[...document.querySelectorAll("#uploadTelemetry .upload-telemetry-value")].map(el => el.getAttribute("aria-label") || el.textContent.trim()).join("|")'); const initialTelemetryValues = await wc.executeJavaScript('[...document.querySelectorAll("#uploadTelemetry .upload-telemetry-value")].map(el => el.getAttribute("aria-label") || el.textContent.trim()).join("|")');
check('Upload telemetry starts with stable empty values', initialTelemetryValues === '0|0|0|0|0|0|0 B/s|--:--'); check('Upload telemetry starts with stable empty values', initialTelemetryValues === '0|0|0|0|0|0|0 B/s|--:--');
const previewQueueCounts = await wc.executeJavaScript(\`(() => {
queueJobs = [{ id: 'existing-done', file: 'C:/ui/existing-done.bin', fileName: 'existing-done.bin', hoster: 'doodstream.com', status: 'done', bytesUploaded: 1024, bytesTotal: 1024, speedKbs: 0, elapsed: 1, remaining: 0, progress: 1 }];
selectedFiles = [];
selectedUploadHosters = ['doodstream.com'];
_sessionDoneCount = 1;
_sessionErrorCount = 0;
_queueStatsCache = null;
updateStatusBar();
selectedFiles = [1, 2, 3].map(index => ({ path: 'C:/ui/new-' + index + '.bin', name: 'new-' + index + '.bin', size: 2048 }));
buildQueuePreview();
const result = {
queue: queueJobs.map(job => job.status).join('|'),
sidebar: ['All', 'Active', 'Waiting', 'Done', 'Error'].map(key => document.getElementById('uploadSidebar' + key + 'Count')?.textContent).join('|'),
telemetry: ['Total', 'Remaining', 'Running'].map(key => document.getElementById('uploadTelemetry' + key)?.getAttribute('aria-label')).join('|')
};
queueJobs = [];
selectedFiles = [];
selectedUploadHosters = [];
_sessionDoneCount = 0;
_queueStatsCache = null;
updateStatusBar();
return result;
})()\`);
check('Adding preview files immediately refreshes upload counts before the batch starts', previewQueueCounts.queue === 'done|preview|preview|preview' && previewQueueCounts.sidebar === '4|0|3|1|0' && previewQueueCounts.telemetry === '4|3|0');
const sidebarBadgeStyle = await wc.executeJavaScript(\`(() => {
const badge = document.getElementById('uploadSidebarAllCount');
const style = getComputedStyle(badge);
const rect = badge.getBoundingClientRect();
return { background: style.backgroundColor, color: style.color, fontSize: parseFloat(style.fontSize), fontWeight: parseInt(style.fontWeight, 10), width: rect.width, height: rect.height };
})()\`);
check('Sidebar count badges use a larger light-blue high-contrast pill', sidebarBadgeStyle.background === 'rgb(186, 208, 252)' && sidebarBadgeStyle.color === 'rgb(16, 23, 35)' && sidebarBadgeStyle.fontSize >= 12 && sidebarBadgeStyle.fontWeight >= 600 && sidebarBadgeStyle.width >= 22 && sidebarBadgeStyle.height >= 22);
const speedSparklineState = await wc.executeJavaScript('(() => { const widget = document.getElementById("uploadSpeedSparkline"); const canvas = document.getElementById("uploadSpeedCanvas"); const rect = canvas?.getBoundingClientRect(); return [Boolean(widget), widget?.classList.contains("is-hidden"), rect?.width > 0, rect?.height > 0, document.getElementById("uploadSpeedValue")?.textContent].join("|"); })()'); const speedSparklineState = await wc.executeJavaScript('(() => { const widget = document.getElementById("uploadSpeedSparkline"); const canvas = document.getElementById("uploadSpeedCanvas"); const rect = canvas?.getBoundingClientRect(); return [Boolean(widget), widget?.classList.contains("is-hidden"), rect?.width > 0, rect?.height > 0, document.getElementById("uploadSpeedValue")?.textContent].join("|"); })()');
check('Upload header exposes the visible speed sparkline', speedSparklineState === 'true|false|true|true|0 B/s'); check('Upload header exposes the visible speed sparkline', speedSparklineState === 'true|false|true|true|0 B/s');