@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,739 @@
1
+ // THE BOARD (Paper SB1): you plan here; the Studio builds.
2
+ //
3
+ // Bar "Storyboard", the shot count and total length, "Shot" and
4
+ // the one primary, "Build planned shots".
5
+ // Style on the desk, no box: the cards no planned shot holds
6
+ // (`styleItemsOf`), then the brand kit's pictures, fonts,
7
+ // palette and guidance, then the drop slot. What sits here
8
+ // guides the whole reel, and the agent reads it.
9
+ // Shots a storyboard sheet: one panel per planned shot or Shot in
10
+ // playing order (`storyEntries`), wrapping, with the cut into
11
+ // the next in the gutter after each and "into 04" where a row
12
+ // wraps, then the add slot. A press selects and opens the Plan
13
+ // panel on the right (`boardRightPanel` "plan"); a double press
14
+ // opens the shot board; a drag moves the shot
15
+ // (`moveBoardEntry`), with an insertion line where it lands.
16
+ //
17
+ // ── EVERY WAY IN ────────────────────────────────────────────────────────
18
+ //
19
+ // The + menu (Upload, Note, Link, From Library) and the drop slot add to
20
+ // Style. Files dropped or pasted anywhere on the board open the drop dialog
21
+ // (`openBoardDrop`), which asks where they count. A board card or a Project
22
+ // file dropped on the canvas lands in Style; dropped on a shot card it pins
23
+ // to that shot (ShotCard). A single link or line of text lands in Style
24
+ // directly. The paste half lives in `bindBoardClipboard`.
25
+ //
26
+ // ── LIVE COMPONENTS, ON DEMAND ──────────────────────────────────────────
27
+ //
28
+ // A component card shows its poster at rest and goes live on hover, focus or
29
+ // selection (`ComponentStage`, `LIVE_CAP`), so a wall of cards costs
30
+ // pictures, not documents. A shot card's sketch is the picture the tab's
31
+ // offscreen still stage draws of it (state/stillThumbs.ts, the same picture
32
+ // the agent's screenshot_shot `{sketch}` returns), drawn in the DOM
33
+ // (`MiniSketch`) until it is ready, so the sheet itself holds no stage.
34
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
35
+ import type { DragEvent as ReactDragEvent, MouseEvent as ReactMouseEvent, ReactNode } from "react";
36
+ import { framesToSeconds } from "@dailies-studio/engine";
37
+ import type { BrandAsset3D, BrandColor3D, BrandGuidance3D, Doc3D, ReferenceItem3D } from "@dailies-studio/shot/doc";
38
+ import { useContextMenu, useToasts } from "@dailies-studio/ui";
39
+ import type { MenuEntry } from "@dailies-studio/ui";
40
+ import { useShotEditor, useStore } from "../../state/context.js";
41
+ import { cardCommentCounts, cardCountWords } from "../../state/comments.js";
42
+ import { gateForViewer } from "../../state/menu.js";
43
+ import { isPartId, isReferenceId, linkHost, shotLengthOf, storyEntries, styleItemsOf } from "../../state/storyboardModel.js";
44
+ import type { StoryEntry } from "../../state/storyboardModel.js";
45
+ import { ADD_ICON, LIBRARY_ICON, LINK_ICON, NOTE_ICON, SECTION_ICONS } from "../../icons.js";
46
+ import { agentInEditor } from "../agent/agentGate.js";
47
+ import { COMMENT_ICON, useComments } from "../comments/parts.js";
48
+ import { ASSET_MIME, readAssetDrag } from "../files/assetDrag.js";
49
+ import { formatDuration } from "../timeline/model.js";
50
+ import { BoardCursors } from "./BoardCursors.js";
51
+ import { moveBoardEntry } from "./boardOrder.js";
52
+ import { pressSelection } from "./boardRows.js";
53
+ import type { Press } from "./boardRows.js";
54
+ import { ComponentStage } from "./ComponentStage.js";
55
+ import { MediaPoster, ReferencePreview, referenceThumbOf } from "./ReferencePreview.js";
56
+ import { SHOT_MIME, ShotCard, entryIdOf } from "./ShotCard.js";
57
+ import { ACCEPT_ATTRIBUTE, STORYBOARD_MIME, readDragPayload, urlFromTransfer, writeDrag } from "./storyboardDrop.js";
58
+
59
+ /** What a drop on the canvas can take. */
60
+ const DROP_TYPES = [STORYBOARD_MIME, ASSET_MIME, "Files", "text/uri-list", "text/plain"];
61
+
62
+ const assetUrl = (path: string): string => `/studio/assets/${encodeURIComponent(path)}`;
63
+
64
+ /** "04": a shot's number in reading order, from its index. */
65
+ const shotNumber = (at: number): string => String(at + 1).padStart(2, "0");
66
+
67
+ /** How many card-and-gutter cells one row of the sheet holds, or null before
68
+ * it is laid out (and in a suite, which lays nothing out). The sheet wraps
69
+ * with the browser's own flex-wrap; this only reads where it did, for the
70
+ * "into 04" hints. */
71
+ function useCellsPerRow(grid: HTMLElement | null): number | null {
72
+ const [perRow, setPerRow] = useState<number | null>(null);
73
+ useEffect(() => {
74
+ if (grid === null || typeof ResizeObserver === "undefined") return;
75
+ const read = (): void => {
76
+ const cell = grid.querySelector<HTMLElement>(".shot-cell")?.offsetWidth ?? 0;
77
+ setPerRow(cell > 0 ? Math.max(1, Math.floor(grid.clientWidth / cell)) : null);
78
+ };
79
+ const observer = new ResizeObserver(read);
80
+ observer.observe(grid);
81
+ read();
82
+ return () => observer.disconnect();
83
+ }, [grid]);
84
+ return perRow;
85
+ }
86
+
87
+ export function BoardCanvas({ rootRef }: { rootRef?: (el: HTMLElement | null) => void }) {
88
+ const store = useStore();
89
+ const doc = useShotEditor((s) => s.doc);
90
+ const selection = useShotEditor((s) => s.storyboardSelection);
91
+ const building = useShotEditor((s) => s.boardBuilding);
92
+ const canComment = useShotEditor((s) => s.canComment);
93
+ const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
94
+ const catalogLoaded = useShotEditor((s) => s.libraryCatalogLoaded);
95
+ const menu = useContextMenu();
96
+ const { toast, node: toasts } = useToasts();
97
+ const upload = useRef<HTMLInputElement | null>(null);
98
+ const [linking, setLinking] = useState(false);
99
+ const [over, setOver] = useState(false);
100
+ const [anchor, setAnchor] = useState<string | null>(null);
101
+ // A shot card being dragged, and the gap it would land in (`moveBoardEntry`).
102
+ const [dragging, setDragging] = useState<string | null>(null);
103
+ const [slot, setSlot] = useState<number | null>(null);
104
+ // The desk scrolls and carries the presence cursors; the grid wraps.
105
+ const [desk, setDesk] = useState<HTMLElement | null>(null);
106
+ const [grid, setGrid] = useState<HTMLElement | null>(null);
107
+ const perRow = useCellsPerRow(grid);
108
+
109
+ const style = useMemo(() => (doc === null ? [] : styleItemsOf(doc)), [doc]);
110
+ const entries = useMemo(() => (doc === null ? [] : storyEntries(doc)), [doc]);
111
+
112
+ // A component card's host and a library card's thumb come off the two
113
+ // registry reads; ask for each once, when a card needs it.
114
+ const needsRegistry = style.some((r) => r.type === "component" || r.type === "library");
115
+ useEffect(() => {
116
+ if (!needsRegistry) return;
117
+ if (!libraryLoaded) void store.refreshLibrary();
118
+ if (!catalogLoaded) void store.refreshLibraryCatalog();
119
+ }, [needsRegistry, libraryLoaded, catalogLoaded, store]);
120
+
121
+ const endDrag = useCallback(() => {
122
+ setDragging(null);
123
+ setSlot(null);
124
+ }, []);
125
+
126
+ if (doc === null) return null;
127
+
128
+ // ── THE DROP ON THE CANVAS ──────────────────────────────────────────────
129
+ const onDragOver = (event: ReactDragEvent): void => {
130
+ const types = Array.from(event.dataTransfer.types ?? []);
131
+ if (!types.some((type) => DROP_TYPES.includes(type))) return;
132
+ event.preventDefault();
133
+ event.dataTransfer.dropEffect = types.includes(STORYBOARD_MIME) && !types.includes("Files") ? "move" : "copy";
134
+ setOver(true);
135
+ };
136
+ const onDrop = (event: ReactDragEvent): void => {
137
+ setOver(false);
138
+ const data = event.dataTransfer;
139
+ const take = (): void => event.preventDefault();
140
+ const payload = readDragPayload(data);
141
+ if (payload !== null) {
142
+ take();
143
+ if (payload.kind === "reference") {
144
+ const live = store.getState().storyboardSelection;
145
+ store.attachStoryboardItems(live.includes(payload.id) ? live.filter(isReferenceId) : [payload.id], null);
146
+ }
147
+ return;
148
+ }
149
+ const asset = readAssetDrag(data);
150
+ if (asset !== null) {
151
+ take();
152
+ store.addReferenceForAsset(asset.asset, null, { select: false });
153
+ return;
154
+ }
155
+ const files = Array.from(data.files ?? []);
156
+ if (files.length > 0) {
157
+ take();
158
+ store.openBoardDrop(files);
159
+ return;
160
+ }
161
+ const url = urlFromTransfer(data);
162
+ if (url !== null) {
163
+ take();
164
+ store.addLinkReference(url, null, { select: false });
165
+ return;
166
+ }
167
+ const text = Array.from(data.types ?? []).includes("text/plain") ? data.getData("text/plain").trim() : "";
168
+ if (text === "") return;
169
+ take();
170
+ store.addStoryboardNote(null, { select: false, text });
171
+ };
172
+
173
+ // ── THE REORDER ─────────────────────────────────────────────────────────
174
+ // Over a cell, the gap before or after its card by which half the pointer
175
+ // is in; over the add slot, the end. One edit writes the new order.
176
+ const onSheetDragOver = (event: ReactDragEvent): void => {
177
+ if (dragging === null || !Array.from(event.dataTransfer.types ?? []).includes(SHOT_MIME)) return;
178
+ event.preventDefault();
179
+ event.dataTransfer.dropEffect = "move";
180
+ const cell = event.target instanceof Element ? event.target.closest<HTMLElement>("[data-slot]") : null;
181
+ if (cell === null) return;
182
+ const at = Number(cell.dataset["slot"]);
183
+ const card = cell.querySelector(".shot-card")?.getBoundingClientRect();
184
+ setSlot(card === undefined || event.clientX <= card.left + card.width / 2 ? at : at + 1);
185
+ };
186
+ const onSheetDrop = (event: ReactDragEvent): void => {
187
+ const id = Array.from(event.dataTransfer.types ?? []).includes(SHOT_MIME) ? event.dataTransfer.getData(SHOT_MIME) : "";
188
+ const to = slot;
189
+ endDrag();
190
+ if (id === "" || to === null) return;
191
+ event.preventDefault();
192
+ store.edit((base) => moveBoardEntry(base, storyEntries(base), id, to));
193
+ };
194
+
195
+ // ── THE + MENU ──────────────────────────────────────────────────────────
196
+ const openAdd = (el: HTMLElement): void => {
197
+ const r = el.getBoundingClientRect();
198
+ menu.open({ x: r.left, y: r.bottom + 6 }, gateForViewer(store.getState(), [
199
+ { kind: "action", id: "board-upload", label: "Upload", icon: ADD_ICON, onSelect: () => upload.current?.click() },
200
+ { kind: "action", id: "board-note", label: "Note", icon: NOTE_ICON, onSelect: () => void store.addStoryboardNote(null) },
201
+ { kind: "action", id: "board-link", label: "Link", icon: LINK_ICON, onSelect: () => setLinking(true) },
202
+ { kind: "action", id: "board-library", label: "From Library", icon: LIBRARY_ICON, onSelect: () => store.setLibraryBrowserOpen(true) },
203
+ { kind: "separator" },
204
+ { kind: "action", id: "board-assets", label: "Project assets", icon: SECTION_ICONS.project, onSelect: () => store.setBoardAssetsOpen(true) },
205
+ ]));
206
+ };
207
+
208
+ // ── SELECTION AND THE CARD MENUS ────────────────────────────────────────
209
+ const styleOrder = style.map((item) => item.id);
210
+ const shotOrder = entries.map(entryIdOf);
211
+ const press = (order: readonly string[]) => (id: string, event: Press): void => {
212
+ const next = pressSelection(store.getState().storyboardSelection, order, anchor, id, event);
213
+ store.setStoryboardSelection(next.selection);
214
+ setAnchor(next.anchor);
215
+ };
216
+ // A double press, Enter and Open land on the shot's board, and never make
217
+ // or enter a Shot. Only "Open in Studio" goes to the Studio.
218
+ const openEntry = (entry: StoryEntry): void => {
219
+ if (entry.kind === "placeholder") store.openShotBoard(entry.part.id);
220
+ else store.openShotBoard(entry.shot.part?.id ?? entry.shot.sceneId);
221
+ };
222
+ // A DOUBLE PRESS OPENS THE SHOT ITS FIRST PRESS PICKED. That press selects
223
+ // the shot and brings up the Plan panel, and the narrower canvas rewraps
224
+ // the board under the pointer (measured in the storyboard e2e: a card moved
225
+ // from x 896 to x 24), so the second press can land on another card or on
226
+ // the ground. So the board owns the double press: it opens the first
227
+ // press's shot, else the card under the pointer, and the second press
228
+ // changes no selection.
229
+ const firstPress = useRef<string | null>(null);
230
+ const pressShot = (id: string, event: Press): void => {
231
+ if ((event.detail ?? 0) >= 2 && firstPress.current !== null) return;
232
+ firstPress.current = id;
233
+ press(shotOrder)(id, event);
234
+ };
235
+ const onDoublePress = (event: ReactMouseEvent): void => {
236
+ const under = event.target instanceof Element ? event.target.closest(".shot-card")?.getAttribute("data-comment-target") : null;
237
+ const id = firstPress.current ?? under ?? null;
238
+ firstPress.current = null;
239
+ const entry = entries.find((e) => entryIdOf(e) === id);
240
+ if (entry !== undefined) openEntry(entry);
241
+ };
242
+ const openInStudio = (entry: StoryEntry): void => {
243
+ if (entry.kind === "placeholder") store.openPlannedShot(entry.part.id);
244
+ else if (entry.shot.part !== null) store.openPlannedShot(entry.shot.part.id);
245
+ else store.openStoryboardShot(entry.shot.sceneId);
246
+ };
247
+ const commentEntry = (target: { partId: string } | { referenceId: string }): MenuEntry[] =>
248
+ canComment ? [{ kind: "action", id: "board-comment", label: "Comment", onSelect: () => store.commentOnCard(target) }] : [];
249
+ const openMenu = (event: ReactMouseEvent, select: string, entriesOf: MenuEntry[]): void => {
250
+ event.preventDefault();
251
+ event.stopPropagation();
252
+ if (!store.getState().storyboardSelection.includes(select)) store.setStoryboardSelection([select]);
253
+ menu.open({ x: event.clientX, y: event.clientY }, gateForViewer(store.getState(), entriesOf));
254
+ };
255
+ const onShotMenu = (entry: StoryEntry, event: ReactMouseEvent): void => {
256
+ const id = entryIdOf(entry);
257
+ openMenu(event, id, [
258
+ { kind: "action", id: "board-open-board", label: "Open", onSelect: () => openEntry(entry) },
259
+ { kind: "action", id: "board-open-shot", label: "Open in Studio", onSelect: () => openInStudio(entry) },
260
+ ...(isPartId(id) ? commentEntry({ partId: id }) : []),
261
+ ...(isPartId(id)
262
+ ? [{ kind: "action" as const, id: "board-delete-shot", label: "Delete", danger: true, onSelect: () => store.deleteStoryboardItems([id]) }]
263
+ : []),
264
+ ]);
265
+ };
266
+ const onCardMenu = (item: ReferenceItem3D, event: ReactMouseEvent): void => {
267
+ openMenu(event, item.id, [
268
+ ...commentEntry({ referenceId: item.id }),
269
+ {
270
+ kind: "action",
271
+ id: "board-delete-card",
272
+ label: "Delete",
273
+ danger: true,
274
+ onSelect: () => {
275
+ const live = store.getState().storyboardSelection.filter(isReferenceId);
276
+ store.deleteStoryboardItems(live.includes(item.id) ? live : [item.id]);
277
+ },
278
+ },
279
+ ]);
280
+ };
281
+
282
+ const build = (): void => {
283
+ const ids = store.buildPlannedShots();
284
+ const first = ids[0];
285
+ if (first === undefined) return;
286
+ toast({
287
+ message: "Being built in Studio by Agent",
288
+ action: { label: "Open in Studio", run: () => store.openPlannedShot(first) },
289
+ });
290
+ };
291
+ const buildable = (doc.storyboard?.parts ?? []).some((p) => p.built === undefined && !building.some((b) => b.part === p.id));
292
+
293
+ // ── THE STYLE WALL'S BRAND CARDS ────────────────────────────────────────
294
+ const branding = doc.resources?.branding ?? [];
295
+ const fonts = branding.filter((b): b is BrandAsset3D => b.type === "asset" && b.fontFamily !== undefined);
296
+ const pictures = branding.filter(
297
+ (b): b is BrandAsset3D => b.type === "asset" && b.fontFamily === undefined && doc.assets[b.asset]?.kind === "image",
298
+ );
299
+ const colors = branding.filter((b): b is BrandColor3D => b.type === "color");
300
+ const guidance = branding.filter((b): b is BrandGuidance3D => b.type === "guidance");
301
+ const styleCount = style.length + fonts.length + pictures.length + (colors.length > 0 ? 1 : 0) + guidance.length;
302
+
303
+ const totalFrames = entries.reduce(
304
+ (n, e) => n + shotLengthOf(doc, e.kind === "shot" ? e.shot.part : e.part, e.kind === "shot" ? e.shot.scene : null),
305
+ 0,
306
+ );
307
+
308
+ const insert = (key: string, before = false): ReactNode => (
309
+ <span key={key} className={`shot-insert${before ? " is-before" : ""}`} data-testid="shot-insert" aria-hidden="true" />
310
+ );
311
+ const cells: ReactNode[] = entries.map((entry, at) => {
312
+ const id = entryIdOf(entry);
313
+ const last = at === entries.length - 1;
314
+ const cut = entry.kind === "shot" ? entry.cut : null;
315
+ const wraps = !last && perRow !== null && (at + 1) % perRow === 0;
316
+ return (
317
+ <div className="shot-cell" key={id} data-slot={at}>
318
+ {slot === 0 && at === 0 ? insert("before", true) : null}
319
+ <ShotCard
320
+ doc={doc}
321
+ entry={entry}
322
+ at={at}
323
+ selected={selection.includes(id)}
324
+ onPress={pressShot}
325
+ onOpen={openEntry}
326
+ onMenu={onShotMenu}
327
+ onDragMove={(moving) => (moving === null ? endDrag() : setDragging(moving))}
328
+ />
329
+ <div className={`shot-gutter${slot === at + 1 ? " is-insert" : ""}`}>
330
+ {slot === at + 1 ? insert("after") : null}
331
+ {last ? null : cut === null ? (
332
+ <span className="shot-transition-chip">Cut</span>
333
+ ) : (
334
+ <button
335
+ type="button"
336
+ className={`shot-transition-chip${selection.includes(cut.id) ? " is-selected" : ""}`}
337
+ data-testid={`shot-transition-${cut.id}`}
338
+ onClick={(event) => {
339
+ event.stopPropagation();
340
+ store.setStoryboardSelection([cut.id]);
341
+ }}
342
+ >
343
+ {cut.label}
344
+ </button>
345
+ )}
346
+ {wraps ? <span className="shot-gutter-wrap">{`into ${shotNumber(at + 1)}`}</span> : null}
347
+ </div>
348
+ </div>
349
+ );
350
+ });
351
+
352
+ return (
353
+ <div
354
+ ref={rootRef}
355
+ className={`board-canvas${over ? " is-drop" : ""}`}
356
+ data-testid="board-canvas"
357
+ tabIndex={-1}
358
+ onDragOver={onDragOver}
359
+ onDragLeave={(event) => {
360
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setOver(false);
361
+ }}
362
+ onDrop={onDrop}
363
+ // A FIRST PRESS ANYWHERE FORGETS the last shot pressed, in the capture
364
+ // phase, so a control that stops its press (a cut chip) cannot leave
365
+ // that shot for a double press on the control to open.
366
+ onClickCapture={(event) => {
367
+ if (event.detail < 2) firstPress.current = null;
368
+ }}
369
+ onClick={(event) => {
370
+ // A press on the ground clears the selection; a card stops nothing,
371
+ // so read whether the press landed on one. A press in a portal (the +
372
+ // menu, a card menu) bubbles here through React, not the DOM: a menu
373
+ // row that selects what it added must not be cleared by its own press.
374
+ if (!(event.target instanceof Element) || !event.currentTarget.contains(event.target)) return;
375
+ // The second press of a double press on a shot keeps what the first
376
+ // picked.
377
+ if (event.detail >= 2 && firstPress.current !== null) return;
378
+ if (event.target.closest(".style-card, .shot-card, button, input") !== null) return;
379
+ store.setStoryboardSelection([]);
380
+ }}
381
+ onDoubleClick={onDoublePress}
382
+ >
383
+ <header className="panel-head board-bar">
384
+ <h2 className="board-bar-title">Storyboard</h2>
385
+ <span className="board-bar-summary" data-testid="board-shots-summary">
386
+ {entries.length === 1 ? "1 shot" : `${entries.length} shots`} · {formatDuration(framesToSeconds(totalFrames, doc.fps))}
387
+ </span>
388
+ <span className="board-bar-spacer" />
389
+ <button type="button" className="btn" data-testid="board-add-shot" onClick={() => store.addPlannedShot()}>
390
+ {ADD_ICON}
391
+ Shot
392
+ </button>
393
+ {agentInEditor() ? (
394
+ <button
395
+ type="button"
396
+ className="btn is-primary"
397
+ data-testid="board-build"
398
+ disabled={!buildable}
399
+ title={buildable ? undefined : "Every planned shot is built or being built."}
400
+ onClick={build}
401
+ >
402
+ Build planned shots
403
+ </button>
404
+ ) : null}
405
+ </header>
406
+
407
+ <div className="board-desk" ref={setDesk}>
408
+ <section className="style-wall" data-testid="style-wall" aria-label="Style">
409
+ <header className="board-section-head">
410
+ <h3 className="caps">Style · whole reel</h3>
411
+ <span className="board-section-hint">The agent uses these wherever they fit.</span>
412
+ <span className="board-bar-spacer" />
413
+ <span className="board-section-count" data-testid="style-wall-count">
414
+ {styleCount === 1 ? "1 item" : `${styleCount} items`}
415
+ </span>
416
+ <button
417
+ type="button"
418
+ className="btn is-icon is-ghost board-section-add"
419
+ aria-label="Add to Style"
420
+ aria-haspopup="menu"
421
+ data-testid="board-add"
422
+ onClick={(event) => openAdd(event.currentTarget)}
423
+ >
424
+ {ADD_ICON}
425
+ </button>
426
+ </header>
427
+ <div className="style-wall-items" role="listbox" aria-label="Style cards" aria-multiselectable>
428
+ {style.map((item) => (
429
+ <StyleCard
430
+ key={item.id}
431
+ doc={doc}
432
+ item={item}
433
+ selected={selection.includes(item.id)}
434
+ onPress={press(styleOrder)}
435
+ onMenu={onCardMenu}
436
+ />
437
+ ))}
438
+ {pictures.map((b) => (
439
+ <div className="style-card" key={b.id} title={b.guidance ?? b.label} data-testid="style-brand-image">
440
+ <img src={assetUrl(doc.assets[b.asset]!.path)} alt={b.label} loading="lazy" draggable={false} />
441
+ </div>
442
+ ))}
443
+ {fonts.map((b) => (
444
+ <FontCard key={b.id} item={b} path={doc.assets[b.asset]?.kind === "font" ? doc.assets[b.asset]!.path : null} />
445
+ ))}
446
+ {colors.length === 0 ? null : (
447
+ <div className="style-card is-palette" data-testid="style-palette">
448
+ <span className="style-palette-swatches">
449
+ {colors.map((c) => (
450
+ <span key={c.id} className="style-palette-swatch" style={{ background: c.value }} title={`${c.label} ${c.value}`} />
451
+ ))}
452
+ </span>
453
+ <span className="style-palette-name">Palette · {colors.length}</span>
454
+ </div>
455
+ )}
456
+ {guidance.map((g) => (
457
+ <div className="style-card is-note" key={g.id} data-testid="style-guidance">
458
+ <span className="style-note-text">{g.text}</span>
459
+ <span className="style-note-meta">Guidance · {g.label}</span>
460
+ </div>
461
+ ))}
462
+ {linking ? (
463
+ <input
464
+ className="style-drop is-link"
465
+ type="url"
466
+ autoFocus
467
+ placeholder="https://"
468
+ aria-label="Link address"
469
+ data-testid="board-link-field"
470
+ onKeyDown={(event) => {
471
+ event.stopPropagation();
472
+ if (event.key === "Escape") setLinking(false);
473
+ if (event.key !== "Enter") return;
474
+ const url = event.currentTarget.value.trim();
475
+ if (url !== "" && store.addLinkReference(url, null, { select: false }) !== null) setLinking(false);
476
+ }}
477
+ onBlur={() => setLinking(false)}
478
+ />
479
+ ) : (
480
+ <button
481
+ type="button"
482
+ className={`drop style-drop${over ? " is-over" : ""}`}
483
+ data-testid="board-drop-slot"
484
+ aria-haspopup="menu"
485
+ onClick={(event) => openAdd(event.currentTarget)}
486
+ >
487
+ {ADD_ICON}
488
+ {over ? "Release to add" : "Drop, paste or link"}
489
+ </button>
490
+ )}
491
+ </div>
492
+ </section>
493
+
494
+ <section className="board-shots" aria-label="Shots">
495
+ <header className="board-section-head">
496
+ <h3 className="caps">Shots</h3>
497
+ <span className="board-section-hint">Double-click a shot to open its shot board.</span>
498
+ </header>
499
+ <div
500
+ ref={setGrid}
501
+ className={`board-shots-row${dragging === null ? "" : " is-moving"}`}
502
+ role="listbox"
503
+ aria-label="Shot cards"
504
+ aria-multiselectable
505
+ onDragOver={onSheetDragOver}
506
+ onDrop={onSheetDrop}
507
+ onDragLeave={(event) => {
508
+ if (!event.currentTarget.contains(event.relatedTarget as Node | null)) setSlot(null);
509
+ }}
510
+ >
511
+ {cells}
512
+ <button
513
+ type="button"
514
+ className="shot-add-slot"
515
+ data-testid="board-add-shot-slot"
516
+ data-slot={entries.length}
517
+ onClick={() => store.addPlannedShot()}
518
+ >
519
+ {ADD_ICON}
520
+ <span className="shot-add-slot-title">Shot</span>
521
+ <span className="shot-add-slot-hint">{`Adds ${shotNumber(entries.length)} at the end. Drag to move it.`}</span>
522
+ </button>
523
+ </div>
524
+ </section>
525
+ <BoardCursors canvas={desk} />
526
+ </div>
527
+
528
+ <input
529
+ ref={upload}
530
+ type="file"
531
+ hidden
532
+ multiple
533
+ accept={ACCEPT_ATTRIBUTE}
534
+ data-testid="board-upload-input"
535
+ onChange={(event) => {
536
+ const all = Array.from(event.currentTarget.files ?? []);
537
+ event.currentTarget.value = "";
538
+ if (all.length > 0) store.openBoardDrop(all);
539
+ }}
540
+ />
541
+ {menu.menu}
542
+ {toasts}
543
+ </div>
544
+ );
545
+ }
546
+
547
+ /** A brand font's face loads under a private family, as the inspector's
548
+ * FontField loads workspace fonts, so a brand font never takes over a name
549
+ * the editor's own chrome uses. */
550
+ const brandFamily = (path: string): string => `dailies-brand-${path.replace(/[^a-z0-9]+/gi, "-")}`;
551
+
552
+ /** A brand font: "Aa" in its own face over the family's name. The browser
553
+ * swaps the face in when the file lands; until then, or with no file, the
554
+ * family as named, if this machine has it, then the UI sans. */
555
+ function FontCard({ item, path }: { item: BrandAsset3D; path: string | null }) {
556
+ useEffect(() => {
557
+ if (path === null || typeof FontFace === "undefined") return;
558
+ // Every weight maps to the one file, so the semibold sample shows the
559
+ // face as drawn, never a synthesized bold.
560
+ const face = new FontFace(brandFamily(path), `url(${JSON.stringify(assetUrl(path))})`, { weight: "1 1000" });
561
+ document.fonts.add(face);
562
+ face.load().catch(() => {});
563
+ return () => {
564
+ document.fonts.delete(face);
565
+ };
566
+ }, [path]);
567
+ const named = `"${item.fontFamily}", var(--font-sans)`;
568
+ return (
569
+ <div className="style-card is-font" title={item.guidance ?? item.label} data-testid="style-font">
570
+ <span className="style-font-sample" style={{ fontFamily: path === null ? named : `"${brandFamily(path)}", ${named}` }}>
571
+ Aa
572
+ </span>
573
+ <span className="style-font-name">{item.fontFamily}</span>
574
+ </div>
575
+ );
576
+ }
577
+
578
+ /** One card on the Style wall. Drags like every board card, so it can pin to
579
+ * a shot card. */
580
+ function StyleCard(props: {
581
+ doc: Doc3D;
582
+ item: ReferenceItem3D;
583
+ selected: boolean;
584
+ onPress: (id: string, press: Press) => void;
585
+ onMenu: (item: ReferenceItem3D, event: ReactMouseEvent) => void;
586
+ }) {
587
+ const { doc, item, selected } = props;
588
+ const store = useStore();
589
+ const canComment = useShotEditor((s) => s.canComment);
590
+ const { threads } = useComments();
591
+ const count = cardCommentCounts(doc, threads, { referenceId: item.id });
592
+ const open = count.board + count.studio;
593
+ const words = cardCountWords(count);
594
+ const [lit, setLit] = useState(false);
595
+ const kind =
596
+ item.type === "asset"
597
+ ? doc.assets[item.asset]?.kind === "video"
598
+ ? "video"
599
+ : "image"
600
+ : item.type === "note" || item.type === "component" || item.type === "link"
601
+ ? item.type
602
+ : "other";
603
+ return (
604
+ <div
605
+ className={`style-card${kind === "note" || kind === "component" || kind === "link" ? ` is-${kind}` : ""}${selected ? " is-selected" : ""}`}
606
+ data-testid={`style-card-${item.id}`}
607
+ data-kind={kind}
608
+ data-comment-target={item.id}
609
+ role="option"
610
+ aria-selected={selected}
611
+ aria-label={item.label}
612
+ tabIndex={0}
613
+ draggable
614
+ onDragStart={(event) => writeDrag(event.dataTransfer, { kind: "reference", id: item.id })}
615
+ onClick={(event) => props.onPress(item.id, event)}
616
+ onKeyDown={(event) => {
617
+ if (event.target !== event.currentTarget || event.key !== " ") return;
618
+ event.preventDefault();
619
+ props.onPress(item.id, event);
620
+ }}
621
+ onContextMenu={(event) => props.onMenu(item, event)}
622
+ onPointerEnter={() => setLit(true)}
623
+ onPointerLeave={() => setLit(false)}
624
+ onFocus={() => setLit(true)}
625
+ onBlur={() => setLit(false)}
626
+ >
627
+ <StyleCardBody doc={doc} item={item} live={lit || selected} />
628
+ {/* THE CARD'S COMMENTS (S5): one corner control, always shown. With
629
+ open threads it is the pin carrying their count; without, the
630
+ Comment icon. */}
631
+ {canComment ? (
632
+ <button
633
+ type="button"
634
+ className={`style-card-comment${open > 0 ? " is-pin" : ""}`}
635
+ aria-label={open > 0 ? `Comment on this card. ${words}` : "Comment on this card"}
636
+ title={open > 0 ? `${words}. Comment (C)` : "Comment (C)"}
637
+ data-testid="style-card-comment"
638
+ onClick={(event) => {
639
+ event.stopPropagation();
640
+ store.commentOnCard({ referenceId: item.id });
641
+ }}
642
+ >
643
+ {open > 0 ? open : COMMENT_ICON}
644
+ </button>
645
+ ) : open > 0 ? (
646
+ <span className="style-card-comment is-pin" aria-label={words} title={words}>
647
+ {open}
648
+ </span>
649
+ ) : null}
650
+ </div>
651
+ );
652
+ }
653
+
654
+ function StyleCardBody({ doc, item, live }: { doc: Doc3D; item: ReferenceItem3D; live: boolean }) {
655
+ if (item.type === "note") {
656
+ return (
657
+ <>
658
+ <span className="style-note-text">{item.text.trim() === "" ? "Empty note" : item.text}</span>
659
+ <span className="style-note-meta">Note</span>
660
+ </>
661
+ );
662
+ }
663
+ if (item.type === "component") return <StyleComponentBody doc={doc} item={item} live={live} />;
664
+ if (item.type === "asset") {
665
+ const asset = doc.assets[item.asset];
666
+ if (asset?.kind === "video") {
667
+ const at = item.frame === undefined ? null : (item.frame * doc.fps.den) / doc.fps.num;
668
+ return <MediaPoster className="style-card-media" src={assetUrl(asset.path)} atSeconds={at} />;
669
+ }
670
+ if (asset?.kind === "image") return <img className="style-card-media" src={assetUrl(asset.path)} alt="" loading="lazy" draggable={false} />;
671
+ }
672
+ if (item.type === "link") {
673
+ return (
674
+ <>
675
+ <span className="style-link-host">{linkHost(item.url)}</span>
676
+ <span className="style-link-title">{item.title ?? item.url}</span>
677
+ </>
678
+ );
679
+ }
680
+ return <StylePreview doc={doc} item={item} />;
681
+ }
682
+
683
+ /** Every other card: the shared preview, so the board and the inspector draw
684
+ * one answer. */
685
+ function StylePreview({ doc, item }: { doc: Doc3D; item: ReferenceItem3D }) {
686
+ const catalog = useShotEditor((s) => s.libraryCatalog);
687
+ const loaded = useShotEditor((s) => s.libraryCatalogLoaded);
688
+ const thumb = referenceThumbOf(item, catalog, loaded);
689
+ return (
690
+ <ReferencePreview
691
+ item={item}
692
+ asset={item.type === "asset" ? doc.assets[item.asset] : undefined}
693
+ fps={doc.fps}
694
+ libraryThumb={thumb.libraryThumb}
695
+ libraryKind={thumb.libraryKind}
696
+ libraryMissing={thumb.libraryMissing}
697
+ />
698
+ );
699
+ }
700
+
701
+ type ComponentCardItem = Extract<ReferenceItem3D, { type: "component" }>;
702
+
703
+ /** A component card: a poster at rest, live on hover, focus or selection. */
704
+ function StyleComponentBody({ doc, item, live }: { doc: Doc3D; item: ComponentCardItem; live: boolean }) {
705
+ const store = useStore();
706
+ const componentId = item.component ?? null;
707
+ const row = useShotEditor((s) => (componentId === null ? undefined : s.library.find((r) => r.id === componentId)));
708
+ const contract = useShotEditor((s) => (componentId === null ? undefined : s.contracts[componentId]));
709
+ const lengthFrames = useShotEditor((s) =>
710
+ componentId === null ? undefined : s.libraryCatalog.find((r) => r.installedAs === componentId)?.lengthFrames,
711
+ );
712
+ useEffect(() => {
713
+ if (live && componentId !== null) void store.loadContract(componentId);
714
+ }, [live, componentId, store]);
715
+ const def = item.def === undefined ? undefined : doc.components?.[item.def];
716
+ const stageable = def !== undefined || row?.host === "three";
717
+ return (
718
+ <>
719
+ <div className="style-component-preview">
720
+ {stageable ? (
721
+ <ComponentStage
722
+ doc={doc}
723
+ item={item}
724
+ contract={contract}
725
+ availability={row?.contractAvailability}
726
+ lengthFrames={lengthFrames}
727
+ live={live}
728
+ fallback={<StylePreview doc={doc} item={item} />}
729
+ />
730
+ ) : (
731
+ <StylePreview doc={doc} item={item} />
732
+ )}
733
+ </div>
734
+ <span className="style-component-foot">
735
+ <span className="style-component-name">{def?.name ?? item.label}</span>
736
+ </span>
737
+ </>
738
+ );
739
+ }