@volter/sdk 0.0.0-stage → 0.5.203
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 +202 -0
- package/NOTICE +20 -0
- package/README.md +38 -3
- package/package.json +510 -4
- package/src/account.ts +210 -0
- package/src/chrome.ts +88 -0
- package/src/client.ts +1646 -0
- package/src/commands.ts +66 -0
- package/src/contributions.ts +619 -0
- package/src/css-numeric-style.ts +97 -0
- package/src/document-probe.ts +282 -0
- package/src/editor-view.ts +225 -0
- package/src/extension.ts +40 -0
- package/src/generations.ts +178 -0
- package/src/host.ts +1157 -0
- package/src/http-transport.browser.ts +14 -0
- package/src/http-transport.node.ts +19 -0
- package/src/index.ts +131 -0
- package/src/kit/CapabilityCoverageSection.tsx +185 -0
- package/src/kit/account-client.ts +333 -0
- package/src/kit/action-registry.ts +317 -0
- package/src/kit/active-product.ts +76 -0
- package/src/kit/active-project.ts +155 -0
- package/src/kit/adapter-editor-config.ts +25 -0
- package/src/kit/adapter-module.ts +7 -0
- package/src/kit/adapter-observation.ts +49 -0
- package/src/kit/animation/animation-clock.ts +479 -0
- package/src/kit/animation/stage-transport.ts +385 -0
- package/src/kit/api/assets.ts +365 -0
- package/src/kit/api/project-open.ts +355 -0
- package/src/kit/api/project-source.ts +180 -0
- package/src/kit/api/project-state.ts +110 -0
- package/src/kit/api/relay.ts +270 -0
- package/src/kit/api/themes.ts +45 -0
- package/src/kit/api-asset-library-wire.ts +45 -0
- package/src/kit/api-base.ts +10 -0
- package/src/kit/api-build.ts +99 -0
- package/src/kit/api-git-wire.ts +56 -0
- package/src/kit/api-logs.ts +92 -0
- package/src/kit/api-project-identity.ts +74 -0
- package/src/kit/api-settings.ts +36 -0
- package/src/kit/api-worktrees.ts +205 -0
- package/src/kit/asset-capabilities.ts +344 -0
- package/src/kit/asset-compare-core.ts +171 -0
- package/src/kit/asset-editor-context.tsx +101 -0
- package/src/kit/asset-events.ts +96 -0
- package/src/kit/asset-inspector-actions.ts +87 -0
- package/src/kit/asset-selection-viewer-registry.ts +113 -0
- package/src/kit/asset-selection.ts +146 -0
- package/src/kit/asset-thumbnails.ts +25 -0
- package/src/kit/asset-viewers.ts +115 -0
- package/src/kit/asset-workflow/asset-import-jobs.ts +106 -0
- package/src/kit/asset-workflow/asset-ledger-backend.ts +126 -0
- package/src/kit/asset-workflow/asset-ledger.ts +156 -0
- package/src/kit/asset-workflow/asset-materialization-report.ts +140 -0
- package/src/kit/asset-workflow/asset-pack-manifest.ts +320 -0
- package/src/kit/asset-workflow/asset-types.ts +142 -0
- package/src/kit/asset-workflow/audio-preview-player.ts +193 -0
- package/src/kit/asset-workflow/audio-waveform.ts +22 -0
- package/src/kit/asset-workflow/cloud-asset-client.ts +263 -0
- package/src/kit/asset-workflow/hosted-asset-materialization.ts +236 -0
- package/src/kit/asset-workflow/image-view-scale.ts +31 -0
- package/src/kit/asset-workflow/import-contract.ts +124 -0
- package/src/kit/asset-workflow/ledger-write-lock.ts +244 -0
- package/src/kit/asset-workflow/pixi-spritesheet.ts +197 -0
- package/src/kit/asset-workflow/preview-resource-lifetime.ts +44 -0
- package/src/kit/asset-workflow/project-asset-commands.ts +20 -0
- package/src/kit/asset-workflow/project-content.ts +288 -0
- package/src/kit/asset-workflow/project-source-index.ts +545 -0
- package/src/kit/asset-workflow/thumbnail-system.ts +256 -0
- package/src/kit/authoring/active-adapter.ts +200 -0
- package/src/kit/authoring/active-systems.ts +422 -0
- package/src/kit/authoring/adapter-key.ts +18 -0
- package/src/kit/authoring/authoring-asset-url.ts +27 -0
- package/src/kit/authoring/bootstrap-state.ts +49 -0
- package/src/kit/authoring/boundary-authoring-adapter.ts +189 -0
- package/src/kit/authoring/canvas-scene-guides.ts +84 -0
- package/src/kit/authoring/composite-authoring-adapter.ts +2109 -0
- package/src/kit/authoring/consumer-actions.ts +531 -0
- package/src/kit/authoring/design-time-layers.ts +852 -0
- package/src/kit/authoring/design-time-mount-registry.ts +244 -0
- package/src/kit/authoring/edit-mode-authoring.ts +637 -0
- package/src/kit/authoring/empty-project-authoring.ts +22 -0
- package/src/kit/authoring/instance-source-menu.ts +135 -0
- package/src/kit/authoring/layered-pick.ts +185 -0
- package/src/kit/authoring/mounted-root-subjects.ts +146 -0
- package/src/kit/authoring/no-authoring-adapter.ts +59 -0
- package/src/kit/authoring/object3d-document-persistence.ts +122 -0
- package/src/kit/authoring/panel-authoring.ts +121 -0
- package/src/kit/authoring/project-authoring-session.ts +105 -0
- package/src/kit/authoring/provenance.ts +99 -0
- package/src/kit/authoring/react-canvas-navigation.ts +259 -0
- package/src/kit/authoring/react-design-canvas-style.ts +20 -0
- package/src/kit/authoring/react-story-board.ts +917 -0
- package/src/kit/authoring/selection-scope.ts +195 -0
- package/src/kit/authoring/shell-document-ops.ts +169 -0
- package/src/kit/authoring/story-board-chrome-fit.ts +107 -0
- package/src/kit/authoring/story-board-presentation.ts +111 -0
- package/src/kit/authoring/three-root.ts +67 -0
- package/src/kit/authoring/viewport-tool-context.ts +73 -0
- package/src/kit/authoring/viewport-tool-owner.ts +38 -0
- package/src/kit/authoring/world-session-state.ts +101 -0
- package/src/kit/authoring-seam-evidence.ts +300 -0
- package/src/kit/availability-tick.ts +66 -0
- package/src/kit/bitmap-label.ts +120 -0
- package/src/kit/boot-routing.ts +392 -0
- package/src/kit/breakpoint-state.ts +43 -0
- package/src/kit/build-identity.ts +16 -0
- package/src/kit/bytes-codec.ts +62 -0
- package/src/kit/cancellation-reason.ts +58 -0
- package/src/kit/canvas-frames.ts +88 -0
- package/src/kit/capture-camera-pose.ts +77 -0
- package/src/kit/capture-size.ts +88 -0
- package/src/kit/chrome-registry.ts +159 -0
- package/src/kit/chrome-slot-registry.ts +91 -0
- package/src/kit/collaboration-client.ts +264 -0
- package/src/kit/collaboration-presence.ts +41 -0
- package/src/kit/command-dispatch.ts +19 -0
- package/src/kit/command-listener.ts +2182 -0
- package/src/kit/command-registry.ts +71 -0
- package/src/kit/component-board-registry.ts +205 -0
- package/src/kit/component-states-registry.ts +199 -0
- package/src/kit/components/AlignToolbar.tsx +204 -0
- package/src/kit/components/ApplicationMenus.tsx +372 -0
- package/src/kit/components/AssetEditorShell.tsx +216 -0
- package/src/kit/components/AssetInspectorToolSection.tsx +124 -0
- package/src/kit/components/BoardRulers.tsx +354 -0
- package/src/kit/components/CanvasAddNodeDialogs.tsx +529 -0
- package/src/kit/components/CanvasSceneViewport.tsx +1195 -0
- package/src/kit/components/ChromeSlot.tsx +20 -0
- package/src/kit/components/CodeView.tsx +470 -0
- package/src/kit/components/CompactInspectorShell.tsx +39 -0
- package/src/kit/components/ConsolePanel.tsx +273 -0
- package/src/kit/components/GameplaySessionTimeline.tsx +295 -0
- package/src/kit/components/InspectionProjection.tsx +932 -0
- package/src/kit/components/Inspector.tsx +270 -0
- package/src/kit/components/InspectorCanvasPreview.tsx +35 -0
- package/src/kit/components/InspectorFieldsSection.tsx +290 -0
- package/src/kit/components/InspectorStoriesSection.tsx +92 -0
- package/src/kit/components/InspectorToolSection.tsx +96 -0
- package/src/kit/components/InspectorTransformSection.tsx +245 -0
- package/src/kit/components/LightExplorerPanel.tsx +433 -0
- package/src/kit/components/MediaProperties.tsx +145 -0
- package/src/kit/components/ProjectHeader.tsx +328 -0
- package/src/kit/components/ReactCanvasControls.tsx +358 -0
- package/src/kit/components/RootSelectionOverlay.tsx +3688 -0
- package/src/kit/components/RootTextEditor.tsx +79 -0
- package/src/kit/components/SaveStatus.tsx +70 -0
- package/src/kit/components/SurfaceCrashBoundary.tsx +105 -0
- package/src/kit/components/SurfaceStateOverlay.tsx +24 -0
- package/src/kit/components/ToolContributionSurfaces.tsx +49 -0
- package/src/kit/components/ToolHost.tsx +380 -0
- package/src/kit/components/Toolbar.tsx +811 -0
- package/src/kit/components/TransientHint.tsx +44 -0
- package/src/kit/components/VersionControlSection.tsx +470 -0
- package/src/kit/components/ViewportOverlaysMenu.tsx +177 -0
- package/src/kit/components/VolterLogo.tsx +18 -0
- package/src/kit/components/WorktreeSwitcher.tsx +712 -0
- package/src/kit/components/account-documents.tsx +1162 -0
- package/src/kit/components/asset-documents.tsx +794 -0
- package/src/kit/components/asset-editor-persistence.ts +216 -0
- package/src/kit/components/asset-selection-section.tsx +545 -0
- package/src/kit/components/asset-thumbnails.tsx +307 -0
- package/src/kit/components/asset-viewers/AudioViewer.tsx +201 -0
- package/src/kit/components/asset-viewers/GenericJsonViewer.tsx +102 -0
- package/src/kit/components/asset-viewers/ImageViewer.tsx +300 -0
- package/src/kit/components/asset-viewers/JsonAssetDocument.tsx +98 -0
- package/src/kit/components/asset-viewers/OnlineAssetDetail.tsx +426 -0
- package/src/kit/components/asset-viewers/SourceAssetViewer.tsx +356 -0
- package/src/kit/components/asset-viewers/SpritesheetSpriteView.tsx +102 -0
- package/src/kit/components/asset-viewers/VideoViewer.tsx +101 -0
- package/src/kit/components/asset-viewers/shader-source.ts +144 -0
- package/src/kit/components/board-guides.ts +150 -0
- package/src/kit/components/canvas-scene-hotkeys.ts +37 -0
- package/src/kit/components/canvas-temporary-pivot.ts +34 -0
- package/src/kit/components/core-utilities.tsx +94 -0
- package/src/kit/components/inspector-preview-section.tsx +223 -0
- package/src/kit/components/inspector-revert-label.ts +20 -0
- package/src/kit/components/inspector-selection.ts +42 -0
- package/src/kit/components/inspector-stories-gating.ts +171 -0
- package/src/kit/components/inspector-transform-subject.ts +11 -0
- package/src/kit/components/inspector-transform.ts +88 -0
- package/src/kit/components/kind-documents.tsx +544 -0
- package/src/kit/components/primitives/DraftColorInput.tsx +74 -0
- package/src/kit/components/project-tool-documents.tsx +402 -0
- package/src/kit/components/scene-documents.tsx +221 -0
- package/src/kit/components/status-contributions.tsx +407 -0
- package/src/kit/components/tool-documents.tsx +302 -0
- package/src/kit/components/tool-schema-form.tsx +262 -0
- package/src/kit/components/use-after-paint.ts +41 -0
- package/src/kit/components/use-project-image-assets.ts +86 -0
- package/src/kit/components/workspace-history.ts +32 -0
- package/src/kit/components/world-documents.tsx +570 -0
- package/src/kit/components/world-overlay-gestures.ts +1939 -0
- package/src/kit/composite-screenshot.ts +2238 -0
- package/src/kit/content-entry-source-registry.ts +184 -0
- package/src/kit/contribution-surfaces.ts +48 -0
- package/src/kit/coverage/canvas-reveal.ts +192 -0
- package/src/kit/coverage/design-time-surfaces.ts +101 -0
- package/src/kit/coverage/ontology-invariants.ts +466 -0
- package/src/kit/coverage/session-vitals.ts +503 -0
- package/src/kit/crash-null-boundary.ts +36 -0
- package/src/kit/creation-site-edit.ts +1491 -0
- package/src/kit/creation-site-registry.ts +160 -0
- package/src/kit/delegate-harness-registry.ts +134 -0
- package/src/kit/document-areas.ts +70 -0
- package/src/kit/document-context-registry.ts +193 -0
- package/src/kit/document-open-registry.ts +200 -0
- package/src/kit/document-play-extension.ts +221 -0
- package/src/kit/document-preview-source.ts +20 -0
- package/src/kit/document-renderer-session.ts +138 -0
- package/src/kit/document-stage-sessions.ts +26 -0
- package/src/kit/document-viewports.ts +120 -0
- package/src/kit/editor-api.ts +46 -0
- package/src/kit/editor-chrome-capture.ts +136 -0
- package/src/kit/editor-commands.ts +176 -0
- package/src/kit/editor-console.ts +580 -0
- package/src/kit/editor-current-view.ts +56 -0
- package/src/kit/editor-document-probe.ts +1166 -0
- package/src/kit/editor-git-client.ts +115 -0
- package/src/kit/editor-hotkeys.ts +728 -0
- package/src/kit/editor-lease-view.ts +39 -0
- package/src/kit/editor-lease.ts +415 -0
- package/src/kit/editor-mode.ts +19 -0
- package/src/kit/editor-notifications.ts +140 -0
- package/src/kit/editor-presence.ts +563 -0
- package/src/kit/editor-presentation-activity.ts +58 -0
- package/src/kit/editor-presentation-notice.ts +42 -0
- package/src/kit/editor-runtime.tsx +147 -0
- package/src/kit/editor-server-response.ts +86 -0
- package/src/kit/editor-session-attribution.ts +85 -0
- package/src/kit/editor-session-mode.ts +54 -0
- package/src/kit/editor-state-facets.ts +74 -0
- package/src/kit/editor-view-presentation.ts +777 -0
- package/src/kit/environment-images.ts +58 -0
- package/src/kit/eyedropper-session.ts +60 -0
- package/src/kit/files/file-provider.ts +62 -0
- package/src/kit/files/project-files.ts +270 -0
- package/src/kit/finders/index.ts +137 -0
- package/src/kit/finders/scenes-from-entrypoint-selection.ts +387 -0
- package/src/kit/frame/frame-parts.ts +30 -0
- package/src/kit/framed-document-capture.ts +34 -0
- package/src/kit/game-globals-prelude.ts +143 -0
- package/src/kit/game-surface-defaults.ts +33 -0
- package/src/kit/gameplay-dom-recording.ts +318 -0
- package/src/kit/gameplay-export-state.ts +14 -0
- package/src/kit/gameplay-replay.ts +417 -0
- package/src/kit/gameplay-session-time.ts +9 -0
- package/src/kit/gameplay-sessions.ts +204 -0
- package/src/kit/hierarchy-component-marks.ts +298 -0
- package/src/kit/hierarchy-internals.ts +197 -0
- package/src/kit/hierarchy-kind-icon.ts +217 -0
- package/src/kit/hierarchy-menu-registry.ts +67 -0
- package/src/kit/hierarchy-node-rows.ts +307 -0
- package/src/kit/hierarchy-panel-view.ts +280 -0
- package/src/kit/hierarchy-projection.ts +76 -0
- package/src/kit/hierarchy-row-media.ts +45 -0
- package/src/kit/hierarchy-row-model.ts +308 -0
- package/src/kit/hierarchy-rows.ts +11 -0
- package/src/kit/hierarchy-walk.ts +86 -0
- package/src/kit/history/editor-session.ts +25 -0
- package/src/kit/history/history-commands.ts +147 -0
- package/src/kit/history/history-delegate.ts +187 -0
- package/src/kit/history/history-limit-notices.ts +43 -0
- package/src/kit/history/history-service.ts +1189 -0
- package/src/kit/history/persistence-coordinator.ts +35 -0
- package/src/kit/history/project-file-history.ts +386 -0
- package/src/kit/history/project-root-history-backends.ts +139 -0
- package/src/kit/history/resource-registry.ts +209 -0
- package/src/kit/history/snapshot-store.ts +103 -0
- package/src/kit/history/source-history-backend.ts +546 -0
- package/src/kit/history-types.ts +124 -0
- package/src/kit/hmr-registration-group.ts +67 -0
- package/src/kit/hmr-stable-react-context.ts +23 -0
- package/src/kit/hotkeys.ts +190 -0
- package/src/kit/inference-diagnostics.ts +69 -0
- package/src/kit/initial-project.ts +80 -0
- package/src/kit/inspection/active-subject.ts +571 -0
- package/src/kit/inspection/active-surface.ts +142 -0
- package/src/kit/inspection/compose-subject.ts +1055 -0
- package/src/kit/inspection/compose.ts +7 -0
- package/src/kit/inspection/display.ts +171 -0
- package/src/kit/inspection/document-subject.ts +109 -0
- package/src/kit/inspection/game-subject.ts +85 -0
- package/src/kit/inspection/null-subject.ts +119 -0
- package/src/kit/inspection/serialize.ts +357 -0
- package/src/kit/inspection/use-active-inspection.ts +180 -0
- package/src/kit/inspection-model.ts +542 -0
- package/src/kit/inspection-node-media.ts +58 -0
- package/src/kit/inspector-presentation.ts +203 -0
- package/src/kit/inspector-property-grouping.ts +64 -0
- package/src/kit/inspector-section-registry.ts +221 -0
- package/src/kit/instance-source-actions.ts +163 -0
- package/src/kit/js-heap.ts +71 -0
- package/src/kit/key-actions.ts +91 -0
- package/src/kit/keymap-presets.ts +428 -0
- package/src/kit/layout-policy.ts +31 -0
- package/src/kit/light-explorer-model.ts +134 -0
- package/src/kit/live-canvas-frame.ts +55 -0
- package/src/kit/live-document.ts +296 -0
- package/src/kit/live-gesture-lock.ts +50 -0
- package/src/kit/live-seam-evidence.ts +11 -0
- package/src/kit/live-session-registry.ts +220 -0
- package/src/kit/live-transition.ts +391 -0
- package/src/kit/manifest-project.ts +107 -0
- package/src/kit/module-fetch-diagnosis.ts +192 -0
- package/src/kit/mount-failure-report.ts +154 -0
- package/src/kit/native-selection-style.ts +497 -0
- package/src/kit/object3d-document-write-policy.ts +137 -0
- package/src/kit/packaged-runtime.ts +108 -0
- package/src/kit/palettes/maya.palette.json +57 -0
- package/src/kit/palettes/substance.palette.json +57 -0
- package/src/kit/performance-profiler.ts +367 -0
- package/src/kit/performance-sources.ts +69 -0
- package/src/kit/photograph-notice.ts +141 -0
- package/src/kit/play-boot-phase.ts +166 -0
- package/src/kit/play-camera-flight.ts +35 -0
- package/src/kit/png-encode.worker.ts +26 -0
- package/src/kit/presentation-surface.ts +248 -0
- package/src/kit/product-command.ts +90 -0
- package/src/kit/project-adapter.ts +1140 -0
- package/src/kit/project-asset-refresh.ts +23 -0
- package/src/kit/project-asset-roots.ts +68 -0
- package/src/kit/project-local-state.ts +151 -0
- package/src/kit/project-manager.ts +243 -0
- package/src/kit/project-module-changes.ts +201 -0
- package/src/kit/project-module-split.ts +270 -0
- package/src/kit/project-play-layers.ts +25 -0
- package/src/kit/project-provenance.ts +115 -0
- package/src/kit/project-ready.ts +42 -0
- package/src/kit/project-shape.ts +68 -0
- package/src/kit/project-tools.ts +107 -0
- package/src/kit/projection-types.ts +44 -0
- package/src/kit/readiness.ts +113 -0
- package/src/kit/renderer-resource-counts.ts +27 -0
- package/src/kit/reported-play-state.ts +90 -0
- package/src/kit/resolve-contributed-command.ts +14 -0
- package/src/kit/resolve-relative-specifier.ts +33 -0
- package/src/kit/retained-document-states.ts +91 -0
- package/src/kit/scene-document-plan.ts +320 -0
- package/src/kit/scene-live-open.ts +210 -0
- package/src/kit/scoped-game-css.ts +152 -0
- package/src/kit/served-url.ts +5 -0
- package/src/kit/session-close.ts +17 -0
- package/src/kit/session-tombstone.ts +127 -0
- package/src/kit/settings/settings-provider.ts +82 -0
- package/src/kit/settings-store.ts +348 -0
- package/src/kit/shell-document-state.ts +27 -0
- package/src/kit/shell-store-door.ts +45 -0
- package/src/kit/shell-store.ts +722 -0
- package/src/kit/source-conflict.ts +122 -0
- package/src/kit/stage-context.ts +377 -0
- package/src/kit/stage-invalidation.ts +25 -0
- package/src/kit/stage-store-registry.ts +69 -0
- package/src/kit/startup-failure.ts +80 -0
- package/src/kit/state-report-deferral.ts +73 -0
- package/src/kit/storage/host-files-storage.ts +97 -0
- package/src/kit/storage/http-storage.ts +174 -0
- package/src/kit/storage/index.ts +75 -0
- package/src/kit/storage/mem-storage.ts +158 -0
- package/src/kit/storage/path-lock.ts +44 -0
- package/src/kit/storage/paths.ts +26 -0
- package/src/kit/storage-types.ts +127 -0
- package/src/kit/stories/StoryPreviewMount.tsx +306 -0
- package/src/kit/stories/compose-project-stories.ts +255 -0
- package/src/kit/stories/prefabs-finder.ts +54 -0
- package/src/kit/stories/prefabs-from-stories.ts +182 -0
- package/src/kit/stories/project-story-regions.ts +24 -0
- package/src/kit/stories/story-capture.ts +579 -0
- package/src/kit/stories/story-declared-medium.ts +126 -0
- package/src/kit/stories/story-discovery.ts +176 -0
- package/src/kit/stories/story-dom-runtime.ts +78 -0
- package/src/kit/stories/story-grouping.ts +111 -0
- package/src/kit/stories/story-mount-turn.ts +27 -0
- package/src/kit/stories/story-presentation.ts +215 -0
- package/src/kit/stories/story-preview-component.ts +7 -0
- package/src/kit/stories/story-registry.ts +530 -0
- package/src/kit/stories-scope.ts +35 -0
- package/src/kit/story-document-openers.ts +36 -0
- package/src/kit/story-thumbnails.ts +47 -0
- package/src/kit/surface-keyboard.ts +101 -0
- package/src/kit/surface-state.ts +135 -0
- package/src/kit/system-seam-evidence.ts +72 -0
- package/src/kit/tab-census.ts +202 -0
- package/src/kit/tab-lifecycle-client.ts +227 -0
- package/src/kit/theme-library.ts +897 -0
- package/src/kit/theme-preference.ts +429 -0
- package/src/kit/three-viewport-presentation.ts +23 -0
- package/src/kit/tool-contribution-play.ts +74 -0
- package/src/kit/tool-loader.ts +1918 -0
- package/src/kit/transform-mode-request.ts +66 -0
- package/src/kit/transient-hint.ts +78 -0
- package/src/kit/transport-strip.tsx +174 -0
- package/src/kit/ui-source/adapter-region-includes.ts +238 -0
- package/src/kit/ui-source/file-region-resolver.ts +302 -0
- package/src/kit/ui-source/inspect.ts +775 -0
- package/src/kit/ui-source/source-write-backend.ts +605 -0
- package/src/kit/ui-source/tier-source-write-backend.ts +279 -0
- package/src/kit/user-local-state.ts +105 -0
- package/src/kit/viewport-activation-timings.ts +840 -0
- package/src/kit/viewport-editor-controls.ts +22 -0
- package/src/kit/viewport-presentation.ts +668 -0
- package/src/kit/viewport-surface-status.tsx +55 -0
- package/src/kit/wait-until.ts +37 -0
- package/src/kit/worker-call-metrics.ts +166 -0
- package/src/kit/workspace-areas.ts +191 -0
- package/src/kit/workspace-aux-commands.ts +11 -0
- package/src/kit/workspace-available-documents.ts +142 -0
- package/src/kit/workspace-core-utilities.ts +31 -0
- package/src/kit/workspace-document-ids.ts +59 -0
- package/src/kit/workspace-document-registry.ts +624 -0
- package/src/kit/workspace-document-restore.ts +146 -0
- package/src/kit/workspace-host-commands.ts +141 -0
- package/src/kit/workspace-persistence-gate.ts +40 -0
- package/src/kit/workspace-play-utilities.ts +44 -0
- package/src/kit/workspace-presets.ts +446 -0
- package/src/kit/workspace-regions.ts +276 -0
- package/src/kit/workspace-static-panels.ts +73 -0
- package/src/kit/workspace-status-registry.ts +121 -0
- package/src/kit/workspace-storage.ts +35 -0
- package/src/kit/workspace-style.ts +226 -0
- package/src/kit/workspace-utility-commands.ts +74 -0
- package/src/kit/workspace-utility-registry.ts +263 -0
- package/src/kit/world-adoption-event.ts +23 -0
- package/src/kit/world-adoption.ts +115 -0
- package/src/kit/world-canvas-viewport-state.ts +35 -0
- package/src/kit/world-document-routing.ts +104 -0
- package/src/kit/world-pan-state.ts +198 -0
- package/src/kit/write-pipe.ts +173 -0
- package/src/layout-arrangements.ts +5 -0
- package/src/layouts.tsx +108 -0
- package/src/looks.ts +16 -0
- package/src/project/output-roots.ts +73 -0
- package/src/project/tab-census.ts +155 -0
- package/src/project-tool-catalog.ts +104 -0
- package/src/selection.tsx +107 -0
- package/src/services.ts +18 -0
- package/src/session/build-report.ts +22 -0
- package/src/session/collaboration-types.ts +262 -0
- package/src/session/command-table.ts +327 -0
- package/src/session/discovery.ts +100 -0
- package/src/session/editor-brand.ts +48 -0
- package/src/session/editor-compatibility.ts +317 -0
- package/src/session/editor-control-lifecycle.ts +68 -0
- package/src/session/editor-control-protocol.ts +5 -0
- package/src/session/entrypoint-selection-readers.ts +66 -0
- package/src/session/entrypoint-selection-source.ts +120 -0
- package/src/session/game-css-scope.ts +30 -0
- package/src/session/hosted-attachment.ts +225 -0
- package/src/session/limited-view.ts +82 -0
- package/src/session/product-create.ts +24 -0
- package/src/session/product-locator.ts +478 -0
- package/src/session/project-module-url.ts +242 -0
- package/src/session/project-serving.ts +164 -0
- package/src/session/project-upgrade.ts +403 -0
- package/src/session/registry-format.ts +210 -0
- package/src/session/relative-path-guard.ts +56 -0
- package/src/session/scoped-game-css.ts +461 -0
- package/src/session/source-glob.ts +15 -0
- package/src/session/tool-contribution-convention.ts +123 -0
- package/src/session/workbench-locator.ts +712 -0
- package/src/session.ts +41 -0
- package/src/share.ts +160 -0
- package/src/source-analysis.ts +28 -0
- package/src/source-authoring.ts +439 -0
- package/src/tools/errors.ts +91 -0
- package/src/tools/provider-execution.ts +70 -0
- package/src/tools/registry.ts +341 -0
- package/src/tools/types.ts +159 -0
- package/src/transport.ts +100 -0
- package/src/types.ts +1693 -0
- package/src/views.ts +164 -0
- package/src/widgets/design-system.ts +93 -0
- package/src/widgets/editor-appearance.ts +151 -0
- package/src/widgets/editor-material.ts +83 -0
- package/src/widgets/icon-set-registry.ts +105 -0
- package/src/widgets/index.ts +71 -0
- package/src/widgets/inspector-widgets/AlignmentGrid.tsx +182 -0
- package/src/widgets/inspector-widgets/AssetSlotPicker.tsx +123 -0
- package/src/widgets/inspector-widgets/BorderEditor.tsx +309 -0
- package/src/widgets/inspector-widgets/ColorPicker.tsx +549 -0
- package/src/widgets/inspector-widgets/CurveEditor.tsx +359 -0
- package/src/widgets/inspector-widgets/FilterEditor.tsx +108 -0
- package/src/widgets/inspector-widgets/FontPicker.tsx +191 -0
- package/src/widgets/inspector-widgets/GradientEditor.tsx +623 -0
- package/src/widgets/inspector-widgets/ScrubbableInput.tsx +180 -0
- package/src/widgets/inspector-widgets/ShadowEditor.tsx +319 -0
- package/src/widgets/inspector-widgets/color-utils.ts +201 -0
- package/src/widgets/inspector-widgets/curve-utils.ts +212 -0
- package/src/widgets/inspector-widgets/index.ts +25 -0
- package/src/widgets/inspector-widgets/shared.tsx +140 -0
- package/src/widgets/interactive-edit-scope.ts +33 -0
- package/src/widgets/patterns/Dialog.tsx +140 -0
- package/src/widgets/patterns/Fields.tsx +44 -0
- package/src/widgets/patterns/List.tsx +25 -0
- package/src/widgets/patterns/StateSurface.tsx +40 -0
- package/src/widgets/patterns/Surfaces.tsx +122 -0
- package/src/widgets/patterns/Tabs.tsx +80 -0
- package/src/widgets/patterns/Toolbar.tsx +72 -0
- package/src/widgets/patterns/Tree.tsx +72 -0
- package/src/widgets/primitives/AnchoredMenu.tsx +260 -0
- package/src/widgets/primitives/Button.tsx +62 -0
- package/src/widgets/primitives/ColorInput.tsx +78 -0
- package/src/widgets/primitives/DraftTextInput.tsx +63 -0
- package/src/widgets/primitives/EditorIcon.tsx +157 -0
- package/src/widgets/primitives/FormControls.tsx +88 -0
- package/src/widgets/primitives/HoverPreview.tsx +96 -0
- package/src/widgets/primitives/JsonInput.tsx +113 -0
- package/src/widgets/primitives/Layout.tsx +100 -0
- package/src/widgets/primitives/Menu.tsx +161 -0
- package/src/widgets/primitives/NumberInput.tsx +169 -0
- package/src/widgets/primitives/Panel.tsx +80 -0
- package/src/widgets/primitives/SectionHeader.tsx +77 -0
- package/src/widgets/primitives/Text.tsx +54 -0
- package/src/widgets/primitives/ThemeRootPortal.tsx +52 -0
- package/src/widgets/primitives/Tooltip.tsx +204 -0
- package/src/widgets/primitives/Vec3Input.tsx +70 -0
- package/src/widgets/primitives/banner-tones.ts +32 -0
- package/src/widgets/primitives/clamp-to-viewport.ts +44 -0
- package/src/widgets/primitives/editor-icons.ts +254 -0
- package/src/widgets/primitives/panel-header-styles.ts +42 -0
- package/src/widgets/theme.ts +2841 -0
- package/src/widgets/z-index.ts +25 -0
|
@@ -0,0 +1,932 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The PROJECTIONS of the inspection model — Layer 3, and the only place a
|
|
3
|
+
* composed {@link InspectionSubject} becomes pixels.
|
|
4
|
+
*
|
|
5
|
+
* ONE box, two layouts (`inspection/model.ts`):
|
|
6
|
+
* - `column` — the DEFAULT: the subject's full inspector as a slim vertical
|
|
7
|
+
* column. Identity row (name, visibility eye, a LABELED open-for-edit
|
|
8
|
+
* button), then a persistent SECTION-ICON STRIP (one glyph per
|
|
9
|
+
* `subject.sections`, 1:1) that signposts what the subject HAS and jumps
|
|
10
|
+
* to a section, then every section stacked and collapsible;
|
|
11
|
+
* - `card` — the space-tight fallback: a MINI card (preview, name, the same
|
|
12
|
+
* icon strip, the open-for-edit) that the viewport host
|
|
13
|
+
* (`components/CompactInspectorCard.tsx`) can collapse at the floor to a
|
|
14
|
+
* single-line PILL. Clicking any collapsed form restores the column.
|
|
15
|
+
*
|
|
16
|
+
* "The box should actually be an identical thing regardless of what viewport
|
|
17
|
+
* it's on. The only thing that differs is the sections. The sections only
|
|
18
|
+
* differ because the inspector's structured data is different." (owner.) So
|
|
19
|
+
* everything above the sections — the identity row, the name, the kind, the
|
|
20
|
+
* verbs on it, the icon strip — reads the SAME model fields in both layouts;
|
|
21
|
+
* the only differences below are the layout's own arrangement.
|
|
22
|
+
*
|
|
23
|
+
* Two actions stay unambiguous because only ONE is an unlabeled gesture:
|
|
24
|
+
* clicking the box (any collapsed form, the mini card's preview, a strip
|
|
25
|
+
* glyph) EXPANDS/RESTORES the column — an in-place zoom on the SAME subject —
|
|
26
|
+
* while OPEN-FOR-EDIT is ALWAYS a labeled control that navigates to the
|
|
27
|
+
* subject's DEFINITION (a DIFFERENT subject). The definition link is the
|
|
28
|
+
* subject's own `related` link (`inspection/model.ts` {@link SubjectLink},
|
|
29
|
+
* produced by each adapter's `related` provider), surfaced here as the labeled
|
|
30
|
+
* Edit button only when the subject carries one.
|
|
31
|
+
*
|
|
32
|
+
* The projections are DUMB: they read identity, verbs, related links, and each
|
|
33
|
+
* section's id/title/icon/order/body off the model, and they never learn what
|
|
34
|
+
* a section contains, which contribution replaced which built-in, or which
|
|
35
|
+
* surface produced the subject. `surface` appears here for exactly one reason
|
|
36
|
+
* — it is the key the presentation preference is stored under — and a host
|
|
37
|
+
* that names none simply offers no switch.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
import {
|
|
41
|
+
faArrowUpRightFromSquare,
|
|
42
|
+
faCube,
|
|
43
|
+
faScrewdriverWrench,
|
|
44
|
+
faWindowMinimize,
|
|
45
|
+
faXmark,
|
|
46
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
47
|
+
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons';
|
|
48
|
+
import {
|
|
49
|
+
activeIconGlyph,
|
|
50
|
+
activeIconSetSnapshot,
|
|
51
|
+
Button,
|
|
52
|
+
DraftTextInput,
|
|
53
|
+
EditorIcon,
|
|
54
|
+
IconButton,
|
|
55
|
+
Panel,
|
|
56
|
+
SectionHeader,
|
|
57
|
+
spaceVar,
|
|
58
|
+
subscribeIconSets,
|
|
59
|
+
themeVars,
|
|
60
|
+
} from '@volter/sdk/widgets';
|
|
61
|
+
import {
|
|
62
|
+
Fragment,
|
|
63
|
+
type KeyboardEvent as ReactKeyboardEvent,
|
|
64
|
+
type ReactNode,
|
|
65
|
+
type UIEvent as ReactUIEvent,
|
|
66
|
+
useCallback,
|
|
67
|
+
useRef,
|
|
68
|
+
useState,
|
|
69
|
+
useSyncExternalStore,
|
|
70
|
+
} from 'react';
|
|
71
|
+
import { hierarchyKindIcon } from '../hierarchy-kind-icon';
|
|
72
|
+
import { setActiveScope } from '@volter/sdk/kit/hotkeys';
|
|
73
|
+
import {
|
|
74
|
+
actionsPlacedAt,
|
|
75
|
+
type InspectionAction,
|
|
76
|
+
type InspectionPresentation,
|
|
77
|
+
type InspectionPreviewMode,
|
|
78
|
+
type InspectionSection,
|
|
79
|
+
type InspectionSubject,
|
|
80
|
+
type InspectionSurfaceKind,
|
|
81
|
+
type SubjectLink,
|
|
82
|
+
} from '@volter/sdk/kit/inspection-model';
|
|
83
|
+
import { compactInspectorTabs, setInspectorPresentationOverride } from '../inspector-presentation';
|
|
84
|
+
import { useCompactInspectorHost } from './CompactInspectorShell';
|
|
85
|
+
import { InspectorFieldsSection } from './InspectorFieldsSection';
|
|
86
|
+
import { SurfaceCrashBoundary } from './SurfaceCrashBoundary';
|
|
87
|
+
import { useAfterPaint } from '@volter/sdk/kit/components/use-after-paint';
|
|
88
|
+
|
|
89
|
+
/** The body of one composed section, rendered by whichever layout asked for
|
|
90
|
+
* it: a `fields` body is the generic descriptor grid, a `custom` body is an
|
|
91
|
+
* opaque block that renders itself, a `preview` body is the subject's live
|
|
92
|
+
* square view. No projection looks past this.
|
|
93
|
+
*
|
|
94
|
+
* Each body sits in its OWN crash boundary, and `render()` is called inside
|
|
95
|
+
* it: a contributed section that throws (a hook from the wrong React, a hook
|
|
96
|
+
* called from its render helper) shows its error in place of that one
|
|
97
|
+
* section instead of unmounting the inspector and the editor around it. */
|
|
98
|
+
function sectionBody(section: InspectionSection): ReactNode {
|
|
99
|
+
return (
|
|
100
|
+
<SurfaceCrashBoundary
|
|
101
|
+
key={section.id}
|
|
102
|
+
surface={`inspector-section:${section.id}`}
|
|
103
|
+
label={`The "${section.title}" inspector section`}
|
|
104
|
+
>
|
|
105
|
+
<SectionBodyContent section={section} />
|
|
106
|
+
</SurfaceCrashBoundary>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function SectionBodyContent({ section }: { readonly section: InspectionSection }): ReactNode {
|
|
111
|
+
return section.body.kind === 'fields' ? (
|
|
112
|
+
<InspectorFieldsSection fields={section.body.fields} io={section.body.io} />
|
|
113
|
+
) : (
|
|
114
|
+
section.body.render()
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** A subject verb as an icon button (the `identity` placement — today the
|
|
119
|
+
* visibility eye, beside the name field). */
|
|
120
|
+
function QuickActionIconButton({ action }: { readonly action: InspectionAction }) {
|
|
121
|
+
return (
|
|
122
|
+
<IconButton
|
|
123
|
+
data-testid={action.id}
|
|
124
|
+
aria-label={action.title}
|
|
125
|
+
title={action.title}
|
|
126
|
+
aria-pressed={action.pressed}
|
|
127
|
+
disabled={action.disabled}
|
|
128
|
+
onClick={action.run}
|
|
129
|
+
>
|
|
130
|
+
{action.icon && <EditorIcon icon={action.icon} />}
|
|
131
|
+
</IconButton>
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* The subject's DEFINITION link — the one whose `id` carries the `definition:`
|
|
137
|
+
* marker its producing adapter stamped (mirroring the `creation-site:`
|
|
138
|
+
* convention), never a POSITIONAL guess. `SubjectLink` has no definition field,
|
|
139
|
+
* so identifying "the definition" by list order would silently promote whatever
|
|
140
|
+
* link an adapter happened to list first (a state machine, say). The marker is
|
|
141
|
+
* the contract; the first link that carries it is the open-for-edit target,
|
|
142
|
+
* and its absence is an honest "no definition to open" — no `[0]` fallback.
|
|
143
|
+
*/
|
|
144
|
+
function definitionLink(subject: InspectionSubject): SubjectLink | null {
|
|
145
|
+
return subject.related.find((link) => link.id.startsWith('definition:')) ?? null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** The related links that are NEITHER construction-site provenance (which rides
|
|
149
|
+
* in `identity.note`) NOR the promoted `definition:` link — the rest of the
|
|
150
|
+
* derivation chain (an asset document's state machine and the like). Filtered
|
|
151
|
+
* by kind, so a demoted definition can never reappear here and a non-definition
|
|
152
|
+
* link is never hidden by position. */
|
|
153
|
+
function otherRelatedLinks(subject: InspectionSubject): readonly SubjectLink[] {
|
|
154
|
+
return subject.related.filter(
|
|
155
|
+
(link) => !link.id.startsWith('creation-site:') && !link.id.startsWith('definition:'),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* The LABELED open-for-edit control (icon + text) — the ONE unlabeled-gesture
|
|
161
|
+
* exception's counterweight: it navigates to the subject's DEFINITION (a
|
|
162
|
+
* DIFFERENT subject whose edits change every instance), so it must never be
|
|
163
|
+
* the primary click. Its text is the link's own title ("Open Crate"), which is
|
|
164
|
+
* the honest name of the real `SubjectLink.open` verb it runs.
|
|
165
|
+
*/
|
|
166
|
+
function EditDefinitionButton({ link }: { readonly link: SubjectLink }) {
|
|
167
|
+
return (
|
|
168
|
+
<Button
|
|
169
|
+
variant="secondary"
|
|
170
|
+
size="compact"
|
|
171
|
+
data-testid="inspector-open-definition"
|
|
172
|
+
title={link.title}
|
|
173
|
+
onClick={link.open}
|
|
174
|
+
style={{ display: 'inline-flex', alignItems: 'center', gap: 4, flexShrink: 0 }}
|
|
175
|
+
>
|
|
176
|
+
<EditorIcon icon={faArrowUpRightFromSquare} />
|
|
177
|
+
<span
|
|
178
|
+
style={{
|
|
179
|
+
maxWidth: 120,
|
|
180
|
+
overflow: 'hidden',
|
|
181
|
+
textOverflow: 'ellipsis',
|
|
182
|
+
whiteSpace: 'nowrap',
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{link.title}
|
|
186
|
+
</span>
|
|
187
|
+
</Button>
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The DERIVATION chain beyond the definition — an asset document's state
|
|
192
|
+
* machine and the like. The definition itself is promoted to the identity
|
|
193
|
+
* row's Edit button, so this row renders only what is left. */
|
|
194
|
+
function RelatedSubjects({ subject }: { readonly subject: InspectionSubject }) {
|
|
195
|
+
const rest = otherRelatedLinks(subject);
|
|
196
|
+
if (rest.length === 0) return null;
|
|
197
|
+
return (
|
|
198
|
+
<div
|
|
199
|
+
data-testid="inspector-related-subjects"
|
|
200
|
+
style={{ display: 'flex', flexWrap: 'wrap', gap: 4, padding: '6px 8px' }}
|
|
201
|
+
>
|
|
202
|
+
{rest.map((link) => (
|
|
203
|
+
<Button key={link.id} variant="ghost" onClick={link.open} title={link.title}>
|
|
204
|
+
{link.title}
|
|
205
|
+
</Button>
|
|
206
|
+
))}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** What a section icon DRAWS under the active icon set: the set's own glyph
|
|
212
|
+
* for its name, or the definition's path when the set carries none. */
|
|
213
|
+
function drawnGlyph(icon: IconDefinition): string {
|
|
214
|
+
const glyph = activeIconGlyph(icon.iconName);
|
|
215
|
+
if (glyph) return `${glyph.path}|${glyph.tonedPath ?? ''}`;
|
|
216
|
+
const path = icon.icon[4];
|
|
217
|
+
return Array.isArray(path) ? path.join('|') : path;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* The SECTION-ICON STRIP — one glyph per `subject.sections`, 1:1
|
|
222
|
+
* (`compactInspectorTabs`). It signposts what the subject HAS and jumps to a
|
|
223
|
+
* section; it is NOT a tab that opens one section into a floating panel. In the
|
|
224
|
+
* column `onJump` scrolls the section into view; on the mini card there are no
|
|
225
|
+
* stacked sections, so `onJump` restores the column.
|
|
226
|
+
*/
|
|
227
|
+
function SectionIconStrip({
|
|
228
|
+
subject,
|
|
229
|
+
onJump,
|
|
230
|
+
}: {
|
|
231
|
+
readonly subject: InspectionSubject;
|
|
232
|
+
readonly onJump: (sectionId: string) => void;
|
|
233
|
+
}) {
|
|
234
|
+
const tabs = compactInspectorTabs(subject);
|
|
235
|
+
useSyncExternalStore(subscribeIconSets, activeIconSetSnapshot, activeIconSetSnapshot);
|
|
236
|
+
// The strip is a JUMP AID — a table of contents. It earns its row only
|
|
237
|
+
// when there is real navigation to do (a dense entity subject: preview,
|
|
238
|
+
// transform, property groups, stories…) AND its glyphs can be told
|
|
239
|
+
// apart. Three sections need no navigator, and identical icons signpost
|
|
240
|
+
// nothing — the 1:1 derivation stands (owner, 2026-08-07); an
|
|
241
|
+
// uninformative row earns no pixels (owner-sighted, 2026-08-23). The glyphs
|
|
242
|
+
// are told apart by what is DRAWN, not by name: a name the active icon set
|
|
243
|
+
// does not carry draws its definition's own path, and Blender's section
|
|
244
|
+
// names all fall back to the one wrench outside the Blender set — thirteen
|
|
245
|
+
// names, one drawing (owner-sighted on the compact card, 2026-09-25).
|
|
246
|
+
if (tabs.length < 5 || new Set(tabs.map((tab) => drawnGlyph(tab.icon))).size < 3) return null;
|
|
247
|
+
return (
|
|
248
|
+
<div
|
|
249
|
+
className="volter-inspector-icon-strip"
|
|
250
|
+
role="toolbar"
|
|
251
|
+
aria-label="Sections"
|
|
252
|
+
data-testid="inspector-section-strip"
|
|
253
|
+
>
|
|
254
|
+
{tabs.map((tab) => (
|
|
255
|
+
<IconButton
|
|
256
|
+
key={tab.id}
|
|
257
|
+
size="compact"
|
|
258
|
+
data-testid={`section-icon-${tab.id}`}
|
|
259
|
+
aria-label={tab.title}
|
|
260
|
+
title={tab.title}
|
|
261
|
+
onClick={() => onJump(tab.id)}
|
|
262
|
+
>
|
|
263
|
+
<EditorIcon icon={tab.icon} />
|
|
264
|
+
</IconButton>
|
|
265
|
+
))}
|
|
266
|
+
</div>
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** The subject's live preview at `thumbnail` fill, or a quiet kind glyph when
|
|
271
|
+
* the subject has none (react/DOM/no-Object3D subjects). The picture follows a
|
|
272
|
+
* frame after selection (`components/use-after-paint.ts`), so the glyph holds
|
|
273
|
+
* the geometry until it lands and nothing reflows. Inert: the host owns the
|
|
274
|
+
* single click. */
|
|
275
|
+
function SubjectThumbnail({
|
|
276
|
+
render,
|
|
277
|
+
glyph,
|
|
278
|
+
subjectKey,
|
|
279
|
+
}: {
|
|
280
|
+
readonly render: ((mode?: InspectionPreviewMode) => ReactNode) | null;
|
|
281
|
+
readonly glyph: InspectionSection['icon'];
|
|
282
|
+
readonly subjectKey: string;
|
|
283
|
+
}) {
|
|
284
|
+
const picture = useAfterPaint(subjectKey) ? render : null;
|
|
285
|
+
return picture ? (
|
|
286
|
+
<div aria-hidden="true" style={{ position: 'absolute', inset: 0, pointerEvents: 'none' }}>
|
|
287
|
+
{picture('thumbnail')}
|
|
288
|
+
</div>
|
|
289
|
+
) : (
|
|
290
|
+
<EditorIcon icon={glyph} />
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/** The editable-or-readonly name field, styled the same either way (a readonly
|
|
295
|
+
* `<input>` invites typing that vanishes, so it stands in as plain text).
|
|
296
|
+
*
|
|
297
|
+
* ITS INK IS THE FIELD'S, NOT A READ-ONLY TONE. The stand-in took
|
|
298
|
+
* `content.dim` (150) where every string Blender draws in its Properties
|
|
299
|
+
* editor inks #e5e5e5 — measured on `properties-object.png`'s datablock row
|
|
300
|
+
* (y 142..178, full width: ceiling 229 with 245 px, nothing above it), the
|
|
301
|
+
* row this one is the counterpart to. A WRONG MEMBER, not a wrong value:
|
|
302
|
+
* read-only is said by the absent caret and the absent hover, not by a
|
|
303
|
+
* quieter ink, and the field already restates every other part of the real
|
|
304
|
+
* control's paint (`compact-inspector.css`'s
|
|
305
|
+
* `[data-testid="ingest-name"][data-readonly="true"]`). */
|
|
306
|
+
function SubjectNameField({ subject }: { readonly subject: InspectionSubject }) {
|
|
307
|
+
const identity = subject.identity;
|
|
308
|
+
if (!identity) return null;
|
|
309
|
+
const summary = [identity.kindLabel, identity.note?.text].filter(Boolean).join(' — ');
|
|
310
|
+
if (identity.rename.readOnly) {
|
|
311
|
+
return (
|
|
312
|
+
<div
|
|
313
|
+
data-testid="ingest-name"
|
|
314
|
+
data-readonly="true"
|
|
315
|
+
{...(summary ? { title: summary } : {})}
|
|
316
|
+
style={{
|
|
317
|
+
flex: 1,
|
|
318
|
+
minWidth: 0,
|
|
319
|
+
fontSize: 'var(--volter-font-md)',
|
|
320
|
+
fontWeight: 600,
|
|
321
|
+
color: themeVars.content.primary,
|
|
322
|
+
whiteSpace: 'nowrap',
|
|
323
|
+
overflow: 'hidden',
|
|
324
|
+
textOverflow: 'ellipsis',
|
|
325
|
+
}}
|
|
326
|
+
>
|
|
327
|
+
{subject.title}
|
|
328
|
+
</div>
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return (
|
|
332
|
+
<DraftTextInput
|
|
333
|
+
data-testid="ingest-name"
|
|
334
|
+
aria-label="Name"
|
|
335
|
+
value={subject.title}
|
|
336
|
+
onCommit={(next) => {
|
|
337
|
+
if (next.trim() !== '') identity.rename.set(next);
|
|
338
|
+
}}
|
|
339
|
+
data-emphasis="strong"
|
|
340
|
+
{...(summary ? { title: summary } : {})}
|
|
341
|
+
style={{ flex: 1, minWidth: 0 }}
|
|
342
|
+
/>
|
|
343
|
+
);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* The subject's identity ROW — name, the identity verbs (the eye), and the
|
|
348
|
+
* labeled open-for-edit button when the subject carries a definition — over a
|
|
349
|
+
* dim kind/provenance line. One component, read by both layouts; a subject
|
|
350
|
+
* with no identity row shows its headline and quiet hint instead.
|
|
351
|
+
*/
|
|
352
|
+
function SubjectIdentity({ subject }: { readonly subject: InspectionSubject }) {
|
|
353
|
+
const identity = subject.identity;
|
|
354
|
+
if (!identity) {
|
|
355
|
+
return (
|
|
356
|
+
<div style={{ padding: 12 }}>
|
|
357
|
+
<div style={{ fontSize: 'var(--volter-font-md)', fontWeight: 600 }}>{subject.title}</div>
|
|
358
|
+
{subject.hint && (
|
|
359
|
+
<div
|
|
360
|
+
data-testid="inspector-subject-hint"
|
|
361
|
+
style={{
|
|
362
|
+
marginTop: 4,
|
|
363
|
+
fontSize: 'var(--volter-font-base)',
|
|
364
|
+
color: themeVars.content.dim,
|
|
365
|
+
}}
|
|
366
|
+
>
|
|
367
|
+
{subject.hint}
|
|
368
|
+
</div>
|
|
369
|
+
)}
|
|
370
|
+
</div>
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
const definition = definitionLink(subject);
|
|
374
|
+
return (
|
|
375
|
+
<div
|
|
376
|
+
className="volter-inspector-identity"
|
|
377
|
+
// Blender's datablock row — the counterpart to this one — is 21px: its
|
|
378
|
+
// 18px name field plus a hair. An 8px inset all round made ours 37, the
|
|
379
|
+
// tallest thing in the column and the loudest place it stopped reading
|
|
380
|
+
// as Properties. The inset is now the 2px step vertically (the optional
|
|
381
|
+
// second lines below still need a floor) and stays 8px across.
|
|
382
|
+
style={{
|
|
383
|
+
padding: `${spaceVar[1]} ${spaceVar[4]}`,
|
|
384
|
+
borderBottom: '1px solid var(--volter-structural-divider)',
|
|
385
|
+
}}
|
|
386
|
+
>
|
|
387
|
+
<div style={{ display: 'flex', gap: 6, alignItems: 'center' }}>
|
|
388
|
+
<SubjectNameField subject={subject} />
|
|
389
|
+
{/* The kind rides the name row — two lines said one thing
|
|
390
|
+
(owner-sighted, 2026-08-23). */}
|
|
391
|
+
{identity.kindLabel && (
|
|
392
|
+
<span
|
|
393
|
+
data-testid="inspector-kind-label"
|
|
394
|
+
style={{
|
|
395
|
+
fontSize: 'var(--volter-font-base)',
|
|
396
|
+
color: themeVars.content.dim,
|
|
397
|
+
whiteSpace: 'nowrap',
|
|
398
|
+
}}
|
|
399
|
+
>
|
|
400
|
+
{identity.kindLabel}
|
|
401
|
+
</span>
|
|
402
|
+
)}
|
|
403
|
+
{actionsPlacedAt(subject, 'identity').map((action) => (
|
|
404
|
+
<QuickActionIconButton key={action.id} action={action} />
|
|
405
|
+
))}
|
|
406
|
+
{definition && <EditDefinitionButton link={definition} />}
|
|
407
|
+
</div>
|
|
408
|
+
{/* WHERE this is written. The Hierarchy panel used to head itself with
|
|
409
|
+
this path; Blender's Outliner header is one row and names no file,
|
|
410
|
+
and the datablock row — this one — is where the file belongs. The
|
|
411
|
+
seam word rides `kindLabel` one line above, so this is the path
|
|
412
|
+
alone. */}
|
|
413
|
+
{identity.document && (
|
|
414
|
+
<div
|
|
415
|
+
data-testid="inspector-document-path"
|
|
416
|
+
data-document-path={identity.document.path}
|
|
417
|
+
{...(identity.document.title ? { title: identity.document.title } : {})}
|
|
418
|
+
style={{
|
|
419
|
+
marginTop: spaceVar[1],
|
|
420
|
+
fontSize: 'var(--volter-font-base)',
|
|
421
|
+
color: themeVars.content.muted,
|
|
422
|
+
whiteSpace: 'nowrap',
|
|
423
|
+
overflow: 'hidden',
|
|
424
|
+
textOverflow: 'ellipsis',
|
|
425
|
+
direction: 'rtl',
|
|
426
|
+
textAlign: 'left',
|
|
427
|
+
}}
|
|
428
|
+
>
|
|
429
|
+
{/* `direction: rtl` ellipsizes at the HEAD, so a long path keeps its
|
|
430
|
+
filename — the half a person reads. The bidi isolate keeps the
|
|
431
|
+
slashes in their authored order inside it. */}
|
|
432
|
+
<bdi>{identity.document.path}</bdi>
|
|
433
|
+
</div>
|
|
434
|
+
)}
|
|
435
|
+
{/* WHAT ELSE THE GIZMO IS HOLDING. The fields below belong to the one
|
|
436
|
+
node named above; a drag moves the whole selection. Saying so is the
|
|
437
|
+
difference between a panel that is scoped and a panel that is wrong
|
|
438
|
+
(`InspectionSubject.alsoSelected`). */}
|
|
439
|
+
{subject.alsoSelected ? (
|
|
440
|
+
<div
|
|
441
|
+
data-testid="inspector-also-selected"
|
|
442
|
+
style={{ marginTop: 6, fontSize: 'var(--volter-font-base)', color: themeVars.content.dim }}
|
|
443
|
+
>
|
|
444
|
+
{`${subject.alsoSelected + 1} selected — fields edit this one, the gizmo moves all`}
|
|
445
|
+
</div>
|
|
446
|
+
) : null}
|
|
447
|
+
{identity.note?.text && (
|
|
448
|
+
<div
|
|
449
|
+
data-testid="inspector-subject-note"
|
|
450
|
+
style={{ marginTop: 6, fontSize: 'var(--volter-font-base)', color: themeVars.content.dim }}
|
|
451
|
+
>
|
|
452
|
+
{identity.note.text}
|
|
453
|
+
</div>
|
|
454
|
+
)}
|
|
455
|
+
</div>
|
|
456
|
+
);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Properties keeps one identity row: kind, name and the subject's actions.
|
|
461
|
+
* The editable name field already identifies the subject, so a second
|
|
462
|
+
* breadcrumb with that same name adds no context. Keep the document details
|
|
463
|
+
* on the row's tooltip and data attribute, and preserve notes/multi-selection.
|
|
464
|
+
*/
|
|
465
|
+
function PropertiesIdentity({ subject }: { readonly subject: InspectionSubject }) {
|
|
466
|
+
const identity = subject.identity;
|
|
467
|
+
if (!identity) return <SubjectIdentity subject={subject} />;
|
|
468
|
+
const definition = definitionLink(subject);
|
|
469
|
+
// The subject's kind glyph follows the same table as its Hierarchy row.
|
|
470
|
+
const glyph = identity.kind
|
|
471
|
+
? hierarchyKindIcon(identity.kind, firstSectionGlyph(subject))
|
|
472
|
+
: firstSectionGlyph(subject);
|
|
473
|
+
const chainTitle = [identity.kindLabel, identity.document?.path, identity.document?.title]
|
|
474
|
+
.filter(Boolean)
|
|
475
|
+
.join(' — ');
|
|
476
|
+
return (
|
|
477
|
+
<div className="volter-inspector-identity" data-volter-identity-layout="properties">
|
|
478
|
+
<div
|
|
479
|
+
className="volter-inspector-datablock"
|
|
480
|
+
data-testid="inspector-document-path"
|
|
481
|
+
{...(identity.document ? { 'data-document-path': identity.document.path } : {})}
|
|
482
|
+
{...(chainTitle ? { title: chainTitle } : {})}
|
|
483
|
+
>
|
|
484
|
+
<span
|
|
485
|
+
className="volter-inspector-datablock-kind"
|
|
486
|
+
data-testid="inspector-kind-label"
|
|
487
|
+
{...(identity.kindLabel ? { title: identity.kindLabel } : {})}
|
|
488
|
+
>
|
|
489
|
+
<EditorIcon icon={glyph} tone="primary" />
|
|
490
|
+
</span>
|
|
491
|
+
<SubjectNameField subject={subject} />
|
|
492
|
+
{actionsPlacedAt(subject, 'identity').map((action) => (
|
|
493
|
+
<QuickActionIconButton key={action.id} action={action} />
|
|
494
|
+
))}
|
|
495
|
+
{definition && <EditDefinitionButton link={definition} />}
|
|
496
|
+
</div>
|
|
497
|
+
{subject.alsoSelected ? (
|
|
498
|
+
<div
|
|
499
|
+
data-testid="inspector-also-selected"
|
|
500
|
+
style={{
|
|
501
|
+
marginTop: spaceVar[3],
|
|
502
|
+
fontSize: 'var(--volter-font-base)',
|
|
503
|
+
color: themeVars.content.dim,
|
|
504
|
+
}}
|
|
505
|
+
>
|
|
506
|
+
{`${subject.alsoSelected + 1} selected — fields edit this one, the gizmo moves all`}
|
|
507
|
+
</div>
|
|
508
|
+
) : null}
|
|
509
|
+
{identity.note?.text && (
|
|
510
|
+
<div
|
|
511
|
+
data-testid="inspector-subject-note"
|
|
512
|
+
style={{
|
|
513
|
+
marginTop: spaceVar[3],
|
|
514
|
+
fontSize: 'var(--volter-font-base)',
|
|
515
|
+
color: themeVars.content.dim,
|
|
516
|
+
}}
|
|
517
|
+
>
|
|
518
|
+
{identity.note.text}
|
|
519
|
+
</div>
|
|
520
|
+
)}
|
|
521
|
+
</div>
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** One section under its own header — title AND icon, unconditionally. The
|
|
526
|
+
* disclosure triangle is the STACK's affordance (`collapsible`). The wrapper
|
|
527
|
+
* carries `data-inspector-section` so the icon strip can scroll to it. */
|
|
528
|
+
function ProjectedSection({ section }: { readonly section: InspectionSection }) {
|
|
529
|
+
return (
|
|
530
|
+
<div data-testid={section.testId} data-inspector-section={section.id}>
|
|
531
|
+
<SectionHeader
|
|
532
|
+
label={section.title}
|
|
533
|
+
icon={section.icon}
|
|
534
|
+
description={section.description}
|
|
535
|
+
collapsible
|
|
536
|
+
defaultOpen={section.defaultOpen ?? true}
|
|
537
|
+
>
|
|
538
|
+
{sectionBody(section)}
|
|
539
|
+
</SectionHeader>
|
|
540
|
+
</div>
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* The NARROW COLUMN (default): identity row, the section-icon strip, the
|
|
546
|
+
* derivation chain, then every section stacked and collapsible. A strip glyph
|
|
547
|
+
* scrolls its section into view within this column.
|
|
548
|
+
*/
|
|
549
|
+
export function NarrowColumn({ subject }: { readonly subject: InspectionSubject }) {
|
|
550
|
+
const sectionsRef = useRef<HTMLDivElement>(null);
|
|
551
|
+
const jumpToSection = (sectionId: string) => {
|
|
552
|
+
const target = sectionsRef.current?.querySelector(`[data-inspector-section="${sectionId}"]`);
|
|
553
|
+
target?.scrollIntoView({ block: 'nearest', behavior: 'smooth' });
|
|
554
|
+
};
|
|
555
|
+
return (
|
|
556
|
+
<div style={{ pointerEvents: 'auto' }}>
|
|
557
|
+
<SubjectIdentity subject={subject} />
|
|
558
|
+
<SectionIconStrip subject={subject} onJump={jumpToSection} />
|
|
559
|
+
<RelatedSubjects subject={subject} />
|
|
560
|
+
<div ref={sectionsRef}>
|
|
561
|
+
{subject.sections.map((section) => (
|
|
562
|
+
<Fragment key={section.id}>
|
|
563
|
+
<ProjectedSection section={section} />
|
|
564
|
+
</Fragment>
|
|
565
|
+
))}
|
|
566
|
+
</div>
|
|
567
|
+
</div>
|
|
568
|
+
);
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Which tab each subject last showed under the PROPERTIES presentation, and
|
|
573
|
+
* the last tab shown anywhere — Blender keeps the Properties editor on the
|
|
574
|
+
* same tab across selections when the new object has it, and that is the
|
|
575
|
+
* behaviour a modeler's hands expect: pick Material, click through objects,
|
|
576
|
+
* still on Material. Keyed by subject id, bounded like the scroll memory.
|
|
577
|
+
*/
|
|
578
|
+
const propertiesTabMemory = new Map<string, string>();
|
|
579
|
+
let lastPropertiesTab: string | null = null;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* The PROPERTIES column: identity row and derivation chain as the narrow
|
|
583
|
+
* column has them, then the sections TABBED — a vertical icon rail down the
|
|
584
|
+
* left edge, one section body at a time under a non-collapsible header (a
|
|
585
|
+
* triangle would offer to hide the only thing in the box). Every section
|
|
586
|
+
* earns a rail glyph, 1:1 with `subject.sections` — this rail is the
|
|
587
|
+
* NAVIGATION, not a table of contents, so the strip's "five sections or no
|
|
588
|
+
* strip" economy does not apply.
|
|
589
|
+
*/
|
|
590
|
+
export function PropertiesColumn({ subject }: { readonly subject: InspectionSubject }) {
|
|
591
|
+
const tabs = compactInspectorTabs(subject);
|
|
592
|
+
const remembered = propertiesTabMemory.get(subject.id) ?? lastPropertiesTab;
|
|
593
|
+
const [chosen, setChosen] = useState<string | null>(null);
|
|
594
|
+
// WHAT THE RAIL OPENS ON, in order: the tab this person clicked, the one
|
|
595
|
+
// they last used, THE TAB A SECTION DECLARED AS THE DEFAULT, and only then
|
|
596
|
+
// the first tab. The declaration exists because Blender's Properties editor
|
|
597
|
+
// does not open on its first tab: `SpaceProperties.context` is stored per
|
|
598
|
+
// screen in the startup file, and at factory settings
|
|
599
|
+
// `bpy.data.screens['Layout']` reads OBJECT while the rail's first tab is
|
|
600
|
+
// Render (measured against Blender 5.2 LTS, walk 5 parity row 3 — ours
|
|
601
|
+
// opened the cube on the Render tab). A rail whose sections declare nothing
|
|
602
|
+
// opens on its first tab exactly as before.
|
|
603
|
+
const activeId =
|
|
604
|
+
(chosen && tabs.some((tab) => tab.id === chosen) ? chosen : null) ??
|
|
605
|
+
(remembered && tabs.some((tab) => tab.id === remembered) ? remembered : null) ??
|
|
606
|
+
tabs.find((tab) => tab.railDefault)?.id ??
|
|
607
|
+
tabs[0]?.id ??
|
|
608
|
+
null;
|
|
609
|
+
const active = subject.sections.find((section) => section.id === activeId) ?? null;
|
|
610
|
+
const choose = (id: string) => {
|
|
611
|
+
setChosen(id);
|
|
612
|
+
if (propertiesTabMemory.size > 200) propertiesTabMemory.clear();
|
|
613
|
+
propertiesTabMemory.set(subject.id, id);
|
|
614
|
+
lastPropertiesTab = id;
|
|
615
|
+
};
|
|
616
|
+
return (
|
|
617
|
+
// The class carries no paint: it is the one named handle the rail needs
|
|
618
|
+
// to reach the bottom of the panel (`compact-inspector.css`, beside the
|
|
619
|
+
// rail's own `min-height: 100%`). Without a name this wrapper sizes to
|
|
620
|
+
// its content and the rail stops under the last tab.
|
|
621
|
+
<div className="volter-inspector-properties-fill" style={{ pointerEvents: 'auto' }}>
|
|
622
|
+
{/* THE RAIL IS THE AREA'S FULL LEFT EDGE, and the identity rows sit
|
|
623
|
+
BESIDE it, not above. Measured on `properties-object.png` at 2x: the
|
|
624
|
+
rail column runs x 2..57 from the body's top (y=47) to the frame's
|
|
625
|
+
bottom, and the breadcrumb's own glyph starts at x=80 — everything
|
|
626
|
+
the editor draws is to the right of the rail. Ours used to stack the
|
|
627
|
+
identity across the full width and start the rail under it. */}
|
|
628
|
+
<div className="volter-inspector-properties" data-testid="inspector-properties">
|
|
629
|
+
<div
|
|
630
|
+
className="volter-inspector-properties-rail"
|
|
631
|
+
role="tablist"
|
|
632
|
+
aria-orientation="vertical"
|
|
633
|
+
aria-label="Sections"
|
|
634
|
+
>
|
|
635
|
+
{tabs.map((tab, index) => (
|
|
636
|
+
<Fragment key={tab.id}>
|
|
637
|
+
{/* THE RAIL HAS GROUPS, and Blender's draws ground between them:
|
|
638
|
+
`ED_buttons_tabs_list` (`space_buttons.cc:201-255`) calls
|
|
639
|
+
`add_spacer()` between the tool tab, the scene group, the
|
|
640
|
+
collection tab, the object group and Texture, appending a
|
|
641
|
+
`BCONTEXT_SEPARATOR` the rail renders as a gap rather than a
|
|
642
|
+
tab. Measured on `modeling-edit-none.png` down x=2852: 1 CSS
|
|
643
|
+
px of hairline between cells of ONE group, 8.5 across a group
|
|
644
|
+
boundary. Sections that name no group form one trailing group
|
|
645
|
+
— a subject with no `railGroup` anywhere draws no separator
|
|
646
|
+
at all, which is every non-Blender rail. */}
|
|
647
|
+
{index > 0 && tab.railGroup !== tabs[index - 1]?.railGroup ? (
|
|
648
|
+
<div
|
|
649
|
+
className="volter-inspector-properties-rail-separator"
|
|
650
|
+
data-testid="properties-tab-separator"
|
|
651
|
+
role="presentation"
|
|
652
|
+
/>
|
|
653
|
+
) : null}
|
|
654
|
+
<IconButton
|
|
655
|
+
size="compact"
|
|
656
|
+
role="tab"
|
|
657
|
+
aria-selected={tab.id === activeId}
|
|
658
|
+
data-testid={`properties-tab-${tab.id}`}
|
|
659
|
+
aria-label={tab.title}
|
|
660
|
+
title={tab.title}
|
|
661
|
+
onClick={() => choose(tab.id)}
|
|
662
|
+
>
|
|
663
|
+
{/* A tab glyph, not body text: Blender's Properties tabs are
|
|
664
|
+
14px in a 28px strip, and the default `sm` rendered these at
|
|
665
|
+
10 — a rail of specks beside the reference. */}
|
|
666
|
+
<EditorIcon icon={tab.icon} size="xl" />
|
|
667
|
+
</IconButton>
|
|
668
|
+
</Fragment>
|
|
669
|
+
))}
|
|
670
|
+
</div>
|
|
671
|
+
<div className="volter-inspector-properties-body" role="tabpanel">
|
|
672
|
+
<PropertiesIdentity subject={subject} />
|
|
673
|
+
<RelatedSubjects subject={subject} />
|
|
674
|
+
{/* NO HEADER OVER THE ACTIVE TAB. Blender's Properties editor never
|
|
675
|
+
heads a tab's content with the tab's own name — measured on three
|
|
676
|
+
frames at 2x: `properties-object.png` runs header → breadcrumb →
|
|
677
|
+
datablock row → the `Transform` CARD; `properties-data-edit.png`
|
|
678
|
+
runs the same three rows → the `Vertex Groups` card;
|
|
679
|
+
`modeling.png`'s Modifier tab → `Add Modifier`. The rail already
|
|
680
|
+
says which editor you are in, and a slab repeating it was the
|
|
681
|
+
full-bleed bar at the top of our column. The sections BELOW keep
|
|
682
|
+
their own headers, because in the reference they are the cards. */}
|
|
683
|
+
{active ? (
|
|
684
|
+
<div data-testid={active.testId} data-inspector-section={active.id}>
|
|
685
|
+
{sectionBody(active)}
|
|
686
|
+
</div>
|
|
687
|
+
) : null}
|
|
688
|
+
{/* Sections that host publishing contributions stay live behind the
|
|
689
|
+
active tab (`InspectionSection.keepMounted`). */}
|
|
690
|
+
{subject.sections
|
|
691
|
+
.filter((section) => section.keepMounted && section.id !== activeId)
|
|
692
|
+
.map((section) => (
|
|
693
|
+
<div
|
|
694
|
+
key={section.id}
|
|
695
|
+
data-inspector-section={section.id}
|
|
696
|
+
data-inspector-section-hidden="true"
|
|
697
|
+
style={{ display: 'none' }}
|
|
698
|
+
>
|
|
699
|
+
{sectionBody(section)}
|
|
700
|
+
</div>
|
|
701
|
+
))}
|
|
702
|
+
</div>
|
|
703
|
+
</div>
|
|
704
|
+
</div>
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/** The mini card's preview, or a kind glyph for a no-preview subject. */
|
|
709
|
+
/** The preview's stand-in: the first section's glyph when the active set
|
|
710
|
+
* draws one for it, the cube otherwise. A section name the set does not carry
|
|
711
|
+
* draws the generic project-tool wrench (`tool-loader.ts`), which says nothing
|
|
712
|
+
* about the subject — a Blender object under any set but Blender's. */
|
|
713
|
+
function firstSectionGlyph(subject: InspectionSubject) {
|
|
714
|
+
const first = subject.sections[0]?.icon;
|
|
715
|
+
if (!first) return faCube;
|
|
716
|
+
return drawnGlyph(first) === drawnGlyph(faScrewdriverWrench) ? faCube : first;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* The MINI card — the space-tight fallback: the subject's preview (or a kind
|
|
721
|
+
* glyph) above its name and eye, the same section-icon strip, and the labeled
|
|
722
|
+
* open-for-edit when the subject carries a definition. Clicking the preview or
|
|
723
|
+
* a strip glyph EXPANDS the column (an in-place zoom on the same subject);
|
|
724
|
+
* `minimize` (when the viewport host provides one) drops to the pill.
|
|
725
|
+
*
|
|
726
|
+
* `surface` is the key the expand preference is written under; a bounded host
|
|
727
|
+
* that names none renders the card without the expand affordance.
|
|
728
|
+
*/
|
|
729
|
+
export function MiniInspectorCard({
|
|
730
|
+
subject,
|
|
731
|
+
surface,
|
|
732
|
+
}: {
|
|
733
|
+
readonly subject: InspectionSubject;
|
|
734
|
+
readonly surface?: InspectionSurfaceKind | undefined;
|
|
735
|
+
}) {
|
|
736
|
+
const host = useCompactInspectorHost();
|
|
737
|
+
const previewSection = subject.sections.find((section) => section.body.kind === 'preview');
|
|
738
|
+
const previewBody = previewSection?.body.kind === 'preview' ? previewSection.body : null;
|
|
739
|
+
const definition = definitionLink(subject);
|
|
740
|
+
const expand = surface ? () => setInspectorPresentationOverride(surface, 'column') : null;
|
|
741
|
+
const inIdentity = subject.identity !== null;
|
|
742
|
+
return (
|
|
743
|
+
<div
|
|
744
|
+
className="volter-mini-inspector"
|
|
745
|
+
data-testid="inspector-panel"
|
|
746
|
+
data-volter-inspector-presentation="card"
|
|
747
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
748
|
+
>
|
|
749
|
+
{host && (
|
|
750
|
+
<IconButton
|
|
751
|
+
size="compact"
|
|
752
|
+
className="volter-mini-inspector-minimize"
|
|
753
|
+
data-testid="inspector-minimize"
|
|
754
|
+
aria-label="Minimize inspector"
|
|
755
|
+
title="Minimize inspector"
|
|
756
|
+
onClick={host.minimize}
|
|
757
|
+
>
|
|
758
|
+
<EditorIcon icon={faWindowMinimize} />
|
|
759
|
+
</IconButton>
|
|
760
|
+
)}
|
|
761
|
+
{/* The preview (or kind glyph) is the primary EXPAND gesture — clicking
|
|
762
|
+
the box restores the column. */}
|
|
763
|
+
<div
|
|
764
|
+
className="volter-mini-inspector-preview"
|
|
765
|
+
data-testid="inspector-mini-preview"
|
|
766
|
+
{...(expand
|
|
767
|
+
? {
|
|
768
|
+
role: 'button',
|
|
769
|
+
tabIndex: 0,
|
|
770
|
+
'aria-label': `Expand inspector for ${subject.title}`,
|
|
771
|
+
title: `Expand inspector for ${subject.title}`,
|
|
772
|
+
onClick: expand,
|
|
773
|
+
onKeyDown: (event: ReactKeyboardEvent) => {
|
|
774
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
775
|
+
event.preventDefault();
|
|
776
|
+
expand();
|
|
777
|
+
}
|
|
778
|
+
},
|
|
779
|
+
}
|
|
780
|
+
: {})}
|
|
781
|
+
>
|
|
782
|
+
<SubjectThumbnail
|
|
783
|
+
render={previewBody ? previewBody.render : null}
|
|
784
|
+
glyph={firstSectionGlyph(subject)}
|
|
785
|
+
subjectKey={subject.id}
|
|
786
|
+
/>
|
|
787
|
+
</div>
|
|
788
|
+
{inIdentity ? (
|
|
789
|
+
<div className="volter-mini-inspector-identity">
|
|
790
|
+
<SubjectNameField subject={subject} />
|
|
791
|
+
{actionsPlacedAt(subject, 'identity').map((action) => (
|
|
792
|
+
<QuickActionIconButton key={action.id} action={action} />
|
|
793
|
+
))}
|
|
794
|
+
</div>
|
|
795
|
+
) : (
|
|
796
|
+
<div className="volter-mini-inspector-identity">
|
|
797
|
+
<div style={{ flex: 1, minWidth: 0, fontSize: 'var(--volter-font-md)', fontWeight: 600 }}>
|
|
798
|
+
{subject.title}
|
|
799
|
+
</div>
|
|
800
|
+
</div>
|
|
801
|
+
)}
|
|
802
|
+
<SectionIconStrip subject={subject} onJump={() => expand?.()} />
|
|
803
|
+
{definition && (
|
|
804
|
+
<div className="volter-mini-inspector-actions">
|
|
805
|
+
<EditDefinitionButton link={definition} />
|
|
806
|
+
</div>
|
|
807
|
+
)}
|
|
808
|
+
</div>
|
|
809
|
+
);
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
/**
|
|
813
|
+
* The PILL — the floor: a single line carrying the subject's thumb-glyph, its
|
|
814
|
+
* name, and an expand affordance. The whole lozenge restores the column;
|
|
815
|
+
* `onClear` is the minimized state's only route to deselect (there is no
|
|
816
|
+
* viewport gesture reaching a minimized box).
|
|
817
|
+
*/
|
|
818
|
+
export function InspectorPill({
|
|
819
|
+
subject,
|
|
820
|
+
onExpand,
|
|
821
|
+
onClear,
|
|
822
|
+
}: {
|
|
823
|
+
readonly subject: InspectionSubject;
|
|
824
|
+
readonly onExpand: () => void;
|
|
825
|
+
readonly onClear?: (() => void) | undefined;
|
|
826
|
+
}) {
|
|
827
|
+
const previewSection = subject.sections.find((section) => section.body.kind === 'preview');
|
|
828
|
+
const previewBody = previewSection?.body.kind === 'preview' ? previewSection.body : null;
|
|
829
|
+
return (
|
|
830
|
+
<div className="volter-inspector-pill" data-testid="inspector-pill">
|
|
831
|
+
{/* The role-annotated div is the sanctioned clickable-card idiom — a raw
|
|
832
|
+
native control here trips the design-system product-chrome scan. */}
|
|
833
|
+
<div
|
|
834
|
+
role="button"
|
|
835
|
+
tabIndex={0}
|
|
836
|
+
className="volter-inspector-pill-open"
|
|
837
|
+
aria-label={`Expand inspector for ${subject.title}`}
|
|
838
|
+
title={`Expand inspector for ${subject.title}`}
|
|
839
|
+
onClick={onExpand}
|
|
840
|
+
onKeyDown={(event) => {
|
|
841
|
+
if (event.key === ' ' || event.key === 'Enter') {
|
|
842
|
+
event.preventDefault();
|
|
843
|
+
onExpand();
|
|
844
|
+
}
|
|
845
|
+
}}
|
|
846
|
+
>
|
|
847
|
+
<span className="volter-inspector-pill-thumb" aria-hidden="true">
|
|
848
|
+
{previewBody ? (
|
|
849
|
+
previewBody.render('thumbnail')
|
|
850
|
+
) : (
|
|
851
|
+
<EditorIcon icon={firstSectionGlyph(subject)} />
|
|
852
|
+
)}
|
|
853
|
+
</span>
|
|
854
|
+
<span className="volter-inspector-pill-name">{subject.title}</span>
|
|
855
|
+
</div>
|
|
856
|
+
{onClear && (
|
|
857
|
+
<IconButton
|
|
858
|
+
data-testid="inspector-fab-clear-selection"
|
|
859
|
+
aria-label="Clear selection"
|
|
860
|
+
title="Clear selection"
|
|
861
|
+
onClick={onClear}
|
|
862
|
+
>
|
|
863
|
+
<EditorIcon icon={faXmark} />
|
|
864
|
+
</IconButton>
|
|
865
|
+
)}
|
|
866
|
+
</div>
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
export interface InspectionProjectionProps {
|
|
871
|
+
readonly subject: InspectionSubject;
|
|
872
|
+
/** Which layout to render. The live shell reads this off the composed
|
|
873
|
+
* subject through `inspection/display.ts`; it is never re-derived here. */
|
|
874
|
+
readonly presentation: InspectionPresentation;
|
|
875
|
+
/** The surface this projection is showing, when it is the live shell's —
|
|
876
|
+
* the key the presentation-switch affordances write their override under.
|
|
877
|
+
* Omitted in bounded design-system hosts, which offer no switch. */
|
|
878
|
+
readonly surface?: InspectionSurfaceKind;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Column scroll memory across the RE-PROJECTION remount. A source write's HMR
|
|
883
|
+
* re-projection drops the selection for ~a beat (the measured, unpinned drop
|
|
884
|
+
* `react-world-authoring-adapter.ts#scheduleSelectionRepair` documents), which
|
|
885
|
+
* unmounts this panel entirely; the repair restores the SAME subject and the
|
|
886
|
+
* fresh mount landed at the top — measured live: the column node is REPLACED
|
|
887
|
+
* and `scrollTop` resets to 0 within 600ms of any style write, which the
|
|
888
|
+
* blind walker read as "every Fill edit scrolls the inspector back to top"
|
|
889
|
+
* (Fill just sits below the fold). Keyed by subject id, so a genuinely new
|
|
890
|
+
* selection still opens at the top while the same subject keeps its place.
|
|
891
|
+
*/
|
|
892
|
+
const columnScrollMemory = new Map<string, number>();
|
|
893
|
+
|
|
894
|
+
/** ONE subject, projected. */
|
|
895
|
+
export function InspectionProjectionView({
|
|
896
|
+
subject,
|
|
897
|
+
presentation,
|
|
898
|
+
surface,
|
|
899
|
+
}: InspectionProjectionProps) {
|
|
900
|
+
const subjectId = subject.id;
|
|
901
|
+
const restoreScroll = useCallback(
|
|
902
|
+
(el: HTMLDivElement | null) => {
|
|
903
|
+
if (el) el.scrollTop = columnScrollMemory.get(subjectId) ?? 0;
|
|
904
|
+
},
|
|
905
|
+
[subjectId],
|
|
906
|
+
);
|
|
907
|
+
if (presentation === 'card') {
|
|
908
|
+
return <MiniInspectorCard subject={subject} {...(surface ? { surface } : {})} />;
|
|
909
|
+
}
|
|
910
|
+
return (
|
|
911
|
+
<Panel
|
|
912
|
+
name="Inspector"
|
|
913
|
+
hideHeader
|
|
914
|
+
className="volter-content-frost"
|
|
915
|
+
data-testid="inspector-panel"
|
|
916
|
+
data-volter-inspector-presentation={presentation}
|
|
917
|
+
onPointerDown={() => setActiveScope('inspector')}
|
|
918
|
+
style={{ overflowY: 'auto' }}
|
|
919
|
+
ref={restoreScroll}
|
|
920
|
+
onScroll={(e: ReactUIEvent<HTMLDivElement>) => {
|
|
921
|
+
if (columnScrollMemory.size > 200) columnScrollMemory.clear();
|
|
922
|
+
columnScrollMemory.set(subjectId, e.currentTarget.scrollTop);
|
|
923
|
+
}}
|
|
924
|
+
>
|
|
925
|
+
{presentation === 'properties' ? (
|
|
926
|
+
<PropertiesColumn subject={subject} />
|
|
927
|
+
) : (
|
|
928
|
+
<NarrowColumn subject={subject} />
|
|
929
|
+
)}
|
|
930
|
+
</Panel>
|
|
931
|
+
);
|
|
932
|
+
}
|