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

View File

@ -45,6 +45,8 @@
display: flex;
flex-direction: column;
border-right: 1px solid rgba(255,255,255,0.1);
overflow-y: auto;
overflow-x: hidden;
}
.logo {
@ -312,7 +314,43 @@
.header-actions {
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 {
@ -544,8 +582,13 @@
/* Empty State */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
padding: 60px 20px;
min-height: 60vh;
padding: 20px;
color: var(--text-secondary);
}
@ -1049,8 +1092,13 @@
</head>
<body class="theme-twitch">
<div class="update-banner" id="updateBanner">
<span id="updateText">Neue Version verfugbar!</span>
<button onclick="downloadUpdate()">Jetzt aktualisieren</button>
<span id="updateText">Neue Version verfügbar!</span>
<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>
<!-- Clip Dialog Modal -->
@ -1156,10 +1204,6 @@
<div class="section-title">Streamer</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-header">
@ -1178,6 +1222,10 @@
<header class="header">
<h1 id="pageTitle">VODs</h1>
<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()">
<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
@ -2080,31 +2128,58 @@
}
}
// Updates
// Updates - wird jetzt automatisch vom main process via Events gesteuert
async function checkUpdateSilent() {
const result = await window.api.checkUpdate();
if (result.hasUpdate) {
document.getElementById('updateBanner').classList.add('show');
document.getElementById('updateText').textContent = `Version ${result.version} verfugbar: ${result.changelog}`;
}
// Auto-Updater läuft automatisch beim App-Start
await window.api.checkUpdate();
}
async function checkUpdate() {
const result = await window.api.checkUpdate();
if (result.hasUpdate) {
alert(`Neue Version ${result.version} verfugbar!\n\n${result.changelog}`);
// Manueller Check - zeigt Info wenn kein Update
await window.api.checkUpdate();
// 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 {
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() {
window.api.checkUpdate().then(result => {
if (result.downloadUrl) {
require('electron').shell.openExternal(result.downloadUrl);
}
});
}
// Auto-Update Event Listeners
window.api.onUpdateAvailable((info) => {
document.getElementById('updateBanner').style.display = 'flex';
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
init();

View File

@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
// ==========================================
// CONFIG & CONSTANTS
// ==========================================
const APP_VERSION = '3.6.3';
const APP_VERSION = '3.6.8';
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
// Paths
@ -836,28 +836,66 @@ function createWindow(): void {
mainWindow = null;
});
// Setup auto-updater after window is ready
setTimeout(() => {
checkForUpdates();
setupAutoUpdater();
}, 3000);
}
async function checkForUpdates(): Promise<{ hasUpdate: boolean; version?: string; changelog?: string; downloadUrl?: string }> {
try {
const response = await axios.get(UPDATE_CHECK_URL, { timeout: 5000 });
const latest = response.data.version;
// ==========================================
// AUTO-UPDATER (electron-updater)
// ==========================================
function setupAutoUpdater() {
autoUpdater.autoDownload = false;
autoUpdater.autoInstallOnAppQuit = true;
if (latest !== APP_VERSION) {
return {
hasUpdate: true,
version: latest,
changelog: response.data.changelog,
downloadUrl: response.data.download_url
};
autoUpdater.on('checking-for-update', () => {
console.log('Checking for updates...');
});
autoUpdater.on('update-available', (info) => {
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);
}
return { hasUpdate: false };
});
autoUpdater.on('update-not-available', () => {
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('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) => {

View File

@ -88,6 +88,9 @@ contextBridge.exposeInMainWorld('api', {
// App
getVersion: () => ipcRenderer.invoke('get-version'),
checkUpdate: () => ipcRenderer.invoke('check-update'),
downloadUpdate: () => ipcRenderer.invoke('download-update'),
installUpdate: () => ipcRenderer.invoke('install-update'),
openExternal: (url: string) => ipcRenderer.invoke('open-external', url),
// Events
onDownloadProgress: (callback: (progress: DownloadProgress) => void) => {
@ -107,5 +110,16 @@ contextBridge.exposeInMainWorld('api', {
},
onMergeProgress: (callback: (percent: number) => void) => {
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));
}
});