@pascal-app/editor 1.0.5 → 1.0.7

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 (317) hide show
  1. package/package.json +20 -17
  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 +20 -8
  6. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  7. package/src/components/editor/first-person-controls.tsx +72 -61
  8. package/src/components/editor/floating-action-menu.tsx +27 -9
  9. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  10. package/src/components/editor/floorplan-panel.test.ts +220 -0
  11. package/src/components/editor/floorplan-panel.tsx +285 -132
  12. package/src/components/editor/footprint-height-handle.tsx +1 -1
  13. package/src/components/editor/grid.tsx +24 -15
  14. package/src/components/editor/group-actions.ts +19 -1
  15. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  16. package/src/components/editor/group-move-3d.ts +27 -2
  17. package/src/components/editor/group-selection-box-3d.tsx +4 -1
  18. package/src/components/editor/group-transform-shared.ts +20 -0
  19. package/src/components/editor/index.tsx +130 -20
  20. package/src/components/editor/inspector-extension-actions.tsx +36 -0
  21. package/src/components/editor/localized-html.tsx +22 -0
  22. package/src/components/editor/node-action-menu.tsx +46 -12
  23. package/src/components/editor/node-arrow-handles.tsx +1 -2
  24. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
  25. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  26. package/src/components/editor/registry-action-contributions.test.tsx +67 -6
  27. package/src/components/editor/registry-action-contributions.tsx +115 -15
  28. package/src/components/editor/riser-diagram-panel.tsx +59 -9
  29. package/src/components/editor/room-controls.tsx +1 -1
  30. package/src/components/editor/room-handles-2d.tsx +156 -6
  31. package/src/components/editor/room-handles.tsx +241 -51
  32. package/src/components/editor/room-highlight.tsx +2 -0
  33. package/src/components/editor/room-labels.tsx +29 -15
  34. package/src/components/editor/selection-manager.tsx +15 -102
  35. package/src/components/editor/separator-end-grip.tsx +74 -0
  36. package/src/components/editor/site-edge-labels.tsx +1 -1
  37. package/src/components/editor/thumbnail-generator.test.ts +15 -6
  38. package/src/components/editor/thumbnail-generator.tsx +26 -1
  39. package/src/components/editor/use-floorplan-background-placement.ts +55 -52
  40. package/src/components/editor/views/view-bar.tsx +111 -26
  41. package/src/components/editor/wall-measurement-label.tsx +1 -1
  42. package/src/components/editor/wall-move-side-handles.tsx +5 -1
  43. package/src/components/editor/wall-pull-grip.tsx +193 -0
  44. package/src/components/editor/wall-snap-beacon-layer.tsx +81 -1
  45. package/src/components/editor-2d/floorplan-adjacent-walls-layer.test.ts +76 -0
  46. package/src/components/editor-2d/floorplan-adjacent-walls-layer.tsx +186 -0
  47. package/src/components/editor-2d/floorplan-alignment-guide-layer.tsx +63 -3
  48. package/src/components/editor-2d/floorplan-group-move.tsx +31 -3
  49. package/src/components/editor-2d/floorplan-measurement-tool-layer.test.ts +6 -0
  50. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +22 -13
  51. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +11 -3
  52. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +7 -1
  53. package/src/components/editor-2d/floorplan-snap-beacon-layer.tsx +96 -4
  54. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +52 -12
  55. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +12 -1
  56. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +197 -37
  57. package/src/components/procedural-items/inspector.tsx +59 -12
  58. package/src/components/systems/ceiling/ceiling-bracket-batch.test.ts +2 -0
  59. package/src/components/systems/ceiling/ceiling-system.test.ts +141 -0
  60. package/src/components/systems/ceiling/ceiling-system.tsx +53 -18
  61. package/src/components/tools/distribution/distribution-run-tool.tsx +50 -26
  62. package/src/components/tools/item/placement-history.test.tsx +1 -1
  63. package/src/components/tools/item/placement-strategies.test.ts +64 -1
  64. package/src/components/tools/item/placement-strategies.ts +5 -9
  65. package/src/components/tools/item/use-placement-coordinator.tsx +30 -34
  66. package/src/components/tools/registry/item-surface-move.test.ts +11 -0
  67. package/src/components/tools/select/select-candidates.test.ts +45 -0
  68. package/src/components/tools/select/select-candidates.ts +13 -2
  69. package/src/components/tools/shared/cursor-sphere.tsx +10 -3
  70. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  71. package/src/components/tools/shared/placement-box.tsx +1 -1
  72. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  73. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  74. package/src/components/tools/wall/wall-drafting.test.ts +299 -23
  75. package/src/components/tools/wall/wall-drafting.ts +132 -24
  76. package/src/components/tools/wall/wall-pull-snap.test.ts +274 -0
  77. package/src/components/tools/wall/wall-pull-snap.ts +563 -0
  78. package/src/components/tools/wall/wall-snap-geometry.test.ts +113 -1
  79. package/src/components/tools/wall/wall-snap-geometry.ts +137 -0
  80. package/src/components/tools/zone/zone-tool.tsx +3 -3
  81. package/src/components/ui/action-menu/action-button.tsx +2 -0
  82. package/src/components/ui/action-menu/camera-actions.tsx +8 -7
  83. package/src/components/ui/action-menu/control-modes.tsx +9 -5
  84. package/src/components/ui/action-menu/follow-pascal-pill.tsx +3 -1
  85. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +7 -1
  86. package/src/components/ui/action-menu/furnish-tools.tsx +7 -6
  87. package/src/components/ui/action-menu/index.tsx +1 -0
  88. package/src/components/ui/action-menu/measurement-control.tsx +35 -29
  89. package/src/components/ui/action-menu/point-ask-button.tsx +4 -3
  90. package/src/components/ui/action-menu/structure-tools.tsx +25 -23
  91. package/src/components/ui/action-menu/view-toggles.test.ts +18 -0
  92. package/src/components/ui/action-menu/view-toggles.tsx +256 -51
  93. package/src/components/ui/build-panel/build-panel.tsx +163 -25
  94. package/src/components/ui/command-palette/editor-commands.tsx +38 -11
  95. package/src/components/ui/controls/finish-swatch.tsx +45 -19
  96. package/src/components/ui/controls/material-picker.tsx +24 -17
  97. package/src/components/ui/controls/metric-control.test.ts +52 -0
  98. package/src/components/ui/controls/metric-control.tsx +62 -24
  99. package/src/components/ui/controls/segmented-control.tsx +5 -2
  100. package/src/components/ui/controls/slider-control.test.tsx +23 -0
  101. package/src/components/ui/controls/slider-control.tsx +30 -14
  102. package/src/components/ui/floating-level-selector.tsx +100 -48
  103. package/src/components/ui/helpers/building-helper.tsx +5 -3
  104. package/src/components/ui/helpers/contextual-helper-panel.tsx +97 -37
  105. package/src/components/ui/helpers/helper-manager.test.tsx +53 -3
  106. package/src/components/ui/helpers/helper-manager.tsx +82 -57
  107. package/src/components/ui/helpers/item-helper.tsx +6 -4
  108. package/src/components/ui/helpers/registered-tool-helper.tsx +3 -1
  109. package/src/components/ui/level-duplicate-dialog.tsx +32 -23
  110. package/src/components/ui/level-wall-copy-prompt.tsx +124 -0
  111. package/src/components/ui/panels/collections/collections-popover.tsx +11 -11
  112. package/src/components/ui/panels/deck-boards-fields.tsx +136 -0
  113. package/src/components/ui/panels/floor-foundation-panel.tsx +113 -38
  114. package/src/components/ui/panels/floor-opening-panel.tsx +73 -25
  115. package/src/components/ui/panels/inspector-extensions.tsx +66 -0
  116. package/src/components/ui/panels/mobile-panel-sheet.tsx +3 -1
  117. package/src/components/ui/panels/mobile-selection-bar.tsx +8 -6
  118. package/src/components/ui/panels/multi-field-value.test.ts +27 -0
  119. package/src/components/ui/panels/multi-field-value.ts +20 -1
  120. package/src/components/ui/panels/multi-height-mode.tsx +8 -7
  121. package/src/components/ui/panels/multi-parametric-inspector.tsx +122 -34
  122. package/src/components/ui/panels/multi-selection-panel.tsx +75 -35
  123. package/src/components/ui/panels/node-display.ts +35 -24
  124. package/src/components/ui/panels/panel-manager.tsx +5 -3
  125. package/src/components/ui/panels/panel-wrapper.tsx +22 -109
  126. package/src/components/ui/panels/parametric-field-control.tsx +13 -10
  127. package/src/components/ui/panels/parametric-inspector.tsx +18 -12
  128. package/src/components/ui/panels/reference-panel.tsx +51 -35
  129. package/src/components/ui/panels/room-construction-rows.tsx +464 -65
  130. package/src/components/ui/panels/room-name-options.test.ts +36 -3
  131. package/src/components/ui/panels/room-name-options.ts +110 -11
  132. package/src/components/ui/panels/room-openings-field.tsx +21 -12
  133. package/src/components/ui/panels/room-panel.tsx +206 -99
  134. package/src/components/ui/panels/shape-choice.tsx +7 -5
  135. package/src/components/ui/panels/wall-region-list.tsx +7 -7
  136. package/src/components/ui/primitives/shortcut-token.tsx +10 -6
  137. package/src/components/ui/sidebar/icon-rail.tsx +11 -9
  138. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +10 -10
  139. package/src/components/ui/sidebar/panels/items-panel/index.tsx +10 -9
  140. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +3 -0
  141. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +102 -32
  142. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +4 -3
  143. package/src/components/ui/sidebar/panels/site-panel/ceiling-tree-node.tsx +4 -2
  144. package/src/components/ui/sidebar/panels/site-panel/chimney-tree-node.tsx +4 -2
  145. package/src/components/ui/sidebar/panels/site-panel/collection-tree-node.tsx +7 -5
  146. package/src/components/ui/sidebar/panels/site-panel/column-tree-node.tsx +4 -2
  147. package/src/components/ui/sidebar/panels/site-panel/default-names.ts +75 -0
  148. package/src/components/ui/sidebar/panels/site-panel/door-tree-node.tsx +4 -2
  149. package/src/components/ui/sidebar/panels/site-panel/dormer-tree-node.tsx +4 -2
  150. package/src/components/ui/sidebar/panels/site-panel/elevator-tree-node.tsx +4 -2
  151. package/src/components/ui/sidebar/panels/site-panel/fence-tree-node.tsx +4 -2
  152. package/src/components/ui/sidebar/panels/site-panel/gutter-tree-node.tsx +4 -2
  153. package/src/components/ui/sidebar/panels/site-panel/index.tsx +117 -64
  154. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +36 -3
  155. package/src/components/ui/sidebar/panels/site-panel/item-tree-node.tsx +4 -2
  156. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +7 -4
  157. package/src/components/ui/sidebar/panels/site-panel/registry-tree-node.tsx +8 -2
  158. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +8 -5
  159. package/src/components/ui/sidebar/panels/site-panel/shelf-tree-node.tsx +4 -2
  160. package/src/components/ui/sidebar/panels/site-panel/slab-tree-node.tsx +8 -3
  161. package/src/components/ui/sidebar/panels/site-panel/solar-panel-tree-node.tsx +4 -2
  162. package/src/components/ui/sidebar/panels/site-panel/spawn-tree-node.tsx +4 -2
  163. package/src/components/ui/sidebar/panels/site-panel/stair-tree-node.tsx +8 -7
  164. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +7 -6
  165. package/src/components/ui/sidebar/panels/site-panel/tree-node.tsx +16 -1
  166. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +15 -8
  167. package/src/components/ui/sidebar/panels/site-panel/wall-tree-node.tsx +4 -2
  168. package/src/components/ui/sidebar/panels/site-panel/window-tree-node.tsx +4 -2
  169. package/src/components/ui/sidebar/panels/site-panel/zone-membership-checkbox.tsx +3 -1
  170. package/src/components/ui/sidebar/panels/site-panel/zone-tree-node.tsx +18 -8
  171. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +25 -14
  172. package/src/components/ui/sidebar/tab-bar.tsx +4 -1
  173. package/src/components/ui/sidebar/use-plugin-panels.tsx +19 -10
  174. package/src/components/ui/snap-target-badge.tsx +7 -6
  175. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +721 -0
  176. package/src/components/ui/wall-type/wall-type-menu.tsx +167 -0
  177. package/src/components/viewer/floorplan-compass-button.tsx +12 -3
  178. package/src/components/viewer/floorplan-preview.tsx +27 -19
  179. package/src/components/viewer/render-settings-menu.tsx +294 -0
  180. package/src/components/viewer/sky-control.tsx +710 -0
  181. package/src/components/viewer/use-viewer-ui-sounds.test.ts +15 -0
  182. package/src/components/viewer/use-viewer-ui-sounds.ts +46 -0
  183. package/src/components/viewer/viewer-controls-bar.tsx +278 -260
  184. package/src/components/viewer/viewer-hover-label.tsx +12 -4
  185. package/src/components/viewer/viewer-rooms.tsx +61 -4
  186. package/src/components/viewer/viewer-scene-header.tsx +28 -17
  187. package/src/components/viewer/viewer-stage-switcher.tsx +4 -2
  188. package/src/components/viewer-overlay.tsx +8 -0
  189. package/src/components/walkthrough-hud.tsx +75 -2
  190. package/src/hooks/use-keyboard.test.ts +85 -4
  191. package/src/hooks/use-keyboard.ts +65 -7
  192. package/src/hooks/use-selected-room.ts +12 -1
  193. package/src/i18n/floor-footprint-name.ts +30 -0
  194. package/src/i18n/level-name.ts +39 -0
  195. package/src/i18n/locales/en/messages.po +15117 -0
  196. package/src/i18n/locales/en/messages.ts +1 -0
  197. package/src/i18n/locales/en-XA/messages.ts +1 -0
  198. package/src/i18n/locales/es/messages.po +37501 -0
  199. package/src/i18n/locales/es/messages.ts +1 -0
  200. package/src/i18n/locales/fr/messages.po +37501 -0
  201. package/src/i18n/locales/fr/messages.ts +1 -0
  202. package/src/i18n/locales/ja/messages.po +37501 -0
  203. package/src/i18n/locales/ja/messages.ts +1 -0
  204. package/src/i18n/locales/zh-CN/messages.po +37501 -0
  205. package/src/i18n/locales/zh-CN/messages.ts +1 -0
  206. package/src/i18n/locales.test.ts +63 -0
  207. package/src/i18n/locales.ts +32 -0
  208. package/src/i18n/message-slot.tsx +25 -0
  209. package/src/i18n/node-messages.ts +169 -0
  210. package/src/i18n/pseudo-locale.ts +17 -0
  211. package/src/i18n/published-entry.test.ts +17 -0
  212. package/src/i18n/scene-theme-name.ts +16 -0
  213. package/src/index.tsx +156 -4
  214. package/src/lib/add-storey.test.ts +3 -3
  215. package/src/lib/contextual-help.test.ts +20 -1
  216. package/src/lib/contextual-help.ts +95 -19
  217. package/src/lib/continuation.ts +38 -11
  218. package/src/lib/floor-footprints.test.ts +2 -1
  219. package/src/lib/floor-foundation-support.test.ts +7 -3
  220. package/src/lib/floorplan/dimension-drive.test.ts +36 -2
  221. package/src/lib/floorplan/dimension-drive.ts +68 -3
  222. package/src/lib/floorplan/floorplan-export.test.ts +16 -5
  223. package/src/lib/floorplan/floorplan-export.tsx +13 -16
  224. package/src/lib/floorplan/floorplan-extension.test.ts +11 -12
  225. package/src/lib/floorplan/floorplan-extension.ts +37 -18
  226. package/src/lib/floorplan/floorplan-mode.test.ts +13 -6
  227. package/src/lib/floorplan/floorplan-mode.ts +14 -12
  228. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +1 -1
  229. package/src/lib/floorplan/floorplan-readonly.ts +3 -3
  230. package/src/lib/floorplan/schedules.ts +10 -6
  231. package/src/lib/floorplan/site-plan/site-annotations.test.ts +46 -1
  232. package/src/lib/floorplan/site-plan/site-annotations.ts +3 -0
  233. package/src/lib/floorplan/site-plan/site-parts.ts +6 -3
  234. package/src/lib/geometry-script/author.test.ts +114 -0
  235. package/src/lib/geometry-script/author.ts +55 -23
  236. package/src/lib/geometry-script/client.ts +3 -0
  237. package/src/lib/geometry-script/finish-scripts.ts +7 -7
  238. package/src/lib/glb-export.test.ts +327 -0
  239. package/src/lib/glb-export.ts +70 -2
  240. package/src/lib/grid-frame.test.ts +56 -3
  241. package/src/lib/grid-frame.ts +28 -1
  242. package/src/lib/history.test.ts +2 -1
  243. package/src/lib/hud-title.ts +48 -24
  244. package/src/lib/ifc-export.test.ts +41 -1
  245. package/src/lib/inspector-card-mode.test.ts +3 -31
  246. package/src/lib/inspector-card-mode.ts +5 -10
  247. package/src/lib/inspector-expanded.test.ts +13 -0
  248. package/src/lib/inspector-expanded.ts +13 -1
  249. package/src/lib/keyboard-pan.test.ts +7 -0
  250. package/src/lib/keyboard-pan.ts +9 -2
  251. package/src/lib/layer-thickness.test.ts +22 -0
  252. package/src/lib/layer-thickness.ts +79 -0
  253. package/src/lib/lot/drop-in.test.ts +59 -1
  254. package/src/lib/lot/drop-in.ts +42 -50
  255. package/src/lib/material-swatch-style.ts +24 -0
  256. package/src/lib/measurement-parser.ts +7 -3
  257. package/src/lib/measurements.test.ts +17 -0
  258. package/src/lib/measurements.ts +20 -5
  259. package/src/lib/mezzanine-selection.test.ts +3 -1
  260. package/src/lib/orbit-near-plane.test.ts +12 -0
  261. package/src/lib/orbit-near-plane.ts +13 -0
  262. package/src/lib/paint-regions.test.ts +8 -5
  263. package/src/lib/paint-scope.test.ts +53 -0
  264. package/src/lib/paint-scope.ts +18 -4
  265. package/src/lib/panel-rows.ts +17 -8
  266. package/src/lib/reference-scale.ts +83 -0
  267. package/src/lib/room-built-on.test.ts +26 -12
  268. package/src/lib/room-built-on.ts +20 -2
  269. package/src/lib/room-construction.test.ts +28 -1
  270. package/src/lib/room-construction.ts +14 -0
  271. package/src/lib/room-controls-phase6.test.ts +140 -0
  272. package/src/lib/room-handle-drag.ts +557 -42
  273. package/src/lib/room-name-catalog.test.ts +8 -0
  274. package/src/lib/room-name-catalog.ts +8 -1
  275. package/src/lib/room-name-messages.test.ts +132 -0
  276. package/src/lib/room-name-messages.ts +147 -0
  277. package/src/lib/room-name-storage.test.ts +334 -0
  278. package/src/lib/room-push-dimensions.test.ts +37 -0
  279. package/src/lib/room-push-dimensions.ts +226 -2
  280. package/src/lib/room-selection.test.ts +23 -6
  281. package/src/lib/scene-clipboard.test.ts +70 -49
  282. package/src/lib/scene-clipboard.ts +9 -4
  283. package/src/lib/selection-cycle.ts +63 -0
  284. package/src/lib/separator-edit.test.ts +431 -0
  285. package/src/lib/separator-edit.ts +179 -0
  286. package/src/lib/separator-lines.ts +38 -0
  287. package/src/lib/sfx-bus.ts +27 -1
  288. package/src/lib/sfx-player.ts +16 -0
  289. package/src/lib/tool-cursor-icon.ts +5 -1
  290. package/src/lib/tool-shortcuts.ts +130 -0
  291. package/src/lib/viewer-selection.test.ts +28 -24
  292. package/src/lib/viewer-selection.ts +20 -5
  293. package/src/lib/wall-draw-variant.test.ts +7 -6
  294. package/src/lib/wall-draw-variant.ts +107 -6
  295. package/src/lib/wall-nudge.test.ts +44 -0
  296. package/src/lib/wall-nudge.ts +131 -0
  297. package/src/lib/wall-parallel-snap.test.ts +83 -0
  298. package/src/lib/wall-parallel-snap.ts +176 -0
  299. package/src/lib/wall-placement-pointer.test.ts +23 -0
  300. package/src/lib/wall-placement-pointer.ts +18 -0
  301. package/src/lib/wall-pull.test.ts +245 -0
  302. package/src/lib/wall-pull.ts +209 -0
  303. package/src/lib/wall-push-line-up.test.ts +132 -0
  304. package/src/lib/wall-region-list.ts +14 -5
  305. package/src/lib/wall-run-snap.test.ts +188 -0
  306. package/src/lib/wall-run-snap.ts +83 -0
  307. package/src/lib/wall-type.test.ts +125 -0
  308. package/src/lib/wall-type.ts +119 -0
  309. package/src/lib/window-interaction.test.ts +42 -0
  310. package/src/lib/window-interaction.ts +47 -5
  311. package/src/lib/world-grid-snap.test.ts +139 -2
  312. package/src/lib/world-grid-snap.ts +102 -105
  313. package/src/store/use-alignment-guides.ts +27 -1
  314. package/src/store/use-editor.tsx +48 -0
  315. package/src/store/use-floorplan-annotation-visibility.test.ts +64 -0
  316. package/src/store/use-floorplan-annotation-visibility.ts +47 -22
  317. package/src/store/use-wall-snap-indicator.ts +15 -2
@@ -1,3 +1,5 @@
1
+ 'use client'
2
+
1
3
  import {
2
4
  type AnyNode,
3
5
  type AnyNodeId,
@@ -9,6 +11,8 @@ import {
9
11
  type SiteNode,
10
12
  useScene,
11
13
  type ZoneNode,
14
+ useLocale,
15
+ usePascalI18n,
12
16
  } from '@pascal-app/core'
13
17
  import { markPerfAction, useViewer } from '@pascal-app/viewer'
14
18
  import {
@@ -53,8 +57,8 @@ import {
53
57
  buildLevelDuplicateCreateOps,
54
58
  type LevelDuplicatePreset,
55
59
  } from '@pascal-app/core/building'
56
- import { getDefaultLevelName, getLevelDisplayName } from '@pascal-app/core'
57
60
  import { addStoreyOnTop } from './../../../../../lib/add-storey'
61
+ import { offerExteriorWallCopy } from './../../../../../components/ui/level-wall-copy-prompt'
58
62
  import { deleteLevelWithFallbackSelection } from './../../../../../lib/level-selection'
59
63
  import {
60
64
  formatAreaLabel,
@@ -70,7 +74,6 @@ import { requestRoomDeletion } from './../../../../../lib/room-structure-command
70
74
  import {
71
75
  selectZoneOrRoom,
72
76
  useZoneSelected,
73
- zoneKindLabel,
74
77
  } from './../../../../../lib/room-zone-routing'
75
78
  import { createUnitInBuilding, toggleZoneMembership } from './../../../../../lib/units'
76
79
  import { cn } from './../../../../../lib/utils'
@@ -79,10 +82,13 @@ import { useUploadStore } from '../../../../../store/use-upload'
79
82
  import { MetricControl } from '../../../controls/metric-control'
80
83
  import { LevelDuplicateDialog } from '../../../level-duplicate-dialog'
81
84
  import { CollectionsSection } from './collection-tree-node'
82
- import { InlineRenameInput } from './inline-rename-input'
85
+ import { InlineRenameInput, RoomRenameInput } from './inline-rename-input'
83
86
  import { ZoneMembershipCheckbox } from './zone-membership-checkbox'
84
87
  import { focusTreeNode, TreeNode, TreeNodeWrapper } from './tree-node'
85
88
  import { TreeNodeDragProvider } from './tree-node-drag'
89
+ import { zoneDefaultName } from './default-names'
90
+ import { nodeLabelDescriptor } from '../../../../../i18n/node-messages'
91
+ import { authoredLevelName, localizedLevelName } from '../../../../../i18n/level-name'
86
92
 
87
93
  // ============================================================================
88
94
  // PROPERTY LINE SECTION
@@ -126,6 +132,7 @@ function useSiteNode(): SiteNode | null {
126
132
  }
127
133
 
128
134
  const PropertyLineSection = memo(function PropertyLineSection() {
135
+ const i18n = usePascalI18n()
129
136
  const siteNode = useSiteNode()
130
137
  const updateNode = useScene((state) => state.updateNode)
131
138
  const mode = useEditor((state) => state.mode)
@@ -194,7 +201,7 @@ const PropertyLineSection = memo(function PropertyLineSection() {
194
201
 
195
202
  <div className="flex items-center gap-2">
196
203
  <Pentagon className="h-4 w-4 text-muted-foreground" />
197
- <span className="font-medium text-sm">Property Line</span>
204
+ <span className="font-medium text-sm">{i18n._({ id: 'editor.sidebar.panels.sitePanel.propertyLine', message: 'Property Line' })}</span>
198
205
  </div>
199
206
  <button
200
207
  className={cn(
@@ -212,13 +219,13 @@ const PropertyLineSection = memo(function PropertyLineSection() {
212
219
  {/* Measurements */}
213
220
  <div className="relative flex gap-3 pr-3 pb-2 pl-10">
214
221
  <div className="text-muted-foreground text-xs">
215
- Area:{' '}
222
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.area', message: 'Area:' })}{' '}
216
223
  <span className="text-foreground">
217
224
  {displayArea.toFixed(1)} {getAreaUnitLabel(viewerUnit)}
218
225
  </span>
219
226
  </div>
220
227
  <div className="text-muted-foreground text-xs">
221
- Perimeter:{' '}
228
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.perimeter', message: 'Perimeter:' })}{' '}
222
229
  <span className="text-foreground">
223
230
  {displayPerimeter.toFixed(1)} {linearLabel}
224
231
  </span>
@@ -272,7 +279,7 @@ const PropertyLineSection = memo(function PropertyLineSection() {
272
279
  onClick={handleAddPoint}
273
280
  >
274
281
  <Plus className="h-3 w-3" />
275
- Add point
282
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.addPoint', message: 'Add point' })}
276
283
  </button>
277
284
  </div>
278
285
  )}
@@ -297,6 +304,7 @@ const CameraPopover = memo(function CameraPopover({
297
304
  onOpenChange: (open: boolean) => void
298
305
  buttonClassName?: string
299
306
  }) {
307
+ const i18n = usePascalI18n()
300
308
  const updateNode = useScene((state) => state.updateNode)
301
309
  return (
302
310
  <Popover onOpenChange={onOpenChange} open={open}>
@@ -307,7 +315,7 @@ const CameraPopover = memo(function CameraPopover({
307
315
  buttonClassName,
308
316
  )}
309
317
  onClick={(e) => e.stopPropagation()}
310
- title="Camera snapshot"
318
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
311
319
  >
312
320
  <Camera className="h-3.5 w-3.5" />
313
321
  {hasCamera && (
@@ -332,7 +340,7 @@ const CameraPopover = memo(function CameraPopover({
332
340
  }}
333
341
  >
334
342
  <Camera className="h-3.5 w-3.5" />
335
- View snapshot
343
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
336
344
  </button>
337
345
  )}
338
346
  <button
@@ -344,7 +352,7 @@ const CameraPopover = memo(function CameraPopover({
344
352
  }}
345
353
  >
346
354
  <Camera className="h-3.5 w-3.5" />
347
- {hasCamera ? 'Update snapshot' : 'Take snapshot'}
355
+ {hasCamera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
348
356
  </button>
349
357
  {hasCamera && (
350
358
  <button
@@ -356,7 +364,7 @@ const CameraPopover = memo(function CameraPopover({
356
364
  }}
357
365
  >
358
366
  <Trash2 className="h-3.5 w-3.5" />
359
- Clear snapshot
367
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
360
368
  </button>
361
369
  )}
362
370
  </div>
@@ -376,6 +384,7 @@ const ReferenceItem = memo(function ReferenceItem({
376
384
  setSelectedReferenceId: (id: string) => void
377
385
  handleDelete: (id: string, e: React.MouseEvent) => void
378
386
  }) {
387
+ const i18n = usePascalI18n()
379
388
  const [isEditing, setIsEditing] = useState(false)
380
389
  const [isExpanded, setIsExpanded] = useState(true)
381
390
  const updateNode = useScene((state) => state.updateNode)
@@ -450,13 +459,13 @@ const ReferenceItem = memo(function ReferenceItem({
450
459
  >
451
460
  {isCapture ? (
452
461
  <img
453
- alt="Capture"
462
+ alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.capture', message: 'Capture' })}
454
463
  className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
455
464
  src="/icons/mesh.webp"
456
465
  />
457
466
  ) : (
458
467
  <img
459
- alt="Guide"
468
+ alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.guide', message: 'Guide' })}
460
469
  className="h-3.5 w-3.5 shrink-0 object-contain opacity-70 transition-opacity group-hover/ref:opacity-100"
461
470
  src="/icons/floorplan.webp"
462
471
  />
@@ -477,7 +486,7 @@ const ReferenceItem = memo(function ReferenceItem({
477
486
  event.stopPropagation()
478
487
  updateNode(refNode.id, { visible: !isVisible })
479
488
  }}
480
- title={isVisible ? 'Hide' : 'Show'}
489
+ title={isVisible ? i18n._({ id: 'editor.sidebar.panels.sitePanel.hide', message: 'Hide' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.show', message: 'Show' })}
481
490
  type="button"
482
491
  >
483
492
  {isVisible ? <Eye className="h-3 w-3" /> : <EyeOff className="h-3 w-3" />}
@@ -486,7 +495,7 @@ const ReferenceItem = memo(function ReferenceItem({
486
495
  <button
487
496
  className="z-20 flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/ref:opacity-100 dark:hover:bg-white/10"
488
497
  onClick={(e) => handleDelete(refNode.id, e)}
489
- title="Delete"
498
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.delete', message: 'Delete' })}
490
499
  type="button"
491
500
  >
492
501
  <Trash2 className="h-3 w-3" />
@@ -516,6 +525,7 @@ const LevelReferences = memo(function LevelReferences({
516
525
  onUploadAsset,
517
526
  onDeleteAsset,
518
527
  }: LevelReferencesProps) {
528
+ const i18n = usePascalI18n()
519
529
  const createNode = useScene((s) => s.createNode)
520
530
  const deleteNode = useScene((s) => s.deleteNode)
521
531
  const setSelection = useViewer((s) => s.setSelection)
@@ -676,7 +686,12 @@ const LevelReferences = memo(function LevelReferences({
676
686
  ) : (
677
687
  <Plus className="h-3.5 w-3.5" />
678
688
  )}
679
- {uploading ? `Uploading ${uploadingType}... ${progress}%` : 'Upload scan/floorplan'}
689
+ {uploading ? i18n._({
690
+ id: 'editor.sidebar.panels.sitePanel.uploadingProgress',
691
+ message:
692
+ '{assetType, select, guide {Uploading guide... {progress}%} other {Uploading scan... {progress}%}}',
693
+ values: { assetType: uploadingType, progress },
694
+ }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.uploadScanFloorplan', message: 'Upload scan/floorplan' })}
680
695
  </button>
681
696
 
682
697
  <input
@@ -736,6 +751,8 @@ const LevelItem = memo(function LevelItem({
736
751
  onUploadAsset?: (projectId: string, levelId: string, file: File, type: 'scan' | 'guide') => void
737
752
  onDeleteAsset?: (projectId: string, url: string) => void
738
753
  }) {
754
+ const i18n = usePascalI18n()
755
+ const locale = useLocale()
739
756
  const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false)
740
757
  const [duplicateDialogOpen, setDuplicateDialogOpen] = useState(false)
741
758
  const [isEditing, setIsEditing] = useState(false)
@@ -820,7 +837,14 @@ const LevelItem = memo(function LevelItem({
820
837
 
821
838
  <div className="relative z-20 flex h-8 items-center pr-1 pl-[28px]">
822
839
  <button
840
+ aria-expanded={isExpanded}
841
+ aria-label={
842
+ isExpanded
843
+ ? i18n._({ id: 'editor.layers.collapse', message: 'Collapse' })
844
+ : i18n._({ id: 'editor.layers.expand', message: 'Expand' })
845
+ }
823
846
  className="z-20 flex h-4 w-4 shrink-0 cursor-pointer items-center justify-center bg-inherit"
847
+ type="button"
824
848
  onClick={(e) => {
825
849
  e.stopPropagation()
826
850
  if (isSelected) {
@@ -840,7 +864,7 @@ const LevelItem = memo(function LevelItem({
840
864
 
841
865
  <div className="flex h-8 min-w-0 flex-1 cursor-pointer items-center gap-2 py-0 pl-0.5 text-sm">
842
866
  <img
843
- alt="Level"
867
+ alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.level', message: 'Level' })}
844
868
  className={cn(
845
869
  'h-4 w-4 shrink-0 object-contain transition-all duration-200',
846
870
  !isSelected && 'opacity-60 grayscale',
@@ -848,8 +872,9 @@ const LevelItem = memo(function LevelItem({
848
872
  src="/icons/level.webp"
849
873
  />
850
874
  <InlineRenameInput
851
- defaultName={getDefaultLevelName(level.level)}
852
- displayName={getLevelDisplayName(level)}
875
+ defaultName={localizedLevelName({ level: level.level }, i18n, locale)}
876
+ displayName={localizedLevelName(level, i18n, locale)}
877
+ editableName={(name) => authoredLevelName({ name, level: level.level })}
853
878
  isEditing={isEditing}
854
879
  nodeId={level.id}
855
880
  onStartEditing={() => setIsEditing(true)}
@@ -867,7 +892,7 @@ const LevelItem = memo(function LevelItem({
867
892
  : 'text-muted-foreground hover:bg-accent hover:text-foreground',
868
893
  )}
869
894
  onClick={(e) => e.stopPropagation()}
870
- title="Camera snapshot"
895
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
871
896
  >
872
897
  <Camera className="h-3.5 w-3.5" />
873
898
  {level.camera && (
@@ -892,7 +917,7 @@ const LevelItem = memo(function LevelItem({
892
917
  }}
893
918
  >
894
919
  <Camera className="h-3.5 w-3.5" />
895
- View snapshot
920
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
896
921
  </button>
897
922
  )}
898
923
  <button
@@ -904,7 +929,7 @@ const LevelItem = memo(function LevelItem({
904
929
  }}
905
930
  >
906
931
  <Camera className="h-3.5 w-3.5" />
907
- {level.camera ? 'Update snapshot' : 'Take snapshot'}
932
+ {level.camera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
908
933
  </button>
909
934
  {level.camera && (
910
935
  <button
@@ -916,7 +941,7 @@ const LevelItem = memo(function LevelItem({
916
941
  }}
917
942
  >
918
943
  <Trash2 className="h-3.5 w-3.5" />
919
- Clear snapshot
944
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
920
945
  </button>
921
946
  )}
922
947
  </div>
@@ -925,6 +950,11 @@ const LevelItem = memo(function LevelItem({
925
950
  <Popover>
926
951
  <PopoverTrigger asChild>
927
952
  <button
953
+ aria-label={i18n._({
954
+ id: 'editor.layers.levelOptions',
955
+ message: '{levelName} options',
956
+ values: { levelName: localizedLevelName(level, i18n, locale) },
957
+ })}
928
958
  className={cn(
929
959
  'mr-1 flex h-6 w-6 shrink-0 cursor-pointer items-center justify-center rounded-md opacity-0 transition-colors group-hover/level:opacity-100',
930
960
  selectedLevelId === level.id
@@ -936,31 +966,31 @@ const LevelItem = memo(function LevelItem({
936
966
  <MoreHorizontal className="h-3.5 w-3.5" />
937
967
  </button>
938
968
  </PopoverTrigger>
939
- <PopoverContent align="start" className="w-48 p-1" side="right">
969
+ <PopoverContent align="start" className="w-max min-w-48 max-w-72 p-1" side="right">
940
970
  <button
941
971
  className="flex w-full cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm transition-colors hover:bg-accent"
942
972
  onClick={() => handleDuplicateLevel()}
943
- title="Duplicate level"
973
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicateLevel', message: 'Duplicate level' })}
944
974
  >
945
975
  <Copy className="h-3.5 w-3.5" />
946
- Duplicate
976
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicate', message: 'Duplicate' })}
947
977
  </button>
948
978
  <button
949
979
  className="flex w-full cursor-pointer items-center gap-2 rounded px-3 py-1.5 text-left text-sm transition-colors hover:bg-accent"
950
980
  onClick={() => setDuplicateDialogOpen(true)}
951
- title="Duplicate level with options"
981
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicateLevelWithOptions', message: 'Duplicate level with options' })}
952
982
  >
953
983
  <Copy className="h-3.5 w-3.5" />
954
- Duplicate with options...
984
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.duplicateWithOptions', message: 'Duplicate with options...' })}
955
985
  </button>
956
986
  <button
957
987
  className="flex w-full items-center gap-2 rounded px-3 py-1.5 text-left text-sm transition-colors enabled:cursor-pointer enabled:hover:bg-accent enabled:hover:text-red-600 disabled:cursor-not-allowed disabled:opacity-50"
958
988
  disabled={!canDeleteLevel}
959
989
  onClick={() => deleteLevelWithFallbackSelection(level.id)}
960
- title={canDeleteLevel ? 'Delete level' : 'The ground level cannot be deleted'}
990
+ title={canDeleteLevel ? i18n._({ id: 'editor.sidebar.panels.sitePanel.deleteLevel', message: 'Delete level' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.theGroundLevelCannotBeDeleted', message: 'The ground level cannot be deleted' })}
961
991
  >
962
992
  <Trash2 className="h-3.5 w-3.5" />
963
- Delete
993
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.delete', message: 'Delete' })}
964
994
  </button>
965
995
  </PopoverContent>
966
996
  </Popover>
@@ -977,7 +1007,7 @@ const LevelItem = memo(function LevelItem({
977
1007
  <div className="relative border-border/50 border-b py-2 pr-3 pl-[60px]">
978
1008
  <div className="pointer-events-none absolute top-0 bottom-0 left-[45px] z-10 w-px bg-border/50" />
979
1009
  <MetricControl
980
- label="Base elevation"
1010
+ label={i18n._({ id: 'editor.sidebar.panels.sitePanel.baseElevation', message: 'Base elevation' })}
981
1011
  onChange={(value) => updateNode(level.id, { baseElevation: value })}
982
1012
  precision={2}
983
1013
  step={0.05}
@@ -1014,6 +1044,7 @@ const LevelsSection = memo(function LevelsSection({
1014
1044
  onUploadAsset?: (projectId: string, levelId: string, file: File, type: 'scan' | 'guide') => void
1015
1045
  onDeleteAsset?: (projectId: string, url: string) => void
1016
1046
  } = {}) {
1047
+ const i18n = usePascalI18n()
1017
1048
  const updateNode = useScene((state) => state.updateNode)
1018
1049
  const selectedBuildingId = useViewer((state) => state.selection.buildingId)
1019
1050
  const selectedLevelId = useViewer((state) => state.selection.levelId)
@@ -1038,6 +1069,7 @@ const LevelsSection = memo(function LevelsSection({
1038
1069
  const handleAddLevel = () => {
1039
1070
  const newLevel = addStoreyOnTop(building.id)
1040
1071
  setSelection({ buildingId: building.id, levelId: newLevel.id })
1072
+ offerExteriorWallCopy(newLevel.id)
1041
1073
  }
1042
1074
 
1043
1075
  return (
@@ -1056,7 +1088,7 @@ const LevelsSection = memo(function LevelsSection({
1056
1088
  <div className="relative z-10 flex items-center pr-1 pl-[38px]">
1057
1089
  <Plus className="h-3.5 w-3.5" />
1058
1090
  </div>
1059
- <span className="truncate">Add level</span>
1091
+ <span className="truncate">{i18n._({ id: 'editor.sidebar.panels.sitePanel.addLevel', message: 'Add level' })}</span>
1060
1092
  </button>
1061
1093
  {levels.length === 0 && (
1062
1094
  <div className="relative flex h-8 select-none items-center border-border/50 border-b py-0 pr-2 pl-[38px] text-muted-foreground text-xs">
@@ -1064,7 +1096,7 @@ const LevelsSection = memo(function LevelsSection({
1064
1096
  <div className="pointer-events-none absolute top-0 bottom-1/2 left-[21px] w-px bg-border/50" />
1065
1097
  {/* Horizontal branch line */}
1066
1098
  <div className="pointer-events-none absolute top-1/2 left-[21px] h-px w-[11px] bg-border/50" />
1067
- No levels yet
1099
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.noLevelsYet', message: 'No levels yet' })}
1068
1100
  </div>
1069
1101
  )}
1070
1102
  {[...levels].reverse().map((level, index) => (
@@ -1091,6 +1123,7 @@ const UnitsSection = memo(function UnitsSection({
1091
1123
  }: {
1092
1124
  buildingId: BuildingNode['id']
1093
1125
  }) {
1126
+ const i18n = usePascalI18n()
1094
1127
  const unitIds = useScene(
1095
1128
  useShallow((s) => {
1096
1129
  const building = s.nodes[buildingId] as BuildingNode | undefined
@@ -1117,7 +1150,7 @@ const UnitsSection = memo(function UnitsSection({
1117
1150
  <button
1118
1151
  className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground dark:hover:bg-white/10"
1119
1152
  onClick={handleNewUnit}
1120
- title="New unit"
1153
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.newUnit', message: 'New unit' })}
1121
1154
  type="button"
1122
1155
  >
1123
1156
  <Plus className="h-3 w-3" />
@@ -1129,7 +1162,7 @@ const UnitsSection = memo(function UnitsSection({
1129
1162
  icon={<Group className="h-3.5 w-3.5" />}
1130
1163
  label={
1131
1164
  <span className="flex items-center gap-1.5">
1132
- Units
1165
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.units', message: 'Units' })}
1133
1166
  {hasUnits && <span className="text-muted-foreground text-xs">{unitIds.length}</span>}
1134
1167
  </span>
1135
1168
  }
@@ -1145,7 +1178,7 @@ const UnitsSection = memo(function UnitsSection({
1145
1178
  hasChildren={false}
1146
1179
  icon={<Plus className="h-3.5 w-3.5" />}
1147
1180
  isLast
1148
- label="New unit"
1181
+ label={i18n._({ id: 'editor.sidebar.panels.sitePanel.newUnit', message: 'New unit' })}
1149
1182
  onClick={handleNewUnit}
1150
1183
  onToggle={() => {}}
1151
1184
  />
@@ -1163,6 +1196,7 @@ const ZoneItem = memo(function ZoneItem({
1163
1196
  depth: number
1164
1197
  isLast?: boolean
1165
1198
  }) {
1199
+ const i18n = usePascalI18n()
1166
1200
  const [isEditing, setIsEditing] = useState(false)
1167
1201
  const [cameraPopoverOpen, setCameraPopoverOpen] = useState(false)
1168
1202
  const deleteNode = useScene((state) => state.deleteNode)
@@ -1190,7 +1224,7 @@ const ZoneItem = memo(function ZoneItem({
1190
1224
  }
1191
1225
  }, [isSelected])
1192
1226
 
1193
- const defaultName = `${zoneKindLabel(zone)} (${formatAreaLabel(calculatePolygonArea(zone.polygon), unit)})`
1227
+ const defaultName = zoneDefaultName(i18n, zone, formatAreaLabel(calculatePolygonArea(zone.polygon), unit))
1194
1228
 
1195
1229
  const handleClick = () => {
1196
1230
  setMode('select')
@@ -1250,14 +1284,22 @@ const ZoneItem = memo(function ZoneItem({
1250
1284
  <ZoneMembershipCheckbox
1251
1285
  checked={focusedUnit.members.includes(zone.id)}
1252
1286
  onToggle={() => toggleZoneMembership(focusedUnit.id, zone.id)}
1253
- unitName={focusedUnit.name || 'Unit'}
1287
+ unitName={focusedUnit.name || i18n._(nodeLabelDescriptor('unit', 'Unit'))}
1254
1288
  />
1255
1289
  )}
1256
1290
  <span className={cn('mr-2', !isSelected && 'opacity-40')}>
1257
- <ColorDot color={zone.color} onChange={handleColorChange} />
1291
+ <ColorDot
1292
+ color={zone.color}
1293
+ label={i18n._({
1294
+ id: 'editor.layers.spaceColour',
1295
+ message: '{role, select, room {Room colour} other {Zone colour}}',
1296
+ values: { role: zone.spaceRole ?? 'generic' },
1297
+ })}
1298
+ onChange={handleColorChange}
1299
+ />
1258
1300
  </span>
1259
1301
  <div className="min-w-0 flex-1 pr-1">
1260
- <InlineRenameInput
1302
+ <RoomRenameInput
1261
1303
  defaultName={defaultName}
1262
1304
  isEditing={isEditing}
1263
1305
  nodeId={zone.id}
@@ -1272,7 +1314,7 @@ const ZoneItem = memo(function ZoneItem({
1272
1314
  <button
1273
1315
  className="relative flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/row:opacity-100 dark:hover:bg-white/10"
1274
1316
  onClick={(e) => e.stopPropagation()}
1275
- title="Camera snapshot"
1317
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
1276
1318
  >
1277
1319
  <Camera className="h-3 w-3" />
1278
1320
  {zone.camera && (
@@ -1297,7 +1339,7 @@ const ZoneItem = memo(function ZoneItem({
1297
1339
  }}
1298
1340
  >
1299
1341
  <Camera className="h-3.5 w-3.5" />
1300
- View snapshot
1342
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
1301
1343
  </button>
1302
1344
  )}
1303
1345
  <button
@@ -1309,7 +1351,7 @@ const ZoneItem = memo(function ZoneItem({
1309
1351
  }}
1310
1352
  >
1311
1353
  <Camera className="h-3.5 w-3.5" />
1312
- {zone.camera ? 'Update snapshot' : 'Take snapshot'}
1354
+ {zone.camera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
1313
1355
  </button>
1314
1356
  {zone.camera && (
1315
1357
  <button
@@ -1321,15 +1363,17 @@ const ZoneItem = memo(function ZoneItem({
1321
1363
  }}
1322
1364
  >
1323
1365
  <Trash2 className="h-3.5 w-3.5" />
1324
- Clear snapshot
1366
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
1325
1367
  </button>
1326
1368
  )}
1327
1369
  </div>
1328
1370
  </PopoverContent>
1329
1371
  </Popover>
1330
1372
  <button
1331
- className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/row:opacity-100 dark:hover:bg-white/10"
1373
+ aria-label={i18n._({ id: 'editor.sidebar.panels.sitePanel.delete', message: 'Delete' })}
1374
+ className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground opacity-0 transition-colors hover:bg-black/5 hover:text-foreground group-hover/row:opacity-100 focus-visible:opacity-100 dark:hover:bg-white/10"
1332
1375
  onClick={handleDelete}
1376
+ type="button"
1333
1377
  >
1334
1378
  <Trash2 className="h-3 w-3" />
1335
1379
  </button>
@@ -1339,6 +1383,7 @@ const ZoneItem = memo(function ZoneItem({
1339
1383
  })
1340
1384
 
1341
1385
  const MultiSelectionBadge = memo(function MultiSelectionBadge() {
1386
+ const i18n = usePascalI18n()
1342
1387
  const selectedIds = useViewer((state) => state.selection.selectedIds)
1343
1388
  const setSelection = useViewer((state) => state.setSelection)
1344
1389
 
@@ -1347,11 +1392,15 @@ const MultiSelectionBadge = memo(function MultiSelectionBadge() {
1347
1392
  return (
1348
1393
  <div className="pointer-events-none sticky top-4 z-50 flex h-0 w-full justify-center overflow-visible">
1349
1394
  <div className="pointer-events-auto flex items-center gap-2.5 rounded-full border border-primary/20 bg-primary px-0.5 py-4 pl-2 font-medium text-primary-foreground text-xs shadow-black/10 shadow-lg backdrop-blur-md">
1350
- <span>{selectedIds.length} objects selected</span>
1395
+ <span>{i18n._({
1396
+ id: 'editor.sidebar.panels.sitePanel.objectsSelected',
1397
+ message: '{count, plural, one {# object selected} other {# objects selected}}',
1398
+ values: { count: selectedIds.length },
1399
+ })}</span>
1351
1400
  <button
1352
1401
  className="cursor-pointer rounded-full p-1.5 transition-colors hover:bg-primary-foreground/20"
1353
1402
  onClick={() => setSelection({ selectedIds: [] })}
1354
- title="Clear selection"
1403
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSelection', message: 'Clear selection' })}
1355
1404
  >
1356
1405
  <X className="h-4 w-4" />
1357
1406
  </button>
@@ -1361,6 +1410,7 @@ const MultiSelectionBadge = memo(function MultiSelectionBadge() {
1361
1410
  })
1362
1411
 
1363
1412
  const RoomsSection = memo(function RoomsSection() {
1413
+ const i18n = usePascalI18n()
1364
1414
  const selectedLevelId = useViewer((state) => state.selection.levelId)
1365
1415
  const armToolMode = useEditor((state) => state.armToolMode)
1366
1416
  const levelZones = useScene(
@@ -1390,7 +1440,7 @@ const RoomsSection = memo(function RoomsSection() {
1390
1440
  <button
1391
1441
  className="flex h-6 w-6 cursor-pointer items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-black/5 hover:text-foreground dark:hover:bg-white/10"
1392
1442
  onClick={handleDrawZone}
1393
- title="Draw a zone"
1443
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.drawAZone', message: 'Draw a zone' })}
1394
1444
  type="button"
1395
1445
  >
1396
1446
  <Plus className="h-3 w-3" />
@@ -1402,7 +1452,7 @@ const RoomsSection = memo(function RoomsSection() {
1402
1452
  icon={<LayoutGrid className="h-3.5 w-3.5" />}
1403
1453
  label={
1404
1454
  <span className="flex items-center gap-1.5">
1405
- Rooms
1455
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.rooms', message: 'Rooms' })}
1406
1456
  {rooms.length > 0 && (
1407
1457
  <span className="text-muted-foreground text-xs">{rooms.length}</span>
1408
1458
  )}
@@ -1413,7 +1463,7 @@ const RoomsSection = memo(function RoomsSection() {
1413
1463
  >
1414
1464
  {levelZones.length === 0 && (
1415
1465
  <div className="border-border/50 border-b py-2 pr-3 pl-[52px] text-muted-foreground text-xs">
1416
- Close walls around a space to make a room.
1466
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.closeWallsAroundASpaceTo', message: 'Close walls around a space to make a room.' })}
1417
1467
  </div>
1418
1468
  )}
1419
1469
  {rooms.map((zone, index) => (
@@ -1430,7 +1480,7 @@ const RoomsSection = memo(function RoomsSection() {
1430
1480
  className="pt-3 pb-1 pl-[52px] font-medium text-[11px] text-muted-foreground"
1431
1481
  data-other-zones
1432
1482
  >
1433
- Other zones
1483
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.otherZones', message: 'Other zones' })}
1434
1484
  </div>
1435
1485
  {others.map((zone, index) => (
1436
1486
  <ZoneItem depth={2} isLast={index === others.length - 1} key={zone.id} zone={zone} />
@@ -1443,6 +1493,7 @@ const RoomsSection = memo(function RoomsSection() {
1443
1493
  })
1444
1494
 
1445
1495
  const ContentSection = memo(function ContentSection() {
1496
+ const i18n = usePascalI18n()
1446
1497
  const selectedLevelId = useViewer((state) => state.selection.levelId)
1447
1498
  const level = useScene((s) =>
1448
1499
  selectedLevelId ? ((s.nodes[selectedLevelId] as LevelNode | undefined) ?? null) : null,
@@ -1462,7 +1513,7 @@ const ContentSection = memo(function ContentSection() {
1462
1513
 
1463
1514
  if (!level) {
1464
1515
  return (
1465
- <div className="px-3 py-4 text-muted-foreground text-sm">Select a level to view content</div>
1516
+ <div className="px-3 py-4 text-muted-foreground text-sm">{i18n._({ id: 'editor.sidebar.panels.sitePanel.selectALevelToViewContent', message: 'Select a level to view content' })}</div>
1466
1517
  )
1467
1518
  }
1468
1519
 
@@ -1474,7 +1525,7 @@ const ContentSection = memo(function ContentSection() {
1474
1525
  icon={<Boxes className="h-3.5 w-3.5" />}
1475
1526
  label={
1476
1527
  <span className="flex items-center gap-1.5">
1477
- Elements
1528
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.elements', message: 'Elements' })}
1478
1529
  {elementChildren.length > 0 && (
1479
1530
  <span className="text-muted-foreground text-xs">{elementChildren.length}</span>
1480
1531
  )}
@@ -1485,7 +1536,7 @@ const ContentSection = memo(function ContentSection() {
1485
1536
  >
1486
1537
  {elementChildren.length === 0 ? (
1487
1538
  <div className="border-border/50 border-b py-2 pr-3 pl-[52px] text-muted-foreground text-xs">
1488
- No elements on this level
1539
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.noElementsOnThisLevel', message: 'No elements on this level' })}
1489
1540
  </div>
1490
1541
  ) : (
1491
1542
  <TreeNodeDragProvider>
@@ -1520,6 +1571,7 @@ const BuildingItem = memo(function BuildingItem({
1520
1571
  onUploadAsset?: (projectId: string, levelId: string, file: File, type: 'scan' | 'guide') => void
1521
1572
  onDeleteAsset?: (projectId: string, url: string) => void
1522
1573
  }) {
1574
+ const i18n = usePascalI18n()
1523
1575
  const setSelection = useViewer((state) => state.setSelection)
1524
1576
  const phase = useEditor((state) => state.phase)
1525
1577
  const setPhase = useEditor((state) => state.setPhase)
@@ -1560,14 +1612,14 @@ const BuildingItem = memo(function BuildingItem({
1560
1612
  >
1561
1613
  <div className="flex h-full min-w-0 flex-1 cursor-pointer items-center gap-2 py-2 pl-3">
1562
1614
  <img
1563
- alt="Building"
1615
+ alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.building', message: 'Building' })}
1564
1616
  className={cn(
1565
1617
  'h-5 w-5 object-contain transition-all',
1566
1618
  !isBuildingActive && 'opacity-60 grayscale',
1567
1619
  )}
1568
1620
  src="/icons/building.webp"
1569
1621
  />
1570
- <span className="truncate font-medium text-sm">{building.name || 'Building'}</span>
1622
+ <span className="truncate font-medium text-sm">{building.name || i18n._({ id: 'editor.sidebar.panels.sitePanel.building', message: 'Building' })}</span>
1571
1623
  </div>
1572
1624
  <Popover
1573
1625
  onOpenChange={(open) => setBuildingCameraOpen(open ? building.id : null)}
@@ -1582,7 +1634,7 @@ const BuildingItem = memo(function BuildingItem({
1582
1634
  : 'text-muted-foreground hover:bg-accent hover:text-foreground',
1583
1635
  )}
1584
1636
  onClick={(e) => e.stopPropagation()}
1585
- title="Camera snapshot"
1637
+ title={i18n._({ id: 'editor.sidebar.panels.sitePanel.cameraSnapshot', message: 'Camera snapshot' })}
1586
1638
  >
1587
1639
  <Camera className="h-4 w-4" />
1588
1640
  {building.camera && (
@@ -1607,7 +1659,7 @@ const BuildingItem = memo(function BuildingItem({
1607
1659
  }}
1608
1660
  >
1609
1661
  <Camera className="h-3.5 w-3.5" />
1610
- View snapshot
1662
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.viewSnapshot', message: 'View snapshot' })}
1611
1663
  </button>
1612
1664
  )}
1613
1665
  <button
@@ -1619,7 +1671,7 @@ const BuildingItem = memo(function BuildingItem({
1619
1671
  }}
1620
1672
  >
1621
1673
  <Camera className="h-3.5 w-3.5" />
1622
- {building.camera ? 'Update snapshot' : 'Take snapshot'}
1674
+ {building.camera ? i18n._({ id: 'editor.sidebar.panels.sitePanel.updateSnapshot', message: 'Update snapshot' }) : i18n._({ id: 'editor.sidebar.panels.sitePanel.takeSnapshot', message: 'Take snapshot' })}
1623
1675
  </button>
1624
1676
  {building.camera && (
1625
1677
  <button
@@ -1631,7 +1683,7 @@ const BuildingItem = memo(function BuildingItem({
1631
1683
  }}
1632
1684
  >
1633
1685
  <Trash2 className="h-3.5 w-3.5" />
1634
- Clear snapshot
1686
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.clearSnapshot', message: 'Clear snapshot' })}
1635
1687
  </button>
1636
1688
  )}
1637
1689
  </div>
@@ -1679,6 +1731,7 @@ export interface SitePanelProps {
1679
1731
  }
1680
1732
 
1681
1733
  export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanelProps = {}) {
1734
+ const i18n = usePascalI18n()
1682
1735
  const rootNodeIds = useScene((state) => state.rootNodeIds)
1683
1736
  const updateNode = useScene((state) => state.updateNode)
1684
1737
  const selectedBuildingId = useViewer((state) => state.selection.buildingId)
@@ -1703,7 +1756,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
1703
1756
 
1704
1757
  return (
1705
1758
  <LayoutGroup>
1706
- <div className="flex h-full flex-col">
1759
+ <div className="flex h-full flex-col" data-surface="site-panel">
1707
1760
  {/* Site Header */}
1708
1761
  {siteNode && (
1709
1762
  <motion.div
@@ -1725,14 +1778,14 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
1725
1778
  >
1726
1779
  <div className="flex items-center gap-2">
1727
1780
  <img
1728
- alt="Site"
1781
+ alt={i18n._({ id: 'editor.sidebar.panels.sitePanel.site', message: 'Site' })}
1729
1782
  className={cn(
1730
1783
  'h-5 w-5 object-contain transition-all',
1731
1784
  phase !== 'site' && 'opacity-60 grayscale',
1732
1785
  )}
1733
1786
  src="/icons/site-flag.webp"
1734
1787
  />
1735
- <span className="font-medium text-sm">{siteNode.name || 'Site'}</span>
1788
+ <span className="font-medium text-sm">{siteNode.name || i18n._({ id: 'editor.sidebar.panels.sitePanel.site', message: 'Site' })}</span>
1736
1789
  </div>
1737
1790
  <CameraPopover
1738
1791
  buttonClassName={cn(
@@ -1776,7 +1829,7 @@ export function SitePanel({ projectId, onUploadAsset, onDeleteAsset }: SitePanel
1776
1829
  {/* Buildings List */}
1777
1830
  {buildings.length === 0 ? (
1778
1831
  <motion.div className="px-3 py-4 text-muted-foreground text-sm" layout="position">
1779
- No buildings yet
1832
+ {i18n._({ id: 'editor.sidebar.panels.sitePanel.noBuildingsYet', message: 'No buildings yet' })}
1780
1833
  </motion.div>
1781
1834
  ) : (
1782
1835
  <div className="flex min-h-0 flex-1 flex-col">