@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,656 @@
1
+ // FONT PICKS, PLANNED. Pure functions over a document, the Google catalog and
2
+ // the workspace font list: the files each target text layer needs for a pick
3
+ // (its face set), the weight and style it then draws at, the stage preview of
4
+ // a pick, what the Typography rows offer for a selection, and the families a
5
+ // Project already holds. The store's verbs (`applyFont`, `setFontWeight`,
6
+ // `setFontStyle`, `previewFont`, `selfHostFont`, `followFontWeights`) fetch
7
+ // and write what these plan, in one edit.
8
+ //
9
+ // The catalog and its subset ranges are lazy chunks, so every function here
10
+ // takes them already loaded, and answers "loading" where it needs one it was
11
+ // not given.
12
+ import { drawsMachineFont, fontStack, primaryFontFamily } from "@dailies-studio/shot/doc";
13
+ import type { Doc3D, Layer3D, ShotAsset } from "@dailies-studio/shot/doc";
14
+ import {
15
+ appliesVariableFile,
16
+ catalogEntryOf,
17
+ catalogFileOf,
18
+ categoryFallback,
19
+ faceWeight,
20
+ findGoogleFont,
21
+ googleFacePlan,
22
+ googleFaceSet,
23
+ googleFontFileName,
24
+ googleStyle,
25
+ nearestWeight,
26
+ subsetsForText,
27
+ } from "../fonts/googleFonts.js";
28
+ import type { GoogleFace, GoogleFontEntry, GoogleFontRanges, GoogleFontStyle } from "../fonts/googleFonts.js";
29
+ import { workspaceFontUrl } from "../api.js";
30
+ import type { WorkspaceFont } from "../api.js";
31
+ import type { ShotFontPreview, ShotFontPreviewFace } from "../bridgeTypes.js";
32
+ import { storedName } from "./storyboardModel.js";
33
+
34
+ export type TextLayer = Extract<Layer3D, { kind: "text" }>;
35
+
36
+ /** What the picker hands the store: one family for the selected text. */
37
+ export type FontPick =
38
+ /** A Google catalog family: its files are copied from jsDelivr, or reused. */
39
+ | { kind: "google"; entry: GoogleFontEntry }
40
+ /** A workspace family: its rows in the workspace font list. */
41
+ | { kind: "workspace"; family: string; fonts: readonly WorkspaceFont[] }
42
+ /** A family whose files the Project already holds: nothing is fetched. */
43
+ | { kind: "project"; family: string; fallback: string; faces: readonly ProjectFace[] };
44
+
45
+ /** One face of a family the Project holds: the asset ids a layer takes. */
46
+ export interface ProjectFace {
47
+ fontAsset: string;
48
+ fontSubsets?: readonly string[];
49
+ /** A static face's weight. Absent for a variable face, or when unknown. */
50
+ weight?: number;
51
+ /** A variable face's `wght` range. */
52
+ axis?: readonly [number, number];
53
+ /** Absent when unknown. */
54
+ italic?: boolean;
55
+ }
56
+
57
+ /** One file of a face set that may have to be fetched. */
58
+ export interface FaceFile {
59
+ /** Where the bytes download from; also what dedupes it across layers. */
60
+ url: string;
61
+ /** The name it is uploaded as. */
62
+ name: string;
63
+ /** Finds a copy the Project already holds, so nothing is fetched. */
64
+ reuse: (asset: ShotAsset) => boolean;
65
+ /** Absent for a combined or whole file. */
66
+ unicodeRange?: string;
67
+ /** Set for a workspace font, which downloads through its own route. */
68
+ workspace?: WorkspaceFont;
69
+ /** Bytes already in hand (an upload's own file): nothing is downloaded. */
70
+ file?: File | (() => Promise<File>);
71
+ }
72
+
73
+ /** A file of a face set: one to fetch or reuse, or a Project asset. */
74
+ export type FaceSource = FaceFile | { asset: string };
75
+
76
+ /** What one target text layer becomes. */
77
+ export interface LayerFontPlan {
78
+ layerId: string;
79
+ /** The face set, `fontAsset` first. Empty keeps the layer's own files. */
80
+ files: readonly FaceSource[];
81
+ /** The `fontFamily` to write; null keeps the layer's. */
82
+ fontFamily: string | null;
83
+ /** The weight the face draws at, written to the layer. */
84
+ weight: number;
85
+ /** The style the face is, written to the layer (upright is no key). */
86
+ style: GoogleFontStyle;
87
+ }
88
+
89
+ /** What a font write came to. `message` says why for refused and failed,
90
+ * and names the locked layers an applied write skipped. */
91
+ export interface FontApplyOutcome {
92
+ status: "applied" | "refused" | "failed" | "superseded";
93
+ message?: string;
94
+ }
95
+
96
+ /** The Font field's in-flight apply (`ShotEditorState.fontApply`). */
97
+ export interface FontApplyProgress {
98
+ /** The family being applied, as the Font field names it. While a
99
+ * workspace upload runs, before the server has named the family, the
100
+ * files: "1 font file", "3 font files". */
101
+ family: string;
102
+ /** Downloading the face set's files, or copying them into the Project. */
103
+ phase: "download" | "upload";
104
+ /** Bytes read across the face set's downloads. */
105
+ loaded: number;
106
+ /** Their whole size, or null while any download's size is unknown. */
107
+ total: number | null;
108
+ /** Why it failed, or null while it runs. `retryFontApply` runs the same
109
+ * apply again on the same layers. */
110
+ failed: string | null;
111
+ /** The text layers the write lands on, captured at the call, so the Font
112
+ * field shows it only while one of them is selected. */
113
+ layerIds: readonly string[];
114
+ }
115
+
116
+ /** A target text layer as an apply reads it at the call. */
117
+ export interface FontTargetLayer {
118
+ id: string;
119
+ weight: number;
120
+ style: GoogleFontStyle;
121
+ content: string;
122
+ }
123
+
124
+ export const styleOf = (layer: TextLayer): GoogleFontStyle => layer.text.style.fontStyle ?? "normal";
125
+
126
+ export const targetOf = (layer: TextLayer): FontTargetLayer => ({
127
+ id: layer.id,
128
+ weight: layer.text.style.weight,
129
+ style: styleOf(layer),
130
+ content: layer.text.content,
131
+ });
132
+
133
+ export { fontStack };
134
+
135
+ const clamp = (value: number, lo: number, hi: number): number => Math.min(hi, Math.max(lo, value));
136
+
137
+ /** What a file says about its face: the weight it holds, or its `wght`
138
+ * range, and its slant. Each absent when unknown. */
139
+ export interface FaceFacts {
140
+ weight?: number;
141
+ axis?: readonly [number, number];
142
+ italic?: boolean;
143
+ }
144
+
145
+ /**
146
+ * The face a layer that wants `weight` and `style` draws from a family's
147
+ * files, the browser's way: a file of that style when the family has one,
148
+ * then the CSS Fonts 4 nearest weight (`nearestWeight`), a variable file
149
+ * counting as every weight on its axis. Answers the drawn weight and style.
150
+ * Files that say nothing about themselves (no weight, axis or italic, such as
151
+ * a font uploaded before its name table was read) keep the layer's own.
152
+ */
153
+ export function chooseFace<T>(
154
+ faces: readonly T[],
155
+ facts: (face: T) => FaceFacts,
156
+ weight: number,
157
+ style: GoogleFontStyle,
158
+ ): { face: T; weight: number; style: GoogleFontStyle } {
159
+ const known = faces.filter((face) => {
160
+ const f = facts(face);
161
+ return f.weight !== undefined || f.axis !== undefined || f.italic !== undefined;
162
+ });
163
+ if (known.length === 0) return { face: faces[0]!, weight, style };
164
+ const styled = known.filter((face) => (facts(face).italic === true) === (style === "italic"));
165
+ const pool = styled.length > 0 ? styled : known;
166
+ const drawn = pool.map((face) => {
167
+ const { axis, weight: own } = facts(face);
168
+ return axis === undefined ? (own ?? 400) : clamp(weight, axis[0], axis[1]);
169
+ });
170
+ const best = nearestWeight({ weights: drawn }, weight);
171
+ const face = pool[drawn.indexOf(best)]!;
172
+ return { face, weight: best, style: facts(face).italic === true ? "italic" : "normal" };
173
+ }
174
+
175
+ /** A workspace font's facts. A variable file's axis wins over its weight
176
+ * class, which only names its default instance. */
177
+ export function workspaceFacts(font: WorkspaceFont): FaceFacts {
178
+ return {
179
+ ...(font.wghtAxis !== undefined
180
+ ? { axis: [font.wghtAxis[0], font.wghtAxis[2]] as const }
181
+ : font.weight !== undefined
182
+ ? { weight: font.weight }
183
+ : {}),
184
+ ...(font.italic === undefined ? {} : { italic: font.italic }),
185
+ };
186
+ }
187
+
188
+ const projectFacts = (face: ProjectFace): FaceFacts => face;
189
+
190
+ const googleFile = (face: GoogleFace): FaceFile => ({
191
+ url: face.url,
192
+ name: face.fileName,
193
+ // A catalog copy by its stored name, which says family, weight, style and
194
+ // subset (`catalogFileOf`).
195
+ reuse: (asset) => asset.kind === "font" && storedName(asset.path) === face.fileName,
196
+ ...(face.unicodeRange === undefined ? {} : { unicodeRange: face.unicodeRange }),
197
+ });
198
+
199
+ /**
200
+ * A Google family on each layer, keeping the layer's weight and style where
201
+ * the family can draw them (`googleFacePlan`, which the MCP `set_font` op
202
+ * plans with too): a static family takes the nearest weight's face set and
203
+ * writes that weight; a variable one takes its `wght` set and keeps the
204
+ * layer's weight clamped to the axis. A static-only family (CJK) never takes
205
+ * a variable file (`faceWeight`).
206
+ */
207
+ export function planGooglePick(
208
+ entry: GoogleFontEntry,
209
+ ranges: GoogleFontRanges,
210
+ layers: readonly FontTargetLayer[],
211
+ ): LayerFontPlan[] {
212
+ const fontFamily = fontStack(entry.family, categoryFallback(entry.category));
213
+ const sets = new Map<string, FaceFile[]>();
214
+ return layers.map((layer) => {
215
+ const plan = googleFacePlan(entry, ranges, layer.weight, layer.style);
216
+ const key = `${plan.at}:${plan.style}`;
217
+ let files = sets.get(key);
218
+ if (files === undefined) {
219
+ files = plan.faces.map(googleFile);
220
+ sets.set(key, files);
221
+ }
222
+ return { layerId: layer.id, files, fontFamily, weight: plan.weight, style: plan.style };
223
+ });
224
+ }
225
+
226
+ /** Inter's upright variable face set from the chrome's own bundled files
227
+ * (`BUNDLED_INTER_FILES`): the catalog's file names and ranges, so it is the
228
+ * same copy a pick of Inter makes, and the bundle's urls. A subset the
229
+ * bundle lacks is left out. */
230
+ export function bundledInterFaceSet(
231
+ inter: GoogleFontEntry,
232
+ ranges: GoogleFontRanges,
233
+ bundled: Readonly<Record<string, string>>,
234
+ ): FaceFile[] {
235
+ return googleFaceSet(inter, ranges, "variable", "normal").flatMap((face) => {
236
+ const url = bundled[face.subset];
237
+ return url === undefined ? [] : [{ ...googleFile(face), url }];
238
+ });
239
+ }
240
+
241
+ /**
242
+ * Whether the Project font file at `path` holds the workspace font `font`'s
243
+ * bytes. Both stores name a file `<hash8>-<safe name>` (`workspaceFontId`,
244
+ * `storedAssetName`), and a Project upload of bytes it already holds answers
245
+ * the name it first stored them under, so a copy's path need not be the id.
246
+ */
247
+ export const isCopyOf = (path: string, font: WorkspaceFont): boolean => path.slice(0, 9) === font.id.slice(0, 9);
248
+
249
+ const workspaceFile = (font: WorkspaceFont): FaceFile => ({
250
+ url: workspaceFontUrl(font.id),
251
+ name: font.filename,
252
+ reuse: (asset) => asset.kind === "font" && isCopyOf(asset.path, font),
253
+ workspace: font,
254
+ });
255
+
256
+ /** A workspace family on each layer: the file of the layer's style (when the
257
+ * family has one) nearest its weight, or a variable file covering it. */
258
+ export function planWorkspacePick(
259
+ family: string,
260
+ fonts: readonly WorkspaceFont[],
261
+ layers: readonly FontTargetLayer[],
262
+ ): LayerFontPlan[] {
263
+ const fontFamily = fontStack(family);
264
+ return layers.map((layer) => {
265
+ const chosen = chooseFace(fonts, workspaceFacts, layer.weight, layer.style);
266
+ return { layerId: layer.id, files: [workspaceFile(chosen.face)], fontFamily, weight: chosen.weight, style: chosen.style };
267
+ });
268
+ }
269
+
270
+ /** A family the Project holds, on each layer, from its own files. */
271
+ export function planProjectPick(
272
+ pick: Extract<FontPick, { kind: "project" }>,
273
+ layers: readonly FontTargetLayer[],
274
+ ): LayerFontPlan[] {
275
+ const fontFamily = fontStack(pick.family, pick.fallback);
276
+ return layers.map((layer) => {
277
+ const chosen = chooseFace(pick.faces, projectFacts, layer.weight, layer.style);
278
+ const files = [chosen.face.fontAsset, ...(chosen.face.fontSubsets ?? [])].map((asset) => ({ asset }));
279
+ return { layerId: layer.id, files, fontFamily, weight: chosen.weight, style: chosen.style };
280
+ });
281
+ }
282
+
283
+ /** The one dispatcher. A Google pick needs the subset ranges loaded. */
284
+ export function planFontPick(
285
+ pick: FontPick,
286
+ layers: readonly FontTargetLayer[],
287
+ ranges: GoogleFontRanges | null,
288
+ ): LayerFontPlan[] {
289
+ switch (pick.kind) {
290
+ case "google":
291
+ if (ranges === null) throw new RangeError("A Google font is planned with the subset ranges loaded.");
292
+ return planGooglePick(pick.entry, ranges, layers);
293
+ case "workspace":
294
+ return planWorkspacePick(pick.family, pick.fonts, layers);
295
+ case "project":
296
+ return planProjectPick(pick, layers);
297
+ }
298
+ }
299
+
300
+ /** The family a pick puts on text, as the Font field names it. */
301
+ export function pickFamily(pick: FontPick): string {
302
+ return pick.kind === "google" ? pick.entry.family : pick.family;
303
+ }
304
+
305
+ const assetUrl = (path: string): string => `/studio/assets/${encodeURIComponent(path)}`;
306
+
307
+ /**
308
+ * The stage preview of a pick on `layers` (bridge v50). The face is the one
309
+ * the apply would give the FIRST layer (its weight and style rule), and only
310
+ * the layers the apply gives that same face show it: one preview family holds
311
+ * one face, which the stage would draw for any style a layer asks for, so a
312
+ * mixed selection's other layers keep their own font during the hover rather
313
+ * than show a face the apply will not give them. A Google family loads only
314
+ * the subsets those layers' text needs. Null with no layers.
315
+ */
316
+ export function fontPreviewOf(
317
+ pick: FontPick,
318
+ containerId: string,
319
+ layers: readonly FontTargetLayer[],
320
+ ranges: GoogleFontRanges | null,
321
+ assets: Doc3D["assets"],
322
+ ): ShotFontPreview | null {
323
+ const first = layers[0];
324
+ if (first === undefined) return null;
325
+ const sameFace = (key: (layer: FontTargetLayer) => string): FontTargetLayer[] =>
326
+ layers.filter((layer) => key(layer) === key(first));
327
+ if (pick.kind === "google") {
328
+ if (ranges === null) throw new RangeError("A Google font is previewed with the subset ranges loaded.");
329
+ const { entry } = pick;
330
+ const style = googleStyle(entry, first.style);
331
+ const at = faceWeight(entry, first.weight);
332
+ const shown = sameFace((layer) => `${faceWeight(entry, layer.weight)}:${googleStyle(entry, layer.style)}`);
333
+ const wanted = subsetsForText(entry, ranges, shown.map((layer) => layer.content).join("\n"));
334
+ const axis = entry.axes?.["wght"];
335
+ return {
336
+ containerId,
337
+ layerIds: shown.map((layer) => layer.id),
338
+ family: entry.family,
339
+ fallback: categoryFallback(entry.category),
340
+ style,
341
+ weightRange: at === "variable" && axis !== undefined ? [axis[0], axis[1]] : [1, 1000],
342
+ faces: googleFaceSet(entry, ranges, at, style)
343
+ .filter((face) => wanted.includes(face.subset))
344
+ .map((face) => ({ url: face.url, ...(face.unicodeRange === undefined ? {} : { unicodeRange: face.unicodeRange }) })),
345
+ };
346
+ }
347
+ if (pick.kind === "workspace") {
348
+ const faceOf = (layer: FontTargetLayer) => chooseFace(pick.fonts, workspaceFacts, layer.weight, layer.style);
349
+ const chosen = faceOf(first);
350
+ const axis = workspaceFacts(chosen.face).axis;
351
+ return {
352
+ containerId,
353
+ layerIds: sameFace((layer) => {
354
+ const face = faceOf(layer);
355
+ return `${face.face.id}:${face.style}`;
356
+ }).map((layer) => layer.id),
357
+ family: pick.family,
358
+ fallback: "sans-serif",
359
+ style: chosen.style,
360
+ weightRange: axis === undefined ? [1, 1000] : axis,
361
+ faces: [{ url: workspaceFontUrl(chosen.face.id) }],
362
+ };
363
+ }
364
+ const faceOf = (layer: FontTargetLayer) => chooseFace(pick.faces, projectFacts, layer.weight, layer.style);
365
+ const chosen = faceOf(first);
366
+ const faces: ShotFontPreviewFace[] = [];
367
+ for (const id of [chosen.face.fontAsset, ...(chosen.face.fontSubsets ?? [])]) {
368
+ const asset = assets[id];
369
+ if (asset?.kind !== "font") continue;
370
+ faces.push({ url: assetUrl(asset.path), ...(asset.unicodeRange === undefined ? {} : { unicodeRange: asset.unicodeRange }) });
371
+ }
372
+ return {
373
+ containerId,
374
+ layerIds: sameFace((layer) => {
375
+ const face = faceOf(layer);
376
+ return `${face.face.fontAsset}:${face.style}`;
377
+ }).map((layer) => layer.id),
378
+ family: pick.family,
379
+ fallback: pick.fallback,
380
+ style: chosen.style,
381
+ weightRange: chosen.face.axis ?? [1, 1000],
382
+ faces,
383
+ };
384
+ }
385
+
386
+ // ── What the Typography rows offer ─────────────────────────────────────────
387
+
388
+ /** What a text layer's family can draw, for the Weight and Italic rows and
389
+ * the machine-font note. */
390
+ export type LayerFontFacts =
391
+ /** Its file is a catalog copy, or it draws a machine font, and the catalog
392
+ * was not handed in yet. */
393
+ | { kind: "loading" }
394
+ /** A Google family. `axis` is set when it applies `wght` files (a number
395
+ * field clamped to it); else `weights` is the named list. */
396
+ | {
397
+ kind: "catalog";
398
+ family: string;
399
+ entry: GoogleFontEntry;
400
+ weights: readonly number[];
401
+ axis: readonly [number, number] | null;
402
+ italic: boolean;
403
+ }
404
+ /** A workspace family, `fonts` its rows. `axis` is the variable file's range
405
+ * for the layer's style when the family has one. */
406
+ | {
407
+ kind: "workspace";
408
+ family: string;
409
+ fonts: readonly WorkspaceFont[];
410
+ weights: readonly number[];
411
+ axis: readonly [number, number] | null;
412
+ italic: boolean;
413
+ }
414
+ /** Another file (an upload, a copy the workspace list no longer has, or
415
+ * the list not handed in): any weight, and italic as the file draws it. */
416
+ | { kind: "file"; family: string }
417
+ /** No file: the computer's font. `selfHost` is the catalog family "Self-host
418
+ * <family>" applies (`selfHostFont`), or null ("Upload font file"). */
419
+ | { kind: "machine"; family: string; selfHost: GoogleFontEntry | null };
420
+
421
+ /** The workspace family a font row belongs to: rows that share its family. */
422
+ export const workspaceFamilyOf = (fonts: readonly WorkspaceFont[], font: WorkspaceFont): WorkspaceFont[] =>
423
+ fonts.filter((f) => f.family === font.family);
424
+
425
+ /** The facts of one text layer. `catalog` and `workspace` are what is loaded,
426
+ * or null. */
427
+ export function fontFactsOf(
428
+ layer: TextLayer,
429
+ assets: Doc3D["assets"],
430
+ catalog: readonly GoogleFontEntry[] | null,
431
+ workspace: readonly WorkspaceFont[] | null,
432
+ ): LayerFontFacts {
433
+ const style = layer.text.style;
434
+ const family = primaryFontFamily(style.fontFamily);
435
+ if (drawsMachineFont(layer, assets)) {
436
+ return catalog === null ? { kind: "loading" } : { kind: "machine", family, selfHost: findGoogleFont(catalog, family) ?? null };
437
+ }
438
+ const asset = style.fontAsset === undefined ? undefined : assets[style.fontAsset];
439
+ if (asset === undefined) return { kind: "file", family };
440
+ const own = workspace?.find((font) => isCopyOf(asset.path, font));
441
+ if (own !== undefined && workspace !== null) {
442
+ const fonts = workspaceFamilyOf(workspace, own);
443
+ const italic = (style.fontStyle ?? "normal") === "italic";
444
+ // The weights of the layer's own style when the family has files in it:
445
+ // an italic cut only at 400 offers 400 on italic text, so the menu never
446
+ // names a weight the apply would draw from the upright file.
447
+ const inStyle = fonts.filter((f) => (f.italic === true) === italic);
448
+ const pool = inStyle.length > 0 ? inStyle : fonts;
449
+ const weights = [...new Set(pool.flatMap((f) => (f.wghtAxis === undefined && f.weight !== undefined ? [f.weight] : [])))];
450
+ const variable = pool.find((f) => f.wghtAxis !== undefined) ?? fonts.find((f) => f.wghtAxis !== undefined);
451
+ return {
452
+ kind: "workspace",
453
+ family: own.family,
454
+ fonts,
455
+ weights: weights.sort((a, b) => a - b),
456
+ axis: variable === undefined ? null : workspaceFacts(variable).axis!,
457
+ // Whether the apply draws italic (`chooseFace`): files that say nothing
458
+ // about their slant keep the layer's style, so they can.
459
+ italic: chooseFace(fonts, workspaceFacts, style.weight, "italic").style === "italic",
460
+ };
461
+ }
462
+ const file = catalogFileOf(asset.path);
463
+ if (file === null) return { kind: "file", family };
464
+ if (catalog === null) return { kind: "loading" };
465
+ const entry = catalogEntryOf(catalog, family, file);
466
+ if (entry === undefined) return { kind: "file", family };
467
+ const axis = entry.axes?.["wght"];
468
+ return {
469
+ kind: "catalog",
470
+ family: entry.family,
471
+ entry,
472
+ weights: entry.weights,
473
+ axis: appliesVariableFile(entry) && axis !== undefined ? [axis[0], axis[1]] : null,
474
+ italic: entry.styles.includes("italic"),
475
+ };
476
+ }
477
+
478
+ const fold = (family: string): string => family.toLowerCase().replace(/\s+/gu, " ").trim();
479
+
480
+ /**
481
+ * The facts the Typography rows render for a selection: the selected text
482
+ * layers' family when they share one, `mixed` when they do not, `loading`
483
+ * while any of them waits on the catalog, null with no text layer selected.
484
+ */
485
+ export function selectionFontFacts(
486
+ layers: readonly Layer3D[],
487
+ assets: Doc3D["assets"],
488
+ catalog: readonly GoogleFontEntry[] | null,
489
+ workspace: readonly WorkspaceFont[] | null,
490
+ ): LayerFontFacts | { kind: "mixed" } | null {
491
+ const facts = layers
492
+ .filter((layer): layer is TextLayer => layer.kind === "text")
493
+ .map((layer) => fontFactsOf(layer, assets, catalog, workspace));
494
+ const first = facts[0];
495
+ if (first === undefined) return null;
496
+ if (facts.some((f) => f.kind === "loading")) return { kind: "loading" };
497
+ const key = (f: LayerFontFacts): string => (f.kind === "loading" ? "" : `${f.kind}:${fold(f.family)}`);
498
+ return facts.every((f) => key(f) === key(first)) ? first : { kind: "mixed" };
499
+ }
500
+
501
+ // ── In this Project ────────────────────────────────────────────────────────
502
+
503
+ /** One family the Project holds files for, listed once. */
504
+ export interface ProjectFontFamily {
505
+ /** `family` case-folded: what makes two sources one row. */
506
+ key: string;
507
+ family: string;
508
+ /** Where its first files came from. */
509
+ source: "catalog" | "workspace" | "file";
510
+ /** Re-applies it from the Project's own files, with no fetch. */
511
+ pick: Extract<FontPick, { kind: "project" }>;
512
+ }
513
+
514
+ export interface ProjectFonts {
515
+ /** By family name. */
516
+ families: ProjectFontFamily[];
517
+ /** Families text layers name with no file (`drawsMachineFont`), by name,
518
+ * leaving out one the Project also holds files for. */
519
+ machine: { key: string; family: string }[];
520
+ }
521
+
522
+ /** Every text layer in the document: each Shot's, then each component's. */
523
+ function textLayersOf(doc: Doc3D): TextLayer[] {
524
+ return [...Object.values(doc.scenesById), ...Object.values(doc.components ?? {})].flatMap((home) =>
525
+ Object.values(home.layersById).filter((layer): layer is TextLayer => layer.kind === "text"),
526
+ );
527
+ }
528
+
529
+ /** A file name's stem as a family: `BrandSerif-Regular.woff2` reads
530
+ * "BrandSerif Regular". */
531
+ const stemFamily = (path: string): string =>
532
+ storedName(path).replace(/\.[^.]*$/u, "").replace(/[-_]+/gu, " ").trim() || "Font";
533
+
534
+ /**
535
+ * The families a Project already holds files for, each once (case-folded):
536
+ * catalog copies grouped by their catalog family (one face per weight and
537
+ * style, its subsets together), workspace copies by the workspace list's
538
+ * family, then every other font asset by the brand row that names it, else
539
+ * its file stem. Each comes with the pick that re-applies it from those
540
+ * files. Without the catalog, a catalog copy's family is the one a text layer
541
+ * names for it, else its file name's.
542
+ */
543
+ export function projectFontFamilies(
544
+ doc: Doc3D,
545
+ catalog: readonly GoogleFontEntry[] | null,
546
+ workspace: readonly WorkspaceFont[] | null,
547
+ ): ProjectFonts {
548
+ const assets = Object.values(doc.assets).filter((asset) => asset.kind === "font");
549
+ // Workspace fonts by the `<hash8>-` their copies share (`isCopyOf`).
550
+ const byHash = new Map((workspace ?? []).map((font) => [font.id.slice(0, 9), font]));
551
+ const workspaceOf = (asset: ShotAsset): WorkspaceFont | undefined => byHash.get(asset.path.slice(0, 9));
552
+ // The catalog by the file-name spelling of its family, read back through
553
+ // the same two helpers that write and parse a catalog file's name.
554
+ const bySafe = new Map(
555
+ (catalog ?? []).map((entry) => [catalogFileOf(googleFontFileName(entry, "variable"))!.safeFamily, entry]),
556
+ );
557
+ const layers = textLayersOf(doc);
558
+ const usedAs = new Map<string, string>();
559
+ for (const layer of layers) {
560
+ const id = layer.text.style.fontAsset;
561
+ if (id !== undefined && !usedAs.has(id)) usedAs.set(id, primaryFontFamily(layer.text.style.fontFamily));
562
+ }
563
+ const branded = new Map<string, string>();
564
+ for (const scope of [doc.resources, ...Object.values(doc.scenesById).map((scene) => scene.resources)]) {
565
+ for (const item of scope?.branding ?? []) {
566
+ if (item.type === "asset" && item.fontFamily !== undefined && !branded.has(item.asset)) {
567
+ branded.set(item.asset, primaryFontFamily(item.fontFamily) || item.fontFamily);
568
+ }
569
+ }
570
+ }
571
+ const rows = new Map<string, ProjectFontFamily & { faces: ProjectFace[] }>();
572
+ const add = (family: string, source: ProjectFontFamily["source"], fallback: string, face: ProjectFace): void => {
573
+ const key = fold(family);
574
+ let row = rows.get(key);
575
+ if (row === undefined) {
576
+ const faces: ProjectFace[] = [];
577
+ row = { key, family, source, faces, pick: { kind: "project", family, fallback, faces } };
578
+ rows.set(key, row);
579
+ }
580
+ row.faces.push(face);
581
+ };
582
+ // Catalog copies: one face per (family, weight, style), the default subset
583
+ // first.
584
+ const catalogFaces = new Map<string, { entry: GoogleFontEntry | undefined; family: string; files: ShotAsset[] }>();
585
+ const rest: ShotAsset[] = [];
586
+ for (const asset of assets) {
587
+ const file = workspaceOf(asset) !== undefined ? null : catalogFileOf(asset.path);
588
+ if (file === null) {
589
+ rest.push(asset);
590
+ continue;
591
+ }
592
+ const entry = bySafe.get(file.safeFamily);
593
+ const faceKey = `${file.safeFamily}:${file.weight}:${file.style}`;
594
+ const held = catalogFaces.get(faceKey);
595
+ if (held !== undefined) {
596
+ held.files.push(asset);
597
+ continue;
598
+ }
599
+ const family = entry?.family ?? usedAs.get(asset.id) ?? file.safeFamily.replace(/-/gu, " ");
600
+ catalogFaces.set(faceKey, { entry, family, files: [asset] });
601
+ }
602
+ for (const { entry, family, files } of catalogFaces.values()) {
603
+ const parsed = files.map((asset) => ({ asset, file: catalogFileOf(asset.path)! }));
604
+ const order = entry === undefined ? [] : [entry.defSubset, ...entry.subsets];
605
+ // The file with no subset suffix is the default subset's, catalog or not;
606
+ // a subset the catalog does not list goes last.
607
+ const rank = (subset: string | undefined): number => {
608
+ if (subset === undefined) return -1;
609
+ const at = order.indexOf(subset);
610
+ return at < 0 ? order.length : at;
611
+ };
612
+ parsed.sort((a, b) => rank(a.file.subset) - rank(b.file.subset));
613
+ const [head, ...others] = parsed;
614
+ const { weight, style } = head!.file;
615
+ const axis = entry?.axes?.["wght"];
616
+ add(family, "catalog", entry === undefined ? "sans-serif" : categoryFallback(entry.category), {
617
+ fontAsset: head!.asset.id,
618
+ ...(others.length === 0 ? {} : { fontSubsets: others.map((o) => o.asset.id) }),
619
+ ...(weight === "variable" ? { axis: axis === undefined ? ([1, 1000] as const) : ([axis[0], axis[1]] as const) } : { weight }),
620
+ italic: style === "italic",
621
+ });
622
+ }
623
+ for (const asset of rest) {
624
+ const font = workspaceOf(asset);
625
+ if (font !== undefined) add(font.family, "workspace", "sans-serif", { fontAsset: asset.id, ...workspaceFacts(font) });
626
+ }
627
+ for (const asset of rest) {
628
+ if (workspaceOf(asset) !== undefined) continue;
629
+ add(branded.get(asset.id) ?? stemFamily(asset.path), "file", "sans-serif", { fontAsset: asset.id });
630
+ }
631
+ const families = [...rows.values()]
632
+ .map(({ faces: _faces, ...row }) => row)
633
+ .sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));
634
+ const machine = new Map<string, string>();
635
+ for (const layer of layers) {
636
+ if (!drawsMachineFont(layer, doc.assets)) continue;
637
+ const family = primaryFontFamily(layer.text.style.fontFamily);
638
+ const key = fold(family);
639
+ if (key.length > 0 && !rows.has(key) && !machine.has(key)) machine.set(key, family);
640
+ }
641
+ return {
642
+ families,
643
+ machine: [...machine].map(([key, family]) => ({ key, family })).sort((a, b) => (a.key < b.key ? -1 : 1)),
644
+ };
645
+ }
646
+
647
+ /** The other character-set files the Project holds for the face whose
648
+ * default file is `assetId`, or undefined when it has none: what a write
649
+ * that sets `fontAsset` to it sets as `fontSubsets`. */
650
+ export function projectSubsetsOf(doc: Doc3D, assetId: string): readonly string[] | undefined {
651
+ for (const family of projectFontFamilies(doc, null, null).families) {
652
+ const face = family.pick.faces.find((f) => f.fontAsset === assetId);
653
+ if (face !== undefined) return face.fontSubsets;
654
+ }
655
+ return undefined;
656
+ }