feat(queue): add "Hoster entfernen" submenu to context menu

Right-click on queue now shows a "Hoster entfernen ▸" submenu listing
all hosters with job count (e.g. "Vidmoly (3)"). Clicking removes all
jobs for that hoster, cancels active uploads, and saves immediately.

Also fixes submenu viewport flip measurement (was reading offsetWidth
on display:none elements).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Administrator 2026-03-21 09:03:13 +01:00
parent ccfb7c18ba
commit a64ebd1587
2 changed files with 47 additions and 0 deletions

View File

@ -1019,6 +1019,25 @@ function showContextMenu(x, y) {
cancelSep.style.display = 'none'; cancelSep.style.display = 'none';
} }
// Dynamic "delete by hoster" submenu
const deleteHosterSubmenu = menu.querySelector('.ctx-hoster-delete-submenu');
const deleteHosterContainer = menu.querySelector('.ctx-hoster-delete-items');
const hosterCounts = new Map();
queueJobs.forEach(j => hosterCounts.set(j.hoster, (hosterCounts.get(j.hoster) || 0) + 1));
deleteHosterContainer.innerHTML = '';
if (hosterCounts.size > 0) {
deleteHosterSubmenu.style.display = '';
hosterCounts.forEach((count, hoster) => {
const item = document.createElement('div');
item.className = 'ctx-item ctx-item-danger';
item.dataset.action = `delete-hoster:${hoster}`;
item.textContent = `${getHosterLabel(hoster)} (${count})`;
deleteHosterContainer.appendChild(item);
});
} else {
deleteHosterSubmenu.style.display = 'none';
}
menu.style.display = 'block'; menu.style.display = 'block';
const menuX = Math.min(x, window.innerWidth - menu.offsetWidth - 5); const menuX = Math.min(x, window.innerWidth - menu.offsetWidth - 5);
menu.style.left = menuX + 'px'; menu.style.left = menuX + 'px';
@ -1026,7 +1045,12 @@ function showContextMenu(x, y) {
// Flip submenus if they would overflow the viewport right edge // Flip submenus if they would overflow the viewport right edge
menu.querySelectorAll('.ctx-submenu-items').forEach(sub => { menu.querySelectorAll('.ctx-submenu-items').forEach(sub => {
// Temporarily show to measure actual width (display:none → offsetWidth=0)
sub.style.visibility = 'hidden';
sub.style.display = 'block';
sub.classList.toggle('flip-left', menuX + menu.offsetWidth + sub.offsetWidth > window.innerWidth); sub.classList.toggle('flip-left', menuX + menu.offsetWidth + sub.offsetWidth > window.innerWidth);
sub.style.display = '';
sub.style.visibility = '';
}); });
} }
@ -1252,6 +1276,25 @@ async function handleContextAction(action) {
renderQueueTable(); renderQueueTable();
updateStatusBar(); updateStatusBar();
updateQueueActionButtons(); updateQueueActionButtons();
} else if (action.startsWith('delete-hoster:')) {
const hoster = action.replace('delete-hoster:', '');
// Cancel active uploads for this hoster
const activeIds = queueJobs
.filter(j => j.hoster === hoster && (j.status === 'uploading' || j.status === 'queued' || j.status === 'retrying' || j.status === 'getting-server' || j.status === 'preview'))
.map(j => j.id);
if (activeIds.length > 0) await window.api.cancelSelectedJobs(activeIds);
// Remove ALL jobs for this hoster
queueJobs = queueJobs.filter(j => {
if (j.hoster === hoster) { removeJobFromIndex(j); return false; }
return true;
});
selectedJobIds.clear();
syncSelectedFilesFromQueue();
renderQueueTable();
if (queueJobs.length === 0) { selectedFiles = []; updateUploadView(); }
updateStatusBar();
updateQueueActionButtons();
persistQueueStateSoon(true);
} else if (action.startsWith('shutdown-')) { } else if (action.startsWith('shutdown-')) {
const mode = action.replace('shutdown-', ''); const mode = action.replace('shutdown-', '');
await window.api.setShutdownAfterFinish(mode); await window.api.setShutdownAfterFinish(mode);

View File

@ -265,6 +265,10 @@
<div class="ctx-separator"></div> <div class="ctx-separator"></div>
<div class="ctx-item" data-action="delete-selected">Entfernen</div> <div class="ctx-item" data-action="delete-selected">Entfernen</div>
<div class="ctx-item" data-action="delete-all">Alle entfernen</div> <div class="ctx-item" data-action="delete-all">Alle entfernen</div>
<div class="ctx-submenu ctx-hoster-delete-submenu" style="display:none">
<div class="ctx-item ctx-item-danger">Hoster entfernen &#9656;</div>
<div class="ctx-submenu-items ctx-hoster-delete-items"></div>
</div>
<div class="ctx-separator ctx-hoster-cancel-sep" style="display:none"></div> <div class="ctx-separator ctx-hoster-cancel-sep" style="display:none"></div>
<div class="ctx-hoster-cancel-items"></div> <div class="ctx-hoster-cancel-items"></div>
</div> </div>