Refactor first-person and spawn selection handling
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import type { ThreeEvent } from '@react-three/fiber'
|
import type { ThreeEvent } from '@react-three/fiber'
|
||||||
import type { Object3D } from 'three'
|
|
||||||
import mitt from 'mitt'
|
import mitt from 'mitt'
|
||||||
|
import type { Object3D } from 'three'
|
||||||
import type {
|
import type {
|
||||||
BuildingNode,
|
BuildingNode,
|
||||||
CeilingNode,
|
CeilingNode,
|
||||||
@@ -104,10 +104,8 @@ export interface ThumbnailGenerateEvent {
|
|||||||
|
|
||||||
export interface CameraControlFitSceneEvent {
|
export interface CameraControlFitSceneEvent {
|
||||||
/**
|
/**
|
||||||
* XZ-plane axis-aligned bounds of the scene's geometry, computed from the
|
* XZ-plane axis-aligned bounds for camera framing. Omitted values let the
|
||||||
* scene graph (see `@pascal-app/editor`'s `computeSceneBoundsXZ`). The
|
* listener choose its default framing pose.
|
||||||
* viewer's camera-controls listener frames the camera onto this box.
|
|
||||||
* Omitted values fall back to the camera's default pose.
|
|
||||||
*/
|
*/
|
||||||
bounds?: {
|
bounds?: {
|
||||||
min: [number, number]
|
min: [number, number]
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
'use client'
|
'use client'
|
||||||
import { type CameraControlEvent, emitter, sceneRegistry, useScene } from '@pascal-app/core'
|
import {
|
||||||
|
type CameraControlEvent,
|
||||||
|
type CameraControlFitSceneEvent,
|
||||||
|
emitter,
|
||||||
|
sceneRegistry,
|
||||||
|
useScene,
|
||||||
|
} from '@pascal-app/core'
|
||||||
import { useViewer, ZONE_LAYER } from '@pascal-app/viewer'
|
import { useViewer, ZONE_LAYER } from '@pascal-app/viewer'
|
||||||
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
import { CameraControls, CameraControlsImpl } from '@react-three/drei'
|
||||||
import { useThree } from '@react-three/fiber'
|
import { useThree } from '@react-three/fiber'
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import {
|
|||||||
import { ViewerOverlay } from '../../components/viewer-overlay'
|
import { ViewerOverlay } from '../../components/viewer-overlay'
|
||||||
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
import { ViewerZoneSystem } from '../../components/viewer-zone-system'
|
||||||
import { type PresetsAdapter, PresetsProvider } from '../../contexts/presets-context'
|
import { type PresetsAdapter, PresetsProvider } from '../../contexts/presets-context'
|
||||||
import { useAutoFrame } from '../../hooks/use-auto-frame'
|
|
||||||
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
|
||||||
import { useKeyboard } from '../../hooks/use-keyboard'
|
import { useKeyboard } from '../../hooks/use-keyboard'
|
||||||
import {
|
import {
|
||||||
@@ -952,6 +951,8 @@ export default function Editor({
|
|||||||
const [isSceneLoading, setIsSceneLoading] = useState(false)
|
const [isSceneLoading, setIsSceneLoading] = useState(false)
|
||||||
const [hasLoadedInitialScene, setHasLoadedInitialScene] = useState(false)
|
const [hasLoadedInitialScene, setHasLoadedInitialScene] = useState(false)
|
||||||
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
const isPreviewMode = useEditor((s) => s.isPreviewMode)
|
||||||
|
const firstPersonPreviousLevelRef = useRef(useViewer.getState().selection.levelId)
|
||||||
|
const wasFirstPersonModeRef = useRef(isFirstPersonMode)
|
||||||
|
|
||||||
const sidebarWidth = useSidebarStore((s) => s.width)
|
const sidebarWidth = useSidebarStore((s) => s.width)
|
||||||
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
const isSidebarCollapsed = useSidebarStore((s) => s.isCollapsed)
|
||||||
@@ -969,6 +970,39 @@ export default function Editor({
|
|||||||
}
|
}
|
||||||
}, [projectId])
|
}, [projectId])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const wasFirstPersonMode = wasFirstPersonModeRef.current
|
||||||
|
wasFirstPersonModeRef.current = isFirstPersonMode
|
||||||
|
|
||||||
|
if (isFirstPersonMode && !wasFirstPersonMode) {
|
||||||
|
const viewer = useViewer.getState()
|
||||||
|
firstPersonPreviousLevelRef.current = viewer.selection.levelId
|
||||||
|
viewer.setCameraMode('perspective')
|
||||||
|
viewer.setWallMode('up')
|
||||||
|
viewer.setWalkthroughMode(true)
|
||||||
|
viewer.setSelection({ selectedIds: [], zoneId: null })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(wasFirstPersonMode && !isFirstPersonMode)) return
|
||||||
|
|
||||||
|
const viewer = useViewer.getState()
|
||||||
|
const previousLevelId = firstPersonPreviousLevelRef.current
|
||||||
|
firstPersonPreviousLevelRef.current = null
|
||||||
|
viewer.setWalkthroughMode(false)
|
||||||
|
|
||||||
|
if (!previousLevelId) return
|
||||||
|
|
||||||
|
const previousLevelNode = useScene.getState().nodes[previousLevelId]
|
||||||
|
if (previousLevelNode?.type === 'level') {
|
||||||
|
viewer.setSelection({
|
||||||
|
levelId: previousLevelId,
|
||||||
|
zoneId: null,
|
||||||
|
selectedIds: [],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [isFirstPersonMode])
|
||||||
|
|
||||||
// Load scene on mount (or when onLoad identity changes, e.g. project switch)
|
// Load scene on mount (or when onLoad identity changes, e.g. project switch)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
|
|||||||
@@ -88,7 +88,9 @@ function MoveItemContent({ movingNode }: { movingNode: ItemNode }) {
|
|||||||
return <>{cursor}</>
|
return <>{cursor}</>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MoveTool: React.FC = () => {
|
export const MoveTool: React.FC<{
|
||||||
|
onSpawnMoved?: (nodeId: SpawnNode['id']) => void
|
||||||
|
}> = ({ onSpawnMoved }) => {
|
||||||
const movingNode = useEditor((state) => state.movingNode)
|
const movingNode = useEditor((state) => state.movingNode)
|
||||||
|
|
||||||
if (!movingNode) return null
|
if (!movingNode) return null
|
||||||
@@ -102,7 +104,8 @@ export const MoveTool: React.FC = () => {
|
|||||||
if (movingNode.type === 'wall') return <MoveWallTool node={movingNode as WallNode} />
|
if (movingNode.type === 'wall') return <MoveWallTool node={movingNode as WallNode} />
|
||||||
if (movingNode.type === 'roof' || movingNode.type === 'roof-segment')
|
if (movingNode.type === 'roof' || movingNode.type === 'roof-segment')
|
||||||
return <MoveRoofTool node={movingNode as RoofNode | RoofSegmentNode} />
|
return <MoveRoofTool node={movingNode as RoofNode | RoofSegmentNode} />
|
||||||
if (movingNode.type === 'spawn') return <MoveSpawnTool node={movingNode as SpawnNode} />
|
if (movingNode.type === 'spawn')
|
||||||
|
return <MoveSpawnTool node={movingNode as SpawnNode} onCommitted={onSpawnMoved} />
|
||||||
if (movingNode.type === 'stair' || movingNode.type === 'stair-segment')
|
if (movingNode.type === 'stair' || movingNode.type === 'stair-segment')
|
||||||
return <MoveRoofTool node={movingNode as StairNode | StairSegmentNode} />
|
return <MoveRoofTool node={movingNode as StairNode | StairSegmentNode} />
|
||||||
return <MoveItemContent movingNode={movingNode as ItemNode} />
|
return <MoveItemContent movingNode={movingNode as ItemNode} />
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import '../../../three-types'
|
|||||||
import {
|
import {
|
||||||
emitter,
|
emitter,
|
||||||
type GridEvent,
|
type GridEvent,
|
||||||
sceneRegistry,
|
|
||||||
type SpawnNode,
|
type SpawnNode,
|
||||||
|
sceneRegistry,
|
||||||
useLiveTransforms,
|
useLiveTransforms,
|
||||||
useScene,
|
useScene,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { Vector3 } from 'three'
|
import { Vector3 } from 'three'
|
||||||
import { sfxEmitter } from '../../../lib/sfx-bus'
|
import { sfxEmitter } from '../../../lib/sfx-bus'
|
||||||
@@ -35,7 +34,10 @@ function getLevelLocalSpawnPosition(node: SpawnNode, event: GridEvent): [number,
|
|||||||
return [roundToHalf(worldVector.x), worldVector.y, roundToHalf(worldVector.z)]
|
return [roundToHalf(worldVector.x), worldVector.y, roundToHalf(worldVector.z)]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MoveSpawnTool: React.FC<{ node: SpawnNode }> = ({ node }) => {
|
export const MoveSpawnTool: React.FC<{
|
||||||
|
node: SpawnNode
|
||||||
|
onCommitted?: (nodeId: SpawnNode['id']) => void
|
||||||
|
}> = ({ node, onCommitted }) => {
|
||||||
const [previewPosition, setPreviewPosition] = useState<[number, number, number]>(node.position)
|
const [previewPosition, setPreviewPosition] = useState<[number, number, number]>(node.position)
|
||||||
|
|
||||||
const exitMoveMode = useCallback(() => {
|
const exitMoveMode = useCallback(() => {
|
||||||
@@ -66,7 +68,7 @@ export const MoveSpawnTool: React.FC<{ node: SpawnNode }> = ({ node }) => {
|
|||||||
committed = true
|
committed = true
|
||||||
useScene.temporal.getState().resume()
|
useScene.temporal.getState().resume()
|
||||||
useScene.getState().updateNode(node.id, { position: nextPosition })
|
useScene.getState().updateNode(node.id, { position: nextPosition })
|
||||||
useViewer.getState().setSelection({ selectedIds: [node.id] })
|
onCommitted?.(node.id)
|
||||||
useLiveTransforms.getState().clear(node.id)
|
useLiveTransforms.getState().clear(node.id)
|
||||||
sfxEmitter.emit('sfx:item-place')
|
sfxEmitter.emit('sfx:item-place')
|
||||||
exitMoveMode()
|
exitMoveMode()
|
||||||
@@ -91,7 +93,7 @@ export const MoveSpawnTool: React.FC<{ node: SpawnNode }> = ({ node }) => {
|
|||||||
useScene.temporal.getState().resume()
|
useScene.temporal.getState().resume()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [exitMoveMode, node])
|
}, [exitMoveMode, node, onCommitted])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CursorSphere color="#60a5fa" height={2.2} position={previewPosition} showTooltip={false} />
|
<CursorSphere color="#60a5fa" height={2.2} position={previewPosition} showTooltip={false} />
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import {
|
|||||||
emitter,
|
emitter,
|
||||||
type GridEvent,
|
type GridEvent,
|
||||||
type LevelNode,
|
type LevelNode,
|
||||||
sceneRegistry,
|
|
||||||
SpawnNode,
|
SpawnNode,
|
||||||
|
type SpawnNode as SpawnNodeType,
|
||||||
|
sceneRegistry,
|
||||||
useScene,
|
useScene,
|
||||||
} from '@pascal-app/core'
|
} from '@pascal-app/core'
|
||||||
import { useViewer } from '@pascal-app/viewer'
|
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import type { Group } from 'three'
|
import type { Group } from 'three'
|
||||||
import { Vector3 } from 'three'
|
import { Vector3 } from 'three'
|
||||||
@@ -56,8 +56,12 @@ function getLevelLocalSpawnPosition(
|
|||||||
return [roundToHalf(worldVector.x), worldVector.y, roundToHalf(worldVector.z)]
|
return [roundToHalf(worldVector.x), worldVector.y, roundToHalf(worldVector.z)]
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SpawnTool: React.FC = () => {
|
type SpawnToolProps = {
|
||||||
const currentLevelId = useViewer((state) => state.selection.levelId)
|
currentLevelId: LevelNode['id'] | null
|
||||||
|
onPlaced?: (spawnId: SpawnNodeType['id']) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SpawnTool: React.FC<SpawnToolProps> = ({ currentLevelId, onPlaced }) => {
|
||||||
const [, setCursorPosition] = useState<[number, number, number] | null>(null)
|
const [, setCursorPosition] = useState<[number, number, number] | null>(null)
|
||||||
const cursorRef = useRef<Group>(null)
|
const cursorRef = useRef<Group>(null)
|
||||||
|
|
||||||
@@ -87,7 +91,7 @@ export const SpawnTool: React.FC = () => {
|
|||||||
if (duplicateSpawnIds.length > 0) {
|
if (duplicateSpawnIds.length > 0) {
|
||||||
useScene.getState().deleteNodes(duplicateSpawnIds)
|
useScene.getState().deleteNodes(duplicateSpawnIds)
|
||||||
}
|
}
|
||||||
useViewer.getState().setSelection({ selectedIds: [existingSpawnId] })
|
onPlaced?.(existingSpawnId)
|
||||||
} else {
|
} else {
|
||||||
const spawn = SpawnNode.parse({
|
const spawn = SpawnNode.parse({
|
||||||
name: 'Spawn Point',
|
name: 'Spawn Point',
|
||||||
@@ -95,7 +99,7 @@ export const SpawnTool: React.FC = () => {
|
|||||||
rotation: 0,
|
rotation: 0,
|
||||||
})
|
})
|
||||||
useScene.getState().createNode(spawn, currentLevelId)
|
useScene.getState().createNode(spawn, currentLevelId)
|
||||||
useViewer.getState().setSelection({ selectedIds: [spawn.id] })
|
onPlaced?.(spawn.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
sfxEmitter.emit('sfx:structure-build')
|
sfxEmitter.emit('sfx:structure-build')
|
||||||
@@ -110,7 +114,7 @@ export const SpawnTool: React.FC = () => {
|
|||||||
emitter.off('grid:move', onGridMove)
|
emitter.off('grid:move', onGridMove)
|
||||||
emitter.off('grid:click', onGridClick)
|
emitter.off('grid:click', onGridClick)
|
||||||
}
|
}
|
||||||
}, [currentLevelId])
|
}, [currentLevelId, onPlaced])
|
||||||
|
|
||||||
if (!currentLevelId) return null
|
if (!currentLevelId) return null
|
||||||
|
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ const tools: Record<Phase, Partial<Record<Tool, React.FC>>> = {
|
|||||||
door: DoorTool,
|
door: DoorTool,
|
||||||
item: ItemTool,
|
item: ItemTool,
|
||||||
zone: ZoneTool,
|
zone: ZoneTool,
|
||||||
spawn: SpawnTool,
|
|
||||||
window: WindowTool,
|
window: WindowTool,
|
||||||
},
|
},
|
||||||
furnish: {
|
furnish: {
|
||||||
@@ -63,8 +62,10 @@ export const ToolManager: React.FC = () => {
|
|||||||
const curvingFence = useEditor((state) => state.curvingFence)
|
const curvingFence = useEditor((state) => state.curvingFence)
|
||||||
const editingHole = useEditor((state) => state.editingHole)
|
const editingHole = useEditor((state) => state.editingHole)
|
||||||
const selectedZoneId = useViewer((state) => state.selection.zoneId)
|
const selectedZoneId = useViewer((state) => state.selection.zoneId)
|
||||||
|
const selectedLevelId = useViewer((state) => state.selection.levelId)
|
||||||
const buildingId = useViewer((state) => state.selection.buildingId)
|
const buildingId = useViewer((state) => state.selection.buildingId)
|
||||||
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
const selectedIds = useViewer((state) => state.selection.selectedIds)
|
||||||
|
const setSelection = useViewer((state) => state.setSelection)
|
||||||
const nodes = useScene((state) => state.nodes)
|
const nodes = useScene((state) => state.nodes)
|
||||||
|
|
||||||
// Building transform for the local group — all building-relative tools live inside this group
|
// Building transform for the local group — all building-relative tools live inside this group
|
||||||
@@ -125,12 +126,15 @@ export const ToolManager: React.FC = () => {
|
|||||||
const showBuildTool = mode === 'build' && tool !== null
|
const showBuildTool = mode === 'build' && tool !== null
|
||||||
|
|
||||||
const BuildToolComponent = showBuildTool ? tools[phase]?.[tool] : null
|
const BuildToolComponent = showBuildTool ? tools[phase]?.[tool] : null
|
||||||
|
const handleSpawnSelected = (nodeId: `spawn_${string}`) => {
|
||||||
|
setSelection({ selectedIds: [nodeId] })
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{showSiteBoundaryEditor && <SiteBoundaryEditor />}
|
{showSiteBoundaryEditor && <SiteBoundaryEditor />}
|
||||||
{/* World-space tools: site boundary and building movement operate in world coordinates */}
|
{/* World-space tools: site boundary and building movement operate in world coordinates */}
|
||||||
{movingNode?.type === 'building' && <MoveTool />}
|
{movingNode?.type === 'building' && <MoveTool onSpawnMoved={handleSpawnSelected} />}
|
||||||
|
|
||||||
{/* Building-local group: all other tools are relative to the selected building.
|
{/* Building-local group: all other tools are relative to the selected building.
|
||||||
Cursor visuals set positions in building-local space; this group applies the
|
Cursor visuals set positions in building-local space; this group applies the
|
||||||
@@ -154,7 +158,12 @@ export const ToolManager: React.FC = () => {
|
|||||||
{movingFenceEndpoint && <MoveFenceEndpointTool target={movingFenceEndpoint} />}
|
{movingFenceEndpoint && <MoveFenceEndpointTool target={movingFenceEndpoint} />}
|
||||||
{curvingWall && <CurveWallTool node={curvingWall} />}
|
{curvingWall && <CurveWallTool node={curvingWall} />}
|
||||||
{curvingFence && <CurveFenceTool node={curvingFence} />}
|
{curvingFence && <CurveFenceTool node={curvingFence} />}
|
||||||
{movingNode && movingNode.type !== 'building' && <MoveTool />}
|
{movingNode && movingNode.type !== 'building' && (
|
||||||
|
<MoveTool onSpawnMoved={handleSpawnSelected} />
|
||||||
|
)}
|
||||||
|
{!movingNode && showBuildTool && tool === 'spawn' && (
|
||||||
|
<SpawnTool currentLevelId={selectedLevelId} onPlaced={handleSpawnSelected} />
|
||||||
|
)}
|
||||||
{!movingNode && BuildToolComponent && <BuildToolComponent />}
|
{!movingNode && BuildToolComponent && <BuildToolComponent />}
|
||||||
</group>
|
</group>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -204,7 +204,6 @@ type EditorState = {
|
|||||||
// First-person walkthrough mode (street view)
|
// First-person walkthrough mode (street view)
|
||||||
isFirstPersonMode: boolean
|
isFirstPersonMode: boolean
|
||||||
_viewModeBeforeFirstPerson: ViewMode | null
|
_viewModeBeforeFirstPerson: ViewMode | null
|
||||||
_levelIdBeforeFirstPerson: LevelNode['id'] | null
|
|
||||||
setFirstPersonMode: (enabled: boolean) => void
|
setFirstPersonMode: (enabled: boolean) => void
|
||||||
// Development-only camera debug flag for inspecting underside geometry
|
// Development-only camera debug flag for inspecting underside geometry
|
||||||
allowUndergroundCamera: boolean
|
allowUndergroundCamera: boolean
|
||||||
@@ -637,46 +636,25 @@ const useEditor = create<EditorState>()(
|
|||||||
setAllowUndergroundCamera: (enabled) => set({ allowUndergroundCamera: enabled }),
|
setAllowUndergroundCamera: (enabled) => set({ allowUndergroundCamera: enabled }),
|
||||||
isFirstPersonMode: false,
|
isFirstPersonMode: false,
|
||||||
_viewModeBeforeFirstPerson: null as ViewMode | null,
|
_viewModeBeforeFirstPerson: null as ViewMode | null,
|
||||||
_levelIdBeforeFirstPerson: null as LevelNode['id'] | null,
|
|
||||||
setFirstPersonMode: (enabled) => {
|
setFirstPersonMode: (enabled) => {
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
const currentViewMode = get().viewMode
|
const currentViewMode = get().viewMode
|
||||||
const currentLevelId = useViewer.getState().selection.levelId
|
|
||||||
useViewer.getState().setCameraMode('perspective')
|
|
||||||
useViewer.getState().setWallMode('up')
|
|
||||||
useViewer.getState().setWalkthroughMode(true)
|
|
||||||
set({
|
set({
|
||||||
isFirstPersonMode: true,
|
isFirstPersonMode: true,
|
||||||
_viewModeBeforeFirstPerson: currentViewMode,
|
_viewModeBeforeFirstPerson: currentViewMode,
|
||||||
_levelIdBeforeFirstPerson: currentLevelId,
|
|
||||||
viewMode: '3d',
|
viewMode: '3d',
|
||||||
isFloorplanOpen: false,
|
isFloorplanOpen: false,
|
||||||
mode: 'select',
|
mode: 'select',
|
||||||
tool: null,
|
tool: null,
|
||||||
catalogCategory: null,
|
catalogCategory: null,
|
||||||
})
|
})
|
||||||
useViewer.getState().setSelection({ selectedIds: [], zoneId: null })
|
|
||||||
} else {
|
} else {
|
||||||
const prevMode = get()._viewModeBeforeFirstPerson
|
const prevMode = get()._viewModeBeforeFirstPerson
|
||||||
const prevLevelId = get()._levelIdBeforeFirstPerson
|
|
||||||
useViewer.getState().setWalkthroughMode(false)
|
|
||||||
set({
|
set({
|
||||||
isFirstPersonMode: false,
|
isFirstPersonMode: false,
|
||||||
_viewModeBeforeFirstPerson: null,
|
_viewModeBeforeFirstPerson: null,
|
||||||
_levelIdBeforeFirstPerson: null,
|
|
||||||
...(prevMode ? { viewMode: prevMode, isFloorplanOpen: prevMode !== '3d' } : {}),
|
...(prevMode ? { viewMode: prevMode, isFloorplanOpen: prevMode !== '3d' } : {}),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (prevLevelId) {
|
|
||||||
const prevLevelNode = useScene.getState().nodes[prevLevelId]
|
|
||||||
if (prevLevelNode?.type === 'level') {
|
|
||||||
useViewer.getState().setSelection({
|
|
||||||
levelId: prevLevelId,
|
|
||||||
zoneId: null,
|
|
||||||
selectedIds: [],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
activeSidebarPanel: DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
activeSidebarPanel: DEFAULT_ACTIVE_SIDEBAR_PANEL,
|
||||||
|
|||||||
Reference in New Issue
Block a user