feat: floating drop target window and English column labels
- Small always-on-top drop target window (toggle in Settings > Allgemein) - Files dropped on it get added to the queue with hoster modal - Auto-shows on app start if previously enabled - Column headers now in English (Filename, Uploaded/Size, Progress) - Statusbar labels in English (Connections, Total) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
c0b9ec9d17
commit
0851bb09fc
@ -32,6 +32,7 @@ const DEFAULTS = {
|
|||||||
parallelUploadCount: 0, // 0 = use per-hoster limits only
|
parallelUploadCount: 0, // 0 = use per-hoster limits only
|
||||||
scaleParallelUploads: false,
|
scaleParallelUploads: false,
|
||||||
removeFromQueueOnDone: false,
|
removeFromQueueOnDone: false,
|
||||||
|
showDropTarget: false,
|
||||||
globalMaxSpeedKbs: 0, // 0 = unlimited global speed
|
globalMaxSpeedKbs: 0, // 0 = unlimited global speed
|
||||||
pendingQueue: null,
|
pendingQueue: null,
|
||||||
scramble: {
|
scramble: {
|
||||||
|
|||||||
62
main.js
62
main.js
@ -13,6 +13,7 @@ const backupCrypto = require('./lib/backup-crypto');
|
|||||||
const FolderMonitor = require('./lib/folder-monitor');
|
const FolderMonitor = require('./lib/folder-monitor');
|
||||||
|
|
||||||
let mainWindow;
|
let mainWindow;
|
||||||
|
let dropTargetWindow = null;
|
||||||
let tray = null;
|
let tray = null;
|
||||||
const configStore = new ConfigStore(app);
|
const configStore = new ConfigStore(app);
|
||||||
let uploadManager = null;
|
let uploadManager = null;
|
||||||
@ -497,6 +498,14 @@ app.whenReady().then(() => {
|
|||||||
debugLog(`folder-monitor auto-start failed: ${err.message}`);
|
debugLog(`folder-monitor auto-start failed: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Auto-show drop target if enabled
|
||||||
|
try {
|
||||||
|
const dtConfig = configStore.load();
|
||||||
|
if (dtConfig.globalSettings && dtConfig.globalSettings.showDropTarget) {
|
||||||
|
createDropTargetWindow();
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
// Auto-check for updates after 3 seconds
|
// Auto-check for updates after 3 seconds
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
try {
|
try {
|
||||||
@ -518,6 +527,7 @@ app.on('window-all-closed', () => {
|
|||||||
|
|
||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
try { folderMonitor.stop(); } catch {}
|
try { folderMonitor.stop(); } catch {}
|
||||||
|
destroyDropTargetWindow();
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- IPC Handlers ---
|
// --- IPC Handlers ---
|
||||||
@ -893,6 +903,58 @@ ipcMain.handle('get-always-on-top', () => {
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// --- Drop Target Window ---
|
||||||
|
function createDropTargetWindow() {
|
||||||
|
if (dropTargetWindow && !dropTargetWindow.isDestroyed()) return;
|
||||||
|
const { screen } = require('electron');
|
||||||
|
const display = screen.getPrimaryDisplay();
|
||||||
|
const { width, height } = display.workAreaSize;
|
||||||
|
dropTargetWindow = new BrowserWindow({
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
x: width - 140,
|
||||||
|
y: height - 140,
|
||||||
|
frame: false,
|
||||||
|
transparent: true,
|
||||||
|
alwaysOnTop: true,
|
||||||
|
skipTaskbar: true,
|
||||||
|
resizable: false,
|
||||||
|
minimizable: false,
|
||||||
|
maximizable: false,
|
||||||
|
focusable: false,
|
||||||
|
webPreferences: {
|
||||||
|
contextIsolation: true,
|
||||||
|
nodeIntegration: false,
|
||||||
|
preload: path.join(__dirname, 'preload-drop-target.js')
|
||||||
|
}
|
||||||
|
});
|
||||||
|
dropTargetWindow.loadFile('renderer/drop-target.html');
|
||||||
|
dropTargetWindow.on('closed', () => { dropTargetWindow = null; });
|
||||||
|
}
|
||||||
|
|
||||||
|
function destroyDropTargetWindow() {
|
||||||
|
if (dropTargetWindow && !dropTargetWindow.isDestroyed()) {
|
||||||
|
dropTargetWindow.close();
|
||||||
|
dropTargetWindow = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ipcMain.handle('show-drop-target', () => {
|
||||||
|
createDropTargetWindow();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('hide-drop-target', () => {
|
||||||
|
destroyDropTargetWindow();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.on('drop-target:files', (_event, paths) => {
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send('drop-target:files', paths);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// --- Shutdown after finish ---
|
// --- Shutdown after finish ---
|
||||||
let shutdownMode = 'nothing';
|
let shutdownMode = 'nothing';
|
||||||
let shutdownTimer = null;
|
let shutdownTimer = null;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "multi-hoster-uploader",
|
"name": "multi-hoster-uploader",
|
||||||
"version": "1.9.6",
|
"version": "1.9.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": {
|
||||||
|
|||||||
5
preload-drop-target.js
Normal file
5
preload-drop-target.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const { contextBridge, ipcRenderer } = require('electron');
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('dropTargetApi', {
|
||||||
|
sendFiles: (paths) => ipcRenderer.send('drop-target:files', paths)
|
||||||
|
});
|
||||||
@ -64,6 +64,13 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
ipcRenderer.on('folder-monitor:new-files', (_event, data) => callback(data));
|
ipcRenderer.on('folder-monitor:new-files', (_event, data) => callback(data));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Drop Target
|
||||||
|
showDropTarget: () => ipcRenderer.invoke('show-drop-target'),
|
||||||
|
hideDropTarget: () => ipcRenderer.invoke('hide-drop-target'),
|
||||||
|
onDropTargetFiles: (callback) => {
|
||||||
|
ipcRenderer.on('drop-target:files', (_event, paths) => callback(paths));
|
||||||
|
},
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
debugTestUpload: () => ipcRenderer.invoke('debug-test-upload'),
|
debugTestUpload: () => ipcRenderer.invoke('debug-test-upload'),
|
||||||
debugLog: (msg) => ipcRenderer.invoke('debug-log', msg),
|
debugLog: (msg) => ipcRenderer.invoke('debug-log', msg),
|
||||||
@ -91,5 +98,6 @@ contextBridge.exposeInMainWorld('api', {
|
|||||||
ipcRenderer.removeAllListeners('app:update-progress');
|
ipcRenderer.removeAllListeners('app:update-progress');
|
||||||
ipcRenderer.removeAllListeners('shutdown-countdown');
|
ipcRenderer.removeAllListeners('shutdown-countdown');
|
||||||
ipcRenderer.removeAllListeners('folder-monitor:new-files');
|
ipcRenderer.removeAllListeners('folder-monitor:new-files');
|
||||||
|
ipcRenderer.removeAllListeners('drop-target:files');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -104,6 +104,11 @@ async function init() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Drop target window: files dropped on the small floating window
|
||||||
|
window.api.onDropTargetFiles((paths) => {
|
||||||
|
addPathsToQueue(paths);
|
||||||
|
});
|
||||||
|
|
||||||
window.api.debugLog('init complete, all listeners registered');
|
window.api.debugLog('init complete, all listeners registered');
|
||||||
|
|
||||||
// Restore always-on-top state
|
// Restore always-on-top state
|
||||||
@ -414,39 +419,10 @@ function setupDragDrop() {
|
|||||||
const dropZone = document.getElementById('dropZone');
|
const dropZone = document.getElementById('dropZone');
|
||||||
// Allow drop on the entire upload view
|
// Allow drop on the entire upload view
|
||||||
const uploadView = document.getElementById('upload-view');
|
const uploadView = document.getElementById('upload-view');
|
||||||
const dropOverlay = document.getElementById('dropOverlay');
|
|
||||||
|
|
||||||
// Window-level drag overlay
|
|
||||||
let dragCounter = 0;
|
|
||||||
window.addEventListener('dragenter', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
if (!e.dataTransfer.types.includes('Files')) return;
|
|
||||||
dragCounter++;
|
|
||||||
dropOverlay.classList.add('visible');
|
|
||||||
});
|
|
||||||
window.addEventListener('dragleave', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
dragCounter--;
|
|
||||||
if (dragCounter <= 0) { dragCounter = 0; dropOverlay.classList.remove('visible'); }
|
|
||||||
});
|
|
||||||
window.addEventListener('dragover', (e) => { e.preventDefault(); });
|
|
||||||
let _dropHandled = false;
|
|
||||||
window.addEventListener('drop', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
dragCounter = 0;
|
|
||||||
dropOverlay.classList.remove('visible');
|
|
||||||
// Process files dropped anywhere (fallback if no specific handler caught it)
|
|
||||||
if (!_dropHandled && e.dataTransfer.files.length > 0) {
|
|
||||||
addDroppedFiles(e.dataTransfer.files).catch(console.error);
|
|
||||||
}
|
|
||||||
_dropHandled = false;
|
|
||||||
});
|
|
||||||
|
|
||||||
dropZone.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.add('drag-over'); });
|
dropZone.addEventListener('dragover', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.add('drag-over'); });
|
||||||
dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
|
dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); });
|
||||||
dropZone.addEventListener('drop', (e) => {
|
dropZone.addEventListener('drop', (e) => {
|
||||||
e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('drag-over');
|
e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('drag-over');
|
||||||
_dropHandled = true;
|
|
||||||
addDroppedFiles(e.dataTransfer.files).catch(console.error);
|
addDroppedFiles(e.dataTransfer.files).catch(console.error);
|
||||||
});
|
});
|
||||||
dropZone.addEventListener('click', () => pickFiles());
|
dropZone.addEventListener('click', () => pickFiles());
|
||||||
@ -456,7 +432,6 @@ function setupDragDrop() {
|
|||||||
uploadView.addEventListener('drop', (e) => {
|
uploadView.addEventListener('drop', (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
if (e.target.closest('.drop-zone')) return; // handled above
|
if (e.target.closest('.drop-zone')) return; // handled above
|
||||||
_dropHandled = true;
|
|
||||||
addDroppedFiles(e.dataTransfer.files).catch(console.error);
|
addDroppedFiles(e.dataTransfer.files).catch(console.error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1665,8 +1640,8 @@ function updateStatusBar() {
|
|||||||
const uploadedSize = Math.max(0, stats.totalSize - stats.remainingSize);
|
const uploadedSize = Math.max(0, stats.totalSize - stats.remainingSize);
|
||||||
document.getElementById('sbTotal').textContent = `${formatSize(uploadedSize)} / ${formatSize(stats.totalSize)}`;
|
document.getElementById('sbTotal').textContent = `${formatSize(uploadedSize)} / ${formatSize(stats.totalSize)}`;
|
||||||
document.getElementById('sbEta').textContent = `ETA ${etaSeconds > 0 ? formatTime(etaSeconds) : '--:--'}`;
|
document.getElementById('sbEta').textContent = `ETA ${etaSeconds > 0 ? formatTime(etaSeconds) : '--:--'}`;
|
||||||
document.getElementById('sbConnections').textContent = `Aktive Verbindungen: ${lastUploadStats.activeJobs || 0}`;
|
document.getElementById('sbConnections').textContent = `Connections: ${lastUploadStats.activeJobs || 0}`;
|
||||||
document.getElementById('sbQueueCount').textContent = `Gesamt: ${stats.total}`;
|
document.getElementById('sbQueueCount').textContent = `Total: ${stats.total}`;
|
||||||
document.getElementById('sbRemainingCount').textContent = `Remaining: ${stats.remaining}`;
|
document.getElementById('sbRemainingCount').textContent = `Remaining: ${stats.remaining}`;
|
||||||
document.getElementById('sbInProgressCount').textContent = `In Progress: ${stats.inProgress}`;
|
document.getElementById('sbInProgressCount').textContent = `In Progress: ${stats.inProgress}`;
|
||||||
document.getElementById('sbDoneCount').textContent = `Done: ${stats.done}`;
|
document.getElementById('sbDoneCount').textContent = `Done: ${stats.done}`;
|
||||||
@ -1780,6 +1755,10 @@ function renderSettings() {
|
|||||||
<label>Queue beim Start wiederherstellen</label>
|
<label>Queue beim Start wiederherstellen</label>
|
||||||
<input type="checkbox" class="settings-autosave" id="resumeQueueOnLaunchInput" ${globalSettings.resumeQueueOnLaunch === false ? '' : 'checked'}>
|
<input type="checkbox" class="settings-autosave" id="resumeQueueOnLaunchInput" ${globalSettings.resumeQueueOnLaunch === false ? '' : 'checked'}>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="settings-row checkbox-row">
|
||||||
|
<label>Drop-Target anzeigen</label>
|
||||||
|
<input type="checkbox" class="settings-autosave" id="showDropTargetInput" ${globalSettings.showDropTarget ? 'checked' : ''}>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-section-label">Updates</div>
|
<div class="settings-section-label">Updates</div>
|
||||||
<div class="settings-row">
|
<div class="settings-row">
|
||||||
@ -2056,6 +2035,7 @@ async function saveSettings(options = {}) {
|
|||||||
parallelUploadCount: Math.max(0, Math.min(100, parseInt(document.getElementById('parallelUploadCountInput')?.value || '0', 10) || 0)),
|
parallelUploadCount: Math.max(0, Math.min(100, parseInt(document.getElementById('parallelUploadCountInput')?.value || '0', 10) || 0)),
|
||||||
scaleParallelUploads: !!document.getElementById('scaleParallelUploadsInput')?.checked,
|
scaleParallelUploads: !!document.getElementById('scaleParallelUploadsInput')?.checked,
|
||||||
removeFromQueueOnDone: !!document.getElementById('removeFromQueueOnDoneInput')?.checked,
|
removeFromQueueOnDone: !!document.getElementById('removeFromQueueOnDoneInput')?.checked,
|
||||||
|
showDropTarget: !!document.getElementById('showDropTargetInput')?.checked,
|
||||||
globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)),
|
globalMaxSpeedKbs: Math.max(0, Math.round((parseFloat(document.getElementById('globalMaxSpeedMbsInput')?.value || '0') || 0) * 1024)),
|
||||||
folderMonitor: {
|
folderMonitor: {
|
||||||
enabled: !!document.getElementById('fmEnabledInput')?.checked,
|
enabled: !!document.getElementById('fmEnabledInput')?.checked,
|
||||||
@ -2080,6 +2060,13 @@ async function saveSettings(options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Drop target window
|
||||||
|
const dtCheckbox = document.getElementById('showDropTargetInput');
|
||||||
|
if (dtCheckbox) {
|
||||||
|
if (dtCheckbox.checked) await window.api.showDropTarget();
|
||||||
|
else await window.api.hideDropTarget();
|
||||||
|
}
|
||||||
|
|
||||||
for (const name of HOSTERS) {
|
for (const name of HOSTERS) {
|
||||||
const hs = { ...(hosterSettings[name] || {}) };
|
const hs = { ...(hosterSettings[name] || {}) };
|
||||||
document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => {
|
document.querySelectorAll(`.hs-input[data-hoster="${name}"]`).forEach(input => {
|
||||||
|
|||||||
68
renderer/drop-target.html
Normal file
68
renderer/drop-target.html
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
html, body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
background: transparent;
|
||||||
|
-webkit-app-region: drag;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
.target {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 2px dashed rgba(126, 220, 255, 0.5);
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(22, 24, 28, 0.85);
|
||||||
|
transition: border-color 0.15s, background 0.15s;
|
||||||
|
}
|
||||||
|
.target.drag-over {
|
||||||
|
border-color: rgba(126, 220, 255, 0.9);
|
||||||
|
background: rgba(62, 167, 255, 0.15);
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
font-size: 64px;
|
||||||
|
font-weight: 200;
|
||||||
|
color: rgba(126, 220, 255, 0.7);
|
||||||
|
line-height: 1;
|
||||||
|
-webkit-app-region: no-drag;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="target" id="target">
|
||||||
|
<div class="icon">+</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
const target = document.getElementById('target');
|
||||||
|
target.addEventListener('dragover', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
target.classList.add('drag-over');
|
||||||
|
});
|
||||||
|
target.addEventListener('dragleave', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
target.classList.remove('drag-over');
|
||||||
|
});
|
||||||
|
target.addEventListener('drop', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
target.classList.remove('drag-over');
|
||||||
|
const paths = [];
|
||||||
|
for (const file of e.dataTransfer.files) {
|
||||||
|
if (file.path) paths.push(file.path);
|
||||||
|
}
|
||||||
|
if (paths.length > 0) {
|
||||||
|
window.dropTargetApi.sendFiles(paths);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@ -7,13 +7,6 @@
|
|||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="dropOverlay" class="drop-overlay">
|
|
||||||
<div class="drop-overlay-content">
|
|
||||||
<div class="drop-overlay-icon">+</div>
|
|
||||||
<p>Dateien hier ablegen</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<nav class="tab-bar">
|
<nav class="tab-bar">
|
||||||
<button class="tab active" data-view="upload">Upload</button>
|
<button class="tab active" data-view="upload">Upload</button>
|
||||||
<button class="tab" data-view="accounts">Accounts</button>
|
<button class="tab" data-view="accounts">Accounts</button>
|
||||||
@ -84,14 +77,14 @@
|
|||||||
<table class="queue-table" id="queueTable">
|
<table class="queue-table" id="queueTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-filename sortable" data-sort="filename">Dateiname</th>
|
<th class="col-filename sortable" data-sort="filename">Filename</th>
|
||||||
<th class="col-size sortable" data-sort="size">Hochgeladen / Größe</th>
|
<th class="col-size sortable" data-sort="size">Uploaded / Size</th>
|
||||||
<th class="col-host sortable" data-sort="host">Host</th>
|
<th class="col-host sortable" data-sort="host">Host</th>
|
||||||
<th class="col-status sortable" data-sort="status">Status</th>
|
<th class="col-status sortable" data-sort="status">Status</th>
|
||||||
<th class="col-elapsed">Zeit</th>
|
<th class="col-elapsed">Zeit</th>
|
||||||
<th class="col-remaining">Rest</th>
|
<th class="col-remaining">Rest</th>
|
||||||
<th class="col-speed sortable" data-sort="speed">Speed</th>
|
<th class="col-speed sortable" data-sort="speed">Speed</th>
|
||||||
<th class="col-progress sortable" data-sort="progress">Fortschritt</th>
|
<th class="col-progress sortable" data-sort="progress">Progress</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="queueBody"></tbody>
|
<tbody id="queueBody"></tbody>
|
||||||
@ -118,7 +111,7 @@
|
|||||||
<thead id="recentFilesHead">
|
<thead id="recentFilesHead">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="col-date sortable" data-recent-sort="date">Datum<span class="sort-indicator">▼</span></th>
|
<th class="col-date sortable" data-recent-sort="date">Datum<span class="sort-indicator">▼</span></th>
|
||||||
<th class="col-filename sortable" data-recent-sort="filename">Dateiname<span class="sort-indicator">↕</span></th>
|
<th class="col-filename sortable" data-recent-sort="filename">Filename<span class="sort-indicator">↕</span></th>
|
||||||
<th class="col-host sortable" data-recent-sort="host">Host<span class="sort-indicator">↕</span></th>
|
<th class="col-host sortable" data-recent-sort="host">Host<span class="sort-indicator">↕</span></th>
|
||||||
<th class="col-link sortable" data-recent-sort="link">Link<span class="sort-indicator">↕</span></th>
|
<th class="col-link sortable" data-recent-sort="link">Link<span class="sort-indicator">↕</span></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@ -169,36 +169,6 @@ body {
|
|||||||
box-shadow: var(--panel-shadow);
|
box-shadow: var(--panel-shadow);
|
||||||
}
|
}
|
||||||
.drop-zone:hover, .drop-zone.drag-over { border-color: rgba(126, 220, 255, 0.6); background-color: rgba(62, 167, 255, 0.06); }
|
.drop-zone:hover, .drop-zone.drag-over { border-color: rgba(126, 220, 255, 0.6); background-color: rgba(62, 167, 255, 0.06); }
|
||||||
|
|
||||||
/* Drop overlay (full-window drop target) */
|
|
||||||
.drop-overlay {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
z-index: 9999;
|
|
||||||
background: rgba(16, 18, 22, 0.92);
|
|
||||||
backdrop-filter: blur(6px);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
.drop-overlay.visible { display: flex; }
|
|
||||||
.drop-overlay-content {
|
|
||||||
text-align: center;
|
|
||||||
color: rgba(126, 220, 255, 0.9);
|
|
||||||
}
|
|
||||||
.drop-overlay-icon {
|
|
||||||
font-size: 120px;
|
|
||||||
font-weight: 200;
|
|
||||||
line-height: 1;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: rgba(126, 220, 255, 0.8);
|
|
||||||
}
|
|
||||||
.drop-overlay-content p {
|
|
||||||
font-size: 16px;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
.drop-icon { font-size: 40px; margin-bottom: 8px; }
|
.drop-icon { font-size: 40px; margin-bottom: 8px; }
|
||||||
|
|
||||||
/* Queue Container */
|
/* Queue Container */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user