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:
parent
1409d77d38
commit
f058c71946
4
typescript-version/build/installer.nsh
Normal file
4
typescript-version/build/installer.nsh
Normal 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
|
||||||
@ -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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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) => {
|
||||||
|
|||||||
@ -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));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user