@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,301 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CSV round-trip for TABLE data assets (W6c, docs/DATA-TOOLS-DESIGN.md §7).
|
|
3
|
+
*
|
|
4
|
+
* React-free, sibling discipline to `inspectors/json-schema-fields.ts` — pure
|
|
5
|
+
* functions, unit-tested in Node, no DOM.
|
|
6
|
+
*
|
|
7
|
+
* ARCHITECTURE RULE: the editor never executes the project's Zod schema (the
|
|
8
|
+
* W2 pin — see `json-schema-fields.ts`'s header). Cell coercion here is by
|
|
9
|
+
* the EMITTED schema's `DataFieldDescriptor` KIND only (number/boolean/enum/
|
|
10
|
+
* string/color/group/json) — never a real `.parse()`. Real validation stays
|
|
11
|
+
* at the runtime parse-on-load (`defineData`) and the W5 build
|
|
12
|
+
* plugin/`vgai doctor` gate; this module's job is just "don't corrupt the
|
|
13
|
+
* file", not "be a validator".
|
|
14
|
+
*
|
|
15
|
+
* Import is WHOLE-TABLE REPLACE, deliberately: a CSV file IS the table (every
|
|
16
|
+
* row this import doesn't mention is gone). And it's ATOMIC — any coercion
|
|
17
|
+
* error anywhere in the sheet means NOTHING is written; the caller must not
|
|
18
|
+
* call `writeFile` when `errors.length > 0`.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { DataFieldDescriptor } from './inspectors/json-schema-fields';
|
|
22
|
+
import { defaultValueForDescriptor } from './inspectors/json-schema-fields';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Export: table -> CSV text
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
/** RFC-4180 field escaping: quote when the cell contains a comma, quote, or
|
|
29
|
+
* newline; double any embedded quote. */
|
|
30
|
+
function escapeCsvField(value: string): string {
|
|
31
|
+
if (/[",\r\n]/.test(value)) {
|
|
32
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
33
|
+
}
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** One CSV cell for row-schema field `f`, from row value `v` (the row's
|
|
38
|
+
* current value under key `f.name`, or `undefined` if absent). */
|
|
39
|
+
function cellToCsv(f: DataFieldDescriptor, v: unknown): string {
|
|
40
|
+
if (v === undefined) return '';
|
|
41
|
+
// Nested/unrenderable kinds — or any non-primitive runtime value, in case
|
|
42
|
+
// data and schema disagree — round-trip as a JSON-encoded cell.
|
|
43
|
+
if (f.kind === 'group' || f.kind === 'json' || (v !== null && typeof v === 'object')) {
|
|
44
|
+
return escapeCsvField(JSON.stringify(v));
|
|
45
|
+
}
|
|
46
|
+
return escapeCsvField(String(v));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Serialize a table asset's data (minus `"$schema"`) to CSV text: header
|
|
51
|
+
* `key,<rowFields in schema order>`, one line per data key in FILE order,
|
|
52
|
+
* `\r\n` line endings (spreadsheet-friendly), trailing newline.
|
|
53
|
+
*/
|
|
54
|
+
export function tableToCsv(
|
|
55
|
+
data: Record<string, unknown>,
|
|
56
|
+
rowFields: DataFieldDescriptor[],
|
|
57
|
+
): string {
|
|
58
|
+
const header = ['key', ...rowFields.map((f) => f.name)].map(escapeCsvField).join(',');
|
|
59
|
+
const lines = [header];
|
|
60
|
+
for (const [key, row] of Object.entries(data)) {
|
|
61
|
+
if (key === '$schema') continue;
|
|
62
|
+
const rowObj = row && typeof row === 'object' ? (row as Record<string, unknown>) : {};
|
|
63
|
+
const cells = [escapeCsvField(key), ...rowFields.map((f) => cellToCsv(f, rowObj[f.name]))];
|
|
64
|
+
lines.push(cells.join(','));
|
|
65
|
+
}
|
|
66
|
+
return `${lines.join('\r\n')}\r\n`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Import: CSV text -> string[][], then -> table
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Advance one step while scanning INSIDE a quoted field. Returns the next
|
|
75
|
+
* scan position, the character (if any) to append to the current field, and
|
|
76
|
+
* whether this step closed the quote — pulled out of `parseCsv`'s main loop
|
|
77
|
+
* purely to keep that loop's branching flat (this is the one place with
|
|
78
|
+
* nested conditionals: a `""` escape vs. a closing `"`).
|
|
79
|
+
*/
|
|
80
|
+
function stepQuoted(
|
|
81
|
+
text: string,
|
|
82
|
+
i: number,
|
|
83
|
+
): { next: number; char: string | null; closed: boolean } {
|
|
84
|
+
const ch = text[i]!;
|
|
85
|
+
if (ch !== '"') return { next: i + 1, char: ch, closed: false };
|
|
86
|
+
if (text[i + 1] === '"') return { next: i + 2, char: '"', closed: false }; // `""` escape
|
|
87
|
+
return { next: i + 1, char: null, closed: true }; // closing quote
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Minimal RFC-4180 parser: quoted fields, `""` escapes, `\r\n`/`\n` line
|
|
92
|
+
* endings, no external deps. Returns rows of raw string cells (no
|
|
93
|
+
* type coercion — that's `csvToTable`'s job).
|
|
94
|
+
*/
|
|
95
|
+
export function parseCsv(text: string): string[][] {
|
|
96
|
+
const rows: string[][] = [];
|
|
97
|
+
let row: string[] = [];
|
|
98
|
+
let field = '';
|
|
99
|
+
let inQuotes = false;
|
|
100
|
+
let i = 0;
|
|
101
|
+
const n = text.length;
|
|
102
|
+
|
|
103
|
+
const endField = () => {
|
|
104
|
+
row.push(field);
|
|
105
|
+
field = '';
|
|
106
|
+
};
|
|
107
|
+
const endRow = () => {
|
|
108
|
+
endField();
|
|
109
|
+
rows.push(row);
|
|
110
|
+
row = [];
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
while (i < n) {
|
|
114
|
+
if (inQuotes) {
|
|
115
|
+
const step = stepQuoted(text, i);
|
|
116
|
+
if (step.char !== null) field += step.char;
|
|
117
|
+
if (step.closed) inQuotes = false;
|
|
118
|
+
i = step.next;
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const ch = text[i]!;
|
|
122
|
+
if (ch === '"') {
|
|
123
|
+
inQuotes = true;
|
|
124
|
+
i += 1;
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
if (ch === ',') {
|
|
128
|
+
endField();
|
|
129
|
+
i += 1;
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (ch === '\r' || ch === '\n') {
|
|
133
|
+
// Treat \r\n and bare \r as one line ending.
|
|
134
|
+
if (ch === '\r' && text[i + 1] === '\n') i += 1;
|
|
135
|
+
endRow();
|
|
136
|
+
i += 1;
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
field += ch;
|
|
140
|
+
i += 1;
|
|
141
|
+
}
|
|
142
|
+
// Trailing field/row (no terminating newline, or the file has one and this
|
|
143
|
+
// is an empty tail — skip a wholly-empty trailing row from a final \r\n).
|
|
144
|
+
if (field !== '' || row.length > 0) {
|
|
145
|
+
endRow();
|
|
146
|
+
}
|
|
147
|
+
return rows;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Coerce one raw CSV cell string to field `f`'s runtime value. Returns
|
|
151
|
+
* `{ error }` instead of throwing — the caller aggregates errors across the
|
|
152
|
+
* whole sheet and refuses the import atomically if any exist. */
|
|
153
|
+
function coerceCell(
|
|
154
|
+
raw: string,
|
|
155
|
+
f: DataFieldDescriptor,
|
|
156
|
+
rowNum: number,
|
|
157
|
+
): { value: unknown } | { error: string } {
|
|
158
|
+
if (raw === '') {
|
|
159
|
+
// This is a PRESENT cell that's blank — `coerceRow` handles an entirely
|
|
160
|
+
// MISSING column itself and never reaches here with one (#5). A
|
|
161
|
+
// `kind: 'string'` field's blank cell IS the value `""`, never "absent",
|
|
162
|
+
// so it must not be swapped for the schema's default: an export→reimport
|
|
163
|
+
// of a legitimately-empty string field has to be a no-op diff, not a
|
|
164
|
+
// mutation. Every other kind has no meaningful "empty" runtime value, so
|
|
165
|
+
// those keep falling back to the field default.
|
|
166
|
+
return { value: f.kind === 'string' ? '' : defaultValueForDescriptor(f) };
|
|
167
|
+
}
|
|
168
|
+
switch (f.kind) {
|
|
169
|
+
case 'number': {
|
|
170
|
+
const n = Number(raw);
|
|
171
|
+
if (!Number.isFinite(n)) {
|
|
172
|
+
return { error: `row ${rowNum}, col ${f.name}: "${raw}" is not a number` };
|
|
173
|
+
}
|
|
174
|
+
return { value: n };
|
|
175
|
+
}
|
|
176
|
+
case 'boolean': {
|
|
177
|
+
const lower = raw.trim().toLowerCase();
|
|
178
|
+
if (lower === 'true' || lower === '1') return { value: true };
|
|
179
|
+
if (lower === 'false' || lower === '0') return { value: false };
|
|
180
|
+
return { error: `row ${rowNum}, col ${f.name}: "${raw}" is not a boolean (true/false/1/0)` };
|
|
181
|
+
}
|
|
182
|
+
case 'enum': {
|
|
183
|
+
const options = f.options ?? [];
|
|
184
|
+
if (!options.includes(raw)) {
|
|
185
|
+
return {
|
|
186
|
+
error: `row ${rowNum}, col ${f.name}: "${raw}" is not one of [${options.join(', ')}]`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
return { value: raw };
|
|
190
|
+
}
|
|
191
|
+
case 'string':
|
|
192
|
+
case 'color':
|
|
193
|
+
return { value: raw };
|
|
194
|
+
case 'group':
|
|
195
|
+
case 'json':
|
|
196
|
+
try {
|
|
197
|
+
return { value: JSON.parse(raw) };
|
|
198
|
+
} catch {
|
|
199
|
+
return { error: `row ${rowNum}, col ${f.name}: "${raw}" is not valid JSON` };
|
|
200
|
+
}
|
|
201
|
+
default:
|
|
202
|
+
return { value: raw };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface CsvImportResult {
|
|
207
|
+
/** Present (and non-empty `rows`) only when `errors` is empty. */
|
|
208
|
+
rows: Record<string, Record<string, unknown>>;
|
|
209
|
+
/** Any coercion/structural problem, one entry per issue. Non-empty means
|
|
210
|
+
* the caller must NOT write — the import is atomic, all-or-nothing. */
|
|
211
|
+
errors: string[];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Coerce ONE data row (`line`, the raw CSV cells including the leading key)
|
|
216
|
+
* into its key + typed row object, pushing any problem onto `errors` (never
|
|
217
|
+
* throwing). Returns `null` when the row has no usable key (empty or the
|
|
218
|
+
* reserved `"$schema"`) — pulled out of `csvToTable`'s main loop so that loop
|
|
219
|
+
* only has to handle the duplicate-key check across rows.
|
|
220
|
+
*/
|
|
221
|
+
function coerceRow(
|
|
222
|
+
line: string[],
|
|
223
|
+
rowNum: number,
|
|
224
|
+
rowFields: DataFieldDescriptor[],
|
|
225
|
+
columns: string[],
|
|
226
|
+
errors: string[],
|
|
227
|
+
): { key: string; value: Record<string, unknown> } | null {
|
|
228
|
+
const key = (line[0] ?? '').trim();
|
|
229
|
+
if (!key) {
|
|
230
|
+
errors.push(`row ${rowNum}: key cannot be empty.`);
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
if (key === '$schema') {
|
|
234
|
+
errors.push(`row ${rowNum}: "$schema" is reserved and cannot be a row key.`);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const rowValue: Record<string, unknown> = {};
|
|
239
|
+
for (const f of rowFields) {
|
|
240
|
+
// +1: `columns` excludes the leading "key" column that `line[0]` holds.
|
|
241
|
+
const colIdx = columns.indexOf(f.name);
|
|
242
|
+
if (colIdx < 0) {
|
|
243
|
+
// Column missing from the sheet ENTIRELY (distinct from a present but
|
|
244
|
+
// blank cell, #5) — there is no cell content to preserve, so every
|
|
245
|
+
// kind (string included) falls to the schema default here.
|
|
246
|
+
rowValue[f.name] = defaultValueForDescriptor(f);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const raw = line[colIdx + 1] ?? '';
|
|
250
|
+
const result = coerceCell(raw, f, rowNum);
|
|
251
|
+
if ('error' in result) {
|
|
252
|
+
errors.push(result.error);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
rowValue[f.name] = result.value;
|
|
256
|
+
}
|
|
257
|
+
return { key, value: rowValue };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Parse CSV text into table rows for row-schema `rowFields`. Never throws;
|
|
262
|
+
* collects every problem into `errors` and returns `rows: {}` alongside them
|
|
263
|
+
* (callers must check `errors.length` before using `rows` — a non-empty
|
|
264
|
+
* `errors` means the sheet is not a valid whole-table replacement).
|
|
265
|
+
*/
|
|
266
|
+
export function csvToTable(text: string, rowFields: DataFieldDescriptor[]): CsvImportResult {
|
|
267
|
+
const errors: string[] = [];
|
|
268
|
+
const grid = parseCsv(text).filter((r) => !(r.length === 1 && r[0] === ''));
|
|
269
|
+
if (grid.length === 0) {
|
|
270
|
+
return { rows: {}, errors: ['CSV is empty — expected a header row starting with "key".'] };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const header = grid[0]!;
|
|
274
|
+
if (header[0] !== 'key') {
|
|
275
|
+
return { rows: {}, errors: [`First column must be "key" (found "${header[0] ?? ''}").`] };
|
|
276
|
+
}
|
|
277
|
+
const columns = header.slice(1);
|
|
278
|
+
const validNames = new Set(rowFields.map((f) => f.name));
|
|
279
|
+
for (const col of columns) {
|
|
280
|
+
if (!validNames.has(col)) {
|
|
281
|
+
errors.push(
|
|
282
|
+
`Unknown column "${col}" — valid columns are [${rowFields.map((f) => f.name).join(', ')}].`,
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const rows: Record<string, Record<string, unknown>> = {};
|
|
288
|
+
for (let r = 1; r < grid.length; r++) {
|
|
289
|
+
const rowNum = r + 1; // 1-based, header is row 1 (spreadsheet-familiar numbering)
|
|
290
|
+
const coerced = coerceRow(grid[r]!, rowNum, rowFields, columns, errors);
|
|
291
|
+
if (!coerced) continue;
|
|
292
|
+
if (coerced.key in rows) {
|
|
293
|
+
errors.push(`row ${rowNum}: duplicate key "${coerced.key}".`);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
rows[coerced.key] = coerced.value;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (errors.length > 0) return { rows: {}, errors };
|
|
300
|
+
return { rows, errors: [] };
|
|
301
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure decision for the inspector's entity-name field commit (T1.12b): given
|
|
3
|
+
* the in-progress draft and the last-committed name, returns the trimmed name
|
|
4
|
+
* to commit, or `null` if nothing should be committed (empty/whitespace-only
|
|
5
|
+
* draft, or unchanged). Kept import-free (no React/Three/editor-store deps)
|
|
6
|
+
* so it can be unit-tested without pulling in the HeaderSection module graph.
|
|
7
|
+
*
|
|
8
|
+
* Used by `EntityNameField` in
|
|
9
|
+
* `authoring/first-party-inspector/HeaderSection.tsx`, whose `commit()` handler
|
|
10
|
+
* (wired to `onBlur`/Enter only — never `onChange`) calls this before pushing
|
|
11
|
+
* to the store, so the store only sees one call per edit gesture instead of
|
|
12
|
+
* one per keystroke.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveNameCommit(draft: string, name: string): string | null {
|
|
15
|
+
const trimmed = draft.trim();
|
|
16
|
+
if (trimmed && trimmed !== name) return trimmed;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure grouping logic behind the generic adapter-driven inspector's
|
|
3
|
+
* `PropertyDescriptor.group?` rendering (T3.4 slice 2, docs/ADAPTER-
|
|
4
|
+
* AUTHORING-DESIGN.md §1.E). Extracted out of `Inspector.tsx`'s (formerly
|
|
5
|
+
* `IngestPanels.tsx`'s `GenericInspector`) inspector shell component (a react
|
|
6
|
+
* component with no jsdom/testing-library in this repo — `vitest.config.ts`
|
|
7
|
+
* runs `environment: 'node'`) so
|
|
8
|
+
* this can be unit-tested directly as data-in/data-out, matching the
|
|
9
|
+
* repo's existing "test the pure logic headlessly" convention.
|
|
10
|
+
*
|
|
11
|
+
* Ungrouped fields keep their original relative order (byte-stable with the
|
|
12
|
+
* pre-`group` flat-list rendering); named groups are returned in FIRST-SEEN
|
|
13
|
+
* order, each carrying a slugified, testid-safe `groupId` (lowercase,
|
|
14
|
+
* non-alphanumeric runs collapsed to a single `-`, no leading/trailing `-`).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type { PropertyDescriptor } from '@engine/adapter';
|
|
18
|
+
|
|
19
|
+
export interface PropertyGroup {
|
|
20
|
+
name: string;
|
|
21
|
+
/** Slugified group name — the suffix of the rendered section's
|
|
22
|
+
* `data-testid="ingest-group-<groupId>"`. */
|
|
23
|
+
groupId: string;
|
|
24
|
+
properties: PropertyDescriptor[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface GroupedProperties {
|
|
28
|
+
ungrouped: PropertyDescriptor[];
|
|
29
|
+
groups: PropertyGroup[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Lowercase, non-alphanumeric runs -> single `-`, no leading/trailing `-`. */
|
|
33
|
+
export function slugifyGroupName(name: string): string {
|
|
34
|
+
return name
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
37
|
+
.replace(/^-+|-+$/g, '');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function groupProperties(props: readonly PropertyDescriptor[]): GroupedProperties {
|
|
41
|
+
const ungrouped: PropertyDescriptor[] = [];
|
|
42
|
+
const order: string[] = [];
|
|
43
|
+
const byName = new Map<string, PropertyDescriptor[]>();
|
|
44
|
+
|
|
45
|
+
for (const p of props) {
|
|
46
|
+
if (p.group) {
|
|
47
|
+
if (!byName.has(p.group)) {
|
|
48
|
+
byName.set(p.group, []);
|
|
49
|
+
order.push(p.group);
|
|
50
|
+
}
|
|
51
|
+
byName.get(p.group)!.push(p);
|
|
52
|
+
} else {
|
|
53
|
+
ungrouped.push(p);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
ungrouped,
|
|
59
|
+
groups: order.map((name) => ({
|
|
60
|
+
name,
|
|
61
|
+
groupId: slugifyGroupName(name),
|
|
62
|
+
properties: byName.get(name)!,
|
|
63
|
+
})),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AlignmentGrid — a 3×3 flex `justify-content`×`align-items` picker (spec 27
|
|
3
|
+
* §5 C1). Ported from `visual-edit/inspector.tsx`'s `AlignmentGrid`
|
|
4
|
+
* (:304-351). `AnchorPresets` below REUSES the existing `ui-editor/
|
|
5
|
+
* inspector.tsx` `ANCHOR_PRESETS` table's VALUES (now exported there for
|
|
6
|
+
* this reuse) as a data-only copy — not a live import — because
|
|
7
|
+
* `ui-editor/inspector.tsx` pulls in `UIEditStore`/`react-store`/
|
|
8
|
+
* `node-ops`/the inspector-section-registry (a whole editing-mode runtime
|
|
9
|
+
* graph this pure, adapter-free widget kit must not drag in, Rule zero's
|
|
10
|
+
* "talk ONLY to the AuthoringAdapter contract" spirit extended to "don't
|
|
11
|
+
* import a store-coupled module just for one constant"). Keep this table in
|
|
12
|
+
* sync with `ui-editor/inspector.tsx`'s `ANCHOR_PRESETS` if either changes —
|
|
13
|
+
* C2's wiring step is the natural point to collapse them into one shared,
|
|
14
|
+
* dependency-free `anchor-presets.ts` module both sides import.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { type ChangeHandlers, THEME } from './shared';
|
|
18
|
+
|
|
19
|
+
const ANCHOR_PRESETS: Record<
|
|
20
|
+
string,
|
|
21
|
+
{ anchor: AnchorRect['anchor']; offset: AnchorRect['offset'] }
|
|
22
|
+
> = {
|
|
23
|
+
full: {
|
|
24
|
+
anchor: { left: 0, top: 0, right: 1, bottom: 1 },
|
|
25
|
+
offset: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
26
|
+
},
|
|
27
|
+
top: {
|
|
28
|
+
anchor: { left: 0, top: 0, right: 1, bottom: 0 },
|
|
29
|
+
offset: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
30
|
+
},
|
|
31
|
+
bottom: {
|
|
32
|
+
anchor: { left: 0, top: 1, right: 1, bottom: 1 },
|
|
33
|
+
offset: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
34
|
+
},
|
|
35
|
+
left: {
|
|
36
|
+
anchor: { left: 0, top: 0, right: 0, bottom: 1 },
|
|
37
|
+
offset: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
38
|
+
},
|
|
39
|
+
right: {
|
|
40
|
+
anchor: { left: 1, top: 0, right: 1, bottom: 1 },
|
|
41
|
+
offset: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
42
|
+
},
|
|
43
|
+
center: {
|
|
44
|
+
anchor: { left: 0.5, top: 0.5, right: 0.5, bottom: 0.5 },
|
|
45
|
+
offset: { left: -60, top: -20, right: 60, bottom: 20 },
|
|
46
|
+
},
|
|
47
|
+
'top-left': {
|
|
48
|
+
anchor: { left: 0, top: 0, right: 0, bottom: 0 },
|
|
49
|
+
offset: { left: 0, top: 0, right: 120, bottom: 40 },
|
|
50
|
+
},
|
|
51
|
+
'top-right': {
|
|
52
|
+
anchor: { left: 1, top: 0, right: 1, bottom: 0 },
|
|
53
|
+
offset: { left: -120, top: 0, right: 0, bottom: 40 },
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const JUSTIFY = ['flex-start', 'center', 'flex-end'] as const;
|
|
58
|
+
const ALIGN = ['flex-start', 'center', 'flex-end'] as const;
|
|
59
|
+
|
|
60
|
+
export interface AlignmentValue {
|
|
61
|
+
justify: (typeof JUSTIFY)[number];
|
|
62
|
+
align: (typeof ALIGN)[number];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** One 3×3 dot — split out of `AlignmentGrid`'s double `.map` purely to keep
|
|
66
|
+
* each function under biome's cognitive-complexity ceiling; same markup. */
|
|
67
|
+
function AlignmentCell({
|
|
68
|
+
row,
|
|
69
|
+
col,
|
|
70
|
+
justify,
|
|
71
|
+
align,
|
|
72
|
+
isActive,
|
|
73
|
+
disabled,
|
|
74
|
+
onSelect,
|
|
75
|
+
}: {
|
|
76
|
+
row: number;
|
|
77
|
+
col: number;
|
|
78
|
+
justify: AlignmentValue['justify'];
|
|
79
|
+
align: AlignmentValue['align'];
|
|
80
|
+
isActive: boolean;
|
|
81
|
+
disabled?: boolean | undefined;
|
|
82
|
+
onSelect: (justify: AlignmentValue['justify'], align: AlignmentValue['align']) => void;
|
|
83
|
+
}): React.ReactElement {
|
|
84
|
+
return (
|
|
85
|
+
<div
|
|
86
|
+
data-testid={`alignment-grid-${row}-${col}`}
|
|
87
|
+
onClick={() => {
|
|
88
|
+
if (!disabled) onSelect(justify, align);
|
|
89
|
+
}}
|
|
90
|
+
style={{
|
|
91
|
+
width: 16,
|
|
92
|
+
height: 16,
|
|
93
|
+
display: 'flex',
|
|
94
|
+
alignItems: 'center',
|
|
95
|
+
justifyContent: 'center',
|
|
96
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
97
|
+
borderRadius: 2,
|
|
98
|
+
background: isActive ? THEME.accent : 'transparent',
|
|
99
|
+
}}
|
|
100
|
+
>
|
|
101
|
+
<div
|
|
102
|
+
style={{
|
|
103
|
+
width: isActive ? 6 : 4,
|
|
104
|
+
height: isActive ? 6 : 4,
|
|
105
|
+
borderRadius: '50%',
|
|
106
|
+
background: isActive ? '#fff' : THEME.textMuted,
|
|
107
|
+
}}
|
|
108
|
+
/>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function AlignmentGrid({
|
|
114
|
+
value,
|
|
115
|
+
onChange,
|
|
116
|
+
disabled,
|
|
117
|
+
}: ChangeHandlers<AlignmentValue>): React.ReactElement {
|
|
118
|
+
const jIdx = JUSTIFY.indexOf(value.justify);
|
|
119
|
+
const aIdx = ALIGN.indexOf(value.align);
|
|
120
|
+
|
|
121
|
+
return (
|
|
122
|
+
<div
|
|
123
|
+
data-testid="alignment-grid"
|
|
124
|
+
style={{
|
|
125
|
+
display: 'inline-grid',
|
|
126
|
+
gridTemplateColumns: 'repeat(3, 16px)',
|
|
127
|
+
gridTemplateRows: 'repeat(3, 16px)',
|
|
128
|
+
gap: 1,
|
|
129
|
+
background: THEME.inputBg,
|
|
130
|
+
borderRadius: 6,
|
|
131
|
+
padding: 4,
|
|
132
|
+
opacity: disabled ? 0.35 : 1,
|
|
133
|
+
}}
|
|
134
|
+
>
|
|
135
|
+
{ALIGN.map((a, row) =>
|
|
136
|
+
JUSTIFY.map((j, col) => (
|
|
137
|
+
<AlignmentCell
|
|
138
|
+
key={`${row}-${col}`}
|
|
139
|
+
row={row}
|
|
140
|
+
col={col}
|
|
141
|
+
justify={j}
|
|
142
|
+
align={a}
|
|
143
|
+
isActive={row === aIdx && col === jIdx}
|
|
144
|
+
disabled={disabled}
|
|
145
|
+
onSelect={(justify, align) => onChange({ justify, align })}
|
|
146
|
+
/>
|
|
147
|
+
)),
|
|
148
|
+
)}
|
|
149
|
+
</div>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface AnchorRect {
|
|
154
|
+
anchor: { left: number; top: number; right: number; bottom: number };
|
|
155
|
+
offset: { left: number; top: number; right: number; bottom: number };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export interface AnchorPresetsProps {
|
|
159
|
+
onSelect: (name: string, preset: AnchorRect) => void;
|
|
160
|
+
disabled?: boolean | undefined;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function AnchorPresets({ onSelect, disabled }: AnchorPresetsProps): React.ReactElement {
|
|
164
|
+
return (
|
|
165
|
+
<div data-testid="anchor-presets" style={{ display: 'flex', flexWrap: 'wrap', gap: 4 }}>
|
|
166
|
+
{Object.entries(ANCHOR_PRESETS).map(([name, preset]) => (
|
|
167
|
+
<button
|
|
168
|
+
key={name}
|
|
169
|
+
type="button"
|
|
170
|
+
data-testid={`anchor-preset-${name}`}
|
|
171
|
+
disabled={disabled}
|
|
172
|
+
onClick={() => onSelect(name, preset)}
|
|
173
|
+
style={{
|
|
174
|
+
background: 'rgba(255,255,255,0.06)',
|
|
175
|
+
border: `1px solid ${THEME.border}`,
|
|
176
|
+
borderRadius: 3,
|
|
177
|
+
color: THEME.text,
|
|
178
|
+
fontSize: 10,
|
|
179
|
+
padding: '3px 6px',
|
|
180
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
181
|
+
}}
|
|
182
|
+
>
|
|
183
|
+
{name}
|
|
184
|
+
</button>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
);
|
|
188
|
+
}
|