first commit

This commit is contained in:
2025-06-10 15:28:23 +05:30
commit e22a2dc275
699 changed files with 100382 additions and 0 deletions

View File

@@ -0,0 +1,311 @@
import * as THREE from "three";
import { useEffect, useRef, useState } from "react";
import { useFrame, useThree } 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, useCamMode, useSocketStore } from "../../../store/builder/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 useModuleStore from "../../../store/useModuleStore";
import { getAvatarColor } from "../functions/getAvatarColor";
import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
import setCameraView from "../functions/setCameraView";
const CamModelsGroup = () => {
const navigate = useNavigate();
const groupRef = useRef<THREE.Group>(null);
const email = localStorage.getItem("email");
const { setActiveUsers } = useActiveUsers();
const { socket } = useSocketStore();
const { activeModule } = useModuleStore();
const { selectedUser, setSelectedUser } = useSelectedUserStore();
const { isPlaying } = usePlayButtonStore();
// eslint-disable-next-line react-hooks/exhaustive-deps
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath("three/examples/jsm/libs/draco/gltf/");
loader.setDRACOLoader(dracoLoader);
const { camMode } = useCamMode();
const { camera, controls } = useThree(); // Access R3F camera and controls
useEffect(() => {
if (camMode !== "FollowPerson") return;
// If a user is selected, set the camera view to their location
// and update the camera and controls accordingly
if (selectedUser?.location) {
const { position, rotation, target } = selectedUser.location;
if (rotation && target)
setCameraView({
controls,
camera,
position,
rotation,
target,
username: selectedUser.name,
});
}
}, [selectedUser, camera, controls, camMode]);
const [cams, setCams] = useState<any[]>([]);
const [models, setModels] = useState<
Record<
string,
{
targetPosition: THREE.Vector3;
targetRotation: THREE.Euler;
target: THREE.Vector3;
}
>
>({});
const dedupeCams = (cams: any[]) => {
const seen = new Set();
return cams.filter((cam) => {
if (seen.has(cam.uuid)) return false;
seen.add(cam.uuid);
return true;
});
};
const dedupeUsers = (users: any[]) => {
const seen = new Set();
return users.filter((user) => {
if (seen.has(user._id)) return false;
seen.add(user._id);
return true;
});
};
useEffect(() => {
if (!email) navigate("/");
if (!socket) return;
const organization = email!.split("@")[1].split(".")[0];
socket.on("userConnectResponse", (data: any) => {
if (!groupRef.current) 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
);
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.userData = data.data.userData;
newModel.userData.target = new THREE.Vector3(
data.data.target.x,
data.data.target.y,
data.data.target.z
);
setCams((prev) => dedupeCams([...prev, newModel]));
setActiveUsers((prev: any) =>
dedupeUsers([...prev, data.data.userData])
);
});
});
socket.on("userDisConnectResponse", (data: any) => {
if (!groupRef.current) return;
if (socket.id === data.socketId || organization !== data.organization)
return;
setCams((prev) =>
prev.filter((cam) => cam.uuid !== data.data.userData._id)
);
setActiveUsers((prev: any) =>
prev.filter((user: any) => user._id !== data.data.userData._id)
);
});
socket.on("v1:camera:Response:update", (data: any) => {
// console.log('data: ', data);
if (
!groupRef.current ||
socket.id === data.socketId ||
organization !== data.organization
)
return;
if (selectedUser && selectedUser?.id === data.data.userId) {
setSelectedUser({
color: selectedUser.color,
name: selectedUser.name,
id: selectedUser.id,
location: {
position: data.data.position,
rotation: data.data.rotation,
target: data.data.target,
},
});
}
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
),
target: new THREE.Vector3(
data.data.target.x,
data.data.target.y,
data.data.target.z
),
},
}));
});
return () => {
socket.off("userConnectResponse");
socket.off("userDisConnectResponse");
socket.off("v1:camera:Response:update");
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [email, loader, navigate, setActiveUsers, socket]);
useFrame(() => {
if (!groupRef.current) return;
Object.keys(models).forEach((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
);
});
});
useEffect(() => {
if (!groupRef.current) return;
const organization = email!.split("@")[1].split(".")[0];
getActiveUsersData(organization).then((data) => {
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.userData = cam.userData;
cam.userData.position = newModel.position;
cam.userData.rotation = newModel.rotation;
newModel.userData.target = cam.target;
return newModel;
});
const users = filteredData.map((cam: any) => cam.userData);
setActiveUsers((prev: any) => dedupeUsers([...prev, ...users]));
setCams((prev) => dedupeCams([...prev, ...newCams]));
});
}
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<group ref={groupRef} name="Cam-Model-Group">
{cams.map((cam, index) => (
<primitive
key={cam.uuid}
//eslint-disable-next-line
object={cam}
visible={
selectedUser?.name !== cam.userData.userName &&
activeModule !== "visualization" &&
!isPlaying
}
>
<Html
as="div"
center
zIndexRange={[1, 0]}
sprite
style={{
color: "white",
textAlign: "center",
fontFamily: "Arial, sans-serif",
display: `${activeModule !== "visualization" ? "" : "none"}`,
opacity: `${selectedUser?.name !== cam.userData.userName && !isPlaying
? 1
: 0
}`,
transition: "opacity .2s ease",
}}
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={cam.userData.userImage ?? ""}
userName={cam.userData.userName}
id={cam.userData._id}
color={getAvatarColor(index, cam.userData.userName)}
position={cam.position}
rotation={cam.rotation}
target={cam.userData.target}
/>
</Html>
</primitive>
))}
</group>
);
};
export default CamModelsGroup;

View File

@@ -0,0 +1,69 @@
import React from "react";
import CustomAvatar from "../users/Avatar";
import { useSelectedUserStore } from "../../../store/collaboration/useCollabStore";
import { useCamMode } from "../../../store/builder/store";
interface CollabUserIconProps {
userName: string;
userImage?: string;
color: string;
id: string;
position?: {
x: number;
y: number;
z: number;
};
rotation?: {
x: number;
y: number;
z: number;
};
target?: {
x: number;
y: number;
z: number;
};
}
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
userImage,
userName,
id,
color,
position,
rotation,
target,
}) => {
const { setSelectedUser } = useSelectedUserStore();
const { setCamMode } = useCamMode();
return (
<div className="collab-user-live-container">
<button
id="live-user-button"
className="user-image-container"
onClick={() => {
if (!position || !rotation || !target) return;
// Set the selected user in the store
setSelectedUser({
id: id,
color: color,
name: userName,
location: { position, rotation, target },
});
setCamMode("FollowPerson");
}}
>
{userImage ? (
<img className="user-image" src={userImage} alt={userName} />
) : (
<CustomAvatar name={userName} color={color} />
)}
</button>
<div className="user-name" style={{ backgroundColor: color }}>
{userName}
</div>
</div>
);
};
export default CollabUserIcon;

View File

@@ -0,0 +1,18 @@
import React from "react";
import CamModelsGroup from "./camera/collabCams";
import CommentsGroup from "./comments/commentsGroup";
const Collaboration: React.FC = () => {
return (
<>
<CamModelsGroup />
<CommentsGroup />
</>
);
};
export default Collaboration;

View File

@@ -0,0 +1,134 @@
import { useEffect, useState } from "react";
import { useActiveTool } from "../../../store/builder/store"
import { useThree } from "@react-three/fiber";
import { MathUtils, Vector3 } from "three";
import { useCommentStore } from "../../../store/collaboration/useCommentStore";
import CommentInstances from "./instances/commentInstances";
import { Sphere } from "@react-three/drei";
function CommentsGroup() {
const { gl, raycaster, camera, scene, pointer } = useThree();
const { activeTool } = useActiveTool();
const { addComment } = useCommentStore();
const [hoverPos, setHoverPos] = useState<Vector3 | null>(null);
const userId = localStorage.getItem('userId') ?? '';
useEffect(() => {
const canvasElement = gl.domElement;
let drag = false;
let isLeftMouseDown = false;
const onMouseDown = (evt: MouseEvent) => {
if (evt.button === 0) {
isLeftMouseDown = true;
drag = false;
}
};
const onMouseUp = (evt: MouseEvent) => {
if (evt.button === 0) {
isLeftMouseDown = false;
}
}
const onMouseMove = () => {
if (isLeftMouseDown) {
drag = true;
}
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("zonePlane") &&
!intersect.object.name.includes("SelectionGroup") &&
!intersect.object.name.includes("selectionAssetGroup") &&
!intersect.object.name.includes("SelectionGroupBoundingBoxLine") &&
!intersect.object.name.includes("SelectionGroupBoundingBox") &&
!intersect.object.name.includes("SelectionGroupBoundingLine") &&
intersect.object.type !== "GridHelper"
);
if (intersects.length > 0) {
const point = intersects[0].point;
setHoverPos(new Vector3(point.x, Math.max(point.y, 0), point.z));
} else {
setHoverPos(null);
}
};
const onMouseClick = () => {
if (drag) return;
const intersects = raycaster
.intersectObjects(scene.children, true)
.filter(
(intersect) =>
!intersect.object.name.includes("Roof") &&
!intersect.object.name.includes("MeasurementReference") &&
!intersect.object.name.includes("agv-collider") &&
!intersect.object.name.includes("zonePlane") &&
!intersect.object.name.includes("SelectionGroup") &&
!intersect.object.name.includes("selectionAssetGroup") &&
!intersect.object.name.includes("SelectionGroupBoundingBoxLine") &&
!intersect.object.name.includes("SelectionGroupBoundingBox") &&
!intersect.object.name.includes("SelectionGroupBoundingLine") &&
intersect.object.type !== "GridHelper"
);
if (intersects.length > 0) {
const position = new Vector3(intersects[0].point.x, Math.max(intersects[0].point.y, 0), intersects[0].point.z);
const comment: CommentSchema = {
state: 'active',
commentId: MathUtils.generateUUID(),
creatorId: userId,
createdAt: new Date().toISOString(),
comment: '',
lastUpdatedAt: new Date().toISOString(),
position: position.toArray(),
rotation: [0, 0, 0],
replies: []
}
addComment(comment);
setHoverPos(null);
}
}
if (activeTool === 'comment') {
canvasElement.addEventListener("mousedown", onMouseDown);
canvasElement.addEventListener("mouseup", onMouseUp);
canvasElement.addEventListener("mousemove", onMouseMove);
canvasElement.addEventListener("click", onMouseClick);
} else {
setHoverPos(null);
}
return () => {
canvasElement.removeEventListener("mousedown", onMouseDown);
canvasElement.removeEventListener("mouseup", onMouseUp);
canvasElement.removeEventListener("mousemove", onMouseMove);
canvasElement.removeEventListener("click", onMouseClick);
};
}, [activeTool, camera])
return (
<>
<CommentInstances />
{hoverPos && (
<Sphere name={'commentHolder'} args={[0.1, 16, 16]} position={hoverPos}>
<meshStandardMaterial color="orange" />
</Sphere>
)}
</>
)
}
export default CommentsGroup

View File

@@ -0,0 +1,64 @@
import { Html, TransformControls } from '@react-three/drei';
import { useEffect, useRef, useState } from 'react'
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
import CommentThreads from '../../../../../components/ui/collaboration/CommentThreads';
function CommentInstance({ comment }: { comment: CommentSchema }) {
const { isPlaying } = usePlayButtonStore();
const CommentRef = useRef(null);
const [selectedComment, setSelectedComment] = useState<CommentSchema | null>(null);
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
const keyCombination = detectModifierKeys(e);
if (!selectedComment) return;
if (keyCombination === "G") {
setTransformMode((prev) => (prev === "translate" ? null : "translate"));
}
if (keyCombination === "R") {
setTransformMode((prev) => (prev === "rotate" ? null : "rotate"));
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [selectedComment]);
const commentClicked = () => {
console.log('hii');
setSelectedComment(comment);
}
if (comment.state === 'inactive' || isPlaying) return null;
return (
<>
<Html
ref={CommentRef}
zIndexRange={[1, 0]}
prepend
sprite
center
position={comment.position}
rotation={comment.rotation}
className='comments-main-wrapper'
>
<CommentThreads commentClicked={commentClicked} />
</Html>
{CommentRef.current && transformMode && (
<TransformControls
object={CommentRef.current}
mode={transformMode}
onMouseUp={(e) => {
}}
/>
)}
</>
)
}
export default CommentInstance;

View File

@@ -0,0 +1,23 @@
import React, { useEffect } from 'react'
import CommentInstance from './commentInstance/commentInstance'
import { useCommentStore } from '../../../../store/collaboration/useCommentStore'
function CommentInstances() {
const { comments } = useCommentStore();
useEffect(() => {
// console.log('comments: ', comments);
}, [comments])
return (
<>
{comments.map((comment: CommentSchema) => (
<React.Fragment key={comment.commentId}>
<CommentInstance comment={comment} />
</React.Fragment>
))}
</>
)
}
export default CommentInstances

View File

@@ -0,0 +1,56 @@
const avatarColors: string[] = [
"#FF5733", // Vivid Orange
"#48ac2a", // Leaf Green
"#0050eb", // Bright Blue
"#FF33A1", // Hot Pink
"#FF8C33", // Sunset Orange
"#8C33FF", // Violet Purple
"#FF3333", // Fiery Red
"#43c06d", // Emerald Green
"#A133FF", // Royal Purple
"#C70039", // Crimson Red
"#900C3F", // Deep Burgundy
"#581845", // Plum Purple
"#3859AD", // Steel Blue
"#08873E", // Forest Green
"#E74C3C", // Cherry Red
"#00adff", // Sky Blue
"#DBAD05", // Golden Yellow
"#A13E31", // Brick Red
"#94C40E", // Lime Green
"#060C47", // Midnight Blue
"#2FAFAF", // Teal
];
export function getAvatarColor(index: number, name?: string): string {
// Check if the color is already stored in localStorage
const localStorageKey = "userAvatarColors";
// Check if local storage is available
if (name) {
let userColors = JSON.parse(localStorage.getItem(localStorageKey) ?? "{}");
// Check if the user already has an assigned color
if (userColors[name]) {
return userColors[name];
}
// Find a new color not already assigned
const usedColors = Object.values(userColors);
const availableColors = avatarColors.filter(color => !usedColors.includes(color));
// Assign a new color
const assignedColor = availableColors.length > 0
? availableColors[0]
: avatarColors[index % avatarColors.length];
userColors[name] = assignedColor;
// Save back to local storage
localStorage.setItem(localStorageKey, JSON.stringify(userColors));
return assignedColor;
}
// Fallback: Assign a color using the index if no name or local storage is unavailable
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

@@ -0,0 +1,34 @@
import * as THREE from 'three';
interface SetCameraViewProps {
controls: any;
camera: THREE.Camera;
position: THREE.Vector3 | { x: number; y: number; z: number };
rotation: THREE.Euler | { x: number; y: number; z: number };
username?: string;
target?: THREE.Vector3 | { x: number; y: number; z: number };
}
export default async function setCameraView({
controls,
camera,
position,
rotation,
username,
target
}: SetCameraViewProps) {
if (!controls || !camera) return;
if (target == null) return;
// Normalize position
const newPosition = position instanceof THREE.Vector3
? position
: new THREE.Vector3(position.x, position.y, position.z);
const newTarget = target instanceof THREE.Vector3 ? target : new THREE.Vector3(target.x, target.y, target.z);
if (controls.setTarget) {
controls?.setLookAt(...newPosition.toArray(), ...newTarget.toArray(), true);
}
}

View File

@@ -0,0 +1,884 @@
import { useEffect } from "react";
import * as THREE from "three";
import gsap from "gsap";
import { GLTF, GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import {
useSocketStore,
useActiveLayer,
useWallItems,
useLayers,
useUpdateScene,
useWalls,
useDeletedLines,
useNewLines,
useZonePoints,
useZones,
} from "../../../store/builder/store";
import * as Types from "../../../types/world/worldTypes";
import * as CONSTANTS from "../../../types/world/worldConstants";
// import { setFloorItemApi } from "../../services/factoryBuilder/assest/floorAsset/setFloorItemApi";
import objectLineToArray from "../../builder/geomentries/lines/lineConvertions/objectLineToArray";
import addLineToScene from "../../builder/geomentries/lines/addLineToScene";
import updateLinesPositions from "../../builder/geomentries/lines/updateLinesPositions";
import updateLines from "../../builder/geomentries/lines/updateLines";
import updateDistanceText from "../../builder/geomentries/lines/updateDistanceText";
import updateFloorLines from "../../builder/geomentries/floors/updateFloorLines";
import loadWalls from "../../builder/geomentries/walls/loadWalls";
import RemoveConnectedLines from "../../builder/geomentries/lines/removeConnectedLines";
import Layer2DVisibility from "../../builder/geomentries/layers/layer2DVisibility";
import { retrieveGLTF, storeGLTF } from "../../../utils/indexDB/idbUtils";
import { getZonesApi } from "../../../services/factoryBuilder/zones/getZonesApi";
import { useParams } from "react-router-dom";
import { useAssetsStore } from "../../../store/builder/useAssetStore";
import { useEventsStore } from "../../../store/simulation/useEventsStore";
import { useProductStore } from "../../../store/simulation/useProductStore";
export default function SocketResponses({
floorPlanGroup,
lines,
floorGroup,
floorGroupAisle,
scene,
onlyFloorlines,
itemsGroup,
isTempLoader,
tempLoader,
currentLayerPoint,
floorPlanGroupPoint,
floorPlanGroupLine,
zoneGroup,
dragPointControls,
}: any) {
const { socket } = useSocketStore();
const { activeLayer, setActiveLayer } = useActiveLayer();
const { wallItems, setWallItems } = useWallItems();
const { setLayers } = useLayers();
const { setUpdateScene } = useUpdateScene();
const { setWalls } = useWalls();
const { setDeletedLines } = useDeletedLines();
const { setNewLines } = useNewLines();
const { zones, setZones } = useZones();
const { zonePoints, setZonePoints } = useZonePoints();
const { projectId } = useParams();
const { addAsset, updateAsset, removeAsset } = useAssetsStore();
useEffect(() => {
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
if (!socket) return;
socket.on("cameraCreateResponse", (data: any) => {
//
});
socket.on("userConnectRespones", (data: any) => {
//
});
socket.on("userDisConnectRespones", (data: any) => {
//
});
socket.on("v1:camera:Response:update", (data: any) => {
//
});
socket.on("EnvironmentUpdateResponse", (data: any) => {
//
});
socket.on("v1:model-asset:response:add", async (data: any) => {
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "Model created successfully") {
try {
const asset: Asset = {
modelUuid: data.data.modelUuid,
modelName: data.data.modelName,
assetId: data.data.modelfileID,
position: data.data.position,
rotation: [data.data.rotation.x, data.data.rotation.y, data.data.rotation.z],
isLocked: data.data.isLocked,
isCollidable: false,
isVisible: data.data.isVisible,
opacity: 1,
}
addAsset(asset);
echo.success("Added model through collaboration");
} catch (error) {
echo.error("Failed to create model through collaboration");
}
} else if (data.message === "Model updated successfully") {
try {
const asset: Asset = {
modelUuid: data.data.modelUuid,
modelName: data.data.modelName,
assetId: data.data.modelfileID,
position: data.data.position,
rotation: [data.data.rotation.x, data.data.rotation.y, data.data.rotation.z],
isLocked: data.data.isLocked,
isCollidable: false,
isVisible: data.data.isVisible,
opacity: 1,
}
updateAsset(asset.modelUuid, {
position: asset.position,
rotation: asset.rotation,
});
echo.success("Updated model through collaboration");
} catch (error) {
echo.error("Failed to update model through collaboration");
}
} else {
echo.error("Failed executing action from collaboration");
}
});
socket.on("v1:model-asset:response:delete", (data: any) => {
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "Model deleted successfully") {
try {
const deletedUUID = data.data.modelUuid;
useEventsStore.getState().removeEvent(deletedUUID);
useProductStore.getState().deleteEvent(deletedUUID);
removeAsset(deletedUUID);
echo.success("Model Removed successfully through collaboration");
} catch (error) {
echo.error("Failed to remove model through collaboration");
}
}
});
socket.on("v1:Line:response:update", (data: any) => {
// console.log('data: ', data);
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "line updated") {
const DraggedUUID = data.data.uuid;
const DraggedPosition = new THREE.Vector3(
data.data.position.x,
data.data.position.y,
data.data.position.z
);
const point = floorPlanGroupPoint.current.getObjectByProperty(
"uuid",
DraggedUUID
);
point.position.set(
DraggedPosition.x,
DraggedPosition.y,
DraggedPosition.z
);
const affectedLines = updateLinesPositions(
{ uuid: DraggedUUID, position: DraggedPosition },
lines
);
updateLines(floorPlanGroupLine, affectedLines);
updateDistanceText(scene, floorPlanGroupLine, affectedLines);
updateFloorLines(onlyFloorlines, {
uuid: DraggedUUID,
position: DraggedPosition,
});
loadWalls(lines, setWalls);
setUpdateScene(true);
}
});
socket.on("v1:Line:response:delete", (data: any) => {
// console.log('data: ', data);
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "line deleted") {
const line = objectLineToArray(data.data);
const linePoints = line;
const connectedpoints = [linePoints[0][1], linePoints[1][1]];
onlyFloorlines.current = onlyFloorlines.current
.map((floorline: any) =>
floorline.filter(
(line: any) =>
line[0][1] !== connectedpoints[0] &&
line[1][1] !== connectedpoints[1]
)
)
.filter((floorline: any) => floorline.length > 0);
const removedLine = lines.current.find(
(item: any) =>
(item[0][1] === linePoints[0][1] &&
item[1][1] === linePoints[1][1]) ||
(item[0][1] === linePoints[1][1] && item[1][1] === linePoints[0][1])
);
lines.current = lines.current.filter(
(item: any) => item !== removedLine
);
floorPlanGroupLine.current.children.forEach((line: any) => {
const linePoints = line.userData.linePoints as [
number,
string,
number
][];
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (
(uuid1 === connectedpoints[0] && uuid2 === connectedpoints[1]) ||
(uuid1 === connectedpoints[1] && uuid2 === connectedpoints[0])
) {
line.material.dispose();
line.geometry.dispose();
floorPlanGroupLine.current.remove(line);
setDeletedLines([line.userData.linePoints]);
}
});
connectedpoints.forEach((pointUUID) => {
let isConnected = false;
floorPlanGroupLine.current.children.forEach((line: any) => {
const linePoints = line.userData.linePoints;
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === pointUUID || uuid2 === pointUUID) {
isConnected = true;
}
});
if (!isConnected) {
floorPlanGroupPoint.current.children.forEach((point: any) => {
if (point.uuid === pointUUID) {
point.material.dispose();
point.geometry.dispose();
floorPlanGroupPoint.current.remove(point);
}
});
}
});
loadWalls(lines, setWalls);
setUpdateScene(true);
echo.success("Line Removed!");
}
});
socket.on("v1:Line:response:delete:point", (data: any) => {
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "point deleted") {
const point = floorPlanGroupPoint.current?.getObjectByProperty(
"uuid",
data.data
);
point.material.dispose();
point.geometry.dispose();
floorPlanGroupPoint.current.remove(point);
onlyFloorlines.current = onlyFloorlines.current
.map((floorline: any) =>
floorline.filter(
(line: any) =>
line[0][1] !== data.data && line[1][1] !== data.data
)
)
.filter((floorline: any) => floorline.length > 0);
RemoveConnectedLines(
data.data,
floorPlanGroupLine,
floorPlanGroupPoint,
setDeletedLines,
lines
);
loadWalls(lines, setWalls);
setUpdateScene(true);
echo.success("Point Removed!");
}
});
socket.on("v1:Line:response:delete:layer", async (data: any) => {
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "layer deleted") {
setActiveLayer(1);
const removedLayer = data.data;
const removedLines: Types.Lines = lines.current.filter(
(line: any) => line[0][2] === removedLayer
);
////////// Remove Points and lines from the removed layer //////////
removedLines.forEach(async (line) => {
line.forEach(async (removedPoint) => {
const removableLines: THREE.Mesh[] = [];
const connectedpoints: string[] = [];
floorPlanGroupLine.current.children.forEach((line: any) => {
const linePoints = line.userData.linePoints as [
number,
string,
number
][];
const uuid1 = linePoints[0][1];
const uuid2 = linePoints[1][1];
if (uuid1 === removedPoint[1] || uuid2 === removedPoint[1]) {
connectedpoints.push(uuid1 === removedPoint[1] ? uuid2 : uuid1);
removableLines.push(line as THREE.Mesh);
}
});
if (removableLines.length > 0) {
removableLines.forEach((line: any) => {
lines.current = lines.current.filter(
(item: any) =>
JSON.stringify(item) !==
JSON.stringify(line.userData.linePoints)
);
line.material.dispose();
line.geometry.dispose();
floorPlanGroupLine.current.remove(line);
});
}
const point = floorPlanGroupPoint.current.getObjectByProperty(
"uuid",
removedPoint[1]
);
if (point) {
point.material.dispose();
point.geometry.dispose();
floorPlanGroupPoint.current.remove(point);
}
});
});
////////// Update the remaining lines layer values in the userData and in lines.current //////////
let remaining = lines.current.filter(
(line: any) => line[0][2] !== removedLayer
);
let updatedLines: Types.Lines = [];
remaining.forEach((line: any) => {
let newLines = JSON.parse(JSON.stringify(line));
if (newLines[0][2] > removedLayer) {
newLines[0][2] -= 1;
newLines[1][2] -= 1;
}
const matchingLine = floorPlanGroupLine.current.children.find(
(l: any) =>
l.userData.linePoints[0][1] === line[0][1] &&
l.userData.linePoints[1][1] === line[1][1]
);
if (matchingLine) {
const updatedUserData = JSON.parse(
JSON.stringify(matchingLine.userData)
);
updatedUserData.linePoints[0][2] = newLines[0][2];
updatedUserData.linePoints[1][2] = newLines[1][2];
matchingLine.userData = updatedUserData;
}
updatedLines.push(newLines);
});
lines.current = updatedLines;
localStorage.setItem("Lines", JSON.stringify(lines.current));
////////// Also remove OnlyFloorLines and update it in localstorage //////////
onlyFloorlines.current = onlyFloorlines.current.filter((floor: any) => {
return floor[0][0][2] !== removedLayer;
});
const meshToRemove = floorGroup.current?.children.find(
(mesh: any) => mesh.name === `Only_Floor_Line_${removedLayer}`
);
if (meshToRemove) {
meshToRemove.geometry.dispose();
meshToRemove.material.dispose();
floorGroup.current?.remove(meshToRemove);
}
const zonesData = await getZonesApi(organization, projectId);
const highestLayer = Math.max(
1,
lines.current.reduce(
(maxLayer: number, segment: any) =>
Math.max(maxLayer, segment.layer || 0),
0
),
zonesData.reduce(
(maxLayer: number, zone: any) =>
Math.max(maxLayer, zone.layer || 0),
0
)
);
setLayers(highestLayer);
loadWalls(lines, setWalls);
setUpdateScene(true);
echo.success("Layer Removed!");
}
});
}, [socket]);
useEffect(() => {
if (!socket) return;
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
socket.on("v1:wallItem:Response:Delete", (data: any) => {
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "wallitem deleted") {
const deletedUUID = data.data.modelUuid;
let WallItemsRef = wallItems;
const Items = WallItemsRef.filter((item: any) => item.model?.uuid !== deletedUUID);
setWallItems([]);
setTimeout(async () => {
WallItemsRef = Items;
setWallItems(WallItemsRef);
const WallItemsForStorage = WallItemsRef.map((item: any) => {
const { model, ...rest } = item;
return {
...rest,
modelUuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
echo.success("Model Removed!");
}, 50);
}
});
socket.on("v1:wallItems:Response:Update", (data: any) => {
//
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "wallIitem created") {
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader();
dracoLoader.setDecoderPath('https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/draco/gltf/');
loader.setDRACOLoader(dracoLoader);
// Check THREE.js cache first
const cachedModel = THREE.Cache.get(data.data.modelfileID);
if (cachedModel) {
handleModelLoad(cachedModel);
return;
}
// Check IndexedDB cache
retrieveGLTF(data.data.modelfileID).then((cachedModelBlob) => {
if (cachedModelBlob) {
const blobUrl = URL.createObjectURL(cachedModelBlob);
loader.load(blobUrl, (gltf) => {
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(data.data.modelfileID, gltf);
handleModelLoad(gltf);
});
return;
}
})
// Load from backend if not in any cache
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${data.data.modelfileID}`, async (gltf) => {
try {
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${data.data.modelfileID}`).then((res) => res.blob());
await storeGLTF(data.data.modelfileID, modelBlob);
THREE.Cache.add(data.data.modelfileID, gltf);
await handleModelLoad(gltf);
} catch (error) {
handleModelLoad(gltf);
}
});
async function handleModelLoad(gltf: GLTF) {
const model = gltf.scene.clone();
model.uuid = data.data.modelUuid;
model.children[0].children.forEach((child) => {
if (child.name !== "CSG_REF") {
child.castShadow = true;
child.receiveShadow = true;
}
});
const newWallItem = {
type: data.data.type,
model: model,
modelName: data.data.modelName,
modelfileID: data.data.modelfileID,
scale: data.data.scale,
csgscale: data.data.csgscale,
csgposition: data.data.csgposition,
position: data.data.position,
quaternion: data.data.quaternion,
};
setWallItems((prevItems: Types.wallItems) => {
const updatedItems = [...prevItems, newWallItem];
const WallItemsForStorage = updatedItems.map(item => {
const { model, ...rest } = item;
return {
...rest,
modelUuid: model?.uuid,
};
});
localStorage.setItem("WallItems", JSON.stringify(WallItemsForStorage));
echo.success("Model Added!");
return updatedItems;
});
}
} else if (data.message === "wallIitem updated") {
const updatedUUID = data.data.modelUuid;
setWallItems((prevItems: any) => {
const updatedItems = prevItems.map((item: any) => {
if (item.model.uuid === updatedUUID) {
return {
...item,
position: data.data.position,
quaternion: data.data.quaternion,
scale: data.data.scale,
csgscale: data.data.csgscale,
csgposition: data.data.csgposition,
};
}
return item;
});
const WallItemsForStorage = updatedItems.map((item: any) => {
const { model, ...rest } = item;
return {
...rest,
modelUuid: model?.uuid,
};
});
localStorage.setItem(
"WallItems",
JSON.stringify(WallItemsForStorage)
);
echo.success("Model Updated!");
return updatedItems;
});
}
});
return () => {
socket.off("v1:wallItem:Response:Delete");
socket.off("v1:wallItems:Response:Update");
};
}, [wallItems]);
function getPointColor(lineType: string | undefined): string {
switch (lineType) {
case CONSTANTS.lineConfig.wallName:
return CONSTANTS.pointConfig.wallOuterColor;
case CONSTANTS.lineConfig.floorName:
return CONSTANTS.pointConfig.floorOuterColor;
case CONSTANTS.lineConfig.aisleName:
return CONSTANTS.pointConfig.aisleOuterColor;
default:
return CONSTANTS.pointConfig.defaultOuterColor;
}
}
function getLineColor(lineType: string | undefined): string {
switch (lineType) {
case CONSTANTS.lineConfig.wallName:
return CONSTANTS.lineConfig.wallColor;
case CONSTANTS.lineConfig.floorName:
return CONSTANTS.lineConfig.floorColor;
case CONSTANTS.lineConfig.aisleName:
return CONSTANTS.lineConfig.aisleColor;
default:
return CONSTANTS.lineConfig.defaultColor;
}
}
useEffect(() => {
if (!socket) return;
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
socket.on("v1:Line:response:create", async (data: any) => {
//
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "line create") {
const line: Types.Line = objectLineToArray(data.data);
const type = line[0][3];
const pointColour = getPointColor(type);
const lineColour = getLineColor(type);
setNewLines([line]);
line.forEach((line) => {
const existingPoint =
floorPlanGroupPoint.current?.getObjectByProperty("uuid", line[1]);
if (existingPoint) {
return;
}
const geometry = new THREE.BoxGeometry(
...CONSTANTS.pointConfig.boxScale
);
const material = new THREE.ShaderMaterial({
uniforms: {
uOuterColor: { value: new THREE.Color(pointColour) }, // Blue color for the border
uInnerColor: {
value: new THREE.Color(CONSTANTS.pointConfig.defaultInnerColor),
}, // White color for the inner square
},
vertexShader: `
varying vec2 vUv;
void main() {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
varying vec2 vUv;
uniform vec3 uOuterColor;
uniform vec3 uInnerColor;
void main() {
// Define the size of the white square as a proportion of the face
float borderThickness = 0.2; // Adjust this value for border thickness
if (vUv.x > borderThickness && vUv.x < 1.0 - borderThickness &&
vUv.y > borderThickness && vUv.y < 1.0 - borderThickness) {
gl_FragColor = vec4(uInnerColor, 1.0); // White inner square
} else {
gl_FragColor = vec4(uOuterColor, 1.0); // Blue border
}
}
`,
});
const point = new THREE.Mesh(geometry, material);
point.name = "point";
point.uuid = line[1];
point.userData = { type: type, color: pointColour };
point.position.set(line[0].x, line[0].y, line[0].z);
currentLayerPoint.current.push(point);
floorPlanGroupPoint.current?.add(point);
});
if (dragPointControls.current) {
dragPointControls.current!.objects = currentLayerPoint.current;
}
addLineToScene(
new THREE.Vector3(line[0][0].x, line[0][0].y, line[0][0].z),
new THREE.Vector3(line[1][0].x, line[1][0].y, line[1][0].z),
lineColour,
line,
floorPlanGroupLine
);
lines.current.push(line);
const zonesData = await getZonesApi(organization, projectId);
const highestLayer = Math.max(
1,
lines.current.reduce(
(maxLayer: number, segment: any) =>
Math.max(maxLayer, segment.layer || 0),
0
),
zonesData.reduce(
(maxLayer: number, zone: any) =>
Math.max(maxLayer, zone.layer || 0),
0
)
);
setLayers(highestLayer);
Layer2DVisibility(
activeLayer,
floorPlanGroup,
floorPlanGroupLine,
floorPlanGroupPoint,
currentLayerPoint,
dragPointControls
);
loadWalls(lines, setWalls);
setUpdateScene(true);
}
});
return () => {
socket.off("v1:Line:response:create");
};
}, [socket, activeLayer]);
useEffect(() => {
if (!socket) return;
const email = localStorage.getItem("email");
const organization = email!.split("@")[1].split(".")[0];
socket.on("v1:zone:response:updates", (data: any) => {
// console.log('data: ', data);
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "zone created") {
const pointsArray: [number, number, number][] = data.data.points;
const vector3Array = pointsArray.map(
([x, y, z]) => new THREE.Vector3(x, y, z)
);
const newZones = [...zones, data.data];
setZones(newZones);
const updatedZonePoints = [...zonePoints, ...vector3Array];
setZonePoints(updatedZonePoints);
const highestLayer = Math.max(
1,
lines.current.reduce(
(maxLayer: number, segment: any) =>
Math.max(maxLayer, segment.layer || 0),
0
),
newZones.reduce(
(maxLayer: number, zone: any) =>
Math.max(maxLayer, zone.layer || 0),
0
)
);
setLayers(highestLayer);
setUpdateScene(true);
}
if (data.message === "zone updated") {
const updatedZones = zones.map((zone: any) =>
zone.zoneUuid === data.data.zoneUuid ? data.data : zone
);
setZones(updatedZones);
setUpdateScene(true);
}
});
socket.on("v1:zone:response:delete", (data: any) => {
// console.log('data: ', data);
if (socket.id === data.socketId) {
return;
}
if (organization !== data.organization) {
return;
}
if (data.message === "zone deleted") {
const updatedZones = zones.filter(
(zone: any) => zone.zoneUuid !== data.data.zoneUuid
);
setZones(updatedZones);
const zoneIndex = zones.findIndex(
(zone: any) => zone.zoneUuid === data.data.zoneUuid
);
if (zoneIndex !== -1) {
const updatedzonePoints = zonePoints.filter(
(_: any, index: any) =>
index < zoneIndex * 4 || index >= zoneIndex * 4 + 4
);
setZonePoints(updatedzonePoints);
}
const highestLayer = Math.max(
1,
lines.current.reduce(
(maxLayer: number, segment: any) =>
Math.max(maxLayer, segment.layer || 0),
0
),
updatedZones.reduce(
(maxLayer: number, zone: any) =>
Math.max(maxLayer, zone.layer || 0),
0
)
);
setLayers(highestLayer);
setUpdateScene(true);
}
});
return () => {
socket.off("v1:zone:response:updates");
socket.off("v1:zone:response:delete");
};
}, [socket, zones, zonePoints]);
return <></>;
}

View File

@@ -0,0 +1,58 @@
import React, { useEffect, useState } from "react";
import { getInitials } from "../functions/getInitials";
interface AvatarProps {
name: string; // Name can be a full name or initials
size?: number;
textColor?: string;
color?: string; // Optional color prop for future use
}
const CustomAvatar: React.FC<AvatarProps> = ({
name,
size = 100,
textColor = "#ffffff",
color, // Optional color prop for future use
}) => {
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 = color ?? "#323232"; // Use color prop or generate color based on 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);
}
}, [color, 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;