Add context menu and context controls for asset manipulation

This commit is contained in:
2025-08-12 09:54:42 +05:30
parent a08cec33ab
commit c71b25c407
12 changed files with 505 additions and 29 deletions

View File

@@ -3,6 +3,7 @@ import {
useLoadingProgress, useLoadingProgress,
useRenameModeStore, useRenameModeStore,
useSaveVersion, useSaveVersion,
useSelectedAssets,
useSelectedComment, useSelectedComment,
useSelectedFloorItem, useSelectedFloorItem,
useSocketStore, useSocketStore,
@@ -58,6 +59,7 @@ function MainScene() {
const { setFloatingWidget } = useFloatingWidget(); const { setFloatingWidget } = useFloatingWidget();
const { clearComparisonProduct } = useComparisonProduct(); const { clearComparisonProduct } = useComparisonProduct();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem(); const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedAssets } = useSelectedAssets();
const { assetStore, productStore } = useSceneContext(); const { assetStore, productStore } = useSceneContext();
const { products } = productStore(); const { products } = productStore();
const { setName } = assetStore(); const { setName } = assetStore();
@@ -97,18 +99,35 @@ function MainScene() {
const handleObjectRename = async (newName: string) => { const handleObjectRename = async (newName: string) => {
if (!projectId) return if (!projectId) return
let response = await setAssetsApi({ if (selectedFloorItem) {
modelUuid: selectedFloorItem.userData.modelUuid, setAssetsApi({
modelName: newName, modelUuid: selectedFloorItem.userData.modelUuid,
projectId modelName: newName,
}); projectId
selectedFloorItem.userData = { }).then(() => {
...selectedFloorItem.userData, selectedFloorItem.userData = {
modelName: newName ...selectedFloorItem.userData,
}; modelName: newName
setSelectedFloorItem(selectedFloorItem); };
setIsRenameMode(false); setSelectedFloorItem(selectedFloorItem);
setName(selectedFloorItem.userData.modelUuid, response.modelName); setIsRenameMode(false);
setName(selectedFloorItem.userData.modelUuid, newName);
})
} else if (selectedAssets.length === 1) {
setAssetsApi({
modelUuid: selectedAssets[0].userData.modelUuid,
modelName: newName,
projectId
}).then(() => {
selectedAssets[0].userData = {
...selectedAssets[0].userData,
modelName: newName
};
setAssetsApi(selectedAssets);
setIsRenameMode(false);
setName(selectedAssets[0].userData.modelUuid, newName);
})
}
} }
return ( return (
@@ -135,7 +154,7 @@ function MainScene() {
{(isPlaying) && {(isPlaying) &&
activeModule !== "simulation" && <ControlsPlayer />} activeModule !== "simulation" && <ControlsPlayer />}
{isRenameMode && selectedFloorItem?.userData.modelName && <RenameTooltip name={selectedFloorItem?.userData.modelName} onSubmit={handleObjectRename} />} {isRenameMode && (selectedFloorItem?.userData.modelName || selectedAssets.length === 1) && <RenameTooltip name={selectedFloorItem?.userData.modelName || selectedAssets[0].userData.modelName} onSubmit={handleObjectRename} />}
{/* remove this later */} {/* remove this later */}
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />} {activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
</> </>
@@ -188,7 +207,7 @@ function MainScene() {
{activeModule !== "market" && !selectedUser && <Footer />} {activeModule !== "market" && !selectedUser && <Footer />}
<VersionSaved /> <VersionSaved />
{(commentPositionState !== null || selectedComment !== null) && <ThreadChat/>} {(commentPositionState !== null || selectedComment !== null) && <ThreadChat />}
</> </>
); );

View File

@@ -0,0 +1,207 @@
import React from "react";
type ContextMenuProps = {
visibility: {
rename: boolean;
focus: boolean;
flipX: boolean;
flipZ: boolean;
move: boolean;
rotate: boolean;
duplicate: boolean;
copy: boolean;
paste: boolean;
modifier: boolean;
group: boolean;
array: boolean;
delete: boolean;
};
onRename: () => void;
onFocus: () => void;
onFlipX: () => void;
onFlipZ: () => void;
onMove: () => void;
onRotate: () => void;
onDuplicate: () => void;
onCopy: () => void;
onPaste: () => void;
onGroup: () => void;
onArray: () => void;
onDelete: () => void;
};
const ContextMenu: React.FC<ContextMenuProps> = ({
visibility,
onRename,
onFocus,
onFlipX,
onFlipZ,
onMove,
onRotate,
onDuplicate,
onCopy,
onPaste,
onGroup,
onArray,
onDelete,
}) => {
const styles: { [key: string]: React.CSSProperties } = {
contextMenu: {
position: "absolute",
top: 0,
left: 0,
backgroundColor: "#2c2c2c",
borderRadius: "6px",
padding: "8px",
color: "white",
fontFamily: "sans-serif",
fontSize: "14px",
boxShadow: "0 0 8px rgba(0,0,0,0.4)",
zIndex: 1000,
},
menuItem: {
margin: "4px 0",
},
button: {
background: "none",
border: "none",
color: "inherit",
display: "flex",
justifyContent: "space-between",
width: "180px",
padding: "4px 8px",
cursor: "pointer",
},
submenu: {
marginLeft: "10px",
marginTop: "4px",
backgroundColor: "#3a3a3a",
borderRadius: "4px",
padding: "4px",
},
shortcut: {
opacity: 0.6,
},
};
return (
<div style={styles.contextMenu}>
<div>
{visibility.rename && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onRename}>
<span>Rename</span>
<span style={styles.shortcut}>F2</span>
</button>
</div>
)}
{visibility.focus && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onFocus}>
<span>Focus</span>
<span style={styles.shortcut}>F</span>
</button>
</div>
)}
{visibility.flipX && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onFlipX}>
Flip to X axis
</button>
</div>
)}
{visibility.flipZ && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onFlipZ}>
Flip to Z axis
</button>
</div>
)}
{(visibility.move || visibility.rotate) && (
<div style={styles.menuItem}>
<button style={styles.button}>Transform</button>
<div style={styles.submenu}>
{visibility.move && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onMove}>
<span>Move</span>
<span style={styles.shortcut}>G</span>
</button>
</div>
)}
{visibility.rotate && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onRotate}>
<span>Rotate</span>
<span style={styles.shortcut}>R</span>
</button>
</div>
)}
</div>
</div>
)}
{visibility.duplicate && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onDuplicate}>
<span>Duplicate</span>
<span style={styles.shortcut}>Ctrl + D</span>
</button>
</div>
)}
{visibility.copy && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onCopy}>
<span>Copy Objects</span>
<span style={styles.shortcut}>Ctrl + C</span>
</button>
</div>
)}
{visibility.paste && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onPaste}>
<span>Paste Objects</span>
<span style={styles.shortcut}>Ctrl + V</span>
</button>
</div>
)}
{visibility.modifier && (
<div style={styles.menuItem}>
<button style={styles.button}>Modifiers</button>
</div>
)}
{(visibility.group || visibility.array) && (
<div style={styles.menuItem}>
<button style={styles.button}>Group / Array</button>
<div style={styles.submenu}>
{visibility.group && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onGroup}>
<span>Group</span>
<span style={styles.shortcut}>Ctrl + G</span>
</button>
</div>
)}
{visibility.array && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onArray}>
Array
</button>
</div>
)}
</div>
</div>
)}
{visibility.delete && (
<div style={styles.menuItem}>
<button style={styles.button} onClick={onDelete}>
<span>Delete</span>
<span style={styles.shortcut}>X</span>
</button>
</div>
)}
</div>
</div>
);
};
export default ContextMenu;

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 { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData"; import { getUserData } from "../../../functions/getUserData";
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";
@@ -146,6 +147,8 @@ export default function Controls() {
<TransformControl /> <TransformControl />
<ContextControls />
</> </>
); );
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import { getUserData } from "../../../../../functions/getUserData";
import { useSceneContext } from "../../../sceneContext"; import { useSceneContext } from "../../../sceneContext";
import { useVersionContext } from "../../../../builder/version/versionContext"; import { useVersionContext } from "../../../../builder/version/versionContext";
import { useProductContext } from "../../../../simulation/products/productContext"; 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 { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
import DuplicationControls3D from "./duplicationControls3D"; import DuplicationControls3D from "./duplicationControls3D";
import CopyPasteControls3D from "./copyPasteControls3D"; import CopyPasteControls3D from "./copyPasteControls3D";
@@ -33,6 +33,7 @@ const SelectionControls3D: React.FC = () => {
const { socket } = useSocketStore(); const { socket } = useSocketStore();
const { assetStore, eventStore, productStore } = useSceneContext(); const { assetStore, eventStore, productStore } = useSceneContext();
const { removeAsset } = assetStore(); const { removeAsset } = assetStore();
const { contextAction, setContextAction } = useContextActionStore()
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]); const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext(); const { selectedVersionStore } = useVersionContext();
@@ -65,6 +66,13 @@ const SelectionControls3D: React.FC = () => {
}); });
}; };
useEffect(() => {
if (contextAction === "deleteAsset") {
setContextAction(null);
deleteSelection()
}
}, [contextAction])
useEffect(() => { useEffect(() => {
if (!camera || !scene || toggleView) return; if (!camera || !scene || toggleView) return;
@@ -107,7 +115,7 @@ const SelectionControls3D: React.FC = () => {
if (event.button === 2 && !event.ctrlKey && !event.shiftKey) { if (event.button === 2 && !event.ctrlKey && !event.shiftKey) {
isRightClick.current = false; isRightClick.current = false;
if (!rightClickMoved.current) { if (!rightClickMoved.current) {
clearSelection(); // clearSelection();
} }
return; return;
} }
@@ -192,7 +200,7 @@ const SelectionControls3D: React.FC = () => {
const onContextMenu = (event: MouseEvent) => { const onContextMenu = (event: MouseEvent) => {
event.preventDefault(); event.preventDefault();
if (!rightClickMoved.current) { if (!rightClickMoved.current) {
clearSelection(); // clearSelection();
} }
rightClickMoved.current = false; rightClickMoved.current = false;
}; };

View File

@@ -18,7 +18,7 @@ function UndoRedo2DControls() {
const { selectedVersion } = selectedVersionStore(); const { selectedVersion } = selectedVersionStore();
useEffect(() => { useEffect(() => {
console.log(undoStack, redoStack); // console.log(undoStack, redoStack);
}, [undoStack, redoStack]); }, [undoStack, redoStack]);
useEffect(() => { useEffect(() => {

View File

@@ -1,3 +1,4 @@
import { Object3D } from "three";
import { create } from "zustand"; import { create } from "zustand";
import { io } from "socket.io-client"; import { io } from "socket.io-client";
import * as CONSTANTS from "../../types/world/worldConstants"; import * as CONSTANTS from "../../types/world/worldConstants";
@@ -166,9 +167,14 @@ export const useNavMesh = create<any>((set: any) => ({
setNavMesh: (x: any) => set({ navMesh: x }), setNavMesh: (x: any) => set({ navMesh: x }),
})); }));
export const useSelectedAssets = create<any>((set: any) => ({ type SelectedAssetsState = {
selectedAssets: Object3D[];
setSelectedAssets: (assets: Object3D[]) => void;
};
export const useSelectedAssets = create<SelectedAssetsState>((set) => ({
selectedAssets: [], selectedAssets: [],
setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })), setSelectedAssets: (assets) => set({ selectedAssets: assets }),
})); }));
export const useLayers = create<any>((set: any) => ({ export const useLayers = create<any>((set: any) => ({
@@ -632,3 +638,7 @@ export const useSelectedPath = create<any>((set: any) => ({
selectedPath: "auto", selectedPath: "auto",
setSelectedPath: (x: any) => set({ selectedPath: x }), setSelectedPath: (x: any) => set({ selectedPath: x }),
})); }));
export const useContextActionStore = create<any>((set: any) => ({
contextAction: null,
setContextAction: (x: any) => set({ contextAction: x }),
}));

View File

@@ -11,6 +11,7 @@ import useVersionHistoryVisibleStore, {
useDfxUpload, useDfxUpload,
useRenameModeStore, useRenameModeStore,
useSaveVersion, useSaveVersion,
useSelectedAssets,
useSelectedComment, useSelectedComment,
useSelectedFloorItem, useSelectedFloorItem,
useSelectedWallItem, useSelectedWallItem,
@@ -50,6 +51,7 @@ const KeyPressListener: React.FC = () => {
const { setViewSceneLabels } = useViewSceneStore(); const { setViewSceneLabels } = useViewSceneStore();
const { isRenameMode, setIsRenameMode } = useRenameModeStore(); const { isRenameMode, setIsRenameMode } = useRenameModeStore();
const { selectedFloorItem } = useSelectedFloorItem(); const { selectedFloorItem } = useSelectedFloorItem();
const { selectedAssets } = useSelectedAssets();
const { setCreateNewVersion } = useVersionHistoryStore(); const { setCreateNewVersion } = useVersionHistoryStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore(); const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { setSelectedComment } = useSelectedComment(); const { setSelectedComment } = useSelectedComment();
@@ -254,7 +256,7 @@ const KeyPressListener: React.FC = () => {
setViewSceneLabels((prev) => !prev); setViewSceneLabels((prev) => !prev);
} }
if (selectedFloorItem && keyCombination === "F2") { if ((selectedFloorItem || selectedAssets.length === 1) && keyCombination === "F2") {
setIsRenameMode(true); setIsRenameMode(true);
} }
@@ -281,6 +283,7 @@ const KeyPressListener: React.FC = () => {
hidePlayer, hidePlayer,
selectedFloorItem, selectedFloorItem,
isRenameMode, isRenameMode,
selectedAssets
]); ]);
return null; return null;