@pascal-app/editor 0.5.1 → 0.7.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.
Files changed (150) hide show
  1. package/package.json +12 -7
  2. package/src/components/editor/bottom-sheet.tsx +149 -0
  3. package/src/components/editor/custom-camera-controls.tsx +75 -7
  4. package/src/components/editor/editor-layout-mobile.tsx +264 -0
  5. package/src/components/editor/editor-layout-v2.tsx +29 -0
  6. package/src/components/editor/first-person/build-collider-world.ts +365 -0
  7. package/src/components/editor/first-person/bvh-ecctrl.tsx +795 -0
  8. package/src/components/editor/first-person-controls.tsx +496 -143
  9. package/src/components/editor/floating-action-menu.tsx +281 -83
  10. package/src/components/editor/floating-building-action-menu.tsx +4 -3
  11. package/src/components/editor/floorplan-background-selection.ts +113 -0
  12. package/src/components/editor/floorplan-panel.tsx +10442 -3275
  13. package/src/components/editor/index.tsx +270 -20
  14. package/src/components/editor/node-action-menu.tsx +14 -1
  15. package/src/components/editor/selection-manager.tsx +766 -12
  16. package/src/components/editor/site-edge-labels.tsx +9 -3
  17. package/src/components/editor/thumbnail-generator.tsx +350 -157
  18. package/src/components/editor/use-floorplan-background-placement.ts +257 -0
  19. package/src/components/editor/use-floorplan-hit-testing.ts +171 -0
  20. package/src/components/editor/use-floorplan-scene-data.ts +189 -0
  21. package/src/components/editor/wall-measurement-label.tsx +377 -58
  22. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +95 -0
  23. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +160 -0
  24. package/src/components/editor-2d/floorplan-hotkey-handlers.tsx +92 -0
  25. package/src/components/editor-2d/renderers/floorplan-draft-layer.tsx +119 -0
  26. package/src/components/editor-2d/renderers/floorplan-marquee-layer.tsx +58 -0
  27. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +197 -0
  28. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +113 -0
  29. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +474 -0
  30. package/src/components/editor-2d/svg-paths.ts +119 -0
  31. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +272 -0
  32. package/src/components/systems/roof/roof-edit-system.tsx +5 -5
  33. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +1 -0
  34. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +2 -0
  35. package/src/components/tools/ceiling/ceiling-tool.tsx +5 -5
  36. package/src/components/tools/ceiling/move-ceiling-tool.tsx +257 -0
  37. package/src/components/tools/column/column-tool.tsx +97 -0
  38. package/src/components/tools/column/move-column-tool.tsx +105 -0
  39. package/src/components/tools/door/door-tool.tsx +19 -0
  40. package/src/components/tools/door/move-door-tool.tsx +38 -8
  41. package/src/components/tools/fence/curve-fence-tool.tsx +179 -0
  42. package/src/components/tools/fence/fence-drafting.ts +27 -8
  43. package/src/components/tools/fence/fence-tool.tsx +159 -3
  44. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +438 -0
  45. package/src/components/tools/fence/move-fence-tool.tsx +102 -27
  46. package/src/components/tools/item/move-tool.tsx +19 -1
  47. package/src/components/tools/item/placement-math.ts +44 -7
  48. package/src/components/tools/item/placement-strategies.ts +111 -33
  49. package/src/components/tools/item/placement-types.ts +7 -0
  50. package/src/components/tools/item/use-draft-node.ts +2 -0
  51. package/src/components/tools/item/use-placement-coordinator.tsx +701 -61
  52. package/src/components/tools/roof/move-roof-tool.tsx +111 -43
  53. package/src/components/tools/shared/polygon-editor.tsx +244 -29
  54. package/src/components/tools/shared/segment-angle.ts +156 -0
  55. package/src/components/tools/slab/move-slab-tool.tsx +182 -0
  56. package/src/components/tools/slab/slab-boundary-editor.tsx +1 -0
  57. package/src/components/tools/slab/slab-hole-editor.tsx +2 -0
  58. package/src/components/tools/spawn/move-spawn-tool.tsx +101 -0
  59. package/src/components/tools/spawn/spawn-tool.tsx +130 -0
  60. package/src/components/tools/stair/stair-tool.tsx +11 -3
  61. package/src/components/tools/tool-manager.tsx +30 -3
  62. package/src/components/tools/wall/curve-wall-tool.tsx +176 -0
  63. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +423 -0
  64. package/src/components/tools/wall/move-wall-tool.tsx +356 -0
  65. package/src/components/tools/wall/wall-drafting.ts +348 -17
  66. package/src/components/tools/wall/wall-tool.tsx +134 -2
  67. package/src/components/tools/window/move-window-tool.tsx +28 -0
  68. package/src/components/tools/window/window-tool.tsx +17 -0
  69. package/src/components/ui/action-menu/camera-actions.tsx +37 -33
  70. package/src/components/ui/action-menu/control-modes.tsx +37 -5
  71. package/src/components/ui/action-menu/index.tsx +91 -1
  72. package/src/components/ui/action-menu/structure-tools.tsx +2 -0
  73. package/src/components/ui/action-menu/view-toggles.tsx +424 -35
  74. package/src/components/ui/command-palette/editor-commands.tsx +27 -5
  75. package/src/components/ui/command-palette/index.tsx +0 -1
  76. package/src/components/ui/controls/material-picker.tsx +189 -169
  77. package/src/components/ui/controls/slider-control.tsx +88 -26
  78. package/src/components/ui/floating-level-selector.tsx +286 -55
  79. package/src/components/ui/helpers/helper-manager.tsx +5 -0
  80. package/src/components/ui/item-catalog/catalog-items.tsx +1121 -1219
  81. package/src/components/ui/item-catalog/item-catalog.tsx +42 -175
  82. package/src/components/ui/level-duplicate-dialog.tsx +115 -0
  83. package/src/components/ui/panels/ceiling-panel.tsx +47 -27
  84. package/src/components/ui/panels/column-panel.tsx +715 -0
  85. package/src/components/ui/panels/door-panel.tsx +986 -294
  86. package/src/components/ui/panels/fence-panel.tsx +55 -12
  87. package/src/components/ui/panels/item-panel.tsx +5 -5
  88. package/src/components/ui/panels/mobile-panel-sheet.tsx +108 -0
  89. package/src/components/ui/panels/mobile-selection-bar.tsx +100 -0
  90. package/src/components/ui/panels/node-display.ts +39 -0
  91. package/src/components/ui/panels/paint-panel.tsx +138 -0
  92. package/src/components/ui/panels/panel-manager.tsx +241 -30
  93. package/src/components/ui/panels/panel-wrapper.tsx +48 -39
  94. package/src/components/ui/panels/reference-panel.tsx +243 -9
  95. package/src/components/ui/panels/roof-panel.tsx +30 -62
  96. package/src/components/ui/panels/roof-segment-panel.tsx +8 -23
  97. package/src/components/ui/panels/slab-panel.tsx +46 -24
  98. package/src/components/ui/panels/spawn-panel.tsx +155 -0
  99. package/src/components/ui/panels/stair-panel.tsx +117 -69
  100. package/src/components/ui/panels/stair-segment-panel.tsx +13 -27
  101. package/src/components/ui/panels/wall-panel.tsx +71 -17
  102. package/src/components/ui/panels/window-panel.tsx +665 -146
  103. package/src/components/ui/sidebar/mobile-tab-bar.tsx +46 -0
  104. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +2 -2
  105. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +9 -5
  106. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +7 -3
  107. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +77 -0
  108. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -3
  109. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +7 -3
  110. package/src/components/ui/sidebar/panels/site-panel/index.tsx +138 -56
  111. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +7 -3
  112. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +9 -5
  113. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +7 -3
  114. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +7 -3
  115. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +82 -0
  116. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +7 -3
  117. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +3 -3
  118. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +12 -6
  119. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +7 -3
  120. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -3
  121. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +15 -8
  122. package/src/components/ui/sidebar/tab-bar.tsx +3 -0
  123. package/src/components/ui/viewer-toolbar.tsx +96 -2
  124. package/src/components/viewer-overlay.tsx +25 -19
  125. package/src/hooks/use-auto-frame.ts +45 -0
  126. package/src/hooks/use-contextual-tools.ts +14 -13
  127. package/src/hooks/use-keyboard.ts +67 -9
  128. package/src/hooks/use-mobile.ts +12 -12
  129. package/src/index.tsx +2 -1
  130. package/src/lib/door-interaction.ts +88 -0
  131. package/src/lib/floorplan/geometry.ts +263 -0
  132. package/src/lib/floorplan/index.ts +38 -0
  133. package/src/lib/floorplan/items.ts +179 -0
  134. package/src/lib/floorplan/selection-tool.ts +231 -0
  135. package/src/lib/floorplan/stairs.ts +478 -0
  136. package/src/lib/floorplan/types.ts +57 -0
  137. package/src/lib/floorplan/walls.ts +23 -0
  138. package/src/lib/guide-events.ts +10 -0
  139. package/src/lib/history.ts +20 -0
  140. package/src/lib/level-duplication.test.ts +72 -0
  141. package/src/lib/level-duplication.ts +153 -0
  142. package/src/lib/local-guide-image.ts +42 -0
  143. package/src/lib/material-paint.ts +284 -0
  144. package/src/lib/roof-duplication.ts +214 -0
  145. package/src/lib/scene-bounds.test.ts +183 -0
  146. package/src/lib/scene-bounds.ts +169 -0
  147. package/src/lib/sfx-player.ts +96 -13
  148. package/src/lib/stair-duplication.ts +126 -0
  149. package/src/lib/window-interaction.ts +86 -0
  150. package/src/store/use-editor.tsx +279 -15
@@ -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
+ }