diff --git a/apps/editor/components/editor/index.tsx b/apps/editor/components/editor/index.tsx index 58111041..b9356486 100644 --- a/apps/editor/components/editor/index.tsx +++ b/apps/editor/components/editor/index.tsx @@ -17,8 +17,8 @@ import { AppSidebar } from '../ui/sidebar/app-sidebar' import { CustomCameraControls } from './custom-camera-controls' import { SelectionManager } from './selection-manager' -initSpatialGridSync() useScene.getState().loadScene() +initSpatialGridSync() export default function Editor() { useKeyboard() diff --git a/apps/editor/components/tools/item/item-tool.tsx b/apps/editor/components/tools/item/item-tool.tsx index ca9045ea..b824a0ed 100644 --- a/apps/editor/components/tools/item/item-tool.tsx +++ b/apps/editor/components/tools/item/item-tool.tsx @@ -2,6 +2,7 @@ import { emitter, type GridEvent, ItemNode, + isObject, sceneRegistry, useScene, useSpatialQuery, @@ -29,6 +30,12 @@ function snapToGrid(position: number, dimension: number): number { return Math.round((position - offset) * 2) / 2 + offset } +const stripTransient = (meta: any) => { + if (!isObject(meta)) return meta + const { isTransient, ...rest } = meta as Record + return rest +} + export const ItemTool: React.FC = () => { const cursorRef = useRef(null!) const draftItem = useRef(null) @@ -90,6 +97,9 @@ export const ItemTool: React.FC = () => { position: [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z], name: selectedItem.name, asset: selectedItem, + metadata: { + isTransient: true, + }, }) useScene.getState().createNode(draftItem.current, currentLevelId) checkCanPlace() @@ -124,8 +134,10 @@ export const ItemTool: React.FC = () => { if (!currentLevelId || !draftItem.current || !checkCanPlace()) return useScene.temporal.getState().resume() + useScene.getState().updateNode(draftItem.current.id, { position: [gridPosition.current.x, 0, gridPosition.current.z], + metadata: stripTransient(draftItem.current.metadata), }) draftItem.current = null @@ -187,6 +199,7 @@ export const ItemTool: React.FC = () => { useScene.getState().updateNode(draftItem.current.id, { position: [gridPosition.current.x, gridPosition.current.y, gridPosition.current.z], parentId: event.node.id, + metadata: stripTransient(draftItem.current.metadata), }) useScene.getState().dirtyNodes.add(event.node.id) draftItem.current = null diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 29393cac..b340bd8a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -26,3 +26,5 @@ export * from './schema' export { default as useScene } from './store/use-scene' // Systems export { WallSystem } from './systems/wall/wall-system' + +export { isObject } from './utils/types' \ No newline at end of file diff --git a/packages/core/src/store/use-scene.ts b/packages/core/src/store/use-scene.ts index 53afbd88..89c6bbc0 100644 --- a/packages/core/src/store/use-scene.ts +++ b/packages/core/src/store/use-scene.ts @@ -6,6 +6,7 @@ import { persist } from 'zustand/middleware' import { BuildingNode, type Zone } from '../schema' import { LevelNode } from '../schema/nodes/level' import type { AnyNode, AnyNodeId } from '../schema/types' +import { isObject } from '../utils/types' import * as nodeActions from './actions/node-actions' import * as zoneActions from './actions/zone-actions' @@ -76,10 +77,10 @@ const useScene = create()( loadScene: () => { if (get().rootNodeIds.length > 0) { - // Assign all nodes as dirty to force re-validation + // Assign all nodes as dirty to force re-validation Object.values(get().nodes).forEach((node) => { get().markDirty(node.id) - }) + }) return // Scene already loaded } @@ -149,7 +150,14 @@ const useScene = create()( { name: 'editor-storage', partialize: (state) => ({ - nodes: state.nodes, + nodes: Object.fromEntries( + Object.entries(state.nodes).filter(([_, node]) => { + const meta = node.metadata + const isTransient = isObject(meta) && 'isTransient' in meta && meta.isTransient === true + + return !isTransient + }), + ), rootNodeIds: state.rootNodeIds, zones: state.zones, zoneIds: state.zoneIds, diff --git a/packages/core/src/utils/types.ts b/packages/core/src/utils/types.ts new file mode 100644 index 00000000..abe67dbd --- /dev/null +++ b/packages/core/src/utils/types.ts @@ -0,0 +1,7 @@ +/** + * Type guard to check if a value is a plain object (and not null or an array). + * Useful for narrowing down Zod's generic JSON types. + */ +export const isObject = (val: unknown): val is Record => { + return val !== null && typeof val === 'object' && !Array.isArray(val) +} \ No newline at end of file