mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 12:51:47 -03:00
Release Build v2.2.0 (#236)
* fix: resolve cross-platform EPERM permissions errors modManager.js: - Switch from hardcoded 'junction' to dynamic symlink type based on OS (fixing Linux EPERM). - Add retry logic for directory removal to handle file locking race conditions. - Improve broken symlink detection during profile sync. gameManager.js: - Implement retry loop (3 attempts) for game directory removal in updateGameFiles to prevent EBUSY/EPERM errors on Windows. paths.js: - Prevent fs.mkdirSync failure in getModsPath by pre-checking for broken symbolic links. * fix: missing pacman builds * prepare release for 2.1.1 minor fix for EPERM error permission * prepare release 2.1.1 minor fix EPERM permission error * prepare release 2.1.1 * Update README.md Windows Prequisites for ARM64 builds * fix: remove broken symlink after detected * fix: add pathexists for paths.js to check symlink * fix: isbrokenlink should be true to remove the symlink * add arch package .pkg.tar.zst for release * fix: release workflow for build-arch and build-linux * build-arch job now only build arch .pkg.tar.zst package instead of the whole generic linux. * build-linux job now exclude .pacman package since its deprecated and should not be used. * fix: removes pacman build as it replaced by tar.zst and adds build:arch shortcut for pkgbuild * aur: add proper VCS (-git) PKGBUILD created clean VCS-based PKGBUILD following arch packaging conventions. this explicitly marked as a rolling (-git) build and derives its version dynamically from git tags and commit history via pkgver(). previous hybrid approach has been changed. key changes: - use -git suffix to clearly indicate rolling source builds - set pkgver=0 and compute the actual version via pkgver() - build only a directory layout using electron-builder (--dir) - avoid generating AppImage, deb, rpm, or pacman installers - align build and package steps with Arch packaging guidelines note: this PKGBUILD is intended for development and AUR use only and is not suitable for binary redistribution or release artifacts. * ci: add fixed-version PKGBUILD for Arch Linux releases this PKGBUILD intended for CI and GitHub release artifacts. targets tagged releases only and uses a fixed pkgver that matches the corresponding git tag. all of the VCS logic has been removed to PKGBUILD-git to ensure reproducible builds and stable versioning suitable for binary distribution. the build process relies on electron-builder directory output (--dir) and packages only the unpacked application into a standard Arch Linux package (.pkg.tar.zst). other distro format are excluded from this path and handled separately. this change establishes a clear separation between: - rolling AUR development builds (-git) - CI-generated, versioned Arch Linux release packages the result is predictable artifact naming, correct version alignment, and Arch-compliant packaging for downstream users. * Update README.md adds information for Arch build * Update README.md BUILD.md location was changed and now this link is poiting to nothing * Update PKGBUILD * Update PKGBUILD-git * chore: fix ubuntu/debian part in README.md * Polish language support (#195) * Update support_request.yml Added hardware specification * Update bug_report.yml Add logs textfield to bug report * chore: add changelog in README.md * fix screenshot input in feature_request.yml * add hardware spec input in bug_report.yml * fix: PKGBUILD pkgname variable fix * userdata migration [need review from other OS] * french translate * Add German and Swedish translations Added de.json and sv.json locale files for German and Swedish language support. Updated i18n.js to register 'de' and 'sv' as available languages in the launcher. * Update README.md * chore: add offline-mode warning to the README.md * chore: add downloads counter in README.md * fix: Steam Deck/Ubuntu crash - use system libzstd.so The bundled libzstd.so is incompatible with glibc 2.41's stricter heap validation, causing "free(): invalid pointer" crashes. Solution: Automatically replace bundled libzstd.so with system version on Linux. The launcher detects and symlinks to /usr/lib/libzstd.so.1. - Auto-detect system libzstd at common paths (Arch, Debian, Fedora) - Backup bundled version as libzstd.so.bundled - Create symlink to system version - Add HYTALE_NO_LIBZSTD_FIX=1 to disable if needed Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: remove Windows and Linux ARM64 information on the README.md * Update support_request.yml * fix: improve update system UX and macOS compatibility Update System Improvements: - Fix duplicate update popups by disabling legacy updater.js - Add skip button to update popup (shows after 30s, on error, or after download) - Add macOS-specific handling with manual download as primary option - Add missing open-download-page IPC handler - Add missing unblockInterface() method to properly clean up after popup close - Add quitAndInstallUpdate alias in preload for compatibility - Remove pulse animation when download completes - Fix manual download button to show correct status and close popup - Sync player name to settings input after first install Client Patcher Cleanup: - Remove server patching code (server uses pre-patched JAR from CDN) - Simplify to client-only patching - Remove unused imports (crypto, AdmZip, execSync, spawn, javaManager) - Remove unused methods (stringToUtf8, findAndReplaceDomainUtf8) - Move localhost dev code to backup file for reference Code Quality Fixes: - Fix duplicate DOMContentLoaded handlers in install.js - Fix duplicate checkForUpdates definition in preload.js - Fix redundant if/else in onProgressUpdate callback - Fix typo "Harwadre" -> "Hardware" in preload.js Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * Add Russian language support Added Russian (ru) to the list of available languages. * chore: drafting documentation on SERVER.md * Some updates in Russian language localization file * fix * Update ru.json * Fixed Java runtime name and fixed typo * fixed untranslated place * Update ru.json * Update ru.json * Update ru.json * Update ru.json * Update ru.json * fix: timeout getLatestClient fixes #138 * fix: change default version to 7.pwr in main.js * fix: change default release version to 7.pwr * fix: change version release to 7.pwr * docs: Add comprehensive troubleshooting guide (#209) Add TROUBLESHOOTING.md with solutions for common issues including: - Windows: Firewall configuration, duplicate mods, SmartScreen - Linux: GPU detection (NVIDIA/AMD), SDL3_image/libpng dependencies, Wayland/X11 issues, Steam Deck support - macOS: Rosetta 2 for Apple Silicon, code signing, quarantine - Connection: Server boot failures, regional restrictions - Authentication: Token errors, config reset procedures - Avatar/Cosmetics: F2P limitations documentation - Backup locations for all platforms - Log locations for bug reports Solutions compiled from closed GitHub issues (#205, #155, #90, #60, #144, #192) and community feedback. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> * Standardize language codes, improve formatting, and update all locale files. (#224) * Update German (Germany) localization * Update Español (España) localization * Update French (France) localization * Update Polish (Poland) localization * Update Portuguese (Brazil) localization * Update Russian (Russia) localization * Update Swedish (Sweden) localization * Update Turkish (Turkey) localization * Update language codes, names and alphabetical in i18n system * Changed Spanish language name to the Formal name "Spanish (Spain)" * Fix PKGBUILD-git * Fix PKGBUILD * delete cache after installation * Enforce 16-char player name limit and update mod sync Added a maxlength attribute to the player name input and enforced a 16-character limit in both install and settings scripts, providing user feedback if exceeded. Refactored modManager.js to replace symlink-based mod management with a copy-based system, copying enabled mods to HytaleSaves\Mods and removing legacy symlink logic to improve compatibility and avoid permission issues. * Update installation subtitle * chore: update quickstart link in README.md * chore: delete warning of Ubuntu-Debian at Linux Prequisites section * added featured server list from api * Add Featured Servers page to GUI * Update Discord invite URL in client patcher * Add differential update system * Remove launcher chat and add Discord popup * fix: removed 'check disk space' alert on permission file error * fix: upgrade tar to ^7.5.6 version * fix: re-add universal arch for mac * fix: upgrade electron/rebuild to 4.0.3 * fix: removed override tar version * fix: pkgbuild version to 2.1.2 * fix: src.tar.zst and srcinfo missing files * feat: add Indonesian language translation * fix: GPU preference hint to Laptop-only * feat: create two columns for settings page * Add Discord invite link to rpc * docs: add recordings form, fix OS list * Release v2.2.0 * Release v2.2.0 * Release v2.2.0 * chore: delete icon.ico, moved to build folder * chore: delete icon.png, moved to build folder * fix: build and release for tag push-only in release.yml * fix: gamescope steam deck issue fixes #186 hopefully * Support branch selection for server patching * chose: add auto-patch system for pre-release JAR --------- Co-authored-by: TalesAmaral <57869141+TalesAmaral@users.noreply.github.com> Co-authored-by: walti0 <95646872+walti0@users.noreply.github.com> Co-authored-by: AMIAY <letudiantenrap.collab@gmail.com> Co-authored-by: sanasol <mail@sanasol.ws> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: Terromur <79866197+Terromur@users.noreply.github.com> Co-authored-by: Zakhar Smokotov <zaharb840@gmail.com> Co-authored-by: xSamiVS <samtaiebc@gmail.com>
This commit is contained in:
500
GUI/js/chat.js
500
GUI/js/chat.js
@@ -1,500 +0,0 @@
|
||||
|
||||
let socket = null;
|
||||
let isAuthenticated = false;
|
||||
let messageQueue = [];
|
||||
let chatUsername = '';
|
||||
let userColor = '#3498db';
|
||||
let userBadge = null;
|
||||
const SOCKET_URL = 'https://chat.hytalef2p.com';
|
||||
const MAX_MESSAGE_LENGTH = 500;
|
||||
|
||||
async function getOrCreatePlayerId() {
|
||||
return `user_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
}
|
||||
|
||||
export async function initChat() {
|
||||
if (window.electronAPI?.loadChatUsername) {
|
||||
chatUsername = await window.electronAPI.loadChatUsername();
|
||||
}
|
||||
|
||||
if (window.electronAPI?.loadChatColor) {
|
||||
const savedColor = await window.electronAPI.loadChatColor();
|
||||
if (savedColor) {
|
||||
userColor = savedColor;
|
||||
}
|
||||
}
|
||||
|
||||
if (!chatUsername || chatUsername.trim() === '') {
|
||||
showUsernameModal();
|
||||
return;
|
||||
}
|
||||
|
||||
setupChatUI();
|
||||
setupColorSelector();
|
||||
await connectToChat();
|
||||
}
|
||||
|
||||
function showUsernameModal() {
|
||||
const modal = document.getElementById('chatUsernameModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'flex';
|
||||
|
||||
const input = document.getElementById('chatUsernameInput');
|
||||
if (input) {
|
||||
setTimeout(() => input.focus(), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function hideUsernameModal() {
|
||||
const modal = document.getElementById('chatUsernameModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
async function submitChatUsername() {
|
||||
const input = document.getElementById('chatUsernameInput');
|
||||
const errorMsg = document.getElementById('chatUsernameError');
|
||||
|
||||
if (!input) return;
|
||||
|
||||
const username = input.value.trim();
|
||||
|
||||
if (username.length === 0) {
|
||||
if (errorMsg) errorMsg.textContent = 'Username cannot be empty';
|
||||
return;
|
||||
}
|
||||
|
||||
if (username.length < 3) {
|
||||
if (errorMsg) errorMsg.textContent = 'Username must be at least 3 characters';
|
||||
return;
|
||||
}
|
||||
|
||||
if (username.length > 20) {
|
||||
if (errorMsg) errorMsg.textContent = 'Username must be 20 characters or less';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!/^[a-zA-Z0-9_-]+$/.test(username)) {
|
||||
if (errorMsg) errorMsg.textContent = 'Username can only contain letters, numbers, - and _';
|
||||
return;
|
||||
}
|
||||
|
||||
chatUsername = username;
|
||||
if (window.electronAPI?.saveChatUsername) {
|
||||
await window.electronAPI.saveChatUsername(username);
|
||||
}
|
||||
|
||||
hideUsernameModal();
|
||||
|
||||
setupChatUI();
|
||||
await connectToChat();
|
||||
}
|
||||
|
||||
function setupChatUI() {
|
||||
const sendBtn = document.getElementById('chatSendBtn');
|
||||
const chatInput = document.getElementById('chatInput');
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
|
||||
if (!sendBtn || !chatInput || !chatMessages) {
|
||||
console.warn('Chat UI elements not found');
|
||||
return;
|
||||
}
|
||||
|
||||
sendBtn.addEventListener('click', () => {
|
||||
sendMessage();
|
||||
});
|
||||
|
||||
chatInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
chatInput.addEventListener('input', () => {
|
||||
if (chatInput.value.length > MAX_MESSAGE_LENGTH) {
|
||||
chatInput.value = chatInput.value.substring(0, MAX_MESSAGE_LENGTH);
|
||||
}
|
||||
updateCharCounter();
|
||||
});
|
||||
|
||||
updateCharCounter();
|
||||
}
|
||||
|
||||
async function connectToChat() {
|
||||
try {
|
||||
if (!window.io) {
|
||||
await loadSocketIO();
|
||||
}
|
||||
|
||||
const userId = await window.electronAPI?.getUserId();
|
||||
|
||||
if (!userId) {
|
||||
console.error('User ID not available');
|
||||
addSystemMessage('Error: Could not connect to chat');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!chatUsername || chatUsername.trim() === '') {
|
||||
console.error('Chat username not set');
|
||||
addSystemMessage('Error: Username not set');
|
||||
return;
|
||||
}
|
||||
|
||||
socket = io(SOCKET_URL, {
|
||||
transports: ['websocket', 'polling'],
|
||||
reconnection: true,
|
||||
reconnectionAttempts: 5,
|
||||
reconnectionDelay: 1000
|
||||
});
|
||||
|
||||
socket.on('connect', async () => {
|
||||
console.log('Connected to chat server');
|
||||
|
||||
const uuid = await window.electronAPI?.getCurrentUuid();
|
||||
|
||||
socket.emit('authenticate', {
|
||||
username: chatUsername,
|
||||
userId,
|
||||
uuid: uuid,
|
||||
userColor: userColor
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('authenticated', (data) => {
|
||||
isAuthenticated = true;
|
||||
userBadge = data.badge;
|
||||
addSystemMessage(`Connected as ${data.username}`);
|
||||
|
||||
while (messageQueue.length > 0) {
|
||||
const msg = messageQueue.shift();
|
||||
socket.emit('send_message', { message: msg });
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('message', (data) => {
|
||||
if (data.type === 'system') {
|
||||
addSystemMessage(data.message);
|
||||
} else if (data.type === 'user') {
|
||||
addUserMessage(data.username, data.message, data.timestamp, data.userColor, data.badge);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('users_update', (data) => {
|
||||
updateOnlineCount(data.count);
|
||||
});
|
||||
|
||||
socket.on('error', (data) => {
|
||||
addSystemMessage(`Error: ${data.message}`, 'error');
|
||||
});
|
||||
|
||||
socket.on('clear_chat', (data) => {
|
||||
clearAllMessages();
|
||||
addSystemMessage(data.message || 'Chat cleared by server', 'warning');
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
isAuthenticated = false;
|
||||
console.log('Disconnected from chat server');
|
||||
addSystemMessage('Disconnected from chat', 'error');
|
||||
});
|
||||
|
||||
socket.on('connect_error', (error) => {
|
||||
console.error('Connection error:', error);
|
||||
addSystemMessage('Connection error. Retrying...', 'error');
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error connecting to chat:', error);
|
||||
addSystemMessage('Failed to connect to chat server', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function loadSocketIO() {
|
||||
return new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = 'https://cdn.socket.io/4.6.1/socket.io.min.js';
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.head.appendChild(script);
|
||||
});
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
const chatInput = document.getElementById('chatInput');
|
||||
const message = chatInput.value.trim();
|
||||
|
||||
if (!message || message.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.length > MAX_MESSAGE_LENGTH) {
|
||||
addSystemMessage(`Message too long (max ${MAX_MESSAGE_LENGTH} characters)`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!socket || !isAuthenticated) {
|
||||
messageQueue.push(message);
|
||||
addSystemMessage('Connecting... Your message will be sent soon.', 'warning');
|
||||
chatInput.value = '';
|
||||
updateCharCounter();
|
||||
return;
|
||||
}
|
||||
|
||||
socket.emit('send_message', { message });
|
||||
|
||||
chatInput.value = '';
|
||||
updateCharCounter();
|
||||
}
|
||||
|
||||
function addUserMessage(username, message, timestamp, userColor = '#3498db', badge = null) {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (!chatMessages) return;
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = 'chat-message user-message';
|
||||
|
||||
const time = new Date(timestamp).toLocaleTimeString('en-US', {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
|
||||
let badgeHTML = '';
|
||||
if (badge) {
|
||||
let badgeStyle = '';
|
||||
if (badge.style === 'rainbow') {
|
||||
badgeStyle = `background: linear-gradient(45deg, #ff6b6b, #4ecdc4, #45b7d1, #96ceb4, #ffeaa7, #fab1a0, #fd79a8); background-size: 400% 400%; animation: rainbow 3s ease infinite; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-weight: bold; display: inline;`;
|
||||
} else if (badge.style === 'gradient') {
|
||||
if (badge.badge === 'CONTRIBUTOR') {
|
||||
badgeStyle = `background: linear-gradient(45deg, #22c55e, #16a34a); background-size: 200% 200%; animation: contributorGlow 2s ease infinite; -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text; font-weight: bold; display: inline;`;
|
||||
} else {
|
||||
badgeStyle = `color: ${badge.color}; font-weight: bold; display: inline;`;
|
||||
}
|
||||
}
|
||||
|
||||
badgeHTML = `<span class="user-badge" style="${badgeStyle}">[${badge.badge}]</span> `;
|
||||
}
|
||||
|
||||
messageDiv.innerHTML = `
|
||||
<div class="message-header">
|
||||
<span class="message-user-info">${badgeHTML}<span class="message-username" style="font-weight: bold;" data-username-color="${userColor}">${escapeHtml(username)}</span></span>
|
||||
<span class="message-time">${time}</span>
|
||||
</div>
|
||||
<div class="message-content">${message}</div>
|
||||
`;
|
||||
|
||||
const usernameElement = messageDiv.querySelector('.message-username');
|
||||
if (usernameElement) {
|
||||
applyUserColorStyle(usernameElement, userColor);
|
||||
}
|
||||
|
||||
chatMessages.appendChild(messageDiv);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function addSystemMessage(message, type = 'info') {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (!chatMessages) return;
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = `chat-message system-message system-${type}`;
|
||||
messageDiv.innerHTML = `
|
||||
<div class="message-content">
|
||||
<i class="fas fa-info-circle"></i> ${escapeHtml(message)}
|
||||
</div>
|
||||
`;
|
||||
|
||||
chatMessages.appendChild(messageDiv);
|
||||
scrollToBottom();
|
||||
}
|
||||
|
||||
function updateOnlineCount(count) {
|
||||
const onlineCountElement = document.getElementById('chatOnlineCount');
|
||||
if (onlineCountElement) {
|
||||
onlineCountElement.textContent = count;
|
||||
}
|
||||
}
|
||||
|
||||
function updateCharCounter() {
|
||||
const chatInput = document.getElementById('chatInput');
|
||||
const charCounter = document.getElementById('chatCharCounter');
|
||||
|
||||
if (chatInput && charCounter) {
|
||||
const length = chatInput.value.length;
|
||||
charCounter.textContent = `${length}/${MAX_MESSAGE_LENGTH}`;
|
||||
|
||||
if (length > MAX_MESSAGE_LENGTH * 0.9) {
|
||||
charCounter.classList.add('warning');
|
||||
} else {
|
||||
charCounter.classList.remove('warning');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function scrollToBottom() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function clearAllMessages() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.innerHTML = '';
|
||||
console.log('Chat cleared');
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', () => {
|
||||
if (socket && socket.connected) {
|
||||
socket.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const usernameSubmitBtn = document.getElementById('chatUsernameSubmit');
|
||||
const usernameCancelBtn = document.getElementById('chatUsernameCancel');
|
||||
const usernameInput = document.getElementById('chatUsernameInput');
|
||||
|
||||
if (usernameSubmitBtn) {
|
||||
usernameSubmitBtn.addEventListener('click', submitChatUsername);
|
||||
}
|
||||
|
||||
if (usernameCancelBtn) {
|
||||
usernameCancelBtn.addEventListener('click', () => {
|
||||
hideUsernameModal();
|
||||
const playNavItem = document.querySelector('[data-page="play"]');
|
||||
if (playNavItem) playNavItem.click();
|
||||
});
|
||||
}
|
||||
|
||||
if (usernameInput) {
|
||||
usernameInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
submitChatUsername();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const chatNavItem = document.querySelector('[data-page="chat"]');
|
||||
if (chatNavItem) {
|
||||
chatNavItem.addEventListener('click', () => {
|
||||
if (!socket) {
|
||||
initChat();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function setupColorSelector() {
|
||||
const colorBtn = document.getElementById('chatColorBtn');
|
||||
if (colorBtn) {
|
||||
colorBtn.addEventListener('click', showChatColorModal);
|
||||
}
|
||||
|
||||
const colorOptions = document.querySelectorAll('.color-option');
|
||||
colorOptions.forEach(option => {
|
||||
option.addEventListener('click', () => {
|
||||
document.querySelectorAll('.color-option').forEach(o => o.classList.remove('selected'));
|
||||
option.classList.add('selected');
|
||||
updateColorPreview();
|
||||
});
|
||||
});
|
||||
|
||||
const customColor = document.getElementById('customColor');
|
||||
if (customColor) {
|
||||
customColor.addEventListener('input', () => {
|
||||
document.querySelectorAll('.color-option').forEach(o => o.classList.remove('selected'));
|
||||
updateColorPreview();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function showChatColorModal() {
|
||||
const modal = document.getElementById('chatColorModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'flex';
|
||||
updateColorPreview();
|
||||
}
|
||||
}
|
||||
|
||||
window.closeChatColorModal = function() {
|
||||
const modal = document.getElementById('chatColorModal');
|
||||
if (modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
function updateColorPreview() {
|
||||
const preview = document.getElementById('colorPreview');
|
||||
if (!preview) return;
|
||||
|
||||
const selectedOption = document.querySelector('.color-option.selected');
|
||||
let color = '#3498db';
|
||||
|
||||
if (selectedOption) {
|
||||
color = selectedOption.dataset.color;
|
||||
} else {
|
||||
const customColor = document.getElementById('customColor');
|
||||
if (customColor) color = customColor.value;
|
||||
}
|
||||
|
||||
preview.style.color = color;
|
||||
preview.style.background = 'transparent';
|
||||
preview.style.webkitBackgroundClip = 'initial';
|
||||
preview.style.webkitTextFillColor = 'initial';
|
||||
}
|
||||
|
||||
window.applyChatColor = async function() {
|
||||
let newColor;
|
||||
|
||||
const selectedOption = document.querySelector('.color-option.selected');
|
||||
if (selectedOption) {
|
||||
newColor = selectedOption.dataset.color;
|
||||
} else {
|
||||
const customColor = document.getElementById('customColor');
|
||||
newColor = customColor ? customColor.value : '#3498db';
|
||||
}
|
||||
|
||||
userColor = newColor;
|
||||
|
||||
if (window.electronAPI?.saveChatColor) {
|
||||
await window.electronAPI.saveChatColor(newColor);
|
||||
}
|
||||
|
||||
if (socket && isAuthenticated) {
|
||||
const uuid = await window.electronAPI?.getCurrentUuid();
|
||||
socket.emit('authenticate', {
|
||||
username: chatUsername,
|
||||
userId: await getOrCreatePlayerId(),
|
||||
uuid: uuid,
|
||||
userColor: userColor
|
||||
});
|
||||
|
||||
addSystemMessage('Username color updated successfully', 'success');
|
||||
}
|
||||
|
||||
closeChatColorModal();
|
||||
}
|
||||
|
||||
function applyUserColorStyle(element, color) {
|
||||
element.style.color = color;
|
||||
element.style.background = 'transparent';
|
||||
element.style.webkitBackgroundClip = 'initial';
|
||||
element.style.webkitTextFillColor = 'initial';
|
||||
}
|
||||
|
||||
window.ChatAPI = {
|
||||
send: sendMessage,
|
||||
disconnect: () => socket?.disconnect()
|
||||
};
|
||||
176
GUI/js/featured.js
Normal file
176
GUI/js/featured.js
Normal file
@@ -0,0 +1,176 @@
|
||||
// Featured Servers Management
|
||||
const FEATURED_SERVERS_API = 'https://assets.authbp.xyz/featured.json';
|
||||
|
||||
/**
|
||||
* Safely escape HTML while preserving UTF-8 characters
|
||||
*/
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and display featured servers
|
||||
*/
|
||||
async function loadFeaturedServers() {
|
||||
const featuredContainer = document.getElementById('featuredServersList');
|
||||
const myServersContainer = document.getElementById('myServersList');
|
||||
|
||||
try {
|
||||
console.log('[FeaturedServers] Fetching from', FEATURED_SERVERS_API);
|
||||
|
||||
// Fetch featured servers from API (no cache)
|
||||
const response = await fetch(FEATURED_SERVERS_API, {
|
||||
cache: 'no-store',
|
||||
headers: {
|
||||
'Cache-Control': 'no-cache',
|
||||
'Pragma': 'no-cache',
|
||||
'Accept-Charset': 'utf-8'
|
||||
}
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const text = await response.text();
|
||||
const data = JSON.parse(text);
|
||||
const featuredServers = data.featuredServers || [];
|
||||
|
||||
console.log('[FeaturedServers] Loaded', featuredServers.length, 'featured servers');
|
||||
|
||||
// Render featured servers
|
||||
if (featuredServers.length === 0) {
|
||||
featuredContainer.innerHTML = `
|
||||
<div class="loading-spinner">
|
||||
<i class="fas fa-info-circle fa-2x"></i>
|
||||
<p>No featured servers</p>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
const featuredHTML = featuredServers.map((server, index) => {
|
||||
console.log(`[FeaturedServers] Building featured card ${index + 1}:`, server.Name);
|
||||
|
||||
const escapedName = escapeHtml(server.Name || 'Unknown Server');
|
||||
const escapedAddress = escapeHtml(server.Address || '');
|
||||
const bannerUrl = server.img_Banner || 'https://via.placeholder.com/400x240/1e293b/ffffff?text=Server+Banner';
|
||||
|
||||
return `
|
||||
<div class="featured-server-card">
|
||||
<img
|
||||
src="${bannerUrl}"
|
||||
alt="${escapedName}"
|
||||
class="featured-server-banner"
|
||||
onerror="this.src='https://via.placeholder.com/400x240/1e293b/ffffff?text=Server'"
|
||||
/>
|
||||
<div class="featured-server-content">
|
||||
<h3 class="featured-server-name">${escapedName}</h3>
|
||||
<div class="featured-server-address">
|
||||
<span class="server-address-text">${escapedAddress}</span>
|
||||
<button class="copy-address-btn" onclick="copyServerAddress('${escapedAddress}', this)">
|
||||
<i class="fas fa-copy"></i>
|
||||
<span>Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
featuredContainer.innerHTML = featuredHTML;
|
||||
}
|
||||
|
||||
// Show "Coming Soon" for my servers
|
||||
myServersContainer.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #94a3b8; font-size: 1.2rem;">
|
||||
<p>Coming Soon</p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
} catch (error) {
|
||||
console.error('[FeaturedServers] Error loading servers:', error);
|
||||
featuredContainer.innerHTML = `
|
||||
<div class="loading-spinner">
|
||||
<i class="fas fa-exclamation-triangle fa-2x" style="color: #ef4444;"></i>
|
||||
<p>Failed to load servers</p>
|
||||
<p style="font-size: 0.9rem; color: #64748b;">${error.message}</p>
|
||||
</div>
|
||||
`;
|
||||
myServersContainer.innerHTML = `
|
||||
<div style="display: flex; align-items: center; justify-content: center; height: 100%; color: #94a3b8; font-size: 1.2rem;">
|
||||
<p>Coming Soon</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy server address to clipboard
|
||||
*/
|
||||
async function copyServerAddress(address, button) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(address);
|
||||
|
||||
// Visual feedback
|
||||
const originalHTML = button.innerHTML;
|
||||
button.classList.add('copied');
|
||||
button.innerHTML = '<i class="fas fa-check"></i><span>Copied!</span>';
|
||||
|
||||
setTimeout(() => {
|
||||
button.classList.remove('copied');
|
||||
button.innerHTML = originalHTML;
|
||||
}, 2000);
|
||||
|
||||
console.log('[FeaturedServers] Copied address:', address);
|
||||
} catch (error) {
|
||||
console.error('[FeaturedServers] Failed to copy address:', error);
|
||||
|
||||
// Fallback for older browsers
|
||||
const textArea = document.createElement('textarea');
|
||||
textArea.value = address;
|
||||
textArea.style.position = 'fixed';
|
||||
textArea.style.left = '-999999px';
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
document.execCommand('copy');
|
||||
const originalHTML = button.innerHTML;
|
||||
button.classList.add('copied');
|
||||
button.innerHTML = '<i class="fas fa-check"></i><span>Copied!</span>';
|
||||
|
||||
setTimeout(() => {
|
||||
button.classList.remove('copied');
|
||||
button.innerHTML = originalHTML;
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
console.error('[FeaturedServers] Fallback copy also failed:', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
}
|
||||
|
||||
// Load featured servers when the featured page becomes visible
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
|
||||
const featuredPage = document.getElementById('featured-page');
|
||||
if (featuredPage && featuredPage.classList.contains('active')) {
|
||||
loadFeaturedServers();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const featuredPage = document.getElementById('featured-page');
|
||||
if (featuredPage) {
|
||||
observer.observe(featuredPage, { attributes: true });
|
||||
|
||||
// Load immediately if already visible
|
||||
if (featuredPage.classList.contains('active')) {
|
||||
loadFeaturedServers();
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -4,10 +4,15 @@ const i18n = (() => {
|
||||
let translations = {};
|
||||
const availableLanguages = [
|
||||
{ code: 'en', name: 'English' },
|
||||
{ code: 'es-ES', name: 'Español (España)' },
|
||||
{ code: 'de-DE', name: 'German (Germany)' },
|
||||
{ code: 'es-ES', name: 'Spanish (Spain)' },
|
||||
{ code: 'fr-FR', name: 'French (France)' },
|
||||
{ code: 'pl-PL', name: 'Polish (Poland)' },
|
||||
{ code: 'pt-BR', name: 'Portuguese (Brazil)' },
|
||||
{ code: 'ru-RU', name: 'Russian (Russia)' },
|
||||
{ code: 'sv-SE', name: 'Swedish (Sweden)' },
|
||||
{ code: 'tr-TR', name: 'Turkish (Turkey)' },
|
||||
{ code: 'pl-PL', name: 'Polish (Poland)' }
|
||||
{ code: 'id-ID', name: 'Indonesian (Indonesia)' }
|
||||
];
|
||||
|
||||
// Load single language file
|
||||
|
||||
@@ -45,9 +45,17 @@ export function setupInstallation() {
|
||||
export async function installGame() {
|
||||
if (isDownloading || (installBtn && installBtn.disabled)) return;
|
||||
|
||||
const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
let playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
const installPath = installPathInput ? installPathInput.value.trim() : '';
|
||||
|
||||
// Limit player name to 16 characters
|
||||
if (playerName.length > 16) {
|
||||
playerName = playerName.substring(0, 16);
|
||||
if (installPlayerName) {
|
||||
installPlayerName.value = playerName;
|
||||
}
|
||||
}
|
||||
|
||||
const selectedBranchRadio = document.querySelector('input[name="installBranch"]:checked');
|
||||
const selectedBranch = selectedBranchRadio ? selectedBranchRadio.value : 'release';
|
||||
|
||||
@@ -72,8 +80,11 @@ export async function installGame() {
|
||||
setTimeout(() => {
|
||||
window.LauncherUI.hideProgress();
|
||||
window.LauncherUI.showLauncherOrInstall(true);
|
||||
// Sync player name to both launcher and settings inputs
|
||||
const playerNameInput = document.getElementById('playerName');
|
||||
if (playerNameInput) playerNameInput.value = playerName;
|
||||
const settingsPlayerName = document.getElementById('settingsPlayerName');
|
||||
if (settingsPlayerName) settingsPlayerName.value = playerName;
|
||||
resetInstallButton();
|
||||
}, 2000);
|
||||
}
|
||||
@@ -125,8 +136,11 @@ function simulateInstallation(playerName) {
|
||||
setTimeout(() => {
|
||||
window.LauncherUI.hideProgress();
|
||||
window.LauncherUI.showLauncherOrInstall(true);
|
||||
// Sync player name to both launcher and settings inputs
|
||||
const playerNameInput = document.getElementById('playerName');
|
||||
if (playerNameInput) playerNameInput.value = playerName;
|
||||
const settingsPlayerName = document.getElementById('settingsPlayerName');
|
||||
if (settingsPlayerName) settingsPlayerName.value = playerName;
|
||||
resetInstallButton();
|
||||
}, 2000);
|
||||
}
|
||||
@@ -188,7 +202,16 @@ export async function browseInstallPath() {
|
||||
async function savePlayerName() {
|
||||
try {
|
||||
if (window.electronAPI && window.electronAPI.saveSettings) {
|
||||
const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
let playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player';
|
||||
|
||||
// Limit player name to 16 characters
|
||||
if (playerName.length > 16) {
|
||||
playerName = playerName.substring(0, 16);
|
||||
if (installPlayerName) {
|
||||
installPlayerName.value = playerName;
|
||||
}
|
||||
}
|
||||
|
||||
await window.electronAPI.saveSettings({ playerName });
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -246,9 +269,3 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
setupInstallation();
|
||||
await checkGameStatusAndShowInterface();
|
||||
});
|
||||
window.browseInstallPath = browseInstallPath;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
setupInstallation();
|
||||
await checkGameStatusAndShowInterface();
|
||||
});
|
||||
|
||||
@@ -4,23 +4,66 @@ import './launcher.js';
|
||||
import './news.js';
|
||||
import './mods.js';
|
||||
import './players.js';
|
||||
import './chat.js';
|
||||
import './settings.js';
|
||||
import './logs.js';
|
||||
|
||||
// Initialize i18n immediately (before DOMContentLoaded)
|
||||
let i18nInitialized = false;
|
||||
(async () => {
|
||||
const savedLang = await window.electronAPI?.loadLanguage();
|
||||
await i18n.init(savedLang);
|
||||
i18nInitialized = true;
|
||||
|
||||
// Update language selector if DOM is already loaded
|
||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||
updateLanguageSelector();
|
||||
}
|
||||
})();
|
||||
|
||||
async function checkDiscordPopup() {
|
||||
try {
|
||||
const config = await window.electronAPI?.loadConfig();
|
||||
if (!config || config.discordPopup === undefined || config.discordPopup === false) {
|
||||
const modal = document.getElementById('discordPopupModal');
|
||||
if (modal) {
|
||||
const buttons = modal.querySelectorAll('.discord-popup-btn');
|
||||
buttons.forEach(btn => btn.disabled = true);
|
||||
|
||||
setTimeout(() => {
|
||||
modal.style.display = 'flex';
|
||||
modal.classList.add('active');
|
||||
|
||||
setTimeout(() => {
|
||||
buttons.forEach(btn => btn.disabled = false);
|
||||
}, 2000);
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to check Discord popup:', error);
|
||||
}
|
||||
}
|
||||
|
||||
window.closeDiscordPopup = function() {
|
||||
const modal = document.getElementById('discordPopupModal');
|
||||
if (modal) {
|
||||
modal.classList.remove('active');
|
||||
setTimeout(() => {
|
||||
modal.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
};
|
||||
|
||||
window.joinDiscord = async function() {
|
||||
await window.electronAPI?.openExternal('https://discord.gg/hf2pdc');
|
||||
|
||||
try {
|
||||
await window.electronAPI?.saveConfig({ discordPopup: true });
|
||||
} catch (error) {
|
||||
console.error('Failed to save Discord popup state:', error);
|
||||
}
|
||||
|
||||
closeDiscordPopup();
|
||||
};
|
||||
|
||||
function updateLanguageSelector() {
|
||||
const langSelect = document.getElementById('languageSelect');
|
||||
if (langSelect) {
|
||||
@@ -51,32 +94,9 @@ function updateLanguageSelector() {
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Populate language selector (wait for i18n if needed)
|
||||
if (i18nInitialized) {
|
||||
updateLanguageSelector();
|
||||
}
|
||||
|
||||
// Discord notification
|
||||
const notification = document.getElementById('discordNotification');
|
||||
if (notification) {
|
||||
const dismissed = localStorage.getItem('discordNotificationDismissed');
|
||||
if (!dismissed) {
|
||||
setTimeout(() => {
|
||||
notification.style.display = 'flex';
|
||||
}, 3000);
|
||||
} else {
|
||||
notification.style.display = 'none';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
window.closeDiscordNotification = function() {
|
||||
const notification = document.getElementById('discordNotification');
|
||||
if (notification) {
|
||||
notification.classList.add('hidden');
|
||||
setTimeout(() => {
|
||||
notification.style.display = 'none';
|
||||
}, 300);
|
||||
}
|
||||
localStorage.setItem('discordNotificationDismissed', 'true');
|
||||
};
|
||||
checkDiscordPopup();
|
||||
});
|
||||
@@ -439,6 +439,13 @@ async function savePlayerName() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (playerName.length > 16) {
|
||||
const msg = window.i18n ? window.i18n.t('notifications.playerNameTooLong') : 'Player name must be 16 characters or less';
|
||||
showNotification(msg, 'error');
|
||||
settingsPlayerName.value = playerName.substring(0, 16);
|
||||
return;
|
||||
}
|
||||
|
||||
await window.electronAPI.saveUsername(playerName);
|
||||
const successMsg = window.i18n ? window.i18n.t('notifications.playerNameSaved') : 'Player name saved successfully';
|
||||
showNotification(successMsg, 'success');
|
||||
|
||||
17
GUI/js/ui.js
17
GUI/js/ui.js
@@ -63,8 +63,10 @@ function handleNavigation() {
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
const page = item.getAttribute('data-page');
|
||||
showPage(`${page}-page`);
|
||||
setActiveNav(page);
|
||||
if (page) {
|
||||
showPage(`${page}-page`);
|
||||
setActiveNav(page);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -843,7 +845,7 @@ function getErrorMessage(technicalMessage, errorType) {
|
||||
case 'stall':
|
||||
return 'Download stalled due to slow connection. Please retry.';
|
||||
case 'file':
|
||||
return 'Unable to save file. Check disk space and permissions. Please retry.';
|
||||
return 'Unable to save file. Check permissions. Please retry.';
|
||||
case 'permission':
|
||||
return 'Permission denied. Check if launcher has write access. Please retry.';
|
||||
case 'server':
|
||||
@@ -972,7 +974,7 @@ function setupRetryButton() {
|
||||
if (!currentDownloadState.retryData || currentDownloadState.errorType === 'jre') {
|
||||
currentDownloadState.retryData = {
|
||||
branch: 'release',
|
||||
fileName: '4.pwr'
|
||||
fileName: '7.pwr'
|
||||
};
|
||||
console.log('[UI] Created default PWR retry data:', currentDownloadState.retryData);
|
||||
}
|
||||
@@ -1040,7 +1042,7 @@ function setupRetryButton() {
|
||||
} else {
|
||||
currentDownloadState.retryData = {
|
||||
branch: 'release',
|
||||
fileName: '4.pwr'
|
||||
fileName: '7.pwr'
|
||||
};
|
||||
}
|
||||
console.log('[UI] Created default retry data:', currentDownloadState.retryData);
|
||||
@@ -1100,7 +1102,10 @@ function getRetryContextMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
// Make toggleMaximize globally available
|
||||
window.openDiscordExternal = function() {
|
||||
window.electronAPI?.openExternal('https://discord.gg/hf2pdc');
|
||||
};
|
||||
|
||||
window.toggleMaximize = toggleMaximize;
|
||||
|
||||
document.addEventListener('DOMContentLoaded', setupUI);
|
||||
|
||||
216
GUI/js/update.js
216
GUI/js/update.js
@@ -6,12 +6,12 @@ class ClientUpdateManager {
|
||||
}
|
||||
|
||||
init() {
|
||||
window.electronAPI.onUpdatePopup((updateInfo) => {
|
||||
this.showUpdatePopup(updateInfo);
|
||||
});
|
||||
console.log('🔧 ClientUpdateManager initializing...');
|
||||
|
||||
// Listen for electron-updater events
|
||||
// Listen for electron-updater events from main.js
|
||||
// This is the primary update trigger - main.js checks for updates on startup
|
||||
window.electronAPI.onUpdateAvailable((updateInfo) => {
|
||||
console.log('📥 update-available event received:', updateInfo);
|
||||
this.showUpdatePopup(updateInfo);
|
||||
});
|
||||
|
||||
@@ -20,18 +20,30 @@ class ClientUpdateManager {
|
||||
});
|
||||
|
||||
window.electronAPI.onUpdateDownloaded((updateInfo) => {
|
||||
console.log('📦 update-downloaded event received:', updateInfo);
|
||||
this.showUpdateDownloaded(updateInfo);
|
||||
});
|
||||
|
||||
window.electronAPI.onUpdateError((errorInfo) => {
|
||||
console.log('❌ update-error event received:', errorInfo);
|
||||
this.handleUpdateError(errorInfo);
|
||||
});
|
||||
|
||||
this.checkForUpdatesOnDemand();
|
||||
console.log('✅ ClientUpdateManager initialized');
|
||||
|
||||
// Note: Don't call checkForUpdatesOnDemand() here - main.js already checks
|
||||
// for updates after 3 seconds and sends 'update-available' event.
|
||||
// Calling it here would cause duplicate popups.
|
||||
}
|
||||
|
||||
showUpdatePopup(updateInfo) {
|
||||
if (this.updatePopupVisible) return;
|
||||
console.log('🔔 showUpdatePopup called, updatePopupVisible:', this.updatePopupVisible);
|
||||
|
||||
// Check if popup already exists in DOM (extra safety)
|
||||
if (this.updatePopupVisible || document.getElementById('update-popup-overlay')) {
|
||||
console.log('⚠️ Update popup already visible, skipping');
|
||||
return;
|
||||
}
|
||||
|
||||
this.updatePopupVisible = true;
|
||||
|
||||
@@ -92,7 +104,10 @@ class ClientUpdateManager {
|
||||
</div>
|
||||
|
||||
<div class="update-popup-footer">
|
||||
This popup cannot be closed until you update the launcher
|
||||
<span id="update-footer-text">Downloading update...</span>
|
||||
<button id="update-skip-btn" class="update-skip-btn" style="display: none; margin-top: 0.5rem; background: transparent; border: 1px solid rgba(255,255,255,0.2); color: #9ca3af; padding: 0.5rem 1rem; border-radius: 0.25rem; cursor: pointer; font-size: 0.75rem;">
|
||||
Skip for now (not recommended)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -113,16 +128,43 @@ class ClientUpdateManager {
|
||||
installBtn.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
||||
installBtn.disabled = true;
|
||||
installBtn.innerHTML = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Installing...';
|
||||
|
||||
|
||||
try {
|
||||
await window.electronAPI.quitAndInstallUpdate();
|
||||
|
||||
// If we're still here after 5 seconds, the install probably failed
|
||||
setTimeout(() => {
|
||||
console.log('⚠️ Install may have failed - showing skip option');
|
||||
installBtn.disabled = false;
|
||||
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Try Again';
|
||||
|
||||
// Show skip button
|
||||
const skipBtn = document.getElementById('update-skip-btn');
|
||||
const footerText = document.getElementById('update-footer-text');
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'inline-block';
|
||||
if (footerText) {
|
||||
footerText.textContent = 'Install not working? Skip for now:';
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
} catch (error) {
|
||||
console.error('❌ Error installing update:', error);
|
||||
installBtn.disabled = false;
|
||||
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Install & Restart';
|
||||
|
||||
// Show skip button on error
|
||||
const skipBtn = document.getElementById('update-skip-btn');
|
||||
const footerText = document.getElementById('update-footer-text');
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'inline-block';
|
||||
if (footerText) {
|
||||
footerText.textContent = 'Install failed. Skip for now:';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -138,10 +180,15 @@ class ClientUpdateManager {
|
||||
|
||||
try {
|
||||
await window.electronAPI.openDownloadPage();
|
||||
console.log('✅ Download page opened, launcher will close...');
|
||||
|
||||
downloadBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Launcher closing...';
|
||||
|
||||
console.log('✅ Download page opened');
|
||||
|
||||
downloadBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Opened in browser';
|
||||
|
||||
// Close the popup after opening download page
|
||||
setTimeout(() => {
|
||||
this.closeUpdatePopup();
|
||||
}, 1500);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error opening download page:', error);
|
||||
downloadBtn.disabled = false;
|
||||
@@ -161,9 +208,39 @@ class ClientUpdateManager {
|
||||
});
|
||||
}
|
||||
|
||||
// Show skip button after 30 seconds as fallback (in case update is stuck)
|
||||
setTimeout(() => {
|
||||
const skipBtn = document.getElementById('update-skip-btn');
|
||||
const footerText = document.getElementById('update-footer-text');
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'inline-block';
|
||||
if (footerText) {
|
||||
footerText.textContent = 'Update taking too long?';
|
||||
}
|
||||
}
|
||||
}, 30000);
|
||||
|
||||
const skipBtn = document.getElementById('update-skip-btn');
|
||||
if (skipBtn) {
|
||||
skipBtn.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.closeUpdatePopup();
|
||||
});
|
||||
}
|
||||
|
||||
console.log('🔔 Update popup displayed with new style');
|
||||
}
|
||||
|
||||
closeUpdatePopup() {
|
||||
const overlay = document.getElementById('update-popup-overlay');
|
||||
if (overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
this.updatePopupVisible = false;
|
||||
this.unblockInterface();
|
||||
}
|
||||
|
||||
updateDownloadProgress(progress) {
|
||||
const progressBar = document.getElementById('update-progress-bar');
|
||||
const progressPercent = document.getElementById('update-progress-percent');
|
||||
@@ -197,35 +274,96 @@ class ClientUpdateManager {
|
||||
const statusText = document.getElementById('update-status-text');
|
||||
const progressContainer = document.getElementById('update-progress-container');
|
||||
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');
|
||||
|
||||
if (statusText) {
|
||||
statusText.textContent = 'Update downloaded! Ready to install.';
|
||||
// Remove breathing/pulse animation when download is complete
|
||||
if (popupContainer) {
|
||||
popupContainer.classList.remove('update-popup-pulse');
|
||||
}
|
||||
|
||||
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 (buttonsContainer) {
|
||||
buttonsContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
console.log('✅ Update downloaded, ready to install');
|
||||
// Always show skip button in downloaded state
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'inline-block';
|
||||
console.log('✅ Skip button made visible');
|
||||
} else {
|
||||
console.error('❌ Skip button not found in DOM!');
|
||||
}
|
||||
|
||||
console.log('✅ Update downloaded, ready to install. autoInstallSupported:', autoInstallSupported);
|
||||
}
|
||||
|
||||
handleUpdateError(errorInfo) {
|
||||
console.error('Update error:', errorInfo);
|
||||
|
||||
|
||||
// Show skip button immediately on any error
|
||||
const skipBtn = document.getElementById('update-skip-btn');
|
||||
const footerText = document.getElementById('update-footer-text');
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'inline-block';
|
||||
if (footerText) {
|
||||
footerText.textContent = 'Update failed. You can skip for now.';
|
||||
}
|
||||
}
|
||||
|
||||
// If manual download is required, update the UI (this will handle status text)
|
||||
if (errorInfo.requiresManualDownload) {
|
||||
this.showManualDownloadRequired(errorInfo);
|
||||
return; // Don't do anything else, showManualDownloadRequired handles everything
|
||||
}
|
||||
|
||||
|
||||
// For non-critical errors, just show error message without changing status
|
||||
const errorMessage = document.getElementById('update-error-message');
|
||||
const errorText = document.getElementById('update-error-text');
|
||||
|
||||
|
||||
if (errorMessage && errorText) {
|
||||
let message = errorInfo.message || 'An error occurred during the update process.';
|
||||
if (errorInfo.isMacSigningError) {
|
||||
@@ -289,6 +427,16 @@ class ClientUpdateManager {
|
||||
buttonsContainer.style.display = 'block';
|
||||
}
|
||||
|
||||
// Show skip button for manual download errors
|
||||
const skipBtn = document.getElementById('update-skip-btn');
|
||||
const footerText = document.getElementById('update-footer-text');
|
||||
if (skipBtn) {
|
||||
skipBtn.style.display = 'inline-block';
|
||||
if (footerText) {
|
||||
footerText.textContent = 'Or continue without updating:';
|
||||
}
|
||||
}
|
||||
|
||||
console.log('⚠️ Manual download required due to update error');
|
||||
}
|
||||
|
||||
@@ -300,13 +448,35 @@ class ClientUpdateManager {
|
||||
|
||||
document.body.classList.add('no-select');
|
||||
|
||||
document.addEventListener('keydown', this.blockKeyEvents.bind(this), true);
|
||||
|
||||
document.addEventListener('contextmenu', this.blockContextMenu.bind(this), true);
|
||||
|
||||
// Store bound functions so we can remove them later
|
||||
this._boundBlockKeyEvents = this.blockKeyEvents.bind(this);
|
||||
this._boundBlockContextMenu = this.blockContextMenu.bind(this);
|
||||
|
||||
document.addEventListener('keydown', this._boundBlockKeyEvents, true);
|
||||
document.addEventListener('contextmenu', this._boundBlockContextMenu, true);
|
||||
|
||||
console.log('🚫 Interface blocked for update');
|
||||
}
|
||||
|
||||
unblockInterface() {
|
||||
const mainContent = document.querySelector('.flex.w-full.h-screen');
|
||||
if (mainContent) {
|
||||
mainContent.classList.remove('interface-blocked');
|
||||
}
|
||||
|
||||
document.body.classList.remove('no-select');
|
||||
|
||||
// Remove event listeners
|
||||
if (this._boundBlockKeyEvents) {
|
||||
document.removeEventListener('keydown', this._boundBlockKeyEvents, true);
|
||||
}
|
||||
if (this._boundBlockContextMenu) {
|
||||
document.removeEventListener('contextmenu', this._boundBlockContextMenu, true);
|
||||
}
|
||||
|
||||
console.log('✅ Interface unblocked');
|
||||
}
|
||||
|
||||
blockKeyEvents(event) {
|
||||
if (event.target.closest('#update-popup-overlay')) {
|
||||
if ((event.key === 'Enter' || event.key === ' ') &&
|
||||
|
||||
Reference in New Issue
Block a user