@pascal-app/editor 1.1.0 → 1.1.1

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 (260) hide show
  1. package/package.json +9 -15
  2. package/src/components/editor/action-menu-button.tsx +1 -1
  3. package/src/components/editor/alignment-3d-guide-layer.tsx +1 -1
  4. package/src/components/editor/custom-camera-controls.tsx +11 -11
  5. package/src/components/editor/editor-layout-v2.tsx +1 -1
  6. package/src/components/editor/elevation-3d-guide-layer.tsx +1 -1
  7. package/src/components/editor/first-person-controls.tsx +20 -1981
  8. package/src/components/editor/floating-action-menu.tsx +1 -1
  9. package/src/components/editor/floating-building-action-menu.tsx +1 -1
  10. package/src/components/editor/floor-region-controls.tsx +6 -2
  11. package/src/components/editor/floorplan-navigation-presentation.test.ts +0 -40
  12. package/src/components/editor/floorplan-navigation-presentation.ts +0 -66
  13. package/src/components/editor/floorplan-panel.tsx +26 -22
  14. package/src/components/editor/floorplan-wall-typing-keys.ts +1 -1
  15. package/src/components/editor/follow-pascal.test.tsx +1 -2
  16. package/src/components/editor/follow-pascal.tsx +1 -1
  17. package/src/components/editor/footprint-height-handle.tsx +1 -1
  18. package/src/components/editor/group-actions.ts +1 -2
  19. package/src/components/editor/group-floating-action-menu.tsx +1 -1
  20. package/src/components/editor/group-move-3d.ts +1 -2
  21. package/src/components/editor/group-rotate-handle.tsx +1 -2
  22. package/src/components/editor/handles/use-handle-drag.ts +1 -2
  23. package/src/components/editor/index.tsx +24 -14
  24. package/src/components/editor/inspector-extension-actions.tsx +1 -1
  25. package/src/components/editor/node-arrow-handles.tsx +1 -1
  26. package/src/components/editor/open-wall-ends-3d-layer.tsx +1 -1
  27. package/src/components/editor/opening-guides-3d-layer.tsx +1 -1
  28. package/src/components/editor/room-controls.tsx +7 -4
  29. package/src/components/editor/room-handles-2d.tsx +2 -3
  30. package/src/components/editor/room-handles.tsx +1 -2
  31. package/src/components/editor/room-highlight.tsx +6 -105
  32. package/src/components/editor/room-labels.test.ts +8 -822
  33. package/src/components/editor/room-labels.tsx +40 -1671
  34. package/src/components/editor/room-webgpu-materials.test.ts +1 -1
  35. package/src/components/editor/selection-manager.tsx +5 -3
  36. package/src/components/editor/site-edge-labels.tsx +1 -1
  37. package/src/components/editor/snapshot-capture-overlay.tsx +2 -5
  38. package/src/components/editor/use-floorplan-background-placement.ts +2 -1
  39. package/src/components/editor/views/plugins-view.tsx +1 -1
  40. package/src/components/editor/views/view-bar.tsx +9 -9
  41. package/src/components/editor/wall-measurement-label.tsx +1 -1
  42. package/src/components/editor/wall-move-side-handles.tsx +1 -1
  43. package/src/components/editor/wall-pull-grip.tsx +2 -2
  44. package/src/components/editor-2d/floorplan-group-move.tsx +2 -3
  45. package/src/components/editor-2d/floorplan-measurement-tool-layer.tsx +1 -1
  46. package/src/components/editor-2d/floorplan-open-wall-ends-layer.tsx +1 -1
  47. package/src/components/editor-2d/floorplan-registry-move-overlay.tsx +1 -2
  48. package/src/components/editor-2d/renderers/floorplan-annotation-layout.test.ts +0 -37
  49. package/src/components/editor-2d/renderers/floorplan-annotation-layout.ts +0 -30
  50. package/src/components/editor-2d/renderers/floorplan-dimension-edit-overlay.tsx +13 -3
  51. package/src/components/editor-2d/renderers/floorplan-placement-preview-layer.tsx +1 -2
  52. package/src/components/editor-2d/renderers/floorplan-registry-layer.test.ts +8 -10
  53. package/src/components/editor-2d/renderers/floorplan-registry-layer.tsx +25 -33
  54. package/src/components/systems/roof/roof-edit-system.tsx +4 -3
  55. package/src/components/tools/distribution/distribution-run-tool.tsx +1 -1
  56. package/src/components/tools/item/placement-history.test.tsx +1 -1
  57. package/src/components/tools/item/use-placement-coordinator.tsx +2 -3
  58. package/src/components/tools/paint-region/wall-paint-region-tool.tsx +6 -2
  59. package/src/components/tools/registry/move-registry-node-tool.tsx +1 -2
  60. package/src/components/tools/shared/cursor-sphere.tsx +1 -1
  61. package/src/components/tools/shared/draft-measurement-label.tsx +1 -1
  62. package/src/components/tools/shared/placement-box.tsx +1 -1
  63. package/src/components/tools/shared/placement-dimension-guides.tsx +1 -1
  64. package/src/components/tools/site/terrain-brush-cursor.tsx +1 -1
  65. package/src/components/ui/action-menu/camera-actions.tsx +2 -2
  66. package/src/components/ui/action-menu/control-modes.tsx +1 -1
  67. package/src/components/ui/action-menu/follow-pascal-pill.tsx +1 -1
  68. package/src/components/ui/action-menu/index.tsx +3 -3
  69. package/src/components/ui/action-menu/measurement-control.tsx +3 -3
  70. package/src/components/ui/action-menu/placement.ts +0 -18
  71. package/src/components/ui/action-menu/point-ask-button.tsx +1 -1
  72. package/src/components/ui/action-menu/view-toggles.tsx +5 -5
  73. package/src/components/ui/controls/finish-swatch.tsx +1 -1
  74. package/src/components/ui/controls/material-paint-panel.tsx +2 -2
  75. package/src/components/ui/controls/scene-material-list.tsx +2 -2
  76. package/src/components/ui/controls/select-control.tsx +1 -1
  77. package/src/components/ui/controls/terrain-sculpt-panel.tsx +1 -1
  78. package/src/components/ui/floating-level-selector.tsx +3 -3
  79. package/src/components/ui/helpers/contextual-helper-panel.tsx +1 -1
  80. package/src/components/ui/helpers/helper-manager.test.tsx +1 -1
  81. package/src/components/ui/helpers/helper-manager.tsx +1 -1
  82. package/src/components/ui/level-duplicate-dialog.tsx +1 -1
  83. package/src/components/ui/panels/collections/collections-popover.tsx +2 -2
  84. package/src/components/ui/panels/floor-foundation-panel.tsx +2 -2
  85. package/src/components/ui/panels/floor-opening-panel.tsx +4 -4
  86. package/src/components/ui/panels/node-display.ts +1 -1
  87. package/src/components/ui/panels/panel-manager.tsx +1 -1
  88. package/src/components/ui/panels/panel-wrapper.tsx +1 -1
  89. package/src/components/ui/panels/parametric-inspector.tsx +1 -1
  90. package/src/components/ui/panels/room-construction-rows.tsx +6 -6
  91. package/src/components/ui/panels/room-name-options.ts +1 -5
  92. package/src/components/ui/panels/room-panel.tsx +6 -8
  93. package/src/components/ui/primitives/color-dot.tsx +2 -2
  94. package/src/components/ui/primitives/opacity-control.tsx +2 -2
  95. package/src/components/ui/primitives/sidebar.tsx +3 -3
  96. package/src/components/ui/right-stack.tsx +1 -1
  97. package/src/components/ui/sidebar/icon-rail.tsx +1 -1
  98. package/src/components/ui/sidebar/panels/items-panel/function-tree-panel.tsx +1 -1
  99. package/src/components/ui/sidebar/panels/settings-panel/audio-settings-dialog.tsx +2 -2
  100. package/src/components/ui/sidebar/panels/settings-panel/construction-animation-setting.tsx +1 -1
  101. package/src/components/ui/sidebar/panels/settings-panel/index.tsx +2 -2
  102. package/src/components/ui/sidebar/panels/settings-panel/keyboard-shortcuts-dialog.tsx +1 -1
  103. package/src/components/ui/sidebar/panels/settings-panel/load-build-dialog.tsx +1 -1
  104. package/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +1 -1
  105. package/src/components/ui/sidebar/panels/site-panel/building-tree-node.tsx +1 -1
  106. package/src/components/ui/sidebar/panels/site-panel/index.tsx +2 -2
  107. package/src/components/ui/sidebar/panels/site-panel/inline-rename-input.tsx +1 -1
  108. package/src/components/ui/sidebar/panels/site-panel/level-tree-node.tsx +1 -1
  109. package/src/components/ui/sidebar/panels/site-panel/roof-tree-node.tsx +46 -7
  110. package/src/components/ui/sidebar/panels/site-panel/tree-node-actions.tsx +1 -1
  111. package/src/components/ui/sidebar/panels/site-panel/tree-node-drag.tsx +14 -5
  112. package/src/components/ui/sidebar/panels/site-panel/unit-tree-node.tsx +2 -2
  113. package/src/components/ui/sidebar/panels/zone-panel/index.tsx +3 -3
  114. package/src/components/ui/sidebar/tab-bar.tsx +1 -1
  115. package/src/components/ui/wall-type/wall-type-editor-dialog.tsx +1 -1
  116. package/src/components/ui/wall-type/wall-type-menu.tsx +1 -1
  117. package/src/components/viewer-overlay.tsx +1 -2
  118. package/src/hooks/use-auto-frame.ts +1 -2
  119. package/src/hooks/use-keyboard.ts +1 -2
  120. package/src/hooks/use-selected-room.ts +5 -7
  121. package/src/i18n/floor-footprint-name.ts +1 -1
  122. package/src/i18n/locales/en/messages.po +180 -182
  123. package/src/i18n/locales/es/messages.po +180 -182
  124. package/src/i18n/locales/fr/messages.po +180 -182
  125. package/src/i18n/locales/ja/messages.po +180 -182
  126. package/src/i18n/locales/zh-CN/messages.po +180 -182
  127. package/src/i18n/locales.test.ts +1 -1
  128. package/src/index.tsx +109 -131
  129. package/src/lib/below-grade.test.tsx +4 -4
  130. package/src/lib/camera-facing.test.ts +1 -1
  131. package/src/lib/camera-facing.ts +2 -3
  132. package/src/lib/editor-level-display.ts +1 -1
  133. package/src/lib/floor-footprints.test.ts +2 -2
  134. package/src/lib/floor-opening-draft.ts +1 -1
  135. package/src/lib/floorplan/annotation-visibility.test.ts +1 -1
  136. package/src/lib/floorplan/annotation-visibility.ts +1 -1
  137. package/src/lib/floorplan/floorplan-export.test.ts +1 -1
  138. package/src/lib/floorplan/floorplan-export.tsx +8 -11
  139. package/src/lib/floorplan/floorplan-extension.ts +4 -157
  140. package/src/lib/floorplan/floorplan-mode.ts +2 -1
  141. package/src/lib/floorplan/floorplan-pdfkit-renderer.test.ts +1 -1
  142. package/src/lib/floorplan/floorplan-pdfkit-renderer.ts +8 -6
  143. package/src/lib/floorplan/floorplan-plugin-install.test.tsx +3 -4
  144. package/src/lib/floorplan/geometry.ts +2 -112
  145. package/src/lib/floorplan/index.ts +0 -9
  146. package/src/lib/floorplan/items.ts +2 -1
  147. package/src/lib/floorplan/schedules.ts +6 -3
  148. package/src/lib/floorplan/sheet-export.ts +7 -3
  149. package/src/lib/floorplan/site-plan/site-parts.ts +7 -1
  150. package/src/lib/floorplan/site-plan/site-plan-layer.tsx +1 -1
  151. package/src/lib/follow-pascal-presentation.ts +1 -1
  152. package/src/lib/glb-export.ts +1 -1
  153. package/src/lib/material-paint.ts +1 -1
  154. package/src/lib/measurements.test.ts +2 -1
  155. package/src/lib/mezzanine-selection.test.ts +1 -1
  156. package/src/lib/paint-region-mode.ts +1 -1
  157. package/src/lib/point-ask/session.ts +1 -2
  158. package/src/lib/roof-duplication.test.ts +100 -0
  159. package/src/lib/roof-duplication.ts +30 -5
  160. package/src/lib/room-handle-drag.ts +2 -12
  161. package/src/lib/room-name-catalog.test.ts +2 -1
  162. package/src/lib/room-name-messages.test.ts +2 -1
  163. package/src/lib/room-name-messages.ts +1 -118
  164. package/src/lib/room-name-storage.test.ts +2 -1
  165. package/src/lib/room-selection-commands.ts +2 -2
  166. package/src/lib/room-selection.test.ts +6 -6
  167. package/src/lib/room-transform-session.ts +1 -2
  168. package/src/lib/room-zone-routing.ts +2 -2
  169. package/src/lib/selection-routing.test.ts +0 -28
  170. package/src/lib/selection-routing.ts +1 -40
  171. package/src/lib/sfx-bus.ts +8 -32
  172. package/src/lib/sfx-player.test.ts +72 -1
  173. package/src/lib/sfx-player.ts +49 -337
  174. package/src/lib/viewer-selection.test.ts +38 -10
  175. package/src/lib/wall-pull.test.ts +1 -1
  176. package/src/lib/wall-pull.ts +1 -27
  177. package/src/store/use-editor.tsx +17 -26
  178. package/src/store/use-floorplan-annotation-visibility.ts +5 -5
  179. package/src/store/use-wall-region-handles-selection.ts +1 -2
  180. package/src/components/editor/__bench__/room-label-placement.bench.ts +0 -108
  181. package/src/components/editor/first-person/build-collider-world.test.ts +0 -596
  182. package/src/components/editor/first-person/build-collider-world.ts +0 -549
  183. package/src/components/editor/first-person/ground-holes.test.ts +0 -128
  184. package/src/components/editor/first-person/ground-holes.ts +0 -130
  185. package/src/components/editor/first-person/mechanism-targets.test.ts +0 -162
  186. package/src/components/editor/first-person/mechanism-targets.ts +0 -88
  187. package/src/components/editor/first-person/terrain-collider.test.ts +0 -132
  188. package/src/components/editor/first-person/terrain-collider.ts +0 -212
  189. package/src/components/editor/floorplan-camera-sync.test.ts +0 -249
  190. package/src/components/editor/floorplan-camera-sync.ts +0 -213
  191. package/src/components/editor/localized-html.tsx +0 -22
  192. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.test.tsx +0 -236
  193. package/src/components/editor-2d/renderers/floorplan-dimension-renderer.tsx +0 -708
  194. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.test.tsx +0 -385
  195. package/src/components/editor-2d/renderers/floorplan-geometry-renderer.tsx +0 -670
  196. package/src/components/editor-2d/renderers/floorplan-label-angle.test.ts +0 -89
  197. package/src/components/editor-2d/renderers/floorplan-label-angle.ts +0 -106
  198. package/src/components/editor-2d/svg-paths.ts +0 -124
  199. package/src/components/ui/action-menu/action-button.tsx +0 -89
  200. package/src/components/ui/action-menu/follow-pascal-toggle.tsx +0 -42
  201. package/src/components/ui/primitives/button.tsx +0 -69
  202. package/src/components/ui/primitives/dropdown-menu.tsx +0 -232
  203. package/src/components/ui/primitives/popover.tsx +0 -42
  204. package/src/components/ui/primitives/switch.tsx +0 -29
  205. package/src/components/ui/primitives/tooltip.tsx +0 -57
  206. package/src/components/ui/scene-loader.tsx +0 -73
  207. package/src/components/viewer/floorplan-compass-button.tsx +0 -67
  208. package/src/components/viewer/floorplan-preview-geometry.test.ts +0 -107
  209. package/src/components/viewer/floorplan-preview-geometry.ts +0 -281
  210. package/src/components/viewer/floorplan-preview-navigation.test.ts +0 -43
  211. package/src/components/viewer/floorplan-preview-navigation.ts +0 -62
  212. package/src/components/viewer/floorplan-preview-visibility.test.ts +0 -35
  213. package/src/components/viewer/floorplan-preview-visibility.ts +0 -19
  214. package/src/components/viewer/floorplan-preview.tsx +0 -1065
  215. package/src/components/viewer/render-settings-menu.tsx +0 -294
  216. package/src/components/viewer/sky-control.tsx +0 -710
  217. package/src/components/viewer/use-viewer-camera-navigation-sync.ts +0 -140
  218. package/src/components/viewer/use-viewer-ui-sounds.test.ts +0 -15
  219. package/src/components/viewer/use-viewer-ui-sounds.ts +0 -46
  220. package/src/components/viewer/viewer-controls-bar.tsx +0 -496
  221. package/src/components/viewer/viewer-hover-label.tsx +0 -42
  222. package/src/components/viewer/viewer-rooms.tsx +0 -248
  223. package/src/components/viewer/viewer-scene-header.tsx +0 -286
  224. package/src/components/viewer/viewer-selection-manager.tsx +0 -124
  225. package/src/components/viewer/viewer-stage-modes.test.ts +0 -38
  226. package/src/components/viewer/viewer-stage-modes.ts +0 -49
  227. package/src/components/viewer/viewer-stage-switcher.tsx +0 -97
  228. package/src/components/viewer/viewer-stage.test.tsx +0 -47
  229. package/src/components/viewer/viewer-stage.tsx +0 -221
  230. package/src/components/walkthrough-hud.tsx +0 -193
  231. package/src/hooks/use-mobile.ts +0 -19
  232. package/src/i18n/level-name.ts +0 -39
  233. package/src/i18n/scene-theme-name.ts +0 -16
  234. package/src/lib/below-grade.ts +0 -133
  235. package/src/lib/camera-pose.test.ts +0 -237
  236. package/src/lib/camera-pose.ts +0 -172
  237. package/src/lib/floorplan/floorplan-extension.test.ts +0 -42
  238. package/src/lib/floorplan/floorplan-readonly.test.ts +0 -47
  239. package/src/lib/floorplan/floorplan-readonly.ts +0 -95
  240. package/src/lib/floorplan/marks.test.ts +0 -213
  241. package/src/lib/floorplan/marks.ts +0 -238
  242. package/src/lib/measurements.ts +0 -8
  243. package/src/lib/node-display-label.ts +0 -37
  244. package/src/lib/room-assembly-overlay.test.ts +0 -275
  245. package/src/lib/room-assembly-overlay.ts +0 -265
  246. package/src/lib/room-selection.ts +0 -417
  247. package/src/lib/scene-bounds.test.ts +0 -327
  248. package/src/lib/scene-bounds.ts +0 -233
  249. package/src/lib/typing-target.ts +0 -9
  250. package/src/lib/viewer-selection.ts +0 -595
  251. package/src/lib/zone-content.ts +0 -133
  252. package/src/store/camera-pose-store.test.ts +0 -29
  253. package/src/store/camera-pose-store.ts +0 -27
  254. package/src/store/navigation-sync-pose-store.test.ts +0 -34
  255. package/src/store/navigation-sync-pose-store.ts +0 -27
  256. package/src/store/use-audio.test.ts +0 -146
  257. package/src/store/use-audio.tsx +0 -169
  258. package/src/store/use-first-person-hud.ts +0 -40
  259. package/src/store/use-follow-pascal.test.ts +0 -62
  260. package/src/store/use-follow-pascal.ts +0 -53
@@ -1,294 +0,0 @@
1
- 'use client'
2
-
3
- import { type PascalI18n, usePascalI18n, useScene } from '@pascal-app/core'
4
- import {
5
- type RenderQuality,
6
- requestDeviceLocation,
7
- resolveLocation,
8
- type TimeOfDay,
9
- useViewer,
10
- type Weather,
11
- } from '@pascal-app/viewer'
12
- import {
13
- ArrowDownLeft,
14
- Check,
15
- Cloud,
16
- CloudSun,
17
- Gauge,
18
- LocateFixed,
19
- Moon,
20
- PenLine,
21
- Sun,
22
- Sunrise,
23
- Sunset,
24
- } from 'lucide-react'
25
- import { useState } from 'react'
26
- import {
27
- DropdownMenuItem,
28
- DropdownMenuSub,
29
- DropdownMenuSubContent,
30
- DropdownMenuSubTrigger,
31
- } from '../ui/primitives/dropdown-menu'
32
- import { openSkyPanel, useSkyPanel, weatherIcon } from './sky-control'
33
-
34
- /**
35
- * Render settings shared by every display dropdown:
36
- * - `QualityMenu` (both appearances): Low / Medium / High.
37
- * - `DaylightMenu` (colored): time-of-day and weather presets, plus "Use my location" when the
38
- * site has no geocoded point — or, while the sky chip is in the viewport, a row opening it.
39
- * - `EdgesToggleItem` (monochrome): ink edges on or off.
40
- */
41
-
42
- function timeOptions(i18n: PascalI18n) {
43
- return [
44
- {
45
- id: 'golden',
46
- icon: Sunrise,
47
- name: i18n._({ id: 'viewer.daylight.time.golden', message: 'Golden hour' }),
48
- },
49
- {
50
- id: 'midday',
51
- icon: Sun,
52
- name: i18n._({ id: 'viewer.daylight.time.midday', message: 'Midday' }),
53
- },
54
- {
55
- id: 'blue',
56
- icon: Sunset,
57
- name: i18n._({ id: 'viewer.daylight.time.blue', message: 'Blue hour' }),
58
- },
59
- {
60
- id: 'night',
61
- icon: Moon,
62
- name: i18n._({ id: 'viewer.daylight.time.night', message: 'Night' }),
63
- },
64
- ] as const satisfies readonly { id: TimeOfDay; icon: typeof Sun; name: string }[]
65
- }
66
-
67
- function weatherOptions(i18n: PascalI18n) {
68
- return [
69
- { id: 'clear', name: i18n._({ id: 'viewer.daylight.weather.clear', message: 'Clear' }) },
70
- { id: 'fair', name: i18n._({ id: 'viewer.daylight.weather.fair', message: 'Fair' }) },
71
- { id: 'cloudy', name: i18n._({ id: 'viewer.daylight.weather.cloudy', message: 'Cloudy' }) },
72
- {
73
- id: 'overcast',
74
- name: i18n._({ id: 'viewer.daylight.weather.overcast', message: 'Overcast' }),
75
- },
76
- ] as const satisfies readonly { id: Weather; name: string }[]
77
- }
78
-
79
- function qualityOptions(i18n: PascalI18n) {
80
- return [
81
- {
82
- id: 'low',
83
- name: i18n._({ id: 'viewer.daylight.quality.low', message: 'Low' }),
84
- detail: i18n._({
85
- id: 'viewer.daylight.quality.lowDetail',
86
- message: 'Fastest — no ambient occlusion or bloom',
87
- }),
88
- },
89
- {
90
- id: 'medium',
91
- name: i18n._({ id: 'viewer.daylight.quality.medium', message: 'Medium' }),
92
- detail: i18n._({
93
- id: 'viewer.daylight.quality.mediumDetail',
94
- message: 'Half-resolution ambient occlusion',
95
- }),
96
- },
97
- {
98
- id: 'high',
99
- name: i18n._({ id: 'viewer.daylight.quality.high', message: 'High' }),
100
- detail: i18n._({
101
- id: 'viewer.daylight.quality.highDetail',
102
- message: 'Lighting-aware ambient occlusion and bloom',
103
- }),
104
- },
105
- ] as const satisfies readonly { id: RenderQuality; name: string; detail: string }[]
106
- }
107
-
108
- export function QualityMenu({
109
- subContentClassName = 'dark min-w-56',
110
- }: {
111
- subContentClassName?: string
112
- }) {
113
- const i18n = usePascalI18n()
114
- const quality = useViewer((s) => s.renderQuality)
115
- const choice = useViewer((s) => s.renderQualityChoice)
116
- const qualities = qualityOptions(i18n)
117
- const active = qualities.find((o) => o.id === quality) ?? qualities[2]
118
- const autoName = i18n._({ id: 'viewer.daylight.quality.auto', message: 'Auto' })
119
- return (
120
- <DropdownMenuSub>
121
- <DropdownMenuSubTrigger>
122
- <Gauge className="h-4 w-4" />
123
- <span>{i18n._({ id: 'viewer.daylight.quality.title', message: 'Quality' })}</span>
124
- <span className="ml-auto text-muted-foreground text-xs">
125
- {choice === 'auto' ? `${autoName} (${active.name})` : active.name}
126
- </span>
127
- </DropdownMenuSubTrigger>
128
- <DropdownMenuSubContent className={subContentClassName}>
129
- <DropdownMenuItem onSelect={() => useViewer.getState().setRenderQuality('auto')}>
130
- <div className="flex flex-col">
131
- <span className="text-foreground">{autoName}</span>
132
- <span className="text-muted-foreground text-xs">
133
- {i18n._({
134
- id: 'viewer.daylight.quality.autoDetail',
135
- message: 'Picked for this device: {tier}',
136
- values: { tier: active.name },
137
- })}
138
- </span>
139
- </div>
140
- {choice === 'auto' ? <Check className="ml-auto h-4 w-4 text-foreground" /> : null}
141
- </DropdownMenuItem>
142
- {qualities.map((option) => (
143
- <DropdownMenuItem
144
- key={option.id}
145
- onSelect={() => useViewer.getState().setRenderQuality(option.id)}
146
- >
147
- <div className="flex flex-col">
148
- <span className="text-foreground">{option.name}</span>
149
- <span className="text-muted-foreground text-xs">{option.detail}</span>
150
- </div>
151
- {choice === option.id ? <Check className="ml-auto h-4 w-4 text-foreground" /> : null}
152
- </DropdownMenuItem>
153
- ))}
154
- </DropdownMenuSubContent>
155
- </DropdownMenuSub>
156
- )
157
- }
158
-
159
- export function DaylightMenu({
160
- subContentClassName = 'dark min-w-48',
161
- }: {
162
- subContentClassName?: string
163
- }) {
164
- const i18n = usePascalI18n()
165
- const timeOfDay = useViewer((s) => s.timeOfDay)
166
- const weather = useViewer((s) => s.weather)
167
- const deviceLocation = useViewer((s) => s.deviceLocation)
168
- const siteHasLocation = useScene((state) => {
169
- for (const id of state.rootNodeIds) {
170
- const node = state.nodes[id] as
171
- | { type?: string; parcel?: { originLngLat?: unknown }; dossier?: { point?: unknown } }
172
- | undefined
173
- if (node?.type === 'site') return Boolean(node.parcel?.originLngLat || node.dossier?.point)
174
- }
175
- return false
176
- })
177
- const [locating, setLocating] = useState(false)
178
- // The sky chip beside the compass carries time and weather wherever it is mounted.
179
- const skyChip = useSkyPanel((s) => s.hosts > 0)
180
- const times = timeOptions(i18n)
181
- const weathers = weatherOptions(i18n)
182
- const activeTime = times.find((o) => o.id === timeOfDay) ?? times[1]
183
- const activeWeather = weathers.find((o) => o.id === weather) ?? weathers[1]
184
- const source = siteHasLocation ? 'site' : resolveLocation(null, deviceLocation).source
185
-
186
- if (skyChip) {
187
- const WeatherIcon = weatherIcon(weather)
188
- return (
189
- <DropdownMenuItem onSelect={openSkyPanel}>
190
- <WeatherIcon className="h-4 w-4" />
191
- <span>{i18n._({ id: 'viewer.daylight.sky.menuItem', message: 'Time & weather' })}</span>
192
- <span className="ml-auto flex items-center gap-1 text-violet-600 text-xs dark:text-violet-300">
193
- {i18n._({ id: 'viewer.daylight.sky.inViewport', message: 'In viewport' })}
194
- <ArrowDownLeft className="h-3.5 w-3.5" />
195
- </span>
196
- </DropdownMenuItem>
197
- )
198
- }
199
-
200
- return (
201
- <>
202
- <DropdownMenuSub>
203
- <DropdownMenuSubTrigger>
204
- <activeTime.icon className="h-4 w-4" />
205
- <span>{i18n._({ id: 'viewer.daylight.time.title', message: 'Time of day' })}</span>
206
- <span className="ml-auto text-muted-foreground text-xs">{activeTime.name}</span>
207
- </DropdownMenuSubTrigger>
208
- <DropdownMenuSubContent className={subContentClassName}>
209
- {times.map((option) => (
210
- <DropdownMenuItem
211
- key={option.id}
212
- onSelect={() => useViewer.getState().setTimeOfDay(option.id)}
213
- >
214
- <option.icon className="h-4 w-4" />
215
- <span>{option.name}</span>
216
- {timeOfDay === option.id ? <Check className="ml-auto h-4 w-4" /> : null}
217
- </DropdownMenuItem>
218
- ))}
219
- {source !== 'site' ? (
220
- <DropdownMenuItem
221
- disabled={locating}
222
- onSelect={(event) => {
223
- event.preventDefault()
224
- setLocating(true)
225
- requestDeviceLocation()
226
- .then((location) => useViewer.getState().setDeviceLocation(location))
227
- .catch(() => {})
228
- .finally(() => setLocating(false))
229
- }}
230
- >
231
- <LocateFixed className="h-4 w-4" />
232
- <div className="flex flex-col">
233
- <span>
234
- {i18n._({ id: 'viewer.daylight.location.use', message: 'Use my location' })}
235
- </span>
236
- <span className="text-muted-foreground text-xs">
237
- {source === 'device'
238
- ? i18n._({
239
- id: 'viewer.daylight.location.device',
240
- message: 'Sun follows your location',
241
- })
242
- : i18n._({
243
- id: 'viewer.daylight.location.approximate',
244
- message: 'Sun position is approximate',
245
- })}
246
- </span>
247
- </div>
248
- </DropdownMenuItem>
249
- ) : null}
250
- </DropdownMenuSubContent>
251
- </DropdownMenuSub>
252
- <DropdownMenuSub>
253
- <DropdownMenuSubTrigger>
254
- {weather === 'clear' ? <CloudSun className="h-4 w-4" /> : <Cloud className="h-4 w-4" />}
255
- <span>{i18n._({ id: 'viewer.daylight.weather.title', message: 'Weather' })}</span>
256
- <span className="ml-auto text-muted-foreground text-xs">{activeWeather.name}</span>
257
- </DropdownMenuSubTrigger>
258
- <DropdownMenuSubContent className={subContentClassName}>
259
- {weathers.map((option) => (
260
- <DropdownMenuItem
261
- key={option.id}
262
- onSelect={() => useViewer.getState().setWeather(option.id)}
263
- >
264
- <span>{option.name}</span>
265
- {weather === option.id ? <Check className="ml-auto h-4 w-4" /> : null}
266
- </DropdownMenuItem>
267
- ))}
268
- </DropdownMenuSubContent>
269
- </DropdownMenuSub>
270
- </>
271
- )
272
- }
273
-
274
- /** Ink edges on (soft) or off. The menu stays open on toggle. */
275
- export function EdgesToggleItem() {
276
- const i18n = usePascalI18n()
277
- const on = useViewer((s) => s.edges !== 'off')
278
- return (
279
- <DropdownMenuItem
280
- onSelect={(event) => {
281
- event.preventDefault()
282
- useViewer.getState().setEdges(on ? 'off' : 'soft')
283
- }}
284
- >
285
- <PenLine className="h-4 w-4" />
286
- <span>{i18n._({ id: 'viewer.display.edges.title', message: 'Edges' })}</span>
287
- <span className="ml-auto text-muted-foreground text-xs">
288
- {on
289
- ? i18n._({ id: 'viewer.common.on', message: 'On' })
290
- : i18n._({ id: 'viewer.common.off', message: 'Off' })}
291
- </span>
292
- </DropdownMenuItem>
293
- )
294
- }