@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,293 @@
1
+ // The Library's right column for a SOUND PACK: the pack's files in manifest
2
+ // order, an audition per file, the picked file's waveform, the Add that places
3
+ // that one file as a clip in the open Shot, and the Pin that lands it as a
4
+ // sound card. A pack is never a card itself (the validator refuses a `library`
5
+ // card of kind "sounds"), so this column adds one sound at a time and the
6
+ // browser's `onAdd` never sees a pack.
7
+ //
8
+ // ONE AUDITION AT A TIME, through ONE `Audio` element owned by this panel. A
9
+ // second play stops the first; closing the Library or picking another item
10
+ // unmounts the panel, which stops it. It follows the tab's monitor settings
11
+ // (`previewMuted`, `previewVolume`), the ones the transport's speaker sets,
12
+ // because an author who muted the preview did not mean "except the library".
13
+ //
14
+ // THE BYTES COME FROM THE LIBRARY ROUTE, `libraryFileUrl` (`GET /api/library/
15
+ // items/<id>/files/<name>`), never the Project's asset store: auditioning a
16
+ // sound must not import the pack. The waveform is the timeline's own
17
+ // `loadPeaks` and `drawClipWaveform`, keyed on that URL. `loadPeaks` caches by
18
+ // `asset.path` and fetches `/studio/assets/<path>` by default, so the URL rides
19
+ // in as the path and `fetchBytes` fetches it as it is: a root-relative URL
20
+ // can never collide with a Project asset path, so the shared cache stays
21
+ // honest without a second waveform module.
22
+ import { useEffect, useRef, useState } from "react";
23
+ import { libraryFileLabel, libraryFileUrl } from "@dailies-studio/shot/doc";
24
+ import { drawClipWaveform, loadPeaks } from "@dailies-studio/timeline";
25
+ import type { PeakData } from "@dailies-studio/timeline";
26
+ import type { LibraryItem, LibraryItemDetail } from "../../api.js";
27
+ import { ADD_ICON, CHEVRON_DOWN_ICON, PLAY_ICON, STOP_ICON } from "../../icons.js";
28
+ import { useShotEditor, useStore } from "../../state/context.js";
29
+ import { openBoardForPin, placeInShotRefusal } from "../../state/store.js";
30
+ import { formatDuration } from "../timeline/model.js";
31
+ import { displayNameOf, plural } from "./libraryModel.js";
32
+
33
+ type SoundFile = NonNullable<LibraryItemDetail["sounds"]>["files"][number];
34
+
35
+ const NONE: never[] = [];
36
+
37
+ /** Rows shown before "Show all": Paper's fold, enough to read the pack's
38
+ * range without pushing the waveform and the Add off a laptop screen. */
39
+ export const SOUND_FOLD = 7;
40
+
41
+ /** The bare file name `addStoryboardLibrarySound` matches the import on. */
42
+ const fileNameOf = (file: SoundFile): string => file.path.slice(file.path.lastIndexOf("/") + 1);
43
+
44
+ export function LibrarySoundPanel({
45
+ item,
46
+ canAdd,
47
+ adding,
48
+ }: {
49
+ item: LibraryItem;
50
+ canAdd: boolean;
51
+ adding: boolean;
52
+ }) {
53
+ const store = useStore();
54
+ const detail = useShotEditor((s) => s.libraryItems[item.id]);
55
+ const muted = useShotEditor((s) => s.previewMuted);
56
+ const volume = useShotEditor((s) => s.previewVolume);
57
+ const [failed, setFailed] = useState(false);
58
+ const [picked, setPicked] = useState<string | null>(null);
59
+ const [playing, setPlaying] = useState<string | null>(null);
60
+ const [showAll, setShowAll] = useState(false);
61
+ // ponytail: this add's in-flight flag is the panel's own, so the browser's
62
+ // `adding` does not see it; lift it into the browser if a second add door
63
+ // ever shares this column.
64
+ const [busy, setBusy] = useState(false);
65
+ const [error, setError] = useState<string | null>(null);
66
+ const placeRefusal = useShotEditor((s) => placeInShotRefusal(s, "sound"));
67
+ const boardOpen = useShotEditor((s) => s.storyboardOpen);
68
+ const audio = useRef<HTMLAudioElement | null>(null);
69
+
70
+ useEffect(() => {
71
+ let alive = true;
72
+ void store.loadLibraryItem(item.id).then((d) => {
73
+ if (alive && d === null) setFailed(true);
74
+ });
75
+ return () => {
76
+ alive = false;
77
+ };
78
+ }, [item.id, store]);
79
+ // Stop on unmount: the Library closed, or another item was picked.
80
+ useEffect(
81
+ () => () => {
82
+ audio.current?.pause();
83
+ audio.current = null;
84
+ },
85
+ [],
86
+ );
87
+ // The monitor settings follow live, mid-audition included.
88
+ useEffect(() => {
89
+ if (audio.current === null) return;
90
+ audio.current.muted = muted;
91
+ audio.current.volume = volume;
92
+ }, [muted, volume]);
93
+
94
+ const files = detail?.sounds?.files ?? NONE;
95
+ const name = displayNameOf(item);
96
+ const selected = files.find((f) => f.path === picked) ?? files[0];
97
+ const shown = showAll ? files : files.slice(0, SOUND_FOLD);
98
+
99
+ const stop = (): void => {
100
+ audio.current?.pause();
101
+ audio.current = null;
102
+ setPlaying(null);
103
+ };
104
+ const audition = (file: SoundFile): void => {
105
+ setPicked(file.path);
106
+ if (playing === file.path) {
107
+ stop();
108
+ return;
109
+ }
110
+ audio.current?.pause();
111
+ const el = new Audio(libraryFileUrl(item.id, file.path));
112
+ el.muted = muted;
113
+ el.volume = volume;
114
+ const done = (): void => {
115
+ if (audio.current !== el) return;
116
+ audio.current = null;
117
+ setPlaying(null);
118
+ };
119
+ el.addEventListener("ended", done);
120
+ el.addEventListener("error", done);
121
+ audio.current = el;
122
+ setPlaying(file.path);
123
+ // A refused play (autoplay policy, an unreadable file) ends the audition
124
+ // quietly: the row's button goes back to Play, which is the whole truth.
125
+ void el.play().catch(done);
126
+ };
127
+ // ADD places the sound as a clip at the playhead of the open Shot (D2); PIN
128
+ // makes it a sound card for the agent. Both import that one file only.
129
+ const run = (act: () => Promise<unknown>, placed: boolean): void => {
130
+ setBusy(true);
131
+ setError(null);
132
+ // The store reports its own refusals to the shell's error strip, which
133
+ // sits under this dialog, so the strip is cleared first and whatever the
134
+ // add leaves there is printed here too.
135
+ store.dismissError();
136
+ void act()
137
+ .then(() => {
138
+ const message = store.getState().error;
139
+ if (message !== null) setError(message);
140
+ // A placed sound is on the timeline under this dialog: show it.
141
+ else if (placed && store.getState().libraryBrowserOpen) store.setLibraryBrowserOpen(false);
142
+ })
143
+ .finally(() => setBusy(false));
144
+ };
145
+ const add = (file: SoundFile): void => run(() => store.dropLibraryOnFrame(item.id, fileNameOf(file)), true);
146
+ const pin = (file: SoundFile): void =>
147
+ run(async () => {
148
+ if (openBoardForPin(store)) await store.addStoryboardLibrarySound(null, item.id, fileNameOf(file));
149
+ }, false);
150
+
151
+ let body;
152
+ if (detail === undefined) {
153
+ body = <p className="library-preview-note">{failed ? "The sounds could not be read." : "Loading the sounds…"}</p>;
154
+ } else if (files.length === 0) {
155
+ body = <p className="library-preview-note">This pack ships no sounds.</p>;
156
+ } else {
157
+ body = (
158
+ <>
159
+ <ul className="library-sound-list" aria-label={`Sounds in ${name}`}>
160
+ {shown.map((file) => {
161
+ const label = libraryFileLabel(file);
162
+ const on = file === selected;
163
+ const live = playing === file.path;
164
+ return (
165
+ <li key={file.path} className={on ? "library-sound-row is-selected" : "library-sound-row"} data-file={file.path}>
166
+ <button
167
+ type="button"
168
+ className="library-sound-play"
169
+ aria-label={live ? `Stop ${label}` : `Play ${label}`}
170
+ onClick={() => audition(file)}
171
+ >
172
+ {live ? STOP_ICON : PLAY_ICON}
173
+ </button>
174
+ <button type="button" className="library-sound-pick" aria-pressed={on} onClick={() => setPicked(file.path)}>
175
+ <span className="library-sound-name">{label}</span>
176
+ <span className="library-sound-length">{formatDuration(file.durationMs / 1000)}</span>
177
+ </button>
178
+ </li>
179
+ );
180
+ })}
181
+ </ul>
182
+ {files.length > SOUND_FOLD && !showAll ? (
183
+ <button type="button" className="library-sound-more" onClick={() => setShowAll(true)}>
184
+ Show all {files.length} sounds
185
+ {CHEVRON_DOWN_ICON}
186
+ </button>
187
+ ) : null}
188
+ {selected === undefined ? null : (
189
+ <div className="library-sound-selected" data-testid="library-sound-selected">
190
+ <p className="library-sound-selected-name">
191
+ <span>{libraryFileLabel(selected)}</span>
192
+ <span className="library-sound-length">{formatDuration(selected.durationMs / 1000)}</span>
193
+ </p>
194
+ <SoundWave url={libraryFileUrl(item.id, selected.path)} />
195
+ <button
196
+ type="button"
197
+ className="btn is-primary is-lg"
198
+ data-testid="library-add-sound"
199
+ disabled={!canAdd || adding || busy}
200
+ aria-disabled={placeRefusal !== null || undefined}
201
+ title={!canAdd ? "Open a Project to add library items." : (placeRefusal ?? "Import this sound and place it at the playhead of the open Shot.")}
202
+ onClick={() => {
203
+ if (placeRefusal === null) add(selected);
204
+ }}
205
+ >
206
+ {ADD_ICON}
207
+ {busy ? "Adding…" : "Add to shot"}
208
+ </button>
209
+ <button
210
+ type="button"
211
+ className="btn is-outline is-lg"
212
+ data-testid="library-pin-sound"
213
+ disabled={!canAdd || adding || busy}
214
+ title={canAdd ? `Import this sound and pin it as a card the agent reads.${boardOpen ? "" : " Opens the storyboard."}` : "Open a Project to add library items."}
215
+ onClick={() => pin(selected)}
216
+ >
217
+ Pin to board
218
+ </button>
219
+ {error === null ? null : (
220
+ <p className="library-preview-error" role="alert" data-testid="library-add-error">
221
+ {error}
222
+ </p>
223
+ )}
224
+ <p className="library-preview-hint">Add places it in the open Shot. Pin saves it for later.</p>
225
+ </div>
226
+ )}
227
+ </>
228
+ );
229
+ }
230
+
231
+ return (
232
+ <aside className="library-preview is-sounds" aria-label={`Preview: ${name}`} data-testid="library-preview">
233
+ <div className="library-preview-purpose">
234
+ <h2 className="library-preview-name">{name}</h2>
235
+ {files.length === 0 ? null : <p className="library-preview-note">{plural(files.length, "sound")} · Choose one to preview</p>}
236
+ </div>
237
+ {body}
238
+ <footer className="library-preview-meta">
239
+ <span>Sound pack</span>
240
+ {item.version === undefined ? null : <span>v{item.version}</span>}
241
+ </footer>
242
+ </aside>
243
+ );
244
+ }
245
+
246
+ /** The picked sound's envelope. Decoration: a file that will not decode
247
+ * leaves the well empty, and the canvas is hidden from assistive tech. */
248
+ export function SoundWave({ url }: { url: string }) {
249
+ const canvas = useRef<HTMLCanvasElement | null>(null);
250
+ const [data, setData] = useState<PeakData | null>(null);
251
+ useEffect(() => {
252
+ let alive = true;
253
+ setData(null);
254
+ loadPeaks(
255
+ { path: url },
256
+ {
257
+ fetchBytes: async () => {
258
+ // A stock sound's file is on its site's CDN: asked for with no
259
+ // cookie and no page address, as the search is. A pack sound is
260
+ // the studio's own, read as every asset is.
261
+ const own = new URL(url, location.href).origin === location.origin;
262
+ const res = await fetch(url, own ? {} : { credentials: "omit", referrerPolicy: "no-referrer" });
263
+ if (!res.ok) throw new Error(`${url}: ${res.status}`);
264
+ return await res.arrayBuffer();
265
+ },
266
+ },
267
+ )
268
+ .then((d) => {
269
+ if (alive) setData(d);
270
+ })
271
+ .catch(() => {});
272
+ return () => {
273
+ alive = false;
274
+ };
275
+ }, [url]);
276
+ useEffect(() => {
277
+ const el = canvas.current;
278
+ if (el === null || data === null || data.peaks.length === 0) return;
279
+ drawClipWaveform(el, {
280
+ data,
281
+ fromSeconds: 0,
282
+ toSeconds: data.sourceSeconds || 1,
283
+ widthPx: el.clientWidth || 240,
284
+ heightPx: el.clientHeight || 46,
285
+ color: getComputedStyle(el).color,
286
+ });
287
+ }, [data]);
288
+ return (
289
+ <div className="library-sound-wave">
290
+ <canvas ref={canvas} aria-hidden="true" data-testid="library-sound-wave" data-drawn={data !== null && data.peaks.length > 0 ? "true" : "false"} />
291
+ </div>
292
+ );
293
+ }
@@ -0,0 +1,245 @@
1
+ // The Library's Generated sounds: the workspace's sound history. The sound
2
+ // effects, music beds and voice lines this member made, or that were made for
3
+ // a Project they may view: hosted, every one added to a Project, and the rest
4
+ // for a day after they were made; locally every take in the store, since a
5
+ // take there is a file. It fills the catalog and preview columns, like Stock
6
+ // sounds.
7
+ //
8
+ // ADD places the take as a bed clip at the playhead (`placeTake`, the Generate
9
+ // drawer's own verb), a scored bed at frame 0 where its sections line up, so a
10
+ // take is reused without paying for it again. DELETE takes it out of the
11
+ // history: hosted its waiting bytes go, locally its file stays for any Project
12
+ // that uses it.
13
+ //
14
+ // A PAGE CAN BE EMPTY AND STILL GO ON: hosted, a page reads a bounded number
15
+ // of rows and keeps the ones this member may see, so "Load more" shows
16
+ // whenever there is a `next`, list or no list.
17
+ import { useEffect, useRef, useState } from "react";
18
+ import { SegmentedControl } from "@dailies-studio/ui";
19
+ import { deleteGeneration, generationHistory } from "../../api.js";
20
+ import type { GenerationKind, GenerationTake } from "../../api.js";
21
+ import { ADD_ICON, PLAY_ICON, STOP_ICON } from "../../icons.js";
22
+ import { useShotEditor, useStore } from "../../state/context.js";
23
+ import { formatDuration } from "../timeline/model.js";
24
+
25
+ type Filter = "all" | GenerationKind;
26
+
27
+ const KIND_WORDS: Record<GenerationKind, string> = { sfx: "Sound effect", music: "Music", voice: "Voice" };
28
+
29
+ export function SoundHistory({ canAdd }: { canAdd: boolean }) {
30
+ const store = useStore();
31
+ const muted = useShotEditor((s) => s.previewMuted);
32
+ const volume = useShotEditor((s) => s.previewVolume);
33
+ const [takes, setTakes] = useState<GenerationTake[] | null>(null);
34
+ const [next, setNext] = useState<string | null>(null);
35
+ /** A page on its way: a second "Load more" waits for it. */
36
+ const [loading, setLoading] = useState(false);
37
+ const [failed, setFailed] = useState<string | null>(null);
38
+ const [filter, setFilter] = useState<Filter>("all");
39
+ const [picked, setPicked] = useState<string | null>(null);
40
+ const [playing, setPlaying] = useState<string | null>(null);
41
+ const [busy, setBusy] = useState(false);
42
+ const [error, setError] = useState<string | null>(null);
43
+ const audio = useRef<HTMLAudioElement | null>(null);
44
+
45
+ const load = (before?: string): void => {
46
+ setLoading(true);
47
+ void generationHistory(before)
48
+ .then(
49
+ (page) => {
50
+ setTakes((held) => (before === undefined ? page.takes : [...(held ?? []), ...page.takes]));
51
+ setNext(page.next);
52
+ },
53
+ (e: unknown) => setFailed(e instanceof Error ? e.message : String(e)),
54
+ )
55
+ .finally(() => setLoading(false));
56
+ };
57
+ useEffect(() => {
58
+ load();
59
+ return () => {
60
+ audio.current?.pause();
61
+ audio.current = null;
62
+ };
63
+ }, []);
64
+ useEffect(() => {
65
+ if (audio.current === null) return;
66
+ audio.current.muted = muted;
67
+ audio.current.volume = volume;
68
+ }, [muted, volume]);
69
+
70
+ const shown = (takes ?? []).filter((t) => filter === "all" || t.kind === filter);
71
+ const selected = shown.find((t) => t.id === picked) ?? shown[0] ?? null;
72
+
73
+ const audition = (take: GenerationTake): void => {
74
+ setPicked(take.id);
75
+ audio.current?.pause();
76
+ if (playing === take.id) {
77
+ audio.current = null;
78
+ setPlaying(null);
79
+ return;
80
+ }
81
+ const el = new Audio(take.url);
82
+ el.muted = muted;
83
+ el.volume = volume;
84
+ const done = (): void => {
85
+ if (audio.current !== el) return;
86
+ audio.current = null;
87
+ setPlaying(null);
88
+ };
89
+ el.addEventListener("ended", done);
90
+ el.addEventListener("error", done);
91
+ audio.current = el;
92
+ setPlaying(take.id);
93
+ void el.play().catch(done);
94
+ };
95
+
96
+ const add = (take: GenerationTake): void => {
97
+ setBusy(true);
98
+ setError(null);
99
+ void store
100
+ .placeTake(take, undefined, take.scored === true ? 0 : undefined)
101
+ .then(() => {
102
+ if (store.getState().libraryBrowserOpen) store.setLibraryBrowserOpen(false);
103
+ })
104
+ .catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))
105
+ .finally(() => setBusy(false));
106
+ };
107
+
108
+ const remove = (take: GenerationTake): void => {
109
+ setBusy(true);
110
+ setError(null);
111
+ void deleteGeneration(take.id)
112
+ .then(() => setTakes((held) => (held ?? []).filter((t) => t.id !== take.id)))
113
+ .catch((e: unknown) => setError(e instanceof Error ? e.message : String(e)))
114
+ .finally(() => setBusy(false));
115
+ };
116
+
117
+ const more =
118
+ next === null ? null : (
119
+ <button type="button" className="btn is-outline" data-testid="history-more" disabled={loading} onClick={() => load(next)}>
120
+ {loading ? "Loading…" : "Load more"}
121
+ </button>
122
+ );
123
+ let body;
124
+ if (takes === null) {
125
+ body = (
126
+ <p className="library-preview-note" role="status">
127
+ {failed ?? "Reading the sound history…"}
128
+ </p>
129
+ );
130
+ } else if (shown.length === 0) {
131
+ body = (
132
+ <>
133
+ <p className="library-preview-note" role="status">
134
+ {takes.length === 0
135
+ ? next === null
136
+ ? "Nothing generated yet. Sound mode's Generate makes effects, music and voice lines."
137
+ : "None of the newest takes is yours to see. Load more to look further back."
138
+ : "No takes of this kind yet."}
139
+ </p>
140
+ {more}
141
+ </>
142
+ );
143
+ } else {
144
+ body = (
145
+ <>
146
+ <ul className="library-sound-list" aria-label="Generated sounds" data-testid="history-list">
147
+ {shown.map((take) => {
148
+ const live = playing === take.id;
149
+ const on = take === selected;
150
+ return (
151
+ <li key={take.id} className={on ? "library-sound-row is-selected" : "library-sound-row"} data-id={take.id}>
152
+ <button type="button" className="library-sound-play" aria-label={live ? `Stop ${take.prompt}` : `Play ${take.prompt}`} onClick={() => audition(take)}>
153
+ {live ? STOP_ICON : PLAY_ICON}
154
+ </button>
155
+ <button type="button" className="library-sound-pick" aria-pressed={on} onClick={() => setPicked(take.id)}>
156
+ <span className="library-sound-name">{take.prompt}</span>
157
+ <span className="stock-sound-license">{KIND_WORDS[take.kind]}</span>
158
+ {take.seconds === null ? null : <span className="library-sound-length">{formatDuration(take.seconds)}</span>}
159
+ </button>
160
+ </li>
161
+ );
162
+ })}
163
+ </ul>
164
+ {more}
165
+ </>
166
+ );
167
+ }
168
+
169
+ return (
170
+ <>
171
+ <section className="library-catalog" aria-label="Generated sounds" data-testid="sound-history">
172
+ <header className="library-catalog-head">
173
+ <div>
174
+ <h2>Generated sounds</h2>
175
+ <p>Generated sounds you can open: every one added to a Project, and the rest for a day.</p>
176
+ </div>
177
+ </header>
178
+ <SegmentedControl
179
+ ariaLabel="Kind of take"
180
+ className="stock-kind"
181
+ value={filter}
182
+ options={[
183
+ { value: "all", label: "All" },
184
+ { value: "sfx", label: "Effects" },
185
+ { value: "music", label: "Music" },
186
+ { value: "voice", label: "Voice" },
187
+ ]}
188
+ onChange={(next) => setFilter(next as Filter)}
189
+ />
190
+ {body}
191
+ </section>
192
+ <aside className="library-preview is-sounds" aria-label="Preview: generated sound" data-testid="history-preview">
193
+ {selected === null ? (
194
+ <p className="library-preview-empty">Pick a take to preview it here.</p>
195
+ ) : (
196
+ <>
197
+ <div className="library-preview-purpose">
198
+ <h2 className="library-preview-name">{selected.prompt}</h2>
199
+ <p className="library-preview-note">
200
+ {KIND_WORDS[selected.kind]}
201
+ {selected.seconds === null ? "" : ` · ${formatDuration(selected.seconds)}`} · {new Date(selected.createdAt).toLocaleDateString()}
202
+ </p>
203
+ </div>
204
+ <div className="library-sound-selected" data-testid="history-selected">
205
+ <button
206
+ type="button"
207
+ className="btn is-primary is-lg"
208
+ data-testid="history-add"
209
+ disabled={!canAdd || busy}
210
+ title={
211
+ !canAdd
212
+ ? "Open a Project to add sounds."
213
+ : selected.scored === true
214
+ ? "Scored to a film's cuts: added at frame 0, where its sections line up."
215
+ : "Add this take at the playhead."
216
+ }
217
+ onClick={() => add(selected)}
218
+ >
219
+ {ADD_ICON}
220
+ {busy ? "Working…" : selected.scored === true ? "Add at the start" : "Add at playhead"}
221
+ </button>
222
+ <button type="button" className="btn is-outline is-lg" data-testid="history-delete" disabled={busy} onClick={() => remove(selected)}>
223
+ Delete from history
224
+ </button>
225
+ {error === null ? null : (
226
+ <p className="library-preview-error" role="alert" data-testid="history-error">
227
+ {error}
228
+ </p>
229
+ )}
230
+ <p className="library-preview-hint">
231
+ {selected.kept === true
232
+ ? "Kept: it stays here until you delete it."
233
+ : "Not added to a Project yet: it leaves the history a day after it was made."}
234
+ </p>
235
+ </div>
236
+ </>
237
+ )}
238
+ <footer className="library-preview-meta">
239
+ <span>Sound history</span>
240
+ <span>{takes === null ? "" : `${takes.length}${next === null ? "" : "+"} take${takes.length === 1 ? "" : "s"}`}</span>
241
+ </footer>
242
+ </aside>
243
+ </>
244
+ );
245
+ }