@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,158 @@
1
+ // OTHER PEOPLE'S CURSORS ON THE BOARD (lane R7, Paper page 21): an arrow in
2
+ // each person's colour with their name beside it. Studio has no cursors.
3
+ //
4
+ // A cursor travels as `u,v` across the board canvas's whole scrolled content
5
+ // (`scrollWidth` by `scrollHeight`), tagged `space: "canvas"`, so it lands on
6
+ // the same card wherever each person has scrolled. A cursor in any other space
7
+ // (an older build's frame-space "board") is not drawn: its `u,v` measure
8
+ // something else. Only a tab on the board sends one: leaving the
9
+ // canvas, or the board, clears it at once; on it, the presence store sends at
10
+ // most 15 a second.
11
+ //
12
+ // ON A SHOT BOARD the same layer sits on the sketch paper (`where`, the
13
+ // sketch id): `u,v` across the paper, tagged `space: "sketch"` with that id, so
14
+ // a cursor shows only to people on the same sketch, and the board's cursors
15
+ // never show there.
16
+ import { useEffect, useState } from "react";
17
+ import type { SyncCursor } from "@dailies-studio/shot/doc";
18
+ import { useStore } from "../../state/context.js";
19
+ import { usePresence } from "../header/PresenceStack.js";
20
+
21
+ /** The canvas as the mapping reads it: its client box, scroll offset and
22
+ * scrolled content size. */
23
+ export type BoardBox = {
24
+ left: number;
25
+ top: number;
26
+ width: number;
27
+ height: number;
28
+ scrollLeft: number;
29
+ scrollTop: number;
30
+ scrollWidth: number;
31
+ scrollHeight: number;
32
+ };
33
+
34
+ const boxOf = (el: HTMLElement, paper: boolean): BoardBox => {
35
+ const r = el.getBoundingClientRect();
36
+ // The paper does not scroll, and an item hanging off its edge must not
37
+ // stretch what `u,v` measure.
38
+ if (paper) return { left: r.left, top: r.top, width: r.width, height: r.height, scrollLeft: 0, scrollTop: 0, scrollWidth: r.width, scrollHeight: r.height };
39
+ return {
40
+ left: r.left,
41
+ top: r.top,
42
+ width: r.width,
43
+ height: r.height,
44
+ scrollLeft: el.scrollLeft,
45
+ scrollTop: el.scrollTop,
46
+ scrollWidth: el.scrollWidth,
47
+ scrollHeight: el.scrollHeight,
48
+ };
49
+ };
50
+
51
+ /** A client point as `u,v` across the canvas content, or null off the canvas. */
52
+ export function boardPoint(box: BoardBox, x: number, y: number): { x: number; y: number } | null {
53
+ if (box.scrollWidth <= 0 || box.scrollHeight <= 0) return null;
54
+ if (x < box.left || x > box.left + box.width || y < box.top || y > box.top + box.height) return null;
55
+ return {
56
+ x: (x - box.left + box.scrollLeft) / box.scrollWidth,
57
+ y: (y - box.top + box.scrollTop) / box.scrollHeight,
58
+ };
59
+ }
60
+
61
+ /** Where `u,v` is in the canvas content, in px from its top left. */
62
+ export function boardSpot(box: Pick<BoardBox, "scrollWidth" | "scrollHeight">, u: number, v: number): { left: number; top: number } {
63
+ return { left: u * box.scrollWidth, top: v * box.scrollHeight };
64
+ }
65
+
66
+ /** Is this cursor on the surface `where` names (null: the board canvas)? */
67
+ export function cursorHere(cursor: SyncCursor | null | undefined, where: string | null): boolean {
68
+ return where === null ? cursor?.space === "canvas" : cursor?.space === "sketch" && cursor.where === where;
69
+ }
70
+
71
+ export function BoardCursors({ canvas, where = null }: { canvas: HTMLElement | null; where?: string | null }) {
72
+ const store = useStore();
73
+ const { live, people } = usePresence();
74
+ const paper = where !== null;
75
+
76
+ // SEND: this tab's pointer over the canvas, while the session is live. A
77
+ // scroll under a still pointer moves it across the content too.
78
+ useEffect(() => {
79
+ if (!live || canvas === null) return;
80
+ const aborter = new AbortController();
81
+ let last: { x: number; y: number } | null = null;
82
+ const send = (): void => {
83
+ const at = last === null ? null : boardPoint(boxOf(canvas, where !== null), last.x, last.y);
84
+ const space = where === null ? ({ space: "canvas" } as const) : ({ space: "sketch", where } as const);
85
+ store.presence.update({ cursor: at === null ? null : { ...at, ...space } });
86
+ };
87
+ const off = (): void => {
88
+ last = null;
89
+ store.presence.update({ cursor: null });
90
+ };
91
+ canvas.addEventListener(
92
+ "pointermove",
93
+ (event) => {
94
+ last = { x: event.clientX, y: event.clientY };
95
+ send();
96
+ },
97
+ { signal: aborter.signal },
98
+ );
99
+ canvas.addEventListener("scroll", () => last !== null && send(), { signal: aborter.signal });
100
+ canvas.addEventListener("pointerleave", off, { signal: aborter.signal });
101
+ return () => {
102
+ aborter.abort();
103
+ off();
104
+ };
105
+ }, [live, canvas, store, where]);
106
+
107
+ // DRAW: in content px, so a cursor scrolls with the cards. A resize of the
108
+ // canvas re-reads its content size.
109
+ const [, remeasure] = useState(0);
110
+ useEffect(() => {
111
+ if (canvas === null || typeof ResizeObserver === "undefined") return;
112
+ const sized = new ResizeObserver(() => remeasure((n) => n + 1));
113
+ sized.observe(canvas);
114
+ return () => sized.disconnect();
115
+ }, [canvas]);
116
+
117
+ if (!live || canvas === null) return null;
118
+ const here = people.filter((p) => cursorHere(p.cursor, where));
119
+ if (here.length === 0) return null;
120
+ const box = boxOf(canvas, paper);
121
+ const size = paper ? { scrollWidth: box.width, scrollHeight: box.height } : { scrollWidth: canvas.scrollWidth, scrollHeight: canvas.scrollHeight };
122
+ return (
123
+ // Sized to the content and clipped, so a cursor at the edge never grows
124
+ // the scroll area it is placed across.
125
+ <div
126
+ className="board-cursors"
127
+ aria-hidden="true"
128
+ data-testid="board-cursors"
129
+ style={{ width: size.scrollWidth, height: size.scrollHeight }}
130
+ >
131
+ {here.map((person) => {
132
+ const at = boardSpot(size, person.cursor!.x, person.cursor!.y);
133
+ return (
134
+ <div
135
+ key={person.user}
136
+ className="board-cursor"
137
+ data-testid="board-cursor"
138
+ data-user={person.user}
139
+ style={{ left: at.left, top: at.top }}
140
+ >
141
+ <svg width="16" height="16" viewBox="0 0 16 16">
142
+ <path
143
+ d="M1 1 L1 13 L4.5 9.8 L7 15 L9.2 14 L6.8 8.9 L11.5 8.9 Z"
144
+ fill={person.color}
145
+ stroke="#0B0B0B"
146
+ strokeWidth="1"
147
+ strokeLinejoin="round"
148
+ />
149
+ </svg>
150
+ <span className="board-cursor-tag" style={{ background: person.color }}>
151
+ {person.name}
152
+ </span>
153
+ </div>
154
+ );
155
+ })}
156
+ </div>
157
+ );
158
+ }
@@ -0,0 +1,161 @@
1
+ // THE DROP DIALOG (S2): files dropped or pasted on the board, and where they
2
+ // count. Pick the scope once:
3
+ //
4
+ // Style the whole reel; cards on no planned shot.
5
+ // One shot cards pinned to the shot picked here (`reference.part`).
6
+ // Keep saved to the Project's Assets, no card (the plan wins over the
7
+ // mock's `use: "inspiration"`).
8
+ //
9
+ // "Why these?" is copied onto each card's note, and the green box says what
10
+ // the agent will read, in the brief's own words (`dropBriefLine`).
11
+ //
12
+ // A `Dialog` (z 1100, its own focus trap and Escape), opened by
13
+ // `openBoardDrop` and closed by `commitBoardDrop` or `closeBoardDrop`.
14
+ import { useEffect, useMemo, useState } from "react";
15
+ import { Dialog } from "@dailies-studio/ui";
16
+ import { useShotEditor, useStore } from "../../state/context.js";
17
+ import { dropBriefLine, partTitleOf } from "../../state/storyboardModel.js";
18
+ import type { DropScope } from "../../state/storyboardModel.js";
19
+ import { PartPicker } from "./PartPicker.js";
20
+
21
+ /** The thumbs drawn before the rest are counted. */
22
+ const THUMBS_MAX = 10;
23
+
24
+ export function BoardDropDialog() {
25
+ const store = useStore();
26
+ const drop = useShotEditor((s) => s.boardDrop);
27
+ const doc = useShotEditor((s) => s.doc);
28
+ const [scope, setScope] = useState<DropScope>("style");
29
+ const [partId, setPartId] = useState<string | null>(null);
30
+ const [note, setNote] = useState("");
31
+
32
+ // A FRESH DROP STARTS FRESH: Style, no note, and the selected shot (or the
33
+ // first) as the pick for One shot.
34
+ const files = drop?.files ?? null;
35
+ useEffect(() => {
36
+ if (files === null) return;
37
+ const parts = store.getState().doc?.storyboard?.parts ?? [];
38
+ const chosen = store.getState().storyboardSelection.find((id) => parts.some((p) => p.id === id));
39
+ setScope("style");
40
+ setNote("");
41
+ setPartId(chosen ?? parts[0]?.id ?? null);
42
+ }, [files, store]);
43
+
44
+ // One object URL per image, revoked when the drop goes.
45
+ const thumbs = useMemo(
46
+ () =>
47
+ (files ?? []).slice(0, THUMBS_MAX).map((file) => ({
48
+ name: file.name,
49
+ url: file.type.startsWith("image/") ? URL.createObjectURL(file) : null,
50
+ })),
51
+ [files],
52
+ );
53
+ useEffect(
54
+ () => () => {
55
+ for (const thumb of thumbs) if (thumb.url !== null) URL.revokeObjectURL(thumb.url);
56
+ },
57
+ [thumbs],
58
+ );
59
+
60
+ if (drop === null || doc === null) return null;
61
+ const parts = doc.storyboard?.parts ?? [];
62
+ const count = drop.files.length;
63
+ const images = drop.files.every((f) => f.type.startsWith("image/"));
64
+ const noun = images ? (count === 1 ? "image" : "images") : count === 1 ? "file" : "files";
65
+ const pick = partId !== null && parts.some((p) => p.id === partId) ? partId : null;
66
+ const pickTitle = pick === null ? null : partTitleOf(doc, pick);
67
+ const primary = scope === "style" ? "Add to Style" : scope === "keep" ? "Keep in Assets" : `Pin to ${pickTitle ?? "a shot"}`;
68
+
69
+ const commit = (): void => {
70
+ void store.commitBoardDrop(scope === "shot" ? { scope, partId: pick ?? "", note } : { scope, note });
71
+ };
72
+
73
+ const option = (value: DropScope, title: string, desc: string) => (
74
+ <button
75
+ type="button"
76
+ className={`board-drop-scope${scope === value ? " is-selected" : ""}`}
77
+ data-testid={`board-drop-scope-${value}`}
78
+ aria-pressed={scope === value}
79
+ disabled={value === "shot" && parts.length === 0}
80
+ title={value === "shot" && parts.length === 0 ? "Add a shot first" : undefined}
81
+ onClick={() => setScope(value)}
82
+ >
83
+ <span className="board-drop-scope-title">{title}</span>
84
+ <span className="board-drop-scope-desc">{desc}</span>
85
+ </button>
86
+ );
87
+
88
+ return (
89
+ <Dialog
90
+ open
91
+ className="board-drop"
92
+ title={
93
+ <>
94
+ <span className="board-drop-title" data-testid="board-drop-title">
95
+ {count} {noun} dropped
96
+ </span>
97
+ <span className="board-drop-esc" aria-hidden="true">
98
+ Esc
99
+ </span>
100
+ </>
101
+ }
102
+ onClose={() => store.closeBoardDrop()}
103
+ actions={
104
+ <>
105
+ <button type="button" className="btn board-drop-cancel" data-testid="board-drop-cancel" onClick={() => store.closeBoardDrop()}>
106
+ Cancel
107
+ </button>
108
+ <button
109
+ type="button"
110
+ className="btn is-primary"
111
+ data-testid="board-drop-commit"
112
+ disabled={scope === "shot" && pick === null}
113
+ onClick={commit}
114
+ >
115
+ {primary}
116
+ </button>
117
+ </>
118
+ }
119
+ >
120
+ <div className="board-drop-thumbs" data-testid="board-drop-thumbs">
121
+ {thumbs.map((thumb, at) =>
122
+ thumb.url === null ? (
123
+ <span key={at} className="board-drop-thumb is-file" title={thumb.name}>
124
+ {thumb.name}
125
+ </span>
126
+ ) : (
127
+ <img key={at} className="board-drop-thumb" src={thumb.url} alt={thumb.name} />
128
+ ),
129
+ )}
130
+ {count > THUMBS_MAX ? <span className="board-drop-thumb is-more">+{count - THUMBS_MAX}</span> : null}
131
+ </div>
132
+ <label className="board-drop-field">
133
+ <span className="board-drop-label">Why these?</span>
134
+ <textarea
135
+ className="board-drop-note"
136
+ data-testid="board-drop-note"
137
+ rows={2}
138
+ value={note}
139
+ placeholder="Say what to take from them."
140
+ onChange={(event) => setNote(event.currentTarget.value)}
141
+ onKeyDown={(event) => event.stopPropagation()}
142
+ />
143
+ </label>
144
+ <div className="board-drop-field">
145
+ <span className="board-drop-label">Where does it count?</span>
146
+ <div className="board-drop-scopes">
147
+ {option("style", "Style", "Whole reel. The agent uses it where it fits.")}
148
+ {option("shot", "One shot", pickTitle === null ? "Pin to one shot. Only that shot." : `Pin to ${pickTitle}. Only that shot.`)}
149
+ {option("keep", "Keep", "Save to Assets. Not used yet.")}
150
+ </div>
151
+ {scope === "shot" ? (
152
+ <PartPicker parts={parts} value={pick} noneLabel="Pick a shot" ariaLabel="Shot to pin to" onChange={setPartId} />
153
+ ) : null}
154
+ </div>
155
+ <p className="board-drop-agent" data-testid="board-drop-agent">
156
+ <span className="board-drop-agent-dot" aria-hidden="true" />
157
+ {dropBriefLine(count, note, scope, pickTitle)}
158
+ </p>
159
+ </Dialog>
160
+ );
161
+ }
@@ -0,0 +1,42 @@
1
+ // THE BOARD'S RIGHT PANEL FOR ONE SELECTED SHOT (Paper SB1): its plan, with
2
+ // the board's head (number, name, status) and the two ways on: Open in Studio
3
+ // and the one primary, Open shot board. The shot board shows the same plan
4
+ // with its own head and footer (`PlanOccupant`, RightColumn).
5
+ import { useShotEditor, useStore } from "../../state/context.js";
6
+ import { shotBoardOf } from "../../state/workspace.js";
7
+ import { PlanPanel } from "./PlanPanel.js";
8
+
9
+ export function BoardPlan({ partId }: { partId: string }) {
10
+ const store = useStore();
11
+ return (
12
+ <PlanPanel
13
+ partId={partId}
14
+ variant="board"
15
+ footer={
16
+ <>
17
+ <button type="button" className="btn board-plan-action" data-testid="board-open-studio" onClick={() => store.openPlannedShot(partId)}>
18
+ Open in Studio
19
+ </button>
20
+ <button
21
+ type="button"
22
+ className="btn is-primary board-plan-action"
23
+ data-testid="board-open-board"
24
+ onClick={() => store.openShotBoard(partId)}
25
+ >
26
+ Open shot board
27
+ </button>
28
+ </>
29
+ }
30
+ />
31
+ );
32
+ }
33
+
34
+ /** The right column's "plan" occupant (`boardRightPanel`): the open shot
35
+ * board's plan, following its sketch, else the selected shot card's. */
36
+ export function PlanOccupant() {
37
+ const plan = useShotEditor(shotBoardOf);
38
+ const sketch = useShotEditor((s) => s.storyboardSketch);
39
+ const picked = useShotEditor((s) => (s.storyboardSelection.length === 1 ? s.storyboardSelection[0]! : null));
40
+ if (plan !== null) return <PlanPanel partId={plan} variant="shot" {...(sketch === null ? {} : { sketchId: sketch })} />;
41
+ return picked === null ? null : <BoardPlan partId={picked} />;
42
+ }
@@ -0,0 +1,187 @@
1
+ // THE COMPONENT SHEET (S3): a component card on the board, playing, tuned and
2
+ // scoped without leaving the board.
3
+ //
4
+ // THE DOCKED RIGHT CELL, 400 wide, and only while exactly one component card
5
+ // is selected (`boardRightPanel` answers "component"). The mock floats it; the
6
+ // docked shell has no overlays, so it docks (values file, resolved conflicts).
7
+ //
8
+ // bar the name, a code or 3D badge, and who made it.
9
+ // stage the component live (`ComponentStage`), on the Components
10
+ // workspace's own look (`componentPreview`, bridge v45), with a
11
+ // chip that says whether it loops or holds the card's frame.
12
+ // props THIS USE (`ThisUsePanel`, keyed `cardUseKey`): every prop of
13
+ // the master, a blue dot on the ones this card changed. Tweaks ride
14
+ // on the reference (`reference.props`); the master is never copied.
15
+ // board where the card counts (Style, or one shot through
16
+ // `reference.part`), whether the video has it (a component card
17
+ // is a requirement), its Frame, Use and Importance (the card-use
18
+ // block every board panel shares), a vendored component's library,
19
+ // and the note the agent reads with it.
20
+ // footer how many tweaks the card holds, and the door to the master.
21
+ //
22
+ // It is the ONLY panel a component card has on the board, so everything the
23
+ // inspector shows for any other card is here too. A VIEWER reads it all with
24
+ // the writes off (`LockedControls`); the bar, the stage and the doors stay
25
+ // live. The head and the foot stay put while the body scrolls.
26
+ import { useEffect } from "react";
27
+ import type { ReferenceItem3D } from "@dailies-studio/shot/doc";
28
+ import { framesToSeconds } from "@dailies-studio/engine";
29
+ import { defDurationFrames } from "@dailies-studio/shot/doc";
30
+ import { InspectorRow, InspectorSection, LockedControls, SegmentedControl } from "@dailies-studio/ui";
31
+ import { useShotEditor, useStore } from "../../state/context.js";
32
+ import { isReferenceId } from "../../state/storyboardModel.js";
33
+ import { cardUseKey } from "../../state/workspace.js";
34
+ import { ThisUsePanel } from "../studio/ThisUsePanel.js";
35
+ import { formatDuration } from "../timeline/model.js";
36
+ import { CardNoteRow, CardUseRows, ReadoutRow, RequirementRows } from "./boardRows.js";
37
+ import { ComponentStage, DEFAULT_LOOP_SECONDS } from "./ComponentStage.js";
38
+ import { PartPicker } from "./PartPicker.js";
39
+
40
+ type ComponentCard = Extract<ReferenceItem3D, { type: "component" }>;
41
+
42
+ export function ComponentSheet() {
43
+ const store = useStore();
44
+ const doc = useShotEditor((s) => s.doc);
45
+ const selection = useShotEditor((s) => s.storyboardSelection);
46
+ const readOnly = useShotEditor((s) => s.readOnly);
47
+ const contracts = useShotEditor((s) => s.contracts);
48
+ const library = useShotEditor((s) => s.library);
49
+ const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
50
+ const look = useShotEditor((s) => s.componentPreview);
51
+ const id = selection.length === 1 && isReferenceId(selection[0]!) ? selection[0]! : null;
52
+ const found = id === null ? undefined : doc?.resources?.references?.find((r) => r.id === id);
53
+ const item: ComponentCard | undefined = found?.type === "component" ? found : undefined;
54
+ const componentId = item?.component ?? null;
55
+ const catalogRow = useShotEditor((s) =>
56
+ componentId === null ? undefined : s.libraryCatalog.find((row) => row.installedAs === componentId),
57
+ );
58
+
59
+ // HOOKS BEFORE THE EARLY RETURN. The contract feeds the props rows and the
60
+ // stage's binding; the registry row names the host and the author.
61
+ useEffect(() => {
62
+ if (componentId !== null) void store.loadContract(componentId);
63
+ }, [componentId, store]);
64
+ useEffect(() => {
65
+ if (componentId !== null && !libraryLoaded) void store.refreshLibrary();
66
+ }, [componentId, libraryLoaded, store]);
67
+
68
+ if (doc === null || id === null || item === undefined) return null;
69
+ const parts = doc.storyboard?.parts ?? [];
70
+ const def = item.def === undefined ? undefined : doc.components?.[item.def];
71
+ const row = componentId === null ? undefined : library.find((r) => r.id === componentId);
72
+ const contract = componentId === null ? undefined : contracts[componentId];
73
+ const name = def?.name ?? item.label;
74
+ const author = row?.library?.publisher ?? "This project";
75
+ const fps = doc.fps;
76
+ const ownLength = def !== undefined ? defDurationFrames(def) : catalogRow?.lengthFrames;
77
+ const loopFrames = look.loopFrames ?? ownLength ?? Math.round(DEFAULT_LOOP_SECONDS * (fps.num / fps.den));
78
+ const tweaks = Object.keys(item.props).length;
79
+ const pinned = item.part !== undefined && parts.some((p) => p.id === item.part) ? item.part : null;
80
+ // PROVENANCE, for a component the Project vendored from the library: read
81
+ // off the registry row, which is why the card carries no such field.
82
+ // `modified` means the installed files no longer hash to what was installed.
83
+ const vendored = row?.library?.scope === "shared" ? row.library : undefined;
84
+
85
+ const openComponent = (): void => {
86
+ if (item.def !== undefined) void store.selectComponent({ kind: "studio", def: item.def });
87
+ else if (componentId !== null) void store.selectComponent({ kind: "code", module: componentId });
88
+ };
89
+
90
+ return (
91
+ <aside className="component-sheet" data-testid="component-sheet" aria-label={`${name} component`}>
92
+ <header className="panel-head component-sheet-bar">
93
+ <span className="component-sheet-name">{name}</span>
94
+ <span className="badge">{def !== undefined ? "3D" : "code"}</span>
95
+ <span className="component-sheet-author">{author}</span>
96
+ </header>
97
+ <div className="component-sheet-body">
98
+ <div className="component-sheet-stage">
99
+ {def !== undefined || row?.host === "three" ? (
100
+ <ComponentStage
101
+ doc={doc}
102
+ item={item}
103
+ contract={contract}
104
+ availability={row?.contractAvailability}
105
+ lengthFrames={ownLength}
106
+ {...(look.background === null ? {} : { background: look.background })}
107
+ loopFrames={loopFrames}
108
+ />
109
+ ) : (
110
+ <span className="component-stage-status" role="status">
111
+ {row === undefined && !libraryLoaded ? "Loading the preview…" : "This component has no live preview here."}
112
+ </span>
113
+ )}
114
+ {/* A card that names a frame shows that frame and holds it
115
+ (ComponentStage), so the chip says which of the two it does. */}
116
+ <span className="component-sheet-loop" data-testid="component-sheet-loop">
117
+ {item.frame === undefined ? (
118
+ <>
119
+ <span className="component-sheet-loop-play" aria-hidden="true" />
120
+ Looping · {formatDuration(framesToSeconds(loopFrames, fps))}
121
+ </>
122
+ ) : (
123
+ `Holding frame ${item.frame}`
124
+ )}
125
+ </span>
126
+ </div>
127
+ <LockedControls value={readOnly}>
128
+ <ThisUsePanel useKey={cardUseKey(item.id)} />
129
+ <InspectorSection title="On the board" collapsible={false}>
130
+ {/* WHERE IT COUNTS, the drop dialog's words: the whole reel, or the
131
+ one shot named in the row under it. The row is "Counts in",
132
+ not "Use": Use is what the card is for, two rows down. */}
133
+ <InspectorRow label="Counts in">
134
+ <SegmentedControl
135
+ ariaLabel="Counts in"
136
+ value={pinned === null ? "style" : "shot"}
137
+ options={[
138
+ { value: "style", label: "Style", title: "The whole reel: the agent uses it where it fits", testId: "component-sheet-style" },
139
+ {
140
+ value: "shot",
141
+ label: "One shot",
142
+ disabled: parts.length === 0,
143
+ title: parts.length === 0 ? "Add a shot first" : "Pin it to one shot: only that shot uses it",
144
+ testId: "component-sheet-pin",
145
+ },
146
+ ]}
147
+ onChange={(value) => {
148
+ if (value === "style" && pinned !== null) store.attachStoryboardItems([id], null);
149
+ else if (value === "shot" && pinned === null && parts[0] !== undefined) store.attachStoryboardItems([id], parts[0].id);
150
+ }}
151
+ />
152
+ </InspectorRow>
153
+ {pinned === null ? null : (
154
+ <InspectorRow label="Pinned shot">
155
+ <PartPicker
156
+ parts={parts}
157
+ value={pinned}
158
+ noneLabel="Style"
159
+ ariaLabel="Pinned shot"
160
+ onChange={(part) => store.attachStoryboardItems([id], part)}
161
+ />
162
+ </InspectorRow>
163
+ )}
164
+ <RequirementRows doc={doc} item={item} />
165
+ <CardUseRows card={item} />
166
+ {vendored === undefined ? null : (
167
+ <ReadoutRow label="Library" {...(vendored.libraryId === undefined ? {} : { title: vendored.libraryId })}>
168
+ {vendored.publisher ?? "dailies"}/{vendored.libraryId ?? componentId}
169
+ {vendored.libraryVersion === undefined ? "" : ` v${vendored.libraryVersion}`}
170
+ {vendored.modified === true ? " · modified" : ""}
171
+ </ReadoutRow>
172
+ )}
173
+ <CardNoteRow card={item} />
174
+ </InspectorSection>
175
+ </LockedControls>
176
+ </div>
177
+ <footer className="panel-foot component-sheet-foot">
178
+ <span className="component-sheet-tweaks" data-testid="component-sheet-tweaks">
179
+ {tweaks === 1 ? "1 tweak" : `${tweaks} tweaks`} · linked to master
180
+ </span>
181
+ <button type="button" className="btn is-primary" data-testid="component-sheet-open" onClick={openComponent}>
182
+ Open component
183
+ </button>
184
+ </footer>
185
+ </aside>
186
+ );
187
+ }