diff --git a/electron/main.js b/electron/main.js index b0d46be..44c2ff7 100644 --- a/electron/main.js +++ b/electron/main.js @@ -39,6 +39,12 @@ function setupAutoUpdater() { autoUpdater.on('update-not-available', () => { console.log('[AutoUpdater] App is up to date.'); + if (mainWindow) mainWindow.webContents.send('update-not-available'); + }); + + // Manual check from renderer + ipcMain.on('check-for-updates', () => { + try { autoUpdater.checkForUpdates(); } catch (e) { console.error('[AutoUpdater]', e.message); } }); autoUpdater.on('error', (err) => { diff --git a/electron/preload.js b/electron/preload.js index c64bfe1..62725b6 100644 --- a/electron/preload.js +++ b/electron/preload.js @@ -5,7 +5,9 @@ contextBridge.exposeInMainWorld('electronAPI', { version: '1.5.0', onUpdateAvailable: (callback) => ipcRenderer.on('update-available', callback), onUpdateReady: (callback) => ipcRenderer.on('update-ready', callback), + onUpdateNotAvailable: (callback) => ipcRenderer.on('update-not-available', callback), onUpdateError: (callback) => ipcRenderer.on('update-error', (_e, msg) => callback(msg)), + checkForUpdates: () => ipcRenderer.send('check-for-updates'), 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 f024d8d..da8d568 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 [updateState, setUpdateState] = useState<'idle' | 'downloading' | 'ready' | 'error'>('idle'); + const [updateState, setUpdateState] = useState<'idle' | 'checking' | 'downloading' | 'ready' | 'uptodate' | 'error'>('idle'); const [activeTab, setActiveTabRaw] = useState(() => localStorage.getItem('hub_activeTab') ?? ''); const setActiveTab = (tab: string) => { @@ -100,9 +100,15 @@ export default function App() { if (!api?.onUpdateAvailable) return; api.onUpdateAvailable(() => setUpdateState('downloading')); api.onUpdateReady(() => setUpdateState('ready')); + api.onUpdateNotAvailable?.(() => setUpdateState('uptodate')); api.onUpdateError?.(() => setUpdateState('error')); }, []); + const handleCheckForUpdates = () => { + setUpdateState('checking'); + (window as any).electronAPI?.checkForUpdates(); + }; + // Tab icon mapping const tabIcons: Record = { radio: '\u{1F30D}', @@ -151,6 +157,16 @@ export default function App() { Desktop App )} + {(window as any).electronAPI && ( + + )} v{version} @@ -158,6 +174,15 @@ export default function App() { {updateState !== 'idle' && (
+ {updateState === 'checking' && ( + <> +
{'\u{1F50D}'}
+

Suche nach Updates...

+
+
+
+ + )} {updateState === 'downloading' && ( <>
{'\u2B07\uFE0F'}
@@ -178,6 +203,16 @@ export default function App() { )} + {updateState === 'uptodate' && ( + <> +
{'\u2705'}
+

Alles aktuell!

+

Du verwendest bereits die neueste Version (v{version}).

+ + + )} {updateState === 'error' && ( <>
{'\u274C'}
diff --git a/web/src/styles.css b/web/src/styles.css index f5eecd7..07909e2 100644 --- a/web/src/styles.css +++ b/web/src/styles.css @@ -226,6 +226,27 @@ html, body { border-radius: 4px; } +/* ── Check for Updates Button ── */ +.hub-check-update-btn { + background: none; + border: 1px solid var(--border); + border-radius: var(--radius); + color: var(--text-secondary); + font-size: 14px; + padding: 2px 8px; + cursor: pointer; + transition: all var(--transition); + line-height: 1; +} +.hub-check-update-btn:hover:not(:disabled) { + color: var(--accent); + border-color: var(--accent); +} +.hub-check-update-btn:disabled { + opacity: 0.4; + cursor: default; +} + /* ── Update Modal ── */ .hub-update-overlay { position: fixed;