@dailies-studio/shot-editor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (432) hide show
  1. package/dist/.vite/manifest.json +382 -0
  2. package/dist/assets/AgentPanel-CQejLbiX.js +2 -0
  3. package/dist/assets/AgentPanel-DJEphah0.css +1 -0
  4. package/dist/assets/AgentQuestionDialog-BqkD0kXF.js +1 -0
  5. package/dist/assets/CodeStudio-Dv4ihHpz.js +23 -0
  6. package/dist/assets/CommentsPanel-B4yhhT1T.js +1 -0
  7. package/dist/assets/ComponentLibraryRail-Ch_dsAru.js +1 -0
  8. package/dist/assets/ComponentStage-BRhRotyH.js +1 -0
  9. package/dist/assets/CurvePane-BjFVabKX.css +1 -0
  10. package/dist/assets/CurvePane-R22WzllJ.js +1 -0
  11. package/dist/assets/ExportDialog-CWLQWTmG.js +2 -0
  12. package/dist/assets/FontPicker-DWKaYL7F.js +1 -0
  13. package/dist/assets/FontPicker-v0IOIArM.css +1 -0
  14. package/dist/assets/GenerateDrawer-Bh3SqDKt.js +2 -0
  15. package/dist/assets/InsertDrawer-Ce1fod1L.js +1 -0
  16. package/dist/assets/LibraryBrowser-D9sEGz5w.js +2 -0
  17. package/dist/assets/MakeItYoursSection-CMO4319_.js +2 -0
  18. package/dist/assets/MaterialGraphCanvas-BFzl4wWR.js +23 -0
  19. package/dist/assets/MaterialGraphCanvas-BIUD59ik.css +1 -0
  20. package/dist/assets/MaterialWorkspace-CqTWqaRA.js +14 -0
  21. package/dist/assets/PreferencesDialog-CBf9_OoD.js +1 -0
  22. package/dist/assets/PresetsSection-CUEaoCVU.js +1 -0
  23. package/dist/assets/SoundHistory-BeS8EW1h.js +1 -0
  24. package/dist/assets/SoundMode-BmcZru3i.js +3 -0
  25. package/dist/assets/StockSounds-DdAUGRYK.js +1 -0
  26. package/dist/assets/TemplateFieldSection-CaeXpLFo.js +1 -0
  27. package/dist/assets/VersionHistoryPanel-CxG6XhNP.js +1 -0
  28. package/dist/assets/board-B88EPuLO.css +1 -0
  29. package/dist/assets/board-CL-he7SL.js +3 -0
  30. package/dist/assets/components-0oOSPN0l.js +1 -0
  31. package/dist/assets/googleFontRanges-gp32wJMo.js +1 -0
  32. package/dist/assets/googleFonts-BNon6M7X.js +1 -0
  33. package/dist/assets/iconsAndAliases-DL7gXP5j.js +1863 -0
  34. package/dist/assets/index-4F5NQmI8.css +1 -0
  35. package/dist/assets/index-BGHQGV5z.js +117 -0
  36. package/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  37. package/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  38. package/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  39. package/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  40. package/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  41. package/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  42. package/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  43. package/dist/assets/libraryModel-CpIwfFha.js +1 -0
  44. package/dist/assets/lucide-6iK64tTB.js +9319 -0
  45. package/dist/assets/motionStamp-CH-Hew4E.js +1 -0
  46. package/dist/assets/takeMeasure-Tdg0TxEi.js +1 -0
  47. package/dist/assets/webmcp-BsB9gxWC.js +2 -0
  48. package/dist/index.html +34 -3
  49. package/package.json +12 -9
  50. package/src/addFamilies.ts +66 -0
  51. package/src/api.ts +1631 -55
  52. package/src/app/App.tsx +977 -198
  53. package/src/app/lazyPanel.tsx +137 -0
  54. package/src/app/lazySurfaces.tsx +203 -0
  55. package/src/audio/index.tsx +41 -10
  56. package/src/audio/meter.ts +485 -0
  57. package/src/audio/meterMath.ts +273 -0
  58. package/src/audio/meterStore.ts +155 -0
  59. package/src/audio/offlineMix.ts +289 -0
  60. package/src/audio/preview.ts +928 -118
  61. package/src/bridge.ts +15 -5
  62. package/src/bridgeTypes.ts +1611 -36
  63. package/src/channels.ts +17 -32
  64. package/src/fonts/googleFonts.ts +4 -0
  65. package/src/hostApi.ts +174 -0
  66. package/src/icons.tsx +770 -0
  67. package/src/layerWords.ts +61 -0
  68. package/src/main.tsx +56 -4
  69. package/src/panels/ResizeHandle.tsx +33 -43
  70. package/src/panels/agent/AgentDots.tsx +36 -0
  71. package/src/panels/agent/AgentPanel.tsx +119 -43
  72. package/src/panels/agent/AgentQuestionDialog.tsx +202 -0
  73. package/src/panels/agent/agent.css +113 -97
  74. package/src/panels/agent/agentGate.ts +35 -0
  75. package/src/panels/code/CodeDefaultsPanel.tsx +345 -0
  76. package/src/panels/code/CodeStudio.tsx +373 -0
  77. package/src/panels/code/FileTree.tsx +265 -0
  78. package/src/panels/code/codeBufferGuard.ts +52 -0
  79. package/src/panels/code/codeParts.tsx +321 -0
  80. package/src/panels/comments/CommentComposer.tsx +239 -0
  81. package/src/panels/comments/CommentThread.tsx +346 -0
  82. package/src/panels/comments/CommentsPanel.tsx +336 -0
  83. package/src/panels/comments/InboxButton.tsx +131 -0
  84. package/src/panels/comments/MentionMenu.tsx +98 -0
  85. package/src/panels/comments/parts.tsx +171 -0
  86. package/src/panels/export/ExportDialog.tsx +919 -96
  87. package/src/panels/export/ExportScrim.tsx +57 -0
  88. package/src/panels/files/AssetsTab.tsx +656 -0
  89. package/src/panels/files/FilePanel.tsx +1466 -216
  90. package/src/panels/files/LayerMarks.tsx +40 -0
  91. package/src/panels/files/LeftColumn.tsx +37 -0
  92. package/src/panels/files/McpSection.tsx +690 -47
  93. package/src/panels/files/MissingAssetsNotice.tsx +60 -0
  94. package/src/panels/files/assetDrag.ts +79 -0
  95. package/src/panels/files/resourceParts.tsx +255 -0
  96. package/src/panels/fonts/FontPicker.tsx +762 -0
  97. package/src/panels/fonts/faceCache.ts +207 -0
  98. package/src/panels/fonts/fontPicker.css +271 -0
  99. package/src/panels/fonts/pickerModel.ts +265 -0
  100. package/src/panels/fonts/pickerRequest.ts +46 -0
  101. package/src/panels/header/AccountAvatar.tsx +76 -0
  102. package/src/panels/header/AgentChip.tsx +77 -0
  103. package/src/panels/header/AppMenu.tsx +213 -0
  104. package/src/panels/header/Header.tsx +564 -84
  105. package/src/panels/header/HistoryControls.tsx +31 -5
  106. package/src/panels/header/PresenceStack.tsx +329 -0
  107. package/src/panels/header/ProjectTitle.tsx +232 -0
  108. package/src/panels/header/ShareButton.tsx +59 -0
  109. package/src/panels/header/StatusBadges.tsx +358 -27
  110. package/src/panels/header/SyncDeletedDialog.tsx +88 -0
  111. package/src/panels/header/ZoomControls.tsx +46 -15
  112. package/src/panels/header/agentHandoff.ts +52 -0
  113. package/src/panels/header/leave.ts +26 -0
  114. package/src/panels/help/HelpSection.tsx +189 -163
  115. package/src/panels/insert/InsertDrawer.tsx +723 -0
  116. package/src/panels/insert/iconSet.ts +109 -0
  117. package/src/panels/inspector/ExposeDot.tsx +117 -0
  118. package/src/panels/inspector/FramingRows.tsx +314 -44
  119. package/src/panels/inspector/InspectorFooter.tsx +91 -0
  120. package/src/panels/inspector/InspectorHead.tsx +425 -0
  121. package/src/panels/inspector/InspectorPanel.tsx +408 -124
  122. package/src/panels/inspector/KeyframeDot.tsx +373 -0
  123. package/src/panels/inspector/KeyframeSection.tsx +257 -0
  124. package/src/panels/inspector/MarkerSection.tsx +55 -11
  125. package/src/panels/inspector/ParamRow.tsx +84 -53
  126. package/src/panels/inspector/RightColumn.tsx +152 -0
  127. package/src/panels/inspector/TimeSection.tsx +866 -0
  128. package/src/panels/inspector/animate/EaseControl.tsx +244 -0
  129. package/src/panels/inspector/animate/PresetsSection.tsx +135 -0
  130. package/src/panels/inspector/animate/ShotSection.tsx +562 -48
  131. package/src/panels/inspector/design/AlignRow.tsx +95 -31
  132. package/src/panels/inspector/design/AppearanceSection.tsx +118 -9
  133. package/src/panels/inspector/design/ClonerSection.tsx +323 -62
  134. package/src/panels/inspector/design/ComponentSection.tsx +50 -43
  135. package/src/panels/inspector/design/CornerSection.tsx +45 -14
  136. package/src/panels/inspector/design/DefinitionProps.tsx +315 -0
  137. package/src/panels/inspector/design/DefinitionSection.tsx +136 -0
  138. package/src/panels/inspector/design/DeviceSection.tsx +210 -0
  139. package/src/panels/inspector/design/FillSection.tsx +83 -51
  140. package/src/panels/inspector/design/FontField.tsx +196 -0
  141. package/src/panels/inspector/design/HtmlSection.tsx +23 -12
  142. package/src/panels/inspector/design/InstanceSection.tsx +373 -154
  143. package/src/panels/inspector/design/MaskSection.tsx +84 -0
  144. package/src/panels/inspector/design/MaterialSection.tsx +177 -0
  145. package/src/panels/inspector/design/MediaSection.tsx +273 -120
  146. package/src/panels/inspector/design/MeshSection.tsx +910 -82
  147. package/src/panels/inspector/design/PaintRows.tsx +550 -0
  148. package/src/panels/inspector/design/PlateStyleSection.tsx +177 -0
  149. package/src/panels/inspector/design/ShadowSection.tsx +221 -48
  150. package/src/panels/inspector/design/SplineFollowSection.tsx +235 -0
  151. package/src/panels/inspector/design/StrokeSection.tsx +228 -43
  152. package/src/panels/inspector/design/TemplateFieldSection.tsx +57 -0
  153. package/src/panels/inspector/design/TransformSection.tsx +244 -50
  154. package/src/panels/inspector/design/TypographySection.tsx +679 -110
  155. package/src/panels/inspector/layout.ts +295 -0
  156. package/src/panels/inspector/propValue.ts +199 -0
  157. package/src/panels/inspector/scene/CameraSection.tsx +232 -22
  158. package/src/panels/inspector/scene/EffectsSection.tsx +722 -71
  159. package/src/panels/inspector/scene/MakeItYoursSection.tsx +180 -0
  160. package/src/panels/inspector/scene/SceneDocSection.tsx +191 -0
  161. package/src/panels/inspector/scene/SceneSection.tsx +219 -44
  162. package/src/panels/inspector/scene/TransitionSection.tsx +523 -72
  163. package/src/panels/inspector/storyboard/StoryboardReferenceSection.tsx +593 -0
  164. package/src/panels/inspector/storyboard/VideoStill.tsx +120 -0
  165. package/src/panels/inspector/timeModel.ts +190 -0
  166. package/src/panels/inspector/useDocWrite.ts +438 -146
  167. package/src/panels/library/LibraryBrowser.tsx +435 -0
  168. package/src/panels/library/LibraryGrid.tsx +190 -0
  169. package/src/panels/library/LibraryPreviewPanel.tsx +349 -0
  170. package/src/panels/library/LibraryRail.tsx +84 -0
  171. package/src/panels/library/LibrarySoundPanel.tsx +293 -0
  172. package/src/panels/library/SoundHistory.tsx +245 -0
  173. package/src/panels/library/StockSounds.tsx +390 -0
  174. package/src/panels/library/libraryModel.ts +147 -0
  175. package/src/panels/library/stockSearch.ts +77 -0
  176. package/src/panels/maskMarks.tsx +117 -0
  177. package/src/panels/material/MaterialCommentNode.tsx +54 -0
  178. package/src/panels/material/MaterialGraphCanvas.tsx +861 -0
  179. package/src/panels/material/MaterialGraphMenu.tsx +45 -0
  180. package/src/panels/material/MaterialGroupNode.tsx +56 -0
  181. package/src/panels/material/MaterialLibrary.tsx +253 -0
  182. package/src/panels/material/MaterialNode.tsx +453 -0
  183. package/src/panels/material/MaterialNodePalette.tsx +349 -0
  184. package/src/panels/material/MaterialParameterControls.tsx +525 -0
  185. package/src/panels/material/MaterialRerouteNode.tsx +42 -0
  186. package/src/panels/material/MaterialWorkspace.tsx +501 -0
  187. package/src/panels/material/NodePreview.tsx +70 -0
  188. package/src/panels/material/flowModel.ts +441 -0
  189. package/src/panels/material/parameterValues.ts +231 -0
  190. package/src/panels/material/previewScheduler.ts +235 -0
  191. package/src/panels/material/reactFlow.css +6 -0
  192. package/src/panels/material/socketGlyph.ts +85 -0
  193. package/src/panels/material/viewportMemory.ts +58 -0
  194. package/src/panels/preferences/PreferencesDialog.tsx +215 -0
  195. package/src/panels/share/ShareComponentDialog.tsx +247 -0
  196. package/src/panels/stage/StageMenu.tsx +35 -5
  197. package/src/panels/stage/StagePanel.tsx +338 -23
  198. package/src/panels/stage/cellSlots.tsx +70 -0
  199. package/src/panels/storyboard/BoardCanvas.tsx +739 -0
  200. package/src/panels/storyboard/BoardCursors.tsx +158 -0
  201. package/src/panels/storyboard/BoardDropDialog.tsx +161 -0
  202. package/src/panels/storyboard/BoardPlan.tsx +42 -0
  203. package/src/panels/storyboard/ComponentSheet.tsx +187 -0
  204. package/src/panels/storyboard/ComponentStage.tsx +235 -0
  205. package/src/panels/storyboard/MiniSketch.tsx +173 -0
  206. package/src/panels/storyboard/PartPicker.tsx +64 -0
  207. package/src/panels/storyboard/PlanPanel.tsx +617 -0
  208. package/src/panels/storyboard/ReferencePreview.tsx +217 -0
  209. package/src/panels/storyboard/ShotBrief.tsx +360 -0
  210. package/src/panels/storyboard/ShotCard.tsx +251 -0
  211. package/src/panels/storyboard/StagePreview.tsx +348 -0
  212. package/src/panels/storyboard/StillThumbs.tsx +162 -0
  213. package/src/panels/storyboard/StoryboardView.tsx +79 -0
  214. package/src/panels/storyboard/board.css +1065 -0
  215. package/src/panels/storyboard/board.ts +12 -0
  216. package/src/panels/storyboard/boardClipboard.ts +111 -0
  217. package/src/panels/storyboard/boardOrder.ts +68 -0
  218. package/src/panels/storyboard/boardRows.tsx +425 -0
  219. package/src/panels/storyboard/liveCap.ts +90 -0
  220. package/src/panels/storyboard/plan.css +295 -0
  221. package/src/panels/storyboard/posterCache.ts +133 -0
  222. package/src/panels/storyboard/shotboard/ComponentPicker.tsx +373 -0
  223. package/src/panels/storyboard/shotboard/ImagePicker.tsx +145 -0
  224. package/src/panels/storyboard/shotboard/ShotBoard.tsx +292 -0
  225. package/src/panels/storyboard/shotboard/SketchCanvas.tsx +443 -0
  226. package/src/panels/storyboard/shotboard/SketchInspector.tsx +471 -0
  227. package/src/panels/storyboard/shotboard/SketchItem.tsx +240 -0
  228. package/src/panels/storyboard/shotboard/SketchLayers.tsx +130 -0
  229. package/src/panels/storyboard/shotboard/SketchPlay.tsx +108 -0
  230. package/src/panels/storyboard/shotboard/SketchStrip.tsx +178 -0
  231. package/src/panels/storyboard/shotboard/SketchToolFooter.tsx +99 -0
  232. package/src/panels/storyboard/shotboard/sketch.css +1392 -0
  233. package/src/panels/storyboard/shotboard/sketchModel.ts +219 -0
  234. package/src/panels/storyboard/storyboardDrop.ts +164 -0
  235. package/src/panels/studio/ComponentLibraryRail.tsx +346 -0
  236. package/src/panels/studio/ComponentSidePanel.tsx +355 -0
  237. package/src/panels/studio/ComponentStudio.tsx +243 -0
  238. package/src/panels/studio/ComponentsHome.tsx +318 -0
  239. package/src/panels/studio/ComponentsPreview.tsx +179 -0
  240. package/src/panels/studio/EveryUse.tsx +244 -0
  241. package/src/panels/studio/ThisUsePanel.tsx +679 -0
  242. package/src/panels/studio/componentParts.tsx +341 -0
  243. package/src/panels/studio/components.ts +6 -0
  244. package/src/panels/studio/newCodeComponent.ts +66 -0
  245. package/src/panels/timeline/AudioFxButton.tsx +290 -0
  246. package/src/panels/timeline/AudioLane.tsx +674 -85
  247. package/src/panels/timeline/BlockRow.tsx +154 -85
  248. package/src/panels/timeline/ChannelRow.tsx +378 -129
  249. package/src/panels/timeline/CurvePane.tsx +508 -0
  250. package/src/panels/timeline/Filmstrip.tsx +361 -0
  251. package/src/panels/timeline/GenerateDrawer.tsx +407 -0
  252. package/src/panels/timeline/KeyStretch.tsx +115 -0
  253. package/src/panels/timeline/LifespanRow.tsx +730 -47
  254. package/src/panels/timeline/MarkerLane.tsx +46 -8
  255. package/src/panels/timeline/PlayButton.tsx +91 -0
  256. package/src/panels/timeline/PropertyPicker.tsx +254 -0
  257. package/src/panels/timeline/ShotRow.tsx +400 -0
  258. package/src/panels/timeline/SoundMode.tsx +1406 -0
  259. package/src/panels/timeline/TimelineDock.tsx +2338 -272
  260. package/src/panels/timeline/TransportVolume.tsx +163 -0
  261. package/src/panels/timeline/beatRuler.ts +74 -0
  262. package/src/panels/timeline/curveModel.ts +500 -0
  263. package/src/panels/timeline/duckUnderVoice.ts +68 -0
  264. package/src/panels/timeline/keyframeMenu.ts +138 -0
  265. package/src/panels/timeline/labelWidth.ts +49 -0
  266. package/src/panels/timeline/marquee.ts +155 -0
  267. package/src/panels/timeline/model.ts +1860 -199
  268. package/src/panels/timeline/snap.ts +125 -43
  269. package/src/panels/timeline/staggerPreview.tsx +92 -0
  270. package/src/panels/timeline/takeMeasure.ts +72 -0
  271. package/src/panels/timeline/useLive.ts +48 -0
  272. package/src/panels/timeline/viewport.ts +234 -0
  273. package/src/panels/toolbar/BackChip.tsx +116 -0
  274. package/src/panels/toolbar/CameraViewBar.tsx +118 -49
  275. package/src/panels/toolbar/CanvasBar.tsx +117 -0
  276. package/src/panels/toolbar/InsertButton.tsx +38 -0
  277. package/src/panels/toolbar/IsolationTrail.tsx +156 -0
  278. package/src/panels/toolbar/PanelToggles.tsx +86 -0
  279. package/src/panels/toolbar/ShadingBar.tsx +119 -26
  280. package/src/panels/toolbar/ToolDockBar.tsx +329 -80
  281. package/src/panels/toolbar/ToolFooter.tsx +102 -0
  282. package/src/panels/toolbar/ViewModeBar.tsx +247 -38
  283. package/src/panels/toolbar/WorkPlaneBar.tsx +94 -0
  284. package/src/panels/toolbar/useAddRows.tsx +326 -0
  285. package/src/panels/toolbar/useDismiss.ts +122 -0
  286. package/src/panels/versions/VersionHistoryPanel.tsx +274 -0
  287. package/src/panels/versions/versionModel.ts +103 -0
  288. package/src/panels/virtualWindow.ts +271 -0
  289. package/src/shotTokens.css +151 -60
  290. package/src/stage/FrustumGizmo.tsx +504 -84
  291. package/src/stage/GridPlane.tsx +225 -16
  292. package/src/stage/NodePreviews.tsx +628 -0
  293. package/src/stage/PerfHud.tsx +437 -47
  294. package/src/stage/SplitRenderer.tsx +816 -169
  295. package/src/stage/StageApp.tsx +4274 -486
  296. package/src/stage/StageRootBoundary.tsx +114 -0
  297. package/src/stage/StageTools.tsx +651 -151
  298. package/src/stage/TransformGizmo.tsx +696 -0
  299. package/src/stage/adaptivePresentation.ts +145 -0
  300. package/src/stage/bridgeClient.ts +2522 -41
  301. package/src/stage/exportWorker.ts +9 -0
  302. package/src/stage/feed.ts +305 -14
  303. package/src/stage/fontPreview.ts +176 -0
  304. package/src/stage/frustumGeometry.ts +319 -1
  305. package/src/stage/furnitureTokens.ts +33 -0
  306. package/src/stage/gizmoMath.ts +508 -16
  307. package/src/stage/gpuTimer.ts +276 -0
  308. package/src/stage/instanceModules.ts +194 -33
  309. package/src/stage/interactions/move.ts +24 -5
  310. package/src/stage/isolation.ts +291 -33
  311. package/src/stage/jsSelfProfiler.ts +325 -0
  312. package/src/stage/main.tsx +116 -16
  313. package/src/stage/modalState.ts +222 -23
  314. package/src/stage/moduleEpoch.ts +60 -0
  315. package/src/stage/motionPathStage.ts +279 -0
  316. package/src/stage/motionPreview.ts +74 -0
  317. package/src/stage/overlays/Overlays.tsx +1112 -159
  318. package/src/stage/overlays/Rulers.tsx +495 -0
  319. package/src/stage/panes.ts +159 -10
  320. package/src/stage/project.ts +68 -3
  321. package/src/stage/renderScaleController.ts +730 -0
  322. package/src/stage/rulers.ts +129 -0
  323. package/src/stage/sceneExtent.ts +75 -0
  324. package/src/stage/shading.ts +20 -0
  325. package/src/stage/splineStage.ts +632 -0
  326. package/src/stage/stagePerformanceTelemetry.ts +717 -0
  327. package/src/stage/stageTheme.ts +36 -0
  328. package/src/stage/stageWarmup.ts +11 -0
  329. package/src/stage/textAutoSize.ts +124 -0
  330. package/src/stage/tier1Export.ts +641 -0
  331. package/src/stage/tokenColor.ts +14 -9
  332. package/src/stage/view.ts +56 -9
  333. package/src/stage/workingView.ts +277 -33
  334. package/src/state/accountPrefs.ts +261 -0
  335. package/src/state/agentActions.ts +173 -15
  336. package/src/state/agentActivity.ts +198 -0
  337. package/src/state/agentQuestion.ts +239 -0
  338. package/src/state/bootOpen.ts +42 -0
  339. package/src/state/cameraVerbs.ts +139 -0
  340. package/src/state/clientId.ts +6 -1
  341. package/src/state/comments.ts +1099 -0
  342. package/src/state/context.tsx +19 -1
  343. package/src/state/editorPrefs.ts +220 -0
  344. package/src/state/errorReports.ts +121 -0
  345. package/src/state/fieldWrite.ts +161 -0
  346. package/src/state/fontRecents.ts +70 -0
  347. package/src/state/fonts.ts +656 -0
  348. package/src/state/graphClipboard.ts +414 -0
  349. package/src/state/imageThumbs.ts +216 -0
  350. package/src/state/insert.ts +444 -67
  351. package/src/state/isolationTrail.ts +92 -0
  352. package/src/state/keyMoves.ts +134 -0
  353. package/src/state/keyframeClipboard.ts +177 -0
  354. package/src/state/keymap.ts +1153 -117
  355. package/src/state/layoutPrefs.ts +272 -0
  356. package/src/state/layouts.ts +205 -0
  357. package/src/state/measureMedia.ts +167 -0
  358. package/src/state/menu.ts +2341 -28
  359. package/src/state/motionStamp.ts +7 -0
  360. package/src/state/panelSize.ts +375 -5
  361. package/src/state/pendingChanges.ts +119 -0
  362. package/src/state/playHold.ts +78 -0
  363. package/src/state/playbackPreparation.ts +562 -0
  364. package/src/state/posterCapture.ts +242 -0
  365. package/src/state/presence.ts +254 -0
  366. package/src/state/projectComponents.ts +160 -0
  367. package/src/state/renderEvents.ts +298 -18
  368. package/src/state/runtimeErrors.ts +46 -0
  369. package/src/state/selection.ts +12 -0
  370. package/src/state/shortcuts.ts +1606 -0
  371. package/src/state/sketchGuide.ts +51 -0
  372. package/src/state/splineRefs.ts +87 -0
  373. package/src/state/stageHealth.ts +259 -0
  374. package/src/state/stageWatchdog.ts +47 -0
  375. package/src/state/stillStage.ts +330 -0
  376. package/src/state/stillThumbs.ts +474 -0
  377. package/src/state/store.ts +23591 -2982
  378. package/src/state/storyboardModel.ts +528 -0
  379. package/src/state/sync.ts +1257 -0
  380. package/src/state/syncCache.ts +159 -0
  381. package/src/state/triangles.ts +13 -0
  382. package/src/state/types.ts +1698 -55
  383. package/src/state/unsavedGuard.ts +49 -0
  384. package/src/state/viewMode.ts +200 -0
  385. package/src/state/webmcp.ts +1128 -0
  386. package/src/state/workspace.ts +1147 -0
  387. package/src/styles/assets-tab.css +304 -0
  388. package/src/styles/canvas.css +545 -0
  389. package/src/styles/comments.css +1435 -0
  390. package/src/styles/controls.css +162 -0
  391. package/src/styles/dialogs.css +390 -0
  392. package/src/styles/insert-drawer.css +586 -0
  393. package/src/styles/inspector-board.css +207 -0
  394. package/src/styles/inspector-object.css +486 -0
  395. package/src/styles/inspector-shot.css +252 -0
  396. package/src/styles/inspector.css +360 -0
  397. package/src/styles/left-column.css +1628 -0
  398. package/src/styles/library-preview.css +575 -0
  399. package/src/styles/library.css +512 -0
  400. package/src/styles/modes/code.css +569 -0
  401. package/src/styles/modes/component.css +1561 -0
  402. package/src/styles/modes/material.css +1201 -0
  403. package/src/styles/modes/storyboard.css +518 -0
  404. package/src/styles/share-component.css +49 -0
  405. package/src/styles/shell.css +653 -0
  406. package/src/styles/shot-brief.css +360 -0
  407. package/src/styles/states.css +76 -0
  408. package/src/styles/sync.css +380 -0
  409. package/src/styles/timeline-graph.css +384 -0
  410. package/src/styles/timeline-meters.css +89 -0
  411. package/src/styles/timeline-rows.css +1959 -0
  412. package/src/styles/timeline-sound.css +1003 -0
  413. package/src/styles/timeline.css +754 -0
  414. package/src/styles/tool-footer.css +525 -0
  415. package/src/styles/topbar.css +886 -0
  416. package/src/styles/versions.css +262 -0
  417. package/src/styles.css +72 -2921
  418. package/src/tools.ts +108 -23
  419. package/dist/assets/index-B7notge7.css +0 -1
  420. package/dist/assets/index-BBE6Ta9L.js +0 -52
  421. package/src/panels/files/icons.tsx +0 -118
  422. package/src/panels/header/DocSwitcher.tsx +0 -85
  423. package/src/panels/header/TransportBar.tsx +0 -86
  424. package/src/panels/inspector/animate/BlockTiming.tsx +0 -341
  425. package/src/panels/inspector/animate/DuringBlocks.tsx +0 -85
  426. package/src/panels/inspector/animate/InOutPresets.tsx +0 -57
  427. package/src/panels/inspector/animate/ResolvedChannels.tsx +0 -150
  428. package/src/panels/inspector/animate/useBlocks.ts +0 -43
  429. package/src/panels/material/MaterialPicker.tsx +0 -172
  430. package/src/panels/timeline/SceneStrip.tsx +0 -145
  431. package/src/panels/toolbar/AddMenu.tsx +0 -230
  432. package/src/stage/MoveGizmo.tsx +0 -411
@@ -1,5 +1,7 @@
1
1
  // Only for CLONER layers, and absent otherwise — the same rule MeshSection and
2
- // TypographySection follow, for the same reason.
2
+ // TypographySection follow, for the same reason. `layout.ts` declares the
3
+ // ceiling; the LEAD-is-a-cloner withholding below is this file's, which is
4
+ // where `layout.ts` says a shape-of-selection rule belongs.
3
5
  //
4
6
  // Deliberately the SHORT list: how many, in what arrangement, and how they are
5
7
  // staggered in time. The step and jitter channels, the per-clone override map
@@ -15,10 +17,26 @@
15
17
  // about the frame budget, not a broken document. The hard cap IS a refusal, and
16
18
  // that one is the validator's.
17
19
  //
20
+ // ── EVERY NUMBER DRAGS LIVE ─────────────────────────────────────────────
21
+ //
22
+ // A number row is `ClonerNumber`: its typed value and its drag are ONE
23
+ // builder over each cloner's own block, so the clones move while the pointer
24
+ // is down, and a release lands the document its moves drew.
25
+ //
18
26
  // ponytail: no override editor. Per-clone edits are agent-authored today; build
19
27
  // the row when someone is actually art-directing one clone out of forty.
20
- import { InspectorRow, InspectorSection, NumberField, SegmentedControl } from "@dailies-studio/ui";
21
- import { CLONER_MODES, CLONE_FROMS, MAX_CLONES, cloneCountWarning } from "@dailies-studio/shot/doc";
28
+ import { Combobox, InspectorRow, InspectorSection, NumberField, SegmentedControl, wordOptions } from "@dailies-studio/ui";
29
+ import {
30
+ CLONER_MODES,
31
+ CLONE_FROMS,
32
+ MAX_CLONES,
33
+ MAX_SPLINE_ANGLE_DEG,
34
+ SPLINE_DOMAINS,
35
+ SPLINE_FORWARD_AXES,
36
+ SPLINE_ORIENTATIONS,
37
+ cloneCountWarning,
38
+ meshPrimitiveOf,
39
+ } from "@dailies-studio/shot/doc";
22
40
  import type {
23
41
  CloneFrom,
24
42
  ClonerConfig,
@@ -26,17 +44,33 @@ import type {
26
44
  ClonerMode,
27
45
  ClonerModeKind,
28
46
  Layer3D,
47
+ SplineDomain,
48
+ SplineForwardAxis,
49
+ SplineOrientation,
29
50
  } from "@dailies-studio/shot/doc";
30
- import { useSelectedLayers, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
51
+ import { useShotEditor } from "../../../state/context.js";
52
+ import { useSelectedLayers, useSelectionScrub, useSelectionWrite } from "../useDocWrite.js";
53
+ import { KeyframeDot } from "../KeyframeDot.js";
54
+ import { splineSourceIsSafe } from "../../../state/splineRefs.js";
31
55
 
32
56
  type ClonerBlock = ClonerLayer3D["cloner"];
57
+ type SplineClonerMode = Extract<ClonerMode, { kind: "spline" }>;
58
+
59
+ /** What a widget with no `mixed` prop of its own shows when the selection
60
+ * disagrees. `SegmentedControl` renders an unmatched value as "none of these
61
+ * selected" and `Combobox` falls back to its placeholder, which is the same
62
+ * statement in each one's own vocabulary. Both constants are
63
+ * `TypographySection`'s spelling; `StrokeSection` writes the empty value
64
+ * inline and has no combobox to place a placeholder in. */
65
+ const NO_SEGMENT = "";
66
+ const MIXED_PLACEHOLDER = "Mixed";
33
67
 
34
68
  /** The mode a kind switch lands on, with its own required fields filled in.
35
69
  * Switching arrangement must produce a VALID mode: a `{kind:"grid"}` with no
36
- * columns is refused on save, so a segmented control that could write one is a
37
- * control that only breaks documents. The numbers are the same defaults
70
+ * columns is refused on save, so a picker that could write one is a control
71
+ * that only breaks documents. The numbers are the same defaults
38
72
  * `add_layer` uses. */
39
- function defaultMode(kind: ClonerModeKind): ClonerMode {
73
+ function defaultMode(kind: ClonerModeKind, spline?: string): ClonerMode {
40
74
  switch (kind) {
41
75
  case "grid":
42
76
  return { kind, columns: 3, spacing: [240, 240] };
@@ -44,6 +78,8 @@ function defaultMode(kind: ClonerModeKind): ClonerMode {
44
78
  return { kind, radiusPx: 320 };
45
79
  case "scatter":
46
80
  return { kind, extent: [320, 320, 0] };
81
+ case "spline":
82
+ return spline === undefined ? { kind: "linear", step: [240, 0, 0] } : { kind, spline };
47
83
  default:
48
84
  return { kind: "linear", step: [240, 0, 0] };
49
85
  }
@@ -52,86 +88,229 @@ function defaultMode(kind: ClonerModeKind): ClonerMode {
52
88
  export function ClonerSection() {
53
89
  const write = useSelectionWrite();
54
90
  const layers = useSelectedLayers();
55
- const cloner = useSelectionValue((layer) => (layer.kind === "cloner" ? layer.cloner : undefined));
56
- if (!layers.some((l) => l.kind === "cloner") || cloner.value === undefined) return null;
91
+ const doc = useShotEditor((state) => state.doc);
92
+ const sceneId = useShotEditor((state) => state.sceneId);
93
+ const editingDef = useShotEditor((state) => state.editingDef);
94
+ // ── THE CLONER LAYERS THEMSELVES, NOT `useSelectionValue` ───────────────
95
+ //
96
+ // That hook compares members with `Object.is` and `layer.cloner` is an
97
+ // OBJECT, so two selected cloners were "Mixed" by identity — every row wore
98
+ // the placeholder over numbers the author could see agree, while the writes
99
+ // below still fanned out to both. The hook reports such a read on
100
+ // `console.error`, which is how this was found. Mixed is asked per FIELD by
101
+ // `mixedOn` instead, the shape `MeshSection` and `DeviceSection` already use.
102
+ //
103
+ // Data gate, not a kind gate: see layout.ts. The LEAD is the subject, so a
104
+ // selection led in document order by something that is not a cloner has no
105
+ // arrangement to show — `inspectorLayout.test.tsx` pins that withholding, and
106
+ // the rule survives the move off the hook because the lead is still what a
107
+ // row displays.
108
+ const lead = layers[0];
109
+ if (lead === undefined || lead.kind !== "cloner") return null;
57
110
 
58
- const c = cloner.value;
111
+ const c = lead.cloner;
112
+ const cloners = layers.filter((l): l is ClonerLayer3D => l.kind === "cloner");
59
113
  const patch = (next: (block: ClonerBlock) => ClonerBlock): void =>
60
114
  write((layer) => (layer.kind === "cloner" ? ({ ...layer, cloner: next(layer.cloner) } as Layer3D) : layer));
61
115
 
62
- const set = <K extends keyof ClonerBlock>(key: K, value: ClonerBlock[K]): void =>
63
- patch((block) => ({ ...block, [key]: value }));
116
+ /** ONE ROW, ONE QUESTION: does THIS field disagree across the selected
117
+ * cloners? Every row reads the scalar it displays, so a row that agrees shows
118
+ * its value however many layers are selected. */
119
+ const mixedOn = <T,>(read: (block: ClonerBlock) => T): boolean =>
120
+ cloners.some((l) => !Object.is(read(l.cloner), read(c)));
121
+
122
+ /** The same question for a row only the SPLINE arrangement has. A selected
123
+ * cloner arranged some other way reads `undefined` here, which disagrees with
124
+ * any number — the honest answer, because that layer has no such field for
125
+ * the fan-out to land on either. */
126
+ const mixedOnSpline = <T,>(read: (mode: SplineClonerMode) => T): boolean =>
127
+ mixedOn((block) => (block.mode?.kind === "spline" ? read(block.mode) : undefined));
128
+
129
+ /** One spline-mode field, on each cloner's OWN base mode, and only on
130
+ * cloners arranged along a spline (`withSplineMode`). */
131
+ const setSpline = (edit: (mode: SplineClonerMode) => SplineClonerMode): void =>
132
+ patch((block) => withSplineMode(block, edit));
133
+
134
+ /** One number of the CURRENT arrangement (F16), written on each cloner's OWN
135
+ * mode and only on cloners arranged the same way, so a fan-out never copies
136
+ * the lead's arrangement onto another cloner. Mixed is per field, as the
137
+ * spline rows ask it. */
138
+ const modeNumber = <K extends ClonerModeKind>(
139
+ kind: K,
140
+ label: string,
141
+ read: (mode: Extract<ClonerMode, { kind: K }>) => number,
142
+ edit: (mode: Extract<ClonerMode, { kind: K }>, next: number) => ClonerMode,
143
+ field: { min?: number; unit?: string } = {},
144
+ ) => {
145
+ const own = (block: ClonerBlock) =>
146
+ block.mode?.kind === kind ? (block.mode as Extract<ClonerMode, { kind: K }>) : undefined;
147
+ return (
148
+ <ClonerNumber
149
+ key={label}
150
+ value={read(own(c)!)}
151
+ mixed={mixedOn((block) => { const mode = own(block); return mode === undefined ? undefined : read(mode); })}
152
+ label={label}
153
+ ariaLabel={`Cloner ${label.toLowerCase()}`}
154
+ step={1}
155
+ {...(field.min === undefined ? {} : { min: field.min })}
156
+ {...(field.unit === undefined ? {} : { unit: field.unit })}
157
+ edit={(block, next) => { const mode = own(block); return mode === undefined ? block : { ...block, mode: edit(mode, next) }; }}
158
+ />
159
+ );
160
+ };
64
161
 
65
162
  const stagger = c.stagger;
66
- const warning = cloneCountWarning(c.count);
163
+ // ── THE WARNING BELONGS TO A NUMBER THAT IS ON SCREEN ──────────────────
164
+ //
165
+ // It names a count ("120 clones is a lot to draw"), so it can only be said
166
+ // about a count the author can see. With two cloners selected the Count
167
+ // field reads "Mixed" and shows no number at all, and a sentence underneath
168
+ // naming the LEAD layer's 120 would be pointing at a figure nothing in the
169
+ // panel displays — worse than silence, because it reads as a fact about the
170
+ // selection rather than about one member of it.
171
+ const countMixed = mixedOn((block) => block.count);
172
+ const warning = countMixed ? null : cloneCountWarning(c.count);
173
+ const home = doc === null
174
+ ? undefined
175
+ : editingDef === null
176
+ ? (sceneId === null ? undefined : doc.scenesById[sceneId])
177
+ : doc.components?.[editingDef];
178
+ const selected = new Set(layers.map((layer) => layer.id));
179
+ const baseFollowOwnsPath = layers.some(
180
+ (layer) => layer.kind === "cloner" && layer.splineFollow !== undefined,
181
+ );
182
+ const splines = Object.values(home?.layersById ?? {})
183
+ .filter((layer) =>
184
+ layer.kind === "mesh" &&
185
+ meshPrimitiveOf(layer.mesh)?.kind === "spline" &&
186
+ !selected.has(layer.id) &&
187
+ splineSourceIsSafe(home?.layersById ?? {}, [...selected], layer.id, "cloner"),
188
+ )
189
+ .map((layer) => ({ value: layer.id, label: layer.label }));
190
+ const modeOptions = wordOptions(
191
+ CLONER_MODES.filter(
192
+ (kind) =>
193
+ kind !== "spline" ||
194
+ c.mode?.kind === "spline" ||
195
+ (!baseFollowOwnsPath && splines.length > 0),
196
+ ),
197
+ );
198
+ const splineMode = c.mode?.kind === "spline" ? c.mode : null;
199
+ const splineMixed = splineMode !== null && mixedOnSpline((mode) => mode.spline);
200
+ const modeMixed = mixedOn((block) => block.mode?.kind ?? "linear");
201
+ const fromMixed = mixedOn((block) => block.stagger?.from ?? "start");
67
202
 
68
203
  return (
69
- <InspectorSection title="Cloner" persistKey="shot:cloner">
70
- <InspectorRow label="Count">
71
- <NumberField
72
- value={c.count}
73
- mixed={cloner.mixed}
74
- ariaLabel="Clone count"
75
- step={1}
76
- min={1}
77
- // CLAMPED at the field, so the cap is a thing the control cannot
78
- // exceed rather than a save that fails after the fact. The validator
79
- // still refuses it, for every other way in.
80
- max={MAX_CLONES}
81
- onCommit={(next) => set("count", Math.min(MAX_CLONES, Math.max(1, Math.round(next))))}
82
- />
83
- </InspectorRow>
204
+ <InspectorSection className="insp-object" title="Cloner" persistKey="shot:cloner">
205
+ {/* The name is the FIELD's, not the row's: the label rides inside the
206
+ box, and the whole box is one drag surface. CLAMPED at the field
207
+ (`MAX_CLONES`), so the cap is a thing the control cannot exceed
208
+ rather than a save that fails after the fact. The validator still
209
+ refuses it, for every other way in. */}
210
+ <ClonerNumber
211
+ value={c.count}
212
+ mixed={countMixed}
213
+ label="Count"
214
+ ariaLabel="Clone count"
215
+ step={1}
216
+ min={1}
217
+ max={MAX_CLONES}
218
+ edit={(block, next) => ({ ...block, count: Math.round(next) })}
219
+ />
84
220
  {warning === null ? null : (
85
- /* `inspector-empty` is the panel's muted-paragraph style and this is a
86
- muted paragraph. A `.inspector-warning` would be the same three
87
- declarations under a name that promised a colour it does not have. */
88
- <p className="inspector-empty" data-testid="cloner-count-warning">
221
+ /* A warning, in the panel's warning register: the save still goes
222
+ through, but the frame budget will feel it. */
223
+ <div className="insp-notice is-warn" data-testid="cloner-count-warning">
89
224
  {warning}
90
- </p>
225
+ </div>
91
226
  )}
92
227
 
228
+ {/* A COMBOBOX, as the house line has it past four options: five
229
+ arrangements as segments left each word 9px at the inspector's floor
230
+ and 14px at its default width, run together into one string. */}
93
231
  <InspectorRow label="Arrange">
94
- <SegmentedControl
232
+ <Combobox
95
233
  ariaLabel="Cloner mode"
96
- value={c.mode?.kind ?? "linear"}
97
- options={CLONER_MODES.map((kind) => ({ value: kind, label: kind }))}
98
- onChange={(next) => set("mode", defaultMode(next as ClonerModeKind))}
234
+ value={modeMixed ? NO_SEGMENT : (c.mode?.kind ?? "linear")}
235
+ placeholder={modeMixed ? MIXED_PLACEHOLDER : "Arrange"}
236
+ options={modeOptions}
237
+ // A cloner already arranged this way keeps its own settings.
238
+ onChange={(next) =>
239
+ patch((block) =>
240
+ (block.mode?.kind ?? "linear") === next
241
+ ? block
242
+ : { ...block, mode: defaultMode(next as ClonerModeKind, splines[0]?.value) },
243
+ )
244
+ }
99
245
  />
100
246
  </InspectorRow>
247
+ {c.mode?.kind === "grid" ? [
248
+ modeNumber("grid", "Columns", (m) => m.columns, (m, n) => ({ ...m, columns: Math.max(1, Math.round(n)) }), { min: 1 }),
249
+ modeNumber("grid", "Gap X", (m) => m.spacing[0], (m, n) => ({ ...m, spacing: withAxis(m.spacing, 0, n) })),
250
+ modeNumber("grid", "Gap Y", (m) => m.spacing[1], (m, n) => ({ ...m, spacing: withAxis(m.spacing, 1, n) })),
251
+ ] : null}
252
+ {c.mode?.kind === "radial" ? [
253
+ modeNumber("radial", "Radius", (m) => m.radiusPx, (m, n) => ({ ...m, radiusPx: n })),
254
+ modeNumber("radial", "Start", (m) => m.startDeg ?? 0, (m, n) => ({ ...m, startDeg: n }), { unit: "°" }),
255
+ modeNumber("radial", "Sweep", (m) => m.sweepDeg ?? 360, (m, n) => ({ ...m, sweepDeg: n }), { unit: "°" }),
256
+ ] : null}
257
+ {c.mode?.kind === "scatter"
258
+ ? AXES.map((axis, i) => modeNumber("scatter", `Extent ${axis}`, (m) => m.extent[i]!, (m, n) => ({ ...m, extent: withAxis(m.extent, i, n) })))
259
+ : null}
260
+ {c.mode?.kind === "linear"
261
+ ? AXES.map((axis, i) => modeNumber("linear", `Step ${axis}`, (m) => m.step[i]!, (m, n) => ({ ...m, step: withAxis(m.step, i, n) })))
262
+ : null}
263
+ {baseFollowOwnsPath && splineMode === null ? (
264
+ <p className="insp-note">Remove spline follow before using spline distribution.</p>
265
+ ) : null}
266
+
267
+ {splineMode === null ? null : (
268
+ <>
269
+ <InspectorRow label="Spline">
270
+ <Combobox options={splines} value={splineMixed ? NO_SEGMENT : splineMode.spline} ariaLabel="Cloner spline" placeholder={splineMixed ? MIXED_PLACEHOLDER : "Choose spline"} onChange={(spline) => setSpline((mode) => ({ ...mode, spline }))} />
271
+ </InspectorRow>
272
+ <InspectorRow label="Domain">
273
+ <SegmentedControl ariaLabel="Cloner spline domain" value={mixedOnSpline((mode) => mode.domain ?? "full") ? NO_SEGMENT : (splineMode.domain ?? "full")} options={wordOptions(SPLINE_DOMAINS)} onChange={(domain) => setSpline((mode) => ({ ...mode, domain: domain as SplineDomain }))} />
274
+ </InspectorRow>
275
+ <ClonerNumber path="cloner.mode.start" label="Start" value={splineMode.start ?? 0} mixed={mixedOnSpline((mode) => mode.start ?? 0)} min={0} max={1} step={0.01} edit={(block, start) => withSplineMode(block, (mode) => ({ ...mode, start }))} />
276
+ <ClonerNumber path="cloner.mode.end" label="End" value={splineMode.end ?? 1} mixed={mixedOnSpline((mode) => mode.end ?? 1)} min={0} max={1} step={0.01} edit={(block, end) => withSplineMode(block, (mode) => ({ ...mode, end }))} />
277
+ <ClonerNumber path="cloner.mode.progressOffset" label="Offset" value={splineMode.progressOffset ?? 0} mixed={mixedOnSpline((mode) => mode.progressOffset ?? 0)} step={0.01} edit={(block, progressOffset) => withSplineMode(block, (mode) => ({ ...mode, progressOffset }))} />
278
+ <InspectorRow label="Orient">
279
+ <SegmentedControl ariaLabel="Cloner spline orientation" value={mixedOnSpline((mode) => mode.orientation ?? "path") ? NO_SEGMENT : (splineMode.orientation ?? "path")} options={wordOptions(SPLINE_ORIENTATIONS)} onChange={(orientation) => setSpline((mode) => ({ ...mode, orientation: orientation as SplineOrientation }))} />
280
+ </InspectorRow>
281
+ {(splineMode.orientation ?? "path") === "path" ? (
282
+ <>
283
+ <InspectorRow label="Forward">
284
+ <Combobox options={SPLINE_FORWARD_AXES.map((value) => ({ value, label: value.toUpperCase() }))} value={mixedOnSpline((mode) => mode.forwardAxis ?? "+x") ? NO_SEGMENT : (splineMode.forwardAxis ?? "+x")} ariaLabel="Cloner forward axis" placeholder={mixedOnSpline((mode) => mode.forwardAxis ?? "+x") ? MIXED_PLACEHOLDER : "Axis"} onChange={(forwardAxis) => setSpline((mode) => ({ ...mode, forwardAxis: forwardAxis as SplineForwardAxis }))} />
285
+ </InspectorRow>
286
+ <ClonerNumber path="cloner.mode.bankDeg" label="Bank" value={splineMode.bankDeg ?? 0} mixed={mixedOnSpline((mode) => mode.bankDeg ?? 0)} min={-MAX_SPLINE_ANGLE_DEG} max={MAX_SPLINE_ANGLE_DEG} step={1} unit="°" edit={(block, bankDeg) => withSplineMode(block, (mode) => ({ ...mode, bankDeg }))} />
287
+ </>
288
+ ) : null}
289
+ </>
290
+ )}
101
291
 
102
292
  {/* The stagger's SPACING, as `each` — the gap between clones. `span`
103
293
  expresses the same thing as a total and the two contradict each other
104
294
  the moment the count changes, so the panel offers exactly one of them
105
295
  and writing it CLEARS the other rather than saving a document the
106
296
  validator refuses. */}
107
- <InspectorRow label="Stagger">
108
- <NumberField
109
- value={stagger?.each ?? 0}
110
- mixed={cloner.mixed}
111
- ariaLabel="Stagger each"
112
- unit="f"
113
- step={1}
114
- onCommit={(next) =>
115
- patch((block) => {
116
- const each = Math.round(next);
117
- if (each === 0) {
118
- // Absent is how "no stagger" is spelled everywhere in this
119
- // lane. Writing `each: 0` would be a second way to say it.
120
- const { stagger: _drop, ...rest } = block;
121
- return rest as ClonerConfig;
122
- }
123
- const { span: _dropSpan, ...keep } = block.stagger ?? {};
124
- return { ...block, stagger: { ...keep, each } };
125
- })
126
- }
127
- />
128
- </InspectorRow>
297
+ <ClonerNumber
298
+ value={stagger?.each ?? 0}
299
+ mixed={mixedOn((block) => block.stagger?.each ?? 0)}
300
+ label="Stagger"
301
+ ariaLabel="Stagger each"
302
+ unit="f"
303
+ step={1}
304
+ edit={withStaggerEach}
305
+ />
129
306
 
307
+ {/* Six orders: a combobox, as Arrange is. */}
130
308
  <InspectorRow label="From">
131
- <SegmentedControl
309
+ <Combobox
132
310
  ariaLabel="Stagger from"
133
- value={stagger?.from ?? "start"}
134
- options={CLONE_FROMS.map((from) => ({ value: from, label: from }))}
311
+ value={fromMixed ? NO_SEGMENT : (stagger?.from ?? "start")}
312
+ placeholder={fromMixed ? MIXED_PLACEHOLDER : "From"}
313
+ options={wordOptions(CLONE_FROMS)}
135
314
  onChange={(next) =>
136
315
  patch((block) => ({ ...block, stagger: { ...block.stagger, from: next as CloneFrom } }))
137
316
  }
@@ -140,3 +319,85 @@ export function ClonerSection() {
140
319
  </InspectorSection>
141
320
  );
142
321
  }
322
+
323
+ const AXES = ["X", "Y", "Z"] as const;
324
+
325
+ /** A copy of a spacing, step or extent vector with one axis replaced. */
326
+ function withAxis<V extends number[]>(vec: V, axis: number, next: number): V {
327
+ return vec.map((value, i) => (i === axis ? next : value)) as V;
328
+ }
329
+
330
+ /** A spline-mode edit on ONE cloner's own base mode, and only when it is
331
+ * arranged along a spline. Spreading the lead's painted mode instead moved
332
+ * every cloner onto the lead's spline and baked its sampled values. */
333
+ function withSplineMode(block: ClonerBlock, edit: (mode: SplineClonerMode) => SplineClonerMode): ClonerBlock {
334
+ return block.mode?.kind === "spline" ? { ...block, mode: edit(block.mode) } : block;
335
+ }
336
+
337
+ /** The stagger's spacing, which CLEARS `span`: the two contradict each other
338
+ * the moment the count changes. 0 deletes the stagger: absent is how "no
339
+ * stagger" is spelled everywhere in this lane, and `each: 0` would be a
340
+ * second way to say it. */
341
+ function withStaggerEach(block: ClonerBlock, next: number): ClonerBlock {
342
+ const each = Math.round(next);
343
+ if (each === 0) {
344
+ const { stagger: _drop, ...rest } = block;
345
+ return rest as ClonerConfig;
346
+ }
347
+ const { span: _dropSpan, ...keep } = block.stagger ?? {};
348
+ return { ...block, stagger: { ...keep, each } };
349
+ }
350
+
351
+ /**
352
+ * One number of a cloner, as one box: the typed value and the drag are ONE
353
+ * builder (`edit`) over each cloner's own block, re-derived from the gesture's
354
+ * base on every capture, so a drag moves the clones live and never compounds.
355
+ * `path` is the track it keys, which puts its diamond in the box.
356
+ */
357
+ function ClonerNumber({
358
+ label,
359
+ ariaLabel,
360
+ value,
361
+ mixed,
362
+ unit,
363
+ min,
364
+ max,
365
+ step,
366
+ path,
367
+ edit,
368
+ }: {
369
+ label: string;
370
+ ariaLabel?: string;
371
+ value: number;
372
+ mixed: boolean;
373
+ unit?: string;
374
+ min?: number;
375
+ max?: number;
376
+ step: number;
377
+ path?: string;
378
+ edit: (block: ClonerBlock, value: number) => ClonerBlock;
379
+ }) {
380
+ const write = useSelectionWrite();
381
+ const build = (layer: Layer3D, next: number): Layer3D =>
382
+ layer.kind === "cloner" ? ({ ...layer, cloner: edit(layer.cloner, next) } as Layer3D) : layer;
383
+ const scrub = useSelectionScrub((layer, raw) => build(layer, Number(raw)));
384
+ return (
385
+ <InspectorRow>
386
+ <NumberField
387
+ value={value}
388
+ mixed={mixed}
389
+ label={label}
390
+ ariaLabel={ariaLabel ?? label}
391
+ {...(unit === undefined ? {} : { unit })}
392
+ {...(min === undefined ? {} : { min })}
393
+ {...(max === undefined ? {} : { max })}
394
+ step={step}
395
+ trailing={path === undefined ? undefined : <KeyframeDot path={path} label={label} />}
396
+ onCommit={(next) => write((layer) => build(layer, next))}
397
+ onScrubStart={scrub.start}
398
+ onScrub={scrub.move}
399
+ onScrubEnd={scrub.end}
400
+ />
401
+ </InspectorRow>
402
+ );
403
+ }
@@ -5,40 +5,64 @@
5
5
  // a component a contract rather than a copy. A definition that exposes nothing
6
6
  // gets a panel that says so, because "no controls" and "this component has no
7
7
  // controls" read very differently to the author who just made one.
8
- import { InspectorRow, InspectorSection, PropControl } from "@dailies-studio/ui";
9
- import { componentDefsOf, instancesOf } from "@dailies-studio/shot/doc";
8
+ //
9
+ // ── ONE ROW FOR ONE USE'S PROP ──────────────────────────────────────────
10
+ //
11
+ // An override is this placement's value for one of the definition's props,
12
+ // which is exactly what Components' THIS USE panel shows for the same layer.
13
+ // So the rows ARE that panel's rows (`UsePropRow`): the control each prop's
14
+ // target describes (`exposedRowOf`, so a published 0..1 opacity is a bounded
15
+ // field and a published enum keeps its options), the override read the way
16
+ // the renderer writes it (`appliedOverride`: under a `renamedFrom` key too,
17
+ // and a value it quarantines read as none), the master read off what the
18
+ // definition draws (`masterValueOf`, the interior layer, never a stale
19
+ // published default), and one write (`writeUseValue`), by which typing the
20
+ // master's value stores nothing and the instance goes on following its
21
+ // definition.
22
+ import { InspectorRow, InspectorSection } from "@dailies-studio/ui";
23
+ import { appliedOverride, componentDefsOf, instancesOf } from "@dailies-studio/shot/doc";
10
24
  import type { ComponentLayer3D } from "@dailies-studio/shot/doc";
11
- import type { ControlSpec } from "@dailies-studio/contract";
12
- import type { Value } from "@dailies-studio/engine";
25
+ import { useMemo } from "react";
13
26
  import { useShotEditor, useStore } from "../../../state/context.js";
14
- import { NO_SCRUB, useSelectedLayers } from "../useDocWrite.js";
27
+ import { layerUseKey, masterValueOf } from "../../../state/workspace.js";
28
+ import { UsePropRow } from "../../studio/ThisUsePanel.js";
29
+ import { exposedRowOf, projectAssetsOf } from "../propValue.js";
30
+ import { useSelectedLayers } from "../useDocWrite.js";
15
31
 
16
32
  export function ComponentSection() {
17
33
  const store = useStore();
18
34
  const doc = useShotEditor((s) => s.doc);
35
+ const contracts = useShotEditor((s) => s.contracts);
36
+ // THE CONTAINER the layer lives in, the store's `layerContainer` rule: a
37
+ // component placed inside another definition is a use of that definition.
38
+ const container = useShotEditor((s) => s.editingDef ?? s.sceneId);
19
39
  const layers = useSelectedLayers();
40
+ const assetTable = doc?.assets;
41
+ const assets = useMemo(() => projectAssetsOf(assetTable), [assetTable]);
20
42
  // ONE instance, not a mixed selection: overrides are per definition, and two
21
43
  // instances of two different definitions have no shared vocabulary at all.
22
- const instance = layers.length === 1 && layers[0]?.kind === "component" ? layers[0] : null;
23
- if (doc === null || instance === null) return null;
24
- const def = componentDefsOf(doc)[(instance as ComponentLayer3D).component.def];
44
+ const instance = layers.length === 1 && layers[0]?.kind === "component" ? (layers[0] as ComponentLayer3D) : null;
45
+ if (doc === null || instance === null || container === null) return null;
46
+ const def = componentDefsOf(doc)[instance.component.def];
25
47
 
26
48
  if (def === undefined) {
27
49
  return (
28
- <InspectorSection title="Component" persistKey="shot:component">
29
- <p className="inspector-note">
50
+ <InspectorSection className="insp-object" title="Component" persistKey="shot:component">
51
+ <p className="insp-note">
30
52
  This places a component that is not in this document. Its definition
31
- was probably deleted — undo, or delete this layer.
53
+ was probably deleted. Undo, or delete this layer.
32
54
  </p>
33
55
  </InspectorSection>
34
56
  );
35
57
  }
36
58
 
37
- const overrides = (instance as ComponentLayer3D).component.overrides;
59
+ const overrides = instance.component.overrides;
38
60
  const count = instancesOf(doc, def.id).length;
61
+ const useKey = layerUseKey(container, instance.id);
62
+ const use = { def: def.id };
39
63
 
40
64
  return (
41
- <InspectorSection title="Component" persistKey="shot:component">
65
+ <InspectorSection className="insp-object" title="Component" persistKey="shot:component">
42
66
  <InspectorRow label="Definition">
43
67
  <button
44
68
  type="button"
@@ -51,43 +75,26 @@ export function ComponentSection() {
51
75
  {def.name}
52
76
  </button>
53
77
  </InspectorRow>
54
- <p className="inspector-note">
55
- {count === 1 ? "The only one placed." : `${count} placed — editing it changes all of them.`}
78
+ <p className="insp-note">
79
+ {count === 1 ? "The only one placed." : `${count} placed. Editing it changes all of them.`}
56
80
  </p>
57
81
 
58
82
  {def.exposed.length === 0 ? (
59
- <p className="inspector-note">
83
+ <p className="insp-empty">
60
84
  Nothing is exposed yet. Open the definition and expose a field to make
61
85
  it overridable here.
62
86
  </p>
63
87
  ) : (
64
- def.exposed.map((prop) => {
65
- const value = overrides[prop.key];
66
- return (
67
- <InspectorRow key={prop.key} label={prop.label}>
68
- <PropControl
69
- spec={{ key: prop.key, label: prop.label, kind: prop.kind, default: prop.default } as ControlSpec}
70
- value={value ?? prop.default}
71
- commit={(next) => store.setComponentOverride(instance.id, prop.key, next as Value)}
72
- scrub={NO_SCRUB}
73
- />
74
- {/* The override's own reset. An instance that says nothing FOLLOWS
75
- its definition, so clearing is not "set it back to today's
76
- default" — it is "stop having an opinion", and the two differ
77
- the moment the definition changes. */}
78
- <button
79
- type="button"
80
- className="layer-flag"
81
- aria-label={`Reset ${prop.label}`}
82
- title="Follow the component again"
83
- disabled={value === undefined}
84
- onClick={() => store.setComponentOverride(instance.id, prop.key, null)}
85
- >
86
- ↺
87
- </button>
88
- </InspectorRow>
89
- );
90
- })
88
+ def.exposed.map((prop) => (
89
+ <UsePropRow
90
+ key={prop.key}
91
+ useKey={useKey}
92
+ row={exposedRowOf(prop, def, doc.materials, contracts)}
93
+ stored={appliedOverride(def, prop, overrides)?.value}
94
+ master={masterValueOf(doc, contracts, use, prop.key)}
95
+ assets={assets}
96
+ />
97
+ ))
91
98
  )}
92
99
  </InspectorSection>
93
100
  );