Skip to content
Merged
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
9 changes: 9 additions & 0 deletions packages/editor/src/components/editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,9 @@ export interface EditorProps {
sidebarOverlay?: ReactNode
viewerBanner?: ReactNode

/** Shown in the preview header. */
projectName?: string | null

// Panel config (passed through to sidebar panels — v1 only)
settingsPanelProps?: SettingsPanelProps
sitePanelProps?: SitePanelProps
Expand Down Expand Up @@ -1254,12 +1257,14 @@ function PreviewStage({
isFirstPersonMode,
mode,
onModeChange,
projectName,
showLoader,
viewerContent,
}: {
isFirstPersonMode: boolean
mode: ViewerStageMode
onModeChange: (mode: ViewerStageMode) => void
projectName?: string | null
showLoader: boolean
viewerContent: ReactNode
}) {
Expand All @@ -1286,6 +1291,7 @@ function PreviewStage({
<ViewerOverlay
hideBottomBar={stageMode !== '3d'}
onBack={() => useEditor.getState().setPreviewMode(false)}
projectName={projectName}
/>
)}

Expand Down Expand Up @@ -1333,6 +1339,7 @@ function EditorContent({
immersive,
sidebarOverlay,
viewerBanner,
projectName,
settingsPanelProps,
sitePanelProps,
extraSidebarPanels,
Expand Down Expand Up @@ -1688,6 +1695,7 @@ function EditorContent({
isFirstPersonMode={isFirstPersonMode}
mode={previewStageMode}
onModeChange={setPreviewStageMode}
projectName={projectName}
showLoader={visibleLoader}
viewerContent={previewViewerContent}
/>
Expand Down Expand Up @@ -1767,6 +1775,7 @@ function EditorContent({
isFirstPersonMode={isFirstPersonMode}
mode={previewStageMode}
onModeChange={setPreviewStageMode}
projectName={projectName}
showLoader={visibleLoader}
viewerContent={previewViewerContent}
/>
Expand Down
6 changes: 6 additions & 0 deletions packages/viewer/src/components/viewer/selection-manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,9 @@ export const SelectionManager = () => {
// re-subscribe when plugin kinds register after mount (async plugin load)
void registryVersion
const onEnter = (event: NodeEvent) => {
// Walkthrough is camera-only: first-person controls own hover for the
// interactable target, and pointer lock keeps raycasting from a frozen cursor.
if (useViewer.getState().walkthroughMode) return
const strategy = getStrategy()
if (!strategy) return
// Ceilings are selected via their floor-plan helper and the
Expand All @@ -325,6 +328,7 @@ export const SelectionManager = () => {
}

const onLeave = (event: NodeEvent) => {
if (useViewer.getState().walkthroughMode) return

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Walkthrough leaves leftover hover outlines

Medium Severity

Walkthrough gating ignores enter and leave but never clears an existing hoveredId. OutlinerSync still outlines that id, so a building, level, or zone hovered before Walk can stay outlined for the whole walkthrough unless an interactable overwrites it.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 55f66e1. Configure here.

const strategy = getStrategy()
if (!strategy) return
if (event.node.type === 'ceiling') return
Expand All @@ -341,6 +345,7 @@ export const SelectionManager = () => {
}

const onClick = (event: NodeEvent) => {
if (useViewer.getState().walkthroughMode) return
const strategy = getStrategy()
if (!strategy) return
if (event.node.type === 'ceiling') return
Expand Down Expand Up @@ -411,6 +416,7 @@ const PointerMissedHandler = ({
// Only handle left clicks
const viewerState = useViewer.getState()
if (viewerState.cameraDragging || viewerState.inputDragging) return
if (viewerState.walkthroughMode) return
if (event.button !== 0) return

// Use requestAnimationFrame to check after R3F event handlers
Expand Down
24 changes: 24 additions & 0 deletions packages/viewer/src/store/use-viewer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,27 @@ describe('unit focus', () => {
expect(useViewer.getState().selection).toBe(selection)
})
})

describe('scene theme', () => {
afterEach(() => useViewer.getState().setSceneTheme('studio'))

test('a shown theme renders without replacing the saved one', () => {
useViewer.getState().setSceneTheme('paper')
useViewer.getState().showSceneTheme('night')

expect(useViewer.getState().sceneTheme).toBe('night')
expect(useViewer.getState().savedSceneTheme).toBe('paper')

useViewer.getState().showSceneTheme(null)
expect(useViewer.getState().sceneTheme).toBe('paper')
})

test('picking a theme while another is shown saves the pick', () => {
useViewer.getState().showSceneTheme('night')
useViewer.getState().setSceneTheme('sunset')
useViewer.getState().showSceneTheme(null)

expect(useViewer.getState().sceneTheme).toBe('sunset')
expect(useViewer.getState().savedSceneTheme).toBe('sunset')
})
})
23 changes: 17 additions & 6 deletions packages/viewer/src/store/use-viewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ type ViewerState = {
setCameraMode: (mode: 'perspective' | 'orthographic') => void

sceneTheme: string
/** The user's own pick, and the only theme that persists. */
savedSceneTheme: string
setSceneTheme: (id: string) => void
/** Show a theme without saving it (e.g. a project's own mood); `null`
* returns to the saved one. */
showSceneTheme: (id: string | null) => void

renderContext: RenderContext
setRenderContext: (context: RenderContext) => void
Expand Down Expand Up @@ -368,7 +373,9 @@ const useViewer = create<ViewerState>()(
setCameraMode: (mode) => set({ cameraMode: mode }),

sceneTheme: 'studio',
setSceneTheme: (id) => set({ sceneTheme: id }),
savedSceneTheme: 'studio',
setSceneTheme: (id) => set({ sceneTheme: id, savedSceneTheme: id }),
showSceneTheme: (id) => set((state) => ({ sceneTheme: id ?? state.savedSceneTheme })),

renderContext: 'editor',
setRenderContext: (context) => set({ renderContext: context }),
Expand Down Expand Up @@ -560,13 +567,17 @@ const useViewer = create<ViewerState>()(
}),
{
name: 'viewer-preferences',
merge: (persistedState, currentState) => ({
...currentState,
...normalizePersistedViewerState(persistedState),
}),
merge: (persistedState, currentState) => {
const persisted = normalizePersistedViewerState(persistedState)
return {
...currentState,
...persisted,
savedSceneTheme: persisted.sceneTheme ?? currentState.savedSceneTheme,
}
},
partialize: (state) => ({
cameraMode: state.cameraMode,
sceneTheme: state.sceneTheme,
sceneTheme: state.savedSceneTheme,
shadingByContext: state.shadingByContext,
textures: state.textures,
colorPreset: state.colorPreset,
Expand Down
Loading