avatar started

This commit is contained in:
Vishnu 2025-03-28 19:20:52 +05:30
parent f058508b50
commit c968f7865b
10 changed files with 292 additions and 109 deletions

View File

@ -1,14 +1,18 @@
import React from "react";
import React, { useEffect } from "react";
import { AppDockIcon } from "../../icons/HeaderIcons";
import orgImg from "../../../assets/orgTemp.png"
import orgImg from "../../../assets/orgTemp.png";
import { useActiveUsers } from "../../../store/store";
import { getAvatarColor } from "../../../modules/collaboration/users/functions/getAvatarColor";
import { ActiveUser } from "../../../types/users";
const Header: React.FC = () => {
const guestUsers = [
{ value: "Nazria", color: "#43C06D" },
{ value: "Name1", color: "#0050EB" },
{ value: "Abigail", color: "#FF6600" },
{ value: "Jack", color: "#488EF6" },
]; // Example guest users array
const { activeUsers } = useActiveUsers();
const userName = localStorage.getItem("userName") || "Anonymous";
const guestUsers: ActiveUser[] = activeUsers.filter(
(user: ActiveUser) => user.userName !== userName
);
console.log('guestUsers: ', guestUsers);
return (
<div className="header-container">
@ -25,18 +29,23 @@ const Header: React.FC = () => {
<div className="other-guest">+{guestUsers.length - 3}</div>
)}
{guestUsers.slice(0, 3).map((user, index) => (
<div
key={index}
className="user-profile"
style={{ background: user.color }}
>
{user.value[0]}
</div>
<>
<div
key={index}
className="user-profile"
style={{ background: getAvatarColor(index) }}
>
{user.userName[0]}
</div>
</>
))}
</div>
<div className="user-profile-container">
<div className="user-profile" style={{ background: "#48AC2A" }}>
V
<div
className="user-profile"
style={{ background: "var(--accent-color)" }}
>
{userName[0]}
</div>
<div className="user-organization">
<img src={orgImg} alt="" />

View File

@ -144,7 +144,7 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
return (
<div
ref={containerRef}
className={`zoon-wrapper ${
className={`zone-wrapper ${
selectedZone?.activeSides?.includes("bottom") && "bottom"
}`}
>

View File

@ -1,112 +1,169 @@
import * as THREE from 'three';
import { useEffect, useRef, useState } from 'react';
import { useFrame } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import camModel from '../../assets/gltf-glb/camera face 2.gltf';
import getActiveUsersData from '../../services/factoryBuilder/collab/getActiveUsers';
import { useActiveUsers, useSocketStore } from '../../store/store';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
import { useNavigate } from 'react-router-dom';
import { Text, Html } from '@react-three/drei';
import CollabUserIcon from './collabUserIcon';
import image from '../../assets/image/userImage.png';
import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useFrame } from "@react-three/fiber";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import camModel from "../../assets/gltf-glb/camera face 2.gltf";
import getActiveUsersData from "../../services/factoryBuilder/collab/getActiveUsers";
import { useActiveUsers, useSocketStore } from "../../store/store";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { useNavigate } from "react-router-dom";
import { Html } from "@react-three/drei";
import CollabUserIcon from "./collabUserIcon";
import { getAvatarColor } from "./users/functions/getAvatarColor";
const CamModelsGroup = () => {
let navigate = useNavigate();
const groupRef = useRef<THREE.Group>(null);
const email = localStorage.getItem('email');
const email = localStorage.getItem("email");
const { activeUsers, setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler }>>({});
const [models, setModels] = useState<
Record<
string,
{ targetPosition: THREE.Vector3; targetRotation: THREE.Euler }
>
>({});
dracoLoader.setDecoderPath('three/examples/jsm/libs/draco/gltf/');
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
useEffect(() => {
if (!email) {
navigate('/');
navigate("/");
}
if (!socket) return;
const organization = email!.split('@')[1].split('.')[0];
const organization = email!.split("@")[1].split(".")[0];
socket.on('userConnectRespones', (data: any) => {
socket.on("userConnectRespones", (data: any) => {
if (!groupRef.current) return;
if (data.data.userData.email === email) return
if (socket.id === data.socketId || organization !== data.organization) return;
if (data.data.userData.email === email) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
const model = groupRef.current.getObjectByProperty('uuid', data.data.userData._id);
const model = groupRef.current.getObjectByProperty(
"uuid",
data.data.userData._id
);
if (model) {
groupRef.current.remove(model);
}
loader.load(camModel, (gltf) => {
const newModel = gltf.scene.clone();
newModel.uuid = data.data.userData._id;
newModel.position.set(data.data.position.x, data.data.position.y, data.data.position.z);
newModel.rotation.set(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z);
newModel.position.set(
data.data.position.x,
data.data.position.y,
data.data.position.z
);
newModel.rotation.set(
data.data.rotation.x,
data.data.rotation.y,
data.data.rotation.z
);
newModel.userData = data.data.userData;
setCams((prev) => [...prev, newModel]);
setActiveUsers([...activeUsers, data.data.userData]);
});
});
socket.on('userDisConnectRespones', (data: any) => {
socket.on("userDisConnectRespones", (data: any) => {
if (!groupRef.current) return;
if (socket.id === data.socketId || organization !== data.organization) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
setCams((prev) => prev.filter((cam) => cam.uuid !== data.data.userData._id));
setActiveUsers(activeUsers.filter((user: any) => user._id !== data.data.userData._id));
setCams((prev) =>
prev.filter((cam) => cam.uuid !== data.data.userData._id)
);
setActiveUsers(
activeUsers.filter((user: any) => user._id !== data.data.userData._id)
);
});
socket.on('cameraUpdateResponse', (data: any) => {
if (!groupRef.current || socket.id === data.socketId || organization !== data.organization) return;
socket.on("cameraUpdateResponse", (data: any) => {
if (
!groupRef.current ||
socket.id === data.socketId ||
organization !== data.organization
)
return;
setModels((prev) => ({
...prev,
[data.data.userId]: {
targetPosition: new THREE.Vector3(data.data.position.x, data.data.position.y, data.data.position.z),
targetRotation: new THREE.Euler(data.data.rotation.x, data.data.rotation.y, data.data.rotation.z),
targetPosition: new THREE.Vector3(
data.data.position.x,
data.data.position.y,
data.data.position.z
),
targetRotation: new THREE.Euler(
data.data.rotation.x,
data.data.rotation.y,
data.data.rotation.z
),
},
}));
});
return () => {
socket.off('userConnectRespones');
socket.off('userDisConnectRespones');
socket.off('cameraUpdateResponse');
socket.off("userConnectRespones");
socket.off("userDisConnectRespones");
socket.off("cameraUpdateResponse");
};
}, [socket]);
useFrame(() => {
if (!groupRef.current) return;
Object.keys(models).forEach((uuid) => {
const model = groupRef.current!.getObjectByProperty('uuid', uuid);
const model = groupRef.current!.getObjectByProperty("uuid", uuid);
if (!model) return;
const { targetPosition, targetRotation } = models[uuid];
model.position.lerp(targetPosition, 0.1);
model.rotation.x = THREE.MathUtils.lerp(model.rotation.x, targetRotation.x, 0.1);
model.rotation.y = THREE.MathUtils.lerp(model.rotation.y, targetRotation.y, 0.1);
model.rotation.z = THREE.MathUtils.lerp(model.rotation.z, targetRotation.z, 0.1);
model.rotation.x = THREE.MathUtils.lerp(
model.rotation.x,
targetRotation.x,
0.1
);
model.rotation.y = THREE.MathUtils.lerp(
model.rotation.y,
targetRotation.y,
0.1
);
model.rotation.z = THREE.MathUtils.lerp(
model.rotation.z,
targetRotation.z,
0.1
);
});
});
useEffect(() => {
if (!groupRef.current) return;
const organization = email!.split('@')[1].split('.')[0];
const organization = email!.split("@")[1].split(".")[0];
getActiveUsersData(organization).then((data) => {
const filteredData = data.cameraDatas.filter((camera: any) => camera.userData.email !== email);
const filteredData = data.cameraDatas.filter(
(camera: any) => camera.userData.email !== email
);
if (filteredData.length > 0) {
loader.load(camModel, (gltf) => {
const newCams = filteredData.map((cam: any) => {
const newModel = gltf.scene.clone();
newModel.uuid = cam.userData._id;
newModel.position.set(cam.position.x, cam.position.y, cam.position.z);
newModel.rotation.set(cam.rotation.x, cam.rotation.y, cam.rotation.z);
newModel.position.set(
cam.position.x,
cam.position.y,
cam.position.z
);
newModel.rotation.set(
cam.rotation.x,
cam.rotation.y,
cam.rotation.z
);
newModel.userData = cam.userData;
console.log('cam.userData: ', cam.userData);
setActiveUsers([...activeUsers, cam.userData]);
return newModel;
});
@ -119,7 +176,7 @@ const CamModelsGroup = () => {
return (
<group ref={groupRef} name="Cam-Model-Group">
{cams.map((cam, index) => (
<primitive key={index} object={cam} >
<primitive key={index} object={cam}>
<Html
as="div"
center
@ -130,8 +187,14 @@ const CamModelsGroup = () => {
textAlign: "center",
fontFamily: "Arial, sans-serif",
}}
position={[-0.015, 0, 0.7]}>
<CollabUserIcon color={"#ff0000"} userImage={image} userName={cam.userData.userName} />
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={""}
userName={cam.userData.userName}
index={index}
color={getAvatarColor(index)}
/>
</Html>
</primitive>
))}

View File

@ -1,53 +1,33 @@
import React from "react";
import CustomAvatar from "./users/Avatar";
interface CollabUserIconProps {
color: string;
userImage: string;
userName: string;
userName: string;
userImage?: string;
index?: number;
color: string;
}
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
color,
userImage,
userName,
userImage,
userName,
index = 0,
color,
}) => {
return (
<div
style={{
display: "flex",
justifyContent: "center",
alignItems: "center",
flexDirection: "column",
gap: "6px",
// transform:"translate(-20%, 0%)",
}}
>
<img
src={userImage}
alt={userName}
style={{
width: "30px",
height: "30px",
outline: `2px solid ${color}`,
borderRadius: "50%",
objectFit: 'cover'
}}
/>
<div
style={{
display: 'flex',
padding: "3px 5px",
backgroundColor: color,
borderRadius: "6px",
color: "white",
fontSize: "14px",
fontWeight: 400
}}
>
{userName}
</div>
</div>
);
return (
<div className="collab-user-live-container">
<div className="user-image-container">
{userImage ? (
<img className="user-image" src={userImage} alt={userName} />
) : (
<CustomAvatar name={userName} index={index} />
)}
</div>
<div className="user-name" style={{ backgroundColor: color }}>
{userName}
</div>
</div>
);
};
export default CollabUserIcon;

View File

@ -0,0 +1,59 @@
import React, { useEffect, useState } from "react";
import { getInitials } from "./functions/getInitials";
import { getAvatarColor } from "./functions/getAvatarColor";
interface AvatarProps {
name: string; // Name can be a full name or initials
size?: number;
index?: number;
textColor?: string;
}
const CustomAvatar: React.FC<AvatarProps> = ({
name,
size = 100,
index = 0,
textColor = "#ffffff",
}) => {
const [imageSrc, setImageSrc] = useState<string | null>(null);
useEffect(() => {
const canvas = document.createElement("canvas"); // Create an offscreen canvas
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext("2d");
if (ctx) {
const initials = getInitials(name); // Convert name to initials if needed
// Draw background
ctx.fillStyle = getAvatarColor(index);
ctx.fillRect(0, 0, size, size);
// Draw initials
ctx.fillStyle = textColor;
ctx.font = `bold ${size / 2}px Arial`;
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(initials, size / 2, size / 2);
// Generate image source
const dataURL = canvas.toDataURL("image/png");
setImageSrc(dataURL);
}
}, [name, size, textColor]);
if (!imageSrc) {
return null; // Return null while the image is being generated
}
return (
<img
className="user-image"
src={imageSrc}
alt="User Avatar"
style={{ width: "100%", height: "100%" }}
/>
);
};
export default CustomAvatar;

View File

@ -0,0 +1,26 @@
const avatarColors: string[] = [
"#FF5733", // Red Orange
"#48ac2a", // Leaf Green
"#0050eb", // Royal Blue
"#FF33A1", // Hot Pink
"#FF8C33", // Deep Orange
"#8C33FF", // Violet
"#FF3333", // Bright Red
"#43c06d", // Emerald Green
"#A133FF", // Amethyst Purple
"#C70039", // Crimson
"#900C3F", // Maroon
"#581845", // Plum
"#3498DB", // Sky Blue
"#2ECC71", // Green Mint
"#E74C3C", // Tomato Red
"#00adff", // Azure
"#DBAD05", // Amber Yellow
"#FF5733", // Red Orange
"#FF33A1", // Hot Pink
"#900C3F", // Maroon
];
export function getAvatarColor(index: number): string {
return avatarColors[index % avatarColors.length];
}

View File

@ -0,0 +1,10 @@
export const getInitials = (fullName: string): string => {
// Extract initials from the name
const words = fullName.split(" ");
const initials = words
.map((word) => word[0])
.slice(0, 2)
.join("")
.toUpperCase();
return initials;
};

View File

@ -110,3 +110,29 @@
}
}
}
.collab-user-live-container{
@include flex-center;
flex-direction: column;
gap: 6px;
.user-image-container{
height: 30px;
width: 30px;
border-radius: #{$border-radius-circle};
overflow: hidden;
.user-image{
height: 100%;
width: 100%;
object-fit: cover;
vertical-align: top;
}
}
.user-name{
padding: 4px 6px;
border-radius: #{$border-radius-small};
color: white;
font-size: var(--font-size-regulaar);
font-weight: var(--font-size-regulaar);
text-transform: capitalize;
}
}

View File

@ -225,11 +225,13 @@
border-radius: 50%;
font-weight: var(--font-weight-bold);
color: white;
text-transform: capitalize;
}
.guest-users-container {
display: flex;
width: 100%;
justify-content: flex-end;
.other-guest {
@include flex-center;
height: 26px;
@ -249,7 +251,8 @@
display: flex;
.user-organization {
height: 100%;
height: 26px;
width: 52px;
max-width: 52px;
border-radius: 20px;
overflow: hidden;

View File

@ -8,4 +8,11 @@ export interface User {
type AccessOption = {
option: string;
};
export type ActiveUser = {
_id: string;
userName: string;
email: string;
activeStatus?: string; // Optional property
};