v2.4.8: UI improvements, update popup fixes, per-profile branch tracking

- Fix auto-update popup: indeterminate progress fallback when no download events, show 100% on complete
- Remove macOS auto-update warning (app is now signed)
- Disable update popup pulse animation
- Remove news tab and news section from home screen
- Center play section vertically, add community links with colored icons
- Add game version + branch display on play page (from manifest)
- Add last played timestamp tracking
- Version badge links to git.sanhost.net releases
- Profiles now store version_branch and version_client per-configuration
- Profile switch restores branch/version and refreshes settings UI
- DevTools enabled in dev mode (electron . --dev)
- Reorder community links: Chat, Discord, TG Channel, TG Group, Source

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
sanasol
2026-02-28 21:47:23 +01:00
parent fcf041be39
commit 57056e5b7a
11 changed files with 337 additions and 146 deletions

View File

@@ -169,6 +169,16 @@ window.switchProfile = async (id) => {
// Refresh UI
await loadProfiles();
// Refresh branch radio buttons in settings
if (window.SettingsAPI?.reloadBranch) {
await window.SettingsAPI.reloadBranch();
}
// Refresh game info bar on play page
if (window.LauncherUI?.loadGameInfoBar) {
window.LauncherUI.loadGameInfoBar();
}
// Refresh Mods
if (window.modsManager) {
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();

View File

@@ -1,7 +1,6 @@
import './ui.js';
import './install.js';
import './launcher.js';
import './news.js';
import { initModsManager } from './mods.js';
import './players.js';
import './settings.js';

View File

@@ -1924,6 +1924,11 @@ async function switchBranch(newBranch) {
await loadVersionBranch();
console.log('[Settings] Radio buttons updated after branch switch');
// Refresh game info bar on play page
if (window.LauncherUI?.loadGameInfoBar) {
window.LauncherUI.loadGameInfoBar();
}
setTimeout(() => {
if (window.LauncherUI) {
window.LauncherUI.hideProgress();

View File

@@ -138,6 +138,7 @@ function showLauncherOrInstall(isInstalled) {
if (gameTitle) gameTitle.style.display = '';
showPage('play-page');
setActiveNav('play');
loadGameInfoBar();
} else {
if (launcher) launcher.style.display = 'none';
if (install) {
@@ -738,13 +739,69 @@ async function checkGameInstallation() {
}
}
async function loadGameInfoBar() {
try {
const info = await window.electronAPI.getGameInfo();
const bar = document.getElementById('game-info-bar');
if (!bar) return;
const versionEl = document.getElementById('game-version-info');
const branchEl = document.getElementById('game-branch-info');
const lastPlayedEl = document.getElementById('game-last-played');
if (versionEl) {
versionEl.classList.remove('game-info-loading');
if (info.version) {
const display = info.readableVersion
? `${info.version} - ${info.readableVersion}`
: info.version;
versionEl.textContent = display;
} else {
versionEl.textContent = 'Not installed';
}
}
if (branchEl) {
branchEl.classList.remove('game-info-loading');
const isPreRelease = info.branch === 'pre-release';
branchEl.textContent = isPreRelease ? 'Pre-Release' : 'Release';
branchEl.style.color = isPreRelease ? '#f59e0b' : '';
}
if (lastPlayedEl && info.lastPlayed) {
lastPlayedEl.style.display = '';
if (!lastPlayedEl.previousElementSibling?.classList?.contains('game-info-sep')) {
const sep = document.createElement('span');
sep.className = 'game-info-sep';
lastPlayedEl.before(sep);
}
lastPlayedEl.textContent = formatTimeAgo(info.lastPlayed);
}
} catch (e) {
console.log('Could not load game info:', e);
}
}
function formatTimeAgo(timestamp) {
const diff = Date.now() - timestamp;
const minutes = Math.floor(diff / 60000);
const hours = Math.floor(diff / 3600000);
const days = Math.floor(diff / 86400000);
if (minutes < 1) return 'Just played';
if (minutes < 60) return `Played ${minutes}m ago`;
if (hours < 24) return `Played ${hours}h ago`;
if (days < 30) return `Played ${days}d ago`;
return `Played ${Math.floor(days / 30)}mo ago`;
}
window.LauncherUI = {
showPage,
setActiveNav,
showLauncherOrInstall,
showProgress,
hideProgress,
updateProgress
updateProgress,
loadGameInfoBar
};
// Make installation effects globally available

View File

@@ -16,6 +16,8 @@ class ClientUpdateManager {
});
window.electronAPI.onUpdateDownloadProgress((progress) => {
console.log('📊 download-progress event:', progress);
this.downloadProgressReceived = true;
this.updateDownloadProgress(progress);
});
@@ -49,7 +51,7 @@ class ClientUpdateManager {
const popupHTML = `
<div id="update-popup-overlay">
<div class="update-popup-container update-popup-pulse">
<div class="update-popup-container">
<div class="update-popup-header">
<div class="update-popup-icon">
<i class="fas fa-download"></i>
@@ -118,11 +120,29 @@ class ClientUpdateManager {
this.blockInterface();
// Show progress container immediately (auto-download is enabled)
this.downloadProgressReceived = false;
const progressContainer = document.getElementById('update-progress-container');
if (progressContainer) {
progressContainer.style.display = 'block';
}
// If no progress events arrive within 2 seconds, show indeterminate animation
setTimeout(() => {
if (!this.downloadProgressReceived && progressContainer) {
const bar = document.getElementById('update-progress-bar');
const speed = document.getElementById('update-progress-speed');
const percent = document.getElementById('update-progress-percent');
if (bar) {
bar.style.width = '100%';
bar.style.animation = 'indeterminateProgress 1.5s ease-in-out infinite';
bar.style.opacity = '0.7';
}
if (speed) speed.textContent = '';
if (percent) percent.textContent = 'Downloading...';
console.log('⏳ No download-progress events received, showing indeterminate progress');
}
}, 2000);
const installBtn = document.getElementById('update-install-btn');
if (installBtn) {
installBtn.addEventListener('click', async (e) => {
@@ -248,6 +268,10 @@ class ClientUpdateManager {
const progressSize = document.getElementById('update-progress-size');
if (progressBar && progress) {
// Stop indeterminate animation if it was running
progressBar.style.animation = 'none';
progressBar.style.opacity = '1';
const percent = Math.round(progress.percent || 0);
progressBar.style.width = `${percent}%`;
@@ -273,57 +297,35 @@ class ClientUpdateManager {
showUpdateDownloaded(updateInfo) {
const statusText = document.getElementById('update-status-text');
const progressContainer = document.getElementById('update-progress-container');
const progressBar = document.getElementById('update-progress-bar');
const progressPercent = document.getElementById('update-progress-percent');
const progressSpeed = document.getElementById('update-progress-speed');
const progressSize = document.getElementById('update-progress-size');
const buttonsContainer = document.getElementById('update-buttons-container');
const installBtn = document.getElementById('update-install-btn');
const downloadBtn = document.getElementById('update-download-btn');
const skipBtn = document.getElementById('update-skip-btn');
const footerText = document.getElementById('update-footer-text');
const popupContainer = document.querySelector('.update-popup-container');
// Remove breathing/pulse animation when download is complete
if (popupContainer) {
popupContainer.classList.remove('update-popup-pulse');
// Stop indeterminate animation and show 100%
if (progressBar) {
progressBar.style.animation = 'none';
progressBar.style.opacity = '1';
progressBar.style.width = '100%';
}
if (progressPercent) progressPercent.textContent = '100%';
if (progressSpeed) progressSpeed.textContent = 'Complete';
if (progressContainer) progressContainer.style.display = 'block';
// Hide progress after a short delay so user sees 100%
setTimeout(() => {
if (progressContainer) progressContainer.style.display = 'none';
}, 1500);
if (statusText) {
statusText.textContent = 'Update downloaded! Ready to install.';
}
if (progressContainer) {
progressContainer.style.display = 'none';
}
// Use platform info from main process if available, fallback to browser detection
const autoInstallSupported = updateInfo.autoInstallSupported !== undefined
? updateInfo.autoInstallSupported
: navigator.platform.toUpperCase().indexOf('MAC') < 0;
if (!autoInstallSupported) {
// macOS: Show manual download as primary since auto-update doesn't work
if (statusText) {
statusText.textContent = 'Update downloaded but auto-install may not work on macOS.';
}
if (installBtn) {
// Still show install button but as secondary option
installBtn.classList.add('update-download-btn-secondary');
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Try Install & Restart';
}
if (downloadBtn) {
// Make manual download primary
downloadBtn.classList.remove('update-download-btn-secondary');
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Manually (Recommended)';
}
if (footerText) {
footerText.textContent = 'Auto-install often fails on macOS:';
}
} else {
// Windows/Linux: Auto-install should work
if (statusText) {
statusText.textContent = 'Update downloaded! Ready to install.';
}
if (footerText) {
footerText.textContent = 'Click to install the update:';
}
if (footerText) {
footerText.textContent = 'Click to install the update:';
}
if (buttonsContainer) {
@@ -338,7 +340,7 @@ class ClientUpdateManager {
console.error('❌ Skip button not found in DOM!');
}
console.log('✅ Update downloaded, ready to install. autoInstallSupported:', autoInstallSupported);
console.log('✅ Update downloaded, ready to install');
}
handleUpdateError(errorInfo) {
@@ -366,9 +368,6 @@ class ClientUpdateManager {
if (errorMessage && errorText) {
let message = errorInfo.message || 'An error occurred during the update process.';
if (errorInfo.isMacSigningError) {
message = 'Auto-update requires code signing. Please download manually.';
}
errorText.textContent = message;
errorMessage.style.display = 'block';
}