@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
@@ -1,110 +1,359 @@
1
- // The tool dock: one button per DOCK_TOOLS row, plus the shape flyout.
1
+ // The tool dock: one button per DOCK_TOOLS row, plus the shape flyout, the
2
+ // Insert drawer's +, the Comment tool and the Animate mode. Eleven controls:
3
+ // one run of ten (two `.tool-group`s, the menu and Comment), one rule, then
4
+ // the mode.
2
5
  //
3
6
  // Everything here is derived from `TOOL_SPECS`. The icon map is keyed on
4
7
  // `DockTool` and `FlyoutTool`, so a tool added to the table without a glyph is
5
8
  // a COMPILE ERROR rather than an empty button — which is the whole reason the
6
9
  // table's surfaces are types rather than strings.
7
- import { useState } from "react";
8
- import { DOCK_TOOLS, FLYOUT_TOOLS, specOf } from "../../tools.js";
10
+ //
11
+ // ── THE ROW IS GROUPED, AND THE GROUPING IS THE TABLE'S ──────────────────
12
+ //
13
+ // A flat run of ten controls said all ten were the same kind of thing. Two of
14
+ // them say what a press MEANS, six make something, one is a menu and the last
15
+ // is a mode. The groups are SEMANTIC (`role="group"`, named for a screen
16
+ // reader) and the row draws ONE rule, before the mode, which is Paper's footer:
17
+ // Pick, Draw and Add read as one run of aimed controls, and only the mode is
18
+ // a different kind of thing at a glance. The two tool groups come from
19
+ // `DOCK_GROUPS`, which is derived from `ToolSpec.group`: this file never asks
20
+ // which group a tool is in, so a new tool cannot land in the dock ungrouped
21
+ // and it cannot land in a group the table disagrees with.
22
+ import { Fragment, useRef, useState } from "react";
23
+ import type { ReactNode } from "react";
24
+ import { DOCK_GROUPS, FLYOUT_TOOLS, specOf } from "../../tools.js";
9
25
  import type { DockTool, FlyoutTool, ShotTool } from "../../tools.js";
10
26
  import { useShotEditor, useStore } from "../../state/context.js";
11
- import { AddMenu } from "./AddMenu.js";
12
-
13
- /** Text glyphs rather than an icon set: one fewer dependency, and every one of
14
- * these is a shape the font already draws. */
15
- const DOCK_GLYPHS: Record<DockTool, string> = {
16
- select: "▹",
17
- move: "✥",
18
- text: "T",
19
- image: "▤",
20
- rect: "▭",
21
- path: "✎",
22
- };
23
-
24
- const FLYOUT_GLYPHS: Record<FlyoutTool, string> = {
25
- ellipse: "◯",
26
- line: "╱",
27
- arrow: "↗",
28
- };
27
+ import { isSceneContext, toolLayoutMode } from "../../state/viewMode.js";
28
+ import { toolRefusal } from "../../state/keymap.js";
29
+ import { isolationInsertRefusal } from "../../state/isolationTrail.js";
30
+ import { toolKey } from "../../state/shortcuts.js";
31
+ import { NO_COMMENT, readOnlyReason } from "../../state/workspace.js";
32
+ import { COMMENT_ICON } from "../comments/parts.js";
33
+ import { toolFooterShown } from "../../state/panelSize.js";
34
+ import { InsertButton } from "./InsertButton.js";
35
+ import { useDismiss } from "./useDismiss.js";
36
+ // The shared set (basics overhaul): the text glyphs became SVGs with the same
37
+ // typed-map guarantee — a dock tool added to tools.ts without a glyph in
38
+ // icons.tsx does not build.
39
+ import { DOCK_TOOL_ICONS, FLYOUT_TOOL_ICONS, RECORD_ICON, SHAPES_ICON } from "../../icons.js";
29
40
 
30
41
  export function ToolDockBar() {
31
42
  const store = useStore();
32
43
  const tool = useShotEditor((s) => s.tool);
33
44
  const animateMode = useShotEditor((s) => s.animateMode);
34
- const open = useShotEditor((s) => s.name !== null);
45
+ // WHAT IS ON SCREEN. In Video the frame is, whatever layout waits
46
+ // underneath, and `setTool` brings that layout to the frame when a
47
+ // camera-pane tool is armed from there. So a dock that read the chosen
48
+ // layout would grey out the rect tool while the author looked straight at
49
+ // the surface it draws on. Inside a definition every tool's pane is
50
+ // (`toolLayoutMode`).
51
+ const layoutMode = useShotEditor(toolLayoutMode);
52
+ const readOnly = useShotEditor((s) => s.readOnly && readOnlyReason(s));
53
+ const leafIsolation = useShotEditor(isolationInsertRefusal);
54
+ // The footer is shown at every level of the stage (`toolFooterShown`), and
55
+ // this bar is the narrower answer inside it: the footer's row AND a shot or
56
+ // a definition. It reads both rather than trusting ToolFooter's branch, so
57
+ // the bar is correct wherever it is rendered (the tests render it bare).
58
+ const shown = useShotEditor((s) => toolFooterShown(s) && !isSceneContext(s));
59
+ const commenting = useShotEditor((s) => s.boardTool === "comment");
60
+ const canComment = useShotEditor((s) => s.canComment);
35
61
  const [flyoutOpen, setFlyoutOpen] = useState(false);
36
- if (!open) return null;
62
+ const shapesAnchor = useRef<HTMLDivElement>(null);
63
+ useDismiss(flyoutOpen, shapesAnchor, () => setFlyoutOpen(false));
64
+ // ── THE WHOLE DOCK IS ABSENT OUTSIDE A SHOT, AND IN THE MODES ────────────
65
+ //
66
+ // Every control it holds is an insert, and `insertContext` refuses every
67
+ // insert in the Project context, so this used to render ten buttons of which
68
+ // ten were dead there: seven tools, the shape flyout, Add, and an Animate
69
+ // toggle whose only subject is a layer that cannot be selected without
70
+ // opening a shot first. The modes own the canvas cell outright, so a dock
71
+ // under it would address a picture that is not on screen. In the modes the
72
+ // footer's row closes (`toolFooterShown`). In the Project context the row
73
+ // stays, for the view track on its right, and ToolFooter puts the shot under
74
+ // the playhead where these tools would be.
75
+ //
76
+ // That reverses the disabled-not-absent argument below FOR THESE CASES, and
77
+ // deliberately. The argument is right about the LAYOUT refusal, where the
78
+ // author is one digit away and the tool is genuinely a capability they are
79
+ // about to use again. The context refusal is a different shape: the Project
80
+ // context has its own complete verb set (add, reorder, trim, split, delete,
81
+ // open) in the scene strip, and a dock of greyed-out element tools over it
82
+ // teaches a map of the editor that is wrong at this level. Nobody is stranded:
83
+ // the shots list and the strip both carry a `›` that opens one, and the keys
84
+ // still answer with `toolRefusal`'s sentence, which is now the only place that
85
+ // sentence is needed.
86
+ if (!shown) return null;
87
+
88
+ /**
89
+ * DISABLED, NOT ABSENT — which is the one place this bar parts company with
90
+ * the bars in the top strip. ShadingBar and CameraViewBar unmount in the
91
+ * layout that has no working pane, because what they address is a pane that
92
+ * is not on screen. A creating tool is not addressed to a pane, it is a
93
+ * CAPABILITY of the editor, and an author who cannot find the rect tool
94
+ * concludes the shot lane has none — so the button stays, and its title is
95
+ * where the layout answer is taught. `toolRefusal` writes that sentence, and
96
+ * the keystroke's refusal says exactly the same thing.
97
+ *
98
+ * SPLIT KEEPS THEM ENABLED: the camera pane is the right half, and every
99
+ * insert lane it feeds works there.
100
+ *
101
+ * ISOLATED IN A LEAF DISABLES THEM TOO: an insert joins the isolated layer,
102
+ * and a rectangle cannot hold one (`isolationInsertRefusal`).
103
+ *
104
+ * The Project context never reaches this function: the whole bar is
105
+ * unmounted there, for the reason argued at the early return above.
106
+ */
107
+ const refusalFor = (t: ShotTool): string | null => toolRefusal(t, layoutMode, false, readOnly, leafIsolation);
108
+
109
+ const toolButton = (t: DockTool) => (
110
+ <ToolButton
111
+ key={t}
112
+ armed={tool === t}
113
+ refusal={refusalFor(t)}
114
+ title={toolTitle(t)}
115
+ icon={DOCK_TOOL_ICONS[t]}
116
+ onClick={() => store.setTool(t)}
117
+ />
118
+ );
119
+
120
+ const shapes = (
121
+ <div className="tool-flyout-anchor" ref={shapesAnchor}>
122
+ {/* ONE FIXED GLYPH, the Shapes mark, whatever is armed. The trigger used
123
+ to wear the armed or last-used shape, which made it look like a fourth
124
+ kind of tool; it is a MENU of three, and the wash is what says one of
125
+ the three is in play. The flyout's rows wear the shapes themselves.
37
126
 
38
- const label = (t: ShotTool): string => {
39
- const spec = specOf(t);
40
- const key = spec.key === null ? "" : ` (${spec.shift === true ? "⇧" : ""}${spec.key.toUpperCase()})`;
41
- return `${spec.label}${key}`;
42
- };
127
+ THE TITLE STAYS "Shapes", and must never become the armed tool's
128
+ label: the dock's tests find each tool's button by a title that
129
+ STARTS WITH its label, so "Ellipse…" here would make one button
130
+ answer for two tools and the flyout's own row would go unchecked.
131
+
132
+ Armed from `state.tool`, not from the row's click, because the row is
133
+ not the only way in: `O`, `L` and `⇧L` arm these three too. */}
134
+ <button
135
+ type="button"
136
+ className={`tool-button has-caret${FLYOUT_TOOLS.includes(tool as FlyoutTool) ? " is-armed" : ""}`}
137
+ aria-haspopup="menu"
138
+ aria-expanded={flyoutOpen}
139
+ // The glyph is fixed, so the NAME carries which shape is armed: a
140
+ // screen reader hears "Shapes: Ellipse" where a sighted author sees the
141
+ // wash. The title stays "Shapes" (see above).
142
+ aria-label={FLYOUT_TOOLS.includes(tool as FlyoutTool) ? `Shapes: ${specOf(tool).label}` : "Shapes"}
143
+ title="Shapes"
144
+ onClick={() => setFlyoutOpen((v) => !v)}
145
+ >
146
+ <span className="tool-icon" aria-hidden="true">
147
+ {SHAPES_ICON}
148
+ </span>
149
+ <span className="tool-caret" aria-hidden />
150
+ </button>
151
+ {flyoutOpen ? (
152
+ <div className="tool-flyout" role="menu">
153
+ {FLYOUT_TOOLS.map((t) => (
154
+ <ToolFlyoutRow
155
+ key={t}
156
+ // A RADIO row: one of the three is armed at a time, and the
157
+ // armed one says so to a screen reader, not only by its wash.
158
+ checked={tool === t}
159
+ refusal={refusalFor(t)}
160
+ title={toolTitle(t)}
161
+ icon={FLYOUT_TOOL_ICONS[t]}
162
+ label={specOf(t).label}
163
+ onClick={() => {
164
+ store.setTool(t);
165
+ setFlyoutOpen(false);
166
+ }}
167
+ />
168
+ ))}
169
+ </div>
170
+ ) : null}
171
+ </div>
172
+ );
43
173
 
44
174
  return (
45
175
  <div className="tool-dock" role="toolbar" aria-label="Tools" data-testid="tool-dock">
46
- {DOCK_TOOLS.map((t) => (
47
- <button
48
- key={t}
49
- type="button"
50
- className={`tool-button${tool === t ? " is-armed" : ""}`}
51
- aria-pressed={tool === t}
52
- title={label(t)}
53
- onClick={() => store.setTool(t)}
54
- >
55
- {DOCK_GLYPHS[t]}
56
- </button>
176
+ {DOCK_GROUPS.map(({ label: group, tools }) => (
177
+ <Fragment key={group}>
178
+ <div className="tool-group" role="group" aria-label={group}>
179
+ {tools.map((t) =>
180
+ // THE SHAPES TRIGGER LIVES INSIDE THE DRAW GROUP, beside the
181
+ // rect it flies out of — it is one of the six that make
182
+ // something, and putting it after the group's rule would say it
183
+ // is a fourth kind of control. It rides `rect` rather than being
184
+ // appended to the group, because "beside rect" is the placement
185
+ // and the group's order is the table's.
186
+ t === "rect" ? (
187
+ <Fragment key={t}>
188
+ {toolButton(t)}
189
+ {shapes}
190
+ </Fragment>
191
+ ) : (
192
+ toolButton(t)
193
+ ),
194
+ )}
195
+ </div>
196
+ </Fragment>
57
197
  ))}
58
198
 
59
- <div className="tool-flyout-anchor">
60
- <button
61
- type="button"
62
- className={`tool-button${FLYOUT_TOOLS.includes(tool as FlyoutTool) ? " is-armed" : ""}`}
63
- aria-haspopup="menu"
64
- aria-expanded={flyoutOpen}
65
- title="Shapes"
66
- onClick={() => setFlyoutOpen((v) => !v)}
67
- >
68
- ◇
69
- </button>
70
- {flyoutOpen ? (
71
- <div className="tool-flyout" role="menu">
72
- {FLYOUT_TOOLS.map((t) => (
73
- <button
74
- key={t}
75
- type="button"
76
- role="menuitem"
77
- className={`tool-button${tool === t ? " is-armed" : ""}`}
78
- title={label(t)}
79
- onClick={() => {
80
- store.setTool(t);
81
- setFlyoutOpen(false);
82
- }}
83
- >
84
- {FLYOUT_GLYPHS[t]} {specOf(t).label}
85
- </button>
86
- ))}
87
- </div>
88
- ) : null}
89
- </div>
199
+ {/* NOT a tool either: it opens the Insert drawer, the kinds a DRAG
200
+ CANNOT EXPRESS (a file, a registry choice, an operation on the
201
+ selection) beside icons, shaders and backgrounds. See InsertButton.
202
+ It rides the run unruled, because it is still something the author
203
+ aims at and presses, and its wash says the drawer is open. */}
204
+ <InsertButton />
205
+ {/* THE COMMENT TOOL (S5), the board's own: armed, the next click on
206
+ the camera pane leaves a Studio comment at the playhead. A Viewer
207
+ who may comment keeps it while the tools above are read-only. */}
208
+ <ToolButton
209
+ armed={commenting}
210
+ refusal={canComment ? null : NO_COMMENT}
211
+ title="Comment on the picture at the playhead (C)"
212
+ icon={COMMENT_ICON}
213
+ onClick={() => store.setBoardTool(commenting ? null : "comment")}
214
+ />
90
215
 
91
- {/* NOT a tool either, and beside the shapes for the same reason: it is
92
- the kinds a DRAG CANNOT EXPRESS — a file, a registry choice, an
93
- operation on the selection. See AddMenu. */}
94
- <AddMenu />
216
+ {/* NOT a tool, and the row's one rule says so. Animate changes what an
217
+ edit MEANS while any tool is held, see tools.ts. */}
218
+ <span className="tool-separator" aria-hidden />
219
+ {/* IT GETS A WORD, and it is the only control here that has one. Until
220
+ now it was a bare `⏺` at the end of seven tool icons — a glyph in the
221
+ transport vocabulary sitting in a drawing dock, with the tooltip as
222
+ the only thing that said "Animate". The tools stay iconic because
223
+ they are a mutually-exclusive set an author learns once and then aims
224
+ at, and Add stays iconic because its caret says what
225
+ it is; a MODE is neither — it is either on or off, it changes what
226
+ every one of those tools MEANS while it is on, and a state has to say
227
+ at rest that it is on.
95
228
 
96
- {/* NOT a tool, and the separator says so. Animate changes what an edit
97
- MEANS while any tool is held — see tools.ts. */}
98
- <span className="tool-separator" aria-hidden="true" />
229
+ ── AND THE WORD IS AFFORDABLE ─────────────────────────────────────
230
+
231
+ Measured off the stylesheet rather than eyeballed. The footer's
232
+ buttons are the house 28px control (`.tool-footer .tool-button`), the
233
+ gaps are --space-2 (4px). Six direct children of `.tool-dock`:
234
+
235
+ Pick group 2 × 28 + 1 gap × 4 = 60
236
+ Draw group 6 × 28 (5 tools + the shapes trigger)
237
+ + 5 gaps × 4 = 188
238
+ Add one 28px trigger = 28
239
+ Comment one 28px tool = 28
240
+ separator 1 + margin 1 + 1 = 3
241
+ Animate 8 + 14 icon + 6 gap + ~47 label + 8 = 83
242
+ ──────────────────────────────────────────────────────────────
243
+ children 390, + 5 dock gaps × 4 = 410
244
+
245
+ The dock has no padding or border of its own: the footer owns both,
246
+ 16px a side, so the row alone is about 442. It shares the footer
247
+ with the view track on the right, and the TRACK is what gives way
248
+ when the two do not fit: it goes compact and then folds to one
249
+ picker (tool-footer.css has the measured thresholds), and these
250
+ tools never fold. dockedShellE2e measures the footer at three
251
+ widths, so a change to this row that breaks the fit fails there.
252
+
253
+ So the "is the word worth its width" argument is settled by having
254
+ the space, and the word was the right answer anyway, because a mode
255
+ has to say at rest that it is on. */}
99
256
  <button
100
257
  type="button"
101
- className={`tool-button tool-animate${animateMode ? " is-armed" : ""}`}
258
+ className={`tool-button tool-animate is-labelled${animateMode ? " is-armed" : ""}`}
102
259
  aria-pressed={animateMode}
103
- title="Animate (⇧A) — a canvas edit becomes a block ending at the playhead"
260
+ // The accessible name is the word alone, not the word plus the glyph.
261
+ aria-label="Animate"
262
+ title="Animate (⇧A): edits write keyframes at the playhead"
104
263
  onClick={() => store.setAnimateMode(!animateMode)}
105
264
  >
106
- ⏺
265
+ <span className="tool-icon" aria-hidden="true">
266
+ {RECORD_ICON}
267
+ </span>
268
+ <span className="tool-label">Animate</span>
107
269
  </button>
108
270
  </div>
109
271
  );
110
272
  }
273
+
274
+ /** A tool button's title: its label, then its key ("Rect (R)"). */
275
+ export function toolTitle(t: ShotTool): string {
276
+ const key = toolKey(t);
277
+ return `${specOf(t).label}${key === null ? "" : ` (${key})`}`;
278
+ }
279
+
280
+ /**
281
+ * ONE TOOL BUTTON, shared by this dock and the Insert button
282
+ * (InsertButton.tsx), so both draw the same armed wash and disable the same way.
283
+ * `refusal` is the sentence a disabled button says in its title, and null
284
+ * means the button works and its title is `title`.
285
+ *
286
+ * `label` is a labelled button's (no caller passes one since the board's
287
+ * tool rail went): the button's name, kept
288
+ * whatever the title says, and drawn under the glyph as `caption` (the label
289
+ * itself unless a shorter word is given). The Studio dock passes neither.
290
+ */
291
+ export function ToolButton(props: {
292
+ armed: boolean;
293
+ refusal: string | null;
294
+ title: string;
295
+ icon: ReactNode;
296
+ label?: string;
297
+ caption?: string;
298
+ onClick: () => void;
299
+ }) {
300
+ return (
301
+ <button
302
+ type="button"
303
+ className={`tool-button${props.armed ? " is-armed" : ""}`}
304
+ aria-pressed={props.armed}
305
+ aria-label={props.label}
306
+ disabled={props.refusal !== null}
307
+ title={props.refusal ?? props.title}
308
+ onClick={props.onClick}
309
+ >
310
+ <span className="tool-icon" aria-hidden="true">
311
+ {props.icon}
312
+ </span>
313
+ {props.label === undefined ? null : (
314
+ <span className="tool-caption" aria-hidden="true">
315
+ {props.caption ?? props.label}
316
+ </span>
317
+ )}
318
+ </button>
319
+ );
320
+ }
321
+
322
+ /**
323
+ * ONE ROW OF A TOOL FLYOUT. With `checked` it is a radio row (one tool of the
324
+ * set is armed); without it a plain menu item (an insert that arms nothing).
325
+ * `chip` is the key that arms the tool, which only the board rail shows: the
326
+ * Studio footer stays as it is.
327
+ */
328
+ export function ToolFlyoutRow(props: {
329
+ checked?: boolean;
330
+ refusal: string | null;
331
+ title: string;
332
+ icon: ReactNode;
333
+ label: string;
334
+ chip?: string;
335
+ onClick: () => void;
336
+ }) {
337
+ const radio = props.checked !== undefined;
338
+ return (
339
+ <button
340
+ type="button"
341
+ role={radio ? "menuitemradio" : "menuitem"}
342
+ {...(radio ? { "aria-checked": props.checked } : {})}
343
+ className={`tool-button${props.checked === true ? " is-armed" : ""}`}
344
+ disabled={props.refusal !== null}
345
+ title={props.refusal ?? props.title}
346
+ onClick={props.onClick}
347
+ >
348
+ <span className="tool-icon" aria-hidden="true">
349
+ {props.icon}
350
+ </span>{" "}
351
+ {props.label}
352
+ {props.chip === undefined ? null : (
353
+ <kbd className="tool-key-chip" aria-hidden="true">
354
+ {props.chip}
355
+ </kbd>
356
+ )}
357
+ </button>
358
+ );
359
+ }
@@ -0,0 +1,102 @@
1
+ // THE TOOL FOOTER'S ROW: what is on the left, then the view track on the right.
2
+ //
3
+ // App mounts this inside the `.tool-footer` PanelBoundary wherever the footer
4
+ // is shown (`toolFooterShown`): every level of the stage workspace, and never
5
+ // in a mode. The two halves are pushed apart (`justify-content:
6
+ // space-between`, tool-footer.css), so the track sits at the right edge in
7
+ // every context and the left half changes under it.
8
+ //
9
+ // ── THE LEFT HALF IS THE TOOLS, OR THE SHOT THEY WOULD DRAW INTO ───────────
10
+ //
11
+ // Inside a shot or a definition the left half is ToolDockBar. In the Project
12
+ // context every tool there would be dead (ToolDockBar has the argument), so the
13
+ // left half names the shot under the playhead instead: what the picture above
14
+ // is showing, at the level where the picture is a sequence of shots. With no
15
+ // shot under the playhead it shows nothing, rather than a placeholder that
16
+ // would describe a picture that is not there.
17
+ //
18
+ // ── THE RIGHT HALF IS THE VIEW TRACK ───────────────────────────────────────
19
+ //
20
+ // ViewModeBar, which applies at every level and gates itself (it is absent
21
+ // inside a definition, which has no shot camera). It gives way before the
22
+ // tools do: it compacts, then folds to one picker, on a container query on
23
+ // this row (tool-footer.css has the measured widths). The tools never fold.
24
+ import { FPS_OPTIONS } from "@dailies-studio/timeline";
25
+ import { framesToSeconds } from "@dailies-studio/engine";
26
+ import type { Rational } from "@dailies-studio/engine";
27
+ import { masterToSceneCached } from "@dailies-studio/shot/doc";
28
+ import { useShotEditor, useShotEditorHot } from "../../state/context.js";
29
+ import type { ShotEditorState } from "../../state/types.js";
30
+ import { isSceneContext } from "../../state/viewMode.js";
31
+ import { formatDuration } from "../timeline/model.js";
32
+ import { ToolDockBar } from "./ToolDockBar.js";
33
+ import { ViewModeBar } from "./ViewModeBar.js";
34
+
35
+ /** The rate as the Project's own picker words it: an `FPS_OPTIONS` label when
36
+ * the rational is one of them (so 30000/1001 reads "29.97", never "29.970"),
37
+ * else three decimals with the trailing zeros dropped. */
38
+ export function fpsLabel(fps: Rational): string {
39
+ const known = FPS_OPTIONS.find((o) => o.fps.num * fps.den === fps.num * o.fps.den);
40
+ return known?.label ?? String(Number((fps.num / fps.den).toFixed(3)));
41
+ }
42
+
43
+ /**
44
+ * The shot under the playhead, as two strings, or null with none.
45
+ *
46
+ * STRINGS, NEVER AN OBJECT: the footer survives playback, and `useShotEditor`
47
+ * compares with `Object.is`, so each line is resolved inside its own selector
48
+ * and only a change of words re-renders the row.
49
+ *
50
+ * THE PLAYHEAD'S SHOT, NEVER THE PIN. `state.sceneId` is a pin that a bare
51
+ * `setScene` moves without seeking, and a zero-length shot hands the frame to
52
+ * its neighbour while the pin stays on it, so it can name a shot the stage is
53
+ * not drawing. The footer describes the picture above it, so it resolves the
54
+ * frame first, as `insertContext` and the bridge do, and falls back to the pin
55
+ * only where the frame lands in no shot.
56
+ *
57
+ * The NUMBER is the shot's place in `doc.scenes`, the order the left column's
58
+ * Shots list and the timeline draw. The DURATION is `formatDuration`, the
59
+ * same string the left column's shot rows print, so one length is never spelled
60
+ * two ways on one screen.
61
+ */
62
+ export function shotMeta(
63
+ s: Pick<ShotEditorState, "doc" | "sceneId" | "frame">,
64
+ ): { name: string; line: string } | null {
65
+ const { doc } = s;
66
+ if (doc === null) return null;
67
+ const sceneId = masterToSceneCached(doc, s.frame)?.sceneId ?? s.sceneId;
68
+ if (sceneId === null) return null;
69
+ const scene = doc.scenesById[sceneId];
70
+ const index = doc.scenes.indexOf(sceneId);
71
+ if (scene === undefined || index === -1) return null;
72
+ const number = String(index + 1).padStart(2, "0");
73
+ return {
74
+ name: scene.name,
75
+ line: `Shot ${number} · ${formatDuration(framesToSeconds(scene.durationFrames, doc.fps))} · ${fpsLabel(doc.fps)} fps`,
76
+ };
77
+ }
78
+
79
+ function ShotMeta() {
80
+ // Hot: the Shot under the playhead moves at every cut the transport plays,
81
+ // which the cold lane sleeps through (store `set`). Strings, so the row
82
+ // renders at a cut and not per frame.
83
+ const name = useShotEditorHot((s) => shotMeta(s)?.name ?? null);
84
+ const line = useShotEditorHot((s) => shotMeta(s)?.line ?? null);
85
+ if (name === null || line === null) return null;
86
+ return (
87
+ <div className="shot-meta" data-testid="shot-meta">
88
+ <span className="shot-meta-name">{name}</span>
89
+ <span className="shot-meta-line">{line}</span>
90
+ </div>
91
+ );
92
+ }
93
+
94
+ export function ToolFooter() {
95
+ const project = useShotEditor(isSceneContext);
96
+ return (
97
+ <>
98
+ {project ? <ShotMeta /> : <ToolDockBar />}
99
+ <ViewModeBar />
100
+ </>
101
+ );
102
+ }