Merge remote-tracking branch 'origin/main-dev' into feature/layout-comparison-version
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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={() => {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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)} m²`;
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
10
app/src/modules/builder/functions/getArea.ts
Normal file
10
app/src/modules/builder/functions/getArea.ts
Normal 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);
|
||||
}
|
||||
48
app/src/modules/builder/functions/getCenteroid.tsx
Normal file
48
app/src/modules/builder/functions/getCenteroid.tsx
Normal 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);
|
||||
}
|
||||
@@ -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)} m²`;
|
||||
|
||||
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;
|
||||
@@ -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,
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)} m²`;
|
||||
|
||||
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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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)} m²`;
|
||||
|
||||
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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() {
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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"] },
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user