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:
@@ -160,3 +160,153 @@ export function RenameIcon() {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function FocusIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.31999 1.56H9.89999C10.0325 1.56 10.14 1.66745 10.14 1.8V4.14M10.14 7.5V9.9C10.14 10.0325 10.0325 10.14 9.89999 10.14H7.31999M4.55999 10.14H1.91999C1.78744 10.14 1.67999 10.0325 1.67999 9.9V7.5M1.67999 4.14V1.8C1.67999 1.66745 1.78744 1.56 1.91999 1.56H4.55999" stroke="white" stroke-linecap="round" />
|
||||
<circle cx="6.00005" cy="5.87999" r="1.7" stroke="white" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function TransformIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_55_63)">
|
||||
<path d="M3 0.75C2.40326 0.75 1.83097 0.987053 1.40901 1.40901C0.987053 1.83097 0.75 2.40326 0.75 3C0.75 3.59674 0.987053 4.16903 1.40901 4.59099C1.83097 5.01295 2.40326 5.25 3 5.25C3.24134 5.24937 3.481 5.20991 3.7098 5.13314L3.28805 4.71141L4.79632 3.20316L4.94545 3.05402L5.22342 3.33199C5.24047 3.22214 5.24935 3.11117 5.25 3C5.25 2.40326 5.01295 1.83097 4.59099 1.40901C4.16903 0.987053 3.59674 0.75 3 0.75ZM4.94545 3.65062L3.88467 4.71141L5.92336 6.75L5.37333 7.30001L8.07427 7.84017L7.53403 5.13923L6.98405 5.68922L4.94545 3.65062ZM8.28647 6.75L8.61202 8.37797L6.75 8.00555V11.25H11.25V6.75H8.28645H8.28647Z" fill="#FCFDFD" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_55_63">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export function DublicateIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_190)">
|
||||
<path d="M9 1.5H2C1.72386 1.5 1.5 1.72386 1.5 2V9C1.5 9.27615 1.27614 9.5 1 9.5C0.72386 9.5 0.5 9.27615 0.5 9V2C0.5 1.17158 1.17158 0.5 2 0.5H9C9.27615 0.5 9.5 0.72386 9.5 1C9.5 1.27614 9.27615 1.5 9 1.5Z" fill="white" />
|
||||
<path d="M6.5 5.5C6.5 5.22385 6.72385 5 7 5C7.27615 5 7.5 5.22385 7.5 5.5V6.5H8.5C8.77615 6.5 9 6.72385 9 7C9 7.27615 8.77615 7.5 8.5 7.5H7.5V8.5C7.5 8.77615 7.27615 9 7 9C6.72385 9 6.5 8.77615 6.5 8.5V7.5H5.5C5.22385 7.5 5 7.27615 5 7C5 6.72385 5.22385 6.5 5.5 6.5H6.5V5.5Z" fill="white" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 2.5C10.8285 2.5 11.5 3.17158 11.5 4V10C11.5 10.8285 10.8285 11.5 10 11.5H4C3.17158 11.5 2.5 10.8285 2.5 10V4C2.5 3.17158 3.17158 2.5 4 2.5H10ZM10 3.5C10.2761 3.5 10.5 3.72386 10.5 4V10C10.5 10.2761 10.2761 10.5 10 10.5H4C3.72386 10.5 3.5 10.2761 3.5 10V4C3.5 3.72386 3.72386 3.5 4 3.5H10Z" fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_190">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
export function CopyIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_197)">
|
||||
<path d="M4.375 1.5H8.875C9.22018 1.5 9.5 1.77982 9.5 2.125V5.07422C9.25749 5.02497 9.00651 5 8.75 5C6.67893 5 5 6.67893 5 8.75C5 8.91951 5.01426 9.08616 5.03613 9.25H4.375C4.02982 9.25 3.75 8.97018 3.75 8.625V2.125C3.75 1.77982 4.02982 1.5 4.375 1.5Z" stroke="white" />
|
||||
<path d="M7.02181 10.8891C5.8404 9.93469 5.6564 8.20324 6.61085 7.02182C7.56529 5.84041 9.29675 5.65641 10.4782 6.61086C11.6596 7.5653 11.8436 9.29676 10.8891 10.4782C9.93468 11.6596 8.20322 11.8436 7.02181 10.8891ZM7.53035 9.63652C7.55951 9.73588 7.64818 9.80729 7.7514 9.81511L7.79716 9.81441L7.84067 9.80562C7.94019 9.77642 8.01223 9.68724 8.01987 9.58381L8.01932 9.53942L7.89568 8.38246L9.76272 9.88956L9.80012 9.91475C9.89114 9.96447 10.0045 9.95243 10.083 9.88469L10.1143 9.8522L10.1395 9.8148C10.1892 9.72378 10.1772 9.61043 10.1094 9.53189L10.0769 9.50062L8.20913 7.99291L9.36823 7.86974L9.41174 7.86095C9.52542 7.82759 9.60327 7.71674 9.59039 7.59475C9.57742 7.47272 9.47859 7.38002 9.36039 7.37128L9.3154 7.37259L7.5357 7.5631L7.50592 7.57044L7.46405 7.58808L7.42779 7.61277L7.40435 7.63401L7.37612 7.66895L7.36028 7.69633L7.35134 7.71672L7.33894 7.75692L7.33456 7.781L7.33441 7.81227L7.52217 9.59225L7.53035 9.63652Z" fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_197">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PasteIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_207)">
|
||||
<path d="M4.375 1.5H8.875C9.22018 1.5 9.5 1.77982 9.5 2.125V5.07422C9.25749 5.02497 9.00651 5 8.75 5C6.67893 5 5 6.67893 5 8.75C5 8.91951 5.01426 9.08616 5.03613 9.25H4.375C4.02982 9.25 3.75 8.97018 3.75 8.625V2.125C3.75 1.77982 4.02982 1.5 4.375 1.5Z" stroke="white" />
|
||||
<path d="M10.4408 6.58164C11.6383 7.51587 11.8516 9.24395 10.9174 10.4414C9.9832 11.6389 8.25512 11.8523 7.05765 10.918C5.86019 9.98382 5.64679 8.25574 6.58102 7.05828C7.51524 5.86081 9.24332 5.64742 10.4408 6.58164ZM9.95361 7.84272C9.92276 7.74387 9.83289 7.67398 9.72956 7.66791L9.68382 7.66939L9.64046 7.67892C9.54146 7.7098 9.47094 7.8002 9.46506 7.90374L9.46636 7.94811L9.60965 9.10281L7.71726 7.62766L7.67944 7.60311C7.58759 7.55494 7.47446 7.56891 7.39709 7.63798L7.36637 7.67099L7.34182 7.70881C7.29366 7.80066 7.30763 7.91379 7.37669 7.99117L7.4097 8.02188L9.30286 9.49763L8.14603 9.64048L8.10268 9.65001C7.98957 9.6853 7.91362 9.79746 7.92858 9.91921C7.94362 10.041 8.044 10.132 8.16233 10.1387L8.2073 10.1367L9.9835 9.91593L10.0131 9.90809L10.0547 9.88974L10.0906 9.86444L10.1136 9.8428L10.1413 9.80739L10.1566 9.77974L10.1652 9.7592L10.1769 9.71879L10.1809 9.69464L10.1805 9.66338L9.96254 7.88684L9.95361 7.84272Z" fill="white" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_207">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ModifiersIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2 4.5V3.23607C2 3.08082 2.03615 2.92771 2.10558 2.78886L2.5 2H5L5.5 3H10.5C10.7761 3 11 3.22386 11 3.5V4.5V9C11 9.5523 10.5523 10 10 10H9" stroke="white" stroke-linecap="round" stroke-linejoin="round" />
|
||||
<path d="M8.61805 4.5H1.15458C0.824894 4.5 0.585449 4.81349 0.672199 5.13155L1.79898 9.2631C1.91764 9.6982 2.3128 10 2.76375 10H9.84535C10.175 10 10.4145 9.6865 10.3277 9.36845L9.10045 4.86844C9.0411 4.65091 8.84355 4.5 8.61805 4.5Z" stroke="white" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function DeleteIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_241)">
|
||||
<path d="M4.70588 5.32353V9.02941M7.17647 5.32353V9.02941M9.64706 2.85294V10.2647C9.64706 10.947 9.09402 11.5 8.41177 11.5H3.47059C2.78835 11.5 2.23529 10.947 2.23529 10.2647V2.85294M1 2.85294H10.8824M7.79412 2.85294V2.23529C7.79412 1.55306 7.24108 1 6.55882 1H5.32353C4.6413 1 4.08824 1.55306 4.08824 2.23529V2.85294" stroke="white" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_241">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function MoveIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1_241)">
|
||||
<path d="M4.70588 5.32353V9.02941M7.17647 5.32353V9.02941M9.64706 2.85294V10.2647C9.64706 10.947 9.09402 11.5 8.41177 11.5H3.47059C2.78835 11.5 2.23529 10.947 2.23529 10.2647V2.85294M1 2.85294H10.8824M7.79412 2.85294V2.23529C7.79412 1.55306 7.24108 1 6.55882 1H5.32353C4.6413 1 4.08824 1.55306 4.08824 2.23529V2.85294" stroke="white" stroke-linecap="round" stroke-linejoin="round" />
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1_241">
|
||||
<rect width="12" height="12" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function RotateIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.99998 1.31111H4.7251L3.88539 0.471406L4.3568 0L6.00124 1.64444L4.37502 3.27067L3.90361 2.79927L4.72511 1.97777H3.99998C2.89541 1.97777 1.99998 2.87321 1.99998 3.97777H1.33331C1.33331 2.50501 2.52722 1.31111 3.99998 1.31111ZM3.99998 5.33333C3.99998 4.59696 4.59693 4 5.33331 4H10.6667C11.4031 4 12 4.59696 12 5.33333V10.6667C12 11.4031 11.4031 12 10.6667 12H5.33331C4.59693 12 3.99998 11.4031 3.99998 10.6667V5.33333ZM5.33331 4.66667H10.6667C11.0349 4.66667 11.3333 4.96514 11.3333 5.33333V10.6667C11.3333 11.0349 11.0349 11.3333 10.6667 11.3333H5.33331C4.96513 11.3333 4.66664 11.0349 4.66664 10.6667V5.33333C4.66664 4.96514 4.96513 4.66667 5.33331 4.66667Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function GroupIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.99998 1.31111H4.7251L3.88539 0.471406L4.3568 0L6.00124 1.64444L4.37502 3.27067L3.90361 2.79927L4.72511 1.97777H3.99998C2.89541 1.97777 1.99998 2.87321 1.99998 3.97777H1.33331C1.33331 2.50501 2.52722 1.31111 3.99998 1.31111ZM3.99998 5.33333C3.99998 4.59696 4.59693 4 5.33331 4H10.6667C11.4031 4 12 4.59696 12 5.33333V10.6667C12 11.4031 11.4031 12 10.6667 12H5.33331C4.59693 12 3.99998 11.4031 3.99998 10.6667V5.33333ZM5.33331 4.66667H10.6667C11.0349 4.66667 11.3333 4.96514 11.3333 5.33333V10.6667C11.3333 11.0349 11.0349 11.3333 10.6667 11.3333H5.33331C4.96513 11.3333 4.66664 11.0349 4.66664 10.6667V5.33333C4.66664 4.96514 4.96513 4.66667 5.33331 4.66667Z" fill="#FCFDFD" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function ArrayIcon() {
|
||||
return (
|
||||
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="1.09998" y="0.5" width="1.66667" height="5.66667" rx="0.5" stroke="white" />
|
||||
<rect x="5.09998" y="3.16797" width="1.66667" height="5.66667" rx="0.5" stroke="white" />
|
||||
<rect x="9.09998" y="5.83203" width="1.66667" height="5.66667" rx="0.5" stroke="white" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function SubMenuIcon() {
|
||||
return (
|
||||
<svg width="4" height="6" viewBox="0 0 4 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0.5 6V0L4 3L0.5 6Z" fill="white" />
|
||||
</svg>
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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,setSelectedAssets } = useSelectedAssets();
|
||||
const { assetStore, productStore } = useSceneContext();
|
||||
const { products } = productStore();
|
||||
const { setName } = assetStore();
|
||||
@@ -97,18 +99,40 @@ function MainScene() {
|
||||
|
||||
const handleObjectRename = async (newName: string) => {
|
||||
if (!projectId) return
|
||||
let response = await setAssetsApi({
|
||||
if (selectedFloorItem) {
|
||||
console.log('selectedFloorItem.userData.modelUuid: ', selectedFloorItem.userData.modelUuid);
|
||||
console.log(' newName: ', newName);
|
||||
console.log('projectId: ', projectId);
|
||||
setAssetsApi({
|
||||
modelUuid: selectedFloorItem.userData.modelUuid,
|
||||
modelName: newName,
|
||||
projectId
|
||||
});
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || ''
|
||||
}).then(() => {
|
||||
selectedFloorItem.userData = {
|
||||
...selectedFloorItem.userData,
|
||||
modelName: newName
|
||||
};
|
||||
setSelectedFloorItem(selectedFloorItem);
|
||||
setIsRenameMode(false);
|
||||
setName(selectedFloorItem.userData.modelUuid, response.modelName);
|
||||
setName(selectedFloorItem.userData.modelUuid, newName);
|
||||
})
|
||||
} else if (selectedAssets.length === 1) {
|
||||
setAssetsApi({
|
||||
modelUuid: selectedAssets[0].userData.modelUuid,
|
||||
modelName: newName,
|
||||
projectId,
|
||||
versionId: selectedVersion?.versionId || ''
|
||||
}).then(() => {
|
||||
selectedAssets[0].userData = {
|
||||
...selectedAssets[0].userData,
|
||||
modelName: newName
|
||||
};
|
||||
setSelectedAssets(selectedAssets);
|
||||
setIsRenameMode(false);
|
||||
setName(selectedAssets[0].userData.modelUuid, newName);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -135,7 +159,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 />}
|
||||
</>
|
||||
|
||||
@@ -1,23 +1,62 @@
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Search from "../../ui/inputs/Search";
|
||||
import DropDownList from "../../ui/list/DropDownList";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { isPointInsidePolygon } from "../../../functions/isPointInsidePolygon";
|
||||
|
||||
interface ZoneData {
|
||||
id: string;
|
||||
name: string;
|
||||
assets: { id: string; name: string; position?: []; rotation?: {} }[];
|
||||
}
|
||||
|
||||
const Outline: React.FC = () => {
|
||||
const [searchValue, setSearchValue] = useState<string>("");
|
||||
const [zoneDataList, setZoneDataList] = useState<ZoneData[]>([]);
|
||||
const [buildingsList, setBuildingsList] = useState<{ id: string; name: string }[]>([]);
|
||||
const [isLayersOpen, setIsLayersOpen] = useState(true);
|
||||
const [isBuildingsOpen, setIsBuildingsOpen] = useState(false);
|
||||
const [isZonesOpen, setIsZonesOpen] = useState(false);
|
||||
const { assetStore, zoneStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { zones } = zoneStore();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const updatedZoneList: ZoneData[] = zones?.map((zone: any) => {
|
||||
const polygon2D = zone.points.map((p: any) => [p.position[0], p.position[2]]);
|
||||
const assetsInZone = assets.filter((item: any) => {
|
||||
const [x, , z] = item.position;
|
||||
return isPointInsidePolygon([x, z], polygon2D as [number, number][]);
|
||||
})
|
||||
.map((item: any) => ({
|
||||
id: item.modelUuid,
|
||||
name: item.modelName,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
}));
|
||||
|
||||
|
||||
return {
|
||||
id: zone.zoneUuid,
|
||||
name: zone.zoneName,
|
||||
assets: assetsInZone,
|
||||
};
|
||||
});
|
||||
|
||||
setZoneDataList(updatedZoneList);
|
||||
}, [zones, assets]);
|
||||
|
||||
const handleSearchChange = (value: string) => {
|
||||
setSearchValue(value);
|
||||
// console.log(value); // Log the search value if needed
|
||||
};
|
||||
|
||||
const dropdownItems = [
|
||||
{ id: "1", name: "Ground Floor", active: true },
|
||||
// { id: "2", name: "Floor 1" },
|
||||
]; // Example dropdown items
|
||||
const dropdownItems = [{ id: "1", name: "Ground Floor" }];
|
||||
|
||||
return (
|
||||
<div className="outline-container">
|
||||
<Search onChange={handleSearchChange} />
|
||||
|
||||
{searchValue ? (
|
||||
<div className="searched-content">
|
||||
<p>Results for "{searchValue}"</p>
|
||||
@@ -28,7 +67,8 @@ const Outline: React.FC = () => {
|
||||
<DropDownList
|
||||
value="Layers"
|
||||
items={dropdownItems}
|
||||
defaultOpen={true}
|
||||
isOpen={isLayersOpen}
|
||||
onToggle={() => setIsLayersOpen((prev) => !prev)}
|
||||
showKebabMenu={false}
|
||||
showFocusIcon={true}
|
||||
remove
|
||||
@@ -36,10 +76,18 @@ const Outline: React.FC = () => {
|
||||
</section>
|
||||
<section className="outline-section overflow">
|
||||
<DropDownList
|
||||
value="Scene"
|
||||
items={dropdownItems}
|
||||
defaultOpen={true}
|
||||
listType="outline"
|
||||
value="Buildings"
|
||||
items={buildingsList}
|
||||
isOpen={isBuildingsOpen}
|
||||
onToggle={() => setIsBuildingsOpen((prev) => !prev)}
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
/>
|
||||
<DropDownList
|
||||
value="Zones"
|
||||
items={zoneDataList}
|
||||
isOpen={isZonesOpen}
|
||||
onToggle={() => setIsZonesOpen((prev) => !prev)}
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
/>
|
||||
|
||||
@@ -42,11 +42,11 @@ const ZoneProperties: React.FC = () => {
|
||||
|
||||
|
||||
let response = await zoneCameraUpdate(zonesdata, organization, projectId, selectedVersion?.versionId || "");
|
||||
// console.log('response: ', response);
|
||||
//
|
||||
if (response.message === "zone updated") {
|
||||
setEdit(false);
|
||||
} else {
|
||||
// console.log(response);
|
||||
//
|
||||
}
|
||||
} catch (error) {
|
||||
echo.error("Failed to set zone view");
|
||||
@@ -75,7 +75,7 @@ const ZoneProperties: React.FC = () => {
|
||||
// )
|
||||
// );
|
||||
} else {
|
||||
// console.log(response?.message);
|
||||
//
|
||||
}
|
||||
}
|
||||
function handleVectorChange(
|
||||
@@ -85,7 +85,7 @@ const ZoneProperties: React.FC = () => {
|
||||
setSelectedZone((prev) => ({ ...prev, [key]: newValue }));
|
||||
}
|
||||
const checkZoneNameDuplicate = (name: string) => {
|
||||
console.log('zones: ', zones);
|
||||
|
||||
return zones.some(
|
||||
(zone: any) =>
|
||||
zone.zoneName?.trim().toLowerCase() === name?.trim().toLowerCase() &&
|
||||
|
||||
@@ -1,30 +1,18 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React from "react";
|
||||
import List from "./List";
|
||||
import { AddIcon, ArrowIcon, FocusIcon } from "../../icons/ExportCommonIcons";
|
||||
import KebabMenuListMultiSelect from "./KebebMenuListMultiSelect";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
|
||||
interface DropDownListProps {
|
||||
value?: string; // Value to display in the DropDownList
|
||||
items?: { id: string; name: string }[]; // Items to display in the dropdown list
|
||||
showFocusIcon?: boolean; // Determines if the FocusIcon should be displayed
|
||||
showAddIcon?: boolean; // Determines if the AddIcon should be displayed
|
||||
showKebabMenu?: boolean; // Determines if the KebabMenuList should be displayed
|
||||
kebabMenuItems?: { id: string; name: string }[]; // Items for the KebabMenuList
|
||||
defaultOpen?: boolean; // Determines if the dropdown list should be open by default
|
||||
listType?: string; // Type of list to display
|
||||
value?: string;
|
||||
items?: { id: string; name: string }[];
|
||||
showFocusIcon?: boolean;
|
||||
showAddIcon?: boolean;
|
||||
showKebabMenu?: boolean;
|
||||
kebabMenuItems?: { id: string; name: string }[];
|
||||
remove?: boolean;
|
||||
}
|
||||
|
||||
interface Zone {
|
||||
zoneUuid: string;
|
||||
zoneName: string;
|
||||
points: [number, number, number][]; // polygon vertices
|
||||
}
|
||||
interface ZoneData {
|
||||
id: string;
|
||||
name: string;
|
||||
assets: { id: string; name: string; position?: []; rotation?: {} }[];
|
||||
isOpen: boolean;
|
||||
onToggle: () => void;
|
||||
}
|
||||
|
||||
const DropDownList: React.FC<DropDownListProps> = ({
|
||||
@@ -38,76 +26,13 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
{ id: "Paths", name: "Paths" },
|
||||
{ id: "Zones", name: "Zones" },
|
||||
],
|
||||
defaultOpen = false,
|
||||
listType = "default",
|
||||
remove,
|
||||
isOpen,
|
||||
onToggle,
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState<boolean>(defaultOpen);
|
||||
|
||||
const handleToggle = () => {
|
||||
setIsOpen((prev) => !prev); // Toggle the state
|
||||
};
|
||||
|
||||
const [zoneDataList, setZoneDataList] = useState<ZoneData[]>([]);
|
||||
const { assetStore, zoneStore } = useSceneContext();
|
||||
const { assets } = assetStore();
|
||||
const { zones } = zoneStore()
|
||||
|
||||
|
||||
const isPointInsidePolygon = (
|
||||
point: [number, number],
|
||||
polygon: [number, number][]
|
||||
) => {
|
||||
let inside = false;
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const xi = polygon[i][0],
|
||||
zi = polygon[i][1];
|
||||
const xj = polygon[j][0],
|
||||
zj = polygon[j][1];
|
||||
|
||||
const intersect =
|
||||
// eslint-disable-next-line no-mixed-operators
|
||||
zi > point[1] !== zj > point[1] &&
|
||||
point[0] < ((xj - xi) * (point[1] - zi)) / (zj - zi + 0.000001) + xi;
|
||||
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const updatedZoneList: ZoneData[] = zones?.map((zone: any) => {
|
||||
const polygon2D = zone.points.map((p: [number, number, number]) => [
|
||||
p[0],
|
||||
p[2],
|
||||
]);
|
||||
|
||||
const assetsInZone = assets
|
||||
.filter((item: any) => {
|
||||
const [x, , z] = item.position;
|
||||
return isPointInsidePolygon([x, z], polygon2D as [number, number][]);
|
||||
})
|
||||
.map((item: any) => ({
|
||||
id: item.modelUuid,
|
||||
name: item.modelName,
|
||||
position: item.position,
|
||||
rotation: item.rotation,
|
||||
}));
|
||||
|
||||
return {
|
||||
id: zone.zoneUuid,
|
||||
name: zone.zoneName,
|
||||
assets: assetsInZone,
|
||||
};
|
||||
});
|
||||
|
||||
setZoneDataList(updatedZoneList);
|
||||
}, [zones, assets]);
|
||||
|
||||
return (
|
||||
<div className="dropdown-list-container">
|
||||
{/* eslint-disable-next-line */}
|
||||
<div className="head" onClick={handleToggle}>
|
||||
<div className="head" onClick={onToggle}>
|
||||
<div className="value">{value}</div>
|
||||
<div className="options">
|
||||
{showFocusIcon && (
|
||||
@@ -130,31 +55,15 @@ const DropDownList: React.FC<DropDownListProps> = ({
|
||||
title="collapse-btn"
|
||||
className="collapse-icon option"
|
||||
style={{ transform: isOpen ? "rotate(0deg)" : "rotate(-90deg)" }}
|
||||
// onClick={handleToggle}
|
||||
>
|
||||
<ArrowIcon />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isOpen && (
|
||||
<div className="lists-container">
|
||||
{listType === "default" && <List items={items} remove={remove} />}
|
||||
{listType === "outline" && (
|
||||
<>
|
||||
<DropDownList
|
||||
value="Buildings"
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
// items={zoneDataList}
|
||||
/>
|
||||
<DropDownList
|
||||
value="Zones"
|
||||
showKebabMenu={false}
|
||||
showAddIcon={false}
|
||||
items={zoneDataList}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<List items={items} remove={remove} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -136,7 +136,6 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
|
||||
}
|
||||
|
||||
async function handleZoneAssetName(newName: string) {
|
||||
|
||||
if (zoneAssetId?.id) {
|
||||
let response = await setAssetsApi({
|
||||
modelUuid: zoneAssetId.id,
|
||||
|
||||
187
app/src/components/ui/menu/contextMenu.tsx
Normal file
187
app/src/components/ui/menu/contextMenu.tsx
Normal file
@@ -0,0 +1,187 @@
|
||||
import React from "react";
|
||||
import { ArrayIcon, CopyIcon, DeleteIcon, DublicateIcon, FlipXAxisIcon, FlipZAxisIcon, FocusIcon, GroupIcon, ModifiersIcon, MoveIcon, PasteIcon, RenameIcon, RotateIcon, SubMenuIcon, TransformIcon } from "../../icons/ContextMenuIcons";
|
||||
|
||||
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,
|
||||
}) => {
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className="context-menu">
|
||||
{visibility.rename && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onRename}>
|
||||
<div className="icon"><RenameIcon /></div>
|
||||
<span>Rename</span>
|
||||
</button>
|
||||
<span className="shortcut">F2</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.focus && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onFocus}>
|
||||
<div className="icon"><FocusIcon /></div>
|
||||
<span>Focus</span>
|
||||
</button>
|
||||
<span className="shortcut">F</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.flipX && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onFlipX}>
|
||||
<div className="icon"><FlipXAxisIcon /></div>
|
||||
<span>Flip to X axis</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{visibility.flipZ && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onFlipZ}>
|
||||
<div className="icon"><FlipZAxisIcon /></div>
|
||||
<span>Flip to Z axis</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
{(visibility.move || visibility.rotate) && (
|
||||
<div className="menuItem">
|
||||
<button className="button">
|
||||
<div className="icon"><TransformIcon /></div>
|
||||
<span>Transform</span>
|
||||
</button>
|
||||
<div className="more"><SubMenuIcon /></div>
|
||||
<div className="submenu">
|
||||
{visibility.move && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onMove}>
|
||||
<div className="icon"><MoveIcon /></div>
|
||||
<span>Move</span>
|
||||
</button>
|
||||
<span className="shortcut">G</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.rotate && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onRotate}>
|
||||
<div className="icon"><RotateIcon /></div>
|
||||
<span>Rotate</span>
|
||||
</button>
|
||||
<span className="shortcut">R</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{visibility.duplicate && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onDuplicate}>
|
||||
<div className="icon"><DublicateIcon /></div>
|
||||
<span>Duplicate</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + D</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.copy && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onCopy}>
|
||||
<div className="icon"><CopyIcon /></div>
|
||||
<span>Copy Objects</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + C</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.paste && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onPaste}>
|
||||
<div className="icon"><PasteIcon /></div>
|
||||
<span>Paste Objects</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + V</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.modifier && (
|
||||
<div className="menuItem">
|
||||
<div className="icon"><ModifiersIcon /></div>
|
||||
<button className="button">Modifiers</button>
|
||||
</div>
|
||||
)}
|
||||
{(visibility.group || visibility.array) && (
|
||||
<div className="menuItem">
|
||||
<button className="button">Group / Array</button>
|
||||
<div className="submenu">
|
||||
{visibility.group && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onGroup}>
|
||||
<GroupIcon />
|
||||
<span>Group</span>
|
||||
</button>
|
||||
<span className="shortcut">Ctrl + G</span>
|
||||
</div>
|
||||
)}
|
||||
{visibility.array && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onArray}>
|
||||
<div className="icon"><ArrayIcon /></div>
|
||||
<span>Array</span>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{visibility.delete && (
|
||||
<div className="menuItem">
|
||||
<button className="button" onClick={onDelete}>
|
||||
<div className="icon"><DeleteIcon /></div>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
<span className="shortcut">X</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContextMenu;
|
||||
20
app/src/functions/isPointInsidePolygon.ts
Normal file
20
app/src/functions/isPointInsidePolygon.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
export const isPointInsidePolygon = (
|
||||
point: [number, number],
|
||||
polygon: [number, number][]
|
||||
) => {
|
||||
let inside = false;
|
||||
for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
|
||||
const xi = polygon[i][0],
|
||||
zi = polygon[i][1];
|
||||
const xj = polygon[j][0],
|
||||
zj = polygon[j][1];
|
||||
|
||||
const intersect =
|
||||
// eslint-disable-next-line no-mixed-operators
|
||||
zi > point[1] !== zj > point[1] &&
|
||||
point[0] < ((xj - xi) * (point[1] - zi)) / (zj - zi + 0.000001) + xi;
|
||||
|
||||
if (intersect) inside = !inside;
|
||||
}
|
||||
return inside;
|
||||
};
|
||||
@@ -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 { 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 />
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -202,10 +202,10 @@ const UserAuth: React.FC = () => {
|
||||
</div>
|
||||
{!isSignIn && (
|
||||
<div className="policy-checkbox">
|
||||
<input type="checkbox" name="" id="" required />
|
||||
<div className="label">
|
||||
<input type="checkbox" id="tos" required />
|
||||
<label htmlFor="tos" className="label">
|
||||
I have read and agree to the terms of service
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
)}
|
||||
<button id="form-submit" type="submit" className="continue-button">
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Object3D } from "three";
|
||||
import { create } from "zustand";
|
||||
import { io } from "socket.io-client";
|
||||
import * as CONSTANTS from "../../types/world/worldConstants";
|
||||
@@ -166,9 +167,14 @@ export const useNavMesh = create<any>((set: any) => ({
|
||||
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: [],
|
||||
setSelectedAssets: (x: any) => set(() => ({ selectedAssets: x })),
|
||||
setSelectedAssets: (assets) => set({ selectedAssets: assets }),
|
||||
}));
|
||||
|
||||
export const useLayers = create<any>((set: any) => ({
|
||||
@@ -632,3 +638,7 @@ export const useSelectedPath = create<any>((set: any) => ({
|
||||
selectedPath: "auto",
|
||||
setSelectedPath: (x: any) => set({ selectedPath: x }),
|
||||
}));
|
||||
export const useContextActionStore = create<any>((set: any) => ({
|
||||
contextAction: null,
|
||||
setContextAction: (x: any) => set({ contextAction: x }),
|
||||
}));
|
||||
|
||||
62
app/src/styles/components/contextMenu/_contextMenu.scss
Normal file
62
app/src/styles/components/contextMenu/_contextMenu.scss
Normal file
@@ -0,0 +1,62 @@
|
||||
@use "../../abstracts/variables" as *;
|
||||
@use "../../abstracts/mixins" as *;
|
||||
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(50px);
|
||||
color: var(--text-button-color);
|
||||
box-shadow: var(--box-shadow-light);
|
||||
border-radius: 6px;
|
||||
z-index: 1000;
|
||||
min-width: 200px;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
|
||||
.menuItem {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 6px 8px;
|
||||
border-radius: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
.submenu {
|
||||
display: none;
|
||||
min-width: 178px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 100%; // place directly beside
|
||||
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(50px);
|
||||
color: var(--text-button-color);
|
||||
box-shadow: var(--box-shadow-light);
|
||||
|
||||
padding: 4px;
|
||||
border-radius: 6px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.button {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
// Keep submenu open while hovering parent OR submenu
|
||||
&:hover .submenu,
|
||||
.submenu:hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--background-color-button);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
@use "components/simulation/analysis";
|
||||
@use "components/logs/logs";
|
||||
@use "components/footer/footer.scss";
|
||||
@use "components/contextMenu/contextMenu";
|
||||
|
||||
// layout
|
||||
@use "layout/loading";
|
||||
|
||||
@@ -11,6 +11,7 @@ import useVersionHistoryVisibleStore, {
|
||||
useDfxUpload,
|
||||
useRenameModeStore,
|
||||
useSaveVersion,
|
||||
useSelectedAssets,
|
||||
useSelectedComment,
|
||||
useSelectedFloorItem,
|
||||
useSelectedWallItem,
|
||||
@@ -50,6 +51,7 @@ const KeyPressListener: React.FC = () => {
|
||||
const { setViewSceneLabels } = useViewSceneStore();
|
||||
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
||||
const { selectedFloorItem } = useSelectedFloorItem();
|
||||
const { selectedAssets } = useSelectedAssets();
|
||||
const { setCreateNewVersion } = useVersionHistoryStore();
|
||||
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
||||
const { setSelectedComment } = useSelectedComment();
|
||||
@@ -251,7 +253,7 @@ const KeyPressListener: React.FC = () => {
|
||||
setViewSceneLabels((prev) => !prev);
|
||||
}
|
||||
|
||||
if (selectedFloorItem && keyCombination === "F2") {
|
||||
if ((selectedFloorItem || selectedAssets.length === 1) && keyCombination === "F2") {
|
||||
setIsRenameMode(true);
|
||||
}
|
||||
|
||||
@@ -278,6 +280,7 @@ const KeyPressListener: React.FC = () => {
|
||||
hidePlayer,
|
||||
selectedFloorItem,
|
||||
isRenameMode,
|
||||
selectedAssets
|
||||
]);
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user