@pascal-app/editor 1.0.6 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (382) hide show
  1. package/package.json +18 -15
  2. package/src/components/editor/action-menu-button.tsx +4 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +102 -11
  4. package/src/components/editor/bake-thumbnail.tsx +3 -2
  5. package/src/components/editor/custom-camera-controls.tsx +110 -42
  6. package/src/components/editor/duplicate-node.ts +2 -1
  7. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  8. package/src/components/editor/first-person-controls.tsx +77 -68
  9. package/src/components/editor/fit-scene-framing.test.ts +318 -0
  10. package/src/components/editor/fit-scene-framing.ts +142 -0
  11. package/src/components/editor/floating-action-menu.tsx +31 -11
  12. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  13. package/src/components/editor/floor-openings-overlay.tsx +1 -1
  14. package/src/components/editor/floor-region-controls.tsx +2 -2
  15. package/src/components/editor/floorplan-panel.test.ts +220 -0
  16. package/src/components/editor/floorplan-panel.tsx +458 -140
  17. package/src/components/editor/floorplan-wall-typing-keys.test.tsx +152 -0
  18. package/src/components/editor/floorplan-wall-typing-keys.ts +79 -0
  19. package/src/components/editor/footprint-height-handle.tsx +2 -2
  20. package/src/components/editor/grid.tsx +24 -15
  21. package/src/components/editor/group-actions.ts +19 -1
  22. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  23. package/src/components/editor/group-move-3d.ts +27 -2
  24. package/src/components/editor/group-selection-box-3d.tsx +4 -1
  25. package/src/components/editor/group-transform-shared.ts +23 -1
  26. package/src/components/editor/index.tsx +150 -20
  27. package/src/components/editor/inspector-extension-actions.tsx +36 -0
  28. package/src/components/editor/localized-html.tsx +22 -0
  29. package/src/components/editor/measurement-pill.tsx +1 -1
  30. package/src/components/editor/node-action-menu.tsx +46 -12
  31. package/src/components/editor/node-arrow-handles.tsx +2 -3
  32. package/src/components/editor/open-wall-ends-3d-layer.tsx +2 -3
  33. package/src/components/editor/open-wall-ends-hint.tsx +1 -1
  34. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  35. package/src/components/editor/quick-measurement-card.tsx +1 -1
  36. package/src/components/editor/registry-action-contributions.test.tsx +67 -6
  37. package/src/components/editor/registry-action-contributions.tsx +115 -15
  38. package/src/components/editor/riser-diagram-panel.tsx +59 -9
  39. package/src/components/editor/room-controls.tsx +4 -3
  40. package/src/components/editor/room-handles-2d.tsx +158 -7
  41. package/src/components/editor/room-handles.tsx +244 -54
  42. package/src/components/editor/room-highlight.tsx +2 -0
  43. package/src/components/editor/room-labels.tsx +33 -17
  44. package/src/components/editor/selection-manager.tsx +15 -102
  45. package/src/components/editor/separator-end-grip.tsx +74 -0
  46. package/src/components/editor/site-edge-labels.tsx +7 -3
  47. package/src/components/editor/thumbnail-generator.test.ts +15 -6
  48. package/src/components/editor/thumbnail-generator.tsx +26 -1
  49. package/src/components/editor/use-floorplan-background-placement.ts +55 -52
  50. package/src/components/editor/views/view-bar.tsx +111 -26
  51. package/src/components/editor/wall-measurement-label.tsx +31 -15
  52. package/src/components/editor/wall-move-side-handles.tsx +15 -7
  53. package/src/components/editor/wall-pull-grip.tsx +193 -0
  54. package/src/components/editor/wall-region-handles.tsx +2 -2
  55. package/src/components/editor/wall-snap-beacon-layer.tsx +134 -33
  56. package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
  57. package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
  58. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
  59. package/src/components/editor-2d/floorplan-group-move.tsx +32 -4
  60. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
  61. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +33 -24
  62. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +12 -5
  63. package/src/components/editor-2d/floorplan-registry-action-menu.tsx +2 -1
  64. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +29 -5
  65. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
  66. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
  67. package/src/components/editor-2d/renderers/floorplan-draft-measurement.tsx +7 -1
  68. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +13 -1
  69. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.test.tsx +35 -0
  70. package/src/components/editor-2d/renderers/floorplan-placement-typing-hud.tsx +65 -0
  71. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +34 -1
  72. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +211 -39
  73. package/src/components/procedural-items/inspector.tsx +59 -12
  74. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
  75. package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
  76. package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
  77. package/src/components/systems/roof/roof-edit-system.test.tsx +15 -1
  78. package/src/components/systems/roof/roof-edit-system.tsx +30 -19
  79. package/src/components/tools/distribution/distribution-run-tool.test.tsx +371 -3
  80. package/src/components/tools/distribution/distribution-run-tool.tsx +200 -58
  81. package/src/components/tools/item/move-tool.tsx +1 -6
  82. package/src/components/tools/item/placement-history.test.tsx +1 -1
  83. package/src/components/tools/item/placement-strategies.test.ts +64 -1
  84. package/src/components/tools/item/placement-strategies.ts +5 -9
  85. package/src/components/tools/item/use-placement-coordinator.tsx +31 -35
  86. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +1 -1
  87. package/src/components/tools/registry/item-surface-move.test.ts +11 -0
  88. package/src/components/tools/registry/move-registry-node-tool.test.ts +42 -1
  89. package/src/components/tools/registry/move-registry-node-tool.tsx +38 -14
  90. package/src/components/tools/select/select-candidates.test.ts +45 -0
  91. package/src/components/tools/select/select-candidates.ts +13 -2
  92. package/src/components/tools/shared/cursor-sphere.tsx +10 -3
  93. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  94. package/src/components/tools/shared/placement-box.tsx +2 -6
  95. package/src/components/tools/shared/placement-coordinate-input.tsx +94 -0
  96. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  97. package/src/components/tools/shared/segment-angle.ts +0 -4
  98. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  99. package/src/components/tools/tool-manager.tsx +1 -24
  100. package/src/components/tools/wall/wall-drafting.test.ts +299 -23
  101. package/src/components/tools/wall/wall-drafting.ts +132 -24
  102. package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
  103. package/src/components/tools/wall/wall-pull-snap.ts +563 -0
  104. package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
  105. package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
  106. package/src/components/tools/wall/wall-typed-entry.test.ts +99 -0
  107. package/src/components/tools/wall/wall-typed-entry.ts +70 -0
  108. package/src/components/tools/zone/zone-tool.tsx +3 -3
  109. package/src/components/ui/action-menu/action-button.tsx +2 -0
  110. package/src/components/ui/action-menu/camera-actions.tsx +8 -7
  111. package/src/components/ui/action-menu/control-modes.tsx +9 -5
  112. package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
  113. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
  114. package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
  115. package/src/components/ui/action-menu/index.tsx +1 -0
  116. package/src/components/ui/action-menu/measurement-control.tsx +35 -29
  117. package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
  118. package/src/components/ui/action-menu/structure-tools.tsx +25 -23
  119. package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
  120. package/src/components/ui/action-menu/view-toggles.tsx +256 -51
  121. package/src/components/ui/build-panel/build-panel.tsx +163 -25
  122. package/src/components/ui/command-palette/editor-commands.tsx +38 -11
  123. package/src/components/ui/controls/finish-swatch.tsx +45 -19
  124. package/src/components/ui/controls/material-picker.tsx +24 -17
  125. package/src/components/ui/controls/metric-control.test.ts +52 -0
  126. package/src/components/ui/controls/metric-control.tsx +62 -24
  127. package/src/components/ui/controls/segmented-control.tsx +5 -2
  128. package/src/components/ui/controls/slider-control.test.tsx +23 -0
  129. package/src/components/ui/controls/slider-control.tsx +47 -19
  130. package/src/components/ui/controls/tool-options-panel.tsx +74 -44
  131. package/src/components/ui/floating-level-selector.tsx +100 -48
  132. package/src/components/ui/helpers/building-helper.tsx +5 -3
  133. package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
  134. package/src/components/ui/helpers/helper-manager.test.tsx +106 -3
  135. package/src/components/ui/helpers/helper-manager.tsx +136 -57
  136. package/src/components/ui/helpers/item-helper.tsx +6 -4
  137. package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
  138. package/src/components/ui/level-duplicate-dialog.tsx +32 -23
  139. package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
  140. package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
  141. package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
  142. package/src/components/ui/panels/floor-foundation-panel.tsx +114 -39
  143. package/src/components/ui/panels/floor-opening-panel.tsx +74 -26
  144. package/src/components/ui/panels/inspector-extensions.tsx +66 -0
  145. package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
  146. package/src/components/ui/panels/mobile-selection-bar.tsx +34 -28
  147. package/src/components/ui/panels/multi-field-value.test.ts +27 -0
  148. package/src/components/ui/panels/multi-field-value.ts +20 -1
  149. package/src/components/ui/panels/multi-height-mode.tsx +11 -10
  150. package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
  151. package/src/components/ui/panels/multi-selection-panel.tsx +76 -36
  152. package/src/components/ui/panels/node-display.ts +35 -24
  153. package/src/components/ui/panels/panel-manager.tsx +9 -5
  154. package/src/components/ui/panels/panel-wrapper.tsx +63 -127
  155. package/src/components/ui/panels/parametric-field-control.tsx +13 -10
  156. package/src/components/ui/panels/parametric-inspector.tsx +18 -12
  157. package/src/components/ui/panels/reference-panel.tsx +51 -35
  158. package/src/components/ui/panels/room-construction-rows.tsx +465 -66
  159. package/src/components/ui/panels/room-name-options.test.ts +36 -3
  160. package/src/components/ui/panels/room-name-options.ts +110 -11
  161. package/src/components/ui/panels/room-openings-field.tsx +22 -13
  162. package/src/components/ui/panels/room-panel.tsx +207 -100
  163. package/src/components/ui/panels/shape-choice.tsx +7 -5
  164. package/src/components/ui/panels/wall-region-list.tsx +7 -7
  165. package/src/components/ui/primitives/shortcut-token.tsx +10 -6
  166. package/src/components/ui/right-stack.test.ts +13 -6
  167. package/src/components/ui/right-stack.tsx +21 -10
  168. package/src/components/ui/sidebar/icon-rail.tsx +11 -9
  169. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
  170. package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
  171. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
  172. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
  173. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -5
  174. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
  175. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +5 -3
  176. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
  177. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
  178. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
  179. package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
  180. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
  181. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
  182. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
  183. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
  184. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
  185. package/src/components/ui/sidebar/panels/site-panel/index.tsx +118 -72
  186. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
  187. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
  188. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
  189. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
  190. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
  191. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
  192. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +9 -4
  193. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
  194. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
  195. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
  196. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
  197. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
  198. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
  199. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
  200. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
  201. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
  202. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +19 -9
  203. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
  204. package/src/components/ui/sidebar/tab-bar.tsx +4 -1
  205. package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
  206. package/src/components/ui/snap-target-badge.tsx +7 -6
  207. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
  208. package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
  209. package/src/components/viewer/floorplan-compass-button.tsx +12 -3
  210. package/src/components/viewer/floorplan-preview.tsx +27 -19
  211. package/src/components/viewer/render-settings-menu.tsx +294 -0
  212. package/src/components/viewer/sky-control.tsx +710 -0
  213. package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
  214. package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
  215. package/src/components/viewer/viewer-controls-bar.tsx +278 -260
  216. package/src/components/viewer/viewer-hover-label.tsx +12 -4
  217. package/src/components/viewer/viewer-rooms.tsx +62 -5
  218. package/src/components/viewer/viewer-scene-header.tsx +28 -17
  219. package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
  220. package/src/components/viewer-overlay.tsx +8 -0
  221. package/src/components/walkthrough-hud.tsx +75 -2
  222. package/src/hooks/use-keyboard.test.ts +85 -4
  223. package/src/hooks/use-keyboard.ts +65 -7
  224. package/src/hooks/use-selected-room.ts +12 -1
  225. package/src/i18n/floor-footprint-name.ts +30 -0
  226. package/src/i18n/level-name.ts +39 -0
  227. package/src/i18n/locales/en/messages.po +15849 -0
  228. package/src/i18n/locales/en/messages.ts +1 -0
  229. package/src/i18n/locales/en-XA/messages.ts +1 -0
  230. package/src/i18n/locales/es/messages.po +39331 -0
  231. package/src/i18n/locales/es/messages.ts +1 -0
  232. package/src/i18n/locales/fr/messages.po +39331 -0
  233. package/src/i18n/locales/fr/messages.ts +1 -0
  234. package/src/i18n/locales/ja/messages.po +39331 -0
  235. package/src/i18n/locales/ja/messages.ts +1 -0
  236. package/src/i18n/locales/zh-CN/messages.po +39331 -0
  237. package/src/i18n/locales/zh-CN/messages.ts +1 -0
  238. package/src/i18n/locales.test.ts +63 -0
  239. package/src/i18n/locales.ts +32 -0
  240. package/src/i18n/message-slot.tsx +25 -0
  241. package/src/i18n/node-messages.ts +169 -0
  242. package/src/i18n/pseudo-locale.ts +17 -0
  243. package/src/i18n/published-entry.test.ts +17 -0
  244. package/src/i18n/scene-theme-name.ts +16 -0
  245. package/src/index.tsx +224 -45
  246. package/src/lib/add-storey.test.ts +3 -3
  247. package/src/lib/contextual-help.test.ts +20 -1
  248. package/src/lib/contextual-help.ts +101 -19
  249. package/src/lib/continuation.ts +38 -11
  250. package/src/lib/elevation-guides.ts +21 -6
  251. package/src/lib/elevator-support.ts +1 -72
  252. package/src/lib/floor-footprints.test.ts +3 -2
  253. package/src/lib/floor-footprints.ts +1 -9
  254. package/src/lib/floor-foundation-support.test.ts +15 -6
  255. package/src/lib/floor-opening-draft.ts +1 -1
  256. package/src/lib/floor-region-session.ts +1 -1
  257. package/src/lib/floorplan/dimension-drive.test.ts +36 -2
  258. package/src/lib/floorplan/dimension-drive.ts +68 -3
  259. package/src/lib/floorplan/floorplan-export.test.ts +16 -5
  260. package/src/lib/floorplan/floorplan-export.tsx +13 -16
  261. package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
  262. package/src/lib/floorplan/floorplan-extension.ts +37 -18
  263. package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
  264. package/src/lib/floorplan/floorplan-mode.ts +14 -12
  265. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
  266. package/src/lib/floorplan/floorplan-readonly.ts +3 -3
  267. package/src/lib/floorplan/open-wall-ends.ts +2 -1
  268. package/src/lib/floorplan/schedules.ts +10 -6
  269. package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
  270. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
  271. package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
  272. package/src/lib/geometry-script/author.test.ts +114 -0
  273. package/src/lib/geometry-script/author.ts +55 -23
  274. package/src/lib/geometry-script/client.ts +3 -0
  275. package/src/lib/geometry-script/finish-scripts.ts +7 -7
  276. package/src/lib/gesture-lifecycle.ts +3 -1
  277. package/src/lib/glb-export.test.ts +327 -0
  278. package/src/lib/glb-export.ts +70 -2
  279. package/src/lib/grid-frame.test.ts +56 -3
  280. package/src/lib/grid-frame.ts +28 -1
  281. package/src/lib/history.test.ts +2 -1
  282. package/src/lib/hud-title.ts +48 -24
  283. package/src/lib/ifc-export.test.ts +41 -1
  284. package/src/lib/inspector-card-mode.test.ts +3 -31
  285. package/src/lib/inspector-card-mode.ts +5 -10
  286. package/src/lib/inspector-expanded.test.ts +13 -0
  287. package/src/lib/inspector-expanded.ts +13 -1
  288. package/src/lib/interaction/scope.ts +1 -0
  289. package/src/lib/keyboard-pan.test.ts +7 -0
  290. package/src/lib/keyboard-pan.ts +9 -2
  291. package/src/lib/layer-thickness.test.ts +22 -0
  292. package/src/lib/layer-thickness.ts +79 -0
  293. package/src/lib/level-selection.test.ts +18 -2
  294. package/src/lib/level-selection.ts +31 -8
  295. package/src/lib/linear-display.ts +1 -1
  296. package/src/lib/live-gesture-round-trip.test.ts +1 -1
  297. package/src/lib/lot/drop-in.test.ts +59 -1
  298. package/src/lib/lot/drop-in.ts +42 -50
  299. package/src/lib/material-swatch-style.ts +24 -0
  300. package/src/lib/measurement-label.test.ts +1 -1
  301. package/src/lib/measurement-parser.ts +7 -3
  302. package/src/lib/measurements.test.ts +24 -6
  303. package/src/lib/measurements.ts +1 -203
  304. package/src/lib/mezzanine-selection.test.ts +3 -1
  305. package/src/lib/node-action-movement.test.tsx +115 -0
  306. package/src/lib/node-action-movement.ts +2 -0
  307. package/src/lib/orbit-near-plane.test.ts +12 -0
  308. package/src/lib/orbit-near-plane.ts +13 -0
  309. package/src/lib/paint-regions.test.ts +8 -5
  310. package/src/lib/paint-scope.test.ts +53 -0
  311. package/src/lib/paint-scope.ts +18 -4
  312. package/src/lib/panel-rows.ts +17 -8
  313. package/src/lib/placement-metadata.ts +4 -0
  314. package/src/lib/point-ask/describe-target.ts +6 -8
  315. package/src/lib/point-ask/region.ts +1 -1
  316. package/src/lib/print-shell-compiler.ts +12 -1
  317. package/src/lib/print-wall-solids.test.ts +11 -0
  318. package/src/lib/print-wall-solids.ts +9 -0
  319. package/src/lib/reference-scale.ts +83 -0
  320. package/src/lib/room-assembly-overlay.ts +1 -1
  321. package/src/lib/room-built-on.test.ts +28 -17
  322. package/src/lib/room-built-on.ts +21 -17
  323. package/src/lib/room-construction-commands.ts +3 -2
  324. package/src/lib/room-construction.test.ts +28 -1
  325. package/src/lib/room-construction.ts +14 -0
  326. package/src/lib/room-controls-phase6.test.ts +141 -1
  327. package/src/lib/room-handle-drag.ts +554 -58
  328. package/src/lib/room-name-catalog.test.ts +8 -0
  329. package/src/lib/room-name-catalog.ts +8 -1
  330. package/src/lib/room-name-messages.test.ts +132 -0
  331. package/src/lib/room-name-messages.ts +147 -0
  332. package/src/lib/room-name-storage.test.ts +334 -0
  333. package/src/lib/room-push-dimensions.test.ts +38 -1
  334. package/src/lib/room-push-dimensions.ts +226 -2
  335. package/src/lib/room-selection.test.ts +23 -6
  336. package/src/lib/scene-bounds.test.ts +144 -0
  337. package/src/lib/scene-bounds.ts +79 -15
  338. package/src/lib/scene-clipboard.test.ts +70 -49
  339. package/src/lib/scene-clipboard.ts +11 -5
  340. package/src/lib/selection-cycle.ts +63 -0
  341. package/src/lib/separator-edit.test.ts +431 -0
  342. package/src/lib/separator-edit.ts +179 -0
  343. package/src/lib/separator-lines.ts +38 -0
  344. package/src/lib/sfx-bus.ts +27 -1
  345. package/src/lib/sfx-player.ts +16 -0
  346. package/src/lib/snapping-mode.test.ts +23 -1
  347. package/src/lib/tool-cursor-icon.ts +5 -1
  348. package/src/lib/tool-shortcuts.ts +130 -0
  349. package/src/lib/viewer-selection.test.ts +28 -24
  350. package/src/lib/viewer-selection.ts +21 -6
  351. package/src/lib/wall-draw-variant.test.ts +7 -6
  352. package/src/lib/wall-draw-variant.ts +107 -6
  353. package/src/lib/wall-nudge.test.ts +44 -0
  354. package/src/lib/wall-nudge.ts +131 -0
  355. package/src/lib/wall-parallel-snap.test.ts +83 -0
  356. package/src/lib/wall-parallel-snap.ts +176 -0
  357. package/src/lib/wall-placement-pointer.test.ts +23 -0
  358. package/src/lib/wall-placement-pointer.ts +18 -0
  359. package/src/lib/wall-pull.test.ts +245 -0
  360. package/src/lib/wall-pull.ts +209 -0
  361. package/src/lib/wall-push-line-up.test.ts +132 -0
  362. package/src/lib/wall-region-list.ts +17 -6
  363. package/src/lib/wall-run-snap.test.ts +188 -0
  364. package/src/lib/wall-run-snap.ts +83 -0
  365. package/src/lib/wall-type.test.ts +125 -0
  366. package/src/lib/wall-type.ts +119 -0
  367. package/src/lib/window-interaction.test.ts +44 -0
  368. package/src/lib/world-grid-snap.test.ts +139 -2
  369. package/src/lib/world-grid-snap.ts +102 -105
  370. package/src/store/use-alignment-guides.ts +27 -1
  371. package/src/store/use-editor.tsx +53 -0
  372. package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
  373. package/src/store/use-floorplan-annotation-visibility.ts +47 -22
  374. package/src/store/use-placement-typing.test.ts +165 -0
  375. package/src/store/use-placement-typing.ts +186 -0
  376. package/src/store/use-wall-snap-indicator.ts +15 -2
  377. package/src/components/tools/elevator/elevator-defaults.ts +0 -8
  378. package/src/components/tools/elevator/elevator-tool.tsx +0 -372
  379. package/src/components/tools/elevator/move-elevator-tool.tsx +0 -262
  380. package/src/lib/door-interaction.ts +0 -102
  381. package/src/lib/measurement-label.ts +0 -68
  382. package/src/lib/window-interaction.ts +0 -100
@@ -1,3 +1,4 @@
1
+ import { type MessageDescriptor, usePascalI18n } from '@pascal-app/core'
1
2
  import { Keyboard } from 'lucide-react'
2
3
  import { Button } from './../../../../../components/ui/primitives/button'
3
4
  import {
@@ -12,27 +13,31 @@ import {
12
13
  ShortcutToken,
13
14
  shortcutDisplayValue,
14
15
  } from './../../../../../components/ui/primitives/shortcut-token'
16
+ import { TOOL_SHORTCUT_KEYS } from './../../../../../lib/tool-shortcuts'
15
17
 
16
18
  type Shortcut = {
17
- keys: string[]
19
+ keys: readonly string[]
18
20
  action: string
19
21
  note?: string
20
22
  }
21
23
 
22
24
  type ShortcutCategory = {
23
- title: string
25
+ title: MessageDescriptor
24
26
  shortcuts: Shortcut[]
25
27
  }
26
28
 
27
29
  const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
28
30
  {
29
- title: 'Editor Navigation',
31
+ title: /* i18n */ {
32
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.editorNavigation',
33
+ message: 'Editor Navigation',
34
+ },
30
35
  shortcuts: [
31
36
  { keys: ['1'], action: 'Switch to Site' },
32
37
  { keys: ['2'], action: 'Switch to Building' },
33
38
  { keys: ['\\'], action: 'Open or close the split view' },
34
- { keys: ['F'], action: 'Open the items catalog with the item tool' },
35
- { keys: ['Z'], action: 'Draw a zone' },
39
+ { keys: TOOL_SHORTCUT_KEYS.item, action: 'Open the items catalog with the item tool' },
40
+ { keys: TOOL_SHORTCUT_KEYS.zone, action: 'Draw a zone' },
36
41
  {
37
42
  keys: ['Cmd/Ctrl', 'Arrow Up'],
38
43
  action: 'Select next level in the active building',
@@ -45,12 +50,21 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
45
50
  ],
46
51
  },
47
52
  {
48
- title: 'Modes & History',
53
+ title: /* i18n */ {
54
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.modesHistory',
55
+ message: 'Modes & History',
56
+ },
49
57
  shortcuts: [
50
- { keys: ['V'], action: 'Switch to Select mode' },
51
- { keys: ['B'], action: 'Switch to Build mode' },
52
- { keys: ['M'], action: 'Activate the last measurement tool' },
53
- { keys: ['X'], action: 'Switch to Delete mode' },
58
+ { keys: TOOL_SHORTCUT_KEYS.select, action: 'Switch to Select mode' },
59
+ {
60
+ keys: TOOL_SHORTCUT_KEYS.wall,
61
+ action: 'Switch to Build mode, drawing with the last Rooms shape',
62
+ note: 'Rectangle, Polygon or Walls, whichever you picked last; Rectangle the first time.',
63
+ },
64
+ { keys: TOOL_SHORTCUT_KEYS.measurement, action: 'Activate the last measurement tool' },
65
+ { keys: TOOL_SHORTCUT_KEYS.paint, action: 'Switch to Paint mode' },
66
+ { keys: TOOL_SHORTCUT_KEYS.terrain, action: 'Sculpt the ground (terrain)' },
67
+ { keys: TOOL_SHORTCUT_KEYS.delete, action: 'Switch to Delete mode' },
54
68
  {
55
69
  keys: ['Esc'],
56
70
  action: 'Cancel the active tool and return to Select mode',
@@ -63,7 +77,30 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
63
77
  ],
64
78
  },
65
79
  {
66
- title: 'Selection',
80
+ title: /* i18n */ {
81
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.buildTools',
82
+ message: 'Build Tools',
83
+ },
84
+ shortcuts: [
85
+ {
86
+ keys: TOOL_SHORTCUT_KEYS.door,
87
+ action: 'Place a door',
88
+ note: 'D alone pans the view. While placing, the door swings toward the side of the wall the cursor is on; R flips it.',
89
+ },
90
+ { keys: TOOL_SHORTCUT_KEYS.window, action: 'Place a window', note: 'W alone pans the view.' },
91
+ { keys: TOOL_SHORTCUT_KEYS.stair, action: 'Place stairs', note: 'S alone pans the view.' },
92
+ {
93
+ keys: TOOL_SHORTCUT_KEYS.roof,
94
+ action: 'Draw a roof',
95
+ note: "R alone still rotates or flips the selection, and while placing it stays the placement's own R.",
96
+ },
97
+ ],
98
+ },
99
+ {
100
+ title: /* i18n */ {
101
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.selection',
102
+ message: 'Selection',
103
+ },
67
104
  shortcuts: [
68
105
  {
69
106
  keys: ['Cmd/Ctrl', 'C'],
@@ -78,8 +115,7 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
78
115
  {
79
116
  keys: ['Cmd/Ctrl', 'V'],
80
117
  action: 'Paste and place copied objects',
81
- note:
82
- 'Carries a preview under the cursor. Click to place it, or press Escape to cancel.',
118
+ note: 'Carries a preview under the cursor. Click to place it, or press Escape to cancel.',
83
119
  },
84
120
  {
85
121
  keys: ['Cmd/Ctrl', 'Left click'],
@@ -105,14 +141,23 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
105
141
  {
106
142
  keys: ['Cmd/Ctrl', 'G'],
107
143
  action: 'Group the multi-selection (session only)',
108
- note:
109
- 'Editor-only. Plain click a member later to reselect the whole group. Not saved with the project.',
144
+ note: 'Editor-only. Plain click a member later to reselect the whole group. Not saved with the project.',
110
145
  },
111
146
  {
112
147
  keys: ['Cmd/Ctrl', 'Shift', 'G'],
113
148
  action: 'Ungroup the session selection',
114
149
  note: 'Keeps the current selection; only dissolves the session group.',
115
150
  },
151
+ {
152
+ keys: ['Arrow keys'],
153
+ action: 'Nudge the selected wall 1 in (1 cm in metric)',
154
+ note: 'In the floor plan. Stops where it lines up with a parallel wall; joined walls follow.',
155
+ },
156
+ {
157
+ keys: ['Tab'],
158
+ action: 'Select the next object under the pointer',
159
+ note: 'In the floor plan, for walls that overlap or hide under another. Shift+Tab goes back.',
160
+ },
116
161
  {
117
162
  keys: ['Esc'],
118
163
  action: 'Clear the selection',
@@ -121,7 +166,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
121
166
  ],
122
167
  },
123
168
  {
124
- title: 'Direct Manipulation',
169
+ title: /* i18n */ {
170
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.directManipulation',
171
+ message: 'Direct Manipulation',
172
+ },
125
173
  shortcuts: [
126
174
  {
127
175
  keys: ['Cmd/Ctrl', 'Left click'],
@@ -141,7 +189,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
141
189
  ],
142
190
  },
143
191
  {
144
- title: 'Drawing Tools',
192
+ title: /* i18n */ {
193
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.drawingTools',
194
+ message: 'Drawing Tools',
195
+ },
145
196
  shortcuts: [
146
197
  // Shift and Ctrl each mean one thing held and another tapped, and only
147
198
  // the hold was documented — which read as the taps not existing. Both
@@ -157,9 +208,9 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
157
208
  note: 'Tap and release on its own. Use it when the default half-metre grid is too coarse — placing a window, for instance.',
158
209
  },
159
210
  {
160
- keys: ['Shift'],
161
- action: 'Bypass guided snapping and angle constraints',
162
- note: 'Hold during the active gesture. Passive guide or measurement feedback may stay visible.',
211
+ keys: ['Alt'],
212
+ action: 'Bypass snapping, angle constraints and collisions',
213
+ note: 'Hold during the active gesture; release to snap again. Passive guide or measurement feedback may stay visible.',
163
214
  },
164
215
  {
165
216
  keys: ['Shift'],
@@ -169,7 +220,10 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
169
220
  ],
170
221
  },
171
222
  {
172
- title: 'Item Placement',
223
+ title: /* i18n */ {
224
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.itemPlacement',
225
+ message: 'Item Placement',
226
+ },
173
227
  shortcuts: [
174
228
  {
175
229
  keys: ['R', 'T'],
@@ -177,7 +231,8 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
177
231
  },
178
232
  {
179
233
  keys: ['E'],
180
- action: 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed',
234
+ action:
235
+ 'Operate the selected node — doors, windows, and cabinet doors/drawers animate open/closed',
181
236
  },
182
237
  {
183
238
  keys: ['Shift'],
@@ -187,12 +242,15 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
187
242
  ],
188
243
  },
189
244
  {
190
- title: 'Camera',
245
+ title: /* i18n */ {
246
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.camera',
247
+ message: 'Camera',
248
+ },
191
249
  shortcuts: [
192
250
  {
193
251
  keys: ['W', 'A', 'S', 'D'],
194
252
  action: 'Pan camera',
195
- note: 'Moves in screen space, similar to dragging the camera view.',
253
+ note: 'Moves in screen space, similar to dragging the camera view. With Shift, W, S and D place a window, stairs and a door instead.',
196
254
  },
197
255
  {
198
256
  keys: ['Middle click'],
@@ -208,7 +266,7 @@ const SHORTCUT_CATEGORIES: ShortcutCategory[] = [
208
266
  },
209
267
  ]
210
268
 
211
- function ShortcutKeys({ keys }: { keys: string[] }) {
269
+ function ShortcutKeys({ keys }: { keys: readonly string[] }) {
212
270
  return (
213
271
  <div className="flex flex-wrap items-center gap-1">
214
272
  {keys.map((key, index) => (
@@ -222,32 +280,44 @@ function ShortcutKeys({ keys }: { keys: string[] }) {
222
280
  }
223
281
 
224
282
  export function KeyboardShortcutsDialog() {
283
+ const i18n = usePascalI18n()
225
284
  return (
226
285
  <Dialog>
227
286
  <DialogTrigger asChild>
228
287
  <Button className="w-full justify-start gap-2" variant="outline">
229
288
  <Keyboard className="size-4" />
230
- Keyboard shortcuts
289
+ {i18n._({
290
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts',
291
+ message: 'Keyboard shortcuts',
292
+ })}
231
293
  </Button>
232
294
  </DialogTrigger>
233
295
  <DialogContent className="flex max-h-[85vh] flex-col overflow-hidden p-0 sm:max-w-3xl">
234
296
  <DialogHeader className="shrink-0 border-b px-6 py-4">
235
- <DialogTitle>Keyboard shortcuts</DialogTitle>
297
+ <DialogTitle>
298
+ {i18n._({
299
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.keyboardShortcuts',
300
+ message: 'Keyboard shortcuts',
301
+ })}
302
+ </DialogTitle>
236
303
  <DialogDescription>
237
- Shortcuts are context-aware. Guided constraints are enabled by default; hold Shift
238
- during an active gesture to build freely.
304
+ {i18n._({
305
+ id: 'editor.sidebar.panels.settingsPanel.keyboardShortcutsDialog.shortcutsAreContextAwareGuidedConstraints',
306
+ message:
307
+ 'Shortcuts are context-aware. Snapping is a visible mode: tap Shift to cycle it, hold Alt during a gesture to place freely.',
308
+ })}
239
309
  </DialogDescription>
240
310
  </DialogHeader>
241
311
 
242
312
  <div className="flex-1 space-y-5 overflow-y-auto px-6 py-4">
243
313
  {SHORTCUT_CATEGORIES.map((category) => (
244
- <section className="space-y-2" key={category.title}>
245
- <h3 className="font-medium text-sm">{category.title}</h3>
314
+ <section className="space-y-2" key={category.title.id}>
315
+ <h3 className="font-medium text-sm">{i18n._(category.title)}</h3>
246
316
  <div className="overflow-hidden rounded-md border border-border/80">
247
317
  {category.shortcuts.map((shortcut, index) => (
248
318
  <div
249
319
  className="grid grid-cols-[minmax(130px,220px)_1fr] gap-3 px-3 py-2"
250
- key={`${category.title}-${shortcut.action}`}
320
+ key={`${category.title.id}-${shortcut.action}`}
251
321
  >
252
322
  <ShortcutKeys keys={shortcut.keys} />
253
323
  <div>
@@ -23,11 +23,7 @@ import {
23
23
  DialogHeader,
24
24
  DialogTitle,
25
25
  } from '../../../../../components/ui/primitives/dialog'
26
- import {
27
- getAreaUnitLabel,
28
- type LinearUnit,
29
- squareMetersToAreaUnit,
30
- } from '../../../../../lib/measurements'
26
+ import { getAreaUnitLabel, type LinearUnit, squareMetersToAreaUnit } from '@pascal-app/core'
31
27
 
32
28
  export type PendingImport = {
33
29
  fileName: string
@@ -1,4 +1,4 @@
1
- import { type BuildingNode, useScene } from '@pascal-app/core'
1
+ import { type BuildingNode, useScene, usePascalI18n } from '@pascal-app/core'
2
2
  import { useViewer } from '@pascal-app/viewer'
3
3
  import { Building2, Plus } from 'lucide-react'
4
4
  import { memo, useState } from 'react'
@@ -23,6 +23,7 @@ export const BuildingTreeNode = memo(function BuildingTreeNode({
23
23
  depth,
24
24
  isLast,
25
25
  }: BuildingTreeNodeProps) {
26
+ const i18n = usePascalI18n()
26
27
  const [expanded, setExpanded] = useState(true)
27
28
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
28
29
  const name = useScene((s) => s.nodes[nodeId]?.name)
@@ -63,7 +64,7 @@ export const BuildingTreeNode = memo(function BuildingTreeNode({
63
64
  <Plus className="h-3 w-3" />
64
65
  </button>
65
66
  </TooltipTrigger>
66
- <TooltipContent side="right">Add new level</TooltipContent>
67
+ <TooltipContent side="right">{i18n._({ id: 'editor.sidebar.panels.sitePanel.buildingTreeNode.addNewLevel', message: 'Add new level' })}</TooltipContent>
67
68
  </Tooltip>
68
69
  </div>
69
70
  }
@@ -75,7 +76,7 @@ export const BuildingTreeNode = memo(function BuildingTreeNode({
75
76
  isLast={isLast}
76
77
  isSelected={isSelected}
77
78
  isVisible={isVisible}
78
- label={name || 'Building'}
79
+ label={name || i18n._({ id: 'editor.sidebar.panels.sitePanel.buildingTreeNode.building', message: 'Building' })}
79
80
  onClick={handleClick}
80
81
  onDoubleClick={() => focusTreeNode(nodeId)}
81
82
  onToggle={() => setExpanded(!expanded)}
@@ -1,13 +1,14 @@
1
- import { type AnyNodeId, type CeilingNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, type CeilingNode, useScene, usePascalI18n} from '@pascal-app/core'
2
2
  import { useViewer } from '@pascal-app/viewer'
3
3
  import Image from 'next/image'
4
4
  import { memo, useCallback, useEffect, useState } from 'react'
5
5
  import { useShallow } from 'zustand/react/shallow'
6
6
  import { startCeilingEditFromTreeSelection } from './../../../../../lib/ceiling-edit-session'
7
- import { formatAreaLabel } from './../../../../../lib/measurements'
7
+ import { formatAreaLabel } from '@pascal-app/core'
8
8
  import { InlineRenameInput } from './inline-rename-input'
9
9
  import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
10
10
  import { TreeNodeActions } from './tree-node-actions'
11
+ import { ceilingDefaultName } from './default-names'
11
12
 
12
13
  interface CeilingTreeNodeProps {
13
14
  nodeId: AnyNodeId
@@ -20,6 +21,7 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
20
21
  depth,
21
22
  isLast,
22
23
  }: CeilingTreeNodeProps) {
24
+ const i18n = usePascalI18n()
23
25
  const [expanded, setExpanded] = useState(false)
24
26
  const [isEditing, setIsEditing] = useState(false)
25
27
  const isVisible = useScene((s) => s.nodes[nodeId as AnyNodeId]?.visible !== false)
@@ -77,7 +79,7 @@ export const CeilingTreeNode = memo(function CeilingTreeNode({
77
79
  const handleStartEditing = useCallback(() => setIsEditing(true), [])
78
80
  const handleStopEditing = useCallback(() => setIsEditing(false), [])
79
81
 
80
- const defaultName = `Ceiling (${formatAreaLabel(calculatePolygonArea(polygon), unit)})`
82
+ const defaultName = ceilingDefaultName(i18n, formatAreaLabel(calculatePolygonArea(polygon), unit))
81
83
 
82
84
  return (
83
85
  <TreeNodeWrapper
@@ -1,4 +1,4 @@
1
- import { type AnyNodeId, type ChimneyNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, type ChimneyNode, useScene, usePascalI18n } 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'
@@ -6,6 +6,7 @@ import { SnapTargetIcon } from '../../../snap-target-badge'
6
6
  import { InlineRenameInput } from './inline-rename-input'
7
7
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
8
8
  import { TreeNodeActions } from './tree-node-actions'
9
+ import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
9
10
 
10
11
  interface ChimneyTreeNodeProps {
11
12
  nodeId: AnyNodeId
@@ -18,6 +19,7 @@ export const ChimneyTreeNode = memo(function ChimneyTreeNode({
18
19
  depth,
19
20
  isLast,
20
21
  }: ChimneyTreeNodeProps) {
22
+ const i18n = usePascalI18n()
21
23
  const [isEditing, setIsEditing] = useState(false)
22
24
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
23
25
  const node = useScene((s) => s.nodes[nodeId] as ChimneyNode | undefined)
@@ -39,7 +41,7 @@ export const ChimneyTreeNode = memo(function ChimneyTreeNode({
39
41
  [nodeId, setSelection],
40
42
  )
41
43
 
42
- const defaultName = node?.name || 'Chimney'
44
+ const defaultName = node?.name || i18n._(nodeLabelDescriptor('chimney', 'Chimney'))
43
45
 
44
46
  return (
45
47
  <TreeNodeWrapper
@@ -6,7 +6,7 @@ import {
6
6
  findLevelAncestorId,
7
7
  type LevelNode,
8
8
  resolveBuildingForLevel,
9
- useScene,
9
+ useScene, usePascalI18n,
10
10
  } from '@pascal-app/core'
11
11
  import { useViewer } from '@pascal-app/viewer'
12
12
  import { Layers, Trash2 } from 'lucide-react'
@@ -44,6 +44,7 @@ function useLiveMembers(collectionId: CollectionId) {
44
44
 
45
45
  /** Collections, listed under the level tree. Absent when the scene has none. */
46
46
  export const CollectionsSection = memo(function CollectionsSection() {
47
+ const i18n = usePascalI18n()
47
48
  const collectionIds = useScene(useShallow((s) => Object.keys(s.collections) as CollectionId[]))
48
49
  const [expanded, setExpanded] = useState(true)
49
50
 
@@ -58,7 +59,7 @@ export const CollectionsSection = memo(function CollectionsSection() {
58
59
  icon={<Layers className="h-3.5 w-3.5" />}
59
60
  label={
60
61
  <span className="flex items-center gap-1.5">
61
- Collections
62
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.collectionTreeNode.collections', message: 'Collections' })}
62
63
  <span className="text-muted-foreground text-xs">{collectionIds.length}</span>
63
64
  </span>
64
65
  }
@@ -90,6 +91,7 @@ const CollectionTreeNode = memo(function CollectionTreeNode({
90
91
  depth,
91
92
  isLast,
92
93
  }: CollectionTreeNodeProps) {
94
+ const i18n = usePascalI18n()
93
95
  const [isEditing, setIsEditing] = useState(false)
94
96
  const [expanded, setExpanded] = useState(false)
95
97
  const name = useScene((s) => s.collections[collectionId]?.name)
@@ -147,7 +149,7 @@ const CollectionTreeNode = memo(function CollectionTreeNode({
147
149
  event.stopPropagation()
148
150
  deleteCollection(collectionId)
149
151
  }}
150
- title="Delete collection"
152
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.collectionTreeNode.deleteCollection', message: 'Delete collection' })}
151
153
  type="button"
152
154
  >
153
155
  <Trash2 className="h-3 w-3" />
@@ -159,7 +161,7 @@ const CollectionTreeNode = memo(function CollectionTreeNode({
159
161
  icon={
160
162
  <ColorDot
161
163
  color={color ?? '#6366f1'}
162
- label="Collection colour"
164
+ label={i18n._({ id: 'editor.sidebar.panels.sitePanel.collectionTreeNode.collectionColour', message: 'Collection colour' })}
163
165
  onChange={(next) => updateCollection(collectionId, { color: next })}
164
166
  />
165
167
  }
@@ -169,7 +171,7 @@ const CollectionTreeNode = memo(function CollectionTreeNode({
169
171
  label={
170
172
  <span className="flex min-w-0 items-center gap-1.5">
171
173
  <InlineRenameField
172
- defaultName="Collection"
174
+ defaultName={i18n._({ id: 'editor.layers.defaultName.collection', message: 'Collection' })}
173
175
  isEditing={isEditing}
174
176
  name={name}
175
177
  onRename={handleRename}
@@ -1,10 +1,11 @@
1
- import { type AnyNodeId, type ColumnNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, type ColumnNode, useScene, usePascalI18n } 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 { InlineRenameInput } from './inline-rename-input'
6
6
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
7
7
  import { TreeNodeActions } from './tree-node-actions'
8
+ import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
8
9
 
9
10
  interface ColumnTreeNodeProps {
10
11
  nodeId: AnyNodeId
@@ -17,6 +18,7 @@ export const ColumnTreeNode = memo(function ColumnTreeNode({
17
18
  depth,
18
19
  isLast,
19
20
  }: ColumnTreeNodeProps) {
21
+ const i18n = usePascalI18n()
20
22
  const [isEditing, setIsEditing] = useState(false)
21
23
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
22
24
  const node = useScene((s) => s.nodes[nodeId] as ColumnNode | undefined)
@@ -38,7 +40,7 @@ export const ColumnTreeNode = memo(function ColumnTreeNode({
38
40
  [nodeId, setSelection],
39
41
  )
40
42
 
41
- const defaultName = node?.name || 'Column'
43
+ const defaultName = node?.name || i18n._(nodeLabelDescriptor('column', 'Column'))
42
44
 
43
45
  return (
44
46
  <TreeNodeWrapper
@@ -0,0 +1,75 @@
1
+ import type { PascalI18n, RoofType, StairSegmentNode, ZoneNode } from '@pascal-app/core'
2
+
3
+ /** Names a layer row shows (and its drag preview) until the user renames it. */
4
+
5
+ export function zoneDefaultName(
6
+ i18n: PascalI18n,
7
+ zone: { spaceRole?: ZoneNode['spaceRole'] } | undefined,
8
+ area: string,
9
+ ): string {
10
+ return zone?.spaceRole === 'room'
11
+ ? i18n._({ id: 'editor.layers.defaultName.room', message: 'Room ({area})', values: { area } })
12
+ : i18n._({ id: 'editor.layers.defaultName.zone', message: 'Zone ({area})', values: { area } })
13
+ }
14
+
15
+ export function slabDefaultName(i18n: PascalI18n, area: string): string {
16
+ return i18n._({ id: 'editor.layers.defaultName.slab', message: 'Slab ({area})', values: { area } })
17
+ }
18
+
19
+ export function ceilingDefaultName(i18n: PascalI18n, area: string): string {
20
+ return i18n._({
21
+ id: 'editor.layers.defaultName.ceiling',
22
+ message: 'Ceiling ({area})',
23
+ values: { area },
24
+ })
25
+ }
26
+
27
+ export function roofDefaultName(i18n: PascalI18n, count: number): string {
28
+ return i18n._({
29
+ id: 'editor.layers.defaultName.roof',
30
+ message: 'Roof ({count, plural, one {# segment} other {# segments}})',
31
+ values: { count },
32
+ })
33
+ }
34
+
35
+ export function staircaseDefaultName(i18n: PascalI18n, count: number): string {
36
+ return i18n._({
37
+ id: 'editor.layers.defaultName.staircase',
38
+ message: 'Staircase ({count, plural, one {# segment} other {# segments}})',
39
+ values: { count },
40
+ })
41
+ }
42
+
43
+ export function roofSegmentDefaultName(
44
+ i18n: PascalI18n,
45
+ roofType: RoofType,
46
+ width: number,
47
+ depth: number,
48
+ ): string {
49
+ return i18n._({
50
+ id: 'editor.layers.defaultName.roofSegment',
51
+ message:
52
+ '{roofType, select, hip {Hip} gable {Gable} shed {Shed} gambrel {Gambrel} dutch {Dutch} mansard {Mansard} flat {Flat} conical {Conical} barrel {Barrel} other {Roof}} ({width}×{depth}m)',
53
+ values: { roofType, width: width.toFixed(1), depth: depth.toFixed(1) },
54
+ })
55
+ }
56
+
57
+ export function stairSegmentDefaultName(
58
+ i18n: PascalI18n,
59
+ segmentType: StairSegmentNode['segmentType'],
60
+ width: number,
61
+ length: number,
62
+ ): string {
63
+ const values = { width: width.toFixed(1), length: length.toFixed(1) }
64
+ return segmentType === 'stair'
65
+ ? i18n._({
66
+ id: 'editor.layers.defaultName.flight',
67
+ message: 'Flight ({width}×{length}m)',
68
+ values,
69
+ })
70
+ : i18n._({
71
+ id: 'editor.layers.defaultName.landing',
72
+ message: 'Landing ({width}×{length}m)',
73
+ values,
74
+ })
75
+ }
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { type AnyNodeId, type DoorNode, useScene } from '@pascal-app/core'
3
+ import { type AnyNodeId, type DoorNode, useScene, usePascalI18n } 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'
@@ -8,6 +8,7 @@ import { resolveNodeSnapTarget, SnapTargetIcon } from '../../../snap-target-badg
8
8
  import { InlineRenameInput } from './inline-rename-input'
9
9
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
10
10
  import { TreeNodeActions } from './tree-node-actions'
11
+ import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
11
12
 
12
13
  interface DoorTreeNodeProps {
13
14
  nodeId: AnyNodeId
@@ -20,6 +21,7 @@ export const DoorTreeNode = memo(function DoorTreeNode({
20
21
  depth,
21
22
  isLast,
22
23
  }: DoorTreeNodeProps) {
24
+ const i18n = usePascalI18n()
23
25
  const [isEditing, setIsEditing] = useState(false)
24
26
  const isVisible = useScene((s) => s.nodes[nodeId as AnyNodeId]?.visible !== false)
25
27
  const node = useScene((s) => s.nodes[nodeId] as DoorNode | undefined)
@@ -62,7 +64,7 @@ export const DoorTreeNode = memo(function DoorTreeNode({
62
64
  isVisible={isVisible}
63
65
  label={
64
66
  <InlineRenameInput
65
- defaultName="Door"
67
+ defaultName={i18n._(nodeLabelDescriptor('door', 'Door'))}
66
68
  isEditing={isEditing}
67
69
  nodeId={nodeId as AnyNodeId}
68
70
  onStartEditing={handleStartEditing}
@@ -1,4 +1,4 @@
1
- import { type AnyNodeId, type DormerNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, type DormerNode, useScene, usePascalI18n } 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'
@@ -7,6 +7,7 @@ import { SnapTargetIcon } from '../../../snap-target-badge'
7
7
  import { InlineRenameInput } from './inline-rename-input'
8
8
  import { focusTreeNode, handleTreeSelection, TreeNode, TreeNodeWrapper } from './tree-node'
9
9
  import { TreeNodeActions } from './tree-node-actions'
10
+ import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
10
11
 
11
12
  interface DormerTreeNodeProps {
12
13
  nodeId: AnyNodeId
@@ -25,6 +26,7 @@ export const DormerTreeNode = memo(function DormerTreeNode({
25
26
  depth,
26
27
  isLast,
27
28
  }: DormerTreeNodeProps) {
29
+ const i18n = usePascalI18n()
28
30
  const [isEditing, setIsEditing] = useState(false)
29
31
  const [expanded, setExpanded] = useState(true)
30
32
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
@@ -50,7 +52,7 @@ export const DormerTreeNode = memo(function DormerTreeNode({
50
52
  [nodeId, setSelection],
51
53
  )
52
54
 
53
- const defaultName = node?.name || 'Dormer'
55
+ const defaultName = node?.name || i18n._(nodeLabelDescriptor('dormer', 'Dormer'))
54
56
 
55
57
  return (
56
58
  <TreeNodeWrapper
@@ -1,10 +1,11 @@
1
- import { type AnyNodeId, type ElevatorNode, useScene } from '@pascal-app/core'
1
+ import { type AnyNodeId, type ElevatorNode, useScene, usePascalI18n } 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 { InlineRenameInput } from './inline-rename-input'
6
6
  import { focusTreeNode, handleTreeSelection, TreeNodeWrapper } from './tree-node'
7
7
  import { TreeNodeActions } from './tree-node-actions'
8
+ import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
8
9
 
9
10
  interface ElevatorTreeNodeProps {
10
11
  nodeId: AnyNodeId
@@ -17,6 +18,7 @@ export const ElevatorTreeNode = memo(function ElevatorTreeNode({
17
18
  depth,
18
19
  isLast,
19
20
  }: ElevatorTreeNodeProps) {
21
+ const i18n = usePascalI18n()
20
22
  const [isEditing, setIsEditing] = useState(false)
21
23
  const isVisible = useScene((s) => s.nodes[nodeId]?.visible !== false)
22
24
  const node = useScene((s) => s.nodes[nodeId] as ElevatorNode | undefined)
@@ -53,7 +55,7 @@ export const ElevatorTreeNode = memo(function ElevatorTreeNode({
53
55
  isVisible={isVisible}
54
56
  label={
55
57
  <InlineRenameInput
56
- defaultName={node?.name || 'Elevator'}
58
+ defaultName={node?.name || i18n._(nodeLabelDescriptor('elevator', 'Elevator'))}
57
59
  isEditing={isEditing}
58
60
  nodeId={nodeId}
59
61
  onStartEditing={() => setIsEditing(true)}