diff --git a/client/.env b/client/.env index b8dedb4..f41d947 100644 --- a/client/.env +++ b/client/.env @@ -1 +1,2 @@ -VITE_API_URL=http://192.168.1.23:3000 \ No newline at end of file +# VITE_API_URL=http://192.168.1.23:3000 +VITE_API_URL=http://localhost:3000 \ No newline at end of file diff --git a/client/public/img/popups/equalizer_bars.svg b/client/public/img/popups/equalizer_bars.svg new file mode 100644 index 0000000..423b34d --- /dev/null +++ b/client/public/img/popups/equalizer_bars.svg @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/src/components/PopupWrapper.tsx b/client/src/components/PopupWrapper.tsx index a7641d3..b4745b5 100644 --- a/client/src/components/PopupWrapper.tsx +++ b/client/src/components/PopupWrapper.tsx @@ -44,11 +44,11 @@ function PopupWrapper({ setPosition({ x: Math.min( Math.max(0, position.x + x - mouseDownPosition.x), - window.innerWidth - wrapperRef.current.clientWidth + innerWidth - wrapperRef.current.clientWidth ), y: Math.min( Math.max(0, position.y + y - mouseDownPosition.y), - window.innerHeight - wrapperRef.current.clientHeight + innerHeight - wrapperRef.current.clientHeight ), }); setMouseDownPosition({ x, y }); diff --git a/client/src/components/SessionUsersPanel.tsx b/client/src/components/SessionUsersPanel.tsx index eb9fec7..8f30b34 100644 --- a/client/src/components/SessionUsersPanel.tsx +++ b/client/src/components/SessionUsersPanel.tsx @@ -115,7 +115,7 @@ export default function SessionUsersPanel() { transform: `translate(${isLeft ? "0" : "-100%"}, ${ isTop ? "0" : "-100%" })`, - transition: "all 0.3s ease-out", + transition: "all 0.3s ease-in-out", }; }; diff --git a/client/src/components/modals/SoundCheckModal.tsx b/client/src/components/modals/SoundCheckModal.tsx index a786f10..a523e8d 100644 --- a/client/src/components/modals/SoundCheckModal.tsx +++ b/client/src/components/modals/SoundCheckModal.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect } from "react"; +import { useRef, useEffect, useState } from "react"; import ModalWrapper from "../ModalWrapper"; import Button from "../ui/Button"; import RestartIcon from "../icons/RestartIcon"; @@ -21,10 +21,46 @@ function SoundCheckModal({ }: SoundCheckModalProps) { const { setModal } = useModalStore(); const audioContextRef = useRef(null); + const animationFrameRef = useRef(null); + const [isPlaying, setIsPlaying] = useState(false); + const [playProgress, setPlayProgress] = useState(0); // Прогресс воспроизведения 0-1 + const playStartTimeRef = useRef(0); + + // Высоты баров эквалайзера (16 баров как в дизайне) + const barCount = 16; + const barHeights = [ + 3, 12, 16, 20, 44, 28, 34, 60, 8, 40, 18, 36, 24, 13, 10, 6, + ]; + + // Обновление прогресса воспроизведения + useEffect(() => { + if (!isPlaying) return; + + const updateProgress = () => { + const elapsed = Date.now() - playStartTimeRef.current; + const progress = Math.min(elapsed / 3000, 1); // 3 секунды + setPlayProgress(progress); + + if (progress < 1) { + animationFrameRef.current = requestAnimationFrame(updateProgress); + } + }; + + updateProgress(); + + return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } + }; + }, [isPlaying]); // Очистка AudioContext при размонтировании useEffect(() => { return () => { + if (animationFrameRef.current) { + cancelAnimationFrame(animationFrameRef.current); + } if (audioContextRef.current) { audioContextRef.current.close(); audioContextRef.current = null; @@ -42,6 +78,7 @@ function SoundCheckModal({ const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); + // Подключаем напрямую oscillator.connect(gainNode); gainNode.connect(audioContext.destination); @@ -54,12 +91,23 @@ function SoundCheckModal({ gainNode.gain.setValueAtTime(baseVolume * 0.3, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime( 0.01, - audioContext.currentTime + 1 + audioContext.currentTime + 3 ); - // Играем звук 1 секунду + // Запускаем анимацию прогресса + playStartTimeRef.current = Date.now(); + setIsPlaying(true); + setPlayProgress(0); + + // Играем звук 3 секунды oscillator.start(audioContext.currentTime); - oscillator.stop(audioContext.currentTime + 1); + oscillator.stop(audioContext.currentTime + 3); + + // Останавливаем анимацию после окончания звука + setTimeout(() => { + setIsPlaying(false); + setPlayProgress(0); + }, 3000); }; return ( @@ -76,20 +124,28 @@ function SoundCheckModal({
- {[3, 12, 16, 20, 44, 28, 34, 60, 8, 40, 18, 36, 24, 13, 10, 6].map( - (height, index) => ( + {barHeights.map((height, index) => { + // Определяем, заполнен ли бар синим (прогресс слева направо) + const barProgress = (index + 1) / barCount; + const isActivated = playProgress >= barProgress || !isPlaying; + + return (
- ) - )} + ); + })}
))} diff --git a/client/src/components/ui/UserCamera.tsx b/client/src/components/ui/UserCamera.tsx index 9b7a622..a6ba952 100644 --- a/client/src/components/ui/UserCamera.tsx +++ b/client/src/components/ui/UserCamera.tsx @@ -1,6 +1,4 @@ -import { AnimatePresence, motion } from "motion/react"; -import { useState } from "react"; - +import { useEffect, useRef } from "react"; import HandRaisedOffFilledIcon from "../icons/HandRaisedOffFilledIcon"; import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon"; import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon"; @@ -23,7 +21,7 @@ interface UserCameraControlsProps { interface UserCameraProps extends UserCameraControlsProps { isAdmin?: boolean; name?: string; - mediaStream?: string; + mediaStream?: MediaStream | null; isSpeaking?: boolean; } @@ -37,23 +35,25 @@ export default function UserCamera({ isSpeaking = false, isAdmin = false, name = "Гость", - mediaStream = "", + mediaStream = null, }: UserCameraProps) { - const [isHover, setIsHover] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + ref.current.srcObject = mediaStream; + } + }, [mediaStream]); return ( - setIsHover(true)} - onMouseLeave={() => setIsHover(false)} - animate={{ - width: isHover ? "10.833vw" : "6.944vw", - border: isSpeaking - ? "0.139vw solid #7B60F3" - : "0.139vw solid #FFFFFF4D", - }} +
{isAdmin && } @@ -66,7 +66,7 @@ export default function UserCamera({
+
); } @@ -90,63 +89,51 @@ function UserCameraControls({ isMuted, isVideoOff, isControlDisabled, - isHover, onMute, onVideoOff, onCanControl, -}: UserCameraControlsProps & { isHover: boolean }) { +}: UserCameraControlsProps) { return ( -
- - {isHover ? ( - - : } - size={"small"} - enabled={!isMuted} - onClick={onMute} - /> - : } - size={"small"} - enabled={!isVideoOff} - onClick={onVideoOff} - /> - - ) : ( - - ) - } - size={"small"} - enabled={!isControlDisabled} - onClick={onCanControl} - /> - - ) : ( - -
- -
-
+
+
+ > +
+ +
+
+
e.stopPropagation()} + > + : } + size={"small"} + disabled={isMuted} + onClick={onMute} + /> + : } + size={"small"} + disabled={isVideoOff} + onClick={onVideoOff} + /> + + ) : ( + + ) + } + size={"small"} + disabled={isControlDisabled} + onClick={onCanControl} + /> +
); } diff --git a/client/src/components/ui/UserDevicesControls.tsx b/client/src/components/ui/UserDevicesControls.tsx index 122123a..2eac422 100644 --- a/client/src/components/ui/UserDevicesControls.tsx +++ b/client/src/components/ui/UserDevicesControls.tsx @@ -23,29 +23,29 @@ export default function UserDevicesControls() { } return ( -
+
e.stopPropagation()} size="large" icon={} onClick={ToggleAudioDevice} - enabled={true} /> e.stopPropagation()} size="large" icon={} - enabled={true} onClick={ToggleVideoDevice} /> e.stopPropagation()} size="large" icon={} onClick={ToggleCanControl} - enabled={true} /> e.stopPropagation()} size="large" icon={} - enabled={true} onClick={ToggleSettings} />
diff --git a/client/src/pages/HomePage.tsx b/client/src/pages/HomePage.tsx index 1c9ecfd..ae9a568 100644 --- a/client/src/pages/HomePage.tsx +++ b/client/src/pages/HomePage.tsx @@ -7,8 +7,8 @@ import usePopupStore from "../store/popupStore"; import SettingsModal from "../components/modals/SettingsModal"; import useModalStore from "../store/modalStore"; import CogFilledIcon from "../components/icons/CogFilledIcon"; -import ParticipantsPopup from "../components/popups/ParticipantsPopup"; import SessionUsersPanel from "../components/SessionUsersPanel"; +import SharePopup from "../components/popups/SharePopup"; function HomePage() { const { data: user } = useMe(); @@ -33,7 +33,9 @@ function HomePage() { setPopup()} + onClick={() => + setPopup() + } >