perf: lazy history refresh + append-only recent panel + queue-cleanup merge
Three more targeted wins:
- loadHistory() was called unconditionally on every handleBatchDone,
doing an IPC roundtrip + full history-table rebuild even when the
user is on the Upload tab and can't see it. Now it sets a dirty
flag and the actual refresh is deferred until the user switches
to the Verlauf tab. On a fresh tab click it always runs.
- renderRecentUploadsPanel append-only fast path: when the sort is
'date desc' (the default) and the dataset only grew, the panel
inserts the new rows at the top via insertAdjacentHTML instead
of rebuilding the 5000-row tbody from scratch. Length shrinks or
sort-change still trigger a full rebuild.
- handleBatchDone's removeFromQueueOnDone cleanup now does one pass
(build keep-list + detach from index together) instead of two
separate filter() scans over queueJobs.
This commit is contained in:
parent
1bcd7a2078
commit
f16dd9ffa6
@ -200,13 +200,21 @@ async function init() {
|
||||
}
|
||||
|
||||
// --- Tab switching ---
|
||||
let _historyDirty = false;
|
||||
function _isHistoryTabActive() {
|
||||
const tab = document.querySelector('.tab.active');
|
||||
return !!(tab && tab.dataset.view === 'history');
|
||||
}
|
||||
document.querySelectorAll('.tab').forEach(tab => {
|
||||
tab.addEventListener('click', () => {
|
||||
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
||||
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
||||
tab.classList.add('active');
|
||||
document.getElementById(`${tab.dataset.view}-view`).classList.add('active');
|
||||
if (tab.dataset.view === 'history') loadHistory();
|
||||
if (tab.dataset.view === 'history') {
|
||||
_historyDirty = false;
|
||||
loadHistory();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -1820,16 +1828,25 @@ function handleBatchDone(summary) {
|
||||
updateQueueActionButtons();
|
||||
renderQueueTable();
|
||||
renderRecentUploadsPanel();
|
||||
loadHistory();
|
||||
// History is only visible on the Verlauf tab. Mark it dirty and refresh when
|
||||
// the user actually switches to it — skips an IPC + full table rebuild per
|
||||
// batch-done when the user is watching the upload view.
|
||||
_historyDirty = true;
|
||||
if (_isHistoryTabActive()) loadHistory();
|
||||
|
||||
const removeOnDone = config.globalSettings && config.globalSettings.removeFromQueueOnDone;
|
||||
if (removeOnDone) {
|
||||
const doneJobs = queueJobs.filter(j => j.status === 'done');
|
||||
for (const job of doneJobs) {
|
||||
// Single pass: build the keep-list and clean up the index for removed jobs.
|
||||
const nextJobs = [];
|
||||
for (const job of queueJobs) {
|
||||
if (job.status === 'done') {
|
||||
removeJobFromIndex(job);
|
||||
selectedJobIds.delete(job.id);
|
||||
} else {
|
||||
nextJobs.push(job);
|
||||
}
|
||||
queueJobs = queueJobs.filter(j => j.status !== 'done');
|
||||
}
|
||||
queueJobs = nextJobs;
|
||||
renderQueueTable();
|
||||
}
|
||||
|
||||
@ -3215,24 +3232,50 @@ function updateRecentSortHeaders() {
|
||||
|
||||
let _recentListenersBound = false;
|
||||
|
||||
function _buildRecentRowHtml(row) {
|
||||
const cls = `recent-file-row${row.isError ? ' error' : ''}${selectedRecentIds.has(row.order) ? ' selected' : ''}`;
|
||||
return `<tr class="${cls}" data-order="${row.order}" data-link="${escapeAttr(row.link)}">`
|
||||
+ `<td>${escapeHtml(row.date)}</td>`
|
||||
+ `<td title="${escapeAttr(row.filename)}">${escapeHtml(row.filename)}</td>`
|
||||
+ `<td>${escapeHtml(row.host)}</td>`
|
||||
+ `<td title="${escapeAttr(row.link)}">${escapeHtml(row.link)}</td>`
|
||||
+ `</tr>`;
|
||||
}
|
||||
|
||||
// Tracks the last rendered dataset so we can append-only when the user is just
|
||||
// accumulating new uploads (the default case: sort=date desc, rows only grow).
|
||||
let _recentLastRenderedSig = '';
|
||||
let _recentLastRenderedLen = 0;
|
||||
|
||||
function renderRecentUploadsPanel() {
|
||||
const tbody = document.getElementById('recentFilesBody');
|
||||
if (!tbody) return;
|
||||
if (!sessionFilesData.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="4" class="empty-state">Noch keine Uploads in dieser Session.</td></tr>';
|
||||
_recentLastRenderedSig = '';
|
||||
_recentLastRenderedLen = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const rows = sortRecentFiles(sessionFilesData);
|
||||
const sig = `${recentSortState.key}|${recentSortState.direction}`;
|
||||
const dateDescAppendOnly = sig === 'date|desc'
|
||||
&& _recentLastRenderedSig === sig
|
||||
&& rows.length > _recentLastRenderedLen
|
||||
&& tbody.querySelectorAll('.recent-file-row').length === _recentLastRenderedLen;
|
||||
|
||||
tbody.innerHTML = rows.map(row => `
|
||||
<tr class="recent-file-row${row.isError ? ' error' : ''}${selectedRecentIds.has(row.order) ? ' selected' : ''}" data-order="${row.order}" data-link="${escapeAttr(row.link)}">
|
||||
<td>${escapeHtml(row.date)}</td>
|
||||
<td title="${escapeAttr(row.filename)}">${escapeHtml(row.filename)}</td>
|
||||
<td>${escapeHtml(row.host)}</td>
|
||||
<td title="${escapeAttr(row.link)}">${escapeHtml(row.link)}</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
if (dateDescAppendOnly) {
|
||||
// Fast path: only new rows (date desc puts newest on top) — insert them
|
||||
// at the top without rebuilding the 5000-row tbody below.
|
||||
const added = rows.length - _recentLastRenderedLen;
|
||||
let html = '';
|
||||
for (let i = 0; i < added; i++) html += _buildRecentRowHtml(rows[i]);
|
||||
tbody.insertAdjacentHTML('afterbegin', html);
|
||||
} else {
|
||||
tbody.innerHTML = rows.map(_buildRecentRowHtml).join('');
|
||||
}
|
||||
_recentLastRenderedSig = sig;
|
||||
_recentLastRenderedLen = rows.length;
|
||||
|
||||
// Event delegation – bind once, not per-row
|
||||
if (!_recentListenersBound) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user