@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,441 @@
1
+ // The graph, as React Flow wants it — and the memo that makes a keystroke cost
2
+ // ONE card.
3
+ //
4
+ // ── THE DEFECT THIS MODULE EXISTS FOR ───────────────────────────────────
5
+ //
6
+ // v1 built the node array inside the canvas with a `useMemo` over the whole
7
+ // graph, and every node's `data` was a fresh object literal carrying the WHOLE
8
+ // graph. So typing one digit into one Float field rebuilt every card's props,
9
+ // and React Flow re-rendered all of them: at forty nodes a held arrow key was
10
+ // visibly behind the author's finger, and the profile was ninety-five percent
11
+ // cards that had not changed.
12
+ //
13
+ // The fix is not "memo the components". A `memo()` compares props, and props
14
+ // that are rebuilt every render compare unequal every render. The fix is to
15
+ // stop rebuilding them: a WeakMap keyed on the GRAPH NODE OBJECT holds the flow
16
+ // node it produced, beside a key naming everything about the card that is NOT
17
+ // the node itself (its wires, its selection, its group's origin, its problem).
18
+ // The store writes graphs with structural sharing — `{...graph, nodesById: {…}}`
19
+ // — so an untouched node keeps its identity across an edit, hits the cache, and
20
+ // its React Flow node object comes back `===`. React Flow's own reconciler then
21
+ // skips it, and so does React.
22
+ //
23
+ // `materialGraphCanvas.test.tsx` pins that directly: build twice across a
24
+ // one-node edit and assert the OTHER node's flow object is identity-equal.
25
+ //
26
+ // ── WHAT MAY NOT GO IN `data` ───────────────────────────────────────────
27
+ //
28
+ // Anything that changes for a reason unrelated to this card. The graph itself
29
+ // is the obvious one and is gone. Node PREVIEWS are the subtle one: a thumbnail
30
+ // arrives asynchronously for a handful of cards, and threading it through here
31
+ // would invalidate the memo for every card each time one landed. They ride a
32
+ // React context instead (`NodePreview.tsx`), which re-renders the previews and
33
+ // nothing else.
34
+ import type { Edge, Node } from "@xyflow/react";
35
+ import { materialGraphNodeSockets, materialGraphNodeSpec } from "@dailies-studio/shot/doc";
36
+ import type {
37
+ MaterialGraph3D,
38
+ MaterialGraphNode,
39
+ MaterialGraphNodeSpec,
40
+ MaterialGraphParameter,
41
+ MaterialGraphProblem,
42
+ MaterialGraphResolvedType,
43
+ MaterialGraphSocketSpec,
44
+ MaterialGraphTypeIndex,
45
+ ShotAsset,
46
+ } from "@dailies-studio/shot/doc";
47
+ import type { MaterialGraphSelection } from "../../state/types.js";
48
+
49
+ /** The four renderers. `group` and `comment` are inert boxes, `reroute` is a
50
+ * dot with one handle a side, and everything else is a compute card. */
51
+ export type MaterialFlowNodeType = "material" | "materialGroup" | "materialComment" | "materialReroute";
52
+
53
+ /** The two dropdown vocabularies every card shares. Built ONCE per graph and
54
+ * handed to all of them by identity, so forty cards hold one array rather than
55
+ * forty copies of the same options. */
56
+ export interface MaterialChoices {
57
+ images: readonly { value: string; label: string }[];
58
+ parameters: readonly { value: string; label: string }[];
59
+ }
60
+
61
+ export interface MaterialFlowData extends Record<string, unknown> {
62
+ /** Carried rather than read from the store at commit time, for the reason
63
+ * every graph verb takes them as parameters: a card built inside a function
64
+ * must write to that function even after the canvas has climbed out. */
65
+ materialId: string;
66
+ path: readonly string[];
67
+ node: MaterialGraphNode;
68
+ spec: MaterialGraphNodeSpec;
69
+ /** Resolved PER NODE — a subgraph call's sockets are its function's
70
+ * signature, not the static spec's empty lists. */
71
+ inputs: readonly MaterialGraphSocketSpec[];
72
+ outputs: readonly MaterialGraphSocketSpec[];
73
+ /** `"in:<socket>"` / `"out:<socket>"` for every socket a wire lands on. The
74
+ * card needs it for the ink and the widgets need it to know an input is
75
+ * driven and its literal no longer says anything. */
76
+ connected: ReadonlySet<string>;
77
+ parameter: MaterialGraphParameter | undefined;
78
+ assetLabel: string | undefined;
79
+ /** What this card actually produces, once the wires upstream are resolved.
80
+ * Null while the type index cannot say, which is what a cycle or a dangling
81
+ * wire looks like from here. */
82
+ outputType: MaterialGraphResolvedType | null;
83
+ /** The validator's sentence about this node, or null. */
84
+ problem: string | null;
85
+ choices: MaterialChoices;
86
+ }
87
+
88
+ export type MaterialFlowNode = Node<MaterialFlowData, MaterialFlowNodeType>;
89
+
90
+ export interface MaterialFlowInput {
91
+ graph: MaterialGraph3D;
92
+ materialId: string;
93
+ /** The scope trail on screen. Only nodes whose `scope` is its last element
94
+ * are drawn — a function body is a different canvas. */
95
+ path: readonly string[];
96
+ assets: Readonly<Record<string, ShotAsset>>;
97
+ selection: MaterialGraphSelection;
98
+ problems: readonly MaterialGraphProblem[];
99
+ /** Built once by the caller, which needs it for `isValidConnection` anyway. */
100
+ types: MaterialGraphTypeIndex;
101
+ }
102
+
103
+ /** The nodes belonging to one scope. Exported because the palette, the
104
+ * scheduler and Select All all need the same answer. */
105
+ export function nodesInScope(graph: MaterialGraph3D, path: readonly string[]): MaterialGraphNode[] {
106
+ const scope = path.length === 0 ? undefined : path[path.length - 1];
107
+ return Object.values(graph.nodesById).filter((node) => (node.scope ?? undefined) === scope);
108
+ }
109
+
110
+ /** Which renderer draws this kind. */
111
+ function flowTypeOf(node: MaterialGraphNode): MaterialFlowNodeType {
112
+ if (node.kind === "group") return "materialGroup";
113
+ if (node.kind === "comment") return "materialComment";
114
+ if (node.kind === "reroute") return "materialReroute";
115
+ return "material";
116
+ }
117
+
118
+ // ── the choices memo ────────────────────────────────────────────────────────
119
+ //
120
+ // A one-entry cache keyed on the two SOURCE objects. Both are replaced by
121
+ // identity when their contents change (the store never mutates in place), so
122
+ // identity is the whole test — and one entry is enough because exactly one
123
+ // graph is on screen.
124
+ let choicesMemo: {
125
+ assets: object;
126
+ parameters: object;
127
+ value: MaterialChoices;
128
+ token: number;
129
+ } | null = null;
130
+ let choicesSerial = 0;
131
+
132
+ function choicesFor(
133
+ assets: Readonly<Record<string, ShotAsset>>,
134
+ parameters: Readonly<Record<string, MaterialGraphParameter>>,
135
+ ): { value: MaterialChoices; token: number } {
136
+ if (choicesMemo !== null && choicesMemo.assets === assets && choicesMemo.parameters === parameters) {
137
+ return { value: choicesMemo.value, token: choicesMemo.token };
138
+ }
139
+ choicesSerial += 1;
140
+ const value: MaterialChoices = {
141
+ images: Object.values(assets)
142
+ .filter((asset) => asset.kind === "image")
143
+ .map((asset) => ({ value: asset.id, label: asset.path.split("/").pop() ?? asset.id }))
144
+ .sort((a, b) => a.label.localeCompare(b.label)),
145
+ parameters: Object.values(parameters)
146
+ .sort((a, b) => a.label.localeCompare(b.label))
147
+ .map((parameter) => ({ value: parameter.id, label: `${parameter.label} · ${parameter.type}` })),
148
+ };
149
+ choicesMemo = { assets, parameters, value, token: choicesSerial };
150
+ return { value, token: choicesSerial };
151
+ }
152
+
153
+ /** The suites build several unrelated documents in one process, and a stale
154
+ * one-entry memo across them would hand the second document the first's
155
+ * options. Never called by the app, where the cache is a hit or a miss and
156
+ * both are correct. */
157
+ export function resetMaterialChoicesMemo(): void {
158
+ choicesMemo = null;
159
+ }
160
+
161
+ // ── the node cache ──────────────────────────────────────────────────────────
162
+
163
+ interface FlowCacheEntry {
164
+ key: string;
165
+ flow: MaterialFlowNode;
166
+ }
167
+
168
+ const FLOW_CACHE = new WeakMap<MaterialGraphNode, FlowCacheEntry>();
169
+ const EDGE_CACHE = new WeakMap<object, { key: string; edge: Edge }>();
170
+
171
+ /** Which sockets on each node carry a wire, in one pass over the edges. */
172
+ function connectionsOf(graph: MaterialGraph3D): Map<string, Set<string>> {
173
+ const out = new Map<string, Set<string>>();
174
+ const add = (nodeId: string, key: string): void => {
175
+ const found = out.get(nodeId);
176
+ if (found === undefined) out.set(nodeId, new Set([key]));
177
+ else found.add(key);
178
+ };
179
+ for (const edge of graph.edges) {
180
+ add(edge.source.nodeId, `out:${edge.source.socket}`);
181
+ add(edge.target.nodeId, `in:${edge.target.socket}`);
182
+ }
183
+ return out;
184
+ }
185
+
186
+ /**
187
+ * The validator's problems, indexed by the node they are about.
188
+ *
189
+ * `at` is a document path — `nodesById.mgn_x.kind` — so the node id is its
190
+ * second segment. Only the FIRST problem per node is kept: a card has room for
191
+ * one sentence, and the panel below the canvas lists them all.
192
+ */
193
+ function problemsByNode(problems: readonly MaterialGraphProblem[]): Map<string, string> {
194
+ const out = new Map<string, string>();
195
+ for (const problem of problems) {
196
+ const parts = problem.at.split(".");
197
+ if (parts[0] !== "nodesById") continue;
198
+ const id = parts[1];
199
+ if (id === undefined || out.has(id)) continue;
200
+ out.set(id, problem.message);
201
+ }
202
+ return out;
203
+ }
204
+
205
+ const EMPTY_SOCKETS: ReadonlySet<string> = new Set<string>();
206
+
207
+ /**
208
+ * Build the whole canvas.
209
+ *
210
+ * GROUPS COME FIRST in the array, which is a React Flow requirement rather than
211
+ * a preference: a subflow child is dropped outright if its parent has not been
212
+ * seen yet, so a member whose group sorted after it would simply not render.
213
+ */
214
+ export function buildMaterialFlow(input: MaterialFlowInput): {
215
+ nodes: MaterialFlowNode[];
216
+ edges: Edge[];
217
+ } {
218
+ const { graph, materialId, path, assets, selection, problems, types } = input;
219
+ const scoped = nodesInScope(graph, path);
220
+ const connections = connectionsOf(graph);
221
+ const problemOf = problemsByNode(problems);
222
+ const selected = new Set(selection.nodeIds);
223
+ const selectedEdges = new Set(selection.edgeIds);
224
+ const { value: choices, token: choicesToken } = choicesFor(assets, graph.parameters);
225
+
226
+ const groups = scoped.filter((node) => node.kind === "group");
227
+ const rest = scoped.filter((node) => node.kind !== "group");
228
+ const nodes: MaterialFlowNode[] = [];
229
+
230
+ for (const node of [...groups, ...rest]) {
231
+ // A member's position is stored ABSOLUTE in the document and React Flow
232
+ // wants it relative to the parent, so the group's own origin is part of
233
+ // what this card depends on — and therefore part of its cache key.
234
+ const parent = node.groupId === undefined ? undefined : graph.nodesById[node.groupId];
235
+ const parentUsable = parent?.kind === "group" && (parent.scope ?? undefined) === (node.scope ?? undefined);
236
+ const origin = parentUsable ? parent.position : null;
237
+ const connected = connections.get(node.id) ?? EMPTY_SOCKETS;
238
+ const problem = problemOf.get(node.id) ?? null;
239
+ const isSelected = selected.has(node.id);
240
+ const spec = materialGraphNodeSpec(node.kind);
241
+ const sockets = spec === null ? { inputs: [], outputs: [] } : materialGraphNodeSockets(graph, node);
242
+ const outputType =
243
+ sockets.outputs[0] === undefined ? null : types.outputTypeOf(node.id, sockets.outputs[0].id);
244
+ const parameter = node.parameterId === undefined ? undefined : graph.parameters[node.parameterId];
245
+ const assetLabel =
246
+ node.assetId === undefined
247
+ ? undefined
248
+ : (assets[node.assetId]?.path.split("/").pop() ?? node.assetId);
249
+
250
+ const key = [
251
+ isSelected ? "1" : "0",
252
+ [...connected].sort().join(","),
253
+ origin === null ? "-" : `${origin[0]},${origin[1]}`,
254
+ problem ?? "",
255
+ outputType ?? "",
256
+ parameter === undefined ? "" : `${parameter.id}:${parameter.label}:${parameter.type}`,
257
+ assetLabel ?? "",
258
+ String(choicesToken),
259
+ // Dynamic sockets belong to the function, not the node — a rename of a
260
+ // function input changes this card and nothing about `node`.
261
+ sockets.inputs.map((s) => `${s.id}:${s.type}`).join("|"),
262
+ sockets.outputs.map((s) => `${s.id}:${s.type}`).join("|"),
263
+ ].join("");
264
+
265
+ const hit = FLOW_CACHE.get(node);
266
+ if (hit !== undefined && hit.key === key) {
267
+ nodes.push(hit.flow);
268
+ continue;
269
+ }
270
+ if (spec === null) continue;
271
+
272
+ const data: MaterialFlowData = {
273
+ materialId,
274
+ path,
275
+ node,
276
+ spec,
277
+ inputs: sockets.inputs,
278
+ outputs: sockets.outputs,
279
+ connected,
280
+ parameter,
281
+ assetLabel,
282
+ outputType,
283
+ problem,
284
+ choices,
285
+ };
286
+ const flow: MaterialFlowNode = {
287
+ id: node.id,
288
+ type: flowTypeOf(node),
289
+ position:
290
+ origin === null
291
+ ? { x: node.position[0], y: node.position[1] }
292
+ : { x: node.position[0] - origin[0], y: node.position[1] - origin[1] },
293
+ selected: isSelected,
294
+ data,
295
+ ...(origin === null ? {} : { parentId: node.groupId! }),
296
+ ...(node.kind === "group" || node.kind === "comment"
297
+ ? { style: { width: node.size?.[0] ?? 240, height: node.size?.[1] ?? 160 } }
298
+ : {}),
299
+ };
300
+ FLOW_CACHE.set(node, { key, flow });
301
+ nodes.push(flow);
302
+ }
303
+
304
+ const edges: Edge[] = [];
305
+ const inScope = new Set(scoped.map((node) => node.id));
306
+ for (const edge of graph.edges) {
307
+ // An edge is drawn only when BOTH ends are on this canvas. A cross-scope
308
+ // wire cannot exist (`canConnect` refuses one), so this is really "the
309
+ // other scope's wires stay in the other scope".
310
+ if (!inScope.has(edge.source.nodeId) || !inScope.has(edge.target.nodeId)) continue;
311
+ const key = selectedEdges.has(edge.id) ? "1" : "0";
312
+ const hit = EDGE_CACHE.get(edge);
313
+ if (hit !== undefined && hit.key === key) {
314
+ edges.push(hit.edge);
315
+ continue;
316
+ }
317
+ const flow: Edge = {
318
+ id: edge.id,
319
+ source: edge.source.nodeId,
320
+ sourceHandle: edge.source.socket,
321
+ target: edge.target.nodeId,
322
+ targetHandle: edge.target.socket,
323
+ type: "smoothstep",
324
+ className: "material-edge",
325
+ selected: key === "1",
326
+ reconnectable: true,
327
+ };
328
+ EDGE_CACHE.set(edge, { key, edge: flow });
329
+ edges.push(flow);
330
+ }
331
+
332
+ return { nodes, edges };
333
+ }
334
+
335
+ /**
336
+ * A dragged React Flow node's position, back in DOCUMENT coordinates.
337
+ *
338
+ * The one place the subflow offset is undone. Doing it at each call site is how
339
+ * a group member ends up saved at its group-relative position and jumps to the
340
+ * top-left corner of the box on the next load.
341
+ */
342
+ export function graphPositionOf(
343
+ flow: { position: { x: number; y: number }; parentId?: string | undefined },
344
+ graph: MaterialGraph3D,
345
+ ): [number, number] {
346
+ const parent = flow.parentId === undefined ? undefined : graph.nodesById[flow.parentId];
347
+ if (parent === undefined) return [Math.round(flow.position.x), Math.round(flow.position.y)];
348
+ return [
349
+ Math.round(flow.position.x + parent.position[0]),
350
+ Math.round(flow.position.y + parent.position[1]),
351
+ ];
352
+ }
353
+
354
+ export interface FlowRect {
355
+ x: number;
356
+ y: number;
357
+ width: number;
358
+ height: number;
359
+ }
360
+
361
+ /**
362
+ * Does the segment a→b cross the rect?
363
+ *
364
+ * Liang–Barsky, which answers "does it cross" without computing where — the
365
+ * canvas only needs the boolean, once per edge per pointer frame. The two
366
+ * degenerate cases matter and are handled: a segment PARALLEL to a slab is in
367
+ * only if it already lies inside it, and a zero-length segment reduces to a
368
+ * point test.
369
+ */
370
+ export function segmentIntersectsRect(
371
+ a: { x: number; y: number },
372
+ b: { x: number; y: number },
373
+ rect: FlowRect,
374
+ ): boolean {
375
+ const dx = b.x - a.x;
376
+ const dy = b.y - a.y;
377
+ let t0 = 0;
378
+ let t1 = 1;
379
+ const slabs: readonly [number, number][] = [
380
+ [-dx, a.x - rect.x],
381
+ [dx, rect.x + rect.width - a.x],
382
+ [-dy, a.y - rect.y],
383
+ [dy, rect.y + rect.height - a.y],
384
+ ];
385
+ for (const [p, q] of slabs) {
386
+ if (p === 0) {
387
+ // Parallel to this slab: outside it means it can never enter.
388
+ if (q < 0) return false;
389
+ continue;
390
+ }
391
+ const r = q / p;
392
+ if (p < 0) {
393
+ if (r > t1) return false;
394
+ if (r > t0) t0 = r;
395
+ } else {
396
+ if (r < t0) return false;
397
+ if (r < t1) t1 = r;
398
+ }
399
+ }
400
+ return true;
401
+ }
402
+
403
+ /** A group box's document rect. Mirrors the fallback `buildMaterialFlow` uses
404
+ * when a box carries no size, so the picture and the hit test agree. */
405
+ function groupRect(node: MaterialGraphNode): FlowRect {
406
+ return {
407
+ x: node.position[0],
408
+ y: node.position[1],
409
+ width: node.size?.[0] ?? 240,
410
+ height: node.size?.[1] ?? 160,
411
+ };
412
+ }
413
+
414
+ /**
415
+ * The group box under a DOCUMENT point, or null.
416
+ *
417
+ * SMALLEST AREA WINS. Boxes do not nest in schema v17, but two of them can
418
+ * still overlap because nothing stops an author dragging one over another, and
419
+ * "the one I am pointing at" is the tighter of the two every time. Ties break
420
+ * on id so a drop is not decided by object order.
421
+ */
422
+ export function groupUnderPoint(
423
+ graph: MaterialGraph3D,
424
+ path: readonly string[],
425
+ point: readonly [number, number],
426
+ ): string | null {
427
+ const scope = path.length === 0 ? undefined : path[path.length - 1];
428
+ let best: { id: string; area: number } | null = null;
429
+ for (const node of Object.values(graph.nodesById)) {
430
+ if (node.kind !== "group") continue;
431
+ if ((node.scope ?? undefined) !== scope) continue;
432
+ const rect = groupRect(node);
433
+ if (point[0] < rect.x || point[0] > rect.x + rect.width) continue;
434
+ if (point[1] < rect.y || point[1] > rect.y + rect.height) continue;
435
+ const area = rect.width * rect.height;
436
+ if (best === null || area < best.area || (area === best.area && node.id < best.id)) {
437
+ best = { id: node.id, area };
438
+ }
439
+ }
440
+ return best === null ? null : best.id;
441
+ }
@@ -0,0 +1,231 @@
1
+ // ONE GRAPH PARAMETER'S VALUES ON EVERY MESH THAT WEARS THE GRAPH: the per-mesh
2
+ // override and the keyframe track, on the whole model's binding and on each
3
+ // submesh slot that binds the graph.
4
+ //
5
+ // A reusable graph's parameter is edited in one place and lives in many: each
6
+ // mesh binding the graph may override it, and may animate it on a track at the
7
+ // binding's own path (`materialGraphParamPath(id, slot)`). Two edits of the
8
+ // parameter reach all of them at once and must agree about where they are:
9
+ // deleting it (every override and track that names it goes too, or the save
10
+ // refuses the orphans) and narrowing its range (a value left outside the new
11
+ // range is refused by the save as well). Both walk the bindings here, so a
12
+ // slot binding is never the one the walk forgot. Pure and React-free.
13
+ import { valueEquals } from "@dailies-studio/engine";
14
+ import type { Value } from "@dailies-studio/engine";
15
+ import {
16
+ isGraphMaterial,
17
+ isMaterialGraphValueForType,
18
+ materialGraphParamPath,
19
+ parseMaterialSlotKey,
20
+ } from "@dailies-studio/shot/doc";
21
+ import type {
22
+ Doc3D,
23
+ Layer3D,
24
+ MaterialGraphBinding,
25
+ MaterialGraphParameter,
26
+ MaterialGraphValue,
27
+ ShotTrack,
28
+ ShotTracks,
29
+ } from "@dailies-studio/shot/doc";
30
+
31
+ /** One binding's half of a parameter: its override and its track. */
32
+ export interface ParameterValues {
33
+ override: MaterialGraphValue | undefined;
34
+ track: ShotTrack | undefined;
35
+ }
36
+
37
+ /** A new override and track for a binding's old ones. Returning the inputs
38
+ * (by identity) changes nothing; undefined drops that half. */
39
+ type Rewrite = (values: ParameterValues) => ParameterValues;
40
+
41
+ type Overrides = Record<string, MaterialGraphValue> | undefined;
42
+
43
+ function rewriteLayer(layer: Layer3D, materialId: string, parameterId: string, rewrite: Rewrite): Layer3D {
44
+ if (layer.kind !== "mesh" || !isGraphMaterial(layer.mesh.material)) return layer;
45
+ const binding = layer.mesh.material;
46
+ let tracks: ShotTracks | undefined = layer.tracks;
47
+ let tracksMoved = false;
48
+
49
+ /** The binding's overrides after the rewrite (the same object when its
50
+ * override did not move), with the track written into `tracks`. */
51
+ const visit = (slot: number | null, overrides: Overrides): Overrides => {
52
+ const path = materialGraphParamPath(parameterId, slot);
53
+ const before: ParameterValues = { override: overrides?.[parameterId], track: tracks?.[path] };
54
+ const after = rewrite(before);
55
+ if (after.track !== before.track) {
56
+ const next: ShotTracks = { ...tracks };
57
+ if (after.track === undefined) delete next[path];
58
+ else next[path] = after.track;
59
+ tracks = Object.keys(next).length === 0 ? undefined : next;
60
+ tracksMoved = true;
61
+ }
62
+ if (after.override === before.override) return overrides;
63
+ const next = { ...overrides };
64
+ if (after.override === undefined) delete next[parameterId];
65
+ else next[parameterId] = after.override;
66
+ return Object.keys(next).length === 0 ? undefined : next;
67
+ };
68
+
69
+ let material: MaterialGraphBinding = binding;
70
+ // The whole model's binding, only when it is THIS graph: another graph may
71
+ // hold a parameter with the same id, and its path is this one's too.
72
+ if (binding.materialId === materialId) {
73
+ const overrides = visit(null, binding.overrides);
74
+ if (overrides !== binding.overrides) {
75
+ const { overrides: _old, ...rest } = binding;
76
+ material = overrides === undefined ? rest : { ...rest, overrides };
77
+ }
78
+ }
79
+ if (binding.slots !== undefined) {
80
+ let slots = binding.slots;
81
+ for (const key of Object.keys(binding.slots)) {
82
+ const index = parseMaterialSlotKey(key);
83
+ const slot = Object.prototype.hasOwnProperty.call(binding.slots, key) ? binding.slots[key] : undefined;
84
+ if (index === null || slot === undefined || slot.materialId !== materialId) continue;
85
+ const overrides = visit(index, slot.overrides);
86
+ if (overrides === slot.overrides) continue;
87
+ if (slots === binding.slots) slots = { ...binding.slots };
88
+ slots[key] = overrides === undefined ? { materialId: slot.materialId } : { materialId: slot.materialId, overrides };
89
+ }
90
+ if (slots !== binding.slots) material = { ...material, slots };
91
+ }
92
+
93
+ if (material === binding && !tracksMoved) return layer;
94
+ const next: Layer3D = { ...layer, mesh: { ...layer.mesh, material } };
95
+ if (!tracksMoved) return next;
96
+ if (tracks !== undefined) return { ...next, tracks };
97
+ const { tracks: _dropped, ...bare } = next;
98
+ return bare as Layer3D;
99
+ }
100
+
101
+ function rewriteLayers(
102
+ layers: Readonly<Record<string, Layer3D>>,
103
+ materialId: string,
104
+ parameterId: string,
105
+ rewrite: Rewrite,
106
+ ): Readonly<Record<string, Layer3D>> {
107
+ let out: Record<string, Layer3D> | null = null;
108
+ for (const [id, layer] of Object.entries(layers)) {
109
+ const next = rewriteLayer(layer, materialId, parameterId, rewrite);
110
+ if (next === layer) continue;
111
+ out ??= { ...layers };
112
+ out[id] = next;
113
+ }
114
+ return out ?? layers;
115
+ }
116
+
117
+ /**
118
+ * The document with `rewrite` applied to one parameter's override and track
119
+ * on every binding of `materialId`, in every Shot and every definition. The
120
+ * same document when nothing moved.
121
+ */
122
+ export function rewriteParameterValues(doc: Doc3D, materialId: string, parameterId: string, rewrite: Rewrite): Doc3D {
123
+ let scenesById: Doc3D["scenesById"] | null = null;
124
+ for (const [id, scene] of Object.entries(doc.scenesById)) {
125
+ const layersById = rewriteLayers(scene.layersById, materialId, parameterId, rewrite);
126
+ if (layersById === scene.layersById) continue;
127
+ scenesById ??= { ...doc.scenesById };
128
+ scenesById[id] = { ...scene, layersById: layersById as Record<string, Layer3D> };
129
+ }
130
+ let components: NonNullable<Doc3D["components"]> | null = null;
131
+ for (const [id, def] of Object.entries(doc.components ?? {})) {
132
+ const layersById = rewriteLayers(def.layersById, materialId, parameterId, rewrite);
133
+ if (layersById === def.layersById) continue;
134
+ components ??= { ...doc.components };
135
+ components[id] = { ...def, layersById: layersById as Record<string, Layer3D> };
136
+ }
137
+ if (scenesById === null && components === null) return doc;
138
+ return { ...doc, ...(scenesById === null ? {} : { scenesById }), ...(components === null ? {} : { components }) };
139
+ }
140
+
141
+ /** A float parameter's range; an absent end is open. */
142
+ export interface ParameterRange {
143
+ min?: number;
144
+ max?: number;
145
+ }
146
+
147
+ function clampTo(value: number, range: ParameterRange): number {
148
+ return Math.max(range.min ?? -Infinity, Math.min(range.max ?? Infinity, value));
149
+ }
150
+
151
+ function outside(value: unknown, range: ParameterRange): boolean {
152
+ return typeof value === "number" && clampTo(value, range) !== value;
153
+ }
154
+
155
+ /** How many per-mesh values (overrides and keyframes) of a float parameter
156
+ * sit outside `range`: what a narrowed range would leave the save to refuse. */
157
+ export function valuesOutsideRange(doc: Doc3D, materialId: string, parameterId: string, range: ParameterRange): number {
158
+ let count = 0;
159
+ rewriteParameterValues(doc, materialId, parameterId, (values) => {
160
+ if (outside(values.override, range)) count += 1;
161
+ for (const key of values.track?.keyframes ?? []) if (outside(key.value, range)) count += 1;
162
+ return values;
163
+ });
164
+ return count;
165
+ }
166
+
167
+ /** A parameter with its range set (an absent end is open) and its default
168
+ * moved inside it, which the graph's own check requires. The same object
169
+ * when nothing moves. */
170
+ export function withParameterRange(parameter: MaterialGraphParameter, range: ParameterRange): MaterialGraphParameter {
171
+ const fallback =
172
+ typeof parameter.default === "number" ? clampTo(parameter.default, range) : parameter.default;
173
+ if (parameter.min === range.min && parameter.max === range.max && fallback === parameter.default) return parameter;
174
+ const { min: _min, max: _max, ...rest } = parameter;
175
+ return {
176
+ ...rest,
177
+ ...(range.min === undefined ? {} : { min: range.min }),
178
+ ...(range.max === undefined ? {} : { max: range.max }),
179
+ default: fallback,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * A parameter's DEFAULT written for the length of a drag: the narrow pure
185
+ * write a scrub re-derives from its base on every move (`writeMaterialNodeValue`'s
186
+ * precedent). It types the value by the parameter and keeps a float inside its
187
+ * range, so it can never change the graph's shape or break its own check; a
188
+ * typed value crosses `updateProjectMaterial`'s validated door instead.
189
+ */
190
+ export function writeParameterDefault(
191
+ doc: Doc3D,
192
+ materialId: string,
193
+ parameterId: string,
194
+ value: unknown,
195
+ ): Doc3D {
196
+ const graph = doc.materials?.[materialId];
197
+ if (graph === undefined || !Object.prototype.hasOwnProperty.call(graph.parameters, parameterId)) return doc;
198
+ const parameter = graph.parameters[parameterId]!;
199
+ if (!isMaterialGraphValueForType(value, parameter.type)) return doc;
200
+ const next: MaterialGraphValue =
201
+ typeof value === "number"
202
+ ? clampTo(value, {
203
+ ...(parameter.min === undefined ? {} : { min: parameter.min }),
204
+ ...(parameter.max === undefined ? {} : { max: parameter.max }),
205
+ })
206
+ : value;
207
+ if (valueEquals(parameter.default as Value, next as Value)) return doc;
208
+ return {
209
+ ...doc,
210
+ materials: {
211
+ ...doc.materials,
212
+ [materialId]: { ...graph, parameters: { ...graph.parameters, [parameterId]: { ...parameter, default: next } } },
213
+ },
214
+ };
215
+ }
216
+
217
+ /** The document with every per-mesh override and keyframe value of a float
218
+ * parameter clamped into `range`. */
219
+ export function clampParameterValues(doc: Doc3D, materialId: string, parameterId: string, range: ParameterRange): Doc3D {
220
+ return rewriteParameterValues(doc, materialId, parameterId, ({ override, track }) => {
221
+ const clampedOverride = typeof override === "number" ? clampTo(override, range) : override;
222
+ let clampedTrack = track;
223
+ if (track !== undefined && track.keyframes.some((key) => outside(key.value, range))) {
224
+ clampedTrack = {
225
+ ...track,
226
+ keyframes: track.keyframes.map((key) => (outside(key.value, range) ? { ...key, value: clampTo(key.value as number, range) } : key)),
227
+ };
228
+ }
229
+ return { override: clampedOverride, track: clampedTrack };
230
+ });
231
+ }