Compare commits

...

63 Commits

Author SHA1 Message Date
a3b2d258cf socket and api integerated successfully 2025-09-02 10:48:58 +05:30
4bfd0cf937 Merge branch 'main-dev' into dev-api-socket-coordination 2025-09-02 09:50:37 +05:30
ae4fd2ff01 store optimization 2025-09-02 09:46:27 +05:30
d5d064b875 Merge remote-tracking branch 'origin/main-demo' into main-dev 2025-09-02 09:17:08 +05:30
cfc1f2dee8 Merge branch 'main-dev' of http://185.100.212.76:7778/Dwinzo-Beta/Dwinzo_Demo into main-dev 2025-09-02 09:16:45 +05:30
0ac2bde6d3 code optimization 2025-09-02 09:16:41 +05:30
1cef2987a6 logout theme fix 2025-09-01 18:30:03 +05:30
0ab0510daf refactor: remove unused handleDecalUp function and clean up event handlers 2025-09-01 18:22:27 +05:30
6f9da9e9c0 bug fix 2025-09-01 18:04:59 +05:30
8e7c5a1aa0 added api fallback for sockets 2025-09-01 17:36:40 +05:30
765f4acb57 store optimization 2025-09-01 15:29:06 +05:30
ef98b3c1a3 optimized old zustand store and post processing outlines 2025-09-01 15:09:04 +05:30
09c909c377 decal drop bug fix 2025-09-01 12:37:09 +05:30
7d7100893c simulation and decal bug fix 2025-09-01 11:42:24 +05:30
9825c3ef12 worker img update 2025-08-29 17:58:37 +05:30
3bc0e28267 - online off line status updated
- echo added to rename
2025-08-29 17:47:29 +05:30
f9d314b69f decal default scale scale update 2025-08-29 17:16:50 +05:30
b956ed57e8 human resource management profile ui fix 2025-08-29 17:07:00 +05:30
df36ee0366 Merge remote-tracking branch 'origin/dev-resourceManagement' into main-demo 2025-08-29 16:58:27 +05:30
ef9c3a9c63 dashboard sidebar updated 2025-08-29 16:39:14 +05:30
62ddc1c25f styles file name updated 2025-08-29 16:26:49 +05:30
1b161b2176 - 404-page added and fallback for project not found
- page nav handling for project-not-found updated
2025-08-29 16:24:03 +05:30
a14f7fcf6a dashboard kebab bug fix 2025-08-29 15:14:24 +05:30
b6783f99d3 refactor: reorganize asset management components and enhance search functionality 2025-08-29 13:47:07 +05:30
b2311ab186 Merge remote-tracking branch 'origin/decal-list' into main-demo 2025-08-29 12:52:50 +05:30
d915c643d1 Merge remote-tracking branch 'origin/main-demo' into decal-list 2025-08-29 12:46:48 +05:30
363906aa12 added search functionality for decals 2025-08-29 12:46:34 +05:30
e23e339ed3 decal scale fix 2025-08-29 10:10:54 +05:30
2ba418ab6c dashboard style fix 2025-08-28 18:15:10 +05:30
982a8ef4aa aisle props update 2025-08-28 18:11:54 +05:30
e304614df5 Merge remote-tracking branch 'origin/main-dev' into main-demo 2025-08-28 18:06:42 +05:30
7bbb221c66 - asset icons updated
- aisle props changes
- aisle style updated
2025-08-28 18:06:34 +05:30
94bec4f2f0 added selectedAisleProperties and its backend updation 2025-08-28 18:00:00 +05:30
cb87cd067b added decal fallback 2025-08-28 16:32:45 +05:30
92b1ba6197 decal bug fix 2025-08-28 16:01:16 +05:30
5ef383095a Merge remote-tracking branch 'origin/main-dev' into main-demo 2025-08-28 15:54:24 +05:30
c4d1f90ee7 refactor: enhance asset search UI and improve styling for headers and buttons 2025-08-28 15:51:48 +05:30
8ff609b85c added decal dropping to scene, decal movement from one wall to another wall, one floor to another floor, one wall to floor and one floor to wall 2025-08-28 15:50:45 +05:30
d129a86885 added cache and indexdb loading for decal 2025-08-26 17:39:45 +05:30
d4f12d230f added IMage store in idex db 2025-08-26 17:25:04 +05:30
441f1efb23 side bar left bug fix 2025-08-26 16:49:16 +05:30
243fd6452c Merge remote-tracking branch 'origin/decal-list' into main-dev 2025-08-26 16:39:18 +05:30
c0e040fb3a added decal movement across the wall 2025-08-26 16:38:29 +05:30
165325468a added ui fro and iintegerated ui for decal modification 2025-08-26 15:50:02 +05:30
98be35bf5f included decal-list 2025-08-26 15:36:07 +05:30
547fd1af12 bug fix in wall, wall Asset, floor , and decal selection and unselection, added decal deletion 2025-08-26 14:43:38 +05:30
0387d7a932 file and folder structure changed 2025-08-26 13:15:53 +05:30
bb2a27e2f9 added individual rotation to rotation controls 2025-08-26 12:33:31 +05:30
06b6b3d0ce new TransformControls3D bug fix 2025-08-26 12:31:27 +05:30
b4b412ce14 added move and rotate tool using tranformControls 2025-08-26 12:02:46 +05:30
1d2a42b7bd Merge branch 'main-demo' into dev-resourceManagement 2025-08-26 09:36:42 +05:30
6fa4d5323d feat: add asset thumbnail fetching and update worker data display in Hrm component 2025-08-26 09:36:09 +05:30
ed099c7f75 Merge remote-tracking branch 'origin/main-demo' into main-dev 2025-08-26 09:24:05 +05:30
e813f194c7 resource manager style update 2025-08-25 18:21:28 +05:30
2125c3dc73 Merge remote-tracking branch 'origin/dev-resourceManagement' into main-demo 2025-08-25 18:17:40 +05:30
cd86846a37 Merge remote-tracking branch 'origin/ui' into main-demo 2025-08-25 18:16:51 +05:30
6026c3b82b refactor: remove unnecessary console logs in asset management and thumbnail fetching 2025-08-25 18:16:02 +05:30
358ce22767 feat: implement asset thumbnail fetching and update Hrm and AssetManagement components to use asset data 2025-08-25 18:13:57 +05:30
ff22152d8c Merge branch 'main-demo' into dev-resourceManagement 2025-08-25 17:05:53 +05:30
6aeef163d2 feat: implement resource management ID handling in Hrm and AssetManagement components 2025-08-25 17:05:24 +05:30
246236c15f fix:
- react-hooks/exhaustive-deps added
- style update
- key fix
2025-08-25 16:22:41 +05:30
affffe09c8 feat: measurement tool axis lock added, style update, no animation ui update 2025-08-25 14:08:25 +05:30
67b942ac16 bug fix wallAsset 2025-08-23 17:52:15 +05:30
182 changed files with 7856 additions and 5105 deletions

View File

@@ -7,6 +7,7 @@ import UserAuth from "./pages/UserAuth";
import "./styles/main.scss";
import { LoggerProvider } from "./components/ui/log/LoggerContext";
import ForgotPassword from "./pages/ForgotPassword";
import PageNotFound from "./pages/PageNotFound";
const App: React.FC = () => {
@@ -23,6 +24,7 @@ const App: React.FC = () => {
<Route path="/forgot" element={<ForgotPassword />} />
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/projects/:projectId" element={<Project />} />
<Route path="*" element={<PageNotFound />} />
</Routes>
</Router>
</LoggerProvider>

View File

@@ -0,0 +1,9 @@
<svg width="435" height="192" viewBox="0 0 435 192" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M434.294 35.8327L419.597 87.2712C406.577 88.0129 399.354 88.9272 386.529 90.946L393.877 59.3474L332.885 112.991L377.711 126.218L379.915 115.93C393.611 111.893 401.218 109.947 414.453 107.847L407.84 134.301L427.681 140.18L419.597 167.37L401.226 162.226L392.407 191.62L362.279 183.537L369.627 151.938L297.612 132.097L304.961 104.173L398.286 24.8093L434.294 35.8327Z" fill="#0F0F0F"/>
<path d="M107.287 110.787L127.128 106.378L132.271 133.567L113.9 137.976L119.779 168.84L86.7109 176.923L80.833 144.59L63.9316 148.999C72.6686 121.327 81.38 109.098 102.144 91.6804L107.287 110.787Z" fill="#0F0F0F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M204.287 0.55928C212.617 -0.162914 217.275 -0.209574 225.598 0.55928C254.396 3.22002 271.652 31.4235 274.832 50.529C276.587 61.0745 277.015 67.0381 277.037 77.7185C265.905 97.6708 263.444 104.779 252.787 126.218C243.954 141.438 233.146 152.429 218.249 162.226C186.397 160.022 167.896 140.12 157.991 109.317C154.89 92.6487 155.445 73.5003 156.521 59.3474C158.678 31.0003 174.892 6.89136 204.287 0.55928ZM217.147 23.9997C197.265 23.9997 181.147 48.624 181.147 78.9997C181.147 109.375 197.265 134 217.147 134C237.029 133.999 253.147 109.375 253.147 78.9997C253.147 48.6243 237.029 24.0001 217.147 23.9997Z" fill="#0F0F0F"/>
<path d="M99.2041 71.1052L66.1357 81.3923L60.2568 50.529L29.3936 126.953L66.1357 120.34C58.6892 132.967 56.9985 139.297 54.3779 150.468L7.34766 160.757L0 131.362L49.2344 17.4606L85.2422 10.113L99.2041 71.1052Z" fill="#0F0F0F"/>
<path d="M277.037 96.8249C275.203 113.08 274.273 122.314 268.953 134.301C257.417 152.202 250.176 154.67 237.354 160.021C254.909 142.239 264.111 127.901 277.037 96.8249Z" fill="#0F0F0F"/>
<path d="M99.2041 81.3923C89.3926 88.8475 83.8084 94.1174 73.4844 109.317L68.3398 91.6804L99.2041 81.3923Z" fill="#0F0F0F"/>
<path d="M383.59 100.499C397.029 98.1517 404.407 97.8166 417.393 98.2946C404.464 99.701 397.094 100.893 383.59 104.173V100.499Z" fill="#0F0F0F"/>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

View File

@@ -1,4 +1,5 @@
import React, { useState, useRef, useEffect, act } from "react";
import React, { useState, useRef } from "react";
import { createPortal } from "react-dom";
import img from "../../assets/image/image.png";
import { useNavigate } from "react-router-dom";
import { getUserData } from "../../functions/getUserData";
@@ -7,11 +8,11 @@ import {
useProjectName,
useSocketStore,
} from "../../store/builder/store";
import { viewProject } from "../../services/dashboard/viewProject";
import OuterClick from "../../utils/outerClick";
import { KebabIcon } from "../icons/ExportCommonIcons";
import { getAllProjects } from "../../services/dashboard/getAllProjects";
import { updateProject } from "../../services/dashboard/updateProject";
// import { viewProject } from "../../services/dashboard/viewProject";
// import { updateProject } from "../../services/dashboard/updateProject";
interface DashBoardCardProps {
projectName: string;
@@ -69,9 +70,9 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
const kebabRef = useRef<HTMLDivElement>(null);
const navigateToProject = async (e: any) => {
if (active && active == "trash") return;
if (active && active === "trash") return;
try {
const viewProjects = await viewProject(organization, projectId, userId);
// const viewProjects = await viewProject(organization, projectId, userId);
setLoadingProgress(1);
setProjectName(projectName);
@@ -96,13 +97,13 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
case "open in new tab":
try {
if (active === "shared" && createdBy) {
const newTab = await viewProject(
organization,
projectId,
createdBy?._id
);
// const newTab = await viewProject(
// organization,
// projectId,
// createdBy?._id
// );
} else {
const newTab = await viewProject(organization, projectId, userId);
// const newTab = await viewProject(organization, projectId, userId);
setProjectName(projectName);
setIsKebabOpen(false);
@@ -226,7 +227,7 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
className="dashboard-card-container"
onClick={navigateToProject}
title={projectName}
onMouseLeave={() => setIsKebabOpen(false)}
// onMouseLeave={() => setIsKebabOpen(false)}
>
<div className="dashboard-card-wrapper">
<div className="preview-container">
@@ -267,7 +268,7 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
)}
{createdAt && (
<div className="project-data">
{active && active == "trash" ? `Trashed by you` : `Edited `}{" "}
{active && active === "trash" ? `Trashed by you` : `Edited `}{" "}
{getRelativeTime(createdAt)}
</div>
)}
@@ -292,22 +293,37 @@ const DashboardCard: React.FC<DashBoardCardProps> = ({
</div>
</div>
</div>
{isKebabOpen && (
<div className="kebab-options-wrapper">
{getOptions().map((option) => (
<button
key={option}
className="option"
onClick={(e) => {
e.stopPropagation();
handleOptionClick(option);
}}
>
{option}
</button>
))}
</div>
)}
{isKebabOpen &&
createPortal(
<div
className="kebab-options-wrapper"
style={{
position: "fixed",
zIndex: 9999,
top: kebabRef.current
? kebabRef.current.getBoundingClientRect().bottom +
window.scrollY
: 0,
left: kebabRef.current
? kebabRef.current.getBoundingClientRect().left + window.scrollX
: 0,
}}
>
{getOptions().map((option) => (
<button
key={option}
className="option"
onClick={(e) => {
e.stopPropagation();
handleOptionClick(option);
}}
>
{option}
</button>
))}
</div>,
document.body
)}
</button>
);
};

View File

@@ -39,7 +39,7 @@ const DashboardHome: React.FC = () => {
if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) {
setRecentProjects(projects);
}
} catch (error) {}
} catch (error) { }
};
const handleRecentProjectSearch = async (inputValue: string) => {
@@ -66,7 +66,7 @@ const DashboardHome: React.FC = () => {
// );
//
//socket for delete Project
// SOCKET for delete Project
const deleteProject = {
projectId,
organization,
@@ -90,7 +90,7 @@ const DashboardHome: React.FC = () => {
};
});
setIsSearchActive(false);
} catch (error) {}
} catch (error) { }
};
const handleDuplicateRecentProject = async (
@@ -98,15 +98,18 @@ const DashboardHome: React.FC = () => {
projectName: string,
thumbnail: string
) => {
const duplicateRecentProjectData = {
userId,
thumbnail,
organization,
projectUuid: generateUniqueId(),
refProjectID: projectId,
projectName,
};
projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData);
if (projectSocket) {
const duplicateRecentProjectData = {
userId,
thumbnail,
organization,
projectUuid: generateUniqueId(),
refProjectID: projectId,
projectName,
};
projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData);
}
};
const renderProjects = () => {

View File

@@ -60,7 +60,7 @@ const DashboardProjects: React.FC = () => {
if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
setWorkspaceProjects(projects);
}
} catch (error) {}
} catch (error) { }
};
const handleDeleteProject = async (projectId: any) => {
@@ -77,7 +77,7 @@ const DashboardProjects: React.FC = () => {
userId,
};
//socket for deleting the project
// SOCKET for deleting the project
if (projectSocket) {
projectSocket.emit("v1:project:delete", deleteProjects);
} else {
@@ -95,7 +95,7 @@ const DashboardProjects: React.FC = () => {
};
});
setIsSearchActive(false);
} catch (error) {}
} catch (error) { }
};
const handleDuplicateWorkspaceProject = async (
@@ -110,16 +110,17 @@ const DashboardProjects: React.FC = () => {
// projectName
// );
// console.log("duplicatedProject: ", duplicatedProject);
const duplicateProjectData = {
userId,
thumbnail,
organization,
projectUuid: generateUniqueId(),
refProjectID: projectId,
projectName,
};
projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
if (projectSocket) {
const duplicateProjectData = {
userId,
thumbnail,
organization,
projectUuid: generateUniqueId(),
refProjectID: projectId,
projectName,
};
projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
}
};
const renderProjects = () => {
@@ -166,7 +167,7 @@ const DashboardProjects: React.FC = () => {
try {
const sharedWithMe = await sharedWithMeProjects();
setSharedWithMeProjects(sharedWithMe);
} catch {}
} catch { }
};
useEffect(() => {

View File

@@ -38,12 +38,13 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
const handleCreateNewProject = async () => {
const token = localStorage.getItem("token");
const refreshToken = localStorage.getItem("refreshToken");
console.log("refreshToken: ", refreshToken);
if (!token || !refreshToken) {
console.error('token expired');
return;
}
try {
const projectId = generateProjectId();
useSocketStore
.getState()
.initializeSocket(email, organization, token, refreshToken);
useSocketStore.getState().initializeSocket(email, organization, token, refreshToken);
//API for creating new Project
// const project = await createProject(
@@ -60,11 +61,10 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
projectUuid: projectId,
};
console.log("projectSocket: ", projectSocket);
if (projectSocket) {
const handleResponse = (data: any) => {
if (data.message === "Project created successfully") {
setLoadingProgress(1)
setLoadingProgress(1);
navigate(`/projects/${data.data.projectId}`);
}
projectSocket.off("v1-project:response:add", handleResponse); // Clean up
@@ -90,7 +90,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<div className="user-name">
{userName
? userName.charAt(0).toUpperCase() +
userName.slice(1).toLowerCase()
userName.slice(1).toLowerCase()
: "Anonymous"}
</div>
</div>
@@ -141,7 +141,10 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
activeTab === "Tutorials" ? "option-list active" : "option-list"
}
title="coming soon"
onClick={() => setActiveTab("Tutorials")}
onClick={() => {
// setActiveTab("Tutorials");
console.warn("Tutorials comming soon");
}}
>
<TutorialsIcon />
Tutorials
@@ -153,14 +156,17 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
: "option-list"
}
title="coming soon"
onClick={() => setActiveTab("Documentation")}
onClick={() => {
// setActiveTab("Documentation");
console.warn("Documentation comming soon");
}}
>
<DocumentationIcon />
Documentation
</div>
</div>
<div className="side-bar-options-container" title="coming soon">
<div className="option-list">
<div className="side-bar-options-container">
<div className="option-list" title="coming soon">
<SettingsIcon />
Settings
</div>
@@ -175,7 +181,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<LogoutIcon />
Log out
</div>
<div className="option-list">
<div className="option-list" title="coming soon">
<HelpIcon />
Help & Feedback
</div>

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import { HelpIcon } from "../icons/DashboardIcon";
import React, { useEffect, useState } from "react";
import { HelpIcon, WifiIcon } from "../icons/DashboardIcon";
import { useLogger } from "../ui/log/LoggerContext";
import { GetLogIcon } from "./getLogIcons";
import {
@@ -31,6 +31,27 @@ const Footer: React.FC = () => {
const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore();
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
useEffect(() => {
const handleOnline = () => {
echo.success('You are back Online');
setIsOnline(true);
};
const handleOffline = () => {
echo.warn('Changes made now might not be saved');
echo.error('You are now Offline.');
setIsOnline(false);
};
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, []);
const mouseButtons = [
{
icon: <CurserLeftIcon />,
@@ -98,6 +119,16 @@ const Footer: React.FC = () => {
<HelpIcon />
</div>
</div>
<div
className={`wifi-connection ${
isOnline ? "connected" : "disconnected"
}`}
>
<div className="icon">
<WifiIcon />
</div>
<div className="tooltip">{isOnline ? "Online" : "Offline"}</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,370 @@
export const ForkLiftIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_6014_655)">
<g filter="url(#filter0_f_6014_655)">
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<defs>
<filter
id="filter0_f_6014_655"
x="2.14674"
y="4.11427"
width="17"
height="11.7715"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="0.233881"
result="effect1_foregroundBlur_6014_655"
/>
</filter>
<clipPath id="clip0_6014_655">
<rect
width="18.7105"
height="18.7105"
fill="white"
transform="translate(0.644775 0.644531)"
/>
</clipPath>
</defs>
</svg>
);
};
export const ConveyorIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clip-path="url(#clip0_352_182)">
<path
d="M8.16336 9.72707H8.82742C8.80273 9.66301 8.7893 9.59363 8.7893 9.52144V6.32707C8.7893 6.1677 8.85492 6.02238 8.95961 5.91738C9.06586 5.8127 9.21086 5.74707 9.3693 5.74707H12.8659C13.0255 5.74707 13.1705 5.81238 13.2755 5.91738C13.3805 6.02238 13.4455 6.16707 13.4455 6.32707V9.52144C13.4455 9.59363 13.4321 9.66301 13.4074 9.72707H15.918C16.4902 9.72707 17.0105 9.96113 17.3874 10.3377C17.764 10.7146 17.998 11.2349 17.998 11.8071C17.998 12.3793 17.764 12.8996 17.3871 13.2761C17.0105 13.653 16.4896 13.8874 15.918 13.8874H4.07836C3.50617 13.8874 2.98586 13.6533 2.60867 13.2761C2.23211 12.8996 1.99805 12.3793 1.99805 11.8071C1.99805 11.2361 2.23242 10.7158 2.60961 10.3383C2.8793 10.0677 3.22305 9.87082 3.60742 9.78113C3.56655 9.70067 3.54524 9.61169 3.54523 9.52144V6.32707C3.54523 6.1677 3.61086 6.02238 3.71555 5.91738C3.82148 5.8127 3.9668 5.74707 4.12492 5.74707H7.6218C7.78148 5.74707 7.92617 5.81238 8.03117 5.91738C8.13617 6.02238 8.20148 6.16707 8.20148 6.32707V9.52144C8.20148 9.59363 8.18805 9.66301 8.16336 9.72707ZM15.6268 6.7652L16.823 7.89488L15.5705 9.08207L15.183 8.67488L15.6805 8.20363L14.1824 8.20582V7.64426L15.7365 7.64207L15.2415 7.17457L15.6268 6.7652ZM5.28961 6.6077H6.46648L6.48617 8.05176L5.8543 7.66645L5.26117 8.04238L5.28961 6.6077ZM7.6218 6.2402H4.12492C4.10117 6.2402 4.0793 6.24988 4.06367 6.26551C4.04762 6.282 4.03856 6.30406 4.03836 6.32707V9.52144C4.03836 9.54551 4.04773 9.56738 4.06305 9.58269C4.07961 9.59832 4.10117 9.60801 4.12492 9.60801H7.6218C7.64492 9.60801 7.6668 9.59801 7.68273 9.58238C7.69836 9.56644 7.70836 9.54457 7.70836 9.52144V6.32707C7.70836 6.30301 7.69867 6.28145 7.68305 6.26582C7.66742 6.24988 7.64555 6.2402 7.6218 6.2402ZM10.5334 6.6077H11.7102L11.7299 8.05176L11.098 7.66645L10.5046 8.04238L10.5334 6.6077ZM12.8659 6.2402H9.3693C9.34523 6.2402 9.32336 6.24988 9.30773 6.26551C9.29169 6.282 9.28262 6.30406 9.28242 6.32707V9.52144C9.28242 9.54551 9.29211 9.56738 9.30711 9.58269C9.32367 9.59832 9.34523 9.60801 9.3693 9.60801H12.8659C12.889 9.60801 12.9109 9.59801 12.9268 9.58238C12.9424 9.56644 12.9524 9.54457 12.9524 9.52144V6.32707C12.9524 6.30301 12.9427 6.28145 12.9271 6.26582C12.9115 6.24988 12.8896 6.2402 12.8659 6.2402ZM14.7877 11.0064C15.2299 11.0064 15.5887 11.3649 15.5887 11.8071C15.5887 12.2493 15.2299 12.608 14.7877 12.608C14.3455 12.608 13.9871 12.2493 13.9871 11.8071C13.9871 11.3649 14.3455 11.0064 14.7877 11.0064ZM11.5946 11.0064C12.0368 11.0064 12.3952 11.3649 12.3952 11.8071C12.3952 12.2493 12.0368 12.608 11.5946 12.608C11.1521 12.608 10.7937 12.2493 10.7937 11.8071C10.7937 11.3649 11.1521 11.0064 11.5946 11.0064ZM8.40148 11.0064C8.84367 11.0064 9.20242 11.3649 9.20242 11.8071C9.20242 12.2493 8.84367 12.608 8.40148 12.608C7.9593 12.608 7.60086 12.2493 7.60086 11.8071C7.60086 11.3649 7.9593 11.0064 8.40148 11.0064ZM5.20836 11.0064C5.65055 11.0064 6.0093 11.3649 6.0093 11.8071C6.0093 12.2493 5.65055 12.608 5.20836 12.608C4.76617 12.608 4.40742 12.2493 4.40742 11.8071C4.40742 11.3649 4.76617 11.0064 5.20836 11.0064ZM15.918 10.3608H4.07836C3.68086 10.3608 3.3193 10.5239 3.05711 10.7861C2.7943 11.0474 2.63211 11.4089 2.63211 11.8071C2.63211 12.2043 2.79492 12.5661 3.05711 12.828C3.3193 13.0905 3.68117 13.2533 4.07836 13.2533H15.918C16.3149 13.2533 16.6768 13.0902 16.939 12.828C17.2012 12.5658 17.3643 12.2043 17.3643 11.8071C17.3643 11.4099 17.2012 11.0483 16.939 10.7861C16.6768 10.5239 16.3152 10.3608 15.918 10.3608Z"
fill="white"
/>
<path
d="M9.2334 9.76172L9.15527 6.13672H13.1396V9.76172H9.2334Z"
fill="white"
/>
<path
d="M3.9209 9.76172L3.84277 6.13672H7.82715V9.76172H3.9209Z"
fill="white"
/>
<path
d="M11.7178 6.60645H10.5303L10.499 8.05957L11.0928 7.66895L11.7334 8.05957L11.7178 6.60645Z"
fill="#494949"
/>
<path
d="M6.43555 6.60645H5.24805L5.2168 8.05957L5.81055 7.66895L6.45117 8.05957L6.43555 6.60645Z"
fill="#494949"
/>
</g>
<defs>
<clipPath id="clip0_352_182">
<rect
width="16"
height="8.14031"
fill="white"
transform="translate(1.99902 5.74707)"
/>
</clipPath>
</defs>
</svg>
);
};
export const RoboticArmIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M13.9796 9.68306L10.6387 7.13761L12.5478 5.22852L15.2523 8.25124L13.9796 9.68306Z"
fill="white"
/>
<rect
x="3.07955"
y="15.1713"
width="9.06818"
height="2.06818"
rx="0.238636"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<rect
x="4.59277"
y="10.9551"
width="2.86364"
height="4.13636"
fill="white"
/>
<rect
x="5.22656"
y="12.5459"
width="0.636364"
height="2.22727"
rx="0.318182"
fill="#494949"
/>
<rect
x="11.2617"
y="16.0898"
width="0.636364"
height="2.22727"
rx="0.318182"
transform="rotate(88.7682 11.2617 16.0898)"
fill="#494949"
/>
<rect
x="5.71387"
y="8.08301"
width="4.85107"
height="2.86364"
transform="rotate(-46.048 5.71387 8.08301)"
fill="white"
/>
<circle
cx="6.02326"
cy="10.161"
r="1.98864"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<circle
cx="10.4764"
cy="5.06916"
r="1.98864"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<circle cx="6.02379" cy="10.1615" r="1.11364" fill="#494949" />
<circle cx="10.4769" cy="5.06871" r="1.11364" fill="#494949" />
<path
d="M14.9316 8.17188C15.5026 8.17188 15.9656 8.63511 15.9658 9.20605V10.2402H13.8975V9.20605C13.8977 8.63522 14.3608 8.17207 14.9316 8.17188Z"
fill="white"
stroke="white"
stroke-width="0.159091"
/>
<path
d="M14.4561 10.1602H15.7295C16.3442 10.1604 16.8426 10.6587 16.8428 11.2734V13.291C16.8427 13.4108 16.7153 13.4875 16.6094 13.4316L15.834 13.0215V11.6855C15.8338 11.4222 15.6208 11.2092 15.3574 11.209H14.8799C14.6164 11.209 14.4025 11.4221 14.4023 11.6855V13.0205L13.5742 13.4424C13.4684 13.4963 13.3428 13.4186 13.3428 13.2998V11.2734C13.3429 10.6586 13.8412 10.1603 14.4561 10.1602Z"
stroke="white"
stroke-width="0.318182"
/>
<rect
x="8.43457"
y="6.18359"
width="0.636364"
height="2.22727"
rx="0.318182"
transform="rotate(46.5101 8.43457 6.18359)"
fill="#494949"
/>
<rect
x="14.0859"
y="7.48438"
width="0.636364"
height="1.34988"
rx="0.318182"
transform="rotate(131.749 14.0859 7.48438)"
fill="#494949"
/>
</svg>
);
};
export const MachineIcon = () => {
return (
<svg
width="21"
height="20"
viewBox="0 0 21 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.9369 3.38379V5.39368H10.7644V4.55635H9.5918V3.38379H11.9369Z"
fill="white"
/>
<path
d="M8.07979 12.0938H6.90723V16.2816H8.07979V12.0938Z"
fill="white"
/>
<path
d="M5.90694 12.0938H4.73438V16.2816H5.90694V12.0938Z"
fill="white"
/>
<path
d="M9.59346 4.55635V5.8964H8.4209V3.38379H9.59346V4.55635Z"
fill="white"
/>
<path
d="M6.40843 5.89694C6.96354 5.89694 7.41354 5.44693 7.41354 4.89183C7.41354 4.33672 6.96354 3.88672 6.40843 3.88672C5.85332 3.88672 5.40332 4.33672 5.40332 4.89183C5.40332 5.44693 5.85332 5.89694 6.40843 5.89694Z"
fill="white"
/>
<path
d="M11.418 5.08984V4.08984"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
<path
d="M10.7646 7.9055L10.9324 6.90039H12.2724L12.4399 7.9055H13.1097V9.91539H10.0947V7.9055H10.7646Z"
fill="white"
/>
<path
d="M14.7864 5.39258H13.7812V6.73295H14.7864V5.39258Z"
fill="white"
/>
<path d="M16.6304 16.2832H15.29V17.4558H16.6304V16.2832Z" fill="white" />
<path
d="M7.58266 16.2832V17.4558H4.23242V16.2832H4.73481H5.90737H7.07994H7.58266Z"
fill="white"
/>
<path d="M14.7864 13.9365H8.4209V15.1091H14.7864V13.9365Z" fill="white" />
<path
d="M7.08048 10.585V12.0925H5.90791H4.73535V10.585H7.08048Z"
fill="#494949"
/>
<path
d="M14.7862 11.2557H8.42069V13.9358V15.1083V16.2809H7.58337H7.08065V12.093V10.5855H4.73552V12.093V16.2809H4.23313H3.39551V7.23531H4.40059H8.42069V9.91537H10.096H13.1109H14.7862V6.73292V5.39258H17.1314V16.2809H16.629H15.2886H14.7862V15.1083V13.9358V11.2557Z"
fill="white"
/>
<path
d="M8.42343 5.89613V7.23615H4.40332V2.5459H8.4234V3.38355L8.42343 5.89613ZM7.41865 4.89102C7.41865 4.33596 6.96861 3.88591 6.41354 3.88591C5.85847 3.88591 5.40843 4.33596 5.40843 4.89102C5.40843 5.44609 5.85847 5.89613 6.41354 5.89613C6.96861 5.89613 7.41865 5.44609 7.41865 4.89102Z"
fill="white"
/>
<path
d="M7.41862 8.15625H4.40332V8.65963H7.41862V8.15625Z"
fill="#494949"
/>
<path
d="M7.41862 9.16211H4.40332V9.66549H7.41862V9.16211Z"
fill="#494949"
/>
<path
d="M6.41496 3.63379C5.722 3.63379 5.1582 4.19758 5.1582 4.89057C5.1582 5.58356 5.722 6.14736 6.41496 6.14736C7.10795 6.14736 7.67174 5.58356 7.67174 4.89057C7.67174 4.19758 7.10798 3.63379 6.41496 3.63379ZM6.41496 5.64401C5.99953 5.64401 5.66159 5.30603 5.66159 4.8906C5.66159 4.47518 5.99953 4.1372 6.41496 4.1372C6.83039 4.1372 7.16836 4.47518 7.16836 4.8906C7.16836 5.30603 6.83039 5.64401 6.41496 5.64401Z"
fill="#494949"
/>
<path
d="M6.74732 4.63965H6.07715V5.14303H6.74732V4.63965Z"
fill="#494949"
/>
<path
d="M8.91992 5.08984V4.08984"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
<path
d="M12.3857 9.08984H10.8857"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
<path
d="M15.9199 6.09082V12.5908"
stroke="#494949"
stroke-width="0.5"
stroke-linecap="round"
/>
</svg>
);
};
export const CraneIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.5 5.7502L12.875 1.3752C12.6875 1.1877 12.375 1.1877 12.125 1.3127L5.4375 5.6252H1.875C1.5 5.6252 1.25 5.8752 1.25 6.2502C1.25 6.6252 1.5 6.8752 1.875 6.8752H4.375V10.0002H1.875C1.5 10.0002 1.25 10.2502 1.25 10.6252V13.7502C1.25 14.1252 1.5 14.3752 1.875 14.3752H8.125C8.5 14.3752 8.75 14.1252 8.75 13.7502V10.6252C8.75 10.2502 8.5 10.0002 8.125 10.0002H5.625V6.8752H10.625V15.9377L8.375 17.6252C8.1875 17.8127 8.0625 18.0627 8.1875 18.3127C8.25 18.5627 8.5 18.7502 8.75 18.7502H16.25C16.5 18.7502 16.75 18.5627 16.8125 18.3127C16.875 18.0627 16.8125 17.7502 16.625 17.6252L14.375 15.9377V6.8752H18.125C18.375 6.8752 18.625 6.6877 18.6875 6.4377C18.8125 6.1877 18.75 5.9377 18.5 5.7502ZM3.6875 11.2502L2.5 12.7502V11.2502H3.6875ZM5 11.6252L6.1875 13.1252H3.8125L5 11.6252ZM7.5 12.7502L6.3125 11.2502H7.5V12.7502ZM11.875 3.0002V5.6252H11.25H7.75L11.875 3.0002ZM13.75 5.6252H13.125V3.1252L16.3125 5.6252H13.75Z"
fill="white"
/>
</svg>
);
};
type TypeBasedAssetIconsProps = {
assetType: string;
};
export function TypeBasedAssetIcons({ assetType }: TypeBasedAssetIconsProps) {
return (
<div>
{assetType === "StaticMachine" && <MachineIcon />}
{assetType === "Vehicle" && <ForkLiftIcon />}
{assetType === "Conveyor" && <ConveyorIcon />}
{assetType === "Crane" && <CraneIcon />}
{assetType === "ArmBot" && <RoboticArmIcon />}
</div>
);
}

View File

@@ -253,3 +253,20 @@ export function LogoutIcon() {
</svg>
);
}
export function WifiIcon() {
return (
<svg
width="12"
height="12"
viewBox="0 0 12 12"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.78458 7.91952C6.35145 7.4864 5.64945 7.4864 5.21633 7.91952C4.7832 8.35265 4.7832 9.05465 5.21633 9.48777C5.64945 9.9209 6.35145 9.9209 6.78458 9.48777C7.2177 9.05465 7.2177 8.35265 6.78458 7.91952ZM9.7902 6.48215L8.67945 7.5929C7.20008 6.11352 4.8012 6.11352 3.32183 7.5929L2.21108 6.48215C4.30395 4.38927 7.69733 4.38927 9.7902 6.48215ZM10.574 5.69802C8.04795 3.17202 3.95258 3.17202 1.42658 5.69802L0.283203 4.55465C3.4407 1.39715 8.55983 1.39715 11.7173 4.55465L10.574 5.69802Z"
fill="white"
/>
</svg>
);
}

View File

@@ -1697,93 +1697,6 @@ export const TargetIcon = () => {
);
};
export const ForkLiftIcon = () => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_6014_655)">
<g filter="url(#filter0_f_6014_655)">
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<path
d="M4.97826 12.4473C4.1581 12.4473 3.49243 13.112 3.49243 13.9331C3.49243 14.753 4.15813 15.4177 4.97826 15.4177C5.79818 15.4177 6.46284 14.753 6.46284 13.9331C6.46284 13.1119 5.79815 12.4473 4.97826 12.4473ZM4.97826 14.4544C4.69055 14.4544 4.45567 14.2208 4.45567 13.9331C4.45567 13.6454 4.69052 13.4105 4.97826 13.4105C5.26573 13.4105 5.49957 13.6454 5.49957 13.9331C5.49957 14.2208 5.26573 14.4544 4.97826 14.4544Z"
fill="white"
/>
<path
d="M11.3257 5.81717C11.1156 5.08596 10.4467 4.58203 9.6857 4.58203H5.20523C4.84291 4.58203 4.5491 4.87583 4.5491 5.23816V9.41249H4.07715C3.26935 9.41249 2.6145 10.0673 2.6145 10.8751V13.4258H2.88458C3.11388 12.4815 3.96429 11.7774 4.9782 11.7774C5.99161 11.7774 6.84123 12.4815 7.07052 13.4258H8.05798C8.13812 12.1123 9.22862 11.0679 10.5618 11.0679C11.2884 11.0679 11.9421 11.3804 12.4009 11.876V11.7487V10.3279C12.4009 9.8179 12.3293 9.31021 12.1885 8.82012L11.3257 5.81717ZM10.1793 8.21563L9.49446 8.6286L10.5313 10.6025H8.67763L7.81739 9.41249H6.07579V5.66572H9.6857C9.96187 5.66572 10.208 5.85115 10.2844 6.1165L11.1471 9.11919C11.2058 9.32329 11.2487 9.53116 11.2769 9.74131L10.1793 8.21563Z"
fill="white"
/>
<path
d="M10.5616 11.7383C9.54538 11.7383 8.7207 12.5617 8.7207 13.5792C8.7207 14.5948 9.54538 15.4183 10.5616 15.4183C11.5772 15.4183 12.4007 14.5948 12.4007 13.5792C12.4007 12.5617 11.5772 11.7383 10.5616 11.7383ZM10.5616 14.225C10.2051 14.225 9.91402 13.9357 9.91402 13.5792C9.91402 13.2226 10.2051 12.9316 10.5616 12.9316C10.9176 12.9316 11.2074 13.2226 11.2074 13.5792C11.2074 13.9357 10.9176 14.225 10.5616 14.225Z"
fill="white"
/>
<path
d="M14.8484 14.1482C14.3396 14.1482 13.9269 13.7355 13.9269 13.2268V4.58203H12.7197V15.0314H18.679V14.1482H14.8484Z"
fill="white"
/>
<path
d="M9.85501 7.42463L8.88497 8.09259C8.78745 8.15961 8.76301 8.2929 8.83003 8.39041C8.89704 8.48768 9.03033 8.51237 9.12785 8.44535L10.0979 7.77714C10.1954 7.71012 10.2199 7.57681 10.1529 7.47957C10.0858 7.38206 9.95231 7.35733 9.85501 7.42463Z"
fill="white"
/>
</g>
<defs>
<filter
id="filter0_f_6014_655"
x="2.14674"
y="4.11427"
width="17"
height="11.7715"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend
mode="normal"
in="SourceGraphic"
in2="BackgroundImageFix"
result="shape"
/>
<feGaussianBlur
stdDeviation="0.233881"
result="effect1_foregroundBlur_6014_655"
/>
</filter>
<clipPath id="clip0_6014_655">
<rect
width="18.7105"
height="18.7105"
fill="white"
transform="translate(0.644775 0.644531)"
/>
</clipPath>
</defs>
</svg>
);
};
export const RightHalfFillCircleIcon = () => {
return (
<svg

View File

@@ -1,13 +1,11 @@
import React, { useEffect } from "react";
import {
useLoadingProgress,
useRenameModeStore,
useSaveVersion,
useSelectedAssets,
useSelectedComment,
useSelectedFloorItem,
useSocketStore,
useWidgetSubOption,
useLoadingProgress,
useRenameModeStore,
useSaveVersion,
useSelectedComment,
useSocketStore,
useWidgetSubOption,
} from "../../../store/builder/store";
import useModuleStore, { useThreeDStore } from "../../../store/useModuleStore";
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
@@ -27,10 +25,7 @@ import ControlsPlayer from "../controls/ControlsPlayer";
import SelectFloorPlan from "../../temporary/SelectFloorPlan";
import { createHandleDrop } from "../../../modules/visualization/functions/handleUiDrop";
import Scene from "../../../modules/scene/scene";
import {
useComparisonProduct,
useMainProduct,
} from "../../../store/simulation/useSimulationStore";
import { useComparisonProduct, useMainProduct } from "../../../store/simulation/useSimulationStore";
import { useProductContext } from "../../../modules/simulation/products/productContext";
import RegularDropDown from "../../ui/inputs/RegularDropDown";
import RenameTooltip from "../../ui/features/RenameTooltip";
@@ -42,184 +37,172 @@ import { useVersionContext } from "../../../modules/builder/version/versionConte
import VersionSaved from "../sidebarRight/versionHisory/VersionSaved";
import Footer from "../../footer/Footer";
import ThreadChat from "../../ui/collaboration/ThreadChat";
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
function MainScene() {
const { setMainProduct } = useMainProduct();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { isVersionSaved, setIsVersionSaved } = useSaveVersion();
const { activeModule } = useModuleStore();
const { selectedUser } = useSelectedUserStore();
const { loadingProgress } = useLoadingProgress();
const { toggleThreeD } = useThreeDStore();
const { isPlaying } = usePlayButtonStore();
const { widgetSubOption } = useWidgetSubOption();
const { visualizationSocket } = useSocketStore();
const { selectedZone } = useSelectedZoneStore();
const { setFloatingWidget } = useFloatingWidget();
const { clearComparisonProduct } = useComparisonProduct();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
const { assetStore, productStore } = useSceneContext();
const { products } = productStore();
const { setName } = assetStore();
const { projectId } = useParams()
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
const { versionHistory } = useVersionHistoryStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion, setSelectedVersion } = selectedVersionStore();
const { selectedComment, commentPositionState } = useSelectedComment();
const { setMainProduct } = useMainProduct();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { isVersionSaved, setIsVersionSaved } = useSaveVersion();
const { activeModule } = useModuleStore();
const { selectedUser } = useSelectedUserStore();
const { loadingProgress } = useLoadingProgress();
const { toggleThreeD } = useThreeDStore();
const { isPlaying } = usePlayButtonStore();
const { widgetSubOption } = useWidgetSubOption();
const { visualizationSocket } = useSocketStore();
const { selectedZone } = useSelectedZoneStore();
const { setFloatingWidget } = useFloatingWidget();
const { clearComparisonProduct } = useComparisonProduct();
const { selectedFloorAsset, setSelectedFloorAsset } = useBuilderStore();
const { assetStore, productStore } = useSceneContext();
const { products } = productStore();
const { setName, selectedAssets, setSelectedAssets } = assetStore();
const { projectId } = useParams()
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
const { versionHistory } = useVersionHistoryStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion, setSelectedVersion } = selectedVersionStore();
const { selectedComment, commentPositionState } = useSelectedComment();
useEffect(() => {
if (activeModule !== 'simulation') {
clearComparisonProduct();
setIsVersionSaved(false);
}
}, [activeModule, clearComparisonProduct, setIsVersionSaved])
useEffect(() => {
if (versionHistory.length > 0) {
setSelectedVersion(versionHistory[0])
}
}, [setSelectedVersion, versionHistory])
const handleSelectVersion = (option: string) => {
const version = versionHistory.find((version) => version.versionName === option);
if (version) {
setSelectedVersion(version);
}
};
const handleSelectProduct = (option: string) => {
const product = products.find((product) => product.productName === option);
if (product) {
setMainProduct(product.productUuid, product.productName);
}
};
const handleObjectRename = async (newName: string) => {
if (!projectId) return
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,
versionId: selectedVersion?.versionId || ''
}).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,
versionId: selectedVersion?.versionId || ''
}).then(() => {
selectedAssets[0].userData = {
...selectedAssets[0].userData,
modelName: newName
};
setSelectedAssets(selectedAssets);
setIsRenameMode(false);
setName(selectedAssets[0].userData.modelUuid, newName);
})
}
}
return (
<>
{!selectedUser && (
<>
<KeyPressListener />
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
{!isPlaying && (
<>
{toggleThreeD && !isVersionSaved && <ModuleToggle />}
<SideBarLeft />
<SideBarRight />
</>
)}
<RealTimeVisulization />
{activeModule === "market" && <MarketPlace />}
{activeModule !== "market" && !isPlaying && !isVersionSaved && (
<Tools />
)}
{(isPlaying) &&
activeModule === "simulation" &&
loadingProgress === 0 && <SimulationPlayer />}
{(isPlaying) &&
activeModule !== "simulation" && <ControlsPlayer />}
{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 />}
</>
)}
<div
className="scene-container"
id="work-space-three-d-canvas"
style={{
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
borderRadius:
isPlaying || activeModule !== "visualization" ? "" : "6px",
}}
role="application"
onDrop={(event) =>
createHandleDrop({
widgetSubOption,
visualizationSocket,
selectedZone,
setFloatingWidget,
event,
projectId,
versionId: selectedVersion?.versionId || '',
})
useEffect(() => {
if (activeModule !== 'simulation') {
clearComparisonProduct();
setIsVersionSaved(false);
}
onDragOver={(event) => event.preventDefault()}
>
<Scene layout="Main Layout" />
</div>
}, [activeModule, clearComparisonProduct, setIsVersionSaved])
{selectedProduct && selectedVersion && isVersionSaved && !isPlaying && activeModule === "simulation" && (
<div className="selectLayout-wrapper">
<RegularDropDown
header={selectedVersion.versionName}
options={versionHistory.map((v) => v.versionName)} // Pass layout names as options
onSelect={handleSelectVersion}
search={false}
/>
<br />
<RegularDropDown
header={selectedProduct.productName}
options={products.map((l) => l.productName)} // Pass layout names as options
onSelect={handleSelectProduct}
search={false}
/>
</div>
)}
useEffect(() => {
if (versionHistory.length > 0) {
setSelectedVersion(versionHistory[0])
}
}, [setSelectedVersion, versionHistory])
{activeModule !== "market" && !selectedUser && <Footer />}
const handleSelectVersion = (option: string) => {
const version = versionHistory.find((version) => version.versionName === option);
if (version) {
setSelectedVersion(version);
}
};
<VersionSaved />
const handleSelectProduct = (option: string) => {
const product = products.find((product) => product.productName === option);
if (product) {
setMainProduct(product.productUuid, product.productName);
}
};
{
(commentPositionState !== null || selectedComment !== null) &&
<ThreadChat />
}
const handleObjectRename = async (newName: string) => {
if (!projectId) return
if (selectedFloorAsset) {
setAssetsApi({
modelUuid: selectedFloorAsset.userData.modelUuid,
modelName: newName,
projectId,
versionId: selectedVersion?.versionId || ''
}).then(() => {
selectedFloorAsset.userData = { ...selectedFloorAsset.userData, modelName: newName };
setSelectedFloorAsset(selectedFloorAsset);
setIsRenameMode(false);
setName(selectedFloorAsset.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 (
<>
{!selectedUser && (
<>
<KeyPressListener />
{loadingProgress > 0 && <LoadingPage progress={loadingProgress} />}
{!isPlaying && (
<>
{toggleThreeD && !isVersionSaved && <ModuleToggle />}
<SideBarLeft />
<SideBarRight />
</>
)}
<RealTimeVisulization />
{activeModule === "market" && <MarketPlace />}
{activeModule !== "market" && !isPlaying && !isVersionSaved && (
<Tools />
)}
{(isPlaying) && activeModule === "simulation" && loadingProgress === 0 && <SimulationPlayer />}
{(isPlaying) && activeModule !== "simulation" && <ControlsPlayer />}
{isRenameMode && (selectedFloorAsset?.userData.modelName || selectedAssets.length === 1) && <RenameTooltip name={selectedFloorAsset?.userData.modelName || selectedAssets[0].userData.modelName} onSubmit={handleObjectRename} />}
{/* remove this later */}
{activeModule === "builder" && !toggleThreeD && <SelectFloorPlan />}
</>
)}
<div
className="scene-container"
id="work-space-three-d-canvas"
style={{
height: isPlaying || activeModule !== "visualization" ? "100vh" : "",
width: isPlaying || activeModule !== "visualization" ? "100vw" : "",
left: isPlaying || activeModule !== "visualization" ? "0%" : "",
borderRadius:
isPlaying || activeModule !== "visualization" ? "" : "6px",
}}
role="application"
onDrop={(event) =>
createHandleDrop({
widgetSubOption,
visualizationSocket,
selectedZone,
setFloatingWidget,
event,
projectId,
versionId: selectedVersion?.versionId || '',
})
}
onDragOver={(event) => event.preventDefault()}
>
<Scene layout="Main Layout" />
</div>
{selectedProduct && selectedVersion && isVersionSaved && !isPlaying && activeModule === "simulation" && (
<div className="selectLayout-wrapper">
<RegularDropDown
header={selectedVersion.versionName}
options={versionHistory.map((v) => v.versionName)} // Pass layout names as options
onSelect={handleSelectVersion}
search={false}
/>
<br />
<RegularDropDown
header={selectedProduct.productName}
options={products.map((l) => l.productName)} // Pass layout names as options
onSelect={handleSelectProduct}
search={false}
/>
</div>
)}
{activeModule !== "market" && !selectedUser && <Footer />}
<VersionSaved />
{
(commentPositionState !== null || selectedComment !== null) &&
<ThreadChat />
}
</>
);
}
export default MainScene;

View File

@@ -1,307 +0,0 @@
import React, { useEffect, useState } from "react";
import Search from "../../ui/inputs/Search";
import { getCategoryAsset } from "../../../services/factoryBuilder/asset/assets/getCategoryAsset";
import { fetchAssets } from "../../../services/marketplace/fetchAssets";
import { useDecalStore, useSelectedItem } from "../../../store/builder/store";
// images -------------------
import vehicle from "../../../assets/image/categories/vehicles.png";
import workStation from "../../../assets/image/categories/workStation.png";
import machines from "../../../assets/image/categories/machines.png";
import worker from "../../../assets/image/categories/worker.png";
import storage from "../../../assets/image/categories/storage.png";
import office from "../../../assets/image/categories/office.png";
import safety from "../../../assets/image/categories/safety.png";
import feneration from "../../../assets/image/categories/feneration.png";
import decal from "../../../assets/image/categories/decal.png";
import SkeletonUI from "../../templates/SkeletonUI";
import {
AlertIcon,
DecalInfoIcon,
HangTagIcon,
NavigationIcon,
} from "../../icons/ExportCommonIcons";
// -------------------------------------
interface AssetProp {
filename: string;
thumbnail?: string;
category: string;
description?: string;
tags: string;
url?: string;
uploadDate?: number;
isArchieve?: boolean;
animated?: boolean;
price?: number;
CreatedBy?: string;
}
interface CategoryListProp {
assetImage?: string;
assetName?: string;
categoryImage: string;
category: string;
}
const Assets: React.FC = () => {
const { setSelectedItem } = useSelectedItem();
const [searchValue, setSearchValue] = useState<string>("");
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [categoryAssets, setCategoryAssets] = useState<AssetProp[]>([]);
const [filtereredAssets, setFiltereredAssets] = useState<AssetProp[]>([]);
const [categoryList, setCategoryList] = useState<CategoryListProp[]>([]);
const [isLoading, setisLoading] = useState<boolean>(false); // Loading state for assets
const handleSearchChange = (value: string) => {
const searchTerm = value.toLowerCase();
setSearchValue(value);
if (searchTerm.trim() === "" && !selectedCategory) {
setCategoryAssets([]);
return;
}
const filteredModels = filtereredAssets?.filter((model) => {
if (!model?.tags || !model?.filename || !model?.category) return false;
if (searchTerm.startsWith(":") && searchTerm.length > 1) {
const tagSearchTerm = searchTerm.slice(1);
return model.tags.toLowerCase().includes(tagSearchTerm);
} else if (selectedCategory) {
return (
model.category
.toLowerCase()
.includes(selectedCategory.toLowerCase()) &&
model.filename.toLowerCase().includes(searchTerm)
);
} else {
return model.filename.toLowerCase().includes(searchTerm);
}
});
setCategoryAssets(filteredModels);
};
useEffect(() => {
const filteredAssets = async () => {
try {
const filt = await fetchAssets();
setFiltereredAssets(filt);
} catch {
echo.error("Filter asset not found");
}
};
filteredAssets();
}, [categoryAssets]);
useEffect(() => {
setCategoryList([
{ category: "Fenestration", categoryImage: feneration },
{ category: "Decals", categoryImage: decal },
{ category: "Vehicles", categoryImage: vehicle },
{ category: "Workstation", categoryImage: workStation },
{ category: "Machines", categoryImage: machines },
{ category: "Workers", categoryImage: worker },
{ category: "Storage", categoryImage: storage },
{ category: "Safety", categoryImage: safety },
{ category: "Office", categoryImage: office },
]);
}, []);
const fetchCategoryAssets = async (asset: any) => {
setisLoading(true);
setSelectedCategory(asset);
try {
const res = await getCategoryAsset(asset);
setCategoryAssets(res);
setFiltereredAssets(res);
setisLoading(false); // End loading
// eslint-disable-next-line
} catch (error) {
echo.error("failed to fetch assets");
setisLoading(false);
}
};
const activeSubcategories = [
{ name: "Safety", icon: <AlertIcon /> },
{ name: "Navigation", icon: <NavigationIcon /> },
{ name: "Branding", icon: <HangTagIcon /> },
{ name: "Informational", icon: <DecalInfoIcon /> },
];
const { selectedSubCategory, setSelectedSubCategory } = useDecalStore();
return (
<div className="assets-container-main">
<Search onChange={handleSearchChange} />
<div className="assets-list-section">
<section>
{(() => {
if (isLoading) {
return <SkeletonUI type="asset" />; // Show skeleton when loading
}
if (searchValue) {
return (
<div className="assets-result">
<div className="assets-wrapper">
<div className="searched-content">
<p>Results for {searchValue}</p>
</div>
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
<div
key={`${index}-${asset.filename}`}
className="assets"
id={asset.filename}
title={asset.filename}
>
<img
src={asset?.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type:
asset.type === "undefined"
? undefined
: asset.type,
});
}}
/>
<div className="asset-name">
{asset.filename
.split("_")
.map(
(word: any) =>
word.charAt(0).toUpperCase() + word.slice(1)
)
.join(" ")}
</div>
</div>
))}
</div>
</div>
</div>
);
}
if (selectedCategory) {
return (
<div className="assets-wrapper">
<h2>
{selectedCategory}
<button
className="back-button"
id="asset-backButtom"
onClick={() => {
setSelectedCategory(null);
setSelectedSubCategory(null);
setCategoryAssets([]);
}}
>
Back
</button>
</h2>
{selectedCategory === "Decals" && (
<>
<div className="catogory-asset-filter">
{activeSubcategories.map((cat, index) => (
<div
key={index}
className={`catogory-asset-filter-wrapper ${
selectedSubCategory === cat.name ? "active" : ""
}`}
onClick={() => setSelectedSubCategory(cat.name)}
>
<div className="sub-catagory">{cat.icon}</div>
<div className="sub-catagory">{cat.name}</div>
</div>
))}
</div>
</>
)}
<div className="assets-container">
{categoryAssets?.map((asset: any, index: number) => (
<div
key={`${index}-${asset}`}
className="assets"
id={asset.filename}
title={asset.filename}
>
<img
src={asset?.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() => {
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type:
asset.type === "undefined"
? undefined
: asset.type,
category: asset.category,
subType: asset.subType,
});
}}
/>
<div className="asset-name">
{asset.filename
.split("_")
.map(
(word: any) =>
word.charAt(0).toUpperCase() + word.slice(1)
)
.join(" ")}
</div>
</div>
))}
{categoryAssets.length === 0 && (
<div className="no-asset">
🚧 The asset shelf is empty. We're working on filling it
up!
</div>
)}
</div>
</div>
);
}
return (
<div className="assets-wrapper">
<h2>Categories</h2>
<div className="categories-container">
{Array.from(
new Set(categoryList.map((asset) => asset.category))
).map((category, index) => {
const categoryInfo = categoryList.find(
(asset) => asset.category === category
);
return (
<div
key={`${index}-${category}`}
className="category"
id={category}
onClick={() => fetchCategoryAssets(category)}
>
<img
src={categoryInfo?.categoryImage ?? ""}
alt={category}
className="category-image"
draggable={false}
/>
<div className="category-name">{category}</div>
</div>
);
})}
</div>
</div>
);
})()}
</section>
</div>
</div>
);
};
export default Assets;

View File

@@ -3,7 +3,7 @@ import ToggleHeader from "../../ui/inputs/ToggleHeader";
import Outline from "./Outline";
import Header from "./Header";
import { useToggleStore } from "../../../store/useUIToggleStore";
import Assets from "./Assets";
import Assets from "./assetList/Assets";
import useModuleStore from "../../../store/useModuleStore";
import Widgets from "./visualization/widgets/Widgets";
import Templates from "../../../modules/visualization/template/Templates";

View File

@@ -0,0 +1,176 @@
import React, { useState, useEffect, useMemo, useCallback } from "react";
import { getFilteredAssets } from "./assetsHelpers/filteredAssetsHelper";
import { fetchCategoryDecals } from "./assetsHelpers/fetchDecalsHelper";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import {
fetchAllAssets,
fetchCategoryAssets,
} from "./assetsHelpers/fetchAssetsHelper";
import Search from "../../../ui/inputs/Search";
import SkeletonUI from "../../../templates/SkeletonUI";
import { RenderAsset } from "./assetsHelpers/renderAssetHelper";
import {
ACTIVE_DECAL_SUBCATEGORIES,
CATEGORY_LIST,
} from "./assetsHelpers/constants";
import { ArrowIcon } from "../../../icons/ExportCommonIcons";
const Assets: React.FC = () => {
const { selectedDecalCategory, setSelectedDecalCategory } = useBuilderStore();
const [searchValue, setSearchValue] = useState<string | null>(null);
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);
const [assets, setAssets] = useState<AssetProp[] | DecalProp[]>([]);
const [globalResults, setGlobalResults] = useState<(AssetProp | DecalProp)[]>(
[]
);
const [isLoading, setIsLoading] = useState(false);
const filteredAssets = useMemo(
() =>
getFilteredAssets({
assets,
searchValue,
selectedCategory,
selectedDecalCategory,
}),
[assets, searchValue, selectedCategory, selectedDecalCategory]
);
const handleFetchCategory = useCallback(
async (category: string) => {
setIsLoading(true);
setSelectedCategory(category);
if (category === "Decals") {
const res = await fetchCategoryDecals("Safety");
setAssets(res);
setSelectedDecalCategory("Safety");
} else {
const res = await fetchCategoryAssets(category);
setAssets(res);
}
setIsLoading(false);
},
[setSelectedDecalCategory]
);
const fetchGlobalSearch = useCallback(async (term: string) => {
setIsLoading(true);
const allAssets = await fetchAllAssets();
const lowerTerm = term.toLowerCase();
const matches = allAssets.filter(
(a) =>
a.filename.toLowerCase().includes(lowerTerm) ||
a.tags?.toLowerCase().includes(lowerTerm) ||
a.category?.toLowerCase().includes(lowerTerm)
);
setGlobalResults(matches);
setIsLoading(false);
}, []);
useEffect(() => {
if (!selectedCategory && searchValue?.trim())
fetchGlobalSearch(searchValue);
else setGlobalResults([]);
}, [searchValue, selectedCategory, fetchGlobalSearch]);
return (
<div className="assets-container-main">
<Search debounced onChange={setSearchValue} value={searchValue} />
<div className="assets-list-section">
<section>
{isLoading ? (
<SkeletonUI type="asset" />
) : searchValue || selectedCategory ? (
<div className="assets-wrapper">
{selectedCategory ? (
<>
<h2 className="header">
{selectedCategory}
<button
className="back-button"
onClick={() => {
setSelectedCategory(null);
setSelectedDecalCategory(null);
setAssets([]);
setSearchValue(null);
}}
>
<div className="back-arrow">
<ArrowIcon />
</div>{" "}
Back
</button>
</h2>
{selectedCategory === "Decals" && (
<div className="catogory-asset-filter">
{ACTIVE_DECAL_SUBCATEGORIES.map((cat) => (
<div
key={cat.name}
className={`catogory-asset-filter-wrapper ${selectedDecalCategory === cat.name ? "active" : ""
}`}
onClick={async () => {
setIsLoading(true);
const res = await fetchCategoryDecals(cat.name);
setAssets(res);
setSelectedDecalCategory(cat.name);
setIsLoading(false);
}}
>
<div className="sub-catagory">{cat.icon}</div>
<div className="sub-catagory">{cat.name}</div>
</div>
))}
</div>
)}
<div className="assets-container">
{filteredAssets.map((a, i) => (
<RenderAsset key={i} asset={a} index={i} />
))}
{filteredAssets.length === 0 && (
<div className="no-asset">🚧 No assets found</div>
)}
</div>
</>
) : (
<>
<h2 className="header">Global Search Results</h2>
<div className="assets-container">
{globalResults.map((a, i) => (
<RenderAsset key={i} asset={a} index={i} />
))}
{globalResults.length === 0 && (
<div className="no-asset">🔎 No matches found</div>
)}
</div>
</>
)}
</div>
) : (
<div className="assets-wrapper">
<h2 className="categories-header">Categories</h2>
<div className="categories-container">
{CATEGORY_LIST.map((cat) => (
<div
key={cat.category}
className="category"
onClick={() => handleFetchCategory(cat.category)}
>
<img
src={cat.categoryImage}
alt={cat.category}
className="category-image"
draggable={false}
/>
<div className="category-name">{cat.category}</div>
</div>
))}
</div>
</div>
)}
</section>
</div>
</div>
);
};
export default Assets;

View File

@@ -0,0 +1,34 @@
import vehicle from "../../../../../assets/image/categories/vehicles.png";
import workStation from "../../../../../assets/image/categories/workStation.png";
import machines from "../../../../../assets/image/categories/machines.png";
import worker from "../../../../../assets/image/categories/worker.png";
import storage from "../../../../../assets/image/categories/storage.png";
import office from "../../../../../assets/image/categories/office.png";
import safety from "../../../../../assets/image/categories/safety.png";
import feneration from "../../../../../assets/image/categories/feneration.png";
import decal from "../../../../../assets/image/categories/decal.png";
import {
AlertIcon,
DecalInfoIcon,
HangTagIcon,
NavigationIcon,
} from "../../../../icons/ExportCommonIcons";
export const CATEGORY_LIST: CategoryListProp[] = [
{ category: "Fenestration", categoryImage: feneration },
{ category: "Decals", categoryImage: decal },
{ category: "Vehicles", categoryImage: vehicle },
{ category: "Workstation", categoryImage: workStation },
{ category: "Machines", categoryImage: machines },
{ category: "Workers", categoryImage: worker },
{ category: "Storage", categoryImage: storage },
{ category: "Safety", categoryImage: safety },
{ category: "Office", categoryImage: office },
];
export const ACTIVE_DECAL_SUBCATEGORIES = [
{ name: "Safety", icon: <AlertIcon /> },
{ name: "Navigation", icon: <NavigationIcon /> },
{ name: "Branding", icon: <HangTagIcon /> },
{ name: "Informational", icon: <DecalInfoIcon /> },
];

View File

@@ -0,0 +1,22 @@
import { getCategoryAsset } from "../../../../../services/factoryBuilder/asset/assets/getCategoryAsset";
import { fetchAssets } from "../../../../../services/marketplace/fetchAssets";
export const fetchCategoryAssets = async (category: string): Promise<AssetProp[]> => {
if (category === "Decals") return []; // handled separately
try {
const res = await getCategoryAsset(category);
return res;
} catch (err) {
console.error("Failed to fetch category assets", err);
return [];
}
};
export const fetchAllAssets = async (): Promise<AssetProp[]> => {
try {
return await fetchAssets();
} catch (err) {
console.error("Failed to fetch all assets", err);
return [];
}
};

View File

@@ -0,0 +1,11 @@
import { getCategoryDecals } from "../../../../../services/factoryBuilder/asset/decals/getCategoryDecals";
export const fetchCategoryDecals = async (subcategory: string): Promise<DecalProp[]> => {
try {
const res = await getCategoryDecals(subcategory);
return res;
} catch (err) {
console.error("Failed to fetch decals", err);
return [];
}
};

View File

@@ -0,0 +1,34 @@
interface FilterProps {
assets: AssetProp[] | DecalProp[];
searchValue: string | null;
selectedCategory: string | null;
selectedDecalCategory: string | null;
}
export const getFilteredAssets = ({
assets,
searchValue,
selectedCategory,
selectedDecalCategory,
}: FilterProps) => {
const term = searchValue?.trim().toLowerCase();
if (!term) return assets;
if (selectedCategory === "Decals" || selectedDecalCategory) {
return (assets as DecalProp[]).filter((a) =>
a.decalName?.toLowerCase().includes(term)
);
}
return (assets as AssetProp[]).filter((a) => {
const tags = a.tags?.toLowerCase() ?? "";
const filename = a.filename?.toLowerCase() ?? "";
const category = a.category?.toLowerCase() ?? "";
if (term.startsWith(":")) return tags.includes(term.slice(1));
if (selectedCategory)
return category.includes(selectedCategory.toLowerCase()) && filename.includes(term);
return filename.includes(term);
});
};

View File

@@ -0,0 +1,59 @@
import React from "react";
import { useSelectedItem } from "../../../../../store/builder/store";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
export const RenderAsset: React.FC<{ asset: AssetProp | DecalProp; index: number }> = ({ asset, index }) => {
const { setSelectedItem } = useSelectedItem();
const { setDroppedDecal } = useBuilderStore();
if ("decalName" in asset) {
return (
<div key={`${index}-${asset.decalName}`} className="assets" id={asset.decalName} title={asset.decalName}>
<img
src={asset.decalImage}
alt={asset.decalName}
className="asset-image"
onPointerDown={() => {
setDroppedDecal({
category: asset.category,
decalName: asset.decalName,
decalImage: asset.decalImage,
decalId: asset.id,
})
}}
/>
<div className="asset-name">
{asset.decalName
.split("_")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ")}
</div>
</div>
);
}
return (
<div key={`${index}-${asset.filename}`} className="assets" id={asset.filename} title={asset.filename}>
<img
src={asset.thumbnail}
alt={asset.filename}
className="asset-image"
onPointerDown={() =>
setSelectedItem({
name: asset.filename,
id: asset.AssetID,
type: asset.type === "undefined" ? undefined : asset.type,
category: asset.category,
subType: asset.subType,
})
}
/>
<div className="asset-name">
{asset.filename
.split("_")
.map((w) => w.charAt(0).toUpperCase() + w.slice(1))
.join(" ")}
</div>
</div>
);
};

View File

@@ -1,29 +1,13 @@
import React, { useEffect, useState } from "react";
import Header from "./Header";
import useModuleStore, {
useSubModuleStore,
} from "../../../store/useModuleStore";
import {
AnalysisIcon,
FilePackageIcon,
MechanicsIcon,
PropertiesIcon,
SimulationIcon,
} from "../../icons/SimulationIcons";
import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore";
import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon, } from "../../icons/SimulationIcons";
import { useToggleStore } from "../../../store/useUIToggleStore";
import Visualization from "./visualization/Visualization";
import Analysis from "./analysis/Analysis";
import Simulations from "./simulation/Simulations";
import useVersionHistoryVisibleStore, {
useDecalStore,
useSaveVersion,
useSelectedFloorItem,
useToolMode,
} from "../../../store/builder/store";
import {
useSelectedEventData,
useSelectedEventSphere,
} from "../../../store/simulation/useSimulationStore";
import useVersionHistoryVisibleStore, { useSaveVersion, useToolMode } from "../../../store/builder/store";
import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore";
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
import GlobalProperties from "./properties/GlobalProperties";
import AssetProperties from "./properties/AssetProperties";
@@ -35,319 +19,280 @@ import WallProperties from "./properties/WallProperties";
import FloorProperties from "./properties/FloorProperties";
import SelectedWallProperties from "./properties/SelectedWallProperties";
import SelectedFloorProperties from "./properties/SelectedFloorProperties";
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
import ResourceManagement from "./resourceManagement/ResourceManagement";
import DecalProperties from "./properties/DecalProperties";
type DisplayComponent =
| "versionHistory"
| "globalProperties"
| "aisleProperties"
| "wallProperties"
| "floorProperties"
| "assetProperties"
| "selectedWallProperties"
| "selectedFloorProperties"
| "zoneProperties"
| "simulations"
| "mechanics"
| "analysis"
| "visualization"
| "selectedDecalProperties"
| "resourceManagement"
| "none";
| "versionHistory"
| "globalProperties"
| "aisleProperties"
| "wallProperties"
| "floorProperties"
| "assetProperties"
| "selectedWallProperties"
| "selectedFloorProperties"
| "selectedDecalProperties"
| "selectedAisleProperties"
| "zoneProperties"
| "simulations"
| "mechanics"
| "analysis"
| "visualization"
| "resourceManagement"
| "none";
const SideBarRight: React.FC = () => {
const { activeModule } = useModuleStore();
const { toggleUIRight } = useToggleStore();
const { toolMode } = useToolMode();
const { subModule, setSubModule } = useSubModuleStore();
const { selectedFloorItem } = useSelectedFloorItem();
const { selectedWall, selectedFloor, selectedAisle } = useBuilderStore();
const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere();
const { viewVersionHistory, setVersionHistoryVisible } =
useVersionHistoryVisibleStore();
const { isVersionSaved } = useSaveVersion();
const { selectedSubCategory } = useDecalStore();
const { selectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { toggleUIRight } = useToggleStore();
const { toolMode } = useToolMode();
const { subModule, setSubModule } = useSubModuleStore();
const { selectedWall, selectedFloor, selectedAisle, selectedFloorAsset } = useBuilderStore();
const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere();
const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { isVersionSaved } = useSaveVersion();
const [displayComponent, setDisplayComponent] =
useState<DisplayComponent>("none");
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
useEffect(() => {
if (activeModule !== "simulation") setSubModule("properties");
if (activeModule === "simulation") setSubModule("simulations");
}, [activeModule, setSubModule]);
useEffect(() => {
if (activeModule !== "simulation") setSubModule("properties");
if (activeModule === "simulation") setSubModule("simulations");
}, [activeModule, setSubModule]);
useEffect(() => {
if (
activeModule !== "mechanics" &&
selectedEventData &&
selectedEventSphere
) {
setSubModule("mechanics");
} else if (!selectedEventData && !selectedEventSphere) {
if (activeModule === "simulation") {
setSubModule("simulations");
}
}
if (activeModule !== "simulation") {
setSubModule("properties");
}
}, [activeModule, selectedEventData, selectedEventSphere, setSubModule]);
useEffect(() => {
if (activeModule === "visualization") {
setDisplayComponent("visualization");
return;
}
if (!isVersionSaved && activeModule === "simulation") {
if (subModule === "simulations") {
setDisplayComponent("simulations");
return;
}
if (subModule === "mechanics") {
setDisplayComponent("mechanics");
return;
}
if (subModule === "analysis") {
setDisplayComponent("analysis");
return;
}
if (subModule === "resourceManagement") {
setDisplayComponent("resourceManagement");
return;
}
}
if (activeModule === "simulation" || activeModule === "builder") {
if (subModule === "resourceManagement") {
setDisplayComponent("resourceManagement");
return;
}
}
if (subModule === "properties" && activeModule !== "visualization") {
if (selectedFloorItem) {
setDisplayComponent("assetProperties");
return;
}
if (
!selectedFloorItem &&
!selectedFloor &&
!selectedAisle &&
selectedWall
) {
setDisplayComponent("selectedWallProperties");
return;
}
if (
!selectedFloorItem &&
!selectedWall &&
!selectedAisle &&
selectedFloor
) {
setDisplayComponent("selectedFloorProperties");
return;
}
if (viewVersionHistory) {
setDisplayComponent("versionHistory");
return;
}
if (selectedSubCategory) {
setDisplayComponent("selectedDecalProperties");
return;
}
if (
!selectedFloorItem &&
!selectedFloor &&
!selectedWall &&
!selectedSubCategory
) {
if (toolMode === "Aisle") {
setDisplayComponent("aisleProperties");
return;
useEffect(() => {
if (activeModule !== "mechanics" && selectedEventData && selectedEventSphere) {
setSubModule("mechanics");
} else if (!selectedEventData && !selectedEventSphere) {
if (activeModule === "simulation") {
setSubModule("simulations");
}
}
if (toolMode === "Wall") {
setDisplayComponent("wallProperties");
return;
if (activeModule !== "simulation") {
setSubModule("properties");
}
if (toolMode === "Floor") {
setDisplayComponent("floorProperties");
return;
}, [activeModule, selectedEventData, selectedEventSphere, setSubModule]);
useEffect(() => {
if (activeModule === "visualization") {
setDisplayComponent("visualization");
return;
}
setDisplayComponent("globalProperties");
return;
}
}
if (
subModule === "zoneProperties" &&
(activeModule === "builder" || activeModule === "simulation")
) {
setDisplayComponent("zoneProperties");
return;
}
if (!isVersionSaved && activeModule === "simulation") {
if (subModule === "simulations") {
setDisplayComponent("simulations");
return;
}
if (subModule === "mechanics") {
setDisplayComponent("mechanics");
return;
}
if (subModule === "analysis") {
setDisplayComponent("analysis");
return;
}
if (subModule === "resourceManagement") {
setDisplayComponent("resourceManagement");
return;
}
}
setDisplayComponent("none");
}, [
viewVersionHistory,
activeModule,
subModule,
isVersionSaved,
selectedFloorItem,
selectedWall,
selectedFloor,
selectedAisle,
toolMode,
selectedSubCategory,
]);
if (activeModule === "simulation" || activeModule === "builder") {
if (subModule === "resourceManagement") {
setDisplayComponent("resourceManagement");
return;
}
}
const renderComponent = () => {
switch (displayComponent) {
case "versionHistory":
return <VersionHistory />;
case "globalProperties":
return <GlobalProperties />;
case "aisleProperties":
return <AisleProperties />;
case "wallProperties":
return <WallProperties />;
case "floorProperties":
return <FloorProperties />;
case "assetProperties":
return <AssetProperties />;
case "selectedWallProperties":
return <SelectedWallProperties />;
case "selectedFloorProperties":
return <SelectedFloorProperties />;
case "zoneProperties":
return <ZoneProperties />;
case "simulations":
return <Simulations />;
case "mechanics":
return <EventProperties />;
case "analysis":
return <Analysis />;
case "visualization":
return <Visualization />;
case "selectedDecalProperties":
return <DecalProperties />;
case "resourceManagement":
return <ResourceManagement />;
default:
return null;
}
};
if (subModule === "properties" && activeModule !== "visualization") {
if (selectedFloorAsset) {
setDisplayComponent("assetProperties");
return;
}
if (!selectedFloorAsset && !selectedFloor && !selectedAisle && !selectedDecal && selectedWall) {
setDisplayComponent("selectedWallProperties");
return;
}
if (!selectedFloorAsset && !selectedWall && !selectedAisle && !selectedDecal && selectedFloor) {
setDisplayComponent("selectedFloorProperties");
return;
}
if (viewVersionHistory && !selectedFloorAsset && !selectedWall && !selectedAisle && !selectedFloor && !selectedDecal) {
setDisplayComponent("versionHistory");
return;
}
if (!selectedFloorAsset && !selectedFloor && !selectedAisle && !selectedWall && selectedDecal) {
setDisplayComponent("selectedDecalProperties");
return;
}
if (!selectedFloorAsset && !selectedFloor && !selectedWall && !selectedDecal && selectedAisle) {
setDisplayComponent("selectedAisleProperties");
return;
}
if (!selectedFloorAsset && !selectedFloor && !selectedWall && !selectedDecal && !selectedAisle) {
if (toolMode === "Aisle") {
setDisplayComponent("aisleProperties");
return;
}
if (toolMode === "Wall") {
setDisplayComponent("wallProperties");
return;
}
if (toolMode === "Floor") {
setDisplayComponent("floorProperties");
return;
}
setDisplayComponent("globalProperties");
return;
}
}
return (
<div
className={`sidebar-right-wrapper ${
toggleUIRight && (!isVersionSaved || activeModule !== "simulation")
? "open"
: "closed"
}`}
>
<Header />
{toggleUIRight && (
<>
{(!isVersionSaved || activeModule !== "simulation") && (
<div className="sidebar-actions-container">
{activeModule !== "simulation" && (
if (subModule === "zoneProperties" && (activeModule === "builder" || activeModule === "simulation")) {
setDisplayComponent("zoneProperties");
return;
}
setDisplayComponent("none");
}, [viewVersionHistory, activeModule, subModule, isVersionSaved, selectedFloorAsset, selectedWall, selectedFloor, selectedAisle, toolMode, selectedDecal]);
const renderComponent = () => {
switch (displayComponent) {
case "versionHistory":
return <VersionHistory />;
case "globalProperties":
return <GlobalProperties />;
case "aisleProperties":
return <AisleProperties />;
case "wallProperties":
return <WallProperties />;
case "floorProperties":
return <FloorProperties />;
case "assetProperties":
return <AssetProperties />;
case "zoneProperties":
return <ZoneProperties />;
case "selectedWallProperties":
return <SelectedWallProperties />;
case "selectedFloorProperties":
return <SelectedFloorProperties />;
case "selectedDecalProperties":
return <SelectedDecalProperties />;
case "selectedAisleProperties":
return <SelectedAisleProperties />;
case "simulations":
return <Simulations />;
case "mechanics":
return <EventProperties />;
case "analysis":
return <Analysis />;
case "visualization":
return <Visualization />;
case "resourceManagement":
return <ResourceManagement />;
default:
return null;
}
};
return (
<div className={`sidebar-right-wrapper ${toggleUIRight && (!isVersionSaved || activeModule !== "simulation") ? "open" : "closed"}`} onPointerDown={(e) => e.stopPropagation()}>
<Header />
{toggleUIRight && (
<>
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${
subModule === "properties" ? "active" : ""
}`}
onClick={() => {
setSubModule("properties");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">properties</div>
<PropertiesIcon isActive={subModule === "properties"} />
</button>
{(!isVersionSaved || activeModule !== "simulation") && (
<div className="sidebar-actions-container">
{activeModule !== "simulation" && (
<>
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
}`}
onClick={() => {
setSubModule("properties");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">properties</div>
<PropertiesIcon isActive={subModule === "properties"} />
</button>
</>
)}
{activeModule === "simulation" && (
<>
<button
id="sidebar-action-list-simulation"
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
}`}
onClick={() => {
setSubModule("simulations");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">simulations</div>
<SimulationIcon isActive={subModule === "simulations"} />
</button>
<button
id="sidebar-action-list-mechanics"
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
}`}
onClick={() => {
setSubModule("mechanics");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">mechanics</div>
<MechanicsIcon isActive={subModule === "mechanics"} />
</button>
<button
id="sidebar-action-list-analysis"
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
}`}
onClick={() => {
setSubModule("analysis");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">analysis</div>
<AnalysisIcon isActive={subModule === "analysis"} />
</button>
</>
)}
{(activeModule === "builder" ||
activeModule === "simulation") && (
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${subModule === "resourceManagement" ? "active" : ""
}`}
onClick={() => {
setSubModule("resourceManagement");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">Resource Management</div>
<FilePackageIcon
isActive={subModule === "resourceManagement"}
/>
</button>
)}
</div>
)}
{displayComponent !== "none" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
{renderComponent()}
{/* <ResourceManagement /> */}
</div>
</div>
)}
</>
)}
{activeModule === "simulation" && (
<>
<button
id="sidebar-action-list-simulation"
className={`sidebar-action-list ${
subModule === "simulations" ? "active" : ""
}`}
onClick={() => {
setSubModule("simulations");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">simulations</div>
<SimulationIcon isActive={subModule === "simulations"} />
</button>
<button
id="sidebar-action-list-mechanics"
className={`sidebar-action-list ${
subModule === "mechanics" ? "active" : ""
}`}
onClick={() => {
setSubModule("mechanics");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">mechanics</div>
<MechanicsIcon isActive={subModule === "mechanics"} />
</button>
<button
id="sidebar-action-list-analysis"
className={`sidebar-action-list ${
subModule === "analysis" ? "active" : ""
}`}
onClick={() => {
setSubModule("analysis");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">analysis</div>
<AnalysisIcon isActive={subModule === "analysis"} />
</button>
</>
)}
{(activeModule === "builder" ||
activeModule === "simulation") && (
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${
subModule === "resourceManagement" ? "active" : ""
}`}
onClick={() => {
setSubModule("resourceManagement");
setVersionHistoryVisible(false);
}}
>
<div className="tooltip">Resource Management</div>
<FilePackageIcon
isActive={subModule === "resourceManagement"}
/>
</button>
)}
</div>
)}
{displayComponent !== "none" && (
<div className="sidebar-right-container">
<div className="sidebar-right-content-container">
{renderComponent()}
{/* <ResourceManagement /> */}
</div>
</div>
)}
</>
)}
</div>
);
)}
</div>
);
};
export default SideBarRight;

View File

@@ -1,30 +1,46 @@
import React from "react";
interface RotationInputProps {
onChange: (value: string) => void; // Callback for value change
placeholder?: string; // Optional placeholder
type?: string; // Input type (e.g., text, number, email)
heading?: string;
label?: string;
onChange: (value: string) => void;
placeholder?: string;
type?: string;
value?: number;
disabled?: boolean;
min?: number;
max?: number;
step?: number;
}
const RotationInput: React.FC<RotationInputProps> = ({
label = "Rotate :",
heading = "Rotation",
onChange,
placeholder = "Enter value", // Default placeholder
type = "number", // Default type
value = "number",
placeholder = "Enter value",
type = "number",
value,
disabled = false,
min,
max,
step,
}) => {
return (
<div className="custom-input-container">
<div className="header">Rotation</div>
<div className="header">{heading}</div>
<div className="inputs-container" style={{ display: "block" }}>
<div className="input-container">
<div className="custom-input-label">Rotate : </div>
<div className="custom-input-label">{label}</div>
<input
className="custom-input-field"
type={type}
onChange={(e) => onChange(e.target.value)}
placeholder={placeholder}
value={value}
disabled={disabled}
min={min}
max={max}
step={step}
/>
</div>
</div>

View File

@@ -21,24 +21,25 @@ interface TextureItem {
texture: string;
}
export const aisleTextureList: TextureItem[] = [
{ color: "yellow", id: "#FBE50E", brief: "pedestrian walkways", texture: "" },
{ color: "gray", id: "#6F6F7A", brief: "basic", texture: "" },
{ color: "green", id: "#43C06D", brief: "pedestrian walkways", texture: "" },
{ color: "orange", id: "#FF711B", brief: "material flow", texture: "" },
{ color: "blue", id: "#488EF6", brief: "vehicle paths", texture: "" },
{ color: "purple", id: "#AF52DE", brief: "material flow", texture: "" },
{ color: "red", id: "#FF3B30", brief: "safety zone", texture: "" },
{ color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
{ color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
{ color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
];
const AisleProperties: React.FC = () => {
const [collapsePresets, setCollapsePresets] = useState(false);
const [collapseTexture, setCollapseTexture] = useState(true);
const { aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, isFlipped, setAisleType, setAisleColor, setAisleWidth, setDashLength, setGapLength, setDotRadius, setAisleLength, setIsFlipped } = useBuilderStore();
const aisleTextureList: TextureItem[] = [
{ color: "yellow", id: "yellow", brief: "pedestrian walkways", texture: "" },
{ color: "gray", id: "gray", brief: "basic", texture: "" },
{ color: "green", id: "green", brief: "pedestrian walkways", texture: "" },
{ color: "orange", id: "orange", brief: "material flow", texture: "" },
{ color: "blue", id: "blue", brief: "vehicle paths", texture: "" },
{ color: "purple", id: "purple", brief: "material flow", texture: "" },
{ color: "red", id: "red", brief: "safety zone", texture: "" },
{ color: "bright green", id: "#66FF00", brief: "safety zone", texture: "" },
{ color: "yellow-black", id: "yellow-black", brief: "utility aisles", texture: "" },
{ color: "white-black", id: "white-black", brief: "utility aisles", texture: "" },
];
const aisleTypes: {
name: string;
@@ -97,27 +98,27 @@ const AisleProperties: React.FC = () => {
const dashLengthValue = useMemo(() => {
return dashLength.toString();
}, [aisleType, dashLength]);
}, [dashLength]);
const dotRadiusValue = useMemo(() => {
return dotRadius.toString();
}, [aisleType, dotRadius]);
}, [dotRadius]);
const gapLengthValue = useMemo(() => {
return gapLength.toString();
}, [aisleType, gapLength]);
}, [gapLength]);
const aisleWidthValue = useMemo(() => {
return aisleWidth.toString();
}, [aisleType, aisleWidth]);
}, [aisleWidth]);
const aisleLengthValue = useMemo(() => {
return aisleLength.toString();
}, [aisleType, aisleLength]);
}, [aisleLength]);
const aisleIsFlipped = useMemo(() => {
return isFlipped;
}, [aisleType, isFlipped]);
}, [isFlipped]);
const renderAdvancedProperties = () => {
switch (aisleType) {
@@ -281,11 +282,16 @@ const AisleProperties: React.FC = () => {
<button
key={val.id}
title={val.brief || val.id}
className={`aisle-list ${aisleColor === val.color ? "selected" : ""}`}
className={`aisle-list ${aisleColor === val.id ? "selected" : ""}`}
onClick={() => setAisleColor(val.id)}
aria-pressed={aisleColor === val.id}
>
<div className="texture-display">{val.texture}</div>
<div
className={`texture-display ${val.id}`}
style={{ background: val.id }}
>
{val.texture}
</div>
<div className="aisle-color">{val.color}</div>
<div className="aisle-brief">{`( ${val.brief} )`}</div>
</button>

View File

@@ -4,134 +4,138 @@ import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { RemoveIcon } from "../../../icons/ExportCommonIcons";
import PositionInput from "../customInput/PositionInputs";
import RotationInput from "../customInput/RotationInput";
import {
useSelectedFloorItem,
useObjectPosition,
useObjectRotation,
} from "../../../../store/builder/store";
import { useObjectPosition, useObjectRotation } from "../../../../store/builder/store";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
interface UserData {
id: number;
label: string;
value: string;
id: number;
label: string;
value: string;
}
const AssetProperties: React.FC = () => {
const [userData, setUserData] = useState<UserData[]>([]);
const { selectedFloorItem } = useSelectedFloorItem();
const { objectPosition } = useObjectPosition();
const { objectRotation } = useObjectRotation();
const { assetStore } = useSceneContext();
const { assets, setCurrentAnimation } = assetStore();
const { loopAnimation } = useBuilderStore();
const [hoveredIndex, setHoveredIndex] = useState<any>(null);
const [userData, setUserData] = useState<UserData[]>([]);
const { objectPosition } = useObjectPosition();
const { objectRotation } = useObjectRotation();
const { assetStore } = useSceneContext();
const { assets, setCurrentAnimation } = assetStore();
const { loopAnimation, selectedFloorAsset } = useBuilderStore();
const [hoveredIndex, setHoveredIndex] = useState<any>(null);
const handleAddUserData = () => {
setUserData([]);
};
const handleAddUserData = () => {
setUserData([]);
};
const handleUserDataChange = (id: number, newValue: string) => {};
const handleUserDataChange = (id: number, newValue: string) => { };
const handleRemoveUserData = (id: number) => {};
const handleRemoveUserData = (id: number) => { };
const handleAnimationClick = (animation: string) => {
if (selectedFloorItem) {
setCurrentAnimation(
selectedFloorItem.uuid,
animation,
true,
loopAnimation,
true
);
}
};
const handleAnimationClick = (animation: string) => {
if (selectedFloorAsset) {
setCurrentAnimation(
selectedFloorAsset.uuid,
animation,
true,
loopAnimation,
true
);
}
};
if (!selectedFloorItem) return null;
if (!selectedFloorAsset) return null;
return (
<div className="asset-properties-container">
{/* Name */}
<div className="header">{selectedFloorItem.userData.modelName}</div>
<section>
{objectPosition && (
<PositionInput
onChange={() => {}}
value1={parseFloat(objectPosition.x.toFixed(5))}
value2={parseFloat(objectPosition.z.toFixed(5))}
/>
)}
{objectRotation && (
<RotationInput
onChange={() => {}}
value={parseFloat(objectRotation.y.toFixed(5))}
/>
)}
</section>
return (
<div className="asset-properties-container">
{/* Name */}
<div className="header">{selectedFloorAsset.userData.modelName}</div>
<section>
{objectPosition && (
<PositionInput
disabled={true}
onChange={() => { }}
value1={parseFloat(objectPosition.x.toFixed(5))}
value2={parseFloat(objectPosition.z.toFixed(5))}
/>
)}
{objectRotation && (
<RotationInput
disabled={true}
onChange={() => { }}
value={parseFloat(objectRotation.y.toFixed(5))}
/>
)}
</section>
<div className="header">Render settings</div>
<section>
<InputToggle inputKey="visible" label="Visible" />
<InputToggle inputKey="frustumCull" label="Frustum cull" />
</section>
<div className="header">Render settings</div>
<section>
<InputToggle inputKey="visible" label="Visible" />
<InputToggle inputKey="frustumCull" label="Frustum cull" />
</section>
<section>
<div className="header">User Data</div>
{userData.map((data) => (
<div className="input-container">
<InputWithDropDown
key={data.id}
label={data.label}
value={data.value}
editableLabel
onChange={(newValue) => handleUserDataChange(data.id, newValue)}
/>
<div
className="remove-button"
onClick={() => handleRemoveUserData(data.id)}
>
<RemoveIcon />
</div>
</div>
))}
<section>
<div className="header">User Data</div>
{userData.map((data, i) => (
<div className="input-container" key={i}>
<InputWithDropDown
key={data.id}
label={data.label}
value={data.value}
editableLabel
onChange={(newValue) => handleUserDataChange(data.id, newValue)}
/>
<div
className="remove-button"
onClick={() => handleRemoveUserData(data.id)}
>
<RemoveIcon />
</div>
</div>
))}
{/* Add new user data */}
<div className="optimize-button" onClick={handleAddUserData}>
+ Add
{/* Add new user data */}
<div className="optimize-button" onClick={handleAddUserData}>
+ Add
</div>
</section>
<div className="header">Animations</div>
<section className="animations-lists">
{assets.map((asset, i) => {
if (asset.modelUuid !== selectedFloorAsset.uuid || !asset.animations)
return (
i === 0 && (
<div className="no-animation" key={i}>
Looks like there are no preset animations yet. Stay tuned for
future additions!
</div>
)
);
return asset.animations.map((animation, index) => (
<div key={index} className="animations-list-wrapper">
<div
onClick={() => handleAnimationClick(animation)}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
className="animations-list"
style={{
background:
hoveredIndex === index
? "var(--background-color-button)"
: "var(--background-color)",
color:
hoveredIndex === index ? "var(--text-button-color)" : "",
}}
>
{animation.charAt(0).toUpperCase() +
animation.slice(1).toLowerCase()}
</div>
</div>
));
})}
</section>
</div>
</section>
<div className="header">Animations</div>
<section className="animations-lists">
{assets.map((asset) => {
if (asset.modelUuid !== selectedFloorItem.uuid || !asset.animations)
return null;
return asset.animations.map((animation, index) => (
<div key={index} className="animations-list-wrapper">
<div
onClick={() => handleAnimationClick(animation)}
onMouseEnter={() => setHoveredIndex(index)}
onMouseLeave={() => setHoveredIndex(null)}
className="animations-list"
style={{
background:
hoveredIndex === index
? "var(--background-color-button)"
: "var(--background-color)",
color:
hoveredIndex === index ? "var(--text-button-color)" : "",
}}
>
{animation.charAt(0).toUpperCase() +
animation.slice(1).toLowerCase()}
</div>
</div>
));
})}
</section>
</div>
);
);
};
export default AssetProperties;

View File

@@ -1,53 +0,0 @@
import {
LayeringBottomIcon,
LayeringTopIcon,
} from "../../../icons/ExportCommonIcons";
import InputRange from "../../../ui/inputs/InputRange";
import RotationInput from "../customInput/RotationInput";
import Vector3Input from "../customInput/Vector3Input";
const DecalProperties = () => {
return (
<div className="decal-transformation-container">
<div className="header">Decal Propertis</div>
<section>
<RotationInput
onChange={() => {}}
value={10}
/>
<Vector3Input
onChange={(value) => console.log(value)}
header="Scale"
value={[0, 0, 0] as [number, number, number]}
/>
</section>
<section>
<InputRange
label="Opacity"
value={1}
min={0}
step={0.1}
max={1}
onChange={(value: number) => console.log(value)}
key={"6"}
/>
<div className="transformation-wrapper opacity">
<div className="transformation-header">Layering</div>
<div className="layers-list">
<button className="layer-move-btn">
<LayeringBottomIcon />
</button>
<button className="layer-move-btn">
<LayeringTopIcon />
</button>
</div>
</div>
</section>
</div>
);
};
export default DecalProperties;

View File

@@ -3,27 +3,13 @@ import InputRange from "../../../ui/inputs/InputRange";
import InputToggle from "../../../ui/inputs/InputToggle";
import { AIIcon } from "../../../icons/ExportCommonIcons";
import LabeledButton from "../../../ui/inputs/LabledButton";
import {
useAzimuth,
useElevation,
useLimitDistance,
useRenderDistance,
useResetCamera,
useRoofVisibility,
useSelectedWallItem,
useShadows,
useSocketStore,
useTileDistance,
useToggleView,
useWallVisibility,
} from "../../../../store/builder/store";
import { useAzimuth, useElevation, useLimitDistance, useRenderDistance, useResetCamera, useRoofVisibility, useShadows, useSocketStore, useTileDistance, useToggleView, useWallVisibility } from "../../../../store/builder/store";
import { setEnvironment } from "../../../../services/factoryBuilder/environment/setEnvironment";
import * as CONSTANTS from "../../../../types/world/worldConstants";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../../functions/getUserData";
const GlobalProperties: React.FC = () => {
const { toggleView, setToggleView } = useToggleView();
const { selectedWallItem, setSelectedWallItem } = useSelectedWallItem();
const { roofVisibility, setRoofVisibility } = useRoofVisibility();
const { wallVisibility, setWallVisibility } = useWallVisibility();
const { shadows, setShadows } = useShadows();
@@ -90,16 +76,6 @@ const GlobalProperties: React.FC = () => {
setDistance(value);
setRenderDistance(value);
}
function updateGridDistance(value: number) {
setGridDistance(value);
// setGridValue({ size: value * 100, divisions: (value * 100) / 4 });
// setPlaneValue({ height: value * 100, width: value * 100 });
}
function updatedGrid(value: number) {
// console.log(" (value * 100) / 4 : ", (value * 100) / 4);
setGridValue({ size: value * 100, divisions: (value * 100) / 4 });
setPlaneValue({ height: value * 100, width: value * 100 });
}
const updatedDist = async (value: number) => {
setRenderDistance(value);
@@ -145,7 +121,7 @@ const GlobalProperties: React.FC = () => {
setRoofVisibility(!roofVisibility); // Toggle roof visibility
};
// Function to toggle wall visibility
const changeWallVisibility = async () => {
//using REST
const data = await setEnvironment(
@@ -203,20 +179,10 @@ const GlobalProperties: React.FC = () => {
const toggleResetCamera = () => {
if (!toggleView) {
setResetCamera(true); // Trigger reset camera action
setResetCamera(true);
}
};
// function changeRenderDistance(e: any) {
// if (parseInt(e.target.value) < 20) {
// setRenderDistance(20);
// } else if (parseInt(e.target.value) > 75) {
// setRenderDistance(75);
// } else {
// setRenderDistance(parseInt(e.target.value));
// }
// }
return (
<div className="global-properties-container">
<section>
@@ -253,16 +219,10 @@ const GlobalProperties: React.FC = () => {
/>
<div className="split"></div>
{/* //visibleEdgeColor={CONSTANTS.outlineConfig.assetDeleteColor} */}
<InputToggle
inputKey="4"
label="Limit Render Distance"
value={limitDistance}
// onClick={() => {
// setLimitDistance(!limitDistance);
// // setDistance(75);
// // setRenderDistance(75);
// }}
onClick={async () => {
await limitRenderDistance(); // Call the function here
}}
@@ -277,26 +237,6 @@ const GlobalProperties: React.FC = () => {
onPointerUp={updatedDist}
key={"6"}
/>
{/* <div className="split"></div>
<InputToggle
inputKey="6"
label="Display Grid"
value={limitGridDistance}
onClick={() => {
setLimitGridDistance(!limitGridDistance);
}}
/>
<InputRange
label="Tile Distance"
disabled={!limitGridDistance}
value={gridDistance}
key={"7"}
min={1}
max={5}
onChange={(value: number) => updateGridDistance(value)}
onPointerUp={updatedGrid}
/> */}
</section>
</div>
);

View File

@@ -0,0 +1,536 @@
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import InputWithDropDown from "../../../ui/inputs/InputWithDropDown";
import { ArrowIcon } from "../../../icons/ExportCommonIcons";
// image imports
import Arc from "../../../../assets/image/aisleTypes/Arc.png";
import Arrow from "../../../../assets/image/aisleTypes/Arrow.png";
import Arrows from "../../../../assets/image/aisleTypes/Arrows.png";
import Circle from "../../../../assets/image/aisleTypes/Circle.png";
import Dashed from "../../../../assets/image/aisleTypes/Dashed.png";
import Directional from "../../../../assets/image/aisleTypes/Directional.png";
import Dotted from "../../../../assets/image/aisleTypes/Dotted.png";
import Solid from "../../../../assets/image/aisleTypes/Solid.png";
import InputToggle from "../../../ui/inputs/InputToggle";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
import { useSocketStore } from "../../../../store/builder/store";
import { getUserData } from "../../../../functions/getUserData";
import { aisleTextureList } from "./AisleProperties";
import { upsertAisleApi } from "../../../../services/factoryBuilder/aisle/upsertAisleApi";
const SelectedAisleProperties: React.FC = () => {
const [collapsePresets, setCollapsePresets] = useState(false);
const [collapseTexture, setCollapseTexture] = useState(true);
const { aisleStore } = useSceneContext();
const { getAisleById, updateAisle, setDashedAisleProperties, setDottedAisleProperties, setArrowsAisleProperties, setArcAisleWidth, setColor } = aisleStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { socket } = useSocketStore();
const { userId, organization } = getUserData();
const { projectId } = useParams();
const [selectedAisleData, setSelectedAisleData] = useState<Aisle | undefined>();
const { selectedAisle, setSelectedAisle } = useBuilderStore();
useEffect(() => {
const aisleData = getAisleById(selectedAisle?.aisleMesh?.uuid || "");
setSelectedAisleData(aisleData);
}, [selectedAisle, getAisleById]);
if (!selectedAisleData) return null;
const updateBackend = (updatedAisle: Aisle) => {
if (updatedAisle && projectId) {
if (!socket?.connected) {
// API
upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
} else {
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
}
socket.emit('v1:model-aisle:add', data);
}
}
}
const aisleTypes: {
name: string;
type: AisleTypes;
id: string;
thumbnail: string;
}[] = [
{ name: "Solid", type: "solid-aisle", id: "1", thumbnail: Solid },
{ name: "Dotted", type: "dotted-aisle", id: "2", thumbnail: Dotted },
{ name: "Dashed", type: "dashed-aisle", id: "3", thumbnail: Dashed },
{ name: "Arrow", type: "arrow-aisle", id: "4", thumbnail: Arrow },
{ name: "Continuous Arrows", type: "arrows-aisle", id: "5", thumbnail: Arrows },
{ name: "Directional", type: "junction-aisle", id: "6", thumbnail: Directional },
{ name: "Arc", type: "arc-aisle", id: "7", thumbnail: Arc },
{ name: "Circle", type: "circle-aisle", id: "8", thumbnail: Circle },
];
const createAisleTypeObject = (newType: AisleTypes, currentType: AisleType): AisleType => {
switch (newType) {
case 'solid-aisle':
return {
aisleType: 'solid-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
} as SolidAisle;
case 'dashed-aisle':
return {
aisleType: 'dashed-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
dashLength: 'dashLength' in currentType ? (currentType as DashedAisle).dashLength : 0.5,
gapLength: 'gapLength' in currentType ? (currentType as DashedAisle).gapLength : 0.3
} as DashedAisle;
case 'dotted-aisle':
return {
aisleType: 'dotted-aisle',
aisleColor: currentType.aisleColor,
dotRadius: 'dotRadius' in currentType ? (currentType as DottedAisle).dotRadius : 0.1,
gapLength: 'gapLength' in currentType ? (currentType as DottedAisle).gapLength : 0.3
} as DottedAisle;
case 'arrow-aisle':
return {
aisleType: 'arrow-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
} as ArrowAisle;
case 'arrows-aisle':
return {
aisleType: 'arrows-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
aisleLength: 'aisleLength' in currentType ? (currentType as ArrowsAisle).aisleLength : 0.6,
gapLength: 'gapLength' in currentType ? (currentType as ArrowsAisle).gapLength : 0.3
} as ArrowsAisle;
case 'arc-aisle':
return {
aisleType: 'arc-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
isFlipped: 'isFlipped' in currentType ? (currentType as ArcAisle).isFlipped : false
} as ArcAisle;
case 'circle-aisle':
return {
aisleType: 'circle-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1
} as CircleAisle;
case 'junction-aisle':
return {
aisleType: 'junction-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 'aisleWidth' in currentType ? currentType.aisleWidth : 0.1,
isFlipped: 'isFlipped' in currentType ? (currentType as JunctionAisle).isFlipped : false
} as JunctionAisle;
default:
return {
aisleType: 'solid-aisle',
aisleColor: currentType.aisleColor,
aisleWidth: 0.1
} as SolidAisle;
}
};
const handleAisleTypeChange = (newType: AisleTypes) => {
if (!selectedAisle?.aisleData) return;
const newAisleType = createAisleTypeObject(newType, selectedAisleData.type);
const updatedAisle = updateAisle(selectedAisleData.aisleUuid, {
type: newAisleType
});
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: null });
setSelectedAisleData({
...selectedAisleData,
type: newAisleType
});
if (updatedAisle) {
updateBackend(updatedAisle);
}
};
const handleColorChange = (value: AisleColors) => {
const updatedAisle = setColor(selectedAisleData.aisleUuid, value);
setSelectedAisleData({
...selectedAisleData,
type: {
...selectedAisleData.type,
aisleColor: value
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
};
const handleAisleWidthChange = (value: string) => {
const width = parseFloat(value);
if (!isNaN(width) && selectedAisleData.type.aisleType !== 'dotted-aisle') {
const updatedAisle = updateAisle(selectedAisleData.aisleUuid, {
type: {
...selectedAisleData.type,
aisleWidth: width
}
});
setSelectedAisleData({
...selectedAisleData,
type: {
...selectedAisleData.type,
aisleWidth: width
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleDashLengthChange = (value: string) => {
const length = parseFloat(value);
if (!isNaN(length) && selectedAisleData.type.aisleType === 'dashed-aisle') {
const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, {
dashLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DashedAisle),
dashLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleGapLengthChange = (value: string) => {
const length = parseFloat(value);
if (!isNaN(length) && (selectedAisleData.type.aisleType === 'dashed-aisle' || selectedAisleData.type.aisleType === 'dotted-aisle' || selectedAisleData.type.aisleType === 'arrows-aisle')) {
if (selectedAisleData.type.aisleType === 'dashed-aisle') {
const updatedAisle = setDashedAisleProperties(selectedAisleData.aisleUuid, {
gapLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DashedAisle),
gapLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
} else if (selectedAisleData.type.aisleType === 'dotted-aisle') {
const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, {
gapLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DottedAisle),
gapLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
} else if (selectedAisleData.type.aisleType === 'arrows-aisle') {
const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, {
gapLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as ArrowsAisle),
gapLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
}
};
const handleDotRadiusChange = (value: string) => {
const radius = parseFloat(value);
if (!isNaN(radius) && selectedAisleData.type.aisleType === 'dotted-aisle') {
const updatedAisle = setDottedAisleProperties(selectedAisleData.aisleUuid, {
dotRadius: radius
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as DottedAisle),
dotRadius: radius
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleAisleLengthChange = (value: string) => {
const length = parseFloat(value);
if (!isNaN(length) && selectedAisleData.type.aisleType === 'arrows-aisle') {
const updatedAisle = setArrowsAisleProperties(selectedAisleData.aisleUuid, {
aisleLength: length
});
setSelectedAisleData({
...selectedAisleData,
type: {
...(selectedAisleData.type as ArrowsAisle),
aisleLength: length
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const handleIsFlippedChange = () => {
if (selectedAisleData.type.aisleType === 'arc-aisle' || selectedAisleData.type.aisleType === 'junction-aisle') {
const currentType = selectedAisleData.type as ArcAisle | JunctionAisle;
const currentFlipped = currentType.isFlipped || false;
const updatedAisle = setArcAisleWidth(selectedAisleData.aisleUuid, {
isFlipped: !currentFlipped
});
setSelectedAisleData({
...selectedAisleData,
type: {
...currentType,
isFlipped: !currentFlipped
}
})
if (updatedAisle) {
updateBackend(updatedAisle);
}
}
};
const renderAdvancedProperties = () => {
switch (selectedAisleData.type.aisleType) {
case 'dashed-aisle':
const dashedType = selectedAisleData.type as DashedAisle;
return (
<>
<InputWithDropDown
label="Dash Length"
value={`${dashedType.dashLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleDashLengthChange}
/>
<InputWithDropDown
label="Gap Length"
value={`${dashedType.gapLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleGapLengthChange}
/>
</>
);
case 'dotted-aisle':
const dottedType = selectedAisleData.type as DottedAisle;
return (
<>
<InputWithDropDown
label="Dot Radius"
value={`${dottedType.dotRadius}`}
min={0.1}
step={0.1}
max={2}
onChange={handleDotRadiusChange}
/>
<InputWithDropDown
label="Gap Length"
value={`${dottedType.gapLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleGapLengthChange}
/>
</>
);
case 'arrows-aisle':
const arrowsType = selectedAisleData.type as ArrowsAisle;
return (
<>
<InputWithDropDown
label="Arrow Length"
value={`${arrowsType.aisleLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleAisleLengthChange}
/>
<InputWithDropDown
label="Gap Length"
value={`${arrowsType.gapLength}`}
min={0.1}
step={0.1}
max={2}
onChange={handleGapLengthChange}
/>
</>
);
case 'junction-aisle':
case 'arc-aisle':
const flippedType = selectedAisleData.type as ArcAisle | JunctionAisle;
return (
<InputToggle
inputKey="Flip Aisle"
label="Flip Aisle"
value={flippedType.isFlipped || false}
onClick={handleIsFlippedChange}
/>
);
default:
return null;
}
};
return (
<div className="aisle-properties-container">
<div className="header">Properties</div>
{/* Basic Properties */}
<section>
{selectedAisleData.type.aisleType !== 'dotted-aisle' &&
<InputWithDropDown
label="Aisle Width"
value={`${selectedAisleData.type.aisleWidth || 0.5}`}
min={0.1}
step={0.1}
max={2}
onChange={handleAisleWidthChange}
/>
}
{renderAdvancedProperties()}
</section>
{/* Presets */}
<section>
<button
className="header"
onClick={() => setCollapsePresets(!collapsePresets)}
aria-expanded={!collapsePresets}
>
<div className="value">Presets</div>
<div className="icon">
<ArrowIcon />
</div>
</button>
{!collapsePresets && (
<div className="presets-list-container">
{aisleTypes.map((val) => (
<div className="preset-list" key={val.id}>
<button
className={`thumbnail ${selectedAisleData.type.aisleType === val.type ? "selected" : ""}`}
title={val.name}
onClick={() => handleAisleTypeChange(val.type)}
>
<img src={val.thumbnail} alt="" />
</button>
</div>
))}
</div>
)}
</section>
{/* Texture */}
<section>
<button
className="header"
onClick={() => setCollapseTexture(!collapseTexture)}
aria-expanded={!collapseTexture}
>
<div className="value">Aisle Texture</div>
<div className="icon" style={{ rotate: collapseTexture ? "" : "-90deg" }}>
<ArrowIcon />
</div>
</button>
{collapseTexture && (
<div className="aisle-texture-container">
{aisleTextureList.map((val) => (
<button
key={val.id}
title={val.brief || val.id}
className={`aisle-list ${selectedAisleData.type.aisleColor === val.id ? "selected" : ""}`}
onClick={() => handleColorChange(val.id)}
aria-pressed={selectedAisleData.type.aisleColor === val.id}
>
<div
className={`texture-display ${val.id}`}
style={{ background: val.id }}
>
{val.texture}
</div>
<div className="aisle-color">{val.color}</div>
<div className="aisle-brief">{`( ${val.brief} )`}</div>
</button>
))}
</div>
)}
</section>
</div>
);
};
export default SelectedAisleProperties;

View File

@@ -0,0 +1,194 @@
import { useParams } from "react-router-dom";
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons";
import { useSocketStore } from "../../../../store/builder/store";
import InputRange from "../../../ui/inputs/InputRange";
import { getUserData } from "../../../../functions/getUserData";
import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
const SelectedDecalProperties = () => {
const { selectedDecal, setSelectedDecal } = useBuilderStore();
const { wallStore, floorStore } = useSceneContext();
const { updateDecal: updateDecalInWall } = wallStore();
const { updateDecal: updateDecalInFloor } = floorStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { socket } = useSocketStore();
const updateBackend = (updatedData: Wall | Floor) => {
if ('wallUuid' in updatedData) {
if (projectId && updatedData) {
if (!socket?.connected) {
// API
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
} else {
// SOCKET
const data = {
wallData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
} else if ('floorUuid' in updatedData) {
if (projectId && updatedData) {
if (!socket?.connected) {
// API
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
} else {
// SOCKET
const data = {
floorData: updatedData,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}
}
const handleRotationChange = (value: number) => {
if (!selectedDecal) return;
const updatedDecal = { ...selectedDecal.decalData, decalRotation: value };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ('wallUuid' in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
}
const handleScaleChange = (value: number) => {
if (!selectedDecal) return;
const updatedDecal = { ...selectedDecal.decalData, decalScale: value };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ('wallUuid' in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
}
const handleOpacityChange = (value: number) => {
if (!selectedDecal) return;
const updatedDecal = { ...selectedDecal.decalData, decalOpacity: value };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ('wallUuid' in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
}
const handleLayerChange = (direction: "up" | "down") => {
if (!selectedDecal) return;
const position: [number, number, number] = [...(selectedDecal.decalData.decalPosition || [0, 0, 0]),];
if (direction === "up") {
position[2] = Math.abs(position[2]);
} else {
position[2] = -Math.abs(position[2]);
}
const updatedDecal: Decal = { ...selectedDecal.decalData, decalPosition: position, };
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
if ("wallUuid" in selectedDecal.decalData.decalType) {
const updatedWall = updateDecalInWall(updatedDecal.decalUuid, updatedDecal);
if (updatedWall) updateBackend(updatedWall);
} else if ("floorUuid" in selectedDecal.decalData.decalType) {
const updatedFloor = updateDecalInFloor(updatedDecal.decalUuid, updatedDecal);
if (updatedFloor) updateBackend(updatedFloor);
}
};
if (!selectedDecal) return null;
return (
<div className="decal-transformation-container">
<div className="header">Decal Properties</div>
<section>
<InputRange
label="Rotation"
value={selectedDecal.decalData.decalRotation || 0}
min={0}
max={360}
step={1}
onChange={(value: number) => handleRotationChange(value)}
/>
<InputRange
label="Scale"
value={selectedDecal.decalData.decalScale || 1}
min={0.1}
max={2}
step={0.1}
onChange={(value: number) => handleScaleChange(value)}
/>
</section>
<section>
<InputRange
label="Opacity"
value={selectedDecal.decalData.decalOpacity || 1}
min={0.1}
step={0.1}
max={1}
onChange={(value: number) => handleOpacityChange(value)}
/>
<div className="transformation-wrapper opacity">
<div className="transformation-header">Layering</div>
<div className="layers-list">
<button
className="layer-move-btn"
onClick={() => handleLayerChange("down")}
>
<LayeringBottomIcon />
</button>
<button
className="layer-move-btn"
onClick={() => handleLayerChange("up")}
>
<LayeringTopIcon />
</button>
</div>
</div>
</section>
</div>
);
};
export default SelectedDecalProperties;

View File

@@ -10,7 +10,7 @@ import { getUserData } from "../../../../functions/getUserData";
import { useSocketStore } from "../../../../store/builder/store";
import { materials } from "./FloorProperties";
// import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
const SelectedFloorProperties = () => {
const [depth, setDepth] = useState("");
@@ -43,22 +43,25 @@ const SelectedFloorProperties = () => {
if (!isNaN(parsed) && floor) {
const updatedFloor = updateFloor(floor.floorUuid, { floorDepth: parsed });
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
}
};
@@ -69,10 +72,40 @@ const SelectedFloorProperties = () => {
if (!isNaN(parsed) && floor) {
const updatedFloor = updateFloor(floor.floorUuid, { bevelStrength: parsed });
if (projectId) {
if (!socket?.connected) {
// API
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}
};
const handleIsBeveledToggle = () => {
setIsBeveled(!isBeveled);
if (!floor) return;
const updatedFloor = updateFloor(floor.floorUuid, { isBeveled: !floor.isBeveled });
if (projectId) {
if (!socket?.connected) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
@@ -87,30 +120,6 @@ const SelectedFloorProperties = () => {
socket.emit('v1:model-Floor:add', data);
}
}
};
const handleIsBeveledToggle = () => {
setIsBeveled(!isBeveled);
if (!floor) return;
const updatedFloor = updateFloor(floor.floorUuid, { isBeveled: !floor.isBeveled });
if (projectId) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
};
@@ -119,22 +128,25 @@ const SelectedFloorProperties = () => {
const key = activeSurface === "top" ? "topMaterial" : "sideMaterial";
const updatedFloor = updateFloor(floor.floorUuid, { [key]: material.textureId });
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
};

View File

@@ -11,7 +11,7 @@ import { useParams } from "react-router-dom";
import { getUserData } from "../../../../functions/getUserData";
import { useSocketStore } from "../../../../store/builder/store";
// import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
const SelectedWallProperties = () => {
const [height, setHeight] = useState("");
@@ -47,22 +47,25 @@ const SelectedWallProperties = () => {
if (!isNaN(height) && wall) {
const updatedWall = updateWall(wall.wallUuid, { wallHeight: height });
if (updatedWall && projectId) {
if (!socket?.connected) {
// API
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
socket.emit('v1:model-Wall:add', data);
}
}
};
@@ -73,10 +76,41 @@ const SelectedWallProperties = () => {
if (!isNaN(thickness) && wall) {
const updatedWall = updateWall(wall.wallUuid, { wallThickness: thickness });
if (updatedWall && projectId) {
if (!socket?.connected) {
// API
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
}
};
const handleSelectMaterial = (material: { textureId: string; textureName: string }) => {
if (!wall) return;
const updated = (activeSide === "side1" ? { insideMaterial: material.textureId } : { outsideMaterial: material.textureId })
const updatedWall = updateWall(wall.wallUuid, updated);
if (updatedWall && projectId) {
if (!socket?.connected) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
@@ -93,31 +127,6 @@ const SelectedWallProperties = () => {
}
};
const handleSelectMaterial = (material: { textureId: string; textureName: string }) => {
if (!wall) return;
const updated = (activeSide === "side1" ? { insideMaterial: material.textureId } : { outsideMaterial: material.textureId })
const updatedWall = updateWall(wall.wallUuid, updated);
if (updatedWall && projectId) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
};
if (!wall) return null;
const selectedMaterials = {

View File

@@ -1,190 +1,225 @@
import { useEffect, useState } from 'react'
import { ClockThreeIcon, LocationPinIcon, TargetIcon } from '../../../../icons/ExportCommonIcons'
import { useSceneContext } from '../../../../../modules/scene/sceneContext';
import { useProductContext } from '../../../../../modules/simulation/products/productContext';
import RenameInput from '../../../../ui/inputs/RenameInput';
import { useEffect, useState } from "react";
import {
ClockThreeIcon,
LocationPinIcon,
TargetIcon,
} from "../../../../icons/ExportCommonIcons";
import { useSceneContext } from "../../../../../modules/scene/sceneContext";
import RenameInput from "../../../../ui/inputs/RenameInput";
import { useResourceManagementId } from "../../../../../store/builder/store";
import { getAssetThumbnail } from "../../../../../services/factoryBuilder/asset/assets/getAssetThumbnail";
// import NavigateCatagory from '../NavigateCatagory'
const Hrm = () => {
const [selectedCard, setSelectedCard] = useState(0);
const [workers, setWorkers] = useState<any[]>([]);
const { productStore } = useSceneContext();
const { products, getProductById } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
useEffect(() => {
if (selectedProduct) {
const productDetails = getProductById(selectedProduct.productUuid);
const workerDetails = productDetails?.eventDatas || [];
const formattedWorkers = workerDetails
.filter((worker: any) => worker.type === "human")
.map((worker: any, index: number) => ({
employee: {
image: "",
name: worker.modelName,
employee_id: `HR-${204 + index}`,
status: "Active",
},
task: {
status: "Ongoing",
title: worker.taskTitle || "No Task Assigned",
location: {
floor: worker.floor || 0,
zone: worker.zone || "N/A"
},
planned_time_hours: worker.plannedTime || 0,
time_spent_hours: worker.timeSpent || 0,
total_tasks: worker.totalTasks || 0,
completed_tasks: worker.completedTasks || 0
},
actions: [
"Assign Task",
"Reassign Task",
"Pause",
"Emergency Stop"
],
location: `Floor ${worker.floor || "-"} . Zone ${worker.zone || "-"}`
}));
setWorkers(formattedWorkers);
}
}, [selectedProduct, getProductById]);
useEffect(() => {
// console.log("Workers data updated:", workers);
}, [workers]);
// const employee_details = [
// {
// "employee": {
// image: "",
// "name": "John Doe",
// "employee_id": "HR-204",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Inspecting Machine X",
// "location": {
// "floor": 4,
// "zone": "B"
// },
// "planned_time_hours": 6,
// "time_spent_hours": 2,
// "total_tasks": 12,
// "completed_tasks": 3
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Alice Smith",
// "employee_id": "HR-205",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Calibrating Sensor Y",
// "location": {
// "floor": 2,
// "zone": "A"
// },
// "planned_time_hours": 4,
// "time_spent_hours": 1.5,
// "total_tasks": 10,
// "completed_tasks": 2
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Michael Lee",
// "employee_id": "HR-206",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Testing Conveyor Belt Z",
// "location": {
// "floor": 5,
// "zone": "C"
// },
// "planned_time_hours": 5,
// "time_spent_hours": 3,
// "total_tasks": 8,
// "completed_tasks": 5
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// ]
function handleRenameWorker(newName: string) {
// console.log('newName: ', newName);
const [selectedCard, setSelectedCard] = useState(0);
const [workers, setWorkers] = useState<any[]>([]);
const { setResourceManagementId } = useResourceManagementId();
const { assetStore } = useSceneContext();
const { assets: allAssets } = assetStore();
async function getAsset(assetId: string) {
let thumbnail = await getAssetThumbnail(assetId);
if (thumbnail.thumbnail) {
let assetImage = thumbnail.thumbnail;
return assetImage;
}
}
useEffect(() => {
if (allAssets.length > 0) {
const fetchWorkers = async () => {
const humans = allAssets.filter(
(worker: any) => worker.eventData.type === "Human"
);
return (
<>
{/* <NavigateCatagory
const formattedWorkers = await Promise.all(
humans.map(async (worker: any, index: number) => {
const assetImage = await getAsset(worker.assetId);
return {
employee: {
image: assetImage,
name: worker.modelName,
modelId: worker.modelUuid,
employee_id: `HR-${204 + index}`,
status: "Active",
},
task: {
status: "Ongoing",
title: worker.taskTitle ?? "No Task Assigned",
location: {
floor: worker.floor ?? 0,
zone: worker.zone ?? "N/A",
},
planned_time_hours: worker.plannedTime ?? 0,
time_spent_hours: worker.timeSpent ?? 0,
total_tasks: worker.totalTasks ?? 0,
completed_tasks: worker.completedTasks ?? 0,
},
actions: [
"Assign Task",
"Reassign Task",
"Pause",
"Emergency Stop",
],
location: `Floor ${worker.floor || "-"} . Zone ${
worker.zone || "-"
}`,
};
})
);
setWorkers(formattedWorkers);
};
fetchWorkers();
}
}, [allAssets]);
// const employee_details = [
// {
// "employee": {
// image: "",
// "name": "John Doe",
// "employee_id": "HR-204",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Inspecting Machine X",
// "location": {
// "floor": 4,
// "zone": "B"
// },
// "planned_time_hours": 6,
// "time_spent_hours": 2,
// "total_tasks": 12,
// "completed_tasks": 3
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Alice Smith",
// "employee_id": "HR-205",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Calibrating Sensor Y",
// "location": {
// "floor": 2,
// "zone": "A"
// },
// "planned_time_hours": 4,
// "time_spent_hours": 1.5,
// "total_tasks": 10,
// "completed_tasks": 2
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// {
// "employee": {
// image: "",
// "name": "Michael Lee",
// "employee_id": "HR-206",
// "status": "Active",
// },
// "task": {
// "status": "Ongoing",
// "title": "Testing Conveyor Belt Z",
// "location": {
// "floor": 5,
// "zone": "C"
// },
// "planned_time_hours": 5,
// "time_spent_hours": 3,
// "total_tasks": 8,
// "completed_tasks": 5
// },
// "actions": [
// "Assign Task",
// "Reassign Task",
// "Pause",
// "Emergency Stop"
// ],
// "location": "Floor 4 . Zone B"
// },
// ]
function handleRenameWorker(newName: string) {}
function handleHumanClick(employee: any) {
if (employee.modelId) {
setResourceManagementId(employee.modelId);
}
}
return (
<>
{/* <NavigateCatagory
category={["All People", "Technician", "Operator", "Supervisor", "Safety Officer"]}
selectedCategory={selectedCategory}
setSelectedCategory={setSelectedCategory}
/> */}
<div className='hrm-container assetManagement-wrapper'>
{workers.map((employee, index) => (
<div
className={`analysis-wrapper ${selectedCard === index ? "active" : ""}`}
onClick={() => setSelectedCard(index)}
key={index}
>
<header>
<div className="user-details">
<div className="user-image-wrapper">
<img className='user-image' src={employee.employee.image} alt="" />
<div className={`status ${employee.employee.status}`}></div>
</div>
<div className="details">
{/* <div className="employee-name">{employee.employee.name}</div> */}
<RenameInput value={employee.employee.name} onRename={handleRenameWorker} />
<div className="employee-id">{employee.employee.employee_id}</div>
</div>
</div>
<div className="hrm-container assetManagement-wrapper">
{workers.map((employee, index) => (
<div
className={`analysis-wrapper ${
selectedCard === index ? "active" : ""
}`}
onClick={() => setSelectedCard(index)}
key={index}
>
<header>
<div className="user-details">
<div className="user-image-wrapper">
<img
className="user-image"
src={employee.employee.image}
alt=""
/>
<div className={`status ${employee.employee.status}`}></div>
</div>
<div className="details">
{/* <div className="employee-name">{employee.employee.name}</div> */}
<RenameInput
value={employee.employee.name}
onRename={handleRenameWorker}
/>
<div className="employee-id">
{employee.employee.employee_id}
</div>
</div>
</div>
<div className="see-more">View more</div>
</header>
<div
className="see-more"
onClick={() => {
handleHumanClick(employee.employee);
}}
>
View in Scene
</div>
</header>
<div className="content">
{/* <div className="task-info">
<div className="content">
{/* <div className="task-info">
<div className="task-wrapper">
<div className="task-label">
<span className='label-icon'><ListTaskIcon /></span>
@@ -205,17 +240,20 @@ const Hrm = () => {
</div>
</div> */}
<div className="task-stats">
<div className="stat-item">
<div className="task-stats">
<div className="stat-item">
<div className="stat-wrapper">
<span className="stat-icon">
<ClockThreeIcon />
</span>
<span>Planned time:</span>
</div>
<div className="stat-wrapper">
<span className="stat-icon"><ClockThreeIcon /></span>
<span>Planned time:</span>
</div>
<span className='stat-value'>{employee.task.planned_time_hours} hr</span>
</div>
{/* <div className="stat-item">
<span className="stat-value">
{employee.task.planned_time_hours} hr
</span>
</div>
{/* <div className="stat-item">
<div className="stat-wrapper">
<span className="stat-icon"><SlectedTickIcon /></span>
@@ -233,39 +271,41 @@ const Hrm = () => {
<span className='stat-value'>{employee.task.time_spent_hours} hr</span>
</div> */}
<div className="stat-item">
<div className="stat-item">
<div className="stat-wrapper">
<span className="stat-icon">
<TargetIcon />
</span>
<span>Cost per hr:</span>
</div>
<div className="stat-wrapper">
<span className="stat-icon"><TargetIcon /></span>
<span>Cost per hr:</span>
</div>
<span className="stat-value">
{employee.task.completed_tasks}
</span>
</div>
</div>
<span className='stat-value'>{employee.task.completed_tasks}</span>
</div>
</div>
<div className="location-wrapper">
<div className="location-header">
<div className="icon">
<LocationPinIcon />
</div>
<div className="header">Location:</div>
</div>
<div className="location-value">{employee.location}</div>
</div>
<div className="task-actions">
{/* <button className="btn btn-default">Assign Task</button>
<button className="btn btn-default">Reassign Task</button> */}
<button className="btn btn-default">Pause</button>
<button className="btn btn-danger">Emergency Stop</button>
</div>
</div>
</div>
))}
<div className="location-wrapper">
<div className="location-header">
<div className="icon">
<LocationPinIcon />
</div>
<div className="header">Location:</div>
</div>
<div className="location-value">{employee.location}</div>
</div>
{/* <div className="task-actions">
<button className="btn btn-default">Assign Task</button>
<button className="btn btn-default">Reassign Task</button>
<button className="btn btn-default">Pause</button>
<button className="btn btn-danger">Emergency Stop</button>
</div> */}
</div>
</>
)
}
</div>
))}
</div>
</>
);
};
export default Hrm
export default Hrm;

View File

@@ -1,60 +1,70 @@
import { useEffect, useState } from 'react'
// import NavigateCatagory from '../../NavigateCatagory'
import { EyeIcon, ForkLiftIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons';
import assetImage from "../../../../../../assets/image/asset-image.png"
import { EyeIcon, KebabIcon, LocationPinIcon, RightHalfFillCircleIcon } from '../../../../../icons/ExportCommonIcons';
import assetImageFallback from "../../../../../../assets/image/asset-image.png"
import { useSceneContext } from '../../../../../../modules/scene/sceneContext';
import { useProductContext } from '../../../../../../modules/simulation/products/productContext';
import RenameInput from '../../../../../ui/inputs/RenameInput';
import { useResourceManagementId } from '../../../../../../store/builder/store';
import { getAssetThumbnail } from '../../../../../../services/factoryBuilder/asset/assets/getAssetThumbnail';
import { TypeBasedAssetIcons } from '../../../../../icons/AssetTypeIcons';
const AssetManagement = () => {
// const [selectedCategory, setSelectedCategory] = useState("All Assets");
const [expandedAssetId, setExpandedAssetId] = useState<string | null>(null);
const [assets, setAssets] = useState<any[]>([]);
const { setResourceManagementId } = useResourceManagementId();
const { assetStore } = useSceneContext();
const { assets: allAssets } = assetStore();
const { productStore } = useSceneContext();
const { products, getProductById } = productStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
async function getAsset(assetId: string) {
let thumbnail = await getAssetThumbnail(assetId)
if (thumbnail.thumbnail) {
let assetImage = thumbnail.thumbnail ?? assetImageFallback;
return assetImage;
}
}
useEffect(() => {
if (selectedProduct) {
const productDetails = getProductById(selectedProduct.productUuid);
const productAssets = productDetails?.eventDatas || [];
const grouped: Record<string, any> = {};
productAssets.forEach((asset: any) => {
if (asset.type === "storageUnit" || asset.type === "human") return;
if (!grouped[asset.modelName]) {
grouped[asset.modelName] = {
id: asset.assetId,
name: asset.modelName,
model: asset.modelCode || "N/A",
status: asset.status || "Online",
usageRate: asset.usageRate || 15,
level: asset.level || "Level 1",
image: assetImage,
description: asset.description || "No description",
cost: asset.cost || 0,
count: 1,
};
} else {
grouped[asset.modelName].count += 1;
}
});
if (allAssets.length > 0) {
const fetchAssets = async () => {
const grouped: Record<string, any> = {};
setAssets(Object.values(grouped));
// Use Promise.all to handle all async operations
await Promise.all(allAssets.map(async (asset: any) => {
if (asset.eventData.type === "Storage" || asset.eventData.type === "Human") return;
const assetImage = await getAsset(asset.assetId);
if (!grouped[asset.assetId]) {
//
grouped[asset.assetId] = {
id: asset.modelUuid,
assetId: asset.assetId,
name: asset.modelName,
type: asset.eventData.type,
model: asset.modelCode ?? "N/A",
status: asset.status ?? "Online",
usageRate: asset.usageRate ?? 15,
level: asset.level ?? "Level 1",
image: assetImage,
description: asset.description ?? "No description",
cost: asset.cost ?? 0,
count: 1,
};
} else {
grouped[asset.assetId].count += 1;
}
}));
setAssets(Object.values(grouped));
}
fetchAssets();
}
}, [selectedProduct]);
}, [allAssets]);
function handleRenameAsset(newName: string) {
// console.log('newName: ', newName);
// if (expandedAssetId) {
// setAssets(prevAssets =>
// prevAssets.map(asset =>
// asset.id === expandedAssetId ? { ...asset, name: newName } : asset
// )
// );
// }
}
useEffect(() => {
@@ -62,6 +72,10 @@ const AssetManagement = () => {
}, [assets]);
function handleAssetClick(id: string) {
setResourceManagementId(id);
}
// const dummyAssets = [
// {
// id: '1',
@@ -105,35 +119,32 @@ const AssetManagement = () => {
return (
<>
{/* <NavigateCatagory
category={["All Assets", "Machines", "Workstation", "Vehicles"]}
selectedCategory={selectedCategory}
setSelectedCategory={setSelectedCategory}
/> */}
category={["All Assets", "Machines", "Workstation", "Vehicles"]}
selectedCategory={selectedCategory}
setSelectedCategory={setSelectedCategory}
/> */}
<div className='assetManagement-container assetManagement-wrapper'>
{assets.map((asset, index) => (
<div className={`assetManagement-card-wrapper ${expandedAssetId === asset.id ? "openViewMore" : ""}`} key={index}>
<header>
<div className="header-wrapper">
{expandedAssetId === asset.id ?
<>
<div className="drop-icon" onClick={() => setExpandedAssetId(null)}></div>
<img className='asset-image' src={asset.image} alt="" />
</>
:
<div className="icon"><ForkLiftIcon /></div>
<div className="icon"><TypeBasedAssetIcons assetType={asset.type}/></div>
}
<div className="asset-details-container">
<div className="asset-details">
<div className="asset-details" >
{/* <div className="asset-name">{asset.name}</div> */}
<RenameInput value={asset.name} onRename={handleRenameAsset} />
{asset.count !== 1 && <div>
<span className="asset-id-label">x</span>
<span className="asset-id">{asset.count}</span>
</div>}
<div className="asset-model">{asset.model}</div>
</div>
<div className="asset-status-wrapper">
@@ -164,7 +175,7 @@ const AssetManagement = () => {
</div>
</div>
<div className="asset-estimate__view-button">
<div className="asset-estimate__view-button" onClick={() => handleAssetClick(asset.id)}>
<EyeIcon isClosed={false} />
<div className="asset-estimate__view-text">View in Scene</div>
</div>
@@ -204,16 +215,10 @@ const AssetManagement = () => {
<div className="value">{expandedAssetId === asset.id ? "View Less" : "View More"}</div>
<div className="icon"><KebabIcon /></div>
</div>
</div>
</div>
)}
</div>
))}
</div >
</>

View File

@@ -11,9 +11,9 @@ import { getUserData } from "../../../../functions/getUserData";
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
const VersionSaved = () => {
const { versionHistory, addVersion, createNewVersion, setCreateNewVersion } = useVersionHistoryStore();
const { addVersion, createNewVersion, setCreateNewVersion } = useVersionHistoryStore();
const { selectedVersionStore } = useVersionContext();
const { setSelectedVersion } = selectedVersionStore();
const { selectedVersion, setSelectedVersion } = selectedVersionStore();
const [newName, setNewName] = useState(new Date().toLocaleString("en-US", {
month: "short",
day: "numeric",
@@ -26,8 +26,6 @@ const VersionSaved = () => {
const { projectId } = useParams();
const { userId } = getUserData();
const latestVersion = versionHistory?.[0];
useEffect(() => {
if (createNewVersion) {
const defaultName = new Date().toLocaleString("en-US", {
@@ -43,12 +41,12 @@ const VersionSaved = () => {
}, [createNewVersion]);
const handleSave = () => {
if (!latestVersion || !projectId) return;
if (!selectedVersion || !projectId) return;
const updatedName = (newName.trim() || latestVersion.versionName) ?? latestVersion.timeStamp;
const updatedDescription = (description.trim() || latestVersion.versionName) ?? latestVersion.timeStamp;
const updatedName = (newName.trim() || selectedVersion.versionName) ?? selectedVersion.timeStamp;
const updatedDescription = (description.trim() || selectedVersion.versionName) ?? selectedVersion.timeStamp;
createVersionApi(projectId, userId, latestVersion.versionId, updatedName, updatedDescription).then((data) => {
createVersionApi(projectId, userId, selectedVersion.versionId, updatedName, updatedDescription).then((data) => {
setSaveFinish(true);
setCreateNewVersion(false);
@@ -84,7 +82,7 @@ const VersionSaved = () => {
setCreateNewVersion(false);
};
if (!latestVersion) return null;
if (!selectedVersion) return null;
return (
<div className={`versionSaved`}>
@@ -105,7 +103,7 @@ const VersionSaved = () => {
placeholder="Enter new version name"
/>
<div className="label">
by @{latestVersion.createdBy}{" "}{new Date(latestVersion.timeStamp).toLocaleDateString("en-US", {
by @{selectedVersion.createdBy}{" "}{new Date(selectedVersion.timeStamp).toLocaleDateString("en-US", {
year: "numeric",
month: "long",
day: "2-digit",

View File

@@ -3,7 +3,7 @@ import RenameInput from "./inputs/RenameInput";
import { ArrowIcon } from "../icons/ExportCommonIcons";
import MenuBar from "./menu/menu";
import { ProjectIcon } from "../icons/HeaderIcons";
import { useProjectName, useSocketStore } from "../../store/builder/store";
import { useProjectName } from "../../store/builder/store";
import { useParams } from "react-router-dom";
import { getAllProjects } from "../../services/dashboard/getAllProjects";
import { updateProject } from "../../services/dashboard/updateProject";
@@ -14,7 +14,7 @@ const FileMenu: React.FC = () => {
const containerRef = useRef<HTMLButtonElement>(null);
let clickTimeout: NodeJS.Timeout | null = null;
const { projectName, setProjectName } = useProjectName();
const { dashBoardSocket } = useSocketStore();
// const { dashBoardSocket } = useSocketStore();
const { projectId } = useParams();
const { userId, organization, email } = getUserData();

View File

@@ -1,17 +1,17 @@
import React, { useEffect, useRef, useState } from "react";
import {
AsileIcon,
CommentIcon,
CursorIcon,
DeleteIcon,
FloorIcon,
FreeMoveIcon,
MeasureToolIcon,
PenIcon,
PlayIcon,
SaveTemplateIcon,
WallIcon,
ZoneIcon,
AsileIcon,
CommentIcon,
CursorIcon,
DeleteIcon,
FloorIcon,
FreeMoveIcon,
MeasureToolIcon,
PenIcon,
PlayIcon,
SaveTemplateIcon,
WallIcon,
ZoneIcon,
} from "../icons/ExportToolsIcons";
import { ArrowIcon, TickIcon } from "../icons/ExportCommonIcons";
import useModuleStore, { useThreeDStore } from "../../store/useModuleStore";
@@ -20,414 +20,417 @@ import { usePlayButtonStore } from "../../store/usePlayButtonStore";
import useTemplateStore from "../../store/useTemplateStore";
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
import {
useActiveTool,
useAddAction,
useSelectedWallItem,
useSocketStore,
useToggleView,
useToolMode,
useActiveSubTool,
useShortcutStore,
useActiveTool,
useAddAction,
useSocketStore,
useToggleView,
useToolMode,
useActiveSubTool,
useShortcutStore,
} from "../../store/builder/store";
import { useToggleStore } from "../../store/useUIToggleStore";
import {
use3DWidget,
useFloatingWidget,
use3DWidget,
useFloatingWidget,
} from "../../store/visualization/useDroppedObjectsStore";
import { useParams } from "react-router-dom";
import { useVersionContext } from "../../modules/builder/version/versionContext";
import { MoveIcon, RotateIcon } from "../icons/ShortcutIcons";
import { useBuilderStore } from "../../store/builder/useBuilderStore";
// Utility component
const ToolButton = ({
toolKey,
toolId,
icon: Icon,
active,
onClick,
tooltip,
toolKey,
toolId,
icon: Icon,
active,
onClick,
tooltip,
}: any) => (
<button
key={toolKey} // used in rendering list
id={toolId}
className={`tool-button ${active ? "active" : ""}`}
onClick={onClick}
>
<div className="tooltip">{tooltip}</div>
<div className="tool" id={toolId}>
<Icon isActive={active} />
</div>
</button>
<button
key={toolKey} // used in rendering list
id={toolId}
className={`tool-button ${active ? "active" : ""}`}
onClick={onClick}
>
<div className="tooltip">{tooltip}</div>
<div className="tool" id={toolId}>
<Icon isActive={active} />
</div>
</button>
);
const Tools: React.FC = () => {
const { activeModule } = useModuleStore();
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { showShortcuts } = useShortcutStore();
const { activeModule } = useModuleStore();
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { showShortcuts } = useShortcutStore();
const { activeTool, setActiveTool, setToolMode, setAddAction } = useStoreHooks();
const { setSelectedWallAsset } = useBuilderStore();
const { setActiveSubTool, activeSubTool } = useActiveSubTool();
const { setToggleUI } = useToggleStore();
const { setToggleView, toggleView } = useToggleView();
const { activeTool, setActiveTool, setToolMode, setAddAction } =
useStoreHooks();
const { addTemplate, templates } = useTemplateStore();
const { selectedZone } = useSelectedZoneStore();
const { floatingWidget } = useFloatingWidget();
const { widgets3D } = use3DWidget();
const { setActiveSubTool, activeSubTool } = useActiveSubTool();
const { setSelectedWallItem } = useSelectedWallItem();
const { setToggleUI } = useToggleStore();
const { setToggleView, toggleView } = useToggleView();
const { visualizationSocket } = useSocketStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { addTemplate, templates } = useTemplateStore();
const { selectedZone } = useSelectedZoneStore();
const { floatingWidget } = useFloatingWidget();
const { widgets3D } = use3DWidget();
const dropdownRef = useRef<HTMLButtonElement>(null);
const [openDrop, setOpenDrop] = useState(false);
const { visualizationSocket } = useSocketStore();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
// 1. Set UI toggles on initial render
useEffect(() => {
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const dropdownRef = useRef<HTMLButtonElement>(null);
const [openDrop, setOpenDrop] = useState(false);
// 2. Update tool based on subtool and module
useEffect(() => {
setActiveTool(activeSubTool);
setActiveSubTool(activeSubTool);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeModule]);
// 1. Set UI toggles on initial render
useEffect(() => {
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// 3. Update tools behavior based on selected tool and view mode
useEffect(() => {
resetTools();
updateToolBehavior(activeTool, toggleView);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTool, toggleView]);
// 2. Update tool based on subtool and module
useEffect(() => {
setActiveTool(activeSubTool);
setActiveSubTool(activeSubTool);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeModule]);
// 4. Dropdown auto-close
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setOpenDrop(false);
}
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
// 3. Update tools behavior based on selected tool and view mode
useEffect(() => {
resetTools();
updateToolBehavior(activeTool, toggleView);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [activeTool, toggleView]);
// 4. Dropdown auto-close
useEffect(() => {
const handleClickOutside = (e: MouseEvent) => {
if (
dropdownRef.current &&
!dropdownRef.current.contains(e.target as Node)
) {
setOpenDrop(false);
}
const resetTools = () => {
setToolMode(null);
setAddAction(null);
};
document.addEventListener("mousedown", handleClickOutside);
return () => document.removeEventListener("mousedown", handleClickOutside);
}, []);
const resetTools = () => {
setToolMode(null);
setAddAction(null);
};
const updateToolBehavior = (tool: string, is2D: boolean) => {
switch (tool) {
case "cursor":
is2D ? setToolMode("move") : setToolMode("cursor");
break;
case "draw-wall":
is2D && setToolMode("Wall");
break;
case "draw-aisle":
is2D && setToolMode("Aisle");
break;
case "draw-zone":
is2D && setToolMode("Zone");
break;
case "draw-floor":
is2D && setToolMode("Floor");
break;
case "measure":
setToolMode("MeasurementScale");
break;
case "Add pillar":
if (!is2D) setAddAction("Pillar");
break;
case "delete":
is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete");
break;
}
};
const toggle2D3D = () => {
const toggleTo2D = toggleView;
setToggleView(!toggleTo2D);
setToggleThreeD(toggleTo2D);
setToggleUI(toggleTo2D, toggleTo2D);
if (toggleTo2D) {
setSelectedWallItem(null);
setAddAction(null);
}
setActiveTool("cursor");
setActiveSubTool("cursor");
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
};
const renderBuilderTools = () => (
<>
{!toggleThreeD && (
<div className="draw-tools">
<ToolButton
toolId="drawWall"
icon={WallIcon}
tooltip="draw wall (q)"
active={activeTool === "draw-wall"}
onClick={() => setActiveTool("draw-wall")}
/>
<ToolButton
toolId="drawZone"
icon={ZoneIcon}
tooltip="draw zone (e)"
active={activeTool === "draw-zone"}
onClick={() => setActiveTool("draw-zone")}
/>
<ToolButton
toolId="drawAisle"
icon={AsileIcon}
tooltip="draw aisle (r)"
active={activeTool === "draw-aisle"}
onClick={() => setActiveTool("draw-aisle")}
/>
<ToolButton
toolId="drawFloor"
icon={FloorIcon}
tooltip="draw floor (t)"
active={activeTool === "draw-floor"}
onClick={() => setActiveTool("draw-floor")}
/>
</div>
)}
<div className="draw-tools">
<ToolButton
toolId="measureScale"
icon={MeasureToolIcon}
tooltip="measure scale (m)"
active={activeTool === "measure"}
onClick={() => setActiveTool("measure")}
/>
</div>
</>
);
const renderSimulationTools = () => (
<div className="draw-tools">
<ToolButton
toolId="pen"
icon={PenIcon}
tooltip="pen"
active={activeTool === "pen"}
onClick={() => setActiveTool("pen")}
/>
</div>
);
const renderVisualizationTools = () => (
<div className="draw-tools">
<ToolButton
toolId="saveTemplate"
icon={SaveTemplateIcon}
tooltip="save template"
active={false}
onClick={() =>
handleSaveTemplate({
addTemplate,
floatingWidget,
widgets3D,
selectedZone,
templates,
visualizationSocket,
projectId,
versionId: selectedVersion?.versionId || "",
})
const updateToolBehavior = (tool: string, is2D: boolean) => {
switch (tool) {
case "cursor":
is2D ? setToolMode("move") : setToolMode("cursor");
break;
case "draw-wall":
is2D && setToolMode("Wall");
break;
case "draw-aisle":
is2D && setToolMode("Aisle");
break;
case "draw-zone":
is2D && setToolMode("Zone");
break;
case "draw-floor":
is2D && setToolMode("Floor");
break;
case "move":
if (!is2D) setToolMode("Move-Asset");
break;
case "rotate":
if (!is2D) setToolMode("Rotate-Asset");
break;
case "measure":
setToolMode("MeasurementScale");
break;
case "Add pillar":
if (!is2D) setAddAction("Pillar");
break;
case "delete":
is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete");
break;
}
/>
</div>
);
};
const renderModeSwitcher = () => (
<button
id="toggle-threed-button"
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
onClick={toggle2D3D}
>
<div className="tooltip">toggle view (tab)</div>
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>2d</div>
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>3d</div>
</button>
);
const toggle2D3D = () => {
const toggleTo2D = toggleView;
setToggleView(!toggleTo2D);
setToggleThreeD(toggleTo2D);
setToggleUI(toggleTo2D, toggleTo2D);
if (toggleTo2D) {
setSelectedWallAsset(null);
setAddAction(null);
}
setActiveTool("cursor");
setActiveSubTool("cursor");
setToggleUI(
localStorage.getItem("navBarUiLeft") !== "false",
localStorage.getItem("navBarUiRight") !== "false"
);
};
const getIconByTool = (tool: string) => {
switch (tool) {
case "cursor":
return CursorIcon;
case "free-hand":
return FreeMoveIcon;
case "delete":
return DeleteIcon;
case "move":
return MoveIcon;
case "rotate":
return RotateIcon;
default:
return CursorIcon;
}
};
const getTooltipShortcut = (tool: string) => {
switch (tool) {
case "cursor":
return "v";
case "free-hand":
return "h";
case "delete":
return "x";
default:
return "";
}
};
const getIconComponent = (option: string) => {
switch (option) {
case "cursor":
return <CursorIcon isActive={false} />;
case "free-hand":
return <FreeMoveIcon isActive={false} />;
case "delete":
return <DeleteIcon isActive={false} />;
case "move":
return <MoveIcon />;
case "rotate":
return <RotateIcon />;
default:
return null;
}
};
return (
<div className={`tools-container ${showShortcuts ? "visible" : ""}`}>
<div className="activeDropicon">
{/* Tool Picker (cursor, delete, etc.) */}
{["cursor", "free-hand", "delete"].map(
(tool) =>
activeSubTool === tool && (
<ToolButton
key={tool}
toolId={tool}
icon={getIconByTool(tool)}
tooltip={`${tool} (${getTooltipShortcut(tool)})`}
active={activeTool === tool}
onClick={() => setActiveTool(tool)}
/>
)
)}
{/* Dropdown Menu */}
{activeModule !== "visualization" && (
<button
id="drop-down-button"
title="drop-down"
className="drop-down-option-button"
ref={dropdownRef}
onClick={() => setOpenDrop(!openDrop)}
>
<ArrowIcon />
{openDrop && (
<div className="drop-down-container">
{["cursor", "free-hand", "delete"].map((option) => (
<button
key={option}
id={`${option}-tool`}
className="option-list"
onClick={() => {
setActiveTool(option);
setActiveSubTool(option);
setOpenDrop(false);
}}
>
<div className="active-option">
{activeSubTool === option && <TickIcon />}
</div>
{getIconComponent(option)}
<div className="option">{option}</div>
</button>
))}
</div>
const renderBuilderTools = () => (
<>
{!toggleThreeD && (
<div className="draw-tools">
<ToolButton
toolId="drawWall"
icon={WallIcon}
tooltip="draw wall (q)"
active={activeTool === "draw-wall"}
onClick={() => setActiveTool("draw-wall")}
/>
<ToolButton
toolId="drawZone"
icon={ZoneIcon}
tooltip="draw zone (e)"
active={activeTool === "draw-zone"}
onClick={() => setActiveTool("draw-zone")}
/>
<ToolButton
toolId="drawAisle"
icon={AsileIcon}
tooltip="draw aisle (r)"
active={activeTool === "draw-aisle"}
onClick={() => setActiveTool("draw-aisle")}
/>
<ToolButton
toolId="drawFloor"
icon={FloorIcon}
tooltip="draw floor (t)"
active={activeTool === "draw-floor"}
onClick={() => setActiveTool("draw-floor")}
/>
</div>
)}
</button>
)}
</div>
{activeModule !== "visualization" && (
<>
<div className="split"></div>
<div className="transform-tools">
{["move", "rotate"].map((tool) => (
<ToolButton
key={tool}
toolId={tool}
icon={getIconByTool(tool)}
tooltip={`${tool}`}
active={activeTool === tool}
onClick={() => setActiveTool(tool)}
/>
))}
</div>
<div className="draw-tools">
<ToolButton
toolId="measureScale"
icon={MeasureToolIcon}
tooltip="measure scale (m)"
active={activeTool === "measure"}
onClick={() => setActiveTool("measure")}
/>
</div>
</>
)}
);
<div className="split"></div>
{activeModule === "builder" && renderBuilderTools()}
{activeModule === "simulation" && renderSimulationTools()}
{activeModule === "visualization" && renderVisualizationTools()}
const renderSimulationTools = () => (
<div className="draw-tools">
<ToolButton
toolId="pen"
icon={PenIcon}
tooltip="pen"
active={activeTool === "pen"}
onClick={() => setActiveTool("pen")}
/>
</div>
);
<div className="split"></div>
<div className="general-options">
<ToolButton
toolId="comment"
icon={CommentIcon}
tooltip="comment"
active={activeTool === "comment"}
onClick={() => setActiveTool("comment")}
/>
{toggleThreeD && (
<ToolButton
toolId="play"
icon={PlayIcon}
tooltip="play (ctrl + p)"
active={activeTool === "play"}
onClick={() => setIsPlaying(!isPlaying)}
/>
)}
</div>
const renderVisualizationTools = () => (
<div className="draw-tools">
<ToolButton
toolId="saveTemplate"
icon={SaveTemplateIcon}
tooltip="save template"
active={false}
onClick={() =>
handleSaveTemplate({
addTemplate,
floatingWidget,
widgets3D,
selectedZone,
templates,
visualizationSocket,
projectId,
versionId: selectedVersion?.versionId || "",
})
}
/>
</div>
);
{activeModule === "builder" && (
<>
<div className="split"></div>
{renderModeSwitcher()}
</>
)}
</div>
);
const renderModeSwitcher = () => (
<button
id="toggle-threed-button"
className={`toggle-threed-button${toggleThreeD ? " toggled" : ""}`}
onClick={toggle2D3D}
>
<div className="tooltip">toggle view (tab)</div>
<div className={`toggle-option${!toggleThreeD ? " active" : ""}`}>2d</div>
<div className={`toggle-option${toggleThreeD ? " active" : ""}`}>3d</div>
</button>
);
const getIconByTool = (tool: string) => {
switch (tool) {
case "cursor":
return CursorIcon;
case "free-hand":
return FreeMoveIcon;
case "delete":
return DeleteIcon;
case "move":
return MoveIcon;
case "rotate":
return RotateIcon;
default:
return CursorIcon;
}
};
const getTooltipShortcut = (tool: string) => {
switch (tool) {
case "cursor":
return "v";
case "free-hand":
return "h";
case "delete":
return "x";
default:
return "";
}
};
const getIconComponent = (option: string) => {
switch (option) {
case "cursor":
return <CursorIcon isActive={false} />;
case "free-hand":
return <FreeMoveIcon isActive={false} />;
case "delete":
return <DeleteIcon isActive={false} />;
case "move":
return <MoveIcon />;
case "rotate":
return <RotateIcon />;
default:
return null;
}
};
return (
<div className={`tools-container ${showShortcuts ? "visible" : ""}`}>
<div className="activeDropicon">
{/* Tool Picker (cursor, delete, etc.) */}
{["cursor", "free-hand", "delete"].map(
(tool) =>
activeSubTool === tool && (
<ToolButton
key={tool}
toolId={tool}
icon={getIconByTool(tool)}
tooltip={`${tool} (${getTooltipShortcut(tool)})`}
active={activeTool === tool}
onClick={() => setActiveTool(tool)}
/>
)
)}
{/* Dropdown Menu */}
{activeModule !== "visualization" && (
<button
id="drop-down-button"
title="drop-down"
className="drop-down-option-button"
ref={dropdownRef}
onClick={() => setOpenDrop(!openDrop)}
>
<ArrowIcon />
{openDrop && (
<div className="drop-down-container">
{["cursor", "free-hand", "delete"].map((option) => (
<button
key={option}
id={`${option}-tool`}
className="option-list"
onClick={() => {
setActiveTool(option);
setActiveSubTool(option);
setOpenDrop(false);
}}
>
<div className="active-option">
{activeSubTool === option && <TickIcon />}
</div>
{getIconComponent(option)}
<div className="option">{option}</div>
</button>
))}
</div>
)}
</button>
)}
</div>
{toggleThreeD && activeModule !== "visualization" && (
<>
<div className="split"></div>
<div className="transform-tools">
{["move", "rotate"].map((tool) => (
<ToolButton
key={tool}
toolId={tool}
icon={getIconByTool(tool)}
tooltip={`${tool}`}
active={activeTool === tool}
onClick={() => setActiveTool(tool)}
/>
))}
</div>
</>
)}
<div className="split"></div>
{activeModule === "builder" && renderBuilderTools()}
{activeModule === "simulation" && renderSimulationTools()}
{activeModule === "visualization" && renderVisualizationTools()}
<div className="split"></div>
<div className="general-options">
<ToolButton
toolId="comment"
icon={CommentIcon}
tooltip="comment"
active={activeTool === "comment"}
onClick={() => setActiveTool("comment")}
/>
{toggleThreeD && (
<ToolButton
toolId="play"
icon={PlayIcon}
tooltip="play (ctrl + p)"
active={activeTool === "play"}
onClick={() => setIsPlaying(!isPlaying)}
/>
)}
</div>
{activeModule === "builder" && (
<>
<div className="split"></div>
{renderModeSwitcher()}
</>
)}
</div>
);
};
// Extracted common store logic
const useStoreHooks = () => {
return {
...useActiveTool(),
...useToolMode(),
...useAddAction(),
};
return {
...useActiveTool(),
...useToolMode(),
...useAddAction(),
};
};
export default Tools;

View File

@@ -65,32 +65,34 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
if (isEditableThread && editedThread) {
try {
// const editThreadTitle = await editThreadTitleApi(projectId, (val as CommentSchema).threadId, value, selectedVersion?.versionId || "")
// if (editThreadTitle.message == "ThreadTitle updated Successfully") {
// const editedThread: CommentSchema = {
// state: 'active',
// threadId: editThreadTitle.data.replyId,
// creatorId: userId,
// createdAt: getRelativeTime(editThreadTitle.data.createdAt),
// threadTitle: value,
// lastUpdatedAt: new Date().toISOString(),
// position: editThreadTitle.data.position,
// rotation: [0, 0, 0],
// comments: [],
// }
// updateComment((val as CommentSchema).threadId, editedThread)
// }
// projectId, userId, threadTitle, organization, threadId
const threadEdit = {
projectId,
userId,
threadTitle: value,
organization,
threadId: (val as CommentSchema).threadId || selectedComment.threadId,
versionId: selectedVersion?.versionId || ""
}
if (!threadSocket?.active) {
const editThreadTitle = await editThreadTitleApi(projectId, (val as CommentSchema).threadId, value, selectedVersion?.versionId || "")
if (editThreadTitle.message == "ThreadTitle updated Successfully") {
const editedThread: CommentSchema = {
state: 'active',
threadId: editThreadTitle.data.replyId,
creatorId: userId,
createdAt: getRelativeTime(editThreadTitle.data.createdAt),
threadTitle: value,
lastUpdatedAt: new Date().toISOString(),
position: editThreadTitle.data.position,
rotation: [0, 0, 0],
comments: [],
}
updateComment((val as CommentSchema).threadId, editedThread)
}
} else {
const threadEdit = {
projectId,
userId,
threadTitle: value,
organization,
threadId: (val as CommentSchema).threadId || selectedComment.threadId,
versionId: selectedVersion?.versionId || ""
}
threadSocket.emit('v1:thread:updateTitle', threadEdit)
threadSocket.emit('v1:thread:updateTitle', threadEdit)
}
} catch {
}
} else {
@@ -277,7 +279,7 @@ const Messages: React.FC<MessageProps> = ({ val, i, setMessages, mode, setIsEdit
<div className="message">
{"comment" in val ? val.comment : val.threadTitle}
</div>
</div>
</div >
)}

View File

@@ -4,7 +4,6 @@ import {
useLeftData,
useTopData,
} from "../../../store/visualization/useZone3DWidgetStore";
import { useRenameModeStore } from "../../../store/builder/store";
type RenameTooltipProps = {
name: string;
@@ -20,6 +19,7 @@ const RenameTooltip: React.FC<RenameTooltipProps> = ({ name, onSubmit }) => {
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onSubmit(value.trim());
echo.info(`Selected Object has been renamed to ${value.trim()}`);
setTop(0);
setLeft(0);
};

View File

@@ -12,7 +12,12 @@ interface RenameInputProps {
canEdit?: boolean;
}
const RenameInput: React.FC<RenameInputProps> = ({ value, onRename, checkDuplicate, canEdit = true }) => {
const RenameInput: React.FC<RenameInputProps> = ({
value,
onRename,
checkDuplicate,
canEdit = true,
}) => {
const [isEditing, setIsEditing] = useState(false);
const [text, setText] = useState(value);
const [isDuplicate, setIsDuplicate] = useState(false);
@@ -36,10 +41,10 @@ const RenameInput: React.FC<RenameInputProps> = ({ value, onRename, checkDuplica
};
const handleBlur = () => {
if (isDuplicate) return
if (isDuplicate) return;
setIsEditing(false);
if (onRename && !isDuplicate) {
echo.info(`Selected Object has been renamed to ${text}`)
onRename(text);
}
};
@@ -52,6 +57,7 @@ const RenameInput: React.FC<RenameInputProps> = ({ value, onRename, checkDuplica
if (e.key === "Enter" && !isDuplicate) {
setIsEditing(false);
if (onRename) {
echo.info(`Selected Object has been renamed to ${text}`)
onRename(text);
}
}
@@ -80,4 +86,4 @@ const RenameInput: React.FC<RenameInputProps> = ({ value, onRename, checkDuplica
</>
);
};
export default RenameInput
export default RenameInput;

View File

@@ -1,16 +1,18 @@
import React, { ChangeEvent, useState } from "react";
import React, { ChangeEvent, useEffect, useState } from "react";
import { CloseIcon, SearchIcon } from "../../icons/ExportCommonIcons";
interface SearchProps {
value?: string; // The current value of the search input
value?: string | null; // The current value of the search input
placeholder?: string; // Placeholder text for the input
onChange: (value: string) => void; // Callback function to handle input changes
debounced?: boolean; // New prop: whether to debounce onChange
}
const Search: React.FC<SearchProps> = ({
value = "",
placeholder = "Search",
onChange,
debounced = false, // Default false
}) => {
// State to track the input value and focus status
const [inputValue, setInputValue] = useState(value);
@@ -19,28 +21,49 @@ const Search: React.FC<SearchProps> = ({
const handleInputChange = (event: ChangeEvent<HTMLInputElement>) => {
const newValue = event.target.value;
setInputValue(newValue);
onChange(newValue); // Call the onChange prop with the new value
if (!debounced) {
onChange(newValue); // Immediate call if not debounced
}
};
useEffect(() => {
if (value === null) {
setInputValue("");
handleBlur();
}
}, [value]);
// Handle debounced effect
useEffect(() => {
if (!debounced) return;
const timer = setTimeout(() => {
onChange(inputValue ?? "");
}, 500);
return () => clearTimeout(timer);
}, [inputValue, debounced, onChange]);
const handleClear = () => {
console.warn("Search field cleared.");
setInputValue("");
onChange(""); // Clear the input value
onChange(""); // Clear immediately
};
const handleFocus = () => {
setIsFocused(true); // Set focus state to true
setIsFocused(true);
};
const handleBlur = () => {
setIsFocused(false); // Set focus state to false
setIsFocused(false);
};
return (
<div className="search-wrapper">
<div
className={`search-container ${
isFocused || inputValue ? "active" : ""
}`}
className={`search-container ${isFocused || inputValue ? "active" : ""
}`}
>
<div className="icon-container">
<SearchIcon />
@@ -48,7 +71,7 @@ const Search: React.FC<SearchProps> = ({
<input
type="text"
className="search-input"
value={inputValue}
value={inputValue ?? ""}
placeholder={placeholder}
onChange={handleInputChange}
onFocus={handleFocus}
@@ -68,4 +91,4 @@ const Search: React.FC<SearchProps> = ({
);
};
export default Search;
export default Search;

View File

@@ -57,6 +57,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
const { zoneStore } = useSceneContext();
const { zones, setZoneName } = zoneStore();
useEffect(() => {
useSelectedZoneStore.getState().setSelectedZone({
zoneName: "",
@@ -121,6 +122,7 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
}
function handleAssetClick(asset: Asset) {
setZoneAssetId(asset);
}
@@ -157,8 +159,9 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
modelUuid: zoneAssetId.id,
modelName: newName,
projectId,
versionId: selectedVersion?.versionId || ''
});
// console.log("response: ", response);
setName(zoneAssetId.id, response.modelName);
}
@@ -255,9 +258,8 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
}}
>
<div
className={`list-item ${
selectedZone.zoneUuid === item.id ? "active" : ""
}`}
className={`list-item ${selectedZone.zoneUuid === item.id ? "active" : ""
}`}
>
<div className="zone-header">
<button className="value" id="zone-name">
@@ -301,9 +303,8 @@ const List: React.FC<ListProps> = ({ items = [], remove }) => {
{item.assets.map((asset) => (
<li
key={`asset-${asset.id}`}
className={`list-container asset-item ${
zoneAssetId?.id === asset.id ? "active" : ""
}`}
className={`list-container asset-item ${zoneAssetId?.id === asset.id ? "active" : ""
}`}
onClick={() => handleAssetClick(asset)}
>
<div className="list-item">

View File

@@ -5,7 +5,9 @@ import { toggleTheme } from "../../../utils/theme";
import useVersionHistoryVisibleStore, {
useShortcutStore,
} from "../../../store/builder/store";
import useModuleStore, { useSubModuleStore } from "../../../store/useModuleStore";
import useModuleStore, {
useSubModuleStore,
} from "../../../store/useModuleStore";
import { useVersionHistoryStore } from "../../../store/builder/useVersionHistoryStore";
interface MenuBarProps {
@@ -21,11 +23,12 @@ interface MenuItem {
}
const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
const navigate = useNavigate();
const [activeMenu, setActiveMenu] = useState<string | null>(null);
const [activeSubMenu, setActiveSubMenu] = useState<string | null>(null);
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({});
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>(
{}
);
const { setCreateNewVersion } = useVersionHistoryStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
@@ -49,7 +52,9 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
};
const handleLogout = () => {
const theme = localStorage.getItem("theme") ?? "light";
localStorage.clear();
localStorage.setItem("theme", theme);
navigate("/");
};
@@ -61,7 +66,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
setCreateNewVersion(true);
setVersionHistoryVisible(true);
setSubModule("properties");
setActiveModule('builder');
setActiveModule("builder");
}
const menus: Record<string, MenuItem[]> = {
@@ -222,7 +227,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
onClick={() => {
setVersionHistoryVisible(true);
setSubModule("properties");
setActiveModule('builder');
setActiveModule("builder");
}}
>
<div className="menu-button">

View File

@@ -1,47 +0,0 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import type { CameraControls } from "@react-three/drei";
export const useCameraShortcuts = (controlsRef: React.RefObject<CameraControls>) => {
const { camera } = useThree();
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!controlsRef.current) return;
// get current distance from camera to target
const target = new THREE.Vector3();
controlsRef.current.getTarget(target);
const distance = camera.position.distanceTo(target);
let pos: THREE.Vector3 | null = null;
switch (e.key) {
case "1": // Front
pos = new THREE.Vector3(0, 0, distance).add(target);
break;
case "3": // Right
pos = new THREE.Vector3(distance, 0, 0).add(target);
break;
case "7": // Top
pos = new THREE.Vector3(0, distance, 0).add(target);
break;
case "9": // Back
pos = new THREE.Vector3(0, 0, -distance).add(target);
break;
}
if (pos) {
controlsRef.current.setLookAt(
pos.x, pos.y, pos.z, // camera position
target.x, target.y, target.z, // keep same target
true // smooth transition
);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [controlsRef, camera]);
};

View File

@@ -0,0 +1,14 @@
import DecalCreator from './decalCreator/decalCreator'
function Decal() {
return (
<>
<DecalCreator />
</>
)
}
export default Decal

View File

@@ -0,0 +1,159 @@
import { MathUtils } from 'three';
import { useEffect } from 'react';
import { useThree } from '@react-three/fiber';
import { useParams } from 'react-router-dom';
import { useSocketStore } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import useModuleStore from '../../../../store/useModuleStore';
import { useSceneContext } from '../../../scene/sceneContext';
import { useVersionContext } from '../../version/versionContext';
import { getUserData } from '../../../../functions/getUserData';
import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
function DecalCreator() {
const { wallStore, floorStore } = useSceneContext();
const { addDecal: addDecalOnWall, getWallById } = wallStore();
const { addDecal: addDecalOnFloor, getFloorById } = floorStore();
const { droppedDecal, setDroppedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { socket } = useSocketStore();
const { controls, gl, pointer, camera, raycaster, scene } = useThree();
useEffect(() => {
const canvasElement = gl.domElement;
const onDrop = (event: DragEvent) => {
if (droppedDecal) {
setDroppedDecal(null);
pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const wallIntersect = intersects.find(i => i.object.userData && i.object.userData.wallUuid);
const floorIntersect = intersects.find(i => i.object.userData && i.object.userData.floorUuid);
if (wallIntersect) {
const wall = getWallById(wallIntersect.object.userData.wallUuid);
if (!wall) return;
const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone());
const decal: Decal = {
decalUuid: MathUtils.generateUUID(),
decalName: droppedDecal.decalName,
decalId: droppedDecal.decalId,
decalType: {
type: 'Wall',
wallUuid: wallIntersect.object.userData.wallUuid,
},
decalPosition: [point.x, point.y, (wall.wallThickness / 2 + 0.001) * (wallIntersect.normal?.z || 1)],
decalRotation: 0,
decalOpacity: 1,
decalScale: 0.5,
}
addDecalOnWall(wallIntersect.object.userData.wallUuid, decal);
setTimeout(() => {
const updatedWall = getWallById(wallIntersect.object.userData.wallUuid);
if (updatedWall) {
if (projectId) {
if (!socket?.connected) {
// API
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
}
}, 0)
} else if (floorIntersect) {
const floor = getFloorById(floorIntersect.object.userData.floorUuid);
if (!floor) return;
const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone());
const decal: Decal = {
decalUuid: MathUtils.generateUUID(),
decalName: droppedDecal.decalName,
decalId: droppedDecal.decalId,
decalType: {
type: 'Floor',
floorUuid: floorIntersect.object.userData.floorUuid,
},
decalPosition: [point.x, point.y, -0.001],
decalRotation: 0,
decalOpacity: 1,
decalScale: 0.5,
}
addDecalOnFloor(floorIntersect.object.userData.floorUuid, decal);
setTimeout(() => {
const updatedFloor = getFloorById(floorIntersect.object.userData.floorUuid);
if (projectId && updatedFloor) {
if (!socket?.connected) {
// API
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
} else {
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}, 0)
}
}
};
const onDragOver = (event: any) => {
event.preventDefault();
};
if (activeModule === "builder") {
canvasElement.addEventListener("drop", onDrop);
canvasElement.addEventListener("dragover", onDragOver);
}
return () => {
canvasElement.removeEventListener("drop", onDrop);
canvasElement.removeEventListener("dragover", onDragOver);
};
}, [droppedDecal, camera, activeModule, controls]);
return (
<>
</>
)
}
export default DecalCreator

View File

@@ -1,50 +0,0 @@
import * as THREE from 'three';
import { Decal } from '@react-three/drei'
import { useLoader } from '@react-three/fiber';
import { useToggleView } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import defaultMaterial from '../../../assets/textures/floor/wall-tex.png';
import useModuleStore from '../../../store/useModuleStore';
function DecalInstance({ visible = true, decal, zPosition = decal.decalPosition[2] }: { visible?: boolean, decal: Decal, zPosition?: number }) {
const { setSelectedWall, setSelectedFloor, selectedDecal, setSelectedDecal } = useBuilderStore();
const { togglView } = useToggleView();
const { activeModule } = useModuleStore();
const material = useLoader(THREE.TextureLoader, defaultMaterial);
return (
<Decal
// debug
visible={visible}
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]}
rotation={[0, 0, decal.decalRotation]}
scale={[decal.decalScale, decal.decalScale, 0.01]}
userData={decal}
onClick={(e) => {
if (visible && !togglView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
setSelectedDecal(e.object);
setSelectedWall(null);
setSelectedFloor(null);
}
}
}}
onPointerMissed={() => {
if (selectedDecal && selectedDecal.userData.decalUuid === decal.decalUuid) {
setSelectedDecal(null);
}
}}
>
<meshBasicMaterial
map={material}
side={THREE.DoubleSide}
polygonOffset
polygonOffsetFactor={-1}
/>
</Decal>
)
}
export default DecalInstance

View File

@@ -0,0 +1,172 @@
import * as THREE from 'three';
import { Decal } from '@react-three/drei'
import { useToggleView, useToolMode } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { retrieveImage, storeImage } from '../../../../utils/indexDB/idbUtils';
import defaultMaterial from '../../../../assets/image/fallback/fallback decal 1.png';
import useModuleStore from '../../../../store/useModuleStore';
import { useEffect, useRef, useState } from 'react';
import { useDecalEventHandlers } from '../eventHandler/useDecalEventHandlers';
function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalPosition[2] }: { parent: Wall | Floor; visible?: boolean, decal: Decal, zPosition?: number }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const { selectedDecal, deletableDecal, setSelectedDecal, setDeletableDecal } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const decalRef = useRef<any>(null);
useEffect(() => {
if (selectedDecal?.decalData.decalUuid === decal.decalUuid && !selectedDecal.decalMesh) {
setSelectedDecal({ decalData: selectedDecal.decalData, decalMesh: decalRef.current });
}
// eslint-disable-next-line
}, [selectedDecal])
const { handlePointerMissed, handlePointerLeave, handleClick, handlePointerDown, handlePointerEnter } = useDecalEventHandlers({ parent, decal, visible });
const [texture, setTexture] = useState<THREE.Texture | null>(null);
const logDecalStatus = (decalId: string, status: string) => {
// console.log(decalId, status);
}
const loadDefaultTexture = () => {
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
defaultMaterial,
(fallbackTex) => {
fallbackTex.name = "default-decal";
setTexture(fallbackTex);
logDecalStatus(decal.decalId, 'default-loaded');
},
undefined,
(error) => {
console.error("Error loading default decal texture:", error);
}
);
};
const loadDecalTexture = async (decalId: string) => {
try {
const cachedTexture = THREE.Cache.get(decalId);
if (cachedTexture) {
setTexture(cachedTexture);
logDecalStatus(decalId, 'cache-loaded');
return;
}
const indexedDBTexture = await retrieveImage(decalId);
if (indexedDBTexture) {
const blobUrl = URL.createObjectURL(indexedDBTexture);
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
blobUrl,
(tex) => {
URL.revokeObjectURL(blobUrl);
tex.name = decalId;
THREE.Cache.add(decalId, tex);
setTexture(tex);
logDecalStatus(decalId, 'indexedDB-loaded');
},
undefined,
(error) => {
console.error(`Error loading texture from IndexedDB:`, error);
URL.revokeObjectURL(blobUrl);
loadFromBackend(decalId);
}
);
return;
}
loadFromBackend(decalId);
} catch (error) {
console.error("Error loading decal texture:", error);
loadDefaultTexture();
}
};
const loadFromBackend = (decalId: string) => {
const textureUrl = `${url_Backend_dwinzo}/api/v1/DecalImage/${decalId}`;
const textureLoader = new THREE.TextureLoader();
textureLoader.load(
textureUrl,
async (tex) => {
tex.name = decalId;
THREE.Cache.add(decalId, tex);
setTexture(tex);
logDecalStatus(decalId, 'backend-loaded');
try {
const response = await fetch(textureUrl);
const blob = await response.blob();
await storeImage(decalId, blob);
} catch (error) {
console.error("Error storing texture in IndexedDB:", error);
}
},
undefined,
(error) => {
echo.error(`Error loading texture from backend: ${decal.decalName}`);
loadDefaultTexture();
}
);
};
useEffect(() => {
if (decal.decalId) {
loadDecalTexture(decal.decalId);
} else {
loadDefaultTexture();
}
// eslint-disable-next-line
}, [decal.decalId]);
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (selectedDecal) setSelectedDecal(null);
}
if (toolMode !== '3D-Delete') {
if (deletableDecal) setDeletableDecal(null);
}
} else {
if (selectedDecal) setSelectedDecal(null);
if (deletableDecal) setDeletableDecal(null);
}
// eslint-disable-next-line
}, [toggleView, toolMode, activeModule, selectedDecal, deletableDecal]);
return (
<Decal
// debug
visible={visible}
ref={decalRef}
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]}
rotation={[0, 0, decal.decalRotation * (Math.PI / 180)]}
scale={[(decal.decalType.type === 'Floor' || zPosition < 0) ? -decal.decalScale : decal.decalScale, decal.decalScale, 0.01]}
userData={decal}
onPointerDown={(e) => { if (e.button === 0) handlePointerDown(e) }}
onClick={(e) => { handleClick(e) }}
onPointerEnter={(e) => { handlePointerEnter(e) }}
onPointerLeave={(e) => { handlePointerLeave(e) }}
onPointerMissed={() => handlePointerMissed()}
>
<meshBasicMaterial
map={texture}
side={THREE.DoubleSide}
polygonOffset
polygonOffsetFactor={-1}
transparent
opacity={decal.decalOpacity}
/>
</Decal>
)
}
export default DecalInstance

View File

@@ -0,0 +1,359 @@
import * as THREE from 'three';
import { CameraControls } from '@react-three/drei';
import { ThreeEvent, useFrame, useThree } from '@react-three/fiber';
import { useEffect, useState } from 'react';
import { useSocketStore, useToggleView, useToolMode } from '../../../../store/builder/store';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import useModuleStore from '../../../../store/useModuleStore';
import { getUserData } from '../../../../functions/getUserData';
import { useVersionContext } from '../../version/versionContext';
import { useParams } from 'react-router-dom';
import { useSceneContext } from '../../../scene/sceneContext';
import { detectModifierKeys } from '../../../../utils/shortcutkeys/detectModifierKeys';
import handleDecalPositionSnap from '../functions/handleDecalPositionSnap';
import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
export function useDecalEventHandlers({
parent,
decal,
visible,
}: {
parent: Wall | Floor;
decal: Decal;
visible: boolean;
}) {
const { wallStore, floorStore } = useSceneContext();
const { removeDecal: removeDecalInWall, updateDecalPosition: updateDecalPositionInWall, getWallById, addDecal: addDecalToWall } = wallStore();
const { removeDecal: removeDecalInFloor, updateDecalPosition: updateDecalPositionInFloor, getFloorById, addDecal: addDecalToFloor } = floorStore();
const { setSelectedWall, setSelectedFloor, setSelectedDecal, setDeletableDecal, deletableDecal, selectedDecal, setDecalDragState, decalDragState } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { userId, organization } = getUserData();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { projectId } = useParams();
const { socket } = useSocketStore();
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "">("");
const { raycaster, pointer, camera, scene, gl, controls } = useThree();
useFrame(() => {
if (activeModule !== 'builder' || toggleView || !decalDragState.isDragging || !selectedDecal || selectedDecal.decalData.decalUuid !== decal.decalUuid) return;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const wallIntersect = intersects.find(i => i.object.userData?.wallUuid);
const floorIntersect = intersects.find(i => i.object.userData?.floorUuid);
let offset = decalDragState.dragOffset || new THREE.Vector3(0, 0, 0);
if (wallIntersect) {
const wallUuid = wallIntersect.object.userData.wallUuid;
const point = wallIntersect.object.worldToLocal(wallIntersect.point.clone());
let finalPos;
if (keyEvent === "Ctrl") {
finalPos = handleDecalPositionSnap(point, offset, parent, decal, 0.05)
} else {
finalPos = point
}
if ("wallUuid" in parent && parent.wallUuid === wallUuid && decal.decalType.type === 'Wall') {
updateDecalPositionInWall(decal.decalUuid, [finalPos.x + offset.x, finalPos.y + offset.y, decal.decalPosition[2]]);
} else if (decal.decalType.type === 'Wall' && wallUuid) {
deleteDecal(decal.decalUuid, parent);
const addedDecal = addDecalToWall(wallUuid, {
...decal,
decalPosition: [finalPos.x + offset.x, finalPos.y + offset.y, decal.decalPosition[2]],
decalType: { type: 'Wall', wallUuid: wallUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
} else if (decal.decalType.type === 'Floor' && wallUuid) {
deleteDecal(decal.decalUuid, parent);
const wall = getWallById(wallUuid);
if (!wall) return;
const addedDecal = addDecalToWall(wallUuid, {
...decal,
decalPosition: [finalPos.x + offset.x, finalPos.y + offset.y, wall.wallThickness / 2 + 0.001],
decalType: { type: 'Wall', wallUuid: wallUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
}
} else if (floorIntersect) {
const floorUuid = floorIntersect.object.userData.floorUuid;
const point = floorIntersect.object.worldToLocal(floorIntersect.point.clone());
let finalPos;
if (keyEvent === "Ctrl") {
finalPos = handleDecalPositionSnap(point, offset, parent, decal, 0.25)
} else {
finalPos = point
}
if ("floorUuid" in parent && parent.floorUuid === floorUuid && decal.decalType.type === 'Floor') {
updateDecalPositionInFloor(decal.decalUuid, [finalPos.x + offset.x, finalPos.y + offset.y, decal.decalPosition[2]]);
} else if (decal.decalType.type === 'Floor' && floorUuid) {
deleteDecal(decal.decalUuid, parent);
const addedDecal = addDecalToFloor(floorUuid, {
...decal,
decalPosition: [finalPos.x + offset.x, finalPos.y + offset.y, decal.decalPosition[2]],
decalType: { type: 'Floor', floorUuid: floorUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
} else if (decal.decalType.type === 'Wall' && floorUuid) {
deleteDecal(decal.decalUuid, parent);
const floor = getFloorById(floorUuid);
if (!floor) return;
const addedDecal = addDecalToFloor(floorUuid, {
...decal,
decalPosition: [finalPos.x + offset.x, finalPos.y + offset.y, -0.001],
decalType: { type: 'Floor', floorUuid: floorUuid }
});
if (addedDecal) {
setSelectedDecal({ decalMesh: null, decalData: addedDecal })
}
}
}
});
const handlePointerUp = () => {
if (controls) {
(controls as CameraControls).enabled = true;
}
if (decalDragState.isDragging) {
setDecalDragState(false, null, null);
if ('wallUuid' in parent) {
setTimeout(() => {
const updatedWall = getWallById(parent.wallUuid);
if (updatedWall) {
if (projectId) {
if (!socket?.connected) {
// API
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
}
}, 0)
} else if ('floorUuid' in parent) {
setTimeout(() => {
const updatedFloor = parent;
if (projectId && updatedFloor) {
if (!socket?.connected) {
// API
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
} else {
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}, 0)
}
}
};
const deleteDecal = (decalUuid: string, parent: Wall | Floor) => {
if ('wallUuid' in parent) {
const updatedWall = removeDecalInWall(decalUuid);
if (projectId && updatedWall) {
if (!socket?.connected) {
// API
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
} else if ('floorUuid' in parent) {
const updatedFloor = removeDecalInFloor(decalUuid);
if (projectId && updatedFloor) {
if (!socket?.connected) {
// API
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
} else {
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
}
}
}
const handlePointerDown = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid && toolMode === 'cursor' && (selectedDecal && selectedDecal.decalData.decalUuid === decal.decalUuid)) {
e.stopPropagation();
setDecalDragState(true, decal.decalUuid, null);
if (controls) {
(controls as CameraControls).enabled = false;
}
setSelectedWall(null);
setSelectedFloor(null);
const localIntersect = e.object.worldToLocal(e.point.clone());
let dragOffset = new THREE.Vector3(decal.decalPosition[0] - localIntersect.x, decal.decalPosition[1] - localIntersect.y, 0);
setDecalDragState(true, decal.decalUuid, dragOffset);
}
}
};
const handleClick = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
if (toolMode === 'cursor') {
setSelectedDecal({ decalMesh: e.object, decalData: decal });
setSelectedWall(null);
setSelectedFloor(null);
} else if (toolMode === '3D-Delete') {
deleteDecal(e.object.userData.decalUuid, parent);
}
}
}
};
const handlePointerEnter = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
if (toolMode === '3D-Delete') {
setDeletableDecal(e.object);
}
}
}
};
const handlePointerLeave = (e: ThreeEvent<MouseEvent>) => {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.decalUuid) {
e.stopPropagation();
if (toolMode === '3D-Delete' && deletableDecal && deletableDecal?.userData.decalUuid === e.object.userData.decalUuid) {
setDeletableDecal(null);
}
}
}
};
const handlePointerMissed = () => {
if (selectedDecal && selectedDecal.decalMesh && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) {
setSelectedDecal(null);
setKeyEvent("");
}
};
const onKeyUp = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "") {
setKeyEvent("");
} else if (keyCombination === "Ctrl") {
setKeyEvent(keyCombination);
}
};
const onKeyDown = (event: KeyboardEvent) => {
const keyCombination = detectModifierKeys(event);
if (keyCombination !== keyEvent) {
if (keyCombination === "Ctrl") {
setKeyEvent(keyCombination);
} else {
setKeyEvent("");
}
}
}
useEffect(() => {
const canvasElement = gl.domElement;
if (activeModule === 'builder' && !toggleView && selectedDecal && selectedDecal.decalData.decalUuid === decal.decalUuid) {
canvasElement.addEventListener('pointerup', handlePointerUp);
canvasElement?.addEventListener("keyup", onKeyUp);
canvasElement.addEventListener("keydown", onKeyDown);
}
return () => {
canvasElement.removeEventListener('pointerup', handlePointerUp);
canvasElement?.removeEventListener("keyup", onKeyUp);
canvasElement.removeEventListener("keydown", onKeyDown);
};
// eslint-disable-next-line
}, [gl, activeModule, toggleView, selectedDecal, camera, controls, visible, parent, decal, decalDragState]);
return {
handlePointerDown,
handleClick,
handlePointerEnter,
handlePointerLeave,
handlePointerMissed,
deleteDecal
};
}

View File

@@ -0,0 +1,43 @@
import * as THREE from 'three';
function snapToFixedPoint(
position: [number, number, number],
snapInterval: number = 0.1
): [number, number, number] {
return [
Math.round(position[0] / snapInterval) * snapInterval,
Math.round(position[1] / snapInterval) * snapInterval,
Math.round(position[2] / snapInterval) * snapInterval,
];
}
function handleDecalPositionSnap(
point: THREE.Vector3,
offset: THREE.Vector3,
parent: Wall | Floor,
decal: Decal,
snapInterval: number = 0.1
): THREE.Vector3 {
let rawPos: [number, number, number];
if ("wallUuid" in parent) {
// snap relative to wall
rawPos = [
point.x + offset.x,
point.y + offset.y,
decal.decalPosition[2], // keep depth as-is
];
} else {
// snap relative to floor
rawPos = [
point.x + offset.x,
point.y + offset.y,
decal.decalPosition[2],
];
}
const snapped = snapToFixedPoint(rawPos, snapInterval);
return new THREE.Vector3(snapped[0], snapped[1], snapped[2]);
}
export default handleDecalPositionSnap;

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const arc = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arc-aisle') return null;
@@ -63,8 +69,8 @@ function ArcAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const arrow = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'arrow-aisle') return null;
@@ -50,8 +56,8 @@ function ArrowAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Instances, Instance } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const { arrowGeometry, arrowInstances } = useMemo(() => {
const result = {
@@ -68,8 +74,8 @@ function ArrowsAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
};

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const circle = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'circle-aisle') return null;
@@ -38,8 +44,8 @@ function CircleAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Instances, Instance } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const dashInstances = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dashed-aisle') return [];
@@ -43,8 +49,8 @@ function DashedAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
};

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Instance, Instances } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const dotPositions = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'dotted-aisle') return [];
@@ -27,8 +33,8 @@ function DottedAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,13 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const arrows = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'junction-aisle') return null;
@@ -85,8 +91,8 @@ function JunctionAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -1,5 +1,5 @@
import * as THREE from 'three';
import { useMemo, useRef } from 'react';
import { useEffect, useMemo, useRef } from 'react';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../../types/world/worldConstants';
import { useToolMode } from '../../../../../../store/builder/store';
@@ -8,7 +8,14 @@ import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore
function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
const aisleRef = useRef<THREE.Group>(null);
const { toolMode } = useToolMode();
const { setSelectedAisle, hoveredPoint } = useBuilderStore();
const { setSelectedAisle, hoveredPoint, selectedAisle } = useBuilderStore();
useEffect(() => {
if (selectedAisle?.aisleData.aisleUuid === aisle.aisleUuid && !selectedAisle.aisleMesh) {
setSelectedAisle({ aisleData: selectedAisle.aisleData, aisleMesh: aisleRef.current });
}
}, [selectedAisle])
const shape = useMemo(() => {
if (aisle.points.length < 2 || aisle.type.aisleType !== 'solid-aisle') return null;
@@ -35,8 +42,8 @@ function SolidAisle({ aisle }: { readonly aisle: Aisle }) {
}, [aisle]);
const handleClick = () => {
if (toolMode === 'move' && !hoveredPoint) {
setSelectedAisle(aisleRef.current);
if ((toolMode === 'move' || toolMode === 'cursor') && !hoveredPoint) {
setSelectedAisle({ aisleMesh: aisleRef.current, aisleData: aisle });
}
}

View File

@@ -10,7 +10,7 @@ import ReferenceAisle from './referenceAisle';
import ReferencePoint from '../../point/reference/referencePoint';
import { getUserData } from '../../../../functions/getUserData';
// import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi';
import { upsertAisleApi } from '../../../../services/factoryBuilder/aisle/upsertAisleApi';
function AisleCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@@ -93,431 +93,70 @@ function AisleCreator() {
}
}
if (aisleType === 'solid-aisle') {
const aisleConfigs: Record<string, (color: string) => any> = {
"solid-aisle": (color) => ({ aisleType: "solid-aisle", aisleColor: color, aisleWidth }),
"dashed-aisle": (color) => ({ aisleType: "dashed-aisle", aisleColor: color, aisleWidth, dashLength, gapLength }),
"dotted-aisle": (color) => ({ aisleType: "dotted-aisle", aisleColor: color, dotRadius, gapLength }),
"arrow-aisle": (color) => ({ aisleType: "arrow-aisle", aisleColor: color, aisleWidth }),
"arrows-aisle": (color) => ({ aisleType: "arrows-aisle", aisleColor: color, aisleWidth, aisleLength, gapLength }),
"arc-aisle": (color) => ({ aisleType: "arc-aisle", aisleColor: color, aisleWidth, isFlipped }),
"circle-aisle": (color) => ({ aisleType: "circle-aisle", aisleColor: color, aisleWidth }),
"junction-aisle": (color) => ({ aisleType: "junction-aisle", aisleColor: color, aisleWidth, isFlipped })
};
if (!aisleType || !(aisleType in aisleConfigs)) return;
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
return;
}
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: aisleConfigs[aisleType](aisleColor)
};
addAisle(aisle);
push2D({
type: "Draw",
actions: [{
actionType: "Line-Create",
point: { type: "Aisle", lineData: aisle, timeStamp: new Date().toISOString() }
}]
});
addAilseToBackend(aisle);
setTempPoints([newPoint]);
};
const addAilseToBackend = (aisle: Aisle) => {
if (projectId) {
if (!socket?.connected) {
// API
upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'solid-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
};
addAisle(aisle);
// SOCKET
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'dashed-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'dashed-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth,
dashLength: dashLength,
gapLength: gapLength
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'dotted-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'dotted-aisle',
aisleColor: aisleColor,
dotRadius: dotRadius,
gapLength: gapLength
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'arrow-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'arrow-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'arrows-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'arrows-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth,
aisleLength: aisleLength,
gapLength: gapLength
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'arc-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'arc-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth,
isFlipped: isFlipped
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'circle-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'circle-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
}
} else if (aisleType === 'junction-aisle') {
if (tempPoints.length === 0) {
setTempPoints([newPoint]);
setIsCreating(true);
} else {
const aisle: Aisle = {
aisleUuid: THREE.MathUtils.generateUUID(),
points: [tempPoints[0], newPoint],
type: {
aisleType: 'junction-aisle',
aisleColor: aisleColor,
aisleWidth: aisleWidth,
isFlipped: isFlipped
}
};
addAisle(aisle);
push2D({
type: 'Draw',
actions: [
{
actionType: 'Line-Create',
point: {
type: 'Aisle',
lineData: aisle,
timeStamp: new Date().toISOString(),
}
}
],
})
if (projectId) {
// API
// upsertAisleApi(aisle.aisleUuid, aisle.points, aisle.type, projectId, selectedVersion?.versionId || '')
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
})
}
setTempPoints([newPoint]);
socket.emit("v1:model-aisle:add", {
projectId,
versionId: selectedVersion?.versionId || "",
userId,
organization,
aisleUuid: aisle.aisleUuid,
points: aisle.points,
type: aisle.type
});
}
}
};
}
const onContext = (event: any) => {
event.preventDefault();

View File

@@ -1,7 +1,7 @@
import * as THREE from "three"
import { useEffect } from 'react'
import { getFloorAssets } from '../../../services/factoryBuilder/asset/floorAsset/getFloorItemsApi';
import { useLoadingProgress, useRenameModeStore, useSelectedFloorItem, useSelectedItem, useSocketStore } from '../../../store/builder/store';
import { useLoadingProgress, useRenameModeStore, useSelectedItem, useSocketStore } from '../../../store/builder/store';
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
import { FloorItems, RefMesh } from "../../../types/world/worldTypes";
@@ -15,6 +15,7 @@ import { useLeftData, useTopData } from "../../../store/visualization/useZone3DW
import { getUserData } from "../../../functions/getUserData";
import { useSceneContext } from "../../scene/sceneContext";
import { useVersionContext } from "../version/versionContext";
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
const gltfLoaderWorker = new Worker(new URL("../../../services/factoryBuilder/webWorkers/gltfLoaderWorker.js", import.meta.url));
@@ -28,7 +29,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
const { selectedVersion } = selectedVersionStore();
const { setAssets, addAsset, clearAssets } = assetStore();
const { addEvent, clearEvents } = eventStore();
const { setSelectedFloorItem } = useSelectedFloorItem();
const { setSelectedFloorAsset } = useBuilderStore();
const { selectedItem, setSelectedItem } = useSelectedItem();
const { projectId } = useParams();
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
@@ -363,6 +364,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
setLeft(relativeX);
}
};
const onMouseUp = (evt: any) => {
setIsRenameMode(false);
}
@@ -376,7 +378,7 @@ function AssetsGroup({ plane }: { readonly plane: RefMesh }) {
if ((controls as CameraControls)) {
const target = (controls as CameraControls).getTarget(new THREE.Vector3());
(controls as CameraControls).setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
setSelectedFloorAsset(null);
}
}

View File

@@ -7,14 +7,14 @@ import * as CONSTANTS from "../../../../types/world/worldConstants";
import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator";
import { getUserData } from "../../../../functions/getUserData";
// import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
async function addAssetModel(
scene: THREE.Scene,
raycaster: THREE.Raycaster,
camera: THREE.Camera,
pointer: THREE.Vector2,
socket: Socket<any>,
socket: Socket<any> | null,
selectedItem: any,
setSelectedItem: any,
addEvent: (event: EventsSchema) => void,
@@ -106,7 +106,7 @@ async function handleModelLoad(
selectedItem: any,
addEvent: (event: EventsSchema) => void,
addAsset: (asset: Asset) => void,
socket: Socket<any>,
socket: Socket<any> | null,
versionId: string,
projectId?: string,
userId?: string
@@ -140,22 +140,6 @@ async function handleModelLoad(
opacity: 1,
};
// API
// await setAssetsApi(
// organization,
// newFloorItem.modelUuid,
// newFloorItem.modelName,
// newFloorItem.assetId,
// newFloorItem.position,
// { x: 0, y: 0, z: 0 },
// false,
// true,
// );
// SOCKET
if (selectedItem.type) {
const data = PointsCalculator(
selectedItem.type,
@@ -449,14 +433,34 @@ async function handleModelLoad(
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
isLocked: false,
isVisible: true,
socketId: socket.id,
socketId: socket?.id || '',
eventData: eventData,
versionId: versionId,
projectId: projectId,
userId: userId,
};
socket.emit("v1:model-asset:add", completeData);
if (!socket?.connected) {
// API
setAssetsApi({
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
assetId: newFloorItem.assetId,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
eventData: eventData,
isLocked: false,
isVisible: true,
versionId: versionId,
projectId: projectId,
});
} else {
// SOCKET
socket.emit("v1:model-asset:add", completeData);
}
const asset: Asset = {
modelUuid: completeData.modelUuid,
@@ -482,13 +486,32 @@ async function handleModelLoad(
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
isLocked: false,
isVisible: true,
socketId: socket.id,
socketId: socket?.id || '',
versionId: versionId,
projectId: projectId,
userId: userId,
};
socket.emit("v1:model-asset:add", data);
if (!socket?.connected) {
// API
setAssetsApi({
modelUuid: newFloorItem.modelUuid,
modelName: newFloorItem.modelName,
position: newFloorItem.position,
assetId: newFloorItem.assetId,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
isLocked: false,
isVisible: true,
versionId: versionId,
projectId: projectId,
});
} else {
// SOCKET
socket.emit("v1:model-asset:add", data);
}
const asset = {
modelUuid: data.modelUuid,

View File

@@ -3,9 +3,8 @@ import { CameraControls } from '@react-three/drei';
import { ThreeEvent, useThree } from '@react-three/fiber';
import { useCallback, useEffect, useRef } from 'react';
import { useActiveTool, useDeletableFloorItem, useSelectedFloorItem, useToggleView, useZoneAssetId } from '../../../../../../store/builder/store';
import { useActiveTool, useResourceManagementId, useToggleView, useZoneAssetId, useSocketStore } from '../../../../../../store/builder/store';
import useModuleStore, { useSubModuleStore } from '../../../../../../store/useModuleStore';
import { useSocketStore } from '../../../../../../store/builder/store';
import { useSceneContext } from '../../../../../scene/sceneContext';
import { useProductContext } from '../../../../../simulation/products/productContext';
import { useVersionContext } from '../../../../version/versionContext';
@@ -13,9 +12,10 @@ import { useParams } from 'react-router-dom';
import { getUserData } from '../../../../../../functions/getUserData';
import { useLeftData, useTopData } from '../../../../../../store/visualization/useZone3DWidgetStore';
import { useSelectedAsset } from '../../../../../../store/simulation/useSimulationStore';
import { upsertProductOrEventApi } from '../../../../../../services/simulation/products/UpsertProductOrEventApi';
import { useBuilderStore } from '../../../../../../store/builder/useBuilderStore';
// import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
import { upsertProductOrEventApi } from '../../../../../../services/simulation/products/UpsertProductOrEventApi';
import { deleteFloorItem } from '../../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi';
export function useModelEventHandlers({
boundingBox,
@@ -34,13 +34,13 @@ export function useModelEventHandlers({
const { socket } = useSocketStore();
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
const { push3D } = undoRedo3DStore();
const { getAssetById, removeAsset } = assetStore();
const { removeAsset } = assetStore();
const { zoneAssetId, setZoneAssetId } = useZoneAssetId();
const { resourceManagementId, setResourceManagementId } = useResourceManagementId();
const { removeEvent, getEventByModelUuid } = eventStore();
const { getIsEventInProduct, addPoint, deleteEvent } = productStore();
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { deletableFloorAsset, setDeletableFloorAsset, selectedFloorAsset, setSelectedFloorAsset } = useBuilderStore();
const { selectedProductStore } = useProductContext();
const { selectedProduct } = selectedProductStore();
const { selectedVersionStore } = useVersionContext();
@@ -74,19 +74,28 @@ export function useModelEventHandlers({
if (zoneAssetId.id === asset.modelUuid) {
handleDblClick(asset);
}
}, [zoneAssetId])
useEffect(() => {
if (!selectedFloorItem) {
if (!resourceManagementId) return
if (resourceManagementId === asset.modelUuid) {
handleDblClick(asset);
}
}, [resourceManagementId])
useEffect(() => {
if (!selectedFloorAsset) {
setZoneAssetId(null);
}
}, [selectedFloorItem])
}, [selectedFloorAsset])
const handleDblClick = (asset: Asset) => {
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && (activeModule === 'builder' || (activeModule === 'simulation' && resourceManagementId))) {
const frontView = false;
if (frontView) {
const size = boundingBox.getSize(new THREE.Vector3());
const center = boundingBox.getCenter(new THREE.Vector3());
@@ -107,6 +116,7 @@ export function useModelEventHandlers({
paddingBottom: 5,
paddingRight: 5,
});
} else {
const collisionPos = new THREE.Vector3();
@@ -126,32 +136,37 @@ export function useModelEventHandlers({
(controls as CameraControls).setLookAt(newCameraPos.x, newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true);
}
setSelectedFloorItem(groupRef.current);
setSelectedFloorAsset(groupRef.current);
setResourceManagementId("");
}
};
const handleClick = async (evt: ThreeEvent<MouseEvent>, asset: Asset) => {
if (leftDrag.current || toggleView) return;
if (activeTool === 'delete' && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
if (activeTool === 'delete' && deletableFloorAsset && deletableFloorAsset.uuid === asset.modelUuid) {
//REST
if (!socket?.connected) {
// const response = await deleteFloorItem(organization, asset.modelUuid, asset.modelName);
// REST
//SOCKET
deleteFloorItem(organization, asset.modelUuid, asset.modelName);
const data = {
organization,
modelUuid: asset.modelUuid,
modelName: asset.modelName,
socketId: socket.id,
userId,
versionId: selectedVersion?.versionId || '',
projectId
} else {
// SOCKET
const data = {
organization,
modelUuid: asset.modelUuid,
modelName: asset.modelName,
socketId: socket.id,
userId,
versionId: selectedVersion?.versionId || '',
projectId
}
socket.emit('v1:model-asset:delete', data)
}
const response = socket.emit('v1:model-asset:delete', data)
removeEvent(asset.modelUuid);
const updatedEvents = deleteEvent(asset.modelUuid);
@@ -164,27 +179,24 @@ export function useModelEventHandlers({
);
})
if (response) {
removeAsset(asset.modelUuid);
removeAsset(asset.modelUuid);
push3D({
type: 'Scene',
actions: [
{
module: "builder",
actionType: "Asset-Delete",
asset: {
type: "Asset",
assetData: asset,
timeStap: new Date().toISOString()
}
push3D({
type: 'Scene',
actions: [
{
module: "builder",
actionType: "Asset-Delete",
asset: {
type: "Asset",
assetData: asset,
timeStap: new Date().toISOString()
}
]
});
}
]
});
echo.success("Model Removed!");
}
echo.success("Model Removed!");
} else if (activeModule === 'simulation' && subModule === "simulations" && activeTool === 'pen') {
if (asset.eventData && asset.eventData.type === 'Conveyor') {
@@ -224,19 +236,19 @@ export function useModelEventHandlers({
const handlePointerOver = useCallback((asset: Asset) => {
if (activeTool === "delete" && activeModule === 'builder') {
if (deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
if (deletableFloorAsset && deletableFloorAsset.uuid === asset.modelUuid) {
return;
} else {
setDeletableFloorItem(groupRef.current);
setDeletableFloorAsset(groupRef.current);
}
}
}, [activeTool, activeModule, deletableFloorItem]);
}, [activeTool, activeModule, deletableFloorAsset]);
const handlePointerOut = useCallback((evt: ThreeEvent<MouseEvent>, asset: Asset) => {
if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorItem && deletableFloorItem.uuid === asset.modelUuid) {
setDeletableFloorItem(null);
if (evt.intersections.length === 0 && activeTool === "delete" && deletableFloorAsset && deletableFloorAsset.uuid === asset.modelUuid) {
setDeletableFloorAsset(null);
}
}, [activeTool, deletableFloorItem]);
}, [activeTool, deletableFloorAsset]);
const handleContextMenu = (asset: Asset, evt: ThreeEvent<MouseEvent>) => {
if (rightDrag.current || toggleView) return;

View File

@@ -2,8 +2,9 @@ import * as THREE from 'three';
import { useEffect, useRef, useState } from 'react';
import { retrieveGLTF, storeGLTF } from '../../../../../utils/indexDB/idbUtils';
import { GLTF, GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { useDeletableFloorItem, useSelectedAssets, useSelectedFloorItem, useToggleView, useToolMode } from '../../../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../../../store/builder/store';
import { AssetBoundingBox } from '../../functions/assetBoundingBox';
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import useModuleStore from '../../../../../store/useModuleStore';
import { useSceneContext } from '../../../../scene/sceneContext';
import { SkeletonUtils } from 'three-stdlib';
@@ -19,15 +20,13 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { assetStore } = useSceneContext();
const { resetAnimation } = assetStore();
const { setDeletableFloorItem } = useDeletableFloorItem();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { resetAnimation, selectedAssets } = assetStore();
const { setDeletableFloorAsset, selectedFloorAsset, setSelectedFloorAsset } = useBuilderStore();
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
const [isSelected, setIsSelected] = useState(false);
const groupRef = useRef<THREE.Group>(null);
const [fieldData, setFieldData] = useState<any>();
const { selectedAssets } = useSelectedAssets();
useEffect(() => {
if (!fieldData && asset.eventData) {
@@ -53,17 +52,17 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
}, [asset.modelUuid, fieldData])
useEffect(() => {
setDeletableFloorItem(null);
if (selectedFloorItem === null || selectedFloorItem.userData.modelUuid !== asset.modelUuid) {
setDeletableFloorAsset(null);
if (selectedFloorAsset === null || selectedFloorAsset.userData.modelUuid !== asset.modelUuid) {
resetAnimation(asset.modelUuid);
}
}, [activeModule, toolMode, selectedFloorItem])
}, [activeModule, toolMode, selectedFloorAsset])
useEffect(() => {
if (selectedFloorItem && selectedFloorItem.userData.modelUuid === asset.modelUuid) {
setSelectedFloorItem(groupRef.current);
if (selectedFloorAsset && selectedFloorAsset.userData.modelUuid === asset.modelUuid) {
setSelectedFloorAsset(groupRef.current);
}
}, [isRendered, selectedFloorItem])
}, [isRendered, selectedFloorAsset])
useEffect(() => {
if (selectedAssets.length > 0) {
@@ -88,6 +87,10 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
}
}, [gltfScene]);
const logModelStatus = (modelId: string, status: string) => {
// console.log(modelId, status);
}
useEffect(() => {
// Calculate Bounding Box
const calculateBoundingBox = (scene: THREE.Object3D) => {
@@ -103,6 +106,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
clone.animations = cachedModel.animations || [];
setGltfScene(clone);
calculateBoundingBox(clone);
logModelStatus(assetId, 'cache-loaded');
return;
}
@@ -118,6 +122,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
logModelStatus(assetId, 'indexedDB-loaded');
},
undefined,
(error) => {
@@ -140,6 +145,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
THREE.Cache.add(assetId, gltf);
setGltfScene(gltf.scene.clone());
calculateBoundingBox(gltf.scene);
logModelStatus(assetId, 'backend-loaded');
})
.catch((error) => {
console.error(

View File

@@ -2,9 +2,10 @@ import { useEffect, useRef, useState } from "react";
import { useThree, useFrame } from "@react-three/fiber";
import { Group, Vector3 } from "three";
import { CameraControls } from '@react-three/drei';
import { useLimitDistance, useRenderDistance, useSelectedFloorItem, useToggleView } from '../../../../store/builder/store';
import { useLimitDistance, useRenderDistance } from '../../../../store/builder/store';
import { useSelectedAsset } from '../../../../store/simulation/useSimulationStore';
import { useSceneContext } from '../../../scene/sceneContext';
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import Model from './model/model';
import { GLTFLoader } from "three/examples/jsm/Addons";
@@ -16,7 +17,7 @@ function Models({ loader }: { loader: GLTFLoader }) {
const assetGroupRef = useRef<Group>(null);
const { assetStore } = useSceneContext();
const { assets } = assetStore();
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
const { selectedFloorAsset, setSelectedFloorAsset } = useBuilderStore();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const { limitDistance } = useLimitDistance();
const { renderDistance } = useRenderDistance();
@@ -49,10 +50,10 @@ function Models({ loader }: { loader: GLTFLoader }) {
ref={assetGroupRef}
onPointerMissed={(e) => {
e.stopPropagation();
if (selectedFloorItem) {
if (selectedFloorAsset) {
const target = (controls as CameraControls).getTarget(new Vector3());
(controls as CameraControls).setTarget(target.x, 0, target.z, true);
setSelectedFloorItem(null);
setSelectedFloorAsset(null);
}
if (selectedAsset) {
clearSelectedAsset();

View File

@@ -33,13 +33,14 @@ import AssetsGroup from "./asset/assetsGroup";
import DxfFile from "./dfx/LoadBlueprint";
import AislesGroup from "./aisle/aislesGroup";
import WallGroup from "./wall/wallGroup";
import WallAssetGroup from "./wallAsset/wallAssetGroup";
import FloorGroup from "./floor/floorGroup";
import ZoneGroup from "./zone/zoneGroup";
import Decal from "./Decal/decal";
import { useParams } from "react-router-dom";
import { useBuilderStore } from "../../store/builder/useBuilderStore";
import { getUserData } from "../../functions/getUserData";
import WallAssetGroup from "./wallAsset/wallAssetGroup";
export default function Builder() {
const state = useThree<Types.ThreeState>();
@@ -106,6 +107,8 @@ export default function Builder() {
</Geometry>
</mesh>
<Decal />
<AislesGroup />
<FloorGroup />

View File

@@ -9,6 +9,8 @@ import { useVersionContext } from '../version/versionContext';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useSceneContext } from '../../scene/sceneContext';
import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
/**
* DxfFile component handles the rendering and manipulation of DXf file data in a 3D scene.
* It processes the DXF data to create points and lines representing walls and allows
@@ -39,20 +41,25 @@ const DxfFile = () => {
if (dfxWallGenerate) {
dfxWallGenerate.map((wall: Wall) => {
const data = {
wallData: wall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
addWall(wall);
socket.emit('v1:model-Wall:add', data);
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall);
} else {
// SOCKET
const data = {
wallData: wall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
addWall(wall);
socket.emit('v1:model-Wall:add', data);
}
}
})
}
}, [dfxWallGenerate]);

View File

@@ -1,12 +1,13 @@
import { useMemo } from "react";
import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, } from "three";
import { Shape, Vector2, DoubleSide, TextureLoader, RepeatWrapping, SRGBColorSpace, NoColorSpace, ExtrudeGeometry, Vector3, Euler, } from "three";
import { useLoader } from "@react-three/fiber";
import { Extrude } from "@react-three/drei";
import useModuleStore from "../../../../../store/useModuleStore";
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import { useToggleView } from "../../../../../store/builder/store";
import * as Constants from "../../../../../types/world/worldConstants";
import DecalInstance from "../../../Decal/decalInstance/decalInstance";
import texturePath from "../../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../../assets/textures/floor/black.png";
import material1 from "../../../../../assets/textures/floor/factory wall texture.jpg";
@@ -28,7 +29,7 @@ import material4MetalicMap from "../../../../../assets/textures/floor/tex3/metal
import material4NormalMap from "../../../../../assets/textures/floor/tex3/metal_plate_nor_gl_1k.png";
function FloorInstance({ floor }: { floor: Floor }) {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { selectedFloor, setSelectedFloor, setSelectedDecal } = useBuilderStore();
const savedTheme = localStorage.getItem("theme");
@@ -67,20 +68,26 @@ function FloorInstance({ floor }: { floor: Floor }) {
},
};
const shape = useMemo(() => {
const shape = new Shape();
const shapeData = useMemo(() => {
const points = floor.points.map((p) => new Vector2(p.position[0], p.position[2]));
if (points.length < 3) return null;
shape.moveTo(points[0].x, points[0].y);
for (let i = 1; i < points.length; i++) {
shape.lineTo(points[i].x, points[i].y);
const centroidX = points.reduce((sum, p) => sum + p.x, 0) / points.length;
const centroidY = points.reduce((sum, p) => sum + p.y, 0) / points.length;
const relativePoints = points.map((p) => new Vector2(p.x - centroidX, p.y - centroidY));
const shape = new Shape();
shape.moveTo(relativePoints[0].x, relativePoints[0].y);
for (let i = 1; i < relativePoints.length; i++) {
shape.lineTo(relativePoints[i].x, relativePoints[i].y);
}
return shape;
return { shape, center: [centroidX, centroidY] };
}, [floor]);
const textureScale = Constants.floorConfig.textureScale;
// Helper function to handle texture maps and filter out null values
function getMaterialMaps(material: any, defaultMap: any) {
const materialMap = material.map || defaultMap;
const normalMap = material.normalMap || null;
@@ -90,26 +97,18 @@ function FloorInstance({ floor }: { floor: Floor }) {
return [materialMap, normalMap, roughnessMap, metalnessMap].filter((texture): texture is string => texture !== null);
}
// Default material map
const defaultMaterialMap = materials["Default Material"].map;
// Get top and side material maps
const topMaterial = materials[floor.topMaterial];
const sideMaterial = materials[floor.sideMaterial];
// Get the filtered lists for top and side textures
const topTexturesList = getMaterialMaps(topMaterial, defaultMaterialMap);
const sideTexturesList = getMaterialMaps(sideMaterial, defaultMaterialMap);
// Use loader to load top and side textures
const [topTexture, topNormalTexture, topRoughnessTexture, topMetalicTexture] = useLoader(TextureLoader, topTexturesList);
const [sideTexture, sideNormalTexture, sideRoughnessTexture, sideMetalicTexture] = useLoader(TextureLoader, sideTexturesList);
// Early exit if materials are missing
if (!materials[floor.topMaterial] || !materials[floor.sideMaterial]) return null;
// Combine and pair textures with their corresponding material
const textureMaterialMap = [
{
textures: [
@@ -131,7 +130,6 @@ function FloorInstance({ floor }: { floor: Floor }) {
},
];
// Apply texture settings
textureMaterialMap.forEach(({ textures, materialKey }) => {
const tileScale = materials[materialKey]?.textureTileScale ?? [
textureScale,
@@ -143,23 +141,39 @@ function FloorInstance({ floor }: { floor: Floor }) {
tex.wrapS = tex.wrapT = RepeatWrapping;
tex.repeat.set(tileScale[0], tileScale[1]);
tex.anisotropy = 16;
// First texture is always the color map (use SRGB), others should be linear
tex.colorSpace = idx < 1 ? SRGBColorSpace : NoColorSpace;
});
});
if (!shape) return null;
const geometry = useMemo(() => {
if (!shapeData) return null;
return new ExtrudeGeometry(shapeData.shape, {
depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1,
bevelEnabled: floor.isBeveled,
bevelSegments: floor.bevelStrength,
bevelOffset: -0.1,
bevelSize: 0.1,
bevelThickness: 0.1,
});
}, [shapeData, floor]);
if (!geometry) return null;
return (
<mesh
castShadow
receiveShadow
geometry={geometry}
name={`Floor-${floor.floorUuid}`}
rotation={[Math.PI / 2, 0, 0]}
position={[0, !floor.isBeveled ? floor.floorDepth - 0.1 : floor.floorDepth - 0.2, 0,]}
position={[
shapeData?.center[0] ?? 0,
!floor.isBeveled ? floor.floorDepth - 0.1 : floor.floorDepth - 0.2,
shapeData?.center[1] ?? 0,
]}
userData={floor}
onDoubleClick={(e) => {
if (!togglView && activeModule === "builder") {
if (!toggleView && activeModule === "builder") {
if (e.object.userData.floorUuid) {
e.stopPropagation();
setSelectedFloor(e.object);
@@ -173,41 +187,32 @@ function FloorInstance({ floor }: { floor: Floor }) {
}
}}
>
<Extrude
name={`Floor-${floor.floorUuid}`}
args={[shape,
{
depth: !floor.isBeveled ? floor.floorDepth : floor.floorDepth - 0.1,
bevelEnabled: floor.isBeveled,
bevelSegments: floor.bevelStrength,
bevelOffset: -0.1,
bevelSize: 0.1,
bevelThickness: 0.1,
},
]}
userData={floor}
>
<meshPhysicalMaterial
attach="material-0"
color={Constants.floorConfig.defaultColor}
map={topTexture}
roughnessMap={topRoughnessTexture}
metalnessMap={topMetalicTexture}
normalMap={topNormalTexture}
roughness={1.5}
metalness={1.0}
side={DoubleSide}
/>
<meshStandardMaterial
attach="material-1"
color={Constants.floorConfig.defaultColor}
map={sideTexture?.clone()}
roughnessMap={sideRoughnessTexture?.clone()}
metalnessMap={sideMetalicTexture?.clone()}
normalMap={sideNormalTexture?.clone()}
side={DoubleSide}
/>
</Extrude>
<meshPhysicalMaterial
attach="material-0"
color={Constants.floorConfig.defaultColor}
map={topTexture}
roughnessMap={topRoughnessTexture}
metalnessMap={topMetalicTexture}
normalMap={topNormalTexture}
roughness={1.5}
metalness={1.0}
side={DoubleSide}
/>
<meshStandardMaterial
attach="material-1"
color={Constants.floorConfig.defaultColor}
map={sideTexture?.clone()}
roughnessMap={sideRoughnessTexture?.clone()}
metalnessMap={sideMetalicTexture?.clone()}
normalMap={sideNormalTexture?.clone()}
roughness={1.5}
metalness={1.0}
side={DoubleSide}
/>
{floor.decals.map((decal) => (
<DecalInstance parent={floor} key={decal.decalUuid} decal={decal} />
))}
</mesh>
);
}

View File

@@ -2,21 +2,36 @@ import React, { useEffect, useMemo } from 'react';
import { Vector3 } from 'three';
import { Html } from '@react-three/drei';
import { useSceneContext } from '../../../scene/sceneContext';
import { useToggleView } from '../../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../../store/builder/store';
import Line from '../../line/line';
import Point from '../../point/point';
import FloorInstance from './Instance/floorInstance';
import Floor2DInstance from './Instance/floor2DInstance';
import useModuleStore from '../../../../store/useModuleStore';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
function FloorInstances() {
const { floorStore } = useSceneContext();
const { floors } = floorStore();
const { setSelectedFloor, selectedFloor } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
useEffect(() => {
// console.log('floors: ', floors);
}, [floors])
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (selectedFloor) setSelectedFloor(null);
}
} else {
if (selectedFloor) setSelectedFloor(null);
}
}, [toggleView, toolMode, activeModule, selectedFloor]);
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set<string>();

View File

@@ -10,7 +10,7 @@ import { getUserData } from '../../../../functions/getUserData';
import ReferencePoint from '../../point/reference/referencePoint';
import ReferenceFloor from './referenceFloor';
// import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
import { upsertFloorApi } from '../../../../services/factoryBuilder/floor/upsertFloorApi';
function FloorCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@@ -120,23 +120,25 @@ function FloorCreator() {
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
setTempPoints([]);
setIsCreating(false);
@@ -174,23 +176,25 @@ function FloorCreator() {
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
setTempPoints([]);
setIsCreating(false);
@@ -238,23 +242,25 @@ function FloorCreator() {
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
}
setTempPoints([]);

View File

@@ -10,7 +10,7 @@ import FloorInstances from './Instances/floorInstances';
import { getFloorsApi } from '../../../services/factoryBuilder/floor/getFloorsApi';
function FloorGroup() {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { setSelectedFloor, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -21,11 +21,11 @@ function FloorGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
if (toggleView || activeModule !== 'builder') {
setSelectedFloor(null);
setSelectedDecal(null);
}
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -13,14 +13,14 @@ import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCurso
import { useSelectedPoints } from '../../../store/simulation/useSimulationStore';
import { calculateAssetTransformationOnWall } from '../wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall';
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
// import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
// import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
// import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
// import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
// import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
interface LineProps {
points: [Point, Point];
@@ -120,43 +120,48 @@ function Line({ points }: Readonly<LineProps>) {
if (projectId && asset) {
removeWallAsset(asset.modelUuid);
if (!socket?.connected) {
// API
// API
// deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: asset.modelUuid,
wallUuid: asset.wallUuid
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: asset.modelUuid,
wallUuid: asset.wallUuid
}
socket.emit('v1:wall-asset:delete', data);
}
socket.emit('v1:wall-asset:delete', data);
}
})
if (!socket?.connected) {
// API
// API
// deleteWallApi(projectId, selectedVersion?.versionId || '', removedWall.wallUuid);
deleteWallApi(projectId, selectedVersion?.versionId || '', removedWall.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
wallUuid: removedWall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallUuid: removedWall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:delete', data);
}
socket.emit('v1:model-Wall:delete', data);
push2D({
type: 'Draw',
actions: [
@@ -180,22 +185,25 @@ function Line({ points }: Readonly<LineProps>) {
if (removedFloors.length > 0) {
removedFloors.forEach(floor => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
} else {
// SOCKET
// SOCKET
const data = {
floorUuid: floor.floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorUuid: floor.floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:delete', data);
}
socket.emit('v1:model-Floor:delete', data);
}
});
@@ -218,22 +226,25 @@ function Line({ points }: Readonly<LineProps>) {
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
});
@@ -263,22 +274,25 @@ function Line({ points }: Readonly<LineProps>) {
if (removedZones.length > 0) {
removedZones.forEach(zone => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
} else {
// SOCKET
// SOCKET
const data = {
zoneUuid: zone.zoneUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneUuid: zone.zoneUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:delete', data);
}
socket.emit('v1:zone:delete', data);
}
});
@@ -301,22 +315,25 @@ function Line({ points }: Readonly<LineProps>) {
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
}
});
@@ -435,40 +452,47 @@ function Line({ points }: Readonly<LineProps>) {
});
if (projectId && updatedWallAsset) {
// API
if (!socket?.connected) {
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
} else {
// SOCKET
// SOCKET
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
socket.emit('v1:wall-asset:add', data);
}
});
}
// API
if (!socket?.connected) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// API
// SOCKET
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
socket.emit('v1:model-Wall:add', data);
})
if (initialPositions.walls && initialPositions.walls.length > 0) {
@@ -495,22 +519,25 @@ function Line({ points }: Readonly<LineProps>) {
if (updatedFloors.length > 0 && projectId) {
updatedFloors.forEach(updatedFloor => {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
})
if (initialPositions.floors && initialPositions.floors.length > 0) {
@@ -537,22 +564,25 @@ function Line({ points }: Readonly<LineProps>) {
if (updatedZones.length > 0 && projectId) {
updatedZones.forEach(updatedZone => {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: updatedZone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: updatedZone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
})
if (initialPositions.zones && initialPositions.zones.length > 0) {

View File

@@ -11,16 +11,16 @@ import { useParams } from 'react-router-dom';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
// import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
// import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
// import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
// import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
// import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
// import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
// import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
// import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
// import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
// import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
import { upsertAisleApi } from '../../../services/factoryBuilder/aisle/upsertAisleApi';
import { deleteAisleApi } from '../../../services/factoryBuilder/aisle/deleteAisleApi';
import { upsertWallApi } from '../../../services/factoryBuilder/wall/upsertWallApi';
import { deleteWallApi } from '../../../services/factoryBuilder/wall/deleteWallApi';
import { upsertFloorApi } from '../../../services/factoryBuilder/floor/upsertFloorApi';
import { deleteFloorApi } from '../../../services/factoryBuilder/floor/deleteFloorApi';
import { upsertZoneApi } from '../../../services/factoryBuilder/zone/upsertZoneApi';
import { deleteZoneApi } from '../../../services/factoryBuilder/zone/deleteZoneApi';
import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
import { deleteWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
import { getUserData } from '../../../functions/getUserData';
import { handleCanvasCursors } from '../../../utils/mouseUtils/handleCanvasCursors';
@@ -190,22 +190,25 @@ function Point({ point }: { readonly point: Point }) {
const updatedAisles = getAislesByPointId(point.pointUuid);
if (updatedAisles.length > 0 && projectId) {
updatedAisles.forEach((updatedAisle) => {
if (!socket?.connected) {
// API
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
} else {
// SOCKET
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
})
socket.emit('v1:model-aisle:add', {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
})
}
})
if (initialStates.aisles && initialStates.aisles.length > 0) {
@@ -244,40 +247,47 @@ function Point({ point }: { readonly point: Point }) {
});
if (projectId && updatedWallAsset) {
// API
if (!socket?.connected) {
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
} else {
// SOCKET
// SOCKET
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
socket.emit('v1:wall-asset:add', data);
}
});
}
// API
if (!socket?.connected) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
// API
// SOCKET
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
// SOCKET
const data = {
wallData: updatedWall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
socket.emit('v1:model-Wall:add', data);
});
}
@@ -301,22 +311,25 @@ function Point({ point }: { readonly point: Point }) {
const updatedFloors = getFloorsByPointId(point.pointUuid);
if (updatedFloors && updatedFloors.length > 0 && projectId) {
updatedFloors.forEach((updatedFloor) => {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: updatedFloor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
});
}
@@ -340,22 +353,25 @@ function Point({ point }: { readonly point: Point }) {
const updatedZones = getZonesByPointId(point.pointUuid);
if (updatedZones && updatedZones.length > 0 && projectId) {
updatedZones.forEach((updatedZone) => {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: updatedZone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: updatedZone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
});
}
@@ -388,23 +404,26 @@ function Point({ point }: { readonly point: Point }) {
if (removedAisles.length > 0) {
removedAisles.forEach(aisle => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
} else {
// SOCKET
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid
}
socket.emit('v1:model-aisle:delete', data);
}
socket.emit('v1:model-aisle:delete', data);
}
});
@@ -436,44 +455,50 @@ function Point({ point }: { readonly point: Point }) {
if (projectId && asset) {
removeWallAsset(asset.modelUuid);
if (!socket?.connected) {
// API
// API
// deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: asset.modelUuid,
wallUuid: asset.wallUuid
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: asset.modelUuid,
wallUuid: asset.wallUuid
}
socket.emit('v1:wall-asset:delete', data);
}
socket.emit('v1:wall-asset:delete', data);
}
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
wallUuid: wall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallUuid: wall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:delete', data);
}
socket.emit('v1:model-Wall:delete', data);
}
});
@@ -501,22 +526,25 @@ function Point({ point }: { readonly point: Point }) {
if (removedFloors.length > 0) {
removedFloors.forEach(floor => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
} else {
// SOCKET
// SOCKET
const data = {
floorUuid: floor.floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorUuid: floor.floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:delete', data);
}
socket.emit('v1:model-Floor:delete', data);
}
});
@@ -539,22 +567,25 @@ function Point({ point }: { readonly point: Point }) {
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
});
@@ -583,22 +614,25 @@ function Point({ point }: { readonly point: Point }) {
if (removedZones.length > 0) {
removedZones.forEach(zone => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
} else {
// SOCKET
// SOCKET
const data = {
zoneUuid: zone.zoneUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneUuid: zone.zoneUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:delete', data);
}
socket.emit('v1:zone:delete', data);
}
});
@@ -621,22 +655,25 @@ function Point({ point }: { readonly point: Point }) {
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
}
});

View File

@@ -10,7 +10,7 @@ import { useToggleView, useWallVisibility } from '../../../../../store/builder/s
import { useBuilderStore } from '../../../../../store/builder/useBuilderStore';
import * as Constants from '../../../../../types/world/worldConstants';
import DecalInstance from '../../../Decal/decalInstance';
import DecalInstance from '../../../Decal/decalInstance/decalInstance';
import defaultMaterial from '../../../../../assets/textures/floor/wall-tex.png';
import material1 from '../../../../../assets/textures/floor/factory wall texture.jpg';
@@ -21,7 +21,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
const { wallAssets, getWallAssetsByWall, setVisibility } = wallAssetStore();
const assets = getWallAssetsByWall(wall.wallUuid);
const { selectedWall, setSelectedWall, setSelectedDecal } = useBuilderStore();
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { camera } = useThree();
const { wallVisibility } = useWallVisibility();
@@ -118,10 +118,11 @@ function Wall({ wall }: { readonly wall: Wall }) {
>
{(assets.length > 0 || (walls[0].wallUuid === wall.wallUuid && wallAssets.length > 0)) ?
<Base
name={`BaseWall${wall.wallUuid}`}
castShadow
receiveShadow
ref={meshRef}
geometry={geometry}
geometry={visible ? geometry : new THREE.BoxGeometry(0, 0)}
position={[centerX, centerY, centerZ]}
rotation={[0, -angle, 0]}
userData={wall}
@@ -154,7 +155,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
userData={wall}
name={`WallReference_${wall.wallUuid}`}
onDoubleClick={(e) => {
if (visible && !togglView && activeModule === 'builder') {
if (visible && !toggleView && activeModule === 'builder') {
if (e.object.userData.wallUuid) {
e.stopPropagation();
setSelectedWall(e.object);
@@ -171,7 +172,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
<MeshDiscardMaterial />
{wall.decals.map((decal) => (
<DecalInstance zPosition={wall.wallThickness / 2 + 0.001} visible={visible} key={decal.decalUuid} decal={decal} />
<DecalInstance parent={wall} visible={visible} key={decal.decalUuid} decal={decal} />
))}
</mesh>
</mesh>

View File

@@ -2,8 +2,9 @@ import React, { useEffect, useMemo } from 'react';
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three';
import { Html, Extrude } from '@react-three/drei';
import { useLoader } from '@react-three/fiber';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { useSceneContext } from '../../../scene/sceneContext';
import { useToggleView } from '../../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../../store/builder/store';
import { useWallClassification } from './instance/helpers/useWallClassification';
import Line from '../../line/line';
import Point from '../../point/point';
@@ -12,17 +13,31 @@ import * as Constants from '../../../../types/world/worldConstants';
import texturePath from "../../../../assets/textures/floor/white.png";
import texturePathDark from "../../../../assets/textures/floor/black.png";
import useModuleStore from '../../../../store/useModuleStore';
function WallInstances() {
const { wallStore } = useSceneContext();
const { setSelectedWall, selectedWall } = useBuilderStore();
const { toolMode } = useToolMode();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { walls } = wallStore();
const { rooms } = useWallClassification(walls);
const { toggleView } = useToggleView();
useEffect(() => {
// console.log('walls: ', walls);
}, [walls])
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (selectedWall) setSelectedWall(null);
}
} else {
if (selectedWall) setSelectedWall(null);
}
}, [toggleView, toolMode, activeModule, selectedWall]);
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set<string>();

View File

@@ -12,8 +12,8 @@ import getClosestIntersection from '../../line/helpers/getClosestIntersection';
import ReferencePoint from '../../point/reference/referencePoint';
import ReferenceWall from './referenceWall';
// import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
// import { deleteWallApi } from '../../../../services/factoryBuilder/wall/deleteWallApi';
import { upsertWallApi } from '../../../../services/factoryBuilder/wall/upsertWallApi';
import { deleteWallApi } from '../../../../services/factoryBuilder/wall/deleteWallApi';
function WallCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@@ -94,23 +94,26 @@ function WallCreator() {
removeWall(wall.wallUuid);
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
wallUuid: wall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallUuid: wall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:delete', data);
}
socket.emit('v1:model-Wall:delete', data);
}
const point1: Point = {
@@ -147,24 +150,27 @@ function WallCreator() {
addWall(wall2);
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall2);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall2);
} else {
// SOCKET
// SOCKET
const data = {
wallData: wall2,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallData: wall2,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
socket.emit('v1:model-Wall:add', data);
const wall3: Wall = {
wallUuid: THREE.MathUtils.generateUUID(),
points: [point2, newPoint],
@@ -204,24 +210,27 @@ function WallCreator() {
],
})
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall3);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall3);
} else {
// SOCKET
// SOCKET
const data2 = {
wallData: wall3,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data2 = {
wallData: wall3,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data2);
}
}
socket.emit('v1:model-Wall:add', data2);
setTempPoints([newPoint]);
setIsCreating(true);
} else {
@@ -237,24 +246,27 @@ function WallCreator() {
addWall(wall1);
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall1);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall1);
} else {
// SOCKET
// SOCKET
const data = {
wallData: wall1,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallData: wall1,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
socket.emit('v1:model-Wall:add', data);
const wall2: Wall = {
wallUuid: THREE.MathUtils.generateUUID(),
points: [point1, newPoint],
@@ -267,24 +279,27 @@ function WallCreator() {
addWall(wall2);
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall2);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall2);
} else {
// SOCKET
// SOCKET
const data1 = {
wallData: wall2,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data1 = {
wallData: wall2,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data1);
}
}
socket.emit('v1:model-Wall:add', data1);
const wall3: Wall = {
wallUuid: THREE.MathUtils.generateUUID(),
points: [point2, newPoint],
@@ -328,24 +343,27 @@ function WallCreator() {
],
})
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall3);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall3);
} else {
// SOCKET
// SOCKET
const data3 = {
wallData: wall3,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data3 = {
wallData: wall3,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data3);
}
}
socket.emit('v1:model-Wall:add', data3);
setTempPoints([newPoint]);
}
@@ -411,24 +429,27 @@ function WallCreator() {
],
})
// API
if (projectId) {
if (!socket?.connected) {
// API
// if (projectId) {
// upsertWallApi(projectId, selectedVersion?.versionId || '', wall);
// }
upsertWallApi(projectId, selectedVersion?.versionId || '', wall);
} else {
// SOCKET
// SOCKET
const data = {
wallData: wall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallData: wall,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:add', data);
}
}
socket.emit('v1:model-Wall:add', data);
setTempPoints([newPoint]);
}

View File

@@ -11,7 +11,7 @@ import WallInstances from './Instances/wallInstances';
import { getWallsApi } from '../../../services/factoryBuilder/wall/getWallsApi';
function WallGroup() {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { setSelectedWall, setSelectedDecal } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -22,11 +22,11 @@ function WallGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
if (toggleView || activeModule !== 'builder') {
setSelectedWall(null);
setSelectedDecal(null);
}
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -18,10 +18,10 @@ const calculateAssetTransformationOnWall = (
const projection = initialWallNormalized.clone().multiplyScalar(dotProduct);
const perpendicular = new THREE.Vector3().subVectors(assetVector, projection);
const distanceFromWall = perpendicular.length();
const distanceInWall = perpendicular.length();
const crossProduct = new THREE.Vector3().crossVectors(initialWallNormalized, perpendicular).y;
const signedDistance = distanceFromWall * (crossProduct >= 0 ? 1 : -1);
const signedDistance = distanceInWall * (crossProduct >= 0 ? 1 : -1);
const percentage = Math.max(0, Math.min(1, dotProduct / initialWallLength));

View File

@@ -14,8 +14,8 @@ import { useVersionContext } from '../../../version/versionContext';
import { getUserData } from '../../../../../functions/getUserData';
import closestPointOnLineSegment from '../../../line/helpers/getClosestPointOnLineSegment';
// import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
// import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
import { upsertWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
import { deleteWallAssetApi } from '../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi';
function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
@@ -23,7 +23,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const { raycaster, pointer, camera, scene, controls, gl } = useThree();
const { wallStore, wallAssetStore } = useSceneContext();
const { walls, getWallById } = wallStore();
const { updateWallAsset, removeWallAsset } = wallAssetStore();
const { updateWallAsset, removeWallAsset, getWallAssetById } = wallAssetStore();
const { toggleView } = useToggleView();
const { activeTool } = useActiveTool();
const { activeModule } = useModuleStore();
@@ -116,52 +116,35 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const canvasElement = gl.domElement;
const onPointerUp = (e: PointerEvent) => {
draggingRef.current = false;
if (controls) {
(controls as any).enabled = true;
}
if (draggingRef.current) {
draggingRef.current = false;
if (controls) {
(controls as any).enabled = true;
}
if (selectedWallAsset) {
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
pointer.y = -(e.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(pointer, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
const intersect = intersects.find((i: any) => i.object.name.includes('WallReference'));
if (intersect && intersect.object.userData.wallUuid && selectedWallAsset.userData.modelUuid === wallAsset.modelUuid) {
const newPoint = closestPointOnLineSegment(
new THREE.Vector3(intersect.point.x, 0, intersect.point.z),
new THREE.Vector3(...intersect.object.userData.points[0].position),
new THREE.Vector3(...intersect.object.userData.points[1].position)
);
const wallRotation = intersect.object.rotation.clone();
const updatedWallAsset = updateWallAsset(wallAsset.modelUuid, {
wallUuid: intersect.object.userData.wallUuid,
position: [newPoint.x, wallAsset.wallAssetType === 'fixedMove' ? 0 : intersect.point.y, newPoint.z],
rotation: [wallRotation.x, wallRotation.y, wallRotation.z],
});
if (selectedWallAsset) {
const updatedWallAsset = getWallAssetById(wallAsset.modelUuid);
if (projectId && updatedWallAsset) {
if (!socket?.connected) {
// API
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
} else {
// SOCKET
// SOCKET
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
socket.emit('v1:wall-asset:add', data);
}
}
}
@@ -213,24 +196,26 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
const removedWallAsset = removeWallAsset(wallAsset.modelUuid);
if (projectId && removedWallAsset) {
if (!socket?.connected) {
// API
// API
// deleteWallAssetApi(projectId, selectedVersion?.versionId || '', removedWallAsset.modelUuid, removedWallAsset.wallUuid);
deleteWallAssetApi(projectId, selectedVersion?.versionId || '', removedWallAsset.modelUuid, removedWallAsset.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: removedWallAsset.modelUuid,
wallUuid: removedWallAsset.wallUuid
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: removedWallAsset.modelUuid,
wallUuid: removedWallAsset.wallUuid
}
socket.emit('v1:wall-asset:delete', data);
}
socket.emit('v1:wall-asset:delete', data);
}
}
}, [activeTool, activeModule, deletableWallAsset, socket])
@@ -307,7 +292,7 @@ function WallAssetInstance({ wallAsset }: { wallAsset: WallAsset }) {
setSelectedWallAsset(null);
}
}}
onPointerOver={(e) => {
onPointerEnter={(e) => {
if (!toggleView) {
e.stopPropagation();
let currentObject = e.object as THREE.Object3D;

View File

@@ -9,12 +9,12 @@ import { useVersionContext } from '../version/versionContext';
import { getUserData } from '../../../functions/getUserData';
import closestPointOnLineSegment from '../line/helpers/getClosestPointOnLineSegment';
// import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
import { upsertWallAssetApi } from '../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi';
function WallAssetCreator() {
const { socket } = useSocketStore();
const { pointer, camera, raycaster, scene, gl } = useThree();
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { wallAssetStore } = useSceneContext();
const { addWallAsset } = wallAssetStore();
@@ -62,29 +62,31 @@ function WallAssetCreator() {
addWallAsset(newWallAsset);
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', newWallAsset);
upsertWallAssetApi(projectId, selectedVersion?.versionId || '', newWallAsset);
} else {
// SOCKET
// SOCKET
const data = {
wallAssetData: newWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallAssetData: newWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
socket.emit('v1:wall-asset:add', data);
}
}
}
};
if (!togglView && activeModule === 'builder') {
if (!toggleView && activeModule === 'builder') {
canvasElement.addEventListener('drop', onDrop);
}
@@ -92,7 +94,7 @@ function WallAssetCreator() {
canvasElement.removeEventListener('drop', onDrop);
};
}, [gl, camera, togglView, activeModule, socket, selectedItem, setSelectedItem]);
}, [gl, camera, toggleView, activeModule, socket, selectedItem, setSelectedItem]);
return (
<>

View File

@@ -1,5 +1,5 @@
import { useEffect } from 'react';
import { useActiveTool, useToggleView } from '../../../store/builder/store';
import { useToggleView, useToolMode } from '../../../store/builder/store';
import { useBuilderStore } from '../../../store/builder/useBuilderStore';
import { useVersionContext } from '../version/versionContext';
import { useSceneContext } from '../../scene/sceneContext';
@@ -10,10 +10,10 @@ import WallAssetInstances from './Instances/wallAssetInstances'
import { getWallAssetsApi } from '../../../services/factoryBuilder/asset/wallAsset/getWallAssetsApi';
function WallAssetGroup() {
const { togglView } = useToggleView();
const { setSelectedFloorAsset, setDeletableWallAsset } = useBuilderStore();
const { toggleView } = useToggleView();
const { setSelectedWallAsset, setDeletableWallAsset } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
const { toolMode } = useToolMode();
const { selectedVersionStore } = useVersionContext();
const { selectedVersion } = selectedVersionStore();
const { wallAssetStore } = useSceneContext();
@@ -21,11 +21,11 @@ function WallAssetGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
setSelectedFloorAsset(null);
if (toggleView || activeModule !== 'builder' || toolMode !== 'cursor') {
setSelectedWallAsset(null);
}
setDeletableWallAsset(null);
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, toolMode])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -10,7 +10,7 @@ import { getUserData } from '../../../../functions/getUserData';
import ReferencePoint from '../../point/reference/referencePoint';
import ReferenceZone from './referenceZone';
// import { upsertZoneApi } from '../../../../services/factoryBuilder/zone/upsertZoneApi';
import { upsertZoneApi } from '../../../../services/factoryBuilder/zone/upsertZoneApi';
function ZoneCreator() {
const { scene, camera, raycaster, gl, pointer } = useThree();
@@ -33,7 +33,6 @@ function ZoneCreator() {
const [isCreating, setIsCreating] = useState(false);
const { zoneColor, zoneHeight, snappedPosition, snappedPoint, setSnappedPoint, setSnappedPosition } = useBuilderStore();
useEffect(() => {
const canvasElement = gl.domElement;
@@ -119,23 +118,25 @@ function ZoneCreator() {
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
}
setTempPoints([]);
setIsCreating(false);
@@ -171,23 +172,25 @@ function ZoneCreator() {
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
}
setTempPoints([]);
setIsCreating(false);
@@ -233,23 +236,25 @@ function ZoneCreator() {
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
}
}
setTempPoints([]);

View File

@@ -11,7 +11,7 @@ import ZoneInstances from './Instances/zoneInstances';
import { getZonesApi } from '../../../services/factoryBuilder/zone/getZonesApi';
function ZoneGroup() {
const { togglView } = useToggleView();
const { toggleView } = useToggleView();
const { setSelectedZone } = useBuilderStore();
const { activeModule } = useModuleStore();
const { activeTool } = useActiveTool();
@@ -22,10 +22,10 @@ function ZoneGroup() {
const { projectId } = useParams();
useEffect(() => {
if (togglView || activeModule !== 'builder') {
if (toggleView || activeModule !== 'builder') {
setSelectedZone(null);
}
}, [togglView, activeModule, activeTool])
}, [toggleView, activeModule, activeTool])
useEffect(() => {
if (projectId && selectedVersion) {

View File

@@ -5,12 +5,16 @@ export default function SocketResponses() {
const { socket } = useSocketStore();
useEffect(() => {
socket.on("v1:model-asset:response:add", (data: any) => {
// console.log('data: ', data);
});
if (socket) {
socket.on("v1:model-asset:response:add", (data: any) => {
// console.log('data: ', data);
});
}
return () => {
socket.off("v1:model-asset:response:add");
if (socket) {
socket.off("v1:model-asset:response:add");
}
}
}, [socket])

View File

@@ -1,12 +1,13 @@
import { useFrame, useThree } from "@react-three/fiber";
import React, { useEffect, useState } from "react";
import { useKeyboardControls } from "@react-three/drei";
import * as CONSTANTS from "../../../types/world/worldConstants";
import { useCamMode, useToggleView } from "../../../store/builder/store";
import { useKeyboardControls } from "@react-three/drei";
import switchToThirdPerson from "./switchToThirdPerson";
import switchToFirstPerson from "./switchToFirstPerson";
import switchToThirdPerson from "./functions/switchToThirdPerson";
import switchToFirstPerson from "./functions/switchToFirstPerson";
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
import { firstPersonCamera } from "./firstPersonCamera";
import { firstPersonCamera } from "./functions/firstPersonCamera";
const CamMode: React.FC = () => {
const { camMode, setCamMode } = useCamMode();

View File

@@ -1,39 +1,39 @@
import * as CONSTANTS from "../../../types/world/worldConstants";
interface FirstPersonCameraProps {
setIsTransitioning?: (value: boolean) => void;
state: any;
}
interface FirstPersonCameraParams extends FirstPersonCameraProps {
camMode: string;
setCamMode: (mode: string) => void;
switchToFirstPerson: (controls: any, camera: any) => Promise<void>;
switchToThirdPerson: (controls: any, camera: any) => Promise<void>;
}
export async function firstPersonCamera({
setIsTransitioning,
state,
camMode,
setCamMode,
switchToFirstPerson,
switchToThirdPerson
}: FirstPersonCameraParams): Promise<void> {
setIsTransitioning && setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
if (camMode === "ThirdPerson") {
setCamMode("FirstPerson");
await switchToFirstPerson(state.controls, state.camera);
} else if (camMode === "FirstPerson") {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
setIsTransitioning && setIsTransitioning(false);
}
import * as CONSTANTS from "../../../../types/world/worldConstants";
interface FirstPersonCameraProps {
setIsTransitioning?: (value: boolean) => void;
state: any;
}
interface FirstPersonCameraParams extends FirstPersonCameraProps {
camMode: string;
setCamMode: (mode: string) => void;
switchToFirstPerson: (controls: any, camera: any) => Promise<void>;
switchToThirdPerson: (controls: any, camera: any) => Promise<void>;
}
export async function firstPersonCamera({
setIsTransitioning,
state,
camMode,
setCamMode,
switchToFirstPerson,
switchToThirdPerson
}: FirstPersonCameraParams): Promise<void> {
setIsTransitioning && setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
state.controls.mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse;
state.controls.mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse;
if (camMode === "ThirdPerson") {
setCamMode("FirstPerson");
await switchToFirstPerson(state.controls, state.camera);
} else if (camMode === "FirstPerson") {
setCamMode("ThirdPerson");
await switchToThirdPerson(state.controls, state.camera);
}
setIsTransitioning && setIsTransitioning(false);
}

View File

@@ -1,25 +1,25 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
export default async function switchToFirstPerson(
controls: any,
camera: any
) {
if (!controls) return;
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
cameraDirection.normalize();
await controls.setPosition(camera.position.x, 2, camera.position.z, true);
controls.setTarget(camera.position.x, 2, camera.position.z, true);
controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse;
controls.lockPointer();
controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed;
controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed;
controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed;
controls.minDistance = CONSTANTS.firstPersonControls.minDistance;
controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance;
controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle;
import * as THREE from 'three';
import * as CONSTANTS from '../../../../types/world/worldConstants';
export default async function switchToFirstPerson(
controls: any,
camera: any
) {
if (!controls) return;
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
cameraDirection.normalize();
await controls.setPosition(camera.position.x, 2, camera.position.z, true);
controls.setTarget(camera.position.x, 2, camera.position.z, true);
controls.mouseButtons.left = CONSTANTS.firstPersonControls.leftMouse;
controls.lockPointer();
controls.azimuthRotateSpeed = CONSTANTS.firstPersonControls.azimuthRotateSpeed;
controls.polarRotateSpeed = CONSTANTS.firstPersonControls.polarRotateSpeed;
controls.truckSpeed = CONSTANTS.firstPersonControls.truckSpeed;
controls.minDistance = CONSTANTS.firstPersonControls.minDistance;
controls.maxDistance = CONSTANTS.firstPersonControls.maxDistance;
controls.maxPolarAngle = CONSTANTS.firstPersonControls.maxPolarAngle;
}

View File

@@ -1,29 +1,29 @@
import * as THREE from 'three';
import * as CONSTANTS from '../../../types/world/worldConstants';
export default async function switchToThirdPerson(
controls: any,
camera: any
) {
if (!controls) return;
controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse;
controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse;
controls.unlockPointer();
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset);
const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset);
controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true);
controls.setTarget(targetPosition.x, 0, targetPosition.z, true);
controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed;
controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed;
controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed;
controls.minDistance = CONSTANTS.threeDimension.minDistance;
controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance;
controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle;
import * as THREE from 'three';
import * as CONSTANTS from '../../../../types/world/worldConstants';
export default async function switchToThirdPerson(
controls: any,
camera: any
) {
if (!controls) return;
controls.mouseButtons.left = CONSTANTS.thirdPersonControls.leftMouse;
controls.mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
controls.mouseButtons.middle = CONSTANTS.thirdPersonControls.middleMouse;
controls.mouseButtons.wheel = CONSTANTS.thirdPersonControls.wheelMouse;
controls.unlockPointer();
const cameraDirection = new THREE.Vector3();
camera.getWorldDirection(cameraDirection);
const targetOffset = cameraDirection.multiplyScalar(CONSTANTS.thirdPersonControls.targetOffset);
const targetPosition = new THREE.Vector3(camera.position.x, camera.position.y, camera.position.z).add(targetOffset);
controls.setPosition(camera.position.x, CONSTANTS.thirdPersonControls.cameraHeight, camera.position.z, true);
controls.setTarget(targetPosition.x, 0, targetPosition.z, true);
controls.azimuthRotateSpeed = CONSTANTS.thirdPersonControls.azimuthRotateSpeed;
controls.polarRotateSpeed = CONSTANTS.thirdPersonControls.polarRotateSpeed;
controls.truckSpeed = CONSTANTS.thirdPersonControls.truckSpeed;
controls.minDistance = CONSTANTS.threeDimension.minDistance;
controls.maxDistance = CONSTANTS.thirdPersonControls.maxDistance;
controls.maxPolarAngle = CONSTANTS.thirdPersonControls.maxPolarAngle;
}

View File

@@ -0,0 +1,41 @@
import { Socket } from "socket.io-client";
import * as THREE from "three";
import { getUserData } from "../../../../functions/getUserData";
import { CameraControls } from "@react-three/drei";
import { setCameraApi } from "../../../../services/factoryBuilder/camera/setCameraApi";
export default function updateCamPosition(
controls: React.RefObject<CameraControls>,
socket: Socket,
position: THREE.Vector3,
rotation: THREE.Euler,
projectId?: string
) {
const { userId, organization } = getUserData();
if (!controls.current || !projectId) return;
const target = controls.current.getTarget(new THREE.Vector3());
if (!socket?.connected) {
// API
setCameraApi(
projectId,
position,
new THREE.Vector3(target.x, 0, target.z),
new THREE.Vector3(rotation.x, rotation.y, rotation.z),
)
} else {
// SOCKET
const camData = {
organization,
userId: userId,
position: position,
target: new THREE.Vector3(target.x, 0, target.z),
rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z),
socketId: socket.id,
projectId,
};
socket.emit("v1:Camera:set", camData);
}
}

View File

@@ -0,0 +1,73 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import type { CameraControls } from "@react-three/drei";
const CameraShortcutsControls = () => {
const { camera, controls } = useThree();
const isTextInput = (element: Element | null): boolean =>
element instanceof HTMLInputElement ||
element instanceof HTMLTextAreaElement ||
element?.getAttribute("contenteditable") === "true";
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (!controls) return;
const cc = controls as CameraControls;
// get current target
const target = new THREE.Vector3();
cc.getTarget(target);
const distance = camera.position.distanceTo(target);
let pos: THREE.Vector3 | null = null;
const dir = new THREE.Vector3().subVectors(camera.position, target).normalize();
if (isTextInput(document.activeElement)) return;
switch (e.key) {
case "1": // Front
pos = new THREE.Vector3(0, 0, distance).add(target);
break;
case "3": // Right
pos = new THREE.Vector3(distance, 0, 0).add(target);
break;
case "7": // Top
pos = new THREE.Vector3(0, distance, 0).add(target);
break;
case "9": {
// Opposite view logic
if (Math.abs(dir.z) > Math.abs(dir.x) && Math.abs(dir.z) > Math.abs(dir.y)) {
// Currently looking Front/Back → flip Z
pos = new THREE.Vector3(0, 0, -Math.sign(dir.z) * distance).add(target);
} else if (Math.abs(dir.x) > Math.abs(dir.z) && Math.abs(dir.x) > Math.abs(dir.y)) {
// Currently looking Right/Left → flip X
pos = new THREE.Vector3(-Math.sign(dir.x) * distance, 0, 0).add(target);
} else {
// Currently looking Top/Bottom → stay Top
pos = new THREE.Vector3(0, distance, 0).add(target);
}
break;
}
}
if (pos) {
cc.setLookAt(
pos.x, pos.y, pos.z, // camera position
target.x, target.y, target.z, // keep same target
true // smooth transition
);
}
};
window.addEventListener("keydown", handleKeyDown);
return () => window.removeEventListener("keydown", handleKeyDown);
}, [controls, camera]);
return null;
};
export default CameraShortcutsControls;

View File

@@ -4,7 +4,7 @@ import * as THREE from 'three';
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei';
import { useParams } from "react-router-dom";
import * as CONSTANTS from '../../../types/world/worldConstants';
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
import { getUserData } from "../../../functions/getUserData";
import { useToggleView } from "../../../store/builder/store";
@@ -20,7 +20,7 @@ export default function SwitchView() {
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
} else {
try {
getCamera(organization, localStorage.getItem('userId')!, projectId).then((data) => {
getCameraApi(organization, localStorage.getItem('userId')!, projectId).then((data) => {
if (data && data.position && data.target) {
(controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z);
(controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z);

View File

@@ -1,26 +0,0 @@
import { Socket } from "socket.io-client";
import * as THREE from "three";
import { getUserData } from "../../../functions/getUserData";
export default function updateCamPosition(
controls: any,
socket: Socket,
position: THREE.Vector3,
rotation: THREE.Euler,
projectId?: string
) {
const { userId, organization } = getUserData();
if (!controls.current) return;
const target = controls.current.getTarget(new THREE.Vector3());
const camData = {
organization,
userId: userId,
position: position,
target: new THREE.Vector3(target.x, 0, target.z),
rotation: new THREE.Vector3(rotation.x, rotation.y, rotation.z),
socketId: socket.id,
projectId,
};
socket.emit("v1:Camera:set", camData);
}

View File

@@ -1,15 +1,35 @@
import { useEffect, useRef, 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';
import { useEffect, useRef, useState } from "react";
import { useThree } from "@react-three/fiber";
import { CameraControls, Html, ScreenSpace } from "@react-three/drei";
import { useContextActionStore, useRenameModeStore, useToggleView, useToolMode, } from "../../../../store/builder/store";
import ContextMenu from "../../../../components/ui/menu/contextMenu";
import useModuleStore from "../../../../store/useModuleStore";
import { useSceneContext } from "../../sceneContext";
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 [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 { toggleView } = useToggleView();
const { activeModule } = useModuleStore();
const { toolMode } = useToolMode();
const { assetStore } = useSceneContext();
const { selectedAssets, movedObjects, rotatedObjects, duplicatedObjects, copiedObjects } = assetStore();
const { setContextAction } = useContextActionStore();
const { setIsRenameMode } = useRenameModeStore();
const rightDrag = useRef(false);
@@ -93,8 +113,11 @@ function ContextControls() {
event.preventDefault();
if (rightDrag.current) return;
if (selectedAssets.length > 0) {
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2 });
setCanRender(true);
setMenuPosition({ x: event.clientX - gl.domElement.width / 2, y: event.clientY - gl.domElement.height / 2, });
setTimeout(() => {
setCanRender(true);
}, 0)
if (controls) {
(controls as CameraControls).enabled = false;
}
@@ -106,11 +129,11 @@ function ContextControls() {
}
};
if (selectedAssets.length > 0) {
canvasElement.addEventListener('pointerdown', onPointerDown);
canvasElement.addEventListener('pointermove', onPointerMove);
canvasElement.addEventListener('pointerup', onPointerUp);
canvasElement.addEventListener('contextmenu', handleContextClick)
if (selectedAssets.length > 0 && !toggleView && activeModule === "builder" && toolMode === 'cursor' && duplicatedObjects.length === 0 && movedObjects.length === 0 && rotatedObjects.length === 0 && copiedObjects.length === 0) {
canvasElement.addEventListener("pointerdown", onPointerDown);
canvasElement.addEventListener("pointermove", onPointerMove);
canvasElement.addEventListener("pointerup", onPointerUp);
canvasElement.addEventListener("contextmenu", handleContextClick);
} else {
setCanRender(false);
if (controls) {
@@ -120,12 +143,12 @@ function ContextControls() {
}
return () => {
canvasElement.removeEventListener('pointerdown', onPointerDown);
canvasElement.removeEventListener('pointermove', onPointerMove);
canvasElement.removeEventListener('pointerup', onPointerUp);
canvasElement.removeEventListener('contextmenu', handleContextClick);
canvasElement.removeEventListener("pointerdown", onPointerDown);
canvasElement.removeEventListener("pointermove", onPointerMove);
canvasElement.removeEventListener("pointerup", onPointerUp);
canvasElement.removeEventListener("contextmenu", handleContextClick);
};
}, [gl, selectedAssets]);
}, [controls, gl, selectedAssets, toggleView, activeModule, toolMode, duplicatedObjects, movedObjects, rotatedObjects, copiedObjects]);
const handleAssetRename = () => {
setCanRender(false);
@@ -134,67 +157,67 @@ function ContextControls() {
}
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")
}
setContextAction("moveAsset");
};
const handleAssetRotate = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("rotateAsset")
}
setContextAction("rotateAsset");
};
const handleAssetCopy = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("copyAsset")
}
setContextAction("copyAsset");
};
const handleAssetPaste = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("pasteAsset")
}
setContextAction("pasteAsset");
};
const handleAssetDelete = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("deleteAsset")
}
setContextAction("deleteAsset");
};
const handleAssetDuplicate = () => {
setCanRender(false);
if (controls) {
(controls as CameraControls).enabled = true;
}
setContextAction("duplicateAsset")
}
setContextAction("duplicateAsset");
};
return (
<>
{canRender && (
<ScreenSpace depth={1} >
<ScreenSpace depth={1}>
<Html
style={{
position: 'fixed',
position: "fixed",
top: menuPosition.y,
left: menuPosition.x,
zIndex: 1000
zIndex: 1000,
}}
>
<ContextMenu

View File

@@ -3,22 +3,23 @@ import { useRef, useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import * as CONSTANTS from '../../../types/world/worldConstants';
import { useSocketStore, useToggleView, useResetCamera } from "../../../store/builder/store";
import { getCamera } from "../../../services/factoryBuilder/camera/getCameraApi";
import updateCamPosition from "../camera/updateCameraPosition";
import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView";
import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D";
import TransformControl from "./transformControls/transformControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import ContextControls from "./contextControls/contextControls";
import TransformControl from "./transformControls/transformControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
import SelectionControls3D from "./selectionControls/selection3D/selectionControls3D";
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
import { useCameraShortcuts } from "../../../hooks/useCameraShortcuts";
import CameraShortcutsControls from "../camera/shortcutsControls/cameraShortcutsControls";
import { useParams } from "react-router-dom";
import { getUserData } from "../../../functions/getUserData";
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
import { setCameraApi } from "../../../services/factoryBuilder/camera/setCameraApi";
import updateCamPosition from "../camera/functions/updateCameraPosition";
export default function Controls() {
const controlsRef = useRef<CameraControls>(null);
@@ -36,7 +37,7 @@ export default function Controls() {
(controlsRef.current as any).mouseButtons.right = CONSTANTS.thirdPersonControls.rightMouse;
}
getCamera(organization, userId, projectId).then((data) => {
getCameraApi(organization, userId, projectId).then((data) => {
if (data && data.position && data.target) {
controlsRef.current?.setPosition(data.position.x, data.position.y, data.position.z);
controlsRef.current?.setTarget(data.target.x, data.target.y, data.target.z);
@@ -48,21 +49,30 @@ export default function Controls() {
}, []);
useEffect(() => {
if (resetCamera) {
if (resetCamera && projectId) {
controlsRef.current?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
controlsRef.current?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
const camData = {
organization,
userId: userId,
position: new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
socketId: socket.id,
projectId
};
socket.emit('v1:Camera:set', camData)
if (!socket?.connected) {
setCameraApi(
projectId,
new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
)
} else {
const camData = {
organization,
userId: userId,
position: new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
target: new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
rotation: new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation),
socketId: socket.id,
projectId
};
socket.emit('v1:Camera:set', camData)
}
setResetCamera(false);
}
@@ -75,7 +85,7 @@ export default function Controls() {
let intervalId: NodeJS.Timeout | null = null;
const handleRest = () => {
if (hasInteracted && controlsRef.current && state.camera.position && !toggleView) {
if (hasInteracted && controlsRef.current && state.camera.position && !toggleView && socket) {
const position = state.camera.position;
if (position.x === 0 && position.y === 0 && position.z === 0) return;
updateCamPosition(controlsRef, socket, position, state.camera.rotation, projectId);
@@ -117,7 +127,6 @@ export default function Controls() {
stopInterval();
};
}, [toggleView, state, socket]);
useCameraShortcuts(controlsRef);
return (
<>
@@ -140,6 +149,8 @@ export default function Controls() {
<CamMode />
<CameraShortcutsControls />
</CameraControls>
<SelectionControls3D />

View File

@@ -11,10 +11,11 @@ import { useSelectedPoints } from "../../../../../store/simulation/useSimulation
import useModuleStore from "../../../../../store/useModuleStore";
import { calculateAssetTransformationOnWall } from "../../../../builder/wallAsset/Instances/Instance/functions/calculateAssetTransformationOnWall";
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
// import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
import { upsertWallApi } from "../../../../../services/factoryBuilder/wall/upsertWallApi";
import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
import { upsertWallAssetApi } from "../../../../../services/factoryBuilder/asset/wallAsset/upsertWallAssetApi";
function MoveControls2D({
movedObjects,
@@ -270,21 +271,24 @@ function MoveControls2D({
if (updatedAisles.length > 0 && projectId) {
updatedAisles.forEach((updatedAisle) => {
// API
if (!socket?.connected) {
// API
// upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
upsertAisleApi(updatedAisle.aisleUuid, updatedAisle.points, updatedAisle.type, projectId, selectedVersion?.versionId || '');
} else {
// SOCKET
// SOCKET
socket.emit('v1:model-aisle:add', {
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
});
socket.emit('v1:model-aisle:add', {
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization,
aisleUuid: updatedAisle.aisleUuid,
points: updatedAisle.points,
type: updatedAisle.type
});
}
const old = initialStates[movedObject.uuid];
if (old) {
@@ -330,19 +334,22 @@ function MoveControls2D({
});
}
// API
if (!socket?.connected) {
// API
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
upsertWallApi(projectId, selectedVersion?.versionId || '', updatedWall);
} else {
// SOCKET
// SOCKET
socket.emit('v1:model-Wall:add', {
wallData: updatedWall,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
socket.emit('v1:model-Wall:add', {
wallData: updatedWall,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
}
const old = initialStates[movedObject.uuid];
if (old) {
@@ -371,19 +378,22 @@ function MoveControls2D({
if (updatedFloors?.length && projectId) {
updatedFloors.forEach(updatedFloor => {
// API
if (!socket?.connected) {
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedFloor);
} else {
// SOCKET
// SOCKET
socket.emit('v1:model-Floor:add', {
floorData: updatedFloor,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
socket.emit('v1:model-Floor:add', {
floorData: updatedFloor,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
}
const updatedFloorsData = updatedFloors.map((floor) => {
const originalFloor = Floors.find(f => f.floorUuid === floor.floorUuid) || floor;
@@ -410,19 +420,22 @@ function MoveControls2D({
if (updatedZones?.length && projectId) {
updatedZones.forEach(updatedZone => {
// API
if (!socket?.connected) {
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', updatedZone);
} else {
// SOCKET
// SOCKET
socket.emit('v1:zone:add', {
zoneData: updatedZone,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
socket.emit('v1:zone:add', {
zoneData: updatedZone,
projectId,
versionId: selectedVersion?.versionId || '',
userId,
organization
});
}
const updatedZonesData = updatedZones.map((zone) => {
const originalZone = Zones.find(z => z.zoneUuid === zone.zoneUuid) || zone;
@@ -459,21 +472,24 @@ function MoveControls2D({
rotation: updatedWallAsset.rotation
});
// API
if (!socket?.connected) {
// API
// upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
upsertWallAssetApi(projectId, selectedVersion?.versionId || '', updatedWallAsset);
} else {
// SOCKET
// SOCKET
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallAssetData: updatedWallAsset,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:wall-asset:add', data);
}
socket.emit('v1:wall-asset:add', data);
}
});
}

View File

@@ -14,12 +14,13 @@ import { useSelectedPoints } from "../../../../../store/simulation/useSimulation
import { useBuilderStore } from "../../../../../store/builder/useBuilderStore";
import MoveControls2D from "./moveControls2D";
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
// import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
// import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
// import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
// import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi";
// import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
import { deleteWallApi } from "../../../../../services/factoryBuilder/wall/deleteWallApi";
import { deleteFloorApi } from "../../../../../services/factoryBuilder/floor/deleteFloorApi";
import { upsertFloorApi } from "../../../../../services/factoryBuilder/floor/upsertFloorApi";
import { deleteZoneApi } from "../../../../../services/factoryBuilder/zone/deleteZoneApi";
import { upsertZoneApi } from "../../../../../services/factoryBuilder/zone/upsertZoneApi";
import { deleteWallAssetApi } from "../../../../../services/factoryBuilder/asset/wallAsset/deleteWallAssetApi";
const SelectionControls2D: React.FC = () => {
const { camera, controls, gl, scene, pointer } = useThree();
@@ -240,22 +241,25 @@ const SelectionControls2D: React.FC = () => {
if (removedAisles.length > 0) {
removedAisles.forEach(aisle => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
deleteAisleApi(aisle.aisleUuid, projectId, selectedVersion?.versionId || '');
} else {
// SOCKET
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
aisleUuid: aisle.aisleUuid
}
socket.emit('v1:model-aisle:delete', data);
}
socket.emit('v1:model-aisle:delete', data);
}
});
@@ -280,43 +284,49 @@ const SelectionControls2D: React.FC = () => {
removeWallAsset(asset.modelUuid);
// API
if (!socket?.connected) {
// deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
// API
// SOCKET
deleteWallAssetApi(projectId, selectedVersion?.versionId || '', asset.modelUuid, asset.wallUuid);
} else {
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: asset.modelUuid,
wallUuid: asset.wallUuid
// SOCKET
const data = {
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization,
modelUuid: asset.modelUuid,
wallUuid: asset.wallUuid
}
socket.emit('v1:wall-asset:delete', data);
}
socket.emit('v1:wall-asset:delete', data);
}
})
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
deleteWallApi(projectId, selectedVersion?.versionId || '', wall.wallUuid);
} else {
// SOCKET
// SOCKET
const data = {
wallUuid: wall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
wallUuid: wall.wallUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Wall:delete', data);
}
socket.emit('v1:model-Wall:delete', data);
}
});
@@ -335,22 +345,25 @@ const SelectionControls2D: React.FC = () => {
if (removedFloors.length > 0) {
removedFloors.forEach(floor => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
deleteFloorApi(projectId, selectedVersion?.versionId || '', floor.floorUuid);
} else {
// SOCKET
// SOCKET
const data = {
floorUuid: floor.floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorUuid: floor.floorUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:delete', data);
}
socket.emit('v1:model-Floor:delete', data);
}
});
@@ -365,22 +378,25 @@ const SelectionControls2D: React.FC = () => {
if (updatedFloors.length > 0) {
updatedFloors.forEach(floor => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
upsertFloorApi(projectId, selectedVersion?.versionId || '', floor);
} else {
// SOCKET
// SOCKET
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
floorData: floor,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:model-Floor:add', data);
}
socket.emit('v1:model-Floor:add', data);
}
});
@@ -400,22 +416,25 @@ const SelectionControls2D: React.FC = () => {
if (removedZones.length > 0) {
removedZones.forEach(zone => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
deleteZoneApi(projectId, selectedVersion?.versionId || '', zone.zoneUuid);
} else {
// SOCKET
// SOCKET
const data = {
zoneUuid: zone.zoneUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneUuid: zone.zoneUuid,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:delete', data);
}
socket.emit('v1:zone:delete', data);
}
});
@@ -430,22 +449,25 @@ const SelectionControls2D: React.FC = () => {
if (updatedZones.length > 0) {
updatedZones.forEach(zone => {
if (projectId) {
if (!socket?.connected) {
// API
// API
// upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
upsertZoneApi(projectId, selectedVersion?.versionId || '', zone);
} else {
// SOCKET
// SOCKET
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
const data = {
zoneData: zone,
projectId: projectId,
versionId: selectedVersion?.versionId || '',
userId: userId,
organization: organization
}
socket.emit('v1:zone:add', data);
}
socket.emit('v1:zone:add', data);
}
});

View File

@@ -1,7 +1,7 @@
import { Line } from "@react-three/drei";
import { useMemo } from "react";
import * as THREE from "three";
import { useSelectedAssets } from "../../../../../store/builder/store";
import { useMemo } from "react";
import { Line } from "@react-three/drei";
import { useSceneContext } from "../../../sceneContext";
interface BoundingBoxProps {
boundingBoxRef?: any;
@@ -26,7 +26,8 @@ const getBoxLines = (min: THREE.Vector3, max: THREE.Vector3) => [
];
const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) => {
const { selectedAssets } = useSelectedAssets();
const { assetStore } = useSceneContext();
const { selectedAssets } = assetStore();
const savedTheme: string = localStorage.getItem("theme") || "light";
const boxes = useMemo(() => {
@@ -88,9 +89,9 @@ const BoundingBox = ({ boundingBoxRef, isPerAsset = true }: BoundingBoxProps) =>
>
<Line
name="SelectionGroupBoundingBox"
depthWrite={false}
points={box.points}
color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"}
depthWrite={false}
lineWidth={2.7}
segments
position={[box.position[0], box.position[1], box.position[2]]}

Some files were not shown because too many files have changed in this diff Show More