Merge remote-tracking branch 'origin/main-dev' into feature/layout-comparison-version

This commit is contained in:
2025-09-09 09:40:04 +05:30
35 changed files with 1419 additions and 1546 deletions

View File

@@ -3,11 +3,7 @@ import { createPortal } from "react-dom";
import { useNavigate } from "react-router-dom";
import img from "../../assets/image/image.png";
import { getUserData } from "../../functions/getUserData";
import {
useLoadingProgress,
useProjectName,
useSocketStore,
} from "../../store/builder/store";
import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store";
import OuterClick from "../../utils/outerClick";
import { KebabIcon } from "../icons/ExportCommonIcons";
import { getAllProjects } from "../../services/dashboard/getAllProjects";
@@ -15,323 +11,275 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
// import { updateProject } from "../../services/dashboard/updateProject";
interface DashBoardCardProps {
projectName: string;
thumbnail: string;
projectId: string;
createdAt?: string;
isViewed?: string;
createdBy?: { _id: string; userName: string };
handleDeleteProject?: (projectId: string) => Promise<void>;
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
handleRestoreProject?: (projectId: string) => Promise<void>;
handleDuplicateWorkspaceProject?: (
projectId: string,
projectName: string,
thumbnail: string,
userId?: string
) => Promise<void>;
handleDuplicateRecentProject?: (
projectId: string,
projectName: string,
thumbnail: string
) => Promise<void>;
active?: "shared" | "trash" | "recent" | string;
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
openKebabProjectId: string | null;
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
projectName: string;
thumbnail: string;
projectId: string;
createdAt?: string;
isViewed?: string;
createdBy?: { _id: string; userName: string };
handleDeleteProject?: (projectId: string) => Promise<void>;
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
handleRestoreProject?: (projectId: string) => Promise<void>;
handleDuplicateWorkspaceProject?: (projectId: string, projectName: string, thumbnail: string, userId?: string) => Promise<void>;
handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise<void>;
active?: "shared" | "trash" | "recent" | string;
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
openKebabProjectId: string | null;
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
}
type RelativeTimeFormatUnit =
| "year"
| "month"
| "week"
| "day"
| "hour"
| "minute"
| "second";
type RelativeTimeFormatUnit = "year" | "month" | "week" | "day" | "hour" | "minute" | "second";
const kebabOptionsMap: Record<string, string[]> = {
default: ["rename", "delete", "duplicate", "open in new tab"],
trash: ["restore", "delete"],
shared: ["duplicate", "open in new tab"],
default: ["rename", "delete", "duplicate", "open in new tab"],
trash: ["restore", "delete"],
shared: ["duplicate", "open in new tab"],
};
const DashboardCard: React.FC<DashBoardCardProps> = ({
projectName,
thumbnail,
projectId,
active,
handleDeleteProject,
handleRestoreProject,
handleTrashDeleteProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
createdAt,
createdBy,
setRecentDuplicateData,
setProjectDuplicateData,
setActiveFolder,
openKebabProjectId,
setOpenKebabProjectId,
}) => {
const navigate = useNavigate();
const { setProjectName } = useProjectName();
const { userId, organization, userName } = getUserData();
const { projectSocket } = useSocketStore();
const { setLoadingProgress } = useLoadingProgress();
const isKebabOpen = openKebabProjectId === projectId;
const [renameValue, setRenameValue] = useState(projectName);
const [isRenaming, setIsRenaming] = useState(false);
const kebabRef = useRef<HTMLDivElement>(null);
// Close kebab when clicking outside
OuterClick({
contextClassName: [`tag-${projectId}`],
setMenuVisible: () => {
if (isKebabOpen) setOpenKebabProjectId(null);
},
});
const navigateToProject = useCallback(() => {
if (active === "trash") return;
setLoadingProgress(1);
setProjectName(projectName);
navigate(`/projects/${projectId}`);
}, [
active,
projectId,
projectName,
navigate,
setLoadingProgress,
setProjectName,
]);
thumbnail,
projectId,
active,
handleDeleteProject,
handleRestoreProject,
handleTrashDeleteProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
createdAt,
createdBy,
setRecentDuplicateData,
setProjectDuplicateData,
setActiveFolder,
openKebabProjectId,
setOpenKebabProjectId,
}) => {
const navigate = useNavigate();
const { setProjectName } = useProjectName();
const { userId, organization, userName } = getUserData();
const { projectSocket } = useSocketStore();
const { setLoadingProgress } = useLoadingProgress();
const getOptions = useCallback(() => {
if (active === "trash") return kebabOptionsMap.trash;
if (active === "shared" || (createdBy && createdBy._id !== userId)) {
return kebabOptionsMap.shared;
}
return kebabOptionsMap.default;
}, [active, createdBy, userId]);
const isKebabOpen = openKebabProjectId === projectId;
const [renameValue, setRenameValue] = useState(projectName);
const [isRenaming, setIsRenaming] = useState(false);
const kebabRef = useRef<HTMLDivElement>(null);
const handleProjectName = useCallback(
async (newName: string) => {
setRenameValue(newName);
if (!projectId) return;
// Close kebab when clicking outside
OuterClick({
contextClassName: [`tag-${projectId}`],
setMenuVisible: () => {
if (isKebabOpen) setOpenKebabProjectId(null);
},
});
try {
const projects = await getAllProjects(userId, organization);
const projectUuid = projects?.Projects?.find(
(val: any) => val.projectUuid === projectId || val._id === projectId
);
if (!projectUuid) return;
const navigateToProject = useCallback(() => {
if (active === "trash") return;
setLoadingProgress(1);
setProjectName(projectName);
navigate(`/projects/${projectId}`);
}, [active, projectId, projectName, navigate, setLoadingProgress, setProjectName]);
const updatePayload = {
projectId: projectUuid._id,
organization,
userId,
projectName: newName,
const getOptions = useCallback(() => {
if (active === "trash") return kebabOptionsMap.trash;
if (active === "shared" || (createdBy && createdBy._id !== userId)) {
return kebabOptionsMap.shared;
}
return kebabOptionsMap.default;
}, [active, createdBy, userId]);
const handleProjectName = useCallback(
async (newName: string) => {
setRenameValue(newName);
if (!projectId) return;
try {
const projects = await getAllProjects(userId, organization);
const projectUuid = projects?.Projects?.find((val: any) => val.projectUuid === projectId || val._id === projectId);
if (!projectUuid) return;
const updatePayload = {
projectId: projectUuid._id,
organization,
userId,
projectName: newName,
};
if (projectSocket) {
projectSocket.emit("v1:project:update", updatePayload);
}
} catch {
// silent fail
}
},
[projectId, userId, organization, projectSocket]
);
const handleOptionClick = useCallback(
async (option: string) => {
switch (option) {
case "delete":
await (active === "trash" ? handleTrashDeleteProject?.(projectId) : handleDeleteProject?.(projectId));
break;
case "restore":
await handleRestoreProject?.(projectId);
break;
case "open in new tab":
setProjectName(projectName);
window.open(`/projects/${projectId}`, "_blank");
break;
case "rename":
setIsRenaming(true);
break;
case "duplicate":
if (handleDuplicateWorkspaceProject) {
setProjectDuplicateData?.({ projectId, projectName, thumbnail });
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
if (active === "shared") {
setActiveFolder?.("myProjects");
}
} else if (handleDuplicateRecentProject) {
setRecentDuplicateData?.({
projectId,
projectName,
thumbnail,
userId,
});
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
}
break;
}
},
[
projectId,
projectName,
thumbnail,
userId,
active,
handleDeleteProject,
handleTrashDeleteProject,
handleRestoreProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
setProjectName,
setProjectDuplicateData,
setRecentDuplicateData,
setActiveFolder,
]
);
const getRelativeTime = useCallback((dateString: string): string => {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
const intervals: Record<RelativeTimeFormatUnit, number> = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1,
};
if (projectSocket) {
projectSocket.emit("v1:project:update", updatePayload);
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
for (const [unit, seconds] of Object.entries(intervals)) {
const diff = Math.floor(diffInSeconds / seconds);
if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit);
}
} catch {
// silent fail
}
},
[projectId, userId, organization, projectSocket]
);
return "just now";
}, []);
const handleOptionClick = useCallback(
async (option: string) => {
switch (option) {
case "delete":
await (handleDeleteProject?.(projectId) ??
handleTrashDeleteProject?.(projectId));
break;
case "restore":
await handleRestoreProject?.(projectId);
break;
case "open in new tab":
setProjectName(projectName);
window.open(`/projects/${projectId}`, "_blank");
break;
case "rename":
setIsRenaming(true);
break;
case "duplicate":
if (handleDuplicateWorkspaceProject) {
setProjectDuplicateData?.({ projectId, projectName, thumbnail });
await handleDuplicateWorkspaceProject(
projectId,
projectName,
thumbnail,
userId
);
if (active === "shared") {
setActiveFolder?.("myProjects");
}
} else if (handleDuplicateRecentProject) {
setRecentDuplicateData?.({
projectId,
projectName,
thumbnail,
userId,
const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 });
useEffect(() => {
if (isKebabOpen && kebabRef.current) {
const rect = kebabRef.current.getBoundingClientRect();
setKebabPosition({
top: rect.bottom + window.scrollY,
left: rect.left + window.scrollX - 80,
});
await handleDuplicateRecentProject(
projectId,
projectName,
thumbnail
);
}
break;
}
},
[
projectId,
projectName,
thumbnail,
userId,
active,
handleDeleteProject,
handleTrashDeleteProject,
handleRestoreProject,
handleDuplicateWorkspaceProject,
handleDuplicateRecentProject,
setProjectName,
setProjectDuplicateData,
setRecentDuplicateData,
setActiveFolder,
]
);
}
}, [isKebabOpen]);
const getRelativeTime = useCallback((dateString: string): string => {
const date = new Date(dateString);
const now = new Date();
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
return (
<div className="dashboard-card-container" onClick={navigateToProject} title={projectName}>
<div className="dashboard-card-wrapper">
<div className="preview-container">
<img src={thumbnail || img} alt={`${projectName} thumbnail`} />
</div>
const intervals: Record<RelativeTimeFormatUnit, number> = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1,
};
<div className="project-details-container" onClick={(e) => e.stopPropagation()}>
<div className="project-details">
{isRenaming ? (
<input
value={renameValue}
onChange={(e) => handleProjectName(e.target.value)}
onBlur={() => {
setIsRenaming(false);
setProjectName(renameValue);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
setIsRenaming(false);
setProjectName(renameValue);
}
}}
aria-label="Rename project"
autoFocus
/>
) : (
<span>{renameValue}</span>
)}
{createdAt && (
<div className="project-data">
{active === "trash" ? "Trashed" : "Edited"} {getRelativeTime(createdAt)}
</div>
)}
</div>
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
for (const [unit, seconds] of Object.entries(intervals)) {
const diff = Math.floor(diffInSeconds / seconds);
if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit);
}
return "just now";
}, []);
const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 });
useEffect(() => {
if (isKebabOpen && kebabRef.current) {
const rect = kebabRef.current.getBoundingClientRect();
setKebabPosition({
top: rect.bottom + window.scrollY,
left: rect.left + window.scrollX - 80,
});
}
}, [isKebabOpen]);
return (
<div
className="dashboard-card-container"
onClick={navigateToProject}
title={projectName}
>
<div className="dashboard-card-wrapper">
<div className="preview-container">
<img src={thumbnail || img} alt={`${projectName} thumbnail`} />
</div>
<div
className="project-details-container"
onClick={(e) => e.stopPropagation()}
>
<div className="project-details">
{isRenaming ? (
<input
value={renameValue}
onChange={(e) => handleProjectName(e.target.value)}
onBlur={() => {
setIsRenaming(false);
setProjectName(renameValue);
}}
onKeyDown={(e) => {
if (e.key === "Enter") {
setIsRenaming(false);
setProjectName(renameValue);
}
}}
aria-label="Rename project"
autoFocus
/>
) : (
<span>{renameValue}</span>
)}
{createdAt && (
<div className="project-data">
{active === "trash" ? "Trashed" : "Edited"}{" "}
{getRelativeTime(createdAt)}
</div>
)}
</div>
<div className="users-list-container" ref={kebabRef}>
<div className="user-profile">
{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}
<div className="users-list-container" ref={kebabRef}>
<div className="user-profile">{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}</div>
<button
className="kebab-wrapper"
onClick={(e) => {
e.stopPropagation();
setOpenKebabProjectId(isKebabOpen ? null : projectId);
}}
aria-haspopup="true"
aria-expanded={isKebabOpen}
aria-label="Project options"
>
<KebabIcon />
</button>
</div>
</div>
</div>
<button
className="kebab-wrapper"
onClick={(e) => {
e.stopPropagation();
setOpenKebabProjectId(isKebabOpen ? null : projectId);
}}
aria-haspopup="true"
aria-expanded={isKebabOpen}
aria-label="Project options"
>
<KebabIcon />
</button>
</div>
</div>
</div>
{isKebabOpen &&
createPortal(
<div
className={`kebab-options-wrapper tag-${projectId}`}
style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}
>
{getOptions().map((option) => (
<button
key={option}
className="option"
onClick={(e) => {
e.stopPropagation();
handleOptionClick(option);
}}
>
{option}
</button>
))}
</div>,
document.body
)}
</div>
);
{isKebabOpen &&
createPortal(
<div className={`kebab-options-wrapper tag-${projectId}`} style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}>
{getOptions().map((option) => (
<button
key={option}
className="option"
onClick={(e) => {
e.stopPropagation();
handleOptionClick(option);
}}
>
{option}
</button>
))}
</div>,
document.body
)}
</div>
);
};
export default DashboardCard;

View File

@@ -1,13 +1,5 @@
import React from "react";
import {
DocumentationIcon,
HelpIcon,
HomeIcon,
LogoutIcon,
NotificationIcon,
ProjectsIcon,
TutorialsIcon,
} from "../icons/DashboardIcon";
import { DocumentationIcon, HelpIcon, HomeIcon, LogoutIcon, NotificationIcon, ProjectsIcon, TutorialsIcon } from "../icons/DashboardIcon";
import { useNavigate } from "react-router-dom";
import darkThemeImage from "../../assets/image/darkThemeProject.png";
import lightThemeImage from "../../assets/image/lightThemeProject.png";
@@ -54,7 +46,6 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
projectUuid: projectId,
};
console.log('addProject: ', addProject);
projectSocket.emit("v1:project:add", addProject);
} else {
// API
@@ -72,11 +63,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
<div className="side-pannel-header">
<div className="user-container">
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
<div className="user-name">
{userName
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
: "Anonymous"}
</div>
<div className="user-name">{userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"}</div>
</div>
<div className="notifications-container">
<NotificationIcon />
@@ -87,26 +74,15 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
</div>
<div className="side-bar-content-container">
<div className="side-bar-options-container">
<button
className={activeTab === "Home" ? "option-list active" : "option-list"}
onClick={() => setActiveTab("Home")}
>
<button className={activeTab === "Home" ? "option-list active" : "option-list"} onClick={() => setActiveTab("Home")}>
<HomeIcon />
Home
</button>
<button
className={activeTab === "Projects" ? "option-list active" : "option-list"}
title="Projects"
onClick={() => setActiveTab("Projects")}
>
<button className={activeTab === "Projects" ? "option-list active" : "option-list"} title="Projects" onClick={() => setActiveTab("Projects")}>
<ProjectsIcon />
Projects
</button>
<button
className={activeTab === "Trash" ? "option-list active" : "option-list"}
title="Trash"
onClick={() => setActiveTab("Trash")}
>
<button className={activeTab === "Trash" ? "option-list active" : "option-list"} title="Trash" onClick={() => setActiveTab("Trash")}>
<TrashIcon />
Trash
</button>
@@ -123,9 +99,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
Tutorials
</button>
<button
className={
activeTab === "Documentation" ? "option-list active" : "option-list"
}
className={activeTab === "Documentation" ? "option-list active" : "option-list"}
title="coming soon"
disabled
onClick={() => {

View File

@@ -4,7 +4,7 @@ import { useLogger } from "../ui/log/LoggerContext";
import { GetLogIcon } from "./getLogIcons";
import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons";
import ShortcutHelper from "./shortcutHelper";
import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store";
import { useShortcutStore } from "../../store/builder/store";
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import useModuleStore, { useSubModuleStore } from "../../store/ui/useModuleStore";
import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper";
@@ -12,123 +12,118 @@ import { useMouseNoteStore } from "../../store/ui/useUIToggleStore";
import { useSceneContext } from "../../modules/scene/sceneContext";
const Footer: React.FC = () => {
const { logs, setIsLogListVisible } = useLogger();
const lastLog = logs[logs.length - 1] || null;
const { logs, setIsLogListVisible } = useLogger();
const lastLog = logs[logs.length - 1] || null;
const { setActiveModule } = useModuleStore();
const { setSubModule } = useSubModuleStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { isPlaying } = usePlayButtonStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore();
const { versionStore } = useSceneContext();
const { selectedVersion } = versionStore();
const { setActiveModule } = useModuleStore();
const { setSubModule } = useSubModuleStore();
const { isPlaying } = usePlayButtonStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore();
const { versionStore } = useSceneContext();
const { selectedVersion, setVersionHistoryVisible } = versionStore();
const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore();
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore();
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
// -------------------- Online/Offline Handlers --------------------
const handleOnline = useCallback(() => {
echo.success("You are back Online");
setIsOnline(true);
}, []);
// -------------------- Online/Offline Handlers --------------------
const handleOnline = useCallback(() => {
echo.success("You are back Online");
setIsOnline(true);
}, []);
const handleOffline = useCallback(() => {
echo.warn("Changes made now might not be saved");
echo.error("You are now Offline.");
setIsOnline(false);
}, []);
const handleOffline = useCallback(() => {
echo.warn("Changes made now might not be saved");
echo.error("You are now Offline.");
setIsOnline(false);
}, []);
useEffect(() => {
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, [handleOnline, handleOffline]);
useEffect(() => {
window.addEventListener("online", handleOnline);
window.addEventListener("offline", handleOffline);
return () => {
window.removeEventListener("online", handleOnline);
window.removeEventListener("offline", handleOffline);
};
}, [handleOnline, handleOffline]);
// -------------------- Mouse Buttons --------------------
const mouseButtons = useMemo(
() => [
{ icon: <CurserLeftIcon />, label: Leftnote || "Pan", mouse: "left" },
{ icon: <CurserMiddleIcon />, label: Middlenote || "Scroll Zoom", mouse: "middle" },
{ icon: <CurserRightIcon />, label: Rightnote || "Orbit / Cancel action", mouse: "right" },
],
[Leftnote, Middlenote, Rightnote]
);
// -------------------- Mouse Buttons --------------------
const mouseButtons = useMemo(
() => [
{ icon: <CurserLeftIcon />, label: Leftnote || "Pan", mouse: "left" },
{ icon: <CurserMiddleIcon />, label: Middlenote || "Scroll Zoom", mouse: "middle" },
{ icon: <CurserRightIcon />, label: Rightnote || "Orbit / Cancel action", mouse: "right" },
],
[Leftnote, Middlenote, Rightnote]
);
// -------------------- Mouse Helper --------------------
useEffect(() => {
const cleanup = mouseActionHelper();
return () => cleanup();
}, []);
// -------------------- Mouse Helper --------------------
useEffect(() => {
const cleanup = mouseActionHelper();
return () => cleanup();
}, []);
return (
<div className="footer-container">
<div className="footer-wrapper">
{/* Mouse Button Info */}
<div className="selection-wrapper">
{mouseButtons.map(({ icon, label, mouse }) => (
<div className="selector-wrapper" key={mouse}>
<div className="icon">{icon}</div>
<div className="selector">{label}</div>
return (
<div className="footer-container">
<div className="footer-wrapper">
{/* Mouse Button Info */}
<div className="selection-wrapper">
{mouseButtons.map(({ icon, label, mouse }) => (
<div className="selector-wrapper" key={mouse}>
<div className="icon">{icon}</div>
<div className="selector">{label}</div>
</div>
))}
</div>
{/* Logs and Version */}
<div className="logs-wrapper">
<div className="bg-dummy left-top" />
<div className="bg-dummy right-bottom" />
<div className="log-container">
<button id="log-details-buttton" className={`logs-detail ${lastLog?.type ?? ""}`} onClick={() => setIsLogListVisible(true)}>
{lastLog ? (
<>
<span className="log-icon">{GetLogIcon(lastLog.type)}</span>
<span className="log-message">{lastLog.message}</span>
</>
) : (
"There are no logs to display at the moment."
)}
</button>
</div>
<div
className="version"
onClick={() => {
setVersionHistoryVisible(true);
setSubModule("properties");
setActiveModule("builder");
}}
>
{selectedVersion?.version ?? "v 0.0.0"}
<div className="icon">
<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>
))}
{/* Shortcut Helper */}
{!isPlaying && showShortcuts && (
<div className="shortcut-helper-overlay visible">
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
</div>
)}
</div>
{/* Logs and Version */}
<div className="logs-wrapper">
<div className="bg-dummy left-top" />
<div className="bg-dummy right-bottom" />
<div className="log-container">
<button
id="log-details-buttton"
className={`logs-detail ${lastLog?.type ?? ""}`}
onClick={() => setIsLogListVisible(true)}
>
{lastLog ? (
<>
<span className="log-icon">{GetLogIcon(lastLog.type)}</span>
<span className="log-message">{lastLog.message}</span>
</>
) : (
"There are no logs to display at the moment."
)}
</button>
</div>
<div
className="version"
onClick={() => {
setVersionHistoryVisible(true);
setSubModule("properties");
setActiveModule("builder");
}}
>
{selectedVersion?.version ?? "v 0.0.0"}
<div className="icon">
<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>
{/* Shortcut Helper */}
{!isPlaying && showShortcuts && (
<div className="shortcut-helper-overlay visible">
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
</div>
)}
</div>
);
);
};
export default Footer;

View File

@@ -1,13 +1,13 @@
import React, { useEffect, useState } from "react";
import Header from "./Header";
import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore";
import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon, } from "../../icons/SimulationIcons";
import { AnalysisIcon, FilePackageIcon, MechanicsIcon, PropertiesIcon, SimulationIcon } from "../../icons/SimulationIcons";
import { useToggleStore } from "../../../store/ui/useUIToggleStore";
import Visualization from "./visualization/Visualization";
import Analysis from "./analysis/Analysis";
import Simulations from "./simulation/Simulations";
import useVersionHistoryVisibleStore, { useIsComparing, useToolMode } from "../../../store/builder/store";
import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore";
import { useIsComparing, 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";
@@ -22,6 +22,7 @@ import SelectedFloorProperties from "./properties/SelectedFloorProperties";
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
import ResourceManagement from "./resourceManagement/ResourceManagement";
import { useSceneContext } from "../../../modules/scene/sceneContext";
type DisplayComponent =
| "versionHistory"
@@ -51,7 +52,8 @@ const SideBarRight: React.FC = () => {
const { selectedWall, selectedFloor, selectedAisle, selectedFloorAsset } = useBuilderStore();
const { selectedEventData } = useSelectedEventData();
const { selectedEventSphere } = useSelectedEventSphere();
const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { versionStore } = useSceneContext();
const { viewVersionHistory, setVersionHistoryVisible } = versionStore();
const { isComparing } = useIsComparing();
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
@@ -207,8 +209,7 @@ const SideBarRight: React.FC = () => {
<>
<button
id="sidebar-action-list-properties"
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""}`}
onClick={() => {
setSubModule("properties");
setVersionHistoryVisible(false);
@@ -224,8 +225,7 @@ const SideBarRight: React.FC = () => {
<>
<button
id="sidebar-action-list-simulation"
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""}`}
onClick={() => {
setSubModule("simulations");
setVersionHistoryVisible(false);
@@ -236,8 +236,7 @@ const SideBarRight: React.FC = () => {
</button>
<button
id="sidebar-action-list-mechanics"
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""}`}
onClick={() => {
setSubModule("mechanics");
setVersionHistoryVisible(false);
@@ -248,8 +247,7 @@ const SideBarRight: React.FC = () => {
</button>
<button
id="sidebar-action-list-analysis"
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
}`}
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""}`}
onClick={() => {
setSubModule("analysis");
setVersionHistoryVisible(false);
@@ -261,23 +259,19 @@ const SideBarRight: React.FC = () => {
</>
)}
{(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>
)}
{(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>
)}

View File

@@ -2,7 +2,6 @@ import { useParams } from "react-router-dom";
import { AddIcon, ArrowIcon, CloseIcon, KebabIcon, LocationIcon } from "../../../icons/ExportCommonIcons";
import { useSubModuleStore } from "../../../../store/ui/useModuleStore";
import { useSceneContext } from "../../../../modules/scene/sceneContext";
import useVersionHistoryVisibleStore from "../../../../store/builder/store";
import RenameInput from "../../../ui/inputs/RenameInput";
import { getVersionDataApi } from "../../../../services/factoryBuilder/versionControl/getVersionDataApi";
@@ -10,8 +9,7 @@ import { getVersionDataApi } from "../../../../services/factoryBuilder/versionCo
const VersionHistory = () => {
const { setSubModule } = useSubModuleStore();
const { versionStore } = useSceneContext();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { versionHistory, setCreateNewVersion, selectedVersion, setSelectedVersion } = versionStore();
const { versionHistory, setCreateNewVersion, selectedVersion, setSelectedVersion, setVersionHistoryVisible } = versionStore();
const { projectId } = useParams();
const addNewVersion = () => {
@@ -21,16 +19,16 @@ const VersionHistory = () => {
const handleSelectVersion = (version: Version) => {
if (!projectId) return;
getVersionDataApi(projectId, version.versionId).then((versionData) => {
setSelectedVersion(version);
}).catch((err) => {
echo.error(err);
})
getVersionDataApi(projectId, version.versionId)
.then((versionData) => {
setSelectedVersion(version);
})
.catch((err) => {
echo.error(err);
});
};
const handleVersionNameChange = (newName: string, versionId: string) => {
};
const handleVersionNameChange = (newName: string, versionId: string) => {};
return (
<div className="version-history-container">
@@ -38,11 +36,7 @@ const VersionHistory = () => {
<div className="version-history-header">
<div className="version-history-title">Version History</div>
<div className="version-history-icons">
<button
id="add-version"
className="icon add-icon"
onClick={addNewVersion}
>
<button id="add-version" className="icon add-icon" onClick={addNewVersion}>
<AddIcon />
</button>
<div id="version-kebab" className="icon kebab-icon">
@@ -64,9 +58,7 @@ const VersionHistory = () => {
{/* Shortcut Info */}
<div className="version-history-shortcut-info">
<div className="info-icon">i</div>
<div className="shortcut-text">
Press Ctrl + Alt + S to add to version history while editing
</div>
<div className="shortcut-text">Press Ctrl + Alt + S to add to version history while editing</div>
</div>
{/* Current Version Display */}
@@ -76,12 +68,8 @@ const VersionHistory = () => {
<LocationIcon />
</div>
<div className="location-details">
<div className="current-version">
Current Version ({selectedVersion.version})
</div>
<div className="saved-history-count">
{versionHistory.length} Saved History
</div>
<div className="current-version">Current Version ({selectedVersion.version})</div>
<div className="saved-history-count">{versionHistory.length} Saved History</div>
</div>
</div>
)}
@@ -93,16 +81,8 @@ const VersionHistory = () => {
) : (
versionHistory.map((version) => {
const key = `version-${version.versionId}`;
return (
<VersionHistoryItem
key={key}
version={version}
onSelect={handleSelectVersion}
onRename={handleVersionNameChange}
/>
);
return <VersionHistoryItem key={key} version={version} onSelect={handleSelectVersion} onRename={handleVersionNameChange} />;
})
)}
</div>
</div>
@@ -119,22 +99,14 @@ type VersionHistoryItemProps = {
const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ version, onSelect, onRename }) => {
return (
<button
className="saved-version"
>
<div
className="version-name"
onClick={() => onSelect(version)}
>
<button className="saved-version">
<div className="version-name" onClick={() => onSelect(version)}>
v {version.version}
</div>
<div className="version-details">
<div className="details">
<span className="timestamp">
<RenameInput
value={version.versionName ? version.versionName : version.timeStamp}
onRename={(newName) => onRename(newName, version.versionId)}
/>
<RenameInput value={version.versionName ? version.versionName : version.timeStamp} onRename={(newName) => onRename(newName, version.versionId)} />
</span>
<span className="saved-by">
<div className="user-profile">{version.createdBy[0]}</div>

View File

@@ -1,104 +1,84 @@
import React from "react";
import useModuleStore from "../../store/ui/useModuleStore";
import {
BuilderIcon,
CartIcon,
SimulationIcon,
VisualizationIcon,
} from "../icons/ExportModuleIcons";
import {useToggleStore} from "../../store/ui/useUIToggleStore";
import useVersionStore from "../../store/builder/store";
import { BuilderIcon, CartIcon, SimulationIcon, VisualizationIcon } from "../icons/ExportModuleIcons";
import { useToggleStore } from "../../store/ui/useUIToggleStore";
import { useSceneContext } from "../../modules/scene/sceneContext";
const ModuleToggle: React.FC = () => {
const { activeModule, setActiveModule } = useModuleStore();
const { setToggleUI } = useToggleStore();
const { setVersionHistoryVisible } = useVersionStore();
const { activeModule, setActiveModule } = useModuleStore();
const { setToggleUI } = useToggleStore();
const { versionStore } = useSceneContext();
const { setVersionHistoryVisible } = versionStore();
return (
<div className="module-toggle-container">
<button
id={"builder"}
className={`module-list ${activeModule === "builder" ? "active" : ""}`}
onClick={() => {
setActiveModule("builder");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft")
? localStorage.getItem("navBarUiLeft") === "true"
: true,
localStorage.getItem("navBarUiRight")
? localStorage.getItem("navBarUiRight") === "true"
: true
);
}}
>
<div className="icon">
<BuilderIcon isActive={activeModule === "builder"} />
return (
<div className="module-toggle-container">
<button
id={"builder"}
className={`module-list ${activeModule === "builder" ? "active" : ""}`}
onClick={() => {
setActiveModule("builder");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
);
}}
>
<div className="icon">
<BuilderIcon isActive={activeModule === "builder"} />
</div>
<div className="module">Builder</div>
</button>
<button
id={"simulation"}
className={`module-list ${activeModule === "simulation" ? "active" : ""}`}
onClick={() => {
setActiveModule("simulation");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
);
}}
>
<div className="icon">
<SimulationIcon isActive={activeModule === "simulation"} />
</div>
<div className="module">Simulation</div>
</button>
<button
id={"visualization"}
className={`module-list ${activeModule === "visualization" ? "active" : ""}`}
onClick={() => {
setActiveModule("visualization");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
);
}}
>
<div className="icon">
<VisualizationIcon isActive={activeModule === "visualization"} />
</div>
<div className="module">Visualization</div>
</button>
<button
id={"market"}
className={`module-list ${activeModule === "market" ? "active" : ""}`}
onClick={() => {
setActiveModule("market");
setVersionHistoryVisible(false);
setToggleUI(false, false);
}}
>
<div className="icon">
<CartIcon isActive={activeModule === "market"} />
</div>
<div className="module">Market Place</div>
</button>
</div>
<div className="module">Builder</div>
</button>
<button
id={"simulation"}
className={`module-list ${
activeModule === "simulation" ? "active" : ""
}`}
onClick={() => {
setActiveModule("simulation");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft")
? localStorage.getItem("navBarUiLeft") === "true"
: true,
localStorage.getItem("navBarUiRight")
? localStorage.getItem("navBarUiRight") === "true"
: true
);
}}
>
<div className="icon">
<SimulationIcon isActive={activeModule === "simulation"} />
</div>
<div className="module">Simulation</div>
</button>
<button
id={"visualization"}
className={`module-list ${
activeModule === "visualization" ? "active" : ""
}`}
onClick={() => {
setActiveModule("visualization");
setVersionHistoryVisible(false);
setToggleUI(
localStorage.getItem("navBarUiLeft")
? localStorage.getItem("navBarUiLeft") === "true"
: true,
localStorage.getItem("navBarUiRight")
? localStorage.getItem("navBarUiRight") === "true"
: true
);
}}
>
<div className="icon">
<VisualizationIcon isActive={activeModule === "visualization"} />
</div>
<div className="module">Visualization</div>
</button>
<button
id={"market"}
className={`module-list ${activeModule === "market" ? "active" : ""}`}
onClick={() => {
setActiveModule("market");
setVersionHistoryVisible(false);
setToggleUI(false, false);
}}
>
<div className="icon">
<CartIcon isActive={activeModule === "market"} />
</div>
<div className="module">Market Place</div>
</button>
</div>
);
);
};
export default ModuleToggle;

View File

@@ -6,7 +6,7 @@ import { handleSaveTemplate } from "../../modules/visualization/functions/handle
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import useTemplateStore from "../../store/ui/useTemplateStore";
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
import { useActiveTool, useAddAction, useSocketStore, useToggleView, useToolMode, useActiveSubTool, useShortcutStore } from "../../store/builder/store";
import { useActiveTool, useSocketStore, useToggleView, useToolMode, useActiveSubTool, useShortcutStore } from "../../store/builder/store";
import { useToggleStore } from "../../store/ui/useUIToggleStore";
import { use3DWidget, useFloatingWidget } from "../../store/visualization/useDroppedObjectsStore";
import { useParams } from "react-router-dom";
@@ -34,7 +34,7 @@ const Tools: React.FC = () => {
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { showShortcuts } = useShortcutStore();
const { activeTool, setActiveTool, setToolMode, setAddAction } = useStoreHooks();
const { activeTool, setActiveTool, setToolMode } = useStoreHooks();
const { setSelectedWallAsset } = useBuilderStore();
const { setActiveSubTool, activeSubTool } = useActiveSubTool();
const { setToggleUI } = useToggleStore();
@@ -86,7 +86,6 @@ const Tools: React.FC = () => {
const resetTools = () => {
setToolMode(null);
setAddAction(null);
};
const updateToolBehavior = (tool: string, is2D: boolean) => {
@@ -116,7 +115,7 @@ const Tools: React.FC = () => {
setToolMode("MeasurementScale");
break;
case "Add pillar":
if (!is2D) setAddAction("Pillar");
if (!is2D) setToolMode("Pillar");
break;
case "delete":
is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete");
@@ -131,7 +130,6 @@ const Tools: React.FC = () => {
setToggleUI(toggleTo2D, toggleTo2D);
if (toggleTo2D) {
setSelectedWallAsset(null);
setAddAction(null);
}
setActiveTool("cursor");
setActiveSubTool("cursor");
@@ -242,7 +240,19 @@ const Tools: React.FC = () => {
<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)} />)}
{["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)}>
@@ -308,7 +318,6 @@ const useStoreHooks = () => {
return {
...useActiveTool(),
...useToolMode(),
...useAddAction(),
};
};

View File

@@ -2,7 +2,7 @@ import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { ArrowIcon } from "../../icons/ExportCommonIcons";
import { toggleTheme } from "../../../utils/theme";
import useVersionHistoryVisibleStore, { useShortcutStore } from "../../../store/builder/store";
import { useShortcutStore } from "../../../store/builder/store";
import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore";
import { useSceneContext } from "../../../modules/scene/sceneContext";
@@ -25,8 +25,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({});
const { versionStore } = useSceneContext();
const { setCreateNewVersion } = versionStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
const { setActiveModule } = useModuleStore();
const { setSubModule } = useSubModuleStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore();
@@ -79,7 +78,16 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
{ label: "Import" },
{ label: "Close File" },
],
Edit: [{ label: "Undo", shortcut: "Ctrl + Z" }, { label: "Redo", shortcut: "Ctrl + Shift + Z" }, { label: "Undo History" }, { label: "Redo History" }, { label: "Find", shortcut: "Ctrl + F" }, { label: "Delete" }, { label: "Select by..." }, { label: "Keymap" }],
Edit: [
{ label: "Undo", shortcut: "Ctrl + Z" },
{ label: "Redo", shortcut: "Ctrl + Shift + Z" },
{ label: "Undo History" },
{ label: "Redo History" },
{ label: "Find", shortcut: "Ctrl + F" },
{ label: "Delete" },
{ label: "Select by..." },
{ label: "Keymap" },
],
View: [
{ label: "Grid" },
{
@@ -172,7 +180,13 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
<div className="dropdown-menu">
{items.map((item) =>
item.submenu ? (
<button id={item.label} key={item.label} className="menu-item-container" onMouseEnter={() => setActiveSubMenu(item.label)} onMouseLeave={() => setActiveSubMenu(null)}>
<button
id={item.label}
key={item.label}
className="menu-item-container"
onMouseEnter={() => setActiveSubMenu(item.label)}
onMouseLeave={() => setActiveSubMenu(null)}
>
<div className="menu-item">
<span>{item.label}</span>
<span className="dropdown-icon">

View File

@@ -19,7 +19,7 @@ function AisleCreator() {
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addAisle, getAislePointById } = aisleStore();
const { addAisle, getAislePointById, getConnectedPoints } = aisleStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
@@ -76,7 +76,9 @@ function AisleCreator() {
newPoint.layer = snappedPoint.layer;
}
if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) {
const connectedPoints = getConnectedPoints(tempPoints[0]?.pointUuid || "");
if (snappedPoint && (snappedPoint.pointUuid === tempPoints[0]?.pointUuid || connectedPoints.some((point) => point.pointUuid === snappedPoint.pointUuid))) {
return;
}
@@ -194,7 +196,32 @@ function AisleCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addAisle, getAislePointById, aisleType, aisleWidth, aisleColor, dashLength, gapLength, dotRadius, aisleLength, snappedPosition, snappedPoint, selectedVersion?.versionId]);
}, [
gl,
camera,
scene,
raycaster,
pointer,
plane,
toggleView,
toolMode,
activeLayer,
socket,
tempPoints,
isCreating,
addAisle,
getAislePointById,
aisleType,
aisleWidth,
aisleColor,
dashLength,
gapLength,
dotRadius,
aisleLength,
snappedPosition,
snappedPoint,
selectedVersion?.versionId,
]);
return (
<>

View File

@@ -7,7 +7,7 @@ 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,
@@ -63,13 +63,12 @@ async function addAssetModel(
}
if (intersectPoint) {
if (intersectPoint.y < 0) {
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
}
const cachedModel = THREE.Cache.get(selectedItem.id);
if (cachedModel) {
handleModelLoad(cachedModel, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
handleModelLoad(cachedModel, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
return;
} else {
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
@@ -79,21 +78,19 @@ async function addAssetModel(
URL.revokeObjectURL(blobUrl);
THREE.Cache.remove(blobUrl);
THREE.Cache.add(selectedItem.id, gltf);
handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
});
} else {
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`,
async (gltf) => {
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
await storeGLTF(selectedItem.id, modelBlob);
THREE.Cache.add(selectedItem.id, gltf);
await handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
}
);
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => {
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
await storeGLTF(selectedItem.id, modelBlob);
THREE.Cache.add(selectedItem.id, gltf);
await handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
});
}
}
}
} catch (error) {
} catch {
echo.error("Failed to add asset");
} finally {
setSelectedItem({});
@@ -118,7 +115,7 @@ async function handleModelLoad(
modelId: selectedItem.id,
modelUuid: model.uuid,
};
model.position.set(intersectPoint!.x, intersectPoint!.y, intersectPoint!.z);
model.position.set(intersectPoint.x, intersectPoint.y, intersectPoint.z);
model.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap);
model.traverse((child: any) => {
@@ -132,7 +129,7 @@ async function handleModelLoad(
modelUuid: model.uuid,
modelName: selectedItem.name,
assetId: selectedItem.id,
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
position: [intersectPoint.x, intersectPoint.y, intersectPoint.z],
rotation: [0, 0, 0],
isLocked: false,
isVisible: true,
@@ -141,13 +138,9 @@ async function handleModelLoad(
};
if (selectedItem.type) {
const data = PointsCalculator(
selectedItem.type,
gltf.scene.clone(),
new THREE.Vector3(...model.rotation)
);
const data = PointsCalculator(selectedItem.type, gltf.scene.clone(), new THREE.Vector3(...model.rotation));
if (!data || !data.points) return;
if (!data?.points) return;
const eventData: any = { type: selectedItem.type, subType: selectedItem.subType };
@@ -159,7 +152,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation,
state: "idle",
type: "transfer",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
speed: 1,
points: data.points.map((point: THREE.Vector3, index: number) => {
const triggers: TriggerSchema[] = [];
@@ -228,7 +221,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation,
state: "idle",
type: "vehicle",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
@@ -266,7 +259,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation,
state: "idle",
type: "roboticArm",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
@@ -300,7 +293,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation,
state: "idle",
type: "machine",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z],
@@ -332,7 +325,7 @@ async function handleModelLoad(
storageCapacity: 10,
storageCount: 10,
materialType: "Default material",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z],
@@ -343,7 +336,7 @@ async function handleModelLoad(
actionName: "Action 1",
actionType: "store",
triggers: [],
}
},
],
},
};
@@ -361,7 +354,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation,
state: "idle",
type: "human",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
speed: 1,
point: {
uuid: THREE.MathUtils.generateUUID(),
@@ -375,23 +368,23 @@ async function handleModelLoad(
loadCount: 1,
assemblyCount: 1,
assemblyCondition: {
conditionType: 'material',
materialType: "Default material"
conditionType: "material",
materialType: "Default material",
},
manufactureCount: 1,
loadCapacity: 1,
processTime: 10,
triggers: []
}
]
}
}
triggers: [],
},
],
},
};
addEvent(humanEvent);
eventData.point = {
uuid: humanEvent.point.uuid,
position: humanEvent.point.position,
rotation: humanEvent.point.rotation,
}
};
} else if (selectedItem.type === "Crane") {
const craneEvent: CraneEventSchema = {
modelUuid: newFloorItem.modelUuid,
@@ -400,7 +393,7 @@ async function handleModelLoad(
rotation: newFloorItem.rotation,
state: "idle",
type: "crane",
subType: selectedItem.subType || '',
subType: selectedItem.subType || "",
point: {
uuid: THREE.MathUtils.generateUUID(),
position: [data.points[0].x, data.points[0].y, data.points[0].z],
@@ -411,17 +404,17 @@ async function handleModelLoad(
actionName: "Action 1",
actionType: "pickAndDrop",
maxPickUpCount: 1,
triggers: []
}
]
}
}
triggers: [],
},
],
},
};
addEvent(craneEvent);
eventData.point = {
uuid: craneEvent.point.uuid,
position: craneEvent.point.position,
rotation: craneEvent.point.rotation,
}
};
}
const completeData = {
@@ -430,10 +423,10 @@ async function handleModelLoad(
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
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,
@@ -448,14 +441,13 @@ async function handleModelLoad(
modelName: newFloorItem.modelName,
position: newFloorItem.position,
assetId: newFloorItem.assetId,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
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
@@ -467,7 +459,7 @@ async function handleModelLoad(
modelName: completeData.modelName,
assetId: completeData.assetId,
position: completeData.position,
rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z,] as [number, number, number],
rotation: [completeData.rotation.x, completeData.rotation.y, completeData.rotation.z] as [number, number, number],
isLocked: completeData.isLocked,
isCollidable: false,
isVisible: completeData.isVisible,
@@ -483,10 +475,10 @@ async function handleModelLoad(
modelName: newFloorItem.modelName,
assetId: newFloorItem.assetId,
position: newFloorItem.position,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
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,
@@ -500,13 +492,12 @@ async function handleModelLoad(
modelName: newFloorItem.modelName,
position: newFloorItem.position,
assetId: newFloorItem.assetId,
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z, },
rotation: { x: model.rotation.x, y: model.rotation.y, z: model.rotation.z },
isLocked: false,
isVisible: true,
versionId: versionId,
projectId: projectId,
});
} else {
// SOCKET

View File

@@ -17,7 +17,6 @@ import SocketResponses from "../collaboration/socket/socketResponses.dev";
import Ground from "../scene/environment/ground";
import MeasurementTool from "../scene/tools/measurementTool";
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
import CalculateAreaGroup from "./groups/calculateAreaGroup";
import LayoutImage from "./layout/layoutImage";
import AssetsGroup from "./asset/assetsGroup";
import DxfFile from "./dfx/LoadBlueprint";
@@ -51,8 +50,8 @@ export default function Builder() {
if (!toggleView) {
setHoveredLine(null);
setHoveredPoint(null);
state.gl.domElement.style.cursor = 'default';
setToolMode('cursor');
state.gl.domElement.style.cursor = "default";
setToolMode("cursor");
}
}, [toggleView]);
@@ -65,14 +64,14 @@ export default function Builder() {
setShadows(data.shadowVisibility);
setRenderDistance(data.renderDistance);
setLimitDistance(data.limitDistance);
})
});
}, [projectId]);
useFrame(() => {
if (csgRef.current && selectedWallAsset) {
csgRef.current.update();
}
})
});
return (
<>
@@ -82,13 +81,11 @@ export default function Builder() {
<AssetsGroup plane={plane} />
<mesh name='Walls-And-WallAssets-Group'>
<mesh name="Walls-And-WallAssets-Group">
<Geometry ref={csgRef} useGroups>
<WallGroup />
<WallAssetGroup />
</Geometry>
</mesh>
@@ -102,8 +99,6 @@ export default function Builder() {
<MeasurementTool />
<CalculateAreaGroup />
<NavMesh />
<DxfFile />

View File

@@ -1,50 +1,61 @@
import { useMemo } from 'react';
import { DoubleSide, Shape, Vector2 } from 'three';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../types/world/worldConstants';
import { useMemo } from "react";
import { DoubleSide, Shape, Vector2 } from "three";
import { Extrude, Html } from "@react-three/drei";
import * as Constants from "../../../../../types/world/worldConstants";
import getCenteroidPoint from "../../../functions/getCenteroid";
import getArea from "../../../functions/getArea";
function Floor2DInstance({ floor }: { readonly floor: Floor }) {
const savedTheme: string | null = localStorage.getItem("theme");
const points2D = useMemo(() => {
return floor.points.map((p) => new Vector2(parseFloat(p.position[0].toFixed(2)), parseFloat(p.position[2].toFixed(2))));
}, [floor]);
const shape = useMemo(() => {
const shape = new Shape();
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);
shape.moveTo(points2D[0].x, points2D[0].y);
for (let i = 1; i < points2D.length; i++) {
shape.lineTo(points2D[i].x, points2D[i].y);
}
shape.lineTo(points2D[0].x, points2D[0].y);
return shape;
}, [floor]);
}, [points2D]);
const area = useMemo(() => getArea(points2D), [points2D]);
const centroid: [number, number, number] = useMemo(() => {
const center = getCenteroidPoint(points2D);
if (!center) return [0, Constants.floorConfig.height + 0.01, 0];
return [center.x, Constants.floorConfig.height + 0.01, center.y] as [number, number, number];
}, [points2D]);
if (!shape) return null;
const formattedArea = `${area.toFixed(2)}`;
return (
<mesh
castShadow
receiveShadow
name={`Floor-2D-${floor.floorUuid}`}
rotation={[Math.PI / 2, 0, 0]}
position={[0, 0, 0]}
userData={floor}
>
<Extrude
name={`Floor-${floor.floorUuid}`}
args={[shape, {
depth: Constants.floorConfig.height,
}]}
userData={floor}
>
<meshBasicMaterial
color={savedTheme === "dark" ? "#808080" : "#808080"}
side={DoubleSide}
transparent
opacity={0.4}
depthWrite={false}
/>
</Extrude>
</mesh>
<>
<mesh castShadow receiveShadow name={`Floor-2D-${floor.floorUuid}`} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]} userData={floor}>
<Extrude name={`Floor-${floor.floorUuid}`} args={[shape, { depth: Constants.floorConfig.height }]} userData={floor}>
<meshBasicMaterial
color={savedTheme === "dark" ? Constants.lineConfig.floorColor : Constants.lineConfig.floorColor}
side={DoubleSide}
transparent
opacity={0.4}
depthWrite={false}
/>
</Extrude>
</mesh>
<Html key={floor.floorUuid} position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
<div className="distance area">
{floor.floorName} ({formattedArea})
</div>
</Html>
</>
);
}
export default Floor2DInstance;
export default Floor2DInstance;

View File

@@ -1,14 +1,14 @@
import React, { useEffect, useMemo } from 'react';
import { Vector3 } from 'three';
import { Html } from '@react-three/drei';
import { useSceneContext } from '../../../scene/sceneContext';
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/ui/useModuleStore';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import React, { useEffect, useMemo } from "react";
import { Vector3 } from "three";
import { Html } from "@react-three/drei";
import { useSceneContext } from "../../../scene/sceneContext";
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/ui/useModuleStore";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
function FloorInstances() {
const { floorStore } = useSceneContext();
@@ -20,11 +20,11 @@ function FloorInstances() {
useEffect(() => {
// console.log('floors: ', floors);
}, [floors])
}, [floors]);
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (!toggleView && activeModule === "builder") {
if (toolMode !== "cursor") {
if (selectedFloor) setSelectedFloor(null);
}
} else if (selectedFloor) {
@@ -36,8 +36,8 @@ function FloorInstances() {
const points: Point[] = [];
const seenUuids = new Set<string>();
floors.forEach(floor => {
floor.points.forEach(point => {
floors.forEach((floor) => {
floor.points.forEach((point) => {
if (!seenUuids.has(point.pointUuid)) {
seenUuids.add(point.pointUuid);
points.push(point);
@@ -65,7 +65,7 @@ function FloorInstances() {
lines.push({
start: current,
end: next,
key: lineKey
key: lineKey,
});
}
}
@@ -76,9 +76,8 @@ function FloorInstances() {
return (
<>
{!toggleView && floors.length > 0 && (
<mesh name='Floors-Group'>
<mesh name="Floors-Group">
{floors.map((floor) => (
<FloorInstance key={floor.floorUuid} floor={floor} />
))}
@@ -86,7 +85,7 @@ function FloorInstances() {
)}
{toggleView && floors.length > 0 && (
<mesh name='Floors-2D-Group'>
<mesh name="Floors-2D-Group">
{floors.map((floor) => (
<Floor2DInstance key={floor.floorUuid} floor={floor} />
))}
@@ -95,14 +94,13 @@ function FloorInstances() {
{toggleView && (
<>
<group name='Floor-Points-Group'>
<group name="Floor-Points-Group">
{allPoints.map((point) => (
<Point key={point.pointUuid} point={point} />
))}
</group>
<group name='Floor-Lines-Group'>
<group name="Floor-Lines-Group">
{allLines.map(({ start, end, key }) => (
<Line key={key} points={[start, end]} />
))}
@@ -114,7 +112,7 @@ function FloorInstances() {
return (
<React.Fragment key={key}>
{toggleView &&
{toggleView && (
<Html
key={`${start.pointUuid}_${end.pointUuid}`}
userData={line}
@@ -125,24 +123,19 @@ function FloorInstances() {
prepend
sprite
>
<div
key={key}
className={`distance ${key}`}
>
<div key={key} className={`distance ${key}`}>
{distance.toFixed(2)} m
</div>
</Html>
}
)}
</React.Fragment>
)
);
})}
</group>
</>
)}
</>
)
);
}
export default FloorInstances;
export default FloorInstances;

View File

@@ -86,6 +86,10 @@ function FloorCreator() {
return;
}
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition;
}
@@ -288,7 +292,7 @@ function FloorCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addFloor, getFloorPointById, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
}, [camera, raycaster, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, floorDepth, isBeveled, bevelStrength, sideMaterial, topMaterial, snappedPosition, snappedPoint]);
return (
<>

View File

@@ -1,29 +0,0 @@
import { Vector2 } from "three";
export function computeArea(data: any, type: "zone" | "rooms" | "aisle"): any {
if (type === "zone") {
const points3D = data.map((p: any) => new Vector2(p[0], p[2]));
let area = 0;
for (let i = 0; i < points3D.length - 1; i++) {
const current = points3D[i];
const next = points3D[i + 1];
area += current.x * next.y - next.x * current.y;
}
return Math.abs(area) / 2;
}
if (type === "rooms") {
const points2D = data.coordinates.map(
(coordinate: any) =>
new Vector2(coordinate.position.x, coordinate.position.z)
);
let area = 0;
for (let i = 0; i < points2D.length - 1; i++) {
const current = points2D[i];
const next = points2D[i + 1];
area += current.x * next.y - next.x * current.y;
}
return Math.abs(area) / 2;
}
}

View File

@@ -0,0 +1,10 @@
import { Vector2 } from "three";
export default function getArea(points: Vector2[]): number {
let sum = 0;
for (let i = 0; i < points.length; i++) {
const j = (i + 1) % points.length;
sum += points[i].x * points[j].y - points[j].x * points[i].y;
}
return Math.abs(sum / 2);
}

View File

@@ -0,0 +1,48 @@
import { Vector2 } from "three";
export default function getCenteroidPoint(points: Vector2[]): Vector2 | null {
if (points.length < 3) return null;
let minZ = points[0].y;
let maxZ = points[0].y;
for (let i = 1; i < points.length; i++) {
minZ = Math.min(minZ, points[i].y);
maxZ = Math.max(maxZ, points[i].y);
}
const scanZ = minZ + (maxZ - minZ) * 0.51;
const intersections: number[] = [];
for (let i = 0; i < points.length; i++) {
const p1 = points[i];
const p2 = points[(i + 1) % points.length];
if (p1.y === p2.y) continue;
const [top, bottom] = p1.y > p2.y ? [p1, p2] : [p2, p1];
if (scanZ >= bottom.y && scanZ < top.y) {
const t = (scanZ - bottom.y) / (top.y - bottom.y);
const x = bottom.x + t * (top.x - bottom.x);
intersections.push(x);
}
}
intersections.sort((a, b) => a - b);
if (intersections.length >= 2) {
const x = (intersections[0] + intersections[1]) / 2;
return new Vector2(x, scanZ);
}
let x = 0,
z = 0;
for (const p of points) {
x += p.x;
z += p.y;
}
x /= points.length;
z /= points.length;
return new Vector2(x, z);
}

View File

@@ -1,106 +0,0 @@
import { useRoomsState, useToggleView } from "../../../store/builder/store";
import { computeArea } from "../functions/computeArea";
import { Html } from "@react-three/drei";
import * as CONSTANTS from "../../../types/world/worldConstants";
import * as turf from "@turf/turf";
import * as THREE from "three";
const CalculateAreaGroup = () => {
const { roomsState } = useRoomsState();
const { toggleView } = useToggleView();
const savedTheme: string | null = localStorage.getItem("theme");
return (
<group name="roomArea" visible={toggleView}>
<group name="roomFills" visible={toggleView}>
{roomsState.length > 0 &&
roomsState.flat().map((room: any, index: number) => {
const coordinates = room.coordinates;
if (!coordinates || coordinates.length < 3) return null;
const coords2D = coordinates.map((p: any) => new THREE.Vector2(p.position.x, p.position.z));
if (!coords2D[0].equals(coords2D[coords2D.length - 1])) {
coords2D.push(coords2D[0]);
}
const shape = new THREE.Shape(coords2D);
const extrudeSettings = {
depth: 0.01,
bevelEnabled: false,
};
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
geometry.rotateX(Math.PI / 2);
const material = new THREE.MeshBasicMaterial({
color: savedTheme === "dark" ? "#d2baff" : "#6f42c1",
side: THREE.DoubleSide,
transparent: true,
opacity: 0.4,
depthWrite: false,
});
return (
<group key={`roomFill-${index}`}>
<mesh
geometry={geometry}
material={material}
position={[0, 0.12, 0]}
/>
</group>
);
})}
</group>
{roomsState.length > 0 &&
roomsState.flat().map((room: any, index: number) => {
if (!toggleView) return null;
const coordinates = room.coordinates;
if (!coordinates || coordinates.length < 3) return null;
let coords2D = coordinates.map((p: any) => [p.position.x, p.position.z,]);
const first = coords2D[0];
const last = coords2D[coords2D.length - 1];
if (first[0] !== last[0] || first[1] !== last[1]) {
coords2D.push(first);
}
const polygon = turf.polygon([coords2D]);
const center2D = turf.center(polygon).geometry.coordinates;
const sumY = coordinates.reduce((sum: number, p: any) => sum + p.position.y, 0);
const avgY = sumY / coordinates.length;
const area = computeArea(room, "rooms");
const formattedArea = `${area.toFixed(2)}`;
const htmlPosition: [number, number, number] = [
center2D[0],
avgY + CONSTANTS.zoneConfig.height,
center2D[1],
];
return (
<Html
key={`${index}-${room.layer || index}`}
position={htmlPosition}
wrapperClass="distance-text-wrapper"
className="distance-text"
zIndexRange={[1, 0]}
prepend
center
sprite
>
<div className={`distance area line-${room.layer || index}`}>
Room ({formattedArea})
</div>
</Html>
);
})}
</group>
);
};
export default CalculateAreaGroup;

View File

@@ -1,16 +1,16 @@
import * as THREE from 'three';
import { useCallback } from 'react';
import { useSceneContext } from '../../../scene/sceneContext';
import * as THREE from "three";
import { useCallback } from "react";
import { useSceneContext } from "../../../scene/sceneContext";
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const CAN_POINT_SNAP = true; // Whether snapping is enabled or not
const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const ANGLE_SNAP_DISTANCE_THRESHOLD = 0.5; // Distance threshold for snapping in meters
const CAN_ANGLE_SNAP = true; // Whether snapping is enabled or not
export const usePointSnapping = (currentPoint: { uuid: string, pointType: string, position: [number, number, number] } | null) => {
export const usePointSnapping = (currentPoint: { uuid: string; pointType: string; position: [number, number, number] } | null) => {
const { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
@@ -21,350 +21,359 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
const getAllOtherWallPoints = useCallback(() => {
if (!currentPoint) return [];
return walls.flatMap(wall =>
wall.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return walls.flatMap((wall) => wall.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [walls, currentPoint]);
const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapWallPoint = useCallback(
(position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherWallPoints();
if (tempPoints) {
otherPoints.push(tempPoints);
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Wall') {
return { position: point.position, isSnapped: true, snappedPoint: point };
const otherPoints = getAllOtherWallPoints();
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Wall") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
}
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherWallPoints]);
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherWallPoints]
);
const snapWallAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const snapWallAngle = useCallback(
(newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: [],
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: []
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedAislePoints]);
},
[currentPoint, getConnectedAislePoints]
);
// Aisle Snapping
const getAllOtherAislePoints = useCallback(() => {
if (!currentPoint) return [];
return aisles.flatMap(aisle =>
aisle.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return aisles.flatMap((aisle) => aisle.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [aisles, currentPoint]);
const snapAislePoint = useCallback((position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapAislePoint = useCallback(
(position: [number, number, number]) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const otherPoints = getAllOtherAislePoints();
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') {
return { position: point.position, isSnapped: true, snappedPoint: point };
const otherPoints = getAllOtherAislePoints();
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Aisle") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
}
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherAislePoints]);
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherAislePoints]
);
const snapAisleAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const snapAisleAngle = useCallback(
(newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: [],
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return {
position: newPosition,
isSnapped: false,
snapSources: []
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedAislePoints]);
},
[currentPoint, getConnectedAislePoints]
);
// Floor Snapping
const getAllOtherFloorPoints = useCallback(() => {
if (!currentPoint) return [];
return floors.flatMap(floor =>
floor.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return floors.flatMap((floor) => floor.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [floors, currentPoint]);
const snapFloorPoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapFloorPoint = useCallback(
(position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherFloorPoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Floor') {
return { position: point.position, isSnapped: true, snappedPoint: point };
let otherPoints = getAllOtherFloorPoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
}
const currentVec = new THREE.Vector3(...position);
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherFloorPoints]);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
const snapFloorAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Floor") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherFloorPoints]
);
const snapFloorAngle = useCallback(
(
newPosition: [number, number, number]
): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedFloorPoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedFloorPoints]);
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
},
[currentPoint, getConnectedFloorPoints]
);
// Zone Snapping
const getAllOtherZonePoints = useCallback(() => {
if (!currentPoint) return [];
return zones.flatMap(zone =>
zone.points.filter(point => point.pointUuid !== currentPoint.uuid)
);
return zones.flatMap((zone) => zone.points.filter((point) => point.pointUuid !== currentPoint.uuid));
}, [zones, currentPoint]);
const snapZonePoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
const snapZonePoint = useCallback(
(position: [number, number, number], tempPoints?: Point[] | []) => {
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
let otherPoints = getAllOtherZonePoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
const currentVec = new THREE.Vector3(...position);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Zone') {
return { position: point.position, isSnapped: true, snappedPoint: point };
let otherPoints = getAllOtherZonePoints();
if (tempPoints) {
otherPoints = [...otherPoints, ...tempPoints];
}
}
const currentVec = new THREE.Vector3(...position);
return { position: position, isSnapped: false, snappedPoint: null };
}, [currentPoint, getAllOtherZonePoints]);
for (const point of otherPoints) {
const pointVec = new THREE.Vector3(...point.position);
const distance = currentVec.distanceTo(pointVec);
const snapZoneAngle = useCallback((newPosition: [number, number, number]): {
position: [number, number, number],
isSnapped: boolean,
snapSources: THREE.Vector3[]
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3, dist: number } | null = null;
let closestZ: { pos: THREE.Vector3, dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Zone") {
return { position: point.position, isSnapped: true, snappedPoint: point };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
return { position: position, isSnapped: false, snappedPoint: null };
},
[currentPoint, getAllOtherZonePoints]
);
const snapZoneAngle = useCallback(
(
newPosition: [number, number, number]
): {
position: [number, number, number];
isSnapped: boolean;
snapSources: THREE.Vector3[];
} => {
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
const connectedPoints = getConnectedZonePoints(currentPoint.uuid);
if (connectedPoints.length === 0) {
return { position: newPosition, isSnapped: false, snapSources: [] };
}
const newPos = new THREE.Vector3(...newPosition);
let closestX: { pos: THREE.Vector3; dist: number } | null = null;
let closestZ: { pos: THREE.Vector3; dist: number } | null = null;
for (const connectedPoint of connectedPoints) {
const cPos = new THREE.Vector3(...connectedPoint.position);
const xDist = Math.abs(newPos.x - cPos.x);
const zDist = Math.abs(newPos.z - cPos.z);
if (xDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestX || xDist < closestX.dist) {
closestX = { pos: cPos, dist: xDist };
}
}
if (zDist < ANGLE_SNAP_DISTANCE_THRESHOLD) {
if (!closestZ || zDist < closestZ.dist) {
closestZ = { pos: cPos, dist: zDist };
}
}
}
}
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
const snappedPos = newPos.clone();
const snapSources: THREE.Vector3[] = [];
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestX) {
snappedPos.x = closestX.pos.x;
snapSources.push(closestX.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
if (closestZ) {
snappedPos.z = closestZ.pos.z;
snapSources.push(closestZ.pos.clone());
}
const isSnapped = snapSources.length > 0;
const isSnapped = snapSources.length > 0;
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources
};
}, [currentPoint, getConnectedZonePoints]);
return {
position: [snappedPos.x, snappedPos.y, snappedPos.z],
isSnapped,
snapSources,
};
},
[currentPoint, getConnectedZonePoints]
);
return {
snapAislePoint,
@@ -376,4 +385,4 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
snapZonePoint,
snapZoneAngle,
};
};
};

View File

@@ -1,151 +1,73 @@
import { useMemo } from 'react';
import * as turf from '@turf/turf';
import { useMemo } from "react";
import * as turf from "@turf/turf";
export function useWallClassification(walls: Walls) {
const findRooms = () => {
if (walls.length < 3) return [];
// Map pointUuid to list of connected line segments
const pointMap = new Map<string, Wall[]>();
const wallSet = new Map<string, Wall>();
const positionPairSet = new Set<string>();
const makeKey = (p1: Point, p2: Point) => {
return [p1.pointUuid, p2.pointUuid].sort((a, b) => a.localeCompare(b)).join("-");
};
const makePositionPairKey = (p1: Point, p2: Point) => {
const sortedPositions = [
[p1.position[0].toFixed(6), p1.position[2].toFixed(6)],
[p2.position[0].toFixed(6), p2.position[2].toFixed(6)],
].sort((a, b) => a[0].localeCompare(b[0]) || a[1].localeCompare(b[1]));
return sortedPositions.map((pos) => pos.join(",")).join("|");
};
for (const wall of walls) {
for (const point of wall.points) {
const list = pointMap.get(point.pointUuid) || [];
list.push(wall);
pointMap.set(point.pointUuid, list);
const [p1, p2] = wall.points;
if (p1.pointUuid === p2.pointUuid || (p1.position[0] === p2.position[0] && p1.position[1] === p2.position[1] && p1.position[2] === p2.position[2])) {
continue;
}
const positionPairKey = makePositionPairKey(p1, p2);
if (positionPairSet.has(positionPairKey)) {
continue;
}
positionPairSet.add(positionPairKey);
const key = makeKey(p1, p2);
if (!wallSet.has(key)) {
wallSet.set(key, wall);
}
}
// Create graph of connected walls using pointUuid
const visited = new Set<string>();
const mergedLineStrings = [];
const uniqueWalls = Array.from(wallSet.values());
const wallKey = (p1: Point, p2: Point) => `${p1.pointUuid}-${p2.pointUuid}`;
const lineStrings = uniqueWalls.map((wall) => {
const coords: [number, number][] = wall.points.map((p) => [p.position[0], p.position[2]]);
return turf.lineString(coords, { wallUuid: wall.wallUuid });
});
for (const wall of walls) {
const key = wallKey(wall.points[0], wall.points[1]);
if (visited.has(key) || visited.has(wallKey(wall.points[1], wall.points[0]))) continue;
const collection = turf.featureCollection(lineStrings);
let line: Point[] = [...wall.points];
visited.add(key);
// Try to extend the line forward and backward by matching endpoints
let extended = true;
while (extended) {
extended = false;
const last = line[line.length - 1];
const nextWalls = pointMap.get(last.pointUuid) || [];
for (const next of nextWalls) {
const [n1, n2] = next.points;
const nextKey = wallKey(n1, n2);
if (visited.has(nextKey) || visited.has(wallKey(n2, n1))) continue;
if (n1.pointUuid === last.pointUuid && n2.pointUuid !== line[line.length - 2]?.pointUuid) {
line.push(n2);
visited.add(nextKey);
extended = true;
break;
} else if (n2.pointUuid === last.pointUuid && n1.pointUuid !== line[line.length - 2]?.pointUuid) {
line.push(n1);
visited.add(nextKey);
extended = true;
break;
}
}
const first = line[0];
const prevWalls = pointMap.get(first.pointUuid) || [];
for (const prev of prevWalls) {
const [p1, p2] = prev.points;
const prevKey = wallKey(p1, p2);
if (visited.has(prevKey) || visited.has(wallKey(p2, p1))) continue;
if (p1.pointUuid === first.pointUuid && p2.pointUuid !== line[1]?.pointUuid) {
line.unshift(p2);
visited.add(prevKey);
extended = true;
break;
} else if (p2.pointUuid === first.pointUuid && p1.pointUuid !== line[1]?.pointUuid) {
line.unshift(p1);
visited.add(prevKey);
extended = true;
break;
}
}
}
// Create merged LineString
const coords = line.map(p => [p.position[0], p.position[2]]);
mergedLineStrings.push(turf.lineString(coords, {
pointUuids: line.map(p => p.pointUuid)
}));
}
const validLineStrings = mergedLineStrings.map(ls => {
const coords = ls.geometry.coordinates.map(coord => coord.join(','));
if (coords.length < 2) return null;
const start = coords[0];
const end = coords[coords.length - 1];
const middle = coords.slice(1, -1);
const seen = new Set<string>([start, end]);
const filteredMiddle: string[] = [];
for (const point of middle) {
if (!seen.has(point)) {
seen.add(point);
filteredMiddle.push(point);
}
}
const newCoords = [start, ...filteredMiddle, end];
if (newCoords.length >= 4) {
const resultCoords = newCoords.map(str => str.split(',').map(Number));
return {
...ls,
geometry: {
...ls.geometry,
coordinates: resultCoords,
},
};
}
return null;
}).filter(Boolean);
if (validLineStrings.length === 0) return [];
const lineStrings = turf.featureCollection(validLineStrings as any);
const polygons = turf.polygonize(lineStrings as any);
const polygons = turf.polygonize(collection);
const rooms: Point[][] = [];
polygons.features.forEach(feature => {
if (feature.geometry.type === 'Polygon') {
const coordinates = feature.geometry.coordinates[0];
polygons.features.forEach((feature) => {
if (feature.geometry.type === "Polygon") {
const coords = feature.geometry.coordinates[0];
const roomPoints: Point[] = [];
for (const [x, z] of coordinates) {
const matchingPoint = walls.flatMap(wall => wall.points)
.find(p =>
p.position[0].toFixed(10) === x.toFixed(10) &&
p.position[2].toFixed(10) === z.toFixed(10)
);
for (const [x, z] of coords) {
const matchingPoint = uniqueWalls.flatMap((w) => w.points).find((p) => p.position[0].toFixed(6) === x.toFixed(6) && p.position[2].toFixed(6) === z.toFixed(6));
if (matchingPoint) {
roomPoints.push(matchingPoint);
}
}
if (roomPoints.length > 0 &&
roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
if (roomPoints.length > 0 && roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
roomPoints.push(roomPoints[0]);
}
@@ -161,12 +83,14 @@ export function useWallClassification(walls: Walls) {
const rooms = useMemo(() => findRooms(), [walls]);
const findWallType = (wall: Wall) => {
const containingRooms = rooms.filter(room => {
const containingRooms = rooms.filter((room) => {
for (let i = 0; i < room.length - 1; i++) {
const p1 = room[i];
const p2 = room[i + 1];
if ((wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) ||
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)) {
if (
(wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) ||
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)
) {
return true;
}
}
@@ -175,18 +99,18 @@ export function useWallClassification(walls: Walls) {
if (containingRooms.length === 0) {
return {
type: 'segment',
rooms: []
type: "segment",
rooms: [],
};
} else if (containingRooms.length === 1) {
return {
type: 'room',
rooms: containingRooms
type: "room",
rooms: containingRooms,
};
} else {
return {
type: 'rooms',
rooms: containingRooms
type: "rooms",
rooms: containingRooms,
};
}
};
@@ -197,16 +121,16 @@ export function useWallClassification(walls: Walls) {
const isRoomWall = (wall: Wall): boolean => {
const type = findWallType(wall).type;
return type === 'room' || type === 'rooms';
return type === "room" || type === "rooms";
};
const isSegmentWall = (wall: Wall): boolean => {
return findWallType(wall).type === 'segment';
return findWallType(wall).type === "segment";
};
const isWallFlipped = (wall: Wall): boolean => {
const wallType = findWallType(wall);
if (wallType.type === 'segment') return false;
if (wallType.type === "segment") return false;
for (const room of wallType.rooms) {
for (let i = 0; i < room.length - 1; i++) {
@@ -223,13 +147,12 @@ export function useWallClassification(walls: Walls) {
return false;
};
return {
rooms,
getWallType,
isRoomWall,
isSegmentWall,
findRooms,
isWallFlipped
isWallFlipped,
};
}
}

View File

@@ -1,19 +1,21 @@
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, useToolMode } from '../../../../store/builder/store';
import { useWallClassification } from './instance/helpers/useWallClassification';
import Line from '../../line/line';
import Point from '../../point/point';
import WallInstance from './instance/wallInstance';
import * as Constants from '../../../../types/world/worldConstants';
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, useToolMode } from "../../../../store/builder/store";
import { useWallClassification } from "./instance/helpers/useWallClassification";
import Line from "../../line/line";
import Point from "../../point/point";
import WallInstance from "./instance/wallInstance";
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/ui/useModuleStore';
import useModuleStore from "../../../../store/ui/useModuleStore";
import getCenteroidPoint from "../../functions/getCenteroid";
import getArea from "../../functions/getArea";
function WallInstances() {
const { wallStore } = useSceneContext();
@@ -25,12 +27,12 @@ function WallInstances() {
const { rooms } = useWallClassification(walls);
useEffect(() => {
// console.log('walls: ', walls);
}, [walls])
// console.log("walls: ", walls);
}, [walls]);
useEffect(() => {
if (!toggleView && activeModule === 'builder') {
if (toolMode !== 'cursor') {
if (!toggleView && activeModule === "builder") {
if (toolMode !== "cursor") {
if (selectedWall) setSelectedWall(null);
}
} else {
@@ -42,8 +44,8 @@ function WallInstances() {
const points: Point[] = [];
const seenUuids = new Set<string>();
walls.forEach(wall => {
wall.points.forEach(point => {
walls.forEach((wall) => {
wall.points.forEach((point) => {
if (!seenUuids.has(point.pointUuid)) {
seenUuids.add(point.pointUuid);
points.push(point);
@@ -62,9 +64,9 @@ function WallInstances() {
<WallInstance key={wall.wallUuid} wall={wall} />
))}
<group name='Wall-Floors-Group'>
<group name="Wall-Floors-Group">
{rooms.map((room, index) => (
<Floor key={index} room={room} />
<Floor3D key={index} room={room} />
))}
</group>
</>
@@ -72,14 +74,19 @@ function WallInstances() {
{toggleView && (
<>
<group name='Wall-Points-Group'>
<group name="Wall-Points-Group">
{allPoints.map((point) => (
<Point key={point.pointUuid} point={point} />
))}
</group>
<group name='Wall-Lines-Group'>
<group name="Wall-Floors-Group">
{rooms.map((room, index) => (
<Floor2D key={index} room={room} />
))}
</group>
<group name="Wall-Lines-Group">
{walls.map((wall) => (
<React.Fragment key={wall.wallUuid}>
<Line points={wall.points} />
@@ -91,8 +98,8 @@ function WallInstances() {
const distance = new Vector3(...wall.points[0].position).distanceTo(new Vector3(...wall.points[1].position));
return (
< React.Fragment key={wall.wallUuid}>
{toggleView &&
<React.Fragment key={wall.wallUuid}>
{toggleView && (
<Html
key={`${wall.points[0].pointUuid}_${wall.points[1].pointUuid}`}
userData={wall}
@@ -103,29 +110,25 @@ function WallInstances() {
prepend
sprite
>
<div
key={wall.wallUuid}
className={`distance ${wall.wallUuid}`}
>
<div key={wall.wallUuid} className={`distance ${wall.wallUuid}`}>
{distance.toFixed(2)} m
</div>
</Html>
}
)}
</React.Fragment>
)
);
})}
</group>
</>
)}
</>
)
);
}
export default WallInstances;
function Floor({ room }: { readonly room: Point[] }) {
const savedTheme: string | null = localStorage.getItem('theme');
function Floor3D({ room }: { readonly room: Point[] }) {
const savedTheme: string | null = localStorage.getItem("theme");
const textureScale = Constants.floorConfig.textureScale;
const floorTexture = useLoader(TextureLoader, savedTheme === "dark" ? texturePathDark : texturePath);
floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping;
@@ -134,10 +137,12 @@ function Floor({ room }: { readonly room: Point[] }) {
const shape = useMemo(() => {
const shape = new Shape();
const points = room.map(p => new Vector2(p.position[0], p.position[2]));
const points = room.map((p) => new Vector2(p.position[0], p.position[2]));
if (points.length < 3) return null;
shape.moveTo(points[0].x, points[0].y);
points.forEach((pt) => { shape.lineTo(pt.x, pt.y); });
points.forEach((pt) => {
shape.lineTo(pt.x, pt.y);
});
return shape;
}, [room]);
@@ -145,15 +150,54 @@ function Floor({ room }: { readonly room: Point[] }) {
return (
<mesh name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
<Extrude
receiveShadow
castShadow
name="Wall-Floor"
args={[shape, { depth: Constants.floorConfig.height, bevelEnabled: false }]}
position={[0, 0, 0]}
>
<Extrude receiveShadow castShadow name="Wall-Floor" args={[shape, { depth: Constants.floorConfig.height, bevelEnabled: false }]} position={[0, 0, 0]}>
<meshStandardMaterial color={Constants.floorConfig.defaultColor} map={floorTexture} side={DoubleSide} />
</Extrude>
</mesh>
);
}
function Floor2D({ room }: { readonly room: Point[] }) {
const savedTheme: string | null = localStorage.getItem("theme");
const points2D = useMemo(() => {
return room.map((p) => new Vector2(parseFloat(p.position[0].toFixed(2)), parseFloat(p.position[2].toFixed(2))));
}, [room]);
const shape = useMemo(() => {
const shape = new Shape();
shape.moveTo(points2D[0].x, points2D[0].y);
for (let i = 1; i < points2D.length; i++) {
shape.lineTo(points2D[i].x, points2D[i].y);
}
shape.lineTo(points2D[0].x, points2D[0].y);
return shape;
}, [points2D]);
const area = useMemo(() => getArea(points2D), [points2D]);
const centroid: [number, number, number] = useMemo(() => {
const center = getCenteroidPoint(points2D);
if (!center) return [0, Constants.floorConfig.height + 0.01, 0];
return [center.x, Constants.floorConfig.height + 0.01, center.y] as [number, number, number];
}, [points2D]);
if (!shape) return null;
const formattedArea = `${area.toFixed(2)}`;
return (
<>
<mesh castShadow receiveShadow name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
<Extrude receiveShadow castShadow name="Wall-Floor" args={[shape, { depth: Constants.floorConfig.height }]} position={[0, 0, 0]}>
<meshBasicMaterial color={savedTheme === "dark" ? Constants.lineConfig.wallColor : Constants.lineConfig.wallColor} side={DoubleSide} transparent opacity={0.4} depthWrite={false} />
</Extrude>
</mesh>
<Html position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
<div className="distance area">({formattedArea})</div>
</Html>
</>
);
}

View File

@@ -1,12 +1,12 @@
import { useEffect, useRef, useState } from 'react';
import * as THREE from 'three';
import { useFrame, useThree } from '@react-three/fiber';
import { Html } from '@react-three/drei';
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
import ReferenceLine from '../../line/reference/referenceLine';
import { useEffect, useRef, useState } from "react";
import * as THREE from "three";
import { useFrame, useThree } from "@react-three/fiber";
import { Html } from "@react-three/drei";
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
import { useActiveLayer, useToolMode, useToggleView } from "../../../../store/builder/store";
import { useDirectionalSnapping } from "../../point/helpers/useDirectionalSnapping";
import { usePointSnapping } from "../../point/helpers/usePointSnapping";
import ReferenceLine from "../../line/reference/referenceLine";
interface ReferenceWallProps {
tempPoints: Point[];
@@ -26,10 +26,10 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null);
const { snapWallPoint } = usePointSnapping({ uuid: 'temp-wall', pointType: 'Wall', position: directionalSnap.position || [0, 0, 0] });
const { snapWallPoint } = usePointSnapping({ uuid: "temp-wall", pointType: "Wall", position: directionalSnap.position || [0, 0, 0] });
useFrame(() => {
if (toolMode === 'Wall' && toggleView && tempPoints.length === 1) {
if (toolMode === "Wall" && toggleView && tempPoints.length === 1) {
raycaster.setFromCamera(pointer, camera);
const intersectionPoint = new THREE.Vector3();
raycaster.ray.intersectPlane(plane, intersectionPoint);
@@ -37,7 +37,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (!intersectionPoint) return;
const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z], tempPoints[0]);
const snapped = snapWallPoint([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
if (snapped.isSnapped && snapped.snappedPoint) {
finalPosition.current = snapped.position;
@@ -58,23 +58,22 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
const wallPoints: [Point, Point] = [
tempPoints[0],
{
pointUuid: 'temp-point',
pointType: 'Wall',
pointUuid: "temp-point",
pointType: "Wall",
position: finalPosition.current,
layer: activeLayer,
}
},
];
setTempWall({
wallUuid: 'temp-wall',
wallUuid: "temp-wall",
points: wallPoints,
outsideMaterial: 'default',
insideMaterial: 'default',
outsideMaterial: "default",
insideMaterial: "default",
wallThickness: wallThickness,
wallHeight: wallHeight,
decals: []
})
decals: [],
});
} else if (tempWall !== null) {
setTempWall(null);
}
@@ -87,9 +86,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
if (!tempWall) return null;
const renderWall = () => {
return (
<ReferenceLine points={tempWall.points} />
)
return <ReferenceLine points={tempWall.points} />;
};
const textPosition = new THREE.Vector3().addVectors(new THREE.Vector3(...tempWall.points[0].position), new THREE.Vector3(...tempWall.points[1].position)).divideScalar(2);
@@ -113,8 +110,8 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
</Html>
)}
</>
)
}
);
};
return (
<group name="Wall-Reference-Group">
@@ -124,4 +121,4 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
);
}
export default ReferenceWall;
export default ReferenceWall;

View File

@@ -22,7 +22,7 @@ function WallCreator() {
const { activeLayer } = useActiveLayer();
const { socket } = useSocketStore();
const { wallStore, undoRedo2DStore, versionStore } = useSceneContext();
const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore();
const { addWall, getWallPointById, removeWall, getWallByPoints, getConnectedPoints } = wallStore();
const { push2D } = undoRedo2DStore();
const drag = useRef(false);
const isLeftMouseDown = useRef(false);
@@ -379,7 +379,9 @@ function WallCreator() {
newPoint.layer = snappedPoint.layer;
}
if (snappedPoint && snappedPoint.pointUuid === tempPoints[0]?.pointUuid) {
const connectedPoints = getConnectedPoints(tempPoints[0]?.pointUuid || "");
if (snappedPoint && (snappedPoint.pointUuid === tempPoints[0]?.pointUuid || connectedPoints.some((point) => point.pointUuid === snappedPoint.pointUuid))) {
return;
}
@@ -485,7 +487,29 @@ function WallCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addWall, getWallPointById, wallThickness, wallHeight, insideMaterial, outsideMaterial, snappedPosition, snappedPoint, selectedVersion?.versionId]);
}, [
gl,
camera,
scene,
raycaster,
pointer,
plane,
toggleView,
toolMode,
activeLayer,
socket,
tempPoints,
isCreating,
addWall,
getWallPointById,
wallThickness,
wallHeight,
insideMaterial,
outsideMaterial,
snappedPosition,
snappedPoint,
selectedVersion?.versionId,
]);
return (
<>

View File

@@ -1,50 +1,55 @@
import { useMemo } from 'react';
import { DoubleSide, Shape, Vector2 } from 'three';
import { Extrude } from '@react-three/drei';
import * as Constants from '../../../../../types/world/worldConstants';
import { useMemo } from "react";
import { DoubleSide, Shape, Vector2 } from "three";
import { Extrude, Html } from "@react-three/drei";
import * as Constants from "../../../../../types/world/worldConstants";
import getCenteroid from "../../../functions/getCenteroid";
import getArea from "../../../functions/getArea";
function Zone2DInstance({ zone }: { readonly zone: Zone }) {
const savedTheme: string | null = localStorage.getItem("theme");
const points2D = useMemo(() => {
return zone.points.map((p) => new Vector2(parseFloat(p.position[0].toFixed(2)), parseFloat(p.position[2].toFixed(2))));
}, [zone]);
const shape = useMemo(() => {
const shape = new Shape();
const points = zone.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);
shape.moveTo(points2D[0].x, points2D[0].y);
for (let i = 1; i < points2D.length; i++) {
shape.lineTo(points2D[i].x, points2D[i].y);
}
shape.lineTo(points2D[0].x, points2D[0].y);
return shape;
}, [zone]);
}, [points2D]);
const area = useMemo(() => getArea(points2D), [points2D]);
const centroid: [number, number, number] = useMemo(() => {
const center = getCenteroid(points2D);
if (!center) return [0, Constants.floorConfig.height + 0.01, 0];
return [center.x, Constants.floorConfig.height + 0.01, center.y] as [number, number, number];
}, [points2D]);
if (!shape) return null;
const formattedArea = `${area.toFixed(2)}`;
return (
<mesh
castShadow
receiveShadow
name={`Zone-2D-${zone.zoneUuid}`}
rotation={[Math.PI / 2, 0, 0]}
position={[0, 0, 0]}
userData={zone}
>
<Extrude
name={`Zone-${zone.zoneUuid}`}
args={[shape, {
depth: Constants.floorConfig.height,
}]}
userData={zone}
>
<meshBasicMaterial
color={savedTheme === "dark" ? "#007BFF" : "#007BFF"}
side={DoubleSide}
transparent
opacity={0.4}
depthWrite={false}
/>
</Extrude>
</mesh>
<>
<mesh castShadow receiveShadow name={`Zone-2D-${zone.zoneUuid}`} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]} userData={zone}>
<Extrude name={`Zone-${zone.zoneUuid}`} args={[shape, { depth: Constants.floorConfig.height }]} userData={zone}>
<meshBasicMaterial color={savedTheme === "dark" ? Constants.lineConfig.zoneColor : Constants.lineConfig.zoneColor} side={DoubleSide} transparent opacity={0.4} depthWrite={false} />
</Extrude>
</mesh>
<Html key={zone.zoneUuid} position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
<div className="distance area">
{zone.zoneName} ({formattedArea})
</div>
</Html>
</>
);
}
export default Zone2DInstance;
export default Zone2DInstance;

View File

@@ -1,12 +1,12 @@
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 Line from '../../line/line';
import Point from '../../point/point';
import ZoneInstance from './Instance/zoneInstance';
import Zone2DInstance from './Instance/zone2DInstance';
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 Line from "../../line/line";
import Point from "../../point/point";
import ZoneInstance from "./Instance/zoneInstance";
import Zone2DInstance from "./Instance/zone2DInstance";
function ZoneInstances() {
const { zoneStore } = useSceneContext();
@@ -15,14 +15,14 @@ function ZoneInstances() {
useEffect(() => {
// console.log('zones: ', zones);
}, [zones])
}, [zones]);
const allPoints = useMemo(() => {
const points: Point[] = [];
const seenUuids = new Set<string>();
zones.forEach(zone => {
zone.points.forEach(point => {
zones.forEach((zone) => {
zone.points.forEach((point) => {
if (!seenUuids.has(point.pointUuid)) {
seenUuids.add(point.pointUuid);
points.push(point);
@@ -50,7 +50,7 @@ function ZoneInstances() {
lines.push({
start: current,
end: next,
key: lineKey
key: lineKey,
});
}
}
@@ -61,9 +61,8 @@ function ZoneInstances() {
return (
<>
{!toggleView && zones.length > 0 && (
<mesh name='Zones-Group'>
<mesh name="Zones-Group">
{zones.map((zone) => (
<ZoneInstance key={zone.zoneUuid} zone={zone} />
))}
@@ -71,7 +70,7 @@ function ZoneInstances() {
)}
{toggleView && zones.length > 0 && (
<mesh name='Zones-2D-Group'>
<mesh name="Zones-2D-Group">
{zones.map((zone) => (
<Zone2DInstance key={zone.zoneUuid} zone={zone} />
))}
@@ -80,14 +79,13 @@ function ZoneInstances() {
{toggleView && (
<>
<group name='Zone-Points-Group'>
<group name="Zone-Points-Group">
{allPoints.map((point) => (
<Point key={point.pointUuid} point={point} />
))}
</group>
<group name='Zone-Lines-Group'>
<group name="Zone-Lines-Group">
{allLines.map(({ start, end, key }) => (
<Line key={key} points={[start, end]} />
))}
@@ -99,7 +97,7 @@ function ZoneInstances() {
return (
<React.Fragment key={key}>
{toggleView &&
{toggleView && (
<Html
key={`${start.pointUuid}_${end.pointUuid}`}
userData={line}
@@ -110,23 +108,19 @@ function ZoneInstances() {
prepend
sprite
>
<div
key={key}
className={`distance ${key}`}
>
<div key={key} className={`distance ${key}`}>
{distance.toFixed(2)} m
</div>
</Html>
}
)}
</React.Fragment>
)
);
})}
</group>
</>
)}
</>
)
);
}
export default ZoneInstances
export default ZoneInstances;

View File

@@ -86,6 +86,10 @@ function ZoneCreator() {
return;
}
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
return;
}
if (snappedPosition && !snappedPoint) {
newPoint.position = snappedPosition;
}
@@ -282,7 +286,7 @@ function ZoneCreator() {
canvasElement.removeEventListener("click", onMouseClick);
canvasElement.removeEventListener("contextmenu", onContext);
};
}, [gl, camera, scene, raycaster, pointer, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
}, [gl, camera, scene, raycaster, plane, toggleView, toolMode, activeLayer, socket, tempPoints, isCreating, addZone, getZonePointById, zoneColor, zoneHeight, snappedPosition, snappedPoint]);
return (
<>

View File

@@ -40,7 +40,7 @@ const CamMode: React.FC = () => {
const keyCombination = detectModifierKeys(event);
if (keyCombination === "/" && !isTransitioning && !toggleView) {
setIsTransitioning && setIsTransitioning(true);
setIsTransitioning(true);
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
@@ -55,10 +55,10 @@ const CamMode: React.FC = () => {
await switchToThirdPerson(state.controls, state.camera);
}
setIsTransitioning && setIsTransitioning(false);
setIsTransitioning(false);
}
if (keyCombination === 'Shift') {
if (keyCombination === "Shift") {
setIsShiftActive(true);
}
};

View File

@@ -1,9 +1,9 @@
import { useEffect } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from 'three';
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei';
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 * as CONSTANTS from "../../../types/world/worldConstants";
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
import { useToggleView } from "../../../store/builder/store";
@@ -18,19 +18,21 @@ export default function SwitchView() {
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
} else {
if (!projectId) return;
getCameraApi(projectId).then((data) => {
if (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);
} else {
getCameraApi(projectId)
.then((data) => {
if (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);
} else {
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
})
.catch(() => {
echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
}
}).catch(() => {
echo.error("Failed to retrieve camera position or target");
(controls as CameraControls)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
})
});
if (controls) {
(controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
@@ -62,4 +64,4 @@ export default function SwitchView() {
)}
</>
);
}
}

View File

@@ -1,5 +1,5 @@
import { CameraControls } from "@react-three/drei";
import { useRef, useEffect } from "react";
import { useRef, useEffect, useState } from "react";
import { useThree } from "@react-three/fiber";
import * as THREE from "three";
import * as CONSTANTS from "../../../types/world/worldConstants";
@@ -7,6 +7,7 @@ import { useSocketStore, useToggleView, useResetCamera } from "../../../store/bu
import CamMode from "../camera/camMode";
import SwitchView from "../camera/switchView";
import SyncCam from "../camera/syncCam";
import ContextControls from "./contextControls/contextControls";
import TransformControl from "./transformControls/transformControls";
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
@@ -20,7 +21,6 @@ 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";
import SyncCam from "../camera/syncCam";
export default function Controls() {
const controlsRef = useRef<CameraControls>(null);
@@ -59,7 +59,12 @@ export default function Controls() {
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
if (!socket?.connected) {
setCameraApi(projectId, new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition), new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget), new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation));
setCameraApi(
projectId,
new THREE.Vector3(...CONSTANTS.threeDimension.defaultPosition),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultTarget),
new THREE.Vector3(...CONSTANTS.threeDimension.defaultRotation)
);
} else {
const camData = {
organization,
@@ -143,7 +148,6 @@ export default function Controls() {
boundaryEnclosesCamera={true}
dollyDragInverted
>
<SwitchView />
<CamMode />

View File

@@ -23,6 +23,7 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
{ name: "right", keys: ["ArrowRight", "d", "D"] },
{ name: "jump", keys: ["Space"] },
],
[]
);

View File

@@ -169,14 +169,24 @@ export function useRetrieveHandler() {
if (vehicle && !vehicle.isActive && vehicle.state === "idle" && vehicle.isPicking && vehicle.currentLoad < vehicle.point.action.loadCapacity) {
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
if (material) {
addCurrentAction(triggeredModel.modelUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "", material.materialType, material.materialId);
addCurrentAction(
triggeredModel.modelUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "",
material.materialType,
material.materialId
);
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
}
}
} else {
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
if (material) {
addCurrentAction(triggeredModel.modelUuid, retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "", material.materialType, material.materialId);
addCurrentAction(
triggeredModel.modelUuid,
retrieval.action.triggers[0]?.triggeredAsset.triggeredAction?.actionUuid ?? "",
material.materialType,
material.materialId
);
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
}
}
@@ -282,7 +292,9 @@ export function useRetrieveHandler() {
}
if (human && !human.isScheduled && human.state === "idle" && human.currentLoad < action.loadCapacity) {
const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid ? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid) : null;
const triggeredModel = action.triggers[0]?.triggeredAsset?.triggeredModel?.modelUuid
? getEventByModelUuid(selectedProduct.productUuid, action.triggers[0].triggeredAsset.triggeredModel.modelUuid)
: null;
const storageAction = getActionByUuid(selectedProduct.productUuid, actionUuid);
@@ -290,7 +302,18 @@ export function useRetrieveHandler() {
const model = getVehicleById(triggeredModel.modelUuid);
if (model && !model.isActive && model.state === "idle" && model.isPicking && model.currentLoad < model.point.action.loadCapacity) {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
@@ -305,6 +328,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}
}
monitoredHumansRef.current.delete(human.modelUuid);
}
return;
}
@@ -312,7 +336,18 @@ export function useRetrieveHandler() {
const armBot = getArmBotById(triggeredModel.modelUuid);
if (armBot && !armBot.isActive && armBot.state === "idle" && !armBot.currentAction) {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
@@ -327,6 +362,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}
}
monitoredHumansRef.current.delete(human.modelUuid);
}
return;
}
@@ -334,7 +370,18 @@ export function useRetrieveHandler() {
const model = getConveyorById(triggeredModel.modelUuid);
if (model && !model.isPaused) {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
@@ -349,6 +396,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}
}
monitoredHumansRef.current.delete(human.modelUuid);
}
return;
}
@@ -385,6 +433,18 @@ export function useRetrieveHandler() {
return;
}
} else if (triggeredModel?.type === "storageUnit") {
if (!monitoredHumansRef.current.has(human.modelUuid)) {
addHumanToMonitor(
human.modelUuid,
() => {
if (humanAsset?.animationState?.current === "idle") {
setCurrentAnimation(human.modelUuid, "pickup", true, false, false);
}
},
action.actionUuid
);
}
monitoredHumansRef.current.add(human.modelUuid);
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
if (lastMaterial) {
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
@@ -397,6 +457,7 @@ export function useRetrieveHandler() {
retrievalCountRef.current.set(actionUuid, currentCount + 1);
}
monitoredHumansRef.current.delete(human.modelUuid);
}
}
}

View File

@@ -61,21 +61,11 @@ export const useLoadingProgress = create<{
setLoadingProgress: (x: number) => set({ loadingProgress: x }),
}));
export const useOrganization = create<any>((set: any) => ({
organization: "",
setOrganization: (x: any) => set(() => ({ organization: x })),
}));
export const useToggleView = create<any>((set: any) => ({
toggleView: false,
setToggleView: (x: any) => set(() => ({ toggleView: x })),
}));
export const useRoomsState = create<any>((set: any) => ({
roomsState: [],
setRoomsState: (x: any) => set(() => ({ roomsState: x })),
}));
export const useSelectedItem = create<any>((set: any) => ({
selectedItem: {
name: "",
@@ -107,11 +97,6 @@ export const useNavMesh = create<any>((set: any) => ({
setNavMesh: (x: any) => set({ navMesh: x }),
}));
export const useLayers = create<any>((set: any) => ({
Layers: 1,
setLayers: (x: any) => set(() => ({ Layers: x })),
}));
export const useCamPosition = create<any>((set: any) => ({
camPosition: { x: undefined, y: undefined, z: undefined },
setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }),
@@ -177,11 +162,6 @@ export const useResetCamera = create<any>((set: any) => ({
setResetCamera: (x: any) => set({ resetCamera: x }),
}));
export const useAddAction = create<any>((set: any) => ({
addAction: null,
setAddAction: (x: any) => set({ addAction: x }),
}));
export const useActiveTool = create<any>((set: any) => ({
activeTool: "cursor",
setActiveTool: (x: any) => set({ activeTool: x }),
@@ -315,11 +295,6 @@ export const useTileDistance = create<any>((set: any) => ({
})),
}));
export const usePlayAgv = create<any>((set, get) => ({
PlayAgv: [],
setPlayAgv: (updateFn: (prev: any[]) => any[]) => set({ PlayAgv: updateFn(get().PlayAgv) }),
}));
// Define the Asset type
type Asset = {
id: string;
@@ -350,19 +325,6 @@ export const useResourceManagementId = create<ResourceManagementState>((set) =>
setResourceManagementId: (id: string) => set({ resourceManagementId: id }),
}));
// version visible hidden
interface VersionHistoryState {
viewVersionHistory: boolean;
setVersionHistoryVisible: (value: boolean) => void;
}
const useVersionHistoryVisibleStore = create<VersionHistoryState>((set) => ({
viewVersionHistory: false,
setVersionHistoryVisible: (value) => set({ viewVersionHistory: value }),
}));
export default useVersionHistoryVisibleStore;
interface ShortcutStore {
showShortcuts: boolean;
setShowShortcuts: (value: boolean) => void;

View File

@@ -1,14 +1,16 @@
import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';
import { create } from "zustand";
import { immer } from "zustand/middleware/immer";
interface VersionStore {
versionHistory: VersionHistory;
selectedVersion: Version | null;
viewVersionHistory: boolean;
createNewVersion: boolean;
setSelectedVersion: (version: Version) => void;
clearSelectedVersion: () => void;
setVersionHistoryVisible: (visibility: boolean) => void;
setCreateNewVersion: (createNewVersion: boolean) => void;
addVersion: (version: Version) => void;
@@ -26,6 +28,7 @@ export const createVersionStore = () => {
immer((set, get) => ({
versionHistory: [],
selectedVersion: null,
viewVersionHistory: false,
createNewVersion: false,
setSelectedVersion: (version) => {
@@ -40,58 +43,64 @@ export const createVersionStore = () => {
});
},
setVersionHistoryVisible: (visibility: boolean) => {
set((state) => {
state.viewVersionHistory = visibility;
});
},
setCreateNewVersion: (createNewVersion: boolean) => {
set((state) => {
state.createNewVersion = createNewVersion;
})
});
},
addVersion: (version: Version) => {
set((state) => {
state.versionHistory.unshift(version);
})
});
},
setVersions: (versions: Version[]) => {
set((state) => {
state.versionHistory = versions;
})
});
},
clearVersions: () => {
set((state) => {
state.versionHistory = [];
state.selectedVersion = null;
state.createNewVersion = false
})
state.createNewVersion = false;
});
},
setVersionName: (versionId: string, versionName: string) => {
set((state) => {
const version = state.versionHistory.find((v) => v.versionId === versionId);
const version = get().getVersionById(versionId);
if (version) {
version.versionName = versionName;
}
})
});
},
updateVersion: (versionId: string, versionName: string, versionDescription: string) => {
set((state) => {
const version = state.versionHistory.find((v) => v.versionId === versionId);
const version = get().getVersionById(versionId);
if (version) {
version.versionName = versionName;
version.versionDescription = versionDescription;
}
})
});
},
getVersionById: (versionId: string) => {
return get().versionHistory.find((v) => {
return v.versionId === versionId
})
}
return v.versionId === versionId;
});
},
}))
);
};
export type VersionStoreType = ReturnType<typeof createVersionStore>;
export type VersionStoreType = ReturnType<typeof createVersionStore>;

View File

@@ -202,7 +202,7 @@ export const firstPersonControls: Controls = {
leftSpeed: -0.1, // Speed of left movement
rightSpeed: 0.1, // Speed of right movement
walkSpeed: 1, // Walk speed
sprintSpeed: 4 // Sprint Speed
sprintSpeed: 4, // Sprint Speed
};
export const thirdPersonControls: ThirdPersonControls = {
@@ -359,7 +359,7 @@ export const roofConfig: RoofConfig = {
export const aisleConfig: AisleConfig = {
width: 0.1, // Width of the aisles
height: 0.01, // Height of the aisles
defaultColor: '#E2AC09', // Default color of the aisles
defaultColor: "#E2AC09", // Default color of the aisles
};
export const zoneConfig: ZoneConfig = {
@@ -384,4 +384,4 @@ export const distanceConfig: DistanceConfig = {
export const undoRedoConfig: undoRedoCount = {
undoRedoCount: 50,
}
};

View File

@@ -1,7 +1,18 @@
import React, { useEffect } from "react";
import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/ui/useModuleStore";
import { usePlayerStore, useToggleStore } from "../../store/ui/useUIToggleStore";
import useVersionHistoryVisibleStore, { useActiveSubTool, useActiveTool, useAddAction, useDfxUpload, useRenameModeStore, useIsComparing, useSelectedComment, useShortcutStore, useToggleView, useToolMode, useViewSceneStore } from "../../store/builder/store";
import {
useActiveSubTool,
useActiveTool,
useDfxUpload,
useRenameModeStore,
useIsComparing,
useSelectedComment,
useShortcutStore,
useToggleView,
useToolMode,
useViewSceneStore,
} from "../../store/builder/store";
import useCameraModeStore, { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
import { detectModifierKeys } from "./detectModifierKeys";
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
@@ -22,7 +33,6 @@ const KeyPressListener: React.FC = () => {
const { setToolMode } = useToolMode();
const { isPlaying, setIsPlaying } = usePlayButtonStore();
const { toggleView, setToggleView } = useToggleView();
const { setAddAction } = useAddAction();
const { setActiveTool } = useActiveTool();
const { clearSelectedZone } = useSelectedZoneStore();
const { showShortcuts, setShowShortcuts } = useShortcutStore();
@@ -33,8 +43,7 @@ const KeyPressListener: React.FC = () => {
const { setViewSceneLabels } = useViewSceneStore();
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
const { selectedFloorAsset, setSelectedWallAsset } = useBuilderStore();
const { setCreateNewVersion } = versionStore();
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
const { setSelectedComment } = useSelectedComment();
const { setDfxUploaded } = useDfxUpload();
const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element?.getAttribute("contenteditable") === "true";
@@ -77,7 +86,6 @@ const KeyPressListener: React.FC = () => {
setToggleThreeD(toggleTo2D);
if (toggleTo2D) {
setSelectedWallAsset(null);
setAddAction(null);
setToggleUI(localStorage.getItem("navBarUiLeft") !== "false", localStorage.getItem("navBarUiRight") !== "false");
} else {
setToggleUI(false, false);
@@ -219,10 +227,6 @@ const KeyPressListener: React.FC = () => {
setShowShortcuts(!showShortcuts);
}
// if (keyCombination === "Ctrl+Shift+P") {
// pref
// }
if (keyCombination === "U") {
setViewSceneLabels((prev) => !prev);
}