@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.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -0,0 +1,407 @@
1
+ // THE GENERATE DRAWER: Sound mode's right column, swapped in for the mixer.
2
+ //
3
+ // A sentence becomes a sound effect, a music bed or a voice line, through
4
+ // `/studio/audio/generate` on either host (api.ts). The take is previewed
5
+ // with the browser's own <audio> before it touches the document, and nothing
6
+ // is placed until "Add to <track> at playhead", which is one undo entry.
7
+ //
8
+ // Locally the take is already a stored asset (the CLI stores it as it
9
+ // generates), so placing it is `placeStoredAudio`. Hosted, the take waits in
10
+ // R2 for a day and placing it is `importAudio` of its bytes, which uploads it
11
+ // into the Project like a dropped file. Either way a past take in the recent
12
+ // list is placed again without generating it twice.
13
+ //
14
+ // It runs on the user's own ElevenLabs key: the environment's locally, the
15
+ // workspace's hosted, which an Owner or Admin adds in Settings, AI keys.
16
+ //
17
+ // A SOUND EFFECT IS TWO TAKES by default: both are made at once, measured in
18
+ // this tab and ranked (`judgeTakes`, a chunk of its own), so the one that
19
+ // measures better leads, with the reason and a tone check when it skews
20
+ // bright. Both stay playable and addable: the ranking advises, the ear decides.
21
+ //
22
+ // MUSIC CAN BE SCORED TO THE PROJECT: "Score to the cuts" sends the film's
23
+ // sections (`scoreSectionsOf`), so the track changes where the film does, and
24
+ // adds it at frame 0, where its sections line up.
25
+ import { useEffect, useMemo, useState } from "react";
26
+ import { SegmentedControl } from "@dailies-studio/ui";
27
+ import { audioTrackOf, audioTracksOf, scoreSectionsOf } from "@dailies-studio/shot/doc";
28
+ import { generateAudioTake, generationStatus } from "../../api.js";
29
+ import type { GenerationBody, GenerationKind, GenerationStatus, GenerationTake } from "../../api.js";
30
+ import { CLOSE_ICON, SPARKLES_ICON } from "../../icons.js";
31
+ import { useShotEditor, useStore } from "../../state/context.js";
32
+
33
+ const KIND_LABEL: Record<GenerationKind, string> = { sfx: "Sound effect", music: "Music", voice: "Voice" };
34
+ const PLACEHOLDER: Record<GenerationKind, string> = {
35
+ sfx: "Short airy whoosh, left to right",
36
+ music: "Warm lo-fi beat, soft keys, 90 bpm",
37
+ voice: "What the voice says, as it should be spoken.",
38
+ };
39
+
40
+ function errorText(e: unknown): string {
41
+ return e instanceof Error ? e.message : String(e);
42
+ }
43
+
44
+ /** The dashboard's AI keys tab. The editor host sends `/projects/*` to the
45
+ * main host, so the relative link works there (as `MANAGE_FONTS_HREF`). */
46
+ export const ADD_KEY_HREF = "/projects/?view=settings-keys";
47
+
48
+ /** Whose ElevenLabs key a take runs on, under the Generate button. */
49
+ export function keyLine(status: GenerationStatus, takes = 1): string {
50
+ const whose = status.host === "local" ? "Uses ELEVENLABS_API_KEY on this machine" : "Uses this workspace's ElevenLabs key";
51
+ return `${whose}${takes > 1 ? " · two takes are two generations" : ""}`;
52
+ }
53
+
54
+ export function GenerateDrawer(props: { onClose: () => void }) {
55
+ const store = useStore();
56
+ const doc = useShotEditor((s) => s.doc);
57
+ const project = useShotEditor((s) => s.name);
58
+ const selected = useShotEditor((s) => s.selectedAudio);
59
+ const [status, setStatus] = useState<GenerationStatus | null>(null);
60
+ const [kind, setKind] = useState<GenerationKind>("sfx");
61
+ const [prompts, setPrompts] = useState<Record<GenerationKind, string>>({ sfx: "", music: "", voice: "" });
62
+ const [seconds, setSeconds] = useState<{ sfx: number; music: number }>({ sfx: 4, music: 30 });
63
+ const [voice, setVoice] = useState<string | null>(null);
64
+ const [instrumental, setInstrumental] = useState(true);
65
+ const [loop, setLoop] = useState(false);
66
+ /** Sound effects: two takes, the better kept (the default). */
67
+ const [twoTakes, setTwoTakes] = useState(true);
68
+ /** Music: compose to the Project's cuts instead of a length. */
69
+ const [fit, setFit] = useState(false);
70
+ const [busy, setBusy] = useState<"generate" | "place" | null>(null);
71
+ const [error, setError] = useState<string | null>(null);
72
+ const [made, setMade] = useState<{ order: GenerationTake[]; reasons: string[]; tone: string | null; scored: boolean } | null>(null);
73
+ const [trackId, setTrackId] = useState<string | null>(null);
74
+
75
+ useEffect(() => {
76
+ if (project === null) return;
77
+ let live = true;
78
+ generationStatus(project).then(
79
+ (s) => {
80
+ if (!live) return;
81
+ setStatus(s);
82
+ setSeconds({ sfx: s.caps.sfx.defaultSeconds, music: s.caps.music.defaultSeconds });
83
+ },
84
+ (e: unknown) => {
85
+ if (live) setError(errorText(e));
86
+ },
87
+ );
88
+ return () => {
89
+ live = false;
90
+ };
91
+ }, [project]);
92
+
93
+ const tracks = doc === null ? [] : audioTracksOf(doc);
94
+ // The selected clip's track, else the first: where "Add" lands unless the
95
+ // author picks another.
96
+ const defaultTrack = useMemo(() => {
97
+ if (doc === null) return undefined;
98
+ const clip = selected.length > 0 ? doc.audio?.[selected[0]!] : undefined;
99
+ return audioTrackOf(doc, clip?.track).id;
100
+ }, [doc, selected]);
101
+ const target = tracks.find((t) => t.id === trackId) ?? tracks.find((t) => t.id === defaultTrack) ?? tracks[0];
102
+ const sections = useMemo(() => (doc === null ? null : scoreSectionsOf(doc)), [doc]);
103
+
104
+ if (doc === null || project === null) return null;
105
+ const prompt = prompts[kind];
106
+ const available = status?.kinds[kind];
107
+ const voices = status?.voices ?? [];
108
+ const pickedVoice = voice ?? voices[0]?.id;
109
+ // Scoring needs a film music can be as long as: under 3 s there is no
110
+ // section, and past the music cap there is no take.
111
+ const filmMs = sections === null ? 0 : sections.reduce((sum, s) => sum + s.durationMs, 0);
112
+ const scoreRefusal =
113
+ sections === null
114
+ ? "The film is shorter than 3 seconds, the shortest a section can be"
115
+ : status !== null && filmMs > status.caps.music.maxSeconds * 1000
116
+ ? `The film is ${Math.round(filmMs / 1000)} s; music can be at most ${status.caps.music.maxSeconds} s`
117
+ : null;
118
+ const scored = kind === "music" && fit && sections !== null && scoreRefusal === null;
119
+ const takes = kind === "sfx" && twoTakes ? 2 : 1;
120
+ const body: GenerationBody = {
121
+ project,
122
+ kind,
123
+ prompt,
124
+ ...(kind === "voice"
125
+ ? pickedVoice === undefined
126
+ ? {}
127
+ : { voice: pickedVoice }
128
+ : scored
129
+ ? {
130
+ seconds: filmMs / 1000,
131
+ instrumental,
132
+ sections: sections.map(({ name, durationMs }) => ({ name, durationMs })),
133
+ }
134
+ : { seconds: seconds[kind], ...(kind === "music" ? { instrumental } : { loop }) }),
135
+ };
136
+ const maxChars = status === null ? 0 : kind === "voice" ? status.caps.voice.maxChars : status.caps.promptMaxChars;
137
+ const caps = status !== null && kind !== "voice" ? status.caps[kind] : null;
138
+ const canGenerate = busy === null && available?.ok === true && prompt.trim() !== "";
139
+
140
+ const generate = async (): Promise<void> => {
141
+ setBusy("generate");
142
+ setError(null);
143
+ try {
144
+ const asked = { ...body };
145
+ const settled = await Promise.allSettled(Array.from({ length: takes }, () => generateAudioTake(asked)));
146
+ const arrived = settled.flatMap((r) => (r.status === "fulfilled" ? [r.value.take] : []));
147
+ if (arrived.length === 0) throw (settled[0] as PromiseRejectedResult).reason;
148
+ // The judge is its own chunk, fetched the first time a sound effect
149
+ // lands: it ranks two takes, and gives one take its tone check. A chunk
150
+ // that does not arrive (a deploy replaced it under this tab) leaves the
151
+ // takes shown, unranked, rather than lost behind an error.
152
+ let ranked: { order: GenerationTake[]; reasons: string[]; tone: string | null } = { order: arrived, reasons: [], tone: null };
153
+ if (kind === "sfx") {
154
+ try {
155
+ ranked = await (await import("./takeMeasure.js")).judgeTakes(arrived, asked.seconds ?? 0);
156
+ } catch {
157
+ if (arrived.length > 1) ranked = { ...ranked, reasons: ["These takes could not be measured here, so listen to both."] };
158
+ }
159
+ }
160
+ const failed = settled.flatMap((r) => (r.status === "rejected" ? [errorText(r.reason)] : []));
161
+ setMade({ ...ranked, reasons: [...ranked.reasons, ...failed.map((f) => `A take failed: ${f}`)], scored });
162
+ setStatus((s) =>
163
+ s === null ? s : { ...s, recent: [...arrived, ...s.recent.filter((r) => !arrived.some((a) => a.id === r.id))] },
164
+ );
165
+ } catch (e) {
166
+ setError(errorText(e));
167
+ } finally {
168
+ setBusy(null);
169
+ }
170
+ };
171
+
172
+ const place = async (t: GenerationTake, atStart = false): Promise<void> => {
173
+ setBusy("place");
174
+ setError(null);
175
+ try {
176
+ await store.placeTake(t, target?.id, atStart ? 0 : undefined);
177
+ } catch (e) {
178
+ setError(errorText(e));
179
+ } finally {
180
+ setBusy(null);
181
+ }
182
+ };
183
+
184
+ return (
185
+ <div className="sound-gen" data-testid="generate-drawer">
186
+ <div className="sound-gen-head">
187
+ <span className="sound-gen-glyph" aria-hidden="true">
188
+ {SPARKLES_ICON}
189
+ </span>
190
+ <span className="sound-gen-title">Generate</span>
191
+ <button type="button" className="btn is-ghost is-icon" aria-label="Close" title="Back to the mixer" onClick={props.onClose}>
192
+ {CLOSE_ICON}
193
+ </button>
194
+ </div>
195
+ <div className="sound-gen-body">
196
+ <SegmentedControl
197
+ className="sound-gen-kinds"
198
+ ariaLabel="What to generate"
199
+ value={kind}
200
+ onChange={(k) => setKind(k === "music" || k === "voice" ? k : "sfx")}
201
+ options={(["sfx", "music", "voice"] as const).map((k) => ({
202
+ value: k,
203
+ label: KIND_LABEL[k],
204
+ testId: `generate-kind-${k}`,
205
+ }))}
206
+ />
207
+ <textarea
208
+ className="text-input sound-gen-prompt"
209
+ data-testid="generate-prompt"
210
+ aria-label={kind === "voice" ? "Script" : "Describe the sound"}
211
+ placeholder={PLACEHOLDER[kind]}
212
+ value={prompt}
213
+ maxLength={maxChars > 0 ? maxChars : undefined}
214
+ rows={kind === "voice" ? 4 : 2}
215
+ onChange={(e) => {
216
+ const value = e.currentTarget.value;
217
+ setPrompts((p) => ({ ...p, [kind]: value }));
218
+ }}
219
+ onKeyDown={(e) => {
220
+ // The dock's keymap must not see typing (Space plays, Q/W trim).
221
+ e.stopPropagation();
222
+ if (e.key === "Enter" && (e.metaKey || e.ctrlKey) && canGenerate) {
223
+ e.preventDefault();
224
+ void generate();
225
+ }
226
+ }}
227
+ />
228
+ {kind === "voice" ? (
229
+ <label className="sound-gen-field">
230
+ <span className="sound-gen-label">Voice</span>
231
+ <select
232
+ className="select sound-gen-select"
233
+ data-testid="generate-voice"
234
+ value={pickedVoice ?? ""}
235
+ onChange={(e) => setVoice(e.currentTarget.value)}
236
+ >
237
+ {voices.map((v) => (
238
+ <option key={v.id} value={v.id}>
239
+ {v.name}, {v.description}
240
+ </option>
241
+ ))}
242
+ </select>
243
+ </label>
244
+ ) : (
245
+ <>
246
+ <div className="sound-gen-row">
247
+ <label className="sound-gen-field">
248
+ <span className="sound-gen-label">Seconds</span>
249
+ <input
250
+ className="text-input sound-gen-seconds"
251
+ data-testid="generate-seconds"
252
+ type="number"
253
+ min={caps?.minSeconds}
254
+ max={caps?.maxSeconds}
255
+ step={kind === "sfx" ? 0.5 : 1}
256
+ disabled={scored}
257
+ value={scored ? (body.seconds ?? 0) : seconds[kind]}
258
+ onKeyDown={(e) => e.stopPropagation()}
259
+ onChange={(e) => {
260
+ const n = Number(e.currentTarget.value);
261
+ if (!Number.isFinite(n) || caps === null) return;
262
+ setSeconds((s) => ({ ...s, [kind]: Math.min(caps.maxSeconds, Math.max(caps.minSeconds, n)) }));
263
+ }}
264
+ />
265
+ </label>
266
+ <label className="sound-gen-check">
267
+ <input
268
+ type="checkbox"
269
+ checked={kind === "music" ? instrumental : loop}
270
+ onChange={(e) => (kind === "music" ? setInstrumental : setLoop)(e.currentTarget.checked)}
271
+ />
272
+ {kind === "music" ? "Instrumental" : "Loop"}
273
+ </label>
274
+ </div>
275
+ {kind === "sfx" ? (
276
+ <label className="sound-gen-check" title="Make two takes and lead with the one that measures better">
277
+ <input type="checkbox" data-testid="generate-two-takes" checked={twoTakes} onChange={(e) => setTwoTakes(e.currentTarget.checked)} />
278
+ Two takes, keep the better
279
+ </label>
280
+ ) : (
281
+ <label
282
+ className="sound-gen-check"
283
+ title={scoreRefusal ?? "Compose to the Project's cuts and named markers"}
284
+ >
285
+ <input
286
+ type="checkbox"
287
+ data-testid="generate-fit"
288
+ checked={scored}
289
+ disabled={scoreRefusal !== null}
290
+ onChange={(e) => setFit(e.currentTarget.checked)}
291
+ />
292
+ Score to the cuts{sections === null ? "" : ` (${sections.length} section${sections.length === 1 ? "" : "s"})`}
293
+ </label>
294
+ )}
295
+ </>
296
+ )}
297
+ <div className="sound-gen-price" data-testid="generate-price">
298
+ {status === null
299
+ ? "Checking what this studio can make…"
300
+ : available?.ok !== true
301
+ ? (available?.reason ?? "Not available here")
302
+ : keyLine(status, takes)}
303
+ {status?.needsKey === "add" ? (
304
+ <>
305
+ {" "}
306
+ <a className="sound-gen-key-link" data-testid="generate-add-key" href={ADD_KEY_HREF} target="_blank" rel="noopener">
307
+ Open AI keys ↗
308
+ </a>
309
+ </>
310
+ ) : null}
311
+ </div>
312
+ <button
313
+ type="button"
314
+ className="btn is-primary sound-gen-go"
315
+ data-testid="generate-go"
316
+ disabled={!canGenerate}
317
+ onClick={() => void generate()}
318
+ >
319
+ {busy === "generate" ? "Generating…" : `Generate ${KIND_LABEL[kind].toLowerCase()}`}
320
+ </button>
321
+ {error === null ? null : (
322
+ <p className="sound-gen-error" role="alert">
323
+ {error}
324
+ </p>
325
+ )}
326
+ {made === null ? null : (
327
+ <div className="sound-gen-take" data-testid="generate-take">
328
+ {made.order.map((take, i) => (
329
+ <div key={take.id} className="sound-gen-take-row" data-testid="generate-take-row">
330
+ <span className="sound-gen-take-name" title={take.prompt}>
331
+ {made.order.length > 1 ? (i === 0 ? "Kept · " : "Other take · ") : ""}
332
+ {take.prompt}
333
+ </span>
334
+ <audio className="sound-gen-audio" controls preload="metadata" src={take.url} />
335
+ <button
336
+ type="button"
337
+ className={i === 0 ? "btn is-primary" : "btn is-outline"}
338
+ data-testid={i === 0 ? "generate-place" : "generate-place-other"}
339
+ disabled={busy !== null || target === undefined}
340
+ onClick={() => void place(take, made.scored)}
341
+ >
342
+ Add to {target?.name ?? "track"} {made.scored ? "at the start" : "at playhead"}
343
+ </button>
344
+ </div>
345
+ ))}
346
+ {made.reasons.length === 0 ? null : (
347
+ <p className="sound-gen-why" data-testid="generate-why">
348
+ {made.reasons.join(" ")}
349
+ </p>
350
+ )}
351
+ {made.tone === null ? null : (
352
+ <p className="sound-gen-why" data-testid="generate-tone">
353
+ {made.tone}
354
+ </p>
355
+ )}
356
+ <div className="sound-gen-take-actions">
357
+ <button type="button" className="btn is-outline" disabled={!canGenerate} onClick={() => void generate()}>
358
+ {takes > 1 ? "Two more takes" : "Another take"}
359
+ </button>
360
+ </div>
361
+ </div>
362
+ )}
363
+ {tracks.length > 1 ? (
364
+ <label className="sound-gen-field">
365
+ <span className="sound-gen-label">Track</span>
366
+ <select
367
+ className="select sound-gen-select"
368
+ data-testid="generate-track"
369
+ value={target?.id ?? ""}
370
+ onChange={(e) => setTrackId(e.currentTarget.value)}
371
+ >
372
+ {tracks.map((t) => (
373
+ <option key={t.id} value={t.id}>
374
+ {t.name}
375
+ </option>
376
+ ))}
377
+ </select>
378
+ </label>
379
+ ) : null}
380
+ {status !== null && status.recent.length > 0 ? (
381
+ <div className="sound-gen-recent">
382
+ <span className="sound-gen-label">Recent</span>
383
+ <ul className="sound-gen-list">
384
+ {status.recent.map((r) => (
385
+ <li key={r.id} className="sound-gen-item">
386
+ <span className="sound-gen-item-kind">{KIND_LABEL[r.kind]}</span>
387
+ <span className="sound-gen-item-prompt" title={r.prompt}>
388
+ {r.prompt}
389
+ </span>
390
+ <button
391
+ type="button"
392
+ className="btn is-ghost sound-gen-item-add"
393
+ disabled={busy !== null || target === undefined}
394
+ title={`Add to ${target?.name ?? "track"} ${r.scored === true ? "at the start, where its sections line up" : "at the playhead"}`}
395
+ onClick={() => void place(r, r.scored === true)}
396
+ >
397
+ Add
398
+ </button>
399
+ </li>
400
+ ))}
401
+ </ul>
402
+ </div>
403
+ ) : null}
404
+ </div>
405
+ </div>
406
+ );
407
+ }
@@ -0,0 +1,115 @@
1
+ // THE STRETCH BRACKET (Rows face): the selected keys' span, with a grab tab at
2
+ // each end. Dragging one scales every selected key's frame about the other end
3
+ // (`dragKeyStretch`, `keyStretchMoves`), across tracks, layers and Shots. Graph
4
+ // mode has its own bracket over one curve; both run `retimeLandings`.
5
+ //
6
+ // It is how a motion preset is retimed without the document remembering one:
7
+ // a stamp selects the keys it wrote, and this bracket stands over them.
8
+ //
9
+ // TWO HALVES, because the dock scrolls under a sticky ruler. The tabs and the
10
+ // readout live IN THE RULER (`KeyStretchTabs`): always in sight, and above the
11
+ // tracks, where a tab in the rows would scroll away or slide under the ruler.
12
+ // The guides through the rows live in `.dock-overlay` (`KeyStretchGuides`) and
13
+ // take no pointer. The tabs sit just inside each end of a band over the span,
14
+ // so the ruler's clip never hides one at frame 0. Both read the dock's frames:
15
+ // a Shot's keys plus its start, less the shot context's re-zero. A pointer
16
+ // gesture, like Graph mode's bracket: the keyboard retimes keys by nudging.
17
+ import { useState } from "react";
18
+ import type { PointerEvent as ReactPointerEvent } from "react";
19
+ import { frameToPx, startDrag } from "@dailies-studio/timeline";
20
+ import { isDefId, sceneStart } from "@dailies-studio/shot/doc";
21
+ import { useShotEditor, useStore } from "../../state/context.js";
22
+ import { frameDelta } from "./model.js";
23
+
24
+ interface Span {
25
+ min: number;
26
+ max: number;
27
+ left: number;
28
+ right: number;
29
+ }
30
+
31
+ /** The selected keys' span in dock frames and pixels, or null when there is
32
+ * no bracket to draw: not the Rows face, a viewer, or under two frames. */
33
+ function useStretchSpan(ppf: number, origin: number): Span | null {
34
+ const keys = useShotEditor((s) => s.selectedKeyframes);
35
+ const doc = useShotEditor((s) => s.doc);
36
+ const rows = useShotEditor((s) => s.dockMode === "rows");
37
+ const readOnly = useShotEditor((s) => s.readOnly);
38
+ if (!rows || readOnly || doc === null || keys.length < 2) return null;
39
+ const at = keys.map((k) => k.frame + (isDefId(k.containerId) ? 0 : (sceneStart(doc, k.containerId) ?? 0)));
40
+ const min = Math.min(...at);
41
+ const max = Math.max(...at);
42
+ if (min === max) return null;
43
+ return { min, max, left: frameToPx(min - origin, ppf), right: frameToPx(max - origin, ppf) };
44
+ }
45
+
46
+ /** The guides through the rows, in `.dock-overlay`. */
47
+ export function KeyStretchGuides({ ppf, origin }: { ppf: number; origin: number }) {
48
+ const span = useStretchSpan(ppf, origin);
49
+ if (span === null) return null;
50
+ return (
51
+ <>
52
+ <span className="dock-key-stretch-guide" style={{ left: span.left }} />
53
+ <span className="dock-key-stretch-guide" style={{ left: span.right }} />
54
+ </>
55
+ );
56
+ }
57
+
58
+ /** The grab tabs and the drag's readout, in the ruler's track area. */
59
+ export function KeyStretchTabs({ ppf, origin }: { ppf: number; origin: number }) {
60
+ const store = useStore();
61
+ const span = useStretchSpan(ppf, origin);
62
+ const [chip, setChip] = useState<{ edge: "left" | "right"; label: string } | null>(null);
63
+ if (span === null) return null;
64
+
65
+ const grab = (edge: "left" | "right") => (e: ReactPointerEvent<HTMLElement>): void => {
66
+ if (e.button !== 0) return;
67
+ e.preventDefault();
68
+ // The ruler's own press seeks: a tab is not the ruler.
69
+ e.stopPropagation();
70
+ if (!store.beginBlockDrag()) return;
71
+ const startX = e.clientX;
72
+ const width = span.max - span.min;
73
+ setChip({ edge, label: "×1.00" });
74
+ startDrag(e, {
75
+ onMove(ev) {
76
+ const factor = store.dragKeyStretch(edge, frameDelta(ev.clientX - startX, ppf));
77
+ if (factor !== null) setChip({ edge, label: `×${factor.toFixed(2)} · ${Math.round(width * factor)}f` });
78
+ },
79
+ onEnd(commit) {
80
+ setChip(null);
81
+ store.endBlockDrag(commit);
82
+ },
83
+ });
84
+ };
85
+
86
+ return (
87
+ <>
88
+ <span className="dock-key-stretch-band" style={{ left: span.left, width: span.right - span.left }} />
89
+ <span
90
+ className="dock-key-stretch-handle is-left"
91
+ data-testid="key-stretch-left"
92
+ title="Drag to stretch the selected keyframes"
93
+ style={{ left: span.left }}
94
+ onPointerDown={grab("left")}
95
+ />
96
+ <span
97
+ className="dock-key-stretch-handle is-right"
98
+ data-testid="key-stretch-right"
99
+ title="Drag to stretch the selected keyframes"
100
+ style={{ left: span.right }}
101
+ onPointerDown={grab("right")}
102
+ />
103
+ {chip === null ? null : (
104
+ <span
105
+ className="stagger-chip dock-key-stretch-chip"
106
+ data-testid="key-stretch-readout"
107
+ role="status"
108
+ style={{ left: chip.edge === "left" ? span.left : span.right }}
109
+ >
110
+ <span className="stagger-chip-step">{chip.label}</span>
111
+ </span>
112
+ )}
113
+ </>
114
+ );
115
+ }