Merge branch 'main-dev' into dev-contextMenu
This commit is contained in:
@@ -2,289 +2,314 @@ import React, { useState, useRef, useEffect, act } from "react";
|
|||||||
import img from "../../assets/image/image.png";
|
import img from "../../assets/image/image.png";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import { getUserData } from "../../functions/getUserData";
|
import { getUserData } from "../../functions/getUserData";
|
||||||
import { useLoadingProgress, useProjectName, useSocketStore } from "../../store/builder/store";
|
import {
|
||||||
|
useLoadingProgress,
|
||||||
|
useProjectName,
|
||||||
|
useSocketStore,
|
||||||
|
} from "../../store/builder/store";
|
||||||
import { viewProject } from "../../services/dashboard/viewProject";
|
import { viewProject } from "../../services/dashboard/viewProject";
|
||||||
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";
|
||||||
|
import { updateProject } from "../../services/dashboard/updateProject";
|
||||||
|
|
||||||
interface DashBoardCardProps {
|
interface DashBoardCardProps {
|
||||||
projectName: string;
|
projectName: string;
|
||||||
thumbnail: any;
|
thumbnail: any;
|
||||||
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,
|
projectId: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
thumbnail: string,
|
thumbnail: string,
|
||||||
userId?: string
|
userId?: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
handleDuplicateRecentProject?: (
|
handleDuplicateRecentProject?: (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
thumbnail: string
|
thumbnail: string
|
||||||
) => Promise<void>;
|
) => Promise<void>;
|
||||||
active?: string;
|
active?: string;
|
||||||
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
setIsSearchActive?: React.Dispatch<React.SetStateAction<boolean>>;
|
||||||
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<Object>>;
|
setRecentDuplicateData?: React.Dispatch<React.SetStateAction<Object>>;
|
||||||
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<Object>>;
|
setProjectDuplicateData?: React.Dispatch<React.SetStateAction<Object>>;
|
||||||
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
|
setActiveFolder?: React.Dispatch<React.SetStateAction<string>>;
|
||||||
}
|
}
|
||||||
type RelativeTimeFormatUnit = any;
|
type RelativeTimeFormatUnit = any;
|
||||||
|
|
||||||
const DashboardCard: React.FC<DashBoardCardProps> = ({
|
const DashboardCard: React.FC<DashBoardCardProps> = ({
|
||||||
projectName,
|
projectName,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
projectId,
|
projectId,
|
||||||
active,
|
active,
|
||||||
handleDeleteProject,
|
handleDeleteProject,
|
||||||
handleRestoreProject,
|
handleRestoreProject,
|
||||||
handleTrashDeleteProject,
|
handleTrashDeleteProject,
|
||||||
handleDuplicateWorkspaceProject,
|
handleDuplicateWorkspaceProject,
|
||||||
handleDuplicateRecentProject,
|
handleDuplicateRecentProject,
|
||||||
createdAt,
|
createdAt,
|
||||||
createdBy,
|
createdBy,
|
||||||
setRecentDuplicateData,
|
setRecentDuplicateData,
|
||||||
setProjectDuplicateData,
|
setProjectDuplicateData,
|
||||||
setActiveFolder
|
setActiveFolder,
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { setProjectName } = useProjectName();
|
const { setProjectName } = useProjectName();
|
||||||
const { userId, organization, userName } = getUserData();
|
const { userId, organization, userName } = getUserData();
|
||||||
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
const [isKebabOpen, setIsKebabOpen] = useState(false);
|
||||||
const [renameValue, setRenameValue] = useState(projectName);
|
const [renameValue, setRenameValue] = useState(projectName);
|
||||||
const [isRenaming, setIsRenaming] = useState(false);
|
const [isRenaming, setIsRenaming] = useState(false);
|
||||||
const { projectSocket } = useSocketStore();
|
const { projectSocket } = useSocketStore();
|
||||||
const { setLoadingProgress } = useLoadingProgress();
|
const { setLoadingProgress } = useLoadingProgress();
|
||||||
const kebabRef = useRef<HTMLDivElement>(null);
|
const kebabRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const navigateToProject = async (e: any) => {
|
const navigateToProject = async (e: any) => {
|
||||||
console.log('active: ', active);
|
if (active && active == "trash") return;
|
||||||
if (active && active == "trash") return;
|
try {
|
||||||
try {
|
const viewProjects = await viewProject(organization, projectId, userId);
|
||||||
const viewProjects = await viewProject(organization, projectId, userId)
|
|
||||||
console.log('viewProjects: ', viewProjects);
|
|
||||||
console.log('projectName: ', projectName);
|
|
||||||
setLoadingProgress(1)
|
|
||||||
setProjectName(projectName);
|
|
||||||
navigate(`/${projectId}`);
|
|
||||||
} catch {
|
|
||||||
|
|
||||||
|
setLoadingProgress(1);
|
||||||
|
setProjectName(projectName);
|
||||||
|
navigate(`/${projectId}`);
|
||||||
|
} catch {}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOptionClick = async (option: string) => {
|
||||||
|
switch (option) {
|
||||||
|
case "delete":
|
||||||
|
if (handleDeleteProject) {
|
||||||
|
handleDeleteProject(projectId);
|
||||||
|
} else if (handleTrashDeleteProject) {
|
||||||
|
handleTrashDeleteProject(projectId);
|
||||||
}
|
}
|
||||||
};
|
break;
|
||||||
|
case "restore":
|
||||||
const handleOptionClick = async (option: string) => {
|
if (handleRestoreProject) {
|
||||||
switch (option) {
|
await handleRestoreProject(projectId);
|
||||||
case "delete":
|
|
||||||
if (handleDeleteProject) {
|
|
||||||
handleDeleteProject(projectId);
|
|
||||||
} else if (handleTrashDeleteProject) {
|
|
||||||
handleTrashDeleteProject(projectId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "restore":
|
|
||||||
if (handleRestoreProject) {
|
|
||||||
await handleRestoreProject(projectId);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "open in new tab":
|
|
||||||
try {
|
|
||||||
if (active === "shared" && createdBy) {
|
|
||||||
console.log("ihreq");
|
|
||||||
const newTab = await viewProject(organization, projectId, createdBy?._id);
|
|
||||||
console.log('newTab: ', newTab);
|
|
||||||
} else {
|
|
||||||
const newTab = await viewProject(organization, projectId, userId);
|
|
||||||
console.log('newTab: ', newTab);
|
|
||||||
setProjectName(projectName);
|
|
||||||
setIsKebabOpen(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
window.open(`/${projectId}`, "_blank");
|
|
||||||
break;
|
|
||||||
case "rename":
|
|
||||||
setIsRenaming(true);
|
|
||||||
break;
|
|
||||||
case "duplicate":
|
|
||||||
if (handleDuplicateWorkspaceProject) {
|
|
||||||
setProjectDuplicateData &&
|
|
||||||
setProjectDuplicateData({
|
|
||||||
projectId,
|
|
||||||
projectName,
|
|
||||||
thumbnail,
|
|
||||||
});
|
|
||||||
await handleDuplicateWorkspaceProject(projectId, projectName, thumbnail, userId);
|
|
||||||
if (active === "shared" && setActiveFolder) {
|
|
||||||
setActiveFolder("myProjects")
|
|
||||||
}
|
|
||||||
} else if (handleDuplicateRecentProject) {
|
|
||||||
setRecentDuplicateData &&
|
|
||||||
setRecentDuplicateData({
|
|
||||||
projectId,
|
|
||||||
projectName,
|
|
||||||
thumbnail,
|
|
||||||
userId
|
|
||||||
});
|
|
||||||
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
setIsKebabOpen(false);
|
break;
|
||||||
};
|
case "open in new tab":
|
||||||
|
|
||||||
OuterClick({
|
|
||||||
contextClassName: ["kebab-wrapper", "kebab-options-wrapper"],
|
|
||||||
setMenuVisible: () => setIsKebabOpen(false),
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleProjectName = async (projectName: string) => {
|
|
||||||
setRenameValue(projectName);
|
|
||||||
if (!projectId) return;
|
|
||||||
try {
|
try {
|
||||||
const projects = await getAllProjects(userId, organization);
|
if (active === "shared" && createdBy) {
|
||||||
if (!projects || !projects.Projects) return;
|
const newTab = await viewProject(
|
||||||
let projectUuid = projects.Projects.find(
|
organization,
|
||||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
projectId,
|
||||||
|
createdBy?._id
|
||||||
);
|
);
|
||||||
const updateProjects = {
|
} else {
|
||||||
projectId: projectUuid,
|
const newTab = await viewProject(organization, projectId, userId);
|
||||||
organization,
|
|
||||||
userId,
|
|
||||||
projectName,
|
|
||||||
thumbnail: undefined,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (projectSocket) {
|
setProjectName(projectName);
|
||||||
projectSocket.emit("v1:project:update", updateProjects);
|
setIsKebabOpen(false);
|
||||||
}
|
}
|
||||||
} catch (error) { }
|
} catch (error) {}
|
||||||
};
|
window.open(`/${projectId}`, "_blank");
|
||||||
|
break;
|
||||||
function getRelativeTime(dateString: string): string {
|
case "rename":
|
||||||
const date = new Date(dateString);
|
setIsRenaming(true);
|
||||||
const now = new Date();
|
break;
|
||||||
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
|
case "duplicate":
|
||||||
|
if (handleDuplicateWorkspaceProject) {
|
||||||
const intervals: Record<RelativeTimeFormatUnit, number> = {
|
setProjectDuplicateData &&
|
||||||
year: 31536000,
|
setProjectDuplicateData({
|
||||||
month: 2592000,
|
projectId,
|
||||||
week: 604800,
|
projectName,
|
||||||
day: 86400,
|
thumbnail,
|
||||||
hour: 3600,
|
});
|
||||||
minute: 60,
|
await handleDuplicateWorkspaceProject(
|
||||||
second: 1,
|
projectId,
|
||||||
};
|
projectName,
|
||||||
|
thumbnail,
|
||||||
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
userId
|
||||||
|
);
|
||||||
for (const key in intervals) {
|
if (active === "shared" && setActiveFolder) {
|
||||||
const unit = key as RelativeTimeFormatUnit;
|
setActiveFolder("myProjects");
|
||||||
const diff = Math.floor(diffInSeconds / intervals[unit]);
|
}
|
||||||
if (diff >= 1) {
|
} else if (handleDuplicateRecentProject) {
|
||||||
return rtf.format(-diff, unit);
|
setRecentDuplicateData &&
|
||||||
}
|
setRecentDuplicateData({
|
||||||
|
projectId,
|
||||||
|
projectName,
|
||||||
|
thumbnail,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
await handleDuplicateRecentProject(projectId, projectName, thumbnail);
|
||||||
}
|
}
|
||||||
return "just now";
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
setIsKebabOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const kebabOptionsMap: Record<string, string[]> = {
|
OuterClick({
|
||||||
default: ["rename", "delete", "duplicate", "open in new tab"],
|
contextClassName: ["kebab-wrapper", "kebab-options-wrapper"],
|
||||||
trash: ["restore", "delete"],
|
setMenuVisible: () => setIsKebabOpen(false),
|
||||||
shared: ["duplicate", "open in new tab"],
|
});
|
||||||
|
|
||||||
|
const handleProjectName = async (projectName: string) => {
|
||||||
|
setRenameValue(projectName);
|
||||||
|
if (!projectId) return;
|
||||||
|
try {
|
||||||
|
const projects = await getAllProjects(userId, organization);
|
||||||
|
if (!projects || !projects.Projects) return;
|
||||||
|
let projectUuid = projects.Projects.find(
|
||||||
|
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||||
|
);
|
||||||
|
const updateProjects = {
|
||||||
|
projectId: projectUuid?._id,
|
||||||
|
organization,
|
||||||
|
userId,
|
||||||
|
projectName,
|
||||||
|
thumbnail: undefined,
|
||||||
|
};
|
||||||
|
// const updatedProjectName = await updateProject(
|
||||||
|
// projectUuid._id,
|
||||||
|
// userId,
|
||||||
|
// organization,
|
||||||
|
// undefined,
|
||||||
|
// projectName
|
||||||
|
// );
|
||||||
|
if (projectSocket) {
|
||||||
|
projectSocket.emit("v1:project:update", updateProjects);
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRelativeTime(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,
|
||||||
};
|
};
|
||||||
|
|
||||||
const getOptions = () => {
|
const rtf = new Intl.RelativeTimeFormat("en", { numeric: "auto" });
|
||||||
if (active === "trash") return kebabOptionsMap.trash;
|
|
||||||
if (active === "shared") return kebabOptionsMap.shared;
|
|
||||||
if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared;
|
|
||||||
return kebabOptionsMap.default;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
for (const key in intervals) {
|
||||||
<button
|
const unit = key as RelativeTimeFormatUnit;
|
||||||
className="dashboard-card-container"
|
const diff = Math.floor(diffInSeconds / intervals[unit]);
|
||||||
onClick={navigateToProject}
|
if (diff >= 1) {
|
||||||
title={projectName}
|
return rtf.format(-diff, unit);
|
||||||
onMouseLeave={() => setIsKebabOpen(false)}
|
}
|
||||||
|
}
|
||||||
|
return "just now";
|
||||||
|
}
|
||||||
|
|
||||||
|
const kebabOptionsMap: Record<string, string[]> = {
|
||||||
|
default: ["rename", "delete", "duplicate", "open in new tab"],
|
||||||
|
trash: ["restore", "delete"],
|
||||||
|
shared: ["duplicate", "open in new tab"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const getOptions = () => {
|
||||||
|
if (active === "trash") return kebabOptionsMap.trash;
|
||||||
|
if (active === "shared") return kebabOptionsMap.shared;
|
||||||
|
if (createdBy && createdBy?._id !== userId) return kebabOptionsMap.shared;
|
||||||
|
return kebabOptionsMap.default;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
className="dashboard-card-container"
|
||||||
|
onClick={navigateToProject}
|
||||||
|
title={projectName}
|
||||||
|
onMouseLeave={() => setIsKebabOpen(false)}
|
||||||
|
>
|
||||||
|
<div className="dashboard-card-wrapper">
|
||||||
|
<div className="preview-container">
|
||||||
|
{thumbnail ? (
|
||||||
|
<img src={thumbnail} alt="" />
|
||||||
|
) : (
|
||||||
|
<img src={img} alt="" />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="project-details-container"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<div className="dashboard-card-wrapper">
|
<div className="project-details">
|
||||||
<div className="preview-container">
|
{isRenaming ? (
|
||||||
{thumbnail ? <img src={thumbnail} alt="" /> : <img src={img} alt="" />}
|
<input
|
||||||
</div>
|
value={renameValue}
|
||||||
<div className="project-details-container" onClick={(e) => { e.stopPropagation() }}>
|
onChange={(e) => {
|
||||||
<div className="project-details">
|
e.stopPropagation();
|
||||||
{isRenaming ? (
|
handleProjectName(e.target.value);
|
||||||
<input
|
}}
|
||||||
value={renameValue}
|
onBlur={() => {
|
||||||
onChange={(e) => {
|
setIsRenaming(false);
|
||||||
e.stopPropagation();
|
setProjectName(renameValue);
|
||||||
handleProjectName(e.target.value);
|
}}
|
||||||
}}
|
onKeyDown={(e) => {
|
||||||
onBlur={() => {
|
if (e.key === "Enter") {
|
||||||
setIsRenaming(false);
|
setProjectName(renameValue);
|
||||||
setProjectName(renameValue);
|
setIsRenaming(false);
|
||||||
}}
|
}
|
||||||
onKeyDown={(e) => {
|
}}
|
||||||
if (e.key === "Enter") {
|
autoFocus
|
||||||
setProjectName(renameValue);
|
/>
|
||||||
setIsRenaming(false);
|
) : (
|
||||||
}
|
<span>{renameValue}</span>
|
||||||
}}
|
|
||||||
autoFocus
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span>{renameValue}</span>
|
|
||||||
)}
|
|
||||||
{createdAt && (
|
|
||||||
<div className="project-data">
|
|
||||||
{active && active == "trash" ? `Trashed by you` : `Edited `}{" "}
|
|
||||||
{getRelativeTime(createdAt)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="users-list-container" ref={kebabRef}>
|
|
||||||
<div className="user-profile">
|
|
||||||
{(!createdBy) ? userName ? userName?.charAt(0).toUpperCase() : "A" : createdBy?.userName?.charAt(0).toUpperCase()}
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
className="kebab-wrapper"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setIsKebabOpen((prev) => !prev);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<KebabIcon />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isKebabOpen && (
|
|
||||||
<div className="kebab-options-wrapper">
|
|
||||||
{getOptions().map((option) => (
|
|
||||||
<button
|
|
||||||
key={option}
|
|
||||||
className="option"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
handleOptionClick(option);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{option}
|
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</button>
|
{createdAt && (
|
||||||
);
|
<div className="project-data">
|
||||||
|
{active && active == "trash" ? `Trashed by you` : `Edited `}{" "}
|
||||||
|
{getRelativeTime(createdAt)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="users-list-container" ref={kebabRef}>
|
||||||
|
<div className="user-profile">
|
||||||
|
{!createdBy
|
||||||
|
? userName
|
||||||
|
? userName?.charAt(0).toUpperCase()
|
||||||
|
: "A"
|
||||||
|
: createdBy?.userName?.charAt(0).toUpperCase()}
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
className="kebab-wrapper"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
setIsKebabOpen((prev) => !prev);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<KebabIcon />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{isKebabOpen && (
|
||||||
|
<div className="kebab-options-wrapper">
|
||||||
|
{getOptions().map((option) => (
|
||||||
|
<button
|
||||||
|
key={option}
|
||||||
|
className="option"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
handleOptionClick(option);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{option}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DashboardCard;
|
export default DashboardCard;
|
||||||
@@ -8,15 +8,16 @@ import { recentlyViewed } from "../../services/dashboard/recentlyViewed";
|
|||||||
import { searchProject } from "../../services/dashboard/searchProjects";
|
import { searchProject } from "../../services/dashboard/searchProjects";
|
||||||
import { deleteProject } from "../../services/dashboard/deleteProject";
|
import { deleteProject } from "../../services/dashboard/deleteProject";
|
||||||
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
||||||
|
import { generateUniqueId } from "../../functions/generateUniqueId";
|
||||||
|
|
||||||
interface Project {
|
interface Project {
|
||||||
_id: string;
|
_id: string;
|
||||||
projectName: string;
|
projectName: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
createdBy: { _id: string, userName: string };
|
createdBy: { _id: string; userName: string };
|
||||||
projectUuid?: string;
|
projectUuid?: string;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
isViewed?: string
|
isViewed?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface RecentProjectsData {
|
interface RecentProjectsData {
|
||||||
@@ -25,12 +26,12 @@ interface RecentProjectsData {
|
|||||||
|
|
||||||
const DashboardHome: React.FC = () => {
|
const DashboardHome: React.FC = () => {
|
||||||
const [recentProjects, setRecentProjects] = useState<RecentProjectsData>({});
|
const [recentProjects, setRecentProjects] = useState<RecentProjectsData>({});
|
||||||
|
|
||||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const { projectSocket } = useSocketStore();
|
const { projectSocket } = useSocketStore();
|
||||||
const [recentDuplicateData, setRecentDuplicateData] = useState<Object>({});
|
const [recentDuplicateData, setRecentDuplicateData] = useState<Object>({});
|
||||||
|
|
||||||
|
|
||||||
const fetchRecentProjects = async () => {
|
const fetchRecentProjects = async () => {
|
||||||
try {
|
try {
|
||||||
const projects = await recentlyViewed(organization, userId);
|
const projects = await recentlyViewed(organization, userId);
|
||||||
@@ -38,9 +39,7 @@ const DashboardHome: React.FC = () => {
|
|||||||
if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) {
|
if (JSON.stringify(projects) !== JSON.stringify(recentProjects)) {
|
||||||
setRecentProjects(projects);
|
setRecentProjects(projects);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
console.error("Error fetching recent projects:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRecentProjectSearch = async (inputValue: string) => {
|
const handleRecentProjectSearch = async (inputValue: string) => {
|
||||||
@@ -65,7 +64,7 @@ const DashboardHome: React.FC = () => {
|
|||||||
// userId,
|
// userId,
|
||||||
// organization
|
// organization
|
||||||
// );
|
// );
|
||||||
// console.log('deletedProject: ', deletedProject);
|
//
|
||||||
|
|
||||||
//socket for delete Project
|
//socket for delete Project
|
||||||
const deleteProject = {
|
const deleteProject = {
|
||||||
@@ -91,9 +90,7 @@ const DashboardHome: React.FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
setIsSearchActive(false);
|
setIsSearchActive(false);
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
console.error("Error deleting project:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicateRecentProject = async (
|
const handleDuplicateRecentProject = async (
|
||||||
@@ -105,7 +102,8 @@ const DashboardHome: React.FC = () => {
|
|||||||
userId,
|
userId,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
organization,
|
organization,
|
||||||
projectUuid: projectId,
|
projectUuid: generateUniqueId(),
|
||||||
|
refProjectID: projectId,
|
||||||
projectName,
|
projectName,
|
||||||
};
|
};
|
||||||
projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData);
|
projectSocket.emit("v1:project:Duplicate", duplicateRecentProjectData);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { deleteProject } from "../../services/dashboard/deleteProject";
|
|||||||
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
import ProjectSocketRes from "./socket/projectSocketRes.dev";
|
||||||
import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject";
|
import { sharedWithMeProjects } from "../../services/dashboard/sharedWithMeProject";
|
||||||
import { duplicateProject } from "../../services/dashboard/duplicateProject";
|
import { duplicateProject } from "../../services/dashboard/duplicateProject";
|
||||||
|
import { generateUniqueId } from "../../functions/generateUniqueId";
|
||||||
|
|
||||||
interface Project {
|
interface Project {
|
||||||
_id: string;
|
_id: string;
|
||||||
@@ -27,7 +28,7 @@ const DashboardProjects: React.FC = () => {
|
|||||||
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>(
|
const [workspaceProjects, setWorkspaceProjects] = useState<WorkspaceProjects>(
|
||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
const [sharedwithMeProject, setSharedWithMeProjects] = useState<any>([])
|
const [sharedwithMeProject, setSharedWithMeProjects] = useState<any>([]);
|
||||||
const [projectDuplicateData, setProjectDuplicateData] = useState<Object>({});
|
const [projectDuplicateData, setProjectDuplicateData] = useState<Object>({});
|
||||||
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
const [isSearchActive, setIsSearchActive] = useState<boolean>(false);
|
||||||
const [activeFolder, setActiveFolder] = useState<string>("myProjects");
|
const [activeFolder, setActiveFolder] = useState<string>("myProjects");
|
||||||
@@ -41,7 +42,11 @@ const DashboardProjects: React.FC = () => {
|
|||||||
}
|
}
|
||||||
if (!setWorkspaceProjects || !setIsSearchActive) return;
|
if (!setWorkspaceProjects || !setIsSearchActive) return;
|
||||||
|
|
||||||
const searchedProject = await searchProject(organization, userId, inputValue);
|
const searchedProject = await searchProject(
|
||||||
|
organization,
|
||||||
|
userId,
|
||||||
|
inputValue
|
||||||
|
);
|
||||||
setIsSearchActive(true);
|
setIsSearchActive(true);
|
||||||
setWorkspaceProjects(searchedProject.message ? {} : searchedProject);
|
setWorkspaceProjects(searchedProject.message ? {} : searchedProject);
|
||||||
};
|
};
|
||||||
@@ -49,14 +54,13 @@ const DashboardProjects: React.FC = () => {
|
|||||||
const fetchAllProjects = async () => {
|
const fetchAllProjects = async () => {
|
||||||
try {
|
try {
|
||||||
const projects = await getAllProjects(userId, organization);
|
const projects = await getAllProjects(userId, organization);
|
||||||
|
|
||||||
if (!projects || !projects.Projects) return;
|
if (!projects || !projects.Projects) return;
|
||||||
|
|
||||||
if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
|
if (JSON.stringify(projects) !== JSON.stringify(workspaceProjects)) {
|
||||||
setWorkspaceProjects(projects);
|
setWorkspaceProjects(projects);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
console.error("Error fetching projects:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteProject = async (projectId: any) => {
|
const handleDeleteProject = async (projectId: any) => {
|
||||||
@@ -66,7 +70,7 @@ const DashboardProjects: React.FC = () => {
|
|||||||
// userId,
|
// userId,
|
||||||
// organization
|
// organization
|
||||||
// );
|
// );
|
||||||
// console.log('deletedProject: ', deletedProject);
|
//
|
||||||
const deleteProjects = {
|
const deleteProjects = {
|
||||||
projectId,
|
projectId,
|
||||||
organization,
|
organization,
|
||||||
@@ -77,7 +81,6 @@ const DashboardProjects: React.FC = () => {
|
|||||||
if (projectSocket) {
|
if (projectSocket) {
|
||||||
projectSocket.emit("v1:project:delete", deleteProjects);
|
projectSocket.emit("v1:project:delete", deleteProjects);
|
||||||
} else {
|
} else {
|
||||||
console.error("Socket is not connected.");
|
|
||||||
}
|
}
|
||||||
setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => {
|
setWorkspaceProjects((prevDiscardedProjects: WorkspaceProjects) => {
|
||||||
if (!Array.isArray(prevDiscardedProjects?.Projects)) {
|
if (!Array.isArray(prevDiscardedProjects?.Projects)) {
|
||||||
@@ -92,22 +95,28 @@ const DashboardProjects: React.FC = () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
setIsSearchActive(false);
|
setIsSearchActive(false);
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
console.error("Error deleting project:", error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDuplicateWorkspaceProject = async (
|
const handleDuplicateWorkspaceProject = async (
|
||||||
projectId: string,
|
projectId: string,
|
||||||
projectName: string,
|
projectName: string,
|
||||||
thumbnail: string,
|
thumbnail: string
|
||||||
) => {
|
) => {
|
||||||
|
// const duplicatedProject = await duplicateProject(
|
||||||
|
// projectId,
|
||||||
|
// generateUniqueId(),
|
||||||
|
// thumbnail,
|
||||||
|
// projectName
|
||||||
|
// );
|
||||||
|
// console.log("duplicatedProject: ", duplicatedProject);
|
||||||
|
|
||||||
const duplicateProjectData = {
|
const duplicateProjectData = {
|
||||||
userId,
|
userId,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
organization,
|
organization,
|
||||||
projectUuid: projectId,
|
projectUuid: generateUniqueId(),
|
||||||
|
refProjectID: projectId,
|
||||||
projectName,
|
projectName,
|
||||||
};
|
};
|
||||||
projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
|
projectSocket.emit("v1:project:Duplicate", duplicateProjectData);
|
||||||
@@ -153,16 +162,12 @@ const DashboardProjects: React.FC = () => {
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const sharedProject = async () => {
|
const sharedProject = async () => {
|
||||||
try {
|
try {
|
||||||
const sharedWithMe = await sharedWithMeProjects();
|
const sharedWithMe = await sharedWithMeProjects();
|
||||||
setSharedWithMeProjects(sharedWithMe)
|
setSharedWithMeProjects(sharedWithMe);
|
||||||
} catch {
|
} catch {}
|
||||||
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isSearchActive) {
|
if (!isSearchActive) {
|
||||||
@@ -172,10 +177,9 @@ const DashboardProjects: React.FC = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (activeFolder === "shared") {
|
if (activeFolder === "shared") {
|
||||||
sharedProject()
|
sharedProject();
|
||||||
}
|
}
|
||||||
}, [activeFolder])
|
}, [activeFolder]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="dashboard-home-container">
|
<div className="dashboard-home-container">
|
||||||
@@ -184,7 +188,10 @@ const DashboardProjects: React.FC = () => {
|
|||||||
handleProjectsSearch={handleProjectsSearch}
|
handleProjectsSearch={handleProjectsSearch}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="dashboard-container" style={{ height: "calc(100% - 87px)" }}>
|
<div
|
||||||
|
className="dashboard-container"
|
||||||
|
style={{ height: "calc(100% - 87px)" }}
|
||||||
|
>
|
||||||
<div className="header-wrapper" style={{ display: "flex", gap: "7px" }}>
|
<div className="header-wrapper" style={{ display: "flex", gap: "7px" }}>
|
||||||
<button
|
<button
|
||||||
className={`header ${activeFolder === "myProjects" && "active"}`}
|
className={`header ${activeFolder === "myProjects" && "active"}`}
|
||||||
@@ -199,19 +206,22 @@ const DashboardProjects: React.FC = () => {
|
|||||||
Shared with me
|
Shared with me
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="cards-container">{activeFolder == "myProjects" ? renderProjects() : renderSharedProjects()}</div>
|
<div className="cards-container">
|
||||||
|
{activeFolder == "myProjects"
|
||||||
|
? renderProjects()
|
||||||
|
: renderSharedProjects()}
|
||||||
|
</div>
|
||||||
|
|
||||||
{projectDuplicateData && Object.keys(projectDuplicateData).length > 0 && (
|
{projectDuplicateData &&
|
||||||
<ProjectSocketRes
|
Object.keys(projectDuplicateData).length > 0 && (
|
||||||
setIsSearchActive={setIsSearchActive}
|
<ProjectSocketRes
|
||||||
setWorkspaceProjects={setWorkspaceProjects}
|
setIsSearchActive={setIsSearchActive}
|
||||||
/>
|
setWorkspaceProjects={setWorkspaceProjects}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DashboardProjects;
|
export default DashboardProjects;
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import { useProductContext } from "../../../../../modules/simulation/products/pr
|
|||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useVersionContext } from "../../../../../modules/builder/version/versionContext";
|
import { useVersionContext } from "../../../../../modules/builder/version/versionContext";
|
||||||
import { useSceneContext } from "../../../../../modules/scene/sceneContext";
|
import { useSceneContext } from "../../../../../modules/scene/sceneContext";
|
||||||
|
import CraneMechanics from "./mechanics/craneMechanics";
|
||||||
|
|
||||||
const EventProperties: React.FC = () => {
|
const EventProperties: React.FC = () => {
|
||||||
const { selectedEventData } = useSelectedEventData();
|
const { selectedEventData } = useSelectedEventData();
|
||||||
@@ -63,6 +64,8 @@ const EventProperties: React.FC = () => {
|
|||||||
return "storageUnit";
|
return "storageUnit";
|
||||||
case "human":
|
case "human":
|
||||||
return "human";
|
return "human";
|
||||||
|
case "crane":
|
||||||
|
return "crane";
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -83,6 +86,7 @@ const EventProperties: React.FC = () => {
|
|||||||
{assetType === "machine" && <MachineMechanics />}
|
{assetType === "machine" && <MachineMechanics />}
|
||||||
{assetType === "storageUnit" && <StorageMechanics />}
|
{assetType === "storageUnit" && <StorageMechanics />}
|
||||||
{assetType === "human" && <HumanMechanics />}
|
{assetType === "human" && <HumanMechanics />}
|
||||||
|
{assetType === "crane" && <CraneMechanics />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!currentEventData && selectedEventSphere && (
|
{!currentEventData && selectedEventSphere && (
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import InputWithDropDown from "../../../../../ui/inputs/InputWithDropDown";
|
||||||
|
|
||||||
|
interface WorkerActionProps {
|
||||||
|
loadCount: {
|
||||||
|
value: number;
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
step: number;
|
||||||
|
defaultValue: string,
|
||||||
|
disabled: false,
|
||||||
|
onChange: (value: number) => void;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkerAction: React.FC<WorkerActionProps> = ({
|
||||||
|
loadCount
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<div className="pillarJib-action-container">
|
||||||
|
{loadCount && (
|
||||||
|
<InputWithDropDown
|
||||||
|
label="Max Load Count"
|
||||||
|
value={loadCount.value.toString()}
|
||||||
|
min={loadCount.min}
|
||||||
|
max={loadCount.max}
|
||||||
|
disabled={loadCount.disabled}
|
||||||
|
defaultValue={loadCount.defaultValue}
|
||||||
|
step={loadCount.step}
|
||||||
|
activeOption="unit"
|
||||||
|
onClick={() => { }}
|
||||||
|
onChange={(value) => loadCount.onChange(parseInt(value))}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WorkerAction;
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { MathUtils } from "three";
|
||||||
|
import RenameInput from "../../../../../ui/inputs/RenameInput";
|
||||||
|
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||||
|
import Trigger from "../trigger/Trigger";
|
||||||
|
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||||
|
import ActionsList from "../components/ActionsList";
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
|
import { useProductContext } from "../../../../../../modules/simulation/products/productContext";
|
||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useVersionContext } from "../../../../../../modules/builder/version/versionContext";
|
||||||
|
import { useSceneContext } from "../../../../../../modules/scene/sceneContext";
|
||||||
|
|
||||||
|
function CraneMechanics() {
|
||||||
|
const [activeOption, setActiveOption] = useState<"pickAndDrop">("pickAndDrop");
|
||||||
|
const [selectedPointData, setSelectedPointData] = useState<CranePointSchema | undefined>();
|
||||||
|
|
||||||
|
const { selectedEventData } = useSelectedEventData();
|
||||||
|
const { productStore } = useSceneContext();
|
||||||
|
const { getPointByUuid, updateAction, addAction, removeAction } = productStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
const { selectedAction, setSelectedAction, clearSelectedAction } = useSelectedAction();
|
||||||
|
const { selectedVersionStore } = useVersionContext();
|
||||||
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
const { projectId } = useParams();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (selectedEventData) {
|
||||||
|
const point = getPointByUuid(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
selectedEventData.data.modelUuid,
|
||||||
|
selectedEventData.selectedPoint
|
||||||
|
) as CranePointSchema | undefined;
|
||||||
|
|
||||||
|
if (point?.actions) {
|
||||||
|
setSelectedPointData(point);
|
||||||
|
|
||||||
|
if (point.actions.length > 0) {
|
||||||
|
const firstAction = point.actions[0];
|
||||||
|
setActiveOption(firstAction.actionType);
|
||||||
|
setSelectedAction(firstAction.actionUuid, firstAction.actionName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
clearSelectedAction();
|
||||||
|
}
|
||||||
|
}, [selectedEventData, selectedProduct]);
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productUuid: string,
|
||||||
|
projectId: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName,
|
||||||
|
productUuid,
|
||||||
|
projectId,
|
||||||
|
eventDatas: eventData,
|
||||||
|
versionId: selectedVersion?.versionId || "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRenameAction = (newName: string) => {
|
||||||
|
if (!selectedAction.actionId || !selectedPointData) return;
|
||||||
|
|
||||||
|
const event = updateAction(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
selectedAction.actionId,
|
||||||
|
{ actionName: newName }
|
||||||
|
);
|
||||||
|
|
||||||
|
const updatedActions = selectedPointData.actions.map(action =>
|
||||||
|
action.actionUuid === selectedAction.actionId
|
||||||
|
? { ...action, actionName: newName }
|
||||||
|
: action
|
||||||
|
);
|
||||||
|
|
||||||
|
setSelectedPointData({
|
||||||
|
...selectedPointData,
|
||||||
|
actions: updatedActions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddAction = () => {
|
||||||
|
if (!selectedEventData || !selectedPointData) return;
|
||||||
|
|
||||||
|
const newAction = {
|
||||||
|
actionUuid: MathUtils.generateUUID(),
|
||||||
|
actionName: `Action ${selectedPointData.actions.length + 1}`,
|
||||||
|
actionType: "pickAndDrop" as const,
|
||||||
|
maxPickUpCount: 1,
|
||||||
|
triggers: [] as TriggerSchema[],
|
||||||
|
};
|
||||||
|
|
||||||
|
const event = addAction(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
selectedEventData.data.modelUuid,
|
||||||
|
selectedEventData.selectedPoint,
|
||||||
|
newAction
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedPointData({
|
||||||
|
...selectedPointData,
|
||||||
|
actions: [...selectedPointData.actions, newAction],
|
||||||
|
});
|
||||||
|
setSelectedAction(newAction.actionUuid, newAction.actionName);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteAction = (actionUuid: string) => {
|
||||||
|
if (!selectedPointData) return;
|
||||||
|
|
||||||
|
const event = removeAction(
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
actionUuid
|
||||||
|
);
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = selectedPointData.actions.findIndex(a => a.actionUuid === actionUuid);
|
||||||
|
const newActions = selectedPointData.actions.filter(a => a.actionUuid !== actionUuid);
|
||||||
|
|
||||||
|
setSelectedPointData({
|
||||||
|
...selectedPointData,
|
||||||
|
actions: newActions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (selectedAction.actionId === actionUuid) {
|
||||||
|
const nextAction = newActions[index] || newActions[index - 1];
|
||||||
|
if (nextAction) {
|
||||||
|
setSelectedAction(nextAction.actionUuid, nextAction.actionName);
|
||||||
|
} else {
|
||||||
|
clearSelectedAction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const availableActions = {
|
||||||
|
defaultOption: "pickAndDrop",
|
||||||
|
options: ["pickAndDrop"],
|
||||||
|
};
|
||||||
|
|
||||||
|
const currentAction = selectedPointData?.actions.find(a => a.actionUuid === selectedAction.actionId);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section>
|
||||||
|
<ActionsList
|
||||||
|
selectedPointData={selectedPointData}
|
||||||
|
multipleAction
|
||||||
|
handleAddAction={handleAddAction}
|
||||||
|
handleDeleteAction={handleDeleteAction}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{selectedAction.actionId && currentAction && (
|
||||||
|
<div className="selected-actions-details">
|
||||||
|
<div className="selected-actions-header">
|
||||||
|
<RenameInput
|
||||||
|
value={selectedAction.actionName || ""}
|
||||||
|
canEdit={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="selected-actions-list">
|
||||||
|
<LabledDropdown
|
||||||
|
defaultOption={activeOption}
|
||||||
|
options={availableActions.options}
|
||||||
|
onSelect={() => { }}
|
||||||
|
disabled={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="tirgger">
|
||||||
|
<Trigger
|
||||||
|
selectedPointData={selectedPointData as any}
|
||||||
|
type={"RoboticArm"}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CraneMechanics
|
||||||
@@ -6,8 +6,8 @@ import RenameInput from "../../../../../ui/inputs/RenameInput";
|
|||||||
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
import LabledDropdown from "../../../../../ui/inputs/LabledDropdown";
|
||||||
import Trigger from "../trigger/Trigger";
|
import Trigger from "../trigger/Trigger";
|
||||||
import ActionsList from "../components/ActionsList";
|
import ActionsList from "../components/ActionsList";
|
||||||
import WorkerAction from "../actions/workerAction";
|
import WorkerAction from "../actions/WorkerAction";
|
||||||
import AssemblyAction from "../actions/assemblyAction";
|
import AssemblyAction from "../actions/AssemblyAction";
|
||||||
|
|
||||||
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
import { useSelectedEventData, useSelectedAction } from "../../../../../../store/simulation/useSimulationStore";
|
||||||
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
import { upsertProductOrEventApi } from "../../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
|
|||||||
@@ -10,100 +10,101 @@ import { updateProject } from "../../services/dashboard/updateProject";
|
|||||||
import { getUserData } from "../../functions/getUserData";
|
import { getUserData } from "../../functions/getUserData";
|
||||||
|
|
||||||
const FileMenu: React.FC = () => {
|
const FileMenu: React.FC = () => {
|
||||||
const [openMenu, setOpenMenu] = useState(false);
|
const [openMenu, setOpenMenu] = useState(false);
|
||||||
const containerRef = useRef<HTMLButtonElement>(null);
|
const containerRef = useRef<HTMLButtonElement>(null);
|
||||||
let clickTimeout: NodeJS.Timeout | null = null;
|
let clickTimeout: NodeJS.Timeout | null = null;
|
||||||
const { projectName, setProjectName } = useProjectName();
|
const { projectName, setProjectName } = useProjectName();
|
||||||
const { dashBoardSocket } = useSocketStore();
|
const { dashBoardSocket } = useSocketStore();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { userId, organization, email } = getUserData();
|
const { userId, organization, email } = getUserData();
|
||||||
|
|
||||||
const handleClick = () => {
|
const handleClick = () => {
|
||||||
if (clickTimeout) return;
|
if (clickTimeout) return;
|
||||||
setOpenMenu((prev) => !prev);
|
setOpenMenu((prev) => !prev);
|
||||||
clickTimeout = setTimeout(() => {
|
clickTimeout = setTimeout(() => {
|
||||||
clickTimeout = null;
|
clickTimeout = null;
|
||||||
}, 800);
|
}, 800);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (
|
||||||
|
containerRef.current &&
|
||||||
|
!containerRef.current.contains(event.target as Node)
|
||||||
|
) {
|
||||||
|
setOpenMenu(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
document.addEventListener("mousedown", handleClickOutside);
|
||||||
const handleClickOutside = (event: MouseEvent) => {
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||||
if (
|
}, []);
|
||||||
containerRef.current &&
|
|
||||||
!containerRef.current.contains(event.target as Node)
|
|
||||||
) {
|
|
||||||
setOpenMenu(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener("mousedown", handleClickOutside);
|
const handleProjectRename = async (projectName: string) => {
|
||||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
setProjectName(projectName);
|
||||||
}, []);
|
if (!projectId) return;
|
||||||
|
|
||||||
const handleProjectRename = async (projectName: string) => {
|
// localStorage.setItem("projectName", newName);
|
||||||
setProjectName(projectName);
|
|
||||||
if (!projectId) return
|
|
||||||
|
|
||||||
// localStorage.setItem("projectName", newName);
|
try {
|
||||||
|
if (!email || !userId) return;
|
||||||
|
|
||||||
try {
|
const projects = await getAllProjects(userId, organization);
|
||||||
|
if (!projects || !projects.Projects) return;
|
||||||
|
// console.log('projects: ', projects);
|
||||||
|
let projectUuid = projects.Projects.find(
|
||||||
|
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||||
|
);
|
||||||
|
|
||||||
if (!email || !userId) return;
|
const updateProjects = {
|
||||||
|
projectId: projectUuid?._id,
|
||||||
|
organization,
|
||||||
|
userId,
|
||||||
|
projectName,
|
||||||
|
thumbnail: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
const projects = await getAllProjects(userId, organization);
|
// if (dashBoardSocket) {
|
||||||
if (!projects || !projects.Projects) return;
|
// const handleResponse = (data: any) => {
|
||||||
// console.log('projects: ', projects);
|
// console.log('Project update response:', data);
|
||||||
let projectUuid = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId)
|
// dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up
|
||||||
|
// };
|
||||||
|
// dashBoardSocket.on("v1-project:response:update", handleResponse);
|
||||||
|
// dashBoardSocket.emit("v1:project:update", updateProjects);
|
||||||
|
// }
|
||||||
|
|
||||||
const updateProjects = {
|
//API for projects rename
|
||||||
projectId: projectUuid,
|
|
||||||
organization,
|
|
||||||
userId,
|
|
||||||
projectName,
|
|
||||||
thumbnail: undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (dashBoardSocket) {
|
const updatedProjectName = await updateProject(
|
||||||
// const handleResponse = (data: any) => {
|
projectId,
|
||||||
// console.log('Project update response:', data);
|
userId,
|
||||||
// dashBoardSocket.off("v1-project:response:update", handleResponse); // Clean up
|
organization,
|
||||||
// };
|
undefined,
|
||||||
// dashBoardSocket.on("v1-project:response:update", handleResponse);
|
projectName
|
||||||
// dashBoardSocket.emit("v1:project:update", updateProjects);
|
);
|
||||||
// }
|
} catch (error) {
|
||||||
|
console.error("Error updating project name:", error);
|
||||||
//API for projects rename
|
}
|
||||||
const updatedProjectName = await updateProject(
|
};
|
||||||
projectId,
|
return (
|
||||||
userId,
|
<button
|
||||||
organization,
|
id="project-dropdown-button"
|
||||||
undefined,
|
className="project-dropdowm-container"
|
||||||
projectName
|
ref={containerRef}
|
||||||
);
|
onClick={handleClick}
|
||||||
//
|
>
|
||||||
} catch (error) {
|
<div className="project-name">
|
||||||
console.error("Error updating project name:", error);
|
<div className="icon">
|
||||||
}
|
<ProjectIcon />
|
||||||
};
|
</div>
|
||||||
return (
|
<RenameInput value={projectName} onRename={handleProjectRename} />
|
||||||
<button
|
</div>
|
||||||
id="project-dropdown-button"
|
<div className="more-options-button">
|
||||||
className="project-dropdowm-container"
|
<ArrowIcon />
|
||||||
ref={containerRef}
|
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
||||||
onClick={handleClick}
|
</div>
|
||||||
>
|
</button>
|
||||||
<div className="project-name">
|
);
|
||||||
<div className="icon">
|
|
||||||
<ProjectIcon />
|
|
||||||
</div>
|
|
||||||
<RenameInput value={projectName} onRename={handleProjectRename} />
|
|
||||||
</div>
|
|
||||||
<div className="more-options-button">
|
|
||||||
<ArrowIcon />
|
|
||||||
{openMenu && <MenuBar setOpenMenu={setOpenMenu} />}
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FileMenu;
|
export default FileMenu;
|
||||||
|
|||||||
@@ -24,17 +24,17 @@ export function useModelEventHandlers({
|
|||||||
boundingBox: THREE.Box3 | null,
|
boundingBox: THREE.Box3 | null,
|
||||||
groupRef: React.RefObject<THREE.Group>,
|
groupRef: React.RefObject<THREE.Group>,
|
||||||
}) {
|
}) {
|
||||||
const { controls, gl } = useThree();
|
const { controls, gl, camera } = useThree();
|
||||||
const { activeTool } = useActiveTool();
|
const { activeTool } = useActiveTool();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
const { subModule } = useSubModuleStore();
|
const { subModule } = useSubModuleStore();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { eventStore, productStore, assetStore } = useSceneContext();
|
const { eventStore, productStore, assetStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
const { removeAsset } = assetStore();
|
const { removeAsset } = assetStore();
|
||||||
const { removeEvent } = eventStore();
|
const { removeEvent, getEventByModelUuid } = eventStore();
|
||||||
const { getIsEventInProduct, addPoint, deleteEvent } = productStore();
|
const { getIsEventInProduct, addPoint, deleteEvent } = productStore();
|
||||||
const { getEventByModelUuid } = eventStore();
|
|
||||||
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
const { setSelectedAsset, clearSelectedAsset } = useSelectedAsset();
|
||||||
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
const { deletableFloorItem, setDeletableFloorItem } = useDeletableFloorItem();
|
||||||
const { setSelectedFloorItem } = useSelectedFloorItem();
|
const { setSelectedFloorItem } = useSelectedFloorItem();
|
||||||
@@ -68,25 +68,47 @@ export function useModelEventHandlers({
|
|||||||
|
|
||||||
const handleDblClick = (asset: Asset) => {
|
const handleDblClick = (asset: Asset) => {
|
||||||
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
if (asset && activeTool === "cursor" && boundingBox && groupRef.current && activeModule === 'builder') {
|
||||||
const size = boundingBox.getSize(new THREE.Vector3());
|
|
||||||
const center = boundingBox.getCenter(new THREE.Vector3());
|
|
||||||
|
|
||||||
const front = new THREE.Vector3(0, 0, 1);
|
const frontView = false;
|
||||||
groupRef.current.localToWorld(front);
|
|
||||||
front.sub(groupRef.current.position).normalize();
|
|
||||||
|
|
||||||
const distance = Math.max(size.x, size.y, size.z) * 2;
|
if (frontView) {
|
||||||
const newPosition = center.clone().addScaledVector(front, distance);
|
const size = boundingBox.getSize(new THREE.Vector3());
|
||||||
|
const center = boundingBox.getCenter(new THREE.Vector3());
|
||||||
|
|
||||||
(controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
const front = new THREE.Vector3(0, 0, 1);
|
||||||
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
groupRef.current.localToWorld(front);
|
||||||
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
front.sub(groupRef.current.position).normalize();
|
||||||
cover: true,
|
|
||||||
paddingTop: 5,
|
const distance = Math.max(size.x, size.y, size.z) * 2;
|
||||||
paddingLeft: 5,
|
const newPosition = center.clone().addScaledVector(front, distance);
|
||||||
paddingBottom: 5,
|
|
||||||
paddingRight: 5,
|
(controls as CameraControls).setPosition(newPosition.x, newPosition.y, newPosition.z, true);
|
||||||
});
|
(controls as CameraControls).setTarget(center.x, center.y, center.z, true);
|
||||||
|
(controls as CameraControls).fitToBox(groupRef.current, true, {
|
||||||
|
cover: true,
|
||||||
|
paddingTop: 5,
|
||||||
|
paddingLeft: 5,
|
||||||
|
paddingBottom: 5,
|
||||||
|
paddingRight: 5,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
|
||||||
|
const collisionPos = new THREE.Vector3();
|
||||||
|
groupRef.current.getWorldPosition(collisionPos);
|
||||||
|
|
||||||
|
const currentPos = new THREE.Vector3().copy(camera.position);
|
||||||
|
|
||||||
|
const target = new THREE.Vector3();
|
||||||
|
if (!controls) return;
|
||||||
|
(controls as CameraControls).getTarget(target);
|
||||||
|
const direction = new THREE.Vector3().subVectors(target, currentPos).normalize();
|
||||||
|
|
||||||
|
const offsetDistance = 5;
|
||||||
|
const newCameraPos = new THREE.Vector3().copy(collisionPos).sub(direction.multiplyScalar(offsetDistance));
|
||||||
|
|
||||||
|
camera.position.copy(newCameraPos);
|
||||||
|
(controls as CameraControls).setLookAt(newCameraPos.x, newCameraPos.y, newCameraPos.z, collisionPos.x, 0, collisionPos.z, true);
|
||||||
|
}
|
||||||
|
|
||||||
setSelectedFloorItem(groupRef.current);
|
setSelectedFloorItem(groupRef.current);
|
||||||
}
|
}
|
||||||
@@ -130,6 +152,21 @@ export function useModelEventHandlers({
|
|||||||
|
|
||||||
removeAsset(asset.modelUuid);
|
removeAsset(asset.modelUuid);
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Delete",
|
||||||
|
asset: {
|
||||||
|
type: "Asset",
|
||||||
|
assetData: asset,
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
echo.success("Model Removed!");
|
echo.success("Model Removed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import useModuleStore from '../../../../../store/useModuleStore';
|
|||||||
import { useSceneContext } from '../../../../scene/sceneContext';
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
import { SkeletonUtils } from 'three-stdlib';
|
import { SkeletonUtils } from 'three-stdlib';
|
||||||
|
|
||||||
import { getAssetIksApi } from '../../../../../services/simulation/ik/getAssetIKs';
|
import { getAssetFieldApi } from '../../../../../services/factoryBuilder/asset/floorAsset/getAssetField';
|
||||||
import { ModelAnimator } from './animator/modelAnimator';
|
import { ModelAnimator } from './animator/modelAnimator';
|
||||||
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
|
import { useModelEventHandlers } from './eventHandlers/useEventHandlers';
|
||||||
|
|
||||||
@@ -26,19 +26,31 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
|||||||
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
const [boundingBox, setBoundingBox] = useState<THREE.Box3 | null>(null);
|
||||||
const [isSelected, setIsSelected] = useState(false);
|
const [isSelected, setIsSelected] = useState(false);
|
||||||
const groupRef = useRef<THREE.Group>(null);
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
const [ikData, setIkData] = useState<any>();
|
const [fieldData, setFieldData] = useState<any>();
|
||||||
const { selectedAssets } = useSelectedAssets();
|
const { selectedAssets } = useSelectedAssets();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!ikData && asset.eventData && asset.eventData.type === 'ArmBot') {
|
if (!fieldData && asset.eventData) {
|
||||||
getAssetIksApi(asset.assetId).then((data) => {
|
getAssetFieldApi(asset.assetId).then((data) => {
|
||||||
if (data.iks) {
|
if (data.type === 'ArmBot') {
|
||||||
const iks: IK[] = data.iks;
|
if (data.data) {
|
||||||
setIkData(iks);
|
const fieldData: IK[] = data.data;
|
||||||
|
setFieldData(fieldData);
|
||||||
|
}
|
||||||
|
} else if (data.type === 'Conveyor') {
|
||||||
|
if (data.data) {
|
||||||
|
const fieldData = data.data;
|
||||||
|
setFieldData(fieldData);
|
||||||
|
}
|
||||||
|
} else if (data.type === 'Crane') {
|
||||||
|
if (data.data) {
|
||||||
|
const fieldData = data.data;
|
||||||
|
setFieldData(fieldData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}, [asset.modelUuid, ikData])
|
}, [asset.modelUuid, fieldData])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setDeletableFloorItem(null);
|
setDeletableFloorItem(null);
|
||||||
@@ -157,7 +169,7 @@ function Model({ asset, isRendered, loader }: { readonly asset: Asset, isRendere
|
|||||||
position={asset.position}
|
position={asset.position}
|
||||||
rotation={asset.rotation}
|
rotation={asset.rotation}
|
||||||
visible={asset.isVisible}
|
visible={asset.isVisible}
|
||||||
userData={{ ...asset, iks: ikData }}
|
userData={{ ...asset, fieldData: fieldData }}
|
||||||
castShadow
|
castShadow
|
||||||
receiveShadow
|
receiveShadow
|
||||||
onDoubleClick={(e) => {
|
onDoubleClick={(e) => {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import { getUserData } from "../../../functions/getUserData";
|
|||||||
import ContextControls from "./contextControls/contextControls";
|
import ContextControls from "./contextControls/contextControls";
|
||||||
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
import SelectionControls2D from "./selectionControls/selection2D/selectionControls2D";
|
||||||
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
import UndoRedo2DControls from "./undoRedoControls/undoRedo2D/undoRedo2DControls";
|
||||||
|
import UndoRedo3DControls from "./undoRedoControls/undoRedo3D/undoRedo3DControls";
|
||||||
|
|
||||||
export default function Controls() {
|
export default function Controls() {
|
||||||
const controlsRef = useRef<CameraControls>(null);
|
const controlsRef = useRef<CameraControls>(null);
|
||||||
@@ -145,6 +146,8 @@ export default function Controls() {
|
|||||||
|
|
||||||
<UndoRedo2DControls />
|
<UndoRedo2DControls />
|
||||||
|
|
||||||
|
<UndoRedo3DControls />
|
||||||
|
|
||||||
<TransformControl />
|
<TransformControl />
|
||||||
|
|
||||||
<ContextControls />
|
<ContextControls />
|
||||||
|
|||||||
@@ -28,7 +28,8 @@ const CopyPasteControls3D = ({
|
|||||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { assetStore, eventStore } = useSceneContext();
|
const { assetStore, eventStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
const { addEvent } = eventStore();
|
const { addEvent } = eventStore();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||||
@@ -209,6 +210,9 @@ const CopyPasteControls3D = ({
|
|||||||
const addPastedObjects = () => {
|
const addPastedObjects = () => {
|
||||||
if (pastedObjects.length === 0) return;
|
if (pastedObjects.length === 0) return;
|
||||||
|
|
||||||
|
const undoActions: UndoRedo3DAction[] = [];
|
||||||
|
const assetsToCopy: AssetData[] = [];
|
||||||
|
|
||||||
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
|
pastedObjects.forEach(async (pastedAsset: THREE.Object3D) => {
|
||||||
if (pastedAsset) {
|
if (pastedAsset) {
|
||||||
const assetUuid = pastedAsset.userData.modelUuid;
|
const assetUuid = pastedAsset.userData.modelUuid;
|
||||||
@@ -540,9 +544,45 @@ const CopyPasteControls3D = ({
|
|||||||
|
|
||||||
updateAsset(asset.modelUuid, asset);
|
updateAsset(asset.modelUuid, asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assetsToCopy.push({
|
||||||
|
type: "Asset",
|
||||||
|
assetData: {
|
||||||
|
modelUuid: newFloorItem.modelUuid,
|
||||||
|
modelName: newFloorItem.modelName,
|
||||||
|
assetId: newFloorItem.assetId,
|
||||||
|
position: [position.x, position.y, position.z],
|
||||||
|
rotation: [pastedAsset.rotation.x, pastedAsset.rotation.y, pastedAsset.rotation.z],
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
isCollidable: false,
|
||||||
|
opacity: 1,
|
||||||
|
eventData: newFloorItem.eventData || undefined
|
||||||
|
},
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (assetsToCopy.length === 1) {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Copied",
|
||||||
|
asset: assetsToCopy[0]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Assets-Copied",
|
||||||
|
assets: assetsToCopy
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: undoActions
|
||||||
|
});
|
||||||
|
|
||||||
echo.success("Object added!");
|
echo.success("Object added!");
|
||||||
clearSelection();
|
clearSelection();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ const DuplicationControls3D = ({
|
|||||||
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
const { selectedAssets, setSelectedAssets } = useSelectedAssets();
|
||||||
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
const plane = useMemo(() => new THREE.Plane(new THREE.Vector3(0, 1, 0), 0), []);
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { assetStore, eventStore } = useSceneContext();
|
const { assetStore, eventStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
const { addEvent } = eventStore();
|
const { addEvent } = eventStore();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
const { assets, addAsset, updateAsset, removeAsset, getAssetById } = assetStore();
|
||||||
@@ -207,6 +208,9 @@ const DuplicationControls3D = ({
|
|||||||
const addDuplicatedAssets = () => {
|
const addDuplicatedAssets = () => {
|
||||||
if (duplicatedObjects.length === 0) return;
|
if (duplicatedObjects.length === 0) return;
|
||||||
|
|
||||||
|
const undoActions: UndoRedo3DAction[] = [];
|
||||||
|
const assetsToDuplicate: AssetData[] = [];
|
||||||
|
|
||||||
duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
|
duplicatedObjects.forEach(async (duplicatedAsset: THREE.Object3D) => {
|
||||||
if (duplicatedAsset) {
|
if (duplicatedAsset) {
|
||||||
const assetUuid = duplicatedAsset.userData.modelUuid;
|
const assetUuid = duplicatedAsset.userData.modelUuid;
|
||||||
@@ -538,9 +542,45 @@ const DuplicationControls3D = ({
|
|||||||
|
|
||||||
updateAsset(asset.modelUuid, asset);
|
updateAsset(asset.modelUuid, asset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
assetsToDuplicate.push({
|
||||||
|
type: "Asset",
|
||||||
|
assetData: {
|
||||||
|
modelUuid: newFloorItem.modelUuid,
|
||||||
|
modelName: newFloorItem.modelName,
|
||||||
|
assetId: newFloorItem.assetId,
|
||||||
|
position: [position.x, position.y, position.z],
|
||||||
|
rotation: [duplicatedAsset.rotation.x, duplicatedAsset.rotation.y, duplicatedAsset.rotation.z],
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
isCollidable: false,
|
||||||
|
opacity: 1,
|
||||||
|
eventData: newFloorItem.eventData || undefined
|
||||||
|
},
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (assetsToDuplicate.length === 1) {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Copied",
|
||||||
|
asset: assetsToDuplicate[0]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Assets-Copied",
|
||||||
|
assets: assetsToDuplicate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: undoActions
|
||||||
|
});
|
||||||
|
|
||||||
echo.success("Object duplicated!");
|
echo.success("Object duplicated!");
|
||||||
clearSelection();
|
clearSelection();
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ function MoveControls3D({
|
|||||||
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
const [keyEvent, setKeyEvent] = useState<"Ctrl" | "Shift" | "Ctrl+Shift" | "">("");
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
const { updateAsset, getAssetById } = assetStore();
|
const { updateAsset, getAssetById } = assetStore();
|
||||||
const { selectedVersionStore } = useVersionContext();
|
const { selectedVersionStore } = useVersionContext();
|
||||||
const { selectedVersion } = selectedVersionStore();
|
const { selectedVersion } = selectedVersionStore();
|
||||||
@@ -292,6 +293,9 @@ function MoveControls3D({
|
|||||||
const placeMovedAssets = () => {
|
const placeMovedAssets = () => {
|
||||||
if (movedObjects.length === 0) return;
|
if (movedObjects.length === 0) return;
|
||||||
|
|
||||||
|
const undoActions: UndoRedo3DAction[] = [];
|
||||||
|
const assetsToUpdate: AssetData[] = [];
|
||||||
|
|
||||||
movedObjects.forEach(async (movedAsset: THREE.Object3D) => {
|
movedObjects.forEach(async (movedAsset: THREE.Object3D) => {
|
||||||
if (movedAsset) {
|
if (movedAsset) {
|
||||||
const assetUuid = movedAsset.userData.modelUuid;
|
const assetUuid = movedAsset.userData.modelUuid;
|
||||||
@@ -299,6 +303,32 @@ function MoveControls3D({
|
|||||||
const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
|
const model = scene.getObjectByProperty("uuid", movedAsset.userData.modelUuid);
|
||||||
if (!asset || !model) return;
|
if (!asset || !model) return;
|
||||||
const position = new THREE.Vector3().copy(model.position);
|
const position = new THREE.Vector3().copy(model.position);
|
||||||
|
const initialState = initialStates[movedAsset.uuid];
|
||||||
|
|
||||||
|
if (initialState) {
|
||||||
|
assetsToUpdate.push({
|
||||||
|
type: "Asset",
|
||||||
|
assetData: {
|
||||||
|
...asset,
|
||||||
|
position: [
|
||||||
|
initialState.position.x,
|
||||||
|
initialState.position.y,
|
||||||
|
initialState.position.z
|
||||||
|
],
|
||||||
|
rotation: [
|
||||||
|
initialState.rotation?.x || 0,
|
||||||
|
initialState.rotation?.y || 0,
|
||||||
|
initialState.rotation?.z || 0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
newData: {
|
||||||
|
...asset,
|
||||||
|
position: [position.x, position.y, position.z],
|
||||||
|
rotation: [movedAsset.rotation.x, movedAsset.rotation.y, movedAsset.rotation.z]
|
||||||
|
},
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const newFloorItem: Types.FloorItemType = {
|
const newFloorItem: Types.FloorItemType = {
|
||||||
modelUuid: movedAsset.userData.modelUuid,
|
modelUuid: movedAsset.userData.modelUuid,
|
||||||
@@ -376,6 +406,27 @@ function MoveControls3D({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (assetsToUpdate.length > 0) {
|
||||||
|
if (assetsToUpdate.length === 1) {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Update",
|
||||||
|
asset: assetsToUpdate[0]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Assets-Update",
|
||||||
|
assets: assetsToUpdate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: undoActions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
echo.success("Object moved!");
|
echo.success("Object moved!");
|
||||||
setIsMoving(false);
|
setIsMoving(false);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
|
|||||||
@@ -32,7 +32,8 @@ function RotateControls3D({
|
|||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
const { updateAsset } = assetStore();
|
const { updateAsset } = assetStore();
|
||||||
const { selectedVersionStore } = useVersionContext();
|
const { selectedVersionStore } = useVersionContext();
|
||||||
const { selectedVersion } = selectedVersionStore();
|
const { selectedVersion } = selectedVersionStore();
|
||||||
@@ -227,12 +228,43 @@ function RotateControls3D({
|
|||||||
const placeRotatedAssets = useCallback(() => {
|
const placeRotatedAssets = useCallback(() => {
|
||||||
if (rotatedObjects.length === 0) return;
|
if (rotatedObjects.length === 0) return;
|
||||||
|
|
||||||
|
const undoActions: UndoRedo3DAction[] = [];
|
||||||
|
const assetsToUpdate: AssetData[] = [];
|
||||||
|
|
||||||
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
rotatedObjects.forEach((obj: THREE.Object3D) => {
|
||||||
if (obj && obj.userData.modelUuid) {
|
if (obj && obj.userData.modelUuid) {
|
||||||
|
const asset = assetStore.getState().getAssetById(obj.userData.modelUuid);
|
||||||
|
if (!asset) return;
|
||||||
|
|
||||||
const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
|
const rotationArray: [number, number, number] = [obj.rotation.x, obj.rotation.y, obj.rotation.z];
|
||||||
|
|
||||||
const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z];
|
const positionArray: [number, number, number] = [obj.position.x, obj.position.y, obj.position.z];
|
||||||
|
|
||||||
|
if (initialRotations[obj.uuid] && initialPositions[obj.uuid]) {
|
||||||
|
assetsToUpdate.push({
|
||||||
|
type: "Asset",
|
||||||
|
assetData: {
|
||||||
|
...asset,
|
||||||
|
position: [
|
||||||
|
initialPositions[obj.uuid].x,
|
||||||
|
initialPositions[obj.uuid].y,
|
||||||
|
initialPositions[obj.uuid].z
|
||||||
|
],
|
||||||
|
rotation: [
|
||||||
|
initialRotations[obj.uuid].x,
|
||||||
|
initialRotations[obj.uuid].y,
|
||||||
|
initialRotations[obj.uuid].z
|
||||||
|
]
|
||||||
|
},
|
||||||
|
newData: {
|
||||||
|
...asset,
|
||||||
|
position: positionArray,
|
||||||
|
rotation: rotationArray
|
||||||
|
},
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const newFloorItem: Types.FloorItemType = {
|
const newFloorItem: Types.FloorItemType = {
|
||||||
modelUuid: obj.userData.modelUuid,
|
modelUuid: obj.userData.modelUuid,
|
||||||
modelName: obj.userData.modelName,
|
modelName: obj.userData.modelName,
|
||||||
@@ -310,6 +342,27 @@ function RotateControls3D({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (assetsToUpdate.length > 0) {
|
||||||
|
if (assetsToUpdate.length === 1) {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Update",
|
||||||
|
asset: assetsToUpdate[0]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Assets-Update",
|
||||||
|
assets: assetsToUpdate
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: undoActions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
setIsRotating(false);
|
setIsRotating(false);
|
||||||
clearSelection();
|
clearSelection();
|
||||||
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
|
}, [rotatedObjects, eventStore, productStore, selectedProduct, updateBackend, projectId, updateAsset, organization, socket, selectedVersion, userId]);
|
||||||
|
|||||||
@@ -31,9 +31,10 @@ const SelectionControls3D: React.FC = () => {
|
|||||||
const boundingBoxRef = useRef<THREE.Mesh>();
|
const boundingBoxRef = useRef<THREE.Mesh>();
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
|
||||||
const { removeAsset } = assetStore();
|
|
||||||
const { contextAction, setContextAction } = useContextActionStore()
|
const { contextAction, setContextAction } = useContextActionStore()
|
||||||
|
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
|
const { removeAsset, getAssetById } = assetStore();
|
||||||
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
const selectionBox = useMemo(() => new SelectionBox(camera, scene), [camera, scene]);
|
||||||
const { toolMode } = useToolMode();
|
const { toolMode } = useToolMode();
|
||||||
const { selectedVersionStore } = useVersionContext();
|
const { selectedVersionStore } = useVersionContext();
|
||||||
@@ -274,12 +275,18 @@ const SelectionControls3D: React.FC = () => {
|
|||||||
const deleteSelection = () => {
|
const deleteSelection = () => {
|
||||||
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
if (selectedAssets.length > 0 && duplicatedObjects.length === 0) {
|
||||||
|
|
||||||
|
const undoActions: UndoRedo3DAction[] = [];
|
||||||
|
const assetsToDelete: AssetData[] = [];
|
||||||
|
|
||||||
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
|
const selectedUUIDs = selectedAssets.map((mesh: THREE.Object3D) => mesh.uuid);
|
||||||
|
|
||||||
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
|
selectedAssets.forEach((selectedMesh: THREE.Object3D) => {
|
||||||
|
const asset = getAssetById(selectedMesh.userData.modelUuid);
|
||||||
|
if (!asset) return;
|
||||||
|
|
||||||
//REST
|
//REST
|
||||||
|
|
||||||
// const response = await deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName);
|
// const response = deleteFloorItem(organization, selectedMesh.userData.modelUuid, selectedMesh.userData.modelName);
|
||||||
|
|
||||||
//SOCKET
|
//SOCKET
|
||||||
|
|
||||||
@@ -329,6 +336,31 @@ const SelectionControls3D: React.FC = () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
assetsToDelete.push({
|
||||||
|
type: "Asset",
|
||||||
|
assetData: asset,
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (assetsToDelete.length === 1) {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Delete",
|
||||||
|
asset: assetsToDelete[0]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
undoActions.push({
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Assets-Delete",
|
||||||
|
assets: assetsToDelete
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: undoActions
|
||||||
});
|
});
|
||||||
|
|
||||||
selectedUUIDs.forEach((uuid: string) => {
|
selectedUUIDs.forEach((uuid: string) => {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as THREE from "three";
|
|||||||
import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store";
|
import { useSelectedFloorItem, useObjectPosition, useObjectRotation, useActiveTool, useSocketStore } from "../../../../store/builder/store";
|
||||||
import { useThree } from "@react-three/fiber";
|
import { useThree } from "@react-three/fiber";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
|
import { detectModifierKeys } from "../../../../utils/shortcutkeys/detectModifierKeys";
|
||||||
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
import { upsertProductOrEventApi } from "../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
// import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
// import { setAssetsApi } from "../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||||
@@ -15,6 +15,7 @@ import { useVersionContext } from "../../../builder/version/versionContext";
|
|||||||
|
|
||||||
export default function TransformControl() {
|
export default function TransformControl() {
|
||||||
const state = useThree();
|
const state = useThree();
|
||||||
|
const ref = useRef(null);
|
||||||
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
const [transformMode, setTransformMode] = useState<"translate" | "rotate" | null>(null);
|
||||||
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
const { selectedFloorItem, setSelectedFloorItem } = useSelectedFloorItem();
|
||||||
const { setObjectPosition } = useObjectPosition();
|
const { setObjectPosition } = useObjectPosition();
|
||||||
@@ -23,7 +24,8 @@ export default function TransformControl() {
|
|||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { selectedProductStore } = useProductContext();
|
const { selectedProductStore } = useProductContext();
|
||||||
const { selectedProduct } = selectedProductStore();
|
const { selectedProduct } = selectedProductStore();
|
||||||
const { assetStore, eventStore, productStore } = useSceneContext();
|
const { assetStore, eventStore, productStore, undoRedo3DStore } = useSceneContext();
|
||||||
|
const { push3D } = undoRedo3DStore();
|
||||||
const { updateAsset, getAssetById } = assetStore();
|
const { updateAsset, getAssetById } = assetStore();
|
||||||
const { userId, organization } = getUserData();
|
const { userId, organization } = getUserData();
|
||||||
const { selectedVersionStore } = useVersionContext();
|
const { selectedVersionStore } = useVersionContext();
|
||||||
@@ -136,13 +138,37 @@ export default function TransformControl() {
|
|||||||
projectId
|
projectId
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log('data: ', data);
|
|
||||||
socket.emit("v1:model-asset:add", data);
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
|
||||||
|
push3D({
|
||||||
|
type: 'Scene',
|
||||||
|
actions: [
|
||||||
|
{
|
||||||
|
module: "builder",
|
||||||
|
actionType: "Asset-Update",
|
||||||
|
asset: {
|
||||||
|
type: "Asset",
|
||||||
|
assetData: asset,
|
||||||
|
newData: {
|
||||||
|
...asset,
|
||||||
|
position: [selectedFloorItem.position.x, 0, selectedFloorItem.position.z],
|
||||||
|
rotation: [selectedFloorItem.rotation.x, selectedFloorItem.rotation.y, selectedFloorItem.rotation.z],
|
||||||
|
},
|
||||||
|
timeStap: new Date().toISOString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
const isTextInput = (element: Element | null): boolean =>
|
||||||
|
element instanceof HTMLInputElement ||
|
||||||
|
element instanceof HTMLTextAreaElement ||
|
||||||
|
element?.getAttribute("contenteditable") === "true";
|
||||||
|
if (isTextInput(document.activeElement)) return;
|
||||||
const keyCombination = detectModifierKeys(e);
|
const keyCombination = detectModifierKeys(e);
|
||||||
if (!selectedFloorItem) return;
|
if (!selectedFloorItem) return;
|
||||||
if (keyCombination === "G") {
|
if (keyCombination === "G") {
|
||||||
@@ -186,8 +212,9 @@ export default function TransformControl() {
|
|||||||
<>
|
<>
|
||||||
{(selectedFloorItem && transformMode) &&
|
{(selectedFloorItem && transformMode) &&
|
||||||
<TransformControls
|
<TransformControls
|
||||||
|
ref={ref}
|
||||||
showX={transformMode === "translate"}
|
showX={transformMode === "translate"}
|
||||||
showY={transformMode === "rotate"}
|
showY={transformMode === "rotate" || transformMode === "translate"}
|
||||||
showZ={transformMode === "translate"}
|
showZ={transformMode === "translate"}
|
||||||
object={selectedFloorItem}
|
object={selectedFloorItem}
|
||||||
mode={transformMode}
|
mode={transformMode}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store";
|
|||||||
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
||||||
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
||||||
|
|
||||||
function useRedoHandler() {
|
function use2DRedoHandler() {
|
||||||
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
||||||
const { redo2D, peekRedo2D } = undoRedo2DStore();
|
const { redo2D, peekRedo2D } = undoRedo2DStore();
|
||||||
const { addWall, removeWall, updateWall } = wallStore();
|
const { addWall, removeWall, updateWall } = wallStore();
|
||||||
@@ -352,4 +352,4 @@ function useRedoHandler() {
|
|||||||
return { handleRedo };
|
return { handleRedo };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useRedoHandler;
|
export default use2DRedoHandler;
|
||||||
@@ -16,7 +16,7 @@ import { useSocketStore } from "../../../../../store/builder/store";
|
|||||||
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
// import { upsertAisleApi } from "../../../../../services/factoryBuilder/aisle/upsertAisleApi";
|
||||||
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
// import { deleteAisleApi } from "../../../../../services/factoryBuilder/aisle/deleteAisleApi";
|
||||||
|
|
||||||
function useUndoHandler() {
|
function use2DUndoHandler() {
|
||||||
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
const { undoRedo2DStore, wallStore, floorStore, zoneStore, aisleStore } = useSceneContext();
|
||||||
const { undo2D, peekUndo2D } = undoRedo2DStore();
|
const { undo2D, peekUndo2D } = undoRedo2DStore();
|
||||||
const { addWall, removeWall, updateWall } = wallStore();
|
const { addWall, removeWall, updateWall } = wallStore();
|
||||||
@@ -67,38 +67,37 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
undo2D();
|
undo2D();
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
|
const handleCreate = (point: UndoRedo2DDataTypeSchema) => {
|
||||||
switch (point.type) {
|
switch (point.type) {
|
||||||
case 'Wall': createWallFromBackend(point.lineData); break;
|
case 'Wall': createWallToBackend(point.lineData); break;
|
||||||
case 'Floor': createFloorFromBackend(point.lineData); break;
|
case 'Floor': createFloorToBackend(point.lineData); break;
|
||||||
case 'Zone': createZoneFromBackend(point.lineData); break;
|
case 'Zone': createZoneToBackend(point.lineData); break;
|
||||||
case 'Aisle': createAisleFromBackend(point.lineData); break;
|
case 'Aisle': createAisleToBackend(point.lineData); break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
|
const handleRemove = (point: UndoRedo2DDataTypeSchema) => {
|
||||||
switch (point.type) {
|
switch (point.type) {
|
||||||
case 'Wall': removeWallFromBackend(point.lineData.wallUuid); break;
|
case 'Wall': removeWallToBackend(point.lineData.wallUuid); break;
|
||||||
case 'Floor': removeFloorFromBackend(point.lineData.floorUuid); break;
|
case 'Floor': removeFloorToBackend(point.lineData.floorUuid); break;
|
||||||
case 'Zone': removeZoneFromBackend(point.lineData.zoneUuid); break;
|
case 'Zone': removeZoneToBackend(point.lineData.zoneUuid); break;
|
||||||
case 'Aisle': removeAisleFromBackend(point.lineData.aisleUuid); break;
|
case 'Aisle': removeAisleToBackend(point.lineData.aisleUuid); break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
|
const handleUpdate = (point: UndoRedo2DDataTypeSchema) => {
|
||||||
switch (point.type) {
|
switch (point.type) {
|
||||||
case 'Wall': updateWallFromBackend(point.lineData.wallUuid, point.lineData); break;
|
case 'Wall': updateWallToBackend(point.lineData.wallUuid, point.lineData); break;
|
||||||
case 'Floor': updateFloorFromBackend(point.lineData.floorUuid, point.lineData); break;
|
case 'Floor': updateFloorToBackend(point.lineData.floorUuid, point.lineData); break;
|
||||||
case 'Zone': updateZoneFromBackend(point.lineData.zoneUuid, point.lineData); break;
|
case 'Zone': updateZoneToBackend(point.lineData.zoneUuid, point.lineData); break;
|
||||||
case 'Aisle': updateAisleFromBackend(point.lineData.aisleUuid, point.lineData); break;
|
case 'Aisle': updateAisleToBackend(point.lineData.aisleUuid, point.lineData); break;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const createWallFromBackend = (wallData: Wall) => {
|
const createWallToBackend = (wallData: Wall) => {
|
||||||
addWall(wallData);
|
addWall(wallData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -119,7 +118,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeWallFromBackend = (wallUuid: string) => {
|
const removeWallToBackend = (wallUuid: string) => {
|
||||||
removeWall(wallUuid);
|
removeWall(wallUuid);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -140,7 +139,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateWallFromBackend = (wallUuid: string, updatedData: Wall) => {
|
const updateWallToBackend = (wallUuid: string, updatedData: Wall) => {
|
||||||
updateWall(wallUuid, updatedData);
|
updateWall(wallUuid, updatedData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -161,7 +160,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createFloorFromBackend = (floorData: Floor) => {
|
const createFloorToBackend = (floorData: Floor) => {
|
||||||
addFloor(floorData);
|
addFloor(floorData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -182,7 +181,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeFloorFromBackend = (floorUuid: string) => {
|
const removeFloorToBackend = (floorUuid: string) => {
|
||||||
removeFloor(floorUuid);
|
removeFloor(floorUuid);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -203,7 +202,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateFloorFromBackend = (floorUuid: string, updatedData: Floor) => {
|
const updateFloorToBackend = (floorUuid: string, updatedData: Floor) => {
|
||||||
updateFloor(floorUuid, updatedData);
|
updateFloor(floorUuid, updatedData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -224,7 +223,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createZoneFromBackend = (zoneData: Zone) => {
|
const createZoneToBackend = (zoneData: Zone) => {
|
||||||
addZone(zoneData);
|
addZone(zoneData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -245,7 +244,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeZoneFromBackend = (zoneUuid: string) => {
|
const removeZoneToBackend = (zoneUuid: string) => {
|
||||||
removeZone(zoneUuid);
|
removeZone(zoneUuid);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -266,7 +265,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateZoneFromBackend = (zoneUuid: string, updatedData: Zone) => {
|
const updateZoneToBackend = (zoneUuid: string, updatedData: Zone) => {
|
||||||
updateZone(zoneUuid, updatedData);
|
updateZone(zoneUuid, updatedData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -287,7 +286,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const createAisleFromBackend = (aisleData: Aisle) => {
|
const createAisleToBackend = (aisleData: Aisle) => {
|
||||||
addAisle(aisleData);
|
addAisle(aisleData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -308,7 +307,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeAisleFromBackend = (aisleUuid: string) => {
|
const removeAisleToBackend = (aisleUuid: string) => {
|
||||||
removeAisle(aisleUuid);
|
removeAisle(aisleUuid);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -329,7 +328,7 @@ function useUndoHandler() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateAisleFromBackend = (aisleUuid: string, updatedData: Aisle) => {
|
const updateAisleToBackend = (aisleUuid: string, updatedData: Aisle) => {
|
||||||
updateAisle(aisleUuid, updatedData);
|
updateAisle(aisleUuid, updatedData);
|
||||||
if (projectId) {
|
if (projectId) {
|
||||||
// API
|
// API
|
||||||
@@ -353,4 +352,4 @@ function useUndoHandler() {
|
|||||||
return { handleUndo };
|
return { handleUndo };
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useUndoHandler;
|
export default use2DUndoHandler;
|
||||||
@@ -0,0 +1,318 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { getUserData } from "../../../../../functions/getUserData";
|
||||||
|
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||||
|
import { useSceneContext } from "../../../sceneContext";
|
||||||
|
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||||
|
import { useSocketStore } from "../../../../../store/builder/store";
|
||||||
|
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
|
|
||||||
|
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||||
|
// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi";
|
||||||
|
|
||||||
|
function use3DRedoHandler() {
|
||||||
|
const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext();
|
||||||
|
const { deleteEvent } = productStore();
|
||||||
|
const { addEvent, removeEvent } = eventStore();
|
||||||
|
const { updateAsset, removeAsset, addAsset } = assetStore();
|
||||||
|
const { redo3D, peekRedo3D } = undoRedo3DStore();
|
||||||
|
const { selectedVersionStore } = useVersionContext();
|
||||||
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
const { userId, organization } = getUserData();
|
||||||
|
const { projectId } = useParams();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productUuid: string,
|
||||||
|
projectId: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productUuid: productUuid,
|
||||||
|
projectId: projectId,
|
||||||
|
eventDatas: eventData,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRedo = () => {
|
||||||
|
const redoData = peekRedo3D();
|
||||||
|
if (!redoData) return;
|
||||||
|
|
||||||
|
if (redoData.type === 'Scene') {
|
||||||
|
const { actions } = redoData;
|
||||||
|
|
||||||
|
actions.forEach(action => {
|
||||||
|
const { actionType } = action;
|
||||||
|
|
||||||
|
if ('asset' in action) {
|
||||||
|
const asset = action.asset;
|
||||||
|
|
||||||
|
if (actionType === 'Asset-Add') {
|
||||||
|
handleAdd(asset);
|
||||||
|
} else if (actionType === 'Asset-Delete') {
|
||||||
|
handleDelete(asset);
|
||||||
|
} else if (actionType === 'Asset-Update') {
|
||||||
|
handleUpdate(asset);
|
||||||
|
} else if (actionType === 'Asset-Copied') {
|
||||||
|
handleCopy(asset);
|
||||||
|
} else if (actionType === 'Asset-Duplicated') {
|
||||||
|
handleDuplicate(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ('assets' in action) {
|
||||||
|
const assets = action.assets;
|
||||||
|
|
||||||
|
if (actionType === 'Assets-Add') {
|
||||||
|
assets.forEach(handleAdd);
|
||||||
|
} else if (actionType === 'Assets-Delete') {
|
||||||
|
assets.forEach(handleDelete);
|
||||||
|
} else if (actionType === 'Assets-Update') {
|
||||||
|
assets.forEach(handleUpdate);
|
||||||
|
} else if (actionType === 'Assets-Copied') {
|
||||||
|
assets.forEach(handleCopy);
|
||||||
|
} else if (actionType === 'Assets-Duplicated') {
|
||||||
|
assets.forEach(handleDuplicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (redoData.type === 'UI') {
|
||||||
|
// Handle UI actions if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
redo3D();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleAdd = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': addAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': addWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': deleteAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdate = (asset: AssetData) => {
|
||||||
|
if (!asset.newData) return;
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': updateAssetToBackend(asset.newData.modelUuid, asset.newData); break;
|
||||||
|
case 'WallAsset': updateWallAssetToBackend(asset.newData.modelUuid, asset.newData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopy = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': copyAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': copyWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDuplicate = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': duplicateAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const addAssetToBackend = (assetData: Asset) => {
|
||||||
|
addAsset(assetData);
|
||||||
|
if (projectId) {
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
assetId: assetData.assetId,
|
||||||
|
position: assetData.position,
|
||||||
|
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
eventData: {},
|
||||||
|
socketId: socket.id,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId,
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (assetData.eventData) {
|
||||||
|
data.eventData = assetData.eventData;
|
||||||
|
addEvent(assetData.eventData as EventsSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// setAssetsApi(data);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAssetToBackend = (assetData: Asset) => {
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
socketId: socket.id,
|
||||||
|
userId,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = socket.emit('v1:model-asset:delete', data)
|
||||||
|
|
||||||
|
removeEvent(assetData.modelUuid);
|
||||||
|
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||||
|
|
||||||
|
updatedEvents.forEach((event) => {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
|
||||||
|
removeAsset(assetData.modelUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => {
|
||||||
|
updateAsset(modelUuid, updatedData);
|
||||||
|
if (projectId) {
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: updatedData.modelUuid,
|
||||||
|
modelName: updatedData.modelName,
|
||||||
|
assetId: updatedData.assetId,
|
||||||
|
position: updatedData.position,
|
||||||
|
rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
socketId: socket.id,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId,
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// setAssetsApi(data);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyAssetToBackend = (assetData: Asset) => {
|
||||||
|
addAsset(assetData);
|
||||||
|
if (projectId) {
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
assetId: assetData.assetId,
|
||||||
|
position: assetData.position,
|
||||||
|
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
eventData: {},
|
||||||
|
socketId: socket.id,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId,
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (assetData.eventData) {
|
||||||
|
data.eventData = assetData.eventData;
|
||||||
|
addEvent(assetData.eventData as EventsSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// setAssetsApi(data);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateAssetToBackend = (assetData: Asset) => {
|
||||||
|
addAsset(assetData);
|
||||||
|
if (projectId) {
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
assetId: assetData.assetId,
|
||||||
|
position: assetData.position,
|
||||||
|
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
eventData: {},
|
||||||
|
socketId: socket.id,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId,
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (assetData.eventData) {
|
||||||
|
data.eventData = assetData.eventData;
|
||||||
|
addEvent(assetData.eventData as EventsSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// setAssetsApi(data);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handleRedo };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default use3DRedoHandler;
|
||||||
@@ -0,0 +1,323 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { getUserData } from "../../../../../functions/getUserData";
|
||||||
|
import { useVersionContext } from "../../../../builder/version/versionContext";
|
||||||
|
import { useSceneContext } from "../../../sceneContext";
|
||||||
|
import { useProductContext } from "../../../../simulation/products/productContext";
|
||||||
|
import { useSocketStore } from "../../../../../store/builder/store";
|
||||||
|
|
||||||
|
import { upsertProductOrEventApi } from "../../../../../services/simulation/products/UpsertProductOrEventApi";
|
||||||
|
|
||||||
|
// import { setAssetsApi } from "../../../../../services/factoryBuilder/asset/floorAsset/setAssetsApi";
|
||||||
|
// import { deleteFloorItem } from "../../../../../services/factoryBuilder/asset/floorAsset/deleteFloorItemApi";
|
||||||
|
|
||||||
|
function use3DUndoHandler() {
|
||||||
|
const { undoRedo3DStore, assetStore, productStore, eventStore } = useSceneContext();
|
||||||
|
const { deleteEvent } = productStore();
|
||||||
|
const { addEvent, removeEvent } = eventStore();
|
||||||
|
const { updateAsset, removeAsset, addAsset } = assetStore();
|
||||||
|
const { undo3D, peekUndo3D } = undoRedo3DStore();
|
||||||
|
const { selectedVersionStore } = useVersionContext();
|
||||||
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
const { userId, organization } = getUserData();
|
||||||
|
const { projectId } = useParams();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
const updateBackend = (
|
||||||
|
productName: string,
|
||||||
|
productUuid: string,
|
||||||
|
projectId: string,
|
||||||
|
eventData: EventsSchema
|
||||||
|
) => {
|
||||||
|
upsertProductOrEventApi({
|
||||||
|
productName: productName,
|
||||||
|
productUuid: productUuid,
|
||||||
|
projectId: projectId,
|
||||||
|
eventDatas: eventData,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUndo = () => {
|
||||||
|
const unDoData = peekUndo3D();
|
||||||
|
if (!unDoData) return;
|
||||||
|
|
||||||
|
if (unDoData.type === 'Scene') {
|
||||||
|
const { actions } = unDoData;
|
||||||
|
|
||||||
|
actions.forEach(action => {
|
||||||
|
const { actionType } = action;
|
||||||
|
|
||||||
|
if ('asset' in action) {
|
||||||
|
const asset = action.asset;
|
||||||
|
|
||||||
|
if (actionType === 'Asset-Add') {
|
||||||
|
handleDelete(asset);
|
||||||
|
} else if (actionType === 'Asset-Delete') {
|
||||||
|
handleAdd(asset);
|
||||||
|
} else if (actionType === 'Asset-Update') {
|
||||||
|
handleUpdate(asset);
|
||||||
|
} else if (actionType === 'Asset-Copied') {
|
||||||
|
handleCopy(asset);
|
||||||
|
} else if (actionType === 'Asset-Duplicated') {
|
||||||
|
handleDuplicate(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if ('assets' in action) {
|
||||||
|
const assets = action.assets;
|
||||||
|
|
||||||
|
if (actionType === 'Assets-Add') {
|
||||||
|
assets.forEach(handleDelete);
|
||||||
|
} else if (actionType === 'Assets-Delete') {
|
||||||
|
assets.forEach(handleAdd);
|
||||||
|
} else if (actionType === 'Assets-Update') {
|
||||||
|
assets.forEach(handleUpdate);
|
||||||
|
} else if (actionType === 'Assets-Copied') {
|
||||||
|
assets.forEach(handleCopy);
|
||||||
|
} else if (actionType === 'Assets-Duplicated') {
|
||||||
|
assets.forEach(handleDuplicate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (unDoData.type === 'UI') {
|
||||||
|
// Handle UI actions if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
undo3D();
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleAdd = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': addAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': addWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDelete = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': deleteAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': deleteWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUpdate = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': updateAssetToBackend(asset.assetData.modelUuid, asset.assetData); break;
|
||||||
|
case 'WallAsset': updateWallAssetToBackend(asset.assetData.modelUuid, asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCopy = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': copyAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': copyWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDuplicate = (asset: AssetData) => {
|
||||||
|
switch (asset.type) {
|
||||||
|
case 'Asset': duplicateAssetToBackend(asset.assetData); break;
|
||||||
|
case 'WallAsset': duplicateWallAssetToBackend(asset.assetData); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const addAssetToBackend = (assetData: Asset) => {
|
||||||
|
addAsset(assetData);
|
||||||
|
if (projectId) {
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
assetId: assetData.assetId,
|
||||||
|
position: assetData.position,
|
||||||
|
rotation: { x: assetData.rotation[0], y: assetData.rotation[1], z: assetData.rotation[2] },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
eventData: {},
|
||||||
|
socketId: socket.id,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId,
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
if (assetData.eventData) {
|
||||||
|
data.eventData = assetData.eventData;
|
||||||
|
addEvent(assetData.eventData as EventsSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// setAssetsApi(data);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteAssetToBackend = (assetData: Asset) => {
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
socketId: socket.id,
|
||||||
|
userId,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = socket.emit('v1:model-asset:delete', data)
|
||||||
|
|
||||||
|
removeEvent(assetData.modelUuid);
|
||||||
|
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||||
|
|
||||||
|
updatedEvents.forEach((event) => {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
|
||||||
|
removeAsset(assetData.modelUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateAssetToBackend = (modelUuid: string, updatedData: Asset) => {
|
||||||
|
updateAsset(modelUuid, updatedData);
|
||||||
|
if (projectId) {
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: updatedData.modelUuid,
|
||||||
|
modelName: updatedData.modelName,
|
||||||
|
assetId: updatedData.assetId,
|
||||||
|
position: updatedData.position,
|
||||||
|
rotation: { x: updatedData.rotation[0], y: updatedData.rotation[1], z: updatedData.rotation[2] },
|
||||||
|
isLocked: false,
|
||||||
|
isVisible: true,
|
||||||
|
socketId: socket.id,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId,
|
||||||
|
userId
|
||||||
|
};
|
||||||
|
|
||||||
|
// API
|
||||||
|
|
||||||
|
// setAssetsApi(data);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
socket.emit("v1:model-asset:add", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyAssetToBackend = (assetData: Asset) => {
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
socketId: socket.id,
|
||||||
|
userId,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = socket.emit('v1:model-asset:delete', data)
|
||||||
|
|
||||||
|
removeEvent(assetData.modelUuid);
|
||||||
|
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||||
|
|
||||||
|
updatedEvents.forEach((event) => {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
|
||||||
|
removeAsset(assetData.modelUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateAssetToBackend = (assetData: Asset) => {
|
||||||
|
//REST
|
||||||
|
|
||||||
|
// const response = deleteFloorItem(organization, assetData.modelUuid, assetData.modelName);
|
||||||
|
|
||||||
|
//SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
organization,
|
||||||
|
modelUuid: assetData.modelUuid,
|
||||||
|
modelName: assetData.modelName,
|
||||||
|
socketId: socket.id,
|
||||||
|
userId,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
projectId
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = socket.emit('v1:model-asset:delete', data)
|
||||||
|
|
||||||
|
removeEvent(assetData.modelUuid);
|
||||||
|
const updatedEvents = deleteEvent(assetData.modelUuid);
|
||||||
|
|
||||||
|
updatedEvents.forEach((event) => {
|
||||||
|
updateBackend(
|
||||||
|
selectedProduct.productName,
|
||||||
|
selectedProduct.productUuid,
|
||||||
|
projectId || '',
|
||||||
|
event
|
||||||
|
);
|
||||||
|
})
|
||||||
|
|
||||||
|
if (response) {
|
||||||
|
|
||||||
|
removeAsset(assetData.modelUuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const addWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateWallAssetToBackend = (modelUuid: string, updatedData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const duplicateWallAssetToBackend = (assetData: WallAsset) => {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return { handleUndo };
|
||||||
|
}
|
||||||
|
|
||||||
|
export default use3DUndoHandler;
|
||||||
@@ -4,15 +4,15 @@ import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModi
|
|||||||
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
||||||
import { useVersionContext } from '../../../../builder/version/versionContext';
|
import { useVersionContext } from '../../../../builder/version/versionContext';
|
||||||
|
|
||||||
import useUndoHandler from '../handlers/useUndoHandler';
|
import use2DUndoHandler from '../handlers/use2DUndoHandler';
|
||||||
import useRedoHandler from '../handlers/useRedoHandler';
|
import use2DRedoHandler from '../handlers/use2DRedoHandler';
|
||||||
|
|
||||||
function UndoRedo2DControls() {
|
function UndoRedo2DControls() {
|
||||||
const { undoRedo2DStore } = useSceneContext();
|
const { undoRedo2DStore } = useSceneContext();
|
||||||
const { undoStack, redoStack } = undoRedo2DStore();
|
const { undoStack, redoStack } = undoRedo2DStore();
|
||||||
const { toggleView } = useToggleView();
|
const { toggleView } = useToggleView();
|
||||||
const { handleUndo } = useUndoHandler();
|
const { handleUndo } = use2DUndoHandler();
|
||||||
const { handleRedo } = useRedoHandler();
|
const { handleRedo } = use2DRedoHandler();
|
||||||
const { socket } = useSocketStore();
|
const { socket } = useSocketStore();
|
||||||
const { selectedVersionStore } = useVersionContext();
|
const { selectedVersionStore } = useVersionContext();
|
||||||
const { selectedVersion } = selectedVersionStore();
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
import { useSceneContext } from '../../../sceneContext'
|
||||||
|
import { detectModifierKeys } from '../../../../../utils/shortcutkeys/detectModifierKeys';
|
||||||
|
import { useSocketStore, useToggleView } from '../../../../../store/builder/store';
|
||||||
|
import { useVersionContext } from '../../../../builder/version/versionContext';
|
||||||
|
import useModuleStore from '../../../../../store/useModuleStore';
|
||||||
|
|
||||||
|
import use3DUndoHandler from '../handlers/use3DUndoHandler';
|
||||||
|
import use3DRedoHandler from '../handlers/use3DRedoHandler';
|
||||||
|
|
||||||
|
function UndoRedo3DControls() {
|
||||||
|
const { undoRedo3DStore } = useSceneContext();
|
||||||
|
const { undoStack, redoStack } = undoRedo3DStore();
|
||||||
|
const { toggleView } = useToggleView();
|
||||||
|
const { activeModule } = useModuleStore();
|
||||||
|
const { handleUndo } = use3DUndoHandler();
|
||||||
|
const { handleRedo } = use3DRedoHandler();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
const { selectedVersionStore } = useVersionContext();
|
||||||
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(undoStack, redoStack);
|
||||||
|
}, [undoStack, redoStack]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
const keyCombination = detectModifierKeys(event);
|
||||||
|
|
||||||
|
if (keyCombination === 'Ctrl+Z') {
|
||||||
|
handleUndo();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keyCombination === 'Ctrl+Y') {
|
||||||
|
handleRedo();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!toggleView) {
|
||||||
|
window.addEventListener('keydown', handleKeyDown);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
|
}, [toggleView, undoStack, redoStack, socket, selectedVersion, activeModule]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UndoRedo3DControls;
|
||||||
22
app/src/modules/scene/helpers/StatsHelper.tsx
Normal file
22
app/src/modules/scene/helpers/StatsHelper.tsx
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Stats } from "@react-three/drei";
|
||||||
|
import { detectModifierKeys } from "../../../utils/shortcutkeys/detectModifierKeys";
|
||||||
|
|
||||||
|
export default function StatsHelper() {
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleKeyDown = (event: KeyboardEvent) => {
|
||||||
|
const keyCombination = detectModifierKeys(event);
|
||||||
|
if (keyCombination === "F1") {
|
||||||
|
event.preventDefault();
|
||||||
|
setVisible(prev => !prev);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener("keydown", handleKeyDown);
|
||||||
|
return () => window.removeEventListener("keydown", handleKeyDown);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return visible ? <Stats className="stats" /> : null;
|
||||||
|
}
|
||||||
@@ -14,8 +14,9 @@ import { getAllProjects } from "../../services/dashboard/getAllProjects";
|
|||||||
import { getUserData } from "../../functions/getUserData";
|
import { getUserData } from "../../functions/getUserData";
|
||||||
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
import { useLoadingProgress, useSocketStore } from "../../store/builder/store";
|
||||||
import { Color, SRGBColorSpace } from "three";
|
import { Color, SRGBColorSpace } from "three";
|
||||||
|
import StatsHelper from "./helpers/StatsHelper";
|
||||||
|
|
||||||
export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Comparison Layout' }) {
|
export default function Scene({ layout }: { readonly layout: "Main Layout" | "Comparison Layout"; }) {
|
||||||
const map = useMemo(() => [
|
const map = useMemo(() => [
|
||||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||||
@@ -32,28 +33,27 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!projectId && loadingProgress > 1) return;
|
if (!projectId && loadingProgress > 1) return;
|
||||||
getAllProjects(userId, organization)
|
getAllProjects(userId, organization).then((projects) => {
|
||||||
.then((projects) => {
|
if (!projects || !projects.Projects) return;
|
||||||
if (!projects || !projects.Projects) return;
|
let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId);
|
||||||
let project = projects.Projects.find((val: any) => val.projectUuid === projectId || val._id === projectId);
|
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0];
|
||||||
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName('canvas')[0];
|
if (!canvas) return;
|
||||||
if (!canvas) return;
|
const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png");
|
||||||
const screenshotDataUrl = (canvas as HTMLCanvasElement)?.toDataURL("image/png");
|
const updateProjects = {
|
||||||
const updateProjects = {
|
projectId: project?._id,
|
||||||
projectId: project?.projectUuid,
|
organization,
|
||||||
organization,
|
userId,
|
||||||
userId,
|
projectName: project?.projectName,
|
||||||
projectName: project?.projectName,
|
thumbnail: screenshotDataUrl,
|
||||||
thumbnail: screenshotDataUrl,
|
};
|
||||||
};
|
if (projectSocket) {
|
||||||
if (projectSocket) {
|
projectSocket.emit("v1:project:update", updateProjects);
|
||||||
projectSocket.emit("v1:project:update", updateProjects);
|
}
|
||||||
}
|
}).catch((err) => {
|
||||||
}).catch((err) => {
|
console.error(err);
|
||||||
console.error(err);
|
});
|
||||||
});
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
}, [activeModule, assets, loadingProgress])
|
}, [activeModule, assets, loadingProgress]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<KeyboardControls map={map}>
|
<KeyboardControls map={map}>
|
||||||
@@ -62,20 +62,17 @@ export default function Scene({ layout }: { readonly layout: 'Main Layout' | 'Co
|
|||||||
shadows
|
shadows
|
||||||
color="#aaaa"
|
color="#aaaa"
|
||||||
eventPrefix="client"
|
eventPrefix="client"
|
||||||
onContextMenu={(e) => {
|
onContextMenu={(e) => { e.preventDefault(); }}
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
performance={{ min: 0.9, max: 1.0 }}
|
performance={{ min: 0.9, max: 1.0 }}
|
||||||
onCreated={(e) => {
|
onCreated={(e) => { e.scene.background = layout === "Main Layout" ? null : new Color(0x19191d); }}
|
||||||
e.scene.background = layout === 'Main Layout' ? null : new Color(0x19191d);
|
gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true }}
|
||||||
}}
|
|
||||||
gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }}
|
|
||||||
>
|
>
|
||||||
<Setup />
|
<Setup />
|
||||||
<Collaboration />
|
<Collaboration />
|
||||||
<Builder />
|
<Builder />
|
||||||
<Simulation />
|
<Simulation />
|
||||||
<Visualization />
|
<Visualization />
|
||||||
|
<StatsHelper />
|
||||||
</Canvas>
|
</Canvas>
|
||||||
</KeyboardControls>
|
</KeyboardControls>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { createZoneStore, ZoneStoreType } from '../../store/builder/useZoneStore
|
|||||||
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
|
import { createFloorStore, FloorStoreType } from '../../store/builder/useFloorStore';
|
||||||
|
|
||||||
import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
|
import { createUndoRedo2DStore, UndoRedo2DStoreType } from '../../store/builder/useUndoRedo2DStore';
|
||||||
|
import { createUndoRedo3DStore, UndoRedo3DStoreType } from '../../store/builder/useUndoRedo3DStore';
|
||||||
|
|
||||||
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
|
import { createEventStore, EventStoreType } from '../../store/simulation/useEventsStore';
|
||||||
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
|
import { createProductStore, ProductStoreType } from '../../store/simulation/useProductStore';
|
||||||
@@ -31,6 +32,7 @@ type SceneContextValue = {
|
|||||||
floorStore: FloorStoreType,
|
floorStore: FloorStoreType,
|
||||||
|
|
||||||
undoRedo2DStore: UndoRedo2DStoreType,
|
undoRedo2DStore: UndoRedo2DStoreType,
|
||||||
|
undoRedo3DStore: UndoRedo3DStoreType,
|
||||||
|
|
||||||
eventStore: EventStoreType,
|
eventStore: EventStoreType,
|
||||||
productStore: ProductStoreType,
|
productStore: ProductStoreType,
|
||||||
@@ -45,6 +47,7 @@ type SceneContextValue = {
|
|||||||
craneStore: CraneStoreType;
|
craneStore: CraneStoreType;
|
||||||
|
|
||||||
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
humanEventManagerRef: React.RefObject<HumanEventManagerState>;
|
||||||
|
craneEventManagerRef: React.RefObject<CraneEventManagerState>;
|
||||||
|
|
||||||
clearStores: () => void;
|
clearStores: () => void;
|
||||||
|
|
||||||
@@ -69,6 +72,7 @@ export function SceneProvider({
|
|||||||
const floorStore = useMemo(() => createFloorStore(), []);
|
const floorStore = useMemo(() => createFloorStore(), []);
|
||||||
|
|
||||||
const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
|
const undoRedo2DStore = useMemo(() => createUndoRedo2DStore(), []);
|
||||||
|
const undoRedo3DStore = useMemo(() => createUndoRedo3DStore(), []);
|
||||||
|
|
||||||
const eventStore = useMemo(() => createEventStore(), []);
|
const eventStore = useMemo(() => createEventStore(), []);
|
||||||
const productStore = useMemo(() => createProductStore(), []);
|
const productStore = useMemo(() => createProductStore(), []);
|
||||||
@@ -83,6 +87,7 @@ export function SceneProvider({
|
|||||||
const craneStore = useMemo(() => createCraneStore(), []);
|
const craneStore = useMemo(() => createCraneStore(), []);
|
||||||
|
|
||||||
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
const humanEventManagerRef = useRef<HumanEventManagerState>({ humanStates: [] });
|
||||||
|
const craneEventManagerRef = useRef<CraneEventManagerState>({ craneStates: [] });
|
||||||
|
|
||||||
const clearStores = useMemo(() => () => {
|
const clearStores = useMemo(() => () => {
|
||||||
assetStore.getState().clearAssets();
|
assetStore.getState().clearAssets();
|
||||||
@@ -92,6 +97,7 @@ export function SceneProvider({
|
|||||||
zoneStore.getState().clearZones();
|
zoneStore.getState().clearZones();
|
||||||
floorStore.getState().clearFloors();
|
floorStore.getState().clearFloors();
|
||||||
undoRedo2DStore.getState().clearUndoRedo2D();
|
undoRedo2DStore.getState().clearUndoRedo2D();
|
||||||
|
undoRedo3DStore.getState().clearUndoRedo3D();
|
||||||
eventStore.getState().clearEvents();
|
eventStore.getState().clearEvents();
|
||||||
productStore.getState().clearProducts();
|
productStore.getState().clearProducts();
|
||||||
materialStore.getState().clearMaterials();
|
materialStore.getState().clearMaterials();
|
||||||
@@ -103,7 +109,8 @@ export function SceneProvider({
|
|||||||
humanStore.getState().clearHumans();
|
humanStore.getState().clearHumans();
|
||||||
craneStore.getState().clearCranes();
|
craneStore.getState().clearCranes();
|
||||||
humanEventManagerRef.current.humanStates = [];
|
humanEventManagerRef.current.humanStates = [];
|
||||||
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
|
craneEventManagerRef.current.craneStates = [];
|
||||||
|
}, [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, undoRedo2DStore, undoRedo3DStore, floorStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore]);
|
||||||
|
|
||||||
const contextValue = useMemo(() => (
|
const contextValue = useMemo(() => (
|
||||||
{
|
{
|
||||||
@@ -114,6 +121,7 @@ export function SceneProvider({
|
|||||||
zoneStore,
|
zoneStore,
|
||||||
floorStore,
|
floorStore,
|
||||||
undoRedo2DStore,
|
undoRedo2DStore,
|
||||||
|
undoRedo3DStore,
|
||||||
eventStore,
|
eventStore,
|
||||||
productStore,
|
productStore,
|
||||||
materialStore,
|
materialStore,
|
||||||
@@ -125,10 +133,11 @@ export function SceneProvider({
|
|||||||
humanStore,
|
humanStore,
|
||||||
craneStore,
|
craneStore,
|
||||||
humanEventManagerRef,
|
humanEventManagerRef,
|
||||||
|
craneEventManagerRef,
|
||||||
clearStores,
|
clearStores,
|
||||||
layout
|
layout
|
||||||
}
|
}
|
||||||
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
|
), [assetStore, wallAssetStore, wallStore, aisleStore, zoneStore, floorStore, undoRedo2DStore, undoRedo3DStore, eventStore, productStore, materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, storageUnitStore, humanStore, craneStore, clearStores, layout]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SceneContext.Provider value={contextValue}>
|
<SceneContext.Provider value={contextValue}>
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { useCallback } from "react";
|
||||||
|
import { useSceneContext } from "../../../../scene/sceneContext";
|
||||||
|
import { useProductContext } from "../../../products/productContext";
|
||||||
|
|
||||||
|
export function usePickAndDropHandler() {
|
||||||
|
const { materialStore, craneStore, productStore } = useSceneContext();
|
||||||
|
const { getMaterialById } = materialStore();
|
||||||
|
const { getModelUuidByActionUuid } = productStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
const { incrementCraneLoad, addCurrentMaterial, addCurrentAction } = craneStore();
|
||||||
|
|
||||||
|
const pickAndDropLogStatus = (materialUuid: string, status: string) => {
|
||||||
|
echo.info(`${materialUuid}, ${status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlePickAndDrop = useCallback((action: CraneAction, materialId?: string) => {
|
||||||
|
if (!action || action.actionType !== 'pickAndDrop' || !materialId) return;
|
||||||
|
|
||||||
|
const material = getMaterialById(materialId);
|
||||||
|
if (!material) return;
|
||||||
|
|
||||||
|
const modelUuid = getModelUuidByActionUuid(selectedProduct.productUuid, action.actionUuid);
|
||||||
|
if (!modelUuid) return;
|
||||||
|
|
||||||
|
incrementCraneLoad(modelUuid, 1);
|
||||||
|
addCurrentAction(modelUuid, action.actionUuid);
|
||||||
|
addCurrentMaterial(modelUuid, material.materialType, material.materialId);
|
||||||
|
|
||||||
|
pickAndDropLogStatus(material.materialName, `performing pickAndDrop action`);
|
||||||
|
|
||||||
|
}, [getMaterialById]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handlePickAndDrop,
|
||||||
|
};
|
||||||
|
}
|
||||||
36
app/src/modules/simulation/actions/crane/useCraneActions.ts
Normal file
36
app/src/modules/simulation/actions/crane/useCraneActions.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useEffect, useCallback } from 'react';
|
||||||
|
import { usePickAndDropHandler } from './actionHandler/usePickAndDropHandler';
|
||||||
|
|
||||||
|
export function useCraneActions() {
|
||||||
|
const { handlePickAndDrop } = usePickAndDropHandler();
|
||||||
|
|
||||||
|
const handleWorkerAction = useCallback((action: CraneAction, materialId: string) => {
|
||||||
|
handlePickAndDrop(action, materialId);
|
||||||
|
}, [handlePickAndDrop]);
|
||||||
|
|
||||||
|
const handleCraneAction = useCallback((action: CraneAction, materialId: string) => {
|
||||||
|
if (!action) return;
|
||||||
|
|
||||||
|
switch (action.actionType) {
|
||||||
|
case 'pickAndDrop':
|
||||||
|
handleWorkerAction(action, materialId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.warn(`Unknown Human action type: ${action.actionType}`);
|
||||||
|
}
|
||||||
|
}, [handleWorkerAction]);
|
||||||
|
|
||||||
|
const cleanup = useCallback(() => {
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
cleanup();
|
||||||
|
};
|
||||||
|
}, [cleanup]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleCraneAction,
|
||||||
|
cleanup
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import { useRoboticArmActions } from "./roboticArm/useRoboticArmActions";
|
|||||||
import { useStorageActions } from "./storageUnit/useStorageUnitActions";
|
import { useStorageActions } from "./storageUnit/useStorageUnitActions";
|
||||||
import { useVehicleActions } from "./vehicle/useVehicleActions";
|
import { useVehicleActions } from "./vehicle/useVehicleActions";
|
||||||
import { useHumanActions } from "./human/useHumanActions";
|
import { useHumanActions } from "./human/useHumanActions";
|
||||||
|
import { useCraneActions } from "./crane/useCraneActions";
|
||||||
import { useCallback, useEffect } from "react";
|
import { useCallback, useEffect } from "react";
|
||||||
|
|
||||||
export function useActionHandler() {
|
export function useActionHandler() {
|
||||||
@@ -19,6 +20,7 @@ export function useActionHandler() {
|
|||||||
const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions();
|
const { handleMachineAction, cleanup: cleanupMachine } = useMachineActions();
|
||||||
const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions();
|
const { handleStorageAction, cleanup: cleanupStorage } = useStorageActions();
|
||||||
const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions();
|
const { handleHumanAction, cleanup: cleanupHuman } = useHumanActions();
|
||||||
|
const { handleCraneAction, cleanup: cleanupCrane } = useCraneActions();
|
||||||
|
|
||||||
const handleAction = useCallback((action: Action, materialId?: string) => {
|
const handleAction = useCallback((action: Action, materialId?: string) => {
|
||||||
if (!action) return;
|
if (!action) return;
|
||||||
@@ -42,6 +44,9 @@ export function useActionHandler() {
|
|||||||
case 'worker': case 'assembly':
|
case 'worker': case 'assembly':
|
||||||
handleHumanAction(action as HumanAction, materialId as string);
|
handleHumanAction(action as HumanAction, materialId as string);
|
||||||
break;
|
break;
|
||||||
|
case 'pickAndDrop':
|
||||||
|
handleCraneAction(action as CraneAction, materialId as string);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
console.warn(`Unknown action type: ${(action as Action).actionType}`);
|
console.warn(`Unknown action type: ${(action as Action).actionType}`);
|
||||||
}
|
}
|
||||||
@@ -49,7 +54,7 @@ export function useActionHandler() {
|
|||||||
echo.error("Failed to handle action");
|
echo.error("Failed to handle action");
|
||||||
console.error("Error handling action:", error);
|
console.error("Error handling action:", error);
|
||||||
}
|
}
|
||||||
}, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction]);
|
}, [handleConveyorAction, handleVehicleAction, handleRoboticArmAction, handleMachineAction, handleStorageAction, handleHumanAction, handleCraneAction]);
|
||||||
|
|
||||||
const cleanup = useCallback(() => {
|
const cleanup = useCallback(() => {
|
||||||
cleanupConveyor();
|
cleanupConveyor();
|
||||||
@@ -58,7 +63,8 @@ export function useActionHandler() {
|
|||||||
cleanupMachine();
|
cleanupMachine();
|
||||||
cleanupStorage();
|
cleanupStorage();
|
||||||
cleanupHuman();
|
cleanupHuman();
|
||||||
}, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman]);
|
cleanupCrane();
|
||||||
|
}, [cleanupConveyor, cleanupVehicle, cleanupRoboticArm, cleanupMachine, cleanupStorage, cleanupHuman, cleanupCrane]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useFrame } from '@react-three/fiber';
|
||||||
|
import { usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../store/usePlayButtonStore';
|
||||||
|
import { useSceneContext } from '../../../scene/sceneContext';
|
||||||
|
import { useProductContext } from '../../products/productContext';
|
||||||
|
|
||||||
|
export function useCraneEventManager() {
|
||||||
|
const { craneStore, productStore, assetStore, craneEventManagerRef } = useSceneContext();
|
||||||
|
const { getCraneById, setCurrentPhase, removeCurrentAction } = craneStore();
|
||||||
|
const { getAssetById } = assetStore();
|
||||||
|
const { getActionByUuid } = productStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { isPaused } = usePauseButtonStore();
|
||||||
|
const { isReset } = useResetButtonStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ((isReset || !isPlaying) && craneEventManagerRef.current) {
|
||||||
|
craneEventManagerRef.current.craneStates = [];
|
||||||
|
}
|
||||||
|
}, [isReset, isPlaying]);
|
||||||
|
|
||||||
|
const addCraneToMonitor = (craneId: string, callback: () => void, actionUuid: string) => {
|
||||||
|
const crane = getCraneById(craneId);
|
||||||
|
const action = getActionByUuid(selectedProduct.productUuid, actionUuid) as CraneAction | undefined;
|
||||||
|
|
||||||
|
if (!crane || !action || action.actionType !== 'pickAndDrop' || !craneEventManagerRef.current) return;
|
||||||
|
|
||||||
|
let state = craneEventManagerRef.current.craneStates.find(c => c.craneId === craneId);
|
||||||
|
if (!state) {
|
||||||
|
state = {
|
||||||
|
craneId,
|
||||||
|
pendingActions: [],
|
||||||
|
currentAction: null,
|
||||||
|
isProcessing: false
|
||||||
|
};
|
||||||
|
craneEventManagerRef.current.craneStates.push(state);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.pendingActions.push({
|
||||||
|
actionUuid,
|
||||||
|
callback
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!state.isProcessing) {
|
||||||
|
processNextAction(state);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const processNextAction = (state: CraneEventState) => {
|
||||||
|
if (state.pendingActions.length === 0) {
|
||||||
|
state.currentAction = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.isProcessing = true;
|
||||||
|
state.currentAction = state.pendingActions.shift() || null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const completeCurrentAction = (state: CraneEventState) => {
|
||||||
|
processNextAction(state);
|
||||||
|
};
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (!craneEventManagerRef.current || craneEventManagerRef.current.craneStates.length === 0 || !isPlaying || isPaused) return;
|
||||||
|
|
||||||
|
for (const craneState of craneEventManagerRef.current.craneStates) {
|
||||||
|
if (!craneState.currentAction || !craneState.isProcessing) continue;
|
||||||
|
|
||||||
|
const { craneId, currentAction } = craneState;
|
||||||
|
const crane = getCraneById(craneId);
|
||||||
|
const craneAsset = getAssetById(craneId);
|
||||||
|
const currentCraneAction = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '') as CraneAction | undefined;
|
||||||
|
|
||||||
|
if (!crane || !craneAsset || !currentCraneAction) continue;
|
||||||
|
if (crane.isActive || crane.state !== "idle") continue;
|
||||||
|
|
||||||
|
if (currentCraneAction.actionType === 'pickAndDrop' && crane.currentLoad < currentCraneAction.maxPickUpCount) {
|
||||||
|
if (currentAction.actionUuid !== crane.currentAction?.actionUuid) {
|
||||||
|
setCurrentPhase(crane.modelUuid, 'init');
|
||||||
|
removeCurrentAction(crane.modelUuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
craneState.isProcessing = false;
|
||||||
|
currentAction.callback();
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
completeCurrentAction(craneState);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
addCraneToMonitor
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,82 +1,79 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
import * as THREE from 'three';
|
import * as THREE from 'three';
|
||||||
import { useEffect, useMemo } from 'react';
|
import { useFrame, useThree } from '@react-three/fiber';
|
||||||
import { useThree } from '@react-three/fiber';
|
import { useAnimationPlaySpeed, usePauseButtonStore, usePlayButtonStore, useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
|
|
||||||
function PillarJibAnimator({ crane }: { crane: CraneStatus }) {
|
function PillarJibAnimator({
|
||||||
|
crane,
|
||||||
|
points,
|
||||||
|
setPoints,
|
||||||
|
animationPhase,
|
||||||
|
setAnimationPhase,
|
||||||
|
onAnimationComplete
|
||||||
|
}: {
|
||||||
|
crane: CraneStatus;
|
||||||
|
points: [THREE.Vector3, THREE.Vector3] | null;
|
||||||
|
setPoints: (points: [THREE.Vector3, THREE.Vector3] | null) => void;
|
||||||
|
animationPhase: string;
|
||||||
|
setAnimationPhase: (phase: string) => void;
|
||||||
|
onAnimationComplete: (action: string) => void;
|
||||||
|
}) {
|
||||||
const { scene } = useThree();
|
const { scene } = useThree();
|
||||||
|
const { assetStore } = useSceneContext();
|
||||||
|
const { resetAsset } = assetStore();
|
||||||
|
const { isPaused } = usePauseButtonStore();
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { isReset } = useResetButtonStore();
|
||||||
|
const { speed } = useAnimationPlaySpeed();
|
||||||
|
|
||||||
|
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isPlaying || isReset) {
|
||||||
|
resetAsset(crane.modelUuid);
|
||||||
|
setAnimationPhase('idle');
|
||||||
|
setPoints(null);
|
||||||
|
}
|
||||||
|
}, [isPlaying, scene, crane.modelUuid, isReset]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||||
|
|
||||||
if (model) {
|
if (!model) return;
|
||||||
const base = model.getObjectByName('base');
|
const hook = model.getObjectByName('hook');
|
||||||
const trolley = model.getObjectByName('trolley');
|
|
||||||
const hook = model.getObjectByName('hook');
|
|
||||||
|
|
||||||
if (base && trolley && hook) {
|
if (!hook) return;
|
||||||
let trolleyDir = 1;
|
const hookWorld = new THREE.Vector3();
|
||||||
let hookDir = 1;
|
hook.getWorldPosition(hookWorld);
|
||||||
|
|
||||||
const trolleySpeed = 0.01;
|
if (crane.currentPhase === 'init-pickup') {
|
||||||
const hookSpeed = 0.01;
|
if (crane.currentMaterials.length > 0) {
|
||||||
const rotationSpeed = 0.005;
|
const material = scene.getObjectByProperty('uuid', crane.currentMaterials[0].materialId);
|
||||||
|
if (material) {
|
||||||
const trolleyMinOffset = -1;
|
const materialWorld = new THREE.Vector3();
|
||||||
const trolleyMaxOffset = 1.75;
|
material.getWorldPosition(materialWorld);
|
||||||
const hookMinOffset = 0.25;
|
setAnimationPhase('init-hook-adjust');
|
||||||
const hookMaxOffset = -1.5;
|
setPoints(
|
||||||
|
[
|
||||||
const originalTrolleyX = trolley.position.x;
|
new THREE.Vector3(hookWorld.x, hookWorld.y, hookWorld.z),
|
||||||
const originalHookY = hook.position.y;
|
new THREE.Vector3(materialWorld.x, materialWorld.y + 0.5, materialWorld.z)
|
||||||
|
]
|
||||||
const animate = () => {
|
);
|
||||||
if (base) {
|
}
|
||||||
base.rotation.y += rotationSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (trolley) {
|
|
||||||
trolley.position.x += trolleyDir * trolleySpeed;
|
|
||||||
if (trolley.position.x >= originalTrolleyX + trolleyMaxOffset ||
|
|
||||||
trolley.position.x <= originalTrolleyX + trolleyMinOffset) {
|
|
||||||
trolleyDir *= -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hook) {
|
|
||||||
hook.position.y += hookDir * hookSpeed;
|
|
||||||
if (hook.position.y >= originalHookY + hookMinOffset ||
|
|
||||||
hook.position.y <= originalHookY + hookMaxOffset) {
|
|
||||||
hookDir *= -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
};
|
|
||||||
|
|
||||||
animate();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [crane, scene]);
|
}, [crane.currentPhase])
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
<PillarJibHelper crane={crane} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PillarJibAnimator;
|
|
||||||
|
|
||||||
function PillarJibHelper({ crane }: { crane: CraneStatus }) {
|
|
||||||
const { scene } = useThree();
|
|
||||||
|
|
||||||
const { geometry, position } = useMemo(() => {
|
|
||||||
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||||
if (!model) return { geometry: null, position: null };
|
if (!model) return;
|
||||||
|
|
||||||
const base = model.getObjectByName('base');
|
const base = model.getObjectByName('base');
|
||||||
const trolley = model.getObjectByName('trolley');
|
const trolley = model.getObjectByName('trolley');
|
||||||
const hook = model.getObjectByName('hook');
|
const hook = model.getObjectByName('hook');
|
||||||
|
|
||||||
if (!base || !trolley || !hook) return { geometry: null, position: null };
|
if (!base || !trolley || !hook || !points) return;
|
||||||
|
|
||||||
const baseWorld = new THREE.Vector3();
|
const baseWorld = new THREE.Vector3();
|
||||||
base.getWorldPosition(baseWorld);
|
base.getWorldPosition(baseWorld);
|
||||||
@@ -87,47 +84,244 @@ function PillarJibHelper({ crane }: { crane: CraneStatus }) {
|
|||||||
const hookWorld = new THREE.Vector3();
|
const hookWorld = new THREE.Vector3();
|
||||||
hook.getWorldPosition(hookWorld);
|
hook.getWorldPosition(hookWorld);
|
||||||
|
|
||||||
|
const trolleyMinOffset = -1;
|
||||||
|
const trolleyMaxOffset = 1.75;
|
||||||
|
const hookMinOffset = 0.25;
|
||||||
|
const hookMaxOffset = -1.5;
|
||||||
|
|
||||||
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
|
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
|
||||||
const outerRadius = distFromBase + 1.75;
|
const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
|
||||||
const innerRadius = Math.max(distFromBase - 1, 0.05);
|
const outerRadius = Math.max(
|
||||||
const height = (0.25 - (-1.5));
|
new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length() + trolleyMaxOffset,
|
||||||
const cylinderYPosition = hookWorld.y + (height / 2) + (-1.5 + 0.25) / 2;
|
innerRadius
|
||||||
|
);
|
||||||
|
|
||||||
const shape = new THREE.Shape();
|
const yMin = hookWorld.y + hookMaxOffset;
|
||||||
shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);
|
const yMax = hookWorld.y + hookMinOffset;
|
||||||
|
|
||||||
const hole = new THREE.Path();
|
function clampToCylinder(pos: THREE.Vector3) {
|
||||||
hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
|
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
|
||||||
shape.holes.push(hole);
|
const distance = xzDist.length();
|
||||||
|
|
||||||
const extrudeSettings = {
|
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
|
||||||
depth: height,
|
if (distance > 0) {
|
||||||
bevelEnabled: false,
|
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
|
||||||
steps: 1
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
|
||||||
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
|
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
|
||||||
|
|
||||||
return { geometry, position };
|
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
|
||||||
}, [scene, crane.modelUuid]);
|
}
|
||||||
|
|
||||||
if (!geometry || !position) return null;
|
const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
|
||||||
|
const newIsInside: [boolean, boolean] = [false, false];
|
||||||
|
|
||||||
|
points.forEach((point, i) => {
|
||||||
|
const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
|
||||||
|
const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
|
||||||
|
const insideY = point.y >= yMin && point.y <= yMax;
|
||||||
|
newIsInside[i] = insideXZ && insideY;
|
||||||
|
|
||||||
|
newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
|
||||||
|
});
|
||||||
|
|
||||||
|
setClampedPoints(newClampedPoints);
|
||||||
|
}, [crane.modelUuid, points]);
|
||||||
|
|
||||||
|
useFrame(() => {
|
||||||
|
if (!isPlaying || isPaused || !points || !clampedPoints || animationPhase === 'idle') return;
|
||||||
|
|
||||||
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
const base = model.getObjectByName('base');
|
||||||
|
const trolley = model.getObjectByName('trolley');
|
||||||
|
const hook = model.getObjectByName('hook');
|
||||||
|
|
||||||
|
if (!base || !trolley || !hook || !trolley.parent) return;
|
||||||
|
|
||||||
|
const baseWorld = new THREE.Vector3();
|
||||||
|
base.getWorldPosition(baseWorld);
|
||||||
|
|
||||||
|
const hookWorld = new THREE.Vector3();
|
||||||
|
hook.getWorldPosition(hookWorld);
|
||||||
|
|
||||||
|
if (!model.userData.animationData) {
|
||||||
|
model.userData.animationData = {
|
||||||
|
originalHookY: hook.position.y,
|
||||||
|
targetHookY: clampedPoints[0].y - baseWorld.y,
|
||||||
|
targetDirection: new THREE.Vector2(),
|
||||||
|
targetTrolleyX: 0,
|
||||||
|
targetWorldPosition: clampedPoints[0].clone(),
|
||||||
|
finalHookTargetY: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const { animationData } = model.userData;
|
||||||
|
const hookSpeed = 0.01 * speed;
|
||||||
|
const rotationSpeed = 0.005 * speed;
|
||||||
|
const trolleySpeed = 0.01 * speed;
|
||||||
|
|
||||||
|
switch (animationPhase) {
|
||||||
|
case 'init-hook-adjust': {
|
||||||
|
const hookWorld = new THREE.Vector3();
|
||||||
|
hook.getWorldPosition(hookWorld);
|
||||||
|
const direction = Math.sign((clampedPoints[0].y - baseWorld.y) - hookWorld.y);
|
||||||
|
hook.position.y += direction * hookSpeed;
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(hookWorld.y - clampedPoints[0].y).toFixed(2)) < 0.05) {
|
||||||
|
setAnimationPhase('init-rotate-base');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'init-rotate-base': {
|
||||||
|
const baseForward = new THREE.Vector3(1, 0, 0);
|
||||||
|
base.localToWorld(baseForward);
|
||||||
|
baseForward.sub(baseWorld);
|
||||||
|
|
||||||
|
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
|
||||||
|
|
||||||
|
const targetWorld = clampedPoints[0];
|
||||||
|
const targetDir = new THREE.Vector2(
|
||||||
|
targetWorld.x - baseWorld.x,
|
||||||
|
targetWorld.z - baseWorld.z
|
||||||
|
).normalize();
|
||||||
|
|
||||||
|
const currentAngle = Math.atan2(currentDir.y, currentDir.x);
|
||||||
|
const targetAngle = Math.atan2(targetDir.y, targetDir.x);
|
||||||
|
let angleDiff = currentAngle - targetAngle;
|
||||||
|
|
||||||
|
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) {
|
||||||
|
base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
|
||||||
|
} else {
|
||||||
|
base.rotation.y += angleDiff;
|
||||||
|
const localTarget = trolley.parent.worldToLocal(clampedPoints[0].clone());
|
||||||
|
animationData.targetTrolleyX = localTarget?.x;
|
||||||
|
setAnimationPhase('init-move-trolley');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'init-move-trolley': {
|
||||||
|
const dx = animationData.targetTrolleyX - trolley.position.x;
|
||||||
|
const direction = Math.sign(dx);
|
||||||
|
trolley.position.x += direction * trolleySpeed;
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) {
|
||||||
|
trolley.position.x = animationData.targetTrolleyX;
|
||||||
|
|
||||||
|
animationData.finalHookTargetY = hook.position.y;
|
||||||
|
setAnimationPhase('init-final-hook-adjust');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'init-final-hook-adjust': {
|
||||||
|
const dy = animationData.finalHookTargetY - hook.position.y;
|
||||||
|
const direction = Math.sign(dy);
|
||||||
|
hook.position.y += direction * hookSpeed;
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
|
||||||
|
hook.position.y = animationData.finalHookTargetY;
|
||||||
|
|
||||||
|
model.userData.animationData = {
|
||||||
|
originalHookY: hook.position.y,
|
||||||
|
targetHookY: clampedPoints[1].y - baseWorld.y + 0.5,
|
||||||
|
targetDirection: new THREE.Vector2(),
|
||||||
|
targetTrolleyX: 0,
|
||||||
|
targetWorldPosition: clampedPoints[1].clone(),
|
||||||
|
finalHookTargetY: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
setAnimationPhase('starting');
|
||||||
|
onAnimationComplete('starting');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'first-hook-adjust': {
|
||||||
|
const direction = Math.sign(animationData.targetHookY - hook.position.y);
|
||||||
|
hook.position.y += direction * hookSpeed;
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(hook.position.y - animationData.targetHookY).toFixed(2)) < 0.05) {
|
||||||
|
hook.position.y = animationData.targetHookY;
|
||||||
|
setAnimationPhase('first-rotate-base');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'first-rotate-base': {
|
||||||
|
const baseForward = new THREE.Vector3(1, 0, 0);
|
||||||
|
base.localToWorld(baseForward);
|
||||||
|
baseForward.sub(baseWorld);
|
||||||
|
|
||||||
|
const currentDir = new THREE.Vector2(baseForward.x, baseForward.z).normalize();
|
||||||
|
|
||||||
|
const targetWorld = clampedPoints[1];
|
||||||
|
const targetDir = new THREE.Vector2(
|
||||||
|
targetWorld.x - baseWorld.x,
|
||||||
|
targetWorld.z - baseWorld.z
|
||||||
|
).normalize();
|
||||||
|
|
||||||
|
const currentAngle = Math.atan2(currentDir.y, currentDir.x);
|
||||||
|
const targetAngle = Math.atan2(targetDir.y, targetDir.x);
|
||||||
|
let angleDiff = currentAngle - targetAngle;
|
||||||
|
|
||||||
|
angleDiff = Math.atan2(Math.sin(angleDiff), Math.cos(angleDiff));
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(angleDiff).toFixed(2)) > 0.05) {
|
||||||
|
base.rotation.y += Math.sign(angleDiff) * rotationSpeed;
|
||||||
|
} else {
|
||||||
|
base.rotation.y += angleDiff;
|
||||||
|
const localTarget = trolley.parent.worldToLocal(clampedPoints[1].clone());
|
||||||
|
animationData.targetTrolleyX = localTarget?.x;
|
||||||
|
setAnimationPhase('first-move-trolley');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'first-move-trolley': {
|
||||||
|
const dx = animationData.targetTrolleyX - trolley.position.x;
|
||||||
|
const direction = Math.sign(dx);
|
||||||
|
trolley.position.x += direction * trolleySpeed;
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(dx).toFixed(2)) < 0.05) {
|
||||||
|
trolley.position.x = animationData.targetTrolleyX;
|
||||||
|
|
||||||
|
animationData.finalHookTargetY = hook.position.y - 0.5;
|
||||||
|
setAnimationPhase('first-final-hook-adjust');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'first-final-hook-adjust': {
|
||||||
|
const dy = animationData.finalHookTargetY - hook.position.y;
|
||||||
|
const direction = Math.sign(dy);
|
||||||
|
hook.position.y += direction * hookSpeed;
|
||||||
|
|
||||||
|
if (parseFloat(Math.abs(dy).toFixed(2)) < 0.05) {
|
||||||
|
hook.position.y = animationData.finalHookTargetY;
|
||||||
|
if (crane.currentPhase === 'init-pickup') {
|
||||||
|
setAnimationPhase('picking');
|
||||||
|
onAnimationComplete('picking');
|
||||||
|
} else if (crane.currentPhase === 'pickup-drop') {
|
||||||
|
setAnimationPhase('dropping');
|
||||||
|
onAnimationComplete('dropping');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<mesh
|
<>
|
||||||
geometry={geometry}
|
</>
|
||||||
position={position}
|
|
||||||
rotation={[Math.PI / 2, 0, 0]}
|
|
||||||
>
|
|
||||||
<meshStandardMaterial
|
|
||||||
color={0x888888}
|
|
||||||
metalness={0.5}
|
|
||||||
roughness={0.4}
|
|
||||||
side={THREE.DoubleSide}
|
|
||||||
transparent={true}
|
|
||||||
opacity={0.3}
|
|
||||||
/>
|
|
||||||
</mesh>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default PillarJibAnimator;
|
||||||
@@ -12,7 +12,7 @@ function CraneInstances() {
|
|||||||
<React.Fragment key={crane.modelUuid}>
|
<React.Fragment key={crane.modelUuid}>
|
||||||
|
|
||||||
{crane.subType === "pillarJib" &&
|
{crane.subType === "pillarJib" &&
|
||||||
<PillarJibInstance crane={crane} />
|
<PillarJibInstance key={crane.modelUuid} crane={crane} />
|
||||||
}
|
}
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
import { useMemo, useState } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import { Box, Sphere } from "@react-three/drei";
|
||||||
|
|
||||||
|
function PillarJibHelper({
|
||||||
|
crane,
|
||||||
|
points
|
||||||
|
}: {
|
||||||
|
crane: CraneStatus,
|
||||||
|
points: [THREE.Vector3, THREE.Vector3] | null;
|
||||||
|
}) {
|
||||||
|
const { scene } = useThree();
|
||||||
|
const [clampedPoints, setClampedPoints] = useState<[THREE.Vector3, THREE.Vector3]>();
|
||||||
|
const [isInside, setIsInside] = useState<[boolean, boolean]>([false, false]);
|
||||||
|
|
||||||
|
const { geometry, position } = useMemo(() => {
|
||||||
|
const model = scene.getObjectByProperty('uuid', crane.modelUuid);
|
||||||
|
if (!model) return { geometry: null, position: null };
|
||||||
|
|
||||||
|
const base = model.getObjectByName('base');
|
||||||
|
const trolley = model.getObjectByName('trolley');
|
||||||
|
const hook = model.getObjectByName('hook');
|
||||||
|
|
||||||
|
if (!base || !trolley || !hook || !points) return { geometry: null, position: null };
|
||||||
|
|
||||||
|
const baseWorld = new THREE.Vector3();
|
||||||
|
base.getWorldPosition(baseWorld);
|
||||||
|
|
||||||
|
const trolleyWorld = new THREE.Vector3();
|
||||||
|
trolley.getWorldPosition(trolleyWorld);
|
||||||
|
|
||||||
|
const hookWorld = new THREE.Vector3();
|
||||||
|
hook.getWorldPosition(hookWorld);
|
||||||
|
|
||||||
|
const trolleyMinOffset = -1;
|
||||||
|
const trolleyMaxOffset = 1.75;
|
||||||
|
const hookMinOffset = 0.25;
|
||||||
|
const hookMaxOffset = -1.5;
|
||||||
|
|
||||||
|
const distFromBase = new THREE.Vector2(trolleyWorld.x - baseWorld.x, trolleyWorld.z - baseWorld.z).length();
|
||||||
|
const outerRadius = distFromBase + trolleyMaxOffset;
|
||||||
|
const innerRadius = Math.max(distFromBase + trolleyMinOffset, 0.05);
|
||||||
|
const height = (hookMinOffset - (hookMaxOffset));
|
||||||
|
const cylinderYPosition = hookWorld.y + (height / 2) + (hookMaxOffset + hookMinOffset) / 2;
|
||||||
|
|
||||||
|
const shape = new THREE.Shape();
|
||||||
|
shape.absarc(0, 0, outerRadius, 0, Math.PI * 2, false);
|
||||||
|
|
||||||
|
const hole = new THREE.Path();
|
||||||
|
hole.absarc(0, 0, innerRadius, 0, Math.PI * 2, true);
|
||||||
|
shape.holes.push(hole);
|
||||||
|
|
||||||
|
const extrudeSettings = {
|
||||||
|
depth: height,
|
||||||
|
bevelEnabled: false,
|
||||||
|
steps: 1
|
||||||
|
};
|
||||||
|
|
||||||
|
const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings);
|
||||||
|
const position: [number, number, number] = [baseWorld.x, cylinderYPosition, baseWorld.z];
|
||||||
|
|
||||||
|
const yMin = hookWorld.y + hookMaxOffset;
|
||||||
|
const yMax = hookWorld.y + hookMinOffset;
|
||||||
|
|
||||||
|
function clampToCylinder(pos: THREE.Vector3) {
|
||||||
|
const xzDist = new THREE.Vector2(pos.x - baseWorld.x, pos.z - baseWorld.z);
|
||||||
|
const distance = xzDist.length();
|
||||||
|
|
||||||
|
let clampedDistance = THREE.MathUtils.clamp(distance, innerRadius, outerRadius);
|
||||||
|
if (distance > 0) {
|
||||||
|
clampedDistance = Math.max(innerRadius, Math.min(distance, outerRadius));
|
||||||
|
}
|
||||||
|
|
||||||
|
const clampedXZ = xzDist.normalize().multiplyScalar(clampedDistance);
|
||||||
|
const y = THREE.MathUtils.clamp(pos.y, yMin, yMax);
|
||||||
|
|
||||||
|
return new THREE.Vector3(baseWorld.x + clampedXZ.x, y, baseWorld.z + clampedXZ.y);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newClampedPoints: [THREE.Vector3, THREE.Vector3] = [new THREE.Vector3(), new THREE.Vector3()];
|
||||||
|
const newIsInside: [boolean, boolean] = [false, false];
|
||||||
|
|
||||||
|
points.forEach((point, i) => {
|
||||||
|
const xzDist = new THREE.Vector2(point.x - baseWorld.x, point.z - baseWorld.z).length();
|
||||||
|
const insideXZ = xzDist >= innerRadius && xzDist <= outerRadius;
|
||||||
|
const insideY = point.y >= yMin && point.y <= yMax;
|
||||||
|
newIsInside[i] = insideXZ && insideY;
|
||||||
|
|
||||||
|
newClampedPoints[i] = newIsInside[i] ? point.clone() : clampToCylinder(point);
|
||||||
|
});
|
||||||
|
|
||||||
|
setClampedPoints(newClampedPoints);
|
||||||
|
setIsInside(newIsInside);
|
||||||
|
|
||||||
|
return { geometry, position };
|
||||||
|
}, [scene, crane.modelUuid, points]);
|
||||||
|
|
||||||
|
if (!geometry || !position) return null;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<mesh
|
||||||
|
geometry={geometry}
|
||||||
|
position={position}
|
||||||
|
rotation={[Math.PI / 2, 0, 0]}
|
||||||
|
>
|
||||||
|
<meshStandardMaterial
|
||||||
|
color={0x888888}
|
||||||
|
metalness={0.5}
|
||||||
|
roughness={0.4}
|
||||||
|
side={THREE.DoubleSide}
|
||||||
|
transparent={true}
|
||||||
|
opacity={0.3}
|
||||||
|
/>
|
||||||
|
</mesh>
|
||||||
|
|
||||||
|
{points && points.map((point, i) => (
|
||||||
|
<Box
|
||||||
|
key={`original-${i}`}
|
||||||
|
position={point}
|
||||||
|
args={[0.15, 0.15, 0.15]}
|
||||||
|
>
|
||||||
|
<meshStandardMaterial color="yellow" />
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
|
||||||
|
{clampedPoints && clampedPoints.map((point, i) => (
|
||||||
|
<Sphere
|
||||||
|
key={`clamped-${i}`}
|
||||||
|
position={point}
|
||||||
|
args={[0.1, 16, 16]}
|
||||||
|
>
|
||||||
|
<meshStandardMaterial color={isInside[i] ? 'lightgreen' : 'orange'} />
|
||||||
|
</Sphere>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PillarJibHelper;
|
||||||
@@ -1,14 +1,61 @@
|
|||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import * as THREE from 'three'
|
||||||
|
import { usePlayButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||||
|
import { useSceneContext } from '../../../../scene/sceneContext';
|
||||||
|
import { useProductContext } from '../../../products/productContext';
|
||||||
|
|
||||||
import PillarJibAnimator from '../animator/pillarJibAnimator'
|
import PillarJibAnimator from '../animator/pillarJibAnimator'
|
||||||
|
import PillarJibHelper from '../helper/pillarJibHelper'
|
||||||
|
|
||||||
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
|
function PillarJibInstance({ crane }: { crane: CraneStatus }) {
|
||||||
|
const { isPlaying } = usePlayButtonStore();
|
||||||
|
const { craneStore, productStore } = useSceneContext();
|
||||||
|
const { getActionByUuid, getEventByModelUuid, getTriggerByUuid } = productStore();
|
||||||
|
const { getCraneById, setCurrentPhase } = craneStore();
|
||||||
|
const { selectedProductStore } = useProductContext();
|
||||||
|
const { selectedProduct } = selectedProductStore();
|
||||||
|
const [animationPhase, setAnimationPhase] = useState<string>('idle');
|
||||||
|
const [points, setPoints] = useState<[THREE.Vector3, THREE.Vector3] | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPlaying) {
|
||||||
|
const action = getActionByUuid(selectedProduct.productUuid, crane?.currentAction?.actionUuid || '');
|
||||||
|
if (!action || action.actionType !== 'pickAndDrop') return;
|
||||||
|
|
||||||
|
if (!crane.isActive && crane.currentPhase === 'init' && crane.currentMaterials.length > 0 && action.maxPickUpCount <= crane.currentMaterials.length) {
|
||||||
|
setCurrentPhase(crane.modelUuid, 'init-pickup');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [crane])
|
||||||
|
|
||||||
|
const handleAnimationComplete = (action: string) => {
|
||||||
|
if (action === 'starting') {
|
||||||
|
setAnimationPhase('first-hook-adjust');
|
||||||
|
} else if (action === 'picking') {
|
||||||
|
setCurrentPhase(crane.modelUuid, 'picking');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<PillarJibAnimator crane={crane} />
|
<PillarJibAnimator
|
||||||
|
key={crane.modelUuid}
|
||||||
|
crane={crane}
|
||||||
|
points={points}
|
||||||
|
setPoints={setPoints}
|
||||||
|
animationPhase={animationPhase}
|
||||||
|
setAnimationPhase={setAnimationPhase}
|
||||||
|
onAnimationComplete={handleAnimationComplete}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<PillarJibHelper
|
||||||
|
crane={crane}
|
||||||
|
points={points}
|
||||||
|
/>
|
||||||
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default PillarJibInstance
|
export default PillarJibInstance;
|
||||||
@@ -22,7 +22,7 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
|||||||
const trySetup = () => {
|
const trySetup = () => {
|
||||||
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
|
const targetMesh = scene?.getObjectByProperty("uuid", armBot.modelUuid);
|
||||||
|
|
||||||
if (!targetMesh || !targetMesh.userData.iks || targetMesh.userData.iks.length < 1) {
|
if (!targetMesh || !targetMesh.userData.fieldData || targetMesh.userData.fieldData.length < 1) {
|
||||||
retryId = setTimeout(trySetup, 100);
|
retryId = setTimeout(trySetup, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -34,8 +34,8 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
|||||||
});
|
});
|
||||||
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
|
if (!OOI.Target_Bone || !OOI.Skinned_Mesh) return;
|
||||||
|
|
||||||
const rawIks: IK[] = targetMesh.userData.iks;
|
const rawIks: IK[] = targetMesh.userData.fieldData;
|
||||||
const iks = rawIks.map((ik) => ({
|
const fieldData = rawIks.map((ik) => ({
|
||||||
target: ik.target,
|
target: ik.target,
|
||||||
effector: ik.effector,
|
effector: ik.effector,
|
||||||
links: ik.links.map((link) => ({
|
links: ik.links.map((link) => ({
|
||||||
@@ -51,10 +51,10 @@ function IKInstance({ setIkSolver, armBot }: IKInstanceProps) {
|
|||||||
minheight: ik.minheight,
|
minheight: ik.minheight,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const solver = new CCDIKSolver(OOI.Skinned_Mesh, iks);
|
const solver = new CCDIKSolver(OOI.Skinned_Mesh, fieldData);
|
||||||
setIkSolver(solver);
|
setIkSolver(solver);
|
||||||
|
|
||||||
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, iks, 0.05)
|
// const helper = new CCDIKHelper(OOI.Skinned_Mesh, fieldData, 0.05)
|
||||||
// scene.add(helper);
|
// scene.add(helper);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -173,8 +173,8 @@ const ArmBotUI = () => {
|
|||||||
|
|
||||||
const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || '');
|
const targetMesh = scene?.getObjectByProperty("uuid", selectedArmBotData?.modelUuid || '');
|
||||||
|
|
||||||
const iks = targetMesh?.userData?.iks;
|
const fieldData = targetMesh?.userData?.fieldData;
|
||||||
const firstIK = Array.isArray(iks) && iks.length > 0 ? iks[0] : {};
|
const firstIK = Array.isArray(fieldData) && fieldData.length > 0 ? fieldData[0] : {};
|
||||||
|
|
||||||
const { handlePointerDown } = useDraggableGLTF(
|
const { handlePointerDown } = useDraggableGLTF(
|
||||||
updatePointToState,
|
updatePointToState,
|
||||||
|
|||||||
@@ -7,9 +7,10 @@ import { useMachineEventManager } from '../../machine/eventManager/useMachineEve
|
|||||||
import { useSceneContext } from '../../../scene/sceneContext';
|
import { useSceneContext } from '../../../scene/sceneContext';
|
||||||
import { useProductContext } from '../../products/productContext';
|
import { useProductContext } from '../../products/productContext';
|
||||||
import { useHumanEventManager } from '../../human/eventManager/useHumanEventManager';
|
import { useHumanEventManager } from '../../human/eventManager/useHumanEventManager';
|
||||||
|
import { useCraneEventManager } from '../../crane/eventManager/useCraneEventManager';
|
||||||
|
|
||||||
export function useTriggerHandler() {
|
export function useTriggerHandler() {
|
||||||
const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, storageUnitStore, productStore } = useSceneContext();
|
const { materialStore, armBotStore, machineStore, conveyorStore, vehicleStore, humanStore, craneStore, storageUnitStore, productStore } = useSceneContext();
|
||||||
const { selectedProductStore } = useProductContext();
|
const { selectedProductStore } = useProductContext();
|
||||||
const { handleAction } = useActionHandler();
|
const { handleAction } = useActionHandler();
|
||||||
const { selectedProduct } = selectedProductStore();
|
const { selectedProduct } = selectedProductStore();
|
||||||
@@ -21,8 +22,10 @@ export function useTriggerHandler() {
|
|||||||
const { addVehicleToMonitor } = useVehicleEventManager();
|
const { addVehicleToMonitor } = useVehicleEventManager();
|
||||||
const { addMachineToMonitor } = useMachineEventManager();
|
const { addMachineToMonitor } = useMachineEventManager();
|
||||||
const { addHumanToMonitor } = useHumanEventManager();
|
const { addHumanToMonitor } = useHumanEventManager();
|
||||||
|
const { addCraneToMonitor } = useCraneEventManager();
|
||||||
const { getVehicleById } = vehicleStore();
|
const { getVehicleById } = vehicleStore();
|
||||||
const { getHumanById, setHumanScheduled } = humanStore();
|
const { getHumanById, setHumanScheduled } = humanStore();
|
||||||
|
const { getCraneById, setCraneScheduled } = craneStore();
|
||||||
const { getMachineById, setMachineActive } = machineStore();
|
const { getMachineById, setMachineActive } = machineStore();
|
||||||
const { getStorageUnitById } = storageUnitStore();
|
const { getStorageUnitById } = storageUnitStore();
|
||||||
const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore();
|
const { getMaterialById, setCurrentLocation, setNextLocation, setPreviousLocation, setIsPaused, setIsVisible, setEndTime } = materialStore();
|
||||||
@@ -388,6 +391,65 @@ export function useTriggerHandler() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (toEvent?.type === 'crane') {
|
||||||
|
// Transfer to Human
|
||||||
|
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||||
|
const material = getMaterialById(materialId);
|
||||||
|
if (material) {
|
||||||
|
|
||||||
|
// Handle current action of the material
|
||||||
|
handleAction(action, materialId);
|
||||||
|
|
||||||
|
if (material.next) {
|
||||||
|
const action = getActionByUuid(selectedProduct.productUuid, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||||
|
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||||
|
|
||||||
|
setPreviousLocation(material.materialId, {
|
||||||
|
modelUuid: material.current.modelUuid,
|
||||||
|
pointUuid: material.current.pointUuid,
|
||||||
|
actionUuid: material.current.actionUuid,
|
||||||
|
})
|
||||||
|
|
||||||
|
setCurrentLocation(material.materialId, {
|
||||||
|
modelUuid: material.next.modelUuid,
|
||||||
|
pointUuid: material.next.pointUuid,
|
||||||
|
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||||
|
});
|
||||||
|
|
||||||
|
setNextLocation(material.materialId, null);
|
||||||
|
|
||||||
|
if (action) {
|
||||||
|
|
||||||
|
if (crane) {
|
||||||
|
if (action && action.triggers.length > 0 &&
|
||||||
|
action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid &&
|
||||||
|
action.triggers[0]?.triggeredAsset?.triggeredAction?.actionUuid &&
|
||||||
|
action.triggers[0]?.triggeredAsset?.triggeredPoint?.pointUuid) {
|
||||||
|
const model = getEventByModelUuid(selectedProduct.productUuid, action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid);
|
||||||
|
|
||||||
|
if (model?.type === 'transfer') {
|
||||||
|
const crane = getCraneById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||||
|
if (crane) {
|
||||||
|
setIsPaused(materialId, true);
|
||||||
|
setCraneScheduled(crane.modelUuid, true);
|
||||||
|
const conveyor = getConveyorById(action.triggers[0]?.triggeredAsset?.triggeredModel.modelUuid || '');
|
||||||
|
if (conveyor) {
|
||||||
|
addConveyorToMonitor(conveyor.modelUuid, () => {
|
||||||
|
addCraneToMonitor(crane.modelUuid, () => {
|
||||||
|
setIsPaused(materialId, true);
|
||||||
|
|
||||||
|
handleAction(action, materialId);
|
||||||
|
}, action.actionUuid)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (fromEvent?.type === 'vehicle') {
|
} else if (fromEvent?.type === 'vehicle') {
|
||||||
if (toEvent?.type === 'transfer') {
|
if (toEvent?.type === 'transfer') {
|
||||||
@@ -1547,6 +1609,29 @@ export function useTriggerHandler() {
|
|||||||
} else if (toEvent?.type === 'human') {
|
} else if (toEvent?.type === 'human') {
|
||||||
// Human to Human
|
// Human to Human
|
||||||
|
|
||||||
|
}
|
||||||
|
} else if (fromEvent?.type === 'crane') {
|
||||||
|
if (toEvent?.type === 'transfer') {
|
||||||
|
// Crane Unit to Transfer
|
||||||
|
|
||||||
|
} else if (toEvent?.type === 'vehicle') {
|
||||||
|
// Crane Unit to Vehicle
|
||||||
|
|
||||||
|
} else if (toEvent?.type === 'machine') {
|
||||||
|
// Crane Unit to Machine
|
||||||
|
|
||||||
|
} else if (toEvent?.type === 'roboticArm') {
|
||||||
|
// Crane Unit to Robotic Arm
|
||||||
|
|
||||||
|
} else if (toEvent?.type === 'storageUnit') {
|
||||||
|
// Crane Unit to Storage Unit
|
||||||
|
|
||||||
|
} else if (toEvent?.type === 'human') {
|
||||||
|
// Crane Unit to Human
|
||||||
|
|
||||||
|
} else if (toEvent?.type === 'crane') {
|
||||||
|
// Crane Unit to Human
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const Project: React.FC = () => {
|
|||||||
const { setUserName } = useUserName();
|
const { setUserName } = useUserName();
|
||||||
const { setOrganization } = useOrganization();
|
const { setOrganization } = useOrganization();
|
||||||
const { projectId } = useParams();
|
const { projectId } = useParams();
|
||||||
const { setProjectName } = useProjectName();
|
const { projectName, setProjectName } = useProjectName();
|
||||||
const { userId, email, organization, userName } = getUserData();
|
const { userId, email, organization, userName } = getUserData();
|
||||||
const { selectedUser } = useSelectedUserStore();
|
const { selectedUser } = useSelectedUserStore();
|
||||||
const { isLogListVisible } = useLogger();
|
const { isLogListVisible } = useLogger();
|
||||||
@@ -56,7 +56,6 @@ const Project: React.FC = () => {
|
|||||||
const matchedProject = allProjects.find(
|
const matchedProject = allProjects.find(
|
||||||
(val: any) => val.projectUuid === projectId || val._id === projectId
|
(val: any) => val.projectUuid === projectId || val._id === projectId
|
||||||
);
|
);
|
||||||
|
|
||||||
if (matchedProject) {
|
if (matchedProject) {
|
||||||
setProjectName(matchedProject.projectName);
|
setProjectName(matchedProject.projectName);
|
||||||
await viewProject(organization, matchedProject._id, userId);
|
await viewProject(organization, matchedProject._id, userId);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_REST_API_BASE_URL}`;
|
||||||
|
|
||||||
export const duplicateProject = async (
|
export const duplicateProject = async (
|
||||||
|
refProjectID: string,
|
||||||
projectUuid: string,
|
projectUuid: string,
|
||||||
thumbnail: string,
|
thumbnail: string,
|
||||||
projectName: string
|
projectName: string
|
||||||
@@ -16,27 +17,32 @@ export const duplicateProject = async (
|
|||||||
token: localStorage.getItem("token") || "", // Coerce null to empty string
|
token: localStorage.getItem("token") || "", // Coerce null to empty string
|
||||||
refresh_token: localStorage.getItem("refreshToken") || "",
|
refresh_token: localStorage.getItem("refreshToken") || "",
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ projectUuid, thumbnail, projectName }),
|
body: JSON.stringify({
|
||||||
|
refProjectID,
|
||||||
|
projectUuid,
|
||||||
|
thumbnail,
|
||||||
|
projectName,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const newAccessToken = response.headers.get("x-access-token");
|
const newAccessToken = response.headers.get("x-access-token");
|
||||||
if (newAccessToken) {
|
if (newAccessToken) {
|
||||||
//console.log("New token received:", newAccessToken);
|
//
|
||||||
localStorage.setItem("token", newAccessToken);
|
localStorage.setItem("token", newAccessToken);
|
||||||
}
|
}
|
||||||
// console.log("response: ", response);
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Failed to add project");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
// console.log("result: ", result);
|
//
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.log(error.message);
|
|
||||||
} else {
|
} else {
|
||||||
console.log("An unknown error occurred");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
let url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
|
||||||
|
|
||||||
export const getAssetIksApi = async (assetId: string) => {
|
export const getAssetFieldApi = async (assetId: string) => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
`${url_Backend_dwinzo}/api/v2/getAssetIks/${assetId}`,
|
`${url_Backend_dwinzo}/api/v2/getAssetField/${assetId}`,
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -20,13 +20,13 @@ export const getAssetIksApi = async (assetId: string) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error("Failed to fetch assetIks");
|
console.error("Failed to fetch asset field");
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
echo.error("Failed to get assetIks");
|
echo.error("Failed to get asset field");
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
console.log(error.message);
|
console.log(error.message);
|
||||||
} else {
|
} else {
|
||||||
78
app/src/store/builder/useUndoRedo3DStore.ts
Normal file
78
app/src/store/builder/useUndoRedo3DStore.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
import { immer } from 'zustand/middleware/immer';
|
||||||
|
import { undoRedoConfig } from '../../types/world/worldConstants';
|
||||||
|
|
||||||
|
type UndoRedo3DStore = {
|
||||||
|
undoStack: UndoRedo3DTypes[];
|
||||||
|
redoStack: UndoRedo3DTypes[];
|
||||||
|
|
||||||
|
push3D: (entry: UndoRedo3DTypes) => void;
|
||||||
|
undo3D: () => UndoRedo3DTypes | undefined;
|
||||||
|
redo3D: () => UndoRedo3DTypes | undefined;
|
||||||
|
clearUndoRedo3D: () => void;
|
||||||
|
|
||||||
|
peekUndo3D: () => UndoRedo3DTypes | undefined;
|
||||||
|
peekRedo3D: () => UndoRedo3DTypes | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createUndoRedo3DStore = () => {
|
||||||
|
return create<UndoRedo3DStore>()(
|
||||||
|
immer((set, get) => ({
|
||||||
|
undoStack: [],
|
||||||
|
redoStack: [],
|
||||||
|
|
||||||
|
push3D: (entry) => {
|
||||||
|
set((state) => {
|
||||||
|
state.undoStack.push(entry);
|
||||||
|
|
||||||
|
if (state.undoStack.length > undoRedoConfig.undoRedoCount) {
|
||||||
|
state.undoStack.shift();
|
||||||
|
}
|
||||||
|
|
||||||
|
state.redoStack = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
undo3D: () => {
|
||||||
|
let lastAction: UndoRedo3DTypes | undefined;
|
||||||
|
set((state) => {
|
||||||
|
lastAction = state.undoStack.pop();
|
||||||
|
if (lastAction) {
|
||||||
|
state.redoStack.unshift(lastAction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return lastAction;
|
||||||
|
},
|
||||||
|
|
||||||
|
redo3D: () => {
|
||||||
|
let redoAction: UndoRedo3DTypes | undefined;
|
||||||
|
set((state) => {
|
||||||
|
redoAction = state.redoStack.shift();
|
||||||
|
if (redoAction) {
|
||||||
|
state.undoStack.push(redoAction);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return redoAction;
|
||||||
|
},
|
||||||
|
|
||||||
|
clearUndoRedo3D: () => {
|
||||||
|
set((state) => {
|
||||||
|
state.undoStack = [];
|
||||||
|
state.redoStack = [];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
peekUndo3D: () => {
|
||||||
|
const stack = get().undoStack;
|
||||||
|
return stack.length > 0 ? stack[stack.length - 1] : undefined;
|
||||||
|
},
|
||||||
|
|
||||||
|
peekRedo3D: () => {
|
||||||
|
const stack = get().redoStack;
|
||||||
|
return stack.length > 0 ? stack[0] : undefined;
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UndoRedo3DStoreType = ReturnType<typeof createUndoRedo3DStore>;
|
||||||
65
app/src/types/builderTypes.d.ts
vendored
65
app/src/types/builderTypes.d.ts
vendored
@@ -254,3 +254,68 @@ type UndoRedo2D = {
|
|||||||
undoStack: UndoRedo2DTypes[];
|
undoStack: UndoRedo2DTypes[];
|
||||||
redoStack: UndoRedo2DTypes[];
|
redoStack: UndoRedo2DTypes[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// Undo/Redo 3D
|
||||||
|
|
||||||
|
type AssetType = {
|
||||||
|
type: "Asset";
|
||||||
|
assetData: Asset;
|
||||||
|
newData?: Asset;
|
||||||
|
eventMetaData?: EventsSchema;
|
||||||
|
timeStap: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type WallAssetType = {
|
||||||
|
type: "WallAsset";
|
||||||
|
assetData: WallAsset;
|
||||||
|
newData?: WallAsset;
|
||||||
|
timeStap: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AssetData = AssetType | WallAssetType;
|
||||||
|
|
||||||
|
type UndoRedo3DActionBuilderSchema = {
|
||||||
|
module: "builder";
|
||||||
|
actionType: "Asset-Add" | "Asset-Delete" | "Asset-Update" | "Asset-Duplicated" | "Asset-Copied" | "Wall-Asset-Add" | "Wall-Asset-Delete" | "Wall-Asset-Update";
|
||||||
|
asset: AssetData;
|
||||||
|
}
|
||||||
|
|
||||||
|
type UndoRedo3DActionSimulationSchema = {
|
||||||
|
module: "simulation";
|
||||||
|
actionType: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
type UndoRedo3DActionSchema = UndoRedo3DActionBuilderSchema | UndoRedo3DActionSimulationSchema;
|
||||||
|
|
||||||
|
type UndoRedo3DActionsBuilderSchema = {
|
||||||
|
module: "builder";
|
||||||
|
actionType: "Assets-Add" | "Assets-Delete" | "Assets-Update" | "Assets-Duplicated" | "Assets-Copied" | "Wall-Assets-Add" | "Wall-Assets-Delete" | "Wall-Assets-Update";
|
||||||
|
assets: AssetData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type UndoRedo3DActionsSimulationSchema = {
|
||||||
|
module: "simulation";
|
||||||
|
actionType: '';
|
||||||
|
}
|
||||||
|
|
||||||
|
type UndoRedo3DActionsSchema = UndoRedo3DActionsBuilderSchema | UndoRedo3DActionsSimulationSchema;
|
||||||
|
|
||||||
|
type UndoRedo3DAction = UndoRedo3DActionSchema | UndoRedo3DActionsSchema;
|
||||||
|
|
||||||
|
type UndoRedo3DDraw = {
|
||||||
|
type: 'Scene';
|
||||||
|
actions: UndoRedo3DAction[];
|
||||||
|
};
|
||||||
|
|
||||||
|
type UndoRedo3DUi = {
|
||||||
|
type: 'UI';
|
||||||
|
action: any; // Define UI actions as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
type UndoRedo3DTypes = UndoRedo3DDraw | UndoRedo3DUi;
|
||||||
|
|
||||||
|
type UndoRedo3D = {
|
||||||
|
undoStack: UndoRedo3DTypes[];
|
||||||
|
redoStack: UndoRedo3DTypes[];
|
||||||
|
};
|
||||||
17
app/src/types/simulationTypes.d.ts
vendored
17
app/src/types/simulationTypes.d.ts
vendored
@@ -336,6 +336,23 @@ type HumanEventManagerState = {
|
|||||||
humanStates: HumanEventState[];
|
humanStates: HumanEventState[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type CraneEventState = {
|
||||||
|
craneId: string;
|
||||||
|
pendingActions: {
|
||||||
|
actionUuid: string;
|
||||||
|
callback: () => void;
|
||||||
|
}[];
|
||||||
|
currentAction: {
|
||||||
|
actionUuid: string;
|
||||||
|
callback: () => void;
|
||||||
|
} | null;
|
||||||
|
isProcessing: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type CraneEventManagerState = {
|
||||||
|
craneStates: CraneEventState[];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Materials
|
// Materials
|
||||||
|
|
||||||
|
|||||||
@@ -191,12 +191,9 @@ const KeyPressListener: React.FC = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleKeyPress = (event: KeyboardEvent) => {
|
const handleKeyPress = (event: KeyboardEvent) => {
|
||||||
if (isTextInput(document.activeElement)) return;
|
|
||||||
|
|
||||||
const keyCombination = detectModifierKeys(event);
|
const keyCombination = detectModifierKeys(event);
|
||||||
|
|
||||||
if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE")
|
if (isTextInput(document.activeElement) && keyCombination !== "ESCAPE") return;
|
||||||
return;
|
|
||||||
|
|
||||||
if (keyCombination === "ESCAPE") {
|
if (keyCombination === "ESCAPE") {
|
||||||
setWalkMode(false);
|
setWalkMode(false);
|
||||||
|
|||||||
Reference in New Issue
Block a user