Files
stream.graff.tech-new/client/src/components/ui/UserCamera.tsx
T

153 lines
4.5 KiB
TypeScript

import { AnimatePresence, motion } from "motion/react";
import { useState } from "react";
import HandRaisedOffFilledIcon from "../icons/HandRaisedOffFilledIcon";
import HandRaisedFilledIcon from "../icons/HandRaisedFilledIcon";
import MicrophoneFilledIcon from "../icons/MicrophoneFilledIcon";
import VideoOffFilledIcon from "../icons/VideoOffFilledIcon";
import MicrophoneOffIcon from "../icons/MicrophoneOffIcon";
import VideoFilledIcon from "../icons/VideoFilledIcon";
import ControlButton from "./ControlButton";
import Admin from "../indicators/Admin";
import clsx from "clsx";
interface UserCameraControlsProps {
isMuted: boolean;
isVideoOff: boolean;
isControlDisabled: boolean;
onMute: () => void;
onVideoOff: () => void;
onCanControl: () => void;
}
interface UserCameraProps extends UserCameraControlsProps {
isAdmin?: boolean;
name?: string;
mediaStream?: string;
isSpeaking?: boolean;
}
export default function UserCamera({
isMuted,
isVideoOff,
isControlDisabled,
onMute,
onVideoOff,
onCanControl,
isSpeaking = false,
isAdmin = false,
name = "Гость",
mediaStream = "",
}: UserCameraProps) {
const [isHover, setIsHover] = useState(false);
return (
<motion.div
onMouseEnter={() => setIsHover(true)}
onMouseLeave={() => setIsHover(false)}
animate={{
width: isHover ? "10.833vw" : "6.944vw",
border: isSpeaking
? "0.139vw solid #7B60F3"
: "0.139vw solid #FFFFFF4D",
}}
className={clsx(
"aspect-square group rounded-[1.667vw] bg-yellow-500 relative flex-shrink-0 transition-all duration-300 pointer-events-auto hover:w-[10.833vw]w-[6.944vw] shadow-[0_4px_40px_0_rgba(15,16,17,0.1),0_2px_2px_0_rgba(0,0,0,0.06)]",
isAdmin && "order-3"
)}
>
{isAdmin && <Admin className="absolute top-0 right-0" />}
<div
key="name"
className="absolute whitespace-nowrap transition-opacity duration-300 group-hover:opacity-100 opacity-0 text-white button-s top-[0.556vw] left-1/2 translate-x-[-50%] px-[0.556vw] py-[0.278vw] rounded-full bg-[#14141440] backdrop-blur-[4px]"
>
{name}
</div>
<video
src={mediaStream}
className="size-full object-cover"
autoPlay
muted={isMuted}
playsInline
/>
<UserCameraControls
isMuted={isMuted}
isVideoOff={isVideoOff}
isControlDisabled={isControlDisabled}
isHover={isHover}
onMute={onMute}
onVideoOff={onVideoOff}
onCanControl={onCanControl}
/>
</motion.div>
);
}
function UserCameraControls({
isMuted,
isVideoOff,
isControlDisabled,
isHover,
onMute,
onVideoOff,
onCanControl,
}: UserCameraControlsProps & { isHover: boolean }) {
return (
<div className="absolute bottom-[0.278vw] left-1/2 translate-x-[-50%]">
<AnimatePresence mode="wait">
{isHover ? (
<motion.div
key="controls"
className="flex gap-[0.278vw] mb-[0.278vw]"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<ControlButton
icon={isMuted ? <MicrophoneOffIcon /> : <MicrophoneFilledIcon />}
size={"small"}
enabled={!isMuted}
onClick={onMute}
/>
<ControlButton
icon={isVideoOff ? <VideoOffFilledIcon /> : <VideoFilledIcon />}
size={"small"}
enabled={!isVideoOff}
onClick={onVideoOff}
/>
<ControlButton
icon={
isControlDisabled ? (
<HandRaisedOffFilledIcon />
) : (
<HandRaisedFilledIcon />
)
}
size={"small"}
enabled={!isControlDisabled}
onClick={onCanControl}
/>
</motion.div>
) : (
<motion.div
key="controls-muted"
className="size-[1.667vw] bg-[#14141426] backdrop-blur-[4px] rounded-full flex items-center justify-center z-10"
initial={{ opacity: 0 }}
animate={{ opacity: isMuted ? 1 : 0 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
>
<div className="size-[0.972vw] text-white">
<MicrophoneOffIcon />
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
}