Compare commits

...

5 Commits

Author SHA1 Message Date
amiay
5170f453ea Merge pull request 'added search bar in "my mods"' (#3) from amiay/hytale-f2p:develop into develop
Reviewed-on: https://git.sanhost.net/sanasol/hytale-f2p/pulls/3
2026-02-24 16:31:54 +00:00
amiay
db3b2fc966 added search bar in my mods
added search bar in my mods
2026-02-24 17:31:13 +01:00
amiay
2f5820e850 Merge pull request 'Mods update' (#2) from amiay/hytale-f2p:develop into develop
Reviewed-on: https://git.sanhost.net/sanasol/hytale-f2p/pulls/2
2026-02-24 16:22:55 +00:00
dsqd2505-netizen
4abb455e0f update "My mods" 2026-02-24 17:15:47 +01:00
dsqd2505-netizen
d5828463f9 Added mods version selector
Added mods version selector
2026-02-24 17:04:46 +01:00
8 changed files with 317 additions and 24 deletions

View File

@@ -663,7 +663,11 @@
<i class="fas fa-times"></i> <i class="fas fa-times"></i>
</button> </button>
</div> </div>
<div class="mods-modal-body"> <div class="mods-modal-body" style="padding-top: 0;">
<div class="mods-search-container" style="margin: 1.5rem; margin-bottom: 1rem;">
<i class="fas fa-search"></i>
<input type="text" id="myModsSearch" placeholder="Search installed mods..." class="mods-search" />
</div>
<div id="installedModsList" class="installed-mods-list"> <div id="installedModsList" class="installed-mods-list">
</div> </div>
</div> </div>
@@ -867,6 +871,25 @@
<script src="js/featured.js"></script> <script src="js/featured.js"></script>
<script type="module" src="js/settings.js"></script> <script type="module" src="js/settings.js"></script>
<script type="module" src="js/update.js"></script> <script type="module" src="js/update.js"></script>
<!-- Version Selection Modal (Isolated Container) -->
<div id="versionSelectModal" class="modal-overlay" style="display: none; position: fixed; inset: 0; z-index: 9999; background: rgba(0, 0, 0, 0.7); backdrop-filter: blur(5px); align-items: center; justify-content: center;">
<div class="glass-panel" style="width: 100%; max-width: 600px; max-height: 80vh; display: flex; flex-direction: column; border-radius: 12px; overflow: hidden; margin: 20px;">
<div class="modal-header" style="display: flex; justify-content: space-between; align-items: center; padding: 1.5rem; border-bottom: 1px solid rgba(255, 255, 255, 0.1);">
<h3 style="margin: 0; font-size: 1.25rem;">Select Version</h3>
<button id="closeVersionModal" class="modal-close" style="background: none; border: none; color: #a0a0a0; font-size: 1.25rem; cursor: pointer;"><i class="fas fa-times"></i></button>
</div>
<div class="modal-body" style="padding: 1.5rem; overflow-y: auto;">
<div id="versionList" class="version-list-container">
<div class="loading-versions" style="display: flex; flex-direction: column; align-items: center; gap: 1rem; color: #a0a0a0;">
<i class="fas fa-spinner fa-spin fa-2x"></i>
<span>Loading versions...</span>
</div>
</div>
</div>
</div>
</div>
<!-- updater.js disabled - using update.js instead which has skip button and macOS handling --> <!-- updater.js disabled - using update.js instead which has skip button and macOS handling -->
</body> </body>

View File

@@ -49,6 +49,18 @@ function setupModsEventListeners() {
closeModalBtn.addEventListener('click', closeMyModsModal); closeModalBtn.addEventListener('click', closeMyModsModal);
} }
const myModsSearchInput = document.getElementById('myModsSearch');
if (myModsSearchInput) {
let myModsSearchTimeout;
myModsSearchInput.addEventListener('input', (e) => {
const query = e.target.value.toLowerCase().trim();
clearTimeout(myModsSearchTimeout);
myModsSearchTimeout = setTimeout(() => {
filterInstalledMods(query);
}, 300);
});
}
const modal = document.getElementById('myModsModal'); const modal = document.getElementById('myModsModal');
if (modal) { if (modal) {
modal.addEventListener('click', (e) => { modal.addEventListener('click', (e) => {
@@ -78,12 +90,30 @@ 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() { function openMyModsModal() {
const modal = document.getElementById('myModsModal'); const modal = document.getElementById('myModsModal');
if (modal) { if (modal) {
modal.classList.add('active'); modal.classList.add('active');
const searchInput = document.getElementById('myModsSearch');
if (searchInput) {
searchInput.value = '';
}
loadInstalledMods(); loadInstalledMods();
} }
} }
@@ -92,6 +122,10 @@ function closeMyModsModal() {
const modal = document.getElementById('myModsModal'); const modal = document.getElementById('myModsModal');
if (modal) { if (modal) {
modal.classList.remove('active'); modal.classList.remove('active');
const searchInput = document.getElementById('myModsSearch');
if (searchInput) {
searchInput.value = '';
}
} }
} }
@@ -113,19 +147,39 @@ async function loadInstalledMods() {
} }
} }
function filterInstalledMods(query) {
if (!query || query === '') {
displayInstalledMods(installedMods);
return;
}
const filtered = installedMods.filter(mod => {
const nameMatch = mod.name?.toLowerCase().includes(query);
const fileNameMatch = mod.fileName?.toLowerCase().includes(query);
const descriptionMatch = mod.description?.toLowerCase().includes(query);
const authorMatch = mod.author?.toLowerCase().includes(query);
return nameMatch || fileNameMatch || descriptionMatch || authorMatch;
});
displayInstalledMods(filtered);
}
function displayInstalledMods(mods) { function displayInstalledMods(mods) {
const modsContainer = document.getElementById('installedModsList'); const modsContainer = document.getElementById('installedModsList');
if (!modsContainer) return; if (!modsContainer) return;
if (mods.length === 0) { if (mods.length === 0) {
const searchInput = document.getElementById('myModsSearch');
const isSearching = searchInput && searchInput.value.trim() !== '';
modsContainer.innerHTML = ` modsContainer.innerHTML = `
<div class=\"empty-installed-mods\"> <div class=\"empty-installed-mods\">
<i class=\"fas fa-box-open\"></i> <i class=\"fas fa-${isSearching ? 'search' : 'box-open'}\"></i>
<h4 data-i18n="mods.noModsInstalled">No Mods Installed</h4> <h4 data-i18n="${isSearching ? 'mods.noModsFound' : 'mods.noModsInstalled'}">${isSearching ? 'No Mods Found' : 'No Mods Installed'}</h4>
<p data-i18n="mods.noModsInstalledDesc">Add mods from CurseForge or import local files</p> <p data-i18n="${isSearching ? 'mods.noModsFoundDesc' : 'mods.noModsInstalledDesc'}">${isSearching ? 'Try a different search term' : 'Add mods from CurseForge or import local files'}</p>
</div> </div>
`; `;
if (window.i18n) { if (window.i18n && !isSearching) {
const container = modsContainer.querySelector('.empty-installed-mods'); const container = modsContainer.querySelector('.empty-installed-mods');
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled'); container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc'); container.querySelector('p').textContent = window.i18n.t('mods.noModsInstalledDesc');
@@ -165,7 +219,7 @@ function createInstalledModCard(mod) {
<div class="installed-mod-info"> <div class="installed-mod-info">
<div class="installed-mod-header"> <div class="installed-mod-header">
<h4 class="installed-mod-name">${mod.name}</h4> <h4 class="installed-mod-name">${mod.name}</h4>
<span class="installed-mod-version">v${mod.version}</span> <span class="installed-mod-version">${mod.fileName || 'v' + mod.version}</span>
</div> </div>
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p> <p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
</div> </div>
@@ -295,13 +349,6 @@ function displayBrowseMods(mods) {
} }
browseContainer.innerHTML = mods.map(mod => createBrowseModCard(mod)).join(''); 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) { function createBrowseModCard(mod) {
@@ -350,12 +397,12 @@ function createBrowseModCard(mod) {
${window.i18n ? window.i18n.t('mods.view') : 'VIEW'} ${window.i18n ? window.i18n.t('mods.view') : 'VIEW'}
</button> </button>
${!isInstalled ? ${!isInstalled ?
`<button id="install-${mod.id}" class="mod-btn-toggle bg-primary text-black hover:bg-primary/80"> `<button data-install-mod-id=\"${mod.id}\" class=\"mod-btn-toggle bg-primary text-black hover:bg-primary/80\">
<i class="fas fa-download"></i> <i class=\"fas fa-download\"></i>
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'} ${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
</button>` : </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> <i class=\"fas fa-check\"></i>
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'} ${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
</button>` </button>`
} }
@@ -364,6 +411,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 = `
<div class="loading-versions">
<i class="fas fa-spinner fa-spin fa-2x" style="margin-bottom: 10px; display: block;"></i>
<span>Loading versions...</span>
</div>
`;
try {
const versions = await window.electronAPI.getModFiles(modId);
if (!versions || versions.length === 0) {
container.innerHTML = `<div class="p-4 text-center text-gray-400" style="padding: 2rem;">No versions found for this mod.</div>`;
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 => `
<div class="version-item">
<div class="version-info">
<div class="version-name">${file.displayName}</div>
<div class="version-meta">
<span><i class="fas fa-calendar"></i> ${new Date(file.fileDate).toLocaleDateString()}</span>
<span><i class="fas fa-download"></i> ${formatNumber(file.downloadCount)}</span>
<span><i class="fas fa-file-archive"></i> ${(file.fileLength / 1024 / 1024).toFixed(2)} MB</span>
</div>
</div>
<div class="version-actions">
<button class="btn-install" data-file-id="${file.id}">
Install
</button>
</div>
</div>
`).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 = `<div class="p-4 text-center text-red-400" style="padding: 2rem;">Error loading versions.<br><small>${error.message}</small></div>`;
}
}
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) { async function downloadAndInstallMod(modInfo) {
try { try {
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`; const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
@@ -762,7 +907,10 @@ window.modsManager = {
closeMyModsModal, closeMyModsModal,
viewModPage, viewModPage,
loadInstalledMods, 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);

View File

@@ -2,7 +2,7 @@ import './ui.js';
import './install.js'; import './install.js';
import './launcher.js'; import './launcher.js';
import './news.js'; import './news.js';
import './mods.js'; import { initModsManager } from './mods.js';
import './players.js'; import './players.js';
import './settings.js'; import './settings.js';
import './logs.js'; import './logs.js';
@@ -15,6 +15,12 @@ let i18nInitialized = false;
if (document.readyState === 'complete' || document.readyState === 'interactive') { if (document.readyState === 'complete' || document.readyState === 'interactive') {
updateLanguageSelector(); updateLanguageSelector();
initModsManager();
} else {
document.addEventListener('DOMContentLoaded', () => {
updateLanguageSelector();
initModsManager();
});
} }
})(); })();

View File

@@ -6610,3 +6610,85 @@ input[type="text"].uuid-input,
opacity: 0.5; opacity: 0.5;
cursor: not-allowed; 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;
}

View File

@@ -84,7 +84,8 @@ const {
loadInstalledMods, loadInstalledMods,
downloadMod, downloadMod,
uninstallMod, uninstallMod,
toggleMod toggleMod,
getModFiles
} = require('./managers/modManager'); } = require('./managers/modManager');
// Services // Services
@@ -187,6 +188,7 @@ module.exports = {
downloadMod, downloadMod,
uninstallMod, uninstallMod,
toggleMod, toggleMod,
getModFiles,
saveModsToConfig, saveModsToConfig,
loadModsFromConfig, loadModsFromConfig,

View File

@@ -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() { async function syncModsForCurrentProfile() {
try { try {
const activeProfile = profileManager.getActiveProfile(); const activeProfile = profileManager.getActiveProfile();
@@ -455,5 +476,6 @@ module.exports = {
syncModsForCurrentProfile, syncModsForCurrentProfile,
generateModId, generateModId,
extractModName, extractModName,
extractVersion extractVersion,
}; getModFiles
};

11
main.js
View File

@@ -1068,7 +1068,7 @@ ipcMain.handle('load-settings', async () => {
} }
}); });
const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher'); const { getModsPath, loadInstalledMods, downloadMod, uninstallMod, toggleMod, getModFiles, getCurrentUuid, getAllUuidMappings, setUuidForUser, generateNewUuid, deleteUuidForUser, resetCurrentUserUuid } = require('./backend/launcher');
const os = require('os'); const os = require('os');
ipcMain.handle('get-local-app-data', async () => { ipcMain.handle('get-local-app-data', async () => {
@@ -1118,6 +1118,15 @@ ipcMain.handle('download-mod', async (event, modInfo) => {
} }
}); });
ipcMain.handle('get-mod-files', async (event, modId) => {
try {
return await getModFiles(modId);
} catch (error) {
console.error('Error getting mod files:', error);
return { success: false, error: error.message };
}
});
ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => { ipcMain.handle('uninstall-mod', async (event, modId, modsPath) => {
try { try {
return await uninstallMod(modId, modsPath); return await uninstallMod(modId, modsPath);

View File

@@ -45,6 +45,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath), loadInstalledMods: (modsPath) => ipcRenderer.invoke('load-installed-mods', modsPath),
downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo), downloadMod: (modInfo) => ipcRenderer.invoke('download-mod', modInfo),
uninstallMod: (modId, modsPath) => ipcRenderer.invoke('uninstall-mod', modId, modsPath), 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), toggleMod: (modId, modsPath) => ipcRenderer.invoke('toggle-mod', modId, modsPath),
selectModFiles: () => ipcRenderer.invoke('select-mod-files'), selectModFiles: () => ipcRenderer.invoke('select-mod-files'),
copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath), copyModFile: (sourcePath, modsPath) => ipcRenderer.invoke('copy-mod-file', sourcePath, modsPath),