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,
useRenameModeStore,
useSaveVersion,
useSelectedAssets,
useSelectedComment,
useSelectedFloorItem,
useSocketStore,
@@ -58,6 +59,7 @@ function MainScene() {
const { setFloatingWidget } = useFloatingWidget();
const { clearComparisonProduct } = useComparisonProduct();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedAssets } = useSelectedAssets();
const { assetStore, productStore } = useSceneContext();
const { products } = productStore();
const { setName } = assetStore();
@@ -97,18 +99,35 @@ function MainScene() {
const handleObjectRename = async (newName: string) => {
if (!projectId) return
let response = await setAssetsApi({
modelUuid: selectedFloorItem.userData.modelUuid,
modelName: newName,
projectId
});
selectedFloorItem.userData = {
...selectedFloorItem.userData,
modelName: newName
};
setSelectedFloorItem(selectedFloorItem);
setIsRenameMode(false);
setName(selectedFloorItem.userData.modelUuid, response.modelName);
if (selectedFloorItem) {
setAssetsApi({
modelUuid: selectedFloorItem.userData.modelUuid,
modelName: newName,
projectId
}).then(() => {
selectedFloorItem.userData = {
...selectedFloorItem.userData,
modelName: newName
};
setSelectedFloorItem(selectedFloorItem);
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 (
@@ -135,7 +154,7 @@ function MainScene() {
{(isPlaying) &&
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 */}
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
</>
@@ -188,7 +207,7 @@ function MainScene() {
{activeModule !== "market" && !selectedUser && <Footer />}
<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;