@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,109 @@
1
+ // THE ICON SET the Insert drawer's Icons section lists: every Lucide icon,
2
+ // under its canonical kebab name, with its aliases kept for search.
3
+ //
4
+ // LAZY. Lucide is about 1850 icons, and none of them is worth a byte of the
5
+ // editor's first load: `loadIconSet` imports the package (and the raw text of
6
+ // its alias index) the first time the Icons section is asked for, and Vite
7
+ // splits both into their own chunk.
8
+ //
9
+ // WHY THE RAW INDEX. `lucide`'s `icons` object keys every icon by its PASCAL
10
+ // name, aliases included, and an alias points at the very same node array as
11
+ // its canonical icon, so identity alone cannot say which name is the real one.
12
+ // The index file can: each line re-exports one icon FILE, whose kebab name is
13
+ // the canonical name, under every Pascal name that means it.
14
+ import type { IconNode } from "@dailies-studio/shot/doc";
15
+
16
+ export interface IconEntry {
17
+ /** Lucide's canonical kebab name, the file name (`arrow-up-right`). */
18
+ name: string;
19
+ /** Every other name Lucide accepts for it, kebab-cased. */
20
+ aliases: readonly string[];
21
+ /** The drawing, element by element, on Lucide's 24×24 grid. */
22
+ node: readonly IconNode[];
23
+ /** Name and aliases in one lowercase string, the thing a query is matched
24
+ * against. */
25
+ search: string;
26
+ }
27
+
28
+ /** `export { default as A, default as B } from './icons/a-b.mjs';` */
29
+ const EXPORT_LINE = /export \{ ([^}]+) \} from '\.\/icons\/([a-z0-9-]+)\.mjs'/g;
30
+
31
+ /** `ArrowUpRight` to `arrow-up-right`, `Grid2x2` to `grid-2x2`: a dash
32
+ * before each inner capital, and before a digit run that follows a letter,
33
+ * except the `x` of a size like `2x2`. Used only for ALIASES, which have no
34
+ * file to read the real kebab name from. */
35
+ export function kebabOf(pascal: string): string {
36
+ return pascal
37
+ .replace(/([a-z0-9])([A-Z])/g, "$1-$2")
38
+ .replace(/(?<![0-9]x)(?<=[a-zA-Z])(?=[0-9])/g, "-")
39
+ .toLowerCase();
40
+ }
41
+
42
+ /**
43
+ * The set, from the alias index's text and the package's `icons` object.
44
+ * Sorted by name. A line whose icon the object does not have is skipped
45
+ * rather than listed as a tile that would draw nothing.
46
+ */
47
+ export function buildIconSet(raw: string, icons: Readonly<Record<string, unknown>>): IconEntry[] {
48
+ const out: IconEntry[] = [];
49
+ for (const match of raw.matchAll(EXPORT_LINE)) {
50
+ const names = (match[1] ?? "")
51
+ .split(",")
52
+ .map((part) => part.trim().replace(/^default as /, ""))
53
+ .filter((part) => part !== "");
54
+ const name = match[2] ?? "";
55
+ const first = names[0];
56
+ const node = first === undefined ? undefined : icons[first];
57
+ if (name === "" || !Array.isArray(node)) continue;
58
+ const aliases = [...new Set(names.map(kebabOf))].filter((alias) => alias !== name);
59
+ out.push({
60
+ name,
61
+ aliases,
62
+ node: node as readonly IconNode[],
63
+ search: [name, ...aliases].join(" "),
64
+ });
65
+ }
66
+ out.sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
67
+ return out;
68
+ }
69
+
70
+ /**
71
+ * The icons a query finds, best first.
72
+ *
73
+ * Every whitespace-separated word must appear in the name or an alias. Ties
74
+ * keep the set's alphabetical order. Ranked: the exact name, then a name that
75
+ * starts with the query, then everything else that matched. An empty query
76
+ * answers the whole set.
77
+ */
78
+ export function searchIcons(set: readonly IconEntry[], query: string): readonly IconEntry[] {
79
+ const q = query.trim().toLowerCase();
80
+ if (q === "") return set;
81
+ const words = q.split(/\s+/);
82
+ const rank = (entry: IconEntry): number => (entry.name === q ? 0 : entry.name.startsWith(q) ? 1 : 2);
83
+ return set
84
+ .filter((entry) => words.every((word) => entry.search.includes(word)))
85
+ .map((entry, index) => ({ entry, index, rank: rank(entry) }))
86
+ .sort((a, b) => a.rank - b.rank || a.index - b.index)
87
+ .map(({ entry }) => entry);
88
+ }
89
+
90
+ let pending: Promise<readonly IconEntry[]> | null = null;
91
+
92
+ /**
93
+ * The whole set, loaded once. A failed load (a chunk the network dropped)
94
+ * clears the memo, so the next open tries again instead of answering the
95
+ * same rejection forever.
96
+ */
97
+ export function loadIconSet(): Promise<readonly IconEntry[]> {
98
+ pending ??= Promise.all([
99
+ import("lucide/dist/esm/iconsAndAliases.mjs?raw"),
100
+ import("lucide"),
101
+ ]).then(
102
+ ([raw, lucide]) => buildIconSet(raw.default, lucide.icons as Readonly<Record<string, unknown>>),
103
+ (error: unknown) => {
104
+ pending = null;
105
+ throw error;
106
+ },
107
+ );
108
+ return pending;
109
+ }
@@ -0,0 +1,117 @@
1
+ // THE EXPOSE PIN: the inspector's one way to publish a property, and the only
2
+ // place in the chrome an `ExposedProp3D` is created.
3
+ //
4
+ // It is `KeyframeDot`'s twin on purpose (the same accessory lane inside the
5
+ // field's box, the same 16px button, one dotted PATH and nothing else),
6
+ // because the two verbs are the same shape of decision about the same row:
7
+ // the diamond says "this moves over time", the pin says "whoever places this
8
+ // decides". An author who has learnt one has learnt both.
9
+ //
10
+ // ── ABSENT, NEVER DEAD ──────────────────────────────────────────────────
11
+ //
12
+ // The diamond's discipline, for the diamond's reason: what the chrome offers
13
+ // and what the document accepts must be the same set by construction, or the
14
+ // author presses a control and the save gate refuses them afterwards. Every
15
+ // condition below is asked of the same function the gate asks. The per-layer
16
+ // ones live in the doc plane as `exposeOffered` (component.ts), which
17
+ // `suggestControls` asks too.
18
+ //
19
+ // a DEFINITION is open an exposed prop belongs to one, and only one
20
+ // exactly ONE layer a prop targets one layer, so a fan-out over a
21
+ // multi-selection would be several props from one
22
+ // press with one key between them
23
+ // not an identity path `identityPathProblem`, `exposeProp`'s first refusal
24
+ // not already published `exposedPropWriting`, the predicate `KeyframeDot`
25
+ // also consults — a second prop on the same field
26
+ // is two absolute writers
27
+ // no track on the path `exposeProp`'s own refusal, asked BEFORE the
28
+ // offer rather than after the press
29
+ // a default is capturable `exposableValueOf` reads the layer and falls back
30
+ // to the path table's, or for a component prop to
31
+ // the LIVE contract's default; with none, the prop
32
+ // would start at "" and change the picture the
33
+ // moment it is made. The press captures the same
34
+ // value (the store's `exposeProp`)
35
+ // its container exists `writeAtPath` REFUSES to create one, so a prop
36
+ // aimed into an absent `paint.stroke` would be
37
+ // permanently inert — the table's `needs`, which is
38
+ // the same clause a first keyframe honours
39
+ //
40
+ // Under `LockedControls` (a viewer) it is disabled, like every other control
41
+ // that writes.
42
+ import { useLocked } from "@dailies-studio/ui";
43
+ import { exposeOffered, trackablePath } from "@dailies-studio/shot/doc";
44
+ import { useShotEditor, useStore } from "../../state/context.js";
45
+ import { useSelectedLayers } from "./useDocWrite.js";
46
+
47
+ /**
48
+ * Whether the pin for `path` is offered here: the clauses above the import,
49
+ * asked of the doc plane's one predicate so "Make component"'s suggested
50
+ * controls offer exactly what this pin does. A row that draws the pin outside
51
+ * a box (a ControlSpec vector's one pin, in PropRow's label column) asks
52
+ * first, because a slot handed a pin that renders nothing still draws, empty.
53
+ */
54
+ export function useExposeOffered(path: string): boolean {
55
+ const doc = useShotEditor((s) => s.doc);
56
+ const editingDef = useShotEditor((s) => s.editingDef);
57
+ const contracts = useShotEditor((s) => s.contracts);
58
+ const layers = useSelectedLayers();
59
+ if (doc === null || editingDef === null || layers.length !== 1) return false;
60
+ const layer = layers[0]!;
61
+ // The registry's contract types an `instance.props.<key>` path, as it does
62
+ // for the diamond beside this one.
63
+ const live = layer.kind === "instance" ? contracts[layer.instance.component] : undefined;
64
+ return exposeOffered(doc, editingDef, layer, path, live);
65
+ }
66
+
67
+ /**
68
+ * The pin for one property: render it as the `trailing` of the box that shows
69
+ * the value. `KeyframeDot` already does it beside every diamond over one
70
+ * path, so a section only mounts this itself for a path with no diamond.
71
+ *
72
+ * `label` and `kind` override the path table's, and exist for the same reason
73
+ * `KeyframeDot`'s `label` does: a contract-driven path is in no table, and a
74
+ * row's wording is sometimes the section's rather than the table's.
75
+ */
76
+ export function ExposeDot({
77
+ path,
78
+ label,
79
+ kind,
80
+ }: {
81
+ path: string;
82
+ label?: string | undefined;
83
+ kind?: string | undefined;
84
+ }) {
85
+ const store = useStore();
86
+ const editingDef = useShotEditor((s) => s.editingDef);
87
+ const layers = useSelectedLayers();
88
+ const locked = useLocked();
89
+ const offered = useExposeOffered(path);
90
+
91
+ // Offered means a definition is open and exactly one layer is selected.
92
+ if (!offered || editingDef === null) return null;
93
+ const layer = layers[0]!;
94
+ const tabled = trackablePath(path);
95
+
96
+ const name = label ?? tabled?.label ?? path;
97
+ // Spread, not `{label, kind}`: under `exactOptionalPropertyTypes` an explicit
98
+ // `label: undefined` is not the same as an absent one, and the store reads
99
+ // absence as "take the table's word".
100
+ const opts = {
101
+ ...(label === undefined ? {} : { label }),
102
+ ...(kind === undefined ? {} : { kind }),
103
+ };
104
+ return (
105
+ <button
106
+ type="button"
107
+ className="insp-key insp-expose"
108
+ data-testid={`expose-dot-${path}`}
109
+ aria-label={`Publish ${name} as a component property`}
110
+ title={`Publish ${name}: every instance of this component can then override it`}
111
+ disabled={locked}
112
+ onClick={() => store.exposeProp(editingDef, layer.id, path, opts)}
113
+ >
114
+ ⊕
115
+ </button>
116
+ );
117
+ }
@@ -1,69 +1,339 @@
1
- // A `Framing` as two rows of fields. ONE definition, three subjects: the
2
- // scene's own camera, and a move's `from` and `to`.
1
+ // A `Framing` as rows of fields. ONE definition and ONE layout, three subjects:
2
+ // the scene's own camera (`CameraSection`), and a camera move's `from`, `to`
3
+ // and path waypoints (`animate/ShotSection.tsx`).
3
4
  //
4
- // It is shared rather than copied because the list IS the schema. A sixth
5
- // framing field would otherwise have to be remembered in two files, and the one
6
- // that got forgotten would silently keep an authored value the author could
7
- // never see or reach.
5
+ // ONE LAYOUT, so a pose reads the same wherever it is edited: the same words
6
+ // in the same places, the same units. The move editor's poses used to be a
7
+ // second grid in another order, naming the orbit "Yaw" and "Pitch" while the
8
+ // Camera section above it said "Pan X" and "Pan Y" and hid every unit, so an
9
+ // author comparing a move's From with the camera read two vocabularies for one
10
+ // set of numbers.
11
+ //
12
+ // The one difference is the zoom's SLIDER, which the Camera section asks for
13
+ // (`zoomSlider`) and a pose does not. A built-in row is a field, Figma's
14
+ // posture, and the camera's Zoom is the one built-in row that already had a
15
+ // slider; a pose's Zoom never did, and a move editor holding a slider per
16
+ // pose would make the exception the rule.
17
+ //
18
+ // It is shared rather than copied because the list IS the schema. A new
19
+ // framing field fails to compile in `FIELDS` until it is described, and lands
20
+ // in a row only when `LAYOUT` places it, so no subject can keep an authored
21
+ // value the author could never see or reach.
8
22
  //
9
23
  // The write is a PATCH and not a whole framing, so a caller can route it
10
24
  // straight at whichever seam owns its subject: `setSceneCamera` goes through
11
25
  // the refusal fork, `setShotFraming` does not, and this component knows about
12
26
  // neither.
13
- import { InspectorRow, NumberField } from "@dailies-studio/ui";
14
- import { MAX_FRAMING_ZOOM, MIN_FRAMING_ZOOM } from "@dailies-studio/shot/doc";
27
+ import { Fragment } from "react";
28
+ import type { ReactNode } from "react";
29
+ import { FieldLabels, IconDiamond, InspectorRow, NumberField, RangeField, formatValue, useLocked } from "@dailies-studio/ui";
30
+ import { MAX_CAMERA_PITCH_DEG, MAX_FRAMING_ZOOM, MIN_FRAMING_ZOOM } from "@dailies-studio/shot/doc";
15
31
  import type { Framing } from "@dailies-studio/shot/doc";
16
32
 
17
- /** The five fields, in the order an author thinks about them: where it looks,
18
- * how much it sees, how it is held, what is sharp. */
19
- const FIELDS: ReadonlyArray<{
20
- key: keyof Framing;
33
+ /** A framing field, by its schema name. */
34
+ export type CameraKey = keyof Framing;
35
+
36
+ interface FramingField {
37
+ /** The visible word, the prefix inside the box. The accessible name starts
38
+ * with it and then names the subject ("Pan X, Camera"), the house "Label,
39
+ * detail", so a speech command can say the word on the box and a screen
40
+ * reader hears it before which framing it belongs to. */
21
41
  label: string;
22
42
  unit?: string;
23
43
  step: number;
44
+ /** HARD limits: what the document accepts. */
24
45
  min?: number;
25
46
  max?: number;
26
- }> = [
27
- { key: "x", label: "X", step: 1 },
28
- { key: "y", label: "Y", step: 1 },
47
+ /** The span the zoom's slider draws (see below). */
48
+ range?: readonly [number, number];
49
+ /** What the word means, when the visible word is a film word. */
50
+ title?: string;
51
+ }
52
+
53
+ /**
54
+ * Every framing field, described once.
55
+ *
56
+ * ── THE WORDS ARE FILM WORDS ────────────────────────────────────────────
57
+ *
58
+ * "Pan X" and "Pan Y" are what a camera operator calls turning the camera
59
+ * across and up: that is `yawDeg` and `pitchDeg`, the orbit around the point
60
+ * the camera looks at. The schema word stays in the tooltip.
61
+ *
62
+ * ── AND THERE IS NO CAMERA Z ────────────────────────────────────────────
63
+ *
64
+ * `Framing` has no camera position on z: the distance is DERIVED from the
65
+ * lens and the zoom. The only z it carries is `focusZ`, the depth of the focus
66
+ * plane, which nothing but depth of field reads. So the field is labelled
67
+ * "Focus", for what it does, rather than "Z", which would promise a dolly the
68
+ * value does not perform.
69
+ *
70
+ * ── AND `range` IS NOT `min`/`max` ─────────────────────────────────────
71
+ *
72
+ * `min`/`max` are the HARD limits, what the document accepts, and they go to
73
+ * the field. `range` is the span Zoom's slider draws, and only Zoom has one:
74
+ * X, Y, Roll and Focus are open and signed (`checkFraming` in validate.ts asks
75
+ * each for nothing but a finite number, and nothing wraps `rollDeg` mod 360, so
76
+ * a roll of 720 is two turns rather than a seam).
77
+ */
78
+ const FIELDS: Record<CameraKey, FramingField> = {
79
+ x: { label: "X", step: 1, title: "Where the camera looks, across the design plane, in design px" },
80
+ y: { label: "Y", step: 1, title: "Where the camera looks, down the design plane, in design px" },
81
+ focusZ: {
82
+ label: "Focus",
83
+ step: 1,
84
+ title: "The depth of the focus plane, in design px. Only depth of field reads it",
85
+ },
29
86
  // Clamped at the widget as well as in the store: the store's clamp is the
30
87
  // boundary that must hold, and this one is what stops the author typing a
31
88
  // value that silently becomes a different one.
32
- { key: "zoom", label: "Zoom", step: 0.01, min: MIN_FRAMING_ZOOM, max: MAX_FRAMING_ZOOM },
33
- { key: "rollDeg", label: "Roll", unit: "deg", step: 0.5 },
34
- { key: "focusZ", label: "Focus", step: 1 },
89
+ //
90
+ // …but 0.05..50 is a VALIDATOR BAND (cameraLimits.ts), not a scale anybody
91
+ // authors across, so it cannot also be the slider. 0.25..4 is two octaves
92
+ // either side of 1: of the 199 authored zooms in the (since deleted)
93
+ // machineage example, 182 sit inside it (the working cluster is 1..1.5: 1,
94
+ // 1.035, 1.075, 1.13, over a body running 0.24 to 2.95), so 1 sits at 20%
95
+ // and 1.5 at 33%. The 17 that do not are one 0.24 and the dive ladder 4.4,
96
+ // 6.6, 10.2, 13.5, 18, 24, 26.4. The slider widens to hold them, and typing,
97
+ // the arrow keys and a scrub all still move them from where they are.
98
+ zoom: { label: "Zoom", step: 0.01, min: MIN_FRAMING_ZOOM, max: MAX_FRAMING_ZOOM, range: [0.25, 4] },
99
+ // THE ORBIT POSE (schema v26): where the camera stands around the point it
100
+ // looks at. Pan X is open like roll; Pan Y stops short of the poles.
101
+ yawDeg: {
102
+ label: "Pan X",
103
+ unit: "°",
104
+ step: 0.5,
105
+ title: "Pan X turns the camera across: its yaw, an orbit around the point it looks at",
106
+ },
107
+ pitchDeg: {
108
+ label: "Pan Y",
109
+ unit: "°",
110
+ step: 0.5,
111
+ min: -MAX_CAMERA_PITCH_DEG,
112
+ max: MAX_CAMERA_PITCH_DEG,
113
+ title: "Pan Y turns the camera up and down: its pitch, an orbit around the point it looks at",
114
+ },
115
+ rollDeg: { label: "Roll", unit: "°", step: 0.5 },
116
+ };
117
+
118
+ /** Every framing field, in `FIELDS` order: the camera's key states read each. */
119
+ export const FRAMING_KEYS = Object.keys(FIELDS) as CameraKey[];
120
+
121
+ /**
122
+ * The rows, top to bottom, in Paper's order: where it looks, what is sharp
123
+ * beside the lens, how much it sees, how it is turned, how it is held.
124
+ *
125
+ * ── THE ROW RULE, WHICH IS A WIDTH RULE ────────────────────────────────
126
+ *
127
+ * An inspector row holds AT MOST TWO labelled boxes, or three only when every
128
+ * label in it is a single character. The label is the half that gives way
129
+ * last, after the value and the unit, and at the rail's narrow end three word
130
+ * labels in one row ("Zoom", "Roll", "Focus") rendered as "Z…", "R…", "F…".
131
+ *
132
+ * A row holding ONE field stretches it full width, which is what Zoom wants
133
+ * for its slider, and what Focus gets when there is no lens beside it (a
134
+ * move's pose has none: the lens is the scene's, not the pose's).
135
+ */
136
+ const LAYOUT: ReadonlyArray<ReadonlyArray<CameraKey | "fov">> = [
137
+ ["x", "y"],
138
+ ["focusZ", "fov"],
139
+ ["zoom"],
140
+ ["yawDeg", "pitchDeg"],
141
+ ["rollDeg"],
35
142
  ];
36
143
 
144
+ /**
145
+ * A camera scrub, as the callbacks the rows drive. The OWNER of the subject
146
+ * implements it (`CameraSection` over the stage's framing lane, the move editor
147
+ * over the panel scrub), so this file still knows about no seam.
148
+ *
149
+ * `start`/`move`/`end` bracket one gesture. `commit` is a discrete value: the
150
+ * end of a gesture when one is open, and a plain one-entry write when none is
151
+ * (a typed value, an arrow nudge, a slider key).
152
+ */
153
+ export interface FramingScrub {
154
+ start(): void;
155
+ move(patch: Partial<Framing>): void;
156
+ end(commit: boolean): void;
157
+ commit(patch: Partial<Framing>): void;
158
+ }
159
+
160
+ /**
161
+ * What a field's diamond says about the camera's keys, which are its POSES
162
+ * (`cameraMarks`): every end of every camera move in the shot.
163
+ *
164
+ * keyed a pose sits at the playhead. A pose holds the whole framing, so
165
+ * every field lights together.
166
+ * animated this field's value differs between the shot's poses.
167
+ * static it does not, or there are no poses.
168
+ */
169
+ export type CameraKeyState = "keyed" | "animated" | "static";
170
+
171
+ /**
172
+ * The diamonds' data, worked out by the section that owns the store. This file
173
+ * stays store-free: it draws a state and calls back.
174
+ */
175
+ export interface CameraKeys {
176
+ stateOf(key: CameraKey): CameraKeyState;
177
+ /** This field's value differs between the shot's poses, at EVERY frame,
178
+ * a pose's own included. Apart from `stateOf` because "keyed" wins the
179
+ * diamond's state on a pose while the value still changes across the
180
+ * shot, and the value's accent ink reads this, so it holds steady as the
181
+ * playhead crosses a pose. */
182
+ animatedOf(key: CameraKey): boolean;
183
+ /** What a click does, as a sentence ("Click to add a camera move ending at
184
+ * the playhead."), or the reason it does nothing. Said in every diamond's
185
+ * tooltip. */
186
+ action: string;
187
+ /** Null when a click cannot do anything here (a path waypoint, the
188
+ * transport playing): the diamond then reads its state and is disabled. */
189
+ toggle: (() => void) | null;
190
+ }
191
+
192
+ const KEY_WORDS: Record<CameraKeyState, string> = {
193
+ keyed: "a camera key sits at the playhead",
194
+ animated: "animated: it changes between the camera keys",
195
+ static: "not animated",
196
+ };
197
+
198
+ /**
199
+ * One field's diamond, INSIDE the field: the house in-field key (`.insp-key`,
200
+ * the property diamond's box and glyph) in the camera's own signal.
201
+ *
202
+ * `is-camera` and `data-camera-state` rather than the property diamond's
203
+ * `is-keyed` / `is-tracked`, because those two mean "a keyframe on a track"
204
+ * and set the value in `--kf` (ui widgets.css). A camera pose is not a
205
+ * keyframe on a track (`model.ts` says the same of its marks), so its states
206
+ * draw in the ACCENT, the camera's documented exception (inspector-shot.css).
207
+ *
208
+ * Disabled, at full strength, when a click can do nothing here: a viewer's
209
+ * locked panel, a path waypoint, or the transport playing (the state is read
210
+ * off the held inspector frame while a press would act on the live one).
211
+ *
212
+ * `data-camera-animated` marks a field whose value changes across the shot,
213
+ * whatever the state: the value's accent ink keys on it (inspector-shot.css).
214
+ */
215
+ function CameraKeyDiamond(props: { name: string; field: CameraKey; keys: CameraKeys }) {
216
+ const locked = useLocked();
217
+ const toggle = props.keys.toggle;
218
+ const live = toggle !== null && !locked;
219
+ const state = props.keys.stateOf(props.field);
220
+ return (
221
+ <button
222
+ type="button"
223
+ className="insp-key is-camera"
224
+ data-camera-state={state}
225
+ {...(props.keys.animatedOf(props.field) ? { "data-camera-animated": "" } : {})}
226
+ data-testid={`camera-field-key-${props.field}`}
227
+ aria-label={`${props.name} key`}
228
+ aria-pressed={state === "keyed"}
229
+ // A viewer's panel says only what the diamond shows: the action it
230
+ // cannot take is not offered, and the store's refusal names VIEW_ONLY.
231
+ title={`${props.name}: ${KEY_WORDS[state]}.${locked ? "" : ` ${props.keys.action}`}`}
232
+ disabled={!live}
233
+ onClick={() => {
234
+ if (live) toggle();
235
+ }}
236
+ >
237
+ <IconDiamond filled={state === "keyed"} />
238
+ </button>
239
+ );
240
+ }
241
+
242
+ /**
243
+ * The framing's rows: X | Y, Focus beside the lens, Zoom across (with its
244
+ * slider in the Camera section), Pan X | Pan Y, then Roll.
245
+ *
246
+ * ── EVERY FIELD IS A STANDARD FIELD ─────────────────────────────────────
247
+ *
248
+ * The house NumberField with its name inside (and, with `zoomSlider`, Zoom the
249
+ * house RangeField, the field plus its slider in one box), the units shown,
250
+ * and, where the subject has keys, the diamond INSIDE the box as its
251
+ * `trailing`. A subject with no keys (a move's pose: its keys ARE the move)
252
+ * draws no diamond at all, rather than a dead one, and its fields are exactly
253
+ * as wide.
254
+ *
255
+ * `fov` is the one field that is not a framing key: it is `Scene3D.fovDeg`,
256
+ * slotted in by the Camera section, which owns the write. It has no diamond:
257
+ * the lens is not part of a pose.
258
+ *
259
+ * The names stay INSIDE the boxes whatever the panel's field-label
260
+ * preference: these are words already (one letter only for X and Y), and a
261
+ * name above each field would double the rows for words the box already says.
262
+ */
37
263
  export function FramingRows(props: {
38
264
  framing: Framing;
39
- /** Prefixes every field's accessible name ("Camera x", "From x"). Three
40
- * framings can be on screen at once, so the label alone is not a name. */
265
+ /** Follows the visible word in every field's accessible name ("X, Camera",
266
+ * "Pan X, From"), and so in its key's ("X, Camera key") and its slider's.
267
+ * Three framings can be on screen at once, so the word alone is not a
268
+ * name. */
41
269
  name: string;
42
- onCommit(patch: Partial<Framing>): void;
270
+ scrub: FramingScrub;
271
+ /** The camera's key diamonds. Absent, the fields carry none. */
272
+ keys?: CameraKeys;
273
+ /** The lens field, owned by the caller because it writes the scene. */
274
+ fov?: ReactNode;
275
+ /** Zoom as the field plus its slider: the Camera section's, and only its
276
+ * (see the header). Absent, Zoom is a field like every other. */
277
+ zoomSlider?: boolean;
43
278
  }) {
279
+ const { scrub, keys } = props;
280
+ const field = (key: CameraKey): ReactNode => {
281
+ const f = FIELDS[key];
282
+ const name = `${f.label}, ${props.name}`;
283
+ const value = props.framing[key] ?? 0;
284
+ const shared = {
285
+ value,
286
+ step: f.step,
287
+ ...(f.unit === undefined ? {} : { unit: f.unit }),
288
+ ...(f.title === undefined ? {} : { title: f.title }),
289
+ ...(keys === undefined ? {} : { trailing: <CameraKeyDiamond name={name} field={key} keys={keys} /> }),
290
+ onScrubStart: scrub.start,
291
+ onScrub: (next: number) => scrub.move({ [key]: next }),
292
+ onScrubEnd: scrub.end,
293
+ onCommit: (next: number) => scrub.commit({ [key]: next }),
294
+ };
295
+ if (props.zoomSlider === true && f.range !== undefined && f.min !== undefined && f.max !== undefined) {
296
+ // The slider spans the authoring range, widened by the field to hold the
297
+ // value, so a dive past 4 still shows its thumb at the end of a scale
298
+ // that contains it rather than pinned to a lie. It announces the value
299
+ // at its own quantum: mid-move the effective zoom is an interpolated
300
+ // float, and "1.051664760227515×" is noise to a listener.
301
+ return (
302
+ <RangeField
303
+ key={key}
304
+ label={f.label}
305
+ ariaLabel={name}
306
+ min={f.min}
307
+ max={f.max}
308
+ range={f.range}
309
+ valueText={`${formatValue(value, f.step)}×`}
310
+ {...shared}
311
+ />
312
+ );
313
+ }
314
+ return (
315
+ <NumberField
316
+ key={key}
317
+ label={f.label}
318
+ ariaLabel={name}
319
+ {...(f.min === undefined ? {} : { min: f.min })}
320
+ {...(f.max === undefined ? {} : { max: f.max })}
321
+ {...shared}
322
+ />
323
+ );
324
+ };
44
325
  return (
45
- <>
46
- {[FIELDS.slice(0, 2), FIELDS.slice(2)].map((group, i) => (
47
- <InspectorRow key={i}>
48
- {group.map((f) => {
49
- const limits: { min?: number; max?: number } = {};
50
- if (f.min !== undefined) limits.min = f.min;
51
- if (f.max !== undefined) limits.max = f.max;
52
- return (
53
- <NumberField
54
- key={f.key}
55
- value={props.framing[f.key]}
56
- label={f.label}
57
- ariaLabel={`${props.name} ${f.label.toLowerCase()}`}
58
- {...(f.unit === undefined ? {} : { unit: f.unit })}
59
- {...limits}
60
- step={f.step}
61
- onCommit={(next) => props.onCommit({ [f.key]: next })}
62
- />
63
- );
64
- })}
65
- </InspectorRow>
66
- ))}
67
- </>
326
+ <FieldLabels value="compact">
327
+ {LAYOUT.map((row) => {
328
+ const cells = row.flatMap((cell): ReactNode[] =>
329
+ cell === "fov" ? (props.fov === undefined ? [] : [<Fragment key="fov">{props.fov}</Fragment>]) : [field(cell)],
330
+ );
331
+ return (
332
+ <InspectorRow key={row.join("|")}>
333
+ {cells.length === 1 ? cells[0] : <div className="insp-vec">{cells}</div>}
334
+ </InspectorRow>
335
+ );
336
+ })}
337
+ </FieldLabels>
68
338
  );
69
339
  }