diff --git a/main.js b/main.js index c987420..9ae00ad 100644 --- a/main.js +++ b/main.js @@ -1,4 +1,4 @@ -const { app, BrowserWindow, ipcMain, dialog, clipboard, powerSaveBlocker, nativeTheme } = require('electron'); +const { app, BrowserWindow, ipcMain, dialog, clipboard, powerSaveBlocker, nativeTheme, Tray, Menu } = require('electron'); nativeTheme.themeSource = 'dark'; const path = require('path'); const fs = require('fs'); @@ -12,6 +12,7 @@ const { checkForUpdate, installUpdate, abortUpdate } = require('./lib/updater'); const backupCrypto = require('./lib/backup-crypto'); let mainWindow; +let tray = null; const configStore = new ConfigStore(app); let uploadManager = null; const HEALTH_CHECK_TIMEOUT = 25000; @@ -453,8 +454,35 @@ function createWindow() { mainWindow.loadFile(path.join(__dirname, 'renderer', 'index.html')); } +function createTray() { + const iconPath = path.join(__dirname, 'assets', 'app_icon.ico'); + tray = new Tray(iconPath); + tray.setToolTip('Multi-Hoster-Upload'); + + const contextMenu = Menu.buildFromTemplate([ + { label: 'Öffnen', click: () => { if (mainWindow) { mainWindow.show(); mainWindow.focus(); } } }, + { type: 'separator' }, + { label: 'Beenden', click: () => { app.quit(); } } + ]); + tray.setContextMenu(contextMenu); + + tray.on('click', () => { + if (mainWindow) { mainWindow.show(); mainWindow.focus(); } + }); +} + +function updateTrayTooltip(text) { + if (tray && !tray.isDestroyed()) tray.setToolTip(text); +} + app.whenReady().then(() => { createWindow(); + createTray(); + + // Minimize to tray instead of taskbar + mainWindow.on('minimize', () => { + mainWindow.hide(); + }); // Auto-check for updates after 3 seconds setTimeout(async () => { @@ -536,9 +564,38 @@ ipcMain.handle('debug-test-upload', async () => { ipcMain.handle('select-folder', async () => { const result = await dialog.showOpenDialog(mainWindow, { - properties: ['openDirectory'] + properties: ['openDirectory', 'multiSelections'] }); - return result.canceled ? null : result.filePaths; + if (result.canceled || !result.filePaths.length) return null; + + // Recursively collect all files from selected folders + const files = []; + const walk = (dir) => { + try { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.isFile()) files.push(full); + } + } catch {} + }; + for (const folder of result.filePaths) walk(folder); + return files.length > 0 ? files : null; +}); + +ipcMain.handle('resolve-folder-files', async (_event, folderPath) => { + const files = []; + const walk = (dir) => { + try { + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const full = path.join(dir, entry.name); + if (entry.isDirectory()) walk(full); + else if (entry.isFile()) files.push(full); + } + } catch {} + }; + walk(folderPath); + return files; }); ipcMain.handle('start-upload', (_event, payload) => { @@ -583,6 +640,13 @@ ipcMain.handle('start-upload', (_event, payload) => { if (mainWindow && !mainWindow.isDestroyed()) { mainWindow.webContents.send('upload-stats', data); } + // Update tray tooltip with upload progress + if (data.state === 'uploading' && data.activeJobs > 0) { + const speedMb = ((data.globalSpeedKbs || 0) / 1024).toFixed(1); + updateTrayTooltip(`Upload: ${data.activeJobs} aktiv - ${speedMb} MB/s`); + } else { + updateTrayTooltip('Multi-Hoster-Upload'); + } }); uploadManager.on('batch-done', async (summary) => { diff --git a/package.json b/package.json index 90b177e..2161326 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "multi-hoster-uploader", - "version": "1.8.5", + "version": "1.8.6", "description": "Upload files to doodstream, voe, vidmoly, byse simultaneously", "main": "main.js", "scripts": { diff --git a/preload.js b/preload.js index 9aa725d..8581453 100644 --- a/preload.js +++ b/preload.js @@ -27,6 +27,7 @@ contextBridge.exposeInMainWorld('api', { // File selection selectFiles: () => ipcRenderer.invoke('select-files'), selectFolder: () => ipcRenderer.invoke('select-folder'), + resolveFolderFiles: (folderPath) => ipcRenderer.invoke('resolve-folder-files', folderPath), // Upload control startUpload: (payload) => ipcRenderer.invoke('start-upload', payload), diff --git a/renderer/app.js b/renderer/app.js index c837473..0104ad4 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -391,7 +391,7 @@ function setupDragDrop() { dropZone.addEventListener('dragleave', (e) => { e.preventDefault(); dropZone.classList.remove('drag-over'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); e.stopPropagation(); dropZone.classList.remove('drag-over'); - addDroppedFiles(e.dataTransfer.files); + addDroppedFiles(e.dataTransfer.files).catch(console.error); }); dropZone.addEventListener('click', () => pickFiles()); @@ -400,38 +400,77 @@ function setupDragDrop() { uploadView.addEventListener('drop', (e) => { e.preventDefault(); if (e.target.closest('.drop-zone')) return; // handled above - addDroppedFiles(e.dataTransfer.files); + addDroppedFiles(e.dataTransfer.files).catch(console.error); }); } let _pendingFiles = []; // Files waiting for hoster modal confirmation -function addDroppedFiles(fileList) { - const files = Array.from(fileList); - const existingPaths = new Set([ - ...selectedFiles.map(f => f.path), - ..._pendingFiles.map(f => f.path) - ]); - const newFiles = []; - for (const file of files) { - // Use webUtils.getPathForFile (Electron 33+) with fallback to file.path - let filePath = ''; - try { filePath = window.api.getPathForFile(file); } catch { filePath = file.path || ''; } - const fileName = file.name || ''; - if (filePath && !existingPaths.has(filePath)) { - newFiles.push({ path: filePath, name: fileName, size: file.size }); - existingPaths.add(filePath); +let _addingDropped = false; + +async function addDroppedFiles(fileList) { + if (_addingDropped) return; + _addingDropped = true; + try { + const files = Array.from(fileList); + const existingPaths = new Set([ + ...selectedFiles.map(f => f.path), + ..._pendingFiles.map(f => f.path) + ]); + const newFiles = []; + + for (const file of files) { + let filePath = ''; + try { filePath = window.api.getPathForFile(file); } catch { filePath = file.path || ''; } + if (!filePath) continue; + + // Detect folders: directories report size 0 and empty type in Electron drag-and-drop + if (file.type === '' && file.size === 0) { + try { + const folderFiles = await window.api.resolveFolderFiles(filePath); + if (folderFiles && folderFiles.length > 0) { + for (const fp of folderFiles) { + if (!existingPaths.has(fp)) { + const name = fp.split('\\').pop().split('/').pop(); + newFiles.push({ path: fp, name, size: null }); + existingPaths.add(fp); + } + } + continue; + } + } catch {} + } + + // Regular file + const fileName = file.name || ''; + if (!existingPaths.has(filePath)) { + newFiles.push({ path: filePath, name: fileName, size: file.size }); + existingPaths.add(filePath); + } } - } - if (newFiles.length > 0) { - _pendingFiles.push(...newFiles); - openHosterModal(); + + if (newFiles.length > 0) { + _pendingFiles.push(...newFiles); + openHosterModal(); + } + } finally { + _addingDropped = false; } } async function pickFiles() { const paths = await window.api.selectFiles(); if (!paths) return; + addPathsToQueue(paths); +} + +async function pickFolder() { + const paths = await window.api.selectFolder(); + if (!paths) return; + addPathsToQueue(paths); +} + +function addPathsToQueue(paths) { const newFiles = []; for (const p of paths) { if (!selectedFiles.find(f => f.path === p) && !_pendingFiles.find(f => f.path === p)) { @@ -2327,6 +2366,7 @@ window.addEventListener('beforeunload', () => { // --- Setup Listeners --- function setupListeners() { document.getElementById('addFilesBtn').addEventListener('click', pickFiles); + document.getElementById('addFolderBtn').addEventListener('click', pickFolder); document.getElementById('startUploadBtn').addEventListener('click', startUpload); document.getElementById('startSelectedBtn').addEventListener('click', startSelectedUpload); diff --git a/renderer/index.html b/renderer/index.html index 0162dfe..c7aae58 100644 --- a/renderer/index.html +++ b/renderer/index.html @@ -28,6 +28,7 @@
@@ -272,8 +273,6 @@