mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-26 06:51:47 -03:00
Compare commits
5 Commits
v2.4.1
...
5170f453ea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5170f453ea | ||
|
|
db3b2fc966 | ||
|
|
2f5820e850 | ||
|
|
4abb455e0f | ||
|
|
d5828463f9 |
@@ -663,7 +663,11 @@
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</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>
|
||||
</div>
|
||||
@@ -867,6 +871,25 @@
|
||||
<script src="js/featured.js"></script>
|
||||
<script type="module" src="js/settings.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 -->
|
||||
</body>
|
||||
|
||||
|
||||
184
GUI/js/mods.js
184
GUI/js/mods.js
@@ -49,6 +49,18 @@ function setupModsEventListeners() {
|
||||
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');
|
||||
if (modal) {
|
||||
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() {
|
||||
const modal = document.getElementById('myModsModal');
|
||||
if (modal) {
|
||||
modal.classList.add('active');
|
||||
const searchInput = document.getElementById('myModsSearch');
|
||||
if (searchInput) {
|
||||
searchInput.value = '';
|
||||
}
|
||||
loadInstalledMods();
|
||||
}
|
||||
}
|
||||
@@ -92,6 +122,10 @@ function closeMyModsModal() {
|
||||
const modal = document.getElementById('myModsModal');
|
||||
if (modal) {
|
||||
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) {
|
||||
const modsContainer = document.getElementById('installedModsList');
|
||||
if (!modsContainer) return;
|
||||
|
||||
if (mods.length === 0) {
|
||||
const searchInput = document.getElementById('myModsSearch');
|
||||
const isSearching = searchInput && searchInput.value.trim() !== '';
|
||||
|
||||
modsContainer.innerHTML = `
|
||||
<div class=\"empty-installed-mods\">
|
||||
<i class=\"fas fa-box-open\"></i>
|
||||
<h4 data-i18n="mods.noModsInstalled">No Mods Installed</h4>
|
||||
<p data-i18n="mods.noModsInstalledDesc">Add mods from CurseForge or import local files</p>
|
||||
<i class=\"fas fa-${isSearching ? 'search' : 'box-open'}\"></i>
|
||||
<h4 data-i18n="${isSearching ? 'mods.noModsFound' : 'mods.noModsInstalled'}">${isSearching ? 'No Mods Found' : 'No Mods Installed'}</h4>
|
||||
<p data-i18n="${isSearching ? 'mods.noModsFoundDesc' : 'mods.noModsInstalledDesc'}">${isSearching ? 'Try a different search term' : 'Add mods from CurseForge or import local files'}</p>
|
||||
</div>
|
||||
`;
|
||||
if (window.i18n) {
|
||||
if (window.i18n && !isSearching) {
|
||||
const container = modsContainer.querySelector('.empty-installed-mods');
|
||||
container.querySelector('h4').textContent = window.i18n.t('mods.noModsInstalled');
|
||||
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-header">
|
||||
<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>
|
||||
<p class="installed-mod-description">${mod.description || (window.i18n ? window.i18n.t('mods.noDescription') : 'No description available')}</p>
|
||||
</div>
|
||||
@@ -295,13 +349,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 +397,12 @@ function createBrowseModCard(mod) {
|
||||
${window.i18n ? window.i18n.t('mods.view') : '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>
|
||||
`<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>
|
||||
${window.i18n ? window.i18n.t('mods.install') : 'INSTALL'}
|
||||
</button>` :
|
||||
`<button class="mod-btn-toggle bg-white/10 text-white" disabled>
|
||||
<i class="fas fa-check"></i>
|
||||
`<button class=\"mod-btn-toggle bg-white/10 text-white\" disabled>
|
||||
<i class=\"fas fa-check\"></i>
|
||||
${window.i18n ? window.i18n.t('mods.installed') : 'INSTALLED'}
|
||||
</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) {
|
||||
try {
|
||||
const downloadMsg = window.i18n ? window.i18n.t('notifications.modsDownloading').replace('{name}', modInfo.name) : `Downloading ${modInfo.name}...`;
|
||||
@@ -762,7 +907,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);
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
@@ -6610,3 +6610,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;
|
||||
}
|
||||
@@ -84,7 +84,8 @@ const {
|
||||
loadInstalledMods,
|
||||
downloadMod,
|
||||
uninstallMod,
|
||||
toggleMod
|
||||
toggleMod,
|
||||
getModFiles
|
||||
} = require('./managers/modManager');
|
||||
|
||||
// Services
|
||||
@@ -187,6 +188,7 @@ module.exports = {
|
||||
downloadMod,
|
||||
uninstallMod,
|
||||
toggleMod,
|
||||
getModFiles,
|
||||
saveModsToConfig,
|
||||
loadModsFromConfig,
|
||||
|
||||
|
||||
@@ -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
|
||||
};
|
||||
11
main.js
11
main.js
@@ -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');
|
||||
|
||||
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) => {
|
||||
try {
|
||||
return await uninstallMod(modId, modsPath);
|
||||
|
||||
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user