From d5828463f9e2a01f44b54a51bb400bf2c8602adc Mon Sep 17 00:00:00 2001 From: dsqd2505-netizen Date: Tue, 24 Feb 2026 17:04:46 +0100 Subject: [PATCH] Added mods version selector Added mods version selector --- GUI/index.html | 19 +++++ GUI/js/mods.js | 134 +++++++++++++++++++++++++++++---- GUI/js/script.js | 8 +- GUI/style.css | 82 ++++++++++++++++++++ backend/launcher.js | 4 +- backend/managers/modManager.js | 26 ++++++- preload.js | 1 + 7 files changed, 257 insertions(+), 17 deletions(-) diff --git a/GUI/index.html b/GUI/index.html index fe51bbc..ac1f7c9 100644 --- a/GUI/index.html +++ b/GUI/index.html @@ -805,6 +805,25 @@ + + + + diff --git a/GUI/js/mods.js b/GUI/js/mods.js index 0ebda25..ef7e1b7 100644 --- a/GUI/js/mods.js +++ b/GUI/js/mods.js @@ -78,6 +78,20 @@ function setupModsEventListeners() { } }); } + + const browseContainer = document.getElementById('browseModsList'); + if (browseContainer) { + browseContainer.addEventListener('click', (e) => { + const installBtn = e.target.closest('[data-install-mod-id]'); + if (installBtn) { + const modId = installBtn.getAttribute('data-install-mod-id'); + const mod = browseMods.find(m => m.id == modId); + if (mod) { + openVersionSelectModal(mod); + } + } + }); + } } function openMyModsModal() { @@ -295,13 +309,6 @@ function displayBrowseMods(mods) { } 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) { @@ -350,12 +357,12 @@ function createBrowseModCard(mod) { ${window.i18n ? window.i18n.t('mods.view') : 'VIEW'} ${!isInstalled ? - `` : - `` } @@ -364,6 +371,104 @@ function createBrowseModCard(mod) { `; } +let currentSelectedMod = null; + +function openVersionSelectModal(mod) { + currentSelectedMod = mod; + const modal = document.getElementById('versionSelectModal'); + const closeBtn = document.getElementById('closeVersionModal'); + const versionList = document.getElementById('versionList'); + + if (modal) { + modal.style.display = 'flex'; + modal.classList.add('active'); + + const closeHandler = () => { + modal.classList.remove('active'); + setTimeout(() => { + modal.style.display = 'none'; + }, 300); + currentSelectedMod = null; + }; + + if (closeBtn) { + closeBtn.onclick = closeHandler; + } + modal.onclick = (e) => { + if (e.target === modal) closeHandler(); + }; + + loadModVersions(mod.id, versionList); + } +} + +async function loadModVersions(modId, container) { + container.innerHTML = ` +
+ + Loading versions... +
+ `; + + try { + const versions = await window.electronAPI.getModFiles(modId); + + if (!versions || versions.length === 0) { + container.innerHTML = `
No versions found for this mod.
`; + return; + } + + // Sort versions by date desc (API returns desc but ensure) + versions.sort((a, b) => new Date(b.fileDate) - new Date(a.fileDate)); + + container.innerHTML = versions.map(file => ` +
+
+
${file.displayName}
+
+ ${new Date(file.fileDate).toLocaleDateString()} + ${formatNumber(file.downloadCount)} + ${(file.fileLength / 1024 / 1024).toFixed(2)} MB +
+
+
+ +
+
+ `).join(''); + + // Add event listeners securely + container.querySelectorAll('.btn-install').forEach((btn, index) => { + const file = versions[index]; // Map index to file data + btn.onclick = () => installVersion(file); + }); + + } catch (error) { + console.error('Error loading versions:', error); + container.innerHTML = `
Error loading versions.
${error.message}
`; + } +} + +async function installVersion(file) { + if (!currentSelectedMod) return; + + const modal = document.getElementById('versionSelectModal'); + modal.style.display = 'none'; + + const modInfo = { + ...currentSelectedMod, + fileId: file.id, + downloadUrl: file.downloadUrl, + fileName: file.fileName, + fileSize: file.fileLength + }; + + await downloadAndInstallMod(modInfo); + currentSelectedMod = null; +} + async function downloadAndInstallMod(modInfo) { try { const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`; @@ -762,7 +867,10 @@ window.modsManager = { closeMyModsModal, viewModPage, loadInstalledMods, - loadBrowseMods + loadBrowseMods, + openVersionSelectModal }; -document.addEventListener('DOMContentLoaded', initModsManager); +// Remove auto-init since we are now calling it from script.js explicitly +// which guarantees order and environment readiness +// document.addEventListener('DOMContentLoaded', initModsManager); diff --git a/GUI/js/script.js b/GUI/js/script.js index a6bd492..d19e949 100644 --- a/GUI/js/script.js +++ b/GUI/js/script.js @@ -2,7 +2,7 @@ import './ui.js'; import './install.js'; import './launcher.js'; import './news.js'; -import './mods.js'; +import { initModsManager } from './mods.js'; import './players.js'; import './settings.js'; import './logs.js'; @@ -15,6 +15,12 @@ let i18nInitialized = false; if (document.readyState === 'complete' || document.readyState === 'interactive') { updateLanguageSelector(); + initModsManager(); + } else { + document.addEventListener('DOMContentLoaded', () => { + updateLanguageSelector(); + initModsManager(); + }); } })(); diff --git a/GUI/style.css b/GUI/style.css index 2bb344e..02d09e9 100644 --- a/GUI/style.css +++ b/GUI/style.css @@ -6476,3 +6476,85 @@ input[type="text"].uuid-input, opacity: 0.5; cursor: not-allowed; } + +/* Version Selection Styles */ +.version-list-container::-webkit-scrollbar { + width: 6px; +} + +.version-list-container::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + border-radius: 3px; +} + +.version-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem; + background: rgba(255, 255, 255, 0.05); + border: 1px solid rgba(255, 255, 255, 0.1); + border-radius: 8px; + margin-bottom: 0.5rem; + transition: all 0.2s ease; +} + +.version-item:hover { + background: rgba(255, 255, 255, 0.1); + transform: translateX(4px); +} + +.version-info { + display: flex; + flex-direction: column; + gap: 0.25rem; +} + +.version-name { + font-weight: 600; + color: #fff; + font-size: 0.95rem; +} + +.version-meta { + font-size: 0.8rem; + color: #a0a0a0; + display: flex; + gap: 1rem; + align-items: center; +} + +.version-meta span { + display: flex; + align-items: center; + gap: 0.35rem; +} + +.version-meta i { + font-size: 0.8em; + opacity: 0.7; +} + +.version-actions .btn-install { + padding: 0.5rem 1.25rem; + background: linear-gradient(135deg, #3b82f6, #2563eb); + color: white; + border: none; + border-radius: 6px; + cursor: pointer; + font-weight: 600; + font-size: 0.85rem; + transition: all 0.2s; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); +} + +.version-actions .btn-install:hover { + transform: translateY(-1px); + box-shadow: 0 6px 8px rgba(0, 0, 0, 0.3); + background: linear-gradient(135deg, #4f93f6, #3b82f6); +} + +.loading-versions { + padding: 3rem; + text-align: center; +} \ No newline at end of file diff --git a/backend/launcher.js b/backend/launcher.js index 962323a..c9bf427 100644 --- a/backend/launcher.js +++ b/backend/launcher.js @@ -78,7 +78,8 @@ const { loadInstalledMods, downloadMod, uninstallMod, - toggleMod + toggleMod, + getModFiles } = require('./managers/modManager'); // Services @@ -181,6 +182,7 @@ module.exports = { downloadMod, uninstallMod, toggleMod, + getModFiles, saveModsToConfig, loadModsFromConfig, diff --git a/backend/managers/modManager.js b/backend/managers/modManager.js index cb7775e..f125ff8 100644 --- a/backend/managers/modManager.js +++ b/backend/managers/modManager.js @@ -285,6 +285,27 @@ async function toggleMod(modId, modsPath) { } } + +async function getModFiles(modId) { + try { + const response = await axios.get(`https://api.curseforge.com/v1/mods/${modId}/files`, { + headers: { + 'x-api-key': API_KEY, + 'Accept': 'application/json' + }, + params: { + pageSize: 20, + sortOrder: 'desc' + } + }); + + return response.data.data; + } catch (error) { + console.error('Error fetching mod files:', error); + return []; + } +} + async function syncModsForCurrentProfile() { try { const activeProfile = profileManager.getActiveProfile(); @@ -455,5 +476,6 @@ module.exports = { syncModsForCurrentProfile, generateModId, extractModName, - extractVersion -}; + extractVersion, + getModFiles +}; \ No newline at end of file diff --git a/preload.js b/preload.js index 09e5438..53a5fba 100644 --- a/preload.js +++ b/preload.js @@ -45,6 +45,7 @@ contextBridge.exposeInMainWorld('electronAPI', { loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath), downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo), uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath), + getModFiles: (modId) => ipcRenderer.invoke('get-mod-files', modId), toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath), selectModFiles: () => ipcRenderer.invoke('select-mod-files'), copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),