import React, { useEffect, useRef, useState, useCallback } from 'react'; import { DraggableWindow } from './DraggableWindow'; interface ChatMsg { text: string; color?: number; timestamp: string; } const CHAT_COLORS: Record = { 0:'#00FF00', 2:'#FFFFFF', 3:'#FF0000', 4:'#FFFFFF', 5:'#33CCFF', 6:'#CCFF99', 7:'#00FFFF', 14:'#FFD700', 15:'#FF69B4', 17:'#AAAAFF', 18:'#88FF88', 21:'#FF8888', 22:'#FFAA66', }; const MAX_HISTORY = 50; const HISTORY_KEY = (name: string) => `mo-chat-history-${name}`; function loadHistory(charName: string): string[] { try { const raw = localStorage.getItem(HISTORY_KEY(charName)); return raw ? JSON.parse(raw) : []; } catch { return []; } } function saveHistory(charName: string, history: string[]) { try { localStorage.setItem(HISTORY_KEY(charName), JSON.stringify(history.slice(-MAX_HISTORY))); } catch { /* quota exceeded — ignore */ } } interface Props { id: string; charName: string; zIndex: number; messages: ChatMsg[]; socket: WebSocket | null; } export const ChatWindow: React.FC = ({ id, charName, zIndex, messages, socket }) => { const msgsRef = useRef(null); const [input, setInput] = useState(''); const historyRef = useRef(loadHistory(charName)); const historyIndexRef = useRef(-1); // -1 = not browsing history const savedInputRef = useRef(''); // preserves current input when browsing history const userScrolledRef = useRef(false); // Auto-scroll only if user is already at the bottom useEffect(() => { const el = msgsRef.current; if (!el) return; if (!userScrolledRef.current) { el.scrollTop = el.scrollHeight; } }, [messages.length]); // Track whether user has scrolled up from bottom const handleScroll = useCallback(() => { const el = msgsRef.current; if (!el) return; const atBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 30; userScrolledRef.current = !atBottom; }, []); const handleSend = useCallback((e: React.FormEvent) => { e.preventDefault(); const text = input.trim(); if (!text || !socket || socket.readyState !== WebSocket.OPEN) return; socket.send(JSON.stringify({ player_name: charName, command: text })); // Add to history historyRef.current.push(text); if (historyRef.current.length > MAX_HISTORY) historyRef.current.shift(); saveHistory(charName, historyRef.current); historyIndexRef.current = -1; savedInputRef.current = ''; setInput(''); // Snap back to bottom on send userScrolledRef.current = false; }, [input, socket, charName]); const handleKeyDown = useCallback((e: React.KeyboardEvent) => { const history = historyRef.current; if (history.length === 0) return; if (e.key === 'ArrowUp') { e.preventDefault(); if (historyIndexRef.current === -1) { // Starting to browse — save current input savedInputRef.current = input; historyIndexRef.current = history.length - 1; } else if (historyIndexRef.current > 0) { historyIndexRef.current--; } setInput(history[historyIndexRef.current]); } else if (e.key === 'ArrowDown') { e.preventDefault(); if (historyIndexRef.current === -1) return; // not browsing if (historyIndexRef.current < history.length - 1) { historyIndexRef.current++; setInput(history[historyIndexRef.current]); } else { // Past the end — restore saved input historyIndexRef.current = -1; setInput(savedInputRef.current); } } }, [input]); return (
{messages.map((m, i) => (
{m.text}
))}
setInput(e.target.value)} onKeyDown={handleKeyDown} placeholder="Enter chat..." />
); };