@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
@@ -6,14 +6,15 @@ import {
6
6
  type BuildingNode,
7
7
  resolveBuildingForLevel,
8
8
  runAsSingleSceneHistoryStep,
9
- useScene,
9
+ useScene, usePascalI18n,
10
+ type PascalI18n,
10
11
  } from '@pascal-app/core'
11
12
  import { unwrapCorner, windowMergeOffer } from '@pascal-app/core/building'
12
13
  import { useViewer } from '@pascal-app/viewer'
13
14
  import { Building2, Copy, Group, Merge, Split, Trash2, Ungroup } from 'lucide-react'
14
15
  import { useMemo } from 'react'
15
16
  import { deleteSelection, duplicateSelectionAndPickUp } from '../../editor/group-actions'
16
- import { formatLinearMeasurement } from '../../../lib/measurements'
17
+ import { formatLinearMeasurement } from '@pascal-app/core'
17
18
  import { triggerSFX } from '../../../lib/sfx-bus'
18
19
  import { writeWindowChanges } from '../../../lib/window-changes'
19
20
  import { collectSelectableCandidateIds } from '../../tools/select/select-candidates'
@@ -57,36 +58,71 @@ function mergeRefusalNote(
57
58
  refusal: { code: string; details: Record<string, unknown> },
58
59
  nodes: Record<string, AnyNode>,
59
60
  length: (meters: number) => string,
61
+ i18n: PascalI18n,
60
62
  ): string {
61
63
  const { blockingId, limit } = refusal.details as { blockingId?: string; limit?: number }
62
64
  const reach = length(limit ?? 0)
63
65
  const blocking = nodes[blockingId ?? '']
64
- const blockingKind = !blocking
65
- ? 'something'
66
- : blocking.type === 'item'
67
- ? 'an item'
68
- : `a ${blocking.type}`
66
+ // A wall's children: the openings and wall-hosted items the merge can run into.
67
+ const kind = blocking?.type ?? 'unknown'
69
68
  switch (refusal.code) {
70
69
  case 'window_already_joined':
71
- return 'One of them already wraps another corner. Unwrap it first.'
70
+ return i18n._({
71
+ id: 'editor.panels.mergeRefusal.alreadyJoined',
72
+ message: 'One of them already wraps another corner. Unwrap it first.',
73
+ })
72
74
  case 'window_not_on_wall':
73
- return 'A roof or dormer window cannot join another window.'
75
+ return i18n._({
76
+ id: 'editor.panels.mergeRefusal.notOnWall',
77
+ message: 'A roof or dormer window cannot join another window.',
78
+ })
74
79
  case 'windows_not_adjacent':
75
- return `They are too far apart. Windows merge with at most ${reach} between them.`
80
+ return i18n._({
81
+ id: 'editor.panels.mergeRefusal.notAdjacent',
82
+ message: 'They are too far apart. Windows merge with at most {reach} between them.',
83
+ values: { reach },
84
+ })
76
85
  case 'opening_between':
77
- return `There is ${blockingKind} between them.`
86
+ return i18n._({
87
+ id: 'editor.panels.mergeRefusal.between',
88
+ message:
89
+ '{kind, select, item {There is an item between them.} door {There is a door between them.} window {There is a window between them.} shelf {There is a shelf between them.} other {There is something between them.}}',
90
+ values: { kind },
91
+ })
78
92
  case 'walls_not_meeting':
79
- return 'Their walls do not meet at a corner.'
93
+ return i18n._({
94
+ id: 'editor.panels.mergeRefusal.wallsNotMeeting',
95
+ message: 'Their walls do not meet at a corner.',
96
+ })
80
97
  case 'corner_angle':
81
- return 'Their walls meet at too sharp or too flat an angle for a corner window.'
98
+ return i18n._({
99
+ id: 'editor.panels.mergeRefusal.cornerAngle',
100
+ message: 'Their walls meet at too sharp or too flat an angle for a corner window.',
101
+ })
82
102
  case 'window_not_at_corner':
83
- return `One of them is too far from the corner. A window joins one within ${reach} of it.`
103
+ return i18n._({
104
+ id: 'editor.panels.mergeRefusal.notAtCorner',
105
+ message: 'One of them is too far from the corner. A window joins one within {reach} of it.',
106
+ values: { reach },
107
+ })
84
108
  case 'height_exceeds_wall':
85
- return 'The window selected first is taller than the other wall. Select the other one first.'
109
+ return i18n._({
110
+ id: 'editor.panels.mergeRefusal.heightExceedsWall',
111
+ message:
112
+ 'The window selected first is taller than the other wall. Select the other one first.',
113
+ })
86
114
  case 'opening_overlap':
87
- return `Sliding them to the corner would overlap ${blockingKind}.`
115
+ return i18n._({
116
+ id: 'editor.panels.mergeRefusal.overlap',
117
+ message:
118
+ '{kind, select, item {Sliding them to the corner would overlap an item.} door {Sliding them to the corner would overlap a door.} window {Sliding them to the corner would overlap a window.} shelf {Sliding them to the corner would overlap a shelf.} other {Sliding them to the corner would overlap something.}}',
119
+ values: { kind },
120
+ })
88
121
  default:
89
- return 'These windows cannot be merged.'
122
+ return i18n._({
123
+ id: 'editor.panels.mergeRefusal.cannotMerge',
124
+ message: 'These windows cannot be merged.',
125
+ })
90
126
  }
91
127
  }
92
128
 
@@ -97,6 +133,7 @@ function mergeRefusalNote(
97
133
  * Unwrap instead; two that cannot join, the reason.
98
134
  */
99
135
  function MergeWindowsAction() {
136
+ const i18n = usePascalI18n()
100
137
  const selectedIds = useViewer((s) => s.selection.selectedIds)
101
138
  const nodes = useScene((s) => s.nodes)
102
139
  const readOnly = useScene((s) => s.readOnly)
@@ -110,7 +147,7 @@ function MergeWindowsAction() {
110
147
  className="basis-full whitespace-nowrap disabled:cursor-not-allowed disabled:opacity-40"
111
148
  disabled={readOnly}
112
149
  icon={<Split className="h-4 w-4" />}
113
- label="Unwrap the corner"
150
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.unwrapTheCorner', message: 'Unwrap the corner' })}
114
151
  onClick={() => {
115
152
  writeWindowChanges({
116
153
  create: null,
@@ -119,13 +156,16 @@ function MergeWindowsAction() {
119
156
  })
120
157
  triggerSFX('sfx:structure-build')
121
158
  }}
122
- title="Unwrap the corner: both windows stay, each framed at the corner"
159
+ title={i18n._({ id: 'editor.panels.multiSelectionPanel.unwrapTheCornerBothWindowsStay', message: 'Unwrap the corner: both windows stay, each framed at the corner' })}
123
160
  />
124
161
  )
125
162
  const { merge } = offer
126
163
  const note = offer.refusal
127
- ? mergeRefusalNote(offer.refusal, nodes, (meters) =>
128
- formatLinearMeasurement(meters, unit, metricNotation),
164
+ ? mergeRefusalNote(
165
+ offer.refusal,
166
+ nodes,
167
+ (meters) => formatLinearMeasurement(meters, unit, metricNotation),
168
+ i18n,
129
169
  )
130
170
  : null
131
171
  return (
@@ -134,7 +174,7 @@ function MergeWindowsAction() {
134
174
  className="basis-full whitespace-nowrap disabled:cursor-not-allowed disabled:opacity-40"
135
175
  disabled={readOnly || !merge}
136
176
  icon={<Merge className="h-4 w-4" />}
137
- label="Merge windows"
177
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.mergeWindows', message: 'Merge windows' })}
138
178
  onClick={() => {
139
179
  if (!merge) return
140
180
  runAsSingleSceneHistoryStep(useScene, () =>
@@ -150,8 +190,8 @@ function MergeWindowsAction() {
150
190
  title={
151
191
  note ??
152
192
  (merge?.kind === 'corner'
153
- ? 'Merge windows into a corner window: both take the height, sill and type of the one you selected first'
154
- : 'Merge windows into one across both: it keeps the type and style of the one you selected first')
193
+ ? i18n._({ id: 'editor.panels.multiSelectionPanel.mergeWindowsIntoACornerWindow', message: 'Merge windows into a corner window: both take the height, sill and type of the one you selected first' })
194
+ : i18n._({ id: 'editor.panels.multiSelectionPanel.mergeWindowsIntoOneAcrossBoth', message: 'Merge windows into one across both: it keeps the type and style of the one you selected first' }))
155
195
  }
156
196
  />
157
197
  {note ? <p className="basis-full px-1 text-muted-foreground text-xs">{note}</p> : null}
@@ -160,6 +200,7 @@ function MergeWindowsAction() {
160
200
  }
161
201
 
162
202
  export function MultiSelectionActions() {
203
+ const i18n = usePascalI18n()
163
204
  const selectedIds = useViewer((s) => s.selection.selectedIds)
164
205
  const sessionGroups = useSessionGroups((s) => s.groups)
165
206
  const sceneNodes = useScene((s) => s.nodes)
@@ -182,39 +223,39 @@ export function MultiSelectionActions() {
182
223
  <ActionButton
183
224
  className="basis-full whitespace-nowrap"
184
225
  icon={<Building2 className="h-4 w-4" />}
185
- label="Select building"
226
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.selectBuilding', message: 'Select building' })}
186
227
  onClick={() => selectBuilding(buildingId)}
187
- title="Select the whole building to move all its levels"
228
+ title={i18n._({ id: 'editor.panels.multiSelectionPanel.selectTheWholeBuildingToMove', message: 'Select the whole building to move all its levels' })}
188
229
  />
189
230
  )}
190
231
  {showGroup && (
191
232
  <ActionButton
192
233
  className="min-w-20 px-2"
193
234
  icon={<Group className="h-4 w-4" />}
194
- label="Group"
235
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.group', message: 'Group' })}
195
236
  onClick={() => groupCurrentSelection()}
196
- title="Group (Ctrl/Cmd+G)"
237
+ title={i18n._({ id: 'editor.panels.multiSelectionPanel.groupCtrlCmdG', message: 'Group (Ctrl/Cmd+G)' })}
197
238
  />
198
239
  )}
199
240
  {showUngroup && (
200
241
  <ActionButton
201
242
  className="min-w-20 px-2"
202
243
  icon={<Ungroup className="h-4 w-4" />}
203
- label="Ungroup"
244
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.ungroup', message: 'Ungroup' })}
204
245
  onClick={() => ungroupCurrentSelection()}
205
- title="Ungroup (Ctrl/Cmd+Shift+G)"
246
+ title={i18n._({ id: 'editor.panels.multiSelectionPanel.ungroupCtrlCmdShiftG', message: 'Ungroup (Ctrl/Cmd+Shift+G)' })}
206
247
  />
207
248
  )}
208
249
  <ActionButton
209
250
  className="min-w-20 px-2"
210
251
  icon={<Copy className="h-4 w-4" />}
211
- label="Duplicate"
252
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.duplicate', message: 'Duplicate' })}
212
253
  onClick={() => duplicateSelectionAndPickUp()}
213
254
  />
214
255
  <ActionButton
215
256
  className="min-w-20 px-2 border-red-500/40 text-red-700 dark:text-red-200 hover:bg-red-500/15"
216
257
  icon={<Trash2 className="h-4 w-4 text-red-600 dark:text-red-400" />}
217
- label="Delete"
258
+ label={i18n._({ id: 'editor.panels.multiSelectionPanel.delete', message: 'Delete' })}
218
259
  onClick={() => deleteSelection()}
219
260
  />
220
261
  </ActionGroup>
@@ -225,6 +266,7 @@ export function MultiSelectionActions() {
225
266
  * Docked multi-selection panel. Includes Group / Ungroup for session selection sets.
226
267
  */
227
268
  export function MultiSelectionPanel({ footer }: { footer?: React.ReactNode }) {
269
+ const i18n = usePascalI18n()
228
270
  const selectedIds = useViewer((s) => s.selection.selectedIds)
229
271
  const setSelection = useViewer((s) => s.setSelection)
230
272
  const sessionGroups = useSessionGroups((s) => s.groups)
@@ -246,16 +288,14 @@ export function MultiSelectionPanel({ footer }: { footer?: React.ReactNode }) {
246
288
  title={
247
289
  matchedGroup
248
290
  ? `${matchedGroup.label} · ${selectedIds.length}`
249
- : `${selectedIds.length} selected`
291
+ : i18n._({ id: 'editor.panels.multiSelectionPanel.lengthSelected', message: '{length} selected', values: { length: selectedIds.length } })
250
292
  }
251
293
  width={320}
252
294
  >
253
295
  {breakdown && <div className="px-3 py-3 text-muted-foreground text-xs">{breakdown}</div>}
254
296
  {matchedGroup && (
255
297
  <div className="border-border/50 border-t px-3 py-2 text-muted-foreground text-xs">
256
- {matchedGroup.label} (session only). Plain click reselects all members. Not saved with the
257
- project.
258
- </div>
298
+ {i18n._({ id: 'editor.panels.multiSelectionPanel.labelSessionOnlyPlainClickReselects', message: '{label} (session only). Plain click reselects all members. Not saved with the project.', values: { label: matchedGroup.label } })}</div>
259
299
  )}
260
300
  <div className="border-border/50 border-t p-3">
261
301
  <MultiSelectionActions />
@@ -1,36 +1,42 @@
1
- import { type AnyNode, scriptImages } from '@pascal-app/core'
1
+ import { type AnyNode, type MessageDescriptor, type PascalI18n, scriptImages } from '@pascal-app/core'
2
+ import { displayRoomName } from '../../../lib/room-name-messages'
2
3
 
3
4
  export type NodeDisplay = {
4
5
  icon: string
5
6
  label: string
6
7
  }
7
8
 
8
- const TYPE_DEFAULTS: Record<string, NodeDisplay> = {
9
- item: { icon: '/icons/item.webp', label: 'Item' },
10
- wall: { icon: '/icons/wall.webp', label: 'Wall' },
11
- door: { icon: '/icons/door.webp', label: 'Door' },
12
- window: { icon: '/icons/window.webp', label: 'Window' },
13
- slab: { icon: '/icons/floor.webp', label: 'Slab' },
14
- ceiling: { icon: '/icons/ceiling.webp', label: 'Ceiling' },
15
- column: { icon: '/icons/column.webp', label: 'Column' },
16
- elevator: { icon: '/icons/elevator.webp', label: 'Elevator' },
17
- fence: { icon: '/icons/fence.webp', label: 'Fence' },
18
- roof: { icon: '/icons/roof.webp', label: 'Roof' },
19
- 'roof-segment': { icon: '/icons/roof.webp', label: 'Roof segment' },
20
- stair: { icon: '/icons/stairs.webp', label: 'Stair' },
21
- 'stair-segment': { icon: '/icons/stairs.webp', label: 'Stair segment' },
22
- scan: { icon: '/icons/mesh.webp', label: '3D Scan' },
23
- guide: { icon: '/icons/floorplan.webp', label: 'Guide image' },
24
- zone: { icon: '/icons/zone.webp', label: 'Zone' },
9
+ const TYPE_DEFAULTS: Record<string, { icon: string; label: MessageDescriptor }> = {
10
+ item: { icon: '/icons/item.webp', label: /* i18n */ { id: 'editor.nodeDisplay.item', message: 'Item' } },
11
+ wall: { icon: '/icons/wall.webp', label: /* i18n */ { id: 'editor.nodeDisplay.wall', message: 'Wall' } },
12
+ door: { icon: '/icons/door.webp', label: /* i18n */ { id: 'editor.nodeDisplay.door', message: 'Door' } },
13
+ window: { icon: '/icons/window.webp', label: /* i18n */ { id: 'editor.nodeDisplay.window', message: 'Window' } },
14
+ slab: { icon: '/icons/floor.webp', label: /* i18n */ { id: 'editor.nodeDisplay.slab', message: 'Slab' } },
15
+ ceiling: { icon: '/icons/ceiling.webp', label: /* i18n */ { id: 'editor.nodeDisplay.ceiling', message: 'Ceiling' } },
16
+ column: { icon: '/icons/column.webp', label: /* i18n */ { id: 'editor.nodeDisplay.column', message: 'Column' } },
17
+ elevator: { icon: '/icons/elevator.webp', label: /* i18n */ { id: 'editor.nodeDisplay.elevator', message: 'Elevator' } },
18
+ fence: { icon: '/icons/fence.webp', label: /* i18n */ { id: 'editor.nodeDisplay.fence', message: 'Fence' } },
19
+ roof: { icon: '/icons/roof.webp', label: /* i18n */ { id: 'editor.nodeDisplay.roof', message: 'Roof' } },
20
+ 'roof-segment': { icon: '/icons/roof.webp', label: /* i18n */ { id: 'editor.nodeDisplay.roofSegment', message: 'Roof segment' } },
21
+ stair: { icon: '/icons/stairs.webp', label: /* i18n */ { id: 'editor.nodeDisplay.stair', message: 'Stair' } },
22
+ 'stair-segment': { icon: '/icons/stairs.webp', label: /* i18n */ { id: 'editor.nodeDisplay.stairSegment', message: 'Stair segment' } },
23
+ scan: { icon: '/icons/mesh.webp', label: /* i18n */ { id: 'editor.nodeDisplay.scan', message: '3D Scan' } },
24
+ guide: { icon: '/icons/floorplan.webp', label: /* i18n */ { id: 'editor.nodeDisplay.guide', message: 'Guide image' } },
25
+ zone: { icon: '/icons/zone.webp', label: /* i18n */ { id: 'editor.nodeDisplay.zone', message: 'Zone' } },
25
26
  }
26
27
 
27
- export function getTypeDisplay(type: string): NodeDisplay {
28
- return TYPE_DEFAULTS[type] ?? { icon: '/icons/select.webp', label: type }
28
+ export function getTypeDisplay(type: string, i18n: PascalI18n): NodeDisplay {
29
+ const known = TYPE_DEFAULTS[type]
30
+ return known ? { icon: known.icon, label: i18n._(known.label) } : { icon: '/icons/select.webp', label: type }
29
31
  }
30
32
 
31
- export function getNodeDisplay(node: AnyNode | null | undefined): NodeDisplay {
32
- if (!node) return { icon: '/icons/select.webp', label: 'Selection' }
33
- const fallback = TYPE_DEFAULTS[node.type] ?? { icon: '/icons/select.webp', label: node.type }
33
+ export function getNodeDisplay(node: AnyNode | null | undefined, i18n: PascalI18n): NodeDisplay {
34
+ if (!node)
35
+ return {
36
+ icon: '/icons/select.webp',
37
+ label: i18n._({ id: 'editor.nodeDisplay.selection', message: 'Selection' }),
38
+ }
39
+ const fallback = getTypeDisplay(node.type, i18n)
34
40
  const scripted = scriptImages(node)?.thumbnail
35
41
  // Item nodes carry an asset with its own thumbnail/name
36
42
  if (node.type === 'item') {
@@ -40,7 +46,12 @@ export function getNodeDisplay(node: AnyNode | null | undefined): NodeDisplay {
40
46
  }
41
47
  }
42
48
  if (node.type === 'zone' && node.spaceRole === 'room') {
43
- return { icon: '/icons/kitchen.webp', label: node.name || 'Room' }
49
+ return {
50
+ icon: '/icons/kitchen.webp',
51
+ label:
52
+ (node.name && displayRoomName(node.name, i18n)) ||
53
+ i18n._({ id: 'editor.nodeDisplay.room', message: 'Room' }),
54
+ }
44
55
  }
45
56
  return {
46
57
  icon: scripted ?? fallback.icon,
@@ -11,6 +11,8 @@ import {
11
11
  type DormerNode,
12
12
  type ElevatorNode,
13
13
  type FenceNode,
14
+ isMoveLocked,
15
+ isNodeDuplicable,
14
16
  type ItemNode,
15
17
  type RoofNode,
16
18
  type RoofSegmentNode,
@@ -19,7 +21,7 @@ import {
19
21
  type StairSegmentNode,
20
22
  useScene,
21
23
  type WallNode,
22
- type WindowNode,
24
+ type WindowNode, usePascalI18n,
23
25
  } from '@pascal-app/core'
24
26
  import { useViewer } from '@pascal-app/viewer'
25
27
  import { useCallback, useEffect, useMemo, useState } from 'react'
@@ -107,6 +109,7 @@ function MobilePanelLayer({
107
109
  panel: React.ReactNode
108
110
  isReference: boolean
109
111
  }) {
112
+ const i18n = usePascalI18n()
110
113
  const setSelection = useViewer((s) => s.setSelection)
111
114
  const setSelectedReferenceId = useEditor((s) => s.setSelectedReferenceId)
112
115
  const setMovingNode = useEditor((s) => s.setMovingNode)
@@ -146,7 +149,7 @@ function MobilePanelLayer({
146
149
 
147
150
  if (!(node || isReference)) return null
148
151
 
149
- const display = getNodeDisplay(node)
152
+ const display = getNodeDisplay(node, i18n)
150
153
 
151
154
  return (
152
155
  <>
@@ -154,9 +157,9 @@ function MobilePanelLayer({
154
157
  <MobileSelectionBar
155
158
  node={node}
156
159
  onDelete={handleDelete}
157
- onDuplicate={handleDuplicate}
160
+ onDuplicate={isNodeDuplicable(node) ? handleDuplicate : undefined}
158
161
  onEdit={() => setIsSheetOpen((v) => !v)}
159
- onMove={handleMove}
162
+ onMove={isMoveLocked(node) ? undefined : handleMove}
160
163
  />
161
164
  )}
162
165
  <MobilePanelSheet
@@ -180,8 +183,9 @@ function MobileMultiPanelLayer({
180
183
  panel: React.ReactNode
181
184
  type: string | null
182
185
  }) {
186
+ const i18n = usePascalI18n()
183
187
  const [isSheetOpen, setIsSheetOpen] = useState(false)
184
- const display = type ? getTypeDisplay(type) : { icon: '/icons/select.webp', label: 'Selection' }
188
+ const display = type ? getTypeDisplay(type, i18n) : { icon: '/icons/select.webp', label: i18n._({ id: 'editor.panels.panelManager.selection', message: 'Selection' }) }
185
189
  const title = breakdown || display.label
186
190
 
187
191
  useEffect(() => {