added user permissions to rooms
This commit is contained in:
@@ -402,8 +402,8 @@ const AnimePlayer = (function() {
|
||||
|
||||
// Control functions
|
||||
function togglePlayPause() {
|
||||
if (_roomMode && !_isRoomHost) {
|
||||
console.log('Guests cannot control playback');
|
||||
if (_roomMode && !_isRoomHost && !hasControlPermission()) {
|
||||
showPermissionToast('You need playback control permission');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -411,12 +411,14 @@ const AnimePlayer = (function() {
|
||||
|
||||
if (els.video.paused) {
|
||||
els.video.play().catch(() => {});
|
||||
if (_roomMode && _isRoomHost) {
|
||||
|
||||
if (_roomMode && (_isRoomHost || hasControlPermission())) {
|
||||
sendRoomEvent('play', { currentTime: els.video.currentTime });
|
||||
}
|
||||
} else {
|
||||
els.video.pause();
|
||||
if (_roomMode && _isRoomHost) {
|
||||
|
||||
if (_roomMode && (_isRoomHost || hasControlPermission())) {
|
||||
sendRoomEvent('pause', { currentTime: els.video.currentTime });
|
||||
}
|
||||
}
|
||||
@@ -460,7 +462,7 @@ const AnimePlayer = (function() {
|
||||
|
||||
function seekToPosition(e) {
|
||||
if (!els.video || !els.progressContainer) return;
|
||||
if (_roomMode && !_isRoomHost) return;
|
||||
if (_roomMode && !_isRoomHost && !hasControlPermission()) return;
|
||||
|
||||
const rect = els.progressContainer.getBoundingClientRect();
|
||||
const pos = (e.clientX - rect.left) / rect.width;
|
||||
@@ -468,8 +470,7 @@ const AnimePlayer = (function() {
|
||||
|
||||
els.video.currentTime = newTime;
|
||||
|
||||
// En room mode, enviar evento de seek
|
||||
if (_roomMode && _isRoomHost) {
|
||||
if (_roomMode && (_isRoomHost || hasControlPermission())) {
|
||||
sendRoomEvent('seek', { currentTime: newTime });
|
||||
}
|
||||
}
|
||||
@@ -484,29 +485,63 @@ const AnimePlayer = (function() {
|
||||
|
||||
function seekRelative(seconds) {
|
||||
if (!els.video) return;
|
||||
if (_roomMode && !_isRoomHost) return;
|
||||
if (_roomMode && !_isRoomHost && !hasControlPermission()) {
|
||||
showPermissionToast('You need playback control permission');
|
||||
return;
|
||||
}
|
||||
|
||||
const newTime = Math.max(0, Math.min(els.video.duration, els.video.currentTime + seconds));
|
||||
els.video.currentTime = newTime;
|
||||
|
||||
// En room mode, enviar evento de seek
|
||||
if (_roomMode && _isRoomHost) {
|
||||
if (_roomMode && (_isRoomHost || hasControlPermission())) {
|
||||
sendRoomEvent('seek', { currentTime: newTime });
|
||||
}
|
||||
}
|
||||
|
||||
function seekToPercent(percent) {
|
||||
if (!els.video) return;
|
||||
if (_roomMode && !_isRoomHost) return;
|
||||
|
||||
if (_roomMode && !_isRoomHost && !hasControlPermission()) {
|
||||
showPermissionToast('You need playback control permission');
|
||||
return;
|
||||
}
|
||||
const newTime = els.video.duration * percent;
|
||||
els.video.currentTime = newTime;
|
||||
|
||||
// En room mode, enviar evento de seek
|
||||
if (_roomMode && _isRoomHost) {
|
||||
if (_roomMode && (_isRoomHost || hasControlPermission())) {
|
||||
sendRoomEvent('seek', { currentTime: newTime });
|
||||
}
|
||||
}
|
||||
|
||||
function hasControlPermission() {
|
||||
return window.__userPermissions?.canControl || false;
|
||||
}
|
||||
|
||||
function showPermissionToast(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'permission-toast';
|
||||
toast.textContent = message;
|
||||
toast.style.cssText = `
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background: rgba(239, 68, 68, 0.95);
|
||||
color: white;
|
||||
padding: 16px 24px;
|
||||
border-radius: 10px;
|
||||
font-weight: 600;
|
||||
z-index: 10000;
|
||||
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
||||
animation: fadeIn 0.3s ease;
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.animation = 'fadeOut 0.3s ease';
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
// Video event handlers
|
||||
function onPlay() {
|
||||
if (els.playPauseBtn) {
|
||||
@@ -589,7 +624,9 @@ const AnimePlayer = (function() {
|
||||
}
|
||||
|
||||
function sendRoomEvent(eventType, data = {}) {
|
||||
if (!_roomMode || !_isRoomHost || !_roomWebSocket) return;
|
||||
if (!_roomMode || !_roomWebSocket) return;
|
||||
if (!_isRoomHost && !hasControlPermission()) return;
|
||||
|
||||
if (_roomWebSocket.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
console.log('Sending room event:', eventType, data);
|
||||
|
||||
@@ -232,10 +232,15 @@ const RoomsApp = (function() {
|
||||
|
||||
if (elements.btnAddQueue) {
|
||||
elements.btnAddQueue.onclick = () => {
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!canManageQueue) {
|
||||
showSystemToast('You need queue management permission');
|
||||
return;
|
||||
}
|
||||
|
||||
if (selectedEpisodes.size === 0) return;
|
||||
|
||||
const sortedEps = Array.from(selectedEpisodes).sort((a, b) => a - b);
|
||||
|
||||
const currentProvider = elements.roomExtSelect ? elements.roomExtSelect.value : 'gogoanime';
|
||||
|
||||
const originalText = elements.btnAddQueue.innerHTML;
|
||||
@@ -279,7 +284,9 @@ const RoomsApp = (function() {
|
||||
}
|
||||
if (elements.roomSdToggle) {
|
||||
elements.roomSdToggle.onclick = () => {
|
||||
if (!isHost) return;
|
||||
const hasAccess = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
|
||||
if (!hasAccess) return;
|
||||
const currentState = elements.roomSdToggle.getAttribute('data-state');
|
||||
const newState = currentState === 'sub' ? 'dub' : 'sub';
|
||||
|
||||
@@ -338,7 +345,9 @@ const RoomsApp = (function() {
|
||||
// --- QUICK CONTROLS LOGIC (Header) ---
|
||||
|
||||
async function populateQuickControls() {
|
||||
if (!isHost || !selectedAnimeData) return;
|
||||
const hasAccess = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
|
||||
if (!hasAccess || !selectedAnimeData) return;
|
||||
if (!extensionsReady) return;
|
||||
|
||||
elements.roomExtSelect.innerHTML = '';
|
||||
@@ -356,7 +365,8 @@ const RoomsApp = (function() {
|
||||
}
|
||||
|
||||
async function onQuickExtensionChange(e, silent = false) {
|
||||
if (!isHost) return;
|
||||
const hasAccess = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!hasAccess) return;
|
||||
|
||||
const ext = elements.roomExtSelect.value;
|
||||
const settings = extensionsStore.settings[ext];
|
||||
@@ -433,7 +443,9 @@ const RoomsApp = (function() {
|
||||
}
|
||||
|
||||
function onQuickServerChange() {
|
||||
if (!isHost) return;
|
||||
const hasAccess = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!hasAccess) return;
|
||||
|
||||
launchStream(false);
|
||||
}
|
||||
|
||||
@@ -488,6 +500,28 @@ const RoomsApp = (function() {
|
||||
if(elements.btnLaunch) elements.btnLaunch.disabled = false;
|
||||
if(elements.btnAddQueue) elements.btnAddQueue.disabled = false;
|
||||
|
||||
if (extensionsReady && elements.selExtension) {
|
||||
if (elements.selExtension.options.length <= 1) {
|
||||
elements.selExtension.innerHTML = '';
|
||||
|
||||
extensionsStore.list.forEach(ext => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = ext;
|
||||
opt.textContent = ext[0].toUpperCase() + ext.slice(1);
|
||||
elements.selExtension.appendChild(opt);
|
||||
});
|
||||
|
||||
let defaultExt = selectedAnimeData.source || 'anilist';
|
||||
if (!extensionsStore.list.includes(defaultExt) && extensionsStore.list.length > 0) {
|
||||
defaultExt = extensionsStore.list[0];
|
||||
}
|
||||
|
||||
elements.selExtension.value = defaultExt;
|
||||
|
||||
handleModalExtensionChange();
|
||||
}
|
||||
}
|
||||
|
||||
setupConfigListeners();
|
||||
|
||||
elements.stepSearch.style.display = 'none';
|
||||
@@ -495,7 +529,7 @@ const RoomsApp = (function() {
|
||||
}
|
||||
|
||||
function setupConfigListeners() {
|
||||
elements.epInc.onclick = () => {
|
||||
elements.epInc.onclick = () => {
|
||||
elements.inpEpisode.value = parseInt(elements.inpEpisode.value || 0) + 1;
|
||||
};
|
||||
elements.epDec.onclick = () => {
|
||||
@@ -625,12 +659,24 @@ const RoomsApp = (function() {
|
||||
let episodeToPlay = activeContext.episode;
|
||||
if (fromModal && elements.inpEpisode) episodeToPlay = elements.inpEpisode.value;
|
||||
|
||||
const ext = overrides.forceExtension || elements.roomExtSelect.value || activeContext.extension;
|
||||
const server = overrides.forceServer || elements.roomServerSelect.value || activeContext.server;
|
||||
const category = elements.roomSdToggle.getAttribute('data-state') || activeContext.category;
|
||||
const ext = overrides.forceExtension ||
|
||||
(fromModal ? (configState.extension || elements.selExtension?.value) : null) ||
|
||||
activeContext.extension ||
|
||||
(elements.roomExtSelect ? elements.roomExtSelect.value : null);
|
||||
|
||||
const server = overrides.forceServer ||
|
||||
(fromModal ? (configState.server || elements.selServer?.value) : null) ||
|
||||
activeContext.server ||
|
||||
(elements.roomServerSelect ? elements.roomServerSelect.value : null);
|
||||
|
||||
const category = elements.roomSdToggle?.getAttribute('data-state') || activeContext.category || 'sub';
|
||||
|
||||
if (!ext || !server) {
|
||||
console.warn("Faltan datos (ext o server) para lanzar el stream");
|
||||
console.warn("Faltan datos (ext o server).", {ext, server});
|
||||
if (fromModal && elements.configError) {
|
||||
elements.configError.textContent = "Please select an extension and server.";
|
||||
elements.configError.style.display = 'block';
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -850,7 +896,6 @@ const RoomsApp = (function() {
|
||||
|
||||
if (context.extension && elements.roomExtSelect.value !== context.extension) {
|
||||
elements.roomExtSelect.value = context.extension;
|
||||
// Importante: Cargar los servidores de esa extensión sin disparar otro play
|
||||
await onQuickExtensionChange(null, true);
|
||||
}
|
||||
|
||||
@@ -918,7 +963,7 @@ const RoomsApp = (function() {
|
||||
episode: ep,
|
||||
title: currentAnimeDetails.title.userPreferred,
|
||||
server: document.getElementById('room-server-select').value || 'gogoanime',
|
||||
category: 'sub' // o lo que esté seleccionado
|
||||
category: 'sub'
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -1081,6 +1126,18 @@ const RoomsApp = (function() {
|
||||
currentUserId = data.userId;
|
||||
currentUsername = data.username;
|
||||
isGuest = data.isGuest;
|
||||
|
||||
if (data.room && data.room.users) {
|
||||
const currentUser = data.room.users.find(u => u.id === data.userId);
|
||||
if (currentUser) {
|
||||
window.__userPermissions = currentUser.permissions || {
|
||||
canControl: false,
|
||||
canManageQueue: false
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (data.room.queue) renderQueue(data.room.queue);
|
||||
updateRoomUI(data.room);
|
||||
|
||||
if (data.room.currentVideo) {
|
||||
@@ -1109,6 +1166,7 @@ const RoomsApp = (function() {
|
||||
break;
|
||||
|
||||
case 'users_update':
|
||||
saveRoomData({ users: data.users });
|
||||
renderUsersList(data.users);
|
||||
break;
|
||||
|
||||
@@ -1127,6 +1185,32 @@ const RoomsApp = (function() {
|
||||
updateUsersList();
|
||||
break;
|
||||
|
||||
case 'permissions_updated':
|
||||
saveRoomData({ users: data.users });
|
||||
|
||||
const updatedUser = data.users.find(u => u.id === currentUserId);
|
||||
if (updatedUser) {
|
||||
window.__userPermissions = updatedUser.permissions || {
|
||||
canControl: false,
|
||||
canManageQueue: false
|
||||
};
|
||||
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (elements.selectAnimeBtn) {
|
||||
elements.selectAnimeBtn.style.display = canManageQueue ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (currentQueue.length > 0) {
|
||||
renderQueue(currentQueue);
|
||||
}
|
||||
}
|
||||
|
||||
if (permissionsModal && permissionsModal.classList.contains('show')) {
|
||||
updatePermissionsList();
|
||||
}
|
||||
|
||||
renderUsersList(data.users);
|
||||
break;
|
||||
case 'chat':
|
||||
addChatMessage(data);
|
||||
const isChatHidden = elements.roomLayout.classList.contains('chat-hidden');
|
||||
@@ -1210,33 +1294,34 @@ const RoomsApp = (function() {
|
||||
return;
|
||||
}
|
||||
|
||||
const canManageQueue = isHost || (window.__userPermissions && window.__userPermissions.canManageQueue);
|
||||
elements.queueList.innerHTML = queueItems.map((item, index) => {
|
||||
let actionsHtml = '';
|
||||
|
||||
if (isHost) {
|
||||
if (canManageQueue) {
|
||||
const isFirst = index === 0;
|
||||
const isLast = index === queueItems.length - 1;
|
||||
|
||||
actionsHtml = `
|
||||
<div class="q-actions">
|
||||
<button class="q-btn play" onclick="playQueueItem('${item.uid}')" title="Play Now">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
<div style="display:flex; gap:2px;">
|
||||
${!isFirst ? `
|
||||
<button class="q-btn" onclick="moveQueueItem('${item.uid}', 'up')" title="Move Up">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 15l-6-6-6 6"/></svg>
|
||||
</button>` : ''}
|
||||
${!isLast ? `
|
||||
<button class="q-btn" onclick="moveQueueItem('${item.uid}', 'down')" title="Move Down">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
|
||||
</button>` : ''}
|
||||
</div>
|
||||
<button class="q-btn remove" onclick="removeQueueItem('${item.uid}')" title="Remove">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
<div class="q-actions">
|
||||
<button class="q-btn play" onclick="playQueueItem('${item.uid}')" title="Play Now">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
|
||||
</button>
|
||||
<div style="display:flex; gap:2px;">
|
||||
${!isFirst ? `
|
||||
<button class="q-btn" onclick="moveQueueItem('${item.uid}', 'up')" title="Move Up">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 15l-6-6-6 6"/></svg>
|
||||
</button>` : ''}
|
||||
${!isLast ? `
|
||||
<button class="q-btn" onclick="moveQueueItem('${item.uid}', 'down')" title="Move Down">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 9l6 6 6-6"/></svg>
|
||||
</button>` : ''}
|
||||
</div>
|
||||
`;
|
||||
<button class="q-btn remove" onclick="removeQueueItem('${item.uid}')" title="Remove">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
return `
|
||||
@@ -1248,11 +1333,17 @@ const RoomsApp = (function() {
|
||||
</div>
|
||||
${actionsHtml}
|
||||
</div>
|
||||
`;
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
window.playQueueItem = async function(uid) {
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!canManageQueue) {
|
||||
showSystemToast('You need queue management permission');
|
||||
return;
|
||||
}
|
||||
|
||||
const item = currentQueue.find(i => i.uid === uid);
|
||||
if (!item) return;
|
||||
|
||||
@@ -1305,12 +1396,25 @@ const RoomsApp = (function() {
|
||||
};
|
||||
|
||||
window.moveQueueItem = function(uid, direction) {
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!canManageQueue) {
|
||||
showSystemToast('You need queue management permission');
|
||||
return;
|
||||
}
|
||||
|
||||
if(ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'queue_move', itemUid: uid, direction }));
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
window.removeQueueItem = function(uid) {
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!canManageQueue) {
|
||||
showSystemToast('You need queue management permission');
|
||||
return;
|
||||
}
|
||||
|
||||
if(ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({ type: 'queue_remove', itemUid: uid }));
|
||||
}
|
||||
@@ -1321,17 +1425,39 @@ const RoomsApp = (function() {
|
||||
elements.roomViewers.textContent = `${room.users.length}`;
|
||||
|
||||
const currentUser = room.users.find(u => u.id === currentUserId);
|
||||
|
||||
if (currentUser) {
|
||||
window.__userPermissions = currentUser.permissions || { canControl: false, canManageQueue: false };
|
||||
}
|
||||
isHost = currentUser?.isHost || false;
|
||||
|
||||
if (elements.selectAnimeBtn) elements.selectAnimeBtn.style.display = isHost ? 'flex' : 'none';
|
||||
if (elements.hostControls) elements.hostControls.style.display = isHost ? 'flex' : 'none';
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
|
||||
const canControlPlayer = isHost || (window.__userPermissions?.canControl || false);
|
||||
|
||||
if (elements.selectAnimeBtn) {
|
||||
elements.selectAnimeBtn.style.display = canManageQueue ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (elements.hostControls) {
|
||||
elements.hostControls.style.display = canManageQueue ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (window.AnimePlayer && typeof window.AnimePlayer.setRoomHost === 'function') {
|
||||
window.AnimePlayer.setRoomHost(isHost);
|
||||
window.AnimePlayer.setRoomHost(canControlPlayer);
|
||||
}
|
||||
|
||||
if (isHost) {
|
||||
initGlobalControls();
|
||||
initPermissionsUI();
|
||||
}
|
||||
else if (canManageQueue) {
|
||||
initGlobalControls();
|
||||
}
|
||||
|
||||
if (canManageQueue && room.metadata) {
|
||||
if(!selectedAnimeData) selectedAnimeData = { ...room.metadata, source: 'anilist' };
|
||||
populateQuickControls();
|
||||
}
|
||||
|
||||
const copyInviteBtn = document.getElementById('copy-invite-btn');
|
||||
@@ -1387,7 +1513,8 @@ const RoomsApp = (function() {
|
||||
}
|
||||
|
||||
async function initGlobalControls() {
|
||||
if (!isHost || !extensionsReady) return;
|
||||
const hasAccess = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
if (!hasAccess || !extensionsReady) return;
|
||||
|
||||
if (elements.roomExtSelect.options.length > 1) return;
|
||||
|
||||
@@ -1820,6 +1947,274 @@ const RoomsApp = (function() {
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
let permissionsModal = null;
|
||||
|
||||
function initPermissionsUI() {
|
||||
if (!isHost) return;
|
||||
|
||||
if (!permissionsModal) {
|
||||
permissionsModal = document.createElement('div');
|
||||
permissionsModal.className = 'modal-overlay';
|
||||
permissionsModal.id = 'permissions-modal';
|
||||
permissionsModal.innerHTML = `
|
||||
<div class="modal-content permissions-content">
|
||||
<button class="modal-close" onclick="closePermissionsModal()">✕</button>
|
||||
<h2 class="modal-title">Manage Permissions</h2>
|
||||
|
||||
<div class="permissions-list" id="permissions-list">
|
||||
<!-- Se llenará dinámicamente -->
|
||||
</div>
|
||||
|
||||
<div class="banned-ips-section">
|
||||
<h3 class="section-title">Banned IPs</h3>
|
||||
<div id="banned-ips-list" class="banned-list">
|
||||
<p class="empty-state">No banned users</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
document.body.appendChild(permissionsModal);
|
||||
}
|
||||
|
||||
const headerRight = document.querySelector('.header-right');
|
||||
if (headerRight && !document.getElementById('permissions-btn')) {
|
||||
const permBtn = document.createElement('button');
|
||||
permBtn.id = 'permissions-btn';
|
||||
permBtn.className = 'btn-icon-glass';
|
||||
permBtn.title = 'Manage Permissions';
|
||||
permBtn.innerHTML = `
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect>
|
||||
<path d="M7 11V7a5 5 0 0 1 10 0v4"></path>
|
||||
</svg>
|
||||
`;
|
||||
permBtn.onclick = openPermissionsModal;
|
||||
|
||||
const searchBtn = document.getElementById('select-anime-btn');
|
||||
if (searchBtn) {
|
||||
headerRight.insertBefore(permBtn, searchBtn);
|
||||
} else {
|
||||
headerRight.appendChild(permBtn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function openPermissionsModal() {
|
||||
if (!permissionsModal || !isHost) return;
|
||||
|
||||
updatePermissionsList();
|
||||
permissionsModal.classList.add('show');
|
||||
}
|
||||
|
||||
window.closePermissionsModal = function() {
|
||||
if (permissionsModal) {
|
||||
permissionsModal.classList.remove('show');
|
||||
}
|
||||
};
|
||||
|
||||
function updatePermissionsList() {
|
||||
const list = document.getElementById('permissions-list');
|
||||
if (!list) return;
|
||||
|
||||
const room = getCurrentRoomData();
|
||||
|
||||
if (!room || !room.users) {
|
||||
list.innerHTML = '<p class="empty-state">No active users</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = room.users
|
||||
.filter(u => !u.isHost)
|
||||
.map(user => createPermissionCard(user))
|
||||
.join('');
|
||||
}
|
||||
|
||||
function createPermissionCard(user) {
|
||||
const perms = user.permissions || { canControl: false, canManageQueue: false };
|
||||
|
||||
return `
|
||||
<div class="permission-card">
|
||||
<div class="user-info-section">
|
||||
<div class="user-avatar-small">
|
||||
${user.avatar ? `<img src="${user.avatar}">` : user.username[0].toUpperCase()}
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<span class="user-name-text">${escapeHtml(user.username)}</span>
|
||||
${user.isGuest ? '<span class="guest-badge">Guest</span>' : ''}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="permissions-toggles">
|
||||
<label class="perm-toggle" for="perm-ctrl-${user.id}">
|
||||
<input type="checkbox"
|
||||
id="perm-ctrl-${user.id}"
|
||||
data-user-id="${user.id}"
|
||||
${perms.canControl ? 'checked' : ''}
|
||||
onchange="togglePermission('${user.id}', 'canControl', this.checked)">
|
||||
<span class="perm-label">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<polygon points="5 3 19 12 5 21 5 3"></polygon>
|
||||
</svg>
|
||||
Playback Control
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<label class="perm-toggle" for="perm-queue-${user.id}">
|
||||
<input type="checkbox"
|
||||
id="perm-queue-${user.id}"
|
||||
data-user-id="${user.id}"
|
||||
${perms.canManageQueue ? 'checked' : ''}
|
||||
onchange="togglePermission('${user.id}', 'canManageQueue', this.checked)">
|
||||
<span class="perm-label">
|
||||
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<line x1="8" y1="6" x2="21" y2="6"></line>
|
||||
<line x1="8" y1="12" x2="21" y2="12"></line>
|
||||
<line x1="8" y1="18" x2="21" y2="18"></line>
|
||||
<line x1="3" y1="6" x2="3.01" y2="6"></line>
|
||||
<line x1="3" y1="12" x2="3.01" y2="12"></line>
|
||||
<line x1="3" y1="18" x2="3.01" y2="18"></line>
|
||||
</svg>
|
||||
Queue Management
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button class="ban-btn" onclick="banUser('${user.id}')" title="Ban User">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<circle cx="12" cy="12" r="10"></circle>
|
||||
<line x1="4.93" y1="4.93" x2="19.07" y2="19.07"></line>
|
||||
</svg>
|
||||
Ban
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
window.togglePermission = function(userId, permission, value) {
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) return;
|
||||
|
||||
console.log(`Toggling permission ${permission} to ${value} for user ${userId}`);
|
||||
|
||||
const permissions = {};
|
||||
permissions[permission] = value;
|
||||
|
||||
ws.send(JSON.stringify({
|
||||
type: 'update_permissions',
|
||||
targetUserId: userId,
|
||||
permissions
|
||||
}));
|
||||
|
||||
showSystemToast(`Permission ${value ? 'granted' : 'revoked'}`);
|
||||
};
|
||||
|
||||
window.banUser = function(userId) {
|
||||
const room = getCurrentRoomData();
|
||||
const user = room?.users?.find(u => u.id === userId);
|
||||
|
||||
if (!user) return;
|
||||
|
||||
const confirmed = confirm(`Ban ${user.username} from this room? They won't be able to rejoin.`);
|
||||
if (!confirmed) return;
|
||||
|
||||
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(JSON.stringify({
|
||||
type: 'ban_user',
|
||||
targetUserId: userId
|
||||
}));
|
||||
}
|
||||
|
||||
showSystemToast(`${user.username} has been banned`);
|
||||
setTimeout(() => updatePermissionsList(), 500);
|
||||
};
|
||||
|
||||
function getCurrentRoomData() {
|
||||
return window.__currentRoomData || null;
|
||||
}
|
||||
|
||||
function saveRoomData(roomData) {
|
||||
window.__currentRoomData = roomData;
|
||||
|
||||
if (roomData.users && currentUserId) {
|
||||
const currentUser = roomData.users.find(u => u.id === currentUserId);
|
||||
if (currentUser && currentUser.permissions) {
|
||||
window.__userPermissions = currentUser.permissions;
|
||||
|
||||
console.log('Permissions updated for current user:', window.__userPermissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const originalHandleMessage = handleWebSocketMessage;
|
||||
handleWebSocketMessage = function(data) {
|
||||
switch(data.type) {
|
||||
case 'permissions_updated':
|
||||
saveRoomData({ users: data.users });
|
||||
|
||||
const updatedUser = data.users.find(u => u.id === currentUserId);
|
||||
if (updatedUser) {
|
||||
window.__userPermissions = updatedUser.permissions || {
|
||||
canControl: false,
|
||||
canManageQueue: false
|
||||
};
|
||||
|
||||
console.log('My permissions updated:', window.__userPermissions);
|
||||
|
||||
const canManageQueue = isHost || (window.__userPermissions?.canManageQueue || false);
|
||||
|
||||
if (elements.selectAnimeBtn) {
|
||||
elements.selectAnimeBtn.style.display = canManageQueue ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (elements.hostControls) {
|
||||
elements.hostControls.style.display = canManageQueue ? 'flex' : 'none';
|
||||
}
|
||||
|
||||
if (canManageQueue) {
|
||||
initGlobalControls();
|
||||
populateQuickControls();
|
||||
}
|
||||
|
||||
if (!canManageQueue && elements.animeSearchModal && elements.animeSearchModal.classList.contains('show')) {
|
||||
closeAnimeSearchModal();
|
||||
showSystemToast("Permissions updated: Access revoked");
|
||||
}
|
||||
|
||||
if (currentQueue && currentQueue.length > 0) {
|
||||
console.log('Forcing queue re-render due to permission change');
|
||||
renderQueue(currentQueue);
|
||||
}
|
||||
}
|
||||
|
||||
if (permissionsModal && permissionsModal.classList.contains('show')) {
|
||||
updatePermissionsList();
|
||||
}
|
||||
|
||||
renderUsersList(data.users);
|
||||
break;
|
||||
|
||||
case 'banned':
|
||||
alert(data.message || 'You have been banned from this room');
|
||||
window.location.href = '/anime';
|
||||
break;
|
||||
|
||||
case 'users_update':
|
||||
saveRoomData({ users: data.users });
|
||||
break;
|
||||
|
||||
default:
|
||||
originalHandleMessage(data);
|
||||
}
|
||||
};
|
||||
|
||||
const originalUpdateRoomUI = updateRoomUI;
|
||||
updateRoomUI = function(room) {
|
||||
originalUpdateRoomUI(room);
|
||||
|
||||
if (isHost) {
|
||||
initPermissionsUI();
|
||||
}
|
||||
};
|
||||
|
||||
return { init };
|
||||
})();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user