@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
|
@@ -1,110 +1,572 @@
|
|
|
1
|
-
// The
|
|
1
|
+
// The top bar: which document, which workspace, and what state it is in.
|
|
2
2
|
//
|
|
3
3
|
// Everything here reads the store and writes through it. No panel holds a copy
|
|
4
4
|
// of the document, so there is no second place for the open document's identity
|
|
5
5
|
// to live and drift.
|
|
6
|
-
|
|
6
|
+
//
|
|
7
|
+
// ── NO TRANSPORT UP HERE ─────────────────────────────────────────────────
|
|
8
|
+
//
|
|
9
|
+
// It used to carry a play button and a `frame / total` readout that were the
|
|
10
|
+
// timeline dock's, character for character (TimelineDock.tsx's `.dock-play` and
|
|
11
|
+
// `DockReadout`). The dock's head strip is OUTSIDE `.dock-body`, and only the
|
|
12
|
+
// body takes the `hidden` attribute on collapse, so both stay on screen at
|
|
13
|
+
// every moment the header's pair did. Two controls that cannot disagree are
|
|
14
|
+
// still two controls to find.
|
|
15
|
+
//
|
|
16
|
+
// What did NOT move to the dock is the playback CLOCK, which was living in that
|
|
17
|
+
// transport bar and is not chrome at all. See `usePlaybackClock`.
|
|
18
|
+
import { useEffect, useRef, type ReactNode } from "react";
|
|
19
|
+
import { PanelBoundary, SegmentedControl } from "@dailies-studio/ui";
|
|
20
|
+
import { frameToPx } from "@dailies-studio/timeline";
|
|
21
|
+
import { DEFAULT_SHOT_FPS } from "@dailies-studio/shot/doc";
|
|
22
|
+
import { defClockFrames, playbackLoops, playbackWindow } from "../../state/types.js";
|
|
23
|
+
import type { ShotEditorState } from "../../state/types.js";
|
|
24
|
+
// Re-exported where it has always been imported from; see `playbackWindow`.
|
|
25
|
+
export { playbackWindow };
|
|
26
|
+
import { refreshAgentCredentials } from "../../state/agentActions.js";
|
|
27
|
+
import { agentInEditor } from "../agent/agentGate.js";
|
|
7
28
|
import { useShotEditor, useStore } from "../../state/context.js";
|
|
8
|
-
import {
|
|
29
|
+
import { shortcutHint } from "../../state/shortcuts.js";
|
|
30
|
+
import { VIEW_ONLY, exportBlocker, workspaceOf } from "../../state/workspace.js";
|
|
31
|
+
import { COMPONENT_ICON, DOWNLOAD_ICON, STORYBOARD_ICON, STUDIO_ICON } from "../../icons.js";
|
|
32
|
+
import { useCodeBufferGuard } from "../code/codeBufferGuard.js";
|
|
33
|
+
import { EYE_ICON } from "../comments/parts.js";
|
|
34
|
+
import { InboxButton } from "../comments/InboxButton.js";
|
|
35
|
+
import { AccountAvatar } from "./AccountAvatar.js";
|
|
36
|
+
import { AgentChip } from "./AgentChip.js";
|
|
37
|
+
import { AppMenu } from "./AppMenu.js";
|
|
9
38
|
import { HistoryControls } from "./HistoryControls.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
39
|
+
import { PresenceStack } from "./PresenceStack.js";
|
|
40
|
+
import { ProjectTitle } from "./ProjectTitle.js";
|
|
41
|
+
import { SaveBadges } from "./StatusBadges.js";
|
|
42
|
+
import { ShareButton } from "./ShareButton.js";
|
|
43
|
+
import { SyncRenamedNote } from "./SyncDeletedDialog.js";
|
|
13
44
|
|
|
14
45
|
export function Header() {
|
|
15
46
|
const store = useStore();
|
|
16
47
|
useEffect(() => {
|
|
17
|
-
|
|
48
|
+
// The agent probe, from the header because the header is mounted for the
|
|
49
|
+
// whole session: the right column's Agent tab is gated on its answer, and
|
|
50
|
+
// a probe that ran only once the drawer opened could not answer that. One
|
|
51
|
+
// request per session either way, since the drawer's own effect finds the
|
|
52
|
+
// answer already in the store. No drawer, no probe.
|
|
53
|
+
if (agentInEditor()) void refreshAgentCredentials(store);
|
|
18
54
|
}, [store]);
|
|
19
55
|
|
|
56
|
+
usePlaybackClock();
|
|
57
|
+
|
|
20
58
|
const name = useShotEditor((s) => s.name);
|
|
59
|
+
// The Library is an aria-modal Dialog that starts BELOW this bar
|
|
60
|
+
// (styles/library.css), so the bar stays in sight but must not stay live: a
|
|
61
|
+
// menu opened here would paint under the dialog (--z-menu 1000 against
|
|
62
|
+
// --z-dialog 1100), and a click here would move focus out of its trap, where
|
|
63
|
+
// Escape and Tab no longer reach it. `inert` takes the bar out of pointer,
|
|
64
|
+
// focus and the accessibility tree together, which is what aria-modal claims.
|
|
65
|
+
const libraryOpen = useShotEditor((s) => s.libraryBrowserOpen);
|
|
66
|
+
// The role's verdict: a version preview is read only too, but not a viewer.
|
|
67
|
+
const readOnly = useShotEditor((s) => s.versionPreview?.roleReadOnly ?? s.readOnly);
|
|
21
68
|
|
|
22
69
|
return (
|
|
23
|
-
<header className="shell-header" data-testid="shot-header">
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
70
|
+
<header className="shell-header" data-testid="shot-header" inert={libraryOpen}>
|
|
71
|
+
{/* INSIDE the positioned container, per the boundary's placement rule:
|
|
72
|
+
a catch swaps the contents for the card, never the header's box. */}
|
|
73
|
+
{/* THREE COLUMNS, Paper's top bar:
|
|
74
|
+
|
|
75
|
+
menu │ Projects › Project › Shot │ history ··· Studio | Storyboard ··· saved Export Agent avatar
|
|
76
|
+
|
|
77
|
+
The switch sits in the MIDDLE track of a 1fr auto 1fr grid, so it is
|
|
78
|
+
centred on the window whatever the two ends hold.
|
|
79
|
+
|
|
80
|
+
The Agent chip is the outside agent's door (MCP health and the
|
|
81
|
+
connect rows, AgentChip.tsx), in a boundary of its own so a crash in
|
|
82
|
+
it leaves the bar standing.
|
|
83
|
+
|
|
84
|
+
WHAT IS NOT HERE. The panel toggles are on the canvas bar
|
|
85
|
+
(PanelToggles.tsx), the in-app Agent is the right column's tab, and
|
|
86
|
+
the Library opens from View > Panels and the Storyboard. Zoom and the
|
|
87
|
+
stage's badges are in the canvas bar too, over the picture they
|
|
88
|
+
describe. The monitor meter is in the timeline's meters cell. */}
|
|
89
|
+
<PanelBoundary label="Header">
|
|
90
|
+
<div className="header-start">
|
|
91
|
+
<AppMenu />
|
|
92
|
+
<span className="header-sep" aria-hidden="true" />
|
|
93
|
+
<ProjectTitle />
|
|
94
|
+
{/* "Renamed to X by Maya" (R10), for a few seconds. */}
|
|
95
|
+
<SyncRenamedNote />
|
|
96
|
+
{/* A Viewer's standing note (Paper page 21, "View only"): an edit
|
|
97
|
+
attempt says the same thing in a toast (ViewOnlyToast). */}
|
|
98
|
+
{readOnly && name !== null ? (
|
|
99
|
+
<span className="view-only-pill" data-testid="view-only-pill" title={VIEW_ONLY}>
|
|
100
|
+
{EYE_ICON}
|
|
101
|
+
View only
|
|
102
|
+
</span>
|
|
103
|
+
) : null}
|
|
104
|
+
<span className="header-sep" aria-hidden="true" />
|
|
105
|
+
<HistoryControls />
|
|
106
|
+
</div>
|
|
107
|
+
<div className="header-center">{name === null ? null : <WorkspaceSwitch />}</div>
|
|
108
|
+
<div className="header-end">
|
|
109
|
+
<SaveBadges />
|
|
110
|
+
{/* Who else is here (Paper page 21), live sync only. */}
|
|
111
|
+
<PresenceStack />
|
|
112
|
+
<ShareButton />
|
|
113
|
+
{name !== null && <ExportButton />}
|
|
114
|
+
<AgentChip />
|
|
115
|
+
<InboxButton />
|
|
116
|
+
<AccountAvatar />
|
|
117
|
+
</div>
|
|
118
|
+
</PanelBoundary>
|
|
33
119
|
</header>
|
|
34
120
|
);
|
|
35
121
|
}
|
|
36
122
|
|
|
37
123
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
124
|
+
* Playback: WALL TIME IN, INTEGER FRAMES OUT.
|
|
125
|
+
*
|
|
126
|
+
* It never accumulates frames per tick. `frame++` per rAF makes playback speed
|
|
127
|
+
* a function of the monitor's refresh rate (a 120Hz laptop plays a 30fps shot
|
|
128
|
+
* at double speed) and makes one dropped frame permanently shift everything
|
|
129
|
+
* after it. Deriving the frame from elapsed time instead plays at the
|
|
130
|
+
* document's own rate on any display, drops frames under load rather than
|
|
131
|
+
* slowing down, and puts the same integer frame on screen that the export will
|
|
132
|
+
* render, because the seek target is `floor(elapsed * fps)` at 1x — see
|
|
133
|
+
* `playbackTargetFrame`, which owns that arithmetic and the shuttle's rate
|
|
134
|
+
* along with it.
|
|
135
|
+
*
|
|
136
|
+
* ── THE WALL TIME IS THE REFRESH'S, NOT THE CALLBACK'S ───────────────────
|
|
137
|
+
*
|
|
138
|
+
* A tick reads its time from rAF's own timestamp, the start of the refresh
|
|
139
|
+
* it draws for, on `performance.now()`'s timeline like `playOrigin.at`, and
|
|
140
|
+
* never from `performance.now()` inside the callback. The callback runs late
|
|
141
|
+
* by whatever the frame did before it (a median of 0.3 to 0.7 ms and up to
|
|
142
|
+
* 17.6 ms in the benchmark's headless Chrome), so a frame boundary that fell
|
|
143
|
+
* a few ms after a refresh landed before the reading on one tick and after
|
|
144
|
+
* it on the next: a healthy film skipped a frame and repeated the next,
|
|
145
|
+
* which the render scale controller read as late frames
|
|
146
|
+
* (renderScaleController.ts `presented`). Elapsed time is clamped at zero,
|
|
147
|
+
* since a refresh can begin before the origin it is the first tick of.
|
|
148
|
+
*
|
|
149
|
+
* ── AND THE BOUNDARIES BETWEEN REFRESHES ──────────────────────────────────
|
|
150
|
+
*
|
|
151
|
+
* The timestamps themselves sit 0.05 to 0.35 ms off the display's grid,
|
|
152
|
+
* coarsened to 0.1 ms, so a boundary on a refresh, or that near one, still
|
|
153
|
+
* fell either side of it from tick to tick. A frame period of p/q refreshes
|
|
154
|
+
* meets the refreshes at q phases R/q apart, and `placedBase` moves the
|
|
155
|
+
* boundaries to the middle of those gaps, R/(2q) from the nearest refresh:
|
|
156
|
+
* half a refresh at 60 and 30 fps on 60 Hz, a quarter at 24. A display whose
|
|
157
|
+
* rate drifts against the document's (the headless lane's 59.978 Hz against
|
|
158
|
+
* 60 fps) carries them back toward a refresh, where the wander made a run
|
|
159
|
+
* of skips and repeats of each crossing; so they are kept in the middle,
|
|
160
|
+
* and the drift costs one slip of a refresh at a time instead. The refresh
|
|
161
|
+
* is measured over a run's latest ticks (`refreshOf`) and kept for the next
|
|
162
|
+
* run, so a page's first run places at its `REFRESH_STAMPS`th tick and every
|
|
163
|
+
* later run at its second, once its first interval agrees with the refresh
|
|
164
|
+
* kept (the window may be on another display). The base stays within half a
|
|
165
|
+
* refresh of the origin the sound counts from too (`playOrigin`), and the
|
|
166
|
+
* audio's drift check allows the picture its refresh (audio/preview.ts).
|
|
167
|
+
*
|
|
168
|
+
* IN THE HEADER, which renders none of it. The loop needs a mount that lasts
|
|
169
|
+
* the whole session: it used to ride the header's transport bar, and the
|
|
170
|
+
* obvious home for it now would be the timeline dock, whose play button is the
|
|
171
|
+
* one that starts it. That dock is collapsible, and a clock that unmounts with
|
|
172
|
+
* a panel is a play button that stops working.
|
|
173
|
+
*
|
|
174
|
+
* The window, the fps and the shuttle rate are subscribed as NUMBERS, never as
|
|
175
|
+
* `doc`. This runs inside the header, and a `doc` subscription would re-render
|
|
176
|
+
* every control up there on each keystroke of an edit.
|
|
46
177
|
*/
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
178
|
+
/**
|
|
179
|
+
* The frame a tick should land on: wall time in, integer frame out, wrapped
|
|
180
|
+
* into the window being played.
|
|
181
|
+
*
|
|
182
|
+
* Extracted from the rAF loop so the arithmetic can be tested without a clock.
|
|
183
|
+
* The loop's own half, which instant a tick reads and where its boundaries
|
|
184
|
+
* sit, is held by header.test.tsx through a fake `requestAnimationFrame`.
|
|
185
|
+
*
|
|
186
|
+
* ── FLOOR, NOT ROUND ─────────────────────────────────────────────────────
|
|
187
|
+
*
|
|
188
|
+
* `Math.floor` is what the shipped player (`player/src/stage.ts`) and the
|
|
189
|
+
* legacy editor both use, and this loop was the only transport in the tree
|
|
190
|
+
* rounding instead. Rounding shows frame N from half a frame BEFORE frame N's
|
|
191
|
+
* instant, so the preview ran a consistent half-frame ahead of the same
|
|
192
|
+
* document's export — small, invisible in isolation, and exactly the kind of
|
|
193
|
+
* disagreement that costs an afternoon when someone finally A/Bs a preview
|
|
194
|
+
* against an MP4. Every frame it draws was always exact; only which instant
|
|
195
|
+
* chose it has changed.
|
|
196
|
+
*
|
|
197
|
+
* ── OFFSET-FORM WRAP ─────────────────────────────────────────────────────
|
|
198
|
+
*
|
|
199
|
+
* A bare `target % total` is right only for a window that begins at 0: inside a
|
|
200
|
+
* shot at [90, 120) it wraps frame 95 to 5, which is a different shot — and
|
|
201
|
+
* `seek`'s clamp would then pin the playhead on frame 90 forever, so playback
|
|
202
|
+
* would look frozen rather than wrong. The same form is what lets a REVERSE
|
|
203
|
+
* rate wrap off the front of the window onto its last frame instead of running
|
|
204
|
+
* to a negative one the clamp would freeze on.
|
|
205
|
+
*
|
|
206
|
+
* ── AND THE START HAS TO BE IN THE WINDOW FOR THAT FORM TO MEAN ANYTHING ─
|
|
207
|
+
*
|
|
208
|
+
* The wrap is a modulus, so it answers for ANY start frame — including one
|
|
209
|
+
* outside the window entirely, which is the ordinary case for a WORK AREA: the
|
|
210
|
+
* bracket is set with ⌥I/⌥O and the playhead is then free to wander off it. Hit
|
|
211
|
+
* play with the playhead at frame 10 and a bracket of [100, 140) and the first
|
|
212
|
+
* tick landed on `100 + ((10 - 100) mod 40)` = frame 130, which is an arbitrary
|
|
213
|
+
* frame three quarters of the way through the loop. It looked like the
|
|
214
|
+
* transport had jumped somewhere random, because it had.
|
|
215
|
+
*
|
|
216
|
+
* So a start OUTSIDE the window becomes the window's IN POINT, which is what
|
|
217
|
+
* pressing play on a bracket means in every editor that has one. A start
|
|
218
|
+
* already inside is untouched, so the shot and definition arms — whose windows
|
|
219
|
+
* always contain the clock they are driving — are unchanged frame for frame.
|
|
220
|
+
*
|
|
221
|
+
* ── THE RATE IS APPLIED TO THE MAGNITUDE, THEN THE SIGN ──────────────────
|
|
222
|
+
*
|
|
223
|
+
* `floor(elapsed * fps * rate)` would be wrong in reverse and silently so:
|
|
224
|
+
* flooring a negative product is `-ceil(|product|)`, which steps back a frame
|
|
225
|
+
* the instant the clock moves at all, so reverse would run a consistent frame
|
|
226
|
+
* AHEAD of forward over the same wall time. Flooring the magnitude and then
|
|
227
|
+
* applying the sign keeps every frame shown from its own instant in both
|
|
228
|
+
* directions, which is the property the floor was chosen for.
|
|
229
|
+
*
|
|
230
|
+
* The default of 1 is not politeness: every caller that is not the shuttle —
|
|
231
|
+
* and every existing test — means 1x, and a required argument would let a new
|
|
232
|
+
* one guess.
|
|
233
|
+
*
|
|
234
|
+
* ── WITH THE LOOP OFF (`loop: false`, the Project context's Loop button) ──
|
|
235
|
+
*
|
|
236
|
+
* The target is CLAMPED to the window instead of wrapped, and the clock stops
|
|
237
|
+
* once it reads the far edge (`playbackEndFrame`). A start already ON that far
|
|
238
|
+
* edge plays again from the opposite one, which is what Play means on a
|
|
239
|
+
* transport that stopped at the end.
|
|
240
|
+
*/
|
|
241
|
+
export function playbackTargetFrame(
|
|
242
|
+
startedFrame: number,
|
|
243
|
+
windowStart: number,
|
|
244
|
+
windowFrames: number,
|
|
245
|
+
fps: number,
|
|
246
|
+
elapsedSeconds: number,
|
|
247
|
+
rate: number = 1,
|
|
248
|
+
loop: boolean = true,
|
|
249
|
+
): number {
|
|
250
|
+
const steps = Math.floor(elapsedSeconds * fps * Math.abs(rate));
|
|
251
|
+
const last = windowStart + windowFrames - 1;
|
|
252
|
+
// See the header: outside the window, playback begins at the in point.
|
|
253
|
+
let from =
|
|
254
|
+
startedFrame >= windowStart && startedFrame < windowStart + windowFrames
|
|
255
|
+
? startedFrame
|
|
256
|
+
: windowStart;
|
|
257
|
+
if (!loop && from === playbackEndFrame(windowStart, windowFrames, rate)) {
|
|
258
|
+
from = rate < 0 ? last : windowStart;
|
|
259
|
+
}
|
|
260
|
+
const target = from + (rate < 0 ? -steps : steps);
|
|
261
|
+
if (!loop) return Math.min(last, Math.max(windowStart, target));
|
|
63
262
|
return (
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
<button
|
|
67
|
-
key={label}
|
|
68
|
-
type="button"
|
|
69
|
-
className={`btn btn-icon${on ? " is-active" : ""}`}
|
|
70
|
-
aria-pressed={on}
|
|
71
|
-
title={`${on ? "Hide" : "Show"} ${label.toLowerCase()}${key === "" ? "" : ` (${key})`}`}
|
|
72
|
-
onClick={run}
|
|
73
|
-
>
|
|
74
|
-
{glyph}
|
|
75
|
-
</button>
|
|
76
|
-
))}
|
|
77
|
-
<button
|
|
78
|
-
type="button"
|
|
79
|
-
className="btn btn-icon"
|
|
80
|
-
title="Hide every panel (.)"
|
|
81
|
-
onClick={() => store.togglePanels()}
|
|
82
|
-
>
|
|
83
|
-
⛶
|
|
84
|
-
</button>
|
|
85
|
-
</div>
|
|
263
|
+
windowStart +
|
|
264
|
+
((((target - windowStart) % windowFrames) + windowFrames) % windowFrames)
|
|
86
265
|
);
|
|
87
266
|
}
|
|
88
267
|
|
|
89
|
-
/**
|
|
90
|
-
*
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
268
|
+
/** The frame a non-looping run stops on: the window's last frame forward, its
|
|
269
|
+
* first in reverse. */
|
|
270
|
+
export function playbackEndFrame(
|
|
271
|
+
windowStart: number,
|
|
272
|
+
windowFrames: number,
|
|
273
|
+
rate: number = 1,
|
|
274
|
+
): number {
|
|
275
|
+
return rate < 0 ? windowStart : windowStart + windowFrames - 1;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/** The latest tick timestamps of a run `refreshOf` measures over. */
|
|
279
|
+
export const REFRESH_STAMPS = 16;
|
|
280
|
+
/** An interval between two ticks this long, ms, is a stall or a hidden tab,
|
|
281
|
+
* and measuring starts again after it. */
|
|
282
|
+
const REFRESH_GAP_MS = 100;
|
|
283
|
+
/** How near a run's own interval must come to the refresh an earlier run
|
|
284
|
+
* measured, as a share of it, before that refresh places this run's
|
|
285
|
+
* boundaries: the window may be on another display now. */
|
|
286
|
+
const REFRESH_AGREES = 0.05;
|
|
287
|
+
/** The largest q `boundaryGap` looks for in a frame period of p/q refreshes:
|
|
288
|
+
* past it the gaps are too narrow for the timestamps' wander. */
|
|
289
|
+
const PATTERN_MAX = 5;
|
|
290
|
+
/** How far the measured ratio may sit from p/q, as a share of it, and still
|
|
291
|
+
* be that pattern: room for a rate a thousandth off its nominal (29.97 fps,
|
|
292
|
+
* a 59.94 Hz display) and for `refreshOf`'s own error. */
|
|
293
|
+
const PATTERN_TOLERANCE = 0.003;
|
|
294
|
+
/** The narrowest gap, as a share of the refresh, whose boundaries are kept
|
|
295
|
+
* clear as the display drifts (q up to 2): a narrower gap's quarter is
|
|
296
|
+
* within the timestamps' wander, and a ratio the estimate took for such a
|
|
297
|
+
* pattern may be none (25 fps on 144 Hz, 5.76 refreshes, is within 0.2% of
|
|
298
|
+
* 23/4), and keeping it clear would slip it again and again. */
|
|
299
|
+
const KEPT_GAP = 0.5;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* The display's refresh interval, ms, from a run's latest tick timestamps:
|
|
303
|
+
* their span over the whole number of refreshes in it, counted with the
|
|
304
|
+
* median interval (one refresh, unless the main thread missed most). Null
|
|
305
|
+
* until there are `REFRESH_STAMPS` of them: one interval carries a
|
|
306
|
+
* timestamp's wander, 2% of a 165 Hz refresh, where fifteen carry a
|
|
307
|
+
* fifteenth of it.
|
|
308
|
+
*/
|
|
309
|
+
export function refreshOf(stamps: readonly number[]): number | null {
|
|
310
|
+
if (stamps.length < REFRESH_STAMPS) return null;
|
|
311
|
+
const intervals = stamps.slice(1).map((at, i) => at - stamps[i]!).sort((a, b) => a - b);
|
|
312
|
+
const span = stamps[stamps.length - 1]! - stamps[0]!;
|
|
313
|
+
return span / Math.round(span / intervals[intervals.length >> 1]!);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** The gap, ms, between the phases at which the boundaries of a frame period
|
|
317
|
+
* of p/q refreshes meet the refreshes, R/q, or null for a ratio that is no
|
|
318
|
+
* such fraction with q up to `PATTERN_MAX`: its boundaries take every phase
|
|
319
|
+
* in turn, and no shift keeps them clear. */
|
|
320
|
+
export function boundaryGap(refreshMs: number, periodMs: number): number | null {
|
|
321
|
+
const ratio = periodMs / refreshMs;
|
|
322
|
+
for (let q = 1; q <= PATTERN_MAX; q += 1) {
|
|
323
|
+
if (Math.abs(ratio - Math.round(ratio * q) / q) <= PATTERN_TOLERANCE * ratio) return refreshMs / q;
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const mod = (value: number, by: number): number => ((value % by) + by) % by;
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Where a run's frame boundaries sit after a tick at `tickAt`, a refresh:
|
|
332
|
+
* the instant the run counts its frames from, `base`, null until placed.
|
|
333
|
+
*
|
|
334
|
+
* Placed in the middle of the gaps (`boundaryGap`), R/(2q) from the nearest
|
|
335
|
+
* refresh, and kept there for q up to 2: a display whose rate drifts against
|
|
336
|
+
* the document's carries them back toward a refresh, so once one comes
|
|
337
|
+
* within a quarter of a gap they go back to the middle. Neither move takes a
|
|
338
|
+
* boundary across a refresh, so neither changes a frame on screen. The base
|
|
339
|
+
* stays within half a refresh of the origin: when the drift has used that
|
|
340
|
+
* up, the move goes the other way and the boundaries slip a whole refresh,
|
|
341
|
+
* once, which is the two rates' own skip.
|
|
342
|
+
*/
|
|
343
|
+
export function placedBase(base: number | null, originAt: number, tickAt: number, refreshMs: number, periodMs: number): number | null {
|
|
344
|
+
const gap = boundaryGap(refreshMs, periodMs);
|
|
345
|
+
if (gap === null) return base;
|
|
346
|
+
const from = base ?? originAt;
|
|
347
|
+
// From the boundary nearest the tick, so the refresh estimate's error is
|
|
348
|
+
// not multiplied by the run's length.
|
|
349
|
+
const nearest = from + periodMs * Math.round((tickAt - from) / periodMs);
|
|
350
|
+
let centre = mod(gap / 2 - mod(nearest - tickAt, gap), gap);
|
|
351
|
+
if (centre > gap / 2) centre -= gap;
|
|
352
|
+
if (base !== null && (gap < KEPT_GAP * refreshMs || Math.abs(centre) <= gap / 4)) return base;
|
|
353
|
+
const next = from + centre;
|
|
354
|
+
return next - Math.round((next - originAt) / refreshMs) * refreshMs;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* THE WORK AREA, DRAWN. The bracket over the ruler that says what a press of
|
|
359
|
+
* Space will actually loop.
|
|
360
|
+
*
|
|
361
|
+
* ── IT HAD NO REPRESENTATION AT ALL ──────────────────────────────────────
|
|
362
|
+
*
|
|
363
|
+
* ⌥I / ⌥O / ⌥X wrote `workArea`, `playbackWindow` read it, the shortcuts manual
|
|
364
|
+
* described it, and NOTHING on screen showed it. So the only evidence an author
|
|
365
|
+
* had that they had bracketed anything was that playback stopped going where
|
|
366
|
+
* they expected, which reads as a broken transport rather than as a feature
|
|
367
|
+
* they turned on — and ⌥X, the way out, is only findable if you already know
|
|
368
|
+
* the thing exists.
|
|
369
|
+
*
|
|
370
|
+
* ── IN THIS FILE BECAUSE THE NUMBERS ARE ─────────────────────────────────
|
|
371
|
+
*
|
|
372
|
+
* It draws `playbackWindow(s)` and not `s.workArea`. Those differ: the window
|
|
373
|
+
* is CLAMPED to the document on every read (a trim can shorten the film under
|
|
374
|
+
* a bracket nobody touched) and falls back to the whole film when the clamp
|
|
375
|
+
* leaves nothing. A bracket painted from the raw field would be a bracket the
|
|
376
|
+
* transport does not honour, which is worse than none — so the two come off the
|
|
377
|
+
* one function, in the one file, and cannot disagree. The dock mounts it into
|
|
378
|
+
* its ruler; see `TimelineDock`.
|
|
379
|
+
*
|
|
380
|
+
* WITHHELD OUTSIDE THE SCENE CONTEXT, the same rule `playbackWindow` and
|
|
381
|
+
* `setWorkAreaEdge` both keep: an entered shot and a component definition each
|
|
382
|
+
* own the transport's window already, so a bracket visible in either would name
|
|
383
|
+
* a loop nothing is running.
|
|
384
|
+
*/
|
|
385
|
+
export function WorkAreaBracket(props: { ppf: number }) {
|
|
386
|
+
const area = useShotEditor((s) => s.workArea);
|
|
387
|
+
const owned = useShotEditor((s) => defClockFrames(s) !== null || s.enteredScene !== null);
|
|
388
|
+
const start = useShotEditor((s) => playbackWindow(s).start);
|
|
389
|
+
const frames = useShotEditor((s) => playbackWindow(s).frames);
|
|
390
|
+
if (area === null || owned || frames <= 0) return null;
|
|
391
|
+
return (
|
|
392
|
+
<span
|
|
393
|
+
className="dock-work-area"
|
|
394
|
+
data-testid="work-area-bracket"
|
|
395
|
+
aria-hidden="true"
|
|
396
|
+
style={{ left: frameToPx(start, props.ppf), width: frameToPx(frames, props.ppf) }}
|
|
397
|
+
/>
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function usePlaybackClock(): void {
|
|
94
402
|
const store = useStore();
|
|
95
|
-
const
|
|
96
|
-
|
|
403
|
+
const playing = useShotEditor((s) => s.playing);
|
|
404
|
+
// The COMPONENT CONTEXT swaps the whole triple (unify P2): total, the seek
|
|
405
|
+
// verb and the starting frame become def-local together, so the same loop
|
|
406
|
+
// wraps a definition's own duration and never touches master time — the
|
|
407
|
+
// scene render, the audio preview and every playhead-paired verb keep the
|
|
408
|
+
// master frame they were on.
|
|
409
|
+
// EITHER SUBJECT, one bit: a definition and a code component both put the
|
|
410
|
+
// stage in the component context, so both swap the triple. `defClockFrames`
|
|
411
|
+
// is the one answer, and it is what `seekDefTo` clamps on too.
|
|
412
|
+
const inDef = useShotEditor((s) => defClockFrames(s) !== null);
|
|
413
|
+
// ── AND THE WINDOW IT LOOPS INSIDE ────────────────────────────────────
|
|
414
|
+
//
|
|
415
|
+
// Two NUMBERS, never `doc` — this file's own rule, and the reason is that a
|
|
416
|
+
// `doc` subscription here re-renders every control in the header on each
|
|
417
|
+
// keystroke of an edit. Both come off ONE function (`playbackWindow`), so a
|
|
418
|
+
// start and a length can never be read from two different subjects, and both
|
|
419
|
+
// are in the effect's deps, so a trim of the entered shot or a moved work
|
|
420
|
+
// area restarts the loop against the window it actually has now.
|
|
421
|
+
const windowStart = useShotEditor((s) => playbackWindow(s).start);
|
|
422
|
+
const total = useShotEditor((s) => playbackWindow(s).frames);
|
|
423
|
+
const fps = useShotEditor((s) =>
|
|
424
|
+
s.doc === null
|
|
425
|
+
? DEFAULT_SHOT_FPS.num / DEFAULT_SHOT_FPS.den
|
|
426
|
+
: s.doc.fps.num / s.doc.fps.den,
|
|
427
|
+
);
|
|
428
|
+
// THE SHUTTLE'S SPEED, read here and nowhere else in the picture lane: the
|
|
429
|
+
// stage is seeked frame by frame either way, so a rate never reaches it and
|
|
430
|
+
// the bridge needs no new message. `setPlaying(true)` resets this to 1, so
|
|
431
|
+
// the loop that a Space bar starts is always 1x.
|
|
432
|
+
const rate = useShotEditor((s) => s.playRate);
|
|
433
|
+
// WRAP OR STOP at the window's end. Always a wrap inside a shot or a
|
|
434
|
+
// definition; the Project context follows the dock's Loop button.
|
|
435
|
+
const loop = useShotEditor(playbackLoops);
|
|
436
|
+
// ── ONE CLOCK FOR PICTURE AND SOUND ────────────────────────────────────
|
|
437
|
+
//
|
|
438
|
+
// `playOrigin` is where and when the transport started, set by the same
|
|
439
|
+
// write that sets `playing`. The audio session anchors on it too
|
|
440
|
+
// (audio/preview.ts), so the loop starts from it rather than from this
|
|
441
|
+
// effect, which runs a React commit later, its boundaries placed at most
|
|
442
|
+
// half a refresh off it. ONCE per origin: a restart for the window, the
|
|
443
|
+
// rate or the loop anchors on its own reading, as before.
|
|
444
|
+
const origin = useShotEditor((s) => s.playOrigin);
|
|
445
|
+
const startedFrom = useRef<ShotEditorState["playOrigin"]>(null);
|
|
446
|
+
// The display's refresh as the last run measured it (`refreshOf`), so a
|
|
447
|
+
// run after the first places its boundaries at its second tick, once its
|
|
448
|
+
// first interval agrees.
|
|
449
|
+
const refresh = useRef<number | null>(null);
|
|
450
|
+
|
|
451
|
+
useEffect(() => {
|
|
452
|
+
// Taken even by a run that cannot start (an empty window), which would
|
|
453
|
+
// find it stale by the time one could.
|
|
454
|
+
const fresh = origin === startedFrom.current ? null : origin;
|
|
455
|
+
startedFrom.current = origin;
|
|
456
|
+
if (!playing || total <= 0) return;
|
|
457
|
+
const startedAt = fresh?.at ?? performance.now();
|
|
458
|
+
const startedFrame = fresh?.frame ?? (inDef ? store.getState().defFrame : store.getState().frame);
|
|
459
|
+
const periodMs = 1000 / (fps * Math.abs(rate));
|
|
460
|
+
let raf = 0;
|
|
461
|
+
const stamps: number[] = [];
|
|
462
|
+
// The instant the run counts from: the origin's own until its boundaries
|
|
463
|
+
// are placed (`placedBase`), at its first tick with a refresh it trusts.
|
|
464
|
+
let base: number | null = null;
|
|
465
|
+
// An earlier run's refresh places this one's boundaries once an interval
|
|
466
|
+
// of this run agrees with it (`REFRESH_AGREES`). Placed on the grid of
|
|
467
|
+
// the display the window was on (60 Hz, a run now on 144 Hz), a base
|
|
468
|
+
// would move by more than a tick, and the playhead step back a frame.
|
|
469
|
+
let trusted = false;
|
|
470
|
+
const tick = (now: number): void => {
|
|
471
|
+
if (stamps.length > 0 && now - stamps[stamps.length - 1]! >= REFRESH_GAP_MS) stamps.length = 0;
|
|
472
|
+
stamps.push(now);
|
|
473
|
+
if (stamps.length > REFRESH_STAMPS) stamps.shift();
|
|
474
|
+
const measured = refreshOf(stamps);
|
|
475
|
+
if (measured !== null) refresh.current = measured;
|
|
476
|
+
const interval = stamps.length > 1 ? now - stamps[stamps.length - 2]! : null;
|
|
477
|
+
trusted ||=
|
|
478
|
+
measured !== null ||
|
|
479
|
+
(interval !== null && refresh.current !== null && Math.abs(interval - refresh.current) <= REFRESH_AGREES * refresh.current);
|
|
480
|
+
if (trusted && refresh.current !== null) base = placedBase(base, startedAt, now, refresh.current, periodMs);
|
|
481
|
+
const elapsed = Math.max(0, now - (base ?? startedAt)) / 1000;
|
|
482
|
+
const wrapped = playbackTargetFrame(
|
|
483
|
+
startedFrame,
|
|
484
|
+
windowStart,
|
|
485
|
+
total,
|
|
486
|
+
fps,
|
|
487
|
+
elapsed,
|
|
488
|
+
rate,
|
|
489
|
+
loop,
|
|
490
|
+
);
|
|
491
|
+
if (inDef) store.seekDef(wrapped);
|
|
492
|
+
else store.seek(wrapped);
|
|
493
|
+
// The end of a run that does not loop: hold the last frame and stop,
|
|
494
|
+
// through `setPlaying` like every other pause.
|
|
495
|
+
if (!loop && wrapped === playbackEndFrame(windowStart, total, rate)) {
|
|
496
|
+
store.setPlaying(false);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
raf = requestAnimationFrame(tick);
|
|
500
|
+
};
|
|
501
|
+
raf = requestAnimationFrame(tick);
|
|
502
|
+
return () => cancelAnimationFrame(raf);
|
|
503
|
+
}, [playing, origin, total, fps, store, inDef, windowStart, rate, loop]);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/** Studio | Storyboard | Components, the workspaces a Project has, centred in
|
|
507
|
+
* the bar.
|
|
508
|
+
*
|
|
509
|
+
* Storyboard is never disabled: from any mode it HANDS OVER
|
|
510
|
+
* (`setStoryboardOpen` closes the material nodes, then leaves Components,
|
|
511
|
+
* each through the exit that records or restores its work), and the one
|
|
512
|
+
* thing that hand-over could lose, Code Studio's unsaved buffers, is asked
|
|
513
|
+
* about first. Components is disabled WITH THE REASON under the material
|
|
514
|
+
* nodes. Studio is always open: it is where every other mode returns to.
|
|
515
|
+
* Primitive subscriptions rather than one derived object, the shell's
|
|
516
|
+
* rule. */
|
|
517
|
+
function WorkspaceSwitch() {
|
|
518
|
+
const store = useStore();
|
|
519
|
+
const open = useShotEditor((s) => s.storyboardOpen);
|
|
520
|
+
const materialWorkspace = useShotEditor((s) => s.materialWorkspace);
|
|
521
|
+
const inComponents = useShotEditor((s) => workspaceOf(s) === "components");
|
|
522
|
+
const componentsBlocker = materialWorkspace === null ? null : "Close the material nodes before opening Components.";
|
|
523
|
+
// Leaving Components drops a code component's buffers, so unsaved text
|
|
524
|
+
// there asks.
|
|
525
|
+
const { guard, dialog } = useCodeBufferGuard();
|
|
526
|
+
const label = (glyph: ReactNode, word: string): ReactNode => (
|
|
527
|
+
<>
|
|
528
|
+
<span className="header-glyph" aria-hidden="true">
|
|
529
|
+
{glyph}
|
|
530
|
+
</span>
|
|
531
|
+
{word}
|
|
532
|
+
</>
|
|
533
|
+
);
|
|
97
534
|
return (
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
535
|
+
<>
|
|
536
|
+
{dialog}
|
|
537
|
+
<SegmentedControl
|
|
538
|
+
ariaLabel="Workspace"
|
|
539
|
+
className="is-accent workspace-switch"
|
|
540
|
+
value={open ? "storyboard" : inComponents ? "components" : "studio"}
|
|
541
|
+
options={[
|
|
542
|
+
{ value: "studio", label: label(STUDIO_ICON, "Studio"), testId: "workspace-studio" },
|
|
543
|
+
{
|
|
544
|
+
value: "storyboard",
|
|
545
|
+
label: label(STORYBOARD_ICON, "Storyboard"),
|
|
546
|
+
title: `Storyboard${shortcutHint("ctx.storyboard")}`,
|
|
547
|
+
testId: "workspace-storyboard",
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
value: "components",
|
|
551
|
+
label: label(COMPONENT_ICON, "Components"),
|
|
552
|
+
title: componentsBlocker ?? "Components",
|
|
553
|
+
disabled: componentsBlocker !== null,
|
|
554
|
+
testId: "workspace-components",
|
|
555
|
+
},
|
|
556
|
+
]}
|
|
557
|
+
onChange={(next) => {
|
|
558
|
+
// The leave sits INSIDE the guard, so Discard drops the buffers
|
|
559
|
+
// before the store's own unsaved-code check runs.
|
|
560
|
+
const toBoard = (): void => {
|
|
561
|
+
store.leaveComponents();
|
|
562
|
+
store.setStoryboardOpen(true);
|
|
563
|
+
};
|
|
564
|
+
if (next === "storyboard") guard(toBoard);
|
|
565
|
+
else if (next === "components") guard(() => void store.openComponents());
|
|
566
|
+
else guard(() => (inComponents ? store.leaveComponents("stage") : store.setStoryboardOpen(false)));
|
|
567
|
+
}}
|
|
568
|
+
/>
|
|
569
|
+
</>
|
|
108
570
|
);
|
|
109
571
|
}
|
|
110
572
|
|
|
@@ -115,17 +577,35 @@ function AgentToggle() {
|
|
|
115
577
|
* runs, so a button that forgot would be the only place the progress lived. */
|
|
116
578
|
function ExportButton() {
|
|
117
579
|
const store = useStore();
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const
|
|
580
|
+
// THE LOCK, not the server job: an in-browser export is just as much a
|
|
581
|
+
// render in progress, and the header used to show nothing at all for one.
|
|
582
|
+
const exportLock = useShotEditor((s) => s.exportLock);
|
|
583
|
+
const running = exportLock !== null;
|
|
584
|
+
const blocked = useShotEditor(exportBlocker);
|
|
585
|
+
const pct =
|
|
586
|
+
running && exportLock.total > 0
|
|
587
|
+
? Math.round((exportLock.done / exportLock.total) * 100)
|
|
588
|
+
: null;
|
|
121
589
|
return (
|
|
122
590
|
<button
|
|
123
591
|
type="button"
|
|
124
592
|
className={`btn is-primary header-export${running ? " is-running" : ""}`}
|
|
125
593
|
aria-label="Export"
|
|
126
|
-
|
|
594
|
+
// A running export stays reachable: the button is its progress.
|
|
595
|
+
disabled={!running && blocked !== null}
|
|
596
|
+
title={!running && blocked !== null ? blocked : `Export${shortcutHint("file.export")}`}
|
|
597
|
+
// The render's own progress, painted into the button that started it, so
|
|
598
|
+
// a closed export dialog is not the only place the number lived. Two
|
|
599
|
+
// stops off the SAME accent: still one primary control in one colour,
|
|
600
|
+
// not a second signal hue competing with it.
|
|
601
|
+
{...(pct === null
|
|
602
|
+
? {}
|
|
603
|
+
: { style: { "--export-pct": pct } as React.CSSProperties })}
|
|
127
604
|
onClick={() => store.setExportOpen(true)}
|
|
128
605
|
>
|
|
606
|
+
<span className="header-glyph" aria-hidden="true">
|
|
607
|
+
{DOWNLOAD_ICON}
|
|
608
|
+
</span>
|
|
129
609
|
{pct === null ? "Export" : `Exporting ${pct}%`}
|
|
130
610
|
</button>
|
|
131
611
|
);
|