Merge remote-tracking branch 'origin/simulation-agv' into simulation

This commit is contained in:
Jerald-Golden-B 2025-03-29 18:15:41 +05:30
commit df571e18fb
19 changed files with 509 additions and 172 deletions

19
app/package-lock.json generated
View File

@ -4133,25 +4133,6 @@
"url": "https://github.com/sponsors/gregberge"
}
},
"node_modules/@testing-library/dom": {
"version": "10.4.0",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz",
"integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"chalk": "^4.1.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@testing-library/jest-dom": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",

View File

@ -5,7 +5,6 @@ const Templates = () => {
const { templates, removeTemplate } = useTemplateStore();
const { setSelectedZone } = useSelectedZoneStore();
console.log("templates: ", templates);
const handleDeleteTemplate = (id: string) => {
removeTemplate(id);
};

View File

@ -1,14 +1,17 @@
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
);
return (
<div className="header-container">
@ -25,18 +28,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

@ -14,7 +14,10 @@ import ConveyorMechanics from "./mechanics/ConveyorMechanics";
import Visualization from "./visualization/Visualization";
import Analysis from "./analysis/Analysis";
import Simulations from "./simulation/Simulations";
import { useSelectedActionSphere } from "../../../store/store";
import {
useSelectedActionSphere,
useselectedFloorItem,
} from "../../../store/store";
import GlobalProperties from "./properties/GlobalProperties";
import AsstePropertiies from "./properties/AssetProperties";
import ZoneProperties from "./properties/ZoneProperties";
@ -25,6 +28,7 @@ const SideBarRight: React.FC = () => {
const { toggleUI } = useToggleStore();
const { selectedActionSphere } = useSelectedActionSphere();
const { subModule, setSubModule } = useSubModuleStore();
const { selectedFloorItem } = useselectedFloorItem();
// Reset activeList whenever activeModule changes
useEffect(() => {
if (activeModule !== "simulation") setSubModule("properties");
@ -38,8 +42,9 @@ const SideBarRight: React.FC = () => {
<div className="sidebar-actions-container">
{/* {activeModule === "builder" && ( */}
<div
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
}`}
className={`sidebar-action-list ${
subModule === "properties" ? "active" : ""
}`}
onClick={() => setSubModule("properties")}
>
<PropertiesIcon isActive={subModule === "properties"} />
@ -48,22 +53,25 @@ const SideBarRight: React.FC = () => {
{activeModule === "simulation" && (
<>
<div
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
}`}
className={`sidebar-action-list ${
subModule === "mechanics" ? "active" : ""
}`}
onClick={() => setSubModule("mechanics")}
>
<MechanicsIcon isActive={subModule === "mechanics"} />
</div>
<div
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
}`}
className={`sidebar-action-list ${
subModule === "simulations" ? "active" : ""
}`}
onClick={() => setSubModule("simulations")}
>
<SimulationIcon isActive={subModule === "simulations"} />
</div>
<div
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
}`}
className={`sidebar-action-list ${
subModule === "analysis" ? "active" : ""
}`}
onClick={() => setSubModule("analysis")}
>
<AnalysisIcon isActive={subModule === "analysis"} />
@ -75,12 +83,21 @@ const SideBarRight: React.FC = () => {
{/* process builder */}
{toggleUI &&
subModule === "properties" &&
activeModule !== "visualization" && (
activeModule !== "visualization" &&
!selectedFloorItem && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<GlobalProperties />
{/* <ZoneProperties /> */}
{/* <AsstePropertiies /> */}
</div>
</div>
)}
{toggleUI &&
subModule === "properties" &&
activeModule !== "visualization" &&
selectedFloorItem && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<AsstePropertiies />
</div>
</div>
)}
@ -89,9 +106,7 @@ const SideBarRight: React.FC = () => {
activeModule === "builder" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
{/* <GlobalProperties /> */}
<ZoneProperties />
{/* <AsstePropertiies /> */}
</div>
</div>
)}
@ -99,20 +114,24 @@ const SideBarRight: React.FC = () => {
{toggleUI && activeModule === "simulation" && (
<>
{subModule === "mechanics" && selectedActionSphere && selectedActionSphere.path.type === "Conveyor" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<ConveyorMechanics />
{subModule === "mechanics" &&
selectedActionSphere &&
selectedActionSphere.path.type === "Conveyor" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<ConveyorMechanics />
</div>
</div>
</div>
)}
{subModule === "mechanics" && selectedActionSphere && selectedActionSphere.path.type === "Vehicle" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<VehicleMechanics />
)}
{subModule === "mechanics" &&
selectedActionSphere &&
selectedActionSphere.path.type === "Vehicle" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
<VehicleMechanics />
</div>
</div>
</div>
)}
)}
{subModule === "mechanics" && !selectedActionSphere && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">

View File

@ -4,12 +4,16 @@ interface PositionInputProps {
onChange: (value: string) => void; // Callback for value change
placeholder?: string; // Optional placeholder
type?: string; // Input type (e.g., text, number, email)
value1?: number;
value2?: number;
}
const PositionInput: React.FC<PositionInputProps> = ({
onChange,
placeholder = "Enter value", // Default placeholder
type = "number", // Default type
value1 = "number",
value2 = "number",
}) => {
return (
<div className="custom-input-container">
@ -22,6 +26,7 @@ const PositionInput: React.FC<PositionInputProps> = ({
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
value={value2}
/>
</div>
<div className="input-container">
@ -31,6 +36,7 @@ const PositionInput: React.FC<PositionInputProps> = ({
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
value={value1}
/>
</div>
</div>

View File

@ -4,17 +4,19 @@ interface RotationInputProps {
onChange: (value: string) => void; // Callback for value change
placeholder?: string; // Optional placeholder
type?: string; // Input type (e.g., text, number, email)
value?: number;
}
const RotationInput: React.FC<RotationInputProps> = ({
onChange,
placeholder = "Enter value", // Default placeholder
type = "number", // Default type
value = "number",
}) => {
return (
<div className="custom-input-container">
<div className="header">Rotation</div>
<div className="inputs-container" style={{display: "block"}}>
<div className="inputs-container" style={{ display: "block" }}>
<div className="input-container">
<div className="custom-input-label">Rotate : </div>
<input
@ -22,6 +24,7 @@ const RotationInput: React.FC<RotationInputProps> = ({
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
value={value}
/>
</div>
</div>

View File

@ -1,9 +1,11 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import InputToggle from "../../../ui/inputs/InputToggle";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { RemoveIcon } from "../../../icons/ExportCommonIcons";
import PositionInput from "../customInput/PositionInputs";
import RotationInput from "../customInput/RotationInput";
import { useselectedFloorItem } from "../../../../store/store";
import * as THREE from "three";
interface UserData {
id: number; // Unique identifier for the user data
@ -14,7 +16,13 @@ interface UserData {
const AssetProperties: React.FC = () => {
const [userData, setUserData] = useState<UserData[]>([]); // State to track user data
const [nextId, setNextId] = useState(1); // Unique ID for new entries
const { selectedFloorItem } = useselectedFloorItem();
let xValue = selectedFloorItem.position.x;
let zValue = selectedFloorItem.position.z;
let rotationRad = selectedFloorItem.rotation.y;
let rotationDeg = THREE.MathUtils.radToDeg(rotationRad);
// useEffect(() => {}, [selectedFloorItem]);
// Function to handle adding new user data
const handleAddUserData = () => {
const newUserData: UserData = {
@ -45,12 +53,16 @@ const AssetProperties: React.FC = () => {
return (
<div className="asset-properties-container">
{/* Name */}
<div className="header">Selected Object</div>
<div className="header">{selectedFloorItem.userData.name}</div>
<div className="split"></div>
<PositionInput onChange={() => { }} />
<RotationInput onChange={() => { }} />
<PositionInput
onChange={() => {}}
value1={xValue.toFixed(5)}
value2={zValue.toFixed(5)}
/>
<RotationInput onChange={() => {}} value={rotationDeg} />
<div className="split"></div>

View File

@ -3,8 +3,31 @@ import InputRange from "../../../ui/inputs/InputRange";
import InputToggle from "../../../ui/inputs/InputToggle";
import { AI_Icon } from "../../../icons/ExportCommonIcons";
import LabeledButton from "../../../ui/inputs/LabledButton";
import {
useAzimuth,
useElevation,
useRenderDistance,
useResetCamera,
useRoofVisibility,
useSelectedWallItem,
useShadows,
useSocketStore,
useToggleView,
useWallVisibility,
} from "../../../../store/store";
import { setEnvironment } from "../../../../services/factoryBuilder/environment/setEnvironment";
const GlobalProperties: React.FC = () => {
const { toggleView, setToggleView } = useToggleView();
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
const { wallVisibility, setWallVisibility } = useWallVisibility();
const { shadows, setShadows } = useShadows();
const { resetCamera, setResetCamera } = useResetCamera();
const { elevation, setElevation } = useElevation();
const { azimuth, setAzimuth } = useAzimuth();
const { renderDistance, setRenderDistance } = useRenderDistance();
const { socket } = useSocketStore();
const [limitDistance, setLimitDistance] = useState(false);
const [distance, setDistance] = useState<number>(5);
@ -23,6 +46,93 @@ const GlobalProperties: React.FC = () => {
function updateGridDistance(value: number) {
setGridDistance(value);
}
// Function to toggle roof visibility
const changeRoofVisibility = async () => {
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
//using REST
const data = await setEnvironment(
organization,
localStorage.getItem("userId")!,
wallVisibility,
!roofVisibility,
shadows
);
// console.log('data: ', data);
//using Socket
// const visData = {
// organization: organization,
// userId: localStorage.getItem('userId')!,
// wallVisibility: wallVisibility,
// roofVisibility: !roofVisibility,
// shadowVisibility: shadows,
// socketId: socket.id
// };
// socket.emit('v1:Environment:set', visData)
setRoofVisibility(!roofVisibility); // Toggle roof visibility
};
// Function to toggle wall visibility
const changeWallVisibility = async () => {
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
//using REST
const data = await setEnvironment(
organization,
localStorage.getItem("userId")!,
!wallVisibility,
roofVisibility,
shadows
);
// console.log('data: ', data);
//using Socket
// const visData = {
// organization: organization,
// userId: localStorage.getItem('userId')!,
// wallVisibility: !wallVisibility,
// roofVisibility: roofVisibility,
// shadowVisibility: shadows,
// socketId: socket.id
// };
// socket.emit('v1:Environment:set', visData)
setWallVisibility(!wallVisibility); // Toggle wall visibility
};
const shadowVisibility = async () => {
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
//using REST
const data = await setEnvironment(
organization,
localStorage.getItem("userId")!,
wallVisibility,
roofVisibility,
!shadows
);
// console.log('data: ', data);
//using Socket
// const visData = {
// organization: organization,
// userId: localStorage.getItem('userId')!,
// wallVisibility: wallVisibility,
// roofVisibility: roofVisibility,
// shadowVisibility: !shadows,
// socketId: socket.id
// };
// socket.emit('v1:Environment:set', visData)
setShadows(!shadows);
};
const toggleResetCamera = () => {
if (!toggleView) {
setResetCamera(true); // Trigger reset camera action
}
};
return (
<div className="global-properties-container">
@ -34,10 +144,29 @@ const GlobalProperties: React.FC = () => {
<div className="split"></div>
<InputToggle inputKey="1" label="Roof Visibility" />
<InputToggle inputKey="2" label="Wall Visibility" />
<InputToggle inputKey="3" label="Shadows Visibility" />
<LabeledButton label="Reset Camera" onClick={() => {}} value="Reset"/>
<InputToggle
value={roofVisibility}
inputKey="1"
label="Roof Visibility"
onClick={changeRoofVisibility}
/>
<InputToggle
value={wallVisibility}
inputKey="2"
label="Wall Visibility"
onClick={changeWallVisibility}
/>
<InputToggle
value={shadows}
inputKey="3"
label="Shadows Visibility"
onClick={shadowVisibility}
/>
<LabeledButton
label="Reset Camera"
onClick={toggleResetCamera}
value="Reset"
/>
<div className="split"></div>

View File

@ -166,8 +166,10 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
return (
<div
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") && "bottom"
}`}
ref={containerRef}
className={`zone-wrapper ${
selectedZone?.activeSides?.includes("bottom") && "bottom"
}`}
>
{/* Left Arrow */}
{showLeftArrow && (

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, activeUsers]);
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

@ -29,11 +29,10 @@ const FilterSearch: React.FC<ModelsProps> = ({
filteredModels,
}) => {
const [activeOption, setActiveOption] = useState("Sort by"); // State for active option
console.log("filteredModels: ", filteredModels);
const handleSelect = (option: string) => {
setActiveOption(option);
console.log("option: ", option);
// Alphabet ascending
// Alphabet descending
// All

View File

@ -47,10 +47,9 @@
border-radius: $border-radius-large;
.search-container {
border: none !important;
box-shadow: $box-shadow-medium;
border-radius: $border-radius-large;
outline: 1px solid var(--border-color);
border: none;
input {
border: none !important;
outline: none;
@ -60,6 +59,12 @@
.regularDropdown-container {
max-width: 159px;
height: 100%;
border-radius: #{$border-radius-large};
border: 1px solid var(--border-color);
.dropdown-header {
align-items: center;
}
}
.button {
@ -128,7 +133,7 @@
justify-content: center;
border-radius: #{$border-radius-medium};
overflow: hidden;
img{
img {
height: inherit;
width: 100%;
object-fit: cover;
@ -143,7 +148,7 @@
display: flex;
flex-direction: column;
gap: 3px;
.assets-name{
.assets-name {
text-transform: capitalize;
}

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
};