@pascal-app/editor 1.0.3 → 1.0.4

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 (440) hide show
  1. package/package.json +11 -9
  2. package/src/components/editor/action-menu-button.tsx +83 -0
  3. package/src/components/editor/bake-thumbnail.tsx +315 -4
  4. package/src/components/editor/capture-level-picker.tsx +72 -0
  5. package/src/components/editor/custom-camera-controls.tsx +202 -57
  6. package/src/components/editor/delete-confirmation-dialog.tsx +297 -7
  7. package/src/components/editor/duplicate-node.ts +244 -0
  8. package/src/components/editor/editor-layout-mobile.tsx +36 -25
  9. package/src/components/editor/editor-layout-v2.tsx +131 -58
  10. package/src/components/editor/export-manager.tsx +34 -8
  11. package/src/components/editor/first-person/build-collider-world.test.ts +51 -0
  12. package/src/components/editor/first-person/build-collider-world.ts +6 -2
  13. package/src/components/editor/first-person/mechanism-targets.test.ts +75 -0
  14. package/src/components/editor/first-person/mechanism-targets.ts +36 -0
  15. package/src/components/editor/first-person-controls.tsx +205 -2
  16. package/src/components/editor/floating-action-menu.tsx +63 -207
  17. package/src/components/editor/floor-aware-camera-controls.test.ts +442 -0
  18. package/src/components/editor/floor-aware-camera-controls.ts +324 -0
  19. package/src/components/editor/floor-openings-overlay.tsx +243 -0
  20. package/src/components/editor/floor-region-controls.tsx +846 -0
  21. package/src/components/editor/floor-region-webgpu-materials.test.ts +12 -0
  22. package/src/components/editor/floor-surface.test.ts +165 -0
  23. package/src/components/editor/floor-surface.ts +182 -0
  24. package/src/components/editor/floorplan-background-selection.test.ts +11 -2
  25. package/src/components/editor/floorplan-background-selection.ts +1 -3
  26. package/src/components/editor/floorplan-panel.tsx +344 -342
  27. package/src/components/editor/footprint-height-handle.tsx +204 -0
  28. package/src/components/editor/group-actions.ts +54 -2
  29. package/src/components/editor/handles/handle-hit-priority.test.tsx +63 -0
  30. package/src/components/editor/handles/handle-hit-priority.tsx +37 -0
  31. package/src/components/editor/handles/swallow-next-click.test.ts +28 -0
  32. package/src/components/editor/handles/use-handle-drag.ts +269 -197
  33. package/src/components/editor/index.tsx +294 -169
  34. package/src/components/editor/join-walls-pill.tsx +44 -0
  35. package/src/components/editor/node-action-menu.tsx +68 -70
  36. package/src/components/editor/node-arrow-handles.tsx +25 -6
  37. package/src/components/editor/open-wall-ends-3d-layer.tsx +273 -0
  38. package/src/components/editor/open-wall-ends-hint.tsx +89 -0
  39. package/src/components/editor/quick-measurement-hud.tsx +3 -2
  40. package/src/components/editor/registry-action-contributions.test.tsx +63 -0
  41. package/src/components/editor/registry-action-contributions.tsx +80 -1
  42. package/src/components/editor/room-controls.tsx +847 -0
  43. package/src/components/editor/room-floor-highlight.tsx +128 -0
  44. package/src/components/editor/room-handles-2d.tsx +354 -0
  45. package/src/components/editor/room-handles.tsx +675 -0
  46. package/src/components/editor/room-highlight.tsx +225 -0
  47. package/src/components/editor/room-labels.test.ts +357 -0
  48. package/src/components/editor/room-labels.tsx +990 -0
  49. package/src/components/editor/room-transform-ghost.tsx +256 -0
  50. package/src/components/editor/room-webgpu-materials.test.ts +27 -0
  51. package/src/components/editor/selection-manager.tsx +451 -317
  52. package/src/components/editor/slab-hole-highlights.tsx +3 -0
  53. package/src/components/editor/snapshot-capture-overlay.tsx +4 -0
  54. package/src/components/editor/snapshot-capture.test.ts +52 -4
  55. package/src/components/editor/snapshot-capture.ts +30 -5
  56. package/src/components/editor/thumbnail-generator.test.ts +308 -0
  57. package/src/components/editor/thumbnail-generator.tsx +1035 -229
  58. package/src/components/editor/use-floorplan-background-placement.ts +31 -11
  59. package/src/components/editor/vector-edge-extractor.tsx +121 -0
  60. package/src/components/editor/views/host-view.tsx +61 -0
  61. package/src/components/editor/views/plugins-view.tsx +670 -0
  62. package/src/components/editor/views/scene-region.tsx +59 -0
  63. package/src/components/editor/views/use-editor-views.ts +57 -0
  64. package/src/components/editor/views/view-bar.tsx +220 -0
  65. package/src/components/editor/wall-justification.test.ts +140 -0
  66. package/src/components/editor/wall-move-side-handles.tsx +43 -142
  67. package/src/components/editor/wall-push-arrow.tsx +92 -0
  68. package/src/components/editor/wall-region-handles.tsx +509 -0
  69. package/src/components/editor/wall-snap-beacon-layer.tsx +3 -0
  70. package/src/components/editor-2d/floorplan-cursor-indicator-overlay.tsx +8 -4
  71. package/src/components/editor-2d/floorplan-open-wall-ends-layer.test.tsx +86 -0
  72. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +290 -0
  73. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +11 -1
  74. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +113 -102
  75. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.test.ts +156 -0
  76. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +278 -0
  77. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +77 -0
  78. package/src/components/editor-2d/renderers/{floorplan-draft-wall-measurement.tsx → floorplan-draft-measurement.tsx} +8 -8
  79. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +4 -1
  80. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +38 -8
  81. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +291 -227
  82. package/src/components/editor-2d/svg-paths.ts +12 -15
  83. package/src/components/procedural-items/inspector.tsx +40 -15
  84. package/src/components/systems/ceiling/ceiling-selection-affordance-system.test.ts +97 -14
  85. package/src/components/systems/ceiling/ceiling-selection-affordance-system.tsx +18 -18
  86. package/src/components/systems/zone/zone-system.test.ts +47 -0
  87. package/src/components/systems/zone/zone-system.tsx +29 -68
  88. package/src/components/tools/item/placement-history.test.tsx +474 -0
  89. package/src/components/tools/item/placement-strategies.ts +8 -4
  90. package/src/components/tools/item/use-draft-node.test.tsx +432 -1
  91. package/src/components/tools/item/use-draft-node.ts +166 -95
  92. package/src/components/tools/item/use-placement-coordinator.tsx +93 -46
  93. package/src/components/tools/paint-region/use-paint-tint.ts +37 -0
  94. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +466 -0
  95. package/src/components/tools/registry/move-registry-node-tool.tsx +3 -1
  96. package/src/components/tools/select/box-select-tool.tsx +2 -12
  97. package/src/components/tools/select/plane-box-select-tool.test.tsx +79 -0
  98. package/src/components/tools/select/plane-box-select-tool.tsx +35 -22
  99. package/src/components/tools/select/select-candidates.test.ts +37 -4
  100. package/src/components/tools/select/select-candidates.ts +15 -11
  101. package/src/components/tools/shared/cursor-sphere.tsx +36 -12
  102. package/src/components/tools/site/site-boundary-editor.tsx +1 -2
  103. package/src/components/tools/stair/stair-defaults.ts +0 -3
  104. package/src/components/tools/stair/stair-tool.tsx +90 -42
  105. package/src/components/tools/tool-manager.tsx +46 -20
  106. package/src/components/tools/wall/wall-drafting.test.ts +19 -4
  107. package/src/components/tools/zone/zone-tool.tsx +4 -12
  108. package/src/components/ui/action-menu/action-button.tsx +3 -0
  109. package/src/components/ui/action-menu/camera-actions.tsx +2 -1
  110. package/src/components/ui/action-menu/control-modes.tsx +2 -35
  111. package/src/components/ui/action-menu/measurement-control.tsx +3 -10
  112. package/src/components/ui/build-panel/build-panel.tsx +235 -0
  113. package/src/components/ui/command-palette/editor-commands.tsx +4 -30
  114. package/src/components/ui/command-palette/index.tsx +5 -24
  115. package/src/components/ui/controls/finish-swatch.tsx +110 -0
  116. package/src/components/ui/controls/material-paint-panel.tsx +97 -26
  117. package/src/components/ui/controls/material-picker.tsx +4 -1
  118. package/src/components/ui/controls/scene-material-list.tsx +4 -32
  119. package/src/components/ui/controls/select-control.tsx +65 -0
  120. package/src/components/ui/floating-level-selector.tsx +9 -14
  121. package/src/components/ui/helpers/building-helper.tsx +4 -1
  122. package/src/components/ui/helpers/contextual-helper-panel.tsx +154 -70
  123. package/src/components/ui/helpers/helper-manager.test.tsx +69 -0
  124. package/src/components/ui/helpers/helper-manager.tsx +325 -14
  125. package/src/components/ui/helpers/item-helper.tsx +8 -0
  126. package/src/components/ui/helpers/registered-tool-helper.tsx +8 -1
  127. package/src/components/ui/icon-ref.tsx +45 -1
  128. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  129. package/src/components/ui/node-icon-image.tsx +16 -0
  130. package/src/components/ui/panels/collections/collections-popover.tsx +20 -14
  131. package/src/components/ui/panels/floor-foundation-panel.tsx +551 -0
  132. package/src/components/ui/panels/floor-opening-panel.tsx +143 -0
  133. package/src/components/ui/panels/mobile-panel-sheet.tsx +2 -2
  134. package/src/components/ui/panels/mobile-selection-bar.tsx +2 -2
  135. package/src/components/ui/panels/multi-field-value.test.ts +26 -3
  136. package/src/components/ui/panels/multi-field-value.ts +6 -3
  137. package/src/components/ui/panels/multi-parametric-inspector.tsx +52 -2
  138. package/src/components/ui/panels/multi-selection-panel.tsx +43 -4
  139. package/src/components/ui/panels/node-display.ts +8 -3
  140. package/src/components/ui/panels/panel-manager.tsx +103 -68
  141. package/src/components/ui/panels/panel-wrapper.tsx +78 -36
  142. package/src/components/ui/panels/parametric-field-control.tsx +1 -1
  143. package/src/components/ui/panels/parametric-inspector.tsx +4 -4
  144. package/src/components/ui/panels/reference-panel.tsx +2 -3
  145. package/src/components/ui/panels/room-construction-rows.tsx +774 -0
  146. package/src/components/ui/panels/room-name-options.test.ts +55 -0
  147. package/src/components/ui/panels/room-name-options.ts +47 -0
  148. package/src/components/ui/panels/room-openings-field.tsx +110 -0
  149. package/src/components/ui/panels/room-panel.tsx +725 -0
  150. package/src/components/ui/panels/shape-choice.tsx +58 -0
  151. package/src/components/ui/panels/wall-region-list.tsx +109 -0
  152. package/src/components/ui/primitives/color-dot.tsx +36 -24
  153. package/src/components/ui/primitives/sidebar.tsx +2 -0
  154. package/src/components/ui/right-stack.test.ts +29 -0
  155. package/src/components/ui/right-stack.tsx +145 -0
  156. package/src/components/ui/sidebar/icon-rail.tsx +5 -0
  157. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +31 -4
  158. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +5 -5
  159. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -4
  160. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +1 -5
  161. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +197 -0
  162. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +1 -5
  163. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +1 -5
  164. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +1 -5
  165. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +1 -5
  166. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +24 -11
  167. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +1 -5
  168. package/src/components/ui/sidebar/panels/site-panel/index.tsx +176 -211
  169. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +30 -8
  170. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +1 -3
  171. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +4 -1
  172. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +1 -3
  173. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +1 -4
  174. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +1 -3
  175. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +21 -13
  176. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +1 -5
  177. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +1 -5
  178. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +1 -5
  179. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +0 -27
  180. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -3
  181. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +1 -5
  182. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +1 -5
  183. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +9 -4
  184. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +0 -2
  185. package/src/components/ui/sidebar/tab-bar.tsx +10 -0
  186. package/src/components/ui/sidebar/use-plugin-panels.tsx +15 -5
  187. package/src/components/viewer/floorplan-compass-button.tsx +9 -1
  188. package/src/components/viewer/floorplan-preview.tsx +5 -6
  189. package/src/components/viewer/viewer-hover-label.tsx +34 -0
  190. package/src/components/viewer/viewer-rooms.tsx +175 -0
  191. package/src/components/viewer/viewer-scene-header.tsx +181 -145
  192. package/src/components/viewer/viewer-selection-manager.tsx +124 -0
  193. package/src/components/viewer/viewer-stage.tsx +2 -2
  194. package/src/components/viewer-zone-system.tsx +3 -8
  195. package/src/hooks/use-auto-save.integration.test.tsx +123 -9
  196. package/src/hooks/use-auto-save.ts +34 -8
  197. package/src/hooks/use-keyboard.test.ts +178 -3
  198. package/src/hooks/use-keyboard.ts +89 -71
  199. package/src/hooks/use-open-wall-ends.test.ts +99 -0
  200. package/src/hooks/use-open-wall-ends.ts +238 -0
  201. package/src/hooks/use-placement-notice.test.ts +48 -0
  202. package/src/hooks/use-placement-notice.ts +42 -0
  203. package/src/hooks/use-selected-room.ts +143 -0
  204. package/src/index.tsx +246 -39
  205. package/src/lib/ceiling-edit-session.test.ts +321 -0
  206. package/src/lib/ceiling-edit-session.ts +206 -0
  207. package/src/lib/ceiling-region-session.test.ts +179 -0
  208. package/src/lib/continuation.test.ts +19 -1
  209. package/src/lib/continuation.ts +25 -4
  210. package/src/lib/door-interaction.ts +14 -0
  211. package/src/lib/editor-level-display.test.ts +96 -0
  212. package/src/lib/editor-level-display.ts +48 -0
  213. package/src/lib/editor-views.ts +144 -0
  214. package/src/lib/export-geometry-wait.test.ts +90 -0
  215. package/src/lib/floor-edit-session.ts +153 -0
  216. package/src/lib/floor-footprints.test.ts +563 -0
  217. package/src/lib/floor-footprints.ts +395 -0
  218. package/src/lib/floor-foundation-support.test.ts +283 -0
  219. package/src/lib/floor-opening-draft.ts +218 -0
  220. package/src/lib/floor-openings.test.ts +216 -0
  221. package/src/lib/floor-region-geometry.ts +191 -0
  222. package/src/lib/floor-region-session.test.ts +485 -0
  223. package/src/lib/floor-region-session.ts +589 -0
  224. package/src/lib/floor-region-snap.ts +101 -0
  225. package/src/lib/floor-step-hover.test.ts +32 -0
  226. package/src/lib/floorplan/annotation-visibility.ts +17 -0
  227. package/src/lib/floorplan/dimension-drive.test.ts +360 -0
  228. package/src/lib/floorplan/dimension-drive.ts +479 -0
  229. package/src/lib/floorplan/floorplan-export.test.ts +12 -8
  230. package/src/lib/floorplan/floorplan-export.tsx +32 -8
  231. package/src/lib/floorplan/floorplan-extension.ts +32 -0
  232. package/src/lib/floorplan/floorplan-mode.test.ts +4 -0
  233. package/src/lib/floorplan/floorplan-mode.ts +2 -0
  234. package/src/lib/floorplan/floorplan-pdfkit-document.ts +339 -18
  235. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +138 -8
  236. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +56 -25
  237. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +138 -0
  238. package/src/lib/floorplan/floorplan-readonly.ts +6 -0
  239. package/src/lib/floorplan/geometry.ts +13 -6
  240. package/src/lib/floorplan/index.ts +0 -9
  241. package/src/lib/floorplan/items.ts +5 -2
  242. package/src/lib/floorplan/marks.test.ts +213 -0
  243. package/src/lib/floorplan/marks.ts +238 -0
  244. package/src/lib/floorplan/open-wall-ends.test.ts +268 -0
  245. package/src/lib/floorplan/open-wall-ends.ts +162 -0
  246. package/src/lib/floorplan/pdf-fonts/GeistMono-Bold.ttf +0 -0
  247. package/src/lib/floorplan/pdf-fonts/GeistMono-Regular.ttf +0 -0
  248. package/src/lib/floorplan/pdf-fonts/LICENSE-GeistMono.txt +92 -0
  249. package/src/lib/floorplan/pdf-fonts/LICENSE-LiberationSans.txt +102 -0
  250. package/src/lib/floorplan/pdf-fonts/LiberationSans-Bold.ttf +0 -0
  251. package/src/lib/floorplan/pdf-fonts/LiberationSans-Regular.ttf +0 -0
  252. package/src/lib/floorplan/schedules.test.ts +189 -0
  253. package/src/lib/floorplan/schedules.ts +339 -0
  254. package/src/lib/floorplan/sheet-export.test.ts +100 -0
  255. package/src/lib/floorplan/sheet-export.ts +318 -0
  256. package/src/lib/floorplan/site-plan/build-site-plan-drawing.ts +779 -0
  257. package/src/lib/floorplan/site-plan/contours.test.ts +43 -0
  258. package/src/lib/floorplan/site-plan/contributors.ts +97 -0
  259. package/src/lib/floorplan/site-plan/coverage.ts +232 -0
  260. package/src/lib/floorplan/site-plan/drawing-type-switch.test.ts +15 -0
  261. package/src/lib/floorplan/site-plan/drawing-type-switch.tsx +86 -0
  262. package/src/lib/floorplan/site-plan/front-edge.test.ts +103 -0
  263. package/src/lib/floorplan/site-plan/front-edge.ts +156 -0
  264. package/src/lib/floorplan/site-plan/geometry.test.ts +275 -0
  265. package/src/lib/floorplan/site-plan/geometry.ts +557 -0
  266. package/src/lib/floorplan/site-plan/index.ts +71 -0
  267. package/src/lib/floorplan/site-plan/site-annotations.test.ts +366 -0
  268. package/src/lib/floorplan/site-plan/site-annotations.ts +1083 -0
  269. package/src/lib/floorplan/site-plan/site-parts.ts +379 -0
  270. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +138 -0
  271. package/src/lib/floorplan/site-plan/street-name.test.ts +69 -0
  272. package/src/lib/floorplan/site-plan/street-name.ts +302 -0
  273. package/src/lib/floorplan/site-plan/yards.test.ts +69 -0
  274. package/src/lib/floorplan/types.ts +1 -27
  275. package/src/lib/fresh-planar-placement.test.ts +36 -0
  276. package/src/lib/fresh-planar-placement.ts +59 -36
  277. package/src/lib/geometry-script/author.ts +152 -0
  278. package/src/lib/geometry-script/client.ts +152 -0
  279. package/src/lib/geometry-script/protocol.ts +12 -0
  280. package/src/lib/gesture-lifecycle.test.ts +595 -0
  281. package/src/lib/gesture-lifecycle.ts +290 -0
  282. package/src/lib/glb-export-zone-holes.test.ts +41 -0
  283. package/src/lib/glb-export.test.ts +485 -0
  284. package/src/lib/glb-export.ts +237 -119
  285. package/src/lib/history.test.ts +6 -0
  286. package/src/lib/history.ts +7 -0
  287. package/src/lib/hud-title.ts +109 -0
  288. package/src/lib/hydration-gated-storage.ts +49 -0
  289. package/src/lib/ifc-export.test.ts +174 -0
  290. package/src/lib/ifc-export.ts +201 -0
  291. package/src/lib/ifc-parts.ts +15 -0
  292. package/src/lib/inspector-expanded.test.ts +44 -0
  293. package/src/lib/inspector-expanded.ts +36 -0
  294. package/src/lib/interaction/scope.ts +18 -0
  295. package/src/lib/keyboard-pan.test.ts +129 -7
  296. package/src/lib/keyboard-pan.ts +161 -12
  297. package/src/lib/live-gesture-round-trip.test.ts +360 -0
  298. package/src/lib/lot/__fixtures__/map-dossier-miami-shores.json +1 -0
  299. package/src/lib/lot/__fixtures__/map-dossier-stpete.json +1 -0
  300. package/src/lib/lot/clean-ring.test.ts +186 -0
  301. package/src/lib/lot/clean-ring.ts +217 -0
  302. package/src/lib/lot/dossier.test.ts +202 -0
  303. package/src/lib/lot/dossier.ts +498 -0
  304. package/src/lib/lot/drop-in.test.ts +326 -0
  305. package/src/lib/lot/drop-in.ts +407 -0
  306. package/src/lib/lot/index.ts +43 -0
  307. package/src/lib/lot/lot-patch.test.ts +340 -0
  308. package/src/lib/lot/lot-patch.ts +394 -0
  309. package/src/lib/lot/parcel-provider.ts +43 -0
  310. package/src/lib/lot/terrain.test.ts +161 -0
  311. package/src/lib/lot/terrain.ts +266 -0
  312. package/src/lib/material-catalog-model.ts +3 -1
  313. package/src/lib/material-paint-panel-model.ts +9 -5
  314. package/src/lib/material-paint.ts +4 -0
  315. package/src/lib/mezzanine-actions.test.ts +342 -0
  316. package/src/lib/mezzanine-draft.test.ts +501 -0
  317. package/src/lib/mezzanine-draft.ts +278 -0
  318. package/src/lib/mezzanine-messages.ts +26 -0
  319. package/src/lib/mezzanine-selection.test.ts +207 -0
  320. package/src/lib/mezzanine-stairs.ts +46 -0
  321. package/src/lib/model-export.ts +3 -1
  322. package/src/lib/node-action-movement.ts +17 -6
  323. package/src/lib/node-display-label.ts +37 -0
  324. package/src/lib/orbit-floor-clearance.test.ts +165 -0
  325. package/src/lib/orbit-floor-clearance.ts +106 -0
  326. package/src/lib/paint-commit-route.test.ts +37 -0
  327. package/src/lib/paint-commit-route.ts +17 -0
  328. package/src/lib/paint-eyedropper.test.tsx +165 -0
  329. package/src/lib/paint-eyedropper.ts +99 -0
  330. package/src/lib/paint-pass-through.test.ts +111 -0
  331. package/src/lib/paint-pass-through.ts +76 -0
  332. package/src/lib/paint-region-hover.ts +25 -0
  333. package/src/lib/paint-region-mode.ts +154 -0
  334. package/src/lib/paint-regions.test.ts +166 -0
  335. package/src/lib/paint-regions.ts +192 -0
  336. package/src/lib/paint-scope.test.ts +317 -35
  337. package/src/lib/paint-scope.ts +266 -25
  338. package/src/lib/panel-rows.ts +21 -0
  339. package/src/lib/parametric-node-update.ts +1 -1
  340. package/src/lib/plate-paint-affected.test.ts +300 -0
  341. package/src/lib/plate-paint-affected.ts +177 -0
  342. package/src/lib/plugin-chat.ts +50 -0
  343. package/src/lib/plugin-panels.test.ts +25 -0
  344. package/src/lib/plugin-panels.ts +94 -0
  345. package/src/lib/print-shell-compiler.ts +1 -0
  346. package/src/lib/registered-draft-snap.ts +12 -0
  347. package/src/lib/roof-duplication.ts +10 -2
  348. package/src/lib/room-assembly-overlay.test.ts +275 -0
  349. package/src/lib/room-assembly-overlay.ts +266 -0
  350. package/src/lib/room-built-on.test.ts +379 -0
  351. package/src/lib/room-built-on.ts +138 -0
  352. package/src/lib/room-construction-commands.ts +712 -0
  353. package/src/lib/room-construction.test.ts +746 -0
  354. package/src/lib/room-construction.ts +385 -0
  355. package/src/lib/room-controls-phase6.test.ts +630 -0
  356. package/src/lib/room-divide-session.ts +523 -0
  357. package/src/lib/room-handle-drag.ts +1068 -0
  358. package/src/lib/room-name-catalog.test.ts +90 -0
  359. package/src/lib/room-name-catalog.ts +168 -0
  360. package/src/lib/room-push-dimensions.test.ts +311 -0
  361. package/src/lib/room-push-dimensions.ts +251 -0
  362. package/src/lib/room-selection-commands.ts +62 -0
  363. package/src/lib/room-selection.test.ts +973 -0
  364. package/src/lib/room-selection.ts +417 -0
  365. package/src/lib/room-structure-commands.ts +94 -0
  366. package/src/lib/room-structure.test.ts +921 -0
  367. package/src/lib/room-transform-session.ts +570 -0
  368. package/src/lib/room-zone-routing.test.ts +294 -0
  369. package/src/lib/room-zone-routing.ts +197 -0
  370. package/src/lib/scene-clipboard.test.ts +364 -0
  371. package/src/lib/scene-clipboard.ts +229 -48
  372. package/src/lib/scene-material-refs.test.ts +79 -0
  373. package/src/lib/scene-material-refs.ts +79 -0
  374. package/src/lib/scene.test.ts +24 -36
  375. package/src/lib/scene.ts +33 -57
  376. package/src/lib/selection-routing.test.ts +129 -45
  377. package/src/lib/selection-routing.ts +53 -74
  378. package/src/lib/session-writes.test.ts +120 -0
  379. package/src/lib/session-writes.ts +205 -0
  380. package/src/lib/sidebar-panel.ts +62 -0
  381. package/src/lib/site-boundary.test.ts +4 -6
  382. package/src/lib/site-boundary.ts +1 -1
  383. package/src/lib/snapping-mode.test.ts +4 -0
  384. package/src/lib/snapping-mode.ts +8 -1
  385. package/src/lib/terrace-draft.test.ts +145 -0
  386. package/src/lib/terrace-draft.ts +213 -0
  387. package/src/lib/tool-cursor-icon.test.ts +16 -0
  388. package/src/lib/tool-cursor-icon.ts +11 -0
  389. package/src/lib/typing-target.ts +9 -0
  390. package/src/lib/units.ts +77 -39
  391. package/src/lib/vector-edges.test.ts +58 -0
  392. package/src/lib/vector-edges.ts +400 -0
  393. package/src/lib/viewer-selection.test.ts +577 -0
  394. package/src/lib/viewer-selection.ts +569 -0
  395. package/src/lib/wall-draw-variant.test.ts +71 -0
  396. package/src/lib/wall-draw-variant.ts +91 -0
  397. package/src/lib/wall-paint-region-session.ts +246 -0
  398. package/src/lib/wall-paint-region.test.ts +373 -0
  399. package/src/lib/wall-polygon-draft.test.ts +262 -0
  400. package/src/lib/wall-polygon-draft.ts +286 -0
  401. package/src/lib/wall-push.test.ts +453 -0
  402. package/src/lib/wall-region-face.ts +352 -0
  403. package/src/lib/wall-region-handles.test.ts +304 -0
  404. package/src/lib/wall-region-handles.ts +368 -0
  405. package/src/lib/wall-region-list.test.ts +119 -0
  406. package/src/lib/wall-region-list.ts +136 -0
  407. package/src/lib/wall-region-snap.ts +110 -0
  408. package/src/lib/window-interaction.ts +14 -0
  409. package/src/lib/zone-content.ts +4 -1
  410. package/src/lib/zone-name.test.ts +17 -0
  411. package/src/lib/zone-name.ts +17 -0
  412. package/src/store/opens-in-select-mode.test.ts +223 -0
  413. package/src/store/paint-mode.test.ts +188 -0
  414. package/src/store/terrain-sculpt-mode.test.ts +19 -21
  415. package/src/store/tool-mode.test.ts +137 -6
  416. package/src/store/use-delete-confirmation.ts +28 -1
  417. package/src/store/use-drawing-view.test.ts +24 -3
  418. package/src/store/use-drawing-view.ts +29 -1
  419. package/src/store/use-editor.tsx +432 -321
  420. package/src/store/use-floorplan-draft-preview.ts +13 -0
  421. package/src/store/use-floorplan-marquee.ts +12 -2
  422. package/src/store/use-quick-measurement-hud.ts +3 -3
  423. package/src/store/use-stair-build-preview.ts +13 -5
  424. package/src/store/use-wall-move-ghosts.ts +4 -2
  425. package/src/store/use-wall-region-handles-selection.test.ts +222 -0
  426. package/src/store/use-wall-region-handles-selection.ts +139 -0
  427. package/src/store/view-layout.test.ts +175 -0
  428. package/src/store/view-layout.ts +196 -0
  429. package/src/test-utils/immediate-animation-frames.ts +16 -0
  430. package/src/test-utils/live-gesture-round-trip.ts +224 -0
  431. package/src/test-utils/selection-harness.ts +82 -0
  432. package/src/components/editor-2d/renderers/floorplan-stair-layer.tsx +0 -499
  433. package/src/components/systems/zone/zone-label-editor-system.tsx +0 -333
  434. package/src/components/tools/fence/fence-drafting.test.ts +0 -100
  435. package/src/components/tools/fence/fence-drafting.ts +0 -266
  436. package/src/components/ui/sidebar/panels/plugins-panel.tsx +0 -218
  437. package/src/components/viewer/viewer-units-panel.tsx +0 -116
  438. package/src/lib/floorplan/stairs.ts +0 -489
  439. package/src/lib/level-duplication.test.ts +0 -144
  440. package/src/lib/level-duplication.ts +0 -195
@@ -3,7 +3,7 @@ import { markPerfAction, useViewer } from '@pascal-app/viewer'
3
3
  import { Layers } from 'lucide-react'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
- import { getDefaultLevelName } from '@pascal-app/core'
6
+ import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
7
7
  import { InlineRenameInput } from './inline-rename-input'
8
8
  import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
9
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -26,6 +26,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
26
26
  useShallow((s) => (s.nodes[nodeId] as LevelNode | undefined)?.children ?? []),
27
27
  )
28
28
  const level = useScene((s) => (s.nodes[nodeId] as LevelNode | undefined)?.level ?? 0)
29
+ const levelName = useScene((s) => (s.nodes[nodeId] as LevelNode | undefined)?.name)
29
30
  const isSelected = useViewer((state) => state.selection.levelId === nodeId)
30
31
  const isHovered = useViewer((state) => state.hoveredId === nodeId)
31
32
  const setSelection = useViewer((state) => state.setSelection)
@@ -40,6 +41,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
40
41
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
41
42
 
42
43
  const defaultName = getDefaultLevelName(level)
44
+ const displayName = getLevelDisplayName({ name: levelName, level })
43
45
 
44
46
  return (
45
47
  <TreeNodeWrapper
@@ -55,6 +57,7 @@ export const LevelTreeNode = memo(function LevelTreeNode({
55
57
  label={
56
58
  <InlineRenameInput
57
59
  defaultName={defaultName}
60
+ displayName={displayName}
58
61
  isEditing={isEditing}
59
62
  nodeId={nodeId}
60
63
  onStartEditing={handleStartEditing}
@@ -10,7 +10,6 @@ import { InlineRenameInput } from './inline-rename-input'
10
10
  import {
11
11
  focusTreeNode,
12
12
  handleTreeSelection,
13
- routeTreeSelectionToNode,
14
13
  TreeNode,
15
14
  TreeNodeWrapper,
16
15
  } from './tree-node'
@@ -99,9 +98,8 @@ export const RegistryTreeNode = memo(function RegistryTreeNode({
99
98
  useViewer.getState().selection.selectedIds,
100
99
  setSelection,
101
100
  )
102
- routeTreeSelectionToNode(node)
103
101
  },
104
- [node, nodeId, setSelection],
102
+ [nodeId, setSelection],
105
103
  )
106
104
 
107
105
  return (
@@ -49,15 +49,12 @@ export const RoofTreeNode = memo(function RoofTreeNode({
49
49
  const handleClick = useCallback(
50
50
  (e: React.MouseEvent) => {
51
51
  e.stopPropagation()
52
- const handled = handleTreeSelection(
52
+ handleTreeSelection(
53
53
  e,
54
54
  nodeId,
55
55
  useViewer.getState().selection.selectedIds,
56
56
  setSelection,
57
57
  )
58
- if (!handled && useEditor.getState().phase === 'furnish') {
59
- useEditor.getState().setPhase('structure')
60
- }
61
58
  if (!e.metaKey && !e.ctrlKey && !e.shiftKey) {
62
59
  setRoofHostDragArmedId(nodeId)
63
60
  }
@@ -9,7 +9,6 @@ import { InlineRenameInput } from './inline-rename-input'
9
9
  import {
10
10
  focusTreeNode,
11
11
  handleTreeSelection,
12
- routeTreeSelectionToNode,
13
12
  TreeNode,
14
13
  TreeNodeWrapper,
15
14
  } from './tree-node'
@@ -70,9 +69,8 @@ export const ShelfTreeNode = memo(function ShelfTreeNode({
70
69
  (e: React.MouseEvent) => {
71
70
  e.stopPropagation()
72
71
  handleTreeSelection(e, nodeId, useViewer.getState().selection.selectedIds, setSelection)
73
- routeTreeSelectionToNode(node)
74
72
  },
75
- [node, nodeId, setSelection],
73
+ [nodeId, setSelection],
76
74
  )
77
75
 
78
76
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
@@ -1,9 +1,8 @@
1
- import { type AnyNodeId, type SlabNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, floorFootprintName, type SlabNode, useScene } from '@pascal-app/core'
2
2
  import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { formatAreaLabel } from './../../../../../lib/measurements'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -27,19 +26,22 @@ export const SlabTreeNode = memo(function SlabTreeNode({
27
26
  const setSelection = useViewer((state) => state.setSelection)
28
27
  const setHoveredId = useViewer((state) => state.setHoveredId)
29
28
  const unit = useViewer((state) => state.unit)
29
+ // A base plate is one footprint's "Floor & foundation": listed by the
30
+ // footprint's name ("Shared floor", "Lanai floor"), opening that panel.
31
+ const footprint = useScene((s) => {
32
+ const node = s.nodes[nodeId]
33
+ return node?.type === 'slab' && node.plateRole === 'base' ? floorFootprintName(s.nodes, node) : null
34
+ })
30
35
 
31
36
  const handleClick = useCallback(
32
37
  (e: React.MouseEvent) => {
33
38
  e.stopPropagation()
34
- const handled = handleTreeSelection(
39
+ handleTreeSelection(
35
40
  e,
36
41
  nodeId,
37
42
  useViewer.getState().selection.selectedIds,
38
43
  setSelection,
39
44
  )
40
- if (!handled && useEditor.getState().phase === 'furnish') {
41
- useEditor.getState().setPhase('structure')
42
- }
43
45
  },
44
46
  [nodeId, setSelection],
45
47
  )
@@ -63,13 +65,19 @@ export const SlabTreeNode = memo(function SlabTreeNode({
63
65
  isSelected={isSelected}
64
66
  isVisible={isVisible}
65
67
  label={
66
- <InlineRenameInput
67
- defaultName={defaultName}
68
- isEditing={isEditing}
69
- nodeId={nodeId}
70
- onStartEditing={handleStartEditing}
71
- onStopEditing={handleStopEditing}
72
- />
68
+ footprint ? (
69
+ <span className="truncate" data-footprint-entry={nodeId}>
70
+ {footprint}
71
+ </span>
72
+ ) : (
73
+ <InlineRenameInput
74
+ defaultName={defaultName}
75
+ isEditing={isEditing}
76
+ nodeId={nodeId}
77
+ onStartEditing={handleStartEditing}
78
+ onStopEditing={handleStopEditing}
79
+ />
80
+ )
73
81
  }
74
82
  nodeId={nodeId}
75
83
  onClick={handleClick}
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useState } from 'react'
5
5
  import { SnapTargetIcon } from '../../../snap-target-badge'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -30,15 +29,12 @@ export const SolarPanelTreeNode = memo(function SolarPanelTreeNode({
30
29
  const handleClick = useCallback(
31
30
  (e: React.MouseEvent) => {
32
31
  e.stopPropagation()
33
- const handled = handleTreeSelection(
32
+ handleTreeSelection(
34
33
  e,
35
34
  nodeId,
36
35
  useViewer.getState().selection.selectedIds,
37
36
  setSelection,
38
37
  )
39
- if (!handled && useEditor.getState().phase === 'furnish') {
40
- useEditor.getState().setPhase('structure')
41
- }
42
38
  },
43
39
  [nodeId, setSelection],
44
40
  )
@@ -4,7 +4,6 @@ import { type SpawnNode, useScene } from '@pascal-app/core'
4
4
  import { useViewer } from '@pascal-app/viewer'
5
5
  import Image from 'next/image'
6
6
  import { memo, useCallback, useState } from 'react'
7
- import useEditor from './../../../../../store/use-editor'
8
7
  import { InlineRenameInput } from './inline-rename-input'
9
8
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -30,15 +29,12 @@ export const SpawnTreeNode = memo(function SpawnTreeNode({
30
29
  const handleClick = useCallback(
31
30
  (e: React.MouseEvent) => {
32
31
  e.stopPropagation()
33
- const handled = handleTreeSelection(
32
+ handleTreeSelection(
34
33
  e,
35
34
  nodeId,
36
35
  useViewer.getState().selection.selectedIds,
37
36
  setSelection,
38
37
  )
39
- if (!handled && useEditor.getState().phase === 'furnish') {
40
- useEditor.getState().setPhase('structure')
41
- }
42
38
  },
43
39
  [nodeId, setSelection],
44
40
  )
@@ -4,7 +4,6 @@ import { AnimatePresence } from 'motion/react'
4
4
  import Image from 'next/image'
5
5
  import { memo, useCallback, useEffect, useState } from 'react'
6
6
  import { useShallow } from 'zustand/react/shallow'
7
- import useEditor from '../../../../../store/use-editor'
8
7
  import { InlineRenameInput } from './inline-rename-input'
9
8
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
9
  import { TreeNodeActions } from './tree-node-actions'
@@ -48,15 +47,12 @@ export const StairTreeNode = memo(function StairTreeNode({
48
47
  const handleClick = useCallback(
49
48
  (e: React.MouseEvent) => {
50
49
  e.stopPropagation()
51
- const handled = handleTreeSelection(
50
+ handleTreeSelection(
52
51
  e,
53
52
  nodeId,
54
53
  useViewer.getState().selection.selectedIds,
55
54
  setSelection,
56
55
  )
57
- if (!handled && useEditor.getState().phase === 'furnish') {
58
- useEditor.getState().setPhase('structure')
59
- }
60
56
  },
61
57
  [nodeId, setSelection],
62
58
  )
@@ -3,8 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import { ChevronRight } from 'lucide-react'
4
4
  import { AnimatePresence, motion } from 'motion/react'
5
5
  import { forwardRef, memo, useEffect, useRef } from 'react'
6
- import { resolveNodeSelectionTarget } from '../../../../../lib/selection-routing'
7
- import useEditor from '../../../../../store/use-editor'
8
6
  import { expandSessionSelectionForNode } from '../../../../../store/use-session-groups'
9
7
 
10
8
  export function handleTreeSelection(
@@ -63,31 +61,6 @@ export function focusTreeNode(nodeId: AnyNodeId) {
63
61
  emitter.emit('camera-controls:focus', { nodeId })
64
62
  }
65
63
 
66
- export function routeTreeSelectionToNode(node: AnyNode | null | undefined) {
67
- const target = node ? resolveNodeSelectionTarget(node) : null
68
- if (!target) return
69
-
70
- const selectedIdsAfterClick = useViewer.getState().selection.selectedIds
71
- const editor = useEditor.getState()
72
- let didRoute = false
73
-
74
- if (target.phase !== editor.phase) {
75
- editor.setPhase(target.phase)
76
- didRoute = true
77
- }
78
- if (
79
- target.phase === 'structure' &&
80
- target.structureLayer &&
81
- target.structureLayer !== useEditor.getState().structureLayer
82
- ) {
83
- useEditor.getState().setStructureLayer(target.structureLayer)
84
- didRoute = true
85
- }
86
- if (didRoute) {
87
- useViewer.getState().setSelection({ selectedIds: selectedIdsAfterClick })
88
- }
89
- }
90
-
91
64
  import { cn } from '../../../../../lib/utils'
92
65
  import { BuildingTreeNode } from './building-tree-node'
93
66
  import { CeilingTreeNode } from './ceiling-tree-node'
@@ -14,7 +14,7 @@ import { useShallow } from 'zustand/react/shallow'
14
14
  import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
15
15
  import { assignZoneToUnit, enterUnitFocus } from './../../../../../lib/units'
16
16
  import { InlineRenameInput } from './inline-rename-input'
17
- import { routeTreeSelectionToNode, TreeNodeWrapper } from './tree-node'
17
+ import { TreeNodeWrapper } from './tree-node'
18
18
 
19
19
  type UnitWarningCode = ReturnType<typeof unitWarnings>[number]['code']
20
20
 
@@ -55,7 +55,6 @@ export const UnitZoneRow = memo(function UnitZoneRow({
55
55
  event.stopPropagation()
56
56
  if (!zone) return
57
57
  setSelection({ levelId: zone.parentId as LevelNode['id'], zoneId })
58
- routeTreeSelectionToNode(zone)
59
58
  },
60
59
  [zone, zoneId, setSelection],
61
60
  )
@@ -94,7 +93,7 @@ export const UnitZoneRow = memo(function UnitZoneRow({
94
93
  keepIconColor
95
94
  label={
96
95
  <span className="flex min-w-0 items-baseline gap-1.5">
97
- <span className="truncate">{zone.name || 'Zone'}</span>
96
+ <span className="truncate">{zone.name || (zone.spaceRole === 'room' ? 'Room' : 'Zone')}</span>
98
97
  {levelLabel && (
99
98
  <span className="shrink-0 text-muted-foreground text-xs">{levelLabel}</span>
100
99
  )}
@@ -3,7 +3,6 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useEffect, useRef, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
- import useEditor from './../../../../../store/use-editor'
7
6
  import { InlineRenameInput } from './inline-rename-input'
8
7
  import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
9
8
  import { TreeNodeActions } from './tree-node-actions'
@@ -52,15 +51,12 @@ export const WallTreeNode = memo(function WallTreeNode({
52
51
  const handleClick = useCallback(
53
52
  (e: React.MouseEvent) => {
54
53
  e.stopPropagation()
55
- const handled = handleTreeSelection(
54
+ handleTreeSelection(
56
55
  e,
57
56
  nodeId,
58
57
  useViewer.getState().selection.selectedIds,
59
58
  setSelection,
60
59
  )
61
- if (!handled && useEditor.getState().phase === 'furnish') {
62
- useEditor.getState().setPhase('structure')
63
- }
64
60
  },
65
61
  [nodeId, setSelection],
66
62
  )
@@ -5,7 +5,6 @@ import { useViewer } from '@pascal-app/viewer'
5
5
  import Image from 'next/image'
6
6
  import { memo, useCallback, useState } from 'react'
7
7
  import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badge'
8
- import useEditor from './../../../../../store/use-editor'
9
8
  import { InlineRenameInput } from './inline-rename-input'
10
9
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
11
10
  import { TreeNodeActions } from './tree-node-actions'
@@ -32,15 +31,12 @@ export const WindowTreeNode = memo(function WindowTreeNode({
32
31
  const handleClick = useCallback(
33
32
  (e: React.MouseEvent) => {
34
33
  e.stopPropagation()
35
- const handled = handleTreeSelection(
34
+ handleTreeSelection(
36
35
  e,
37
36
  nodeId,
38
37
  useViewer.getState().selection.selectedIds,
39
38
  setSelection,
40
39
  )
41
- if (!handled && useEditor.getState().phase === 'furnish') {
42
- useEditor.getState().setPhase('structure')
43
- }
44
40
  },
45
41
  [nodeId, setSelection],
46
42
  )
@@ -3,6 +3,11 @@ import { useViewer } from '@pascal-app/viewer'
3
3
  import { memo, useCallback, useState } from 'react'
4
4
  import { ColorDot } from './../../../../../components/ui/primitives/color-dot'
5
5
  import { formatAreaLabel } from './../../../../../lib/measurements'
6
+ import {
7
+ selectZoneOrRoom,
8
+ useZoneSelected,
9
+ zoneKindLabel,
10
+ } from './../../../../../lib/room-zone-routing'
6
11
  import { toggleZoneMembership } from './../../../../../lib/units'
7
12
  import { InlineRenameInput } from './inline-rename-input'
8
13
  import { focusTreeNode, TreeNodeWrapper } from './tree-node'
@@ -25,9 +30,9 @@ export const ZoneTreeNode = memo(function ZoneTreeNode({
25
30
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
26
31
  const color = useScene((s) => (s.nodes[nodeId] as ZoneNode | undefined)?.color)
27
32
  const polygon = useScene((s) => (s.nodes[nodeId] as ZoneNode | undefined)?.polygon ?? [])
28
- const isSelected = useViewer((state) => state.selection.zoneId === nodeId)
33
+ const isSelected = useZoneSelected(nodeId)
34
+ const kind = useScene((s) => zoneKindLabel(s.nodes[nodeId] as ZoneNode | undefined))
29
35
  const isHovered = useViewer((state) => state.hoveredId === nodeId)
30
- const setSelection = useViewer((state) => state.setSelection)
31
36
  const setHoveredId = useViewer((state) => state.setHoveredId)
32
37
  const unit = useViewer((state) => state.unit)
33
38
  const focusedUnitId = useViewer((state) => state.focusedUnitId)
@@ -36,14 +41,14 @@ export const ZoneTreeNode = memo(function ZoneTreeNode({
36
41
  return focused?.type === 'unit' ? focused : null
37
42
  })
38
43
 
39
- const handleClick = useCallback(() => setSelection({ zoneId: nodeId }), [nodeId, setSelection])
44
+ const handleClick = useCallback(() => selectZoneOrRoom(nodeId), [nodeId])
40
45
  const handleDoubleClick = useCallback(() => focusTreeNode(nodeId), [nodeId])
41
46
  const handleMouseEnter = useCallback(() => setHoveredId(nodeId), [nodeId, setHoveredId])
42
47
  const handleMouseLeave = useCallback(() => setHoveredId(null), [setHoveredId])
43
48
  const handleStartEditing = useCallback(() => setIsEditing(true), [])
44
49
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
45
50
 
46
- const defaultName = `Zone (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
51
+ const defaultName = `${kind} (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
47
52
 
48
53
  return (
49
54
  <TreeNodeWrapper
@@ -137,7 +137,6 @@ export function ZonePanel() {
137
137
  const currentLevelId = useViewer((state) => state.selection.levelId)
138
138
  const selectedZoneId = useViewer((state) => state.selection.zoneId)
139
139
  const setSelection = useViewer((state) => state.setSelection)
140
- const setPhase = useEditor((state) => state.setPhase)
141
140
  const setMode = useEditor((state) => state.setMode)
142
141
  const setTool = useEditor((state) => state.setTool)
143
142
 
@@ -149,7 +148,6 @@ export function ZonePanel() {
149
148
 
150
149
  const handleAddZone = () => {
151
150
  if (currentLevelId) {
152
- setPhase('structure')
153
151
  setMode('build')
154
152
  setTool('zone')
155
153
  }
@@ -19,6 +19,16 @@ export type SidebarTab = {
19
19
  * keeps the icon highlighted regardless of collapse.
20
20
  */
21
21
  noPanel?: boolean
22
+ /** Runs when the entry is picked — with `noPanel`, to put its surface on the stage. */
23
+ onSelect?: () => void
24
+ /**
25
+ * For a `noPanel` entry whose surface can leave the stage on its own (a view
26
+ * closed from the view bar): false once it has, and the rail goes back to
27
+ * the panel that was open before it.
28
+ */
29
+ stageActive?: boolean
30
+ /** Runs when the rail moves from this entry to another: a stage entry takes its surface down. */
31
+ onDeselect?: () => void
22
32
  }
23
33
 
24
34
  interface TabBarProps {
@@ -12,28 +12,37 @@ import {
12
12
  useSyncExternalStore,
13
13
  } from 'react'
14
14
  import useEditor from '../../../store/use-editor'
15
+ import { isViewVisible } from '../../../store/view-layout'
16
+ import { PluginsView } from '../../editor/views/plugins-view'
17
+ import { PLUGINS_VIEW_ID } from '../../editor/views/use-editor-views'
15
18
  import {
16
19
  editorHostPanelRegistry,
17
20
  type EditorHostPanel,
18
21
  managedPluginIds,
19
22
  showsPluginManager,
20
23
  } from '../../../lib/plugin-panels'
24
+ import { IconRefImage } from '../icon-ref'
21
25
  import { ErrorBoundary } from '../primitives/error-boundary'
22
26
  import type { ExtraPanel } from './icon-rail'
23
- import { PluginsPanel } from './panels/plugins-panel'
24
27
 
28
+ // The rail's "+" puts the plugin directory on the stage, as a view in the
29
+ // focused pane, and stays lit while it is there instead of leaving an
30
+ // unrelated panel open beside it. `component` serves the v1 sidebar.
25
31
  const pluginsManagerPanel: ExtraPanel = {
26
32
  id: 'plugins',
27
33
  label: 'Plugins',
28
- icon: <Plus className="h-5 w-5" />,
29
- component: PluginsPanel,
34
+ icon: <img alt="" className="size-8 object-contain" src="/icons/plugins.webp" />,
35
+ component: PluginsView,
36
+ noPanel: true,
37
+ onSelect: () => useEditor.getState().showView(PLUGINS_VIEW_ID),
38
+ onDeselect: () => useEditor.getState().closeView(PLUGINS_VIEW_ID),
30
39
  }
31
40
 
32
41
  /** Resolve a plugin's {@link IconRef} into a rail-sized React node. Mirrors the
33
42
  * inspector's `renderIcon`, sized for the 24px icon-rail button. */
34
43
  function renderIconRef(ref: IconRef): ReactNode {
35
44
  if (ref.kind === 'url') {
36
- return <img alt="" className="h-5 w-5 object-contain" src={ref.src} />
45
+ return <IconRefImage className="h-5 w-5" src={ref.src} />
37
46
  }
38
47
  if (ref.kind === 'iconify') {
39
48
  return <Icon height={20} icon={ref.name} width={20} />
@@ -106,6 +115,7 @@ export function useHostPanels(hostPanels?: ExtraPanel[]): ExtraPanel[] {
106
115
  const workspaceMode = useEditor((s) => s.workspaceMode)
107
116
  const installedPlugins = useScene((s) => s.installedPlugins)
108
117
  const readOnly = useScene((s) => s.readOnly)
118
+ const pluginsViewShown = useEditor((s) => isViewVisible(s, PLUGINS_VIEW_ID))
109
119
  const hostIds = new Set(hostPanels?.map((p) => p.id))
110
120
 
111
121
  useEffect(() => {
@@ -141,7 +151,7 @@ export function useHostPanels(hostPanels?: ExtraPanel[]): ExtraPanel[] {
141
151
  readOnly,
142
152
  workspaceMode,
143
153
  })
144
- ? [pluginsManagerPanel]
154
+ ? [{ ...pluginsManagerPanel, stageActive: pluginsViewShown }]
145
155
  : []
146
156
  return [...(hostPanels ?? []), ...fromRegistry, ...manager]
147
157
  }
@@ -1,25 +1,33 @@
1
1
  'use client'
2
2
 
3
3
  import type React from 'react'
4
+ import { cn } from '../../lib/utils'
4
5
  import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/primitives/tooltip'
5
6
 
6
7
  export type FloorplanCompassButtonProps = {
7
8
  northRotationDeg: number
8
9
  onAlignNorth: () => void
9
10
  needleRef?: React.RefObject<SVGSVGElement | null>
11
+ /** Horizontal anchor; defaults to the bottom-left corner. */
12
+ style?: React.CSSProperties
10
13
  }
11
14
 
12
15
  export function FloorplanCompassButton({
13
16
  northRotationDeg,
14
17
  onAlignNorth,
15
18
  needleRef,
19
+ style,
16
20
  }: FloorplanCompassButtonProps) {
17
21
  return (
18
22
  <Tooltip>
19
23
  <TooltipTrigger asChild>
20
24
  <button
21
25
  aria-label="Align view to north"
22
- className="group pointer-events-auto absolute bottom-3 left-3 z-30 flex h-8 w-8 items-center justify-center rounded-full border border-black/10 bg-white/85 shadow-sm backdrop-blur-md transition hover:bg-white hover:shadow-md dark:border-white/10 dark:bg-neutral-900/85 dark:hover:bg-neutral-900"
26
+ className={cn(
27
+ 'group pointer-events-auto absolute bottom-3 z-30 flex h-8 w-8 items-center justify-center rounded-full border border-black/10 bg-white/85 shadow-sm backdrop-blur-md transition hover:bg-white hover:shadow-md dark:border-white/10 dark:bg-neutral-900/85 dark:hover:bg-neutral-900',
28
+ !style && 'left-3',
29
+ )}
30
+ style={style}
23
31
  onClick={(event) => {
24
32
  event.preventDefault()
25
33
  event.stopPropagation()
@@ -6,6 +6,7 @@ import {
6
6
  type FloorplanGeometry,
7
7
  type FloorplanPalette,
8
8
  type GeometryContext,
9
+ getLevelDisplayName,
9
10
  isNodeKindEnabled,
10
11
  nodeRegistry,
11
12
  useScene,
@@ -143,12 +144,10 @@ function boundsToViewBox(bounds: FloorplanBounds): FloorplanViewBox {
143
144
  }
144
145
 
145
146
  function levelLabel(level: AnyNode): string {
146
- const named = (level as { name?: string }).name?.trim()
147
- if (named) return named
148
- const ordinal = (level as { level?: number }).level ?? 0
149
- if (ordinal === 0) return 'Ground floor'
150
- if (ordinal < 0) return `Basement ${Math.abs(ordinal)}`
151
- return `Level ${ordinal}`
147
+ return getLevelDisplayName({
148
+ name: (level as { name?: string }).name,
149
+ level: (level as { level?: number }).level ?? 0,
150
+ })
152
151
  }
153
152
 
154
153
  function collectLevelTree(root: AnyNode, nodes: Record<string, AnyNode>): AnyNode[] {
@@ -0,0 +1,34 @@
1
+ 'use client'
2
+
3
+ import { useScene } from '@pascal-app/core'
4
+ import { useViewer } from '@pascal-app/viewer'
5
+ import { nodeDisplayLabel } from '../../lib/node-display-label'
6
+ import { type ViewerNodes, zoneLabel } from '../../lib/viewer-selection'
7
+
8
+ /** The hovered node's name, for the bottom pill. A room reads as the room. */
9
+ export function viewerHoverLabel(hoveredId: string | null, nodes: ViewerNodes): string | null {
10
+ const node = hoveredId ? nodes[hoveredId] : undefined
11
+ if (!node) return null
12
+ return node.type === 'zone' ? zoneLabel(node) : nodeDisplayLabel(node)
13
+ }
14
+
15
+ /**
16
+ * What a click would select, named in a pill above the controls bar. Every
17
+ * viewer surface's hit source sets the hover (`applyViewerHover`); hidden in
18
+ * the walkthrough and on devices without a hovering pointer. Names come from
19
+ * the parametric scene store unless the surface passes its own graph.
20
+ */
21
+ export function ViewerHoverLabel({ nodes: nodesProp }: { nodes?: ViewerNodes }) {
22
+ const sceneNodes = useScene((s) => s.nodes)
23
+ const nodes = nodesProp ?? sceneNodes
24
+ const label = useViewer((s) => (s.walkthroughMode ? null : viewerHoverLabel(s.hoveredId, nodes)))
25
+ if (!label) return null
26
+ return (
27
+ <div
28
+ className="pointer-events-none absolute bottom-24 left-1/2 z-20 -translate-x-1/2 rounded-full border border-border/60 bg-background/90 px-3 py-1 text-foreground text-sm shadow-elevation-3 backdrop-blur [@media(hover:none)]:hidden"
29
+ data-testid="viewer-hover-label"
30
+ >
31
+ {label}
32
+ </div>
33
+ )
34
+ }