|
|
|
|
@ -1019,6 +1019,25 @@ function showContextMenu(x, y) {
|
|
|
|
|
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';
|
|
|
|
|
const menuX = Math.min(x, window.innerWidth - menu.offsetWidth - 5);
|
|
|
|
|
menu.style.left = menuX + 'px';
|
|
|
|
|
@ -1026,7 +1045,12 @@ function showContextMenu(x, y) {
|
|
|
|
|
|
|
|
|
|
// Flip submenus if they would overflow the viewport right edge
|
|
|
|
|
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.style.display = '';
|
|
|
|
|
sub.style.visibility = '';
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -1252,6 +1276,25 @@ async function handleContextAction(action) {
|
|
|
|
|
renderQueueTable();
|
|
|
|
|
updateStatusBar();
|
|
|
|
|
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-')) {
|
|
|
|
|
const mode = action.replace('shutdown-', '');
|
|
|
|
|
await window.api.setShutdownAfterFinish(mode);
|
|
|
|
|
|