feat: Enhance vehicle simulation with draggable path points and interactive controls
This commit is contained in:
@@ -1,208 +1,220 @@
|
||||
import { useParams } from "react-router-dom";
|
||||
import React, { useState, useRef, useEffect, Suspense } from "react";
|
||||
import {
|
||||
CompareLayoutIcon,
|
||||
LayoutIcon,
|
||||
ResizerIcon,
|
||||
} from "../../icons/SimulationIcons";
|
||||
import {
|
||||
useLoadingProgress,
|
||||
useSaveVersion,
|
||||
} from "../../../store/builder/store";
|
||||
import { CompareLayoutIcon, LayoutIcon, ResizerIcon } from "../../icons/SimulationIcons";
|
||||
import { useLoadingProgress, useIsComparing } from "../../../store/builder/store";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePlayButtonStore } from "../../../store/ui/usePlayButtonStore";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { getAllProductsApi } from "../../../services/simulation/products/getallProductsApi";
|
||||
import Search from "../inputs/Search";
|
||||
import OuterClick from "../../../utils/outerClick";
|
||||
import Scene from "../../../modules/scene/scene";
|
||||
import { useComparisonProduct } from "../../../store/simulation/useSimulationStore";
|
||||
import { usePlayButtonStore } from "../../../store/usePlayButtonStore";
|
||||
import { useVersionHistoryStore } from "../../../store/builder/useVersionHistoryStore";
|
||||
import { useVersionContext } from "../../../modules/builder/version/versionContext";
|
||||
import { useSceneContext } from "../../../modules/scene/sceneContext";
|
||||
import { getAllProductsApi } from "../../../services/simulation/products/getallProductsApi";
|
||||
import { useParams } from "react-router-dom";
|
||||
import useRestStates from "../../../hooks/useResetStates";
|
||||
|
||||
import { getVersionHistoryApi } from "../../../services/factoryBuilder/versionControl/getVersionHistoryApi";
|
||||
|
||||
const CompareLayOut = () => {
|
||||
const { clearComparisonProduct, comparisonProduct, setComparisonProduct } = useComparisonProduct();
|
||||
const { productStore } = useSceneContext();
|
||||
const { products } = productStore();
|
||||
const { versionHistory } = useVersionHistoryStore();
|
||||
const { selectedVersionStore } = useVersionContext();
|
||||
const { selectedVersion, setSelectedVersion, clearSelectedVersion } = selectedVersionStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const [width, setWidth] = useState("50vw");
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [showLayoutDropdown, setShowLayoutDropdown] = useState(false);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const startWidthRef = useRef<number>(0);
|
||||
const startXRef = useRef<number>(0);
|
||||
const { setIsVersionSaved } = useSaveVersion();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { projectId } = useParams();
|
||||
const { clearComparisonProduct, comparisonProduct, setComparisonProduct } = useComparisonProduct();
|
||||
const { versionStore } = useSceneContext();
|
||||
const { versionHistory, selectedVersion, setSelectedVersion, clearSelectedVersion, setVersions } = versionStore();
|
||||
const { setLoadingProgress } = useLoadingProgress();
|
||||
const [width, setWidth] = useState("50vw");
|
||||
const [isResizing, setIsResizing] = useState(false);
|
||||
const [showLayoutDropdown, setShowLayoutDropdown] = useState(false);
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const startWidthRef = useRef<number>(0);
|
||||
const startXRef = useRef<number>(0);
|
||||
const { setIsComparing } = useIsComparing();
|
||||
const { loadingProgress } = useLoadingProgress();
|
||||
const { setIsPlaying } = usePlayButtonStore();
|
||||
const { projectId } = useParams();
|
||||
const { resetStates } = useRestStates();
|
||||
|
||||
useEffect(() => {
|
||||
if (!comparisonProduct) {
|
||||
clearSelectedVersion();
|
||||
}
|
||||
}, [comparisonProduct])
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
if (selectedVersion?.versionId) {
|
||||
resetStates();
|
||||
}
|
||||
};
|
||||
}, [selectedVersion?.versionId]);
|
||||
|
||||
OuterClick({
|
||||
contextClassName: ["displayLayouts-container", "selectLayout"],
|
||||
setMenuVisible: () => setShowLayoutDropdown(false),
|
||||
});
|
||||
useEffect(() => {
|
||||
if (!projectId) return;
|
||||
|
||||
const handleStartResizing = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
startXRef.current = e.clientX;
|
||||
if (wrapperRef.current) {
|
||||
startWidthRef.current = wrapperRef.current.getBoundingClientRect().width;
|
||||
}
|
||||
};
|
||||
getVersionHistoryApi(projectId)
|
||||
.then((data) => {
|
||||
const versions: VersionHistory = [];
|
||||
data.versions.forEach((version: any) => {
|
||||
versions.push({
|
||||
version: version.version,
|
||||
versionId: version.versionId,
|
||||
versionName: version.versionName,
|
||||
versionDescription: version.description,
|
||||
timeStamp: version.createdAt,
|
||||
createdBy: version.createdBy.userName,
|
||||
});
|
||||
});
|
||||
setVersions(versions);
|
||||
})
|
||||
.catch(() => {
|
||||
console.error("Error fetching version history");
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId]);
|
||||
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isResizing || !wrapperRef.current) return;
|
||||
useEffect(() => {
|
||||
if (!comparisonProduct) {
|
||||
clearSelectedVersion();
|
||||
}
|
||||
}, [comparisonProduct]);
|
||||
|
||||
const dx = startXRef.current - e.clientX;
|
||||
const newWidthPx = startWidthRef.current + dx;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const newWidthVw = (newWidthPx / viewportWidth) * 100;
|
||||
OuterClick({
|
||||
contextClassName: ["displayLayouts-container", "selectLayout"],
|
||||
setMenuVisible: () => setShowLayoutDropdown(false),
|
||||
});
|
||||
|
||||
if (newWidthVw <= 10) {
|
||||
setWidth("0px");
|
||||
} else if (newWidthVw <= 90) {
|
||||
setWidth(`${newWidthPx}px`);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMouseUp = () => {
|
||||
if (!isResizing) return;
|
||||
|
||||
if (wrapperRef.current) {
|
||||
const finalWidthPx = wrapperRef.current.getBoundingClientRect().width;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const finalWidthVw = (finalWidthPx / viewportWidth) * 100;
|
||||
|
||||
if (finalWidthVw <= 10) {
|
||||
setWidth("0px");
|
||||
setIsVersionSaved(false);
|
||||
clearComparisonProduct();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
setWidth(`${finalWidthVw}vw`);
|
||||
}
|
||||
}
|
||||
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isResizing) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
document.body.classList.add("resizing-active");
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
document.body.classList.remove("resizing-active");
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isResizing]);
|
||||
|
||||
// Maintain proportional width on window resize
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (!wrapperRef.current || isResizing) return;
|
||||
|
||||
const currentWidth = wrapperRef.current.style.width;
|
||||
if (currentWidth === "0px" || currentWidth.endsWith("vw")) return;
|
||||
|
||||
const pxWidth = parseFloat(currentWidth);
|
||||
const vwWidth = (pxWidth / window.innerWidth) * 100;
|
||||
setWidth(`${vwWidth}vw`);
|
||||
const handleStartResizing = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
setIsResizing(true);
|
||||
startXRef.current = e.clientX;
|
||||
if (wrapperRef.current) {
|
||||
startWidthRef.current = wrapperRef.current.getBoundingClientRect().width;
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [isResizing]);
|
||||
const handleMouseMove = (e: MouseEvent) => {
|
||||
if (!isResizing || !wrapperRef.current) return;
|
||||
|
||||
const handleSelectLayout = (version: Version) => {
|
||||
getAllProductsApi(projectId || '', version.versionId || '').then((data) => {
|
||||
if (data && data.length > 0) {
|
||||
setSelectedVersion(version);
|
||||
setComparisonProduct(data[0].productUuid, data[0].productName);
|
||||
setLoadingProgress(1);
|
||||
}
|
||||
})
|
||||
};
|
||||
const dx = startXRef.current - e.clientX;
|
||||
const newWidthPx = startWidthRef.current + dx;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const newWidthVw = (newWidthPx / viewportWidth) * 100;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`compareLayOut-wrapper ${width === "0px" ? "closed" : ""}`}
|
||||
ref={wrapperRef}
|
||||
style={{ width }}
|
||||
>
|
||||
{loadingProgress == 0 && selectedVersion?.versionId && (
|
||||
<button
|
||||
title="resize-canvas"
|
||||
id="compare-resize-slider-btn"
|
||||
className="resizer"
|
||||
onMouseDown={handleStartResizing}
|
||||
>
|
||||
<ResizerIcon />
|
||||
</button>
|
||||
)}
|
||||
<div className="chooseLayout-container">
|
||||
{selectedVersion?.versionId && (
|
||||
<div className="compare-layout-canvas-container">
|
||||
<Suspense fallback={null}>
|
||||
<Scene layout="Comparison Layout" />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
if (newWidthVw <= 10) {
|
||||
setWidth("0px");
|
||||
} else if (newWidthVw <= 90) {
|
||||
setWidth(`${newWidthPx}px`);
|
||||
}
|
||||
};
|
||||
|
||||
{width !== "0px" &&
|
||||
!selectedVersion?.versionId && ( // Show only if no layout selected
|
||||
<div className="chooseLayout-wrapper">
|
||||
<div className="icon">
|
||||
<CompareLayoutIcon />
|
||||
</div>
|
||||
<div className="value">Choose Version to compare</div>
|
||||
<button
|
||||
className="selectLayout"
|
||||
onClick={() => setShowLayoutDropdown(!showLayoutDropdown)}
|
||||
>
|
||||
Select Version
|
||||
</button>
|
||||
const handleMouseUp = () => {
|
||||
if (!isResizing) return;
|
||||
|
||||
{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>
|
||||
)}
|
||||
if (wrapperRef.current) {
|
||||
const finalWidthPx = wrapperRef.current.getBoundingClientRect().width;
|
||||
const viewportWidth = window.innerWidth;
|
||||
const finalWidthVw = (finalWidthPx / viewportWidth) * 100;
|
||||
|
||||
if (finalWidthVw <= 10) {
|
||||
setWidth("0px");
|
||||
setIsComparing(false);
|
||||
clearComparisonProduct();
|
||||
setIsPlaying(false);
|
||||
} else {
|
||||
setWidth(`${finalWidthVw}vw`);
|
||||
}
|
||||
}
|
||||
|
||||
setIsResizing(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (isResizing) {
|
||||
window.addEventListener("mousemove", handleMouseMove);
|
||||
window.addEventListener("mouseup", handleMouseUp);
|
||||
document.body.classList.add("resizing-active");
|
||||
}
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("mousemove", handleMouseMove);
|
||||
window.removeEventListener("mouseup", handleMouseUp);
|
||||
document.body.classList.remove("resizing-active");
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [isResizing]);
|
||||
|
||||
// Maintain proportional width on window resize
|
||||
useEffect(() => {
|
||||
const handleResize = () => {
|
||||
if (!wrapperRef.current || isResizing) return;
|
||||
|
||||
const currentWidth = wrapperRef.current.style.width;
|
||||
if (currentWidth === "0px" || currentWidth.endsWith("vw")) return;
|
||||
|
||||
const pxWidth = parseFloat(currentWidth);
|
||||
const vwWidth = (pxWidth / window.innerWidth) * 100;
|
||||
setWidth(`${vwWidth}vw`);
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
return () => window.removeEventListener("resize", handleResize);
|
||||
}, [isResizing]);
|
||||
|
||||
const handleSelectLayout = (version: Version) => {
|
||||
getAllProductsApi(projectId || "", version.versionId || "").then((data) => {
|
||||
if (data && data.length > 0) {
|
||||
setSelectedVersion(version);
|
||||
setComparisonProduct(data[0].productUuid, data[0].productName);
|
||||
setLoadingProgress(1);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={`compareLayOut-wrapper ${width === "0px" ? "closed" : ""}`} ref={wrapperRef} style={{ width }}>
|
||||
{loadingProgress == 0 && selectedVersion?.versionId && (
|
||||
<button title="resize-canvas" id="compare-resize-slider-btn" className="resizer" onMouseDown={handleStartResizing}>
|
||||
<ResizerIcon />
|
||||
</button>
|
||||
)}
|
||||
<div className="chooseLayout-container">
|
||||
{selectedVersion?.versionId && (
|
||||
<div className="compare-layout-canvas-container">
|
||||
<Suspense fallback={null}>
|
||||
<Scene layout="Comparison Layout" />
|
||||
</Suspense>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{width !== "0px" &&
|
||||
!selectedVersion?.versionId && ( // Show only if no layout selected
|
||||
<div className="chooseLayout-wrapper">
|
||||
<div className="icon">
|
||||
<CompareLayoutIcon />
|
||||
</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>
|
||||
)}
|
||||
|
||||
{/* Always show after layout is selected */}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Always show after layout is selected */}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default CompareLayOut;
|
||||
|
||||
Reference in New Issue
Block a user