Merge branch 'main' into v2

This commit is contained in:
2025-04-30 12:01:39 +05:30
75 changed files with 3063 additions and 1495 deletions

View File

@@ -69,7 +69,9 @@ const ZoneGroup: React.FC = () => {
},
transparent: true,
depthWrite: false,
}), []);
}),
[]
);
useEffect(() => {
const fetchZones = async () => {
@@ -148,6 +150,7 @@ const ZoneGroup: React.FC = () => {
}
}, [toolMode, toggleView]);
// eslint-disable-next-line react-hooks/exhaustive-deps
const addZoneToBackend = async (zone: {
zoneId: string;
zoneName: string;
@@ -503,6 +506,15 @@ const ZoneGroup: React.FC = () => {
draggedSphere,
movePoint,
activeLayer,
raycaster,
pointer,
controls,
plane,
setZones,
setZonePoints,
addZoneToBackend,
handleDeleteZone,
updateZoneToBackend,
]);
useFrame(() => {
@@ -551,6 +563,7 @@ const ZoneGroup: React.FC = () => {
key={index}
position={midpoint}
rotation={[0, -angle, 0]}
visible={false}
>
<planeGeometry args={[planeWidth, planeHeight]} />
<primitive

View File

@@ -8,9 +8,9 @@ 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 "../../../functions/collabUserIcon";
import { getAvatarColor } from "../../../functions/users/functions/getAvatarColor";
import CollabUserIcon from "./collabUserIcon";
import useModuleStore from "../../../store/useModuleStore";
import { getAvatarColor } from "../functions/getAvatarColor";
const CamModelsGroup = () => {
const navigate = useNavigate();
@@ -20,13 +20,14 @@ const CamModelsGroup = () => {
const { socket } = useSocketStore();
const { activeModule } = useModuleStore();
// 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 [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, target: THREE.Vector3 }>>({});
const dedupeCams = (cams: any[]) => {
const seen = new Set();
@@ -102,6 +103,7 @@ const CamModelsGroup = () => {
});
socket.on("cameraUpdateResponse", (data: any) => {
if (
!groupRef.current ||
socket.id === data.socketId ||
@@ -122,6 +124,11 @@ const CamModelsGroup = () => {
data.data.rotation.y,
data.data.rotation.z
),
target: new THREE.Vector3(
data.data.target.x,
data.data.target.y,
data.data.target.z
),
},
}));
});
@@ -131,7 +138,7 @@ const CamModelsGroup = () => {
socket.off("userDisConnectResponse");
socket.off("cameraUpdateResponse");
};
}, [socket]);
}, [email, loader, navigate, setActiveUsers, socket]);
useFrame(() => {
if (!groupRef.current) return;
@@ -217,9 +224,11 @@ const CamModelsGroup = () => {
position={[-0.015, 0, 0.7]}
>
<CollabUserIcon
userImage={cam.userData.userImage || ""}
userImage={cam.userData.userImage ?? ""}
userName={cam.userData.userName}
color={getAvatarColor(index, cam.userData.userName)}
position={cam.position}
rotation={cam.rotation}
/>
</Html>
</primitive>

View File

@@ -0,0 +1,56 @@
import React from "react";
import CustomAvatar from "../users/Avatar";
import { useSelectedUserStore } from "../../../store/useCollabStore";
import { useCamMode } from "../../../store/store";
interface CollabUserIconProps {
userName: string;
userImage?: string;
color: string;
position?: {
x: number;
y: number;
z: number;
};
rotation?: {
x: number;
y: number;
z: number;
};
}
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
userImage,
userName,
color,
position,
rotation,
}) => {
const { setSelectedUser } = useSelectedUserStore();
const { setCamMode } = useCamMode();
return (
<div className="collab-user-live-container">
<button
className="user-image-container"
onClick={() => {
if(!position || !rotation) return;
// Set the selected user in the store
setSelectedUser({ color: color, name: userName, location: { position, rotation } });
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

@@ -1,14 +1,32 @@
import React from 'react'
import CamModelsGroup from './camera/collabCams'
import React, { useEffect } from "react";
import CamModelsGroup from "./camera/collabCams";
import { useSelectedUserStore } from "../../store/useCollabStore";
import { useThree } from "@react-three/fiber";
import setCameraView from "./functions/setCameraView";
import { useCamMode } from "../../store/store";
const Collaboration = () => {
return (
<>
const Collaboration: React.FC = () => {
const { selectedUser } = useSelectedUserStore();
const { camMode } = useCamMode();
const { camera, controls } = useThree(); // Access R3F camera and controls
<CamModelsGroup />
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 } = selectedUser.location;
setCameraView({
controls,
camera,
position,
rotation,
username: selectedUser.name,
});
}
}, [selectedUser, camera, controls, camMode]);
</>
)
}
return <CamModelsGroup />;
};
export default Collaboration
export default Collaboration;

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,33 @@
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;
// Normalize position
const newPosition = position instanceof THREE.Vector3
? position
: new THREE.Vector3(position.x, position.y, position.z);
if (controls.setTarget) {
controls?.setLookAt(...newPosition.toArray(), newPosition.x, 0, newPosition.z, true);
}
// Optionally you can log
console.log(`Camera view updated by ${username ?? 'unknown user'}`);
}

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;

View File

@@ -42,31 +42,33 @@ const CardsContainer: React.FC<ModelsProps> = ({ models }) => {
};
return (
<div className="cards-container-container">
<div className="header">Products You May Like</div>
<div className="cards-wrapper-container">
{models.length > 0 &&
models.map((assetDetail) => (
<Card
key={assetDetail._id}
assetName={assetDetail?.filename}
uploadedOn={assetDetail.uploadDate}
price={assetDetail?.price}
rating={4.5}
views={800}
onSelectCard={handleCardSelect}
image={assetDetail.thumbnail}
description={assetDetail.description}
<div className="cards-container-wrapper">
<div className="cards-container-container">
<div className="header">Products You May Like</div>
<div className="cards-wrapper-container">
{models.length > 0 &&
models.map((assetDetail) => (
<Card
key={assetDetail._id}
assetName={assetDetail?.filename}
uploadedOn={assetDetail.uploadDate}
price={assetDetail?.price}
rating={4.5}
views={800}
onSelectCard={handleCardSelect}
image={assetDetail.thumbnail}
description={assetDetail.description}
/>
))}
{/* <RenderOverlay> */}
{selectedCard && (
<AssetPreview
selectedCard={selectedCard}
setSelectedCard={setSelectedCard}
/>
))}
{/* <RenderOverlay> */}
{selectedCard && (
<AssetPreview
selectedCard={selectedCard}
setSelectedCard={setSelectedCard}
/>
)}
{/* </RenderOverlay> */}
)}
{/* </RenderOverlay> */}
</div>
</div>
</div>
);

View File

@@ -1,90 +1,144 @@
import { useFrame, useThree } from '@react-three/fiber';
import React, { useEffect, useState } from 'react';
import * as CONSTANTS from '../../../types/world/worldConstants';
import { useCamMode, useToggleView } from '../../../store/store';
import { useKeyboardControls } from '@react-three/drei';
import switchToThirdPerson from './switchToThirdPerson';
import switchToFirstPerson from './switchToFirstPerson';
import { detectModifierKeys } from '../../../utils/shortcutkeys/detectModifierKeys';
import { useFrame, useThree } from "@react-three/fiber";
import React, { useEffect, useState } from "react";
import * as CONSTANTS from "../../../types/world/worldConstants";
import { useCamMode, useToggleView } from "../../../store/store";
import { useKeyboardControls } from "@react-three/drei";
import switchToThirdPerson from "./switchToThirdPerson";
import switchToFirstPerson from "./switchToFirstPerson";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();
const [, get] = useKeyboardControls()
const [isTransitioning, setIsTransitioning] = useState(false);
const state: any = useThree();
const { toggleView } = useToggleView();
const { camMode, setCamMode } = useCamMode();
const [, get] = useKeyboardControls();
const [isTransitioning, setIsTransitioning] = useState(false);
const state: any = useThree();
const { toggleView } = useToggleView();
const [isShiftActive, setIsShiftActive] = useState(false);
useEffect(() => {
const handlePointerLockChange = async () => {
if (document.pointerLockElement && !toggleView) {
// console.log('Pointer is locked');
} else {
// console.log('Pointer is unlocked');
if (camMode === "FirstPerson" && !toggleView) {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
}
};
document.addEventListener('pointerlockchange', handlePointerLockChange);
return () => {
document.removeEventListener('pointerlockchange', handlePointerLockChange);
};
}, [camMode, toggleView, setCamMode, state.controls, state.camera]);
useEffect(() => {
const handleKeyPress = async (event: any) => {
if (!state.controls) return;
const keyCombination = detectModifierKeys(event);
if (keyCombination === "/" && !isTransitioning && !toggleView) {
setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
if (camMode === 'ThirdPerson') {
setCamMode("FirstPerson");
await switchToFirstPerson(state.controls, state.camera);
} else if (camMode === "FirstPerson") {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
setIsTransitioning(false);
}
};
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [camMode, isTransitioning, toggleView, state.controls, state.camera, setCamMode]);
useFrame(() => {
const { forward, backward, left, right } = get();
if (!state.controls) return
if (!state.controls || camMode === "ThirdPerson" || !document.pointerLockElement) return;
if (forward) {
state.controls.forward(CONSTANTS.firstPersonControls.forwardSpeed, true)
useEffect(() => {
const handlePointerLockChange = async () => {
if (document.pointerLockElement && !toggleView) {
// Pointer is locked
} else {
// Pointer is unlocked
if (camMode === "FirstPerson" && !toggleView) {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
if (backward) {
state.controls.forward(CONSTANTS.firstPersonControls.backwardSpeed, true)
}
if (left) {
state.controls.truck(CONSTANTS.firstPersonControls.leftSpeed, 0, true)
}
if (right) {
state.controls.truck(CONSTANTS.firstPersonControls.rightSpeed, 0, true)
}
});
}
};
return null; // This component does not render any UI
document.addEventListener("pointerlockchange", handlePointerLockChange);
return () => {
document.removeEventListener(
"pointerlockchange",
handlePointerLockChange
);
};
}, [camMode, toggleView, setCamMode, state.controls, state.camera]);
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Shift") {
setIsShiftActive(true);
}
};
const handleKeyUp = (event: KeyboardEvent) => {
if (event.key === "Shift") {
setIsShiftActive(false);
}
};
window.addEventListener("keydown", handleKeyDown);
window.addEventListener("keyup", handleKeyUp);
return () => {
window.removeEventListener("keydown", handleKeyDown);
window.removeEventListener("keyup", handleKeyUp);
};
}, []);
useEffect(() => {
const handleKeyPress = async (event: KeyboardEvent) => {
if (!state.controls) return;
const keyCombination = detectModifierKeys(event);
if (keyCombination === "/" && !isTransitioning && !toggleView) {
setIsTransitioning(true);
state.controls.mouseButtons.left =
CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right =
CONSTANTS.controlsTransition.rightMouse;
state.controls.mouseButtons.wheel =
CONSTANTS.controlsTransition.wheelMouse;
state.controls.mouseButtons.middle =
CONSTANTS.controlsTransition.middleMouse;
if (camMode === "ThirdPerson") {
setCamMode("FirstPerson");
await switchToFirstPerson(state.controls, state.camera);
} else if (camMode === "FirstPerson") {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
setIsTransitioning(false);
}
};
window.addEventListener("keydown", handleKeyPress);
return () => {
window.removeEventListener("keydown", handleKeyPress);
};
}, [
camMode,
isTransitioning,
toggleView,
state.controls,
state.camera,
setCamMode,
]);
useFrame(() => {
const { forward, backward, left, right } = get();
if (!state.controls) return;
if (camMode === "ThirdPerson" || !document.pointerLockElement) return;
const speedMultiplier = isShiftActive ? 4 : 1;
if (forward) {
state.controls.forward(
CONSTANTS.firstPersonControls.forwardSpeed * speedMultiplier,
true
);
}
if (backward) {
state.controls.forward(
CONSTANTS.firstPersonControls.backwardSpeed * speedMultiplier,
true
);
}
if (left) {
state.controls.truck(
CONSTANTS.firstPersonControls.leftSpeed * speedMultiplier,
0,
true
);
}
if (right) {
state.controls.truck(
CONSTANTS.firstPersonControls.rightSpeed * speedMultiplier,
0,
true
);
}
});
return null; // This component does not render any UI
};
export default CamMode;
export default CamMode;

View File

@@ -304,8 +304,8 @@ const AddButtons: React.FC<ButtonsProps> = ({
<EyeIcon
fill={
hiddenPanels[selectedZone.zoneId]?.includes(side)
? "var(--primary-color)"
: "var(--text-color)"
? "var(--icon-default-color-active)"
: "var(--icon-default-color)"
}
/>
</div>
@@ -341,8 +341,8 @@ const AddButtons: React.FC<ButtonsProps> = ({
<LockIcon
fill={
selectedZone.lockedPanels.includes(side)
? "var(--primary-color)"
: "var(--text-color)"
? "var(--icon-default-color-active)"
: "var(--icon-default-color)"
}
/>
</div>