@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,210 @@
1
+ // Only for DEVICE layers, and absent otherwise — the same rule MeshSection and
2
+ // ClonerSection follow, for the same reason.
3
+ //
4
+ // ── WHY THIS EXISTS AT ALL ──────────────────────────────────────────────
5
+ //
6
+ // A device could be ADDED (the Add menu picks a preset) and then never touched:
7
+ // its preset, its body colour, its thickness and which child sits on the glass
8
+ // were all reachable from the agent lane and from nowhere in the chrome. That
9
+ // is the exact defect MeshSection's material rows were written to close — "the
10
+ // author could see it and could not touch it" — and schema v12 made it louder,
11
+ // because `device.color` is a KEYFRAMABLE path with no row to hang its diamond
12
+ // on. A trackable property the inspector cannot show is a property the author
13
+ // can only animate by asking an agent to do it.
14
+ //
15
+ // ── THE READOUTS SHOW THE DERIVED VALUE, NOT THE STORED ONE ─────────────
16
+ //
17
+ // `color` and `depthPx` are both OVERRIDES: absent means "the preset's own",
18
+ // which is a real number the stage is drawing and a blank field would hide. So
19
+ // both rows read `deviceSlab`, the same pure function the runtime uses, and an
20
+ // untouched device shows the colour and thickness it actually has. Nothing is
21
+ // stored until a row is edited — baking the preset's value in on mount would
22
+ // freeze it against a later preset change. Mixed is asked of the SAME derived
23
+ // value, per device: two phones of different widths store no depth and still
24
+ // disagree on the one they draw.
25
+ //
26
+ // ponytail: no notch/camera/home-button rows. Those are DECORATIONS and the
27
+ // device header says so: they go in as child layers, and the moment one lives
28
+ // in the preset table the table stops being data.
29
+ import {
30
+ ColorField,
31
+ Combobox,
32
+ InspectorRow,
33
+ InspectorSection,
34
+ NumberField,
35
+ } from "@dailies-studio/ui";
36
+ import { DEVICE_PRESETS, DEVICE_PRESET_IDS, deviceSlab, homeOf } from "@dailies-studio/shot/doc";
37
+ import type { DeviceLayer3D, Layer3D } from "@dailies-studio/shot/doc";
38
+ import { KeyframeDot } from "../KeyframeDot.js";
39
+ import { useShotEditor } from "../../../state/context.js";
40
+ import {
41
+ useInspectorFrame,
42
+ useSelectedLayers,
43
+ useSelectionScrub,
44
+ useSelectionWrite,
45
+ } from "../useDocWrite.js";
46
+
47
+ type DeviceBlock = DeviceLayer3D["device"];
48
+
49
+ /** The Screen combobox's "no child on the glass" entry. The empty string is the
50
+ * one value a layer id can never be, so it needs no second channel. */
51
+ const NO_SCREEN = "";
52
+
53
+ export function DeviceSection() {
54
+ const write = useSelectionWrite();
55
+ const layers = useSelectedLayers();
56
+ const doc = useShotEditor((s) => s.doc);
57
+ const { sceneId } = useInspectorFrame();
58
+ // THE DRAG HALF of the depth, over the same builder its typed value takes.
59
+ const depthScrub = useSelectionScrub((layer, raw) => withDepth(layer, Number(raw)));
60
+ // THE DEVICE LAYERS THEMSELVES, for MeshSection's reason: `layer.device` is an
61
+ // object, so `useSelectionValue` would report every multi-selection as Mixed
62
+ // by identity over values the author can see agree. Mixed is asked per FIELD.
63
+ const devices = layers.filter((l): l is DeviceLayer3D => l.kind === "device");
64
+ const first = devices[0];
65
+ if (first === undefined) return null;
66
+
67
+ const d = first.device;
68
+ const preset = DEVICE_PRESETS[d.preset];
69
+ const slab = deviceSlab(d, first.transform.size);
70
+ /** One device's derived value for a row, read off `deviceSlab` as the row
71
+ * shows it, so Mixed is asked of what the fields display. */
72
+ const shownOn = <T,>(read: (device: DeviceLayer3D) => T): boolean =>
73
+ devices.some((l) => !Object.is(read(l), read(first)));
74
+ const depthOf = (l: DeviceLayer3D): number => deviceSlab(l.device, l.transform.size)?.depthPx ?? l.device.depthPx ?? 0;
75
+ const colorOf = (l: DeviceLayer3D): string =>
76
+ (deviceSlab(l.device, l.transform.size)?.color ?? l.device.color ?? DEVICE_PRESETS[l.device.preset]?.color ?? BODY_FALLBACK).toLowerCase();
77
+
78
+ const patch = (next: (block: DeviceBlock) => DeviceBlock): void =>
79
+ write((layer) => (layer.kind === "device" ? ({ ...layer, device: next(layer.device) } as Layer3D) : layer));
80
+
81
+ const set = <K extends keyof DeviceBlock>(key: K, value: DeviceBlock[K]): void =>
82
+ patch((block) => ({ ...block, [key]: value }));
83
+
84
+ const mixedOn = <T,>(read: (block: DeviceBlock) => T): boolean =>
85
+ devices.some((l) => !Object.is(read(l.device), read(d)));
86
+
87
+ // The child layers this ONE device holds, by label. Resolved through the
88
+ // container so it is right inside a definition too.
89
+ const home = doc === null || sceneId === null ? null : homeOf(doc, sceneId);
90
+ const children = d.children
91
+ .map((id) => ({ value: id, label: home?.layersById[id]?.label ?? id }))
92
+ .filter((row) => home === null || home.layersById[row.value] !== undefined);
93
+
94
+ return (
95
+ <InspectorSection className="insp-object" title="Device" persistKey="shot:device">
96
+ <InspectorRow label="Preset">
97
+ <Combobox
98
+ options={DEVICE_PRESET_IDS.map((id) => ({
99
+ value: id,
100
+ label: id.charAt(0).toUpperCase() + id.slice(1),
101
+ }))}
102
+ // A selection of two presets shows neither: an empty value is how
103
+ // the combobox says Mixed, over its placeholder.
104
+ value={mixedOn((block) => block.preset) ? "" : d.preset}
105
+ ariaLabel="Device preset"
106
+ placeholder="Mixed"
107
+ onChange={(next) => set("preset", next)}
108
+ />
109
+ </InspectorRow>
110
+
111
+ <BodyColorRow value={slab?.color ?? d.color ?? preset?.color ?? BODY_FALLBACK} mixed={shownOn(colorOf)} />
112
+
113
+ <InspectorRow>
114
+ <NumberField
115
+ // The EFFECTIVE thickness: absent means the preset's ratio times the
116
+ // width, and showing that number is what makes the default legible.
117
+ value={slab?.depthPx ?? d.depthPx ?? 0}
118
+ mixed={shownOn(depthOf)}
119
+ label="Depth"
120
+ ariaLabel="Device depth"
121
+ unit="px"
122
+ min={1}
123
+ step={1}
124
+ onCommit={(next) => write((layer) => withDepth(layer, next))}
125
+ onScrubStart={depthScrub.start}
126
+ onScrub={depthScrub.move}
127
+ onScrubEnd={depthScrub.end}
128
+ />
129
+ </InspectorRow>
130
+
131
+ {/* ONE DEVICE AT A TIME, and this is a correctness gate rather than a
132
+ courtesy: `screen` must name one of THIS device's own children, so a
133
+ fan-out would write an id the other device has never held and
134
+ `validateShotDoc` refuses the save. */}
135
+ {devices.length > 1 ? (
136
+ <div className="insp-notice">Select one device on its own to choose its screen.</div>
137
+ ) : (
138
+ <InspectorRow label="Screen" labelTitle="Which child layer renders on the glass">
139
+ <Combobox
140
+ options={[{ value: NO_SCREEN, label: "None" }, ...children]}
141
+ value={d.screen ?? NO_SCREEN}
142
+ ariaLabel="Device screen child"
143
+ placeholder="Search children"
144
+ onChange={(next) =>
145
+ patch((block) => {
146
+ if (next !== NO_SCREEN) return { ...block, screen: next };
147
+ // Absent, not `null` and not `""`: codegen prints the block by
148
+ // key order, and a present-but-undefined key is a key. The
149
+ // schema accepts null, so this is a house-style choice and not
150
+ // a gate — MeshSection's `clip` row clears the same way.
151
+ const { screen: _drop, ...rest } = block;
152
+ return rest;
153
+ })
154
+ }
155
+ />
156
+ </InspectorRow>
157
+ )}
158
+ </InspectorSection>
159
+ );
160
+ }
161
+
162
+ /** A body colour no preset names, the slab's own last resort. */
163
+ const BODY_FALLBACK = "#1d1d1f";
164
+
165
+ /** One device's depth override, the one builder its typed value and its drag
166
+ * both run. */
167
+ function withDepth(layer: Layer3D, next: number): Layer3D {
168
+ return layer.kind === "device" ? ({ ...layer, device: { ...layer.device, depthPx: Math.round(next) } } as Layer3D) : layer;
169
+ }
170
+
171
+ /**
172
+ * The body colour. Its own component for `ShapeColorRow`'s reason: `ColorPicker`
173
+ * routes its whole drag through `scrub.move`, so a row without the document
174
+ * bracket is a thumb that moves over a document that never changes. OPAQUE,
175
+ * for `writeBodyColor`'s reason below: the picker offers no alpha the row
176
+ * would refuse.
177
+ */
178
+ function BodyColorRow({ value, mixed }: { value: string; mixed: boolean }) {
179
+ const write = useSelectionWrite();
180
+ const scrub = useSelectionScrub((layer, raw) => writeBodyColor(layer, raw));
181
+ return (
182
+ <InspectorRow label="Colour" trailing={<KeyframeDot path="device.color" />}>
183
+ <ColorField
184
+ value={value}
185
+ mixed={mixed}
186
+ opaque
187
+ ariaLabel="Body colour"
188
+ scrub={scrub}
189
+ commit={(hex) => write((layer) => writeBodyColor(layer, hex))}
190
+ />
191
+ </InspectorRow>
192
+ );
193
+ }
194
+
195
+ /**
196
+ * PURE, over the layer it is handed — `writeShapeColor`'s rule and its exact
197
+ * regex.
198
+ *
199
+ * SIX DIGITS, though `validateShotDoc` would take any CSS colour here. The
200
+ * narrower set is the KEYFRAME's: `device.color` is declared `opaqueHex` in the
201
+ * path table, so an 8-digit value typed into an ANIMATED device would be refused
202
+ * by the track gate at the moment it stopped being a constant. A control that
203
+ * accepts a value the same control refuses once a diamond is pressed is worse
204
+ * than one that never offered it.
205
+ */
206
+ function writeBodyColor(layer: Layer3D, raw: unknown): Layer3D {
207
+ if (layer.kind !== "device") return layer;
208
+ if (typeof raw !== "string" || !/^#[0-9a-fA-F]{6}$/.test(raw)) return layer;
209
+ return { ...layer, device: { ...layer.device, color: raw.toLowerCase() } };
210
+ }
@@ -1,86 +1,118 @@
1
- // The fill. Solid only in the inspector for now: a gradient needs the stop
2
- // editor, which @dailies-studio/ui has (`GradientValueEditor`) and which wants
3
- // the gradient adapter the 2D lane wrote — that is a section of its own rather
4
- // than a control squeezed in here.
1
+ // The fill, as the whole `Paint` union: solid, linear gradient or radial.
5
2
  //
6
- // PAINT HAS NO BLOCK DELTAS in v1, so a paint read and a paint write are both
7
- // BASE and cannot disagree. That is why nothing here goes through
8
- // `setEffectiveField`.
9
- import { ColorPicker, InspectorRow, InspectorSection } from "@dailies-studio/ui";
3
+ // It was solid-only until the shots.so pass, and not merely incomplete —
4
+ // DESTRUCTIVE. `withSolidFill` was the only writer, so a gradient fill authored
5
+ // by MCP survived being displayed (the swatch showed its first stop) and died on
6
+ // the first commit, and on the `+`/`−` action too. `paintStyle` (raster.ts) has
7
+ // drawn both gradients on a layer since unify Phase 1; only the authoring
8
+ // surface was missing. `PaintRows` is that surface, shared with the scene
9
+ // background's row, which had the identical hole.
10
+ //
11
+ // Nothing here goes through `setEffectiveField`, which is transform only. An
12
+ // edit is a `PaintEdit` run on EACH layer's own fill as painted (its
13
+ // `paint.fill.color` track sampled), so a multi-selection changes one field
14
+ // per layer and never copies the lead's whole paint onto the rest.
15
+ import { IconMinus, IconPlus, InspectorSection, useLocked } from "@dailies-studio/ui";
16
+ import { KeyframeDot } from "../KeyframeDot.js";
17
+ import { PaintRows, type PaintEdit } from "./PaintRows.js";
10
18
  import type { Layer3D, Paint } from "@dailies-studio/shot/doc";
11
- import { NO_SCRUB, useSelectedLayers, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
19
+ import { useSelectedLayers, useSelectionScrub, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
20
+ // The white the DRAW TOOLS open a fill on. Read rather than restated: "+ Fill"
21
+ // here and a dragged rectangle are two doors into the same first paint, and two
22
+ // whites would be a difference nobody chose.
23
+ import { DEFAULT_PAINT_COLOR } from "../../../state/insert.js";
12
24
 
13
- const DEFAULT_FILL = "#ffffff";
25
+ const DEFAULT_PAINT: Paint = { type: "solid", color: DEFAULT_PAINT_COLOR };
14
26
 
15
27
  export function FillSection() {
16
28
  const write = useSelectionWrite();
17
29
  const layers = useSelectedLayers();
18
- const fill = useSelectionValue((layer) => solidOf(layer.paint.fill));
30
+ // The add/remove action is a plain button, so it reads a viewer's lock here.
31
+ const locked = useLocked();
19
32
  const present = useSelectionValue((layer) => layer.paint.fill !== undefined);
20
-
21
- // A FILL IS INERT ON MEDIA, so the control is absent rather than dead.
22
- // `drawLayer` paints `paint.fill` only in the arm that is neither "mask" nor
23
- // "plate" (raster.ts), and `MediaPlane` asks for exactly those two — the
24
- // content pixels come from the asset. A colour swatch that writes a document
25
- // field and moves nothing is worse than no swatch: the author changes it,
26
- // sees nothing, and concludes the renderer is broken.
27
- //
28
- // ONLY when the whole selection is media. A mixed one keeps the section,
29
- // because the rects in it still fill and `useSelectionWrite` already skips
30
- // the layers a write cannot reach.
33
+ const first = layers.find((layer) => layer.paint.fill !== undefined);
34
+ const fill = first?.paint.fill ?? DEFAULT_PAINT;
35
+ // THE DRAG HALF. The picker's SV square, hue bar and alpha bar never call
36
+ // `commit` while the pointer is down: every one of them reports through
37
+ // `scrub.move` (ui/ColorPicker.tsx). With the commit half alone the thumb
38
+ // moved and the document did not, so the only working colour controls were
39
+ // the hex and R/G/B fields.
31
40
  //
32
- // Deliberately NOT extended to mesh/instance/cloner, whose fills are inert
33
- // for a different reason (they mount their own content and are refused by
34
- // `rasterizesLayer` upstream of this) — one gate per reason.
35
- if (layers.length > 0 && layers.every(isMedia)) return null;
41
+ // It carries a whole PAINT now, so a stop drag and an angle drag ride the same
42
+ // capture the colour bar always did.
43
+ const painted = new Map(layers.map((layer) => [layer.id, layer.paint.fill]));
44
+ const edited = (layer: Layer3D, edit: PaintEdit): Layer3D => {
45
+ const own = painted.get(layer.id) ?? layer.paint.fill;
46
+ return own === undefined ? layer : withFill(layer, edit(own));
47
+ };
48
+ const scrub = useSelectionScrub((layer, raw) => edited(layer, raw as PaintEdit));
49
+ // Over text a fill is the plate behind the words; the glyph colour is
50
+ // `TextFillSection`'s "Fill" (F20).
51
+ const title = layers.every((layer) => layer.kind === "text") ? "Background" : "Fill";
36
52
 
53
+ // NO KIND GATE. Which selections get a Fill at all — the nine kinds Canvas2D
54
+ // draws, minus media, whose content pixels come from its asset — is declared
55
+ // once in `layout.ts` and answered before this section is mounted.
37
56
  return (
38
57
  <InspectorSection
39
- title="Fill"
58
+ className="insp-object"
59
+ title={title}
40
60
  persistKey="shot:fill"
41
61
  action={
42
62
  <button
43
63
  type="button"
44
- className="section-action"
45
- title={present.value === true ? "Remove fill" : "Add fill"}
64
+ className="btn is-icon insp-section-action"
65
+ title={`${present.value === true ? "Remove" : "Add"} ${title.toLowerCase()}`}
66
+ aria-label={`${present.value === true ? "Remove" : "Add"} ${title.toLowerCase()}`}
67
+ disabled={locked}
46
68
  onClick={() =>
47
69
  write((layer) =>
48
70
  present.value === true
49
71
  ? { ...layer, paint: withoutFill(layer.paint) }
50
- : { ...layer, paint: { ...layer.paint, fill: { type: "solid", color: DEFAULT_FILL } } },
72
+ : layer.paint.fill === undefined ? withFill(layer, DEFAULT_PAINT) : layer,
51
73
  )
52
74
  }
53
75
  >
54
- {present.value === true ? "−" : "+"}
76
+ {present.value === true ? <IconMinus /> : <IconPlus />}
55
77
  </button>
56
78
  }
57
79
  >
58
80
  {present.value === true ? (
59
- <InspectorRow label="Color">
60
- <ColorPicker
61
- value={fill.value ?? DEFAULT_FILL}
62
- ariaLabel="Fill colour"
63
- scrub={NO_SCRUB}
64
- commit={(hex) =>
65
- write((layer) => ({ ...layer, paint: { ...layer.paint, fill: { type: "solid", color: hex } } }))
66
- }
67
- />
68
- </InspectorRow>
81
+ <PaintRows
82
+ // NO `key`. Keying this on the paint would remount the whole control
83
+ // on every scrub tick, which unmounts the ColorPicker mid-drag and
84
+ // drops its pointer capture: the thumb moves and the document does
85
+ // not, which is the exact defect this section was written to fix.
86
+ // Nothing here needs a remount anyway, because the stop rows are
87
+ // derived from `value.stops` on every render.
88
+ value={fill}
89
+ all={layers.flatMap((layer) => (layer.paint.fill === undefined ? [] : [layer.paint.fill]))}
90
+ ariaPrefix="Fill"
91
+ // The heading says Fill; the design draws the type row as a bare
92
+ // chip track. A layer fill has a picture behind it, so its alpha
93
+ // gets the Opacity row a scene's ground does not.
94
+ label={null}
95
+ showOpacity
96
+ // `paint.fill.color` is a colour track and the table refuses it over a
97
+ // gradient, which has no single colour to interpolate. The Opacity
98
+ // box is the same track's alpha byte: its diamond, and no second pin.
99
+ solidTrailing={<KeyframeDot path="paint.fill.color" />}
100
+ opacityTrailing={<KeyframeDot path="paint.fill.color" pin={false} />}
101
+ onScrubStart={scrub.start}
102
+ onScrub={(edit) => scrub.move(edit)}
103
+ onScrubEnd={scrub.end}
104
+ onCommit={(edit) => write((layer) => edited(layer, edit))}
105
+ />
69
106
  ) : null}
70
107
  </InspectorSection>
71
108
  );
72
109
  }
73
110
 
74
- /** A gradient has no single colour, so the swatch shows its FIRST stop and the
75
- * picker would overwrite it — which is why the row is only offered for a solid
76
- * and a gradient keeps whatever the document holds. */
77
- function solidOf(paint: Paint | undefined): string {
78
- if (paint === undefined) return DEFAULT_FILL;
79
- return paint.type === "solid" ? paint.color : (paint.stops[0]?.color ?? DEFAULT_FILL);
80
- }
81
-
82
- function isMedia(layer: Layer3D): boolean {
83
- return layer.kind === "image" || layer.kind === "video";
111
+ /** The ONE fill edit, so a drag's move and its release land the same document.
112
+ * Two spellings of this would mean the commit could disagree with the last
113
+ * captured move, and the release would appear to undo the drag. */
114
+ function withFill(layer: Layer3D, paint: Paint): Layer3D {
115
+ return { ...layer, paint: { ...layer.paint, fill: paint } };
84
116
  }
85
117
 
86
118
  function withoutFill(paint: { fill?: Paint }): typeof paint {
@@ -0,0 +1,196 @@
1
+ // THE FONT ROW'S TRIGGER (plans/FONT-PICKER-PLAN.md "The picker"): one field
2
+ // box showing the family drawn in its own face ("Mixed" when the selected text
3
+ // names different families), the one caret, and the font write in flight.
4
+ //
5
+ // A press, Enter, Space or ArrowDown opens the picker (panels/fonts, a lazy
6
+ // chunk mounted only while open; its chunk and the Google catalog preload as
7
+ // the pointer or focus reaches the field). A request from elsewhere (the
8
+ // Typography note's "Upload font file", `requestFontPicker`) opens it on the
9
+ // asked view. A locked panel reads the family and opens nothing: a pick writes
10
+ // the Project.
11
+ //
12
+ // While a font write runs (`state.fontApply`, which outlives the picker) the
13
+ // box shows its progress in the accessory lane, a level in `--meter-lit` with
14
+ // its percent, or a turning arc while the size is unknown (a dashed ring
15
+ // under reduced motion, never a still arc that reads as a level); a failure
16
+ // shows Retry there instead. Neither changes the row's size.
17
+ //
18
+ // The face the name draws in is the In this Project row's (faceCache.ts):
19
+ // the Project's own file, or the computer's for a machine font, so
20
+ // selecting text fetches nothing from jsDelivr.
21
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
22
+ import type { KeyboardEvent } from "react";
23
+ import { drawsMachineFont } from "@dailies-studio/shot/doc";
24
+ import { IconChevronDown, useLocked } from "@dailies-studio/ui";
25
+ import { FontPicker } from "../../../app/lazySurfaces.js";
26
+ import { useShotEditor, useStore } from "../../../state/context.js";
27
+ import type { FontApplyProgress } from "../../../state/fonts.js";
28
+ import { faceStack, familyFace, usePreviewFace } from "../../fonts/faceCache.js";
29
+ import { useFontPickerRequest } from "../../fonts/pickerRequest.js";
30
+ import type { FontPickerClose, FontPickerView } from "../../fonts/pickerRequest.js";
31
+ import { useSelectedLayers } from "../useDocWrite.js";
32
+
33
+ export { MANAGE_FONTS_HREF } from "../../fonts/pickerRequest.js";
34
+
35
+ export interface FontFieldProps {
36
+ /** The display family: `primaryFontFamily` of the layers' `fontFamily`. */
37
+ value: string;
38
+ /** The selected text layers name different families. */
39
+ mixed: boolean;
40
+ }
41
+
42
+ export function FontField({ value, mixed }: FontFieldProps) {
43
+ const store = useStore();
44
+ const locked = useLocked();
45
+ const anchor = useRef<HTMLDivElement>(null);
46
+ const button = useRef<HTMLButtonElement>(null);
47
+ const valueId = useId();
48
+ // The open view, or null while closed.
49
+ const [view, setView] = useState<FontPickerView | null>(null);
50
+ // Set by a close that hands focus back, spent once the picker is gone.
51
+ const refocus = useRef(false);
52
+ const doc = useShotEditor((s) => s.doc);
53
+ const catalog = useShotEditor((s) => s.fontCatalog);
54
+ const workspace = useShotEditor((s) => s.workspaceFonts);
55
+ // A write's progress shows on the field of the text it lands on, not on
56
+ // whatever text is selected while it runs.
57
+ const progress = useShotEditor((s) =>
58
+ s.fontApply !== null && s.fontApply.layerIds.some((id) => s.selection.includes(id)) ? s.fontApply : null,
59
+ );
60
+
61
+ const face = useMemo(
62
+ () => (mixed || value === "" || doc === null ? null : familyFace(doc, value, catalog, workspace?.fonts ?? null)),
63
+ [mixed, value, doc, catalog, workspace],
64
+ );
65
+ const status = usePreviewFace(face);
66
+ // A fileless layer beside the Project's own copy of its family: the picker
67
+ // lists the copy as current, and picking it still has to write.
68
+ const layers = useSelectedLayers();
69
+ const currentMachine = doc !== null && layers.some((layer) => drawsMachineFont(layer, doc.assets));
70
+
71
+ const warm = (): void => {
72
+ void FontPicker.preload();
73
+ void store.loadFontCatalog();
74
+ };
75
+ const openOn = (next: FontPickerView): void => {
76
+ if (locked) return;
77
+ warm();
78
+ setView(next);
79
+ };
80
+ const close = (how: FontPickerClose): void => {
81
+ // Tab moves on FROM the field: focus has to be here before the browser
82
+ // takes the key.
83
+ if (how === "tab") button.current?.focus();
84
+ refocus.current = how === "refocus";
85
+ setView(null);
86
+ };
87
+ useEffect(() => {
88
+ if (view !== null || !refocus.current) return;
89
+ refocus.current = false;
90
+ button.current?.focus();
91
+ }, [view]);
92
+
93
+ // A request made before this field mounted is not this field's to answer.
94
+ const request = useFontPickerRequest();
95
+ const answered = useRef(request?.seq ?? 0);
96
+ useEffect(() => {
97
+ if (request === null || request.seq <= answered.current) return;
98
+ answered.current = request.seq;
99
+ openOn(request.view);
100
+ // eslint-disable-next-line react-hooks/exhaustive-deps
101
+ }, [request]);
102
+
103
+ const onKeyDown = (e: KeyboardEvent<HTMLButtonElement>): void => {
104
+ // Enter and Space press the button; ArrowDown opens it as a list does.
105
+ // The window keymap reads the arrows as a frame step.
106
+ if (e.key !== "ArrowDown" || view !== null) return;
107
+ e.preventDefault();
108
+ e.stopPropagation();
109
+ openOn("list");
110
+ };
111
+
112
+ return (
113
+ <div
114
+ ref={anchor}
115
+ className={`color-field font-field${locked ? " is-readonly" : ""}`}
116
+ onPointerEnter={locked ? undefined : warm}
117
+ onFocus={locked ? undefined : warm}
118
+ >
119
+ <button
120
+ ref={button}
121
+ type="button"
122
+ className="font-field-open"
123
+ aria-label="Font"
124
+ aria-describedby={valueId}
125
+ aria-haspopup="listbox"
126
+ aria-expanded={view !== null}
127
+ disabled={locked}
128
+ onClick={() => (view === null ? openOn("list") : close("refocus"))}
129
+ onKeyDown={onKeyDown}
130
+ >
131
+ <span
132
+ id={valueId}
133
+ className={`font-field-name${mixed ? " is-mixed" : ""}`}
134
+ {...(status === "ready" && face !== null ? { style: { fontFamily: faceStack(face) } } : {})}
135
+ >
136
+ {mixed ? "Mixed" : value}
137
+ </span>
138
+ <IconChevronDown className="field-caret" />
139
+ </button>
140
+ {progress !== null && <FontApplyState progress={progress} locked={locked} onRetry={() => void store.retryFontApply()} />}
141
+ {view !== null && (
142
+ <FontPicker
143
+ anchor={anchor}
144
+ view={view}
145
+ onView={setView}
146
+ current={mixed ? null : value}
147
+ currentMachine={currentMachine}
148
+ onClose={close}
149
+ />
150
+ )}
151
+ </div>
152
+ );
153
+ }
154
+
155
+ /** The font write in flight, in the box's accessory lane. */
156
+ function FontApplyState(props: { progress: FontApplyProgress; locked: boolean; onRetry: () => void }) {
157
+ const { family, phase, loaded, total, failed } = props.progress;
158
+ if (failed !== null) {
159
+ return (
160
+ <span className="field-accessory">
161
+ <button type="button" className="btn font-field-retry" title={failed} disabled={props.locked} onClick={props.onRetry}>
162
+ Retry
163
+ </button>
164
+ </span>
165
+ );
166
+ }
167
+ const percent = total !== null && total > 0 ? Math.min(100, Math.round((loaded / total) * 100)) : null;
168
+ const doing = `${phase === "upload" ? "Uploading" : "Downloading"} ${family}`;
169
+ return (
170
+ <span
171
+ className="field-accessory font-field-apply"
172
+ role="progressbar"
173
+ aria-label={doing}
174
+ aria-valuemin={0}
175
+ aria-valuemax={100}
176
+ {...(percent !== null ? { "aria-valuenow": percent } : {})}
177
+ title={percent === null ? doing : `${doing}, ${percent}%`}
178
+ >
179
+ <svg className={`font-field-ring${percent === null ? " is-indeterminate" : ""}`} viewBox="0 0 12 12" aria-hidden="true">
180
+ <circle className="font-field-ring-track" cx="6" cy="6" r="4.5" />
181
+ {/* A size not known yet draws no level: the sheet dashes the ring, or
182
+ turns a short arc where motion is wanted. */}
183
+ <circle
184
+ className="font-field-ring-level"
185
+ cx="6"
186
+ cy="6"
187
+ r="4.5"
188
+ pathLength={100}
189
+ {...(percent === null ? {} : { strokeDasharray: `${percent} 100` })}
190
+ transform="rotate(-90 6 6)"
191
+ />
192
+ </svg>
193
+ {percent !== null && <span className="font-field-percent">{percent}%</span>}
194
+ </span>
195
+ );
196
+ }
@@ -1,5 +1,5 @@
1
- // Only for HTML layers, and absent otherwise — the same rule every section in
2
- // this folder follows.
1
+ // The html layer's snippet. WHICH selections get this section is `layout.ts`'s
2
+ // answer, not this file's — the kind gate that used to open it is gone.
3
3
  //
4
4
  // TWO FIELDS AND NOT ONE, because the split is a correctness rule rather than a
5
5
  // layout preference: a snippet is parsed as XML, where an unescaped `a > b`
@@ -10,6 +10,9 @@
10
10
  // granularity here and not a compromise: every commit re-serializes the snippet,
11
11
  // re-decodes it through an SVG image and re-rasters the layer, so a per-keystroke
12
12
  // commit would put a decode on the critical path of every character typed.
13
+ //
14
+ // Both boxes GROW with their text, so both rows align to their start: the label
15
+ // stays level with the first line instead of centring on a paragraph.
13
16
  import { InspectorRow, InspectorSection, TextField } from "@dailies-studio/ui";
14
17
  import type { HtmlLayer3D, Layer3D } from "@dailies-studio/shot/doc";
15
18
  import { useSelectedLayers, useSelectionValue, useSelectionWrite } from "../useDocWrite.js";
@@ -17,31 +20,39 @@ import { useSelectedLayers, useSelectionValue, useSelectionWrite } from "../useD
17
20
  export function HtmlSection() {
18
21
  const write = useSelectionWrite();
19
22
  const layers = useSelectedLayers();
20
- const anyHtml = layers.some((l) => l.kind === "html");
21
23
  const markup = useSelectionValue((layer) => (layer.kind === "html" ? layer.html.markup : undefined));
22
24
  const css = useSelectionValue((layer) => (layer.kind === "html" ? (layer.html.css ?? "") : undefined));
23
- if (!anyHtml || markup.value === undefined) return null;
25
+ // data gate, not a kind gate: see layout.ts. A mixed selection led by a rect
26
+ // has no markup to show, and two fields over nothing is worse than none.
27
+ if (markup.value === undefined) return null;
28
+ // MIXED among the html layers alone: a rect beside one has no markup to
29
+ // disagree with, and the write passes it by.
30
+ const htmls = layers.filter((l): l is HtmlLayer3D => l.kind === "html");
31
+ const markupMixed = htmls.some((l) => l.html.markup !== markup.value);
32
+ const cssMixed = htmls.some((l) => (l.html.css ?? "") !== css.value);
24
33
 
25
34
  const patch = (next: Partial<HtmlLayer3D["html"]>): void =>
26
- write((layer) => (layer.kind === "html" ? ({ ...layer, html: { ...layer.html, ...next } } as Layer3D) : layer));
35
+ write((layer) =>
36
+ layer.kind === "html" ? ({ ...layer, html: { ...layer.html, ...next } } as Layer3D) : layer,
37
+ );
27
38
 
28
39
  return (
29
- <InspectorSection title="Html" persistKey="shot:html">
30
- <InspectorRow label="Markup">
40
+ <InspectorSection className="insp-object" title="Html" persistKey="shot:html">
41
+ <InspectorRow label="Markup" align="start">
31
42
  <TextField
32
- value={markup.value}
43
+ value={markupMixed ? "" : markup.value}
33
44
  multiline
34
45
  ariaLabel="Html markup"
35
- placeholder="<div>…</div>"
46
+ placeholder={markupMixed ? "Mixed" : "<div>…</div>"}
36
47
  onCommit={(next) => patch({ markup: next })}
37
48
  />
38
49
  </InspectorRow>
39
- <InspectorRow label="CSS">
50
+ <InspectorRow label="CSS" align="start">
40
51
  <TextField
41
- value={css.value ?? ""}
52
+ value={cssMixed ? "" : (css.value ?? "")}
42
53
  multiline
43
54
  ariaLabel="Html css"
44
- placeholder=".root { … }"
55
+ placeholder={cssMixed ? "Mixed" : ".root { … }"}
45
56
  onCommit={(next) => patch({ css: next })}
46
57
  />
47
58
  </InspectorRow>