created new window

This commit is contained in:
2025-10-13 10:44:17 +05:30
parent 5fb88849aa
commit e00bccb357
14 changed files with 944 additions and 426 deletions

48
app/package-lock.json generated
View File

@@ -2029,7 +2029,7 @@
"version": "0.8.1", "version": "0.8.1",
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"@jridgewell/trace-mapping": "0.3.9" "@jridgewell/trace-mapping": "0.3.9"
}, },
@@ -2041,7 +2041,7 @@
"version": "0.3.9", "version": "0.3.9",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10" "@jridgewell/sourcemap-codec": "^1.4.10"
@@ -4192,6 +4192,26 @@
"url": "https://github.com/sponsors/gregberge" "url": "https://github.com/sponsors/gregberge"
} }
}, },
"node_modules/@testing-library/dom": {
"version": "10.4.1",
"resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
"integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
"license": "MIT",
"peer": true,
"dependencies": {
"@babel/code-frame": "^7.10.4",
"@babel/runtime": "^7.12.5",
"@types/aria-query": "^5.0.1",
"aria-query": "5.3.0",
"dom-accessibility-api": "^0.5.9",
"lz-string": "^1.5.0",
"picocolors": "1.1.1",
"pretty-format": "^27.0.2"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@testing-library/jest-dom": { "node_modules/@testing-library/jest-dom": {
"version": "5.17.0", "version": "5.17.0",
"resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz", "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.17.0.tgz",
@@ -4303,25 +4323,25 @@
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
"integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
"dev": true "devOptional": true
}, },
"node_modules/@tsconfig/node12": { "node_modules/@tsconfig/node12": {
"version": "1.0.11", "version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true "devOptional": true
}, },
"node_modules/@tsconfig/node14": { "node_modules/@tsconfig/node14": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true "devOptional": true
}, },
"node_modules/@tsconfig/node16": { "node_modules/@tsconfig/node16": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true "devOptional": true
}, },
"node_modules/@turf/along": { "node_modules/@turf/along": {
"version": "7.2.0", "version": "7.2.0",
@@ -9093,7 +9113,7 @@
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
"dev": true "devOptional": true
}, },
"node_modules/cross-env": { "node_modules/cross-env": {
"version": "7.0.3", "version": "7.0.3",
@@ -9970,7 +9990,7 @@
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=0.3.1" "node": ">=0.3.1"
} }
@@ -15354,7 +15374,7 @@
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
"dev": true "devOptional": true
}, },
"node_modules/makeerror": { "node_modules/makeerror": {
"version": "1.0.12", "version": "1.0.12",
@@ -20908,7 +20928,7 @@
"version": "10.9.2", "version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"@cspotcode/source-map-support": "^0.8.0", "@cspotcode/source-map-support": "^0.8.0",
"@tsconfig/node10": "^1.0.7", "@tsconfig/node10": "^1.0.7",
@@ -20951,7 +20971,7 @@
"version": "8.3.4", "version": "8.3.4",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
"integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
"dev": true, "devOptional": true,
"dependencies": { "dependencies": {
"acorn": "^8.11.0" "acorn": "^8.11.0"
}, },
@@ -20963,7 +20983,7 @@
"version": "4.1.3", "version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true "devOptional": true
}, },
"node_modules/tsconfig-paths": { "node_modules/tsconfig-paths": {
"version": "3.15.0", "version": "3.15.0",
@@ -21459,7 +21479,7 @@
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
"dev": true "devOptional": true
}, },
"node_modules/v8-to-istanbul": { "node_modules/v8-to-istanbul": {
"version": "8.1.1", "version": "8.1.1",
@@ -22518,7 +22538,7 @@
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true, "devOptional": true,
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }

View File

@@ -1,4 +1,9 @@
import { useCompareProductDataStore, useLoadingProgress, useIsComparing } from "../../../store/builder/store"; import {
useCompareProductDataStore,
useLoadingProgress,
useIsComparing,
useCreateNewWindow,
} from "../../../store/builder/store";
import { useSimulationState } from "../../../store/simulation/useSimulationStore"; import { useSimulationState } from "../../../store/simulation/useSimulationStore";
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
@@ -11,7 +16,8 @@ import { useSimulationManager } from "../../../store/rough/useSimulationManagerS
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi"; import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi";
import { calculateSimulationData } from "./functions/calculateSimulationData"; import { calculateSimulationData } from "./functions/calculateSimulationData";
import NewWindowScene from "../../ui/compareVersion/NewWindowScene";
import Button from "../../ui/compareVersion/Button";
type AssetData = { type AssetData = {
activeTime: number; activeTime: number;
idleTime: number; idleTime: number;
@@ -41,7 +47,11 @@ export interface CompareProduct {
//shiftsPerDay: number; //shiftsPerDay: number;
}; };
} }
export const createCompareProduct = (productUuid: string, productName: string, assets: AssetData[]): CompareProduct => ({ export const createCompareProduct = (
productUuid: string,
productName: string,
assets: AssetData[]
): CompareProduct => ({
productUuid, productUuid,
productName, productName,
simulationData: calculateSimulationData(assets), simulationData: calculateSimulationData(assets),
@@ -61,8 +71,10 @@ function ComparisonScene() {
const { setCompareProductsData } = useCompareProductDataStore(); const { setCompareProductsData } = useCompareProductDataStore();
const [shouldShowComparisonResult, setShouldShowComparisonResult] = useState(false); const [shouldShowComparisonResult, setShouldShowComparisonResult] = useState(false);
const { addSimulationRecord } = useSimulationManager(); const { addSimulationRecord } = useSimulationManager();
const { createNewWindow } = useCreateNewWindow();
useEffect(() => {}); useEffect(() => {
console.log("comparisonScene: ", comparisonScene);
}, [comparisonScene]);
const handleSelectVersion = (option: string) => { const handleSelectVersion = (option: string) => {
const version = versionHistory.find((version) => version.versionName === option); const version = versionHistory.find((version) => version.versionName === option);
@@ -77,12 +89,22 @@ function ComparisonScene() {
echo.log(getData.message); echo.log(getData.message);
const getSimulate = getData?.data?.existingSimulatedData; const getSimulate = getData?.data?.existingSimulatedData;
if (!getSimulate) return; if (!getSimulate) return;
if (!selectedVersion?.versionId || !projectId || getSimulate === undefined || !selectedProduct.productUuid) { if (
!selectedVersion?.versionId ||
!projectId ||
getSimulate === undefined ||
!selectedProduct.productUuid
) {
echo.warn("No prebacked Data found"); echo.warn("No prebacked Data found");
alert("Please run the simulation before comparing."); alert("Please run the simulation before comparing.");
return; return;
} }
addSimulationRecord(projectId, selectedVersion?.versionId || "", selectedProduct.productUuid || "", getSimulate.data); addSimulationRecord(
projectId,
selectedVersion?.versionId || "",
selectedProduct.productUuid || "",
getSimulate.data
);
}); });
} }
}; };
@@ -105,7 +127,12 @@ function ComparisonScene() {
echo.warn(getData.message); echo.warn(getData.message);
const getSimulate = getData?.data?.existingSimulatedData; const getSimulate = getData?.data?.existingSimulatedData;
if (!getSimulate) return; if (!getSimulate) return;
addSimulationRecord(projectId, selectedVersion?.versionId || "", product.productUuid || "", getSimulate.data); addSimulationRecord(
projectId,
selectedVersion?.versionId || "",
product.productUuid || "",
getSimulate.data
);
}); });
setComparisonState(data); setComparisonState(data);
} }
@@ -113,12 +140,32 @@ function ComparisonScene() {
useEffect(() => { useEffect(() => {
if (mainScene && comparisonScene && selectedVersion) { if (mainScene && comparisonScene && selectedVersion) {
const mainVersion = useSimulationManager.getState().getProductById(projectId, mainScene.version.versionUuid, mainScene.product.productUuid); const mainVersion = useSimulationManager
.getState()
.getProductById(
projectId,
mainScene.version.versionUuid,
mainScene.product.productUuid
);
const compareVersion = useSimulationManager.getState().getProductById(projectId, comparisonScene.version.versionUuid, comparisonScene.product.productUuid); const compareVersion = useSimulationManager
.getState()
.getProductById(
projectId,
comparisonScene.version.versionUuid,
comparisonScene.product.productUuid
);
const mainVompareversion = createCompareProduct(mainVersion?.productId ?? "", mainScene.product.productName, mainVersion?.simulateData || []); const mainVompareversion = createCompareProduct(
const compareProduct2 = createCompareProduct(compareVersion?.productId ?? "", comparisonScene.product.productName, compareVersion?.simulateData || []); mainVersion?.productId ?? "",
mainScene.product.productName,
mainVersion?.simulateData || []
);
const compareProduct2 = createCompareProduct(
compareVersion?.productId ?? "",
comparisonScene.product.productName,
compareVersion?.simulateData || []
);
const comparedArray = [mainVompareversion, compareProduct2]; const comparedArray = [mainVompareversion, compareProduct2];
@@ -131,7 +178,14 @@ function ComparisonScene() {
} else { } else {
setShouldShowComparisonResult(false); setShouldShowComparisonResult(false);
} }
}, [mainScene, comparisonScene, selectedVersion, projectId, setCompareProductsData, simulationRecords]); }, [
mainScene,
comparisonScene,
selectedVersion,
projectId,
setCompareProductsData,
simulationRecords,
]);
return ( return (
<> <>
@@ -154,7 +208,20 @@ function ComparisonScene() {
/> />
</div> </div>
)} )}
<CompareLayOut /> {selectedVersion?.versionId && (
<div
style={{
position: "absolute",
top: "10px",
right: "900px",
zIndex: 10,
}}
>
<Button />
</div>
)}
{<CompareLayOut />}
{createNewWindow && <NewWindowScene />}
{shouldShowComparisonResult && !loadingProgress && <ComparisonResult />} {shouldShowComparisonResult && !loadingProgress && <ComparisonResult />}
</> </>
)} )}

View File

@@ -2,182 +2,382 @@ import React, { ReactNode, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom"; import { createPortal } from "react-dom";
type NewWindowProps = { type NewWindowProps = {
children: ReactNode; children: ReactNode;
title?: string; title?: string;
width?: number; width?: number;
height?: number; height?: number;
left?: number; left?: number;
top?: number; top?: number;
center?: boolean; center?: boolean;
features?: Partial<{ features?: Partial<{
toolbar: boolean; toolbar: boolean;
menubar: boolean; menubar: boolean;
scrollbars: boolean; scrollbars: boolean;
resizable: boolean; resizable: boolean;
location: boolean; location: boolean;
status: boolean; status: boolean;
}>; }>;
onClose?: () => void; onClose?: () => void;
copyStyles?: boolean; copyStyles?: boolean;
noopener?: boolean; noopener?: boolean;
className?: string; className?: string;
theme?: string | null; theme?: string | null;
}; };
// export const RenderInNewWindow: React.FC<NewWindowProps> = ({
// children,
// title = "New Window",
// width = 900,
// height = 700,
// left,
// top,
// center = true,
// features,
// onClose,
// copyStyles = true,
// noopener = true,
// className,
// theme = localStorage.getItem("theme") ?? "light",
// }) => {
// const [mounted, setMounted] = useState(false);
// const childWindowRef = useRef<Window | null>(null);
// const containerElRef = useRef<HTMLDivElement | null>(null);
// useEffect(() => {
// if (typeof window === "undefined") return;
// const screenLeft = window.screenLeft ?? window.screenX ?? 0;
// const screenTop = window.screenTop ?? window.screenY ?? 0;
// const availWidth = window.outerWidth ?? window.innerWidth;
// const availHeight = window.outerHeight ?? window.innerHeight;
// const finalLeft =
// center && availWidth ? Math.max(0, screenLeft + (availWidth - width) / 2) : left ?? 100;
// const finalTop =
// center && availHeight
// ? Math.max(0, screenTop + (availHeight - height) / 2)
// : top ?? 100;
// const baseFeatures = [
// `width=${Math.floor(width)}`,
// `height=${Math.floor(height)}`,
// `left=${Math.floor(finalLeft)}`,
// `top=${Math.floor(finalTop)}`,
// ];
// const featureFlags = features ?? {
// toolbar: false,
// menubar: false,
// scrollbars: true,
// resizable: true,
// location: false,
// status: false,
// };
// Object.entries(featureFlags).forEach(([k, v]) =>
// baseFeatures.push(`${k}=${v ? "yes" : "no"}`)
// );
// const newWin = window.open("", "_blank", baseFeatures.join(","));
// if (!newWin) {
// console.warn("Popup blocked or failed to open window.");
// onClose?.();
// return;
// }
// if (noopener) {
// try {
// newWin.opener = null;
// } catch {}
// }
// newWin.document.open();
// newWin.document.write(`<!doctype html>
// <html data-theme=${theme}>
// <head>
// <meta charset="utf-8" />
// <title>${title}</title>
// <meta name="viewport" content="width=device-width, initial-scale=1" />
// </head>
// <body style="margin:0;"></body>
// </html>`);
// newWin.document.close();
// if (copyStyles) {
// const head = newWin.document.head;
// Array.from(document.styleSheets).forEach((styleSheet) => {
// try {
// if ((styleSheet as CSSStyleSheet).cssRules) {
// const newStyleEl = newWin.document.createElement("style");
// const rules = Array.from((styleSheet as CSSStyleSheet).cssRules).map(
// (r) => r.cssText
// );
// newStyleEl.appendChild(newWin.document.createTextNode(rules.join("\n")));
// head.appendChild(newStyleEl);
// }
// } catch {
// const ownerNode = styleSheet.ownerNode as HTMLElement | null;
// if (ownerNode && ownerNode.tagName === "LINK") {
// const link = ownerNode as HTMLLinkElement;
// const newLink = newWin.document.createElement("link");
// newLink.rel = link.rel;
// newLink.href = link.href;
// newLink.media = link.media;
// head.appendChild(newLink);
// }
// }
// });
// }
// const container = newWin.document.createElement("div");
// if (className) container.className = className;
// newWin.document.body.appendChild(container);
// newWin.document.title = title;
// // Handle child window close
// const handleChildUnload = () => {
// onClose?.();
// };
// newWin.addEventListener("beforeunload", handleChildUnload);
// // 👇 Handle parent refresh/close → auto close child
// const handleParentUnload = () => {
// try {
// newWin.close();
// } catch {}
// };
// window.addEventListener("beforeunload", handleParentUnload);
// childWindowRef.current = newWin;
// containerElRef.current = container;
// setMounted(true);
// return () => {
// newWin.removeEventListener("beforeunload", handleChildUnload);
// window.removeEventListener("beforeunload", handleParentUnload);
// try {
// newWin.close();
// } catch {}
// childWindowRef.current = null;
// containerElRef.current = null;
// };
// // eslint-disable-next-line react-hooks/exhaustive-deps
// }, []);
// useEffect(() => {
// const w = childWindowRef.current;
// if (w && !w.closed) {
// w.document.title = title;
// }
// }, [title]);
// if (!mounted || !containerElRef.current) return null;
// return createPortal(children, containerElRef.current);
// };
export const RenderInNewWindow: React.FC<NewWindowProps> = ({ export const RenderInNewWindow: React.FC<NewWindowProps> = ({
children, children,
title = "New Window", title = "3D Viewer",
width = 900, width = 900,
height = 700, height = 700,
left, left,
top, top,
center = true, center = true,
features, features,
onClose, onClose,
copyStyles = true, copyStyles = true,
noopener = true, noopener = true,
className, className,
theme = localStorage.getItem('theme') ?? 'light', theme = localStorage.getItem("theme") ?? "light",
}) => { }) => {
const [mounted, setMounted] = useState(false); const [mounted, setMounted] = useState(false);
const childWindowRef = useRef<Window | null>(null); const childWindowRef = useRef<Window | null>(null);
const containerElRef = useRef<HTMLDivElement | null>(null); const containerElRef = useRef<HTMLDivElement | null>(null);
useEffect(() => { useEffect(() => {
if (typeof window === "undefined") return; if (typeof window === "undefined") return;
const screenLeft = window.screenLeft ?? window.screenX ?? 0; const screenLeft = window.screenLeft ?? window.screenX ?? 0;
const screenTop = window.screenTop ?? window.screenY ?? 0; const screenTop = window.screenTop ?? window.screenY ?? 0;
const availWidth = window.outerWidth ?? window.innerWidth; const availWidth = window.outerWidth ?? window.innerWidth;
const availHeight = window.outerHeight ?? window.innerHeight; const availHeight = window.outerHeight ?? window.innerHeight;
const finalLeft = const finalLeft =
center && availWidth center && availWidth ? Math.max(0, screenLeft + (availWidth - width) / 2) : left ?? 100;
? Math.max(0, screenLeft + (availWidth - width) / 2)
: left ?? 100;
const finalTop = const finalTop =
center && availHeight center && availHeight
? Math.max(0, screenTop + (availHeight - height) / 2) ? Math.max(0, screenTop + (availHeight - height) / 2)
: top ?? 100; : top ?? 100;
const baseFeatures = [ const baseFeatures = [
`width=${Math.floor(width)}`, `width=${Math.floor(width)}`,
`height=${Math.floor(height)}`, `height=${Math.floor(height)}`,
`left=${Math.floor(finalLeft)}`, `left=${Math.floor(finalLeft)}`,
`top=${Math.floor(finalTop)}`, `top=${Math.floor(finalTop)}`,
]; ];
const featureFlags = features ?? { const featureFlags = features ?? {
toolbar: false, toolbar: false,
menubar: false, menubar: false,
scrollbars: true, scrollbars: true,
resizable: true, resizable: true,
location: false, location: false,
status: false, status: false,
}; };
Object.entries(featureFlags).forEach(([k, v]) => Object.entries(featureFlags).forEach(([k, v]) =>
baseFeatures.push(`${k}=${v ? "yes" : "no"}`) baseFeatures.push(`${k}=${v ? "yes" : "no"}`)
); );
const newWin = window.open("", "_blank", baseFeatures.join(",")); const newWin = window.open("", "_blank", baseFeatures.join(","));
if (!newWin) { if (!newWin) {
console.warn("Popup blocked or failed to open window."); console.warn("Popup blocked or failed to open window.");
onClose?.(); onClose?.();
return; return;
} }
if (noopener) { if (noopener) {
try { try {
newWin.opener = null; newWin.opener = null;
} catch {} } catch {}
} }
newWin.document.open(); newWin.document.open();
newWin.document.write(`<!doctype html> newWin.document.write(`<!doctype html>
<html data-theme=${theme}> <html data-theme=${theme}>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<title>${title}</title> <title>${title}</title>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100vw;
height: 100vh;
}
#three-container {
width: 100%;
height: 100%;
}
</style>
</head> </head>
<body style="margin:0;"></body> <body>
<div id="three-container"></div>
</body>
</html>`); </html>`);
newWin.document.close(); newWin.document.close();
if (copyStyles) { if (copyStyles) {
const head = newWin.document.head; const head = newWin.document.head;
Array.from(document.styleSheets).forEach((styleSheet) => { Array.from(document.styleSheets).forEach((styleSheet) => {
try { try {
if ((styleSheet as CSSStyleSheet).cssRules) { if ((styleSheet as CSSStyleSheet).cssRules) {
const newStyleEl = newWin.document.createElement("style"); const newStyleEl = newWin.document.createElement("style");
const rules = Array.from( const rules = Array.from((styleSheet as CSSStyleSheet).cssRules).map(
(styleSheet as CSSStyleSheet).cssRules (r) => r.cssText
).map((r) => r.cssText); );
newStyleEl.appendChild( newStyleEl.appendChild(newWin.document.createTextNode(rules.join("\n")));
newWin.document.createTextNode(rules.join("\n")) head.appendChild(newStyleEl);
); }
head.appendChild(newStyleEl); } catch {
} const ownerNode = styleSheet.ownerNode as HTMLElement | null;
} catch { if (ownerNode && ownerNode.tagName === "LINK") {
const ownerNode = styleSheet.ownerNode as HTMLElement | null; const link = ownerNode as HTMLLinkElement;
if (ownerNode && ownerNode.tagName === "LINK") { const newLink = newWin.document.createElement("link");
const link = ownerNode as HTMLLinkElement; newLink.rel = link.rel;
const newLink = newWin.document.createElement("link"); newLink.href = link.href;
newLink.rel = link.rel; newLink.media = link.media;
newLink.href = link.href; head.appendChild(newLink);
newLink.media = link.media; }
head.appendChild(newLink); }
} });
} }
});
}
const container = newWin.document.createElement("div"); const container = newWin.document.getElementById(
if (className) container.className = className; "three-container"
newWin.document.body.appendChild(container); ) as HTMLDivElement | null;
if (!container) return;
newWin.document.title = title; if (className) container.className = className;
// Handle child window close newWin.document.title = title;
const handleChildUnload = () => {
onClose?.();
};
newWin.addEventListener("beforeunload", handleChildUnload);
// 👇 Handle parent refresh/close → auto close child // ✅ CRITICAL FIX: Wait for window to be fully ready then trigger resize
const handleParentUnload = () => { const initializeWindow = () => {
try { // Force maximize handling
newWin.close(); setTimeout(() => {
} catch {} // Get actual window dimensions (might be maximized)
}; const actualWidth = newWin.innerWidth;
window.addEventListener("beforeunload", handleParentUnload); const actualHeight = newWin.innerHeight;
childWindowRef.current = newWin; console.log("Window dimensions:", actualWidth, actualHeight);
containerElRef.current = container;
setMounted(true);
return () => { // Trigger resize event for Three.js
newWin.removeEventListener("beforeunload", handleChildUnload); newWin.dispatchEvent(new Event("resize"));
window.removeEventListener("beforeunload", handleParentUnload);
try {
newWin.close();
} catch {}
childWindowRef.current = null;
containerElRef.current = null;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => { // Additional safety: trigger again after a short delay
const w = childWindowRef.current; setTimeout(() => {
if (w && !w.closed) { newWin.dispatchEvent(new Event("resize"));
w.document.title = title; }, 200);
} }, 100);
}, [title]); };
if (!mounted || !containerElRef.current) return null; // Handle both load and focus events
newWin.addEventListener("load", initializeWindow);
newWin.addEventListener("focus", initializeWindow);
return createPortal(children, containerElRef.current); // Also initialize immediately if window is already loaded
if (newWin.document.readyState === "complete") {
initializeWindow();
} else {
newWin.addEventListener("DOMContentLoaded", initializeWindow);
}
// Handle child window close
const handleChildUnload = () => {
onClose?.();
};
newWin.addEventListener("beforeunload", handleChildUnload);
// Handle parent refresh/close → auto close child
const handleParentUnload = () => {
try {
newWin.close();
} catch {}
};
window.addEventListener("beforeunload", handleParentUnload);
childWindowRef.current = newWin;
containerElRef.current = container;
setMounted(true);
return () => {
newWin.removeEventListener("load", initializeWindow);
newWin.removeEventListener("focus", initializeWindow);
newWin.removeEventListener("DOMContentLoaded", initializeWindow);
newWin.removeEventListener("beforeunload", handleChildUnload);
window.removeEventListener("beforeunload", handleParentUnload);
try {
newWin.close();
} catch {}
childWindowRef.current = null;
containerElRef.current = null;
};
}, []);
useEffect(() => {
const w = childWindowRef.current;
if (w && !w.closed) {
w.document.title = title;
}
}, [title]);
if (!mounted || !containerElRef.current) return null;
return createPortal(children, containerElRef.current);
}; };

View File

@@ -9,14 +9,18 @@ interface LoadingPageProps {
} }
const LoadingPage: React.FC<LoadingPageProps> = ({ progress }) => { const LoadingPage: React.FC<LoadingPageProps> = ({ progress }) => {
console.log('progress: ', progress);
const { projectName } = useProjectName(); const { projectName } = useProjectName();
const { comparisonScene } = useSimulationState(); const { comparisonScene } = useSimulationState();
const validatedProgress = Math.min(100, Math.max(0, progress)); const validatedProgress = Math.min(100, Math.max(0, progress));
console.log("comparisonScene: ", comparisonScene);
return ( return (
<RenderOverlay> <RenderOverlay>
<div className={`loading-wrapper ${comparisonScene != null ? "comparisionLoading" : ""}`}> <div
className={`loading-wrapper ${comparisonScene != null ? "comparisionLoading" : ""}`}
>
<div className="loading-container"> <div className="loading-container">
<div className="project-name">{projectName}</div> <div className="project-name">{projectName}</div>
<div className="loading-hero-container"> <div className="loading-hero-container">

View File

@@ -0,0 +1,47 @@
import React from "react";
import {
useCreateNewWindow,
useIsComparing,
useLoadingProgress,
} from "../../../store/builder/store";
import { useSimulationState } from "../../../store/simulation/useSimulationStore";
const Button = () => {
const { isComparing, setIsComparing } = useIsComparing();
const { createNewWindow, setCreateNewWindow } = useCreateNewWindow();
const { setLoadingProgress } = useLoadingProgress();
const { clearComparisonState } = useSimulationState();
const handleExit = () => {
setIsComparing(false);
setCreateNewWindow(false);
setLoadingProgress(0);
clearComparisonState();
};
const handleOpenInNewWindow = () => {
// 🧹 Immediately reset any loading or scene state
setLoadingProgress(0);
setCreateNewWindow(true);
};
return (
<div
style={{
position: "absolute",
top: "10px",
left: "10px",
zIndex: 1000,
display: "flex",
gap: "10px",
}}
>
{isComparing && <button onClick={handleExit}>Exit</button>}
{isComparing && !createNewWindow && (
<button onClick={handleOpenInNewWindow}>Open in New Window</button>
)}
</div>
);
};
export default Button;

View File

@@ -1,7 +1,11 @@
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import React, { useState, useRef, useEffect, Suspense } from "react"; import React, { useState, useRef, useEffect, Suspense } from "react";
import { CompareLayoutIcon, LayoutIcon, ResizerIcon } from "../../icons/SimulationIcons"; import { CompareLayoutIcon, LayoutIcon, ResizerIcon } from "../../icons/SimulationIcons";
import { useLoadingProgress, useIsComparing } from "../../../store/builder/store"; import {
useLoadingProgress,
useIsComparing,
useCreateNewWindow,
} from "../../../store/builder/store";
import { useSimulationState } from "../../../store/simulation/useSimulationStore"; import { useSimulationState } from "../../../store/simulation/useSimulationStore";
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore"; import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
import { useSceneContext } from "../../../modules/scene/sceneContext"; import { useSceneContext } from "../../../modules/scene/sceneContext";
@@ -13,11 +17,18 @@ import useRestStates from "../../../hooks/useResetStates";
import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi"; import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi";
import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi"; import { validateSimulationDataApi } from "../../../services/simulation/comparison/validateSimulationDataApi";
import Button from "./Button";
const CompareLayOut = () => { const CompareLayOut = () => {
const { clearComparisonState, comparisonScene, setComparisonState } = useSimulationState(); const { clearComparisonState, comparisonScene, setComparisonState } = useSimulationState();
const { versionStore } = useSceneContext(); const { versionStore } = useSceneContext();
const { versionHistory, selectedVersion, setSelectedVersion, clearSelectedVersion, setVersions } = versionStore(); const {
versionHistory,
selectedVersion,
setSelectedVersion,
clearSelectedVersion,
setVersions,
} = versionStore();
const { setLoadingProgress } = useLoadingProgress(); const { setLoadingProgress } = useLoadingProgress();
const [width, setWidth] = useState("50vw"); const [width, setWidth] = useState("50vw");
const [isResizing, setIsResizing] = useState(false); const [isResizing, setIsResizing] = useState(false);
@@ -30,6 +41,7 @@ const CompareLayOut = () => {
const { setIsPlaying } = usePlayButtonStore(); const { setIsPlaying } = usePlayButtonStore();
const { projectId } = useParams(); const { projectId } = useParams();
const { resetStates } = useRestStates(); const { resetStates } = useRestStates();
const { createNewWindow } = useCreateNewWindow();
useEffect(() => { useEffect(() => {
return () => { return () => {
resetStates(); resetStates();
@@ -178,59 +190,77 @@ const CompareLayOut = () => {
}; };
return ( return (
<div className={`compareLayOut-wrapper ${width === "0px" ? "closed" : ""}`} ref={wrapperRef} style={{ width }}> <>
{loadingProgress === 0 && selectedVersion?.versionId && ( {!createNewWindow && (
<button title="resize-canvas" id="compare-resize-slider-btn" className="resizer" onMouseDown={handleStartResizing}> <div
<ResizerIcon /> className={`compareLayOut-wrapper ${width === "0px" ? "closed" : ""}`}
</button> ref={wrapperRef}
)} style={{ width }}
<div className="chooseLayout-container"> >
{selectedVersion?.versionId && ( {loadingProgress === 0 && selectedVersion?.versionId && (
<div className="compare-layout-canvas-container"> <button
<Suspense fallback={null}> title="resize-canvas"
<Scene layout="Comparison Layout" /> id="compare-resize-slider-btn"
</Suspense> className="resizer"
</div> onMouseDown={handleStartResizing}
)} >
<ResizerIcon />
{width !== "0px" && </button>
!selectedVersion?.versionId && ( // Show only if no layout selected )}
<div className="chooseLayout-wrapper"> <div className="chooseLayout-container">
<div className="icon"> {selectedVersion?.versionId && (
<CompareLayoutIcon /> <div className="compare-layout-canvas-container">
<Suspense fallback={null}>
<Scene layout="Comparison Layout" />
</Suspense>
</div> </div>
<div className="value">Choose Version to compare</div> )}
<button className="selectLayout" onClick={() => setShowLayoutDropdown(!showLayoutDropdown)}>
Select Version
</button>
{showLayoutDropdown && ( {width !== "0px" &&
<div className="displayLayouts-container"> !selectedVersion?.versionId && ( // Show only if no layout selected
<div className="header">Versions</div> <div className="chooseLayout-wrapper">
<Search onChange={() => {}} /> <div className="icon">
<div className="layouts-container"> <CompareLayoutIcon />
{versionHistory.map((version) => (
<button
key={version.versionId}
className="layout-wrapper"
onClick={() => {
handleSelectLayout(version);
setShowLayoutDropdown(false);
}}
>
<LayoutIcon />
<div className="layout">{version.versionName}</div>
</button>
))}
</div> </div>
<div className="value">Choose Version to compare</div>
<button
className="selectLayout"
onClick={() => setShowLayoutDropdown(!showLayoutDropdown)}
>
Select Version
</button>
{showLayoutDropdown && (
<div className="displayLayouts-container">
<div className="header">Versions</div>
<Search onChange={() => {}} />
<div className="layouts-container">
{versionHistory.map((version) => (
<button
key={version.versionId}
className="layout-wrapper"
onClick={() => {
handleSelectLayout(version);
setShowLayoutDropdown(false);
}}
>
<LayoutIcon />
<div className="layout">
{version.versionName}
</div>
</button>
))}
</div>
</div>
)}
</div> </div>
)} )}
</div>
)}
{/* Always show after layout is selected */} {/* Always show after layout is selected */}
</div> </div>
</div> </div>
)}
</>
); );
}; };

View File

@@ -0,0 +1,44 @@
import React, { Suspense, useEffect, useState } from "react";
import { RenderInNewWindow } from "../../templates/CreateNewWindow";
import { useSceneContext } from "../../../modules/scene/sceneContext";
import { useCreateNewWindow, useLoadingProgress } from "../../../store/builder/store";
import Scene from "../../../modules/scene/scene";
import ComparisonResult from "./ComparisonResult";
import Button from "./Button";
const NewWindowScene = () => {
const { versionStore } = useSceneContext();
const { selectedVersion } = versionStore();
const { setCreateNewWindow } = useCreateNewWindow();
const { loadingProgress } = useLoadingProgress();
return (
<>
{selectedVersion?.versionId && (
<div style={{ width: "100%", height: "100%" }}>
<Suspense fallback={null}>
<RenderInNewWindow
title="3D Viewer"
onClose={() => setCreateNewWindow(false)}
>
<div
style={{
position: "absolute",
top: "10px",
right: "900px",
zIndex: 1000,
}}
>
<Button />
</div>
<Scene layout="Comparison Layout" />
{!loadingProgress && <ComparisonResult />}
</RenderInNewWindow>
</Suspense>
</div>
)}
</>
);
};
export default NewWindowScene;

View File

@@ -1,185 +1,164 @@
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { import { LogListIcon, CloseIcon, ExpandIcon2 } from "../../icons/ExportCommonIcons"; // Adjust path as needed
LogListIcon,
CloseIcon,
ExpandIcon2,
} from "../../icons/ExportCommonIcons"; // Adjust path as needed
import { LogEntry, useLogger } from "./LoggerContext"; import { LogEntry, useLogger } from "./LoggerContext";
import { GetLogIcon } from "../../footer/getLogIcons"; import { GetLogIcon } from "../../footer/getLogIcons";
import { RenderInNewWindow } from "../../templates/CreateNewWindow"; import { RenderInNewWindow } from "../../templates/CreateNewWindow";
// --- Logs Component --- // --- Logs Component ---
type LogsProps = { type LogsProps = {
selectedTab: "all" | "info" | "warning" | "error"; selectedTab: "all" | "info" | "warning" | "error";
setSelectedTab: (tab: "all" | "info" | "warning" | "error") => void; setSelectedTab: (tab: "all" | "info" | "warning" | "error") => void;
clear: () => void; clear: () => void;
filteredLogs: LogEntry[]; filteredLogs: LogEntry[];
formatTimestamp: (date: Date) => string; formatTimestamp: (date: Date) => string;
}; };
const Logs: React.FC<LogsProps> = ({ const Logs: React.FC<LogsProps> = ({
selectedTab, selectedTab,
setSelectedTab, setSelectedTab,
clear, clear,
filteredLogs, filteredLogs,
formatTimestamp, formatTimestamp,
}) => { }) => {
return ( return (
<> <>
<div className="log-nav-container"> <div className="log-nav-container">
<div className="log-nav-wrapper"> <div className="log-nav-wrapper">
{["all", "info", "warning", "error"].map((type) => ( {["all", "info", "warning", "error"].map((type) => (
<button <button
id="log-type" id="log-type"
title="log-type" title="log-type"
key={type} key={type}
className={`log-nav ${selectedTab === type ? "active" : ""}`} className={`log-nav ${selectedTab === type ? "active" : ""}`}
onClick={() => setSelectedTab(type as any)} onClick={() => setSelectedTab(type as any)}
> >
{`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`} {`${type.charAt(0).toUpperCase() + type.slice(1)} Logs`}
</button> </button>
))} ))}
</div>
<button
id="clean-btn"
title="clear-btn"
className="clear-button"
onClick={clear}
>
clear
</button>
</div>
{/* Log Entries */}
<div className="log-entry-wrapper">
{filteredLogs.length > 0 ? (
filteredLogs.map((log) => (
<div key={log.id} className={`log-entry ${log.type}`}>
<div className="log-icon">{GetLogIcon(log.type)}</div>
<div className="log-entry-message-container">
<div className="log-entry-message">{log.message}</div>
<div className="message-time">
{formatTimestamp(log.timestamp)}
</div> </div>
</div> <button id="clean-btn" title="clear-btn" className="clear-button" onClick={clear}>
clear
</button>
</div> </div>
))
) : ( {/* Log Entries */}
<div className="no-log"> <div className="log-entry-wrapper">
There are no logs to display at the moment. {filteredLogs.length > 0 ? (
</div> filteredLogs.map((log) => (
)} <div key={log.id} className={`log-entry ${log.type}`}>
</div> <div className="log-icon">{GetLogIcon(log.type)}</div>
</> <div className="log-entry-message-container">
); <div className="log-entry-message">{log.message}</div>
<div className="message-time">{formatTimestamp(log.timestamp)}</div>
</div>
</div>
))
) : (
<div className="no-log">There are no logs to display at the moment.</div>
)}
</div>
</>
);
}; };
// --- LogList Component --- // --- LogList Component ---
const LogList: React.FC = () => { const LogList: React.FC = () => {
const { const { logs, clear, setIsLogListVisible, isLogListVisible, selectedTab, setSelectedTab } =
logs, useLogger();
clear,
setIsLogListVisible,
isLogListVisible,
selectedTab,
setSelectedTab,
} = useLogger();
const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString(); const formatTimestamp = (date: Date) => new Date(date).toLocaleTimeString();
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
console.log('open: ', open);
const filteredLogs =
selectedTab === "all"
? [...logs].reverse()
: [...logs].filter((log) => log.type === selectedTab).reverse();
const filteredLogs = useEffect(() => {
selectedTab === "all" if (isLogListVisible && logs.length > 0) {
? [...logs].reverse() const lastLog = logs[logs.length - 1];
: [...logs].filter((log) => log.type === selectedTab).reverse(); const validTypes = ["all", "info", "warning", "error"];
if (validTypes.includes(lastLog.type)) {
setSelectedTab(lastLog.type as any);
} else {
setSelectedTab("all");
}
}
// eslint-disable-next-line
}, [isLogListVisible]);
useEffect(() => { return (
if (isLogListVisible && logs.length > 0) { <>
const lastLog = logs[logs.length - 1]; {!open ? (
const validTypes = ["all", "info", "warning", "error"]; <div className="log-list-container" onClick={() => setIsLogListVisible(false)}>
if (validTypes.includes(lastLog.type)) { {/* eslint-disable-next-line */}
setSelectedTab(lastLog.type as any); <div
} else { className="log-list-wrapper"
setSelectedTab("all"); onClick={(e) => {
} e.stopPropagation();
} }}
// eslint-disable-next-line >
}, [isLogListVisible]); <div className="log-header">
<div className="log-header-wrapper">
<div className="icon">
<LogListIcon />
</div>
<div className="head">Log List</div>
</div>
<div className="action-buttons-container">
<button
id="expand-log-btn"
title="open in new tab"
className="expand-btn"
onClick={() => {
setOpen(true);
}}
>
<ExpandIcon2 />
</button>
<button
id="close-btn"
title="close"
className="close"
onClick={() => setIsLogListVisible(false)}
>
<CloseIcon />
</button>
</div>
</div>
return ( {/* Logs Section */}
<> <Logs
{!open ? ( selectedTab={selectedTab as any}
<div setSelectedTab={setSelectedTab as any}
className="log-list-container" clear={clear}
onClick={() => setIsLogListVisible(false)} filteredLogs={filteredLogs}
> formatTimestamp={formatTimestamp}
{/* eslint-disable-next-line */} />
<div </div>
className="log-list-wrapper"
onClick={(e) => {
e.stopPropagation();
}}
>
<div className="log-header">
<div className="log-header-wrapper">
<div className="icon">
<LogListIcon />
</div> </div>
<div className="head">Log List</div> ) : (
</div> <RenderInNewWindow
<div className="action-buttons-container"> title="Log list"
<button onClose={() => {
id="expand-log-btn" setOpen(false);
title="open in new tab" setIsLogListVisible(false);
className="expand-btn" }}
onClick={() => {
setOpen(true);
}}
> >
<ExpandIcon2 /> <div className="log-list-new-window-wrapper">
</button> <Logs
<button selectedTab={selectedTab as any}
id="close-btn" setSelectedTab={setSelectedTab as any}
title="close" clear={clear}
className="close" filteredLogs={filteredLogs}
onClick={() => setIsLogListVisible(false)} formatTimestamp={formatTimestamp}
> />
<CloseIcon /> </div>
</button> </RenderInNewWindow>
</div> )}
</div> </>
);
{/* Logs Section */}
<Logs
selectedTab={selectedTab as any}
setSelectedTab={setSelectedTab as any}
clear={clear}
filteredLogs={filteredLogs}
formatTimestamp={formatTimestamp}
/>
</div>
</div>
) : (
<RenderInNewWindow
title="Log list"
onClose={() => {
setOpen(false);
setIsLogListVisible(false);
}}
>
<div className="log-list-new-window-wrapper">
<Logs
selectedTab={selectedTab as any}
setSelectedTab={setSelectedTab as any}
clear={clear}
filteredLogs={filteredLogs}
formatTimestamp={formatTimestamp}
/>
</div>
</RenderInNewWindow>
)}
</>
);
}; };
export default LogList; export default LogList;

View File

@@ -13,13 +13,17 @@ import { getAssetFieldApi } from "../../../../../services/factoryBuilder/asset/f
import { ModelAnimator } from "./animator/modelAnimator"; import { ModelAnimator } from "./animator/modelAnimator";
import { useModelEventHandlers } from "./eventHandlers/useModelEventHandlers"; import { useModelEventHandlers } from "./eventHandlers/useModelEventHandlers";
function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) { function Model({
asset,
isRendered,
loader,
}: Readonly<{ asset: Asset; isRendered: boolean; loader: GLTFLoader }>) {
const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`; const url_Backend_dwinzo = `http://${process.env.REACT_APP_SERVER_MARKETPLACE_URL}`;
const savedTheme: string = localStorage.getItem("theme") || "light"; const savedTheme: string = localStorage.getItem("theme") || "light";
const { toolMode } = useToolMode(); const { toolMode } = useToolMode();
const { toggleView } = useToggleView(); const { toggleView } = useToggleView();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { assetStore } = useSceneContext(); const { assetStore, layout } = useSceneContext();
const { resetAnimation, hasSelectedAsset, updateSelectedAsset, selectedAssets } = assetStore(); const { resetAnimation, hasSelectedAsset, updateSelectedAsset, selectedAssets } = assetStore();
const { setDeletableFloorAsset } = useBuilderStore(); const { setDeletableFloorAsset } = useBuilderStore();
const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null); const [gltfScene, setGltfScene] = useState<GLTF["scene"] | null>(null);
@@ -52,7 +56,12 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere
}, [activeModule, toolMode, selectedAssets]); }, [activeModule, toolMode, selectedAssets]);
useEffect(() => { useEffect(() => {
if (groupRef.current && selectedAssets.length === 1 && selectedAssets[0].userData.modelUuid === asset.modelUuid && hasSelectedAsset(asset.modelUuid)) { if (
groupRef.current &&
selectedAssets.length === 1 &&
selectedAssets[0].userData.modelUuid === asset.modelUuid &&
hasSelectedAsset(asset.modelUuid)
) {
updateSelectedAsset(groupRef.current); updateSelectedAsset(groupRef.current);
} }
}, [isRendered, selectedAssets, asset]); }, [isRendered, selectedAssets, asset]);
@@ -130,7 +139,10 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere
logModelStatus(assetId, "backend-loaded"); logModelStatus(assetId, "backend-loaded");
}) })
.catch((error) => { .catch((error) => {
console.error(`[Backend] Error storing/loading ${asset.modelName}:`, error); console.error(
`[Backend] Error storing/loading ${asset.modelName}:`,
error
);
}); });
}, },
undefined, undefined,
@@ -144,7 +156,8 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere
}); });
}, []); }, []);
const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } = useModelEventHandlers({ boundingBox, groupRef, asset }); const { handleDblClick, handleClick, handlePointerOver, handlePointerOut, handleContextMenu } =
useModelEventHandlers({ boundingBox, groupRef, asset });
return ( return (
<group <group
@@ -197,9 +210,25 @@ function Model({ asset, isRendered, loader }: Readonly<{ asset: Asset; isRendere
<ModelAnimator asset={asset} gltfScene={gltfScene} /> <ModelAnimator asset={asset} gltfScene={gltfScene} />
</> </>
) : ( ) : (
<>{!hasSelectedAsset(asset.modelUuid) && <AssetBoundingBox name="Asset Fallback" boundingBox={boundingBox} color="gray" lineWidth={2.5} />}</> <>
{!hasSelectedAsset(asset.modelUuid) && (
<AssetBoundingBox
name="Asset Fallback"
boundingBox={boundingBox}
color="gray"
lineWidth={2.5}
/>
)}
</>
)}
{hasSelectedAsset(asset.modelUuid) && (
<AssetBoundingBox
name="Asset BBox"
boundingBox={boundingBox}
color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"}
lineWidth={2.7}
/>
)} )}
{hasSelectedAsset(asset.modelUuid) && <AssetBoundingBox name="Asset BBox" boundingBox={boundingBox} color={savedTheme === "dark" ? "#c4abf1" : "#6f42c1"} lineWidth={2.7} />}
</> </>
)} )}
</group> </group>

View File

@@ -3,7 +3,11 @@ import { Group, Vector3 } from "three";
import { CameraControls } from "@react-three/drei"; import { CameraControls } from "@react-three/drei";
import { GLTFLoader } from "three/examples/jsm/Addons"; import { GLTFLoader } from "three/examples/jsm/Addons";
import { useThree, useFrame } from "@react-three/fiber"; import { useThree, useFrame } from "@react-three/fiber";
import { useContextActionStore, useLimitDistance, useRenderDistance } from "../../../../store/builder/store"; import {
useContextActionStore,
useLimitDistance,
useRenderDistance,
} from "../../../../store/builder/store";
import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore"; import { useSelectedAsset } from "../../../../store/simulation/useSimulationStore";
import { useSceneContext } from "../../../scene/sceneContext"; import { useSceneContext } from "../../../scene/sceneContext";
import useZoomMesh from "../../hooks/useZoomMesh"; import useZoomMesh from "../../hooks/useZoomMesh";
@@ -11,12 +15,14 @@ import useCallBackOnKey from "../../../../utils/hooks/useCallBackOnKey";
import Model from "./model/model"; import Model from "./model/model";
const distanceWorker = new Worker(new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)); const distanceWorker = new Worker(
new URL("../../../../services/factoryBuilder/webWorkers/distanceWorker.js", import.meta.url)
);
function Models({ loader }: { readonly loader: GLTFLoader }) { function Models({ loader }: { readonly loader: GLTFLoader }) {
const { controls, camera } = useThree(); const { controls, camera } = useThree();
const assetGroupRef = useRef<Group>(null); const assetGroupRef = useRef<Group>(null);
const { assetStore } = useSceneContext(); const { assetStore, layout } = useSceneContext();
const { assets, selectedAssets, getSelectedAssetUuids } = assetStore(); const { assets, selectedAssets, getSelectedAssetUuids } = assetStore();
const { selectedAsset, clearSelectedAsset } = useSelectedAsset(); const { selectedAsset, clearSelectedAsset } = useSelectedAsset();
const { contextAction, setContextAction } = useContextActionStore(); const { contextAction, setContextAction } = useContextActionStore();
@@ -30,6 +36,14 @@ function Models({ loader }: { readonly loader: GLTFLoader }) {
// console.log(assets); // console.log(assets);
}, [assets]); }, [assets]);
useEffect(() => {
const initialRenderMap: Record<string, boolean> = {};
assets.forEach((asset) => {
initialRenderMap[asset.modelUuid] = true;
});
setRenderMap(initialRenderMap);
}, [assets.length]);
useEffect(() => { useEffect(() => {
if (contextAction === "focusAsset") { if (contextAction === "focusAsset") {
zoomMeshes(getSelectedAssetUuids()); zoomMeshes(getSelectedAssetUuids());
@@ -55,20 +69,51 @@ function Models({ loader }: { readonly loader: GLTFLoader }) {
return { ...prev, [modelUuid]: shouldRender }; return { ...prev, [modelUuid]: shouldRender };
}); });
}; };
}, []);
return () => {
distanceWorker.terminate();
};
}, [distanceWorker, layout]);
useFrame(() => { useFrame(() => {
camera.getWorldPosition(cameraPos.current); camera.getWorldPosition(cameraPos.current);
for (const asset of assets) { for (const asset of assets) {
const isRendered = renderMap[asset.modelUuid] ?? false; const isRendered = renderMap[asset.modelUuid] ?? false;
distanceWorker.postMessage({ // distanceWorker.postMessage({
modelUuid: asset.modelUuid, // modelUuid: asset.modelUuid,
assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2] }, // assetPosition: { x: asset.position[0], y: asset.position[1], z: asset.position[2] },
cameraPosition: cameraPos.current, // cameraPosition: cameraPos.current,
limitDistance, // limitDistance,
renderDistance, // renderDistance,
isRendered, // isRendered,
}); // });
const assetVec = new Vector3(...asset.position);
const cameraVec = new Vector3(
cameraPos.current.x,
cameraPos.current.y,
cameraPos.current.z
);
const distance = assetVec.distanceTo(cameraVec);
if (limitDistance) {
if (!isRendered && distance <= renderDistance) {
setRenderMap((prev) => {
if (prev[asset.modelUuid] === true) return prev;
return { ...prev, [asset.modelUuid]: true };
});
} else if (isRendered && distance > renderDistance) {
setRenderMap((prev) => {
if (prev[asset.modelUuid] === false) return prev;
return { ...prev, [asset.modelUuid]: false };
});
}
} else if (!isRendered) {
setRenderMap((prev) => {
if (prev[asset.modelUuid] === true) return prev;
return { ...prev, [asset.modelUuid]: true };
});
}
} }
}); });
@@ -88,7 +133,12 @@ function Models({ loader }: { readonly loader: GLTFLoader }) {
}} }}
> >
{assets.map((asset) => ( {assets.map((asset) => (
<Model key={asset.modelUuid} asset={asset} isRendered={renderMap[asset.modelUuid] ?? false} loader={loader} /> <Model
key={asset.modelUuid}
asset={asset}
isRendered={renderMap[asset.modelUuid] ?? false}
loader={loader}
/>
))} ))}
</group> </group>
); );

View File

@@ -2,13 +2,11 @@ import { Vector3 } from "three";
import { useFrame, useThree } from "@react-three/fiber"; import { useFrame, useThree } from "@react-three/fiber";
import { CameraControls } from "@react-three/drei"; import { CameraControls } from "@react-three/drei";
import { useSceneContext } from "../sceneContext"; import { useSceneContext } from "../sceneContext";
import { useIsComparing } from "../../../store/builder/store"; import { useCreateNewWindow, useIsComparing } from "../../../store/builder/store";
import { useSceneStore } from "../../../store/scene/useSceneStore"; import { useSceneStore } from "../../../store/scene/useSceneStore";
import { useSimulationState } from "../../../store/simulation/useSimulationStore"; import { useSimulationState } from "../../../store/simulation/useSimulationStore";
import useModuleStore from "../../../store/ui/useModuleStore"; import useModuleStore from "../../../store/ui/useModuleStore";
import * as CONSTANTS from "../../../types/world/worldConstants";
function SyncCam() { function SyncCam() {
const { layout } = useSceneContext(); const { layout } = useSceneContext();
const { controls } = useThree(); const { controls } = useThree();
@@ -16,19 +14,39 @@ function SyncCam() {
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { comparisonScene } = useSimulationState(); const { comparisonScene } = useSimulationState();
const { setCamera, camState } = useSceneStore(); const { setCamera, camState } = useSceneStore();
const { windowRendered } = useCreateNewWindow();
function getControls() {
const position = (controls as CameraControls).getPosition(new Vector3());
const target = (controls as CameraControls).getTarget(new Vector3());
setCamera(position, target);
}
function setControls() {
(controls as CameraControls).setLookAt(
camState.position.x,
camState.position.y,
camState.position.z,
camState.target.x,
camState.target.y,
camState.target.z,
true
);
}
useFrame(() => { useFrame(() => {
if (layout === "Comparison Layout" && controls && camState) { if (
(controls as any).mouseButtons.left = CONSTANTS.controlsTransition.leftMouse; controls &&
(controls as any).mouseButtons.right = CONSTANTS.controlsTransition.rightMouse; isComparing &&
(controls as any).mouseButtons.wheel = CONSTANTS.controlsTransition.wheelMouse; activeModule === "simulation" &&
(controls as any).mouseButtons.middle = CONSTANTS.controlsTransition.middleMouse; comparisonScene &&
(controls as CameraControls).setLookAt(camState.position.x, camState.position.y, camState.position.z, camState.target.x, camState.target.y, camState.target.z, true); camState
} ) {
if (layout === "Main Layout" && controls && isComparing && activeModule === "simulation" && comparisonScene) { if (layout === windowRendered) {
const position = (controls as CameraControls).getPosition(new Vector3()); getControls();
const target = (controls as CameraControls).getTarget(new Vector3()); } else {
setCamera(position, target); setControls();
}
} }
}); });

View File

@@ -1,5 +1,5 @@
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { Canvas } from "@react-three/fiber"; import { Canvas, useThree } from "@react-three/fiber";
import { KeyboardControls } from "@react-three/drei"; import { KeyboardControls } from "@react-three/drei";
import { useSceneContext } from "./sceneContext"; import { useSceneContext } from "./sceneContext";
@@ -11,13 +11,17 @@ import Collaboration from "../collaboration/collaboration";
import useModuleStore from "../../store/ui/useModuleStore"; import useModuleStore from "../../store/ui/useModuleStore";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { getUserData } from "../../functions/getUserData"; import { getUserData } from "../../functions/getUserData";
import { useLoadingProgress } from "../../store/builder/store"; import { useCreateNewWindow, useLoadingProgress } from "../../store/builder/store";
import { useSocketStore } from "../../store/socket/useSocketStore"; import { useSocketStore } from "../../store/socket/useSocketStore";
import { Color, SRGBColorSpace } from "three"; import { Color, SRGBColorSpace } from "three";
import { compressImage } from "../../utils/compressImage"; import { compressImage } from "../../utils/compressImage";
import { ALPHA_ORG } from "../../pages/Dashboard"; import { ALPHA_ORG } from "../../pages/Dashboard";
export default function Scene({ layout }: { readonly layout: "Main Layout" | "Comparison Layout" }) { export default function Scene({
layout,
}: {
readonly layout: "Main Layout" | "Comparison Layout";
}) {
const map = useMemo( const map = useMemo(
() => [ () => [
{ name: "forward", keys: ["ArrowUp", "w", "W"] }, { name: "forward", keys: ["ArrowUp", "w", "W"] },
@@ -35,11 +39,16 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co
const { projectSocket } = useSocketStore(); const { projectSocket } = useSocketStore();
const { activeModule } = useModuleStore(); const { activeModule } = useModuleStore();
const { loadingProgress } = useLoadingProgress(); const { loadingProgress } = useLoadingProgress();
const { setWindowRendered } = useCreateNewWindow();
useEffect(() => { useEffect(() => {
if (!projectId || loadingProgress !== 0) return; if (!projectId || loadingProgress !== 0) return;
const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0]; const canvas = document.getElementById("sceneCanvas")?.getElementsByTagName("canvas")[0];
if (!canvas || !(layoutType === "default" || (layoutType === "useCase" && organization === ALPHA_ORG))) return; if (
!canvas ||
!(layoutType === "default" || (layoutType === "useCase" && organization === ALPHA_ORG))
)
return;
compressImage(canvas.toDataURL("image/png")).then((screenshotDataUrl) => { compressImage(canvas.toDataURL("image/png")).then((screenshotDataUrl) => {
const updateProjects = { const updateProjects = {
projectId, projectId,
@@ -64,11 +73,21 @@ export default function Scene({ layout }: { readonly layout: "Main Layout" | "Co
onContextMenu={(e) => { onContextMenu={(e) => {
e.preventDefault(); e.preventDefault();
}} }}
resize={{ polyfill: ResizeObserver }}
style={{ width: "100vw", height: "100vh", background: "#202020" }}
performance={{ min: 0.9, max: 1.0 }} performance={{ min: 0.9, max: 1.0 }}
onCreated={(e) => { onCreated={(e) => {
e.scene.background = layout === "Main Layout" ? null : new Color(0x19191d); e.scene.background = layout === "Main Layout" ? null : new Color(0x19191d);
}} }}
gl={{ outputColorSpace: SRGBColorSpace, powerPreference: "high-performance", antialias: true, preserveDrawingBuffer: true }} gl={{
outputColorSpace: SRGBColorSpace,
powerPreference: "high-performance",
antialias: true,
preserveDrawingBuffer: true,
}}
onPointerEnter={() => {
setWindowRendered(layout);
}}
> >
<Setup /> <Setup />
<Collaboration /> <Collaboration />

View File

@@ -171,6 +171,7 @@ const Project: React.FC = () => {
<MainScene /> <MainScene />
</SceneProvider> </SceneProvider>
<SceneProvider layout="Comparison Layout" layoutType={layoutType}> <SceneProvider layout="Comparison Layout" layoutType={layoutType}>
<ComparisonScene /> <ComparisonScene />
</SceneProvider> </SceneProvider>
{selectedUser && <FollowPerson />} {selectedUser && <FollowPerson />}

View File

@@ -138,7 +138,8 @@ export const useDrieTemp = create<any>((set: any) => ({
export const useDrieUIValue = create<any>((set: any) => ({ export const useDrieUIValue = create<any>((set: any) => ({
drieUIValue: { touch: null, temperature: null, humidity: null }, drieUIValue: { touch: null, temperature: null, humidity: null },
setDrieUIValue: (x: any) => set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })), setDrieUIValue: (x: any) =>
set((state: any) => ({ drieUIValue: { ...state.drieUIValue, ...x } })),
setTouch: (value: any) => setTouch: (value: any) =>
set((state: any) => ({ set((state: any) => ({
@@ -462,9 +463,18 @@ interface DecalStore {
// Create the Zustand store with types // Create the Zustand store with types
export const useDecalStore = create<DecalStore>((set) => ({ export const useDecalStore = create<DecalStore>((set) => ({
selectedSubCategory: "Safety", selectedSubCategory: "Safety",
setSelectedSubCategory: (subCategory: string | null) => set({ selectedSubCategory: subCategory }), setSelectedSubCategory: (subCategory: string | null) =>
set({ selectedSubCategory: subCategory }),
})); }));
export const comparsionMaterialData = create<any>((set: any) => ({ export const comparsionMaterialData = create<any>((set: any) => ({
materialData: [], materialData: [],
setMaterialData: (x: any) => set({ materialData: x }), setMaterialData: (x: any) => set({ materialData: x }),
})); }));
export const useCreateNewWindow = create<any>((set: any) => ({
createNewWindow: false,
setCreateNewWindow: (x: any) => set({ createNewWindow: x }),
windowRendered: "",
setWindowRendered: (x: any) => set({ windowRendered: x }),
}));