@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,508 @@
1
+ // GRAPH MODE: the dock's track area, showing HOW something moves.
2
+ //
3
+ // The rows answer "when does this happen" and this answers "how does it move",
4
+ // and they are two resolutions of one subject — which is why this is a MODE of
5
+ // the dock rather than a second panel beside it. A graph stacked under forty
6
+ // rows gets the height neither of them can spare.
7
+ //
8
+ // ── WHAT IS HERE AND WHAT IS SHARED ─────────────────────────────────────────
9
+ //
10
+ // The curve itself is `@dailies-studio/timeline`'s `CurveGraph` over the
11
+ // adapter in curveModel.ts. What this file owns is the TOOLBAR (scope, ease
12
+ // presets, the one key's Frame and Value, Normalize, Frame all), the LEFT
13
+ // COLUMN (which curve is focused, which others are shown beside it, each in
14
+ // its colour with its value at the playhead), the empty states in the plot,
15
+ // and the rule for what the pane is looking at when the author has not said.
16
+ //
17
+ // The column is the same width as the rows' label column (`useLabelWidth`, the
18
+ // rail track's shown width), so switching modes does not move the vertical
19
+ // rule the whole panel is read against.
20
+ import {
21
+ CurveGraph,
22
+ EASE_PRESETS,
23
+ KeyframeFields,
24
+ PlayheadChip,
25
+ PlayheadLine,
26
+ matchPreset,
27
+ } from "@dailies-studio/timeline";
28
+ import type { KeyframeAddr, KeyframeFieldsView } from "@dailies-studio/timeline";
29
+ import { IconChevronDown, IconDiamond, formatValue, useContextMenu } from "@dailies-studio/ui";
30
+ import { useEffect, useMemo, useState } from "react";
31
+ import type { CSSProperties, MouseEvent as ReactMouseEvent, ReactElement } from "react";
32
+ import type { Ease } from "@dailies-studio/engine";
33
+ import { containerFrame, defDurationFrames, homeOf, isDefId } from "@dailies-studio/shot/doc";
34
+ import type { Doc3D } from "@dailies-studio/shot/doc";
35
+ import { useShotEditor, useShotEditorHot, useStore } from "../../state/context.js";
36
+ import { primaryKeyframe } from "../../state/types.js";
37
+ import type { ShotEditorState } from "../../state/types.js";
38
+ import { useLabelWidth } from "./labelWidth.js";
39
+ import { keyframeMenuEntries, keyframeMenuTargets } from "./keyframeMenu.js";
40
+ import {
41
+ allAnimatedRows,
42
+ curveRowId,
43
+ curveRowsOf,
44
+ curveSubjectLayer,
45
+ curveTracksOf,
46
+ curveValueAt,
47
+ parseCurveRowId,
48
+ sameSubject,
49
+ shotCurveModel,
50
+ unitOf,
51
+ } from "./curveModel.js";
52
+ import type { CurveSubject } from "./curveModel.js";
53
+
54
+ /** The list's curve colours, in order. Tokens (shotTokens.css), so each theme
55
+ * sets its own readable values. A fifth curve takes the first colour again. */
56
+ const CURVE_COLORS = ["var(--curve-1)", "var(--curve-2)", "var(--curve-3)", "var(--curve-4)"] as const;
57
+
58
+ /** The toolbar's ease presets: the five shapes the graph board names, each one
59
+ * of `EASE_PRESETS` (so the ease written is the palette's own), with the
60
+ * board's glyph for the shape. */
61
+ const TOOLBAR_PRESETS: ReadonlyArray<{ id: string; label: string; glyph: string }> = [
62
+ { id: "linear", label: "Linear", glyph: "M2 12L12 2" },
63
+ { id: "ease-in", label: "Ease in", glyph: "M2 12C8 12 10 8 12 2" },
64
+ { id: "ease-out", label: "Ease out", glyph: "M2 12C4 6 6 2 12 2" },
65
+ { id: "ease-in-out", label: "Ease in-out", glyph: "M2 12C7 12 7 2 12 2" },
66
+ { id: "hold", label: "Hold", glyph: "M2 12H8V2H12" },
67
+ ];
68
+
69
+ const presetEase = (id: string): Ease | undefined => EASE_PRESETS.find((p) => p.id === id)?.ease;
70
+
71
+ /** Which curves the list shows: the selection's owner, or every animated
72
+ * element in the container. */
73
+ type CurveScope = "selected" | "all";
74
+
75
+ export function CurvePane() {
76
+ const store = useStore();
77
+ const doc = useShotEditor((s) => s.doc);
78
+ const editingDef = useShotEditor((s) => s.editingDef);
79
+ const sceneId = useShotEditor((s) => s.sceneId);
80
+ const selection = useShotEditor((s) => s.selection);
81
+ const selectedKeyframes = useShotEditor((s) => s.selectedKeyframes);
82
+ const selectedKeyframe = primaryKeyframe({ selectedKeyframes });
83
+ const ppf = useShotEditor((s) => s.dockPpf);
84
+ const labelWidth = useLabelWidth();
85
+ // ── THE FRAME STAYS OUT OF THIS PANE ─────────────────────────────────────
86
+ //
87
+ // The dock's rule is that the frame stays out of the dock and lives in
88
+ // leaves, and this pane keeps it: the graph's playhead (`CurvePlayhead`,
89
+ // drawn inside `CurveGraph`) and each curve's value at the playhead
90
+ // (`CurveRowValue`) subscribe to it, and nothing else here does. The model
91
+ // is built without it: a frame laid over the model per tick re-rendered
92
+ // the whole pane and the graph sixty times a second for a playhead line.
93
+
94
+ const [scope, setScope] = useState<CurveScope>("selected");
95
+ const [normalize, setNormalize] = useState(false);
96
+ const [frameAll, setFrameAll] = useState(0);
97
+ /** The curves shown BESIDE the focused one, by row id. Local, like the dock's
98
+ * own disclosure sets: it is about this panel's shape and nothing else. */
99
+ const [shownIds, setShownIds] = useState<ReadonlySet<string>>(() => new Set());
100
+
101
+ const subject = curveSubjectLayer(
102
+ { selectedKeyframe, selection, containerId: editingDef ?? sceneId },
103
+ doc ?? undefined,
104
+ );
105
+
106
+ const rows = useMemo(() => {
107
+ if (doc === null || subject === null) return [];
108
+ return scope === "all"
109
+ ? allAnimatedRows(doc, subject.containerId)
110
+ : curveRowsOf(doc, subject.containerId, subject.layerId);
111
+ }, [doc, scope, subject?.containerId, subject?.layerId]);
112
+
113
+ /**
114
+ * Which curve is drawn on top and edited.
115
+ *
116
+ * Seeded from the selected keyframe, and re-seeded whenever that moves to a
117
+ * different property — clicking a diamond in the rows and then switching to
118
+ * the graph should land on the thing that was clicked.
119
+ */
120
+ const [focus, setFocus] = useState<CurveSubject | null>(null);
121
+ const wanted = useMemo(() => {
122
+ if (selectedKeyframe === null) return null;
123
+ const match = rows.find(
124
+ (r) =>
125
+ r.subject.path === selectedKeyframe.path &&
126
+ r.subject.layerId === selectedKeyframe.layerId &&
127
+ r.numberTrack,
128
+ );
129
+ return match?.subject ?? null;
130
+ }, [rows, selectedKeyframe?.path, selectedKeyframe?.layerId]);
131
+ useEffect(() => {
132
+ if (wanted !== null && !sameSubject(wanted, focus)) setFocus(wanted);
133
+ }, [wanted]);
134
+ // A pane that opens on nothing is a panel that reads as broken, so it falls
135
+ // back to the first curve there is. Not stored: a fallback that persisted
136
+ // would fight the seeding above on the next selection.
137
+ const focused =
138
+ focus !== null && rows.some((r) => r.id === curveIdOf(focus) && r.numberTrack)
139
+ ? focus
140
+ : (rows.find((r) => r.numberTrack)?.subject ?? null);
141
+
142
+ // EACH CURVE'S COLOUR, by its place among the curves in the list, so a dot
143
+ // and its line always agree and a curve keeps its colour as others are shown.
144
+ const colors = useMemo(() => {
145
+ const out = new Map<string, string>();
146
+ rows.filter((r) => r.numberTrack).forEach((r, i) => out.set(r.id, CURVE_COLORS[i % CURVE_COLORS.length]!));
147
+ return out;
148
+ }, [rows]);
149
+ const shown = useMemo(
150
+ () => rows.filter((r) => r.numberTrack && shownIds.has(r.id)).map((r) => r.subject),
151
+ [rows, shownIds],
152
+ );
153
+
154
+ const defOpen = editingDef !== null && subject !== null && isDefId(subject.containerId);
155
+ /** The playhead in the subject's own frames, read by the leaves. Held to
156
+ * the Shot's own frames: a cut the transport plays leaves the cold
157
+ * `sceneId` on the Shot it left until Pause, and the frames past it are
158
+ * another Shot's, which this graph does not draw. */
159
+ const containerId = subject?.containerId ?? null;
160
+ const containerSpan = defOpen || doc === null || containerId === null ? 0 : (doc.scenesById[containerId]?.durationFrames ?? 0);
161
+ const localFrameOf = (s: ShotEditorState): number =>
162
+ doc === null || containerId === null
163
+ ? 0
164
+ : defOpen
165
+ ? s.defFrame
166
+ : Math.min(containerFrame(doc, containerId, s.frame), Math.max(0, containerSpan - 1));
167
+
168
+ const model = useMemo(() => {
169
+ if (doc === null || subject === null) return null;
170
+ const def = defOpen ? doc.components?.[subject.containerId] : undefined;
171
+ const containerFrames =
172
+ def !== undefined
173
+ ? defDurationFrames(def)
174
+ : (doc.scenesById[subject.containerId]?.durationFrames ?? 0);
175
+ return shotCurveModel({
176
+ store,
177
+ doc,
178
+ containerId: subject.containerId,
179
+ containerFrames,
180
+ frame: 0,
181
+ fps: doc.fps,
182
+ pxPerFrame: ppf,
183
+ focus: focused,
184
+ rows,
185
+ selected: selectedKeyframes,
186
+ defOpen,
187
+ visible: shown,
188
+ colors,
189
+ });
190
+ }, [store, doc, subject?.containerId, subject?.layerId, defOpen, ppf, focused, rows, selectedKeyframes, shown, colors]);
191
+
192
+ // THE SAME KEYFRAME MENU THE ROWS OPEN. The graph knows a circle was
193
+ // right-clicked and nothing else — ease presets are this lane's vocabulary, so
194
+ // the entries are built here, from the shared builder, against the real track.
195
+ const menu = useContextMenu();
196
+ const openKeyMenu = (ref: KeyframeAddr, at: { x: number; y: number }): void => {
197
+ const subject = parseCurveRowId(ref.rowId);
198
+ if (doc === null || subject === null) return;
199
+ const track = curveTracksOf(doc, subject.containerId, subject.layerId)?.[subject.path];
200
+ const index = track?.keyframes.findIndex((k) => k.id === ref.keyframeId) ?? -1;
201
+ const key = index < 0 ? undefined : track!.keyframes[index]!;
202
+ if (track === undefined || key === undefined) return;
203
+ const sel = {
204
+ containerId: subject.containerId,
205
+ layerId: subject.layerId,
206
+ path: subject.path,
207
+ frame: key.frame,
208
+ };
209
+ const targets = keyframeMenuTargets(store.getState().selectedKeyframes, sel);
210
+ if (targets.length === 1) store.selectKeyframe(sel);
211
+ menu.open(
212
+ at,
213
+ keyframeMenuEntries({
214
+ store,
215
+ key: {
216
+ kind: track.kind,
217
+ ease: key.easeOut,
218
+ last: index === track.keyframes.length - 1,
219
+ curved: track.spatialInterp !== undefined,
220
+ },
221
+ targets,
222
+ at: sel,
223
+ }),
224
+ );
225
+ };
226
+
227
+ if (doc === null) return null;
228
+
229
+ const openScopeMenu = (e: ReactMouseEvent<HTMLButtonElement>): void => {
230
+ const box = e.currentTarget.getBoundingClientRect();
231
+ menu.open({ x: box.left, y: box.bottom + 4 }, [
232
+ { kind: "action", id: "selected", label: "Selected", checked: scope === "selected", onSelect: () => setScope("selected") },
233
+ { kind: "action", id: "all", label: "All animated", checked: scope === "all", onSelect: () => setScope("all") },
234
+ ]);
235
+ };
236
+
237
+ // ── THE EASE PRESETS ACT ON THE SELECTED KEYS ───────────────────────────
238
+ //
239
+ // The whole selection, on every track, through the store's one batched
240
+ // write: one undo step for N keys, the same write the inspector's preset
241
+ // select makes. The active chip is the preset every selected key already
242
+ // has; a mixed or custom selection lights none.
243
+ const selectedEases = selectedKeyframes.flatMap((at) => {
244
+ const key = curveTracksOf(doc, at.containerId, at.layerId)?.[at.path]?.keyframes.find((k) => k.frame === at.frame);
245
+ return key === undefined ? [] : [key.easeOut];
246
+ });
247
+ const presetIds = new Set(selectedEases.map(matchPreset));
248
+ const activePreset = presetIds.size === 1 ? [...presetIds][0]! : null;
249
+ const noKeys = selectedKeyframes.length === 0;
250
+ const applyPreset = (id: string): void => {
251
+ const ease = presetEase(id);
252
+ if (noKeys || ease === undefined) return;
253
+ store.setKeyframesEase(
254
+ selectedKeyframes.map((k) => ({ containerId: k.containerId, layerId: k.layerId, path: k.path, frame: k.frame })),
255
+ ease,
256
+ );
257
+ };
258
+
259
+ // THE ONE KEY'S FRAME AND VALUE, editable in the toolbar: the inspector's own
260
+ // store-free fields, through the graph's gesture, so a typed frame is one
261
+ // undo entry exactly as it is there. Only for ONE key on the focused curve:
262
+ // a frame typed into a set has no one meaning.
263
+ const only = selectedKeyframes.length === 1 ? selectedKeyframes[0]! : null;
264
+ const onlyKey =
265
+ only === null || focused === null || model?.focusedCurve == null
266
+ ? undefined
267
+ : only.containerId === focused.containerId && only.layerId === focused.layerId && only.path === focused.path
268
+ ? model.focusedCurve.keyframes.find((k) => k.frame === only.frame)
269
+ : undefined;
270
+ const fields: KeyframeFieldsView | null =
271
+ onlyKey === undefined || focused === null
272
+ ? null
273
+ : {
274
+ ref: { rowId: curveRowId(focused), keyframeId: onlyKey.id },
275
+ frame: onlyKey.frame,
276
+ value: onlyKey.value,
277
+ ...unitOf(focused.path),
278
+ };
279
+
280
+ // WHOSE CURVES THESE ARE, named over each owner's rows: the rows are
281
+ // property names ("Opacity", "X"), and without the owner a column of them
282
+ // does not say which element the graph is drawing. The layer's own label, or
283
+ // the shot's name for a shot-level track.
284
+ const ownerName = (containerId: string, layerId: string | null): string | null =>
285
+ layerId === null
286
+ ? (doc.scenesById[containerId]?.name ?? null)
287
+ : (homeOf(doc, containerId)?.layersById[layerId]?.label ?? null);
288
+
289
+ const curveCount = rows.filter((r) => r.numberTrack).length;
290
+ const toggleShown = (id: string): void =>
291
+ setShownIds((prev) => {
292
+ const next = new Set(prev);
293
+ if (next.has(id)) next.delete(id);
294
+ else next.add(id);
295
+ return next;
296
+ });
297
+
298
+ const list: ReactElement[] = [];
299
+ let owner: string | undefined;
300
+ for (const row of rows) {
301
+ const rowOwner = row.subject.layerId ?? "";
302
+ if (rowOwner !== owner) {
303
+ owner = rowOwner;
304
+ const name = ownerName(row.subject.containerId, row.subject.layerId);
305
+ // Not a `.curve-row`: a header, never a focus target. The first one sits
306
+ // beside the ruler, the same 28px tall.
307
+ if (name !== null) {
308
+ list.push(
309
+ <div key={`owner-${rowOwner}`} className="curve-scope">
310
+ <IconDiamond className="curve-scope-icon" aria-hidden="true" />
311
+ <span className="curve-scope-name">{name}</span>
312
+ </div>,
313
+ );
314
+ }
315
+ }
316
+ const isFocused = row.id === curveIdOf(focused);
317
+ const isShown = row.numberTrack && (isFocused || shownIds.has(row.id));
318
+ const color = colors.get(row.id);
319
+ list.push(
320
+ <button
321
+ key={row.id}
322
+ type="button"
323
+ className={`curve-row${isFocused ? " is-focused" : ""}${isShown ? " is-shown" : ""}${row.numberTrack ? "" : " is-flat"}`}
324
+ style={{ "--depth": row.depth, ...(color === undefined ? {} : { "--curve-color": color }) } as CSSProperties}
325
+ data-testid={`curve-row-${row.subject.path}${row.subject.component === undefined ? "" : `-${row.subject.component}`}`}
326
+ // A NON-NUMERIC ROW IS NOT A CURVE. A colour has no value axis, so
327
+ // its row is a label and the graph keeps whatever it was drawing —
328
+ // rather than blanking, which reads as breakage.
329
+ disabled={!row.numberTrack}
330
+ title={
331
+ !row.numberTrack
332
+ ? `${row.label} has no value axis to draw`
333
+ : isFocused
334
+ ? `Editing the ${row.label} curve`
335
+ : `Edit the ${row.label} curve. Shift-click to ${shownIds.has(row.id) ? "hide it" : "show it beside the focused one"}`
336
+ }
337
+ onClick={(e) => {
338
+ if (e.shiftKey) {
339
+ if (!isFocused) toggleShown(row.id);
340
+ return;
341
+ }
342
+ setFocus(row.subject);
343
+ }}
344
+ >
345
+ <span className={`curve-row-dot${row.numberTrack ? "" : " is-none"}`} aria-hidden="true" />
346
+ <span className="curve-row-name">{row.label}</span>
347
+ {row.numberTrack && <CurveRowValue doc={doc} subject={row.subject} frameOf={localFrameOf} />}
348
+ </button>,
349
+ );
350
+ }
351
+
352
+ const graphReady = model !== null && model.focusedCurve !== null;
353
+
354
+ return (
355
+ <div className="curve-pane" data-testid="curve-pane">
356
+ {menu.menu}
357
+ <div className="curve-toolbar">
358
+ <div className="curve-toolbar-scope" style={{ width: labelWidth }}>
359
+ <span className="curve-count">Curves · {curveCount}</span>
360
+ <button
361
+ type="button"
362
+ className="curve-scope-btn"
363
+ aria-haspopup="menu"
364
+ aria-label={`Show curves: ${scope === "all" ? "All animated" : "Selected"}`}
365
+ onClick={openScopeMenu}
366
+ >
367
+ {scope === "all" ? "All animated" : "Selected"}
368
+ <IconChevronDown aria-hidden="true" />
369
+ </button>
370
+ </div>
371
+ <div className="curve-toolbar-main">
372
+ <div className="curve-presets" role="group" aria-label="Ease presets">
373
+ {TOOLBAR_PRESETS.map((p) => {
374
+ const active = !noKeys && activePreset === p.id;
375
+ return (
376
+ <button
377
+ key={p.id}
378
+ type="button"
379
+ className={`curve-preset${active ? " is-active" : ""}`}
380
+ aria-pressed={active}
381
+ aria-disabled={noKeys}
382
+ title={noKeys ? "Select keyframes to set their ease" : `Set the selected keyframes to ${p.label.toLowerCase()}`}
383
+ onClick={() => applyPreset(p.id)}
384
+ >
385
+ <svg className="curve-preset-glyph" viewBox="0 0 14 14" aria-hidden="true">
386
+ <path d={p.glyph} />
387
+ </svg>
388
+ {p.label}
389
+ </button>
390
+ );
391
+ })}
392
+ </div>
393
+ <span className="curve-divider" aria-hidden="true" />
394
+ <div className="curve-readouts">
395
+ {fields !== null && model !== null ? (
396
+ <KeyframeFields key={`${fields.ref.rowId}:${fields.ref.keyframeId}`} fields={fields} gesture={model.gesture} bare />
397
+ ) : (
398
+ <>
399
+ <span className="curve-readout" title="Select one keyframe on this curve to edit its frame">
400
+ <span className="curve-readout-label">Frame</span>
401
+ <span className="curve-readout-value">–</span>
402
+ </span>
403
+ <span className="curve-readout" title="Select one keyframe on this curve to edit its value">
404
+ <span className="curve-readout-label">Value</span>
405
+ <span className="curve-readout-value">–</span>
406
+ </span>
407
+ </>
408
+ )}
409
+ </div>
410
+ </div>
411
+ <div className="curve-toolbar-view">
412
+ <button
413
+ type="button"
414
+ className={`curve-tool${normalize ? " is-on" : ""}`}
415
+ aria-pressed={normalize}
416
+ title="Scale every curve to its own range, so their timing reads on one axis"
417
+ onClick={() => setNormalize((n) => !n)}
418
+ >
419
+ Normalize
420
+ </button>
421
+ <button
422
+ type="button"
423
+ className="curve-tool is-filled"
424
+ disabled={!graphReady}
425
+ title="Zoom to fit every key on show"
426
+ onClick={() => setFrameAll((n) => n + 1)}
427
+ >
428
+ Frame all
429
+ </button>
430
+ </div>
431
+ </div>
432
+ <div className="curve-body">
433
+ <div className="curve-rows" style={{ width: labelWidth }}>
434
+ <div className="curve-list">{list}</div>
435
+ {curveCount > 0 && <div className="curve-hint">Click a curve to edit. Shift-click to show more.</div>}
436
+ </div>
437
+ <div className="curve-graph">
438
+ {/* THE EMPTY STATES SIT IN THE PLOT, where the curve would be: the
439
+ column beside it is too narrow to hold a sentence, and the plot
440
+ is the place an author looks when nothing is drawn. */}
441
+ {rows.length === 0 ? (
442
+ <div className="curve-empty">
443
+ {subject === null || (subject.layerId === null && scope === "selected")
444
+ ? "Keyframe a property to see its curve."
445
+ : scope === "all"
446
+ ? "Nothing in this shot is animated yet. Add a keyframe from the rows or the inspector."
447
+ : "This element has no keyframes yet. Add one from the rows or the inspector."}
448
+ </div>
449
+ ) : !graphReady ? (
450
+ <div className="curve-empty">
451
+ None of these properties has a value axis to draw. Their keys stay in the rows.
452
+ </div>
453
+ ) : (
454
+ <CurveGraph
455
+ model={model}
456
+ onKeyContextMenu={openKeyMenu}
457
+ normalize={normalize}
458
+ frameAll={frameAll}
459
+ playhead={({ ppf: scale, offset }) => <CurvePlayhead ppf={scale} offset={offset} frameOf={localFrameOf} />}
460
+ />
461
+ )}
462
+ </div>
463
+ </div>
464
+ </div>
465
+ );
466
+ }
467
+
468
+ /** The graph's playhead, drawn inside `CurveGraph` at its scale and inset, and
469
+ * the pane's one leaf that follows the frame with `CurveRowValue`: a played
470
+ * frame repaints a line, never the pane or the graph. */
471
+ function CurvePlayhead({
472
+ ppf,
473
+ offset,
474
+ frameOf,
475
+ }: {
476
+ ppf: number;
477
+ offset: number;
478
+ frameOf: (s: ShotEditorState) => number;
479
+ }) {
480
+ const frame = useShotEditorHot(frameOf);
481
+ return (
482
+ <PlayheadLine frame={frame} ppf={ppf} offset={offset} className="curves-playhead">
483
+ <PlayheadChip frame={frame} />
484
+ </PlayheadLine>
485
+ );
486
+ }
487
+
488
+ /** One curve's value at the playhead, in the list. A leaf on the frame that
489
+ * repaints only when its number moves. */
490
+ function CurveRowValue({
491
+ doc,
492
+ subject,
493
+ frameOf,
494
+ }: {
495
+ doc: Doc3D;
496
+ subject: CurveSubject;
497
+ frameOf: (s: ShotEditorState) => number;
498
+ }) {
499
+ const value = useShotEditorHot((s) => curveValueAt(doc, subject, frameOf(s), doc.fps));
500
+ return value === null ? null : <span className="curve-row-value">{formatValue(value, 0.01)}</span>;
501
+ }
502
+
503
+ /** The row id of a subject, or "" for none. THROUGH `curveRowId`, never a
504
+ * second spelling of the encoding: the focused comparison and the row's own
505
+ * key have to be the same string by construction. */
506
+ function curveIdOf(subject: CurveSubject | null): string {
507
+ return subject === null ? "" : curveRowId(subject);
508
+ }