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:
parent
2ea1013951
commit
87833b5808
@ -38,6 +38,25 @@ class UploadManager extends EventEmitter {
|
|||||||
this.globalThrottle = null;
|
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) {
|
_getSettings(hoster) {
|
||||||
const settings = { ...DEFAULT_SETTINGS, ...(this.hosterSettings[hoster] || {}) };
|
const settings = { ...DEFAULT_SETTINGS, ...(this.hosterSettings[hoster] || {}) };
|
||||||
const globalLimit = this._getGlobalParallelLimit();
|
const globalLimit = this._getGlobalParallelLimit();
|
||||||
|
|||||||
6
main.js
6
main.js
@ -543,9 +543,11 @@ ipcMain.handle('start-upload', (_event, payload) => {
|
|||||||
}
|
}
|
||||||
// Write to fileuploader.log immediately when a single upload finishes
|
// Write to fileuploader.log immediately when a single upload finishes
|
||||||
if (data.status === 'done' && data.result) {
|
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) {
|
if (link) {
|
||||||
appendUploadLog(data.hoster || '', link, data.fileName || '');
|
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()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
@ -666,6 +668,7 @@ ipcMain.handle('get-hoster-settings', () => {
|
|||||||
|
|
||||||
ipcMain.handle('save-hoster-settings', async (_event, hosterSettings) => {
|
ipcMain.handle('save-hoster-settings', async (_event, hosterSettings) => {
|
||||||
await configStore.save({ hosterSettings });
|
await configStore.save({ hosterSettings });
|
||||||
|
if (uploadManager) uploadManager.updateSettings(hosterSettings, null);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -677,6 +680,7 @@ ipcMain.handle('get-global-settings', () => {
|
|||||||
|
|
||||||
ipcMain.handle('save-global-settings', async (_event, globalSettings) => {
|
ipcMain.handle('save-global-settings', async (_event, globalSettings) => {
|
||||||
await configStore.save({ globalSettings });
|
await configStore.save({ globalSettings });
|
||||||
|
if (uploadManager) uploadManager.updateSettings(null, globalSettings);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -28,6 +28,7 @@ let historySortState = { key: 'date', direction: 'desc' };
|
|||||||
|
|
||||||
// Session-specific files for the "Files" panel (resets each session)
|
// Session-specific files for the "Files" panel (resets each session)
|
||||||
let sessionFilesData = [];
|
let sessionFilesData = [];
|
||||||
|
let recentSortState = { key: 'date', direction: 'desc' }; // null key = default (date desc)
|
||||||
|
|
||||||
// --- Init ---
|
// --- Init ---
|
||||||
async function init() {
|
async function init() {
|
||||||
@ -827,7 +828,9 @@ document.getElementById('contextMenu').addEventListener('click', (e) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function handleContextAction(action) {
|
async function handleContextAction(action) {
|
||||||
if (action === 'copy-links') {
|
if (action === 'start-selected') {
|
||||||
|
startSelectedUpload();
|
||||||
|
} else if (action === 'copy-links') {
|
||||||
const links = getSelectedJobLinks();
|
const links = getSelectedJobLinks();
|
||||||
if (links.length) { window.api.copyToClipboard(links.join('\n')); showCopyToast(`${links.length} Links kopiert`); }
|
if (links.length) { window.api.copyToClipboard(links.join('\n')); showCopyToast(`${links.length} Links kopiert`); }
|
||||||
} else if (action === 'retry-selected') {
|
} else if (action === 'retry-selected') {
|
||||||
@ -1935,6 +1938,33 @@ async function loadHistory() {
|
|||||||
renderHistoryTable(container);
|
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() {
|
function renderRecentUploadsPanel() {
|
||||||
const tbody = document.getElementById('recentFilesBody');
|
const tbody = document.getElementById('recentFilesBody');
|
||||||
if (!tbody) return;
|
if (!tbody) return;
|
||||||
@ -1943,10 +1973,7 @@ function renderRecentUploadsPanel() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rows = sessionFilesData
|
const rows = sortRecentFiles(sessionFilesData);
|
||||||
.slice()
|
|
||||||
.sort((a, b) => b.dateTs - a.dateTs || b.order - a.order)
|
|
||||||
.slice(0, 20);
|
|
||||||
|
|
||||||
tbody.innerHTML = rows.map(row => `
|
tbody.innerHTML = rows.map(row => `
|
||||||
<tr class="recent-file-row${row.isError ? ' error' : ''}" data-link="${escapeAttr(row.link)}">
|
<tr class="recent-file-row${row.isError ? ' error' : ''}" data-link="${escapeAttr(row.link)}">
|
||||||
@ -1967,6 +1994,8 @@ function renderRecentUploadsPanel() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
updateRecentSortHeaders();
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderHistoryTable(container) {
|
function renderHistoryTable(container) {
|
||||||
@ -2041,6 +2070,20 @@ function setupListeners() {
|
|||||||
document.getElementById('chooseHostersBtn').addEventListener('click', openHosterModal);
|
document.getElementById('chooseHostersBtn').addEventListener('click', openHosterModal);
|
||||||
document.getElementById('startUploadBtn').addEventListener('click', startUpload);
|
document.getElementById('startUploadBtn').addEventListener('click', startUpload);
|
||||||
document.getElementById('startSelectedBtn').addEventListener('click', startSelectedUpload);
|
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('reuploadSelectedBtn').addEventListener('click', retrySelectedJobs);
|
||||||
document.getElementById('abortSelectedBtn').addEventListener('click', abortSelectedJobs);
|
document.getElementById('abortSelectedBtn').addEventListener('click', abortSelectedJobs);
|
||||||
document.getElementById('finishStopBtn').addEventListener('click', finishUploadsInProgress);
|
document.getElementById('finishStopBtn').addEventListener('click', finishUploadsInProgress);
|
||||||
|
|||||||
@ -108,12 +108,12 @@
|
|||||||
<div class="recent-tab-body active" id="filesTab">
|
<div class="recent-tab-body active" id="filesTab">
|
||||||
<div class="recent-files-table-wrap">
|
<div class="recent-files-table-wrap">
|
||||||
<table class="recent-files-table">
|
<table class="recent-files-table">
|
||||||
<thead>
|
<thead id="recentFilesHead">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-date">Datum</th>
|
<th class="col-date sortable" data-recent-sort="date">Datum<span class="sort-indicator">▼</span></th>
|
||||||
<th class="col-filename">Dateiname</th>
|
<th class="col-filename sortable" data-recent-sort="filename">Dateiname<span class="sort-indicator">↕</span></th>
|
||||||
<th class="col-host">Host</th>
|
<th class="col-host sortable" data-recent-sort="host">Host<span class="sort-indicator">↕</span></th>
|
||||||
<th class="col-link">Link</th>
|
<th class="col-link sortable" data-recent-sort="link">Link<span class="sort-indicator">↕</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="recentFilesBody"></tbody>
|
<tbody id="recentFilesBody"></tbody>
|
||||||
@ -233,6 +233,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="context-menu" id="contextMenu" style="display:none">
|
<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="copy-links">Links kopieren</div>
|
||||||
<div class="ctx-item" data-action="retry-selected">Erneut versuchen</div>
|
<div class="ctx-item" data-action="retry-selected">Erneut versuchen</div>
|
||||||
<div class="ctx-item" data-action="delete-selected">Entfernen</div>
|
<div class="ctx-item" data-action="delete-selected">Entfernen</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user