@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,12 @@
1
+ // THE STORYBOARD'S CHUNK: the board, the shot board, the drop dialog, and the
2
+ // panels the board puts in the right column, the inspector's card section
3
+ // among them. Loaded on demand through app/lazySurfaces.tsx and nowhere else,
4
+ // so all of it, its stylesheet included, leaves the /scene entry. Studio's
5
+ // Brief (ShotBrief) is not here: it is a Studio panel.
6
+ import "./board.css";
7
+
8
+ export { StoryboardView } from "./StoryboardView.js";
9
+ export { ComponentSheet } from "./ComponentSheet.js";
10
+ export { PlanOccupant } from "./BoardPlan.js";
11
+ export { SketchInspector } from "./shotboard/SketchInspector.js";
12
+ export { StoryboardReferenceSection } from "../inspector/storyboard/StoryboardReferenceSection.js";
@@ -0,0 +1,111 @@
1
+ // THE BOARD'S CLIPBOARD: copy, cut and paste of cards and sketch items.
2
+ //
3
+ // Its own file because TWO documents bind it, the chrome's (StoryboardView)
4
+ // and the stage iframe's (StagePanel), and the stage panel loads with the
5
+ // shell while the board is a chunk of its own.
6
+ import type { ShotEditorStore } from "../../state/store.js";
7
+ import { isReferenceId } from "../../state/storyboardModel.js";
8
+ import { shotBoardOf, workspaceOf } from "../../state/workspace.js";
9
+ import { CLIP_MARKER, STORYBOARD_MIME, urlFromTransfer } from "./storyboardDrop.js";
10
+
11
+ /** What a copy of sketch items marks the clipboard with. Its own type, not
12
+ * `STORYBOARD_MIME`, so a card paste never takes it, nor it a card clip. */
13
+ export const SKETCH_MIME = "application/x-dailies-sketch";
14
+
15
+ /**
16
+ * The board's copy, cut and paste, bound on one document until `signal`
17
+ * aborts. TWO DOCUMENTS bind it: the chrome's (StoryboardView) and the stage
18
+ * iframe's (StagePanel). The pointer on the frame focuses the stage, and the native
19
+ * event then fires in ITS document, so a chrome-only binding left ⌘C ⌘V on a
20
+ * layer dead. Each event checks the board is the workspace, because the stage
21
+ * document outlives the board.
22
+ */
23
+ export function bindBoardClipboard(doc: Document, store: ShotEditorStore, signal: AbortSignal): void {
24
+ const on = (): boolean => workspaceOf(store.getState()) === "storyboard";
25
+ /** Is this event aimed at something an author is typing into? A note card's
26
+ * own text, or the stage's text editor, must still copy and paste as text.
27
+ * Duck-typed: a stage element is not this realm's `HTMLElement`. */
28
+ const isField = (target: EventTarget | null): boolean => {
29
+ const el = target as Partial<HTMLElement> | null;
30
+ return el?.tagName === "INPUT" || el?.tagName === "TEXTAREA" || el?.isContentEditable === true;
31
+ };
32
+ /** A RANGE OF TEXT BEATS THE BOARD. Without this, selecting a timecode or a
33
+ * label anywhere in the chrome and pressing ⌘C would copy the board's cards
34
+ * instead of the words under the cursor. */
35
+ const hasTextSelection = (): boolean => doc.getSelection()?.isCollapsed === false;
36
+
37
+ const onPaste = (event: ClipboardEvent): void => {
38
+ if (!on() || isField(event.target)) return;
39
+ const sketching = shotBoardOf(store.getState()) !== null;
40
+ const all = Array.from(event.clipboardData?.files ?? []);
41
+ if (all.length > 0) {
42
+ // FILES open the drop dialog (S2), which asks where they count. Never a
43
+ // fall-through to the board clip, which would paste a copy of whatever
44
+ // was copied last. On a shot board they are the sketch's: each lands as
45
+ // a pinned card and its item, with no dialog.
46
+ event.preventDefault();
47
+ if (sketching) void store.addSketchItemsFromFiles(all);
48
+ else store.openBoardDrop(all);
49
+ return;
50
+ }
51
+ // THE BOARD'S OWN CLIPS go back as they were copied, and a stale clip is
52
+ // never pasted: only under its marker.
53
+ const data = event.clipboardData;
54
+ const types = Array.from(data?.types ?? []);
55
+ if (sketching && types.includes(SKETCH_MIME)) {
56
+ event.preventDefault();
57
+ store.pasteSketchItems();
58
+ return;
59
+ }
60
+ if (types.includes(STORYBOARD_MIME)) {
61
+ event.preventDefault();
62
+ store.pasteStoryboardItems();
63
+ return;
64
+ }
65
+ // A LINK OR WORDS from anywhere else land on the Style wall: a link card,
66
+ // else a note.
67
+ if (data === null) return;
68
+ const url = urlFromTransfer(data);
69
+ if (url !== null) {
70
+ event.preventDefault();
71
+ store.addLinkReference(url, null, { select: false });
72
+ return;
73
+ }
74
+ const text = data.getData("text/plain").trim();
75
+ if (text === "") return;
76
+ event.preventDefault();
77
+ store.addStoryboardNote(null, { select: false, text });
78
+ };
79
+
80
+ /** A copy, or a cut that also deletes. Both write the clip's marker, so a
81
+ * paste, which takes the clip only under it, takes cards copied anywhere
82
+ * on the board. */
83
+ const clip = (event: ClipboardEvent, cut: boolean): void => {
84
+ if (!on() || isField(event.target) || hasTextSelection()) return;
85
+ // ON A SHOT BOARD, the sketch items selected: onto the shot board's own
86
+ // clip, which a paste on any sketch takes back.
87
+ const state = store.getState();
88
+ if (shotBoardOf(state) !== null) {
89
+ const items = state.sketchSelection;
90
+ if (items.length === 0) return;
91
+ event.preventDefault();
92
+ event.clipboardData?.setData(SKETCH_MIME, CLIP_MARKER);
93
+ store.copySketchItems(items);
94
+ if (cut) store.removeSketchItems(items);
95
+ return;
96
+ }
97
+ // The CARDS in the selection. A press on a lane head selects the planned
98
+ // shot, and a cut of that would copy nothing and then delete the lane, its
99
+ // cards thrown to Global. Nothing to copy leaves the event alone.
100
+ const ids = store.getState().storyboardSelection.filter(isReferenceId);
101
+ if (ids.length === 0) return;
102
+ event.preventDefault();
103
+ event.clipboardData?.setData(STORYBOARD_MIME, CLIP_MARKER);
104
+ store.copyStoryboardItems(ids);
105
+ if (cut) store.deleteStoryboardItems(ids);
106
+ };
107
+
108
+ doc.addEventListener("copy", (event) => clip(event, false), { signal });
109
+ doc.addEventListener("cut", (event) => clip(event, true), { signal });
110
+ doc.addEventListener("paste", onPaste, { signal });
111
+ }
@@ -0,0 +1,68 @@
1
+ // THE BOARD'S ORDER, MOVED BY A DRAG (Paper SB1): a shot card dropped between
2
+ // two others. Pure, so the drop and the tests read one answer.
3
+ //
4
+ // TWO ARRAYS SAY THE ORDER. A planned shot's place is its index in
5
+ // `storyboard.parts` (`movePart`); a Shot's place is its index in the playing
6
+ // order `doc.scenes` (`reorderScene`), and `storyEntries` places the planned
7
+ // shots with no Shot around the Shots by part order. So a move writes both:
8
+ // the parts in the new reading order, and the Shots in it too. Moving a built
9
+ // shot on the board moves it in the film, which is the point: one order.
10
+ //
11
+ // A SHOT NO PLAN LINKS has no index in the parts, so a planned shot with no
12
+ // Shot could not be placed beside it: `storyEntries` would put it by the
13
+ // nearest linked Shot instead, and the drop would land somewhere else. Only
14
+ // then does the move give each such Shot its planned shot first
15
+ // (`pinPartFor`, as a drop on its card does), in the same edit; a move the
16
+ // two arrays can say as they are adds no part.
17
+ import { movePart, pinPartFor, reorderScene } from "@dailies-studio/shot/doc";
18
+ import type { Doc3D } from "@dailies-studio/shot/doc";
19
+ import { storyEntries } from "../../state/storyboardModel.js";
20
+ import type { StoryEntry } from "../../state/storyboardModel.js";
21
+
22
+ /** The id a card selects and drags under: its planned shot, else its Shot. */
23
+ export function entryIdOf(entry: StoryEntry): string {
24
+ return entry.kind === "shot" ? (entry.shot.part?.id ?? entry.shot.sceneId) : entry.part.id;
25
+ }
26
+
27
+ /**
28
+ * `doc` with the card `id` moved to `slot`: the gap before the card now at
29
+ * `slot` in `entries` (`entries.length` is the end). The same document when
30
+ * nothing moves.
31
+ */
32
+ export function moveBoardEntry(doc: Doc3D, entries: readonly StoryEntry[], id: string, slot: number): Doc3D {
33
+ const from = entries.findIndex((entry) => entryIdOf(entry) === id);
34
+ if (from < 0) return doc;
35
+ const at = Math.max(0, Math.min(entries.length, Math.trunc(slot)));
36
+ const to = at > from ? at - 1 : at;
37
+ if (to === from) return doc;
38
+ const next = [...entries];
39
+ const [moved] = next.splice(from, 1);
40
+ next.splice(to, 0, moved!);
41
+ const plain = placeInOrder(doc, next, false);
42
+ const want = next.map(entryIdOf).join(" ");
43
+ return storyEntries(plain).map(entryIdOf).join(" ") === want ? plain : placeInOrder(doc, next, true);
44
+ }
45
+
46
+ /** Both arrays in `order`, each placed at its index in turn: everything
47
+ * before it is already in place, so they end in exactly that order. With
48
+ * `pin`, a Shot no plan links gets its planned shot first. */
49
+ function placeInOrder(doc: Doc3D, order: readonly StoryEntry[], pin: boolean): Doc3D {
50
+ let out = doc;
51
+ const partIds = order.flatMap((entry): string[] => {
52
+ if (entry.kind === "placeholder") return [entry.part.id];
53
+ if (entry.shot.part !== null) return [entry.shot.part.id];
54
+ const pinned = pin ? pinPartFor(out, entry.shot.sceneId) : null;
55
+ if (pinned === null) return [];
56
+ out = pinned.doc;
57
+ return [pinned.part];
58
+ });
59
+ partIds.forEach((partId, index) => {
60
+ out = movePart(out, partId, index);
61
+ });
62
+ order
63
+ .flatMap((entry) => (entry.kind === "shot" ? [entry.shot.sceneId] : []))
64
+ .forEach((sceneId, index) => {
65
+ out = reorderScene(out, sceneId, index);
66
+ });
67
+ return out;
68
+ }
@@ -0,0 +1,425 @@
1
+ // THE BOARD'S SHARED PARTS, one home for what the board and its panels all
2
+ // draw: the press policy, the requirement's rows and action slot, the card-use
3
+ // block and the read-only row.
4
+ import { useRef, useState } from "react";
5
+ import type { ReactNode } from "react";
6
+ import {
7
+ REFERENCE_IMPORTANCES,
8
+ REFERENCE_IMPORTANCE_SPECS,
9
+ REFERENCE_USES,
10
+ REFERENCE_USE_SPECS,
11
+ REQUIREMENT_STATUS_SPECS,
12
+ referenceImportanceOf,
13
+ referenceUseOf,
14
+ requirementStatus,
15
+ updateReference,
16
+ } from "@dailies-studio/shot/doc";
17
+ import type { Doc3D, ReferenceItem3D } from "@dailies-studio/shot/doc";
18
+ import { IconUndo, InspectorRow, NumberField, SegmentedControl, TextField, useContextMenu, useLocked } from "@dailies-studio/ui";
19
+ import type { MenuEntry } from "@dailies-studio/ui";
20
+ import { useShotEditor, useStore } from "../../state/context.js";
21
+ import { instanceHostRefusal } from "../../state/storyboardModel.js";
22
+ import { useScrub } from "../inspector/useDocWrite.js";
23
+ import { AUDIO_ACCEPT_ATTRIBUTE } from "./storyboardDrop.js";
24
+
25
+ /** A press's modifiers, all a selection policy reads, and its click count
26
+ * (`detail`: 2 on a double press's second press, 0 from a key). */
27
+ export type Press = { shiftKey: boolean; ctrlKey: boolean; metaKey: boolean; detail?: number };
28
+
29
+ /**
30
+ * THE SELECTION A PRESS LEAVES, over a list in its visible `order`: Shift
31
+ * takes the range from the anchor (which stays), Ctrl or Cmd toggles the one
32
+ * pressed, a plain press selects it alone. Both move the anchor to it. Pure,
33
+ * so every board list presses one way.
34
+ */
35
+ export function pressSelection(
36
+ current: readonly string[],
37
+ order: readonly string[],
38
+ anchor: string | null,
39
+ id: string,
40
+ event: Press,
41
+ ): { selection: string[]; anchor: string | null } {
42
+ if (event.shiftKey && anchor !== null) {
43
+ const a = order.indexOf(anchor);
44
+ const b = order.indexOf(id);
45
+ if (a !== -1 && b !== -1) return { selection: order.slice(Math.min(a, b), Math.max(a, b) + 1), anchor };
46
+ }
47
+ if (event.ctrlKey || event.metaKey) {
48
+ return { selection: current.includes(id) ? current.filter((x) => x !== id) : [...current, id], anchor: id };
49
+ }
50
+ return { selection: [id], anchor: id };
51
+ }
52
+
53
+ /**
54
+ * WHAT A REQUIREMENT'S ACTION SLOT OFFERS, and the reason when it offers
55
+ * nothing. Read straight off `requirementStatus` and the card's planned shot:
56
+ *
57
+ * place the requirement is missing and Place can write it here
58
+ * place-in a Style card that makes a layer: Place asks which Shot
59
+ * in satisfied: names where, and opens it
60
+ * choose-file a sound with no file yet
61
+ * none nothing to do from the row (done, unchecked library, or a
62
+ * refusal Place would make anyway), with `reason` as the title
63
+ */
64
+ export type PlaceActionKind =
65
+ | { kind: "place"; title: string }
66
+ | { kind: "place-in"; title: string }
67
+ | { kind: "in"; sceneId: string; layerId: string | null; label: string }
68
+ | { kind: "choose-file" }
69
+ | { kind: "none"; reason: string };
70
+
71
+ export function placeActionOf(doc: Doc3D, item: ReferenceItem3D, componentHost: (id: string) => string | null): PlaceActionKind {
72
+ const report = requirementStatus(doc, item);
73
+ if (report === null) return { kind: "none", reason: "" };
74
+ const part = item.part === undefined ? undefined : doc.storyboard?.parts.find((p) => p.id === item.part);
75
+ const linked = part?.scene === undefined ? undefined : doc.scenesById[part.scene];
76
+ if (report.state === "satisfied") {
77
+ const sceneId = report.evidence?.scene ?? linked?.id ?? null;
78
+ const scene = sceneId === null ? undefined : doc.scenesById[sceneId];
79
+ if (scene !== undefined && sceneId !== null) {
80
+ return { kind: "in", sceneId, layerId: report.evidence?.layer ?? null, label: `In ${scene.name}` };
81
+ }
82
+ return { kind: "none", reason: report.reason };
83
+ }
84
+ if (report.state === "done") return { kind: "none", reason: report.reason };
85
+ if (item.type === "sound" && item.asset === undefined) return { kind: "choose-file" };
86
+ if (item.type === "library" || item.type === "note") return { kind: "none", reason: report.reason };
87
+ if (part !== undefined && part.scene === undefined) return { kind: "none", reason: `Create a Shot for ${part.label} first.` };
88
+ if (part !== undefined && linked === undefined) return { kind: "none", reason: `The Shot ${part.label} was linked to is gone. Link another.` };
89
+ if (item.type === "component") {
90
+ const host = item.component === undefined ? null : componentHost(item.component);
91
+ if (host === null) return { kind: "none", reason: `${item.component ?? item.label} is not in the registry.` };
92
+ if (host !== "three") return { kind: "none", reason: instanceHostRefusal(host) };
93
+ }
94
+ if (part === undefined && item.type !== "transition" && item.type !== "sound") {
95
+ return { kind: "place-in", title: "Choose the Shot to place it in" };
96
+ }
97
+ return { kind: "place", title: linked === undefined ? "Place it in the video" : `Place it in ${linked.name}` };
98
+ }
99
+
100
+ /**
101
+ * THE ACTION SLOT, from `placeActionOf`: Place, Place in…
102
+ * (a menu of Shots), In <Shot> (opens it on the evidence), Choose file… (a
103
+ * sound that names a need), or a dimmed Place whose title is the reason.
104
+ * The row and the inspector both draw this, so the two cannot disagree.
105
+ *
106
+ * WITH A SHOT (`sceneId`), the slot is that Shot's: a card that would ask
107
+ * "Place in…" places straight into it, because the author is already in it.
108
+ */
109
+ export function PlaceAction({ doc, item, sceneId }: { doc: Doc3D; item: ReferenceItem3D; sceneId?: string }) {
110
+ const store = useStore();
111
+ const library = useShotEditor((s) => s.library);
112
+ const report = requirementStatus(doc, item);
113
+ const action = placeActionOf(doc, item, (id) => library.find((row) => row.id === id)?.host ?? null);
114
+ const fileRef = useRef<HTMLInputElement | null>(null);
115
+ const placeIn = useContextMenu();
116
+ const [busy, setBusy] = useState(false);
117
+ // A viewer's panel locks the verbs that write (Place, Place in, Choose
118
+ // file); "In <Shot>" only navigates, so it stays live.
119
+ const locked = useLocked();
120
+ const inert = busy || locked;
121
+ const shot = sceneId === undefined ? undefined : doc.scenesById[sceneId];
122
+
123
+ const run = (work: Promise<void>): void => {
124
+ setBusy(true);
125
+ void work
126
+ .catch((e: unknown) => store.reportError(e instanceof Error ? e.message : String(e)))
127
+ .finally(() => setBusy(false));
128
+ };
129
+
130
+ const openPlaceIn = (at: { x: number; y: number }): void => {
131
+ const entries: MenuEntry[] = doc.scenes.map((id) => ({
132
+ kind: "action",
133
+ id: `ledger-place-in-${id}`,
134
+ label: doc.scenesById[id]?.name ?? id,
135
+ onSelect: () => run(store.placeStoryboardItem(item.id, id)),
136
+ }));
137
+ if (entries.length === 0) {
138
+ store.reportError("This Project has no Shot yet. Create one from a planned shot first.");
139
+ return;
140
+ }
141
+ placeIn.open(at, entries);
142
+ };
143
+
144
+ return (
145
+ <>
146
+ {action.kind === "place" ? (
147
+ <button type="button" className="btn" title={action.title} disabled={inert} onClick={() => run(store.placeStoryboardItem(item.id))}>
148
+ Place
149
+ </button>
150
+ ) : action.kind === "place-in" && sceneId !== undefined && shot !== undefined ? (
151
+ <button
152
+ type="button"
153
+ className="btn"
154
+ title={`Place it in ${shot.name}`}
155
+ disabled={inert}
156
+ onClick={() => run(store.placeStoryboardItem(item.id, sceneId))}
157
+ >
158
+ Place
159
+ </button>
160
+ ) : action.kind === "place-in" ? (
161
+ <button
162
+ type="button"
163
+ className="btn"
164
+ title={action.title}
165
+ aria-haspopup="menu"
166
+ disabled={inert}
167
+ onClick={(event) => {
168
+ const r = event.currentTarget.getBoundingClientRect();
169
+ openPlaceIn({ x: r.left, y: r.bottom + 6 });
170
+ }}
171
+ >
172
+ Place in…
173
+ </button>
174
+ ) : action.kind === "in" ? (
175
+ <button
176
+ type="button"
177
+ className="btn ledger-in"
178
+ title="Open the Shot on what satisfies this"
179
+ onClick={() => store.openStoryboardShot(action.sceneId, action.layerId)}
180
+ >
181
+ {action.label}
182
+ </button>
183
+ ) : action.kind === "choose-file" ? (
184
+ <>
185
+ <button type="button" className="btn" disabled={inert} onClick={() => fileRef.current?.click()}>
186
+ Choose file…
187
+ </button>
188
+ <input
189
+ ref={fileRef}
190
+ type="file"
191
+ hidden
192
+ accept={AUDIO_ACCEPT_ATTRIBUTE}
193
+ data-testid="ledger-sound-file"
194
+ onChange={(event) => {
195
+ const file = event.currentTarget.files?.[0];
196
+ event.currentTarget.value = "";
197
+ if (file !== undefined) run(store.setSoundFile(item.id, file));
198
+ }}
199
+ />
200
+ </>
201
+ ) : report?.state === "missing" ? (
202
+ // A REFUSAL SAYS WHY: dimmed, with Place's own sentence as the title.
203
+ <button
204
+ type="button"
205
+ className="btn"
206
+ aria-disabled="true"
207
+ title={action.reason}
208
+ onClick={() => store.reportError(action.reason)}
209
+ >
210
+ Place
211
+ </button>
212
+ ) : (
213
+ <span className="ledger-note" title={action.reason}>
214
+ {report?.state === "done" ? "Ticked by hand" : action.reason}
215
+ </span>
216
+ )}
217
+ {placeIn.menu}
218
+ </>
219
+ );
220
+ }
221
+
222
+ /**
223
+ * A READ-ONLY ROW, the house readout (`insp-readout`, widgets.css), the one
224
+ * the Studio's Time section wears: the field's box on the control ground with
225
+ * no hover, its value in the numeric face and the second ink. `prose` is a
226
+ * sentence rather than a measurement (what an item is for, where a still came
227
+ * from): the body face, wrapping, the row aligned to its first line. It never
228
+ * wears `inert`, which dimmed the row to 0.55 and set its numbers in the 10px
229
+ * sans.
230
+ */
231
+ export function ReadoutRow(props: { label: string; prose?: boolean; title?: string; children: ReactNode }) {
232
+ return (
233
+ <InspectorRow label={props.label} className="insp-readout" {...(props.prose === true ? { align: "start" as const } : {})}>
234
+ <span className={`insp-dim${props.prose === true ? " is-text" : ""}`} {...(props.title === undefined ? {} : { title: props.title })}>
235
+ {props.children}
236
+ </span>
237
+ </InspectorRow>
238
+ );
239
+ }
240
+
241
+ /** A requirement's standing as a badge tone (G1 `.badge`): met reads as
242
+ * success, Missing in the accent because it asks for work, Unchecked quiet. */
243
+ const REQUIREMENT_TONE: Record<keyof typeof REQUIREMENT_STATUS_SPECS, string> = {
244
+ satisfied: " is-success",
245
+ done: " is-success",
246
+ missing: " is-accent",
247
+ unchecked: "",
248
+ };
249
+
250
+ /**
251
+ * A REQUIREMENT'S STANDING: whether the video has it (derived on this render,
252
+ * never stored), why, and the two things to do about it: Place and the Done
253
+ * tick. `PlaceAction` above is the one slot for those verbs, so no two
254
+ * surfaces offer different verbs for one card. The board inspector and the
255
+ * component sheet both draw these rows.
256
+ */
257
+ export function RequirementRows({ doc, item }: { doc: Doc3D; item: ReferenceItem3D }) {
258
+ const store = useStore();
259
+ const locked = useLocked();
260
+ const report = requirementStatus(doc, item);
261
+ if (report === null) return null;
262
+ const spec = REQUIREMENT_STATUS_SPECS[report.state];
263
+ const done = item.done === true;
264
+ const where = [report.evidence?.scene, report.evidence?.layer, report.evidence?.clip].filter((x) => x !== undefined);
265
+ return (
266
+ <>
267
+ {/* A readout row holding the house badge and the reason, which wraps:
268
+ the house readout stands the badge 2px short, so a one-line Status
269
+ row is still 28px tall. */}
270
+ <InspectorRow label="Status" align="start" className="insp-readout">
271
+ <span className={`badge${REQUIREMENT_TONE[report.state]}`} data-testid="requirement-status">
272
+ {spec.label}
273
+ </span>
274
+ <span className="insp-dim is-text">
275
+ {report.reason || spec.title}
276
+ {where.length === 0 ? "" : ` (${where.map((x) => `#${x}`).join(" ")})`}
277
+ </span>
278
+ </InspectorRow>
279
+ <InspectorRow>
280
+ <PlaceAction doc={doc} item={item} />
281
+ <button
282
+ type="button"
283
+ className={`btn${done ? " is-active" : ""}`}
284
+ aria-pressed={done}
285
+ disabled={locked}
286
+ title={done ? "Clear the tick: the status goes back to what the video holds" : "Tick it by hand, whatever the video holds"}
287
+ onClick={() => store.setStoryboardDone(item.id, !done)}
288
+ >
289
+ {done ? "Clear done" : "Mark done"}
290
+ </button>
291
+ </InspectorRow>
292
+ </>
293
+ );
294
+ }
295
+
296
+ // Both option lists come off the schema's own spec tables, so the control's
297
+ // words, the card's hover text and the agent brief's legend cannot drift.
298
+ const USES = REFERENCE_USES.map((value) => ({
299
+ value,
300
+ label: REFERENCE_USE_SPECS[value].label,
301
+ title: REFERENCE_USE_SPECS[value].title,
302
+ }));
303
+ const IMPORTANCES = REFERENCE_IMPORTANCES.map((value) => ({
304
+ value,
305
+ label: REFERENCE_IMPORTANCE_SPECS[value].label,
306
+ title: REFERENCE_IMPORTANCE_SPECS[value].title,
307
+ }));
308
+
309
+ /** A typed or dragged frame as the card stores it: a whole, non-negative
310
+ * frame, or null for a value that is no number at all. */
311
+ function cardFrameOf(raw: unknown): number | null {
312
+ return typeof raw === "number" && Number.isFinite(raw) ? Math.max(0, Math.round(raw)) : null;
313
+ }
314
+
315
+ /**
316
+ * THE CARD-USE BLOCK, one for every panel a card is picked in: the board
317
+ * inspector, the component sheet and the shot board's item inspector. It was
318
+ * three copies in three vocabularies ("Asset | Inspiration" in one, "In the
319
+ * shot | Look like this" in another, and nothing at all for a component card
320
+ * on the board, whose one panel is the sheet).
321
+ *
322
+ * Frame a component card's moment (`frame`): the preview holds it
323
+ * instead of looping. Unset reads the dash; the accessory in its
324
+ * box clears it, and the preview loops again.
325
+ * Use Asset (a requirement the video must hold) or Inspiration.
326
+ * Importance Must or Optional.
327
+ *
328
+ * Each writes null for its own default, so a card nobody has judged carries
329
+ * no keys, and a press on the lit chip writes nothing (`updateReference`
330
+ * returns the same document).
331
+ */
332
+ export function CardUseRows({ card }: { card: ReferenceItem3D }) {
333
+ const store = useStore();
334
+ const locked = useLocked();
335
+ const frameScrub = useScrub((doc, raw) => {
336
+ const frame = cardFrameOf(raw);
337
+ return frame === null ? doc : updateReference(doc, card.id, { frame });
338
+ });
339
+ const held = card.type === "component" ? card.frame : undefined;
340
+ return (
341
+ <>
342
+ {card.type === "component" ? (
343
+ <InspectorRow>
344
+ <NumberField
345
+ // No frame is the loop, not frame 0: the field says so with its dash.
346
+ value={held ?? Number.NaN}
347
+ label="Frame"
348
+ ariaLabel="Frame, component"
349
+ min={0}
350
+ step={1}
351
+ title="The moment of the component this card means. The preview holds it."
352
+ onCommit={(next) => {
353
+ const frame = cardFrameOf(next);
354
+ if (frame !== null) store.updateReference(card.id, { frame });
355
+ }}
356
+ onScrubStart={frameScrub.start}
357
+ onScrub={frameScrub.move}
358
+ onScrubEnd={frameScrub.end}
359
+ trailing={
360
+ held === undefined ? null : (
361
+ <button
362
+ type="button"
363
+ className="insp-key"
364
+ aria-label="Clear the component frame"
365
+ title="Loop the preview again"
366
+ disabled={locked}
367
+ onClick={(e) => {
368
+ // The clear goes with the frame it clears, and the focus
369
+ // fell to <body> with it: it goes to the Frame field the
370
+ // clear sat in, which stays.
371
+ const field = e.currentTarget.closest(".numfield")?.querySelector<HTMLElement>(".numfield-input");
372
+ store.updateReference(card.id, { frame: null });
373
+ field?.focus();
374
+ }}
375
+ >
376
+ <IconUndo />
377
+ </button>
378
+ )
379
+ }
380
+ />
381
+ </InspectorRow>
382
+ ) : null}
383
+ <InspectorRow label="Use">
384
+ <SegmentedControl
385
+ options={USES}
386
+ value={referenceUseOf(card)}
387
+ ariaLabel="Use"
388
+ onChange={(value) => store.updateReference(card.id, { use: value === "asset" ? "asset" : null })}
389
+ />
390
+ </InspectorRow>
391
+ <InspectorRow label="Importance">
392
+ <SegmentedControl
393
+ options={IMPORTANCES}
394
+ value={referenceImportanceOf(card)}
395
+ ariaLabel="Importance"
396
+ onChange={(value) => store.updateReference(card.id, { importance: value === "optional" ? "optional" : null })}
397
+ />
398
+ </InspectorRow>
399
+ </>
400
+ );
401
+ }
402
+
403
+ /**
404
+ * THE CARD'S NOTE TO THE AGENT, the same row wherever the card is picked (the
405
+ * board inspector, the component sheet, the item inspector): multiline,
406
+ * trimmed, and dropped when emptied, so a card nobody annotated carries no
407
+ * key. A note card's own words are its `text`, edited where it is drawn.
408
+ */
409
+ export function CardNoteRow({ card }: { card: Exclude<ReferenceItem3D, { type: "note" }> }) {
410
+ const store = useStore();
411
+ return (
412
+ <InspectorRow label="Note" align="start">
413
+ <TextField
414
+ multiline
415
+ value={card.note ?? ""}
416
+ placeholder="What should the agent notice?"
417
+ ariaLabel="Note"
418
+ onCommit={(text) => {
419
+ const note = text.trim();
420
+ if (note !== (card.note ?? "")) store.updateReference(card.id, { note: note === "" ? null : note });
421
+ }}
422
+ />
423
+ </InspectorRow>
424
+ );
425
+ }