import * as THREE from "three"; import { Block } from "@/core/libs/three-mesh-ui/src/three-mesh-ui.js"; import App from "@/core/app/App"; import { findUIPanelRoot } from "@/utils/scene/uipanel"; import { UIPanelElementController, UIPanelElementInit, UIPanelNode, extractUIPanelProps, normalizeUIPanelElementOptions, } from "./UIPanelElementBase"; const BlockBase = Block as unknown as new (options?: Record) => THREE.Object3D; export default class UIPanelBlock extends BlockBase { type = "UIPanelBlock"; isUIPanelElement = true; options: UIPanelNode; declare set: (options: Record) => void; private controller: UIPanelElementController; private handleAdded = () => { const parent = this.parent as any; if (!parent || !this.isValidParent(parent)) { parent?.remove?.(this); return; } const panel = findUIPanelRoot(this) as any; if (!panel) return; this.controller.updateRootId(panel.uuid); panel.registerElement?.(this); this.traverse(child => { if (child === this) return; const anyChild = child as any; if (!anyChild.isUIPanelElement || !anyChild.options?.id) return; panel.registerElement?.(anyChild); }); panel.requestUpdate?.(); }; private handleRemoved = () => { const rootId = (this as any).metadata?.__uiPanelRootId; const panel = rootId ? App.getObjectByUuid(rootId) : null; panel?.unregisterElement?.(this); }; constructor(options: Partial = {}, init: UIPanelElementInit = {}) { const normalized = normalizeUIPanelElementOptions(options, "block", "Block"); const { props } = extractUIPanelProps(normalized); super(props); this.options = normalized; this.name = normalized.name || "Block"; this.controller = new UIPanelElementController(this, this.options, this.type, init); this.addEventListener("added", this.handleAdded); this.addEventListener("removed", this.handleRemoved); } updateRootId(rootId: string) { this.controller.updateRootId(rootId); } updateOptions(options: Partial) { if (options.name !== undefined) { this.options.name = options.name; this.name = options.name || this.name; } if (options.props) this.setProps(options.props); if (options.states !== undefined) this.setStates(options.states); } setProps(patch: Record) { this.controller.setProps(patch); } setStates(states?: Record>) { this.controller.setStates(states); const rootId = (this as any).metadata?.__uiPanelRootId; const panel = rootId ? App.getObjectByUuid(rootId) : null; panel?.refreshInteraction?.(); } applyState(state: string | null) { this.controller.applyState(state); } hasInteractiveState() { return this.controller.hasInteractiveState(); } dispose() { this.controller.dispose(); } toJSON(meta?: THREE.JSONMeta) { const snapshot = this.controller.detachInternalMeshes(); const data = super.toJSON(meta) as any; this.controller.restoreInternalMeshes(snapshot); data.object.type = this.type; const options = JSON.parse(JSON.stringify(this.options)) as UIPanelNode; options.name = this.name; data.object.options = options; return data; } static fromJSON(json: { options: UIPanelNode }, init: UIPanelElementInit = {}) { return new UIPanelBlock(json.options, init); } private isValidParent(parent: any) { return parent?.type === "UIPanel" || parent?.type === "UIPanelBlock"; } }