@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,180 @@
1
+ // MAKE IT YOURS: the open Project's template fields (schema v50), one row each,
2
+ // for the person who started from a template and now swaps in their own
3
+ // screenshot, headline and logo.
4
+ //
5
+ // A container section: it shows with nothing selected in Studio, above the
6
+ // Shots and inside one alike, and only while `swapFieldsOf` lists a field (a
7
+ // data gate, layout.ts). The rows keep that list's order, the order a viewer
8
+ // meets the fields: the Shots in playing order, then the definitions.
9
+ //
10
+ // ── EVERY WRITE IS AN EXISTING ONE, AIMED AT THE ROW'S OWN LAYER ────────
11
+ //
12
+ // A media row's Replace is the Media section's `replaceLayerMedia`, and a text
13
+ // row writes `text.content` through `writeSelection`, the Text section's path,
14
+ // each told the field's container: a field can sit in a Shot the playhead is
15
+ // not in, or in a definition. So a viewer's refusal, the lock rule and the one
16
+ // undo step are theirs. A locked layer keeps its row, its control locked, and
17
+ // the lock's own sentence on it. The row's name only navigates (into its Shot,
18
+ // or its component, with the layer selected), so it stays live for a viewer.
19
+ import { useMemo, useRef, useState } from "react";
20
+ import { InspectorSection, TextField, useLocked } from "@dailies-studio/ui";
21
+ import { containerFrame, homeOf, lockRefusal, swapFieldsOf } from "@dailies-studio/shot/doc";
22
+ import type { Doc3D, Layer3D, SwapField } from "@dailies-studio/shot/doc";
23
+ import { useShotEditor, useStore } from "../../../state/context.js";
24
+ import type { ShotEditorStore } from "../../../state/store.js";
25
+ import { writeSelection } from "../../../state/fieldWrite.js";
26
+ import { authoringFrame } from "../../../state/viewMode.js";
27
+ import { IMAGE_ACCEPT, VIDEO_ACCEPT } from "../../toolbar/useAddRows.js";
28
+ import { MediaPoster, assetUrl } from "../../storyboard/ReferencePreview.js";
29
+
30
+ export function MakeItYoursSection() {
31
+ const doc = useShotEditor((s) => s.doc);
32
+ const fields = useMemo(() => (doc === null ? [] : swapFieldsOf(doc)), [doc]);
33
+ if (doc === null || fields.length === 0) return null;
34
+ return (
35
+ <InspectorSection
36
+ title="Make it yours"
37
+ persistKey="shot:make-it-yours"
38
+ summary={`${fields.length} ${fields.length === 1 ? "field" : "fields"}`}
39
+ >
40
+ <ul className="make-it-yours" data-testid="make-it-yours">
41
+ {fields.map((field) => (
42
+ <FieldRow key={`${field.containerId}/${field.layerId}`} doc={doc} field={field} />
43
+ ))}
44
+ </ul>
45
+ </InspectorSection>
46
+ );
47
+ }
48
+
49
+ function FieldRow({ doc, field }: { doc: Doc3D; field: SwapField }) {
50
+ const store = useStore();
51
+ const layer = homeOf(doc, field.containerId)?.layersById[field.layerId];
52
+ if (layer === undefined) return null;
53
+ const lock = layer.locked === true ? lockRefusal(layer) : null;
54
+ const where = [
55
+ field.containerName,
56
+ ...(field.container === "component" ? ["changes every use"] : []),
57
+ ...(lock === null ? [] : ["Locked"]),
58
+ ].join(" · ");
59
+ return (
60
+ <li className="make-it-yours-row" data-field={field.layerId}>
61
+ <div className="make-it-yours-head">
62
+ {layer.kind === "image" || layer.kind === "video" ? <MediaThumb doc={doc} layer={layer} /> : null}
63
+ <button
64
+ type="button"
65
+ className="make-it-yours-name"
66
+ title={field.container === "component" ? `Open ${field.containerName} and select this layer` : `Go to ${field.containerName} and select this layer`}
67
+ onClick={() => goToField(store, field)}
68
+ >
69
+ <span className="make-it-yours-label">{field.label}</span>
70
+ <span className="make-it-yours-where">{where}</span>
71
+ </button>
72
+ {layer.kind === "image" || layer.kind === "video" ? <ReplaceButton field={field} kind={layer.kind} lock={lock} /> : null}
73
+ </div>
74
+ {layer.kind === "text" ? (
75
+ <TextField
76
+ value={layer.text.content}
77
+ ariaLabel={`${field.label}, text`}
78
+ // A one-line field commits on Enter; content that already breaks a
79
+ // line keeps the textarea, so an edit never folds its lines.
80
+ multiline={layer.text.content.includes("\n")}
81
+ {...(lock === null ? {} : { readOnly: true, title: lock })}
82
+ onCommit={(content) => writeText(store, field, content)}
83
+ />
84
+ ) : null}
85
+ </li>
86
+ );
87
+ }
88
+
89
+ /** The field's picture as it stands: the stored file, small. */
90
+ function MediaThumb({ doc, layer }: { doc: Doc3D; layer: Extract<Layer3D, { kind: "image" | "video" }> }) {
91
+ const asset = doc.assets[layer.kind === "image" ? layer.image.asset : layer.video.asset];
92
+ return (
93
+ <span className="make-it-yours-thumb" aria-hidden="true" {...(asset === undefined ? { title: "File missing" } : {})}>
94
+ {asset === undefined ? null : layer.kind === "image" ? (
95
+ <img className="make-it-yours-media" src={assetUrl(asset.path)} alt="" draggable={false} loading="lazy" />
96
+ ) : (
97
+ <MediaPoster className="make-it-yours-media" src={assetUrl(asset.path)} atSeconds={null} />
98
+ )}
99
+ </span>
100
+ );
101
+ }
102
+
103
+ /** Replace…, the Media section's verb, for the row's layer. A locked layer's
104
+ * press says why instead of opening the picker. */
105
+ function ReplaceButton({ field, kind, lock }: { field: SwapField; kind: "image" | "video"; lock: string | null }) {
106
+ const store = useStore();
107
+ const locked = useLocked();
108
+ const fileRef = useRef<HTMLInputElement>(null);
109
+ const [busy, setBusy] = useState(false);
110
+ const pick = (file: File): void => {
111
+ setBusy(true);
112
+ void store
113
+ .replaceLayerMedia(field.layerId, file, field.containerId)
114
+ .catch((e: unknown) => store.reportError(e instanceof Error ? e.message : String(e)))
115
+ .finally(() => setBusy(false));
116
+ };
117
+ return (
118
+ <>
119
+ <button
120
+ type="button"
121
+ className="btn"
122
+ aria-label={`Replace, ${field.label}`}
123
+ title={lock ?? `Swap ${field.label} for a file of your own`}
124
+ disabled={locked || busy}
125
+ aria-disabled={lock !== null || undefined}
126
+ onClick={() => {
127
+ if (lock === null) fileRef.current?.click();
128
+ else store.reportError(lock);
129
+ }}
130
+ >
131
+ Replace…
132
+ </button>
133
+ <input
134
+ ref={fileRef}
135
+ type="file"
136
+ hidden
137
+ aria-label={`${field.label}, ${kind} file`}
138
+ accept={kind === "video" ? VIDEO_ACCEPT : IMAGE_ACCEPT}
139
+ onChange={(event) => {
140
+ const file = event.currentTarget.files?.[0];
141
+ event.currentTarget.value = "";
142
+ if (file !== undefined) pick(file);
143
+ }}
144
+ />
145
+ </>
146
+ );
147
+ }
148
+
149
+ /** The Text section's write, `writeSelection` over the one layer, in the
150
+ * field's own container: one undo step, a locked layer named. */
151
+ function writeText(store: ShotEditorStore, field: SwapField, content: string): void {
152
+ const s = store.getState();
153
+ if (s.doc === null) return;
154
+ const at = containerFrame(s.doc, field.containerId, authoringFrame(s));
155
+ let refusal: string | null = null;
156
+ const ran = store.edit((doc) => {
157
+ const out = writeSelection(
158
+ doc,
159
+ field.containerId,
160
+ [field.layerId],
161
+ (layer) => (layer.kind === "text" ? ({ ...layer, text: { ...layer.text, content } } as Layer3D) : layer),
162
+ at,
163
+ );
164
+ refusal = out.refusal;
165
+ return out.doc;
166
+ });
167
+ if (ran && refusal !== null) store.reportError(refusal);
168
+ }
169
+
170
+ /** Into the field's Shot, or its component's definition, with the layer
171
+ * selected: the verbs Every use's Go to takes (`goToComponentUse`). */
172
+ function goToField(store: ShotEditorStore, field: SwapField): void {
173
+ if (field.container === "component") {
174
+ store.editComponent(field.containerId);
175
+ if (store.getState().editingDef === field.containerId) store.select([field.layerId]);
176
+ return;
177
+ }
178
+ store.enterScene(field.containerId);
179
+ if (store.getState().enteredScene === field.containerId) store.select([field.layerId]);
180
+ }
@@ -0,0 +1,191 @@
1
+ // THE PROJECT ITSELF: the document's own four fields, shown in both contexts
2
+ // when nothing is selected.
3
+ //
4
+ // ── COLLAPSIBLE IN BOTH, OPEN BY DEFAULT ONLY WHERE IT IS THE SUBJECT ─────
5
+ //
6
+ // It was pinned open and mounted in the project context alone. Inside a shot
7
+ // the size and rate are still the frame the author is composing into, so the
8
+ // rail now carries the section there too, collapsed to one mono line
9
+ // ("1920×1080 · 30fps") that answers the question without the four rows. In
10
+ // the project context the rows ARE the work, so it opens by default there.
11
+ // The two contexts keep separate collapse bits for that reason: one shared bit
12
+ // would make collapsing it inside a shot hide the project's own panel.
13
+ //
14
+ // ── WHY THIS DID NOT EXIST ──────────────────────────────────────────────
15
+ //
16
+ // The rail's empty-selection branch mounted the SHOT sections in both contexts,
17
+ // so the scene context described whichever shot the playhead was over and
18
+ // changed under every scrub across a cut. The document's own fields had no
19
+ // writer anywhere in the chrome: `doc.fps` and `doc.size` were read by the
20
+ // export dialog and by nothing else, and `doc.name` by nothing at all. A tool
21
+ // that can open a video, arrange it and render it, but cannot say what rate or
22
+ // what size it is, is missing a panel rather than a feature.
23
+ //
24
+ // ── THE RATE IS A PICKER AND NOT A NUMBER ────────────────────────────────
25
+ //
26
+ // `doc.fps` is a RATIONAL, and the rates that matter are not all integers, so
27
+ // the control offers exact pairs from `FPS_OPTIONS` (the one table both editors
28
+ // read) rather than a decimal field. A document naming a rate that list does not
29
+ // have keeps it, labelled, exactly as the transition picker keeps an unknown
30
+ // preset rather than silently rewriting it.
31
+ import { Combobox, InspectorRow, InspectorSection, NumberField, TextField } from "@dailies-studio/ui";
32
+ import type { ComboboxOption } from "@dailies-studio/ui";
33
+ import { FPS_OPTIONS } from "@dailies-studio/timeline";
34
+ import { docDurationFrames } from "@dailies-studio/shot/doc";
35
+ import { framesToSeconds } from "@dailies-studio/engine";
36
+ import type { Rational } from "@dailies-studio/engine";
37
+ import { useShotEditor, useStore } from "../../../state/context.js";
38
+ import { SECTION_ICONS } from "../../../icons.js";
39
+ import { formatDuration } from "../../timeline/model.js";
40
+
41
+ /** "<num>/<den>" — the picker's value, so an exact rational round-trips through
42
+ * a string-valued control without a lookup table on the way back. */
43
+ const rateKey = (fps: Rational): string => `${fps.num}/${fps.den}`;
44
+
45
+ /** Said on the label and on the field, because either one may be what the
46
+ * pointer lands on and a field that moves a source file should warn from
47
+ * both. */
48
+ const COMPONENT_TITLE =
49
+ "The generated component's name. Changing it moves src/shots/<Name>.tsx on the next save.";
50
+
51
+ export function SceneDocSection() {
52
+ const store = useStore();
53
+ const doc = useShotEditor((s) => s.doc);
54
+ const inShot = useShotEditor((s) => s.enteredScene !== null);
55
+ if (doc === null) return null;
56
+
57
+ const total = docDurationFrames(doc);
58
+ const key = rateKey(doc.fps);
59
+ const known = FPS_OPTIONS.some((r) => rateKey(r.fps) === key);
60
+ const options: ComboboxOption[] = [
61
+ // FPS_OPTIONS is fastest-first, as the motion editor has always shown it;
62
+ // this picker reads slowest-first, so the copy is what gets reversed.
63
+ ...FPS_OPTIONS.map((r) => ({ value: rateKey(r.fps), label: r.label })).reverse(),
64
+ // A rate this build's list does not carry is KEPT and named. Dropping to
65
+ // the nearest one would retime the whole video on the first unrelated edit.
66
+ ...(known ? [] : [{ value: key, label: `${(doc.fps.num / doc.fps.den).toFixed(3)} (${key})` }]),
67
+ ];
68
+
69
+ // The rate as a plain number: 30, 29.97, 23.976. Three places at most and
70
+ // trailing zeros dropped, so an integer rate does not read "30.000".
71
+ const fps = Number((doc.fps.num / doc.fps.den).toFixed(3));
72
+
73
+ // A DRAG on W or H resizes the frame under the pointer, as every other
74
+ // dragged field writes: the panel scrub around `setDocSize`, which captures
75
+ // into it while it is open (store.ts), so the whole drag is one undo entry.
76
+ // Each move names the one side it moves and keeps the other as drawn.
77
+ const dragSize = (side: "width" | "height") => ({
78
+ onScrubStart: () => store.beginScrub(),
79
+ onScrub: (value: number) => store.setDocSize({ ...doc.size, [side]: value }),
80
+ onScrubEnd: (commit: boolean) => store.endScrub(commit),
81
+ });
82
+
83
+ return (
84
+ <InspectorSection
85
+ title="Project"
86
+ icon={SECTION_ICONS.project}
87
+ summary={`${doc.size.width}×${doc.size.height} · ${fps}fps`}
88
+ numericSummary
89
+ // `shot:document` is the project context's existing bit, kept so nobody's
90
+ // stored collapse state is thrown away.
91
+ persistKey={inShot ? "shot:document:in-shot" : "shot:document"}
92
+ defaultCollapsed={inShot}
93
+ // No section scope: the rhythm, the fields and the readout face its two
94
+ // derived rows wear are the house's (widgets.css), for every panel alike.
95
+ >
96
+ {/* "Component" and not "Generated component": `.insp-label` is capped at
97
+ 60% of a 288px pill, so the longer wording ellipsised ITSELF and then
98
+ truncated the value beside it, which is the one thing on the row an
99
+ author needs to read. The full story stays on the label's title and in
100
+ the aria name, where length costs nothing. */}
101
+ <InspectorRow label="Component" labelTitle={COMPONENT_TITLE}>
102
+ {/* `doc.name`, not `state.name`. The latter is the stable Project slug
103
+ used by the URL and server; this is the name the
104
+ generated component takes, so committing it moves
105
+ `src/shots/<Name>.tsx` on the next save. The title says so, because
106
+ a field that silently moves a source file is worse than one that
107
+ warns. */}
108
+ <TextField
109
+ value={doc.name}
110
+ ariaLabel="Generated component name"
111
+ title={COMPONENT_TITLE}
112
+ onCommit={(name) => store.setDocName(name)}
113
+ />
114
+ </InspectorRow>
115
+ {/* THE FRAME'S SIZE, A PAIR LIKE TRANSFORM'S W | H: two boxes, each
116
+ its own field with its prefix inside, and no "Size" pill around them
117
+ (a labelled box holding two more boxes drew the name twice and
118
+ squeezed both values). Whole pixels from one up, the store's clamp
119
+ stated where the author types. */}
120
+ <InspectorRow>
121
+ <div className="insp-vec">
122
+ <NumberField
123
+ value={doc.size.width}
124
+ label="W"
125
+ ariaLabel="Project width"
126
+ unit="px"
127
+ min={1}
128
+ step={1}
129
+ {...dragSize("width")}
130
+ onCommit={(width) => store.setDocSize({ ...doc.size, width })}
131
+ />
132
+ <NumberField
133
+ value={doc.size.height}
134
+ label="H"
135
+ ariaLabel="Project height"
136
+ unit="px"
137
+ min={1}
138
+ step={1}
139
+ {...dragSize("height")}
140
+ onCommit={(height) => store.setDocSize({ ...doc.size, height })}
141
+ />
142
+ </div>
143
+ </InspectorRow>
144
+ <InspectorRow label="Rate">
145
+ <Combobox
146
+ ariaLabel="Frame rate"
147
+ options={options}
148
+ value={key}
149
+ onChange={(next) => {
150
+ const [num, den] = next.split("/").map(Number);
151
+ if (num === undefined || den === undefined) return;
152
+ store.setDocFps({ num, den });
153
+ }}
154
+ />
155
+ </InspectorRow>
156
+ {/* DERIVED, so both are text and not fields: the length of a scene is the
157
+ sum of its shots' durations, and the one place to change it is the
158
+ Duration row in the Shot section below. A writable total would need a
159
+ rule for which shot absorbed the difference, and there is no honest
160
+ answer to that.
161
+
162
+ SO THE WORD APPEARS TWICE IN THIS RAIL, once read-only here and once
163
+ editable there, and the section heading above each — Project, Shot —
164
+ is what separates them. That is the deliberate trade: the two rows
165
+ hold the same quantity in the same unit, and spelling one of them
166
+ differently would invent a distinction an author would go looking for
167
+ and not find.
168
+
169
+ TWO ROWS AND NOT ONE. "1:04 · 1920f · 7 shots" read as one cryptic
170
+ string because it answered two different questions at once: how long
171
+ the film runs, and how many pieces it is in. A labelled row each says
172
+ which number is which without a legend.
173
+
174
+ IN THE HOUSE READOUT (`insp-readout`, widgets.css), the Time
175
+ section's: the field's box with nothing to type into and no hover, the
176
+ number in the numeral face in the second ink. They were the 10px sans
177
+ `.insp-dim` alone, which set a total in a smaller face than every
178
+ value around it. */}
179
+ <InspectorRow label="Duration" className="insp-readout">
180
+ <span className="insp-dim" data-testid="scene-length">
181
+ {formatDuration(framesToSeconds(total, doc.fps))} · {total}f
182
+ </span>
183
+ </InspectorRow>
184
+ <InspectorRow label="Shots" className="insp-readout">
185
+ <span className="insp-dim" data-testid="scene-shots">
186
+ {doc.scenes.length}
187
+ </span>
188
+ </InspectorRow>
189
+ </InspectorSection>
190
+ );
191
+ }
@@ -1,71 +1,246 @@
1
- // The scene's own properties, shown when nothing is selected. The subject
2
- // switch is the SELECTION, not a tab: a layer inspector and a scene inspector
1
+ // The shot's own properties, shown when nothing is selected. The subject
2
+ // switch is the SELECTION, not a tab: a layer inspector and a shot inspector
3
3
  // are the same panel looking at whatever the author last clicked.
4
- import { ColorPicker, InspectorRow, InspectorSection, NumberField, TextField } from "@dailies-studio/ui";
5
- import type { Scene3D } from "@dailies-studio/shot/doc";
4
+ //
5
+ // ── THE ROWS ARE SHARED, THE SECTION IS NOT ──────────────────────────────
6
+ //
7
+ // `ShotRows` is the body and two sections wear it. Inside a shot it is
8
+ // `SceneSection`, "Shot". In the project context it is the top half of
9
+ // "Shot & transition" (TransitionSection.tsx), because there the shot and the
10
+ // seam after it are read together. One body, so a row added here lands in
11
+ // both and the two can never disagree about what a shot is.
12
+ //
13
+ // Name and Duration are the shot's IDENTITY and its place on the master
14
+ // timeline, so they show in both contexts (the Name row with one exception,
15
+ // see its own note). Background is what the shot LOOKS like, a judgement made
16
+ // against the framed picture, so it stays inside the shot.
17
+ //
18
+ // THE LENS MOVED TO CAMERA as its FOV row. `fovDeg` is stored on the scene,
19
+ // but it is a camera property to the author: it sets how far the camera
20
+ // stands, which is what parallax strength is. `patchScene` is exported for
21
+ // that row so the lens keeps this file's one write path.
22
+ import { InspectorRow, InspectorSection, NumberField, TextField } from "@dailies-studio/ui";
23
+ import { containerFrame, trimScene } from "@dailies-studio/shot/doc";
24
+ import type { Doc3D, Scene3D } from "@dailies-studio/shot/doc";
25
+ import { SceneKeyframeDot } from "../KeyframeDot.js";
26
+ import { SECTION_ICONS, SPARKLES_ICON } from "../../../icons.js";
27
+ import { PaintRows } from "../design/PaintRows.js";
6
28
  import { useShotEditor, useStore } from "../../../state/context.js";
7
- import { NO_SCRUB } from "../useDocWrite.js";
29
+ import { backgroundLayerOf } from "../../../state/insert.js";
30
+ import { useInspectorScene, useScrub, writeScene } from "../useDocWrite.js";
8
31
 
32
+ /**
33
+ * One scene, patched, over the document it is HANDED.
34
+ *
35
+ * Both write paths run it: a typed value through `edit`, and a drag through
36
+ * `scrubCapture`, which re-derives from the gesture's BASE. Two spellings of
37
+ * this would be two answers to what a background drag did, and the drag's is
38
+ * the one nobody would be watching.
39
+ */
40
+ export function patchScene(doc: Doc3D, sceneId: string, next: Partial<Scene3D>, frame: number): Doc3D {
41
+ // Through `writeScene`, so a shot that ANIMATES its background takes a
42
+ // keyframe at the playhead rather than a constant the track is already
43
+ // overriding (P5). Nothing else it writes is trackable (a name, a duration
44
+ // and the camera's lens are not per-frame values), so this is one path's
45
+ // rule riding the seam every field already crosses.
46
+ return writeScene(doc, sceneId, (scene) => ({ ...scene, ...next }), containerFrame(doc, sceneId, frame));
47
+ }
48
+
49
+ /**
50
+ * Inside a shot, the shot alone: the seam after it is off screen there, so the
51
+ * transition rows are not offered (see `CONTAINER_LAYOUT`).
52
+ *
53
+ * COLLAPSIBLE, like every sibling in the rail. It was pinned open because it
54
+ * was the only thing naming the shot, so collapsing it left the panel
55
+ * describing something it no longer named. The head carries the name now, so
56
+ * the section is free to behave like the rest of the stack. The `persistKey`
57
+ * is unchanged: it is invisible to the author, and renaming it would only
58
+ * throw away the collapse state people already have.
59
+ */
9
60
  export function SceneSection() {
61
+ // A boolean, so the selector returns a primitive and never allocates.
62
+ const has = useShotEditor((s) => s.sceneId !== null && s.doc?.scenesById[s.sceneId] !== undefined);
63
+ if (!has) return null;
64
+ // The same rows in the same look as "Shot & transition", which wears the
65
+ // same body: both are the house rows (widgets.css), with no section scope.
66
+ return (
67
+ <InspectorSection title="Shot" icon={SECTION_ICONS.shot} persistKey="shot:scene">
68
+ <ShotRows />
69
+ </InspectorSection>
70
+ );
71
+ }
72
+
73
+ /** The shot's own rows, the body both shot sections wear. */
74
+ export function ShotRows() {
10
75
  const store = useStore();
11
76
  const doc = useShotEditor((s) => s.doc);
12
77
  const sceneId = useShotEditor((s) => s.sceneId);
13
- if (doc === null || sceneId === null) return null;
14
- const scene = doc.scenesById[sceneId];
15
- if (scene === undefined) return null;
78
+ /** Inside a shot (or a definition, which mounts its own panel): the
79
+ * background rows below. The same bit the rest of the chrome branches on. */
80
+ const inShot = useShotEditor((s) => s.enteredScene !== null);
81
+ const readOnly = useShotEditor((s) => s.readOnly);
82
+ const ground = useShotEditor((s) =>
83
+ s.enteredScene === null ? null : backgroundLayerOf(s.doc?.scenesById[s.enteredScene]),
84
+ );
85
+
86
+ // ── THE DRAG HALF, DECLARED BEFORE THE EARLY RETURNS ──────────────────
87
+ //
88
+ // Before, because a hook that sits after a conditional return changes the
89
+ // hook count between renders. ONE bracket for every patchable field: the
90
+ // widget sends the PATCH it is pointing at, so every background control
91
+ // shares one capture rule instead of one hook each.
92
+ //
93
+ // The scene is read from the STORE inside the capture, never closed over
94
+ // from this render. It is the same live read `useSelectionScrub` makes, so a
95
+ // drag held across a scene switch cannot write into the scene the panel last
96
+ // rendered against.
97
+ const dragScene = useScrub((d, raw) => {
98
+ const id = store.getState().sceneId;
99
+ // The frame LIVE at the capture and against the document BEING WRITTEN.
100
+ return id === null ? d : patchScene(d, id, raw as Partial<Scene3D>, store.getState().frame);
101
+ });
102
+ // The duration goes through `trimScene` and not through the patch above,
103
+ // because that op owns the floor clamp and returns the base BY REFERENCE when
104
+ // the number did not move, so a drag that ends where it started commits
105
+ // nothing, exactly as the typed path behaves.
106
+ const dragDuration = useScrub((d, raw) => {
107
+ const id = store.getState().sceneId;
108
+ return id === null ? d : trimScene(d, id, Number(raw));
109
+ });
110
+ // TYPED at the call sites. `move` takes `unknown`, so without this a
111
+ // mistyped field name would ride a drag straight into the document and only
112
+ // show up as a control that does nothing.
113
+ const moveScene = (next: Partial<Scene3D>): void => dragScene.move(next);
114
+
115
+ // THE SHOT AS PAINTED, not the authored constants. `background.color` is
116
+ // keyframable, and once it carries a track the constant this used to read is
117
+ // pinned by `routeTrackedEdit` — so the well showed a colour the stage had
118
+ // long left, and dragging it moved nothing on screen. Display only; every
119
+ // write below still goes through `patchScene` over the handed document.
120
+ const scene = useInspectorScene();
121
+
122
+ if (doc === null || sceneId === null || scene === null) return null;
16
123
 
17
124
  // A block body, not a concise one: `edit` reports whether it RAN (a drag
18
125
  // holds the history open and refuses), and this caller has nothing to do with
19
126
  // that answer — returning it out of a `void` handler is the type error that
20
127
  // says so.
21
128
  const patch = (next: Partial<Scene3D>): void => {
22
- store.edit((d) => ({
23
- ...d,
24
- scenesById: { ...d.scenesById, [sceneId]: { ...d.scenesById[sceneId]!, ...next } },
25
- }));
129
+ store.edit((d) => patchScene(d, sceneId, next, store.getState().frame));
26
130
  };
27
131
 
28
132
  return (
29
- <InspectorSection title="Shot" persistKey="shot:scene" collapsible={false}>
30
- <InspectorRow label="Name">
31
- <TextField value={scene.name} ariaLabel="Shot name" onCommit={(name) => patch({ name })} />
32
- </InspectorRow>
133
+ <>
134
+ {/* ── THE NAME ROW, IN ONE CONTEXT ONLY ────────────────────────────
135
+ Inside a shot the head IS this field (`InspectorHead`'s "Shot"
136
+ subject, renaming through `renameScene`), and a heading plus a Name
137
+ row two lines below it holding the same string is the panel saying
138
+ the same thing twice.
139
+
140
+ In the SCENE context the head names the PROJECT, because that is what
141
+ the panel is a view of there, and the shot below it is one of its
142
+ parts. So the row stays: dropping it in both contexts would leave the
143
+ scene context with no way to rename a shot at all, and a capability's
144
+ only affordance has to remain visible at rest. */}
145
+ {inShot ? null : (
146
+ <InspectorRow label="Name">
147
+ <TextField value={scene.name} ariaLabel="Shot name" onCommit={(name) => patch({ name })} />
148
+ </InspectorRow>
149
+ )}
33
150
  <InspectorRow>
151
+ {/* DURATION, in frames, carrying its unit in the field's own suffix the
152
+ way every other frame count in this panel does. `ariaLabel` is
153
+ unchanged: it is what the tests address this field by and it already
154
+ said "duration".
155
+
156
+ THE WORD IS THE PROJECT'S, AND THE PROJECT'S ROW IS NOT A FIELD.
157
+ `SceneDocSection`'s Duration is a DERIVED read-only total
158
+ ("1:04 · 1920f", the sum of every shot), and the Project section
159
+ sits directly above this one in both contexts. So the rail now shows the word twice
160
+ there, once read-only and once editable, and the section heading
161
+ above each — Project, Shot — is what separates them. That is the
162
+ trade this rename made, and it is the reason `data-testid` and
163
+ `aria-label` stayed put: they, not the visible word, are how
164
+ anything addresses either row.
165
+
166
+ The transition rows say "Duration" too, because in the project
167
+ context they share this row's section and Duration, Duration, Frames
168
+ for three lengths in frames is the panel inventing a distinction
169
+ that is not there. The row still spelled "Frames",
170
+ `DefinitionSection`'s, never shares a rail with a Duration row, so
171
+ nothing reads two words for one quantity anywhere. That is
172
+ structural and not luck: the definition container renders
173
+ `DefinitionSection` alone. */}
34
174
  <NumberField
35
175
  value={scene.durationFrames}
36
- label="Frames"
176
+ label="Duration"
177
+ unit="f"
37
178
  ariaLabel="Shot duration"
38
179
  min={1}
39
180
  step={1}
181
+ onScrubStart={dragDuration.start}
182
+ onScrub={dragDuration.move}
183
+ onScrubEnd={dragDuration.end}
40
184
  onCommit={(durationFrames) => store.trimScene(sceneId, durationFrames)}
41
185
  />
42
186
  </InspectorRow>
43
- <InspectorRow>
44
- {/* THE ONLY CONTROL ON PARALLAX STRENGTH. A wide fov puts the camera
45
- close, so a depth offset changes scale a lot; a long lens puts it far
46
- and the same offset barely registers (resolve.ts's planeDistance). */}
47
- <NumberField
48
- value={scene.fovDeg}
49
- label="Lens"
50
- ariaLabel="Field of view"
51
- unit="deg"
52
- min={5}
53
- max={120}
54
- step={1}
55
- onCommit={(fovDeg) => patch({ fovDeg })}
56
- />
57
- </InspectorRow>
58
- <InspectorRow label="Background">
59
- {/* A scene is OPAQUE by contract (types.ts), so there is no alpha here
60
- and none is offered — the renderer clears with alpha 1, and a
61
- transparent background would be a promise the export cannot keep. */}
62
- <ColorPicker
63
- value={scene.background.type === "solid" ? scene.background.color : "#0b0b0d"}
64
- ariaLabel="Background colour"
65
- scrub={NO_SCRUB}
66
- commit={(color) => patch({ background: { type: "solid", color } })}
67
- />
68
- </InspectorRow>
69
- </InspectorSection>
187
+ {inShot ? (
188
+ <>
189
+ {/* A scene is OPAQUE by contract (types.ts), so there is no alpha
190
+ anywhere in these rows and none is offered — the renderer clears
191
+ with alpha 1, and a transparent background would be a promise the
192
+ export cannot keep. `opaque` is what keeps that true: without it
193
+ every picker here showed an alpha bar and took an 8-digit hex.
194
+
195
+ The rows write the whole `Paint` union, which is what the renderer
196
+ has always read: `BackgroundPlane` draws both gradients for real
197
+ through the ordinary Canvas2D rasterizer. Before this, the row was
198
+ a solid-only picker whose scrub AND commit each wrote
199
+ `{type: "solid"}`, so touching the swatch DELETED a gradient an
200
+ agent or an MCP call had authored.
201
+
202
+ The keyframe diamond rides the SOLID row only. `background.color`
203
+ is a colour track and the track table refuses it over a gradient,
204
+ which is a paint with no single colour to interpolate. */}
205
+ <PaintRows
206
+ value={scene.background}
207
+ ariaPrefix="Background"
208
+ // The type row READS "Background" here and plain "Type" in
209
+ // `FillSection`, whose own heading already says Fill. Under this
210
+ // heading — "Shot", beside a Duration — "Type" would
211
+ // name nothing it is the type of.
212
+ label="Background"
213
+ opaque
214
+ solidTrailing={<SceneKeyframeDot path="background.color" />}
215
+ onScrubStart={dragScene.start}
216
+ onScrub={(edit) => moveScene({ background: edit(scene.background) })}
217
+ onScrubEnd={dragScene.end}
218
+ onCommit={(edit) => patch({ background: edit(scene.background) })}
219
+ />
220
+ {/* THE DRAWER'S OTHER DOOR. A shader ground is a locked layer at the
221
+ back, not a Paint, so these rows cannot hold one. This opens the
222
+ Insert drawer at Backgrounds, where "Use as background" places it.
223
+ Once one is placed, the door selects it instead: a locked layer
224
+ skips stage clicks, so this and its outline row are the ways to
225
+ its props. Hidden for a viewer, like the drawer it opens. */}
226
+ {readOnly ? null : (
227
+ <InspectorRow>
228
+ <div className="insp-buttons">
229
+ <button
230
+ type="button"
231
+ className="btn shot-shader-button"
232
+ onClick={() =>
233
+ ground === null ? store.openInsertDrawer("Backgrounds") : store.select([ground])
234
+ }
235
+ >
236
+ {SPARKLES_ICON}
237
+ {ground === null ? "Shader background…" : "Edit background"}
238
+ </button>
239
+ </div>
240
+ </InspectorRow>
241
+ )}
242
+ </>
243
+ ) : null}
244
+ </>
70
245
  );
71
246
  }