@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,501 @@
1
+ // THE MATERIAL MODE: the node editor, in the docked frame.
2
+ //
3
+ // It is chrome-only. Nodes remain plain shot data and the mounted StagePanel
4
+ // stays exactly where it is: this draws beside it and leaves one hole for it to
5
+ // render the isolated object into. `closeMaterialWorkspace` puts back the
6
+ // layout, the shading, the tool, the isolation and the selection the entry
7
+ // borrowed.
8
+ //
9
+ // ── WHERE EACH PART LANDS ───────────────────────────────────────────────
10
+ //
11
+ // One component, portalled into the shell's cells (panels/stage/cellSlots.tsx),
12
+ // so its state and the graph's React Flow provider stay one React subtree:
13
+ //
14
+ // LEFT the Project's graphs and the starters (MaterialLibrary.tsx);
15
+ // BAR the way back, the trail, the slot, the compile status, then the
16
+ // TSL toggle, the graph's zoom and Recenter pushed right;
17
+ // CENTRE the graph (or its TSL source), a seam, and the object's HOLE at
18
+ // the right of the canvas cell. The setup screen instead, while
19
+ // the mesh has no graph.
20
+ //
21
+ // The top bar, the inspector and the timeline stay on screen around it. The
22
+ // material's own rows (name, graph, exposed controls) are the inspector's
23
+ // MaterialSection: an exposed control is a per-mesh value with a keyframe
24
+ // dot, authored against the playhead, and the timeline under the graph is
25
+ // where it is scrubbed.
26
+ //
27
+ // ── WHY THE NODE INSPECTOR IS NOT HERE ──────────────────────────────────
28
+ //
29
+ // It showed ONE selected node's value away from the card the author was
30
+ // looking at, and it was the only way to type a number. Every field it had is
31
+ // on the card itself now (`MaterialNode.tsx`), which also removed a second
32
+ // commit path into a node's value.
33
+ //
34
+ // ── AND WHY THE SLOT PICKER IS IN THE BAR ───────────────────────────────
35
+ //
36
+ // A model ships one material per submesh, and schema v17 lets each carry its
37
+ // own graph. Which one is on the canvas is a property of the WORKSPACE and not
38
+ // of the document, exactly like the trail beside it, so it sits in the same
39
+ // row, and everything below is resolved through it.
40
+ import { lazy, Suspense, useRef, useState } from "react";
41
+ import { Combobox } from "@dailies-studio/ui";
42
+ import {
43
+ emitMaterialGraphTsl,
44
+ homeOf,
45
+ isGraphMaterial,
46
+ materialBindingForSlot,
47
+ validateMaterialGraph,
48
+ } from "@dailies-studio/shot/doc";
49
+ import type { MaterialGraph3D, MeshMaterialSlotInfo } from "@dailies-studio/shot/doc";
50
+ import { CHEVRON_LEFT_ICON } from "../../icons.js";
51
+ import { useShotEditor, useStore } from "../../state/context.js";
52
+ import { MATERIAL_COLUMN } from "../../state/panelSize.js";
53
+ import { shortcutHint } from "../../state/shortcuts.js";
54
+ import { workspaceOf } from "../../state/workspace.js";
55
+ import { ResizeHandle } from "../ResizeHandle.js";
56
+ import { InCell } from "../stage/cellSlots.js";
57
+ import { MATERIAL_STARTERS, MaterialLibrary } from "./MaterialLibrary.js";
58
+
59
+ const LazyMaterialGraphCanvas = lazy(async () => {
60
+ const module = await import("./MaterialGraphCanvas.js");
61
+ return { default: module.MaterialGraphCanvas };
62
+ });
63
+
64
+ /** How wide the preview hole may be: half the canvas cell, read LIVE at the
65
+ * drag. Undefined when the cell has no box yet (nothing laid out). */
66
+ function previewCeiling(centre: HTMLElement | null): number | undefined {
67
+ const width = centre?.getBoundingClientRect().width ?? 0;
68
+ return width > 0 ? width / 2 : undefined;
69
+ }
70
+
71
+ export function MaterialWorkspace() {
72
+ const store = useStore();
73
+ // THE MODE PORTALS ONLY IN ITS OWN WORKSPACE, so two modes can never fill
74
+ // one cell.
75
+ const own = useShotEditor((state) => workspaceOf(state) === "material");
76
+ const workspace = useShotEditor((state) => state.materialWorkspace);
77
+ const doc = useShotEditor((state) => state.doc);
78
+ const name = useShotEditor((state) => state.name);
79
+ const materialCompile = useShotEditor((state) => state.materialCompile);
80
+ const path = useShotEditor((state) => state.materialGraphPath);
81
+ const slots = useShotEditor((state) =>
82
+ state.materialWorkspace === null ? undefined : state.meshMaterialSlots[state.materialWorkspace.layerId],
83
+ );
84
+ // The camera is derived until the author orbits it, and a drag on the hole
85
+ // always orbits. Recenter is the visible way back, offered exactly when it
86
+ // is true.
87
+ const orbited = useShotEditor((state) => state.isolationOrbit);
88
+ const [sourceOpen, setSourceOpen] = useState(false);
89
+ // The place in the bar the graph's zoom is portalled into, from INSIDE the
90
+ // lazy canvas's React Flow provider (MaterialGraphCanvas.tsx). A state and
91
+ // not a ref, so the canvas renders again once the place exists.
92
+ const [zoomSlot, setZoomSlot] = useState<HTMLElement | null>(null);
93
+ const centreRef = useRef<HTMLElement | null>(null);
94
+ const holeRef = useRef<HTMLDivElement | null>(null);
95
+
96
+ if (!own || workspace === null || doc === null) return null;
97
+ const layer = homeOf(doc, workspace.containerId)?.layersById[workspace.layerId];
98
+ if (layer?.kind !== "mesh") return null;
99
+ // THROUGH THE SLOT. `workspaceMaterialId` in the store resolves it the same
100
+ // way for the keymap; both call `materialBindingForSlot` rather than each
101
+ // reading `material.slots` themselves.
102
+ const binding = isGraphMaterial(layer.mesh.material)
103
+ ? materialBindingForSlot(layer.mesh.material, workspace.slot)
104
+ : null;
105
+ const graph = binding === null ? undefined : doc.materials?.[binding.materialId];
106
+ const layerName = layer.label?.trim() || "Mesh";
107
+ const projectName = doc.title ?? name ?? doc.name;
108
+ const contextName =
109
+ doc.scenesById[workspace.containerId]?.name ?? doc.components?.[workspace.containerId]?.name ?? "Shot";
110
+
111
+ const problems = graph === undefined ? [] : validateMaterialGraph(graph, doc.assets);
112
+ // No slot test here: the store already resolves the verdict for the open
113
+ // slot (`cachedMaterialCompile`), and a slot without its own graph shows the
114
+ // whole model's verdict, which the stage reports under slot null.
115
+ const compile =
116
+ graph !== undefined &&
117
+ materialCompile.status !== "idle" &&
118
+ materialCompile.materialId === graph.id &&
119
+ materialCompile.layerId === layer.id
120
+ ? materialCompile
121
+ : { status: "idle" as const };
122
+ const compileLabel =
123
+ problems.length > 0
124
+ ? `${problems.length} issue${problems.length === 1 ? "" : "s"}`
125
+ : compile.status === "compiling"
126
+ ? "Compiling…"
127
+ : compile.status === "error"
128
+ ? "GPU error"
129
+ : compile.status === "ready"
130
+ ? "Live"
131
+ : "Waiting";
132
+ const compileTitle =
133
+ problems[0]?.message ??
134
+ (compile.status === "error"
135
+ ? compile.message
136
+ : compile.status === "compiling"
137
+ ? "The stage is compiling this material graph."
138
+ : compile.status === "ready"
139
+ ? "The stage compiled this graph and is previewing it on the isolated mesh."
140
+ : "Waiting for the stage compiler to report this material.");
141
+
142
+ return (
143
+ <>
144
+ <InCell name="left">
145
+ <MaterialLibrary layer={layer} layerName={layerName} currentId={graph?.id ?? null} slot={workspace.slot} />
146
+ </InCell>
147
+
148
+ <InCell name="bar">
149
+ <div className="material-bar">
150
+ {/* THE WAY OUT, first in the bar like every mode's. The trail's upper
151
+ rungs leave too; this is the one that is always in the same place. */}
152
+ <button
153
+ type="button"
154
+ className="back-chip material-back"
155
+ data-testid="material-back"
156
+ aria-label="Close material nodes"
157
+ title="Close material nodes (Esc)"
158
+ onClick={() => store.closeMaterialWorkspace()}
159
+ >
160
+ <span className="tool-icon" aria-hidden="true">
161
+ {CHEVRON_LEFT_ICON}
162
+ </span>
163
+ </button>
164
+ <MaterialCrumbs
165
+ projectName={projectName}
166
+ contextName={contextName}
167
+ layerName={layerName}
168
+ graphName={graph?.name ?? "Material"}
169
+ functionNames={path.map((id) => graph?.functions?.[id]?.name ?? id)}
170
+ />
171
+ <SlotPicker slots={slots} slot={workspace.slot} />
172
+ {graph === undefined ? null : (
173
+ <span
174
+ className={`material-compile-status${problems.length > 0 || compile.status === "error" ? " is-error" : ""}${compile.status === "compiling" ? " is-compiling" : ""}`}
175
+ role="status"
176
+ title={compileTitle}
177
+ >
178
+ <span aria-hidden="true" />
179
+ {compileLabel}
180
+ </span>
181
+ )}
182
+ {graph === undefined ? null : (
183
+ <div className="material-bar-end">
184
+ {/* The visible opener; Tab, Shift+A and a double-click on the
185
+ pane open the same palette (MaterialNodePalette.tsx). */}
186
+ <button
187
+ type="button"
188
+ className="btn"
189
+ data-testid="material-add-node"
190
+ title={`Add node${shortcutHint("graph.add")}`}
191
+ onClick={() => store.openMaterialPalette(null)}
192
+ >
193
+ Add node
194
+ </button>
195
+ <button
196
+ type="button"
197
+ className={`btn${sourceOpen ? " is-active" : ""}`}
198
+ aria-pressed={sourceOpen}
199
+ onClick={() => setSourceOpen((open) => !open)}
200
+ >
201
+ TSL
202
+ </button>
203
+ {/* Filled by the canvas while the graph is on screen; takes no
204
+ room of its own (`display: contents`). */}
205
+ <div className="material-zoom-slot" ref={setZoomSlot} />
206
+ {/* THE WAY BACK for an orbited preview. Nothing re-derives an
207
+ orbited camera, by design, and the hole takes no pointer (a
208
+ drag on it reaches the stage), so a control over it could not
209
+ be pressed. The stage owns the double-click (`onFitRequest`). */}
210
+ {orbited ? (
211
+ <button
212
+ type="button"
213
+ className="btn"
214
+ data-testid="material-refit"
215
+ title="Frame the object head-on again (Home)"
216
+ onClick={() => store.fitWorkingView()}
217
+ >
218
+ Recenter
219
+ </button>
220
+ ) : null}
221
+ </div>
222
+ )}
223
+ </div>
224
+ </InCell>
225
+
226
+ <InCell name="centre">
227
+ <section
228
+ className="material-workspace"
229
+ aria-label={`Material graph for ${layerName}`}
230
+ ref={centreRef}
231
+ >
232
+ {graph === undefined ? (
233
+ <MaterialSetup
234
+ layerId={layer.id}
235
+ legacy={layer.mesh.material !== undefined && !isGraphMaterial(layer.mesh.material)}
236
+ />
237
+ ) : (
238
+ <>
239
+ <div className="material-graph-region">
240
+ {/* THE GPU'S OWN WORDS, on screen and not only in the pill's
241
+ tooltip, which a touch user cannot reach. Validator problems
242
+ need no strip: their node cards already show them. */}
243
+ {compile.status === "error" ? (
244
+ <div className="code-studio-strip is-error" role="alert" data-testid="material-compile-error">
245
+ {compile.message}
246
+ </div>
247
+ ) : null}
248
+ {sourceOpen ? (
249
+ <SourceView graph={graph} />
250
+ ) : (
251
+ <Suspense
252
+ fallback={
253
+ <section className="material-graph-canvas is-loading" aria-label="Loading material node graph">
254
+ <p className="material-empty-copy">Loading node canvas…</p>
255
+ </section>
256
+ }
257
+ >
258
+ <LazyMaterialGraphCanvas
259
+ graph={graph}
260
+ materialId={graph.id}
261
+ layerId={layer.id}
262
+ slot={workspace.slot}
263
+ zoomSlot={zoomSlot}
264
+ />
265
+ </Suspense>
266
+ )}
267
+ </div>
268
+ {/* THE OBJECT, AND NOTHING ELSE IN HERE WITH IT.
269
+ A HOLE and not a component: the stage iframe behind the shell
270
+ is still rendering the isolated layer, and `measureInset` hands
271
+ it this element's rect to frame into. So what shows here is the
272
+ real object under the real material, which is the only reason
273
+ this mode can claim preview === export. `.stage-window` is the
274
+ class `measureInset` LOOKS FOR, shared with every mode's hole.
275
+ An `aria-hidden` empty div, because a screen reader has nothing
276
+ to read in a GL surface this document does not own. */}
277
+ <div
278
+ className="stage-window material-preview-hole"
279
+ aria-hidden="true"
280
+ data-testid="material-preview-hole"
281
+ ref={holeRef}
282
+ />
283
+ {/* ON THE SEAM, beside the hole it sizes, from the same variable
284
+ (`--material-preview-w`). A drag starts from the width the hole
285
+ is DRAWN at and is capped by half the live canvas cell, so the
286
+ edge moves with the pointer from the first pixel. */}
287
+ <ResizeHandle
288
+ axis="x"
289
+ getValue={() =>
290
+ holeRef.current?.getBoundingClientRect().width || store.getState().materialColumnWidth
291
+ }
292
+ // The hole is on the RIGHT, so moving the pointer along +x
293
+ // shrinks it: the inspector's rule.
294
+ sign={-1}
295
+ onChange={(px) => store.setMaterialColumnWidth(px, previewCeiling(centreRef.current))}
296
+ onReset={() => store.setMaterialColumnWidth(MATERIAL_COLUMN.default)}
297
+ className="material-workspace-resize"
298
+ label="Resize the material preview"
299
+ />
300
+ </>
301
+ )}
302
+ </section>
303
+ </InCell>
304
+ </>
305
+ );
306
+ }
307
+
308
+ /**
309
+ * The trail: Project › Shot › Mesh › Material › function › function.
310
+ *
311
+ * The first three rungs are buttons, and each is a way out.
312
+ *
313
+ * The first three rungs all close the workspace, because that is what going
314
+ * back up from a shader means: `closeMaterialWorkspace` restores the viewport,
315
+ * the tool and the selection the entry borrowed. The Project rung additionally
316
+ * leaves the shot, which is the one place the two differ.
317
+ */
318
+ function MaterialCrumbs({
319
+ projectName,
320
+ contextName,
321
+ layerName,
322
+ graphName,
323
+ functionNames,
324
+ }: {
325
+ projectName: string;
326
+ contextName: string;
327
+ layerName: string;
328
+ graphName: string;
329
+ functionNames: readonly string[];
330
+ }) {
331
+ const store = useStore();
332
+ const depth = functionNames.length;
333
+ const leave = (): void => store.closeMaterialWorkspace();
334
+ return (
335
+ <nav className="material-workspace-crumbs" data-testid="material-crumbs">
336
+ <button
337
+ type="button"
338
+ className="crumb"
339
+ title="The whole Project"
340
+ onClick={() => {
341
+ leave();
342
+ store.leaveScene();
343
+ }}
344
+ >
345
+ {projectName}
346
+ </button>
347
+ <button type="button" className="crumb" onClick={leave}>
348
+ {contextName}
349
+ </button>
350
+ <button type="button" className="crumb" onClick={leave}>
351
+ {layerName}
352
+ </button>
353
+ <button
354
+ type="button"
355
+ className={`crumb${depth === 0 ? " is-current" : ""}`}
356
+ aria-current={depth === 0}
357
+ // ONE `exitMaterialSubgraph` PER LEVEL rather than a "go to root" verb,
358
+ // because the store's rung is one level and adding a second spelling of
359
+ // the same climb is how the two end up disagreeing about what clears.
360
+ onClick={() => {
361
+ for (let level = 0; level < depth; level += 1) store.exitMaterialSubgraph();
362
+ }}
363
+ >
364
+ {graphName}
365
+ </button>
366
+ {functionNames.map((fnName, index) => (
367
+ <button
368
+ key={`${index}:${fnName}`}
369
+ type="button"
370
+ className={`crumb${index === depth - 1 ? " is-current" : ""}`}
371
+ aria-current={index === depth - 1}
372
+ onClick={() => {
373
+ for (let level = index; level < depth - 1; level += 1) store.exitMaterialSubgraph();
374
+ }}
375
+ >
376
+ {fnName}
377
+ </button>
378
+ ))}
379
+ </nav>
380
+ );
381
+ }
382
+
383
+ /** The rail's value for the layer-wide binding. Not a slot index, so it cannot
384
+ * collide with one. */
385
+ const WHOLE_MODEL = "all";
386
+
387
+ /**
388
+ * Which of a model's materials is on the canvas.
389
+ *
390
+ * ABSENT when the mesh has no separate materials, which is every primitive and
391
+ * every model with one: a dropdown with a single unchangeable row is furniture
392
+ * that says nothing. "Whole model" is first and is the default, because it is
393
+ * the binding a mesh has before anybody picks a submesh and the only one a
394
+ * primitive can have.
395
+ */
396
+ function SlotPicker({
397
+ slots,
398
+ slot,
399
+ }: {
400
+ slots: readonly MeshMaterialSlotInfo[] | undefined;
401
+ slot: number | null;
402
+ }) {
403
+ const store = useStore();
404
+ if (slots === undefined || slots.length === 0) return null;
405
+ return (
406
+ <Combobox
407
+ className="material-slot-picker"
408
+ // `WHOLE` and not the empty string, which `Combobox` reads as "no value"
409
+ // and renders as a placeholder, so the layer-wide binding (the default
410
+ // and the commonest state) showed as a blank control.
411
+ value={slot === null ? WHOLE_MODEL : String(slot)}
412
+ ariaLabel="Material slot"
413
+ options={[
414
+ { value: WHOLE_MODEL, label: "Whole model" },
415
+ ...slots.map((entry) => ({
416
+ value: String(entry.index),
417
+ label: `${entry.index} · ${entry.name.trim() === "" ? "Material" : entry.name}`,
418
+ })),
419
+ ]}
420
+ onChange={(value) => store.setMaterialSlot(value === WHOLE_MODEL ? null : Number(value))}
421
+ />
422
+ );
423
+ }
424
+
425
+ /**
426
+ * The centre while the mesh has no graph: what this place is, and the three
427
+ * ways to start. The graphs the Project already has are one column over, in
428
+ * the library, which offers the same starters as rows.
429
+ */
430
+ function MaterialSetup({ layerId, legacy }: { layerId: string; legacy: boolean }) {
431
+ const store = useStore();
432
+ return (
433
+ <div className="material-setup">
434
+ <div className="material-setup-intro">
435
+ <span className="material-eyebrow">Material nodes</span>
436
+ <h2>Build the surface on the real object.</h2>
437
+ <p>
438
+ The mesh stays isolated on the stage while nodes describe its TSL material. Start from a neutral model
439
+ here, or pick a graph this Project already has from the library on the left.
440
+ </p>
441
+ {legacy ? (
442
+ <p className="material-legacy-note">
443
+ This mesh uses a legacy catalog material. It will keep rendering until you replace it with a node graph.
444
+ </p>
445
+ ) : null}
446
+ </div>
447
+ <section className="material-starters" aria-label="Start a material">
448
+ {MATERIAL_STARTERS.map((starter) => (
449
+ <button
450
+ type="button"
451
+ className="material-starter-card"
452
+ key={starter.id}
453
+ onClick={() => store.createMeshGraphMaterial(layerId, starter.id)}
454
+ >
455
+ <span className={`material-starter-orb is-${starter.id}`} aria-hidden="true" />
456
+ <span>
457
+ <strong>{starter.name}</strong>
458
+ <small>{starter.detail}</small>
459
+ </span>
460
+ </button>
461
+ ))}
462
+ </section>
463
+ </div>
464
+ );
465
+ }
466
+
467
+ /** The generated TSL, read-only, in place of the graph. */
468
+ function SourceView({ graph }: { graph: MaterialGraph3D }) {
469
+ const source = sourceOf(graph);
470
+ return (
471
+ <section className="material-source-view" aria-label="Generated TSL source">
472
+ <div className="material-source-heading">
473
+ <div>
474
+ <strong>Generated TSL</strong>
475
+ <span>Read-only reference · runtime compiles the data graph directly</span>
476
+ </div>
477
+ <button
478
+ type="button"
479
+ className="btn"
480
+ onClick={() => void navigator.clipboard?.writeText(source.text)}
481
+ disabled={source.error !== null}
482
+ >
483
+ Copy
484
+ </button>
485
+ </div>
486
+ {source.error === null ? (
487
+ <pre tabIndex={0}>{source.text}</pre>
488
+ ) : (
489
+ <div className="material-source-error">{source.error}</div>
490
+ )}
491
+ </section>
492
+ );
493
+ }
494
+
495
+ function sourceOf(graph: MaterialGraph3D): { text: string; error: string | null } {
496
+ try {
497
+ return { text: emitMaterialGraphTsl(graph), error: null };
498
+ } catch (error) {
499
+ return { text: "", error: error instanceof Error ? error.message : "Unable to generate TSL." };
500
+ }
501
+ }
@@ -0,0 +1,70 @@
1
+ // The live thumbnail on a card, and the context that keeps it off every other
2
+ // card's props.
3
+ //
4
+ // The stage answers `requestNodePreviews` with `ImageBitmap`s. A bitmap is not
5
+ // a React value: it cannot be compared, it cannot be serialised, and it holds
6
+ // GPU memory until somebody closes it. So it never enters `MaterialFlowData` —
7
+ // see that module's header. It rides a context whose value changes when a batch
8
+ // lands, which re-renders the handful of components that actually read one.
9
+ //
10
+ // Drawn with `drawImage` into a `<canvas>` rather than handed to an `<img>`,
11
+ // because an ImageBitmap has no URL: `createObjectURL` does not take one, and
12
+ // the alternative (encode to a blob per thumbnail) is a second copy of every
13
+ // pixel for a picture that is about to be scaled down anyway.
14
+ import { createContext, useContext, useEffect, useRef } from "react";
15
+ import type { ReactNode } from "react";
16
+ import type { PreviewEntry } from "./previewScheduler.js";
17
+
18
+ const PreviewContext = createContext<ReadonlyMap<string, PreviewEntry>>(new Map());
19
+
20
+ export function MaterialPreviewProvider({
21
+ entries,
22
+ children,
23
+ }: {
24
+ entries: ReadonlyMap<string, PreviewEntry>;
25
+ children: ReactNode;
26
+ }) {
27
+ return <PreviewContext.Provider value={entries}>{children}</PreviewContext.Provider>;
28
+ }
29
+
30
+ /** What the stage last said about one node: a picture, a sentence, or nothing
31
+ * yet. */
32
+ export function useNodePreview(nodeId: string): PreviewEntry | undefined {
33
+ return useContext(PreviewContext).get(nodeId);
34
+ }
35
+
36
+ /** The thumbnail edge in CSS px. The request asks for the same number in device
37
+ * px, so a retina card draws a downscaled bitmap rather than a soft one. */
38
+ export const NODE_PREVIEW_PX = 96;
39
+
40
+ export function NodePreview({ nodeId }: { nodeId: string }) {
41
+ const entry = useNodePreview(nodeId);
42
+ const canvas = useRef<HTMLCanvasElement | null>(null);
43
+
44
+ useEffect(() => {
45
+ const el = canvas.current;
46
+ if (el === null || entry === undefined || typeof entry === "string") return;
47
+ const context = el.getContext("2d");
48
+ if (context === null) return;
49
+ // The bitmap arrives at whatever size the stage rendered; the card decides
50
+ // how big it is drawn. `clearRect` first, because a batch that failed for
51
+ // this node leaves the previous picture behind otherwise.
52
+ context.clearRect(0, 0, el.width, el.height);
53
+ context.drawImage(entry, 0, 0, el.width, el.height);
54
+ }, [entry]);
55
+
56
+ return (
57
+ <div className="material-node-preview" aria-hidden={typeof entry !== "string"}>
58
+ {typeof entry === "string" ? (
59
+ <span className="material-node-preview-message">{entry}</span>
60
+ ) : (
61
+ <canvas
62
+ ref={canvas}
63
+ className={`material-node-preview-canvas${entry === undefined ? " is-waiting" : ""}`}
64
+ width={NODE_PREVIEW_PX}
65
+ height={NODE_PREVIEW_PX}
66
+ />
67
+ )}
68
+ </div>
69
+ );
70
+ }