From c968f7865b782536258504abb442e8d94d7f756f Mon Sep 17 00:00:00 2001 From: Vishnu Date: Fri, 28 Mar 2025 19:20:52 +0530 Subject: [PATCH] avatar started --- .../components/layout/sidebarRight/Header.tsx | 43 +++-- .../components/ui/componets/DisplayZone.tsx | 2 +- app/src/modules/collaboration/collabCams.tsx | 155 ++++++++++++------ .../modules/collaboration/collabUserIcon.tsx | 66 +++----- .../modules/collaboration/users/Avatar.tsx | 59 +++++++ .../users/functions/getAvatarColor.ts | 26 +++ .../users/functions/getInitials.ts | 10 ++ app/src/styles/layout/popup.scss | 26 +++ app/src/styles/layout/sidebar.scss | 7 +- app/src/types/users.d.ts | 7 + 10 files changed, 292 insertions(+), 109 deletions(-) create mode 100644 app/src/modules/collaboration/users/Avatar.tsx create mode 100644 app/src/modules/collaboration/users/functions/getAvatarColor.ts create mode 100644 app/src/modules/collaboration/users/functions/getInitials.ts diff --git a/app/src/components/layout/sidebarRight/Header.tsx b/app/src/components/layout/sidebarRight/Header.tsx index b80fd53..9abf096 100644 --- a/app/src/components/layout/sidebarRight/Header.tsx +++ b/app/src/components/layout/sidebarRight/Header.tsx @@ -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 (
@@ -25,18 +29,23 @@ const Header: React.FC = () => {
+{guestUsers.length - 3}
)} {guestUsers.slice(0, 3).map((user, index) => ( -
- {user.value[0]} -
+ <> +
+ {user.userName[0]} +
+ ))}
-
- V +
+ {userName[0]}
diff --git a/app/src/components/ui/componets/DisplayZone.tsx b/app/src/components/ui/componets/DisplayZone.tsx index 15e8bda..c93f275 100644 --- a/app/src/components/ui/componets/DisplayZone.tsx +++ b/app/src/components/ui/componets/DisplayZone.tsx @@ -144,7 +144,7 @@ const DisplayZone: React.FC = ({ return (
diff --git a/app/src/modules/collaboration/collabCams.tsx b/app/src/modules/collaboration/collabCams.tsx index da20b34..16e0e29 100644 --- a/app/src/modules/collaboration/collabCams.tsx +++ b/app/src/modules/collaboration/collabCams.tsx @@ -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(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([]); - const [models, setModels] = useState>({}); + 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 ( {cams.map((cam, index) => ( - + { textAlign: "center", fontFamily: "Arial, sans-serif", }} - position={[-0.015, 0, 0.7]}> - + position={[-0.015, 0, 0.7]} + > + ))} diff --git a/app/src/modules/collaboration/collabUserIcon.tsx b/app/src/modules/collaboration/collabUserIcon.tsx index a6c22c0..acc3fa0 100644 --- a/app/src/modules/collaboration/collabUserIcon.tsx +++ b/app/src/modules/collaboration/collabUserIcon.tsx @@ -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 = ({ - color, - userImage, - userName, + userImage, + userName, + index = 0, + color, }) => { - return ( -
- {userName} -
- {userName} -
-
- ); + return ( +
+
+ {userImage ? ( + {userName} + ) : ( + + )} +
+
+ {userName} +
+
+ ); }; export default CollabUserIcon; diff --git a/app/src/modules/collaboration/users/Avatar.tsx b/app/src/modules/collaboration/users/Avatar.tsx new file mode 100644 index 0000000..93d45c3 --- /dev/null +++ b/app/src/modules/collaboration/users/Avatar.tsx @@ -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 = ({ + name, + size = 100, + index = 0, + textColor = "#ffffff", +}) => { + const [imageSrc, setImageSrc] = useState(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 ( + User Avatar + ); +}; + +export default CustomAvatar; diff --git a/app/src/modules/collaboration/users/functions/getAvatarColor.ts b/app/src/modules/collaboration/users/functions/getAvatarColor.ts new file mode 100644 index 0000000..d3186d2 --- /dev/null +++ b/app/src/modules/collaboration/users/functions/getAvatarColor.ts @@ -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]; +} diff --git a/app/src/modules/collaboration/users/functions/getInitials.ts b/app/src/modules/collaboration/users/functions/getInitials.ts new file mode 100644 index 0000000..5ebaa19 --- /dev/null +++ b/app/src/modules/collaboration/users/functions/getInitials.ts @@ -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; +}; \ No newline at end of file diff --git a/app/src/styles/layout/popup.scss b/app/src/styles/layout/popup.scss index a086cf4..20ea3f2 100644 --- a/app/src/styles/layout/popup.scss +++ b/app/src/styles/layout/popup.scss @@ -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; + } +} diff --git a/app/src/styles/layout/sidebar.scss b/app/src/styles/layout/sidebar.scss index d595b92..b248ad2 100644 --- a/app/src/styles/layout/sidebar.scss +++ b/app/src/styles/layout/sidebar.scss @@ -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; diff --git a/app/src/types/users.d.ts b/app/src/types/users.d.ts index 9e8c35a..55d5361 100644 --- a/app/src/types/users.d.ts +++ b/app/src/types/users.d.ts @@ -8,4 +8,11 @@ export interface User { type AccessOption = { option: string; +}; + +export type ActiveUser = { + _id: string; + userName: string; + email: string; + activeStatus?: string; // Optional property }; \ No newline at end of file