import { ChangeEvent, useContext, useEffect, useId, useState } from "react"; import { useSignals } from "@preact/signals-react/runtime"; import { Transition } from "@headlessui/react"; import { faChevronDown, faChevronUp, faCube, faLock, faLockOpen, faTrash, } from "@fortawesome/pro-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { objectPanel as objectPanelSignals, sidePanelHeight, } from "../../signals"; import { EngineContext } from "~/pages/PageEnigma/contexts/EngineContext"; import { Button, H5, InputVector } from "~/components"; import { QueueNames } from "../../Queue/QueueNames"; import Queue from "~/pages/PageEnigma/Queue/Queue"; import { toTimelineActions } from "../../Queue/toTimelineActions"; import { QueueKeyframe } from "~/pages/PageEnigma/models"; import { editorState } from "~/pages/PageEnigma/signals/engine"; import { twMerge } from "tailwind-merge"; import { EditorStates } from "~/pages/PageEnigma/enums"; import { sanitize } from "./utils/sanitize"; import { objectMismatch } from "~/pages/PageEnigma/comps/ControlPanelSceneObject/utils/objectMismatch"; import { XYZ } from "~/pages/PageEnigma/datastructures/common"; // TODO this will be useful later to fix the bug on leading zeros // const formatNumber = (input: string): number => { // // Convert the input string to a number to remove leading zeros // const num = parseFloat(input); // // Convert the number back to a string with at least two decimal places // const str = num.toFixed(2); // return parseFloat(str); // }; const defaultAxises: Record = { x: "0", y: "0", z: "0", }; export const ControlPanelSceneObject = () => { useSignals(); const { isShowing, currentObject } = objectPanelSignals; const editorEngine = useContext(EngineContext); // const [appUiState] = useContext(AppUiContext); const [isCollapsed, setIsCollapsed] = useState(false); // local translation axises to allow for validation before handing them to the engine const [localPosition, setLocalPosition] = useState(defaultAxises); const [localRotation, setLocalRotation] = useState(defaultAxises); const [localScale, setLocalScale] = useState(defaultAxises); // used to update engine object const [inputsUpdated, setInputsUpdated] = useState(false); const [locked, setLocked] = useState(false); const [color, setColor] = useState("#ffffff"); const colorInputId = useId(); const toggleCollapse = () => { setIsCollapsed(!isCollapsed); }; const currentSceneObject = currentObject.value; const position = currentSceneObject?.objectVectors?.position; const rotation = currentSceneObject?.objectVectors?.rotation; const scale = currentSceneObject?.objectVectors?.scale; function localToEngine(xyz: Record) { return { x: parseFloat(xyz.x), y: parseFloat(xyz.y), z: parseFloat(xyz.z), }; } function engineToLocal(xyz: XYZ) { return { x: xyz.x.toString(), y: xyz.y.toString(), z: xyz.z.toString(), }; } useEffect(() => { if (!inputsUpdated || !editorEngine) { return; } setInputsUpdated(false); editorEngine.setSelectedObject( localToEngine(localPosition), localToEngine(localRotation), localToEngine(localScale), ); }, [inputsUpdated, localPosition, localRotation, localScale, editorEngine]); useEffect(() => { if (!editorEngine || !currentSceneObject) { return; } const vectors = currentSceneObject.objectVectors; // local state relies on strings setLocalPosition(engineToLocal(vectors.position)); setLocalRotation(engineToLocal(vectors.rotation)); setLocalScale(engineToLocal(vectors.scale)); setLocked(editorEngine.isObjectLocked(editorEngine?.selected?.uuid || "")); setColor(editorEngine?.selected?.userData.color); }, [currentSceneObject, editorEngine]); if (!currentSceneObject || editorState.value === EditorStates.PREVIEW) { return null; } const isInvalid = (xyz: Record) => Object.values(xyz).some((value) => { if (value === "" || value === "-" || value === ".") { return true; } return !/^-?[0-9]*(.[0-9]*)?$/.test(value); }); const toggleLock = () => { setLocked((lockState: boolean) => !lockState); editorEngine?.lockUnlockObject(editorEngine?.selected?.uuid || ""); }; const handlePositionChange = (xyz: Record) => { if (isInvalid(xyz)) { setLocalPosition(xyz); return; } const cleanXyz = sanitize(xyz); if (objectMismatch(localPosition, cleanXyz)) { setInputsUpdated(true); } setLocalPosition(xyz); }; const handleRotationChange = (xyz: Record) => { if (isInvalid(xyz)) { setLocalRotation(xyz); return; } const cleanXyz = sanitize(xyz); if (objectMismatch(localRotation, cleanXyz)) { setInputsUpdated(true); } setLocalRotation(xyz); }; const handleScaleChange = (xyz: Record) => { if (isInvalid(xyz)) { setLocalScale(xyz); return; } const cleanXyz = sanitize(xyz); if (objectMismatch(localScale, cleanXyz)) { setInputsUpdated(true); } setLocalScale(xyz); }; const handleOnAddKeyFrame = () => { if (currentSceneObject) { if ( position == null || rotation == null || scale == null || currentSceneObject == null ) { return; } for (const key in editorEngine?.timeline.characters) { const element = editorEngine?.timeline.characters[key]; if (key == currentSceneObject.object_uuid) { currentSceneObject.group = element; break; } } Queue.publish({ queueName: QueueNames.TO_TIMELINE, action: toTimelineActions.ADD_KEYFRAME, data: { group: currentSceneObject.group, object_uuid: currentSceneObject.object_uuid, object_name: currentSceneObject.object_name, version: 1, position: currentSceneObject.objectVectors.position, rotation: currentSceneObject.objectVectors.rotation, scale: currentSceneObject.objectVectors.scale, } as QueueKeyframe, }); } }; const handleDeleteObject = () => { editorEngine?.deleteObject(currentSceneObject.object_uuid); }; const getScale = () => { const height = sidePanelHeight.value; return height < 620 ? height / 620 : 1; }; return (

{currentSceneObject.object_name.charAt(0).toUpperCase() + currentSceneObject.object_name.slice(1)}

Color
) => { editorEngine?.setColor( editorEngine?.selected?.uuid || "", e.target.value, ); setColor(e.target.value); }} type="color" value={color} disabled={locked} />
Location
Rotation
Scale
); };