@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,390 @@
1
+ // The Library's Stock sounds: free sounds from Openverse (Freesound effects,
2
+ // Jamendo music), searched from this browser, auditioned, then added to the
3
+ // open Shot or pinned to the board. It fills the catalog column AND the
4
+ // preview column, so the search, the list and the picked sound share one
5
+ // piece of state and nothing about them reaches the store until an add.
6
+ //
7
+ // ONLY SOUNDS A VIDEO MAY USE are listed: CC0, the public domain and CC BY
8
+ // (`OPENVERSE_LICENSES`). The search asks for those, the list drops anything
9
+ // else, and the host refuses anything else again at the add, so the credit a
10
+ // CC BY sound owes is always recorded where every export reads it.
11
+ //
12
+ // ONE AUDITION AT A TIME through one `Audio` element, following the tab's
13
+ // monitor settings, as the pack panel does (`LibrarySoundPanel`).
14
+ import { useEffect, useRef, useState } from "react";
15
+ import type { FormEvent } from "react";
16
+ import { SegmentedControl } from "@dailies-studio/ui";
17
+ import {
18
+ openverseSiteOf,
19
+ creditLineOf,
20
+ creditRequired,
21
+ licenseLabel,
22
+ openverseSourceOf,
23
+ } from "@dailies-studio/shot/doc";
24
+ import type { OpenverseRow, OpenverseSoundKind } from "@dailies-studio/shot/doc";
25
+ import { openverseStatus } from "../../api.js";
26
+ import { ADD_ICON, PLAY_ICON, SEARCH_ICON, STOP_ICON } from "../../icons.js";
27
+ import { useShotEditor, useStore } from "../../state/context.js";
28
+ import { openBoardForPin, placeInShotRefusal } from "../../state/store.js";
29
+ import { formatDuration } from "../timeline/model.js";
30
+ import { SoundWave } from "./LibrarySoundPanel.js";
31
+ import { STOCK_LENGTHS, StockSearchError, lengthLabel, searchStock, withinLength } from "./stockSearch.js";
32
+
33
+ type Status = { kind: "idle" } | { kind: "loading" } | { kind: "error"; message: string } | { kind: "done" };
34
+
35
+ const KIND_WORDS: Record<OpenverseSoundKind, string> = { sfx: "Effects", music: "Music" };
36
+
37
+ /** Why this host cannot add a stock sound: a hosted deployment with no
38
+ * registered Openverse app. Searching and listening still work. */
39
+ const NOT_SET_UP = "Adding stock sounds is not set up on this server yet. You can still search and listen.";
40
+
41
+ export function StockSounds({ canAdd }: { canAdd: boolean }) {
42
+ const store = useStore();
43
+ const muted = useShotEditor((s) => s.previewMuted);
44
+ const volume = useShotEditor((s) => s.previewVolume);
45
+ const placeRefusal = useShotEditor((s) => placeInShotRefusal(s, "sound"));
46
+ const boardOpen = useShotEditor((s) => s.storyboardOpen);
47
+ const [draft, setDraft] = useState("");
48
+ const [kind, setKind] = useState<OpenverseSoundKind>("sfx");
49
+ const [maxSeconds, setMaxSeconds] = useState<number | null>(null);
50
+ const [searched, setSearched] = useState<{ query: string; kind: OpenverseSoundKind } | null>(null);
51
+ const [rows, setRows] = useState<OpenverseRow[]>([]);
52
+ const [page, setPage] = useState({ page: 0, pageCount: 0 });
53
+ const [status, setStatus] = useState<Status>({ kind: "idle" });
54
+ const [picked, setPicked] = useState<string | null>(null);
55
+ const [playing, setPlaying] = useState<string | null>(null);
56
+ const [busy, setBusy] = useState(false);
57
+ const [error, setError] = useState<string | null>(null);
58
+ const audio = useRef<HTMLAudioElement | null>(null);
59
+ // The newest search wins: an older answer that lands late is dropped.
60
+ const ticket = useRef(0);
61
+ // Whether this host can add one. Unknown until asked, and a failed ask
62
+ // leaves the buttons on: the add itself then says what went wrong.
63
+ const [settable, setSettable] = useState(true);
64
+ useEffect(() => {
65
+ let alive = true;
66
+ void openverseStatus().then(
67
+ (status) => {
68
+ if (alive) setSettable(status.imports);
69
+ },
70
+ () => {},
71
+ );
72
+ return () => {
73
+ alive = false;
74
+ };
75
+ }, []);
76
+
77
+ useEffect(
78
+ () => () => {
79
+ audio.current?.pause();
80
+ audio.current = null;
81
+ },
82
+ [],
83
+ );
84
+ useEffect(() => {
85
+ if (audio.current === null) return;
86
+ audio.current.muted = muted;
87
+ audio.current.volume = volume;
88
+ }, [muted, volume]);
89
+
90
+ const run = (query: string, forKind: OpenverseSoundKind, nextPage: number): void => {
91
+ const mine = ++ticket.current;
92
+ setStatus({ kind: "loading" });
93
+ void searchStock(query, forKind, nextPage)
94
+ .then((answer) => {
95
+ if (mine !== ticket.current) return;
96
+ setRows((held) => (nextPage === 1 ? answer.rows : [...held, ...answer.rows.filter((r) => !held.some((h) => h.id === r.id))]));
97
+ setPage({ page: answer.page, pageCount: answer.pageCount });
98
+ setStatus({ kind: "done" });
99
+ })
100
+ .catch((e: unknown) => {
101
+ if (mine !== ticket.current) return;
102
+ setStatus({ kind: "error", message: e instanceof StockSearchError ? e.message : "The search failed. Try again." });
103
+ });
104
+ };
105
+
106
+ const submit = (event?: FormEvent): void => {
107
+ event?.preventDefault();
108
+ const query = draft.trim();
109
+ if (query === "") return;
110
+ setSearched({ query, kind });
111
+ setPicked(null);
112
+ run(query, kind, 1);
113
+ };
114
+
115
+ const pickKind = (next: OpenverseSoundKind): void => {
116
+ setKind(next);
117
+ setMaxSeconds(null);
118
+ // A kind is a different site, so a standing search is asked again there.
119
+ if (searched !== null) {
120
+ setSearched({ query: searched.query, kind: next });
121
+ setPicked(null);
122
+ run(searched.query, next, 1);
123
+ }
124
+ };
125
+
126
+ const shown = withinLength(rows, maxSeconds);
127
+ const selected = shown.find((row) => row.id === picked) ?? shown[0] ?? null;
128
+
129
+ const stop = (): void => {
130
+ audio.current?.pause();
131
+ audio.current = null;
132
+ setPlaying(null);
133
+ };
134
+ const audition = (row: OpenverseRow): void => {
135
+ setPicked(row.id);
136
+ if (playing === row.id) {
137
+ stop();
138
+ return;
139
+ }
140
+ audio.current?.pause();
141
+ const el = new Audio(row.url);
142
+ el.muted = muted;
143
+ el.volume = volume;
144
+ const done = (): void => {
145
+ if (audio.current !== el) return;
146
+ audio.current = null;
147
+ setPlaying(null);
148
+ };
149
+ el.addEventListener("ended", done);
150
+ el.addEventListener("error", done);
151
+ audio.current = el;
152
+ setPlaying(row.id);
153
+ void el.play().catch(done);
154
+ };
155
+
156
+ const act = (row: OpenverseRow, mode: "place" | "pin"): void => {
157
+ if (mode === "pin" && !openBoardForPin(store)) return;
158
+ setBusy(true);
159
+ setError(null);
160
+ store.dismissError();
161
+ void store
162
+ .addStockSound(row.id, mode)
163
+ .then(() => {
164
+ const message = store.getState().error;
165
+ if (message !== null) setError(message);
166
+ else if (mode === "place" && store.getState().libraryBrowserOpen) store.setLibraryBrowserOpen(false);
167
+ })
168
+ .finally(() => setBusy(false));
169
+ };
170
+
171
+ // Further pages, whenever there are any: a page whose rows all fall
172
+ // outside the length, or the policy, is not the end of the search.
173
+ const more =
174
+ searched !== null && page.page < page.pageCount ? (
175
+ <button
176
+ type="button"
177
+ className="btn is-outline"
178
+ data-testid="stock-more"
179
+ disabled={status.kind === "loading"}
180
+ onClick={() => run(searched.query, searched.kind, page.page + 1)}
181
+ >
182
+ {status.kind === "loading" ? "Loading…" : "Load more"}
183
+ </button>
184
+ ) : null;
185
+ let body;
186
+ if (status.kind === "idle") {
187
+ body = (
188
+ <p className="library-preview-note" role="status">
189
+ Search Freesound effects or Jamendo music. Only sounds you may use in a video are listed: CC0, public domain and CC BY.
190
+ </p>
191
+ );
192
+ } else if (status.kind === "loading" && rows.length === 0) {
193
+ body = <p className="library-preview-note" role="status">Searching Openverse…</p>;
194
+ } else if (status.kind === "error" && rows.length === 0) {
195
+ body = (
196
+ <p className="library-preview-error" role="alert" data-testid="stock-error">
197
+ {status.message}
198
+ </p>
199
+ );
200
+ } else if (shown.length === 0) {
201
+ body = (
202
+ <>
203
+ <p className="library-preview-note" role="status">
204
+ {rows.length === 0
205
+ ? more === null
206
+ ? `No free ${KIND_WORDS[kind].toLowerCase()} match ${JSON.stringify(searched?.query ?? "")}. Try other words.`
207
+ : "Nothing on this page may be used in a video. Load more to look further."
208
+ : `None of these is ${lengthLabel(maxSeconds).toLowerCase()}.`}
209
+ </p>
210
+ {more}
211
+ </>
212
+ );
213
+ } else {
214
+ body = (
215
+ <>
216
+ <ul className="library-sound-list" aria-label="Stock sounds" data-testid="stock-list">
217
+ {shown.map((row) => {
218
+ const live = playing === row.id;
219
+ const on = row === selected;
220
+ return (
221
+ <li key={row.id} className={on ? "library-sound-row is-selected" : "library-sound-row"} data-id={row.id}>
222
+ <button type="button" className="library-sound-play" aria-label={live ? `Stop ${row.title}` : `Play ${row.title}`} onClick={() => audition(row)}>
223
+ {live ? STOP_ICON : PLAY_ICON}
224
+ </button>
225
+ <button type="button" className="library-sound-pick" aria-pressed={on} onClick={() => setPicked(row.id)}>
226
+ <span className="library-sound-name">
227
+ {row.title}
228
+ {row.creator === null ? null : <span className="stock-sound-creator"> · {row.creator}</span>}
229
+ </span>
230
+ <span className="stock-sound-license">{licenseLabel(row.license, "")}</span>
231
+ <span className="library-sound-length">{formatDuration((row.durationMs ?? 0) / 1000)}</span>
232
+ </button>
233
+ </li>
234
+ );
235
+ })}
236
+ </ul>
237
+ {status.kind === "error" ? (
238
+ <p className="library-preview-error" role="alert">
239
+ {status.message}
240
+ </p>
241
+ ) : null}
242
+ {more}
243
+ </>
244
+ );
245
+ }
246
+
247
+ const source = selected === null ? null : openverseSourceOf(selected);
248
+ const site = selected === null ? null : openverseSiteOf(selected.source);
249
+
250
+ return (
251
+ <>
252
+ <section className="library-catalog" aria-label="Stock sounds" data-testid="stock-sounds">
253
+ <header className="library-catalog-head">
254
+ <div>
255
+ <h2>Stock sounds</h2>
256
+ <p>Free sounds from Openverse, safe to use in a video.</p>
257
+ </div>
258
+ </header>
259
+ <form className="library-search-row" role="search" onSubmit={submit}>
260
+ {/* Searched on Enter, never per key: each search spends the
261
+ person's own Openverse allowance. */}
262
+ <label className="search-field">
263
+ {SEARCH_ICON}
264
+ <input
265
+ type="search"
266
+ value={draft}
267
+ placeholder={kind === "sfx" ? "Search effects, then press Enter" : "Search music, then press Enter"}
268
+ aria-label="Search stock sounds"
269
+ data-testid="stock-search"
270
+ onChange={(event) => setDraft(event.target.value)}
271
+ />
272
+ </label>
273
+ <select
274
+ className="select is-quiet"
275
+ aria-label="Length"
276
+ data-testid="stock-length"
277
+ value={maxSeconds === null ? "" : String(maxSeconds)}
278
+ onChange={(event) => setMaxSeconds(event.target.value === "" ? null : Number(event.target.value))}
279
+ >
280
+ {STOCK_LENGTHS[kind].map((s) => (
281
+ <option key={s ?? "any"} value={s === null ? "" : String(s)}>
282
+ {lengthLabel(s)}
283
+ </option>
284
+ ))}
285
+ </select>
286
+ </form>
287
+ <SegmentedControl
288
+ ariaLabel="Kind of sound"
289
+ className="stock-kind"
290
+ value={kind}
291
+ options={[
292
+ { value: "sfx", label: "Effects", testId: "stock-kind-sfx" },
293
+ { value: "music", label: "Music", testId: "stock-kind-music" },
294
+ ]}
295
+ onChange={(next) => pickKind(next as OpenverseSoundKind)}
296
+ />
297
+ {body}
298
+ </section>
299
+ <aside className="library-preview is-sounds" aria-label="Preview: stock sound" data-testid="stock-preview">
300
+ {selected === null || source === null ? (
301
+ <p className="library-preview-empty">Pick a sound to preview it here.</p>
302
+ ) : (
303
+ <>
304
+ <div className="library-preview-purpose">
305
+ <h2 className="library-preview-name">{selected.title}</h2>
306
+ <p className="library-preview-note">
307
+ {selected.creator === null ? "" : `by ${selected.creator} · `}
308
+ {site?.name ?? selected.source} · {formatDuration((selected.durationMs ?? 0) / 1000)}
309
+ </p>
310
+ </div>
311
+ <div className="library-sound-selected" data-testid="stock-selected">
312
+ {/* Jamendo's files carry no CORS header, so their envelope
313
+ cannot be read here: the well stays empty, the audition
314
+ still plays. */}
315
+ {/* Drawn once the person picks or plays the row, never for the
316
+ one shown first: drawing it fetches the file from Freesound. */}
317
+ {selected.source === "freesound" && (picked === selected.id || playing === selected.id) ? (
318
+ <SoundWave url={selected.url} />
319
+ ) : null}
320
+ <p className="stock-sound-terms" data-testid="stock-license">
321
+ <strong>{licenseLabel(source.license, source.licenseVersion)}</strong>
322
+ {creditRequired(source.license)
323
+ ? " · Credit required. Dailies keeps the line and puts it in your export."
324
+ : " · No credit needed."}
325
+ </p>
326
+ <p className="stock-sound-credit" data-testid="stock-credit">
327
+ {creditLineOf(source)}
328
+ </p>
329
+ <a className="stock-sound-link" href={selected.landingUrl} target="_blank" rel="noopener noreferrer">
330
+ Open on {site?.name ?? selected.source}
331
+ </a>
332
+ <button
333
+ type="button"
334
+ className="btn is-primary is-lg"
335
+ data-testid="stock-add"
336
+ disabled={!canAdd || busy || !settable}
337
+ aria-disabled={placeRefusal !== null || undefined}
338
+ title={
339
+ !settable
340
+ ? NOT_SET_UP
341
+ : !canAdd
342
+ ? "Open a Project to add sounds."
343
+ : (placeRefusal ?? "Add this sound at the playhead of the open Shot.")
344
+ }
345
+ onClick={() => {
346
+ if (placeRefusal === null) act(selected, "place");
347
+ }}
348
+ >
349
+ {ADD_ICON}
350
+ {busy ? "Adding…" : "Add to shot"}
351
+ </button>
352
+ <button
353
+ type="button"
354
+ className="btn is-outline is-lg"
355
+ data-testid="stock-pin"
356
+ disabled={!canAdd || busy || !settable}
357
+ title={
358
+ !settable
359
+ ? NOT_SET_UP
360
+ : canAdd
361
+ ? `Add this sound and pin it as a card the agent reads.${boardOpen ? "" : " Opens the storyboard."}`
362
+ : "Open a Project to add sounds."
363
+ }
364
+ onClick={() => act(selected, "pin")}
365
+ >
366
+ Pin to board
367
+ </button>
368
+ {error === null ? null : (
369
+ <p className="library-preview-error" role="alert" data-testid="stock-add-error">
370
+ {error}
371
+ </p>
372
+ )}
373
+ <p className="library-preview-hint" data-testid="stock-hint">
374
+ {!settable
375
+ ? NOT_SET_UP
376
+ : selected.source === "freesound"
377
+ ? "Freesound shares a preview-quality MP3, which is what is added."
378
+ : "Add places it in the open Shot. Pin saves it for later."}
379
+ </p>
380
+ </div>
381
+ </>
382
+ )}
383
+ <footer className="library-preview-meta">
384
+ <span>Openverse</span>
385
+ <span>{KIND_WORDS[kind]}</span>
386
+ </footer>
387
+ </aside>
388
+ </>
389
+ );
390
+ }
@@ -0,0 +1,147 @@
1
+ // What the Library browse surface shows, as pure functions over catalog rows:
2
+ // which rows, in which order, under which heading, and the words around them.
3
+ // No React and no store, so every rule here is one unit test away.
4
+ import { libraryDisplayName } from "@dailies-studio/shot/doc";
5
+ import type { ReferenceItem3D } from "@dailies-studio/shot/doc";
6
+ import type { LibraryItem, LibraryItemKind } from "../../api.js";
7
+ import { matchesLibraryItem } from "../../state/storyboardModel.js";
8
+
9
+ /** A rail row: one catalog kind, every kind at once, or Stock sounds, which
10
+ * is no catalog kind: Openverse's free sounds, searched from the browser
11
+ * (`StockSounds`). */
12
+ export type LibraryCategory = "all" | LibraryItemKind | "stock" | "generated";
13
+
14
+ /** The rail, top to bottom. Components lead because a row with no `kind` is
15
+ * one (the registry's own default) and it is the kind an author reaches for
16
+ * first; the rest follow the order the library skill introduces them. */
17
+ export const LIBRARY_KINDS: readonly LibraryItemKind[] = ["component", "blueprint", "material", "shot", "images", "sounds"];
18
+ export const LIBRARY_CATEGORIES: readonly LibraryCategory[] = ["all", ...LIBRARY_KINDS, "stock", "generated"];
19
+
20
+ export const CATEGORY_LABEL: Record<LibraryCategory, string> = {
21
+ all: "All items",
22
+ component: "Components",
23
+ blueprint: "Blueprints",
24
+ material: "Materials",
25
+ shot: "Shots",
26
+ images: "Images",
27
+ sounds: "Sounds",
28
+ stock: "Stock sounds",
29
+ generated: "Generated sounds",
30
+ };
31
+
32
+ /** The sentence under each catalog heading. Components and Sounds are Paper's
33
+ * own words; the rest are written to the same pattern: what the kind is for,
34
+ * in one line, with no product jargon. */
35
+ export const CATEGORY_SUBTITLE: Record<LibraryCategory, string> = {
36
+ all: "Everything this build ships, one kind at a time.",
37
+ component: "Ready-made pieces of motion. Preview one, then make it yours.",
38
+ blueprint: "Step-by-step builds to follow inside a shot.",
39
+ material: "Surfaces that give a model its look.",
40
+ shot: "Finished shots to learn from or build on.",
41
+ images: "Picture packs to bring into this Project.",
42
+ sounds: "Find the sound that makes a moment land.",
43
+ stock: "Free sounds from Openverse, safe to use in a video.",
44
+ generated: "Generated sounds you can open: every one added to a Project, and the rest for a day.",
45
+ };
46
+
47
+ /** A sound pack is counted as a pack, not an item: what the author picks
48
+ * there is one sound out of it, so "12 items" would promise twelve sounds. */
49
+ export const countNoun = (category: LibraryCategory): "item" | "pack" => (category === "sounds" ? "pack" : "item");
50
+
51
+ /** "1 item", "2 items", "0 items". */
52
+ export function plural(n: number, noun: string): string {
53
+ return `${n} ${n === 1 ? noun : `${noun}s`}`;
54
+ }
55
+
56
+ export const kindOf = (row: LibraryItem): LibraryItemKind => row.kind ?? "component";
57
+
58
+ /** Whether Add places this row as a layer: a component, and only a three
59
+ * host can be an instance layer (`instanceHostRefusal`). The rest pin. */
60
+ export const placesAsLayer = (row: LibraryItem): boolean => kindOf(row) === "component" && row.host === "three";
61
+
62
+ /** The name a card prints. ONE helper for the whole surface, shared with the
63
+ * CLI (`libraryDisplayName` in @dailies-studio/shot/doc): the manifest's
64
+ * `displayName` when the server sends one, else the code name in sentence
65
+ * case (`SelectionLoop` -> "Selection loop"). */
66
+ export const displayNameOf = (row: LibraryItem): string => libraryDisplayName(row);
67
+
68
+ export type LibrarySort = "curated" | "name";
69
+
70
+ export const SORT_LABEL: Record<LibrarySort, string> = { curated: "Curated", name: "Name" };
71
+
72
+ /** The search box's rule: every field the old cards printed and the id
73
+ * (`matchesLibraryItem`), plus the printed name, which is no longer `name`. */
74
+ export function matchesQuery(row: LibraryItem, query: string): boolean {
75
+ const q = query.trim().toLowerCase();
76
+ return q === "" || matchesLibraryItem(row, q) || displayNameOf(row).toLowerCase().includes(q);
77
+ }
78
+
79
+ /**
80
+ * The rows one category shows, searched and sorted. "Curated" is the
81
+ * catalog's own order, which is the order the build ships them in and the
82
+ * only order anyone chose; "Name" sorts by the PRINTED name, so the list
83
+ * reads alphabetically on screen rather than by code name.
84
+ */
85
+ export function visibleRows(
86
+ rows: readonly LibraryItem[],
87
+ category: LibraryCategory,
88
+ query: string,
89
+ sort: LibrarySort,
90
+ ): LibraryItem[] {
91
+ const out = rows.filter((row) => (category === "all" || kindOf(row) === category) && matchesQuery(row, query));
92
+ if (sort === "name") {
93
+ out.sort((a, b) => displayNameOf(a).localeCompare(displayNameOf(b), undefined, { sensitivity: "base" }));
94
+ }
95
+ return out;
96
+ }
97
+
98
+ /** "All items" as sections, one per kind in rail order, empty kinds left
99
+ * out so a search does not print a column of empty headings. */
100
+ export function groupByKind(rows: readonly LibraryItem[]): Array<{ kind: LibraryItemKind; rows: LibraryItem[] }> {
101
+ return LIBRARY_KINDS.map((kind) => ({ kind, rows: rows.filter((row) => kindOf(row) === kind) })).filter(
102
+ (section) => section.rows.length > 0,
103
+ );
104
+ }
105
+
106
+ /** Rows per rail category, "all" included. Stock sounds has no count: its
107
+ * rows are a search away, never a list this build holds. */
108
+ export function categoryCounts(rows: readonly LibraryItem[]): Map<LibraryCategory, number> {
109
+ const out = new Map<LibraryCategory, number>(["all" as const, ...LIBRARY_KINDS].map((c) => [c, 0]));
110
+ for (const row of rows) {
111
+ const k = kindOf(row);
112
+ out.set(k, (out.get(k) ?? 0) + 1);
113
+ }
114
+ out.set("all", rows.length);
115
+ return out;
116
+ }
117
+
118
+ /**
119
+ * The rows the Library shows: `source === "shipped"` and NOT `!== "builtin"`.
120
+ * The catalog concatenates three lists and the third is `shared`: a component
121
+ * vendored from a library this build does not ship, which carries no kind, no
122
+ * tags, no thumb and no docs. A shared row has nothing to draw on a card and
123
+ * nothing to pick, and the loose filter would have put every one of them in
124
+ * front of the author. ONE filter for the browser and the shot board's
125
+ * component picker (its Dailies Library section).
126
+ */
127
+ export function shippedRows(catalog: readonly LibraryItem[]): LibraryItem[] {
128
+ return catalog.filter((row) => row.source === "shipped");
129
+ }
130
+
131
+ /**
132
+ * HOW MANY CARDS ON THE BOARD CITE EACH ROW, by item id. A component card
133
+ * names the registry id the install made (`installedAs`), every other kind
134
+ * names the item itself.
135
+ */
136
+ export function onBoardCounts(
137
+ shipped: readonly LibraryItem[],
138
+ references: readonly ReferenceItem3D[],
139
+ ): Map<string, number> {
140
+ const byComponent = new Map(shipped.flatMap((row) => (row.installedAs === undefined ? [] : [[row.installedAs, row.id]])));
141
+ const out = new Map<string, number>();
142
+ for (const ref of references) {
143
+ const id = ref.type === "library" ? ref.library : ref.type === "component" && ref.component !== undefined ? byComponent.get(ref.component) : undefined;
144
+ if (id !== undefined) out.set(id, (out.get(id) ?? 0) + 1);
145
+ }
146
+ return out;
147
+ }
@@ -0,0 +1,77 @@
1
+ // The Library's Stock sounds search, run from this browser straight to
2
+ // Openverse, the way openverse.org itself runs: each person spends their own
3
+ // anonymous allowance (20 searches a minute, 200 a day), so one busy
4
+ // workspace never uses up another's. The editor sends nothing but the words
5
+ // typed: no cookie (`credentials: "omit"`) and no page address
6
+ // (`referrerPolicy: "no-referrer"`).
7
+ //
8
+ // Adding a sound never trusts this answer. The add names the row's id, and
9
+ // the host reads the row again itself before it stores a byte or records a
10
+ // license (`/api/openverse/add`).
11
+ import { fitsSeconds, openverseRowRefusal, openverseSearchUrl, parseOpenverseRow } from "@dailies-studio/shot/doc";
12
+ import type { OpenverseRow, OpenverseSoundKind } from "@dailies-studio/shot/doc";
13
+
14
+ export interface StockPage {
15
+ rows: OpenverseRow[];
16
+ page: number;
17
+ pageCount: number;
18
+ }
19
+
20
+ /** A search that did not answer, in words for the person. */
21
+ export class StockSearchError extends Error {}
22
+
23
+ /** One page of a search: only rows the license policy takes. Searching costs
24
+ * the person's allowance, so the view calls this on Enter, never per key. */
25
+ export async function searchStock(
26
+ query: string,
27
+ kind: OpenverseSoundKind,
28
+ page: number,
29
+ fetchImpl: typeof fetch = (input, init) => fetch(input, init),
30
+ ): Promise<StockPage> {
31
+ let res: Response;
32
+ try {
33
+ res = await fetchImpl(openverseSearchUrl({ query, kind, page }), {
34
+ credentials: "omit",
35
+ referrerPolicy: "no-referrer",
36
+ headers: { accept: "application/json" },
37
+ });
38
+ } catch {
39
+ throw new StockSearchError("Openverse could not be reached. Check your connection and try again.");
40
+ }
41
+ if (res.status === 429) {
42
+ const wait = Number(res.headers.get("retry-after"));
43
+ throw new StockSearchError(
44
+ `Openverse's search limit was reached. Try again in ${Number.isFinite(wait) && wait > 0 ? `${Math.ceil(wait)} seconds` : "a minute"}.`,
45
+ );
46
+ }
47
+ if (!res.ok) throw new StockSearchError(`Openverse answered ${res.status}. Try again in a minute.`);
48
+ const body = (await res.json().catch(() => null)) as { results?: unknown; page_count?: unknown } | null;
49
+ if (body === null || !Array.isArray(body.results)) throw new StockSearchError("Openverse's answer could not be read.");
50
+ return {
51
+ rows: body.results.flatMap((raw) => {
52
+ const row = parseOpenverseRow(raw);
53
+ return row !== null && openverseRowRefusal(row) === null ? [row] : [];
54
+ }),
55
+ page,
56
+ pageCount: typeof body.page_count === "number" ? body.page_count : page,
57
+ };
58
+ }
59
+
60
+ /** The length limits offered per kind, in seconds; null is no limit. A short
61
+ * effect and a long bed want different steps. */
62
+ export const STOCK_LENGTHS: Record<OpenverseSoundKind, readonly (number | null)[]> = {
63
+ sfx: [null, 1, 3, 10, 30],
64
+ music: [null, 30, 60, 180],
65
+ };
66
+
67
+ /** The rows within the length limit. Filtered here, not asked of Openverse,
68
+ * whose own length filter answers rows far outside its bands. */
69
+ export function withinLength(rows: readonly OpenverseRow[], maxSeconds: number | null): OpenverseRow[] {
70
+ return rows.filter((row) => fitsSeconds(row, maxSeconds ?? undefined));
71
+ }
72
+
73
+ /** "Up to 3s", "Any length". */
74
+ export function lengthLabel(seconds: number | null): string {
75
+ if (seconds === null) return "Any length";
76
+ return seconds < 60 ? `Up to ${seconds}s` : `Up to ${seconds / 60} min`;
77
+ }