import { useState, useEffect, useRef } from "react"; import { MessagePanel, type MessagePanelState } from "../../panels/MessagePanel"; import { ChatMessages } from "./ChatMessages"; import { ChatInputWrapper } from "./ChatInputWrapper"; import { setGlobalMessageHandler } from "../../../core/websocket"; import type { Message } from "../../../core/types"; import defaultAvatar from "../../../resources/images/default-avatar.png"; import AnimatedOpacity from "../core/animations/AnimatedOpacity"; import type { DMPanel } from "../../panels/DMPanel"; interface MessagePanelRendererProps { panel: MessagePanel | null; isChatSwitching: boolean; } export function MessagePanelRenderer({ panel, isChatSwitching }: MessagePanelRendererProps) { const [panelState, setPanelState] = useState(null); const [switchIn, setSwitchIn] = useState(false); const [switchOut, setSwitchOut] = useState(false); const messagesEndRef = useRef(null); const [replyTo, setReplyTo] = useState(null); const [replyToVisible, setReplyToVisible] = useState(Boolean(replyTo)); const [editMessage, setEditMessage] = useState(null); const [editVisible, setEditVisible] = useState(Boolean(editMessage)); const [pendingAction, setPendingAction] = useState(null); // Drag & drop const [isDragging, setIsDragging] = useState(false); const dragCounterRef = useRef(0); const addFilesRef = useRef void)>(null); useEffect(() => { if (!panel || !panelState) return; return () => { dragCounterRef.current = 0; setIsDragging(false); }; }, [panel, panelState]); useEffect(() => { if (replyTo) { setReplyToVisible(true); } }, [replyTo]); useEffect(() => { if (editMessage) { setEditVisible(true); } }, [editMessage]); // Handle panel state changes useEffect(() => { if (panel) { setPanelState(panel.getState()); // Set up state change listener const handleStateChange = (newState: MessagePanelState) => { setPanelState(newState); }; // Store the handler for cleanup panel.onStateChange = handleStateChange; // Set up WebSocket message handler for this panel if (panel.handleWebSocketMessage) { setGlobalMessageHandler(panel.handleWebSocketMessage); } } else { setPanelState(null); // Clear global message handler when no panel is active setGlobalMessageHandler(null); } // Cleanup function return () => { if (panel) { if (panel.onStateChange) { panel.onStateChange = null; } // Call destroy to clean up pending timeouts if (typeof panel.destroy === 'function') { panel.destroy(); } } }; }, [panel]); // Handle chat switching animation useEffect(() => { if (isChatSwitching) { setSwitchOut(true); setTimeout(() => { setSwitchOut(false); setSwitchIn(true); setTimeout(() => setSwitchIn(false), 200); }, 250); } }, [isChatSwitching]); // Scroll to bottom when messages change useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [panelState?.messages]); if (!panel || !panelState) { return (
Avatar

Выбор чата

Выберите чат, чтобы начать переписку

Выберите чат на боковой панели, чтобы начать переписку
); } return (
{ if (!e.dataTransfer) return; e.preventDefault(); e.stopPropagation(); dragCounterRef.current += 1; // Only show overlay when actual files are dragged const hasFiles = Array.from(e.dataTransfer.types || []).includes("Files"); if (hasFiles) setIsDragging(true); }} onDragOver={(e) => { if (!e.dataTransfer) return; e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = "copy"; }} onDragLeave={(e) => { e.preventDefault(); e.stopPropagation(); dragCounterRef.current = Math.max(0, dragCounterRef.current - 1); if (dragCounterRef.current === 0) setIsDragging(false); }} onDrop={(e) => { if (!e.dataTransfer) return; e.preventDefault(); e.stopPropagation(); const files = Array.from(e.dataTransfer.files || []); if (files.length > 0 && addFilesRef.current) { addFilesRef.current(files); } setIsDragging(false); dragCounterRef.current = 0; }}>
Avatar

{panelState.title}

{panelState.online ? "Online" : "Offline"} {panelState.isTyping && " • Typing..."}

{panelState.isLoading ? (
Загрузка сообщений...
): ( { if (editMessage || editVisible) { setPendingAction({ type: "reply", message: message }); setEditVisible(false); // onCloseEdit will apply pending } else { setReplyTo(message); } }} onEditSelect={(message) => { if (replyTo || replyToVisible) { setPendingAction({ type: "edit", message: message }); setReplyToVisible(false); // onCloseReply will apply pending } else { setEditMessage(message); } }} onDelete={(id) => panel.handleDeleteMessage(id)} onRetryMessage={(id) => panel.retryMessage(id)} >
)} e.preventDefault()} onDrop={(e) => e.preventDefault()}>
Отпустите файл(ы) для добавления
{ panel.handleSendMessage(text, replyTo?.id, files); setReplyTo(null); }} onSaveEdit={(content) => { if (editMessage) { panel.handleEditMessage(editMessage.id, content); setEditMessage(null); } }} replyTo={replyTo} replyToVisible={replyToVisible} onClearReply={() => { setPendingAction(null); setReplyToVisible(false); }} onCloseReply={() => { setReplyTo(null); if (pendingAction && pendingAction.type === "edit") { setEditMessage(pendingAction.message); setPendingAction(null); } }} editingMessage={editMessage} editVisible={editVisible} onClearEdit={() => { setPendingAction(null); setEditVisible(false); }} onCloseEdit={() => { setEditMessage(null); if (pendingAction && pendingAction.type === "reply") { setReplyTo(pendingAction.message); setPendingAction(null); } }} onProvideFileAdder={(adder) => { addFilesRef.current = adder; }} />
); }