Merge branch 'main-demo' into dev-resourceManagement
This commit is contained in:
@@ -55,6 +55,7 @@ interface ShortcutHelperProps {
|
|||||||
const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
|
const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
|
||||||
setShowShortcuts,
|
setShowShortcuts,
|
||||||
}) => {
|
}) => {
|
||||||
|
|
||||||
const shortcuts: ShortcutGroup[] = [
|
const shortcuts: ShortcutGroup[] = [
|
||||||
// Essential
|
// Essential
|
||||||
{
|
{
|
||||||
@@ -310,6 +311,7 @@ const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
|
|||||||
>
|
>
|
||||||
<CloseIcon />
|
<CloseIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="header">
|
<div className="header">
|
||||||
<div className="header-wrapper">
|
<div className="header-wrapper">
|
||||||
{shortcuts.map((group) => (
|
{shortcuts.map((group) => (
|
||||||
@@ -326,8 +328,7 @@ const ShortcutHelper: React.FC<ShortcutHelperProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`shortcut-wrapper ${
|
className={`shortcut-wrapper ${activeShortcuts.length === 1 ? "single-item" : ""
|
||||||
activeShortcuts.length === 1 ? "single-item" : ""
|
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{activeShortcuts.map((item) => (
|
{activeShortcuts.map((item) => (
|
||||||
|
|||||||
@@ -212,7 +212,11 @@ function MainScene() {
|
|||||||
{activeModule !== "market" && !selectedUser && <Footer />}
|
{activeModule !== "market" && !selectedUser && <Footer />}
|
||||||
|
|
||||||
<VersionSaved />
|
<VersionSaved />
|
||||||
{(commentPositionState !== null || selectedComment !== null) && <ThreadChat />}
|
|
||||||
|
{
|
||||||
|
(commentPositionState !== null || selectedComment !== null) &&
|
||||||
|
<ThreadChat />
|
||||||
|
}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -78,8 +78,8 @@ const AssetProperties: React.FC = () => {
|
|||||||
|
|
||||||
<section>
|
<section>
|
||||||
<div className="header">User Data</div>
|
<div className="header">User Data</div>
|
||||||
{userData.map((data) => (
|
{userData.map((data, i) => (
|
||||||
<div className="input-container">
|
<div className="input-container" key={i}>
|
||||||
<InputWithDropDown
|
<InputWithDropDown
|
||||||
key={data.id}
|
key={data.id}
|
||||||
label={data.label}
|
label={data.label}
|
||||||
@@ -103,9 +103,16 @@ const AssetProperties: React.FC = () => {
|
|||||||
</section>
|
</section>
|
||||||
<div className="header">Animations</div>
|
<div className="header">Animations</div>
|
||||||
<section className="animations-lists">
|
<section className="animations-lists">
|
||||||
{assets.map((asset) => {
|
{assets.map((asset, i) => {
|
||||||
if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations)
|
if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations)
|
||||||
return null;
|
return (
|
||||||
|
i === 0 && (
|
||||||
|
<div className="no-animation">
|
||||||
|
Looks like there are no preset animations yet. Stay tuned for
|
||||||
|
future additions!
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
return asset.animations.map((animation, index) => (
|
return asset.animations.map((animation, index) => (
|
||||||
<div key={index} className="animations-list-wrapper">
|
<div key={index} className="animations-list-wrapper">
|
||||||
|
|||||||
@@ -231,7 +231,10 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
|
<div className="time">{isEditableThread ? getRelativeTime(val.createdAt) : val.createdAt}</div>
|
||||||
</div>
|
</div>
|
||||||
{(val as Reply).creatorId === userId && (
|
{(val as Reply).creatorId === userId && (
|
||||||
<div className="more-options">
|
<div
|
||||||
|
className="more-options"
|
||||||
|
onMouseLeave={() => setOpenOptions(false)}
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
className="more-options-button"
|
className="more-options-button"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -240,35 +243,41 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
|
|||||||
>
|
>
|
||||||
<KebabIcon />
|
<KebabIcon />
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
{openOptions && (
|
{openOptions && (
|
||||||
<div className="options-list">
|
<div className="options-list">
|
||||||
<button
|
<button
|
||||||
className="option"
|
className="option"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setMode && setMode("edit")
|
setMode && setMode("edit");
|
||||||
setOpenOptions(false);
|
setOpenOptions(false);
|
||||||
setEditedThread && setEditedThread(true);
|
setEditedThread && setEditedThread(true);
|
||||||
setIsEditComment(true)
|
setIsEditComment(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
{!(isEditableThread) && <button
|
|
||||||
|
{!isEditableThread && (
|
||||||
|
<button
|
||||||
className="option"
|
className="option"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
handleDeleteAction((val as Reply).replyId);
|
handleDeleteAction((val as Reply).replyId);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
</button>}
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="message">
|
<div className="message">
|
||||||
{"comment" in val ? val.comment : val.threadTitle}
|
{"comment" in val ? val.comment : val.threadTitle}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div >
|
</div >
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -131,9 +131,10 @@ const ThreadChat: React.FC = () => {
|
|||||||
if (dragging) updatePosition(e, true);
|
if (dragging) updatePosition(e, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// Commented this useEffect to prevent offset after user saved the comment
|
||||||
updatePosition({ clientX: position.x, clientY: position.y }, true);
|
// useEffect(() => {
|
||||||
}, [selectedComment]);
|
// updatePosition({ clientX: position.x, clientY: position.y }, true);
|
||||||
|
// }, [selectedComment]);
|
||||||
|
|
||||||
|
|
||||||
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
const handlePointerUp = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||||
@@ -144,6 +145,10 @@ const ThreadChat: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateComments = async (e: any) => {
|
const handleCreateComments = async (e: any) => {
|
||||||
|
// Continue send or create message only there is only value avalibale
|
||||||
|
// To prevent empty value
|
||||||
|
|
||||||
|
if (!value) return;
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
// const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/
|
// const createComments = await addCommentsApi(projectId, value, selectedComment?.threadId, selectedVersion?.versionId || "")/
|
||||||
@@ -163,6 +168,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
|
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
if (threadSocket && mode === "create") {
|
if (threadSocket && mode === "create") {
|
||||||
const addComment = {
|
const addComment = {
|
||||||
versionId: selectedVersion?.versionId || "",
|
versionId: selectedVersion?.versionId || "",
|
||||||
@@ -190,7 +196,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
// removeComment(selectedComment?.threadId)
|
// removeComment(selectedComment?.threadId)
|
||||||
// setSelectedComment([])
|
// setSelectedComment([])
|
||||||
// }
|
// }
|
||||||
console.log('threadSocket:threadChat ', threadSocket);
|
|
||||||
if (threadSocket) {
|
if (threadSocket) {
|
||||||
// projectId, userId, organization, threadId
|
// projectId, userId, organization, threadId
|
||||||
const deleteThread = {
|
const deleteThread = {
|
||||||
@@ -258,7 +264,7 @@ const ThreadChat: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (threadSocket) {
|
if (threadSocket) {
|
||||||
console.log('createThread: ', createThread);
|
|
||||||
|
|
||||||
setInputActive(false);
|
setInputActive(false);
|
||||||
threadSocket.emit("v1:thread:create", createThread);
|
threadSocket.emit("v1:thread:create", createThread);
|
||||||
|
|||||||
47
app/src/hooks/useCameraShortcuts.ts
Normal file
47
app/src/hooks/useCameraShortcuts.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import type { CameraControls } from "@react-three/drei";
|
||||||
|
|
||||||
|
export const useCameraShortcuts = (controlsRef: React.RefObject<CameraControls>) => {
|
||||||
|
const { camera } = useThree();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (!controlsRef.current) return;
|
||||||
|
|
||||||
|
// get current distance from camera to target
|
||||||
|
const target = new THREE.Vector3();
|
||||||
|
controlsRef.current.getTarget(target);
|
||||||
|
|
||||||
|
const distance = camera.position.distanceTo(target);
|
||||||
|
let pos: THREE.Vector3 | null = null;
|
||||||
|
|
||||||
|
switch (e.key) {
|
||||||
|
case "1": // Front
|
||||||
|
pos = new THREE.Vector3(0, 0, distance).add(target);
|
||||||
|
break;
|
||||||
|
case "3": // Right
|
||||||
|
pos = new THREE.Vector3(distance, 0, 0).add(target);
|
||||||
|
break;
|
||||||
|
case "7": // Top
|
||||||
|
pos = new THREE.Vector3(0, distance, 0).add(target);
|
||||||
|
break;
|
||||||
|
case "9": // Back
|
||||||
|
pos = new THREE.Vector3(0, 0, -distance).add(target);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pos) {
|
||||||
|
controlsRef.current.setLookAt(
|
||||||
|
pos.x, pos.y, pos.z, // camera position
|
||||||
|
target.x, target.y, target.z, // keep same target
|
||||||
|
true // smooth transition
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, [controlsRef, camera]);
|
||||||
|
};
|
||||||
@@ -57,6 +57,7 @@ export default function Builder() {
|
|||||||
const { setHoveredPoint, setHoveredLine } = useBuilderStore();
|
const { setHoveredPoint, setHoveredLine } = useBuilderStore();
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!toggleView) {
|
if (!toggleView) {
|
||||||
setHoveredLine(null);
|
setHoveredLine(null);
|
||||||
|
|||||||
@@ -1,13 +1,31 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useThree } from "@react-three/fiber";
|
||||||
import { CameraControls, Html, ScreenSpace } from '@react-three/drei';
|
import { CameraControls, Html, ScreenSpace } from "@react-three/drei";
|
||||||
import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store';
|
import {
|
||||||
import ContextMenu from '../../../../components/ui/menu/contextMenu';
|
useContextActionStore,
|
||||||
|
useRenameModeStore,
|
||||||
|
useSelectedAssets,
|
||||||
|
} from "../../../../store/builder/store";
|
||||||
|
import ContextMenu from "../../../../components/ui/menu/contextMenu";
|
||||||
|
|
||||||
function ContextControls() {
|
function ContextControls() {
|
||||||
const { gl, controls } = useThree();
|
const { gl, controls } = useThree();
|
||||||
const [canRender, setCanRender] = useState(false);
|
const [canRender, setCanRender] = useState(false);
|
||||||
const [visibility, setVisibility] = useState({ rename: true, focus: true, flipX: true, flipZ: true, move: true, rotate: true, duplicate: true, copy: true, paste: true, modifier: false, group: false, array: false, delete: true, });
|
const [visibility, setVisibility] = useState({
|
||||||
|
rename: true,
|
||||||
|
focus: true,
|
||||||
|
flipX: true,
|
||||||
|
flipZ: true,
|
||||||
|
move: true,
|
||||||
|
rotate: true,
|
||||||
|
duplicate: true,
|
||||||
|
copy: true,
|
||||||
|
paste: true,
|
||||||
|
modifier: false,
|
||||||
|
group: false,
|
||||||
|
array: false,
|
||||||
|
delete: true,
|
||||||
|
});
|
||||||
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
|
const [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
|
||||||
const { selectedAssets } = useSelectedAssets();
|
const { selectedAssets } = useSelectedAssets();
|
||||||
const { setContextAction } = useContextActionStore();
|
const { setContextAction } = useContextActionStore();
|
||||||
@@ -93,7 +111,10 @@ function ContextControls() {
|
|||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (rightDrag.current) return;
|
if (rightDrag.current) return;
|
||||||
if (selectedAssets.length > 0) {
|
if (selectedAssets.length > 0) {
|
||||||
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 });
|
setMenuPosition({
|
||||||
|
x: event.clientX - gl.domElement.width / 2,
|
||||||
|
y: event.clientY - gl.domElement.height / 2,
|
||||||
|
});
|
||||||
setCanRender(true);
|
setCanRender(true);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = false;
|
(controls as CameraControls).enabled = false;
|
||||||
@@ -107,10 +128,10 @@ function ContextControls() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (selectedAssets.length > 0) {
|
if (selectedAssets.length > 0) {
|
||||||
canvasElement.addEventListener('pointerdown', onPointerDown);
|
canvasElement.addEventListener("pointerdown", onPointerDown);
|
||||||
canvasElement.addEventListener('pointermove', onPointerMove);
|
canvasElement.addEventListener("pointermove", onPointerMove);
|
||||||
canvasElement.addEventListener('pointerup', onPointerUp);
|
canvasElement.addEventListener("pointerup", onPointerUp);
|
||||||
canvasElement.addEventListener('contextmenu', handleContextClick)
|
canvasElement.addEventListener("contextmenu", handleContextClick);
|
||||||
} else {
|
} else {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
@@ -120,12 +141,12 @@ function ContextControls() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
canvasElement.removeEventListener('pointerdown', onPointerDown);
|
canvasElement.removeEventListener("pointerdown", onPointerDown);
|
||||||
canvasElement.removeEventListener('pointermove', onPointerMove);
|
canvasElement.removeEventListener("pointermove", onPointerMove);
|
||||||
canvasElement.removeEventListener('pointerup', onPointerUp);
|
canvasElement.removeEventListener("pointerup", onPointerUp);
|
||||||
canvasElement.removeEventListener('contextmenu', handleContextClick);
|
canvasElement.removeEventListener("contextmenu", handleContextClick);
|
||||||
};
|
};
|
||||||
}, [gl, selectedAssets]);
|
}, [controls, gl, selectedAssets]);
|
||||||
|
|
||||||
const handleAssetRename = () => {
|
const handleAssetRename = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
@@ -134,56 +155,56 @@ function ContextControls() {
|
|||||||
}
|
}
|
||||||
setContextAction("renameAsset");
|
setContextAction("renameAsset");
|
||||||
setIsRenameMode(true);
|
setIsRenameMode(true);
|
||||||
}
|
};
|
||||||
const handleAssetFocus = () => {
|
const handleAssetFocus = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("focusAsset");
|
setContextAction("focusAsset");
|
||||||
}
|
};
|
||||||
const handleAssetMove = () => {
|
const handleAssetMove = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("moveAsset")
|
setContextAction("moveAsset");
|
||||||
}
|
};
|
||||||
const handleAssetRotate = () => {
|
const handleAssetRotate = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("rotateAsset")
|
setContextAction("rotateAsset");
|
||||||
}
|
};
|
||||||
const handleAssetCopy = () => {
|
const handleAssetCopy = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("copyAsset")
|
setContextAction("copyAsset");
|
||||||
}
|
};
|
||||||
const handleAssetPaste = () => {
|
const handleAssetPaste = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("pasteAsset")
|
setContextAction("pasteAsset");
|
||||||
}
|
};
|
||||||
const handleAssetDelete = () => {
|
const handleAssetDelete = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("deleteAsset")
|
setContextAction("deleteAsset");
|
||||||
}
|
};
|
||||||
const handleAssetDuplicate = () => {
|
const handleAssetDuplicate = () => {
|
||||||
setCanRender(false);
|
setCanRender(false);
|
||||||
if (controls) {
|
if (controls) {
|
||||||
(controls as CameraControls).enabled = true;
|
(controls as CameraControls).enabled = true;
|
||||||
}
|
}
|
||||||
setContextAction("duplicateAsset")
|
setContextAction("duplicateAsset");
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -191,10 +212,10 @@ function ContextControls() {
|
|||||||
<ScreenSpace depth={1}>
|
<ScreenSpace depth={1}>
|
||||||
<Html
|
<Html
|
||||||
style={{
|
style={{
|
||||||
position: 'fixed',
|
position: "fixed",
|
||||||
top: menuPosition.y,
|
top: menuPosition.y,
|
||||||
left: menuPosition.x,
|
left: menuPosition.x,
|
||||||
zIndex: 1000
|
zIndex: 1000,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ContextMenu
|
<ContextMenu
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import ContextControls from "./contextControls/contextControls";
|
|||||||
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
||||||
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
||||||
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
|
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
|
||||||
|
import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts";
|
||||||
|
|
||||||
export default function Controls() {
|
export default function Controls() {
|
||||||
const controlsRef = useRef<CameraControls>(null);
|
const controlsRef = useRef<CameraControls>(null);
|
||||||
@@ -116,6 +117,7 @@ export default function Controls() {
|
|||||||
stopInterval();
|
stopInterval();
|
||||||
};
|
};
|
||||||
}, [toggleView, state, socket]);
|
}, [toggleView, state, socket]);
|
||||||
|
useCameraShortcuts(controlsRef);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ function MoveControls3D({ boundingBoxRef }: any) {
|
|||||||
setContextAction(null);
|
setContextAction(null);
|
||||||
moveAssets()
|
moveAssets()
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [contextAction])
|
}, [contextAction])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -176,6 +177,7 @@ function MoveControls3D({ boundingBoxRef }: any) {
|
|||||||
canvasElement.removeEventListener("keydown", onKeyDown);
|
canvasElement.removeEventListener("keydown", onKeyDown);
|
||||||
canvasElement?.removeEventListener("keyup", onKeyUp);
|
canvasElement?.removeEventListener("keyup", onKeyUp);
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]);
|
}, [camera, controls, scene, toggleView, selectedAssets, socket, pastedObjects, duplicatedObjects, movedObjects, rotatedObjects, keyEvent, initialStates]);
|
||||||
|
|
||||||
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
|
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
|
||||||
@@ -223,6 +225,7 @@ function MoveControls3D({ boundingBoxRef }: any) {
|
|||||||
setDragOffset(newOffset);
|
setDragOffset(newOffset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [axisConstraint, camera, movedObjects])
|
}, [axisConstraint, camera, movedObjects])
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
|
|||||||
@@ -67,6 +67,7 @@ const SelectionControls3D: React.FC = () => {
|
|||||||
setContextAction(null);
|
setContextAction(null);
|
||||||
deleteSelection()
|
deleteSelection()
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [contextAction])
|
}, [contextAction])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -222,12 +223,14 @@ const SelectionControls3D: React.FC = () => {
|
|||||||
helper.enabled = false;
|
helper.enabled = false;
|
||||||
helper.dispose();
|
helper.dispose();
|
||||||
};
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, activeModule, toolMode]);
|
}, [camera, controls, scene, toggleView, selectedAssets, copiedObjects, pastedObjects, duplicatedObjects, movedObjects, socket, rotatedObjects, activeModule, toolMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
|
if (activeModule !== "builder" || toolMode !== 'cursor' || toggleView) {
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [activeModule, toolMode, toggleView]);
|
}, [activeModule, toolMode, toggleView]);
|
||||||
|
|
||||||
const selectAssets = useCallback(() => {
|
const selectAssets = useCallback(() => {
|
||||||
@@ -362,7 +365,7 @@ const SelectionControls3D: React.FC = () => {
|
|||||||
removeAsset(uuid);
|
removeAsset(uuid);
|
||||||
});
|
});
|
||||||
|
|
||||||
echo.success("Selected models removed!");
|
echo.warn("Selected models removed!");
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useCallback, useEffect, useRef, useState } from "react";
|
||||||
import { useThree, useFrame } from "@react-three/fiber";
|
import { useThree, useFrame } from "@react-three/fiber";
|
||||||
import { useToolMode } from "../../../store/builder/store";
|
import { useToolMode } from "../../../store/builder/store";
|
||||||
import { Html, Line } from "@react-three/drei";
|
import { Html, Line } from "@react-three/drei";
|
||||||
@@ -10,7 +10,81 @@ const MeasurementTool = () => {
|
|||||||
|
|
||||||
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
const [points, setPoints] = useState<THREE.Vector3[]>([]);
|
||||||
const [linePoints, setLinePoints] = useState<THREE.Vector3[] | null>(null);
|
const [linePoints, setLinePoints] = useState<THREE.Vector3[] | null>(null);
|
||||||
|
const [axisLock, setAxisLock] = useState<"x" | "y" | "z" | null>(null);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
const keysPressed = useRef<Set<string>>(new Set());
|
||||||
|
|
||||||
|
// Axis lock key handling
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.altKey) {
|
||||||
|
if (e.key.toLowerCase() === "x") keysPressed.current.add("x");
|
||||||
|
else if (e.key.toLowerCase() === "y") keysPressed.current.add("y");
|
||||||
|
else if (e.key.toLowerCase() === "z") keysPressed.current.add("z");
|
||||||
|
|
||||||
|
if (keysPressed.current.has("x")) setAxisLock("x");
|
||||||
|
else if (keysPressed.current.has("y")) setAxisLock("y");
|
||||||
|
else if (keysPressed.current.has("z")) setAxisLock("z");
|
||||||
|
} else if (e.key === "Escape") {
|
||||||
|
setPoints([]);
|
||||||
|
setLinePoints(null);
|
||||||
|
setAxisLock(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleKeyUp = (e: KeyboardEvent) => {
|
||||||
|
keysPressed.current.delete(e.key.toLowerCase());
|
||||||
|
if (keysPressed.current.has("x")) setAxisLock("x");
|
||||||
|
else if (keysPressed.current.has("y")) setAxisLock("y");
|
||||||
|
else if (keysPressed.current.has("z")) setAxisLock("z");
|
||||||
|
else setAxisLock(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
window.addEventListener("keyup", handleKeyUp);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
window.removeEventListener("keyup", handleKeyUp);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getLineColor = useCallback(() => {
|
||||||
|
switch (axisLock) {
|
||||||
|
case "x":
|
||||||
|
return "#d94522"; // Red for X axis
|
||||||
|
case "y":
|
||||||
|
return "#22ab2e"; // Green for Y axis
|
||||||
|
case "z":
|
||||||
|
return "#227bd9"; // Blue for Z axis
|
||||||
|
default:
|
||||||
|
return "#b18ef1"; // Default purple
|
||||||
|
}
|
||||||
|
}, [axisLock]);
|
||||||
|
|
||||||
|
// Apply axis lock to a point
|
||||||
|
const applyAxisLock = useCallback(
|
||||||
|
(point: THREE.Vector3, referencePoint: THREE.Vector3) => {
|
||||||
|
const lockedPoint = point.clone();
|
||||||
|
|
||||||
|
switch (axisLock) {
|
||||||
|
case "x":
|
||||||
|
lockedPoint.y = referencePoint.y;
|
||||||
|
lockedPoint.z = referencePoint.z;
|
||||||
|
break;
|
||||||
|
case "y":
|
||||||
|
lockedPoint.x = referencePoint.x;
|
||||||
|
lockedPoint.z = referencePoint.z;
|
||||||
|
break;
|
||||||
|
case "z":
|
||||||
|
lockedPoint.x = referencePoint.x;
|
||||||
|
lockedPoint.y = referencePoint.y;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lockedPoint;
|
||||||
|
},
|
||||||
|
[axisLock]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const canvasElement = gl.domElement;
|
const canvasElement = gl.domElement;
|
||||||
@@ -45,7 +119,13 @@ const MeasurementTool = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const intersectionPoint = intersects[0].point.clone();
|
let intersectionPoint = intersects[0].point.clone();
|
||||||
|
if (axisLock && points.length > 0) {
|
||||||
|
intersectionPoint = applyAxisLock(
|
||||||
|
intersectionPoint,
|
||||||
|
points[points.length - 1]
|
||||||
|
);
|
||||||
|
}
|
||||||
if (points.length < 2) {
|
if (points.length < 2) {
|
||||||
setPoints([...points, intersectionPoint]);
|
setPoints([...points, intersectionPoint]);
|
||||||
} else {
|
} else {
|
||||||
@@ -84,7 +164,7 @@ const MeasurementTool = () => {
|
|||||||
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
canvasElement.removeEventListener("contextmenu", onContextMenu);
|
||||||
};
|
};
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [toolMode, camera, raycaster, pointer, scene, points]);
|
}, [toolMode, camera, raycaster, pointer, scene, points, axisLock]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (points.length === 1) {
|
if (points.length === 1) {
|
||||||
@@ -107,7 +187,10 @@ const MeasurementTool = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (intersects.length > 0) {
|
if (intersects.length > 0) {
|
||||||
const tempEnd = intersects[0].point.clone();
|
let tempEnd = intersects[0].point.clone();
|
||||||
|
if (axisLock) {
|
||||||
|
tempEnd = applyAxisLock(tempEnd, points[0]);
|
||||||
|
}
|
||||||
updateMeasurement(points[0], tempEnd);
|
updateMeasurement(points[0], tempEnd);
|
||||||
}
|
}
|
||||||
} else if (points.length === 2) {
|
} else if (points.length === 2) {
|
||||||
@@ -139,7 +222,7 @@ const MeasurementTool = () => {
|
|||||||
{/* Main line */}
|
{/* Main line */}
|
||||||
<Line
|
<Line
|
||||||
points={linePoints}
|
points={linePoints}
|
||||||
color="#b18ef1"
|
color={getLineColor()}
|
||||||
lineWidth={2} // actual line width
|
lineWidth={2} // actual line width
|
||||||
depthTest={false}
|
depthTest={false}
|
||||||
depthWrite={false}
|
depthWrite={false}
|
||||||
|
|||||||
@@ -241,10 +241,8 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
|||||||
{Object.keys(zonesData).length !== 0 ? (
|
{Object.keys(zonesData).length !== 0 ? (
|
||||||
<>
|
<>
|
||||||
{Object.values(zonesData).map((zone, index) => (
|
{Object.values(zonesData).map((zone, index) => (
|
||||||
<>
|
|
||||||
{ }
|
|
||||||
<div
|
<div
|
||||||
key={index}
|
key={`${index}_${zone.zoneName}`}
|
||||||
className={`zone ${selectedZone.zoneUuid === zone.zoneUuid ? "active" : ""
|
className={`zone ${selectedZone.zoneUuid === zone.zoneUuid ? "active" : ""
|
||||||
}`}
|
}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -255,7 +253,6 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
|||||||
>
|
>
|
||||||
{zone.zoneName}
|
{zone.zoneName}
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
))}
|
))}
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
border-radius: #{$border-radius-large};
|
border-radius: #{$border-radius-large};
|
||||||
outline: 1px solid var(--border-color);
|
outline: 1px solid var(--border-color);
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
.header {
|
.header {
|
||||||
@include flex-center;
|
@include flex-center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
header {
|
header {
|
||||||
position: relative;
|
position: relative;
|
||||||
@include flex-space-between;
|
@include flex-space-between;
|
||||||
padding: 3px 0;
|
padding-bottom: 6px;
|
||||||
|
|
||||||
.user-details {
|
.user-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -142,6 +142,9 @@
|
|||||||
|
|
||||||
.details {
|
.details {
|
||||||
max-width: 144px;
|
max-width: 144px;
|
||||||
|
.input-value{
|
||||||
|
max-width: 120px;
|
||||||
|
}
|
||||||
.employee-id {
|
.employee-id {
|
||||||
color: #b7b7c6;
|
color: #b7b7c6;
|
||||||
font-size: $tiny;
|
font-size: $tiny;
|
||||||
|
|||||||
@@ -516,6 +516,7 @@
|
|||||||
width: 50px;
|
width: 50px;
|
||||||
border-radius: 100px;
|
border-radius: 100px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
outline: 1px solid var(--accent-color);
|
outline: 1px solid var(--accent-color);
|
||||||
}
|
}
|
||||||
@@ -586,6 +587,7 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
outline: 1px solid #aaaaaa29;
|
outline: 1px solid #aaaaaa29;
|
||||||
@@ -600,6 +602,7 @@
|
|||||||
|
|
||||||
.kebab-icon {
|
.kebab-icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
transform: rotate(90deg) scale(0.8);
|
transform: rotate(90deg) scale(0.8);
|
||||||
}
|
}
|
||||||
@@ -1434,6 +1437,11 @@
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
border-radius: #{$border-radius-large};
|
border-radius: #{$border-radius-large};
|
||||||
|
|
||||||
|
outline: 1px solid var(--border-color);
|
||||||
|
outline-offset: -1px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: var(--background-color);
|
||||||
|
|
||||||
.compare-simulations-header {
|
.compare-simulations-header {
|
||||||
font-weight: var(--font-weight-medium);
|
font-weight: var(--font-weight-medium);
|
||||||
}
|
}
|
||||||
@@ -1598,8 +1606,13 @@
|
|||||||
.animations-lists {
|
.animations-lists {
|
||||||
max-height: 210px;
|
max-height: 210px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
|
.no-animation {
|
||||||
|
padding: 6px 8px;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
.animations-list-wrapper {
|
.animations-list-wrapper {
|
||||||
padding: 0 4px;
|
padding: 0 4px;
|
||||||
|
|
||||||
.animations-list {
|
.animations-list {
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
@@ -1789,6 +1802,7 @@
|
|||||||
padding: 14px;
|
padding: 14px;
|
||||||
padding-bottom: 0;
|
padding-bottom: 0;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|
||||||
.input-toggle-container {
|
.input-toggle-container {
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
|||||||
@@ -171,8 +171,8 @@
|
|||||||
.messages-wrapper {
|
.messages-wrapper {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
padding-top: 0;
|
padding-top: 0;
|
||||||
max-height: 50vh;
|
max-height: 36vh;
|
||||||
overflow-y: auto;
|
overflow: auto;
|
||||||
.edit-container {
|
.edit-container {
|
||||||
.input-container {
|
.input-container {
|
||||||
textarea {
|
textarea {
|
||||||
|
|||||||
@@ -221,8 +221,12 @@ const KeyPressListener: React.FC = () => {
|
|||||||
|
|
||||||
// Shortcuts specific for sidebar visibility toggle and others specific to sidebar if added
|
// Shortcuts specific for sidebar visibility toggle and others specific to sidebar if added
|
||||||
handleSidebarShortcuts(keyCombination);
|
handleSidebarShortcuts(keyCombination);
|
||||||
|
|
||||||
|
|
||||||
// Active module selection (builder, simulation, etc.)
|
// Active module selection (builder, simulation, etc.)
|
||||||
|
if (event.location === 0) { // Location 0 = standard keyboard (not numpad)
|
||||||
handleModuleSwitch(keyCombination);
|
handleModuleSwitch(keyCombination);
|
||||||
|
}
|
||||||
// Common editing tools: cursor | delete | free-hand
|
// Common editing tools: cursor | delete | free-hand
|
||||||
handlePrimaryTools(keyCombination);
|
handlePrimaryTools(keyCombination);
|
||||||
// Shortcuts specific to the builder module (e.g., drawing and measurement tools)
|
// Shortcuts specific to the builder module (e.g., drawing and measurement tools)
|
||||||
|
|||||||
Reference in New Issue
Block a user