@pascal-app/editor 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -7
- package/src/components/editor/editor-layout-v2.tsx +9 -0
- package/src/components/editor/floating-action-menu.tsx +255 -34
- package/src/components/editor/floating-building-action-menu.tsx +4 -3
- package/src/components/editor/floorplan-panel.tsx +1323 -713
- package/src/components/editor/index.tsx +2 -0
- package/src/components/editor/node-action-menu.tsx +14 -1
- package/src/components/editor/selection-manager.tsx +200 -8
- package/src/components/editor/site-edge-labels.tsx +9 -3
- package/src/components/editor/thumbnail-generator.tsx +319 -157
- package/src/components/editor/wall-measurement-label.tsx +120 -32
- package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +272 -0
- package/src/components/systems/roof/roof-edit-system.tsx +5 -5
- package/src/components/tools/ceiling/ceiling-hole-editor.tsx +1 -0
- package/src/components/tools/ceiling/move-ceiling-tool.tsx +257 -0
- package/src/components/tools/door/door-tool.tsx +12 -0
- package/src/components/tools/door/move-door-tool.tsx +10 -0
- package/src/components/tools/fence/curve-fence-tool.tsx +179 -0
- package/src/components/tools/fence/fence-drafting.ts +19 -7
- package/src/components/tools/fence/move-fence-endpoint-tool.tsx +327 -0
- package/src/components/tools/fence/move-fence-tool.tsx +8 -0
- package/src/components/tools/item/move-tool.tsx +9 -0
- package/src/components/tools/item/placement-math.ts +14 -6
- package/src/components/tools/item/placement-strategies.ts +2 -2
- package/src/components/tools/item/use-placement-coordinator.tsx +42 -10
- package/src/components/tools/roof/move-roof-tool.tsx +89 -28
- package/src/components/tools/shared/polygon-editor.tsx +98 -8
- package/src/components/tools/slab/move-slab-tool.tsx +182 -0
- package/src/components/tools/slab/slab-hole-editor.tsx +1 -0
- package/src/components/tools/stair/stair-tool.tsx +11 -3
- package/src/components/tools/tool-manager.tsx +12 -0
- package/src/components/tools/wall/curve-wall-tool.tsx +176 -0
- package/src/components/tools/wall/move-wall-endpoint-tool.tsx +322 -0
- package/src/components/tools/wall/move-wall-tool.tsx +356 -0
- package/src/components/tools/wall/wall-drafting.ts +331 -9
- package/src/components/tools/window/move-window-tool.tsx +10 -0
- package/src/components/tools/window/window-tool.tsx +12 -0
- package/src/components/ui/action-menu/control-modes.tsx +9 -4
- package/src/components/ui/command-palette/editor-commands.tsx +9 -4
- package/src/components/ui/command-palette/index.tsx +0 -1
- package/src/components/ui/controls/material-picker.tsx +127 -94
- package/src/components/ui/controls/slider-control.tsx +28 -14
- package/src/components/ui/item-catalog/catalog-items.tsx +5 -0
- package/src/components/ui/panels/ceiling-panel.tsx +61 -17
- package/src/components/ui/panels/door-panel.tsx +5 -5
- package/src/components/ui/panels/fence-panel.tsx +97 -12
- package/src/components/ui/panels/item-panel.tsx +5 -5
- package/src/components/ui/panels/panel-manager.tsx +31 -29
- package/src/components/ui/panels/reference-panel.tsx +5 -4
- package/src/components/ui/panels/roof-panel.tsx +91 -22
- package/src/components/ui/panels/roof-segment-panel.tsx +23 -13
- package/src/components/ui/panels/slab-panel.tsx +63 -15
- package/src/components/ui/panels/stair-panel.tsx +173 -19
- package/src/components/ui/panels/stair-segment-panel.tsx +28 -17
- package/src/components/ui/panels/wall-panel.tsx +159 -11
- package/src/components/ui/panels/window-panel.tsx +5 -7
- package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/index.tsx +29 -32
- package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +3 -3
- package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -3
- package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +7 -3
- package/src/components/ui/viewer-toolbar.tsx +55 -2
- package/src/components/viewer-overlay.tsx +25 -19
- package/src/hooks/use-contextual-tools.ts +14 -13
- package/src/hooks/use-keyboard.ts +3 -2
- package/src/index.tsx +2 -1
- package/src/lib/history.ts +20 -0
- package/src/lib/sfx-player.ts +96 -13
- package/src/store/use-editor.tsx +118 -10
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
type AnyNodeId,
|
|
5
|
+
emitter,
|
|
6
|
+
type GridEvent,
|
|
7
|
+
pauseSceneHistory,
|
|
8
|
+
resumeSceneHistory,
|
|
9
|
+
useScene,
|
|
10
|
+
type WallNode,
|
|
11
|
+
} from '@pascal-app/core'
|
|
12
|
+
import { useViewer } from '@pascal-app/viewer'
|
|
13
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
14
|
+
import { markToolCancelConsumed } from '../../../hooks/use-keyboard'
|
|
15
|
+
import { sfxEmitter } from '../../../lib/sfx-bus'
|
|
16
|
+
import useEditor from '../../../store/use-editor'
|
|
17
|
+
import { CursorSphere } from '../shared/cursor-sphere'
|
|
18
|
+
import { getWallGridStep, snapScalarToGrid } from './wall-drafting'
|
|
19
|
+
|
|
20
|
+
function rotateVector([x, z]: [number, number], angle: number): [number, number] {
|
|
21
|
+
const cos = Math.cos(angle)
|
|
22
|
+
const sin = Math.sin(angle)
|
|
23
|
+
return [x * cos - z * sin, x * sin + z * cos]
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function samePoint(a: [number, number], b: [number, number]) {
|
|
27
|
+
return a[0] === b[0] && a[1] === b[1]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function stripWallIsNewMetadata(meta: WallNode['metadata']): WallNode['metadata'] {
|
|
31
|
+
if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {
|
|
32
|
+
return meta
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const nextMeta = { ...(meta as Record<string, unknown>) } as Record<string, unknown>
|
|
36
|
+
delete nextMeta.isNew
|
|
37
|
+
return nextMeta as WallNode['metadata']
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
type LinkedWallSnapshot = {
|
|
41
|
+
id: WallNode['id']
|
|
42
|
+
start: [number, number]
|
|
43
|
+
end: [number, number]
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getLinkedWallSnapshots(args: {
|
|
47
|
+
wallId: WallNode['id']
|
|
48
|
+
wallParentId: string | null
|
|
49
|
+
originalStart: [number, number]
|
|
50
|
+
originalEnd: [number, number]
|
|
51
|
+
}) {
|
|
52
|
+
const { wallId, wallParentId, originalStart, originalEnd } = args
|
|
53
|
+
const { nodes } = useScene.getState()
|
|
54
|
+
const snapshots: LinkedWallSnapshot[] = []
|
|
55
|
+
|
|
56
|
+
for (const node of Object.values(nodes)) {
|
|
57
|
+
if (!(node?.type === 'wall' && node.id !== wallId)) {
|
|
58
|
+
continue
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if ((node.parentId ?? null) !== wallParentId) {
|
|
62
|
+
continue
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (
|
|
66
|
+
!samePoint(node.start, originalStart) &&
|
|
67
|
+
!samePoint(node.start, originalEnd) &&
|
|
68
|
+
!samePoint(node.end, originalStart) &&
|
|
69
|
+
!samePoint(node.end, originalEnd)
|
|
70
|
+
) {
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
snapshots.push({
|
|
75
|
+
id: node.id,
|
|
76
|
+
start: [...node.start] as [number, number],
|
|
77
|
+
end: [...node.end] as [number, number],
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return snapshots
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getLinkedWallUpdates(
|
|
85
|
+
linkedWalls: LinkedWallSnapshot[],
|
|
86
|
+
originalStart: [number, number],
|
|
87
|
+
originalEnd: [number, number],
|
|
88
|
+
nextStart: [number, number],
|
|
89
|
+
nextEnd: [number, number],
|
|
90
|
+
) {
|
|
91
|
+
return linkedWalls.map((wall) => ({
|
|
92
|
+
id: wall.id,
|
|
93
|
+
start: samePoint(wall.start, originalStart)
|
|
94
|
+
? nextStart
|
|
95
|
+
: samePoint(wall.start, originalEnd)
|
|
96
|
+
? nextEnd
|
|
97
|
+
: wall.start,
|
|
98
|
+
end: samePoint(wall.end, originalStart)
|
|
99
|
+
? nextStart
|
|
100
|
+
: samePoint(wall.end, originalEnd)
|
|
101
|
+
? nextEnd
|
|
102
|
+
: wall.end,
|
|
103
|
+
}))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export const MoveWallTool: React.FC<{ node: WallNode }> = ({ node }) => {
|
|
107
|
+
const meta =
|
|
108
|
+
typeof node.metadata === 'object' && node.metadata !== null && !Array.isArray(node.metadata)
|
|
109
|
+
? (node.metadata as Record<string, unknown>)
|
|
110
|
+
: {}
|
|
111
|
+
const isNew = !!meta.isNew
|
|
112
|
+
const activatedAtRef = useRef<number>(Date.now())
|
|
113
|
+
const previousGridPosRef = useRef<[number, number] | null>(null)
|
|
114
|
+
const originalStartRef = useRef<[number, number]>([...node.start] as [number, number])
|
|
115
|
+
const originalEndRef = useRef<[number, number]>([...node.end] as [number, number])
|
|
116
|
+
const originalCenterRef = useRef<[number, number]>([
|
|
117
|
+
(node.start[0] + node.end[0]) / 2,
|
|
118
|
+
(node.start[1] + node.end[1]) / 2,
|
|
119
|
+
])
|
|
120
|
+
const originalHalfVectorRef = useRef<[number, number]>([
|
|
121
|
+
(node.end[0] - node.start[0]) / 2,
|
|
122
|
+
(node.end[1] - node.start[1]) / 2,
|
|
123
|
+
])
|
|
124
|
+
const linkedOriginalsRef = useRef(
|
|
125
|
+
isNew
|
|
126
|
+
? []
|
|
127
|
+
: getLinkedWallSnapshots({
|
|
128
|
+
wallId: node.id,
|
|
129
|
+
wallParentId: node.parentId ?? null,
|
|
130
|
+
originalStart: node.start,
|
|
131
|
+
originalEnd: node.end,
|
|
132
|
+
}),
|
|
133
|
+
)
|
|
134
|
+
const dragAnchorRef = useRef<[number, number] | null>(null)
|
|
135
|
+
const nodeIdRef = useRef(node.id)
|
|
136
|
+
const previewRef = useRef<{ start: [number, number]; end: [number, number] } | null>(null)
|
|
137
|
+
const pendingRotationRef = useRef(0)
|
|
138
|
+
const shiftPressedRef = useRef(false)
|
|
139
|
+
|
|
140
|
+
const [cursorLocalPos, setCursorLocalPos] = useState<[number, number, number]>(() => {
|
|
141
|
+
const centerX = (node.start[0] + node.end[0]) / 2
|
|
142
|
+
const centerZ = (node.start[1] + node.end[1]) / 2
|
|
143
|
+
return [centerX, 0, centerZ]
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
const exitMoveMode = useCallback(() => {
|
|
147
|
+
useEditor.getState().setMovingNode(null)
|
|
148
|
+
}, [])
|
|
149
|
+
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
const nodeId = nodeIdRef.current
|
|
152
|
+
const originalStart = originalStartRef.current
|
|
153
|
+
const originalEnd = originalEndRef.current
|
|
154
|
+
const originalCenter = originalCenterRef.current
|
|
155
|
+
const originalHalfVector = originalHalfVectorRef.current
|
|
156
|
+
|
|
157
|
+
pauseSceneHistory(useScene)
|
|
158
|
+
let wasCommitted = false
|
|
159
|
+
|
|
160
|
+
const applyNodePreview = (
|
|
161
|
+
updates: Array<{ id: WallNode['id']; start: [number, number]; end: [number, number] }>,
|
|
162
|
+
) => {
|
|
163
|
+
useScene.getState().updateNodes(
|
|
164
|
+
updates.map((entry) => ({
|
|
165
|
+
id: entry.id as AnyNodeId,
|
|
166
|
+
data: { start: entry.start, end: entry.end },
|
|
167
|
+
})),
|
|
168
|
+
)
|
|
169
|
+
for (const entry of updates) {
|
|
170
|
+
useScene.getState().markDirty(entry.id as AnyNodeId)
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const buildWallFromCenter = (center: [number, number]) => {
|
|
175
|
+
const rotatedHalf = rotateVector(originalHalfVector, pendingRotationRef.current)
|
|
176
|
+
const nextStart: [number, number] = [center[0] - rotatedHalf[0], center[1] - rotatedHalf[1]]
|
|
177
|
+
const nextEnd: [number, number] = [center[0] + rotatedHalf[0], center[1] + rotatedHalf[1]]
|
|
178
|
+
return { start: nextStart, end: nextEnd }
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const applyPreview = (nextStart: [number, number], nextEnd: [number, number]) => {
|
|
182
|
+
previewRef.current = { start: nextStart, end: nextEnd }
|
|
183
|
+
const centerX = (nextStart[0] + nextEnd[0]) / 2
|
|
184
|
+
const centerZ = (nextStart[1] + nextEnd[1]) / 2
|
|
185
|
+
setCursorLocalPos([centerX, 0, centerZ])
|
|
186
|
+
applyNodePreview([
|
|
187
|
+
{ id: nodeId, start: nextStart, end: nextEnd },
|
|
188
|
+
...getLinkedWallUpdates(
|
|
189
|
+
linkedOriginalsRef.current,
|
|
190
|
+
originalStart,
|
|
191
|
+
originalEnd,
|
|
192
|
+
nextStart,
|
|
193
|
+
nextEnd,
|
|
194
|
+
),
|
|
195
|
+
])
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const restoreOriginal = () => {
|
|
199
|
+
applyNodePreview([
|
|
200
|
+
{ id: nodeId, start: originalStart, end: originalEnd },
|
|
201
|
+
...linkedOriginalsRef.current,
|
|
202
|
+
])
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const onGridMove = (event: GridEvent) => {
|
|
206
|
+
const rawX = event.localPosition[0]
|
|
207
|
+
const rawZ = event.localPosition[2]
|
|
208
|
+
const snapStep = getWallGridStep()
|
|
209
|
+
const localX = shiftPressedRef.current ? rawX : snapScalarToGrid(rawX, snapStep)
|
|
210
|
+
const localZ = shiftPressedRef.current ? rawZ : snapScalarToGrid(rawZ, snapStep)
|
|
211
|
+
|
|
212
|
+
if (
|
|
213
|
+
previousGridPosRef.current &&
|
|
214
|
+
(localX !== previousGridPosRef.current[0] || localZ !== previousGridPosRef.current[1])
|
|
215
|
+
) {
|
|
216
|
+
sfxEmitter.emit('sfx:grid-snap')
|
|
217
|
+
}
|
|
218
|
+
previousGridPosRef.current = [localX, localZ]
|
|
219
|
+
|
|
220
|
+
const anchor = dragAnchorRef.current ?? [localX, localZ]
|
|
221
|
+
dragAnchorRef.current = anchor
|
|
222
|
+
|
|
223
|
+
const deltaX = localX - anchor[0]
|
|
224
|
+
const deltaZ = localZ - anchor[1]
|
|
225
|
+
|
|
226
|
+
const nextCenter: [number, number] = [originalCenter[0] + deltaX, originalCenter[1] + deltaZ]
|
|
227
|
+
const nextWall = buildWallFromCenter(nextCenter)
|
|
228
|
+
applyPreview(nextWall.start, nextWall.end)
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const onGridClick = (event: GridEvent) => {
|
|
232
|
+
if (Date.now() - activatedAtRef.current < 150) {
|
|
233
|
+
event.nativeEvent?.stopPropagation?.()
|
|
234
|
+
return
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const preview = previewRef.current ?? { start: originalStart, end: originalEnd }
|
|
238
|
+
|
|
239
|
+
wasCommitted = true
|
|
240
|
+
|
|
241
|
+
// Restore original baseline while paused so the next resume+update
|
|
242
|
+
// registers as a single tracked change (undo reverts to original).
|
|
243
|
+
applyNodePreview([
|
|
244
|
+
{ id: nodeId, start: originalStart, end: originalEnd },
|
|
245
|
+
...linkedOriginalsRef.current,
|
|
246
|
+
])
|
|
247
|
+
|
|
248
|
+
resumeSceneHistory(useScene)
|
|
249
|
+
|
|
250
|
+
const commitUpdates = [
|
|
251
|
+
{
|
|
252
|
+
id: nodeId as AnyNodeId,
|
|
253
|
+
data: isNew
|
|
254
|
+
? {
|
|
255
|
+
start: preview.start,
|
|
256
|
+
end: preview.end,
|
|
257
|
+
metadata: stripWallIsNewMetadata(node.metadata),
|
|
258
|
+
}
|
|
259
|
+
: { start: preview.start, end: preview.end },
|
|
260
|
+
},
|
|
261
|
+
...getLinkedWallUpdates(
|
|
262
|
+
linkedOriginalsRef.current,
|
|
263
|
+
originalStart,
|
|
264
|
+
originalEnd,
|
|
265
|
+
preview.start,
|
|
266
|
+
preview.end,
|
|
267
|
+
).map((entry) => ({
|
|
268
|
+
id: entry.id as AnyNodeId,
|
|
269
|
+
data: { start: entry.start, end: entry.end },
|
|
270
|
+
})),
|
|
271
|
+
]
|
|
272
|
+
useScene.getState().updateNodes(commitUpdates)
|
|
273
|
+
for (const { id } of commitUpdates) {
|
|
274
|
+
useScene.getState().markDirty(id)
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
pauseSceneHistory(useScene)
|
|
278
|
+
|
|
279
|
+
sfxEmitter.emit('sfx:item-place')
|
|
280
|
+
useViewer.getState().setSelection({ selectedIds: [nodeId] })
|
|
281
|
+
exitMoveMode()
|
|
282
|
+
event.nativeEvent?.stopPropagation?.()
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const onKeyDown = (event: KeyboardEvent) => {
|
|
286
|
+
if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement) {
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (event.key === 'Shift') {
|
|
291
|
+
shiftPressedRef.current = true
|
|
292
|
+
return
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const ROTATION_STEP = Math.PI / 4
|
|
296
|
+
let rotationDelta = 0
|
|
297
|
+
if (event.key === 'r' || event.key === 'R') rotationDelta = ROTATION_STEP
|
|
298
|
+
else if (event.key === 't' || event.key === 'T') rotationDelta = -ROTATION_STEP
|
|
299
|
+
|
|
300
|
+
if (rotationDelta === 0) {
|
|
301
|
+
return
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
event.preventDefault()
|
|
305
|
+
pendingRotationRef.current += rotationDelta
|
|
306
|
+
sfxEmitter.emit('sfx:item-rotate')
|
|
307
|
+
|
|
308
|
+
const preview = previewRef.current ?? { start: originalStart, end: originalEnd }
|
|
309
|
+
const currentCenter: [number, number] = [
|
|
310
|
+
(preview.start[0] + preview.end[0]) / 2,
|
|
311
|
+
(preview.start[1] + preview.end[1]) / 2,
|
|
312
|
+
]
|
|
313
|
+
const nextWall = buildWallFromCenter(currentCenter)
|
|
314
|
+
applyPreview(nextWall.start, nextWall.end)
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const onKeyUp = (event: KeyboardEvent) => {
|
|
318
|
+
if (event.key === 'Shift') {
|
|
319
|
+
shiftPressedRef.current = false
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const onCancel = () => {
|
|
324
|
+
restoreOriginal()
|
|
325
|
+
useViewer.getState().setSelection({ selectedIds: [nodeId] })
|
|
326
|
+
resumeSceneHistory(useScene)
|
|
327
|
+
markToolCancelConsumed()
|
|
328
|
+
exitMoveMode()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
emitter.on('grid:move', onGridMove)
|
|
332
|
+
emitter.on('grid:click', onGridClick)
|
|
333
|
+
emitter.on('tool:cancel', onCancel)
|
|
334
|
+
window.addEventListener('keydown', onKeyDown)
|
|
335
|
+
window.addEventListener('keyup', onKeyUp)
|
|
336
|
+
|
|
337
|
+
return () => {
|
|
338
|
+
if (!wasCommitted) {
|
|
339
|
+
restoreOriginal()
|
|
340
|
+
}
|
|
341
|
+
shiftPressedRef.current = false
|
|
342
|
+
resumeSceneHistory(useScene)
|
|
343
|
+
emitter.off('grid:move', onGridMove)
|
|
344
|
+
emitter.off('grid:click', onGridClick)
|
|
345
|
+
emitter.off('tool:cancel', onCancel)
|
|
346
|
+
window.removeEventListener('keydown', onKeyDown)
|
|
347
|
+
window.removeEventListener('keyup', onKeyUp)
|
|
348
|
+
}
|
|
349
|
+
}, [exitMoveMode, isNew, node.metadata])
|
|
350
|
+
|
|
351
|
+
return (
|
|
352
|
+
<group>
|
|
353
|
+
<CursorSphere position={cursorLocalPos} showTooltip={false} />
|
|
354
|
+
</group>
|
|
355
|
+
)
|
|
356
|
+
}
|