@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,671 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Inspector` — the ONE inspector shell (C2, docs/unified-world-editor/26-
|
|
3
|
+
* unified-editor-spec.md §4 C2). Renders straight from the active
|
|
4
|
+
* {@link AuthoringAdapter}'s providers (`hierarchy` / `selection` /
|
|
5
|
+
* `transforms` / `inspector`) — format-neutral `EditorNode`s and
|
|
6
|
+
* `PropertyDescriptor`s, NO `SceneEntity` — PLUS whatever
|
|
7
|
+
* `inspector-section-registry.ts` registrations match the current
|
|
8
|
+
* selection/adapter (`authoring/first-party-inspector/register.tsx` for the
|
|
9
|
+
* threejs adapter, `authoring/ui-inspector-sections.tsx` for the UI
|
|
10
|
+
* adapter). A matched registration may `suppresses` one or more of this
|
|
11
|
+
* file's own built-in blocks it fully replaces (e.g. the first-party
|
|
12
|
+
* `ComponentsSection` suppresses the generic `components` grid block below)
|
|
13
|
+
* so a matched selection never double-renders the same affordance — see
|
|
14
|
+
* `inspector-section-registry.ts`'s header comment for the full design.
|
|
15
|
+
*
|
|
16
|
+
* A2 (docs/unified-world-editor/26-unified-editor-spec.md §2 A2) deleted this
|
|
17
|
+
* file's HIERARCHY half (`IngestHierarchy`/`IngestRowView`/`IngestMoreRow`/
|
|
18
|
+
* `KIND_GLYPH`) — its row-rendering + context-menu logic moved into
|
|
19
|
+
* `components/GameHierarchy.tsx`, the one contract-only hierarchy panel that
|
|
20
|
+
* now serves every session shape (bare single-scene, ingested game,
|
|
21
|
+
* multi-world composite). This file keeps only the INSPECTOR half; C2 folded
|
|
22
|
+
* the former bespoke `InspectorPanel.tsx` into it as registered sections
|
|
23
|
+
* (deleting the A3 dispatch seam that used to choose between the two).
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { AuthoringAdapter, EditorNode, PropertyDescriptor, Transform } from '@engine/adapter';
|
|
27
|
+
import { useSyncExternalStore } from 'react';
|
|
28
|
+
import * as THREE from 'three';
|
|
29
|
+
import { getActiveAuthoring } from '../authoring/active-adapter';
|
|
30
|
+
import { CompositeAuthoringAdapter } from '../authoring/composite-authoring-adapter';
|
|
31
|
+
import { ReactWorldAuthoringAdapter } from '../authoring/react-world-authoring-adapter';
|
|
32
|
+
import { useEditorStore } from '../EditorContext';
|
|
33
|
+
import { setActiveScope } from '../hotkeys';
|
|
34
|
+
import { isIngestActiveReact } from '../ingest-mode';
|
|
35
|
+
import type { InspectorBuiltin } from '../inspector-section-registry';
|
|
36
|
+
import {
|
|
37
|
+
inspectorSectionRegistryVersion,
|
|
38
|
+
matchedInspectorSections,
|
|
39
|
+
subscribeInspectorSectionRegistry,
|
|
40
|
+
} from '../inspector-section-registry';
|
|
41
|
+
import { groupProperties } from './inspector-property-grouping';
|
|
42
|
+
import { Panel } from './primitives/Panel';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* D4 (B2) — the CHILD adapter owning `nodeId`'s synthetic world-group row
|
|
46
|
+
* (`world:<id>`, `CompositeAuthoringAdapter`'s own manufactured node), or
|
|
47
|
+
* `null` when `adapter` isn't a composite or `nodeId` isn't one of its group
|
|
48
|
+
* nodes. Mirrors `GameHierarchy.tsx`'s (unexported) `compositeGroupBadge` —
|
|
49
|
+
* same "ask the public T0 query (`childAdapters()`/`groupNodeId()`), never
|
|
50
|
+
* decode the reserved id yourself" discipline, this file's own use of it.
|
|
51
|
+
* Used ONLY to gate the "Stories" section below: a world-group row whose
|
|
52
|
+
* owning child adapter itself advertises `stories` (a live react world) gets
|
|
53
|
+
* the section (dropdown or "no stories" disclosure); any other row (an
|
|
54
|
+
* ordinary entity, or a world-group row for a kind that never sets
|
|
55
|
+
* `stories` — threejs/pixi/an un-upgraded Boundary placeholder) gets none.
|
|
56
|
+
*/
|
|
57
|
+
function worldGroupChildAdapter(
|
|
58
|
+
adapter: AuthoringAdapter,
|
|
59
|
+
nodeId: string,
|
|
60
|
+
): AuthoringAdapter | null {
|
|
61
|
+
if (!(adapter instanceof CompositeAuthoringAdapter)) return null;
|
|
62
|
+
for (const child of adapter.childAdapters()) {
|
|
63
|
+
if (adapter.groupNodeId(child.worldId) === nodeId) return child.adapter;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* B3 (docs/unified-world-editor/26-unified-editor-spec.md §3 B3) — the CHILD
|
|
70
|
+
* adapter owning a `kind:'component'` CATALOG node (as opposed to
|
|
71
|
+
* `worldGroupChildAdapter`'s `world:<id>` GROUP row above). Resolved by
|
|
72
|
+
* ACTUAL ownership — `CompositeAuthoringAdapter.ownerOf`/`childAdapters()`,
|
|
73
|
+
* the same public T0 query `worldGroupChildAdapter` uses — never by decoding
|
|
74
|
+
* the catalog id's `catalog:` prefix here (Rule zero: this file stays
|
|
75
|
+
* format-neutral about adapter-minted ids; only `node?.kind === 'component'`
|
|
76
|
+
* is this file's own knowledge). `null` for anything that isn't a composite,
|
|
77
|
+
* or a component node the composite doesn't (yet) recognize.
|
|
78
|
+
*/
|
|
79
|
+
function catalogNodeOwnerAdapter(
|
|
80
|
+
adapter: AuthoringAdapter,
|
|
81
|
+
node: EditorNode | null,
|
|
82
|
+
nodeId: string,
|
|
83
|
+
): AuthoringAdapter | null {
|
|
84
|
+
if (!node || node.kind !== 'component') return null;
|
|
85
|
+
if (!(adapter instanceof CompositeAuthoringAdapter)) return null;
|
|
86
|
+
const worldId = adapter.ownerOf(nodeId);
|
|
87
|
+
if (!worldId) return null;
|
|
88
|
+
return adapter.childAdapters().find((c) => c.worldId === worldId)?.adapter ?? null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** B3 — `true` when `nodeId` (a catalog node) IS the owning react adapter's
|
|
92
|
+
* currently-isolated node — the SAME "ask the owning child" discipline as
|
|
93
|
+
* `worldGroupChildAdapter`'s `stories` read, never decoded from the id. */
|
|
94
|
+
function isCatalogNodeIsolated(storiesOwner: AuthoringAdapter | null, nodeId: string): boolean {
|
|
95
|
+
return (
|
|
96
|
+
storiesOwner instanceof ReactWorldAuthoringAdapter && storiesOwner.isolatedNodeId === nodeId
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** B3 — the Isolate toggle's click handler: isolate the catalog node against
|
|
101
|
+
* whichever story is currently selected (the SAME dropdown value the
|
|
102
|
+
* `ingest-stories-select` reuses), or exit isolation back to the entry. */
|
|
103
|
+
function toggleCatalogIsolation(
|
|
104
|
+
adapter: AuthoringAdapter,
|
|
105
|
+
nodeId: string,
|
|
106
|
+
isIsolated: boolean,
|
|
107
|
+
): void {
|
|
108
|
+
if (isIsolated) {
|
|
109
|
+
adapter.stories?.isolate?.(null);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const storyId = adapter.stories?.active(nodeId) ?? undefined;
|
|
113
|
+
adapter.stories?.isolate?.(nodeId, storyId);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const NUM_STYLE: React.CSSProperties = {
|
|
117
|
+
width: 64,
|
|
118
|
+
background: '#1e1e1e',
|
|
119
|
+
color: '#c5c8ce',
|
|
120
|
+
border: '1px solid #333',
|
|
121
|
+
borderRadius: 2,
|
|
122
|
+
padding: '3px 5px',
|
|
123
|
+
fontSize: 12,
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
function AxisRow({
|
|
127
|
+
label,
|
|
128
|
+
values,
|
|
129
|
+
testidPrefix,
|
|
130
|
+
onCommit,
|
|
131
|
+
}: {
|
|
132
|
+
label: string;
|
|
133
|
+
values: [number, number, number];
|
|
134
|
+
testidPrefix: string;
|
|
135
|
+
onCommit: (axis: 0 | 1 | 2, v: number) => void;
|
|
136
|
+
}) {
|
|
137
|
+
const axes: Array<{ a: 0 | 1 | 2; n: string }> = [
|
|
138
|
+
{ a: 0, n: 'x' },
|
|
139
|
+
{ a: 1, n: 'y' },
|
|
140
|
+
{ a: 2, n: 'z' },
|
|
141
|
+
];
|
|
142
|
+
return (
|
|
143
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '4px 8px' }}>
|
|
144
|
+
<span style={{ width: 56, fontSize: 12, color: '#9aa0a6' }}>{label}</span>
|
|
145
|
+
{axes.map(({ a, n }) => (
|
|
146
|
+
<input
|
|
147
|
+
key={n}
|
|
148
|
+
type="number"
|
|
149
|
+
data-testid={`${testidPrefix}-${n}`}
|
|
150
|
+
value={Number.isFinite(values[a]) ? Math.round(values[a] * 1000) / 1000 : 0}
|
|
151
|
+
onChange={(e) => onCommit(a, Number.parseFloat(e.target.value))}
|
|
152
|
+
style={NUM_STYLE}
|
|
153
|
+
/>
|
|
154
|
+
))}
|
|
155
|
+
</div>
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function Inspector() {
|
|
160
|
+
const store = useEditorStore();
|
|
161
|
+
useSyncExternalStore(store.subscribe, store.getSnapshot);
|
|
162
|
+
// Re-render when section registrations change (W3): project inspector
|
|
163
|
+
// TOOLS register asynchronously (folder scan) and re-register on HMR —
|
|
164
|
+
// possibly while a matching selection is already showing. First-party
|
|
165
|
+
// registrations are import-time side effects, so this never fired for them.
|
|
166
|
+
useSyncExternalStore(subscribeInspectorSectionRegistry, inspectorSectionRegistryVersion);
|
|
167
|
+
const adapter = getActiveAuthoring(store);
|
|
168
|
+
const id = [...store.selectedEntityIds][0] ?? null;
|
|
169
|
+
|
|
170
|
+
// No inspector surface: either an opaque prebuilt bundle (iframe floor) or an
|
|
171
|
+
// ingested native-React world (no-authoring by design, D-N8) — the honest copy
|
|
172
|
+
// differs, so branch on which session is active.
|
|
173
|
+
if (!adapter.capabilities.inspectorFields) {
|
|
174
|
+
return (
|
|
175
|
+
<Panel
|
|
176
|
+
name="Inspector (embed-only)"
|
|
177
|
+
data-testid="inspector-panel"
|
|
178
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
179
|
+
>
|
|
180
|
+
<div
|
|
181
|
+
data-testid="ingest-embed-note"
|
|
182
|
+
style={{ padding: 12, color: '#c9a227', fontSize: 12, lineHeight: 1.5 }}
|
|
183
|
+
>
|
|
184
|
+
{isIngestActiveReact() ? (
|
|
185
|
+
<>
|
|
186
|
+
<strong>Embed-only.</strong> This is an ingested native-React game: its components
|
|
187
|
+
render directly as a DOM layer under the host React — there is no scene graph or
|
|
188
|
+
renderer to introspect, and authoring is deliberately off for react ingests (the
|
|
189
|
+
editor never writes JSX back into an unmodified game). The game is hosted, sized, and
|
|
190
|
+
disposed; play it in the Game tab.
|
|
191
|
+
</>
|
|
192
|
+
) : (
|
|
193
|
+
<>
|
|
194
|
+
<strong>Embed-only.</strong> This bundle runs its own renderer in a sandboxed iframe
|
|
195
|
+
the editor cannot reach into — it is hosted and sized, but its scene is not
|
|
196
|
+
introspectable. (This demo's payload is a self-contained canvas app; a real opaque
|
|
197
|
+
three.js bundle, whose realm-local <code>three</code> is unreachable, behaves
|
|
198
|
+
identically.) Source-available games run under the editor's bundler get full live
|
|
199
|
+
editing instead.
|
|
200
|
+
</>
|
|
201
|
+
)}
|
|
202
|
+
</div>
|
|
203
|
+
</Panel>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Nothing selected → environment / scene-settings panel. A matched
|
|
208
|
+
// registration (e.g. the first-party adapter's rich scene Environment
|
|
209
|
+
// section) fully replaces the generic ingest-only background/fog note
|
|
210
|
+
// below (its `suppresses: ['environment']` is exactly why `matched.length
|
|
211
|
+
// > 0` here implies the built-in branch would have been suppressed anyway).
|
|
212
|
+
//
|
|
213
|
+
// `matched`/`suppressed`/`panelNameOverride` are resolved SEPARATELY per
|
|
214
|
+
// branch (here, and again below `id` is confirmed truthy) rather than
|
|
215
|
+
// once up front against a shared `node = id ? adapter.hierarchy.node(id)
|
|
216
|
+
// : null` — collapsing "nothing selected" and "a selected id the current
|
|
217
|
+
// adapter's hierarchy can't resolve" into the same `node === null` would
|
|
218
|
+
// let a first-party composite's null-selection Environment registration
|
|
219
|
+
// (see `authoring/first-party-inspector/register.tsx`'s
|
|
220
|
+
// `isFirstPartySelection` fallback) wrongly match while a (stale/foreign)
|
|
221
|
+
// id is still selected.
|
|
222
|
+
if (!id) {
|
|
223
|
+
const matched = matchedInspectorSections(null, adapter);
|
|
224
|
+
const panelNameOverride = matched.map((reg) => reg.panelName).find((n): n is string => !!n);
|
|
225
|
+
if (matched.length > 0) {
|
|
226
|
+
return (
|
|
227
|
+
<Panel
|
|
228
|
+
name={panelNameOverride ?? 'Environment (ingested game)'}
|
|
229
|
+
data-testid="inspector-panel"
|
|
230
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
231
|
+
style={{ overflowY: 'auto', scrollbarWidth: 'none' }}
|
|
232
|
+
>
|
|
233
|
+
{matched.map(({ Section }, i) => (
|
|
234
|
+
<Section key={i} adapter={adapter} nodeId={null} />
|
|
235
|
+
))}
|
|
236
|
+
</Panel>
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
const scene = store.scene;
|
|
240
|
+
const bg =
|
|
241
|
+
scene?.background instanceof THREE.Color ? `#${scene.background.getHexString()}` : '#000000';
|
|
242
|
+
const hasFog = !!scene?.fog;
|
|
243
|
+
return (
|
|
244
|
+
<Panel
|
|
245
|
+
name="Environment (ingested game)"
|
|
246
|
+
data-testid="inspector-panel"
|
|
247
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
248
|
+
>
|
|
249
|
+
<div style={{ pointerEvents: 'auto', padding: 4 }}>
|
|
250
|
+
<div style={{ padding: 8, color: '#777', fontSize: 12 }}>
|
|
251
|
+
Scene settings — nothing selected.
|
|
252
|
+
</div>
|
|
253
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 8px' }}>
|
|
254
|
+
<span style={{ width: 80, fontSize: 12, color: '#9aa0a6' }}>Background</span>
|
|
255
|
+
<input
|
|
256
|
+
type="color"
|
|
257
|
+
data-testid="ingest-env-background"
|
|
258
|
+
value={bg}
|
|
259
|
+
onChange={(e) => {
|
|
260
|
+
if (scene) {
|
|
261
|
+
scene.background = new THREE.Color(e.target.value);
|
|
262
|
+
store.notifyIngestEdit();
|
|
263
|
+
}
|
|
264
|
+
}}
|
|
265
|
+
/>
|
|
266
|
+
</div>
|
|
267
|
+
<label
|
|
268
|
+
style={{
|
|
269
|
+
display: 'flex',
|
|
270
|
+
gap: 6,
|
|
271
|
+
alignItems: 'center',
|
|
272
|
+
padding: '6px 8px',
|
|
273
|
+
fontSize: 12,
|
|
274
|
+
}}
|
|
275
|
+
>
|
|
276
|
+
<input
|
|
277
|
+
type="checkbox"
|
|
278
|
+
data-testid="ingest-env-fog"
|
|
279
|
+
checked={hasFog}
|
|
280
|
+
onChange={(e) => {
|
|
281
|
+
if (!scene) return;
|
|
282
|
+
scene.fog = e.target.checked ? new THREE.Fog(0x8899aa, 1, 80) : null;
|
|
283
|
+
store.notifyIngestEdit();
|
|
284
|
+
}}
|
|
285
|
+
/>
|
|
286
|
+
Fog
|
|
287
|
+
</label>
|
|
288
|
+
</div>
|
|
289
|
+
</Panel>
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const node = adapter.hierarchy.node(id);
|
|
294
|
+
// A registered section only matches a REAL, resolved node — an id the
|
|
295
|
+
// current adapter's hierarchy can't resolve (`node === null` here, despite
|
|
296
|
+
// `id` being truthy) never matches (mirrors the deleted A3 dispatch's own
|
|
297
|
+
// `ownerOf(id) === null -> false`), so it renders the plain generic
|
|
298
|
+
// built-ins below exactly as it always has, never a first-party section.
|
|
299
|
+
const matched = node ? matchedInspectorSections(node, adapter) : [];
|
|
300
|
+
const suppressed = new Set<InspectorBuiltin>();
|
|
301
|
+
for (const reg of matched) {
|
|
302
|
+
for (const b of reg.suppresses ?? []) suppressed.add(b);
|
|
303
|
+
}
|
|
304
|
+
const panelNameOverride = matched.map((reg) => reg.panelName).find((n): n is string => !!n);
|
|
305
|
+
const t = adapter.transforms?.get(id) ?? {
|
|
306
|
+
position: [0, 0, 0],
|
|
307
|
+
rotation: [0, 0, 0, 1],
|
|
308
|
+
scale: [1, 1, 1],
|
|
309
|
+
};
|
|
310
|
+
// The full property set is DRIVEN BY the adapter's InspectorProvider — a generic
|
|
311
|
+
// PropertyDescriptor list (name/visible/material/light/camera/mesh), not a
|
|
312
|
+
// hardcoded panel. 'name' is rendered at the header; the rest as a generic grid.
|
|
313
|
+
const props = (adapter.inspector?.properties(id) ?? []).filter((p) => p.path !== 'name');
|
|
314
|
+
|
|
315
|
+
// D4 (B2) — story picker gating: the section itself only appears for a
|
|
316
|
+
// world-group row whose owning child adapter has a `stories` provider at
|
|
317
|
+
// all (see `worldGroupChildAdapter`'s doc comment); WITHIN that, the
|
|
318
|
+
// dropdown is gated on `storiesFor(id).length > 0` — NEVER on
|
|
319
|
+
// `adapter.stories` truthiness alone, since the composite's OWN `stories`
|
|
320
|
+
// pass-through is always present regardless of what (if anything) the
|
|
321
|
+
// routed child actually offers.
|
|
322
|
+
//
|
|
323
|
+
// B3 — a `kind:'component'` catalog node ALSO gets this section (its own
|
|
324
|
+
// owner resolution, `catalogNodeOwnerAdapter`, since it isn't a `world:<id>`
|
|
325
|
+
// group row) — the SAME story dropdown (reused verbatim), plus an Isolate
|
|
326
|
+
// toggle that renders the component ALONE against whichever story is
|
|
327
|
+
// currently selected.
|
|
328
|
+
const isCatalogNode = node?.kind === 'component';
|
|
329
|
+
const storiesOwner =
|
|
330
|
+
worldGroupChildAdapter(adapter, id) ?? catalogNodeOwnerAdapter(adapter, node, id);
|
|
331
|
+
const storyRefs = storiesOwner?.stories ? (adapter.stories?.storiesFor(id) ?? []) : [];
|
|
332
|
+
const showStoriesSection = !!storiesOwner?.stories || isCatalogNode;
|
|
333
|
+
const isIsolated = isCatalogNode && isCatalogNodeIsolated(storiesOwner, id);
|
|
334
|
+
|
|
335
|
+
const commitTransform = (axis: 0 | 1 | 2, key: keyof Transform, v: number) => {
|
|
336
|
+
if (!Number.isFinite(v)) return;
|
|
337
|
+
const next: Transform = {
|
|
338
|
+
position: [...t.position] as [number, number, number],
|
|
339
|
+
rotation: [...t.rotation] as [number, number, number, number],
|
|
340
|
+
scale: [...t.scale] as [number, number, number],
|
|
341
|
+
};
|
|
342
|
+
(next[key] as number[])[axis] = v;
|
|
343
|
+
adapter.transforms?.beginEdit(id);
|
|
344
|
+
adapter.transforms?.apply(id, next);
|
|
345
|
+
adapter.transforms?.endEdit(id);
|
|
346
|
+
store.notifyIngestEdit();
|
|
347
|
+
};
|
|
348
|
+
const setProp = (path: string, value: unknown) => {
|
|
349
|
+
adapter.inspector?.set(id, path, value);
|
|
350
|
+
store.notifyIngestEdit();
|
|
351
|
+
};
|
|
352
|
+
// Stable testid: keep the legacy ones the e2e relies on.
|
|
353
|
+
const testidFor = (path: string) =>
|
|
354
|
+
path === 'visible' ? 'ingest-visible' : `ingest-prop-${path.replace(/\./g, '-')}`;
|
|
355
|
+
|
|
356
|
+
// Per-descriptor row renderer (T3.4 slice 2, docs/ADAPTER-AUTHORING-DESIGN.md
|
|
357
|
+
// §1.E) — extracted so BOTH the flat (ungrouped) list and each titled group
|
|
358
|
+
// sub-section below render a `PropertyDescriptor` identically. Behavior is
|
|
359
|
+
// unchanged from before `group` existed; this is a pure refactor of the
|
|
360
|
+
// same per-`p.type` switch.
|
|
361
|
+
const renderProperty = (p: PropertyDescriptor) => {
|
|
362
|
+
const v = adapter.inspector?.get(id, p.path);
|
|
363
|
+
if (p.type === 'boolean') {
|
|
364
|
+
return (
|
|
365
|
+
<label
|
|
366
|
+
key={p.path}
|
|
367
|
+
style={{
|
|
368
|
+
display: 'flex',
|
|
369
|
+
gap: 6,
|
|
370
|
+
alignItems: 'center',
|
|
371
|
+
padding: '6px 8px',
|
|
372
|
+
fontSize: 12,
|
|
373
|
+
}}
|
|
374
|
+
>
|
|
375
|
+
<input
|
|
376
|
+
type="checkbox"
|
|
377
|
+
data-testid={testidFor(p.path)}
|
|
378
|
+
checked={v !== false}
|
|
379
|
+
onChange={(e) => setProp(p.path, e.target.checked)}
|
|
380
|
+
/>
|
|
381
|
+
{p.label}
|
|
382
|
+
</label>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return (
|
|
386
|
+
<div
|
|
387
|
+
key={p.path}
|
|
388
|
+
style={{ display: 'flex', alignItems: 'center', gap: 6, padding: '6px 8px' }}
|
|
389
|
+
>
|
|
390
|
+
<span style={{ width: 64, fontSize: 12, color: '#9aa0a6' }}>{p.label}</span>
|
|
391
|
+
{p.type === 'color' ? (
|
|
392
|
+
<input
|
|
393
|
+
type="color"
|
|
394
|
+
data-testid={p.path === 'material.color' ? 'ingest-color' : testidFor(p.path)}
|
|
395
|
+
value={(v as string) ?? '#ffffff'}
|
|
396
|
+
onChange={(e) => setProp(p.path, e.target.value)}
|
|
397
|
+
/>
|
|
398
|
+
) : p.type === 'number' ? (
|
|
399
|
+
<input
|
|
400
|
+
type="number"
|
|
401
|
+
data-testid={testidFor(p.path)}
|
|
402
|
+
disabled={p.readonly}
|
|
403
|
+
value={typeof v === 'number' ? Math.round(v * 1000) / 1000 : 0}
|
|
404
|
+
onChange={(e) => setProp(p.path, Number.parseFloat(e.target.value))}
|
|
405
|
+
style={NUM_STYLE}
|
|
406
|
+
/>
|
|
407
|
+
) : (
|
|
408
|
+
<input
|
|
409
|
+
type="text"
|
|
410
|
+
data-testid={testidFor(p.path)}
|
|
411
|
+
readOnly={p.readonly}
|
|
412
|
+
value={String(v ?? '')}
|
|
413
|
+
onChange={(e) => setProp(p.path, e.target.value)}
|
|
414
|
+
style={{ ...NUM_STYLE, width: 120 }}
|
|
415
|
+
/>
|
|
416
|
+
)}
|
|
417
|
+
</div>
|
|
418
|
+
);
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// Split into the flat (ungrouped, today's-behavior) list and named groups —
|
|
422
|
+
// grouped fields render together under a titled sub-section instead of the
|
|
423
|
+
// flat grid (§1.E: "the generic inspector panel renders grouped sections").
|
|
424
|
+
// Order preserved: ungrouped fields keep their original position/order
|
|
425
|
+
// (byte-stable when nothing is grouped); groups render in first-seen order
|
|
426
|
+
// AFTER the ungrouped fields. The split itself is a pure, unit-tested
|
|
427
|
+
// helper (`inspector-property-grouping.ts`) — this component only renders it.
|
|
428
|
+
const { ungrouped: ungroupedProps, groups } = groupProperties(props);
|
|
429
|
+
|
|
430
|
+
return (
|
|
431
|
+
<Panel
|
|
432
|
+
name={panelNameOverride ?? 'Inspector (ingested game)'}
|
|
433
|
+
data-testid="inspector-panel"
|
|
434
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
435
|
+
>
|
|
436
|
+
<div style={{ pointerEvents: 'auto' }}>
|
|
437
|
+
{!suppressed.has('header') && (
|
|
438
|
+
<div style={{ padding: 8, borderBottom: '1px solid #333' }}>
|
|
439
|
+
<input
|
|
440
|
+
data-testid="ingest-name"
|
|
441
|
+
value={node?.label ?? ''}
|
|
442
|
+
onChange={(e) => setProp('name', e.target.value)}
|
|
443
|
+
style={{ ...NUM_STYLE, width: '100%', fontWeight: 600 }}
|
|
444
|
+
/>
|
|
445
|
+
<div style={{ marginTop: 6, fontSize: 11, color: '#777' }}>
|
|
446
|
+
{node?.kind} · runtime-only (ingested)
|
|
447
|
+
</div>
|
|
448
|
+
</div>
|
|
449
|
+
)}
|
|
450
|
+
|
|
451
|
+
{!suppressed.has('transform') && (
|
|
452
|
+
<>
|
|
453
|
+
<AxisRow
|
|
454
|
+
label="Position"
|
|
455
|
+
testidPrefix="ingest-position"
|
|
456
|
+
values={t.position}
|
|
457
|
+
onCommit={(a, v) => commitTransform(a, 'position', v)}
|
|
458
|
+
/>
|
|
459
|
+
<AxisRow
|
|
460
|
+
label="Scale"
|
|
461
|
+
testidPrefix="ingest-scale"
|
|
462
|
+
values={t.scale}
|
|
463
|
+
onCommit={(a, v) => commitTransform(a, 'scale', v)}
|
|
464
|
+
/>
|
|
465
|
+
</>
|
|
466
|
+
)}
|
|
467
|
+
|
|
468
|
+
{/* Generic InspectorProvider-driven property grid (per descriptor type) —
|
|
469
|
+
ungrouped fields first (byte-stable: identical to pre-`group` output
|
|
470
|
+
when no descriptor declares one), then each named group as a titled
|
|
471
|
+
sub-section (§1.E). */}
|
|
472
|
+
{!suppressed.has('properties') && (
|
|
473
|
+
<>
|
|
474
|
+
{ungroupedProps.map(renderProperty)}
|
|
475
|
+
{groups.map((group) => (
|
|
476
|
+
<div
|
|
477
|
+
key={group.name}
|
|
478
|
+
data-testid={`ingest-group-${group.groupId}`}
|
|
479
|
+
style={{ borderTop: '1px solid #333', marginTop: 4 }}
|
|
480
|
+
>
|
|
481
|
+
<div
|
|
482
|
+
style={{
|
|
483
|
+
fontSize: 11,
|
|
484
|
+
fontWeight: 600,
|
|
485
|
+
color: '#9aa0a6',
|
|
486
|
+
padding: '6px 8px 2px',
|
|
487
|
+
textTransform: 'uppercase',
|
|
488
|
+
letterSpacing: 0.4,
|
|
489
|
+
}}
|
|
490
|
+
>
|
|
491
|
+
{group.name}
|
|
492
|
+
</div>
|
|
493
|
+
{group.properties.map(renderProperty)}
|
|
494
|
+
</div>
|
|
495
|
+
))}
|
|
496
|
+
</>
|
|
497
|
+
)}
|
|
498
|
+
|
|
499
|
+
{/* Components section (C1, `docs/unified-world-editor/26-unified-editor-
|
|
500
|
+
spec.md` §C1) — driven by `adapter.components` (a `ComponentsProvider`,
|
|
501
|
+
absent on every ingest/pixi/react/world-group adapter reachable from
|
|
502
|
+
here today, so this renders the N/A fallback in practice until C2
|
|
503
|
+
routes first-party nodes through this generic panel). When the
|
|
504
|
+
provider exists AND has something to show, render its `list()` rows
|
|
505
|
+
(each removable) plus an "+ Add component…" select over `available()`.
|
|
506
|
+
Otherwise fall back to the ORIGINAL honest-N/A copy, under the SAME
|
|
507
|
+
`ingest-components-na` testid, byte-identical to before this section
|
|
508
|
+
existed (feat-inspector-components-na / `14-ingest-feature-matrix`). */}
|
|
509
|
+
{!suppressed.has('components') &&
|
|
510
|
+
(() => {
|
|
511
|
+
const compList = adapter.components?.list(id) ?? [];
|
|
512
|
+
const compAvailable = adapter.components?.available(id) ?? [];
|
|
513
|
+
if (!adapter.components || (compList.length === 0 && compAvailable.length === 0)) {
|
|
514
|
+
return (
|
|
515
|
+
<div
|
|
516
|
+
data-testid="ingest-components-na"
|
|
517
|
+
style={{ padding: '8px', borderTop: '1px solid #333', marginTop: 4 }}
|
|
518
|
+
>
|
|
519
|
+
<div style={{ fontSize: 11, fontWeight: 600, color: '#9aa0a6', marginBottom: 4 }}>
|
|
520
|
+
Components
|
|
521
|
+
</div>
|
|
522
|
+
<div style={{ fontSize: 11, color: '#777', fontStyle: 'italic' }}>
|
|
523
|
+
No components — this object is owned by the unmodified game (no engine
|
|
524
|
+
GameComponents).
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
const componentsProvider = adapter.components;
|
|
530
|
+
return (
|
|
531
|
+
<div style={{ padding: '8px', borderTop: '1px solid #333', marginTop: 4 }}>
|
|
532
|
+
<div style={{ fontSize: 11, fontWeight: 600, color: '#9aa0a6', marginBottom: 4 }}>
|
|
533
|
+
Components
|
|
534
|
+
</div>
|
|
535
|
+
{compList.map((c) => (
|
|
536
|
+
<div
|
|
537
|
+
key={c.type}
|
|
538
|
+
data-testid="ingest-component-row"
|
|
539
|
+
style={{
|
|
540
|
+
display: 'flex',
|
|
541
|
+
alignItems: 'center',
|
|
542
|
+
justifyContent: 'space-between',
|
|
543
|
+
padding: '2px 0',
|
|
544
|
+
}}
|
|
545
|
+
>
|
|
546
|
+
<span style={{ fontSize: 11, color: '#c5c8ce' }}>{c.type}</span>
|
|
547
|
+
<button
|
|
548
|
+
type="button"
|
|
549
|
+
data-testid="ingest-component-remove"
|
|
550
|
+
title="Remove component"
|
|
551
|
+
onClick={() => {
|
|
552
|
+
componentsProvider.remove(id, c.type);
|
|
553
|
+
store.notifyIngestEdit();
|
|
554
|
+
}}
|
|
555
|
+
style={{
|
|
556
|
+
background: 'transparent',
|
|
557
|
+
border: 'none',
|
|
558
|
+
color: '#777',
|
|
559
|
+
cursor: 'pointer',
|
|
560
|
+
fontSize: 12,
|
|
561
|
+
lineHeight: 1,
|
|
562
|
+
padding: 0,
|
|
563
|
+
}}
|
|
564
|
+
>
|
|
565
|
+
×
|
|
566
|
+
</button>
|
|
567
|
+
</div>
|
|
568
|
+
))}
|
|
569
|
+
{compAvailable.length > 0 && (
|
|
570
|
+
<select
|
|
571
|
+
data-testid="ingest-component-add"
|
|
572
|
+
value=""
|
|
573
|
+
onChange={(e) => {
|
|
574
|
+
if (e.target.value) {
|
|
575
|
+
componentsProvider.add(id, e.target.value);
|
|
576
|
+
store.notifyIngestEdit();
|
|
577
|
+
}
|
|
578
|
+
}}
|
|
579
|
+
style={{ ...NUM_STYLE, width: '100%', marginTop: 4, cursor: 'pointer' }}
|
|
580
|
+
>
|
|
581
|
+
<option value="">+ Add component…</option>
|
|
582
|
+
{compAvailable.map((name) => (
|
|
583
|
+
<option key={name} value={name}>
|
|
584
|
+
{name}
|
|
585
|
+
</option>
|
|
586
|
+
))}
|
|
587
|
+
</select>
|
|
588
|
+
)}
|
|
589
|
+
</div>
|
|
590
|
+
);
|
|
591
|
+
})()}
|
|
592
|
+
|
|
593
|
+
{/* D4 (B2) — story picker: for a world:<id> group row whose owning
|
|
594
|
+
child adapter itself advertises `stories` (a react world, live or
|
|
595
|
+
not — see `worldGroupChildAdapter`'s doc comment), OR (B3) a
|
|
596
|
+
`kind:'component'` catalog node (`catalogNodeOwnerAdapter`). Any
|
|
597
|
+
other selection (an ordinary entity, or a world kind that never
|
|
598
|
+
sets `stories`) omits this section entirely. */}
|
|
599
|
+
{!suppressed.has('stories') && showStoriesSection && (
|
|
600
|
+
<div style={{ padding: '8px', borderTop: '1px solid #333', marginTop: 4 }}>
|
|
601
|
+
<div style={{ fontSize: 11, fontWeight: 600, color: '#9aa0a6', marginBottom: 4 }}>
|
|
602
|
+
Stories
|
|
603
|
+
</div>
|
|
604
|
+
{storyRefs.length > 0 ? (
|
|
605
|
+
<select
|
|
606
|
+
data-testid="ingest-stories-select"
|
|
607
|
+
value={adapter.stories?.active(id) ?? ''}
|
|
608
|
+
onChange={(e) => {
|
|
609
|
+
const v = e.target.value;
|
|
610
|
+
adapter.stories?.apply(id, v === '' ? null : v);
|
|
611
|
+
}}
|
|
612
|
+
style={{ ...NUM_STYLE, width: '100%' }}
|
|
613
|
+
>
|
|
614
|
+
<option value="">(none)</option>
|
|
615
|
+
{storyRefs.map((s) => (
|
|
616
|
+
<option key={s.id} value={s.id}>
|
|
617
|
+
{s.label}
|
|
618
|
+
</option>
|
|
619
|
+
))}
|
|
620
|
+
</select>
|
|
621
|
+
) : (
|
|
622
|
+
<div
|
|
623
|
+
data-testid="ingest-stories-none"
|
|
624
|
+
style={{ fontSize: 11, color: '#777', fontStyle: 'italic' }}
|
|
625
|
+
>
|
|
626
|
+
No stories — this world defines no `src/ui/registry.ts` states/stories.
|
|
627
|
+
</div>
|
|
628
|
+
)}
|
|
629
|
+
{/* B3 — Isolate toggle: render ONLY this catalog component (alone,
|
|
630
|
+
against whichever story is currently selected in the dropdown
|
|
631
|
+
above) on the world's design-time layer; exiting restores the
|
|
632
|
+
entry (live preview). Session-only — mirrors the story-overlay
|
|
633
|
+
writes below, never persisted to the registry. */}
|
|
634
|
+
{isCatalogNode && (
|
|
635
|
+
<button
|
|
636
|
+
type="button"
|
|
637
|
+
data-testid="catalog-isolate"
|
|
638
|
+
onClick={() => {
|
|
639
|
+
toggleCatalogIsolation(adapter, id, isIsolated);
|
|
640
|
+
store.notifyIngestEdit();
|
|
641
|
+
}}
|
|
642
|
+
style={{
|
|
643
|
+
marginTop: 6,
|
|
644
|
+
width: '100%',
|
|
645
|
+
padding: '6px 8px',
|
|
646
|
+
fontSize: 12,
|
|
647
|
+
background: isIsolated ? '#3a5' : '#2a2a2a',
|
|
648
|
+
color: '#e6e6e6',
|
|
649
|
+
border: '1px solid #444',
|
|
650
|
+
borderRadius: 3,
|
|
651
|
+
cursor: 'pointer',
|
|
652
|
+
}}
|
|
653
|
+
>
|
|
654
|
+
{isIsolated ? 'Exit isolation' : 'Isolate'}
|
|
655
|
+
</button>
|
|
656
|
+
)}
|
|
657
|
+
</div>
|
|
658
|
+
)}
|
|
659
|
+
|
|
660
|
+
{/* C2 — matched registered sections (the first-party threejs adapter's
|
|
661
|
+
rich Transform/Mesh/…/Components/AddSectionMenu, the UI adapter's
|
|
662
|
+
Layout/Style/Props/Events, or any future spec-27 contribution) —
|
|
663
|
+
rendered AFTER the generic grid above, as siblings, in
|
|
664
|
+
registration order. */}
|
|
665
|
+
{matched.map(({ Section }, i) => (
|
|
666
|
+
<Section key={i} adapter={adapter} nodeId={id} />
|
|
667
|
+
))}
|
|
668
|
+
</div>
|
|
669
|
+
</Panel>
|
|
670
|
+
);
|
|
671
|
+
}
|