153 lines
4.5 KiB
TypeScript
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>
|
|
);
|
|
}
|