@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,165 @@
|
|
|
1
|
+
import { HistoryOperationError, type HistoryService } from './history-service';
|
|
2
|
+
import { sha256Hex } from './snapshot-store';
|
|
3
|
+
import type { ResourceDescriptor, ResourceDriver, ResourceKind } from './types';
|
|
4
|
+
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
|
|
8
|
+
export interface JsonHistoryResourceOptions<T> {
|
|
9
|
+
readonly history: HistoryService;
|
|
10
|
+
readonly kind: ResourceKind;
|
|
11
|
+
readonly scope: 'project' | 'session';
|
|
12
|
+
readonly location?: string;
|
|
13
|
+
readonly sessionId?: string;
|
|
14
|
+
readonly displayName?: string;
|
|
15
|
+
readonly capture: () => T;
|
|
16
|
+
readonly restore: (value: T) => void | Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* The part of the captured state that constitutes this resource's CONFLICT
|
|
19
|
+
* IDENTITY — i.e. "has something else changed this resource since the
|
|
20
|
+
* transaction was recorded?". Defaults to the whole captured value.
|
|
21
|
+
*
|
|
22
|
+
* Override it when `capture()` deliberately includes state that is NOT part
|
|
23
|
+
* of the resource's persisted content and may change on its own. The ingest
|
|
24
|
+
* overlay resource is the motivating case: its snapshot carries a mirror of
|
|
25
|
+
* every live `Object3D`'s pose (needed as the RESTORE payload), but an
|
|
26
|
+
* ingested game is a RUNNING game that animates its own objects every frame.
|
|
27
|
+
* Hashing that live simulation state as identity made every ingest
|
|
28
|
+
* transaction fail preflight with `content-conflict` a frame after it was
|
|
29
|
+
* recorded — so undo silently did nothing (issue #81). The resource's real
|
|
30
|
+
* content is the overlay it serializes to disk, which only editor edits
|
|
31
|
+
* touch.
|
|
32
|
+
*/
|
|
33
|
+
readonly conflictIdentity?: (value: T) => unknown;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Concrete JSON document/session resource backed by the project HistoryService. */
|
|
37
|
+
export class JsonHistoryResource<T> {
|
|
38
|
+
readonly descriptor: ResourceDescriptor;
|
|
39
|
+
private revision = 0;
|
|
40
|
+
private readonly unregister: () => void;
|
|
41
|
+
private disposed = false;
|
|
42
|
+
|
|
43
|
+
constructor(private readonly options: JsonHistoryResourceOptions<T>) {
|
|
44
|
+
if (options.scope === 'project') {
|
|
45
|
+
if (!options.location) throw new Error('A project JSON history resource needs a location.');
|
|
46
|
+
this.descriptor = options.history.registry.registerProject(
|
|
47
|
+
options.kind,
|
|
48
|
+
options.location,
|
|
49
|
+
options.displayName,
|
|
50
|
+
);
|
|
51
|
+
} else {
|
|
52
|
+
if (!options.sessionId)
|
|
53
|
+
throw new Error('A session JSON history resource needs a session id.');
|
|
54
|
+
this.descriptor = options.history.registry.registerSession(
|
|
55
|
+
options.kind,
|
|
56
|
+
options.sessionId,
|
|
57
|
+
options.displayName ?? options.kind,
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const driver: ResourceDriver = {
|
|
62
|
+
descriptor: this.descriptor,
|
|
63
|
+
capture: async () => {
|
|
64
|
+
const bytes = this.encode(options.capture());
|
|
65
|
+
return {
|
|
66
|
+
revision: this.revision,
|
|
67
|
+
contentType: 'application/json',
|
|
68
|
+
bytes,
|
|
69
|
+
sha256: await sha256Hex(bytes),
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
preflight: async (expected) => {
|
|
73
|
+
const bytes = this.encode(options.capture());
|
|
74
|
+
const actualSha256 = await sha256Hex(bytes);
|
|
75
|
+
// Fast path (and the behavior for every resource that does NOT narrow
|
|
76
|
+
// its identity): the full captured bytes still hash to what the
|
|
77
|
+
// transaction expects, so nothing has diverged.
|
|
78
|
+
if (actualSha256 === expected.sha256) return { ok: true };
|
|
79
|
+
// Otherwise the bytes differ — but that only counts as a CONFLICT if the
|
|
80
|
+
// part that identifies the resource differs. Compare the narrowed
|
|
81
|
+
// identity against the expected snapshot's own identity, decoded from
|
|
82
|
+
// the stored blob. (See `conflictIdentity` — an ingested game mutates
|
|
83
|
+
// the live-object mirror in its snapshot every frame on its own.)
|
|
84
|
+
if (options.conflictIdentity) {
|
|
85
|
+
const expectedValue = JSON.parse(
|
|
86
|
+
decoder.decode(options.history.snapshots.read(expected).bytes),
|
|
87
|
+
) as T;
|
|
88
|
+
const expectedIdentity = JSON.stringify(options.conflictIdentity(expectedValue));
|
|
89
|
+
const actualIdentity = JSON.stringify(options.conflictIdentity(options.capture()));
|
|
90
|
+
if (expectedIdentity === actualIdentity) return { ok: true };
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
ok: false,
|
|
94
|
+
reason: 'content-conflict',
|
|
95
|
+
actualRevision: this.revision,
|
|
96
|
+
actualSha256,
|
|
97
|
+
};
|
|
98
|
+
},
|
|
99
|
+
restore: async (snapshot) => {
|
|
100
|
+
const bytes = options.history.snapshots.read(snapshot).bytes;
|
|
101
|
+
await options.restore(JSON.parse(decoder.decode(bytes)) as T);
|
|
102
|
+
this.revision++;
|
|
103
|
+
},
|
|
104
|
+
estimateBytes: (snapshot) => snapshot.byteLength,
|
|
105
|
+
};
|
|
106
|
+
this.unregister = options.history.registerDriver(driver);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
record(label: string, before: T, after: T): Promise<boolean> {
|
|
110
|
+
if (this.disposed) return Promise.reject(new Error('JSON history resource is disposed.'));
|
|
111
|
+
this.options.history.assertCanRecordAppliedChange();
|
|
112
|
+
this.revision++;
|
|
113
|
+
return this.options.history
|
|
114
|
+
.recordAppliedTransaction(
|
|
115
|
+
{
|
|
116
|
+
label,
|
|
117
|
+
resources: [this.descriptor.key],
|
|
118
|
+
scope: this.descriptor.scope,
|
|
119
|
+
...(this.descriptor.sessionId ? { sessionId: this.descriptor.sessionId } : {}),
|
|
120
|
+
},
|
|
121
|
+
[
|
|
122
|
+
{
|
|
123
|
+
resource: this.descriptor.key,
|
|
124
|
+
beforeBytes: this.encode(before),
|
|
125
|
+
afterBytes: this.encode(after),
|
|
126
|
+
contentType: 'application/json',
|
|
127
|
+
},
|
|
128
|
+
],
|
|
129
|
+
)
|
|
130
|
+
.catch(async (error: unknown) => {
|
|
131
|
+
try {
|
|
132
|
+
await this.options.restore(structuredClone(before));
|
|
133
|
+
this.revision++;
|
|
134
|
+
} catch (compensationError) {
|
|
135
|
+
throw this.options.history.blockForCompensationFailure(label, [this.descriptor.key], {
|
|
136
|
+
operationError: error,
|
|
137
|
+
compensationError,
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
if (!(error instanceof HistoryOperationError)) {
|
|
141
|
+
this.options.history.reportAppliedChangeFailure(label, [this.descriptor.key], error);
|
|
142
|
+
}
|
|
143
|
+
throw error;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Must be called immediately before a live or overlay mutation begins. */
|
|
148
|
+
assertCanMutate(): void {
|
|
149
|
+
if (this.disposed) throw new Error('JSON history resource is disposed.');
|
|
150
|
+
this.options.history.assertCanRecordAppliedChange();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
dispose(): void {
|
|
154
|
+
if (this.disposed) return;
|
|
155
|
+
this.disposed = true;
|
|
156
|
+
this.unregister();
|
|
157
|
+
if (this.descriptor.sessionId) {
|
|
158
|
+
this.options.history.expireSession(this.descriptor.sessionId);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
private encode(value: T): Uint8Array {
|
|
163
|
+
return encoder.encode(JSON.stringify(value, null, 2));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { ResourceKey } from './types';
|
|
2
|
+
|
|
3
|
+
/** Serializes every in-process writer that touches the same authored resource. */
|
|
4
|
+
export class PersistenceCoordinator {
|
|
5
|
+
private readonly tails = new Map<ResourceKey, Promise<void>>();
|
|
6
|
+
|
|
7
|
+
run<T>(resource: ResourceKey, operation: () => Promise<T>): Promise<T> {
|
|
8
|
+
return this.runMany([resource], operation);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async runMany<T>(resources: readonly ResourceKey[], operation: () => Promise<T>): Promise<T> {
|
|
12
|
+
const keys = [...new Set(resources)].sort();
|
|
13
|
+
if (keys.length === 0) return operation();
|
|
14
|
+
|
|
15
|
+
const previous = keys.map((key) => this.tails.get(key) ?? Promise.resolve());
|
|
16
|
+
const acquired = Promise.all(previous).then(() => undefined);
|
|
17
|
+
let release!: () => void;
|
|
18
|
+
const held = new Promise<void>((resolve) => {
|
|
19
|
+
release = resolve;
|
|
20
|
+
});
|
|
21
|
+
const reservation = acquired.then(() => held);
|
|
22
|
+
for (const key of keys) this.tails.set(key, reservation);
|
|
23
|
+
|
|
24
|
+
await acquired;
|
|
25
|
+
try {
|
|
26
|
+
return await operation();
|
|
27
|
+
} finally {
|
|
28
|
+
release();
|
|
29
|
+
for (const key of keys) {
|
|
30
|
+
if (this.tails.get(key) !== reservation) continue;
|
|
31
|
+
this.tails.delete(key);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { getStorageBackend } from '../storage';
|
|
2
|
+
import type { HistoryService } from './history-service';
|
|
3
|
+
import { normalizeProjectPath } from './resource-registry';
|
|
4
|
+
import { sha256Hex } from './snapshot-store';
|
|
5
|
+
import type {
|
|
6
|
+
ResourceDescriptor,
|
|
7
|
+
ResourceDriver,
|
|
8
|
+
ResourceKey,
|
|
9
|
+
ResourceKind,
|
|
10
|
+
ResourceSnapshot,
|
|
11
|
+
} from './types';
|
|
12
|
+
|
|
13
|
+
const ABSENT = 0;
|
|
14
|
+
const PRESENT = 1;
|
|
15
|
+
|
|
16
|
+
function encodeFile(exists: boolean, bytes: Uint8Array = new Uint8Array()): Uint8Array {
|
|
17
|
+
const encoded = new Uint8Array(bytes.byteLength + 1);
|
|
18
|
+
encoded[0] = exists ? PRESENT : ABSENT;
|
|
19
|
+
if (exists) encoded.set(bytes, 1);
|
|
20
|
+
return encoded;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function decodeFile(encoded: Uint8Array): { exists: boolean; bytes: Uint8Array } {
|
|
24
|
+
const marker = encoded[0];
|
|
25
|
+
if (marker !== ABSENT && marker !== PRESENT) throw new Error('Invalid project-file snapshot.');
|
|
26
|
+
return { exists: marker === PRESENT, bytes: encoded.slice(1) };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function captureFile(
|
|
30
|
+
backend: HistoryFileBackend,
|
|
31
|
+
path: string,
|
|
32
|
+
revision: number,
|
|
33
|
+
contentType: string,
|
|
34
|
+
): Promise<ResourceSnapshot> {
|
|
35
|
+
const exists = await backend.exists(path);
|
|
36
|
+
const bytes = encodeFile(exists, exists ? await backend.readBytes(path) : undefined);
|
|
37
|
+
return { revision, contentType, bytes, sha256: await sha256Hex(bytes) };
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ManagedFile {
|
|
41
|
+
descriptor: ResourceDescriptor;
|
|
42
|
+
revision: number;
|
|
43
|
+
contentType: string;
|
|
44
|
+
listeners: Set<() => void>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ProjectFileMutation {
|
|
48
|
+
readonly path: string;
|
|
49
|
+
readonly data: string | Uint8Array | null;
|
|
50
|
+
readonly kind?: ResourceKind;
|
|
51
|
+
readonly contentType?: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Exact-byte, existence-aware history for project files reached through StorageBackend. */
|
|
55
|
+
export class ProjectFileHistory {
|
|
56
|
+
private readonly files = new Map<string, ManagedFile>();
|
|
57
|
+
private readonly listeners = new Set<(path: string) => void>();
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
private readonly history: HistoryService,
|
|
61
|
+
private readonly backend: HistoryFileBackend,
|
|
62
|
+
) {}
|
|
63
|
+
|
|
64
|
+
write(
|
|
65
|
+
path: string,
|
|
66
|
+
data: string | Uint8Array,
|
|
67
|
+
options: { label: string; kind?: ResourceKind; contentType?: string },
|
|
68
|
+
): Promise<boolean> {
|
|
69
|
+
const normalized = normalizeProjectPath(path);
|
|
70
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : Uint8Array.from(data);
|
|
71
|
+
const file = this.file(
|
|
72
|
+
normalized,
|
|
73
|
+
options.kind ?? 'project-file',
|
|
74
|
+
options.contentType ?? (typeof data === 'string' ? 'text/plain' : 'application/octet-stream'),
|
|
75
|
+
);
|
|
76
|
+
return this.history.transaction(
|
|
77
|
+
{ label: options.label, resources: [file.descriptor.key], scope: 'project' },
|
|
78
|
+
async (tx) => {
|
|
79
|
+
await tx.write(file.descriptor.key, encodeFile(true, bytes), file.contentType);
|
|
80
|
+
return true;
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
writeWithResources(
|
|
86
|
+
path: string,
|
|
87
|
+
data: string | Uint8Array,
|
|
88
|
+
options: { label: string; kind?: ResourceKind; contentType?: string },
|
|
89
|
+
additional: readonly {
|
|
90
|
+
resource: ResourceKey;
|
|
91
|
+
bytes: Uint8Array;
|
|
92
|
+
contentType: string;
|
|
93
|
+
}[],
|
|
94
|
+
): Promise<boolean> {
|
|
95
|
+
const normalized = normalizeProjectPath(path);
|
|
96
|
+
const bytes = typeof data === 'string' ? new TextEncoder().encode(data) : Uint8Array.from(data);
|
|
97
|
+
const file = this.file(
|
|
98
|
+
normalized,
|
|
99
|
+
options.kind ?? 'project-file',
|
|
100
|
+
options.contentType ?? (typeof data === 'string' ? 'text/plain' : 'application/octet-stream'),
|
|
101
|
+
);
|
|
102
|
+
return this.history.transaction(
|
|
103
|
+
{
|
|
104
|
+
label: options.label,
|
|
105
|
+
resources: [file.descriptor.key, ...additional.map((change) => change.resource)],
|
|
106
|
+
scope: 'project',
|
|
107
|
+
},
|
|
108
|
+
async (tx) => {
|
|
109
|
+
await tx.write(file.descriptor.key, encodeFile(true, bytes), file.contentType);
|
|
110
|
+
for (const change of additional) {
|
|
111
|
+
await tx.write(change.resource, change.bytes, change.contentType);
|
|
112
|
+
}
|
|
113
|
+
return true;
|
|
114
|
+
},
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
remove(
|
|
119
|
+
path: string,
|
|
120
|
+
options: { label: string; kind?: ResourceKind; contentType?: string },
|
|
121
|
+
): Promise<boolean> {
|
|
122
|
+
const normalized = normalizeProjectPath(path);
|
|
123
|
+
const file = this.file(
|
|
124
|
+
normalized,
|
|
125
|
+
options.kind ?? 'project-file',
|
|
126
|
+
options.contentType ?? 'application/octet-stream',
|
|
127
|
+
);
|
|
128
|
+
return this.history.transaction(
|
|
129
|
+
{ label: options.label, resources: [file.descriptor.key], scope: 'project' },
|
|
130
|
+
async (tx) => {
|
|
131
|
+
await tx.write(file.descriptor.key, encodeFile(false), file.contentType);
|
|
132
|
+
return true;
|
|
133
|
+
},
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Apply a logical multi-file operation as one failure-atomic undo step. */
|
|
138
|
+
mutate(changes: readonly ProjectFileMutation[], options: { label: string }): Promise<boolean> {
|
|
139
|
+
const staged = changes.map((change) => {
|
|
140
|
+
const path = normalizeProjectPath(change.path);
|
|
141
|
+
const contentType = change.contentType ?? 'application/octet-stream';
|
|
142
|
+
return {
|
|
143
|
+
change,
|
|
144
|
+
file: this.file(path, change.kind ?? 'project-file', contentType),
|
|
145
|
+
contentType,
|
|
146
|
+
};
|
|
147
|
+
});
|
|
148
|
+
const uniqueResources = new Set(staged.map(({ file }) => file.descriptor.key));
|
|
149
|
+
if (uniqueResources.size !== staged.length) {
|
|
150
|
+
throw new Error('A project-file mutation cannot write the same path twice.');
|
|
151
|
+
}
|
|
152
|
+
return this.history.transaction(
|
|
153
|
+
{ label: options.label, resources: [...uniqueResources], scope: 'project' },
|
|
154
|
+
async (tx) => {
|
|
155
|
+
for (const { change, file, contentType } of staged) {
|
|
156
|
+
const bytes =
|
|
157
|
+
change.data === null
|
|
158
|
+
? encodeFile(false)
|
|
159
|
+
: encodeFile(
|
|
160
|
+
true,
|
|
161
|
+
typeof change.data === 'string'
|
|
162
|
+
? new TextEncoder().encode(change.data)
|
|
163
|
+
: Uint8Array.from(change.data),
|
|
164
|
+
);
|
|
165
|
+
await tx.write(file.descriptor.key, bytes, contentType);
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
subscribe(path: string, listener: () => void): () => void {
|
|
173
|
+
const normalized = normalizeProjectPath(path);
|
|
174
|
+
const file = this.files.get(normalized);
|
|
175
|
+
if (!file) throw new Error(`Project file "${normalized}" is not registered with history.`);
|
|
176
|
+
file.listeners.add(listener);
|
|
177
|
+
return () => file.listeners.delete(listener);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
subscribeAll(listener: (path: string) => void): () => void {
|
|
181
|
+
this.listeners.add(listener);
|
|
182
|
+
return () => this.listeners.delete(listener);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
private file(path: string, kind: ResourceKind, contentType: string): ManagedFile {
|
|
186
|
+
const existing = this.files.get(path);
|
|
187
|
+
if (existing) {
|
|
188
|
+
if (existing.descriptor.kind !== kind) {
|
|
189
|
+
throw new Error(
|
|
190
|
+
`Project file "${path}" is already registered as ${existing.descriptor.kind}.`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
existing.contentType = contentType;
|
|
194
|
+
return existing;
|
|
195
|
+
}
|
|
196
|
+
const descriptor = this.history.registry.registerProject(kind, path);
|
|
197
|
+
const file: ManagedFile = {
|
|
198
|
+
descriptor,
|
|
199
|
+
revision: 0,
|
|
200
|
+
contentType,
|
|
201
|
+
listeners: new Set(),
|
|
202
|
+
};
|
|
203
|
+
const driver: ResourceDriver = {
|
|
204
|
+
descriptor,
|
|
205
|
+
capture: () => captureFile(this.backend, path, file.revision, file.contentType),
|
|
206
|
+
preflight: async (expected) => {
|
|
207
|
+
const current = await captureFile(this.backend, path, file.revision, file.contentType);
|
|
208
|
+
return current.sha256 === expected.sha256
|
|
209
|
+
? { ok: true }
|
|
210
|
+
: {
|
|
211
|
+
ok: false,
|
|
212
|
+
reason: 'content-conflict',
|
|
213
|
+
actualRevision: file.revision,
|
|
214
|
+
actualSha256: current.sha256,
|
|
215
|
+
};
|
|
216
|
+
},
|
|
217
|
+
restore: async (snapshot) => {
|
|
218
|
+
const decoded = decodeFile(this.history.snapshots.read(snapshot).bytes);
|
|
219
|
+
if (decoded.exists) await this.backend.write(path, decoded.bytes);
|
|
220
|
+
else await this.backend.remove(path);
|
|
221
|
+
file.revision++;
|
|
222
|
+
for (const listener of file.listeners) listener();
|
|
223
|
+
for (const listener of this.listeners) listener(path);
|
|
224
|
+
},
|
|
225
|
+
estimateBytes: (snapshot) => snapshot.byteLength,
|
|
226
|
+
};
|
|
227
|
+
this.history.registerDriver(driver);
|
|
228
|
+
this.files.set(path, file);
|
|
229
|
+
return file;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export interface HistoryFileBackend {
|
|
234
|
+
readonly id: string;
|
|
235
|
+
readBytes(path: string): Promise<Uint8Array>;
|
|
236
|
+
write(path: string, data: string | Uint8Array): Promise<void>;
|
|
237
|
+
exists(path: string): Promise<boolean>;
|
|
238
|
+
remove(path: string): Promise<void>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const managers = new WeakMap<
|
|
242
|
+
HistoryService,
|
|
243
|
+
Map<string, { backend: HistoryFileBackend; manager: ProjectFileHistory }>
|
|
244
|
+
>();
|
|
245
|
+
|
|
246
|
+
export function getProjectFileHistory(
|
|
247
|
+
history: HistoryService,
|
|
248
|
+
backend: HistoryFileBackend = getStorageBackend(),
|
|
249
|
+
namespace = 'public',
|
|
250
|
+
): ProjectFileHistory {
|
|
251
|
+
let byNamespace = managers.get(history);
|
|
252
|
+
if (!byNamespace) {
|
|
253
|
+
byNamespace = new Map();
|
|
254
|
+
managers.set(history, byNamespace);
|
|
255
|
+
}
|
|
256
|
+
const existing = byNamespace.get(namespace);
|
|
257
|
+
if (existing) {
|
|
258
|
+
if (existing.backend !== backend) {
|
|
259
|
+
throw new Error('The project storage backend changed without creating a new editor session.');
|
|
260
|
+
}
|
|
261
|
+
return existing.manager;
|
|
262
|
+
}
|
|
263
|
+
const manager = new ProjectFileHistory(history, backend);
|
|
264
|
+
byNamespace.set(namespace, { backend, manager });
|
|
265
|
+
return manager;
|
|
266
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { getEditorMode } from '../editor-mode';
|
|
2
|
+
import { getStorageBackend } from '../storage';
|
|
3
|
+
import type { HistoryFileBackend } from './project-file-history';
|
|
4
|
+
|
|
5
|
+
class ScopedHttpHistoryBackend implements HistoryFileBackend {
|
|
6
|
+
constructor(
|
|
7
|
+
readonly id: string,
|
|
8
|
+
private readonly endpoint: string,
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
async readBytes(path: string): Promise<Uint8Array> {
|
|
12
|
+
const response = await fetch(`${this.endpoint}?${new URLSearchParams({ path })}`);
|
|
13
|
+
if (!response.ok) throw new Error(`${this.id}.readBytes '${path}': HTTP ${response.status}`);
|
|
14
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async write(path: string, data: string | Uint8Array): Promise<void> {
|
|
18
|
+
const content = typeof data === 'string' ? data : new TextDecoder().decode(data);
|
|
19
|
+
const response = await fetch(this.endpoint, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: { 'Content-Type': 'application/json' },
|
|
22
|
+
body: JSON.stringify({ path, content }),
|
|
23
|
+
});
|
|
24
|
+
if (!response.ok) throw new Error(`${this.id}.write '${path}': HTTP ${response.status}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async exists(path: string): Promise<boolean> {
|
|
28
|
+
const response = await fetch(`${this.endpoint}?${new URLSearchParams({ path })}`);
|
|
29
|
+
if (response.status === 404) return false;
|
|
30
|
+
if (!response.ok) throw new Error(`${this.id}.exists '${path}': HTTP ${response.status}`);
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async remove(path: string): Promise<void> {
|
|
35
|
+
const response = await fetch(this.endpoint, {
|
|
36
|
+
method: 'DELETE',
|
|
37
|
+
headers: { 'Content-Type': 'application/json' },
|
|
38
|
+
body: JSON.stringify({ path }),
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) throw new Error(`${this.id}.remove '${path}': HTTP ${response.status}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const dataHttp = new ScopedHttpHistoryBackend('project-data-http', '/__editor/data-file');
|
|
45
|
+
const manifestHttp = new ScopedHttpHistoryBackend('project-manifest-http', '/__editor/manifest');
|
|
46
|
+
|
|
47
|
+
export function getDataHistoryBackend(): HistoryFileBackend {
|
|
48
|
+
return getEditorMode() === 'browser' ? getStorageBackend() : dataHttp;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function getManifestHistoryBackend(): HistoryFileBackend {
|
|
52
|
+
return getEditorMode() === 'browser' ? getStorageBackend() : manifestHttp;
|
|
53
|
+
}
|