180 lines
4.2 KiB
TypeScript
180 lines
4.2 KiB
TypeScript
import { useState, useEffect, useRef } from "react";
|
|
import { Paper, Box } from "@mantine/core";
|
|
import {
|
|
Avatar as MantineAvatar,
|
|
AvatarProps as MantineAvatarProps,
|
|
} from "@mantine/core";
|
|
|
|
interface GlitchAvatarProps
|
|
extends Omit<MantineAvatarProps, "radius" | "color" | "size"> {
|
|
name: string;
|
|
src?: string;
|
|
glitchSrc?: string;
|
|
size?: number;
|
|
radius?: string | number;
|
|
withBorder?: boolean;
|
|
contain?: boolean;
|
|
children?: React.ReactNode;
|
|
px?: string | number;
|
|
}
|
|
|
|
const GlitchAvatar = ({
|
|
name,
|
|
src,
|
|
glitchSrc,
|
|
size = 35,
|
|
radius = "100%",
|
|
withBorder = true,
|
|
contain = false,
|
|
children,
|
|
px,
|
|
...props
|
|
}: GlitchAvatarProps) => {
|
|
const [showGlitch, setShowGlitch] = useState(false);
|
|
const [isPlaying, setIsPlaying] = useState(false);
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
|
|
useEffect(() => {
|
|
if (!glitchSrc) return;
|
|
|
|
const scheduleNextGlitch = () => {
|
|
const delay = Math.random() * 10000 + 5000;
|
|
return setTimeout(() => {
|
|
setShowGlitch(true);
|
|
setIsPlaying(true);
|
|
|
|
setTimeout(() => {
|
|
setShowGlitch(false);
|
|
setIsPlaying(false);
|
|
scheduleNextGlitch();
|
|
}, 4000);
|
|
}, delay);
|
|
};
|
|
|
|
const timeoutId = scheduleNextGlitch();
|
|
return () => clearTimeout(timeoutId);
|
|
}, [glitchSrc]);
|
|
|
|
useEffect(() => {
|
|
const video = videoRef.current;
|
|
if (!video) return;
|
|
|
|
const handleEnded = () => {
|
|
setShowGlitch(false);
|
|
setIsPlaying(false);
|
|
};
|
|
|
|
video.addEventListener("ended", handleEnded);
|
|
return () => video.removeEventListener("ended", handleEnded);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
const video = videoRef.current;
|
|
if (!video) return;
|
|
|
|
video.load();
|
|
}, [glitchSrc]);
|
|
|
|
useEffect(() => {
|
|
const video = videoRef.current;
|
|
if (!video || !showGlitch || !isPlaying) return;
|
|
|
|
video.currentTime = 0;
|
|
video.play().catch((err) => {
|
|
console.error("Failed to play glitch", err);
|
|
});
|
|
}, [showGlitch, isPlaying]);
|
|
|
|
return (
|
|
<Box
|
|
style={{
|
|
padding: "8px",
|
|
borderRadius:
|
|
typeof radius === "number"
|
|
? `${radius + 8}px`
|
|
: "calc(var(--mantine-radius-md) + 8px)",
|
|
position: "relative",
|
|
}}
|
|
>
|
|
<Box
|
|
style={{
|
|
opacity: showGlitch ? 0 : 1,
|
|
transition: "opacity 0.05s ease-in-out",
|
|
}}
|
|
>
|
|
<Paper
|
|
py={size / 12.5}
|
|
px={size / 20}
|
|
bg="var(--mantine-color-default-border)"
|
|
radius={radius}
|
|
withBorder={false}
|
|
style={{
|
|
cursor: "default",
|
|
}}
|
|
>
|
|
<MantineAvatar
|
|
alt={name}
|
|
key={name}
|
|
name={name}
|
|
color="initials"
|
|
size={size}
|
|
radius={radius}
|
|
w={size}
|
|
styles={{
|
|
image: {
|
|
objectFit: contain ? "contain" : "cover",
|
|
},
|
|
}}
|
|
src={src}
|
|
{...props}
|
|
>
|
|
{children}
|
|
</MantineAvatar>
|
|
</Paper>
|
|
</Box>
|
|
|
|
{glitchSrc && (
|
|
<Box
|
|
style={{
|
|
position: "absolute",
|
|
top: "8px",
|
|
left: "8px",
|
|
opacity: showGlitch ? 1 : 0,
|
|
visibility: showGlitch ? "visible" : "hidden",
|
|
transition: "opacity 0.05s ease-in-out",
|
|
pointerEvents: "none",
|
|
}}
|
|
>
|
|
<Paper
|
|
py={size / 12.5}
|
|
px={size / 20}
|
|
bg="var(--mantine-color-default-border)"
|
|
radius={radius}
|
|
withBorder={false}
|
|
style={{
|
|
overflow: "hidden",
|
|
}}
|
|
>
|
|
<video
|
|
ref={videoRef}
|
|
src={glitchSrc}
|
|
style={{
|
|
width: `${size}px`,
|
|
height: `${size}px`,
|
|
objectFit: contain ? "contain" : "cover",
|
|
borderRadius: typeof radius === "number" ? `${radius}px` : radius,
|
|
display: "block",
|
|
}}
|
|
muted
|
|
playsInline
|
|
preload="auto"
|
|
/>
|
|
</Paper>
|
|
</Box>
|
|
)}
|
|
</Box>
|
|
);
|
|
};
|
|
|
|
export default GlitchAvatar;
|