@fps-games/editor 0.1.5-beta.5 → 0.1.6-beta.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 (218) hide show
  1. package/dist/build-info.json +16 -16
  2. package/dist/local-editor-agent-actions.d.ts +8 -1
  3. package/dist/local-editor-agent-actions.d.ts.map +1 -1
  4. package/dist/local-editor-agent-actions.js +171 -12
  5. package/dist/local-editor-agent-actions.js.map +1 -1
  6. package/dist/local-editor-agent-context.d.ts +29 -1
  7. package/dist/local-editor-agent-context.d.ts.map +1 -1
  8. package/dist/local-editor-agent-context.js +403 -0
  9. package/dist/local-editor-agent-context.js.map +1 -1
  10. package/dist/local-editor-harness.d.ts +103 -5
  11. package/dist/local-editor-harness.d.ts.map +1 -1
  12. package/dist/local-editor-harness.js +1982 -101
  13. package/dist/local-editor-harness.js.map +1 -1
  14. package/dist/local-editor-marker-graph.d.ts +92 -0
  15. package/dist/local-editor-marker-graph.d.ts.map +1 -0
  16. package/dist/local-editor-marker-graph.js +1432 -0
  17. package/dist/local-editor-marker-graph.js.map +1 -0
  18. package/dist/local-editor-performance-stats.js +1 -1
  19. package/dist/local-editor-performance-stats.js.map +1 -1
  20. package/dist/playable-babylon-facade.d.ts +2 -2
  21. package/dist/playable-babylon-facade.d.ts.map +1 -1
  22. package/dist/playable-babylon-facade.js +1 -1
  23. package/dist/playable-babylon-facade.js.map +1 -1
  24. package/dist/playable-local-editor-host.d.ts +52 -7
  25. package/dist/playable-local-editor-host.d.ts.map +1 -1
  26. package/dist/playable-local-editor-host.js +23 -1
  27. package/dist/playable-local-editor-host.js.map +1 -1
  28. package/dist/playable-sdk.d.ts +4 -2
  29. package/dist/playable-sdk.d.ts.map +1 -1
  30. package/dist/playable-sdk.js +2 -1
  31. package/dist/playable-sdk.js.map +1 -1
  32. package/node_modules/@fps-games/babylon-renderer/package.json +1 -1
  33. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.d.ts.map +1 -1
  34. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js +11 -0
  35. package/node_modules/@fps-games/editor-babylon/dist/camera-controller.js.map +1 -1
  36. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts +3 -1
  37. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.d.ts.map +1 -1
  38. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js +114 -18
  39. package/node_modules/@fps-games/editor-babylon/dist/editor-sky.js.map +1 -1
  40. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts +1 -0
  41. package/node_modules/@fps-games/editor-babylon/dist/editor-world.d.ts.map +1 -1
  42. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js +3 -0
  43. package/node_modules/@fps-games/editor-babylon/dist/editor-world.js.map +1 -1
  44. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts +3 -0
  45. package/node_modules/@fps-games/editor-babylon/dist/index.d.ts.map +1 -1
  46. package/node_modules/@fps-games/editor-babylon/dist/index.js +3 -0
  47. package/node_modules/@fps-games/editor-babylon/dist/index.js.map +1 -1
  48. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts +52 -0
  49. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.d.ts.map +1 -0
  50. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js +1395 -0
  51. package/node_modules/@fps-games/editor-babylon/dist/marker-overlay.js.map +1 -0
  52. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.d.ts.map +1 -1
  53. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js +4 -65
  54. package/node_modules/@fps-games/editor-babylon/dist/material-property-adapter.js.map +1 -1
  55. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts +28 -0
  56. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.d.ts.map +1 -0
  57. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js +235 -0
  58. package/node_modules/@fps-games/editor-babylon/dist/material-slot-source-resolver.js.map +1 -0
  59. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts +37 -0
  60. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.d.ts.map +1 -0
  61. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js +451 -0
  62. package/node_modules/@fps-games/editor-babylon/dist/prefab-comparison-preview-controller.js.map +1 -0
  63. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.d.ts.map +1 -1
  64. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js +14 -3
  65. package/node_modules/@fps-games/editor-babylon/dist/projection-asset-container-importer.js.map +1 -1
  66. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts +1 -0
  67. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.d.ts.map +1 -1
  68. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js +20 -4
  69. package/node_modules/@fps-games/editor-babylon/dist/projection-pick-resolver.js.map +1 -1
  70. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts +3 -6
  71. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.d.ts.map +1 -1
  72. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js +4 -37
  73. package/node_modules/@fps-games/editor-babylon/dist/projection-source-owner-path.js.map +1 -1
  74. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts +44 -1
  75. package/node_modules/@fps-games/editor-babylon/dist/projection.d.ts.map +1 -1
  76. package/node_modules/@fps-games/editor-babylon/dist/projection.js +295 -41
  77. package/node_modules/@fps-games/editor-babylon/dist/projection.js.map +1 -1
  78. package/node_modules/@fps-games/editor-babylon/package.json +5 -5
  79. package/node_modules/@fps-games/editor-browser/dist/index.d.ts +5 -2
  80. package/node_modules/@fps-games/editor-browser/dist/index.d.ts.map +1 -1
  81. package/node_modules/@fps-games/editor-browser/dist/index.js +2 -0
  82. package/node_modules/@fps-games/editor-browser/dist/index.js.map +1 -1
  83. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts +5 -0
  84. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.d.ts.map +1 -0
  85. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js +23 -0
  86. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-asset-kind.js.map +1 -0
  87. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.d.ts.map +1 -1
  88. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js +69 -0
  89. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-actions.js.map +1 -1
  90. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.d.ts.map +1 -1
  91. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js +1 -0
  92. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-attach-dialog.js.map +1 -1
  93. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts +2 -0
  94. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.d.ts.map +1 -1
  95. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js +38 -1
  96. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-controller.js.map +1 -1
  97. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts +1 -1
  98. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-tree.d.ts.map +1 -1
  99. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.d.ts.map +1 -1
  100. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js +8 -2
  101. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-hierarchy-view.js.map +1 -1
  102. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts +1 -1
  103. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.d.ts.map +1 -1
  104. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js +7 -0
  105. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-icons.js.map +1 -1
  106. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js +1 -1
  107. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panel-registry.js.map +1 -1
  108. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts +4 -0
  109. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.d.ts.map +1 -1
  110. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js +1471 -29
  111. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-panels.js.map +1 -1
  112. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts +52 -0
  113. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.d.ts.map +1 -0
  114. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js +131 -0
  115. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-prefab-stage.js.map +1 -0
  116. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts +1 -1
  117. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-primitives.d.ts.map +1 -1
  118. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.d.ts.map +1 -1
  119. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js +9 -2
  120. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-shared.js.map +1 -1
  121. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts +234 -3
  122. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-types.d.ts.map +1 -1
  123. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts +10 -1
  124. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.d.ts.map +1 -1
  125. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js +3 -1
  126. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui-workbench.js.map +1 -1
  127. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts +4 -3
  128. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.d.ts.map +1 -1
  129. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js +1274 -55
  130. package/node_modules/@fps-games/editor-browser/dist/local-editor-ui.js.map +1 -1
  131. package/node_modules/@fps-games/editor-browser/package.json +3 -3
  132. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts +1 -1
  133. package/node_modules/@fps-games/editor-core/dist/scene-graph.d.ts.map +1 -1
  134. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts +2 -0
  135. package/node_modules/@fps-games/editor-core/dist/viewport-tools.d.ts.map +1 -1
  136. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js +1 -0
  137. package/node_modules/@fps-games/editor-core/dist/viewport-tools.js.map +1 -1
  138. package/node_modules/@fps-games/editor-core/package.json +2 -2
  139. package/node_modules/@fps-games/editor-forge-play/package.json +2 -2
  140. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.d.ts.map +1 -1
  141. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js +17 -3
  142. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/catalog.js.map +1 -1
  143. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.d.ts.map +1 -1
  144. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js +2 -1
  145. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/document.js.map +1 -1
  146. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts +3 -2
  147. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-library/types.d.ts.map +1 -1
  148. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts +1 -0
  149. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.d.ts.map +1 -1
  150. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js +68 -2
  151. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/document.js.map +1 -1
  152. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts +1 -1
  153. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.d.ts.map +1 -1
  154. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js +1 -1
  155. package/node_modules/@fps-games/editor-playable-sdk/dist/asset-placement/index.js.map +1 -1
  156. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts +56 -1
  157. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.d.ts.map +1 -1
  158. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js +531 -0
  159. package/node_modules/@fps-games/editor-playable-sdk/dist/document/helpers.js.map +1 -1
  160. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts +103 -2
  161. package/node_modules/@fps-games/editor-playable-sdk/dist/document/types.d.ts.map +1 -1
  162. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts +1 -1
  163. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.d.ts.map +1 -1
  164. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js +1 -1
  165. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/index.js.map +1 -1
  166. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.d.ts.map +1 -1
  167. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js +9 -1
  168. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/schema.js.map +1 -1
  169. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts +1 -0
  170. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.d.ts.map +1 -1
  171. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js +3 -0
  172. package/node_modules/@fps-games/editor-playable-sdk/dist/field-schema/validators.js.map +1 -1
  173. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.d.ts.map +1 -1
  174. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js +3 -7
  175. package/node_modules/@fps-games/editor-playable-sdk/dist/local-harness/lifecycle.js.map +1 -1
  176. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.d.ts.map +1 -1
  177. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js +6 -0
  178. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/binding.js.map +1 -1
  179. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts +2 -10
  180. package/node_modules/@fps-games/editor-playable-sdk/dist/material-binding/types.d.ts.map +1 -1
  181. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.d.ts.map +1 -1
  182. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js +8 -0
  183. package/node_modules/@fps-games/editor-playable-sdk/dist/mutation/field-patch.js.map +1 -1
  184. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.d.ts.map +1 -1
  185. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js +66 -31
  186. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/nodes.js.map +1 -1
  187. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts +2 -1
  188. package/node_modules/@fps-games/editor-playable-sdk/dist/runtime-preview/types.d.ts.map +1 -1
  189. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.d.ts.map +1 -1
  190. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js +40 -2
  191. package/node_modules/@fps-games/editor-playable-sdk/dist/scene-source/protocol.js.map +1 -1
  192. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts +1 -0
  193. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.d.ts.map +1 -1
  194. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js +20 -0
  195. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/descriptors.js.map +1 -1
  196. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.d.ts.map +1 -1
  197. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js +68 -27
  198. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/inspector.js.map +1 -1
  199. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.d.ts.map +1 -1
  200. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js +7 -4
  201. package/node_modules/@fps-games/editor-playable-sdk/dist/serialized-property/object.js.map +1 -1
  202. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.d.ts.map +1 -1
  203. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js +2 -1
  204. package/node_modules/@fps-games/editor-playable-sdk/dist/session/hierarchy.js.map +1 -1
  205. package/node_modules/@fps-games/editor-playable-sdk/package.json +3 -3
  206. package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts +57 -3
  207. package/node_modules/@fps-games/editor-protocol/dist/agent.d.ts.map +1 -1
  208. package/node_modules/@fps-games/editor-protocol/dist/agent.js.map +1 -1
  209. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts +19 -0
  210. package/node_modules/@fps-games/editor-protocol/dist/index.d.ts.map +1 -1
  211. package/node_modules/@fps-games/editor-protocol/dist/index.js +1 -0
  212. package/node_modules/@fps-games/editor-protocol/dist/index.js.map +1 -1
  213. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts +128 -0
  214. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.d.ts.map +1 -0
  215. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js +2 -0
  216. package/node_modules/@fps-games/editor-protocol/dist/marker-graph.js.map +1 -0
  217. package/node_modules/@fps-games/editor-protocol/package.json +1 -1
  218. package/package.json +8 -8
@@ -8,7 +8,7 @@ import { LOCAL_EDITOR_BROWSER_INSPECTOR_VEC3_AXES, resolveLocalEditorBrowserInsp
8
8
  import { DEFAULT_EDITOR_TRANSFORM_OPERATION_SETTINGS, DEFAULT_EDITOR_TRANSFORM_TOOL_DESCRIPTORS, } from '@fps-games/editor-core';
9
9
  export { createLocalEditorViewportRevealController, isLocalEditorElementVisibleInViewport, resolveLocalEditorViewportRevealScrollTop, revealLocalEditorElementInViewport, } from './local-editor-ui-dock-panel.js';
10
10
  export { createLocalEditorHierarchyBlankMenu, createLocalEditorHierarchyCopyShortcutAction, createLocalEditorHierarchyDeleteShortcutAction, createLocalEditorHierarchyDuplicateShortcutAction, createLocalEditorHierarchyNodeMenu, createLocalEditorHierarchyPasteShortcutAction, createLocalEditorHierarchySelectAllShortcutAction, } from './local-editor-ui-hierarchy-actions.js';
11
- export { createLocalEditorHierarchyController, } from './local-editor-ui-hierarchy-controller.js';
11
+ export { createLocalEditorHierarchyController, resolveLocalEditorHierarchyCreateMarkerParentId, } from './local-editor-ui-hierarchy-controller.js';
12
12
  export { resolveLocalEditorHierarchySearchRows, } from './local-editor-ui-hierarchy-view.js';
13
13
  export { createLocalEditorHierarchyAttachDialog, resolveLocalEditorHierarchyAttachTargets, } from './local-editor-ui-hierarchy-attach-dialog.js';
14
14
  export { canLocalEditorHierarchyNodeHaveChildren, createLocalEditorHierarchyTreeModel, isLocalEditorHierarchyNodeSelectable, isLocalEditorHierarchyNodeMovable, } from './local-editor-ui-hierarchy-tree.js';
@@ -22,6 +22,8 @@ import { createShortcutHelpPanel as createLocalEditorShortcutHelpPanel } from '.
22
22
  import { applyLocalEditorTheme, ensureLocalEditorTheme, LOCAL_EDITOR_THEME_CLASS, normalizeLocalEditorThemeName, } from './local-editor-ui-theme.js';
23
23
  import { createDefaultLocalEditorWorkbenchLayout, createLocalEditorWorkbenchLayoutController, createLocalEditorWorkbench, createSceneHeaderToolbar, createWorkbenchPanelContent, } from './local-editor-ui-workbench.js';
24
24
  import { createLocalEditorIcon, isLocalEditorIconName } from './local-editor-ui-icons.js';
25
+ import { resolveLocalEditorBrowserAssetKind, } from './local-editor-ui-asset-kind.js';
26
+ import { createLocalEditorPrefabStageTitle, filterLocalEditorPrefabStageStructureItems, findLocalEditorPrefabStageStructureItem, formatLocalEditorPrefabStageComparisonPreviewStatus, LOCAL_EDITOR_PREFAB_COMPARISON_COLUMN_WIDTH_PX, resolveLocalEditorRightDockVisibility, resolveLocalEditorPrefabStageComparisonPreviewTone, shouldLocalEditorClosePerformanceMonitor, shouldLocalEditorPrefabStageBlockHierarchyShortcut, shouldShowLocalEditorPrefabComparisonColumn, shouldToggleLocalEditorPrefabComparisonShortcut, } from './local-editor-ui-prefab-stage.js';
25
27
  export { applyLocalEditorBrowserInspectorControlBinding, createLocalEditorBrowserInspectorControlRegistry, formatLocalEditorBrowserInspectorNumberValue, formatLocalEditorBrowserInspectorValue, parseLocalEditorBrowserInspectorNumberValue, resolveLocalEditorBrowserInspectorSectionStatus, resolveLocalEditorBrowserInspectorControlRegistration, } from './local-editor-ui-panels.js';
26
28
  export { createLocalEditorBrowserInspectorGroupValueClipboardPayload, createLocalEditorBrowserInspectorVectorCopyPayload, parseLocalEditorBrowserInspectorGroupValueClipboardPayload, } from './local-editor-inspector-clipboard.js';
27
29
  export { LOCAL_EDITOR_BROWSER_INSPECTOR_VEC3_AXES, isLocalEditorBrowserInspectorTransformVectorGroupPath, resolveLocalEditorBrowserInspectorVectorGroupAxisFromPropertyPath, resolveLocalEditorBrowserInspectorVectorGroupPathFromPropertyPath, resolveLocalEditorBrowserInspectorVectorGroupPathFromProperties, } from './local-editor-inspector-groups.js';
@@ -66,8 +68,24 @@ function readInspectorInputValue(input, source) {
66
68
  b: (numeric & 255) / 255,
67
69
  };
68
70
  }
71
+ if (input.dataset.serializedJsonValue === 'true') {
72
+ try {
73
+ const parsed = JSON.parse(input.value);
74
+ return isInspectorSerializedInputValue(parsed) ? parsed : null;
75
+ }
76
+ catch {
77
+ return null;
78
+ }
79
+ }
69
80
  return input.value;
70
81
  }
82
+ function isInspectorSerializedInputValue(value) {
83
+ return value == null
84
+ || typeof value === 'number'
85
+ || typeof value === 'string'
86
+ || typeof value === 'boolean'
87
+ || (!!value && typeof value === 'object' && !Array.isArray(value));
88
+ }
71
89
  const TRANSFORM_TOOL_ICONS = {
72
90
  select: 'select',
73
91
  move: 'move',
@@ -206,7 +224,8 @@ function createInspectorPropertyInputs(input, source, lockedVectorGroups) {
206
224
  if (!propertyInput)
207
225
  return null;
208
226
  const groupPath = resolveLocalEditorBrowserInspectorVectorGroupPathFromPropertyPath(propertyInput.path);
209
- if (!groupPath || !lockedVectorGroups.has(groupPath))
227
+ const forceVectorLock = input.dataset.serializedForceVectorLock === 'true';
228
+ if (!groupPath || (!forceVectorLock && !lockedVectorGroups.has(groupPath)))
210
229
  return [propertyInput];
211
230
  if (propertyInput.control !== 'number' || propertyInput.valueType !== 'number')
212
231
  return [propertyInput];
@@ -352,6 +371,33 @@ function createRenderingActionInput(button) {
352
371
  ...(params ? { params } : {}),
353
372
  };
354
373
  }
374
+ function createInspectorActionInput(button) {
375
+ const actionId = button.dataset.editorInspectorAction;
376
+ if (!actionId)
377
+ return null;
378
+ const params = parseRenderingActionParams(button.dataset.editorInspectorActionParams);
379
+ const targetIds = parseInspectorActionTargetIds(button.dataset.editorInspectorActionTargetIds);
380
+ return {
381
+ actionId,
382
+ targetId: button.dataset.editorInspectorActionTargetId || null,
383
+ targetIds,
384
+ path: button.dataset.editorInspectorActionPath,
385
+ ...(params ? { params } : {}),
386
+ };
387
+ }
388
+ function parseInspectorActionTargetIds(value) {
389
+ if (!value)
390
+ return undefined;
391
+ try {
392
+ const parsed = JSON.parse(value);
393
+ return Array.isArray(parsed) && parsed.every(item => typeof item === 'string')
394
+ ? parsed
395
+ : undefined;
396
+ }
397
+ catch {
398
+ return undefined;
399
+ }
400
+ }
355
401
  function parseRenderingActionParams(value) {
356
402
  if (!value)
357
403
  return null;
@@ -371,7 +417,40 @@ function readInspectorCommitMode(input) {
371
417
  function readRenderingCommitMode(input) {
372
418
  return input.dataset.renderingCommitMode ?? 'live';
373
419
  }
420
+ function isLocalEditorRightDockTab(value) {
421
+ return value === 'inspector' || value === 'rendering' || value === 'markers';
422
+ }
374
423
  function resolveLocalEditorRightDockContentKey(state, tab, inspectorFilter) {
424
+ if (tab === 'markers') {
425
+ const markerPanel = state.markerPanel;
426
+ if (!markerPanel)
427
+ return 'markers:none';
428
+ return [
429
+ 'markers',
430
+ markerPanel.activeMarkerId ?? '',
431
+ markerPanel.markersVisible === false ? 'markers-hidden' : 'markers-visible',
432
+ markerPanel.status ?? '',
433
+ markerPanel.markerTypes.map(markerType => [
434
+ markerType.type,
435
+ markerType.label,
436
+ markerType.kind ?? '',
437
+ markerType.description ?? '',
438
+ markerType.usedByCount ?? 0,
439
+ ].join('=')).join(';'),
440
+ (markerPanel.relations ?? []).map(relation => [
441
+ relation.id,
442
+ relation.type,
443
+ relation.from.kind,
444
+ relation.from.id,
445
+ relation.from.visibility ?? '',
446
+ relation.to.kind,
447
+ relation.to.id,
448
+ relation.to.visibility ?? '',
449
+ relation.selected ? 'selected' : '',
450
+ relation.status ?? '',
451
+ ].join('=')).join(';'),
452
+ ].join(':');
453
+ }
375
454
  if (tab === 'rendering') {
376
455
  const rendering = state.renderingPanel;
377
456
  if (!rendering)
@@ -396,20 +475,22 @@ function resolveLocalEditorRightDockContentKey(state, tab, inspectorFilter) {
396
475
  inspectorFilter,
397
476
  ].join(':');
398
477
  }
399
- function findLocalEditorRightDockScrollViewport(tab, inspectorPanel, renderingPanel) {
400
- const panel = tab === 'inspector' ? inspectorPanel : renderingPanel;
478
+ function findLocalEditorRightDockScrollViewport(tab, inspectorPanel, renderingPanel, markersPanel) {
479
+ const panel = tab === 'inspector' ? inspectorPanel : tab === 'rendering' ? renderingPanel : markersPanel;
401
480
  const selector = tab === 'inspector'
402
481
  ? '[data-editor-inspector-scroll-viewport]'
403
- : '[data-editor-rendering-scroll-viewport]';
482
+ : tab === 'rendering'
483
+ ? '[data-editor-rendering-scroll-viewport]'
484
+ : '[data-editor-markers-scroll-viewport]';
404
485
  return panel.querySelector(selector) ?? panel;
405
486
  }
406
- function captureLocalEditorRightDockScrollSnapshot(state, tab, inspectorFilter, inspectorPanel, renderingPanel) {
487
+ function captureLocalEditorRightDockScrollSnapshot(state, tab, inspectorFilter, inspectorPanel, renderingPanel, markersPanel) {
407
488
  if (!state || state.mode !== 'editor')
408
489
  return null;
409
- const viewport = findLocalEditorRightDockScrollViewport(tab, inspectorPanel, renderingPanel);
490
+ const viewport = findLocalEditorRightDockScrollViewport(tab, inspectorPanel, renderingPanel, markersPanel);
410
491
  if (!viewport)
411
492
  return null;
412
- const panel = tab === 'inspector' ? inspectorPanel : renderingPanel;
493
+ const panel = tab === 'inspector' ? inspectorPanel : tab === 'rendering' ? renderingPanel : markersPanel;
413
494
  return {
414
495
  tab,
415
496
  contentKey: resolveLocalEditorRightDockContentKey(state, tab, inspectorFilter),
@@ -418,15 +499,15 @@ function captureLocalEditorRightDockScrollSnapshot(state, tab, inspectorFilter,
418
499
  scrollTop: viewport.scrollTop,
419
500
  };
420
501
  }
421
- function restoreLocalEditorRightDockScrollSnapshot(snapshot, state, tab, inspectorFilter, inspectorPanel, renderingPanel) {
502
+ function restoreLocalEditorRightDockScrollSnapshot(snapshot, state, tab, inspectorFilter, inspectorPanel, renderingPanel, markersPanel) {
422
503
  if (!snapshot || snapshot.tab !== tab || state.mode !== 'editor')
423
504
  return;
424
- const panel = tab === 'inspector' ? inspectorPanel : renderingPanel;
505
+ const panel = tab === 'inspector' ? inspectorPanel : tab === 'rendering' ? renderingPanel : markersPanel;
425
506
  if ((panel.dataset.editorPanelId ?? tab) !== snapshot.panelId)
426
507
  return;
427
508
  if (resolveLocalEditorRightDockContentKey(state, tab, inspectorFilter) !== snapshot.contentKey)
428
509
  return;
429
- const viewport = findLocalEditorRightDockScrollViewport(tab, inspectorPanel, renderingPanel);
510
+ const viewport = findLocalEditorRightDockScrollViewport(tab, inspectorPanel, renderingPanel, markersPanel);
430
511
  if (!viewport)
431
512
  return;
432
513
  viewport.scrollLeft = snapshot.scrollLeft;
@@ -455,6 +536,132 @@ function readRenderingImmediateSource(input) {
455
536
  return 'list';
456
537
  return 'input';
457
538
  }
539
+ function createMarkerTypeActionInput(button) {
540
+ const action = button.dataset.editorMarkerTypeAction;
541
+ if (action === 'create') {
542
+ const block = button.closest('[data-editor-marker-type-create]');
543
+ if (!block)
544
+ return null;
545
+ const type = readMarkerTypeFieldValue(block, 'type');
546
+ if (!type)
547
+ return null;
548
+ const label = readMarkerTypeFieldValue(block, 'label') || type;
549
+ return {
550
+ action: 'create',
551
+ type,
552
+ label,
553
+ kind: readMarkerTypeFieldValue(block, 'kind') || undefined,
554
+ description: readMarkerTypeFieldValue(block, 'description') || undefined,
555
+ };
556
+ }
557
+ const row = button.closest('[data-editor-marker-type-row]');
558
+ const type = row?.dataset.editorMarkerTypeRow?.trim() ?? '';
559
+ if (!row || !type)
560
+ return null;
561
+ if (action === 'delete')
562
+ return { action: 'delete', type };
563
+ if (action !== 'update')
564
+ return null;
565
+ return {
566
+ action: 'update',
567
+ type,
568
+ label: readMarkerTypeFieldValue(row, 'label') || type,
569
+ kind: readMarkerTypeFieldValue(row, 'kind') || undefined,
570
+ description: readMarkerTypeFieldValue(row, 'description'),
571
+ };
572
+ }
573
+ function createMarkerRelationActionInput(target) {
574
+ const action = target.dataset.editorMarkerRelationAction;
575
+ if (action === 'delete') {
576
+ const row = target.closest('[data-editor-marker-relation-row]');
577
+ const relationId = row?.dataset.editorMarkerRelationRow?.trim() ?? '';
578
+ return relationId ? { action: 'delete', relationId } : null;
579
+ }
580
+ if (action === 'reverse') {
581
+ const row = target.closest('[data-editor-marker-relation-row]');
582
+ const relationId = row?.dataset.editorMarkerRelationRow?.trim() ?? '';
583
+ return relationId ? { action: 'reverse', relationId } : null;
584
+ }
585
+ if (action === 'delete-connected') {
586
+ const markerId = target.dataset.editorMarkerRelationMarkerId?.trim() ?? '';
587
+ return { action: 'delete-connected', ...(markerId ? { markerId } : {}) };
588
+ }
589
+ if (action === 'select') {
590
+ const row = target.closest('[data-editor-marker-relation-row]');
591
+ const relationId = row?.dataset.editorMarkerRelationRow?.trim() ?? '';
592
+ return relationId ? { action: 'select', relationId } : null;
593
+ }
594
+ if (action === 'pick-cancel')
595
+ return { action: 'pick-cancel' };
596
+ if (action !== 'create' && action !== 'pick-start')
597
+ return null;
598
+ const block = target.closest('[data-editor-marker-relation-create]');
599
+ if (!block)
600
+ return null;
601
+ const type = readMarkerRelationTypeFieldValue(block);
602
+ const from = readMarkerRelationEndpointFieldValue(block, 'from');
603
+ const to = readMarkerRelationEndpointFieldValue(block, 'to');
604
+ if (!type || !from || (action === 'create' && !to))
605
+ return null;
606
+ const directed = readMarkerRelationDirectionFieldValue(block);
607
+ if (action === 'pick-start') {
608
+ return {
609
+ action: 'pick-start',
610
+ type,
611
+ fromKind: from.kind,
612
+ fromId: from.id,
613
+ ...(directed === null ? {} : { directed }),
614
+ };
615
+ }
616
+ return {
617
+ action: 'create',
618
+ type,
619
+ fromKind: from.kind,
620
+ fromId: from.id,
621
+ toKind: to.kind,
622
+ toId: to.id,
623
+ ...(directed === null ? {} : { directed }),
624
+ };
625
+ }
626
+ function readMarkerTypeFieldValue(parent, field) {
627
+ const input = parent.querySelector(`[data-editor-marker-type-field="${field}"]`);
628
+ return input?.value.trim() ?? '';
629
+ }
630
+ function readMarkerRelationTypeFieldValue(parent) {
631
+ const select = parent.querySelector('[data-editor-marker-relation-field="type"]');
632
+ return select?.value.trim() ?? '';
633
+ }
634
+ function readMarkerRelationDirectionFieldValue(parent) {
635
+ const select = parent.querySelector('[data-editor-marker-relation-field="directed"]');
636
+ if (!select || select.value === 'default')
637
+ return null;
638
+ return select.value === 'true';
639
+ }
640
+ function readMarkerRelationEndpointFieldValue(parent, field) {
641
+ const select = parent.querySelector(`[data-editor-marker-relation-field="${field}"]`);
642
+ const option = select?.selectedOptions.item(0) ?? null;
643
+ const kind = option?.dataset.editorMarkerRelationEndpointKind?.trim();
644
+ const id = option?.dataset.editorMarkerRelationEndpointId?.trim();
645
+ if (kind && id)
646
+ return { kind, id };
647
+ return decodeMarkerRelationEndpointValue(select?.value);
648
+ }
649
+ function decodeMarkerRelationEndpointValue(value) {
650
+ const raw = value?.trim();
651
+ if (!raw)
652
+ return null;
653
+ const separator = raw.indexOf(':');
654
+ if (separator <= 0)
655
+ return null;
656
+ const kind = raw.slice(0, separator);
657
+ const encodedId = raw.slice(separator + 1);
658
+ try {
659
+ return { kind, id: decodeURIComponent(encodedId) };
660
+ }
661
+ catch {
662
+ return { kind, id: encodedId };
663
+ }
664
+ }
458
665
  function markLocalEditorKeyboardCommittedValue(input) {
459
666
  input.dataset.editorKeyboardCommittedValue = input.value;
460
667
  }
@@ -764,6 +971,50 @@ const PERFORMANCE_MONITOR_METRIC_GROUPS = [
764
971
  ],
765
972
  },
766
973
  ];
974
+ const PREFAB_IMPORT_STATS_METRICS = [
975
+ {
976
+ key: 'source',
977
+ label: 'Source',
978
+ description: '当前 prefab preview 使用的 source model 或 source url。',
979
+ format: stats => stats?.sourceId ?? stats?.assetId ?? '--',
980
+ },
981
+ {
982
+ key: 'nodes',
983
+ label: 'Nodes',
984
+ description: 'Babylon 导入后 runtime snapshot 中的节点总数,包含虚拟 root。',
985
+ format: stats => formatPerformanceInteger(stats?.nodeCount ?? null),
986
+ },
987
+ {
988
+ key: 'transforms',
989
+ label: 'Transforms',
990
+ description: 'runtime snapshot 中 root/transform 节点数量。',
991
+ format: stats => formatPerformanceInteger(stats?.transformCount ?? null),
992
+ },
993
+ {
994
+ key: 'meshes',
995
+ label: 'Meshes',
996
+ description: 'runtime snapshot 中可渲染 mesh 节点数量。',
997
+ format: stats => formatPerformanceInteger(stats?.meshCount ?? null),
998
+ },
999
+ {
1000
+ key: 'materials',
1001
+ label: 'Materials',
1002
+ description: 'runtime snapshot 关联到的材质数量。',
1003
+ format: stats => formatPerformanceInteger(stats?.materialCount ?? null),
1004
+ },
1005
+ {
1006
+ key: 'textures',
1007
+ label: 'Textures',
1008
+ description: 'runtime snapshot 关联到的贴图数量。',
1009
+ format: stats => formatPerformanceInteger(stats?.textureCount ?? null),
1010
+ },
1011
+ {
1012
+ key: 'animations',
1013
+ label: 'Animations',
1014
+ description: 'runtime snapshot 关联到的 animation group 数量。',
1015
+ format: stats => formatPerformanceInteger(stats?.animationCount ?? null),
1016
+ },
1017
+ ];
767
1018
  function createSceneFrameRateOverlay(doc) {
768
1019
  const root = doc.createElement('div');
769
1020
  root.classList.add(LOCAL_EDITOR_THEME_CLASS);
@@ -982,6 +1233,359 @@ function renderPerformanceMonitorOverlay(overlay, stats, open) {
982
1233
  stats?.activeReasons.length ? `active: ${stats.activeReasons.join(', ')}` : '',
983
1234
  ].filter(Boolean).join(' | ');
984
1235
  }
1236
+ function createPrefabImportStatsOverlay(doc) {
1237
+ const root = doc.createElement('section');
1238
+ root.classList.add(LOCAL_EDITOR_THEME_CLASS);
1239
+ root.dataset.editorPrefabImportStatsOverlay = 'true';
1240
+ root.setAttribute('aria-label', 'Prefab import diagnostics');
1241
+ root.style.cssText = [
1242
+ 'display:none',
1243
+ 'width:236px',
1244
+ 'max-width:100%',
1245
+ 'box-sizing:border-box',
1246
+ 'padding:10px',
1247
+ 'border:1px solid color-mix(in srgb, var(--fps-editor-border) 78%, transparent)',
1248
+ 'border-radius:6px',
1249
+ 'background:color-mix(in srgb, var(--fps-editor-chrome) 88%, transparent)',
1250
+ 'box-shadow:var(--fps-editor-shadow-panel)',
1251
+ 'backdrop-filter:blur(6px)',
1252
+ 'font-family:var(--fps-editor-font)',
1253
+ 'font-size:11px',
1254
+ 'font-weight:800',
1255
+ 'color:var(--fps-editor-text)',
1256
+ 'pointer-events:auto',
1257
+ ].join(';');
1258
+ const header = doc.createElement('div');
1259
+ header.style.cssText = 'display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:8px;';
1260
+ const title = doc.createElement('div');
1261
+ title.textContent = 'Prefab Import';
1262
+ title.style.cssText = 'min-width:0;color:var(--fps-editor-text-strong);font-size:12px;font-weight:900;line-height:1.2;';
1263
+ const modeLabel = doc.createElement('span');
1264
+ modeLabel.textContent = 'MISSING';
1265
+ modeLabel.style.cssText = [
1266
+ 'flex:0 0 auto',
1267
+ 'padding:2px 5px',
1268
+ 'border:1px solid var(--fps-editor-border-soft)',
1269
+ 'border-radius:4px',
1270
+ 'background:color-mix(in srgb, var(--fps-editor-field) 72%, transparent)',
1271
+ 'color:var(--fps-editor-muted)',
1272
+ 'font-size:9px',
1273
+ 'line-height:1',
1274
+ 'font-weight:900',
1275
+ ].join(';');
1276
+ header.append(title, modeLabel);
1277
+ const grid = doc.createElement('div');
1278
+ grid.style.cssText = [
1279
+ 'display:grid',
1280
+ 'grid-template-columns:minmax(0,1fr) minmax(76px,108px)',
1281
+ 'gap:5px 10px',
1282
+ 'align-items:center',
1283
+ ].join(';');
1284
+ const values = {};
1285
+ for (const metric of PREFAB_IMPORT_STATS_METRICS) {
1286
+ const rowLabel = doc.createElement('span');
1287
+ rowLabel.textContent = metric.label;
1288
+ rowLabel.title = metric.description;
1289
+ rowLabel.setAttribute('aria-label', `${metric.label}: ${metric.description}`);
1290
+ rowLabel.style.cssText = [
1291
+ 'min-width:0',
1292
+ 'overflow:hidden',
1293
+ 'text-overflow:ellipsis',
1294
+ 'white-space:nowrap',
1295
+ 'color:var(--fps-editor-muted-strong)',
1296
+ 'cursor:help',
1297
+ ].join(';');
1298
+ const value = doc.createElement('span');
1299
+ value.textContent = '--';
1300
+ value.title = metric.description;
1301
+ value.setAttribute('aria-label', `${metric.label}: --。${metric.description}`);
1302
+ value.style.cssText = [
1303
+ 'min-width:0',
1304
+ 'justify-self:end',
1305
+ 'max-width:108px',
1306
+ 'overflow:hidden',
1307
+ 'text-overflow:ellipsis',
1308
+ 'white-space:nowrap',
1309
+ 'font-variant-numeric:tabular-nums',
1310
+ 'color:var(--fps-editor-text-strong)',
1311
+ 'text-align:right',
1312
+ 'cursor:help',
1313
+ ].join(';');
1314
+ values[metric.key] = value;
1315
+ grid.append(rowLabel, value);
1316
+ }
1317
+ root.append(header, grid);
1318
+ return { root, modeLabel, values };
1319
+ }
1320
+ function renderPrefabImportStatsOverlay(overlay, stage) {
1321
+ if (!stage) {
1322
+ overlay.root.style.display = 'none';
1323
+ return;
1324
+ }
1325
+ overlay.root.style.display = 'block';
1326
+ const stats = stage.importStats ?? null;
1327
+ const status = stats?.status ?? (stage.importStructureReady === false ? 'loading' : 'missing');
1328
+ overlay.modeLabel.textContent = formatPrefabImportStatsStatusLabel(status);
1329
+ overlay.modeLabel.style.color = status === 'ready'
1330
+ ? 'var(--fps-editor-success)'
1331
+ : status === 'loading'
1332
+ ? 'var(--fps-editor-warn)'
1333
+ : 'var(--fps-editor-danger-strong)';
1334
+ for (const metric of PREFAB_IMPORT_STATS_METRICS) {
1335
+ const value = metric.format(stats);
1336
+ overlay.values[metric.key].textContent = value;
1337
+ overlay.values[metric.key].title = `${metric.description}\n${value}`;
1338
+ overlay.values[metric.key].setAttribute('aria-label', `${metric.label}: ${value}。${metric.description}`);
1339
+ }
1340
+ overlay.root.title = [
1341
+ `status: ${status}`,
1342
+ stats?.projectionNodeId ? `preview: ${stats.projectionNodeId}` : stage.previewNodeId ? `preview: ${stage.previewNodeId}` : '',
1343
+ stats?.assetId ? `asset: ${stats.assetId}` : '',
1344
+ stats?.sourceId ? `source: ${stats.sourceId}` : '',
1345
+ ].filter(Boolean).join(' | ');
1346
+ }
1347
+ function createPrefabStagePreviewLayout(doc) {
1348
+ const root = doc.createElement('section');
1349
+ root.classList.add(LOCAL_EDITOR_THEME_CLASS);
1350
+ root.dataset.editorPrefabStagePreviewLayout = 'true';
1351
+ root.setAttribute('aria-label', 'Prefab stage preview layout');
1352
+ root.style.cssText = [
1353
+ 'position:absolute',
1354
+ 'left:0',
1355
+ 'right:0',
1356
+ 'top:38px',
1357
+ 'bottom:0',
1358
+ 'z-index:4',
1359
+ 'display:none',
1360
+ 'pointer-events:none',
1361
+ 'overflow:hidden',
1362
+ 'font-family:var(--fps-editor-font)',
1363
+ ].join(';');
1364
+ const mainRegion = doc.createElement('div');
1365
+ mainRegion.dataset.editorPrefabStageMainPreview = 'true';
1366
+ mainRegion.style.cssText = [
1367
+ 'position:absolute',
1368
+ 'left:0',
1369
+ 'top:0',
1370
+ 'bottom:0',
1371
+ 'right:0',
1372
+ 'min-width:0',
1373
+ 'overflow:hidden',
1374
+ 'pointer-events:none',
1375
+ ].join(';');
1376
+ const comparisonColumn = doc.createElement('aside');
1377
+ comparisonColumn.dataset.editorPrefabStageComparisonColumn = 'true';
1378
+ comparisonColumn.setAttribute('aria-label', 'Prefab comparison previews');
1379
+ comparisonColumn.style.cssText = [
1380
+ 'position:absolute',
1381
+ 'top:0',
1382
+ 'right:0',
1383
+ 'bottom:0',
1384
+ `width:${LOCAL_EDITOR_PREFAB_COMPARISON_COLUMN_WIDTH_PX}px`,
1385
+ 'display:none',
1386
+ 'flex-direction:column',
1387
+ 'box-sizing:border-box',
1388
+ 'gap:10px',
1389
+ 'padding:10px',
1390
+ 'border-left:1px solid color-mix(in srgb, var(--fps-editor-divider) 82%, transparent)',
1391
+ 'background:transparent',
1392
+ 'box-shadow:none',
1393
+ 'pointer-events:auto',
1394
+ 'overflow:hidden',
1395
+ ].join(';');
1396
+ root.append(mainRegion, comparisonColumn);
1397
+ return { root, mainRegion, comparisonColumn };
1398
+ }
1399
+ function createPrefabComparisonPreviewOverlay(doc) {
1400
+ const root = doc.createElement('section');
1401
+ root.classList.add(LOCAL_EDITOR_THEME_CLASS);
1402
+ root.dataset.editorPrefabComparisonPreviewOverlay = 'true';
1403
+ root.setAttribute('aria-label', 'Prefab clone and InstanceMesh comparison previews');
1404
+ root.style.cssText = [
1405
+ 'display:none',
1406
+ 'flex:1 1 auto',
1407
+ 'min-height:0',
1408
+ 'width:100%',
1409
+ 'box-sizing:border-box',
1410
+ 'grid-template-rows:minmax(0,1fr) minmax(0,1fr)',
1411
+ 'gap:10px',
1412
+ 'overflow:hidden',
1413
+ 'font-family:var(--fps-editor-font)',
1414
+ 'pointer-events:auto',
1415
+ ].join(';');
1416
+ return { root, cards: new Map() };
1417
+ }
1418
+ function renderPrefabComparisonPreviewOverlay(overlay, stage) {
1419
+ const previews = stage?.comparisonPreviews ?? [];
1420
+ if (!stage || previews.length === 0) {
1421
+ overlay.root.style.display = 'none';
1422
+ return;
1423
+ }
1424
+ overlay.root.style.display = 'grid';
1425
+ const visibleIds = new Set();
1426
+ for (const preview of previews) {
1427
+ const card = getOrCreatePrefabComparisonPreviewCard(overlay, preview);
1428
+ visibleIds.add(preview.id);
1429
+ card.root.style.display = 'flex';
1430
+ card.title.textContent = preview.label;
1431
+ card.statusLabel.textContent = formatLocalEditorPrefabStageComparisonPreviewStatus(preview.status);
1432
+ card.statusLabel.style.color = getPrefabComparisonPreviewStatusColor(preview.status);
1433
+ card.surface.dataset.editorPrefabComparisonPreviewId = preview.id;
1434
+ card.surface.dataset.editorPrefabComparisonPreviewSurface = preview.projectionNodeId;
1435
+ card.surface.dataset.editorPrefabComparisonPreviewStatus = preview.status;
1436
+ card.surface.style.background = preview.status === 'ready'
1437
+ ? 'transparent'
1438
+ : 'color-mix(in srgb, var(--fps-editor-field) 74%, transparent)';
1439
+ card.surface.textContent = preview.status === 'ready'
1440
+ ? ''
1441
+ : preview.status === 'loading'
1442
+ ? 'Preview loading'
1443
+ : 'Preview unavailable';
1444
+ card.root.title = [
1445
+ `${preview.label}: ${preview.status}`,
1446
+ `mode: ${preview.mode}`,
1447
+ `projection: ${preview.projectionNodeId}`,
1448
+ ].join(' | ');
1449
+ }
1450
+ for (const [id, card] of overlay.cards) {
1451
+ if (!visibleIds.has(id))
1452
+ card.root.style.display = 'none';
1453
+ }
1454
+ }
1455
+ function readPrefabComparisonPreviewSurfaces(overlay) {
1456
+ if (overlay.root.style.display === 'none')
1457
+ return [];
1458
+ const surfaces = [];
1459
+ for (const [previewId, card] of overlay.cards) {
1460
+ if (card.root.style.display === 'none')
1461
+ continue;
1462
+ const projectionNodeId = card.surface.dataset.editorPrefabComparisonPreviewSurface;
1463
+ if (!projectionNodeId)
1464
+ continue;
1465
+ const rect = card.surface.getBoundingClientRect();
1466
+ if (rect.width < 1 || rect.height < 1)
1467
+ continue;
1468
+ surfaces.push({
1469
+ previewId,
1470
+ projectionNodeId,
1471
+ element: card.surface,
1472
+ rect,
1473
+ });
1474
+ }
1475
+ return surfaces;
1476
+ }
1477
+ function findPrefabComparisonPreviewSurface(target) {
1478
+ return target instanceof HTMLElement
1479
+ ? target.closest('[data-editor-prefab-comparison-preview-surface]')
1480
+ : null;
1481
+ }
1482
+ function readPrefabComparisonPreviewSurfaceId(surface) {
1483
+ return surface.dataset.editorPrefabComparisonPreviewId
1484
+ ?? surface.closest('[data-editor-prefab-comparison-preview]')?.dataset.editorPrefabComparisonPreview
1485
+ ?? null;
1486
+ }
1487
+ function isPrefabComparisonPreviewSurfaceReady(surface) {
1488
+ return surface.dataset.editorPrefabComparisonPreviewStatus === 'ready';
1489
+ }
1490
+ function getOrCreatePrefabComparisonPreviewCard(overlay, preview) {
1491
+ const existing = overlay.cards.get(preview.id);
1492
+ if (existing)
1493
+ return existing;
1494
+ const doc = overlay.root.ownerDocument;
1495
+ const root = doc.createElement('article');
1496
+ root.dataset.editorPrefabComparisonPreview = preview.id;
1497
+ root.style.cssText = [
1498
+ 'display:flex',
1499
+ 'flex-direction:column',
1500
+ 'min-width:0',
1501
+ 'min-height:0',
1502
+ 'flex:1 1 0',
1503
+ 'box-sizing:border-box',
1504
+ 'padding:8px',
1505
+ 'border:1px solid color-mix(in srgb, var(--fps-editor-border) 78%, transparent)',
1506
+ 'border-radius:6px',
1507
+ 'background:transparent',
1508
+ 'box-shadow:none',
1509
+ 'color:var(--fps-editor-text)',
1510
+ 'pointer-events:auto',
1511
+ 'overflow:hidden',
1512
+ ].join(';');
1513
+ const header = doc.createElement('div');
1514
+ header.style.cssText = [
1515
+ 'display:flex',
1516
+ 'align-items:center',
1517
+ 'justify-content:space-between',
1518
+ 'gap:8px',
1519
+ 'margin-bottom:7px',
1520
+ 'padding:6px 8px',
1521
+ 'border:1px solid color-mix(in srgb, var(--fps-editor-border) 74%, transparent)',
1522
+ 'border-radius:5px',
1523
+ 'background:color-mix(in srgb, var(--fps-editor-chrome) 86%, transparent)',
1524
+ 'box-shadow:0 6px 14px color-mix(in srgb, #000 18%, transparent)',
1525
+ ].join(';');
1526
+ const title = doc.createElement('div');
1527
+ title.textContent = preview.label;
1528
+ title.style.cssText = [
1529
+ 'min-width:0',
1530
+ 'overflow:hidden',
1531
+ 'text-overflow:ellipsis',
1532
+ 'white-space:nowrap',
1533
+ 'color:var(--fps-editor-text-strong)',
1534
+ 'font-size:12px',
1535
+ 'line-height:1.2',
1536
+ 'font-weight:900',
1537
+ ].join(';');
1538
+ const statusLabel = doc.createElement('span');
1539
+ statusLabel.textContent = 'UNAVAILABLE';
1540
+ statusLabel.style.cssText = [
1541
+ 'flex:0 0 auto',
1542
+ 'padding:2px 5px',
1543
+ 'border:1px solid var(--fps-editor-border-soft)',
1544
+ 'border-radius:4px',
1545
+ 'background:color-mix(in srgb, var(--fps-editor-field) 72%, transparent)',
1546
+ 'font-size:9px',
1547
+ 'line-height:1',
1548
+ 'font-weight:900',
1549
+ ].join(';');
1550
+ header.append(title, statusLabel);
1551
+ const surface = doc.createElement('div');
1552
+ surface.style.cssText = [
1553
+ 'display:flex',
1554
+ 'flex:1 1 auto',
1555
+ 'align-items:center',
1556
+ 'justify-content:center',
1557
+ 'width:100%',
1558
+ 'min-height:0',
1559
+ 'box-sizing:border-box',
1560
+ 'border:1px solid color-mix(in srgb, var(--fps-editor-border-soft) 84%, transparent)',
1561
+ 'border-radius:4px',
1562
+ 'background:transparent',
1563
+ 'overflow:hidden',
1564
+ 'color:var(--fps-editor-muted)',
1565
+ 'font-size:10px',
1566
+ 'font-weight:800',
1567
+ 'text-align:center',
1568
+ 'touch-action:none',
1569
+ 'user-select:none',
1570
+ 'cursor:grab',
1571
+ ].join(';');
1572
+ root.append(header, surface);
1573
+ overlay.root.appendChild(root);
1574
+ const card = { root, title, statusLabel, surface };
1575
+ overlay.cards.set(preview.id, card);
1576
+ return card;
1577
+ }
1578
+ function getPrefabComparisonPreviewStatusColor(status) {
1579
+ const tone = resolveLocalEditorPrefabStageComparisonPreviewTone(status);
1580
+ return tone === 'success'
1581
+ ? 'var(--fps-editor-success)'
1582
+ : tone === 'warning'
1583
+ ? 'var(--fps-editor-warn)'
1584
+ : 'var(--fps-editor-danger-strong)';
1585
+ }
1586
+ function formatPrefabImportStatsStatusLabel(status) {
1587
+ return status === 'ready' ? 'READY' : status === 'loading' ? 'LOADING' : 'MISSING';
1588
+ }
985
1589
  function formatPerformanceInteger(value) {
986
1590
  return typeof value === 'number' && Number.isFinite(value) ? String(Math.round(value)) : '--';
987
1591
  }
@@ -1413,10 +2017,10 @@ function formatSvgNumber(value) {
1413
2017
  return Number.isFinite(value) ? value.toFixed(2) : '0';
1414
2018
  }
1415
2019
  function isLocalEditorAssetBrowserTab(value) {
1416
- return value === 'all' || value === 'models' || value === 'materials' || value === 'textures';
2020
+ return value === 'all' || value === 'models' || value === 'prefabs' || value === 'materials' || value === 'textures';
1417
2021
  }
1418
- function createAssetBrowserMenuItems(state, asset) {
1419
- const assetKind = resolveBrowserAssetKind(asset);
2022
+ export function createAssetBrowserMenuItems(state, asset) {
2023
+ const assetKind = resolveLocalEditorBrowserAssetKind(asset);
1420
2024
  const placeable = asset.placeable !== false && asset.disabled !== true;
1421
2025
  const activeId = state.activeId;
1422
2026
  const items = [];
@@ -1449,6 +2053,20 @@ function createAssetBrowserMenuItems(state, asset) {
1449
2053
  label: '查看贴图预览',
1450
2054
  });
1451
2055
  }
2056
+ else if (assetKind === 'prefab') {
2057
+ items.push({
2058
+ id: 'asset.edit-prefab',
2059
+ label: '编辑 Prefab',
2060
+ disabled: !asset.prefab,
2061
+ disabledReason: '该预制体资产缺少可编辑参数。',
2062
+ });
2063
+ items.push({
2064
+ id: 'asset.duplicate-prefab',
2065
+ label: '复制 Prefab',
2066
+ disabled: !asset.assetId && !asset.id,
2067
+ disabledReason: '该预制体资产缺少可复制的 Prefab ID。',
2068
+ });
2069
+ }
1452
2070
  if (assetKind === 'material') {
1453
2071
  items.push({
1454
2072
  id: 'asset.apply-material',
@@ -1486,6 +2104,21 @@ function createAssetBrowserMenuItems(state, asset) {
1486
2104
  disabledReason: '自发光遮罩绑定将在下一步接入。',
1487
2105
  });
1488
2106
  }
2107
+ else if (assetKind === 'prefab') {
2108
+ items.push({
2109
+ id: 'asset.add-to-scene',
2110
+ label: '添加 Prefab 到场景',
2111
+ separatorBefore: true,
2112
+ disabled: !placeable,
2113
+ disabledReason: '该 Prefab 当前不可放置。',
2114
+ });
2115
+ items.push({
2116
+ id: 'asset.place',
2117
+ label: '进入 Prefab 放置模式',
2118
+ disabled: !placeable,
2119
+ disabledReason: '该 Prefab 当前不可放置。',
2120
+ });
2121
+ }
1489
2122
  else {
1490
2123
  items.push({
1491
2124
  id: 'asset.add-to-scene',
@@ -1499,12 +2132,18 @@ function createAssetBrowserMenuItems(state, asset) {
1499
2132
  disabled: !placeable,
1500
2133
  disabledReason: '该模型当前不可放置。',
1501
2134
  });
2135
+ items.push({
2136
+ id: 'asset.create-prefab',
2137
+ label: '创建 Prefab',
2138
+ disabled: !asset.assetId && !asset.id,
2139
+ disabledReason: '该模型缺少可创建 Prefab 的 Asset ID。',
2140
+ separatorBefore: true,
2141
+ });
1502
2142
  items.push({
1503
2143
  id: 'asset.add-as-child',
1504
2144
  label: '添加为当前节点子对象',
1505
2145
  disabled: true,
1506
2146
  disabledReason: '子对象添加动作将在资产 patch 中接入。',
1507
- separatorBefore: true,
1508
2147
  });
1509
2148
  items.push({
1510
2149
  id: 'asset.replace-selected-model',
@@ -1532,22 +2171,12 @@ function getMaterialAssetDeleteDisabledReason(asset) {
1532
2171
  return `该材质正在被 ${usageCount} 处使用,请先替换或解绑。`;
1533
2172
  return undefined;
1534
2173
  }
1535
- function resolveBrowserAssetKind(asset) {
1536
- const kind = `${asset.kind ?? ''}`.trim().toLowerCase();
1537
- const id = `${asset.id ?? ''}`.trim().toLowerCase();
1538
- const assetId = `${asset.assetId ?? ''}`.trim().toLowerCase();
1539
- if (kind === 'material' || id.startsWith('material:') || assetId.startsWith('mat_'))
1540
- return 'material';
1541
- if (kind === 'texture' || kind === 'image' || id.startsWith('texture:') || assetId.startsWith('texture_'))
1542
- return 'texture';
1543
- return 'model';
1544
- }
1545
2174
  function createAssetBrowserActionInput(actionId, asset) {
1546
2175
  return {
1547
2176
  actionId,
1548
2177
  assetId: asset.assetId ?? asset.id,
1549
2178
  browserAssetId: asset.id,
1550
- assetKind: asset.kind,
2179
+ assetKind: asset.kind ?? resolveLocalEditorBrowserAssetKind(asset),
1551
2180
  };
1552
2181
  }
1553
2182
  function copyTextToClipboard(win, text) {
@@ -1755,6 +2384,7 @@ export function createLocalEditorBrowserUi(options = {}) {
1755
2384
  const redoButton = createToolbarIconButton(doc, '重做', 'redo', '重做');
1756
2385
  let helpOpen = false;
1757
2386
  let performanceMonitorOpen = false;
2387
+ let prefabComparisonPreviewOpen = true;
1758
2388
  let assetPreviewOpen = false;
1759
2389
  let previewedAsset = null;
1760
2390
  let assetPreviewMode = 'preview';
@@ -1762,6 +2392,8 @@ export function createLocalEditorBrowserUi(options = {}) {
1762
2392
  let assetPickerFilter = '';
1763
2393
  let assetPickerInput = null;
1764
2394
  let assetPickerProperty = null;
2395
+ let prefabStageExplorerFilter = '';
2396
+ let prefabComparisonPreviewDrag = null;
1765
2397
  const editorStatusButton = createToolbarIconButton(doc, '编辑器状态', 'status', '编辑器状态');
1766
2398
  editorStatusButton.dataset.editorStatusButton = 'true';
1767
2399
  editorStatusButton.style.cursor = 'help';
@@ -1772,14 +2404,22 @@ export function createLocalEditorBrowserUi(options = {}) {
1772
2404
  const rightDockTabs = doc.createElement('div');
1773
2405
  const inspectorPanel = createWorkbenchPanelContent(doc);
1774
2406
  const renderingPanel = createWorkbenchPanelContent(doc);
2407
+ const markersPanel = createWorkbenchPanelContent(doc);
1775
2408
  workbench.leftDock.appendChild(leftDockTabs);
1776
2409
  workbench.leftDock.appendChild(hierarchyPanel);
1777
2410
  workbench.rightDock.appendChild(rightDockTabs);
1778
2411
  workbench.rightDock.appendChild(inspectorPanel);
1779
2412
  workbench.rightDock.appendChild(renderingPanel);
2413
+ workbench.rightDock.appendChild(markersPanel);
1780
2414
  root.appendChild(workbench.root);
1781
2415
  tooltipSurfaces.add(workbench.root);
1782
- const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench);
2416
+ const workbenchLayoutController = createLocalEditorWorkbenchLayoutController(doc, workbench, {
2417
+ onAfterApply(layout) {
2418
+ if (currentState?.mode === 'editor' && currentState.prefabStage) {
2419
+ applyPrefabStageWorkbenchLayoutOverride(layout);
2420
+ }
2421
+ },
2422
+ });
1783
2423
  const findAssetBrowserScrollViewport = () => (assetPanel.querySelector('[data-editor-asset-browser-scroll-viewport]'));
1784
2424
  const assetBrowserViewportReveal = createLocalEditorViewportRevealController({
1785
2425
  getViewport: findAssetBrowserScrollViewport,
@@ -2152,13 +2792,20 @@ export function createLocalEditorBrowserUi(options = {}) {
2152
2792
  performanceMonitorButton.dataset.performanceMonitorToggle = 'true';
2153
2793
  const gridToggleButton = createToolbarIconButton(doc, '网格', 'grid', '显示 / 隐藏 Scene View 网格');
2154
2794
  gridToggleButton.dataset.editorGridToggle = 'true';
2795
+ const prefabComparisonPreviewButton = createToolbarIconButton(doc, '对比预览', 'eye', 'Compare Previews (Shift+P)', 'toggle');
2796
+ prefabComparisonPreviewButton.dataset.prefabComparisonPreviewToggle = 'true';
2155
2797
  const sceneHelpButton = createToolbarIconButton(doc, '快捷键', 'help', '快捷键与操作说明');
2156
2798
  sceneQuickActions.appendChild(localTestGroup);
2799
+ const prefabStageBackButton = createToolbarIconButton(doc, '返回场景', 'chevron-right', '返回主场景');
2800
+ prefabStageBackButton.dataset.editorPrefabStageBack = 'true';
2801
+ prefabStageBackButton.style.display = 'none';
2802
+ sceneQuickActions.appendChild(prefabStageBackButton);
2157
2803
  sceneQuickActions.appendChild(undoButton);
2158
2804
  sceneQuickActions.appendChild(redoButton);
2159
2805
  sceneQuickActions.appendChild(dirtyBadge);
2160
2806
  sceneUtilityActions.appendChild(performanceMonitorButton);
2161
2807
  sceneUtilityActions.appendChild(gridToggleButton);
2808
+ sceneUtilityActions.appendChild(prefabComparisonPreviewButton);
2162
2809
  sceneUtilityActions.appendChild(themeToggleButton);
2163
2810
  sceneUtilityActions.appendChild(sceneHelpButton);
2164
2811
  const cameraPreviewGroup = doc.createElement('div');
@@ -2190,6 +2837,9 @@ export function createLocalEditorBrowserUi(options = {}) {
2190
2837
  const projectionToggleButton = createToolbarIconButton(doc, '投影模式', 'projection-perspective', '切换投影模式');
2191
2838
  projectionToggleButton.dataset.viewportProjectionToggle = 'true';
2192
2839
  viewportToolsGroup.appendChild(projectionToggleButton);
2840
+ const markerVisibilityButton = createToolbarIconButton(doc, '标记视图', 'view-overlay', '隐藏标记与关系线', 'toggle');
2841
+ markerVisibilityButton.dataset.viewportMarkerVisibilityToggle = 'true';
2842
+ viewportToolsGroup.appendChild(markerVisibilityButton);
2193
2843
  const overlaySettingsButton = createToolbarIconButton(doc, '视口信息层设置', 'view-overlay', '视口信息层设置', 'settings');
2194
2844
  overlaySettingsButton.dataset.viewportOverlaySettingsToggle = 'true';
2195
2845
  viewportToolsGroup.appendChild(overlaySettingsButton);
@@ -2266,6 +2916,112 @@ export function createLocalEditorBrowserUi(options = {}) {
2266
2916
  const coordinateAxesOverlay = createCoordinateAxesOverlay(doc);
2267
2917
  const sceneFrameRateOverlay = createSceneFrameRateOverlay(doc);
2268
2918
  const performanceMonitorOverlay = createPerformanceMonitorOverlay(doc);
2919
+ const prefabImportStatsOverlay = createPrefabImportStatsOverlay(doc);
2920
+ const prefabComparisonPreviewOverlay = createPrefabComparisonPreviewOverlay(doc);
2921
+ const prefabStagePreviewLayout = createPrefabStagePreviewLayout(doc);
2922
+ prefabImportStatsOverlay.root.style.position = 'absolute';
2923
+ prefabImportStatsOverlay.root.style.top = '10px';
2924
+ prefabImportStatsOverlay.root.style.right = '14px';
2925
+ prefabComparisonPreviewOverlay.root.style.height = '100%';
2926
+ prefabStagePreviewLayout.mainRegion.appendChild(prefabImportStatsOverlay.root);
2927
+ prefabStagePreviewLayout.comparisonColumn.appendChild(prefabComparisonPreviewOverlay.root);
2928
+ const emitPrefabComparisonPreviewInteraction = (input) => {
2929
+ if (currentState?.mode !== 'editor' || !currentState.prefabStage)
2930
+ return;
2931
+ callbacks.onPrefabComparisonPreviewInteraction?.(input);
2932
+ };
2933
+ const endPrefabComparisonPreviewDrag = (pointerId) => {
2934
+ const drag = prefabComparisonPreviewDrag;
2935
+ if (!drag || (pointerId != null && drag.pointerId !== pointerId))
2936
+ return;
2937
+ try {
2938
+ if (!drag.surface.hasPointerCapture || drag.surface.hasPointerCapture(drag.pointerId)) {
2939
+ drag.surface.releasePointerCapture?.(drag.pointerId);
2940
+ }
2941
+ }
2942
+ catch {
2943
+ // Pointer capture may already be released by the browser when this cleanup runs.
2944
+ }
2945
+ drag.surface.style.cursor = 'grab';
2946
+ prefabComparisonPreviewDrag = null;
2947
+ };
2948
+ const onPrefabComparisonPreviewPointerDown = (event) => {
2949
+ if (event.button !== 0 || currentState?.mode !== 'editor' || !currentState.prefabStage)
2950
+ return;
2951
+ const surface = findPrefabComparisonPreviewSurface(event.target);
2952
+ if (!surface || !isPrefabComparisonPreviewSurfaceReady(surface))
2953
+ return;
2954
+ const previewId = readPrefabComparisonPreviewSurfaceId(surface);
2955
+ if (!previewId)
2956
+ return;
2957
+ event.preventDefault();
2958
+ event.stopPropagation();
2959
+ endPrefabComparisonPreviewDrag();
2960
+ try {
2961
+ surface.setPointerCapture?.(event.pointerId);
2962
+ }
2963
+ catch {
2964
+ // Pointer capture is an input nicety; drag state still works through the overlay listener.
2965
+ }
2966
+ surface.style.cursor = 'grabbing';
2967
+ prefabComparisonPreviewDrag = {
2968
+ pointerId: event.pointerId,
2969
+ previewId,
2970
+ surface,
2971
+ lastClientX: event.clientX,
2972
+ lastClientY: event.clientY,
2973
+ };
2974
+ };
2975
+ const onPrefabComparisonPreviewPointerMove = (event) => {
2976
+ const drag = prefabComparisonPreviewDrag;
2977
+ if (!drag || drag.pointerId !== event.pointerId)
2978
+ return;
2979
+ event.preventDefault();
2980
+ event.stopPropagation();
2981
+ const deltaX = event.clientX - drag.lastClientX;
2982
+ const deltaY = event.clientY - drag.lastClientY;
2983
+ drag.lastClientX = event.clientX;
2984
+ drag.lastClientY = event.clientY;
2985
+ if (Math.abs(deltaX) < 0.001 && Math.abs(deltaY) < 0.001)
2986
+ return;
2987
+ emitPrefabComparisonPreviewInteraction({
2988
+ kind: 'orbit',
2989
+ previewId: drag.previewId,
2990
+ deltaX,
2991
+ deltaY,
2992
+ });
2993
+ };
2994
+ const onPrefabComparisonPreviewPointerEnd = (event) => {
2995
+ const drag = prefabComparisonPreviewDrag;
2996
+ if (!drag || drag.pointerId !== event.pointerId)
2997
+ return;
2998
+ event.preventDefault();
2999
+ event.stopPropagation();
3000
+ endPrefabComparisonPreviewDrag(event.pointerId);
3001
+ };
3002
+ const onPrefabComparisonPreviewWheel = (event) => {
3003
+ if (currentState?.mode !== 'editor' || !currentState.prefabStage)
3004
+ return;
3005
+ const surface = findPrefabComparisonPreviewSurface(event.target);
3006
+ if (!surface || !isPrefabComparisonPreviewSurfaceReady(surface))
3007
+ return;
3008
+ const previewId = readPrefabComparisonPreviewSurfaceId(surface);
3009
+ if (!previewId)
3010
+ return;
3011
+ event.preventDefault();
3012
+ event.stopPropagation();
3013
+ emitPrefabComparisonPreviewInteraction({
3014
+ kind: 'zoom',
3015
+ previewId,
3016
+ deltaY: event.deltaY,
3017
+ });
3018
+ };
3019
+ prefabComparisonPreviewOverlay.root.addEventListener('pointerdown', onPrefabComparisonPreviewPointerDown);
3020
+ prefabComparisonPreviewOverlay.root.addEventListener('pointermove', onPrefabComparisonPreviewPointerMove);
3021
+ prefabComparisonPreviewOverlay.root.addEventListener('pointerup', onPrefabComparisonPreviewPointerEnd);
3022
+ prefabComparisonPreviewOverlay.root.addEventListener('pointercancel', onPrefabComparisonPreviewPointerEnd);
3023
+ prefabComparisonPreviewOverlay.root.addEventListener('lostpointercapture', onPrefabComparisonPreviewPointerEnd);
3024
+ prefabComparisonPreviewOverlay.root.addEventListener('wheel', onPrefabComparisonPreviewWheel, { passive: false });
2269
3025
  const spatialOverlay = createSpatialOverlay(doc);
2270
3026
  const measurementOverlay = createMeasurementOverlay(doc);
2271
3027
  sceneToolOverlay.appendChild(sceneTitle);
@@ -2286,6 +3042,7 @@ export function createLocalEditorBrowserUi(options = {}) {
2286
3042
  workbench.sceneFrame.appendChild(measurementOverlay.root);
2287
3043
  workbench.sceneFrame.appendChild(sceneFrameRateOverlay.root);
2288
3044
  workbench.sceneFrame.appendChild(performanceMonitorOverlay.root);
3045
+ workbench.sceneFrame.appendChild(prefabStagePreviewLayout.root);
2289
3046
  workbench.sceneFrame.appendChild(coordinateAxesOverlay.root);
2290
3047
  root.appendChild(localTestMenu);
2291
3048
  root.appendChild(toolbarOverflowMenu);
@@ -2483,6 +3240,9 @@ export function createLocalEditorBrowserUi(options = {}) {
2483
3240
  applyLocalEditorTheme(measurementOverlay.root, activeTheme);
2484
3241
  applyLocalEditorTheme(sceneFrameRateOverlay.root, activeTheme);
2485
3242
  applyLocalEditorTheme(performanceMonitorOverlay.root, activeTheme);
3243
+ applyLocalEditorTheme(prefabStagePreviewLayout.root, activeTheme);
3244
+ applyLocalEditorTheme(prefabImportStatsOverlay.root, activeTheme);
3245
+ applyLocalEditorTheme(prefabComparisonPreviewOverlay.root, activeTheme);
2486
3246
  applyLocalEditorTheme(shortcutHelpPanel, activeTheme);
2487
3247
  applyLocalEditorTheme(assetPreviewModal, activeTheme);
2488
3248
  applyLocalEditorTheme(assetPickerModal, activeTheme);
@@ -3002,6 +3762,9 @@ export function createLocalEditorBrowserUi(options = {}) {
3002
3762
  performanceMonitorButton.addEventListener('click', () => {
3003
3763
  setPerformanceMonitorOpen(!performanceMonitorOpen);
3004
3764
  });
3765
+ prefabComparisonPreviewButton.addEventListener('click', () => {
3766
+ setPrefabComparisonPreviewOpen(!prefabComparisonPreviewOpen);
3767
+ });
3005
3768
  gridToggleButton.addEventListener('click', () => {
3006
3769
  const visible = currentState?.grid?.visible ?? true;
3007
3770
  callbacks.onGridVisibleChange?.(!visible);
@@ -3055,6 +3818,12 @@ export function createLocalEditorBrowserUi(options = {}) {
3055
3818
  callbacks.onViewportProjectionModeChange?.(nextMode);
3056
3819
  return;
3057
3820
  }
3821
+ const markerVisibilityToggle = target?.closest('[data-viewport-marker-visibility-toggle]');
3822
+ if (markerVisibilityToggle) {
3823
+ const markersVisible = currentState?.viewportTools?.markersVisible ?? true;
3824
+ callbacks.onMarkerViewportVisibilityChange?.(!markersVisible);
3825
+ return;
3826
+ }
3058
3827
  const overlayToggle = target?.closest('[data-viewport-overlay-settings-toggle]');
3059
3828
  if (overlayToggle) {
3060
3829
  event.stopPropagation();
@@ -3088,10 +3857,21 @@ export function createLocalEditorBrowserUi(options = {}) {
3088
3857
  contextMenu.close();
3089
3858
  performanceMonitorOpen = open;
3090
3859
  LocalEditorShared.applyButtonActiveState(performanceMonitorButton, performanceMonitorOpen);
3091
- renderPerformanceMonitorOverlay(performanceMonitorOverlay, currentState?.mode === 'editor' ? currentState.performanceStats ?? null : null, currentState?.mode === 'editor' && performanceMonitorOpen);
3860
+ renderPerformanceMonitorOverlay(performanceMonitorOverlay, currentState?.mode === 'editor' && !currentState.prefabStage ? currentState.performanceStats ?? null : null, currentState?.mode === 'editor' && !currentState.prefabStage && performanceMonitorOpen);
3092
3861
  if (notify)
3093
3862
  callbacks.onPerformanceMonitorOpenChange?.(performanceMonitorOpen);
3094
3863
  };
3864
+ const setPrefabComparisonPreviewOpen = (open) => {
3865
+ if (prefabComparisonPreviewOpen === open)
3866
+ return;
3867
+ if (!open)
3868
+ endPrefabComparisonPreviewDrag();
3869
+ prefabComparisonPreviewOpen = open;
3870
+ if (currentState)
3871
+ renderPreviewState(currentState);
3872
+ LocalEditorShared.applyButtonActiveState(prefabComparisonPreviewButton, prefabComparisonPreviewOpen);
3873
+ callbacks.onPrefabComparisonPreviewLayoutChange?.();
3874
+ };
3095
3875
  const openAssetPreviewModal = (asset) => {
3096
3876
  if (!asset.preview)
3097
3877
  return;
@@ -3164,7 +3944,10 @@ export function createLocalEditorBrowserUi(options = {}) {
3164
3944
  }
3165
3945
  function renderAssetPreviewModal() {
3166
3946
  const asset = resolvePreviewedAssetFromState();
3167
- if (!assetPreviewOpen || !asset || (assetPreviewMode === 'preview' && !asset.preview) || (assetPreviewMode === 'material-editor' && !asset.material)) {
3947
+ if (!assetPreviewOpen
3948
+ || !asset
3949
+ || (assetPreviewMode === 'preview' && !asset.preview)
3950
+ || (assetPreviewMode === 'material-editor' && !asset.material)) {
3168
3951
  assetPreviewModal.style.display = 'none';
3169
3952
  assetPreviewTitle.textContent = '';
3170
3953
  assetPreviewMeta.textContent = '';
@@ -3404,7 +4187,7 @@ export function createLocalEditorBrowserUi(options = {}) {
3404
4187
  const options = [{ label: '继承 / 无', value: '', meta: '清空贴图引用' }];
3405
4188
  const seen = new Set(['']);
3406
4189
  for (const asset of currentState?.assets ?? []) {
3407
- if (resolveBrowserAssetKind(asset) !== 'texture')
4190
+ if (resolveLocalEditorBrowserAssetKind(asset) !== 'texture')
3408
4191
  continue;
3409
4192
  const value = asset.preview?.kind === 'image'
3410
4193
  ? asset.preview.url
@@ -3564,6 +4347,9 @@ export function createLocalEditorBrowserUi(options = {}) {
3564
4347
  sceneHelpButton.addEventListener('click', () => {
3565
4348
  setShortcutHelpOpen(!helpOpen);
3566
4349
  });
4350
+ prefabStageBackButton.addEventListener('click', () => {
4351
+ callbacks.onExitPrefabStage?.();
4352
+ });
3567
4353
  themeToggleButton.addEventListener('click', () => {
3568
4354
  setActiveTheme(activeTheme === 'dark' ? 'light' : 'dark');
3569
4355
  });
@@ -3583,9 +4369,9 @@ export function createLocalEditorBrowserUi(options = {}) {
3583
4369
  closeAssetPreviewModal();
3584
4370
  });
3585
4371
  assetPreviewBody.addEventListener('change', (event) => {
4372
+ const target = event.target instanceof HTMLElement ? event.target : null;
3586
4373
  if (assetPreviewMode !== 'material-editor')
3587
4374
  return;
3588
- const target = event.target instanceof HTMLElement ? event.target : null;
3589
4375
  const input = target?.closest('[data-editor-asset-material-field]');
3590
4376
  if (!input)
3591
4377
  return;
@@ -3764,6 +4550,10 @@ export function createLocalEditorBrowserUi(options = {}) {
3764
4550
  openAssetMaterialEditorModal(asset);
3765
4551
  return;
3766
4552
  }
4553
+ if (item.id === 'asset.edit-prefab') {
4554
+ callbacks.onAssetAction?.(createAssetBrowserActionInput(item.id, asset));
4555
+ return;
4556
+ }
3767
4557
  if (item.id === 'asset.copy-id') {
3768
4558
  copyTextToClipboard(doc.defaultView, asset.assetId ?? asset.id);
3769
4559
  return;
@@ -3776,7 +4566,7 @@ export function createLocalEditorBrowserUi(options = {}) {
3776
4566
  const target = event.target instanceof HTMLElement ? event.target : null;
3777
4567
  const openTabButton = target?.closest('[data-editor-open-right-dock-tab]');
3778
4568
  const openTab = openTabButton?.dataset.editorOpenRightDockTab;
3779
- if (openTab === 'inspector' || openTab === 'rendering') {
4569
+ if (isLocalEditorRightDockTab(openTab)) {
3780
4570
  panelRegistry.setRightDockTab(openTab);
3781
4571
  if (currentState)
3782
4572
  render(currentState);
@@ -3784,7 +4574,7 @@ export function createLocalEditorBrowserUi(options = {}) {
3784
4574
  }
3785
4575
  const tabButton = target?.closest('[data-editor-right-dock-tab]');
3786
4576
  const tab = tabButton?.dataset.editorRightDockTab;
3787
- if (tab === 'inspector' || tab === 'rendering') {
4577
+ if (isLocalEditorRightDockTab(tab)) {
3788
4578
  panelRegistry.setRightDockTab(tab);
3789
4579
  if (currentState)
3790
4580
  render(currentState);
@@ -3792,6 +4582,12 @@ export function createLocalEditorBrowserUi(options = {}) {
3792
4582
  });
3793
4583
  workbench.leftDock.addEventListener('click', (event) => {
3794
4584
  const target = event.target instanceof HTMLElement ? event.target : null;
4585
+ const prefabStageItem = target?.closest('[data-editor-prefab-stage-item]');
4586
+ const prefabStageItemId = prefabStageItem?.dataset.editorPrefabStageItem;
4587
+ if (prefabStageItemId) {
4588
+ callbacks.onSelectPrefabStageItem?.(prefabStageItemId);
4589
+ return;
4590
+ }
3795
4591
  const tabButton = target?.closest('[data-editor-left-dock-tab]');
3796
4592
  const tab = tabButton?.dataset.editorLeftDockTab;
3797
4593
  if (tab === 'hierarchy') {
@@ -3800,6 +4596,15 @@ export function createLocalEditorBrowserUi(options = {}) {
3800
4596
  render(currentState);
3801
4597
  }
3802
4598
  });
4599
+ workbench.leftDock.addEventListener('input', (event) => {
4600
+ const target = event.target instanceof HTMLElement ? event.target : null;
4601
+ const input = target?.closest('[data-editor-prefab-stage-explorer-search]');
4602
+ if (!input)
4603
+ return;
4604
+ prefabStageExplorerFilter = input.value;
4605
+ if (currentState)
4606
+ render(currentState);
4607
+ });
3803
4608
  const hierarchyController = createLocalEditorHierarchyController({
3804
4609
  doc,
3805
4610
  panel: hierarchyPanel,
@@ -3882,6 +4687,28 @@ export function createLocalEditorBrowserUi(options = {}) {
3882
4687
  callbacks.onRenderingAction?.(actionInput);
3883
4688
  return;
3884
4689
  }
4690
+ const inspectorActionButton = target?.closest('[data-editor-inspector-action]');
4691
+ if (inspectorActionButton) {
4692
+ if (inspectorActionButton.disabled)
4693
+ return;
4694
+ const actionInput = createInspectorActionInput(inspectorActionButton);
4695
+ if (!actionInput)
4696
+ return;
4697
+ event.preventDefault();
4698
+ event.stopPropagation();
4699
+ callbacks.onInspectorAction?.(actionInput);
4700
+ return;
4701
+ }
4702
+ const relationTarget = target?.closest('[data-editor-marker-relation-action]');
4703
+ if (relationTarget && !(relationTarget instanceof HTMLButtonElement && relationTarget.disabled)) {
4704
+ const relationInput = createMarkerRelationActionInput(relationTarget);
4705
+ if (!relationInput)
4706
+ return;
4707
+ event.preventDefault();
4708
+ event.stopPropagation();
4709
+ callbacks.onMarkerRelationAction?.(relationInput);
4710
+ return;
4711
+ }
3885
4712
  const vectorLockButton = target?.closest('[data-editor-inspector-vector-lock]');
3886
4713
  if (vectorLockButton) {
3887
4714
  const groupPath = vectorLockButton.dataset.editorInspectorVectorLock;
@@ -4084,6 +4911,28 @@ export function createLocalEditorBrowserUi(options = {}) {
4084
4911
  openStaticShadowBakePopover();
4085
4912
  callbacks.onRenderingAction?.(actionInput);
4086
4913
  });
4914
+ markersPanel.addEventListener('click', (event) => {
4915
+ const eventTarget = event.target instanceof HTMLElement ? event.target : null;
4916
+ const relationTarget = eventTarget?.closest('[data-editor-marker-relation-action]');
4917
+ if (relationTarget && !(relationTarget instanceof HTMLButtonElement && relationTarget.disabled)) {
4918
+ const relationInput = createMarkerRelationActionInput(relationTarget);
4919
+ if (!relationInput)
4920
+ return;
4921
+ event.preventDefault();
4922
+ event.stopPropagation();
4923
+ callbacks.onMarkerRelationAction?.(relationInput);
4924
+ return;
4925
+ }
4926
+ const actionButton = eventTarget?.closest('[data-editor-marker-type-action]');
4927
+ if (!actionButton || actionButton.disabled)
4928
+ return;
4929
+ const actionInput = createMarkerTypeActionInput(actionButton);
4930
+ if (!actionInput)
4931
+ return;
4932
+ event.preventDefault();
4933
+ event.stopPropagation();
4934
+ callbacks.onMarkerTypeAction?.(actionInput);
4935
+ });
4087
4936
  const onKeyDown = (event) => {
4088
4937
  if (event.defaultPrevented)
4089
4938
  return;
@@ -4125,9 +4974,27 @@ export function createLocalEditorBrowserUi(options = {}) {
4125
4974
  closeStaticShadowBakePopover();
4126
4975
  return;
4127
4976
  }
4977
+ if (key === 'escape' && currentState?.prefabStage) {
4978
+ event.preventDefault();
4979
+ callbacks.onExitPrefabStage?.();
4980
+ return;
4981
+ }
4128
4982
  const primaryModifier = event.metaKey || event.ctrlKey;
4129
4983
  const handleDocumentShortcut = inputRouter.shouldHandleDocumentShortcut(event);
4130
4984
  const handleGlobalShortcut = inputRouter.shouldHandleGlobalShortcut(event);
4985
+ if (shouldToggleLocalEditorPrefabComparisonShortcut({
4986
+ inPrefabStage: currentState.prefabStage != null,
4987
+ handleGlobalShortcut,
4988
+ key,
4989
+ shiftKey: event.shiftKey,
4990
+ metaKey: event.metaKey,
4991
+ ctrlKey: event.ctrlKey,
4992
+ altKey: event.altKey,
4993
+ })) {
4994
+ event.preventDefault();
4995
+ setPrefabComparisonPreviewOpen(!prefabComparisonPreviewOpen);
4996
+ return;
4997
+ }
4131
4998
  if (handleDocumentShortcut && localTestActionsEnabled && primaryModifier && key === 's') {
4132
4999
  event.preventDefault();
4133
5000
  callbacks.onSaveScene?.();
@@ -4146,9 +5013,22 @@ export function createLocalEditorBrowserUi(options = {}) {
4146
5013
  callbacks.onRedo?.();
4147
5014
  return;
4148
5015
  }
4149
- if (handleGlobalShortcut && hierarchyController.handleEditShortcut(event)) {
5016
+ if (shouldLocalEditorPrefabStageBlockHierarchyShortcut({
5017
+ inPrefabStage: currentState.prefabStage != null,
5018
+ handleGlobalShortcut,
5019
+ key,
5020
+ metaKey: event.metaKey,
5021
+ ctrlKey: event.ctrlKey,
5022
+ altKey: event.altKey,
5023
+ })) {
5024
+ event.preventDefault();
5025
+ return;
5026
+ }
5027
+ if (!currentState.prefabStage && handleGlobalShortcut && hierarchyController.handleEditShortcut(event)) {
4150
5028
  return;
4151
5029
  }
5030
+ if (currentState.prefabStage)
5031
+ return;
4152
5032
  if (!handleGlobalShortcut || event.metaKey || event.ctrlKey || event.altKey)
4153
5033
  return;
4154
5034
  const toolByKey = {
@@ -4190,8 +5070,276 @@ export function createLocalEditorBrowserUi(options = {}) {
4190
5070
  ? operationState.canAlign
4191
5071
  : operationState.canDistribute;
4192
5072
  }
5073
+ function applyPrefabStageWorkbenchLayoutOverride(layout = workbenchLayoutController.getLayout()) {
5074
+ const leftWidth = Math.min(Math.max(layout.leftWidth, 220), 420);
5075
+ const rightWidth = Math.min(Math.max(layout.rightWidth, 300), 520);
5076
+ workbench.leftDock.style.display = 'flex';
5077
+ workbench.leftDock.style.width = `${leftWidth}px`;
5078
+ workbench.leftDock.style.bottom = '0';
5079
+ workbench.leftDock.style.zIndex = '';
5080
+ workbench.leftDock.style.boxShadow = '';
5081
+ workbench.rightDock.style.display = 'flex';
5082
+ workbench.rightDock.style.width = `${rightWidth}px`;
5083
+ workbench.rightDock.style.zIndex = '';
5084
+ workbench.rightDock.style.boxShadow = '';
5085
+ workbench.sceneFrame.style.left = `${leftWidth}px`;
5086
+ workbench.sceneFrame.style.right = `${rightWidth}px`;
5087
+ workbench.sceneFrame.style.bottom = '0';
5088
+ workbench.bottomDock.style.display = 'none';
5089
+ workbench.bottomResizer.style.display = 'none';
5090
+ workbench.bottomToggle.style.display = 'none';
5091
+ workbench.leftToggle.style.display = 'none';
5092
+ workbench.rightToggle.style.display = 'none';
5093
+ workbench.leftResizer.style.bottom = '0';
5094
+ workbench.rightResizer.style.bottom = '0';
5095
+ }
5096
+ function renderPrefabStageStructurePanel(stage) {
5097
+ LocalEditorShared.clearElement(hierarchyPanel);
5098
+ hierarchyPanel.dataset.editorPanelId = 'prefab-explorer';
5099
+ hierarchyPanel.appendChild(createPrefabStagePanelHeader('Prefab Explorer', 'hierarchy'));
5100
+ const search = doc.createElement('input');
5101
+ search.type = 'search';
5102
+ search.dataset.editorPrefabStageExplorerSearch = 'true';
5103
+ search.placeholder = 'Filter';
5104
+ search.value = prefabStageExplorerFilter;
5105
+ search.style.cssText = [
5106
+ 'width:calc(100% - 16px)',
5107
+ 'height:28px',
5108
+ 'margin:6px 8px 8px',
5109
+ 'padding:0 8px',
5110
+ 'border:1px solid var(--fps-editor-border-strong)',
5111
+ 'border-radius:0',
5112
+ 'background:var(--fps-editor-input-bg)',
5113
+ 'color:var(--fps-editor-text)',
5114
+ 'font-size:12px',
5115
+ 'font-weight:700',
5116
+ 'outline:none',
5117
+ ].join(';');
5118
+ hierarchyPanel.appendChild(search);
5119
+ if (stage.importStructureReady === false) {
5120
+ const loading = doc.createElement('div');
5121
+ loading.textContent = 'Loading import structure...';
5122
+ loading.style.cssText = 'padding:0 8px 8px;color:var(--fps-editor-muted);font-size:11px;font-weight:800';
5123
+ hierarchyPanel.appendChild(loading);
5124
+ }
5125
+ const list = doc.createElement('div');
5126
+ list.style.cssText = 'display:flex;flex-direction:column;gap:1px';
5127
+ const visibleItems = filterLocalEditorPrefabStageStructureItems(stage.structureItems, prefabStageExplorerFilter);
5128
+ const renderItem = (item, depth) => {
5129
+ const button = doc.createElement('button');
5130
+ button.type = 'button';
5131
+ button.dataset.editorPrefabStageItem = item.id;
5132
+ const selected = stage.selectedItemId === item.id;
5133
+ const icon = isLocalEditorIconName(item.icon)
5134
+ ? item.icon
5135
+ : item.kind === 'root'
5136
+ ? 'prefab'
5137
+ : item.kind === 'source'
5138
+ ? 'asset'
5139
+ : item.kind === 'material'
5140
+ ? 'material-slot'
5141
+ : item.kind === 'texture'
5142
+ ? 'asset'
5143
+ : item.kind === 'animation'
5144
+ ? 'execute'
5145
+ : item.kind === 'group'
5146
+ ? 'group'
5147
+ : 'object';
5148
+ button.style.cssText = [
5149
+ 'width:100%',
5150
+ 'min-height:26px',
5151
+ 'display:flex',
5152
+ 'align-items:center',
5153
+ 'gap:6px',
5154
+ `padding:3px 8px 3px ${8 + depth * 16}px`,
5155
+ `border:1px solid ${selected ? 'var(--fps-editor-accent)' : 'transparent'}`,
5156
+ 'border-radius:0',
5157
+ `background:${selected ? 'var(--fps-editor-row-selected)' : 'transparent'}`,
5158
+ `color:${selected ? 'var(--fps-editor-row-selected-text)' : 'var(--fps-editor-text)'}`,
5159
+ 'font-size:12px',
5160
+ 'font-weight:800',
5161
+ 'text-align:left',
5162
+ 'cursor:pointer',
5163
+ 'overflow:hidden',
5164
+ ].join(';');
5165
+ button.appendChild(createLocalEditorIcon(doc, icon, { size: 14 }));
5166
+ const label = doc.createElement('span');
5167
+ label.textContent = item.label;
5168
+ label.style.cssText = 'min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1';
5169
+ button.appendChild(label);
5170
+ if (item.readonly === true) {
5171
+ const readonlyBadge = doc.createElement('span');
5172
+ readonlyBadge.textContent = 'READONLY';
5173
+ readonlyBadge.style.cssText = [
5174
+ 'flex:0 0 auto',
5175
+ 'padding:1px 4px',
5176
+ 'border-radius:2px',
5177
+ 'background:var(--fps-editor-chip-bg)',
5178
+ 'color:var(--fps-editor-muted-strong)',
5179
+ 'font-size:9px',
5180
+ 'font-weight:900',
5181
+ ].join(';');
5182
+ button.appendChild(readonlyBadge);
5183
+ }
5184
+ list.appendChild(button);
5185
+ for (const child of item.children ?? [])
5186
+ renderItem(child, depth + 1);
5187
+ };
5188
+ for (const item of visibleItems)
5189
+ renderItem(item, 0);
5190
+ if (visibleItems.length === 0) {
5191
+ const empty = doc.createElement('div');
5192
+ empty.textContent = prefabStageExplorerFilter.trim() ? 'No matching items.' : 'No structure available.';
5193
+ empty.style.cssText = 'padding:8px;color:var(--fps-editor-muted);font-size:12px;font-weight:800';
5194
+ list.appendChild(empty);
5195
+ }
5196
+ hierarchyPanel.appendChild(list);
5197
+ }
5198
+ function renderPrefabStagePropertiesPanel(stage) {
5199
+ inspectorPanel.dataset.editorPanelId = 'prefab-properties';
5200
+ inspectorPanel.style.display = '';
5201
+ renderingPanel.style.display = 'none';
5202
+ if (stage.inspectorObject) {
5203
+ LocalEditorPanels.renderInspectorPanel(doc, inspectorPanel, {
5204
+ ...currentState,
5205
+ selectedIds: stage.inspectorObject.targetIds,
5206
+ activeId: stage.inspectorObject.activeId,
5207
+ selectionSummary: {
5208
+ count: stage.inspectorObject.targetIds.length,
5209
+ activeId: stage.inspectorObject.activeId,
5210
+ },
5211
+ serializedObject: null,
5212
+ serializedMultiObject: null,
5213
+ inspectorObject: stage.inspectorObject,
5214
+ inspectorMultiObject: null,
5215
+ }, inspectorFilter, {
5216
+ ...options.inspector,
5217
+ title: 'Prefab Properties',
5218
+ emptyText: 'Select an item in Prefab Explorer.',
5219
+ summaryTitle: 'Prefab',
5220
+ lockedVectorGroups: lockedInspectorVectorGroups,
5221
+ });
5222
+ return;
5223
+ }
5224
+ LocalEditorShared.clearElement(inspectorPanel);
5225
+ inspectorPanel.appendChild(createPrefabStagePanelHeader('Prefab Properties', 'inspector'));
5226
+ const selectedItem = findLocalEditorPrefabStageStructureItem(stage, stage.selectedItemId);
5227
+ const grid = doc.createElement('div');
5228
+ grid.style.cssText = 'display:flex;flex-direction:column;gap:7px;min-width:0';
5229
+ appendPrefabStageReadonlyRow(grid, '名称', stage.label);
5230
+ appendPrefabStageReadonlyRow(grid, 'Prefab ID', stage.assetId);
5231
+ appendPrefabStageReadonlyRow(grid, 'Source', stage.sourceAssetId ?? '未设置');
5232
+ appendPrefabStageReadonlyRow(grid, 'Preview', stage.previewNodeId ?? '未生成');
5233
+ appendPrefabStageReadonlyRow(grid, 'Selected', selectedItem?.label ?? stage.selectedItemId ?? 'Prefab Root');
5234
+ appendPrefabStageReadonlyRow(grid, 'Mode', stage.readonly ? '只读' : '可编辑');
5235
+ appendPrefabStageSelectedItemDetails(grid, selectedItem);
5236
+ inspectorPanel.appendChild(grid);
5237
+ }
5238
+ function appendPrefabStageSelectedItemDetails(container, item) {
5239
+ if (!item)
5240
+ return;
5241
+ appendPrefabStageReadonlyRow(container, 'Kind', item.kind);
5242
+ if (item.sourceAssetId)
5243
+ appendPrefabStageReadonlyRow(container, 'Source Asset', item.sourceAssetId);
5244
+ if (item.slotId)
5245
+ appendPrefabStageReadonlyRow(container, 'Slot ID', item.slotId);
5246
+ if (item.ownerNodePath)
5247
+ appendPrefabStageReadonlyRow(container, 'Owner Path', item.ownerNodePath);
5248
+ if (item.meshIndex != null)
5249
+ appendPrefabStageReadonlyRow(container, 'Mesh Index', String(item.meshIndex));
5250
+ if (item.primitiveIndex != null)
5251
+ appendPrefabStageReadonlyRow(container, 'Primitive', String(item.primitiveIndex));
5252
+ if (item.sourceMaterialIndex != null) {
5253
+ appendPrefabStageReadonlyRow(container, 'Material Index', String(item.sourceMaterialIndex));
5254
+ }
5255
+ if (item.sourceMaterialIndices?.length) {
5256
+ appendPrefabStageReadonlyRow(container, 'Material Indices', item.sourceMaterialIndices.join(', '));
5257
+ }
5258
+ if (item.sourceMaterialName)
5259
+ appendPrefabStageReadonlyRow(container, 'Material Name', item.sourceMaterialName);
5260
+ if (item.sourceMaterialNames?.length) {
5261
+ appendPrefabStageReadonlyRow(container, 'Material Names', item.sourceMaterialNames.join(', '));
5262
+ }
5263
+ if (item.meta)
5264
+ appendPrefabStageReadonlyRow(container, 'Meta', item.meta);
5265
+ }
5266
+ function resolvePrefabStageAssetFromState(stage) {
5267
+ return currentState?.assets.find(asset => (asset.id === stage.browserAssetId
5268
+ || asset.id === `prefab:${stage.assetId}`
5269
+ || asset.assetId === stage.assetId
5270
+ || asset.prefab?.id === stage.assetId)) ?? null;
5271
+ }
5272
+ function createPrefabStagePanelHeader(title, icon) {
5273
+ const header = doc.createElement('div');
5274
+ header.style.cssText = [
5275
+ 'display:flex',
5276
+ 'align-items:center',
5277
+ 'gap:8px',
5278
+ 'min-height:30px',
5279
+ 'margin:-8px -8px 8px',
5280
+ 'padding:0 8px',
5281
+ 'border-bottom:1px solid var(--fps-editor-divider)',
5282
+ 'background:var(--fps-editor-chrome-dark)',
5283
+ ].join(';');
5284
+ header.appendChild(createLocalEditorIcon(doc, icon, { size: 14 }));
5285
+ const label = doc.createElement('h2');
5286
+ label.textContent = title;
5287
+ label.style.cssText = [
5288
+ 'min-width:0',
5289
+ 'margin:0',
5290
+ 'overflow:hidden',
5291
+ 'text-overflow:ellipsis',
5292
+ 'white-space:nowrap',
5293
+ 'font-size:13px',
5294
+ 'font-weight:800',
5295
+ 'color:var(--fps-editor-text-strong)',
5296
+ ].join(';');
5297
+ header.appendChild(label);
5298
+ return header;
5299
+ }
5300
+ function appendPrefabStageReadonlyRow(container, label, value) {
5301
+ const row = doc.createElement('div');
5302
+ row.style.cssText = [
5303
+ 'display:grid',
5304
+ 'grid-template-columns:92px minmax(0,1fr)',
5305
+ 'gap:8px',
5306
+ 'align-items:center',
5307
+ 'min-height:26px',
5308
+ ].join(';');
5309
+ const rowLabel = doc.createElement('div');
5310
+ rowLabel.textContent = label;
5311
+ rowLabel.style.cssText = [
5312
+ 'min-width:0',
5313
+ 'overflow:hidden',
5314
+ 'text-overflow:ellipsis',
5315
+ 'white-space:nowrap',
5316
+ 'color:var(--fps-editor-muted)',
5317
+ 'font-size:11px',
5318
+ 'font-weight:800',
5319
+ ].join(';');
5320
+ const output = doc.createElement('div');
5321
+ output.textContent = value;
5322
+ output.title = value;
5323
+ output.style.cssText = [
5324
+ 'min-width:0',
5325
+ 'overflow:hidden',
5326
+ 'text-overflow:ellipsis',
5327
+ 'white-space:nowrap',
5328
+ 'padding:4px 7px',
5329
+ 'border:1px solid var(--fps-editor-border)',
5330
+ 'border-radius:3px',
5331
+ 'background:var(--fps-editor-field)',
5332
+ 'color:var(--fps-editor-text-strong)',
5333
+ 'font-size:12px',
5334
+ 'font-weight:800',
5335
+ ].join(';');
5336
+ row.appendChild(rowLabel);
5337
+ row.appendChild(output);
5338
+ container.appendChild(row);
5339
+ }
4193
5340
  const renderPreviewState = (state) => {
4194
5341
  const inEditor = state.mode === 'editor';
5342
+ const inPrefabStage = !!state.prefabStage;
4195
5343
  const disabled = state.busy;
4196
5344
  const transformTool = state.transformTool ?? null;
4197
5345
  const transformOperations = state.transformOperations ?? {
@@ -4235,8 +5383,22 @@ export function createLocalEditorBrowserUi(options = {}) {
4235
5383
  ? 'var(--fps-editor-border)'
4236
5384
  : statusToneColor;
4237
5385
  renderCoordinateAxesOverlay(coordinateAxesOverlay, inEditor ? state.coordinateAxes ?? null : null);
4238
- renderSceneFrameRateOverlay(sceneFrameRateOverlay, inEditor ? state.sceneFrameStats ?? null : null);
4239
- renderPerformanceMonitorOverlay(performanceMonitorOverlay, inEditor ? state.performanceStats ?? null : null, inEditor && performanceMonitorOpen);
5386
+ renderSceneFrameRateOverlay(sceneFrameRateOverlay, inEditor && !inPrefabStage ? state.sceneFrameStats ?? null : null);
5387
+ renderPerformanceMonitorOverlay(performanceMonitorOverlay, inEditor && !inPrefabStage ? state.performanceStats ?? null : null, inEditor && !inPrefabStage && performanceMonitorOpen);
5388
+ const comparisonColumnVisible = shouldShowLocalEditorPrefabComparisonColumn({
5389
+ inPrefabStage: inEditor && inPrefabStage,
5390
+ userOpen: prefabComparisonPreviewOpen,
5391
+ sceneWidth: workbench.sceneFrame.clientWidth,
5392
+ });
5393
+ if (!comparisonColumnVisible)
5394
+ endPrefabComparisonPreviewDrag();
5395
+ prefabStagePreviewLayout.root.style.display = inEditor && inPrefabStage ? 'block' : 'none';
5396
+ prefabStagePreviewLayout.mainRegion.style.right = comparisonColumnVisible
5397
+ ? `${LOCAL_EDITOR_PREFAB_COMPARISON_COLUMN_WIDTH_PX}px`
5398
+ : '0';
5399
+ prefabStagePreviewLayout.comparisonColumn.style.display = comparisonColumnVisible ? 'flex' : 'none';
5400
+ renderPrefabImportStatsOverlay(prefabImportStatsOverlay, inEditor && inPrefabStage ? state.prefabStage ?? null : null);
5401
+ renderPrefabComparisonPreviewOverlay(prefabComparisonPreviewOverlay, inEditor && inPrefabStage && comparisonColumnVisible ? state.prefabStage ?? null : null);
4240
5402
  renderSpatialOverlay(spatialOverlay, inEditor ? state.viewportSpatialOverlay ?? null : null);
4241
5403
  renderMeasurementOverlay(measurementOverlay, inEditor ? state.viewportMeasurement ?? null : null);
4242
5404
  const boxSelection = state.boxSelection;
@@ -4263,38 +5425,44 @@ export function createLocalEditorBrowserUi(options = {}) {
4263
5425
  };
4264
5426
  renderPreviewState(currentState);
4265
5427
  if (shouldRenderInspectorPreview && panelRegistry.getRightDockTab() === 'inspector') {
4266
- const rightDockScrollSnapshot = captureLocalEditorRightDockScrollSnapshot(currentState, 'inspector', inspectorFilter, inspectorPanel, renderingPanel);
5428
+ const rightDockScrollSnapshot = captureLocalEditorRightDockScrollSnapshot(currentState, 'inspector', inspectorFilter, inspectorPanel, renderingPanel, markersPanel);
4267
5429
  LocalEditorPanels.renderInspectorPanel(doc, inspectorPanel, currentState, inspectorFilter, {
4268
5430
  ...options.inspector,
4269
5431
  lockedVectorGroups: lockedInspectorVectorGroups,
4270
5432
  });
4271
- restoreLocalEditorRightDockScrollSnapshot(rightDockScrollSnapshot, currentState, 'inspector', inspectorFilter, inspectorPanel, renderingPanel);
5433
+ restoreLocalEditorRightDockScrollSnapshot(rightDockScrollSnapshot, currentState, 'inspector', inspectorFilter, inspectorPanel, renderingPanel, markersPanel);
4272
5434
  }
4273
5435
  };
4274
5436
  const render = (state) => {
4275
5437
  const previousState = currentState;
4276
5438
  const previousRightTab = panelRegistry.getRightDockTab();
4277
- const rightDockScrollSnapshot = captureLocalEditorRightDockScrollSnapshot(previousState, previousRightTab, inspectorFilter, inspectorPanel, renderingPanel);
5439
+ const rightDockScrollSnapshot = captureLocalEditorRightDockScrollSnapshot(previousState, previousRightTab, inspectorFilter, inspectorPanel, renderingPanel, markersPanel);
4278
5440
  currentState = state;
4279
5441
  const focusSnapshot = captureLocalEditorEditableFocus(doc);
4280
5442
  const inEditor = state.mode === 'editor';
4281
5443
  const disabled = state.busy;
5444
+ const prefabStage = inEditor ? state.prefabStage ?? null : null;
5445
+ const inPrefabStage = !!prefabStage;
5446
+ if (!inPrefabStage || previousState?.prefabStage?.assetId !== prefabStage.assetId) {
5447
+ prefabStageExplorerFilter = '';
5448
+ }
4282
5449
  if (!inEditor || disabled)
4283
5450
  contextMenu.close();
4284
5451
  hostChrome.style.display = !inEditor && localTestActionsEnabled ? 'flex' : 'none';
4285
5452
  enterEditorButton.disabled = disabled;
4286
- for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton, performanceMonitorButton, gridToggleButton]) {
5453
+ for (const button of [saveButton, saveAndRunButton, discardRunButton, undoButton, redoButton, sceneHelpButton, sceneCameraButton, performanceMonitorButton, gridToggleButton, prefabComparisonPreviewButton]) {
4287
5454
  button.style.display = 'inline-flex';
4288
5455
  button.disabled = disabled;
4289
5456
  }
4290
- localTestGroup.style.display = inEditor && localTestActionsEnabled ? 'flex' : 'none';
5457
+ localTestGroup.style.display = inEditor && !inPrefabStage && localTestActionsEnabled ? 'flex' : 'none';
4291
5458
  localTestButton.disabled = disabled;
4292
5459
  if (!inEditor || !localTestActionsEnabled)
4293
5460
  closeLocalTestMenu();
4294
5461
  if (!inEditor)
4295
5462
  helpOpen = false;
4296
- if (!inEditor && performanceMonitorOpen)
5463
+ if (shouldLocalEditorClosePerformanceMonitor({ inEditor, inPrefabStage, performanceMonitorOpen })) {
4297
5464
  setPerformanceMonitorOpen(false);
5465
+ }
4298
5466
  if (!inEditor || disabled) {
4299
5467
  assetPreviewOpen = false;
4300
5468
  previewedAsset = null;
@@ -4306,7 +5474,16 @@ export function createLocalEditorBrowserUi(options = {}) {
4306
5474
  syncInputModalOpen();
4307
5475
  const transformTool = state.transformTool ?? null;
4308
5476
  workbench.root.style.display = inEditor ? '' : 'none';
5477
+ workbench.root.dataset.editorPrefabStage = String(inPrefabStage);
5478
+ sceneTitle.textContent = createLocalEditorPrefabStageTitle(prefabStage);
4309
5479
  sceneToolOverlay.style.display = inEditor ? 'flex' : 'none';
5480
+ prefabStageBackButton.style.display = inPrefabStage ? 'inline-flex' : 'none';
5481
+ workbench.leftToggle.style.display = '';
5482
+ workbench.rightToggle.style.display = '';
5483
+ workbench.bottomToggle.style.display = '';
5484
+ workbenchLayoutController.refresh();
5485
+ if (inPrefabStage)
5486
+ applyPrefabStageWorkbenchLayoutOverride();
4310
5487
  if (!inEditor)
4311
5488
  closeSnapSettingsPopover();
4312
5489
  if (!inEditor)
@@ -4318,7 +5495,7 @@ export function createLocalEditorBrowserUi(options = {}) {
4318
5495
  if (!inEditor)
4319
5496
  closeStaticShadowBakePopover();
4320
5497
  const staticShadowBakeAction = findLocalEditorRenderingAction(state.renderingPanel, STATIC_SHADOW_BAKE_ACTION_ID);
4321
- const staticShadowBakeVisible = inEditor && !!staticShadowBakeAction;
5498
+ const staticShadowBakeVisible = inEditor && !inPrefabStage && !!staticShadowBakeAction;
4322
5499
  staticShadowBakeGroup.style.display = staticShadowBakeVisible ? 'flex' : 'none';
4323
5500
  const staticShadowBakeDisabled = disabled || !staticShadowBakeAction || staticShadowBakeAction.action.disabled === true;
4324
5501
  staticShadowBakeButton.disabled = staticShadowBakeDisabled;
@@ -4330,7 +5507,7 @@ export function createLocalEditorBrowserUi(options = {}) {
4330
5507
  LocalEditorShared.applyButtonActiveState(staticShadowBakeButton, !!staticShadowBakeAction?.system?.progress);
4331
5508
  renderStaticShadowBakePopover(state);
4332
5509
  const sceneCameraPreview = state.sceneCameraPreview ?? { enabled: false, available: false };
4333
- cameraPreviewGroup.style.display = inEditor ? 'flex' : 'none';
5510
+ cameraPreviewGroup.style.display = inEditor && !inPrefabStage ? 'flex' : 'none';
4334
5511
  sceneCameraButton.disabled = disabled || !sceneCameraPreview.available;
4335
5512
  const sceneCameraTooltip = sceneCameraPreview.available
4336
5513
  ? '从 Main Camera 查看当前场景'
@@ -4357,6 +5534,10 @@ export function createLocalEditorBrowserUi(options = {}) {
4357
5534
  setToolbarButtonIcon(doc, projectionToggleButton, projectionMode === 'orthographic' ? 'projection-ortho' : 'projection-perspective');
4358
5535
  setToolbarButtonTooltip(projectionToggleButton, viewportProjectionTooltip || `投影模式:${VIEWPORT_PROJECTION_LABELS[projectionMode]}。切换为${VIEWPORT_PROJECTION_LABELS[nextProjectionMode]}`);
4359
5536
  LocalEditorShared.applyButtonActiveState(projectionToggleButton, projectionMode === 'orthographic');
5537
+ const markersVisible = viewportTools?.markersVisible ?? true;
5538
+ markerVisibilityButton.disabled = disabled;
5539
+ setToolbarButtonTooltip(markerVisibilityButton, markersVisible ? '隐藏标记与关系线' : '显示标记与关系线');
5540
+ LocalEditorShared.applyButtonActiveState(markerVisibilityButton, markersVisible);
4360
5541
  overlaySettingsButton.disabled = disabled;
4361
5542
  setToolbarButtonTooltip(overlaySettingsButton, '视口信息层设置');
4362
5543
  LocalEditorShared.applyButtonActiveState(overlaySettingsButton, overlaySettingsOpen);
@@ -4378,12 +5559,21 @@ export function createLocalEditorBrowserUi(options = {}) {
4378
5559
  if (!inEditor || disabled || viewportToolsGroup.style.display === 'none')
4379
5560
  closeOverlaySettingsPopover();
4380
5561
  const gridState = state.grid ?? { visible: false, available: false };
4381
- performanceMonitorButton.disabled = disabled;
5562
+ performanceMonitorButton.style.display = inPrefabStage ? 'none' : 'inline-flex';
5563
+ performanceMonitorButton.disabled = disabled || inPrefabStage;
4382
5564
  setToolbarButtonTooltip(performanceMonitorButton, performanceMonitorOpen ? '隐藏性能监控' : '显示性能监控');
4383
5565
  LocalEditorShared.applyButtonActiveState(performanceMonitorButton, performanceMonitorOpen);
5566
+ prefabComparisonPreviewButton.style.display = inPrefabStage ? 'inline-flex' : 'none';
5567
+ prefabComparisonPreviewButton.disabled = disabled || !inPrefabStage;
5568
+ setToolbarButtonTooltip(prefabComparisonPreviewButton, prefabComparisonPreviewOpen
5569
+ ? '隐藏对比预览 (Shift+P)'
5570
+ : '显示对比预览 (Shift+P)');
5571
+ LocalEditorShared.applyButtonActiveState(prefabComparisonPreviewButton, inPrefabStage && prefabComparisonPreviewOpen);
4384
5572
  gridToggleButton.disabled = disabled || !gridState.available;
4385
5573
  setToolbarButtonTooltip(gridToggleButton, gridState.visible ? '隐藏 Scene View 网格' : '显示 Scene View 网格');
4386
5574
  LocalEditorShared.applyButtonActiveState(gridToggleButton, gridState.visible);
5575
+ toolGroup.style.display = inEditor && !inPrefabStage ? 'flex' : 'none';
5576
+ spaceGroup.style.display = inEditor && !inPrefabStage ? 'flex' : 'none';
4387
5577
  for (const [tool, button] of toolButtons) {
4388
5578
  button.disabled = disabled;
4389
5579
  LocalEditorShared.applyButtonActiveState(button, transformTool?.activeTool === tool);
@@ -4401,7 +5591,7 @@ export function createLocalEditorBrowserUi(options = {}) {
4401
5591
  canDistribute: false,
4402
5592
  };
4403
5593
  const operationSettings = transformOperations.settings;
4404
- snapGroup.style.display = inEditor ? 'flex' : 'none';
5594
+ snapGroup.style.display = inEditor && !inPrefabStage ? 'flex' : 'none';
4405
5595
  snapButton.disabled = disabled;
4406
5596
  snapEnabledInput.disabled = disabled;
4407
5597
  snapEnabledInput.checked = operationSettings.snap.enabled;
@@ -4414,7 +5604,7 @@ export function createLocalEditorBrowserUi(options = {}) {
4414
5604
  }
4415
5605
  if (!inEditor || disabled || snapGroup.style.display === 'none')
4416
5606
  closeSnapSettingsPopover();
4417
- placementGroup.style.display = inEditor ? 'flex' : 'none';
5607
+ placementGroup.style.display = inEditor && !inPrefabStage ? 'flex' : 'none';
4418
5608
  placementButton.disabled = disabled;
4419
5609
  setToolbarButtonIcon(doc, placementButton, PLACEMENT_MODE_ICONS[operationSettings.placementMode]);
4420
5610
  setToolbarButtonTooltip(placementButton, `放置模式 · ${PLACEMENT_MODE_DESCRIPTIONS[operationSettings.placementMode]}`);
@@ -4425,7 +5615,7 @@ export function createLocalEditorBrowserUi(options = {}) {
4425
5615
  }
4426
5616
  if (!inEditor || disabled || placementGroup.style.display === 'none')
4427
5617
  closePlacementSettingsPopover();
4428
- actionGroup.style.display = inEditor ? 'flex' : 'none';
5618
+ actionGroup.style.display = inEditor && !inPrefabStage ? 'flex' : 'none';
4429
5619
  const hasTransformAction = transformOperations.canAlign || transformOperations.canDistribute;
4430
5620
  transformActionButton.disabled = disabled || !hasTransformAction;
4431
5621
  setToolbarButtonTooltip(transformActionButton, hasTransformAction ? '对齐与分布' : '选择至少 2 个对象后可用');
@@ -4449,9 +5639,29 @@ export function createLocalEditorBrowserUi(options = {}) {
4449
5639
  scheduleToolbarOverflowLayout();
4450
5640
  if (!inEditor)
4451
5641
  return;
5642
+ const activeRightTab = panelRegistry.getRightDockTab();
5643
+ if (inPrefabStage && prefabStage) {
5644
+ const rightDockVisibility = resolveLocalEditorRightDockVisibility({
5645
+ inPrefabStage: true,
5646
+ activeRightTab,
5647
+ });
5648
+ leftDockTabs.style.display = 'none';
5649
+ rightDockTabs.style.display = rightDockVisibility.tabsDisplay;
5650
+ inspectorPanel.style.display = rightDockVisibility.inspectorDisplay;
5651
+ renderingPanel.style.display = rightDockVisibility.renderingDisplay;
5652
+ renderPrefabStageStructurePanel(prefabStage);
5653
+ renderPrefabStagePropertiesPanel(prefabStage);
5654
+ restoreLocalEditorEditableFocus(doc, focusSnapshot);
5655
+ return;
5656
+ }
5657
+ leftDockTabs.style.display = '';
4452
5658
  const hierarchyDescriptor = panelRegistry.getActivePanel('left');
4453
5659
  const rightDescriptor = panelRegistry.getActivePanel('right');
4454
- const activeRightTab = panelRegistry.getRightDockTab();
5660
+ const rightDockVisibility = resolveLocalEditorRightDockVisibility({
5661
+ inPrefabStage: false,
5662
+ activeRightTab,
5663
+ });
5664
+ rightDockTabs.style.display = rightDockVisibility.tabsDisplay;
4455
5665
  const activeBottomTab = panelRegistry.getBottomDockTab();
4456
5666
  const assetBrowserTabChanged = assetBrowserTab !== lastRenderedAssetBrowserTab;
4457
5667
  const bottomDockTabChanged = activeBottomTab !== lastRenderedBottomDockTab;
@@ -4462,8 +5672,10 @@ export function createLocalEditorBrowserUi(options = {}) {
4462
5672
  hierarchyPanel.dataset.editorPanelId = hierarchyDescriptor?.id ?? 'hierarchy';
4463
5673
  inspectorPanel.dataset.editorPanelId = activeRightTab === 'inspector' ? rightDescriptor?.id ?? 'inspector' : 'inspector';
4464
5674
  renderingPanel.dataset.editorPanelId = activeRightTab === 'rendering' ? rightDescriptor?.id ?? 'rendering' : 'rendering';
4465
- inspectorPanel.style.display = activeRightTab === 'inspector' ? '' : 'none';
4466
- renderingPanel.style.display = activeRightTab === 'rendering' ? '' : 'none';
5675
+ markersPanel.dataset.editorPanelId = activeRightTab === 'markers' ? rightDescriptor?.id ?? 'markers' : 'markers';
5676
+ inspectorPanel.style.display = rightDockVisibility.inspectorDisplay;
5677
+ renderingPanel.style.display = rightDockVisibility.renderingDisplay;
5678
+ markersPanel.style.display = activeRightTab === 'markers' ? '' : 'none';
4467
5679
  LocalEditorPanels.renderWorkbenchLeftDockTabs(doc, leftDockTabs, 'hierarchy', panelRegistry.getPanels('left'));
4468
5680
  hierarchyController.render(state);
4469
5681
  LocalEditorPanels.renderWorkbenchBottomDockPanel(doc, assetPanel, state, activeBottomTab, panelRegistry.getPanels('bottom'), assetBrowserTab);
@@ -4486,11 +5698,14 @@ export function createLocalEditorBrowserUi(options = {}) {
4486
5698
  lockedVectorGroups: lockedInspectorVectorGroups,
4487
5699
  });
4488
5700
  }
4489
- else {
5701
+ else if (activeRightTab === 'rendering') {
4490
5702
  LocalEditorPanels.renderRenderingPanel(doc, renderingPanel, state);
4491
5703
  }
5704
+ else {
5705
+ LocalEditorPanels.renderMarkerPanel(doc, markersPanel, state);
5706
+ }
4492
5707
  restoreLocalEditorEditableFocus(doc, focusSnapshot);
4493
- restoreLocalEditorRightDockScrollSnapshot(rightDockScrollSnapshot, state, activeRightTab, inspectorFilter, inspectorPanel, renderingPanel);
5708
+ restoreLocalEditorRightDockScrollSnapshot(rightDockScrollSnapshot, state, activeRightTab, inspectorFilter, inspectorPanel, renderingPanel, markersPanel);
4494
5709
  };
4495
5710
  return {
4496
5711
  update(state) {
@@ -4500,12 +5715,15 @@ export function createLocalEditorBrowserUi(options = {}) {
4500
5715
  updateSceneFrameStats(stats) {
4501
5716
  if (currentState)
4502
5717
  currentState = { ...currentState, sceneFrameStats: stats };
4503
- renderSceneFrameRateOverlay(sceneFrameRateOverlay, currentState?.mode === 'editor' ? stats : null);
5718
+ renderSceneFrameRateOverlay(sceneFrameRateOverlay, currentState?.mode === 'editor' && !currentState.prefabStage ? stats : null);
4504
5719
  },
4505
5720
  updatePerformanceStats(stats) {
4506
5721
  if (currentState)
4507
5722
  currentState = { ...currentState, performanceStats: stats };
4508
- renderPerformanceMonitorOverlay(performanceMonitorOverlay, currentState?.mode === 'editor' ? stats : null, currentState?.mode === 'editor' && performanceMonitorOpen);
5723
+ renderPerformanceMonitorOverlay(performanceMonitorOverlay, currentState?.mode === 'editor' && !currentState.prefabStage ? stats : null, currentState?.mode === 'editor' && !currentState.prefabStage && performanceMonitorOpen);
5724
+ },
5725
+ getPrefabComparisonPreviewSurfaces() {
5726
+ return readPrefabComparisonPreviewSurfaces(prefabComparisonPreviewOverlay);
4509
5727
  },
4510
5728
  setTheme(theme) {
4511
5729
  setActiveTheme(theme);
@@ -4535,6 +5753,7 @@ export function createLocalEditorBrowserUi(options = {}) {
4535
5753
  measurementOverlay.root.remove();
4536
5754
  sceneFrameRateOverlay.root.remove();
4537
5755
  performanceMonitorOverlay.root.remove();
5756
+ prefabStagePreviewLayout.root.remove();
4538
5757
  boxSelectionOverlay.remove();
4539
5758
  shortcutHelpPanel.remove();
4540
5759
  assetPreviewModal.remove();