Compare commits

..

No commits in common. "22869df8a5fa85f781fb455a9bfeaebc86df4c5e" and "530fd03c22acf43cfe6616fd28f10c7372b8d629" have entirely different histories.

2 changed files with 18 additions and 68 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "multi-hoster-uploader", "name": "multi-hoster-uploader",
"version": "3.0.8", "version": "3.0.7",
"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": {

View File

@ -229,43 +229,18 @@ function _isHistoryTabActive() {
const tab = document.querySelector('.tab.active'); const tab = document.querySelector('.tab.active');
return !!(tab && tab.dataset.view === 'history'); return !!(tab && tab.dataset.view === 'history');
} }
// Cache the tab/view collections once and use event delegation on the parent document.querySelectorAll('.tab').forEach(tab => {
// so tab switches don't trigger three querySelectorAll walks per click. tab.addEventListener('click', () => {
(() => { document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
const tabs = Array.from(document.querySelectorAll('.tab')); document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
const views = Array.from(document.querySelectorAll('.view'));
const tabsByView = {};
const viewsById = {};
for (const t of tabs) tabsByView[t.dataset.view] = t;
for (const v of views) viewsById[v.id] = v;
let activeTab = tabs.find(t => t.classList.contains('active')) || tabs[0];
const handle = (target) => {
const tab = target.closest('.tab');
if (!tab || tab === activeTab) return;
if (activeTab) {
activeTab.classList.remove('active');
const prevView = viewsById[`${activeTab.dataset.view}-view`];
if (prevView) prevView.classList.remove('active');
}
tab.classList.add('active'); tab.classList.add('active');
const nextView = viewsById[`${tab.dataset.view}-view`]; document.getElementById(`${tab.dataset.view}-view`).classList.add('active');
if (nextView) nextView.classList.add('active');
activeTab = tab;
if (tab.dataset.view === 'history') { if (tab.dataset.view === 'history') {
_historyDirty = false; _historyDirty = false;
loadHistory(); loadHistory();
} }
}; });
});
const tabBar = tabs[0] && tabs[0].parentElement;
if (tabBar) {
tabBar.addEventListener('click', (e) => handle(e.target));
} else {
// Fallback: bind per-tab if somehow no common parent
tabs.forEach(t => t.addEventListener('click', () => handle(t)));
}
})();
// --- Hoster selection --- // --- Hoster selection ---
function accountHasCreds(name, account) { function accountHasCreds(name, account) {
@ -1200,22 +1175,6 @@ function handleRowClick(e, row) {
// --- Context menu --- // --- Context menu ---
let alwaysOnTopState = false; let alwaysOnTopState = false;
// Cache hoster-counts for the context menu. Invalidated on structural changes
// to queueJobs (the length-based signature is good enough — a job's hoster
// never changes after it's created).
let _hosterCountsCache = { sig: '', result: new Map() };
function _getHosterCounts() {
const sig = `${queueJobs.length}`;
if (_hosterCountsCache.sig === sig) return _hosterCountsCache.result;
const m = new Map();
for (let i = 0; i < queueJobs.length; i++) {
const h = queueJobs[i].hoster;
m.set(h, (m.get(h) || 0) + 1);
}
_hosterCountsCache = { sig, result: m };
return m;
}
function handleRowContextMenu(e, row) { function handleRowContextMenu(e, row) {
e.preventDefault(); e.preventDefault();
const jobId = row.dataset.jobId; const jobId = row.dataset.jobId;
@ -1245,11 +1204,11 @@ function showContextMenu(x, y) {
const startItem = menu.querySelector('[data-action="start-selected"]'); const startItem = menu.querySelector('[data-action="start-selected"]');
if (startItem) startItem.textContent = n > 1 ? `Ausgewählte starten (${n})` : 'Ausgewählte starten'; if (startItem) startItem.textContent = n > 1 ? `Ausgewählte starten (${n})` : 'Ausgewählte starten';
// Dynamic "delete by hoster" submenu — cached count keyed by queue length // Dynamic "delete by hoster" submenu
// so a right-click on a 5000-job queue doesn't rescan everything.
const deleteHosterSubmenu = menu.querySelector('.ctx-hoster-delete-submenu'); const deleteHosterSubmenu = menu.querySelector('.ctx-hoster-delete-submenu');
const deleteHosterContainer = menu.querySelector('.ctx-hoster-delete-items'); const deleteHosterContainer = menu.querySelector('.ctx-hoster-delete-items');
const hosterCounts = _getHosterCounts(); const hosterCounts = new Map();
queueJobs.forEach(j => hosterCounts.set(j.hoster, (hosterCounts.get(j.hoster) || 0) + 1));
deleteHosterContainer.innerHTML = ''; deleteHosterContainer.innerHTML = '';
if (hosterCounts.size > 0) { if (hosterCounts.size > 0) {
deleteHosterSubmenu.style.display = ''; deleteHosterSubmenu.style.display = '';
@ -2739,17 +2698,10 @@ async function saveSettings(options = {}) {
newHosterSettings[name] = hs; newHosterSettings[name] = hs;
} }
// Fire both saves in parallel instead of serializing the two IPC round-trips. await window.api.saveHosterSettings(newHosterSettings);
// Skip the getConfig refetch — we just wrote it, we know the new state, and await window.api.saveGlobalSettings(globalSettings);
// the round-trip added 100200ms of UI stall per keystroke (autosave fires config = await window.api.getConfig();
// on every input change). hosterSettings = config.hosterSettings || {};
await Promise.all([
window.api.saveHosterSettings(newHosterSettings),
window.api.saveGlobalSettings(globalSettings)
]);
config.hosterSettings = newHosterSettings;
config.globalSettings = globalSettings;
hosterSettings = newHosterSettings;
clearTimeout(settingsSaveTimer); clearTimeout(settingsSaveTimer);
// Start/stop folder monitor based on settings // Start/stop folder monitor based on settings
@ -3406,11 +3358,9 @@ function renderRecentUploadsPanel() {
if (selectedRecentIds.has(id)) selectedRecentIds.delete(id); if (selectedRecentIds.has(id)) selectedRecentIds.delete(id);
else selectedRecentIds.add(id); else selectedRecentIds.add(id);
} else if (e.shiftKey && selectedRecentIds.size > 0) { } else if (e.shiftKey && selectedRecentIds.size > 0) {
// Reuse the already-sorted array from the sort cache instead of // Use already-sorted DOM order (cheap) instead of re-sorting the full array.
// querying every .recent-file-row in the DOM (O(visible) vs O(N) const sortedOrders = Array.from(tbody.querySelectorAll('.recent-file-row'))
// on large panels). .map(r => parseInt(r.dataset.order, 10));
const sortedOrders = (_recentSortCache.result || sortRecentFiles(sessionFilesData))
.map(r => r.order);
const lastIdx = sortedOrders.findIndex(o => selectedRecentIds.has(o)); const lastIdx = sortedOrders.findIndex(o => selectedRecentIds.has(o));
const curIdx = sortedOrders.indexOf(id); const curIdx = sortedOrders.indexOf(id);
if (lastIdx >= 0 && curIdx >= 0) { if (lastIdx >= 0 && curIdx >= 0) {