@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
@@ -3,12 +3,17 @@
3
3
  import { Icon } from '@iconify/react'
4
4
  import {
5
5
  acquireSceneReadOnlyLease,
6
+ createI18n,
6
7
  getCatalogMaterialById,
7
8
  getLibraryMaterialIdFromRef,
9
+ getMaterialSwatch,
8
10
  getSceneMaterialIdFromRef,
9
11
  initSpaceDetectionSync,
10
12
  initSpatialGridSync,
13
+ type MessageDescriptor,
14
+ PascalI18nProvider,
11
15
  spatialGridManager,
16
+ usePascalI18n,
12
17
  useScene,
13
18
  } from '@pascal-app/core'
14
19
  import {
@@ -32,6 +37,7 @@ import {
32
37
  useCallback,
33
38
  useEffect,
34
39
  useLayoutEffect,
40
+ useMemo,
35
41
  useRef,
36
42
  useState,
37
43
  } from 'react'
@@ -41,6 +47,7 @@ import { type SaveStatus, useAutoSave } from '../../hooks/use-auto-save'
41
47
  import { useKeyboard } from '../../hooks/use-keyboard'
42
48
  import { useIsMobile } from '../../hooks/use-mobile'
43
49
  import { useSaveShortcut } from '../../hooks/use-save-shortcut'
50
+ import { type HINT_KEY_NAMES, toolHintKey, toolHintKeyDescriptor } from '../../i18n/node-messages'
44
51
  import { useCeilingEditSessionOwner } from '../../lib/ceiling-edit-session'
45
52
  import { showsWholeBuilding, useEditorLevelDisplay } from '../../lib/editor-level-display'
46
53
  import { isSceneView } from '../../lib/editor-views'
@@ -71,7 +78,7 @@ import useSessionGroups from '../../store/use-session-groups'
71
78
  import { paneOfView, VIEW_2D, VIEW_3D, type ViewPaneIndex } from '../../store/view-layout'
72
79
  import { PointAskLayer } from '../point-ask/point-ask-layer'
73
80
  import { CeilingSelectionAffordanceSystem } from '../systems/ceiling/ceiling-selection-affordance-system'
74
- import { CeilingSystem } from '../systems/ceiling/ceiling-system'
81
+ import { CeilingSystem, CeilingTopsSync } from '../systems/ceiling/ceiling-system'
75
82
  import { RoofEditSystem } from '../systems/roof/roof-edit-system'
76
83
  import { SelectionAffordanceManager } from '../systems/selection-affordance-manager'
77
84
  import { StairEditSystem } from '../systems/stair/stair-edit-system'
@@ -96,6 +103,8 @@ import { SettingsPanel, type SettingsPanelProps } from '../ui/sidebar/panels/set
96
103
  import { SitePanel, type SitePanelProps } from '../ui/sidebar/panels/site-panel'
97
104
  import type { SidebarTab } from '../ui/sidebar/tab-bar'
98
105
  import { useHostPanels } from '../ui/sidebar/use-plugin-panels'
106
+ import { SKY_DOCK_HEIGHT, SkyControl } from '../viewer/sky-control'
107
+ import { useViewerUiSounds } from '../viewer/use-viewer-ui-sounds'
99
108
  import { ViewerHoverLabel } from '../viewer/viewer-hover-label'
100
109
  import { ParametricViewerRooms } from '../viewer/viewer-rooms'
101
110
  import { ViewerSelectionManager } from '../viewer/viewer-selection-manager'
@@ -202,6 +211,8 @@ function initializeEditorRuntime(): () => void {
202
211
  }
203
212
  }
204
213
  export interface EditorProps {
214
+ /** UI locale. Hosts may also call `setLocale` before mounting. */
215
+ locale?: string
205
216
  // Layout version — 'v1' (default) or 'v2' (navbar + two-column)
206
217
  layoutVersion?: 'v1' | 'v2'
207
218
  // Viewport edge the tool menu docks to (desktop only; mobile stays at the bottom)
@@ -315,12 +326,22 @@ export interface EditorProps {
315
326
  }
316
327
 
317
328
  function EditorSceneCrashFallback() {
329
+ const i18n = usePascalI18n()
318
330
  return (
319
331
  <div className="fixed inset-0 z-80 flex items-center justify-center bg-background/95 p-4 text-foreground">
320
332
  <div className="w-full max-w-md rounded-2xl border border-border/60 bg-background p-6 shadow-xl">
321
- <h2 className="font-semibold text-lg">The editor scene failed to render</h2>
333
+ <h2 className="font-semibold text-lg">
334
+ {i18n._({
335
+ id: 'editor.theEditorSceneFailedToRender',
336
+ message: 'The editor scene failed to render',
337
+ })}
338
+ </h2>
322
339
  <p className="mt-2 text-muted-foreground text-sm">
323
- You can retry the scene or return home without reloading the whole app shell.
340
+ {i18n._({
341
+ id: 'editor.youCanRetryTheSceneOr',
342
+ message:
343
+ 'You can retry the scene or return home without reloading the whole app shell.',
344
+ })}
324
345
  </p>
325
346
  <div className="mt-4 flex items-center gap-2">
326
347
  <button
@@ -328,13 +349,13 @@ function EditorSceneCrashFallback() {
328
349
  onClick={() => window.location.reload()}
329
350
  type="button"
330
351
  >
331
- Reload editor
352
+ {i18n._({ id: 'editor.reloadEditor', message: 'Reload editor' })}
332
353
  </button>
333
354
  <a
334
355
  className="rounded-md border border-border bg-background px-3 py-2 font-medium text-sm hover:bg-accent/40"
335
356
  href="/"
336
357
  >
337
- Back to home
358
+ {i18n._({ id: 'editor.backToHome', message: 'Back to home' })}
338
359
  </a>
339
360
  </div>
340
361
  </div>
@@ -345,6 +366,7 @@ function EditorSceneCrashFallback() {
345
366
  // ── Sidebar slot: in-flow, resizable, collapses to a grab strip ──────────────
346
367
 
347
368
  function SidebarSlot({ children }: { children: ReactNode }) {
369
+ const i18n = usePascalI18n()
348
370
  const width = useSidebarStore((s) => s.width)
349
371
  const isCollapsed = useSidebarStore((s) => s.isCollapsed)
350
372
  const setIsCollapsed = useSidebarStore((s) => s.setIsCollapsed)
@@ -416,7 +438,7 @@ function SidebarSlot({ children }: { children: ReactNode }) {
416
438
  <div
417
439
  className="absolute inset-0 z-10 cursor-col-resize transition-colors hover:bg-primary/20"
418
440
  onPointerDown={handleGrabDown}
419
- title="Expand sidebar"
441
+ title={i18n._({ id: 'editor.expandSidebar', message: 'Expand sidebar' })}
420
442
  />
421
443
  ) : (
422
444
  children
@@ -494,26 +516,29 @@ const CAMERA_CONTROL_HINTS: CameraControlHint[] = [
494
516
  { action: 'Zoom', keys: [{ value: 'Scroll' }] },
495
517
  ]
496
518
 
497
- const CAMERA_SHORTCUT_KEY_META: Record<string, { icon?: string; label: string; text?: string }> = {
519
+ const CAMERA_SHORTCUT_KEY_META: Record<
520
+ string,
521
+ { icon?: string; label: MessageDescriptor; text?: string }
522
+ > = {
498
523
  'Left click': {
499
524
  icon: 'ph:mouse-left-click-fill',
500
- label: 'Left click',
525
+ label: hintKeyName('Left click'),
501
526
  },
502
527
  'Middle click': {
503
528
  icon: 'qlementine-icons:mouse-middle-button-16',
504
- label: 'Middle click',
529
+ label: hintKeyName('Middle click'),
505
530
  },
506
531
  'Right click': {
507
532
  icon: 'ph:mouse-right-click-fill',
508
- label: 'Right click',
533
+ label: hintKeyName('Right click'),
509
534
  },
510
535
  Scroll: {
511
536
  icon: 'qlementine-icons:mouse-middle-button-16',
512
- label: 'Scroll wheel',
537
+ label: hintKeyName('Scroll wheel'),
513
538
  },
514
539
  Space: {
515
540
  icon: 'lucide:space',
516
- label: 'Space',
541
+ label: hintKeyName('Space'),
517
542
  },
518
543
  }
519
544
 
@@ -544,8 +569,21 @@ function writeCameraControlsHintDismissed(dismissed: boolean) {
544
569
  } catch {}
545
570
  }
546
571
 
572
+ /** Key names shared with the tool HUD (`editor.toolHint.key.*`). */
573
+ function hintKeyName(key: (typeof HINT_KEY_NAMES)[number]): MessageDescriptor {
574
+ return toolHintKeyDescriptor(key) ?? { id: key, message: key }
575
+ }
576
+
577
+ const CAMERA_HINT_ACTION_LABELS: Record<CameraHintAction, MessageDescriptor> = {
578
+ Pan: /* i18n */ { id: 'editor.cameraHint.pan', message: 'Pan' },
579
+ Rotate: /* i18n */ { id: 'editor.cameraHint.rotate', message: 'Rotate' },
580
+ Zoom: /* i18n */ { id: 'editor.cameraHint.zoom', message: 'Zoom' },
581
+ }
582
+
547
583
  function InlineShortcutKey({ shortcutKey }: { shortcutKey: ShortcutKey }) {
548
- const meta = CAMERA_SHORTCUT_KEY_META[shortcutKey.value]
584
+ const i18n = usePascalI18n()
585
+ const entry = CAMERA_SHORTCUT_KEY_META[shortcutKey.value]
586
+ const meta = entry ? { ...entry, label: i18n._(entry.label) } : undefined
549
587
 
550
588
  if (meta?.icon) {
551
589
  return (
@@ -563,7 +601,7 @@ function InlineShortcutKey({ shortcutKey }: { shortcutKey: ShortcutKey }) {
563
601
 
564
602
  return (
565
603
  <span className="font-medium text-[11px] text-foreground/90">
566
- {meta?.text ?? shortcutKey.value}
604
+ {meta?.text ?? toolHintKey(shortcutKey.value, i18n)}
567
605
  </span>
568
606
  )
569
607
  }
@@ -582,10 +620,11 @@ function ShortcutSequence({ keys }: { keys: ShortcutKey[] }) {
582
620
  }
583
621
 
584
622
  function CameraControlHintItem({ hint }: { hint: CameraControlHint }) {
623
+ const i18n = usePascalI18n()
585
624
  return (
586
625
  <div className="flex min-w-0 flex-col items-center gap-1.5 px-4 text-center first:pl-0 last:pr-0">
587
626
  <span className="font-medium text-[10px] text-muted-foreground/60 tracking-[0.03em]">
588
- {hint.action}
627
+ {i18n._(CAMERA_HINT_ACTION_LABELS[hint.action])}
589
628
  </span>
590
629
  <div className="flex flex-wrap items-center justify-center gap-1.5">
591
630
  <ShortcutSequence keys={hint.keys} />
@@ -607,6 +646,7 @@ function ViewerCanvasControlsHint({
607
646
  belowTopMenu?: boolean
608
647
  onDismiss: () => void
609
648
  }) {
649
+ const i18n = usePascalI18n()
610
650
  const all = CAMERA_CONTROL_HINTS
611
651
  // A host teaching one gesture at a time narrows this to the one it is asking
612
652
  // for, and to nothing once it is done. Null — the default — is all of them.
@@ -626,7 +666,7 @@ function ViewerCanvasControlsHint({
626
666
  style={belowTopMenu ? { top: TOP_MENU_CLEARANCE } : undefined}
627
667
  >
628
668
  <section
629
- aria-label="Camera controls hint"
669
+ aria-label={i18n._({ id: 'editor.cameraControlsHint', message: 'Camera controls hint' })}
630
670
  className="pointer-events-auto flex items-start gap-3 rounded-2xl border border-border/35 bg-background/90 px-3.5 py-2.5 shadow-elevation-4 backdrop-blur-xl"
631
671
  >
632
672
  <div
@@ -640,7 +680,10 @@ function ViewerCanvasControlsHint({
640
680
  <Tooltip>
641
681
  <TooltipTrigger asChild>
642
682
  <button
643
- aria-label="Dismiss camera controls hint"
683
+ aria-label={i18n._({
684
+ id: 'editor.dismissCameraControlsHint',
685
+ message: 'Dismiss camera controls hint',
686
+ })}
644
687
  className="flex h-5 shrink-0 items-center justify-center self-center border-border/18 border-l pl-3 text-muted-foreground/70 transition-colors hover:text-foreground"
645
688
  onClick={onDismiss}
646
689
  type="button"
@@ -655,7 +698,7 @@ function ViewerCanvasControlsHint({
655
698
  </button>
656
699
  </TooltipTrigger>
657
700
  <TooltipContent side="bottom" sideOffset={8}>
658
- Dismiss
701
+ {i18n._({ id: 'editor.dismiss', message: 'Dismiss' })}
659
702
  </TooltipContent>
660
703
  </Tooltip>
661
704
  </section>
@@ -733,7 +776,17 @@ function getActivePaintMaterialSwatchImageUrl(
733
776
  const catalogId =
734
777
  getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
735
778
  const catalogMaterial = getCatalogMaterialById(catalogId)
736
- return catalogMaterial?.previewThumbnailUrl ?? catalogMaterial?.preset.maps.albedoMap
779
+ return catalogMaterial ? (getMaterialSwatch(catalogMaterial).imageUrl ?? undefined) : undefined
780
+ }
781
+
782
+ /** Whether the badge's image is a white albedo its colour tints (a siding's profile). */
783
+ function isActivePaintMaterialSwatchTinted(material: ActivePaintMaterial | null) {
784
+ if (material?.material?.texture?.url || getSceneMaterialIdFromRef(material?.materialPreset))
785
+ return false
786
+ const catalogId =
787
+ getLibraryMaterialIdFromRef(material?.materialPreset) ?? material?.material?.id ?? undefined
788
+ const catalogMaterial = getCatalogMaterialById(catalogId)
789
+ return catalogMaterial ? getMaterialSwatch(catalogMaterial).tinted : false
737
790
  }
738
791
 
739
792
  function PaintCursorBadge({
@@ -741,6 +794,7 @@ function PaintCursorBadge({
741
794
  state,
742
795
  swatchColor,
743
796
  swatchImageUrl,
797
+ swatchTinted = false,
744
798
  isEraser,
745
799
  isPicker = false,
746
800
  }: {
@@ -748,6 +802,8 @@ function PaintCursorBadge({
748
802
  state: PaintCursorBadgeState
749
803
  swatchColor: string
750
804
  swatchImageUrl?: string
805
+ /** Multiply the image by the colour (a tinted albedo). */
806
+ swatchTinted?: boolean
751
807
  isEraser: boolean
752
808
  /** The eyedropper: the swatch is what a click would take. */
753
809
  isPicker?: boolean
@@ -821,6 +877,7 @@ function PaintCursorBadge({
821
877
  backgroundImage: swatchImageUrl
822
878
  ? `url(${JSON.stringify(swatchImageUrl)})`
823
879
  : undefined,
880
+ backgroundBlendMode: swatchImageUrl && swatchTinted ? 'multiply' : undefined,
824
881
  opacity: state === 'aim' ? 0.55 : 1,
825
882
  }}
826
883
  />
@@ -1084,6 +1141,7 @@ function PaintCursorLayer({
1084
1141
  const swatchMaterial = picking ? picked : activePaintMaterial
1085
1142
  const swatchColor = getActivePaintMaterialSwatchColor(swatchMaterial, sceneMaterials)
1086
1143
  const swatchImageUrl = getActivePaintMaterialSwatchImageUrl(swatchMaterial, sceneMaterials)
1144
+ const swatchTinted = isActivePaintMaterialSwatchTinted(swatchMaterial)
1087
1145
 
1088
1146
  if (!active || !position) return null
1089
1147
 
@@ -1099,6 +1157,7 @@ function PaintCursorLayer({
1099
1157
  state={badgeState}
1100
1158
  swatchColor={swatchColor}
1101
1159
  swatchImageUrl={swatchImageUrl}
1160
+ swatchTinted={swatchTinted}
1102
1161
  />
1103
1162
  </div>
1104
1163
  )
@@ -1150,6 +1209,9 @@ const ViewerCanvas = memo(function ViewerCanvas({
1150
1209
  const views = useEditorViews()
1151
1210
  const isPreviewMode = useEditor((s) => s.isPreviewMode)
1152
1211
  const isCaptureMode = useEditor((s) => s.isCaptureMode)
1212
+ const textures = useViewer((s) => s.textures)
1213
+ const isMobile = useIsMobile()
1214
+ const planOnRight = layout.split && layout.panes[1] === VIEW_2D
1153
1215
  useUnitFocusRules()
1154
1216
  const wholeBuilding = useEditor((s) =>
1155
1217
  showsWholeBuilding({
@@ -1319,6 +1381,22 @@ const ViewerCanvas = memo(function ViewerCanvas({
1319
1381
  viewerSceneSlot={viewerSceneSlot}
1320
1382
  />
1321
1383
  </Viewer>
1384
+ {!showLoader &&
1385
+ textures &&
1386
+ !isMobile &&
1387
+ !(
1388
+ isFirstPersonMode ||
1389
+ isStudioMode ||
1390
+ isVersionPreviewMode ||
1391
+ isCaptureMode ||
1392
+ isPreviewMode
1393
+ ) ? (
1394
+ // Beside the compass (which moves to the plan's corner when the plan is on the right).
1395
+ <SkyControl
1396
+ height={SKY_DOCK_HEIGHT.editor}
1397
+ style={{ left: planOnRight ? 12 : 52 }}
1398
+ />
1399
+ ) : null}
1322
1400
  </div>
1323
1401
  {showLoader && loadingOverlay}
1324
1402
  </div>
@@ -1327,6 +1405,7 @@ const ViewerCanvas = memo(function ViewerCanvas({
1327
1405
  const viewId = layout.panes[pane]
1328
1406
  return (
1329
1407
  <div
1408
+ aria-busy={isSceneDataLoading}
1330
1409
  className="relative min-w-0 overflow-hidden"
1331
1410
  data-view-pane={pane}
1332
1411
  key={viewId}
@@ -1335,6 +1414,9 @@ const ViewerCanvas = memo(function ViewerCanvas({
1335
1414
  style={paneStyle(pane)}
1336
1415
  >
1337
1416
  <HostView key={viewId} view={views.find((view) => view.id === viewId)} />
1417
+ {/* A restored pane's plugin is known only once the project has loaded: until
1418
+ then it would read "This view isn't available". */}
1419
+ {isSceneDataLoading && loadingOverlay}
1338
1420
  </div>
1339
1421
  )
1340
1422
  })}
@@ -1384,6 +1466,7 @@ function PreviewStage({
1384
1466
  )
1385
1467
 
1386
1468
  useViewerFloorDisplay()
1469
+ useViewerUiSounds()
1387
1470
  const stageMode = isFirstPersonMode || !hasFloorplan ? '3d' : mode
1388
1471
  const stageModes = hasFloorplan && !isFirstPersonMode ? undefined : (['3d'] as const)
1389
1472
 
@@ -1714,6 +1797,13 @@ function EditorContent({
1714
1797
  }
1715
1798
  }, [isFirstPersonMode, isPreviewMode])
1716
1799
 
1800
+ // The viewer's walkthrough says how the first-person camera moves, for the
1801
+ // viewer-side consumers that tell a walk from a drone flight.
1802
+ const firstPersonMovementMode = useEditor((s) => s.firstPersonMovementMode)
1803
+ useEffect(() => {
1804
+ if (isFirstPersonMode) useViewer.getState().setWalkthroughMovement(firstPersonMovementMode)
1805
+ }, [isFirstPersonMode, firstPersonMovementMode])
1806
+
1717
1807
  const previewViewerContent = (
1718
1808
  <Viewer
1719
1809
  defaultRender={EDITOR_DEFAULT_RENDER}
@@ -1826,6 +1916,7 @@ function EditorContent({
1826
1916
  return (
1827
1917
  <>
1828
1918
  <FloorplanModeCoordinator />
1919
+ <CeilingTopsSync />
1829
1920
  {!isLoading && isPreviewMode ? (
1830
1921
  <PreviewStage
1831
1922
  isFirstPersonMode={isFirstPersonMode}
@@ -1912,6 +2003,7 @@ function EditorContent({
1912
2003
  return (
1913
2004
  <div className="flex h-full w-full gap-3 bg-neutral-100 p-3 text-foreground">
1914
2005
  <FloorplanModeCoordinator />
2006
+ <CeilingTopsSync />
1915
2007
  {!isLoading && isPreviewMode ? (
1916
2008
  <PreviewStage
1917
2009
  isFirstPersonMode={isFirstPersonMode}
@@ -1960,10 +2052,28 @@ function EditorContent({
1960
2052
  )
1961
2053
  }
1962
2054
 
1963
- export default function Editor(props: EditorProps) {
2055
+ function EditorFrame(props: EditorProps) {
1964
2056
  return (
1965
2057
  <Profiler id="editor" onRender={recordEditorRender}>
1966
2058
  <EditorContent {...props} />
1967
2059
  </Profiler>
1968
2060
  )
1969
2061
  }
2062
+
2063
+ function LocalizedEditor(props: EditorProps & { locale: string }) {
2064
+ const i18n = useMemo(() => createI18n({ locale: props.locale }), [props.locale])
2065
+
2066
+ return (
2067
+ <PascalI18nProvider i18n={i18n}>
2068
+ <EditorFrame {...props} />
2069
+ </PascalI18nProvider>
2070
+ )
2071
+ }
2072
+
2073
+ export default function Editor(props: EditorProps) {
2074
+ return props.locale ? (
2075
+ <LocalizedEditor {...props} locale={props.locale} />
2076
+ ) : (
2077
+ <EditorFrame {...props} />
2078
+ )
2079
+ }
@@ -0,0 +1,36 @@
1
+ 'use client'
2
+
3
+ import { useIsMobile } from '../../hooks/use-mobile'
4
+ import { openExtension } from '../../lib/inspector-card-mode'
5
+ import { useInspectorExpanded } from '../../lib/inspector-expanded'
6
+ import { renderExtensionIcon, useInspectorExtensions } from '../ui/panels/inspector-extensions'
7
+ import { ActionMenuButton } from './action-menu-button'
8
+
9
+ /**
10
+ * One action-menu button per plugin inspector extension of the selected node
11
+ * (`Plugin.inspectorExtensions`, e.g. a wall's Engineering): it opens the
12
+ * inspector card on that extension, or keeps it there. The extension that owns
13
+ * the card body for this node (`primaryWhen`) is already showing and gets none;
14
+ * the mobile sheet lists every extension under the kind's controls instead.
15
+ */
16
+ export function InspectorExtensionActions() {
17
+ const isMobile = useIsMobile()
18
+ const { extensions, primaryExtension } = useInspectorExtensions()
19
+ const active = useInspectorExpanded((state) => (state.expanded ? state.activeExtensionId : null))
20
+ if (isMobile) return null
21
+ return extensions
22
+ .filter((extension) => extension !== primaryExtension)
23
+ .map((extension) => (
24
+ <ActionMenuButton
25
+ key={extension.id}
26
+ label={extension.title}
27
+ onClick={(event) => {
28
+ event.stopPropagation()
29
+ useInspectorExpanded.getState().applyMode(openExtension(extension.id))
30
+ }}
31
+ pressed={active === extension.id}
32
+ >
33
+ {renderExtensionIcon(extension.icon)}
34
+ </ActionMenuButton>
35
+ ))
36
+ }
@@ -0,0 +1,22 @@
1
+ 'use client'
2
+
3
+ import { PascalI18nProvider, usePascalI18n } from '@pascal-app/core'
4
+ import { Html as DreiHtml } from '@react-three/drei'
5
+ import { type ComponentProps, forwardRef } from 'react'
6
+
7
+ /**
8
+ * drei's `Html`, keeping the editor's i18n scope. `Html` renders its children
9
+ * in a separate React root, which sees none of the surrounding context, so a
10
+ * component inside it would translate with the default (English) scope instead
11
+ * of the editor's `?lang=` or `<Editor locale>` one.
12
+ */
13
+ export const Html = forwardRef<HTMLDivElement, Omit<ComponentProps<typeof DreiHtml>, 'ref'>>(
14
+ function Html({ children, ...props }, ref) {
15
+ const i18n = usePascalI18n()
16
+ return (
17
+ <DreiHtml {...props} ref={ref}>
18
+ <PascalI18nProvider i18n={i18n}>{children}</PascalI18nProvider>
19
+ </DreiHtml>
20
+ )
21
+ },
22
+ )
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import { Icon } from '@iconify/react'
4
+ import { usePascalI18n } from '@pascal-app/core'
4
5
  import {
5
6
  Copy,
6
7
  Group,
@@ -65,70 +66,103 @@ export function NodeActionMenu({
65
66
  onPointerEnter,
66
67
  onPointerLeave,
67
68
  children,
68
- deleteLabel = 'Delete',
69
+ deleteLabel,
69
70
  deleteDisabledReason,
70
71
  }: NodeActionMenuProps) {
72
+ const i18n = usePascalI18n()
71
73
  return (
72
74
  <div
73
75
  className="pointer-events-auto flex items-center gap-1 rounded-lg border border-border bg-background/95 p-1 shadow-xl backdrop-blur-md"
76
+ data-node-action-menu
74
77
  onPointerDown={onPointerDown}
75
78
  onPointerEnter={onPointerEnter}
76
79
  onPointerLeave={onPointerLeave}
77
80
  onPointerUp={onPointerUp}
78
81
  >
79
82
  {onFind && (
80
- <ActionMenuButton label="Find in catalog" onClick={onFind}>
83
+ <ActionMenuButton
84
+ label={i18n._({ id: 'editor.nodeActionMenu.findInCatalog', message: 'Find in catalog' })}
85
+ onClick={onFind}
86
+ >
81
87
  <Search className="h-4 w-4" />
82
88
  </ActionMenuButton>
83
89
  )}
84
90
  {onMove && (
85
- <ActionMenuButton label="Move" onClick={onMove}>
91
+ <ActionMenuButton
92
+ label={i18n._({ id: 'editor.nodeActionMenu.move', message: 'Move' })}
93
+ onClick={onMove}
94
+ >
86
95
  <Move className="h-4 w-4" />
87
96
  </ActionMenuButton>
88
97
  )}
89
98
  {onEditMesh && (
90
- <ActionMenuButton label="Edit mesh" onClick={onEditMesh}>
99
+ <ActionMenuButton
100
+ label={i18n._({ id: 'editor.nodeActionMenu.editMesh', message: 'Edit mesh' })}
101
+ onClick={onEditMesh}
102
+ >
91
103
  <PencilRuler className="h-4 w-4" />
92
104
  </ActionMenuButton>
93
105
  )}
94
106
  {onGroup && (
95
- <ActionMenuButton keys={['Cmd/Ctrl', 'G']} label="Group selection" onClick={onGroup}>
107
+ <ActionMenuButton
108
+ keys={['Cmd/Ctrl', 'G']}
109
+ label={i18n._({ id: 'editor.nodeActionMenu.groupSelection', message: 'Group selection' })}
110
+ onClick={onGroup}
111
+ >
96
112
  <Group className="h-4 w-4" />
97
113
  </ActionMenuButton>
98
114
  )}
99
115
  {onUngroup && (
100
116
  <ActionMenuButton
101
117
  keys={['Cmd/Ctrl', 'Shift', 'G']}
102
- label="Ungroup selection"
118
+ label={i18n._({
119
+ id: 'editor.nodeActionMenu.ungroupSelection',
120
+ message: 'Ungroup selection',
121
+ })}
103
122
  onClick={onUngroup}
104
123
  >
105
124
  <Ungroup className="h-4 w-4" />
106
125
  </ActionMenuButton>
107
126
  )}
108
127
  {onCurve && (
109
- <ActionMenuButton label="Curve" onClick={onCurve}>
128
+ <ActionMenuButton
129
+ label={i18n._({ id: 'editor.nodeActionMenu.curve', message: 'Curve' })}
130
+ onClick={onCurve}
131
+ >
110
132
  <Spline className="h-4 w-4" />
111
133
  </ActionMenuButton>
112
134
  )}
113
135
  {onRotateLeft && (
114
- <ActionMenuButton label="Rotate left" onClick={onRotateLeft}>
136
+ <ActionMenuButton
137
+ label={i18n._({ id: 'editor.nodeActionMenu.rotateLeft', message: 'Rotate left' })}
138
+ onClick={onRotateLeft}
139
+ >
115
140
  <RotateCcw className="h-4 w-4" />
116
141
  </ActionMenuButton>
117
142
  )}
118
143
  {onRotateRight && (
119
- <ActionMenuButton label="Rotate right" onClick={onRotateRight}>
144
+ <ActionMenuButton
145
+ label={i18n._({ id: 'editor.nodeActionMenu.rotateRight', message: 'Rotate right' })}
146
+ onClick={onRotateRight}
147
+ >
120
148
  <RotateCw className="h-4 w-4" />
121
149
  </ActionMenuButton>
122
150
  )}
123
151
  <RegistryActionContributions />
124
152
  {children}
125
153
  {onDuplicate && (
126
- <ActionMenuButton label="Duplicate" onClick={onDuplicate}>
154
+ <ActionMenuButton
155
+ label={i18n._({ id: 'editor.nodeActionMenu.duplicate', message: 'Duplicate' })}
156
+ onClick={onDuplicate}
157
+ >
127
158
  <Copy className="h-4 w-4" />
128
159
  </ActionMenuButton>
129
160
  )}
130
161
  {onAddHole && (
131
- <ActionMenuButton label="Cut out" onClick={onAddHole}>
162
+ <ActionMenuButton
163
+ label={i18n._({ id: 'editor.nodeActionMenu.cutOut', message: 'Cut out' })}
164
+ onClick={onAddHole}
165
+ >
132
166
  <Icon height={16} icon="carbon:cut-out" width={16} />
133
167
  </ActionMenuButton>
134
168
  )}
@@ -138,7 +172,7 @@ export function NodeActionMenu({
138
172
  disabled={!!deleteDisabledReason}
139
173
  disabledReason={deleteDisabledReason}
140
174
  keys={['Delete / Backspace']}
141
- label={deleteLabel}
175
+ label={deleteLabel ?? i18n._({ id: 'editor.nodeActionMenu.delete', message: 'Delete' })}
142
176
  onClick={onDelete}
143
177
  >
144
178
  <Trash2 className="h-4 w-4" />
@@ -21,7 +21,6 @@ import {
21
21
  useScene,
22
22
  } from '@pascal-app/core'
23
23
  import { useViewer } from '@pascal-app/viewer'
24
- import { Html } from '@react-three/drei'
25
24
  import { createPortal, type ThreeEvent, useFrame, useThree } from '@react-three/fiber'
26
25
  import { useEffect, useMemo, useRef, useState } from 'react'
27
26
  import {
@@ -41,7 +40,6 @@ import {
41
40
  Vector3,
42
41
  } from 'three'
43
42
  import { mergeGeometries } from 'three/examples/jsm/utils/BufferGeometryUtils.js'
44
-
45
43
  import { MeshBasicNodeMaterial } from 'three/webgpu'
46
44
  import { useCeilingEditCeilingId } from '../../lib/ceiling-edit-session'
47
45
  import { EDITOR_LAYER } from '../../lib/constants'
@@ -78,6 +76,7 @@ import {
78
76
  import { createLinearResizeDragBinding, linearResizeFactor } from './handles/linear-resize-drag'
79
77
  import { resolveResizeSnapValue } from './handles/resize-snap'
80
78
  import { type HandleDragControls, useHandleDrag } from './handles/use-handle-drag'
79
+ import { Html } from './localized-html'
81
80
 
82
81
  // Pooled scratch for the handle rig's world-relative pose mapping.
83
82
  const _rigRelative = new Matrix4()
@@ -2,7 +2,6 @@
2
2
 
3
3
  import { type OpenWallEnd, sceneRegistry } 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 { memo, useEffect, useMemo, useRef } from 'react'
8
7
  import { BoxGeometry, CircleGeometry, type Group, RingGeometry } from 'three'
@@ -19,6 +18,7 @@ import { cn } from '../../lib/utils'
19
18
  import useEditor from '../../store/use-editor'
20
19
  import { isViewVisible, VIEW_3D, visibleScene } from '../../store/view-layout'
21
20
  import { JoinWallsPill } from './join-walls-pill'
21
+ import { Html } from './localized-html'
22
22
  import { OpenWallEndsHint } from './open-wall-ends-hint'
23
23
 
24
24
  /**
@@ -1,7 +1,6 @@
1
1
  'use client'
2
2
 
3
3
  import { useViewer } from '@pascal-app/viewer'
4
- import { Html } from '@react-three/drei'
5
4
  import { memo, useEffect, useLayoutEffect, useMemo } from 'react'
6
5
  import { BufferGeometry, Float32BufferAttribute, Line as ThreeLine } from 'three'
7
6
  import { LineBasicNodeMaterial } from 'three/webgpu'
@@ -10,6 +9,7 @@ import useOpeningGuides, {
10
9
  type OpeningGuide3D,
11
10
  type OpeningGuideVec3,
12
11
  } from '../../store/use-opening-guides'
12
+ import { Html } from './localized-html'
13
13
  import { formatMeasurement } from './measurement-pill'
14
14
 
15
15
  const DIMENSION_COLOR = 0x81_8c_f8 // indigo — a neutral measurement