v3.6.8: Header search bar, GitHub releases auto-update

- Move streamer search to header (top right)
- Switch from custom server to GitHub Releases for auto-updates
- Add force-close running app in installer
- Center empty state vertically
- Add sidebar scroll fix
- Add desktop shortcut with version name

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
xRangerDE 2026-02-04 15:33:02 +01:00
parent 1409d77d38
commit f058c71946
5 changed files with 207 additions and 47 deletions

View File

@ -0,0 +1,4 @@
!macro customInit
; Kill running Twitch VOD Manager process before installation
nsExec::ExecToLog 'taskkill /F /IM "Twitch VOD Manager.exe"'
!macroend

View File

@ -1,6 +1,6 @@
{ {
"name": "twitch-vod-manager", "name": "twitch-vod-manager",
"version": "3.6.3", "version": "3.6.8",
"description": "Twitch VOD Manager - Download Twitch VODs easily", "description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js", "main": "dist/main.js",
"author": "xRangerDE", "author": "xRangerDE",
@ -35,11 +35,16 @@
"nsis": { "nsis": {
"oneClick": false, "oneClick": false,
"allowToChangeInstallationDirectory": true, "allowToChangeInstallationDirectory": true,
"deleteAppDataOnUninstall": false "deleteAppDataOnUninstall": false,
"createDesktopShortcut": true,
"createStartMenuShortcut": true,
"shortcutName": "Twitch VOD Manager v${version}",
"include": "build/installer.nsh"
}, },
"publish": { "publish": {
"provider": "generic", "provider": "github",
"url": "http://24-music.de/" "owner": "Sucukdeluxe",
"repo": "Twitch-VOD-Manager"
} }
} }
} }

View File

@ -45,6 +45,8 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
border-right: 1px solid rgba(255,255,255,0.1); border-right: 1px solid rgba(255,255,255,0.1);
overflow-y: auto;
overflow-x: hidden;
} }
.logo { .logo {
@ -312,7 +314,43 @@
.header-actions { .header-actions {
display: flex; display: flex;
gap: 10px; align-items: center;
gap: 15px;
}
.header-search {
display: flex;
gap: 8px;
}
.header-search input {
background: var(--bg-card);
border: 1px solid rgba(255,255,255,0.1);
border-radius: 4px;
padding: 8px 12px;
color: var(--text);
font-size: 13px;
width: 200px;
}
.header-search input:focus {
outline: none;
border-color: var(--accent);
}
.header-search button {
background: var(--accent);
border: none;
border-radius: 4px;
color: white;
padding: 8px 12px;
cursor: pointer;
font-size: 14px;
font-weight: bold;
}
.header-search button:hover {
opacity: 0.9;
} }
.btn-icon { .btn-icon {
@ -544,8 +582,13 @@
/* Empty State */ /* Empty State */
.empty-state { .empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center; text-align: center;
padding: 60px 20px; min-height: 60vh;
padding: 20px;
color: var(--text-secondary); color: var(--text-secondary);
} }
@ -1049,8 +1092,13 @@
</head> </head>
<body class="theme-twitch"> <body class="theme-twitch">
<div class="update-banner" id="updateBanner"> <div class="update-banner" id="updateBanner">
<span id="updateText">Neue Version verfugbar!</span> <span id="updateText">Neue Version verfügbar!</span>
<button onclick="downloadUpdate()">Jetzt aktualisieren</button> <div id="updateProgress" style="display: none; flex: 1; margin: 0 15px;">
<div style="background: rgba(0,0,0,0.3); border-radius: 4px; height: 8px; overflow: hidden;">
<div id="updateProgressBar" style="background: white; height: 100%; width: 0%; transition: width 0.3s;"></div>
</div>
</div>
<button id="updateButton" onclick="downloadUpdate()">Jetzt herunterladen</button>
</div> </div>
<!-- Clip Dialog Modal --> <!-- Clip Dialog Modal -->
@ -1156,10 +1204,6 @@
<div class="section-title">Streamer</div> <div class="section-title">Streamer</div>
<div class="streamers" id="streamerList"></div> <div class="streamers" id="streamerList"></div>
<div class="add-streamer">
<input type="text" id="newStreamer" placeholder="Streamer hinzufugen..." onkeypress="if(event.key==='Enter')addStreamer()">
<button onclick="addStreamer()">+</button>
</div>
<div class="queue-section"> <div class="queue-section">
<div class="queue-header"> <div class="queue-header">
@ -1178,6 +1222,10 @@
<header class="header"> <header class="header">
<h1 id="pageTitle">VODs</h1> <h1 id="pageTitle">VODs</h1>
<div class="header-actions"> <div class="header-actions">
<div class="header-search">
<input type="text" id="newStreamer" placeholder="Streamer hinzufugen..." onkeypress="if(event.key==='Enter')addStreamer()">
<button onclick="addStreamer()">+</button>
</div>
<button class="btn-icon" onclick="refreshVODs()"> <button class="btn-icon" onclick="refreshVODs()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg> <svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
Aktualisieren Aktualisieren
@ -2080,31 +2128,58 @@
} }
} }
// Updates // Updates - wird jetzt automatisch vom main process via Events gesteuert
async function checkUpdateSilent() { async function checkUpdateSilent() {
const result = await window.api.checkUpdate(); // Auto-Updater läuft automatisch beim App-Start
if (result.hasUpdate) { await window.api.checkUpdate();
document.getElementById('updateBanner').classList.add('show');
document.getElementById('updateText').textContent = `Version ${result.version} verfugbar: ${result.changelog}`;
}
} }
async function checkUpdate() { async function checkUpdate() {
const result = await window.api.checkUpdate(); // Manueller Check - zeigt Info wenn kein Update
if (result.hasUpdate) { await window.api.checkUpdate();
alert(`Neue Version ${result.version} verfugbar!\n\n${result.changelog}`); // Wenn kein Update, kommt kein Event - kurz warten dann Info zeigen
setTimeout(() => {
if (document.getElementById('updateBanner').style.display !== 'flex') {
alert('Du hast die neueste Version!');
}
}, 2000);
}
let updateReady = false;
function downloadUpdate() {
if (updateReady) {
// Update ist heruntergeladen - installieren
window.api.installUpdate();
} else { } else {
alert('Du hast die neueste Version!'); // Update herunterladen
document.getElementById('updateButton').textContent = 'Wird heruntergeladen...';
document.getElementById('updateButton').disabled = true;
document.getElementById('updateProgress').style.display = 'block';
window.api.downloadUpdate();
} }
} }
function downloadUpdate() { // Auto-Update Event Listeners
window.api.checkUpdate().then(result => { window.api.onUpdateAvailable((info) => {
if (result.downloadUrl) { document.getElementById('updateBanner').style.display = 'flex';
require('electron').shell.openExternal(result.downloadUrl); document.getElementById('updateText').textContent = `Version ${info.version} verfügbar!`;
} });
});
} window.api.onUpdateDownloadProgress((progress) => {
document.getElementById('updateProgressBar').style.width = progress.percent + '%';
const mb = (progress.transferred / 1024 / 1024).toFixed(1);
const totalMb = (progress.total / 1024 / 1024).toFixed(1);
document.getElementById('updateText').textContent = `Downloading: ${mb} / ${totalMb} MB`;
});
window.api.onUpdateDownloaded((info) => {
updateReady = true;
document.getElementById('updateProgress').style.display = 'none';
document.getElementById('updateText').textContent = `Version ${info.version} bereit!`;
document.getElementById('updateButton').textContent = 'Jetzt installieren';
document.getElementById('updateButton').disabled = false;
});
// Start // Start
init(); init();

View File

@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
// ========================================== // ==========================================
// CONFIG & CONSTANTS // CONFIG & CONSTANTS
// ========================================== // ==========================================
const APP_VERSION = '3.6.3'; const APP_VERSION = '3.6.8';
const UPDATE_CHECK_URL = 'http://24-music.de/version.json'; const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
// Paths // Paths
@ -836,28 +836,66 @@ function createWindow(): void {
mainWindow = null; mainWindow = null;
}); });
// Setup auto-updater after window is ready
setTimeout(() => { setTimeout(() => {
checkForUpdates(); setupAutoUpdater();
}, 3000); }, 3000);
} }
async function checkForUpdates(): Promise<{ hasUpdate: boolean; version?: string; changelog?: string; downloadUrl?: string }> { // ==========================================
try { // AUTO-UPDATER (electron-updater)
const response = await axios.get(UPDATE_CHECK_URL, { timeout: 5000 }); // ==========================================
const latest = response.data.version; function setupAutoUpdater() {
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
if (latest !== APP_VERSION) { autoUpdater.on('checking-for-update', () => {
return { console.log('Checking for updates...');
hasUpdate: true, });
version: latest,
changelog: response.data.changelog, autoUpdater.on('update-available', (info) => {
downloadUrl: response.data.download_url console.log('Update available:', info.version);
}; if (mainWindow) {
mainWindow.webContents.send('update-available', {
version: info.version,
releaseDate: info.releaseDate
});
} }
} catch (e) { });
console.error('Update check failed:', e);
} autoUpdater.on('update-not-available', () => {
return { hasUpdate: false }; console.log('No updates available');
});
autoUpdater.on('download-progress', (progress) => {
console.log(`Download progress: ${progress.percent.toFixed(1)}%`);
if (mainWindow) {
mainWindow.webContents.send('update-download-progress', {
percent: progress.percent,
bytesPerSecond: progress.bytesPerSecond,
transferred: progress.transferred,
total: progress.total
});
}
});
autoUpdater.on('update-downloaded', (info) => {
console.log('Update downloaded:', info.version);
if (mainWindow) {
mainWindow.webContents.send('update-downloaded', {
version: info.version
});
}
});
autoUpdater.on('error', (err) => {
console.error('Auto-updater error:', err);
});
// Check for updates
autoUpdater.checkForUpdates().catch(err => {
console.error('Update check failed:', err);
});
} }
// ========================================== // ==========================================
@ -949,7 +987,31 @@ ipcMain.handle('open-folder', (_, folderPath: string) => {
ipcMain.handle('get-version', () => APP_VERSION); ipcMain.handle('get-version', () => APP_VERSION);
ipcMain.handle('check-update', async () => { ipcMain.handle('check-update', async () => {
return await checkForUpdates(); try {
const result = await autoUpdater.checkForUpdates();
return { checking: true };
} catch (err) {
console.error('Update check failed:', err);
return { error: true };
}
});
ipcMain.handle('download-update', async () => {
try {
await autoUpdater.downloadUpdate();
return { downloading: true };
} catch (err) {
console.error('Download failed:', err);
return { error: true };
}
});
ipcMain.handle('install-update', () => {
autoUpdater.quitAndInstall(false, true);
});
ipcMain.handle('open-external', async (_, url: string) => {
await shell.openExternal(url);
}); });
ipcMain.handle('download-clip', async (_, clipUrl: string) => { ipcMain.handle('download-clip', async (_, clipUrl: string) => {

View File

@ -88,6 +88,9 @@ contextBridge.exposeInMainWorld('api', {
// App // App
getVersion: () => ipcRenderer.invoke('get-version'), getVersion: () => ipcRenderer.invoke('get-version'),
checkUpdate: () => ipcRenderer.invoke('check-update'), checkUpdate: () => ipcRenderer.invoke('check-update'),
downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'),
openExternal: (url: string) => ipcRenderer.invoke('open-external', url),
// Events // Events
onDownloadProgress: (callback: (progress: DownloadProgress) => void) => { onDownloadProgress: (callback: (progress: DownloadProgress) => void) => {
@ -107,5 +110,16 @@ contextBridge.exposeInMainWorld('api', {
}, },
onMergeProgress: (callback: (percent: number) => void) => { onMergeProgress: (callback: (percent: number) => void) => {
ipcRenderer.on('merge-progress', (_, percent) => callback(percent)); ipcRenderer.on('merge-progress', (_, percent) => callback(percent));
},
// Auto-Update Events
onUpdateAvailable: (callback: (info: { version: string; releaseDate?: string }) => void) => {
ipcRenderer.on('update-available', (_, info) => callback(info));
},
onUpdateDownloadProgress: (callback: (progress: { percent: number; bytesPerSecond: number; transferred: number; total: number }) => void) => {
ipcRenderer.on('update-download-progress', (_, progress) => callback(progress));
},
onUpdateDownloaded: (callback: (info: { version: string }) => void) => {
ipcRenderer.on('update-downloaded', (_, info) => callback(info));
} }
}); });