@volter/editor-core 0.5.57
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/BUNDLED_NOTICES +1527 -0
- package/LICENSE +686 -0
- package/LICENSE-APACHE +202 -0
- package/NOTICE +18 -0
- package/README.md +24 -0
- package/dist/build/chunk-JS4D4XZ3.js +175 -0
- package/dist/build/chunk-JS4D4XZ3.js.map +7 -0
- package/dist/build/vite-plugin-product-contributions.js +100 -0
- package/dist/build/vite-plugin-product-contributions.js.map +7 -0
- package/dist/build/vite-plugin-shared-react.js +25 -0
- package/dist/build/vite-plugin-shared-react.js.map +7 -0
- package/dist/build/vite-plugin-shared-three.js +86 -0
- package/dist/build/vite-plugin-shared-three.js.map +7 -0
- package/dist/server/chunk-KMJC7AGO.js +98 -0
- package/dist/server/chunk-KMJC7AGO.js.map +7 -0
- package/dist/server/chunk-PSILUMGS.js +46 -0
- package/dist/server/chunk-PSILUMGS.js.map +7 -0
- package/dist/server/frame-proxy.js +255 -0
- package/dist/server/frame-proxy.js.map +7 -0
- package/dist/server/open-browser.js +53 -0
- package/dist/server/open-browser.js.map +7 -0
- package/dist/server/process-shutdown.js +127 -0
- package/dist/server/process-shutdown.js.map +7 -0
- package/dist/server/session-registry.js +141 -0
- package/dist/server/session-registry.js.map +7 -0
- package/dist/server/spawn-opener.js +11 -0
- package/dist/server/spawn-opener.js.map +7 -0
- package/dist/server/worktree-identity.js +11 -0
- package/dist/server/worktree-identity.js.map +7 -0
- package/dist-server/packaged.mjs +88816 -0
- package/package.json +118 -0
- package/scripts/build-plugins.mjs +14 -0
- package/scripts/build-server.mjs +26 -0
- package/scripts/build-session.mjs +9 -0
- package/server/account-credentials.ts +60 -0
- package/server/account-service.ts +1434 -0
- package/server/adapter-region-includes.ts +47 -0
- package/server/asset-catalog-v2.ts +175 -0
- package/server/asset-history-snapshots.ts +196 -0
- package/server/asset-ledger-store.ts +195 -0
- package/server/asset-library-routes.ts +1595 -0
- package/server/blender-wasm-artifact.ts +430 -0
- package/server/boot-timings.ts +106 -0
- package/server/canonical-path.ts +50 -0
- package/server/checkout-workspace-preflight.ts +349 -0
- package/server/cloud-asset-catalog.ts +158 -0
- package/server/coding-inference-launch.ts +237 -0
- package/server/collaboration-account-client.ts +202 -0
- package/server/collaboration-attribution.ts +122 -0
- package/server/collaboration-session.ts +934 -0
- package/server/comfyui-bridge.ts +248 -0
- package/server/console-ledger.ts +499 -0
- package/server/creation-site-transform.ts +304 -0
- package/server/creation-site-write.ts +648 -0
- package/server/data-file-serialize.ts +158 -0
- package/server/editor-brand-html.ts +72 -0
- package/server/editor-control-socket.ts +366 -0
- package/server/editor-server-options.ts +190 -0
- package/server/editor-server.ts +1483 -0
- package/server/editor-sse.ts +352 -0
- package/server/engine-provenance.ts +169 -0
- package/server/engine-source-restart.ts +117 -0
- package/server/frame-bridge.ts +115 -0
- package/server/frame-proxy.ts +490 -0
- package/server/frame-workbench.ts +259 -0
- package/server/frontend-handoff.ts +229 -0
- package/server/game-globals-shadow.ts +113 -0
- package/server/gameplay-session-retention.ts +149 -0
- package/server/gameplay-sessions.ts +123 -0
- package/server/generation-jobs.ts +201 -0
- package/server/generation-reconciler.ts +137 -0
- package/server/generative-execution-context.ts +59 -0
- package/server/git-workflow.ts +612 -0
- package/server/harness-chat-caller.ts +36 -0
- package/server/harness-chat-service.ts +1681 -0
- package/server/idle-shutdown.ts +170 -0
- package/server/js-profiling-policy.ts +52 -0
- package/server/launcher-settings.ts +84 -0
- package/server/local-asset-catalog.ts +410 -0
- package/server/managed-account-defaults.ts +22 -0
- package/server/mock-control-plane.ts +540 -0
- package/server/model-import-conversion.ts +345 -0
- package/server/native-credential-store.ts +162 -0
- package/server/open-browser.ts +118 -0
- package/server/packaged.ts +1192 -0
- package/server/play-stall.ts +126 -0
- package/server/process-shutdown.ts +218 -0
- package/server/product-presets.ts +55 -0
- package/server/project-build-artifact.ts +95 -0
- package/server/project-components.ts +100 -0
- package/server/project-dependency-invalidation.ts +114 -0
- package/server/project-file-scan.ts +235 -0
- package/server/project-hmr-files.ts +215 -0
- package/server/project-install-roots.ts +210 -0
- package/server/project-kinds.ts +71 -0
- package/server/project-mcp-servers.ts +82 -0
- package/server/project-module-freshness.ts +109 -0
- package/server/project-module-instance.ts +375 -0
- package/server/project-optimize-deps-entries.ts +827 -0
- package/server/project-output-writer.ts +679 -0
- package/server/project-package-origin.ts +63 -0
- package/server/project-root-surface.ts +831 -0
- package/server/project-scratch-path.ts +47 -0
- package/server/project-script-hmr.ts +619 -0
- package/server/project-serving-plugins.ts +236 -0
- package/server/project-tools.ts +653 -0
- package/server/project-validation.ts +250 -0
- package/server/project-verbs.ts +127 -0
- package/server/project-view.ts +52 -0
- package/server/project-watch.ts +1134 -0
- package/server/project-work-coordinator.ts +288 -0
- package/server/provider-credentials.ts +331 -0
- package/server/recent-projects-store.ts +98 -0
- package/server/redact-secrets.ts +71 -0
- package/server/repository-presence.ts +267 -0
- package/server/routes/account.ts +357 -0
- package/server/routes/agents.ts +60 -0
- package/server/routes/assets.ts +154 -0
- package/server/routes/build.ts +13 -0
- package/server/routes/collaboration.ts +595 -0
- package/server/routes/configurations.ts +419 -0
- package/server/routes/context.ts +264 -0
- package/server/routes/control-plane.ts +1811 -0
- package/server/routes/logs.ts +272 -0
- package/server/routes/project-identity.ts +302 -0
- package/server/routes/project-open.ts +429 -0
- package/server/routes/project-source.ts +686 -0
- package/server/routes/project-state.ts +383 -0
- package/server/routes/relay.ts +1029 -0
- package/server/routes/route-helpers.ts +29 -0
- package/server/routes/served-modules.ts +138 -0
- package/server/routes/session-tabs.ts +196 -0
- package/server/routes/settings.ts +121 -0
- package/server/routes/share-control.ts +486 -0
- package/server/routes/themes.ts +116 -0
- package/server/routes/tools.ts +230 -0
- package/server/routes/worktrees.ts +595 -0
- package/server/scoped-game-css.ts +461 -0
- package/server/server-utils.ts +1194 -0
- package/server/session-product.ts +112 -0
- package/server/session-registry.ts +230 -0
- package/server/share-claims.ts +138 -0
- package/server/share-host.ts +526 -0
- package/server/share-session-gateway.ts +1362 -0
- package/server/share-tunnel.ts +123 -0
- package/server/spawn-opener.ts +74 -0
- package/server/support/play/log-format.ts +76 -0
- package/server/support/play/session-record.ts +199 -0
- package/server/support/project/build-discipline.ts +718 -0
- package/server/support/project/inspection-node.ts +119 -0
- package/server/support/project/log-naming.ts +54 -0
- package/server/support/project/provenance.ts +290 -0
- package/server/support/project/run-name.ts +18 -0
- package/server/support/project/session-journal.ts +830 -0
- package/server/support/project/shared.ts +327 -0
- package/server/tab-bootstrap.ts +62 -0
- package/server/tab-heartbeat.ts +477 -0
- package/server/tab-lifecycle.ts +550 -0
- package/server/tab-presence.ts +1508 -0
- package/server/team-agent-mirror.ts +95 -0
- package/server/twin-auth.ts +138 -0
- package/server/vendored-lock-recorder.ts +968 -0
- package/server/worktree-identity.ts +121 -0
- package/server/worktree-management.ts +398 -0
- package/server/worktree-retention.d.mts +13 -0
- package/server/worktree-retention.mjs +205 -0
- package/src/EditorContext.tsx +169 -0
- package/src/account.ts +333 -0
- package/src/action-registry.ts +532 -0
- package/src/active-product.ts +74 -0
- package/src/active-project.ts +155 -0
- package/src/adapter-editor-config.ts +25 -0
- package/src/adapter-observation.ts +49 -0
- package/src/animation/stage-transport.ts +379 -0
- package/src/animation/three-clips-subject.ts +172 -0
- package/src/api/asset-library-wire.ts +45 -0
- package/src/api/assets.ts +365 -0
- package/src/api/base.ts +10 -0
- package/src/api/build.ts +99 -0
- package/src/api/git-wire.ts +56 -0
- package/src/api/logs.ts +92 -0
- package/src/api/project-identity.ts +74 -0
- package/src/api/project-open.ts +355 -0
- package/src/api/project-source.ts +162 -0
- package/src/api/project-state.ts +107 -0
- package/src/api/relay.ts +268 -0
- package/src/api/settings.ts +36 -0
- package/src/api/themes.ts +45 -0
- package/src/api/worktrees.ts +257 -0
- package/src/asset-compare-core.ts +171 -0
- package/src/asset-compare.ts +294 -0
- package/src/asset-editor-context.tsx +100 -0
- package/src/asset-events.ts +97 -0
- package/src/asset-inspector-actions.ts +87 -0
- package/src/asset-preview-framing.ts +357 -0
- package/src/asset-preview.ts +2802 -0
- package/src/asset-selection-viewer-registry.ts +113 -0
- package/src/asset-selection.ts +133 -0
- package/src/asset-workflow/asset-capabilities.ts +344 -0
- package/src/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/asset-workflow/asset-ledger.ts +156 -0
- package/src/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/asset-workflow/asset-types.ts +142 -0
- package/src/asset-workflow/asset-workflow-quality.ts +61 -0
- package/src/asset-workflow/audio-preview-player.ts +193 -0
- package/src/asset-workflow/audio-waveform.ts +22 -0
- package/src/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/asset-workflow/folder-preview.ts +345 -0
- package/src/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/asset-workflow/image-view-scale.ts +32 -0
- package/src/asset-workflow/import-contract.ts +124 -0
- package/src/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/asset-workflow/model-inspection.ts +818 -0
- package/src/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/asset-workflow/project-asset-commands.ts +23 -0
- package/src/asset-workflow/project-asset-health.ts +139 -0
- package/src/asset-workflow/project-asset-operations.ts +154 -0
- package/src/asset-workflow/project-asset-roots.ts +68 -0
- package/src/asset-workflow/project-content.ts +288 -0
- package/src/asset-workflow/project-source-index.ts +550 -0
- package/src/asset-workflow/thumbnail-system.ts +256 -0
- package/src/authoring/active-adapter.ts +199 -0
- package/src/authoring/active-systems.ts +422 -0
- package/src/authoring/adapter-key.ts +18 -0
- package/src/authoring/authoring-asset-url.ts +27 -0
- package/src/authoring/bootstrap-state.ts +49 -0
- package/src/authoring/boundary-authoring-adapter.ts +184 -0
- package/src/authoring/breakpoint-state.ts +43 -0
- package/src/authoring/canvas-scene-guides.ts +76 -0
- package/src/authoring/component-instance-root.ts +171 -0
- package/src/authoring/composite-authoring-adapter.ts +2110 -0
- package/src/authoring/consumer-actions.ts +520 -0
- package/src/authoring/css-numeric-style.ts +97 -0
- package/src/authoring/design-time-layers.ts +849 -0
- package/src/authoring/design-time-mount-registry.ts +235 -0
- package/src/authoring/design-time-settle.ts +343 -0
- package/src/authoring/edit-mode-authoring.ts +619 -0
- package/src/authoring/eyedropper-session.ts +60 -0
- package/src/authoring/instance-source-menu-register.ts +13 -0
- package/src/authoring/instance-source-menu.ts +135 -0
- package/src/authoring/layered-pick.ts +183 -0
- package/src/authoring/live-gesture-lock.ts +50 -0
- package/src/authoring/live-object-transform.ts +62 -0
- package/src/authoring/mount-failure-report.ts +154 -0
- package/src/authoring/mounted-root-subjects.ts +144 -0
- package/src/authoring/no-authoring-adapter.ts +55 -0
- package/src/authoring/null-inspection-subjects.tsx +75 -0
- package/src/authoring/object3d-document-persistence.ts +106 -0
- package/src/authoring/object3d-document-session-registry.ts +120 -0
- package/src/authoring/object3d-document-session.ts +1326 -0
- package/src/authoring/object3d-gesture-controller.ts +113 -0
- package/src/authoring/panel-authoring.ts +121 -0
- package/src/authoring/prefab-instance-inspector-section.tsx +237 -0
- package/src/authoring/prefab-instance-section-model.ts +55 -0
- package/src/authoring/project-authoring-session.ts +105 -0
- package/src/authoring/provenance.ts +99 -0
- package/src/authoring/quarks-particle-systems.ts +19 -0
- package/src/authoring/react-canvas-navigation.ts +255 -0
- package/src/authoring/react-design-canvas-style.ts +20 -0
- package/src/authoring/react-story-board.ts +937 -0
- package/src/authoring/selection-scope.ts +195 -0
- package/src/authoring/shell-document-ops.ts +169 -0
- package/src/authoring/shell-object3d-document-write-policy.ts +108 -0
- package/src/authoring/shell-viewport-policy.ts +48 -0
- package/src/authoring/source-object3d-authoring-adapter.ts +526 -0
- package/src/authoring/stories-scope.ts +35 -0
- package/src/authoring/story-board-chrome-fit.ts +107 -0
- package/src/authoring/story-board-presentation.ts +111 -0
- package/src/authoring/three-projection-core.ts +226 -0
- package/src/authoring/viewport-pick-context.ts +40 -0
- package/src/authoring/viewport-raycast.ts +240 -0
- package/src/authoring/viewport-tool-context.ts +73 -0
- package/src/authoring/world-canvas-viewport-state.ts +35 -0
- package/src/authoring/world-hidden-viewport.ts +152 -0
- package/src/authoring/world-pan-state.ts +198 -0
- package/src/authoring/world-session-state.ts +101 -0
- package/src/authoring/write-pipe.ts +173 -0
- package/src/availability-tick.ts +66 -0
- package/src/bitmap-label.ts +120 -0
- package/src/blender-tab-metrics.ts +161 -0
- package/src/board-open-actions.ts +20 -0
- package/src/boot-routing.ts +391 -0
- package/src/build-identity.ts +15 -0
- package/src/bytes-codec.ts +62 -0
- package/src/camera-authoring.ts +175 -0
- package/src/cancellation-reason.ts +58 -0
- package/src/canvas-preview-frames.ts +482 -0
- package/src/capture-camera-pose.ts +77 -0
- package/src/chrome-registry.ts +159 -0
- package/src/chrome-slot-registry.ts +91 -0
- package/src/collaboration-client.ts +264 -0
- package/src/collaboration-presence.ts +41 -0
- package/src/command-dispatch.ts +19 -0
- package/src/command-listener.ts +2743 -0
- package/src/command-registry.ts +70 -0
- package/src/component-board-registry.ts +205 -0
- package/src/component-states-registry.ts +200 -0
- package/src/components/AgentPresentationNotice.tsx +75 -0
- package/src/components/AlignToolbar.tsx +204 -0
- package/src/components/AppRoot.tsx +394 -0
- package/src/components/ApplicationMenus.tsx +415 -0
- package/src/components/AssetBrowser.tsx +2843 -0
- package/src/components/AssetEditorShell.tsx +216 -0
- package/src/components/AssetInspectorToolSection.tsx +124 -0
- package/src/components/BoardRulers.tsx +354 -0
- package/src/components/CameraInfo.tsx +81 -0
- package/src/components/CanvasSceneViewport.tsx +556 -0
- package/src/components/CapabilityCoverageSection.tsx +185 -0
- package/src/components/CenterDocuments.tsx +22 -0
- package/src/components/ChromeSlot.tsx +20 -0
- package/src/components/CodeView.tsx +470 -0
- package/src/components/CollaborationHeaderControl.css +74 -0
- package/src/components/CompactInspectorCard.tsx +190 -0
- package/src/components/CompactInspectorShell.tsx +39 -0
- package/src/components/ConsolePanel.css +55 -0
- package/src/components/ConsolePanel.tsx +270 -0
- package/src/components/DefaultEditorLayout.tsx +106 -0
- package/src/components/DocumentHeaderStrip.tsx +96 -0
- package/src/components/DocumentShelfRail.tsx +45 -0
- package/src/components/DocumentThumbnail.tsx +345 -0
- package/src/components/EditorLeaseGuard.tsx +372 -0
- package/src/components/ErrorBanner.tsx +123 -0
- package/src/components/FolderPreviewTile.tsx +159 -0
- package/src/components/GameHierarchy.tsx +3842 -0
- package/src/components/GameplaySessionTimeline.tsx +295 -0
- package/src/components/InspectionProjection.tsx +968 -0
- package/src/components/Inspector.tsx +233 -0
- package/src/components/InspectorCanvasPreview.tsx +35 -0
- package/src/components/InspectorFieldsSection.tsx +290 -0
- package/src/components/InspectorObjectPreview.tsx +57 -0
- package/src/components/InspectorStoriesSection.tsx +92 -0
- package/src/components/InspectorToolSection.tsx +96 -0
- package/src/components/InspectorTransformSection.tsx +245 -0
- package/src/components/LightExplorerPanel.tsx +433 -0
- package/src/components/MediaProperties.tsx +145 -0
- package/src/components/NonThreeAuthoringBootstrap.tsx +19 -0
- package/src/components/Object3DDocumentToolbar.css +100 -0
- package/src/components/Object3DDocumentToolbar.tsx +454 -0
- package/src/components/Object3DDocumentViewport.tsx +58 -0
- package/src/components/ProjectHeader.css +617 -0
- package/src/components/ProjectHeader.tsx +328 -0
- package/src/components/ProjectLayout.tsx +249 -0
- package/src/components/ReactCanvasControls.tsx +284 -0
- package/src/components/RootSelectionOverlay.tsx +3162 -0
- package/src/components/RootTextEditor.tsx +79 -0
- package/src/components/SaveStatus.tsx +70 -0
- package/src/components/StageHost.tsx +1954 -0
- package/src/components/StageOverlays.tsx +21 -0
- package/src/components/StartupErrorScreen.tsx +76 -0
- package/src/components/StartupLoadingScreen.tsx +65 -0
- package/src/components/StatsOverlay.tsx +78 -0
- package/src/components/SurfaceStateOverlay.tsx +24 -0
- package/src/components/ToolContributionSurfaces.tsx +65 -0
- package/src/components/ToolHost.tsx +370 -0
- package/src/components/ToolObject3DPreview.tsx +39 -0
- package/src/components/Toolbar.tsx +595 -0
- package/src/components/TransientHint.tsx +44 -0
- package/src/components/TransportStrip.tsx +174 -0
- package/src/components/VersionControlSection.tsx +470 -0
- package/src/components/VgaiLogo.css +83 -0
- package/src/components/VgaiLogo.tsx +35 -0
- package/src/components/ViewportControlsHint.tsx +60 -0
- package/src/components/ViewportFurniture.tsx +401 -0
- package/src/components/ViewportOverlay.tsx +145 -0
- package/src/components/ViewportOverlaysMenu.tsx +153 -0
- package/src/components/ViewportShadingMenu.tsx +300 -0
- package/src/components/ViewportViewMenu.tsx +101 -0
- package/src/components/WorkspaceDocumentSurface.tsx +204 -0
- package/src/components/WorkspaceUtilitySurface.tsx +22 -0
- package/src/components/WorktreeSwitcher.css +239 -0
- package/src/components/WorktreeSwitcher.tsx +890 -0
- package/src/components/account-documents.tsx +1162 -0
- package/src/components/asset-documents.tsx +812 -0
- package/src/components/asset-editor-persistence.ts +216 -0
- package/src/components/asset-selection-section.tsx +535 -0
- package/src/components/asset-thumbnails.tsx +312 -0
- package/src/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/components/asset-viewers/EntityModelDocument.tsx +122 -0
- package/src/components/asset-viewers/EnvironmentAssetDocument.tsx +440 -0
- package/src/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/components/asset-viewers/JsonAssetDocument.tsx +93 -0
- package/src/components/asset-viewers/LiveModuleDocument.tsx +417 -0
- package/src/components/asset-viewers/LutAssetDocument.tsx +444 -0
- package/src/components/asset-viewers/ModelAssetDocument.tsx +105 -0
- package/src/components/asset-viewers/Object3DPreview.tsx +359 -0
- package/src/components/asset-viewers/OnlineAssetDetail.tsx +421 -0
- package/src/components/asset-viewers/PasteboardModuleDocument.tsx +67 -0
- package/src/components/asset-viewers/QuarksAssetDocument.tsx +526 -0
- package/src/components/asset-viewers/ShaderAssetDocument.tsx +743 -0
- package/src/components/asset-viewers/SourceAssetViewer.tsx +281 -0
- package/src/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/components/asset-viewers/shader-source.ts +144 -0
- package/src/components/asset-workflow.css +731 -0
- package/src/components/board-guides.ts +150 -0
- package/src/components/compact-inspector.css +539 -0
- package/src/components/core-utilities.tsx +90 -0
- package/src/components/editor-notifications.css +40 -0
- package/src/components/engine-workspace.css +274 -0
- package/src/components/inspector-preview-section.tsx +238 -0
- package/src/components/inspector-property-grouping.ts +64 -0
- package/src/components/inspector-revert-label.ts +20 -0
- package/src/components/inspector-selection.ts +42 -0
- package/src/components/inspector-stories-gating.ts +171 -0
- package/src/components/inspector-transform-subject.ts +11 -0
- package/src/components/inspector-transform.ts +75 -0
- package/src/components/kind-documents.tsx +500 -0
- package/src/components/palette-action-publisher.tsx +140 -0
- package/src/components/primitives/DraftColorInput.tsx +74 -0
- package/src/components/product-shell.css +1829 -0
- package/src/components/project-tool-documents.tsx +410 -0
- package/src/components/scene-documents.tsx +223 -0
- package/src/components/stage-keyboard.tsx +37 -0
- package/src/components/stage-overlay-set.tsx +107 -0
- package/src/components/stage-presence-markers.ts +478 -0
- package/src/components/standard-viewport-dressing.ts +417 -0
- package/src/components/status-contributions.tsx +403 -0
- package/src/components/tool-documents.tsx +309 -0
- package/src/components/tool-schema-form.tsx +262 -0
- package/src/components/use-after-paint.ts +41 -0
- package/src/components/use-project-image-assets.ts +86 -0
- package/src/components/viewport-header-controls.css +90 -0
- package/src/components/viewport-surface-status.tsx +55 -0
- package/src/components/workspace-history.ts +32 -0
- package/src/components/workspace-static-panel-registry.tsx +152 -0
- package/src/components/workspace-surfaces.css +621 -0
- package/src/components/world-documents.tsx +586 -0
- package/src/components/world-overlay-gestures.ts +1694 -0
- package/src/composite-screenshot.ts +1641 -0
- package/src/console-sync.ts +131 -0
- package/src/constraint-helper.ts +338 -0
- package/src/content-entry-source-registry.ts +184 -0
- package/src/coverage/authoring-seam-evidence.ts +300 -0
- package/src/coverage/canvas-reveal.ts +192 -0
- package/src/coverage/design-time-surfaces.ts +101 -0
- package/src/coverage/live-seam-evidence.ts +11 -0
- package/src/coverage/ontology-invariants.ts +466 -0
- package/src/coverage/session-vitals.ts +501 -0
- package/src/coverage/system-seam-evidence.ts +72 -0
- package/src/crash-null-boundary.ts +36 -0
- package/src/creation-site-edit.ts +1479 -0
- package/src/creation-site-registry.ts +160 -0
- package/src/delegate-harness-registry.ts +143 -0
- package/src/document-context-registry.ts +177 -0
- package/src/document-open-registry.ts +200 -0
- package/src/document-preview-source.ts +20 -0
- package/src/document-renderer-session.ts +138 -0
- package/src/editor-api.ts +46 -0
- package/src/editor-chrome-capture.ts +139 -0
- package/src/editor-commands.ts +164 -0
- package/src/editor-console.ts +563 -0
- package/src/editor-current-view.ts +82 -0
- package/src/editor-document-probe.ts +881 -0
- package/src/editor-git-client.ts +115 -0
- package/src/editor-host-door.ts +462 -0
- package/src/editor-hotkeys.ts +842 -0
- package/src/editor-lease-view.ts +39 -0
- package/src/editor-lease.ts +415 -0
- package/src/editor-mode.ts +19 -0
- package/src/editor-notifications.ts +140 -0
- package/src/editor-presence.ts +563 -0
- package/src/editor-presentation-activity.ts +50 -0
- package/src/editor-presentation-notice.ts +42 -0
- package/src/editor-runtime.tsx +145 -0
- package/src/editor-server-response.ts +86 -0
- package/src/editor-session-attribution.ts +75 -0
- package/src/editor-session-mode.ts +54 -0
- package/src/editor-shell-store.ts +1304 -0
- package/src/editor-state-facets.ts +74 -0
- package/src/editor-styles.css +33 -0
- package/src/editor-view-presentation.ts +810 -0
- package/src/editor-viewport.ts +5302 -0
- package/src/entity-lod.ts +31 -0
- package/src/entity-object.ts +91 -0
- package/src/files/file-provider.ts +62 -0
- package/src/files/project-files.ts +264 -0
- package/src/finders/index.ts +136 -0
- package/src/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/frame/bridge.tsx +1408 -0
- package/src/frame/product.ts +77 -0
- package/src/gameplay-dom-recording.ts +319 -0
- package/src/gameplay-export-state.ts +14 -0
- package/src/gameplay-replay.ts +417 -0
- package/src/gameplay-session-time.ts +9 -0
- package/src/gameplay-sessions.ts +204 -0
- package/src/harness-chat-types.ts +253 -0
- package/src/hierarchy-component-marks.ts +298 -0
- package/src/hierarchy-drop.ts +91 -0
- package/src/hierarchy-expansion-state.ts +80 -0
- package/src/hierarchy-header-slot.ts +52 -0
- package/src/hierarchy-internals.ts +197 -0
- package/src/hierarchy-kind-icon.ts +217 -0
- package/src/hierarchy-mark-reader.ts +73 -0
- package/src/hierarchy-menu-registry.ts +67 -0
- package/src/hierarchy-node-rows.ts +307 -0
- package/src/hierarchy-panel-view.ts +280 -0
- package/src/hierarchy-projection.ts +76 -0
- package/src/hierarchy-row-cache.ts +243 -0
- package/src/hierarchy-row-model.ts +308 -0
- package/src/hierarchy-rows.ts +11 -0
- package/src/hierarchy-walk.ts +86 -0
- package/src/history/editor-session.ts +25 -0
- package/src/history/history-commands.ts +147 -0
- package/src/history/history-delegate.ts +163 -0
- package/src/history/history-limit-notices.ts +43 -0
- package/src/history/history-service.ts +1173 -0
- package/src/history/persistence-coordinator.ts +35 -0
- package/src/history/project-file-history.ts +386 -0
- package/src/history/project-root-history-backends.ts +139 -0
- package/src/history/resource-registry.ts +209 -0
- package/src/history/snapshot-store.ts +103 -0
- package/src/history/source-history-backend.ts +546 -0
- package/src/history/types.ts +124 -0
- package/src/hmr-registration-group.ts +67 -0
- package/src/hmr-stable-react-context.ts +23 -0
- package/src/hotkeys.ts +188 -0
- package/src/inference-diagnostics.ts +69 -0
- package/src/initial-project.ts +80 -0
- package/src/inspection/active-subject.ts +578 -0
- package/src/inspection/active-surface.ts +142 -0
- package/src/inspection/compose.ts +1064 -0
- package/src/inspection/display.ts +170 -0
- package/src/inspection/document-subject.ts +109 -0
- package/src/inspection/game-subject.ts +85 -0
- package/src/inspection/model.ts +542 -0
- package/src/inspection/null-subject.ts +115 -0
- package/src/inspection/serialize.ts +357 -0
- package/src/inspection/use-active-inspection.ts +180 -0
- package/src/inspector-presentation.ts +201 -0
- package/src/inspector-section-registry.ts +221 -0
- package/src/instance-source-actions.ts +163 -0
- package/src/instanced-presentation.ts +164 -0
- package/src/js-heap.ts +71 -0
- package/src/key-actions.ts +91 -0
- package/src/keymap-presets.ts +401 -0
- package/src/layout-policy.ts +31 -0
- package/src/learn-links.ts +73 -0
- package/src/light-explorer-model.ts +134 -0
- package/src/live-canvas-frame.ts +55 -0
- package/src/live-document.ts +279 -0
- package/src/live-module-source.ts +230 -0
- package/src/live-session-registry.ts +220 -0
- package/src/live-transition.ts +633 -0
- package/src/manifest-project.ts +107 -0
- package/src/model-thumbnail.ts +523 -0
- package/src/native-selection-style.ts +202 -0
- package/src/object3d-document-write-policy.ts +137 -0
- package/src/packaged-runtime.ts +108 -0
- package/src/palettes/maya.palette.json +57 -0
- package/src/palettes/substance.palette.json +57 -0
- package/src/pasteboard-module.ts +81 -0
- package/src/performance-profiler.ts +367 -0
- package/src/performance-sources.ts +69 -0
- package/src/play-boot-phase.ts +145 -0
- package/src/presentation-surface.ts +248 -0
- package/src/project-adapter.ts +1143 -0
- package/src/project-asset-refresh.ts +26 -0
- package/src/project-declaration-refresh.ts +31 -0
- package/src/project-local-state.ts +118 -0
- package/src/project-manager.ts +243 -0
- package/src/project-module-changes.ts +163 -0
- package/src/project-module-split.ts +266 -0
- package/src/project-provenance.ts +115 -0
- package/src/project-ready.ts +42 -0
- package/src/project-session-reset.ts +67 -0
- package/src/project-shape.ts +68 -0
- package/src/project-tool-discovery.ts +66 -0
- package/src/project-tools.ts +107 -0
- package/src/project-work-types.ts +149 -0
- package/src/projection/three.ts +898 -0
- package/src/projection/types.ts +44 -0
- package/src/readiness.ts +113 -0
- package/src/reflection-probe-helper.ts +142 -0
- package/src/reported-play-state.ts +90 -0
- package/src/resolve-relative-specifier.ts +33 -0
- package/src/scene-document-plan.ts +315 -0
- package/src/scene-framing.ts +315 -0
- package/src/scene-live-open.ts +210 -0
- package/src/scene-view-fog.ts +89 -0
- package/src/scoped-game-css.ts +152 -0
- package/src/session-orphan-record.ts +193 -0
- package/src/session-tombstone.ts +126 -0
- package/src/settings/settings-provider.ts +82 -0
- package/src/settings-store.ts +345 -0
- package/src/shared-view-restore.ts +42 -0
- package/src/shell-store-door.ts +45 -0
- package/src/source-conflict.ts +122 -0
- package/src/spatial-handle-visuals.ts +332 -0
- package/src/stage-context.ts +341 -0
- package/src/stage-store-registry.ts +63 -0
- package/src/stale-chunk-recovery.ts +34 -0
- package/src/state-report-deferral.ts +73 -0
- package/src/storage/host-files-storage.ts +97 -0
- package/src/storage/http-storage.ts +174 -0
- package/src/storage/index.ts +75 -0
- package/src/storage/mem-storage.ts +149 -0
- package/src/storage/path-lock.ts +44 -0
- package/src/storage/paths.ts +26 -0
- package/src/storage/types.ts +116 -0
- package/src/stories/StoryComponentThumbnail.tsx +184 -0
- package/src/stories/StoryPreviewMount.tsx +306 -0
- package/src/stories/component-content-source.tsx +106 -0
- package/src/stories/component-states-source.ts +78 -0
- package/src/stories/compose-project-stories.ts +255 -0
- package/src/stories/pixi-story-model.ts +30 -0
- package/src/stories/prefabs-finder.ts +54 -0
- package/src/stories/prefabs-from-stories.ts +182 -0
- package/src/stories/project-story-discovery.ts +78 -0
- package/src/stories/project-story-regions.ts +24 -0
- package/src/stories/story-actions.ts +54 -0
- package/src/stories/story-capture-command.ts +124 -0
- package/src/stories/story-capture.ts +584 -0
- package/src/stories/story-declared-medium.ts +126 -0
- package/src/stories/story-discovery.ts +176 -0
- package/src/stories/story-document-openers.ts +36 -0
- package/src/stories/story-dom-runtime.ts +78 -0
- package/src/stories/story-grouping.ts +111 -0
- package/src/stories/story-lane.ts +88 -0
- package/src/stories/story-mount-turn.ts +27 -0
- package/src/stories/story-opener.ts +260 -0
- package/src/stories/story-pixi-preview.ts +408 -0
- package/src/stories/story-presentation.ts +215 -0
- package/src/stories/story-registry.ts +517 -0
- package/src/stories/story-three-preview.ts +807 -0
- package/src/stories/three-story-model.ts +96 -0
- package/src/story-three-preview-runtime.ts +56 -0
- package/src/surface-keyboard.ts +112 -0
- package/src/surface-state.ts +124 -0
- package/src/tab-bootstrap.js +628 -0
- package/src/tab-census.ts +198 -0
- package/src/tab-lifecycle-client.ts +196 -0
- package/src/theme-library.ts +844 -0
- package/src/theme-preference.ts +378 -0
- package/src/theme.css +3437 -0
- package/src/three-viewport/bone-selection-highlight.ts +119 -0
- package/src/three-viewport/camera-fit.ts +41 -0
- package/src/three-viewport/interactive-renderer.ts +132 -0
- package/src/three-viewport/selection-brackets.ts +310 -0
- package/src/three-viewport/selection-outline.ts +183 -0
- package/src/three-viewport/skeleton-helper.ts +61 -0
- package/src/three-viewport/source-color.ts +167 -0
- package/src/three-viewport/studio-environment.ts +96 -0
- package/src/three-viewport-presentation.ts +23 -0
- package/src/tool-contribution-play.ts +74 -0
- package/src/tool-loader.ts +1798 -0
- package/src/transform-mode-request.ts +57 -0
- package/src/transient-hint.ts +78 -0
- package/src/trigger-volume-helper.ts +116 -0
- package/src/ui-source/adapter-region-includes.ts +241 -0
- package/src/ui-source/ensure-import.ts +132 -0
- package/src/ui-source/file-region-resolver.ts +302 -0
- package/src/ui-source/inspect.ts +775 -0
- package/src/ui-source/oid-transform.ts +1845 -0
- package/src/ui-source/plan-csf-story.ts +196 -0
- package/src/ui-source/plan-extract-component.ts +421 -0
- package/src/ui-source/plan-fork-component.ts +621 -0
- package/src/ui-source/plan-named-style.ts +95 -0
- package/src/ui-source/plan-source-edit.ts +353 -0
- package/src/ui-source/r3f-contract-resolver.ts +327 -0
- package/src/ui-source/r3f-diagnostic-index.ts +165 -0
- package/src/ui-source/r3f-environment-binding.ts +133 -0
- package/src/ui-source/r3f-joint-binding.ts +251 -0
- package/src/ui-source/r3f-lod-binding.ts +91 -0
- package/src/ui-source/r3f-particle-binding.ts +235 -0
- package/src/ui-source/r3f-physics-binding.ts +483 -0
- package/src/ui-source/r3f-project-contracts.ts +444 -0
- package/src/ui-source/relative-import-specifier.ts +34 -0
- package/src/ui-source/reparent-guard.ts +544 -0
- package/src/ui-source/source-edit-request.ts +97 -0
- package/src/ui-source/source-write-backend.ts +618 -0
- package/src/ui-source/syntactic-prop-specs.ts +296 -0
- package/src/ui-source/tier-source-write-backend.ts +279 -0
- package/src/ui-source/ts-ast.ts +171 -0
- package/src/ui-source/utility-class-support.ts +169 -0
- package/src/ui-source/write-component-default.ts +133 -0
- package/src/ui-source/writer.ts +1989 -0
- package/src/viewport-activation-timings.ts +840 -0
- package/src/viewport-authoring-policy.ts +186 -0
- package/src/viewport-controls-hint.ts +57 -0
- package/src/viewport-door.ts +237 -0
- package/src/viewport-shading-boundary.ts +12 -0
- package/src/vite-error-surface.ts +141 -0
- package/src/wait-until.ts +37 -0
- package/src/workspace-areas.ts +156 -0
- package/src/workspace-aux-commands.ts +11 -0
- package/src/workspace-available-documents.ts +127 -0
- package/src/workspace-core-utilities.ts +31 -0
- package/src/workspace-document-ids.ts +59 -0
- package/src/workspace-document-registry.ts +612 -0
- package/src/workspace-document-restore.ts +145 -0
- package/src/workspace-host-commands.ts +141 -0
- package/src/workspace-persistence-gate.ts +40 -0
- package/src/workspace-play-utilities.ts +44 -0
- package/src/workspace-presets.ts +425 -0
- package/src/workspace-regions.ts +294 -0
- package/src/workspace-state-persistence.ts +536 -0
- package/src/workspace-static-panels.ts +73 -0
- package/src/workspace-status-registry.ts +121 -0
- package/src/workspace-style.ts +239 -0
- package/src/workspace-utility-commands.ts +74 -0
- package/src/workspace-utility-registry.ts +263 -0
- package/src/workspace-viewport-rect.ts +97 -0
- package/src/world-adoption.ts +115 -0
- package/src/world-document-routing.ts +104 -0
- package/vite-plugin-creation-site-write.ts +184 -0
- package/vite-plugin-creation-site.ts +80 -0
- package/vite-plugin-game-static.ts +303 -0
- package/vite-plugin-module-doorways.ts +336 -0
- package/vite-plugin-product-contributions.ts +197 -0
- package/vite-plugin-project-game-static.ts +125 -0
- package/vite-plugin-project-jsx-js.ts +65 -0
- package/vite-plugin-project-root-absolute-assets.ts +359 -0
- package/vite-plugin-shared-react.ts +420 -0
- package/vite-plugin-shared-three.ts +203 -0
- package/vite-plugin-ui-oid.ts +2106 -0
|
@@ -0,0 +1,937 @@
|
|
|
1
|
+
import { themeVars, zIndex } from '@volter/editor-sdk/widgets';
|
|
2
|
+
import {
|
|
3
|
+
REACT_STORY_TRANSPARENCY_BASE,
|
|
4
|
+
reactStoryTransparencyBackground,
|
|
5
|
+
} from './react-design-canvas-style';
|
|
6
|
+
import {
|
|
7
|
+
applyStoryChrome,
|
|
8
|
+
FRAME_LABEL_GAP as FLOATING_LABEL_GAP,
|
|
9
|
+
FRAME_LABEL_HEIGHT as FLOATING_LABEL_HEIGHT,
|
|
10
|
+
GROUP_LABEL_GAP,
|
|
11
|
+
GROUP_LABEL_HEIGHT,
|
|
12
|
+
placeStoryChrome,
|
|
13
|
+
} from './story-board-chrome-fit';
|
|
14
|
+
import type {
|
|
15
|
+
ResolvedStoryPresentation,
|
|
16
|
+
StoryBoardPresentation,
|
|
17
|
+
StoryPresentationIndex,
|
|
18
|
+
} from './story-board-presentation';
|
|
19
|
+
import { getRootCanvasViewport, subscribeRootCanvasViewport } from './world-canvas-viewport-state';
|
|
20
|
+
import { getRootPan, setRootView, subscribeRootPan } from './world-pan-state';
|
|
21
|
+
|
|
22
|
+
const DEFAULT_FRAME_WIDTH = 640;
|
|
23
|
+
const DEFAULT_FRAME_HEIGHT = 400;
|
|
24
|
+
const BOARD_PADDING = 64;
|
|
25
|
+
const FRAME_GAP = 64;
|
|
26
|
+
const VIEW_PADDING = 72;
|
|
27
|
+
/** Below this client width a truncated frame title is visual noise. */
|
|
28
|
+
const MIN_FRAME_LABEL_WIDTH = 28;
|
|
29
|
+
|
|
30
|
+
/** Vertical space between two group blocks — the ordinary frame gap plus the
|
|
31
|
+
* chrome that sits above the next block (its group label and its frames'
|
|
32
|
+
* own floating labels). Never applied to a single-group board, which is why
|
|
33
|
+
* an ungrouped project lays out byte-for-byte as it did before grouping. */
|
|
34
|
+
const GROUP_BLOCK_GAP =
|
|
35
|
+
FRAME_GAP + GROUP_LABEL_HEIGHT + GROUP_LABEL_GAP + FLOATING_LABEL_HEIGHT + FLOATING_LABEL_GAP;
|
|
36
|
+
|
|
37
|
+
export interface ReactStoryBoardStory {
|
|
38
|
+
readonly id: string;
|
|
39
|
+
readonly label: string;
|
|
40
|
+
readonly modulePath: string;
|
|
41
|
+
/** Composed CSF meta title when the module authored one. Organization input
|
|
42
|
+
* only (`authoring/story-board-presentation.ts`). */
|
|
43
|
+
readonly title?: string | undefined;
|
|
44
|
+
readonly parameters?: Readonly<Record<string, unknown>> | undefined;
|
|
45
|
+
readonly globals?: Readonly<Record<string, unknown>> | undefined;
|
|
46
|
+
readonly viewportLocked?: boolean | undefined;
|
|
47
|
+
/**
|
|
48
|
+
* The frame's own size in authored px, for a story that BAKES its cell.
|
|
49
|
+
*
|
|
50
|
+
* A dom story omits it: its frame is a Storybook viewport (or the shared
|
|
51
|
+
* responsive preset), which is what makes a UI frame a phone or a desktop.
|
|
52
|
+
* A canvas story supplies the content-bounds frame resolved by its native
|
|
53
|
+
* board — so the shared substrate uses that exact authored-space rectangle
|
|
54
|
+
* rather than scaling it into a default UI viewport. See
|
|
55
|
+
* `@vgai/canvas`'s `canvas-board/CanvasBoardDocument.tsx`.
|
|
56
|
+
*/
|
|
57
|
+
readonly frameSize?: { readonly width: number; readonly height: number } | undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** One component section: its named story frames, in Storybook order. */
|
|
61
|
+
interface BoardGroup {
|
|
62
|
+
readonly key: string;
|
|
63
|
+
readonly label: string;
|
|
64
|
+
readonly stories: ReactStoryBoardStory[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Project the shared Storybook index into component sections. */
|
|
68
|
+
function groupStories(
|
|
69
|
+
stories: readonly ReactStoryBoardStory[],
|
|
70
|
+
index: StoryPresentationIndex,
|
|
71
|
+
): BoardGroup[] {
|
|
72
|
+
const byId = new Map(stories.map((story) => [story.id, story]));
|
|
73
|
+
return index.sections.map((section) => ({
|
|
74
|
+
key: section.id,
|
|
75
|
+
label: section.label,
|
|
76
|
+
stories: section.storyIds.flatMap((storyId) => {
|
|
77
|
+
const story = byId.get(storyId);
|
|
78
|
+
return story ? [story] : [];
|
|
79
|
+
}),
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const pathSegments = (path: string): string[] =>
|
|
84
|
+
path
|
|
85
|
+
.split('/')
|
|
86
|
+
.map((part) => part.trim())
|
|
87
|
+
.filter(Boolean);
|
|
88
|
+
interface BoardBlock {
|
|
89
|
+
key: string;
|
|
90
|
+
label: string | null;
|
|
91
|
+
stories: ReactStoryBoardStory[];
|
|
92
|
+
}
|
|
93
|
+
function boardBlocks(groups: readonly BoardGroup[]): BoardBlock[] {
|
|
94
|
+
const soloSections = new Set(
|
|
95
|
+
groups.filter((group) => group.stories.length === 1).map((group) => group.key),
|
|
96
|
+
);
|
|
97
|
+
const blocks: BoardBlock[] = [];
|
|
98
|
+
{
|
|
99
|
+
const byCategory = new Map<string, BoardBlock>();
|
|
100
|
+
for (const group of groups) {
|
|
101
|
+
if (!soloSections.has(group.key)) {
|
|
102
|
+
blocks.push({ key: group.key, label: group.label, stories: [...group.stories] });
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const category = pathSegments(group.label).slice(0, -1).join('/');
|
|
106
|
+
let block = byCategory.get(category);
|
|
107
|
+
if (!block) {
|
|
108
|
+
block = { key: `category:${category}`, label: category || null, stories: [] };
|
|
109
|
+
byCategory.set(category, block);
|
|
110
|
+
blocks.push(block);
|
|
111
|
+
}
|
|
112
|
+
block.stories.push(...group.stories);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return blocks;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Shared authored-space placement for the board and its chrome-free preview. */
|
|
120
|
+
export function reactStoryBoardLayout(
|
|
121
|
+
stories: readonly ReactStoryBoardStory[],
|
|
122
|
+
index: StoryPresentationIndex,
|
|
123
|
+
presentationFor: (story: ReactStoryBoardStory) => { width: number; height: number },
|
|
124
|
+
) {
|
|
125
|
+
const blocks = boardBlocks(groupStories(stories, index));
|
|
126
|
+
const layouts = new Map<string, FrameLayout>();
|
|
127
|
+
const groupOrigins = new Map<string, { x: number; y: number; roomAbove: number }>();
|
|
128
|
+
let blockTop = BOARD_PADDING;
|
|
129
|
+
let widestBlock = 0;
|
|
130
|
+
|
|
131
|
+
// One block per BoardBlock (see the blocks derivation above): variant
|
|
132
|
+
// sections as themselves, solo cards in shared category grids.
|
|
133
|
+
blocks.forEach((group, blockIndex) => {
|
|
134
|
+
const columns = Math.max(1, Math.ceil(Math.sqrt(group.stories.length)));
|
|
135
|
+
const rows = Math.ceil(group.stories.length / columns);
|
|
136
|
+
const presentations = group.stories.map((story) => presentationFor(story));
|
|
137
|
+
const columnWidths = Array.from({ length: columns }, () => 0);
|
|
138
|
+
const rowHeights = Array.from({ length: rows }, () => 0);
|
|
139
|
+
presentations.forEach((presentation, index) => {
|
|
140
|
+
const column = index % columns;
|
|
141
|
+
const row = Math.floor(index / columns);
|
|
142
|
+
columnWidths[column] = Math.max(columnWidths[column]!, presentation.width);
|
|
143
|
+
rowHeights[row] = Math.max(rowHeights[row]!, presentation.height);
|
|
144
|
+
});
|
|
145
|
+
const columnX = columnWidths.map((_, column) =>
|
|
146
|
+
columnWidths.slice(0, column).reduce((sum, width) => sum + width + FRAME_GAP, 0),
|
|
147
|
+
);
|
|
148
|
+
const rowY = rowHeights.map((_, row) =>
|
|
149
|
+
rowHeights.slice(0, row).reduce((sum, height) => sum + height + FRAME_GAP, 0),
|
|
150
|
+
);
|
|
151
|
+
// Nothing sits above the board's very first row, so its chrome is never
|
|
152
|
+
// constrained; every later block sits one block gap below its predecessor.
|
|
153
|
+
// An unlabeled block carries no group title, so it borrows only the room
|
|
154
|
+
// its own frame labels need.
|
|
155
|
+
const blockRoom =
|
|
156
|
+
blockIndex === 0
|
|
157
|
+
? Number.POSITIVE_INFINITY
|
|
158
|
+
: group.label !== null
|
|
159
|
+
? GROUP_BLOCK_GAP
|
|
160
|
+
: FRAME_GAP + FLOATING_LABEL_HEIGHT + FLOATING_LABEL_GAP;
|
|
161
|
+
if (group.label !== null) {
|
|
162
|
+
groupOrigins.set(group.key, { x: BOARD_PADDING, y: blockTop, roomAbove: blockRoom });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
group.stories.forEach((story, index) => {
|
|
166
|
+
const row = Math.floor(index / columns);
|
|
167
|
+
const column = index % columns;
|
|
168
|
+
const presentation = presentations[index]!;
|
|
169
|
+
const { width, height } = presentation;
|
|
170
|
+
const x = BOARD_PADDING + columnX[column]!;
|
|
171
|
+
const y = blockTop + rowY[row]!;
|
|
172
|
+
layouts.set(story.id, {
|
|
173
|
+
x,
|
|
174
|
+
y,
|
|
175
|
+
width,
|
|
176
|
+
height,
|
|
177
|
+
roomAbove: row === 0 ? blockRoom : FRAME_GAP,
|
|
178
|
+
withGroupLabel: blocks.length > 1 && row === 0 && group.label !== null,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const blockWidth =
|
|
183
|
+
columnWidths.reduce((sum, width) => sum + width, 0) + Math.max(0, columns - 1) * FRAME_GAP;
|
|
184
|
+
const blockHeight =
|
|
185
|
+
rowHeights.reduce((sum, height) => sum + height, 0) + Math.max(0, rows - 1) * FRAME_GAP;
|
|
186
|
+
widestBlock = Math.max(widestBlock, blockWidth);
|
|
187
|
+
blockTop += blockHeight + GROUP_BLOCK_GAP;
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const boardWidth = BOARD_PADDING * 2 + widestBlock;
|
|
191
|
+
const boardHeight = blockTop - GROUP_BLOCK_GAP + BOARD_PADDING;
|
|
192
|
+
|
|
193
|
+
return { layouts, groupOrigins, width: boardWidth, height: stories.length ? boardHeight : 0 };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface ReactStoryBoardFrame {
|
|
197
|
+
readonly element: HTMLElement;
|
|
198
|
+
readonly content: HTMLElement;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export type ReactStoryBoardSelectionIntent = 'highlight' | 'zoom';
|
|
202
|
+
|
|
203
|
+
interface FrameLayout {
|
|
204
|
+
readonly x: number;
|
|
205
|
+
readonly y: number;
|
|
206
|
+
readonly width: number;
|
|
207
|
+
readonly height: number;
|
|
208
|
+
/** Authored px between this frame's top edge and the bottom edge of the
|
|
209
|
+
* frames above it — `Infinity` on the board's own first row. Multiplied by
|
|
210
|
+
* zoom it is the client-space room the floating chrome must fit into. */
|
|
211
|
+
readonly roomAbove: number;
|
|
212
|
+
/** True on the first row of a block on a grouped board: that row's chrome
|
|
213
|
+
* stack also carries the group name, which is served first. */
|
|
214
|
+
readonly withGroupLabel: boolean;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export interface ReactStoryBoard {
|
|
218
|
+
readonly element: HTMLElement;
|
|
219
|
+
readonly frames: ReadonlyMap<string, ReactStoryBoardFrame>;
|
|
220
|
+
activeContent(): HTMLElement;
|
|
221
|
+
activate(storyId: string, focus?: boolean): void;
|
|
222
|
+
clearError(storyId: string): void;
|
|
223
|
+
fitAll(): void;
|
|
224
|
+
selectAtClientPoint(clientX: number, clientY: number): boolean;
|
|
225
|
+
zoomAtClientPoint(clientX: number, clientY: number): boolean;
|
|
226
|
+
setError(storyId: string, message: string): void;
|
|
227
|
+
/**
|
|
228
|
+
* Present the story's MEASURED content rectangle as its cell — the dom
|
|
229
|
+
* lane's version of the canvas exhibit's content-bounds frame + crop. The
|
|
230
|
+
* mount keeps its declared-viewport harness; the frame becomes a window
|
|
231
|
+
* onto `offset → offset + frame`. `null` restores the declared
|
|
232
|
+
* presentation. The caller (design-time-layers) measures after each mount;
|
|
233
|
+
* a `fullscreen` story never gets one.
|
|
234
|
+
*/
|
|
235
|
+
setMeasuredFrame(
|
|
236
|
+
storyId: string,
|
|
237
|
+
measured: {
|
|
238
|
+
frame: { width: number; height: number };
|
|
239
|
+
mount: { width: number; height: number };
|
|
240
|
+
offset: { x: number; y: number };
|
|
241
|
+
} | null,
|
|
242
|
+
): void;
|
|
243
|
+
/** Show (non-empty) or clear (`null`) the self-explanation on EVERY card —
|
|
244
|
+
* the condition it reports is a project-level fact, never a per-story one. */
|
|
245
|
+
setNote(message: string | null): void;
|
|
246
|
+
viewport(): ReactStoryBoardViewportState;
|
|
247
|
+
setViewport(viewportId: string | null): void;
|
|
248
|
+
setRotation(rotated: boolean): void;
|
|
249
|
+
dispose(): void;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
export interface ReactStoryBoardViewportState {
|
|
253
|
+
readonly storyId: string;
|
|
254
|
+
readonly selectedViewportId: string | null;
|
|
255
|
+
readonly locked: boolean;
|
|
256
|
+
readonly rotated: boolean;
|
|
257
|
+
readonly choices: ResolvedStoryPresentation['choices'];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const boardsByContainer = new WeakMap<HTMLElement, ReactStoryBoard>();
|
|
261
|
+
const boardViewportListeners = new Set<() => void>();
|
|
262
|
+
const EMPTY_BOARD_VIEWPORT: ReactStoryBoardViewportState | null = null;
|
|
263
|
+
|
|
264
|
+
function notifyBoardViewport(): void {
|
|
265
|
+
for (const listener of boardViewportListeners) listener();
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export function subscribeReactStoryBoardViewport(listener: () => void): () => void {
|
|
269
|
+
boardViewportListeners.add(listener);
|
|
270
|
+
return () => boardViewportListeners.delete(listener);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* The active board's frame placements in BOARD coordinates (the unzoomed
|
|
275
|
+
* layer pixels `layout()` writes to each frame's `style.left/top`) — what
|
|
276
|
+
* "Materialize pasteboard" reads to turn the DERIVED auto-grid into AUTHORED
|
|
277
|
+
* `<At x y>` literals. Null when the container holds no board.
|
|
278
|
+
*/
|
|
279
|
+
export function reactStoryBoardFramePlacements(
|
|
280
|
+
element: HTMLElement | null,
|
|
281
|
+
): Array<{ storyId: string; x: number; y: number }> | null {
|
|
282
|
+
// Boards register under the layer's PARENT (`createReactStoryBoard`'s
|
|
283
|
+
// `container`), while the queryable marker sits on the layer itself —
|
|
284
|
+
// accept either, so a caller holding `[data-vgai-react-story-board]` works.
|
|
285
|
+
const board = element
|
|
286
|
+
? (boardsByContainer.get(element) ??
|
|
287
|
+
(element.parentElement ? boardsByContainer.get(element.parentElement) : undefined))
|
|
288
|
+
: undefined;
|
|
289
|
+
if (!board) return null;
|
|
290
|
+
const placements: Array<{ storyId: string; x: number; y: number }> = [];
|
|
291
|
+
for (const [storyId, frame] of board.frames) {
|
|
292
|
+
placements.push({
|
|
293
|
+
storyId,
|
|
294
|
+
x: Math.round(Number.parseFloat(frame.element.style.left) || 0),
|
|
295
|
+
y: Math.round(Number.parseFloat(frame.element.style.top) || 0),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
return placements;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export function getReactStoryBoardViewport(
|
|
302
|
+
container: HTMLElement | null,
|
|
303
|
+
): ReactStoryBoardViewportState | null {
|
|
304
|
+
return container ? (boardsByContainer.get(container)?.viewport() ?? EMPTY_BOARD_VIEWPORT) : null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export function setActiveReactStoryBoardViewport(
|
|
308
|
+
container: HTMLElement | null,
|
|
309
|
+
viewportId: string | null,
|
|
310
|
+
): boolean {
|
|
311
|
+
const board = container ? boardsByContainer.get(container) : undefined;
|
|
312
|
+
if (!board) return false;
|
|
313
|
+
board.setViewport(viewportId);
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function toggleActiveReactStoryBoardViewportRotation(
|
|
318
|
+
container: HTMLElement | null,
|
|
319
|
+
): boolean {
|
|
320
|
+
const board = container ? boardsByContainer.get(container) : undefined;
|
|
321
|
+
if (!board || board.viewport().locked) return false;
|
|
322
|
+
board.setRotation(!board.viewport().rotated);
|
|
323
|
+
return true;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
function fallbackFrameSize(): { width: number; height: number } {
|
|
327
|
+
const viewport = getRootCanvasViewport();
|
|
328
|
+
return {
|
|
329
|
+
width: viewport.width ?? DEFAULT_FRAME_WIDTH,
|
|
330
|
+
height: viewport.height ?? DEFAULT_FRAME_HEIGHT,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function fitBounds(
|
|
335
|
+
container: HTMLElement,
|
|
336
|
+
bounds: { x: number; y: number; width: number; height: number },
|
|
337
|
+
maximumZoom: number,
|
|
338
|
+
): void {
|
|
339
|
+
if (bounds.width <= 0 || bounds.height <= 0) return;
|
|
340
|
+
const availableWidth = Math.max(1, container.clientWidth - VIEW_PADDING * 2);
|
|
341
|
+
const availableHeight = Math.max(1, container.clientHeight - VIEW_PADDING * 2);
|
|
342
|
+
const zoom = Math.min(
|
|
343
|
+
maximumZoom,
|
|
344
|
+
Math.max(0.1, Math.min(availableWidth / bounds.width, availableHeight / bounds.height)),
|
|
345
|
+
);
|
|
346
|
+
setRootView(
|
|
347
|
+
container.clientWidth / 2 - (bounds.x + bounds.width / 2) * zoom,
|
|
348
|
+
container.clientHeight / 2 - (bounds.y + bounds.height / 2) * zoom,
|
|
349
|
+
zoom,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/** Fit the board hosted by `container`, if it has one. Used by viewport chrome. */
|
|
354
|
+
export function fitReactStoryBoard(container: HTMLElement): boolean {
|
|
355
|
+
const board = boardsByContainer.get(container);
|
|
356
|
+
if (!board) return false;
|
|
357
|
+
board.fitAll();
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Focus an inactive story frame from a canvas pointer press. Returns true
|
|
362
|
+
* only when the point was inside an inactive frame and its story-selection
|
|
363
|
+
* callback was dispatched. The selection overlay uses this to give the whole
|
|
364
|
+
* frame—not only its small label—the expected Figma-style click target. */
|
|
365
|
+
export function selectReactStoryFrameAtPoint(
|
|
366
|
+
container: HTMLElement | null,
|
|
367
|
+
clientX: number,
|
|
368
|
+
clientY: number,
|
|
369
|
+
): boolean {
|
|
370
|
+
if (!container) return false;
|
|
371
|
+
return boardsByContainer.get(container)?.selectAtClientPoint(clientX, clientY) ?? false;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/** Zoom to the story frame under a canvas double-click. Unlike single-click
|
|
375
|
+
* selection, this also handles the already-active frame so recentering never
|
|
376
|
+
* requires a hierarchy round trip. */
|
|
377
|
+
export function zoomReactStoryFrameAtPoint(
|
|
378
|
+
container: HTMLElement | null,
|
|
379
|
+
clientX: number,
|
|
380
|
+
clientY: number,
|
|
381
|
+
): boolean {
|
|
382
|
+
if (!container) return false;
|
|
383
|
+
return boardsByContainer.get(container)?.zoomAtClientPoint(clientX, clientY) ?? false;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Editor-only Figma-style canvas over a set of CSF stories — THE board
|
|
388
|
+
* substrate, not a dom-only one. Every story owns an isolated frame; the active
|
|
389
|
+
* frame is only the authoring focus, never a runtime state.
|
|
390
|
+
*
|
|
391
|
+
* Two boards mount it, and the difference between them is membership and what
|
|
392
|
+
* goes IN a frame, never layout: the `UI`/`Dev` boards
|
|
393
|
+
* (`design-time-layers.ts`) mount each dom story's React render into
|
|
394
|
+
* `frames.get(id).content`, and Play tears that whole board down before
|
|
395
|
+
* mounting the root's real manifest entry; the `2D` board
|
|
396
|
+
* (`@vgai/canvas`'s `canvas-board/CanvasBoardDocument.tsx`) puts each canvas story's own mounted
|
|
397
|
+
* Pixi host in the same place, cropped to the mounted stage's content bounds.
|
|
398
|
+
* A frame is DOM either way — a canvas story renders a `<canvas>`, which is
|
|
399
|
+
* why it is an ordinary frame here and not a second board engine.
|
|
400
|
+
*/
|
|
401
|
+
export function createReactStoryBoard(
|
|
402
|
+
layer: HTMLElement,
|
|
403
|
+
stories: readonly ReactStoryBoardStory[],
|
|
404
|
+
initialStoryId: string,
|
|
405
|
+
onStorySelected: (storyId: string, intent: ReactStoryBoardSelectionIntent) => void,
|
|
406
|
+
// INJECTED, not defaulted (`authoring/story-board-presentation.ts`): the
|
|
407
|
+
// board is host chrome's substrate and must not import the CSF
|
|
408
|
+
// implementation that organizes and sizes the frames. `resolve` is called
|
|
409
|
+
// INSIDE the layout pass below, per story and per viewport override, so a
|
|
410
|
+
// caller cannot pre-compute the answers and hand over a table.
|
|
411
|
+
presentation: StoryBoardPresentation,
|
|
412
|
+
): ReactStoryBoard {
|
|
413
|
+
if (stories.length === 0) throw new Error('A React story board requires at least one story.');
|
|
414
|
+
|
|
415
|
+
const container = layer.parentElement;
|
|
416
|
+
if (!container) throw new Error('A React story board layer must be attached before mounting.');
|
|
417
|
+
|
|
418
|
+
layer.dataset['vgaiReactStoryBoard'] = 'true';
|
|
419
|
+
// The board may be larger than the visible document. Individual story
|
|
420
|
+
// frames provide their own layout/paint containment for fixed-position UI.
|
|
421
|
+
layer.style.contain = 'none';
|
|
422
|
+
|
|
423
|
+
const boardElement = document.createElement('div');
|
|
424
|
+
boardElement.dataset['testid'] = 'react-story-board';
|
|
425
|
+
boardElement.dataset['vgaiReactStoryBoardCanvas'] = 'true';
|
|
426
|
+
boardElement.style.position = 'absolute';
|
|
427
|
+
boardElement.style.left = '0';
|
|
428
|
+
boardElement.style.top = '0';
|
|
429
|
+
boardElement.style.transformOrigin = '0 0';
|
|
430
|
+
layer.appendChild(boardElement);
|
|
431
|
+
|
|
432
|
+
// Labels are editor chrome, not game DOM. Keep them in a synchronized
|
|
433
|
+
// sibling above RootSelectionOverlay (z=overlayLow), otherwise that
|
|
434
|
+
// full-cover interaction layer correctly used for DOM picking intercepts
|
|
435
|
+
// every label click before it reaches the board.
|
|
436
|
+
const chromeElement = document.createElement('div');
|
|
437
|
+
chromeElement.dataset['testid'] = 'react-story-board-chrome';
|
|
438
|
+
chromeElement.dataset['vgaiStoryBoardChromeFor'] = layer.dataset['worldId'] ?? '';
|
|
439
|
+
chromeElement.style.position = 'absolute';
|
|
440
|
+
chromeElement.style.inset = '0';
|
|
441
|
+
chromeElement.style.zIndex = String(zIndex.sticky);
|
|
442
|
+
chromeElement.style.display = layer.style.display || 'block';
|
|
443
|
+
chromeElement.style.pointerEvents = 'none';
|
|
444
|
+
chromeElement.style.overflow = 'visible';
|
|
445
|
+
container.appendChild(chromeElement);
|
|
446
|
+
|
|
447
|
+
const frames = new Map<string, ReactStoryBoardFrame>();
|
|
448
|
+
const errors = new Map<string, HTMLElement>();
|
|
449
|
+
const notes = new Map<string, HTMLElement>();
|
|
450
|
+
const labels = new Map<string, HTMLButtonElement>();
|
|
451
|
+
const layouts = new Map<string, FrameLayout>();
|
|
452
|
+
// The dom lane's transcription of the canvas board's exhibit model
|
|
453
|
+
// (`canvas-board-model.ts`): the MOUNT is a harness at the story's declared
|
|
454
|
+
// viewport — the box its anchors resolve against — while the FRAME presents
|
|
455
|
+
// the measured content rectangle inside it. Filled per story by the owner
|
|
456
|
+
// (design-time-layers) after each mount; a story whose presentation is
|
|
457
|
+
// `fullscreen` never gets an entry and presents its whole declared box.
|
|
458
|
+
const measuredFrames = new Map<
|
|
459
|
+
string,
|
|
460
|
+
{
|
|
461
|
+
frame: { width: number; height: number };
|
|
462
|
+
mount: { width: number; height: number };
|
|
463
|
+
offset: { x: number; y: number };
|
|
464
|
+
}
|
|
465
|
+
>();
|
|
466
|
+
// Organization is presentation-only and fixed for the board's lifetime.
|
|
467
|
+
// A board with one component needs no redundant section label.
|
|
468
|
+
const groups = groupStories(stories, presentation.index);
|
|
469
|
+
const groupLabels = new Map<string, HTMLElement>();
|
|
470
|
+
const groupOrigins = new Map<string, { x: number; y: number; roomAbove: number }>();
|
|
471
|
+
const viewportOverrides = new Map<string, string | null>();
|
|
472
|
+
const rotationOverrides = new Map<string, boolean>();
|
|
473
|
+
let viewportSnapshot: ReactStoryBoardViewportState;
|
|
474
|
+
let activeStoryId = stories.some((story) => story.id === initialStoryId)
|
|
475
|
+
? initialStoryId
|
|
476
|
+
: stories[0]!.id;
|
|
477
|
+
let disposed = false;
|
|
478
|
+
let pendingFit = false;
|
|
479
|
+
|
|
480
|
+
const presentationFor = (story: ReactStoryBoardStory): ResolvedStoryPresentation => {
|
|
481
|
+
const resolved = presentation.resolve(
|
|
482
|
+
story,
|
|
483
|
+
story.frameSize ?? fallbackFrameSize(),
|
|
484
|
+
viewportOverrides.has(story.id) ? viewportOverrides.get(story.id) : undefined,
|
|
485
|
+
rotationOverrides.get(story.id),
|
|
486
|
+
);
|
|
487
|
+
// A measured content frame wins the CELL: the board cell is the subject's
|
|
488
|
+
// rectangle, not the whole mount harness. Same `fullscreen` collapse as an
|
|
489
|
+
// exact `frameSize` below, and for the same reason — the measured
|
|
490
|
+
// rectangle already owns its inset.
|
|
491
|
+
const measured = measuredFrames.get(story.id);
|
|
492
|
+
if (measured) {
|
|
493
|
+
return {
|
|
494
|
+
...resolved,
|
|
495
|
+
width: measured.frame.width,
|
|
496
|
+
height: measured.frame.height,
|
|
497
|
+
layout: 'fullscreen',
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
// A story that supplies an exact frame already owns its inset/crop.
|
|
501
|
+
// A `padded` default layout would add a second inset inside that measured
|
|
502
|
+
// rectangle, so the content would sit clipped and off-centre. Nothing else
|
|
503
|
+
// about the presentation changes — a viewport the story really authored
|
|
504
|
+
// still wins the size above.
|
|
505
|
+
return story.frameSize ? { ...resolved, layout: 'fullscreen' } : resolved;
|
|
506
|
+
};
|
|
507
|
+
|
|
508
|
+
const updateViewportSnapshot = (): void => {
|
|
509
|
+
const presentation = presentationFor(stories.find((story) => story.id === activeStoryId)!);
|
|
510
|
+
viewportSnapshot = {
|
|
511
|
+
storyId: activeStoryId,
|
|
512
|
+
selectedViewportId: presentation.selectedViewportId,
|
|
513
|
+
locked: presentation.locked,
|
|
514
|
+
rotated: presentation.rotated,
|
|
515
|
+
choices: presentation.choices,
|
|
516
|
+
};
|
|
517
|
+
};
|
|
518
|
+
updateViewportSnapshot();
|
|
519
|
+
|
|
520
|
+
// The board's presented hierarchy is Category → Component → Variant, and a
|
|
521
|
+
// SINGLE-variant component folds only its variant level away. Generated
|
|
522
|
+
// projects emit one `Default` export per component module, so componentized
|
|
523
|
+
// sections would otherwise render a section title above every card AND a
|
|
524
|
+
// "Default" label on it — a section per frame, twice-labeled, one row each.
|
|
525
|
+
// The solo card takes its component's name (the title path's last segment)
|
|
526
|
+
// and joins its CATEGORY's block — the path minus that segment ("Prefabs",
|
|
527
|
+
// "UI/Screens") — so the categories the titles author remain the board's
|
|
528
|
+
// grouping. A section with real variants keeps its own titled block and its
|
|
529
|
+
// variant labels, which is the shape the two-level names exist for.
|
|
530
|
+
const soloSections = new Set(
|
|
531
|
+
groups.filter((group) => group.stories.length === 1).map((group) => group.key),
|
|
532
|
+
);
|
|
533
|
+
const pathSegments = (path: string): string[] =>
|
|
534
|
+
path
|
|
535
|
+
.split('/')
|
|
536
|
+
.map((segment) => segment.trim())
|
|
537
|
+
.filter(Boolean);
|
|
538
|
+
const presentedLabels = new Map<string, string>();
|
|
539
|
+
for (const group of groups) {
|
|
540
|
+
for (const story of group.stories) {
|
|
541
|
+
const componentName =
|
|
542
|
+
(story.title ? pathSegments(story.title).at(-1) : undefined) ??
|
|
543
|
+
pathSegments(group.label).at(-1);
|
|
544
|
+
presentedLabels.set(
|
|
545
|
+
story.id,
|
|
546
|
+
soloSections.has(group.key) ? (componentName ?? story.label) : story.label,
|
|
547
|
+
);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** Layout/label blocks: variant sections as themselves, solo sections merged
|
|
552
|
+
* per category (order of first appearance; a pathless category is an
|
|
553
|
+
* unlabeled block). Fixed for the board's lifetime, like the groups. */
|
|
554
|
+
const blocks = boardBlocks(groups);
|
|
555
|
+
|
|
556
|
+
for (const story of groups.flatMap((group) => group.stories)) {
|
|
557
|
+
const frame = document.createElement('section');
|
|
558
|
+
frame.dataset['testid'] = 'react-story-frame';
|
|
559
|
+
frame.dataset['storyId'] = story.id;
|
|
560
|
+
frame.dataset['storyLabel'] = story.label;
|
|
561
|
+
frame.dataset['storyGroup'] =
|
|
562
|
+
presentation.index.sections.find((section) => section.storyIds.includes(story.id))?.label ??
|
|
563
|
+
'';
|
|
564
|
+
frame.style.position = 'absolute';
|
|
565
|
+
frame.style.border = `1px solid ${themeVars.boundary.strong}`;
|
|
566
|
+
frame.style.borderRadius = '0';
|
|
567
|
+
// The transparency checkerboard lives on the FRAME, behind the content box
|
|
568
|
+
// it exactly covers — never as an inline background on `content` itself.
|
|
569
|
+
// `content` is a game-CSS scope root (`scoped-game-css.ts`), and an inline
|
|
570
|
+
// background there beats every rule in the game's own stylesheet, so a
|
|
571
|
+
// game whose `body` paints an opaque backdrop would have shown the
|
|
572
|
+
// editor's checkerboard through its own HUD. Behind the content box the
|
|
573
|
+
// checkerboard still shows through wherever the story authors nothing,
|
|
574
|
+
// which is the whole thing it is for.
|
|
575
|
+
frame.style.backgroundColor = REACT_STORY_TRANSPARENCY_BASE;
|
|
576
|
+
frame.style.backgroundImage = reactStoryTransparencyBackground();
|
|
577
|
+
frame.style.backgroundPosition = '0 0, 8px 8px';
|
|
578
|
+
frame.style.backgroundSize = '16px 16px';
|
|
579
|
+
frame.style.boxShadow = 'none';
|
|
580
|
+
|
|
581
|
+
const label = document.createElement('button');
|
|
582
|
+
label.type = 'button';
|
|
583
|
+
label.dataset['testid'] = 'react-story-frame-label';
|
|
584
|
+
label.dataset['storyId'] = story.id;
|
|
585
|
+
label.title = `${presentedLabels.get(story.id) ?? story.label} — click to select, double-click to zoom — ${story.modulePath}`;
|
|
586
|
+
label.textContent = presentedLabels.get(story.id) ?? story.label;
|
|
587
|
+
label.style.position = 'absolute';
|
|
588
|
+
label.style.height = `${FLOATING_LABEL_HEIGHT}px`;
|
|
589
|
+
label.style.padding = '0 2px';
|
|
590
|
+
label.style.border = '0';
|
|
591
|
+
label.style.appearance = 'none';
|
|
592
|
+
label.style.background = 'transparent';
|
|
593
|
+
label.style.color = themeVars.content.primary;
|
|
594
|
+
label.style.font = `600 12px ${themeVars.typography.sans}`;
|
|
595
|
+
label.style.lineHeight = `${FLOATING_LABEL_HEIGHT}px`;
|
|
596
|
+
label.style.textAlign = 'left';
|
|
597
|
+
label.style.cursor = 'default';
|
|
598
|
+
label.style.pointerEvents = 'auto';
|
|
599
|
+
label.style.whiteSpace = 'nowrap';
|
|
600
|
+
label.style.overflow = 'hidden';
|
|
601
|
+
label.style.textOverflow = 'ellipsis';
|
|
602
|
+
label.style.width = 'max-content';
|
|
603
|
+
label.addEventListener('click', () => onStorySelected(story.id, 'highlight'));
|
|
604
|
+
label.addEventListener('dblclick', () => onStorySelected(story.id, 'zoom'));
|
|
605
|
+
|
|
606
|
+
const content = document.createElement('div');
|
|
607
|
+
content.dataset['testid'] = 'react-story-frame-content';
|
|
608
|
+
content.dataset['storyId'] = story.id;
|
|
609
|
+
content.style.position = 'absolute';
|
|
610
|
+
content.style.left = '0';
|
|
611
|
+
content.style.top = '0';
|
|
612
|
+
content.style.overflow = 'hidden';
|
|
613
|
+
content.style.contain = 'layout paint';
|
|
614
|
+
content.style.boxSizing = 'border-box';
|
|
615
|
+
|
|
616
|
+
// WHY A CARD EXPLAINS ITSELF. A component whose layout lives in its
|
|
617
|
+
// game's page-level stylesheet renders, without that sheet, as real DOM
|
|
618
|
+
// with zero layout — and a person looking at the board reads that as the
|
|
619
|
+
// editor being broken, not as a missing declaration (it was read that way
|
|
620
|
+
// three times before this note existed). So the card says what is missing,
|
|
621
|
+
// in the place the missing thing is SEEN. It is board CHROME, a sibling of
|
|
622
|
+
// `content` and deliberately never inside it: the authoring adapter walks
|
|
623
|
+
// `activeContent().children` as the story's own source tree, and an
|
|
624
|
+
// editor-owned div in there would surface in the hierarchy as an element
|
|
625
|
+
// the story does not have.
|
|
626
|
+
const note = document.createElement('div');
|
|
627
|
+
note.dataset['testid'] = 'react-story-frame-note';
|
|
628
|
+
note.dataset['storyId'] = story.id;
|
|
629
|
+
note.style.position = 'absolute';
|
|
630
|
+
note.style.left = '0';
|
|
631
|
+
note.style.right = '0';
|
|
632
|
+
note.style.bottom = '0';
|
|
633
|
+
note.style.display = 'none';
|
|
634
|
+
note.style.padding = '6px 8px';
|
|
635
|
+
note.style.boxSizing = 'border-box';
|
|
636
|
+
note.style.background = themeVars.surface.overlay;
|
|
637
|
+
note.style.color = themeVars.content.muted;
|
|
638
|
+
note.style.font = `11px ${themeVars.typography.sans}`;
|
|
639
|
+
note.style.pointerEvents = 'none';
|
|
640
|
+
|
|
641
|
+
const error = document.createElement('div');
|
|
642
|
+
error.dataset['testid'] = 'react-story-frame-error';
|
|
643
|
+
error.dataset['storyId'] = story.id;
|
|
644
|
+
error.style.position = 'absolute';
|
|
645
|
+
error.style.inset = '0';
|
|
646
|
+
error.style.display = 'none';
|
|
647
|
+
error.style.padding = '16px';
|
|
648
|
+
error.style.boxSizing = 'border-box';
|
|
649
|
+
error.style.background = themeVars.surface.overlay;
|
|
650
|
+
error.style.color = themeVars.semantic.danger;
|
|
651
|
+
error.style.font = `12px ${themeVars.typography.mono}`;
|
|
652
|
+
error.style.pointerEvents = 'none';
|
|
653
|
+
|
|
654
|
+
frame.append(content, note, error);
|
|
655
|
+
boardElement.appendChild(frame);
|
|
656
|
+
chromeElement.appendChild(label);
|
|
657
|
+
frames.set(story.id, { element: frame, content });
|
|
658
|
+
errors.set(story.id, error);
|
|
659
|
+
notes.set(story.id, note);
|
|
660
|
+
labels.set(story.id, label);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Group names are chrome of the SAME floating kind as the frame labels —
|
|
664
|
+
// constant size above a zooming board, in the same synchronized sibling
|
|
665
|
+
// layer. They name a cluster; they are not selectable, so unlike a frame
|
|
666
|
+
// label they take no pointer events.
|
|
667
|
+
// One block needs no name over it — a single title above the whole board
|
|
668
|
+
// distinguishes nothing, which is the same rule the single-group board
|
|
669
|
+
// always held.
|
|
670
|
+
const showBlockLabels = blocks.length > 1;
|
|
671
|
+
if (showBlockLabels) {
|
|
672
|
+
for (const block of blocks) {
|
|
673
|
+
// An unlabeled block (pathless solo cards) shows nothing above itself —
|
|
674
|
+
// its cards already wear their component names.
|
|
675
|
+
if (block.label === null) continue;
|
|
676
|
+
const groupLabel = document.createElement('div');
|
|
677
|
+
groupLabel.dataset['testid'] = 'react-story-group-label';
|
|
678
|
+
groupLabel.dataset['storyGroup'] = block.key;
|
|
679
|
+
groupLabel.textContent = block.label;
|
|
680
|
+
groupLabel.style.position = 'absolute';
|
|
681
|
+
groupLabel.style.height = `${GROUP_LABEL_HEIGHT}px`;
|
|
682
|
+
groupLabel.style.lineHeight = `${GROUP_LABEL_HEIGHT}px`;
|
|
683
|
+
groupLabel.style.color = themeVars.content.primary;
|
|
684
|
+
groupLabel.style.font = `700 14px ${themeVars.typography.sans}`;
|
|
685
|
+
groupLabel.style.whiteSpace = 'nowrap';
|
|
686
|
+
groupLabel.style.pointerEvents = 'none';
|
|
687
|
+
groupLabel.style.width = 'max-content';
|
|
688
|
+
chromeElement.appendChild(groupLabel);
|
|
689
|
+
groupLabels.set(block.key, groupLabel);
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const updateActiveAppearance = (): void => {
|
|
694
|
+
for (const [storyId, frame] of frames) {
|
|
695
|
+
const active = storyId === activeStoryId;
|
|
696
|
+
frame.element.dataset['active'] = active ? 'true' : 'false';
|
|
697
|
+
frame.element.style.outline = active ? `2px solid ${themeVars.accent.default}` : 'none';
|
|
698
|
+
frame.element.style.outlineOffset = active ? '3px' : '0';
|
|
699
|
+
const label = labels.get(storyId)!;
|
|
700
|
+
label.setAttribute('aria-pressed', String(active));
|
|
701
|
+
label.style.color = active ? themeVars.accent.default : themeVars.content.muted;
|
|
702
|
+
}
|
|
703
|
+
};
|
|
704
|
+
|
|
705
|
+
/** The authored room above a row, in client px at the current zoom. */
|
|
706
|
+
const clientRoom = (authored: number, zoom: number): number =>
|
|
707
|
+
Number.isFinite(authored) ? authored * zoom : Number.POSITIVE_INFINITY;
|
|
708
|
+
|
|
709
|
+
// Story names are editor chrome, not board content. Convert each authored-
|
|
710
|
+
// space frame origin into client-space placement instead of transforming
|
|
711
|
+
// the label subtree; font size, height, and hit target therefore remain
|
|
712
|
+
// constant while the frames zoom underneath them, matching Figma labels.
|
|
713
|
+
// At far zoom that constant chrome no longer fits the gap it lives in, so
|
|
714
|
+
// `placeStoryChrome` degrades it (scale, then fade, then drop) rather than
|
|
715
|
+
// letting it paint over the frames above — see story-board-chrome-fit.ts.
|
|
716
|
+
const positionLabels = (): void => {
|
|
717
|
+
const view = getRootPan();
|
|
718
|
+
for (const [storyId, frameLayout] of layouts) {
|
|
719
|
+
const label = labels.get(storyId)!;
|
|
720
|
+
const { frame } = placeStoryChrome(
|
|
721
|
+
clientRoom(frameLayout.roomAbove, view.zoom),
|
|
722
|
+
frameLayout.withGroupLabel,
|
|
723
|
+
);
|
|
724
|
+
label.style.left = `${view.x + frameLayout.x * view.zoom}px`;
|
|
725
|
+
label.style.top = `${view.y + frameLayout.y * view.zoom - frame.offset}px`;
|
|
726
|
+
// A floating name may borrow half the inter-frame gap, but never the
|
|
727
|
+
// next frame's column. The old 80px floor made labels for small 2D
|
|
728
|
+
// sprites collide at overview zoom even while their frames were apart;
|
|
729
|
+
// at extreme zoom a title truncates, then disappears below the readable
|
|
730
|
+
// width above instead of claiming space from its neighbour.
|
|
731
|
+
const maxWidth = Math.max(1, (frameLayout.width + FRAME_GAP / 2) * view.zoom);
|
|
732
|
+
label.style.maxWidth = `${maxWidth}px`;
|
|
733
|
+
applyStoryChrome(
|
|
734
|
+
label,
|
|
735
|
+
maxWidth >= MIN_FRAME_LABEL_WIDTH ? frame : { ...frame, opacity: 0, visible: false },
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
for (const [key, origin] of groupOrigins) {
|
|
739
|
+
const groupLabel = groupLabels.get(key);
|
|
740
|
+
if (!groupLabel) continue;
|
|
741
|
+
const { group } = placeStoryChrome(clientRoom(origin.roomAbove, view.zoom), true);
|
|
742
|
+
if (!group) continue;
|
|
743
|
+
groupLabel.style.left = `${view.x + origin.x * view.zoom}px`;
|
|
744
|
+
groupLabel.style.top = `${view.y + origin.y * view.zoom - group.offset}px`;
|
|
745
|
+
applyStoryChrome(groupLabel, group);
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
|
|
749
|
+
// One block per Storybook component, stacked top to bottom. Frames inside a
|
|
750
|
+
// block may differ in size because each story owns its viewport globals.
|
|
751
|
+
const layout = (): void => {
|
|
752
|
+
layouts.clear();
|
|
753
|
+
groupOrigins.clear();
|
|
754
|
+
const placement = reactStoryBoardLayout(stories, presentation.index, presentationFor);
|
|
755
|
+
for (const [key, origin] of placement.groupOrigins) groupOrigins.set(key, origin);
|
|
756
|
+
for (const story of stories) {
|
|
757
|
+
const frameLayout = placement.layouts.get(story.id);
|
|
758
|
+
if (!frameLayout) continue;
|
|
759
|
+
layouts.set(story.id, frameLayout);
|
|
760
|
+
const { x, y, width, height } = frameLayout;
|
|
761
|
+
const presentation = presentationFor(story);
|
|
762
|
+
const frame = frames.get(story.id)!;
|
|
763
|
+
frame.element.style.left = `${x}px`;
|
|
764
|
+
frame.element.style.top = `${y}px`;
|
|
765
|
+
frame.element.style.width = `${width}px`;
|
|
766
|
+
frame.element.style.height = `${height}px`;
|
|
767
|
+
// A measured story's CONTENT stays at its mount-harness size and is
|
|
768
|
+
// translated so the measured rectangle fills the frame — the frame is
|
|
769
|
+
// a window onto the declared viewport, exactly the canvas exhibit's
|
|
770
|
+
// crop. Everything else keeps content == frame.
|
|
771
|
+
const measured = measuredFrames.get(story.id);
|
|
772
|
+
frame.element.style.overflow = measured ? 'hidden' : '';
|
|
773
|
+
frame.content.style.width = `${measured ? measured.mount.width : width}px`;
|
|
774
|
+
frame.content.style.height = `${measured ? measured.mount.height : height}px`;
|
|
775
|
+
frame.content.style.left = `${measured ? -measured.offset.x : 0}px`;
|
|
776
|
+
frame.content.style.top = `${measured ? -measured.offset.y : 0}px`;
|
|
777
|
+
frame.content.dataset['storyLayout'] = presentation.layout;
|
|
778
|
+
frame.content.style.padding = presentation.layout === 'padded' ? '16px' : '0';
|
|
779
|
+
frame.content.style.display = presentation.layout === 'centered' ? 'flex' : 'block';
|
|
780
|
+
frame.content.style.alignItems = presentation.layout === 'centered' ? 'center' : '';
|
|
781
|
+
frame.content.style.justifyContent = presentation.layout === 'centered' ? 'center' : '';
|
|
782
|
+
}
|
|
783
|
+
const boardWidth = placement.width;
|
|
784
|
+
const boardHeight = placement.height;
|
|
785
|
+
boardElement.style.width = `${boardWidth}px`;
|
|
786
|
+
boardElement.style.height = `${boardHeight}px`;
|
|
787
|
+
layer.style.width = `${boardWidth}px`;
|
|
788
|
+
layer.style.height = `${boardHeight}px`;
|
|
789
|
+
positionLabels();
|
|
790
|
+
};
|
|
791
|
+
|
|
792
|
+
const fitAll = (): void => {
|
|
793
|
+
const width = Number.parseFloat(boardElement.style.width);
|
|
794
|
+
const height = Number.parseFloat(boardElement.style.height);
|
|
795
|
+
fitBounds(container, { x: 0, y: 0, width, height }, 1);
|
|
796
|
+
};
|
|
797
|
+
|
|
798
|
+
const scheduleFitActive = (): void => {
|
|
799
|
+
if (pendingFit) return;
|
|
800
|
+
pendingFit = true;
|
|
801
|
+
queueMicrotask(() => {
|
|
802
|
+
pendingFit = false;
|
|
803
|
+
if (!disposed) activate(activeStoryId, true);
|
|
804
|
+
});
|
|
805
|
+
};
|
|
806
|
+
|
|
807
|
+
const activate = (storyId: string, focus = false): void => {
|
|
808
|
+
const frame = frames.get(storyId);
|
|
809
|
+
const frameLayout = layouts.get(storyId);
|
|
810
|
+
if (!frame || !frameLayout) return;
|
|
811
|
+
activeStoryId = storyId;
|
|
812
|
+
updateActiveAppearance();
|
|
813
|
+
updateViewportSnapshot();
|
|
814
|
+
notifyBoardViewport();
|
|
815
|
+
if (focus) fitBounds(container, frameLayout, 2);
|
|
816
|
+
};
|
|
817
|
+
|
|
818
|
+
const storyAtClientPoint = (clientX: number, clientY: number): string | null => {
|
|
819
|
+
for (const [storyId, frame] of frames) {
|
|
820
|
+
const rect = frame.content.getBoundingClientRect();
|
|
821
|
+
if (
|
|
822
|
+
clientX >= rect.left &&
|
|
823
|
+
clientX <= rect.right &&
|
|
824
|
+
clientY >= rect.top &&
|
|
825
|
+
clientY <= rect.bottom
|
|
826
|
+
) {
|
|
827
|
+
return storyId;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
return null;
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
const unsubscribeViewport = subscribeRootCanvasViewport(() => {
|
|
834
|
+
layout();
|
|
835
|
+
scheduleFitActive();
|
|
836
|
+
});
|
|
837
|
+
const unsubscribePan = subscribeRootPan(positionLabels);
|
|
838
|
+
|
|
839
|
+
layout();
|
|
840
|
+
updateActiveAppearance();
|
|
841
|
+
positionLabels();
|
|
842
|
+
|
|
843
|
+
const board: ReactStoryBoard = {
|
|
844
|
+
element: boardElement,
|
|
845
|
+
frames,
|
|
846
|
+
activeContent: () => frames.get(activeStoryId)!.content,
|
|
847
|
+
activate,
|
|
848
|
+
clearError: (storyId) => {
|
|
849
|
+
const error = errors.get(storyId);
|
|
850
|
+
if (!error) return;
|
|
851
|
+
error.style.display = 'none';
|
|
852
|
+
error.textContent = '';
|
|
853
|
+
},
|
|
854
|
+
fitAll,
|
|
855
|
+
selectAtClientPoint: (clientX, clientY) => {
|
|
856
|
+
const storyId = storyAtClientPoint(clientX, clientY);
|
|
857
|
+
if (!storyId || storyId === activeStoryId) return false;
|
|
858
|
+
onStorySelected(storyId, 'highlight');
|
|
859
|
+
return true;
|
|
860
|
+
},
|
|
861
|
+
zoomAtClientPoint: (clientX, clientY) => {
|
|
862
|
+
const storyId = storyAtClientPoint(clientX, clientY);
|
|
863
|
+
if (!storyId) return false;
|
|
864
|
+
onStorySelected(storyId, 'zoom');
|
|
865
|
+
return true;
|
|
866
|
+
},
|
|
867
|
+
setError: (storyId, message) => {
|
|
868
|
+
const error = errors.get(storyId);
|
|
869
|
+
if (!error) return;
|
|
870
|
+
error.textContent = message;
|
|
871
|
+
error.style.display = 'block';
|
|
872
|
+
},
|
|
873
|
+
setMeasuredFrame: (storyId, measured) => {
|
|
874
|
+
if (disposed || !frames.has(storyId)) return;
|
|
875
|
+
const previous = measuredFrames.get(storyId);
|
|
876
|
+
if (measured === null) {
|
|
877
|
+
if (!previous) return;
|
|
878
|
+
measuredFrames.delete(storyId);
|
|
879
|
+
} else {
|
|
880
|
+
if (
|
|
881
|
+
previous &&
|
|
882
|
+
previous.frame.width === measured.frame.width &&
|
|
883
|
+
previous.frame.height === measured.frame.height &&
|
|
884
|
+
previous.mount.width === measured.mount.width &&
|
|
885
|
+
previous.mount.height === measured.mount.height &&
|
|
886
|
+
previous.offset.x === measured.offset.x &&
|
|
887
|
+
previous.offset.y === measured.offset.y
|
|
888
|
+
) {
|
|
889
|
+
return;
|
|
890
|
+
}
|
|
891
|
+
measuredFrames.set(storyId, measured);
|
|
892
|
+
}
|
|
893
|
+
layout();
|
|
894
|
+
positionLabels();
|
|
895
|
+
},
|
|
896
|
+
setNote: (message) => {
|
|
897
|
+
for (const note of notes.values()) {
|
|
898
|
+
note.textContent = message ?? '';
|
|
899
|
+
note.style.display = message === null ? 'none' : 'block';
|
|
900
|
+
}
|
|
901
|
+
},
|
|
902
|
+
viewport: () => viewportSnapshot,
|
|
903
|
+
setViewport: (viewportId) => {
|
|
904
|
+
if (viewportSnapshot.locked) return;
|
|
905
|
+
viewportOverrides.set(activeStoryId, viewportId);
|
|
906
|
+
layout();
|
|
907
|
+
updateViewportSnapshot();
|
|
908
|
+
notifyBoardViewport();
|
|
909
|
+
activate(activeStoryId, true);
|
|
910
|
+
},
|
|
911
|
+
setRotation: (rotated) => {
|
|
912
|
+
if (viewportSnapshot.locked) return;
|
|
913
|
+
rotationOverrides.set(activeStoryId, rotated);
|
|
914
|
+
layout();
|
|
915
|
+
updateViewportSnapshot();
|
|
916
|
+
notifyBoardViewport();
|
|
917
|
+
activate(activeStoryId, true);
|
|
918
|
+
},
|
|
919
|
+
dispose: () => {
|
|
920
|
+
if (disposed) return;
|
|
921
|
+
disposed = true;
|
|
922
|
+
unsubscribeViewport();
|
|
923
|
+
unsubscribePan();
|
|
924
|
+
if (boardsByContainer.get(container) === board) boardsByContainer.delete(container);
|
|
925
|
+
notifyBoardViewport();
|
|
926
|
+
boardElement.remove();
|
|
927
|
+
chromeElement.remove();
|
|
928
|
+
},
|
|
929
|
+
};
|
|
930
|
+
boardsByContainer.set(container, board);
|
|
931
|
+
notifyBoardViewport();
|
|
932
|
+
// Open at a useful editing scale on the active story. The complete-board
|
|
933
|
+
// overview remains one explicit "Fit board" click away; auto-fitting every
|
|
934
|
+
// story made desktop-resolution screens postage stamps on first open.
|
|
935
|
+
scheduleFitActive();
|
|
936
|
+
return board;
|
|
937
|
+
}
|