@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,194 @@
|
|
|
1
|
+
import { connectEvents } from './editor-api';
|
|
2
|
+
import { getEditorMode } from './editor-mode';
|
|
3
|
+
|
|
4
|
+
export type ConsoleLevel = 'info' | 'warn' | 'error';
|
|
5
|
+
|
|
6
|
+
export interface ConsoleEntry {
|
|
7
|
+
id: number;
|
|
8
|
+
timestamp: number;
|
|
9
|
+
level: ConsoleLevel;
|
|
10
|
+
message: string;
|
|
11
|
+
source?: string;
|
|
12
|
+
subsystem?: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
count: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const MAX_ENTRIES = 1000;
|
|
18
|
+
|
|
19
|
+
class EditorConsole {
|
|
20
|
+
private _entries: ConsoleEntry[] = [];
|
|
21
|
+
private _listeners = new Set<() => void>();
|
|
22
|
+
private _nextId = 0;
|
|
23
|
+
private _version = 0;
|
|
24
|
+
private _sink: ((entry: ConsoleEntry) => void) | null = null;
|
|
25
|
+
|
|
26
|
+
/** Register (or clear) a callback invoked for every console entry. */
|
|
27
|
+
setSink(fn: ((entry: ConsoleEntry) => void) | null): void {
|
|
28
|
+
this._sink = fn;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
counts = { info: 0, warn: 0, error: 0 };
|
|
32
|
+
|
|
33
|
+
log(message: string, source?: string): void {
|
|
34
|
+
this._push('info', message, source);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
warn(message: string, source?: string): void {
|
|
38
|
+
this._push('warn', message, source);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
error(message: string, source?: string): void {
|
|
42
|
+
this._push('error', message, source);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Log with structured metadata (used by the engine logger). */
|
|
46
|
+
logStructured(
|
|
47
|
+
level: ConsoleLevel,
|
|
48
|
+
message: string,
|
|
49
|
+
source: string,
|
|
50
|
+
subsystem?: string,
|
|
51
|
+
metadata?: Record<string, unknown>,
|
|
52
|
+
): void {
|
|
53
|
+
this._push(level, message, source, subsystem, metadata);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
clear(): void {
|
|
57
|
+
this._entries = [];
|
|
58
|
+
this.counts = { info: 0, warn: 0, error: 0 };
|
|
59
|
+
this._notify();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
getEntries(): readonly ConsoleEntry[] {
|
|
63
|
+
return this._entries;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// useSyncExternalStore compatible
|
|
67
|
+
subscribe = (cb: () => void): (() => void) => {
|
|
68
|
+
this._listeners.add(cb);
|
|
69
|
+
return () => this._listeners.delete(cb);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
getSnapshot = (): number => this._version;
|
|
73
|
+
|
|
74
|
+
private _push(
|
|
75
|
+
level: ConsoleLevel,
|
|
76
|
+
message: string,
|
|
77
|
+
source?: string,
|
|
78
|
+
subsystem?: string,
|
|
79
|
+
metadata?: Record<string, unknown>,
|
|
80
|
+
): void {
|
|
81
|
+
// Collapse identical consecutive messages
|
|
82
|
+
const last = this._entries[this._entries.length - 1];
|
|
83
|
+
if (last && last.level === level && last.message === message && last.source === source) {
|
|
84
|
+
last.count++;
|
|
85
|
+
last.timestamp = Date.now();
|
|
86
|
+
this._sink?.(last);
|
|
87
|
+
this._notify();
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const entry: ConsoleEntry = {
|
|
92
|
+
id: this._nextId++,
|
|
93
|
+
timestamp: Date.now(),
|
|
94
|
+
level,
|
|
95
|
+
message,
|
|
96
|
+
count: 1,
|
|
97
|
+
};
|
|
98
|
+
if (source) entry.source = source;
|
|
99
|
+
if (subsystem) entry.subsystem = subsystem;
|
|
100
|
+
if (metadata) entry.metadata = metadata;
|
|
101
|
+
this._entries.push(entry);
|
|
102
|
+
this.counts[level]++;
|
|
103
|
+
this._sink?.(entry);
|
|
104
|
+
|
|
105
|
+
// Cap at MAX_ENTRIES, drop oldest
|
|
106
|
+
if (this._entries.length > MAX_ENTRIES) {
|
|
107
|
+
const removed = this._entries.shift()!;
|
|
108
|
+
this.counts[removed.level] = Math.max(0, this.counts[removed.level] - 1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this._notify();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
private _notify(): void {
|
|
115
|
+
this._version++;
|
|
116
|
+
for (const fn of this._listeners) fn();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const editorConsole = new EditorConsole();
|
|
121
|
+
|
|
122
|
+
// Test-only hook (e2e) — the window mirror `design/24-scene-ui.md` A4(f)
|
|
123
|
+
// anticipates: direct read access to console entries without DOM-scraping
|
|
124
|
+
// `ConsolePanel` (which has no per-entry testids). Same ad-hoc `window.__vgai*`
|
|
125
|
+
// convention as every other dev-only hook in `test-window.ts`.
|
|
126
|
+
(window as unknown as { __vgaiEditorConsole?: EditorConsole })['__vgaiEditorConsole'] =
|
|
127
|
+
editorConsole;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Listen for `console-bridge` postMessage events from game iframes (or self)
|
|
131
|
+
* and pipe them into the editor console.
|
|
132
|
+
*/
|
|
133
|
+
const BRIDGE_LEVEL_MAP: Record<string, ConsoleLevel> = {
|
|
134
|
+
log: 'info',
|
|
135
|
+
warn: 'warn',
|
|
136
|
+
error: 'error',
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/** LogLevel string → ConsoleLevel for engine-log messages. */
|
|
140
|
+
const ENGINE_LEVEL_MAP: Record<string, ConsoleLevel> = {
|
|
141
|
+
trace: 'info',
|
|
142
|
+
debug: 'info',
|
|
143
|
+
info: 'info',
|
|
144
|
+
warn: 'warn',
|
|
145
|
+
error: 'error',
|
|
146
|
+
fatal: 'error',
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
window.addEventListener('message', (e: MessageEvent) => {
|
|
150
|
+
// Structured engine-log messages (from engine logger)
|
|
151
|
+
if (e.data?.type === 'engine-log') {
|
|
152
|
+
const level = ENGINE_LEVEL_MAP[e.data.level];
|
|
153
|
+
if (!level) return;
|
|
154
|
+
editorConsole.logStructured(level, e.data.message, 'engine', e.data.subsystem, e.data.metadata);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Plain console-bridge messages (from game code)
|
|
159
|
+
if (e.data?.type === 'console-bridge') {
|
|
160
|
+
const level = BRIDGE_LEVEL_MAP[e.data.level];
|
|
161
|
+
if (!level) return;
|
|
162
|
+
editorConsole[level === 'info' ? 'log' : level](e.data.message, 'engine');
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Connect to the editor SSE endpoint and pipe `server-log` events
|
|
168
|
+
* into the editor console tagged with source 'server'.
|
|
169
|
+
*/
|
|
170
|
+
export function connectServerLogs(): () => void {
|
|
171
|
+
// Phase A cleanup: browser mode has no Node dev server, so `/__editor/events`
|
|
172
|
+
// does not exist — an unconditional `EventSource` here would auto-retry
|
|
173
|
+
// against a dead endpoint forever (the same anti-pattern A2 already fixed
|
|
174
|
+
// for the CLI/asset/scene SSE consumers via `getEditorMode() === 'browser'`
|
|
175
|
+
// guards; this one was missed). There is no browser-mode server-log source
|
|
176
|
+
// to bridge from, so this is a clean skip, not a routed replacement.
|
|
177
|
+
if (getEditorMode() === 'browser') {
|
|
178
|
+
return () => {};
|
|
179
|
+
}
|
|
180
|
+
const source = connectEvents();
|
|
181
|
+
|
|
182
|
+
source.addEventListener('server-log', (e: MessageEvent) => {
|
|
183
|
+
try {
|
|
184
|
+
const data = JSON.parse(e.data) as { level: string; message: string };
|
|
185
|
+
const level = BRIDGE_LEVEL_MAP[data.level];
|
|
186
|
+
if (!level) return;
|
|
187
|
+
editorConsole[level === 'info' ? 'log' : level](data.message, 'server');
|
|
188
|
+
} catch {
|
|
189
|
+
/* ignore malformed events */
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return () => source.close();
|
|
194
|
+
}
|
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectAllNodeIds,
|
|
3
|
+
getActiveAuthoring,
|
|
4
|
+
hasAuthoringOverride,
|
|
5
|
+
} from './authoring/active-adapter';
|
|
6
|
+
import { timelineRedo, timelineUndo } from './authoring/undo-timeline';
|
|
7
|
+
import { resetWorldPan } from './authoring/world-pan-state';
|
|
8
|
+
import {
|
|
9
|
+
boxEditForId,
|
|
10
|
+
computeArrowNudgePatch,
|
|
11
|
+
readSpacingValues,
|
|
12
|
+
rectForId,
|
|
13
|
+
} from './components/world-overlay-gestures';
|
|
14
|
+
import type { EditorStore } from './editor-store';
|
|
15
|
+
import type { EditorViewport } from './editor-viewport';
|
|
16
|
+
import { installHotkeys, registerHotkeys, setActiveScope } from './hotkeys';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* D4 (spec27 §6 D4, §8 "Keyboard / undo / pan-zoom" row) — RECONCILED by the
|
|
20
|
+
* `overlay-panzoom` unit (see `docs/unified-world-editor/27-visual-react-
|
|
21
|
+
* editing.md` §1 D-E's own reconciliation note and `GOLDEN-GATE.md`).
|
|
22
|
+
*
|
|
23
|
+
* Space-pan is BUILT: `WorldSelectionOverlay.tsx`'s own Space-held +
|
|
24
|
+
* pointer-drag handling (its `spaceHeldRef`/`panDragRef`), writing the shared
|
|
25
|
+
* `world-pan-state.ts` singleton, which `design-time-layers.ts` applies to
|
|
26
|
+
* every world-layer host IN LOCKSTEP with the overlay's own wrapper — a pure
|
|
27
|
+
* CSS `transform: translate()` on both. Not registered here in
|
|
28
|
+
* `editor-hotkeys.ts`'s `registerHotkeys` array because that registry is a
|
|
29
|
+
* single-fire-per-keydown dispatcher with no "held" concept (see
|
|
30
|
+
* `hotkeys.ts`); Space-pan needs a literal hold state, so it follows the
|
|
31
|
+
* SAME precedent this file's own vertex-snap `V`-key handling below already
|
|
32
|
+
* set (a raw `window` keydown/keyup pair) — just owned by the overlay
|
|
33
|
+
* component instead of this file, since the gesture IS a pointer-drag over
|
|
34
|
+
* the overlay's own interaction layer, not a stateless action like
|
|
35
|
+
* duplicate/delete/nudge above.
|
|
36
|
+
*
|
|
37
|
+
* Zoom-fit/selection is a stated EXCLUDE — evidence checked (not assumed):
|
|
38
|
+
* the 3D viewport (`ViewportPanel.tsx`/`editor-viewport.ts`) already has a
|
|
39
|
+
* real zoom substrate (Three.js `OrbitControls`, numpad view presets), so
|
|
40
|
+
* zoom-fit there is pure duplication, not a gap. The react/DOM world
|
|
41
|
+
* surface's `RectProvider.rect(id)` is `getBoundingClientRect()`-derived and
|
|
42
|
+
* host-relative (`react-world-authoring-adapter.ts`'s `toHostRelative`) —
|
|
43
|
+
* UNLIKE pan (a pure translation, which cancels out of that subtraction with
|
|
44
|
+
* zero consumer changes, per `world-pan-state.ts`'s own soundness doc
|
|
45
|
+
* comment), a host `scale()` bakes the zoom factor into every rect AND every
|
|
46
|
+
* screen-space gesture delta (`toHostLocal`), so it would require an
|
|
47
|
+
* explicit zoom-aware conversion at all ~14 host-relative chrome consumers
|
|
48
|
+
* (selection/hover box, 8 handles, rotate, labels, spacing bands, measure
|
|
49
|
+
* lines, snap guides, badges, parent outline, drag-ghost, reorder marker,
|
|
50
|
+
* empty-container hints, align-toolbar anchor, marquee) and the 3
|
|
51
|
+
* screen-delta gesture patches (`computeResizePatch`/`computeMovePatch`/
|
|
52
|
+
* `computeSpacingPatch`) — a foundation rebase against the ~2360-test unit
|
|
53
|
+
* suite + the full 27x e2e series (all pinned at the implicit `scale=1`
|
|
54
|
+
* frame) that is disproportionate to one keyboard-row affordance the
|
|
55
|
+
* visual-edit reference itself only had live in its own demo host, not its
|
|
56
|
+
* reusable library. No dead keybinding is registered for it.
|
|
57
|
+
*/
|
|
58
|
+
|
|
59
|
+
// The five action functions below (`duplicateSelection` … `nudgeSelection`)
|
|
60
|
+
// are exported for two reasons: `registerEditorHotkeys` wires them to their
|
|
61
|
+
// keybindings below, AND `editor-hotkeys.test.ts` unit-tests the
|
|
62
|
+
// capability-gate behavior (structure/boxEdit absent -> no-op) directly
|
|
63
|
+
// against them, without needing a full `EditorViewport`/canvas fixture.
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* D4 (spec27 §6 D4 "Ctrl+D duplicate, Del/Backspace delete — route through
|
|
67
|
+
* adapter.structure.* (capability-gated)"): the pre-D4 `'d'`/`'delete'`/
|
|
68
|
+
* `'backspace'` bindings below called the FIRST-PARTY-only `store.
|
|
69
|
+
* duplicateEntity`/`store.deleteEntities` directly — a silent no-op for a
|
|
70
|
+
* react-world/ingest/scene-UI selection (those ids aren't in `store`'s own
|
|
71
|
+
* `SceneEntity` map). This keeps the first-party path'S existing batched-
|
|
72
|
+
* undo behavior (`store.deleteEntities` pushes ONE undo entry for the whole
|
|
73
|
+
* selection; looping the contract's per-id `structure.remove` would instead
|
|
74
|
+
* push N) byte-identical, and ADDS the override path via `adapter.structure`
|
|
75
|
+
* (capability-gated — a no-op when absent, e.g. the ingested `react-dom`
|
|
76
|
+
* adapter, spec:308-309). `store.selectedEntityIds` is the shared,
|
|
77
|
+
* adapter-routed selection set either way (every adapter's own
|
|
78
|
+
* `selection.set` funnels into it) — only the ACTION differs by path.
|
|
79
|
+
*/
|
|
80
|
+
export function duplicateSelection(store: EditorStore): void {
|
|
81
|
+
if (store.selectedEntityIds.size === 0) return;
|
|
82
|
+
if (hasAuthoringOverride()) {
|
|
83
|
+
const structure = getActiveAuthoring(store).structure;
|
|
84
|
+
if (!structure) return;
|
|
85
|
+
for (const id of store.selectedEntityIds) structure.duplicate(id);
|
|
86
|
+
} else {
|
|
87
|
+
for (const id of store.selectedEntityIds) store.duplicateEntity(id);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* D4.R2 (spec27 §6 D4 reopen) — a multi-node override delete prefers the
|
|
93
|
+
* batched `structure.removeMany` (ONE undo entry for the whole selection,
|
|
94
|
+
* matching the first-party `store.deleteEntities` batched-undo semantics
|
|
95
|
+
* below) when the active adapter implements it; falls back to the per-id
|
|
96
|
+
* `structure.remove` loop for any adapter that doesn't (N undo entries).
|
|
97
|
+
* `await`ed the same way `remove` is (harmless no-op for a synchronous/
|
|
98
|
+
* in-memory implementer) — see `StructureProvider.removeMany`'s doc comment.
|
|
99
|
+
*
|
|
100
|
+
* SOURCE-CORRUPTION FIX (bug-panel reopen, post-D4.R2) + DELETE-ORDER-
|
|
101
|
+
* RESIDUAL FIX (bug-panel follow-up, post-50f90a6d): the per-id fallback loop
|
|
102
|
+
* used to fire `structure.remove(id)` for every id in RAW selection order —
|
|
103
|
+
* document/top-first, the same order a marquee/DFS walk produces — with NO
|
|
104
|
+
* `await` between iterations. For an adapter whose per-op target offset is
|
|
105
|
+
* resolved from an index that is only refreshed by a real reparse (never
|
|
106
|
+
* between same-file writes — `ReactWorldAuthoringAdapter`'s `OidStore.index`),
|
|
107
|
+
* that is a real, default-gesture-reachable source-corruption hazard:
|
|
108
|
+
* deleting an EARLIER element first shifts every LATER element's still-stale
|
|
109
|
+
* `{line, col}` out from under it, and firing every write in the same tick
|
|
110
|
+
* can race the underlying network request, letting a later write silently
|
|
111
|
+
* clobber an earlier one. Both mechanisms are fixed for the fallback loop,
|
|
112
|
+
* together (see the CORRECTNESS INVARIANT comment on
|
|
113
|
+
* `ReactWorldAuthoringAdapter.structure`'s per-id `remove` for the full
|
|
114
|
+
* soundness argument, including its own remaining caveat):
|
|
115
|
+
* 1. sort `ids` into REVERSE DOCUMENT ORDER (bottom-most/deepest first) —
|
|
116
|
+
* derived from the SAME DFS pre-order `collectAllNodeIds` walks (the
|
|
117
|
+
* marquee/select-all candidate order), reversed, so a selected
|
|
118
|
+
* descendant is always deleted before a selected ancestor;
|
|
119
|
+
* 2. `await` each `structure.remove(id)` before starting the next, so
|
|
120
|
+
* write N only ever begins once write N-1 has landed on disk.
|
|
121
|
+
* A synchronous/in-memory `remove` (e.g. `UIAuthoringAdapter`'s) is
|
|
122
|
+
* unaffected — awaiting a non-promise return is a harmless no-op, and its own
|
|
123
|
+
* `removeMany` batches instead of ever reaching this loop. This fallback loop
|
|
124
|
+
* is sound ONLY when `collectAllNodeIds`' walk order matches true SOURCE
|
|
125
|
+
* order — `ReactWorldAuthoringAdapter` now implements `removeMany` (a single
|
|
126
|
+
* shared-snapshot batched delete, order-independent by construction) exactly
|
|
127
|
+
* so THIS loop is never reached for it; it stays here, documented and tested,
|
|
128
|
+
* as the honest degrade for any override adapter that has no batched delete.
|
|
129
|
+
*/
|
|
130
|
+
export async function deleteSelection(store: EditorStore): Promise<void> {
|
|
131
|
+
if (store.selectedEntityIds.size === 0) return;
|
|
132
|
+
if (hasAuthoringOverride()) {
|
|
133
|
+
const adapter = getActiveAuthoring(store);
|
|
134
|
+
const structure = adapter.structure;
|
|
135
|
+
if (!structure) return;
|
|
136
|
+
const ids = [...store.selectedEntityIds];
|
|
137
|
+
if (structure.removeMany) {
|
|
138
|
+
await structure.removeMany(ids);
|
|
139
|
+
} else {
|
|
140
|
+
// Reverse DOCUMENT order (bottom-most/deepest first) — see this
|
|
141
|
+
// function's own doc comment. `collectAllNodeIds` walks the adapter's
|
|
142
|
+
// hierarchy in the same DFS pre-order (parent-before-child, top-first)
|
|
143
|
+
// a marquee/select-all uses; an id outside that walk (should not
|
|
144
|
+
// happen — every selected id came FROM this adapter's own hierarchy)
|
|
145
|
+
// sorts last (rank -1) as a defensive fallback, never a throw.
|
|
146
|
+
const rank = new Map(collectAllNodeIds(adapter).map((id, i) => [id, i]));
|
|
147
|
+
const ordered = [...ids].sort((a, b) => (rank.get(b) ?? -1) - (rank.get(a) ?? -1));
|
|
148
|
+
for (const id of ordered) await structure.remove(id);
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
store.deleteEntities([...store.selectedEntityIds]);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** D4 — `Ctrl+Alt+G` wrap / `Ctrl+Shift+G` unwrap (spec §6 D4, §5 "wrap/
|
|
156
|
+
* unwrap ... via `structure.*`"): no first-party equivalent exists (a 3D
|
|
157
|
+
* entity can't be "wrapped"), so this is override-only, capability-gated on
|
|
158
|
+
* the OPTIONAL `structure.wrap`/`structure.unwrap` (absent for the
|
|
159
|
+
* ingested `react-dom` adapter and the first-party threejs adapter alike —
|
|
160
|
+
* both correctly no-op). Applies to every selected id, same multi-select
|
|
161
|
+
* loop `duplicateSelection`/`deleteSelection` use. */
|
|
162
|
+
export function wrapSelection(store: EditorStore): void {
|
|
163
|
+
if (store.selectedEntityIds.size === 0) return;
|
|
164
|
+
const structure = getActiveAuthoring(store).structure;
|
|
165
|
+
if (!structure?.wrap) return;
|
|
166
|
+
for (const id of store.selectedEntityIds) structure.wrap(id);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function unwrapSelection(store: EditorStore): void {
|
|
170
|
+
if (store.selectedEntityIds.size === 0) return;
|
|
171
|
+
const structure = getActiveAuthoring(store).structure;
|
|
172
|
+
if (!structure?.unwrap) return;
|
|
173
|
+
for (const id of store.selectedEntityIds) structure.unwrap(id);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* D4.c (spec §6 D4 "arrow-nudge (↑↓←→, Shift=×10) ... writes margins/
|
|
178
|
+
* offsets"): nudges every currently-selected id whose owner exposes
|
|
179
|
+
* `boxEdit` (capability-gated — silently skips ids that don't, e.g. a 3D
|
|
180
|
+
* entity or an ingested-world node with no spatial write path). Position/
|
|
181
|
+
* margin math is the pure, independently-tested `computeArrowNudgePatch`
|
|
182
|
+
* (`world-overlay-gestures.ts`); this function only resolves the per-id
|
|
183
|
+
* rect/position/margin inputs it needs and brackets the write in a single
|
|
184
|
+
* `begin`/`apply`/`end` (one undo step per nudged node, per B1/D-1).
|
|
185
|
+
*/
|
|
186
|
+
export function nudgeSelection(store: EditorStore, dx: number, dy: number): void {
|
|
187
|
+
if (store.selectedEntityIds.size === 0) return;
|
|
188
|
+
const adapter = getActiveAuthoring(store);
|
|
189
|
+
for (const id of store.selectedEntityIds) {
|
|
190
|
+
const boxEdit = boxEditForId(adapter, id);
|
|
191
|
+
if (!boxEdit) continue;
|
|
192
|
+
const rect = rectForId(adapter, id);
|
|
193
|
+
if (!rect) continue;
|
|
194
|
+
const position = adapter.inspector?.get(id, 'style.position');
|
|
195
|
+
const isPositioned = position === 'absolute' || position === 'fixed';
|
|
196
|
+
const spacing = readSpacingValues(adapter, id);
|
|
197
|
+
const patch = computeArrowNudgePatch(dx, dy, isPositioned, rect, {
|
|
198
|
+
left: spacing.marginLeft,
|
|
199
|
+
top: spacing.marginTop,
|
|
200
|
+
});
|
|
201
|
+
if (Object.keys(patch).length === 0) continue;
|
|
202
|
+
boxEdit.begin(id);
|
|
203
|
+
boxEdit.apply(id, patch);
|
|
204
|
+
boxEdit.end(id);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Register all standard editor hotkeys. Returns a dispose function that removes them.
|
|
210
|
+
* Call once when the viewport is created.
|
|
211
|
+
*/
|
|
212
|
+
export function registerEditorHotkeys(
|
|
213
|
+
store: EditorStore,
|
|
214
|
+
viewport: EditorViewport,
|
|
215
|
+
canvas: HTMLCanvasElement,
|
|
216
|
+
): () => void {
|
|
217
|
+
const unregister = registerHotkeys([
|
|
218
|
+
// --- Global (fire regardless of active panel) ---
|
|
219
|
+
// Undo/redo (T3.2 slice 2): dispatch through the editor-global undo TIMELINE,
|
|
220
|
+
// NOT `hasAuthoringOverride() ? adapter : store` — the timeline pops whichever
|
|
221
|
+
// adapter (first-party store / ingest / UI) recorded the MOST RECENT op,
|
|
222
|
+
// globally, regardless of which one is currently active (docs/
|
|
223
|
+
// PERSISTENCE-PROVIDER-DESIGN.md §3). Ctrl+Y mirrors Ctrl+Shift+Z (Windows
|
|
224
|
+
// convention) — see `undo-timeline.ts`.
|
|
225
|
+
{
|
|
226
|
+
key: 'z',
|
|
227
|
+
mod: true,
|
|
228
|
+
shift: true,
|
|
229
|
+
scope: 'global',
|
|
230
|
+
action: () => timelineRedo(),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
key: 'y',
|
|
234
|
+
mod: true,
|
|
235
|
+
scope: 'global',
|
|
236
|
+
action: () => timelineRedo(),
|
|
237
|
+
},
|
|
238
|
+
{
|
|
239
|
+
key: 'z',
|
|
240
|
+
mod: true,
|
|
241
|
+
scope: 'global',
|
|
242
|
+
action: () => timelineUndo(),
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
key: 'c',
|
|
246
|
+
mod: true,
|
|
247
|
+
scope: 'global',
|
|
248
|
+
action: () => {
|
|
249
|
+
if (store.selectedEntityIds.size > 0) store.copyEntities();
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
{
|
|
253
|
+
key: 'x',
|
|
254
|
+
mod: true,
|
|
255
|
+
scope: 'global',
|
|
256
|
+
action: () => {
|
|
257
|
+
if (store.selectedEntityIds.size > 0) store.cutEntities();
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
key: 'v',
|
|
262
|
+
mod: true,
|
|
263
|
+
scope: 'global',
|
|
264
|
+
action: () => {
|
|
265
|
+
if (store.hasClipboard) store.pasteEntities();
|
|
266
|
+
},
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
key: 'd',
|
|
270
|
+
mod: true,
|
|
271
|
+
scope: 'global',
|
|
272
|
+
action: () => duplicateSelection(store),
|
|
273
|
+
},
|
|
274
|
+
// D4 — wrap/unwrap (override-only, capability-gated — see
|
|
275
|
+
// `wrapSelection`/`unwrapSelection`'s doc comments).
|
|
276
|
+
{
|
|
277
|
+
key: 'g',
|
|
278
|
+
mod: true,
|
|
279
|
+
alt: true,
|
|
280
|
+
scope: 'global',
|
|
281
|
+
action: () => wrapSelection(store),
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
key: 'g',
|
|
285
|
+
mod: true,
|
|
286
|
+
shift: true,
|
|
287
|
+
scope: 'global',
|
|
288
|
+
action: () => unwrapSelection(store),
|
|
289
|
+
},
|
|
290
|
+
// D4 — arrow-nudge (↑↓←→, Shift=×10 — see `nudgeSelection`'s doc comment).
|
|
291
|
+
{ key: 'arrowup', scope: 'global', action: () => nudgeSelection(store, 0, -1) },
|
|
292
|
+
{ key: 'arrowup', shift: true, scope: 'global', action: () => nudgeSelection(store, 0, -10) },
|
|
293
|
+
{ key: 'arrowdown', scope: 'global', action: () => nudgeSelection(store, 0, 1) },
|
|
294
|
+
{ key: 'arrowdown', shift: true, scope: 'global', action: () => nudgeSelection(store, 0, 10) },
|
|
295
|
+
{ key: 'arrowleft', scope: 'global', action: () => nudgeSelection(store, -1, 0) },
|
|
296
|
+
{ key: 'arrowleft', shift: true, scope: 'global', action: () => nudgeSelection(store, -10, 0) },
|
|
297
|
+
{ key: 'arrowright', scope: 'global', action: () => nudgeSelection(store, 1, 0) },
|
|
298
|
+
{
|
|
299
|
+
key: 'arrowright',
|
|
300
|
+
shift: true,
|
|
301
|
+
scope: 'global',
|
|
302
|
+
action: () => nudgeSelection(store, 10, 0),
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
key: 'a',
|
|
306
|
+
mod: true,
|
|
307
|
+
scope: 'global',
|
|
308
|
+
action: () => {
|
|
309
|
+
if (hasAuthoringOverride()) {
|
|
310
|
+
const a = getActiveAuthoring(store);
|
|
311
|
+
a.selection?.set(collectAllNodeIds(a)); // select all ingested nodes
|
|
312
|
+
} else store.selectAll();
|
|
313
|
+
},
|
|
314
|
+
},
|
|
315
|
+
{ key: 's', mod: true, scope: 'global', action: () => void store.saveNow() },
|
|
316
|
+
// D4 (spec27 §8 "space-pan" row) — Ctrl+0, the conventional "reset
|
|
317
|
+
// zoom/pan to 100%" shortcut (browsers/design tools alike), resets the
|
|
318
|
+
// DOM/react overlay's shared pan translate (`world-pan-state.ts`) back to
|
|
319
|
+
// `(0, 0)`. A no-op when already at rest or when the active surface has
|
|
320
|
+
// no pan-capable world (there is nothing for it to affect either way —
|
|
321
|
+
// `resetWorldPan()` itself no-ops at `(0,0)`).
|
|
322
|
+
{ key: '0', mod: true, scope: 'global', action: () => resetWorldPan() },
|
|
323
|
+
{
|
|
324
|
+
key: 'delete',
|
|
325
|
+
scope: 'global',
|
|
326
|
+
action: () => void deleteSelection(store),
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
key: 'backspace',
|
|
330
|
+
scope: 'global',
|
|
331
|
+
action: () => void deleteSelection(store),
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
key: 'escape',
|
|
335
|
+
scope: 'global',
|
|
336
|
+
// While playing, Escape is owned by play-mode (stop on Escape) — don't also
|
|
337
|
+
// clear the editor selection out from under the running game.
|
|
338
|
+
action: () => {
|
|
339
|
+
if (store.playState === 'stopped') store.select(null);
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
{
|
|
343
|
+
key: 'k',
|
|
344
|
+
mod: true,
|
|
345
|
+
scope: 'global',
|
|
346
|
+
action: () => store.toggleCommandPalette(),
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
key: '`',
|
|
350
|
+
scope: 'global',
|
|
351
|
+
action: () => window.dispatchEvent(new CustomEvent('editor:toggle-console')),
|
|
352
|
+
},
|
|
353
|
+
|
|
354
|
+
// --- Viewport-scoped (only when viewport/canvas is active) ---
|
|
355
|
+
{
|
|
356
|
+
key: 'w',
|
|
357
|
+
scope: 'viewport',
|
|
358
|
+
action: () => {
|
|
359
|
+
if (!viewport.isFlying) store.setTransformMode('translate');
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
key: 'e',
|
|
364
|
+
scope: 'viewport',
|
|
365
|
+
action: () => {
|
|
366
|
+
if (!viewport.isFlying) store.setTransformMode('rotate');
|
|
367
|
+
},
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
key: 'r',
|
|
371
|
+
scope: 'viewport',
|
|
372
|
+
action: () => {
|
|
373
|
+
if (!viewport.isFlying) store.setTransformMode('scale');
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
key: 'f',
|
|
378
|
+
scope: 'viewport',
|
|
379
|
+
action: () => store.focusOnSelection(),
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
key: 'l',
|
|
383
|
+
scope: 'viewport',
|
|
384
|
+
action: () => {
|
|
385
|
+
if (store.selectedEntityIds.size > 0)
|
|
386
|
+
for (const id of store.selectedEntityIds) store.toggleLocked(id);
|
|
387
|
+
},
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
key: '.',
|
|
391
|
+
scope: 'viewport',
|
|
392
|
+
action: () => {
|
|
393
|
+
const pivotModes = ['active-element', 'median-point', 'individual-origins'] as const;
|
|
394
|
+
const idx = pivotModes.indexOf(store.pivotMode);
|
|
395
|
+
store.setPivotMode(pivotModes[(idx + 1) % pivotModes.length]!);
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
{ code: 'Numpad7', key: '', scope: 'viewport', action: () => viewport.setViewPreset('top') },
|
|
399
|
+
{ code: 'Numpad1', key: '', scope: 'viewport', action: () => viewport.setViewPreset('front') },
|
|
400
|
+
{
|
|
401
|
+
code: 'Numpad3',
|
|
402
|
+
key: '',
|
|
403
|
+
scope: 'viewport',
|
|
404
|
+
action: () => viewport.setViewPreset('right'),
|
|
405
|
+
},
|
|
406
|
+
{
|
|
407
|
+
code: 'Numpad5',
|
|
408
|
+
key: '',
|
|
409
|
+
scope: 'viewport',
|
|
410
|
+
action: () => viewport.setViewPreset('perspective'),
|
|
411
|
+
},
|
|
412
|
+
]);
|
|
413
|
+
|
|
414
|
+
const onPointerDown = () => setActiveScope('viewport');
|
|
415
|
+
canvas.addEventListener('pointerdown', onPointerDown);
|
|
416
|
+
installHotkeys();
|
|
417
|
+
|
|
418
|
+
// Vertex snap: hold V to activate
|
|
419
|
+
const onKeyDown = (e: KeyboardEvent) => {
|
|
420
|
+
if (e.key === 'v' && !e.metaKey && !e.ctrlKey && !e.repeat) {
|
|
421
|
+
if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;
|
|
422
|
+
store.setVertexSnapActive(true);
|
|
423
|
+
viewport.activateVertexSnap();
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
const onKeyUp = (e: KeyboardEvent) => {
|
|
427
|
+
if (e.key === 'v' && !e.metaKey && !e.ctrlKey) {
|
|
428
|
+
store.setVertexSnapActive(false);
|
|
429
|
+
viewport.deactivateVertexSnap();
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
window.addEventListener('keydown', onKeyDown);
|
|
433
|
+
window.addEventListener('keyup', onKeyUp);
|
|
434
|
+
|
|
435
|
+
return () => {
|
|
436
|
+
unregister();
|
|
437
|
+
canvas.removeEventListener('pointerdown', onPointerDown);
|
|
438
|
+
window.removeEventListener('keydown', onKeyDown);
|
|
439
|
+
window.removeEventListener('keyup', onKeyUp);
|
|
440
|
+
};
|
|
441
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build mode — server-first vs browser-first.
|
|
3
|
+
*
|
|
4
|
+
* Two first-class build profiles, distinguished by Vite's `import.meta.env.MODE`
|
|
5
|
+
* (reliably injected in BOTH dev and build — unlike custom `define`s, which are
|
|
6
|
+
* not statically replaced by the dev middleware server):
|
|
7
|
+
* - 'browser' — the static build (`npm run build:web` → `--mode web`, e.g.
|
|
8
|
+
* GitHub Pages). No server; storage defaults to the in-browser OPFS VFS,
|
|
9
|
+
* with a seeded Starter and an opt-in "Open folder" (File System Access).
|
|
10
|
+
* - 'server' — everything else: the installed app + `npm run dev`. Assumes a
|
|
11
|
+
* Node backend; storage defaults to `HttpStorage` (edits the on-disk project).
|
|
12
|
+
*
|
|
13
|
+
* Both share one codebase; only the default backend + first-run behavior differ.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
declare const __VGAI_PROJECT_PATH__: string;
|
|
17
|
+
|
|
18
|
+
export type EditorMode = 'server' | 'browser';
|
|
19
|
+
|
|
20
|
+
/** The active build mode. `--mode web` → browser-first; dev/prod → server-first. */
|
|
21
|
+
export function getEditorMode(): EditorMode {
|
|
22
|
+
return import.meta.env.MODE === 'web' ? 'browser' : 'server';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Absolute project root injected by the dev/prod server (empty in browser mode). */
|
|
26
|
+
export function getProjectDefinePath(): string {
|
|
27
|
+
return typeof __VGAI_PROJECT_PATH__ !== 'undefined' ? __VGAI_PROJECT_PATH__ : '';
|
|
28
|
+
}
|