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 { useNavigate } from "react-router-dom";
|
||||||
import img from "../../assets/image/image.png";
|
import img from "../../assets/image/image.png";
|
||||||
import { getUserData } from "../../functions/getUserData";
|
import { getUserData } from "../../functions/getUserData";
|
||||||
import {
|
import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store";
|
||||||
useLoadingProgress,
|
|
||||||
useProjectName,
|
|
||||||
useSocketStore,
|
|
||||||
} from "../../store/builder/store";
|
|
||||||
import OuterClick from "../../utils/outerClick";
|
import OuterClick from "../../utils/outerClick";
|
||||||
import { KebabIcon } from "../icons/ExportCommonIcons";
|
import { KebabIcon } from "../icons/ExportCommonIcons";
|
||||||
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
||||||
@@ -15,323 +11,275 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
|||||||
// import { updateProject } from "../../services/dashboard/updateProject";
|
// import { updateProject } from "../../services/dashboard/updateProject";
|
||||||
|
|
||||||
interface DashBoardCardProps {
|
interface DashBoardCardProps {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
createdAt?: string;
|
createdAt?: string;
|
||||||
isViewed?: string;
|
isViewed?: string;
|
||||||
createdBy?: { _id: string; userName: string };
|
createdBy?: { _id: string; userName: string };
|
||||||
handleDeleteProject?: (projectId: string) => Promise<void>;
|
handleDeleteProject?: (projectId: string) => Promise<void>;
|
||||||
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
|
handleTrashDeleteProject?: (projectId: string) => Promise<void>;
|
||||||
handleRestoreProject?: (projectId: string) => Promise<void>;
|
handleRestoreProject?: (projectId: string) => Promise<void>;
|
||||||
handleDuplicateWorkspaceProject?: (
|
handleDuplicateWorkspaceProject?: (projectId: string, projectName: string, thumbnail: string, userId?: string) => Promise<void>;
|
||||||
projectId: string,
|
handleDuplicateRecentProject?: (projectId: string, projectName: string, thumbnail: string) => Promise<void>;
|
||||||
projectName: string,
|
active?: "shared" | "trash" | "recent" | string;
|
||||||
thumbnail: string,
|
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
userId?: string
|
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
|
||||||
) => Promise<void>;
|
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<object>>;
|
||||||
handleDuplicateRecentProject?: (
|
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
projectId: string,
|
openKebabProjectId: string | null;
|
||||||
projectName: string,
|
setOpenKebabProjectId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||||
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 =
|
type RelativeTimeFormatUnit = "year" | "month" | "week" | "day" | "hour" | "minute" | "second";
|
||||||
| "year"
|
|
||||||
| "month"
|
|
||||||
| "week"
|
|
||||||
| "day"
|
|
||||||
| "hour"
|
|
||||||
| "minute"
|
|
||||||
| "second";
|
|
||||||
|
|
||||||
const kebabOptionsMap: Record<string, string[]> = {
|
const kebabOptionsMap: Record<string, string[]> = {
|
||||||
default: ["rename", "delete", "duplicate", "open in new tab"],
|
default: ["rename", "delete", "duplicate", "open in new tab"],
|
||||||
trash: ["restore", "delete"],
|
trash: ["restore", "delete"],
|
||||||
shared: ["duplicate", "open in new tab"],
|
shared: ["duplicate", "open in new tab"],
|
||||||
};
|
};
|
||||||
|
|
||||||
const DashboardCard: React.FC<DashBoardCardProps> = ({
|
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,
|
projectName,
|
||||||
navigate,
|
thumbnail,
|
||||||
setLoadingProgress,
|
projectId,
|
||||||
setProjectName,
|
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(() => {
|
const isKebabOpen = openKebabProjectId === projectId;
|
||||||
if (active === "trash") return kebabOptionsMap.trash;
|
const [renameValue, setRenameValue] = useState(projectName);
|
||||||
if (active === "shared" || (createdBy && createdBy._id !== userId)) {
|
const [isRenaming, setIsRenaming] = useState(false);
|
||||||
return kebabOptionsMap.shared;
|
const kebabRef = useRef<HTMLDivElement>(null);
|
||||||
}
|
|
||||||
return kebabOptionsMap.default;
|
|
||||||
}, [active, createdBy, userId]);
|
|
||||||
|
|
||||||
const handleProjectName = useCallback(
|
// Close kebab when clicking outside
|
||||||
async (newName: string) => {
|
OuterClick({
|
||||||
setRenameValue(newName);
|
contextClassName: [`tag-${projectId}`],
|
||||||
if (!projectId) return;
|
setMenuVisible: () => {
|
||||||
|
if (isKebabOpen) setOpenKebabProjectId(null);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
const navigateToProject = useCallback(() => {
|
||||||
const projects = await getAllProjects(userId, organization);
|
if (active === "trash") return;
|
||||||
const projectUuid = projects?.Projects?.find(
|
setLoadingProgress(1);
|
||||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
setProjectName(projectName);
|
||||||
);
|
navigate(`/projects/${projectId}`);
|
||||||
if (!projectUuid) return;
|
}, [active, projectId, projectName, navigate, setLoadingProgress, setProjectName]);
|
||||||
|
|
||||||
const updatePayload = {
|
const getOptions = useCallback(() => {
|
||||||
projectId: projectUuid._id,
|
if (active === "trash") return kebabOptionsMap.trash;
|
||||||
organization,
|
if (active === "shared" || (createdBy && createdBy._id !== userId)) {
|
||||||
userId,
|
return kebabOptionsMap.shared;
|
||||||
projectName: newName,
|
}
|
||||||
|
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) {
|
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||||
projectSocket.emit("v1:project:update", updatePayload);
|
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 {
|
return "just now";
|
||||||
// silent fail
|
}, []);
|
||||||
}
|
|
||||||
},
|
|
||||||
[projectId, userId, organization, projectSocket]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOptionClick = useCallback(
|
const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 });
|
||||||
async (option: string) => {
|
|
||||||
switch (option) {
|
useEffect(() => {
|
||||||
case "delete":
|
if (isKebabOpen && kebabRef.current) {
|
||||||
await (handleDeleteProject?.(projectId) ??
|
const rect = kebabRef.current.getBoundingClientRect();
|
||||||
handleTrashDeleteProject?.(projectId));
|
setKebabPosition({
|
||||||
break;
|
top: rect.bottom + window.scrollY,
|
||||||
case "restore":
|
left: rect.left + window.scrollX - 80,
|
||||||
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,
|
}, [isKebabOpen]);
|
||||||
projectName,
|
|
||||||
thumbnail
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[
|
|
||||||
projectId,
|
|
||||||
projectName,
|
|
||||||
thumbnail,
|
|
||||||
userId,
|
|
||||||
active,
|
|
||||||
handleDeleteProject,
|
|
||||||
handleTrashDeleteProject,
|
|
||||||
handleRestoreProject,
|
|
||||||
handleDuplicateWorkspaceProject,
|
|
||||||
handleDuplicateRecentProject,
|
|
||||||
setProjectName,
|
|
||||||
setProjectDuplicateData,
|
|
||||||
setRecentDuplicateData,
|
|
||||||
setActiveFolder,
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
const getRelativeTime = useCallback((dateString: string): string => {
|
return (
|
||||||
const date = new Date(dateString);
|
<div className="dashboard-card-container" onClick={navigateToProject} title={projectName}>
|
||||||
const now = new Date();
|
<div className="dashboard-card-wrapper">
|
||||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
<div className="preview-container">
|
||||||
|
<img src={thumbnail || img} alt={`${projectName} thumbnail`} />
|
||||||
|
</div>
|
||||||
|
|
||||||
const intervals: Record<RelativeTimeFormatUnit, number> = {
|
<div className="project-details-container" onClick={(e) => e.stopPropagation()}>
|
||||||
year: 31536000,
|
<div className="project-details">
|
||||||
month: 2592000,
|
{isRenaming ? (
|
||||||
week: 604800,
|
<input
|
||||||
day: 86400,
|
value={renameValue}
|
||||||
hour: 3600,
|
onChange={(e) => handleProjectName(e.target.value)}
|
||||||
minute: 60,
|
onBlur={() => {
|
||||||
second: 1,
|
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" });
|
<div className="users-list-container" ref={kebabRef}>
|
||||||
for (const [unit, seconds] of Object.entries(intervals)) {
|
<div className="user-profile">{(createdBy?.userName || userName || "A").charAt(0).toUpperCase()}</div>
|
||||||
const diff = Math.floor(diffInSeconds / seconds);
|
<button
|
||||||
if (diff >= 1) return rtf.format(-diff, unit as RelativeTimeFormatUnit);
|
className="kebab-wrapper"
|
||||||
}
|
onClick={(e) => {
|
||||||
return "just now";
|
e.stopPropagation();
|
||||||
}, []);
|
setOpenKebabProjectId(isKebabOpen ? null : projectId);
|
||||||
|
}}
|
||||||
const [kebabPosition, setKebabPosition] = useState({ top: 0, left: 0 });
|
aria-haspopup="true"
|
||||||
|
aria-expanded={isKebabOpen}
|
||||||
useEffect(() => {
|
aria-label="Project options"
|
||||||
if (isKebabOpen && kebabRef.current) {
|
>
|
||||||
const rect = kebabRef.current.getBoundingClientRect();
|
<KebabIcon />
|
||||||
setKebabPosition({
|
</button>
|
||||||
top: rect.bottom + window.scrollY,
|
</div>
|
||||||
left: rect.left + window.scrollX - 80,
|
</div>
|
||||||
});
|
|
||||||
}
|
|
||||||
}, [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>
|
</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 &&
|
{isKebabOpen &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div
|
<div className={`kebab-options-wrapper tag-${projectId}`} style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}>
|
||||||
className={`kebab-options-wrapper tag-${projectId}`}
|
{getOptions().map((option) => (
|
||||||
style={{ position: "fixed", zIndex: 9999, ...kebabPosition }}
|
<button
|
||||||
>
|
key={option}
|
||||||
{getOptions().map((option) => (
|
className="option"
|
||||||
<button
|
onClick={(e) => {
|
||||||
key={option}
|
e.stopPropagation();
|
||||||
className="option"
|
handleOptionClick(option);
|
||||||
onClick={(e) => {
|
}}
|
||||||
e.stopPropagation();
|
>
|
||||||
handleOptionClick(option);
|
{option}
|
||||||
}}
|
</button>
|
||||||
>
|
))}
|
||||||
{option}
|
</div>,
|
||||||
</button>
|
document.body
|
||||||
))}
|
)}
|
||||||
</div>,
|
</div>
|
||||||
document.body
|
);
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DashboardCard;
|
export default DashboardCard;
|
||||||
|
|||||||
@@ -1,13 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import {
|
import { DocumentationIcon, HelpIcon, HomeIcon, LogoutIcon, NotificationIcon, ProjectsIcon, TutorialsIcon } from "../icons/DashboardIcon";
|
||||||
DocumentationIcon,
|
|
||||||
HelpIcon,
|
|
||||||
HomeIcon,
|
|
||||||
LogoutIcon,
|
|
||||||
NotificationIcon,
|
|
||||||
ProjectsIcon,
|
|
||||||
TutorialsIcon,
|
|
||||||
} from "../icons/DashboardIcon";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import darkThemeImage from "../../assets/image/darkThemeProject.png";
|
import darkThemeImage from "../../assets/image/darkThemeProject.png";
|
||||||
import lightThemeImage from "../../assets/image/lightThemeProject.png";
|
import lightThemeImage from "../../assets/image/lightThemeProject.png";
|
||||||
@@ -54,7 +46,6 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
|||||||
projectUuid: projectId,
|
projectUuid: projectId,
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('addProject: ', addProject);
|
|
||||||
projectSocket.emit("v1:project:add", addProject);
|
projectSocket.emit("v1:project:add", addProject);
|
||||||
} else {
|
} else {
|
||||||
// API
|
// API
|
||||||
@@ -72,11 +63,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
|||||||
<div className="side-pannel-header">
|
<div className="side-pannel-header">
|
||||||
<div className="user-container">
|
<div className="user-container">
|
||||||
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
|
<div className="user-profile">{userName?.charAt(0).toUpperCase()}</div>
|
||||||
<div className="user-name">
|
<div className="user-name">{userName ? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase() : "Anonymous"}</div>
|
||||||
{userName
|
|
||||||
? userName.charAt(0).toUpperCase() + userName.slice(1).toLowerCase()
|
|
||||||
: "Anonymous"}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="notifications-container">
|
<div className="notifications-container">
|
||||||
<NotificationIcon />
|
<NotificationIcon />
|
||||||
@@ -87,26 +74,15 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="side-bar-content-container">
|
<div className="side-bar-content-container">
|
||||||
<div className="side-bar-options-container">
|
<div className="side-bar-options-container">
|
||||||
<button
|
<button className={activeTab === "Home" ? "option-list active" : "option-list"} onClick={() => setActiveTab("Home")}>
|
||||||
className={activeTab === "Home" ? "option-list active" : "option-list"}
|
|
||||||
onClick={() => setActiveTab("Home")}
|
|
||||||
>
|
|
||||||
<HomeIcon />
|
<HomeIcon />
|
||||||
Home
|
Home
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className={activeTab === "Projects" ? "option-list active" : "option-list"} title="Projects" onClick={() => setActiveTab("Projects")}>
|
||||||
className={activeTab === "Projects" ? "option-list active" : "option-list"}
|
|
||||||
title="Projects"
|
|
||||||
onClick={() => setActiveTab("Projects")}
|
|
||||||
>
|
|
||||||
<ProjectsIcon />
|
<ProjectsIcon />
|
||||||
Projects
|
Projects
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button className={activeTab === "Trash" ? "option-list active" : "option-list"} title="Trash" onClick={() => setActiveTab("Trash")}>
|
||||||
className={activeTab === "Trash" ? "option-list active" : "option-list"}
|
|
||||||
title="Trash"
|
|
||||||
onClick={() => setActiveTab("Trash")}
|
|
||||||
>
|
|
||||||
<TrashIcon />
|
<TrashIcon />
|
||||||
Trash
|
Trash
|
||||||
</button>
|
</button>
|
||||||
@@ -123,9 +99,7 @@ const SidePannel: React.FC<SidePannelProps> = ({ setActiveTab, activeTab }) => {
|
|||||||
Tutorials
|
Tutorials
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className={
|
className={activeTab === "Documentation" ? "option-list active" : "option-list"}
|
||||||
activeTab === "Documentation" ? "option-list active" : "option-list"
|
|
||||||
}
|
|
||||||
title="coming soon"
|
title="coming soon"
|
||||||
disabled
|
disabled
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useLogger } from "../ui/log/LoggerContext";
|
|||||||
import { GetLogIcon } from "./getLogIcons";
|
import { GetLogIcon } from "./getLogIcons";
|
||||||
import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons";
|
import { CurserLeftIcon, CurserMiddleIcon, CurserRightIcon } from "../icons/LogIcons";
|
||||||
import ShortcutHelper from "./shortcutHelper";
|
import ShortcutHelper from "./shortcutHelper";
|
||||||
import useVersionHistoryVisibleStore, { useShortcutStore } from "../../store/builder/store";
|
import { useShortcutStore } from "../../store/builder/store";
|
||||||
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
|
||||||
import useModuleStore, { useSubModuleStore } from "../../store/ui/useModuleStore";
|
import useModuleStore, { useSubModuleStore } from "../../store/ui/useModuleStore";
|
||||||
import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper";
|
import { mouseActionHelper } from "../../utils/mouseUtils/mouseHelper";
|
||||||
@@ -12,123 +12,118 @@ import { useMouseNoteStore } from "../../store/ui/useUIToggleStore";
|
|||||||
import { useSceneContext } from "../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../modules/scene/sceneContext";
|
||||||
|
|
||||||
const Footer: React.FC = () => {
|
const Footer: React.FC = () => {
|
||||||
const { logs, setIsLogListVisible } = useLogger();
|
const { logs, setIsLogListVisible } = useLogger();
|
||||||
const lastLog = logs[logs.length - 1] || null;
|
const lastLog = logs[logs.length - 1] || null;
|
||||||
|
|
||||||
const { setActiveModule } = useModuleStore();
|
const { setActiveModule } = useModuleStore();
|
||||||
const { setSubModule } = useSubModuleStore();
|
const { setSubModule } = useSubModuleStore();
|
||||||
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
const { isPlaying } = usePlayButtonStore();
|
||||||
const { isPlaying } = usePlayButtonStore();
|
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
const { versionStore } = useSceneContext();
|
||||||
const { versionStore } = useSceneContext();
|
const { selectedVersion, setVersionHistoryVisible } = versionStore();
|
||||||
const { selectedVersion } = versionStore();
|
|
||||||
|
|
||||||
const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore();
|
const { Leftnote, Middlenote, Rightnote } = useMouseNoteStore();
|
||||||
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
|
const [isOnline, setIsOnline] = useState<boolean>(navigator.onLine);
|
||||||
|
|
||||||
// -------------------- Online/Offline Handlers --------------------
|
// -------------------- Online/Offline Handlers --------------------
|
||||||
const handleOnline = useCallback(() => {
|
const handleOnline = useCallback(() => {
|
||||||
echo.success("You are back Online");
|
echo.success("You are back Online");
|
||||||
setIsOnline(true);
|
setIsOnline(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOffline = useCallback(() => {
|
const handleOffline = useCallback(() => {
|
||||||
echo.warn("Changes made now might not be saved");
|
echo.warn("Changes made now might not be saved");
|
||||||
echo.error("You are now Offline.");
|
echo.error("You are now Offline.");
|
||||||
setIsOnline(false);
|
setIsOnline(false);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
window.addEventListener("online", handleOnline);
|
window.addEventListener("online", handleOnline);
|
||||||
window.addEventListener("offline", handleOffline);
|
window.addEventListener("offline", handleOffline);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("online", handleOnline);
|
window.removeEventListener("online", handleOnline);
|
||||||
window.removeEventListener("offline", handleOffline);
|
window.removeEventListener("offline", handleOffline);
|
||||||
};
|
};
|
||||||
}, [handleOnline, handleOffline]);
|
}, [handleOnline, handleOffline]);
|
||||||
|
|
||||||
// -------------------- Mouse Buttons --------------------
|
// -------------------- Mouse Buttons --------------------
|
||||||
const mouseButtons = useMemo(
|
const mouseButtons = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{ icon: <CurserLeftIcon />, label: Leftnote || "Pan", mouse: "left" },
|
{ icon: <CurserLeftIcon />, label: Leftnote || "Pan", mouse: "left" },
|
||||||
{ icon: <CurserMiddleIcon />, label: Middlenote || "Scroll Zoom", mouse: "middle" },
|
{ icon: <CurserMiddleIcon />, label: Middlenote || "Scroll Zoom", mouse: "middle" },
|
||||||
{ icon: <CurserRightIcon />, label: Rightnote || "Orbit / Cancel action", mouse: "right" },
|
{ icon: <CurserRightIcon />, label: Rightnote || "Orbit / Cancel action", mouse: "right" },
|
||||||
],
|
],
|
||||||
[Leftnote, Middlenote, Rightnote]
|
[Leftnote, Middlenote, Rightnote]
|
||||||
);
|
);
|
||||||
|
|
||||||
// -------------------- Mouse Helper --------------------
|
// -------------------- Mouse Helper --------------------
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const cleanup = mouseActionHelper();
|
const cleanup = mouseActionHelper();
|
||||||
return () => cleanup();
|
return () => cleanup();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="footer-container">
|
<div className="footer-container">
|
||||||
<div className="footer-wrapper">
|
<div className="footer-wrapper">
|
||||||
{/* Mouse Button Info */}
|
{/* Mouse Button Info */}
|
||||||
<div className="selection-wrapper">
|
<div className="selection-wrapper">
|
||||||
{mouseButtons.map(({ icon, label, mouse }) => (
|
{mouseButtons.map(({ icon, label, mouse }) => (
|
||||||
<div className="selector-wrapper" key={mouse}>
|
<div className="selector-wrapper" key={mouse}>
|
||||||
<div className="icon">{icon}</div>
|
<div className="icon">{icon}</div>
|
||||||
<div className="selector">{label}</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>
|
</div>
|
||||||
))}
|
|
||||||
|
{/* Shortcut Helper */}
|
||||||
|
{!isPlaying && showShortcuts && (
|
||||||
|
<div className="shortcut-helper-overlay visible">
|
||||||
|
<ShortcutHelper setShowShortcuts={setShowShortcuts} />
|
||||||
|
</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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Footer;
|
export default Footer;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore";
|
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 { useToggleStore } from "../../../store/ui/useUIToggleStore";
|
||||||
import Visualization from "./visualization/Visualization";
|
import Visualization from "./visualization/Visualization";
|
||||||
import Analysis from "./analysis/Analysis";
|
import Analysis from "./analysis/Analysis";
|
||||||
import Simulations from "./simulation/Simulations";
|
import Simulations from "./simulation/Simulations";
|
||||||
import useVersionHistoryVisibleStore, { useIsComparing, useToolMode } from "../../../store/builder/store";
|
import { useIsComparing, useToolMode } from "../../../store/builder/store";
|
||||||
import { useSelectedEventData, useSelectedEventSphere, } from "../../../store/simulation/useSimulationStore";
|
import { useSelectedEventData, useSelectedEventSphere } from "../../../store/simulation/useSimulationStore";
|
||||||
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
|
import { useBuilderStore } from "../../../store/builder/useBuilderStore";
|
||||||
import GlobalProperties from "./properties/GlobalProperties";
|
import GlobalProperties from "./properties/GlobalProperties";
|
||||||
import AssetProperties from "./properties/AssetProperties";
|
import AssetProperties from "./properties/AssetProperties";
|
||||||
@@ -22,6 +22,7 @@ import SelectedFloorProperties from "./properties/SelectedFloorProperties";
|
|||||||
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
|
import SelectedDecalProperties from "./properties/SelectedDecalProperties";
|
||||||
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
|
import SelectedAisleProperties from "./properties/SelectedAisleProperties";
|
||||||
import ResourceManagement from "./resourceManagement/ResourceManagement";
|
import ResourceManagement from "./resourceManagement/ResourceManagement";
|
||||||
|
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||||
|
|
||||||
type DisplayComponent =
|
type DisplayComponent =
|
||||||
| "versionHistory"
|
| "versionHistory"
|
||||||
@@ -51,7 +52,8 @@ const SideBarRight: React.FC = () => {
|
|||||||
const { selectedWall, selectedFloor, selectedAisle, selectedFloorAsset } = useBuilderStore();
|
const { selectedWall, selectedFloor, selectedAisle, selectedFloorAsset } = useBuilderStore();
|
||||||
const { selectedEventData } = useSelectedEventData();
|
const { selectedEventData } = useSelectedEventData();
|
||||||
const { selectedEventSphere } = useSelectedEventSphere();
|
const { selectedEventSphere } = useSelectedEventSphere();
|
||||||
const { viewVersionHistory, setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
const { versionStore } = useSceneContext();
|
||||||
|
const { viewVersionHistory, setVersionHistoryVisible } = versionStore();
|
||||||
const { isComparing } = useIsComparing();
|
const { isComparing } = useIsComparing();
|
||||||
|
|
||||||
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
|
const [displayComponent, setDisplayComponent] = useState<DisplayComponent>("none");
|
||||||
@@ -207,8 +209,7 @@ const SideBarRight: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
id="sidebar-action-list-properties"
|
id="sidebar-action-list-properties"
|
||||||
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""
|
className={`sidebar-action-list ${subModule === "properties" ? "active" : ""}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSubModule("properties");
|
setSubModule("properties");
|
||||||
setVersionHistoryVisible(false);
|
setVersionHistoryVisible(false);
|
||||||
@@ -224,8 +225,7 @@ const SideBarRight: React.FC = () => {
|
|||||||
<>
|
<>
|
||||||
<button
|
<button
|
||||||
id="sidebar-action-list-simulation"
|
id="sidebar-action-list-simulation"
|
||||||
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""
|
className={`sidebar-action-list ${subModule === "simulations" ? "active" : ""}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSubModule("simulations");
|
setSubModule("simulations");
|
||||||
setVersionHistoryVisible(false);
|
setVersionHistoryVisible(false);
|
||||||
@@ -236,8 +236,7 @@ const SideBarRight: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="sidebar-action-list-mechanics"
|
id="sidebar-action-list-mechanics"
|
||||||
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""
|
className={`sidebar-action-list ${subModule === "mechanics" ? "active" : ""}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSubModule("mechanics");
|
setSubModule("mechanics");
|
||||||
setVersionHistoryVisible(false);
|
setVersionHistoryVisible(false);
|
||||||
@@ -248,8 +247,7 @@ const SideBarRight: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
id="sidebar-action-list-analysis"
|
id="sidebar-action-list-analysis"
|
||||||
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""
|
className={`sidebar-action-list ${subModule === "analysis" ? "active" : ""}`}
|
||||||
}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSubModule("analysis");
|
setSubModule("analysis");
|
||||||
setVersionHistoryVisible(false);
|
setVersionHistoryVisible(false);
|
||||||
@@ -261,23 +259,19 @@ const SideBarRight: React.FC = () => {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(activeModule === "builder" ||
|
{(activeModule === "builder" || activeModule === "simulation") && (
|
||||||
activeModule === "simulation") && (
|
<button
|
||||||
<button
|
id="sidebar-action-list-properties"
|
||||||
id="sidebar-action-list-properties"
|
className={`sidebar-action-list ${subModule === "resourceManagement" ? "active" : ""}`}
|
||||||
className={`sidebar-action-list ${subModule === "resourceManagement" ? "active" : ""
|
onClick={() => {
|
||||||
}`}
|
setSubModule("resourceManagement");
|
||||||
onClick={() => {
|
setVersionHistoryVisible(false);
|
||||||
setSubModule("resourceManagement");
|
}}
|
||||||
setVersionHistoryVisible(false);
|
>
|
||||||
}}
|
<div className="tooltip">Resource Management</div>
|
||||||
>
|
<FilePackageIcon isActive={subModule === "resourceManagement"} />
|
||||||
<div className="tooltip">Resource Management</div>
|
</button>
|
||||||
<FilePackageIcon
|
)}
|
||||||
isActive={subModule === "resourceManagement"}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { useParams } from "react-router-dom";
|
|||||||
import { AddIcon, ArrowIcon, CloseIcon, KebabIcon, LocationIcon } from "../../../icons/ExportCommonIcons";
|
import { AddIcon, ArrowIcon, CloseIcon, KebabIcon, LocationIcon } from "../../../icons/ExportCommonIcons";
|
||||||
import { useSubModuleStore } from "../../../../store/ui/useModuleStore";
|
import { useSubModuleStore } from "../../../../store/ui/useModuleStore";
|
||||||
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||||
import useVersionHistoryVisibleStore from "../../../../store/builder/store";
|
|
||||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||||
|
|
||||||
import { getVersionDataApi } from "../../../../services/factoryBuilder/versionControl/getVersionDataApi";
|
import { getVersionDataApi } from "../../../../services/factoryBuilder/versionControl/getVersionDataApi";
|
||||||
@@ -10,8 +9,7 @@ import { getVersionDataApi } from "../../../../services/factoryBuilder/versionCo
|
|||||||
const VersionHistory = () => {
|
const VersionHistory = () => {
|
||||||
const { setSubModule } = useSubModuleStore();
|
const { setSubModule } = useSubModuleStore();
|
||||||
const { versionStore } = useSceneContext();
|
const { versionStore } = useSceneContext();
|
||||||
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
const { versionHistory, setCreateNewVersion, selectedVersion, setSelectedVersion, setVersionHistoryVisible } = versionStore();
|
||||||
const { versionHistory, setCreateNewVersion, selectedVersion, setSelectedVersion } = versionStore();
|
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
|
|
||||||
const addNewVersion = () => {
|
const addNewVersion = () => {
|
||||||
@@ -21,16 +19,16 @@ const VersionHistory = () => {
|
|||||||
const handleSelectVersion = (version: Version) => {
|
const handleSelectVersion = (version: Version) => {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
|
|
||||||
getVersionDataApi(projectId, version.versionId).then((versionData) => {
|
getVersionDataApi(projectId, version.versionId)
|
||||||
setSelectedVersion(version);
|
.then((versionData) => {
|
||||||
}).catch((err) => {
|
setSelectedVersion(version);
|
||||||
echo.error(err);
|
})
|
||||||
})
|
.catch((err) => {
|
||||||
|
echo.error(err);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleVersionNameChange = (newName: string, versionId: string) => {
|
const handleVersionNameChange = (newName: string, versionId: string) => {};
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="version-history-container">
|
<div className="version-history-container">
|
||||||
@@ -38,11 +36,7 @@ const VersionHistory = () => {
|
|||||||
<div className="version-history-header">
|
<div className="version-history-header">
|
||||||
<div className="version-history-title">Version History</div>
|
<div className="version-history-title">Version History</div>
|
||||||
<div className="version-history-icons">
|
<div className="version-history-icons">
|
||||||
<button
|
<button id="add-version" className="icon add-icon" onClick={addNewVersion}>
|
||||||
id="add-version"
|
|
||||||
className="icon add-icon"
|
|
||||||
onClick={addNewVersion}
|
|
||||||
>
|
|
||||||
<AddIcon />
|
<AddIcon />
|
||||||
</button>
|
</button>
|
||||||
<div id="version-kebab" className="icon kebab-icon">
|
<div id="version-kebab" className="icon kebab-icon">
|
||||||
@@ -64,9 +58,7 @@ const VersionHistory = () => {
|
|||||||
{/* Shortcut Info */}
|
{/* Shortcut Info */}
|
||||||
<div className="version-history-shortcut-info">
|
<div className="version-history-shortcut-info">
|
||||||
<div className="info-icon">i</div>
|
<div className="info-icon">i</div>
|
||||||
<div className="shortcut-text">
|
<div className="shortcut-text">Press Ctrl + Alt + S to add to version history while editing</div>
|
||||||
Press Ctrl + Alt + S to add to version history while editing
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Current Version Display */}
|
{/* Current Version Display */}
|
||||||
@@ -76,12 +68,8 @@ const VersionHistory = () => {
|
|||||||
<LocationIcon />
|
<LocationIcon />
|
||||||
</div>
|
</div>
|
||||||
<div className="location-details">
|
<div className="location-details">
|
||||||
<div className="current-version">
|
<div className="current-version">Current Version ({selectedVersion.version})</div>
|
||||||
Current Version ({selectedVersion.version})
|
<div className="saved-history-count">{versionHistory.length} Saved History</div>
|
||||||
</div>
|
|
||||||
<div className="saved-history-count">
|
|
||||||
{versionHistory.length} Saved History
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -93,16 +81,8 @@ const VersionHistory = () => {
|
|||||||
) : (
|
) : (
|
||||||
versionHistory.map((version) => {
|
versionHistory.map((version) => {
|
||||||
const key = `version-${version.versionId}`;
|
const key = `version-${version.versionId}`;
|
||||||
return (
|
return <VersionHistoryItem key={key} version={version} onSelect={handleSelectVersion} onRename={handleVersionNameChange} />;
|
||||||
<VersionHistoryItem
|
|
||||||
key={key}
|
|
||||||
version={version}
|
|
||||||
onSelect={handleSelectVersion}
|
|
||||||
onRename={handleVersionNameChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -119,22 +99,14 @@ type VersionHistoryItemProps = {
|
|||||||
|
|
||||||
const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ version, onSelect, onRename }) => {
|
const VersionHistoryItem: React.FC<VersionHistoryItemProps> = ({ version, onSelect, onRename }) => {
|
||||||
return (
|
return (
|
||||||
<button
|
<button className="saved-version">
|
||||||
className="saved-version"
|
<div className="version-name" onClick={() => onSelect(version)}>
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="version-name"
|
|
||||||
onClick={() => onSelect(version)}
|
|
||||||
>
|
|
||||||
v {version.version}
|
v {version.version}
|
||||||
</div>
|
</div>
|
||||||
<div className="version-details">
|
<div className="version-details">
|
||||||
<div className="details">
|
<div className="details">
|
||||||
<span className="timestamp">
|
<span className="timestamp">
|
||||||
<RenameInput
|
<RenameInput value={version.versionName ? version.versionName : version.timeStamp} onRename={(newName) => onRename(newName, version.versionId)} />
|
||||||
value={version.versionName ? version.versionName : version.timeStamp}
|
|
||||||
onRename={(newName) => onRename(newName, version.versionId)}
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
<span className="saved-by">
|
<span className="saved-by">
|
||||||
<div className="user-profile">{version.createdBy[0]}</div>
|
<div className="user-profile">{version.createdBy[0]}</div>
|
||||||
|
|||||||
@@ -1,104 +1,84 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import useModuleStore from "../../store/ui/useModuleStore";
|
import useModuleStore from "../../store/ui/useModuleStore";
|
||||||
import {
|
import { BuilderIcon, CartIcon, SimulationIcon, VisualizationIcon } from "../icons/ExportModuleIcons";
|
||||||
BuilderIcon,
|
import { useToggleStore } from "../../store/ui/useUIToggleStore";
|
||||||
CartIcon,
|
import { useSceneContext } from "../../modules/scene/sceneContext";
|
||||||
SimulationIcon,
|
|
||||||
VisualizationIcon,
|
|
||||||
} from "../icons/ExportModuleIcons";
|
|
||||||
import {useToggleStore} from "../../store/ui/useUIToggleStore";
|
|
||||||
import useVersionStore from "../../store/builder/store";
|
|
||||||
|
|
||||||
const ModuleToggle: React.FC = () => {
|
const ModuleToggle: React.FC = () => {
|
||||||
const { activeModule, setActiveModule } = useModuleStore();
|
const { activeModule, setActiveModule } = useModuleStore();
|
||||||
const { setToggleUI } = useToggleStore();
|
const { setToggleUI } = useToggleStore();
|
||||||
const { setVersionHistoryVisible } = useVersionStore();
|
const { versionStore } = useSceneContext();
|
||||||
|
const { setVersionHistoryVisible } = versionStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="module-toggle-container">
|
<div className="module-toggle-container">
|
||||||
<button
|
<button
|
||||||
id={"builder"}
|
id={"builder"}
|
||||||
className={`module-list ${activeModule === "builder" ? "active" : ""}`}
|
className={`module-list ${activeModule === "builder" ? "active" : ""}`}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveModule("builder");
|
setActiveModule("builder");
|
||||||
setVersionHistoryVisible(false);
|
setVersionHistoryVisible(false);
|
||||||
setToggleUI(
|
setToggleUI(
|
||||||
localStorage.getItem("navBarUiLeft")
|
localStorage.getItem("navBarUiLeft") ? localStorage.getItem("navBarUiLeft") === "true" : true,
|
||||||
? localStorage.getItem("navBarUiLeft") === "true"
|
localStorage.getItem("navBarUiRight") ? localStorage.getItem("navBarUiRight") === "true" : true
|
||||||
: true,
|
);
|
||||||
localStorage.getItem("navBarUiRight")
|
}}
|
||||||
? localStorage.getItem("navBarUiRight") === "true"
|
>
|
||||||
: true
|
<div className="icon">
|
||||||
);
|
<BuilderIcon isActive={activeModule === "builder"} />
|
||||||
}}
|
</div>
|
||||||
>
|
<div className="module">Builder</div>
|
||||||
<div className="icon">
|
</button>
|
||||||
<BuilderIcon isActive={activeModule === "builder"} />
|
<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>
|
||||||
<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;
|
export default ModuleToggle;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { handleSaveTemplate } from "../../modules/visualization/functions/handle
|
|||||||
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
|
import { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
|
||||||
import useTemplateStore from "../../store/ui/useTemplateStore";
|
import useTemplateStore from "../../store/ui/useTemplateStore";
|
||||||
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
|
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 { useToggleStore } from "../../store/ui/useUIToggleStore";
|
||||||
import { use3DWidget, useFloatingWidget } from "../../store/visualization/useDroppedObjectsStore";
|
import { use3DWidget, useFloatingWidget } from "../../store/visualization/useDroppedObjectsStore";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
@@ -34,7 +34,7 @@ const Tools: React.FC = () => {
|
|||||||
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
|
const { toggleThreeD, setToggleThreeD } = useThreeDStore();
|
||||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||||
const { showShortcuts } = useShortcutStore();
|
const { showShortcuts } = useShortcutStore();
|
||||||
const { activeTool, setActiveTool, setToolMode, setAddAction } = useStoreHooks();
|
const { activeTool, setActiveTool, setToolMode } = useStoreHooks();
|
||||||
const { setSelectedWallAsset } = useBuilderStore();
|
const { setSelectedWallAsset } = useBuilderStore();
|
||||||
const { setActiveSubTool, activeSubTool } = useActiveSubTool();
|
const { setActiveSubTool, activeSubTool } = useActiveSubTool();
|
||||||
const { setToggleUI } = useToggleStore();
|
const { setToggleUI } = useToggleStore();
|
||||||
@@ -86,7 +86,6 @@ const Tools: React.FC = () => {
|
|||||||
|
|
||||||
const resetTools = () => {
|
const resetTools = () => {
|
||||||
setToolMode(null);
|
setToolMode(null);
|
||||||
setAddAction(null);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateToolBehavior = (tool: string, is2D: boolean) => {
|
const updateToolBehavior = (tool: string, is2D: boolean) => {
|
||||||
@@ -116,7 +115,7 @@ const Tools: React.FC = () => {
|
|||||||
setToolMode("MeasurementScale");
|
setToolMode("MeasurementScale");
|
||||||
break;
|
break;
|
||||||
case "Add pillar":
|
case "Add pillar":
|
||||||
if (!is2D) setAddAction("Pillar");
|
if (!is2D) setToolMode("Pillar");
|
||||||
break;
|
break;
|
||||||
case "delete":
|
case "delete":
|
||||||
is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete");
|
is2D ? setToolMode("2D-Delete") : setToolMode("3D-Delete");
|
||||||
@@ -131,7 +130,6 @@ const Tools: React.FC = () => {
|
|||||||
setToggleUI(toggleTo2D, toggleTo2D);
|
setToggleUI(toggleTo2D, toggleTo2D);
|
||||||
if (toggleTo2D) {
|
if (toggleTo2D) {
|
||||||
setSelectedWallAsset(null);
|
setSelectedWallAsset(null);
|
||||||
setAddAction(null);
|
|
||||||
}
|
}
|
||||||
setActiveTool("cursor");
|
setActiveTool("cursor");
|
||||||
setActiveSubTool("cursor");
|
setActiveSubTool("cursor");
|
||||||
@@ -242,7 +240,19 @@ const Tools: React.FC = () => {
|
|||||||
<div className={`tools-container ${showShortcuts ? "visible" : ""}`}>
|
<div className={`tools-container ${showShortcuts ? "visible" : ""}`}>
|
||||||
<div className="activeDropicon">
|
<div className="activeDropicon">
|
||||||
{/* Tool Picker (cursor, delete, etc.) */}
|
{/* 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 */}
|
{/* Dropdown Menu */}
|
||||||
{activeModule !== "visualization" && (
|
{activeModule !== "visualization" && (
|
||||||
<button id="drop-down-button" title="drop-down" className="drop-down-option-button" ref={dropdownRef} onClick={() => setOpenDrop(!openDrop)}>
|
<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 {
|
return {
|
||||||
...useActiveTool(),
|
...useActiveTool(),
|
||||||
...useToolMode(),
|
...useToolMode(),
|
||||||
...useAddAction(),
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
||||||
import { toggleTheme } from "../../../utils/theme";
|
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 useModuleStore, { useSubModuleStore } from "../../../store/ui/useModuleStore";
|
||||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||||
|
|
||||||
@@ -25,8 +25,7 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
|||||||
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({});
|
const [selectedItems, setSelectedItems] = useState<Record<string, boolean>>({});
|
||||||
|
|
||||||
const { versionStore } = useSceneContext();
|
const { versionStore } = useSceneContext();
|
||||||
const { setCreateNewVersion } = versionStore();
|
const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
|
||||||
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
|
||||||
const { setActiveModule } = useModuleStore();
|
const { setActiveModule } = useModuleStore();
|
||||||
const { setSubModule } = useSubModuleStore();
|
const { setSubModule } = useSubModuleStore();
|
||||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||||
@@ -79,7 +78,16 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
|||||||
{ label: "Import" },
|
{ label: "Import" },
|
||||||
{ label: "Close File" },
|
{ 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: [
|
View: [
|
||||||
{ label: "Grid" },
|
{ label: "Grid" },
|
||||||
{
|
{
|
||||||
@@ -172,7 +180,13 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
|||||||
<div className="dropdown-menu">
|
<div className="dropdown-menu">
|
||||||
{items.map((item) =>
|
{items.map((item) =>
|
||||||
item.submenu ? (
|
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">
|
<div className="menu-item">
|
||||||
<span>{item.label}</span>
|
<span>{item.label}</span>
|
||||||
<span className="dropdown-icon">
|
<span className="dropdown-icon">
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ function AisleCreator() {
|
|||||||
const { activeLayer } = useActiveLayer();
|
const { activeLayer } = useActiveLayer();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext();
|
const { aisleStore, undoRedo2DStore, versionStore } = useSceneContext();
|
||||||
const { addAisle, getAislePointById } = aisleStore();
|
const { addAisle, getAislePointById, getConnectedPoints } = aisleStore();
|
||||||
const { push2D } = undoRedo2DStore();
|
const { push2D } = undoRedo2DStore();
|
||||||
const drag = useRef(false);
|
const drag = useRef(false);
|
||||||
const isLeftMouseDown = useRef(false);
|
const isLeftMouseDown = useRef(false);
|
||||||
@@ -76,7 +76,9 @@ function AisleCreator() {
|
|||||||
newPoint.layer = snappedPoint.layer;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +196,32 @@ function AisleCreator() {
|
|||||||
canvasElement.removeEventListener("click", onMouseClick);
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
canvasElement.removeEventListener("contextmenu", onContext);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as CONSTANTS from "../../../../types/world/worldConstants";
|
|||||||
import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator";
|
import PointsCalculator from "../../../simulation/events/points/functions/pointsCalculator";
|
||||||
|
|
||||||
import { getUserData } from "../../../../functions/getUserData";
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
import { setAssetsApi } from '../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi';
|
import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||||
|
|
||||||
async function addAssetModel(
|
async function addAssetModel(
|
||||||
scene: THREE.Scene,
|
scene: THREE.Scene,
|
||||||
@@ -63,13 +63,12 @@ async function addAssetModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (intersectPoint) {
|
if (intersectPoint) {
|
||||||
|
|
||||||
if (intersectPoint.y < 0) {
|
if (intersectPoint.y < 0) {
|
||||||
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
intersectPoint = new THREE.Vector3(intersectPoint.x, 0, intersectPoint.z);
|
||||||
}
|
}
|
||||||
const cachedModel = THREE.Cache.get(selectedItem.id);
|
const cachedModel = THREE.Cache.get(selectedItem.id);
|
||||||
if (cachedModel) {
|
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;
|
return;
|
||||||
} else {
|
} else {
|
||||||
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
const cachedModelBlob = await retrieveGLTF(selectedItem.id);
|
||||||
@@ -79,21 +78,19 @@ async function addAssetModel(
|
|||||||
URL.revokeObjectURL(blobUrl);
|
URL.revokeObjectURL(blobUrl);
|
||||||
THREE.Cache.remove(blobUrl);
|
THREE.Cache.remove(blobUrl);
|
||||||
THREE.Cache.add(selectedItem.id, gltf);
|
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 {
|
} else {
|
||||||
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`,
|
loader.load(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`, async (gltf) => {
|
||||||
async (gltf) => {
|
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
||||||
const modelBlob = await fetch(`${url_Backend_dwinzo}/api/v2/AssetFile/${selectedItem.id}`).then((res) => res.blob());
|
await storeGLTF(selectedItem.id, modelBlob);
|
||||||
await storeGLTF(selectedItem.id, modelBlob);
|
THREE.Cache.add(selectedItem.id, gltf);
|
||||||
THREE.Cache.add(selectedItem.id, gltf);
|
await handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || "", projectId, userId);
|
||||||
await handleModelLoad(gltf, intersectPoint!, selectedItem, addEvent, addAsset, socket, selectedVersion?.versionId || '', projectId, userId);
|
});
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
echo.error("Failed to add asset");
|
echo.error("Failed to add asset");
|
||||||
} finally {
|
} finally {
|
||||||
setSelectedItem({});
|
setSelectedItem({});
|
||||||
@@ -118,7 +115,7 @@ async function handleModelLoad(
|
|||||||
modelId: selectedItem.id,
|
modelId: selectedItem.id,
|
||||||
modelUuid: model.uuid,
|
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.scale.set(...CONSTANTS.assetConfig.defaultScaleAfterGsap);
|
||||||
|
|
||||||
model.traverse((child: any) => {
|
model.traverse((child: any) => {
|
||||||
@@ -132,7 +129,7 @@ async function handleModelLoad(
|
|||||||
modelUuid: model.uuid,
|
modelUuid: model.uuid,
|
||||||
modelName: selectedItem.name,
|
modelName: selectedItem.name,
|
||||||
assetId: selectedItem.id,
|
assetId: selectedItem.id,
|
||||||
position: [intersectPoint!.x, intersectPoint!.y, intersectPoint!.z],
|
position: [intersectPoint.x, intersectPoint.y, intersectPoint.z],
|
||||||
rotation: [0, 0, 0],
|
rotation: [0, 0, 0],
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
@@ -141,13 +138,9 @@ async function handleModelLoad(
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (selectedItem.type) {
|
if (selectedItem.type) {
|
||||||
const data = PointsCalculator(
|
const data = PointsCalculator(selectedItem.type, gltf.scene.clone(), new THREE.Vector3(...model.rotation));
|
||||||
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 };
|
const eventData: any = { type: selectedItem.type, subType: selectedItem.subType };
|
||||||
|
|
||||||
@@ -159,7 +152,7 @@ async function handleModelLoad(
|
|||||||
rotation: newFloorItem.rotation,
|
rotation: newFloorItem.rotation,
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: "transfer",
|
type: "transfer",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
speed: 1,
|
speed: 1,
|
||||||
points: data.points.map((point: THREE.Vector3, index: number) => {
|
points: data.points.map((point: THREE.Vector3, index: number) => {
|
||||||
const triggers: TriggerSchema[] = [];
|
const triggers: TriggerSchema[] = [];
|
||||||
@@ -228,7 +221,7 @@ async function handleModelLoad(
|
|||||||
rotation: newFloorItem.rotation,
|
rotation: newFloorItem.rotation,
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: "vehicle",
|
type: "vehicle",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
speed: 1,
|
speed: 1,
|
||||||
point: {
|
point: {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
@@ -266,7 +259,7 @@ async function handleModelLoad(
|
|||||||
rotation: newFloorItem.rotation,
|
rotation: newFloorItem.rotation,
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: "roboticArm",
|
type: "roboticArm",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
speed: 1,
|
speed: 1,
|
||||||
point: {
|
point: {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
@@ -300,7 +293,7 @@ async function handleModelLoad(
|
|||||||
rotation: newFloorItem.rotation,
|
rotation: newFloorItem.rotation,
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: "machine",
|
type: "machine",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
point: {
|
point: {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||||
@@ -332,7 +325,7 @@ async function handleModelLoad(
|
|||||||
storageCapacity: 10,
|
storageCapacity: 10,
|
||||||
storageCount: 10,
|
storageCount: 10,
|
||||||
materialType: "Default material",
|
materialType: "Default material",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
point: {
|
point: {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||||
@@ -343,7 +336,7 @@ async function handleModelLoad(
|
|||||||
actionName: "Action 1",
|
actionName: "Action 1",
|
||||||
actionType: "store",
|
actionType: "store",
|
||||||
triggers: [],
|
triggers: [],
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -361,7 +354,7 @@ async function handleModelLoad(
|
|||||||
rotation: newFloorItem.rotation,
|
rotation: newFloorItem.rotation,
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: "human",
|
type: "human",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
speed: 1,
|
speed: 1,
|
||||||
point: {
|
point: {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
@@ -375,23 +368,23 @@ async function handleModelLoad(
|
|||||||
loadCount: 1,
|
loadCount: 1,
|
||||||
assemblyCount: 1,
|
assemblyCount: 1,
|
||||||
assemblyCondition: {
|
assemblyCondition: {
|
||||||
conditionType: 'material',
|
conditionType: "material",
|
||||||
materialType: "Default material"
|
materialType: "Default material",
|
||||||
},
|
},
|
||||||
manufactureCount: 1,
|
manufactureCount: 1,
|
||||||
loadCapacity: 1,
|
loadCapacity: 1,
|
||||||
processTime: 10,
|
processTime: 10,
|
||||||
triggers: []
|
triggers: [],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
addEvent(humanEvent);
|
addEvent(humanEvent);
|
||||||
eventData.point = {
|
eventData.point = {
|
||||||
uuid: humanEvent.point.uuid,
|
uuid: humanEvent.point.uuid,
|
||||||
position: humanEvent.point.position,
|
position: humanEvent.point.position,
|
||||||
rotation: humanEvent.point.rotation,
|
rotation: humanEvent.point.rotation,
|
||||||
}
|
};
|
||||||
} else if (selectedItem.type === "Crane") {
|
} else if (selectedItem.type === "Crane") {
|
||||||
const craneEvent: CraneEventSchema = {
|
const craneEvent: CraneEventSchema = {
|
||||||
modelUuid: newFloorItem.modelUuid,
|
modelUuid: newFloorItem.modelUuid,
|
||||||
@@ -400,7 +393,7 @@ async function handleModelLoad(
|
|||||||
rotation: newFloorItem.rotation,
|
rotation: newFloorItem.rotation,
|
||||||
state: "idle",
|
state: "idle",
|
||||||
type: "crane",
|
type: "crane",
|
||||||
subType: selectedItem.subType || '',
|
subType: selectedItem.subType || "",
|
||||||
point: {
|
point: {
|
||||||
uuid: THREE.MathUtils.generateUUID(),
|
uuid: THREE.MathUtils.generateUUID(),
|
||||||
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
position: [data.points[0].x, data.points[0].y, data.points[0].z],
|
||||||
@@ -411,17 +404,17 @@ async function handleModelLoad(
|
|||||||
actionName: "Action 1",
|
actionName: "Action 1",
|
||||||
actionType: "pickAndDrop",
|
actionType: "pickAndDrop",
|
||||||
maxPickUpCount: 1,
|
maxPickUpCount: 1,
|
||||||
triggers: []
|
triggers: [],
|
||||||
}
|
},
|
||||||
]
|
],
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
addEvent(craneEvent);
|
addEvent(craneEvent);
|
||||||
eventData.point = {
|
eventData.point = {
|
||||||
uuid: craneEvent.point.uuid,
|
uuid: craneEvent.point.uuid,
|
||||||
position: craneEvent.point.position,
|
position: craneEvent.point.position,
|
||||||
rotation: craneEvent.point.rotation,
|
rotation: craneEvent.point.rotation,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const completeData = {
|
const completeData = {
|
||||||
@@ -430,10 +423,10 @@ async function handleModelLoad(
|
|||||||
modelName: newFloorItem.modelName,
|
modelName: newFloorItem.modelName,
|
||||||
assetId: newFloorItem.assetId,
|
assetId: newFloorItem.assetId,
|
||||||
position: newFloorItem.position,
|
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,
|
isLocked: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
socketId: socket?.id || '',
|
socketId: socket?.id || "",
|
||||||
eventData: eventData,
|
eventData: eventData,
|
||||||
versionId: versionId,
|
versionId: versionId,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
@@ -448,14 +441,13 @@ async function handleModelLoad(
|
|||||||
modelName: newFloorItem.modelName,
|
modelName: newFloorItem.modelName,
|
||||||
position: newFloorItem.position,
|
position: newFloorItem.position,
|
||||||
assetId: newFloorItem.assetId,
|
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,
|
eventData: eventData,
|
||||||
isLocked: false,
|
isLocked: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
versionId: versionId,
|
versionId: versionId,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// SOCKET
|
// SOCKET
|
||||||
|
|
||||||
@@ -467,7 +459,7 @@ async function handleModelLoad(
|
|||||||
modelName: completeData.modelName,
|
modelName: completeData.modelName,
|
||||||
assetId: completeData.assetId,
|
assetId: completeData.assetId,
|
||||||
position: completeData.position,
|
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,
|
isLocked: completeData.isLocked,
|
||||||
isCollidable: false,
|
isCollidable: false,
|
||||||
isVisible: completeData.isVisible,
|
isVisible: completeData.isVisible,
|
||||||
@@ -483,10 +475,10 @@ async function handleModelLoad(
|
|||||||
modelName: newFloorItem.modelName,
|
modelName: newFloorItem.modelName,
|
||||||
assetId: newFloorItem.assetId,
|
assetId: newFloorItem.assetId,
|
||||||
position: newFloorItem.position,
|
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,
|
isLocked: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
socketId: socket?.id || '',
|
socketId: socket?.id || "",
|
||||||
versionId: versionId,
|
versionId: versionId,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
@@ -500,13 +492,12 @@ async function handleModelLoad(
|
|||||||
modelName: newFloorItem.modelName,
|
modelName: newFloorItem.modelName,
|
||||||
position: newFloorItem.position,
|
position: newFloorItem.position,
|
||||||
assetId: newFloorItem.assetId,
|
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,
|
isLocked: false,
|
||||||
isVisible: true,
|
isVisible: true,
|
||||||
versionId: versionId,
|
versionId: versionId,
|
||||||
projectId: projectId,
|
projectId: projectId,
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// SOCKET
|
// SOCKET
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import SocketResponses from "../collaboration/socket/socketResponses.dev";
|
|||||||
import Ground from "../scene/environment/ground";
|
import Ground from "../scene/environment/ground";
|
||||||
import MeasurementTool from "../scene/tools/measurementTool";
|
import MeasurementTool from "../scene/tools/measurementTool";
|
||||||
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
||||||
import CalculateAreaGroup from "./groups/calculateAreaGroup";
|
|
||||||
import LayoutImage from "./layout/layoutImage";
|
import LayoutImage from "./layout/layoutImage";
|
||||||
import AssetsGroup from "./asset/assetsGroup";
|
import AssetsGroup from "./asset/assetsGroup";
|
||||||
import DxfFile from "./dfx/LoadBlueprint";
|
import DxfFile from "./dfx/LoadBlueprint";
|
||||||
@@ -51,8 +50,8 @@ export default function Builder() {
|
|||||||
if (!toggleView) {
|
if (!toggleView) {
|
||||||
setHoveredLine(null);
|
setHoveredLine(null);
|
||||||
setHoveredPoint(null);
|
setHoveredPoint(null);
|
||||||
state.gl.domElement.style.cursor = 'default';
|
state.gl.domElement.style.cursor = "default";
|
||||||
setToolMode('cursor');
|
setToolMode("cursor");
|
||||||
}
|
}
|
||||||
}, [toggleView]);
|
}, [toggleView]);
|
||||||
|
|
||||||
@@ -65,14 +64,14 @@ export default function Builder() {
|
|||||||
setShadows(data.shadowVisibility);
|
setShadows(data.shadowVisibility);
|
||||||
setRenderDistance(data.renderDistance);
|
setRenderDistance(data.renderDistance);
|
||||||
setLimitDistance(data.limitDistance);
|
setLimitDistance(data.limitDistance);
|
||||||
})
|
});
|
||||||
}, [projectId]);
|
}, [projectId]);
|
||||||
|
|
||||||
useFrame(() => {
|
useFrame(() => {
|
||||||
if (csgRef.current && selectedWallAsset) {
|
if (csgRef.current && selectedWallAsset) {
|
||||||
csgRef.current.update();
|
csgRef.current.update();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -82,13 +81,11 @@ export default function Builder() {
|
|||||||
|
|
||||||
<AssetsGroup plane={plane} />
|
<AssetsGroup plane={plane} />
|
||||||
|
|
||||||
<mesh name='Walls-And-WallAssets-Group'>
|
<mesh name="Walls-And-WallAssets-Group">
|
||||||
<Geometry ref={csgRef} useGroups>
|
<Geometry ref={csgRef} useGroups>
|
||||||
|
|
||||||
<WallGroup />
|
<WallGroup />
|
||||||
|
|
||||||
<WallAssetGroup />
|
<WallAssetGroup />
|
||||||
|
|
||||||
</Geometry>
|
</Geometry>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|
||||||
@@ -102,8 +99,6 @@ export default function Builder() {
|
|||||||
|
|
||||||
<MeasurementTool />
|
<MeasurementTool />
|
||||||
|
|
||||||
<CalculateAreaGroup />
|
|
||||||
|
|
||||||
<NavMesh />
|
<NavMesh />
|
||||||
|
|
||||||
<DxfFile />
|
<DxfFile />
|
||||||
|
|||||||
@@ -1,50 +1,61 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from "react";
|
||||||
import { DoubleSide, Shape, Vector2 } from 'three';
|
import { DoubleSide, Shape, Vector2 } from "three";
|
||||||
import { Extrude } from '@react-three/drei';
|
import { Extrude, Html } from "@react-three/drei";
|
||||||
import * as Constants from '../../../../../types/world/worldConstants';
|
import * as Constants from "../../../../../types/world/worldConstants";
|
||||||
|
import getCenteroidPoint from "../../../functions/getCenteroid";
|
||||||
|
import getArea from "../../../functions/getArea";
|
||||||
|
|
||||||
function Floor2DInstance({ floor }: { readonly floor: Floor }) {
|
function Floor2DInstance({ floor }: { readonly floor: Floor }) {
|
||||||
const savedTheme: string | null = localStorage.getItem("theme");
|
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 = useMemo(() => {
|
||||||
const shape = new Shape();
|
const shape = new Shape();
|
||||||
const points = floor.points.map(p => new Vector2(p.position[0], p.position[2]));
|
shape.moveTo(points2D[0].x, points2D[0].y);
|
||||||
if (points.length < 3) return null;
|
for (let i = 1; i < points2D.length; i++) {
|
||||||
shape.moveTo(points[0].x, points[0].y);
|
shape.lineTo(points2D[i].x, points2D[i].y);
|
||||||
for (let i = 1; i < points.length; i++) {
|
|
||||||
shape.lineTo(points[i].x, points[i].y);
|
|
||||||
}
|
}
|
||||||
|
shape.lineTo(points2D[0].x, points2D[0].y);
|
||||||
return shape;
|
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;
|
if (!shape) return null;
|
||||||
|
|
||||||
|
const formattedArea = `${area.toFixed(2)} m²`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<>
|
||||||
castShadow
|
<mesh castShadow receiveShadow name={`Floor-2D-${floor.floorUuid}`} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]} userData={floor}>
|
||||||
receiveShadow
|
<Extrude name={`Floor-${floor.floorUuid}`} args={[shape, { depth: Constants.floorConfig.height }]} userData={floor}>
|
||||||
name={`Floor-2D-${floor.floorUuid}`}
|
<meshBasicMaterial
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
color={savedTheme === "dark" ? Constants.lineConfig.floorColor : Constants.lineConfig.floorColor}
|
||||||
position={[0, 0, 0]}
|
side={DoubleSide}
|
||||||
userData={floor}
|
transparent
|
||||||
>
|
opacity={0.4}
|
||||||
<Extrude
|
depthWrite={false}
|
||||||
name={`Floor-${floor.floorUuid}`}
|
/>
|
||||||
args={[shape, {
|
</Extrude>
|
||||||
depth: Constants.floorConfig.height,
|
</mesh>
|
||||||
}]}
|
|
||||||
userData={floor}
|
<Html key={floor.floorUuid} position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
|
||||||
>
|
<div className="distance area">
|
||||||
<meshBasicMaterial
|
{floor.floorName} ({formattedArea})
|
||||||
color={savedTheme === "dark" ? "#808080" : "#808080"}
|
</div>
|
||||||
side={DoubleSide}
|
</Html>
|
||||||
transparent
|
</>
|
||||||
opacity={0.4}
|
|
||||||
depthWrite={false}
|
|
||||||
/>
|
|
||||||
</Extrude>
|
|
||||||
</mesh>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Floor2DInstance;
|
export default Floor2DInstance;
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { Vector3 } from 'three';
|
import { Vector3 } from "three";
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from "@react-three/drei";
|
||||||
import { useSceneContext } from '../../../scene/sceneContext';
|
import { useSceneContext } from "../../../scene/sceneContext";
|
||||||
import { useToggleView, useToolMode } from '../../../../store/builder/store';
|
import { useToggleView, useToolMode } from "../../../../store/builder/store";
|
||||||
import Line from '../../line/line';
|
import Line from "../../line/line";
|
||||||
import Point from '../../point/point';
|
import Point from "../../point/point";
|
||||||
import FloorInstance from './Instance/floorInstance';
|
import FloorInstance from "./Instance/floorInstance";
|
||||||
import Floor2DInstance from './Instance/floor2DInstance';
|
import Floor2DInstance from "./Instance/floor2DInstance";
|
||||||
import useModuleStore from '../../../../store/ui/useModuleStore';
|
import useModuleStore from "../../../../store/ui/useModuleStore";
|
||||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||||
|
|
||||||
function FloorInstances() {
|
function FloorInstances() {
|
||||||
const { floorStore } = useSceneContext();
|
const { floorStore } = useSceneContext();
|
||||||
@@ -20,11 +20,11 @@ function FloorInstances() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('floors: ', floors);
|
// console.log('floors: ', floors);
|
||||||
}, [floors])
|
}, [floors]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!toggleView && activeModule === 'builder') {
|
if (!toggleView && activeModule === "builder") {
|
||||||
if (toolMode !== 'cursor') {
|
if (toolMode !== "cursor") {
|
||||||
if (selectedFloor) setSelectedFloor(null);
|
if (selectedFloor) setSelectedFloor(null);
|
||||||
}
|
}
|
||||||
} else if (selectedFloor) {
|
} else if (selectedFloor) {
|
||||||
@@ -36,8 +36,8 @@ function FloorInstances() {
|
|||||||
const points: Point[] = [];
|
const points: Point[] = [];
|
||||||
const seenUuids = new Set<string>();
|
const seenUuids = new Set<string>();
|
||||||
|
|
||||||
floors.forEach(floor => {
|
floors.forEach((floor) => {
|
||||||
floor.points.forEach(point => {
|
floor.points.forEach((point) => {
|
||||||
if (!seenUuids.has(point.pointUuid)) {
|
if (!seenUuids.has(point.pointUuid)) {
|
||||||
seenUuids.add(point.pointUuid);
|
seenUuids.add(point.pointUuid);
|
||||||
points.push(point);
|
points.push(point);
|
||||||
@@ -65,7 +65,7 @@ function FloorInstances() {
|
|||||||
lines.push({
|
lines.push({
|
||||||
start: current,
|
start: current,
|
||||||
end: next,
|
end: next,
|
||||||
key: lineKey
|
key: lineKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,9 +76,8 @@ function FloorInstances() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{!toggleView && floors.length > 0 && (
|
{!toggleView && floors.length > 0 && (
|
||||||
<mesh name='Floors-Group'>
|
<mesh name="Floors-Group">
|
||||||
{floors.map((floor) => (
|
{floors.map((floor) => (
|
||||||
<FloorInstance key={floor.floorUuid} floor={floor} />
|
<FloorInstance key={floor.floorUuid} floor={floor} />
|
||||||
))}
|
))}
|
||||||
@@ -86,7 +85,7 @@ function FloorInstances() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{toggleView && floors.length > 0 && (
|
{toggleView && floors.length > 0 && (
|
||||||
<mesh name='Floors-2D-Group'>
|
<mesh name="Floors-2D-Group">
|
||||||
{floors.map((floor) => (
|
{floors.map((floor) => (
|
||||||
<Floor2DInstance key={floor.floorUuid} floor={floor} />
|
<Floor2DInstance key={floor.floorUuid} floor={floor} />
|
||||||
))}
|
))}
|
||||||
@@ -95,14 +94,13 @@ function FloorInstances() {
|
|||||||
|
|
||||||
{toggleView && (
|
{toggleView && (
|
||||||
<>
|
<>
|
||||||
<group name='Floor-Points-Group'>
|
<group name="Floor-Points-Group">
|
||||||
{allPoints.map((point) => (
|
{allPoints.map((point) => (
|
||||||
<Point key={point.pointUuid} point={point} />
|
<Point key={point.pointUuid} point={point} />
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group name='Floor-Lines-Group'>
|
<group name="Floor-Lines-Group">
|
||||||
|
|
||||||
{allLines.map(({ start, end, key }) => (
|
{allLines.map(({ start, end, key }) => (
|
||||||
<Line key={key} points={[start, end]} />
|
<Line key={key} points={[start, end]} />
|
||||||
))}
|
))}
|
||||||
@@ -114,7 +112,7 @@ function FloorInstances() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={key}>
|
<React.Fragment key={key}>
|
||||||
{toggleView &&
|
{toggleView && (
|
||||||
<Html
|
<Html
|
||||||
key={`${start.pointUuid}_${end.pointUuid}`}
|
key={`${start.pointUuid}_${end.pointUuid}`}
|
||||||
userData={line}
|
userData={line}
|
||||||
@@ -125,24 +123,19 @@ function FloorInstances() {
|
|||||||
prepend
|
prepend
|
||||||
sprite
|
sprite
|
||||||
>
|
>
|
||||||
<div
|
<div key={key} className={`distance ${key}`}>
|
||||||
key={key}
|
|
||||||
className={`distance ${key}`}
|
|
||||||
>
|
|
||||||
{distance.toFixed(2)} m
|
{distance.toFixed(2)} m
|
||||||
</div>
|
</div>
|
||||||
</Html>
|
</Html>
|
||||||
}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FloorInstances;
|
export default FloorInstances;
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ function FloorCreator() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (snappedPosition && !snappedPoint) {
|
if (snappedPosition && !snappedPoint) {
|
||||||
newPoint.position = snappedPosition;
|
newPoint.position = snappedPosition;
|
||||||
}
|
}
|
||||||
@@ -288,7 +292,7 @@ function FloorCreator() {
|
|||||||
canvasElement.removeEventListener("click", onMouseClick);
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
canvasElement.removeEventListener("contextmenu", onContext);
|
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 (
|
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 * as THREE from "three";
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from "react";
|
||||||
import { useSceneContext } from '../../../scene/sceneContext';
|
import { useSceneContext } from "../../../scene/sceneContext";
|
||||||
|
|
||||||
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
|
const POINT_SNAP_THRESHOLD = 0.5; // Distance threshold for snapping in meters
|
||||||
|
|
||||||
const CAN_POINT_SNAP = true; // Whether snapping is enabled or not
|
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
|
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 { aisleStore, wallStore, floorStore, zoneStore } = useSceneContext();
|
||||||
const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
|
const { aisles, getConnectedPoints: getConnectedAislePoints } = aisleStore();
|
||||||
const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
|
const { walls, getConnectedPoints: getConnectedWallPoints } = wallStore();
|
||||||
@@ -21,350 +21,359 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
|
|||||||
|
|
||||||
const getAllOtherWallPoints = useCallback(() => {
|
const getAllOtherWallPoints = useCallback(() => {
|
||||||
if (!currentPoint) return [];
|
if (!currentPoint) return [];
|
||||||
return walls.flatMap(wall =>
|
return walls.flatMap((wall) => wall.points.filter((point) => point.pointUuid !== currentPoint.uuid));
|
||||||
wall.points.filter(point => point.pointUuid !== currentPoint.uuid)
|
|
||||||
);
|
|
||||||
}, [walls, currentPoint]);
|
}, [walls, currentPoint]);
|
||||||
|
|
||||||
const snapWallPoint = useCallback((position: [number, number, number], tempPoints?: Point) => {
|
const snapWallPoint = useCallback(
|
||||||
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
(position: [number, number, number]) => {
|
||||||
|
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
|
|
||||||
const otherPoints = getAllOtherWallPoints();
|
const otherPoints = getAllOtherWallPoints();
|
||||||
if (tempPoints) {
|
const currentVec = new THREE.Vector3(...position);
|
||||||
otherPoints.push(tempPoints);
|
for (const point of otherPoints) {
|
||||||
}
|
const pointVec = new THREE.Vector3(...point.position);
|
||||||
const currentVec = new THREE.Vector3(...position);
|
const distance = currentVec.distanceTo(pointVec);
|
||||||
for (const point of otherPoints) {
|
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Wall") {
|
||||||
const pointVec = new THREE.Vector3(...point.position);
|
return { position: point.position, isSnapped: true, snappedPoint: point };
|
||||||
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 };
|
||||||
return { position: position, isSnapped: false, snappedPoint: null };
|
},
|
||||||
}, [currentPoint, getAllOtherWallPoints]);
|
[currentPoint, getAllOtherWallPoints]
|
||||||
|
);
|
||||||
|
|
||||||
const snapWallAngle = useCallback((newPosition: [number, number, number]): {
|
const snapWallAngle = useCallback(
|
||||||
position: [number, number, number],
|
(newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
|
||||||
isSnapped: boolean,
|
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
|
||||||
snapSources: THREE.Vector3[]
|
|
||||||
} => {
|
const connectedPoints = getConnectedWallPoints(currentPoint.uuid);
|
||||||
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
|
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 {
|
return {
|
||||||
position: newPosition,
|
position: [snappedPos.x, snappedPos.y, snappedPos.z],
|
||||||
isSnapped: false,
|
isSnapped,
|
||||||
snapSources: []
|
snapSources,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
[currentPoint, getConnectedAislePoints]
|
||||||
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]);
|
|
||||||
|
|
||||||
// Aisle Snapping
|
// Aisle Snapping
|
||||||
|
|
||||||
const getAllOtherAislePoints = useCallback(() => {
|
const getAllOtherAislePoints = useCallback(() => {
|
||||||
if (!currentPoint) return [];
|
if (!currentPoint) return [];
|
||||||
|
|
||||||
return aisles.flatMap(aisle =>
|
return aisles.flatMap((aisle) => aisle.points.filter((point) => point.pointUuid !== currentPoint.uuid));
|
||||||
aisle.points.filter(point => point.pointUuid !== currentPoint.uuid)
|
|
||||||
);
|
|
||||||
}, [aisles, currentPoint]);
|
}, [aisles, currentPoint]);
|
||||||
|
|
||||||
const snapAislePoint = useCallback((position: [number, number, number]) => {
|
const snapAislePoint = useCallback(
|
||||||
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
(position: [number, number, number]) => {
|
||||||
|
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
|
|
||||||
const otherPoints = getAllOtherAislePoints();
|
const otherPoints = getAllOtherAislePoints();
|
||||||
const currentVec = new THREE.Vector3(...position);
|
const currentVec = new THREE.Vector3(...position);
|
||||||
for (const point of otherPoints) {
|
for (const point of otherPoints) {
|
||||||
const pointVec = new THREE.Vector3(...point.position);
|
const pointVec = new THREE.Vector3(...point.position);
|
||||||
const distance = currentVec.distanceTo(pointVec);
|
const distance = currentVec.distanceTo(pointVec);
|
||||||
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === 'Aisle') {
|
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Aisle") {
|
||||||
return { position: point.position, isSnapped: true, snappedPoint: point };
|
return { position: point.position, isSnapped: true, snappedPoint: point };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return { position: position, isSnapped: false, snappedPoint: null };
|
return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
}, [currentPoint, getAllOtherAislePoints]);
|
},
|
||||||
|
[currentPoint, getAllOtherAislePoints]
|
||||||
|
);
|
||||||
|
|
||||||
const snapAisleAngle = useCallback((newPosition: [number, number, number]): {
|
const snapAisleAngle = useCallback(
|
||||||
position: [number, number, number],
|
(newPosition: [number, number, number]): { position: [number, number, number]; isSnapped: boolean; snapSources: THREE.Vector3[] } => {
|
||||||
isSnapped: boolean,
|
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
|
||||||
snapSources: THREE.Vector3[]
|
|
||||||
} => {
|
const connectedPoints = getConnectedAislePoints(currentPoint.uuid);
|
||||||
if (!currentPoint || !CAN_ANGLE_SNAP) return { position: newPosition, isSnapped: false, snapSources: [] };
|
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 {
|
return {
|
||||||
position: newPosition,
|
position: [snappedPos.x, snappedPos.y, snappedPos.z],
|
||||||
isSnapped: false,
|
isSnapped,
|
||||||
snapSources: []
|
snapSources,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
[currentPoint, getConnectedAislePoints]
|
||||||
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]);
|
|
||||||
|
|
||||||
// Floor Snapping
|
// Floor Snapping
|
||||||
|
|
||||||
const getAllOtherFloorPoints = useCallback(() => {
|
const getAllOtherFloorPoints = useCallback(() => {
|
||||||
if (!currentPoint) return [];
|
if (!currentPoint) return [];
|
||||||
|
|
||||||
return floors.flatMap(floor =>
|
return floors.flatMap((floor) => floor.points.filter((point) => point.pointUuid !== currentPoint.uuid));
|
||||||
floor.points.filter(point => point.pointUuid !== currentPoint.uuid)
|
|
||||||
);
|
|
||||||
}, [floors, currentPoint]);
|
}, [floors, currentPoint]);
|
||||||
|
|
||||||
const snapFloorPoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
|
const snapFloorPoint = useCallback(
|
||||||
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
(position: [number, number, number], tempPoints?: Point[] | []) => {
|
||||||
|
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
|
|
||||||
let otherPoints = getAllOtherFloorPoints();
|
let otherPoints = getAllOtherFloorPoints();
|
||||||
if (tempPoints) {
|
if (tempPoints) {
|
||||||
otherPoints = [...otherPoints, ...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 };
|
|
||||||
}
|
}
|
||||||
}
|
const currentVec = new THREE.Vector3(...position);
|
||||||
|
|
||||||
return { position: position, isSnapped: false, snappedPoint: null };
|
for (const point of otherPoints) {
|
||||||
}, [currentPoint, getAllOtherFloorPoints]);
|
const pointVec = new THREE.Vector3(...point.position);
|
||||||
|
const distance = currentVec.distanceTo(pointVec);
|
||||||
|
|
||||||
const snapFloorAngle = useCallback((newPosition: [number, number, number]): {
|
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Floor") {
|
||||||
position: [number, number, number],
|
return { position: point.position, isSnapped: true, snappedPoint: point };
|
||||||
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) {
|
return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
if (!closestZ || zDist < closestZ.dist) {
|
},
|
||||||
closestZ = { pos: cPos, dist: zDist };
|
[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 snappedPos = newPos.clone();
|
||||||
const snapSources: THREE.Vector3[] = [];
|
const snapSources: THREE.Vector3[] = [];
|
||||||
|
|
||||||
if (closestX) {
|
if (closestX) {
|
||||||
snappedPos.x = closestX.pos.x;
|
snappedPos.x = closestX.pos.x;
|
||||||
snapSources.push(closestX.pos.clone());
|
snapSources.push(closestX.pos.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closestZ) {
|
if (closestZ) {
|
||||||
snappedPos.z = closestZ.pos.z;
|
snappedPos.z = closestZ.pos.z;
|
||||||
snapSources.push(closestZ.pos.clone());
|
snapSources.push(closestZ.pos.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSnapped = snapSources.length > 0;
|
const isSnapped = snapSources.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: [snappedPos.x, snappedPos.y, snappedPos.z],
|
position: [snappedPos.x, snappedPos.y, snappedPos.z],
|
||||||
isSnapped,
|
isSnapped,
|
||||||
snapSources
|
snapSources,
|
||||||
};
|
};
|
||||||
}, [currentPoint, getConnectedFloorPoints]);
|
},
|
||||||
|
[currentPoint, getConnectedFloorPoints]
|
||||||
|
);
|
||||||
|
|
||||||
// Zone Snapping
|
// Zone Snapping
|
||||||
|
|
||||||
const getAllOtherZonePoints = useCallback(() => {
|
const getAllOtherZonePoints = useCallback(() => {
|
||||||
if (!currentPoint) return [];
|
if (!currentPoint) return [];
|
||||||
|
|
||||||
return zones.flatMap(zone =>
|
return zones.flatMap((zone) => zone.points.filter((point) => point.pointUuid !== currentPoint.uuid));
|
||||||
zone.points.filter(point => point.pointUuid !== currentPoint.uuid)
|
|
||||||
);
|
|
||||||
}, [zones, currentPoint]);
|
}, [zones, currentPoint]);
|
||||||
|
|
||||||
const snapZonePoint = useCallback((position: [number, number, number], tempPoints?: Point[] | []) => {
|
const snapZonePoint = useCallback(
|
||||||
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
(position: [number, number, number], tempPoints?: Point[] | []) => {
|
||||||
|
if (!currentPoint || !CAN_POINT_SNAP) return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
|
|
||||||
let otherPoints = getAllOtherZonePoints();
|
let otherPoints = getAllOtherZonePoints();
|
||||||
if (tempPoints) {
|
if (tempPoints) {
|
||||||
otherPoints = [...otherPoints, ...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 };
|
|
||||||
}
|
}
|
||||||
}
|
const currentVec = new THREE.Vector3(...position);
|
||||||
|
|
||||||
return { position: position, isSnapped: false, snappedPoint: null };
|
for (const point of otherPoints) {
|
||||||
}, [currentPoint, getAllOtherZonePoints]);
|
const pointVec = new THREE.Vector3(...point.position);
|
||||||
|
const distance = currentVec.distanceTo(pointVec);
|
||||||
|
|
||||||
const snapZoneAngle = useCallback((newPosition: [number, number, number]): {
|
if (distance <= POINT_SNAP_THRESHOLD && currentPoint.pointType === "Zone") {
|
||||||
position: [number, number, number],
|
return { position: point.position, isSnapped: true, snappedPoint: point };
|
||||||
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) {
|
return { position: position, isSnapped: false, snappedPoint: null };
|
||||||
if (!closestZ || zDist < closestZ.dist) {
|
},
|
||||||
closestZ = { pos: cPos, dist: zDist };
|
[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 snappedPos = newPos.clone();
|
||||||
const snapSources: THREE.Vector3[] = [];
|
const snapSources: THREE.Vector3[] = [];
|
||||||
|
|
||||||
if (closestX) {
|
if (closestX) {
|
||||||
snappedPos.x = closestX.pos.x;
|
snappedPos.x = closestX.pos.x;
|
||||||
snapSources.push(closestX.pos.clone());
|
snapSources.push(closestX.pos.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closestZ) {
|
if (closestZ) {
|
||||||
snappedPos.z = closestZ.pos.z;
|
snappedPos.z = closestZ.pos.z;
|
||||||
snapSources.push(closestZ.pos.clone());
|
snapSources.push(closestZ.pos.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSnapped = snapSources.length > 0;
|
const isSnapped = snapSources.length > 0;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
position: [snappedPos.x, snappedPos.y, snappedPos.z],
|
position: [snappedPos.x, snappedPos.y, snappedPos.z],
|
||||||
isSnapped,
|
isSnapped,
|
||||||
snapSources
|
snapSources,
|
||||||
};
|
};
|
||||||
}, [currentPoint, getConnectedZonePoints]);
|
},
|
||||||
|
[currentPoint, getConnectedZonePoints]
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
snapAislePoint,
|
snapAislePoint,
|
||||||
@@ -376,4 +385,4 @@ export const usePointSnapping = (currentPoint: { uuid: string, pointType: string
|
|||||||
snapZonePoint,
|
snapZonePoint,
|
||||||
snapZoneAngle,
|
snapZoneAngle,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,151 +1,73 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from "react";
|
||||||
import * as turf from '@turf/turf';
|
import * as turf from "@turf/turf";
|
||||||
|
|
||||||
export function useWallClassification(walls: Walls) {
|
export function useWallClassification(walls: Walls) {
|
||||||
|
|
||||||
const findRooms = () => {
|
const findRooms = () => {
|
||||||
if (walls.length < 3) return [];
|
if (walls.length < 3) return [];
|
||||||
|
|
||||||
// Map pointUuid to list of connected line segments
|
const wallSet = new Map<string, Wall>();
|
||||||
const pointMap = 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 wall of walls) {
|
||||||
for (const point of wall.points) {
|
const [p1, p2] = wall.points;
|
||||||
const list = pointMap.get(point.pointUuid) || [];
|
|
||||||
list.push(wall);
|
if (p1.pointUuid === p2.pointUuid || (p1.position[0] === p2.position[0] && p1.position[1] === p2.position[1] && p1.position[2] === p2.position[2])) {
|
||||||
pointMap.set(point.pointUuid, list);
|
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 uniqueWalls = Array.from(wallSet.values());
|
||||||
const visited = new Set<string>();
|
|
||||||
const mergedLineStrings = [];
|
|
||||||
|
|
||||||
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 collection = turf.featureCollection(lineStrings);
|
||||||
const key = wallKey(wall.points[0], wall.points[1]);
|
|
||||||
if (visited.has(key) || visited.has(wallKey(wall.points[1], wall.points[0]))) continue;
|
|
||||||
|
|
||||||
let line: Point[] = [...wall.points];
|
const polygons = turf.polygonize(collection);
|
||||||
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 rooms: Point[][] = [];
|
const rooms: Point[][] = [];
|
||||||
|
|
||||||
polygons.features.forEach(feature => {
|
polygons.features.forEach((feature) => {
|
||||||
if (feature.geometry.type === 'Polygon') {
|
if (feature.geometry.type === "Polygon") {
|
||||||
const coordinates = feature.geometry.coordinates[0];
|
const coords = feature.geometry.coordinates[0];
|
||||||
const roomPoints: Point[] = [];
|
const roomPoints: Point[] = [];
|
||||||
|
|
||||||
for (const [x, z] of coordinates) {
|
for (const [x, z] of coords) {
|
||||||
const matchingPoint = walls.flatMap(wall => wall.points)
|
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));
|
||||||
.find(p =>
|
|
||||||
p.position[0].toFixed(10) === x.toFixed(10) &&
|
|
||||||
p.position[2].toFixed(10) === z.toFixed(10)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (matchingPoint) {
|
if (matchingPoint) {
|
||||||
roomPoints.push(matchingPoint);
|
roomPoints.push(matchingPoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roomPoints.length > 0 &&
|
if (roomPoints.length > 0 && roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
|
||||||
roomPoints[0].pointUuid !== roomPoints[roomPoints.length - 1].pointUuid) {
|
|
||||||
roomPoints.push(roomPoints[0]);
|
roomPoints.push(roomPoints[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,12 +83,14 @@ export function useWallClassification(walls: Walls) {
|
|||||||
const rooms = useMemo(() => findRooms(), [walls]);
|
const rooms = useMemo(() => findRooms(), [walls]);
|
||||||
|
|
||||||
const findWallType = (wall: Wall) => {
|
const findWallType = (wall: Wall) => {
|
||||||
const containingRooms = rooms.filter(room => {
|
const containingRooms = rooms.filter((room) => {
|
||||||
for (let i = 0; i < room.length - 1; i++) {
|
for (let i = 0; i < room.length - 1; i++) {
|
||||||
const p1 = room[i];
|
const p1 = room[i];
|
||||||
const p2 = room[i + 1];
|
const p2 = room[i + 1];
|
||||||
if ((wall.points[0].pointUuid === p1.pointUuid && wall.points[1].pointUuid === p2.pointUuid) ||
|
if (
|
||||||
(wall.points[0].pointUuid === p2.pointUuid && wall.points[1].pointUuid === p1.pointUuid)) {
|
(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;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -175,18 +99,18 @@ export function useWallClassification(walls: Walls) {
|
|||||||
|
|
||||||
if (containingRooms.length === 0) {
|
if (containingRooms.length === 0) {
|
||||||
return {
|
return {
|
||||||
type: 'segment',
|
type: "segment",
|
||||||
rooms: []
|
rooms: [],
|
||||||
};
|
};
|
||||||
} else if (containingRooms.length === 1) {
|
} else if (containingRooms.length === 1) {
|
||||||
return {
|
return {
|
||||||
type: 'room',
|
type: "room",
|
||||||
rooms: containingRooms
|
rooms: containingRooms,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
type: 'rooms',
|
type: "rooms",
|
||||||
rooms: containingRooms
|
rooms: containingRooms,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -197,16 +121,16 @@ export function useWallClassification(walls: Walls) {
|
|||||||
|
|
||||||
const isRoomWall = (wall: Wall): boolean => {
|
const isRoomWall = (wall: Wall): boolean => {
|
||||||
const type = findWallType(wall).type;
|
const type = findWallType(wall).type;
|
||||||
return type === 'room' || type === 'rooms';
|
return type === "room" || type === "rooms";
|
||||||
};
|
};
|
||||||
|
|
||||||
const isSegmentWall = (wall: Wall): boolean => {
|
const isSegmentWall = (wall: Wall): boolean => {
|
||||||
return findWallType(wall).type === 'segment';
|
return findWallType(wall).type === "segment";
|
||||||
};
|
};
|
||||||
|
|
||||||
const isWallFlipped = (wall: Wall): boolean => {
|
const isWallFlipped = (wall: Wall): boolean => {
|
||||||
const wallType = findWallType(wall);
|
const wallType = findWallType(wall);
|
||||||
if (wallType.type === 'segment') return false;
|
if (wallType.type === "segment") return false;
|
||||||
|
|
||||||
for (const room of wallType.rooms) {
|
for (const room of wallType.rooms) {
|
||||||
for (let i = 0; i < room.length - 1; i++) {
|
for (let i = 0; i < room.length - 1; i++) {
|
||||||
@@ -223,13 +147,12 @@ export function useWallClassification(walls: Walls) {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
rooms,
|
rooms,
|
||||||
getWallType,
|
getWallType,
|
||||||
isRoomWall,
|
isRoomWall,
|
||||||
isSegmentWall,
|
isSegmentWall,
|
||||||
findRooms,
|
findRooms,
|
||||||
isWallFlipped
|
isWallFlipped,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,19 +1,21 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from 'three';
|
import { DoubleSide, RepeatWrapping, Shape, SRGBColorSpace, TextureLoader, Vector2, Vector3 } from "three";
|
||||||
import { Html, Extrude } from '@react-three/drei';
|
import { Html, Extrude } from "@react-three/drei";
|
||||||
import { useLoader } from '@react-three/fiber';
|
import { useLoader } from "@react-three/fiber";
|
||||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||||
import { useSceneContext } from '../../../scene/sceneContext';
|
import { useSceneContext } from "../../../scene/sceneContext";
|
||||||
import { useToggleView, useToolMode } from '../../../../store/builder/store';
|
import { useToggleView, useToolMode } from "../../../../store/builder/store";
|
||||||
import { useWallClassification } from './instance/helpers/useWallClassification';
|
import { useWallClassification } from "./instance/helpers/useWallClassification";
|
||||||
import Line from '../../line/line';
|
import Line from "../../line/line";
|
||||||
import Point from '../../point/point';
|
import Point from "../../point/point";
|
||||||
import WallInstance from './instance/wallInstance';
|
import WallInstance from "./instance/wallInstance";
|
||||||
import * as Constants from '../../../../types/world/worldConstants';
|
import * as Constants from "../../../../types/world/worldConstants";
|
||||||
|
|
||||||
import texturePath from "../../../../assets/textures/floor/white.png";
|
import texturePath from "../../../../assets/textures/floor/white.png";
|
||||||
import texturePathDark from "../../../../assets/textures/floor/black.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() {
|
function WallInstances() {
|
||||||
const { wallStore } = useSceneContext();
|
const { wallStore } = useSceneContext();
|
||||||
@@ -25,12 +27,12 @@ function WallInstances() {
|
|||||||
const { rooms } = useWallClassification(walls);
|
const { rooms } = useWallClassification(walls);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('walls: ', walls);
|
// console.log("walls: ", walls);
|
||||||
}, [walls])
|
}, [walls]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!toggleView && activeModule === 'builder') {
|
if (!toggleView && activeModule === "builder") {
|
||||||
if (toolMode !== 'cursor') {
|
if (toolMode !== "cursor") {
|
||||||
if (selectedWall) setSelectedWall(null);
|
if (selectedWall) setSelectedWall(null);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -42,8 +44,8 @@ function WallInstances() {
|
|||||||
const points: Point[] = [];
|
const points: Point[] = [];
|
||||||
const seenUuids = new Set<string>();
|
const seenUuids = new Set<string>();
|
||||||
|
|
||||||
walls.forEach(wall => {
|
walls.forEach((wall) => {
|
||||||
wall.points.forEach(point => {
|
wall.points.forEach((point) => {
|
||||||
if (!seenUuids.has(point.pointUuid)) {
|
if (!seenUuids.has(point.pointUuid)) {
|
||||||
seenUuids.add(point.pointUuid);
|
seenUuids.add(point.pointUuid);
|
||||||
points.push(point);
|
points.push(point);
|
||||||
@@ -62,9 +64,9 @@ function WallInstances() {
|
|||||||
<WallInstance key={wall.wallUuid} wall={wall} />
|
<WallInstance key={wall.wallUuid} wall={wall} />
|
||||||
))}
|
))}
|
||||||
|
|
||||||
<group name='Wall-Floors-Group'>
|
<group name="Wall-Floors-Group">
|
||||||
{rooms.map((room, index) => (
|
{rooms.map((room, index) => (
|
||||||
<Floor key={index} room={room} />
|
<Floor3D key={index} room={room} />
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
</>
|
</>
|
||||||
@@ -72,14 +74,19 @@ function WallInstances() {
|
|||||||
|
|
||||||
{toggleView && (
|
{toggleView && (
|
||||||
<>
|
<>
|
||||||
<group name='Wall-Points-Group'>
|
<group name="Wall-Points-Group">
|
||||||
{allPoints.map((point) => (
|
{allPoints.map((point) => (
|
||||||
<Point key={point.pointUuid} point={point} />
|
<Point key={point.pointUuid} point={point} />
|
||||||
))}
|
))}
|
||||||
</group>
|
</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) => (
|
{walls.map((wall) => (
|
||||||
<React.Fragment key={wall.wallUuid}>
|
<React.Fragment key={wall.wallUuid}>
|
||||||
<Line points={wall.points} />
|
<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));
|
const distance = new Vector3(...wall.points[0].position).distanceTo(new Vector3(...wall.points[1].position));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
< React.Fragment key={wall.wallUuid}>
|
<React.Fragment key={wall.wallUuid}>
|
||||||
{toggleView &&
|
{toggleView && (
|
||||||
<Html
|
<Html
|
||||||
key={`${wall.points[0].pointUuid}_${wall.points[1].pointUuid}`}
|
key={`${wall.points[0].pointUuid}_${wall.points[1].pointUuid}`}
|
||||||
userData={wall}
|
userData={wall}
|
||||||
@@ -103,29 +110,25 @@ function WallInstances() {
|
|||||||
prepend
|
prepend
|
||||||
sprite
|
sprite
|
||||||
>
|
>
|
||||||
<div
|
<div key={wall.wallUuid} className={`distance ${wall.wallUuid}`}>
|
||||||
key={wall.wallUuid}
|
|
||||||
className={`distance ${wall.wallUuid}`}
|
|
||||||
>
|
|
||||||
{distance.toFixed(2)} m
|
{distance.toFixed(2)} m
|
||||||
</div>
|
</div>
|
||||||
</Html>
|
</Html>
|
||||||
}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default WallInstances;
|
export default WallInstances;
|
||||||
|
|
||||||
function Floor({ room }: { readonly room: Point[] }) {
|
function Floor3D({ room }: { readonly room: Point[] }) {
|
||||||
const savedTheme: string | null = localStorage.getItem('theme');
|
const savedTheme: string | null = localStorage.getItem("theme");
|
||||||
const textureScale = Constants.floorConfig.textureScale;
|
const textureScale = Constants.floorConfig.textureScale;
|
||||||
const floorTexture = useLoader(TextureLoader, savedTheme === "dark" ? texturePathDark : texturePath);
|
const floorTexture = useLoader(TextureLoader, savedTheme === "dark" ? texturePathDark : texturePath);
|
||||||
floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping;
|
floorTexture.wrapS = floorTexture.wrapT = RepeatWrapping;
|
||||||
@@ -134,10 +137,12 @@ function Floor({ room }: { readonly room: Point[] }) {
|
|||||||
|
|
||||||
const shape = useMemo(() => {
|
const shape = useMemo(() => {
|
||||||
const shape = new Shape();
|
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;
|
if (points.length < 3) return null;
|
||||||
shape.moveTo(points[0].x, points[0].y);
|
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;
|
return shape;
|
||||||
}, [room]);
|
}, [room]);
|
||||||
|
|
||||||
@@ -145,15 +150,54 @@ function Floor({ room }: { readonly room: Point[] }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
|
<mesh name="Wall-Floor" rotation={[Math.PI / 2, 0, 0]}>
|
||||||
<Extrude
|
<Extrude receiveShadow castShadow name="Wall-Floor" args={[shape, { depth: Constants.floorConfig.height, bevelEnabled: false }]} position={[0, 0, 0]}>
|
||||||
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} />
|
<meshStandardMaterial color={Constants.floorConfig.defaultColor} map={floorTexture} side={DoubleSide} />
|
||||||
</Extrude>
|
</Extrude>
|
||||||
</mesh>
|
</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 { useEffect, useRef, useState } from "react";
|
||||||
import * as THREE from 'three';
|
import * as THREE from "three";
|
||||||
import { useFrame, useThree } from '@react-three/fiber';
|
import { useFrame, useThree } from "@react-three/fiber";
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from "@react-three/drei";
|
||||||
import { useBuilderStore } from '../../../../store/builder/useBuilderStore';
|
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||||
import { useActiveLayer, useToolMode, useToggleView } from '../../../../store/builder/store';
|
import { useActiveLayer, useToolMode, useToggleView } from "../../../../store/builder/store";
|
||||||
import { useDirectionalSnapping } from '../../point/helpers/useDirectionalSnapping';
|
import { useDirectionalSnapping } from "../../point/helpers/useDirectionalSnapping";
|
||||||
import { usePointSnapping } from '../../point/helpers/usePointSnapping';
|
import { usePointSnapping } from "../../point/helpers/usePointSnapping";
|
||||||
import ReferenceLine from '../../line/reference/referenceLine';
|
import ReferenceLine from "../../line/reference/referenceLine";
|
||||||
|
|
||||||
interface ReferenceWallProps {
|
interface ReferenceWallProps {
|
||||||
tempPoints: Point[];
|
tempPoints: Point[];
|
||||||
@@ -26,10 +26,10 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
|||||||
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
|
const [currentPosition, setCurrentPosition] = useState<[number, number, number]>(tempPoints[0]?.position);
|
||||||
|
|
||||||
const directionalSnap = useDirectionalSnapping(currentPosition, tempPoints[0]?.position || null);
|
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(() => {
|
useFrame(() => {
|
||||||
if (toolMode === 'Wall' && toggleView && tempPoints.length === 1) {
|
if (toolMode === "Wall" && toggleView && tempPoints.length === 1) {
|
||||||
raycaster.setFromCamera(pointer, camera);
|
raycaster.setFromCamera(pointer, camera);
|
||||||
const intersectionPoint = new THREE.Vector3();
|
const intersectionPoint = new THREE.Vector3();
|
||||||
raycaster.ray.intersectPlane(plane, intersectionPoint);
|
raycaster.ray.intersectPlane(plane, intersectionPoint);
|
||||||
@@ -37,7 +37,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
|||||||
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
|
setCurrentPosition([intersectionPoint.x, intersectionPoint.y, intersectionPoint.z]);
|
||||||
|
|
||||||
if (!intersectionPoint) return;
|
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) {
|
if (snapped.isSnapped && snapped.snappedPoint) {
|
||||||
finalPosition.current = snapped.position;
|
finalPosition.current = snapped.position;
|
||||||
@@ -58,23 +58,22 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
|||||||
const wallPoints: [Point, Point] = [
|
const wallPoints: [Point, Point] = [
|
||||||
tempPoints[0],
|
tempPoints[0],
|
||||||
{
|
{
|
||||||
pointUuid: 'temp-point',
|
pointUuid: "temp-point",
|
||||||
pointType: 'Wall',
|
pointType: "Wall",
|
||||||
position: finalPosition.current,
|
position: finalPosition.current,
|
||||||
layer: activeLayer,
|
layer: activeLayer,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
setTempWall({
|
setTempWall({
|
||||||
wallUuid: 'temp-wall',
|
wallUuid: "temp-wall",
|
||||||
points: wallPoints,
|
points: wallPoints,
|
||||||
outsideMaterial: 'default',
|
outsideMaterial: "default",
|
||||||
insideMaterial: 'default',
|
insideMaterial: "default",
|
||||||
wallThickness: wallThickness,
|
wallThickness: wallThickness,
|
||||||
wallHeight: wallHeight,
|
wallHeight: wallHeight,
|
||||||
decals: []
|
decals: [],
|
||||||
})
|
});
|
||||||
|
|
||||||
} else if (tempWall !== null) {
|
} else if (tempWall !== null) {
|
||||||
setTempWall(null);
|
setTempWall(null);
|
||||||
}
|
}
|
||||||
@@ -87,9 +86,7 @@ function ReferenceWall({ tempPoints }: Readonly<ReferenceWallProps>) {
|
|||||||
if (!tempWall) return null;
|
if (!tempWall) return null;
|
||||||
|
|
||||||
const renderWall = () => {
|
const renderWall = () => {
|
||||||
return (
|
return <ReferenceLine points={tempWall.points} />;
|
||||||
<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);
|
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>
|
</Html>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<group name="Wall-Reference-Group">
|
<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 { activeLayer } = useActiveLayer();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { wallStore, undoRedo2DStore, versionStore } = useSceneContext();
|
const { wallStore, undoRedo2DStore, versionStore } = useSceneContext();
|
||||||
const { addWall, getWallPointById, removeWall, getWallByPoints } = wallStore();
|
const { addWall, getWallPointById, removeWall, getWallByPoints, getConnectedPoints } = wallStore();
|
||||||
const { push2D } = undoRedo2DStore();
|
const { push2D } = undoRedo2DStore();
|
||||||
const drag = useRef(false);
|
const drag = useRef(false);
|
||||||
const isLeftMouseDown = useRef(false);
|
const isLeftMouseDown = useRef(false);
|
||||||
@@ -379,7 +379,9 @@ function WallCreator() {
|
|||||||
newPoint.layer = snappedPoint.layer;
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,7 +487,29 @@ function WallCreator() {
|
|||||||
canvasElement.removeEventListener("click", onMouseClick);
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
canvasElement.removeEventListener("contextmenu", onContext);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,50 +1,55 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from "react";
|
||||||
import { DoubleSide, Shape, Vector2 } from 'three';
|
import { DoubleSide, Shape, Vector2 } from "three";
|
||||||
import { Extrude } from '@react-three/drei';
|
import { Extrude, Html } from "@react-three/drei";
|
||||||
import * as Constants from '../../../../../types/world/worldConstants';
|
import * as Constants from "../../../../../types/world/worldConstants";
|
||||||
|
import getCenteroid from "../../../functions/getCenteroid";
|
||||||
|
import getArea from "../../../functions/getArea";
|
||||||
|
|
||||||
function Zone2DInstance({ zone }: { readonly zone: Zone }) {
|
function Zone2DInstance({ zone }: { readonly zone: Zone }) {
|
||||||
const savedTheme: string | null = localStorage.getItem("theme");
|
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 = useMemo(() => {
|
||||||
const shape = new Shape();
|
const shape = new Shape();
|
||||||
const points = zone.points.map(p => new Vector2(p.position[0], p.position[2]));
|
shape.moveTo(points2D[0].x, points2D[0].y);
|
||||||
if (points.length < 3) return null;
|
for (let i = 1; i < points2D.length; i++) {
|
||||||
shape.moveTo(points[0].x, points[0].y);
|
shape.lineTo(points2D[i].x, points2D[i].y);
|
||||||
for (let i = 1; i < points.length; i++) {
|
|
||||||
shape.lineTo(points[i].x, points[i].y);
|
|
||||||
}
|
}
|
||||||
|
shape.lineTo(points2D[0].x, points2D[0].y);
|
||||||
return shape;
|
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;
|
if (!shape) return null;
|
||||||
|
|
||||||
|
const formattedArea = `${area.toFixed(2)} m²`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<>
|
||||||
castShadow
|
<mesh castShadow receiveShadow name={`Zone-2D-${zone.zoneUuid}`} rotation={[Math.PI / 2, 0, 0]} position={[0, 0, 0]} userData={zone}>
|
||||||
receiveShadow
|
<Extrude name={`Zone-${zone.zoneUuid}`} args={[shape, { depth: Constants.floorConfig.height }]} userData={zone}>
|
||||||
name={`Zone-2D-${zone.zoneUuid}`}
|
<meshBasicMaterial color={savedTheme === "dark" ? Constants.lineConfig.zoneColor : Constants.lineConfig.zoneColor} side={DoubleSide} transparent opacity={0.4} depthWrite={false} />
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
</Extrude>
|
||||||
position={[0, 0, 0]}
|
</mesh>
|
||||||
userData={zone}
|
|
||||||
>
|
<Html key={zone.zoneUuid} position={centroid} wrapperClass="distance-text-wrapper" className="distance-text" zIndexRange={[1, 0]} prepend center sprite>
|
||||||
<Extrude
|
<div className="distance area">
|
||||||
name={`Zone-${zone.zoneUuid}`}
|
{zone.zoneName} ({formattedArea})
|
||||||
args={[shape, {
|
</div>
|
||||||
depth: Constants.floorConfig.height,
|
</Html>
|
||||||
}]}
|
</>
|
||||||
userData={zone}
|
|
||||||
>
|
|
||||||
<meshBasicMaterial
|
|
||||||
color={savedTheme === "dark" ? "#007BFF" : "#007BFF"}
|
|
||||||
side={DoubleSide}
|
|
||||||
transparent
|
|
||||||
opacity={0.4}
|
|
||||||
depthWrite={false}
|
|
||||||
/>
|
|
||||||
</Extrude>
|
|
||||||
</mesh>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Zone2DInstance;
|
export default Zone2DInstance;
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React, { useEffect, useMemo } from 'react';
|
import React, { useEffect, useMemo } from "react";
|
||||||
import { Vector3 } from 'three';
|
import { Vector3 } from "three";
|
||||||
import { Html } from '@react-three/drei';
|
import { Html } from "@react-three/drei";
|
||||||
import { useSceneContext } from '../../../scene/sceneContext';
|
import { useSceneContext } from "../../../scene/sceneContext";
|
||||||
import { useToggleView } from '../../../../store/builder/store';
|
import { useToggleView } from "../../../../store/builder/store";
|
||||||
import Line from '../../line/line';
|
import Line from "../../line/line";
|
||||||
import Point from '../../point/point';
|
import Point from "../../point/point";
|
||||||
import ZoneInstance from './Instance/zoneInstance';
|
import ZoneInstance from "./Instance/zoneInstance";
|
||||||
import Zone2DInstance from './Instance/zone2DInstance';
|
import Zone2DInstance from "./Instance/zone2DInstance";
|
||||||
|
|
||||||
function ZoneInstances() {
|
function ZoneInstances() {
|
||||||
const { zoneStore } = useSceneContext();
|
const { zoneStore } = useSceneContext();
|
||||||
@@ -15,14 +15,14 @@ function ZoneInstances() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// console.log('zones: ', zones);
|
// console.log('zones: ', zones);
|
||||||
}, [zones])
|
}, [zones]);
|
||||||
|
|
||||||
const allPoints = useMemo(() => {
|
const allPoints = useMemo(() => {
|
||||||
const points: Point[] = [];
|
const points: Point[] = [];
|
||||||
const seenUuids = new Set<string>();
|
const seenUuids = new Set<string>();
|
||||||
|
|
||||||
zones.forEach(zone => {
|
zones.forEach((zone) => {
|
||||||
zone.points.forEach(point => {
|
zone.points.forEach((point) => {
|
||||||
if (!seenUuids.has(point.pointUuid)) {
|
if (!seenUuids.has(point.pointUuid)) {
|
||||||
seenUuids.add(point.pointUuid);
|
seenUuids.add(point.pointUuid);
|
||||||
points.push(point);
|
points.push(point);
|
||||||
@@ -50,7 +50,7 @@ function ZoneInstances() {
|
|||||||
lines.push({
|
lines.push({
|
||||||
start: current,
|
start: current,
|
||||||
end: next,
|
end: next,
|
||||||
key: lineKey
|
key: lineKey,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,9 +61,8 @@ function ZoneInstances() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
{!toggleView && zones.length > 0 && (
|
{!toggleView && zones.length > 0 && (
|
||||||
<mesh name='Zones-Group'>
|
<mesh name="Zones-Group">
|
||||||
{zones.map((zone) => (
|
{zones.map((zone) => (
|
||||||
<ZoneInstance key={zone.zoneUuid} zone={zone} />
|
<ZoneInstance key={zone.zoneUuid} zone={zone} />
|
||||||
))}
|
))}
|
||||||
@@ -71,7 +70,7 @@ function ZoneInstances() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{toggleView && zones.length > 0 && (
|
{toggleView && zones.length > 0 && (
|
||||||
<mesh name='Zones-2D-Group'>
|
<mesh name="Zones-2D-Group">
|
||||||
{zones.map((zone) => (
|
{zones.map((zone) => (
|
||||||
<Zone2DInstance key={zone.zoneUuid} zone={zone} />
|
<Zone2DInstance key={zone.zoneUuid} zone={zone} />
|
||||||
))}
|
))}
|
||||||
@@ -80,14 +79,13 @@ function ZoneInstances() {
|
|||||||
|
|
||||||
{toggleView && (
|
{toggleView && (
|
||||||
<>
|
<>
|
||||||
<group name='Zone-Points-Group'>
|
<group name="Zone-Points-Group">
|
||||||
{allPoints.map((point) => (
|
{allPoints.map((point) => (
|
||||||
<Point key={point.pointUuid} point={point} />
|
<Point key={point.pointUuid} point={point} />
|
||||||
))}
|
))}
|
||||||
</group>
|
</group>
|
||||||
|
|
||||||
<group name='Zone-Lines-Group'>
|
<group name="Zone-Lines-Group">
|
||||||
|
|
||||||
{allLines.map(({ start, end, key }) => (
|
{allLines.map(({ start, end, key }) => (
|
||||||
<Line key={key} points={[start, end]} />
|
<Line key={key} points={[start, end]} />
|
||||||
))}
|
))}
|
||||||
@@ -99,7 +97,7 @@ function ZoneInstances() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={key}>
|
<React.Fragment key={key}>
|
||||||
{toggleView &&
|
{toggleView && (
|
||||||
<Html
|
<Html
|
||||||
key={`${start.pointUuid}_${end.pointUuid}`}
|
key={`${start.pointUuid}_${end.pointUuid}`}
|
||||||
userData={line}
|
userData={line}
|
||||||
@@ -110,23 +108,19 @@ function ZoneInstances() {
|
|||||||
prepend
|
prepend
|
||||||
sprite
|
sprite
|
||||||
>
|
>
|
||||||
<div
|
<div key={key} className={`distance ${key}`}>
|
||||||
key={key}
|
|
||||||
className={`distance ${key}`}
|
|
||||||
>
|
|
||||||
{distance.toFixed(2)} m
|
{distance.toFixed(2)} m
|
||||||
</div>
|
</div>
|
||||||
</Html>
|
</Html>
|
||||||
}
|
)}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
)
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
</group>
|
</group>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ZoneInstances
|
export default ZoneInstances;
|
||||||
|
|||||||
@@ -86,6 +86,10 @@ function ZoneCreator() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tempPoints.length <= 2 && pointIntersects && pointIntersects.object.uuid === tempPoints[0]?.pointUuid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (snappedPosition && !snappedPoint) {
|
if (snappedPosition && !snappedPoint) {
|
||||||
newPoint.position = snappedPosition;
|
newPoint.position = snappedPosition;
|
||||||
}
|
}
|
||||||
@@ -282,7 +286,7 @@ function ZoneCreator() {
|
|||||||
canvasElement.removeEventListener("click", onMouseClick);
|
canvasElement.removeEventListener("click", onMouseClick);
|
||||||
canvasElement.removeEventListener("contextmenu", onContext);
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ const CamMode: React.FC = () => {
|
|||||||
const keyCombination = detectModifierKeys(event);
|
const keyCombination = detectModifierKeys(event);
|
||||||
|
|
||||||
if (keyCombination === "/" && !isTransitioning && !toggleView) {
|
if (keyCombination === "/" && !isTransitioning && !toggleView) {
|
||||||
setIsTransitioning && setIsTransitioning(true);
|
setIsTransitioning(true);
|
||||||
|
|
||||||
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
|
state.controls.mouseButtons.left = CONSTANTS.controlsTransition.leftMouse;
|
||||||
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
|
state.controls.mouseButtons.right = CONSTANTS.controlsTransition.rightMouse;
|
||||||
@@ -55,10 +55,10 @@ const CamMode: React.FC = () => {
|
|||||||
await switchToThirdPerson(state.controls, state.camera);
|
await switchToThirdPerson(state.controls, state.camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsTransitioning && setIsTransitioning(false);
|
setIsTransitioning(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyCombination === 'Shift') {
|
if (keyCombination === "Shift") {
|
||||||
setIsShiftActive(true);
|
setIsShiftActive(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import * as THREE from 'three';
|
import * as THREE from "three";
|
||||||
import { PerspectiveCamera, OrthographicCamera, CameraControls } from '@react-three/drei';
|
import { PerspectiveCamera, OrthographicCamera, CameraControls } from "@react-three/drei";
|
||||||
import { useParams } from "react-router-dom";
|
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 { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||||
import { useToggleView } from "../../../store/builder/store";
|
import { useToggleView } from "../../../store/builder/store";
|
||||||
|
|
||||||
@@ -18,19 +18,21 @@ export default function SwitchView() {
|
|||||||
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
|
(controls as any).mouseButtons.right = CONSTANTS.twoDimension.rightMouse;
|
||||||
} else {
|
} else {
|
||||||
if (!projectId) return;
|
if (!projectId) return;
|
||||||
getCameraApi(projectId).then((data) => {
|
getCameraApi(projectId)
|
||||||
if (data?.position && data?.target) {
|
.then((data) => {
|
||||||
(controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z);
|
if (data?.position && data?.target) {
|
||||||
(controls as CameraControls)?.setTarget(data.target.x, data.target.y, data.target.z);
|
(controls as CameraControls)?.setPosition(data.position.x, data.position.y, data.position.z);
|
||||||
} else {
|
(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)?.setPosition(...CONSTANTS.threeDimension.defaultPosition);
|
||||||
(controls as CameraControls)?.setTarget(...CONSTANTS.threeDimension.defaultTarget);
|
(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) {
|
if (controls) {
|
||||||
(controls as any).mouseButtons.left = CONSTANTS.threeDimension.leftMouse;
|
(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 { CameraControls } from "@react-three/drei";
|
||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect, useState } from "react";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
import * as THREE from "three";
|
import * as THREE from "three";
|
||||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||||
@@ -7,6 +7,7 @@ import { useSocketStore, useToggleView, useResetCamera } from "../../../store/bu
|
|||||||
|
|
||||||
import CamMode from "../camera/camMode";
|
import CamMode from "../camera/camMode";
|
||||||
import SwitchView from "../camera/switchView";
|
import SwitchView from "../camera/switchView";
|
||||||
|
import SyncCam from "../camera/syncCam";
|
||||||
import ContextControls from "./contextControls/contextControls";
|
import ContextControls from "./contextControls/contextControls";
|
||||||
import TransformControl from "./transformControls/transformControls";
|
import TransformControl from "./transformControls/transformControls";
|
||||||
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
||||||
@@ -20,7 +21,6 @@ import { getUserData } from "../../../functions/getUserData";
|
|||||||
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
|
import { getCameraApi } from "../../../services/factoryBuilder/camera/getCameraApi";
|
||||||
import { setCameraApi } from "../../../services/factoryBuilder/camera/setCameraApi";
|
import { setCameraApi } from "../../../services/factoryBuilder/camera/setCameraApi";
|
||||||
import updateCamPosition from "../camera/functions/updateCameraPosition";
|
import updateCamPosition from "../camera/functions/updateCameraPosition";
|
||||||
import SyncCam from "../camera/syncCam";
|
|
||||||
|
|
||||||
export default function Controls() {
|
export default function Controls() {
|
||||||
const controlsRef = useRef<CameraControls>(null);
|
const controlsRef = useRef<CameraControls>(null);
|
||||||
@@ -59,7 +59,12 @@ export default function Controls() {
|
|||||||
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
|
controlsRef.current?.rotateAzimuthTo(CONSTANTS.threeDimension.defaultAzimuth);
|
||||||
|
|
||||||
if (!socket?.connected) {
|
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 {
|
} else {
|
||||||
const camData = {
|
const camData = {
|
||||||
organization,
|
organization,
|
||||||
@@ -143,7 +148,6 @@ export default function Controls() {
|
|||||||
boundaryEnclosesCamera={true}
|
boundaryEnclosesCamera={true}
|
||||||
dollyDragInverted
|
dollyDragInverted
|
||||||
>
|
>
|
||||||
|
|
||||||
<SwitchView />
|
<SwitchView />
|
||||||
|
|
||||||
<CamMode />
|
<CamMode />
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co
|
|||||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||||
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||||
{ name: "right", keys: ["ArrowRight", "d", "D"] },
|
{ 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) {
|
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);
|
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
|
||||||
if (material) {
|
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}`);
|
retrieveLogStatus(material.materialName, `is being picked by ${armBot?.modelName}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
|
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
|
||||||
if (material) {
|
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}`);
|
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) {
|
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);
|
const storageAction = getActionByUuid(selectedProduct.productUuid, actionUuid);
|
||||||
|
|
||||||
@@ -290,7 +302,18 @@ export function useRetrieveHandler() {
|
|||||||
const model = getVehicleById(triggeredModel.modelUuid);
|
const model = getVehicleById(triggeredModel.modelUuid);
|
||||||
if (model && !model.isActive && model.state === "idle" && model.isPicking && model.currentLoad < model.point.action.loadCapacity) {
|
if (model && !model.isActive && model.state === "idle" && model.isPicking && model.currentLoad < model.point.action.loadCapacity) {
|
||||||
if (humanAsset?.animationState?.current === "idle") {
|
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) {
|
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
|
||||||
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
||||||
if (lastMaterial) {
|
if (lastMaterial) {
|
||||||
@@ -305,6 +328,7 @@ export function useRetrieveHandler() {
|
|||||||
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
monitoredHumansRef.current.delete(human.modelUuid);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -312,7 +336,18 @@ export function useRetrieveHandler() {
|
|||||||
const armBot = getArmBotById(triggeredModel.modelUuid);
|
const armBot = getArmBotById(triggeredModel.modelUuid);
|
||||||
if (armBot && !armBot.isActive && armBot.state === "idle" && !armBot.currentAction) {
|
if (armBot && !armBot.isActive && armBot.state === "idle" && !armBot.currentAction) {
|
||||||
if (humanAsset?.animationState?.current === "idle") {
|
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) {
|
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
|
||||||
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
||||||
if (lastMaterial) {
|
if (lastMaterial) {
|
||||||
@@ -327,6 +362,7 @@ export function useRetrieveHandler() {
|
|||||||
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
monitoredHumansRef.current.delete(human.modelUuid);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -334,7 +370,18 @@ export function useRetrieveHandler() {
|
|||||||
const model = getConveyorById(triggeredModel.modelUuid);
|
const model = getConveyorById(triggeredModel.modelUuid);
|
||||||
if (model && !model.isPaused) {
|
if (model && !model.isPaused) {
|
||||||
if (humanAsset?.animationState?.current === "idle") {
|
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) {
|
} else if (humanAsset?.animationState?.current === "pickup" && humanAsset.animationState.isCompleted) {
|
||||||
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
||||||
if (lastMaterial) {
|
if (lastMaterial) {
|
||||||
@@ -349,6 +396,7 @@ export function useRetrieveHandler() {
|
|||||||
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
monitoredHumansRef.current.delete(human.modelUuid);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -385,6 +433,18 @@ export function useRetrieveHandler() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (triggeredModel?.type === "storageUnit") {
|
} 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);
|
const lastMaterial = getLastMaterial(storageUnit.modelUuid);
|
||||||
if (lastMaterial) {
|
if (lastMaterial) {
|
||||||
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
|
const material = createNewMaterial(lastMaterial.materialId, lastMaterial.materialType, storageAction as StorageAction);
|
||||||
@@ -397,6 +457,7 @@ export function useRetrieveHandler() {
|
|||||||
|
|
||||||
retrievalCountRef.current.set(actionUuid, currentCount + 1);
|
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 }),
|
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) => ({
|
export const useToggleView = create<any>((set: any) => ({
|
||||||
toggleView: false,
|
toggleView: false,
|
||||||
setToggleView: (x: any) => set(() => ({ toggleView: x })),
|
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) => ({
|
export const useSelectedItem = create<any>((set: any) => ({
|
||||||
selectedItem: {
|
selectedItem: {
|
||||||
name: "",
|
name: "",
|
||||||
@@ -107,11 +97,6 @@ export const useNavMesh = create<any>((set: any) => ({
|
|||||||
setNavMesh: (x: any) => set({ navMesh: x }),
|
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) => ({
|
export const useCamPosition = create<any>((set: any) => ({
|
||||||
camPosition: { x: undefined, y: undefined, z: undefined },
|
camPosition: { x: undefined, y: undefined, z: undefined },
|
||||||
setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }),
|
setCamPosition: (newCamPosition: any) => set({ camPosition: newCamPosition }),
|
||||||
@@ -177,11 +162,6 @@ export const useResetCamera = create<any>((set: any) => ({
|
|||||||
setResetCamera: (x: any) => set({ resetCamera: x }),
|
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) => ({
|
export const useActiveTool = create<any>((set: any) => ({
|
||||||
activeTool: "cursor",
|
activeTool: "cursor",
|
||||||
setActiveTool: (x: any) => set({ activeTool: x }),
|
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
|
// Define the Asset type
|
||||||
type Asset = {
|
type Asset = {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -350,19 +325,6 @@ export const useResourceManagementId = create<ResourceManagementState>((set) =>
|
|||||||
setResourceManagementId: (id: string) => set({ resourceManagementId: id }),
|
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 {
|
interface ShortcutStore {
|
||||||
showShortcuts: boolean;
|
showShortcuts: boolean;
|
||||||
setShowShortcuts: (value: boolean) => void;
|
setShowShortcuts: (value: boolean) => void;
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { create } from 'zustand';
|
import { create } from "zustand";
|
||||||
import { immer } from 'zustand/middleware/immer';
|
import { immer } from "zustand/middleware/immer";
|
||||||
|
|
||||||
interface VersionStore {
|
interface VersionStore {
|
||||||
versionHistory: VersionHistory;
|
versionHistory: VersionHistory;
|
||||||
selectedVersion: Version | null;
|
selectedVersion: Version | null;
|
||||||
|
viewVersionHistory: boolean;
|
||||||
createNewVersion: boolean;
|
createNewVersion: boolean;
|
||||||
|
|
||||||
setSelectedVersion: (version: Version) => void;
|
setSelectedVersion: (version: Version) => void;
|
||||||
clearSelectedVersion: () => void;
|
clearSelectedVersion: () => void;
|
||||||
|
|
||||||
|
setVersionHistoryVisible: (visibility: boolean) => void;
|
||||||
setCreateNewVersion: (createNewVersion: boolean) => void;
|
setCreateNewVersion: (createNewVersion: boolean) => void;
|
||||||
|
|
||||||
addVersion: (version: Version) => void;
|
addVersion: (version: Version) => void;
|
||||||
@@ -26,6 +28,7 @@ export const createVersionStore = () => {
|
|||||||
immer((set, get) => ({
|
immer((set, get) => ({
|
||||||
versionHistory: [],
|
versionHistory: [],
|
||||||
selectedVersion: null,
|
selectedVersion: null,
|
||||||
|
viewVersionHistory: false,
|
||||||
createNewVersion: false,
|
createNewVersion: false,
|
||||||
|
|
||||||
setSelectedVersion: (version) => {
|
setSelectedVersion: (version) => {
|
||||||
@@ -40,58 +43,64 @@ export const createVersionStore = () => {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
setVersionHistoryVisible: (visibility: boolean) => {
|
||||||
|
set((state) => {
|
||||||
|
state.viewVersionHistory = visibility;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
setCreateNewVersion: (createNewVersion: boolean) => {
|
setCreateNewVersion: (createNewVersion: boolean) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.createNewVersion = createNewVersion;
|
state.createNewVersion = createNewVersion;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
addVersion: (version: Version) => {
|
addVersion: (version: Version) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.versionHistory.unshift(version);
|
state.versionHistory.unshift(version);
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setVersions: (versions: Version[]) => {
|
setVersions: (versions: Version[]) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.versionHistory = versions;
|
state.versionHistory = versions;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
clearVersions: () => {
|
clearVersions: () => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.versionHistory = [];
|
state.versionHistory = [];
|
||||||
state.selectedVersion = null;
|
state.selectedVersion = null;
|
||||||
state.createNewVersion = false
|
state.createNewVersion = false;
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setVersionName: (versionId: string, versionName: string) => {
|
setVersionName: (versionId: string, versionName: string) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const version = state.versionHistory.find((v) => v.versionId === versionId);
|
const version = get().getVersionById(versionId);
|
||||||
if (version) {
|
if (version) {
|
||||||
version.versionName = versionName;
|
version.versionName = versionName;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateVersion: (versionId: string, versionName: string, versionDescription: string) => {
|
updateVersion: (versionId: string, versionName: string, versionDescription: string) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const version = state.versionHistory.find((v) => v.versionId === versionId);
|
const version = get().getVersionById(versionId);
|
||||||
if (version) {
|
if (version) {
|
||||||
version.versionName = versionName;
|
version.versionName = versionName;
|
||||||
version.versionDescription = versionDescription;
|
version.versionDescription = versionDescription;
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getVersionById: (versionId: string) => {
|
getVersionById: (versionId: string) => {
|
||||||
return get().versionHistory.find((v) => {
|
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
|
leftSpeed: -0.1, // Speed of left movement
|
||||||
rightSpeed: 0.1, // Speed of right movement
|
rightSpeed: 0.1, // Speed of right movement
|
||||||
walkSpeed: 1, // Walk speed
|
walkSpeed: 1, // Walk speed
|
||||||
sprintSpeed: 4 // Sprint Speed
|
sprintSpeed: 4, // Sprint Speed
|
||||||
};
|
};
|
||||||
|
|
||||||
export const thirdPersonControls: ThirdPersonControls = {
|
export const thirdPersonControls: ThirdPersonControls = {
|
||||||
@@ -359,7 +359,7 @@ export const roofConfig: RoofConfig = {
|
|||||||
export const aisleConfig: AisleConfig = {
|
export const aisleConfig: AisleConfig = {
|
||||||
width: 0.1, // Width of the aisles
|
width: 0.1, // Width of the aisles
|
||||||
height: 0.01, // Height 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 = {
|
export const zoneConfig: ZoneConfig = {
|
||||||
@@ -384,4 +384,4 @@ export const distanceConfig: DistanceConfig = {
|
|||||||
|
|
||||||
export const undoRedoConfig: undoRedoCount = {
|
export const undoRedoConfig: undoRedoCount = {
|
||||||
undoRedoCount: 50,
|
undoRedoCount: 50,
|
||||||
}
|
};
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/ui/useModuleStore";
|
import useModuleStore, { useSubModuleStore, useThreeDStore } from "../../store/ui/useModuleStore";
|
||||||
import { usePlayerStore, useToggleStore } from "../../store/ui/useUIToggleStore";
|
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 useCameraModeStore, { usePlayButtonStore } from "../../store/ui/usePlayButtonStore";
|
||||||
import { detectModifierKeys } from "./detectModifierKeys";
|
import { detectModifierKeys } from "./detectModifierKeys";
|
||||||
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
|
import { useSelectedZoneStore } from "../../store/visualization/useZoneStore";
|
||||||
@@ -22,7 +33,6 @@ const KeyPressListener: React.FC = () => {
|
|||||||
const { setToolMode } = useToolMode();
|
const { setToolMode } = useToolMode();
|
||||||
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||||
const { toggleView, setToggleView } = useToggleView();
|
const { toggleView, setToggleView } = useToggleView();
|
||||||
const { setAddAction } = useAddAction();
|
|
||||||
const { setActiveTool } = useActiveTool();
|
const { setActiveTool } = useActiveTool();
|
||||||
const { clearSelectedZone } = useSelectedZoneStore();
|
const { clearSelectedZone } = useSelectedZoneStore();
|
||||||
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
const { showShortcuts, setShowShortcuts } = useShortcutStore();
|
||||||
@@ -33,8 +43,7 @@ const KeyPressListener: React.FC = () => {
|
|||||||
const { setViewSceneLabels } = useViewSceneStore();
|
const { setViewSceneLabels } = useViewSceneStore();
|
||||||
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
const { isRenameMode, setIsRenameMode } = useRenameModeStore();
|
||||||
const { selectedFloorAsset, setSelectedWallAsset } = useBuilderStore();
|
const { selectedFloorAsset, setSelectedWallAsset } = useBuilderStore();
|
||||||
const { setCreateNewVersion } = versionStore();
|
const { setCreateNewVersion, setVersionHistoryVisible } = versionStore();
|
||||||
const { setVersionHistoryVisible } = useVersionHistoryVisibleStore();
|
|
||||||
const { setSelectedComment } = useSelectedComment();
|
const { setSelectedComment } = useSelectedComment();
|
||||||
const { setDfxUploaded } = useDfxUpload();
|
const { setDfxUploaded } = useDfxUpload();
|
||||||
const isTextInput = (element: Element | null): boolean => element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element?.getAttribute("contenteditable") === "true";
|
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);
|
setToggleThreeD(toggleTo2D);
|
||||||
if (toggleTo2D) {
|
if (toggleTo2D) {
|
||||||
setSelectedWallAsset(null);
|
setSelectedWallAsset(null);
|
||||||
setAddAction(null);
|
|
||||||
setToggleUI(localStorage.getItem("navBarUiLeft") !== "false", localStorage.getItem("navBarUiRight") !== "false");
|
setToggleUI(localStorage.getItem("navBarUiLeft") !== "false", localStorage.getItem("navBarUiRight") !== "false");
|
||||||
} else {
|
} else {
|
||||||
setToggleUI(false, false);
|
setToggleUI(false, false);
|
||||||
@@ -219,10 +227,6 @@ const KeyPressListener: React.FC = () => {
|
|||||||
setShowShortcuts(!showShortcuts);
|
setShowShortcuts(!showShortcuts);
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (keyCombination === "Ctrl+Shift+P") {
|
|
||||||
// pref
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (keyCombination === "U") {
|
if (keyCombination === "U") {
|
||||||
setViewSceneLabels((prev) => !prev);
|
setViewSceneLabels((prev) => !prev);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user