@pascal-app/editor 1.0.6 → 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 +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 +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
@@ -24,11 +24,11 @@ import {
24
24
  sceneRegistry,
25
25
  summarizeSystemFor,
26
26
  useLiveNodeOverrides,
27
+ usePascalI18n,
27
28
  useScene,
28
29
  type WallNode,
29
30
  } from '@pascal-app/core'
30
31
  import { useViewer } from '@pascal-app/viewer'
31
- import { Html } from '@react-three/drei'
32
32
  import { useFrame } from '@react-three/fiber'
33
33
  import { useCallback, useMemo, useRef } from 'react'
34
34
  import * as THREE from 'three'
@@ -53,6 +53,7 @@ import useInteractionScope, {
53
53
  } from '../../store/use-interaction-scope'
54
54
  import { IconRefGlyph } from '../ui/icon-ref'
55
55
  import { duplicateNodeAndPickUp } from './duplicate-node'
56
+ import { Html } from './localized-html'
56
57
  import { formatMeasurement, MeasurementPill } from './measurement-pill'
57
58
  import { NodeActionMenu } from './node-action-menu'
58
59
  import { startZoneRoomTransform } from './room-controls'
@@ -296,6 +297,7 @@ function getHeightPillDimensions(node: WallNode | FenceNode): {
296
297
  }
297
298
 
298
299
  export function FloatingActionMenu() {
300
+ const i18n = usePascalI18n()
299
301
  const reducedMotion = useReducedMotion()
300
302
  const selectedIds = useViewer((s) => s.selection.selectedIds)
301
303
  const updateNode = useScene((s) => s.updateNode)
@@ -749,16 +751,21 @@ export function FloatingActionMenu() {
749
751
  {hasAxisCycling(node.type) ? (
750
752
  <div className="flex items-center gap-2 whitespace-nowrap rounded-full border border-border/60 bg-background/90 px-4 py-1.5 text-xs tabular-nums shadow-sm backdrop-blur">
751
753
  <span className="font-medium text-foreground">
752
- Axis {rotationAxis.toUpperCase()}
754
+ {i18n._({ id: 'editor.floatingActionMenu.axis', message: 'Axis' })}{' '}
755
+ {rotationAxis.toUpperCase()}
753
756
  </span>
754
757
  <span aria-hidden className="text-muted-foreground">
755
758
  ·
756
759
  </span>
757
- <span className="text-muted-foreground">R/T rotate</span>
760
+ <span className="text-muted-foreground">
761
+ {i18n._({ id: 'editor.floatingActionMenu.rTRotate', message: 'R/T rotate' })}
762
+ </span>
758
763
  <span aria-hidden className="text-muted-foreground">
759
764
  ·
760
765
  </span>
761
- <span className="text-muted-foreground">⌥ axis</span>
766
+ <span className="text-muted-foreground">
767
+ {i18n._({ id: 'editor.floatingActionMenu.axis2', message: '⌥ axis' })}
768
+ </span>
762
769
  </div>
763
770
  ) : null}
764
771
  </div>
@@ -788,6 +795,7 @@ function SystemSummaryPill({
788
795
  nodeId: AnyNodeId
789
796
  unit: 'metric' | 'imperial'
790
797
  }) {
798
+ const i18n = usePascalI18n()
791
799
  const allNodes = useScene((s) => s.nodes)
792
800
  const summary = useMemo(() => summarizeSystemFor(nodeId, allNodes), [nodeId, allNodes])
793
801
  if (!summary) return null
@@ -796,7 +804,7 @@ function SystemSummaryPill({
796
804
  <span className="font-medium text-foreground">
797
805
  {summary.systems.length > 0
798
806
  ? summary.systems.map((sys) => sys[0]!.toUpperCase() + sys.slice(1)).join(' + ')
799
- : 'System'}
807
+ : i18n._({ id: 'editor.floatingActionMenu.system', message: 'System' })}
800
808
  </span>
801
809
  {summary.runCount > 0 ? (
802
810
  <>
@@ -804,8 +812,12 @@ function SystemSummaryPill({
804
812
  ·
805
813
  </span>
806
814
  <span className="text-muted-foreground">
807
- {formatMeasurement(summary.runLengthM, unit, metricNotation)} · {summary.runCount}{' '}
808
- {summary.runCount === 1 ? 'run' : 'runs'}
815
+ {formatMeasurement(summary.runLengthM, unit, metricNotation)} ·{' '}
816
+ {i18n._({
817
+ id: 'editor.floatingActionMenu.runCount',
818
+ message: '{count, plural, one {{number} run} other {{number} runs}}',
819
+ values: { count: summary.runCount, number: String(summary.runCount) },
820
+ })}
809
821
  </span>
810
822
  </>
811
823
  ) : null}
@@ -815,7 +827,11 @@ function SystemSummaryPill({
815
827
  ·
816
828
  </span>
817
829
  <span className="text-muted-foreground">
818
- {summary.terminalCount} {summary.terminalCount === 1 ? 'register' : 'registers'}
830
+ {i18n._({
831
+ id: 'editor.floatingActionMenu.registerCount',
832
+ message: '{count, plural, one {# register} other {# registers}}',
833
+ values: { count: summary.terminalCount },
834
+ })}
819
835
  </span>
820
836
  </>
821
837
  ) : null}
@@ -824,7 +840,9 @@ function SystemSummaryPill({
824
840
  <span aria-hidden className="text-muted-foreground">
825
841
  ·
826
842
  </span>
827
- <span className="font-medium text-amber-500">⚠ no equipment</span>
843
+ <span className="font-medium text-amber-500">
844
+ {i18n._({ id: 'editor.floatingActionMenu.noEquipment', message: '⚠ no equipment' })}
845
+ </span>
828
846
  </>
829
847
  )}
830
848
  </div>
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { type BuildingNode, sceneRegistry, useScene } from '@pascal-app/core'
4
4
  import { useViewer } from '@pascal-app/viewer'
5
- import { Html } from '@react-three/drei'
6
5
  import { useFrame } from '@react-three/fiber'
7
6
  import { useCallback, useRef } from 'react'
8
7
  import * as THREE from 'three'
9
8
  import { sfxEmitter } from '../../lib/sfx-bus'
10
9
  import useEditor from '../../store/use-editor'
10
+ import { Html } from './localized-html'
11
11
  import { NodeActionMenu } from './node-action-menu'
12
12
 
13
13
  export function FloatingBuildingActionMenu() {
@@ -0,0 +1,220 @@
1
+ import { expect, test } from 'bun:test'
2
+ import { GuideScaleReference } from '@pascal-app/core'
3
+ import { z } from 'zod'
4
+ import { createGuideScaleReference, formatReferenceScaleLabel } from '../../lib/reference-scale'
5
+ import {
6
+ formatFloorplanNumber,
7
+ getReferenceScaleInputError,
8
+ parseReferenceScaleLength,
9
+ } from './floorplan-panel'
10
+
11
+ const Core106GuideScaleReference = z.object({
12
+ start: z.tuple([z.number(), z.number()]),
13
+ end: z.tuple([z.number(), z.number()]),
14
+ realLengthMeters: z.number().positive(),
15
+ measuredLengthUnits: z.number().positive(),
16
+ metersPerUnit: z.number().positive(),
17
+ label: z.string(),
18
+ })
19
+
20
+ function runtimeScaleReference(overrides: Record<string, unknown> = {}): GuideScaleReference {
21
+ return {
22
+ start: [0, 0],
23
+ end: [1, 0],
24
+ realLengthMeters: 2.75,
25
+ measuredLengthUnits: 1,
26
+ metersPerUnit: 2.75,
27
+ label: '2.75 m',
28
+ ...overrides,
29
+ } as unknown as GuideScaleReference
30
+ }
31
+
32
+ test('floor-plan reference scales parse with the scoped locale', () => {
33
+ expect(parseReferenceScaleLength('1,000', 'meters', 'fr')).toBe(1)
34
+ expect(parseReferenceScaleLength('1,000', 'meters', 'en')).toBe(1000)
35
+ })
36
+
37
+ test('floor-plan reference scales display with the scoped locale', () => {
38
+ expect(formatFloorplanNumber(2.75, 'fr')).toBe('2,75')
39
+ expect(formatFloorplanNumber(2.75, 'en')).toBe('2.75')
40
+ })
41
+
42
+ test('reference-scale scenes remain compatible and format numeric and legacy labels', () => {
43
+ const reference = createGuideScaleReference(
44
+ {
45
+ start: [0, 0],
46
+ end: [1, 0],
47
+ realLengthMeters: 2.75,
48
+ measuredLengthUnits: 1,
49
+ metersPerUnit: 2.75,
50
+ },
51
+ 2.75,
52
+ 'meters',
53
+ )
54
+
55
+ expect(reference).toEqual({
56
+ start: [0, 0],
57
+ end: [1, 0],
58
+ realLengthMeters: 2.75,
59
+ measuredLengthUnits: 1,
60
+ metersPerUnit: 2.75,
61
+ displayLength: 2.75,
62
+ displayUnit: 'meters',
63
+ label: '2.75 m',
64
+ })
65
+ expect(Core106GuideScaleReference.safeParse(reference).success).toBe(true)
66
+ expect(formatReferenceScaleLabel(reference, 'fr')).toBe('2,75 m')
67
+ expect(formatReferenceScaleLabel(reference, 'en')).toBe('2.75 m')
68
+ const legacyFrenchReference = GuideScaleReference.parse({
69
+ start: [0, 0],
70
+ end: [1, 0],
71
+ realLengthMeters: 2.75,
72
+ measuredLengthUnits: 1,
73
+ metersPerUnit: 2.75,
74
+ label: '2,75 m',
75
+ })
76
+ expect(formatReferenceScaleLabel(legacyFrenchReference, 'en')).toBe('2.75 m')
77
+ expect(
78
+ formatReferenceScaleLabel(
79
+ GuideScaleReference.parse({
80
+ ...legacyFrenchReference,
81
+ label: '2.75 m',
82
+ }),
83
+ 'fr',
84
+ ),
85
+ ).toBe('2,75 m')
86
+ expect(
87
+ formatReferenceScaleLabel(
88
+ GuideScaleReference.parse({
89
+ ...legacyFrenchReference,
90
+ label: 'Known span',
91
+ }),
92
+ 'fr',
93
+ ),
94
+ ).toBe('Known span')
95
+ })
96
+
97
+ test('oversized legacy reference labels bypass parsing within the input budget', () => {
98
+ const label = `${'1, '.repeat(34_000)}?`
99
+ const reference = runtimeScaleReference({ label })
100
+ const startedAt = performance.now()
101
+ const formatted = formatReferenceScaleLabel(reference, 'en')
102
+ const elapsed = performance.now() - startedAt
103
+
104
+ expect(formatted).toBe(label)
105
+ expect(elapsed).toBeLessThan(50)
106
+ })
107
+
108
+ test('reference-scale labels tolerate malformed persisted values', () => {
109
+ expect(formatReferenceScaleLabel(runtimeScaleReference({ label: 42 }), 'en')).toBe('42')
110
+ expect(formatReferenceScaleLabel(runtimeScaleReference({ label: null }), 'en')).toBe('')
111
+ expect(
112
+ formatReferenceScaleLabel(runtimeScaleReference({ label: { toString: null } }), 'en'),
113
+ ).toBe('')
114
+ expect(
115
+ formatReferenceScaleLabel(runtimeScaleReference({ label: Object.create(null) }), 'en'),
116
+ ).toBe('')
117
+ expect(formatReferenceScaleLabel(runtimeScaleReference({ label: Symbol('x') }), 'en')).toBe('')
118
+ expect(formatReferenceScaleLabel(runtimeScaleReference({ label: ['1 m'] }), 'en')).toBe('')
119
+ expect(
120
+ formatReferenceScaleLabel(
121
+ runtimeScaleReference({
122
+ displayLength: 2.75,
123
+ displayUnit: '__proto__',
124
+ label: 'fallback',
125
+ }),
126
+ 'en',
127
+ ),
128
+ ).toBe('fallback')
129
+ expect(
130
+ formatReferenceScaleLabel(
131
+ runtimeScaleReference({
132
+ displayLength: 2.75,
133
+ displayUnit: 'constructor',
134
+ label: 'fallback',
135
+ }),
136
+ 'en',
137
+ ),
138
+ ).toBe('fallback')
139
+ expect(
140
+ formatReferenceScaleLabel(
141
+ runtimeScaleReference({
142
+ displayLength: Number.POSITIVE_INFINITY,
143
+ displayUnit: 'meters',
144
+ label: 'fallback',
145
+ }),
146
+ 'en',
147
+ ),
148
+ ).toBe('fallback')
149
+ expect(
150
+ formatReferenceScaleLabel(
151
+ runtimeScaleReference({
152
+ displayLength: Number.NaN,
153
+ displayUnit: 'meters',
154
+ label: 'fallback',
155
+ }),
156
+ 'en',
157
+ ),
158
+ ).toBe('fallback')
159
+ })
160
+
161
+ test('reference-scale schema caps persisted display lengths', () => {
162
+ expect(
163
+ GuideScaleReference.safeParse(
164
+ runtimeScaleReference({
165
+ displayLength: 1_000_000,
166
+ displayUnit: 'meters',
167
+ }),
168
+ ).success,
169
+ ).toBe(true)
170
+ expect(
171
+ GuideScaleReference.safeParse(
172
+ runtimeScaleReference({
173
+ displayLength: 1_000_001,
174
+ displayUnit: 'meters',
175
+ }),
176
+ ).success,
177
+ ).toBe(false)
178
+ })
179
+
180
+ test('reference-scale calibration rejects lengths the schema would not accept', () => {
181
+ expect(getReferenceScaleInputError('1000000', 'meters', 'en')).toBeNull()
182
+ expect(getReferenceScaleInputError('2.75', 'feet', 'en')).toBeNull()
183
+ expect(getReferenceScaleInputError('1000001', 'meters', 'en')).toBe(
184
+ 'Length must be at most 1000000 m.',
185
+ )
186
+ expect(getReferenceScaleInputError('1e9', 'centimeters', 'en')).toBe(
187
+ 'Length must be at most 1000000 cm.',
188
+ )
189
+ expect(getReferenceScaleInputError('0', 'meters', 'en')).toBe('Length must be greater than 0.')
190
+ expect(getReferenceScaleInputError('', 'meters', 'en')).toBe('Enter the real length of the line.')
191
+ })
192
+
193
+ test('reference-scale creation drops display fields outside the schema range', () => {
194
+ const base = {
195
+ start: [0, 0] as [number, number],
196
+ end: [1, 0] as [number, number],
197
+ realLengthMeters: 2_000_000,
198
+ measuredLengthUnits: 1,
199
+ metersPerUnit: 2_000_000,
200
+ }
201
+ const oversized = createGuideScaleReference(base, 2_000_000, 'meters')
202
+ expect(oversized.displayLength).toBeUndefined()
203
+ expect(oversized.displayUnit).toBeUndefined()
204
+ expect(oversized.label).toBe('2000000 m')
205
+ expect(GuideScaleReference.safeParse(oversized).success).toBe(true)
206
+
207
+ const atCap = createGuideScaleReference(base, 1_000_000, 'meters')
208
+ expect(atCap.displayLength).toBe(1_000_000)
209
+ expect(GuideScaleReference.safeParse(atCap).success).toBe(true)
210
+ })
211
+
212
+ test('legacy reference-scale labels accept whitespace runs between digit groups', () => {
213
+ const label = (value: string) =>
214
+ formatReferenceScaleLabel(runtimeScaleReference({ label: value }), 'en')
215
+ expect(label('1 234,5 m')).toBe('1234.5 m')
216
+ expect(label('1 \u00a0234 cm')).toBe('1234 cm')
217
+ expect(label('12\u202f345\t\t678 ft')).toBe('12345678 ft')
218
+ expect(label('1 234 m')).toBe('1234 m')
219
+ expect(label('1 234 ')).toBe('1 234 ')
220
+ })