@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,13 +1,14 @@
1
1
  'use client'
2
2
 
3
+ import { usePascalI18n } from '@pascal-app/core'
3
4
  import { Pentagon, Square } from 'lucide-react'
4
5
  import { cn } from '../../../lib/utils'
5
6
 
6
7
  export type OutlineShape = 'rectangle' | 'polygon'
7
8
 
8
9
  const SHAPES = [
9
- { id: 'rectangle', label: 'Rectangle', Icon: Square },
10
- { id: 'polygon', label: 'Polygon', Icon: Pentagon },
10
+ { id: 'rectangle', label: /* i18n */ { id: 'editor.panels.shapeChoice.rectangle', message: 'Rectangle' }, Icon: Square },
11
+ { id: 'polygon', label: /* i18n */ { id: 'editor.panels.shapeChoice.polygon', message: 'Polygon' }, Icon: Pentagon },
11
12
  ] as const
12
13
 
13
14
  const pill =
@@ -32,13 +33,14 @@ export function ShapeChoice({
32
33
  /** `data-shape-choice` hook for tests and proofs. */
33
34
  data?: string
34
35
  }) {
36
+ const i18n = usePascalI18n()
35
37
  return (
36
38
  <div
37
- className={cn('flex min-h-7 items-center justify-between gap-2', className)}
39
+ className={cn('flex min-h-7 flex-wrap items-center justify-between gap-2', className)}
38
40
  data-shape-choice={data}
39
41
  >
40
42
  <span className="whitespace-nowrap">{label}</span>
41
- <div className="flex items-center gap-1.5">
43
+ <div className="flex flex-wrap items-center gap-1.5">
42
44
  {SHAPES.map(({ id, label: shapeLabel, Icon }) => (
43
45
  <button
44
46
  className={pill}
@@ -49,7 +51,7 @@ export function ShapeChoice({
49
51
  type="button"
50
52
  >
51
53
  <Icon aria-hidden className="h-3 w-3" />
52
- {shapeLabel}
54
+ {i18n._(shapeLabel)}
53
55
  </button>
54
56
  ))}
55
57
  </div>
@@ -4,7 +4,7 @@ import {
4
4
  type AnyNodeId,
5
5
  useLiveNodeOverrides,
6
6
  useScene,
7
- type WallFaceRegion,
7
+ type WallFaceRegion, usePascalI18n,
8
8
  } from '@pascal-app/core'
9
9
  import { useViewer } from '@pascal-app/viewer'
10
10
  import { Trash2 } from 'lucide-react'
@@ -25,6 +25,7 @@ const ROW_ACTION_CLASS =
25
25
 
26
26
  /** The wall panel's "Paint regions" section: one row per region, select or delete. */
27
27
  export function WallPaintRegionList({ wallId }: { wallId: string }) {
28
+ const i18n = usePascalI18n()
28
29
  const nodes = useScene((state) => state.nodes)
29
30
  const materials = useScene((state) => state.materials)
30
31
  const unit = useViewer((state) => state.unit)
@@ -48,7 +49,7 @@ export function WallPaintRegionList({ wallId }: { wallId: string }) {
48
49
  return (
49
50
  // Focus inside this list still lets Delete target the active region.
50
51
  <div className="contents" data-wall-region-list={wallId}>
51
- <PanelSection title="Paint regions">
52
+ <PanelSection title={i18n._({ id: 'editor.panels.wallRegionList.paintRegions', message: 'Paint regions' })}>
52
53
  <div className="flex flex-col gap-1">
53
54
  {rows.map((row) => {
54
55
  const isActive = active?.wallId === wallId && active.regionId === row.id
@@ -75,6 +76,7 @@ export function WallPaintRegionList({ wallId }: { wallId: string }) {
75
76
  backgroundImage: row.finish.imageUrl
76
77
  ? `url(${row.finish.imageUrl})`
77
78
  : undefined,
79
+ backgroundBlendMode: row.finish.tinted ? 'multiply' : undefined,
78
80
  }}
79
81
  />
80
82
  <span className="flex min-w-0 flex-col leading-tight">
@@ -85,13 +87,13 @@ export function WallPaintRegionList({ wallId }: { wallId: string }) {
85
87
  </span>
86
88
  </button>
87
89
  <button
88
- aria-label="Delete region"
90
+ aria-label={i18n._({ id: 'editor.panels.wallRegionList.deleteRegion', message: 'Delete region' })}
89
91
  className={ROW_ACTION_CLASS}
90
92
  onClick={() => {
91
93
  if (isActive) setActive(null)
92
94
  if (removeWallRegion(wallId, row.id)) sfxEmitter.emit('sfx:structure-delete')
93
95
  }}
94
- title="Delete region"
96
+ title={i18n._({ id: 'editor.panels.wallRegionList.deleteRegion', message: 'Delete region' })}
95
97
  type="button"
96
98
  >
97
99
  <Trash2 className="h-3 w-3" />
@@ -99,9 +101,7 @@ export function WallPaintRegionList({ wallId }: { wallId: string }) {
99
101
  </div>
100
102
  )
101
103
  })}
102
- {capNotice && (
103
- <div className="px-1 text-[11px] text-muted-foreground">{capNotice}</div>
104
- )}
104
+ {capNotice && <div className="px-1 text-[11px] text-muted-foreground">{capNotice}</div>}
105
105
  </div>
106
106
  </PanelSection>
107
107
  </div>
@@ -1,6 +1,8 @@
1
1
  import { Icon } from '@iconify/react'
2
+ import { usePascalI18n } from '@pascal-app/core'
2
3
  import type * as React from 'react'
3
4
 
5
+ import { toolHintKey } from '../../../i18n/node-messages'
4
6
  import { cn } from '../../../lib/utils'
5
7
 
6
8
  const MOUSE_SHORTCUTS = {
@@ -70,20 +72,22 @@ function ShortcutToken({ className, displayValue, value, ...props }: ShortcutTok
70
72
  const isCommand = COMMAND_VALUES.has(value)
71
73
  const isShift = value === 'Shift'
72
74
  const commandDisplay = IS_MAC ? '⌘' : 'Ctrl'
73
- const commandLabel = IS_MAC ? 'Command' : 'Control'
75
+ const i18n = usePascalI18n()
76
+ const commandLabel = toolHintKey(IS_MAC ? 'Command' : 'Control', i18n)
77
+ const mouseLabel = mouseShortcut ? toolHintKey(mouseShortcut.label, i18n) : undefined
78
+ const shown = displayValue ?? toolHintKey(value, i18n)
74
79
 
75
80
  return (
76
81
  <kbd
77
82
  aria-label={
78
- mouseShortcut?.label ??
79
- (isCommand ? commandLabel : isShift ? 'Shift' : (displayValue ?? value))
83
+ mouseLabel ?? (isCommand ? commandLabel : isShift ? 'Shift' : shown)
80
84
  }
81
85
  className={cn(
82
86
  'inline-flex h-6 items-center rounded border border-border bg-muted px-2 font-medium font-mono text-[11px] text-muted-foreground',
83
87
  (mouseShortcut || isShift) && 'justify-center px-1.5',
84
88
  className,
85
89
  )}
86
- title={mouseShortcut?.label ?? (isCommand ? commandLabel : isShift ? 'Shift' : value)}
90
+ title={mouseLabel ?? (isCommand ? commandLabel : isShift ? 'Shift' : shown)}
87
91
  {...props}
88
92
  >
89
93
  {mouseShortcut ? (
@@ -96,7 +100,7 @@ function ShortcutToken({ className, displayValue, value, ...props }: ShortcutTok
96
100
  icon={mouseShortcut.icon}
97
101
  width={14}
98
102
  />
99
- <span className="sr-only">{mouseShortcut.label}</span>
103
+ <span className="sr-only">{mouseLabel}</span>
100
104
  </>
101
105
  ) : isShift ? (
102
106
  // Icon rather than the ⇧ text glyph — the font renders the glyph's
@@ -117,7 +121,7 @@ function ShortcutToken({ className, displayValue, value, ...props }: ShortcutTok
117
121
  // it up a touch on Mac. "Ctrl" stays at the token's normal size.
118
122
  <span className={IS_MAC ? 'text-[13px] leading-none' : undefined}>{commandDisplay}</span>
119
123
  ) : (
120
- (displayValue ?? value)
124
+ shown
121
125
  )}
122
126
  </kbd>
123
127
  )
@@ -14,16 +14,23 @@ test('the inspector keeps 180 px, or its own height if shorter, above the full c
14
14
  ).toEqual({ inspector: 0, card: 240 })
15
15
  })
16
16
 
17
- test('on a short window the inspector gives way first, down to its header', () => {
17
+ test('on a short window the help card scrolls to preserve editing space', () => {
18
18
  expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 400 })).toEqual({
19
- inspector: 152,
20
- card: 240,
19
+ inspector: 180,
20
+ card: 212,
21
21
  })
22
22
  })
23
23
 
24
- test('on a very short window the inspector keeps its header and the card scrolls', () => {
24
+ test('expanded hints cannot squeeze the inspector down to just its header', () => {
25
25
  expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 258 })).toEqual({
26
- inspector: 52,
27
- card: 198,
26
+ inspector: 180,
27
+ card: 70,
28
+ })
29
+ })
30
+
31
+ test('a column shorter than the inspector minimum still bounds both cards', () => {
32
+ expect(rightStackFloors({ ...tall, naturalHeight: 500, stackHeight: 100 })).toEqual({
33
+ inspector: 92,
34
+ card: 0,
28
35
  })
29
36
  })
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { createContext, type ReactNode, useContext, useEffect, useRef } from 'react'
3
+ import { createContext, type ReactNode, useContext, useLayoutEffect, useRef } from 'react'
4
4
  import { useIsMobile } from '../../hooks/use-mobile'
5
5
  import { cn } from '../../lib/utils'
6
6
  import { TOP_MENU_CLEARANCE } from './action-menu/placement'
@@ -20,9 +20,8 @@ const GAP = 8
20
20
 
21
21
  /**
22
22
  * The heights neither panel shrinks below. The inspector keeps 180 px (or its
23
- * own height, if shorter) while the full card fits under it, and never less
24
- * than its header row. The card shows in full whenever the column allows; on a
25
- * very short window it gives way and scrolls on itself — both stay usable.
23
+ * own height, if shorter) and never less than its header row when the column allows.
24
+ * The card uses the remaining room and scrolls on itself on a short window — both stay usable.
26
25
  */
27
26
  export function rightStackFloors(args: {
28
27
  naturalHeight: number
@@ -33,9 +32,10 @@ export function rightStackFloors(args: {
33
32
  const { naturalHeight, headerHeight, cardHeight, stackHeight } = args
34
33
  const gap = naturalHeight > 0 && cardHeight > 0 ? GAP : 0
35
34
  const header = Math.min(headerHeight, naturalHeight)
36
- const inspector = Math.max(
37
- header,
38
- Math.min(INSPECTOR_MIN_HEIGHT, naturalHeight, stackHeight - cardHeight - gap),
35
+ const inspector = Math.min(
36
+ naturalHeight,
37
+ stackHeight,
38
+ Math.max(header, Math.min(INSPECTOR_MIN_HEIGHT, stackHeight - gap)),
39
39
  )
40
40
  const card = Math.max(0, Math.min(cardHeight, stackHeight - gap - inspector))
41
41
  return { inspector, card }
@@ -78,7 +78,7 @@ export function RightStack({
78
78
 
79
79
  // `min(180px, its own height)` has no CSS form for a flex item that scrolls,
80
80
  // so both floors are measured (`rightStackFloors`).
81
- useEffect(() => {
81
+ useLayoutEffect(() => {
82
82
  if (isMobile) return
83
83
  const stack = stackRef.current
84
84
  const slot = inspectorRef.current
@@ -88,18 +88,29 @@ export function RightStack({
88
88
  const fit = () => {
89
89
  frame = 0
90
90
  const { natural, header } = inspectorHeights(slot)
91
+ const cardHeight = Array.from(card.children).reduce(
92
+ (height, child) => height + child.scrollHeight + (child instanceof HTMLElement ? child.offsetHeight - child.clientHeight : 0),
93
+ 0,
94
+ )
91
95
  const floors = rightStackFloors({
92
96
  naturalHeight: natural,
93
97
  headerHeight: header,
94
- cardHeight: card.scrollHeight,
98
+ cardHeight,
95
99
  stackHeight: stack.clientHeight,
96
100
  })
97
101
  const inspectorMin = `${floors.inspector}px`
98
102
  const cardMin = `${floors.card}px`
103
+ // Custom panels may wrap PanelWrapper in a non-flex element. Give the
104
+ // card an explicit inherited limit so those wrappers cannot bypass it.
105
+ const available = Math.max(0, stack.clientHeight - floors.card - (natural > 0 && floors.card > 0 ? GAP : 0))
106
+ const limit = `${available}px`
107
+ if (slot.style.getPropertyValue('--inspector-available-height') !== limit) {
108
+ slot.style.setProperty('--inspector-available-height', limit)
109
+ }
99
110
  if (slot.style.minHeight !== inspectorMin) slot.style.minHeight = inspectorMin
100
111
  if (card.style.minHeight !== cardMin) card.style.minHeight = cardMin
101
112
  // The card is click-through; it takes the pointer only when it has to scroll.
102
- const pointer = floors.card < card.scrollHeight ? 'auto' : ''
113
+ const pointer = floors.card < cardHeight ? 'auto' : ''
103
114
  if (card.style.pointerEvents !== pointer) card.style.pointerEvents = pointer
104
115
  }
105
116
  const schedule = () => {
@@ -1,5 +1,6 @@
1
1
  'use client'
2
2
 
3
+ import { type MessageDescriptor, usePascalI18n } from '@pascal-app/core'
3
4
  import { Plus } from 'lucide-react'
4
5
  import type { ComponentType, ReactNode } from 'react'
5
6
  import {
@@ -32,19 +33,19 @@ interface IconRailProps {
32
33
  className?: string
33
34
  }
34
35
 
35
- const sitePanel: { id: PanelId; iconSrc: string; label: string } = {
36
+ const sitePanel: { id: PanelId; iconSrc: string; label: MessageDescriptor } = {
36
37
  id: 'site',
37
38
  iconSrc: '/icons/level.webp',
38
- label: 'Site',
39
+ label: /* i18n */ { id: 'editor.sidebar.iconRail.site', message: 'Site' },
39
40
  }
40
41
 
41
- const settingsPanel: { id: PanelId; iconSrc: string; label: string } = {
42
+ const settingsPanel: { id: PanelId; iconSrc: string; label: MessageDescriptor } = {
42
43
  id: 'settings',
43
44
  iconSrc: '/icons/settings.webp',
44
- label: 'Settings',
45
+ label: /* i18n */ { id: 'editor.sidebar.iconRail.settings', message: 'Settings' },
45
46
  }
46
47
 
47
- const panels: { id: PanelId; iconSrc: string; label: string }[] = [sitePanel, settingsPanel]
48
+ const panels: { id: PanelId; iconSrc: string; label: MessageDescriptor }[] = [sitePanel, settingsPanel]
48
49
 
49
50
  export function IconRail({
50
51
  activePanel,
@@ -53,6 +54,7 @@ export function IconRail({
53
54
  extraPanels,
54
55
  className,
55
56
  }: IconRailProps) {
57
+ const i18n = usePascalI18n()
56
58
  const regularExtraPanels = extraPanels?.filter((panel) => !panel.pluginId && panel.id !== 'plugins')
57
59
  const pluginPanels = extraPanels?.filter((panel) => panel.pluginId)
58
60
  const pluginsPanel = extraPanels?.find((panel) => panel.id === 'plugins')
@@ -113,7 +115,7 @@ export function IconRail({
113
115
  type="button"
114
116
  >
115
117
  <img
116
- alt={panel.label}
118
+ alt={i18n._(panel.label)}
117
119
  className={cn(
118
120
  'h-6 w-6 object-contain transition-all',
119
121
  !isActive && 'opacity-50 saturate-0',
@@ -122,7 +124,7 @@ export function IconRail({
122
124
  />
123
125
  </button>
124
126
  </TooltipTrigger>
125
- <TooltipContent side="right">{panel.label}</TooltipContent>
127
+ <TooltipContent side="right">{i18n._(panel.label)}</TooltipContent>
126
128
  </Tooltip>
127
129
  )
128
130
  })}
@@ -144,7 +146,7 @@ export function IconRail({
144
146
  type="button"
145
147
  >
146
148
  <img
147
- alt={panel.label}
149
+ alt={i18n._(panel.label)}
148
150
  className={cn(
149
151
  'h-6 w-6 object-contain transition-all',
150
152
  !isActive && 'opacity-50 saturate-0',
@@ -153,7 +155,7 @@ export function IconRail({
153
155
  />
154
156
  </button>
155
157
  </TooltipTrigger>
156
- <TooltipContent side="right">{panel.label}</TooltipContent>
158
+ <TooltipContent side="right">{i18n._(panel.label)}</TooltipContent>
157
159
  </Tooltip>
158
160
  )
159
161
  })}
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import type { AssetInput } from '@pascal-app/core'
3
+ import { type AssetInput, type MessageDescriptor, usePascalI18n } from '@pascal-app/core'
4
4
  import { Root as TooltipRoot } from '@radix-ui/react-tooltip'
5
5
  import NextImage from 'next/image'
6
6
  import { useMemo, useState } from 'react'
@@ -21,10 +21,10 @@ export type FunctionTreeNode = {
21
21
  children: FunctionTreeNode[]
22
22
  }
23
23
 
24
- const SOURCE_CHIPS: Array<{ id: NonNullable<AssetInput['source']>; label: string }> = [
25
- { id: 'library', label: 'Library' },
26
- { id: 'community', label: 'Community' },
27
- { id: 'mine', label: 'Mine' },
24
+ const SOURCE_CHIPS: Array<{ id: NonNullable<AssetInput['source']>; label: MessageDescriptor }> = [
25
+ { id: 'library', label: /* i18n */ { id: 'editor.sidebar.panels.itemsPanel.functionTreePanel.library', message: 'Library' } },
26
+ { id: 'community', label: /* i18n */ { id: 'editor.sidebar.panels.itemsPanel.functionTreePanel.community', message: 'Community' } },
27
+ { id: 'mine', label: /* i18n */ { id: 'editor.sidebar.panels.itemsPanel.functionTreePanel.mine', message: 'Mine' } },
28
28
  ]
29
29
 
30
30
  /** Every slug at or below `node`, so a non-leaf selection matches descendants. */
@@ -64,6 +64,7 @@ export function FunctionTreePanel({
64
64
  leadingTile?: React.ReactNode
65
65
  emptyState?: React.ReactNode
66
66
  }) {
67
+ const i18n = usePascalI18n()
67
68
  const [activeRootSlug, setActiveRootSlug] = useState<string | null>(
68
69
  functionTree[0]?.slug ?? null,
69
70
  )
@@ -178,7 +179,7 @@ export function FunctionTreePanel({
178
179
  setSearch(e.target.value)
179
180
  onSearchChange?.(e.target.value)
180
181
  }}
181
- placeholder="Search..."
182
+ placeholder={i18n._({ id: 'editor.sidebar.panels.itemsPanel.functionTreePanel.search', message: 'Search...' })}
182
183
  type="text"
183
184
  value={search}
184
185
  />
@@ -197,7 +198,7 @@ export function FunctionTreePanel({
197
198
  onClick={() => setActiveSource(isActive ? null : chip.id)}
198
199
  type="button"
199
200
  >
200
- {chip.label}
201
+ {i18n._(chip.label)}
201
202
  </button>
202
203
  )
203
204
  })}
@@ -217,7 +218,7 @@ export function FunctionTreePanel({
217
218
  onClick={() => setActiveChildSlug(null)}
218
219
  type="button"
219
220
  >
220
- All
221
+ {i18n._({ id: 'editor.sidebar.panels.itemsPanel.functionTreePanel.all', message: 'All' })}
221
222
  </button>
222
223
  {activeRoot.children.map((child) => {
223
224
  const isActive = activeChildSlug === child.slug
@@ -250,8 +251,7 @@ export function FunctionTreePanel({
250
251
  ) : isServerSearch && search && searchResults?.length === 0 ? (
251
252
  (emptyState ?? (
252
253
  <div className="flex h-full items-center justify-center text-muted-foreground text-xs">
253
- No results for &ldquo;{search}&rdquo;
254
- </div>
254
+ {i18n._({ id: 'editor.sidebar.panels.itemsPanel.functionTreePanel.noResultsForSearch', message: 'No results for “{search}”', values: { search } })}</div>
255
255
  ))
256
256
  ) : (
257
257
  <ItemCatalog
@@ -1,5 +1,6 @@
1
1
  'use client'
2
2
 
3
+ import { usePascalI18n } from '@pascal-app/core'
3
4
  import type { AssetInput } from '@pascal-app/core'
4
5
  import NextImage from 'next/image'
5
6
  import { useEffect, useState } from 'react'
@@ -98,6 +99,7 @@ function LegacyItemsPanel({
98
99
  showSourceFilter?: boolean
99
100
  showTagFilters?: boolean
100
101
  }) {
102
+ const i18n = usePascalI18n()
101
103
  const mode = useEditor((s) => s.mode)
102
104
  const catalogCategory = useEditor((s) => s.catalogCategory)
103
105
  const setMode = useEditor((s) => s.setMode)
@@ -167,9 +169,9 @@ function LegacyItemsPanel({
167
169
  // filter even before they own any items. Selecting "Mine" with no
168
170
  // matching items falls through to the empty/no-results state.
169
171
  const sourceChips: Array<{ id: AssetInput['source']; label: string }> = [
170
- { id: 'library', label: 'Library' },
171
- { id: 'community', label: 'Community' },
172
- { id: 'mine', label: 'Mine' },
172
+ { id: 'library', label: i18n._({ id: 'editor.sidebar.panels.itemsPanel.library', message: 'Library' }) },
173
+ { id: 'community', label: i18n._({ id: 'editor.sidebar.panels.itemsPanel.community', message: 'Community' }) },
174
+ { id: 'mine', label: i18n._({ id: 'editor.sidebar.panels.itemsPanel.mine', message: 'Mine' }) },
173
175
  ]
174
176
  const allTags = Array.from(new Set(categoryItems.flatMap((item) => item.tags ?? [])))
175
177
  const placementTags = allTags.filter((t) => PLACEMENT_TAGS.has(t))
@@ -215,13 +217,13 @@ function LegacyItemsPanel({
215
217
  type="button"
216
218
  >
217
219
  <NextImage
218
- alt={cat.label}
220
+ alt={i18n._(cat.label)}
219
221
  className={cn('size-7 object-contain', !isActive && 'opacity-60 grayscale')}
220
222
  height={28}
221
223
  src={cat.iconSrc}
222
224
  width={28}
223
225
  />
224
- <span className="font-medium text-[10px] leading-none">{cat.label}</span>
226
+ <span className="font-medium text-[10px] leading-none">{i18n._(cat.label)}</span>
225
227
  </button>
226
228
  )
227
229
  })}
@@ -242,7 +244,7 @@ function LegacyItemsPanel({
242
244
  setSearch(e.target.value)
243
245
  onSearchChange?.(e.target.value)
244
246
  }}
245
- placeholder="Search..."
247
+ placeholder={i18n._({ id: 'editor.sidebar.panels.itemsPanel.search', message: 'Search...' })}
246
248
  type="text"
247
249
  value={search}
248
250
  />
@@ -284,7 +286,7 @@ function LegacyItemsPanel({
284
286
  onClick={() => setActivePlacementTag(null)}
285
287
  type="button"
286
288
  >
287
- All
289
+ {i18n._({ id: 'editor.sidebar.panels.itemsPanel.all', message: 'All' })}
288
290
  </button>
289
291
  {placementTags.map((tag) => {
290
292
  const count = placementCount(tag)
@@ -376,8 +378,7 @@ function LegacyItemsPanel({
376
378
  ) : isServerSearch && search && searchResults?.length === 0 ? (
377
379
  (emptyState ?? (
378
380
  <div className="flex h-full items-center justify-center text-muted-foreground text-xs">
379
- No results for &ldquo;{search}&rdquo;
380
- </div>
381
+ {i18n._({ id: 'editor.sidebar.panels.itemsPanel.noResultsForSearch', message: 'No results for “{search}”', values: { search } })}</div>
381
382
  ))
382
383
  ) : (
383
384
  <ItemCatalog
@@ -73,6 +73,7 @@ import { Switch } from './../../../../../components/ui/primitives/switch'
73
73
  import { cn } from './../../../../../lib/utils'
74
74
  import { refusedObjectsNotice } from './../../../../../components/editor/group-actions'
75
75
  import { addStoreyBelow, addStoreyOnTop } from './../../../../../lib/add-storey'
76
+ import { offerExteriorWallCopy } from './../../../../../components/ui/level-wall-copy-prompt'
76
77
  import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
77
78
  import { bringBuildArtifacts } from './../../../../../lib/scene-clipboard'
78
79
  import useEditor, { selectDefaultBuildingAndLevel } from './../../../../../store/use-editor'
@@ -423,10 +424,12 @@ function FloorSettingsSection() {
423
424
  const addFloor = () => {
424
425
  const newLevel = addStoreyOnTop(building.id)
425
426
  setSelection({ buildingId: building.id, levelId: newLevel.id })
427
+ offerExteriorWallCopy(newLevel.id)
426
428
  }
427
429
  const addBasement = () => {
428
430
  const newLevel = addStoreyBelow(building.id)
429
431
  setSelection({ buildingId: building.id, levelId: newLevel.id })
432
+ offerExteriorWallCopy(newLevel.id)
430
433
  }
431
434
 
432
435
  return (