@vgai/editor 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +35 -0
- package/dist/a/b/c/test-nested.json +1 -0
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +1 -0
- package/dist/assets/SelectionBox-BXsv_6aH.js +1 -0
- package/dist/assets/browser-project-scripts-DOru4L1x.js +2 -0
- package/dist/assets/browserAll-CEkWYlFk.js +1 -0
- package/dist/assets/collision-world-DniMB0qq.glb +0 -0
- package/dist/assets/create-runtime-DYIWr3va.js +1 -0
- package/dist/assets/data-asset-Bb9sIM7i.js +5 -0
- package/dist/assets/esbuild-BHljloGq.wasm +0 -0
- package/dist/assets/fake-three-BMTQ9rl_.js +1 -0
- package/dist/assets/game-8SDZPPgH.js +1 -0
- package/dist/assets/game-BI111o5f.js +10 -0
- package/dist/assets/game-CevorxyV.js +1 -0
- package/dist/assets/game-Di1H0gFj.js +1 -0
- package/dist/assets/game-Di7XlywD.js +1 -0
- package/dist/assets/game-DjnCaw8R.js +1 -0
- package/dist/assets/game-Q9A0aXSV.js +1 -0
- package/dist/assets/game-pt_1lQLv.js +1 -0
- package/dist/assets/game-source-CvidN7AB.js +1 -0
- package/dist/assets/game-xsOho_wg.js +83 -0
- package/dist/assets/index-Bp5cecD6.js +12 -0
- package/dist/assets/index-CPVV6pjw.js +1 -0
- package/dist/assets/index-CpOnYyeD.js +213 -0
- package/dist/assets/index-Cs8xwv3e.js +1 -0
- package/dist/assets/index-DL7RcQ0n.js +27 -0
- package/dist/assets/index-Dr8H_6Rl.js +1 -0
- package/dist/assets/index-_bCaipUk.js +13650 -0
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +1 -0
- package/dist/assets/mount-manifest-Bhk0ohsl.js +1 -0
- package/dist/assets/recast-navigation.wasm-compat-CvP4UGK6.js +1 -0
- package/dist/assets/scene-query-BivAHhJu.js +1 -0
- package/dist/assets/stats.module--VATS4Kh.js +1 -0
- package/dist/assets/webworkerAll-BLrH0F6F.js +1 -0
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +1 -0
- package/dist/assets/xstate.esm-BgUD33nl.js +3 -0
- package/dist/index.html +37 -0
- package/dist/ingest/racing-game/UPSTREAM.md +68 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Black.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BlackItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Bold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-BoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLight.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ExtraLightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Italic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Light.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-LightItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Medium.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-MediumItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Regular.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBold.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-SemiBoldItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-Thin.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff +0 -0
- package/dist/ingest/racing-game/a/Inter-ThinItalic.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-italic.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter-roman.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/Inter.var.woff2 +0 -0
- package/dist/ingest/racing-game/a/index.css +1 -0
- package/dist/ingest/racing-game/images/bronze.png +0 -0
- package/dist/ingest/racing-game/images/gold.png +0 -0
- package/dist/ingest/racing-game/images/silver.png +0 -0
- package/dist/ingest/racing-game/index.html +15 -0
- package/dist/ingest/racing-game/models/chassis-draco.glb +0 -0
- package/dist/ingest/racing-game/models/track-draco.glb +0 -0
- package/dist/ingest/racing-game/models/wheel-draco.glb +0 -0
- package/dist/ingest/racing-game/rg-entry.js +1 -0
- package/dist/ingest/racing-game/sounds/accelerate.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/boost.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/crash.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/engine.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/honk.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/tire-brake.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/train.mp3 +0 -0
- package/dist/ingest/racing-game/sounds/water.mp3 +0 -0
- package/dist/ingest/racing-game/textures/cursor.svg +3 -0
- package/dist/ingest/racing-game/textures/dikhololo_night_1k.hdr +0 -0
- package/dist/ingest/racing-game/textures/heightmap_1024.png +0 -0
- package/dist/ingest/racing-game/textures/mask.svg +11 -0
- package/dist/ingest/racing-game/textures/no_sound.png +0 -0
- package/dist/ingest/racing-game/three-r139.module.js +50617 -0
- package/dist/ingest/racing-game/vendor.js +179 -0
- package/dist/ingest/racing-game/vgai.game.json +20 -0
- package/dist/ingest/simcity/UPSTREAM.md +65 -0
- package/dist/ingest/simcity/favicon.ico +0 -0
- package/dist/ingest/simcity/fonts/Bebas-Regular.otf +0 -0
- package/dist/ingest/simcity/fonts/Digitalt.otf +0 -0
- package/dist/ingest/simcity/fonts/Stanberry.ttf +0 -0
- package/dist/ingest/simcity/icons/bulldozer-color.png +0 -0
- package/dist/ingest/simcity/icons/bulldozer.png +0 -0
- package/dist/ingest/simcity/icons/calendar.png +0 -0
- package/dist/ingest/simcity/icons/factory-color.png +0 -0
- package/dist/ingest/simcity/icons/factory.png +0 -0
- package/dist/ingest/simcity/icons/house-color.png +0 -0
- package/dist/ingest/simcity/icons/house.png +0 -0
- package/dist/ingest/simcity/icons/job.png +0 -0
- package/dist/ingest/simcity/icons/office.png +0 -0
- package/dist/ingest/simcity/icons/pause-color.png +0 -0
- package/dist/ingest/simcity/icons/pause.png +0 -0
- package/dist/ingest/simcity/icons/person.png +0 -0
- package/dist/ingest/simcity/icons/play-color.png +0 -0
- package/dist/ingest/simcity/icons/play.png +0 -0
- package/dist/ingest/simcity/icons/power-color.png +0 -0
- package/dist/ingest/simcity/icons/power-line-color.png +0 -0
- package/dist/ingest/simcity/icons/power.png +0 -0
- package/dist/ingest/simcity/icons/road-color.png +0 -0
- package/dist/ingest/simcity/icons/road.png +0 -0
- package/dist/ingest/simcity/icons/select-color.png +0 -0
- package/dist/ingest/simcity/icons/select.png +0 -0
- package/dist/ingest/simcity/icons/store-color.png +0 -0
- package/dist/ingest/simcity/index.html +94 -0
- package/dist/ingest/simcity/index.js +58 -0
- package/dist/ingest/simcity/main.css +233 -0
- package/dist/ingest/simcity/models/armored-truck.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-corner.glb +0 -0
- package/dist/ingest/simcity/models/building-block-4floor-short.glb +0 -0
- package/dist/ingest/simcity/models/building-block-5floor.glb +0 -0
- package/dist/ingest/simcity/models/building-burger-joint.glb +0 -0
- package/dist/ingest/simcity/models/building-cafe.glb +0 -0
- package/dist/ingest/simcity/models/building-casino.glb +0 -0
- package/dist/ingest/simcity/models/building-cinema.glb +0 -0
- package/dist/ingest/simcity/models/building-house-block-big.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-large.glb +0 -0
- package/dist/ingest/simcity/models/building-house-family-small.glb +0 -0
- package/dist/ingest/simcity/models/building-office-balcony.glb +0 -0
- package/dist/ingest/simcity/models/building-office-big.glb +0 -0
- package/dist/ingest/simcity/models/building-office-pyramid.glb +0 -0
- package/dist/ingest/simcity/models/building-office-tall.glb +0 -0
- package/dist/ingest/simcity/models/building-office.glb +0 -0
- package/dist/ingest/simcity/models/building-restaurant.glb +0 -0
- package/dist/ingest/simcity/models/building-skyscraper.glb +0 -0
- package/dist/ingest/simcity/models/car-ambulance-pickup.glb +0 -0
- package/dist/ingest/simcity/models/car-baywatch.glb +0 -0
- package/dist/ingest/simcity/models/car-hippie-van.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger-race.glb +0 -0
- package/dist/ingest/simcity/models/car-passenger.glb +0 -0
- package/dist/ingest/simcity/models/car-police.glb +0 -0
- package/dist/ingest/simcity/models/car-taxi.glb +0 -0
- package/dist/ingest/simcity/models/car-tow-truck.glb +0 -0
- package/dist/ingest/simcity/models/car-truck-dump.glb +0 -0
- package/dist/ingest/simcity/models/car-veteran.glb +0 -0
- package/dist/ingest/simcity/models/construction-small.glb +0 -0
- package/dist/ingest/simcity/models/data-center.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory-old.glb +0 -0
- package/dist/ingest/simcity/models/industry-factory.glb +0 -0
- package/dist/ingest/simcity/models/industry-refinery.glb +0 -0
- package/dist/ingest/simcity/models/industry-warehouse.glb +0 -0
- package/dist/ingest/simcity/models/power_line_pole_modified.glb +0 -0
- package/dist/ingest/simcity/models/tile-plain_grass.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-curve.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-end.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection-t.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-intersection.glb +0 -0
- package/dist/ingest/simcity/models/tile-road-straight.glb +0 -0
- package/dist/ingest/simcity/models/truck.glb +0 -0
- package/dist/ingest/simcity/statusIcons/no-power.png +0 -0
- package/dist/ingest/simcity/statusIcons/no-road-access.png +0 -0
- package/dist/ingest/simcity/textures/base.png +0 -0
- package/dist/ingest/simcity/textures/grid.png +0 -0
- package/dist/ingest/simcity/textures/specular.png +0 -0
- package/dist/ingest/simcity/three-r155.module.js +51466 -0
- package/dist/ingest/simcity/vgai.game.json +20 -0
- package/dist/ingest/tanks/UPSTREAM.md +69 -0
- package/dist/ingest/tanks/index.html +214 -0
- package/dist/ingest/tanks/models/pixelTank.fbx +0 -0
- package/dist/ingest/tanks/tanks-entry.js +92 -0
- package/dist/ingest/tanks/three-r170.module.js +54571 -0
- package/dist/ingest/tanks/vgai.game.json +20 -0
- package/dist/inputmaps/default.inputmap.json +41 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/dist/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/dist/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/dist/scenes/untitled.vscn.json +29 -0
- package/dist/textures/waternormals.jpg +0 -0
- package/dist-server/packaged.mjs +18155 -0
- package/package.json +36 -0
- package/server/asset-library-routes.ts +833 -0
- package/server/canonical-path.ts +50 -0
- package/server/cloud-asset-catalog.ts +101 -0
- package/server/dev.ts +350 -0
- package/server/editor-server.ts +1537 -0
- package/server/editor-sse.ts +73 -0
- package/server/game-globals-shadow.ts +58 -0
- package/server/local-asset-catalog.ts +163 -0
- package/server/packaged.ts +571 -0
- package/server/prod.ts +75 -0
- package/server/project-optimize-deps-entries.ts +149 -0
- package/server/project-view.ts +65 -0
- package/server/server-utils.ts +430 -0
- package/server/session-registry.ts +93 -0
- package/src/EditorContext.tsx +87 -0
- package/src/achieved-tier.ts +126 -0
- package/src/action-registry.ts +268 -0
- package/src/adapter-resolver.ts +1858 -0
- package/src/asset-events.ts +100 -0
- package/src/authoring/active-adapter.ts +98 -0
- package/src/authoring/active-systems.ts +50 -0
- package/src/authoring/boundary-authoring-adapter.ts +178 -0
- package/src/authoring/composite-authoring-adapter.ts +891 -0
- package/src/authoring/design-time-layers.ts +776 -0
- package/src/authoring/edit-mode-authoring.ts +643 -0
- package/src/authoring/embed-only-adapter.ts +57 -0
- package/src/authoring/ephemeral-persistence.ts +26 -0
- package/src/authoring/eyedropper-session.ts +69 -0
- package/src/authoring/first-party-hierarchy-menu.ts +74 -0
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +118 -0
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +550 -0
- package/src/authoring/first-party-inspector/HeaderSection.tsx +329 -0
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +44 -0
- package/src/authoring/first-party-inspector/register.tsx +108 -0
- package/src/authoring/ingest-authoring-adapter.ts +475 -0
- package/src/authoring/ingest-capture.ts +94 -0
- package/src/authoring/ingest-game-adapter.ts +291 -0
- package/src/authoring/ingest-iframe-adapter.ts +38 -0
- package/src/authoring/ingest-iframe-reachable-adapter.ts +401 -0
- package/src/authoring/ingest-overlay.ts +193 -0
- package/src/authoring/layered-pick.ts +87 -0
- package/src/authoring/mount-failure-report.ts +112 -0
- package/src/authoring/overlay-report.ts +52 -0
- package/src/authoring/project-scenes.ts +111 -0
- package/src/authoring/react-dom-authoring-adapter.ts +795 -0
- package/src/authoring/react-inspector-section.tsx +1041 -0
- package/src/authoring/react-world-authoring-adapter.ts +2400 -0
- package/src/authoring/root-activation.ts +61 -0
- package/src/authoring/scene-ui-layer.ts +485 -0
- package/src/authoring/story-overlay.ts +48 -0
- package/src/authoring/ui-authoring-adapter.ts +325 -0
- package/src/authoring/ui-hierarchy-menu.ts +40 -0
- package/src/authoring/ui-inspector-sections.tsx +156 -0
- package/src/authoring/undo-timeline.ts +145 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +746 -0
- package/src/authoring/viewport-pick-context.ts +31 -0
- package/src/authoring/viewport-raycast.ts +80 -0
- package/src/authoring/world-overlay.ts +76 -0
- package/src/authoring/world-pan-state.ts +115 -0
- package/src/authoring/world-session-state.ts +102 -0
- package/src/authoring/world2d-authoring-adapter.ts +313 -0
- package/src/browser-external-watch.ts +98 -0
- package/src/browser-play-entry.ts +112 -0
- package/src/browser-project-scripts.ts +112 -0
- package/src/browser-transpile.ts +246 -0
- package/src/browser-watch-reload.ts +100 -0
- package/src/command-listener.ts +326 -0
- package/src/components/AlignToolbar.tsx +189 -0
- package/src/components/AppRoot.tsx +73 -0
- package/src/components/AssetBrowser.tsx +502 -0
- package/src/components/BottomPanel.tsx +326 -0
- package/src/components/BuildPanel.tsx +455 -0
- package/src/components/CameraInfo.tsx +56 -0
- package/src/components/CommandPalette.tsx +265 -0
- package/src/components/ConsolePanel.tsx +272 -0
- package/src/components/DataPanel.tsx +1268 -0
- package/src/components/DefaultEditorLayout.tsx +509 -0
- package/src/components/FolderTree.tsx +179 -0
- package/src/components/GameHierarchy.tsx +1349 -0
- package/src/components/Inspector.tsx +671 -0
- package/src/components/InspectorToolSection.tsx +74 -0
- package/src/components/MountFailureBanner.tsx +110 -0
- package/src/components/NetworkSection.tsx +109 -0
- package/src/components/OnlineAssetBrowser.tsx +448 -0
- package/src/components/OverlayReportBanner.tsx +122 -0
- package/src/components/PlayBar.tsx +356 -0
- package/src/components/PrefabSaveDialog.tsx +201 -0
- package/src/components/ProjectHeader.tsx +174 -0
- package/src/components/ProjectScreen.tsx +1004 -0
- package/src/components/RightPanel.tsx +166 -0
- package/src/components/RootActivateDialog.tsx +105 -0
- package/src/components/SaveDialog.tsx +197 -0
- package/src/components/SaveStatus.tsx +69 -0
- package/src/components/StatsOverlay.tsx +35 -0
- package/src/components/ToolHost.tsx +80 -0
- package/src/components/Toolbar.tsx +307 -0
- package/src/components/ViewportOverlay.tsx +234 -0
- package/src/components/ViewportPanel.tsx +369 -0
- package/src/components/WorldContextMenu.tsx +257 -0
- package/src/components/WorldSelectionOverlay.tsx +2003 -0
- package/src/components/WorldTextEditor.tsx +86 -0
- package/src/components/asset-viewers/AssetViewer.tsx +38 -0
- package/src/components/asset-viewers/AudioViewer.tsx +149 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +77 -0
- package/src/components/asset-viewers/ImageViewer.tsx +65 -0
- package/src/components/asset-viewers/InputMapViewer.tsx +206 -0
- package/src/components/asset-viewers/ModelViewer.tsx +180 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +236 -0
- package/src/components/data-csv.ts +301 -0
- package/src/components/entity-name-commit.ts +18 -0
- package/src/components/inspector-property-grouping.ts +65 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +188 -0
- package/src/components/inspector-widgets/BorderEditor.tsx +305 -0
- package/src/components/inspector-widgets/ColorPicker.tsx +548 -0
- package/src/components/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/components/inspector-widgets/FontPicker.tsx +196 -0
- package/src/components/inspector-widgets/GradientEditor.tsx +277 -0
- package/src/components/inspector-widgets/ScrubbableInput.tsx +152 -0
- package/src/components/inspector-widgets/ShadowEditor.tsx +332 -0
- package/src/components/inspector-widgets/color-utils.ts +202 -0
- package/src/components/inspector-widgets/gallery-main.tsx +167 -0
- package/src/components/inspector-widgets/index.ts +22 -0
- package/src/components/inspector-widgets/shared.tsx +135 -0
- package/src/components/inspectors/AnimationSection.tsx +143 -0
- package/src/components/inspectors/AudioSection.tsx +215 -0
- package/src/components/inspectors/BoneAttachmentSection.tsx +82 -0
- package/src/components/inspectors/CameraSection.tsx +105 -0
- package/src/components/inspectors/ComponentsSection.tsx +358 -0
- package/src/components/inspectors/JointsSection.tsx +255 -0
- package/src/components/inspectors/LightSection.tsx +116 -0
- package/src/components/inspectors/MaterialSection.tsx +1189 -0
- package/src/components/inspectors/MeshSection.tsx +99 -0
- package/src/components/inspectors/NavigationSection.tsx +58 -0
- package/src/components/inspectors/ParticleSection.tsx +681 -0
- package/src/components/inspectors/PhysicsSection.tsx +326 -0
- package/src/components/inspectors/PostProcessingSection.tsx +1290 -0
- package/src/components/inspectors/RenderingSection.tsx +109 -0
- package/src/components/inspectors/ShadowSection.tsx +84 -0
- package/src/components/inspectors/SplineSection.tsx +191 -0
- package/src/components/inspectors/TransformSection.tsx +191 -0
- package/src/components/inspectors/json-schema-fields.ts +281 -0
- package/src/components/inspectors/multi-edit-utils.ts +49 -0
- package/src/components/inspectors/zod-fields.ts +184 -0
- package/src/components/primitives/ColorInput.tsx +109 -0
- package/src/components/primitives/JsonInput.tsx +98 -0
- package/src/components/primitives/NumberInput.tsx +164 -0
- package/src/components/primitives/Panel.tsx +68 -0
- package/src/components/primitives/SectionHeader.tsx +45 -0
- package/src/components/primitives/Tooltip.tsx +82 -0
- package/src/components/primitives/Vec3Input.tsx +62 -0
- package/src/components/world-overlay-gestures.ts +1438 -0
- package/src/doctor/data-checks.ts +285 -0
- package/src/doctor/report.ts +386 -0
- package/src/editor-api.ts +700 -0
- package/src/editor-console.ts +194 -0
- package/src/editor-hotkeys.ts +441 -0
- package/src/editor-layers.ts +2 -0
- package/src/editor-mode.ts +28 -0
- package/src/editor-store.ts +2225 -0
- package/src/editor-viewport.ts +1924 -0
- package/src/entity-factory.ts +221 -0
- package/src/game-globals-prelude.ts +27 -0
- package/src/gated-globals.ts +131 -0
- package/src/generated/setup-by-example.generated.ts +53 -0
- package/src/gizmo-registry.ts +608 -0
- package/src/handle-system.ts +221 -0
- package/src/hierarchy-menu-registry.ts +70 -0
- package/src/hierarchy-rows.ts +73 -0
- package/src/hosted-example.ts +160 -0
- package/src/hotkeys.ts +142 -0
- package/src/ingest/discovery-react.ts +132 -0
- package/src/ingest/discovery.ts +205 -0
- package/src/ingest/discovery2d.ts +204 -0
- package/src/ingest/esm-entry-resolution.ts +102 -0
- package/src/ingest/games/bloom-composer/game.js +99 -0
- package/src/ingest/games/bloom-composer/vgai.game.json +21 -0
- package/src/ingest/games/embed-bundle/embed.html +10 -0
- package/src/ingest/games/embed-bundle/vgai.game.json +21 -0
- package/src/ingest/games/games-fps/assets/collision-world.glb +0 -0
- package/src/ingest/games/games-fps/game.js +479 -0
- package/src/ingest/games/games-fps/vgai.game.json +24 -0
- package/src/ingest/games/iframe-reachable/game-source.js +58 -0
- package/src/ingest/games/iframe-reachable/vgai.game.json +21 -0
- package/src/ingest/games/interactive-cubes/game.js +139 -0
- package/src/ingest/games/interactive-cubes/vgai.game.json +21 -0
- package/src/ingest/games/mismatched-three/fake-three.js +38 -0
- package/src/ingest/games/mismatched-three/game.js +10 -0
- package/src/ingest/games/mismatched-three/vgai.game.json +22 -0
- package/src/ingest/games/own-three-bundled/game.js +43 -0
- package/src/ingest/games/own-three-bundled/vgai.game.json +21 -0
- package/src/ingest/games/pointerlock-maze/game.js +288 -0
- package/src/ingest/games/pointerlock-maze/vgai.game.json +22 -0
- package/src/ingest/games-2d/_fixtures/first-party-scene.scn2d.json +124 -0
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +25 -0
- package/src/ingest/games-2d/bunnymark/game.ts +88 -0
- package/src/ingest/games-2d/bunnymark/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy/game.ts +148 -0
- package/src/ingest/games-2d/flappy/vgai.game.json +21 -0
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +25 -0
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +25 -0
- package/src/ingest/games-2d/tilemap-camera/game.ts +115 -0
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +21 -0
- package/src/ingest/games-react/react-rpg/entry.tsx +185 -0
- package/src/ingest/games-react/react-rpg/shims.d.ts +26 -0
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +129 -0
- package/src/ingest/games-react/react-rpg/vgai.game.json +21 -0
- package/src/ingest/host-namespace-registry.ts +196 -0
- package/src/ingest/types.ts +127 -0
- package/src/ingest-hierarchy-rows.ts +207 -0
- package/src/ingest-mode.ts +2092 -0
- package/src/ingest-siblings.ts +466 -0
- package/src/initial-scene.ts +194 -0
- package/src/inspector-section-registry.ts +117 -0
- package/src/interactive-edit-scope.ts +34 -0
- package/src/main.tsx +23 -0
- package/src/manifest-project.ts +43 -0
- package/src/model-thumbnail.ts +156 -0
- package/src/module-mode.ts +513 -0
- package/src/navmesh-handler.ts +94 -0
- package/src/packaged-runtime.ts +49 -0
- package/src/play-mode.ts +1148 -0
- package/src/playground/main.ts +206 -0
- package/src/project-manager.ts +173 -0
- package/src/scene-diff.ts +112 -0
- package/src/scene-serializer.ts +144 -0
- package/src/scene-sync.ts +587 -0
- package/src/scene-ui-mode/active-scene-ui.ts +58 -0
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +152 -0
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +29 -0
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +74 -0
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +49 -0
- package/src/scene-ui-mode/ui-prefab-io.ts +76 -0
- package/src/script-loader.ts +143 -0
- package/src/storage/fsa-storage.ts +55 -0
- package/src/storage/handle-storage.ts +150 -0
- package/src/storage/handle-store.ts +116 -0
- package/src/storage/http-storage.ts +137 -0
- package/src/storage/index.ts +147 -0
- package/src/storage/mem-storage.ts +135 -0
- package/src/storage/opfs-storage.ts +25 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/seed.ts +106 -0
- package/src/storage/types.ts +110 -0
- package/src/tool-loader.ts +291 -0
- package/src/ui-editor/inspector.tsx +563 -0
- package/src/ui-editor/node-ops.ts +266 -0
- package/src/ui-editor/overlay.tsx +380 -0
- package/src/ui-editor/palette.tsx +114 -0
- package/src/ui-editor/react-store.ts +78 -0
- package/src/ui-editor/timeline-panel.tsx +443 -0
- package/src/ui-editor/ui-edit-store.ts +552 -0
- package/src/ui-editor/ui-prefab.ts +95 -0
- package/src/ui-source/inspect.ts +544 -0
- package/src/ui-source/oid-transform.ts +133 -0
- package/src/ui-source/source-write-backend.ts +203 -0
- package/src/ui-source/tailwind-map.ts +174 -0
- package/src/ui-source/writer.ts +1175 -0
- package/src/widgets/index.ts +73 -0
- package/template/.claude/skills/editor/SKILL.md +252 -0
- package/template/CLAUDE.md +512 -0
- package/template/LICENSE +30 -0
- package/template/emit-schemas.ts +42 -0
- package/template/index.html +17 -0
- package/template/package.json +53 -0
- package/template/project.json +9 -0
- package/template/public/audio/ambient-arena.wav +0 -0
- package/template/public/audio/ambient-music.wav +0 -0
- package/template/public/audio/ambient-nature.wav +0 -0
- package/template/public/audio/ambient-wind.wav +0 -0
- package/template/public/inputmaps/default.inputmap.json +41 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_decoder.wasm +0 -0
- package/template/public/jsm/libs/draco/gltf/draco_encoder.js +33 -0
- package/template/public/jsm/libs/draco/gltf/draco_wasm_wrapper.js +116 -0
- package/template/public/scenes/default.vscn.json +43 -0
- package/template/public/scenes/untitled.vscn.json +29 -0
- package/template/public/textures/waternormals.jpg +0 -0
- package/template/server/colyseus-setup.ts +85 -0
- package/template/server/main.ts +34 -0
- package/template/server/rooms/arena-room.ts +420 -0
- package/template/server/rooms/game-room.ts +162 -0
- package/template/server/rooms.ts +15 -0
- package/template/server/tsconfig.json +19 -0
- package/template/src/data/README.md +40 -0
- package/template/src/data/assets.ts +31 -0
- package/template/src/data/tuning.data.json +5 -0
- package/template/src/data/tuning.schema.json +21 -0
- package/template/src/data/tuning.schema.ts +34 -0
- package/template/src/data/tuning.ts +24 -0
- package/template/src/main.ts +157 -0
- package/template/src/scripts/components/data-spinner.ts +45 -0
- package/template/src/scripts/components/scene-camera.ts +40 -0
- package/template/src/scripts/main.ts +94 -0
- package/template/src/scripts/registry.ts +46 -0
- package/template/src/tools/README.md +14 -0
- package/template/src/tools/example.tool.tsx +91 -0
- package/template/src/tools/spin.tool.tsx +101 -0
- package/template/src/ui/Button.tsx +27 -0
- package/template/src/ui/DefaultHud.tsx +40 -0
- package/template/src/ui/KeyHint.tsx +25 -0
- package/template/src/ui/Panel.tsx +52 -0
- package/template/src/ui/RebindPanel.tsx +129 -0
- package/template/src/ui/Slider.tsx +28 -0
- package/template/src/ui/StatusDot.tsx +22 -0
- package/template/src/ui/Toggle.tsx +19 -0
- package/template/src/ui/game-state.tsx +37 -0
- package/template/src/ui/index.ts +7 -0
- package/template/src/ui/mount-default-hud.tsx +19 -0
- package/template/src/ui/mount-ui.ts +15 -0
- package/template/tsconfig.json +36 -0
- package/template/validate-asset-content.ts +78 -0
- package/template/validate-manifest.ts +55 -0
- package/template/validate-scenes.ts +211 -0
- package/template/vgai.game.json +21 -0
- package/template/vite.config.ts +128 -0
- package/vite-plugin-example-assets.ts +60 -0
- package/vite-plugin-game-globals.ts +46 -0
- package/vite-plugin-game-static.ts +71 -0
- package/vite-plugin-project-game-static.ts +84 -0
- package/vite-plugin-react-game-static.ts +98 -0
- package/vite-plugin-react-world-provider.ts +81 -0
- package/vite-plugin-react-world-runtime.ts +76 -0
- package/vite-plugin-ui-oid.ts +561 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D8 root activation — the SHARED dirty-gate choke point (A4,
|
|
3
|
+
* docs/unified-world-editor/26-unified-editor-spec.md §2 A4). Both
|
|
4
|
+
* `GameHierarchy`'s radio-row double-click and the asset-browser's scene
|
|
5
|
+
* double-click (`DefaultEditorLayout.tsx`'s `handleOpenScene`) call
|
|
6
|
+
* {@link requestRootActivation} instead of `adapter.rootGroups.activate()`
|
|
7
|
+
* directly, so the confirmation UX can never drift between the two call
|
|
8
|
+
* sites (map §4).
|
|
9
|
+
*
|
|
10
|
+
* `window.confirm()` is banned (editor-wide convention — see CLAUDE.md's
|
|
11
|
+
* "Dialogs" note): adapters can't render UI at all, and the shell must not
|
|
12
|
+
* block synchronously either. When the active adapter is dirty, this
|
|
13
|
+
* dispatches a `window` `CustomEvent` (the same decoupling pattern
|
|
14
|
+
* `first-party-hierarchy-menu.ts` already uses for
|
|
15
|
+
* `editor:save-as-prefab`/`editor:open-asset`) for `DefaultEditorLayout` to
|
|
16
|
+
* show {@link RootActivateDialog} and perform the actual activation only once
|
|
17
|
+
* the user confirms. When the adapter is NOT dirty, this activates
|
|
18
|
+
* immediately — no dialog, no round trip, no race.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { AuthoringAdapter } from '@engine/adapter';
|
|
22
|
+
|
|
23
|
+
export interface RootActivationRequest {
|
|
24
|
+
readonly groupId: string;
|
|
25
|
+
readonly rootId: string;
|
|
26
|
+
/** Display label for the confirmation dialog — resolved by THIS module via
|
|
27
|
+
* `hierarchy.node(rootId)?.label` so callers never need to format one
|
|
28
|
+
* themselves (and never need to know the id's shape). */
|
|
29
|
+
readonly label: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const ROOT_ACTIVATION_REQUEST_EVENT = 'editor:request-root-activation';
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Request activating `rootId` within `groupId` on `adapter`. Dirty-gates
|
|
36
|
+
* through a shell-rendered confirmation dialog; otherwise activates directly.
|
|
37
|
+
*/
|
|
38
|
+
export function requestRootActivation(
|
|
39
|
+
adapter: AuthoringAdapter,
|
|
40
|
+
groupId: string,
|
|
41
|
+
rootId: string,
|
|
42
|
+
): void {
|
|
43
|
+
if (adapter.persistence?.isDirty()) {
|
|
44
|
+
const label = adapter.hierarchy.node(rootId)?.label ?? rootId;
|
|
45
|
+
window.dispatchEvent(
|
|
46
|
+
new CustomEvent(ROOT_ACTIVATION_REQUEST_EVENT, {
|
|
47
|
+
detail: { groupId, rootId, label } satisfies RootActivationRequest,
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
void adapter.rootGroups?.activate(groupId, rootId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Find the `RootGroup` (of `adapter`'s own `rootGroups`) that owns `rootId`,
|
|
56
|
+
* or `null` if none does. Shared by the asset-browser reroute and any other
|
|
57
|
+
* caller that only has a root id, not the owning group id, in hand. */
|
|
58
|
+
export function findOwningRootGroupId(adapter: AuthoringAdapter, rootId: string): string | null {
|
|
59
|
+
const group = adapter.rootGroups?.groups().find((g) => g.memberRootIds.includes(rootId));
|
|
60
|
+
return group?.id ?? null;
|
|
61
|
+
}
|
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene-UI design-time layer (C3, docs/unified-world-editor/26-unified-
|
|
3
|
+
* editor-spec.md §4 C3) — the box-model overlay + stage guides "in the
|
|
4
|
+
* normal viewport" the spec calls for, replacing the retired standalone
|
|
5
|
+
* UI-edit mode's fullscreen `#ui-edit-mode-layer`. Sibling of
|
|
6
|
+
* `design-time-layers.ts`, but
|
|
7
|
+
* mounts the scene-UI composite child (`edit-mode-authoring.ts`'s `kind:
|
|
8
|
+
* 'scene-ui'` child) instead of a react/pixi world: that child already
|
|
9
|
+
* carries a LIVE `UIAuthoringAdapter` at composite-install time (there is no
|
|
10
|
+
* Boundary-to-live upgrade dance here, unlike react/pixi) — this module only
|
|
11
|
+
* gives it a DOM layer, a pick surface (D12/B4), and keeps the shared
|
|
12
|
+
* `EditorStore` and the child's own `UIEditStore` in lockstep.
|
|
13
|
+
*
|
|
14
|
+
* Reuses EVERY existing building block — no new UI runtime, no fork:
|
|
15
|
+
* - `SceneUITree` (the exact runtime that renders at game-time) in `editMode`.
|
|
16
|
+
* - `SelectionOverlay` (`ui-editor/overlay.tsx`) — hover/selection/marquee/
|
|
17
|
+
* resize boxes + the `Measure` spacing guide — IS the box-model overlay;
|
|
18
|
+
* it renders boxes only while `store.selection` is non-empty, so it
|
|
19
|
+
* "activates contextually on UI-node selection" for free.
|
|
20
|
+
* - `UIAuthoringAdapter.pickable` (added by C3) backed by `hitTest`
|
|
21
|
+
* (`ui-editor/react-store.ts`), the same geometric primitive the overlay
|
|
22
|
+
* itself uses for hover/click.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import type { UIRoot } from '@engine/scene/schema';
|
|
26
|
+
import type { UIAnimationController } from '@vgai/scene-ui/animation';
|
|
27
|
+
import { SceneUITree } from '@vgai/scene-ui/mount';
|
|
28
|
+
import type { ProjectUIRegistry } from '@vgai/scene-ui/registry';
|
|
29
|
+
import { createElement, useEffect, useRef, useState } from 'react';
|
|
30
|
+
import { createRoot } from 'react-dom/client';
|
|
31
|
+
import {
|
|
32
|
+
getCachedProjectUIRegistry,
|
|
33
|
+
loadProjectUIRegistry,
|
|
34
|
+
setCachedProjectUIRegistry,
|
|
35
|
+
} from '../adapter-resolver';
|
|
36
|
+
import type { EditorStore } from '../editor-store';
|
|
37
|
+
import { getCurrentProject } from '../project-manager';
|
|
38
|
+
import { findNode } from '../ui-editor/node-ops';
|
|
39
|
+
import { SelectionOverlay } from '../ui-editor/overlay';
|
|
40
|
+
import { useUIStore } from '../ui-editor/react-store';
|
|
41
|
+
import type { UIEditStore } from '../ui-editor/ui-edit-store';
|
|
42
|
+
import type { UIAuthoringAdapter } from './ui-authoring-adapter';
|
|
43
|
+
import { isWorldHidden, isWorldInteractive } from './world-session-state';
|
|
44
|
+
|
|
45
|
+
/** Container-ish UI kinds (mirrors `ui-editor/palette.tsx`'s own unexported
|
|
46
|
+
* `isContainer` — a project-registry component insert (below) targets the
|
|
47
|
+
* primary selection when it's one of these, else the first root, exactly
|
|
48
|
+
* like the harness palette's own "add under selected container" rule. */
|
|
49
|
+
const CONTAINER_KINDS = new Set([
|
|
50
|
+
'canvas',
|
|
51
|
+
'box',
|
|
52
|
+
'grid',
|
|
53
|
+
'stack',
|
|
54
|
+
'scroll',
|
|
55
|
+
'panel',
|
|
56
|
+
'center',
|
|
57
|
+
'wrap',
|
|
58
|
+
'aspect',
|
|
59
|
+
'sizebox',
|
|
60
|
+
'tab',
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
/** Pure (DOM-free) equality check for the R3 echo guard — exported for unit
|
|
64
|
+
* testing without a browser: `uiStore.onCommit -> store.setSceneUI -> store
|
|
65
|
+
* notify -> this module's sync sees "changed" -> uiStore.load()` would wipe
|
|
66
|
+
* selection + undo after every edit unless the sync SKIPS a no-op round
|
|
67
|
+
* trip (the just-committed content already matches). */
|
|
68
|
+
export function sceneUIChanged(a: readonly UIRoot[], b: readonly UIRoot[]): boolean {
|
|
69
|
+
return JSON.stringify(a) !== JSON.stringify(b);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* The content-sync decision for the viewport layer's `EditorStore`
|
|
74
|
+
* subscription (C3 skeptic fix): reload the UI child from the committed
|
|
75
|
+
* document ONLY when it genuinely diverged AND no interactive gesture owns the
|
|
76
|
+
* preview `_roots`. During a live drag/resize `uiStore.serialize()` reflects
|
|
77
|
+
* uncommitted preview state, so `sceneUIChanged` is true and a naive reload
|
|
78
|
+
* would revert the drag and wipe selection/local-undo mid-motion (reachable
|
|
79
|
+
* whenever any `EditorStore` notify — e.g. the previous commit's async
|
|
80
|
+
* autosave — lands inside the gesture window). `endEdit()` commits through
|
|
81
|
+
* `setSceneUI`, after which content matches and no reload is needed. Exported
|
|
82
|
+
* so the wiring in `mountSceneUILayer` is unit-testable against a real
|
|
83
|
+
* `UIEditStore`'s gesture state (the subscription closure otherwise is not).
|
|
84
|
+
*/
|
|
85
|
+
export function shouldReloadUIChild(
|
|
86
|
+
sceneUI: readonly UIRoot[],
|
|
87
|
+
uiStore: Pick<UIEditStore, 'inGesture' | 'serialize'>,
|
|
88
|
+
): boolean {
|
|
89
|
+
return !uiStore.inGesture && sceneUIChanged(sceneUI, uiStore.serialize());
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** E1 "lose nothing" migration (§8 "board-state/responsive/locale preview") —
|
|
93
|
+
* the harness's fixed-resolution device presets (`UIEditor.tsx`'s
|
|
94
|
+
* `RES_PRESETS`/`I3`), ported verbatim so the unified editor's scene-UI
|
|
95
|
+
* chrome doesn't drop the responsive-preview affordance. `fit` (the default)
|
|
96
|
+
* keeps today's full-bleed `inset:0` stage untouched — only picking an
|
|
97
|
+
* explicit preset switches the stage to a centered, fixed-size box. */
|
|
98
|
+
const RES_PRESETS: Record<string, { width: number; height: number } | null> = {
|
|
99
|
+
fit: null,
|
|
100
|
+
'1920×1080': { width: 1920, height: 1080 },
|
|
101
|
+
'1280×720': { width: 1280, height: 720 },
|
|
102
|
+
'375×812 (phone)': { width: 375, height: 812 },
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
/** Fit a fixed preset's ASPECT into a capped display box (verbatim from
|
|
106
|
+
* `UIEditor.tsx`) so a resolution preview never clips against the
|
|
107
|
+
* design-time layer's own (usually much larger) bounds. */
|
|
108
|
+
function fitDisplaySize(res: { width: number; height: number }): { width: number; height: number } {
|
|
109
|
+
const maxW = 960;
|
|
110
|
+
const maxH = 560;
|
|
111
|
+
const s = Math.min(maxW / res.width, maxH / res.height, 1);
|
|
112
|
+
return { width: Math.round(res.width * s), height: Math.round(res.height * s) };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function createStageLayer(container: HTMLElement, worldId: string, zIndex: number): HTMLElement {
|
|
116
|
+
const layer = document.createElement('div');
|
|
117
|
+
layer.dataset['worldId'] = worldId;
|
|
118
|
+
layer.style.position = 'absolute';
|
|
119
|
+
layer.style.top = '0';
|
|
120
|
+
layer.style.left = '0';
|
|
121
|
+
layer.style.width = '100%';
|
|
122
|
+
layer.style.height = '100%';
|
|
123
|
+
layer.style.zIndex = String(zIndex);
|
|
124
|
+
layer.style.display = isWorldHidden(worldId) ? 'none' : 'block';
|
|
125
|
+
// B1 semantics: `pointer-events:none` at rest — plain click-select goes
|
|
126
|
+
// through B4 (`layered-pick.ts`, via `UIAuthoringAdapter.pickable`)
|
|
127
|
+
// instead, so the overlay's own drag/resize/marquee handlers only need to
|
|
128
|
+
// receive real pointer events while the layer's `interactive` toggle is on.
|
|
129
|
+
layer.style.pointerEvents = isWorldInteractive(worldId) ? 'auto' : 'none';
|
|
130
|
+
container.appendChild(layer);
|
|
131
|
+
return layer;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
interface SceneUILayerRootProps {
|
|
135
|
+
uiStore: UIEditStore;
|
|
136
|
+
projectRoot: string | undefined;
|
|
137
|
+
/** E1.b — the child's ONE animation controller, owned by `UIAuthoringAdapter`
|
|
138
|
+
* (shared with the timeline panel, `scene-ui-mode/scene-ui-timeline-tab.tsx`)
|
|
139
|
+
* instead of a `useMemo`'d local instance — see that adapter field's doc
|
|
140
|
+
* comment for why the two mounts must share one instance. */
|
|
141
|
+
anim: UIAnimationController;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** The stage + overlay, composed exactly like `UIEditor.tsx`'s center stage
|
|
145
|
+
* (`:259-283`), minus the chrome — plus a project-registry load (§3.3),
|
|
146
|
+
* mirroring the retired standalone UI-edit mode's own registry resolution
|
|
147
|
+
* (components/strings/states) without its demo-fixture fallback branch
|
|
148
|
+
* (dropped by design, Q3). */
|
|
149
|
+
function SceneUILayerRoot({ uiStore, projectRoot, anim }: SceneUILayerRootProps): React.ReactNode {
|
|
150
|
+
useUIStore(uiStore);
|
|
151
|
+
const stageRef = useRef<HTMLDivElement>(null);
|
|
152
|
+
const [registry, setRegistry] = useState<ProjectUIRegistry | null>(() =>
|
|
153
|
+
getCachedProjectUIRegistry(),
|
|
154
|
+
);
|
|
155
|
+
// I3/G5 "lose nothing" migration — resolution/locale preview (see
|
|
156
|
+
// `RES_PRESETS`'s doc comment above); session-local UI state (not
|
|
157
|
+
// persisted), same as the harness toolbar's own `useState`.
|
|
158
|
+
const [resKey, setResKey] = useState('fit');
|
|
159
|
+
const previewRes = RES_PRESETS[resKey] ?? null;
|
|
160
|
+
const display = previewRes ? fitDisplaySize(previewRes) : null;
|
|
161
|
+
|
|
162
|
+
// E1.R2 review follow-up: this used to also call `uiStore.setPreviewResolution
|
|
163
|
+
// (previewRes)` on every `previewRes` change. `UIEditStore.previewResolution`
|
|
164
|
+
// (the field that write feeds) has ZERO readers anywhere in the repo — the
|
|
165
|
+
// stage resize below is driven entirely by the LOCAL `display`/`resKey` React
|
|
166
|
+
// state, never by that store field — so the write was dead-ported state left
|
|
167
|
+
// over from the harness (`UIEditor.tsx`, which still calls it, unreviewed
|
|
168
|
+
// here) and has been dropped rather than pinned by a test that could never
|
|
169
|
+
// observe anything.
|
|
170
|
+
|
|
171
|
+
useEffect(() => {
|
|
172
|
+
if (!projectRoot) return;
|
|
173
|
+
let cancelled = false;
|
|
174
|
+
void loadProjectUIRegistry(projectRoot).then((r) => {
|
|
175
|
+
// Cache regardless of the registry's outcome (present/absent/broken) —
|
|
176
|
+
// the SAME cache slot the broken-registry loud warn reads from
|
|
177
|
+
// (`adapter-resolver.ts`), preserving 24-ui-registry's assertion.
|
|
178
|
+
setCachedProjectUIRegistry(r);
|
|
179
|
+
if (cancelled) return;
|
|
180
|
+
setRegistry(r);
|
|
181
|
+
if (r?.states) {
|
|
182
|
+
for (const [name, data] of Object.entries(r.states)) uiStore.defineState(name, data);
|
|
183
|
+
// 'default' (else the first entry) seeds the binding preview — same
|
|
184
|
+
// resolution order the retired standalone UI-edit mode used.
|
|
185
|
+
const first = r.states['default'] ? 'default' : Object.keys(r.states)[0];
|
|
186
|
+
if (first) uiStore.loadState(first);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return () => {
|
|
190
|
+
cancelled = true;
|
|
191
|
+
};
|
|
192
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps -- one-shot load per mount
|
|
193
|
+
}, [projectRoot]);
|
|
194
|
+
|
|
195
|
+
// Board-state switching (C12) — a minimal always-on strip (NOT the richer
|
|
196
|
+
// harness toolbar, `UIEditor.tsx`'s `state-${s}` buttons, which stays
|
|
197
|
+
// harness-only): the registry's named states need SOME surface in the
|
|
198
|
+
// unified editor so a binding preview is actually reachable, per §6's
|
|
199
|
+
// "24-ui-registry" testing note. Kept `pointer-events: auto` explicitly —
|
|
200
|
+
// out of the B1 `interactive` toggle's scope (that gates the SelectionOverlay's
|
|
201
|
+
// drag/resize/marquee, spec-27 Phase B/C/D territory; a state switcher is a
|
|
202
|
+
// small, always-on affordance, not that).
|
|
203
|
+
const stateNames = registry?.states ? Object.keys(registry.states) : [];
|
|
204
|
+
const stateStrip =
|
|
205
|
+
stateNames.length > 0
|
|
206
|
+
? createElement(
|
|
207
|
+
'div',
|
|
208
|
+
{
|
|
209
|
+
'data-testid': 'scene-ui-states',
|
|
210
|
+
style: {
|
|
211
|
+
position: 'absolute',
|
|
212
|
+
top: 0,
|
|
213
|
+
left: 0,
|
|
214
|
+
zIndex: 20,
|
|
215
|
+
display: 'flex',
|
|
216
|
+
gap: 4,
|
|
217
|
+
padding: 4,
|
|
218
|
+
pointerEvents: 'auto',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
...stateNames.map((name) =>
|
|
222
|
+
createElement(
|
|
223
|
+
'button',
|
|
224
|
+
{
|
|
225
|
+
key: name,
|
|
226
|
+
'data-testid': `scene-ui-state-${name}`,
|
|
227
|
+
onClick: () => uiStore.loadState(name),
|
|
228
|
+
style: { fontSize: 10 },
|
|
229
|
+
},
|
|
230
|
+
name,
|
|
231
|
+
),
|
|
232
|
+
),
|
|
233
|
+
)
|
|
234
|
+
: null;
|
|
235
|
+
|
|
236
|
+
// Registry-component insertion — a minimal always-on strip, same rationale
|
|
237
|
+
// as the state strip above: `structure.creatableKinds` (the hierarchy's
|
|
238
|
+
// "Create ▸" menu) only knows the fixed built-in kinds (`CREATABLE_KINDS`,
|
|
239
|
+
// `ui-authoring-adapter.ts`), never a project's OWN named registry
|
|
240
|
+
// components — inserting one needs SOME surface (the harness has none
|
|
241
|
+
// either; both a project registry AND a component-insert UI are new to
|
|
242
|
+
// this task). Targets the primary selection when it's a container, else
|
|
243
|
+
// the first root — the same rule `ui-editor/palette.tsx`'s own `add` uses.
|
|
244
|
+
const componentNames = registry?.components ? Object.keys(registry.components) : [];
|
|
245
|
+
const componentStrip =
|
|
246
|
+
componentNames.length > 0
|
|
247
|
+
? createElement(
|
|
248
|
+
'div',
|
|
249
|
+
{
|
|
250
|
+
'data-testid': 'scene-ui-components',
|
|
251
|
+
style: {
|
|
252
|
+
position: 'absolute',
|
|
253
|
+
top: 0,
|
|
254
|
+
right: 0,
|
|
255
|
+
zIndex: 20,
|
|
256
|
+
display: 'flex',
|
|
257
|
+
gap: 4,
|
|
258
|
+
padding: 4,
|
|
259
|
+
pointerEvents: 'auto',
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
...componentNames.map((name) =>
|
|
263
|
+
createElement(
|
|
264
|
+
'button',
|
|
265
|
+
{
|
|
266
|
+
key: name,
|
|
267
|
+
'data-testid': `scene-ui-insert-${name}`,
|
|
268
|
+
onClick: () => {
|
|
269
|
+
const primary = uiStore.primary;
|
|
270
|
+
const primaryLoc = primary ? findNode(uiStore.roots, primary) : null;
|
|
271
|
+
const parentId =
|
|
272
|
+
primaryLoc && CONTAINER_KINDS.has(primaryLoc.node.kind)
|
|
273
|
+
? primary!
|
|
274
|
+
: ((uiStore.roots[0]?.id as string | undefined) ?? null);
|
|
275
|
+
if (parentId) {
|
|
276
|
+
uiStore.addNodeValue(parentId, { kind: 'component', name, component: name });
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
style: { fontSize: 10 },
|
|
280
|
+
},
|
|
281
|
+
name,
|
|
282
|
+
),
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
: null;
|
|
286
|
+
|
|
287
|
+
// I3/G5 — a minimal always-on strip for the responsive/locale preview
|
|
288
|
+
// (§8's "board-state/responsive/locale preview" MIGRATE row), same
|
|
289
|
+
// rationale/placement discipline as `stateStrip`/`componentStrip` above:
|
|
290
|
+
// the harness's richer toolbar (`UIEditor.tsx`'s `tb-res`/`tb-locale`)
|
|
291
|
+
// stays harness-only; the unified editor needs SOME surface so the
|
|
292
|
+
// affordance isn't silently dropped. Locale options come from the
|
|
293
|
+
// registry's own `strings` tables — the same source the harness reads
|
|
294
|
+
// (`Object.keys(strings ?? { en: {} })`).
|
|
295
|
+
const localeNames = registry?.strings ? Object.keys(registry.strings) : ['en'];
|
|
296
|
+
const previewStrip = createElement(
|
|
297
|
+
'div',
|
|
298
|
+
{
|
|
299
|
+
'data-testid': 'scene-ui-preview-controls',
|
|
300
|
+
style: {
|
|
301
|
+
position: 'absolute',
|
|
302
|
+
top: 0,
|
|
303
|
+
left: '50%',
|
|
304
|
+
transform: 'translateX(-50%)',
|
|
305
|
+
zIndex: 20,
|
|
306
|
+
display: 'flex',
|
|
307
|
+
gap: 4,
|
|
308
|
+
padding: 4,
|
|
309
|
+
pointerEvents: 'auto',
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
createElement(
|
|
313
|
+
'select',
|
|
314
|
+
{
|
|
315
|
+
'data-testid': 'scene-ui-res-preset',
|
|
316
|
+
value: resKey,
|
|
317
|
+
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => setResKey(e.target.value),
|
|
318
|
+
style: { fontSize: 10 },
|
|
319
|
+
},
|
|
320
|
+
...Object.keys(RES_PRESETS).map((k) => createElement('option', { key: k, value: k }, k)),
|
|
321
|
+
),
|
|
322
|
+
createElement(
|
|
323
|
+
'select',
|
|
324
|
+
{
|
|
325
|
+
'data-testid': 'scene-ui-locale',
|
|
326
|
+
value: uiStore.locale,
|
|
327
|
+
onChange: (e: React.ChangeEvent<HTMLSelectElement>) => uiStore.setLocale(e.target.value),
|
|
328
|
+
style: { fontSize: 10 },
|
|
329
|
+
},
|
|
330
|
+
...localeNames.map((l) => createElement('option', { key: l, value: l }, l)),
|
|
331
|
+
),
|
|
332
|
+
);
|
|
333
|
+
|
|
334
|
+
return createElement(
|
|
335
|
+
'div',
|
|
336
|
+
{
|
|
337
|
+
// Default ('fit', `display === null`) — an unchanged full-bleed box
|
|
338
|
+
// (byte-identical to pre-migration layout: `position:absolute;inset:0`
|
|
339
|
+
// containing a 100%x100% stage, no flex). A picked preset instead
|
|
340
|
+
// centers the fixed-size stage box within the same full-bleed area.
|
|
341
|
+
style: display
|
|
342
|
+
? {
|
|
343
|
+
position: 'absolute',
|
|
344
|
+
inset: 0,
|
|
345
|
+
display: 'flex',
|
|
346
|
+
alignItems: 'center',
|
|
347
|
+
justifyContent: 'center',
|
|
348
|
+
}
|
|
349
|
+
: { position: 'absolute', inset: 0 },
|
|
350
|
+
},
|
|
351
|
+
createElement(
|
|
352
|
+
'div',
|
|
353
|
+
{
|
|
354
|
+
ref: stageRef,
|
|
355
|
+
'data-ui-stage': true,
|
|
356
|
+
style: display
|
|
357
|
+
? {
|
|
358
|
+
position: 'relative',
|
|
359
|
+
width: display.width,
|
|
360
|
+
height: display.height,
|
|
361
|
+
outline: '1px dashed #4a90d9',
|
|
362
|
+
}
|
|
363
|
+
: { position: 'relative', width: '100%', height: '100%' },
|
|
364
|
+
},
|
|
365
|
+
createElement(SceneUITree, {
|
|
366
|
+
roots: uiStore.roots,
|
|
367
|
+
data: uiStore.data,
|
|
368
|
+
components: registry?.components,
|
|
369
|
+
strings: registry?.strings,
|
|
370
|
+
locale: uiStore.locale,
|
|
371
|
+
editMode: true,
|
|
372
|
+
viewportOverride: display ?? undefined,
|
|
373
|
+
anim,
|
|
374
|
+
}),
|
|
375
|
+
createElement(SelectionOverlay, { store: uiStore, stageRef }),
|
|
376
|
+
),
|
|
377
|
+
stateStrip,
|
|
378
|
+
componentStrip,
|
|
379
|
+
previewStrip,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Mount the scene-UI design-time layer for `worldId` (the composite's
|
|
385
|
+
* `scene-ui` child) into `container` (`ViewportPanel`'s own container — the
|
|
386
|
+
* same one `design-time-layers.ts`'s react/pixi layers use). Returns a
|
|
387
|
+
* disposer. Call it once per composite install; `design-time-layers.ts`'s
|
|
388
|
+
* `mountDesignTimeLayers` is the one production call site.
|
|
389
|
+
*/
|
|
390
|
+
export function mountSceneUILayer(
|
|
391
|
+
container: HTMLElement,
|
|
392
|
+
worldId: string,
|
|
393
|
+
uiAdapter: UIAuthoringAdapter,
|
|
394
|
+
store: EditorStore,
|
|
395
|
+
zIndex: number,
|
|
396
|
+
): () => void {
|
|
397
|
+
const projectRoot = getCurrentProject()?.rootPath;
|
|
398
|
+
const uiStore = uiAdapter.uiStore;
|
|
399
|
+
const layer = createStageLayer(container, worldId, zIndex);
|
|
400
|
+
uiAdapter.attachPickSurface(layer);
|
|
401
|
+
|
|
402
|
+
const root = createRoot(layer);
|
|
403
|
+
root.render(createElement(SceneUILayerRoot, { uiStore, projectRoot, anim: uiAdapter.anim }));
|
|
404
|
+
|
|
405
|
+
// R3 (echo guard) + R4-adjacent selection bridge: `uiStore.select()` can be
|
|
406
|
+
// reached WITHOUT going through `UIAuthoringAdapter.selection.set` (e.g.
|
|
407
|
+
// `SelectionOverlay`'s own click handler, `UIEditStore.addNode`'s
|
|
408
|
+
// auto-select) — mirroring `uiStore.selection` outward keeps the SHARED
|
|
409
|
+
// `EditorStore.selectedEntityIds` (what `Inspector.tsx`/`GameHierarchy`
|
|
410
|
+
// read) in lockstep no matter which surface drove the change. `suppressing`
|
|
411
|
+
// breaks the two-way ping-pong: each direction's write happens INSIDE the
|
|
412
|
+
// flag, so the other direction's listener (which fires synchronously,
|
|
413
|
+
// re-entrantly) sees it set and no-ops.
|
|
414
|
+
let suppressing = false;
|
|
415
|
+
let lastMirroredUiSelectionKey = uiStore.selection.join('\u0000');
|
|
416
|
+
|
|
417
|
+
const unsubUI = uiStore.subscribe(() => {
|
|
418
|
+
if (suppressing) return;
|
|
419
|
+
const key = uiStore.selection.join('\u0000');
|
|
420
|
+
if (key === lastMirroredUiSelectionKey) return;
|
|
421
|
+
lastMirroredUiSelectionKey = key;
|
|
422
|
+
suppressing = true;
|
|
423
|
+
try {
|
|
424
|
+
store.selectMultiple(uiStore.selection);
|
|
425
|
+
} finally {
|
|
426
|
+
suppressing = false;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const unsubStore = store.subscribe(() => {
|
|
431
|
+
// C9 content sync — external changes (undo/redo of the whole document,
|
|
432
|
+
// scene switch) reload the UI child; the equality check IS the R3 guard
|
|
433
|
+
// against the commit echo (`onCommit -> setSceneUI -> notify -> here`
|
|
434
|
+
// sees the just-committed content already matches, skips `load()`).
|
|
435
|
+
//
|
|
436
|
+
// The `inGesture` gate is the C3 skeptic fix: during a live drag/resize,
|
|
437
|
+
// `uiStore.serialize()` reflects the uncommitted preview `_roots`, so it
|
|
438
|
+
// differs from the committed `getSceneUI()` and this branch would call
|
|
439
|
+
// `load()` — reverting the drag and wiping selection/local-undo mid-motion.
|
|
440
|
+
// Any `EditorStore` notify can reach here mid-gesture (most reachably the
|
|
441
|
+
// previous commit's async autosave resolving inside the next drag's
|
|
442
|
+
// window). Skip the reload while a gesture owns `_roots`; `endEdit()`
|
|
443
|
+
// commits through `setSceneUI`, after which content matches and no reload
|
|
444
|
+
// is needed. (A genuine external doc change arriving mid-gesture — vanishing
|
|
445
|
+
// rare — is simply deferred, never allowed to destroy the active gesture.)
|
|
446
|
+
const sceneUI = store.getSceneUI();
|
|
447
|
+
if (shouldReloadUIChild(sceneUI, uiStore)) {
|
|
448
|
+
uiStore.load(sceneUI);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!suppressing) {
|
|
452
|
+
const ids = [...store.selectedEntityIds];
|
|
453
|
+
const uiOwnsSelection = ids.length > 0 && ids.every((id) => uiStore.find(id) != null);
|
|
454
|
+
if (uiOwnsSelection) {
|
|
455
|
+
// Already reflects (or will reflect, via the mirror above) uiStore's
|
|
456
|
+
// own selection — nothing to clear, and don't let a stale key here
|
|
457
|
+
// cause a redundant re-mirror on the next uiStore notify.
|
|
458
|
+
lastMirroredUiSelectionKey = ids.join('\u0000');
|
|
459
|
+
} else if (uiStore.selection.length > 0) {
|
|
460
|
+
// A non-UI (or empty) selection landed on the shared store — the UI
|
|
461
|
+
// child no longer owns it. Clear the UI selection so a stale
|
|
462
|
+
// highlighted node doesn't linger and the inspector never
|
|
463
|
+
// double-matches (R4's "one inspector" discipline).
|
|
464
|
+
lastMirroredUiSelectionKey = '';
|
|
465
|
+
suppressing = true;
|
|
466
|
+
try {
|
|
467
|
+
uiStore.select([]);
|
|
468
|
+
} finally {
|
|
469
|
+
suppressing = false;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
layer.style.display = isWorldHidden(worldId) ? 'none' : 'block';
|
|
475
|
+
layer.style.pointerEvents = isWorldInteractive(worldId) ? 'auto' : 'none';
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
return () => {
|
|
479
|
+
unsubUI();
|
|
480
|
+
unsubStore();
|
|
481
|
+
uiAdapter.detachPickSurface();
|
|
482
|
+
root.unmount();
|
|
483
|
+
layer.remove();
|
|
484
|
+
};
|
|
485
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Story overlay (Q2 decoupling, D4, `docs/unified-world-editor/26-unified-editor-spec.md`
|
|
3
|
+
* §3 B2) — a module-level, per-worldId slot for an EDITOR-SIDE stories
|
|
4
|
+
* source that plugs into `ReactWorldAuthoringAdapter`'s `storySources`
|
|
5
|
+
* constructor option without the adapter (or `design-time-layers.ts`, which
|
|
6
|
+
* constructs it) needing to know where the extra source came from. Same
|
|
7
|
+
* shape as `world-session-state.ts`: a bare get/set slot, no subscribe of
|
|
8
|
+
* its own — a caller that mutates it re-renders/re-mounts through whatever
|
|
9
|
+
* already reacts to `store.notifyIngestEdit()`.
|
|
10
|
+
*
|
|
11
|
+
* This is the DECOUPLING plumbing only (the spec's Q2 requirement: "the
|
|
12
|
+
* provider takes stories as a constructor input; the project registry is
|
|
13
|
+
* merely the default source... add the parameter and a unit test, no UI for
|
|
14
|
+
* authoring overlays yet"). There is no file-loader or editor UI writing
|
|
15
|
+
* into this slot in B2 — that is future work; today it exists so an overlay
|
|
16
|
+
* source can be set programmatically (e.g. by a future authoring UI, or a
|
|
17
|
+
* test) and observed to WIN over the registry's stories on an id collision
|
|
18
|
+
* (`mountReactLayer` composes `storySources: [registrySource, overlaySource]`
|
|
19
|
+
* — later entries win, see `ReactWorldAuthoringAdapter`'s doc comment).
|
|
20
|
+
*
|
|
21
|
+
* A process-lifetime singleton (not project-scoped), matching
|
|
22
|
+
* `world-session-state.ts`'s own stance — nothing here needs a
|
|
23
|
+
* project-switch reset within a single browser tab.
|
|
24
|
+
*/
|
|
25
|
+
import type { UIStory } from '@vgai/scene-ui/registry';
|
|
26
|
+
|
|
27
|
+
const _overlays = new Map<string, Record<string, UIStory>>();
|
|
28
|
+
|
|
29
|
+
/** Set (replacing wholesale) `worldId`'s overlay stories source. */
|
|
30
|
+
export function setStoryOverlay(worldId: string, stories: Record<string, UIStory>): void {
|
|
31
|
+
_overlays.set(worldId, stories);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** `worldId`'s overlay stories source, or `undefined` if none was set. */
|
|
35
|
+
export function getStoryOverlay(worldId: string): Record<string, UIStory> | undefined {
|
|
36
|
+
return _overlays.get(worldId);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function clearStoryOverlay(worldId: string): void {
|
|
40
|
+
_overlays.delete(worldId);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Test-only: drop every world's overlay so unit tests don't leak state
|
|
44
|
+
* across cases (this module is a process-lifetime singleton — see the
|
|
45
|
+
* header comment). */
|
|
46
|
+
export function _resetStoryOverlaysForTest(): void {
|
|
47
|
+
_overlays.clear();
|
|
48
|
+
}
|