Finish virtual profile

This commit is contained in:
Fuhrmann 2025-03-17 13:54:02 +01:00
parent 46db218492
commit 913af8fba6
3 changed files with 298 additions and 132 deletions

View file

@ -1,6 +1,14 @@
"use client";
import { ChangeEvent, ReactNode, useContext, useRef, useState } from "react";
import {
ChangeEvent,
ReactNode,
forwardRef,
useContext,
useImperativeHandle,
useRef,
useState,
} from "react";
import {
SceneViewContext,
@ -15,7 +23,7 @@ function Toggle({
defaultChecked,
}: {
title: string;
onChange: (e: any) => void;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
defaultChecked?: boolean;
}) {
return (
@ -35,68 +43,95 @@ function Toggle({
);
}
function Accordion({
children,
title,
}: {
children?: ReactNode;
enum Position {
Start,
Center,
End,
}
interface AccordionRef {
open: (b: boolean) => void;
}
interface AccordionProps {
title: string;
}) {
const [expanded, setExpanded] = useState<boolean>(true);
const accordionBodyRef = useRef<HTMLDivElement>(null);
position: Position;
open: boolean;
children: ReactNode;
}
function handleClick() {
if (!accordionBodyRef.current) return;
const Accordion = forwardRef<AccordionRef, AccordionProps>(
({ children, title, position, open }, ref) => {
const [expanded, setExpanded] = useState<boolean>(open);
const accordionBodyRef = useRef<HTMLDivElement>(null);
accordionBodyRef.current.classList.toggle("hidden");
setExpanded(!expanded);
}
useImperativeHandle(ref, () => ({
open: (b: boolean) => setExpanded(b),
}));
return (
<div>
<h2 id="accordion-collapse-heading-1">
<button
type="button"
className="flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b-0 border-gray-200 rounded-t focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3 hover:cursor-pointer"
data-accordion-target="#accordion-collapse-body-1"
aria-expanded={expanded ? "true" : "false"}
aria-controls="accordion-collapse-body-1"
onClick={handleClick}
>
<span>{title}</span>
<svg
data-accordion-icon
className="w-3 h-3 rotate-180 shrink-0"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
function handleClick() {
if (!accordionBodyRef.current) return;
setExpanded(!expanded);
}
const className =
position === Position.Center
? "flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b border-gray-200 focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3 hover:cursor-pointer"
: "flex items-center justify-between w-full p-5 font-medium rtl:text-right text-gray-500 border border-b border-gray-200 rounded-t focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-800 dark:border-gray-700 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 gap-3 hover:cursor-pointer";
return (
<div>
<h2 id="accordion-collapse-heading-1">
<button
type="button"
className={className}
data-accordion-target="#accordion-collapse-body-1"
aria-expanded={expanded ? "true" : "false"}
aria-controls="accordion-collapse-body-1"
onClick={handleClick}
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5 5 1 1 5"
/>
</svg>
</button>
</h2>
<div
id="accordion-collapse-body-1"
ref={accordionBodyRef}
aria-labelledby="accordion-collapse-heading-1"
>
<div className="p-5 border border-gray-200 dark:border-gray-700 dark:bg-gray-900">
{children}
<span>{title}</span>
<svg
data-accordion-icon
className={
expanded ? "w-3 h-3 shrink-0" : "w-3 h-3 rotate-180 shrink-0"
}
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 10 6"
>
<path
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth="2"
d="M9 5 5 1 1 5"
/>
</svg>
</button>
</h2>
<div
id="accordion-collapse-body-1"
ref={accordionBodyRef}
aria-labelledby="accordion-collapse-heading-1"
className={expanded ? "" : "hidden"}
>
<div className="p-5 border border-gray-200 dark:border-gray-700 dark:bg-gray-900">
{children}
</div>
</div>
</div>
</div>
);
}
);
}
);
Accordion.displayName = "Accordion";
export function Form() {
const svgContainerRef = useRef<HTMLDivElement>(null);
const accordionRef1 = useRef<AccordionRef>(null);
const accordionRef0 = useRef<AccordionRef>(null);
const { sceneView } = useContext(SceneViewContext) as SceneViewContextType;
function handleChange() {
@ -133,11 +168,10 @@ export function Form() {
if (!sceneView) return;
if ((e.target as HTMLInputElement).checked) {
sceneView.enableRaycaster();
sceneView.addEventListener("svg-created", handleSVGCreated);
// Enable raycaster with callback to handle svg element
sceneView.enableRaycaster(handleSVGCreated);
} else {
sceneView.disableRaycaster();
sceneView.removeEventListener("svg-created", handleSVGCreated);
}
}
@ -149,61 +183,83 @@ export function Form() {
svgContainerRef.current.removeChild(c);
}
svgContainerRef.current.appendChild(e.detail.element);
if (accordionRef0.current) {
accordionRef0.current.open(false);
}
if (accordionRef1.current) {
accordionRef1.current.open(true);
}
}
return (
<div className="w-full flex flex-col gap-2 overflow-y-auto">
<div className="w-full flex flex-col gap-3 p-4 border border-gray-200 rounded shadow">
<Toggle title="Slicing Box" onChange={handleChange} />
<Toggle title="Drilling Profiler" onChange={handleDrilling} />
<Toggle title="Coordinate Grid" onChange={handleChangeCG} />
<Toggle title="Wireframe" onChange={handleChangeWireframe} />
<Toggle
title="Topography (OSM)"
onChange={handleChangeTopography}
defaultChecked
/>
<Accordion title="Layers">
{
<div className="flex flex-col gap-2">
{sceneView?.model.children.map((child) => {
const key = `toggle-visibility-${child.name}`;
const color = `#${(
(child as Mesh).material as MeshStandardMaterial
).color.getHexString()}`;
const visible = (child as Mesh).visible;
<div className="w-full max-h-full flex flex-col gap-2">
<div className="w-full h-full flex flex-col gap-3 p-4 border border-gray-200 rounded shadow">
<div className="border border-gray-200 rounded grid grid-cols-2 gap-y-2 p-2">
<Toggle title="Slicing Box" onChange={handleChange} />
<Toggle title="Virtual Profile" onChange={handleDrilling} />
<Toggle title="Coordinate Grid" onChange={handleChangeCG} />
<Toggle title="Wireframe" onChange={handleChangeWireframe} />
<Toggle
title="Topography (OSM)"
onChange={handleChangeTopography}
defaultChecked
/>
</div>
<div className="overflow-y-auto">
<Accordion
title="Layers"
position={Position.Start}
open={true}
ref={accordionRef0}
>
{
<div className="flex flex-col gap-2">
{sceneView?.model.children.map((child) => {
const key = `toggle-visibility-${child.name}`;
const color = `#${(
(child as Mesh).material as MeshStandardMaterial
).color.getHexString()}`;
const visible = (child as Mesh).visible;
return (
<div
key={key}
className="flex items-center justify-start gap-2.5 border-b border-gray-200 py-1"
>
<span
className="inline-block w-5 h-5 flex-none rounded"
style={{
backgroundColor: color,
}}
></span>
<input
id={key}
type="checkbox"
onChange={() => handleCheckboxChange(child.name)}
className="hover:cursor-pointer"
defaultChecked={visible ? true : false}
/>
<label
htmlFor={key}
className="antialiased font-light text-gray-700"
return (
<div
key={key}
className="flex items-center justify-start gap-2.5 border-b border-gray-200 py-1"
>
{child.name}
</label>
</div>
);
})}
</div>
}
</Accordion>
<div ref={svgContainerRef}> </div>
<span
className="inline-block w-5 h-5 flex-none rounded"
style={{
backgroundColor: color,
}}
></span>
<input
id={key}
type="checkbox"
onChange={() => handleCheckboxChange(child.name)}
className="hover:cursor-pointer"
defaultChecked={visible ? true : false}
/>
<label
htmlFor={key}
className="antialiased font-light text-gray-700"
>
{child.name}
</label>
</div>
);
})}
</div>
}
</Accordion>
<Accordion
title="Virtual Profile"
position={Position.Center}
open={false}
ref={accordionRef1}
>
<div ref={svgContainerRef}> </div>
</Accordion>
</div>
</div>
</div>
);