Profile System & Mod Loading Fixes

Added a full profile system and fixed a few critical mod loading issues.

What changed

Profiles — Implemented proper profile management (create, switch, delete). Each profile now has its own isolated mod list.

Mod Isolation — Fixed ModManager so mods are strictly scoped to the active profile. Browsing and installing only affects the selected profile.

Critical Fix — Fixed a path bug where mods were being saved to ~/AppData/Local on macOS (Windows path) instead of ~/Library/Application Support. Mods now save to the correct location and load correctly in-game.

Stability — Added an auto-sync step before every launch to make sure the physical mods folder always matches the active profile.

UI — Added a profile selector dropdown and a profile management modal.
This commit is contained in:
Rahul-Sahani04
2026-01-20 11:52:36 +05:30
parent fffc730788
commit 64892c81e9
14 changed files with 1424 additions and 509 deletions

View File

@@ -14,15 +14,15 @@ export function setupLauncher() {
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 (!isDownloading) return;
@@ -31,43 +31,207 @@ export function setupLauncher() {
}
});
}
if (window.electronAPI && window.electronAPI.onProgressUpdate) {
window.electronAPI.onProgressUpdate((data) => {
if (!isDownloading) return;
if (window.LauncherUI) {
window.LauncherUI.updateProgress(data);
}
});
}
// Initial Profile Load
loadProfiles();
// Close dropdown on outside click
document.addEventListener('click', (e) => {
const selector = document.getElementById('profileSelector');
if (selector && !selector.contains(e.target)) {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) dropdown.classList.remove('show');
}
});
}
// ==========================================
// PROFILE MANAGEMENT
// ==========================================
async function loadProfiles() {
try {
if (!window.electronAPI || !window.electronAPI.profile) return;
const profiles = await window.electronAPI.profile.list();
const activeProfile = await window.electronAPI.profile.getActive();
renderProfileList(profiles, activeProfile);
updateCurrentProfileUI(activeProfile);
} catch (error) {
console.error('Failed to load profiles:', error);
}
}
function renderProfileList(profiles, activeProfile) {
const list = document.getElementById('profileList');
const managerList = document.getElementById('managerProfileList');
if (!list) return;
// Dropdown List
list.innerHTML = profiles.map(p => `
<div class="profile-item ${p.id === activeProfile.id ? 'active' : ''}"
onclick="switchProfile('${p.id}')">
<span>${p.name}</span>
${p.id === activeProfile.id ? '<i class="fas fa-check ml-auto"></i>' : ''}
</div>
`).join('');
// Manager Modal List
if (managerList) {
managerList.innerHTML = profiles.map(p => `
<div class="profile-manager-item ${p.id === activeProfile.id ? 'active' : ''}">
<div class="flex items-center gap-3">
<i class="fas fa-user-circle text-xl text-gray-400"></i>
<div>
<div class="font-bold">${p.name}</div>
<div class="text-xs text-gray-500">ID: ${p.id.substring(0, 8)}...</div>
</div>
</div>
${p.id !== activeProfile.id ? `
<button class="profile-delete-btn" onclick="deleteProfile('${p.id}')" title="Delete Profile">
<i class="fas fa-trash"></i>
</button>
` : '<span class="text-xs text-green-500 font-bold px-2">ACTIVE</span>'}
</div>
`).join('');
}
}
function updateCurrentProfileUI(profile) {
const nameEl = document.getElementById('currentProfileName');
if (nameEl && profile) {
nameEl.textContent = profile.name;
}
}
window.toggleProfileDropdown = () => {
const dropdown = document.getElementById('profileDropdown');
if (dropdown) {
dropdown.classList.toggle('show');
}
};
window.openProfileManager = () => {
const modal = document.getElementById('profileManagerModal');
if (modal) {
modal.style.display = 'flex';
// Refresh list
loadProfiles();
}
// Close dropdown
const dropdown = document.getElementById('profileDropdown');
if (dropdown) dropdown.classList.remove('show');
};
window.closeProfileManager = () => {
const modal = document.getElementById('profileManagerModal');
if (modal) modal.style.display = 'none';
};
window.createNewProfile = async () => {
const input = document.getElementById('newProfileName');
if (!input || !input.value.trim()) return;
try {
const name = input.value.trim();
await window.electronAPI.profile.create(name);
input.value = '';
await loadProfiles();
} catch (error) {
console.error('Failed to create profile:', error);
alert('Failed to create profile: ' + error.message);
}
};
window.deleteProfile = async (id) => {
if (!confirm('Are you sure you want to delete this profile? parameters and mods configuration will be lost.')) return;
try {
await window.electronAPI.profile.delete(id);
await loadProfiles();
} catch (error) {
console.error('Failed to delete profile:', error);
alert('Failed to delete profile: ' + error.message);
}
};
window.switchProfile = async (id) => {
try {
if (window.LauncherUI) window.LauncherUI.showProgress();
if (window.LauncherUI) window.LauncherUI.updateProgress({ message: 'Switching Profile...' });
await window.electronAPI.profile.activate(id);
// Refresh UI
await loadProfiles();
// Refresh Mods
if (window.modsManager) {
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();
if (window.modsManager.loadBrowseMods) await window.modsManager.loadBrowseMods();
}
// Close dropdown
const dropdown = document.getElementById('profileDropdown');
if (dropdown) dropdown.classList.remove('show');
if (window.LauncherUI) {
window.LauncherUI.updateProgress({ message: 'Profile Switched!' });
setTimeout(() => window.LauncherUI.hideProgress(), 1000);
}
} catch (error) {
console.error('Failed to switch profile:', error);
alert('Failed to switch profile: ' + error.message);
if (window.LauncherUI) window.LauncherUI.hideProgress();
}
};
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.LauncherUI) window.LauncherUI.updateProgress({ message: 'Starting game...' });
if (window.electronAPI && window.electronAPI.launchGame) {
const result = await window.electronAPI.launchGame(playerName, javaPath, '');
isDownloading = false;
if (window.LauncherUI) {
window.LauncherUI.hideProgress();
}
resetPlayButton();
if (result.success) {
if (window.electronAPI.minimizeWindow) {
setTimeout(() => {
@@ -79,7 +243,7 @@ export async function launch() {
}
} else {
isDownloading = false;
if (window.LauncherUI) {
window.LauncherUI.hideProgress();
}
@@ -87,7 +251,7 @@ export async function launch() {
}
} catch (error) {
isDownloading = false;
if (window.LauncherUI) {
window.LauncherUI.hideProgress();
}
@@ -231,15 +395,15 @@ export async function uninstallGame() {
}
async function performUninstall() {
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!' });
@@ -304,7 +468,7 @@ async function saveJavaPath() {
function toggleCustomJava() {
if (!customJavaOptions) return;
if (customJavaCheck && customJavaCheck.checked) {
customJavaOptions.style.display = 'block';
} else {

View File

@@ -22,10 +22,10 @@ function setupModsEventListeners() {
let searchTimeout;
searchInput.addEventListener('input', (e) => {
searchQuery = e.target.value.toLowerCase().trim();
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
modsPage = 0;
modsPage = 0;
loadBrowseMods();
}, 500);
});
@@ -52,7 +52,7 @@ function setupModsEventListeners() {
const prevPageBtn = document.getElementById('prevPage');
const nextPageBtn = document.getElementById('nextPage');
if (prevPageBtn) {
prevPageBtn.addEventListener('click', () => {
if (modsPage > 0) {
@@ -61,7 +61,7 @@ function setupModsEventListeners() {
}
});
}
if (nextPageBtn) {
nextPageBtn.addEventListener('click', () => {
if (modsPage < modsTotalPages - 1) {
@@ -97,7 +97,7 @@ async function loadInstalledMods() {
const mods = await window.electronAPI?.loadInstalledMods(modsPath);
installedMods = mods || [];
displayInstalledMods(installedMods);
} catch (error) {
console.error('Error loading installed mods:', error);
@@ -196,13 +196,13 @@ async function loadBrowseMods() {
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,
@@ -221,7 +221,7 @@ async function loadBrowseMods() {
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,
@@ -282,18 +282,25 @@ function displayBrowseMods(mods) {
}
function createBrowseModCard(mod) {
const isInstalled = installedMods.some(installed =>
installed.name.toLowerCase().includes(mod.name.toLowerCase()) ||
installed.curseForgeId == mod.id
);
const isInstalled = installedMods.some(installed => {
// Check by CurseForge ID (most reliable)
if (installed.curseForgeId && installed.curseForgeId.toString() === mod.id.toString()) {
return true;
}
// Check by exact name match for manually installed mods
if (installed.name.toLowerCase() === mod.name.toLowerCase()) {
return true;
}
return false;
});
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>`
}
${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\">
@@ -319,16 +326,16 @@ function createBrowseModCard(mod) {
<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\">
${!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>
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
<i class=\"fas fa-check\"></i>
INSTALLED
</button>`
}
}
</div>
</div>
`;
@@ -337,9 +344,9 @@ function createBrowseModCard(mod) {
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,
@@ -354,11 +361,11 @@ async function downloadAndInstallMod(modInfo) {
curseForgeId: modInfo.modId,
curseForgeFileId: modInfo.fileId
};
installedMods.push(newMod);
await loadInstalledMods();
await loadBrowseMods();
await loadBrowseMods();
window.LauncherUI?.hideProgress();
showNotification(`${modInfo.name} installed successfully! 🎉`, 'success');
} else {
@@ -374,10 +381,10 @@ async function downloadAndInstallMod(modInfo) {
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();
@@ -400,13 +407,13 @@ async function deleteMod(modId) {
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();
await loadBrowseMods();
window.LauncherUI?.hideProgress();
showNotification(`"${mod.name}" deleted successfully`, 'success');
} else {
@@ -436,21 +443,21 @@ function showNotification(message, type = 'info', duration = 4000) {
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',
error: '#ef4444',
info: '#3b82f6',
warning: '#f59e0b'
};
notification.innerHTML = `
<div class="notification-content">
<i class="fas ${icons[type]}"></i>
@@ -460,7 +467,7 @@ function showNotification(message, type = 'info', duration = 4000) {
<i class="fas fa-times"></i>
</button>
`;
notification.style.cssText = `
position: fixed;
top: 20px;
@@ -481,14 +488,14 @@ function showNotification(message, type = 'info', duration = 4000) {
font-size: 14px;
font-weight: 500;
`;
const contentStyle = `
display: flex;
align-items: center;
gap: 10px;
flex: 1;
`;
const closeStyle = `
background: none;
border: none;
@@ -500,17 +507,17 @@ function showNotification(message, type = 'info', duration = 4000) {
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) {
@@ -651,13 +658,13 @@ function updatePagination() {
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';
@@ -681,7 +688,7 @@ function showInstalledModsError(message) {
function viewModPage(modId) {
console.log('Looking for mod with ID:', modId, 'Type:', typeof modId);
console.log('Available mods:', browseMods.map(m => ({ id: m.id, name: m.name, type: typeof m.id })));
const mod = browseMods.find(m => m.id.toString() === modId.toString());
if (mod) {
console.log('Found mod:', mod.name);
@@ -694,9 +701,9 @@ function viewModPage(modId) {
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 {
@@ -717,7 +724,9 @@ window.modsManager = {
deleteMod,
openMyModsModal,
closeMyModsModal,
viewModPage
viewModPage,
loadInstalledMods,
loadBrowseMods
};
document.addEventListener('DOMContentLoaded', initModsManager);

View File

@@ -45,22 +45,32 @@ function handleNavigation() {
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');
const profileSelector = document.querySelector('.profile-selector');
if (profileSelector) {
profileSelector.style.pointerEvents = 'auto';
profileSelector.style.zIndex = '10000';
}
if (windowControls) {
windowControls.style.pointerEvents = 'auto';
windowControls.style.zIndex = '10000';
}
if (header) {
header.style.webkitAppRegion = 'drag';
if (windowControls) {
windowControls.style.webkitAppRegion = 'no-drag';
}
if (profileSelector) {
profileSelector.style.webkitAppRegion = 'no-drag';
}
}
if (window.electronAPI) {
if (minimizeBtn) {
minimizeBtn.onclick = (e) => {
@@ -82,7 +92,7 @@ function showLauncherOrInstall(isInstalled) {
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';
@@ -137,14 +147,14 @@ 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);
@@ -157,13 +167,13 @@ function updateProgress(data) {
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 {
@@ -182,7 +192,7 @@ function setupAnimations() {
function setupFirstLaunchHandlers() {
console.log('Setting up first launch handlers...');
window.electronAPI.onFirstLaunchUpdate((data) => {
console.log('Received first launch update event:', data);
showFirstLaunchUpdateDialog(data);
@@ -195,12 +205,12 @@ function setupFirstLaunchHandlers() {
showProgress();
updateProgress(data);
});
let lockButtonTimeout = null;
window.electronAPI.onLockPlayButton((locked) => {
lockPlayButton(locked);
if (locked) {
if (lockButtonTimeout) {
clearTimeout(lockButtonTimeout);
@@ -221,12 +231,12 @@ function setupFirstLaunchHandlers() {
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 = `
@@ -243,7 +253,7 @@ function showFirstLaunchUpdateDialog(data) {
justify-content: center !important;
pointer-events: all !important;
`;
const modalDialog = document.createElement('div');
modalDialog.className = 'first-launch-modal-dialog';
modalDialog.style.cssText = `
@@ -257,7 +267,7 @@ function showFirstLaunchUpdateDialog(data) {
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;">
@@ -306,9 +316,9 @@ function showFirstLaunchUpdateDialog(data) {
</button>
</div>
`;
modalOverlay.appendChild(modalDialog);
modalOverlay.onclick = (e) => {
if (e.target === modalOverlay) {
e.preventDefault();
@@ -316,7 +326,7 @@ function showFirstLaunchUpdateDialog(data) {
return false;
}
};
document.addEventListener('keydown', function preventEscape(e) {
if (e.key === 'Escape') {
e.preventDefault();
@@ -324,33 +334,33 @@ function showFirstLaunchUpdateDialog(data) {
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')) {
@@ -358,21 +368,21 @@ function lockPlayButton(locked) {
}
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');
}
}
@@ -381,12 +391,12 @@ function lockPlayButton(locked) {
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';
@@ -397,21 +407,21 @@ async function acceptFirstLaunchUpdate() {
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');
@@ -434,7 +444,7 @@ function dismissFirstLaunchDialog() {
if (modal) {
modal.remove();
}
lockPlayButton(false);
window.electronAPI.markAsLaunched && window.electronAPI.markAsLaunched();
}
@@ -443,13 +453,13 @@ 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);
@@ -463,9 +473,9 @@ function setupUI() {
progressPercent = document.getElementById('progressPercent');
progressSpeed = document.getElementById('progressSpeed');
progressSize = document.getElementById('progressSize');
lockPlayButton(true);
setTimeout(() => {
const playButton = document.getElementById('homePlayBtn');
if (playButton && playButton.getAttribute('data-locked') === 'true') {
@@ -476,13 +486,13 @@ function setupUI() {
}
}
}, 25000);
handleNavigation();
setupWindowControls();
setupSidebarLogo();
setupAnimations();
setupFirstLaunchHandlers();
document.body.focus();
}