Enhance collaboration features by adding user targeting and camera control functionality
- Implement user following functionality in Header component - Update FollowPerson component to handle target and rotation - Modify CollabUserIcon to include target data for selected users - Adjust setCameraView function to utilize target for camera positioning - Extend user types to include position, rotation, and target properties
This commit is contained in:
@@ -1,10 +1,10 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } 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 { useActiveUsers, useCamMode } from "../../../store/store";
|
||||||
import { ActiveUser } from "../../../types/users";
|
import { ActiveUser } from "../../../types/users";
|
||||||
import CollaborationPopup from "../../templates/CollaborationPopup";
|
import CollaborationPopup from "../../templates/CollaborationPopup";
|
||||||
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
import { getAvatarColor } from "../../../modules/collaboration/functions/getAvatarColor";
|
||||||
|
import { useSelectedUserStore } from "../../../store/useCollabStore";
|
||||||
|
|
||||||
const Header: React.FC = () => {
|
const Header: React.FC = () => {
|
||||||
const { activeUsers } = useActiveUsers();
|
const { activeUsers } = useActiveUsers();
|
||||||
@@ -15,6 +15,37 @@ const Header: React.FC = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [userManagement, setUserManagement] = useState(false);
|
const [userManagement, setUserManagement] = useState(false);
|
||||||
|
const { setSelectedUser } = useSelectedUserStore();
|
||||||
|
const { setCamMode } = useCamMode();
|
||||||
|
|
||||||
|
function handleUserFollow(user: any, index: number) {
|
||||||
|
const position = {
|
||||||
|
x: user.position?.x!,
|
||||||
|
y: user.position?.y!,
|
||||||
|
z: user.position?.z!,
|
||||||
|
};
|
||||||
|
const target = {
|
||||||
|
x: user.target?.x!,
|
||||||
|
y: user.target?.y!,
|
||||||
|
z: user.target?.z!,
|
||||||
|
};
|
||||||
|
const rotation = {
|
||||||
|
x: user.rotation?.x!,
|
||||||
|
y: user.rotation?.y!,
|
||||||
|
z: user.rotation?.z!,
|
||||||
|
};
|
||||||
|
|
||||||
|
// retun on no data
|
||||||
|
if (!position || !target || !rotation) return;
|
||||||
|
|
||||||
|
// Set the selected user in the store
|
||||||
|
setSelectedUser({
|
||||||
|
color: getAvatarColor(index, user.userName),
|
||||||
|
name: user.userName,
|
||||||
|
location: { position, rotation, target },
|
||||||
|
});
|
||||||
|
setCamMode("FollowPerson");
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -42,13 +73,16 @@ const Header: React.FC = () => {
|
|||||||
<div className="other-guest">+{guestUsers.length - 3}</div>
|
<div className="other-guest">+{guestUsers.length - 3}</div>
|
||||||
)}
|
)}
|
||||||
{guestUsers.slice(0, 3).map((user, index) => (
|
{guestUsers.slice(0, 3).map((user, index) => (
|
||||||
<div
|
<button
|
||||||
key={index}
|
key={`${index}-${user.userName}`}
|
||||||
className="user-profile"
|
className="user-profile"
|
||||||
style={{ background: getAvatarColor(index, user.userName) }}
|
style={{ background: getAvatarColor(index, user.userName) }}
|
||||||
|
onClick={() => {
|
||||||
|
handleUserFollow(user, index);
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{user.userName[0]}
|
{user.userName[0]}
|
||||||
</div>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<div className="user-profile-container">
|
<div className="user-profile-container">
|
||||||
|
|||||||
@@ -13,13 +13,13 @@ const FollowPerson: React.FC = () => {
|
|||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
<div
|
<div
|
||||||
className="follow-person-container"
|
className="follow-person-container"
|
||||||
onClick={() => {
|
onPointerDown={() => {
|
||||||
clearSelectedUser();
|
clearSelectedUser();
|
||||||
setCamMode("FirstPerson");
|
setCamMode("FirstPerson");
|
||||||
}}
|
}}
|
||||||
style={{ "--user-color": selectedUser.color } as React.CSSProperties}
|
style={{ "--user-color": selectedUser.color } as React.CSSProperties}
|
||||||
>
|
>
|
||||||
<div className="follower-name">{selectedUser.name}</div>
|
<div className="follower-name">Viewing through {selectedUser.name}’s eyes</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</RenderOverlay>
|
</RenderOverlay>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Html } from "@react-three/drei";
|
|||||||
import CollabUserIcon from "./collabUserIcon";
|
import CollabUserIcon from "./collabUserIcon";
|
||||||
import useModuleStore from "../../../store/useModuleStore";
|
import useModuleStore from "../../../store/useModuleStore";
|
||||||
import { getAvatarColor } from "../functions/getAvatarColor";
|
import { getAvatarColor } from "../functions/getAvatarColor";
|
||||||
|
import { useSelectedUserStore } from "../../../store/useCollabStore";
|
||||||
|
|
||||||
const CamModelsGroup = () => {
|
const CamModelsGroup = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -19,6 +20,7 @@ const CamModelsGroup = () => {
|
|||||||
const { setActiveUsers } = useActiveUsers();
|
const { setActiveUsers } = useActiveUsers();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
|
const { selectedUser } = useSelectedUserStore();
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
const loader = new GLTFLoader();
|
const loader = new GLTFLoader();
|
||||||
@@ -27,7 +29,16 @@ const CamModelsGroup = () => {
|
|||||||
loader.setDRACOLoader(dracoLoader);
|
loader.setDRACOLoader(dracoLoader);
|
||||||
|
|
||||||
const [cams, setCams] = useState<any[]>([]);
|
const [cams, setCams] = useState<any[]>([]);
|
||||||
const [models, setModels] = useState<Record<string, { targetPosition: THREE.Vector3; targetRotation: THREE.Euler, target: THREE.Vector3 }>>({});
|
const [models, setModels] = useState<
|
||||||
|
Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
targetPosition: THREE.Vector3;
|
||||||
|
targetRotation: THREE.Euler;
|
||||||
|
target: THREE.Vector3;
|
||||||
|
}
|
||||||
|
>
|
||||||
|
>({});
|
||||||
|
|
||||||
const dedupeCams = (cams: any[]) => {
|
const dedupeCams = (cams: any[]) => {
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
@@ -81,6 +92,11 @@ const CamModelsGroup = () => {
|
|||||||
data.data.rotation.z
|
data.data.rotation.z
|
||||||
);
|
);
|
||||||
newModel.userData = data.data.userData;
|
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]));
|
setCams((prev) => dedupeCams([...prev, newModel]));
|
||||||
setActiveUsers((prev: any) =>
|
setActiveUsers((prev: any) =>
|
||||||
@@ -103,7 +119,6 @@ const CamModelsGroup = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on("cameraUpdateResponse", (data: any) => {
|
socket.on("cameraUpdateResponse", (data: any) => {
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!groupRef.current ||
|
!groupRef.current ||
|
||||||
socket.id === data.socketId ||
|
socket.id === data.socketId ||
|
||||||
@@ -191,6 +206,10 @@ const CamModelsGroup = () => {
|
|||||||
cam.rotation.z
|
cam.rotation.z
|
||||||
);
|
);
|
||||||
newModel.userData = cam.userData;
|
newModel.userData = cam.userData;
|
||||||
|
cam.userData.position = newModel.position;
|
||||||
|
cam.userData.rotation = newModel.rotation;
|
||||||
|
newModel.userData.target = cam.target;
|
||||||
|
|
||||||
return newModel;
|
return newModel;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,13 +222,17 @@ const CamModelsGroup = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group
|
<group ref={groupRef} name="Cam-Model-Group">
|
||||||
ref={groupRef}
|
|
||||||
name="Cam-Model-Group"
|
|
||||||
visible={activeModule !== "visualization" ? true : false}
|
|
||||||
>
|
|
||||||
{cams.map((cam, index) => (
|
{cams.map((cam, index) => (
|
||||||
<primitive key={cam.uuid} object={cam}>
|
<primitive
|
||||||
|
key={cam.uuid}
|
||||||
|
//eslint-disable-next-line
|
||||||
|
object={cam}
|
||||||
|
visible={
|
||||||
|
selectedUser?.name !== cam.userData.userName &&
|
||||||
|
activeModule !== "visualization"
|
||||||
|
}
|
||||||
|
>
|
||||||
<Html
|
<Html
|
||||||
as="div"
|
as="div"
|
||||||
center
|
center
|
||||||
@@ -229,6 +252,7 @@ const CamModelsGroup = () => {
|
|||||||
color={getAvatarColor(index, cam.userData.userName)}
|
color={getAvatarColor(index, cam.userData.userName)}
|
||||||
position={cam.position}
|
position={cam.position}
|
||||||
rotation={cam.rotation}
|
rotation={cam.rotation}
|
||||||
|
target={cam.userData.target}
|
||||||
/>
|
/>
|
||||||
</Html>
|
</Html>
|
||||||
</primitive>
|
</primitive>
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ interface CollabUserIconProps {
|
|||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
};
|
};
|
||||||
|
target?: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
||||||
@@ -25,6 +30,7 @@ const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
|||||||
color,
|
color,
|
||||||
position,
|
position,
|
||||||
rotation,
|
rotation,
|
||||||
|
target,
|
||||||
}) => {
|
}) => {
|
||||||
const { setSelectedUser } = useSelectedUserStore();
|
const { setSelectedUser } = useSelectedUserStore();
|
||||||
const { setCamMode } = useCamMode();
|
const { setCamMode } = useCamMode();
|
||||||
@@ -33,10 +39,13 @@ const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
|||||||
<button
|
<button
|
||||||
className="user-image-container"
|
className="user-image-container"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if(!position || !rotation) return;
|
if (!position || !rotation || !target) return;
|
||||||
|
|
||||||
// Set the selected user in the store
|
// Set the selected user in the store
|
||||||
setSelectedUser({ color: color, name: userName, location: { position, rotation } });
|
setSelectedUser({
|
||||||
|
color: color,
|
||||||
|
name: userName,
|
||||||
|
location: { position, rotation, target },
|
||||||
|
});
|
||||||
setCamMode("FollowPerson");
|
setCamMode("FollowPerson");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ const Collaboration: React.FC = () => {
|
|||||||
// If a user is selected, set the camera view to their location
|
// If a user is selected, set the camera view to their location
|
||||||
// and update the camera and controls accordingly
|
// and update the camera and controls accordingly
|
||||||
if (selectedUser?.location) {
|
if (selectedUser?.location) {
|
||||||
const { position, rotation } = selectedUser.location;
|
const { position, rotation, target } = selectedUser.location;
|
||||||
|
if (rotation && target)
|
||||||
setCameraView({
|
setCameraView({
|
||||||
controls,
|
controls,
|
||||||
camera,
|
camera,
|
||||||
position,
|
position,
|
||||||
rotation,
|
rotation,
|
||||||
|
target,
|
||||||
username: selectedUser.name,
|
username: selectedUser.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,16 @@ export default async function setCameraView({
|
|||||||
}: SetCameraViewProps) {
|
}: SetCameraViewProps) {
|
||||||
if (!controls || !camera) return;
|
if (!controls || !camera) return;
|
||||||
|
|
||||||
|
if (target == null) return;
|
||||||
|
|
||||||
// Normalize position
|
// Normalize position
|
||||||
const newPosition = position instanceof THREE.Vector3
|
const newPosition = position instanceof THREE.Vector3
|
||||||
? position
|
? position
|
||||||
: new THREE.Vector3(position.x, position.y, position.z);
|
: new THREE.Vector3(position.x, position.y, position.z);
|
||||||
|
|
||||||
if (controls.setTarget) {
|
const newTarget = target instanceof THREE.Vector3 ? target : new THREE.Vector3(target.x, target.y, target.z);
|
||||||
controls?.setLookAt(...newPosition.toArray(), newPosition.x, 0, newPosition.z, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (controls.setTarget) {
|
||||||
|
controls?.setLookAt(...newPosition.toArray(), ...newTarget.toArray(), true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,12 @@ interface SelectedUser {
|
|||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
};
|
};
|
||||||
rotation: {
|
rotation?: {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
z: number;
|
||||||
|
};
|
||||||
|
target?: {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
z: number;
|
z: number;
|
||||||
|
|||||||
3
app/src/types/users.d.ts
vendored
3
app/src/types/users.d.ts
vendored
@@ -15,4 +15,7 @@ export type ActiveUser = {
|
|||||||
userName: string;
|
userName: string;
|
||||||
email: string;
|
email: string;
|
||||||
activeStatus?: string; // Optional property
|
activeStatus?: string; // Optional property
|
||||||
|
position?: { x: number; y: number; z: number; };
|
||||||
|
rotation?: { x: number; y: number; z: number; };
|
||||||
|
target?: { x: number; y: number; z: number; };
|
||||||
};
|
};
|
||||||
Reference in New Issue
Block a user