Complete 3.1.1: Grid Implementation Foundation - Replace flexbox with CSS Grid for theater layout
This commit is contained in:
parent
c48042ea4b
commit
1879c44202
6 changed files with 21 additions and 20 deletions
414
assets/js/chat.js
Normal file
414
assets/js/chat.js
Normal file
|
|
@ -0,0 +1,414 @@
|
|||
// Chat System Module
|
||||
// Handles chat functionality, message handling, and user authentication
|
||||
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
AppModules.require('api', 'ui-controls');
|
||||
AppModules.register('chat');
|
||||
|
||||
// Initialize chat system
|
||||
function initializeChat() {
|
||||
AppLogger.log('Initializing chat system...');
|
||||
|
||||
// Get user ID first
|
||||
API.getUserId()
|
||||
.then(data => {
|
||||
AppLogger.log('Chat initialized with user:', AppState.userId, AppState.isAdmin ? '(admin)' : '');
|
||||
|
||||
// Show admin toast if applicable
|
||||
if (AppState.isAdmin) {
|
||||
UIControls.showToast('Admin mode activated');
|
||||
}
|
||||
|
||||
// Set up nickname handling
|
||||
const nicknameInput = document.getElementById('nickname');
|
||||
if (nicknameInput) {
|
||||
// Load saved nickname
|
||||
const savedNickname = localStorage.getItem('chatNickname');
|
||||
if (savedNickname) {
|
||||
nicknameInput.value = savedNickname;
|
||||
}
|
||||
|
||||
// Handle nickname changes
|
||||
UIControls.DOMUtils.addEvent(nicknameInput, 'change', function() {
|
||||
const nickname = this.value.trim();
|
||||
localStorage.setItem('chatNickname', nickname);
|
||||
AppState.nickname = nickname;
|
||||
});
|
||||
}
|
||||
|
||||
// Set up message input handling
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
if (messageInput) {
|
||||
// Enter key to send message
|
||||
UIControls.DOMUtils.addEvent(messageInput, 'keypress', function(e) {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
}
|
||||
});
|
||||
|
||||
// Typing indicator
|
||||
UIControls.DOMUtils.addEvent(messageInput, 'input', function() {
|
||||
handleTypingIndicator();
|
||||
});
|
||||
}
|
||||
|
||||
// Set up send button
|
||||
const sendButton = document.querySelector('.chat-input button') ||
|
||||
document.querySelector('#messageInput').nextElementSibling;
|
||||
if (sendButton && sendButton.tagName === 'BUTTON') {
|
||||
UIControls.DOMUtils.addEvent(sendButton, 'click', sendMessage);
|
||||
}
|
||||
|
||||
// Start polling for messages
|
||||
startMessagePolling();
|
||||
|
||||
// Send initial heartbeat
|
||||
API.sendHeartbeat();
|
||||
|
||||
AppLogger.log('Chat system initialized successfully');
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('Failed to initialize chat:', error);
|
||||
UIControls.showToast('Failed to initialize chat');
|
||||
});
|
||||
}
|
||||
|
||||
// Send message functionality
|
||||
function sendMessage() {
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const nicknameInput = document.getElementById('nickname');
|
||||
const message = messageInput?.value?.trim() || '';
|
||||
const nickname = nicknameInput?.value?.trim() || '';
|
||||
|
||||
if (!nickname) {
|
||||
if (nicknameInput) {
|
||||
nicknameInput.style.borderColor = 'var(--dodgers-red)';
|
||||
nicknameInput.focus();
|
||||
}
|
||||
UIControls.showToast('Please enter a nickname');
|
||||
setTimeout(() => {
|
||||
if (nicknameInput) nicknameInput.style.borderColor = '#e0e0e0';
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
return; // Just return quietly for empty message
|
||||
}
|
||||
|
||||
// Send message via API
|
||||
API.sendMessage(nickname, message)
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
if (messageInput) messageInput.value = '';
|
||||
AppState.nickname = nickname;
|
||||
// Message will be added automatically by polling
|
||||
} else if (data.error) {
|
||||
UIControls.showToast(data.error);
|
||||
UIControls.updateConnectionStatus(false);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('Error sending message:', error);
|
||||
UIControls.showToast('Failed to send message');
|
||||
UIControls.updateConnectionStatus(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Delete message (admin only)
|
||||
function deleteMessage(messageId) {
|
||||
if (!AppState.isAdmin) return;
|
||||
|
||||
if (!confirm('Delete this message?')) return;
|
||||
|
||||
API.deleteMessage(messageId)
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
removeMessageFromUI(messageId);
|
||||
UIControls.showToast('Message deleted');
|
||||
} else {
|
||||
UIControls.showToast('Failed to delete message');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('Error deleting message:', error);
|
||||
UIControls.showToast('Failed to delete message');
|
||||
});
|
||||
}
|
||||
|
||||
// Ban user (admin only)
|
||||
function banUser(userId) {
|
||||
if (!AppState.isAdmin) return;
|
||||
|
||||
if (!confirm('Ban this user from chat?')) return;
|
||||
|
||||
API.banUser(userId)
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
UIControls.showToast('User banned');
|
||||
} else {
|
||||
UIControls.showToast('Failed to ban user');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('Error banning user:', error);
|
||||
UIControls.showToast('Failed to ban user');
|
||||
});
|
||||
}
|
||||
|
||||
// Clear chat (admin only)
|
||||
function clearChat() {
|
||||
if (!AppState.isAdmin) return;
|
||||
|
||||
if (!confirm('Clear all chat messages?')) return;
|
||||
|
||||
API.clearChat()
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
clearMessagesUI();
|
||||
UIControls.showToast('Chat cleared');
|
||||
AppState.lastMessageId = '';
|
||||
API.sendHeartbeat(); // Force refresh all users
|
||||
} else {
|
||||
UIControls.showToast('Failed to clear chat');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('Error clearing chat:', error);
|
||||
UIControls.showToast('Failed to clear chat');
|
||||
});
|
||||
}
|
||||
|
||||
// Message polling system
|
||||
function startMessagePolling() {
|
||||
// Poll for new messages
|
||||
setInterval(fetchMessages, AppConfig.api.chatPollInterval);
|
||||
|
||||
// Send heartbeat periodically
|
||||
setInterval(API.sendHeartbeat, AppConfig.api.heartbeatInterval);
|
||||
|
||||
// Initial fetch
|
||||
fetchMessages();
|
||||
}
|
||||
|
||||
// Fetch and display messages
|
||||
function fetchMessages() {
|
||||
API.fetchMessages()
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Handle initial load or message updates
|
||||
if (data.all_messages !== null) {
|
||||
// Initial load or forced refresh (like after admin clear)
|
||||
handleInitialMessageLoad(data);
|
||||
} else {
|
||||
// Incremental updates
|
||||
handleNewMessages(data.messages);
|
||||
}
|
||||
|
||||
// Check for admin clear
|
||||
if (data.message_count === 0 && AppState.allMessages.length > 0) {
|
||||
handleChatCleared();
|
||||
}
|
||||
|
||||
// Update last message ID
|
||||
if (AppState.allMessages.length > 0) {
|
||||
AppState.lastMessageId = AppState.allMessages[AppState.allMessages.length - 1].id;
|
||||
}
|
||||
|
||||
UIControls.updateConnectionStatus(true);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
AppLogger.error('Error fetching messages:', error);
|
||||
UIControls.updateConnectionStatus(false);
|
||||
});
|
||||
}
|
||||
|
||||
// Handle initial message load
|
||||
function handleInitialMessageLoad(data) {
|
||||
AppState.allMessages = data.all_messages || [];
|
||||
displayAllMessages();
|
||||
}
|
||||
|
||||
// Handle new messages
|
||||
function handleNewMessages(messages) {
|
||||
if (!messages || messages.length === 0) return;
|
||||
|
||||
messages.forEach(msg => {
|
||||
appendMessage(msg);
|
||||
|
||||
// Show notification if chat is collapsed and message is not from current user
|
||||
if (AppState.chatCollapsed && msg.user_id !== AppState.userId) {
|
||||
AppState.unreadCount++;
|
||||
UIControls.updateNotificationBadge();
|
||||
UIControls.playNotificationSound();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle chat cleared by admin
|
||||
function handleChatCleared() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.innerHTML = '<div class="system-message">Chat was cleared by admin</div>';
|
||||
}
|
||||
AppState.allMessages = [];
|
||||
AppState.lastMessageId = '';
|
||||
}
|
||||
|
||||
// Typing indicator system
|
||||
function handleTypingIndicator() {
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
if (!messageInput) return;
|
||||
|
||||
const hasContent = messageInput.value.length > 0;
|
||||
|
||||
if (hasContent && !AppState.isTyping) {
|
||||
AppState.isTyping = true;
|
||||
}
|
||||
|
||||
clearTimeout(AppState.typingTimer);
|
||||
AppState.typingTimer = setTimeout(() => {
|
||||
AppState.isTyping = false;
|
||||
}, AppConfig.chat.typingTimeout);
|
||||
}
|
||||
|
||||
// UI manipulation functions
|
||||
function displayAllMessages() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
|
||||
if (!chatMessages) return;
|
||||
|
||||
if (AppState.allMessages.length === 0) {
|
||||
chatMessages.innerHTML = '<div class="empty-chat">No messages yet. Be the first to say hello! 👋</div>';
|
||||
AppState.lastMessageCount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
chatMessages.innerHTML = '';
|
||||
|
||||
AppState.allMessages.forEach(msg => {
|
||||
appendMessageElement(msg, false);
|
||||
});
|
||||
|
||||
// Scroll to bottom
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
AppState.lastMessageCount = AppState.allMessages.length;
|
||||
}
|
||||
|
||||
function appendMessage(msg) {
|
||||
AppState.allMessages.push(msg);
|
||||
appendMessageElement(msg, true);
|
||||
AppState.lastMessageCount = AppState.allMessages.length;
|
||||
}
|
||||
|
||||
function appendMessageElement(msg, animate) {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
|
||||
if (!chatMessages) return;
|
||||
|
||||
// Remove empty chat message if it exists
|
||||
const emptyChat = chatMessages.querySelector('.empty-chat');
|
||||
if (emptyChat) {
|
||||
emptyChat.remove();
|
||||
}
|
||||
|
||||
const messageDiv = document.createElement('div');
|
||||
messageDiv.className = 'message';
|
||||
messageDiv.setAttribute('data-message-id', msg.id);
|
||||
|
||||
// Own message styling
|
||||
if (msg.user_id === AppState.userId) {
|
||||
messageDiv.className += ' own-message';
|
||||
}
|
||||
|
||||
// Admin message styling
|
||||
if (msg.is_admin) {
|
||||
messageDiv.className += ' admin-message';
|
||||
}
|
||||
|
||||
// Disable animation for initial load
|
||||
if (!animate) {
|
||||
messageDiv.style.animation = 'none';
|
||||
}
|
||||
|
||||
// Create admin actions if user is admin
|
||||
const adminActions = (AppState.isAdmin && msg.user_id !== AppState.userId) ?
|
||||
`<div class="message-actions">
|
||||
<button class="delete-btn" onclick="Chat.deleteMessage('${msg.id}')">Delete</button>
|
||||
<button class="ban-btn" onclick="Chat.banUser('${msg.user_id}')">Ban</button>
|
||||
</div>` : '';
|
||||
|
||||
// Create admin badge
|
||||
const adminBadge = msg.is_admin ? '👑 ' : '';
|
||||
|
||||
messageDiv.innerHTML = `
|
||||
<div class="message-header">
|
||||
<span class="message-nickname${msg.is_admin ? ' admin' : ''}">${adminBadge}${msg.nickname}</span>
|
||||
<span class="message-id">#${msg.user_id}</span>
|
||||
<span class="message-time">${msg.time}</span>
|
||||
</div>
|
||||
<div class="message-text">${escapeHtml(msg.message)}</div>
|
||||
${adminActions}
|
||||
`;
|
||||
|
||||
chatMessages.appendChild(messageDiv);
|
||||
|
||||
// Auto-scroll to bottom on new messages
|
||||
if (animate) {
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
function removeMessageFromUI(messageId) {
|
||||
const messageElement = document.querySelector(`[data-message-id="${messageId}"]`);
|
||||
if (messageElement) {
|
||||
messageElement.style.animation = 'slideOut 0.3s';
|
||||
setTimeout(() => {
|
||||
messageElement.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Remove from internal array
|
||||
AppState.allMessages = AppState.allMessages.filter(msg => msg.id !== messageId);
|
||||
}
|
||||
|
||||
function clearMessagesUI() {
|
||||
const chatMessages = document.getElementById('chatMessages');
|
||||
if (chatMessages) {
|
||||
chatMessages.innerHTML = '<div class="system-message">Chat cleared by admin</div>';
|
||||
}
|
||||
AppState.allMessages = [];
|
||||
AppState.lastMessageId = '';
|
||||
}
|
||||
|
||||
// Utility function for HTML escaping
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// Public API
|
||||
window.Chat = {
|
||||
initializeChat: initializeChat,
|
||||
sendMessage: sendMessage,
|
||||
deleteMessage: deleteMessage,
|
||||
banUser: banUser,
|
||||
clearChat: clearChat,
|
||||
displayAllMessages: displayAllMessages,
|
||||
appendMessage: appendMessage
|
||||
};
|
||||
|
||||
// Initialize when DOM is ready and modules are loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Small delay to ensure all modules are loaded
|
||||
setTimeout(initializeChat, 100);
|
||||
});
|
||||
|
||||
AppLogger.log('Chat module loaded');
|
||||
|
||||
})();
|
||||
Loading…
Add table
Add a link
Reference in a new issue