Refactor asset types and improve UI primitives
Replaces usage of the Asset type with AssetInput across editor components and hooks for consistency with core types. Adds default dimensions fallback for asset placement logic. Refactors UI primitives (button, sidebar, opacity control) to support forwarding refs and asChild prop, and updates imports for primitives. Updates Radix UI dependencies in package.json and bun.lock.
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { type AnyNode, type BuildingNode, type LevelNode, type ZoneNode, useScene } from '@pascal-app/core'
|
||||
import { type AnyNode, type AnyNodeId, type BuildingNode, type LevelNode, type ZoneNode, useScene } from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { Box, ChevronRight, Diamond, Eye, EyeOff, Image, Layers, Layers2 } from 'lucide-react'
|
||||
|
||||
@@ -28,16 +28,16 @@ export const ViewerOverlay = () => {
|
||||
|
||||
// Get the first selected item (if any)
|
||||
const selectedNode = selection.selectedIds.length > 0
|
||||
? (nodes[selection.selectedIds[0]!] as AnyNode | undefined)
|
||||
? (nodes[selection.selectedIds[0] as AnyNodeId] as AnyNode | undefined)
|
||||
: null
|
||||
|
||||
// Get all levels for the selected building
|
||||
const levels = building?.children
|
||||
.map((id) => nodes[id] as LevelNode | undefined)
|
||||
.map((id) => nodes[id as AnyNodeId] as LevelNode | undefined)
|
||||
.filter((n): n is LevelNode => n?.type === 'level')
|
||||
.sort((a, b) => a.level - b.level) ?? []
|
||||
|
||||
const handleLevelClick = (levelId: string) => {
|
||||
const handleLevelClick = (levelId: LevelNode['id']) => {
|
||||
// When switching levels, deselect zone and items
|
||||
useViewer.getState().setSelection({ levelId })
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export const ViewerZoneSystem = () => {
|
||||
// 2. Zone is not on the selected level
|
||||
// 3. A zone is already selected (hide all zones to show zone contents)
|
||||
const isOnSelectedLevel = zone.parentId === levelId
|
||||
const shouldShow = levelId && isOnSelectedLevel && !zoneId
|
||||
const shouldShow = !!levelId && isOnSelectedLevel && !zoneId
|
||||
|
||||
obj.visible = shouldShow
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ import {
|
||||
stripTransient,
|
||||
} from './placement-math'
|
||||
|
||||
const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
|
||||
|
||||
// ============================================================================
|
||||
// FLOOR STRATEGY
|
||||
// ============================================================================
|
||||
@@ -36,7 +38,8 @@ export const floorStrategy = {
|
||||
move(ctx: PlacementContext, event: GridEvent): PlacementResult | null {
|
||||
if (ctx.state.surface !== 'floor') return null
|
||||
|
||||
const [dimX, , dimZ] = ctx.asset.dimensions
|
||||
const dims = ctx.asset.dimensions ?? DEFAULT_DIMENSIONS
|
||||
const [dimX, , dimZ] = dims
|
||||
const x = snapToGrid(event.position[0], dimX)
|
||||
const z = snapToGrid(event.position[2], dimZ)
|
||||
|
||||
@@ -240,8 +243,9 @@ export const ceilingStrategy = {
|
||||
const ceilingLevelId = resolveLevelId(event.node, nodes)
|
||||
if (ctx.levelId !== ceilingLevelId) return null
|
||||
|
||||
const [dimX, , dimZ] = ctx.asset.dimensions
|
||||
const itemHeight = ctx.asset.dimensions[1]
|
||||
const dims = ctx.asset.dimensions ?? DEFAULT_DIMENSIONS
|
||||
const [dimX, , dimZ] = dims
|
||||
const itemHeight = dims[1]
|
||||
|
||||
const x = snapToGrid(event.position[0], dimX)
|
||||
const z = snapToGrid(event.position[2], dimZ)
|
||||
@@ -266,8 +270,9 @@ export const ceilingStrategy = {
|
||||
if (ctx.state.surface !== 'ceiling') return null
|
||||
if (!ctx.draftItem) return null
|
||||
|
||||
const [dimX, , dimZ] = ctx.asset.dimensions
|
||||
const itemHeight = ctx.asset.dimensions[1]
|
||||
const dims = ctx.asset.dimensions ?? DEFAULT_DIMENSIONS
|
||||
const [dimX, , dimZ] = dims
|
||||
const itemHeight = dims[1]
|
||||
|
||||
const x = snapToGrid(event.position[0], dimX)
|
||||
const z = snapToGrid(event.position[2], dimZ)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import type { AnyNode, CeilingNode, ItemNode, LevelNode, WallNode } from '@pascal-app/core'
|
||||
import type { AnyNode, AssetInput, CeilingNode, ItemNode, LevelNode, WallNode } from '@pascal-app/core'
|
||||
import type { Vector3 } from 'three'
|
||||
import type { Asset } from '../../../../../packages/core/src/schema/nodes/item'
|
||||
|
||||
// ============================================================================
|
||||
// PLACEMENT STATE
|
||||
@@ -26,7 +25,7 @@ export interface PlacementState {
|
||||
* Read-only snapshot passed to every strategy call.
|
||||
*/
|
||||
export interface PlacementContext {
|
||||
asset: Asset
|
||||
asset: AssetInput
|
||||
levelId: LevelNode['id'] | null
|
||||
draftItem: ItemNode | null
|
||||
gridPosition: Vector3
|
||||
|
||||
@@ -2,7 +2,7 @@ import { type AnyNodeId, ItemNode, useScene } from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useCallback, useMemo, useRef } from 'react'
|
||||
import type { Vector3 } from 'three'
|
||||
import type { Asset } from '../../../../../packages/core/src/schema/nodes/item'
|
||||
import type { AssetInput } from '@pascal-app/core'
|
||||
import { stripTransient } from './placement-math'
|
||||
|
||||
interface OriginalState {
|
||||
@@ -19,7 +19,7 @@ export interface DraftNodeHandle {
|
||||
/** Whether the current draft was adopted (move mode) vs created (create mode) */
|
||||
readonly isAdopted: boolean
|
||||
/** Create a new draft item at the given position. Returns the created node or null. */
|
||||
create: (gridPosition: Vector3, asset: Asset, rotation?: [number, number, number]) => ItemNode | null
|
||||
create: (gridPosition: Vector3, asset: AssetInput, rotation?: [number, number, number]) => ItemNode | null
|
||||
/** Take ownership of an existing scene node as the draft (for move mode). */
|
||||
adopt: (node: ItemNode) => void
|
||||
/** Commit the current draft. Create mode: delete+recreate. Move mode: update in place. */
|
||||
@@ -41,7 +41,7 @@ export function useDraftNode(): DraftNodeHandle {
|
||||
const adoptedRef = useRef(false)
|
||||
const originalStateRef = useRef<OriginalState | null>(null)
|
||||
|
||||
const create = useCallback((gridPosition: Vector3, asset: Asset, rotation?: [number, number, number]): ItemNode | null => {
|
||||
const create = useCallback((gridPosition: Vector3, asset: AssetInput, rotation?: [number, number, number]): ItemNode | null => {
|
||||
const currentLevelId = useViewer.getState().selection.levelId
|
||||
if (!currentLevelId) return null
|
||||
|
||||
|
||||
@@ -23,10 +23,12 @@ import {
|
||||
} from './placement-strategies'
|
||||
import type { PlacementState, TransitionResult } from './placement-types'
|
||||
import type { DraftNodeHandle } from './use-draft-node'
|
||||
import type { Asset } from '../../../../../packages/core/src/schema/nodes/item'
|
||||
import type { AssetInput } from '@pascal-app/core'
|
||||
|
||||
const DEFAULT_DIMENSIONS: [number, number, number] = [1, 1, 1]
|
||||
|
||||
export interface PlacementCoordinatorConfig {
|
||||
asset: Asset
|
||||
asset: AssetInput
|
||||
draftNode: DraftNodeHandle
|
||||
initDraft: (gridPosition: Vector3) => void
|
||||
onCommitted: () => boolean
|
||||
@@ -441,12 +443,9 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
||||
|
||||
// ---- Bounding box geometry ----
|
||||
|
||||
const boxGeometry = new BoxGeometry(
|
||||
asset.dimensions[0],
|
||||
asset.dimensions[1],
|
||||
asset.dimensions[2],
|
||||
)
|
||||
boxGeometry.translate(0, asset.dimensions[1] / 2, 0)
|
||||
const dims = asset.dimensions ?? DEFAULT_DIMENSIONS
|
||||
const boxGeometry = new BoxGeometry(dims[0], dims[1], dims[2])
|
||||
boxGeometry.translate(0, dims[1] / 2, 0)
|
||||
cursorRef.current.geometry = boxGeometry
|
||||
|
||||
// ---- Subscribe ----
|
||||
@@ -507,7 +506,7 @@ export function usePlacementCoordinator(config: PlacementCoordinatorConfig): Rea
|
||||
mesh.position.y = spatialGridManager.getSlabElevationForItem(
|
||||
levelId,
|
||||
[gridPosition.current.x, gridPosition.current.y, gridPosition.current.z],
|
||||
asset.dimensions,
|
||||
asset.dimensions ?? DEFAULT_DIMENSIONS,
|
||||
draftNode.current.rotation,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -218,7 +218,7 @@ export const SlabTool: React.FC = () => {
|
||||
|
||||
// Reset state
|
||||
pointsRef.current = [];
|
||||
setPreview({ points: [], cursorPoint: null });
|
||||
setPreview({ points: [], cursorPoint: null, levelY: 0 });
|
||||
mainLineRef.current.visible = false;
|
||||
closingLineRef.current.visible = false;
|
||||
|
||||
@@ -240,7 +240,7 @@ export const SlabTool: React.FC = () => {
|
||||
|
||||
// Reset state
|
||||
pointsRef.current = [];
|
||||
setPreview({ points: [], cursorPoint: null });
|
||||
setPreview({ points: [], cursorPoint: null, levelY: 0 });
|
||||
mainLineRef.current.visible = false;
|
||||
closingLineRef.current.visible = false;
|
||||
|
||||
|
||||
@@ -39,17 +39,28 @@ function Button({
|
||||
variant,
|
||||
size,
|
||||
asChild = false,
|
||||
ref,
|
||||
...props
|
||||
}: React.ComponentProps<'button'> &
|
||||
VariantProps<typeof buttonVariants> & {
|
||||
asChild?: boolean
|
||||
}) {
|
||||
const Comp = asChild ? Slot : 'button'
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
data-slot="button"
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<Comp
|
||||
<button
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
data-slot="button"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
import { Eye, EyeOff } from 'lucide-react'
|
||||
import { useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { Slider } from '@/components/ui/slider'
|
||||
import { Button } from '@/components/ui/primitives/button'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/primitives/popover'
|
||||
import { Slider } from '@/components/ui/primitives/slider'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
interface OpacityControlProps {
|
||||
@@ -65,8 +65,8 @@ export function OpacityControl({
|
||||
<Slider
|
||||
max={100}
|
||||
min={0}
|
||||
onValueChange={([value]) => {
|
||||
onOpacityChange(value)
|
||||
onValueChange={(values: number[]) => {
|
||||
if (values[0] !== undefined) onOpacityChange(values[0])
|
||||
}}
|
||||
step={1}
|
||||
value={[actualOpacity]}
|
||||
|
||||
@@ -397,12 +397,27 @@ function SidebarGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
function SidebarGroupLabel({
|
||||
className,
|
||||
asChild = false,
|
||||
ref,
|
||||
...props
|
||||
}: React.ComponentProps<"div"> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "div";
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot
|
||||
className={cn(
|
||||
"flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
||||
className,
|
||||
)}
|
||||
data-sidebar="group-label"
|
||||
data-slot="sidebar-group-label"
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Comp
|
||||
<div
|
||||
className={cn(
|
||||
"flex h-8 shrink-0 items-center rounded-md px-2 font-medium text-sidebar-foreground/70 text-xs outline-hidden ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0",
|
||||
@@ -410,6 +425,7 @@ function SidebarGroupLabel({
|
||||
)}
|
||||
data-sidebar="group-label"
|
||||
data-slot="sidebar-group-label"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -418,21 +434,34 @@ function SidebarGroupLabel({
|
||||
function SidebarGroupAction({
|
||||
className,
|
||||
asChild = false,
|
||||
ref,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> & { asChild?: boolean }) {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
const classes = cn(
|
||||
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"after:-inset-2 after:absolute md:after:hidden",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className,
|
||||
);
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot
|
||||
className={classes}
|
||||
data-sidebar="group-action"
|
||||
data-slot="sidebar-group-action"
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={cn(
|
||||
"absolute top-3.5 right-3 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
// Increases the hit area of the button on mobile.
|
||||
"after:-inset-2 after:absolute md:after:hidden",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className,
|
||||
)}
|
||||
<button
|
||||
className={classes}
|
||||
data-sidebar="group-action"
|
||||
data-slot="sidebar-group-action"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -503,22 +532,34 @@ function SidebarMenuButton({
|
||||
size = "default",
|
||||
tooltip,
|
||||
className,
|
||||
ref,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> & {
|
||||
asChild?: boolean;
|
||||
isActive?: boolean;
|
||||
tooltip?: string | React.ComponentProps<typeof TooltipContent>;
|
||||
} & VariantProps<typeof sidebarMenuButtonVariants>) {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
const { isMobile, state } = useSidebar();
|
||||
const classes = cn(sidebarMenuButtonVariants({ variant, size }), className);
|
||||
|
||||
const button = (
|
||||
<Comp
|
||||
className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
|
||||
const button = asChild ? (
|
||||
<Slot
|
||||
className={classes}
|
||||
data-active={isActive}
|
||||
data-sidebar="menu-button"
|
||||
data-size={size}
|
||||
data-slot="sidebar-menu-button"
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<button
|
||||
className={classes}
|
||||
data-active={isActive}
|
||||
data-sidebar="menu-button"
|
||||
data-size={size}
|
||||
data-slot="sidebar-menu-button"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -550,29 +591,42 @@ function SidebarMenuAction({
|
||||
className,
|
||||
asChild = false,
|
||||
showOnHover = false,
|
||||
ref,
|
||||
...props
|
||||
}: React.ComponentProps<"button"> & {
|
||||
asChild?: boolean;
|
||||
showOnHover?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "button";
|
||||
const classes = cn(
|
||||
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"after:-inset-2 after:absolute md:after:hidden",
|
||||
"peer-data-[size=sm]/menu-button:top-1",
|
||||
"peer-data-[size=default]/menu-button:top-1.5",
|
||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
showOnHover &&
|
||||
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
||||
className,
|
||||
);
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot
|
||||
className={classes}
|
||||
data-sidebar="menu-action"
|
||||
data-slot="sidebar-menu-action"
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={cn(
|
||||
"absolute top-1.5 right-1 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
// Increases the hit area of the button on mobile.
|
||||
"after:-inset-2 after:absolute md:after:hidden",
|
||||
"peer-data-[size=sm]/menu-button:top-1",
|
||||
"peer-data-[size=default]/menu-button:top-1.5",
|
||||
"peer-data-[size=lg]/menu-button:top-2.5",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
showOnHover &&
|
||||
"group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0",
|
||||
className,
|
||||
)}
|
||||
<button
|
||||
className={classes}
|
||||
data-sidebar="menu-action"
|
||||
data-slot="sidebar-menu-action"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
@@ -673,28 +727,44 @@ function SidebarMenuSubButton({
|
||||
size = "md",
|
||||
isActive = false,
|
||||
className,
|
||||
ref,
|
||||
...props
|
||||
}: React.ComponentProps<"a"> & {
|
||||
asChild?: boolean;
|
||||
size?: "sm" | "md";
|
||||
isActive?: boolean;
|
||||
}) {
|
||||
const Comp = asChild ? Slot : "a";
|
||||
const classes = cn(
|
||||
"-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
||||
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
||||
size === "sm" && "text-xs",
|
||||
size === "md" && "text-sm",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className,
|
||||
);
|
||||
|
||||
if (asChild) {
|
||||
return (
|
||||
<Slot
|
||||
className={classes}
|
||||
data-active={isActive}
|
||||
data-sidebar="menu-sub-button"
|
||||
data-size={size}
|
||||
data-slot="sidebar-menu-sub-button"
|
||||
ref={ref as never}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Comp
|
||||
className={cn(
|
||||
"-translate-x-px flex h-7 min-w-0 items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-hidden ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground",
|
||||
"data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground",
|
||||
size === "sm" && "text-xs",
|
||||
size === "md" && "text-sm",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className,
|
||||
)}
|
||||
<a
|
||||
className={classes}
|
||||
data-active={isActive}
|
||||
data-sidebar="menu-sub-button"
|
||||
data-size={size}
|
||||
data-slot="sidebar-menu-sub-button"
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useScene } from '@pascal-app/core'
|
||||
import { type AnyNodeId, useScene } from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { useEffect } from 'react'
|
||||
import useEditor from '@/store/use-editor'
|
||||
@@ -44,7 +44,7 @@ export const useKeyboard = () => {
|
||||
} else if (e.key === 'Delete' || e.key === 'Backspace') {
|
||||
e.preventDefault()
|
||||
|
||||
const selectedNodeIds = useViewer.getState().selection.selectedIds
|
||||
const selectedNodeIds = useViewer.getState().selection.selectedIds as AnyNodeId[]
|
||||
|
||||
if (selectedNodeIds.length > 0) {
|
||||
useScene.getState().deleteNodes(selectedNodeIds)
|
||||
|
||||
@@ -13,10 +13,17 @@
|
||||
"dependencies": {
|
||||
"@pascal-app/core": "*",
|
||||
"@pascal-app/viewer": "*",
|
||||
"@radix-ui/react-alert-dialog": "^1.1.15",
|
||||
"@radix-ui/react-context-menu": "^2.2.16",
|
||||
"@radix-ui/react-dialog": "^1.1.15",
|
||||
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
||||
"@radix-ui/react-popover": "^1.1.15",
|
||||
"@radix-ui/react-select": "^2.2.6",
|
||||
"@radix-ui/react-separator": "^1.1.8",
|
||||
"@radix-ui/react-slider": "^1.3.6",
|
||||
"@radix-ui/react-slot": "^1.2.4",
|
||||
"@radix-ui/react-switch": "^1.2.6",
|
||||
"@radix-ui/react-tabs": "^1.1.13",
|
||||
"@radix-ui/react-tooltip": "^1.2.8",
|
||||
"@react-three/uikit-lucide": "^1.0.60",
|
||||
"@repo/ui": "*",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { type BuildingNode, type ItemNode, type LevelNode, useScene } from '@pascal-app/core'
|
||||
import { useViewer } from '@pascal-app/viewer'
|
||||
import { create } from 'zustand'
|
||||
import type { Asset } from '../../../packages/core/src/schema/nodes/item'
|
||||
import type { AssetInput } from '@pascal-app/core'
|
||||
|
||||
export type Phase = 'site' | 'structure' | 'furnish'
|
||||
|
||||
@@ -54,8 +54,8 @@ type EditorState = {
|
||||
setStructureLayer: (layer: StructureLayer) => void
|
||||
catalogCategory: CatalogCategory | null
|
||||
setCatalogCategory: (category: CatalogCategory | null) => void
|
||||
selectedItem: Asset | null
|
||||
setSelectedItem: (item: Asset) => void
|
||||
selectedItem: AssetInput | null
|
||||
setSelectedItem: (item: AssetInput) => void
|
||||
movingNode: ItemNode | null
|
||||
setMovingNode: (node: ItemNode | null) => void
|
||||
selectedReferenceId: string | null
|
||||
|
||||
Reference in New Issue
Block a user