Merge pull request 'dev-contextMenu' (#1) from dev-contextMenu into main-demo

Reviewed-on: http://185.100.212.76:7778/Dwinzo-Beta/Dwinzo_Demo/pulls/1
This commit was merged in pull request #1.
This commit is contained in:
2025-08-14 04:58:00 +00:00
20 changed files with 803 additions and 153 deletions

View File

@@ -0,0 +1,194 @@
import { useEffect, useState } from 'react';
import { useThree } from '@react-three/fiber';
import { CameraControls, Html, ScreenSpace } from '@react-three/drei';
import { useContextActionStore, useRenameModeStore, useSelectedAssets } from '../../../../store/builder/store';
import ContextMenu from '../../../../components/ui/menu/contextMenu';
function ContextControls() {
const { gl, controls } = useThree();
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 [menuPosition, setMenuPosition] = useState({ x: 0, y: 0 });
const { selectedAssets } = useSelectedAssets();
const { setContextAction } = useContextActionStore();
const { setIsRenameMode } = useRenameModeStore();
useEffect(() => {
if (selectedAssets.length === 1) {
setVisibility({
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,
});
} else if (selectedAssets.length > 1) {
setVisibility({
rename: false,
focus: true,
flipX: true,
flipZ: true,
move: true,
rotate: true,
duplicate: true,
copy: true,
paste: true,
modifier: false,
group: true,
array: false,
delete: true,
});
} else {
setVisibility({
rename: false,
focus: false,
flipX: false,
flipZ: false,
move: false,
rotate: false,
duplicate: false,
copy: false,
paste: false,
modifier: false,
group: false,
array: false,
delete: false,
});
}
}, [selectedAssets]);
useEffect(() => {
const canvasElement = gl.domElement;
const handleContextClick = (event: MouseEvent) => {
event.preventDefault();
if (selectedAssets.length > 0) {
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 });
setCanRender(true);
if (controls) {
(controls as CameraControls).enabled = false;
}
} else {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
}
};
if (selectedAssets.length > 0) {
canvasElement.addEventListener('contextmenu', handleContextClick)
} else {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setMenuPosition({ x: 0, y: 0 });
}
return () => {
canvasElement.removeEventListener('contextmenu', handleContextClick);
};
}, [gl, selectedAssets]);
const handleAssetRename = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("renameAsset");
setIsRenameMode(true);
}
const handleAssetFocus = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("focusAsset");
}
const handleAssetMove = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("moveAsset")
}
const handleAssetRotate = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("rotateAsset")
}
const handleAssetCopy = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("copyAsset")
}
const handleAssetPaste = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("pasteAsset")
}
const handleAssetDelete = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("deleteAsset")
}
const handleAssetDuplicate = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("duplicateAsset")
}
return (
<>
{canRender && (
<ScreenSpace depth={1} >
<Html
style={{
position: 'fixed',
top: menuPosition.y,
left: menuPosition.x,
zIndex: 1000
}}
>
<ContextMenu
visibility={visibility}
onRename={() => handleAssetRename()}
onFocus={() => handleAssetFocus()}
onFlipX={() => console.log("Flip to X")}
onFlipZ={() => console.log("Flip to Z")}
onMove={() => handleAssetMove()}
onRotate={() => handleAssetRotate()}
onDuplicate={() => handleAssetDuplicate()}
onCopy={() => handleAssetCopy()}
onPaste={() => handleAssetPaste()}
onGroup={() => console.log("Group")}
onArray={() => console.log("Array")}
onDelete={() => handleAssetDelete()}
/>
</Html>
</ScreenSpace>
)}
</>
);
}
export default ContextControls;

View File

@@ -14,6 +14,7 @@ import TransformControl from "./transformControls/transformControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import ContextControls from "./contextControls/contextControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
@@ -149,6 +150,8 @@ export default function Controls() {
<TransformControl />
<ContextControls />
</>
);
}

View File

@@ -2,7 +2,7 @@ import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
@@ -41,6 +41,7 @@ const CopyPasteControls3D = ({
const [relativePositions, setRelativePositions] = useState<THREE.Vector3[]>([]);
const [centerOffset, setCenterOffset] = useState<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const { contextAction, setContextAction } = useContextActionStore()
const calculateRelativePositions = useCallback((objects: THREE.Object3D[]) => {
if (objects.length === 0) return { center: new THREE.Vector3(), relatives: [] };
@@ -58,6 +59,16 @@ const CopyPasteControls3D = ({
return { center, relatives };
}, []);
useEffect(() => {
if (contextAction === "copyAsset") {
setContextAction(null);
copySelection()
} else if (contextAction === "pasteAsset") {
setContextAction(null);
pasteCopiedObjects()
}
}, [contextAction])
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;

View File

@@ -2,7 +2,7 @@ import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { SkeletonUtils } from "three-stdlib";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { useParams } from "react-router-dom";
@@ -39,12 +39,20 @@ const DuplicationControls3D = ({
const [initialPositions, setInitialPositions] = useState<Record<string, THREE.Vector3>>({});
const [isDuplicating, setIsDuplicating] = useState(false);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const { contextAction, setContextAction } = useContextActionStore()
const calculateDragOffset = useCallback((point: THREE.Object3D, hitPoint: THREE.Vector3) => {
const pointPosition = new THREE.Vector3().copy(point.position);
return new THREE.Vector3().subVectors(pointPosition, hitPoint);
}, []);
useEffect(() => {
if (contextAction === "duplicateAsset") {
setContextAction(null);
duplicateSelection()
}
}, [contextAction])
useEffect(() => {
if (!camera || !scene || toggleView) return;
const canvasElement = gl.domElement;

View File

@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { detectModifierKeys } from "../../../../../utils/shortcutkeys/detectModifierKeys";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
@@ -48,6 +48,7 @@ function MoveControls3D({
const [initialStates, setInitialStates] = useState<Record<string, { position: THREE.Vector3; rotation?: THREE.Euler; }>>({});
const [isMoving, setIsMoving] = useState(false);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const { contextAction, setContextAction } = useContextActionStore()
const updateBackend = (
productName: string,
@@ -64,6 +65,13 @@ function MoveControls3D({
});
};
useEffect(() => {
if (contextAction === "moveAsset") {
setContextAction(null);
moveAssets()
}
}, [contextAction])
useEffect(() => {
if (!camera || !scene || toggleView) return;

View File

@@ -1,7 +1,7 @@
import * as THREE from "three";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useFrame, useThree } from "@react-three/fiber";
import { useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView } from "../../../../../store/builder/store";
import * as Types from "../../../../../types/world/worldTypes";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import { useParams } from "react-router-dom";
@@ -43,10 +43,8 @@ function RotateControls3D({
const [isRotating, setIsRotating] = useState(false);
const prevPointerPosition = useRef<THREE.Vector2 | null>(null);
const rotationCenter = useRef<THREE.Vector3 | null>(null);
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({
left: false,
right: false,
});
const mouseButtonsDown = useRef<{ left: boolean; right: boolean }>({ left: false, right: false, });
const { contextAction, setContextAction } = useContextActionStore()
const updateBackend = useCallback((
productName: string,
@@ -63,6 +61,13 @@ function RotateControls3D({
});
}, [selectedVersion]);
useEffect(() => {
if (contextAction === "rotateAsset") {
setContextAction(null);
rotateAssets()
}
}, [contextAction])
useEffect(() => {
if (!camera || !scene || toggleView) return;

View File

@@ -10,7 +10,7 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext";
import { useProductContext } from "../../../../simulation/products/productContext";
import { useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { useContextActionStore, useSelectedAssets, useSocketStore, useToggleView, useToolMode, } from "../../../../../store/builder/store";
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import DuplicationControls3D from "./duplicationControls3D";
import CopyPasteControls3D from "./copyPasteControls3D";
@@ -31,6 +31,7 @@ const SelectionControls3D: React.FC = () => {
const boundingBoxRef = useRef<THREE.Mesh>();
const { activeModule } = useModuleStore();
const { socket } = useSocketStore();
const { contextAction, setContextAction } = useContextActionStore()
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { removeAsset, getAssetById } = assetStore();
@@ -66,6 +67,13 @@ const SelectionControls3D: React.FC = () => {
});
};
useEffect(() => {
if (contextAction === "deleteAsset") {
setContextAction(null);
deleteSelection()
}
}, [contextAction])
useEffect(() => {
if (!camera || !scene || toggleView) return;
@@ -108,7 +116,7 @@ const SelectionControls3D: React.FC = () => {
if (event.button === 2 && !event.ctrlKey && !event.shiftKey) {
isRightClick.current = false;
if (!rightClickMoved.current) {
clearSelection();
// clearSelection();
}
return;
}
@@ -193,7 +201,7 @@ const SelectionControls3D: React.FC = () => {
const onContextMenu = (event: MouseEvent) => {
event.preventDefault();
if (!rightClickMoved.current) {
clearSelection();
// clearSelection();
}
rightClickMoved.current = false;
};