Add Teleport/Effects options to player list

This commit is contained in:
MCHost
2025-06-23 19:15:00 -04:00
parent 53678a76ae
commit f1964e9ef1
4 changed files with 379 additions and 32 deletions

View File

@ -84,7 +84,15 @@ document.addEventListener('DOMContentLoaded', () => {
backupBtn: document.getElementById('backupBtn'), backupBtn: document.getElementById('backupBtn'),
modListSearch: document.getElementById('modListSearch'), modListSearch: document.getElementById('modListSearch'),
clearModListSearch: document.getElementById('clearModListSearch'), clearModListSearch: document.getElementById('clearModListSearch'),
modListPagination: document.getElementById('modListPagination') modListPagination: document.getElementById('modListPagination'),
teleportModal: document.getElementById('teleportModal'),
teleportPlayerName: document.getElementById('teleportPlayerName'),
teleportDestination: document.getElementById('teleportDestination'),
teleportForm: document.getElementById('teleportForm'),
effectModal: document.getElementById('effectModal'),
effectPlayerName: document.getElementById('effectPlayerName'),
effectSelect: document.getElementById('effectSelect'),
effectForm: document.getElementById('effectForm')
}; };
const loadouts = { const loadouts = {
@ -168,7 +176,8 @@ document.addEventListener('DOMContentLoaded', () => {
geyserStatus: '', geyserStatus: '',
sftpStatus: '', sftpStatus: '',
activeNotifications: new Map(), activeNotifications: new Map(),
allMods: [] allMods: [],
currentPlayers: []
}; };
function showNotification(message, type = 'loading', key = null) { function showNotification(message, type = 'loading', key = null) {
@ -646,6 +655,8 @@ document.addEventListener('DOMContentLoaded', () => {
if (message.type === 'list-players' && message.data?.players) { if (message.type === 'list-players' && message.data?.players) {
const players = message.data.players || []; const players = message.data.players || [];
state.currentPlayers = players;
const isSinglePlayer = players.length <= 1;
const playerListHtml = players.length > 0 const playerListHtml = players.length > 0
? players.map(player => ` ? players.map(player => `
<div class="flex items-center space-x-4 mb-2"> <div class="flex items-center space-x-4 mb-2">
@ -653,6 +664,8 @@ document.addEventListener('DOMContentLoaded', () => {
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<button class="tell-player bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm" data-player="${player}">Tell</button> <button class="tell-player bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm" data-player="${player}">Tell</button>
<button class="give-player bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm" data-player="${player}">Give</button> <button class="give-player bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm" data-player="${player}">Give</button>
<button class="teleport-player bg-cyan-600 hover:bg-cyan-700 px-2 py-1 rounded text-sm ${isSinglePlayer ? 'opacity-50 cursor-not-allowed' : ''}" data-player="${player}" ${isSinglePlayer ? 'disabled' : ''}>Teleport</button>
<button class="effect-player bg-teal-600 hover:bg-teal-700 px-2 py-1 rounded text-sm" data-player="${player}">Effect</button>
<button class="op-player bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm" data-player="${player}">Op</button> <button class="op-player bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm" data-player="${player}">Op</button>
<button class="deop-player bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm" data-player="${player}">Deop</button> <button class="deop-player bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm" data-player="${player}">Deop</button>
<button class="kick-player bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm" data-player="${player}">Kick</button> <button class="kick-player bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm" data-player="${player}">Kick</button>
@ -664,6 +677,66 @@ document.addEventListener('DOMContentLoaded', () => {
if (state.playerList !== playerListHtml && elements.playerList) { if (state.playerList !== playerListHtml && elements.playerList) {
elements.playerList.innerHTML = playerListHtml; elements.playerList.innerHTML = playerListHtml;
state.playerList = playerListHtml; state.playerList = playerListHtml;
document.querySelectorAll('.tell-player').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
if (!player) {
showNotification('Invalid player name.', 'error');
return;
}
elements.tellPlayerName.textContent = player;
elements.tellMessage.value = '';
elements.tellModal.classList.remove('hidden');
});
});
document.querySelectorAll('.give-player').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
if (!player) {
showNotification('Invalid player name.', 'error');
return;
}
elements.givePlayerName.textContent = player;
elements.loadoutSelect.value = 'custom';
elements.customGiveFields.classList.remove('hidden');
resetItemFields();
elements.giveModal.classList.remove('hidden');
});
});
document.querySelectorAll('.teleport-player').forEach(button => {
if (!button.disabled) {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
if (!player) {
showNotification('Invalid player name.', 'error');
return;
}
elements.teleportPlayerName.textContent = player;
elements.teleportDestination.innerHTML = state.currentPlayers
.filter(p => p !== player)
.map(p => `<option value="${p}">${p}</option>`)
.join('');
elements.teleportModal.classList.remove('hidden');
});
}
});
document.querySelectorAll('.effect-player').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
if (!player) {
showNotification('Invalid player name.', 'error');
return;
}
elements.effectPlayerName.textContent = player;
elements.effectSelect.value = 'speed:30:1';
elements.effectModal.classList.remove('hidden');
});
});
document.querySelectorAll('.kick-player').forEach(button => { document.querySelectorAll('.kick-player').forEach(button => {
button.addEventListener('click', () => { button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim(); const player = button.getAttribute('data-player').trim();
@ -791,34 +864,6 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
}); });
document.querySelectorAll('.tell-player').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
if (!player) {
showNotification('Invalid player name.', 'error');
return;
}
elements.tellPlayerName.textContent = player;
elements.tellMessage.value = '';
elements.tellModal.classList.remove('hidden');
});
});
document.querySelectorAll('.give-player').forEach(button => {
button.addEventListener('click', () => {
const player = button.getAttribute('data-player').trim();
if (!player) {
showNotification('Invalid player name.', 'error');
return;
}
elements.givePlayerName.textContent = player;
elements.loadoutSelect.value = 'custom';
elements.customGiveFields.classList.remove('hidden');
resetItemFields();
elements.giveModal.classList.remove('hidden');
});
});
} }
} }
@ -1052,7 +1097,6 @@ document.addEventListener('DOMContentLoaded', () => {
renderModList(); renderModList();
} }
// Debounce function to limit the rate of search execution
function debounce(func, wait) { function debounce(func, wait) {
let timeout; let timeout;
return function executedFunction(...args) { return function executedFunction(...args) {
@ -1204,6 +1248,49 @@ document.addEventListener('DOMContentLoaded', () => {
} }
} }
async function sendTeleportCommand() {
const player = elements.teleportPlayerName.textContent.trim();
const destination = elements.teleportDestination.value.trim();
if (!player || !destination) {
showNotification('Source and destination players are required.', 'error', 'teleport-error');
return;
}
try {
const command = `tp ${player} ${destination}`;
const key = `action-teleport-player-${player}`;
const notification = showNotification(`Teleporting ${player} to ${destination}...`, 'loading', key);
const response = await wsRequest('/console', 'POST', { command });
updateNotification(notification, `Teleported ${player} to ${destination} successfully`, 'success', key);
elements.teleportModal.classList.add('hidden');
} catch (error) {
console.error(`Teleport ${player} error:`, error);
showNotification(`Failed to teleport ${player}: ${error.message}`, 'error', 'teleport-error');
}
}
async function sendEffectCommand() {
const player = elements.effectPlayerName.textContent.trim();
const effectData = elements.effectSelect.value.split(':');
const effect = effectData[0];
const duration = parseInt(effectData[1], 10);
const amplifier = parseInt(effectData[2], 10);
if (!player || !effect) {
showNotification('Player name and effect are required.', 'error', 'effect-error');
return;
}
try {
const command = `effect give ${player} minecraft:${effect} ${duration} ${amplifier}`;
const key = `action-effect-player-${player}`;
const notification = showNotification(`Applying ${effect} to ${player}...`, 'loading', key);
const response = await wsRequest('/console', 'POST', { command });
updateNotification(notification, `Applied ${effect} to ${player} successfully`, 'success', key);
elements.effectModal.classList.add('hidden');
} catch (error) {
console.error(`Apply effect to ${player} error:`, error);
showNotification(`Failed to apply effect to ${player}: ${error.message}`, 'error', 'effect-error');
}
}
function addItemField() { function addItemField() {
const itemList = elements.itemList; const itemList = elements.itemList;
const itemEntry = document.createElement('div'); const itemEntry = document.createElement('div');
@ -1637,6 +1724,14 @@ document.addEventListener('DOMContentLoaded', () => {
elements.giveModal.classList.add('hidden'); elements.giveModal.classList.add('hidden');
}); });
elements.teleportModal.querySelector('.modal-close').addEventListener('click', () => {
elements.teleportModal.classList.add('hidden');
});
elements.effectModal.querySelector('.modal-close').addEventListener('click', () => {
elements.effectModal.classList.add('hidden');
});
elements.loadoutSelect.addEventListener('change', () => { elements.loadoutSelect.addEventListener('change', () => {
const isCustom = elements.loadoutSelect.value === 'custom'; const isCustom = elements.loadoutSelect.value === 'custom';
elements.customGiveFields.classList.toggle('hidden', !isCustom); elements.customGiveFields.classList.toggle('hidden', !isCustom);
@ -1659,6 +1754,16 @@ document.addEventListener('DOMContentLoaded', () => {
} }
}); });
elements.teleportForm.addEventListener('submit', (e) => {
e.preventDefault();
sendTeleportCommand();
});
elements.effectForm.addEventListener('submit', (e) => {
e.preventDefault();
sendEffectCommand();
});
elements.tellForm.addEventListener('submit', (e) => { elements.tellForm.addEventListener('submit', (e) => {
e.preventDefault(); e.preventDefault();
sendTellMessage(); sendTellMessage();
@ -1684,7 +1789,6 @@ document.addEventListener('DOMContentLoaded', () => {
elements.clearModListSearch.addEventListener('click', clearModListSearch); elements.clearModListSearch.addEventListener('click', clearModListSearch);
// Debounced search for installed mods
const debouncedModListSearch = debounce((query) => { const debouncedModListSearch = debounce((query) => {
modListSearchQuery = query.trim(); modListSearchQuery = query.trim();
modListCurrentPage = 1; modListCurrentPage = 1;

View File

@ -128,6 +128,7 @@
min-width: 80px; min-width: 80px;
text-align: center; text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-family: 'Minecraft', sans-serif; /* Apply Minecraft font */
} }
.control-btn:hover:not(.disabled-btn) { .control-btn:hover:not(.disabled-btn) {
@ -138,6 +139,60 @@
transform: translateY(0); transform: translateY(0);
} }
/* Existing player button styles */
.tell-player {
@apply bg-blue-600 hover:bg-blue-700 px-2 py-1 rounded text-sm;
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.give-player {
@apply bg-green-600 hover:bg-green-700 px-2 py-1 rounded text-sm;
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.op-player, .deop-player {
@apply bg-purple-600 hover:bg-purple-700 px-2 py-1 rounded text-sm;
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.kick-player, .ban-player {
@apply bg-red-600 hover:bg-red-700 px-2 py-1 rounded text-sm;
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
/* New player button styles */
.teleport-player {
@apply bg-cyan-600 hover:bg-cyan-700 px-2 py-1 rounded text-sm;
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.teleport-player:hover:not(.disabled-btn) {
transform: translateY(-1px);
}
.teleport-player:active:not(.disabled-btn) {
transform: translateY(0);
}
.effect-player {
@apply bg-teal-600 hover:bg-teal-700 px-2 py-1 rounded text-sm;
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.effect-player:hover:not(.disabled-btn) {
transform: translateY(-1px);
}
.effect-player:active:not(.disabled-btn) {
transform: translateY(0);
}
.modal { .modal {
position: fixed; position: fixed;
inset: 0; inset: 0;
@ -228,6 +283,20 @@
width: 100%; width: 100%;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
/* Ensure player buttons stack nicely on mobile */
.tell-player,
.give-player,
.teleport-player,
.effect-player,
.op-player,
.deop-player,
.kick-player,
.ban-player {
width: 100%;
text-align: center;
margin-top: 0.25rem;
}
} }
/* Additional styles */ /* Additional styles */

View File

@ -21,6 +21,10 @@
--color-yellow-700: oklch(55.4% 0.135 66.442); --color-yellow-700: oklch(55.4% 0.135 66.442);
--color-green-600: oklch(62.7% 0.194 149.214); --color-green-600: oklch(62.7% 0.194 149.214);
--color-green-700: oklch(52.7% 0.154 150.069); --color-green-700: oklch(52.7% 0.154 150.069);
--color-teal-600: oklch(60% 0.118 184.704);
--color-teal-700: oklch(51.1% 0.096 186.391);
--color-cyan-600: oklch(60.9% 0.126 221.723);
--color-cyan-700: oklch(52% 0.105 223.128);
--color-blue-400: oklch(70.7% 0.165 254.624); --color-blue-400: oklch(70.7% 0.165 254.624);
--color-blue-500: oklch(62.3% 0.214 259.815); --color-blue-500: oklch(62.3% 0.214 259.815);
--color-blue-600: oklch(54.6% 0.245 262.881); --color-blue-600: oklch(54.6% 0.245 262.881);
@ -401,6 +405,9 @@
.bg-blue-600 { .bg-blue-600 {
background-color: var(--color-blue-600); background-color: var(--color-blue-600);
} }
.bg-cyan-600 {
background-color: var(--color-cyan-600);
}
.bg-gray-600 { .bg-gray-600 {
background-color: var(--color-gray-600); background-color: var(--color-gray-600);
} }
@ -422,6 +429,9 @@
.bg-red-600 { .bg-red-600 {
background-color: var(--color-red-600); background-color: var(--color-red-600);
} }
.bg-teal-600 {
background-color: var(--color-teal-600);
}
.bg-yellow-600 { .bg-yellow-600 {
background-color: var(--color-yellow-600); background-color: var(--color-yellow-600);
} }
@ -511,6 +521,9 @@
.text-white { .text-white {
color: var(--color-white); color: var(--color-white);
} }
.opacity-50 {
opacity: 50%;
}
.shadow-lg { .shadow-lg {
--tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1));
box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);
@ -522,6 +535,13 @@
} }
} }
} }
.hover\:bg-cyan-700 {
&:hover {
@media (hover: hover) {
background-color: var(--color-cyan-700);
}
}
}
.hover\:bg-gray-700 { .hover\:bg-gray-700 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
@ -550,6 +570,13 @@
} }
} }
} }
.hover\:bg-teal-700 {
&:hover {
@media (hover: hover) {
background-color: var(--color-teal-700);
}
}
}
.hover\:bg-yellow-700 { .hover\:bg-yellow-700 {
&:hover { &:hover {
@media (hover: hover) { @media (hover: hover) {
@ -699,6 +726,7 @@
min-width: 80px; min-width: 80px;
text-align: center; text-align: center;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-family: 'Minecraft', sans-serif;
} }
.control-btn:hover:not(.disabled-btn) { .control-btn:hover:not(.disabled-btn) {
transform: translateY(-1px); transform: translateY(-1px);
@ -706,6 +734,108 @@
.control-btn:active:not(.disabled-btn) { .control-btn:active:not(.disabled-btn) {
transform: translateY(0); transform: translateY(0);
} }
.tell-player {
border-radius: 0.25rem;
background-color: var(--color-blue-600);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:hover {
@media (hover: hover) {
background-color: var(--color-blue-700);
}
}
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.give-player {
border-radius: 0.25rem;
background-color: var(--color-green-600);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:hover {
@media (hover: hover) {
background-color: var(--color-green-700);
}
}
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.op-player, .deop-player {
border-radius: 0.25rem;
background-color: var(--color-purple-600);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:hover {
@media (hover: hover) {
background-color: var(--color-purple-700);
}
}
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.kick-player, .ban-player {
border-radius: 0.25rem;
background-color: var(--color-red-600);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:hover {
@media (hover: hover) {
background-color: var(--color-red-700);
}
}
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.teleport-player {
border-radius: 0.25rem;
background-color: var(--color-cyan-600);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:hover {
@media (hover: hover) {
background-color: var(--color-cyan-700);
}
}
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.teleport-player:hover:not(.disabled-btn) {
transform: translateY(-1px);
}
.teleport-player:active:not(.disabled-btn) {
transform: translateY(0);
}
.effect-player {
border-radius: 0.25rem;
background-color: var(--color-teal-600);
padding-inline: calc(var(--spacing) * 2);
padding-block: calc(var(--spacing) * 1);
font-size: var(--text-sm);
line-height: var(--tw-leading, var(--text-sm--line-height));
&:hover {
@media (hover: hover) {
background-color: var(--color-teal-700);
}
}
font-family: 'Minecraft', sans-serif;
transition: all 0.2s ease;
}
.effect-player:hover:not(.disabled-btn) {
transform: translateY(-1px);
}
.effect-player:active:not(.disabled-btn) {
transform: translateY(0);
}
.modal { .modal {
position: fixed; position: fixed;
inset: 0; inset: 0;
@ -779,6 +909,11 @@
width: 100%; width: 100%;
margin-top: 0.5rem; margin-top: 0.5rem;
} }
.tell-player, .give-player, .teleport-player, .effect-player, .op-player, .deop-player, .kick-player, .ban-player {
width: 100%;
text-align: center;
margin-top: 0.25rem;
}
} }
.bg-gray-800.p-6.rounded-lg.shadow-lg .grid { .bg-gray-800.p-6.rounded-lg.shadow-lg .grid {
overflow-x: hidden; overflow-x: hidden;

View File

@ -75,6 +75,45 @@
</div> </div>
</div> </div>
<div id="teleportModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Teleport <span id="teleportPlayerName"></span></h2>
<form id="teleportForm">
<div class="mb-4">
<label for="teleportDestination" class="block text-sm font-medium mb-1">Select Destination Player</label>
<select id="teleportDestination" class="bg-gray-700 px-4 py-2 rounded text-white w-full">
<!-- Options populated dynamically -->
</select>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Teleport</button>
</form>
</div>
</div>
<div id="effectModal" class="modal hidden">
<div class="modal-content">
<button class="modal-close">×</button>
<h2 class="text-xl font-semibold mb-4">Apply Effect to <span id="effectPlayerName"></span></h2>
<form id="effectForm">
<div class="mb-4">
<label for="effectSelect" class="block text-sm font-medium mb-1">Select Effect</label>
<select id="effectSelect" class="bg-gray-700 px-4 py-2 rounded text-white w-full">
<option value="speed:30:1">Speed (30s, Level 1)</option>
<option value="strength:30:1">Strength (30s, Level 1)</option>
<option value="regeneration:30:1">Regeneration (30s, Level 1)</option>
<option value="jump_boost:30:1">Jump Boost (30s, Level 1)</option>
<option value="invisibility:30:1">Invisibility (30s, Level 1)</option>
<option value="night_vision:60:1">Night Vision (60s, Level 1)</option>
<option value="fire_resistance:60:1">Fire Resistance (60s, Level 1)</option>
<option value="water_breathing:60:1">Water Breathing (60s, Level 1)</option>
</select>
</div>
<button type="submit" class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded w-full">Apply Effect</button>
</form>
</div>
</div>
<div id="editPropertiesModal" class="modal hidden"> <div id="editPropertiesModal" class="modal hidden">
<div class="modal-content"> <div class="modal-content">
<button class="modal-close">×</button> <button class="modal-close">×</button>