diff --git a/electron/main.js b/electron/main.js index a853766..b0d46be 100644 --- a/electron/main.js +++ b/electron/main.js @@ -24,6 +24,7 @@ function setupAutoUpdater() { autoUpdater.on('update-available', () => { console.log('[AutoUpdater] Update available, downloading...'); + if (mainWindow) mainWindow.webContents.send('update-available'); }); autoUpdater.on('update-downloaded', (_event, releaseNotes, releaseName) => { @@ -42,6 +43,7 @@ function setupAutoUpdater() { autoUpdater.on('error', (err) => { console.error('[AutoUpdater] Error:', err.message); + if (mainWindow) mainWindow.webContents.send('update-error', err.message); }); // Check for updates after 5 seconds, then every 30 minutes diff --git a/electron/preload.js b/electron/preload.js index 4c37118..c64bfe1 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -5,6 +5,7 @@ contextBridge.exposeInMainWorld('electronAPI', { version: '1.5.0', onUpdateAvailable: (callback) => ipcRenderer.on('update-available', callback), onUpdateReady: (callback) => ipcRenderer.on('update-ready', callback), + onUpdateError: (callback) => ipcRenderer.on('update-error', (_e, msg) => callback(msg)), installUpdate: () => ipcRenderer.send('install-update'), setStreaming: (active) => ipcRenderer.send('streaming-status', active), }); diff --git a/web/src/App.tsx b/web/src/App.tsx index 85b43f2..f024d8d 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -27,7 +27,7 @@ export function registerTab(pluginName: string, component: React.FC<{ data: any export default function App() { const [connected, setConnected] = useState(false); const [plugins, setPlugins] = useState([]); - const [updateReady, setUpdateReady] = useState(false); + const [updateState, setUpdateState] = useState<'idle' | 'downloading' | 'ready' | 'error'>('idle'); const [activeTab, setActiveTabRaw] = useState(() => localStorage.getItem('hub_activeTab') ?? ''); const setActiveTab = (tab: string) => { @@ -97,8 +97,10 @@ export default function App() { // Listen for Electron auto-update events useEffect(() => { const api = (window as any).electronAPI; - if (!api?.onUpdateReady) return; - api.onUpdateReady(() => setUpdateReady(true)); + if (!api?.onUpdateAvailable) return; + api.onUpdateAvailable(() => setUpdateState('downloading')); + api.onUpdateReady(() => setUpdateState('ready')); + api.onUpdateError?.(() => setUpdateState('error')); }, []); // Tab icon mapping @@ -153,12 +155,40 @@ export default function App() { - {updateReady && ( -
- Neues Update verfügbar! - + {updateState !== 'idle' && ( +
+
+ {updateState === 'downloading' && ( + <> +
{'\u2B07\uFE0F'}
+

Update verfügbar

+

Update wird heruntergeladen...

+
+
+
+ + )} + {updateState === 'ready' && ( + <> +
{'\u2705'}
+

Update bereit!

+

Die App wird neu gestartet, um das Update zu installieren.

+ + + )} + {updateState === 'error' && ( + <> +
{'\u274C'}
+

Update fehlgeschlagen

+

Das Update konnte nicht heruntergeladen werden.

+ + + )} +
)} diff --git a/web/src/styles.css b/web/src/styles.css index ed5bd67..f5eecd7 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -226,22 +226,61 @@ html, body { border-radius: 4px; } -/* ── Update Banner ── */ -.hub-update-bar { +/* ── Update Modal ── */ +.hub-update-overlay { + position: fixed; + inset: 0; + z-index: 9999; display: flex; align-items: center; justify-content: center; - gap: 12px; - padding: 8px 16px; - background: rgba(var(--accent-rgb), 0.15); - border-bottom: 1px solid rgba(var(--accent-rgb), 0.3); - font-size: 13px; - font-weight: 500; - color: var(--accent); + background: rgba(0, 0, 0, 0.7); + backdrop-filter: blur(4px); } -.hub-update-bar button { - padding: 4px 14px; - font-size: 12px; +.hub-update-modal { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 12px; + padding: 32px 40px; + text-align: center; + min-width: 320px; + max-width: 400px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); +} +.hub-update-icon { + font-size: 40px; + margin-bottom: 12px; +} +.hub-update-modal h2 { + margin: 0 0 8px; + font-size: 18px; + color: var(--text-primary); +} +.hub-update-modal p { + margin: 0 0 20px; + font-size: 14px; + color: var(--text-secondary); +} +.hub-update-progress { + height: 4px; + border-radius: 2px; + background: var(--bg-deep); + overflow: hidden; +} +.hub-update-progress-bar { + height: 100%; + width: 40%; + border-radius: 2px; + background: var(--accent); + animation: hub-update-slide 1.5s ease-in-out infinite; +} +@keyframes hub-update-slide { + 0% { transform: translateX(-100%); } + 100% { transform: translateX(350%); } +} +.hub-update-btn { + padding: 8px 32px; + font-size: 14px; font-weight: 600; border: none; border-radius: var(--radius); @@ -250,7 +289,7 @@ html, body { cursor: pointer; transition: opacity var(--transition); } -.hub-update-bar button:hover { +.hub-update-btn:hover { opacity: 0.85; }