@vgai/editor 0.2.0 → 0.4.0-canary.20260715.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/README.md +24 -0
- package/dist/assets/axe-C7YBtOuM.js +30 -0
- package/dist/assets/browserAll-DDtGGE48.js +1 -0
- package/dist/assets/create-runtime-DbTlBlKG.js +1 -0
- package/dist/assets/{game-BI111o5f.js → game-BE0r7DMI.js} +3 -3
- package/dist/assets/game-BdTHR2_X.js +1 -0
- package/dist/assets/{game-pt_1lQLv.js → game-C3iQGSqo.js} +1 -1
- package/dist/assets/game-CcHfYxjP.js +1 -0
- package/dist/assets/game-Cqzj_lpD.js +1 -0
- package/dist/assets/game-DSZdeCHu.js +1 -0
- package/dist/assets/game-Dch9sAPc.js +1 -0
- package/dist/assets/{game-xsOho_wg.js → game-SAFn90pA.js} +3 -3
- package/dist/assets/game-loop-DMIgSMV-.js +1 -0
- package/dist/assets/{game-source-CvidN7AB.js → game-source-Dk83w26t.js} +1 -1
- package/dist/assets/{index-Bp5cecD6.js → index-4idIsHHJ.js} +2 -2
- package/dist/assets/{index-DL7RcQ0n.js → index-C-LhOTBN.js} +1 -1
- package/dist/assets/{index-Cs8xwv3e.js → index-CDkxceqE.js} +1 -1
- package/dist/assets/index-CQEfi217.js +15412 -0
- package/dist/assets/index-Dvi-V0NE.css +1 -0
- package/dist/assets/ingest-siblings-s0P5I1Ub.js +1 -0
- package/dist/assets/mount-manifest-DXMOlF79.js +1 -0
- package/dist/assets/react-18-C9eHn8x1.js +1 -0
- package/dist/assets/webworkerAll-Fn0e-ouF.js +1 -0
- package/dist/assets/world-documents-GM0a9Kv7.js +1 -0
- package/dist/index.html +15 -13
- package/dist/ingest/racing-game/vgai.game.json +5 -4
- package/dist/ingest/simcity/vgai.game.json +5 -4
- package/dist/ingest/tanks/vgai.game.json +5 -4
- package/dist/project-scripts.bundle.js +3917 -0
- package/dist/scenes/untitled.vscn.json +2 -0
- package/dist-server/packaged.mjs +7697 -3435
- package/package.json +31 -4
- package/scripts/probe-reporter.ts +349 -0
- package/scripts/run-probe.ts +2260 -0
- package/server/asset-catalog-audit.ts +268 -0
- package/server/asset-catalog-v2.ts +146 -0
- package/server/asset-library-routes.ts +276 -58
- package/server/canonical-path.ts +1 -1
- package/server/dev.ts +148 -44
- package/server/editor-server.ts +888 -61
- package/server/editor-sse.ts +115 -4
- package/server/harness-chat-service.ts +796 -0
- package/server/local-asset-catalog.ts +157 -22
- package/server/packaged.ts +51 -21
- package/server/process-shutdown.ts +136 -0
- package/server/prod.ts +9 -6
- package/server/project-hmr-files.ts +41 -0
- package/server/project-operations.ts +343 -0
- package/server/project-optimize-deps-entries.ts +4 -4
- package/server/project-output-writer.ts +156 -0
- package/server/project-validation.ts +140 -0
- package/server/project-view.ts +8 -50
- package/server/server-utils.ts +75 -10
- package/server/session-registry.ts +12 -0
- package/src/EditorContext.tsx +107 -3
- package/src/action-registry.ts +115 -17
- package/src/adapter-resolver.ts +213 -478
- package/src/asset-events.ts +64 -1
- package/src/asset-preview.ts +906 -0
- package/src/asset-selection.ts +121 -0
- package/src/asset-workflow/asset-capabilities.ts +259 -0
- package/src/asset-workflow/asset-editor-errors.ts +46 -0
- package/src/asset-workflow/asset-import-pipeline.ts +176 -0
- package/src/asset-workflow/asset-library-query.ts +107 -0
- package/src/asset-workflow/asset-types.ts +192 -0
- package/src/asset-workflow/asset-workflow-quality.ts +65 -0
- package/src/asset-workflow/import-contract.ts +113 -0
- package/src/asset-workflow/model-conversion.ts +126 -0
- package/src/asset-workflow/model-inspection.ts +185 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +153 -0
- package/src/asset-workflow/project-asset-operations.ts +119 -0
- package/src/asset-workflow/thumbnail-system.ts +172 -0
- package/src/authoring/active-systems.ts +15 -0
- package/src/authoring/boundary-authoring-adapter.ts +16 -5
- package/src/authoring/composite-authoring-adapter.ts +305 -124
- package/src/authoring/design-time-layers.ts +300 -117
- package/src/authoring/edit-mode-authoring.ts +278 -293
- package/src/authoring/embed-only-adapter.ts +4 -2
- package/src/authoring/first-party-hierarchy-menu.ts +7 -2
- package/src/authoring/first-party-inspector/AddSectionMenu.tsx +5 -12
- package/src/authoring/first-party-inspector/EnvironmentSection.tsx +53 -126
- package/src/authoring/first-party-inspector/HeaderSection.tsx +54 -65
- package/src/authoring/first-party-inspector/is-first-party-selection.ts +30 -11
- package/src/authoring/first-party-inspector/register.tsx +13 -2
- package/src/authoring/ingest-authoring-adapter.ts +142 -45
- package/src/authoring/mount-failure-report.ts +5 -5
- package/src/authoring/provenance.ts +41 -0
- package/src/authoring/react-dom-authoring-adapter.ts +92 -92
- package/src/authoring/react-inspector-section.tsx +489 -51
- package/src/authoring/react-world-authoring-adapter.ts +541 -306
- package/src/authoring/story-overlay.ts +1 -1
- package/src/authoring/story-types.ts +11 -0
- package/src/authoring/vgai-scene-authoring-adapter.ts +54 -25
- package/src/authoring/viewport-tool-context.ts +78 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +186 -0
- package/src/authoring/world-pan-state.ts +33 -17
- package/src/authoring/world-session-state.ts +1 -1
- package/src/authoring/world2d-authoring-adapter.ts +109 -52
- package/src/availability-tick.ts +56 -0
- package/src/browser-play-entry.ts +5 -5
- package/src/browser-project-scripts.ts +1 -0
- package/src/build-identity.ts +15 -0
- package/src/command-listener.ts +893 -19
- package/src/components/AlignToolbar.tsx +74 -63
- package/src/components/AppRoot.tsx +193 -26
- package/src/components/ApplicationMenus.tsx +272 -0
- package/src/components/AssetBrowser.tsx +1450 -293
- package/src/components/AssetEditorShell.tsx +141 -0
- package/src/components/AssetImportDetails.tsx +218 -0
- package/src/components/AssetLibraryPanel.tsx +14 -0
- package/src/components/CameraInfo.tsx +11 -20
- package/src/components/CenterDocuments.tsx +585 -0
- package/src/components/CommandPalette.tsx +75 -30
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +57 -112
- package/src/components/DebugConsolePanel.tsx +461 -0
- package/src/components/DebuggerPanel.tsx +165 -0
- package/src/components/DefaultEditorLayout.tsx +167 -231
- package/src/components/GameHierarchy.tsx +873 -290
- package/src/components/GamePerformanceOverlay.tsx +46 -0
- package/src/components/HarnessChatPanel.tsx +2352 -0
- package/src/components/HistoryPanel.tsx +226 -0
- package/src/components/Inspector.tsx +153 -99
- package/src/components/InspectorToolSection.tsx +6 -4
- package/src/components/MountFailureBanner.tsx +31 -48
- package/src/components/NetworkSection.tsx +25 -14
- package/src/components/OnlineAssetBrowser.tsx +794 -343
- package/src/components/OverlayReportBanner.tsx +38 -64
- package/src/components/PerformancePanel.tsx +206 -0
- package/src/components/PlayBar.tsx +93 -263
- package/src/components/PrefabSaveDialog.tsx +55 -127
- package/src/components/ProjectHeader.css +60 -0
- package/src/components/ProjectHeader.tsx +170 -105
- package/src/components/ProjectScreen.tsx +246 -184
- package/src/components/ReactCanvasControls.tsx +185 -0
- package/src/components/ResolutionPicker.tsx +85 -0
- package/src/components/RootActivateDialog.tsx +39 -68
- package/src/components/SaveDialog.tsx +55 -127
- package/src/components/SaveStatus.tsx +11 -29
- package/src/components/StartupLoadingScreen.tsx +96 -0
- package/src/components/StateWatchPanel.tsx +302 -0
- package/src/components/StatsOverlay.tsx +12 -5
- package/src/components/StatusBar.tsx +64 -0
- package/src/components/ThemeManagerDialog.css +89 -0
- package/src/components/ThemeManagerDialog.tsx +371 -0
- package/src/components/ToolHost.tsx +26 -15
- package/src/components/Toolbar.tsx +117 -214
- package/src/components/VgaiVoxelLogo.tsx +89 -0
- package/src/components/ViewportOverlay.tsx +97 -172
- package/src/components/ViewportPanel.tsx +130 -31
- package/src/components/WorkspaceDock.tsx +891 -0
- package/src/components/WorldSelectionOverlay.tsx +51 -63
- package/src/components/WorldTextEditor.tsx +9 -5
- package/src/components/asset-documents.tsx +390 -0
- package/src/components/asset-editor-persistence.ts +44 -0
- package/src/components/asset-selection-section.tsx +338 -0
- package/src/components/asset-viewers/AudioViewer.tsx +73 -21
- package/src/components/asset-viewers/GenericJsonViewer.tsx +26 -11
- package/src/components/asset-viewers/ImageViewer.tsx +43 -11
- package/src/components/asset-viewers/InputMapViewer.tsx +512 -134
- package/src/components/asset-viewers/MaterialAssetEditor.tsx +540 -0
- package/src/components/asset-viewers/ModelViewer.tsx +805 -121
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +177 -60
- package/src/components/asset-viewers/SourceAssetViewer.tsx +121 -0
- package/src/components/asset-workflow.css +506 -0
- package/src/components/build-documents.tsx +696 -0
- package/src/components/core-utilities.tsx +75 -0
- package/src/components/data-documents.tsx +364 -0
- package/src/components/{DataPanel.tsx → data-editor.tsx} +138 -288
- package/src/components/debugger-panel-state.ts +57 -0
- package/src/components/inspector-widgets/AlignmentGrid.tsx +8 -14
- package/src/components/inspector-widgets/BorderEditor.tsx +41 -33
- package/src/components/inspector-widgets/ColorPicker.tsx +39 -39
- package/src/components/inspector-widgets/FontPicker.tsx +13 -21
- package/src/components/inspector-widgets/GradientEditor.tsx +11 -19
- package/src/components/inspector-widgets/ScrubbableInput.tsx +6 -3
- package/src/components/inspector-widgets/ShadowEditor.tsx +20 -33
- package/src/components/inspector-widgets/shared.tsx +90 -49
- package/src/components/inspectors/AnimationSection.tsx +39 -40
- package/src/components/inspectors/AudioSection.tsx +28 -39
- package/src/components/inspectors/BoneAttachmentSection.tsx +19 -24
- package/src/components/inspectors/CameraSection.tsx +14 -23
- package/src/components/inspectors/ComponentsSection.tsx +39 -43
- package/src/components/inspectors/JointsSection.tsx +34 -43
- package/src/components/inspectors/LightSection.tsx +90 -35
- package/src/components/inspectors/MaterialSection.tsx +137 -174
- package/src/components/inspectors/MeshSection.tsx +9 -17
- package/src/components/inspectors/NavigationSection.tsx +8 -15
- package/src/components/inspectors/ParticleSection.tsx +91 -76
- package/src/components/inspectors/PhysicsSection.tsx +42 -46
- package/src/components/inspectors/PostProcessingSection.tsx +28 -36
- package/src/components/inspectors/RenderingSection.tsx +12 -20
- package/src/components/inspectors/ShadowSection.tsx +7 -8
- package/src/components/inspectors/SplineSection.tsx +31 -42
- package/src/components/inspectors/TransformSection.tsx +14 -22
- package/src/components/patterns/Dialog.tsx +95 -0
- package/src/components/patterns/Fields.tsx +44 -0
- package/src/components/patterns/List.tsx +25 -0
- package/src/components/patterns/StateSurface.tsx +40 -0
- package/src/components/patterns/Surfaces.tsx +115 -0
- package/src/components/patterns/Tabs.tsx +80 -0
- package/src/components/patterns/Toolbar.tsx +51 -0
- package/src/components/patterns/Tree.tsx +72 -0
- package/src/components/primitives/Button.tsx +46 -0
- package/src/components/primitives/ColorInput.tsx +11 -47
- package/src/components/primitives/EditorIcon.tsx +99 -0
- package/src/components/primitives/FormControls.tsx +71 -0
- package/src/components/primitives/JsonInput.tsx +15 -2
- package/src/components/primitives/Layout.tsx +100 -0
- package/src/components/primitives/Menu.tsx +105 -0
- package/src/components/primitives/NumberInput.tsx +8 -35
- package/src/components/primitives/Panel.tsx +49 -44
- package/src/components/primitives/SectionHeader.tsx +10 -28
- package/src/components/primitives/Text.tsx +46 -0
- package/src/components/primitives/Tooltip.tsx +20 -12
- package/src/components/primitives/Vec3Input.tsx +3 -0
- package/src/components/primitives/banner-tones.ts +33 -0
- package/src/components/primitives/editor-icons.ts +102 -0
- package/src/components/primitives/panel-header-styles.ts +30 -0
- package/src/components/project-operation-documents.tsx +226 -0
- package/src/components/status-contributions.tsx +175 -0
- package/src/components/story-documents-register.ts +15 -0
- package/src/components/story-documents.tsx +1185 -0
- package/src/components/tool-documents.tsx +110 -0
- package/src/components/workspace-dock.css +293 -0
- package/src/components/workspace-history.ts +33 -0
- package/src/components/world-documents.tsx +145 -0
- package/src/components/xstate-documents.tsx +209 -0
- package/src/composite-screenshot.ts +248 -0
- package/src/design-system/index.ts +70 -0
- package/src/design-system-stories/Foundation.stories.tsx +183 -0
- package/src/design-system-stories/Patterns.stories.tsx +251 -0
- package/src/design-system-stories/Workspace.stories.tsx +538 -0
- package/src/doctor/report.ts +57 -13
- package/src/editor-api.ts +295 -31
- package/src/editor-compatibility.ts +200 -0
- package/src/editor-console.ts +35 -17
- package/src/editor-hotkeys.ts +85 -33
- package/src/editor-store.ts +538 -233
- package/src/editor-viewport.ts +160 -34
- package/src/entity-factory.ts +14 -3
- package/src/gizmo-registry.ts +21 -5
- package/src/harness-chat-editor-context.ts +37 -0
- package/src/harness-chat-indicator.ts +68 -0
- package/src/harness-chat-layout.ts +53 -0
- package/src/harness-chat-lifecycle.ts +170 -0
- package/src/harness-chat-reconnect.ts +85 -0
- package/src/harness-chat-types.ts +409 -0
- package/src/hierarchy-drop.ts +50 -0
- package/src/hierarchy-expansion-state.ts +100 -0
- package/src/hierarchy-menu-registry.ts +1 -1
- package/src/hierarchy-projection.ts +76 -0
- package/src/history/editor-session.ts +21 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-service.ts +904 -0
- package/src/history/json-history-resource.ts +165 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +266 -0
- package/src/history/project-root-history-backends.ts +53 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +108 -0
- package/src/history/source-history-backend.ts +374 -0
- package/src/history/types.ts +100 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hosted-example.ts +12 -2
- package/src/hotkeys.ts +89 -18
- package/src/ingest/discovery-react.ts +9 -10
- package/src/ingest/discovery.ts +4 -4
- package/src/ingest/discovery2d.ts +5 -5
- package/src/ingest/games/bloom-composer/vgai.game.json +3 -3
- package/src/ingest/games/embed-bundle/vgai.game.json +3 -3
- package/src/ingest/games/games-fps/vgai.game.json +3 -3
- package/src/ingest/games/iframe-reachable/vgai.game.json +3 -3
- package/src/ingest/games/interactive-cubes/vgai.game.json +3 -3
- package/src/ingest/games/mismatched-three/vgai.game.json +3 -3
- package/src/ingest/games/own-three-bundled/vgai.game.json +3 -3
- package/src/ingest/games/pointerlock-maze/vgai.game.json +7 -4
- package/src/ingest/games-2d/bubbo-bubbo/vgai.game.json +3 -3
- package/src/ingest/games-2d/bunnymark/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy/vgai.game.json +3 -3
- package/src/ingest/games-2d/flappy-pixi/vgai.game.json +3 -3
- package/src/ingest/games-2d/puzzling-potions/vgai.game.json +3 -3
- package/src/ingest/games-2d/tilemap-camera/vgai.game.json +3 -3
- package/src/ingest/games-react/react-rpg/vgai.game.json +3 -3
- package/src/ingest/types.ts +1 -1
- package/src/ingest-mode.ts +47 -52
- package/src/ingest-siblings.ts +23 -33
- package/src/initial-scene.ts +1 -2
- package/src/main.tsx +16 -8
- package/src/manifest-project.ts +10 -12
- package/src/model-thumbnail.ts +261 -26
- package/src/module-mode.ts +44 -50
- package/src/play-log-events.ts +26 -0
- package/src/play-mode.ts +495 -401
- package/src/play-scene-adoption.ts +23 -0
- package/src/playground/main.ts +68 -24
- package/src/playwright-shim.ts +473 -0
- package/src/project-manager.ts +90 -10
- package/src/project-operations.ts +112 -0
- package/src/project-story-discovery.ts +25 -0
- package/src/project-tool-discovery.ts +43 -0
- package/src/scene-serializer.ts +0 -6
- package/src/scene-sync.ts +7 -2
- package/src/script-loader.ts +3 -3
- package/src/skeleton-helper.ts +57 -0
- package/src/storage/seed.ts +68 -8
- package/src/stories/StoryPreviewMount.tsx +265 -0
- package/src/stories/action-stories.fixture.tsx +44 -0
- package/src/stories/button-stories.fixture.tsx +50 -0
- package/src/stories/compose-project-stories.ts +182 -0
- package/src/stories/loader-stories.fixture.tsx +53 -0
- package/src/stories/story-discovery.ts +130 -0
- package/src/stories/story-registry.ts +146 -0
- package/src/theme-css.ts +1049 -0
- package/src/theme-library.ts +238 -0
- package/src/theme-preference.ts +130 -0
- package/src/theme.ts +527 -0
- package/src/tool-loader.ts +80 -24
- package/src/ui-source/inspect.ts +27 -4
- package/src/ui-source/source-write-backend.ts +83 -22
- package/src/widgets/index.ts +9 -18
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-dock-controller.ts +64 -0
- package/src/workspace-document-ids.ts +5 -0
- package/src/workspace-document-registry.ts +367 -0
- package/src/workspace-state-persistence.ts +455 -0
- package/src/workspace-status-registry.ts +110 -0
- package/src/workspace-utility-commands.ts +49 -0
- package/src/workspace-utility-registry.ts +161 -0
- package/src/world-document-routing.ts +16 -0
- package/src/xstate-inspect/XStateMachineInspector.tsx +544 -0
- package/src/xstate-inspect/character-animation-machine.fixture.ts +74 -0
- package/src/xstate-inspect/use-live-actor-state.ts +39 -0
- package/src/xstate-inspect/xstate-graph.ts +265 -0
- package/src/xstate-inspect/xstate-layout.ts +76 -0
- package/src/z-index.ts +26 -0
- package/template/.claude/skills/editor/SKILL.md +129 -90
- package/template/AGENTS.md +1110 -0
- package/template/CLAUDE.md +1 -512
- package/template/IDIOMS.md +50 -0
- package/template/check-idioms.ts +706 -0
- package/template/manifest-entry-modules-plugin.ts +53 -0
- package/template/package.json +13 -4
- package/template/public/scenes/default.scn2d.json +6 -0
- package/template/public/scenes/default.vscn.json +7 -1
- package/template/public/scenes/untitled.vscn.json +10 -0
- package/template/scripts/playtest.ts +40 -0
- package/template/server/loopback.ts +75 -0
- package/template/server/rooms/arena-room.ts +69 -1
- package/template/server/rooms/game-room.ts +68 -1
- package/template/src/data/README.md +9 -4
- package/template/src/data/data-ref.ts +35 -0
- package/template/src/data/tuning.schema.ts +1 -1
- package/template/src/main.ts +41 -62
- package/template/src/manifest-entry-modules.d.ts +3 -0
- package/template/src/operations/README.md +88 -0
- package/template/src/runtime/pixi-adapter.ts +24 -0
- package/template/src/scripts/components/data-spinner.ts +43 -2
- package/template/src/scripts/components/spin-lap-math.ts +27 -0
- package/template/src/tools/README.md +5 -3
- package/template/src/tools/example.tool.tsx +13 -7
- package/template/src/ui/Button.stories.tsx +66 -0
- package/template/src/ui/RebindPanel.tsx +2 -6
- package/template/src/ui/game.tsx +17 -0
- package/template/tests/acceptance/example.spec.ts +64 -0
- package/template/tests/logic/example.test.ts +31 -0
- package/template/tests/logic/room.test.ts +100 -0
- package/template/tests/tsconfig.json +30 -0
- package/template/validate-manifest.ts +5 -5
- package/template/validate-scenes.ts +50 -18
- package/template/vgai.game.json +20 -13
- package/template/vite.config.ts +77 -16
- package/vite-plugin-ui-oid.ts +261 -17
- package/dist/assets/CCDIKSolver-Dz-ysxZF.js +0 -1
- package/dist/assets/SelectionBox-BXsv_6aH.js +0 -1
- package/dist/assets/browser-project-scripts-DOru4L1x.js +0 -2
- package/dist/assets/browserAll-CEkWYlFk.js +0 -1
- package/dist/assets/create-runtime-DYIWr3va.js +0 -1
- package/dist/assets/data-asset-Bb9sIM7i.js +0 -5
- package/dist/assets/game-8SDZPPgH.js +0 -1
- package/dist/assets/game-CevorxyV.js +0 -1
- package/dist/assets/game-Di7XlywD.js +0 -1
- package/dist/assets/game-DjnCaw8R.js +0 -1
- package/dist/assets/game-Q9A0aXSV.js +0 -1
- package/dist/assets/index-CPVV6pjw.js +0 -1
- package/dist/assets/index-CpOnYyeD.js +0 -213
- package/dist/assets/index-Dr8H_6Rl.js +0 -1
- package/dist/assets/index-_bCaipUk.js +0 -13650
- package/dist/assets/ingest-siblings-Dz8eEqrV.js +0 -1
- package/dist/assets/mount-manifest-Bhk0ohsl.js +0 -1
- package/dist/assets/scene-query-BivAHhJu.js +0 -1
- package/dist/assets/webworkerAll-BLrH0F6F.js +0 -1
- package/dist/assets/xstate-animation-binding-1NGLmw6y.js +0 -1
- package/dist/assets/xstate.esm-BgUD33nl.js +0 -3
- package/src/authoring/scene-ui-layer.ts +0 -485
- package/src/authoring/ui-authoring-adapter.ts +0 -325
- package/src/authoring/ui-hierarchy-menu.ts +0 -40
- package/src/authoring/ui-inspector-sections.tsx +0 -156
- package/src/authoring/undo-timeline.ts +0 -145
- package/src/components/BottomPanel.tsx +0 -326
- package/src/components/BuildPanel.tsx +0 -455
- package/src/components/FolderTree.tsx +0 -179
- package/src/components/RightPanel.tsx +0 -166
- package/src/components/WorldContextMenu.tsx +0 -257
- package/src/components/asset-viewers/AssetViewer.tsx +0 -38
- package/src/ingest/games-react/react-rpg/src/ui/game-state.tsx +0 -129
- package/src/scene-ui-mode/active-scene-ui.ts +0 -58
- package/src/scene-ui-mode/scene-ui-palette-panel.tsx +0 -152
- package/src/scene-ui-mode/scene-ui-timeline-tab.tsx +0 -29
- package/src/scene-ui-mode/ui-prefab-dialog-host.tsx +0 -74
- package/src/scene-ui-mode/ui-prefab-hierarchy-menu.ts +0 -49
- package/src/scene-ui-mode/ui-prefab-io.ts +0 -76
- package/src/ui-editor/inspector.tsx +0 -563
- package/src/ui-editor/node-ops.ts +0 -266
- package/src/ui-editor/overlay.tsx +0 -380
- package/src/ui-editor/palette.tsx +0 -114
- package/src/ui-editor/react-store.ts +0 -78
- package/src/ui-editor/timeline-panel.tsx +0 -443
- package/src/ui-editor/ui-edit-store.ts +0 -552
- package/src/ui-editor/ui-prefab.ts +0 -95
- package/template/project.json +0 -9
- package/template/src/scripts/main.ts +0 -94
- package/template/src/ui/DefaultHud.tsx +0 -40
- package/template/src/ui/game-state.tsx +0 -37
- package/template/src/ui/mount-default-hud.tsx +0 -19
- package/template/src/ui/mount-ui.ts +0 -15
- package/vite-plugin-react-world-provider.ts +0 -81
|
@@ -1,552 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UIEditStore — the editing state + mutation ops for the UI authoring surface.
|
|
3
|
-
*
|
|
4
|
-
* Holds the UI roots (the .vscn `ui` tree, the source of truth — C9), the
|
|
5
|
-
* selection, a 50-deep undo stack, a clipboard, the live data source for bindings,
|
|
6
|
-
* and named board states/scenarios (C12). Plain observable store (subscribe/
|
|
7
|
-
* version) so React binds via useSyncExternalStore. Edits route through here;
|
|
8
|
-
* each user gesture pushes exactly one undo entry (preview-vs-commit, C6/K4).
|
|
9
|
-
*/
|
|
10
|
-
import type { UIAnimation, UINode, UIRoot } from '@engine/scene/schema';
|
|
11
|
-
import { createDataSource, type UIDataSource } from '@vgai/scene-ui/data-source';
|
|
12
|
-
import { notifyUndoableOp, registerUndoAdapter } from '../authoring/undo-timeline';
|
|
13
|
-
import {
|
|
14
|
-
cloneWithNewIds,
|
|
15
|
-
countNodes,
|
|
16
|
-
deleteNodePath,
|
|
17
|
-
duplicateNode,
|
|
18
|
-
ensureIds,
|
|
19
|
-
findNode,
|
|
20
|
-
insertNode,
|
|
21
|
-
moveNode,
|
|
22
|
-
newId,
|
|
23
|
-
removeNode,
|
|
24
|
-
reorderNode,
|
|
25
|
-
setNodePath,
|
|
26
|
-
unwrapNode,
|
|
27
|
-
updateNode,
|
|
28
|
-
wrapNode,
|
|
29
|
-
} from './node-ops';
|
|
30
|
-
|
|
31
|
-
const MAX_UNDO = 50;
|
|
32
|
-
|
|
33
|
-
/** A default node skeleton for a newly-created node of a given kind (palette). */
|
|
34
|
-
export function defaultNodeForKind(kind: UINode['kind']): UINode {
|
|
35
|
-
const id = newId(kind);
|
|
36
|
-
const base: UINode = { id, name: capitalize(kind), kind };
|
|
37
|
-
switch (kind) {
|
|
38
|
-
case 'text':
|
|
39
|
-
return { ...base, props: { text: 'Text' } };
|
|
40
|
-
case 'button':
|
|
41
|
-
return {
|
|
42
|
-
...base,
|
|
43
|
-
props: { text: 'Button' },
|
|
44
|
-
style: { backgroundColor: '#4af', color: '#000', borderRadius: 6 },
|
|
45
|
-
};
|
|
46
|
-
case 'box':
|
|
47
|
-
return { ...base, layout: { direction: 'row', gap: 8 }, style: {} };
|
|
48
|
-
case 'panel':
|
|
49
|
-
return {
|
|
50
|
-
...base,
|
|
51
|
-
layout: { padding: [8, 8, 8, 8] },
|
|
52
|
-
style: { backgroundColor: 'rgba(0,0,0,0.5)', borderRadius: 6 },
|
|
53
|
-
};
|
|
54
|
-
case 'grid':
|
|
55
|
-
return { ...base, layout: { columns: 3, gap: 8 } };
|
|
56
|
-
case 'image':
|
|
57
|
-
return { ...base, layout: { width: 48, height: 48 }, props: {} };
|
|
58
|
-
case 'progress':
|
|
59
|
-
return {
|
|
60
|
-
...base,
|
|
61
|
-
layout: { width: 120, height: 10 },
|
|
62
|
-
props: { value: 50, max: 100 },
|
|
63
|
-
style: { backgroundColor: '#4af' },
|
|
64
|
-
};
|
|
65
|
-
case 'input':
|
|
66
|
-
return { ...base, props: { placeholder: 'Input' } };
|
|
67
|
-
case 'toggle':
|
|
68
|
-
return { ...base, props: { text: 'Toggle', checked: false } };
|
|
69
|
-
case 'slider':
|
|
70
|
-
return { ...base, props: { value: 50, min: 0, max: 100 } };
|
|
71
|
-
default:
|
|
72
|
-
return base;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function capitalize(s: string): string {
|
|
77
|
-
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* The minimal schema-valid empty canvas root (design/24-scene-ui.md D10):
|
|
82
|
-
* `UIRootSchema` requires only `kind:'canvas'` plus the shared node fields
|
|
83
|
-
* (schema/ui.ts) — `space`/`scaling`/`theme` are all optional. Seeds a
|
|
84
|
-
* ui-less scene in PROJECT mode (a registry is present) instead of the demo
|
|
85
|
-
* fixture `COMPLEX_UI_ROOTS` — an author's own project should never land the
|
|
86
|
-
* demo tree, only an empty stage to build on.
|
|
87
|
-
*/
|
|
88
|
-
export function defaultCanvasRoot(): UIRoot {
|
|
89
|
-
return { id: newId('canvas'), kind: 'canvas', name: 'Canvas', space: 'screen', children: [] };
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
export interface UIEditStoreOptions {
|
|
93
|
-
/** Initial live data source state for bindings (game state). */
|
|
94
|
-
data?: Record<string, unknown>;
|
|
95
|
-
/** Called after a committed edit so the host can persist to .vscn (C9). */
|
|
96
|
-
onCommit?: (roots: UIRoot[]) => void;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export class UIEditStore {
|
|
100
|
-
private _roots: UIRoot[];
|
|
101
|
-
private _selection: string[] = [];
|
|
102
|
-
private _undo: string[] = [];
|
|
103
|
-
private _redo: string[] = [];
|
|
104
|
-
private _clipboard: UINode | null = null;
|
|
105
|
-
private _version = 0;
|
|
106
|
-
private _listeners = new Set<() => void>();
|
|
107
|
-
private _interactive: string | null = null; // snapshot taken at gesture start
|
|
108
|
-
private _states = new Map<string, Record<string, unknown>>();
|
|
109
|
-
private _sourceOid: string | null = null;
|
|
110
|
-
private _locale = 'en';
|
|
111
|
-
private _previewResolution: { width: number; height: number } | null = null;
|
|
112
|
-
private _onCommit: ((roots: UIRoot[]) => void) | undefined;
|
|
113
|
-
|
|
114
|
-
readonly data: UIDataSource;
|
|
115
|
-
|
|
116
|
-
constructor(roots: UIRoot[], opts: UIEditStoreOptions = {}) {
|
|
117
|
-
this._roots = ensureIds(roots);
|
|
118
|
-
this.data = createDataSource(opts.data ?? {});
|
|
119
|
-
this._onCommit = opts.onCommit;
|
|
120
|
-
// T3.2 slice 2: this store IS the undo/redo target the editor-global timeline
|
|
121
|
-
// dispatches to for entries it records below (`pushUndo`/`endEdit`). C3's
|
|
122
|
-
// `edit-mode-authoring.ts` (`exitEditModeAuthoring`) unregisters it on
|
|
123
|
-
// teardown for the unified editor's scene-UI child; the standalone
|
|
124
|
-
// harness (harness.tsx) never tears down, so it stays registered for the
|
|
125
|
-
// page's lifetime — harmless, mirrors `EditorStore`.
|
|
126
|
-
registerUndoAdapter(this);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// --- subscription ---
|
|
130
|
-
subscribe = (l: () => void): (() => void) => {
|
|
131
|
-
this._listeners.add(l);
|
|
132
|
-
return () => this._listeners.delete(l);
|
|
133
|
-
};
|
|
134
|
-
getVersion = (): number => this._version;
|
|
135
|
-
private notify(): void {
|
|
136
|
-
this._version += 1;
|
|
137
|
-
for (const l of this._listeners) l();
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// --- reads ---
|
|
141
|
-
get roots(): UIRoot[] {
|
|
142
|
-
return this._roots;
|
|
143
|
-
}
|
|
144
|
-
get selection(): string[] {
|
|
145
|
-
return this._selection;
|
|
146
|
-
}
|
|
147
|
-
get primary(): string | null {
|
|
148
|
-
return this._selection[this._selection.length - 1] ?? null;
|
|
149
|
-
}
|
|
150
|
-
get locale(): string {
|
|
151
|
-
return this._locale;
|
|
152
|
-
}
|
|
153
|
-
get previewResolution(): { width: number; height: number } | null {
|
|
154
|
-
return this._previewResolution;
|
|
155
|
-
}
|
|
156
|
-
get clipboardHasContent(): boolean {
|
|
157
|
-
return this._clipboard !== null;
|
|
158
|
-
}
|
|
159
|
-
get canUndo(): boolean {
|
|
160
|
-
return this._undo.length > 0;
|
|
161
|
-
}
|
|
162
|
-
get canRedo(): boolean {
|
|
163
|
-
return this._redo.length > 0;
|
|
164
|
-
}
|
|
165
|
-
/**
|
|
166
|
-
* True while an interactive gesture (drag/resize) is live — between
|
|
167
|
-
* `beginEdit()` and `endEdit()`, `_roots` holds uncommitted preview state.
|
|
168
|
-
* The viewport layer's content-sync (`scene-ui-layer.ts`) reads this to
|
|
169
|
-
* avoid reloading the committed document over an in-flight gesture, which
|
|
170
|
-
* would revert the drag and wipe selection/local-undo mid-motion.
|
|
171
|
-
*/
|
|
172
|
-
get inGesture(): boolean {
|
|
173
|
-
return this._interactive != null;
|
|
174
|
-
}
|
|
175
|
-
stateNames(): string[] {
|
|
176
|
-
return [...this._states.keys()];
|
|
177
|
-
}
|
|
178
|
-
nodeCount(): number {
|
|
179
|
-
return countNodes(this._roots);
|
|
180
|
-
}
|
|
181
|
-
find(id: string) {
|
|
182
|
-
return findNode(this._roots, id);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// --- selection ---
|
|
186
|
-
select(ids: string[]): void {
|
|
187
|
-
this._selection = ids;
|
|
188
|
-
this.notify();
|
|
189
|
-
}
|
|
190
|
-
selectOne(id: string | null, additive = false): void {
|
|
191
|
-
if (id == null) this._selection = [];
|
|
192
|
-
else if (additive)
|
|
193
|
-
this._selection = this._selection.includes(id)
|
|
194
|
-
? this._selection.filter((s) => s !== id)
|
|
195
|
-
: [...this._selection, id];
|
|
196
|
-
else this._selection = [id];
|
|
197
|
-
if (!additive) this._sourceOid = null; // a node selection clears any source selection
|
|
198
|
-
this.notify();
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** The selected component-internal source element (an OID), for live source edits (C7). */
|
|
202
|
-
get sourceOid(): string | null {
|
|
203
|
-
return this._sourceOid;
|
|
204
|
-
}
|
|
205
|
-
setSourceSelection(oid: string | null): void {
|
|
206
|
-
this._sourceOid = oid;
|
|
207
|
-
this.notify();
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// --- undo plumbing ---
|
|
211
|
-
private snapshot(): string {
|
|
212
|
-
return JSON.stringify(this._roots);
|
|
213
|
-
}
|
|
214
|
-
private pushUndo(): void {
|
|
215
|
-
this._undo.push(this.snapshot());
|
|
216
|
-
if (this._undo.length > MAX_UNDO) this._undo.shift();
|
|
217
|
-
this._redo = [];
|
|
218
|
-
notifyUndoableOp(this); // T3.2 slice 2 — tag this op on the global timeline
|
|
219
|
-
}
|
|
220
|
-
private commit(next: UIRoot[]): void {
|
|
221
|
-
this.pushUndo();
|
|
222
|
-
this._roots = next;
|
|
223
|
-
this._onCommit?.(this._roots);
|
|
224
|
-
this.notify();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
undo(): boolean {
|
|
228
|
-
// `false` tells the undo timeline "couldn't act — put the entry back"
|
|
229
|
-
// (see UndoAdapter in ../authoring/undo-timeline.ts).
|
|
230
|
-
const prev = this._undo.pop();
|
|
231
|
-
if (prev == null) return false;
|
|
232
|
-
this._redo.push(this.snapshot());
|
|
233
|
-
this._roots = JSON.parse(prev);
|
|
234
|
-
this._onCommit?.(this._roots);
|
|
235
|
-
this.notify();
|
|
236
|
-
return true;
|
|
237
|
-
}
|
|
238
|
-
redo(): boolean {
|
|
239
|
-
const next = this._redo.pop();
|
|
240
|
-
if (next == null) return false;
|
|
241
|
-
this._undo.push(this.snapshot());
|
|
242
|
-
this._roots = JSON.parse(next);
|
|
243
|
-
this._onCommit?.(this._roots);
|
|
244
|
-
this.notify();
|
|
245
|
-
return true;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// --- editing (each is one undo step) ---
|
|
249
|
-
setProp(id: string, path: string, value: unknown): void {
|
|
250
|
-
this.commit(setNodePath(this._roots, id, path, value));
|
|
251
|
-
}
|
|
252
|
-
/** Multi-select shared-property edit (K9). One undo step for all targets. */
|
|
253
|
-
setPropMulti(ids: string[], path: string, value: unknown): void {
|
|
254
|
-
let next = this._roots;
|
|
255
|
-
for (const id of ids) next = setNodePath(next, id, path, value);
|
|
256
|
-
this.commit(next);
|
|
257
|
-
}
|
|
258
|
-
deleteProp(id: string, path: string): void {
|
|
259
|
-
this.commit(deleteNodePath(this._roots, id, path));
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/** Apply multiple path->value patches to each id in ONE undo step (e.g. anchor presets). */
|
|
263
|
-
setMany(ids: string[], patches: Record<string, unknown>): void {
|
|
264
|
-
let next = this._roots;
|
|
265
|
-
for (const id of ids) {
|
|
266
|
-
for (const [path, value] of Object.entries(patches))
|
|
267
|
-
next = setNodePath(next, id, path, value);
|
|
268
|
-
}
|
|
269
|
-
this.commit(next);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
addNode(parentId: string, kind: UINode['kind'], index?: number): string {
|
|
273
|
-
const node = defaultNodeForKind(kind);
|
|
274
|
-
this.commit(insertNode(this._roots, parentId, node, index));
|
|
275
|
-
const id = node.id as string;
|
|
276
|
-
this.selectOne(id);
|
|
277
|
-
return id;
|
|
278
|
-
}
|
|
279
|
-
addNodeValue(parentId: string, node: UINode, index?: number): string {
|
|
280
|
-
const withIds = cloneWithNewIds(node);
|
|
281
|
-
this.commit(insertNode(this._roots, parentId, withIds, index));
|
|
282
|
-
return withIds.id as string;
|
|
283
|
-
}
|
|
284
|
-
remove(id: string): void {
|
|
285
|
-
this.commit(removeNode(this._roots, id));
|
|
286
|
-
this._selection = this._selection.filter((s) => s !== id);
|
|
287
|
-
}
|
|
288
|
-
/** D4.R2 (spec27 §6 D4 reopen) — remove every id in ONE undo step (one
|
|
289
|
-
* `commit`), the `StructureProvider.removeMany` backing for
|
|
290
|
-
* `UIAuthoringAdapter`. `removeSelected` (below) is now just this called
|
|
291
|
-
* with the current selection. */
|
|
292
|
-
removeMany(ids: readonly string[]): void {
|
|
293
|
-
if (ids.length === 0) return;
|
|
294
|
-
let next = this._roots;
|
|
295
|
-
for (const id of ids) next = removeNode(next, id);
|
|
296
|
-
this.commit(next);
|
|
297
|
-
const removed = new Set(ids);
|
|
298
|
-
this._selection = this._selection.filter((s) => !removed.has(s));
|
|
299
|
-
}
|
|
300
|
-
removeSelected(): void {
|
|
301
|
-
this.removeMany(this._selection);
|
|
302
|
-
}
|
|
303
|
-
duplicate(id: string): string | null {
|
|
304
|
-
const res = duplicateNode(this._roots, id);
|
|
305
|
-
if (res.newId) {
|
|
306
|
-
this.commit(res.roots);
|
|
307
|
-
this.selectOne(res.newId);
|
|
308
|
-
}
|
|
309
|
-
return res.newId;
|
|
310
|
-
}
|
|
311
|
-
reparent(id: string, newParentId: string, index?: number): void {
|
|
312
|
-
this.commit(moveNode(this._roots, id, newParentId, index));
|
|
313
|
-
}
|
|
314
|
-
reorder(id: string, newIndex: number): void {
|
|
315
|
-
this.commit(reorderNode(this._roots, id, newIndex));
|
|
316
|
-
}
|
|
317
|
-
/** Wrap a node in a new container (C8). Returns the wrapper id. */
|
|
318
|
-
wrap(id: string, kind: UINode['kind'] = 'box'): string | null {
|
|
319
|
-
const res = wrapNode(this._roots, id, kind);
|
|
320
|
-
if (res.wrapperId) {
|
|
321
|
-
this.commit(res.roots);
|
|
322
|
-
this.selectOne(res.wrapperId);
|
|
323
|
-
}
|
|
324
|
-
return res.wrapperId;
|
|
325
|
-
}
|
|
326
|
-
/** Unwrap a container, splicing its children into the parent (C8). */
|
|
327
|
-
unwrap(id: string): void {
|
|
328
|
-
this.commit(unwrapNode(this._roots, id));
|
|
329
|
-
}
|
|
330
|
-
/** Inline text edit (C8) — set a node's text prop. */
|
|
331
|
-
editText(id: string, text: string): void {
|
|
332
|
-
this.commit(setNodePath(this._roots, id, 'props.text', text));
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
// --- keyframe / timeline animation (S1) ---
|
|
336
|
-
|
|
337
|
-
/** Map a node's animation clip immutably and commit (one undo step). */
|
|
338
|
-
private updateClip(id: string, animIndex: number, fn: (clip: UIAnimation) => UIAnimation): void {
|
|
339
|
-
this.commit(
|
|
340
|
-
updateNode(this._roots, id, (node) => {
|
|
341
|
-
const anims = [...(node.animations ?? [])];
|
|
342
|
-
const clip = anims[animIndex];
|
|
343
|
-
if (!clip) return node;
|
|
344
|
-
anims[animIndex] = fn(clip);
|
|
345
|
-
return { ...node, animations: anims };
|
|
346
|
-
}),
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
/** Add a (default) animation clip to a node. Returns its index. */
|
|
351
|
-
addAnimation(id: string, clip?: UIAnimation): number {
|
|
352
|
-
const loc = findNode(this._roots, id);
|
|
353
|
-
if (!loc) return -1;
|
|
354
|
-
const count = loc.node.animations?.length ?? 0;
|
|
355
|
-
const next: UIAnimation = clip ?? {
|
|
356
|
-
name: `clip${count + 1}`,
|
|
357
|
-
duration: 1,
|
|
358
|
-
loop: false,
|
|
359
|
-
tracks: [],
|
|
360
|
-
};
|
|
361
|
-
this.commit(
|
|
362
|
-
updateNode(this._roots, id, (node) => ({
|
|
363
|
-
...node,
|
|
364
|
-
animations: [...(node.animations ?? []), next],
|
|
365
|
-
})),
|
|
366
|
-
);
|
|
367
|
-
return count;
|
|
368
|
-
}
|
|
369
|
-
removeAnimation(id: string, animIndex: number): void {
|
|
370
|
-
this.commit(
|
|
371
|
-
updateNode(this._roots, id, (node) => {
|
|
372
|
-
const anims = (node.animations ?? []).filter((_, i) => i !== animIndex);
|
|
373
|
-
return { ...node, animations: anims };
|
|
374
|
-
}),
|
|
375
|
-
);
|
|
376
|
-
}
|
|
377
|
-
setAnimationField(
|
|
378
|
-
id: string,
|
|
379
|
-
animIndex: number,
|
|
380
|
-
field: 'name' | 'duration' | 'loop' | 'autoplay',
|
|
381
|
-
value: unknown,
|
|
382
|
-
): void {
|
|
383
|
-
this.updateClip(id, animIndex, (c) => ({ ...c, [field]: value }));
|
|
384
|
-
}
|
|
385
|
-
addTrack(
|
|
386
|
-
id: string,
|
|
387
|
-
animIndex: number,
|
|
388
|
-
property: UIAnimation['tracks'][number]['property'],
|
|
389
|
-
): void {
|
|
390
|
-
this.updateClip(id, animIndex, (c) =>
|
|
391
|
-
c.tracks.some((t) => t.property === property)
|
|
392
|
-
? c
|
|
393
|
-
: { ...c, tracks: [...c.tracks, { property, keyframes: [] }] },
|
|
394
|
-
);
|
|
395
|
-
}
|
|
396
|
-
removeTrack(id: string, animIndex: number, trackIndex: number): void {
|
|
397
|
-
this.updateClip(id, animIndex, (c) => ({
|
|
398
|
-
...c,
|
|
399
|
-
tracks: c.tracks.filter((_, i) => i !== trackIndex),
|
|
400
|
-
}));
|
|
401
|
-
}
|
|
402
|
-
/** Upsert a keyframe on a track at `time` (record mode + manual add), keeping order. */
|
|
403
|
-
setKeyframe(
|
|
404
|
-
id: string,
|
|
405
|
-
animIndex: number,
|
|
406
|
-
trackIndex: number,
|
|
407
|
-
time: number,
|
|
408
|
-
value: number | string,
|
|
409
|
-
easing?: UIAnimation['tracks'][number]['keyframes'][number]['easing'],
|
|
410
|
-
): void {
|
|
411
|
-
this.updateClip(id, animIndex, (c) => {
|
|
412
|
-
const tracks = [...c.tracks];
|
|
413
|
-
const track = tracks[trackIndex];
|
|
414
|
-
if (!track) return c;
|
|
415
|
-
const kfs = track.keyframes.filter((k) => Math.abs(k.time - time) > 1e-4);
|
|
416
|
-
kfs.push({ time, value, ...(easing ? { easing } : {}) });
|
|
417
|
-
kfs.sort((a, b) => a.time - b.time);
|
|
418
|
-
tracks[trackIndex] = { ...track, keyframes: kfs };
|
|
419
|
-
return { ...c, tracks };
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
removeKeyframe(id: string, animIndex: number, trackIndex: number, kfIndex: number): void {
|
|
423
|
-
this.updateClip(id, animIndex, (c) => {
|
|
424
|
-
const tracks = [...c.tracks];
|
|
425
|
-
const track = tracks[trackIndex];
|
|
426
|
-
if (!track) return c;
|
|
427
|
-
tracks[trackIndex] = { ...track, keyframes: track.keyframes.filter((_, i) => i !== kfIndex) };
|
|
428
|
-
return { ...c, tracks };
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
setKeyframeEasing(
|
|
432
|
-
id: string,
|
|
433
|
-
animIndex: number,
|
|
434
|
-
trackIndex: number,
|
|
435
|
-
kfIndex: number,
|
|
436
|
-
easing: UIAnimation['tracks'][number]['keyframes'][number]['easing'],
|
|
437
|
-
): void {
|
|
438
|
-
this.updateClip(id, animIndex, (c) => {
|
|
439
|
-
const tracks = [...c.tracks];
|
|
440
|
-
const track = tracks[trackIndex];
|
|
441
|
-
if (!track) return c;
|
|
442
|
-
const kfs = track.keyframes.map((k, i) => (i === kfIndex ? { ...k, easing } : k));
|
|
443
|
-
tracks[trackIndex] = { ...track, keyframes: kfs };
|
|
444
|
-
return { ...c, tracks };
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// --- clipboard (K8) ---
|
|
449
|
-
copy(id?: string): void {
|
|
450
|
-
const target = id ?? this.primary;
|
|
451
|
-
if (!target) return;
|
|
452
|
-
const loc = findNode(this._roots, target);
|
|
453
|
-
if (loc) this._clipboard = JSON.parse(JSON.stringify(loc.node));
|
|
454
|
-
}
|
|
455
|
-
cut(id?: string): void {
|
|
456
|
-
const target = id ?? this.primary;
|
|
457
|
-
if (!target) return;
|
|
458
|
-
this.copy(target);
|
|
459
|
-
this.remove(target);
|
|
460
|
-
}
|
|
461
|
-
paste(targetParentId?: string): string | null {
|
|
462
|
-
if (!this._clipboard) return null;
|
|
463
|
-
const parentId = targetParentId ?? this.primary ?? this._roots[0]?.id;
|
|
464
|
-
if (!parentId) return null;
|
|
465
|
-
// paste INTO the selected container, or as sibling if selection is a leaf
|
|
466
|
-
const loc = findNode(this._roots, parentId);
|
|
467
|
-
const intoId =
|
|
468
|
-
loc && (loc.node.children || isContainer(loc.node))
|
|
469
|
-
? parentId
|
|
470
|
-
: (loc?.parent?.id ?? this._roots[0]?.id);
|
|
471
|
-
if (!intoId) return null;
|
|
472
|
-
return this.addNodeValue(intoId, this._clipboard);
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
// --- live preview vs commit (C6/K4) ---
|
|
476
|
-
/** Begin an interactive gesture (drag): snapshot once for a single undo step. */
|
|
477
|
-
beginEdit(): void {
|
|
478
|
-
this._interactive = this.snapshot();
|
|
479
|
-
}
|
|
480
|
-
/** Live property set during a gesture — NO undo push, NO persist callback. */
|
|
481
|
-
previewSetProp(id: string, path: string, value: unknown): void {
|
|
482
|
-
this._roots = setNodePath(this._roots, id, path, value);
|
|
483
|
-
this.notify();
|
|
484
|
-
}
|
|
485
|
-
previewSetPropMulti(ids: string[], path: string, value: unknown): void {
|
|
486
|
-
let next = this._roots;
|
|
487
|
-
for (const id of ids) next = setNodePath(next, id, path, value);
|
|
488
|
-
this._roots = next;
|
|
489
|
-
this.notify();
|
|
490
|
-
}
|
|
491
|
-
/** End the gesture: push the pre-gesture snapshot as the single undo step. */
|
|
492
|
-
endEdit(): void {
|
|
493
|
-
if (this._interactive == null) return;
|
|
494
|
-
this._undo.push(this._interactive);
|
|
495
|
-
if (this._undo.length > MAX_UNDO) this._undo.shift();
|
|
496
|
-
this._redo = [];
|
|
497
|
-
notifyUndoableOp(this); // T3.2 slice 2 — tag this op on the global timeline
|
|
498
|
-
this._interactive = null;
|
|
499
|
-
this._onCommit?.(this._roots);
|
|
500
|
-
this.notify();
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
// --- board states / in-realm scenarios (C12) ---
|
|
504
|
-
defineState(name: string, data: Record<string, unknown>): void {
|
|
505
|
-
this._states.set(name, data);
|
|
506
|
-
}
|
|
507
|
-
loadState(name: string): void {
|
|
508
|
-
const s = this._states.get(name);
|
|
509
|
-
if (!s) return;
|
|
510
|
-
for (const k of Object.keys(s)) this.data.set(k, s[k]);
|
|
511
|
-
this.notify();
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
// --- responsive preview (I3) + locale (G5) ---
|
|
515
|
-
setPreviewResolution(res: { width: number; height: number } | null): void {
|
|
516
|
-
this._previewResolution = res;
|
|
517
|
-
this.notify();
|
|
518
|
-
}
|
|
519
|
-
setLocale(locale: string): void {
|
|
520
|
-
this._locale = locale;
|
|
521
|
-
this.notify();
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
/** Serialize the UI tree for persistence into a SceneFile.ui (C9). */
|
|
525
|
-
serialize(): UIRoot[] {
|
|
526
|
-
return JSON.parse(JSON.stringify(this._roots));
|
|
527
|
-
}
|
|
528
|
-
/** Replace the whole tree (e.g. external load). Clears undo. */
|
|
529
|
-
load(roots: UIRoot[]): void {
|
|
530
|
-
this._roots = ensureIds(roots);
|
|
531
|
-
this._undo = [];
|
|
532
|
-
this._redo = [];
|
|
533
|
-
this._selection = [];
|
|
534
|
-
this.notify();
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
function isContainer(node: UINode): boolean {
|
|
539
|
-
return [
|
|
540
|
-
'canvas',
|
|
541
|
-
'box',
|
|
542
|
-
'grid',
|
|
543
|
-
'stack',
|
|
544
|
-
'scroll',
|
|
545
|
-
'panel',
|
|
546
|
-
'center',
|
|
547
|
-
'wrap',
|
|
548
|
-
'aspect',
|
|
549
|
-
'sizebox',
|
|
550
|
-
'tab',
|
|
551
|
-
].includes(node.kind);
|
|
552
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* UI prefab reuse (H1-H4): instantiate a saved UI subtree with fresh ids, apply
|
|
3
|
-
* per-instance overrides (H2), expose named props/slots (H3), and resolve NESTED
|
|
4
|
-
* prefab references with cycle protection (H4). Pure functions — unit tested.
|
|
5
|
-
*/
|
|
6
|
-
import type { UINode, UIPrefabFile } from '@engine/scene/schema';
|
|
7
|
-
import { cloneWithNewIds } from './node-ops';
|
|
8
|
-
|
|
9
|
-
/** Per-instance overrides for a prefab instance (H2). */
|
|
10
|
-
export interface UIPrefabOverrides {
|
|
11
|
-
props?: Record<string, unknown>;
|
|
12
|
-
style?: Record<string, unknown>;
|
|
13
|
-
/** Content injected into named slots (H3): slotName → nodes. */
|
|
14
|
-
slots?: Record<string, UINode[]>;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/** A registry resolving a prefab name/path → its file (for nested reuse, H4). */
|
|
18
|
-
export type UIPrefabRegistry = Record<string, UIPrefabFile>;
|
|
19
|
-
|
|
20
|
-
const MAX_DEPTH = 32;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Instantiate a prefab into a UINode tree: fresh ids, overrides merged onto the
|
|
24
|
-
* root (H2), nested `component` references to other prefabs resolved recursively
|
|
25
|
-
* (H4), and slot content injected (H3). Throws on a cyclic prefab reference.
|
|
26
|
-
*/
|
|
27
|
-
export function instantiateUIPrefab(
|
|
28
|
-
prefab: UIPrefabFile,
|
|
29
|
-
overrides: UIPrefabOverrides = {},
|
|
30
|
-
registry: UIPrefabRegistry = {},
|
|
31
|
-
_stack: string[] = [],
|
|
32
|
-
): UINode {
|
|
33
|
-
if (_stack.includes(prefab.name)) {
|
|
34
|
-
throw new Error(`cyclic UI prefab reference: ${[..._stack, prefab.name].join(' -> ')}`);
|
|
35
|
-
}
|
|
36
|
-
if (_stack.length > MAX_DEPTH) throw new Error('UI prefab nesting too deep');
|
|
37
|
-
|
|
38
|
-
const root = cloneWithNewIds(prefab.root);
|
|
39
|
-
const resolved = resolveNested(root, registry, [..._stack, prefab.name]);
|
|
40
|
-
return mergeUIPrefabInstance(resolved, overrides, prefab);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Apply per-instance overrides to a prefab root, gated by exposedProps/slots (H2/H3). */
|
|
44
|
-
export function mergeUIPrefabInstance(
|
|
45
|
-
node: UINode,
|
|
46
|
-
overrides: UIPrefabOverrides,
|
|
47
|
-
prefab?: UIPrefabFile,
|
|
48
|
-
): UINode {
|
|
49
|
-
const next: UINode = { ...node };
|
|
50
|
-
if (overrides.props) {
|
|
51
|
-
const allowed = prefab?.exposedProps;
|
|
52
|
-
const incoming = allowed ? pick(overrides.props, allowed) : overrides.props;
|
|
53
|
-
next.props = { ...next.props, ...incoming } as UINode['props'];
|
|
54
|
-
}
|
|
55
|
-
if (overrides.style) {
|
|
56
|
-
next.style = { ...next.style, ...overrides.style } as UINode['style'];
|
|
57
|
-
}
|
|
58
|
-
if (overrides.slots) {
|
|
59
|
-
next.children = injectSlots(next.children ?? [], overrides.slots);
|
|
60
|
-
}
|
|
61
|
-
return next;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/** Replace `component` nodes that reference a prefab in the registry with its instance (H4). */
|
|
65
|
-
function resolveNested(node: UINode, registry: UIPrefabRegistry, stack: string[]): UINode {
|
|
66
|
-
if (node.kind === 'component' && node.component && registry[node.component]) {
|
|
67
|
-
const ov: UIPrefabOverrides = node.props
|
|
68
|
-
? { props: node.props as Record<string, unknown> }
|
|
69
|
-
: {};
|
|
70
|
-
return instantiateUIPrefab(registry[node.component] as UIPrefabFile, ov, registry, stack);
|
|
71
|
-
}
|
|
72
|
-
if (!node.children) return node;
|
|
73
|
-
return { ...node, children: node.children.map((c) => resolveNested(c, registry, stack)) };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/** Inject slot content where a child is a slot marker (kind 'component' named '$slot:name'). */
|
|
77
|
-
function injectSlots(children: UINode[], slots: Record<string, UINode[]>): UINode[] {
|
|
78
|
-
const out: UINode[] = [];
|
|
79
|
-
for (const c of children) {
|
|
80
|
-
const slotName =
|
|
81
|
-
c.kind === 'component' && c.component?.startsWith('$slot:') ? c.component.slice(6) : null;
|
|
82
|
-
if (slotName && slots[slotName]) {
|
|
83
|
-
out.push(...slots[slotName].map(cloneWithNewIds));
|
|
84
|
-
} else {
|
|
85
|
-
out.push(c.children ? { ...c, children: injectSlots(c.children, slots) } : c);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function pick(obj: Record<string, unknown>, keys: string[]): Record<string, unknown> {
|
|
92
|
-
const out: Record<string, unknown> = {};
|
|
93
|
-
for (const k of keys) if (k in obj) out[k] = obj[k];
|
|
94
|
-
return out;
|
|
95
|
-
}
|