merged to main
This commit is contained in:
commit
c953c71f3f
|
@ -45,7 +45,7 @@ const Header: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={index}
|
||||||
className="user-profile"
|
className="user-profile"
|
||||||
style={{ background: getAvatarColor(index) }}
|
style={{ background: getAvatarColor(index, user.userName) }}
|
||||||
>
|
>
|
||||||
{user.userName[0]}
|
{user.userName[0]}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -7,13 +7,11 @@ import {
|
||||||
VisualizationIcon,
|
VisualizationIcon,
|
||||||
} from "../icons/ExportModuleIcons";
|
} from "../icons/ExportModuleIcons";
|
||||||
import useToggleStore from "../../store/useUIToggleStore";
|
import useToggleStore from "../../store/useUIToggleStore";
|
||||||
import { useSelectedZoneStore } from "../../store/useZoneStore";
|
|
||||||
|
|
||||||
const ModuleToggle: React.FC = () => {
|
const ModuleToggle: React.FC = () => {
|
||||||
const { activeModule, setActiveModule } = useModuleStore();
|
const { activeModule, setActiveModule } = useModuleStore();
|
||||||
const { setToggleUI } = useToggleStore();
|
const { setToggleUI } = useToggleStore();
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-toggle-container">
|
<div className="module-toggle-container">
|
||||||
<div
|
<div
|
||||||
|
|
|
@ -31,6 +31,7 @@ import {
|
||||||
useToggleView,
|
useToggleView,
|
||||||
useToolMode,
|
useToolMode,
|
||||||
useTransformMode,
|
useTransformMode,
|
||||||
|
useActiveSubTool,
|
||||||
} from "../../store/store";
|
} from "../../store/store";
|
||||||
import useToggleStore from "../../store/useUIToggleStore";
|
import useToggleStore from "../../store/useUIToggleStore";
|
||||||
import {
|
import {
|
||||||
|
@ -41,7 +42,7 @@ import {
|
||||||
|
|
||||||
const Tools: React.FC = () => {
|
const Tools: React.FC = () => {
|
||||||
const { templates } = useTemplateStore();
|
const { templates } = useTemplateStore();
|
||||||
const [activeSubTool, setActiveSubTool] = useState("cursor");
|
const { activeSubTool, setActiveSubTool } = useActiveSubTool();
|
||||||
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
|
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
|
||||||
const { setToggleUI } = useToggleStore();
|
const { setToggleUI } = useToggleStore();
|
||||||
|
|
||||||
|
@ -56,8 +57,7 @@ const Tools: React.FC = () => {
|
||||||
|
|
||||||
const { widgets3D } = use3DWidget();
|
const { widgets3D } = use3DWidget();
|
||||||
|
|
||||||
const zones = useDroppedObjectsStore((state) => state.zones);
|
const zones = useDroppedObjectsStore((state) => state.zones);
|
||||||
|
|
||||||
|
|
||||||
// wall options
|
// wall options
|
||||||
const { toggleView, setToggleView } = useToggleView();
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { getLines } from "../../../../services/factoryBuilder/lines/getLinesApi";
|
import { getLines } from "../../../../../services/factoryBuilder/lines/getLinesApi";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import {
|
import {
|
||||||
useActiveLayer,
|
useActiveLayer,
|
||||||
useDeletedLines,
|
useDeletedLines,
|
||||||
useNewLines,
|
useNewLines,
|
||||||
useToggleView,
|
useToggleView,
|
||||||
} from "../../../../store/store";
|
} from "../../../../../store/store";
|
||||||
import objectLinesToArray from "./lineConvertions/objectLinesToArray";
|
import objectLinesToArray from "../lineConvertions/objectLinesToArray";
|
||||||
import { Html } from "@react-three/drei";
|
import { Html } from "@react-three/drei";
|
||||||
import * as Types from "../../../../types/world/worldTypes";
|
import * as Types from "../../../../../types/world/worldTypes";
|
||||||
|
|
||||||
const DistanceText = () => {
|
const DistanceText = () => {
|
||||||
const [lines, setLines] = useState<
|
const [lines, setLines] = useState<
|
||||||
|
@ -122,7 +122,7 @@ const DistanceText = () => {
|
||||||
wrapperClass="distance-text-wrapper"
|
wrapperClass="distance-text-wrapper"
|
||||||
className="distance-text"
|
className="distance-text"
|
||||||
// other
|
// other
|
||||||
zIndexRange={[100, 0]}
|
zIndexRange={[1, 0]}
|
||||||
prepend
|
prepend
|
||||||
sprite
|
sprite
|
||||||
>
|
>
|
|
@ -0,0 +1,71 @@
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { Html } from "@react-three/drei";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useActiveLayer } from "../../../../../store/store";
|
||||||
|
|
||||||
|
const ReferenceDistanceText = ({ line }: { line: any }) => {
|
||||||
|
interface TextState {
|
||||||
|
distance: string;
|
||||||
|
position: THREE.Vector3;
|
||||||
|
userData: any;
|
||||||
|
layer: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [text, setTexts] = useState<TextState | null>(null);
|
||||||
|
const { activeLayer } = useActiveLayer();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (line) {
|
||||||
|
if (line.parent === null) {
|
||||||
|
setTexts(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const distance = line.userData.linePoints.cursorPosition.distanceTo(
|
||||||
|
line.userData.linePoints.startPoint
|
||||||
|
);
|
||||||
|
const midpoint = new THREE.Vector3()
|
||||||
|
.addVectors(
|
||||||
|
line.userData.linePoints.cursorPosition,
|
||||||
|
line.userData.linePoints.startPoint
|
||||||
|
)
|
||||||
|
.divideScalar(2);
|
||||||
|
const newTexts = {
|
||||||
|
distance: distance.toFixed(1),
|
||||||
|
position: midpoint,
|
||||||
|
userData: line,
|
||||||
|
layer: activeLayer,
|
||||||
|
};
|
||||||
|
setTexts(newTexts);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group name="Reference_Distance_Text">
|
||||||
|
<mesh>
|
||||||
|
{text !== null && (
|
||||||
|
<Html
|
||||||
|
// data
|
||||||
|
key={text.distance}
|
||||||
|
userData={text.userData}
|
||||||
|
position={[text.position.x, 1, text.position.z]}
|
||||||
|
// class
|
||||||
|
wrapperClass="distance-text-wrapper"
|
||||||
|
className="distance-text"
|
||||||
|
// other
|
||||||
|
zIndexRange={[1, 0]}
|
||||||
|
prepend
|
||||||
|
sprite
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={`Reference_Distance line-${text.userData.userData}`}
|
||||||
|
>
|
||||||
|
{text.distance} m
|
||||||
|
</div>
|
||||||
|
</Html>
|
||||||
|
)}
|
||||||
|
</mesh>
|
||||||
|
</group>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ReferenceDistanceText;
|
|
@ -1,48 +0,0 @@
|
||||||
import * as THREE from 'three';
|
|
||||||
import { Html } from '@react-three/drei';
|
|
||||||
import { useState, useEffect } from 'react';
|
|
||||||
import { useActiveLayer } from '../../../../store/store';
|
|
||||||
|
|
||||||
const ReferenceDistanceText = ({ line }: { line: any }) => {
|
|
||||||
interface TextState {
|
|
||||||
distance: string;
|
|
||||||
position: THREE.Vector3;
|
|
||||||
userData: any;
|
|
||||||
layer: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
const [text, setTexts] = useState<TextState | null>(null);
|
|
||||||
const { activeLayer } = useActiveLayer();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (line) {
|
|
||||||
if (line.parent === null) {
|
|
||||||
setTexts(null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const distance = line.userData.linePoints.cursorPosition.distanceTo(line.userData.linePoints.startPoint);
|
|
||||||
const midpoint = new THREE.Vector3().addVectors(line.userData.linePoints.cursorPosition, line.userData.linePoints.startPoint).divideScalar(2);
|
|
||||||
const newTexts = {
|
|
||||||
distance: distance.toFixed(1),
|
|
||||||
position: midpoint,
|
|
||||||
userData: line,
|
|
||||||
layer: activeLayer
|
|
||||||
};
|
|
||||||
setTexts(newTexts);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<group name='Reference_Distance_Text'>
|
|
||||||
<mesh>
|
|
||||||
{text !== null &&
|
|
||||||
< Html transform sprite key={text.distance} userData={text.userData} scale={5} position={[text.position.x, 1, text.position.z]} style={{ pointerEvents: 'none' }}>
|
|
||||||
<div className={`Reference_Distance line-${text.userData.userData}`}>{text.distance} m</div>
|
|
||||||
</Html>
|
|
||||||
}
|
|
||||||
</mesh>
|
|
||||||
</group >
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ReferenceDistanceText;
|
|
|
@ -69,10 +69,7 @@ const ZoneGroup: React.FC = () => {
|
||||||
},
|
},
|
||||||
transparent: true,
|
transparent: true,
|
||||||
depthWrite: false,
|
depthWrite: false,
|
||||||
blending: THREE.NormalBlending,
|
}), []);
|
||||||
}),
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchZones = async () => {
|
const fetchZones = async () => {
|
||||||
|
|
|
@ -185,10 +185,9 @@ const CamModelsGroup = () => {
|
||||||
position={[-0.015, 0, 0.7]}
|
position={[-0.015, 0, 0.7]}
|
||||||
>
|
>
|
||||||
<CollabUserIcon
|
<CollabUserIcon
|
||||||
userImage={""}
|
userImage={cam.userData.userImage ||""}
|
||||||
userName={cam.userData.userName}
|
userName={cam.userData.userName}
|
||||||
index={index}
|
color={getAvatarColor(index, cam.userData.userName)}
|
||||||
color={getAvatarColor(index)}
|
|
||||||
/>
|
/>
|
||||||
</Html>
|
</Html>
|
||||||
</primitive>
|
</primitive>
|
||||||
|
|
|
@ -4,14 +4,12 @@ import CustomAvatar from "./users/Avatar";
|
||||||
interface CollabUserIconProps {
|
interface CollabUserIconProps {
|
||||||
userName: string;
|
userName: string;
|
||||||
userImage?: string;
|
userImage?: string;
|
||||||
index?: number;
|
|
||||||
color: string;
|
color: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
||||||
userImage,
|
userImage,
|
||||||
userName,
|
userName,
|
||||||
index = 0,
|
|
||||||
color,
|
color,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
|
@ -20,24 +18,7 @@ const CollabUserIcon: React.FC<CollabUserIconProps> = ({
|
||||||
{userImage ? (
|
{userImage ? (
|
||||||
<img className="user-image" src={userImage} alt={userName} />
|
<img className="user-image" src={userImage} alt={userName} />
|
||||||
) : (
|
) : (
|
||||||
<CustomAvatar name={userName} index={index} color={color} />
|
<CustomAvatar name={userName} color={color} />
|
||||||
// <div
|
|
||||||
// className="user-image"
|
|
||||||
// style={{
|
|
||||||
// lineHeight: "30px",
|
|
||||||
// textTransform: "uppercase",
|
|
||||||
// textAlign: "center",
|
|
||||||
// fontSize: "16px",
|
|
||||||
// borderRadius: "50%",
|
|
||||||
// backgroundColor: color,
|
|
||||||
// overflow: "hidden",
|
|
||||||
// backgroundSize: "cover",
|
|
||||||
// backgroundPosition: "center",
|
|
||||||
// color: "white",
|
|
||||||
// fontWeight: "bold",
|
|
||||||
// }}>
|
|
||||||
// {userName[0]}
|
|
||||||
// </div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="user-name" style={{ backgroundColor: color }}>
|
<div className="user-name" style={{ backgroundColor: color }}>
|
||||||
|
|
|
@ -5,7 +5,6 @@ import { getAvatarColor } from "./functions/getAvatarColor";
|
||||||
interface AvatarProps {
|
interface AvatarProps {
|
||||||
name: string; // Name can be a full name or initials
|
name: string; // Name can be a full name or initials
|
||||||
size?: number;
|
size?: number;
|
||||||
index?: number;
|
|
||||||
textColor?: string;
|
textColor?: string;
|
||||||
color?: string; // Optional color prop for future use
|
color?: string; // Optional color prop for future use
|
||||||
}
|
}
|
||||||
|
@ -13,7 +12,6 @@ interface AvatarProps {
|
||||||
const CustomAvatar: React.FC<AvatarProps> = ({
|
const CustomAvatar: React.FC<AvatarProps> = ({
|
||||||
name,
|
name,
|
||||||
size = 100,
|
size = 100,
|
||||||
index = 0,
|
|
||||||
textColor = "#ffffff",
|
textColor = "#ffffff",
|
||||||
color, // Optional color prop for future use
|
color, // Optional color prop for future use
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -28,7 +26,7 @@ const CustomAvatar: React.FC<AvatarProps> = ({
|
||||||
const initials = getInitials(name); // Convert name to initials if needed
|
const initials = getInitials(name); // Convert name to initials if needed
|
||||||
|
|
||||||
// Draw background
|
// Draw background
|
||||||
ctx.fillStyle = color || getAvatarColor(index); // Use color prop or generate color based on index
|
ctx.fillStyle = color || "#323232"; // Use color prop or generate color based on index
|
||||||
ctx.fillRect(0, 0, size, size);
|
ctx.fillRect(0, 0, size, size);
|
||||||
|
|
||||||
// Draw initials
|
// Draw initials
|
||||||
|
@ -42,7 +40,7 @@ const CustomAvatar: React.FC<AvatarProps> = ({
|
||||||
const dataURL = canvas.toDataURL("image/png");
|
const dataURL = canvas.toDataURL("image/png");
|
||||||
setImageSrc(dataURL);
|
setImageSrc(dataURL);
|
||||||
}
|
}
|
||||||
}, [name, size, textColor, index]);
|
}, [name, size, textColor]);
|
||||||
|
|
||||||
if (!imageSrc) {
|
if (!imageSrc) {
|
||||||
return null; // Return null while the image is being generated
|
return null; // Return null while the image is being generated
|
||||||
|
@ -55,18 +53,6 @@ const CustomAvatar: React.FC<AvatarProps> = ({
|
||||||
alt="User Avatar"
|
alt="User Avatar"
|
||||||
style={{ width: "100%", height: "100%" }}
|
style={{ width: "100%", height: "100%" }}
|
||||||
/>
|
/>
|
||||||
// <div
|
|
||||||
// className="user-image"
|
|
||||||
// style={{
|
|
||||||
// width: size,
|
|
||||||
// height: size,
|
|
||||||
// borderRadius: "50%",
|
|
||||||
// overflow: "hidden",
|
|
||||||
// backgroundSize: "cover",
|
|
||||||
// backgroundPosition: "center",
|
|
||||||
// }}>
|
|
||||||
// {name[0]}
|
|
||||||
// </div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,26 +1,67 @@
|
||||||
const avatarColors: string[] = [
|
const avatarColors: string[] = [
|
||||||
"#FF5733", // Red Orange
|
"#FF5733", // Vivid Orange
|
||||||
"#48ac2a", // Leaf Green
|
"#48ac2a", // Leaf Green
|
||||||
"#0050eb", // Royal Blue
|
"#0050eb", // Bright Blue
|
||||||
"#FF33A1", // Hot Pink
|
"#FF33A1", // Hot Pink
|
||||||
"#FF8C33", // Deep Orange
|
"#FF8C33", // Sunset Orange
|
||||||
"#8C33FF", // Violet
|
"#8C33FF", // Violet Purple
|
||||||
"#FF3333", // Bright Red
|
"#FF3333", // Fiery Red
|
||||||
"#43c06d", // Emerald Green
|
"#43c06d", // Emerald Green
|
||||||
"#A133FF", // Amethyst Purple
|
"#A133FF", // Royal Purple
|
||||||
"#C70039", // Crimson
|
"#C70039", // Crimson Red
|
||||||
"#900C3F", // Maroon
|
"#900C3F", // Deep Burgundy
|
||||||
"#581845", // Plum
|
"#581845", // Plum Purple
|
||||||
"#3498DB", // Sky Blue
|
"#3859AD", // Steel Blue
|
||||||
"#2ECC71", // Green Mint
|
"#08873E", // Forest Green
|
||||||
"#E74C3C", // Tomato Red
|
"#E74C3C", // Cherry Red
|
||||||
"#00adff", // Azure
|
"#00adff", // Sky Blue
|
||||||
"#DBAD05", // Amber Yellow
|
"#DBAD05", // Golden Yellow
|
||||||
"#FF5733", // Red Orange
|
"#A13E31", // Brick Red
|
||||||
"#FF33A1", // Hot Pink
|
"#94C40E", // Lime Green
|
||||||
"#900C3F", // Maroon
|
"#060C47", // Midnight Blue
|
||||||
|
"#2FAFAF", // Teal
|
||||||
];
|
];
|
||||||
|
|
||||||
export function getAvatarColor(index: number): string {
|
export function getAvatarColor(index: number, name?: string): string {
|
||||||
|
// Check if the color is already stored in localStorage
|
||||||
|
const localStorageKey = "userAvatarColors";
|
||||||
|
// Helper function to check if local storage is available
|
||||||
|
function isLocalStorageAvailable(): boolean {
|
||||||
|
try {
|
||||||
|
const testKey = "__test__";
|
||||||
|
localStorage.setItem(testKey, "test");
|
||||||
|
localStorage.removeItem(testKey);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check if local storage is available
|
||||||
|
if (isLocalStorageAvailable() && 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];
|
return avatarColors[index % avatarColors.length];
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,190 +1,244 @@
|
||||||
import * as THREE from 'three';
|
import * as THREE from "three";
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useThree, useFrame } from '@react-three/fiber';
|
import { useThree, useFrame } from "@react-three/fiber";
|
||||||
import { useToolMode } from '../../../store/store';
|
import { useToolMode } from "../../../store/store";
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from "@react-three/drei";
|
||||||
|
|
||||||
const MeasurementTool = () => {
|
const MeasurementTool = () => {
|
||||||
const { gl, raycaster, pointer, camera, scene } = useThree();
|
const { gl, raycaster, pointer, camera, scene } = useThree();
|
||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
|
|
||||||
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
||||||
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(null);
|
const [tubeGeometry, setTubeGeometry] = useState<THREE.TubeGeometry | null>(
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
null
|
||||||
const [startConePosition, setStartConePosition] = useState<THREE.Vector3 | null>(null);
|
);
|
||||||
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const [startConeQuaternion, setStartConeQuaternion] = useState(new THREE.Quaternion());
|
const [startConePosition, setStartConePosition] =
|
||||||
const [endConeQuaternion, setEndConeQuaternion] = useState(new THREE.Quaternion());
|
useState<THREE.Vector3 | null>(null);
|
||||||
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
|
const [endConePosition, setEndConePosition] = useState<THREE.Vector3 | null>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
const [startConeQuaternion, setStartConeQuaternion] = useState(
|
||||||
|
new THREE.Quaternion()
|
||||||
|
);
|
||||||
|
const [endConeQuaternion, setEndConeQuaternion] = useState(
|
||||||
|
new THREE.Quaternion()
|
||||||
|
);
|
||||||
|
const [coneSize, setConeSize] = useState({ radius: 0.2, height: 0.5 });
|
||||||
|
|
||||||
|
const MIN_RADIUS = 0.001,
|
||||||
|
MAX_RADIUS = 0.1;
|
||||||
|
const MIN_CONE_RADIUS = 0.01,
|
||||||
|
MAX_CONE_RADIUS = 0.4;
|
||||||
|
const MIN_CONE_HEIGHT = 0.035,
|
||||||
|
MAX_CONE_HEIGHT = 2.0;
|
||||||
|
|
||||||
const MIN_RADIUS = 0.001, MAX_RADIUS = 0.1;
|
useEffect(() => {
|
||||||
const MIN_CONE_RADIUS = 0.01, MAX_CONE_RADIUS = 0.4;
|
const canvasElement = gl.domElement;
|
||||||
const MIN_CONE_HEIGHT = 0.035, MAX_CONE_HEIGHT = 2.0;
|
let drag = false;
|
||||||
|
let isLeftMouseDown = false;
|
||||||
|
|
||||||
useEffect(() => {
|
const onMouseDown = () => {
|
||||||
const canvasElement = gl.domElement;
|
isLeftMouseDown = true;
|
||||||
let drag = false;
|
drag = false;
|
||||||
let isLeftMouseDown = false;
|
|
||||||
|
|
||||||
const onMouseDown = () => {
|
|
||||||
isLeftMouseDown = true;
|
|
||||||
drag = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseUp = (evt: any) => {
|
|
||||||
isLeftMouseDown = false;
|
|
||||||
if (evt.button === 0 && !drag) {
|
|
||||||
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.type === "GridHelper"));
|
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
const intersectionPoint = intersects[0].point.clone();
|
|
||||||
if (points.length < 2) {
|
|
||||||
setPoints([...points, intersectionPoint]);
|
|
||||||
} else {
|
|
||||||
setPoints([intersectionPoint]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onMouseMove = () => {
|
|
||||||
if (isLeftMouseDown) drag = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onContextMenu = (evt: any) => {
|
|
||||||
evt.preventDefault();
|
|
||||||
if (!drag) {
|
|
||||||
evt.preventDefault();
|
|
||||||
setPoints([]);
|
|
||||||
setTubeGeometry(null);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (toolMode === "MeasurementScale") {
|
|
||||||
canvasElement.addEventListener("pointerdown", onMouseDown);
|
|
||||||
canvasElement.addEventListener("pointermove", onMouseMove);
|
|
||||||
canvasElement.addEventListener("pointerup", onMouseUp);
|
|
||||||
canvasElement.addEventListener("contextmenu", onContextMenu);
|
|
||||||
} else {
|
|
||||||
resetMeasurement();
|
|
||||||
setPoints([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
canvasElement.removeEventListener("pointerdown", onMouseDown);
|
|
||||||
canvasElement.removeEventListener("pointermove", onMouseMove);
|
|
||||||
canvasElement.removeEventListener("pointerup", onMouseUp);
|
|
||||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
|
||||||
};
|
|
||||||
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
|
||||||
|
|
||||||
useFrame(() => {
|
|
||||||
if (points.length === 1) {
|
|
||||||
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.type === "GridHelper"));
|
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
|
||||||
updateMeasurement(points[0], intersects[0].point);
|
|
||||||
}
|
|
||||||
} else if (points.length === 2) {
|
|
||||||
updateMeasurement(points[0], points[1]);
|
|
||||||
} else {
|
|
||||||
resetMeasurement();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
|
|
||||||
const distance = start.distanceTo(end);
|
|
||||||
|
|
||||||
const radius = THREE.MathUtils.clamp(distance * 0.02, MIN_RADIUS, MAX_RADIUS);
|
|
||||||
const coneRadius = THREE.MathUtils.clamp(distance * 0.05, MIN_CONE_RADIUS, MAX_CONE_RADIUS);
|
|
||||||
const coneHeight = THREE.MathUtils.clamp(distance * 0.2, MIN_CONE_HEIGHT, MAX_CONE_HEIGHT);
|
|
||||||
|
|
||||||
setConeSize({ radius: coneRadius, height: coneHeight });
|
|
||||||
|
|
||||||
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
|
||||||
|
|
||||||
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
|
|
||||||
|
|
||||||
let tubeStart = start.clone().add(offset);
|
|
||||||
let tubeEnd = end.clone().sub(offset);
|
|
||||||
|
|
||||||
tubeStart.y = Math.max(tubeStart.y, 0);
|
|
||||||
tubeEnd.y = Math.max(tubeEnd.y, 0);
|
|
||||||
|
|
||||||
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
|
|
||||||
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
|
|
||||||
|
|
||||||
setStartConePosition(tubeStart);
|
|
||||||
setEndConePosition(tubeEnd);
|
|
||||||
setStartConeQuaternion(getArrowOrientation(start, end));
|
|
||||||
setEndConeQuaternion(getArrowOrientation(end, start));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetMeasurement = () => {
|
const onMouseUp = (evt: any) => {
|
||||||
|
isLeftMouseDown = false;
|
||||||
|
if (evt.button === 0 && !drag) {
|
||||||
|
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.type === "GridHelper")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (intersects.length > 0) {
|
||||||
|
const intersectionPoint = intersects[0].point.clone();
|
||||||
|
if (points.length < 2) {
|
||||||
|
setPoints([...points, intersectionPoint]);
|
||||||
|
} else {
|
||||||
|
setPoints([intersectionPoint]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onMouseMove = () => {
|
||||||
|
if (isLeftMouseDown) drag = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onContextMenu = (evt: any) => {
|
||||||
|
evt.preventDefault();
|
||||||
|
if (!drag) {
|
||||||
|
evt.preventDefault();
|
||||||
|
setPoints([]);
|
||||||
setTubeGeometry(null);
|
setTubeGeometry(null);
|
||||||
setStartConePosition(null);
|
}
|
||||||
setEndConePosition(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
|
if (toolMode === "MeasurementScale") {
|
||||||
const direction = new THREE.Vector3().subVectors(end, start).normalize().negate();
|
canvasElement.addEventListener("pointerdown", onMouseDown);
|
||||||
const quaternion = new THREE.Quaternion();
|
canvasElement.addEventListener("pointermove", onMouseMove);
|
||||||
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
canvasElement.addEventListener("pointerup", onMouseUp);
|
||||||
return quaternion;
|
canvasElement.addEventListener("contextmenu", onContextMenu);
|
||||||
|
} else {
|
||||||
|
resetMeasurement();
|
||||||
|
setPoints([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
canvasElement.removeEventListener("pointerdown", onMouseDown);
|
||||||
|
canvasElement.removeEventListener("pointermove", onMouseMove);
|
||||||
|
canvasElement.removeEventListener("pointerup", onMouseUp);
|
||||||
|
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||||
};
|
};
|
||||||
|
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
||||||
|
|
||||||
useEffect(() => {
|
useFrame(() => {
|
||||||
if (points.length === 2) {
|
if (points.length === 1) {
|
||||||
console.log(points[0].distanceTo(points[1]));
|
raycaster.setFromCamera(pointer, camera);
|
||||||
}
|
const intersects = raycaster
|
||||||
}, [points])
|
.intersectObjects(scene.children, true)
|
||||||
|
.filter(
|
||||||
|
(intersect) =>
|
||||||
|
!intersect.object.name.includes("Roof") &&
|
||||||
|
!intersect.object.name.includes("MeasurementReference") &&
|
||||||
|
!intersect.object.name.includes("agv-collider") &&
|
||||||
|
!(intersect.object.type === "GridHelper")
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
if (intersects.length > 0) {
|
||||||
<group ref={groupRef} name="MeasurementGroup">
|
updateMeasurement(points[0], intersects[0].point);
|
||||||
{startConePosition && (
|
}
|
||||||
<mesh name='MeasurementReference' position={startConePosition} quaternion={startConeQuaternion}>
|
} else if (points.length === 2) {
|
||||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
updateMeasurement(points[0], points[1]);
|
||||||
<meshBasicMaterial color="yellow" />
|
} else {
|
||||||
</mesh>
|
resetMeasurement();
|
||||||
)}
|
}
|
||||||
{endConePosition && (
|
});
|
||||||
<mesh name='MeasurementReference' position={endConePosition} quaternion={endConeQuaternion}>
|
|
||||||
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
|
||||||
<meshBasicMaterial color="yellow" />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
{tubeGeometry && (
|
|
||||||
<mesh name='MeasurementReference' geometry={tubeGeometry}>
|
|
||||||
<meshBasicMaterial color="yellow" />
|
|
||||||
</mesh>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{startConePosition && endConePosition && (
|
const updateMeasurement = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||||
<Html
|
const distance = start.distanceTo(end);
|
||||||
as="div"
|
|
||||||
center
|
const radius = THREE.MathUtils.clamp(
|
||||||
zIndexRange={[1, 0]}
|
distance * 0.02,
|
||||||
style={{
|
MIN_RADIUS,
|
||||||
padding: "10px",
|
MAX_RADIUS
|
||||||
color: "white",
|
);
|
||||||
borderRadius: "8px",
|
const coneRadius = THREE.MathUtils.clamp(
|
||||||
textAlign: "center",
|
distance * 0.05,
|
||||||
fontFamily: "Arial, sans-serif",
|
MIN_CONE_RADIUS,
|
||||||
}}
|
MAX_CONE_RADIUS
|
||||||
transform
|
);
|
||||||
sprite
|
const coneHeight = THREE.MathUtils.clamp(
|
||||||
scale={THREE.MathUtils.clamp(startConePosition.distanceTo(endConePosition) * 0.25, 0, 10)}
|
distance * 0.2,
|
||||||
position={[(startConePosition.x + endConePosition.x) / 2, (startConePosition.y + endConePosition.y) / 2, (startConePosition.z + endConePosition.z) / 2]}
|
MIN_CONE_HEIGHT,
|
||||||
>
|
MAX_CONE_HEIGHT
|
||||||
<div style={{ color: "black" }} >{startConePosition.distanceTo(endConePosition).toFixed(2)} m</div>
|
|
||||||
</Html>
|
|
||||||
)}
|
|
||||||
</group>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
setConeSize({ radius: coneRadius, height: coneHeight });
|
||||||
|
|
||||||
|
const direction = new THREE.Vector3().subVectors(end, start).normalize();
|
||||||
|
|
||||||
|
const offset = direction.clone().multiplyScalar(coneHeight * 0.5);
|
||||||
|
|
||||||
|
let tubeStart = start.clone().add(offset);
|
||||||
|
let tubeEnd = end.clone().sub(offset);
|
||||||
|
|
||||||
|
tubeStart.y = Math.max(tubeStart.y, 0);
|
||||||
|
tubeEnd.y = Math.max(tubeEnd.y, 0);
|
||||||
|
|
||||||
|
const curve = new THREE.CatmullRomCurve3([tubeStart, tubeEnd]);
|
||||||
|
setTubeGeometry(new THREE.TubeGeometry(curve, 20, radius, 8, false));
|
||||||
|
|
||||||
|
setStartConePosition(tubeStart);
|
||||||
|
setEndConePosition(tubeEnd);
|
||||||
|
setStartConeQuaternion(getArrowOrientation(start, end));
|
||||||
|
setEndConeQuaternion(getArrowOrientation(end, start));
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetMeasurement = () => {
|
||||||
|
setTubeGeometry(null);
|
||||||
|
setStartConePosition(null);
|
||||||
|
setEndConePosition(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getArrowOrientation = (start: THREE.Vector3, end: THREE.Vector3) => {
|
||||||
|
const direction = new THREE.Vector3()
|
||||||
|
.subVectors(end, start)
|
||||||
|
.normalize()
|
||||||
|
.negate();
|
||||||
|
const quaternion = new THREE.Quaternion();
|
||||||
|
quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), direction);
|
||||||
|
return quaternion;
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (points.length === 2) {
|
||||||
|
console.log(points[0].distanceTo(points[1]));
|
||||||
|
}
|
||||||
|
}, [points]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<group ref={groupRef} name="MeasurementGroup">
|
||||||
|
{startConePosition && (
|
||||||
|
<mesh
|
||||||
|
name="MeasurementReference"
|
||||||
|
position={startConePosition}
|
||||||
|
quaternion={startConeQuaternion}
|
||||||
|
>
|
||||||
|
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
{endConePosition && (
|
||||||
|
<mesh
|
||||||
|
name="MeasurementReference"
|
||||||
|
position={endConePosition}
|
||||||
|
quaternion={endConeQuaternion}
|
||||||
|
>
|
||||||
|
<coneGeometry args={[coneSize.radius, coneSize.height, 16]} />
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
{tubeGeometry && (
|
||||||
|
<mesh name="MeasurementReference" geometry={tubeGeometry}>
|
||||||
|
<meshBasicMaterial color="yellow" />
|
||||||
|
</mesh>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{startConePosition && endConePosition && (
|
||||||
|
<Html
|
||||||
|
scale={THREE.MathUtils.clamp(
|
||||||
|
startConePosition.distanceTo(endConePosition) * 0.25,
|
||||||
|
0,
|
||||||
|
10
|
||||||
|
)}
|
||||||
|
position={[
|
||||||
|
(startConePosition.x + endConePosition.x) / 2,
|
||||||
|
(startConePosition.y + endConePosition.y) / 2,
|
||||||
|
(startConePosition.z + endConePosition.z) / 2,
|
||||||
|
]}
|
||||||
|
// class
|
||||||
|
wrapperClass="distance-text-wrapper"
|
||||||
|
className="distance-text"
|
||||||
|
// other
|
||||||
|
zIndexRange={[1, 0]}
|
||||||
|
prepend
|
||||||
|
sprite
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
{startConePosition.distanceTo(endConePosition).toFixed(2)} m
|
||||||
|
</div>
|
||||||
|
</Html>
|
||||||
|
)}
|
||||||
|
</group>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MeasurementTool;
|
export default MeasurementTool;
|
||||||
|
|
|
@ -6,8 +6,8 @@ import { useThree, useFrame } from "@react-three/fiber";
|
||||||
|
|
||||||
////////// Component Imports //////////
|
////////// Component Imports //////////
|
||||||
|
|
||||||
import DistanceText from "../../builder/geomentries/lines/distanceText";
|
import DistanceText from "../../builder/geomentries/lines/distanceText/distanceText";
|
||||||
import ReferenceDistanceText from "../../builder/geomentries/lines/referenceDistanceText";
|
import ReferenceDistanceText from "../../builder/geomentries/lines/distanceText/referenceDistanceText";
|
||||||
|
|
||||||
////////// Assests Imports //////////
|
////////// Assests Imports //////////
|
||||||
|
|
||||||
|
|
|
@ -22,17 +22,17 @@ import LoadingPage from "../components/templates/LoadingPage";
|
||||||
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
||||||
import RenderOverlay from "../components/templates/Overlay";
|
import RenderOverlay from "../components/templates/Overlay";
|
||||||
import MenuBar from "../components/ui/menu/menu";
|
import MenuBar from "../components/ui/menu/menu";
|
||||||
|
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
||||||
|
|
||||||
const Project: React.FC = () => {
|
const Project: React.FC = () => {
|
||||||
let navigate = useNavigate();
|
let navigate = useNavigate();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { loadingProgress, setLoadingProgress } = useLoadingProgress();
|
const { loadingProgress } = useLoadingProgress();
|
||||||
const { setUserName } = useUserName();
|
const { setUserName } = useUserName();
|
||||||
const { setOrganization } = useOrganization();
|
const { setOrganization } = useOrganization();
|
||||||
const { setFloorItems } = useFloorItems();
|
const { setFloorItems } = useFloorItems();
|
||||||
const { setWallItems } = useWallItems();
|
const { setWallItems } = useWallItems();
|
||||||
const { setZones } = useZones();
|
const { setZones } = useZones();
|
||||||
const [openMenu, setOpenMenu] = useState<boolean>(true);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFloorItems([]);
|
setFloorItems([]);
|
||||||
|
@ -56,6 +56,7 @@ const Project: React.FC = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="project-main">
|
<div className="project-main">
|
||||||
|
<KeyPressListener />
|
||||||
{loadingProgress && <LoadingPage progress={loadingProgress} />}
|
{loadingProgress && <LoadingPage progress={loadingProgress} />}
|
||||||
{!isPlaying && (
|
{!isPlaying && (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -248,6 +248,11 @@ export const useActiveTool = create<any>((set: any) => ({
|
||||||
setActiveTool: (x: any) => set({ activeTool: x }),
|
setActiveTool: (x: any) => set({ activeTool: x }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
export const useActiveSubTool = create<any>((set: any) => ({
|
||||||
|
activeSubTool: "cursor",
|
||||||
|
setActiveSubTool: (x: any) => set({ activeSubTool: x }),
|
||||||
|
}));
|
||||||
|
|
||||||
export const use2DUndoRedo = create<any>((set: any) => ({
|
export const use2DUndoRedo = create<any>((set: any) => ({
|
||||||
is2DUndoRedo: null,
|
is2DUndoRedo: null,
|
||||||
set2DUndoRedo: (x: any) => set({ is2DUndoRedo: x }),
|
set2DUndoRedo: (x: any) => set({ is2DUndoRedo: x }),
|
||||||
|
|
|
@ -146,5 +146,6 @@
|
||||||
font-size: var(--font-size-regulaar);
|
font-size: var(--font-size-regulaar);
|
||||||
font-weight: var(--font-size-regulaar);
|
font-weight: var(--font-size-regulaar);
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,17 +68,16 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: var(--background-color);
|
background-color: var(--background-color);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 0px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
|
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
max-width: 80%;
|
max-width: 80%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-width: calc(100% - 500px);
|
max-width: calc(100% - 500px);
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
transform: translate(-50%, -0%);
|
transform: translate(-50%, -10%);
|
||||||
|
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
display: none;
|
display: none;
|
||||||
|
|
|
@ -6,9 +6,9 @@
|
||||||
}
|
}
|
||||||
.distance-text {
|
.distance-text {
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
.distance {
|
div {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
transform: translate(-50%, -50%) scale(.8);
|
transform: translate(-50%, -50%) scale(0.8);
|
||||||
pointer-events: none !important;
|
pointer-events: none !important;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
// style
|
// style
|
||||||
|
@ -22,6 +22,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.pointer-none{
|
.pointer-none {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,187 @@
|
||||||
|
// Importing React and useEffect from React library
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
// Importing the necessary hooks and types from React and TypeScript
|
||||||
|
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
|
||||||
|
import useToggleStore from "../../store/useUIToggleStore";
|
||||||
|
import { useActiveSubTool, useActiveTool, useAddAction, useDeleteTool, useSelectedWallItem, useToggleView, useToolMode } from "../../store/store";
|
||||||
|
import { usePlayButtonStore } from "../../store/usePlayButtonStore";
|
||||||
|
|
||||||
|
const KeyPressListener: React.FC = () => {
|
||||||
|
// Function to detect if Shift, Ctrl, Alt, or combinations are pressed
|
||||||
|
const detectModifierKeys = (event: KeyboardEvent): string => {
|
||||||
|
const modifiers = [
|
||||||
|
event.ctrlKey ? "Ctrl" : "",
|
||||||
|
event.altKey ? "Alt" : "",
|
||||||
|
event.shiftKey ? "Shift" : "",
|
||||||
|
event.metaKey ? "Meta" : "" // Add support for Command/Win key
|
||||||
|
].filter(Boolean);
|
||||||
|
|
||||||
|
// Ignore modifier keys when they're pressed alone
|
||||||
|
const isModifierKey = [
|
||||||
|
"Control", "Shift", "Alt", "Meta",
|
||||||
|
"Ctrl", "AltGraph", "OS" // Additional modifier key aliases
|
||||||
|
].includes(event.key);
|
||||||
|
|
||||||
|
const mainKey = isModifierKey ? "" : event.key.toUpperCase();
|
||||||
|
|
||||||
|
// Handle special cases for keys with different representations
|
||||||
|
const normalizedKey = mainKey === " " ? "Space" : mainKey;
|
||||||
|
|
||||||
|
// Build the combination string
|
||||||
|
if (modifiers.length > 0 && normalizedKey) {
|
||||||
|
return `${modifiers.join("+")}+${normalizedKey}`;
|
||||||
|
} else if (modifiers.length > 0) {
|
||||||
|
return modifiers.join("+");
|
||||||
|
} else {
|
||||||
|
return normalizedKey;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Importing the necessary hooks from the store
|
||||||
|
const { activeModule, setActiveModule } = useModuleStore();
|
||||||
|
const { setActiveSubTool } = useActiveSubTool();
|
||||||
|
const { toggleUI, setToggleUI } = useToggleStore();
|
||||||
|
const { setToggleThreeD } = useThreeDStore();
|
||||||
|
const { setToolMode } = useToolMode();
|
||||||
|
const { setIsPlaying } = usePlayButtonStore();
|
||||||
|
|
||||||
|
// wall options
|
||||||
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
|
const { setDeleteTool } = useDeleteTool();
|
||||||
|
const { setAddAction } = useAddAction();
|
||||||
|
const { setSelectedWallItem } = useSelectedWallItem();
|
||||||
|
const { setActiveTool } = useActiveTool();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Function to handle keydown events
|
||||||
|
const handleKeyPress = (event: KeyboardEvent) => {
|
||||||
|
// Allow default behavior for F5 and F12
|
||||||
|
const keyCombination = detectModifierKeys(event);
|
||||||
|
|
||||||
|
if (["F5", "F11", "F12"].includes(event.key) || keyCombination === "Ctrl+R") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent default action for the key press
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
// Detect the key combination pressed
|
||||||
|
if (keyCombination) {
|
||||||
|
// Check for specific key combinations to switch modules
|
||||||
|
if (keyCombination === "1" && !toggleView) {
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
setActiveModule("builder");
|
||||||
|
}
|
||||||
|
if (keyCombination === "2" && !toggleView) {
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
setActiveModule("simulation");
|
||||||
|
}
|
||||||
|
if (keyCombination === "3" && !toggleView) {
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
setActiveModule("visualization");
|
||||||
|
}
|
||||||
|
if (keyCombination === "4" && !toggleView) {
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
setToggleUI(false);
|
||||||
|
setActiveModule("market");
|
||||||
|
}
|
||||||
|
|
||||||
|
// sidebar toggle
|
||||||
|
if (keyCombination === "Ctrl+." && activeModule !== "market") {
|
||||||
|
setToggleUI(!toggleUI);
|
||||||
|
}
|
||||||
|
|
||||||
|
// tools toggle
|
||||||
|
if (keyCombination === "V") {
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setActiveSubTool("cursor");
|
||||||
|
}
|
||||||
|
if (keyCombination === "X") {
|
||||||
|
setActiveTool("delete");
|
||||||
|
setActiveSubTool("delete");
|
||||||
|
}
|
||||||
|
if (keyCombination === "H") {
|
||||||
|
setActiveTool("free-hand");
|
||||||
|
setActiveSubTool("free-hand");
|
||||||
|
}
|
||||||
|
|
||||||
|
// player toggle
|
||||||
|
if (keyCombination === "Ctrl+P" && !toggleView) {
|
||||||
|
setIsPlaying(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// builder key combination
|
||||||
|
if (activeModule === "builder") {
|
||||||
|
if (keyCombination === "TAB") {
|
||||||
|
if (toggleView) {
|
||||||
|
setToggleView(false);
|
||||||
|
setToggleThreeD(true);
|
||||||
|
setActiveTool("cursor");
|
||||||
|
} else {
|
||||||
|
setSelectedWallItem(null);
|
||||||
|
setDeleteTool(false);
|
||||||
|
setAddAction(null);
|
||||||
|
setToggleView(true);
|
||||||
|
setToggleThreeD(false);
|
||||||
|
setActiveTool("cursor");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// builder tools
|
||||||
|
if (toggleView) {
|
||||||
|
if (keyCombination === "Q" || keyCombination === "6") {
|
||||||
|
setActiveTool("draw-wall");
|
||||||
|
setToolMode("Wall");
|
||||||
|
}
|
||||||
|
if (keyCombination === "R" || keyCombination === "7") {
|
||||||
|
setActiveTool("draw-aisle");
|
||||||
|
setToolMode("Aisle");
|
||||||
|
}
|
||||||
|
if (keyCombination === "E" || keyCombination === "8") {
|
||||||
|
setActiveTool("draw-zone");
|
||||||
|
setToolMode("Zone");
|
||||||
|
}
|
||||||
|
if (keyCombination === "T" || keyCombination === "9") {
|
||||||
|
setActiveTool("draw-floor");
|
||||||
|
setToolMode("Floor");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (keyCombination === "M") {
|
||||||
|
setActiveTool("measure");
|
||||||
|
setToolMode("MeasurementScale");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undo redo
|
||||||
|
if (keyCombination === "Ctrl+Z") {
|
||||||
|
// Handle undo action here
|
||||||
|
}
|
||||||
|
if (keyCombination === "Ctrl+Y" || keyCombination === "Ctrl+Shift+Z") {
|
||||||
|
// Handle redo action here
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanup function to remove event listener
|
||||||
|
if (keyCombination === "ESCAPE") {
|
||||||
|
setActiveTool("cursor");
|
||||||
|
setIsPlaying(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listener for keydown
|
||||||
|
window.addEventListener("keydown", handleKeyPress);
|
||||||
|
|
||||||
|
// Cleanup function to remove event listener
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyPress);
|
||||||
|
};
|
||||||
|
}, [activeModule, toggleUI, toggleView]); // Empty dependency array ensures this runs only once
|
||||||
|
|
||||||
|
return null; // No UI component to render, so return null
|
||||||
|
};
|
||||||
|
|
||||||
|
export default KeyPressListener;
|
Loading…
Reference in New Issue