@dailies-studio/shot-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.vite/manifest.json +382 -0
- package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
- package/dist/assets/AgentPanel-DJEphah0.css +1 -0
- package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
- package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
- package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
- package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
- package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
- package/dist/assets/CurvePane-BjFVabKX.css +1 -0
- package/dist/assets/CurvePane-R22WzllJ.js +1 -0
- package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
- package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
- package/dist/assets/FontPicker-v0IOIArM.css +1 -0
- package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
- package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
- package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
- package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
- package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
- package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
- package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
- package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
- package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
- package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
- package/dist/assets/SoundMode-BmcZru3i.js +3 -0
- package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
- package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
- package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
- package/dist/assets/board-B88EPuLO.css +1 -0
- package/dist/assets/board-CL-he7SL.js +3 -0
- package/dist/assets/components-0oOSPN0l.js +1 -0
- package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
- package/dist/assets/googleFonts-BNon6M7X.js +1 -0
- package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
- package/dist/assets/index-4F5NQmI8.css +1 -0
- package/dist/assets/index-BGHQGV5z.js +117 -0
- package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/dist/assets/libraryModel-CpIwfFha.js +1 -0
- package/dist/assets/lucide-6iK64tTB.js +9319 -0
- package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
- package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
- package/dist/assets/webmcp-BsB9gxWC.js +2 -0
- package/dist/index.html +34 -3
- package/package.json +12 -9
- package/src/addFamilies.ts +66 -0
- package/src/api.ts +1631 -55
- package/src/app/App.tsx +977 -198
- package/src/app/lazyPanel.tsx +137 -0
- package/src/app/lazySurfaces.tsx +203 -0
- package/src/audio/index.tsx +41 -10
- package/src/audio/meter.ts +485 -0
- package/src/audio/meterMath.ts +273 -0
- package/src/audio/meterStore.ts +155 -0
- package/src/audio/offlineMix.ts +289 -0
- package/src/audio/preview.ts +928 -118
- package/src/bridge.ts +15 -5
- package/src/bridgeTypes.ts +1611 -36
- package/src/channels.ts +17 -32
- package/src/fonts/googleFonts.ts +4 -0
- package/src/hostApi.ts +174 -0
- package/src/icons.tsx +770 -0
- package/src/layerWords.ts +61 -0
- package/src/main.tsx +56 -4
- package/src/panels/ResizeHandle.tsx +33 -43
- package/src/panels/agent/AgentDots.tsx +36 -0
- package/src/panels/agent/AgentPanel.tsx +119 -43
- package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
- package/src/panels/agent/agent.css +113 -97
- package/src/panels/agent/agentGate.ts +35 -0
- package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
- package/src/panels/code/CodeStudio.tsx +373 -0
- package/src/panels/code/FileTree.tsx +265 -0
- package/src/panels/code/codeBufferGuard.ts +52 -0
- package/src/panels/code/codeParts.tsx +321 -0
- package/src/panels/comments/CommentComposer.tsx +239 -0
- package/src/panels/comments/CommentThread.tsx +346 -0
- package/src/panels/comments/CommentsPanel.tsx +336 -0
- package/src/panels/comments/InboxButton.tsx +131 -0
- package/src/panels/comments/MentionMenu.tsx +98 -0
- package/src/panels/comments/parts.tsx +171 -0
- package/src/panels/export/ExportDialog.tsx +919 -96
- package/src/panels/export/ExportScrim.tsx +57 -0
- package/src/panels/files/AssetsTab.tsx +656 -0
- package/src/panels/files/FilePanel.tsx +1466 -216
- package/src/panels/files/LayerMarks.tsx +40 -0
- package/src/panels/files/LeftColumn.tsx +37 -0
- package/src/panels/files/McpSection.tsx +690 -47
- package/src/panels/files/MissingAssetsNotice.tsx +60 -0
- package/src/panels/files/assetDrag.ts +79 -0
- package/src/panels/files/resourceParts.tsx +255 -0
- package/src/panels/fonts/FontPicker.tsx +762 -0
- package/src/panels/fonts/faceCache.ts +207 -0
- package/src/panels/fonts/fontPicker.css +271 -0
- package/src/panels/fonts/pickerModel.ts +265 -0
- package/src/panels/fonts/pickerRequest.ts +46 -0
- package/src/panels/header/AccountAvatar.tsx +76 -0
- package/src/panels/header/AgentChip.tsx +77 -0
- package/src/panels/header/AppMenu.tsx +213 -0
- package/src/panels/header/Header.tsx +564 -84
- package/src/panels/header/HistoryControls.tsx +31 -5
- package/src/panels/header/PresenceStack.tsx +329 -0
- package/src/panels/header/ProjectTitle.tsx +232 -0
- package/src/panels/header/ShareButton.tsx +59 -0
- package/src/panels/header/StatusBadges.tsx +358 -27
- package/src/panels/header/SyncDeletedDialog.tsx +88 -0
- package/src/panels/header/ZoomControls.tsx +46 -15
- package/src/panels/header/agentHandoff.ts +52 -0
- package/src/panels/header/leave.ts +26 -0
- package/src/panels/help/HelpSection.tsx +189 -163
- package/src/panels/insert/InsertDrawer.tsx +723 -0
- package/src/panels/insert/iconSet.ts +109 -0
- package/src/panels/inspector/ExposeDot.tsx +117 -0
- package/src/panels/inspector/FramingRows.tsx +314 -44
- package/src/panels/inspector/InspectorFooter.tsx +91 -0
- package/src/panels/inspector/InspectorHead.tsx +425 -0
- package/src/panels/inspector/InspectorPanel.tsx +408 -124
- package/src/panels/inspector/KeyframeDot.tsx +373 -0
- package/src/panels/inspector/KeyframeSection.tsx +257 -0
- package/src/panels/inspector/MarkerSection.tsx +55 -11
- package/src/panels/inspector/ParamRow.tsx +84 -53
- package/src/panels/inspector/RightColumn.tsx +152 -0
- package/src/panels/inspector/TimeSection.tsx +866 -0
- package/src/panels/inspector/animate/EaseControl.tsx +244 -0
- package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
- package/src/panels/inspector/animate/ShotSection.tsx +562 -48
- package/src/panels/inspector/design/AlignRow.tsx +95 -31
- package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
- package/src/panels/inspector/design/ClonerSection.tsx +323 -62
- package/src/panels/inspector/design/ComponentSection.tsx +50 -43
- package/src/panels/inspector/design/CornerSection.tsx +45 -14
- package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
- package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
- package/src/panels/inspector/design/DeviceSection.tsx +210 -0
- package/src/panels/inspector/design/FillSection.tsx +83 -51
- package/src/panels/inspector/design/FontField.tsx +196 -0
- package/src/panels/inspector/design/HtmlSection.tsx +23 -12
- package/src/panels/inspector/design/InstanceSection.tsx +373 -154
- package/src/panels/inspector/design/MaskSection.tsx +84 -0
- package/src/panels/inspector/design/MaterialSection.tsx +177 -0
- package/src/panels/inspector/design/MediaSection.tsx +273 -120
- package/src/panels/inspector/design/MeshSection.tsx +910 -82
- package/src/panels/inspector/design/PaintRows.tsx +550 -0
- package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
- package/src/panels/inspector/design/ShadowSection.tsx +221 -48
- package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
- package/src/panels/inspector/design/StrokeSection.tsx +228 -43
- package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
- package/src/panels/inspector/design/TransformSection.tsx +244 -50
- package/src/panels/inspector/design/TypographySection.tsx +679 -110
- package/src/panels/inspector/layout.ts +295 -0
- package/src/panels/inspector/propValue.ts +199 -0
- package/src/panels/inspector/scene/CameraSection.tsx +232 -22
- package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
- package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
- package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
- package/src/panels/inspector/scene/SceneSection.tsx +219 -44
- package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
- package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
- package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
- package/src/panels/inspector/timeModel.ts +190 -0
- package/src/panels/inspector/useDocWrite.ts +438 -146
- package/src/panels/library/LibraryBrowser.tsx +435 -0
- package/src/panels/library/LibraryGrid.tsx +190 -0
- package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
- package/src/panels/library/LibraryRail.tsx +84 -0
- package/src/panels/library/LibrarySoundPanel.tsx +293 -0
- package/src/panels/library/SoundHistory.tsx +245 -0
- package/src/panels/library/StockSounds.tsx +390 -0
- package/src/panels/library/libraryModel.ts +147 -0
- package/src/panels/library/stockSearch.ts +77 -0
- package/src/panels/maskMarks.tsx +117 -0
- package/src/panels/material/MaterialCommentNode.tsx +54 -0
- package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
- package/src/panels/material/MaterialGraphMenu.tsx +45 -0
- package/src/panels/material/MaterialGroupNode.tsx +56 -0
- package/src/panels/material/MaterialLibrary.tsx +253 -0
- package/src/panels/material/MaterialNode.tsx +453 -0
- package/src/panels/material/MaterialNodePalette.tsx +349 -0
- package/src/panels/material/MaterialParameterControls.tsx +525 -0
- package/src/panels/material/MaterialRerouteNode.tsx +42 -0
- package/src/panels/material/MaterialWorkspace.tsx +501 -0
- package/src/panels/material/NodePreview.tsx +70 -0
- package/src/panels/material/flowModel.ts +441 -0
- package/src/panels/material/parameterValues.ts +231 -0
- package/src/panels/material/previewScheduler.ts +235 -0
- package/src/panels/material/reactFlow.css +6 -0
- package/src/panels/material/socketGlyph.ts +85 -0
- package/src/panels/material/viewportMemory.ts +58 -0
- package/src/panels/preferences/PreferencesDialog.tsx +215 -0
- package/src/panels/share/ShareComponentDialog.tsx +247 -0
- package/src/panels/stage/StageMenu.tsx +35 -5
- package/src/panels/stage/StagePanel.tsx +338 -23
- package/src/panels/stage/cellSlots.tsx +70 -0
- package/src/panels/storyboard/BoardCanvas.tsx +739 -0
- package/src/panels/storyboard/BoardCursors.tsx +158 -0
- package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
- package/src/panels/storyboard/BoardPlan.tsx +42 -0
- package/src/panels/storyboard/ComponentSheet.tsx +187 -0
- package/src/panels/storyboard/ComponentStage.tsx +235 -0
- package/src/panels/storyboard/MiniSketch.tsx +173 -0
- package/src/panels/storyboard/PartPicker.tsx +64 -0
- package/src/panels/storyboard/PlanPanel.tsx +617 -0
- package/src/panels/storyboard/ReferencePreview.tsx +217 -0
- package/src/panels/storyboard/ShotBrief.tsx +360 -0
- package/src/panels/storyboard/ShotCard.tsx +251 -0
- package/src/panels/storyboard/StagePreview.tsx +348 -0
- package/src/panels/storyboard/StillThumbs.tsx +162 -0
- package/src/panels/storyboard/StoryboardView.tsx +79 -0
- package/src/panels/storyboard/board.css +1065 -0
- package/src/panels/storyboard/board.ts +12 -0
- package/src/panels/storyboard/boardClipboard.ts +111 -0
- package/src/panels/storyboard/boardOrder.ts +68 -0
- package/src/panels/storyboard/boardRows.tsx +425 -0
- package/src/panels/storyboard/liveCap.ts +90 -0
- package/src/panels/storyboard/plan.css +295 -0
- package/src/panels/storyboard/posterCache.ts +133 -0
- package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
- package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
- package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
- package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
- package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
- package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
- package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
- package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
- package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
- package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
- package/src/panels/storyboard/shotboard/sketch.css +1392 -0
- package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
- package/src/panels/storyboard/storyboardDrop.ts +164 -0
- package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
- package/src/panels/studio/ComponentSidePanel.tsx +355 -0
- package/src/panels/studio/ComponentStudio.tsx +243 -0
- package/src/panels/studio/ComponentsHome.tsx +318 -0
- package/src/panels/studio/ComponentsPreview.tsx +179 -0
- package/src/panels/studio/EveryUse.tsx +244 -0
- package/src/panels/studio/ThisUsePanel.tsx +679 -0
- package/src/panels/studio/componentParts.tsx +341 -0
- package/src/panels/studio/components.ts +6 -0
- package/src/panels/studio/newCodeComponent.ts +66 -0
- package/src/panels/timeline/AudioFxButton.tsx +290 -0
- package/src/panels/timeline/AudioLane.tsx +674 -85
- package/src/panels/timeline/BlockRow.tsx +154 -85
- package/src/panels/timeline/ChannelRow.tsx +378 -129
- package/src/panels/timeline/CurvePane.tsx +508 -0
- package/src/panels/timeline/Filmstrip.tsx +361 -0
- package/src/panels/timeline/GenerateDrawer.tsx +407 -0
- package/src/panels/timeline/KeyStretch.tsx +115 -0
- package/src/panels/timeline/LifespanRow.tsx +730 -47
- package/src/panels/timeline/MarkerLane.tsx +46 -8
- package/src/panels/timeline/PlayButton.tsx +91 -0
- package/src/panels/timeline/PropertyPicker.tsx +254 -0
- package/src/panels/timeline/ShotRow.tsx +400 -0
- package/src/panels/timeline/SoundMode.tsx +1406 -0
- package/src/panels/timeline/TimelineDock.tsx +2338 -272
- package/src/panels/timeline/TransportVolume.tsx +163 -0
- package/src/panels/timeline/beatRuler.ts +74 -0
- package/src/panels/timeline/curveModel.ts +500 -0
- package/src/panels/timeline/duckUnderVoice.ts +68 -0
- package/src/panels/timeline/keyframeMenu.ts +138 -0
- package/src/panels/timeline/labelWidth.ts +49 -0
- package/src/panels/timeline/marquee.ts +155 -0
- package/src/panels/timeline/model.ts +1860 -199
- package/src/panels/timeline/snap.ts +125 -43
- package/src/panels/timeline/staggerPreview.tsx +92 -0
- package/src/panels/timeline/takeMeasure.ts +72 -0
- package/src/panels/timeline/useLive.ts +48 -0
- package/src/panels/timeline/viewport.ts +234 -0
- package/src/panels/toolbar/BackChip.tsx +116 -0
- package/src/panels/toolbar/CameraViewBar.tsx +118 -49
- package/src/panels/toolbar/CanvasBar.tsx +117 -0
- package/src/panels/toolbar/InsertButton.tsx +38 -0
- package/src/panels/toolbar/IsolationTrail.tsx +156 -0
- package/src/panels/toolbar/PanelToggles.tsx +86 -0
- package/src/panels/toolbar/ShadingBar.tsx +119 -26
- package/src/panels/toolbar/ToolDockBar.tsx +329 -80
- package/src/panels/toolbar/ToolFooter.tsx +102 -0
- package/src/panels/toolbar/ViewModeBar.tsx +247 -38
- package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
- package/src/panels/toolbar/useAddRows.tsx +326 -0
- package/src/panels/toolbar/useDismiss.ts +122 -0
- package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
- package/src/panels/versions/versionModel.ts +103 -0
- package/src/panels/virtualWindow.ts +271 -0
- package/src/shotTokens.css +151 -60
- package/src/stage/FrustumGizmo.tsx +504 -84
- package/src/stage/GridPlane.tsx +225 -16
- package/src/stage/NodePreviews.tsx +628 -0
- package/src/stage/PerfHud.tsx +437 -47
- package/src/stage/SplitRenderer.tsx +816 -169
- package/src/stage/StageApp.tsx +4274 -486
- package/src/stage/StageRootBoundary.tsx +114 -0
- package/src/stage/StageTools.tsx +651 -151
- package/src/stage/TransformGizmo.tsx +696 -0
- package/src/stage/adaptivePresentation.ts +145 -0
- package/src/stage/bridgeClient.ts +2522 -41
- package/src/stage/exportWorker.ts +9 -0
- package/src/stage/feed.ts +305 -14
- package/src/stage/fontPreview.ts +176 -0
- package/src/stage/frustumGeometry.ts +319 -1
- package/src/stage/furnitureTokens.ts +33 -0
- package/src/stage/gizmoMath.ts +508 -16
- package/src/stage/gpuTimer.ts +276 -0
- package/src/stage/instanceModules.ts +194 -33
- package/src/stage/interactions/move.ts +24 -5
- package/src/stage/isolation.ts +291 -33
- package/src/stage/jsSelfProfiler.ts +325 -0
- package/src/stage/main.tsx +116 -16
- package/src/stage/modalState.ts +222 -23
- package/src/stage/moduleEpoch.ts +60 -0
- package/src/stage/motionPathStage.ts +279 -0
- package/src/stage/motionPreview.ts +74 -0
- package/src/stage/overlays/Overlays.tsx +1112 -159
- package/src/stage/overlays/Rulers.tsx +495 -0
- package/src/stage/panes.ts +159 -10
- package/src/stage/project.ts +68 -3
- package/src/stage/renderScaleController.ts +730 -0
- package/src/stage/rulers.ts +129 -0
- package/src/stage/sceneExtent.ts +75 -0
- package/src/stage/shading.ts +20 -0
- package/src/stage/splineStage.ts +632 -0
- package/src/stage/stagePerformanceTelemetry.ts +717 -0
- package/src/stage/stageTheme.ts +36 -0
- package/src/stage/stageWarmup.ts +11 -0
- package/src/stage/textAutoSize.ts +124 -0
- package/src/stage/tier1Export.ts +641 -0
- package/src/stage/tokenColor.ts +14 -9
- package/src/stage/view.ts +56 -9
- package/src/stage/workingView.ts +277 -33
- package/src/state/accountPrefs.ts +261 -0
- package/src/state/agentActions.ts +173 -15
- package/src/state/agentActivity.ts +198 -0
- package/src/state/agentQuestion.ts +239 -0
- package/src/state/bootOpen.ts +42 -0
- package/src/state/cameraVerbs.ts +139 -0
- package/src/state/clientId.ts +6 -1
- package/src/state/comments.ts +1099 -0
- package/src/state/context.tsx +19 -1
- package/src/state/editorPrefs.ts +220 -0
- package/src/state/errorReports.ts +121 -0
- package/src/state/fieldWrite.ts +161 -0
- package/src/state/fontRecents.ts +70 -0
- package/src/state/fonts.ts +656 -0
- package/src/state/graphClipboard.ts +414 -0
- package/src/state/imageThumbs.ts +216 -0
- package/src/state/insert.ts +444 -67
- package/src/state/isolationTrail.ts +92 -0
- package/src/state/keyMoves.ts +134 -0
- package/src/state/keyframeClipboard.ts +177 -0
- package/src/state/keymap.ts +1153 -117
- package/src/state/layoutPrefs.ts +272 -0
- package/src/state/layouts.ts +205 -0
- package/src/state/measureMedia.ts +167 -0
- package/src/state/menu.ts +2341 -28
- package/src/state/motionStamp.ts +7 -0
- package/src/state/panelSize.ts +375 -5
- package/src/state/pendingChanges.ts +119 -0
- package/src/state/playHold.ts +78 -0
- package/src/state/playbackPreparation.ts +562 -0
- package/src/state/posterCapture.ts +242 -0
- package/src/state/presence.ts +254 -0
- package/src/state/projectComponents.ts +160 -0
- package/src/state/renderEvents.ts +298 -18
- package/src/state/runtimeErrors.ts +46 -0
- package/src/state/selection.ts +12 -0
- package/src/state/shortcuts.ts +1606 -0
- package/src/state/sketchGuide.ts +51 -0
- package/src/state/splineRefs.ts +87 -0
- package/src/state/stageHealth.ts +259 -0
- package/src/state/stageWatchdog.ts +47 -0
- package/src/state/stillStage.ts +330 -0
- package/src/state/stillThumbs.ts +474 -0
- package/src/state/store.ts +23591 -2982
- package/src/state/storyboardModel.ts +528 -0
- package/src/state/sync.ts +1257 -0
- package/src/state/syncCache.ts +159 -0
- package/src/state/triangles.ts +13 -0
- package/src/state/types.ts +1698 -55
- package/src/state/unsavedGuard.ts +49 -0
- package/src/state/viewMode.ts +200 -0
- package/src/state/webmcp.ts +1128 -0
- package/src/state/workspace.ts +1147 -0
- package/src/styles/assets-tab.css +304 -0
- package/src/styles/canvas.css +545 -0
- package/src/styles/comments.css +1435 -0
- package/src/styles/controls.css +162 -0
- package/src/styles/dialogs.css +390 -0
- package/src/styles/insert-drawer.css +586 -0
- package/src/styles/inspector-board.css +207 -0
- package/src/styles/inspector-object.css +486 -0
- package/src/styles/inspector-shot.css +252 -0
- package/src/styles/inspector.css +360 -0
- package/src/styles/left-column.css +1628 -0
- package/src/styles/library-preview.css +575 -0
- package/src/styles/library.css +512 -0
- package/src/styles/modes/code.css +569 -0
- package/src/styles/modes/component.css +1561 -0
- package/src/styles/modes/material.css +1201 -0
- package/src/styles/modes/storyboard.css +518 -0
- package/src/styles/share-component.css +49 -0
- package/src/styles/shell.css +653 -0
- package/src/styles/shot-brief.css +360 -0
- package/src/styles/states.css +76 -0
- package/src/styles/sync.css +380 -0
- package/src/styles/timeline-graph.css +384 -0
- package/src/styles/timeline-meters.css +89 -0
- package/src/styles/timeline-rows.css +1959 -0
- package/src/styles/timeline-sound.css +1003 -0
- package/src/styles/timeline.css +754 -0
- package/src/styles/tool-footer.css +525 -0
- package/src/styles/topbar.css +886 -0
- package/src/styles/versions.css +262 -0
- package/src/styles.css +72 -2921
- package/src/tools.ts +108 -23
- package/dist/assets/index-B7notge7.css +0 -1
- package/dist/assets/index-BBE6Ta9L.js +0 -52
- package/src/panels/files/icons.tsx +0 -118
- package/src/panels/header/DocSwitcher.tsx +0 -85
- package/src/panels/header/TransportBar.tsx +0 -86
- package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
- package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
- package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
- package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
- package/src/panels/inspector/animate/useBlocks.ts +0 -43
- package/src/panels/material/MaterialPicker.tsx +0 -172
- package/src/panels/timeline/SceneStrip.tsx +0 -145
- package/src/panels/toolbar/AddMenu.tsx +0 -230
- package/src/stage/MoveGizmo.tsx +0 -411
|
@@ -0,0 +1,641 @@
|
|
|
1
|
+
// Tier-1 export for the shot stage (the browser-side sibling of the motion
|
|
2
|
+
// harness's stageExport.ts, a fifth of its size because a shot is ONE canvas):
|
|
3
|
+
// per frame, arm a snapshot, commit the frame on the live ShotRuntime, capture
|
|
4
|
+
// synchronously from its onPresented call stack, and feed the encode worker.
|
|
5
|
+
// whose frameDone ack IS the encoder's backpressure, so memory stays bounded
|
|
6
|
+
// at ~one frame in flight.
|
|
7
|
+
//
|
|
8
|
+
// There is no SnapDOM, no compositing pass and no per-layer split here:
|
|
9
|
+
// ShotRuntime draws every layer kind, text included, onto one WebGL canvas.
|
|
10
|
+
// The default framebuffer is deliberately UNPRESERVED for playback speed, so
|
|
11
|
+
// capture must begin in the same task as the draw, before Chromium presents
|
|
12
|
+
// and retires it. A later canvas read can be a valid-sized all-black image.
|
|
13
|
+
//
|
|
14
|
+
// The loop is a plain awaited while, never rAF. A hidden tab still stops the
|
|
15
|
+
// stage itself (rAF, ResizeObserver and WebGL's rAF-polled program links all
|
|
16
|
+
// halt), so each frame's deadline counts visible time only and the export
|
|
17
|
+
// pauses rather than fails while the tab is behind another. It does not poll
|
|
18
|
+
// either: ClockDriver
|
|
19
|
+
// calls onPresented only after the draw and only when non-draw readiness claims
|
|
20
|
+
// are clear. The exact frame, marker and backing size are checked in that same
|
|
21
|
+
// callback before the armed snapshot is invoked.
|
|
22
|
+
import { CAPABILITY_PROBE_TIMED_OUT, converse, gifFrameStride, gifRate } from "@dailies-studio/shot/tier1";
|
|
23
|
+
import type { Mp4Encoding, Tier1Capability, WorkerLike } from "@dailies-studio/shot/tier1";
|
|
24
|
+
import type { Rational } from "@dailies-studio/engine";
|
|
25
|
+
|
|
26
|
+
/** What the exporter needs from the stage. Everything here must be honest
|
|
27
|
+
* about the CURRENT stage. The exporter never touches stage internals, and
|
|
28
|
+
* the tests fake this seam whole. */
|
|
29
|
+
export interface ShotExportHost {
|
|
30
|
+
/** The export framing's numbers, or null before a document is loaded. */
|
|
31
|
+
composition(): { width: number; height: number; fps: Rational } | null;
|
|
32
|
+
/** Frames the requested scope runs, resolved against the CURRENT document
|
|
33
|
+
* (the slice the runtime will render), or null for an unknown scene id. */
|
|
34
|
+
scopeFrames(sceneId: string | undefined): number | null;
|
|
35
|
+
/** The export-local frames on which a scene begins (always including 0).
|
|
36
|
+
* An export never warms the next scene, so each of these is a cold mount:
|
|
37
|
+
* models to parse, rasters to draw, programs to link. */
|
|
38
|
+
sceneStartFrames(sceneId: string | undefined): readonly number[];
|
|
39
|
+
/** Flip the stage into export framing for `sceneId` (undefined = whole
|
|
40
|
+
* timeline) at an uncovered sentinel frame. Remembers what to restore; the
|
|
41
|
+
* first armed seek to frame 0 is therefore always a real presentation. */
|
|
42
|
+
begin(sceneId: string | undefined): void;
|
|
43
|
+
/** Commit `frame` on the export stage. */
|
|
44
|
+
seek(frame: number): void;
|
|
45
|
+
/** Restore the author's framing, playhead and any deferred chrome pushes. */
|
|
46
|
+
end(): void;
|
|
47
|
+
/** The live GL canvas, or null before the runtime mounted. */
|
|
48
|
+
canvas(): HTMLCanvasElement | null;
|
|
49
|
+
/** True when the stage's LAST COMMIT was the export framing at `frame`.
|
|
50
|
+
* the DOM marker StageApp stamps in the same commit that claims the draw
|
|
51
|
+
* latch, which is what makes the in-presentation capture race-free. */
|
|
52
|
+
frameCommitted(frame: number): boolean;
|
|
53
|
+
/** What the outstanding readiness claims are waiting on, for the timeout
|
|
54
|
+
* message: each label with its claim's own description. */
|
|
55
|
+
waitingOn(): readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* Arm one exact in-draw snapshot before `seek(frame)`.
|
|
58
|
+
*
|
|
59
|
+
* The stage resolves it only from ShotRuntime's matching onPresented call,
|
|
60
|
+
* after advance() drew and while the unpreserved default framebuffer is
|
|
61
|
+
* still readable. It also requires export framing, the matching DOM commit
|
|
62
|
+
* marker, and the exact composition-sized backing store. A second arm is a
|
|
63
|
+
* refusal rather than two consumers racing for one framebuffer.
|
|
64
|
+
*/
|
|
65
|
+
armCapture(
|
|
66
|
+
frame: number,
|
|
67
|
+
size: { width: number; height: number },
|
|
68
|
+
snapshot: (canvas: HTMLCanvasElement) => Promise<ImageBitmap>,
|
|
69
|
+
): Promise<ImageBitmap>;
|
|
70
|
+
/** Reject the currently armed capture, if any. */
|
|
71
|
+
cancelCapture(error: Error): void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ShotExportDeps {
|
|
75
|
+
createWorker(): WorkerLike;
|
|
76
|
+
toBitmap(canvas: HTMLCanvasElement): Promise<ImageBitmap>;
|
|
77
|
+
/** Run `fn` after `ms`, returning a cancel. The only timer this lane needs:
|
|
78
|
+
* one refusal deadline per armed frame. */
|
|
79
|
+
after(ms: number, fn: () => void): () => void;
|
|
80
|
+
/** A monotonic clock, for charging a frame's deadline visible time only. */
|
|
81
|
+
now(): number;
|
|
82
|
+
/** Whether the page is hidden (another tab in front, minimised). */
|
|
83
|
+
hidden(): boolean;
|
|
84
|
+
/** Subscribe to visibility changes; returns the unsubscribe. */
|
|
85
|
+
onVisibilityChange(listener: () => void): () => void;
|
|
86
|
+
/** An encode worker failed (its `error` event): recorded beside the
|
|
87
|
+
* export's own refusal for the stage's health (bridge v35). */
|
|
88
|
+
onWorkerError?(message: string): void;
|
|
89
|
+
/** Scale a captured frame to `width`x`height` and encode it as `type`. The
|
|
90
|
+
* caller closes the bitmap. */
|
|
91
|
+
encodeStill(
|
|
92
|
+
bitmap: ImageBitmap,
|
|
93
|
+
width: number,
|
|
94
|
+
height: number,
|
|
95
|
+
type: string,
|
|
96
|
+
quality: number | undefined,
|
|
97
|
+
): Promise<Blob>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export interface ShotExportMovieOptions {
|
|
101
|
+
/** Absent = mp4. A GIF carries no audio and takes no bitrate or quantizer. */
|
|
102
|
+
format?: "mp4" | "gif" | undefined;
|
|
103
|
+
sceneId?: string | undefined;
|
|
104
|
+
/** Master frames of the whole timeline to export (the work area). Never
|
|
105
|
+
* with `sceneId`. Absent = the whole scope. */
|
|
106
|
+
window?: { start: number; frames: number } | undefined;
|
|
107
|
+
videoBitrate?: number | undefined;
|
|
108
|
+
/** The fixed H.264 QP the worker encodes at where a hardware encoder
|
|
109
|
+
* honours one (`MovieJob.quantizer`). */
|
|
110
|
+
quantizer?: number | undefined;
|
|
111
|
+
audio?: { sampleRate: number; channels: Float32Array[] } | undefined;
|
|
112
|
+
onProgress?: ((framesDone: number, totalFrames: number) => void) | undefined;
|
|
113
|
+
signal?: AbortSignal | undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** One exact still of the whole timeline, delivered at `width`x`height`. */
|
|
117
|
+
export interface ShotStillOptions {
|
|
118
|
+
/** A MASTER frame of the whole timeline. */
|
|
119
|
+
frame: number;
|
|
120
|
+
width: number;
|
|
121
|
+
height: number;
|
|
122
|
+
type: "image/png" | "image/jpeg" | "image/webp";
|
|
123
|
+
/** Lossy encoders only. Absent = the browser's default. */
|
|
124
|
+
quality?: number | undefined;
|
|
125
|
+
signal?: AbortSignal | undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export interface ShotStageExporter {
|
|
129
|
+
capability(): Promise<Tier1Capability>;
|
|
130
|
+
exportMovie(
|
|
131
|
+
opts: ShotExportMovieOptions,
|
|
132
|
+
): Promise<{ blob: Blob; extension: string; audioSkippedReason?: string; encoding?: Mp4Encoding }>;
|
|
133
|
+
/** The same export framing and in-draw capture as one movie frame, for the
|
|
134
|
+
* dashboard poster and the storyboard's "frame from shot". Rejects with the
|
|
135
|
+
* reason, and restores the stage on every path. */
|
|
136
|
+
captureStill(opts: ShotStillOptions): Promise<Blob>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The first frame, and the first frame of every later scene, may re-raster
|
|
140
|
+
* every text layer at the export scale, parse models, link programs, decode
|
|
141
|
+
* images and settle video seeks (on frame 0, on top of the framing swap).
|
|
142
|
+
* This uses the still lane's cold-start budget (settleShotStill's 60 s), not a
|
|
143
|
+
* per-frame one. */
|
|
144
|
+
const FIRST_FRAME_TIMEOUT_MS = 60_000;
|
|
145
|
+
const FRAME_TIMEOUT_MS = 20_000;
|
|
146
|
+
const CAPABILITY_TIMEOUT_MS = 20_000;
|
|
147
|
+
/** One inactivity window for worker startup, one encoded frame, or finalize.
|
|
148
|
+
* The worker may encode a long audio mix during startup and a software-backed
|
|
149
|
+
* 4K frame can be legitimately slow, so this is deliberately much larger
|
|
150
|
+
* than the presentation deadline. It is still finite: a codec promise that
|
|
151
|
+
* never settles must restore the editor rather than own it forever. A GIF's
|
|
152
|
+
* finish encodes the whole film and reports each frame ("finishing"), and
|
|
153
|
+
* every report starts the window again, so a long GIF is not refused for
|
|
154
|
+
* being long. */
|
|
155
|
+
const WORKER_OPERATION_TIMEOUT_MS = 10 * 60_000;
|
|
156
|
+
|
|
157
|
+
function defaultDeps(): ShotExportDeps {
|
|
158
|
+
return {
|
|
159
|
+
createWorker: () =>
|
|
160
|
+
new Worker(new URL("./exportWorker.ts", import.meta.url), {
|
|
161
|
+
type: "module",
|
|
162
|
+
}) as unknown as WorkerLike,
|
|
163
|
+
toBitmap: (canvas) => createImageBitmap(canvas),
|
|
164
|
+
after: (ms, fn) => {
|
|
165
|
+
const timer = setTimeout(fn, ms);
|
|
166
|
+
return () => clearTimeout(timer);
|
|
167
|
+
},
|
|
168
|
+
now: () => performance.now(),
|
|
169
|
+
// "hidden" only: jsdom and prerendered pages report other states, and a
|
|
170
|
+
// deadline that never ran would be a hang rather than a pause.
|
|
171
|
+
hidden: () => typeof document !== "undefined" && document.visibilityState === "hidden",
|
|
172
|
+
onVisibilityChange: (listener) => {
|
|
173
|
+
if (typeof document === "undefined") return () => undefined;
|
|
174
|
+
document.addEventListener("visibilitychange", listener);
|
|
175
|
+
return () => document.removeEventListener("visibilitychange", listener);
|
|
176
|
+
},
|
|
177
|
+
encodeStill: async (bitmap, width, height, type, quality) => {
|
|
178
|
+
const canvas = new OffscreenCanvas(width, height);
|
|
179
|
+
const ctx = canvas.getContext("2d");
|
|
180
|
+
if (ctx === null) throw new Error("a 2d canvas is unavailable, so the still could not be encoded");
|
|
181
|
+
// A poster is a third of the design width: the default low-quality
|
|
182
|
+
// (bilinear) filter aliases at that ratio.
|
|
183
|
+
ctx.imageSmoothingQuality = "high";
|
|
184
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
185
|
+
return canvas.convertToBlob(quality === undefined ? { type } : { type, quality });
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** A channel's exact bytes as a standalone ArrayBuffer for the transfer
|
|
191
|
+
* list. The mix's Float32Array may be a view into a larger buffer.
|
|
192
|
+
*
|
|
193
|
+
* The mix is made in the CHROME's realm and this runs in the stage iframe's,
|
|
194
|
+
* so `instanceof ArrayBuffer` (this realm's constructor) was always false and
|
|
195
|
+
* every export copied its whole mix first. The brand check works across
|
|
196
|
+
* realms, and still refuses a SharedArrayBuffer, which cannot be transferred.
|
|
197
|
+
* The mix is made fresh for each export and not read after it is handed
|
|
198
|
+
* over, so transferring it (which detaches it in the chrome) is safe. */
|
|
199
|
+
export function transferableChannel(ch: Float32Array): ArrayBuffer {
|
|
200
|
+
if (
|
|
201
|
+
ch.byteOffset === 0 &&
|
|
202
|
+
ch.byteLength === ch.buffer.byteLength &&
|
|
203
|
+
Object.prototype.toString.call(ch.buffer) === "[object ArrayBuffer]"
|
|
204
|
+
) {
|
|
205
|
+
return ch.buffer as ArrayBuffer;
|
|
206
|
+
}
|
|
207
|
+
return ch.slice().buffer as ArrayBuffer;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export function createShotStageExporter(
|
|
211
|
+
host: ShotExportHost,
|
|
212
|
+
overrides: Partial<ShotExportDeps> = {},
|
|
213
|
+
): ShotStageExporter {
|
|
214
|
+
const deps: ShotExportDeps = { ...defaultDeps(), ...overrides };
|
|
215
|
+
const capabilityCache = new Map<string, Tier1Capability>();
|
|
216
|
+
/** A started encode worker, its failures reported beside the refusal the
|
|
217
|
+
* conversation raises (`onWorkerError`). A fake without events is left
|
|
218
|
+
* as it is. */
|
|
219
|
+
const startWorker = (): WorkerLike => {
|
|
220
|
+
const worker = deps.createWorker();
|
|
221
|
+
const target = worker as unknown as { addEventListener?: (type: string, listener: (event: unknown) => void) => void };
|
|
222
|
+
target.addEventListener?.("error", (event) => {
|
|
223
|
+
const message = (event as { message?: unknown }).message;
|
|
224
|
+
deps.onWorkerError?.(`the export worker failed: ${typeof message === "string" && message !== "" ? message : "its script did not load or threw"}`);
|
|
225
|
+
});
|
|
226
|
+
return worker;
|
|
227
|
+
};
|
|
228
|
+
let active = false;
|
|
229
|
+
|
|
230
|
+
function requireComposition(): { width: number; height: number; fps: Rational } {
|
|
231
|
+
const comp = host.composition();
|
|
232
|
+
if (!comp) throw new Error("export needs a loaded shot");
|
|
233
|
+
return comp;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* `after`, charging VISIBLE time only. A hidden tab stops rAF, the
|
|
238
|
+
* ResizeObserver the framing swap waits on, and the rAF-polled completion of
|
|
239
|
+
* a WebGL program link, so no frame can present while the author has another
|
|
240
|
+
* tab in front. Counting that time refused exports the author had merely
|
|
241
|
+
* stopped watching; paused, the export simply resumes when they come back.
|
|
242
|
+
*/
|
|
243
|
+
function visibleDeadline(ms: number, fn: () => void): () => void {
|
|
244
|
+
let spent = 0;
|
|
245
|
+
let visibleSince: number | null = deps.hidden() ? null : deps.now();
|
|
246
|
+
let cancelTimer = (): void => {};
|
|
247
|
+
let finished = false;
|
|
248
|
+
const schedule = (): void => {
|
|
249
|
+
cancelTimer();
|
|
250
|
+
cancelTimer = () => {};
|
|
251
|
+
if (visibleSince === null) return;
|
|
252
|
+
cancelTimer = deps.after(Math.max(0, ms - spent), () => {
|
|
253
|
+
if (finished) return;
|
|
254
|
+
finished = true;
|
|
255
|
+
unsubscribe();
|
|
256
|
+
fn();
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
const unsubscribe = deps.onVisibilityChange(() => {
|
|
260
|
+
if (finished) return;
|
|
261
|
+
if (deps.hidden()) {
|
|
262
|
+
if (visibleSince === null) return;
|
|
263
|
+
spent += deps.now() - visibleSince;
|
|
264
|
+
visibleSince = null;
|
|
265
|
+
} else {
|
|
266
|
+
if (visibleSince !== null) return;
|
|
267
|
+
visibleSince = deps.now();
|
|
268
|
+
}
|
|
269
|
+
schedule();
|
|
270
|
+
});
|
|
271
|
+
schedule();
|
|
272
|
+
return () => {
|
|
273
|
+
finished = true;
|
|
274
|
+
cancelTimer();
|
|
275
|
+
unsubscribe();
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Arm a bounded capture and retain the old gate's actionable timeout. */
|
|
280
|
+
function captureFrame(
|
|
281
|
+
frame: number,
|
|
282
|
+
timeoutMs: number,
|
|
283
|
+
comp: { width: number; height: number },
|
|
284
|
+
signal: AbortSignal | undefined,
|
|
285
|
+
): Promise<ImageBitmap> {
|
|
286
|
+
return new Promise<ImageBitmap>((resolve, reject) => {
|
|
287
|
+
let done = false;
|
|
288
|
+
const capture = host.armCapture(frame, comp, deps.toBitmap);
|
|
289
|
+
let cancelDeadline = (): void => {};
|
|
290
|
+
const cleanup = (): void => {
|
|
291
|
+
cancelDeadline();
|
|
292
|
+
signal?.removeEventListener("abort", onAbort);
|
|
293
|
+
};
|
|
294
|
+
const cancel = (error: Error): void => {
|
|
295
|
+
if (done) return;
|
|
296
|
+
done = true;
|
|
297
|
+
cleanup();
|
|
298
|
+
host.cancelCapture(error);
|
|
299
|
+
reject(error);
|
|
300
|
+
};
|
|
301
|
+
const onAbort = (): void => cancel(new Error("export cancelled"));
|
|
302
|
+
cancelDeadline = visibleDeadline(timeoutMs, () => {
|
|
303
|
+
const canvas = host.canvas();
|
|
304
|
+
const size =
|
|
305
|
+
canvas !== null && (canvas.width !== comp.width || canvas.height !== comp.height)
|
|
306
|
+
? `the canvas to reach ${comp.width}x${comp.height} (it is ${canvas.width}x${canvas.height})`
|
|
307
|
+
: "";
|
|
308
|
+
const claims = host.waitingOn().join(", ");
|
|
309
|
+
const commit = host.frameCommitted(frame) ? "" : "the stage to commit";
|
|
310
|
+
const parts = [claims, size, commit].filter(Boolean);
|
|
311
|
+
const error = new Error(
|
|
312
|
+
`export frame ${frame} was not captured within ${Math.round(timeoutMs / 1000)}s of visible time: ` +
|
|
313
|
+
`waiting on ${parts.join("; ") || "the matching presentation"}`,
|
|
314
|
+
);
|
|
315
|
+
cancel(error);
|
|
316
|
+
});
|
|
317
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
318
|
+
if (signal?.aborted) onAbort();
|
|
319
|
+
void capture.then(
|
|
320
|
+
(bitmap) => {
|
|
321
|
+
if (done) {
|
|
322
|
+
bitmap.close();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
done = true;
|
|
326
|
+
cleanup();
|
|
327
|
+
resolve(bitmap);
|
|
328
|
+
},
|
|
329
|
+
(error: unknown) => {
|
|
330
|
+
if (done) return;
|
|
331
|
+
done = true;
|
|
332
|
+
cleanup();
|
|
333
|
+
reject(error);
|
|
334
|
+
},
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** Bound one worker request without weakening converse's single-pending
|
|
340
|
+
* protocol. The caller still owns teardown: a deadline rejects into the
|
|
341
|
+
* existing cancel/terminate/finally path, while a late worker reply is
|
|
342
|
+
* ignored after talk.dispose(). `start` gets `alive`, which starts the
|
|
343
|
+
* window again: the request's own progress, when it reports any. */
|
|
344
|
+
function workerDeadline<T>(start: (alive: () => void) => Promise<T>, message: string): Promise<T> {
|
|
345
|
+
return new Promise<T>((resolve, reject) => {
|
|
346
|
+
let settled = false;
|
|
347
|
+
let cancelDeadline = (): void => {};
|
|
348
|
+
const arm = (): void => {
|
|
349
|
+
cancelDeadline();
|
|
350
|
+
cancelDeadline = deps.after(WORKER_OPERATION_TIMEOUT_MS, () => {
|
|
351
|
+
if (settled) return;
|
|
352
|
+
settled = true;
|
|
353
|
+
reject(new Error(message));
|
|
354
|
+
});
|
|
355
|
+
};
|
|
356
|
+
arm();
|
|
357
|
+
const promise = start(() => {
|
|
358
|
+
if (!settled) arm();
|
|
359
|
+
});
|
|
360
|
+
void promise.then(
|
|
361
|
+
(value) => {
|
|
362
|
+
if (settled) return;
|
|
363
|
+
settled = true;
|
|
364
|
+
cancelDeadline();
|
|
365
|
+
resolve(value);
|
|
366
|
+
},
|
|
367
|
+
(error: unknown) => {
|
|
368
|
+
if (settled) return;
|
|
369
|
+
settled = true;
|
|
370
|
+
cancelDeadline();
|
|
371
|
+
reject(error);
|
|
372
|
+
},
|
|
373
|
+
);
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/** Reserve the lane and flip the stage into export framing for `scope`. */
|
|
378
|
+
function enter(scope: string | undefined): void {
|
|
379
|
+
active = true;
|
|
380
|
+
try {
|
|
381
|
+
host.begin(scope);
|
|
382
|
+
} catch (e) {
|
|
383
|
+
active = false;
|
|
384
|
+
throw e;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Restore the author's stage and free the lane, on every path. */
|
|
389
|
+
function leave(): void {
|
|
390
|
+
try {
|
|
391
|
+
host.end();
|
|
392
|
+
} catch (endError) {
|
|
393
|
+
// A restore that fails must not replace the capture's own outcome: a
|
|
394
|
+
// finished movie or still is still returned, and a failure still reports
|
|
395
|
+
// its own cause. The stage has left export framing regardless (`end`
|
|
396
|
+
// sets that in a finally).
|
|
397
|
+
console.error("[export] restoring the stage after the export failed", endError);
|
|
398
|
+
} finally {
|
|
399
|
+
active = false;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function capability(signal?: AbortSignal): Promise<Tier1Capability> {
|
|
404
|
+
const comp = requireComposition();
|
|
405
|
+
const key = `${comp.width}x${comp.height}`;
|
|
406
|
+
const cached = capabilityCache.get(key);
|
|
407
|
+
if (cached) return cached;
|
|
408
|
+
let worker: WorkerLike;
|
|
409
|
+
try {
|
|
410
|
+
worker = startWorker();
|
|
411
|
+
} catch (e) {
|
|
412
|
+
const reason = `export worker could not start: ${e instanceof Error ? e.message : String(e)}`;
|
|
413
|
+
return {
|
|
414
|
+
mp4: { ok: false, reason },
|
|
415
|
+
pngSequence: { ok: false, reason },
|
|
416
|
+
gif: { ok: false, reason },
|
|
417
|
+
audio: { ok: false, reason },
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const controller = new AbortController();
|
|
421
|
+
let timedOut = false;
|
|
422
|
+
const relayAbort = (): void => controller.abort();
|
|
423
|
+
const cancelDeadline = deps.after(CAPABILITY_TIMEOUT_MS, () => {
|
|
424
|
+
timedOut = true;
|
|
425
|
+
controller.abort();
|
|
426
|
+
});
|
|
427
|
+
signal?.addEventListener("abort", relayAbort, { once: true });
|
|
428
|
+
if (signal?.aborted) relayAbort();
|
|
429
|
+
const talk = converse(worker, controller.signal);
|
|
430
|
+
try {
|
|
431
|
+
const res = await talk.request(
|
|
432
|
+
{ type: "probe", width: comp.width, height: comp.height },
|
|
433
|
+
["capability"],
|
|
434
|
+
);
|
|
435
|
+
capabilityCache.set(key, res.capability);
|
|
436
|
+
return res.capability;
|
|
437
|
+
} catch (e) {
|
|
438
|
+
const reason = timedOut
|
|
439
|
+
? CAPABILITY_PROBE_TIMED_OUT
|
|
440
|
+
: signal?.aborted
|
|
441
|
+
? "export cancelled"
|
|
442
|
+
: e instanceof Error
|
|
443
|
+
? e.message
|
|
444
|
+
: String(e);
|
|
445
|
+
return {
|
|
446
|
+
mp4: { ok: false, reason },
|
|
447
|
+
pngSequence: { ok: false, reason },
|
|
448
|
+
gif: { ok: false, reason },
|
|
449
|
+
audio: { ok: false, reason },
|
|
450
|
+
};
|
|
451
|
+
} finally {
|
|
452
|
+
cancelDeadline();
|
|
453
|
+
signal?.removeEventListener("abort", relayAbort);
|
|
454
|
+
talk.dispose();
|
|
455
|
+
worker.terminate();
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
return {
|
|
460
|
+
capability,
|
|
461
|
+
|
|
462
|
+
async exportMovie({ format, sceneId, window, videoBitrate, quantizer, audio, onProgress, signal }) {
|
|
463
|
+
const gif = format === "gif";
|
|
464
|
+
if (active) throw new Error("an export is already running");
|
|
465
|
+
const comp = requireComposition();
|
|
466
|
+
const scope = sceneId === undefined || sceneId === "" ? undefined : sceneId;
|
|
467
|
+
const scopeTotal = host.scopeFrames(scope);
|
|
468
|
+
if (scopeTotal === null) throw new Error(`unknown scene '${sceneId}'`);
|
|
469
|
+
if (window !== undefined) {
|
|
470
|
+
if (scope !== undefined) throw new Error("a frame window is for the whole Project, not one Shot");
|
|
471
|
+
const { start, frames } = window;
|
|
472
|
+
if (!Number.isSafeInteger(start) || !Number.isSafeInteger(frames) || start < 0 || frames < 1 || start + frames > scopeTotal) {
|
|
473
|
+
throw new Error(`frames ${start}..${start + frames} are outside this Project's timeline (0..${scopeTotal})`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
// The first master frame and how many follow it.
|
|
477
|
+
const first = window?.start ?? 0;
|
|
478
|
+
const total = window?.frames ?? scopeTotal;
|
|
479
|
+
if (total <= 0) throw new Error("the export scope has no frames");
|
|
480
|
+
|
|
481
|
+
// Reserve the lane and enter export framing BEFORE the optional async
|
|
482
|
+
// audio probe. That defers chrome document/playhead pushes while the
|
|
483
|
+
// sampled composition and duration are in use, and a second export
|
|
484
|
+
// cannot pass the guard during the probe.
|
|
485
|
+
enter(scope);
|
|
486
|
+
try {
|
|
487
|
+
// Audio is gated on the probe BEFORE any capture work: an unencodable-
|
|
488
|
+
// audio browser ships silently WITH the reason (the dialog renders it),
|
|
489
|
+
// never silently-silently, and never crashes mid-encode instead.
|
|
490
|
+
let audioJob: { sampleRate: number; channels: ArrayBuffer[] } | undefined;
|
|
491
|
+
let audioSkippedReason: string | undefined;
|
|
492
|
+
// A GIF has no audio track: the dialog never offers one, and a mix
|
|
493
|
+
// handed here anyway has nowhere to go.
|
|
494
|
+
if (audio && !gif) {
|
|
495
|
+
const cap = await capability(signal);
|
|
496
|
+
if (signal?.aborted) throw new Error("export cancelled");
|
|
497
|
+
if (!cap.mp4.ok) {
|
|
498
|
+
throw new Error(cap.mp4.reason ?? "this browser cannot encode MP4");
|
|
499
|
+
}
|
|
500
|
+
if (!cap.audio.ok) {
|
|
501
|
+
audioSkippedReason = cap.audio.reason;
|
|
502
|
+
} else {
|
|
503
|
+
audioJob = {
|
|
504
|
+
sampleRate: audio.sampleRate,
|
|
505
|
+
channels: audio.channels.map(transferableChannel),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// A GIF faster than 50 fps is written at a fraction of the rate,
|
|
511
|
+
// every `stride`-th frame (`gifFrameStride`): only those are captured,
|
|
512
|
+
// and the encoder is handed an ordinary film at the GIF's rate.
|
|
513
|
+
const stride = gif ? gifFrameStride(comp.fps) : 1;
|
|
514
|
+
const count = Math.ceil(total / stride);
|
|
515
|
+
const worker = startWorker();
|
|
516
|
+
const talk = converse(worker, signal);
|
|
517
|
+
try {
|
|
518
|
+
const started = await workerDeadline(
|
|
519
|
+
() =>
|
|
520
|
+
talk.request(
|
|
521
|
+
{
|
|
522
|
+
type: "start",
|
|
523
|
+
kind: gif ? "gif" : "mp4",
|
|
524
|
+
width: comp.width,
|
|
525
|
+
height: comp.height,
|
|
526
|
+
fps: gif ? gifRate(comp.fps) : comp.fps,
|
|
527
|
+
totalFrames: count,
|
|
528
|
+
...(audioJob ? { audio: audioJob } : {}),
|
|
529
|
+
...(videoBitrate !== undefined && !gif ? { videoBitrate } : {}),
|
|
530
|
+
...(quantizer !== undefined && !gif ? { quantizer } : {}),
|
|
531
|
+
},
|
|
532
|
+
["started"],
|
|
533
|
+
audioJob?.channels,
|
|
534
|
+
),
|
|
535
|
+
"export encoder did not start within 600 seconds",
|
|
536
|
+
);
|
|
537
|
+
// Every scene's first frame is a cold mount (an export never warms
|
|
538
|
+
// the next scene), so each gets the cold budget, not only frame 0.
|
|
539
|
+
// Decimated, that is the first captured frame at or after it.
|
|
540
|
+
const coldFrames = new Set(
|
|
541
|
+
host
|
|
542
|
+
.sceneStartFrames(scope)
|
|
543
|
+
.filter((start) => start >= first && start < first + total)
|
|
544
|
+
.map((start) => Math.ceil((start - first) / stride)),
|
|
545
|
+
);
|
|
546
|
+
coldFrames.add(0);
|
|
547
|
+
for (let index = 0; index < count; index++) {
|
|
548
|
+
if (signal?.aborted) throw new Error("export cancelled");
|
|
549
|
+
const frame = first + index * stride;
|
|
550
|
+
const bitmapPromise = captureFrame(
|
|
551
|
+
frame,
|
|
552
|
+
coldFrames.has(index) ? FIRST_FRAME_TIMEOUT_MS : FRAME_TIMEOUT_MS,
|
|
553
|
+
comp,
|
|
554
|
+
signal,
|
|
555
|
+
);
|
|
556
|
+
host.seek(frame);
|
|
557
|
+
const bitmap = await bitmapPromise;
|
|
558
|
+
try {
|
|
559
|
+
// The ack resolves when the encoder can take more, providing backpressure.
|
|
560
|
+
const ack = await workerDeadline(
|
|
561
|
+
() =>
|
|
562
|
+
talk.request(
|
|
563
|
+
{ type: "frame", index, bitmap },
|
|
564
|
+
["frameDone"],
|
|
565
|
+
[bitmap],
|
|
566
|
+
),
|
|
567
|
+
`export frame ${frame} was captured, but the encoder did not acknowledge it within 600 seconds`,
|
|
568
|
+
);
|
|
569
|
+
if (ack.index !== index) {
|
|
570
|
+
throw new Error(
|
|
571
|
+
`export worker protocol violation: frame ${index} was acknowledged as ${ack.index}`,
|
|
572
|
+
);
|
|
573
|
+
}
|
|
574
|
+
} finally {
|
|
575
|
+
// If converse refuses before postMessage (an already-aborted
|
|
576
|
+
// signal or dead channel), ownership never left this thread.
|
|
577
|
+
// close() is also harmless after a successful transfer.
|
|
578
|
+
bitmap.close();
|
|
579
|
+
}
|
|
580
|
+
onProgress?.(index + 1, count);
|
|
581
|
+
}
|
|
582
|
+
const done = await workerDeadline(
|
|
583
|
+
(alive) => talk.request({ type: "finish" }, ["done"], undefined, () => alive()),
|
|
584
|
+
"export encoder made no progress finishing the file for 600 seconds",
|
|
585
|
+
);
|
|
586
|
+
return {
|
|
587
|
+
blob: new Blob([done.data], { type: done.mimeType }),
|
|
588
|
+
extension: done.extension,
|
|
589
|
+
...(audioSkippedReason !== undefined ? { audioSkippedReason } : {}),
|
|
590
|
+
...(started.encoding !== undefined ? { encoding: started.encoding } : {}),
|
|
591
|
+
};
|
|
592
|
+
} catch (e) {
|
|
593
|
+
// Best-effort worker-side teardown; terminate() below is the backstop.
|
|
594
|
+
try {
|
|
595
|
+
worker.postMessage({ type: "cancel" });
|
|
596
|
+
} catch {
|
|
597
|
+
// worker already dead
|
|
598
|
+
}
|
|
599
|
+
throw e;
|
|
600
|
+
} finally {
|
|
601
|
+
talk.dispose();
|
|
602
|
+
worker.terminate();
|
|
603
|
+
}
|
|
604
|
+
} finally {
|
|
605
|
+
leave();
|
|
606
|
+
}
|
|
607
|
+
},
|
|
608
|
+
|
|
609
|
+
async captureStill({ frame, width, height, type, quality, signal }) {
|
|
610
|
+
if (active) throw new Error("an export is already running");
|
|
611
|
+
const comp = requireComposition();
|
|
612
|
+
const total = host.scopeFrames(undefined) ?? 0;
|
|
613
|
+
if (!Number.isSafeInteger(frame) || frame < 0 || frame >= total) {
|
|
614
|
+
throw new Error(`frame ${frame} is outside this Project's timeline (0..${total - 1})`);
|
|
615
|
+
}
|
|
616
|
+
for (const [name, px] of [["width", width], ["height", height]] as const) {
|
|
617
|
+
if (!Number.isSafeInteger(px) || px < 1) {
|
|
618
|
+
throw new Error(`still ${name} must be a positive whole number of pixels, got ${px}`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
let bitmap: ImageBitmap;
|
|
622
|
+
enter(undefined);
|
|
623
|
+
try {
|
|
624
|
+
// Always the cold budget: the still's scene is usually not the one
|
|
625
|
+
// on screen, and an export never warms ahead.
|
|
626
|
+
const captured = captureFrame(frame, FIRST_FRAME_TIMEOUT_MS, comp, signal);
|
|
627
|
+
host.seek(frame);
|
|
628
|
+
bitmap = await captured;
|
|
629
|
+
} finally {
|
|
630
|
+
// The stage goes back to the author before the encode: scaling and
|
|
631
|
+
// compressing the picture needs the bitmap, not export framing.
|
|
632
|
+
leave();
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
return await deps.encodeStill(bitmap, width, height, type, quality);
|
|
636
|
+
} finally {
|
|
637
|
+
bitmap.close();
|
|
638
|
+
}
|
|
639
|
+
},
|
|
640
|
+
};
|
|
641
|
+
}
|
package/src/stage/tokenColor.ts
CHANGED
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
//
|
|
3
3
|
// Every editor-only object in the working pane is drawn with a MATERIAL colour,
|
|
4
4
|
// and a material colour cannot read a CSS custom property the way an SVG fill
|
|
5
|
-
// can. Resolving one
|
|
6
|
-
//
|
|
7
|
-
// the single
|
|
8
|
-
//
|
|
9
|
-
// Not for GL CLEAR colours: SplitRenderer's clay backdrop is hard-coded because
|
|
10
|
-
// it is written before any style has resolved.
|
|
5
|
+
// can. Resolving one when the appearance-keyed material is built inside the
|
|
6
|
+
// stage document is what lets the gizmo, grid, clear colours and SVG overlay
|
|
7
|
+
// share the single definitions in shotTokens.css instead of carrying literals.
|
|
11
8
|
import { Color } from "three";
|
|
12
9
|
|
|
10
|
+
export const STAGE_THEME_TOKENS_ID = "shot-stage-theme-tokens";
|
|
11
|
+
|
|
12
|
+
/** Read from the editor-only probe when the real stage boot installed it.
|
|
13
|
+
* Tests and isolated component mounts keep the original root fallback. */
|
|
14
|
+
export function tokenValue(name: string, fallback: string): string {
|
|
15
|
+
if (typeof document === "undefined") return fallback;
|
|
16
|
+
const source = document.getElementById(STAGE_THEME_TOKENS_ID) ?? document.documentElement;
|
|
17
|
+
return getComputedStyle(source).getPropertyValue(name).trim() || fallback;
|
|
18
|
+
}
|
|
19
|
+
|
|
13
20
|
export function tokenColor(name: string, fallback: string): Color {
|
|
14
21
|
const backstop = new Color(fallback);
|
|
15
|
-
|
|
16
|
-
const token = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
|
|
17
|
-
if (token.length === 0) return backstop;
|
|
22
|
+
const token = tokenValue(name, fallback);
|
|
18
23
|
try {
|
|
19
24
|
return new Color(token);
|
|
20
25
|
} catch {
|