mirror of
https://github.com/amiayweb/Hytale-F2P.git
synced 2026-02-26 12:21:46 -03:00
Add files via upload
This commit is contained in:
358
GUI/js/chat.js
Normal file
358
GUI/js/chat.js
Normal file
@@ -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 = `
|
||||||
|
<div class="message-header">
|
||||||
|
<span class="message-username">${escapeHtml(username)}</span>
|
||||||
|
<span class="message-time">${time}</span>
|
||||||
|
</div>
|
||||||
|
<div class="message-content">${message}</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
window.ChatAPI = {
|
||||||
|
send: sendMessage,
|
||||||
|
disconnect: () => socket?.disconnect()
|
||||||
|
};
|
||||||
194
GUI/js/install.js
Normal file
194
GUI/js/install.js
Normal file
@@ -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();
|
||||||
|
});
|
||||||
235
GUI/js/launcher.js
Normal file
235
GUI/js/launcher.js
Normal file
@@ -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);
|
||||||
724
GUI/js/mods.js
Normal file
724
GUI/js/mods.js
Normal file
@@ -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 = `
|
||||||
|
<div class=\"empty-installed-mods\">
|
||||||
|
<i class=\"fas fa-box-open\"></i>
|
||||||
|
<h4>No Mods Installed</h4>
|
||||||
|
<p>Add mods from CurseForge or import local files</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 `
|
||||||
|
<div class="installed-mod-card" data-mod-id="${mod.id}">
|
||||||
|
<div class="installed-mod-icon">
|
||||||
|
<i class="fas fa-cube"></i>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="installed-mod-info">
|
||||||
|
<div class="installed-mod-header">
|
||||||
|
<h4 class="installed-mod-name">${mod.name}</h4>
|
||||||
|
<span class="installed-mod-version">v${mod.version}</span>
|
||||||
|
</div>
|
||||||
|
<p class="installed-mod-description">${mod.description || 'No description available'}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="installed-mod-actions">
|
||||||
|
<div class="installed-mod-status ${statusClass}">
|
||||||
|
<i class="fas fa-circle"></i>
|
||||||
|
${statusText}
|
||||||
|
</div>
|
||||||
|
<div class="installed-mod-buttons">
|
||||||
|
<button id="delete-installed-${mod.id}" class="installed-mod-btn-icon" title="Delete mod">
|
||||||
|
<i class="fas fa-trash"></i>
|
||||||
|
</button>
|
||||||
|
<button id="toggle-installed-${mod.id}" class="installed-mod-btn-toggle ${toggleBtnClass}">
|
||||||
|
<i class="fas ${toggleIcon}"></i>
|
||||||
|
${toggleBtnText}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadBrowseMods() {
|
||||||
|
const browseContainer = document.getElementById('browseModsList');
|
||||||
|
if (!browseContainer) return;
|
||||||
|
|
||||||
|
browseContainer.innerHTML = '<div class=\"loading-mods\"><div class=\"loading-spinner\"></div><span>Loading mods from CurseForge...</span></div>';
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!API_KEY || API_KEY.length < 10) {
|
||||||
|
browseContainer.innerHTML = `
|
||||||
|
<div class=\"empty-browse-mods\">
|
||||||
|
<i class=\"fas fa-key\"></i>
|
||||||
|
<h4>API Key Required</h4>
|
||||||
|
<p>CurseForge API key is needed to browse mods</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 = `
|
||||||
|
<div class=\"empty-browse-mods error\">
|
||||||
|
<i class=\"fas fa-exclamation-triangle\"></i>
|
||||||
|
<h4>API Error</h4>
|
||||||
|
<p>Failed to load mods from CurseForge</p>
|
||||||
|
<small>${error.message}</small>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayBrowseMods(mods) {
|
||||||
|
const browseContainer = document.getElementById('browseModsList');
|
||||||
|
if (!browseContainer) return;
|
||||||
|
|
||||||
|
if (mods.length === 0) {
|
||||||
|
browseContainer.innerHTML = `
|
||||||
|
<div class=\"empty-browse-mods\">
|
||||||
|
<i class=\"fas fa-search\"></i>
|
||||||
|
<h4>No Mods Found</h4>
|
||||||
|
<p>Try adjusting your search</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
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 `
|
||||||
|
<div class=\"mod-card ${isInstalled ? 'installed' : ''}\" data-mod-id=\"${mod.id}\">
|
||||||
|
<div class=\"mod-image\">
|
||||||
|
${mod.thumbnailUrl ?
|
||||||
|
`<img src=\"${mod.thumbnailUrl}\" alt=\"${mod.name}\" onerror=\"this.parentElement.innerHTML='<i class=\\\"fas fa-puzzle-piece\\\"></i>'\">` :
|
||||||
|
`<i class=\"fas fa-puzzle-piece\"></i>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=\"mod-info\">
|
||||||
|
<div class=\"mod-header\">
|
||||||
|
<h3 class=\"mod-name\">${mod.name}</h3>
|
||||||
|
<span class=\"mod-version\">${mod.version}</span>
|
||||||
|
</div>
|
||||||
|
<p class=\"mod-description\">${mod.summary}</p>
|
||||||
|
<div class=\"mod-meta\">
|
||||||
|
<span class=\"mod-meta-item\">
|
||||||
|
<i class=\"fas fa-user\"></i>
|
||||||
|
${mod.author}
|
||||||
|
</span>
|
||||||
|
<span class=\"mod-meta-item\">
|
||||||
|
<i class=\"fas fa-download\"></i>
|
||||||
|
${formatNumber(mod.downloadCount)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class=\"mod-actions\">
|
||||||
|
<button id=\"view-${mod.id}\" class=\"mod-btn-toggle bg-blue-600 text-white hover:bg-blue-700\" onclick=\"window.modsManager.viewModPage(${mod.id})\">
|
||||||
|
<i class=\"fas fa-external-link-alt\"></i>
|
||||||
|
VIEW
|
||||||
|
</button>
|
||||||
|
${!isInstalled ?
|
||||||
|
`<button id=\"install-${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
|
||||||
|
<i class=\"fas fa-download\"></i>
|
||||||
|
INSTALL
|
||||||
|
</button>` :
|
||||||
|
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||||
|
<i class=\"fas fa-check\"></i>
|
||||||
|
INSTALLED
|
||||||
|
</button>`
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class="notification-content">
|
||||||
|
<i class="fas ${icons[type]}"></i>
|
||||||
|
<span>${message}</span>
|
||||||
|
</div>
|
||||||
|
<button class="notification-close" onclick="this.parentElement.remove()">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div style="padding: 24px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
||||||
|
<div style="display: flex; align-items: center; gap: 12px; color: #ef4444;">
|
||||||
|
<i class="fas fa-exclamation-triangle" style="font-size: 24px;"></i>
|
||||||
|
<h3 style="margin: 0; font-size: 1.2rem; font-weight: 600;">Confirm Deletion</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 24px; color: #e5e7eb;">
|
||||||
|
<p style="margin: 0; line-height: 1.5; font-size: 1rem;">${message}</p>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 20px 24px; display: flex; gap: 12px; justify-content: flex-end; border-top: 1px solid rgba(255,255,255,0.1);">
|
||||||
|
<button class="mod-confirm-cancel" style="
|
||||||
|
background: transparent;
|
||||||
|
color: #9ca3af;
|
||||||
|
border: 1px solid rgba(156, 163, 175, 0.3);
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
">Cancel</button>
|
||||||
|
<button class="mod-confirm-delete" style="
|
||||||
|
background: #ef4444;
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
transition: all 0.2s;
|
||||||
|
">Delete</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 = `
|
||||||
|
<div class=\"empty-installed-mods error\">
|
||||||
|
<i class=\"fas fa-exclamation-triangle\"></i>
|
||||||
|
<h4>Error</h4>
|
||||||
|
<p>${message}</p>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
124
GUI/js/news.js
Normal file
124
GUI/js/news.js
Normal file
@@ -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 => `
|
||||||
|
<div class="news-item news-card" onclick="openNewsDetails(${article.id})">
|
||||||
|
<div class="news-image" style="background-image: url('${article.image}');"></div>
|
||||||
|
<div class="news-overlay">
|
||||||
|
<span class="news-type">${article.type}</span>
|
||||||
|
<span class="news-date">${article.date}</span>
|
||||||
|
</div>
|
||||||
|
<div class="news-content">
|
||||||
|
<h3 class="news-title">${article.title}</h3>
|
||||||
|
<p class="news-summary">${article.summary}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function displayFullNews(news) {
|
||||||
|
const allNewsGrid = document.getElementById('allNewsGrid');
|
||||||
|
if (!allNewsGrid) return;
|
||||||
|
|
||||||
|
allNewsGrid.innerHTML = news.map(article => `
|
||||||
|
<div class="news-item news-card" onclick="openNewsDetails(${article.id})">
|
||||||
|
<div class="news-image" style="background-image: url('${article.image}');"></div>
|
||||||
|
<div class="news-overlay">
|
||||||
|
<span class="news-type">${article.type}</span>
|
||||||
|
<span class="news-date">${article.date}</span>
|
||||||
|
</div>
|
||||||
|
<div class="news-content">
|
||||||
|
<h3 class="news-title">${article.title}</h3>
|
||||||
|
<p class="news-summary">${article.summary}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showErrorNews() {
|
||||||
|
const newsGrid = document.getElementById('newsGrid');
|
||||||
|
if (newsGrid) {
|
||||||
|
newsGrid.innerHTML = `
|
||||||
|
<div class="loading-news">
|
||||||
|
<i class="fas fa-exclamation-triangle text-4xl mb-4 text-yellow-500"></i>
|
||||||
|
<span>Unable to load news</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
154
GUI/js/players.js
Normal file
154
GUI/js/players.js
Normal file
@@ -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);
|
||||||
143
GUI/js/settings.js
Normal file
143
GUI/js/settings.js
Normal file
@@ -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
|
||||||
|
};
|
||||||
469
GUI/js/ui.js
Normal file
469
GUI/js/ui.js
Normal file
@@ -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 = `
|
||||||
|
<div style="background: linear-gradient(135deg, rgba(147, 51, 234, 0.2), rgba(59, 130, 246, 0.2)); padding: 25px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
||||||
|
<h2 style="margin: 0; color: #fff; font-size: 1.5rem; font-weight: 600; text-align: center;">
|
||||||
|
🔄 Game Update Required
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 30px; color: #e5e7eb; line-height: 1.6;">
|
||||||
|
<div style="text-align: center; margin-bottom: 25px;">
|
||||||
|
<p style="font-size: 1.1rem; margin-bottom: 15px;">
|
||||||
|
An existing Hytale installation has been detected and must be updated to the latest version.
|
||||||
|
</p>
|
||||||
|
<p style="color: #10b981; font-weight: 500; margin-bottom: 20px;">
|
||||||
|
✅ Your game saves and settings will be preserved
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background: rgba(59, 130, 246, 0.1); padding: 20px; border-radius: 8px; border-left: 4px solid #3b82f6; margin: 20px 0;">
|
||||||
|
<p style="margin: 8px 0; font-family: 'Courier New', monospace; font-size: 0.9em;">
|
||||||
|
<strong>📁 Location:</strong> ${data.existingGame.installPath}
|
||||||
|
</p>
|
||||||
|
<p style="margin: 8px 0; font-family: 'Courier New', monospace; font-size: 0.9em;">
|
||||||
|
<strong>💾 UserData:</strong> ${data.existingGame.hasUserData ? '✅ Found (will be preserved)' : '❌ Not found'}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="background: rgba(234, 179, 8, 0.1); padding: 15px; border-radius: 8px; border-left: 4px solid #eab308; margin: 20px 0;">
|
||||||
|
<p style="margin: 0; color: #fbbf24; font-weight: 500; font-size: 0.95em;">
|
||||||
|
⚠️ This update is mandatory and cannot be skipped
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="padding: 25px; border-top: 1px solid rgba(255,255,255,0.1); text-align: center;">
|
||||||
|
<button id="updateGameBtn" style="
|
||||||
|
background: linear-gradient(135deg, #9333ea, #3b82f6) !important;
|
||||||
|
color: white !important;
|
||||||
|
border: none !important;
|
||||||
|
padding: 15px 30px !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
font-size: 1rem !important;
|
||||||
|
font-weight: 600 !important;
|
||||||
|
cursor: pointer !important;
|
||||||
|
transition: all 0.2s ease !important;
|
||||||
|
min-width: 200px !important;
|
||||||
|
" onmouseover="this.style.transform='scale(1.05)'" onmouseout="this.style.transform='scale(1)'">
|
||||||
|
🚀 Update Game Now
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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);
|
||||||
162
GUI/js/update.js
Normal file
162
GUI/js/update.js
Normal file
@@ -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 = `
|
||||||
|
<div id="update-popup-overlay">
|
||||||
|
<div class="update-popup-container update-popup-pulse">
|
||||||
|
<div class="update-popup-header">
|
||||||
|
<div class="update-popup-icon">
|
||||||
|
<i class="fas fa-download"></i>
|
||||||
|
</div>
|
||||||
|
<h2 class="update-popup-title">
|
||||||
|
NEW UPDATE AVAILABLE
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="update-popup-versions">
|
||||||
|
<div class="version-row">
|
||||||
|
<span class="version-label">Current Version:</span>
|
||||||
|
<span class="version-current">${updateInfo.currentVersion}</span>
|
||||||
|
</div>
|
||||||
|
<div class="version-row">
|
||||||
|
<span class="version-label">New Version:</span>
|
||||||
|
<span class="version-new">${updateInfo.newVersion}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="update-popup-message">
|
||||||
|
A new version of Hytale F2P Launcher is available.<br>
|
||||||
|
Please download the latest version to continue using the launcher.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="update-download-btn" class="update-download-btn">
|
||||||
|
<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>
|
||||||
|
Download Update
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="update-popup-footer">
|
||||||
|
This popup cannot be closed until you update the launcher
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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 = '<i class="fas fa-spinner fa-spin" style="margin-right: 0.5rem;"></i>Opening GitHub...';
|
||||||
|
|
||||||
|
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...';
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error opening download page:', error);
|
||||||
|
downloadBtn.disabled = false;
|
||||||
|
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>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;
|
||||||
Reference in New Issue
Block a user