Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ og-test

# Worktrees
.worktrees

# Local agent/runtime logs
.codex/logs/
29 changes: 27 additions & 2 deletions apps/editor/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
'use client'

import { Editor, ItemsPanel } from '@pascal-app/editor'
import { Editor, ItemsPanel, type SceneGraph } from '@pascal-app/editor'
import {
NavigationToolbarButton,
NavigationViewerFrame,
prepareNavigationSceneGraph,
} from '@pascal-app/robot/editor'
import { shouldPauseNavigationAutoSave, useNavigation } from '@pascal-app/robot'
import { Layers, Package, Settings } from 'lucide-react'
import Link from 'next/link'
import {
Expand Down Expand Up @@ -33,8 +39,21 @@ const SIDEBAR_TABS = [
]

const PROJECT_ID = 'local-editor'
const LOCAL_STORAGE_SCENE_KEY = 'pascal-editor-scene'

async function loadNavigationSceneFromLocalStorage(): Promise<SceneGraph | null> {
try {
const raw = window.localStorage.getItem(LOCAL_STORAGE_SCENE_KEY)
const scene = raw ? (JSON.parse(raw) as SceneGraph) : null
return prepareNavigationSceneGraph(scene) ?? scene
} catch {
return null
}
}

export default function Home() {
const robotMode = useNavigation((state) => state.robotMode)

return (
<div className="relative h-screen w-screen">
{PROJECT_ID === 'local-editor' && (
Expand All @@ -54,11 +73,17 @@ export default function Home() {
</div>
)}
<Editor
editorInteractionsDisabled={robotMode !== null}
layoutVersion="v2"
onLoad={loadNavigationSceneFromLocalStorage}
projectId={PROJECT_ID}
renderViewer={(children, props) => (
<NavigationViewerFrame {...props}>{children}</NavigationViewerFrame>
)}
shouldPauseAutoSave={shouldPauseNavigationAutoSave}
sidebarTabs={SIDEBAR_TABS}
viewerToolbarLeft={<CommunityViewerToolbarLeft />}
viewerToolbarRight={<CommunityViewerToolbarRight />}
viewerToolbarRight={<CommunityViewerToolbarRight before={<NavigationToolbarButton />} />}
/>
</div>
)
Expand Down
32 changes: 27 additions & 5 deletions apps/editor/components/scene-loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import {
type SceneGraph,
type SidebarTab,
} from '@pascal-app/editor'
import {
NavigationPanel,
NavigationToolbarButton,
NavigationViewerFrame,
prepareNavigationSceneGraph,
} from '@pascal-app/robot/editor'
import { shouldPauseNavigationAutoSave, useNavigation } from '@pascal-app/robot'
import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useCallback, useEffect, useRef, useState } from 'react'
Expand All @@ -30,6 +37,11 @@ const SIDEBAR_TABS: (SidebarTab & { component: React.ComponentType })[] = [
label: 'Scene',
component: () => null, // Built-in SitePanel handles this
},
{
id: 'robot',
label: 'Robot',
component: NavigationPanel,
},
]

interface SceneLoaderProps {
Expand Down Expand Up @@ -60,13 +72,17 @@ function sceneGraphSignature(graph: SceneGraphWithCollections): string {

export function SceneLoader({ initialScene, meta }: SceneLoaderProps) {
const router = useRouter()
const robotMode = useNavigation((state) => state.robotMode)
const versionRef = useRef(meta.version)
const lastRemoteGraphJsonRef = useRef<string | null>(null)
const suppressRemoteSaveUntilRef = useRef(0)
const [conflict, setConflict] = useState(false)
const [saveError, setSaveError] = useState<string | null>(null)

const handleLoad = useCallback(async () => initialScene, [initialScene])
const handleLoad = useCallback(
async () => prepareNavigationSceneGraph(initialScene) ?? initialScene,
[initialScene],
)

const handleSave = useCallback(
async (graph: SceneGraph) => {
Expand Down Expand Up @@ -123,9 +139,10 @@ export function SceneLoader({ initialScene, meta }: SceneLoaderProps) {
if (payload.version <= versionRef.current) return

versionRef.current = payload.version
lastRemoteGraphJsonRef.current = sceneGraphSignature(payload.graph)
const graph = prepareNavigationSceneGraph(payload.graph) ?? payload.graph
lastRemoteGraphJsonRef.current = sceneGraphSignature(graph)
suppressRemoteSaveUntilRef.current = Date.now() + 2500
applySceneGraphToEditor(payload.graph)
applySceneGraphToEditor(graph)
setConflict(false)
setSaveError(null)
})
Expand Down Expand Up @@ -184,7 +201,7 @@ export function SceneLoader({ initialScene, meta }: SceneLoaderProps) {
<p className="font-medium text-destructive text-xs">{saveError}</p>
</div>
)}
<div className="pointer-events-none absolute top-4 right-4 z-40 flex items-center gap-2">
<div className="pointer-events-none absolute top-16 right-4 z-40 flex items-center gap-2">
<Link
className="pointer-events-auto rounded-md border border-border bg-background/90 px-3 py-1.5 font-medium text-xs shadow-sm backdrop-blur hover:bg-accent/40"
href="/scenes"
Expand All @@ -193,14 +210,19 @@ export function SceneLoader({ initialScene, meta }: SceneLoaderProps) {
</Link>
</div>
<Editor
editorInteractionsDisabled={robotMode !== null}
layoutVersion="v2"
onLoad={handleLoad}
onSave={handleSave}
onThumbnailCapture={handleThumb}
projectId={meta.projectId ?? 'default'}
renderViewer={(children, props) => (
<NavigationViewerFrame {...props}>{children}</NavigationViewerFrame>
)}
shouldPauseAutoSave={shouldPauseNavigationAutoSave}
sidebarTabs={SIDEBAR_TABS}
viewerToolbarLeft={<CommunityViewerToolbarLeft />}
viewerToolbarRight={<CommunityViewerToolbarRight />}
viewerToolbarRight={<CommunityViewerToolbarRight before={<NavigationToolbarButton />} />}
/>
</div>
)
Expand Down
4 changes: 3 additions & 1 deletion apps/editor/components/viewer-toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -344,9 +344,11 @@ export function CommunityViewerToolbarLeft() {
)
}

export function CommunityViewerToolbarRight() {
export function CommunityViewerToolbarRight({ before }: { before?: ReactNode } = {}) {
return (
<div className={TOOLBAR_CONTAINER}>
{before}
{before ? <div className="my-1.5 w-px bg-border/50" /> : null}
<LevelModeToggle />
<WallModeToggle />
<GridVisibilityToggle />
Expand Down
14 changes: 14 additions & 0 deletions apps/editor/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { NextConfig } from 'next'
import path from 'node:path'

const nextConfig: NextConfig = {
logging: {
Expand All @@ -13,6 +14,7 @@ const nextConfig: NextConfig = {
'@pascal-app/core',
'@pascal-app/editor',
'@pascal-app/mcp',
'@pascal-app/robot',
],
turbopack: {
resolveAlias: {
Expand All @@ -22,6 +24,18 @@ const nextConfig: NextConfig = {
'@react-three/drei': './node_modules/@react-three/drei',
},
},
webpack: (config) => {
config.resolve ??= {}
config.resolve.alias = {
...(config.resolve.alias ?? {}),
'three/tsl': path.resolve(__dirname, './node_modules/three/build/three.tsl.js'),
'three/webgpu': path.resolve(__dirname, './node_modules/three/build/three.webgpu.js'),
'three$': path.resolve(__dirname, './node_modules/three/build/three.module.js'),
'@react-three/fiber': path.resolve(__dirname, './node_modules/@react-three/fiber'),
'@react-three/drei': path.resolve(__dirname, './node_modules/@react-three/drei'),
}
return config
},
experimental: {
serverActions: {
bodySizeLimit: '100mb',
Expand Down
1 change: 1 addition & 0 deletions apps/editor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@pascal-app/core": "*",
"@pascal-app/editor": "*",
"@pascal-app/mcp": "*",
"@pascal-app/robot": "*",
"@pascal-app/viewer": "*",
"@radix-ui/react-tooltip": "^1.2.8",
"@react-three/drei": "^10.7.7",
Expand Down
Binary file added apps/editor/public/items/pascal-truck/model.glb
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading