paginate mods area and add a search mods feature

This commit is contained in:
MCHost
2025-06-23 18:35:09 -04:00
parent 3c80228324
commit 53678a76ae
2 changed files with 136 additions and 34 deletions

View File

@ -10,6 +10,8 @@ document.addEventListener('DOMContentLoaded', () => {
let currentPage = 1; let currentPage = 1;
let totalResults = 0; let totalResults = 0;
const resultsPerPage = 10; const resultsPerPage = 10;
let modListCurrentPage = 1;
let modListSearchQuery = '';
let allProperties = {}; let allProperties = {};
const filteredSettings = [ const filteredSettings = [
'bug-report-link', 'bug-report-link',
@ -79,7 +81,10 @@ document.addEventListener('DOMContentLoaded', () => {
connectionStatus: document.getElementById('connectionStatus'), connectionStatus: document.getElementById('connectionStatus'),
geyserStatus: document.getElementById('geyserStatus'), geyserStatus: document.getElementById('geyserStatus'),
sftpStatus: document.getElementById('sftpStatus'), sftpStatus: document.getElementById('sftpStatus'),
backupBtn: document.getElementById('backupBtn') backupBtn: document.getElementById('backupBtn'),
modListSearch: document.getElementById('modListSearch'),
clearModListSearch: document.getElementById('clearModListSearch'),
modListPagination: document.getElementById('modListPagination')
}; };
const loadouts = { const loadouts = {
@ -162,11 +167,11 @@ document.addEventListener('DOMContentLoaded', () => {
connectionStatus: '', connectionStatus: '',
geyserStatus: '', geyserStatus: '',
sftpStatus: '', sftpStatus: '',
activeNotifications: new Map() // Track active notifications to prevent duplicates activeNotifications: new Map(),
allMods: []
}; };
function showNotification(message, type = 'loading', key = null) { function showNotification(message, type = 'loading', key = null) {
// If a notification for this key exists, remove it
if (key && state.activeNotifications.has(key)) { if (key && state.activeNotifications.has(key)) {
const existing = state.activeNotifications.get(key); const existing = state.activeNotifications.get(key);
existing.notification.style.opacity = '0'; existing.notification.style.opacity = '0';
@ -200,7 +205,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
function updateNotification(notification, message, type, key = null) { function updateNotification(notification, message, type, key = null) {
// Ensure only one notification per key
if (key && state.activeNotifications.has(key) && state.activeNotifications.get(key).notification !== notification) { if (key && state.activeNotifications.has(key) && state.activeNotifications.get(key).notification !== notification) {
const existing = state.activeNotifications.get(key); const existing = state.activeNotifications.get(key);
existing.notification.style.opacity = '0'; existing.notification.style.opacity = '0';
@ -407,7 +411,7 @@ document.addEventListener('DOMContentLoaded', () => {
} }
const requestId = crypto.randomUUID(); const requestId = crypto.randomUUID();
const action = endpoint.replace('/', '').replace('-', ' '); const action = endpoint.replace('/', '').replace('-', ' ');
const key = `action-${action}`; // Unique key per action type const key = `action-${action}`;
const notification = showNotification(`Processing ${action}...`, 'loading', key); const notification = showNotification(`Processing ${action}...`, 'loading', key);
pendingRequests.set(requestId, { resolve, reject, notification, key }); pendingRequests.set(requestId, { resolve, reject, notification, key });
ws.send(JSON.stringify({ type: 'request', requestId, endpoint, method, body })); ws.send(JSON.stringify({ type: 'request', requestId, endpoint, method, body }));
@ -819,32 +823,8 @@ document.addEventListener('DOMContentLoaded', () => {
} }
if (message.type === 'mod-list' && message.data?.mods) { if (message.type === 'mod-list' && message.data?.mods) {
const modListHtml = message.data.mods.map(mod => ` state.allMods = message.data.mods || [];
<div class="bg-gray-700 p-4 rounded"> renderModList();
<p><strong>${mod.name}</strong> (${mod.version})</p>
<p>ID: ${mod.id}</p>
<button class="uninstall-mod bg-red-600 hover:bg-red-700 px-2 py-1 rounded mt-2" data-mod-id="${mod.id}">Uninstall</button>
</div>
`).join('');
if (state.modListHtml !== modListHtml && elements.modList) {
elements.modList.innerHTML = modListHtml;
state.modListHtml = modListHtml;
document.querySelectorAll('.uninstall-mod').forEach(button => {
button.addEventListener('click', async () => {
const modId = button.getAttribute('data-mod-id');
const key = `action-uninstall-mod-${modId}`;
try {
await wsRequest('/uninstall', 'POST', { mod: modId });
const response = await wsRequest('/mod-list');
updateNonDockerUI({ type: 'mod-list', data: response });
showNotification(`Mod ${modId} uninstalled successfully`, 'success', key);
} catch (error) {
console.error('Uninstall mod error:', error);
showNotification(`Failed to uninstall mod ${modId}: ${error.message}`, 'error', key);
}
});
});
}
} }
if (message.type === 'log' && message.data?.message) { if (message.type === 'log' && message.data?.message) {
@ -1020,6 +1000,41 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
function updateModListPagination() {
const filteredMods = state.allMods.filter(mod =>
mod.name.toLowerCase().includes(modListSearchQuery.toLowerCase())
);
const totalModPages = Math.max(1, Math.ceil(filteredMods.length / resultsPerPage));
elements.modListPagination.innerHTML = '';
const createPageButton = (page, text, disabled = false) => {
const button = document.createElement('button');
button.textContent = text;
button.className = `px-3 py-1 rounded ${disabled || page === modListCurrentPage
? 'bg-gray-600 cursor-not-allowed'
: 'bg-blue-600 hover:bg-blue-700'
}`;
if (!disabled && page !== modListCurrentPage) {
button.addEventListener('click', () => {
modListCurrentPage = page;
renderModList();
});
}
elements.modListPagination.appendChild(button);
};
if (filteredMods.length > 0) {
createPageButton(modListCurrentPage - 1, 'Previous', modListCurrentPage === 1);
const maxButtons = 5;
const startPage = Math.max(1, modListCurrentPage - Math.floor(maxButtons / 2));
const endPage = Math.min(totalModPages, startPage + maxButtons - 1);
for (let i = startPage; i <= endPage; i++) {
createPageButton(i, i.toString());
}
createPageButton(modListCurrentPage + 1, 'Next', modListCurrentPage === totalModPages);
}
}
function closeSearch() { function closeSearch() {
elements.modSearch.value = ''; elements.modSearch.value = '';
elements.modResults.innerHTML = ''; elements.modResults.innerHTML = '';
@ -1029,6 +1044,27 @@ document.addEventListener('DOMContentLoaded', () => {
totalResults = 0; totalResults = 0;
} }
function clearModListSearch() {
elements.modListSearch.value = '';
modListSearchQuery = '';
modListCurrentPage = 1;
elements.clearModListSearch.classList.add('hidden');
renderModList();
}
// Debounce function to limit the rate of search execution
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
async function searchMods(page = currentPage) { async function searchMods(page = currentPage) {
currentPage = page; currentPage = page;
const mod = elements.modSearch.value.trim(); const mod = elements.modSearch.value.trim();
@ -1076,6 +1112,47 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
function renderModList() {
const filteredMods = state.allMods.filter(mod =>
mod.name.toLowerCase().includes(modListSearchQuery.toLowerCase())
);
const startIndex = (modListCurrentPage - 1) * resultsPerPage;
const endIndex = startIndex + resultsPerPage;
const paginatedMods = filteredMods.slice(startIndex, endIndex);
const modListHtml = paginatedMods.length > 0
? paginatedMods.map(mod => `
<div class="bg-gray-700 p-4 rounded">
<p><strong>${mod.name}</strong> (${mod.version})</p>
<p>ID: ${mod.id}</p>
<button class="uninstall-mod bg-red-600 hover:bg-red-700 px-2 py-1 rounded mt-2" data-mod-id="${mod.id}">Uninstall</button>
</div>
`).join('')
: '<p class="text-gray-400">No mods found.</p>';
if (state.modListHtml !== modListHtml && elements.modList) {
elements.modList.innerHTML = modListHtml;
state.modListHtml = modListHtml;
document.querySelectorAll('.uninstall-mod').forEach(button => {
button.addEventListener('click', async () => {
const modId = button.getAttribute('data-mod-id');
const key = `action-uninstall-mod-${modId}`;
try {
await wsRequest('/uninstall', 'POST', { mod: modId });
const response = await wsRequest('/mod-list');
updateNonDockerUI({ type: 'mod-list', data: response });
showNotification(`Mod ${modId} uninstalled successfully`, 'success', key);
} catch (error) {
console.error('Uninstall mod error:', error);
showNotification(`Failed to uninstall mod ${modId}: ${error.message}`, 'error', key);
}
});
});
}
updateModListPagination();
}
async function sendConsoleCommand() { async function sendConsoleCommand() {
const command = elements.consoleInput.value.trim(); const command = elements.consoleInput.value.trim();
if (command) { if (command) {
@ -1330,18 +1407,19 @@ document.addEventListener('DOMContentLoaded', () => {
const key = `action-update-mods`; const key = `action-update-mods`;
const response = await wsRequest('/update-mods', 'POST'); const response = await wsRequest('/update-mods', 'POST');
if (response.error) { if (response.error) {
updateNotification(notification, `Failed to update mods: ${response.error}`, 'error', key);
elements.updateModsOutput.textContent = `Error: ${response.error}`; elements.updateModsOutput.textContent = `Error: ${response.error}`;
showNotification(`Failed to update mods: ${response.error}`, 'error', key);
} else { } else {
const output = response.output || 'No output from mod update.'; const output = response.output || 'No output from mod update.';
elements.updateModsOutput.textContent = output; elements.updateModsOutput.textContent = output;
showNotification('Mods updated successfully', 'success', key);
} }
elements.updateModsModal.classList.remove('hidden'); elements.updateModsModal.classList.remove('hidden');
} catch (error) { } catch (error) {
console.error('Update mods error:', error); console.error('Update mods error:', error);
showNotification(`Failed to update mods: ${error.message}`, 'error', 'update-mods-error');
elements.updateModsOutput.textContent = `Error: ${error.message}`; elements.updateModsOutput.textContent = `Error: ${error.message}`;
elements.updateModsModal.classList.remove('hidden'); elements.updateModsModal.classList.remove('hidden');
showNotification(`Failed to update mods: ${error.message}`, 'error', 'update-mods-error');
} }
} }
@ -1390,6 +1468,7 @@ document.addEventListener('DOMContentLoaded', () => {
try { try {
const key = `action-my-link`; const key = `action-my-link`;
await wsRequest('/my-link'); await wsRequest('/my-link');
showNotification('Connection link generated successfully', 'success', key);
} catch (error) { } catch (error) {
console.error('Generate connection link error:', error); console.error('Generate connection link error:', error);
showNotification(`Failed to generate connection link: ${error.message}`, 'error', 'my-link-error'); showNotification(`Failed to generate connection link: ${error.message}`, 'error', 'my-link-error');
@ -1400,6 +1479,7 @@ document.addEventListener('DOMContentLoaded', () => {
try { try {
const key = `action-my-geyser-link`; const key = `action-my-geyser-link`;
await wsRequest('/my-geyser-link'); await wsRequest('/my-geyser-link');
showNotification('Geyser link generated successfully', 'success', key);
} catch (error) { } catch (error) {
console.error('Generate geyser link error:', error); console.error('Generate geyser link error:', error);
showNotification(`Failed to generate Geyser link: ${error.message}`, 'error', 'geyser-link-error'); showNotification(`Failed to generate Geyser link: ${error.message}`, 'error', 'geyser-link-error');
@ -1410,6 +1490,7 @@ document.addEventListener('DOMContentLoaded', () => {
try { try {
const key = `action-my-sftp`; const key = `action-my-sftp`;
await wsRequest('/my-sftp'); await wsRequest('/my-sftp');
showNotification('SFTP link generated successfully', 'success', key);
} catch (error) { } catch (error) {
console.error('Generate SFTP link error:', error); console.error('Generate SFTP link error:', error);
showNotification(`Failed to generate SFTP link: ${error.message}`, 'error', 'sftp-link-error'); showNotification(`Failed to generate SFTP link: ${error.message}`, 'error', 'sftp-link-error');
@ -1601,6 +1682,20 @@ document.addEventListener('DOMContentLoaded', () => {
saveServerProperties(); saveServerProperties();
}); });
elements.clearModListSearch.addEventListener('click', clearModListSearch);
// Debounced search for installed mods
const debouncedModListSearch = debounce((query) => {
modListSearchQuery = query.trim();
modListCurrentPage = 1;
elements.clearModListSearch.classList.toggle('hidden', !modListSearchQuery);
renderModList();
}, 300);
elements.modListSearch.addEventListener('input', (e) => {
debouncedModListSearch(e.target.value);
});
if (apiKey) { if (apiKey) {
connectWebSocket(); connectWebSocket();
} else { } else {

View File

@ -187,7 +187,14 @@
<div id="modResults" class="grid grid-cols-1 md:grid-cols-2 gap-4"></div> <div id="modResults" class="grid grid-cols-1 md:grid-cols-2 gap-4"></div>
<div id="pagination" class="mt-4 flex justify-center space-x-2"></div> <div id="pagination" class="mt-4 flex justify-center space-x-2"></div>
<h3 class="text-lg font-semibold mt-4">Installed Mods</h3> <h3 class="text-lg font-semibold mt-4">Installed Mods</h3>
<div id="modList" class="mt-2"></div> <div class="mb-4 flex space-x-2">
<input id="modListSearch" type="text" placeholder="Search Installed Mods"
class="bg-gray-700 px-4 py-2 rounded text-white flex-grow">
<button id="clearModListSearch" type="button"
class="bg-gray-600 hover:bg-gray-700 px-4 py-2 rounded hidden">Clear</button>
</div>
<div id="modList" class="mt-2 grid grid-cols-1 md:grid-cols-2 gap-4"></div>
<div id="modListPagination" class="mt-4 flex justify-center space-x-2"></div>
</div> </div>
<div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6"> <div class="bg-gray-800 p-6 rounded-lg shadow-lg mb-6">