@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
|
@@ -0,0 +1,891 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type DockviewApi,
|
|
3
|
+
DockviewDefaultTab,
|
|
4
|
+
type DockviewPanelApi,
|
|
5
|
+
DockviewReact,
|
|
6
|
+
type DockviewTheme,
|
|
7
|
+
type IDockviewPanelHeaderProps,
|
|
8
|
+
type IDockviewPanelProps,
|
|
9
|
+
} from 'dockview-react';
|
|
10
|
+
import {
|
|
11
|
+
createContext,
|
|
12
|
+
useCallback,
|
|
13
|
+
useContext,
|
|
14
|
+
useEffect,
|
|
15
|
+
useRef,
|
|
16
|
+
useState,
|
|
17
|
+
useSyncExternalStore,
|
|
18
|
+
} from 'react';
|
|
19
|
+
import { availabilityTickVersion, subscribeAvailabilityTick } from '../availability-tick';
|
|
20
|
+
import { Button } from '../design-system';
|
|
21
|
+
import { useEditorStore } from '../EditorContext';
|
|
22
|
+
import {
|
|
23
|
+
harnessChat,
|
|
24
|
+
harnessChatLayoutVersion,
|
|
25
|
+
setHarnessChatCollapsed,
|
|
26
|
+
subscribeHarnessChatLayout,
|
|
27
|
+
} from '../harness-chat-layout';
|
|
28
|
+
import type { HotkeyScope } from '../hotkeys';
|
|
29
|
+
import {
|
|
30
|
+
installWorkspaceDockCommands,
|
|
31
|
+
type WorkspaceDockCommands,
|
|
32
|
+
} from '../workspace-dock-controller';
|
|
33
|
+
import {
|
|
34
|
+
activateWorkspaceDocument,
|
|
35
|
+
activeWorkspaceDocumentId,
|
|
36
|
+
closeWorkspaceDocument,
|
|
37
|
+
openWorkspaceDocuments,
|
|
38
|
+
saveWorkspaceDocument,
|
|
39
|
+
subscribeWorkspaceDocuments,
|
|
40
|
+
workspaceDocumentRegistryVersion,
|
|
41
|
+
} from '../workspace-document-registry';
|
|
42
|
+
import { installWorkspaceStatePersistence } from '../workspace-state-persistence';
|
|
43
|
+
import {
|
|
44
|
+
availableWorkspaceUtilities,
|
|
45
|
+
registeredWorkspaceUtilities,
|
|
46
|
+
subscribeWorkspaceUtilities,
|
|
47
|
+
type WorkspaceUtilityRegistration,
|
|
48
|
+
workspaceUtilityBadge,
|
|
49
|
+
workspaceUtilityRegistryVersion,
|
|
50
|
+
} from '../workspace-utility-registry';
|
|
51
|
+
import { AssetBrowser } from './AssetBrowser';
|
|
52
|
+
import { AssetLibraryPanel } from './AssetLibraryPanel';
|
|
53
|
+
import { GameHierarchy } from './GameHierarchy';
|
|
54
|
+
import { HarnessChatPanel } from './HarnessChatPanel';
|
|
55
|
+
import { HistoryPanel } from './HistoryPanel';
|
|
56
|
+
import { Inspector } from './Inspector';
|
|
57
|
+
import 'dockview-react/dist/styles/dockview.css';
|
|
58
|
+
import './workspace-dock.css';
|
|
59
|
+
|
|
60
|
+
const COMPONENT_ID = 'vgai-workspace-panel';
|
|
61
|
+
const TAB_COMPONENT_ID = 'vgai-workspace-tab';
|
|
62
|
+
|
|
63
|
+
const HIERARCHY_PANEL_ID = 'navigator:hierarchy';
|
|
64
|
+
const AGENT_PANEL_ID = 'workspace:agent';
|
|
65
|
+
const ASSETS_PANEL_ID = 'navigator:assets';
|
|
66
|
+
const ASSET_LIBRARY_PANEL_ID = 'navigator:asset-library';
|
|
67
|
+
const INSPECTOR_PANEL_ID = 'workspace:inspector';
|
|
68
|
+
const HISTORY_PANEL_ID = 'aux:undo-history';
|
|
69
|
+
|
|
70
|
+
const LEFT_GROUP_ID = 'vgai:left';
|
|
71
|
+
const AGENT_GROUP_ID = 'vgai:agent';
|
|
72
|
+
const ASSETS_GROUP_ID = 'vgai:assets';
|
|
73
|
+
const RIGHT_GROUP_ID = 'vgai:right';
|
|
74
|
+
const BOTTOM_GROUP_ID = 'vgai:bottom';
|
|
75
|
+
const CENTER_GROUP_ID = 'vgai:center';
|
|
76
|
+
const normalizedAgentGroups = new WeakSet<object>();
|
|
77
|
+
|
|
78
|
+
type StaticPanelKind = 'agent' | 'hierarchy' | 'assets' | 'asset-library' | 'inspector' | 'history';
|
|
79
|
+
type WorkspacePanelParams =
|
|
80
|
+
| { kind: 'static'; staticKind: StaticPanelKind; closeable: boolean }
|
|
81
|
+
| { kind: 'document'; documentId: string; closeable: boolean }
|
|
82
|
+
| { kind: 'utility'; utilityId: string; closeable: boolean };
|
|
83
|
+
|
|
84
|
+
function hotkeyScopeForPanel(params: WorkspacePanelParams): HotkeyScope {
|
|
85
|
+
if (params.kind === 'utility') return 'workspace';
|
|
86
|
+
if (params.kind === 'document') {
|
|
87
|
+
return isWorldSurfaceDocument(params.documentId) ? 'viewport' : 'workspace';
|
|
88
|
+
}
|
|
89
|
+
switch (params.staticKind) {
|
|
90
|
+
case 'hierarchy':
|
|
91
|
+
return 'hierarchy';
|
|
92
|
+
case 'assets':
|
|
93
|
+
case 'asset-library':
|
|
94
|
+
return 'asset-browser';
|
|
95
|
+
case 'inspector':
|
|
96
|
+
return 'inspector';
|
|
97
|
+
case 'agent':
|
|
98
|
+
case 'history':
|
|
99
|
+
return 'workspace';
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const OpenSceneContext = createContext<(assetPath: string) => void>(() => undefined);
|
|
104
|
+
|
|
105
|
+
export const vgaiDockviewTheme: DockviewTheme = {
|
|
106
|
+
name: 'vgai',
|
|
107
|
+
className: 'dockview-theme-vgai',
|
|
108
|
+
colorScheme: 'dark',
|
|
109
|
+
gap: 1,
|
|
110
|
+
edgeGroupCollapsedSize: 28,
|
|
111
|
+
dndOverlayMounting: 'absolute',
|
|
112
|
+
dndPanelOverlay: 'group',
|
|
113
|
+
dndTabIndicator: 'line',
|
|
114
|
+
dndOverlayBorder: '1px solid var(--dv-active-sash-color)',
|
|
115
|
+
tabGroupIndicator: 'none',
|
|
116
|
+
tabAnimation: 'smooth',
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
function usePanelActive(api: DockviewPanelApi): boolean {
|
|
120
|
+
const [active, setActive] = useState(api.isActive);
|
|
121
|
+
useEffect(() => {
|
|
122
|
+
setActive(api.isActive);
|
|
123
|
+
const disposable = api.onDidActiveChange((event) => setActive(event.isActive));
|
|
124
|
+
return () => disposable.dispose();
|
|
125
|
+
}, [api]);
|
|
126
|
+
return active;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function StaticPanelContent({ staticKind }: { staticKind: StaticPanelKind }) {
|
|
130
|
+
const onOpenScene = useContext(OpenSceneContext);
|
|
131
|
+
switch (staticKind) {
|
|
132
|
+
case 'agent':
|
|
133
|
+
return <HarnessChatPanel />;
|
|
134
|
+
case 'hierarchy':
|
|
135
|
+
return <GameHierarchy />;
|
|
136
|
+
case 'assets':
|
|
137
|
+
return <AssetBrowser onOpenScene={onOpenScene} />;
|
|
138
|
+
case 'asset-library':
|
|
139
|
+
return <AssetLibraryPanel />;
|
|
140
|
+
case 'inspector':
|
|
141
|
+
return <Inspector />;
|
|
142
|
+
case 'history':
|
|
143
|
+
return <HistoryPanel />;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function WorkspacePanel({ api, params }: IDockviewPanelProps<WorkspacePanelParams>) {
|
|
148
|
+
const active = usePanelActive(api);
|
|
149
|
+
useSyncExternalStore(
|
|
150
|
+
subscribeWorkspaceDocuments,
|
|
151
|
+
workspaceDocumentRegistryVersion,
|
|
152
|
+
workspaceDocumentRegistryVersion,
|
|
153
|
+
);
|
|
154
|
+
useSyncExternalStore(
|
|
155
|
+
subscribeWorkspaceUtilities,
|
|
156
|
+
workspaceUtilityRegistryVersion,
|
|
157
|
+
workspaceUtilityRegistryVersion,
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
if (params.kind === 'static') {
|
|
161
|
+
return (
|
|
162
|
+
<div
|
|
163
|
+
className="vgai-dock-panel"
|
|
164
|
+
data-testid={`dock-panel:${api.id}`}
|
|
165
|
+
data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
|
|
166
|
+
>
|
|
167
|
+
<StaticPanelContent staticKind={params.staticKind} />
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (params.kind === 'utility') {
|
|
173
|
+
const utility = availableWorkspaceUtilities().find((item) => item.id === params.utilityId);
|
|
174
|
+
if (!utility) {
|
|
175
|
+
return <div className="vgai-dock-empty">This utility is not available in this session.</div>;
|
|
176
|
+
}
|
|
177
|
+
const Content = utility.Content;
|
|
178
|
+
return (
|
|
179
|
+
<div
|
|
180
|
+
className="vgai-dock-panel"
|
|
181
|
+
data-testid={`bottom-panel-utility-${params.utilityId}`}
|
|
182
|
+
data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
|
|
183
|
+
>
|
|
184
|
+
<Content />
|
|
185
|
+
</div>
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const document = openWorkspaceDocuments().find(
|
|
190
|
+
(item) => item.descriptor.id === params.documentId,
|
|
191
|
+
);
|
|
192
|
+
if (!document) return <div className="vgai-dock-empty">Restoring document…</div>;
|
|
193
|
+
const Content = document.descriptor.Content;
|
|
194
|
+
const Toolbar = document.descriptor.Toolbar;
|
|
195
|
+
return (
|
|
196
|
+
<div
|
|
197
|
+
className="vgai-dock-document"
|
|
198
|
+
data-testid={`workspace-doc:${params.documentId}`}
|
|
199
|
+
data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
|
|
200
|
+
>
|
|
201
|
+
{Toolbar && (
|
|
202
|
+
<div className="vgai-dock-document-toolbar">
|
|
203
|
+
<Toolbar documentId={params.documentId} active={active} />
|
|
204
|
+
</div>
|
|
205
|
+
)}
|
|
206
|
+
<div className="vgai-dock-document-content">
|
|
207
|
+
<Content documentId={params.documentId} active={active} />
|
|
208
|
+
</div>
|
|
209
|
+
</div>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function workspaceTabTestId(params: WorkspacePanelParams, panelId: string): string {
|
|
214
|
+
if (params.kind === 'document') return `workspace-tab:${params.documentId}`;
|
|
215
|
+
if (params.kind === 'utility') return `bottom-tab-utility-${params.utilityId}`;
|
|
216
|
+
return `dock-tab:${panelId}`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function isWorldSurfaceDocument(documentId: string): boolean {
|
|
220
|
+
const kind = openWorkspaceDocuments().find((item) => item.descriptor.id === documentId)
|
|
221
|
+
?.descriptor.kind;
|
|
222
|
+
return kind === 'game' || kind === 'scene' || kind === 'world';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function documentTabFamily(params: WorkspacePanelParams): 'world' | 'resource' | undefined {
|
|
226
|
+
if (params.kind !== 'document') return undefined;
|
|
227
|
+
return isWorldSurfaceDocument(params.documentId) ? 'world' : 'resource';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** An older saved layout can restore resource tabs before world documents
|
|
231
|
+
* that did not exist when it was written. Stable-partition only that upgrade
|
|
232
|
+
* case; ordinary user ordering inside each family remains untouched. */
|
|
233
|
+
function normalizeDocumentSectionOrder(
|
|
234
|
+
api: DockviewApi,
|
|
235
|
+
centerGroupId: string,
|
|
236
|
+
documentIds: readonly string[],
|
|
237
|
+
): void {
|
|
238
|
+
const centerDocumentIds = documentIds.filter(
|
|
239
|
+
(documentId) => api.getPanel(documentId)?.api.group.id === centerGroupId,
|
|
240
|
+
);
|
|
241
|
+
const firstDocumentId = centerDocumentIds[0];
|
|
242
|
+
const group = firstDocumentId ? api.getPanel(firstDocumentId)?.api.group : undefined;
|
|
243
|
+
if (!group) return;
|
|
244
|
+
for (const [index, documentId] of centerDocumentIds.entries()) {
|
|
245
|
+
const panel = api.getPanel(documentId);
|
|
246
|
+
if (!panel) continue;
|
|
247
|
+
if (group.panels[index]?.id === documentId) continue;
|
|
248
|
+
panel.api.moveTo({ group, index, skipSetActive: true });
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const subscribeToNothing = () => () => undefined;
|
|
253
|
+
const staticBadgeSnapshot = () => 0;
|
|
254
|
+
|
|
255
|
+
/** Badge values often come from a store independent of the registration
|
|
256
|
+
* registry (the editor console is the first-party example). Keep that live
|
|
257
|
+
* source attached to the tab even while its utility panel is collapsed. */
|
|
258
|
+
function useWorkspaceUtilityBadge(utilityId: string | null) {
|
|
259
|
+
const utility = utilityId
|
|
260
|
+
? registeredWorkspaceUtilities().find((item) => item.id === utilityId)
|
|
261
|
+
: undefined;
|
|
262
|
+
useSyncExternalStore(
|
|
263
|
+
utility?.subscribeBadge ?? subscribeToNothing,
|
|
264
|
+
utility?.badgeSnapshot ?? staticBadgeSnapshot,
|
|
265
|
+
utility?.badgeSnapshot ?? staticBadgeSnapshot,
|
|
266
|
+
);
|
|
267
|
+
return utilityId ? workspaceUtilityBadge(utilityId) : null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function closeDockviewDocumentTab(
|
|
271
|
+
props: IDockviewPanelHeaderProps<WorkspacePanelParams>,
|
|
272
|
+
documentId: string,
|
|
273
|
+
discardDirty = false,
|
|
274
|
+
): boolean {
|
|
275
|
+
if (!closeWorkspaceDocument(documentId, { discardDirty })) return false;
|
|
276
|
+
// Close through Dockview immediately so its physical tab order—including
|
|
277
|
+
// user moves—chooses the visible neighbor. If that neighbor is another
|
|
278
|
+
// document, mirror it back after the registry's generic fallback ran.
|
|
279
|
+
props.api.close();
|
|
280
|
+
const next = props.containerApi.activePanel;
|
|
281
|
+
if (!next) return true;
|
|
282
|
+
const nextParams = next.api.getParameters<WorkspacePanelParams>();
|
|
283
|
+
if (nextParams.kind === 'document') activateWorkspaceDocument(nextParams.documentId);
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function WorkspaceTab(props: IDockviewPanelHeaderProps<WorkspacePanelParams>) {
|
|
288
|
+
const { api, params } = props;
|
|
289
|
+
const [confirmingClose, setConfirmingClose] = useState(false);
|
|
290
|
+
const [savingClose, setSavingClose] = useState(false);
|
|
291
|
+
useSyncExternalStore(
|
|
292
|
+
subscribeWorkspaceDocuments,
|
|
293
|
+
workspaceDocumentRegistryVersion,
|
|
294
|
+
workspaceDocumentRegistryVersion,
|
|
295
|
+
);
|
|
296
|
+
useSyncExternalStore(
|
|
297
|
+
subscribeWorkspaceUtilities,
|
|
298
|
+
workspaceUtilityRegistryVersion,
|
|
299
|
+
workspaceUtilityRegistryVersion,
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const document =
|
|
303
|
+
params.kind === 'document'
|
|
304
|
+
? openWorkspaceDocuments().find((item) => item.descriptor.id === params.documentId)
|
|
305
|
+
: null;
|
|
306
|
+
const badge = useWorkspaceUtilityBadge(params.kind === 'utility' ? params.utilityId : null);
|
|
307
|
+
const testId = workspaceTabTestId(params, api.id);
|
|
308
|
+
const tabFamily = documentTabFamily(params);
|
|
309
|
+
const closeProps =
|
|
310
|
+
params.kind === 'document' && params.closeable
|
|
311
|
+
? {
|
|
312
|
+
closeActionOverride: () => {
|
|
313
|
+
if (document?.dirty) {
|
|
314
|
+
setConfirmingClose(true);
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
closeDockviewDocumentTab(props, params.documentId);
|
|
318
|
+
},
|
|
319
|
+
}
|
|
320
|
+
: params.kind === 'static' && params.staticKind === 'agent'
|
|
321
|
+
? {
|
|
322
|
+
closeActionOverride: () => {
|
|
323
|
+
setHarnessChatCollapsed(true);
|
|
324
|
+
props.api.close();
|
|
325
|
+
},
|
|
326
|
+
}
|
|
327
|
+
: {};
|
|
328
|
+
if (params.kind === 'document' && document && confirmingClose) {
|
|
329
|
+
return (
|
|
330
|
+
<div
|
|
331
|
+
className="vgai-dock-tab vgai-dock-tab-unsaved"
|
|
332
|
+
data-testid={`workspace-tab-unsaved:${params.documentId}`}
|
|
333
|
+
data-document-family={tabFamily}
|
|
334
|
+
role="group"
|
|
335
|
+
aria-label={`Save changes to ${document.title}?`}
|
|
336
|
+
onPointerDown={(event) => event.stopPropagation()}
|
|
337
|
+
>
|
|
338
|
+
<span className="vgai-dock-tab-unsaved-label">Unsaved</span>
|
|
339
|
+
<Button
|
|
340
|
+
type="button"
|
|
341
|
+
disabled={savingClose}
|
|
342
|
+
onClick={() => {
|
|
343
|
+
setSavingClose(true);
|
|
344
|
+
void saveWorkspaceDocument(params.documentId).then((saved) => {
|
|
345
|
+
setSavingClose(false);
|
|
346
|
+
if (saved) closeDockviewDocumentTab(props, params.documentId);
|
|
347
|
+
});
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
{savingClose ? 'Saving…' : 'Save'}
|
|
351
|
+
</Button>
|
|
352
|
+
<Button
|
|
353
|
+
type="button"
|
|
354
|
+
disabled={savingClose}
|
|
355
|
+
onClick={() => closeDockviewDocumentTab(props, params.documentId, true)}
|
|
356
|
+
>
|
|
357
|
+
Discard
|
|
358
|
+
</Button>
|
|
359
|
+
<Button type="button" disabled={savingClose} onClick={() => setConfirmingClose(false)}>
|
|
360
|
+
Cancel
|
|
361
|
+
</Button>
|
|
362
|
+
</div>
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return (
|
|
367
|
+
<div
|
|
368
|
+
className="vgai-dock-tab"
|
|
369
|
+
data-testid={testId}
|
|
370
|
+
data-dirty={document?.dirty || undefined}
|
|
371
|
+
data-document-family={tabFamily}
|
|
372
|
+
data-editor-hotkey-scope={hotkeyScopeForPanel(params)}
|
|
373
|
+
>
|
|
374
|
+
<DockviewDefaultTab {...props} hideClose={!params.closeable} {...closeProps} />
|
|
375
|
+
{document?.dirty && (
|
|
376
|
+
<span className="vgai-dock-tab-dirty" title="Unsaved" aria-hidden="true" />
|
|
377
|
+
)}
|
|
378
|
+
{badge && badge.count > 0 && (
|
|
379
|
+
<span className={`vgai-dock-tab-badge vgai-dock-tab-badge-${badge.severity}`}>
|
|
380
|
+
{badge.count}
|
|
381
|
+
</span>
|
|
382
|
+
)}
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function addStaticPanel(
|
|
388
|
+
api: DockviewApi,
|
|
389
|
+
options: {
|
|
390
|
+
id: string;
|
|
391
|
+
title: string;
|
|
392
|
+
staticKind: StaticPanelKind;
|
|
393
|
+
groupId: string;
|
|
394
|
+
inactive?: boolean;
|
|
395
|
+
closeable?: boolean;
|
|
396
|
+
},
|
|
397
|
+
): void {
|
|
398
|
+
if (api.getPanel(options.id)) return;
|
|
399
|
+
api.addPanel<WorkspacePanelParams>({
|
|
400
|
+
id: options.id,
|
|
401
|
+
title: options.title,
|
|
402
|
+
component: COMPONENT_ID,
|
|
403
|
+
tabComponent: TAB_COMPONENT_ID,
|
|
404
|
+
renderer: 'always',
|
|
405
|
+
inactive: options.inactive ?? false,
|
|
406
|
+
position: { referenceGroup: options.groupId },
|
|
407
|
+
params: {
|
|
408
|
+
kind: 'static',
|
|
409
|
+
staticKind: options.staticKind,
|
|
410
|
+
closeable: options.closeable ?? false,
|
|
411
|
+
},
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Permanent editor columns are normal Dockview grid groups. Edge groups put
|
|
416
|
+
* their headers on the outside edge, producing rotated vertical labels and a
|
|
417
|
+
* second rail beside the actual panel. Grid groups preserve all Dockview
|
|
418
|
+
* resize/drag/float behavior while using the conventional horizontal header. */
|
|
419
|
+
function ensureSideGroups(api: DockviewApi, centerGroupId: string): void {
|
|
420
|
+
let addedLeft = false;
|
|
421
|
+
let addedRight = false;
|
|
422
|
+
if (!api.getGroup(LEFT_GROUP_ID)) {
|
|
423
|
+
api.addGroup({
|
|
424
|
+
id: LEFT_GROUP_ID,
|
|
425
|
+
referenceGroup: centerGroupId,
|
|
426
|
+
direction: 'left',
|
|
427
|
+
initialWidth: 260,
|
|
428
|
+
constraints: { minimumWidth: 220, maximumWidth: 320 },
|
|
429
|
+
});
|
|
430
|
+
addedLeft = true;
|
|
431
|
+
}
|
|
432
|
+
if (!api.getGroup(RIGHT_GROUP_ID)) {
|
|
433
|
+
api.addGroup({
|
|
434
|
+
id: RIGHT_GROUP_ID,
|
|
435
|
+
referenceGroup: centerGroupId,
|
|
436
|
+
direction: 'right',
|
|
437
|
+
initialWidth: 280,
|
|
438
|
+
constraints: { minimumWidth: 240, maximumWidth: 340 },
|
|
439
|
+
});
|
|
440
|
+
addedRight = true;
|
|
441
|
+
}
|
|
442
|
+
// Adding the second sibling can redistribute the first split before the
|
|
443
|
+
// root has completed its initial layout. Set the intended editor widths
|
|
444
|
+
// after both groups exist; Dockview queues these values if still unmeasured.
|
|
445
|
+
if (addedLeft) api.getGroup(LEFT_GROUP_ID)?.api.setSize({ width: 260 });
|
|
446
|
+
if (addedRight) api.getGroup(RIGHT_GROUP_ID)?.api.setSize({ width: 280 });
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function ensureAgentGroup(api: DockviewApi): void {
|
|
450
|
+
const existing = api.getGroup(AGENT_GROUP_ID);
|
|
451
|
+
if (existing) {
|
|
452
|
+
// Dockview keeps an empty group after the optional Agent panel is hidden.
|
|
453
|
+
// Refresh its constraints before reusing it: serialized/older constraints
|
|
454
|
+
// can otherwise let a 360px chat regrow to ~450px on restore.
|
|
455
|
+
existing.api.setConstraints({ minimumWidth: 300, maximumWidth: 380 });
|
|
456
|
+
existing.api.setSize({ width: 360 });
|
|
457
|
+
normalizedAgentGroups.add(existing);
|
|
458
|
+
requestAnimationFrame(() => {
|
|
459
|
+
api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
// Absolute `right` makes Agent a sibling of the entire existing grid tree.
|
|
464
|
+
// Referencing the Hierarchy group here is subtly wrong: once Assets splits
|
|
465
|
+
// below Hierarchy, Dockview expands that lower split beneath Agent too and
|
|
466
|
+
// leaves chat occupying only one corner instead of a full-height column.
|
|
467
|
+
const agentGroup = api.addGroup({
|
|
468
|
+
id: AGENT_GROUP_ID,
|
|
469
|
+
direction: 'right',
|
|
470
|
+
initialWidth: 360,
|
|
471
|
+
constraints: { minimumWidth: 300, maximumWidth: 380 },
|
|
472
|
+
});
|
|
473
|
+
normalizedAgentGroups.add(agentGroup);
|
|
474
|
+
// Agent is added after the navigator and inspector. Reassert their semantic
|
|
475
|
+
// defaults after Dockview introduces the fourth column so the root split
|
|
476
|
+
// does not redistribute every tool surface toward equal widths.
|
|
477
|
+
api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
|
|
478
|
+
api.getGroup(LEFT_GROUP_ID)?.api.setSize({ width: 260 });
|
|
479
|
+
api.getGroup(RIGHT_GROUP_ID)?.api.setSize({ width: 280 });
|
|
480
|
+
// Adding the Agent panel after this group returns causes Dockview to run one
|
|
481
|
+
// more root layout pass. Reapply once that pass has landed; otherwise the
|
|
482
|
+
// inspector absorbs the spare width (observed as ~399px at a 1280px
|
|
483
|
+
// viewport) and leaves the authored document narrower than every tool.
|
|
484
|
+
requestAnimationFrame(() => {
|
|
485
|
+
if (!api.getGroup(AGENT_GROUP_ID)) return;
|
|
486
|
+
api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
|
|
487
|
+
api.getGroup(LEFT_GROUP_ID)?.api.setSize({ width: 260 });
|
|
488
|
+
api.getGroup(RIGHT_GROUP_ID)?.api.setSize({ width: 280 });
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function ensureBottomEdgeGroup(api: DockviewApi): void {
|
|
493
|
+
if (!api.getEdgeGroup('bottom')) {
|
|
494
|
+
api.addEdgeGroup('bottom', {
|
|
495
|
+
id: BOTTOM_GROUP_ID,
|
|
496
|
+
initialSize: 260,
|
|
497
|
+
minimumSize: 120,
|
|
498
|
+
collapsedSize: 28,
|
|
499
|
+
collapsed: true,
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function ensureCenterGroup(api: DockviewApi): string {
|
|
505
|
+
// Restored layouts contain several grid groups. "First grid group" is
|
|
506
|
+
// usually the Hierarchy navigator, so resolving generically would append
|
|
507
|
+
// every newly discovered world document there and leave the real center
|
|
508
|
+
// empty. The semantic group id is the durable layout contract.
|
|
509
|
+
const existing = api.getGroup(CENTER_GROUP_ID);
|
|
510
|
+
if (existing) return existing.id;
|
|
511
|
+
return api.addGroup({ id: CENTER_GROUP_ID, direction: 'right' }).id;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function ensureAssetNavigators(api: DockviewApi): void {
|
|
515
|
+
if (!api.getPanel(ASSETS_PANEL_ID)) {
|
|
516
|
+
if (!api.getGroup(ASSETS_GROUP_ID)) {
|
|
517
|
+
const hierarchyGroup = api.getPanel(HIERARCHY_PANEL_ID)?.api.group;
|
|
518
|
+
api.addGroup({
|
|
519
|
+
id: ASSETS_GROUP_ID,
|
|
520
|
+
referenceGroup: hierarchyGroup ?? LEFT_GROUP_ID,
|
|
521
|
+
direction: 'below',
|
|
522
|
+
initialHeight: 340,
|
|
523
|
+
constraints: { minimumHeight: 180 },
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
addStaticPanel(api, {
|
|
527
|
+
id: ASSETS_PANEL_ID,
|
|
528
|
+
title: 'Assets',
|
|
529
|
+
staticKind: 'assets',
|
|
530
|
+
groupId: ASSETS_GROUP_ID,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
const assetsPanel = api.getPanel(ASSETS_PANEL_ID);
|
|
534
|
+
if (!api.getPanel(ASSET_LIBRARY_PANEL_ID)) {
|
|
535
|
+
addStaticPanel(api, {
|
|
536
|
+
id: ASSET_LIBRARY_PANEL_ID,
|
|
537
|
+
title: 'Library',
|
|
538
|
+
staticKind: 'asset-library',
|
|
539
|
+
groupId: assetsPanel?.api.group.id ?? ASSETS_GROUP_ID,
|
|
540
|
+
inactive: true,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
const libraryPanel = api.getPanel(ASSET_LIBRARY_PANEL_ID);
|
|
544
|
+
if (assetsPanel && libraryPanel && libraryPanel.api.group.id !== assetsPanel.api.group.id) {
|
|
545
|
+
libraryPanel.api.moveTo({ group: assetsPanel.api.group, index: 1, skipSetActive: true });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
function reconcileStaticPanels(api: DockviewApi): void {
|
|
550
|
+
// The scene-UI insertion catalog used to be promoted to a permanent
|
|
551
|
+
// navigator tab by the first Dockview migration. Creation now comes from
|
|
552
|
+
// the selected node's adapter through Hierarchy's one generic Add menu;
|
|
553
|
+
// discard any older layout that restores the legacy blank panel.
|
|
554
|
+
const legacyUIPalette = api.getPanel('navigator:ui-palette');
|
|
555
|
+
if (legacyUIPalette) api.removePanel(legacyUIPalette);
|
|
556
|
+
const legacyProject = api.getPanel('navigator:project');
|
|
557
|
+
if (legacyProject) api.removePanel(legacyProject);
|
|
558
|
+
const restoredAgentGroup = api.getGroup(AGENT_GROUP_ID);
|
|
559
|
+
if (restoredAgentGroup && !normalizedAgentGroups.has(restoredAgentGroup)) {
|
|
560
|
+
// `fromJSON` restores the constraints serialized by the version that
|
|
561
|
+
// wrote the layout. Refresh them so v7's width correction also applies
|
|
562
|
+
// to an already-present panel, not only to a newly created/restored one.
|
|
563
|
+
restoredAgentGroup.api.setConstraints({ minimumWidth: 300, maximumWidth: 380 });
|
|
564
|
+
restoredAgentGroup.api.setSize({ width: 360 });
|
|
565
|
+
normalizedAgentGroups.add(restoredAgentGroup);
|
|
566
|
+
requestAnimationFrame(() => {
|
|
567
|
+
api.getGroup(AGENT_GROUP_ID)?.api.setSize({ width: 360 });
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
const agentPanel = api.getPanel(AGENT_PANEL_ID);
|
|
571
|
+
if (harnessChat().collapsed) {
|
|
572
|
+
if (agentPanel) api.removePanel(agentPanel);
|
|
573
|
+
} else if (!agentPanel) {
|
|
574
|
+
ensureAgentGroup(api);
|
|
575
|
+
addStaticPanel(api, {
|
|
576
|
+
id: AGENT_PANEL_ID,
|
|
577
|
+
title: 'Agent',
|
|
578
|
+
staticKind: 'agent',
|
|
579
|
+
groupId: AGENT_GROUP_ID,
|
|
580
|
+
closeable: true,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
addStaticPanel(api, {
|
|
584
|
+
id: HIERARCHY_PANEL_ID,
|
|
585
|
+
title: 'Hierarchy',
|
|
586
|
+
staticKind: 'hierarchy',
|
|
587
|
+
groupId: LEFT_GROUP_ID,
|
|
588
|
+
});
|
|
589
|
+
// Local Assets and the external Library are permanent peer navigators below
|
|
590
|
+
// Hierarchy. Opening a Library result creates a center asset document; the
|
|
591
|
+
// acquisition surface itself never disappears from the shell.
|
|
592
|
+
ensureAssetNavigators(api);
|
|
593
|
+
addStaticPanel(api, {
|
|
594
|
+
id: INSPECTOR_PANEL_ID,
|
|
595
|
+
title: 'Inspector',
|
|
596
|
+
staticKind: 'inspector',
|
|
597
|
+
groupId: RIGHT_GROUP_ID,
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
function reconcileDocumentPanels(api: DockviewApi, centerGroupId: string): void {
|
|
602
|
+
const documents = openWorkspaceDocuments();
|
|
603
|
+
const documentIds = new Set(documents.map((item) => item.descriptor.id));
|
|
604
|
+
let structureChanged = false;
|
|
605
|
+
for (const panel of api.panels) {
|
|
606
|
+
const params = panel.api.getParameters<WorkspacePanelParams>();
|
|
607
|
+
if (params.kind === 'document' && !documentIds.has(params.documentId)) {
|
|
608
|
+
api.removePanel(panel);
|
|
609
|
+
structureChanged = true;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
let previousPanel = undefined as ReturnType<DockviewApi['getPanel']>;
|
|
613
|
+
for (const document of documents) {
|
|
614
|
+
const existing = api.getPanel(document.descriptor.id);
|
|
615
|
+
if (existing) {
|
|
616
|
+
if (existing.api.title !== document.title) existing.api.setTitle(document.title);
|
|
617
|
+
previousPanel = existing;
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
previousPanel = api.addPanel<WorkspacePanelParams>({
|
|
621
|
+
id: document.descriptor.id,
|
|
622
|
+
title: document.title,
|
|
623
|
+
component: COMPONENT_ID,
|
|
624
|
+
tabComponent: TAB_COMPONENT_ID,
|
|
625
|
+
renderer: 'always',
|
|
626
|
+
inactive: true,
|
|
627
|
+
position: previousPanel
|
|
628
|
+
? { referencePanel: previousPanel }
|
|
629
|
+
: { referenceGroup: centerGroupId },
|
|
630
|
+
params: {
|
|
631
|
+
kind: 'document',
|
|
632
|
+
documentId: document.descriptor.id,
|
|
633
|
+
closeable: document.descriptor.closeable !== false,
|
|
634
|
+
},
|
|
635
|
+
});
|
|
636
|
+
structureChanged = true;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// Reordering an already-correct rail on every availability/utility tick
|
|
640
|
+
// causes Dockview to visibly rotate active tabs despite `skipSetActive`.
|
|
641
|
+
// The family partition only needs repair after the document structure
|
|
642
|
+
// changes; ordinary reconciliation and user tab focus are read-only.
|
|
643
|
+
if (structureChanged) {
|
|
644
|
+
const orderedDocumentIds = [
|
|
645
|
+
...documents.filter((document) => isWorldSurfaceDocument(document.descriptor.id)),
|
|
646
|
+
...documents.filter((document) => !isWorldSurfaceDocument(document.descriptor.id)),
|
|
647
|
+
].map((document) => document.descriptor.id);
|
|
648
|
+
normalizeDocumentSectionOrder(api, centerGroupId, orderedDocumentIds);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// Game is a pinned player-facing anchor, not just whichever document was
|
|
652
|
+
// restored first. Keep it at index zero in its current tab group while
|
|
653
|
+
// leaving user-chosen group placement/docking intact.
|
|
654
|
+
const gamePanel = api.getPanel('workspace:game');
|
|
655
|
+
if (gamePanel && gamePanel.api.group.panels[0]?.id !== gamePanel.id) {
|
|
656
|
+
gamePanel.api.moveTo({ group: gamePanel.api.group, index: 0, skipSetActive: true });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function ensureUtilityPanel(
|
|
661
|
+
api: DockviewApi,
|
|
662
|
+
utility: WorkspaceUtilityRegistration,
|
|
663
|
+
inactive = true,
|
|
664
|
+
): void {
|
|
665
|
+
const panelId = `utility:${utility.id}`;
|
|
666
|
+
const existing = api.getPanel(panelId);
|
|
667
|
+
if (existing) {
|
|
668
|
+
if (existing.api.title !== utility.title) existing.api.setTitle(utility.title);
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
api.addPanel<WorkspacePanelParams>({
|
|
672
|
+
id: panelId,
|
|
673
|
+
title: utility.title,
|
|
674
|
+
component: COMPONENT_ID,
|
|
675
|
+
tabComponent: TAB_COMPONENT_ID,
|
|
676
|
+
renderer: 'always',
|
|
677
|
+
inactive,
|
|
678
|
+
position: { referenceGroup: BOTTOM_GROUP_ID },
|
|
679
|
+
params: {
|
|
680
|
+
kind: 'utility',
|
|
681
|
+
utilityId: utility.id,
|
|
682
|
+
closeable: utility.closeable ?? false,
|
|
683
|
+
},
|
|
684
|
+
});
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
function reconcileUtilityPanels(api: DockviewApi): void {
|
|
688
|
+
const utilities = availableWorkspaceUtilities();
|
|
689
|
+
const utilityIds = new Set(utilities.map((item) => item.id));
|
|
690
|
+
for (const panel of api.panels) {
|
|
691
|
+
const params = panel.api.getParameters<WorkspacePanelParams>();
|
|
692
|
+
if (params.kind === 'utility' && !utilityIds.has(params.utilityId)) api.removePanel(panel);
|
|
693
|
+
}
|
|
694
|
+
for (const utility of utilities) {
|
|
695
|
+
if (utility.visibleByDefault !== false) ensureUtilityPanel(api, utility);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
function reconcileWorkspace(api: DockviewApi): void {
|
|
700
|
+
const centerGroupId = ensureCenterGroup(api);
|
|
701
|
+
ensureSideGroups(api, centerGroupId);
|
|
702
|
+
ensureBottomEdgeGroup(api);
|
|
703
|
+
reconcileStaticPanels(api);
|
|
704
|
+
reconcileDocumentPanels(api, centerGroupId);
|
|
705
|
+
reconcileUtilityPanels(api);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
export function WorkspaceDock({ onOpenScene }: { onOpenScene: (assetPath: string) => void }) {
|
|
709
|
+
const store = useEditorStore();
|
|
710
|
+
const apiRef = useRef<DockviewApi | null>(null);
|
|
711
|
+
const readyCleanupRef = useRef<(() => void) | null>(null);
|
|
712
|
+
const lastSyncedDocumentRef = useRef<string | null>(null);
|
|
713
|
+
const documentVersion = useSyncExternalStore(
|
|
714
|
+
subscribeWorkspaceDocuments,
|
|
715
|
+
workspaceDocumentRegistryVersion,
|
|
716
|
+
workspaceDocumentRegistryVersion,
|
|
717
|
+
);
|
|
718
|
+
const utilityVersion = useSyncExternalStore(
|
|
719
|
+
subscribeWorkspaceUtilities,
|
|
720
|
+
workspaceUtilityRegistryVersion,
|
|
721
|
+
workspaceUtilityRegistryVersion,
|
|
722
|
+
);
|
|
723
|
+
const availabilityVersion = useSyncExternalStore(
|
|
724
|
+
subscribeAvailabilityTick,
|
|
725
|
+
availabilityTickVersion,
|
|
726
|
+
availabilityTickVersion,
|
|
727
|
+
);
|
|
728
|
+
const agentLayoutVersion = useSyncExternalStore(
|
|
729
|
+
subscribeHarnessChatLayout,
|
|
730
|
+
harnessChatLayoutVersion,
|
|
731
|
+
harnessChatLayoutVersion,
|
|
732
|
+
);
|
|
733
|
+
const activeDocumentId = activeWorkspaceDocumentId();
|
|
734
|
+
|
|
735
|
+
const resetLayout = useCallback(() => {
|
|
736
|
+
const api = apiRef.current;
|
|
737
|
+
if (!api) return;
|
|
738
|
+
api.clear();
|
|
739
|
+
// Dockview disposes panel renderers after `clear()` returns. Reusing the
|
|
740
|
+
// same panel ids in that call stack can therefore leave fresh headers
|
|
741
|
+
// attached to renderers that are about to be torn down. Rebuild on the
|
|
742
|
+
// next frame, once the cleared React panel tree has unmounted.
|
|
743
|
+
requestAnimationFrame(() => {
|
|
744
|
+
if (apiRef.current !== api) return;
|
|
745
|
+
reconcileWorkspace(api);
|
|
746
|
+
api.getEdgeGroup('bottom')?.collapse();
|
|
747
|
+
const activeDocumentId = activeWorkspaceDocumentId();
|
|
748
|
+
if (activeDocumentId) api.getPanel(activeDocumentId)?.api.setActive();
|
|
749
|
+
});
|
|
750
|
+
}, []);
|
|
751
|
+
|
|
752
|
+
useEffect(() => {
|
|
753
|
+
if (apiRef.current) reconcileWorkspace(apiRef.current);
|
|
754
|
+
}, [documentVersion, utilityVersion, availabilityVersion, agentLayoutVersion]);
|
|
755
|
+
|
|
756
|
+
useEffect(() => {
|
|
757
|
+
const api = apiRef.current;
|
|
758
|
+
if (!api || activeDocumentId === lastSyncedDocumentRef.current) return;
|
|
759
|
+
lastSyncedDocumentRef.current = activeDocumentId;
|
|
760
|
+
if (activeDocumentId) api.getPanel(activeDocumentId)?.api.setActive();
|
|
761
|
+
}, [activeDocumentId, documentVersion]);
|
|
762
|
+
|
|
763
|
+
useEffect(
|
|
764
|
+
() => () => {
|
|
765
|
+
readyCleanupRef.current?.();
|
|
766
|
+
readyCleanupRef.current = null;
|
|
767
|
+
},
|
|
768
|
+
[],
|
|
769
|
+
);
|
|
770
|
+
|
|
771
|
+
const onReady = useCallback(
|
|
772
|
+
({ api }: { api: DockviewApi }) => {
|
|
773
|
+
apiRef.current = api;
|
|
774
|
+
reconcileWorkspace(api);
|
|
775
|
+
const uninstallPersistence = installWorkspaceStatePersistence(store, api, () =>
|
|
776
|
+
reconcileWorkspace(api),
|
|
777
|
+
);
|
|
778
|
+
const initialDocumentId = activeWorkspaceDocumentId();
|
|
779
|
+
lastSyncedDocumentRef.current = initialDocumentId;
|
|
780
|
+
if (initialDocumentId) api.getPanel(initialDocumentId)?.api.setActive();
|
|
781
|
+
|
|
782
|
+
const activeDisposable = api.onDidActivePanelChange((event) => {
|
|
783
|
+
if (event.origin !== 'user' || !event.panel) return;
|
|
784
|
+
const params = event.panel.api.getParameters<WorkspacePanelParams>();
|
|
785
|
+
if (params.kind === 'document') activateWorkspaceDocument(params.documentId);
|
|
786
|
+
});
|
|
787
|
+
|
|
788
|
+
const commands: WorkspaceDockCommands = {
|
|
789
|
+
showNavigator(id) {
|
|
790
|
+
const panelId =
|
|
791
|
+
id === 'hierarchy'
|
|
792
|
+
? HIERARCHY_PANEL_ID
|
|
793
|
+
: id === 'asset-library'
|
|
794
|
+
? ASSET_LIBRARY_PANEL_ID
|
|
795
|
+
: ASSETS_PANEL_ID;
|
|
796
|
+
const panel = api.getPanel(panelId);
|
|
797
|
+
panel?.api.setActive();
|
|
798
|
+
},
|
|
799
|
+
showUtility(id) {
|
|
800
|
+
reconcileWorkspace(api);
|
|
801
|
+
const utility = availableWorkspaceUtilities().find((item) => item.id === id);
|
|
802
|
+
if (!utility) return;
|
|
803
|
+
ensureUtilityPanel(api, utility, false);
|
|
804
|
+
const panel = api.getPanel(`utility:${id}`);
|
|
805
|
+
if (!panel) return;
|
|
806
|
+
panel.api.setActive();
|
|
807
|
+
if (panel.api.location.type === 'edge') panel.api.group.api.expand();
|
|
808
|
+
},
|
|
809
|
+
toggleUtility(id) {
|
|
810
|
+
reconcileWorkspace(api);
|
|
811
|
+
const utility = availableWorkspaceUtilities().find((item) => item.id === id);
|
|
812
|
+
if (!utility) return;
|
|
813
|
+
ensureUtilityPanel(api, utility, false);
|
|
814
|
+
const panel = api.getPanel(`utility:${id}`);
|
|
815
|
+
if (!panel) return;
|
|
816
|
+
const group = panel.api.group.api;
|
|
817
|
+
if (panel.api.isActive && panel.api.location.type === 'edge' && !group.isCollapsed()) {
|
|
818
|
+
group.collapse();
|
|
819
|
+
} else {
|
|
820
|
+
panel.api.setActive();
|
|
821
|
+
if (panel.api.location.type === 'edge') group.expand();
|
|
822
|
+
}
|
|
823
|
+
},
|
|
824
|
+
showAuxiliary(id) {
|
|
825
|
+
if (id !== 'undo-history') return;
|
|
826
|
+
if (!api.getPanel(HISTORY_PANEL_ID)) {
|
|
827
|
+
addStaticPanel(api, {
|
|
828
|
+
id: HISTORY_PANEL_ID,
|
|
829
|
+
title: 'Undo History',
|
|
830
|
+
staticKind: 'history',
|
|
831
|
+
groupId: RIGHT_GROUP_ID,
|
|
832
|
+
closeable: true,
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
api.getPanel(HISTORY_PANEL_ID)?.api.setActive();
|
|
836
|
+
api.getPanel(HISTORY_PANEL_ID)?.api.group.api.expand();
|
|
837
|
+
},
|
|
838
|
+
toggleFocus() {
|
|
839
|
+
if (api.hasMaximizedGroup()) {
|
|
840
|
+
api.exitMaximizedGroup();
|
|
841
|
+
api.setEdgeGroupVisible('bottom', true);
|
|
842
|
+
} else {
|
|
843
|
+
const activeDocument = activeWorkspaceDocumentId();
|
|
844
|
+
const focusPanel =
|
|
845
|
+
(activeDocument ? api.getPanel(activeDocument) : undefined) ?? api.activePanel;
|
|
846
|
+
if (!focusPanel) return;
|
|
847
|
+
api.setEdgeGroupVisible('bottom', false);
|
|
848
|
+
api.maximizeGroup(focusPanel);
|
|
849
|
+
}
|
|
850
|
+
},
|
|
851
|
+
resetLayout,
|
|
852
|
+
};
|
|
853
|
+
const uninstallCommands = installWorkspaceDockCommands(commands);
|
|
854
|
+
readyCleanupRef.current?.();
|
|
855
|
+
readyCleanupRef.current = () => {
|
|
856
|
+
activeDisposable.dispose();
|
|
857
|
+
uninstallPersistence();
|
|
858
|
+
uninstallCommands();
|
|
859
|
+
if (apiRef.current === api) apiRef.current = null;
|
|
860
|
+
};
|
|
861
|
+
},
|
|
862
|
+
[resetLayout, store],
|
|
863
|
+
);
|
|
864
|
+
|
|
865
|
+
const components = useRef({ [COMPONENT_ID]: WorkspacePanel }).current;
|
|
866
|
+
const tabs = useRef({ [TAB_COMPONENT_ID]: WorkspaceTab }).current;
|
|
867
|
+
|
|
868
|
+
return (
|
|
869
|
+
<main
|
|
870
|
+
className="vgai-workspace-dock"
|
|
871
|
+
aria-label="Editor workspace"
|
|
872
|
+
data-testid="workspace-dock"
|
|
873
|
+
data-editor-hotkey-scope="workspace"
|
|
874
|
+
data-editor-hotkey-fallback
|
|
875
|
+
>
|
|
876
|
+
<OpenSceneContext.Provider value={onOpenScene}>
|
|
877
|
+
<DockviewReact
|
|
878
|
+
components={components}
|
|
879
|
+
tabComponents={tabs}
|
|
880
|
+
onReady={onReady}
|
|
881
|
+
theme={vgaiDockviewTheme}
|
|
882
|
+
defaultRenderer="always"
|
|
883
|
+
keyboardNavigation
|
|
884
|
+
disableFloatingGroups={false}
|
|
885
|
+
singleTabMode="fullwidth"
|
|
886
|
+
tabGroupAccent="off"
|
|
887
|
+
/>
|
|
888
|
+
</OpenSceneContext.Provider>
|
|
889
|
+
</main>
|
|
890
|
+
);
|
|
891
|
+
}
|