Merge remote-tracking branch 'origin/simulation-animation' into simulation
This commit is contained in:
commit
d74b9969f4
|
@ -29,13 +29,13 @@ export function DownloadIcon() {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M2.5 11.875C2.84518 11.875 3.125 12.1548 3.125 12.5C3.125 13.6962 3.12633 14.5304 3.21096 15.1599C3.29317 15.7714 3.44354 16.0952 3.67418 16.3258C3.90481 16.5565 4.22863 16.7068 4.8401 16.7891C5.46956 16.8737 6.30383 16.875 7.5 16.875H12.5C13.6962 16.875 14.5304 16.8737 15.1599 16.7891C15.7714 16.7068 16.0952 16.5565 16.3258 16.3258C16.5565 16.0952 16.7068 15.7714 16.7891 15.1599C16.8737 14.5304 16.875 13.6962 16.875 12.5C16.875 12.1548 17.1548 11.875 17.5 11.875C17.8452 11.875 18.125 12.1548 18.125 12.5V12.5458C18.125 13.6854 18.125 14.604 18.0279 15.3265C17.9271 16.0766 17.7113 16.7081 17.2097 17.2097C16.7081 17.7113 16.0766 17.9271 15.3265 18.0279C14.604 18.125 13.6854 18.125 12.5458 18.125H7.45428C6.31462 18.125 5.39602 18.125 4.67354 18.0279C3.92345 17.9271 3.29189 17.7113 2.79029 17.2097C2.28869 16.7081 2.07295 16.0766 1.9721 15.3265C1.87497 14.604 1.87498 13.6854 1.875 12.5458C1.875 12.5305 1.875 12.5152 1.875 12.5C1.875 12.1548 2.15483 11.875 2.5 11.875Z"
|
d="M2.5 11.875C2.84518 11.875 3.125 12.1548 3.125 12.5C3.125 13.6962 3.12633 14.5304 3.21096 15.1599C3.29317 15.7714 3.44354 16.0952 3.67418 16.3258C3.90481 16.5565 4.22863 16.7068 4.8401 16.7891C5.46956 16.8737 6.30383 16.875 7.5 16.875H12.5C13.6962 16.875 14.5304 16.8737 15.1599 16.7891C15.7714 16.7068 16.0952 16.5565 16.3258 16.3258C16.5565 16.0952 16.7068 15.7714 16.7891 15.1599C16.8737 14.5304 16.875 13.6962 16.875 12.5C16.875 12.1548 17.1548 11.875 17.5 11.875C17.8452 11.875 18.125 12.1548 18.125 12.5V12.5458C18.125 13.6854 18.125 14.604 18.0279 15.3265C17.9271 16.0766 17.7113 16.7081 17.2097 17.2097C16.7081 17.7113 16.0766 17.9271 15.3265 18.0279C14.604 18.125 13.6854 18.125 12.5458 18.125H7.45428C6.31462 18.125 5.39602 18.125 4.67354 18.0279C3.92345 17.9271 3.29189 17.7113 2.79029 17.2097C2.28869 16.7081 2.07295 16.0766 1.9721 15.3265C1.87497 14.604 1.87498 13.6854 1.875 12.5458C1.875 12.5305 1.875 12.5152 1.875 12.5C1.875 12.1548 2.15483 11.875 2.5 11.875Z"
|
||||||
fill="#FCFDFD"
|
fill="var(--primary-color)"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M10.0003 13.9583C10.1758 13.9583 10.3432 13.8846 10.4616 13.7551L13.7949 10.1093C14.0278 9.8545 14.0102 9.45917 13.7554 9.22625C13.5007 8.99333 13.1053 9.011 12.8724 9.26575L10.6253 11.7235V2.5C10.6253 2.15483 10.3455 1.875 10.0003 1.875C9.65516 1.875 9.37533 2.15483 9.37533 2.5V11.7235L7.12827 9.26575C6.89535 9.011 6.50002 8.99333 6.24527 9.22625C5.99052 9.45917 5.97281 9.8545 6.20573 10.1093L9.53908 13.7551C9.65749 13.8846 9.82483 13.9583 10.0003 13.9583Z"
|
d="M10.0003 13.9583C10.1758 13.9583 10.3432 13.8846 10.4616 13.7551L13.7949 10.1093C14.0278 9.8545 14.0102 9.45917 13.7554 9.22625C13.5007 8.99333 13.1053 9.011 12.8724 9.26575L10.6253 11.7235V2.5C10.6253 2.15483 10.3455 1.875 10.0003 1.875C9.65516 1.875 9.37533 2.15483 9.37533 2.5V11.7235L7.12827 9.26575C6.89535 9.011 6.50002 8.99333 6.24527 9.22625C5.99052 9.45917 5.97281 9.8545 6.20573 10.1093L9.53908 13.7551C9.65749 13.8846 9.82483 13.9583 10.0003 13.9583Z"
|
||||||
fill="#FCFDFD"
|
fill="var(--primary-color)"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
@ -52,13 +52,13 @@ export function EyeIconBig() {
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
d="M2.75 11.918C6.05 4.58464 15.95 4.58464 19.25 11.918"
|
d="M2.75 11.918C6.05 4.58464 15.95 4.58464 19.25 11.918"
|
||||||
stroke="#2B3344"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
<path
|
<path
|
||||||
d="M11 15.5859C10.6389 15.5859 10.2813 15.5148 9.94762 15.3766C9.61398 15.2384 9.31082 15.0358 9.05546 14.7805C8.80009 14.5251 8.59753 14.222 8.45933 13.8883C8.32113 13.5547 8.25 13.1971 8.25 12.8359C8.25 12.4748 8.32113 12.1172 8.45933 11.7836C8.59753 11.4499 8.80009 11.1468 9.05546 10.8914C9.31082 10.636 9.61398 10.4335 9.94762 10.2953C10.2813 10.1571 10.6389 10.0859 11 10.0859C11.7293 10.0859 12.4288 10.3757 12.9445 10.8914C13.4603 11.4071 13.75 12.1066 13.75 12.8359C13.75 13.5653 13.4603 14.2648 12.9445 14.7805C12.4288 15.2962 11.7293 15.5859 11 15.5859Z"
|
d="M11 15.5859C10.6389 15.5859 10.2813 15.5148 9.94762 15.3766C9.61398 15.2384 9.31082 15.0358 9.05546 14.7805C8.80009 14.5251 8.59753 14.222 8.45933 13.8883C8.32113 13.5547 8.25 13.1971 8.25 12.8359C8.25 12.4748 8.32113 12.1172 8.45933 11.7836C8.59753 11.4499 8.80009 11.1468 9.05546 10.8914C9.31082 10.636 9.61398 10.4335 9.94762 10.2953C10.2813 10.1571 10.6389 10.0859 11 10.0859C11.7293 10.0859 12.4288 10.3757 12.9445 10.8914C13.4603 11.4071 13.75 12.1066 13.75 12.8359C13.75 13.5653 13.4603 14.2648 12.9445 14.7805C12.4288 15.2962 11.7293 15.5859 11 15.5859Z"
|
||||||
stroke="#2B3344"
|
stroke="var(--text-color)"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
/>
|
/>
|
||||||
|
@ -80,7 +80,7 @@ export function CommentsIcon() {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M8 13C7.416 13 6.852 12.932 6.31 12.8165L3.956 14.2315L3.9875 11.912C2.183 10.827 1 9.033 1 7C1 3.6865 4.134 1 8 1C11.866 1 15 3.6865 15 7C15 10.314 11.866 13 8 13ZM8 0C3.582 0 0 3.1345 0 7C0 9.2095 1.1725 11.177 3 12.4595V16L6.5045 13.8735C6.9895 13.9535 7.4885 14 8 14C12.418 14 16 10.866 16 7C16 3.1345 12.418 0 8 0ZM11.5 5.5H4.5C4.224 5.5 4 5.724 4 6C4 6.2765 4.224 6.5 4.5 6.5H11.5C11.776 6.5 12 6.2765 12 6C12 5.724 11.776 5.5 11.5 5.5ZM10.5 8.5H5.5C5.224 8.5 5 8.7235 5 9C5 9.2765 5.224 9.5 5.5 9.5H10.5C10.776 9.5 11 9.2765 11 9C11 8.7235 10.776 8.5 10.5 8.5Z"
|
d="M8 13C7.416 13 6.852 12.932 6.31 12.8165L3.956 14.2315L3.9875 11.912C2.183 10.827 1 9.033 1 7C1 3.6865 4.134 1 8 1C11.866 1 15 3.6865 15 7C15 10.314 11.866 13 8 13ZM8 0C3.582 0 0 3.1345 0 7C0 9.2095 1.1725 11.177 3 12.4595V16L6.5045 13.8735C6.9895 13.9535 7.4885 14 8 14C12.418 14 16 10.866 16 7C16 3.1345 12.418 0 8 0ZM11.5 5.5H4.5C4.224 5.5 4 5.724 4 6C4 6.2765 4.224 6.5 4.5 6.5H11.5C11.776 6.5 12 6.2765 12 6C12 5.724 11.776 5.5 11.5 5.5ZM10.5 8.5H5.5C5.224 8.5 5 8.7235 5 9C5 9.2765 5.224 9.5 5.5 9.5H10.5C10.776 9.5 11 9.2765 11 9C11 8.7235 10.776 8.5 10.5 8.5Z"
|
||||||
fill="#2B3344"
|
fill="var(--text-color)"
|
||||||
/>
|
/>
|
||||||
</g>
|
</g>
|
||||||
<defs>
|
<defs>
|
||||||
|
@ -105,7 +105,7 @@ export function VerifiedIcon() {
|
||||||
fillRule="evenodd"
|
fillRule="evenodd"
|
||||||
clipRule="evenodd"
|
clipRule="evenodd"
|
||||||
d="M4.7962 2.10014C4.67444 2.2039 4.61356 2.25579 4.54853 2.29937C4.39948 2.39927 4.23209 2.46861 4.05605 2.50336C3.97926 2.51853 3.89952 2.52489 3.74004 2.53761C3.33935 2.56959 3.139 2.58558 2.97186 2.64462C2.58526 2.78117 2.28117 3.08526 2.14462 3.47186C2.08558 3.639 2.06959 3.83935 2.03761 4.24004C2.02489 4.39952 2.01853 4.47926 2.00336 4.55605C1.96861 4.73209 1.89927 4.89948 1.79937 5.04853C1.75579 5.11356 1.70391 5.17444 1.60014 5.2962C1.33942 5.60215 1.20905 5.7551 1.13261 5.91505C0.955796 6.285 0.955796 6.715 1.13261 7.08495C1.20906 7.2449 1.33942 7.39785 1.60014 7.7038C1.70389 7.82555 1.75579 7.88645 1.79937 7.95145C1.89927 8.1005 1.96861 8.2679 2.00336 8.44395C2.01853 8.52075 2.02489 8.6005 2.03761 8.75995C2.06959 9.16065 2.08558 9.361 2.14462 9.52815C2.28117 9.91475 2.58526 10.2189 2.97186 10.3554C3.139 10.4144 3.33935 10.4304 3.74004 10.4624C3.89952 10.4751 3.97926 10.4815 4.05605 10.4966C4.23209 10.5314 4.39948 10.6007 4.54853 10.7007C4.61356 10.7442 4.67444 10.7961 4.7962 10.8998C5.10215 11.1606 5.2551 11.2909 5.41505 11.3674C5.785 11.5442 6.215 11.5442 6.58495 11.3674C6.7449 11.2909 6.89785 11.1606 7.2038 10.8998C7.32555 10.7961 7.38645 10.7442 7.45145 10.7007C7.6005 10.6007 7.7679 10.5314 7.94395 10.4966C8.02075 10.4815 8.1005 10.4751 8.25995 10.4624C8.66065 10.4304 8.861 10.4144 9.02815 10.3554C9.41475 10.2189 9.71885 9.91475 9.8554 9.52815C9.9144 9.361 9.9304 9.16065 9.9624 8.75995C9.9751 8.6005 9.9815 8.52075 9.99665 8.44395C10.0314 8.2679 10.1007 8.1005 10.2007 7.95145C10.2442 7.88645 10.2961 7.82555 10.3998 7.7038C10.6606 7.39785 10.7909 7.2449 10.8674 7.08495C11.0442 6.715 11.0442 6.285 10.8674 5.91505C10.7909 5.7551 10.6606 5.60215 10.3998 5.2962C10.2961 5.17444 10.2442 5.11356 10.2007 5.04853C10.1007 4.89948 10.0314 4.73209 9.99665 4.55605C9.9815 4.47926 9.9751 4.39952 9.9624 4.24004C9.9304 3.83935 9.9144 3.639 9.8554 3.47186C9.71885 3.08526 9.41475 2.78117 9.02815 2.64462C8.861 2.58558 8.66065 2.56959 8.25995 2.53761C8.1005 2.52489 8.02075 2.51853 7.94395 2.50336C7.7679 2.46861 7.6005 2.39927 7.45145 2.29937C7.38645 2.25579 7.32555 2.20391 7.2038 2.10014C6.89785 1.83942 6.7449 1.70906 6.58495 1.63261C6.215 1.4558 5.785 1.4558 5.41505 1.63261C5.2551 1.70905 5.10215 1.83942 4.7962 2.10014ZM8.18675 5.43157C8.34565 5.27265 8.34565 5.015 8.18675 4.85608C8.02785 4.69717 7.77015 4.69717 7.61125 4.85608L5.18615 7.2812L4.38873 6.4838C4.22982 6.3249 3.97216 6.3249 3.81325 6.4838C3.65433 6.6427 3.65433 6.90035 3.81325 7.0593L4.89839 8.14445C5.0573 8.30335 5.31495 8.30335 5.4739 8.14445L8.18675 5.43157Z"
|
d="M4.7962 2.10014C4.67444 2.2039 4.61356 2.25579 4.54853 2.29937C4.39948 2.39927 4.23209 2.46861 4.05605 2.50336C3.97926 2.51853 3.89952 2.52489 3.74004 2.53761C3.33935 2.56959 3.139 2.58558 2.97186 2.64462C2.58526 2.78117 2.28117 3.08526 2.14462 3.47186C2.08558 3.639 2.06959 3.83935 2.03761 4.24004C2.02489 4.39952 2.01853 4.47926 2.00336 4.55605C1.96861 4.73209 1.89927 4.89948 1.79937 5.04853C1.75579 5.11356 1.70391 5.17444 1.60014 5.2962C1.33942 5.60215 1.20905 5.7551 1.13261 5.91505C0.955796 6.285 0.955796 6.715 1.13261 7.08495C1.20906 7.2449 1.33942 7.39785 1.60014 7.7038C1.70389 7.82555 1.75579 7.88645 1.79937 7.95145C1.89927 8.1005 1.96861 8.2679 2.00336 8.44395C2.01853 8.52075 2.02489 8.6005 2.03761 8.75995C2.06959 9.16065 2.08558 9.361 2.14462 9.52815C2.28117 9.91475 2.58526 10.2189 2.97186 10.3554C3.139 10.4144 3.33935 10.4304 3.74004 10.4624C3.89952 10.4751 3.97926 10.4815 4.05605 10.4966C4.23209 10.5314 4.39948 10.6007 4.54853 10.7007C4.61356 10.7442 4.67444 10.7961 4.7962 10.8998C5.10215 11.1606 5.2551 11.2909 5.41505 11.3674C5.785 11.5442 6.215 11.5442 6.58495 11.3674C6.7449 11.2909 6.89785 11.1606 7.2038 10.8998C7.32555 10.7961 7.38645 10.7442 7.45145 10.7007C7.6005 10.6007 7.7679 10.5314 7.94395 10.4966C8.02075 10.4815 8.1005 10.4751 8.25995 10.4624C8.66065 10.4304 8.861 10.4144 9.02815 10.3554C9.41475 10.2189 9.71885 9.91475 9.8554 9.52815C9.9144 9.361 9.9304 9.16065 9.9624 8.75995C9.9751 8.6005 9.9815 8.52075 9.99665 8.44395C10.0314 8.2679 10.1007 8.1005 10.2007 7.95145C10.2442 7.88645 10.2961 7.82555 10.3998 7.7038C10.6606 7.39785 10.7909 7.2449 10.8674 7.08495C11.0442 6.715 11.0442 6.285 10.8674 5.91505C10.7909 5.7551 10.6606 5.60215 10.3998 5.2962C10.2961 5.17444 10.2442 5.11356 10.2007 5.04853C10.1007 4.89948 10.0314 4.73209 9.99665 4.55605C9.9815 4.47926 9.9751 4.39952 9.9624 4.24004C9.9304 3.83935 9.9144 3.639 9.8554 3.47186C9.71885 3.08526 9.41475 2.78117 9.02815 2.64462C8.861 2.58558 8.66065 2.56959 8.25995 2.53761C8.1005 2.52489 8.02075 2.51853 7.94395 2.50336C7.7679 2.46861 7.6005 2.39927 7.45145 2.29937C7.38645 2.25579 7.32555 2.20391 7.2038 2.10014C6.89785 1.83942 6.7449 1.70906 6.58495 1.63261C6.215 1.4558 5.785 1.4558 5.41505 1.63261C5.2551 1.70905 5.10215 1.83942 4.7962 2.10014ZM8.18675 5.43157C8.34565 5.27265 8.34565 5.015 8.18675 4.85608C8.02785 4.69717 7.77015 4.69717 7.61125 4.85608L5.18615 7.2812L4.38873 6.4838C4.22982 6.3249 3.97216 6.3249 3.81325 6.4838C3.65433 6.6427 3.65433 6.90035 3.81325 7.0593L4.89839 8.14445C5.0573 8.30335 5.31495 8.30335 5.4739 8.14445L8.18675 5.43157Z"
|
||||||
fill="#6F42C1"
|
fill="var(--accent-color)"
|
||||||
/>
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,7 +40,6 @@ const GlobalProperties: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateDistance(value: number) {
|
function updateDistance(value: number) {
|
||||||
console.log("value: ", value);
|
|
||||||
setDistance(value);
|
setDistance(value);
|
||||||
setRenderDistance(value);
|
setRenderDistance(value);
|
||||||
}
|
}
|
||||||
|
@ -61,7 +60,7 @@ const GlobalProperties: React.FC = () => {
|
||||||
!roofVisibility,
|
!roofVisibility,
|
||||||
shadows
|
shadows
|
||||||
);
|
);
|
||||||
// console.log('data: ', data);
|
//
|
||||||
|
|
||||||
//using Socket
|
//using Socket
|
||||||
// const visData = {
|
// const visData = {
|
||||||
|
@ -88,7 +87,7 @@ const GlobalProperties: React.FC = () => {
|
||||||
roofVisibility,
|
roofVisibility,
|
||||||
shadows
|
shadows
|
||||||
);
|
);
|
||||||
// console.log('data: ', data);
|
//
|
||||||
|
|
||||||
//using Socket
|
//using Socket
|
||||||
// const visData = {
|
// const visData = {
|
||||||
|
@ -115,7 +114,7 @@ const GlobalProperties: React.FC = () => {
|
||||||
roofVisibility,
|
roofVisibility,
|
||||||
!shadows
|
!shadows
|
||||||
);
|
);
|
||||||
// console.log('data: ', data);
|
//
|
||||||
|
|
||||||
//using Socket
|
//using Socket
|
||||||
// const visData = {
|
// const visData = {
|
||||||
|
|
|
@ -15,7 +15,6 @@ interface DisplayZoneProps {
|
||||||
[key: string]: {
|
[key: string]: {
|
||||||
activeSides: Side[];
|
activeSides: Side[];
|
||||||
panelOrder: Side[];
|
panelOrder: Side[];
|
||||||
|
|
||||||
lockedPanels: Side[];
|
lockedPanels: Side[];
|
||||||
widgets: Widget[];
|
widgets: Widget[];
|
||||||
zoneId: string;
|
zoneId: string;
|
||||||
|
@ -27,7 +26,6 @@ interface DisplayZoneProps {
|
||||||
zoneName: string;
|
zoneName: string;
|
||||||
activeSides: Side[];
|
activeSides: Side[];
|
||||||
panelOrder: Side[];
|
panelOrder: Side[];
|
||||||
|
|
||||||
lockedPanels: Side[];
|
lockedPanels: Side[];
|
||||||
zoneId: string;
|
zoneId: string;
|
||||||
zoneViewPortTarget: number[];
|
zoneViewPortTarget: number[];
|
||||||
|
@ -45,7 +43,6 @@ interface DisplayZoneProps {
|
||||||
zoneName: string;
|
zoneName: string;
|
||||||
activeSides: Side[];
|
activeSides: Side[];
|
||||||
panelOrder: Side[];
|
panelOrder: Side[];
|
||||||
|
|
||||||
lockedPanels: Side[];
|
lockedPanels: Side[];
|
||||||
zoneId: string;
|
zoneId: string;
|
||||||
zoneViewPortTarget: number[];
|
zoneViewPortTarget: number[];
|
||||||
|
@ -66,19 +63,18 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
selectedZone,
|
selectedZone,
|
||||||
setSelectedZone,
|
setSelectedZone,
|
||||||
}) => {
|
}) => {
|
||||||
|
// Refs
|
||||||
// Ref for the container element
|
|
||||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
const scrollContainerRef = useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
// State to track overflow visibility
|
// State to track overflow visibility
|
||||||
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
const [showLeftArrow, setShowLeftArrow] = useState(false);
|
||||||
const [showRightArrow, setShowRightArrow] = useState(false);
|
const [showRightArrow, setShowRightArrow] = useState(false);
|
||||||
const { floatingWidget, setFloatingWidget } = useFloatingWidget()
|
const { floatingWidget, setFloatingWidget } = useFloatingWidget();
|
||||||
|
|
||||||
// Function to calculate overflow state
|
// Function to calculate overflow state
|
||||||
const updateOverflowState = useCallback(() => {
|
const updateOverflowState = useCallback(() => {
|
||||||
const container = containerRef.current;
|
const container = scrollContainerRef.current;
|
||||||
|
|
||||||
if (container) {
|
if (container) {
|
||||||
const isOverflowing = container.scrollWidth > container.clientWidth;
|
const isOverflowing = container.scrollWidth > container.clientWidth;
|
||||||
const canScrollLeft = container.scrollLeft > 0;
|
const canScrollLeft = container.scrollLeft > 0;
|
||||||
|
@ -91,59 +87,56 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const container = containerRef.current;
|
const container = scrollContainerRef.current;
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
if (container) {
|
// Initial calculation after the DOM has been rendered
|
||||||
// Initial calculation after the DOM has been rendered
|
const observer = new ResizeObserver(updateOverflowState);
|
||||||
const handleInitialRender = () => {
|
observer.observe(container);
|
||||||
requestAnimationFrame(updateOverflowState);
|
|
||||||
};
|
|
||||||
|
|
||||||
handleInitialRender();
|
// Update on scroll
|
||||||
|
const handleScroll = () => updateOverflowState();
|
||||||
|
container.addEventListener("scroll", handleScroll);
|
||||||
|
|
||||||
// Update on window resize or scroll
|
// Add mouse wheel listener for horizontal scrolling
|
||||||
const handleResize = () => updateOverflowState();
|
const handleWheel = (event: WheelEvent) => {
|
||||||
const handleScroll = () => updateOverflowState();
|
if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) {
|
||||||
|
event.preventDefault();
|
||||||
|
container.scrollBy({
|
||||||
|
left: event.deltaY * 2,
|
||||||
|
behavior: "smooth",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Add mouse wheel listener for horizontal scrolling
|
container.addEventListener("wheel", handleWheel, { passive: false });
|
||||||
const handleWheel = (event: WheelEvent) => {
|
|
||||||
event.preventDefault(); // Prevent default vertical scrolling
|
|
||||||
if (container) {
|
|
||||||
container.scrollBy({
|
|
||||||
left: event.deltaY * 2, // Translate vertical scroll to horizontal scroll
|
|
||||||
behavior: "smooth",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
container.addEventListener("scroll", handleScroll);
|
// Initial check
|
||||||
window.addEventListener("resize", handleResize);
|
updateOverflowState();
|
||||||
container.addEventListener("wheel", handleWheel, { passive: false });
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
container.removeEventListener("scroll", handleScroll);
|
observer.disconnect();
|
||||||
window.removeEventListener("resize", handleResize);
|
container.removeEventListener("scroll", handleScroll);
|
||||||
container.removeEventListener("wheel", handleWheel);
|
container.removeEventListener("wheel", handleWheel);
|
||||||
};
|
};
|
||||||
}
|
|
||||||
}, [updateOverflowState]);
|
}, [updateOverflowState]);
|
||||||
|
|
||||||
// Handle scrolling with navigation arrows
|
// Handle scrolling with navigation arrows
|
||||||
const handleScrollLeft = () => {
|
const handleScrollLeft = () => {
|
||||||
const container = containerRef.current;
|
const container = scrollContainerRef.current;
|
||||||
if (container) {
|
if (container) {
|
||||||
container.scrollBy({
|
container.scrollBy({
|
||||||
left: -200, // Scroll left by 200px
|
left: -200,
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleScrollRight = () => {
|
const handleScrollRight = () => {
|
||||||
const container = containerRef.current;
|
const container = scrollContainerRef.current;
|
||||||
if (container) {
|
if (container) {
|
||||||
container.scrollBy({
|
container.scrollBy({
|
||||||
left: 200, // Scroll right by 200px
|
left: 200,
|
||||||
behavior: "smooth",
|
behavior: "smooth",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -152,24 +145,22 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
|
async function handleSelect2dZoneData(zoneId: string, zoneName: string) {
|
||||||
try {
|
try {
|
||||||
if (selectedZone?.zoneId === zoneId) {
|
if (selectedZone?.zoneId === zoneId) {
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const email = localStorage.getItem("email") || "";
|
const email = localStorage.getItem("email") || "";
|
||||||
const organization = email?.split("@")[1]?.split(".")[0];
|
const organization = email?.split("@")[1]?.split(".")[0];
|
||||||
// Fetch data from backend
|
|
||||||
let response = await getSelect2dZoneData(zoneId, organization);
|
let response = await getSelect2dZoneData(zoneId, organization);
|
||||||
console.log('response: ', response);
|
console.log('response: ', response);
|
||||||
let res = await getFloatingZoneData(zoneId, organization);
|
let res = await getFloatingZoneData(zoneId, organization);
|
||||||
setFloatingWidget(res)
|
setFloatingWidget(res);
|
||||||
// Set the selected zone in the store
|
|
||||||
useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
|
useDroppedObjectsStore.getState().setZone(zoneName, zoneId);
|
||||||
if (Array.isArray(res)) {
|
if (Array.isArray(res)) {
|
||||||
res.forEach((val) => {
|
res.forEach((val) => {
|
||||||
useDroppedObjectsStore.getState().addObject(zoneName, val);
|
useDroppedObjectsStore.getState().addObject(zoneName, val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Update selected zone state
|
|
||||||
setSelectedZone({
|
setSelectedZone({
|
||||||
zoneName,
|
zoneName,
|
||||||
activeSides: response.activeSides || [],
|
activeSides: response.activeSides || [],
|
||||||
|
@ -181,17 +172,14 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
zoneViewPortPosition: response.viewPortposition || {},
|
zoneViewPortPosition: response.viewPortposition || {},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") && "bottom"
|
className={`zone-wrapper ${selectedZone?.activeSides?.includes("bottom") ? "bottom" : ""}`}
|
||||||
}`}
|
|
||||||
>
|
>
|
||||||
{/* Left Arrow */}
|
{/* Left Arrow */}
|
||||||
{showLeftArrow && (
|
{showLeftArrow && (
|
||||||
|
@ -200,28 +188,31 @@ const DisplayZone: React.FC<DisplayZoneProps> = ({
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Zones Wrapper */}
|
{/* Scrollable Zones Container */}
|
||||||
{Object.keys(zonesData).length !== 0 ? (
|
<div
|
||||||
<div ref={containerRef} className="zones-wrapper">
|
ref={scrollContainerRef}
|
||||||
{Object.keys(zonesData).map((zoneName, index) => (
|
className="zones-wrapper"
|
||||||
<div
|
style={{ overflowX: "auto", whiteSpace: "nowrap" }}
|
||||||
key={index}
|
>
|
||||||
className={`zone ${selectedZone.zoneName === zoneName ? "active" : ""
|
{Object.keys(zonesData).length !== 0 ? (
|
||||||
}`}
|
<>
|
||||||
onClick={() => {
|
{Object.keys(zonesData).map((zoneName, index) => (
|
||||||
handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)
|
<div
|
||||||
}}
|
key={index}
|
||||||
>
|
className={`zone ${selectedZone.zoneName === zoneName ? "active" : ""}`}
|
||||||
{zoneName}
|
onClick={() => handleSelect2dZoneData(zonesData[zoneName]?.zoneId, zoneName)}
|
||||||
</div>
|
>
|
||||||
))}
|
{zoneName}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
))}
|
||||||
<div className="no-zone">
|
</>
|
||||||
<InfoIcon />
|
) : (
|
||||||
No zones? Create one!
|
<div className="no-zone">
|
||||||
</div>
|
<InfoIcon />
|
||||||
)}
|
No zones? Create one!
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Right Arrow */}
|
{/* Right Arrow */}
|
||||||
{showRightArrow && (
|
{showRightArrow && (
|
||||||
|
|
|
@ -1,93 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
interface DistanceLinesProps {
|
|
||||||
obj: {
|
|
||||||
position: {
|
|
||||||
top?: number | "auto";
|
|
||||||
left?: number | "auto";
|
|
||||||
right?: number | "auto";
|
|
||||||
bottom?: number | "auto";
|
|
||||||
};
|
|
||||||
};
|
|
||||||
activeEdges: {
|
|
||||||
vertical: "top" | "bottom";
|
|
||||||
horizontal: "left" | "right";
|
|
||||||
} | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DistanceLines: React.FC<DistanceLinesProps> = ({ obj, activeEdges }) => {
|
|
||||||
if (!activeEdges) return null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{activeEdges.vertical === "top" && typeof obj.position.top === "number" && (
|
|
||||||
<div
|
|
||||||
className="distance-line top"
|
|
||||||
style={{
|
|
||||||
top: 0,
|
|
||||||
left:
|
|
||||||
activeEdges.horizontal === "left"
|
|
||||||
? `${(obj.position.left as number) + 125}px`
|
|
||||||
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
|
|
||||||
height: `${obj.position.top}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="distance-label">{obj.position.top.toFixed()}px</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEdges.vertical === "bottom" &&
|
|
||||||
typeof obj.position.bottom === "number" && (
|
|
||||||
<div
|
|
||||||
className="distance-line bottom"
|
|
||||||
style={{
|
|
||||||
bottom: 0,
|
|
||||||
left:
|
|
||||||
activeEdges.horizontal === "left"
|
|
||||||
? `${(obj.position.left as number) + 125}px`
|
|
||||||
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
|
|
||||||
height: `${obj.position.bottom}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="distance-label">{obj.position.bottom.toFixed()}px</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEdges.horizontal === "left" &&
|
|
||||||
typeof obj.position.left === "number" && (
|
|
||||||
<div
|
|
||||||
className="distance-line left"
|
|
||||||
style={{
|
|
||||||
left: 0,
|
|
||||||
top:
|
|
||||||
activeEdges.vertical === "top"
|
|
||||||
? `${(obj.position.top as number) + 41.5}px`
|
|
||||||
: `calc(100% - ${(obj.position.bottom as number) + 41.5}px)`,
|
|
||||||
width: `${obj.position.left}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="distance-label">{obj.position.left.toFixed()}px</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{activeEdges.horizontal === "right" &&
|
|
||||||
typeof obj.position.right === "number" && (
|
|
||||||
<div
|
|
||||||
className="distance-line right"
|
|
||||||
style={{
|
|
||||||
right: 0,
|
|
||||||
top:
|
|
||||||
activeEdges.vertical === "top"
|
|
||||||
? `${(obj.position.top as number) + 41.5}px`
|
|
||||||
: `calc(100% - ${(obj.position.bottom as number) + 41.5}px)`,
|
|
||||||
width: `${obj.position.right}px`,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span className="distance-label">{obj.position.right.toFixed()}px</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default DistanceLines;
|
|
|
@ -20,21 +20,31 @@ const DistanceLines: React.FC<DistanceLinesProps> = ({ obj, activeEdges }) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{activeEdges.vertical === "top" && typeof obj.position.top === "number" && (
|
{activeEdges.vertical === "top" &&
|
||||||
<div
|
typeof obj.position.top === "number" && (
|
||||||
className="distance-line top"
|
<div
|
||||||
style={{
|
className="distance-line top"
|
||||||
top: 0,
|
style={{
|
||||||
left:
|
top: 0,
|
||||||
activeEdges.horizontal === "left"
|
left:
|
||||||
? `${(obj.position.left as number) + 125}px`
|
activeEdges.horizontal === "left"
|
||||||
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
|
? `${(obj.position.left as number) + 125}px`
|
||||||
height: `${obj.position.top}px`,
|
: `calc(100% - ${(obj.position.right as number) + 125}px)`,
|
||||||
}}
|
height: `${obj.position.top}px`,
|
||||||
>
|
}}
|
||||||
<span className="distance-label">{obj.position.top}px</span>
|
>
|
||||||
</div>
|
<span
|
||||||
)}
|
className="distance-label"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translate(-50%,0%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{obj.position.top}px
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{activeEdges.vertical === "bottom" &&
|
{activeEdges.vertical === "bottom" &&
|
||||||
typeof obj.position.bottom === "number" && (
|
typeof obj.position.bottom === "number" && (
|
||||||
|
@ -49,7 +59,16 @@ const DistanceLines: React.FC<DistanceLinesProps> = ({ obj, activeEdges }) => {
|
||||||
height: `${obj.position.bottom}px`,
|
height: `${obj.position.bottom}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="distance-label">{obj.position.bottom}px</span>
|
<span
|
||||||
|
className="distance-label"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "50%",
|
||||||
|
transform: "translate(-50%,0%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{obj.position.bottom}px
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -66,7 +85,16 @@ const DistanceLines: React.FC<DistanceLinesProps> = ({ obj, activeEdges }) => {
|
||||||
width: `${obj.position.left}px`,
|
width: `${obj.position.left}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="distance-label">{obj.position.left}px</span>
|
<span
|
||||||
|
className="distance-label"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: "50%",
|
||||||
|
transform: "translate(0,-50%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{obj.position.left}px
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -83,11 +111,20 @@ const DistanceLines: React.FC<DistanceLinesProps> = ({ obj, activeEdges }) => {
|
||||||
width: `${obj.position.right}px`,
|
width: `${obj.position.right}px`,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className="distance-label">{obj.position.right}px</span>
|
<span
|
||||||
|
className="distance-label"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: "50%",
|
||||||
|
transform: "translate(0,-50%)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{obj.position.right}px
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default DistanceLines;
|
export default DistanceLines;
|
||||||
|
|
|
@ -386,7 +386,6 @@ const DroppedObjects: React.FC = () => {
|
||||||
<FleetEfficiencyComponent object={obj} />
|
<FleetEfficiencyComponent object={obj} />
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
{renderObjectContent(obj)}
|
|
||||||
<div
|
<div
|
||||||
className="icon kebab"
|
className="icon kebab"
|
||||||
onClick={(event) => handleKebabClick(obj.id, event)}
|
onClick={(event) => handleKebabClick(obj.id, event)}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Canvas } from "@react-three/fiber";
|
import { Canvas } from "@react-three/fiber";
|
||||||
import { Environment, KeyboardControls } from "@react-three/drei";
|
import { Environment, KeyboardControls, Stars } from "@react-three/drei";
|
||||||
|
|
||||||
import World from "./world/world";
|
import World from "./world/world";
|
||||||
import Controls from "./controls/controls";
|
import Controls from "./controls/controls";
|
||||||
|
@ -50,7 +50,7 @@ export default function Scene() {
|
||||||
<ZoneCentreTarget />
|
<ZoneCentreTarget />
|
||||||
<Simulation />
|
<Simulation />
|
||||||
<PostProcessing />
|
<PostProcessing />
|
||||||
{savedTheme !== "dark" && <Sun />}
|
{savedTheme !== "dark" ? <Sun /> : <></>}
|
||||||
<Shadows />
|
<Shadows />
|
||||||
<CamModelsGroup />
|
<CamModelsGroup />
|
||||||
<MqttEvents />
|
<MqttEvents />
|
||||||
|
|
|
@ -0,0 +1,416 @@
|
||||||
|
import React, { useRef, useState, useEffect, useMemo } from "react";
|
||||||
|
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||||
|
import { GLTFLoader } from "three-stdlib";
|
||||||
|
import { useLoader, useFrame } from "@react-three/fiber";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { GLTF } from "three-stdlib";
|
||||||
|
import boxGltb from "../../../assets/gltf-glb/crate_box.glb";
|
||||||
|
|
||||||
|
interface PointAction {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
type: "Inherit" | "Spawn" | "Despawn" | "Delay" | "Swap";
|
||||||
|
material: string;
|
||||||
|
delay: string | number;
|
||||||
|
spawnInterval: string | number;
|
||||||
|
isUsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessPoint {
|
||||||
|
uuid: string;
|
||||||
|
position: number[];
|
||||||
|
rotation: number[];
|
||||||
|
actions: PointAction[];
|
||||||
|
connections: {
|
||||||
|
source: { pathUUID: string; pointUUID: string };
|
||||||
|
targets: { pathUUID: string; pointUUID: string }[];
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessPath {
|
||||||
|
modeluuid: string;
|
||||||
|
modelName: string;
|
||||||
|
points: ProcessPoint[];
|
||||||
|
pathPosition: number[];
|
||||||
|
pathRotation: number[];
|
||||||
|
speed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessData {
|
||||||
|
id: string;
|
||||||
|
paths: ProcessPath[];
|
||||||
|
animationPath: { x: number; y: number; z: number }[];
|
||||||
|
pointActions: PointAction[][];
|
||||||
|
speed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnimationState {
|
||||||
|
currentIndex: number;
|
||||||
|
progress: number;
|
||||||
|
isAnimating: boolean;
|
||||||
|
speed: number;
|
||||||
|
isDelaying: boolean;
|
||||||
|
delayStartTime: number;
|
||||||
|
currentDelayDuration: number;
|
||||||
|
delayComplete: boolean;
|
||||||
|
currentPathIndex: number;
|
||||||
|
spawnPoints: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
position: THREE.Vector3;
|
||||||
|
interval: number;
|
||||||
|
lastSpawnTime: number;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAX_SPAWNED_OBJECTS = 20;
|
||||||
|
|
||||||
|
const ProcessAnimator: React.FC<{ processes: ProcessData[] }> = ({
|
||||||
|
processes,
|
||||||
|
}) => {
|
||||||
|
console.log("processes: ", processes);
|
||||||
|
const gltf = useLoader(GLTFLoader, boxGltb) as GLTF;
|
||||||
|
const { isPlaying, setIsPlaying } = usePlayButtonStore();
|
||||||
|
|
||||||
|
const groupRef = useRef<THREE.Group>(null);
|
||||||
|
const meshRef = useRef<THREE.Mesh>(null);
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const spawnedObjectsRef = useRef<THREE.Object3D[]>([]);
|
||||||
|
|
||||||
|
const materials = useMemo(
|
||||||
|
() => ({
|
||||||
|
Wood: new THREE.MeshStandardMaterial({ color: 0x8b4513 }),
|
||||||
|
Box: new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xcccccc,
|
||||||
|
metalness: 0.8,
|
||||||
|
roughness: 0.2,
|
||||||
|
}),
|
||||||
|
Crate: new THREE.MeshStandardMaterial({
|
||||||
|
color: 0x00aaff,
|
||||||
|
metalness: 0.1,
|
||||||
|
roughness: 0.5,
|
||||||
|
}),
|
||||||
|
Default: new THREE.MeshStandardMaterial({ color: 0x00ff00 }),
|
||||||
|
}),
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const [currentMaterial, setCurrentMaterial] = useState<THREE.Material>(
|
||||||
|
materials.Default
|
||||||
|
);
|
||||||
|
|
||||||
|
const { animationPath, currentProcess } = useMemo(() => {
|
||||||
|
const defaultProcess = {
|
||||||
|
animationPath: [],
|
||||||
|
pointActions: [],
|
||||||
|
speed: 1,
|
||||||
|
paths: [],
|
||||||
|
};
|
||||||
|
const cp = processes?.[0] || defaultProcess;
|
||||||
|
return {
|
||||||
|
animationPath:
|
||||||
|
cp.animationPath?.map((p) => new THREE.Vector3(p.x, p.y, p.z)) || [],
|
||||||
|
currentProcess: cp,
|
||||||
|
};
|
||||||
|
}, [processes]);
|
||||||
|
|
||||||
|
const animationStateRef = useRef<AnimationState>({
|
||||||
|
currentIndex: 0,
|
||||||
|
progress: 0,
|
||||||
|
isAnimating: false,
|
||||||
|
speed: currentProcess.speed,
|
||||||
|
isDelaying: false,
|
||||||
|
delayStartTime: 0,
|
||||||
|
currentDelayDuration: 0,
|
||||||
|
delayComplete: false,
|
||||||
|
currentPathIndex: 0,
|
||||||
|
spawnPoints: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPointDataForAnimationIndex = (index: number) => {
|
||||||
|
if (!processes[0]?.paths) return null;
|
||||||
|
|
||||||
|
if (index < 3) {
|
||||||
|
return processes[0].paths[0]?.points[index];
|
||||||
|
} else {
|
||||||
|
const path2Index = index - 3;
|
||||||
|
return processes[0].paths[1]?.points[path2Index];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPlaying) {
|
||||||
|
setVisible(true);
|
||||||
|
animationStateRef.current = {
|
||||||
|
currentIndex: 0,
|
||||||
|
progress: 0,
|
||||||
|
isAnimating: true,
|
||||||
|
speed: currentProcess.speed,
|
||||||
|
isDelaying: false,
|
||||||
|
delayStartTime: 0,
|
||||||
|
currentDelayDuration: 0,
|
||||||
|
delayComplete: false,
|
||||||
|
currentPathIndex: 0,
|
||||||
|
spawnPoints: {},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Clear spawned objects
|
||||||
|
if (groupRef.current) {
|
||||||
|
spawnedObjectsRef.current.forEach((obj) => {
|
||||||
|
if (groupRef.current?.children.includes(obj)) {
|
||||||
|
groupRef.current.remove(obj);
|
||||||
|
}
|
||||||
|
if (obj instanceof THREE.Mesh) {
|
||||||
|
obj.material.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spawnedObjectsRef.current = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
|
||||||
|
if (currentRef && animationPath.length > 0) {
|
||||||
|
currentRef.position.copy(animationPath[0]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
animationStateRef.current.isAnimating = false;
|
||||||
|
}
|
||||||
|
}, [isPlaying, currentProcess, animationPath]);
|
||||||
|
|
||||||
|
const handleMaterialSwap = (materialType: string) => {
|
||||||
|
const newMaterial =
|
||||||
|
materials[materialType as keyof typeof materials] || materials.Default;
|
||||||
|
setCurrentMaterial(newMaterial);
|
||||||
|
|
||||||
|
spawnedObjectsRef.current.forEach((obj) => {
|
||||||
|
if (obj instanceof THREE.Mesh) {
|
||||||
|
obj.material = newMaterial.clone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const hasNonInheritActions = (actions: PointAction[] = []) => {
|
||||||
|
return actions.some((action) => action.isUsed && action.type !== "Inherit");
|
||||||
|
};
|
||||||
|
|
||||||
|
const handlePointActions = (
|
||||||
|
actions: PointAction[] = [],
|
||||||
|
currentTime: number,
|
||||||
|
currentPosition: THREE.Vector3
|
||||||
|
) => {
|
||||||
|
let shouldStopAnimation = false;
|
||||||
|
|
||||||
|
actions.forEach((action) => {
|
||||||
|
if (!action.isUsed) return;
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
case "Delay":
|
||||||
|
if (
|
||||||
|
!animationStateRef.current.isDelaying &&
|
||||||
|
!animationStateRef.current.delayComplete
|
||||||
|
) {
|
||||||
|
const delayDuration =
|
||||||
|
typeof action.delay === "number"
|
||||||
|
? action.delay
|
||||||
|
: parseFloat(action.delay as string) || 0;
|
||||||
|
|
||||||
|
if (delayDuration > 0) {
|
||||||
|
animationStateRef.current.isDelaying = true;
|
||||||
|
animationStateRef.current.delayStartTime = currentTime;
|
||||||
|
animationStateRef.current.currentDelayDuration = delayDuration;
|
||||||
|
shouldStopAnimation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Despawn":
|
||||||
|
setVisible(false);
|
||||||
|
setIsPlaying(false);
|
||||||
|
animationStateRef.current.isAnimating = false;
|
||||||
|
shouldStopAnimation = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Spawn":
|
||||||
|
const spawnInterval =
|
||||||
|
typeof action.spawnInterval === "number"
|
||||||
|
? action.spawnInterval
|
||||||
|
: parseFloat(action.spawnInterval as string) || 1;
|
||||||
|
|
||||||
|
const positionKey = currentPosition.toArray().join(",");
|
||||||
|
|
||||||
|
animationStateRef.current.spawnPoints[positionKey] = {
|
||||||
|
position: currentPosition.clone(),
|
||||||
|
interval: spawnInterval,
|
||||||
|
lastSpawnTime: currentTime - spawnInterval, // Force immediate spawn
|
||||||
|
};
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Swap":
|
||||||
|
if (action.material) {
|
||||||
|
handleMaterialSwap(action.material);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "Inherit":
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return shouldStopAnimation;
|
||||||
|
};
|
||||||
|
|
||||||
|
useFrame((state, delta) => {
|
||||||
|
const currentRef = gltf?.scene ? groupRef.current : meshRef.current;
|
||||||
|
if (
|
||||||
|
!currentRef ||
|
||||||
|
!animationStateRef.current.isAnimating ||
|
||||||
|
animationPath.length < 2
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTime = state.clock.getElapsedTime();
|
||||||
|
const path = animationPath;
|
||||||
|
const stateRef = animationStateRef.current;
|
||||||
|
|
||||||
|
if (stateRef.currentIndex === 3 && stateRef.currentPathIndex === 0) {
|
||||||
|
stateRef.currentPathIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPointData = getPointDataForAnimationIndex(
|
||||||
|
stateRef.currentIndex
|
||||||
|
);
|
||||||
|
|
||||||
|
if (stateRef.progress === 0 && currentPointData?.actions) {
|
||||||
|
const shouldStop = handlePointActions(
|
||||||
|
currentPointData.actions,
|
||||||
|
currentTime,
|
||||||
|
currentRef.position
|
||||||
|
);
|
||||||
|
if (shouldStop) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stateRef.isDelaying) {
|
||||||
|
if (
|
||||||
|
currentTime - stateRef.delayStartTime >=
|
||||||
|
stateRef.currentDelayDuration
|
||||||
|
) {
|
||||||
|
stateRef.isDelaying = false;
|
||||||
|
stateRef.delayComplete = true;
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle spawning - this is the key updated part
|
||||||
|
Object.entries(stateRef.spawnPoints).forEach(([key, spawnPoint]) => {
|
||||||
|
if (currentTime - spawnPoint.lastSpawnTime >= spawnPoint.interval) {
|
||||||
|
spawnPoint.lastSpawnTime = currentTime;
|
||||||
|
|
||||||
|
if (gltf?.scene && groupRef?.current) {
|
||||||
|
const newObject = gltf.scene.clone();
|
||||||
|
newObject.position.copy(spawnPoint.position);
|
||||||
|
|
||||||
|
newObject.traverse((child) => {
|
||||||
|
if (child instanceof THREE.Mesh) {
|
||||||
|
child.material = currentMaterial.clone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
groupRef.current.add(newObject);
|
||||||
|
spawnedObjectsRef.current.push(newObject);
|
||||||
|
|
||||||
|
// Clean up old objects if needed
|
||||||
|
console.log(
|
||||||
|
"spawnedObjectsRef.current.length: ",
|
||||||
|
spawnedObjectsRef.current.length
|
||||||
|
);
|
||||||
|
if (spawnedObjectsRef.current.length > MAX_SPAWNED_OBJECTS) {
|
||||||
|
const oldest = spawnedObjectsRef.current.shift();
|
||||||
|
if (oldest && groupRef.current.children.includes(oldest)) {
|
||||||
|
groupRef.current.remove(oldest);
|
||||||
|
if (oldest instanceof THREE.Mesh) {
|
||||||
|
oldest.material.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const nextPointIdx = stateRef.currentIndex + 1;
|
||||||
|
const isLastPoint = nextPointIdx >= path.length;
|
||||||
|
|
||||||
|
if (isLastPoint) {
|
||||||
|
if (currentPointData?.actions) {
|
||||||
|
const shouldStop = !hasNonInheritActions(currentPointData.actions);
|
||||||
|
if (shouldStop) {
|
||||||
|
currentRef.position.copy(path[stateRef.currentIndex]);
|
||||||
|
setIsPlaying(false);
|
||||||
|
stateRef.isAnimating = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isLastPoint) {
|
||||||
|
const nextPoint = path[nextPointIdx];
|
||||||
|
const distance = path[stateRef.currentIndex].distanceTo(nextPoint);
|
||||||
|
const movement = stateRef.speed * delta;
|
||||||
|
stateRef.progress += movement / distance;
|
||||||
|
|
||||||
|
if (stateRef.progress >= 1) {
|
||||||
|
stateRef.currentIndex = nextPointIdx;
|
||||||
|
stateRef.progress = 0;
|
||||||
|
stateRef.delayComplete = false;
|
||||||
|
currentRef.position.copy(nextPoint);
|
||||||
|
} else {
|
||||||
|
currentRef.position.lerpVectors(
|
||||||
|
path[stateRef.currentIndex],
|
||||||
|
nextPoint,
|
||||||
|
stateRef.progress
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (groupRef.current) {
|
||||||
|
spawnedObjectsRef.current.forEach((obj) => {
|
||||||
|
if (groupRef.current?.children.includes(obj)) {
|
||||||
|
groupRef.current.remove(obj);
|
||||||
|
}
|
||||||
|
if (obj instanceof THREE.Mesh) {
|
||||||
|
obj.material.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
spawnedObjectsRef.current = [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (!processes || processes.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gltf?.scene) {
|
||||||
|
return visible ? (
|
||||||
|
<mesh ref={meshRef} material={currentMaterial}>
|
||||||
|
<boxGeometry args={[1, 1, 1]} />
|
||||||
|
</mesh>
|
||||||
|
) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return visible ? (
|
||||||
|
<group ref={groupRef}>
|
||||||
|
<primitive
|
||||||
|
object={gltf.scene.clone()}
|
||||||
|
scale={[1, 1, 1]}
|
||||||
|
material={currentMaterial}
|
||||||
|
/>
|
||||||
|
</group>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProcessAnimator;
|
|
@ -0,0 +1,17 @@
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import ProcessCreator from "./processCreator";
|
||||||
|
import ProcessAnimator from "./processAnimator";
|
||||||
|
|
||||||
|
const ProcessContainer: React.FC = () => {
|
||||||
|
const [processes, setProcesses] = useState<any[]>([]);
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ProcessCreator onProcessesCreated={setProcesses} />
|
||||||
|
{processes.length > 0 && <ProcessAnimator processes={processes} />}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ProcessContainer;
|
|
@ -0,0 +1,398 @@
|
||||||
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useCallback,
|
||||||
|
useRef,
|
||||||
|
} from "react";
|
||||||
|
import { useSimulationPaths } from "../../../store/store";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { useThree } from "@react-three/fiber";
|
||||||
|
import {
|
||||||
|
ConveyorEventsSchema,
|
||||||
|
VehicleEventsSchema,
|
||||||
|
} from "../../../types/world/worldTypes";
|
||||||
|
|
||||||
|
// Type definitions
|
||||||
|
export interface PointAction {
|
||||||
|
uuid: string;
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
material: string;
|
||||||
|
delay: number | string;
|
||||||
|
spawnInterval: string | number;
|
||||||
|
isUsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PathPoint {
|
||||||
|
uuid: string;
|
||||||
|
position: [number, number, number];
|
||||||
|
actions: PointAction[];
|
||||||
|
connections: {
|
||||||
|
targets: Array<{ pathUUID: string }>;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SimulationPath {
|
||||||
|
modeluuid: string;
|
||||||
|
points: PathPoint[];
|
||||||
|
pathPosition: [number, number, number];
|
||||||
|
speed?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Process {
|
||||||
|
id: string;
|
||||||
|
paths: SimulationPath[];
|
||||||
|
animationPath: THREE.Vector3[];
|
||||||
|
pointActions: PointAction[][];
|
||||||
|
speed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProcessCreatorProps {
|
||||||
|
onProcessesCreated: (processes: Process[]) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert event schemas to SimulationPath
|
||||||
|
function convertToSimulationPath(
|
||||||
|
path: ConveyorEventsSchema | VehicleEventsSchema
|
||||||
|
): SimulationPath {
|
||||||
|
const { modeluuid } = path;
|
||||||
|
|
||||||
|
// Simplified normalizeAction function that preserves exact original properties
|
||||||
|
const normalizeAction = (action: any): PointAction => {
|
||||||
|
return { ...action }; // Return exact copy with no modifications
|
||||||
|
};
|
||||||
|
|
||||||
|
if (path.type === "Conveyor") {
|
||||||
|
return {
|
||||||
|
modeluuid,
|
||||||
|
points: path.points.map((point) => ({
|
||||||
|
uuid: point.uuid,
|
||||||
|
position: point.position,
|
||||||
|
actions: point.actions.map(normalizeAction), // Preserve exact actions
|
||||||
|
connections: {
|
||||||
|
targets: point.connections.targets.map((target) => ({
|
||||||
|
pathUUID: target.pathUUID,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
pathPosition: path.position,
|
||||||
|
speed:
|
||||||
|
typeof path.speed === "string"
|
||||||
|
? parseFloat(path.speed) || 1
|
||||||
|
: path.speed || 1,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
modeluuid,
|
||||||
|
points: [
|
||||||
|
{
|
||||||
|
uuid: path.point.uuid,
|
||||||
|
position: path.point.position,
|
||||||
|
actions: Array.isArray(path.point.actions)
|
||||||
|
? path.point.actions.map(normalizeAction)
|
||||||
|
: [normalizeAction(path.point.actions)],
|
||||||
|
connections: {
|
||||||
|
targets: path.point.connections.targets.map((target) => ({
|
||||||
|
pathUUID: target.pathUUID,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
pathPosition: path.position,
|
||||||
|
speed: path.point.speed || 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom shallow comparison for arrays
|
||||||
|
const areArraysEqual = (a: any[], b: any[]) => {
|
||||||
|
if (a.length !== b.length) return false;
|
||||||
|
for (let i = 0; i < a.length; i++) {
|
||||||
|
if (a[i] !== b[i]) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Helper function to create an empty process
|
||||||
|
const createEmptyProcess = (): Process => ({
|
||||||
|
id: `process-${Math.random().toString(36).substring(2, 11)}`,
|
||||||
|
paths: [],
|
||||||
|
animationPath: [],
|
||||||
|
pointActions: [],
|
||||||
|
speed: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Enhanced connection checking function
|
||||||
|
function shouldReverseNextPath(
|
||||||
|
currentPath: SimulationPath,
|
||||||
|
nextPath: SimulationPath
|
||||||
|
): boolean {
|
||||||
|
if (nextPath.points.length !== 3) return false;
|
||||||
|
|
||||||
|
const currentLastPoint = currentPath.points[currentPath.points.length - 1];
|
||||||
|
const nextFirstPoint = nextPath.points[0];
|
||||||
|
const nextLastPoint = nextPath.points[nextPath.points.length - 1];
|
||||||
|
|
||||||
|
// Check if current last connects to next last (requires reversal)
|
||||||
|
const connectsToLast = currentLastPoint.connections.targets.some(
|
||||||
|
(target) =>
|
||||||
|
target.pathUUID === nextPath.modeluuid &&
|
||||||
|
nextLastPoint.connections.targets.some(
|
||||||
|
(t) => t.pathUUID === currentPath.modeluuid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if current last connects to next first (no reversal needed)
|
||||||
|
const connectsToFirst = currentLastPoint.connections.targets.some(
|
||||||
|
(target) =>
|
||||||
|
target.pathUUID === nextPath.modeluuid &&
|
||||||
|
nextFirstPoint.connections.targets.some(
|
||||||
|
(t) => t.pathUUID === currentPath.modeluuid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only reverse if connected to last point and not to first point
|
||||||
|
return connectsToLast && !connectsToFirst;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Updated path adjustment function
|
||||||
|
function adjustPathPointsOrder(paths: SimulationPath[]): SimulationPath[] {
|
||||||
|
if (paths.length < 2) return paths;
|
||||||
|
|
||||||
|
const adjustedPaths = [...paths];
|
||||||
|
|
||||||
|
for (let i = 0; i < adjustedPaths.length - 1; i++) {
|
||||||
|
const currentPath = adjustedPaths[i];
|
||||||
|
const nextPath = adjustedPaths[i + 1];
|
||||||
|
|
||||||
|
if (shouldReverseNextPath(currentPath, nextPath)) {
|
||||||
|
const reversedPoints = [
|
||||||
|
nextPath.points[2],
|
||||||
|
nextPath.points[1],
|
||||||
|
nextPath.points[0],
|
||||||
|
];
|
||||||
|
|
||||||
|
adjustedPaths[i + 1] = {
|
||||||
|
...nextPath,
|
||||||
|
points: reversedPoints,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return adjustedPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main hook for process creation
|
||||||
|
export function useProcessCreation() {
|
||||||
|
const { scene } = useThree();
|
||||||
|
const [processes, setProcesses] = useState<Process[]>([]);
|
||||||
|
|
||||||
|
const hasSpawnAction = useCallback((path: SimulationPath): boolean => {
|
||||||
|
return path.points.some((point) =>
|
||||||
|
point.actions.some((action) => action.type.toLowerCase() === "spawn")
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const createProcess = useCallback(
|
||||||
|
(paths: SimulationPath[]): Process => {
|
||||||
|
if (!paths || paths.length === 0) {
|
||||||
|
return createEmptyProcess();
|
||||||
|
}
|
||||||
|
|
||||||
|
const animationPath: THREE.Vector3[] = [];
|
||||||
|
const pointActions: PointAction[][] = [];
|
||||||
|
const processSpeed = paths[0]?.speed || 1;
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
for (const point of path.points) {
|
||||||
|
const obj = scene.getObjectByProperty("uuid", point.uuid);
|
||||||
|
if (!obj) {
|
||||||
|
console.warn(`Object with UUID ${point.uuid} not found in scene`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const position = obj.getWorldPosition(new THREE.Vector3());
|
||||||
|
animationPath.push(position.clone());
|
||||||
|
pointActions.push(point.actions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `process-${Math.random().toString(36).substring(2, 11)}`,
|
||||||
|
paths,
|
||||||
|
animationPath,
|
||||||
|
pointActions,
|
||||||
|
speed: processSpeed,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[scene]
|
||||||
|
);
|
||||||
|
|
||||||
|
const getAllConnectedPaths = useCallback(
|
||||||
|
(
|
||||||
|
initialPath: SimulationPath,
|
||||||
|
allPaths: SimulationPath[],
|
||||||
|
visited: Set<string> = new Set()
|
||||||
|
): SimulationPath[] => {
|
||||||
|
const connectedPaths: SimulationPath[] = [];
|
||||||
|
const queue: SimulationPath[] = [initialPath];
|
||||||
|
visited.add(initialPath.modeluuid);
|
||||||
|
|
||||||
|
const pathMap = new Map<string, SimulationPath>();
|
||||||
|
allPaths.forEach((path) => pathMap.set(path.modeluuid, path));
|
||||||
|
|
||||||
|
while (queue.length > 0) {
|
||||||
|
const currentPath = queue.shift()!;
|
||||||
|
connectedPaths.push(currentPath);
|
||||||
|
|
||||||
|
// Process outgoing connections
|
||||||
|
for (const point of currentPath.points) {
|
||||||
|
for (const target of point.connections.targets) {
|
||||||
|
if (!visited.has(target.pathUUID)) {
|
||||||
|
const targetPath = pathMap.get(target.pathUUID);
|
||||||
|
if (targetPath) {
|
||||||
|
visited.add(target.pathUUID);
|
||||||
|
queue.push(targetPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process incoming connections
|
||||||
|
for (const [uuid, path] of pathMap) {
|
||||||
|
if (!visited.has(uuid)) {
|
||||||
|
const hasConnectionToCurrent = path.points.some((point) =>
|
||||||
|
point.connections.targets.some(
|
||||||
|
(t) => t.pathUUID === currentPath.modeluuid
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (hasConnectionToCurrent) {
|
||||||
|
visited.add(uuid);
|
||||||
|
queue.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return connectedPaths;
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const createProcessesFromPaths = useCallback(
|
||||||
|
(paths: SimulationPath[]): Process[] => {
|
||||||
|
if (!paths || paths.length === 0) return [];
|
||||||
|
|
||||||
|
const visited = new Set<string>();
|
||||||
|
const processes: Process[] = [];
|
||||||
|
const pathMap = new Map<string, SimulationPath>();
|
||||||
|
paths.forEach((path) => pathMap.set(path.modeluuid, path));
|
||||||
|
|
||||||
|
for (const path of paths) {
|
||||||
|
if (!visited.has(path.modeluuid) && hasSpawnAction(path)) {
|
||||||
|
const connectedPaths = getAllConnectedPaths(path, paths, visited);
|
||||||
|
const adjustedPaths = adjustPathPointsOrder(connectedPaths);
|
||||||
|
const process = createProcess(adjustedPaths);
|
||||||
|
processes.push(process);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return processes;
|
||||||
|
},
|
||||||
|
[createProcess, getAllConnectedPaths, hasSpawnAction]
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
processes,
|
||||||
|
createProcessesFromPaths,
|
||||||
|
setProcesses,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ProcessCreator: React.FC<ProcessCreatorProps> = React.memo(
|
||||||
|
({ onProcessesCreated }) => {
|
||||||
|
const { simulationPaths } = useSimulationPaths();
|
||||||
|
const { createProcessesFromPaths } = useProcessCreation();
|
||||||
|
const prevPathsRef = useRef<SimulationPath[]>([]);
|
||||||
|
const prevProcessesRef = useRef<Process[]>([]);
|
||||||
|
|
||||||
|
const convertedPaths = useMemo((): SimulationPath[] => {
|
||||||
|
if (!simulationPaths) return [];
|
||||||
|
return simulationPaths.map((path) =>
|
||||||
|
convertToSimulationPath(
|
||||||
|
path as ConveyorEventsSchema | VehicleEventsSchema
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [simulationPaths]);
|
||||||
|
|
||||||
|
const pathsDependency = useMemo(() => {
|
||||||
|
if (!convertedPaths) return null;
|
||||||
|
return convertedPaths.map((path) => ({
|
||||||
|
id: path.modeluuid,
|
||||||
|
hasSpawn: path.points.some((p: PathPoint) =>
|
||||||
|
p.actions.some((a: PointAction) => a.type.toLowerCase() === "spawn")
|
||||||
|
),
|
||||||
|
connections: path.points
|
||||||
|
.flatMap((p: PathPoint) =>
|
||||||
|
p.connections.targets.map((t: { pathUUID: string }) => t.pathUUID)
|
||||||
|
)
|
||||||
|
.join(","),
|
||||||
|
}));
|
||||||
|
}, [convertedPaths]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!convertedPaths || convertedPaths.length === 0) {
|
||||||
|
if (prevProcessesRef.current.length > 0) {
|
||||||
|
onProcessesCreated([]);
|
||||||
|
prevProcessesRef.current = [];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (areArraysEqual(prevPathsRef.current, convertedPaths)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevPathsRef.current = convertedPaths;
|
||||||
|
const newProcesses = createProcessesFromPaths(convertedPaths);
|
||||||
|
|
||||||
|
// console.log("--- Action Types in Paths ---");
|
||||||
|
// convertedPaths.forEach((path) => {
|
||||||
|
// path.points.forEach((point) => {
|
||||||
|
// point.actions.forEach((action) => {
|
||||||
|
// console.log(
|
||||||
|
// `Path ${path.modeluuid}, Point ${point.uuid}: ${action.type}`
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// console.log("New processes:", newProcesses);
|
||||||
|
|
||||||
|
if (
|
||||||
|
newProcesses.length !== prevProcessesRef.current.length ||
|
||||||
|
!newProcesses.every(
|
||||||
|
(proc, i) =>
|
||||||
|
proc.paths.length === prevProcessesRef.current[i]?.paths.length &&
|
||||||
|
proc.paths.every(
|
||||||
|
(path, j) =>
|
||||||
|
path.modeluuid ===
|
||||||
|
prevProcessesRef.current[i]?.paths[j]?.modeluuid
|
||||||
|
)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
onProcessesCreated(newProcesses);
|
||||||
|
// prevProcessesRef.current = newProcesses;
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
pathsDependency,
|
||||||
|
onProcessesCreated,
|
||||||
|
convertedPaths,
|
||||||
|
createProcessesFromPaths,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default ProcessCreator;
|
|
@ -5,6 +5,7 @@ import Behaviour from './behaviour/behaviour';
|
||||||
import PathCreation from './path/pathCreation';
|
import PathCreation from './path/pathCreation';
|
||||||
import PathConnector from './path/pathConnector';
|
import PathConnector from './path/pathConnector';
|
||||||
import useModuleStore from '../../store/useModuleStore';
|
import useModuleStore from '../../store/useModuleStore';
|
||||||
|
import ProcessContainer from './process/processContainer';
|
||||||
|
|
||||||
function Simulation() {
|
function Simulation() {
|
||||||
const { activeModule } = useModuleStore();
|
const { activeModule } = useModuleStore();
|
||||||
|
@ -36,6 +37,7 @@ function Simulation() {
|
||||||
<>
|
<>
|
||||||
<PathCreation pathsGroupRef={pathsGroupRef} />
|
<PathCreation pathsGroupRef={pathsGroupRef} />
|
||||||
<PathConnector pathsGroupRef={pathsGroupRef} />
|
<PathConnector pathsGroupRef={pathsGroupRef} />
|
||||||
|
<ProcessContainer />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
--border-color: #{$border-color-dark}; // Border color for dark theme
|
--border-color: #{$border-color-dark}; // Border color for dark theme
|
||||||
|
|
||||||
// Shadow variables
|
// Shadow variables
|
||||||
--shadow-main-dark: #{$shadow-color-dark}; // Main shadow color
|
--shadow-main-dark: #{$shadow-color}; // Main shadow color
|
||||||
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark); // Light shadow
|
--box-shadow-light: 0px 2px 4px var(--shadow-main-dark); // Light shadow
|
||||||
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark); // Medium shadow
|
--box-shadow-medium: 0px 4px 8px var(--shadow-main-dark); // Medium shadow
|
||||||
--box-shadow-heavy: 0px 8px 16px var(--shadow-main-dark); // Heavy shadow
|
--box-shadow-heavy: 0px 8px 16px var(--shadow-main-dark); // Heavy shadow
|
||||||
|
@ -75,6 +75,7 @@
|
||||||
height: 100vh; // Full viewport height
|
height: 100vh; // Full viewport height
|
||||||
width: 100vw; // Full viewport width
|
width: 100vw; // Full viewport width
|
||||||
overflow: hidden; // Prevent scrollbars
|
overflow: hidden; // Prevent scrollbars
|
||||||
|
background-color: #232323;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Root overlay styles
|
// Root overlay styles
|
||||||
|
@ -123,7 +124,7 @@ body {
|
||||||
/* Scrollbar handle color */
|
/* Scrollbar handle color */
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
/* Rounded corners */
|
/* Rounded corners */
|
||||||
border: 2px solid #f4f4f4;
|
border: 2px solid var(--primary-color);
|
||||||
/* Padding around the scrollbar handle */
|
/* Padding around the scrollbar handle */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ input {
|
||||||
border: 1px solid var(--border-color);
|
border: 1px solid var(--border-color);
|
||||||
outline: none;
|
outline: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
color: var(--input-text-color);
|
||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:active {
|
&:active {
|
||||||
|
@ -552,6 +553,11 @@ input {
|
||||||
.input-value {
|
.input-value {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
&::-webkit-inner-spin-button,
|
||||||
|
&::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,6 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
left: calc(120px / 2);
|
|
||||||
top: 100px;
|
|
||||||
padding: 14px;
|
|
||||||
padding-bottom: 60px;
|
padding-bottom: 60px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -39,6 +36,8 @@
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
margin-top: 2px;
|
||||||
|
padding: 0 24px;
|
||||||
|
|
||||||
.search-wrapper {
|
.search-wrapper {
|
||||||
min-width: 60%;
|
min-width: 60%;
|
||||||
|
@ -143,6 +142,7 @@
|
||||||
.assets-container {
|
.assets-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
.name-container {
|
.name-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -177,6 +177,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.vendor-icon {
|
.vendor-icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
font-weight: #{$bold-weight};
|
font-weight: #{$bold-weight};
|
||||||
font-size: $regular;
|
font-size: $regular;
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
// Main Container
|
// Main Container
|
||||||
.realTime-viz {
|
.realTime-viz {
|
||||||
background-color: var(--background-color);
|
background-color: #131313;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
box-shadow: $box-shadow-medium;
|
box-shadow: $box-shadow-medium;
|
||||||
width: calc(100% - (320px + 270px + 90px));
|
width: calc(100% - (320px + 270px + 90px));
|
||||||
|
@ -22,7 +22,7 @@
|
||||||
min-height: 83px;
|
min-height: 83px;
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
border: 1.23px solid var(--border-color);
|
border: 1.23px solid var(--border-color);
|
||||||
box-shadow: var(--box-shadow-heavy);
|
box-shadow: 0px 4.91px 4.91px 0px #0000001c;
|
||||||
border-radius: $border-radius-medium;
|
border-radius: $border-radius-medium;
|
||||||
padding: 18px;
|
padding: 18px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
@ -31,7 +31,6 @@
|
||||||
|
|
||||||
.scene-container {
|
.scene-container {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: #232323;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
|
@ -192,11 +191,11 @@
|
||||||
height: 25% !important;
|
height: 25% !important;
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
max-height: 100%;
|
max-height: 100%;
|
||||||
border: 1px dashed #a9a9a9;
|
// border: 1px dashed var(--background-color-gray);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0px 2px 6px 0px rgba(60, 60, 67, 0.1);
|
box-shadow: var(--box-shadow-medium);
|
||||||
padding: 6px 0;
|
padding: 6px 0;
|
||||||
background-color: white;
|
background-color: var(--background-color);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.kebab {
|
.kebab {
|
||||||
|
@ -534,7 +533,7 @@
|
||||||
|
|
||||||
.zone {
|
.zone {
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid var(--highlight-accent-color);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
@ -614,10 +613,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.distance-line {
|
.distance-line {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
border-style: dashed;
|
border-style: dashed;
|
||||||
|
|
|
@ -69,7 +69,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
color: #ff4d4f;
|
color: #f3453f;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
@ -89,7 +89,7 @@
|
||||||
border-radius: #{$border-radius-extra-large};
|
border-radius: #{$border-radius-extra-large};
|
||||||
background: var(--background-color);
|
background: var(--background-color);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #333;
|
color: var(--input-text-color);
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
border-color: var(--accent-color);
|
border-color: var(--accent-color);
|
||||||
|
|
|
@ -202,7 +202,7 @@ export const thirdPersonControls: ThirdPersonControls = {
|
||||||
maxDistance: 100, // Maximum distance from the target
|
maxDistance: 100, // Maximum distance from the target
|
||||||
maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle
|
maxPolarAngle: Math.PI / 2 - 0.05, // Maximum polar angle
|
||||||
minZoom: 6, // Minimum zoom level
|
minZoom: 6, // Minimum zoom level
|
||||||
maxZoom: 21, // Maximum zoom level
|
maxZoom: 100, // Maximum zoom level
|
||||||
targetOffset: 20, // Offset of the target from the camera
|
targetOffset: 20, // Offset of the target from the camera
|
||||||
cameraHeight: 30, // Height of the camera
|
cameraHeight: 30, // Height of the camera
|
||||||
leftMouse: 2, // Mouse button for panning
|
leftMouse: 2, // Mouse button for panning
|
||||||
|
|
Loading…
Reference in New Issue