From 3ebe727f58c61e1ae372baf8a0a5f8b52c51fea7 Mon Sep 17 00:00:00 2001 From: AMIAY Date: Sun, 18 Jan 2026 01:52:24 +0100 Subject: [PATCH] Add files via upload --- GUI/js/chat.js | 358 ++++++++++++++++++++++ GUI/js/install.js | 194 ++++++++++++ GUI/js/launcher.js | 235 +++++++++++++++ GUI/js/mods.js | 724 +++++++++++++++++++++++++++++++++++++++++++++ GUI/js/news.js | 124 ++++++++ GUI/js/players.js | 154 ++++++++++ GUI/js/settings.js | 143 +++++++++ GUI/js/ui.js | 469 +++++++++++++++++++++++++++++ GUI/js/update.js | 162 ++++++++++ 9 files changed, 2563 insertions(+) create mode 100644 GUI/js/chat.js create mode 100644 GUI/js/install.js create mode 100644 GUI/js/launcher.js create mode 100644 GUI/js/mods.js create mode 100644 GUI/js/news.js create mode 100644 GUI/js/players.js create mode 100644 GUI/js/settings.js create mode 100644 GUI/js/ui.js create mode 100644 GUI/js/update.js diff --git a/GUI/js/chat.js b/GUI/js/chat.js new file mode 100644 index 0000000..90bd1d8 --- /dev/null +++ b/GUI/js/chat.js @@ -0,0 +1,358 @@ + +let socket = null; +let isAuthenticated = false; +let messageQueue = []; +let chatUsername = ''; +const SOCKET_URL = 'http://3.10.208.30:3001'; +const MAX_MESSAGE_LENGTH = 500; + +export async function initChat() { + if (window.electronAPI?.loadChatUsername) { + chatUsername = await window.electronAPI.loadChatUsername(); + } + + if (!chatUsername || chatUsername.trim() === '') { + showUsernameModal(); + return; + } + + setupChatUI(); + 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', () => { + console.log('Connected to chat server'); + socket.emit('authenticate', { username: chatUsername, userId }); + }); + + socket.on('authenticated', (data) => { + isAuthenticated = true; + 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); + } + }); + + 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) { + 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' + }); + + messageDiv.innerHTML = ` +
+ ${escapeHtml(username)} + ${time} +
+
${message}
+ `; + + 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 = ` +
+ ${escapeHtml(message)} +
+ `; + + 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(); + } + }); + } +}); + +window.ChatAPI = { + send: sendMessage, + disconnect: () => socket?.disconnect() +}; diff --git a/GUI/js/install.js b/GUI/js/install.js new file mode 100644 index 0000000..0fa5a78 --- /dev/null +++ b/GUI/js/install.js @@ -0,0 +1,194 @@ +let isDownloading = false; + +let installPage; +let installBtn; +let installText; +let installPlayerName; +let installCustomCheck; +let installCustomOptions; +let installPathInput; + +export function setupInstallation() { + installPage = document.getElementById('install-page'); + installBtn = document.getElementById('installBtn'); + installText = document.getElementById('installText'); + installPlayerName = document.getElementById('installPlayerName'); + installCustomCheck = document.getElementById('installCustomCheck'); + installCustomOptions = document.getElementById('installCustomOptions'); + installPathInput = document.getElementById('installPath'); + + if (installCustomCheck && installCustomOptions) { + installCustomCheck.addEventListener('change', (e) => { + if (e.target.checked) { + installCustomOptions.classList.add('show'); + } else { + installCustomOptions.classList.remove('show'); + } + }); + } + + if (installPlayerName) { + installPlayerName.addEventListener('change', savePlayerName); + } +} + +export async function installGame() { + if (isDownloading || (installBtn && installBtn.disabled)) return; + + const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player'; + const installPath = installPathInput ? installPathInput.value.trim() : ''; + + if (window.LauncherUI) window.LauncherUI.showProgress(); + isDownloading = true; + if (installBtn) { + installBtn.disabled = true; + installText.textContent = 'INSTALLING...'; + } + + try { + if (window.electronAPI && window.electronAPI.installGame) { + const result = await window.electronAPI.installGame(playerName, '', installPath); + + if (result.success) { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + window.LauncherUI.showLauncherOrInstall(true); + const playerNameInput = document.getElementById('playerName'); + if (playerNameInput) playerNameInput.value = playerName; + }, 2000); + } + } else { + throw new Error(result.error || 'Installation failed'); + } + } else { + simulateInstallation(playerName); + } + } catch (error) { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: `Installation failed: ${error.message}` }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + resetInstallButton(); + }, 3000); + } + } +} + +function simulateInstallation(playerName) { + let progress = 0; + const interval = setInterval(() => { + progress += Math.random() * 3; + if (progress > 100) progress = 100; + + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ + percent: progress, + message: progress < 100 ? 'Installing game files...' : 'Installation complete!', + speed: 1024 * 1024 * (5 + Math.random() * 10), + downloaded: progress * 1024 * 1024 * 20, + total: 1024 * 1024 * 2000 + }); + } + + if (progress >= 100) { + clearInterval(interval); + setTimeout(() => { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: 'Installation completed successfully!' }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + window.LauncherUI.showLauncherOrInstall(true); + const playerNameInput = document.getElementById('playerName'); + if (playerNameInput) playerNameInput.value = playerName; + resetInstallButton(); + }, 2000); + } + }, 1000); + } + }, 200); +} + +function resetInstallButton() { + isDownloading = false; + if (installBtn) { + installBtn.disabled = false; + installText.textContent = 'INSTALL HYTALE'; + } +} + +export async function browseInstallPath() { + try { + if (window.electronAPI && window.electronAPI.selectInstallPath) { + const result = await window.electronAPI.selectInstallPath(); + if (result && installPathInput) { + installPathInput.value = result; + } + } + } catch (error) { + console.error('Error browsing install path:', error); + } +} + +async function savePlayerName() { + try { + if (window.electronAPI && window.electronAPI.saveSettings) { + const playerName = (installPlayerName ? installPlayerName.value.trim() : '') || 'Player'; + await window.electronAPI.saveSettings({ playerName }); + } + } catch (error) { + console.error('Error saving player name:', error); + } +} + +export async function checkGameStatusAndShowInterface() { + try { + if (window.electronAPI && window.electronAPI.isGameInstalled) { + const installed = await window.electronAPI.isGameInstalled(); + if (window.LauncherUI) { + window.LauncherUI.showLauncherOrInstall(installed); + } + if (installed) { + await loadPlayerSettings(); + } + } else { + if (window.LauncherUI) { + window.LauncherUI.showLauncherOrInstall(false); + } + } + } catch (error) { + console.error('Error checking game status:', error); + if (window.LauncherUI) { + window.LauncherUI.showLauncherOrInstall(false); + } + } +} + +async function loadPlayerSettings() { + try { + if (window.electronAPI && window.electronAPI.loadSettings) { + const settings = await window.electronAPI.loadSettings(); + if (settings) { + const playerNameInput = document.getElementById('playerName'); + const javaPathInput = document.getElementById('javaPath'); + if (settings.playerName && playerNameInput) { + playerNameInput.value = settings.playerName; + } + if (settings.javaPath && javaPathInput) { + javaPathInput.value = settings.javaPath; + } + } + } + } catch (error) { + console.error('Error loading settings:', error); + } +} + +window.installGame = installGame; +window.browseInstallPath = browseInstallPath; + +document.addEventListener('DOMContentLoaded', async () => { + setupInstallation(); + await checkGameStatusAndShowInterface(); +}); diff --git a/GUI/js/launcher.js b/GUI/js/launcher.js new file mode 100644 index 0000000..977f4ea --- /dev/null +++ b/GUI/js/launcher.js @@ -0,0 +1,235 @@ +let isDownloading = false; + +let playBtn; +let playText; +let homePlayBtn; +let uninstallBtn; +let playerNameInput; +let javaPathInput; + +export function setupLauncher() { + playBtn = document.getElementById('playBtn'); + playText = document.getElementById('playText'); + homePlayBtn = document.getElementById('homePlayBtn'); + uninstallBtn = document.getElementById('uninstallBtn'); + playerNameInput = document.getElementById('playerName'); + javaPathInput = document.getElementById('javaPath'); + + if (playerNameInput) { + playerNameInput.addEventListener('change', savePlayerName); + } + + if (javaPathInput) { + javaPathInput.addEventListener('change', saveJavaPath); + } + + if (window.electronAPI && window.electronAPI.onProgressUpdate) { + window.electronAPI.onProgressUpdate((data) => { + if (window.LauncherUI) { + window.LauncherUI.showProgress(); + window.LauncherUI.updateProgress(data); + } + }); + } +} + +export async function launch() { + if (isDownloading || (playBtn && playBtn.disabled)) return; + + let playerName = 'Player'; + if (window.SettingsAPI && window.SettingsAPI.getCurrentPlayerName) { + playerName = window.SettingsAPI.getCurrentPlayerName(); + } else if (playerNameInput && playerNameInput.value.trim()) { + playerName = playerNameInput.value.trim(); + } + + let javaPath = ''; + if (window.SettingsAPI && window.SettingsAPI.getCurrentJavaPath) { + javaPath = window.SettingsAPI.getCurrentJavaPath(); + } + + if (window.LauncherUI) window.LauncherUI.showProgress(); + isDownloading = true; + if (playBtn) { + playBtn.disabled = true; + playText.textContent = 'LAUNCHING...'; + } + + try { + if (window.electronAPI && window.electronAPI.launchGame) { + const result = await window.electronAPI.launchGame(playerName, javaPath, ''); + + if (result.success) { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: 'Game started successfully!' }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + if (window.electronAPI.minimizeWindow) { + window.electronAPI.minimizeWindow(); + } + }, 2000); + } + } else { + throw new Error(result.error || 'Launch failed'); + } + } else { + setTimeout(() => { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: 'Game started successfully!' }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + resetPlayButton(); + }, 2000); + } + }, 2000); + } + } catch (error) { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: `Failed: ${error.message}` }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + resetPlayButton(); + }, 3000); + } + } +} + +export async function uninstallGame() { + if (!confirm('Are you sure you want to uninstall Hytale? All game files will be deleted.')) { + return; + } + + if (window.LauncherUI) window.LauncherUI.showProgress(); + if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Uninstalling game...' }); + if (uninstallBtn) uninstallBtn.disabled = true; + + try { + if (window.electronAPI && window.electronAPI.uninstallGame) { + const result = await window.electronAPI.uninstallGame(); + + if (result.success) { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + window.LauncherUI.showLauncherOrInstall(false); + }, 2000); + } + } else { + throw new Error(result.error || 'Uninstall failed'); + } + } else { + setTimeout(() => { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: 'Game uninstalled successfully!' }); + setTimeout(() => { + window.LauncherUI.hideProgress(); + window.LauncherUI.showLauncherOrInstall(false); + }, 2000); + } + }, 2000); + } + } catch (error) { + if (window.LauncherUI) { + window.LauncherUI.updateProgress({ message: `Uninstall failed: ${error.message}` }); + setTimeout(() => window.LauncherUI.hideProgress(), 3000); + } + } finally { + if (uninstallBtn) uninstallBtn.disabled = false; + } +} + +function resetPlayButton() { + isDownloading = false; + if (playBtn) { + playBtn.disabled = false; + playText.textContent = 'PLAY'; + } +} + +async function savePlayerName() { + try { + if (window.electronAPI && window.electronAPI.saveSettings) { + const playerName = (playerNameInput ? playerNameInput.value.trim() : '') || 'Player'; + await window.electronAPI.saveSettings({ playerName }); + } + } catch (error) { + console.error('Error saving player name:', error); + } +} + +async function saveJavaPath() { + try { + if (window.electronAPI && window.electronAPI.saveSettings) { + const javaPath = (javaPathInput ? javaPathInput.value.trim() : '') || ''; + await window.electronAPI.saveSettings({ javaPath }); + } + } catch (error) { + console.error('Error saving Java path:', error); + } +} + +function toggleCustomJava() { + if (!customJavaOptions) return; + + if (customJavaCheck && customJavaCheck.checked) { + customJavaOptions.style.display = 'block'; + } else { + customJavaOptions.style.display = 'none'; + if (customJavaPath) customJavaPath.value = ''; + saveCustomJavaPath(''); + } +} + +async function browseJavaPath() { + try { + if (window.electronAPI && window.electronAPI.browseJavaPath) { + const result = await window.electronAPI.browseJavaPath(); + if (result && result.filePaths && result.filePaths.length > 0) { + const selectedPath = result.filePaths[0]; + if (customJavaPath) { + customJavaPath.value = selectedPath; + } + await saveCustomJavaPath(selectedPath); + } + } + } catch (error) { + console.error('Error browsing Java path:', error); + } +} + +async function saveCustomJavaPath(path) { + try { + if (window.electronAPI && window.electronAPI.saveJavaPath) { + await window.electronAPI.saveJavaPath(path); + } + } catch (error) { + console.error('Error saving custom Java path:', error); + } +} + +async function loadCustomJavaPath() { + try { + if (window.electronAPI && window.electronAPI.loadJavaPath) { + const savedPath = await window.electronAPI.loadJavaPath(); + if (savedPath && savedPath.trim()) { + if (customJavaPath) { + customJavaPath.value = savedPath; + } + if (customJavaCheck) { + customJavaCheck.checked = true; + } + if (customJavaOptions) { + customJavaOptions.style.display = 'block'; + } + } + } + } catch (error) { + console.error('Error loading custom Java path:', error); + } +} + +window.launch = launch; +window.uninstallGame = uninstallGame; + +document.addEventListener('DOMContentLoaded', setupLauncher); diff --git a/GUI/js/mods.js b/GUI/js/mods.js new file mode 100644 index 0000000..9f5d540 --- /dev/null +++ b/GUI/js/mods.js @@ -0,0 +1,724 @@ + +const API_KEY = '$2a$10$bqk254NMZOWVTzLVJCcxEOmhcyUujKxA5xk.kQCN9q0KNYFJd5b32'; +const CURSEFORGE_API = 'https://api.curseforge.com/v1'; +const HYTALE_GAME_ID = 70216; + +let installedMods = []; +let browseMods = []; +let searchQuery = ''; +let modsPage = 0; +let modsPageSize = 20; +let modsTotalPages = 1; + +export async function initModsManager() { + setupModsEventListeners(); + await loadInstalledMods(); + await loadBrowseMods(); +} + +function setupModsEventListeners() { + const searchInput = document.getElementById('modsSearch'); + if (searchInput) { + let searchTimeout; + searchInput.addEventListener('input', (e) => { + searchQuery = e.target.value.toLowerCase().trim(); + + clearTimeout(searchTimeout); + searchTimeout = setTimeout(() => { + modsPage = 0; + loadBrowseMods(); + }, 500); + }); + } + + const myModsBtn = document.getElementById('myModsBtn'); + if (myModsBtn) { + myModsBtn.addEventListener('click', openMyModsModal); + } + + const closeModalBtn = document.getElementById('closeMyModsModal'); + if (closeModalBtn) { + closeModalBtn.addEventListener('click', closeMyModsModal); + } + + const modal = document.getElementById('myModsModal'); + if (modal) { + modal.addEventListener('click', (e) => { + if (e.target === modal) { + closeMyModsModal(); + } + }); + } + + const prevPageBtn = document.getElementById('prevPage'); + const nextPageBtn = document.getElementById('nextPage'); + + if (prevPageBtn) { + prevPageBtn.addEventListener('click', () => { + if (modsPage > 0) { + modsPage--; + loadBrowseMods(); + } + }); + } + + if (nextPageBtn) { + nextPageBtn.addEventListener('click', () => { + if (modsPage < modsTotalPages - 1) { + modsPage++; + loadBrowseMods(); + } + }); + } +} + +function openMyModsModal() { + const modal = document.getElementById('myModsModal'); + if (modal) { + modal.classList.add('active'); + loadInstalledMods(); + } +} + +function closeMyModsModal() { + const modal = document.getElementById('myModsModal'); + if (modal) { + modal.classList.remove('active'); + } +} + +async function loadInstalledMods() { + try { + const modsPath = await window.electronAPI?.getModsPath(); + if (!modsPath) { + showInstalledModsError('Could not get mods directory'); + return; + } + + const mods = await window.electronAPI?.loadInstalledMods(modsPath); + installedMods = mods || []; + + displayInstalledMods(installedMods); + } catch (error) { + console.error('Error loading installed mods:', error); + showInstalledModsError('Failed to load installed mods'); + } +} + +function displayInstalledMods(mods) { + const modsContainer = document.getElementById('installedModsList'); + if (!modsContainer) return; + + if (mods.length === 0) { + modsContainer.innerHTML = ` +
+ +

No Mods Installed

+

Add mods from CurseForge or import local files

+
+ `; + return; + } + + modsContainer.innerHTML = mods.map(mod => createInstalledModCard(mod)).join(''); + + mods.forEach(mod => { + const toggleBtn = document.getElementById(`toggle-installed-${mod.id}`); + const deleteBtn = document.getElementById(`delete-installed-${mod.id}`); + + if (toggleBtn) { + toggleBtn.addEventListener('click', () => toggleMod(mod.id)); + } + + if (deleteBtn) { + deleteBtn.addEventListener('click', () => deleteMod(mod.id)); + } + }); +} + +function createInstalledModCard(mod) { + const statusClass = mod.enabled ? 'text-primary' : 'text-zinc-500'; + const statusText = mod.enabled ? 'ACTIVE' : 'DISABLED'; + const toggleBtnClass = mod.enabled ? 'btn-disable' : 'btn-enable'; + const toggleBtnText = mod.enabled ? 'DISABLE' : 'ENABLE'; + const toggleIcon = mod.enabled ? 'fa-pause' : 'fa-play'; + + return ` +
+
+ +
+ +
+
+

${mod.name}

+ v${mod.version} +
+

${mod.description || 'No description available'}

+
+ +
+
+ + ${statusText} +
+
+ + +
+
+
+ `; +} + +async function loadBrowseMods() { + const browseContainer = document.getElementById('browseModsList'); + if (!browseContainer) return; + + browseContainer.innerHTML = '
Loading mods from CurseForge...
'; + + try { + if (!API_KEY || API_KEY.length < 10) { + browseContainer.innerHTML = ` +
+ +

API Key Required

+

CurseForge API key is needed to browse mods

+
+ `; + return; + } + + const offset = modsPage * modsPageSize; + let url = `${CURSEFORGE_API}/mods/search?gameId=${HYTALE_GAME_ID}&pageSize=${modsPageSize}&sortOrder=desc&sortField=6&index=${offset}`; + + if (searchQuery && searchQuery.length > 0) { + url += `&searchFilter=${encodeURIComponent(searchQuery)}`; + } + + console.log('Fetching mods from page', modsPage + 1, 'offset:', offset, 'search:', searchQuery || 'none', 'URL:', url); + + const response = await fetch(url, { + headers: { + 'x-api-key': API_KEY, + 'Accept': 'application/json' + } + }); + + console.log('Response status:', response.status); + + if (!response.ok) { + const errorText = await response.text(); + console.error('API Error Response:', errorText); + throw new Error(`CurseForge API error: ${response.status} - ${errorText}`); + } + + const data = await response.json(); + console.log('API Response data:', data); + console.log('Total mods found:', data.data?.length || 0); + + browseMods = (data.data || []).map(mod => ({ + id: mod.id.toString(), + name: mod.name, + slug: mod.slug, + summary: mod.summary || 'No description available', + downloadCount: mod.downloadCount || 0, + author: mod.authors?.[0]?.name || 'Unknown', + version: mod.latestFiles?.[0]?.displayName || 'Unknown', + thumbnailUrl: mod.logo?.thumbnailUrl || null, + websiteUrl: mod.links?.websiteUrl || null, + modId: mod.id, + fileId: mod.latestFiles?.[0]?.id, + fileName: mod.latestFiles?.[0]?.fileName, + downloadUrl: mod.latestFiles?.[0]?.downloadUrl + })); + + console.log('Processed mods:', browseMods.length); + + modsTotalPages = Math.ceil((data.pagination?.totalCount || 1) / modsPageSize); + displayBrowseMods(browseMods); + updatePagination(); + } catch (error) { + console.error('Error loading browse mods:', error); + browseContainer.innerHTML = ` +
+ +

API Error

+

Failed to load mods from CurseForge

+ ${error.message} +
+ `; + } +} + +function displayBrowseMods(mods) { + const browseContainer = document.getElementById('browseModsList'); + if (!browseContainer) return; + + if (mods.length === 0) { + browseContainer.innerHTML = ` +
+ +

No Mods Found

+

Try adjusting your search

+
+ `; + return; + } + + browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join(''); + + mods.forEach(mod => { + const installBtn = document.getElementById(`install-${mod.id}`); + if (installBtn) { + installBtn.addEventListener('click', () => downloadAndInstallMod(mod)); + } + }); +} + +function createBrowseModCard(mod) { + const isInstalled = installedMods.some(installed => + installed.name.toLowerCase().includes(mod.name.toLowerCase()) || + installed.curseForgeId == mod.id + ); + + return ` +
+
+ ${mod.thumbnailUrl ? + `\"${mod.name}\"'\">` : + `` + } +
+ +
+
+

${mod.name}

+ ${mod.version} +
+

${mod.summary}

+
+ + + ${mod.author} + + + + ${formatNumber(mod.downloadCount)} + +
+
+ +
+ + ${!isInstalled ? + `` : + `` + } +
+
+ `; +} + +async function downloadAndInstallMod(modInfo) { + try { + window.LauncherUI?.showProgress(`Downloading ${modInfo.name}...`); + + const result = await window.electronAPI?.downloadMod(modInfo); + + if (result?.success) { + const newMod = { + id: result.modInfo.id, + name: modInfo.name, + version: modInfo.version, + description: modInfo.summary, + author: modInfo.author, + enabled: true, + fileName: result.fileName, + fileSize: result.modInfo.fileSize, + dateInstalled: new Date().toISOString(), + curseForgeId: modInfo.modId, + curseForgeFileId: modInfo.fileId + }; + + installedMods.push(newMod); + + await loadInstalledMods(); + await loadBrowseMods(); + window.LauncherUI?.hideProgress(); + showNotification(`${modInfo.name} installed successfully! 🎉`, 'success'); + } else { + throw new Error(result?.error || 'Failed to download mod'); + } + } catch (error) { + console.error('Error downloading mod:', error); + window.LauncherUI?.hideProgress(); + showNotification('Failed to download mod: ' + error.message, 'error'); + } +} + +async function toggleMod(modId) { + try { + window.LauncherUI?.showProgress('Toggling mod...'); + + const modsPath = await window.electronAPI?.getModsPath(); + const result = await window.electronAPI?.toggleMod(modId, modsPath); + + if (result?.success) { + await loadInstalledMods(); + window.LauncherUI?.hideProgress(); + } else { + throw new Error(result?.error || 'Failed to toggle mod'); + } + } catch (error) { + console.error('Error toggling mod:', error); + window.LauncherUI?.hideProgress(); + showNotification('Failed to toggle mod: ' + error.message, 'error'); + } +} + +async function deleteMod(modId) { + const mod = installedMods.find(m => m.id === modId); + if (!mod) return; + + showConfirmModal( + `Are you sure you want to delete "${mod.name}"? This action cannot be undone.`, + async () => { + try { + window.LauncherUI?.showProgress('Deleting mod...'); + + const modsPath = await window.electronAPI?.getModsPath(); + const result = await window.electronAPI?.uninstallMod(modId, modsPath); + + if (result?.success) { + await loadInstalledMods(); + await loadBrowseMods(); + window.LauncherUI?.hideProgress(); + showNotification(`"${mod.name}" deleted successfully`, 'success'); + } else { + throw new Error(result?.error || 'Failed to delete mod'); + } + } catch (error) { + console.error('Error deleting mod:', error); + window.LauncherUI?.hideProgress(); + showNotification('Failed to delete mod: ' + error.message, 'error'); + } + } + ); +} + +function formatNumber(num) { + if (!num) return '0'; + if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; + if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; + return num.toString(); +} + +function showNotification(message, type = 'info', duration = 4000) { + const existing = document.querySelector(`.mod-notification.${type}`); + if (existing) { + existing.remove(); + } + + const notification = document.createElement('div'); + notification.className = `mod-notification ${type}`; + + const icons = { + success: 'fa-check-circle', + error: 'fa-exclamation-circle', + info: 'fa-info-circle', + warning: 'fa-exclamation-triangle' + }; + + const colors = { + success: '#10b981', + error: '#ef4444', + info: '#3b82f6', + warning: '#f59e0b' + }; + + notification.innerHTML = ` +
+ + ${message} +
+ + `; + + notification.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + background: ${colors[type]}; + color: white; + padding: 16px 20px; + border-radius: 8px; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); + z-index: 10000; + min-width: 300px; + max-width: 400px; + transform: translateX(100%); + transition: transform 0.3s ease; + display: flex; + align-items: center; + justify-content: space-between; + font-size: 14px; + font-weight: 500; + `; + + const contentStyle = ` + display: flex; + align-items: center; + gap: 10px; + flex: 1; + `; + + const closeStyle = ` + background: none; + border: none; + color: white; + cursor: pointer; + padding: 4px; + border-radius: 4px; + opacity: 0.8; + transition: opacity 0.2s; + margin-left: 10px; + `; + + notification.querySelector('.notification-content').style.cssText = contentStyle; + notification.querySelector('.notification-close').style.cssText = closeStyle; + + document.body.appendChild(notification); + + // Animate in + setTimeout(() => { + notification.style.transform = 'translateX(0)'; + }, 10); + + // Auto remove + setTimeout(() => { + if (notification.parentElement) { + notification.style.transform = 'translateX(100%)'; + setTimeout(() => { + notification.remove(); + }, 300); + } + }, duration); +} + +// Custom confirmation modal +function showConfirmModal(message, onConfirm, onCancel = null) { + const existingModal = document.querySelector('.mod-confirm-modal'); + if (existingModal) { + existingModal.remove(); + } + + const modal = document.createElement('div'); + modal.className = 'mod-confirm-modal'; + modal.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(4px); + z-index: 20000; + display: flex; + align-items: center; + justify-content: center; + opacity: 0; + transition: opacity 0.3s ease; + `; + + const dialog = document.createElement('div'); + dialog.className = 'mod-confirm-dialog'; + dialog.style.cssText = ` + background: #1f2937; + border-radius: 12px; + padding: 0; + min-width: 400px; + max-width: 500px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6); + border: 1px solid rgba(239, 68, 68, 0.3); + transform: scale(0.9); + transition: transform 0.3s ease; + `; + + dialog.innerHTML = ` +
+
+ +

Confirm Deletion

+
+
+
+

${message}

+
+
+ + +
+ `; + + modal.appendChild(dialog); + document.body.appendChild(modal); + + // Animate in + setTimeout(() => { + modal.style.opacity = '1'; + dialog.style.transform = 'scale(1)'; + }, 10); + + // Event handlers + const cancelBtn = dialog.querySelector('.mod-confirm-cancel'); + const deleteBtn = dialog.querySelector('.mod-confirm-delete'); + + const closeModal = () => { + modal.style.opacity = '0'; + dialog.style.transform = 'scale(0.9)'; + setTimeout(() => { + modal.remove(); + }, 300); + }; + + cancelBtn.onclick = () => { + closeModal(); + if (onCancel) onCancel(); + }; + + deleteBtn.onclick = () => { + closeModal(); + onConfirm(); + }; + + modal.onclick = (e) => { + if (e.target === modal) { + closeModal(); + if (onCancel) onCancel(); + } + }; + + // Escape key + const handleEscape = (e) => { + if (e.key === 'Escape') { + closeModal(); + if (onCancel) onCancel(); + document.removeEventListener('keydown', handleEscape); + } + }; + document.addEventListener('keydown', handleEscape); +} + +function updatePagination() { + const currentPageEl = document.getElementById('currentPage'); + const totalPagesEl = document.getElementById('totalPages'); + const prevBtn = document.getElementById('prevPage'); + const nextBtn = document.getElementById('nextPage'); + + if (currentPageEl) currentPageEl.textContent = modsPage + 1; + if (totalPagesEl) totalPagesEl.textContent = modsTotalPages; + + if (prevBtn) { + prevBtn.disabled = modsPage === 0; + prevBtn.style.opacity = modsPage === 0 ? '0.5' : '1'; + prevBtn.style.cursor = modsPage === 0 ? 'not-allowed' : 'pointer'; + } + + if (nextBtn) { + nextBtn.disabled = modsPage >= modsTotalPages - 1; + nextBtn.style.opacity = modsPage >= modsTotalPages - 1 ? '0.5' : '1'; + nextBtn.style.cursor = modsPage >= modsTotalPages - 1 ? 'not-allowed' : 'pointer'; + } +} + +function showInstalledModsError(message) { + const modsContainer = document.getElementById('installedModsList'); + if (!modsContainer) return; + + modsContainer.innerHTML = ` +
+ +

Error

+

${message}

+
+ `; +} + +function viewModPage(modId) { + console.log('Looking for mod with ID:', modId, 'Type:', typeof modId); + console.log('Available mods:', browseMods.map(m => ({ id: m.id, name: m.name, type: typeof m.id }))); + + const mod = browseMods.find(m => m.id.toString() === modId.toString()); + if (mod) { + console.log('Found mod:', mod.name); + let modUrl; + if (mod.websiteUrl && mod.websiteUrl.includes('curseforge.com')) { + modUrl = mod.websiteUrl; + } else if (mod.slug) { + modUrl = `https://www.curseforge.com/hytale/mods/${mod.slug}`; + } else { + const nameSlug = mod.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, ''); + modUrl = `https://www.curseforge.com/hytale/mods/${nameSlug}`; + } + + console.log('Opening URL:', modUrl); + + if (window.electronAPI && window.electronAPI.openExternalLink) { + window.electronAPI.openExternalLink(modUrl); + } else { + if (window.electronAPI && window.electronAPI.shell) { + window.electronAPI.shell.openExternal(modUrl); + } else { + window.open(modUrl, '_blank'); + } + } + } else { + console.error('Mod not found with ID:', modId); + showNotification('Mod information not found', 'error'); + } +} + +window.modsManager = { + toggleMod, + deleteMod, + openMyModsModal, + closeMyModsModal, + viewModPage +}; + +document.addEventListener('DOMContentLoaded', initModsManager); diff --git a/GUI/js/news.js b/GUI/js/news.js new file mode 100644 index 0000000..7bc5abd --- /dev/null +++ b/GUI/js/news.js @@ -0,0 +1,124 @@ + +let newsData = []; + +export async function loadNews() { + try { + if (window.electronAPI && window.electronAPI.getHytaleNews) { + try { + const realNews = await window.electronAPI.getHytaleNews(); + if (realNews && realNews.length > 0) { + newsData = realNews.slice(0, 10).map((article, index) => ({ + id: index + 1, + title: article.title, + summary: article.description, + type: "NEWS", + image: article.imageUrl || '', + date: formatDate(article.date), + url: article.destUrl + })); + displayHomeNews(newsData.slice(0, 5)); + displayFullNews(newsData); + } else { + showErrorNews(); + } + } catch (error) { + console.log('Failed to load news:', error.message); + showErrorNews(); + } + } else { + showErrorNews(); + } + } catch (error) { + console.error('Error loading news:', error); + showErrorNews(); + } +} + +function displayHomeNews(news) { + const newsGrid = document.getElementById('newsGrid'); + if (!newsGrid) return; + + newsGrid.innerHTML = news.map(article => ` +
+
+
+ ${article.type} + ${article.date} +
+
+

${article.title}

+

${article.summary}

+
+
+ `).join(''); +} + +function displayFullNews(news) { + const allNewsGrid = document.getElementById('allNewsGrid'); + if (!allNewsGrid) return; + + allNewsGrid.innerHTML = news.map(article => ` +
+
+
+ ${article.type} + ${article.date} +
+
+

${article.title}

+

${article.summary}

+
+
+ `).join(''); +} + +function showErrorNews() { + const newsGrid = document.getElementById('newsGrid'); + if (newsGrid) { + newsGrid.innerHTML = ` +
+ + Unable to load news +
+ `; + } +} + +function openNewsDetails(newsId) { + const article = newsData.find(item => item.id === newsId); + if (article && article.url) { + openNewsArticle(article.url); + } else { + console.log('Opening news article:', article); + } +} + +function openNewsArticle(url) { + if (url && url !== '#' && window.electronAPI && window.electronAPI.openExternal) { + window.electronAPI.openExternal(url); + } +} + +function formatDate(dateString) { + if (!dateString) return 'RECENTLY'; + + const date = new Date(dateString); + const now = new Date(); + const diffTime = Math.abs(now - date); + const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)); + + if (diffDays === 1) return '1 DAY AGO'; + if (diffDays < 7) return `${diffDays} DAYS AGO`; + if (diffDays < 30) return `${Math.ceil(diffDays / 7)} WEEKS AGO`; + return date.toLocaleDateString(); +} + +window.openNewsDetails = openNewsDetails; +window.navigateToPage = (page) => { + if (window.LauncherUI) { + window.LauncherUI.showPage(`${page}-page`); + window.LauncherUI.setActiveNav(page); + } +}; + +document.addEventListener('DOMContentLoaded', loadNews); diff --git a/GUI/js/players.js b/GUI/js/players.js new file mode 100644 index 0000000..84efc0a --- /dev/null +++ b/GUI/js/players.js @@ -0,0 +1,154 @@ + +const API_URL = 'http://3.10.208.30/api'; +let updateInterval = null; +let currentUserId = null; + +export async function initPlayersCounter() { + setupPlayersCounter(); + + if (window.electronAPI && window.electronAPI.getUserId) { + currentUserId = await window.electronAPI.getUserId(); + } else { + console.error('Electron API not available'); + return; + } + + let username = 'Player'; + if (window.electronAPI.loadUsername) { + const savedUsername = await window.electronAPI.loadUsername(); + if (savedUsername) username = savedUsername; + } + + await registerPlayer(username, currentUserId); + + await fetchPlayerStats(); + startAutoUpdate(); +} + +function setupPlayersCounter() { + const counterElement = document.getElementById('playersOnlineCounter'); + if (!counterElement) { + console.warn('Players counter element not found'); + } +} + +async function fetchPlayerStats() { + try { + const response = await fetch(`${API_URL}/players/stats`); + + if (!response.ok) { + throw new Error(`API error: ${response.status}`); + } + + const data = await response.json(); + updateCounterDisplay(data); + } catch (error) { + console.error('Error fetching player stats:', error); + updateCounterDisplay({ online: 0, peak: 0 }); + } +} + +function updateCounterDisplay(stats) { + const counterElement = document.getElementById('playersOnlineCounter'); + const onlineCount = document.getElementById('onlineCount'); + + if (onlineCount) { + onlineCount.textContent = stats.online || 0; + } + + if (counterElement) { + counterElement.classList.add('updated'); + setTimeout(() => { + counterElement.classList.remove('updated'); + }, 300); + } +} + +async function registerPlayer(username, userId) { + try { + const response = await fetch(`${API_URL}/players/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ username, userId }) + }); + + if (!response.ok) { + throw new Error(`Failed to register player: ${response.status}`); + } + + const data = await response.json(); + currentUserId = userId; + console.log('Player registered:', data); + + await fetchPlayerStats(); + + return data; + } catch (error) { + console.error('Error registering player:', error); + return null; + } +} + +async function unregisterPlayer(userId) { + try { + const response = await fetch(`${API_URL}/players/unregister`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ userId }) + }); + + if (!response.ok) { + throw new Error(`Failed to unregister player: ${response.status}`); + } + + const data = await response.json(); + currentUserId = null; + console.log('Player unregistered:', data); + + await fetchPlayerStats(); + + return data; + } catch (error) { + console.error('Error unregistering player:', error); + return null; + } +} + +function startAutoUpdate() { + updateInterval = setInterval(async () => { + await fetchPlayerStats(); + + if (currentUserId) { + const username = window.LauncherState?.username || 'Player'; + await registerPlayer(username, currentUserId); + } + }, 3000); +} + +function stopAutoUpdate() { + if (updateInterval) { + clearInterval(updateInterval); + updateInterval = null; + } +} + + +window.addEventListener('beforeunload', () => { + if (currentUserId) { + const data = JSON.stringify({ userId: currentUserId }); + navigator.sendBeacon(`${API_URL}/players/unregister`, data); + } + stopAutoUpdate(); +}); + +window.PlayersAPI = { + register: registerPlayer, + unregister: unregisterPlayer, + fetchStats: fetchPlayerStats +}; + +document.addEventListener('DOMContentLoaded', initPlayersCounter); diff --git a/GUI/js/settings.js b/GUI/js/settings.js new file mode 100644 index 0000000..e987871 --- /dev/null +++ b/GUI/js/settings.js @@ -0,0 +1,143 @@ + +let customJavaCheck; +let customJavaOptions; +let customJavaPath; +let browseJavaBtn; +let settingsPlayerName; + +export function initSettings() { + setupSettingsElements(); + loadAllSettings(); +} + +function setupSettingsElements() { + customJavaCheck = document.getElementById('customJavaCheck'); + customJavaOptions = document.getElementById('customJavaOptions'); + customJavaPath = document.getElementById('customJavaPath'); + browseJavaBtn = document.getElementById('browseJavaBtn'); + settingsPlayerName = document.getElementById('settingsPlayerName'); + + if (customJavaCheck) { + customJavaCheck.addEventListener('change', toggleCustomJava); + } + + if (browseJavaBtn) { + browseJavaBtn.addEventListener('click', browseJavaPath); + } + + if (settingsPlayerName) { + settingsPlayerName.addEventListener('change', savePlayerName); + } +} + +function toggleCustomJava() { + if (!customJavaOptions) return; + + if (customJavaCheck && customJavaCheck.checked) { + customJavaOptions.style.display = 'block'; + } else { + customJavaOptions.style.display = 'none'; + if (customJavaPath) customJavaPath.value = ''; + saveCustomJavaPath(''); + } +} + +async function browseJavaPath() { + try { + if (window.electronAPI && window.electronAPI.browseJavaPath) { + const result = await window.electronAPI.browseJavaPath(); + if (result && result.filePaths && result.filePaths.length > 0) { + const selectedPath = result.filePaths[0]; + if (customJavaPath) { + customJavaPath.value = selectedPath; + } + await saveCustomJavaPath(selectedPath); + } + } + } catch (error) { + console.error('Error browsing Java path:', error); + } +} + +async function saveCustomJavaPath(path) { + try { + if (window.electronAPI && window.electronAPI.saveJavaPath) { + await window.electronAPI.saveJavaPath(path); + } + } catch (error) { + console.error('Error saving custom Java path:', error); + } +} + +async function loadCustomJavaPath() { + try { + if (window.electronAPI && window.electronAPI.loadJavaPath) { + const savedPath = await window.electronAPI.loadJavaPath(); + if (savedPath && savedPath.trim()) { + if (customJavaPath) { + customJavaPath.value = savedPath; + } + if (customJavaCheck) { + customJavaCheck.checked = true; + } + if (customJavaOptions) { + customJavaOptions.style.display = 'block'; + } + } + } + } catch (error) { + console.error('Error loading custom Java path:', error); + } +} + +async function savePlayerName() { + try { + if (window.electronAPI && window.electronAPI.saveUsername && settingsPlayerName) { + const playerName = settingsPlayerName.value.trim() || 'Player'; + await window.electronAPI.saveUsername(playerName); + } + } catch (error) { + console.error('Error saving player name:', error); + } +} + +async function loadPlayerName() { + try { + if (window.electronAPI && window.electronAPI.loadUsername && settingsPlayerName) { + const savedName = await window.electronAPI.loadUsername(); + if (savedName) { + settingsPlayerName.value = savedName; + } + } + } catch (error) { + console.error('Error loading player name:', error); + } +} + +async function loadAllSettings() { + await loadCustomJavaPath(); + await loadPlayerName(); +} + + +export function getCurrentJavaPath() { + if (customJavaCheck && customJavaCheck.checked && customJavaPath) { + return customJavaPath.value.trim(); + } + return ''; +} + + +export function getCurrentPlayerName() { + if (settingsPlayerName && settingsPlayerName.value.trim()) { + return settingsPlayerName.value.trim(); + } + return 'Player'; +} + +document.addEventListener('DOMContentLoaded', initSettings); + +window.SettingsAPI = { + getCurrentJavaPath, + getCurrentPlayerName +}; \ No newline at end of file diff --git a/GUI/js/ui.js b/GUI/js/ui.js new file mode 100644 index 0000000..9f12233 --- /dev/null +++ b/GUI/js/ui.js @@ -0,0 +1,469 @@ + +let progressOverlay; +let progressBar; +let progressBarFill; +let progressText; +let progressPercent; +let progressSpeed; +let progressSize; + +function showPage(pageId) { + const pages = document.querySelectorAll('.page'); + pages.forEach(page => { + if (page.id === pageId) { + page.classList.add('active'); + page.style.display = ''; + } else { + page.classList.remove('active'); + page.style.display = 'none'; + } + }); +} + +function setActiveNav(page) { + const navItems = document.querySelectorAll('.nav-item'); + navItems.forEach(item => { + if (item.getAttribute('data-page') === page) { + item.classList.add('active'); + } else { + item.classList.remove('active'); + } + }); +} + +function handleNavigation() { + const navItems = document.querySelectorAll('.nav-item'); + navItems.forEach(item => { + item.addEventListener('click', () => { + const page = item.getAttribute('data-page'); + showPage(`${page}-page`); + setActiveNav(page); + }); + }); +} + +function setupWindowControls() { + const minimizeBtn = document.querySelector('.window-controls .minimize'); + const closeBtn = document.querySelector('.window-controls .close'); + + const windowControls = document.querySelector('.window-controls'); + const header = document.querySelector('.header'); + + if (windowControls) { + windowControls.style.pointerEvents = 'auto'; + windowControls.style.zIndex = '10000'; + } + + if (header) { + header.style.webkitAppRegion = 'drag'; + if (windowControls) { + windowControls.style.webkitAppRegion = 'no-drag'; + } + } + + if (window.electronAPI) { + if (minimizeBtn) { + minimizeBtn.onclick = (e) => { + e.stopPropagation(); + window.electronAPI.minimizeWindow(); + }; + } + if (closeBtn) { + closeBtn.onclick = (e) => { + e.stopPropagation(); + window.electronAPI.closeWindow(); + }; + } + } +} + +function showLauncherOrInstall(isInstalled) { + const launcher = document.getElementById('launcher-container'); + const install = document.getElementById('install-page'); + const sidebar = document.querySelector('.sidebar'); + const gameTitle = document.querySelector('.game-title-section'); + + if (isInstalled) { + if (launcher) launcher.style.display = ''; + if (install) install.style.display = 'none'; + if (sidebar) sidebar.style.pointerEvents = 'auto'; + if (gameTitle) gameTitle.style.display = ''; + showPage('play-page'); + setActiveNav('play'); + } else { + if (launcher) launcher.style.display = 'none'; + if (install) { + install.style.display = ''; + install.classList.add('active'); + } + if (sidebar) sidebar.style.pointerEvents = 'none'; + if (gameTitle) gameTitle.style.display = 'none'; + const pages = document.querySelectorAll('#launcher-container .page'); + pages.forEach(page => page.classList.remove('active')); + } +} + +function setupSidebarLogo() { + const logo = document.querySelector('.sidebar-logo img'); + if (logo) { + logo.addEventListener('click', () => { + showPage('play-page'); + setActiveNav('play'); + }); + } +} + +function showProgress() { + if (progressOverlay) { + progressOverlay.style.display = 'block'; + setTimeout(() => { + progressOverlay.style.opacity = '1'; + progressOverlay.style.transform = 'translateY(0)'; + }, 10); + } +} + +function hideProgress() { + if (progressOverlay) { + progressOverlay.style.opacity = '0'; + progressOverlay.style.transform = 'translateY(20px)'; + setTimeout(() => { + progressOverlay.style.display = 'none'; + }, 300); + } +} + +function updateProgress(data) { + if (data.message && progressText) { + progressText.textContent = data.message; + } + + if (data.percent !== null && data.percent !== undefined) { + const percent = Math.min(100, Math.max(0, Math.round(data.percent))); + if (progressPercent) progressPercent.textContent = `${percent}%`; + if (progressBarFill) progressBarFill.style.width = `${percent}%`; + if (progressBar) progressBar.style.width = `${percent}%`; + } + + if (data.speed && data.downloaded && data.total) { + const speedMB = (data.speed / 1024 / 1024).toFixed(2); + const downloadedMB = (data.downloaded / 1024 / 1024).toFixed(2); + const totalMB = (data.total / 1024 / 1024).toFixed(2); + if (progressSpeed) progressSpeed.textContent = `${speedMB} MB/s`; + if (progressSize) progressSize.textContent = `${downloadedMB} / ${totalMB} MB`; + } +} + +function setupAnimations() { + document.body.style.opacity = '0'; + document.body.style.transform = 'translateY(20px)'; + + setTimeout(() => { + document.body.style.transition = 'all 0.6s ease'; + document.body.style.opacity = '1'; + document.body.style.transform = 'translateY(0)'; + }, 100); + + const style = document.createElement('style'); + style.textContent = ` + @keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } + } + `; + document.head.appendChild(style); +} + +function setupFirstLaunchHandlers() { + console.log('Setting up first launch handlers...'); + + window.electronAPI.onFirstLaunchUpdate((data) => { + console.log('Received first launch update event:', data); + showFirstLaunchUpdateDialog(data); + }); + + window.electronAPI.onFirstLaunchWelcome(() => { + }); + + window.electronAPI.onFirstLaunchProgress((data) => { + showProgress(); + updateProgress(data); + }); + + window.electronAPI.onLockPlayButton((locked) => { + lockPlayButton(locked); + }); +} + +function showFirstLaunchUpdateDialog(data) { + console.log('Creating first launch modal...'); + + const existingModal = document.querySelector('.first-launch-modal-overlay'); + if (existingModal) { + existingModal.remove(); + } + + const modalOverlay = document.createElement('div'); + modalOverlay.className = 'first-launch-modal-overlay'; + modalOverlay.style.cssText = ` + position: fixed !important; + top: 0 !important; + left: 0 !important; + right: 0 !important; + bottom: 0 !important; + background: rgba(0, 0, 0, 0.95) !important; + backdrop-filter: blur(10px) !important; + z-index: 999999 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + pointer-events: all !important; + `; + + const modalDialog = document.createElement('div'); + modalDialog.className = 'first-launch-modal-dialog'; + modalDialog.style.cssText = ` + background: #1a1a1a !important; + border-radius: 12px !important; + padding: 0 !important; + width: 500px !important; + max-width: 90vw !important; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.8) !important; + border: 1px solid rgba(147, 51, 234, 0.5) !important; + overflow: hidden !important; + animation: modalSlideIn 0.3s ease-out !important; + `; + + modalDialog.innerHTML = ` +
+

+ 🔄 Game Update Required +

+
+
+
+

+ An existing Hytale installation has been detected and must be updated to the latest version. +

+

+ ✅ Your game saves and settings will be preserved +

+
+ +
+

+ 📁 Location: ${data.existingGame.installPath} +

+

+ 💾 UserData: ${data.existingGame.hasUserData ? '✅ Found (will be preserved)' : '❌ Not found'} +

+
+ +
+

+ ⚠️ This update is mandatory and cannot be skipped +

+
+
+
+ +
+ `; + + modalOverlay.appendChild(modalDialog); + + modalOverlay.onclick = (e) => { + if (e.target === modalOverlay) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + document.addEventListener('keydown', function preventEscape(e) { + if (e.key === 'Escape') { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }); + + document.body.appendChild(modalOverlay); + + const updateBtn = document.getElementById('updateGameBtn'); + updateBtn.onclick = () => { + acceptFirstLaunchUpdate(); + }; + + window.firstLaunchExistingGame = data.existingGame; + + console.log('First launch modal created and displayed'); +} + +function lockPlayButton(locked) { + const playButton = document.getElementById('homePlayBtn'); + + if (!playButton) { + console.warn('Play button not found'); + return; + } + + if (locked) { + playButton.style.opacity = '0.5'; + playButton.style.pointerEvents = 'none'; + playButton.style.cursor = 'not-allowed'; + playButton.setAttribute('data-locked', 'true'); + + const spanElement = playButton.querySelector('span'); + if (spanElement) { + if (!playButton.getAttribute('data-original-text')) { + playButton.setAttribute('data-original-text', spanElement.textContent); + } + spanElement.textContent = 'CHECKING...'; + } + + console.log('Play button locked'); + } else { + playButton.style.opacity = ''; + playButton.style.pointerEvents = ''; + playButton.style.cursor = ''; + playButton.removeAttribute('data-locked'); + + const spanElement = playButton.querySelector('span'); + const originalText = playButton.getAttribute('data-original-text'); + if (spanElement && originalText) { + spanElement.textContent = originalText; + playButton.removeAttribute('data-original-text'); + } + + console.log('Play button unlocked'); + } +} + + + +async function acceptFirstLaunchUpdate() { + const existingGame = window.firstLaunchExistingGame; + + if (!existingGame) { + showNotification('Error: Game data not found', 'error'); + return; + } + + const modal = document.querySelector('.first-launch-modal-overlay'); + if (modal) { + modal.style.pointerEvents = 'none'; + const btn = document.getElementById('updateGameBtn'); + if (btn) { + btn.style.opacity = '0.5'; + btn.style.cursor = 'not-allowed'; + btn.textContent = '🔄 Updating...'; + } + } + + try { + showProgress(); + updateProgress({ message: 'Starting mandatory game update...', percent: 0 }); + + const result = await window.electronAPI.acceptFirstLaunchUpdate(existingGame); + + window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched(); + + if (modal) { + modal.remove(); + } + + lockPlayButton(false); + + if (result.success) { + hideProgress(); + showNotification('Game updated successfully! 🎉', 'success'); + } else { + hideProgress(); + showNotification(`Update failed: ${result.error}`, 'error'); + } + } catch (error) { + if (modal) { + modal.remove(); + } + lockPlayButton(false); + hideProgress(); + showNotification(`Update error: ${error.message}`, 'error'); + } +} + +function dismissFirstLaunchDialog() { + const modal = document.querySelector('.first-launch-modal-overlay'); + if (modal) { + modal.remove(); + } + + lockPlayButton(false); + window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched(); +} + +function showNotification(message, type = 'info') { + const notification = document.createElement('div'); + notification.className = `notification notification-${type}`; + notification.textContent = message; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.classList.add('show'); + }, 100); + + setTimeout(() => { + notification.remove(); + }, 5000); +} + +function setupUI() { + progressOverlay = document.getElementById('progressOverlay'); + progressBar = document.getElementById('progressBar'); + progressBarFill = document.getElementById('progressBarFill'); + progressText = document.getElementById('progressText'); + progressPercent = document.getElementById('progressPercent'); + progressSpeed = document.getElementById('progressSpeed'); + progressSize = document.getElementById('progressSize'); + + lockPlayButton(true); + + handleNavigation(); + setupWindowControls(); + setupSidebarLogo(); + setupAnimations(); + setupFirstLaunchHandlers(); + + document.body.focus(); +} + +window.LauncherUI = { + showPage, + setActiveNav, + showLauncherOrInstall, + showProgress, + hideProgress, + updateProgress +}; + +document.addEventListener('DOMContentLoaded', setupUI); diff --git a/GUI/js/update.js b/GUI/js/update.js new file mode 100644 index 0000000..00393b4 --- /dev/null +++ b/GUI/js/update.js @@ -0,0 +1,162 @@ + +class ClientUpdateManager { + constructor() { + this.updatePopupVisible = false; + this.init(); + } + + init() { + window.electronAPI.onUpdatePopup((updateInfo) => { + this.showUpdatePopup(updateInfo); + }); + + this.checkForUpdatesOnDemand(); + } + + showUpdatePopup(updateInfo) { + if (this.updatePopupVisible) return; + + this.updatePopupVisible = true; + + const popupHTML = ` +
+
+
+
+ +
+

+ NEW UPDATE AVAILABLE +

+
+ +
+
+ Current Version: + ${updateInfo.currentVersion} +
+
+ New Version: + ${updateInfo.newVersion} +
+
+ +
+ A new version of Hytale F2P Launcher is available.
+ Please download the latest version to continue using the launcher. +
+ + + + +
+
+ `; + + document.body.insertAdjacentHTML('beforeend', popupHTML); + + this.blockInterface(); + + const downloadBtn = document.getElementById('update-download-btn'); + if (downloadBtn) { + downloadBtn.addEventListener('click', async (e) => { + e.preventDefault(); + e.stopPropagation(); + + downloadBtn.disabled = true; + downloadBtn.innerHTML = 'Opening GitHub...'; + + try { + await window.electronAPI.openDownloadPage(); + console.log('✅ Download page opened, launcher will close...'); + + downloadBtn.innerHTML = 'Launcher closing...'; + + } catch (error) { + console.error('❌ Error opening download page:', error); + downloadBtn.disabled = false; + downloadBtn.innerHTML = 'Download Update'; + } + }); + } + + const overlay = document.getElementById('update-popup-overlay'); + if (overlay) { + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }); + } + + console.log('🔔 Update popup displayed with new style'); + } + + blockInterface() { + const mainContent = document.querySelector('.flex.w-full.h-screen'); + if (mainContent) { + mainContent.classList.add('interface-blocked'); + } + + document.body.classList.add('no-select'); + + document.addEventListener('keydown', this.blockKeyEvents.bind(this), true); + + document.addEventListener('contextmenu', this.blockContextMenu.bind(this), true); + + console.log('🚫 Interface blocked for update'); + } + + blockKeyEvents(event) { + if (event.target.closest('#update-popup-overlay')) { + if ((event.key === 'Enter' || event.key === ' ') && + event.target.id === 'update-download-btn') { + return; + } + if (event.key !== 'Tab') { + event.preventDefault(); + event.stopPropagation(); + } + return; + } + + event.preventDefault(); + event.stopPropagation(); + event.stopImmediatePropagation(); + return false; + } + + blockContextMenu(event) { + if (!event.target.closest('#update-popup-overlay')) { + event.preventDefault(); + event.stopPropagation(); + return false; + } + } + + async checkForUpdatesOnDemand() { + try { + const updateInfo = await window.electronAPI.checkForUpdates(); + if (updateInfo.updateAvailable) { + this.showUpdatePopup(updateInfo); + } + return updateInfo; + } catch (error) { + console.error('Error checking for updates:', error); + return { updateAvailable: false, error: error.message }; + } + } +} + +document.addEventListener('DOMContentLoaded', () => { + window.updateManager = new ClientUpdateManager(); +}); + +window.ClientUpdateManager = ClientUpdateManager; \ No newline at end of file