From 81ede241d5b11aa1070755a0e09a17f66317b9e5 Mon Sep 17 00:00:00 2001 From: wass08 Date: Tue, 24 Feb 2026 14:48:59 +0900 Subject: [PATCH] save site + camera build position --- .../viewer/[id]/viewer-camera-controls.tsx | 32 ++- .../ui/sidebar/panels/site-panel/index.tsx | 196 +++++++++++++++++- 2 files changed, 208 insertions(+), 20 deletions(-) diff --git a/apps/editor/app/viewer/[id]/viewer-camera-controls.tsx b/apps/editor/app/viewer/[id]/viewer-camera-controls.tsx index ab59152b..17f1d745 100644 --- a/apps/editor/app/viewer/[id]/viewer-camera-controls.tsx +++ b/apps/editor/app/viewer/[id]/viewer-camera-controls.tsx @@ -44,23 +44,33 @@ export const ViewerCameraControls = () => { controls.current.setLookAt(30, 30, 30, 0, 0, 0, false) } - if (!targetNodeId) return - const node = nodes[targetNodeId] + let node = targetNodeId ? nodes[targetNodeId] : null; + if (!targetNodeId) { + const site = Object.values(nodes).find((n) => n.type === 'site') + node = site || null + } if (!node) return // Check if node has a saved camera if (node.camera) { + const { position, target } = node.camera - controls.current.setLookAt( - position[0], - position[1], - position[2], - target[0], - target[1], - target[2], - true, - ) + requestAnimationFrame(() => { + controls.current.setLookAt( + position[0], + position[1], + position[2], + target[0], + target[1], + target[2], + true, + ) + }) + return + } + if (!targetNodeId) { + // No selection and no site - do nothing return } diff --git a/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx b/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx index 54581749..edd234be 100644 --- a/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx +++ b/apps/editor/components/ui/sidebar/panels/site-panel/index.tsx @@ -1,4 +1,5 @@ import { + type AnyNodeId, type BuildingNode, emitter, LevelNode, @@ -225,20 +226,122 @@ function PropertyLineSection() { // SITE PHASE VIEW - Property line + building buttons // ============================================================================ +function CameraPopover({ + nodeId, + hasCamera, + open, + onOpenChange, + buttonClassName, +}: { + nodeId: AnyNodeId; + hasCamera: boolean; + open: boolean; + onOpenChange: (open: boolean) => void; + buttonClassName?: string; +}) { + const updateNode = useScene((state) => state.updateNode); + return ( + + + + + e.stopPropagation()} + > +
+ {hasCamera && ( + + )} + + {hasCamera && ( + + )} +
+
+
+ ); +} + function SitePhaseView() { const nodes = useScene((state) => state.nodes); const rootNodeIds = useScene((state) => state.rootNodeIds); + const updateNode = useScene((state) => state.updateNode); const selectedBuildingId = useViewer((state) => state.selection.buildingId); const setSelection = useViewer((state) => state.setSelection); + const [siteCameraOpen, setSiteCameraOpen] = useState(false); + const [buildingCameraOpen, setBuildingCameraOpen] = useState(null); - // Get site node and its building children const siteNode = rootNodeIds[0] ? nodes[rootNodeIds[0]] : null; const buildings = (siteNode?.type === 'site' ? siteNode.children : []) - .map((child) => typeof child === 'string' ? nodes[child] : child) + .map((child) => { + const id = typeof child === 'string' ? child : child.id; + return nodes[id] as BuildingNode | undefined; + }) .filter((node): node is BuildingNode => node?.type === "building"); return (
+ {/* Site row */} + {siteNode && ( +
+
+ + {siteNode.name || "Site"} +
+ +
+ )} {buildings.length === 0 ? (
@@ -247,19 +350,91 @@ function SitePhaseView() { ) : (
{buildings.map((building) => ( - + + setBuildingCameraOpen(open ? building.id : null)} + > + + + + e.stopPropagation()} + > +
+ {building.camera && ( + + )} + + {building.camera && ( + + )} +
+
+
+
))}
)} @@ -280,7 +455,10 @@ function BuildingSelector() { // Get site node and its building children const siteNode = rootNodeIds[0] ? nodes[rootNodeIds[0]] : null; const buildings = (siteNode?.type === 'site' ? siteNode.children : []) - .map((child) => typeof child === 'string' ? nodes[child] : child) + .map((child) => { + const id = typeof child === 'string' ? child : child.id; + return nodes[id] as BuildingNode | undefined; + }) .filter((node): node is BuildingNode => node?.type === "building"); const selectedBuilding = selectedBuildingId