added useLineEventHandler
This commit is contained in:
585
app/src/modules/builder/line/eventHandler/useLineEventHandler.ts
Normal file
585
app/src/modules/builder/line/eventHandler/useLineEventHandler.ts
Normal file
@@ -0,0 +1,585 @@
|
||||
import * as THREE from "three";
|
||||
import { ThreeEvent, useThree } from "@react-three/fiber";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { useSocketStore, useToolMode } from "../../../../store/builder/store";
|
||||
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||
import { useSceneContext } from "../../../scene/sceneContext";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../../functions/getUserData";
|
||||
import { handleCanvasCursors } from "../../../../utils/mouseUtils/handleCanvasCursors";
|
||||
import { useSelectedPoints } from "../../../../store/simulation/useSimulationStore";
|
||||
import { calculateAssetTransformationOnWall } from "../../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall";
|
||||
|
||||
import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
|
||||
import { deleteWallApi } from "../../../../services/factoryBuilder/wall/deleteWallApi";
|
||||
import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
|
||||
import { deleteFloorApi } from "../../../../services/factoryBuilder/floor/deleteFloorApi";
|
||||
import { deleteZoneApi } from "../../../../services/factoryBuilder/zone/deleteZoneApi";
|
||||
import { upsertZoneApi } from "../../../../services/factoryBuilder/zone/upsertZoneApi";
|
||||
import { upsertWallAssetApi } from "../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi";
|
||||
import { deleteWallAssetApi } from "../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi";
|
||||
|
||||
interface LineProps {
|
||||
points: [Point, Point];
|
||||
}
|
||||
|
||||
export function useLineEventHandler({ points }: Readonly<LineProps>) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { raycaster, camera, pointer } = useThree();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const [isDeletable, setIsDeletable] = useState(false);
|
||||
const { socket } = useSocketStore();
|
||||
const { toolMode } = useToolMode();
|
||||
const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore, versionStore } = useSceneContext();
|
||||
const { push2D } = undoRedo2DStore();
|
||||
const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore();
|
||||
const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore();
|
||||
const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore();
|
||||
const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore();
|
||||
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
||||
const { selectedPoints } = useSelectedPoints();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
const [initialPositions, setInitialPositions] = useState<{
|
||||
aisles?: Aisle[];
|
||||
walls?: Wall[];
|
||||
floors?: Floor[];
|
||||
zones?: Zone[];
|
||||
}>({});
|
||||
|
||||
const handleDrag = useCallback(() => {
|
||||
if (toolMode === "move" && isHovered && dragOffset) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (hit) {
|
||||
handleCanvasCursors("grabbing");
|
||||
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
||||
|
||||
const start = new THREE.Vector3(...points[0].position);
|
||||
const end = new THREE.Vector3(...points[1].position);
|
||||
const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
|
||||
|
||||
const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint);
|
||||
|
||||
const newStart = new THREE.Vector3().addVectors(start, delta);
|
||||
const newEnd = new THREE.Vector3().addVectors(end, delta);
|
||||
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
||||
setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
||||
}
|
||||
if (points[0].pointType === "Floor" && points[1].pointType === "Floor") {
|
||||
setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
||||
setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
||||
}
|
||||
if (points[0].pointType === "Zone" && points[1].pointType === "Zone") {
|
||||
setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
||||
setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [camera, toolMode, isHovered, dragOffset, points]);
|
||||
|
||||
const handleDragStart = useCallback(() => {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (hit && !hoveredPoint) {
|
||||
const start = new THREE.Vector3(...points[0].position);
|
||||
const end = new THREE.Vector3(...points[1].position);
|
||||
const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
|
||||
|
||||
const offset = new THREE.Vector3().subVectors(midPoint, hit);
|
||||
setDragOffset(offset);
|
||||
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
const wall = getWallByPoints(points);
|
||||
if (wall) {
|
||||
const walls = getConnectedWallsByWallId(wall.wallUuid, false);
|
||||
setInitialPositions({ walls });
|
||||
}
|
||||
} else if (points[0].pointType === "Floor") {
|
||||
const floors = getFloorsByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ floors });
|
||||
} else if (points[0].pointType === "Zone") {
|
||||
const zones = getZonesByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ zones });
|
||||
}
|
||||
}
|
||||
}, [camera, points, hoveredPoint]);
|
||||
|
||||
const handleDragEnd = useCallback(() => {
|
||||
if (toolMode !== "move" || !dragOffset) return;
|
||||
handleCanvasCursors("default");
|
||||
setDragOffset(null);
|
||||
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
const wall = getWallByPoints(points);
|
||||
if (!wall) return;
|
||||
const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false);
|
||||
|
||||
if (updatedWalls.length > 0 && projectId) {
|
||||
updatedWalls.forEach((updatedWall) => {
|
||||
const initialWall = initialPositions.walls?.find((w) => w.wallUuid === updatedWall.wallUuid);
|
||||
|
||||
if (initialWall) {
|
||||
const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid);
|
||||
|
||||
assetsOnWall.forEach((asset) => {
|
||||
const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall);
|
||||
|
||||
const updatedWallAsset = updateWallAsset(asset.modelUuid, {
|
||||
position: [position[0], asset.position[1], position[2]],
|
||||
rotation: rotation,
|
||||
});
|
||||
|
||||
if (projectId && updatedWallAsset) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertWallAssetApi(projectId, selectedVersion?.versionId || "", updatedWallAsset);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallAssetData: updatedWallAsset,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:wall-asset:add", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertWallApi(projectId, selectedVersion?.versionId || "", updatedWall);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallData: updatedWall,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Wall:add", data);
|
||||
}
|
||||
});
|
||||
|
||||
if (initialPositions.walls && initialPositions.walls.length > 0) {
|
||||
const updatedPoints = initialPositions.walls.map((wall) => ({
|
||||
type: "Wall" as const,
|
||||
lineData: wall,
|
||||
newData: updatedWalls.find((w) => w.wallUuid === wall.wallUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedPoints,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (points[0].pointType === "Floor" && points[1].pointType === "Floor") {
|
||||
const updatedFloors1 = getFloorsByPointId(points[0].pointUuid);
|
||||
const updatedFloors2 = getFloorsByPointId(points[1].pointUuid);
|
||||
const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid));
|
||||
|
||||
if (updatedFloors.length > 0 && projectId) {
|
||||
updatedFloors.forEach((updatedFloor) => {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertFloorApi(projectId, selectedVersion?.versionId || "", updatedFloor);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
floorData: updatedFloor,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Floor:add", data);
|
||||
}
|
||||
});
|
||||
|
||||
if (initialPositions.floors && initialPositions.floors.length > 0) {
|
||||
const updatedPoints = initialPositions.floors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: floor,
|
||||
newData: updatedFloors.find((f) => f.floorUuid === floor.floorUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedPoints,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (points[0].pointType === "Zone" && points[1].pointType === "Zone") {
|
||||
const updatedZones1 = getZonesByPointId(points[0].pointUuid);
|
||||
const updatedZones2 = getZonesByPointId(points[1].pointUuid);
|
||||
const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid));
|
||||
|
||||
if (updatedZones.length > 0 && projectId) {
|
||||
updatedZones.forEach((updatedZone) => {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertZoneApi(projectId, selectedVersion?.versionId || "", updatedZone);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
zoneData: updatedZone,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:add", data);
|
||||
}
|
||||
});
|
||||
|
||||
if (initialPositions.zones && initialPositions.zones.length > 0) {
|
||||
const updatedPoints = initialPositions.zones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: zone,
|
||||
newData: updatedZones.find((z) => z.zoneUuid === zone.zoneUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedPoints,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [socket, points, toolMode, dragOffset, projectId, selectedVersion, userId, organization]);
|
||||
|
||||
const handleLineClick = useCallback(() => {
|
||||
if (toolMode === "2D-Delete") {
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
const removedWall = removeWallByPoints(points);
|
||||
if (removedWall && projectId) {
|
||||
const assetsOnWall = getWallAssetsByWall(removedWall.wallUuid);
|
||||
|
||||
assetsOnWall.forEach((asset) => {
|
||||
if (projectId && asset) {
|
||||
removeWallAsset(asset.modelUuid);
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteWallAssetApi(projectId, selectedVersion?.versionId || "", asset.modelUuid, asset.wallUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
wallUuid: asset.wallUuid,
|
||||
};
|
||||
|
||||
socket.emit("v1:wall-asset:delete", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteWallApi(projectId, selectedVersion?.versionId || "", removedWall.wallUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallUuid: removedWall.wallUuid,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Wall:delete", data);
|
||||
}
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Line-Delete",
|
||||
point: {
|
||||
type: "Wall",
|
||||
lineData: removedWall,
|
||||
timeStamp: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
setHoveredLine(null);
|
||||
}
|
||||
if (points[0].pointType === "Floor" && points[1].pointType === "Floor") {
|
||||
const Floors = getFloorsByPoints(points);
|
||||
const { removedFloors, updatedFloors } = removeFloorByPoints(points);
|
||||
if (removedFloors.length > 0) {
|
||||
removedFloors.forEach((floor) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteFloorApi(projectId, selectedVersion?.versionId || "", floor.floorUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
floorUuid: floor.floorUuid,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Floor:delete", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const removedFloorsData = removedFloors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Delete",
|
||||
points: removedFloorsData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (updatedFloors.length > 0) {
|
||||
updatedFloors.forEach((floor) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertFloorApi(projectId, selectedVersion?.versionId || "", floor);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
floorData: floor,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Floor:add", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updatedFloorsData = updatedFloors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: Floors.find((f) => f.floorUuid === floor.floorUuid) || floor,
|
||||
newData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedFloorsData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
setHoveredLine(null);
|
||||
}
|
||||
if (points[0].pointType === "Zone" && points[1].pointType === "Zone") {
|
||||
const Zones = getZonesByPoints(points);
|
||||
const { removedZones, updatedZones } = removeZoneByPoints(points);
|
||||
if (removedZones.length > 0) {
|
||||
removedZones.forEach((zone) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteZoneApi(projectId, selectedVersion?.versionId || "", zone.zoneUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
zoneUuid: zone.zoneUuid,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:delete", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const removedZonesData = removedZones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Delete",
|
||||
points: removedZonesData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (updatedZones.length > 0) {
|
||||
updatedZones.forEach((zone) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertZoneApi(projectId, selectedVersion?.versionId || "", zone);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
zoneData: zone,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:add", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updatedZonesData = updatedZones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: Zones.find((z) => z.zoneUuid === zone.zoneUuid) || zone,
|
||||
newData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedZonesData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
setHoveredLine(null);
|
||||
}
|
||||
handleCanvasCursors("default");
|
||||
}
|
||||
}, [socket, points, toolMode, projectId, selectedVersion, userId, organization]);
|
||||
|
||||
const handlePointerOver = useCallback(
|
||||
(e: ThreeEvent<MouseEvent>) => {
|
||||
if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) {
|
||||
setHoveredLine(points);
|
||||
setIsHovered(true);
|
||||
if (toolMode === "move" && !hoveredPoint) {
|
||||
handleCanvasCursors("grab");
|
||||
}
|
||||
}
|
||||
},
|
||||
[selectedPoints, toolMode, hoveredPoint]
|
||||
);
|
||||
|
||||
const handlePointerOut = useCallback(() => {
|
||||
if (hoveredLine && isHovered) {
|
||||
setHoveredLine(null);
|
||||
if (!hoveredPoint && toolMode === "move") {
|
||||
handleCanvasCursors("default");
|
||||
}
|
||||
}
|
||||
setIsHovered(false);
|
||||
}, [hoveredLine, isHovered, hoveredPoint, toolMode]);
|
||||
|
||||
useEffect(() => {
|
||||
if (toolMode === "2D-Delete") {
|
||||
if (isHovered && !hoveredPoint) {
|
||||
setIsDeletable(true);
|
||||
} else {
|
||||
setIsDeletable(false);
|
||||
}
|
||||
} else {
|
||||
setIsDeletable(false);
|
||||
}
|
||||
}, [isHovered, toolMode, hoveredPoint]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) {
|
||||
setIsHovered(false);
|
||||
}
|
||||
}, [hoveredLine]);
|
||||
|
||||
return {
|
||||
isHovered,
|
||||
isDeletable,
|
||||
handleDrag,
|
||||
handleDragStart,
|
||||
handleDragEnd,
|
||||
handleLineClick,
|
||||
handlePointerOver,
|
||||
handlePointerOut,
|
||||
};
|
||||
}
|
||||
@@ -1,57 +1,15 @@
|
||||
import * as THREE from "three";
|
||||
import { useThree } from "@react-three/fiber";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useMemo } from "react";
|
||||
import { DragControls, Tube } from "@react-three/drei";
|
||||
import { useSocketStore, useToolMode } from "../../../store/builder/store";
|
||||
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
|
||||
import { useSceneContext } from "../../scene/sceneContext";
|
||||
import * as Constants from "../../../types/world/worldConstants";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { getUserData } from "../../../functions/getUserData";
|
||||
import { handleCanvasCursors } from "../../../utils/mouseUtils/handleCanvasCursors";
|
||||
import { useSelectedPoints } from "../../../store/simulation/useSimulationStore";
|
||||
import { calculateAssetTransformationOnWall } from "../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall";
|
||||
|
||||
import { upsertWallApi } from "../../../services/factoryBuilder/wall/upsertWallApi";
|
||||
import { deleteWallApi } from "../../../services/factoryBuilder/wall/deleteWallApi";
|
||||
import { upsertFloorApi } from "../../../services/factoryBuilder/floor/upsertFloorApi";
|
||||
import { deleteFloorApi } from "../../../services/factoryBuilder/floor/deleteFloorApi";
|
||||
import { deleteZoneApi } from "../../../services/factoryBuilder/zone/deleteZoneApi";
|
||||
import { upsertZoneApi } from "../../../services/factoryBuilder/zone/upsertZoneApi";
|
||||
import { upsertWallAssetApi } from "../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi";
|
||||
import { deleteWallAssetApi } from "../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi";
|
||||
import { useLineEventHandler } from "./eventHandler/useLineEventHandler";
|
||||
|
||||
interface LineProps {
|
||||
points: [Point, Point];
|
||||
}
|
||||
|
||||
function Line({ points }: Readonly<LineProps>) {
|
||||
const [isHovered, setIsHovered] = useState(false);
|
||||
const { raycaster, camera, pointer } = useThree();
|
||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||
const [isDeletable, setIsDeletable] = useState(false);
|
||||
const { socket } = useSocketStore();
|
||||
const { toolMode } = useToolMode();
|
||||
const { wallStore, floorStore, zoneStore, undoRedo2DStore, wallAssetStore, versionStore } = useSceneContext();
|
||||
const { push2D } = undoRedo2DStore();
|
||||
const { getWallAssetsByWall, updateWallAsset, removeWallAsset } = wallAssetStore();
|
||||
const { removeWallByPoints, setPosition: setWallPosition, getWallByPoints, getConnectedWallsByWallId } = wallStore();
|
||||
const { removeFloorByPoints, setPosition: setFloorPosition, getFloorsByPointId, getFloorsByPoints } = floorStore();
|
||||
const { removeZoneByPoints, setPosition: setZonePosition, getZonesByPointId, getZonesByPoints } = zoneStore();
|
||||
const { selectedVersion } = versionStore();
|
||||
const { hoveredLine, setHoveredLine, hoveredPoint } = useBuilderStore();
|
||||
const [dragOffset, setDragOffset] = useState<THREE.Vector3 | null>(null);
|
||||
const { selectedPoints } = useSelectedPoints();
|
||||
const { projectId } = useParams();
|
||||
const { userId, organization } = getUserData();
|
||||
|
||||
const [initialPositions, setInitialPositions] = useState<{
|
||||
aisles?: Aisle[];
|
||||
walls?: Wall[];
|
||||
floors?: Floor[];
|
||||
zones?: Zone[];
|
||||
}>({});
|
||||
|
||||
const path = useMemo(() => {
|
||||
const [start, end] = points.map((p) => new THREE.Vector3(...p.position));
|
||||
return new THREE.LineCurve3(start, end);
|
||||
@@ -88,534 +46,19 @@ function Line({ points }: Readonly<LineProps>) {
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (toolMode === "2D-Delete") {
|
||||
if (isHovered && !hoveredPoint) {
|
||||
setIsDeletable(true);
|
||||
} else {
|
||||
setIsDeletable(false);
|
||||
}
|
||||
} else {
|
||||
setIsDeletable(false);
|
||||
}
|
||||
}, [isHovered, colors.defaultLineColor, colors.defaultDeleteColor, toolMode, hoveredPoint]);
|
||||
|
||||
useEffect(() => {
|
||||
if (hoveredLine && (hoveredLine[0].pointUuid !== points[0].pointUuid || hoveredLine[1].pointUuid !== points[1].pointUuid)) {
|
||||
setIsHovered(false);
|
||||
}
|
||||
}, [hoveredLine]);
|
||||
|
||||
const handlePointClick = (points: [Point, Point]) => {
|
||||
if (toolMode === "2D-Delete") {
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
const removedWall = removeWallByPoints(points);
|
||||
if (removedWall && projectId) {
|
||||
const assetsOnWall = getWallAssetsByWall(removedWall.wallUuid);
|
||||
|
||||
assetsOnWall.forEach((asset) => {
|
||||
if (projectId && asset) {
|
||||
removeWallAsset(asset.modelUuid);
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteWallAssetApi(projectId, selectedVersion?.versionId || "", asset.modelUuid, asset.wallUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
modelUuid: asset.modelUuid,
|
||||
wallUuid: asset.wallUuid,
|
||||
};
|
||||
|
||||
socket.emit("v1:wall-asset:delete", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteWallApi(projectId, selectedVersion?.versionId || "", removedWall.wallUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallUuid: removedWall.wallUuid,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Wall:delete", data);
|
||||
}
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Line-Delete",
|
||||
point: {
|
||||
type: "Wall",
|
||||
lineData: removedWall,
|
||||
timeStamp: new Date().toISOString(),
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
setHoveredLine(null);
|
||||
}
|
||||
if (points[0].pointType === "Floor" && points[1].pointType === "Floor") {
|
||||
const Floors = getFloorsByPoints(points);
|
||||
const { removedFloors, updatedFloors } = removeFloorByPoints(points);
|
||||
if (removedFloors.length > 0) {
|
||||
removedFloors.forEach((floor) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteFloorApi(projectId, selectedVersion?.versionId || "", floor.floorUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
floorUuid: floor.floorUuid,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Floor:delete", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const removedFloorsData = removedFloors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Delete",
|
||||
points: removedFloorsData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (updatedFloors.length > 0) {
|
||||
updatedFloors.forEach((floor) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertFloorApi(projectId, selectedVersion?.versionId || "", floor);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
floorData: floor,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Floor:add", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updatedFloorsData = updatedFloors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: Floors.find((f) => f.floorUuid === floor.floorUuid) || floor,
|
||||
newData: floor,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedFloorsData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
setHoveredLine(null);
|
||||
}
|
||||
if (points[0].pointType === "Zone" && points[1].pointType === "Zone") {
|
||||
const Zones = getZonesByPoints(points);
|
||||
const { removedZones, updatedZones } = removeZoneByPoints(points);
|
||||
if (removedZones.length > 0) {
|
||||
removedZones.forEach((zone) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
deleteZoneApi(projectId, selectedVersion?.versionId || "", zone.zoneUuid);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
zoneUuid: zone.zoneUuid,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:delete", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const removedZonesData = removedZones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Delete",
|
||||
points: removedZonesData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
if (updatedZones.length > 0) {
|
||||
updatedZones.forEach((zone) => {
|
||||
if (projectId) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertZoneApi(projectId, selectedVersion?.versionId || "", zone);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
zoneData: zone,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:add", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const updatedZonesData = updatedZones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: Zones.find((z) => z.zoneUuid === zone.zoneUuid) || zone,
|
||||
newData: zone,
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedZonesData,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
setHoveredLine(null);
|
||||
}
|
||||
handleCanvasCursors("default");
|
||||
}
|
||||
};
|
||||
|
||||
const handleDrag = (points: [Point, Point]) => {
|
||||
if (toolMode === "move" && isHovered && dragOffset) {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (hit) {
|
||||
handleCanvasCursors("grabbing");
|
||||
const positionWithOffset = new THREE.Vector3().addVectors(hit, dragOffset);
|
||||
|
||||
const start = new THREE.Vector3(...points[0].position);
|
||||
const end = new THREE.Vector3(...points[1].position);
|
||||
const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
|
||||
|
||||
const delta = new THREE.Vector3().subVectors(positionWithOffset, midPoint);
|
||||
|
||||
const newStart = new THREE.Vector3().addVectors(start, delta);
|
||||
const newEnd = new THREE.Vector3().addVectors(end, delta);
|
||||
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
setWallPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
||||
setWallPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
||||
}
|
||||
if (points[0].pointType === "Floor" && points[1].pointType === "Floor") {
|
||||
setFloorPosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
||||
setFloorPosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
||||
}
|
||||
if (points[0].pointType === "Zone" && points[1].pointType === "Zone") {
|
||||
setZonePosition(points[0].pointUuid, [newStart.x, newStart.y, newStart.z]);
|
||||
setZonePosition(points[1].pointUuid, [newEnd.x, newEnd.y, newEnd.z]);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragStart = (points: [Point, Point]) => {
|
||||
raycaster.setFromCamera(pointer, camera);
|
||||
const intersectionPoint = new THREE.Vector3();
|
||||
const hit = raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||
|
||||
if (hit && !hoveredPoint) {
|
||||
const start = new THREE.Vector3(...points[0].position);
|
||||
const end = new THREE.Vector3(...points[1].position);
|
||||
const midPoint = new THREE.Vector3().addVectors(start, end).multiplyScalar(0.5);
|
||||
|
||||
const offset = new THREE.Vector3().subVectors(midPoint, hit);
|
||||
setDragOffset(offset);
|
||||
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
const wall = getWallByPoints(points);
|
||||
if (wall) {
|
||||
const walls = getConnectedWallsByWallId(wall.wallUuid, false);
|
||||
setInitialPositions({ walls });
|
||||
}
|
||||
} else if (points[0].pointType === "Floor") {
|
||||
const floors = getFloorsByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ floors });
|
||||
} else if (points[0].pointType === "Zone") {
|
||||
const zones = getZonesByPointId(points[0].pointUuid);
|
||||
setInitialPositions({ zones });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleDragEnd = (points: [Point, Point]) => {
|
||||
if (toolMode !== "move" || !dragOffset) return;
|
||||
handleCanvasCursors("default");
|
||||
setDragOffset(null);
|
||||
|
||||
if (points[0].pointType === "Wall" && points[1].pointType === "Wall") {
|
||||
const wall = getWallByPoints(points);
|
||||
if (!wall) return;
|
||||
const updatedWalls = getConnectedWallsByWallId(wall.wallUuid, false);
|
||||
|
||||
if (updatedWalls.length > 0 && projectId) {
|
||||
updatedWalls.forEach((updatedWall) => {
|
||||
const initialWall = initialPositions.walls?.find((w) => w.wallUuid === updatedWall.wallUuid);
|
||||
|
||||
if (initialWall) {
|
||||
const assetsOnWall = getWallAssetsByWall(updatedWall.wallUuid);
|
||||
|
||||
assetsOnWall.forEach((asset) => {
|
||||
const { position, rotation } = calculateAssetTransformationOnWall(asset, initialWall, updatedWall);
|
||||
|
||||
const updatedWallAsset = updateWallAsset(asset.modelUuid, {
|
||||
position: [position[0], asset.position[1], position[2]],
|
||||
rotation: rotation,
|
||||
});
|
||||
|
||||
if (projectId && updatedWallAsset) {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertWallAssetApi(projectId, selectedVersion?.versionId || "", updatedWallAsset);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallAssetData: updatedWallAsset,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:wall-asset:add", data);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertWallApi(projectId, selectedVersion?.versionId || "", updatedWall);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
wallData: updatedWall,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Wall:add", data);
|
||||
}
|
||||
});
|
||||
|
||||
if (initialPositions.walls && initialPositions.walls.length > 0) {
|
||||
const updatedPoints = initialPositions.walls.map((wall) => ({
|
||||
type: "Wall" as const,
|
||||
lineData: wall,
|
||||
newData: updatedWalls.find((w) => w.wallUuid === wall.wallUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedPoints,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (points[0].pointType === "Floor" && points[1].pointType === "Floor") {
|
||||
const updatedFloors1 = getFloorsByPointId(points[0].pointUuid);
|
||||
const updatedFloors2 = getFloorsByPointId(points[1].pointUuid);
|
||||
const updatedFloors = [...updatedFloors1, ...updatedFloors2].filter((floor, index, self) => index === self.findIndex((f) => f.floorUuid === floor.floorUuid));
|
||||
|
||||
if (updatedFloors.length > 0 && projectId) {
|
||||
updatedFloors.forEach((updatedFloor) => {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertFloorApi(projectId, selectedVersion?.versionId || "", updatedFloor);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
floorData: updatedFloor,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:model-Floor:add", data);
|
||||
}
|
||||
});
|
||||
|
||||
if (initialPositions.floors && initialPositions.floors.length > 0) {
|
||||
const updatedPoints = initialPositions.floors.map((floor) => ({
|
||||
type: "Floor" as const,
|
||||
lineData: floor,
|
||||
newData: updatedFloors.find((f) => f.floorUuid === floor.floorUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedPoints,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (points[0].pointType === "Zone" && points[1].pointType === "Zone") {
|
||||
const updatedZones1 = getZonesByPointId(points[0].pointUuid);
|
||||
const updatedZones2 = getZonesByPointId(points[1].pointUuid);
|
||||
const updatedZones = [...updatedZones1, ...updatedZones2].filter((zone, index, self) => index === self.findIndex((z) => z.zoneUuid === zone.zoneUuid));
|
||||
|
||||
if (updatedZones.length > 0 && projectId) {
|
||||
updatedZones.forEach((updatedZone) => {
|
||||
if (!socket?.connected) {
|
||||
// API
|
||||
|
||||
upsertZoneApi(projectId, selectedVersion?.versionId || "", updatedZone);
|
||||
} else {
|
||||
// SOCKET
|
||||
|
||||
const data = {
|
||||
zoneData: updatedZone,
|
||||
projectId: projectId,
|
||||
versionId: selectedVersion?.versionId || "",
|
||||
userId: userId,
|
||||
organization: organization,
|
||||
};
|
||||
|
||||
socket.emit("v1:zone:add", data);
|
||||
}
|
||||
});
|
||||
|
||||
if (initialPositions.zones && initialPositions.zones.length > 0) {
|
||||
const updatedPoints = initialPositions.zones.map((zone) => ({
|
||||
type: "Zone" as const,
|
||||
lineData: zone,
|
||||
newData: updatedZones.find((z) => z.zoneUuid === zone.zoneUuid),
|
||||
timeStamp: new Date().toISOString(),
|
||||
}));
|
||||
|
||||
push2D({
|
||||
type: "Draw",
|
||||
actions: [
|
||||
{
|
||||
actionType: "Lines-Update",
|
||||
points: updatedPoints,
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
const { isDeletable, handleDrag, handleDragStart, handleDragEnd, handleLineClick, handlePointerOver, handlePointerOut } = useLineEventHandler({ points });
|
||||
|
||||
return (
|
||||
<DragControls axisLock="y" autoTransform={false} onDragStart={() => handleDragStart(points)} onDrag={() => handleDrag(points)} onDragEnd={() => handleDragEnd(points)}>
|
||||
<DragControls axisLock="y" autoTransform={false} onDragStart={handleDragStart} onDrag={handleDrag} onDragEnd={handleDragEnd}>
|
||||
<Tube
|
||||
name={`${points[0].pointType}-Line`}
|
||||
key={`${points[0].pointUuid}-${points[1].pointUuid}`}
|
||||
uuid={`${points[0].pointUuid}-${points[1].pointUuid}`}
|
||||
userData={{ points, path }}
|
||||
args={[path, Constants.lineConfig.tubularSegments, Constants.lineConfig.radius, Constants.lineConfig.radialSegments, false]}
|
||||
onClick={() => {
|
||||
handlePointClick(points);
|
||||
}}
|
||||
onPointerOver={(e) => {
|
||||
if (selectedPoints.length === 0 && e.buttons === 0 && !e.ctrlKey) {
|
||||
setHoveredLine(points);
|
||||
setIsHovered(true);
|
||||
if (toolMode === "move" && !hoveredPoint) {
|
||||
handleCanvasCursors("grab");
|
||||
}
|
||||
}
|
||||
}}
|
||||
onPointerOut={() => {
|
||||
if (hoveredLine && isHovered) {
|
||||
setHoveredLine(null);
|
||||
if (!hoveredPoint && toolMode === "move") {
|
||||
handleCanvasCursors("default");
|
||||
}
|
||||
}
|
||||
setIsHovered(false);
|
||||
}}
|
||||
onClick={handleLineClick}
|
||||
onPointerOver={handlePointerOver}
|
||||
onPointerOut={handlePointerOut}
|
||||
>
|
||||
<meshStandardMaterial color={isDeletable ? colors.defaultDeleteColor : colors.defaultLineColor} />
|
||||
</Tube>
|
||||
|
||||
Reference in New Issue
Block a user