@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,544 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-DOM inspection helpers (Cap 1 of the React visual-edit parity port,
|
|
3
|
+
* `docs/REACT-VISUAL-EDIT-PARITY.md`). These are `../../../visual-edit/preload.ts`'s
|
|
4
|
+
* inspection FUNCTIONS ported as DIRECT-CALL helpers.
|
|
5
|
+
*
|
|
6
|
+
* The no-iframe adaptation (see the parity spec): visual-edit injects `preload.ts` into
|
|
7
|
+
* an iframe and inspects the target app across a `postMessage` bridge. vgai mounts React
|
|
8
|
+
* worlds as DOM layers in the SAME document and `ReactWorldAuthoringAdapter` already holds
|
|
9
|
+
* the live root — so these run against the live element directly, no iframe/bridge.
|
|
10
|
+
*
|
|
11
|
+
* Headless-testable: none of these reach for a DOM global unconditionally. Fiber reads
|
|
12
|
+
* work on any object carrying a `__reactFiber$…` key (a plain fixture satisfies it).
|
|
13
|
+
* `getComputedStyles` takes a resolver (defaulting to `window.getComputedStyle` in a real
|
|
14
|
+
* browser, or the element's inline `.style` under vitest's `node` env — no jsdom).
|
|
15
|
+
* `getMatchedCssRules` takes its stylesheet list (defaulting to `document.styleSheets`).
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const OID_ATTR = 'data-oid';
|
|
19
|
+
|
|
20
|
+
/** The ~70 most-edited CSS properties visual-edit's `getComputedStyles` captures (:73). */
|
|
21
|
+
export const COMPUTED_STYLE_PROPS: readonly string[] = [
|
|
22
|
+
'width',
|
|
23
|
+
'height',
|
|
24
|
+
'minWidth',
|
|
25
|
+
'minHeight',
|
|
26
|
+
'maxWidth',
|
|
27
|
+
'maxHeight',
|
|
28
|
+
'margin',
|
|
29
|
+
'marginTop',
|
|
30
|
+
'marginRight',
|
|
31
|
+
'marginBottom',
|
|
32
|
+
'marginLeft',
|
|
33
|
+
'padding',
|
|
34
|
+
'paddingTop',
|
|
35
|
+
'paddingRight',
|
|
36
|
+
'paddingBottom',
|
|
37
|
+
'paddingLeft',
|
|
38
|
+
'display',
|
|
39
|
+
'flexDirection',
|
|
40
|
+
'flexWrap',
|
|
41
|
+
'justifyContent',
|
|
42
|
+
'alignItems',
|
|
43
|
+
'gap',
|
|
44
|
+
'flexGrow',
|
|
45
|
+
'flexShrink',
|
|
46
|
+
'flexBasis',
|
|
47
|
+
'alignSelf',
|
|
48
|
+
'position',
|
|
49
|
+
'top',
|
|
50
|
+
'right',
|
|
51
|
+
'bottom',
|
|
52
|
+
'left',
|
|
53
|
+
'backgroundColor',
|
|
54
|
+
'backgroundImage',
|
|
55
|
+
'color',
|
|
56
|
+
'fontSize',
|
|
57
|
+
'fontWeight',
|
|
58
|
+
'fontFamily',
|
|
59
|
+
'borderRadius',
|
|
60
|
+
'borderTopLeftRadius',
|
|
61
|
+
'borderTopRightRadius',
|
|
62
|
+
'borderBottomLeftRadius',
|
|
63
|
+
'borderBottomRightRadius',
|
|
64
|
+
'border',
|
|
65
|
+
'borderWidth',
|
|
66
|
+
'borderColor',
|
|
67
|
+
'borderStyle',
|
|
68
|
+
'borderTopWidth',
|
|
69
|
+
'borderRightWidth',
|
|
70
|
+
'borderBottomWidth',
|
|
71
|
+
'borderLeftWidth',
|
|
72
|
+
'borderTopStyle',
|
|
73
|
+
'borderRightStyle',
|
|
74
|
+
'borderBottomStyle',
|
|
75
|
+
'borderLeftStyle',
|
|
76
|
+
'borderTopColor',
|
|
77
|
+
'borderRightColor',
|
|
78
|
+
'borderBottomColor',
|
|
79
|
+
'borderLeftColor',
|
|
80
|
+
'opacity',
|
|
81
|
+
'overflow',
|
|
82
|
+
'zIndex',
|
|
83
|
+
'transform',
|
|
84
|
+
'mixBlendMode',
|
|
85
|
+
'filter',
|
|
86
|
+
'backdropFilter',
|
|
87
|
+
'textShadow',
|
|
88
|
+
'boxShadow',
|
|
89
|
+
'textAlign',
|
|
90
|
+
'lineHeight',
|
|
91
|
+
'letterSpacing',
|
|
92
|
+
'textTransform',
|
|
93
|
+
'textDecoration',
|
|
94
|
+
'whiteSpace',
|
|
95
|
+
'wordWrap',
|
|
96
|
+
'cursor',
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
/** camelCase CSS prop -> kebab-case (`fontSize` -> `font-size`). */
|
|
100
|
+
export function camelToKebab(prop: string): string {
|
|
101
|
+
return prop.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** kebab-case CSS prop -> camelCase (`font-size` -> `fontSize`). */
|
|
105
|
+
export function kebabToCamel(prop: string): string {
|
|
106
|
+
return prop.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** The minimal shape a resolved computed-style value source needs. A real
|
|
110
|
+
* `CSSStyleDeclaration` satisfies this structurally. */
|
|
111
|
+
export interface ComputedStyleLike {
|
|
112
|
+
getPropertyValue(prop: string): string;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Resolves an element to a `getPropertyValue`-capable style source (kebab keys). */
|
|
116
|
+
export type ComputedStyleResolver = (el: unknown) => ComputedStyleLike;
|
|
117
|
+
|
|
118
|
+
/** Read one inline style property off an element's `.style` of either shape (a real
|
|
119
|
+
* `CSSStyleDeclaration` or a plain-object test fixture) — the same read
|
|
120
|
+
* `react-world-authoring-adapter.ts` uses. */
|
|
121
|
+
function inlineStyleProp(style: unknown, prop: string): unknown {
|
|
122
|
+
return style ? (style as Record<string, unknown>)[prop] : undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The default resolver: real `window.getComputedStyle` in a browser (so CLASS-styled
|
|
127
|
+
* and CSS-file-styled properties resolve, fixing the "class-styled props show blank"
|
|
128
|
+
* bug), or — under vitest's `node` env (no `window`) — a shim reading the element's
|
|
129
|
+
* inline `.style` object, so headless fixtures keep working unchanged.
|
|
130
|
+
*/
|
|
131
|
+
export const browserOrInlineResolver: ComputedStyleResolver = (el) => {
|
|
132
|
+
const w = (globalThis as { window?: { getComputedStyle?: (e: unknown) => ComputedStyleLike } })
|
|
133
|
+
.window;
|
|
134
|
+
if (w?.getComputedStyle) return w.getComputedStyle(el);
|
|
135
|
+
const style = (el as { style?: unknown }).style;
|
|
136
|
+
return { getPropertyValue: (k: string) => String(inlineStyleProp(style, kebabToCamel(k)) ?? '') };
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Capture the commonly-edited computed styles of an element as a camelCase-keyed record
|
|
141
|
+
* (visual-edit `getComputedStyles` :73). The resolver is injectable for headless tests;
|
|
142
|
+
* the default resolves against the live browser DOM (or inline style under `node`).
|
|
143
|
+
*/
|
|
144
|
+
export function getComputedStyles(
|
|
145
|
+
el: unknown,
|
|
146
|
+
resolve: ComputedStyleResolver = browserOrInlineResolver,
|
|
147
|
+
): Record<string, string> {
|
|
148
|
+
const cs = resolve(el);
|
|
149
|
+
const result: Record<string, string> = {};
|
|
150
|
+
for (const p of COMPUTED_STYLE_PROPS) result[p] = cs.getPropertyValue(camelToKebab(p));
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Read a single computed style property (camelCase), via the same resolver. */
|
|
155
|
+
export function getComputedStyleValue(
|
|
156
|
+
el: unknown,
|
|
157
|
+
prop: string,
|
|
158
|
+
resolve: ComputedStyleResolver = browserOrInlineResolver,
|
|
159
|
+
): string {
|
|
160
|
+
return resolve(el).getPropertyValue(camelToKebab(prop));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- React fiber inspection (visual-edit preload.ts :110/:134/:158) ---
|
|
164
|
+
|
|
165
|
+
/** A minimal fiber node shape — enough for the reads below (`preload.ts` uses `any`). */
|
|
166
|
+
interface FiberLike {
|
|
167
|
+
return?: FiberLike | null;
|
|
168
|
+
type?: unknown;
|
|
169
|
+
memoizedProps?: Record<string, unknown> | null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Find the React fiber attached to a DOM element (React 18+ `__reactFiber$…`, older
|
|
173
|
+
* `__reactInternalInstance$…`). Works on any object carrying such a key — a plain
|
|
174
|
+
* fixture with `{ __reactFiber$x: {...} }` satisfies it headlessly. */
|
|
175
|
+
export function findFiber(el: unknown): FiberLike | null {
|
|
176
|
+
if (!el || typeof el !== 'object') return null;
|
|
177
|
+
const key = Object.keys(el as object).find(
|
|
178
|
+
(k) => k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$'),
|
|
179
|
+
);
|
|
180
|
+
if (!key) return null;
|
|
181
|
+
return ((el as Record<string, unknown>)[key] as FiberLike) ?? null;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** The parent fiber IF this element is the ROOT element of a function component
|
|
185
|
+
* (`fiber.return` points straight at the component fiber). Else null. */
|
|
186
|
+
function componentParentFiber(el: unknown): FiberLike | null {
|
|
187
|
+
const fiber = findFiber(el);
|
|
188
|
+
if (!fiber?.return) return null;
|
|
189
|
+
const parent = fiber.return;
|
|
190
|
+
return parent.type && typeof parent.type === 'function' ? parent : null;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Nearest component name for an element that is a component's root element
|
|
194
|
+
* (visual-edit `getReactComponentName` :110). */
|
|
195
|
+
export function getReactComponentName(el: unknown): string | null {
|
|
196
|
+
const parent = componentParentFiber(el);
|
|
197
|
+
if (!parent) return null;
|
|
198
|
+
const type = parent.type as { displayName?: string; name?: string };
|
|
199
|
+
return type.displayName || type.name || null;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The call-site `data-oid` prop passed to a component root (`<StatCard data-oid="x"/>`)
|
|
203
|
+
* read off the component fiber's props (visual-edit `getCallSiteOid` :134). */
|
|
204
|
+
export function getCallSiteOid(el: unknown): string | null {
|
|
205
|
+
const parent = componentParentFiber(el);
|
|
206
|
+
if (!parent) return null;
|
|
207
|
+
const v = parent.memoizedProps?.[OID_ATTR];
|
|
208
|
+
return typeof v === 'string' ? v : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Call-site OID for component roots, else the element's own `data-oid`
|
|
212
|
+
* (visual-edit `getEffectiveOid` :148). */
|
|
213
|
+
export function getEffectiveOid(el: { getAttribute?(name: string): string | null }): string | null {
|
|
214
|
+
return getCallSiteOid(el) ?? el.getAttribute?.(OID_ATTR) ?? null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Primitive (string/number/boolean) props of a component root, stringified
|
|
218
|
+
* (visual-edit `readFiberProps` :158). Skips children/key/ref/data-oid/style/className
|
|
219
|
+
* and any non-primitive value. Returns null when there are none. */
|
|
220
|
+
export function readFiberProps(el: unknown): Record<string, string> | null {
|
|
221
|
+
const parent = componentParentFiber(el);
|
|
222
|
+
if (!parent) return null;
|
|
223
|
+
const props = parent.memoizedProps;
|
|
224
|
+
if (!props || typeof props !== 'object') return null;
|
|
225
|
+
|
|
226
|
+
const SKIP = new Set(['children', 'key', 'ref', OID_ATTR, 'style', 'className']);
|
|
227
|
+
const result: Record<string, string> = {};
|
|
228
|
+
for (const [key, value] of Object.entries(props)) {
|
|
229
|
+
if (SKIP.has(key)) continue;
|
|
230
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
231
|
+
result[key] = String(value);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return Object.keys(result).length > 0 ? result : null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** The component's call-site OID + its editable primitive props, or null if the element
|
|
238
|
+
* is not a component root with props (visual-edit `getComponentProps` :463). Cap 4 feeds
|
|
239
|
+
* the Props inspector section from this. */
|
|
240
|
+
export function getComponentProps(
|
|
241
|
+
el: { getAttribute?(name: string): string | null } & unknown,
|
|
242
|
+
): { callSiteOid: string; props: Record<string, string> } | null {
|
|
243
|
+
const callSiteOid = getCallSiteOid(el);
|
|
244
|
+
if (!callSiteOid) return null;
|
|
245
|
+
const props = readFiberProps(el);
|
|
246
|
+
if (!props) return null;
|
|
247
|
+
return { callSiteOid, props };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// --- Matched CSS rules (visual-edit preload.ts getMatchedCssRules :473) ---
|
|
251
|
+
|
|
252
|
+
export interface MatchedCssRule {
|
|
253
|
+
selectorText: string;
|
|
254
|
+
properties: Record<string, string>;
|
|
255
|
+
/** The source file the owning `<style>`/`<link>` traces to (via `data-vite-dev-id`). */
|
|
256
|
+
sourceFile: string;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** The minimal element shape needed to trace matched rules — `matches(selector)`. */
|
|
260
|
+
export interface MatchableElement {
|
|
261
|
+
matches(selector: string): boolean;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/** A structural stand-in for a `CSSStyleRule` (headless-testable). */
|
|
265
|
+
export interface StyleRuleLike {
|
|
266
|
+
selectorText: string;
|
|
267
|
+
style: { length: number; getPropertyValue(prop: string): string; [index: number]: string };
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** A structural stand-in for a `CSSStyleSheet` + its owner node. */
|
|
271
|
+
export interface StyleSheetLike {
|
|
272
|
+
ownerNode?: { id?: string; getAttribute?(name: string): string | null } | null;
|
|
273
|
+
cssRules: ArrayLike<unknown>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** The id of visual-edit's own preview `<style>` tag — skipped when tracing rules. */
|
|
277
|
+
const PREVIEW_STYLE_TAG_ID = '__visual-edit-preview-styles';
|
|
278
|
+
|
|
279
|
+
/** Is this rule a style rule (vs `@media`/`@keyframes`)? Uses `CSSStyleRule` when the DOM
|
|
280
|
+
* provides it, else a structural check so headless fixtures work. */
|
|
281
|
+
function isStyleRule(rule: unknown): rule is StyleRuleLike {
|
|
282
|
+
const CSR = (globalThis as { CSSStyleRule?: unknown }).CSSStyleRule;
|
|
283
|
+
if (typeof CSR === 'function') return rule instanceof (CSR as new () => unknown);
|
|
284
|
+
const r = rule as Partial<StyleRuleLike>;
|
|
285
|
+
return !!r && typeof r.selectorText === 'string' && !!r.style;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Read every declared property off a style rule's declaration block. */
|
|
289
|
+
function readRuleProperties(rule: StyleRuleLike): Record<string, string> {
|
|
290
|
+
const properties: Record<string, string> = {};
|
|
291
|
+
for (let i = 0; i < rule.style.length; i++) {
|
|
292
|
+
const prop = rule.style[i] as string;
|
|
293
|
+
properties[prop] = rule.style.getPropertyValue(prop);
|
|
294
|
+
}
|
|
295
|
+
return properties;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Trace the CSS rules from first-party stylesheets that MATCH an element, keyed back to
|
|
300
|
+
* their source file via the owner node's `data-vite-dev-id` (visual-edit
|
|
301
|
+
* `getMatchedCssRules` :473). Only rules from a stylesheet with a `data-vite-dev-id`
|
|
302
|
+
* (i.e. a real source file Vite is serving) are returned — Cap 2 edits those files.
|
|
303
|
+
* Sheets default to `document.styleSheets`; injectable for headless tests.
|
|
304
|
+
*/
|
|
305
|
+
export function getMatchedCssRules(
|
|
306
|
+
el: MatchableElement,
|
|
307
|
+
sheets: Iterable<StyleSheetLike> = (
|
|
308
|
+
globalThis as { document?: { styleSheets?: Iterable<StyleSheetLike> } }
|
|
309
|
+
).document?.styleSheets ?? [],
|
|
310
|
+
): MatchedCssRule[] {
|
|
311
|
+
const matched: MatchedCssRule[] = [];
|
|
312
|
+
for (const sheet of sheets) {
|
|
313
|
+
const owner = sheet.ownerNode;
|
|
314
|
+
if (owner?.id === PREVIEW_STYLE_TAG_ID) continue;
|
|
315
|
+
const sourceFile = owner?.getAttribute?.('data-vite-dev-id') ?? null;
|
|
316
|
+
if (!sourceFile) continue;
|
|
317
|
+
try {
|
|
318
|
+
for (const rule of Array.from(sheet.cssRules)) {
|
|
319
|
+
if (isStyleRule(rule) && el.matches(rule.selectorText)) {
|
|
320
|
+
matched.push({
|
|
321
|
+
selectorText: rule.selectorText,
|
|
322
|
+
properties: readRuleProperties(rule),
|
|
323
|
+
sourceFile,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
} catch {
|
|
328
|
+
// Cross-origin stylesheet — its `cssRules` throws; skip it (same as visual-edit).
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return matched;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// --- Design tokens (visual-edit preload.ts getDesignTokens :506) — Cap 7 ---
|
|
335
|
+
|
|
336
|
+
export interface DesignToken {
|
|
337
|
+
name: string;
|
|
338
|
+
value: string;
|
|
339
|
+
category: 'color' | 'spacing' | 'other';
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Enumerate the `:root` CSS custom properties (design tokens) resolved on the document
|
|
344
|
+
* root, classified color/spacing/other (visual-edit `getDesignTokens` :506). Captures
|
|
345
|
+
* Tailwind v4 `@theme`/`oklch` vars; skips Tailwind internal `--tw-*`. The computed
|
|
346
|
+
* declaration is injectable for headless tests (defaults to the live `:root`).
|
|
347
|
+
*/
|
|
348
|
+
export function getDesignTokens(
|
|
349
|
+
computed: (ComputedStyleLike & ArrayLike<string>) | undefined = readRootComputed(),
|
|
350
|
+
): DesignToken[] {
|
|
351
|
+
if (!computed) return [];
|
|
352
|
+
const tokens: DesignToken[] = [];
|
|
353
|
+
const seen = new Set<string>();
|
|
354
|
+
for (let i = 0; i < computed.length; i++) {
|
|
355
|
+
const name = computed[i] as string;
|
|
356
|
+
if (!name.startsWith('--') || name.startsWith('--tw-') || seen.has(name)) continue;
|
|
357
|
+
seen.add(name);
|
|
358
|
+
const value = computed.getPropertyValue(name).trim();
|
|
359
|
+
if (!value) continue;
|
|
360
|
+
tokens.push({ name, value, category: classifyToken(name, value) });
|
|
361
|
+
}
|
|
362
|
+
return tokens;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const COLOR_VALUE_RE = /^(#[0-9a-f]{3,8}|rgba?\(|hsla?\(|oklch\()/i;
|
|
366
|
+
const SPACING_NAME_RE = /spacing|gap|margin|padding|radius|size|width|height/i;
|
|
367
|
+
|
|
368
|
+
/** Classify a design token by its name + value (visual-edit `getDesignTokens` :524). */
|
|
369
|
+
function classifyToken(name: string, value: string): DesignToken['category'] {
|
|
370
|
+
if (/color/i.test(name) || COLOR_VALUE_RE.test(value)) return 'color';
|
|
371
|
+
if (SPACING_NAME_RE.test(name) && /^\d/.test(value)) return 'spacing';
|
|
372
|
+
return 'other';
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// --- Cap 8: UX-polish helpers (visual-edit preload.ts getElementsInRect :635 /
|
|
376
|
+
// getSnapTargets :592 / getEmptyContainers :563 / getColorAtPoint :667 /
|
|
377
|
+
// getAvailableFonts :677). Ported as pure/injectable direct-call helpers. ---
|
|
378
|
+
|
|
379
|
+
export interface Rect {
|
|
380
|
+
x: number;
|
|
381
|
+
y: number;
|
|
382
|
+
width: number;
|
|
383
|
+
height: number;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** Axis-aligned rectangle intersection test (edges from x/y/width/height). */
|
|
387
|
+
export function rectsIntersect(a: Rect, b: Rect): boolean {
|
|
388
|
+
if (a.x + a.width < b.x || a.x > b.x + b.width) return false;
|
|
389
|
+
if (a.y + a.height < b.y || a.y > b.y + b.height) return false;
|
|
390
|
+
return true;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/** A marquee candidate: an OID-tagged element's rect + the oids of its OID-tagged
|
|
394
|
+
* descendants (for the deepest-element filter). */
|
|
395
|
+
export interface MarqueeCandidate {
|
|
396
|
+
oid: string;
|
|
397
|
+
rect: Rect;
|
|
398
|
+
descendantOids: string[];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Marquee-select: the OID elements intersecting `rect`, filtered to the DEEPEST matches —
|
|
403
|
+
* an element is dropped if any of its OID descendants also matched (visual-edit
|
|
404
|
+
* `getElementsInRect` :635). Zero-area candidates are ignored. Pure; the browser wrapper
|
|
405
|
+
* builds candidates from `document.querySelectorAll('[data-oid]')`.
|
|
406
|
+
*/
|
|
407
|
+
export function getElementsInRect(
|
|
408
|
+
rect: Rect,
|
|
409
|
+
candidates: readonly MarqueeCandidate[],
|
|
410
|
+
): { oid: string; rect: Rect }[] {
|
|
411
|
+
const hit = candidates.filter(
|
|
412
|
+
(c) => (c.rect.width > 0 || c.rect.height > 0) && rectsIntersect(c.rect, rect),
|
|
413
|
+
);
|
|
414
|
+
const hitOids = new Set(hit.map((c) => c.oid));
|
|
415
|
+
return hit
|
|
416
|
+
.filter((c) => !c.descendantOids.some((d) => d !== c.oid && hitOids.has(d)))
|
|
417
|
+
.map((c) => ({ oid: c.oid, rect: c.rect }));
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/** Per-side padding of a container (px). */
|
|
421
|
+
export interface Padding {
|
|
422
|
+
top: number;
|
|
423
|
+
right: number;
|
|
424
|
+
bottom: number;
|
|
425
|
+
left: number;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Snap targets for dragging a child: the parent's PADDING box + its other children's rects
|
|
430
|
+
* (visual-edit `getSnapTargets` :592). Pure geometry.
|
|
431
|
+
*/
|
|
432
|
+
export function computeSnapTargets(
|
|
433
|
+
parentRect: Rect,
|
|
434
|
+
padding: Padding,
|
|
435
|
+
siblingRects: readonly Rect[],
|
|
436
|
+
): { paddingBox: Rect; siblingRects: Rect[] } {
|
|
437
|
+
return {
|
|
438
|
+
paddingBox: {
|
|
439
|
+
x: parentRect.x + padding.left,
|
|
440
|
+
y: parentRect.y + padding.top,
|
|
441
|
+
width: parentRect.width - padding.left - padding.right,
|
|
442
|
+
height: parentRect.height - padding.top - padding.bottom,
|
|
443
|
+
},
|
|
444
|
+
siblingRects: siblingRects.filter((r) => r.width > 0 && r.height > 0),
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** An empty-container candidate (an OID element with no visible children/text). */
|
|
449
|
+
export interface EmptyCandidate {
|
|
450
|
+
oid: string;
|
|
451
|
+
rect: Rect;
|
|
452
|
+
displayName: string;
|
|
453
|
+
hasVisibleChildren: boolean;
|
|
454
|
+
hasText: boolean;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Empty-container hint targets: OID elements with no visible children, no text, and a
|
|
459
|
+
* collapsed dimension (< 8px) — shown as a placeholder so an empty flex/grid container is
|
|
460
|
+
* still selectable (visual-edit `getEmptyContainers` :563). The placeholder rect is grown
|
|
461
|
+
* to a minimum tappable size.
|
|
462
|
+
*/
|
|
463
|
+
export function findEmptyContainers(
|
|
464
|
+
candidates: readonly EmptyCandidate[],
|
|
465
|
+
): { oid: string; rect: Rect; displayName: string }[] {
|
|
466
|
+
const out: { oid: string; rect: Rect; displayName: string }[] = [];
|
|
467
|
+
for (const c of candidates) {
|
|
468
|
+
if (c.hasVisibleChildren || c.hasText) continue;
|
|
469
|
+
if (c.rect.width >= 8 && c.rect.height >= 8) continue;
|
|
470
|
+
out.push({
|
|
471
|
+
oid: c.oid,
|
|
472
|
+
displayName: c.displayName,
|
|
473
|
+
rect: {
|
|
474
|
+
x: c.rect.x,
|
|
475
|
+
y: c.rect.y,
|
|
476
|
+
width: Math.max(c.rect.width, 40),
|
|
477
|
+
height: Math.max(c.rect.height, 24),
|
|
478
|
+
},
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const TRANSPARENT_BG = new Set(['', 'transparent', 'rgba(0, 0, 0, 0)']);
|
|
485
|
+
|
|
486
|
+
/** Is a computed background color effectively transparent? */
|
|
487
|
+
export function isTransparentBackground(bg: string): boolean {
|
|
488
|
+
return TRANSPARENT_BG.has(bg);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Eyedropper: the effective background color at a point — the first non-transparent
|
|
493
|
+
* background walking from the hit element up its ancestor chain, else `#fff` (visual-edit
|
|
494
|
+
* `getColorAtPoint` :667 / `getEffectiveBackground` :63). `bgChain` is hit-element-first.
|
|
495
|
+
*/
|
|
496
|
+
export function effectiveColorFromChain(bgChain: readonly string[]): string {
|
|
497
|
+
for (const bg of bgChain) if (!isTransparentBackground(bg)) return bg;
|
|
498
|
+
return '#fff';
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
const WEB_SAFE_FONTS = [
|
|
502
|
+
'Arial',
|
|
503
|
+
'Helvetica',
|
|
504
|
+
'Times New Roman',
|
|
505
|
+
'Georgia',
|
|
506
|
+
'Verdana',
|
|
507
|
+
'Courier New',
|
|
508
|
+
'Impact',
|
|
509
|
+
'Trebuchet MS',
|
|
510
|
+
'Palatino Linotype',
|
|
511
|
+
'Lucida Console',
|
|
512
|
+
'monospace',
|
|
513
|
+
'sans-serif',
|
|
514
|
+
'serif',
|
|
515
|
+
];
|
|
516
|
+
|
|
517
|
+
/** The available font families: the loaded fonts (quotes stripped) merged with the
|
|
518
|
+
* web-safe fallbacks, de-duped and sorted (visual-edit `getAvailableFonts` :677). */
|
|
519
|
+
export function mergeFonts(loaded: readonly string[] = []): string[] {
|
|
520
|
+
const fonts = new Set<string>();
|
|
521
|
+
for (const f of loaded) fonts.add(f.replace(/^["']|["']$/g, ''));
|
|
522
|
+
for (const f of WEB_SAFE_FONTS) fonts.add(f);
|
|
523
|
+
return [...fonts].sort((a, b) => a.localeCompare(b));
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** Browser wrapper: enumerate `document.fonts` families merged with the web-safe list.
|
|
527
|
+
* Returns just the web-safe list under a headless (no `document.fonts`) environment. */
|
|
528
|
+
export function getAvailableFonts(): string[] {
|
|
529
|
+
const g = globalThis as { document?: { fonts?: Iterable<{ family: string }> } };
|
|
530
|
+
const loaded: string[] = [];
|
|
531
|
+
const docFonts = g.document?.fonts;
|
|
532
|
+
if (docFonts) for (const f of docFonts) loaded.push(f.family);
|
|
533
|
+
return mergeFonts(loaded);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function readRootComputed(): (ComputedStyleLike & ArrayLike<string>) | undefined {
|
|
537
|
+
const g = globalThis as {
|
|
538
|
+
window?: { getComputedStyle?: (e: unknown) => ComputedStyleLike & ArrayLike<string> };
|
|
539
|
+
document?: { documentElement?: unknown };
|
|
540
|
+
};
|
|
541
|
+
const root = g.document?.documentElement;
|
|
542
|
+
if (!root || !g.window?.getComputedStyle) return undefined;
|
|
543
|
+
return g.window.getComputedStyle(root);
|
|
544
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OID instrumentation (C1/C2) — the visual-edit "stamp every JSX element with a
|
|
3
|
+
* stable source id" trick, ported to vgai using the TypeScript compiler API
|
|
4
|
+
* (vgai has `typescript`; it does NOT have @babel/*). Hand-authored React UI
|
|
5
|
+
* component source is transformed at build/dev time to carry a `data-oid` on every
|
|
6
|
+
* JSX element, and an OID -> {file,line,col,component,tag} index is built so the
|
|
7
|
+
* editor can map a clicked element back to its exact source location.
|
|
8
|
+
*
|
|
9
|
+
* Stability (C1): unlike visual-edit's line:col key (which shifts every id below an
|
|
10
|
+
* edit), we key by a CONTENT signature `component:tag:nthOccurrence`, so an edit on
|
|
11
|
+
* an unrelated line keeps existing ids stable. The `data-oid` lives ONLY in the
|
|
12
|
+
* transformed output, never on disk (the writer edits the original source instead).
|
|
13
|
+
*/
|
|
14
|
+
import ts from 'typescript';
|
|
15
|
+
|
|
16
|
+
export interface OidEntry {
|
|
17
|
+
file: string;
|
|
18
|
+
line: number; // 1-based
|
|
19
|
+
col: number; // 0-based (matches ts char)
|
|
20
|
+
component: string | null;
|
|
21
|
+
tag: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Persistent oid store: signature -> oid (kept stable across re-transforms). */
|
|
25
|
+
export class OidStore {
|
|
26
|
+
private bySig = new Map<string, string>();
|
|
27
|
+
private counter = 0;
|
|
28
|
+
readonly index = new Map<string, OidEntry>();
|
|
29
|
+
|
|
30
|
+
getOrCreate(sig: string): string {
|
|
31
|
+
let oid = this.bySig.get(sig);
|
|
32
|
+
if (!oid) {
|
|
33
|
+
this.counter += 1;
|
|
34
|
+
oid = `o${this.counter.toString(36)}${hash(sig).toString(36)}`;
|
|
35
|
+
this.bySig.set(sig, oid);
|
|
36
|
+
}
|
|
37
|
+
return oid;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Clear index entries for a file before re-transforming it. */
|
|
41
|
+
clearFile(file: string): void {
|
|
42
|
+
for (const [oid, e] of this.index) if (e.file === file) this.index.delete(oid);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function hash(s: string): number {
|
|
47
|
+
let h = 0;
|
|
48
|
+
for (let i = 0; i < s.length; i++) h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
|
|
49
|
+
return Math.abs(h);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface Insertion {
|
|
53
|
+
pos: number;
|
|
54
|
+
text: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Transform source: insert `data-oid="…"` on every JSX element and register the
|
|
59
|
+
* index. Returns the transformed code + the entries added for this file.
|
|
60
|
+
*/
|
|
61
|
+
export function transformSource(
|
|
62
|
+
code: string,
|
|
63
|
+
file: string,
|
|
64
|
+
store: OidStore,
|
|
65
|
+
): { code: string; entries: OidEntry[] } {
|
|
66
|
+
const sf = ts.createSourceFile(
|
|
67
|
+
file,
|
|
68
|
+
code,
|
|
69
|
+
ts.ScriptTarget.Latest,
|
|
70
|
+
/*setParentNodes*/ true,
|
|
71
|
+
ts.ScriptKind.TSX,
|
|
72
|
+
);
|
|
73
|
+
store.clearFile(file);
|
|
74
|
+
const insertions: Insertion[] = [];
|
|
75
|
+
const entries: OidEntry[] = [];
|
|
76
|
+
const componentStack: string[] = [];
|
|
77
|
+
// per (component:tag) occurrence counters for stable signatures
|
|
78
|
+
const occ = new Map<string, number>();
|
|
79
|
+
|
|
80
|
+
const pushComponent = (name: string | undefined): boolean => {
|
|
81
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
82
|
+
componentStack.push(name);
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const visit = (node: ts.Node): void => {
|
|
89
|
+
let pushed = false;
|
|
90
|
+
if (ts.isFunctionDeclaration(node) && node.name) pushed = pushComponent(node.name.text);
|
|
91
|
+
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name))
|
|
92
|
+
pushed = pushComponent(node.name.text);
|
|
93
|
+
|
|
94
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
95
|
+
const tag = node.tagName.getText(sf);
|
|
96
|
+
const component = componentStack[componentStack.length - 1] ?? null;
|
|
97
|
+
const sigBase = `${file}:${component}:${tag}`;
|
|
98
|
+
const n = occ.get(sigBase) ?? 0;
|
|
99
|
+
occ.set(sigBase, n + 1);
|
|
100
|
+
const sig = `${sigBase}:${n}`;
|
|
101
|
+
const oid = store.getOrCreate(sig);
|
|
102
|
+
const { line, character } = sf.getLineAndCharacterOfPosition(node.getStart(sf));
|
|
103
|
+
const entry: OidEntry = { file, line: line + 1, col: character, component, tag };
|
|
104
|
+
store.index.set(oid, entry);
|
|
105
|
+
entries.push(entry);
|
|
106
|
+
// insert ` data-oid="oid"` right after the tag name
|
|
107
|
+
insertions.push({ pos: node.tagName.getEnd(), text: ` data-oid="${oid}"` });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
ts.forEachChild(node, visit);
|
|
111
|
+
if (pushed) componentStack.pop();
|
|
112
|
+
};
|
|
113
|
+
visit(sf);
|
|
114
|
+
|
|
115
|
+
// apply insertions back-to-front so offsets stay valid
|
|
116
|
+
insertions.sort((a, b) => b.pos - a.pos);
|
|
117
|
+
let out = code;
|
|
118
|
+
for (const ins of insertions) out = out.slice(0, ins.pos) + ins.text + out.slice(ins.pos);
|
|
119
|
+
return { code: out, entries };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 1-based line + 0-based col -> absolute char offset in `code`. */
|
|
123
|
+
export function lineColToOffset(code: string, line: number, col: number): number {
|
|
124
|
+
let offset = 0;
|
|
125
|
+
let cur = 1;
|
|
126
|
+
while (cur < line) {
|
|
127
|
+
const nl = code.indexOf('\n', offset);
|
|
128
|
+
if (nl === -1) return code.length;
|
|
129
|
+
offset = nl + 1;
|
|
130
|
+
cur += 1;
|
|
131
|
+
}
|
|
132
|
+
return offset + col;
|
|
133
|
+
}
|