Add context menu and context controls for asset manipulation
This commit is contained in:
@@ -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 />}
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
207
app/src/components/ui/menu/contextMenu.tsx
Normal file
207
app/src/components/ui/menu/contextMenu.tsx
Normal 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;
|
||||||
@@ -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;
|
||||||
@@ -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 />
|
||||||
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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(() => {
|
||||||
|
|||||||
@@ -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 }),
|
||||||
|
}));
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user