feat: sortable recent files, start selected context menu, live settings

- Add sortable columns in recent files panel (date, filename, host, link)
- Add "Start selected" to right-click context menu
- Live-apply settings changes during uploads (parallel count, speed limits)
- Add fallback file_code check for upload logging
- Add warning log when upload completes without link

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Administrator 2026-03-11 03:47:00 +01:00
parent 2ea1013951
commit 87833b5808
4 changed files with 78 additions and 11 deletions

View File

@ -38,6 +38,25 @@ class UploadManager extends EventEmitter {
this.globalThrottle = null;
}
updateSettings(hosterSettings, globalSettings) {
this.hosterSettings = hosterSettings || this.hosterSettings;
this.globalSettings = globalSettings || this.globalSettings;
// Live-update semaphores for running uploads
for (const [hoster, sem] of Object.entries(this.semaphores)) {
const settings = this._getSettings(hoster);
sem.updateLimit(settings.parallelCount);
}
// Update global throttle if speed limit changed
if (this.globalThrottle) {
const newKbs = (this.globalSettings.globalMaxSpeedKbs || 0);
if (newKbs > 0) {
this.globalThrottle.updateRate(newKbs * 1024);
} else {
this.globalThrottle = null;
}
}
}
_getSettings(hoster) {
const settings = { ...DEFAULT_SETTINGS, ...(this.hosterSettings[hoster] || {}) };
const globalLimit = this._getGlobalParallelLimit();

View File

@ -543,9 +543,11 @@ ipcMain.handle('start-upload', (_event, payload) => {
}
// Write to fileuploader.log immediately when a single upload finishes
if (data.status === 'done' && data.result) {
const link = data.result.download_url || data.result.embed_url || '';
const link = data.result.download_url || data.result.embed_url || data.result.file_code || '';
if (link) {
appendUploadLog(data.hoster || '', link, data.fileName || '');
} else {
debugLog(`WARNING: done but no link for ${data.fileName} @ ${data.hoster}: ${JSON.stringify(data.result)}`);
}
}
if (mainWindow && !mainWindow.isDestroyed()) {
@ -666,6 +668,7 @@ ipcMain.handle('get-hoster-settings', () => {
ipcMain.handle('save-hoster-settings', async (_event, hosterSettings) => {
await configStore.save({ hosterSettings });
if (uploadManager) uploadManager.updateSettings(hosterSettings, null);
return true;
});
@ -677,6 +680,7 @@ ipcMain.handle('get-global-settings', () => {
ipcMain.handle('save-global-settings', async (_event, globalSettings) => {
await configStore.save({ globalSettings });
if (uploadManager) uploadManager.updateSettings(null, globalSettings);
return true;
});

View File

@ -28,6 +28,7 @@ let historySortState = { key: 'date', direction: 'desc' };
// Session-specific files for the "Files" panel (resets each session)
let sessionFilesData = [];
let recentSortState = { key: 'date', direction: 'desc' }; // null key = default (date desc)
// --- Init ---
async function init() {
@ -827,7 +828,9 @@ document.getElementById('contextMenu').addEventListener('click', (e) => {
});
async function handleContextAction(action) {
if (action === 'copy-links') {
if (action === 'start-selected') {
startSelectedUpload();
} else if (action === 'copy-links') {
const links = getSelectedJobLinks();
if (links.length) { window.api.copyToClipboard(links.join('\n')); showCopyToast(`${links.length} Links kopiert`); }
} else if (action === 'retry-selected') {
@ -1935,6 +1938,33 @@ async function loadHistory() {
renderHistoryTable(container);
}
function sortRecentFiles(data) {
const sorted = data.slice();
const { key, direction } = recentSortState;
const dir = direction === 'asc' ? 1 : -1;
sorted.sort((a, b) => {
if (key === 'date') return dir * ((a.dateTs - b.dateTs) || (a.order - b.order));
if (key === 'filename') return dir * a.filename.localeCompare(b.filename, 'de', { sensitivity: 'base' });
if (key === 'host') return dir * a.host.localeCompare(b.host, 'de', { sensitivity: 'base' });
if (key === 'link') return dir * a.link.localeCompare(b.link, 'de', { sensitivity: 'base' });
return 0;
});
return sorted;
}
function updateRecentSortHeaders() {
const head = document.getElementById('recentFilesHead');
if (!head) return;
head.querySelectorAll('th[data-recent-sort]').forEach(th => {
const key = th.dataset.recentSort;
const active = recentSortState.key === key;
const arrow = active ? (recentSortState.direction === 'asc' ? '▲' : '▼') : '↕';
th.classList.toggle('active', active);
const indicator = th.querySelector('.sort-indicator');
if (indicator) indicator.textContent = arrow;
});
}
function renderRecentUploadsPanel() {
const tbody = document.getElementById('recentFilesBody');
if (!tbody) return;
@ -1943,10 +1973,7 @@ function renderRecentUploadsPanel() {
return;
}
const rows = sessionFilesData
.slice()
.sort((a, b) => b.dateTs - a.dateTs || b.order - a.order)
.slice(0, 20);
const rows = sortRecentFiles(sessionFilesData);
tbody.innerHTML = rows.map(row => `
<tr class="recent-file-row${row.isError ? ' error' : ''}" data-link="${escapeAttr(row.link)}">
@ -1967,6 +1994,8 @@ function renderRecentUploadsPanel() {
}
});
});
updateRecentSortHeaders();
}
function renderHistoryTable(container) {
@ -2041,6 +2070,20 @@ function setupListeners() {
document.getElementById('chooseHostersBtn').addEventListener('click', openHosterModal);
document.getElementById('startUploadBtn').addEventListener('click', startUpload);
document.getElementById('startSelectedBtn').addEventListener('click', startSelectedUpload);
// Recent files sort headers
document.getElementById('recentFilesHead').addEventListener('click', (e) => {
const th = e.target.closest('th[data-recent-sort]');
if (!th) return;
const key = th.dataset.recentSort;
if (recentSortState.key === key) {
recentSortState.direction = recentSortState.direction === 'desc' ? 'asc' : 'desc';
} else {
recentSortState.key = key;
recentSortState.direction = key === 'date' ? 'desc' : 'asc';
}
renderRecentUploadsPanel();
});
document.getElementById('reuploadSelectedBtn').addEventListener('click', retrySelectedJobs);
document.getElementById('abortSelectedBtn').addEventListener('click', abortSelectedJobs);
document.getElementById('finishStopBtn').addEventListener('click', finishUploadsInProgress);

View File

@ -108,12 +108,12 @@
<div class="recent-tab-body active" id="filesTab">
<div class="recent-files-table-wrap">
<table class="recent-files-table">
<thead>
<thead id="recentFilesHead">
<tr>
<th class="col-date">Datum</th>
<th class="col-filename">Dateiname</th>
<th class="col-host">Host</th>
<th class="col-link">Link</th>
<th class="col-date sortable" data-recent-sort="date">Datum<span class="sort-indicator"></span></th>
<th class="col-filename sortable" data-recent-sort="filename">Dateiname<span class="sort-indicator"></span></th>
<th class="col-host sortable" data-recent-sort="host">Host<span class="sort-indicator"></span></th>
<th class="col-link sortable" data-recent-sort="link">Link<span class="sort-indicator"></span></th>
</tr>
</thead>
<tbody id="recentFilesBody"></tbody>
@ -233,6 +233,7 @@
</div>
<div class="context-menu" id="contextMenu" style="display:none">
<div class="ctx-item" data-action="start-selected">Ausgewählte starten</div>
<div class="ctx-item" data-action="copy-links">Links kopieren</div>
<div class="ctx-item" data-action="retry-selected">Erneut versuchen</div>
<div class="ctx-item" data-action="delete-selected">Entfernen</div>