From ddeb7d81486549412fbe593a162bbce32d9439bd Mon Sep 17 00:00:00 2001 From: Lanskikh Date: Wed, 5 Nov 2025 15:07:38 +0500 Subject: [PATCH] Update environment variables for production URLs, enhance DraggableContainer with padding parsing for responsive positioning, and improve PopupWrapper and SessionUsersPanel styling for better layout consistency. Refactor ChatPopup and UserCamera components for improved logging and conditional rendering based on user roles. --- client/.env | 8 +- client/src/components/DraggableContainer.tsx | 91 +++++++++++++++++-- client/src/components/PopupWrapper.tsx | 2 +- client/src/components/SessionUsersPanel.tsx | 2 +- client/src/components/indicators/Admin.tsx | 4 +- client/src/components/popups/ChatPopup.tsx | 63 +++++++++---- .../components/popups/ParticipantsPopup.tsx | 5 +- client/src/components/ui/ActionsPopover.tsx | 2 +- client/src/components/ui/Avatar.tsx | 6 +- client/src/components/ui/UserCamera.tsx | 80 +++++++++++----- client/src/pages/SessionPage.tsx | 13 ++- 11 files changed, 215 insertions(+), 61 deletions(-) diff --git a/client/.env b/client/.env index 0f22f67..5e374be 100644 --- a/client/.env +++ b/client/.env @@ -1,4 +1,4 @@ -VITE_API_URL=http://localhost:3000 -VITE_WEBRTC_URL=http://localhost:3001 -# VITE_API_URL=https://stream.graff.estate/api -# VITE_WEBRTC_URL=https://stream.graff.estate \ No newline at end of file +# VITE_API_URL=http://localhost:3000 +# VITE_WEBRTC_URL=http://localhost:3001 +VITE_API_URL=https://stream.graff.estate/api +VITE_WEBRTC_URL=https://stream.graff.estate \ No newline at end of file diff --git a/client/src/components/DraggableContainer.tsx b/client/src/components/DraggableContainer.tsx index 9ba2011..a6a00e5 100644 --- a/client/src/components/DraggableContainer.tsx +++ b/client/src/components/DraggableContainer.tsx @@ -152,6 +152,24 @@ export default function DraggableContainer({ initialPosition: { top: 0, left: 0 }, }); + // Функция для преобразования padding в пиксели + const parsePadding = (paddingValue: number | string): number => { + if (typeof paddingValue === "number") return paddingValue; + + const value = parseFloat(paddingValue); + if (paddingValue.endsWith("vw")) { + return (value / 100) * window.innerWidth; + } else if (paddingValue.endsWith("vh")) { + return (value / 100) * window.innerHeight; + } else if (paddingValue.endsWith("%")) { + // Для процентов берем среднее между шириной и высотой + return (value / 100) * Math.min(window.innerWidth, window.innerHeight); + } else { + // По умолчанию считаем что это пиксели + return value; + } + }; + // Функция для преобразования угла в позицию const getPositionFromCorner = (corner: Corner): Position => { switch (corner) { @@ -446,14 +464,75 @@ export default function DraggableContainer({ // eslint-disable-next-line react-hooks/exhaustive-deps }, [isDragging]); + // Конвертируем позицию из transform-based в absolute только после стабилизации размеров + // Это нужно для корректной работы внутренних поповеров useEffect(() => { - if (!containerRef.current || enableSnapping) return; - const rect = containerRef.current.getBoundingClientRect(); - setPosition({ - top: rect.top, - left: rect.left, + if (!containerRef.current || enableSnapping || !position.transform) return; + + const container = containerRef.current; + let timeoutId: NodeJS.Timeout; + let lastHeight = 0; + + // Используем ResizeObserver для отслеживания изменений размера + const resizeObserver = new ResizeObserver((entries) => { + const entry = entries[0]; + const currentHeight = entry.contentRect.height; + + // Если высота изменилась, перезапускаем таймер + if (currentHeight !== lastHeight) { + lastHeight = currentHeight; + clearTimeout(timeoutId); + + timeoutId = setTimeout(() => { + if (!containerRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + let { top, left } = rect; + + // Проверяем, не выходит ли попап за границы экрана + const windowHeight = window.innerHeight; + const windowWidth = window.innerWidth; + + // Корректируем вертикальную позицию если нужно + if (centerVertical) { + if (rect.bottom > windowHeight) { + // Попап выходит за нижнюю границу - прижимаем к низу с отступом + top = windowHeight - rect.height - parsePadding(padding); + } else if (rect.top < 0) { + // Попап выходит за верхнюю границу - прижимаем к верху с отступом + top = parsePadding(padding); + } + } + + // Корректируем горизонтальную позицию если нужно + if (centerHorizontal) { + if (rect.right > windowWidth) { + left = windowWidth - rect.width - parsePadding(padding); + } else if (rect.left < 0) { + left = parsePadding(padding); + } + } + + setPosition({ + top, + left, + }); + }, 100); + } }); - }, [enableSnapping]); + + resizeObserver.observe(container); + + return () => { + resizeObserver.disconnect(); + clearTimeout(timeoutId); + }; + }, [ + enableSnapping, + position.transform, + centerVertical, + centerHorizontal, + padding, + ]); // Устанавливаем cursor стили на элемент-хэндл useEffect(() => { diff --git a/client/src/components/PopupWrapper.tsx b/client/src/components/PopupWrapper.tsx index d6a9f57..e69af85 100644 --- a/client/src/components/PopupWrapper.tsx +++ b/client/src/components/PopupWrapper.tsx @@ -20,7 +20,7 @@ function PopupWrapper({ return (
diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index 3c9138d..dc9f33e 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -208,7 +208,7 @@ function SessionUsersPanel({ isVideoOff={participant.isVideoOff || false} isSpeaking={participant.isSpeaking} isControlDisabled={true} - isAdmin={true} + isAdmin={false} mediaStream={participant.stream} hasLocalMediaPermission={hasLocalStream} onMute={() => console.log(`Mute user ${participant.id}`)} diff --git a/client/src/components/indicators/Admin.tsx b/client/src/components/indicators/Admin.tsx index d0bf3ed..aed51d3 100644 --- a/client/src/components/indicators/Admin.tsx +++ b/client/src/components/indicators/Admin.tsx @@ -5,11 +5,11 @@ export default function Admin({ className }: { className?: string }) { return (
-
+
diff --git a/client/src/components/popups/ChatPopup.tsx b/client/src/components/popups/ChatPopup.tsx index 9a018f1..6142979 100644 --- a/client/src/components/popups/ChatPopup.tsx +++ b/client/src/components/popups/ChatPopup.tsx @@ -13,7 +13,9 @@ interface ChatPopupProps { sessionId?: string; } -export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = {}) { +export default function ChatPopup({ + sessionId: sessionIdProp, +}: ChatPopupProps = {}) { const headerRef = useRef(null); // Получаем sessionId из пропсов или из URL параметров const { id: sessionIdFromParams } = useParams<{ id: string }>(); @@ -32,7 +34,10 @@ export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = // Обновляем userId в WebRTC сервисе при авторизации useEffect(() => { if (user?.id && updateUserId) { - console.log("[ChatPopup] User authenticated, updating WebRTC userId to:", user.id); + console.log( + "[ChatPopup] User authenticated, updating WebRTC userId to:", + user.id + ); updateUserId(user.id); } }, [user?.id, updateUserId]); @@ -42,10 +47,23 @@ export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = const myIdentifier = user?.id || currentUserId; console.log("[ChatPopup] Rendering with sessionId:", sessionId); - console.log("[ChatPopup] My identifier:", myIdentifier, "isAuthenticated:", !!user, "user.id:", user?.id, "currentUserId:", currentUserId); + console.log( + "[ChatPopup] My identifier:", + myIdentifier, + "isAuthenticated:", + !!user, + "user.id:", + user?.id, + "currentUserId:", + currentUserId + ); // Загружаем историю через REST API - const { data: historyMessages = [], isLoading, error } = useChatHistory(sessionId); + const { + data: historyMessages = [], + isLoading, + error, + } = useChatHistory(sessionId); console.log("[ChatPopup] Chat history state:", { historyMessages: historyMessages.length, @@ -67,9 +85,9 @@ export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = const newRealtimeMessages = sessionRealtimeMessages.filter( (m) => !historyIds.has(m.id) ); - + // Дедупликация на всякий случай - убираем дубликаты по ID - const allMessagesMap = new Map(); + const allMessagesMap = new Map(); [...historyMessages, ...newRealtimeMessages].forEach((msg) => { allMessagesMap.set(msg.id, msg); }); @@ -85,7 +103,10 @@ export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = newRealtime: newRealtimeMessages.length, total: allMessages.length, historyIds: Array.from(historyIds), - realtimeIds: realtimeMessages.map(m => ({ id: m.id, sessionId: m.sessionId })), + realtimeIds: realtimeMessages.map((m) => ({ + id: m.id, + sessionId: m.sessionId, + })), }); function onMessageSend(message: string) { @@ -103,10 +124,10 @@ export default function ChatPopup({ sessionId: sessionIdProp }: ChatPopupProps = > -
+
({ - id: m.id, - senderId: m.senderId, - isFromMe: m.senderId === currentUserId, - content: m.content.substring(0, 20) - }))); + console.log( + "[MessageFeed] Messages:", + messages.map((m) => ({ + id: m.id, + senderId: m.senderId, + isFromMe: m.senderId === currentUserId, + content: m.content.substring(0, 20), + })) + ); // Скролл при первой загрузке сообщений useEffect(() => { @@ -313,13 +337,14 @@ function MessageInput({ e.preventDefault(); const textarea = textareaRef.current; if (!textarea) return; - + const start = textarea.selectionStart; const end = textarea.selectionEnd; - const newMessage = message.substring(0, start) + "\n" + message.substring(end); - + const newMessage = + message.substring(0, start) + "\n" + message.substring(end); + setMessage(newMessage); - + // Устанавливаем курсор после вставленного переноса строки setTimeout(() => { textarea.selectionStart = textarea.selectionEnd = start + 1; diff --git a/client/src/components/popups/ParticipantsPopup.tsx b/client/src/components/popups/ParticipantsPopup.tsx index 7c0558c..b17b4d2 100644 --- a/client/src/components/popups/ParticipantsPopup.tsx +++ b/client/src/components/popups/ParticipantsPopup.tsx @@ -128,7 +128,10 @@ function ParticipantItem({ : undefined; return ( -
+
diff --git a/client/src/components/ui/ActionsPopover.tsx b/client/src/components/ui/ActionsPopover.tsx index d9635f4..25a3169 100644 --- a/client/src/components/ui/ActionsPopover.tsx +++ b/client/src/components/ui/ActionsPopover.tsx @@ -54,7 +54,7 @@ export default function ActionsPopover({ isOpened={opened} parentElRef={buttonRef} position="vertical" - className={clsx("2xl:w-[17.222vw] w-[53.333vw]", className)} + className={clsx("2xl:w-[17.222vw] max-2xl:w-[192px]", className)} > {options.map((option) => (