@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,234 @@
1
+ // WHICH PIXELS OF THE DOCK ARE ON SCREEN RIGHT NOW.
2
+ //
3
+ // A dock-local store, read by the filmstrip canvases and by nothing else. It
4
+ // exists for one reason: a video row's bar can be a hundred thousand pixels
5
+ // wide at a deep zoom, and a strip that drew and fetched all of it would spend
6
+ // a scrub's worth of work on pixels nobody is looking at, and would ask for a
7
+ // canvas past the width a browser will allocate.
8
+ //
9
+ // ── WHY THIS IS NOT THE SHOT STORE ────────────────────────────────────────
10
+ //
11
+ // Nothing here is document state, nothing here survives a reload, and nothing
12
+ // outside this lane may read it. Putting a scroll position in the shot store
13
+ // would put a value that changes at scroll rate on a channel every panel in the
14
+ // chrome subscribes to. The hot channel is worse still: `useShotEditorHot` is
15
+ // the FRAME, its contract is "the smallest leaf that must repaint every tick",
16
+ // and a scroll position is neither the frame nor on the frame's schedule.
17
+ // See `useLive.ts`'s header for the rule this one obeys.
18
+ //
19
+ // ── THE QUANTUM IS THE POINT ──────────────────────────────────────────────
20
+ //
21
+ // `scrollLeft` moves in single pixels, so an unquantised store would notify
22
+ // every subscriber on every pixel of a pan and a filmstrip would redraw sixty
23
+ // times a second while a hand is on the trackpad. Rounded out to a 64 px grid,
24
+ // a subscriber hears about a scroll once every 64 px and the strip it draws is
25
+ // stable in between. The range is rounded OUTWARD (floor the left, ceil the
26
+ // right), so the quantised range always CONTAINS the real one: a coarser grid
27
+ // can cost an extra column of work, never a missing one.
28
+ //
29
+ // ── AND THE MARGIN IS WHY A QUANTUM IS ENOUGH ─────────────────────────────
30
+ //
31
+ // Work is admitted for the visible range plus `DOCK_VIEWPORT_MARGIN_PX` on each
32
+ // side, which is eight quanta. So a bar that scrolls into view was already
33
+ // drawn before it arrived, and the strip is never caught blank at the leading
34
+ // edge of a pan.
35
+
36
+ /** The grid the visible range is rounded onto, in content pixels. */
37
+ export const DOCK_VIEWPORT_QUANTUM_PX = 64;
38
+
39
+ /** How far outside the visible range a bar still draws and still fetches. */
40
+ export const DOCK_VIEWPORT_MARGIN_PX = 512;
41
+
42
+ /** The scroller this lane measures. One selector, spelled once, because a
43
+ * filmstrip finds its own scroller by walking up from its canvas rather than
44
+ * being handed one: the dock's row tree is owned by other components and this
45
+ * lane must not make them thread a ref through it. */
46
+ export const DOCK_SCROLLER_SELECTOR = ".dock-scroll";
47
+
48
+ /**
49
+ * The visible slice of the dock's scrolled content, in CONTENT pixels.
50
+ *
51
+ * Content pixels and not client pixels: content x 0 is the left edge of the
52
+ * scrolled content, which is where the sticky label column sits, so a bar's
53
+ * content x is `labelWidth + itsLaneLeft` and needs no layout read to know.
54
+ */
55
+ export interface DockViewport {
56
+ left: number;
57
+ right: number;
58
+ }
59
+
60
+ /**
61
+ * Nothing has been measured.
62
+ *
63
+ * An empty range rather than a zero-width one at the origin, and the
64
+ * difference matters: `visibleSlice` reads `right <= left` as "no answer" and
65
+ * admits the bar, because a strip that drew nothing until a scroll event
66
+ * arrived would be blank on every first paint and permanently blank in any
67
+ * environment with no layout at all (jsdom measures every element at zero).
68
+ * A viewport that cannot be measured must not be able to hide a row.
69
+ */
70
+ export const DOCK_VIEWPORT_UNMEASURED: DockViewport = Object.freeze({ left: 0, right: 0 });
71
+
72
+ /**
73
+ * How much of a bar an UNMEASURED viewport admits, in bar-local pixels.
74
+ *
75
+ * ponytail: a flat ceiling rather than a second measurement path. It exists so
76
+ * that "we do not know what is visible" cannot mean "allocate a canvas as wide
77
+ * as the bar", which at 60 px/frame over a fifty-second film is six figures of
78
+ * pixels and no browser's problem to solve. Roughly a laptop dock's width, so
79
+ * a real browser that has not scrolled yet still draws a full screen of strip.
80
+ */
81
+ export const DOCK_VIEWPORT_UNMEASURED_PX = 2048;
82
+
83
+ interface Entry {
84
+ /** Replaced only when the QUANTISED range moves, because `useSyncExternalStore`
85
+ * compares snapshots by identity and a fresh object per read is an infinite
86
+ * render loop. */
87
+ view: DockViewport;
88
+ listeners: Set<() => void>;
89
+ /** The one coalescing slot. Non-null means a measure is already booked for
90
+ * the next frame, so a burst of scroll events costs one measurement. */
91
+ raf: number | null;
92
+ detach: () => void;
93
+ }
94
+
95
+ const entries = new WeakMap<HTMLElement, Entry>();
96
+
97
+ function quantise(el: HTMLElement): DockViewport {
98
+ const q = DOCK_VIEWPORT_QUANTUM_PX;
99
+ const from = el.scrollLeft;
100
+ const to = from + el.clientWidth;
101
+ // A scroller with no width has not been laid out. Reporting `{0, 0}` here is
102
+ // what routes it to the unmeasured branch rather than to "nothing is visible".
103
+ if (!(to > from) || !Number.isFinite(from) || !Number.isFinite(to)) return DOCK_VIEWPORT_UNMEASURED;
104
+ return { left: Math.floor(from / q) * q, right: Math.ceil(to / q) * q };
105
+ }
106
+
107
+ function measure(el: HTMLElement, entry: Entry): void {
108
+ const next = quantise(el);
109
+ if (next.left === entry.view.left && next.right === entry.view.right) return;
110
+ entry.view = next;
111
+ for (const fn of [...entry.listeners]) {
112
+ try {
113
+ fn();
114
+ } catch {
115
+ // One subscriber throwing must not stop the others hearing about it.
116
+ // Same posture as the filmstrip loader's `notify`: this lane is
117
+ // decoration and may not fail an edit.
118
+ }
119
+ }
120
+ }
121
+
122
+ function schedule(el: HTMLElement, entry: Entry): void {
123
+ if (entry.raf !== null) return;
124
+ if (typeof requestAnimationFrame !== "function") {
125
+ measure(el, entry);
126
+ return;
127
+ }
128
+ entry.raf = requestAnimationFrame(() => {
129
+ entry.raf = null;
130
+ measure(el, entry);
131
+ });
132
+ }
133
+
134
+ function entryFor(el: HTMLElement): Entry {
135
+ const existing = entries.get(el);
136
+ if (existing !== undefined) return existing;
137
+ const entry: Entry = {
138
+ view: DOCK_VIEWPORT_UNMEASURED,
139
+ listeners: new Set(),
140
+ raf: null,
141
+ detach: () => {},
142
+ };
143
+ const onChange = (): void => schedule(el, entry);
144
+ // PASSIVE: this listener never calls `preventDefault`, and saying so keeps it
145
+ // off the critical path of the dock's own wheel handling, which is
146
+ // deliberately non-passive (see TimelineDock's shift-pan).
147
+ el.addEventListener("scroll", onChange, { passive: true });
148
+ // A resize changes the visible range without a scroll event: collapsing the
149
+ // inspector widens the dock and uncovers bars nothing scrolled to.
150
+ const ro = typeof ResizeObserver === "function" ? new ResizeObserver(onChange) : null;
151
+ ro?.observe(el);
152
+ entry.detach = () => {
153
+ el.removeEventListener("scroll", onChange);
154
+ ro?.disconnect();
155
+ if (entry.raf !== null && typeof cancelAnimationFrame === "function") cancelAnimationFrame(entry.raf);
156
+ entry.raf = null;
157
+ entries.delete(el);
158
+ };
159
+ entries.set(el, entry);
160
+ // Measured once up front, so the first subscriber does not have to wait for a
161
+ // scroll that may never come.
162
+ entry.view = quantise(el);
163
+ return entry;
164
+ }
165
+
166
+ /** The scroller a dock element lives in, or null when it is not in one. */
167
+ export function dockScrollerOf(el: Element | null): HTMLElement | null {
168
+ if (el === null) return null;
169
+ try {
170
+ return el.closest<HTMLElement>(DOCK_SCROLLER_SELECTOR);
171
+ } catch {
172
+ return null;
173
+ }
174
+ }
175
+
176
+ /**
177
+ * The current snapshot. Stable by identity between changes.
178
+ *
179
+ * A PURE READ: it never creates an entry and never attaches a listener, because
180
+ * `useSyncExternalStore` calls its getter during render and a getter with a
181
+ * side effect is a listener attached from a render React may throw away.
182
+ * Subscribing is what starts measuring, and React re-reads immediately after
183
+ * subscribing, so the first measurement is never missed.
184
+ */
185
+ export function readDockViewport(el: HTMLElement | null): DockViewport {
186
+ if (el === null) return DOCK_VIEWPORT_UNMEASURED;
187
+ return entries.get(el)?.view ?? DOCK_VIEWPORT_UNMEASURED;
188
+ }
189
+
190
+ /** Subscribe to a scroller's quantised range. Returns the unsubscribe, which
191
+ * tears the listeners down with the last subscriber: this store holds a DOM
192
+ * node, so an entry that outlived its dock would pin a detached tree. */
193
+ export function subscribeDockViewport(el: HTMLElement | null, onChange: () => void): () => void {
194
+ if (el === null) return () => {};
195
+ const entry = entryFor(el);
196
+ entry.listeners.add(onChange);
197
+ return () => {
198
+ entry.listeners.delete(onChange);
199
+ if (entry.listeners.size === 0) entry.detach();
200
+ };
201
+ }
202
+
203
+ /** Test hook, and the one way to drop an entry without a subscriber churn. */
204
+ export function resetDockViewport(el: HTMLElement | null): void {
205
+ if (el === null) return;
206
+ entries.get(el)?.detach();
207
+ }
208
+
209
+ /**
210
+ * The slice of a bar worth drawing, in BAR-LOCAL pixels, or null when the bar
211
+ * is far enough off screen that neither its pixels nor its tiles are worth
212
+ * asking for.
213
+ *
214
+ * ONE function for the whole rule, so "is this row visible" and "which part of
215
+ * it do I draw" cannot answer differently: the skip is exactly the case where
216
+ * the intersection is empty, rather than a second threshold beside it.
217
+ *
218
+ * @param barContentX The bar's left edge in CONTENT pixels (`labelWidth`
219
+ * plus its lane-local left), which the caller already
220
+ * computed to place the bar and must not recompute here.
221
+ */
222
+ export function visibleSlice(
223
+ view: DockViewport,
224
+ barContentX: number,
225
+ barWidthPx: number,
226
+ ): { fromPx: number; toPx: number } | null {
227
+ if (!(barWidthPx > 0) || !Number.isFinite(barContentX)) return null;
228
+ if (!(view.right > view.left)) {
229
+ return { fromPx: 0, toPx: Math.min(barWidthPx, DOCK_VIEWPORT_UNMEASURED_PX) };
230
+ }
231
+ const fromPx = Math.max(0, view.left - DOCK_VIEWPORT_MARGIN_PX - barContentX);
232
+ const toPx = Math.min(barWidthPx, view.right + DOCK_VIEWPORT_MARGIN_PX - barContentX);
233
+ return toPx > fromPx ? { fromPx, toPx } : null;
234
+ }
@@ -0,0 +1,116 @@
1
+ // The way out of whatever the author is inside, over the canvas they are
2
+ // looking at.
3
+ //
4
+ // ── WHY ANOTHER ONE ─────────────────────────────────────────────────────
5
+ //
6
+ // The rail's back row and the header's shot crumb also climb a level, but the
7
+ // rail collapses under `[` and neither is over the picture. The author's eyes
8
+ // and pointer are on the canvas; the exit belongs there too. The timeline's
9
+ // corner names the place and is not a way out (TimelineDock.tsx).
10
+ //
11
+ // FIRST IN THE CANVAS BAR (CanvasBar.tsx), which is where a back control goes
12
+ // in every editor that has one, and it reads left to right with the rest of
13
+ // the bar: where am I, which pane, how that pane is drawn.
14
+ //
15
+ // ── ABSENT INSIDE AN ISOLATION ──────────────────────────────────────────
16
+ //
17
+ // There the canvas bar shows the isolation trail instead (IsolationTrail.tsx),
18
+ // which names every level and climbs to any of them. `backTarget` below still
19
+ // answers one level up for the rail's back row, which is the Escape ladder's
20
+ // rule (keymap.ts).
21
+ //
22
+ // ── AND IT NAMES NO KEY, BECAUSE ESCAPE IS NOT THIS CLICK ───────────────
23
+ //
24
+ // Never inside a component: an open definition is the Components workspace
25
+ // (`workspaceOf`), so the stage's bar (`StageBar` in CanvasBar.tsx) is not
26
+ // rendered there and Components brings its own way back.
27
+ //
28
+ // The shot title carries no hint, and that is a judgement rather than an
29
+ // oversight. A single Escape from an entered shot DOES run `leaveScene`
30
+ // — but only after the ladder's earlier rungs decline it, so with anything
31
+ // selected, the first press deselects and the shot stays open. This click
32
+ // never deselects. A hint that holds only when the canvas is empty is the same
33
+ // defect one sentence weaker.
34
+ //
35
+ // ABSENT IN THE PROJECT CONTEXT, not disabled. Same call the tool dock makes,
36
+ // for the same reason: at the top of the tree a back control points at nothing,
37
+ // and a permanently dead button is worse than no button.
38
+ import { parentOf } from "@dailies-studio/shot/doc";
39
+ import { CHEVRON_LEFT_ICON } from "../../icons.js";
40
+ import { useShotEditor, useStore } from "../../state/context.js";
41
+ import type { ShotEditorStore } from "../../state/store.js";
42
+ import type { ShotEditorState } from "../../state/types.js";
43
+ import { isSceneContext } from "../../state/viewMode.js";
44
+
45
+ /**
46
+ * ONE LEVEL UP, FROM WHEREVER THE AUTHOR IS: the destination's name and the
47
+ * verb that goes there. This chip and the rail's back row (FilePanel.tsx) both
48
+ * read it, so the two controls cannot disagree about where "back" is.
49
+ *
50
+ * `label` is null when the destination is the Project. Inside a definition
51
+ * (only the rail's back row reads that branch) it is the Components home.
52
+ * Isolation climbs with
53
+ * `exitIsolation`, the store's own one-level verb and the one Escape runs, so
54
+ * the parent is read the way that verb reads it: from `sceneId`'s scene.
55
+ */
56
+ export function backTarget(s: ShotEditorState): {
57
+ label: string | null;
58
+ run: (store: ShotEditorStore) => void;
59
+ } | null {
60
+ if (s.doc === null) return null;
61
+ if (s.editingDef !== null) {
62
+ return { label: "All components", run: (store) => void store.openComponents() };
63
+ }
64
+ const scene = s.sceneId === null ? undefined : s.doc.scenesById[s.sceneId];
65
+ if (s.isolation !== null && scene?.layersById[s.isolation] !== undefined) {
66
+ const parent = parentOf(scene, s.isolation);
67
+ return {
68
+ label:
69
+ parent === null
70
+ ? scene.name
71
+ : (scene.layersById[parent]?.label ?? parent),
72
+ run: (store) => store.exitIsolation(),
73
+ };
74
+ }
75
+ if (s.enteredScene === null) return null;
76
+ // `leaveScene` already clears the isolation, the selection and the armed
77
+ // tool (see twoContexts.test.ts), so there is no exit chain to repeat here.
78
+ return { label: null, run: (store) => store.leaveScene() };
79
+ }
80
+
81
+ export function BackChip() {
82
+ const store = useStore();
83
+ const open = useShotEditor((s) => s.name !== null);
84
+ // The project context, which is where this chip has nothing to point at.
85
+ // `isSceneContext` also asks `doc !== null`, which costs nothing here: the
86
+ // store writes `name` and `doc` together, so `open` above already said it.
87
+ const inScene = useShotEditor(isSceneContext);
88
+ const isolated = useShotEditor(
89
+ (s) => s.editingDef === null && s.isolation !== null,
90
+ );
91
+ // INSIDE AN ISOLATION THE TRAIL IS THE WAY OUT (IsolationTrail.tsx): it
92
+ // names every level and climbs to any of them, so a one-level chip beside
93
+ // it would be a second, shorter copy.
94
+ if (!open || inScene || isolated) return null;
95
+
96
+ const label = "Shots";
97
+ const title = "Back to all shots, to arrange the project";
98
+
99
+ // A plain 28px control in the docked canvas bar. It used to sit in an island
100
+ // wrapper of its own (`.back-bar`), which went with the floating panels.
101
+ return (
102
+ <button
103
+ type="button"
104
+ className="back-chip"
105
+ data-testid="strip-back"
106
+ title={title}
107
+ aria-label={title}
108
+ onClick={() => backTarget(store.getState())?.run(store)}
109
+ >
110
+ <span className="tool-icon" aria-hidden="true">
111
+ {CHEVRON_LEFT_ICON}
112
+ </span>
113
+ <span className="back-chip-label">{label}</span>
114
+ </button>
115
+ );
116
+ }
@@ -1,70 +1,139 @@
1
- // The camera verbs that belong to the WORKING PANE, floating over it.
1
+ // The camera verbs, as ONE menu button over the stage.
2
2
  //
3
- // ── WHY THESE THREE ARE TOGETHER, AND WHY THEY ARE HERE ─────────────────
3
+ // ── WHY A MENU WITH WORDS, AND NOT THREE GLYPHS ─────────────────────────
4
4
  //
5
- // A frustum you can look at but not act on is decoration. What makes a free
6
- // view worth having is the ROUND TRIP between it and the camera, and the round
7
- // trip is exactly these: point the camera at what I am looking at, keep
8
- // pointing it there while I move, and frame this selection. They sit over the
9
- // pane they act on rather than in the inspector, because the pane is where the
10
- // author's eyes and pointer already are.
5
+ // This bar was three unlabelled icon buttons (a camera, a link, a fit) whose
6
+ // sentences lived in `title` tooltips, and the founder's verdict was "I don't
7
+ // know what these options are, when they apply, or whether they are good". The
8
+ // words had been cut for 310px of control-strip budget. A menu button costs
9
+ // one control's width, keeps every word on a row an author can read, and has a
10
+ // CAPTION: the one sentence that names the two cameras, which nothing on
11
+ // screen had ever said.
11
12
  //
12
- // Mounted only while the working pane is on screen: in camera mode there is no
13
- // "this view" to align to, and a control that is present but meaningless is
14
- // worse than one that is absent.
13
+ // THE WORD "Camera" IS THE GROUP'S, not the trigger's. The canvas bar labels
14
+ // each pane's group with the pane's own word (CanvasBar.tsx), right before
15
+ // this button, so the trigger is a glyph square like the shading one beside
16
+ // the "3D" word. A word on both would read "Camera Camera".
15
17
  //
16
- // ── THE FOLLOW TOGGLE IS NOT CALLED A LOCK ──────────────────────────────
18
+ // Every word comes from `state/cameraVerbs.ts`, which the View menu and the
19
+ // shortcut manual read too. This file decides only what is disabled and why.
17
20
  //
18
- // Blender's long-running complaint about the equivalent toggle is that its name
19
- // says the opposite of what it does — nothing is locked, the camera is being
20
- // moved by every orbit — and that it is easy to forget it is on. So it is named
21
- // for what happens, it is `aria-pressed`, and the affordance that stops it
22
- // being forgotten is drawn INSIDE the pane (see Overlays' FollowBadge). A
23
- // checkbox in a panel nobody is looking at while they orbit is how the Blender
24
- // version gets forgotten.
21
+ // ── SHOWN WHEREVER THE CAMERA PANE IS ───────────────────────────────────
25
22
  //
26
- // A FLOATING SIBLING of the stage, never a wrapper. See App.tsx's header.
23
+ // CanvasBar mounts it in the camera pane's group, so it is on screen in the
24
+ // Camera and Split layouts, which is where a document OPENS and where Pilot
25
+ // does its work. In the 3D layout the camera pane is not on screen and
26
+ // neither is this menu: its verbs are in the View menu, "Move camera to
27
+ // view" included. Hidden inside a component definition (no shot camera) and
28
+ // under Video (no furniture on a finished picture).
29
+ //
30
+ // The menu portals through `Popover`, exactly as `ShadingBar` does and for its
31
+ // reasons: it closes on an outside press and on Escape, flips when the window
32
+ // is short, and clamps on-screen.
33
+ import { useRef, useState } from "react";
34
+ import { Popover } from "@dailies-studio/ui";
27
35
  import { useShotEditor, useStore } from "../../state/context.js";
36
+ import { inDefinition, videoView } from "../../state/viewMode.js";
37
+ import { shortcutHint } from "../../state/shortcuts.js";
38
+ import { CAMERA_VERB_KEYS, TWO_CAMERAS, cameraVerb } from "../../state/cameraVerbs.js";
39
+ import type { CameraVerb } from "../../state/cameraVerbs.js";
40
+ import { CAMERA_ICON } from "../../icons.js";
28
41
 
29
42
  export function CameraViewBar() {
30
43
  const store = useStore();
31
- const mode = useShotEditor((s) => s.stageLayout.mode);
32
- const follows = useShotEditor((s) => s.cameraFollowsView);
33
- const hasSelection = useShotEditor((s) => s.selection.length > 0);
34
44
  const open = useShotEditor((s) => s.name !== null);
35
- if (!open || mode === "camera") return null;
45
+ const pilot = useShotEditor((s) => s.pilot);
46
+ const driver = useShotEditor((s) => s.cameraDriver);
47
+ const hasSelection = useShotEditor((s) => s.selection.length > 0);
48
+ const inDef = useShotEditor(inDefinition);
49
+ const watching = useShotEditor(videoView);
50
+ const inShot = useShotEditor((s) => s.enteredScene !== null);
51
+ const anchor = useRef<HTMLButtonElement>(null);
52
+ const [menuOpen, setMenuOpen] = useState(false);
53
+ if (!open || inDef || watching) {
54
+ // Returning null is not an unmount: a menu left open when the gate closed
55
+ // would come back open. ShadingBar's own note.
56
+ if (menuOpen) setMenuOpen(false);
57
+ return null;
58
+ }
59
+
60
+ const driven =
61
+ driver === null
62
+ ? null
63
+ : `The camera is driven by ${driver}, so the shot camera has no effect while it is.`;
64
+
65
+ /** Why a verb cannot run right now, or null when it can. */
66
+ const refusal = (key: CameraVerb): string | null => {
67
+ if (key === "addMove") return inShot ? null : "Open a shot to add a move to it.";
68
+ if (driven !== null) return driven;
69
+ if (key === "frameSelection" && !hasSelection) return "Select a layer first.";
70
+ return null;
71
+ };
72
+ const run = (key: CameraVerb): void => {
73
+ setMenuOpen(false);
74
+ switch (key) {
75
+ case "pilot":
76
+ store.setPilot(!pilot);
77
+ return;
78
+ case "toView":
79
+ store.alignCameraToWorkingView();
80
+ return;
81
+ case "frameSelection":
82
+ store.fitCameraToSelection();
83
+ return;
84
+ case "addMove":
85
+ store.addShotAtPlayhead();
86
+ return;
87
+ }
88
+ };
36
89
 
37
90
  return (
38
91
  <div className="camera-view-bar" role="group" aria-label="Camera" data-testid="camera-view-bar">
39
92
  <button
93
+ ref={anchor}
40
94
  type="button"
41
- className="btn"
42
- title="Point the shot camera where this view is looking (⌥C)"
43
- onClick={() => store.alignCameraToWorkingView()}
44
- >
45
- Align camera
46
- </button>
47
- <button
48
- type="button"
49
- className={`btn${follows ? " is-armed" : ""}`}
50
- aria-pressed={follows}
51
- title="Keep the shot camera pointed where this view looks, while you orbit"
52
- onClick={() => store.setCameraFollowsView(!follows)}
53
- >
54
- Follow view
55
- </button>
56
- <button
57
- type="button"
58
- className="btn"
59
- // Disabled rather than refusing, because "nothing is selected" is
60
- // visible on screen — a refusal message is for the cases the author
61
- // cannot see, like a shot painting over the value they just wrote.
62
- disabled={!hasSelection}
63
- title="Frame the selected layers"
64
- onClick={() => store.fitCameraToSelection()}
95
+ className={`tool-button has-caret${pilot ? " is-armed" : ""}`}
96
+ aria-haspopup="menu"
97
+ aria-expanded={menuOpen}
98
+ aria-label="Camera"
99
+ title={TWO_CAMERAS}
100
+ onClick={() => setMenuOpen((v) => !v)}
65
101
  >
66
- Fit to selection
102
+ <span className="tool-icon" aria-hidden="true">
103
+ {CAMERA_ICON}
104
+ </span>
105
+ <span className="tool-caret" aria-hidden="true" />
67
106
  </button>
107
+ <Popover open={menuOpen} anchor={anchor} onClose={() => setMenuOpen(false)}>
108
+ <div className="shading-menu camera-menu" role="menu" data-testid="camera-menu">
109
+ <div className="camera-menu-caption" role="presentation">
110
+ {TWO_CAMERAS}
111
+ </div>
112
+ {CAMERA_VERB_KEYS.map((key) => {
113
+ const spec = cameraVerb(key);
114
+ const why = refusal(key);
115
+ const armed = key === "pilot" && pilot;
116
+ return (
117
+ // THE SENTENCE HANGS ON THE WRAPPER: a disabled button fires no
118
+ // pointer events, so its own `title` could never open in the one
119
+ // state that needs explaining.
120
+ <span key={key} style={{ display: "flex" }} title={why ?? spec.sentence}>
121
+ <button
122
+ type="button"
123
+ role={key === "pilot" ? "menuitemcheckbox" : "menuitem"}
124
+ {...(key === "pilot" ? { "aria-checked": pilot } : {})}
125
+ className={`tool-button${armed ? " is-armed" : ""}`}
126
+ disabled={why !== null}
127
+ onClick={() => run(key)}
128
+ >
129
+ {spec.label}
130
+ <span className="camera-menu-key">{shortcutHint(spec.menuId).trim()}</span>
131
+ </button>
132
+ </span>
133
+ );
134
+ })}
135
+ </div>
136
+ </Popover>
68
137
  </div>
69
138
  );
70
139
  }
@@ -0,0 +1,117 @@
1
+ // The canvas bar: the docked row over the canvas cell (`.control-strip`).
2
+ //
3
+ // Left to right: the way out (BackChip, or the IsolationTrail inside an
4
+ // isolation), one group per pane on screen, then
5
+ // the stage's badges as the spring and the zoom, pushed right, and last the
6
+ // panel toggles, which every workspace keeps.
7
+ // It names the PLACE and how each pane is drawn. The docked footer under the
8
+ // canvas carries the tools and the view track (ToolFooter.tsx).
9
+ //
10
+ // ── ONE GROUP PER PANE, OVER ITS PANE ───────────────────────────────────
11
+ //
12
+ // Each group starts with the pane's word from `LAYOUT_SPECS` and holds that
13
+ // pane's controls. In the split the camera group starts AT THE SEAM, so each
14
+ // group sits over the pane it acts on, unless that would clip a control. The
15
+ // CSS does the placing (`.canvas-bar-lead.is-split` in styles/canvas.css) from
16
+ // `--split-x`, which App writes beside `--rail-w`, so this renders no geometry
17
+ // of its own.
18
+ //
19
+ // The pane that zoom and fit address (`activePane`) has its word in accent
20
+ // ink, so in a split the bar says which half the zoom buttons will move.
21
+ //
22
+ // ── THE VIEW TRACK IS IN THE FOOTER ─────────────────────────────────────
23
+ //
24
+ // Clean view and 3D | Split | Camera (ViewModeBar) answer for the whole
25
+ // canvas, not for one pane, so they never sat inside a pane's group. They
26
+ // rode this bar right of the spring until the Paper redesign moved them to
27
+ // the right half of the tool footer, which is where Paper draws them. What
28
+ // that freed is why this bar's give order is short now (canvas.css).
29
+ //
30
+ // ── WHAT IS ON SCREEN, NOT WHAT IS CHOSEN ───────────────────────────────
31
+ //
32
+ // Video draws the camera pane over any layout without writing it
33
+ // (`shownLayoutMode`). The groups follow the picture.
34
+ //
35
+ // ── NEVER EMPTY, NEVER A NEW HEIGHT ─────────────────────────────────────
36
+ //
37
+ // Zoom and the badges are here at every level and in Video, and the bar is
38
+ // always one `--panel-head-h` row, so the canvas cell under it never moves.
39
+ import type { ReactNode } from "react";
40
+ import { useShotEditor } from "../../state/context.js";
41
+ import { layoutSpec } from "../../state/layouts.js";
42
+ import { shownLayoutMode } from "../../state/viewMode.js";
43
+ import type { StagePane } from "../../stage/panes.js";
44
+ import { StageBadges } from "../header/StatusBadges.js";
45
+ import { ZoomControls } from "../header/ZoomControls.js";
46
+ import { BackChip } from "./BackChip.js";
47
+ import { CameraViewBar } from "./CameraViewBar.js";
48
+ import { IsolationTrail } from "./IsolationTrail.js";
49
+ import { ShadingBar } from "./ShadingBar.js";
50
+ import { PanelToggles } from "./PanelToggles.js";
51
+ import { WorkPlaneBar } from "./WorkPlaneBar.js";
52
+ import { CellSlot } from "../stage/cellSlots.js";
53
+ import { workspaceOf } from "../../state/workspace.js";
54
+
55
+ export function CanvasBar() {
56
+ // A MODE BRINGS ITS OWN BAR: the way back and its own verbs, portalled into
57
+ // the slot below (cellSlots.tsx). The stage's panes and zoom mean nothing
58
+ // over a board, a graph or a code editor.
59
+ const stage = useShotEditor((s) => workspaceOf(s) === "stage");
60
+ return (
61
+ <>
62
+ <CellSlot name="bar" className="canvas-bar-mode" />
63
+ {stage ? <StageBar /> : null}
64
+ {/* Last in every workspace: a mode's bar is its own, the panels are the
65
+ window's. */}
66
+ <PanelToggles />
67
+ </>
68
+ );
69
+ }
70
+
71
+ function StageBar() {
72
+ const open = useShotEditor((s) => s.name !== null);
73
+ const layout = useShotEditor(shownLayoutMode);
74
+ const activePane = useShotEditor((s) => s.activePane);
75
+ // No document, no panes: the bar keeps its row and holds nothing to act on.
76
+ const working = open && layout !== "camera";
77
+ const camera = open && layout !== "working";
78
+ const split = working && camera;
79
+ return (
80
+ <>
81
+ <div className={`canvas-bar-lead${split ? " is-split" : ""}`}>
82
+ <BackChip />
83
+ <IsolationTrail />
84
+ {working ? (
85
+ <PaneGroup pane="working" active={activePane === "working"}>
86
+ <ShadingBar />
87
+ <WorkPlaneBar />
88
+ </PaneGroup>
89
+ ) : null}
90
+ </div>
91
+ {camera ? (
92
+ <PaneGroup pane="camera" active={activePane === "camera"}>
93
+ <CameraViewBar />
94
+ </PaneGroup>
95
+ ) : null}
96
+ {/* The badges are the bar's spring: they push the zoom to the end and
97
+ are the first thing to give when the bar runs short (canvas.css). */}
98
+ <StageBadges />
99
+ <ZoomControls />
100
+ </>
101
+ );
102
+ }
103
+
104
+ function PaneGroup(props: { pane: StagePane; active: boolean; children: ReactNode }) {
105
+ const word = layoutSpec(props.pane).label;
106
+ return (
107
+ <div
108
+ className={`canvas-pane${props.active ? " is-active" : ""}`}
109
+ role="group"
110
+ aria-label={`${word} pane`}
111
+ data-testid={`canvas-pane-${props.pane}`}
112
+ >
113
+ <span className="canvas-pane-word">{word}</span>
114
+ {props.children}
115
+ </div>
116
+ );
117
+ }