@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
package/src/state/types.ts
CHANGED
|
@@ -15,21 +15,477 @@
|
|
|
15
15
|
// So: one document, flat fields, and roughly 1700 lines of board machinery
|
|
16
16
|
// simply not written. Autosave debounce, the sha256 rev and the generation
|
|
17
17
|
// fence all still apply — they were never board-shaped, only board-keyed.
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
import {
|
|
19
|
+
CODE_PREVIEW_FRAMES,
|
|
20
|
+
defDurationFrames,
|
|
21
|
+
docDurationFrames,
|
|
22
|
+
sceneToMaster,
|
|
23
|
+
} from "@dailies-studio/shot/doc";
|
|
24
|
+
import {
|
|
25
|
+
CODE_LIVE_WIDTH,
|
|
26
|
+
DOCK_HEIGHT,
|
|
27
|
+
DOCK_PPF,
|
|
28
|
+
INSPECTOR_WIDTH,
|
|
29
|
+
MATERIAL_COLUMN,
|
|
30
|
+
RAIL_WIDTH,
|
|
31
|
+
} from "./panelSize.js";
|
|
32
|
+
import type {
|
|
33
|
+
Doc3D,
|
|
34
|
+
MaterialGraphSocketType,
|
|
35
|
+
MeshMaterialSlotInfo,
|
|
36
|
+
} from "@dailies-studio/shot/doc";
|
|
37
|
+
import type { Ease } from "@dailies-studio/engine";
|
|
38
|
+
import type {
|
|
39
|
+
AgentCredentialsInfo,
|
|
40
|
+
AgentTranscriptItem,
|
|
41
|
+
} from "@dailies-studio/agent-client";
|
|
21
42
|
import type { Contract } from "@dailies-studio/contract";
|
|
22
|
-
import type {
|
|
23
|
-
|
|
43
|
+
import type {
|
|
44
|
+
CodeDiagnostic,
|
|
45
|
+
ComponentDefaultsMeta,
|
|
46
|
+
ComponentFileHome,
|
|
47
|
+
ComponentListingUpdate,
|
|
48
|
+
ComponentRow,
|
|
49
|
+
LibraryItem,
|
|
50
|
+
LibraryItemDetail,
|
|
51
|
+
LibraryItemKind,
|
|
52
|
+
ProjectAssetUsage,
|
|
53
|
+
ProjectFile,
|
|
54
|
+
RenderJob,
|
|
55
|
+
ShotVersion,
|
|
56
|
+
ShotVersionList,
|
|
57
|
+
WorkspaceComponent,
|
|
58
|
+
WorkspaceComponentVersion,
|
|
59
|
+
WorkspaceComponentVersionInfo,
|
|
60
|
+
WorkspaceFontList,
|
|
61
|
+
} from "../api.js";
|
|
62
|
+
import type { GoogleFontEntry } from "../fonts/googleFonts.js";
|
|
63
|
+
import type { FontApplyProgress } from "./fonts.js";
|
|
64
|
+
import type { Value } from "@dailies-studio/engine";
|
|
65
|
+
import type {
|
|
66
|
+
PlaybackResolution,
|
|
67
|
+
ShadingMode,
|
|
68
|
+
ShotComponentBackground,
|
|
69
|
+
ShotStageInfo,
|
|
70
|
+
StagePerformanceReport,
|
|
71
|
+
StagePieceFailure,
|
|
72
|
+
ShotView,
|
|
73
|
+
SplineBasis,
|
|
74
|
+
SplineEditContext,
|
|
75
|
+
StageInset,
|
|
76
|
+
StageLayout,
|
|
77
|
+
StagePane,
|
|
78
|
+
WorkingView,
|
|
79
|
+
WorkPlaneTarget,
|
|
80
|
+
} from "../bridgeTypes.js";
|
|
24
81
|
import { INITIAL_WORKING_VIEW } from "../stage/workingView.js";
|
|
25
82
|
import type { DrawPoint } from "./insert.js";
|
|
26
83
|
import type { ShotTool } from "../tools.js";
|
|
84
|
+
import type { SyncStatus } from "./sync.js";
|
|
27
85
|
|
|
28
86
|
export type SaveState = "idle" | "saving" | "saved" | "error";
|
|
29
87
|
|
|
88
|
+
/** The dialog a delete under live sync shows (`syncDeleted`). */
|
|
89
|
+
export interface SyncDeleted {
|
|
90
|
+
/** The Project's store name and title when it was trashed. */
|
|
91
|
+
name: string;
|
|
92
|
+
title: string;
|
|
93
|
+
/** Who trashed it, by name; null when it was this user or is unknown. */
|
|
94
|
+
by: string | null;
|
|
95
|
+
color: string | null;
|
|
96
|
+
/** The Trash handle; null when the session did not say (no Restore). */
|
|
97
|
+
trashId: string | null;
|
|
98
|
+
/** `describePending` of what waits. */
|
|
99
|
+
lines: string[];
|
|
100
|
+
count: number;
|
|
101
|
+
/** A Restore or Save as copy is under way. */
|
|
102
|
+
busy: boolean;
|
|
103
|
+
error: string | null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** The Components workspace's two tabs: the component alone on the stage, or
|
|
107
|
+
* the studio that edits it (Component Studio or Code Studio). */
|
|
108
|
+
export type ComponentsTab = "preview" | "edit";
|
|
109
|
+
|
|
110
|
+
/** The shot board's tools (its footer): Select V, Text T, Box R, Arrow A,
|
|
111
|
+
* Image I and Component C. Image and Component open their pickers. */
|
|
112
|
+
export type SketchTool = "select" | "text" | "box" | "arrow" | "image" | "component";
|
|
113
|
+
|
|
114
|
+
/** Which component the Components workspace shows: a Studio-made definition
|
|
115
|
+
* of this document, or a code component by registry id. */
|
|
116
|
+
/** One use whose changes have no prop in a newer version: where it is, and
|
|
117
|
+
* the keys. The changes stay on the use, kept aside, not applied. */
|
|
118
|
+
export interface ComponentUpdateRemoved {
|
|
119
|
+
where: string;
|
|
120
|
+
keys: string[];
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** An Update waiting for a yes, with the props it drops that uses changed. */
|
|
124
|
+
export interface ComponentUpdatePending {
|
|
125
|
+
defId: string;
|
|
126
|
+
version: number;
|
|
127
|
+
/** True for a Community copy: the server writes it, so the yes is always
|
|
128
|
+
* asked and says how to take it back. */
|
|
129
|
+
community: boolean;
|
|
130
|
+
/** One entry per use that loses a changed prop. Null when the Community
|
|
131
|
+
* listing's definition could not be read first: its list then comes after
|
|
132
|
+
* the update, in `ComponentUpdateApplied`. */
|
|
133
|
+
removed: ComponentUpdateRemoved[] | null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** A Community update the server wrote into this document, held so the
|
|
137
|
+
* author sees what it kept aside and can put the document back. */
|
|
138
|
+
export interface ComponentUpdateApplied {
|
|
139
|
+
defId: string;
|
|
140
|
+
version: number;
|
|
141
|
+
removed: ComponentUpdateRemoved[];
|
|
142
|
+
/** The document the server wrote against, and the one it wrote. Revert
|
|
143
|
+
* update puts `before` back only while the open document is `after`. */
|
|
144
|
+
before: Doc3D;
|
|
145
|
+
after: Doc3D;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export type ComponentsSelection = { kind: "studio"; def: string } | { kind: "code"; module: string };
|
|
149
|
+
|
|
150
|
+
/** The Shot a door into Components left (C7): the entered Shot, the playhead,
|
|
151
|
+
* the selection, and the use the door opened (`layerUseKey`). `shotFrame` is
|
|
152
|
+
* the frame inside that Shot, for the crumb. */
|
|
153
|
+
export type ComponentsOrigin = {
|
|
154
|
+
sel: ComponentsSelection;
|
|
155
|
+
useKey: string;
|
|
156
|
+
enteredScene: string | null;
|
|
157
|
+
sceneId: string | null;
|
|
158
|
+
frame: number;
|
|
159
|
+
shotFrame: number;
|
|
160
|
+
selection: readonly string[];
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/** The Components workspace's preview look (bridge v45). Null fields keep the
|
|
164
|
+
* stage's own ground and the component's own length. */
|
|
165
|
+
/** One build check of a code component. */
|
|
166
|
+
export interface ComponentBuild {
|
|
167
|
+
status: "building" | "built" | "failed";
|
|
168
|
+
/** Why it failed. */
|
|
169
|
+
message?: string;
|
|
170
|
+
/** The workspace version the passing build recorded. */
|
|
171
|
+
version?: number;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export interface ComponentPreviewLook {
|
|
175
|
+
background: ShotComponentBackground | null;
|
|
176
|
+
loopFrames: number | null;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The Insert drawer's sections, top to bottom, and its jump chips. */
|
|
180
|
+
export const INSERT_SECTIONS = ["Basics", "Icons", "Shaders", "Backgrounds"] as const;
|
|
181
|
+
export type InsertSection = (typeof INSERT_SECTIONS)[number];
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* What the open source buffer is doing, when it is doing something other than
|
|
185
|
+
* sitting there.
|
|
186
|
+
*
|
|
187
|
+
* DIRTY IS NOT IN HERE. It is `content !== savedContent`, a comparison that
|
|
188
|
+
* cannot be stale, and a status value that also claimed to know would be a
|
|
189
|
+
* second answer to the same question — the one that ends up disagreeing after
|
|
190
|
+
* an undo lands the buffer back on the saved text.
|
|
191
|
+
*
|
|
192
|
+
* `"conflict"` is STICKY: the file changed on disk under the buffer (a 409, or
|
|
193
|
+
* an agent's edit arriving over SSE) and stays that way until the author picks
|
|
194
|
+
* reload or overwrite, because silently choosing either one loses work.
|
|
195
|
+
*/
|
|
196
|
+
export type CodeStatus = "idle" | "saving" | "conflict" | "error";
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* The source file behind the open code component.
|
|
200
|
+
*
|
|
201
|
+
* SAVES EXPLICITLY (Ctrl+S), never on keystroke: every save re-runs contract
|
|
202
|
+
* extraction and reloads the stage, so autosaving half-typed code would thrash
|
|
203
|
+
* both and show the author a stream of broken renders they did not ask for.
|
|
204
|
+
*/
|
|
205
|
+
export interface CodeFileState {
|
|
206
|
+
/** Posix path relative to the project root, which is `ComponentRow.rel`. */
|
|
207
|
+
rel: string;
|
|
208
|
+
/** Live editor text, LF-normalised (CodeMirror is LF-only internally). */
|
|
209
|
+
content: string;
|
|
210
|
+
/** Text at last load or save, LF. Dirty is `content !== savedContent`. */
|
|
211
|
+
savedContent: string;
|
|
212
|
+
/** Server hash at last load or save, sent as `baseHash` so a save can 409. */
|
|
213
|
+
savedHash: string | null;
|
|
214
|
+
/** Restored on save, so a CRLF file round-trips as CRLF. */
|
|
215
|
+
lineEnding: "lf" | "crlf";
|
|
216
|
+
status: CodeStatus;
|
|
217
|
+
/** What went wrong, when `status` is "error" or "conflict". */
|
|
218
|
+
error: string | null;
|
|
219
|
+
/** The HTTP status behind `error`. 404 means the file was deleted on disk. */
|
|
220
|
+
errorStatus: number | null;
|
|
221
|
+
diagnostics: readonly CodeDiagnostic[];
|
|
222
|
+
/**
|
|
223
|
+
* This file is a BUILD PRODUCT the studio itself emits, so the server will
|
|
224
|
+
* refuse every write to it with a 403 and no force.
|
|
225
|
+
*
|
|
226
|
+
* Carried on the buffer so the editor can say so BEFORE the author types a
|
|
227
|
+
* page of changes into a file that cannot accept them. Read from the file's
|
|
228
|
+
* own bytes on the way in (`SourceFile.generated`), not guessed from the
|
|
229
|
+
* path: the marker rule catches an emit that moved, and the directory rule
|
|
230
|
+
* alone would miss it. `docName` is the owning Project's store name when
|
|
231
|
+
* the server resolved it, so the strip can open it.
|
|
232
|
+
*/
|
|
233
|
+
generated: { docName: string | null } | null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The viewport/editor state borrowed while one mesh's material graph is open.
|
|
237
|
+
* None of it is document data: leaving the graph hands the author back the
|
|
238
|
+
* exact stage, camera and tool posture they were using before they went in. */
|
|
239
|
+
export interface MaterialWorkspaceRestore {
|
|
240
|
+
selection: readonly string[];
|
|
241
|
+
stageLayout: StageLayout;
|
|
242
|
+
workingView: WorkingView;
|
|
243
|
+
activePane: StagePane;
|
|
244
|
+
shadingMode: ShadingMode;
|
|
245
|
+
previousShading: ShadingMode;
|
|
246
|
+
tool: ShotTool;
|
|
247
|
+
pilot: boolean;
|
|
248
|
+
isolation: string | null;
|
|
249
|
+
isolationOrbit: boolean;
|
|
250
|
+
splineEdit: SplineEditContext | null;
|
|
251
|
+
animateMode: boolean;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** A persistent material-authoring context over the live stage. The target is
|
|
255
|
+
* addressed by its container because component definitions and shots can both
|
|
256
|
+
* contain mesh ids, while the graph itself is resolved through the layer's
|
|
257
|
+
* current material binding on every document update (undo may replace it). */
|
|
258
|
+
export interface MaterialWorkspaceState {
|
|
259
|
+
containerId: string;
|
|
260
|
+
layerId: string;
|
|
261
|
+
/**
|
|
262
|
+
* WHICH SURFACE of the target the graph is editing (schema v17).
|
|
263
|
+
*
|
|
264
|
+
* A glTF material index, or null for the layer-wide binding — which is the
|
|
265
|
+
* only possibility on a primitive, and the default on a model until the
|
|
266
|
+
* author picks a slot. Editor state: the DOCUMENT records the binding, and
|
|
267
|
+
* this records which one is on the canvas.
|
|
268
|
+
*/
|
|
269
|
+
slot: number | null;
|
|
270
|
+
restore: MaterialWorkspaceRestore;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/** What the graph canvas has selected. Nodes and edges together because one
|
|
274
|
+
* rubber band picks up both, and Delete acts on whatever it caught. */
|
|
275
|
+
export interface MaterialGraphSelection {
|
|
276
|
+
nodeIds: readonly string[];
|
|
277
|
+
edgeIds: readonly string[];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* The socket a palette was opened FROM, when the author dragged a wire into
|
|
282
|
+
* empty space rather than pressing Tab.
|
|
283
|
+
*
|
|
284
|
+
* `side` says which end of the wire is in hand: `"source"` means the author is
|
|
285
|
+
* holding an OUTPUT and wants something to feed, `"target"` means they are
|
|
286
|
+
* holding an input and want something to feed it. The type rides along so the
|
|
287
|
+
* palette can put the kinds that would actually connect at the top, rather than
|
|
288
|
+
* letting the author find out by dropping one and being refused.
|
|
289
|
+
*/
|
|
290
|
+
export interface MaterialPaletteSource {
|
|
291
|
+
nodeId: string;
|
|
292
|
+
socket: string;
|
|
293
|
+
side: "source" | "target";
|
|
294
|
+
type: MaterialGraphSocketType;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* "Open the node palette here". A MAILBOX, like `stageMenu` and `appMenu`, with
|
|
299
|
+
* the same fresh-identity rule: pressing Tab twice at the same point must
|
|
300
|
+
* re-open the palette rather than be swallowed by value equality.
|
|
301
|
+
*
|
|
302
|
+
* `at` is a point in GRAPH coordinates, or null when the palette should place
|
|
303
|
+
* what it adds wherever the canvas considers the middle — the keyboard route,
|
|
304
|
+
* which has no pointer to ask.
|
|
305
|
+
*/
|
|
306
|
+
export interface MaterialPaletteRequest {
|
|
307
|
+
at: { x: number; y: number } | null;
|
|
308
|
+
from?: MaterialPaletteSource;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Stage-originated shader status. Kept separate from the document because a
|
|
312
|
+
* compiler result says what this browser rendered, never what the author saved. */
|
|
313
|
+
export type MaterialCompileState =
|
|
314
|
+
| { status: "idle" }
|
|
315
|
+
| {
|
|
316
|
+
status: "compiling";
|
|
317
|
+
materialId: string;
|
|
318
|
+
layerId: string;
|
|
319
|
+
slot: number | null;
|
|
320
|
+
}
|
|
321
|
+
| {
|
|
322
|
+
status: "ready";
|
|
323
|
+
materialId: string;
|
|
324
|
+
layerId: string;
|
|
325
|
+
slot: number | null;
|
|
326
|
+
}
|
|
327
|
+
| {
|
|
328
|
+
status: "error";
|
|
329
|
+
materialId: string;
|
|
330
|
+
layerId: string;
|
|
331
|
+
slot: number | null;
|
|
332
|
+
message: string;
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* ONE KEYFRAME, AS A CURVE GESTURE ADDRESSES IT.
|
|
337
|
+
*
|
|
338
|
+
* ── AND WHY THIS ONE IS BY ID WHEN `selectedKeyframe` IS BY POSITION ──────
|
|
339
|
+
*
|
|
340
|
+
* `selectedKeyframe`'s comment argues that a PERSISTED subject must be a
|
|
341
|
+
* position: a key can be replaced by a drag landing on its frame, and an id
|
|
342
|
+
* kept across that would address something that is gone.
|
|
343
|
+
*
|
|
344
|
+
* A curve gesture is the opposite case. It runs inside ONE scrub, every capture
|
|
345
|
+
* re-derives from the same base document, and the whole point of the drag is
|
|
346
|
+
* that the frame is CHANGING. A position captured at the press would be right
|
|
347
|
+
* for one pointer event and wrong after it; the id is stable for exactly as
|
|
348
|
+
* long as the gesture is, and `captureCurveOps` resolves it against the base —
|
|
349
|
+
* never against the live document.
|
|
350
|
+
*/
|
|
351
|
+
export interface CurveKeyRef {
|
|
352
|
+
containerId: string;
|
|
353
|
+
/** Null = the SHOT itself, the address every keyframe verb already takes. */
|
|
354
|
+
layerId: string | null;
|
|
355
|
+
path: string;
|
|
356
|
+
/** The keyframe's own `kf_…` id. */
|
|
357
|
+
keyframeId: string;
|
|
358
|
+
/** Which component of a VECTOR track the value edit writes. Absent on a
|
|
359
|
+
* scalar track; a vector's keyframe frames are shared across components
|
|
360
|
+
* because they are one track. */
|
|
361
|
+
component?: number;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/** A curve gesture's edits, applied in order inside one scrub capture. A batch
|
|
365
|
+
* and not one call each because a keyframe drag MOVES and SETS THE VALUE
|
|
366
|
+
* atomically, and two captures would be two undo entries and one visible
|
|
367
|
+
* intermediate document. */
|
|
368
|
+
export type CurveOp =
|
|
369
|
+
| {
|
|
370
|
+
kind: "move";
|
|
371
|
+
/** `keys[0]` ANCHORS: every key moves by one delta, and a snapped move
|
|
372
|
+
* snaps the first one's landing and applies what it got back to the rest,
|
|
373
|
+
* so the shape of a multi-key selection survives the pull. */
|
|
374
|
+
keys: readonly CurveKeyRef[];
|
|
375
|
+
deltaFrames: number;
|
|
376
|
+
/** Pull the anchor onto a nearby edge — THE ROWS' snap, same targets and
|
|
377
|
+
* same threshold. Off (absent) is the raw rounded delta. */
|
|
378
|
+
snap?: boolean;
|
|
379
|
+
}
|
|
380
|
+
| { kind: "setValue"; key: CurveKeyRef; value: number }
|
|
381
|
+
| { kind: "setEase"; keys: readonly CurveKeyRef[]; ease: Ease };
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* ONE KEYFRAME'S ADDRESS, as the store holds it and every keyframe verb takes
|
|
385
|
+
* it: which container, which layer (null = the shot itself), which path, which
|
|
386
|
+
* frame. See `selectedKeyframes` for why it is a position and not an id.
|
|
387
|
+
*/
|
|
388
|
+
export interface KeyframeSel {
|
|
389
|
+
containerId: string;
|
|
390
|
+
layerId: string | null;
|
|
391
|
+
path: string;
|
|
392
|
+
frame: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** Do these two addresses name the same keyframe? All four fields, because
|
|
396
|
+
* that is the whole address — a path match in the wrong container is a
|
|
397
|
+
* different key on a different layer. */
|
|
398
|
+
export function sameKeyframeSel(a: KeyframeSel, b: KeyframeSel): boolean {
|
|
399
|
+
return (
|
|
400
|
+
a.containerId === b.containerId &&
|
|
401
|
+
a.layerId === b.layerId &&
|
|
402
|
+
a.path === b.path &&
|
|
403
|
+
a.frame === b.frame
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/** The same identity as a Map key. A PIPE, `curveRowId`'s reason: every part is
|
|
408
|
+
* an id slug or a dotted path and none of them can contain one. */
|
|
409
|
+
export function keyframeSelKey(a: KeyframeSel): string {
|
|
410
|
+
return `${a.containerId}|${a.layerId ?? ""}|${a.path}|${a.frame}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* The selection's PRIMARY keyframe, or null.
|
|
415
|
+
*
|
|
416
|
+
* Every consumer that describes ONE keyframe — the inspector's heading, the
|
|
417
|
+
* curve pane's subject ladder, "which track am I looking at" — reads this
|
|
418
|
+
* rather than indexing the array, so widening the field stayed a rename at
|
|
419
|
+
* each of those sites instead of a decision at each of them.
|
|
420
|
+
*/
|
|
421
|
+
export function primaryKeyframe(s: {
|
|
422
|
+
selectedKeyframes: readonly KeyframeSel[];
|
|
423
|
+
}): KeyframeSel | null {
|
|
424
|
+
return s.selectedKeyframes[0] ?? null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* EVERY SPEED THE SHUTTLE HAS, in ladder order.
|
|
429
|
+
*
|
|
430
|
+
* A closed list rather than a number, so "what does a third press of J do" has
|
|
431
|
+
* one answer and no caller can invent 3.5x. `1` is the resting rate and the one
|
|
432
|
+
* every start returns to; the negatives run the picture backwards.
|
|
433
|
+
*
|
|
434
|
+
* SIX AND NOT MORE. Each rung is a doubling, which is the only spacing where a
|
|
435
|
+
* press feels like a step rather than a nudge, and 4x at 30fps is already 120
|
|
436
|
+
* distinct frames a second — past that the display, not the ladder, is the
|
|
437
|
+
* limit.
|
|
438
|
+
*/
|
|
439
|
+
export const SHUTTLE_RATES = [-4, -2, -1, 1, 2, 4] as const;
|
|
440
|
+
|
|
441
|
+
export type PlayRate = (typeof SHUTTLE_RATES)[number];
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* A window of MASTER frames, half open: `start` plays, `end` does not.
|
|
445
|
+
*
|
|
446
|
+
* The shape `sceneWindows` already uses, deliberately — the playback clock
|
|
447
|
+
* wraps `[start, end)` for an entered shot and for a work area with the same
|
|
448
|
+
* arithmetic, and a closed range here would need one of the two to subtract a
|
|
449
|
+
* frame that the other does not.
|
|
450
|
+
*/
|
|
451
|
+
export interface WorkArea {
|
|
452
|
+
start: number;
|
|
453
|
+
end: number;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* One ask for the dock to show some layers' rows (`dockReveal`).
|
|
458
|
+
*
|
|
459
|
+
* THREE SEPARATE ANSWERS, because the two askers want different amounts. The
|
|
460
|
+
* board's "Fine-tune in Studio" selects keys, and a key on a closed row is a
|
|
461
|
+
* selection nobody can see, so it opens the layer's own lanes. A pick on the stage or in Layers only wants the row in view: opening
|
|
462
|
+
* its lanes would grow the dock under the author on every click.
|
|
463
|
+
*/
|
|
464
|
+
export interface DockReveal {
|
|
465
|
+
layerIds: readonly string[];
|
|
466
|
+
/** Open each layer's own channel lanes (the shot context's disclosure). */
|
|
467
|
+
lanes: boolean;
|
|
468
|
+
/** Open the row's shot group and scroll the first layer's row into view. */
|
|
469
|
+
scroll: boolean;
|
|
470
|
+
/** Flash the first layer's row once (`is-revealed`). */
|
|
471
|
+
flash: boolean;
|
|
472
|
+
/** Audio clip ids to show (gap 8). Sound mode scrolls to the first clip's
|
|
473
|
+
* track row and flashes it; the other modes have no clip rows to show. */
|
|
474
|
+
audioIds?: readonly string[];
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/** The Preferences page fields `setEditorPref` writes. The grid, the badges
|
|
478
|
+
* and the playhead rule keep their older setters. */
|
|
479
|
+
export type EditorPrefField =
|
|
480
|
+
| "motionPaths"
|
|
481
|
+
| "rulers"
|
|
482
|
+
| "smartGuides"
|
|
483
|
+
| "blockLabels"
|
|
484
|
+
| "fieldLabels"
|
|
485
|
+
| "toolbarPosition"
|
|
486
|
+
| "playbackResolution";
|
|
487
|
+
|
|
30
488
|
export interface ShotEditorState {
|
|
31
|
-
/** Everything in `.studio/shots/`, for the switcher. */
|
|
32
|
-
shots: readonly ShotListing[];
|
|
33
489
|
/** The open document's store name, or null at the empty state. */
|
|
34
490
|
name: string | null;
|
|
35
491
|
doc: Doc3D | null;
|
|
@@ -40,47 +496,203 @@ export interface ShotEditorState {
|
|
|
40
496
|
/** MASTER frame. Integer, always. */
|
|
41
497
|
frame: number;
|
|
42
498
|
playing: boolean;
|
|
499
|
+
/**
|
|
500
|
+
* HOW FAST, AND WHICH WAY, the transport runs. Only read while `playing`.
|
|
501
|
+
*
|
|
502
|
+
* A WORKSPACE PREF and never the document: it is the author holding a key to
|
|
503
|
+
* find a moment, in the same family as `previewVolume` and for the same
|
|
504
|
+
* reason — an export that came out at 4x because somebody shuttled would be
|
|
505
|
+
* the worst kind of silent corruption. `setPlaying(true)` puts it back to 1,
|
|
506
|
+
* so a shuttle is never a mode the author has to remember to leave.
|
|
507
|
+
*
|
|
508
|
+
* THE PICTURE ONLY. See `transportAudible`: sound is silent at every rate but
|
|
509
|
+
* 1, because reverse needs reversed buffers and 2x needs a time stretcher,
|
|
510
|
+
* and either one is a second clock that can disagree with the picture's.
|
|
511
|
+
*/
|
|
512
|
+
playRate: PlayRate;
|
|
513
|
+
/**
|
|
514
|
+
* WHETHER THE PROJECT TRANSPORT LOOPS its window (the work area, or the whole
|
|
515
|
+
* film). Off, playback stops on the window's last frame. The dock's Loop
|
|
516
|
+
* button, in the Project context only: a shot or a definition always loops,
|
|
517
|
+
* because those are the views an author watches a beat in over and over
|
|
518
|
+
* (`playbackLoops` is the effective answer). A workspace pref like
|
|
519
|
+
* `playRate`, carried across a document switch by `workspacePrefs`.
|
|
520
|
+
*/
|
|
521
|
+
loopPlayback: boolean;
|
|
522
|
+
/**
|
|
523
|
+
* A QUEUED PLAY, or null: Play is on and the transport has not started.
|
|
524
|
+
*
|
|
525
|
+
* `"armed"` waits for the stage to be ready (`stageReady`), `"preparing"`
|
|
526
|
+
* runs a Play pass that has drawn `done` of `total` frames (each frame
|
|
527
|
+
* playback would pay a first-use cost on, see `playbackPreparation.ts`), and
|
|
528
|
+
* `"starting"` waits for the stage to hand over on the start frame. The
|
|
529
|
+
* transport then starts by itself, in one write that clears this and sets
|
|
530
|
+
* `playing` and `playOrigin`.
|
|
531
|
+
*
|
|
532
|
+
* `playing` stays the transport's truth and is false while this is set.
|
|
533
|
+
* Every toggle reads `playOn`, so a second press cancels a queued Play. The
|
|
534
|
+
* same pass run while the author sits paused (the background pass) sets no
|
|
535
|
+
* intent: it is no start, and nothing on screen shows it.
|
|
536
|
+
*/
|
|
537
|
+
playIntent: { phase: "armed" | "preparing" | "starting"; done: number; total: number } | null;
|
|
538
|
+
/**
|
|
539
|
+
* WHERE AND WHEN THE TRANSPORT STARTED, or null while it is stopped: the
|
|
540
|
+
* frame (`defFrame` inside a definition) and the `performance.now()` of the
|
|
541
|
+
* start. Written in the same write that turns `playing` true, and cleared
|
|
542
|
+
* on every stop. The picture clock and the audio session both anchor on
|
|
543
|
+
* it, so picture and sound start from one instant.
|
|
544
|
+
*/
|
|
545
|
+
playOrigin: { frame: number; at: number } | null;
|
|
546
|
+
/**
|
|
547
|
+
* THE WORK AREA: the window playback loops inside, or null for the whole
|
|
548
|
+
* document.
|
|
549
|
+
*
|
|
550
|
+
* MASTER frames, half open — `[start, end)`, the shape `sceneWindows` uses,
|
|
551
|
+
* so "the last frame" needs no off-by-one anywhere.
|
|
552
|
+
*
|
|
553
|
+
* SESSION STATE AND DELIBERATELY NOT A DOCUMENT FIELD. It is a monitoring
|
|
554
|
+
* preference, like the preview volume: a document field would cost a schema
|
|
555
|
+
* bump and a migration for something that is not part of the film, and the
|
|
556
|
+
* render side already owns an internal frame range a CLI flag can reach
|
|
557
|
+
* without touching the document at all.
|
|
558
|
+
*
|
|
559
|
+
* IT IS ALSO NOT A WORKSPACE PREF, which is the one place it parts company
|
|
560
|
+
* with `playRate`: these are frames of THIS document, and carrying them to
|
|
561
|
+
* the next one would loop the transport over a window that means nothing
|
|
562
|
+
* there. `workspacePrefs` leaves it out on purpose, so a document switch
|
|
563
|
+
* clears it.
|
|
564
|
+
*
|
|
565
|
+
* It applies only in the scene context. A shot already loops inside its own
|
|
566
|
+
* window and a component definition runs its own clock, so both of those
|
|
567
|
+
* have an answer already and a second window would be two answers.
|
|
568
|
+
*/
|
|
569
|
+
workArea: WorkArea | null;
|
|
43
570
|
|
|
44
|
-
/**
|
|
45
|
-
*
|
|
46
|
-
*
|
|
571
|
+
/**
|
|
572
|
+
* Which shot the inspector and the isolation scope are looking at: the one
|
|
573
|
+
* the PLAYHEAD is in, or the one a row/strip click has pinned.
|
|
574
|
+
*
|
|
575
|
+
* DERIVED, NOT A MODE BIT. `sceneAt` ends `?? doc.scenes[0] ?? null`, so it is
|
|
576
|
+
* non-null for any open document, and `seek`, `onHistoryChange` and
|
|
577
|
+
* `loadInto` all rewrite it. It used to be described here as "the single
|
|
578
|
+
* bit that decides macro vs micro" — that was true of the two-level dock this
|
|
579
|
+
* lane deleted, and stayed on the field for a release after the thing it
|
|
580
|
+
* described was gone. The macro/micro bit is `enteredScene`, below.
|
|
581
|
+
*/
|
|
47
582
|
sceneId: string | null;
|
|
583
|
+
/**
|
|
584
|
+
* The shot the author has ENTERED, or null for the scene context.
|
|
585
|
+
*
|
|
586
|
+
* THE ONE CONTEXT BIT, and the reason it is a field of its own rather than a
|
|
587
|
+
* reading of `sceneId`: `sceneId` follows the playhead, so "no shot entered"
|
|
588
|
+
* has no spelling in it at all.
|
|
589
|
+
*
|
|
590
|
+
* null → SCENE context. Arrange shots, aim the camera, grade.
|
|
591
|
+
* set → SHOT context. The elements of that shot and their
|
|
592
|
+
* property lanes.
|
|
593
|
+
* `editingDef` → COMPONENT context, nested inside the shot context.
|
|
594
|
+
*
|
|
595
|
+
* Written ONLY by `enterScene`/`leaveScene` (and the document-swap clear),
|
|
596
|
+
* never derived. While it is set the master playhead is clamped inside that
|
|
597
|
+
* shot's window by `clampToEntered`, which is what keeps every one of the
|
|
598
|
+
* ~83 existing `sceneId` readers correct with no edit: `sceneAt` can only
|
|
599
|
+
* ever answer with the entered shot.
|
|
600
|
+
*/
|
|
601
|
+
enteredScene: string | null;
|
|
48
602
|
/** Layer ids, in document order, deduped. */
|
|
49
603
|
selection: readonly string[];
|
|
50
604
|
|
|
51
605
|
/** Which tool is armed. Decides what a canvas press does, and which plane a
|
|
52
606
|
* move unprojects onto. */
|
|
53
607
|
tool: ShotTool;
|
|
608
|
+
/** Per-shot construction planes. Editor state only and reset on project load. */
|
|
609
|
+
workPlanes: Readonly<Record<string, WorkPlaneTarget>>;
|
|
610
|
+
/** Interpolation used by the next Shift+P draft. A workspace preference. */
|
|
611
|
+
splineBasis: SplineBasis;
|
|
612
|
+
/** The spline whose anchors are being edited, or null for layer mode. */
|
|
613
|
+
splineEdit: SplineEditContext | null;
|
|
54
614
|
|
|
55
615
|
/**
|
|
56
|
-
*
|
|
616
|
+
* ⇧A, Animate: edits write keyframes at the playhead.
|
|
57
617
|
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
618
|
+
* With it on, every author edit (a drag, a typed value, a nudge) still lands
|
|
619
|
+
* as a value, and the store then keys each static field it moved: the old
|
|
620
|
+
* value at the layer's start and the new one at the playhead, in the same
|
|
621
|
+
* undo step (`noteChange`, store.ts). A field a track already writes is
|
|
622
|
+
* keyed at the playhead whether this is on or not. An agent apply never
|
|
623
|
+
* records.
|
|
61
624
|
*
|
|
62
|
-
*
|
|
63
|
-
* arms
|
|
625
|
+
* A BEHAVIOUR bit: nothing but ⇧A and its toolbar button may write this, so
|
|
626
|
+
* looking at a panel never arms recording on the next drag.
|
|
64
627
|
*/
|
|
65
|
-
inspectorMode: "design" | "animate";
|
|
66
628
|
animateMode: boolean;
|
|
67
629
|
|
|
68
630
|
/**
|
|
69
|
-
* The
|
|
631
|
+
* The camera move the dock has focused, or null.
|
|
70
632
|
*
|
|
71
|
-
* Addressed by (scene,
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* scene
|
|
76
|
-
|
|
77
|
-
|
|
633
|
+
* Addressed by (scene, shot) even though a shot id is unique across the
|
|
634
|
+
* document: the SCENE is what stopped being derivable when the dock became
|
|
635
|
+
* one master strip. `state.sceneId` follows the playhead, so a move clicked
|
|
636
|
+
* in scene 3 while the playhead sits in scene 1 would have sent Delete at
|
|
637
|
+
* scene 1, where the id does not exist, and it would silently do nothing.
|
|
638
|
+
*/
|
|
639
|
+
selectedMove: { sceneId: string; shotId: string } | null;
|
|
640
|
+
/**
|
|
641
|
+
* The track keyframes the dock has focused. Empty when none is.
|
|
642
|
+
*
|
|
643
|
+
* ── ADDRESSED BY POSITION, NOT BY ID ────────────────────────────────────
|
|
644
|
+
*
|
|
645
|
+
* A `kf_…` id exists and is unique, and this deliberately does not use it. The
|
|
646
|
+
* three edits an author makes to a keyframe are "move it", "ease it" and
|
|
647
|
+
* "delete it", and every one of them is `(track, frame)` — `setKeyframeAt`
|
|
648
|
+
* REPLACES by frame, and a drag onto an occupied frame replaces the key that
|
|
649
|
+
* was there. Keying selection on an id would leave it pointing at a keyframe
|
|
650
|
+
* a drag had just replaced, which is a stale subject with no symptom until
|
|
651
|
+
* Delete removes the wrong thing.
|
|
652
|
+
*
|
|
653
|
+
* The container is carried for `selectedMove`'s reason, one level up: a
|
|
654
|
+
* definition's interior tracks live under a `cd_…` id and a shot's under a
|
|
655
|
+
* `sc_…` one, and `state.sceneId` follows the playhead, so a diamond clicked
|
|
656
|
+
* in another shot would have sent Delete somewhere it does not exist.
|
|
657
|
+
*
|
|
658
|
+
* `layerId: null` means the SHOT ITSELF is the subject — its background and
|
|
659
|
+
* its post stack (P5). Null rather than a second field, because every verb
|
|
660
|
+
* that acts on a keyframe already carries the whole address and a parallel
|
|
661
|
+
* `sceneKeyframe` would double each of them.
|
|
662
|
+
*
|
|
663
|
+
* ── A LIST, WITH `[0]` AS THE PRIMARY ─────────────────────────────────
|
|
664
|
+
*
|
|
665
|
+
* Box-select in the graph, dragging four keys at once, applying one ease to
|
|
666
|
+
* a run and copy/paste are all the same missing capability, and this single
|
|
667
|
+
* field was the whole of what was missing: `CurveGraph` and `TimelineModel`
|
|
668
|
+
* were already written for a set (`selectedAddrs`, `selectKeyframes(refs,
|
|
669
|
+
* mode)`, `move.refs[]`) and only this store answered with one.
|
|
670
|
+
*
|
|
671
|
+
* ORDERED, and `[0]` is the PRIMARY — what the inspector describes, what the
|
|
672
|
+
* curve pane focuses, what a "which track" question resolves against. The
|
|
673
|
+
* most recently added entry becomes primary, which is what makes shift-click
|
|
674
|
+
* feel like extending a selection rather than shuffling it.
|
|
675
|
+
*
|
|
676
|
+
* Still POSITIONS, for the reason above: ids stay a gesture-scope concept,
|
|
677
|
+
* resolved against a drag's own base.
|
|
678
|
+
*/
|
|
679
|
+
selectedKeyframes: readonly KeyframeSel[];
|
|
680
|
+
/**
|
|
681
|
+
* ROWS or GRAPH: which face the dock's track area shows.
|
|
682
|
+
*
|
|
683
|
+
* The AE / Rive posture, and a MODE rather than a second panel because the two
|
|
684
|
+
* answer the same question at different resolutions — "when does this happen"
|
|
685
|
+
* and "how does it move" — and a graph beside the rows would take the height
|
|
686
|
+
* the rows need to be readable at all.
|
|
687
|
+
*
|
|
688
|
+
* In the store rather than in the dock because the keymap toggles it and the
|
|
689
|
+
* dock's own `open`/`openShots` sets do not have that problem.
|
|
78
690
|
*/
|
|
79
|
-
|
|
691
|
+
dockMode: "rows" | "graph" | "sound";
|
|
80
692
|
/**
|
|
81
693
|
* The marker the strip has selected, or null.
|
|
82
694
|
*
|
|
83
|
-
* A BARE ID, unlike `
|
|
695
|
+
* A BARE ID, unlike `selectedMove`: a marker belongs to the document rather
|
|
84
696
|
* than to a scene (`MarkerRef.frame` is a master frame), so there is nothing
|
|
85
697
|
* to address it relative to and nothing that can go stale under a retime.
|
|
86
698
|
*
|
|
@@ -89,6 +701,25 @@ export interface ShotEditorState {
|
|
|
89
701
|
* Delete" can never remove a marker the author stopped looking at.
|
|
90
702
|
*/
|
|
91
703
|
selectedMarker: string | null;
|
|
704
|
+
/**
|
|
705
|
+
* The audio clips the Sound dock has selected (gap 8): clip ids in either
|
|
706
|
+
* home. The next subject after the marker for Delete, ⌘D, ⌘B and Q / W.
|
|
707
|
+
*
|
|
708
|
+
* It KEEPS the layer selection, because the inspector keeps showing the
|
|
709
|
+
* layer and a clip has no inspector of its own. Any other focus (a camera move, a
|
|
710
|
+
* key, a marker, a layer pick) clears it.
|
|
711
|
+
*/
|
|
712
|
+
selectedAudio: readonly string[];
|
|
713
|
+
/**
|
|
714
|
+
* Per-tab SOLO: the track ids this tab hears alone. Monitoring, never the
|
|
715
|
+
* document: not saved, not synced, never in an export. Empty is the full mix.
|
|
716
|
+
*/
|
|
717
|
+
soloTracks: readonly string[];
|
|
718
|
+
/** Snap drags to the beat grid when the document has a tempo. Per tab. */
|
|
719
|
+
snapBeats: boolean;
|
|
720
|
+
/** A note the dock shows in its head, such as "3 clips split". Not an
|
|
721
|
+
* error; the dock drops it after a moment. */
|
|
722
|
+
dockNotice: { text: string } | null;
|
|
92
723
|
/**
|
|
93
724
|
* The box a drawn IMAGE layer is waiting to land in, or null.
|
|
94
725
|
*
|
|
@@ -111,13 +742,279 @@ export interface ShotEditorState {
|
|
|
111
742
|
* inside the iframe is a different document's event and never reaches the
|
|
112
743
|
* chrome window's outside-click listener.
|
|
113
744
|
*/
|
|
114
|
-
stageMenu: {
|
|
745
|
+
stageMenu: {
|
|
746
|
+
at: { x: number; y: number };
|
|
747
|
+
hits: readonly string[];
|
|
748
|
+
pane: StagePane;
|
|
749
|
+
/** Set when the press landed on a motion path dot (gap 3): the menu is
|
|
750
|
+
* that key's, not the layer's. */
|
|
751
|
+
pathKey?: { layerId: string; keyId: string };
|
|
752
|
+
} | null;
|
|
753
|
+
/**
|
|
754
|
+
* ⌘K asked for the app menu. A MAILBOX, like `stageMenu` above, and for the
|
|
755
|
+
* same reason and with the same identity rule: fresh object per press, so a
|
|
756
|
+
* second ⌘K re-opens rather than being swallowed by value equality.
|
|
757
|
+
*
|
|
758
|
+
* It carries NOTHING — the menu opens under its own trigger, whose rect only
|
|
759
|
+
* the header knows. The object exists so that an effect has an identity to key
|
|
760
|
+
* on; a boolean would need a second verb to put it back.
|
|
761
|
+
*
|
|
762
|
+
* Never persisted, never a document edit, and never cleared by the store: the
|
|
763
|
+
* menu owns its own dismissal (Escape, an outside press, a row firing), none
|
|
764
|
+
* of which the store can see.
|
|
765
|
+
*/
|
|
766
|
+
appMenu: Record<string, never> | null;
|
|
767
|
+
/** "Focus the column's search", asked by `/`. A MAILBOX on `appMenu`'s
|
|
768
|
+
* model: the store cannot reach the input, so it parks a fresh identity and
|
|
769
|
+
* the panel's effect moves the caret. Never cleared, never persisted. */
|
|
770
|
+
searchFocus: Record<string, never> | null;
|
|
115
771
|
/**
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
772
|
+
* A request that Code Studio take ONE STEP BACK, the way its own Escape and
|
|
773
|
+
* back chevron do: leaving asks first when a buffer is dirty. A fresh object per press, on
|
|
774
|
+
* `appMenu`'s model, because the key can arrive where the panel cannot hear
|
|
775
|
+
* it (the stage iframe forwards its keys straight to `handleKey`), and the
|
|
776
|
+
* store cannot open the confirm that leaving needs.
|
|
119
777
|
*/
|
|
120
|
-
|
|
778
|
+
codeBackRequest: Record<string, never> | null;
|
|
779
|
+
/** The node editor over one real mesh, or null for the ordinary shot shell. */
|
|
780
|
+
materialWorkspace: MaterialWorkspaceState | null;
|
|
781
|
+
/** Fraction of the viewport occupied by the graph pane. Workspace-only. */
|
|
782
|
+
materialColumnWidth: number;
|
|
783
|
+
/** A code component's live preview width in px, clamped to `CODE_LIVE_WIDTH`. */
|
|
784
|
+
codeLiveWidth: number;
|
|
785
|
+
/**
|
|
786
|
+
* What the graph canvas has selected.
|
|
787
|
+
*
|
|
788
|
+
* Its own field rather than a second use of `selection`, which holds LAYER
|
|
789
|
+
* ids: the two are live at the same time (the workspace keeps the mesh
|
|
790
|
+
* selected on the stage while the author works on its nodes), and one field
|
|
791
|
+
* serving both would make Delete in the graph reach for the mesh.
|
|
792
|
+
*
|
|
793
|
+
* Revalidated on every document write beside `isolation`: undo, an agent
|
|
794
|
+
* write or a delete can take a node away, and a selection pointing at nothing
|
|
795
|
+
* makes the next Delete a silent no-op.
|
|
796
|
+
*/
|
|
797
|
+
materialGraphSelection: MaterialGraphSelection;
|
|
798
|
+
/**
|
|
799
|
+
* WHICH SCOPE the canvas is showing: the trail of function ids the author has
|
|
800
|
+
* entered, innermost last. Empty is the main graph.
|
|
801
|
+
*
|
|
802
|
+
* A TRAIL and not one id, because the crumb bar has to name every level to
|
|
803
|
+
* get back out of a function entered from inside another one. Editor state:
|
|
804
|
+
* the document records which nodes belong to which function (`node.scope`),
|
|
805
|
+
* and this records which of those the author is looking at.
|
|
806
|
+
*/
|
|
807
|
+
materialGraphPath: readonly string[];
|
|
808
|
+
/** The node palette's mailbox, or null. Fresh identity per request — see
|
|
809
|
+
* `MaterialPaletteRequest`. */
|
|
810
|
+
materialPalette: MaterialPaletteRequest | null;
|
|
811
|
+
/** Do graph drags land on the grid? A workspace preference, never document
|
|
812
|
+
* data: node positions are layout and an author's snapping habit is not. */
|
|
813
|
+
materialSnapToGrid: boolean;
|
|
814
|
+
/** "Frame the graph" asked for. A MAILBOX for `appMenu`'s reason: only the
|
|
815
|
+
* canvas knows its own viewport, so the store parks an identity and the
|
|
816
|
+
* canvas's effect does the fit. */
|
|
817
|
+
materialFrameRequest: Record<string, never> | null;
|
|
818
|
+
/** Latest compile status for the graph currently mounted on the stage. */
|
|
819
|
+
materialCompile: MaterialCompileState;
|
|
820
|
+
// ── the storyboard ───────────────────────────────────────────────────────
|
|
821
|
+
/**
|
|
822
|
+
* The fourth mode: the Project's planning board. A FLAG over the shell,
|
|
823
|
+
* not a place that remembers what it replaced: it clears nothing on
|
|
824
|
+
* entry and restores nothing on exit. Mirrored as `?storyboard=1`.
|
|
825
|
+
*/
|
|
826
|
+
storyboardOpen: boolean;
|
|
827
|
+
/**
|
|
828
|
+
* THE SHOT BOARD: the planned shot ("part_…") whose sketches the board
|
|
829
|
+
* shows, or null for the whole board. The workspace stays `storyboard`.
|
|
830
|
+
* Closing the board KEEPS it, so the switch's Storyboard segment comes back
|
|
831
|
+
* to the view the author left; a write that removes the part clears it.
|
|
832
|
+
* Mirrored as `?plan=<partId>`. Session state, never saved.
|
|
833
|
+
*/
|
|
834
|
+
storyboardPlan: string | null;
|
|
835
|
+
/** The sketch the shot board shows ("skt_…"), one of the plan's, or null
|
|
836
|
+
* while the plan has none yet. Mirrored as `?sketch=<id>`. */
|
|
837
|
+
storyboardSketch: string | null;
|
|
838
|
+
/** The items selected on the open sketch ("ski_…"), revalidated on every
|
|
839
|
+
* write like `storyboardSelection`. */
|
|
840
|
+
sketchSelection: readonly string[];
|
|
841
|
+
/** The shot board's armed tool. A view pref, never saved. */
|
|
842
|
+
sketchTool: SketchTool;
|
|
843
|
+
/** Play on the shot board: the sketch live in one stage. A preview, so
|
|
844
|
+
* nothing is saved, and it stops when the shot board closes. */
|
|
845
|
+
sketchPlaying: boolean;
|
|
846
|
+
/** "Guide: Sketch N" in the Studio: the sketch the stage draws as dashed
|
|
847
|
+
* outlines, or null. A view, never saved, and it goes with its sketch. */
|
|
848
|
+
sketchGuide: { partId: string; sketchId: string } | null;
|
|
849
|
+
/**
|
|
850
|
+
* Where leaving a mode (the Components workspace or the material nodes)
|
|
851
|
+
* goes back to. `"storyboard"` when the mode was entered over the board, so
|
|
852
|
+
* a trip from the board comes back to the board; `"stage"` otherwise.
|
|
853
|
+
* Written on the first entry only, so switching subjects inside a mode keeps
|
|
854
|
+
* it. Entering a Shot resets it.
|
|
855
|
+
*/
|
|
856
|
+
returnTarget: "stage" | "storyboard";
|
|
857
|
+
/**
|
|
858
|
+
* The Components workspace is up with NO component open: its empty state,
|
|
859
|
+
* or the top bar's door before a pick. Once a subject opens, `editingDef`
|
|
860
|
+
* or `codeComponent` holds the workspace and this goes false.
|
|
861
|
+
*/
|
|
862
|
+
componentsOpen: boolean;
|
|
863
|
+
/** Which tab of the Components workspace shows. Every edit door lands on
|
|
864
|
+
* `"edit"`; the top bar's door keeps what the author last picked. */
|
|
865
|
+
componentsTab: ComponentsTab;
|
|
866
|
+
/** The component the Components workspace last showed. Written by every
|
|
867
|
+
* door, kept on leave, so the top bar reopens it. Cleared on a document
|
|
868
|
+
* swap: a definition id names one document's component. */
|
|
869
|
+
componentsSelection: ComponentsSelection | null;
|
|
870
|
+
/** Where a Shot door into Components came from (C7), or null. Back to Shot
|
|
871
|
+
* and Esc restore it. Leaving Components any other way clears it. */
|
|
872
|
+
componentsOrigin: ComponentsOrigin | null;
|
|
873
|
+
/** The preview's ground and loop (bridge v45), pushed with the component
|
|
874
|
+
* context. */
|
|
875
|
+
componentPreview: ComponentPreviewLook;
|
|
876
|
+
/** The workspace component store's rows (`GET /api/workspace-components`),
|
|
877
|
+
* or null before the first read. The `cpt_` id of a component of this
|
|
878
|
+
* Project is looked up here by its home. */
|
|
879
|
+
workspaceComponents: WorkspaceComponent[] | null;
|
|
880
|
+
/** Which Project each code file belongs to, from the same read (`homes`):
|
|
881
|
+
* the module paths every live Project lists as made for it or uses. Null
|
|
882
|
+
* before the first read, and Your library's "made for no Project" waits on
|
|
883
|
+
* it. Kept across a Project switch, like the rows. */
|
|
884
|
+
componentHomes: ComponentFileHome[] | null;
|
|
885
|
+
/** The font apply in flight for the Font field (`applyFont` and the verbs
|
|
886
|
+
* that share its lane): its download and upload progress, and its failure
|
|
887
|
+
* with Retry (`retryFontApply`). Null when none runs or the last one
|
|
888
|
+
* landed; cleared by a newer apply and by a Project switch. */
|
|
889
|
+
fontApply: FontApplyProgress | null;
|
|
890
|
+
/** The workspace font list (`GET /api/fonts`, `loadWorkspaceFonts`), or
|
|
891
|
+
* null before the first read that succeeded. */
|
|
892
|
+
workspaceFonts: WorkspaceFontList | null;
|
|
893
|
+
/** The Google catalog once loaded (`loadFontCatalog`), for the font
|
|
894
|
+
* helpers that take it (`state/fonts.ts`); null before. */
|
|
895
|
+
fontCatalog: readonly GoogleFontEntry[] | null;
|
|
896
|
+
/** The versions read per workspace component, newest first. */
|
|
897
|
+
componentVersions: Readonly<Record<string, WorkspaceComponentVersionInfo[]>>;
|
|
898
|
+
/** The last build check per code component (registry id). A failing one
|
|
899
|
+
* keeps its last good build on screen and refuses an export. */
|
|
900
|
+
componentBuilds: Readonly<Record<string, ComponentBuild>>;
|
|
901
|
+
/** The Share dialog for the component Components shows (lane P draws it). */
|
|
902
|
+
shareComponentOpen: boolean;
|
|
903
|
+
/** The History view of the component Components shows, or null when it is
|
|
904
|
+
* closed. `viewing` is the version read for the read-only view. */
|
|
905
|
+
componentHistory: { viewing: WorkspaceComponentVersion | null } | null;
|
|
906
|
+
/** Every use (C4): the centre lists the uses of `subject` in place of the
|
|
907
|
+
* preview, and the right panel shows THIS USE for `use` (a key from
|
|
908
|
+
* `cardUseKey`/`layerUseKey` in workspace.ts). It names its subject, so a
|
|
909
|
+
* switch to another component shows that one's preview again without a
|
|
910
|
+
* reset at every door (`usesViewOf`). Null when closed. */
|
|
911
|
+
componentUses: { subject: ComponentsSelection; use: string | null } | null;
|
|
912
|
+
/** The latest version of each Community listing this document's copies
|
|
913
|
+
* came from, by listing id. Read by `refreshListingUpdates`. */
|
|
914
|
+
componentListingUpdates: Readonly<Record<string, ComponentListingUpdate>>;
|
|
915
|
+
/** An Update held for a yes: applying it drops props this document's uses
|
|
916
|
+
* changed, so the author sees them first. Null when none is pending. */
|
|
917
|
+
componentUpdate: ComponentUpdatePending | null;
|
|
918
|
+
/** The Community update just written, until Done, Revert update or a doc
|
|
919
|
+
* swap. Null when none. */
|
|
920
|
+
componentUpdated: ComponentUpdateApplied | null;
|
|
921
|
+
/**
|
|
922
|
+
* The library browser: the whole shipped catalog, full screen, where an
|
|
923
|
+
* author picks what this Project wants.
|
|
924
|
+
*
|
|
925
|
+
* A DIALOG AND NOT A FIFTH MODE, which is the one decision in this field
|
|
926
|
+
* worth writing down. `Dialog` portals to `document.body` at `--z-dialog`
|
|
927
|
+
* 1100, above the whole shell (whose top tier is the overlay, 10), and it
|
|
928
|
+
* brings its own focus trap and its own Escape. So this flag costs no
|
|
929
|
+
* workspace in `workspaceOf`, no shell class, no cell slots, no keymap lane
|
|
930
|
+
* and no `edgesOf` key.
|
|
931
|
+
*
|
|
932
|
+
* It also has to be a dialog to be usable. A fifth mode would join the
|
|
933
|
+
* modes' one-at-a-time rule (`workspaceOf`: opening one closes the
|
|
934
|
+
* others), so it could not share the screen with the board, which is
|
|
935
|
+
* exactly where an author chooses what to build with. This opens over
|
|
936
|
+
* anything, including the board.
|
|
937
|
+
*/
|
|
938
|
+
libraryBrowserOpen: boolean;
|
|
939
|
+
/**
|
|
940
|
+
* The Insert drawer over the left column: closed (null), or open and asked
|
|
941
|
+
* to show `section`. A FRESH OBJECT per open, so asking again for the
|
|
942
|
+
* section already open scrolls back to it. Closed by any change of
|
|
943
|
+
* workspace (opening a mode), and never open for a viewer.
|
|
944
|
+
*/
|
|
945
|
+
insertDrawer: { section: InsertSection } | null;
|
|
946
|
+
/** The author has met the drawer (closed it, or added anything). Remembered
|
|
947
|
+
* in `editorPrefs`; while false, an empty Project opens the drawer once. */
|
|
948
|
+
insertDrawerSeen: boolean;
|
|
949
|
+
/** A layer the drawer placed WITHOUT selecting it ("Use as background"):
|
|
950
|
+
* its outline row flashes and says where it went. The next selection
|
|
951
|
+
* clears it. */
|
|
952
|
+
justPlaced: string | null;
|
|
953
|
+
/**
|
|
954
|
+
* The planned shot currently being renamed, or null. Its name field shows in
|
|
955
|
+
* the Plan panel's head and on the shot board's bar (`PartName`).
|
|
956
|
+
*
|
|
957
|
+
* ONE SPELLING FOR EVERY SURFACE, so a rename started with F2 and one started
|
|
958
|
+
* from a double-click on the name are the same rename.
|
|
959
|
+
*/
|
|
960
|
+
storyboardRenaming: string | null;
|
|
961
|
+
/**
|
|
962
|
+
* What the board has selected: reference ids ("ref_…"), still ids
|
|
963
|
+
* ("stl_…"), part ids ("part_…"), and the derived gap and cut ids. Its own field
|
|
964
|
+
* for `materialGraphSelection`'s reason, and revalidated on every document
|
|
965
|
+
* write beside it: a delete, an undo or an agent write can take a card away.
|
|
966
|
+
*/
|
|
967
|
+
storyboardSelection: readonly string[];
|
|
968
|
+
/**
|
|
969
|
+
* The `animateMode` the board parked on open, or null while it is closed.
|
|
970
|
+
* The board is planning, so an armed key-on-change bit would turn every
|
|
971
|
+
* placement into keys. Close restores it, as Material does.
|
|
972
|
+
*/
|
|
973
|
+
storyboardRestoreAnimate: boolean | null;
|
|
974
|
+
/**
|
|
975
|
+
* Whether a reference's "More" fold (every control of a component card) is
|
|
976
|
+
* open. A component card's "All controls" sets it, so the fold the
|
|
977
|
+
* inspector shows is the one the card asked for. A view pref.
|
|
978
|
+
*/
|
|
979
|
+
referenceMoreOpen: boolean;
|
|
980
|
+
/**
|
|
981
|
+
* Whether the board's Project assets panel is open in the right column
|
|
982
|
+
* (`boardRightPanel` "assets"), from the board's + menu. Where "Keep in
|
|
983
|
+
* Assets" lands and a file is uploaded, removed or dragged onto a card.
|
|
984
|
+
* False whenever the board is closed. Session state, never saved.
|
|
985
|
+
*/
|
|
986
|
+
boardAssetsOpen: boolean;
|
|
987
|
+
/**
|
|
988
|
+
* The category the library browser opens on, or null to keep its own. A
|
|
989
|
+
* mailbox: `setLibraryBrowserOpen(true, category)` parks it and the browser
|
|
990
|
+
* applies it once on open.
|
|
991
|
+
*/
|
|
992
|
+
libraryBrowserCategory: "all" | LibraryItemKind | null;
|
|
993
|
+
/**
|
|
994
|
+
* The planned shots "Build planned shots" handed to the agent (S1). A card
|
|
995
|
+
* reads Building while its part is here and it is not marked built. All
|
|
996
|
+
* leave when the agent run that followed ends. Session state, never saved;
|
|
997
|
+
* a document switch drops it.
|
|
998
|
+
*/
|
|
999
|
+
boardBuilding: { part: string }[];
|
|
1000
|
+
/** True once an agent run started after the last "Build planned shots",
|
|
1001
|
+
* so the run's end clears `boardBuilding`. */
|
|
1002
|
+
boardBuildingRan: boolean;
|
|
1003
|
+
/** Files dropped or pasted on the board, waiting for the drop dialog's
|
|
1004
|
+
* scope (S2). Null while the dialog is closed. */
|
|
1005
|
+
boardDrop: { files: File[] } | null;
|
|
1006
|
+
/**
|
|
1007
|
+
* The last "Add motion" request: the planned shot and the Shot it asked the
|
|
1008
|
+
* agent to change. The review card warns when a proposal redraws any other
|
|
1009
|
+
* Shot, and copies this part's prompt. Transient: a document switch drops it.
|
|
1010
|
+
*/
|
|
1011
|
+
motionRequest: { partId: string; sceneId: string | null } | null;
|
|
1012
|
+
/** Text the agent composer adopts on its next render. A fresh identity per
|
|
1013
|
+
* ask, consumed once by the mounted composer. */
|
|
1014
|
+
agentPrefill: { text: string } | null;
|
|
1015
|
+
/** Layers the timeline dock shows on its next render. A fresh identity per
|
|
1016
|
+
* ask, consumed once by the mounted dock, `agentPrefill`'s shape. */
|
|
1017
|
+
dockReveal: DockReveal | null;
|
|
121
1018
|
/**
|
|
122
1019
|
* Triangle counts by mesh layer id, pushed up from the stage as models load.
|
|
123
1020
|
*
|
|
@@ -127,6 +1024,16 @@ export interface ShotEditorState {
|
|
|
127
1024
|
* layer whose model has not arrived yet reports nothing at all.
|
|
128
1025
|
*/
|
|
129
1026
|
meshTriangles: Readonly<Record<string, number>>;
|
|
1027
|
+
/**
|
|
1028
|
+
* The glTF materials each loaded model ships, pushed up from the stage as
|
|
1029
|
+
* models parse.
|
|
1030
|
+
*
|
|
1031
|
+
* The same push-not-poll argument the triangle count makes: a material index,
|
|
1032
|
+
* name and map set live in the FILE, so no amount of reading the document
|
|
1033
|
+
* tells the slot rail that slot 3 is called "Screen". Absent for a layer
|
|
1034
|
+
* whose model has not loaded, and for every primitive.
|
|
1035
|
+
*/
|
|
1036
|
+
meshMaterialSlots: Readonly<Record<string, readonly MeshMaterialSlotInfo[]>>;
|
|
130
1037
|
/**
|
|
131
1038
|
* The subtree being edited in isolation, or null for the whole scene.
|
|
132
1039
|
*
|
|
@@ -161,6 +1068,26 @@ export interface ShotEditorState {
|
|
|
161
1068
|
/** The shading the working pane was in before the current one, so ⇧Z can
|
|
162
1069
|
* toggle wireframe and come BACK rather than stranding the author in it. */
|
|
163
1070
|
previousShading: ShadingMode;
|
|
1071
|
+
/**
|
|
1072
|
+
* The shading to put back on leaving the open definition, or null if there is
|
|
1073
|
+
* nothing to put back.
|
|
1074
|
+
*
|
|
1075
|
+
* ── WHY A DEFINITION OPENS IN MATERIAL ──────────────────────────────────
|
|
1076
|
+
*
|
|
1077
|
+
* Clay is a MODELLING aid: it strips colour and texture so silhouette and
|
|
1078
|
+
* proportion read. A component definition is made of exactly what clay
|
|
1079
|
+
* strips — painted layers, stacked, drawn with `depthTest` off — so the
|
|
1080
|
+
* default solid shading rendered an opened definition as ONE featureless grey
|
|
1081
|
+
* rectangle. It looked like the editor had hung.
|
|
1082
|
+
*
|
|
1083
|
+
* The same argument the material picker already makes on close: a look you
|
|
1084
|
+
* cannot see is a choice you cannot make.
|
|
1085
|
+
*
|
|
1086
|
+
* SEPARATE FROM `previousShading`, which belongs to ⇧Z's wireframe toggle.
|
|
1087
|
+
* One field serving both would make leaving a definition and pressing ⇧Z
|
|
1088
|
+
* fight over the same slot.
|
|
1089
|
+
*/
|
|
1090
|
+
shadingBeforeDef: ShadingMode | null;
|
|
164
1091
|
/**
|
|
165
1092
|
* The component definition the OUTLINE is scoped to, or null for the scene.
|
|
166
1093
|
*
|
|
@@ -175,6 +1102,136 @@ export interface ShotEditorState {
|
|
|
175
1102
|
* every write beside `isolation` — a definition can be deleted too.
|
|
176
1103
|
*/
|
|
177
1104
|
editingDef: string | null;
|
|
1105
|
+
/**
|
|
1106
|
+
* The DEF-LOCAL playhead while a definition is open (unify P2). A SECOND
|
|
1107
|
+
* field beside `frame` on purpose: `frame` is master time and every
|
|
1108
|
+
* playhead-paired verb (addShotAtPlayhead, Animate's keys, audio preview, the
|
|
1109
|
+
* scene render) reads it — repurposing it def-locally would silently corrupt
|
|
1110
|
+
* all of them. While `editingDef` is null this is inert. Clamped against
|
|
1111
|
+
* `defDurationFrames` at the same seam `editingDef` is validated.
|
|
1112
|
+
*/
|
|
1113
|
+
defFrame: number;
|
|
1114
|
+
/**
|
|
1115
|
+
* The chrome's zoom over the Studio's head-on render. 1 = the whole canvas
|
|
1116
|
+
* fitted, 2 = twice as large.
|
|
1117
|
+
*
|
|
1118
|
+
* EDITOR STATE, pushed to the stage with the component context: the stage
|
|
1119
|
+
* frames the definition from `def.size`, so the zoom is a scale on that
|
|
1120
|
+
* frame's half-extents rather than anything the document records.
|
|
1121
|
+
*
|
|
1122
|
+
* NOT a workspace pref, and deliberately not: it is scoped to ONE definition's
|
|
1123
|
+
* canvas, so carrying it to the next definition would open a component the
|
|
1124
|
+
* author has never seen at a magnification they chose for another one. It
|
|
1125
|
+
* resets to 1 on every entry, with the rest of the cleared-on-leave set.
|
|
1126
|
+
*/
|
|
1127
|
+
studioZoom: number;
|
|
1128
|
+
/**
|
|
1129
|
+
* THE OTHER COMPONENT FAMILY'S OPEN SUBJECT: a registry id
|
|
1130
|
+
* ("<module>#<Export>") whose source the code studio is showing, or null.
|
|
1131
|
+
*
|
|
1132
|
+
* A SECOND FIELD BESIDE `editingDef` rather than a widened one, for the
|
|
1133
|
+
* reason `defFrame` is a second field beside `frame`: the two subjects are
|
|
1134
|
+
* different KINDS. A definition is a document object with an interior, an
|
|
1135
|
+
* exposed surface and named actions, and every one of those is a panel that
|
|
1136
|
+
* reads `editingDef`. A code component is a .tsx file the author owns; it has
|
|
1137
|
+
* a source buffer and a contract, and nothing else in this chrome. One field
|
|
1138
|
+
* answering both would have every reader ask "which sort is it" first.
|
|
1139
|
+
*
|
|
1140
|
+
* MUTUALLY EXCLUSIVE with `editingDef`: both mean "the stage is rendering one
|
|
1141
|
+
* component standalone", and the stage holds one context. Each entry clears
|
|
1142
|
+
* the other, and `pushComponentContext` is where the two meet.
|
|
1143
|
+
*/
|
|
1144
|
+
codeComponent: string | null;
|
|
1145
|
+
/**
|
|
1146
|
+
* The defaults rail's LIVE overrides, contract key -> engine Value.
|
|
1147
|
+
*
|
|
1148
|
+
* A scrub writes here and pushes the component context; nothing lands in any
|
|
1149
|
+
* document. A committed key is HELD here until the fresh contract arrives
|
|
1150
|
+
* (`loadContract` clears it), so the preview never flashes the old default
|
|
1151
|
+
* between the write landing in source and the re-extraction being adopted.
|
|
1152
|
+
*/
|
|
1153
|
+
codePreviewProps: Readonly<Record<string, Value>>;
|
|
1154
|
+
/**
|
|
1155
|
+
* FOLLOW MODE: the project file Code Studio is following an MCP agent in,
|
|
1156
|
+
* or null. While the agent keeps writing it, its buffer is read-only and
|
|
1157
|
+
* reloads on each write (`adoptSourceChange`), and the preview takes the
|
|
1158
|
+
* props and frame of each `preview_component` the agent asks for. "Take
|
|
1159
|
+
* over" (`takeOverCodeFile`) ends it.
|
|
1160
|
+
*/
|
|
1161
|
+
codeFollow: string | null;
|
|
1162
|
+
/** A file the author asked to follow that the registry does not list yet:
|
|
1163
|
+
* it opens in follow mode as soon as it does (`refreshLibrary`). */
|
|
1164
|
+
codeFollowPending: string | null;
|
|
1165
|
+
/**
|
|
1166
|
+
* The Components props panel's TRIED values, prop key -> engine Value, for
|
|
1167
|
+
* the open subject (a code component's contract keys, or a definition's
|
|
1168
|
+
* exposed keys).
|
|
1169
|
+
*
|
|
1170
|
+
* While `componentApplyAll` is off, every props-panel edit lands here and
|
|
1171
|
+
* nowhere else: the preview draws it (pushed on the component context) and
|
|
1172
|
+
* no document, file or instance changes. Turning `componentApplyAll` on
|
|
1173
|
+
* saves these to the component and clears them. Dropped whenever the
|
|
1174
|
+
* subject changes (the `set` seam), so a tried value never follows the
|
|
1175
|
+
* author to another component.
|
|
1176
|
+
*/
|
|
1177
|
+
componentPlay: Readonly<Record<string, Value>>;
|
|
1178
|
+
/**
|
|
1179
|
+
* The props panel's "Apply to all instances" checkbox. Off (the default) =
|
|
1180
|
+
* try values in the preview only (`componentPlay`). On = every edit saves to
|
|
1181
|
+
* the component itself, which every instance without its own value follows.
|
|
1182
|
+
* Resets to off with the subject.
|
|
1183
|
+
*/
|
|
1184
|
+
componentApplyAll: boolean;
|
|
1185
|
+
/** The key under an active scrub gesture, or null. Its override survives a
|
|
1186
|
+
* contract landing mid-drag (another key's commit must not yank this one). */
|
|
1187
|
+
codeScrubKey: string | null;
|
|
1188
|
+
/** Keys whose defaults commit is IN FLIGHT. The held-override release in
|
|
1189
|
+
* `loadContract` keeps these too: a contract landing for key A's commit
|
|
1190
|
+
* must not yank key B's still-travelling one — that would flash B's old
|
|
1191
|
+
* default on stage for the exact window the hold exists to cover. */
|
|
1192
|
+
codePendingDefaults: readonly string[];
|
|
1193
|
+
/** Each default the author changed while this component is open, as it was
|
|
1194
|
+
* before the first change: what the props head's Reset puts back. Kept by
|
|
1195
|
+
* the store, so it survives the panel remounting, and dropped with the
|
|
1196
|
+
* defaults history (another component, or leaving Code Studio). */
|
|
1197
|
+
codeDefaultsBaseline: Readonly<Record<string, Value>>;
|
|
1198
|
+
/** The last defaults-commit refusal or failure, shown at the top of the
|
|
1199
|
+
* rail. Cleared by the next gesture. */
|
|
1200
|
+
codeDefaultsError: string | null;
|
|
1201
|
+
/** Per-component defaults metadata (target file, writability, per-key
|
|
1202
|
+
* editability), cached beside `contracts` and evicted at the same seams. */
|
|
1203
|
+
contractDefaults: Readonly<Record<string, ComponentDefaultsMeta>>;
|
|
1204
|
+
/**
|
|
1205
|
+
* Every open buffer, keyed by project-relative posix path.
|
|
1206
|
+
*
|
|
1207
|
+
* A MAP AND NOT ONE FILE, because the subject and the buffer stopped being
|
|
1208
|
+
* the same question. A component is one export in one module, but the work of
|
|
1209
|
+
* changing it reaches the helper beside it, the types it imports and the
|
|
1210
|
+
* config that includes it — and an editor that can only hold the file it
|
|
1211
|
+
* previewed makes the author leave to read any of them.
|
|
1212
|
+
*
|
|
1213
|
+
* Keyed by rel rather than an array of tabs: rel IS the identity (`/api/files`
|
|
1214
|
+
* takes one, the diagnostics route takes one, `CodeEditor`'s state cache keys
|
|
1215
|
+
* on one), and a list would need a lookup on every one of those.
|
|
1216
|
+
*/
|
|
1217
|
+
codeFiles: Readonly<Record<string, CodeFileState>>;
|
|
1218
|
+
/** Which buffer the editor is showing, or null when none is open. Always a
|
|
1219
|
+
* key of `codeFiles` or null — nothing may point at a buffer that is not
|
|
1220
|
+
* there, and `closeCodeFile` picks the neighbour rather than leaving this
|
|
1221
|
+
* dangling. */
|
|
1222
|
+
codeActive: string | null;
|
|
1223
|
+
/** Tab order, oldest first. SEPARATE from `codeFiles` because an object's key
|
|
1224
|
+
* order is a language detail and a tab strip's order is a promise to the
|
|
1225
|
+
* author: tabs must not reshuffle when a buffer is replaced. */
|
|
1226
|
+
codeOrder: readonly string[];
|
|
1227
|
+
/** Every editable file in the project, flat, as `GET /api/files` answers.
|
|
1228
|
+
* The tree's shape is drawn from these paths, never stored. */
|
|
1229
|
+
projectFiles: readonly ProjectFile[];
|
|
1230
|
+
projectFilesLoaded: boolean;
|
|
1231
|
+
/** The server's walk hit its cap, so this listing is INCOMPLETE. Said in the
|
|
1232
|
+
* tree, because a file browser that quietly omits files is worse than none. */
|
|
1233
|
+
projectFilesTruncated: boolean;
|
|
1234
|
+
projectFilesError: string | null;
|
|
178
1235
|
/** The export dialog is open. A store bit rather than local state, because
|
|
179
1236
|
* the keymap opens it (⌘E) and the header's button toggles it, and two
|
|
180
1237
|
* places holding "is it open" is one place for them to disagree. */
|
|
@@ -187,11 +1244,46 @@ export interface ShotEditorState {
|
|
|
187
1244
|
* away from the modal without cancelling anything.
|
|
188
1245
|
*/
|
|
189
1246
|
renderJob: RenderJob | null;
|
|
1247
|
+
/**
|
|
1248
|
+
* A render of THIS document is running, either way it was started.
|
|
1249
|
+
*
|
|
1250
|
+
* ONE lock both export paths write, because both mean the same thing to the
|
|
1251
|
+
* chrome: the transport is held, Space and the Play menu refuse, and the
|
|
1252
|
+
* progress on screen comes from here. It is NOT the stage's own
|
|
1253
|
+
* `exporting` (bridgeClient) — that one is the Tier-1 deferral flag and says
|
|
1254
|
+
* nothing about a server render.
|
|
1255
|
+
*
|
|
1256
|
+
* `where` is what the chrome does about it. A BROWSER export renders through
|
|
1257
|
+
* the visible stage by design, so the stage is veiled and says so; a SERVER
|
|
1258
|
+
* render leaves the stage alone and only pauses playback, because the machine
|
|
1259
|
+
* is busy but the canvas is still the author's.
|
|
1260
|
+
*/
|
|
1261
|
+
exportLock: {
|
|
1262
|
+
where: "browser" | "server";
|
|
1263
|
+
done: number;
|
|
1264
|
+
total: number;
|
|
1265
|
+
/** What a server render is doing before its first frame (F98), such as
|
|
1266
|
+
* the one-time browser download. */
|
|
1267
|
+
note?: string;
|
|
1268
|
+
} | null;
|
|
190
1269
|
// ── the agent drawer ─────────────────────────────────────────────────────
|
|
191
1270
|
//
|
|
192
1271
|
// Held in the STORE and not in the panel, for the reason the render job is:
|
|
193
1272
|
// closing the drawer must not cancel a running turn or lose its transcript.
|
|
194
1273
|
agentOpen: boolean;
|
|
1274
|
+
/** The right column's Comments tab is the one picked (plan C5). The agent
|
|
1275
|
+
* wins the column while it is open; the tab comes back when it closes. */
|
|
1276
|
+
commentsOpen: boolean;
|
|
1277
|
+
/** The Studio's Brief tab is the one picked (S4): the planned shot the
|
|
1278
|
+
* Shot under the playhead was built from. Off the board only; the agent and
|
|
1279
|
+
* Comments win the column while open, as over the inspector. */
|
|
1280
|
+
briefOpen: boolean;
|
|
1281
|
+
/** The comment tool (C): off the board, a click on the camera pane drafts
|
|
1282
|
+
* a Studio thread (bridge v45, S5). Null when not armed. */
|
|
1283
|
+
boardTool: "comment" | null;
|
|
1284
|
+
/** The open Project takes comments from this caller
|
|
1285
|
+
* (`LoadedShot.can.comment`). A missing `can` (an older server) is true. */
|
|
1286
|
+
canComment: boolean;
|
|
195
1287
|
agentItems: readonly AgentTranscriptItem[];
|
|
196
1288
|
agentRunning: boolean;
|
|
197
1289
|
agentError: string | null;
|
|
@@ -213,18 +1305,97 @@ export interface ShotEditorState {
|
|
|
213
1305
|
railCollapsed: boolean;
|
|
214
1306
|
inspectorCollapsed: boolean;
|
|
215
1307
|
/**
|
|
216
|
-
* What
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
*
|
|
1308
|
+
* What the focus mode auto-collapsed, per panel: the collapse value to
|
|
1309
|
+
* restore on leaving it, or null when there is nothing pending. Either the
|
|
1310
|
+
* mode is off, or the author toggled that panel themselves inside it and
|
|
1311
|
+
* took ownership (the collapse verbs null the slot).
|
|
1312
|
+
*
|
|
1313
|
+
* The DOCK has no slot, and that is the current rule rather than an
|
|
1314
|
+
* oversight: no view collapses the timeline any more. See `focusHides`.
|
|
220
1315
|
*/
|
|
221
1316
|
focusRestoreInspector: boolean | null;
|
|
222
|
-
|
|
1317
|
+
focusRestoreRail: boolean | null;
|
|
1318
|
+
/**
|
|
1319
|
+
* Whether the focus mode is engaged, or null for off.
|
|
1320
|
+
*
|
|
1321
|
+
* There is exactly ONE now, and it belongs to one place: Video at the SCENE
|
|
1322
|
+
* level, where the subject is the finished cut rather than anything to edit,
|
|
1323
|
+
* so both side rails go and the picture gets the full width. Inside a shot
|
|
1324
|
+
* NEITHER view hides a panel: the author is building there and the panels
|
|
1325
|
+
* are what they build with. See `applyFocus` in store.ts.
|
|
1326
|
+
*
|
|
1327
|
+
* ── WHY THIS IS STORED AND NOT DERIVED ──────────────────────────────────
|
|
1328
|
+
*
|
|
1329
|
+
* It is the "before" of every transition, and the input it would be derived
|
|
1330
|
+
* from cannot supply one: `video` is the same flag everywhere and survives
|
|
1331
|
+
* a trip into a shot, so it means "rails away" at the scene level and
|
|
1332
|
+
* "nothing engaged" one level down, and a CONTEXT change with the flag
|
|
1333
|
+
* unchanged is a real transition in both directions. The restore slots cannot supply it either:
|
|
1334
|
+
* they are null both when the mode was never entered and when the author
|
|
1335
|
+
* took ownership inside it, so a mode with its slots surrendered would look
|
|
1336
|
+
* unengaged and re-collapse the panels the author had just re-opened, on
|
|
1337
|
+
* the next edit, because `syncContextStage` runs on every one of them.
|
|
1338
|
+
*
|
|
1339
|
+
* Stored, the answer is a comparison: same key, nothing happens.
|
|
1340
|
+
*/
|
|
1341
|
+
focusHides: "video" | null;
|
|
1342
|
+
/**
|
|
1343
|
+
* THE AUTHOR IS WATCHING, not working.
|
|
1344
|
+
*
|
|
1345
|
+
* Video is the export's picture with the authoring taken off it: no handles,
|
|
1346
|
+
* no hover, no drawing, no drags. The transport runs in Edit and Video alike.
|
|
1347
|
+
* It is a FLAG OVER THE PICTURE and never a pane layout, which is the whole
|
|
1348
|
+
* correction this field carries. It was `sceneVideo` and applied at the scene
|
|
1349
|
+
* level alone, because a shot's Video was spelled as camera mode
|
|
1350
|
+
* (`stageLayout.mode`) instead. That could not survive contact with the
|
|
1351
|
+
* facts: the rect, ellipse, line, text, image and pen tools draw in the
|
|
1352
|
+
* CAMERA pane and nowhere else, so "Video is the camera pane" made drawing
|
|
1353
|
+
* impossible in the view that could not play and possible in the one that
|
|
1354
|
+
* could.
|
|
1355
|
+
*
|
|
1356
|
+
* So `stageLayout.mode` is EDIT'S LAYOUT now (the 3D viewport, the split, the
|
|
1357
|
+
* frame, on keys ⇧1 / ⇧2 / ⇧3) and this is orthogonal to it. Entering Video
|
|
1358
|
+
* never writes the layout; leaving shows it again. Above a shot it ALSO
|
|
1359
|
+
* puts both side rails away, which is the one focus mode left in the product
|
|
1360
|
+
* (`focusKeyFor`).
|
|
1361
|
+
*
|
|
1362
|
+
* THE WAY OUT IS ANY EDIT. Every selection verb, the document seam, both
|
|
1363
|
+
* scrub lanes, the tool arm and the four "go somewhere to work" doors call
|
|
1364
|
+
* `enterEdit()`, so the first touch of anything lands the author in Edit,
|
|
1365
|
+
* paused, on the frame they were looking at. See the seam list in store.ts.
|
|
1366
|
+
*
|
|
1367
|
+
* A WORKSPACE VALUE: it survives a document switch, like `stageLayout` and
|
|
1368
|
+
* the slots that ride with it, and it is deliberately NOT persisted to
|
|
1369
|
+
* storage: a reload opens on the work, not on the screening room.
|
|
1370
|
+
*/
|
|
1371
|
+
video: boolean;
|
|
223
1372
|
/** Dragged sizes, in PIXELS — see state/panelSize.ts for why not `vh`. */
|
|
224
1373
|
railWidth: number;
|
|
225
1374
|
dockHeight: number;
|
|
1375
|
+
/** The RIGHT rail's width. It was a CSS literal with no writer at all until
|
|
1376
|
+
* the UX overhaul, which is why the panel had been worked around (labels
|
|
1377
|
+
* shortened to fit) rather than widened. State now, for the reason
|
|
1378
|
+
* `railWidth` is state, and it reaches CSS the same way: `--inspector-w`,
|
|
1379
|
+
* written imperatively from App.tsx. */
|
|
1380
|
+
inspectorWidth: number;
|
|
1381
|
+
/**
|
|
1382
|
+
* How far the chrome reaches in over the stage on each side, measured.
|
|
1383
|
+
*
|
|
1384
|
+
* The stage iframe is pinned `inset: 0` and the panels are docked over it, so
|
|
1385
|
+
* only the shell can know this — it measures the boxes and pushes the four
|
|
1386
|
+
* numbers down the bridge for the overlay's furniture and the derived
|
|
1387
|
+
* framing (see `setStageInset`).
|
|
1388
|
+
*
|
|
1389
|
+
* IT IS STATE AND NOT A CLOSURE because the CHROME needs it too. The split
|
|
1390
|
+
* divider divides the part the author can see, not the window, so the
|
|
1391
|
+
* handle's own arithmetic reads the same four numbers the stage does. A
|
|
1392
|
+
* second measurement in App.tsx would be a second answer to a question with
|
|
1393
|
+
* one true answer, and the two would disagree exactly while a panel moves.
|
|
1394
|
+
*/
|
|
1395
|
+
stageInset: StageInset;
|
|
226
1396
|
/** Pixels per frame in the dock. The unit @dailies-studio/timeline's geometry
|
|
227
|
-
* and `useAnchoredZoom` both speak, so there is no second zoom scale.
|
|
1397
|
+
* and `useAnchoredZoom` both speak, so there is no second zoom scale. Its
|
|
1398
|
+
* bounds and its default are `DOCK_PPF` in state/panelSize.ts. */
|
|
228
1399
|
dockPpf: number;
|
|
229
1400
|
|
|
230
1401
|
/**
|
|
@@ -259,17 +1430,26 @@ export interface ShotEditorState {
|
|
|
259
1430
|
* shortcut point at it, so it follows the POINTER and not the selection. */
|
|
260
1431
|
activePane: StagePane;
|
|
261
1432
|
/**
|
|
262
|
-
*
|
|
263
|
-
* camera
|
|
1433
|
+
* PILOTING: the Camera pane's navigation (middle-drag orbit, shift-drag
|
|
1434
|
+
* pan, wheel zoom, R roll) writes the shot camera through the framing
|
|
1435
|
+
* gesture lane instead of moving the pane's viewport.
|
|
264
1436
|
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
1437
|
+
* It replaced "camera follows this view", which projected every 3D-view
|
|
1438
|
+
* orbit onto a camera that cannot tilt and left the two panes disagreeing
|
|
1439
|
+
* mid-drag. Session-only and deliberately NOT called a lock: nothing is
|
|
1440
|
+
* locked, the camera is being moved by everything you do. It is armed only
|
|
1441
|
+
* by its own control or its key, never as a side effect of entering a mode,
|
|
1442
|
+
* for the reason `animateMode` is: a bit that silently makes drags write the
|
|
1443
|
+
* document must be turned on on purpose.
|
|
1444
|
+
*/
|
|
1445
|
+
pilot: boolean;
|
|
1446
|
+
/**
|
|
1447
|
+
* The component that drives the render camera, by name, or null while the
|
|
1448
|
+
* document's rig does. Reported by the stage (bridge v29 `onCameraDriver`):
|
|
1449
|
+
* the inspector's Camera fields and the camera verbs describe a rig the
|
|
1450
|
+
* picture ignores while this is set, and they say so.
|
|
271
1451
|
*/
|
|
272
|
-
|
|
1452
|
+
cameraDriver: string | null;
|
|
273
1453
|
/**
|
|
274
1454
|
* The WORKING pane's shading (solid / wireframe / material / rendered).
|
|
275
1455
|
*
|
|
@@ -279,8 +1459,124 @@ export interface ShotEditorState {
|
|
|
279
1459
|
* sentence with no footnotes.
|
|
280
1460
|
*/
|
|
281
1461
|
shadingMode: ShadingMode;
|
|
1462
|
+
/** The working pane's grid floor: how the author looks at the scene, never
|
|
1463
|
+
* something about the shot. Remembered across reloads in `editorPrefs`. */
|
|
1464
|
+
showGrid: boolean;
|
|
1465
|
+
/**
|
|
1466
|
+
* The two DIAGNOSTIC chips in the canvas bar, shown or hidden.
|
|
1467
|
+
*
|
|
1468
|
+
* Per-viewer prefs on `showGrid`'s rule: how the author is looking at the
|
|
1469
|
+
* chrome, never anything about the shot, and remembered across reloads in
|
|
1470
|
+
* `editorPrefs`. They exist because both chips are for the person
|
|
1471
|
+
* BUILDING the film and neither belongs in a recording of it.
|
|
1472
|
+
*
|
|
1473
|
+
* ONLY these two. The save-state and codegen chips are not on this switch and
|
|
1474
|
+
* must not join it: they are the chrome telling the author something is wrong
|
|
1475
|
+
* or unsaved, and a warning with an off switch is a warning that will be off
|
|
1476
|
+
* on the day it matters.
|
|
1477
|
+
*/
|
|
1478
|
+
showFpsBadge: boolean;
|
|
1479
|
+
showBackendBadge: boolean;
|
|
1480
|
+
/**
|
|
1481
|
+
* A single plain pick of a layer moves a paused playhead onto it (gap 4),
|
|
1482
|
+
* when the playhead is outside the layer's lifespan. A per-viewer
|
|
1483
|
+
* pref on `showGrid`'s rule, kept in `editorPrefs`, and the View menu's
|
|
1484
|
+
* "Move playhead to selection" row.
|
|
1485
|
+
*/
|
|
1486
|
+
selectMovesPlayhead: boolean;
|
|
1487
|
+
/**
|
|
1488
|
+
* THE REST OF THE PREFERENCES PAGE (gap 5, Paper page 24), each a per-viewer
|
|
1489
|
+
* pref on `showGrid`'s rule: kept in `editorPrefs`, synced to the account
|
|
1490
|
+
* when hosted, never in the document, and carried across a document switch.
|
|
1491
|
+
*
|
|
1492
|
+
* `motionPaths`: the selected layer's motion path on the stage.
|
|
1493
|
+
* `rulers`: the camera pane's rulers (and the guides they create).
|
|
1494
|
+
* `smartGuides`: snapping to the frame and the other layers while dragging.
|
|
1495
|
+
* User guides snap either way; Ctrl drops both for one drag.
|
|
1496
|
+
* `blockLabels`: camera move bar names, when they fit or never.
|
|
1497
|
+
* `fieldLabels`: inspector fields show a glyph in the pill, or the name above.
|
|
1498
|
+
* `toolbarPosition`: the tool footer under the canvas, or above it.
|
|
1499
|
+
*/
|
|
1500
|
+
motionPaths: boolean;
|
|
1501
|
+
rulers: boolean;
|
|
1502
|
+
smartGuides: boolean;
|
|
1503
|
+
blockLabels: "fit" | "off";
|
|
1504
|
+
fieldLabels: "compact" | "named";
|
|
1505
|
+
toolbarPosition: "bottom" | "top";
|
|
1506
|
+
/**
|
|
1507
|
+
* What a played frame may render at (bridge v52, `PlaybackResolution`):
|
|
1508
|
+
* Auto lowers it below the display's density only while the GPU cannot keep
|
|
1509
|
+
* up, Full never does, Fast starts at 0.7 of it. Pushed to the stage with
|
|
1510
|
+
* the rulers (`setEditorChrome`). Kept in `editorPrefs` on this browser
|
|
1511
|
+
* only, not on the account: it is a fact about this machine's GPU, which
|
|
1512
|
+
* another device does not share.
|
|
1513
|
+
*/
|
|
1514
|
+
playbackResolution: PlaybackResolution;
|
|
1515
|
+
/** The Preferences dialog is open. A store bit for `exportOpen`'s reason:
|
|
1516
|
+
* ⌘, , the app menu row and the agent's `prefs.open` all open it. */
|
|
1517
|
+
prefsOpen: boolean;
|
|
282
1518
|
/** What the stage reported on ready, or null before it did. */
|
|
283
1519
|
stageInfo: ShotStageInfo | null;
|
|
1520
|
+
/**
|
|
1521
|
+
* What the stage PRESENTED over the last second, or null when it is not
|
|
1522
|
+
* playing.
|
|
1523
|
+
*
|
|
1524
|
+
* Written once a second during playback and retracted on stop. Like
|
|
1525
|
+
* `sceneId`'s per-cut write it is hot-only while `playing` (store `set`), so
|
|
1526
|
+
* the fps chip reads it hot and no other panel wakes for it. Null is the
|
|
1527
|
+
* resting state and means "no measurement", not "zero frames": the chrome's
|
|
1528
|
+
* own seek cadence is not an answer here, because the stage coalesces
|
|
1529
|
+
* requests and drops publications exactly when the two disagree.
|
|
1530
|
+
*/
|
|
1531
|
+
stagePerf: StagePerformanceReport | null;
|
|
1532
|
+
/**
|
|
1533
|
+
* The pieces of the Shot the stage could not draw and isolated (bridge v34
|
|
1534
|
+
* `onPieceFailures`): a layer, a background, a post stack or a transition
|
|
1535
|
+
* that threw while the rest kept drawing. Whole on every change, empty once
|
|
1536
|
+
* everything renders again, and empty with no stage attached. The stage
|
|
1537
|
+
* badge shows it, and the stage refuses an export of any frame it touches.
|
|
1538
|
+
*/
|
|
1539
|
+
stagePieceFailures: readonly StagePieceFailure[];
|
|
1540
|
+
/** The stage's render fallbacks (`health().renderErrors`): an html layer
|
|
1541
|
+
* that failed to decode, a font or glyph engine failure, a missing
|
|
1542
|
+
* texture. Mirrored on each health change for the same badge. */
|
|
1543
|
+
stageRenderErrors: readonly string[];
|
|
1544
|
+
/**
|
|
1545
|
+
* The stage still OWES THE OPEN SUBJECT its first whole presentation
|
|
1546
|
+
* (bridge v51 `onStageBusy`): the Project, a component context or a
|
|
1547
|
+
* material preview, not yet drawn whole by this renderer. Once it has been,
|
|
1548
|
+
* later loads (a font, a texture, a model) hold nothing.
|
|
1549
|
+
*
|
|
1550
|
+
* FALSE WHEN NO STAGE IS ATTACHED, and that is the contract rather than an
|
|
1551
|
+
* accident: a bare store, a unit test and SSR are all "ready". False too
|
|
1552
|
+
* while the stage cannot present at all (a crash, a lost context, a
|
|
1553
|
+
* renderer compile failure), so a queued Play never waits on it for ever.
|
|
1554
|
+
* `onReady` is not this signal: it fires when the RENDERER is up, before
|
|
1555
|
+
* anything is drawn. Read through `stageReady`.
|
|
1556
|
+
*/
|
|
1557
|
+
stageBusy: boolean;
|
|
1558
|
+
/**
|
|
1559
|
+
* A STAGE IS ON ITS WAY: a connect attempt has begun and none has reported
|
|
1560
|
+
* in yet.
|
|
1561
|
+
*
|
|
1562
|
+
* `stageBusy` cannot answer this. It is FALSE when no stage is attached, by
|
|
1563
|
+
* the contract above, so Play used to start for the whole iframe boot: the
|
|
1564
|
+
* stage document, a renderer adapter request and the first commit, with
|
|
1565
|
+
* nothing on the canvas but the stage's own boot ground. Now a press there
|
|
1566
|
+
* is queued (`playIntent` armed) until the stage reports in and draws.
|
|
1567
|
+
*
|
|
1568
|
+
* Read together with `stageInfo === null`, which is what "has not reported
|
|
1569
|
+
* in" means: `onReady` is the single writer of that field, and it fires when
|
|
1570
|
+
* the renderer is up on the document the chrome pushed.
|
|
1571
|
+
*
|
|
1572
|
+
* Written by `StagePanel` alone: true from its first commit (hosted, through
|
|
1573
|
+
* the code host's boot too), and again for the document a reload remints;
|
|
1574
|
+
* false when the attempt fails or the panel unmounts, which also cancels a
|
|
1575
|
+
* queued Play, so a bridge version mismatch or a poll timeout hands the
|
|
1576
|
+
* button back instead of leaving it waiting. A bare store, a unit test and
|
|
1577
|
+
* SSR never mount `StagePanel`, which is what keeps them ready.
|
|
1578
|
+
*/
|
|
1579
|
+
stageExpected: boolean;
|
|
284
1580
|
/** A stage connect or runtime failure, for the overlay strip. */
|
|
285
1581
|
stageError: string | null;
|
|
286
1582
|
|
|
@@ -298,10 +1594,70 @@ export interface ShotEditorState {
|
|
|
298
1594
|
/** Lazily fetched contracts by component id — the inspector's row source. */
|
|
299
1595
|
contracts: Readonly<Record<string, Contract>>;
|
|
300
1596
|
|
|
1597
|
+
// ── the shipped library, for the board's Library lane ────────────────────
|
|
1598
|
+
/** Every `GET /api/library/catalog` row: built-ins, installed shared
|
|
1599
|
+
* components and the SHIPPED items. The rail lists the non-builtins; a
|
|
1600
|
+
* `library` card looks its row up here for the thumb and the docs. */
|
|
1601
|
+
libraryCatalog: readonly LibraryItem[];
|
|
1602
|
+
/** `libraryLoaded`'s rule, for the same reason: a failed fetch also ends
|
|
1603
|
+
* with an empty list, and "no items" must never look like "not read". */
|
|
1604
|
+
libraryCatalogLoaded: boolean;
|
|
1605
|
+
libraryCatalogError: string | null;
|
|
1606
|
+
/** The items the registry REFUSED to read: a lying `library.json`, files that
|
|
1607
|
+
* drifted from `library-manifest.json`, a manifest entry with no directory.
|
|
1608
|
+
* They are omitted from `libraryCatalog`, so without these rows an item this
|
|
1609
|
+
* build broke is indistinguishable from one it never shipped. Each error
|
|
1610
|
+
* names its own fix. */
|
|
1611
|
+
libraryCatalogProblems: readonly { id: string; error: string }[];
|
|
1612
|
+
/** Lazily fetched item details by id (`loadLibraryItem`), the inspector's
|
|
1613
|
+
* Docs source. */
|
|
1614
|
+
libraryItems: Readonly<Record<string, LibraryItemDetail>>;
|
|
1615
|
+
|
|
1616
|
+
/** Server-authoritative media usage for the open Project. Null while it has
|
|
1617
|
+
* not been read, or when the current server predates the scoped asset API. */
|
|
1618
|
+
assetUsage: ProjectAssetUsage | null;
|
|
1619
|
+
|
|
301
1620
|
saveState: SaveState;
|
|
302
1621
|
/** Unsaved edits exist. Separate from `saveState` because a save that is
|
|
303
1622
|
* in flight while a new edit lands is BOTH "saving" and dirty. */
|
|
304
1623
|
dirty: boolean;
|
|
1624
|
+
/** Live sync only (null without it): an edit exists that is only in this
|
|
1625
|
+
* tab's memory, not yet in IndexedDB. Under sync THAT is what a closed tab
|
|
1626
|
+
* loses, so the close guard reads it instead of `dirty`. */
|
|
1627
|
+
syncUnstored: boolean | null;
|
|
1628
|
+
/** Live sync only (null without it): where this device's changes stand,
|
|
1629
|
+
* for the save chip, its popover and the quota banner. A `hold` makes the
|
|
1630
|
+
* Project read only until it clears (`readOnlyReason` says why). */
|
|
1631
|
+
syncStatus: SyncStatus | null;
|
|
1632
|
+
/** The open Project moved on the server under unsaved edits: another tab or
|
|
1633
|
+
* an agent wrote it ("changed"), or it is gone ("deleted"). Autosave stops
|
|
1634
|
+
* while it is set, so the same 409 is not sent on every edit, and the
|
|
1635
|
+
* shell's banner offers the one way on (`reloadLatest`). */
|
|
1636
|
+
saveConflict: "changed" | "deleted" | null;
|
|
1637
|
+
/** Live sync (R10): the open Project was renamed under the session. The
|
|
1638
|
+
* header's short note, "Renamed to X by Maya"; it clears itself. `by` is
|
|
1639
|
+
* the person's name, null when it was this user or nobody is known. */
|
|
1640
|
+
syncRenamed: { to: string; by: string | null; color: string | null } | null;
|
|
1641
|
+
/** Live sync (R10): the open Project went to the Trash under changes of
|
|
1642
|
+
* this tab that never reached the server. The dialog offers `restoreDeleted`
|
|
1643
|
+
* or `saveDeletedAsCopy`; the editor is read only meanwhile. */
|
|
1644
|
+
syncDeleted: SyncDeleted | null;
|
|
1645
|
+
/** The open Project is view only: the server said this caller may not edit
|
|
1646
|
+
* it (`LoadedShot.can.edit === false`, a hosted Viewer), or refused a save
|
|
1647
|
+
* as forbidden. Every document and server write is refused with
|
|
1648
|
+
* `VIEW_ONLY` (`viewOnly` in store.ts) and nothing autosaves. */
|
|
1649
|
+
readOnly: boolean;
|
|
1650
|
+
/** The Version history panel is open in the right column. */
|
|
1651
|
+
versionsOpen: boolean;
|
|
1652
|
+
/** The panel's name field is up and focused (Cmd+S, "Save version"). */
|
|
1653
|
+
versionNameAsk: boolean;
|
|
1654
|
+
/** The open Project's version rows, or null before the first read. */
|
|
1655
|
+
versionList: ShotVersionList | null;
|
|
1656
|
+
/** A version on screen in place of the live document. The chrome shows
|
|
1657
|
+
* `doc` read-only (`readOnly` is true while it is up); the history keeps
|
|
1658
|
+
* the live document, and `roleReadOnly` keeps what the server's role
|
|
1659
|
+
* said, so leaving the preview puts both back. */
|
|
1660
|
+
versionPreview: { version: ShotVersion; roleReadOnly: boolean } | null;
|
|
305
1661
|
/** The last API failure, in words the author can act on. */
|
|
306
1662
|
error: string | null;
|
|
307
1663
|
/** Codegen refused to overwrite a hand-edited generated file. Sticky until
|
|
@@ -318,33 +1674,131 @@ export const INITIAL_VIEW: ShotView = { zoom: 1, panX: 0, panY: 0 };
|
|
|
318
1674
|
/** Camera only, because that is the pane that IS the export: an editor that
|
|
319
1675
|
* opened onto a working pane would open onto a view of the shot that is not
|
|
320
1676
|
* the shot. */
|
|
321
|
-
export const INITIAL_STAGE_LAYOUT: StageLayout = {
|
|
1677
|
+
export const INITIAL_STAGE_LAYOUT: StageLayout = {
|
|
1678
|
+
mode: "camera",
|
|
1679
|
+
splitRatio: 0.5,
|
|
1680
|
+
};
|
|
322
1681
|
|
|
323
1682
|
export const INITIAL_STATE: ShotEditorState = {
|
|
324
|
-
shots: [],
|
|
325
1683
|
name: null,
|
|
326
1684
|
doc: null,
|
|
327
1685
|
rev: null,
|
|
328
1686
|
frame: 0,
|
|
329
1687
|
playing: false,
|
|
1688
|
+
playRate: 1,
|
|
1689
|
+
loopPlayback: true,
|
|
1690
|
+
playIntent: null,
|
|
1691
|
+
playOrigin: null,
|
|
1692
|
+
workArea: null,
|
|
330
1693
|
sceneId: null,
|
|
1694
|
+
enteredScene: null,
|
|
331
1695
|
selection: [],
|
|
332
1696
|
isolation: null,
|
|
333
1697
|
isolationOrbit: false,
|
|
334
1698
|
previousShading: "solid",
|
|
1699
|
+
shadingBeforeDef: null,
|
|
335
1700
|
editingDef: null,
|
|
1701
|
+
defFrame: 0,
|
|
1702
|
+
studioZoom: 1,
|
|
1703
|
+
codeComponent: null,
|
|
1704
|
+
codePreviewProps: {},
|
|
1705
|
+
codeFollow: null,
|
|
1706
|
+
codeFollowPending: null,
|
|
1707
|
+
componentPlay: {},
|
|
1708
|
+
componentApplyAll: false,
|
|
1709
|
+
codeScrubKey: null,
|
|
1710
|
+
codePendingDefaults: [],
|
|
1711
|
+
codeDefaultsBaseline: {},
|
|
1712
|
+
codeDefaultsError: null,
|
|
1713
|
+
contractDefaults: {},
|
|
1714
|
+
codeFiles: {},
|
|
1715
|
+
codeActive: null,
|
|
1716
|
+
codeOrder: [],
|
|
1717
|
+
projectFiles: [],
|
|
1718
|
+
projectFilesLoaded: false,
|
|
1719
|
+
projectFilesTruncated: false,
|
|
1720
|
+
projectFilesError: null,
|
|
336
1721
|
tool: "select",
|
|
337
|
-
|
|
1722
|
+
workPlanes: {},
|
|
1723
|
+
splineBasis: "bezier",
|
|
1724
|
+
splineEdit: null,
|
|
338
1725
|
animateMode: false,
|
|
339
|
-
|
|
1726
|
+
selectedMove: null,
|
|
1727
|
+
selectedKeyframes: [],
|
|
1728
|
+
dockMode: "rows",
|
|
340
1729
|
selectedMarker: null,
|
|
1730
|
+
selectedAudio: [],
|
|
1731
|
+
soloTracks: [],
|
|
1732
|
+
snapBeats: true,
|
|
1733
|
+
dockNotice: null,
|
|
341
1734
|
imagePick: null,
|
|
342
1735
|
stageMenu: null,
|
|
343
|
-
|
|
1736
|
+
appMenu: null,
|
|
1737
|
+
searchFocus: null,
|
|
1738
|
+
codeBackRequest: null,
|
|
1739
|
+
materialWorkspace: null,
|
|
1740
|
+
materialColumnWidth: MATERIAL_COLUMN.default,
|
|
1741
|
+
codeLiveWidth: CODE_LIVE_WIDTH.default,
|
|
1742
|
+
materialGraphSelection: { nodeIds: [], edgeIds: [] },
|
|
1743
|
+
materialGraphPath: [],
|
|
1744
|
+
materialPalette: null,
|
|
1745
|
+
materialSnapToGrid: true,
|
|
1746
|
+
materialFrameRequest: null,
|
|
1747
|
+
materialCompile: { status: "idle" },
|
|
1748
|
+
storyboardOpen: false,
|
|
1749
|
+
storyboardPlan: null,
|
|
1750
|
+
storyboardSketch: null,
|
|
1751
|
+
sketchSelection: [],
|
|
1752
|
+
sketchTool: "select",
|
|
1753
|
+
sketchPlaying: false,
|
|
1754
|
+
sketchGuide: null,
|
|
1755
|
+
returnTarget: "stage",
|
|
1756
|
+
componentsOpen: false,
|
|
1757
|
+
componentsTab: "preview",
|
|
1758
|
+
componentsSelection: null,
|
|
1759
|
+
componentsOrigin: null,
|
|
1760
|
+
// Dark, the desk, from the start: the Backdrop control lights what the
|
|
1761
|
+
// stage draws. Null sent nothing and left the working pane's own grey.
|
|
1762
|
+
componentPreview: { background: "dark", loopFrames: null },
|
|
1763
|
+
workspaceComponents: null,
|
|
1764
|
+
componentHomes: null,
|
|
1765
|
+
fontApply: null,
|
|
1766
|
+
workspaceFonts: null,
|
|
1767
|
+
fontCatalog: null,
|
|
1768
|
+
componentVersions: {},
|
|
1769
|
+
componentBuilds: {},
|
|
1770
|
+
shareComponentOpen: false,
|
|
1771
|
+
componentHistory: null,
|
|
1772
|
+
componentUses: null,
|
|
1773
|
+
componentListingUpdates: {},
|
|
1774
|
+
componentUpdate: null,
|
|
1775
|
+
componentUpdated: null,
|
|
1776
|
+
libraryBrowserOpen: false,
|
|
1777
|
+
insertDrawer: null,
|
|
1778
|
+
insertDrawerSeen: false,
|
|
1779
|
+
justPlaced: null,
|
|
1780
|
+
storyboardRenaming: null,
|
|
1781
|
+
storyboardSelection: [],
|
|
1782
|
+
storyboardRestoreAnimate: null,
|
|
1783
|
+
referenceMoreOpen: false,
|
|
1784
|
+
boardAssetsOpen: false,
|
|
1785
|
+
libraryBrowserCategory: null,
|
|
1786
|
+
boardBuilding: [],
|
|
1787
|
+
boardBuildingRan: false,
|
|
1788
|
+
boardDrop: null,
|
|
1789
|
+
motionRequest: null,
|
|
1790
|
+
agentPrefill: null,
|
|
1791
|
+
dockReveal: null,
|
|
344
1792
|
meshTriangles: {},
|
|
1793
|
+
meshMaterialSlots: {},
|
|
345
1794
|
exportOpen: false,
|
|
346
1795
|
renderJob: null,
|
|
1796
|
+
exportLock: null,
|
|
347
1797
|
agentOpen: false,
|
|
1798
|
+
commentsOpen: false,
|
|
1799
|
+
briefOpen: false,
|
|
1800
|
+
boardTool: null,
|
|
1801
|
+
canComment: true,
|
|
348
1802
|
agentItems: [],
|
|
349
1803
|
agentRunning: false,
|
|
350
1804
|
agentError: null,
|
|
@@ -357,28 +1811,217 @@ export const INITIAL_STATE: ShotEditorState = {
|
|
|
357
1811
|
railCollapsed: false,
|
|
358
1812
|
inspectorCollapsed: false,
|
|
359
1813
|
focusRestoreInspector: null,
|
|
360
|
-
|
|
1814
|
+
focusRestoreRail: null,
|
|
1815
|
+
focusHides: null,
|
|
1816
|
+
video: false,
|
|
361
1817
|
railWidth: RAIL_WIDTH.default,
|
|
362
1818
|
dockHeight: DOCK_HEIGHT.default,
|
|
363
|
-
|
|
1819
|
+
inspectorWidth: INSPECTOR_WIDTH.default,
|
|
1820
|
+
stageInset: { top: 0, right: 0, bottom: 0, left: 0 },
|
|
1821
|
+
dockPpf: DOCK_PPF.default,
|
|
364
1822
|
previewMuted: false,
|
|
365
1823
|
previewVolume: 1,
|
|
366
1824
|
view: INITIAL_VIEW,
|
|
367
1825
|
stageLayout: INITIAL_STAGE_LAYOUT,
|
|
368
1826
|
workingView: INITIAL_WORKING_VIEW,
|
|
369
1827
|
activePane: "camera",
|
|
370
|
-
|
|
1828
|
+
pilot: false,
|
|
1829
|
+
cameraDriver: null,
|
|
371
1830
|
shadingMode: "solid",
|
|
1831
|
+
showGrid: true,
|
|
1832
|
+
showFpsBadge: true,
|
|
1833
|
+
showBackendBadge: true,
|
|
1834
|
+
selectMovesPlayhead: true,
|
|
1835
|
+
motionPaths: true,
|
|
1836
|
+
rulers: false,
|
|
1837
|
+
smartGuides: true,
|
|
1838
|
+
blockLabels: "fit",
|
|
1839
|
+
fieldLabels: "compact",
|
|
1840
|
+
toolbarPosition: "bottom",
|
|
1841
|
+
playbackResolution: "auto",
|
|
1842
|
+
prefsOpen: false,
|
|
372
1843
|
stageInfo: null,
|
|
1844
|
+
stagePerf: null,
|
|
1845
|
+
stagePieceFailures: [],
|
|
1846
|
+
stageRenderErrors: [],
|
|
1847
|
+
stageBusy: false,
|
|
1848
|
+
stageExpected: false,
|
|
373
1849
|
stageError: null,
|
|
374
1850
|
library: [],
|
|
375
1851
|
libraryLoaded: false,
|
|
376
1852
|
libraryError: null,
|
|
377
1853
|
contracts: {},
|
|
1854
|
+
libraryCatalog: [],
|
|
1855
|
+
libraryCatalogLoaded: false,
|
|
1856
|
+
libraryCatalogError: null,
|
|
1857
|
+
libraryCatalogProblems: [],
|
|
1858
|
+
libraryItems: {},
|
|
1859
|
+
assetUsage: null,
|
|
378
1860
|
saveState: "idle",
|
|
379
1861
|
dirty: false,
|
|
1862
|
+
syncUnstored: null,
|
|
1863
|
+
syncStatus: null,
|
|
1864
|
+
saveConflict: null,
|
|
1865
|
+
syncRenamed: null,
|
|
1866
|
+
syncDeleted: null,
|
|
1867
|
+
readOnly: false,
|
|
1868
|
+
versionsOpen: false,
|
|
1869
|
+
versionNameAsk: false,
|
|
1870
|
+
versionList: null,
|
|
1871
|
+
versionPreview: null,
|
|
380
1872
|
error: null,
|
|
381
1873
|
emitWarning: null,
|
|
382
1874
|
canUndo: false,
|
|
383
1875
|
canRedo: false,
|
|
384
1876
|
};
|
|
1877
|
+
|
|
1878
|
+
/**
|
|
1879
|
+
* How long the SUBJECT-LOCAL clock runs, or null when no component is open.
|
|
1880
|
+
*
|
|
1881
|
+
* THE ONE ANSWER FOR THREE FORKS. The component context swaps the whole
|
|
1882
|
+
* playback triple (the total, the seek verb and the starting frame), and that
|
|
1883
|
+
* fork is asked in three places: the header's playback loop reads the total,
|
|
1884
|
+
* the same loop picks `seekDef` over `seek`, and `seekDefTo` clamps against it.
|
|
1885
|
+
* Three copies of "is a component open, and if so how long is it" is three
|
|
1886
|
+
* chances to answer differently, and the symptom of disagreeing is a transport
|
|
1887
|
+
* that wraps at a frame the scrubber cannot reach.
|
|
1888
|
+
*
|
|
1889
|
+
* A CODE COMPONENT HAS NO DURATION of its own — it has a `useStudioClock()` and
|
|
1890
|
+
* whatever it does with it — so it gets the preview's fixed ruler. A definition
|
|
1891
|
+
* has an authored one, or a derived extent.
|
|
1892
|
+
*/
|
|
1893
|
+
export function defClockFrames(s: ShotEditorState): number | null {
|
|
1894
|
+
// The preview's loop length (bridge v45) wraps the stage, so the chrome's
|
|
1895
|
+
// transport wraps at the same frame.
|
|
1896
|
+
const loop = s.componentPreview.loopFrames;
|
|
1897
|
+
if (s.codeComponent !== null) return loop ?? CODE_PREVIEW_FRAMES;
|
|
1898
|
+
const def =
|
|
1899
|
+
s.editingDef === null ? undefined : s.doc?.components?.[s.editingDef];
|
|
1900
|
+
return def === undefined ? null : (loop ?? defDurationFrames(def));
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
/**
|
|
1904
|
+
* THE WINDOW THE TRANSPORT LOOPS INSIDE, in whichever clock is driving.
|
|
1905
|
+
*
|
|
1906
|
+
* One function because the two halves of the answer have to agree: a start from
|
|
1907
|
+
* one reading and a length from another is a loop that wraps to a frame outside
|
|
1908
|
+
* itself, and `seek`'s clamp then pins the playhead there and playback looks
|
|
1909
|
+
* frozen. Three subjects, innermost first:
|
|
1910
|
+
*
|
|
1911
|
+
* A COMPONENT CONTEXT owns the clock outright. Its own duration, based at 0,
|
|
1912
|
+
* and `frame` does not move at all while it is open — so neither the entered
|
|
1913
|
+
* shot nor the work area has any say.
|
|
1914
|
+
*
|
|
1915
|
+
* AN ENTERED SHOT is a window of master time, so it is the loop.
|
|
1916
|
+
*
|
|
1917
|
+
* A WORK AREA is the scene context's own window, and only there: the two
|
|
1918
|
+
* above already answer, and a second window over one of them would be two
|
|
1919
|
+
* answers to one question. CLAMPED to the document on every read rather than
|
|
1920
|
+
* repaired on write — a trim, an undo or a deleted shot can shorten the film
|
|
1921
|
+
* under a work area nobody touched, and a stale end would loop past the last
|
|
1922
|
+
* frame the document has.
|
|
1923
|
+
*
|
|
1924
|
+
* It allocates one two-field object per call, and the two selectors below make
|
|
1925
|
+
* that two allocations per store notification — which during playback is twice
|
|
1926
|
+
* a frame. Deliberate, and within the rule this file keeps: what the rule
|
|
1927
|
+
* forbids is the O(scenes) walk `sceneWindows` materialises per call, which is
|
|
1928
|
+
* why the entered-shot arm reaches for `sceneToMaster` (a prefix sum with an
|
|
1929
|
+
* early return) instead. Two fixed-size literals are not that.
|
|
1930
|
+
*
|
|
1931
|
+
* EXPORTED so the window rule can be asked directly, without a fake rAF queue
|
|
1932
|
+
* to reach it — the same reason `playbackTargetFrame` is.
|
|
1933
|
+
*/
|
|
1934
|
+
export function playbackWindow(s: ShotEditorState): {
|
|
1935
|
+
start: number;
|
|
1936
|
+
frames: number;
|
|
1937
|
+
} {
|
|
1938
|
+
const def = defClockFrames(s);
|
|
1939
|
+
if (def !== null) return { start: 0, frames: def };
|
|
1940
|
+
const doc = s.doc;
|
|
1941
|
+
if (doc === null) return { start: 0, frames: 0 };
|
|
1942
|
+
const total = docDurationFrames(doc);
|
|
1943
|
+
if (s.enteredScene !== null) {
|
|
1944
|
+
const entered = doc.scenesById[s.enteredScene];
|
|
1945
|
+
// A STALE ENTERED SHOT FALLS BACK TO THE WHOLE FILM AND NOT TO THE WORK
|
|
1946
|
+
// AREA. The shot was deleted out from under the author, which is a state
|
|
1947
|
+
// the chrome repairs on its next context sync; borrowing a bracket they
|
|
1948
|
+
// drew in the scene context for those few frames would be the transport
|
|
1949
|
+
// inventing a window nobody asked for at the one moment it is confused.
|
|
1950
|
+
return entered === undefined
|
|
1951
|
+
? { start: 0, frames: total }
|
|
1952
|
+
: {
|
|
1953
|
+
start: sceneToMaster(doc, s.enteredScene, 0) ?? 0,
|
|
1954
|
+
frames: Math.max(0, Math.trunc(entered.durationFrames)),
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
const area = s.workArea;
|
|
1958
|
+
if (area === null) return { start: 0, frames: total };
|
|
1959
|
+
const end = Math.min(total, Math.trunc(area.end));
|
|
1960
|
+
const start = Math.max(0, Math.trunc(area.start));
|
|
1961
|
+
// A BRACKET THE FILM NO LONGER REACHES IS NO WINDOW AT ALL — fall back to the
|
|
1962
|
+
// whole film rather than squeezing it onto the last frame. A one-frame loop
|
|
1963
|
+
// at the end of a document the author just trimmed reads as a frozen
|
|
1964
|
+
// transport, which is the failure this whole file keeps designing against,
|
|
1965
|
+
// and the honest answer to "your bracket is off the end" is the film. A
|
|
1966
|
+
// bracket that still OVERLAPS keeps its overlap: only the end was clamped.
|
|
1967
|
+
return start < end ? { start, frames: end - start } : { start: 0, frames: total };
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
/** Whether the transport wraps at the end of `playbackWindow`. A definition
|
|
1971
|
+
* and an entered shot always do; the Project context follows the Loop pref. */
|
|
1972
|
+
export function playbackLoops(s: ShotEditorState): boolean {
|
|
1973
|
+
if (defClockFrames(s) !== null) return true;
|
|
1974
|
+
return s.enteredScene !== null || s.loopPlayback;
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
/**
|
|
1978
|
+
* IS THE SOUND THE MASTER SOUNDTRACK, PLAYED STRAIGHT? The one answer behind
|
|
1979
|
+
* `TransportSnapshot.audible`.
|
|
1980
|
+
*
|
|
1981
|
+
* TWO REASONS IT IS FALSE, and they are the same reason twice: a second clock
|
|
1982
|
+
* the picture's clock cannot agree with.
|
|
1983
|
+
*
|
|
1984
|
+
* A COMPONENT CONTEXT drives a definition-local clock and never moves
|
|
1985
|
+
* `frame`, so the document's soundtrack has nothing to anchor to — it would
|
|
1986
|
+
* start from a static master frame and play on over a definition looping on
|
|
1987
|
+
* its own. `defClockFrames` is the same predicate the transport forks on.
|
|
1988
|
+
*
|
|
1989
|
+
* ANY RATE BUT 1 is silent, and that is a refusal rather than a gap. Reverse
|
|
1990
|
+
* needs reversed buffers; 2x and 4x with the pitch kept need a time
|
|
1991
|
+
* stretcher. Both are a second scheduler running on its own arithmetic
|
|
1992
|
+
* beside the picture's, and the moment the two round differently the author
|
|
1993
|
+
* is hearing one frame while watching another. This lane refuses that the
|
|
1994
|
+
* way it refuses every other plausible-but-wrong artifact.
|
|
1995
|
+
*
|
|
1996
|
+
* A pure function of the state, so the bridge that pushes the snapshot has no
|
|
1997
|
+
* second opinion to form — see `audio/index.tsx`.
|
|
1998
|
+
*/
|
|
1999
|
+
export function transportAudible(s: ShotEditorState): boolean {
|
|
2000
|
+
return defClockFrames(s) === null && s.playRate === 1;
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
/** The buffer the editor is showing, or null. One reader for `codeActive` so
|
|
2004
|
+
* nothing has to remember that it indexes `codeFiles`. */
|
|
2005
|
+
export function activeCodeFile(s: ShotEditorState): CodeFileState | null {
|
|
2006
|
+
return s.codeActive === null ? null : (s.codeFiles[s.codeActive] ?? null);
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
/** The file the SUBJECT lives in, or null. What lets the URL leave `file=` off
|
|
2010
|
+
* in the ordinary case where the author is looking at the component's own
|
|
2011
|
+
* source: `code=` already says which file that is. */
|
|
2012
|
+
export function codeSubjectRel(s: ShotEditorState): string | null {
|
|
2013
|
+
if (s.codeComponent === null) return null;
|
|
2014
|
+
return s.library.find((r) => r.id === s.codeComponent)?.rel ?? null;
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
/** Is ANY open buffer unsaved? What the leave-confirm asks, and it must ask
|
|
2018
|
+
* across the whole tab set: the buffer on screen being clean says nothing
|
|
2019
|
+
* about the three behind it, and closing the studio drops them all. */
|
|
2020
|
+
export function anyCodeFileDirty(s: Pick<ShotEditorState, "codeFiles">): boolean {
|
|
2021
|
+
return Object.values(s.codeFiles).some((f) => f.content !== f.savedContent);
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
/** Are the code buffers live? Only Code Studio's subject holds them. */
|
|
2025
|
+
export function buffersOpen(s: ShotEditorState): boolean {
|
|
2026
|
+
return s.codeComponent !== null;
|
|
2027
|
+
}
|