release: Multi-Hoster-Upload 2.1.10

This commit is contained in:
Sucukdeluxe
2026-08-11 18:32:05 +02:00
parent 69273b05d1
commit 54de500b34
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).
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
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "multi-hoster-uploader",
"version": "2.1.9",
"version": "2.1.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "multi-hoster-uploader",
"version": "2.1.9",
"version": "2.1.10",
"dependencies": {
"chokidar": "^3.6.0",
"undici": "^7.29.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "multi-hoster-uploader",
"version": "2.1.9",
"version": "2.1.10",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js",
"scripts": {
+23 -26
View File
@@ -1563,40 +1563,37 @@ function suppressPreviewKeysStillSelected(keys) {
// Build preview jobs from selected files x selected hosters (before upload starts)
function buildQueuePreview() {
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');
// Build a Set for fast existence checks
const existingKeys = new Set();
for (const j of queueJobs) {
existingKeys.add(`${j.file}|${j.hoster}`);
}
if (hosters.length > 0) {
const existingKeys = new Set();
for (const j of queueJobs) {
existingKeys.add(`${j.file}|${j.hoster}`);
}
for (const file of selectedFiles) {
for (const hoster of hosters) {
const key = `${file.path}|${hoster}`;
if (!existingKeys.has(key) && !_completedUploadKeys.has(key) && !_suppressedPreviewKeys.has(key)) {
const job = {
id: `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
file: file.path, fileName: file.name, hoster,
status: 'preview', bytesUploaded: 0, bytesTotal: file.size || 0,
speedKbs: 0, elapsed: 0, remaining: 0,
error: null, result: null, attempt: 0, maxAttempts: 0, link: ''
};
queueJobs.push(job);
existingKeys.add(key);
for (const file of selectedFiles) {
for (const hoster of hosters) {
const key = `${file.path}|${hoster}`;
if (!existingKeys.has(key) && !_completedUploadKeys.has(key) && !_suppressedPreviewKeys.has(key)) {
const job = {
id: `preview-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
file: file.path, fileName: file.name, hoster,
status: 'preview', bytesUploaded: 0, bytesTotal: file.size || 0,
speedKbs: 0, elapsed: 0, remaining: 0,
error: null, result: null, attempt: 0, maxAttempts: 0, link: ''
};
queueJobs.push(job);
existingKeys.add(key);
}
}
}
}
rebuildJobIndex();
_queueStatsCache = null;
renderQueueTable();
updateStatusBar();
updateStatsPanel();
persistQueueStateSoon();
}
+14 -6
View File
@@ -2518,13 +2518,18 @@ select.hs-input { max-width: none; width: auto; min-width: 140px; }
}
.view-sidebar-badge {
height: 22px;
min-width: 22px;
padding: 2px 6px;
display: inline-grid;
place-items: center;
padding: 0 7px;
border-radius: 999px;
background: rgba(255, 255, 255, .055);
color: var(--text-dim);
font-size: 10px;
background: var(--accent);
color: var(--accent-ink);
font-size: 12px;
font-weight: 700;
font-variant-numeric: tabular-nums;
line-height: 1;
text-align: center;
}
@@ -3768,11 +3773,14 @@ input[type="checkbox"] {
display: grid;
}
#upload-view .view-sidebar-copy,
#upload-view .view-sidebar-badge {
#upload-view .view-sidebar-copy {
display: block;
}
#upload-view .view-sidebar-badge {
display: grid;
}
#upload-view .upload-sidebar-lower {
min-width: 0;
gap: 10px;
+35 -2
View File
@@ -313,7 +313,7 @@ setTimeout(async () => {
await wc.debugger.sendCommand('Input.dispatchDragEvent', { type: 'drop', ...populatedDropPoint, data: dragData });
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) }))()');
await wc.executeJavaScript('cancelHosterModal(); selectedFiles = []; queueJobs = []; rebuildJobIndex(); updateUploadView(); renderQueueTable();');
await wc.executeJavaScript('cancelHosterModal(); selectedFiles = []; queueJobs = []; rebuildJobIndex(); _queueStatsCache = null; updateUploadView(); renderQueueTable(); updateStatusBar();');
} finally {
if (wc.debugger.isAttached()) wc.debugger.detach();
try { fs.unlinkSync(populatedDropFixture); } catch {}
@@ -337,7 +337,7 @@ setTimeout(async () => {
await wc.debugger.sendCommand('Input.dispatchDragEvent', { type: 'drop', ...duplicateDropPoint, data: dragData });
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") }))()');
await wc.executeJavaScript('selectedFiles = []; queueJobs = []; rebuildJobIndex(); updateUploadView(); renderQueueTable();');
await wc.executeJavaScript('selectedFiles = []; queueJobs = []; rebuildJobIndex(); _queueStatsCache = null; updateUploadView(); renderQueueTable(); updateStatusBar();');
} finally {
if (wc.debugger.isAttached()) wc.debugger.detach();
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("|")');
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("|"); })()');
check('Upload header exposes the visible speed sparkline', speedSparklineState === 'true|false|true|true|0 B/s');