diff --git a/.gitignore b/.gitignore index d45a107..fe318b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ node_modules/ +.dev-runner.lock release/ .artifacts/ __pycache__/ diff --git a/lib/startup-renderer.js b/lib/startup-renderer.js index deded51..8b6722f 100644 --- a/lib/startup-renderer.js +++ b/lib/startup-renderer.js @@ -1,5 +1,5 @@ function configureStartupRenderer(app) { - app.disableHardwareAcceleration(); + return app; } function createStartupWindow(BrowserWindow, options) { diff --git a/main.js b/main.js index c2c63ba..118bcb2 100644 --- a/main.js +++ b/main.js @@ -1358,6 +1358,7 @@ async function runHosterHealthCheck(config, requestedChecks) { function createWindow() { const startupWindow = createStartupWindow(BrowserWindow, { + title: 'Multi Hoster Uploader', width: 1100, height: 750, minWidth: 800, diff --git a/package-lock.json b/package-lock.json index b6c33a3..e06ebe2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "multi-hoster-uploader", - "version": "2.0.7", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "multi-hoster-uploader", - "version": "2.0.7", + "version": "2.1.0", "dependencies": { "chokidar": "^3.6.0", "undici": "^7.29.0", diff --git a/package.json b/package.json index 9b0f19d..3c4192f 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,11 @@ { "name": "multi-hoster-uploader", - "version": "2.0.7", + "version": "2.1.0", "description": "Upload files to doodstream, voe, vidmoly, byse simultaneously", "main": "main.js", "scripts": { "start": "electron .", + "dev": "node scripts/dev-runner.cjs", "test": "node --test tests/*.test.js tests/ui-smoke.js", "test:backup-api": "npm --prefix services/backup-api test", "lint": "eslint .", diff --git a/renderer/app.js b/renderer/app.js index ed77095..e073dbf 100644 --- a/renderer/app.js +++ b/renderer/app.js @@ -526,13 +526,26 @@ function _isHistoryTabActive() { const nextView = viewsById[`${tab.dataset.view}-view`]; if (nextView) nextView.classList.add('active'); activeTab = tab; + syncTabIndicator(tab); if (tab.dataset.view === 'history' && (_historyDirty || !_historyEverLoaded)) { loadHistory(); } }; const tabBar = tabs[0] && tabs[0].parentElement; + const tabIndicator = tabBar?.querySelector('.tab-indicator'); + function syncTabIndicator(tab) { + if (!tabIndicator || !tab) return; + tabIndicator.style.width = `${tab.offsetWidth}px`; + tabIndicator.style.transform = `translateX(${tab.offsetLeft}px)`; + } if (tabBar) { + if (tabIndicator) { + tabIndicator.style.transition = 'none'; + syncTabIndicator(activeTab); + requestAnimationFrame(() => { tabIndicator.style.transition = ''; }); + window.addEventListener('resize', () => syncTabIndicator(activeTab)); + } tabBar.addEventListener('click', (e) => handle(e.target)); tabBar.addEventListener('keydown', (e) => { if (!['ArrowLeft', 'ArrowRight', 'Home', 'End'].includes(e.key)) return; @@ -562,19 +575,45 @@ function initMenuBar() { menuBar.querySelectorAll('[data-menu-dropdown]').forEach(d => { dropdowns[d.dataset.menuDropdown] = d; }); const triggers = {}; menuBar.querySelectorAll('[data-menu-trigger]').forEach(t => { triggers[t.dataset.menuTrigger] = t; }); + const panelTokens = new WeakMap(); + + function openPanel(panel) { + if (!panel) return; + panelTokens.set(panel, (panelTokens.get(panel) || 0) + 1); + panel.classList.remove('menu-closing', 'menu-opening'); + panel.style.display = ''; + void panel.offsetHeight; + panel.classList.add('menu-opening'); + } + + function closePanel(panel) { + if (!panel || window.getComputedStyle(panel).display === 'none') return; + const token = (panelTokens.get(panel) || 0) + 1; + panelTokens.set(panel, token); + panel.classList.remove('menu-opening', 'menu-closing'); + void panel.offsetHeight; + panel.classList.add('menu-closing'); + const finish = () => { + if (panelTokens.get(panel) !== token) return; + panel.style.display = 'none'; + panel.classList.remove('menu-closing'); + }; + panel.addEventListener('animationend', finish, { once: true }); + setTimeout(finish, 220); + } function closeMenus() { openMenu = null; - for (const k in dropdowns) dropdowns[k].style.display = 'none'; + for (const k in dropdowns) closePanel(dropdowns[k]); for (const k in triggers) triggers[k].classList.remove('open'); - menuBar.querySelectorAll('.menu-submenu-dropdown').forEach(s => { s.style.display = 'none'; }); + menuBar.querySelectorAll('.menu-submenu-dropdown').forEach(closePanel); } function openMenuNamed(name) { if (openMenu === name) return; closeMenus(); openMenu = name; - if (dropdowns[name]) dropdowns[name].style.display = ''; + openPanel(dropdowns[name]); if (triggers[name]) triggers[name].classList.add('open'); if (name === 'einstellungen') _syncMenuSettings(); } @@ -590,8 +629,8 @@ function initMenuBar() { menuBar.querySelectorAll('.menu-submenu').forEach(sm => { const sub = sm.querySelector('.menu-submenu-dropdown'); - sm.addEventListener('mouseenter', () => { if (sub) sub.style.display = ''; }); - sm.addEventListener('mouseleave', () => { if (sub) sub.style.display = 'none'; }); + sm.addEventListener('mouseenter', () => openPanel(sub)); + sm.addEventListener('mouseleave', () => closePanel(sub)); }); menuBar.querySelectorAll('[data-menu-action]').forEach(item => { @@ -3544,6 +3583,33 @@ function renderHealthCheckResults(_results) { if (container) container.innerHTML = ''; } +let _appAlertResolve = null; + +function closeAppAlert() { + const modal = document.getElementById('appAlertModal'); + if (!modal) return; + modal.style.display = 'none'; + modal.setAttribute('aria-hidden', 'true'); + const resolve = _appAlertResolve; + _appAlertResolve = null; + if (resolve) resolve(); +} + +function showAppAlert(message, title = 'Hinweis') { + const modal = document.getElementById('appAlertModal'); + const titleEl = document.getElementById('appAlertTitle'); + const messageEl = document.getElementById('appAlertMessage'); + const confirm = document.getElementById('appAlertConfirmBtn'); + if (!modal || !titleEl || !messageEl || !confirm) return Promise.resolve(); + if (_appAlertResolve) closeAppAlert(); + titleEl.textContent = title; + messageEl.textContent = String(message || ''); + modal.style.display = 'flex'; + modal.setAttribute('aria-hidden', 'false'); + confirm.focus(); + return new Promise(resolve => { _appAlertResolve = resolve; }); +} + async function executeHealthCheck(hosters, _mode) { renderHealthCheckResults([]); const result = await window.api.runHealthCheck({ hosters }); @@ -3578,7 +3644,7 @@ async function runHealthCheck(mode = 'manual', requestedHosters = null) { .map(({ name, account }) => ({ hoster: name, accountId: account.id })); } if (hosters.length === 0) { - if (mode === 'manual') alert('Keine Hoster mit Zugangsdaten für einen Check.'); + if (mode === 'manual') await showAppAlert('Keine Hoster mit Zugangsdaten für einen Check.'); return []; } healthCheckRunning = true; @@ -3662,7 +3728,7 @@ function renderSettings() {