@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// The GPU's own time per drawn frame, off three's timestamp queries.
|
|
2
|
+
//
|
|
3
|
+
// WHAT THE NUMBER COVERS IS THREE'S, AND IT IS NARROW. Each render pass is
|
|
4
|
+
// bracketed by a timestamp pair, so a frame's time is the sum of its passes.
|
|
5
|
+
// Texture uploads, mip generation and copies run outside every pass and are
|
|
6
|
+
// not in it, and Chrome quantizes WebGPU timestamps to about 0.07-0.1 ms. That
|
|
7
|
+
// is the right signal for a resolution decision (pixels cost pass time) and
|
|
8
|
+
// the wrong one for a stall an upload or a compile causes.
|
|
9
|
+
//
|
|
10
|
+
// r185 reads `backend.trackTimestamp` per pass (`initTimestampQuery` on both
|
|
11
|
+
// backends), so it can be switched on after the renderer was made. A backend
|
|
12
|
+
// without the feature must never have it on: WebGPU would create a timestamp
|
|
13
|
+
// query set the device cannot validate. The pool holds 2048 queries (1024
|
|
14
|
+
// passes) and only a resolve empties it, so the timer resolves after every
|
|
15
|
+
// draw, or with the next draw while one resolve is still out.
|
|
16
|
+
//
|
|
17
|
+
// EVERY FRAME OF A RESOLVE IS A SAMPLE, AND THE TIMER EMPTIES THREE'S MAP. A
|
|
18
|
+
// resolve answers with the LAST frame it saw, but on WebGPU the frames drawn
|
|
19
|
+
// while the one before it was out are in the same batch. three keeps each
|
|
20
|
+
// pass's ms in the pool's `timestamps`, keyed by a uid that ends in
|
|
21
|
+
// `:f<frame id>`, so the timer sums them per frame. three never deletes from
|
|
22
|
+
// that map and every frame adds new uids, so the timer clears it after each
|
|
23
|
+
// resolve: nothing in the stage reads `getTimestamp`, which is the map's only
|
|
24
|
+
// other reader. WebGL LOSES those frames instead: its pool reads only the
|
|
25
|
+
// queries that had ended when the resolve began, then forgets every query
|
|
26
|
+
// offset, the ones frames drew while it was out included (r185
|
|
27
|
+
// WebGLTimestampQueryPool `resolveQueriesAsync`). There a sample is one frame
|
|
28
|
+
// in two or three, which is why the controller closes a window by time as well
|
|
29
|
+
// as by count (renderScaleController.ts).
|
|
30
|
+
//
|
|
31
|
+
// A SCALED FRAME'S PASSES UP TO THE CANVAS ARE KEPT APART. The copy of P and
|
|
32
|
+
// the present cover the canvas whatever the playback scale, so each frame's
|
|
33
|
+
// sum comes with the share each took, by the uid and kind the presentation
|
|
34
|
+
// child recorded (`takePresentationPass`, packages/shot/src/presentationScale.ts):
|
|
35
|
+
// the present is the output pass a frame at 1 draws too, and the copy one only
|
|
36
|
+
// a scaled frame draws, which the controller prices apart.
|
|
37
|
+
//
|
|
38
|
+
// A frame here is three's rAF tick (`info.frame`), not a stage draw. PerfProbe
|
|
39
|
+
// resets three's Info at the start of every stage draw, so two draws inside
|
|
40
|
+
// one tick (a resize redraw) restart the render-call count in the uid, and
|
|
41
|
+
// three keeps only the second draw's queries for that tick.
|
|
42
|
+
//
|
|
43
|
+
// Known r185 leftover, harmless: WebGPU caches the canvas pass descriptor, so
|
|
44
|
+
// after `stop` it keeps writing its last timestamp pair into the idle query
|
|
45
|
+
// set until three rebuilds it. No pixel depends on it.
|
|
46
|
+
//
|
|
47
|
+
// ── IN PRODUCTION, WHILE THE TRANSPORT PLAYS ─────────────────────────────
|
|
48
|
+
//
|
|
49
|
+
// The playback render scale decides on this number, beside the presented
|
|
50
|
+
// frames' deadlines (renderScaleController.ts: a step down needs both), so
|
|
51
|
+
// `GpuTimer` below is mounted in the editor's non-export branch whenever
|
|
52
|
+
// the stage plays, perf or not (StageApp): the transport, or a preparing
|
|
53
|
+
// pass, whose frames the controller drops. Pause unmounts it. Its cost per
|
|
54
|
+
// drawn frame is a timestamp pair per pass and one resolve: a query-set
|
|
55
|
+
// resolve, a buffer copy and a map, and never a second resolve while one is
|
|
56
|
+
// out. The perf HUD keeps it on while paused too, for its own reading.
|
|
57
|
+
import { useEffect, useRef } from "react";
|
|
58
|
+
import { useFrame, useThree } from "@react-three/fiber";
|
|
59
|
+
import { takePresentationPass } from "@dailies-studio/shot";
|
|
60
|
+
import type { PresentationPass } from "@dailies-studio/shot";
|
|
61
|
+
import { percentile } from "./stagePerformanceTelemetry.js";
|
|
62
|
+
|
|
63
|
+
interface TimestampRenderer {
|
|
64
|
+
backend: {
|
|
65
|
+
isWebGPUBackend?: boolean;
|
|
66
|
+
isWebGLBackend?: boolean;
|
|
67
|
+
/** WebGL: EXT_disjoint_timer_query_webgl2 was found. WebGPU answers true
|
|
68
|
+
* always, so it is no feature test there. */
|
|
69
|
+
hasTimestamp?: boolean;
|
|
70
|
+
hasFeature?: (name: string) => boolean;
|
|
71
|
+
trackTimestamp?: boolean;
|
|
72
|
+
timestampQueryPool?: {
|
|
73
|
+
render?: {
|
|
74
|
+
currentQueryIndex?: number;
|
|
75
|
+
/** Each resolved pass's ms, by uid (`r:<call>:<context>:f<frame id>`). */
|
|
76
|
+
timestamps?: Map<string, number>;
|
|
77
|
+
} | null;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
resolveTimestampsAsync(type: "render"): Promise<number | undefined>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export type GpuTimerKind = "webgpu" | "webgl";
|
|
84
|
+
|
|
85
|
+
export interface GpuTimerHandle {
|
|
86
|
+
readonly kind: GpuTimerKind;
|
|
87
|
+
/** After every draw: resolve the passes it timed. */
|
|
88
|
+
afterDraw(): void;
|
|
89
|
+
/** Drain once, then stop timing passes. */
|
|
90
|
+
stop(): void;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Which timer this renderer can run, or null when it has none. */
|
|
94
|
+
export function gpuTimerKind(renderer: unknown): GpuTimerKind | null {
|
|
95
|
+
const gl = renderer as Partial<TimestampRenderer> | null | undefined;
|
|
96
|
+
const backend = gl?.backend;
|
|
97
|
+
if (backend === undefined || typeof gl?.resolveTimestampsAsync !== "function") return null;
|
|
98
|
+
if (backend.isWebGPUBackend === true) {
|
|
99
|
+
return backend.hasFeature?.("timestamp-query") === true ? "webgpu" : null;
|
|
100
|
+
}
|
|
101
|
+
if (backend.isWebGLBackend === true) return backend.hasTimestamp === true ? "webgl" : null;
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** One resolved frame's ms, and the ms its playback-scale copy of P and its
|
|
106
|
+
* present took (0 each for an unscaled frame). */
|
|
107
|
+
export type GpuFrameSink = (ms: number, copyMs: number, presentMs: number) => void;
|
|
108
|
+
|
|
109
|
+
/** Turn the renderer's timestamps on and hand each resolved frame to
|
|
110
|
+
* `onFrame`, oldest first. Null, and nothing switched on, without a timer. */
|
|
111
|
+
export function startGpuTimer(renderer: unknown, onFrame: GpuFrameSink): GpuTimerHandle | null {
|
|
112
|
+
const kind = gpuTimerKind(renderer);
|
|
113
|
+
if (kind === null) return null;
|
|
114
|
+
const gl = renderer as TimestampRenderer;
|
|
115
|
+
const backend = gl.backend;
|
|
116
|
+
backend.trackTimestamp = true;
|
|
117
|
+
let resolving = false;
|
|
118
|
+
let stopped = false;
|
|
119
|
+
const pool = () => backend.timestampQueryPool?.render ?? null;
|
|
120
|
+
// Asked before every resolve: with no pass written since the last one, a
|
|
121
|
+
// resolve has nothing to read.
|
|
122
|
+
const pending = (): boolean => (pool()?.currentQueryIndex ?? 0) > 0;
|
|
123
|
+
const resolve = (): void => {
|
|
124
|
+
resolving = true;
|
|
125
|
+
void gl.resolveTimestampsAsync("render").then(
|
|
126
|
+
() => {
|
|
127
|
+
resolving = false;
|
|
128
|
+
const timestamps = pool()?.timestamps;
|
|
129
|
+
if (timestamps === undefined) return;
|
|
130
|
+
const frames = framesOf(timestamps, (uid) => takePresentationPass(gl, uid));
|
|
131
|
+
timestamps.clear();
|
|
132
|
+
if (stopped) return;
|
|
133
|
+
for (const frame of frames) onFrame(frame.ms, frame.copyMs, frame.presentMs);
|
|
134
|
+
},
|
|
135
|
+
() => {
|
|
136
|
+
resolving = false;
|
|
137
|
+
pool()?.timestamps?.clear();
|
|
138
|
+
},
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
return {
|
|
142
|
+
kind,
|
|
143
|
+
afterDraw() {
|
|
144
|
+
if (!stopped && !resolving && pending()) resolve();
|
|
145
|
+
},
|
|
146
|
+
stop() {
|
|
147
|
+
if (stopped) return;
|
|
148
|
+
stopped = true;
|
|
149
|
+
// The resolve reads `trackTimestamp` as it is called, so it goes first.
|
|
150
|
+
if (!resolving && pending()) resolve();
|
|
151
|
+
backend.trackTimestamp = false;
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** The stage child that runs the timer on the canvas's renderer: `onTimer`
|
|
157
|
+
* once at mount with the timer it found (null when the renderer has none),
|
|
158
|
+
* then `onFrame` with each resolved frame's ms, its copy's and its
|
|
159
|
+
* present's. Stopped, after one drain, on unmount. Both are read at call
|
|
160
|
+
* time, so a new callback restarts nothing. */
|
|
161
|
+
export function GpuTimer({
|
|
162
|
+
onFrame,
|
|
163
|
+
onTimer,
|
|
164
|
+
}: {
|
|
165
|
+
onFrame: GpuFrameSink;
|
|
166
|
+
onTimer: (kind: GpuTimerKind | null) => void;
|
|
167
|
+
}): null {
|
|
168
|
+
const gl = useThree((s) => s.gl);
|
|
169
|
+
const timer = useRef<GpuTimerHandle | null>(null);
|
|
170
|
+
const sinks = useRef({ onFrame, onTimer });
|
|
171
|
+
sinks.current = { onFrame, onTimer };
|
|
172
|
+
useEffect(() => {
|
|
173
|
+
const started = startGpuTimer(gl, (ms, copyMs, presentMs) => sinks.current.onFrame(ms, copyMs, presentMs));
|
|
174
|
+
timer.current = started;
|
|
175
|
+
sinks.current.onTimer(started?.kind ?? null);
|
|
176
|
+
return () => {
|
|
177
|
+
started?.stop();
|
|
178
|
+
timer.current = null;
|
|
179
|
+
};
|
|
180
|
+
}, [gl]);
|
|
181
|
+
// The scheduler's finish phase, like PerfProbe's: after every render the
|
|
182
|
+
// frame made, the presenters' included. fiber stands its own draw down only
|
|
183
|
+
// for render-phase jobs, so this one can never leave a pane blank.
|
|
184
|
+
useFrame(() => {
|
|
185
|
+
timer.current?.afterDraw();
|
|
186
|
+
}, { phase: "finish" });
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** One resolve's passes summed per frame, in the order three drew them, each
|
|
191
|
+
* with the shares its copy and present passes took (`passOf`). A frame whose
|
|
192
|
+
* sum is not a time is dropped. */
|
|
193
|
+
function framesOf(
|
|
194
|
+
timestamps: ReadonlyMap<string, number>,
|
|
195
|
+
passOf: (uid: string) => PresentationPass | null,
|
|
196
|
+
): { ms: number; copyMs: number; presentMs: number }[] {
|
|
197
|
+
const frames = new Map<string, { ms: number; copyMs: number; presentMs: number }>();
|
|
198
|
+
for (const [uid, ms] of timestamps) {
|
|
199
|
+
const at = uid.lastIndexOf(":f");
|
|
200
|
+
const key = at < 0 ? uid : uid.slice(at + 2);
|
|
201
|
+
let frame = frames.get(key);
|
|
202
|
+
if (frame === undefined) {
|
|
203
|
+
frame = { ms: 0, copyMs: 0, presentMs: 0 };
|
|
204
|
+
frames.set(key, frame);
|
|
205
|
+
}
|
|
206
|
+
frame.ms += ms;
|
|
207
|
+
const pass = passOf(uid);
|
|
208
|
+
if (pass === "copy") frame.copyMs += ms;
|
|
209
|
+
else if (pass === "present") frame.presentMs += ms;
|
|
210
|
+
}
|
|
211
|
+
return [...frames.values()].filter((frame) => Number.isFinite(frame.ms) && frame.ms >= 0);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** GPU time per frame over the samples a reader asked for. Every value is
|
|
215
|
+
* null when there are none. */
|
|
216
|
+
export interface GpuStats {
|
|
217
|
+
samples: number;
|
|
218
|
+
p50Ms: number | null;
|
|
219
|
+
p95Ms: number | null;
|
|
220
|
+
maxMs: number | null;
|
|
221
|
+
/** Samples the ring overwrote since it was made, inside the range or not. */
|
|
222
|
+
lost: number;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export const NO_GPU_STATS: Readonly<GpuStats> = Object.freeze({
|
|
226
|
+
samples: 0,
|
|
227
|
+
p50Ms: null,
|
|
228
|
+
p95Ms: null,
|
|
229
|
+
maxMs: null,
|
|
230
|
+
lost: 0,
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
/** Over nine minutes of frames at 60 a second. */
|
|
234
|
+
const GPU_SAMPLE_CAPACITY = 1 << 15;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* The frames the timer resolved, newest last, at most `capacity` of them: what
|
|
238
|
+
* the HUD and `__perfProbe.gpu()` read. Each is timed when its resolve landed
|
|
239
|
+
* (the stage's `performance.now()`), a frame or two after its draw.
|
|
240
|
+
*/
|
|
241
|
+
export class GpuSamples {
|
|
242
|
+
readonly #at: Float64Array;
|
|
243
|
+
readonly #ms: Float64Array;
|
|
244
|
+
#count = 0;
|
|
245
|
+
|
|
246
|
+
constructor(capacity = GPU_SAMPLE_CAPACITY) {
|
|
247
|
+
this.#at = new Float64Array(capacity);
|
|
248
|
+
this.#ms = new Float64Array(capacity);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
push(at: number, ms: number): void {
|
|
252
|
+
const slot = this.#count % this.#at.length;
|
|
253
|
+
this.#at[slot] = at;
|
|
254
|
+
this.#ms[slot] = ms;
|
|
255
|
+
this.#count += 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** The kept samples that landed in `[fromMs, toMs)`. */
|
|
259
|
+
stats(fromMs = Number.NEGATIVE_INFINITY, toMs = Number.POSITIVE_INFINITY): GpuStats {
|
|
260
|
+
const capacity = this.#at.length;
|
|
261
|
+
const lost = Math.max(0, this.#count - capacity);
|
|
262
|
+
const values: number[] = [];
|
|
263
|
+
for (let index = lost; index < this.#count; index += 1) {
|
|
264
|
+
const slot = index % capacity;
|
|
265
|
+
const at = this.#at[slot]!;
|
|
266
|
+
if (at >= fromMs && at < toMs) values.push(this.#ms[slot]!);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
samples: values.length,
|
|
270
|
+
p50Ms: percentile(values, 0.5),
|
|
271
|
+
p95Ms: percentile(values, 0.95),
|
|
272
|
+
maxMs: percentile(values, 1),
|
|
273
|
+
lost,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -21,19 +21,33 @@
|
|
|
21
21
|
// and needs no network at all.
|
|
22
22
|
//
|
|
23
23
|
// FAILURE IS NEVER FATAL. Every load is caught: a component that will not
|
|
24
|
-
// resolve is
|
|
25
|
-
//
|
|
26
|
-
//
|
|
24
|
+
// resolve is absent from the map, its id is in `failed`, and the stage
|
|
25
|
+
// marks each placed layer of it as a failed piece (a dashed placeholder on the
|
|
26
|
+
// frame, the reason in the inspector). A stage that threw
|
|
27
|
+
// here would blank the whole editor over one broken import. The reason is the
|
|
28
|
+
// import's own error message: hosted, a module that failed to compile or
|
|
29
|
+
// imports what the stage cannot serve throws one naming the file and the
|
|
30
|
+
// specifier, and that is the line an author needs.
|
|
31
|
+
//
|
|
32
|
+
// THE MODULE EPOCH (moduleEpoch.ts). Hosted, each compile of the user's code
|
|
33
|
+
// is a new epoch, and the registry URL and every project-relative controls URL
|
|
34
|
+
// carry it as `?v=<epoch>`. Locally there is never an epoch, and every URL is
|
|
35
|
+
// byte-for-byte what it always was. Both caches are scoped to the epoch, so a
|
|
36
|
+
// bump re-imports (a failed import included) and an unchanged epoch keeps
|
|
37
|
+
// every promise, hence every module identity.
|
|
27
38
|
import {
|
|
39
|
+
CODE_PREVIEW_DEF_ID,
|
|
40
|
+
CODE_PREVIEW_LAYER_ID,
|
|
28
41
|
controlsModuleOf,
|
|
29
42
|
instanceComponentIdsOf,
|
|
30
43
|
instanceContractBindings,
|
|
31
44
|
instanceModuleOf,
|
|
32
45
|
isBareModuleSpecifier,
|
|
33
46
|
} from "@dailies-studio/shot/doc";
|
|
34
|
-
import type { Doc3D } from "@dailies-studio/shot/doc";
|
|
47
|
+
import type { Doc3D, PieceFailure } from "@dailies-studio/shot/doc";
|
|
35
48
|
import type { Contract } from "@dailies-studio/contract";
|
|
36
49
|
import type { ComponentType } from "react";
|
|
50
|
+
import type { ModuleEpoch } from "./moduleEpoch.js";
|
|
37
51
|
|
|
38
52
|
/** The `virtual:studio-components` row shape (cli registry.ts). */
|
|
39
53
|
interface RegistryEntry {
|
|
@@ -51,15 +65,23 @@ export interface InstanceResolution {
|
|
|
51
65
|
/** One line per component that could not be resolved. Empty when everything
|
|
52
66
|
* loaded (or the document has no instances). */
|
|
53
67
|
errors: string[];
|
|
68
|
+
/** Each component whose module did not load, with its line from `errors`.
|
|
69
|
+
* Its placed layers draw a failed placeholder on the frame instead of
|
|
70
|
+
* nothing. */
|
|
71
|
+
failed: Record<string, string>;
|
|
54
72
|
}
|
|
55
73
|
|
|
56
|
-
const EMPTY: InstanceResolution = { instances: {}, contracts: {}, errors: [] };
|
|
74
|
+
const EMPTY: InstanceResolution = { instances: {}, contracts: {}, errors: [], failed: {} };
|
|
75
|
+
|
|
76
|
+
/** A component load's answer: the component, or why there is none (null when
|
|
77
|
+
* the registry simply does not know the id). */
|
|
78
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
79
|
+
type ComponentLoad = { component: ComponentType<any> } | { reason: string | null };
|
|
57
80
|
|
|
58
81
|
/** Module-level caches: the stage re-resolves on every document identity change
|
|
59
82
|
* (an inspector keystroke), and a dynamic import is idempotent but a REJECTED
|
|
60
83
|
* one is not cheap to retry in a loop. */
|
|
61
|
-
|
|
62
|
-
const componentCache = new Map<string, Promise<ComponentType<any> | null>>();
|
|
84
|
+
const componentCache = new Map<string, Promise<ComponentLoad>>();
|
|
63
85
|
const contractCache = new Map<string, Promise<Contract | null>>();
|
|
64
86
|
|
|
65
87
|
/** The registry's URL, not its module ID. `virtual:studio-components` is
|
|
@@ -68,63 +90,147 @@ const contractCache = new Map<string, Promise<Contract | null>>();
|
|
|
68
90
|
* a const so Vite's import analysis cannot see a literal and try. */
|
|
69
91
|
const COMPONENT_REGISTRY_URL = "/@id/virtual:studio-components";
|
|
70
92
|
|
|
71
|
-
|
|
72
|
-
|
|
93
|
+
/** The dynamic import every module load goes through. */
|
|
94
|
+
export type ImportModule = (url: string) => Promise<unknown>;
|
|
95
|
+
const dynamicImport: ImportModule = (url) => import(/* @vite-ignore */ url);
|
|
96
|
+
|
|
97
|
+
/** `url` under `epoch`, or `url` untouched when there is none. */
|
|
98
|
+
function versioned(url: string, epoch: ModuleEpoch): string {
|
|
99
|
+
return epoch === null ? url : `${url}?v=${epoch}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** A cache key scoped to one epoch. Keys hold no newline, so no two collide. */
|
|
103
|
+
function scoped(epoch: ModuleEpoch, key: string): string {
|
|
104
|
+
return epoch === null ? key : `${epoch}\n${key}`;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function registry(
|
|
108
|
+
epoch: ModuleEpoch,
|
|
109
|
+
importModule: ImportModule,
|
|
110
|
+
): Promise<Record<string, RegistryEntry>> {
|
|
111
|
+
const mod = (await importModule(versioned(COMPONENT_REGISTRY_URL, epoch))) as {
|
|
73
112
|
components?: Record<string, RegistryEntry>;
|
|
74
113
|
};
|
|
75
114
|
return mod.components ?? {};
|
|
76
115
|
}
|
|
77
116
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const hit = componentCache.get(
|
|
117
|
+
function loadComponent(id: string, epoch: ModuleEpoch, importModule: ImportModule): Promise<ComponentLoad> {
|
|
118
|
+
const key = scoped(epoch, id);
|
|
119
|
+
const hit = componentCache.get(key);
|
|
81
120
|
if (hit !== undefined) return hit;
|
|
82
|
-
const p = (async () => {
|
|
83
|
-
const entry = (await registry())[id];
|
|
84
|
-
if (entry === undefined) return null;
|
|
121
|
+
const p = (async (): Promise<ComponentLoad> => {
|
|
122
|
+
const entry = (await registry(epoch, importModule))[id];
|
|
123
|
+
if (entry === undefined) return { reason: null };
|
|
85
124
|
const value = await entry.load();
|
|
86
125
|
// A vanished export resolves to undefined; treat it as "not resolvable"
|
|
87
126
|
// rather than rendering `undefined` into the tree.
|
|
88
127
|
return typeof value === "function" || (typeof value === "object" && value !== null)
|
|
89
128
|
? // eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
90
|
-
|
|
129
|
+
{ component: value as ComponentType<any> }
|
|
130
|
+
: { reason: null };
|
|
131
|
+
})().catch((e: unknown): ComponentLoad => ({ reason: e instanceof Error ? `${e.name}: ${e.message}` : String(e) }));
|
|
132
|
+
componentCache.set(key, p);
|
|
133
|
+
return p;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The registry's own contract for one component, over HTTP.
|
|
138
|
+
*
|
|
139
|
+
* FOR THE UNPLACED ONLY. Every other path here resolves a contract from the
|
|
140
|
+
* DOCUMENT: a declared one through the controls module the layer's
|
|
141
|
+
* `EmbedContractRef` names, an inferred one through the snapshot the document
|
|
142
|
+
* froze at bind time. The code studio renders a component the document does
|
|
143
|
+
* not place, so it has neither, and without a contract `buildProps` falls
|
|
144
|
+
* through to the raw prop map — which for a preview is empty, so the component
|
|
145
|
+
* would mount with no props at all rather than at its declared defaults.
|
|
146
|
+
*
|
|
147
|
+
* `/api/components/:id` is the one answer that covers both contract sources,
|
|
148
|
+
* and it is the same route the chrome's own `loadContract` reads, so the two
|
|
149
|
+
* sides cannot end up holding different contracts for one id. Same-origin GET:
|
|
150
|
+
* the stage is served by this server, so the bridge pairing gate never applies.
|
|
151
|
+
*/
|
|
152
|
+
function loadRegistryContract(id: string, epoch: ModuleEpoch): Promise<Contract | null> {
|
|
153
|
+
const key = scoped(epoch, `registry:${id}`);
|
|
154
|
+
const hit = contractCache.get(key);
|
|
155
|
+
if (hit !== undefined) return hit;
|
|
156
|
+
const p = (async () => {
|
|
157
|
+
const res = await fetch(`/api/components/${encodeURIComponent(id)}`);
|
|
158
|
+
if (!res.ok) return null;
|
|
159
|
+
const detail = (await res.json()) as { contract?: unknown };
|
|
160
|
+
const contract = detail.contract;
|
|
161
|
+
return contract !== null && typeof contract === "object" && "keys" in contract
|
|
162
|
+
? (contract as Contract)
|
|
91
163
|
: null;
|
|
92
164
|
})().catch(() => null);
|
|
93
|
-
|
|
165
|
+
contractCache.set(key, p);
|
|
94
166
|
return p;
|
|
95
167
|
}
|
|
96
168
|
|
|
97
|
-
function loadDeclaredContract(
|
|
98
|
-
|
|
169
|
+
function loadDeclaredContract(
|
|
170
|
+
specifier: string,
|
|
171
|
+
epoch: ModuleEpoch,
|
|
172
|
+
importModule: ImportModule,
|
|
173
|
+
): Promise<Contract | null> {
|
|
174
|
+
const key = scoped(epoch, specifier);
|
|
175
|
+
const hit = contractCache.get(key);
|
|
99
176
|
if (hit !== undefined) return hit;
|
|
100
|
-
|
|
177
|
+
// A bare specifier is a package the import map (or Vite) resolves, never a
|
|
178
|
+
// project file, so it stays bare.
|
|
179
|
+
const url = isBareModuleSpecifier(specifier) ? specifier : versioned(`/${specifier}`, epoch);
|
|
101
180
|
const p = (async () => {
|
|
102
|
-
const mod = (await
|
|
181
|
+
const mod = (await importModule(url)) as { controls?: unknown };
|
|
103
182
|
const controls = mod.controls;
|
|
104
183
|
return controls !== null && typeof controls === "object" && "keys" in controls
|
|
105
184
|
? (controls as Contract)
|
|
106
185
|
: null;
|
|
107
186
|
})().catch(() => null);
|
|
108
|
-
contractCache.set(
|
|
187
|
+
contractCache.set(key, p);
|
|
109
188
|
return p;
|
|
110
189
|
}
|
|
111
190
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
191
|
+
export interface ResolveOptions {
|
|
192
|
+
/** The stage's module epoch. Absent or null: today's URLs exactly. */
|
|
193
|
+
readonly epoch?: ModuleEpoch;
|
|
194
|
+
/** Tests pass a recorder here; the stage never does. */
|
|
195
|
+
readonly importModule?: ImportModule;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Resolve every instance target in `doc`. Pure I/O — no React, no state — so
|
|
200
|
+
* the caller owns when it runs and what it does with a partial answer.
|
|
201
|
+
*
|
|
202
|
+
* `extraIds` are components to resolve that the DOCUMENT does not place. The
|
|
203
|
+
* code studio is the one caller: it renders a component standalone through a
|
|
204
|
+
* synthetic definition that lives outside the document, so the id set the
|
|
205
|
+
* document walks would never include it and the preview would sit empty
|
|
206
|
+
* forever. Their contracts come from the registry route rather than from a
|
|
207
|
+
* layer's binding, because a component with no placement has no binding.
|
|
208
|
+
*/
|
|
209
|
+
export async function resolveInstanceModules(
|
|
210
|
+
doc: Doc3D,
|
|
211
|
+
extraIds: readonly string[] = [],
|
|
212
|
+
options: ResolveOptions = {},
|
|
213
|
+
): Promise<InstanceResolution> {
|
|
214
|
+
const ids = [...new Set([...instanceComponentIdsOf(doc), ...extraIds])].sort();
|
|
116
215
|
if (ids.length === 0) return EMPTY;
|
|
216
|
+
const epoch = options.epoch ?? null;
|
|
217
|
+
const importModule = options.importModule ?? dynamicImport;
|
|
117
218
|
|
|
118
219
|
const instances: InstanceModules = {};
|
|
119
220
|
const errors: string[] = [];
|
|
221
|
+
const failed: Record<string, string> = {};
|
|
120
222
|
await Promise.all(
|
|
121
223
|
ids.map(async (id) => {
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
|
|
224
|
+
const loaded = await loadComponent(id, epoch, importModule);
|
|
225
|
+
if ("reason" in loaded) {
|
|
226
|
+
const line =
|
|
227
|
+
`instance "${id}" could not be loaded from the project` +
|
|
228
|
+
(loaded.reason === null ? "." : `: ${loaded.reason}`);
|
|
229
|
+
failed[id] = line;
|
|
230
|
+
errors.push(line);
|
|
125
231
|
return;
|
|
126
232
|
}
|
|
127
|
-
instances[id] =
|
|
233
|
+
instances[id] = loaded.component;
|
|
128
234
|
}),
|
|
129
235
|
);
|
|
130
236
|
|
|
@@ -136,11 +242,11 @@ export async function resolveInstanceModules(doc: Doc3D): Promise<InstanceResolu
|
|
|
136
242
|
instanceContractBindings(doc).map(async ({ component, ref }) => {
|
|
137
243
|
if (ref.source === "snapshot") return;
|
|
138
244
|
const specifier = controlsModuleOf(instanceModuleOf(component), ref.source);
|
|
139
|
-
const contract = await loadDeclaredContract(specifier);
|
|
245
|
+
const contract = await loadDeclaredContract(specifier, epoch, importModule);
|
|
140
246
|
if (contract === null) {
|
|
141
247
|
errors.push(
|
|
142
248
|
`instance "${component}" declares its contract in ${JSON.stringify(specifier)}, which ` +
|
|
143
|
-
`did not load
|
|
249
|
+
`did not load, so its props render unmapped here.`,
|
|
144
250
|
);
|
|
145
251
|
return;
|
|
146
252
|
}
|
|
@@ -148,5 +254,60 @@ export async function resolveInstanceModules(doc: Doc3D): Promise<InstanceResolu
|
|
|
148
254
|
}),
|
|
149
255
|
);
|
|
150
256
|
|
|
151
|
-
|
|
257
|
+
// The unplaced ones, after the document's own — so a component that is BOTH
|
|
258
|
+
// open in the code studio and placed in the shot keeps the binding the
|
|
259
|
+
// document authored, and the registry answer never shadows it.
|
|
260
|
+
await Promise.all(
|
|
261
|
+
extraIds
|
|
262
|
+
.filter((id) => contracts[id] === undefined)
|
|
263
|
+
.map(async (id) => {
|
|
264
|
+
const contract = await loadRegistryContract(id, epoch);
|
|
265
|
+
// Not an error: a component with no contract at all is legal, and it
|
|
266
|
+
// renders with the props it was given. Only a component the registry
|
|
267
|
+
// does not know is worth saying anything about, and `loadComponent`
|
|
268
|
+
// above has already said it.
|
|
269
|
+
if (contract !== null) contracts[id] = contract;
|
|
270
|
+
}),
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
return { instances, contracts, errors, failed };
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* One isolated failure per placed layer whose component did not load, in the
|
|
278
|
+
* runtime's own `PieceFailure` shape, so it rides the same channel a render
|
|
279
|
+
* crash does: the dashed placeholder on the frame, the failure bar, the
|
|
280
|
+
* chrome's badge and inspector row, and the export gate. `codeId` is the code
|
|
281
|
+
* studio's subject, whose one layer lives in no document.
|
|
282
|
+
*/
|
|
283
|
+
export function instanceLoadFailures(
|
|
284
|
+
doc: Doc3D | null,
|
|
285
|
+
failed: Readonly<Record<string, string>>,
|
|
286
|
+
codeId: string | null,
|
|
287
|
+
): PieceFailure[] {
|
|
288
|
+
const out: PieceFailure[] = [];
|
|
289
|
+
const failure = (sceneId: string, layerId: string, message: string): PieceFailure => ({
|
|
290
|
+
kind: "instance",
|
|
291
|
+
sceneId,
|
|
292
|
+
layerId,
|
|
293
|
+
message,
|
|
294
|
+
stack: "",
|
|
295
|
+
firstFrame: 0,
|
|
296
|
+
rev: null,
|
|
297
|
+
});
|
|
298
|
+
if (doc !== null) {
|
|
299
|
+
const homes = [
|
|
300
|
+
...doc.scenes.map((id) => [id, doc.scenesById[id]] as const),
|
|
301
|
+
...Object.entries(doc.components ?? {}),
|
|
302
|
+
];
|
|
303
|
+
for (const [homeId, home] of homes) {
|
|
304
|
+
for (const layer of Object.values(home?.layersById ?? {})) {
|
|
305
|
+
const message = layer.kind === "instance" ? failed[layer.instance.component] : undefined;
|
|
306
|
+
if (message !== undefined) out.push(failure(homeId, layer.id, message));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const codeMessage = codeId === null ? undefined : failed[codeId];
|
|
311
|
+
if (codeMessage !== undefined) out.push(failure(CODE_PREVIEW_DEF_ID, CODE_PREVIEW_LAYER_ID, codeMessage));
|
|
312
|
+
return out;
|
|
152
313
|
}
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
// Turning a pointer into a DESIGN-SPACE delta, which is the one thing the
|
|
2
2
|
// chrome's document math cannot do for itself.
|
|
3
3
|
//
|
|
4
|
-
// ── THE
|
|
4
|
+
// ── THE PLAIN-vs-ALT RULE, WHICH IS THE WHOLE FILE ──────────────────────
|
|
5
5
|
//
|
|
6
6
|
// The 2D lane could say "screen delta ÷ zoom" because everything was coplanar.
|
|
7
7
|
// Here each layer sits at its OWN depth under a perspective camera, so a screen
|
|
8
8
|
// delta maps to a different world delta per layer — and there is no single
|
|
9
9
|
// correct answer, because two different gestures are being asked for:
|
|
10
10
|
//
|
|
11
|
-
//
|
|
11
|
+
// A PLAIN DRAG unprojects onto the LAYER'S OWN tilted plane. Dragging a tilted
|
|
12
12
|
// layer slides it along its own surface, which is what "move this thing"
|
|
13
13
|
// means once the thing is a plane in space.
|
|
14
14
|
//
|
|
15
|
-
//
|
|
16
|
-
// the export frame. Flat screen space: z never changes and tilt is
|
|
17
|
-
// which is what "move it on the canvas" means.
|
|
15
|
+
// AN ALT-DRAG unprojects onto the plane through the layer's origin PARALLEL
|
|
16
|
+
// to the export frame. Flat screen space: z never changes and tilt is
|
|
17
|
+
// ignored, which is what "move it on the canvas" means.
|
|
18
18
|
//
|
|
19
19
|
// Two `Ray.intersectPlane` calls. One plane constructor different. That is the
|
|
20
20
|
// entire distinction, and stating it as two planes rather than as two code
|
|
@@ -44,6 +44,25 @@ export function gesturePlane(kind: MovePlane, matrixWorld: { elements: ArrayLike
|
|
|
44
44
|
return new Plane().setFromNormalAndCoplanarPoint(normal, origin);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* A plane through a WORLD point with a WORLD normal.
|
|
49
|
+
*
|
|
50
|
+
* `gesturePlane`'s sibling for the two things that have no layer matrix to read
|
|
51
|
+
* a basis out of: the transform gizmo's PLANE PADS (whose normal is a design
|
|
52
|
+
* axis) and its VIEW-PLANE DISC (whose normal is the working camera's own
|
|
53
|
+
* forward). Both then feed the same `designDelta` a body drag does, so there is
|
|
54
|
+
* one unprojection in this lane and not three.
|
|
55
|
+
*/
|
|
56
|
+
export function planeAt(
|
|
57
|
+
point: readonly [number, number, number],
|
|
58
|
+
normal: readonly [number, number, number],
|
|
59
|
+
): Plane {
|
|
60
|
+
return new Plane().setFromNormalAndCoplanarPoint(
|
|
61
|
+
new Vector3(normal[0], normal[1], normal[2]).normalize(),
|
|
62
|
+
new Vector3(point[0], point[1], point[2]),
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
/** Where a ray meets a plane, or null when it is parallel to it (an edge-on
|
|
48
67
|
* layer) — which is a real case and must not become a NaN delta. */
|
|
49
68
|
export function unproject(ray: Ray, plane: Plane): Vector3 | null {
|