Includes room interior with floor, walls, glass you can see through, and all uncommitted production changes that were running live. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
654 lines
23 KiB
JavaScript
654 lines
23 KiB
JavaScript
import { Canvas, useThree } from "@react-three/fiber";
|
|
import axios from "axios";
|
|
// import { EffectComposer } from "@react-three/postprocessing";
|
|
import DoorHole from "./logic/DoorHole";
|
|
import { Suspense, useState, useEffect } from "react";
|
|
import Preloader from "./logic/preloader";
|
|
import Door from "./logic/UI/door";
|
|
import Design from "./logic/UI/design";
|
|
import Information from "./logic/UI/information";
|
|
import * as THREE from "three";
|
|
import { MyContext } from "./logic/data/contextapi";
|
|
import Extra from "./logic/UI/extra";
|
|
import Samenstling from "./logic/UI/samenstling";
|
|
import Arrow from "./logic/UI/arrow";
|
|
import { DirectionalLightHelper, CameraHelper } from "three";
|
|
import React, { useRef } from "react";
|
|
export default function Endpoint() {
|
|
const [json, setJson] = useState({ Data: [] });
|
|
|
|
const [step, setStep] = useState("door");
|
|
const [type, settype] = useState("Taatsdeur");
|
|
const [door, setdoor] = useState("3panel");
|
|
const [frameSize, setFrameSize] = useState(0.125);
|
|
const [glassType, setGlassType] = useState(0x111111);
|
|
const [frameType, setFrameType] = useState(
|
|
"./images/doortypes/RAL 9005 fijn structuur.png"
|
|
);
|
|
const [width, setWidth] = useState(90); // State for width
|
|
const [height, setHeight] = useState(250); // State for height
|
|
const [holeWidth, setHoleWidth] = useState(90);
|
|
const [doorConfig, setDoorConfig] = useState("enkele");
|
|
const [sidePannel, setSidePannel] = useState("geen");
|
|
const [sidePannelConfig, setSidePannelConfig] = useState("eigen maat");
|
|
const [sidePannelSize, setSidePannelSize] = useState(width);
|
|
const [stalenType, setStalenType] = useState("tussen");
|
|
const [stalenPart, setStalenPart] = useState(1);
|
|
const [handle, setHandle] = useState(1);
|
|
const [maxWidth, setMaxWidth] = useState(105);
|
|
const [prwType, setPrwType] = useState("Enkele");
|
|
const [prwImage, setPrwImage] = useState(
|
|
"./images/doortypes/enkele deur.png"
|
|
);
|
|
const [extraOptions, setExtraOptions] = useState([
|
|
"Meetservice",
|
|
"Montage service",
|
|
]);
|
|
const [count, setCount] = useState(1);
|
|
|
|
const [coverSheetSteel, setCoverSheetSteel] = useState(false);
|
|
const [coverLearn, setCoverLearn] = useState(false);
|
|
const [coverWoodliness, setCoverWoodliness] = useState(false);
|
|
const [coverMirrors, setCoverMirrors] = useState(false);
|
|
const [coverCylinderLockKey, setCoverCylinderLockKey] = useState(false);
|
|
const [coverDontKnow, setCoverDontKnow] = useState(false);
|
|
|
|
const [handleSheetSteel, setHandleSheetSteel] = useState(false);
|
|
const [handleLearn, setHandleLearn] = useState(false);
|
|
const [handleWoodliness, setHandleWoodliness] = useState(false);
|
|
const [handleMirrors, setHandleMirrors] = useState(false);
|
|
const [handleCylinderLockKey, setHandleCylinderLockKey] = useState(false);
|
|
|
|
const [standardBlack, setStandardBlack] = useState(false);
|
|
const [alternativeRALColour, setAlternativeRALColour] = useState(false);
|
|
const [metallicCoating, setMetallicCoating] = useState(false);
|
|
|
|
const [bright, setBright] = useState(false);
|
|
const [canaleOrCrepi, setCanaleOrCrepi] = useState(false);
|
|
|
|
const [fireResistant, setFireResistant] = useState(false);
|
|
const [outdoorPlacement, setOutdoorPlacement] = useState(false);
|
|
|
|
const [maxHeight, setMaxHeight] = useState(0);
|
|
const [contentHeight, setContentHeight] = useState("calc(100vh - 320px)");
|
|
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const [inprogress, setInprogress] = useState(false);
|
|
|
|
const [bubbles, setBubbles] = useState();
|
|
|
|
const [attachDesign, setAttachDesign] = useState(null);
|
|
|
|
const [colorPickerOpened, setColorPickerOpened] = useState(false);
|
|
|
|
const [customFrameType, setCustomFrameType] = useState({name: "", color: ""});
|
|
|
|
const [compositionImage, setCompositionImage] = useState("https://config.livingsteel.nl/images/doortypes/3%20panel.png");
|
|
|
|
const [glContext, setGlContext] = useState(null);
|
|
const [sceneContext, setSceneContext] = useState(null); // Store the Scene context
|
|
const [cameraContext, setCameraContext] = useState(null); // Store the Camera context
|
|
|
|
// Calculate the maximum height based on the available screen height
|
|
useEffect(() => {
|
|
const userAgent = navigator.userAgent || navigator.vendor || window.opera;
|
|
|
|
const isAndroid = /android/i.test(userAgent);
|
|
const isIOS = /iPad|iPhone|iPod/.test(userAgent) && !window.MSStream;
|
|
const calculateMaxHeight = () => {
|
|
let availableHeight;
|
|
if (window.innerWidth > 800) {
|
|
const screenHeight = window.innerHeight;
|
|
// Adjust maxHeight to leave space for other elements like headers or footers
|
|
availableHeight = screenHeight - 100; // Adjust this value as needed
|
|
} else {
|
|
setContentHeight(
|
|
`calc(100vh - 25vh - ${isAndroid ? "270px" : "310px"})`
|
|
);
|
|
const screenHeight = window.innerHeight * 0.65;
|
|
// Adjust maxHeight to leave space for other elements like headers or footers 30
|
|
availableHeight = screenHeight + 210; // Adjust this value as needed
|
|
}
|
|
setMaxHeight(availableHeight);
|
|
};
|
|
|
|
fetchBubbles();
|
|
|
|
calculateMaxHeight(); // Call once initially
|
|
window.addEventListener("resize", calculateMaxHeight); // Recalculate on window resize
|
|
|
|
return () => {
|
|
window.removeEventListener("resize", calculateMaxHeight); // Cleanup
|
|
};
|
|
}, []);
|
|
|
|
const fetchBubbles = async () => {
|
|
try {
|
|
const response = await axios.get(
|
|
"https://api.config-fencing.com/api/get-bubbles"
|
|
);
|
|
setBubbles(response.data.bubbles);
|
|
} catch (error) {
|
|
console.error("Error fetching bubbles:", error);
|
|
}
|
|
};
|
|
// Create a ref for the SpotLight
|
|
const spotLightRef = useRef();
|
|
return (
|
|
<>
|
|
<MyContext.Provider
|
|
value={{
|
|
json,
|
|
setJson,
|
|
setStep,
|
|
sidePannel,
|
|
setSidePannel,
|
|
type,
|
|
settype,
|
|
door,
|
|
setdoor,
|
|
width,
|
|
setWidth,
|
|
height,
|
|
setHeight,
|
|
holeWidth,
|
|
setHoleWidth,
|
|
doorConfig,
|
|
setDoorConfig,
|
|
prwType,
|
|
setPrwType,
|
|
prwImage,
|
|
setPrwImage,
|
|
count,
|
|
setCount,
|
|
frameSize,
|
|
setFrameSize,
|
|
glassType,
|
|
setGlassType,
|
|
frameType,
|
|
setFrameType,
|
|
sidePannelConfig,
|
|
setSidePannelConfig,
|
|
sidePannelSize,
|
|
setSidePannelSize,
|
|
stalenType,
|
|
setStalenType,
|
|
stalenPart,
|
|
setStalenPart,
|
|
handle,
|
|
setHandle,
|
|
maxWidth,
|
|
setMaxWidth,
|
|
coverSheetSteel,
|
|
setCoverSheetSteel,
|
|
coverLearn,
|
|
setCoverLearn,
|
|
coverWoodliness,
|
|
setCoverWoodliness,
|
|
coverMirrors,
|
|
setCoverMirrors,
|
|
coverCylinderLockKey,
|
|
setCoverCylinderLockKey,
|
|
coverDontKnow,
|
|
setCoverDontKnow,
|
|
handleSheetSteel,
|
|
setHandleSheetSteel,
|
|
handleLearn,
|
|
setHandleLearn,
|
|
handleWoodliness,
|
|
setHandleWoodliness,
|
|
handleMirrors,
|
|
setHandleMirrors,
|
|
handleCylinderLockKey,
|
|
setHandleCylinderLockKey,
|
|
standardBlack,
|
|
setStandardBlack,
|
|
alternativeRALColour,
|
|
setAlternativeRALColour,
|
|
metallicCoating,
|
|
setMetallicCoating,
|
|
bright,
|
|
setBright,
|
|
canaleOrCrepi,
|
|
setCanaleOrCrepi,
|
|
fireResistant,
|
|
setFireResistant,
|
|
outdoorPlacement,
|
|
setOutdoorPlacement,
|
|
setExtraOptions,
|
|
extraOptions,
|
|
open,
|
|
setOpen,
|
|
inprogress,
|
|
setInprogress,
|
|
bubbles,
|
|
setBubbles,
|
|
attachDesign,
|
|
setAttachDesign,
|
|
colorPickerOpened,
|
|
setColorPickerOpened,
|
|
customFrameType,
|
|
setCustomFrameType,
|
|
compositionImage,
|
|
setCompositionImage,
|
|
glContext,
|
|
sceneContext,
|
|
cameraContext,
|
|
}}
|
|
>
|
|
<div style={{ width: "100vw", height: "100svh", overflow: "hidden" }}>
|
|
<div className="body-can-2" style={{}}>
|
|
<div className="body-can-2-1" style={{}}>
|
|
<div className="body-can-2-1-1" style={{}}>
|
|
<Suspense fallback={<Preloader />}>
|
|
<Canvas
|
|
frameloop="demand"
|
|
gl={{
|
|
antialias: true,
|
|
alpha: true,
|
|
toneMapping: THREE.SRGBColorSpace,
|
|
}}
|
|
camera={{ fov: 53 }}
|
|
onCreated={({ gl, scene, camera }) => {
|
|
setGlContext(gl);
|
|
setSceneContext(scene);
|
|
setCameraContext(camera);
|
|
}}
|
|
// shadows // Enable shadows
|
|
>
|
|
{/* Ambient light for basic illumination */}
|
|
|
|
<DoorHole />
|
|
</Canvas>
|
|
</Suspense>
|
|
</div>
|
|
<div className="body-can-2-1-2" style={{ overflowY: "hidden" }}>
|
|
<div
|
|
className="quot"
|
|
style={{
|
|
backgroundColor: "#bdc79d",
|
|
display: "flex",
|
|
alignItems: "center",
|
|
padding: "10px",
|
|
}}
|
|
>
|
|
{/* <span
|
|
style={{
|
|
color: "white",
|
|
fontWeight: "bold",
|
|
marginLeft: "30px",
|
|
}}
|
|
>
|
|
Prijs incl. BTW
|
|
</span> */}
|
|
{/* <div
|
|
style={{
|
|
backgroundColor: "white",
|
|
borderRadius: "20px",
|
|
width: "auto",
|
|
padding: "5px",
|
|
margin: "0 0 0 10px",
|
|
}}
|
|
>
|
|
<span
|
|
className="body-txt"
|
|
style={{}}
|
|
onClick={() => {
|
|
setStep("door");
|
|
}}
|
|
>
|
|
€ 2200,00
|
|
</span>
|
|
</div> */}
|
|
</div>
|
|
<div
|
|
className={`step-content ${
|
|
step === "door" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{
|
|
maxHeight: `${maxHeight}px`,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
marginTop: "auto",
|
|
}}
|
|
onClick={() => {
|
|
setStep("door");
|
|
}}
|
|
>
|
|
<button
|
|
className={`body-btn ${
|
|
step === "door" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{}}
|
|
>
|
|
1
|
|
</button>
|
|
<span
|
|
className={`body-txt ${
|
|
step === "door" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{ opacity: step === "door" ? 1 : 0.4 }}
|
|
onClick={() => {
|
|
setStep("door");
|
|
}}
|
|
>
|
|
DEURMAAT & TYPE
|
|
</span>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
top: "5px",
|
|
right: "20px",
|
|
}}
|
|
>
|
|
<input
|
|
type="radio"
|
|
checked={step === "door"}
|
|
readOnly
|
|
onChange={() => setStep("door")}
|
|
className={`radio ${
|
|
step === "door" ? "expand" : "collapsed"
|
|
}`}
|
|
/>
|
|
{/* <Arrow direction={step === 'door' ? 'down' : 'up'} /> */}
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
overflowY: "auto",
|
|
padding: step == "door" ? "10px" : 0,
|
|
height: step == "door" ? contentHeight : 0,
|
|
opacity: step == "door" ? 1 : 0,
|
|
transition:
|
|
"height 0.3s ease-in-out, opacity 0.5s ease-in-out",
|
|
}}
|
|
>
|
|
<Door />
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`step-content ${
|
|
step === "samenstling" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{
|
|
maxHeight: `${maxHeight}px`,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
}}
|
|
onClick={() => {
|
|
setStep("samenstling");
|
|
}}
|
|
>
|
|
<button
|
|
className={`body-btn ${
|
|
step === "samenstling" ? "expand" : "collapsed"
|
|
}`}
|
|
>
|
|
2
|
|
</button>
|
|
<span
|
|
className={`body-txt ${
|
|
step === "samenstling" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{ opacity: step === "samenstling" ? 1 : 0.4 }}
|
|
onClick={() => {
|
|
setStep("samenstling");
|
|
}}
|
|
>
|
|
Samenstelling
|
|
</span>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
top: "5px",
|
|
right: "20px",
|
|
}}
|
|
>
|
|
<input
|
|
type="radio"
|
|
checked={step === "samenstling"}
|
|
readOnly
|
|
onChange={() => setStep("samenstling")}
|
|
className={`radio ${
|
|
step === "samenstling" ? "expand" : "collapsed"
|
|
}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
overflowY: "auto",
|
|
padding: step == "samenstling" ? "10px" : 0,
|
|
height: step == "samenstling" ? contentHeight : 0,
|
|
opacity: step == "samenstling" ? 1 : 0,
|
|
transition:
|
|
"height 0.3s ease-in-out, opacity 0.5s ease-in-out",
|
|
}}
|
|
>
|
|
<Samenstling />
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`step-content ${
|
|
step === "design" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{
|
|
maxHeight: `${maxHeight}px`,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
}}
|
|
onClick={() => {
|
|
setStep("design");
|
|
}}
|
|
>
|
|
<button
|
|
className={`body-btn ${
|
|
step === "design" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{}}
|
|
>
|
|
3
|
|
</button>
|
|
<span
|
|
className={`body-txt ${
|
|
step === "design" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{ opacity: step === "design" ? 1 : 0.4 }}
|
|
onClick={() => {
|
|
setStep("design");
|
|
}}
|
|
>
|
|
Design, Kleur & GLAS
|
|
</span>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
top: "5px",
|
|
right: "20px",
|
|
}}
|
|
>
|
|
<input
|
|
type="radio"
|
|
checked={step === "design"}
|
|
readOnly
|
|
onChange={() => setStep("design")}
|
|
className={`radio ${
|
|
step === "design" ? "expand" : "collapsed"
|
|
}`}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
backgroundColor: "#fff",
|
|
overflowY: "auto",
|
|
padding: step == "design" ? "10px" : 0,
|
|
height: step == "design" ? contentHeight : 0,
|
|
opacity: step == "design" ? 1 : 0,
|
|
transition:
|
|
"height 0.3s ease-in-out, opacity 0.5s ease-in-out",
|
|
}}
|
|
>
|
|
<Design />
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`step-content ${
|
|
step === "extra" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{
|
|
maxHeight: `${maxHeight}px`,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
}}
|
|
onClick={() => {
|
|
setStep("extra");
|
|
}}
|
|
>
|
|
<button
|
|
className={`body-btn ${
|
|
step === "extra" ? "expand" : "collapsed"
|
|
}`}
|
|
>
|
|
4
|
|
</button>
|
|
<span
|
|
className={`body-txt ${
|
|
step === "extra" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{ opacity: step === "extra" ? 1 : 0.4 }}
|
|
onClick={() => {
|
|
setStep("extra");
|
|
}}
|
|
>
|
|
Extra Opties
|
|
</span>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
top: "5px",
|
|
right: "20px",
|
|
}}
|
|
>
|
|
<input
|
|
type="radio"
|
|
checked={step === "extra"}
|
|
readOnly
|
|
onChange={() => setStep("extra")}
|
|
className={`radio ${
|
|
step === "extra" ? "expand" : "collapsed"
|
|
}`}
|
|
/>
|
|
{/* <Arrow direction={step === 'extra' ? 'down' : 'up'} /> */}
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
overflowY: "auto",
|
|
padding: step == "extra" ? "10px" : 0,
|
|
height: step == "extra" ? contentHeight : 0,
|
|
opacity: step == "extra" ? 1 : 0,
|
|
transition:
|
|
"height 0.3s ease-in-out, opacity 0.5s ease-in-out",
|
|
}}
|
|
>
|
|
<Extra />
|
|
</div>
|
|
</div>
|
|
<div
|
|
className={`pd step-content ${
|
|
step === "information" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{
|
|
maxHeight: `${maxHeight}px`,
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
position: "relative",
|
|
cursor: "pointer",
|
|
}}
|
|
onClick={() => {
|
|
setStep("information");
|
|
}}
|
|
>
|
|
<button
|
|
className={`body-btn ${
|
|
step === "information" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{}}
|
|
>
|
|
5
|
|
</button>
|
|
<span
|
|
className={`body-txt ${
|
|
step === "information" ? "expand" : "collapsed"
|
|
}`}
|
|
style={{ opacity: step === "information" ? 1 : 0.4 }}
|
|
onClick={() => {
|
|
setStep("information");
|
|
}}
|
|
>
|
|
Order informatie
|
|
</span>
|
|
<div
|
|
style={{
|
|
position: "absolute",
|
|
top: "5px",
|
|
right: "20px",
|
|
}}
|
|
>
|
|
<input
|
|
type="radio"
|
|
checked={step === "information"}
|
|
readOnly
|
|
onChange={() => setStep("information")}
|
|
className={`radio ${
|
|
step === "information" ? "expand" : "collapsed"
|
|
}`}
|
|
/>
|
|
{/* <Arrow
|
|
direction={step === 'information' ? 'down' : 'up'}
|
|
/> */}
|
|
</div>
|
|
</div>
|
|
<div
|
|
style={{
|
|
backgroundColor: "#fff",
|
|
overflowY: "auto",
|
|
padding: step == "information" ? "10px" : 0,
|
|
height: step == "information" ? contentHeight : 0,
|
|
opacity: step == "information" ? 1 : 0,
|
|
transition:
|
|
"height 0.3s ease-in-out, opacity 0.5s ease-in-out",
|
|
}}
|
|
>
|
|
<Information />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</MyContext.Provider>
|
|
</>
|
|
);
|
|
}
|