@pascal-app/editor 0.8.0 → 0.9.2

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 (350) hide show
  1. package/package.json +15 -13
  2. package/src/components/editor/alignment-3d-guide-layer.tsx +168 -0
  3. package/src/components/editor/bake-exporter.tsx +47 -0
  4. package/src/components/editor/custom-camera-controls.tsx +795 -28
  5. package/src/components/editor/editor-layout-mobile.tsx +8 -3
  6. package/src/components/editor/editor-layout-v2.tsx +76 -51
  7. package/src/components/editor/export-manager.tsx +69 -32
  8. package/src/components/editor/fence-tangent-lines-3d.tsx +87 -0
  9. package/src/components/editor/first-person/build-collider-world.test.ts +182 -0
  10. package/src/components/editor/first-person/build-collider-world.ts +228 -83
  11. package/src/components/editor/first-person-controls.tsx +943 -27
  12. package/src/components/editor/floating-action-menu.tsx +676 -208
  13. package/src/components/editor/floating-building-action-menu.tsx +2 -2
  14. package/src/components/editor/floorplan-background-selection.test.ts +58 -0
  15. package/src/components/editor/floorplan-background-selection.ts +3 -2
  16. package/src/components/editor/floorplan-panel.tsx +6916 -12137
  17. package/src/components/editor/grid.tsx +197 -36
  18. package/src/components/editor/group-actions.ts +457 -0
  19. package/src/components/editor/group-floating-action-menu.tsx +130 -0
  20. package/src/components/editor/group-move-3d.ts +397 -0
  21. package/src/components/editor/group-rotate-handle.tsx +420 -0
  22. package/src/components/editor/group-selection-box-3d.tsx +173 -0
  23. package/src/components/editor/group-transform-shared.test.ts +396 -0
  24. package/src/components/editor/group-transform-shared.ts +489 -0
  25. package/src/components/editor/handles/handle-arrow.tsx +581 -0
  26. package/src/components/editor/handles/use-handle-drag.ts +246 -0
  27. package/src/components/editor/index.tsx +412 -162
  28. package/src/components/editor/measurement-pill.tsx +115 -0
  29. package/src/components/editor/node-action-menu.tsx +14 -1
  30. package/src/components/editor/node-arrow-handles.tsx +1646 -0
  31. package/src/components/editor/opening-guides-3d-layer.tsx +144 -0
  32. package/src/components/editor/quick-measurement-card.tsx +70 -0
  33. package/src/components/editor/quick-measurement-hud.tsx +28 -0
  34. package/src/components/editor/riser-diagram-panel.tsx +137 -0
  35. package/src/components/editor/selection-manager.tsx +1106 -483
  36. package/src/components/editor/site-edge-labels.tsx +45 -21
  37. package/src/components/editor/slab-hole-highlights.tsx +474 -0
  38. package/src/components/editor/snapshot-capture-overlay.tsx +369 -155
  39. package/src/components/editor/three-context-bridge.ts +22 -0
  40. package/src/components/editor/thumbnail-generator.tsx +140 -41
  41. package/src/components/editor/use-floorplan-background-placement.ts +190 -35
  42. package/src/components/editor/use-floorplan-hit-testing.ts +24 -0
  43. package/src/components/editor/use-floorplan-scene-data.ts +19 -7
  44. package/src/components/editor/use-mesh-settle-epoch.ts +26 -0
  45. package/src/components/editor/wall-measurement-label.tsx +14 -19
  46. package/src/components/editor/wall-move-side-handles.tsx +870 -0
  47. package/src/components/editor/wall-opening-highlights.tsx +181 -0
  48. package/src/components/editor/wall-snap-beacon-layer.tsx +304 -0
  49. package/src/components/editor-2d/floorplan-action-menu-layer.tsx +15 -6
  50. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +142 -0
  51. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +5 -3
  52. package/src/components/editor-2d/floorplan-group-action-menu.tsx +87 -0
  53. package/src/components/editor-2d/floorplan-group-move.tsx +701 -0
  54. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +113 -0
  55. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1656 -0
  56. package/src/components/editor-2d/floorplan-quick-measure-layer.tsx +212 -0
  57. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +392 -0
  58. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +857 -0
  59. package/src/components/editor-2d/floorplan-render-context.tsx +62 -0
  60. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +77 -0
  61. package/src/components/editor-2d/floorplan-wall-move-ghost-layer.tsx +47 -0
  62. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +266 -0
  63. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +15 -0
  64. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +14 -0
  65. package/src/components/editor-2d/renderers/floorplan-measurements-layer.tsx +108 -66
  66. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +62 -0
  67. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +308 -0
  68. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +3325 -0
  69. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +24 -9
  70. package/src/components/editor-2d/renderers/floorplan-voronoi-layer.tsx +128 -0
  71. package/src/components/editor-2d/svg-paths.ts +10 -2
  72. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +439 -57
  73. package/src/components/systems/ceiling/ceiling-system.tsx +109 -12
  74. package/src/components/systems/roof/roof-edit-system.tsx +1851 -22
  75. package/src/components/systems/selection-affordance-manager.tsx +38 -0
  76. package/src/components/systems/stair/stair-edit-system.tsx +1 -1
  77. package/src/components/systems/zone/zone-label-editor-system.tsx +14 -1
  78. package/src/components/systems/zone/zone-system.tsx +42 -13
  79. package/src/components/tools/elevator/elevator-defaults.ts +8 -0
  80. package/src/components/tools/elevator/elevator-tool.tsx +375 -0
  81. package/src/components/tools/elevator/move-elevator-tool.tsx +263 -0
  82. package/src/components/tools/fence/fence-drafting.ts +141 -27
  83. package/src/components/tools/item/move-tool.tsx +45 -105
  84. package/src/components/tools/item/placement-math.test.ts +22 -0
  85. package/src/components/tools/item/placement-math.ts +59 -7
  86. package/src/components/tools/item/placement-strategies.ts +432 -16
  87. package/src/components/tools/item/placement-types.ts +22 -1
  88. package/src/components/tools/item/use-draft-node.ts +51 -2
  89. package/src/components/tools/item/use-placement-coordinator.tsx +1399 -256
  90. package/src/components/tools/registry/move-registry-node-tool.tsx +1064 -0
  91. package/src/components/tools/roof/roof-tool.tsx +350 -28
  92. package/src/components/tools/select/box-select-state.ts +88 -0
  93. package/src/components/tools/select/box-select-tool.tsx +442 -520
  94. package/src/components/tools/select/marquee-geometry.test.ts +161 -0
  95. package/src/components/tools/select/marquee-geometry.ts +155 -0
  96. package/src/components/tools/select/plane-box-select-tool.tsx +570 -0
  97. package/src/components/tools/select/screen-rectangle-selection.ts +84 -0
  98. package/src/components/tools/select/select-candidates.test.ts +109 -0
  99. package/src/components/tools/select/select-candidates.ts +85 -0
  100. package/src/components/tools/shared/affordance-dispatch.ts +30 -0
  101. package/src/components/tools/shared/cursor-sphere.tsx +62 -26
  102. package/src/components/tools/shared/drag-bounding-box.tsx +195 -0
  103. package/src/components/tools/shared/facing-indicator.tsx +87 -0
  104. package/src/components/tools/shared/facing-pose-indicator.tsx +54 -0
  105. package/src/components/tools/shared/floor-stack-preview.ts +25 -0
  106. package/src/components/tools/shared/fresh-placement-visibility.ts +61 -0
  107. package/src/components/tools/shared/placement-box-geometry.ts +123 -0
  108. package/src/components/tools/shared/placement-box.tsx +305 -0
  109. package/src/components/tools/shared/polygon-editor.tsx +866 -175
  110. package/src/components/tools/shared/segment-angle.ts +56 -0
  111. package/src/components/tools/site/site-boundary-editor.tsx +520 -16
  112. package/src/components/tools/site/site-flag-model.ts +18 -0
  113. package/src/components/tools/stair/stair-defaults.ts +2 -1
  114. package/src/components/tools/stair/stair-tool.tsx +342 -37
  115. package/src/components/tools/tool-manager.tsx +239 -62
  116. package/src/components/tools/wall/wall-drafting.test.ts +330 -0
  117. package/src/components/tools/wall/wall-drafting.ts +248 -171
  118. package/src/components/tools/wall/wall-snap-geometry.test.ts +123 -0
  119. package/src/components/tools/wall/wall-snap-geometry.ts +331 -0
  120. package/src/components/tools/zone/zone-boundary-editor.tsx +5 -1
  121. package/src/components/tools/zone/zone-tool.tsx +86 -82
  122. package/src/components/ui/action-menu/action-button.tsx +21 -1
  123. package/src/components/ui/action-menu/camera-actions.tsx +24 -17
  124. package/src/components/ui/action-menu/control-modes.tsx +40 -170
  125. package/src/components/ui/action-menu/furnish-tools.tsx +5 -5
  126. package/src/components/ui/action-menu/index.tsx +3 -117
  127. package/src/components/ui/action-menu/measurement-control.tsx +188 -0
  128. package/src/components/ui/action-menu/structure-tools.tsx +27 -93
  129. package/src/components/ui/action-menu/view-toggles.tsx +279 -90
  130. package/src/components/ui/command-palette/editor-commands.tsx +20 -15
  131. package/src/components/ui/command-palette/index.tsx +5 -3
  132. package/src/components/ui/controls/material-paint-panel.tsx +152 -0
  133. package/src/components/ui/controls/material-picker.tsx +87 -177
  134. package/src/components/ui/controls/material-properties-editor.tsx +108 -0
  135. package/src/components/ui/controls/metric-control.tsx +142 -46
  136. package/src/components/ui/controls/scene-material-list.tsx +247 -0
  137. package/src/components/ui/controls/slider-control.tsx +88 -38
  138. package/src/components/ui/floating-level-selector.tsx +47 -10
  139. package/src/components/ui/helpers/building-helper.tsx +10 -23
  140. package/src/components/ui/helpers/contextual-helper-panel.tsx +428 -0
  141. package/src/components/ui/helpers/helper-manager.tsx +235 -23
  142. package/src/components/ui/helpers/item-helper.tsx +28 -32
  143. package/src/components/ui/helpers/registered-tool-helper.tsx +59 -0
  144. package/src/components/ui/helpers/roof-helper.tsx +10 -12
  145. package/src/components/ui/icon-ref.tsx +47 -0
  146. package/src/components/ui/item-catalog/catalog-items.tsx +40 -1
  147. package/src/components/ui/item-catalog/item-catalog.tsx +16 -36
  148. package/src/components/ui/level-duplicate-dialog.tsx +2 -1
  149. package/src/components/ui/panels/node-display.ts +17 -16
  150. package/src/components/ui/panels/panel-manager.tsx +55 -58
  151. package/src/components/ui/panels/panel-wrapper.tsx +238 -12
  152. package/src/components/ui/panels/parametric-inspector.tsx +482 -0
  153. package/src/components/ui/panels/reference-panel.tsx +54 -19
  154. package/src/components/ui/primitives/dropdown-menu.tsx +12 -8
  155. package/src/components/ui/primitives/shortcut-token.tsx +39 -3
  156. package/src/components/ui/primitives/sidebar.tsx +1 -0
  157. package/src/components/ui/scene-loader.tsx +1 -3
  158. package/src/components/ui/sidebar/app-sidebar.tsx +5 -1
  159. package/src/components/ui/sidebar/icon-rail.tsx +50 -31
  160. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +263 -0
  161. package/src/components/ui/sidebar/panels/items-panel/index.tsx +82 -8
  162. package/src/components/ui/sidebar/panels/plugins-panel.tsx +218 -0
  163. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +122 -41
  164. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +66 -7
  165. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +267 -0
  166. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +3 -1
  167. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +9 -5
  168. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +86 -0
  169. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -1
  170. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +7 -2
  171. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +92 -0
  172. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +75 -0
  173. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +3 -3
  174. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +86 -0
  175. package/src/components/ui/sidebar/panels/site-panel/index.tsx +42 -21
  176. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +31 -17
  177. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +2 -1
  178. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +140 -0
  179. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +48 -8
  180. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +132 -0
  181. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -5
  182. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +86 -0
  183. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +7 -1
  184. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +2 -2
  185. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +112 -39
  186. package/src/components/ui/sidebar/panels/site-panel/tree-structure.ts +36 -0
  187. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -1
  188. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +7 -2
  189. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +3 -3
  190. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +51 -2
  191. package/src/components/ui/sidebar/tab-bar.tsx +75 -1
  192. package/src/components/ui/sidebar/use-plugin-panels.tsx +132 -0
  193. package/src/components/ui/snap-target-badge.test.tsx +40 -0
  194. package/src/components/ui/snap-target-badge.tsx +88 -0
  195. package/src/components/viewer-overlay.tsx +253 -61
  196. package/src/components/viewer-zone-system.tsx +7 -2
  197. package/src/hooks/use-auto-save.test.ts +14 -0
  198. package/src/hooks/use-auto-save.ts +64 -10
  199. package/src/hooks/use-ceiling-events.ts +176 -0
  200. package/src/hooks/use-drag-action.ts +127 -0
  201. package/src/hooks/use-keyboard.ts +493 -95
  202. package/src/hooks/use-selection.ts +64 -0
  203. package/src/index.tsx +451 -6
  204. package/src/lib/active-placement-surface.ts +35 -0
  205. package/src/lib/ceiling-plan-snap.ts +24 -0
  206. package/src/lib/constants.ts +6 -2
  207. package/src/lib/contextual-help.test.ts +112 -0
  208. package/src/lib/contextual-help.ts +144 -0
  209. package/src/lib/continuation.ts +64 -0
  210. package/src/lib/direct-manipulation.test.ts +192 -0
  211. package/src/lib/direct-manipulation.ts +109 -0
  212. package/src/lib/editor-api.ts +50 -0
  213. package/src/lib/elevator-support.ts +96 -0
  214. package/src/lib/floorplan/apply-alignment.test.ts +25 -0
  215. package/src/lib/floorplan/apply-alignment.ts +117 -0
  216. package/src/lib/floorplan/floorplan-export.tsx +364 -0
  217. package/src/lib/floorplan/geometry.ts +53 -0
  218. package/src/lib/floorplan/index.ts +10 -0
  219. package/src/lib/floorplan/items.ts +5 -2
  220. package/src/lib/floorplan/plan-coords.ts +21 -0
  221. package/src/lib/floorplan/selection-tool.ts +40 -0
  222. package/src/lib/floorplan/stairs.ts +12 -1
  223. package/src/lib/fresh-planar-placement.test.ts +339 -0
  224. package/src/lib/fresh-planar-placement.ts +130 -0
  225. package/src/lib/glb-export.test.ts +441 -0
  226. package/src/lib/glb-export.ts +874 -0
  227. package/src/lib/history.ts +11 -1
  228. package/src/lib/interaction/hot-set.test.ts +133 -0
  229. package/src/lib/interaction/hot-set.ts +67 -0
  230. package/src/lib/interaction/overlay-policy.test.ts +51 -0
  231. package/src/lib/interaction/overlay-policy.ts +59 -0
  232. package/src/lib/interaction/scope.ts +211 -0
  233. package/src/lib/level-duplication.ts +54 -42
  234. package/src/lib/level-selection.ts +2 -2
  235. package/src/lib/material-paint.ts +227 -48
  236. package/src/lib/measurement-kind.test.ts +30 -0
  237. package/src/lib/measurement-kind.ts +19 -0
  238. package/src/lib/measurement-label.test.ts +47 -0
  239. package/src/lib/measurement-label.ts +68 -0
  240. package/src/lib/measurement-parser.ts +116 -0
  241. package/src/lib/measurements.test.ts +183 -0
  242. package/src/lib/measurements.ts +199 -0
  243. package/src/lib/paint-scope.test.ts +454 -0
  244. package/src/lib/paint-scope.ts +461 -0
  245. package/src/lib/placement-drag-release.ts +23 -0
  246. package/src/lib/placement-metadata.ts +29 -0
  247. package/src/lib/planar-cursor-placement.test.ts +100 -0
  248. package/src/lib/planar-cursor-placement.ts +42 -0
  249. package/src/lib/plugin-panels.test.ts +19 -0
  250. package/src/lib/plugin-panels.ts +91 -0
  251. package/src/lib/quick-measurement.test.ts +77 -0
  252. package/src/lib/quick-measurement.ts +109 -0
  253. package/src/lib/roof-duplication.ts +7 -7
  254. package/src/lib/roof-hover-outline-proxy.ts +22 -0
  255. package/src/lib/roof-wall-hit.ts +164 -0
  256. package/src/lib/scene-clipboard.test.ts +196 -0
  257. package/src/lib/scene-clipboard.ts +332 -0
  258. package/src/lib/scene.ts +40 -8
  259. package/src/lib/selection-routing.test.ts +298 -0
  260. package/src/lib/selection-routing.ts +175 -0
  261. package/src/lib/sfx/index.ts +1 -0
  262. package/src/lib/sfx/movement-tick.test.ts +65 -0
  263. package/src/lib/sfx/movement-tick.ts +18 -0
  264. package/src/lib/sfx-bus.ts +69 -10
  265. package/src/lib/sfx-player.test.ts +124 -0
  266. package/src/lib/sfx-player.ts +148 -55
  267. package/src/lib/site-boundary.ts +1 -0
  268. package/src/lib/slab-plan-snap.test.ts +93 -0
  269. package/src/lib/slab-plan-snap.ts +108 -0
  270. package/src/lib/snapping-mode.test.ts +138 -0
  271. package/src/lib/snapping-mode.ts +179 -0
  272. package/src/lib/stair-duplication.ts +4 -4
  273. package/src/lib/stair-levels.test.ts +146 -0
  274. package/src/lib/stair-levels.ts +177 -0
  275. package/src/lib/surface-plan-snap.test.ts +71 -0
  276. package/src/lib/surface-plan-snap.ts +256 -0
  277. package/src/lib/use-linear-display.ts +40 -0
  278. package/src/lib/world-grid-snap.ts +231 -0
  279. package/src/lib/zone-content.ts +130 -0
  280. package/src/store/use-alignment-guides.ts +21 -0
  281. package/src/store/use-direct-manipulation-feedback.ts +20 -0
  282. package/src/store/use-editor.tsx +767 -114
  283. package/src/store/use-facing-pose.ts +44 -0
  284. package/src/store/use-fence-curve-draft.ts +21 -0
  285. package/src/store/use-floorplan-draft-preview.ts +101 -0
  286. package/src/store/use-floorplan-marquee.ts +50 -0
  287. package/src/store/use-interaction-scope.test.ts +186 -0
  288. package/src/store/use-interaction-scope.ts +132 -0
  289. package/src/store/use-measurement-draft.test.ts +530 -0
  290. package/src/store/use-measurement-draft.ts +543 -0
  291. package/src/store/use-opening-guides.ts +41 -0
  292. package/src/store/use-placement-preview.ts +39 -0
  293. package/src/store/use-quick-measurement-hud.test.ts +53 -0
  294. package/src/store/use-quick-measurement-hud.ts +77 -0
  295. package/src/store/use-segment-draft-chain.ts +28 -0
  296. package/src/store/use-stair-build-preview.ts +43 -0
  297. package/src/store/use-wall-move-ghosts.ts +36 -0
  298. package/src/store/use-wall-snap-indicator.ts +34 -0
  299. package/src/components/editor/first-person/bvh-ecctrl.tsx +0 -860
  300. package/src/components/editor/preset-thumbnail-generator.tsx +0 -125
  301. package/src/components/editor-2d/renderers/floorplan-roof-layer.tsx +0 -113
  302. package/src/components/tools/building/move-building-tool.tsx +0 -157
  303. package/src/components/tools/ceiling/ceiling-boundary-editor.tsx +0 -43
  304. package/src/components/tools/ceiling/ceiling-hole-editor.tsx +0 -49
  305. package/src/components/tools/ceiling/ceiling-tool.tsx +0 -465
  306. package/src/components/tools/ceiling/move-ceiling-tool.tsx +0 -264
  307. package/src/components/tools/column/column-tool.tsx +0 -97
  308. package/src/components/tools/column/move-column-tool.tsx +0 -105
  309. package/src/components/tools/door/door-math.ts +0 -110
  310. package/src/components/tools/door/door-tool.tsx +0 -324
  311. package/src/components/tools/door/move-door-tool.tsx +0 -412
  312. package/src/components/tools/fence/curve-fence-tool.tsx +0 -178
  313. package/src/components/tools/fence/fence-tool.tsx +0 -346
  314. package/src/components/tools/fence/move-fence-endpoint-tool.tsx +0 -441
  315. package/src/components/tools/fence/move-fence-tool.tsx +0 -302
  316. package/src/components/tools/item/item-tool.tsx +0 -26
  317. package/src/components/tools/roof/move-roof-tool.tsx +0 -364
  318. package/src/components/tools/slab/move-slab-tool.tsx +0 -182
  319. package/src/components/tools/slab/slab-boundary-editor.tsx +0 -43
  320. package/src/components/tools/slab/slab-hole-editor.tsx +0 -49
  321. package/src/components/tools/slab/slab-tool.tsx +0 -322
  322. package/src/components/tools/spawn/move-spawn-tool.tsx +0 -101
  323. package/src/components/tools/spawn/spawn-tool.tsx +0 -130
  324. package/src/components/tools/wall/curve-wall-tool.tsx +0 -178
  325. package/src/components/tools/wall/move-wall-endpoint-tool.tsx +0 -426
  326. package/src/components/tools/wall/move-wall-tool.tsx +0 -358
  327. package/src/components/tools/wall/wall-tool.tsx +0 -332
  328. package/src/components/tools/window/move-window-tool.tsx +0 -447
  329. package/src/components/tools/window/window-math.ts +0 -117
  330. package/src/components/tools/window/window-tool.tsx +0 -332
  331. package/src/components/ui/helpers/ceiling-helper.tsx +0 -20
  332. package/src/components/ui/helpers/slab-helper.tsx +0 -20
  333. package/src/components/ui/helpers/wall-helper.tsx +0 -20
  334. package/src/components/ui/panels/ceiling-panel.tsx +0 -249
  335. package/src/components/ui/panels/column-panel.tsx +0 -759
  336. package/src/components/ui/panels/door-panel.tsx +0 -1299
  337. package/src/components/ui/panels/fence-panel.tsx +0 -222
  338. package/src/components/ui/panels/item-panel.tsx +0 -306
  339. package/src/components/ui/panels/paint-panel.tsx +0 -163
  340. package/src/components/ui/panels/presets/presets-popover.tsx +0 -511
  341. package/src/components/ui/panels/roof-panel.tsx +0 -280
  342. package/src/components/ui/panels/roof-segment-panel.tsx +0 -311
  343. package/src/components/ui/panels/slab-panel.tsx +0 -250
  344. package/src/components/ui/panels/spawn-panel.tsx +0 -161
  345. package/src/components/ui/panels/stair-panel.tsx +0 -577
  346. package/src/components/ui/panels/stair-segment-panel.tsx +0 -325
  347. package/src/components/ui/panels/wall-panel.tsx +0 -273
  348. package/src/components/ui/panels/window-panel.tsx +0 -968
  349. package/src/contexts/presets-context.tsx +0 -121
  350. package/src/hooks/use-contextual-tools.ts +0 -61
@@ -0,0 +1,570 @@
1
+ import '../../../three-types'
2
+
3
+ import { Icon } from '@iconify/react'
4
+ import {
5
+ type AnyNodeId,
6
+ emitter,
7
+ type GridEvent,
8
+ sceneRegistry,
9
+ useScene,
10
+ type ZoneNode,
11
+ } from '@pascal-app/core'
12
+ import { useViewer } from '@pascal-app/viewer'
13
+ import { useThree } from '@react-three/fiber'
14
+ import { useCallback, useEffect, useRef } from 'react'
15
+ import {
16
+ Box3,
17
+ BufferAttribute,
18
+ BufferGeometry,
19
+ DoubleSide,
20
+ type Group,
21
+ LineBasicMaterial,
22
+ LineSegments,
23
+ type Mesh,
24
+ Plane,
25
+ Raycaster,
26
+ Vector2,
27
+ Vector3,
28
+ } from 'three'
29
+ import { EDITOR_LAYER } from '../../../lib/constants'
30
+ import { sfxEmitter } from '../../../lib/sfx-bus'
31
+ import useEditor from '../../../store/use-editor'
32
+ import { CursorSphere } from '../shared/cursor-sphere'
33
+ import { isBoxSelectPointerSuppressed, markBoxSelectHandled } from './box-select-state'
34
+ import { collectSelectableCandidateIds } from './select-candidates'
35
+
36
+ type Bounds = { minX: number; maxX: number; minZ: number; maxZ: number }
37
+
38
+ const BOX_SELECT_ACCENT_COLOR = '#818cf8'
39
+ const DRAG_THRESHOLD_PX = 4
40
+ const tempVec = new Vector3()
41
+ const tempBox = new Box3()
42
+
43
+ function pointInBounds(x: number, z: number, b: Bounds): boolean {
44
+ return x >= b.minX && x <= b.maxX && z >= b.minZ && z <= b.maxZ
45
+ }
46
+
47
+ function segmentsIntersect(
48
+ ax1: number,
49
+ az1: number,
50
+ ax2: number,
51
+ az2: number,
52
+ bx1: number,
53
+ bz1: number,
54
+ bx2: number,
55
+ bz2: number,
56
+ ): boolean {
57
+ const d1 = cross(bx1, bz1, bx2, bz2, ax1, az1)
58
+ const d2 = cross(bx1, bz1, bx2, bz2, ax2, az2)
59
+ const d3 = cross(ax1, az1, ax2, az2, bx1, bz1)
60
+ const d4 = cross(ax1, az1, ax2, az2, bx2, bz2)
61
+
62
+ if (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) {
63
+ return true
64
+ }
65
+
66
+ if (d1 === 0 && onSegment(bx1, bz1, bx2, bz2, ax1, az1)) return true
67
+ if (d2 === 0 && onSegment(bx1, bz1, bx2, bz2, ax2, az2)) return true
68
+ if (d3 === 0 && onSegment(ax1, az1, ax2, az2, bx1, bz1)) return true
69
+ if (d4 === 0 && onSegment(ax1, az1, ax2, az2, bx2, bz2)) return true
70
+
71
+ return false
72
+ }
73
+
74
+ function cross(ax: number, az: number, bx: number, bz: number, cx: number, cz: number): number {
75
+ return (bx - ax) * (cz - az) - (bz - az) * (cx - ax)
76
+ }
77
+
78
+ function onSegment(
79
+ ax: number,
80
+ az: number,
81
+ bx: number,
82
+ bz: number,
83
+ cx: number,
84
+ cz: number,
85
+ ): boolean {
86
+ return (
87
+ Math.min(ax, bx) <= cx &&
88
+ cx <= Math.max(ax, bx) &&
89
+ Math.min(az, bz) <= cz &&
90
+ cz <= Math.max(az, bz)
91
+ )
92
+ }
93
+
94
+ function segmentIntersectsBounds(
95
+ x1: number,
96
+ z1: number,
97
+ x2: number,
98
+ z2: number,
99
+ b: Bounds,
100
+ ): boolean {
101
+ if (pointInBounds(x1, z1, b) || pointInBounds(x2, z2, b)) return true
102
+
103
+ const edges: [number, number, number, number][] = [
104
+ [b.minX, b.minZ, b.maxX, b.minZ],
105
+ [b.maxX, b.minZ, b.maxX, b.maxZ],
106
+ [b.maxX, b.maxZ, b.minX, b.maxZ],
107
+ [b.minX, b.maxZ, b.minX, b.minZ],
108
+ ]
109
+ for (const [ex1, ez1, ex2, ez2] of edges) {
110
+ if (segmentsIntersect(x1, z1, x2, z2, ex1, ez1, ex2, ez2)) return true
111
+ }
112
+ return false
113
+ }
114
+
115
+ function polygonIntersectsBounds(polygon: [number, number][], b: Bounds): boolean {
116
+ if (polygon.some(([x, z]) => pointInBounds(x, z, b))) return true
117
+
118
+ const corners: [number, number][] = [
119
+ [b.minX, b.minZ],
120
+ [b.maxX, b.minZ],
121
+ [b.maxX, b.maxZ],
122
+ [b.minX, b.maxZ],
123
+ ]
124
+ if (corners.some(([cx, cz]) => pointInPolygon(cx, cz, polygon))) return true
125
+
126
+ const edges: [number, number, number, number][] = [
127
+ [b.minX, b.minZ, b.maxX, b.minZ],
128
+ [b.maxX, b.minZ, b.maxX, b.maxZ],
129
+ [b.maxX, b.maxZ, b.minX, b.maxZ],
130
+ [b.minX, b.maxZ, b.minX, b.minZ],
131
+ ]
132
+ for (let i = 0; i < polygon.length; i++) {
133
+ const [px1, pz1] = polygon[i]!
134
+ const [px2, pz2] = polygon[(i + 1) % polygon.length]!
135
+ for (const [ex1, ez1, ex2, ez2] of edges) {
136
+ if (segmentsIntersect(px1, pz1, px2, pz2, ex1, ez1, ex2, ez2)) return true
137
+ }
138
+ }
139
+
140
+ return false
141
+ }
142
+
143
+ function pointInPolygon(x: number, z: number, polygon: [number, number][]): boolean {
144
+ let inside = false
145
+ for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
146
+ const [xi, zi] = polygon[i]!
147
+ const [xj, zj] = polygon[j]!
148
+ if (zi > z !== zj > z && x < ((xj - xi) * (z - zi)) / (zj - zi) + xi) {
149
+ inside = !inside
150
+ }
151
+ }
152
+ return inside
153
+ }
154
+
155
+ function getNodeWorldXZ(nodeId: string): [number, number] | null {
156
+ const obj = sceneRegistry.nodes.get(nodeId)
157
+ if (!obj) return null
158
+ obj.getWorldPosition(tempVec)
159
+ return [tempVec.x, tempVec.z]
160
+ }
161
+
162
+ function objectBoundsIntersectsBounds(nodeId: string, bounds: Bounds): boolean {
163
+ const obj = sceneRegistry.nodes.get(nodeId)
164
+ if (!obj) return false
165
+
166
+ obj.updateWorldMatrix(true, true)
167
+ tempBox.setFromObject(obj)
168
+
169
+ if (tempBox.isEmpty()) {
170
+ const xz = getNodeWorldXZ(nodeId)
171
+ return Boolean(xz && pointInBounds(xz[0], xz[1], bounds))
172
+ }
173
+
174
+ return !(
175
+ tempBox.max.x < bounds.minX ||
176
+ tempBox.min.x > bounds.maxX ||
177
+ tempBox.max.z < bounds.minZ ||
178
+ tempBox.min.z > bounds.maxZ
179
+ )
180
+ }
181
+
182
+ function collectNodeIdsInPlaneBounds(bounds: Bounds | null): string[] {
183
+ const candidateIds = collectSelectableCandidateIds()
184
+ if (!bounds) return candidateIds
185
+
186
+ const { nodes } = useScene.getState()
187
+ return candidateIds.filter((id) => {
188
+ const node = nodes[id as AnyNodeId]
189
+ if (!node) return false
190
+
191
+ if (node.type === 'wall' || node.type === 'fence') {
192
+ return segmentIntersectsBounds(node.start[0], node.start[1], node.end[0], node.end[1], bounds)
193
+ }
194
+
195
+ if (node.type === 'slab' || node.type === 'ceiling' || node.type === 'zone') {
196
+ return polygonIntersectsBounds(node.polygon, bounds)
197
+ }
198
+
199
+ return objectBoundsIntersectsBounds(id, bounds)
200
+ })
201
+ }
202
+
203
+ function haveSameIds(currentIds: string[], nextIds: string[]): boolean {
204
+ return (
205
+ currentIds.length === nextIds.length &&
206
+ currentIds.every((currentId, index) => currentId === nextIds[index])
207
+ )
208
+ }
209
+
210
+ function updateRectVisuals(
211
+ fillMesh: Mesh,
212
+ outline: LineSegments,
213
+ start: Vector3,
214
+ end: Vector3,
215
+ y: number,
216
+ ) {
217
+ const cx = (start.x + end.x) / 2
218
+ const cz = (start.z + end.z) / 2
219
+ const w = Math.abs(end.x - start.x)
220
+ const h = Math.abs(end.z - start.z)
221
+
222
+ if (w < 0.01 && h < 0.01) {
223
+ fillMesh.visible = false
224
+ outline.visible = false
225
+ return
226
+ }
227
+
228
+ fillMesh.visible = true
229
+ fillMesh.position.set(cx, y + 0.02, cz)
230
+ fillMesh.scale.set(w, h, 1)
231
+
232
+ outline.visible = true
233
+ const oy = y + 0.03
234
+ const x0 = cx - w / 2
235
+ const x1 = cx + w / 2
236
+ const z0 = cz - h / 2
237
+ const z1 = cz + h / 2
238
+ const pos = outline.geometry.attributes.position as BufferAttribute
239
+ pos.setXYZ(0, x0, oy, z0)
240
+ pos.setXYZ(1, x1, oy, z0)
241
+ pos.setXYZ(2, x1, oy, z0)
242
+ pos.setXYZ(3, x1, oy, z1)
243
+ pos.setXYZ(4, x1, oy, z1)
244
+ pos.setXYZ(5, x0, oy, z1)
245
+ pos.setXYZ(6, x0, oy, z1)
246
+ pos.setXYZ(7, x0, oy, z0)
247
+ pos.needsUpdate = true
248
+ }
249
+
250
+ function createOutlineSegments(): LineSegments {
251
+ const geometry = new BufferGeometry()
252
+ geometry.setAttribute('position', new BufferAttribute(new Float32Array(8 * 3), 3))
253
+
254
+ const material = new LineBasicMaterial({
255
+ color: BOX_SELECT_ACCENT_COLOR,
256
+ depthTest: false,
257
+ depthWrite: false,
258
+ transparent: true,
259
+ opacity: 0.85,
260
+ })
261
+
262
+ const segments = new LineSegments(geometry, material)
263
+ segments.layers.set(EDITOR_LAYER)
264
+ segments.renderOrder = 2
265
+ segments.visible = false
266
+ segments.frustumCulled = false
267
+
268
+ return segments
269
+ }
270
+
271
+ function getSnappedGridPosition(x: number, z: number): [number, number] {
272
+ return [Math.round(x * 2) / 2, Math.round(z * 2) / 2]
273
+ }
274
+
275
+ function setSnappedPoint(target: Vector3, x: number, y: number, z: number) {
276
+ const [snappedX, snappedZ] = getSnappedGridPosition(x, z)
277
+ target.set(snappedX, y, snappedZ)
278
+ }
279
+
280
+ const BOX_SELECT_TOOLTIP = (
281
+ <Icon
282
+ color="currentColor"
283
+ height={24}
284
+ icon="mdi:select-drag"
285
+ style={{ filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.5))' }}
286
+ width={24}
287
+ />
288
+ )
289
+
290
+ export const PlaneBoxSelectTool: React.FC = () => {
291
+ const { camera, gl } = useThree()
292
+ const setPreviewSelectedIds = useViewer((state) => state.setPreviewSelectedIds)
293
+ const cursorRef = useRef<Group>(null)
294
+ const rectFillRef = useRef<Mesh>(null!)
295
+ const outlineRef = useRef(createOutlineSegments())
296
+ const startPoint = useRef(new Vector3())
297
+ const currentPoint = useRef(new Vector3())
298
+ const pointerDown = useRef(false)
299
+ const isDragging = useRef(false)
300
+ const startClientX = useRef(0)
301
+ const startClientY = useRef(0)
302
+ const gridY = useRef(0)
303
+ const previousGridPosition = useRef<[number, number] | null>(null)
304
+ const previewSelectedIdsRef = useRef<string[]>([])
305
+ const spaceDownRef = useRef(false)
306
+ const raycasterRef = useRef(new Raycaster())
307
+ const pointerNDC = useRef(new Vector2())
308
+ const groundPlane = useRef(new Plane(new Vector3(0, 1, 0), 0))
309
+ const hitPoint = useRef(new Vector3())
310
+
311
+ const syncPreviewSelectedIds = useCallback(
312
+ (nextIds: string[]) => {
313
+ if (haveSameIds(previewSelectedIdsRef.current, nextIds)) return
314
+ previewSelectedIdsRef.current = nextIds
315
+ setPreviewSelectedIds(nextIds)
316
+ },
317
+ [setPreviewSelectedIds],
318
+ )
319
+
320
+ const resetDrag = useCallback(() => {
321
+ pointerDown.current = false
322
+ isDragging.current = false
323
+ rectFillRef.current.visible = false
324
+ outlineRef.current.visible = false
325
+ syncPreviewSelectedIds([])
326
+ }, [syncPreviewSelectedIds])
327
+
328
+ const raycastToGround = useCallback(
329
+ (event: PointerEvent): Vector3 | null => {
330
+ const rect = gl.domElement.getBoundingClientRect()
331
+ pointerNDC.current.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
332
+ pointerNDC.current.y = -((event.clientY - rect.top) / rect.height) * 2 + 1
333
+ raycasterRef.current.setFromCamera(pointerNDC.current, camera)
334
+ if (raycasterRef.current.ray.intersectPlane(groundPlane.current, hitPoint.current)) {
335
+ return hitPoint.current
336
+ }
337
+ return null
338
+ },
339
+ [camera, gl],
340
+ )
341
+
342
+ useEffect(() => {
343
+ const outline = outlineRef.current
344
+ return () => {
345
+ previewSelectedIdsRef.current = []
346
+ setPreviewSelectedIds([])
347
+ outline.geometry.dispose()
348
+ ;(outline.material as LineBasicMaterial).dispose()
349
+ }
350
+ }, [setPreviewSelectedIds])
351
+
352
+ useEffect(() => {
353
+ const unsubscribe = useViewer.subscribe((state) => {
354
+ const levelId = state.selection.levelId
355
+ if (!levelId) return
356
+ const obj = sceneRegistry.nodes.get(levelId)
357
+ if (obj) groundPlane.current.constant = -obj.position.y
358
+ })
359
+ const levelId = useViewer.getState().selection.levelId
360
+ if (levelId) {
361
+ const obj = sceneRegistry.nodes.get(levelId)
362
+ if (obj) groundPlane.current.constant = -obj.position.y
363
+ }
364
+ return unsubscribe
365
+ }, [])
366
+
367
+ useEffect(() => {
368
+ const onKeyDown = (event: KeyboardEvent) => {
369
+ if (event.code !== 'Space') return
370
+ spaceDownRef.current = true
371
+ if (pointerDown.current) {
372
+ resetDrag()
373
+ }
374
+ }
375
+
376
+ const onKeyUp = (event: KeyboardEvent) => {
377
+ if (event.code !== 'Space') return
378
+ spaceDownRef.current = false
379
+ }
380
+
381
+ const onBlur = () => {
382
+ spaceDownRef.current = false
383
+ resetDrag()
384
+ }
385
+
386
+ window.addEventListener('keydown', onKeyDown)
387
+ window.addEventListener('keyup', onKeyUp)
388
+ window.addEventListener('blur', onBlur)
389
+
390
+ return () => {
391
+ window.removeEventListener('keydown', onKeyDown)
392
+ window.removeEventListener('keyup', onKeyUp)
393
+ window.removeEventListener('blur', onBlur)
394
+ }
395
+ }, [resetDrag])
396
+
397
+ useEffect(() => {
398
+ const canvas = gl.domElement
399
+
400
+ const onCanvasPointerDown = (event: PointerEvent) => {
401
+ if (event.button !== 0) return
402
+ if (spaceDownRef.current) return
403
+ if (isBoxSelectPointerSuppressed(event)) return
404
+ if (useViewer.getState().cameraDragging) return
405
+ if (useViewer.getState().inputDragging) return
406
+
407
+ const point = raycastToGround(event)
408
+ if (!point) return
409
+
410
+ setSnappedPoint(startPoint.current, point.x, point.y, point.z)
411
+ setSnappedPoint(currentPoint.current, point.x, point.y, point.z)
412
+ gridY.current = point.y
413
+ pointerDown.current = true
414
+ isDragging.current = false
415
+ previousGridPosition.current = getSnappedGridPosition(point.x, point.z)
416
+ startClientX.current = event.clientX
417
+ startClientY.current = event.clientY
418
+ syncPreviewSelectedIds([])
419
+ }
420
+
421
+ const onCanvasPointerUp = (event: PointerEvent) => {
422
+ if (event.button !== 0) return
423
+ if (isBoxSelectPointerSuppressed(event) || useViewer.getState().inputDragging) {
424
+ markBoxSelectHandled()
425
+ resetDrag()
426
+ return
427
+ }
428
+ if (!pointerDown.current) return
429
+
430
+ if (isDragging.current) {
431
+ const point = raycastToGround(event)
432
+ if (point) setSnappedPoint(currentPoint.current, point.x, point.y, point.z)
433
+
434
+ const bounds: Bounds = {
435
+ minX: Math.min(startPoint.current.x, currentPoint.current.x),
436
+ maxX: Math.max(startPoint.current.x, currentPoint.current.x),
437
+ minZ: Math.min(startPoint.current.z, currentPoint.current.z),
438
+ maxZ: Math.max(startPoint.current.z, currentPoint.current.z),
439
+ }
440
+
441
+ const ids = collectNodeIdsInPlaneBounds(bounds)
442
+ const shouldAppend = event.metaKey || event.ctrlKey || event.shiftKey
443
+ const { phase, structureLayer } = useEditor.getState()
444
+
445
+ if (phase === 'structure' && structureLayer === 'zones') {
446
+ if (ids.length > 0) {
447
+ useViewer.getState().setSelection({ zoneId: ids[0] as ZoneNode['id'] })
448
+ } else if (!shouldAppend) {
449
+ useViewer.getState().setSelection({ zoneId: null })
450
+ }
451
+ } else if (shouldAppend) {
452
+ const currentIds = useViewer.getState().selection.selectedIds
453
+ useViewer.getState().setSelection({
454
+ selectedIds: Array.from(new Set([...currentIds, ...ids])),
455
+ })
456
+ } else {
457
+ const allOnLevel = collectNodeIdsInPlaneBounds(null)
458
+ const { buildingId } = useViewer.getState().selection
459
+ const selectedEntireLevel = allOnLevel.length > 0 && ids.length === allOnLevel.length
460
+
461
+ if (selectedEntireLevel && buildingId) {
462
+ useViewer.getState().setSelection({ buildingId })
463
+ } else {
464
+ useViewer.getState().setSelection({ selectedIds: ids })
465
+ }
466
+ }
467
+
468
+ markBoxSelectHandled()
469
+ }
470
+
471
+ resetDrag()
472
+ }
473
+
474
+ canvas.addEventListener('pointerdown', onCanvasPointerDown)
475
+ canvas.addEventListener('pointerup', onCanvasPointerUp)
476
+
477
+ return () => {
478
+ canvas.removeEventListener('pointerdown', onCanvasPointerDown)
479
+ canvas.removeEventListener('pointerup', onCanvasPointerUp)
480
+ }
481
+ }, [gl, raycastToGround, resetDrag, syncPreviewSelectedIds])
482
+
483
+ useEffect(() => {
484
+ const onMove = (event: GridEvent) => {
485
+ const [snappedX, snappedZ] = getSnappedGridPosition(event.position[0], event.position[2])
486
+
487
+ if (cursorRef.current) {
488
+ cursorRef.current.position.set(snappedX, event.position[1], snappedZ)
489
+ }
490
+
491
+ if (!pointerDown.current) return
492
+ if (isBoxSelectPointerSuppressed(event.nativeEvent)) {
493
+ markBoxSelectHandled()
494
+ resetDrag()
495
+ return
496
+ }
497
+ if (spaceDownRef.current || useViewer.getState().inputDragging) {
498
+ markBoxSelectHandled()
499
+ resetDrag()
500
+ return
501
+ }
502
+
503
+ currentPoint.current.set(snappedX, event.position[1], snappedZ)
504
+
505
+ const nativeEvent = event.nativeEvent as unknown as PointerEvent
506
+ const dx = nativeEvent.clientX - startClientX.current
507
+ const dy = nativeEvent.clientY - startClientY.current
508
+ if (!isDragging.current && Math.hypot(dx, dy) >= DRAG_THRESHOLD_PX) {
509
+ isDragging.current = true
510
+ }
511
+
512
+ if (isDragging.current && rectFillRef.current && outlineRef.current) {
513
+ updateRectVisuals(
514
+ rectFillRef.current,
515
+ outlineRef.current,
516
+ startPoint.current,
517
+ currentPoint.current,
518
+ gridY.current,
519
+ )
520
+
521
+ const nextGridPosition: [number, number] = [snappedX, snappedZ]
522
+ if (
523
+ previousGridPosition.current &&
524
+ (nextGridPosition[0] !== previousGridPosition.current[0] ||
525
+ nextGridPosition[1] !== previousGridPosition.current[1])
526
+ ) {
527
+ sfxEmitter.emit('sfx:grid-snap')
528
+ }
529
+ previousGridPosition.current = nextGridPosition
530
+
531
+ const bounds: Bounds = {
532
+ minX: Math.min(startPoint.current.x, currentPoint.current.x),
533
+ maxX: Math.max(startPoint.current.x, currentPoint.current.x),
534
+ minZ: Math.min(startPoint.current.z, currentPoint.current.z),
535
+ maxZ: Math.max(startPoint.current.z, currentPoint.current.z),
536
+ }
537
+ syncPreviewSelectedIds(collectNodeIdsInPlaneBounds(bounds))
538
+ }
539
+ }
540
+
541
+ emitter.on('grid:move', onMove)
542
+ return () => {
543
+ emitter.off('grid:move', onMove)
544
+ }
545
+ }, [resetDrag, syncPreviewSelectedIds])
546
+
547
+ return (
548
+ <group>
549
+ <CursorSphere ref={cursorRef} tooltipContent={BOX_SELECT_TOOLTIP} />
550
+ <mesh
551
+ layers={EDITOR_LAYER}
552
+ ref={rectFillRef}
553
+ renderOrder={1}
554
+ rotation={[-Math.PI / 2, 0, 0]}
555
+ visible={false}
556
+ >
557
+ <planeGeometry args={[1, 1]} />
558
+ <meshBasicMaterial
559
+ color={BOX_SELECT_ACCENT_COLOR}
560
+ depthTest={false}
561
+ depthWrite={false}
562
+ opacity={0.14}
563
+ side={DoubleSide}
564
+ transparent
565
+ />
566
+ </mesh>
567
+ <primitive object={outlineRef.current} />
568
+ </group>
569
+ )
570
+ }
@@ -0,0 +1,84 @@
1
+ export type ScreenRect = {
2
+ minX: number
3
+ minY: number
4
+ maxX: number
5
+ maxY: number
6
+ }
7
+
8
+ export const SCREEN_RECTANGLE_SELECTION_DRAG_THRESHOLD_PX = 4
9
+
10
+ const SCREEN_RECTANGLE_SELECTION_FILL_COLOR = 'rgba(129, 140, 248, 0.14)'
11
+ const SCREEN_RECTANGLE_SELECTION_BORDER_COLOR = 'rgba(129, 140, 248, 0.9)'
12
+ const SCREEN_RECTANGLE_SELECTION_SHADOW_COLOR = 'rgba(129, 140, 248, 0.28)'
13
+
14
+ export function createScreenRectangleSelectionElement(): HTMLDivElement {
15
+ const element = document.createElement('div')
16
+ element.style.position = 'fixed'
17
+ element.style.display = 'none'
18
+ element.style.pointerEvents = 'none'
19
+ element.style.zIndex = '2147483647'
20
+ element.style.border = `1px solid ${SCREEN_RECTANGLE_SELECTION_BORDER_COLOR}`
21
+ element.style.background = SCREEN_RECTANGLE_SELECTION_FILL_COLOR
22
+ element.style.boxShadow = `0 0 0 1px ${SCREEN_RECTANGLE_SELECTION_SHADOW_COLOR} inset`
23
+ element.style.contain = 'layout paint style'
24
+ return element
25
+ }
26
+
27
+ export function normalizeScreenRect(
28
+ startX: number,
29
+ startY: number,
30
+ endX: number,
31
+ endY: number,
32
+ ): ScreenRect {
33
+ return {
34
+ minX: Math.min(startX, endX),
35
+ minY: Math.min(startY, endY),
36
+ maxX: Math.max(startX, endX),
37
+ maxY: Math.max(startY, endY),
38
+ }
39
+ }
40
+
41
+ export function screenRectFromDomRect(rect: DOMRect | DOMRectReadOnly): ScreenRect {
42
+ return {
43
+ minX: rect.left,
44
+ minY: rect.top,
45
+ maxX: rect.right,
46
+ maxY: rect.bottom,
47
+ }
48
+ }
49
+
50
+ export function screenRectsIntersect(a: ScreenRect, b: ScreenRect): boolean {
51
+ return !(b.maxX < a.minX || b.minX > a.maxX || b.maxY < a.minY || b.minY > a.maxY)
52
+ }
53
+
54
+ export function intersectScreenRects(a: ScreenRect, b: ScreenRect): ScreenRect | null {
55
+ const rect = {
56
+ minX: Math.max(a.minX, b.minX),
57
+ minY: Math.max(a.minY, b.minY),
58
+ maxX: Math.min(a.maxX, b.maxX),
59
+ maxY: Math.min(a.maxY, b.maxY),
60
+ }
61
+
62
+ if (rect.maxX <= rect.minX || rect.maxY <= rect.minY) {
63
+ return null
64
+ }
65
+
66
+ return rect
67
+ }
68
+
69
+ export function updateScreenRectangleSelectionElement(element: HTMLDivElement, rect: ScreenRect) {
70
+ element.style.display = 'block'
71
+ element.style.left = `${rect.minX}px`
72
+ element.style.top = `${rect.minY}px`
73
+ element.style.width = `${Math.max(0, rect.maxX - rect.minX)}px`
74
+ element.style.height = `${Math.max(0, rect.maxY - rect.minY)}px`
75
+ }
76
+
77
+ export function hideScreenRectangleSelectionElement(element: HTMLDivElement | null) {
78
+ if (!element) {
79
+ return
80
+ }
81
+ element.style.display = 'none'
82
+ element.style.width = '0px'
83
+ element.style.height = '0px'
84
+ }