@volter/editor-game 0.5.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/NOTICE +23 -0
- package/contributions/asset-budget-asset.menu.ts +26 -0
- package/contributions/asset-budget.action.ts +18 -0
- package/contributions/asset-budget.document.tsx +21 -0
- package/contributions/asset-budget.menu.ts +22 -0
- package/contributions/audio-unlock.service.ts +17 -0
- package/contributions/audio.utility.tsx +15 -0
- package/contributions/autoplay.service.ts +48 -0
- package/contributions/bridge.command.ts +172 -0
- package/contributions/build-profiles.document.tsx +21 -0
- package/contributions/build-progress.status.tsx +45 -0
- package/contributions/build.action.ts +26 -0
- package/contributions/build.command.ts +27 -0
- package/contributions/build.header.tsx +37 -0
- package/contributions/build.menu.ts +31 -0
- package/contributions/build.service.ts +31 -0
- package/contributions/connection.status.tsx +43 -0
- package/contributions/coverage.service.ts +118 -0
- package/contributions/edit-mode-audio.service.ts +23 -0
- package/contributions/edit-mode-networking.service.ts +30 -0
- package/contributions/game-document.service.ts +25 -0
- package/contributions/game-eval.command.ts +210 -0
- package/contributions/game.layout.ts +19 -0
- package/contributions/gameplay.command.ts +255 -0
- package/contributions/generation.service.ts +96 -0
- package/contributions/generations.status.tsx +53 -0
- package/contributions/ingest.service.ts +80 -0
- package/contributions/instances.command.ts +75 -0
- package/contributions/navmesh.menu.ts +39 -0
- package/contributions/navmesh.service.ts +19 -0
- package/contributions/network.utility.tsx +17 -0
- package/contributions/play.command.ts +355 -0
- package/contributions/profiler.action.ts +16 -0
- package/contributions/profiler.menu.ts +22 -0
- package/contributions/profiler.utility.tsx +17 -0
- package/contributions/react/component-board.service.ts +21 -0
- package/contributions/react/design-time-mount.service.ts +60 -0
- package/contributions/react/pasteboard.action.ts +41 -0
- package/contributions/react/react-inspector.service.ts +85 -0
- package/contributions/react/story-documents.service.ts +44 -0
- package/contributions/scene-document.service.ts +32 -0
- package/contributions/state-watch.action.ts +17 -0
- package/contributions/state-watch.menu.ts +23 -0
- package/contributions/state-watch.utility.tsx +20 -0
- package/contributions/team-playtest.service.ts +124 -0
- package/contributions/three/camera-runtime.inspector.tsx +34 -0
- package/contributions/three/component-board.service.ts +24 -0
- package/contributions/three/component-verbs.command.ts +110 -0
- package/contributions/three/component-verbs.service.ts +92 -0
- package/contributions/three/constraints.inspector.tsx +34 -0
- package/contributions/three/model-asset-sections.service.ts +26 -0
- package/contributions/three/reflection-probe-capture.inspector.tsx +32 -0
- package/contributions/three/story-documents.service.ts +31 -0
- package/contributions/three/three-authoring.service.ts +66 -0
- package/contributions/transport.header.tsx +19 -0
- package/contributions/xstate-behavior.action.ts +42 -0
- package/contributions/xstate-behavior.document.tsx +73 -0
- package/contributions/xstate-behavior.inspector.tsx +28 -0
- package/contributions/xstate-behavior.menu.ts +23 -0
- package/package.json +144 -0
- package/src/asset-budget/AssetBudgetPanel.tsx +1172 -0
- package/src/asset-budget/asset-budget-model.ts +799 -0
- package/src/asset-budget/basis-encoder.ts +165 -0
- package/src/asset-budget/gltf-io.ts +154 -0
- package/src/asset-budget/gltf-optimize.ts +384 -0
- package/src/asset-budget/image-dims.ts +78 -0
- package/src/asset-budget/optimize-apply.ts +221 -0
- package/src/audio/AudioDebuggerPanel.tsx +457 -0
- package/src/audio/audio-debugger-model.ts +87 -0
- package/src/bridge/call.ts +60 -0
- package/src/bridge/dispatch.ts +459 -0
- package/src/bridge/live-frames.ts +25 -0
- package/src/bridge/screenshot.ts +316 -0
- package/src/build/BuildProfilesPanel.tsx +476 -0
- package/src/build/build-session.ts +247 -0
- package/src/build/format-bytes.ts +14 -0
- package/src/command-results.ts +36 -0
- package/src/coverage/live-authoring-surface.ts +30 -0
- package/src/coverage/live-project-verbs.ts +162 -0
- package/src/coverage/native-system-coverage.ts +143 -0
- package/src/coverage/root-coverage.ts +79 -0
- package/src/coverage/session-coverage.ts +193 -0
- package/src/design-system-stories/ApplicationChrome.stories.tsx +100 -0
- package/src/design-system-stories/InspectorNarrowBodies.stories.tsx +406 -0
- package/src/edit-mode/edit-mode-audio.ts +56 -0
- package/src/edit-mode/edit-mode-networking.ts +112 -0
- package/src/game-document/DevicePresetPicker.tsx +84 -0
- package/src/game-document/GameCaptureFrameButton.tsx +45 -0
- package/src/game-document/GameDocument.tsx +215 -0
- package/src/game-document/GamePanel.tsx +662 -0
- package/src/game-document/InstanceInspectorPicker.tsx +183 -0
- package/src/game-document/crowd-debug.ts +183 -0
- package/src/game-document/device-preview.ts +336 -0
- package/src/game-document/game-view-store.ts +107 -0
- package/src/game-document/physics-debug.ts +187 -0
- package/src/generation/GenerationActivity.tsx +421 -0
- package/src/generation/GenerationGallery.css +231 -0
- package/src/generation/generation-documents.tsx +338 -0
- package/src/generation/generation-jobs.ts +128 -0
- package/src/generation/generation-presentation.ts +257 -0
- package/src/host/adapter-reach.ts +445 -0
- package/src/host/adapter-runtime-bindings.ts +303 -0
- package/src/host/after-paint.ts +112 -0
- package/src/host/api/configurations.ts +66 -0
- package/src/host/authoring/babylon-authoring-adapter.ts +703 -0
- package/src/host/authoring/canvas-runtime-recognition.ts +50 -0
- package/src/host/authoring/contract-hierarchy-authoring.ts +144 -0
- package/src/host/authoring/contract-scenes-stories.ts +204 -0
- package/src/host/authoring/creation-site-related.ts +55 -0
- package/src/host/authoring/ephemeral-persistence.ts +29 -0
- package/src/host/authoring/gesture-persist.ts +84 -0
- package/src/host/authoring/ingest-data-writer.ts +232 -0
- package/src/host/authoring/ingest-source-persistence.ts +826 -0
- package/src/host/authoring/mount-isolated-pixi-screen.ts +250 -0
- package/src/host/authoring/mounted-authoring.ts +41 -0
- package/src/host/authoring/owned-pixi-ticker-listeners.ts +96 -0
- package/src/host/authoring/phaser-live-authoring-adapter.ts +265 -0
- package/src/host/authoring/pixi-authoring-adapter.ts +1554 -0
- package/src/host/authoring/pixi-creation-site-write-target.ts +60 -0
- package/src/host/authoring/pixi-isolation-assets.ts +25 -0
- package/src/host/authoring/pixi-live-write-target.ts +979 -0
- package/src/host/authoring/pixi-source-identity.ts +141 -0
- package/src/host/authoring/pixi-still-presentation.ts +71 -0
- package/src/host/authoring/pixi-structure-history.ts +237 -0
- package/src/host/authoring/pixi-transform-channels.ts +205 -0
- package/src/host/authoring/selection-remount-handoff.ts +23 -0
- package/src/host/authoring/source-persistence-backend.ts +373 -0
- package/src/host/authoring/source-refresh-revisions.ts +81 -0
- package/src/host/authoring/struct-write-pipe.ts +143 -0
- package/src/host/auto-frame-window.ts +89 -0
- package/src/host/binding-resolver.ts +393 -0
- package/src/host/browser-transpile.ts +631 -0
- package/src/host/canvas-entry-runtime.ts +95 -0
- package/src/host/components/CameraAuthoringOverlay.tsx +216 -0
- package/src/host/components/HeaderTelemetry.tsx +341 -0
- package/src/host/components/PixiIsolationSceneContent.tsx +280 -0
- package/src/host/components/ResolutionPicker.tsx +89 -0
- package/src/host/components/ThreeIsolationSceneContent.tsx +180 -0
- package/src/host/components/frame-debugger-model.ts +579 -0
- package/src/host/components/header-telemetry-model.ts +74 -0
- package/src/host/components/scene-document.tsx +447 -0
- package/src/host/components/utility-view-state.ts +87 -0
- package/src/host/components/world-root-stage-binding.tsx +133 -0
- package/src/host/components/world-root-stage.ts +1046 -0
- package/src/host/coverage/authoring-read-probe.ts +583 -0
- package/src/host/coverage/capability-coverage.ts +1587 -0
- package/src/host/coverage/coverage-accounting.ts +261 -0
- package/src/host/coverage/game-contract-seam-evidence.ts +82 -0
- package/src/host/coverage/project-verb-coverage.ts +148 -0
- package/src/host/coverage/system-adapter-coverage.ts +373 -0
- package/src/host/design-system-stories/StoryLayout.tsx +104 -0
- package/src/host/design-system-stories/fixtures/authoring.ts +247 -0
- package/src/host/design-system-stories/fixtures/editor-runtime.tsx +115 -0
- package/src/host/document-preview-three.ts +110 -0
- package/src/host/entry-adjudication.ts +89 -0
- package/src/host/game-location-guard.ts +150 -0
- package/src/host/game-module-access.ts +196 -0
- package/src/host/game-realm-page.ts +358 -0
- package/src/host/game-realm-reclaim.ts +55 -0
- package/src/host/game-realm-storage.ts +104 -0
- package/src/host/gameplay-export.ts +288 -0
- package/src/host/gameplay-recording.ts +717 -0
- package/src/host/gated-globals.ts +1511 -0
- package/src/host/history/json-history-resource.ts +254 -0
- package/src/host/ingest/registry.ts +261 -0
- package/src/host/instance-extract-actions.ts +120 -0
- package/src/host/instance-fork-actions.ts +120 -0
- package/src/host/play-control-hook.ts +26 -0
- package/src/host/playwright-shim.ts +479 -0
- package/src/host/projection/dom.ts +295 -0
- package/src/host/projection/pixi.ts +288 -0
- package/src/host/r3f-entry-runtime.ts +77 -0
- package/src/host/react-mount-runtime.ts +162 -0
- package/src/host/realm-services.ts +148 -0
- package/src/host/recording-preview.ts +106 -0
- package/src/host/roots/module-root.ts +203 -0
- package/src/host/roots/react-root.ts +181 -0
- package/src/host/same-realm-loop-gate.ts +544 -0
- package/src/host/scene-view-drawability.ts +69 -0
- package/src/host/sdk/tools.ts +31 -0
- package/src/host/served-bundle-runtime-modules.ts +331 -0
- package/src/host/server-log-bridge.ts +60 -0
- package/src/host/staged-projects.ts +24 -0
- package/src/host/stories/mounted-story-viewport-source.ts +64 -0
- package/src/host/stories/story-arg-descriptors.ts +50 -0
- package/src/host/stories/story-media-presence.ts +152 -0
- package/src/host/surface-content.ts +87 -0
- package/src/host/take-named-export.ts +23 -0
- package/src/host/three-ingest-runtime.ts +76 -0
- package/src/host/types-fastnoise-lite.d.ts +7 -0
- package/src/host/types-mikktspace.d.ts +20 -0
- package/src/host/types-troika-three-text.d.ts +7 -0
- package/src/host/use-active-performance-source.ts +53 -0
- package/src/host/viewport-pose-memory.ts +48 -0
- package/src/host/viewport-root-presentation.ts +40 -0
- package/src/ingest/active-ingest.ts +251 -0
- package/src/ingest/active-scene-navigation.ts +43 -0
- package/src/ingest/authoring/ingest-dom-surface-authoring.ts +191 -0
- package/src/ingest/authoring/ingest-root-adapter.ts +897 -0
- package/src/ingest/capture-wait-report.ts +122 -0
- package/src/ingest/deferred-ingest-play.ts +216 -0
- package/src/ingest/deferred-ingest-session.ts +23 -0
- package/src/ingest/discovery-public-ingest.ts +242 -0
- package/src/ingest/dom-stub-mark.ts +7 -0
- package/src/ingest/entry-load.ts +56 -0
- package/src/ingest/game-contract-realm.ts +34 -0
- package/src/ingest/game-pointer-lock.ts +89 -0
- package/src/ingest/held-scene-repaint.ts +73 -0
- package/src/ingest/host-surface-box.ts +174 -0
- package/src/ingest/ingest-boot-viewport.ts +54 -0
- package/src/ingest/ingest-canvas-scene-document.tsx +177 -0
- package/src/ingest/ingest-canvas-scene.ts +50 -0
- package/src/ingest/ingest-evidence-hook.ts +131 -0
- package/src/ingest/ingest-frame-snapshot.ts +183 -0
- package/src/ingest/ingest-play-commands.ts +134 -0
- package/src/ingest/ingest-play-control.ts +299 -0
- package/src/ingest/ingest-render-debug.ts +320 -0
- package/src/ingest/ingest-siblings.ts +487 -0
- package/src/ingest/ingest-status.ts +62 -0
- package/src/ingest/live-ingest-facet.ts +46 -0
- package/src/ingest/module-mode.ts +229 -0
- package/src/ingest/mount-canvas-ingest-root.ts +899 -0
- package/src/ingest/mount-coverage.ts +296 -0
- package/src/ingest/mount-dom-ingest-root.ts +282 -0
- package/src/ingest/mount-ingest-root.ts +744 -0
- package/src/ingest/mount-three-ingest-root.ts +366 -0
- package/src/ingest/resolve-canvas.ts +47 -0
- package/src/ingest/resolve-three.ts +123 -0
- package/src/ingest/served-bundle.ts +109 -0
- package/src/ingest/served-html-boot.ts +292 -0
- package/src/ingest/surface-canvas.ts +84 -0
- package/src/ingest/surface-dom.ts +84 -0
- package/src/ingest/surface-three.ts +128 -0
- package/src/ingest/types.ts +76 -0
- package/src/ingest/unmount-ingest-root.ts +224 -0
- package/src/navmesh/navmesh-actions.ts +27 -0
- package/src/navmesh/navmesh-handler.ts +237 -0
- package/src/navmesh/navmesh-workflow-store.ts +78 -0
- package/src/network/NetworkInspectorPanel.tsx +644 -0
- package/src/network/network-inspector-model.ts +225 -0
- package/src/play/play-boot-stall.ts +118 -0
- package/src/play/play-log-events.ts +26 -0
- package/src/play/play-mode.ts +2553 -0
- package/src/play/play-recording.ts +335 -0
- package/src/play/react-play-live-authoring.ts +165 -0
- package/src/play-bar/PlayBar.tsx +488 -0
- package/src/play-bar/PlayerCountPicker.tsx +100 -0
- package/src/profiler/FrameDebuggerPanel.tsx +458 -0
- package/src/profiler/PerformancePanel.tsx +1068 -0
- package/src/profiler/ProfilerPanel.tsx +53 -0
- package/src/profiler/frame-debugger-store.ts +97 -0
- package/src/profiler/main-thread-busy.ts +90 -0
- package/src/react/design-time-react-mount.ts +782 -0
- package/src/react/dom-authoring-adapter.ts +764 -0
- package/src/react/pasteboard-materialize.ts +154 -0
- package/src/react/react-inspector-section.tsx +2496 -0
- package/src/react/react-world-authoring-adapter.ts +3795 -0
- package/src/react/story-documents/story-args-section.ts +19 -0
- package/src/react/story-documents/story-documents.tsx +1380 -0
- package/src/react/story-paint-bounds.ts +94 -0
- package/src/react/ui-board-document.tsx +101 -0
- package/src/react/ui-board-title.ts +9 -0
- package/src/react/ui-component-board.ts +72 -0
- package/src/services/audio-pose-guard.ts +81 -0
- package/src/services/game-audio-unlock.ts +48 -0
- package/src/state-watch/StateWatchPanel.tsx +535 -0
- package/src/three/authoring/camera-runtime-inspector-section.tsx +135 -0
- package/src/three/authoring/constraint-inspector-section.tsx +189 -0
- package/src/three/authoring/design-time-renderer.ts +189 -0
- package/src/three/authoring/model-asset-inspector-section.css +41 -0
- package/src/three/authoring/model-asset-inspector-section.tsx +869 -0
- package/src/three/authoring/oid-source-persistence.ts +557 -0
- package/src/three/authoring/r3f-design-session.ts +1174 -0
- package/src/three/authoring/r3f-source-authoring-adapter.ts +5949 -0
- package/src/three/authoring/reflection-probe-inspector-section.tsx +76 -0
- package/src/three/authoring/spatial-audio-handles.ts +215 -0
- package/src/three/authoring/spatial-collider-handles.ts +229 -0
- package/src/three/authoring/spatial-joint-handles.ts +114 -0
- package/src/three/authoring/spatial-light-handles.ts +178 -0
- package/src/three/authoring/spatial-lod-handles.ts +93 -0
- package/src/three/authoring/spatial-particle-handles.ts +368 -0
- package/src/three/authoring/three-authoring-adapter.ts +1953 -0
- package/src/three/authoring/three-scene-identity.ts +19 -0
- package/src/three/authoring/three-spatial-handles.ts +161 -0
- package/src/three/authoring/typed-three-inspector.ts +528 -0
- package/src/three/component-verbs/extract-menu.ts +71 -0
- package/src/three/component-verbs/fork-menu.ts +78 -0
- package/src/three/component-verbs/internals-menu.ts +91 -0
- package/src/three/story-documents/three-story-documents.tsx +607 -0
- package/src/three/three-board/ThreeBoardDocument.tsx +888 -0
- package/src/three/three-board/board-framing.ts +412 -0
- package/src/three/three-board/board-layout.ts +401 -0
- package/src/three/three-board/board-scene.ts +901 -0
- package/src/three/three-board/three-component-board.ts +62 -0
- package/src/xstate/XStateBehaviorSection.tsx +130 -0
- package/src/xstate/XStateMachineInspector.tsx +566 -0
- package/src/xstate/character-animation-machine.fixture.ts +74 -0
- package/src/xstate/live-behaviors.ts +106 -0
- package/src/xstate/use-live-actor-state.ts +44 -0
- package/src/xstate/xstate-graph.ts +235 -0
- package/src/xstate/xstate-layout.ts +76 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canvas implementation of an adapter-declared isolation scene document.
|
|
3
|
+
* Loaded only when a Canvas scene document is actually rendered; the generic
|
|
4
|
+
* scene table registry must not make a Three project evaluate Pixi authoring.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { isolationImportUrl } from '@volter/editor-sdk/session/project-module-url';
|
|
8
|
+
import { themeVars } from '@volter/editor-sdk/widgets';
|
|
9
|
+
import { createPhysics2DRegistry } from '@volter/game-runtime/pixi/physics-registry';
|
|
10
|
+
import { createPhysicsAdapter2D } from '@volter/game-runtime/pixi/system-adapters';
|
|
11
|
+
import { useEffect, useRef, useState } from 'react';
|
|
12
|
+
import {
|
|
13
|
+
type IsolatedPixiScreenCtor,
|
|
14
|
+
type MountedIsolatedPixiScreen,
|
|
15
|
+
mountIsolatedPixiScreen,
|
|
16
|
+
} from '../authoring/mount-isolated-pixi-screen';
|
|
17
|
+
import { PixiAuthoringAdapter } from '../authoring/pixi-authoring-adapter';
|
|
18
|
+
import { createCreationSiteCanvasWriteTarget } from '../authoring/pixi-creation-site-write-target';
|
|
19
|
+
import { installCanvasSceneNavigation } from '@volter/editor-core/authoring/react-canvas-navigation';
|
|
20
|
+
import { createRootViewController } from '@volter/editor-core/authoring/world-pan-state';
|
|
21
|
+
import { capturePixiDisplayObjectThumbnail } from '@volter/editor-core/canvas-preview-frames';
|
|
22
|
+
import type { DocumentPreviewCaptureRequest } from '@volter/editor-core/document-preview-source';
|
|
23
|
+
import { useEditorStore } from '@volter/editor-core/editor-runtime';
|
|
24
|
+
import { authoringJournal } from '../history/json-history-resource';
|
|
25
|
+
import { setActiveScope } from '@volter/editor-core/hotkeys';
|
|
26
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
27
|
+
import { takeNamedExport } from '../take-named-export';
|
|
28
|
+
import { registerWorkspaceDocumentSelection } from '@volter/editor-core/workspace-document-registry';
|
|
29
|
+
import {
|
|
30
|
+
CANVAS_SCENE_BACKGROUND,
|
|
31
|
+
CanvasSceneBackdrop,
|
|
32
|
+
CanvasSceneControls,
|
|
33
|
+
} from '@volter/editor-core/components/CanvasSceneViewport';
|
|
34
|
+
import { RootSelectionOverlay } from '@volter/editor-core/components/RootSelectionOverlay';
|
|
35
|
+
|
|
36
|
+
export interface PixiIsolationSceneContentProps {
|
|
37
|
+
readonly active: boolean;
|
|
38
|
+
readonly documentId: string;
|
|
39
|
+
readonly path: string;
|
|
40
|
+
readonly exportName: string | undefined;
|
|
41
|
+
readonly isolationSetup?: { readonly path: string; readonly export?: string };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function loadSiblingAssetInitializer(
|
|
45
|
+
path: string,
|
|
46
|
+
): Promise<{ initAssets?: () => Promise<void>; assetSourcePath?: string }> {
|
|
47
|
+
const assetsPath = path.replace(/\/screens\/[^/]+$/, '/assets.ts');
|
|
48
|
+
if (assetsPath === path) return {};
|
|
49
|
+
try {
|
|
50
|
+
const project = getCurrentProject();
|
|
51
|
+
if (!project) return {};
|
|
52
|
+
const assets = (await import(
|
|
53
|
+
/* @vite-ignore */ isolationImportUrl(project.rootPath, assetsPath)
|
|
54
|
+
)) as { initAssets?: () => Promise<void> };
|
|
55
|
+
return typeof assets.initAssets === 'function'
|
|
56
|
+
? { initAssets: assets.initAssets, assetSourcePath: assetsPath }
|
|
57
|
+
: {};
|
|
58
|
+
} catch {
|
|
59
|
+
// A screen with no sibling assets.ts still mounts; missing textures surface
|
|
60
|
+
// as the constructor's own error.
|
|
61
|
+
return {};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function PixiIsolationSceneContent({
|
|
66
|
+
active,
|
|
67
|
+
documentId,
|
|
68
|
+
path,
|
|
69
|
+
exportName,
|
|
70
|
+
isolationSetup: isolationSetupSpec,
|
|
71
|
+
}: PixiIsolationSceneContentProps) {
|
|
72
|
+
const store = useEditorStore();
|
|
73
|
+
const containerRef = useRef<HTMLDivElement>(null);
|
|
74
|
+
const viewRef = useRef(createRootViewController());
|
|
75
|
+
const view = viewRef.current;
|
|
76
|
+
const [error, setError] = useState<string | null>(null);
|
|
77
|
+
const [ready, setReady] = useState(false);
|
|
78
|
+
const [adapter, setAdapter] = useState<PixiAuthoringAdapter | null>(null);
|
|
79
|
+
const adapterRef = useRef<PixiAuthoringAdapter | null>(null);
|
|
80
|
+
const mountedRef = useRef<MountedIsolatedPixiScreen | null>(null);
|
|
81
|
+
const [visited, setVisited] = useState(active);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (active) setVisited(true);
|
|
84
|
+
}, [active]);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
const container = containerRef.current;
|
|
88
|
+
if (!container) return;
|
|
89
|
+
return installCanvasSceneNavigation(container, view);
|
|
90
|
+
}, [view]);
|
|
91
|
+
|
|
92
|
+
useEffect(() => {
|
|
93
|
+
if (!adapter) return;
|
|
94
|
+
return registerWorkspaceDocumentSelection(documentId, () => ({
|
|
95
|
+
adapter,
|
|
96
|
+
nodeId: [...store.selectedEntityIds].find((id) => adapter.hierarchy.node(id)) ?? null,
|
|
97
|
+
}));
|
|
98
|
+
}, [adapter, documentId, store]);
|
|
99
|
+
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
if (!visited) return;
|
|
102
|
+
const layer = containerRef.current;
|
|
103
|
+
if (!layer) return;
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
setError(null);
|
|
106
|
+
setReady(false);
|
|
107
|
+
setAdapter(null);
|
|
108
|
+
void loadIsolatedSceneComponent(path, exportName)
|
|
109
|
+
.then(async (Ctor) => {
|
|
110
|
+
const assets = await loadSiblingAssetInitializer(path);
|
|
111
|
+
const isolationSetup = isolationSetupSpec
|
|
112
|
+
? await loadIsolationSetup(isolationSetupSpec.path, isolationSetupSpec.export)
|
|
113
|
+
: undefined;
|
|
114
|
+
return mountIsolatedPixiScreen(Ctor, {
|
|
115
|
+
...assets,
|
|
116
|
+
...(isolationSetup ? { isolationSetup } : {}),
|
|
117
|
+
view,
|
|
118
|
+
layer,
|
|
119
|
+
});
|
|
120
|
+
})
|
|
121
|
+
.then((mounted) => {
|
|
122
|
+
if (cancelled) {
|
|
123
|
+
mounted.dispose();
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
mountedRef.current = mounted;
|
|
127
|
+
const next = new PixiAuthoringAdapter(mounted.screen, store, {
|
|
128
|
+
// The mount already chose the namespace the game's own class was
|
|
129
|
+
// constructed in; taking it back is what keeps ONE per surface.
|
|
130
|
+
pixi: mounted.pixi,
|
|
131
|
+
// Journal ids are project-relative paths — `scene:TitleScreen` is
|
|
132
|
+
// read as a URI scheme and refused (`resource-registry.ts`).
|
|
133
|
+
journal: authoringJournal(`isolation/${documentId.slice('scene:'.length)}`),
|
|
134
|
+
target: createCreationSiteCanvasWriteTarget({
|
|
135
|
+
physics: createPhysicsAdapter2D(createPhysics2DRegistry()),
|
|
136
|
+
history: store.projectHistory,
|
|
137
|
+
}),
|
|
138
|
+
surface: () => mounted.canvas,
|
|
139
|
+
capturePreview: (object, size) =>
|
|
140
|
+
capturePixiDisplayObjectThumbnail(object, { ...size, pixi: mounted.pixi }),
|
|
141
|
+
pointFromClient: (clientX, clientY, surfaceRect) => {
|
|
142
|
+
const pose = view.get();
|
|
143
|
+
return {
|
|
144
|
+
x: (clientX - surfaceRect.left - pose.x) / pose.zoom,
|
|
145
|
+
y: (clientY - surfaceRect.top - pose.y) / pose.zoom,
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
adapterRef.current = next;
|
|
150
|
+
setAdapter(next);
|
|
151
|
+
setReady(true);
|
|
152
|
+
})
|
|
153
|
+
.catch((reason: unknown) => {
|
|
154
|
+
if (cancelled) return;
|
|
155
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
156
|
+
});
|
|
157
|
+
return () => {
|
|
158
|
+
cancelled = true;
|
|
159
|
+
mountedRef.current?.dispose();
|
|
160
|
+
mountedRef.current = null;
|
|
161
|
+
adapterRef.current?.dispose();
|
|
162
|
+
adapterRef.current = null;
|
|
163
|
+
};
|
|
164
|
+
}, [documentId, store, view, visited, path, exportName, isolationSetupSpec]);
|
|
165
|
+
|
|
166
|
+
if (error) {
|
|
167
|
+
return (
|
|
168
|
+
<div
|
|
169
|
+
role="alert"
|
|
170
|
+
data-testid="scene-document-error"
|
|
171
|
+
style={{ padding: 12, fontSize: 12, color: themeVars.semantic.danger, maxWidth: 640 }}
|
|
172
|
+
>
|
|
173
|
+
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
|
174
|
+
{path}
|
|
175
|
+
{exportName ? ` — ${exportName}` : ''}
|
|
176
|
+
</div>
|
|
177
|
+
<div style={{ color: themeVars.content.muted, whiteSpace: 'pre-wrap' }}>{error}</div>
|
|
178
|
+
</div>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return (
|
|
183
|
+
<div
|
|
184
|
+
ref={containerRef}
|
|
185
|
+
data-testid="scene-document-pixi-isolation"
|
|
186
|
+
data-document-id={documentId}
|
|
187
|
+
data-vgai-backdrop-color={active ? CANVAS_SCENE_BACKGROUND : undefined}
|
|
188
|
+
data-vgai-backdrop-policy={active ? 'dark-frost' : undefined}
|
|
189
|
+
onPointerDown={() => setActiveScope('viewport')}
|
|
190
|
+
style={{
|
|
191
|
+
position: 'absolute',
|
|
192
|
+
inset: 0,
|
|
193
|
+
overflow: 'hidden',
|
|
194
|
+
pointerEvents: 'auto',
|
|
195
|
+
backgroundColor: CANVAS_SCENE_BACKGROUND,
|
|
196
|
+
touchAction: 'none',
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
<CanvasSceneBackdrop view={view} />
|
|
200
|
+
{adapter ? <RootSelectionOverlay adapter={adapter} view={view} transformModeAware /> : null}
|
|
201
|
+
<CanvasSceneControls
|
|
202
|
+
active={active}
|
|
203
|
+
{...(adapter ? { adapter } : {})}
|
|
204
|
+
containerRef={containerRef}
|
|
205
|
+
view={view}
|
|
206
|
+
/>
|
|
207
|
+
{!ready ? (
|
|
208
|
+
<div
|
|
209
|
+
data-testid="scene-document-loading"
|
|
210
|
+
style={{ padding: 12, fontSize: 12, color: themeVars.content.muted, fontStyle: 'italic' }}
|
|
211
|
+
>
|
|
212
|
+
mounting…
|
|
213
|
+
</div>
|
|
214
|
+
) : null}
|
|
215
|
+
</div>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function loadIsolatedSceneComponent(
|
|
220
|
+
path: string,
|
|
221
|
+
exportName: string | undefined,
|
|
222
|
+
): Promise<IsolatedPixiScreenCtor> {
|
|
223
|
+
const project = getCurrentProject();
|
|
224
|
+
if (!project) throw new Error('No project is open, so its scene modules cannot be loaded.');
|
|
225
|
+
return takeNamedExport<IsolatedPixiScreenCtor>(
|
|
226
|
+
isolationImportUrl(project.rootPath, path),
|
|
227
|
+
path,
|
|
228
|
+
exportName,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Load the adapter-declared zero-argument prerequisite for one Edit piece. */
|
|
233
|
+
async function loadIsolationSetup(
|
|
234
|
+
path: string,
|
|
235
|
+
exportName: string | undefined,
|
|
236
|
+
): Promise<() => void | Promise<void>> {
|
|
237
|
+
const project = getCurrentProject();
|
|
238
|
+
if (!project) throw new Error('No project is open, so its isolation setup cannot be loaded.');
|
|
239
|
+
return takeNamedExport<() => void | Promise<void>>(
|
|
240
|
+
isolationImportUrl(project.rootPath, path),
|
|
241
|
+
path,
|
|
242
|
+
exportName,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Headless full-composition capture for a Canvas scene Content tile. */
|
|
247
|
+
export async function capturePixiIsolationScenePreview(
|
|
248
|
+
props: Pick<PixiIsolationSceneContentProps, 'path' | 'exportName' | 'isolationSetup'>,
|
|
249
|
+
request: DocumentPreviewCaptureRequest,
|
|
250
|
+
): Promise<string> {
|
|
251
|
+
const layer = document.createElement('div');
|
|
252
|
+
layer.style.cssText = `position:fixed;left:-20000px;top:0;width:${request.width}px;height:${request.height}px;overflow:hidden;`;
|
|
253
|
+
document.body.appendChild(layer);
|
|
254
|
+
const view = createRootViewController();
|
|
255
|
+
let mounted: MountedIsolatedPixiScreen | null = null;
|
|
256
|
+
try {
|
|
257
|
+
const Ctor = await loadIsolatedSceneComponent(props.path, props.exportName);
|
|
258
|
+
const assets = await loadSiblingAssetInitializer(props.path);
|
|
259
|
+
const isolationSetup = props.isolationSetup
|
|
260
|
+
? await loadIsolationSetup(props.isolationSetup.path, props.isolationSetup.export)
|
|
261
|
+
: undefined;
|
|
262
|
+
mounted = await mountIsolatedPixiScreen(Ctor, {
|
|
263
|
+
...assets,
|
|
264
|
+
...(isolationSetup ? { isolationSetup } : {}),
|
|
265
|
+
view,
|
|
266
|
+
layer,
|
|
267
|
+
});
|
|
268
|
+
const image = await capturePixiDisplayObjectThumbnail(mounted.screen, {
|
|
269
|
+
width: request.width,
|
|
270
|
+
height: request.height,
|
|
271
|
+
budget: Number.POSITIVE_INFINITY,
|
|
272
|
+
pixi: mounted.pixi,
|
|
273
|
+
});
|
|
274
|
+
if (!image) throw new Error('The Canvas scene has no capturable composition.');
|
|
275
|
+
return image;
|
|
276
|
+
} finally {
|
|
277
|
+
mounted?.dispose();
|
|
278
|
+
layer.remove();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { faDesktop } from '@fortawesome/free-solid-svg-icons';
|
|
2
|
+
import { AnchoredMenu, Button, EditorIcon, MenuItem, Text } from '@volter/editor-sdk/widgets';
|
|
3
|
+
import { useRef, useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface Resolution {
|
|
6
|
+
label: string;
|
|
7
|
+
width: number | null;
|
|
8
|
+
height: number | null;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const RESOLUTIONS: Resolution[] = [
|
|
12
|
+
{ label: 'Fill', width: null, height: null },
|
|
13
|
+
{ label: 'iPhone SE — 375×667', width: 375, height: 667 },
|
|
14
|
+
{ label: 'iPhone 14 — 390×844', width: 390, height: 844 },
|
|
15
|
+
{ label: 'iPhone 14 Pro Max — 430×932', width: 430, height: 932 },
|
|
16
|
+
{ label: 'iPad Mini — 768×1024', width: 768, height: 1024 },
|
|
17
|
+
{ label: 'iPad Pro 11″ — 834×1194', width: 834, height: 1194 },
|
|
18
|
+
{ label: 'Laptop — 1366×768', width: 1366, height: 768 },
|
|
19
|
+
{ label: 'Desktop — 1920×1080', width: 1920, height: 1080 },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
/** Shared preview-resolution picker for Game and isolated story documents. */
|
|
23
|
+
export function ResolutionPicker({
|
|
24
|
+
resolution,
|
|
25
|
+
onResolutionChange,
|
|
26
|
+
scale,
|
|
27
|
+
title = 'Change preview resolution',
|
|
28
|
+
options = RESOLUTIONS,
|
|
29
|
+
}: {
|
|
30
|
+
resolution: Resolution;
|
|
31
|
+
onResolutionChange: (resolution: Resolution) => void;
|
|
32
|
+
scale: number;
|
|
33
|
+
title?: string;
|
|
34
|
+
/** Menu entries. Callers with a project-declared resolution swap the list
|
|
35
|
+
* so the declared entry is offered and unreachable choices are not. */
|
|
36
|
+
options?: Resolution[];
|
|
37
|
+
}) {
|
|
38
|
+
const [open, setOpen] = useState(false);
|
|
39
|
+
const ref = useRef<HTMLButtonElement>(null);
|
|
40
|
+
|
|
41
|
+
const isFill = resolution.width === null;
|
|
42
|
+
const label = isFill ? 'Fill' : `${resolution.width}×${resolution.height}`;
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<div className="vgai-playbar-popover-anchor">
|
|
46
|
+
<Button
|
|
47
|
+
ref={ref}
|
|
48
|
+
variant="ghost"
|
|
49
|
+
size="comfortable"
|
|
50
|
+
aria-haspopup="menu"
|
|
51
|
+
aria-expanded={open}
|
|
52
|
+
aria-controls={open ? 'vgai-resolution-picker-menu' : undefined}
|
|
53
|
+
onClick={() => setOpen((value) => !value)}
|
|
54
|
+
title={title}
|
|
55
|
+
>
|
|
56
|
+
<EditorIcon icon={faDesktop} size="md" />
|
|
57
|
+
<Text variant="code">{label}</Text>
|
|
58
|
+
{!isFill && scale < 1 && (
|
|
59
|
+
<Text variant="caption" tone="dim">
|
|
60
|
+
{Math.round(scale * 100)}%
|
|
61
|
+
</Text>
|
|
62
|
+
)}
|
|
63
|
+
</Button>
|
|
64
|
+
{open && (
|
|
65
|
+
<AnchoredMenu
|
|
66
|
+
id="vgai-resolution-picker-menu"
|
|
67
|
+
anchorRef={ref}
|
|
68
|
+
className="vgai-resolution-menu"
|
|
69
|
+
gap={4}
|
|
70
|
+
onDismiss={() => setOpen(false)}
|
|
71
|
+
>
|
|
72
|
+
{options.map((candidate) => (
|
|
73
|
+
<MenuItem
|
|
74
|
+
key={candidate.label}
|
|
75
|
+
role="menuitemradio"
|
|
76
|
+
aria-checked={candidate.label === resolution.label}
|
|
77
|
+
onSelect={() => {
|
|
78
|
+
onResolutionChange(candidate);
|
|
79
|
+
setOpen(false);
|
|
80
|
+
}}
|
|
81
|
+
>
|
|
82
|
+
{candidate.label}
|
|
83
|
+
</MenuItem>
|
|
84
|
+
))}
|
|
85
|
+
</AnchoredMenu>
|
|
86
|
+
)}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Three implementation of an adapter-declared isolation scene document.
|
|
3
|
+
*
|
|
4
|
+
* The exact counterpart of `PixiIsolationSceneContent.tsx`, and extracted for
|
|
5
|
+
* the reason that module's own header already gives: "Loaded only when a
|
|
6
|
+
* Canvas scene document is actually rendered; the generic scene table registry
|
|
7
|
+
* must not make a Three project evaluate Pixi authoring." The three half sat
|
|
8
|
+
* INLINE in `scene-documents.tsx` instead, so the generic scene-table registry
|
|
9
|
+
* — which every project's tab row subscribes to at boot — carried the portable
|
|
10
|
+
* CSF mount with it: `stories/story-three-preview.ts`,
|
|
11
|
+
* `stories/mounted-story-viewport-source.ts`, `stories/story-mount-turn.ts`,
|
|
12
|
+
* `story-three-preview-runtime.ts`, `authoring/design-time-settle.ts` and
|
|
13
|
+
* `crash-null-boundary.ts` (measured 2026-09-18, phase 1 unit 9 of the
|
|
14
|
+
* open-source launch: six files, in a `models` build that declares no story).
|
|
15
|
+
*
|
|
16
|
+
* ## The mount, and who owns it
|
|
17
|
+
*
|
|
18
|
+
* Identical to the per-story 3D document's, deliberately and by reuse: the
|
|
19
|
+
* composition is mounted off-screen through `stories/story-three-preview.ts`
|
|
20
|
+
* and wrapped by `stories/mounted-story-viewport-source.ts`, whose
|
|
21
|
+
* per-activation disposer is EMPTY because a remount of this document (source
|
|
22
|
+
* identity change, not a tab flip) still calls `build()` again. This
|
|
23
|
+
* component's own effect cleanup is the ONE teardown path. See that module's
|
|
24
|
+
* header for the measured black-panel defect a second owner produces.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
import { fsImportPath } from '@volter/editor-sdk/session/project-module-url';
|
|
28
|
+
import { themeVars } from '@volter/editor-sdk/widgets';
|
|
29
|
+
import { useEffect, useRef, useState } from 'react';
|
|
30
|
+
import type { DocumentPreviewCaptureRequest } from '@volter/editor-core/document-preview-source';
|
|
31
|
+
import { captureAuthoredThreeScenePreview } from '../document-preview-three';
|
|
32
|
+
import { getCurrentProject } from '@volter/editor-core/project-manager';
|
|
33
|
+
import {
|
|
34
|
+
type MountedStoryViewportSource,
|
|
35
|
+
mountedStoryViewportSource,
|
|
36
|
+
} from '../stories/mounted-story-viewport-source';
|
|
37
|
+
import {
|
|
38
|
+
disposeStoryObject3D,
|
|
39
|
+
mountStoryObject3D,
|
|
40
|
+
type StoryPreviewComponent,
|
|
41
|
+
} from '@volter/editor-core/stories/story-three-preview';
|
|
42
|
+
import { takeNamedExport } from '../take-named-export';
|
|
43
|
+
import type { WorkspaceDocumentContentProps } from '@volter/editor-core/workspace-document-registry';
|
|
44
|
+
import { Object3DDocumentViewport } from '@volter/editor-core/components/Object3DDocumentViewport';
|
|
45
|
+
|
|
46
|
+
export interface ThreeIsolationSceneState {
|
|
47
|
+
/** Project-relative module the composition lives in. */
|
|
48
|
+
readonly path: string;
|
|
49
|
+
readonly exportName: string | undefined;
|
|
50
|
+
readonly label: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function ThreeIsolationSceneContent({
|
|
54
|
+
active,
|
|
55
|
+
documentId,
|
|
56
|
+
state,
|
|
57
|
+
}: WorkspaceDocumentContentProps & { state: ThreeIsolationSceneState | undefined }) {
|
|
58
|
+
const [source, setSource] = useState<MountedStoryViewportSource | null>(null);
|
|
59
|
+
const [error, setError] = useState<string | null>(null);
|
|
60
|
+
const sourceRef = useRef<MountedStoryViewportSource | null>(null);
|
|
61
|
+
// EVERY OPEN DOCUMENT IS MOUNTED, and this row is opened for EVERY
|
|
62
|
+
// scene a project declares — so mounting at open time would build every
|
|
63
|
+
// scene's fiber root at boot. Mount on first activation and retain it
|
|
64
|
+
// afterwards; a mount freed on deactivation is the black-panel defect
|
|
65
|
+
// `mountedStoryViewportSource` documents.
|
|
66
|
+
const [visited, setVisited] = useState(active);
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (active) setVisited(true);
|
|
69
|
+
}, [active]);
|
|
70
|
+
|
|
71
|
+
const path = state?.path;
|
|
72
|
+
const exportName = state?.exportName;
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (!visited || !path) return;
|
|
75
|
+
let cancelled = false;
|
|
76
|
+
setError(null);
|
|
77
|
+
setSource(null);
|
|
78
|
+
void loadSceneComponent(path, exportName)
|
|
79
|
+
.then((Component) => mountStoryObject3D(Component, {}))
|
|
80
|
+
.then((mounted) => {
|
|
81
|
+
if (cancelled) {
|
|
82
|
+
void disposeStoryObject3D(mounted);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
const next = mountedStoryViewportSource(mounted);
|
|
86
|
+
sourceRef.current = next;
|
|
87
|
+
setSource(next);
|
|
88
|
+
})
|
|
89
|
+
.catch((reason: unknown) => {
|
|
90
|
+
if (cancelled) return;
|
|
91
|
+
setError(reason instanceof Error ? reason.message : String(reason));
|
|
92
|
+
});
|
|
93
|
+
return () => {
|
|
94
|
+
cancelled = true;
|
|
95
|
+
// THIS is the mount's one owner — see the module header.
|
|
96
|
+
sourceRef.current?.dispose();
|
|
97
|
+
sourceRef.current = null;
|
|
98
|
+
setSource(null);
|
|
99
|
+
};
|
|
100
|
+
}, [visited, path, exportName]);
|
|
101
|
+
|
|
102
|
+
if (!state) {
|
|
103
|
+
return (
|
|
104
|
+
<div style={{ padding: 12, fontSize: 12, color: themeVars.content.muted }}>
|
|
105
|
+
This scene document is no longer declared by the project's adapter.
|
|
106
|
+
</div>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (error) {
|
|
111
|
+
return (
|
|
112
|
+
<div
|
|
113
|
+
role="alert"
|
|
114
|
+
data-testid="scene-document-error"
|
|
115
|
+
style={{ padding: 12, fontSize: 12, color: themeVars.semantic.danger, maxWidth: 640 }}
|
|
116
|
+
>
|
|
117
|
+
<div style={{ fontWeight: 600, marginBottom: 4 }}>
|
|
118
|
+
{state.path}
|
|
119
|
+
{state.exportName ? ` — ${state.exportName}` : ''}
|
|
120
|
+
</div>
|
|
121
|
+
<div style={{ color: themeVars.content.muted, whiteSpace: 'pre-wrap' }}>{error}</div>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (!source) {
|
|
127
|
+
return (
|
|
128
|
+
<div
|
|
129
|
+
data-testid="scene-document-loading"
|
|
130
|
+
style={{ padding: 12, fontSize: 12, color: themeVars.content.muted, fontStyle: 'italic' }}
|
|
131
|
+
>
|
|
132
|
+
mounting…
|
|
133
|
+
</div>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return (
|
|
138
|
+
<Object3DDocumentViewport
|
|
139
|
+
documentId={documentId}
|
|
140
|
+
sourcePath={state.path}
|
|
141
|
+
build={source.build}
|
|
142
|
+
displayName={state.label}
|
|
143
|
+
active={active}
|
|
144
|
+
/>
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Import the scene's own module through the project-module door and take the
|
|
150
|
+
* export the table named.
|
|
151
|
+
*
|
|
152
|
+
* Cache-busted per mount, the same always-fresh discipline design-time CSF
|
|
153
|
+
* loads use (`stories/story-discovery.ts`): a scene edited in the editor must
|
|
154
|
+
* be re-openable without a reload. The url is built by `fsImportPath`, the ONE
|
|
155
|
+
* owner of `/@fs/` urls for project code (PD-3).
|
|
156
|
+
*/
|
|
157
|
+
async function loadSceneComponent(
|
|
158
|
+
path: string,
|
|
159
|
+
exportName: string | undefined,
|
|
160
|
+
): Promise<StoryPreviewComponent> {
|
|
161
|
+
const project = getCurrentProject();
|
|
162
|
+
if (!project) throw new Error('No project is open, so its scene modules cannot be loaded.');
|
|
163
|
+
const url = `${fsImportPath(project.rootPath, path)}?t=${Date.now()}`;
|
|
164
|
+
return takeNamedExport<StoryPreviewComponent>(url, path, exportName);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Headless capture door for Content. It mounts the authored source without
|
|
168
|
+
* opening or activating its workspace document. */
|
|
169
|
+
export async function captureThreeIsolationScenePreview(
|
|
170
|
+
state: ThreeIsolationSceneState,
|
|
171
|
+
request: DocumentPreviewCaptureRequest,
|
|
172
|
+
): Promise<string> {
|
|
173
|
+
const Component = await loadSceneComponent(state.path, state.exportName);
|
|
174
|
+
const mounted = await mountStoryObject3D(Component, {});
|
|
175
|
+
try {
|
|
176
|
+
return captureAuthoredThreeScenePreview(mounted.root, mounted.scene, request);
|
|
177
|
+
} finally {
|
|
178
|
+
await disposeStoryObject3D(mounted);
|
|
179
|
+
}
|
|
180
|
+
}
|