@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,265 @@
1
+ // THE FONT PICKER'S LIST, as one flat array (FontPicker.tsx windows it): the
2
+ // sections in their fixed order, each a head row then its families, a family
3
+ // once, in the highest section that has it (case-folded), every section
4
+ // filtered by the search and the category chip at once, empty sections gone.
5
+ import { isGenericFontFamily } from "@dailies-studio/shot/doc";
6
+ import type { Doc3D } from "@dailies-studio/shot/doc";
7
+ import { appliesVariableFile, findGoogleFont, pickerShows, searchGoogleFonts } from "../../fonts/googleFonts.js";
8
+ import type { GoogleFontCategory, GoogleFontEntry } from "../../fonts/googleFonts.js";
9
+ import type { WorkspaceFont } from "../../api.js";
10
+ import { projectFontFamilies } from "../../state/fonts.js";
11
+ import type { FontPick, ProjectFontFamily } from "../../state/fonts.js";
12
+ import { fold } from "../../state/fontRecents.js";
13
+ import type { FontRecent } from "../../state/fontRecents.js";
14
+ import { googleFace, projectFace, workspaceFace } from "./faceCache.js";
15
+ import type { PreviewFace } from "./faceCache.js";
16
+
17
+ export type PickerSection = "project" | "recent" | "workspace" | "google";
18
+
19
+ export const SECTION_LABELS: Record<PickerSection, string> = {
20
+ project: "In this Project",
21
+ recent: "Recent",
22
+ workspace: "Workspace fonts",
23
+ google: "Google Fonts",
24
+ };
25
+
26
+ /** What a row says after its name. */
27
+ export type FontTag =
28
+ | { kind: "variable" }
29
+ | { kind: "count"; count: number; unit: "weight" | "style" }
30
+ | { kind: "machine" };
31
+
32
+ export interface FontRow {
33
+ type: "font";
34
+ /** Unique in the list: the section and the family's key. */
35
+ id: string;
36
+ /** The family case-folded: what makes two sources one family. */
37
+ key: string;
38
+ family: string;
39
+ section: PickerSection;
40
+ /** What Enter applies and the stage previews. Null for a machine font the
41
+ * catalog does not have: its way in is Upload. */
42
+ pick: FontPick | null;
43
+ /** A family text names with no file (`drawsMachineFont`). */
44
+ machine: boolean;
45
+ /** Null draws the name in Inter. Only a Google row (Recent, Google Fonts)
46
+ * fetches from jsDelivr: a family the Project holds draws from the
47
+ * Project's own file, a machine font from the computer. */
48
+ face: PreviewFace | null;
49
+ tag: FontTag;
50
+ /** Its catalog category, null when the catalog does not know it. */
51
+ category: GoogleFontCategory | null;
52
+ /** What picking it records in Recent: where the family comes from. Null for
53
+ * a file only this Project holds, which In this Project already lists. */
54
+ recent: FontRecent | null;
55
+ }
56
+
57
+ export interface HeadRow {
58
+ type: "head";
59
+ id: string;
60
+ label: string;
61
+ /** Google Fonts says how many families it lists. */
62
+ count?: number;
63
+ }
64
+
65
+ /** A line in the list that is not a family: the catalog loading or failed. */
66
+ export interface NoteRow {
67
+ type: "note";
68
+ id: string;
69
+ text: string;
70
+ busy: boolean;
71
+ }
72
+
73
+ export type PickerItem = FontRow | HeadRow | NoteRow;
74
+
75
+ export interface PickerInput {
76
+ doc: Doc3D;
77
+ /** Null while it loads. */
78
+ catalog: readonly GoogleFontEntry[] | null;
79
+ /** The catalog's chunk did not load. */
80
+ catalogFailed: boolean;
81
+ /** Null while it loads. */
82
+ workspace: readonly WorkspaceFont[] | null;
83
+ /** The workspace list did not load. */
84
+ workspaceFailed: boolean;
85
+ recents: readonly FontRecent[];
86
+ query: string;
87
+ /** The chip; null is All. */
88
+ category: GoogleFontCategory | null;
89
+ /** The selected text's family, case-folded; null when it is Mixed. */
90
+ current: string | null;
91
+ }
92
+
93
+ /** How well `name` matches the folded `query`, best 0, or -1 for no match:
94
+ * the tiers `searchGoogleFonts` ranks the catalog by (exact, prefix, a
95
+ * word's prefix, anywhere). */
96
+ function matchTier(name: string, query: string): number {
97
+ const n = fold(name);
98
+ if (n === query) return 0;
99
+ if (n.startsWith(query)) return 1;
100
+ if (n.split(" ").some((word) => word.startsWith(query))) return 2;
101
+ return n.includes(query) ? 3 : -1;
102
+ }
103
+
104
+ const googleTag = (entry: GoogleFontEntry): FontTag =>
105
+ appliesVariableFile(entry) ? { kind: "variable" } : { kind: "count", count: entry.weights.length, unit: "weight" };
106
+
107
+ function googleRow(section: PickerSection, entry: GoogleFontEntry): FontRow {
108
+ const key = fold(entry.family);
109
+ return {
110
+ type: "font",
111
+ id: `${section}:${key}`,
112
+ key,
113
+ family: entry.family,
114
+ section,
115
+ pick: { kind: "google", entry },
116
+ machine: false,
117
+ face: googleFace(entry),
118
+ tag: googleTag(entry),
119
+ category: entry.category,
120
+ recent: { kind: "google", family: entry.family },
121
+ };
122
+ }
123
+
124
+ function workspaceRow(section: PickerSection, family: string, fonts: readonly WorkspaceFont[]): FontRow {
125
+ const key = fold(family);
126
+ return {
127
+ type: "font",
128
+ id: `${section}:${key}`,
129
+ key,
130
+ family,
131
+ section,
132
+ pick: { kind: "workspace", family, fonts },
133
+ machine: false,
134
+ face: workspaceFace(fonts),
135
+ tag: fonts.some((font) => font.wghtAxis !== undefined)
136
+ ? { kind: "variable" }
137
+ : { kind: "count", count: fonts.length, unit: "style" },
138
+ category: null,
139
+ recent: { kind: "workspace", family },
140
+ };
141
+ }
142
+
143
+ function projectRow(row: ProjectFontFamily, doc: Doc3D, catalog: readonly GoogleFontEntry[] | null): FontRow {
144
+ const entry = row.source === "catalog" && catalog !== null ? findGoogleFont(catalog, row.family) : undefined;
145
+ const faces = row.pick.faces;
146
+ return {
147
+ type: "font",
148
+ id: `project:${row.key}`,
149
+ key: row.key,
150
+ family: row.family,
151
+ section: "project",
152
+ pick: row.pick,
153
+ machine: false,
154
+ face: projectFace(row, doc.assets, catalog),
155
+ tag: entry !== undefined
156
+ ? googleTag(entry)
157
+ : faces.some((face) => face.axis !== undefined)
158
+ ? { kind: "variable" }
159
+ : { kind: "count", count: faces.length, unit: "style" },
160
+ category: entry?.category ?? null,
161
+ recent: row.source === "file" ? null : { kind: row.source === "catalog" ? "google" : "workspace", family: row.family },
162
+ };
163
+ }
164
+
165
+ function machineRow(key: string, family: string, catalog: readonly GoogleFontEntry[] | null): FontRow {
166
+ const entry = catalog === null ? undefined : findGoogleFont(catalog, family);
167
+ return {
168
+ type: "font",
169
+ id: `project:${key}`,
170
+ key,
171
+ family,
172
+ section: "project",
173
+ pick: entry === undefined ? null : { kind: "google", entry },
174
+ machine: true,
175
+ // The face the stage draws it in, the computer's own: nothing is fetched.
176
+ face: { family, url: null },
177
+ tag: { kind: "machine" },
178
+ category: entry?.category ?? null,
179
+ recent: entry === undefined ? null : { kind: "google", family: entry.family },
180
+ };
181
+ }
182
+
183
+ /** Workspace font rows by family, in list order. */
184
+ function workspaceFamilies(fonts: readonly WorkspaceFont[]): [string, WorkspaceFont[]][] {
185
+ const families = new Map<string, WorkspaceFont[]>();
186
+ for (const font of fonts) {
187
+ const held = families.get(font.family);
188
+ if (held === undefined) families.set(font.family, [font]);
189
+ else held.push(font);
190
+ }
191
+ return [...families];
192
+ }
193
+
194
+ export function pickerItems(input: PickerInput): PickerItem[] {
195
+ const { doc, catalog, workspace, query, category } = input;
196
+ const q = fold(query);
197
+ // A row the catalog does not know shows under All only.
198
+ const shows = (row: FontRow): boolean =>
199
+ row.category === null ? category === null : pickerShows({ category: row.category }, category);
200
+ const seen = new Set<string>();
201
+ const items: PickerItem[] = [];
202
+ /** A section: its rows filtered and (outside Google, which the catalog
203
+ * search ranks) ranked by the search, each family once. */
204
+ const section = (id: PickerSection, rows: readonly FontRow[]): void => {
205
+ let kept = rows.filter(shows);
206
+ if (q.length > 0 && id !== "google") {
207
+ kept = kept
208
+ .map((row, at) => ({ row, at, tier: matchTier(row.family, q) }))
209
+ .filter((hit) => hit.tier >= 0)
210
+ .sort((a, b) => a.tier - b.tier || a.at - b.at)
211
+ .map((hit) => hit.row);
212
+ }
213
+ const once: FontRow[] = [];
214
+ for (const row of kept) {
215
+ if (seen.has(row.key)) continue;
216
+ seen.add(row.key);
217
+ once.push(row);
218
+ }
219
+ if (once.length === 0) return;
220
+ items.push({ type: "head", id: `head:${id}`, label: SECTION_LABELS[id], ...(id === "google" ? { count: once.length } : {}) }, ...once);
221
+ };
222
+
223
+ const project = projectFontFamilies(doc, catalog, workspace);
224
+ section("project", [
225
+ ...project.families.map((row) => projectRow(row, doc, catalog)),
226
+ // A generic keyword (serif) is no family a pick can write or a file can
227
+ // be, so only the current family's row shows it, the check on it.
228
+ ...project.machine
229
+ .filter((m) => m.key === input.current || !isGenericFontFamily(m.family))
230
+ .map((m) => machineRow(m.key, m.family, catalog)),
231
+ ]);
232
+
233
+ section(
234
+ "recent",
235
+ input.recents.flatMap((recent): FontRow[] => {
236
+ if (recent.kind === "google") {
237
+ const entry = catalog === null ? undefined : findGoogleFont(catalog, recent.family);
238
+ return entry === undefined ? [] : [googleRow("recent", entry)];
239
+ }
240
+ const fonts = (workspace ?? []).filter((font) => fold(font.family) === fold(recent.family));
241
+ return fonts.length === 0 ? [] : [workspaceRow("recent", fonts[0]!.family, fonts)];
242
+ }),
243
+ );
244
+
245
+ if (workspace !== null) {
246
+ section("workspace", workspaceFamilies(workspace).map(([family, fonts]) => workspaceRow("workspace", family, fonts)));
247
+ } else if (input.workspaceFailed) {
248
+ items.push(
249
+ { type: "head", id: "head:workspace", label: SECTION_LABELS.workspace },
250
+ { type: "note", id: "note:workspace", text: "Workspace fonts did not load. Open the picker again to retry.", busy: false },
251
+ );
252
+ }
253
+
254
+ if (catalog !== null) {
255
+ section("google", searchGoogleFonts(catalog, query).map((entry) => googleRow("google", entry)));
256
+ } else {
257
+ items.push(
258
+ { type: "head", id: "head:google", label: SECTION_LABELS.google },
259
+ input.catalogFailed
260
+ ? { type: "note", id: "note:google", text: "Google Fonts did not load. Open the picker again to retry.", busy: false }
261
+ : { type: "note", id: "note:google", text: "Loading Google Fonts…", busy: true },
262
+ );
263
+ }
264
+ return items;
265
+ }
@@ -0,0 +1,46 @@
1
+ // A request to open the font picker from somewhere other than its own field:
2
+ // the Typography section's machine-font note opens it on the upload view
3
+ // ("Upload font file"). The Font field listens and opens the picker on the
4
+ // asked view. Kept out of the picker's lazy chunk, so the note can ask
5
+ // before that chunk has loaded.
6
+ import { useSyncExternalStore } from "react";
7
+
8
+ export type FontPickerView = "list" | "upload";
9
+
10
+ /** How the picker closed, which says where focus goes: back to the Font
11
+ * field ("refocus": a pick, Escape in the editor's window), on from the field
12
+ * ("tab": Tab moves to the next control), or where a press outside put it
13
+ * ("stay"; a press or Escape on the stage hands focus to its frame). */
14
+ export type FontPickerClose = "refocus" | "tab" | "stay";
15
+
16
+ /** The dashboard's Fonts tab. The editor host sends `/projects/*` to the main
17
+ * host, so the relative link works on both, and on the local CLI. */
18
+ export const MANAGE_FONTS_HREF = "/projects/?view=settings-fonts";
19
+
20
+ /** One request. `seq` grows with every request, so asking for the same view
21
+ * twice still opens the picker the second time. */
22
+ export interface FontPickerRequest {
23
+ view: FontPickerView;
24
+ seq: number;
25
+ }
26
+
27
+ let current: FontPickerRequest | null = null;
28
+ const listeners = new Set<() => void>();
29
+
30
+ /** Ask the Font field to open the picker on `view`. */
31
+ export function requestFontPicker(view: FontPickerView): void {
32
+ current = { view, seq: (current?.seq ?? 0) + 1 };
33
+ for (const listener of listeners) listener();
34
+ }
35
+
36
+ function subscribe(listener: () => void): () => void {
37
+ listeners.add(listener);
38
+ return () => {
39
+ listeners.delete(listener);
40
+ };
41
+ }
42
+
43
+ /** The latest request, or null before the first. */
44
+ export function useFontPickerRequest(): FontPickerRequest | null {
45
+ return useSyncExternalStore(subscribe, () => current, () => null);
46
+ }
@@ -0,0 +1,76 @@
1
+ // The signed-in author's initials, at the end of the top bar. HOSTED ONLY: the
2
+ // local CLI has no accounts, so `isCloudWorkspace()` keeps this from asking.
3
+ // The name comes off `/auth/me`, the session probe the editor host answers
4
+ // (worker/src/auth.ts), read once per page. A click opens the profile through
5
+ // `leaveFor`, the same way out the app menu's Profile row takes.
6
+ //
7
+ // A guest in the current workspace also gets a "Guest" note beside it. The
8
+ // role is the server's effective one (`/auth/me` says "guest" for a guest), so
9
+ // the editor keeps no rule of its own about who counts as one.
10
+ import { useEffect, useState } from "react";
11
+ import { IconUser, initialsOf, personInitial } from "@dailies-studio/ui";
12
+ import { useStore } from "../../state/context.js";
13
+ import { isCloudWorkspace } from "../../state/posterCapture.js";
14
+ import { leaveFor } from "./leave.js";
15
+
16
+ /** The signed-in author's name and whether they are a guest here, off
17
+ * `/auth/me`. Null locally, before the probe answers, and when it fails.
18
+ * The Components footer's avatar reads it too. */
19
+ export function useSignedIn(): { name: string; guest: boolean } | null {
20
+ const [name, setName] = useState<string | null>(null);
21
+ const [guest, setGuest] = useState(false);
22
+ useEffect(() => {
23
+ if (!isCloudWorkspace()) return;
24
+ let live = true;
25
+ // A failed probe draws nothing: the menu's Profile row is still there.
26
+ void fetch("/auth/me", { credentials: "same-origin" })
27
+ .then((res) => (res.ok ? res.json() : null))
28
+ .then(
29
+ (
30
+ me: {
31
+ signedIn?: boolean;
32
+ login?: string;
33
+ displayName?: string;
34
+ workspace?: { role?: string } | null;
35
+ } | null,
36
+ ) => {
37
+ if (!live || me?.signedIn !== true) return;
38
+ setName(me.displayName ?? me.login ?? null);
39
+ setGuest(me.workspace?.role === "guest");
40
+ },
41
+ )
42
+ .catch(() => {});
43
+ return () => {
44
+ live = false;
45
+ };
46
+ }, []);
47
+ return name === null ? null : { name, guest };
48
+ }
49
+
50
+ export function AccountAvatar() {
51
+ const store = useStore();
52
+ const me = useSignedIn();
53
+ if (me === null) return null;
54
+ const { name, guest } = me;
55
+ return (
56
+ <>
57
+ {guest && (
58
+ <span
59
+ className="header-guest"
60
+ title="You see only the projects shared with you, and only the people on those projects."
61
+ >
62
+ Guest
63
+ </span>
64
+ )}
65
+ <button
66
+ type="button"
67
+ className="header-avatar"
68
+ aria-label={`Profile: ${name}`}
69
+ title={name}
70
+ onClick={() => leaveFor(store, "/projects/?view=profile")}
71
+ >
72
+ {personInitial(name) === null ? <IconUser /> : initialsOf(name)}
73
+ </button>
74
+ </>
75
+ );
76
+ }
@@ -0,0 +1,77 @@
1
+ // The Agent chip, in the top bar before the avatar: whether an outside agent
2
+ // can reach this workspace over MCP, in two words, and the connect rows under
3
+ // it. It replaced the left column's 64px MCP band.
4
+ //
5
+ // The word is HEALTH ONLY (`mcpWord`): "MCP ready", "Read only", "MCP off",
6
+ // "No access". A hosted role without `mcp.connect` wears the off tone.
7
+ // `/api/health` cannot see a client, so the chip never says "connected".
8
+ //
9
+ // One probe (`useMcpHealth`), held here and handed to the details, so the chip
10
+ // and its popover cannot disagree. The popover is portaled to <body>, so the
11
+ // header's `inert` under the Library does not reach it: it closes when the
12
+ // Library opens instead. The Popover's outside-pointerdown ignores its anchor,
13
+ // so a second press on the chip closes it through this toggle.
14
+ import { useEffect, useRef, useState } from "react";
15
+ import { PanelBoundary } from "@dailies-studio/ui";
16
+ import { useShotEditor, useStore } from "../../state/context.js";
17
+ import { ADMINS_ONLY_NOTE, McpPopover, adminsOnly, mcpWord, useMcpHealth } from "../files/McpSection.js";
18
+ import { agentHandoffPrompt } from "./agentHandoff.js";
19
+
20
+ export function AgentChip() {
21
+ const name = useShotEditor((s) => s.name);
22
+ if (name === null) return null;
23
+ return (
24
+ <PanelBoundary label="Agent chip">
25
+ <Chip name={name} />
26
+ </PanelBoundary>
27
+ );
28
+ }
29
+
30
+ function Chip({ name }: { name: string }) {
31
+ const store = useStore();
32
+ const title = useShotEditor((s) => s.doc?.title ?? s.name);
33
+ const libraryOpen = useShotEditor((s) => s.libraryBrowserOpen);
34
+ const { health, retry } = useMcpHealth(name);
35
+ const [open, setOpen] = useState(false);
36
+ const chip = useRef<HTMLButtonElement>(null);
37
+
38
+ useEffect(() => {
39
+ if (libraryOpen) setOpen(false);
40
+ }, [libraryOpen]);
41
+
42
+ const word = mcpWord(health);
43
+ const tone =
44
+ health.kind !== "ready"
45
+ ? "is-unknown"
46
+ : !health.mounted || !health.allowed
47
+ ? "is-off"
48
+ : health.write ? "is-write" : "is-read";
49
+ return (
50
+ <>
51
+ <button
52
+ ref={chip}
53
+ type="button"
54
+ className={`btn agent-chip ${tone}`}
55
+ data-testid="agent-chip"
56
+ aria-haspopup="dialog"
57
+ aria-expanded={open}
58
+ title={adminsOnly(health) ? ADMINS_ONLY_NOTE : "Connect your AI over MCP"}
59
+ onClick={() => setOpen((value) => !value)}
60
+ >
61
+ <span className="agent-chip-dot" aria-hidden="true" />
62
+ <span className="agent-chip-label">Agent</span>
63
+ <span className="agent-chip-word">{word}</span>
64
+ </button>
65
+ <McpPopover
66
+ anchor={chip}
67
+ open={open}
68
+ onClose={() => setOpen(false)}
69
+ health={health}
70
+ onRetry={retry}
71
+ projectName={name}
72
+ projectTitle={title}
73
+ handoff={() => agentHandoffPrompt(store.getState())}
74
+ />
75
+ </>
76
+ );
77
+ }
@@ -0,0 +1,213 @@
1
+ // THE APP MENU'S BUTTON: the brand mark in the top-left, and every verb this
2
+ // chrome has behind it.
3
+ //
4
+ // ── WHY THE TOP-LEFT ─────────────────────────────────────────────────────
5
+ //
6
+ // The brand button leads the Project identity, in the corner every desktop app
7
+ // reserves for its menu.
8
+ //
9
+ // ── AND WHY THIS FILE IS SO SHORT ────────────────────────────────────────
10
+ //
11
+ // The tree is DATA (`appMenuEntries`) and the framework is shared
12
+ // (`useContextMenu`, which already portals to <body>, clamps and flips against
13
+ // the viewport, nests submenus, drives the arrows, closes on Escape and an
14
+ // outside press, and restores focus). What is left is: a button, a rect to open
15
+ // at, and the four things a menu row cannot do by itself.
16
+ //
17
+ // ── THOSE FOUR THINGS ────────────────────────────────────────────────────
18
+ //
19
+ // A file dialog can only be opened by a click on a real `<input>` (the store is
20
+ // not one, which is why `imagePick` exists), and a name has to be ASKED for. So
21
+ // this file owns one hidden input and one `usePrompt`, and hands the menu
22
+ // callbacks, using the same `onRename` hatch `shotMenuEntries` established. Project
23
+ // lifecycle is deliberately absent here; `/projects/` owns it.
24
+ //
25
+ // The input is HERE and not borrowed from `AddMenu`: that one lives inside
26
+ // `ToolDockBar`, which UNMOUNTS ENTIRELY in the scene context, so it is not in
27
+ // the document at exactly the moment a global row would click it.
28
+ import { useEffect, useRef, useState } from "react";
29
+ import { DailiesMark, useConfirm, usePrompt, useContextMenu, useToasts } from "@dailies-studio/ui";
30
+ import { homeOf } from "@dailies-studio/shot/doc";
31
+ import { useStudioTheme } from "@dailies-studio/ui/theme";
32
+ import { useShotEditor, useStore } from "../../state/context.js";
33
+ import { appMenuEntries } from "../../state/menu.js";
34
+ import type { AppMenuHooks } from "../../state/menu.js";
35
+ import { shortcutFor } from "../../state/shortcuts.js";
36
+ import { CHEVRON_DOWN_ICON } from "../../icons.js";
37
+ import { HelpDialog } from "../help/HelpSection.js";
38
+ import { PreferencesDialog } from "../../app/lazySurfaces.js";
39
+ import { leaveFor } from "./leave.js";
40
+
41
+ export function AppMenu() {
42
+ const store = useStore();
43
+ const { preference: themePreference, setPreference: setThemePreference } = useStudioTheme();
44
+ const { open, menu } = useContextMenu();
45
+ const { prompt, dialog } = usePrompt();
46
+ const { confirm, dialog: confirmDialog } = useConfirm();
47
+ const { toast, node: toasts } = useToasts();
48
+ const [helpOpen, setHelpOpen] = useState(false);
49
+ const buttonRef = useRef<HTMLButtonElement | null>(null);
50
+ const audioRef = useRef<HTMLInputElement | null>(null);
51
+ // ⌘K's mailbox. The ONE subscription in this component, and it is to an object
52
+ // identity rather than to any state the tree reads — see `appMenu` in types.ts
53
+ // and `StageMenu`, which is the same shape for the same reason.
54
+ const request = useShotEditor((s) => s.appMenu);
55
+ // `menu` is null while closed, so the button's `aria-expanded` is DERIVED from
56
+ // it. No second open bit: `useContextMenu` owns the three ways a menu is
57
+ // dismissed (Escape, an outside press, an action firing) and reports none of
58
+ // them, so a mirrored boolean here could only ever be the stale one.
59
+ const isOpen = menu !== null;
60
+
61
+ // THE TREE IS READ WITH `getState()` AT OPEN TIME — StageMenu's rule — because
62
+ // a menu built from subscribed slices would need one subscription per predicate
63
+ // in it, in the header, which re-renders every control up there on each
64
+ // notification.
65
+ const report = (e: unknown): void =>
66
+ store.reportError(e instanceof Error ? e.message : String(e));
67
+
68
+ const hooks: AppMenuHooks = {
69
+ themePreference,
70
+ onThemePreference: setThemePreference,
71
+ onRegenerate: () => {
72
+ void confirm({
73
+ title: "Regenerate the component",
74
+ message:
75
+ "This overwrites the generated file with what the document says, discarding any hand " +
76
+ "edits in it. This cannot be undone.",
77
+ confirmLabel: "Regenerate",
78
+ danger: true,
79
+ }).then((yes) => {
80
+ if (yes) void store.regenerateShot();
81
+ });
82
+ },
83
+ onCopyPath: (path) => {
84
+ // `navigator.clipboard` is absent over plain http on a non-localhost host,
85
+ // which the hosted lane is not but a LAN dev server can be, so the failure
86
+ // path says the path instead of silently doing nothing. A success leaves
87
+ // the error strip alone: it may hold an unrelated message.
88
+ const fail = (): void => store.reportError(`Copy failed. The generated file is at ${path}`);
89
+ if (navigator.clipboard === undefined) return fail();
90
+ void navigator.clipboard.writeText(path).then(
91
+ () => void toast({ message: "Path copied", severity: "success" }),
92
+ fail,
93
+ );
94
+ },
95
+ onRenameScene: (sceneId) => {
96
+ const scene = store.getState().doc?.scenesById[sceneId];
97
+ void prompt({
98
+ title: "Rename shot",
99
+ label: "Name",
100
+ initialValue: scene?.name ?? "",
101
+ }).then((name) => {
102
+ if (name !== null && name.trim() !== "") store.renameScene(sceneId, name.trim());
103
+ });
104
+ },
105
+ onRenameLayer: (layerId) => {
106
+ // The container `store.renameLayer` writes to (`layerContainer()`).
107
+ const s = store.getState();
108
+ const container = s.editingDef ?? s.sceneId;
109
+ const layer =
110
+ s.doc === null || container === null ? undefined : homeOf(s.doc, container)?.layersById[layerId];
111
+ void prompt({ title: "Rename layer", label: "Name", initialValue: layer?.label ?? "" }).then((name) => {
112
+ if (name !== null && name.trim() !== "") store.renameLayer(layerId, name.trim());
113
+ });
114
+ },
115
+ onRenameComponent: (defId) => {
116
+ const def = store.getState().doc?.components?.[defId];
117
+ void prompt({
118
+ title: "Rename component",
119
+ label: "Name",
120
+ initialValue: def?.name ?? "",
121
+ }).then((name) => {
122
+ if (name !== null && name.trim() !== "") store.renameComponent(defId, name.trim());
123
+ });
124
+ },
125
+ onImportAudio: () => audioRef.current?.click(),
126
+ onOpenHelp: () => setHelpOpen(true),
127
+ // One way out for every row, shared with the `Projects` crumb and the
128
+ // avatar: see `leaveFor` on why it flushes first and stays on a failure.
129
+ onNavigate: (href) => leaveFor(store, href),
130
+ };
131
+
132
+ const show = (): void => {
133
+ const el = buttonRef.current;
134
+ if (el === null) return;
135
+ // A POINT FROM THE BUTTON'S RECT, not a new anchoring API on the menu.
136
+ // `Popover` already owns anchor-to-element and has no keyboard model;
137
+ // composing the two would be a third floating primitive for two lines of
138
+ // arithmetic. The gap matches the header's own 12px inset feel.
139
+ const r = el.getBoundingClientRect();
140
+ open({ x: r.left, y: r.bottom + 6 }, appMenuEntries(store, store.getState(), hooks), {
141
+ // ONE SURFACE, not a menu plus a palette. Framer's list has a search field
142
+ // at the top AND a "Quick Actions ⌘K" row below it; here ⌘K opens this and
143
+ // the field is already focused, so the second row would point at itself.
144
+ search: "Type to search…",
145
+ // Paper's workspace menu keeps both 16px lanes on every row of every
146
+ // level, so File's labels line up with the root's when it opens beside it.
147
+ lanes: "always",
148
+ });
149
+ };
150
+
151
+ // ⌘K, arriving through the store because the window's keydown listener cannot
152
+ // reach a popover. Keyed on the mailbox OBJECT, so a second press re-opens.
153
+ useEffect(() => {
154
+ if (request === null) return;
155
+ show();
156
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- `show` closes over
157
+ // fresh hooks/state every render by design; the mailbox identity is the trigger.
158
+ }, [request]);
159
+
160
+ return (
161
+ <>
162
+ <button
163
+ ref={buttonRef}
164
+ type="button"
165
+ // `.btn` first: hover, disabled and the `.is-active` accent-soft fill
166
+ // are the chrome's one button recipe, not a second one written here.
167
+ className={`btn app-menu-trigger${isOpen ? " is-active" : ""}`}
168
+ aria-haspopup="menu"
169
+ aria-expanded={isOpen}
170
+ aria-label="Menu"
171
+ // From the shortcut table, not typed here — `ZoomControls` typed its own
172
+ // hint and it drifted.
173
+ title={`Menu (${shortcutFor("app.menu") ?? ""})`}
174
+ data-testid="app-menu-trigger"
175
+ onClick={show}
176
+ >
177
+ {/* The Dailies mark in `currentColor`, the one brand element the
178
+ chrome draws (Paper's workspace trigger). */}
179
+ <DailiesMark className="app-menu-mark" />
180
+ <span className="app-menu-caret" aria-hidden="true">
181
+ {CHEVRON_DOWN_ICON}
182
+ </span>
183
+ </button>
184
+ {menu}
185
+ <input
186
+ ref={audioRef}
187
+ type="file"
188
+ accept="audio/*"
189
+ hidden
190
+ onChange={(e) => {
191
+ const file = e.target.files?.[0] ?? null;
192
+ // Clear FIRST: picking the same file twice must fire again, and an
193
+ // unchanged value fires no event at all.
194
+ e.target.value = "";
195
+ if (file === null) return;
196
+ // At the PLAYHEAD and into the entered shot when there is one, which
197
+ // is `importAudio`'s own convention — a master bed otherwise.
198
+ const s = store.getState();
199
+ void store
200
+ .importAudio(file, s.frame, s.enteredScene ?? undefined)
201
+ .catch(report);
202
+ }}
203
+ />
204
+ {dialog}
205
+ {confirmDialog}
206
+ {toasts}
207
+ <HelpDialog open={helpOpen} onClose={() => setHelpOpen(false)} />
208
+ {/* Opened by `prefsOpen` (the menu row, ⌘, and `prefs.open`), and
209
+ fetched at its first open (app/lazySurfaces.tsx). */}
210
+ <PreferencesDialog />
211
+ </>
212
+ );
213
+ }