Merge branch 'main' of http://185.100.212.76:7776/Dwinzo-Beta/Dwinzo_dev
This commit is contained in:
commit
b7219bce19
|
@ -1040,3 +1040,189 @@ export const EyeCloseIcon = () => {
|
|||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
export const SaveIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14.6942 4.26907C12.9155 3.91031 11.0846 3.91031 9.30594 4.26907C7.25504 4.68274 5.77783 6.50871 5.77783 8.63016V17.8926C5.77783 19.5181 7.51712 20.5298 8.89979 19.7085L11.4179 18.2129C11.7775 17.9993 12.2226 17.9993 12.5822 18.2129L15.1003 19.7085C16.483 20.5298 18.2223 19.5181 18.2223 17.8926V8.63016C18.2223 6.50871 16.745 4.68274 14.6942 4.26907Z"
|
||||
fill="white"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
export const SaveVersionIcon = () => {
|
||||
return (
|
||||
<svg
|
||||
width="183"
|
||||
height="114"
|
||||
viewBox="0 0 183 114"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<foreignObject x="-38.7816" y="-60.596" width="261.775" height="235.114">
|
||||
|
||||
|
||||
</foreignObject>
|
||||
<rect
|
||||
data-figma-bg-blur-radius="60.596"
|
||||
x="22.4204"
|
||||
y="0.60596"
|
||||
width="139.371"
|
||||
height="112.709"
|
||||
rx="10.3013"
|
||||
fill="url(#paint0_linear_4816_8890)"
|
||||
stroke="#564B69"
|
||||
strokeWidth="1.21192"
|
||||
/>
|
||||
<circle cx="33.9336" cy="10.9082" r="2.42384" fill="#6F6F7A" />
|
||||
<circle cx="41.2051" cy="10.9082" r="2.42384" fill="#6F6F7A" />
|
||||
<circle cx="48.4766" cy="10.9082" r="2.42384" fill="#6F6F7A" />
|
||||
<rect
|
||||
x="132.099"
|
||||
y="8.48438"
|
||||
width="20.6026"
|
||||
height="3.63576"
|
||||
rx="1.81788"
|
||||
fill="#6F6F7A"
|
||||
/>
|
||||
<rect
|
||||
x="0.60596"
|
||||
y="21.2075"
|
||||
width="181.788"
|
||||
height="27.8742"
|
||||
rx="13.9371"
|
||||
fill="url(#paint1_linear_4816_8890)"
|
||||
stroke="#564B69"
|
||||
strokeWidth="1.21192"
|
||||
/>
|
||||
<g clipPath="url(#clip1_4816_8890)">
|
||||
<path
|
||||
d="M14.5432 43.9516C9.69069 43.9516 5.74316 40.0038 5.74316 35.1516C5.74316 30.2993 9.69069 26.3516 14.5432 26.3516C19.3956 26.3516 23.3432 30.2993 23.3432 35.1516C23.3432 40.0038 19.3956 43.9516 14.5432 43.9516Z"
|
||||
fill="#D7EBFF"
|
||||
/>
|
||||
<path
|
||||
d="M23.343 35.1516C23.343 30.2993 19.3954 26.3516 14.543 26.3516V43.9516C19.3954 43.9516 23.343 40.0038 23.343 35.1516Z"
|
||||
fill="#C4E2FF"
|
||||
/>
|
||||
<path
|
||||
d="M14.5428 42.8044C10.3233 42.8044 6.89062 39.3715 6.89062 35.1522C6.89062 30.9328 10.3233 27.5 14.5428 27.5C18.7623 27.5 22.195 30.9329 22.195 35.1522C22.195 39.3715 18.7623 42.8044 14.5428 42.8044Z"
|
||||
fill="#88CC2A"
|
||||
/>
|
||||
<g filter="url(#filter1_f_4816_8890)">
|
||||
<path
|
||||
d="M14.5428 42.8044C10.3233 42.8044 6.89062 39.3715 6.89062 35.1522C6.89062 30.9328 10.3233 27.5 14.5428 27.5C18.7623 27.5 22.195 30.9329 22.195 35.1522C22.195 39.3715 18.7623 42.8044 14.5428 42.8044Z"
|
||||
fill="#88CC2A"
|
||||
/>
|
||||
</g>
|
||||
<path
|
||||
d="M22.1952 35.1522C22.1952 30.9329 18.7625 27.5 14.543 27.5V42.8043C18.7625 42.8044 22.1952 39.3715 22.1952 35.1522Z"
|
||||
fill="#7FB335"
|
||||
/>
|
||||
<path
|
||||
d="M13.7776 38.5936C13.4839 38.5936 13.1903 38.4815 12.9661 38.2573L10.6009 35.8921C10.1526 35.4439 10.1526 34.7172 10.6009 34.269C11.0493 33.8206 11.7757 33.8206 12.2241 34.269L13.7776 35.8224L17.053 32.5472C17.5013 32.0988 18.2277 32.0988 18.6761 32.5472C19.1245 32.9954 19.1245 33.7221 18.6761 34.1703L14.5892 38.2573C14.365 38.4815 14.0713 38.5936 13.7776 38.5936Z"
|
||||
fill="white"
|
||||
/>
|
||||
<path
|
||||
d="M17.0531 32.5472L14.543 35.0572V38.295C14.5579 38.2815 14.5749 38.2716 14.5893 38.2572L18.6762 34.1703C19.1246 33.7221 19.1246 32.9954 18.6762 32.5472C18.2278 32.0988 17.5015 32.0988 17.0531 32.5472Z"
|
||||
fill="#EDF0F2"
|
||||
/>
|
||||
</g>
|
||||
<rect
|
||||
x="29.0859"
|
||||
y="31.5078"
|
||||
width="130.887"
|
||||
height="6.0596"
|
||||
rx="3.0298"
|
||||
fill="#6F6F7A"
|
||||
/>
|
||||
<path
|
||||
d="M168.457 32.1172L173.911 37.5708"
|
||||
stroke="#F65648"
|
||||
strokeWidth="1.21192"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<path
|
||||
d="M173.911 32.1172L168.457 37.5708"
|
||||
stroke="#F65648"
|
||||
strokeWidth="1.21192"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<clipPath
|
||||
id="bgblur_0_4816_8890_clip_path"
|
||||
transform="translate(38.7816 60.596)"
|
||||
>
|
||||
<rect
|
||||
x="22.4204"
|
||||
y="0.60596"
|
||||
width="139.371"
|
||||
height="112.709"
|
||||
rx="10.3013"
|
||||
/>
|
||||
</clipPath>
|
||||
<filter
|
||||
id="filter1_f_4816_8890"
|
||||
x="1.93981"
|
||||
y="22.5492"
|
||||
width="25.2058"
|
||||
height="25.2063"
|
||||
filterUnits="userSpaceOnUse"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
mode="normal"
|
||||
in="SourceGraphic"
|
||||
in2="BackgroundImageFix"
|
||||
result="shape"
|
||||
/>
|
||||
<feGaussianBlur
|
||||
stdDeviation="2.47541"
|
||||
result="effect1_foregroundBlur_4816_8890"
|
||||
/>
|
||||
</filter>
|
||||
<linearGradient
|
||||
id="paint0_linear_4816_8890"
|
||||
x1="24.4428"
|
||||
y1="-3.9557e-07"
|
||||
x2="36.8899"
|
||||
y2="151.75"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333333" />
|
||||
<stop offset="1" stopColor="#2D2437" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_4816_8890"
|
||||
x1="3.42139"
|
||||
y1="20.6016"
|
||||
x2="4.04875"
|
||||
y2="59.5968"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#333333" />
|
||||
<stop offset="1" stopColor="#2D2437" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip1_4816_8890">
|
||||
<rect
|
||||
width="24"
|
||||
height="24"
|
||||
fill="white"
|
||||
transform="translate(2.54297 23.1484)"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -547,7 +547,7 @@ export function CompareLayoutIcon() {
|
|||
width="142"
|
||||
height="114"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
|
@ -582,7 +582,7 @@ export function CompareLayoutIcon() {
|
|||
width="9"
|
||||
height="113.5"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
@ -603,7 +603,7 @@ export function CompareLayoutIcon() {
|
|||
width="32"
|
||||
height="32"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
@ -624,7 +624,7 @@ export function CompareLayoutIcon() {
|
|||
width="17.8184"
|
||||
height="17.8203"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
@ -645,7 +645,7 @@ export function CompareLayoutIcon() {
|
|||
width="12.9082"
|
||||
height="12.9062"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
@ -666,7 +666,7 @@ export function CompareLayoutIcon() {
|
|||
width="12.9082"
|
||||
height="12.9062"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
@ -687,7 +687,7 @@ export function CompareLayoutIcon() {
|
|||
width="29"
|
||||
height="70"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
|
@ -722,7 +722,7 @@ export function CompareLayoutIcon() {
|
|||
width="29"
|
||||
height="70"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
|
@ -784,7 +784,7 @@ export const ResizerIcon = () => {
|
|||
width="26.7441"
|
||||
height="24.0703"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
colorInterpolationFilters="sRGB"
|
||||
>
|
||||
<feFlood floodOpacity="0" result="BackgroundImageFix" />
|
||||
<feBlend
|
||||
|
|
|
@ -22,12 +22,12 @@ import { upsertProductOrEventApi } from "../../../../services/simulation/UpsertP
|
|||
import { deleteProductApi } from "../../../../services/simulation/deleteProductApi";
|
||||
import { renameProductApi } from "../../../../services/simulation/renameProductApi";
|
||||
import { determineExecutionMachineSequences } from "../../../../modules/simulation/simulator/functions/determineExecutionMachineSequences";
|
||||
import ComparePopUp from "../../../ui/compare/compare";
|
||||
import ComparePopUp from "../../../ui/compareVersion/Compare";
|
||||
import {
|
||||
useCompareStore,
|
||||
useSaveVersion,
|
||||
} from "../../../../store/builder/store";
|
||||
import CompareLayOut from "../../../ui/compare/CompareLayOut";
|
||||
import CompareLayOut from "../../../ui/compareVersion/CompareLayOut";
|
||||
import useToggleStore from "../../../../store/useUIToggleStore";
|
||||
|
||||
interface Event {
|
||||
|
|
|
@ -7,25 +7,21 @@ import {
|
|||
LocationIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import RenameInput from "../../../ui/inputs/RenameInput";
|
||||
import { useVersionStore } from "../../../../store/builder/store";
|
||||
|
||||
const VersionHistory = () => {
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
|
||||
const initialVersions = [
|
||||
{
|
||||
versionName: "v1.0",
|
||||
timestamp: "April 09, 2025",
|
||||
savedBy: userName,
|
||||
},
|
||||
];
|
||||
|
||||
const [versions, setVersions] = useState(initialVersions);
|
||||
const [selectedVersion, setSelectedVersion] = useState(initialVersions[0]);
|
||||
const { versions, addVersion, setVersions } = useVersionStore();
|
||||
const [selectedVersion, setSelectedVersion] = useState(
|
||||
versions.length > 0 ? versions[0] : null
|
||||
);
|
||||
|
||||
const addNewVersion = () => {
|
||||
const newVersionNumber = versions.length + 1;
|
||||
const newVersion = {
|
||||
versionName: `v${newVersionNumber}.0`,
|
||||
id: crypto.randomUUID(),
|
||||
versionLabel: `v${versions.length + 1}.0`,
|
||||
versionName: "",
|
||||
timestamp: new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
|
@ -34,21 +30,26 @@ const VersionHistory = () => {
|
|||
savedBy: userName,
|
||||
};
|
||||
|
||||
const updated = [newVersion, ...versions];
|
||||
setVersions(updated);
|
||||
const newVersions = [newVersion, ...versions];
|
||||
addVersion(newVersion);
|
||||
setSelectedVersion(newVersion);
|
||||
setVersions(newVersions); // bring new one to top
|
||||
};
|
||||
|
||||
const handleSelectVersion = (version: any) => {
|
||||
setSelectedVersion(version);
|
||||
const reordered = [version, ...versions.filter((v) => v !== version)];
|
||||
|
||||
// Move selected version to top, keep others in same order
|
||||
const reordered = [version, ...versions.filter((v) => v.id !== version.id)];
|
||||
setVersions(reordered);
|
||||
};
|
||||
|
||||
const handleTimestampChange = (newTimestamp: string, index: number) => {
|
||||
const updatedVersions = [...versions];
|
||||
updatedVersions[index].timestamp = newTimestamp;
|
||||
setVersions(updatedVersions);
|
||||
const updated = [...versions];
|
||||
updated[index].timestamp = newTimestamp;
|
||||
|
||||
console.warn("Timestamp updated locally but not persisted in store.");
|
||||
setVersions(updated); // Optional: persist timestamp change
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -82,49 +83,55 @@ const VersionHistory = () => {
|
|||
</div>
|
||||
|
||||
{/* Current Version Display */}
|
||||
<div className="version-history-location">
|
||||
<div className="location-label">
|
||||
<LocationIcon />
|
||||
</div>
|
||||
<div className="location-details">
|
||||
<div className="current-version">
|
||||
Current Version ({selectedVersion.versionName})
|
||||
{selectedVersion && (
|
||||
<div className="version-history-location">
|
||||
<div className="location-label">
|
||||
<LocationIcon />
|
||||
</div>
|
||||
<div className="saved-history-count">
|
||||
{versions.length} Saved History
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Versions List */}
|
||||
<div className="saved-versions-list">
|
||||
{versions.map((version, index) => (
|
||||
<button
|
||||
key={version.versionName}
|
||||
className="saved-version"
|
||||
id={`${version.versionName}-${index}`}
|
||||
onClick={() => handleSelectVersion(version)}
|
||||
>
|
||||
<div className="version-name">{version.versionName}</div>
|
||||
<div className="version-details">
|
||||
<div className="details">
|
||||
<span className="timestamp">
|
||||
<RenameInput
|
||||
value={version.timestamp}
|
||||
onRename={(newTimestamp) =>
|
||||
handleTimestampChange(newTimestamp, index)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span className="saved-by">
|
||||
<div className="user-profile">{version.savedBy[0]}</div>
|
||||
<div className="user-name">{version.savedBy}</div>
|
||||
</span>
|
||||
</div>
|
||||
<ArrowIcon />
|
||||
<div className="location-details">
|
||||
<div className="current-version">
|
||||
Current Version ({selectedVersion.versionLabel})
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
<div className="saved-history-count">
|
||||
{versions.length} Saved History
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Versions List or No Versions Message */}
|
||||
<div className="saved-versions-list">
|
||||
{versions.length === 0 ? (
|
||||
<div className="no-versions-message">No saved versions</div>
|
||||
) : (
|
||||
versions.map((version, index) => (
|
||||
<button
|
||||
key={version.id}
|
||||
className="saved-version"
|
||||
id={`${version.versionName}-${index}`}
|
||||
onClick={() => handleSelectVersion(version)}
|
||||
>
|
||||
<div className="version-name">{version.versionLabel}</div>
|
||||
<div className="version-details">
|
||||
<div className="details">
|
||||
<span className="timestamp">
|
||||
<RenameInput
|
||||
value={version.timestamp}
|
||||
onRename={(newTimestamp) =>
|
||||
handleTimestampChange(newTimestamp, index)
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
<span className="saved-by">
|
||||
<div className="user-profile">{version.savedBy[0]}</div>
|
||||
<div className="user-name">{version.savedBy}</div>
|
||||
</span>
|
||||
</div>
|
||||
<ArrowIcon />
|
||||
</div>
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,186 @@
|
|||
import React, { useState, useEffect, useRef } from "react";
|
||||
import { useVersionStore } from "../../../../store/builder/store";
|
||||
import {
|
||||
CloseIcon,
|
||||
SaveIcon,
|
||||
SaveVersionIcon,
|
||||
} from "../../../icons/ExportCommonIcons";
|
||||
import { RenameIcon } from "../../../icons/ContextMenuIcons";
|
||||
import RenderOverlay from "../../../templates/Overlay";
|
||||
|
||||
const VersionSaved = () => {
|
||||
const { versions, updateVersion } = useVersionStore();
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
const [shouldDismiss, setShouldDismiss] = useState(false);
|
||||
const [showNotification, setShowNotification] = useState(false);
|
||||
const [newName, setNewName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const prevVersionCount = useRef(versions.length);
|
||||
const dismissTimerRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const latestVersion = versions?.[0];
|
||||
|
||||
// Clear dismiss timer when component unmounts
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
};
|
||||
}, []);
|
||||
|
||||
// Handle new version notification and setup dismiss timer
|
||||
useEffect(() => {
|
||||
if (versions.length > prevVersionCount.current) {
|
||||
setShowNotification(true);
|
||||
setShouldDismiss(false);
|
||||
setIsEditing(false);
|
||||
setNewName(versions[0].versionName ?? "");
|
||||
setDescription(versions[0]?.description ?? "");
|
||||
|
||||
// Only start dismiss timer if not in edit mode
|
||||
if (!isEditing) {
|
||||
startDismissTimer();
|
||||
}
|
||||
|
||||
prevVersionCount.current = versions.length;
|
||||
} else if (versions.length < prevVersionCount.current) {
|
||||
prevVersionCount.current = versions.length;
|
||||
}
|
||||
}, [versions, isEditing]);
|
||||
|
||||
// Start or restart the dismiss timer
|
||||
const startDismissTimer = (delay = 5000) => {
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
dismissTimerRef.current = setTimeout(() => {
|
||||
console.log("isEditing: ", isEditing);
|
||||
setShouldDismiss(true);
|
||||
}, delay);
|
||||
};
|
||||
|
||||
// Hide notification after dismiss animation delay
|
||||
useEffect(() => {
|
||||
if (shouldDismiss) {
|
||||
const timer = setTimeout(() => setShowNotification(false), 200);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
}, [shouldDismiss]);
|
||||
|
||||
const handleEditName = () => {
|
||||
setIsEditing(true);
|
||||
setNewName(latestVersion?.versionName ?? "");
|
||||
setDescription(latestVersion?.description ?? "");
|
||||
|
||||
// Clear any existing dismiss timer when editing starts
|
||||
if (dismissTimerRef.current) {
|
||||
clearTimeout(dismissTimerRef.current);
|
||||
dismissTimerRef.current = null;
|
||||
}
|
||||
};
|
||||
|
||||
const handleFinishEdit = () => {
|
||||
if (latestVersion) {
|
||||
updateVersion(latestVersion.id, {
|
||||
versionName: newName,
|
||||
description,
|
||||
});
|
||||
console.log("saved");
|
||||
startDismissTimer(); // Restart 5s timer after save
|
||||
}
|
||||
startDismissTimer(); // Restart 5s timer after save
|
||||
setIsEditing(false);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
setIsEditing(false);
|
||||
startDismissTimer(); // Restart 5s timer after cancel
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setShouldDismiss(true);
|
||||
if (dismissTimerRef.current) clearTimeout(dismissTimerRef.current);
|
||||
};
|
||||
|
||||
if (!showNotification || !latestVersion) return null;
|
||||
|
||||
return (
|
||||
<div className={`versionSaved ${shouldDismiss ? "dismissing" : ""}`}>
|
||||
<div className="version-header">
|
||||
<div className="header-wrapper">
|
||||
<div className="icon">
|
||||
<SaveIcon />
|
||||
</div>
|
||||
<span>Saved New Version</span>
|
||||
</div>
|
||||
<button className="close-btn" onClick={handleClose}>
|
||||
<CloseIcon />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="version-details">
|
||||
<SaveVersionIcon />
|
||||
<div className="details">
|
||||
<div className="details-wrapper">
|
||||
New Version Created {latestVersion.versionLabel}{" "}
|
||||
{latestVersion.timestamp.toUpperCase()}
|
||||
</div>
|
||||
<button onClick={handleEditName}>Edit name</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{isEditing && (
|
||||
<RenderOverlay>
|
||||
<div className="edit-version-popup-wrapper">
|
||||
<div className="details-wrapper-popup-container">
|
||||
<div className="header-wrapper">
|
||||
<RenameIcon />
|
||||
<div className="label">Rename Version</div>
|
||||
</div>
|
||||
<div className="details-wrapper">
|
||||
<div className="version-name">
|
||||
<input
|
||||
type="text"
|
||||
value={newName}
|
||||
onChange={(e) => setNewName(e.target.value)}
|
||||
placeholder="Enter new version name"
|
||||
/>
|
||||
<div className="label">
|
||||
by @{latestVersion.savedBy}{" "}
|
||||
{new Date(latestVersion.timestamp).toLocaleString("en-US", {
|
||||
month: "short",
|
||||
day: "numeric",
|
||||
year: "2-digit",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div className="version-description">
|
||||
{/* <input
|
||||
type="text"
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Add description"
|
||||
/> */}
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Add description"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="btn-wrapper">
|
||||
<button className="cancel" onClick={handleCancel}>
|
||||
Cancel
|
||||
</button>
|
||||
<button className="save" onClick={handleFinishEdit}>
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</RenderOverlay>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default VersionSaved;
|
|
@ -1,9 +1,8 @@
|
|||
import React, { useState } from "react";
|
||||
import React from "react";
|
||||
import { InfoIcon } from "../../icons/ShortcutIcons";
|
||||
import { SaveDiskIcon } from "../../icons/ExportCommonIcons";
|
||||
import { useCompareStore } from "../../../store/builder/store";
|
||||
import OuterClick from "../../../utils/outerClick";
|
||||
import useToggleStore from "../../../store/useUIToggleStore";
|
||||
|
||||
interface ComparePopUpProps {
|
||||
onClose: () => void;
|
|
@ -4,6 +4,7 @@ import { ArrowIcon } from "../../icons/ExportCommonIcons";
|
|||
import { toggleTheme } from "../../../utils/theme";
|
||||
import useVersionHistoryStore, {
|
||||
useShortcutStore,
|
||||
useVersionStore,
|
||||
} from "../../../store/builder/store";
|
||||
import { useSubModuleStore } from "../../../store/useModuleStore";
|
||||
|
||||
|
@ -20,6 +21,8 @@ interface MenuItem {
|
|||
}
|
||||
|
||||
const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
||||
const userName = localStorage.getItem("userName") ?? "Anonymous";
|
||||
|
||||
const navigate = useNavigate();
|
||||
const [activeMenu, setActiveMenu] = useState<string | null>(null);
|
||||
const [activeSubMenu, setActiveSubMenu] = useState<string | null>(null);
|
||||
|
@ -59,7 +62,32 @@ const MenuBar: React.FC<MenuBarProps> = ({ setOpenMenu }) => {
|
|||
File: [
|
||||
{ label: "New File", shortcut: "Ctrl + N" },
|
||||
{ label: "Open Local File", shortcut: "Ctrl + O" },
|
||||
{ label: "Save Version" },
|
||||
{
|
||||
label: "Save Version",
|
||||
action: () => {
|
||||
const versionStore = useVersionStore.getState();
|
||||
const versionCount = versionStore.versions.length;
|
||||
|
||||
const newVersion = {
|
||||
id: crypto.randomUUID(),
|
||||
versionLabel: `v${versionCount + 1}.0`,
|
||||
timestamp: `${new Date().toLocaleTimeString("en-US", {
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true,
|
||||
})} ${new Date().toLocaleDateString("en-US", {
|
||||
year: "numeric",
|
||||
month: "long",
|
||||
day: "2-digit",
|
||||
})}`,
|
||||
|
||||
savedBy: userName,
|
||||
};
|
||||
|
||||
console.log("newVersion: ", newVersion);
|
||||
versionStore.addVersion(newVersion);
|
||||
},
|
||||
},
|
||||
{ label: "Make a Copy" },
|
||||
{ label: "Share" },
|
||||
{ label: "Rename" },
|
||||
|
|
|
@ -48,6 +48,7 @@ import NavMesh from "../simulation/vehicle/navMesh/navMesh";
|
|||
import CalculateAreaGroup from "./groups/calculateAreaGroup";
|
||||
import LayoutImage from "./layout/layoutImage";
|
||||
import AssetsGroup from "./assetGroup/assetsGroup";
|
||||
import { Bvh } from "@react-three/drei";
|
||||
|
||||
export default function Builder() {
|
||||
const state = useThree<Types.ThreeState>(); // Importing the state from the useThree hook, which contains the scene, camera, and other Three.js elements.
|
||||
|
@ -203,29 +204,33 @@ export default function Builder() {
|
|||
<>
|
||||
<Ground grid={grid} plane={plane} />
|
||||
|
||||
<DistanceText key={toggleView} />
|
||||
<Bvh firstHitOnly>
|
||||
<DistanceText key={toggleView} />
|
||||
</Bvh>
|
||||
|
||||
<ReferenceDistanceText
|
||||
key={refTextupdate}
|
||||
line={ReferenceLineMesh.current}
|
||||
/>
|
||||
|
||||
<SocketResponses
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
lines={lines}
|
||||
floorGroup={floorGroup}
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
scene={scene}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
itemsGroup={itemsGroup}
|
||||
isTempLoader={isTempLoader}
|
||||
tempLoader={tempLoader}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
zoneGroup={zoneGroup}
|
||||
dragPointControls={dragPointControls}
|
||||
/>
|
||||
<Bvh firstHitOnly>
|
||||
<SocketResponses
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
lines={lines}
|
||||
floorGroup={floorGroup}
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
scene={scene}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
itemsGroup={itemsGroup}
|
||||
isTempLoader={isTempLoader}
|
||||
tempLoader={tempLoader}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
zoneGroup={zoneGroup}
|
||||
dragPointControls={dragPointControls}
|
||||
/>
|
||||
</Bvh>
|
||||
|
||||
<WallsAndWallItems
|
||||
CSGGroup={CSGGroup}
|
||||
|
@ -237,78 +242,80 @@ export default function Builder() {
|
|||
hoveredDeletableWallItem={hoveredDeletableWallItem}
|
||||
/>
|
||||
|
||||
<FloorItemsGroup
|
||||
itemsGroup={itemsGroup}
|
||||
hoveredDeletableFloorItem={hoveredDeletableFloorItem}
|
||||
AttachedObject={AttachedObject}
|
||||
floorGroup={floorGroup}
|
||||
tempLoader={tempLoader}
|
||||
isTempLoader={isTempLoader}
|
||||
plane={plane}
|
||||
/>
|
||||
<Bvh firstHitOnly>
|
||||
<FloorItemsGroup
|
||||
itemsGroup={itemsGroup}
|
||||
hoveredDeletableFloorItem={hoveredDeletableFloorItem}
|
||||
AttachedObject={AttachedObject}
|
||||
floorGroup={floorGroup}
|
||||
tempLoader={tempLoader}
|
||||
isTempLoader={isTempLoader}
|
||||
plane={plane}
|
||||
/>
|
||||
|
||||
<FloorGroup
|
||||
floorGroup={floorGroup}
|
||||
lines={lines}
|
||||
referencePole={referencePole}
|
||||
hoveredDeletablePillar={hoveredDeletablePillar}
|
||||
/>
|
||||
<FloorGroup
|
||||
floorGroup={floorGroup}
|
||||
lines={lines}
|
||||
referencePole={referencePole}
|
||||
hoveredDeletablePillar={hoveredDeletablePillar}
|
||||
/>
|
||||
|
||||
<FloorPlanGroup
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorGroup={floorGroup}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
hoveredDeletablePoint={hoveredDeletablePoint}
|
||||
hoveredDeletableLine={hoveredDeletableLine}
|
||||
plane={plane}
|
||||
line={line}
|
||||
lines={lines}
|
||||
onlyFloorline={onlyFloorline}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
<FloorPlanGroup
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
floorGroup={floorGroup}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
hoveredDeletablePoint={hoveredDeletablePoint}
|
||||
hoveredDeletableLine={hoveredDeletableLine}
|
||||
plane={plane}
|
||||
line={line}
|
||||
lines={lines}
|
||||
onlyFloorline={onlyFloorline}
|
||||
onlyFloorlines={onlyFloorlines}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
<ZoneGroup />
|
||||
<ZoneGroup />
|
||||
|
||||
<FloorGroupAilse
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
plane={plane}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
line={line}
|
||||
lines={lines}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
<FloorGroupAilse
|
||||
floorGroupAisle={floorGroupAisle}
|
||||
plane={plane}
|
||||
floorPlanGroupLine={floorPlanGroupLine}
|
||||
floorPlanGroupPoint={floorPlanGroupPoint}
|
||||
line={line}
|
||||
lines={lines}
|
||||
currentLayerPoint={currentLayerPoint}
|
||||
dragPointControls={dragPointControls}
|
||||
floorPlanGroup={floorPlanGroup}
|
||||
ReferenceLineMesh={ReferenceLineMesh}
|
||||
LineCreated={LineCreated}
|
||||
isSnapped={isSnapped}
|
||||
ispreSnapped={ispreSnapped}
|
||||
snappedPoint={snappedPoint}
|
||||
isSnappedUUID={isSnappedUUID}
|
||||
isAngleSnapped={isAngleSnapped}
|
||||
anglesnappedPoint={anglesnappedPoint}
|
||||
/>
|
||||
|
||||
{/* <AssetsGroup /> */}
|
||||
{/* <AssetsGroup /> */}
|
||||
|
||||
<MeasurementTool />
|
||||
<MeasurementTool />
|
||||
|
||||
<CalculateAreaGroup />
|
||||
<CalculateAreaGroup />
|
||||
|
||||
<NavMesh lines={lines} />
|
||||
<NavMesh lines={lines} />
|
||||
|
||||
<LayoutImage />
|
||||
<LayoutImage />
|
||||
</Bvh>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,90 @@
|
|||
import * as THREE from 'three';
|
||||
import { useRef, useState } from 'react';
|
||||
import { useFrame } from '@react-three/fiber';
|
||||
import { Clouds, Cloud } from '@react-three/drei';
|
||||
|
||||
interface CloudGroupProps {
|
||||
initialX: number;
|
||||
initialZ: number;
|
||||
speed: number;
|
||||
height: number;
|
||||
}
|
||||
|
||||
function CloudGroup({ initialX, initialZ, speed, height }: CloudGroupProps) {
|
||||
const group = useRef<THREE.Group>(null);
|
||||
|
||||
useFrame((_, delta) => {
|
||||
if (group.current) {
|
||||
|
||||
group.current.position.x += delta * speed;
|
||||
group.current.position.z += delta * speed * 0.5;
|
||||
|
||||
if (group.current.position.x > 500) group.current.position.x = -500;
|
||||
if (group.current.position.z > 500) group.current.position.z = -500;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<group ref={group} position={[initialX, height, initialZ]}>
|
||||
<Clouds material={THREE.MeshBasicMaterial} frustumCulled={false} limit={10000}>
|
||||
<Cloud
|
||||
seed={Math.random() * 100}
|
||||
bounds={[100, 15, 100]}
|
||||
volume={50}
|
||||
color="#eeeeee"
|
||||
scale={4}
|
||||
fade={10000}
|
||||
/>
|
||||
<Cloud
|
||||
seed={Math.random() * 100}
|
||||
bounds={[100, 15, 100]}
|
||||
volume={50}
|
||||
scale={6}
|
||||
color="#ffffff"
|
||||
fade={10000}
|
||||
/>
|
||||
<Cloud
|
||||
seed={Math.random() * 100}
|
||||
bounds={[100, 15, 100]}
|
||||
volume={50}
|
||||
scale={4}
|
||||
fade={10000}
|
||||
color="#f0f0f0"
|
||||
/>
|
||||
</Clouds>
|
||||
</group>
|
||||
);
|
||||
}
|
||||
|
||||
export function MovingClouds() {
|
||||
|
||||
const savedTheme: string | null = localStorage.getItem("theme");
|
||||
const [theme, setTheme] = useState(savedTheme || "light");
|
||||
const cloudGroups = [
|
||||
{ initialX: 0, initialZ: 0, speed: 8, height: 300 },
|
||||
{ initialX: -300, initialZ: 100, speed: 10, height: 300 },
|
||||
{ initialX: 200, initialZ: -150, speed: 4, height: 300 },
|
||||
{ initialX: -400, initialZ: -200, speed: 7, height: 300 },
|
||||
{ initialX: 400, initialZ: 300, speed: 5, height: 300 },
|
||||
{ initialX: -200, initialZ: -300, speed: 7, height: 300 },
|
||||
{ initialX: 300, initialZ: 200, speed: 10, height: 300 },
|
||||
];
|
||||
|
||||
return (
|
||||
<>
|
||||
{theme === 'light' &&
|
||||
<>
|
||||
{cloudGroups.map((group, index) => (
|
||||
<CloudGroup
|
||||
key={index}
|
||||
initialX={group.initialX}
|
||||
initialZ={group.initialZ}
|
||||
speed={group.speed}
|
||||
height={group.height}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -2,50 +2,37 @@ import { useTileDistance, useToggleView } from "../../../store/builder/store";
|
|||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
|
||||
const Ground = ({ grid, plane }: any) => {
|
||||
const { toggleView } = useToggleView();
|
||||
const { planeValue, gridValue } = useTileDistance();
|
||||
const { toggleView } = useToggleView();
|
||||
const { planeValue, gridValue } = useTileDistance();
|
||||
|
||||
return (
|
||||
<mesh name="Ground">
|
||||
<mesh
|
||||
ref={grid}
|
||||
name="Grid"
|
||||
position={
|
||||
!toggleView
|
||||
? CONSTANTS.gridConfig.position3D
|
||||
: CONSTANTS.gridConfig.position2D
|
||||
}
|
||||
>
|
||||
<gridHelper
|
||||
args={[
|
||||
gridValue.size,
|
||||
gridValue.divisions,
|
||||
// CONSTANTS.gridConfig.size,
|
||||
// CONSTANTS.gridConfig.divisions,
|
||||
CONSTANTS.gridConfig.primaryColor,
|
||||
CONSTANTS.gridConfig.secondaryColor,
|
||||
]}
|
||||
/>
|
||||
</mesh>
|
||||
<mesh
|
||||
ref={plane}
|
||||
rotation-x={CONSTANTS.planeConfig.rotation}
|
||||
position={
|
||||
!toggleView
|
||||
? CONSTANTS.planeConfig.position3D
|
||||
: CONSTANTS.planeConfig.position2D
|
||||
}
|
||||
name="Plane"
|
||||
receiveShadow
|
||||
>
|
||||
<planeGeometry
|
||||
args={[planeValue.width, planeValue.height]}
|
||||
// args={[CONSTANTS.planeConfig.width, CONSTANTS.planeConfig.height]}
|
||||
/>
|
||||
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
||||
</mesh>
|
||||
</mesh>
|
||||
);
|
||||
return (
|
||||
<mesh name="Ground">
|
||||
<mesh
|
||||
ref={grid}
|
||||
name="Grid"
|
||||
position={!toggleView ? CONSTANTS.gridConfig.position3D : CONSTANTS.gridConfig.position2D}
|
||||
>
|
||||
<gridHelper
|
||||
args={[
|
||||
gridValue.size,
|
||||
gridValue.divisions,
|
||||
CONSTANTS.gridConfig.primaryColor,
|
||||
CONSTANTS.gridConfig.secondaryColor,
|
||||
]}
|
||||
/>
|
||||
</mesh>
|
||||
<mesh
|
||||
ref={plane}
|
||||
rotation-x={CONSTANTS.planeConfig.rotation}
|
||||
position={!toggleView ? CONSTANTS.planeConfig.position3D : CONSTANTS.planeConfig.position2D}
|
||||
name="Plane"
|
||||
receiveShadow
|
||||
>
|
||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||
<meshBasicMaterial color={CONSTANTS.planeConfig.color} />
|
||||
</mesh>
|
||||
</mesh>
|
||||
);
|
||||
};
|
||||
|
||||
export default Ground;
|
||||
|
|
|
@ -2,111 +2,105 @@ import { useRef, useEffect } from "react";
|
|||
import { useThree } from "@react-three/fiber";
|
||||
import * as THREE from "three";
|
||||
import {
|
||||
useAzimuth,
|
||||
useElevation,
|
||||
useShadows,
|
||||
useSunPosition,
|
||||
useFloorItems,
|
||||
useWallItems,
|
||||
useTileDistance,
|
||||
useAzimuth,
|
||||
useElevation,
|
||||
useShadows,
|
||||
useSunPosition,
|
||||
useFloorItems,
|
||||
useWallItems,
|
||||
useTileDistance,
|
||||
} from "../../../store/builder/store";
|
||||
import * as CONSTANTS from "../../../types/world/worldConstants";
|
||||
const shadowWorker = new Worker(
|
||||
new URL(
|
||||
"../../../services/factoryBuilder/webWorkers/shadowWorker",
|
||||
import.meta.url
|
||||
)
|
||||
new URL(
|
||||
"../../../services/factoryBuilder/webWorkers/shadowWorker",
|
||||
import.meta.url
|
||||
)
|
||||
);
|
||||
|
||||
export default function Shadows() {
|
||||
const { shadows, setShadows } = useShadows();
|
||||
const { sunPosition, setSunPosition } = useSunPosition();
|
||||
const lightRef = useRef<THREE.DirectionalLight | null>(null);
|
||||
const targetRef = useRef<THREE.Object3D | null>(null);
|
||||
const { controls, gl } = useThree();
|
||||
const { elevation, setElevation } = useElevation();
|
||||
const { azimuth, setAzimuth } = useAzimuth();
|
||||
const { floorItems } = useFloorItems();
|
||||
const { wallItems } = useWallItems();
|
||||
const { planeValue } = useTileDistance();
|
||||
const { shadows, setShadows } = useShadows();
|
||||
const { sunPosition, setSunPosition } = useSunPosition();
|
||||
const lightRef = useRef<THREE.DirectionalLight | null>(null);
|
||||
const targetRef = useRef<THREE.Object3D | null>(null);
|
||||
const { controls, gl } = useThree();
|
||||
const { elevation, setElevation } = useElevation();
|
||||
const { azimuth, setAzimuth } = useAzimuth();
|
||||
const { floorItems } = useFloorItems();
|
||||
const { wallItems } = useWallItems();
|
||||
const { planeValue } = useTileDistance();
|
||||
|
||||
useEffect(() => {
|
||||
gl.shadowMap.enabled = true;
|
||||
gl.shadowMap.type = THREE.PCFShadowMap;
|
||||
}, [gl, floorItems, wallItems]);
|
||||
useEffect(() => {
|
||||
gl.shadowMap.enabled = true;
|
||||
gl.shadowMap.type = THREE.PCFShadowMap;
|
||||
}, [gl, floorItems, wallItems]);
|
||||
|
||||
useEffect(() => {
|
||||
if (lightRef.current && targetRef.current) {
|
||||
lightRef.current.target = targetRef.current;
|
||||
}
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
if (lightRef.current && targetRef.current) {
|
||||
lightRef.current.target = targetRef.current;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
shadowWorker.onmessage = (event) => {
|
||||
const { lightPosition, controlsTarget } = event.data;
|
||||
if (lightRef.current && targetRef.current && controls) {
|
||||
lightRef.current.position.copy(lightPosition);
|
||||
targetRef.current.position.copy(controlsTarget);
|
||||
}
|
||||
useEffect(() => {
|
||||
shadowWorker.onmessage = (event) => {
|
||||
const { lightPosition, controlsTarget } = event.data;
|
||||
if (lightRef.current && targetRef.current && controls) {
|
||||
lightRef.current.position.copy(lightPosition);
|
||||
targetRef.current.position.copy(controlsTarget);
|
||||
gl.shadowMap.needsUpdate = true;
|
||||
}
|
||||
};
|
||||
}, [shadowWorker, controls]);
|
||||
|
||||
const updateShadows = () => {
|
||||
if (controls && shadowWorker) {
|
||||
const offsetDistance = CONSTANTS.shadowConfig.shadowOffset;
|
||||
const controlsTarget = (controls as any).getTarget();
|
||||
shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance });
|
||||
}
|
||||
};
|
||||
}, [shadowWorker, controls]);
|
||||
|
||||
const updateShadows = () => {
|
||||
if (controls && shadowWorker) {
|
||||
const offsetDistance = CONSTANTS.shadowConfig.shadowOffset;
|
||||
const controlsTarget = (controls as any).getTarget();
|
||||
shadowWorker.postMessage({ controlsTarget, sunPosition, offsetDistance });
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (controls && shadows) {
|
||||
updateShadows();
|
||||
(controls as any).addEventListener("update", updateShadows);
|
||||
return () => {
|
||||
(controls as any).removeEventListener("update", updateShadows);
|
||||
};
|
||||
}
|
||||
}, [controls, elevation, azimuth, shadows]);
|
||||
|
||||
useEffect(() => {
|
||||
if (controls && shadows) {
|
||||
updateShadows();
|
||||
(controls as any).addEventListener("update", updateShadows);
|
||||
return () => {
|
||||
(controls as any).removeEventListener("update", updateShadows);
|
||||
};
|
||||
}
|
||||
}, [controls, elevation, azimuth, shadows]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* {(lightRef.current?.shadow) &&
|
||||
<cameraHelper visible={shadows} args={[lightRef.current.shadow.camera]} />
|
||||
} */}
|
||||
<directionalLight
|
||||
ref={lightRef}
|
||||
castShadow={shadows}
|
||||
shadow-mapSize-width={CONSTANTS.shadowConfig.shadowmapSizewidth}
|
||||
shadow-mapSize-height={CONSTANTS.shadowConfig.shadowmapSizeheight}
|
||||
shadow-camera-far={CONSTANTS.shadowConfig.shadowcamerafar}
|
||||
shadow-camera-near={CONSTANTS.shadowConfig.shadowcameranear}
|
||||
shadow-camera-top={CONSTANTS.shadowConfig.shadowcameratop}
|
||||
shadow-camera-bottom={CONSTANTS.shadowConfig.shadowcamerabottom}
|
||||
shadow-camera-left={CONSTANTS.shadowConfig.shadowcameraleft}
|
||||
shadow-camera-right={CONSTANTS.shadowConfig.shadowcameraright}
|
||||
shadow-bias={CONSTANTS.shadowConfig.shadowbias}
|
||||
shadow-normalBias={CONSTANTS.shadowConfig.shadownormalBias}
|
||||
/>
|
||||
<object3D ref={targetRef} />
|
||||
<mesh
|
||||
position={CONSTANTS.shadowConfig.shadowMaterialPosition}
|
||||
rotation={CONSTANTS.shadowConfig.shadowMaterialRotation}
|
||||
receiveShadow
|
||||
>
|
||||
{/* <planeGeometry
|
||||
args={[CONSTANTS.planeConfig.width, CONSTANTS.planeConfig.height]}
|
||||
/>
|
||||
<shadowMaterial
|
||||
opacity={CONSTANTS.shadowConfig.shadowMaterialOpacity}
|
||||
transparent
|
||||
/> */}
|
||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||
<shadowMaterial
|
||||
opacity={CONSTANTS.shadowConfig.shadowMaterialOpacity}
|
||||
transparent
|
||||
/>
|
||||
</mesh>
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<>
|
||||
{/* {(lightRef.current?.shadow) &&
|
||||
<cameraHelper visible={shadows} args={[lightRef.current.shadow.camera]} />
|
||||
} */}
|
||||
<directionalLight
|
||||
ref={lightRef}
|
||||
castShadow={shadows}
|
||||
shadow-mapSize-width={CONSTANTS.shadowConfig.shadowmapSizewidth}
|
||||
shadow-mapSize-height={CONSTANTS.shadowConfig.shadowmapSizeheight}
|
||||
shadow-camera-far={CONSTANTS.shadowConfig.shadowcamerafar}
|
||||
shadow-camera-near={CONSTANTS.shadowConfig.shadowcameranear}
|
||||
shadow-camera-top={CONSTANTS.shadowConfig.shadowcameratop}
|
||||
shadow-camera-bottom={CONSTANTS.shadowConfig.shadowcamerabottom}
|
||||
shadow-camera-left={CONSTANTS.shadowConfig.shadowcameraleft}
|
||||
shadow-camera-right={CONSTANTS.shadowConfig.shadowcameraright}
|
||||
shadow-bias={CONSTANTS.shadowConfig.shadowbias}
|
||||
shadow-normalBias={CONSTANTS.shadowConfig.shadownormalBias}
|
||||
/>
|
||||
<object3D ref={targetRef} />
|
||||
<mesh
|
||||
position={CONSTANTS.shadowConfig.shadowMaterialPosition}
|
||||
rotation={CONSTANTS.shadowConfig.shadowMaterialRotation}
|
||||
receiveShadow
|
||||
>
|
||||
<planeGeometry args={[planeValue.width, planeValue.height]} />
|
||||
<shadowMaterial
|
||||
opacity={CONSTANTS.shadowConfig.shadowMaterialOpacity}
|
||||
transparent
|
||||
/>
|
||||
</mesh>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -9,35 +9,32 @@ import Simulation from "../simulation/simulation";
|
|||
import Collaboration from "../collaboration/collaboration";
|
||||
|
||||
export default function Scene() {
|
||||
const map = useMemo(
|
||||
() => [
|
||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||
{ name: "right", keys: ["ArrowRight", "d", "D"] },
|
||||
],
|
||||
[]
|
||||
);
|
||||
const map = useMemo(() => [
|
||||
{ name: "forward", keys: ["ArrowUp", "w", "W"] },
|
||||
{ name: "backward", keys: ["ArrowDown", "s", "S"] },
|
||||
{ name: "left", keys: ["ArrowLeft", "a", "A"] },
|
||||
{ name: "right", keys: ["ArrowRight", "d", "D"] },
|
||||
], []);
|
||||
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
<Canvas
|
||||
eventPrefix="client"
|
||||
gl={{ powerPreference: "high-performance", antialias: true }}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Setup />
|
||||
return (
|
||||
<KeyboardControls map={map}>
|
||||
<Canvas
|
||||
eventPrefix="client"
|
||||
gl={{ powerPreference: "high-performance", antialias: true }}
|
||||
onContextMenu={(e) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<Setup />
|
||||
|
||||
<Collaboration />
|
||||
<Collaboration />
|
||||
|
||||
<Builder />
|
||||
<Builder />
|
||||
|
||||
<Simulation />
|
||||
<Simulation />
|
||||
|
||||
<Visualization />
|
||||
</Canvas>
|
||||
</KeyboardControls>
|
||||
);
|
||||
<Visualization />
|
||||
</Canvas>
|
||||
</KeyboardControls>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import Controls from '../controls/controls';
|
|||
import { Environment } from '@react-three/drei'
|
||||
|
||||
import background from "../../../assets/textures/hdr/mudroadpuresky2k.hdr";
|
||||
import { MovingClouds } from '../clouds/clouds';
|
||||
|
||||
function Setup() {
|
||||
return (
|
||||
|
@ -17,6 +18,8 @@ function Setup() {
|
|||
|
||||
<PostProcessing />
|
||||
|
||||
<MovingClouds />
|
||||
|
||||
<Environment files={background} environmentIntensity={1.5} />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -2,36 +2,43 @@ import React, { useEffect } from 'react'
|
|||
import { useMaterialStore } from '../../../../../store/simulation/useMaterialStore';
|
||||
import { useConveyorStore } from '../../../../../store/simulation/useConveyorStore';
|
||||
import { useResetButtonStore } from '../../../../../store/usePlayButtonStore';
|
||||
import { findConveyorInSequences } from '../../../simulator/functions/findConveyorInSequences';
|
||||
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
|
||||
import { useProductStore } from '../../../../../store/simulation/useProductStore';
|
||||
|
||||
function ConveyorInstance({ conveyor }: { conveyor: ConveyorStatus }) {
|
||||
const { getProductById } = useProductStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const { materials, getMaterialsByCurrentModelUuid } = useMaterialStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
|
||||
const { setConveyorPaused } = useConveyorStore();
|
||||
|
||||
useEffect(() => {
|
||||
const product = getProductById(selectedProduct.productId);
|
||||
if (!product) return;
|
||||
|
||||
const sequenceInfo = findConveyorInSequences(product, conveyor.modelUuid);
|
||||
if (!sequenceInfo) return;
|
||||
|
||||
const { currentSubSequence } = sequenceInfo;
|
||||
const conveyorMaterials = getMaterialsByCurrentModelUuid(conveyor.modelUuid);
|
||||
if (conveyorMaterials && conveyorMaterials?.length > 0) {
|
||||
|
||||
const hasPausedMaterials = conveyorMaterials.some(material => material.isPaused);
|
||||
if (conveyorMaterials && conveyorMaterials.length > 0) {
|
||||
const shouldPauseSubsequence = currentSubSequence.some(subConveyor => {
|
||||
if (subConveyor.type !== 'transfer') return false;
|
||||
const subMaterials = getMaterialsByCurrentModelUuid(subConveyor.modelUuid);
|
||||
return subMaterials?.some(m => m.isPaused) ?? false;
|
||||
});
|
||||
|
||||
if (hasPausedMaterials) {
|
||||
setConveyorPaused(conveyor.modelUuid, true);
|
||||
} else {
|
||||
setConveyorPaused(conveyor.modelUuid, false);
|
||||
}
|
||||
currentSubSequence.forEach(subConveyor => {
|
||||
if (subConveyor.type === 'transfer') {
|
||||
setConveyorPaused(subConveyor.modelUuid, shouldPauseSubsequence);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset, selectedProduct.productId, getProductById]);
|
||||
|
||||
}, [materials, conveyor.modelUuid, getMaterialsByCurrentModelUuid, setConveyorPaused, isReset]);
|
||||
|
||||
useEffect(() => {
|
||||
// console.log('conveyor: ', conveyor);
|
||||
}, [conveyor])
|
||||
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
)
|
||||
return null;
|
||||
}
|
||||
|
||||
export default ConveyorInstance
|
||||
export default React.memo(ConveyorInstance);
|
|
@ -52,7 +52,7 @@ function Products() {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [selectedProduct, products, isReset]);
|
||||
}, [selectedProduct, products, isReset, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProduct.productId) {
|
||||
|
@ -66,7 +66,7 @@ function Products() {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [selectedProduct, products, isReset]);
|
||||
}, [selectedProduct, products, isReset, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProduct.productId) {
|
||||
|
@ -80,7 +80,7 @@ function Products() {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [selectedProduct, products, isReset]);
|
||||
}, [selectedProduct, products, isReset, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProduct.productId) {
|
||||
|
@ -94,7 +94,7 @@ function Products() {
|
|||
});
|
||||
}
|
||||
}
|
||||
}, [selectedProduct, products, isReset]);
|
||||
}, [selectedProduct, products, isReset, isPlaying]);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedProduct.productId) {
|
||||
|
|
|
@ -13,6 +13,7 @@ import { useVehicleStore } from '../../../../../store/simulation/useVehicleStore
|
|||
import { useStorageUnitStore } from '../../../../../store/simulation/useStorageUnitStore';
|
||||
import { useSelectedProduct } from '../../../../../store/simulation/useSimulationStore';
|
||||
import { useTriggerHandler } from '../../../triggers/triggerHandler/useTriggerHandler';
|
||||
import { useCheckActiveRoboticArmsInSubsequence } from '../../../simulator/functions/checkActiveRoboticArmsInSubsequence';
|
||||
|
||||
function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
||||
|
||||
|
@ -30,13 +31,16 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
const { setArmBotActive, setArmBotState, removeCurrentAction } = useArmBotStore();
|
||||
const { decrementVehicleLoad, removeLastMaterial } = useVehicleStore();
|
||||
const { removeLastMaterial: removeLastStorageMaterial, updateCurrentLoad } = useStorageUnitStore();
|
||||
const { setIsVisible, getMaterialById } = useMaterialStore();
|
||||
const { setIsVisible, setIsPaused, getMaterialById } = useMaterialStore();
|
||||
const { selectedProduct } = useSelectedProduct();
|
||||
const { getActionByUuid, getEventByActionUuid, getEventByModelUuid } = useProductStore();
|
||||
const { getActionByUuid, getEventByActionUuid, getEventByModelUuid, getProductById } = useProductStore();
|
||||
const { triggerPointActions } = useTriggerHandler();
|
||||
const { isPlaying } = usePlayButtonStore();
|
||||
const { isReset } = useResetButtonStore();
|
||||
const { isPaused } = usePauseButtonStore();
|
||||
const checkActiveRoboticArms = useCheckActiveRoboticArmsInSubsequence();
|
||||
|
||||
const lastRemoved = useRef<{ type: string, materialId: string } | null>(null);
|
||||
|
||||
function firstFrame() {
|
||||
startTime = performance.now();
|
||||
|
@ -63,6 +67,7 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
removeLastStorageMaterial(previousModel.modelUuid);
|
||||
updateCurrentLoad(previousModel.modelUuid, -1)
|
||||
}
|
||||
lastRemoved.current = { type: previousModel.type, materialId: armBot.currentAction.materialId };
|
||||
} else {
|
||||
setIsVisible(armBot.currentAction.materialId, false);
|
||||
}
|
||||
|
@ -76,11 +81,20 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
|
||||
if (armBot.currentAction) {
|
||||
const action = getActionByUuid(selectedProduct.productId, armBot.currentAction.actionUuid);
|
||||
const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid || '');
|
||||
if (action && action.triggers[0].triggeredAsset?.triggeredModel.modelUuid) {
|
||||
const model = getEventByModelUuid(selectedProduct.productId, action?.triggers[0].triggeredAsset?.triggeredModel.modelUuid);
|
||||
if (!model) return;
|
||||
if (model.type === 'transfer') {
|
||||
setIsVisible(armBot.currentAction.materialId || '', true);
|
||||
|
||||
const product = getProductById(selectedProduct.productId);
|
||||
if (product) {
|
||||
const result = checkActiveRoboticArms(product, armBot.modelUuid);
|
||||
// console.log('result: ', result);
|
||||
// if (result?.hasActiveRoboticArm) {
|
||||
// lastRemoved.current = null;
|
||||
// }
|
||||
}
|
||||
} else if (model.type === 'machine') {
|
||||
//
|
||||
} else if (model.type === 'vehicle') {
|
||||
|
@ -287,6 +301,14 @@ function RoboticArmInstance({ armBot }: { armBot: ArmBotStatus }) {
|
|||
setArmBotState(armBot.modelUuid, "idle")
|
||||
setCurrentPhase("rest");
|
||||
setPath([])
|
||||
|
||||
if (lastRemoved.current) {
|
||||
if (lastRemoved.current.type === 'transfer') {
|
||||
setIsPaused(lastRemoved.current.materialId, true)
|
||||
} else {
|
||||
setIsPaused(lastRemoved.current.materialId, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const logStatus = (id: string, status: string) => {
|
||||
|
|
|
@ -0,0 +1,188 @@
|
|||
import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
|
||||
import { useArmBotStore } from "../../../../store/simulation/useArmBotStore";
|
||||
|
||||
export function getRoboticArmSequencesInProduct(
|
||||
product: {
|
||||
productName: string;
|
||||
productId: string;
|
||||
eventDatas: EventsSchema[];
|
||||
}
|
||||
): EventsSchema[][][] {
|
||||
// Get all machine sequences for this product
|
||||
const machineSequences = determineExecutionMachineSequences([product]);
|
||||
|
||||
const allRoboticArmSequences: EventsSchema[][][] = [];
|
||||
|
||||
// Process each machine sequence separately
|
||||
for (const machineSequence of machineSequences) {
|
||||
const roboticArmSequencesForThisMachineSequence: EventsSchema[][] = [];
|
||||
let currentRoboticArmSequence: EventsSchema[] = [];
|
||||
|
||||
for (const event of machineSequence) {
|
||||
if (event.type === 'roboticArm') {
|
||||
// Add robotic arm to current sequence
|
||||
currentRoboticArmSequence.push(event);
|
||||
} else if (event.type === 'vehicle') {
|
||||
// Vehicle encountered - split the sequence
|
||||
if (currentRoboticArmSequence.length > 0) {
|
||||
roboticArmSequencesForThisMachineSequence.push([...currentRoboticArmSequence]);
|
||||
currentRoboticArmSequence = [];
|
||||
}
|
||||
}
|
||||
// Other machine types continue the current sequence
|
||||
}
|
||||
|
||||
// Add any remaining robotic arms in the current sequence
|
||||
if (currentRoboticArmSequence.length > 0) {
|
||||
roboticArmSequencesForThisMachineSequence.push([...currentRoboticArmSequence]);
|
||||
}
|
||||
|
||||
if (roboticArmSequencesForThisMachineSequence.length > 0) {
|
||||
allRoboticArmSequences.push(roboticArmSequencesForThisMachineSequence);
|
||||
}
|
||||
}
|
||||
|
||||
return allRoboticArmSequences;
|
||||
}
|
||||
|
||||
export function findRoboticArmSubsequence(
|
||||
product: {
|
||||
productName: string;
|
||||
productId: string;
|
||||
eventDatas: EventsSchema[];
|
||||
},
|
||||
roboticArmModelUuid: string
|
||||
): {
|
||||
allSequences: EventsSchema[][][];
|
||||
parentSequence: EventsSchema[][];
|
||||
currentSubSequence: EventsSchema[];
|
||||
} | null {
|
||||
const allSequences = getRoboticArmSequencesInProduct(product);
|
||||
|
||||
for (const parentSequence of allSequences) {
|
||||
for (const currentSubSequence of parentSequence) {
|
||||
const hasTargetRoboticArm = currentSubSequence.some(
|
||||
event => event.type === 'roboticArm' && event.modelUuid === roboticArmModelUuid
|
||||
);
|
||||
|
||||
if (hasTargetRoboticArm) {
|
||||
return {
|
||||
allSequences,
|
||||
parentSequence,
|
||||
currentSubSequence
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// React component/hook that uses the pure functions
|
||||
export function useCheckActiveRoboticArmsInSubsequence() {
|
||||
const { getArmBotById } = useArmBotStore();
|
||||
|
||||
return function (product: {
|
||||
productName: string;
|
||||
productId: string;
|
||||
eventDatas: EventsSchema[];
|
||||
}, roboticArmModelUuid: string) {
|
||||
const result = findRoboticArmSubsequence(product, roboticArmModelUuid);
|
||||
|
||||
if (!result) return null;
|
||||
|
||||
const hasActiveRoboticArm = result.currentSubSequence.some(event => {
|
||||
if (event.type === 'roboticArm' && event.modelUuid !== roboticArmModelUuid) {
|
||||
const armBot = getArmBotById(event.modelUuid);
|
||||
return armBot?.isActive;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
return {
|
||||
...result,
|
||||
hasActiveRoboticArm
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// Helper function to get machine sequences (simplified from your example)
|
||||
function determineExecutionMachineSequences(products: productsSchema): EventsSchema[][] {
|
||||
const pointToEventMap = new Map<string, EventsSchema>();
|
||||
const allPoints: PointsScheme[] = [];
|
||||
|
||||
// First pass: map points to their corresponding events
|
||||
products.forEach(product => {
|
||||
product.eventDatas.forEach(event => {
|
||||
if (event.type === 'transfer') {
|
||||
event.points.forEach(point => {
|
||||
pointToEventMap.set(point.uuid, event);
|
||||
allPoints.push(point);
|
||||
});
|
||||
} else if (
|
||||
event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Build dependency graph
|
||||
const dependencyGraph = new Map<string, string[]>();
|
||||
const triggeredPoints = new Set<string>();
|
||||
|
||||
allPoints.forEach(point => {
|
||||
const triggers = extractTriggersFromPoint(point);
|
||||
const dependencies: string[] = [];
|
||||
|
||||
triggers.forEach(trigger => {
|
||||
const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
|
||||
if (targetUuid && pointToEventMap.has(targetUuid)) {
|
||||
dependencies.push(targetUuid);
|
||||
triggeredPoints.add(targetUuid);
|
||||
}
|
||||
});
|
||||
|
||||
dependencyGraph.set(point.uuid, dependencies);
|
||||
});
|
||||
|
||||
// Find root points (points that aren't triggered by others)
|
||||
const rootPoints = allPoints.filter(point =>
|
||||
!triggeredPoints.has(point.uuid) &&
|
||||
dependencyGraph.get(point.uuid)?.length
|
||||
);
|
||||
|
||||
const executionSequences: EventsSchema[][] = [];
|
||||
|
||||
function buildSequence(startUuid: string): EventsSchema[] {
|
||||
const sequence: EventsSchema[] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
function traverse(uuid: string) {
|
||||
if (visited.has(uuid)) return;
|
||||
visited.add(uuid);
|
||||
|
||||
const event = pointToEventMap.get(uuid);
|
||||
if (event && !sequence.includes(event)) {
|
||||
sequence.push(event);
|
||||
}
|
||||
|
||||
const nextPoints = dependencyGraph.get(uuid) || [];
|
||||
nextPoints.forEach(nextUuid => traverse(nextUuid));
|
||||
}
|
||||
|
||||
traverse(startUuid);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Build sequences from root points
|
||||
rootPoints.forEach(root => {
|
||||
executionSequences.push(buildSequence(root.uuid));
|
||||
});
|
||||
|
||||
return executionSequences;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import { getConveyorSequencesInProduct } from "./getConveyorSequencesForProduct";
|
||||
|
||||
export function findConveyorInSequences(
|
||||
product: {
|
||||
productName: string;
|
||||
productId: string;
|
||||
eventDatas: EventsSchema[];
|
||||
},
|
||||
conveyorUuid: string
|
||||
): {
|
||||
allSequences: EventsSchema[][][];
|
||||
parentSequence: EventsSchema[][];
|
||||
currentSubSequence: EventsSchema[];
|
||||
} | null {
|
||||
// Get all conveyor sequences
|
||||
const allSequences = getConveyorSequencesInProduct(product);
|
||||
|
||||
// Search through all sequences
|
||||
for (const parentSequence of allSequences) {
|
||||
for (const currentSubSequence of parentSequence) {
|
||||
for (const conveyor of currentSubSequence) {
|
||||
// Check if this is the conveyor we're looking for
|
||||
if (conveyor.modelUuid === conveyorUuid) {
|
||||
return {
|
||||
allSequences,
|
||||
parentSequence,
|
||||
currentSubSequence
|
||||
};
|
||||
}
|
||||
|
||||
// Also check points in case the UUID matches a point's conveyor
|
||||
if (conveyor.type === 'transfer') {
|
||||
for (const point of conveyor.points) {
|
||||
if (point.uuid === conveyorUuid) {
|
||||
return {
|
||||
allSequences,
|
||||
parentSequence,
|
||||
currentSubSequence
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conveyor not found
|
||||
return null;
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
import { extractTriggersFromPoint } from "./extractTriggersFromPoint";
|
||||
|
||||
export function getConveyorSequencesInProduct(
|
||||
product: {
|
||||
productName: string;
|
||||
productId: string;
|
||||
eventDatas: EventsSchema[];
|
||||
}
|
||||
): EventsSchema[][][] { // Now returns array of array of arrays
|
||||
// Get all machine sequences for this product
|
||||
const machineSequences = determineExecutionMachineSequences([product]);
|
||||
|
||||
const allConveyorSequences: EventsSchema[][][] = [];
|
||||
|
||||
// Process each machine sequence separately
|
||||
for (const machineSequence of machineSequences) {
|
||||
const conveyorSequencesForThisMachineSequence: EventsSchema[][] = [];
|
||||
let currentConveyorSequence: EventsSchema[] = [];
|
||||
|
||||
for (const event of machineSequence) {
|
||||
if (event.type === 'transfer') {
|
||||
// Add conveyor to current sequence
|
||||
currentConveyorSequence.push(event);
|
||||
} else if (event.type === 'vehicle') {
|
||||
// Vehicle encountered - split the sequence
|
||||
if (currentConveyorSequence.length > 0) {
|
||||
conveyorSequencesForThisMachineSequence.push([...currentConveyorSequence]);
|
||||
currentConveyorSequence = [];
|
||||
}
|
||||
}
|
||||
// Other machine types don't affect the conveyor sequence
|
||||
}
|
||||
|
||||
// Add any remaining conveyors in the current sequence
|
||||
if (currentConveyorSequence.length > 0) {
|
||||
conveyorSequencesForThisMachineSequence.push([...currentConveyorSequence]);
|
||||
}
|
||||
|
||||
if (conveyorSequencesForThisMachineSequence.length > 0) {
|
||||
allConveyorSequences.push(conveyorSequencesForThisMachineSequence);
|
||||
}
|
||||
}
|
||||
|
||||
return allConveyorSequences;
|
||||
}
|
||||
|
||||
// Helper function to get machine sequences (simplified from your example)
|
||||
function determineExecutionMachineSequences(products: productsSchema): EventsSchema[][] {
|
||||
const pointToEventMap = new Map<string, EventsSchema>();
|
||||
const allPoints: PointsScheme[] = [];
|
||||
|
||||
// First pass: map points to their corresponding events
|
||||
products.forEach(product => {
|
||||
product.eventDatas.forEach(event => {
|
||||
if (event.type === 'transfer') {
|
||||
event.points.forEach(point => {
|
||||
pointToEventMap.set(point.uuid, event);
|
||||
allPoints.push(point);
|
||||
});
|
||||
} else if (
|
||||
event.type === 'vehicle' ||
|
||||
event.type === 'machine' ||
|
||||
event.type === 'storageUnit' ||
|
||||
event.type === 'roboticArm'
|
||||
) {
|
||||
pointToEventMap.set(event.point.uuid, event);
|
||||
allPoints.push(event.point);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Build dependency graph
|
||||
const dependencyGraph = new Map<string, string[]>();
|
||||
const triggeredPoints = new Set<string>();
|
||||
|
||||
allPoints.forEach(point => {
|
||||
const triggers = extractTriggersFromPoint(point);
|
||||
const dependencies: string[] = [];
|
||||
|
||||
triggers.forEach(trigger => {
|
||||
const targetUuid = trigger.triggeredAsset?.triggeredPoint?.pointUuid;
|
||||
if (targetUuid && pointToEventMap.has(targetUuid)) {
|
||||
dependencies.push(targetUuid);
|
||||
triggeredPoints.add(targetUuid);
|
||||
}
|
||||
});
|
||||
|
||||
dependencyGraph.set(point.uuid, dependencies);
|
||||
});
|
||||
|
||||
// Find root points (points that aren't triggered by others)
|
||||
const rootPoints = allPoints.filter(point =>
|
||||
!triggeredPoints.has(point.uuid) &&
|
||||
dependencyGraph.get(point.uuid)?.length
|
||||
);
|
||||
|
||||
const executionSequences: EventsSchema[][] = [];
|
||||
|
||||
function buildSequence(startUuid: string): EventsSchema[] {
|
||||
const sequence: EventsSchema[] = [];
|
||||
const visited = new Set<string>();
|
||||
|
||||
function traverse(uuid: string) {
|
||||
if (visited.has(uuid)) return;
|
||||
visited.add(uuid);
|
||||
|
||||
const event = pointToEventMap.get(uuid);
|
||||
if (event && !sequence.includes(event)) {
|
||||
sequence.push(event);
|
||||
}
|
||||
|
||||
const nextPoints = dependencyGraph.get(uuid) || [];
|
||||
nextPoints.forEach(nextUuid => traverse(nextUuid));
|
||||
}
|
||||
|
||||
traverse(startUuid);
|
||||
return sequence;
|
||||
}
|
||||
|
||||
// Build sequences from root points
|
||||
rootPoints.forEach(root => {
|
||||
executionSequences.push(buildSequence(root.uuid));
|
||||
});
|
||||
|
||||
return executionSequences;
|
||||
}
|
|
@ -138,6 +138,7 @@ export function useTriggerHandler() {
|
|||
if (armBot.isActive === false && armBot.state === 'idle') {
|
||||
|
||||
// Handle current action from arm bot
|
||||
setIsPaused(materialId, true);
|
||||
handleAction(action, materialId);
|
||||
|
||||
} else {
|
||||
|
@ -291,6 +292,9 @@ export function useTriggerHandler() {
|
|||
// Machine to Robotic Arm
|
||||
if (materialId && trigger.triggeredAsset && trigger.triggeredAsset.triggeredPoint && trigger.triggeredAsset.triggeredAction) {
|
||||
const material = getMaterialById(materialId);
|
||||
|
||||
setIsPaused(materialId, true);
|
||||
|
||||
if (material) {
|
||||
const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||
const armBot = getArmBotById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
@ -302,7 +306,7 @@ export function useTriggerHandler() {
|
|||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid,
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
@ -338,7 +342,7 @@ export function useTriggerHandler() {
|
|||
const material = getMaterialById(materialId);
|
||||
if (material) {
|
||||
|
||||
setIsPaused(material.materialId, false);
|
||||
// setIsPaused(material.materialId, false);
|
||||
|
||||
setPreviousLocation(material.materialId, {
|
||||
modelUuid: material.current.modelUuid,
|
||||
|
@ -477,7 +481,7 @@ export function useTriggerHandler() {
|
|||
const material = getMaterialById(materialId);
|
||||
if (material) {
|
||||
|
||||
setIsPaused(material.materialId, false);
|
||||
// setIsPaused(material.materialId, false);
|
||||
|
||||
const action = getActionByUuid(selectedProduct.productId, trigger.triggeredAsset.triggeredAction.actionUuid);
|
||||
const machine = getMachineById(trigger.triggeredAsset?.triggeredModel.modelUuid);
|
||||
|
@ -498,7 +502,7 @@ export function useTriggerHandler() {
|
|||
})
|
||||
|
||||
setCurrentLocation(material.materialId, {
|
||||
modelUuid: trigger.triggeredAsset.triggeredModel.modelUuid,
|
||||
modelUuid: material.current.modelUuid,
|
||||
pointUuid: trigger.triggeredAsset.triggeredPoint.pointUuid,
|
||||
actionUuid: trigger.triggeredAsset?.triggeredAction?.actionUuid,
|
||||
});
|
||||
|
|
|
@ -280,26 +280,34 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
|||
unLoadDuration: number,
|
||||
action: VehicleAction
|
||||
) {
|
||||
startTime = performance.now();
|
||||
const fixedInterval = unLoadDuration * (1000 / speed);
|
||||
let lastIncrementTime = performance.now();
|
||||
let pauseStartTime: number | null = null;
|
||||
let totalPausedDuration = 0;
|
||||
const fixedInterval = (unLoadDuration * 1000) / speed;
|
||||
|
||||
const dropLoop = () => {
|
||||
if (isPausedRef.current) {
|
||||
pauseTimeRef.current ??= performance.now();
|
||||
const dropLoop = (currentTime: number) => {
|
||||
const conveyor = getConveyorById(conveyorId);
|
||||
|
||||
if (isPausedRef.current || (conveyor && conveyor.isPaused)) {
|
||||
if (pauseStartTime === null) {
|
||||
pauseStartTime = currentTime;
|
||||
}
|
||||
requestAnimationFrame(dropLoop);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pauseTimeRef.current) {
|
||||
const pauseDuration = performance.now() - pauseTimeRef.current;
|
||||
startTime += pauseDuration;
|
||||
pauseTimeRef.current = null;
|
||||
// If we were paused but now resumed
|
||||
if (pauseStartTime !== null) {
|
||||
totalPausedDuration += currentTime - pauseStartTime;
|
||||
pauseStartTime = null;
|
||||
}
|
||||
|
||||
const elapsedTime = performance.now() - startTime;
|
||||
const conveyor = getConveyorById(conveyorId);
|
||||
if (elapsedTime >= fixedInterval) {
|
||||
if (conveyor && !conveyor.isPaused && vehicleCurrentLoad > 0) {
|
||||
// Adjust for paused time
|
||||
const adjustedCurrentTime = currentTime - totalPausedDuration;
|
||||
const elapsedSinceLastIncrement = adjustedCurrentTime - lastIncrementTime;
|
||||
|
||||
if (elapsedSinceLastIncrement >= fixedInterval) {
|
||||
if (conveyor && vehicleCurrentLoad > 0) {
|
||||
decrementVehicleLoad(vehicleId, 1);
|
||||
vehicleCurrentLoad -= 1;
|
||||
|
||||
|
@ -308,18 +316,18 @@ function VehicleInstance({ agvDetail }: Readonly<{ agvDetail: VehicleStatus }>)
|
|||
triggerPointActions(action, material.materialId);
|
||||
}
|
||||
|
||||
if (vehicleCurrentLoad > 0) {
|
||||
startTime = performance.now();
|
||||
requestAnimationFrame(dropLoop);
|
||||
}
|
||||
} else if (!conveyor?.isActive) {
|
||||
requestAnimationFrame(dropLoop);
|
||||
// Update the last increment time (using adjusted time)
|
||||
lastIncrementTime = adjustedCurrentTime;
|
||||
}
|
||||
} else {
|
||||
}
|
||||
|
||||
// Continue the loop if there's more load to drop
|
||||
if (vehicleCurrentLoad > 0) {
|
||||
requestAnimationFrame(dropLoop);
|
||||
}
|
||||
};
|
||||
dropLoop();
|
||||
|
||||
requestAnimationFrame(dropLoop);
|
||||
}
|
||||
|
||||
function handleMaterialDropToArmBot(model: RoboticArmEventSchema) {
|
||||
|
|
|
@ -244,7 +244,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
|||
{/* "+" Button */}
|
||||
|
||||
<button
|
||||
id="panel-add-button"
|
||||
id={`${side}-add-button`}
|
||||
className={`side-button ${side}${
|
||||
selectedZone.activeSides.includes(side) ? " active" : ""
|
||||
}`}
|
||||
|
@ -280,7 +280,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
|||
? "active"
|
||||
: ""
|
||||
}`}
|
||||
id="hide-panel-visulization"
|
||||
id={`${side}-hide-panel-visulization`}
|
||||
title={
|
||||
hiddenPanels[selectedZone.zoneId]?.includes(side)
|
||||
? "Show Panel"
|
||||
|
@ -301,7 +301,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
|||
<button
|
||||
className="icon"
|
||||
title="Clean Panel"
|
||||
id="clean-panel-visulization"
|
||||
id={`${side}-clean-panel-visulization`}
|
||||
onClick={() => cleanPanel(side)}
|
||||
style={{
|
||||
cursor:
|
||||
|
@ -319,7 +319,7 @@ const AddButtons: React.FC<ButtonsProps> = ({
|
|||
className={`icon ${
|
||||
selectedZone.lockedPanels.includes(side) ? "active" : ""
|
||||
}`}
|
||||
id="lock-panel-visulization"
|
||||
id={`${side}-lock-panel-visulization`}
|
||||
title={
|
||||
selectedZone.lockedPanels.includes(side)
|
||||
? "Unlock Panel"
|
||||
|
|
|
@ -20,7 +20,6 @@ import { useNavigate } from "react-router-dom";
|
|||
import { usePlayButtonStore } from "../store/usePlayButtonStore";
|
||||
import MarketPlace from "../modules/market/MarketPlace";
|
||||
import LoadingPage from "../components/templates/LoadingPage";
|
||||
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
||||
import KeyPressListener from "../utils/shortcutkeys/handleShortcutKeys";
|
||||
import { useSelectedUserStore } from "../store/useCollabStore";
|
||||
import FollowPerson from "../components/templates/FollowPerson";
|
||||
|
@ -34,9 +33,11 @@ import LogList from "../components/ui/log/LogList";
|
|||
import Footer from "../components/footer/Footer";
|
||||
import SelectFloorPlan from "../components/temporary/SelectFloorPlan";
|
||||
import ControlsPlayer from "../components/layout/controls/ControlsPlayer";
|
||||
import CompareLayOut from "../components/ui/compare/CompareLayOut";
|
||||
import CompareLayOut from "../components/ui/compareVersion/CompareLayOut";
|
||||
import useToggleStore from "../store/useUIToggleStore";
|
||||
import RegularDropDown from "../components/ui/inputs/RegularDropDown";
|
||||
import VersionSaved from "../components/layout/sidebarRight/versionHisory/VersionSaved";
|
||||
import SimulationPlayer from "../components/ui/simulation/simulationPlayer";
|
||||
|
||||
const Project: React.FC = () => {
|
||||
let navigate = useNavigate();
|
||||
|
@ -178,6 +179,7 @@ const Project: React.FC = () => {
|
|||
<CompareLayOut dummyLayouts={dummyLayouts} />
|
||||
</>
|
||||
)}
|
||||
<VersionSaved />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -477,15 +477,52 @@ export const useCompareStore = create<CompareStore>((set) => ({
|
|||
toggleComparePopUp: () =>
|
||||
set((state) => ({ comparePopUp: !state.comparePopUp })),
|
||||
}));
|
||||
|
||||
// Define the types for the store state
|
||||
interface VersionStore {
|
||||
// Save state store
|
||||
interface SaveVersionStore {
|
||||
isVersionSaved: boolean;
|
||||
setIsVersionSaved: (value: boolean) => void;
|
||||
}
|
||||
|
||||
// Create the Zustand store
|
||||
export const useSaveVersion = create<VersionStore>((set) => ({
|
||||
isVersionSaved: false, // Default state
|
||||
setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }), // Function to update the state
|
||||
export const useSaveVersion = create<SaveVersionStore>((set) => ({
|
||||
isVersionSaved: false,
|
||||
setIsVersionSaved: (value: boolean) => set({ isVersionSaved: value }),
|
||||
}));
|
||||
|
||||
// Version object type
|
||||
export interface Version {
|
||||
id: string;
|
||||
versionLabel: string;
|
||||
versionName?: string;
|
||||
timestamp: string;
|
||||
savedBy: string;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
// Version list store
|
||||
interface VersionListStore {
|
||||
versions: Version[];
|
||||
addVersion: (version: Version) => void;
|
||||
clearVersions: () => void;
|
||||
setVersions: (newVersions: Version[]) => void;
|
||||
updateVersion: (id: string, data: Partial<Version>) => void; // ✅ Added
|
||||
}
|
||||
|
||||
export const useVersionStore = create<VersionListStore>((set) => ({
|
||||
versions: [],
|
||||
|
||||
addVersion: (newVersion) =>
|
||||
set((state) => ({
|
||||
versions: [newVersion, ...state.versions],
|
||||
})),
|
||||
|
||||
clearVersions: () => set({ versions: [] }),
|
||||
|
||||
setVersions: (newVersions) => set({ versions: newVersions }),
|
||||
|
||||
updateVersion: (id, data) =>
|
||||
set((state) => ({
|
||||
versions: state.versions.map((version) =>
|
||||
version.id === id ? { ...version, ...data } : version
|
||||
),
|
||||
})),
|
||||
}));
|
||||
|
|
|
@ -1738,4 +1738,178 @@
|
|||
to {
|
||||
height: 60%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.versionSaved {
|
||||
min-width: 449px;
|
||||
position: fixed;
|
||||
bottom: 45px;
|
||||
right: 10px;
|
||||
z-index: 10;
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
.version-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.version-details {
|
||||
width: 100%;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
|
||||
.details {
|
||||
width: 100%;
|
||||
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
outline: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 7px 12px;
|
||||
|
||||
.details-wrapper {
|
||||
font-size: var(--font-size-small);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
button {
|
||||
|
||||
font-size: var(--font-size-small);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
border-radius: 12px;
|
||||
padding: 5px 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.dismissing {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.edit-version-popup-wrapper {
|
||||
|
||||
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background: var(--background-color-secondary);
|
||||
@include flex-center;
|
||||
|
||||
.details-wrapper-popup-container {
|
||||
min-width: 535px;
|
||||
width: 520px;
|
||||
background: var(--background-color);
|
||||
border-radius: #{$border-radius-large};
|
||||
backdrop-filter: blur(15px);
|
||||
outline: 1px solid var(--border-color);
|
||||
padding: 6px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
padding: 20px;
|
||||
|
||||
.header-wrapper {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.details-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
|
||||
.version-name,
|
||||
.version-description {
|
||||
|
||||
background: var(--background-color);
|
||||
backdrop-filter: blur(20px);
|
||||
border-radius: 20px;
|
||||
outline: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 4px 12px;
|
||||
position: relative;
|
||||
|
||||
.label {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
font-size: var(--font-size-tiny);
|
||||
color: var(--text-disabled);
|
||||
|
||||
}
|
||||
|
||||
input {
|
||||
border: none;
|
||||
background: none;
|
||||
outline: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.version-description {
|
||||
textarea {
|
||||
|
||||
padding: 4px 8px;
|
||||
width: 100%;
|
||||
min-height: 101px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 20px;
|
||||
|
||||
.save {
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--background-color-button);
|
||||
color: var(--text-button-color);
|
||||
border-radius: 12px;
|
||||
padding: 5px 12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue