@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,230 +0,0 @@
1
- // The ADD menu: the layer kinds a DRAG CANNOT EXPRESS.
2
- //
3
- // A rectangle is a box, so the rect tool is a drag. A model is a FILE, a device
4
- // is a CHOICE FROM A REGISTRY, and a cloner is an operation on what is already
5
- // selected — none of those is a rubber band, and giving each of them a tool
6
- // would put three buttons in the dock that all do the same thing when dragged
7
- // (nothing) and something else entirely when clicked.
8
- //
9
- // ── THE ROWS ARE GATED ON THE SCHEMA AT RUNTIME ─────────────────────────
10
- //
11
- // `hasLayerKind` reads `LAYER_KINDS`, so a kind removed from the document model
12
- // removes its row here instead of leaving a button that mints a layer nothing
13
- // can render. It has to be a runtime test and not `LAYER_KINDS.includes("mesh")`
14
- // inline: against the literal tuple that call is a TYPE error the day the kind
15
- // goes, which is a build nobody can ship — the menu could not degrade, only
16
- // fail to compile.
17
- //
18
- // ── ONE FILE INPUT, TWO PICKERS ─────────────────────────────────────────
19
- //
20
- // The model row needs one, and so does the FRAME TOOL (`image`, labelled
21
- // "Frame" because the file it takes may be an image or a video) — which draws
22
- // its box on the canvas and only then needs a file, so its geometry parks in
23
- // the store (`imagePick`) and is claimed here. A dialog can only be opened by a
24
- // click on a real `<input>`, and the store is not one.
25
- import { useEffect, useRef, useState } from "react";
26
- import { DEVICE_PRESET_IDS } from "@dailies-studio/shot/doc";
27
- import { useShotEditor, useStore } from "../../state/context.js";
28
- import { hasLayerKind } from "../../state/insert.js";
29
-
30
- /**
31
- * What the hidden input is being asked for, or null when idle.
32
- *
33
- * An OBJECT and not a bare string, so asking for the same kind twice is a new
34
- * value: a dialog the author cancelled fires no event at all, and with a string
35
- * the second "image" would be `===` the stale first one, the effect would not
36
- * re-run, and the picker would silently never open again.
37
- */
38
- type Pick = { kind: "image" | "model" };
39
-
40
- export function AddMenu() {
41
- const store = useStore();
42
- const imagePick = useShotEditor((s) => s.imagePick);
43
- const hasSelection = useShotEditor((s) => s.selection.length > 0);
44
- const canGroup = useShotEditor((s) => s.selection.length >= 2);
45
- const library = useShotEditor((s) => s.library);
46
- const libraryLoaded = useShotEditor((s) => s.libraryLoaded);
47
- const [open, setOpen] = useState(false);
48
- const [busy, setBusy] = useState(false);
49
- const [want, setWant] = useState<Pick | null>(null);
50
- const fileRef = useRef<HTMLInputElement | null>(null);
51
-
52
- // A drawn image box is waiting on a file: open the dialog for it. An EFFECT
53
- // is the seam because the draw happened in the STAGE's document, so there is
54
- // no click of ours to hang it off. User activation propagates from a
55
- // same-origin iframe, so the dialog is still allowed to open; if a browser
56
- // ever stops propagating it, the ceiling is that the image tool needs a
57
- // second click here rather than anything breaking.
58
- useEffect(() => {
59
- if (imagePick === null) return;
60
- setWant({ kind: "image" });
61
- }, [imagePick]);
62
-
63
- useEffect(() => {
64
- if (want !== null) fileRef.current?.click();
65
- }, [want]);
66
-
67
- // The registry, once, when the menu first opens rather than at mount: it is
68
- // an HTTP call, and a chrome that never adds a component should never make
69
- // it. A failure leaves the instance rows absent (the error surfaces in the
70
- // inspector, which is the surface that can say what it means for a layer).
71
- useEffect(() => {
72
- if (open && !libraryLoaded) void store.refreshLibrary();
73
- }, [open, libraryLoaded, store]);
74
-
75
- const report = (e: unknown): void =>
76
- store.reportError(e instanceof Error ? e.message : String(e));
77
-
78
- const chose = (file: File | null): void => {
79
- const asked = want;
80
- setWant(null);
81
- if (asked === null) return;
82
- setBusy(true);
83
- const done =
84
- asked.kind === "image"
85
- ? store.completeImageDraw(file)
86
- : file === null
87
- ? Promise.resolve()
88
- : store.addMeshLayer(file);
89
- void done.catch(report).finally(() => setBusy(false));
90
- };
91
-
92
- // Every row is gated, so a menu with nothing left in it is not an empty
93
- // popover — it is no button at all.
94
- const rows: { key: string; label: string; disabled?: boolean; title?: string; run: () => void }[] = [];
95
- if (hasLayerKind("mesh")) {
96
- rows.push({ key: "mesh", label: "3D model…", run: () => setWant({ kind: "model" }) });
97
- }
98
- if (hasLayerKind("device")) {
99
- // FROM THE REGISTRY, never a list written here: a hardcoded one is a second
100
- // place that knows what a device is, and `validateShotDoc` refuses a preset
101
- // the registry does not have — so the two disagreeing means a menu row that
102
- // can only produce a document the author cannot save.
103
- for (const preset of DEVICE_PRESET_IDS) {
104
- rows.push({
105
- key: `device:${preset}`,
106
- label: preset.charAt(0).toUpperCase() + preset.slice(1),
107
- run: () => store.addDeviceLayer(preset),
108
- });
109
- }
110
- }
111
- if (hasLayerKind("html")) {
112
- rows.push({ key: "html", label: "Html snippet", run: () => store.addHtmlLayer() });
113
- }
114
- if (hasLayerKind("instance")) {
115
- // FROM THE LIVE REGISTRY, filtered to the only host that can mount inside
116
- // the shot's 3D canvas. Same reason the device rows come from
117
- // DEVICE_PRESET_IDS: a hardcoded list would be a second place that knows
118
- // what a component is, and the save gate refuses anything else — so a row
119
- // offering a "dom" component could only ever produce a document the author
120
- // cannot save.
121
- for (const row of library.filter((r) => r.host === "three")) {
122
- rows.push({
123
- key: `instance:${row.id}`,
124
- label: row.name,
125
- title: row.id,
126
- run: () => void store.addInstanceLayer(row.id).catch(report),
127
- });
128
- }
129
- }
130
- if (hasLayerKind("group")) {
131
- rows.push({
132
- key: "group",
133
- label: "Group",
134
- // The pointer twin of Ctrl/Cmd+G. Two members minimum, same floor
135
- // `groupSelection` itself enforces: a one-layer group is just a layer
136
- // with an extra transform nobody asked for.
137
- disabled: !canGroup,
138
- title: canGroup ? "Group the selection (Ctrl/Cmd+G)" : "Select two or more layers to group",
139
- run: () => store.groupSelection(),
140
- });
141
- }
142
- if (hasLayerKind("cloner")) {
143
- rows.push({
144
- key: "cloner",
145
- label: "Cloner",
146
- // No empty cloner, ever: `validateShotDoc` refuses one with no children,
147
- // so the row that inserted it would be a row that breaks saving.
148
- disabled: !hasSelection,
149
- title: hasSelection ? "Wrap the selection in a cloner" : "Select a layer to clone first",
150
- run: () => store.addClonerLayer(),
151
- });
152
- }
153
- if (hasLayerKind("component")) {
154
- rows.push({
155
- key: "component",
156
- label: "Component",
157
- // A definition is MADE from something, exactly like a cloner: there is no
158
- // empty one, because a definition with no roots is refused at the gate.
159
- disabled: !hasSelection,
160
- title: hasSelection
161
- ? "Make the selection reusable, and leave one placement here"
162
- : "Select the layers to turn into a component first",
163
- run: () => store.createComponentFromSelection(),
164
- });
165
- }
166
- if (rows.length === 0) return null;
167
-
168
- return (
169
- <div className="tool-flyout-anchor">
170
- <button
171
- type="button"
172
- className="tool-button"
173
- aria-haspopup="menu"
174
- aria-expanded={open}
175
- title="Add a layer at the playhead"
176
- data-testid="add-menu-button"
177
- disabled={busy}
178
- onClick={() => setOpen((v) => !v)}
179
- >
180
- {busy ? "…" : "+"}
181
- </button>
182
- {open ? (
183
- <div className="tool-flyout" role="menu" data-testid="add-menu">
184
- {rows.map((row) => (
185
- <button
186
- key={row.key}
187
- type="button"
188
- role="menuitem"
189
- className="tool-button"
190
- disabled={row.disabled === true}
191
- title={row.title ?? row.label}
192
- onClick={() => {
193
- setOpen(false);
194
- row.run();
195
- }}
196
- >
197
- {row.label}
198
- </button>
199
- ))}
200
- </div>
201
- ) : null}
202
- <input
203
- ref={fileRef}
204
- className="audio-file-input"
205
- type="file"
206
- // EXTENSIONS, NOT `image/*` OR `video/*`. The "sniff" on the far end
207
- // is a filename extension map (`KIND_BY_EXT` in cli/src/assetStore.ts),
208
- // and `storeAsset` writes the bytes BEFORE the kind is consulted — so a
209
- // file the picker offered and the map does not know (.mkv, .svg, .heic)
210
- // is stored, then refused, leaving an orphan blob in the
211
- // content-addressed store that nothing ever collects. The list is
212
- // exactly what the map answers "image" or "video" for.
213
- accept={
214
- want?.kind === "model"
215
- ? ".glb,.gltf,model/gltf-binary"
216
- : ".png,.jpg,.jpeg,.webp,.avif,.gif,.mp4,.webm,.mov,.m4v," +
217
- "image/png,image/jpeg,image/webp,image/avif,image/gif," +
218
- "video/mp4,video/webm,video/quicktime"
219
- }
220
- hidden
221
- data-testid="add-file-input"
222
- onChange={(e) => {
223
- chose(e.currentTarget.files?.[0] ?? null);
224
- // Cleared so re-picking the SAME file fires change again.
225
- e.currentTarget.value = "";
226
- }}
227
- />
228
- </div>
229
- );
230
- }
@@ -1,411 +0,0 @@
1
- // Three drag arms in the working pane, on whatever the author is transforming.
2
- //
3
- // ── ONE GIZMO, TWO SUBJECTS, AND THE FORK IS `modalTarget` ──────────────
4
- //
5
- // With a selection it hangs on the selection's centre and moves layers. With
6
- // NOTHING selected it hangs on the shot camera's apex and moves the camera —
7
- // which is not a second feature bolted on, it is the SAME rule `G` and `S`
8
- // already follow (modalState.ts: "layers when the selection is non-empty, else
9
- // the CAMERA's framing"). Importing `modalTarget` rather than restating it is
10
- // what keeps the drawn gizmo and the keyboard from ever disagreeing about whose
11
- // transform the author is about to change.
12
- //
13
- // The camera's arms mean something slightly different, and `framingArmDelta`
14
- // owns that: it has no free z, so its Z arm is the DOLLY. See that function.
15
- //
16
- // ── WHAT THIS ADDS THAT THE PANE DID NOT HAVE ───────────────────────────
17
- //
18
- // Dragging a layer's BODY in this pane unprojects onto a plane, so it can move
19
- // the layer in two axes at once but never along z. Everything else was on the
20
- // keyboard: G-X, G-Y, G-Z. This is the pointer's way to say the same three
21
- // things, which is the one an author reaching for "place it over there" tries
22
- // first. The camera had even less: a body drag that pans and an apex drag that
23
- // zooms, neither of which can be constrained to an axis.
24
- //
25
- // ROTATE AND SCALE STAY ON R AND S. They already exist, with axis constraints
26
- // and a live pill (modalState.ts); a second set of rings and boxes would be a
27
- // duplicate of a lane that works, and every one of them would need its own
28
- // screen-space arc maths. This gizmo is TRANSLATE ONLY, on purpose.
29
- //
30
- // ── WHY THE DRAG SOLVES A LINE AND NOT A PLANE ──────────────────────────
31
- //
32
- // An arm the author has grabbed must stay under the cursor, and only the
33
- // closest-approach solve between the axis line and the pointer ray does that
34
- // (`axisParam`). Unprojecting onto a plane and zeroing two components — what
35
- // the modal lane does, because it has no drawn arm to stay under — drifts away
36
- // from the pointer as the view turns and runs away entirely at a grazing angle.
37
- //
38
- // ── EDITOR-ONLY, THE SAME THREE WAYS THE FRUSTUM IS ─────────────────────
39
- //
40
- // `layers.set(EDITOR_LAYER)`, SplitRenderer's post pass pinned to bit 0, and no
41
- // `SHOT_LAYER_KEY` userData. It also writes no document: the DRAG does, through
42
- // the same `onGestureStart`/`onGesture`/`onGestureEnd` scrub every other
43
- // transform in this file opens.
44
- import { useEffect, useMemo } from "react";
45
- import { useFrame } from "@react-three/fiber";
46
- import { BufferAttribute, BufferGeometry, Color, LineSegments, Vector3 } from "three";
47
- import type { Camera, OrthographicCamera, PerspectiveCamera } from "three";
48
- import { LineBasicNodeMaterial } from "three/webgpu";
49
- import { EDITOR_LAYER } from "./StageTools.js";
50
- import { DESIGN_AXES, armGrabbable } from "./gizmoMath.js";
51
- import type { DesignAxis, Vec3 } from "./gizmoMath.js";
52
- // `GizmoGrab` lives in gizmoMath.ts with the rest of the axis vocabulary — the
53
- // pointer lane carries one and must not import a three-side module for a type.
54
- import { APEX_GRAB_PX } from "./FrustumGizmo.js";
55
- import { borderDistance, segmentsMoved } from "./frustumGeometry.js";
56
- import { modalTarget } from "./modalState.js";
57
- import { toScreen } from "./project.js";
58
- import { tokenColor } from "./tokenColor.js";
59
- import type { StageFeed } from "./feed.js";
60
- import type { PaneRect } from "./panes.js";
61
-
62
- /** Over everything, and over the frustum too: an arm that a camera gizmo could
63
- * hide is an arm the author cannot grab. */
64
- const GIZMO_RENDER_ORDER = 2_000_000;
65
-
66
- /** Arm tip distance in CSS px, held CONSTANT as the author dollies. A
67
- * world-space length would be a gizmo that fills the pane up close and
68
- * disappears far out — and, because the hit slop below is in screen px, one
69
- * whose grab target changed size with the zoom. */
70
- const ARM_PX = 90;
71
-
72
- /** How close a press has to land to an arm, in CSS px. The frustum's own 14 —
73
- * a line is a line, and the author's finger is the same size in both. */
74
- const GRAB_PX = 14;
75
-
76
- /**
77
- * Where an arm STARTS, in CSS px from the anchor.
78
- *
79
- * It has to clear the anchor by more than BOTH slops added together, and that
80
- * is not cosmetic. On the camera the anchor IS the frustum's apex, whose own
81
- * grab is a 14px screen target (FrustumGizmo's APEX_GRAB_PX) tested AFTER this
82
- * one in the pointer lane. At any start below `APEX_GRAB_PX + GRAB_PX` a press
83
- * aimed at the apex lands within `GRAB_PX` of an arm as well, the arms win, and
84
- * the apex dolly becomes unreachable — silently, because an arm drag looks like
85
- * a working gesture.
86
- *
87
- * 30 > 14 + 14. It also keeps the three roots from piling into one ambiguous
88
- * blob, which is why the layer gizmo wants it too.
89
- */
90
- const ARM_START_PX = 30;
91
-
92
- /**
93
- * How far an arm's ROOT must land from the anchor ON SCREEN to be grabbable.
94
- *
95
- * ── WHY `ARM_START_PX` IS NOT ENOUGH ────────────────────────────────────
96
- *
97
- * The note above asserts an inequality in SCREEN px ("30 > 14 + 14"), but the
98
- * value is converted to WORLD units at the anchor's depth and then projected.
99
- * So the distance the author actually sees is `ARM_START_PX * sin(angle to the
100
- * view direction)`, and an axis pointing near the camera collapses:
101
- *
102
- * At Numpad7 the polar angle clamps to 5 degrees, so the design y arm
103
- * projects to about 2.6 px and every press near the selection centre grabs
104
- * it. A 5 px twitch then throws the layer.
105
- *
106
- * Under ISOLATION'S ORTHOGRAPHIC RIG there is no vanishing point at all: the
107
- * design z axis points exactly at the camera, so the z arm projects to a
108
- * mathematical POINT on the anchor. `borderDistance` to a degenerate segment
109
- * is the distance to that point, so every press within `GRAB_PX` of the
110
- * selection centre grabs it — and then `axisParam` divides by a zero
111
- * denominator, answers null, and `runGizmoGesture` returns before opening a
112
- * scrub. The press is completely dead, under a blue line that looks grabbable.
113
- *
114
- * So the test is done where it is true: after projection, in screen px. An arm
115
- * that fails it is still DRAWN — it is a real axis and hiding it would be
116
- * lying about the geometry — but it does not answer presses, so they fall
117
- * through to the pick lane underneath.
118
- *
119
- * ── AND IT MEASURES THE DRAWN LENGTH, NOT THE ROOT OFFSET ───────────────
120
- *
121
- * The first version of this gate compared the ROOT's distance from the anchor,
122
- * on the reasoning that the tip sits three times further out so the root test
123
- * implies the length test. That is backwards: the root offset is at most
124
- * `ARM_START_PX = 30` px and the threshold is 28, so at the SHIPPED DEFAULT
125
- * view (theta 35, phi 65) all three arms measured 20 to 27 px, every one of
126
- * them failed, `ends` came back empty and the gizmo was inert everywhere. The
127
- * quantity that decides whether a segment can be grabbed is the segment's own
128
- * length, so that is what is measured.
129
- *
130
- * The APEX CLEARANCE is a separate question and only applies to the camera
131
- * subject, where the anchor IS the frustum's apex — see `ARM_START_PX`.
132
- */
133
- const MIN_ARM_SCREEN_PX = GRAB_PX * 2;
134
-
135
- const AXIS_FALLBACK: Readonly<Record<DesignAxis, string>> = {
136
- x: "#ef4444",
137
- y: "#22c55e",
138
- z: "#3b82f6",
139
- };
140
-
141
- /** How far a hovered arm blends towards white. Enough to read as hot at a
142
- * glance, little enough that red / green / blue still name the axis. */
143
- const HOVER_TINT = 0.45;
144
- const WHITE = new Color(1, 1, 1);
145
-
146
- const DEG = Math.PI / 180;
147
- const _origin = new Vector3();
148
- const _root = new Vector3();
149
- const _tip = new Vector3();
150
- const _view = new Vector3();
151
- /** One arm's six position floats, hoisted: the hot path allocates nothing. */
152
- const _next = new Float32Array(6);
153
-
154
- /** Is a world point behind the camera's own plane? View-space z runs NEGATIVE
155
- * in front, so anything at or past zero has no honest projection. */
156
- function behind(point: Vector3, camera: Camera): boolean {
157
- return _view.copy(point).applyMatrix4(camera.matrixWorldInverse).z >= 0;
158
- }
159
-
160
- export interface MoveGizmoProps {
161
- feed: StageFeed;
162
- panes: { working: PaneRect | null; camera: PaneRect | null };
163
- selection: readonly string[];
164
- /** The shot camera's own position at this frame, or null when no scene is on
165
- * screen. Passed in rather than read off the feed: `FrustumGizmo` writes its
166
- * own copy at priority 0 too, and two priority-0 subscribers have no defined
167
- * order — so this comes from `rig`, which `StageApp` already memoizes. */
168
- cameraApex: Vec3 | null;
169
- }
170
-
171
- export function MoveGizmo({ feed, panes, selection, cameraApex }: MoveGizmoProps) {
172
- // One `LineSegments` per axis rather than one buffer with vertex colours:
173
- // three objects and three materials is the whole cost, and vertex colours on
174
- // a node material would be a second thing to keep working across the WebGPU
175
- // and WebGL2 backends for no gain at three vertices apiece.
176
- const arms = useMemo(
177
- () =>
178
- DESIGN_AXES.map(({ axis }) => {
179
- const geometry = new BufferGeometry();
180
- geometry.setAttribute("position", new BufferAttribute(new Float32Array(6), 3));
181
- const material = new LineBasicNodeMaterial();
182
- material.color.copy(tokenColor(`--axis-${axis}`, AXIS_FALLBACK[axis]));
183
- material.depthTest = false;
184
- material.depthWrite = false;
185
- material.toneMapped = false;
186
- // The working pane renders under a clay override in solid/wireframe
187
- // mode, and a grey gizmo is the one thing this must not be.
188
- material.allowOverride = false;
189
- const lines = new LineSegments(geometry, material);
190
- lines.layers.set(EDITOR_LAYER);
191
- lines.renderOrder = GIZMO_RENDER_ORDER;
192
- // World positions rewritten in place — same reason the frustum turns
193
- // culling off: the bounding sphere is never the one the buffer holds.
194
- lines.frustumCulled = false;
195
- lines.matrixAutoUpdate = false;
196
- // How far towards white the hover has taken this arm, so the frame loop
197
- // can skip the colour write on every frame that changed nothing.
198
- return { axis, lines, geometry, material, tint: 0 };
199
- }),
200
- [],
201
- );
202
-
203
- useEffect(
204
- () => () => {
205
- for (const arm of arms) {
206
- arm.geometry.dispose();
207
- arm.material.dispose();
208
- }
209
- },
210
- [arms],
211
- );
212
-
213
- useFrame(() => {
214
- const working = panes.working;
215
- const camera = feed.workingCamera;
216
- // `modalTarget` and not `selection.length` written out again: this is the
217
- // same fork `G` takes, and one of them drifting is a gizmo that moves
218
- // something other than what the pill says it will.
219
- //
220
- // A selection whose layers are all off-frame at this playhead anchors
221
- // NOWHERE rather than falling through to the camera — silently retargeting
222
- // a live selection's gizmo at the camera is the worst answer available.
223
- const subject = modalTarget(selection.length > 0);
224
- const centre = subject === "camera" ? cameraApex : selectionCentre(feed, selection);
225
- if (working === null || camera === null || centre === null) {
226
- for (const arm of arms) arm.lines.visible = false;
227
- feed.gizmoHit = null;
228
- return;
229
- }
230
-
231
- _origin.set(centre[0], centre[1], centre[2]);
232
- const perPixel = worldPerPixel(camera, _origin, working.height);
233
- const start = ARM_START_PX * perPixel;
234
- const length = ARM_PX * perPixel;
235
- // The anchor ON SCREEN, which is what every arm's own screen length is
236
- // measured against below. An anchor behind the camera projects to garbage,
237
- // so nothing is grabbable at all — the same refusal each arm makes.
238
- const anchorVisible = !behind(_origin, camera);
239
- const anchor = toScreen(_origin, camera, working.width, working.height);
240
- // Screen ends, kept beside the world ones so the hit test measures the
241
- // segment that was actually drawn.
242
- const ends: {
243
- axis: DesignAxis;
244
- world: Vec3;
245
- a: [number, number];
246
- b: [number, number];
247
- }[] = [];
248
-
249
- for (const [i, { world }] of DESIGN_AXES.entries()) {
250
- const arm = arms[i]!;
251
- arm.lines.visible = true;
252
- const from: Vec3 = [
253
- centre[0] + world[0] * start,
254
- centre[1] + world[1] * start,
255
- centre[2] + world[2] * start,
256
- ];
257
- const to: Vec3 = [
258
- centre[0] + world[0] * length,
259
- centre[1] + world[1] * length,
260
- centre[2] + world[2] * length,
261
- ];
262
- const attribute = arm.geometry.getAttribute("position") as BufferAttribute;
263
- const buffer = attribute.array as Float32Array;
264
- _next[0] = from[0];
265
- _next[1] = from[1];
266
- _next[2] = from[2];
267
- _next[3] = to[0];
268
- _next[4] = to[1];
269
- _next[5] = to[2];
270
- // The frustum's own half-pixel gate, for its reason: this runs on every
271
- // committed frame, and a parked playhead with a selection must not
272
- // re-upload three buffers per seek.
273
- if (segmentsMoved(buffer, _next)) {
274
- buffer.set(_next);
275
- attribute.needsUpdate = true;
276
- }
277
-
278
- // An arm with EITHER end behind the working camera projects to garbage,
279
- // so it is drawn but not grabbable — the same refusal the frustum's body
280
- // makes, and for the same reason: there is no screen segment to measure
281
- // against.
282
- _root.set(from[0], from[1], from[2]);
283
- _tip.set(to[0], to[1], to[2]);
284
- if (!anchorVisible || behind(_root, camera) || behind(_tip, camera)) continue;
285
- const a = toScreen(_root, camera, working.width, working.height);
286
- const b = toScreen(_tip, camera, working.width, working.height);
287
- // AN ARM THAT COLLAPSED ON SCREEN IS DRAWN BUT NOT GRABBABLE, and on the
288
- // camera it must also clear the apex. Both live in `armGrabbable`, which
289
- // is pure so a gate that decides whether the whole widget works can be
290
- // checked without a GL context.
291
- if (!armGrabbable(a, b, anchor, subject, MIN_ARM_SCREEN_PX, APEX_GRAB_PX + GRAB_PX)) continue;
292
- ends.push({ axis: arm.axis, world, a, b });
293
- }
294
-
295
- feed.gizmoHit = (x, y) => {
296
- let best: (typeof ends)[number] | null = null;
297
- let bestDistance = GRAB_PX;
298
- for (const end of ends) {
299
- // `borderDistance` walks a CLOSED polygon, so a two-point list is that
300
- // one segment measured twice — the point-to-segment distance, out of
301
- // the module that already owns it.
302
- const distance = borderDistance(x, y, [end.a, end.b]);
303
- if (distance <= bestDistance) {
304
- best = end;
305
- bestDistance = distance;
306
- }
307
- }
308
- return best === null
309
- ? null
310
- : { subject, axis: best.axis, origin: centre, direction: best.world };
311
- };
312
-
313
- // ── THE HOVERED ARM LIGHTS UP ─────────────────────────────────────────
314
- //
315
- // Blender highlights the gizmo element under the cursor, and this pane did
316
- // not — so "is this grabbable" was something the author learned by trying.
317
- // Blended TOWARDS WHITE rather than swapped for it, so the axis is still
318
- // identifiable by colour while it is hot.
319
- //
320
- // Here, in the frame loop, off a plain field the pointer lane wrote. A
321
- // hover therefore costs one uniform write per frame and zero React renders,
322
- // which is the whole reason `feed.hoverArm` is a field and not state.
323
- for (const arm of arms) {
324
- const hot = feed.hoverArm === arm.axis && ends.some((e) => e.axis === arm.axis);
325
- const want = hot ? HOVER_TINT : 0;
326
- if (arm.tint === want) continue;
327
- arm.tint = want;
328
- arm.material.color.copy(tokenColor(`--axis-${arm.axis}`, AXIS_FALLBACK[arm.axis])).lerp(WHITE, want);
329
- }
330
- }, 0);
331
-
332
- useEffect(
333
- () => () => {
334
- feed.gizmoHit = null;
335
- },
336
- [feed],
337
- );
338
-
339
- return (
340
- <>
341
- {arms.map((arm) => (
342
- <primitive key={arm.axis} object={arm.lines} />
343
- ))}
344
- </>
345
- );
346
- }
347
-
348
- /**
349
- * Where the arms sit: the mean of the selected layers' own origins.
350
- *
351
- * The GROUP origin out of `feed.frames`, never the mesh's centre — a paint
352
- * layer's plane is grown by the rasterizer's shadow padding, so the mesh would
353
- * put the gizmo a blur radius away from the thing it moves (the same reason
354
- * `collectFrames` reads userData).
355
- *
356
- * Null when nothing selected is on screen this frame, which includes a
357
- * selection whose lifetime has ended: an arm drawn for a layer that is not
358
- * there would drag something the author cannot see.
359
- */
360
- function selectionCentre(feed: StageFeed, selection: readonly string[]): Vec3 | null {
361
- const frames = feed.frames;
362
- if (frames === null || selection.length === 0) return null;
363
- let x = 0;
364
- let y = 0;
365
- let z = 0;
366
- let count = 0;
367
- // ONE FRAME PER ID, the first — the same rule `StageTools`' `byId` follows.
368
- // A cloner stamps its children N times under one id, and averaging all the
369
- // clones put the gizmo in the empty space between them rather than on the
370
- // layer the author selected.
371
- const seen = new Set<string>();
372
- for (const frame of frames) {
373
- if (!selection.includes(frame.id) || seen.has(frame.id)) continue;
374
- seen.add(frame.id);
375
- const e = frame.matrixWorld.elements;
376
- x += e[12]!;
377
- y += e[13]!;
378
- z += e[14]!;
379
- count += 1;
380
- }
381
- return count === 0 ? null : [x / count, y / count, z / count];
382
- }
383
-
384
- /**
385
- * World units per CSS pixel AT A POINT, which is what turns the arm's fixed
386
- * screen length into a world one.
387
- *
388
- * Both camera classes, because isolation swaps an ORTHOGRAPHIC camera into this
389
- * pane's slot (StageTools) and a perspective-only formula would size the gizmo
390
- * off a `fov` that camera does not have — reading `undefined` and drawing arms
391
- * of length NaN, which is a gizmo that silently disappears inside isolation.
392
- *
393
- * Perspective: the visible height at the point's own view depth, over the pane.
394
- * Orthographic: its frustum height over the pane, which has no depth term at
395
- * all — that is what orthographic means.
396
- */
397
- function worldPerPixel(camera: Camera, at: Vector3, paneHeight: number): number {
398
- const height = Math.max(1, paneHeight);
399
- const ortho = camera as OrthographicCamera;
400
- if (ortho.isOrthographicCamera === true) {
401
- const zoom = ortho.zoom > 0 ? ortho.zoom : 1;
402
- return Math.abs(ortho.top - ortho.bottom) / zoom / height;
403
- }
404
- const perspective = camera as PerspectiveCamera;
405
- const fov = Number.isFinite(perspective.fov) ? perspective.fov : 50;
406
- // View-space z runs NEGATIVE in front of the camera, so the depth is its
407
- // negation — and it is floored off zero, because a selection sitting exactly
408
- // at the eye would otherwise give every arm zero length.
409
- const depth = Math.max(-_view.copy(at).applyMatrix4(camera.matrixWorldInverse).z, 1e-3);
410
- return (2 * Math.tan((fov * DEG) / 2) * depth) / height;
411
- }