@arbidocs/blocks 0.5.120 → 0.5.121

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 (413) hide show
  1. package/dist/ClippingMediaViewer-B6WG7Q64.cjs +156 -0
  2. package/dist/ClippingMediaViewer-B6WG7Q64.cjs.map +1 -0
  3. package/dist/ClippingPdfViewer-6GCVTV3V.cjs +79 -0
  4. package/dist/ClippingPdfViewer-6GCVTV3V.cjs.map +1 -0
  5. package/dist/{ConnectedCallDock-WER7R222.cjs → ConnectedCallDock-DC5V5OUJ.cjs} +25 -23
  6. package/dist/ConnectedCallDock-DC5V5OUJ.cjs.map +1 -0
  7. package/dist/{ConnectedCallRoom-T7OBQTYP.cjs → ConnectedCallRoom-FQLXWTFB.cjs} +18 -16
  8. package/dist/{ConnectedCallRoom-T7OBQTYP.cjs.map → ConnectedCallRoom-FQLXWTFB.cjs.map} +1 -1
  9. package/dist/{ConnectedNativeDocxComparison-SGE4R3M4.cjs → ConnectedNativeDocxComparison-4Q5S7VLD.cjs} +17 -17
  10. package/dist/{ConnectedNativeDocxComparison-SGE4R3M4.cjs.map → ConnectedNativeDocxComparison-4Q5S7VLD.cjs.map} +1 -1
  11. package/dist/ConnectedPresentation-4HLDZDWV.cjs +183 -0
  12. package/dist/ConnectedPresentation-4HLDZDWV.cjs.map +1 -0
  13. package/dist/{ConnectedProductionSetDialog-LHB4VQWC.cjs → ConnectedProductionSetDialog-4EAY6KAZ.cjs} +3 -3
  14. package/dist/{ConnectedProductionSetDialog-LHB4VQWC.cjs.map → ConnectedProductionSetDialog-4EAY6KAZ.cjs.map} +1 -1
  15. package/dist/ConnectedSlidesTab-7QDE2CZD.cjs +234 -0
  16. package/dist/ConnectedSlidesTab-7QDE2CZD.cjs.map +1 -0
  17. package/dist/{ConnectedStreamedWordArtifact-6MCLJRIT.cjs → ConnectedStreamedWordArtifact-FFOQJBOI.cjs} +17 -17
  18. package/dist/{ConnectedStreamedWordArtifact-6MCLJRIT.cjs.map → ConnectedStreamedWordArtifact-FFOQJBOI.cjs.map} +1 -1
  19. package/dist/{DocumentReviewCard-BPIAJJED.cjs → DocumentReviewCard-SNWVEZ7S.cjs} +17 -16
  20. package/dist/DocumentReviewCard-SNWVEZ7S.cjs.map +1 -0
  21. package/dist/{FilesViewerDialog-67WK3TZC.cjs → FilesViewerDialog-IZI63FOV.cjs} +52 -45
  22. package/dist/FilesViewerDialog-IZI63FOV.cjs.map +1 -0
  23. package/dist/{GridViewImpl-ZMJJWTOM.cjs → GridViewImpl-LTYET77Q.cjs} +3 -3
  24. package/dist/GridViewImpl-LTYET77Q.cjs.map +1 -0
  25. package/dist/{WorkspaceTableView-J3C6VH4T.cjs → WorkspaceTableView-TEIRB6VJ.cjs} +11 -10
  26. package/dist/WorkspaceTableView-TEIRB6VJ.cjs.map +1 -0
  27. package/dist/agents/index.cjs +297 -297
  28. package/dist/{ai-IM4A23YT.cjs → ai-KBYNRNZ6.cjs} +28 -26
  29. package/dist/ai-KBYNRNZ6.cjs.map +1 -0
  30. package/dist/calls/connected/CallControls.d.ts +2 -0
  31. package/dist/calls/connected/CallControls.js +71 -9
  32. package/dist/calls/connected/CallControls.js.map +2 -2
  33. package/dist/calls/connected/ShareDocumentDialog.d.ts +14 -0
  34. package/dist/calls/connected/ShareDocumentDialog.js +77 -0
  35. package/dist/calls/connected/ShareDocumentDialog.js.map +7 -0
  36. package/dist/calls/index.cjs +121 -121
  37. package/dist/chat/index.cjs +9 -8
  38. package/dist/{chunk-T23UTIRI.cjs → chunk-2PZT3J34.cjs} +6 -6
  39. package/dist/{chunk-T23UTIRI.cjs.map → chunk-2PZT3J34.cjs.map} +1 -1
  40. package/dist/{chunk-SGYSDABK.cjs → chunk-2W5E62EY.cjs} +6 -6
  41. package/dist/{chunk-SGYSDABK.cjs.map → chunk-2W5E62EY.cjs.map} +1 -1
  42. package/dist/{chunk-TDO5UWXF.cjs → chunk-3SCNXNHI.cjs} +5 -5
  43. package/dist/{chunk-TDO5UWXF.cjs.map → chunk-3SCNXNHI.cjs.map} +1 -1
  44. package/dist/{chunk-KTQOGT2L.cjs → chunk-474GPLCM.cjs} +7 -7
  45. package/dist/{chunk-KTQOGT2L.cjs.map → chunk-474GPLCM.cjs.map} +1 -1
  46. package/dist/{chunk-TP5PGWIO.cjs → chunk-4IOYQLKG.cjs} +3 -3
  47. package/dist/{chunk-TP5PGWIO.cjs.map → chunk-4IOYQLKG.cjs.map} +1 -1
  48. package/dist/{chunk-NY3YUCOW.cjs → chunk-4NPCDS2X.cjs} +3 -3
  49. package/dist/{chunk-NY3YUCOW.cjs.map → chunk-4NPCDS2X.cjs.map} +1 -1
  50. package/dist/{chunk-HA66DHW6.cjs → chunk-4RBQ44RY.cjs} +28 -28
  51. package/dist/chunk-4RBQ44RY.cjs.map +1 -0
  52. package/dist/{chunk-BQ5RIWWT.cjs → chunk-4VZZWYEC.cjs} +3 -3
  53. package/dist/{chunk-BQ5RIWWT.cjs.map → chunk-4VZZWYEC.cjs.map} +1 -1
  54. package/dist/{chunk-BP3IJ34D.cjs → chunk-52KGMAO5.cjs} +15 -15
  55. package/dist/{chunk-BP3IJ34D.cjs.map → chunk-52KGMAO5.cjs.map} +1 -1
  56. package/dist/{chunk-5IZFSAEZ.cjs → chunk-5534FYXP.cjs} +4 -4
  57. package/dist/{chunk-5IZFSAEZ.cjs.map → chunk-5534FYXP.cjs.map} +1 -1
  58. package/dist/chunk-5GCCUXMT.cjs +63 -0
  59. package/dist/chunk-5GCCUXMT.cjs.map +1 -0
  60. package/dist/chunk-5LTIZ44D.cjs +95 -0
  61. package/dist/chunk-5LTIZ44D.cjs.map +1 -0
  62. package/dist/{chunk-DFBDQKGE.cjs → chunk-734NPQNR.cjs} +4 -4
  63. package/dist/{chunk-DFBDQKGE.cjs.map → chunk-734NPQNR.cjs.map} +1 -1
  64. package/dist/{chunk-X36UR4A7.cjs → chunk-7VUJGHMY.cjs} +323 -96
  65. package/dist/chunk-7VUJGHMY.cjs.map +1 -0
  66. package/dist/chunk-AFQTQC7A.cjs +35 -0
  67. package/dist/chunk-AFQTQC7A.cjs.map +1 -0
  68. package/dist/{chunk-YYSBWMDJ.cjs → chunk-AY6EZSZB.cjs} +4 -4
  69. package/dist/{chunk-YYSBWMDJ.cjs.map → chunk-AY6EZSZB.cjs.map} +1 -1
  70. package/dist/{chunk-BUVIBHFU.cjs → chunk-BVAT4EVC.cjs} +38 -38
  71. package/dist/chunk-BVAT4EVC.cjs.map +1 -0
  72. package/dist/{chunk-ECLUFKYR.cjs → chunk-C2OICDMI.cjs} +10 -10
  73. package/dist/{chunk-ECLUFKYR.cjs.map → chunk-C2OICDMI.cjs.map} +1 -1
  74. package/dist/{chunk-CPR6EHG7.cjs → chunk-DR2AH7JL.cjs} +179 -55
  75. package/dist/chunk-DR2AH7JL.cjs.map +1 -0
  76. package/dist/{chunk-NJCK6V6U.cjs → chunk-EATIFPKP.cjs} +4 -60
  77. package/dist/chunk-EATIFPKP.cjs.map +1 -0
  78. package/dist/{chunk-B3LZITVF.cjs → chunk-EHP6OH5D.cjs} +11 -451
  79. package/dist/chunk-EHP6OH5D.cjs.map +1 -0
  80. package/dist/{chunk-775S43W7.cjs → chunk-ENGL7P3I.cjs} +31 -31
  81. package/dist/{chunk-775S43W7.cjs.map → chunk-ENGL7P3I.cjs.map} +1 -1
  82. package/dist/{chunk-VZCPMXWC.cjs → chunk-FKFQCK3C.cjs} +4 -4
  83. package/dist/{chunk-VZCPMXWC.cjs.map → chunk-FKFQCK3C.cjs.map} +1 -1
  84. package/dist/{chunk-6UTKM5AB.cjs → chunk-GKVE5JGB.cjs} +76 -76
  85. package/dist/chunk-GKVE5JGB.cjs.map +1 -0
  86. package/dist/{chunk-Q6CLWOP2.cjs → chunk-GPX5VC57.cjs} +50 -50
  87. package/dist/{chunk-Q6CLWOP2.cjs.map → chunk-GPX5VC57.cjs.map} +1 -1
  88. package/dist/{chunk-WAV762GP.cjs → chunk-GQXPNIRE.cjs} +6 -6
  89. package/dist/{chunk-WAV762GP.cjs.map → chunk-GQXPNIRE.cjs.map} +1 -1
  90. package/dist/chunk-GX2EBMJW.cjs +133 -0
  91. package/dist/chunk-GX2EBMJW.cjs.map +1 -0
  92. package/dist/{chunk-7OPOPYV6.cjs → chunk-H5OLHP2U.cjs} +3 -3
  93. package/dist/{chunk-7OPOPYV6.cjs.map → chunk-H5OLHP2U.cjs.map} +1 -1
  94. package/dist/chunk-HYUKPHCT.cjs +66 -0
  95. package/dist/chunk-HYUKPHCT.cjs.map +1 -0
  96. package/dist/{chunk-NLSWDP42.cjs → chunk-JQLWKNJO.cjs} +22 -12
  97. package/dist/chunk-JQLWKNJO.cjs.map +1 -0
  98. package/dist/{chunk-CKCG4NDB.cjs → chunk-JZEYHDWJ.cjs} +75 -75
  99. package/dist/{chunk-CKCG4NDB.cjs.map → chunk-JZEYHDWJ.cjs.map} +1 -1
  100. package/dist/{chunk-PGJYD67F.cjs → chunk-KNGZZC4O.cjs} +7 -6
  101. package/dist/chunk-KNGZZC4O.cjs.map +1 -0
  102. package/dist/{chunk-W6INT5GS.cjs → chunk-KP5AITQ4.cjs} +10 -10
  103. package/dist/{chunk-W6INT5GS.cjs.map → chunk-KP5AITQ4.cjs.map} +1 -1
  104. package/dist/{chunk-YYPZOEED.cjs → chunk-LHWOKOMP.cjs} +23 -2
  105. package/dist/chunk-LHWOKOMP.cjs.map +1 -0
  106. package/dist/{chunk-WXUU3COI.cjs → chunk-MZ2KZXNU.cjs} +3 -3
  107. package/dist/{chunk-WXUU3COI.cjs.map → chunk-MZ2KZXNU.cjs.map} +1 -1
  108. package/dist/chunk-NAJN32MF.cjs +154 -0
  109. package/dist/chunk-NAJN32MF.cjs.map +1 -0
  110. package/dist/{chunk-557MJ4IH.cjs → chunk-NLOETJCB.cjs} +17 -154
  111. package/dist/chunk-NLOETJCB.cjs.map +1 -0
  112. package/dist/{chunk-4XJEHKVZ.cjs → chunk-PIYYBPHH.cjs} +4 -4
  113. package/dist/{chunk-4XJEHKVZ.cjs.map → chunk-PIYYBPHH.cjs.map} +1 -1
  114. package/dist/chunk-Q3XJUXID.cjs +4774 -0
  115. package/dist/chunk-Q3XJUXID.cjs.map +1 -0
  116. package/dist/{chunk-325GVZE6.cjs → chunk-Q6DQGGP4.cjs} +3 -3
  117. package/dist/{chunk-325GVZE6.cjs.map → chunk-Q6DQGGP4.cjs.map} +1 -1
  118. package/dist/chunk-QFXILJMJ.cjs +458 -0
  119. package/dist/chunk-QFXILJMJ.cjs.map +1 -0
  120. package/dist/{chunk-QQWHG3KF.cjs → chunk-RYVZDFVO.cjs} +5 -5
  121. package/dist/{chunk-QQWHG3KF.cjs.map → chunk-RYVZDFVO.cjs.map} +1 -1
  122. package/dist/{chunk-EOG6OT5I.cjs → chunk-TXP2N3DC.cjs} +4 -4
  123. package/dist/{chunk-EOG6OT5I.cjs.map → chunk-TXP2N3DC.cjs.map} +1 -1
  124. package/dist/{chunk-KXIRVKCJ.cjs → chunk-UUENH4BJ.cjs} +3 -3
  125. package/dist/{chunk-KXIRVKCJ.cjs.map → chunk-UUENH4BJ.cjs.map} +1 -1
  126. package/dist/chunk-VJ5ZLRBD.cjs +27 -0
  127. package/dist/chunk-VJ5ZLRBD.cjs.map +1 -0
  128. package/dist/{chunk-3GWWOYGS.cjs → chunk-W6B3RYTX.cjs} +4 -4
  129. package/dist/{chunk-3GWWOYGS.cjs.map → chunk-W6B3RYTX.cjs.map} +1 -1
  130. package/dist/chunk-WOOOMTTA.cjs +19 -0
  131. package/dist/chunk-WOOOMTTA.cjs.map +1 -0
  132. package/dist/{chunk-CJYWHUZR.cjs → chunk-XCKHOXRP.cjs} +4 -4
  133. package/dist/{chunk-CJYWHUZR.cjs.map → chunk-XCKHOXRP.cjs.map} +1 -1
  134. package/dist/{chunk-6WMMZLEV.cjs → chunk-XJEXFEWG.cjs} +2 -27
  135. package/dist/chunk-XJEXFEWG.cjs.map +1 -0
  136. package/dist/chunk-XK4TVJKD.cjs +73 -0
  137. package/dist/chunk-XK4TVJKD.cjs.map +1 -0
  138. package/dist/chunk-XTR26TNE.cjs +1358 -0
  139. package/dist/chunk-XTR26TNE.cjs.map +1 -0
  140. package/dist/{chunk-E7TE2WVT.cjs → chunk-ZVEW3JBT.cjs} +3 -3
  141. package/dist/{chunk-E7TE2WVT.cjs.map → chunk-ZVEW3JBT.cjs.map} +1 -1
  142. package/dist/collaboration/index.cjs +70 -70
  143. package/dist/components/ZoomSlider.d.ts +25 -0
  144. package/dist/components/ZoomSlider.js +59 -0
  145. package/dist/components/ZoomSlider.js.map +7 -0
  146. package/dist/connect/docx.cjs +20 -20
  147. package/dist/connect/index.cjs +1150 -1232
  148. package/dist/connect/index.cjs.map +1 -1
  149. package/dist/connect/viewer.cjs +40 -34
  150. package/dist/connection/ai.d.ts +1 -1
  151. package/dist/connection/files/ConnectedNewFileMenu.d.ts +2 -1
  152. package/dist/connection/files/ConnectedNewFileMenu.js +9 -3
  153. package/dist/connection/files/ConnectedNewFileMenu.js.map +2 -2
  154. package/dist/connection/files/explorer/ConnectedFilesExplorer.js +1 -1
  155. package/dist/connection/files/explorer/ConnectedFilesExplorer.js.map +2 -2
  156. package/dist/connection/files/explorer/FileTiles.js +1 -1
  157. package/dist/connection/files/explorer/FileTiles.js.map +2 -2
  158. package/dist/connection/files/fields/documentField.js +1 -1
  159. package/dist/connection/files/fields/documentField.js.map +2 -2
  160. package/dist/connection/files/grid/documentColumns.js +1 -1
  161. package/dist/connection/files/grid/documentColumns.js.map +2 -2
  162. package/dist/connection/files/page/DocumentsFilterBarActions.js +17 -28
  163. package/dist/connection/files/page/DocumentsFilterBarActions.js.map +2 -2
  164. package/dist/connection/mail/useMailboxDefaults.js +1 -1
  165. package/dist/connection/mail/useMailboxDefaults.js.map +2 -2
  166. package/dist/connection/panels/useSessionPanelMode.js +3 -2
  167. package/dist/connection/panels/useSessionPanelMode.js.map +2 -2
  168. package/dist/connection/presentation/ClippingMediaViewer.d.ts +12 -0
  169. package/dist/connection/presentation/ClippingMediaViewer.js +153 -0
  170. package/dist/connection/presentation/ClippingMediaViewer.js.map +7 -0
  171. package/dist/connection/presentation/ClippingPdfViewer.d.ts +22 -0
  172. package/dist/connection/presentation/ClippingPdfViewer.js +75 -0
  173. package/dist/connection/presentation/ClippingPdfViewer.js.map +7 -0
  174. package/dist/connection/presentation/ConnectedPresentation.d.ts +12 -0
  175. package/dist/connection/presentation/ConnectedPresentation.js +100 -0
  176. package/dist/connection/presentation/ConnectedPresentation.js.map +7 -0
  177. package/dist/connection/presentation/ConnectedSlidesTab.d.ts +20 -0
  178. package/dist/connection/presentation/ConnectedSlidesTab.js +220 -0
  179. package/dist/connection/presentation/ConnectedSlidesTab.js.map +7 -0
  180. package/dist/connection/presentation/CropClipDialog.d.ts +11 -0
  181. package/dist/connection/presentation/CropClipDialog.js +86 -0
  182. package/dist/connection/presentation/CropClipDialog.js.map +7 -0
  183. package/dist/connection/presentation/GenerateTile.d.ts +4 -0
  184. package/dist/connection/presentation/GenerateTile.js +24 -0
  185. package/dist/connection/presentation/GenerateTile.js.map +7 -0
  186. package/dist/connection/presentation/LabelColumnMenu.d.ts +8 -0
  187. package/dist/connection/presentation/LabelColumnMenu.js +38 -0
  188. package/dist/connection/presentation/LabelColumnMenu.js.map +7 -0
  189. package/dist/connection/presentation/PresentationDialogs.d.ts +39 -0
  190. package/dist/connection/presentation/PresentationDialogs.js +295 -0
  191. package/dist/connection/presentation/PresentationDialogs.js.map +7 -0
  192. package/dist/connection/presentation/PresentationFileCard.d.ts +10 -0
  193. package/dist/connection/presentation/PresentationFileCard.js +35 -0
  194. package/dist/connection/presentation/PresentationFileCard.js.map +7 -0
  195. package/dist/connection/presentation/PresentationPill.d.ts +8 -0
  196. package/dist/connection/presentation/PresentationPill.js +44 -0
  197. package/dist/connection/presentation/PresentationPill.js.map +7 -0
  198. package/dist/connection/presentation/PresentationSlot.d.ts +7 -0
  199. package/dist/connection/presentation/PresentationSlot.js +35 -0
  200. package/dist/connection/presentation/PresentationSlot.js.map +7 -0
  201. package/dist/connection/presentation/requests.d.ts +16 -0
  202. package/dist/connection/presentation/requests.js +20 -0
  203. package/dist/connection/presentation/requests.js.map +7 -0
  204. package/dist/connection/presentation/useImageGeneration.d.ts +11 -0
  205. package/dist/connection/presentation/useImageGeneration.js +66 -0
  206. package/dist/connection/presentation/useImageGeneration.js.map +7 -0
  207. package/dist/connection/presentation/useLabelColumns.d.ts +15 -0
  208. package/dist/connection/presentation/useLabelColumns.js +33 -0
  209. package/dist/connection/presentation/useLabelColumns.js.map +7 -0
  210. package/dist/connection/presentation/usePresentationController.d.ts +15 -0
  211. package/dist/connection/presentation/usePresentationController.js +118 -0
  212. package/dist/connection/presentation/usePresentationController.js.map +7 -0
  213. package/dist/connection/presentation/usePresentationFiles.d.ts +44 -0
  214. package/dist/connection/presentation/usePresentationFiles.js +307 -0
  215. package/dist/connection/presentation/usePresentationFiles.js.map +7 -0
  216. package/dist/connection/presentation/useViewerClipping.d.ts +14 -0
  217. package/dist/connection/presentation/useViewerClipping.js +18 -0
  218. package/dist/connection/presentation/useViewerClipping.js.map +7 -0
  219. package/dist/connection/shell/ConnectedAppShell.js +5 -1
  220. package/dist/connection/shell/ConnectedAppShell.js.map +2 -2
  221. package/dist/connection/viewer/CleanViewControl.d.ts +8 -0
  222. package/dist/connection/viewer/CleanViewControl.js +76 -0
  223. package/dist/connection/viewer/CleanViewControl.js.map +7 -0
  224. package/dist/connection/viewer/ConnectedDocumentViewer.js +28 -29
  225. package/dist/connection/viewer/ConnectedDocumentViewer.js.map +2 -2
  226. package/dist/connection/viewer/ViewerCropModal.d.ts +5 -0
  227. package/dist/connection/viewer/ViewerCropModal.js +22 -0
  228. package/dist/connection/viewer/ViewerCropModal.js.map +7 -0
  229. package/dist/connection/viewer/ViewerPdfPane.d.ts +3 -1
  230. package/dist/connection/viewer/ViewerPdfPane.js +37 -0
  231. package/dist/connection/viewer/ViewerPdfPane.js.map +2 -2
  232. package/dist/connection/viewer/drafts.js +6 -4
  233. package/dist/connection/viewer/drafts.js.map +2 -2
  234. package/dist/connection/viewer/shareDocument.d.ts +11 -0
  235. package/dist/connection/viewer/shareDocument.js +11 -0
  236. package/dist/connection/viewer/shareDocument.js.map +7 -0
  237. package/dist/connection/viewer/slidesTabChunk.d.ts +2 -0
  238. package/dist/connection/viewer/slidesTabChunk.js +9 -0
  239. package/dist/connection/viewer/slidesTabChunk.js.map +7 -0
  240. package/dist/connection/viewer/useCleanView.d.ts +14 -0
  241. package/dist/connection/viewer/useCleanView.js +29 -0
  242. package/dist/connection/viewer/useCleanView.js.map +7 -0
  243. package/dist/connection/viewer/useViewedDocument.d.ts +1 -0
  244. package/dist/connection/viewer/useViewedDocument.js +9 -1
  245. package/dist/connection/viewer/useViewedDocument.js.map +2 -2
  246. package/dist/connection/viewer/useViewerTabs.js +18 -4
  247. package/dist/connection/viewer/useViewerTabs.js.map +2 -2
  248. package/dist/connection/workflows/ConnectedWorkflowStart.d.ts +14 -2
  249. package/dist/connection/workflows/ConnectedWorkflowStart.js +5 -2
  250. package/dist/connection/workflows/ConnectedWorkflowStart.js.map +2 -2
  251. package/dist/dashboards/index.cjs +1 -1
  252. package/dist/databases/index.cjs +49 -49
  253. package/dist/document-tools/DocumentToolButtons.d.ts +2 -0
  254. package/dist/document-tools/DocumentToolButtons.js +22 -1
  255. package/dist/document-tools/DocumentToolButtons.js.map +2 -2
  256. package/dist/document-tools/index.cjs +16 -12
  257. package/dist/document-tools/index.d.ts +1 -1
  258. package/dist/document-tools/index.js +2 -0
  259. package/dist/document-tools/index.js.map +2 -2
  260. package/dist/embed/index.cjs +41 -39
  261. package/dist/files/GridViewImpl.js +1 -1
  262. package/dist/files/GridViewImpl.js.map +2 -2
  263. package/dist/files/ViewerChrome.d.ts +5 -1
  264. package/dist/files/ViewerChrome.js +16 -6
  265. package/dist/files/ViewerChrome.js.map +2 -2
  266. package/dist/files/index.cjs +225 -225
  267. package/dist/files/navigator/DocumentSearchList.js +1 -1
  268. package/dist/files/navigator/DocumentSearchList.js.map +2 -2
  269. package/dist/index.cjs +1666 -1664
  270. package/dist/index.cjs.map +1 -1
  271. package/dist/presentation/BoxActionBar.d.ts +11 -0
  272. package/dist/presentation/BoxActionBar.js +182 -0
  273. package/dist/presentation/BoxActionBar.js.map +7 -0
  274. package/dist/presentation/ClipActionBar.d.ts +26 -0
  275. package/dist/presentation/ClipActionBar.js +103 -0
  276. package/dist/presentation/ClipActionBar.js.map +7 -0
  277. package/dist/presentation/ClipCaptureOverlay.d.ts +14 -0
  278. package/dist/presentation/ClipCaptureOverlay.js +69 -0
  279. package/dist/presentation/ClipCaptureOverlay.js.map +7 -0
  280. package/dist/presentation/ClipImage.d.ts +21 -0
  281. package/dist/presentation/ClipImage.js +119 -0
  282. package/dist/presentation/ClipImage.js.map +7 -0
  283. package/dist/presentation/ClipTray.d.ts +11 -0
  284. package/dist/presentation/ClipTray.js +152 -0
  285. package/dist/presentation/ClipTray.js.map +7 -0
  286. package/dist/presentation/ClipViewControls.d.ts +11 -0
  287. package/dist/presentation/ClipViewControls.js +142 -0
  288. package/dist/presentation/ClipViewControls.js.map +7 -0
  289. package/dist/presentation/DesignMenu.d.ts +8 -0
  290. package/dist/presentation/DesignMenu.js +199 -0
  291. package/dist/presentation/DesignMenu.js.map +7 -0
  292. package/dist/presentation/LayoutMenu.d.ts +17 -0
  293. package/dist/presentation/LayoutMenu.js +122 -0
  294. package/dist/presentation/LayoutMenu.js.map +7 -0
  295. package/dist/presentation/MediaClip.d.ts +15 -0
  296. package/dist/presentation/MediaClip.js +67 -0
  297. package/dist/presentation/MediaClip.js.map +7 -0
  298. package/dist/presentation/Notice.d.ts +6 -0
  299. package/dist/presentation/Notice.js +43 -0
  300. package/dist/presentation/Notice.js.map +7 -0
  301. package/dist/presentation/PresentationStage.d.ts +32 -0
  302. package/dist/presentation/PresentationStage.js +232 -0
  303. package/dist/presentation/PresentationStage.js.map +7 -0
  304. package/dist/presentation/PresenterView.d.ts +16 -0
  305. package/dist/presentation/PresenterView.js +69 -0
  306. package/dist/presentation/PresenterView.js.map +7 -0
  307. package/dist/presentation/SlideDecor.d.ts +25 -0
  308. package/dist/presentation/SlideDecor.js +189 -0
  309. package/dist/presentation/SlideDecor.js.map +7 -0
  310. package/dist/presentation/SlidesEditor.d.ts +25 -0
  311. package/dist/presentation/SlidesEditor.js +359 -0
  312. package/dist/presentation/SlidesEditor.js.map +7 -0
  313. package/dist/presentation/StageBox.d.ts +30 -0
  314. package/dist/presentation/StageBox.js +281 -0
  315. package/dist/presentation/StageBox.js.map +7 -0
  316. package/dist/presentation/StageItem.d.ts +37 -0
  317. package/dist/presentation/StageItem.js +211 -0
  318. package/dist/presentation/StageItem.js.map +7 -0
  319. package/dist/presentation/box-style.d.ts +6 -0
  320. package/dist/presentation/box-style.js +19 -0
  321. package/dist/presentation/box-style.js.map +7 -0
  322. package/dist/presentation/boxes.d.ts +105 -0
  323. package/dist/presentation/boxes.js +103 -0
  324. package/dist/presentation/boxes.js.map +7 -0
  325. package/dist/presentation/draw-boxes.d.ts +8 -0
  326. package/dist/presentation/draw-boxes.js +130 -0
  327. package/dist/presentation/draw-boxes.js.map +7 -0
  328. package/dist/presentation/export-boxes.d.ts +10 -0
  329. package/dist/presentation/export-boxes.js +74 -0
  330. package/dist/presentation/export-boxes.js.map +7 -0
  331. package/dist/presentation/export-pptx.d.ts +16 -0
  332. package/dist/presentation/export-pptx.js +194 -0
  333. package/dist/presentation/export-pptx.js.map +7 -0
  334. package/dist/presentation/import-pptx.d.ts +35 -0
  335. package/dist/presentation/import-pptx.js +659 -0
  336. package/dist/presentation/import-pptx.js.map +7 -0
  337. package/dist/presentation/merge-outside-edits.d.ts +12 -0
  338. package/dist/presentation/merge-outside-edits.js +35 -0
  339. package/dist/presentation/merge-outside-edits.js.map +7 -0
  340. package/dist/presentation/model.d.ts +201 -0
  341. package/dist/presentation/model.js +271 -0
  342. package/dist/presentation/model.js.map +7 -0
  343. package/dist/presentation/panel-id.d.ts +34 -0
  344. package/dist/presentation/panel-id.js +24 -0
  345. package/dist/presentation/panel-id.js.map +7 -0
  346. package/dist/presentation/persistence.d.ts +121 -0
  347. package/dist/presentation/persistence.js +141 -0
  348. package/dist/presentation/persistence.js.map +7 -0
  349. package/dist/presentation/pptx-data.d.ts +22 -0
  350. package/dist/presentation/pptx-data.js +83 -0
  351. package/dist/presentation/pptx-data.js.map +7 -0
  352. package/dist/presentation/pptx-xml.d.ts +90 -0
  353. package/dist/presentation/pptx-xml.js +333 -0
  354. package/dist/presentation/pptx-xml.js.map +7 -0
  355. package/dist/presentation/render-slides.d.ts +58 -0
  356. package/dist/presentation/render-slides.js +309 -0
  357. package/dist/presentation/render-slides.js.map +7 -0
  358. package/dist/presentation/saved-boxes.d.ts +11 -0
  359. package/dist/presentation/saved-boxes.js +80 -0
  360. package/dist/presentation/saved-boxes.js.map +7 -0
  361. package/dist/presentation/slide-layout.d.ts +54 -0
  362. package/dist/presentation/slide-layout.js +117 -0
  363. package/dist/presentation/slide-layout.js.map +7 -0
  364. package/dist/presentation/stage-videos.d.ts +5 -0
  365. package/dist/presentation/stage-videos.js +19 -0
  366. package/dist/presentation/stage-videos.js.map +7 -0
  367. package/dist/presentation/store.d.ts +203 -0
  368. package/dist/presentation/store.js +429 -0
  369. package/dist/presentation/store.js.map +7 -0
  370. package/dist/presentation/theme.d.ts +102 -0
  371. package/dist/presentation/theme.js +276 -0
  372. package/dist/presentation/theme.js.map +7 -0
  373. package/dist/presentation/useClipDrag.d.ts +26 -0
  374. package/dist/presentation/useClipDrag.js +65 -0
  375. package/dist/presentation/useClipDrag.js.map +7 -0
  376. package/dist/presentation/useObjectUrl.d.ts +6 -0
  377. package/dist/presentation/useObjectUrl.js +15 -0
  378. package/dist/presentation/useObjectUrl.js.map +7 -0
  379. package/dist/presentation/usePresenterKeys.d.ts +13 -0
  380. package/dist/presentation/usePresenterKeys.js +62 -0
  381. package/dist/presentation/usePresenterKeys.js.map +7 -0
  382. package/dist/settings/index.cjs +151 -150
  383. package/dist/shell/PanelPortal.js +1 -0
  384. package/dist/shell/PanelPortal.js.map +2 -2
  385. package/dist/shell/index.cjs +112 -110
  386. package/dist/shell/panel-windows.d.ts +2 -0
  387. package/dist/shell/panel-windows.js +7 -2
  388. package/dist/shell/panel-windows.js.map +2 -2
  389. package/dist/studio/index.cjs +28 -26
  390. package/dist/studio/index.cjs.map +1 -1
  391. package/dist/styles.css +1 -1
  392. package/dist/workflow-canvas/index.cjs +55 -54
  393. package/dist/workflow-canvas/index.cjs.map +1 -1
  394. package/node_modules/@arbidocs/coedit/package.json +1 -1
  395. package/package.json +6 -5
  396. package/dist/ConnectedCallDock-WER7R222.cjs.map +0 -1
  397. package/dist/DocumentReviewCard-BPIAJJED.cjs.map +0 -1
  398. package/dist/FilesViewerDialog-67WK3TZC.cjs.map +0 -1
  399. package/dist/GridViewImpl-ZMJJWTOM.cjs.map +0 -1
  400. package/dist/WorkspaceTableView-J3C6VH4T.cjs.map +0 -1
  401. package/dist/ai-IM4A23YT.cjs.map +0 -1
  402. package/dist/chunk-557MJ4IH.cjs.map +0 -1
  403. package/dist/chunk-6UTKM5AB.cjs.map +0 -1
  404. package/dist/chunk-6WMMZLEV.cjs.map +0 -1
  405. package/dist/chunk-B3LZITVF.cjs.map +0 -1
  406. package/dist/chunk-BUVIBHFU.cjs.map +0 -1
  407. package/dist/chunk-CPR6EHG7.cjs.map +0 -1
  408. package/dist/chunk-HA66DHW6.cjs.map +0 -1
  409. package/dist/chunk-NJCK6V6U.cjs.map +0 -1
  410. package/dist/chunk-NLSWDP42.cjs.map +0 -1
  411. package/dist/chunk-PGJYD67F.cjs.map +0 -1
  412. package/dist/chunk-X36UR4A7.cjs.map +0 -1
  413. package/dist/chunk-YYPZOEED.cjs.map +0 -1
@@ -0,0 +1,4774 @@
1
+ "use client";
2
+ 'use strict';
3
+
4
+ var chunkWOOOMTTA_cjs = require('./chunk-WOOOMTTA.cjs');
5
+ var chunk5GCCUXMT_cjs = require('./chunk-5GCCUXMT.cjs');
6
+ var chunkNLOETJCB_cjs = require('./chunk-NLOETJCB.cjs');
7
+ var chunkGKVE5JGB_cjs = require('./chunk-GKVE5JGB.cjs');
8
+ var chunk5LTIZ44D_cjs = require('./chunk-5LTIZ44D.cjs');
9
+ var chunkXTR26TNE_cjs = require('./chunk-XTR26TNE.cjs');
10
+ var chunk7JLJH5CW_cjs = require('./chunk-7JLJH5CW.cjs');
11
+ var chunkVZOOGA47_cjs = require('./chunk-VZOOGA47.cjs');
12
+ var chunkTKHCIHE2_cjs = require('./chunk-TKHCIHE2.cjs');
13
+ var chunkULWRNGGE_cjs = require('./chunk-ULWRNGGE.cjs');
14
+ var react = require('react');
15
+ var lucideReact = require('lucide-react');
16
+ var ui = require('@arbidocs/react/ui');
17
+ var reactDom = require('react-dom');
18
+ var viewers = require('@arbidocs/react/viewers');
19
+ var jsxRuntime = require('react/jsx-runtime');
20
+ var react$1 = require('@arbidocs/react');
21
+ var documentTools = require('@arbidocs/react/document-tools');
22
+
23
+ function cropStyle(rect) {
24
+ return {
25
+ position: "absolute",
26
+ width: `${100 / rect.w}%`,
27
+ height: `${100 / rect.h}%`,
28
+ left: `${-rect.x / rect.w * 100}%`,
29
+ top: `${-rect.y / rect.h * 100}%`,
30
+ maxWidth: "none"
31
+ };
32
+ }
33
+ function MediaClip({
34
+ clip,
35
+ className,
36
+ live
37
+ }) {
38
+ const url = chunkWOOOMTTA_cjs.useObjectUrl(clip.source);
39
+ const videoRef = react.useRef(null);
40
+ react.useEffect(() => {
41
+ const video = videoRef.current;
42
+ if (!video) return;
43
+ const toStart = () => {
44
+ video.currentTime = clip.start ?? 0;
45
+ };
46
+ if (video.readyState >= 1) toStart();
47
+ else video.addEventListener("loadedmetadata", toStart, { once: true });
48
+ return () => video.removeEventListener("loadedmetadata", toStart);
49
+ }, [clip.start, url]);
50
+ return /* @__PURE__ */ jsxRuntime.jsx(
51
+ "div",
52
+ {
53
+ className: ui.cn("relative h-full w-full overflow-hidden bg-black", className),
54
+ "data-testid": `clip-image-${clip.id}`,
55
+ children: clip.kind === "video" ? /* @__PURE__ */ jsxRuntime.jsx(
56
+ "video",
57
+ {
58
+ ref: videoRef,
59
+ src: url,
60
+ muted: !live,
61
+ playsInline: true,
62
+ preload: "auto",
63
+ className: "pointer-events-none block select-none",
64
+ style: cropStyle(clip.rect),
65
+ ...live ? { [chunk5LTIZ44D_cjs.STAGE_VIDEO_ATTRIBUTE]: "" } : {}
66
+ }
67
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
68
+ "img",
69
+ {
70
+ src: url,
71
+ alt: clip.caption,
72
+ draggable: false,
73
+ className: "pointer-events-none block select-none",
74
+ style: cropStyle(clip.rect)
75
+ }
76
+ )
77
+ }
78
+ );
79
+ }
80
+ var WIDTH_STEP = 160;
81
+ var SETTLE_MS = 150;
82
+ var ClipImage = react.memo(function ClipImage2({
83
+ clip,
84
+ className,
85
+ live = false
86
+ }) {
87
+ return clip.kind === "pdf" ? /* @__PURE__ */ jsxRuntime.jsx(PdfClip, { clip, className }) : /* @__PURE__ */ jsxRuntime.jsx(MediaClip, { clip, className, live });
88
+ });
89
+ var sameRect = (a, b) => a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h;
90
+ function PdfClip({ clip, className }) {
91
+ const canvasRef = react.useRef(null);
92
+ const [size, setSize] = react.useState({ width: 0, height: 0 });
93
+ const [rendered, setRendered] = react.useState(null);
94
+ const [failed, setFailed] = react.useState(false);
95
+ react.useEffect(() => {
96
+ const canvas = canvasRef.current;
97
+ const view = canvas?.ownerDocument.defaultView;
98
+ if (!canvas || !view) return;
99
+ const measure = () => {
100
+ const box2 = canvas.getBoundingClientRect();
101
+ const dpr = view.devicePixelRatio || 1;
102
+ const width = Math.max(1, Math.round(box2.width * dpr));
103
+ const height = Math.max(1, Math.round(box2.height * dpr));
104
+ setSize((s) => s.width === width && s.height === height ? s : { width, height });
105
+ };
106
+ measure();
107
+ const Observer = view.ResizeObserver ?? ResizeObserver;
108
+ const observer = new Observer(measure);
109
+ observer.observe(canvas);
110
+ return () => observer.disconnect();
111
+ }, []);
112
+ react.useLayoutEffect(() => {
113
+ const canvas = canvasRef.current;
114
+ const context = canvas?.getContext("2d");
115
+ if (!canvas || !context || !size.width) return;
116
+ if (canvas.width !== size.width) canvas.width = size.width;
117
+ if (canvas.height !== size.height) canvas.height = size.height;
118
+ context.fillStyle = "#fff";
119
+ context.fillRect(0, 0, size.width, size.height);
120
+ if (!rendered) return;
121
+ const { rect } = clip;
122
+ const sx = size.width / rect.w;
123
+ const sy = size.height / rect.h;
124
+ try {
125
+ context.drawImage(
126
+ rendered.image,
127
+ (rendered.rect.x - rect.x) * sx,
128
+ (rendered.rect.y - rect.y) * sy,
129
+ rendered.rect.w * sx,
130
+ rendered.rect.h * sy
131
+ );
132
+ } catch {
133
+ setRendered(null);
134
+ }
135
+ }, [size, rendered, clip]);
136
+ const wanted = Math.ceil(size.width / WIDTH_STEP) * WIDTH_STEP;
137
+ const stale = wanted > 0 && (!rendered || !sameRect(rendered.rect, clip.rect) || wanted > rendered.width || wanted < rendered.width / 2);
138
+ react.useEffect(() => {
139
+ if (!stale) return;
140
+ const controller = new AbortController();
141
+ const rect = clip.rect;
142
+ const timer = setTimeout(() => {
143
+ viewers.renderPdfRegion({
144
+ file: clip.source,
145
+ page: clip.page,
146
+ rect,
147
+ masks: clip.masks,
148
+ width: wanted,
149
+ signal: controller.signal
150
+ }).then((image) => {
151
+ if (controller.signal.aborted) return viewers.closePdfRegionImage(image);
152
+ setFailed(false);
153
+ setRendered({ image, rect, width: wanted });
154
+ }).catch(() => !controller.signal.aborted && setFailed(true));
155
+ }, SETTLE_MS);
156
+ return () => {
157
+ clearTimeout(timer);
158
+ controller.abort();
159
+ };
160
+ }, [stale, wanted, clip.source, clip.page, clip.rect, clip.masks]);
161
+ const previous = react.useRef(null);
162
+ react.useEffect(() => {
163
+ if (previous.current && previous.current !== rendered) {
164
+ viewers.closePdfRegionImage(previous.current.image);
165
+ }
166
+ previous.current = rendered;
167
+ }, [rendered]);
168
+ return /* @__PURE__ */ jsxRuntime.jsxs(
169
+ "div",
170
+ {
171
+ className: ui.cn("relative h-full w-full overflow-hidden bg-white", className),
172
+ "data-testid": `clip-image-${clip.id}`,
173
+ children: [
174
+ /* @__PURE__ */ jsxRuntime.jsx(
175
+ "canvas",
176
+ {
177
+ ref: canvasRef,
178
+ className: "pointer-events-none block h-full w-full",
179
+ role: "img",
180
+ "aria-label": clip.caption,
181
+ "aria-busy": !rendered
182
+ }
183
+ ),
184
+ failed && !rendered && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 flex items-center justify-center p-2 text-center text-xs text-neutral-500", children: "Couldn't draw this clip" })
185
+ ]
186
+ }
187
+ );
188
+ }
189
+ var DRAG_THRESHOLD = 6;
190
+ var DROP_ATTRIBUTE = "data-presentation-drop";
191
+ function dropAt(doc, clipId, x, y) {
192
+ const target = doc.elementFromPoint(x, y)?.closest(`[${DROP_ATTRIBUTE}]`);
193
+ if (!target) return false;
194
+ const state = chunkXTR26TNE_cjs.usePresentation.getState();
195
+ if (target.getAttribute(DROP_ATTRIBUTE) === "slide") {
196
+ const index2 = Number(target.getAttribute("data-slide-index"));
197
+ if (!Number.isInteger(index2)) return false;
198
+ state.goTo(index2);
199
+ state.place(clipId);
200
+ return true;
201
+ }
202
+ const index = Number(target.getAttribute("data-slide-index"));
203
+ if (target.hasAttribute("data-slide-index") && Number.isInteger(index)) state.goTo(index);
204
+ const box2 = target.getBoundingClientRect();
205
+ state.place(clipId, { x: (x - box2.left) / box2.width, y: (y - box2.top) / box2.height });
206
+ return true;
207
+ }
208
+ function useClipDrag(clipId, onTap) {
209
+ const press = react.useRef(null);
210
+ const [ghost, setGhost] = react.useState(null);
211
+ const end = () => {
212
+ press.current = null;
213
+ setGhost(null);
214
+ chunkXTR26TNE_cjs.usePresentation.getState().setDragging(null);
215
+ };
216
+ return {
217
+ ghost,
218
+ handlers: {
219
+ onPointerDown: (event) => {
220
+ if (event.button !== 0) return;
221
+ press.current = { x: event.clientX, y: event.clientY, dragging: false };
222
+ },
223
+ onPointerMove: (event) => {
224
+ const p = press.current;
225
+ if (!p) return;
226
+ if (!p.dragging) {
227
+ if (Math.hypot(event.clientX - p.x, event.clientY - p.y) < DRAG_THRESHOLD) return;
228
+ p.dragging = true;
229
+ event.currentTarget.setPointerCapture(event.pointerId);
230
+ chunkXTR26TNE_cjs.usePresentation.getState().setDragging(clipId);
231
+ }
232
+ event.preventDefault();
233
+ setGhost({ x: event.clientX, y: event.clientY });
234
+ },
235
+ onPointerUp: (event) => {
236
+ const p = press.current;
237
+ if (p?.dragging)
238
+ dropAt(event.currentTarget.ownerDocument, clipId, event.clientX, event.clientY);
239
+ else if (p) onTap();
240
+ end();
241
+ },
242
+ onPointerCancel: end
243
+ }
244
+ };
245
+ }
246
+ var TILE_HEIGHT = 72;
247
+ var TILE_WIDTH = { min: 56, max: 168 };
248
+ function tileSize(aspect, height = TILE_HEIGHT) {
249
+ const width = Math.min(Math.max(height * aspect, TILE_WIDTH.min), TILE_WIDTH.max);
250
+ return { width, height };
251
+ }
252
+ function Fitted({ clip, box: box2 }) {
253
+ const wide = clip.aspect > box2.width / box2.height;
254
+ const size = wide ? { width: box2.width, height: box2.width / clip.aspect } : { width: box2.height * clip.aspect, height: box2.height };
255
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none", style: size, children: /* @__PURE__ */ jsxRuntime.jsx(ClipImage, { clip }) });
256
+ }
257
+ function Label({ clip }) {
258
+ const [editing, setEditing] = react.useState(false);
259
+ if (editing) {
260
+ return /* @__PURE__ */ jsxRuntime.jsx(
261
+ "input",
262
+ {
263
+ autoFocus: true,
264
+ defaultValue: clip.caption,
265
+ "aria-label": "Clip label",
266
+ className: "w-full rounded border border-input bg-background px-1 text-[11px] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
267
+ onBlur: (event) => {
268
+ chunkXTR26TNE_cjs.usePresentation.getState().setCaption(clip.id, event.target.value);
269
+ setEditing(false);
270
+ },
271
+ onKeyDown: (event) => {
272
+ if (event.key === "Enter") event.currentTarget.blur();
273
+ if (event.key === "Escape") setEditing(false);
274
+ }
275
+ }
276
+ );
277
+ }
278
+ return /* @__PURE__ */ jsxRuntime.jsx(
279
+ "button",
280
+ {
281
+ type: "button",
282
+ onDoubleClick: () => setEditing(true),
283
+ title: "Double-click to rename",
284
+ className: "block w-full truncate rounded px-0.5 text-left text-[11px] text-muted-foreground hover:text-foreground",
285
+ children: clip.caption || "Untitled"
286
+ }
287
+ );
288
+ }
289
+ function TrayTile({ clip, onSlide }) {
290
+ const { place, removeClip } = chunkXTR26TNE_cjs.usePresentation.getState();
291
+ const drag = useClipDrag(clip.id, () => !onSlide && place(clip.id));
292
+ const itemRef = react.useRef(null);
293
+ const size = tileSize(clip.aspect);
294
+ return /* @__PURE__ */ jsxRuntime.jsxs(
295
+ "li",
296
+ {
297
+ ref: itemRef,
298
+ className: "group relative flex shrink-0 flex-col gap-1",
299
+ style: { width: size.width },
300
+ "data-testid": `tray-clip-${clip.id}`,
301
+ children: [
302
+ /* @__PURE__ */ jsxRuntime.jsxs(
303
+ "div",
304
+ {
305
+ className: ui.cn(
306
+ "relative flex cursor-grab touch-pan-x select-none items-center justify-center overflow-hidden rounded-lg border bg-muted active:cursor-grabbing",
307
+ onSlide ? "border-primary/40" : "border-border hover:bg-muted/70",
308
+ drag.ghost && "opacity-40"
309
+ ),
310
+ style: { height: size.height },
311
+ ...drag.handlers,
312
+ title: onSlide ? "On this slide: drag to place it again" : "Drag onto the slide, or tap to add",
313
+ "data-testid": "tray-clip-handle",
314
+ children: [
315
+ /* @__PURE__ */ jsxRuntime.jsx(Fitted, { clip, box: { width: size.width - 8, height: size.height - 8 } }),
316
+ onSlide && /* @__PURE__ */ jsxRuntime.jsx(
317
+ "span",
318
+ {
319
+ className: "absolute left-1 top-1 flex size-4 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm",
320
+ "aria-label": "On this slide",
321
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "size-3" })
322
+ }
323
+ )
324
+ ]
325
+ }
326
+ ),
327
+ /* @__PURE__ */ jsxRuntime.jsx(
328
+ "button",
329
+ {
330
+ type: "button",
331
+ className: "absolute right-1 top-1 flex size-5 items-center justify-center rounded-md bg-background/90 text-muted-foreground opacity-0 shadow-sm backdrop-blur-sm transition-opacity hover:text-destructive focus-visible:opacity-100 group-hover:opacity-100 pointer-coarse:size-11 pointer-coarse:opacity-100",
332
+ title: "Delete clip",
333
+ "aria-label": `Delete ${clip.caption || "clip"}`,
334
+ onClick: () => removeClip(clip.id),
335
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-3" })
336
+ }
337
+ ),
338
+ /* @__PURE__ */ jsxRuntime.jsx(Label, { clip }),
339
+ drag.ghost && reactDom.createPortal(
340
+ /* @__PURE__ */ jsxRuntime.jsx(
341
+ "div",
342
+ {
343
+ className: "pointer-events-none fixed z-[100] flex -translate-x-1/2 -translate-y-1/2 items-center justify-center rounded-lg border border-border bg-card p-1 shadow-xl",
344
+ style: { left: drag.ghost.x, top: drag.ghost.y },
345
+ "data-testid": "tray-drag-ghost",
346
+ children: /* @__PURE__ */ jsxRuntime.jsx(Fitted, { clip, box: tileSize(clip.aspect, 96) })
347
+ }
348
+ ),
349
+ // The tray's own window (the app's, or the presentation pop-out's).
350
+ itemRef.current?.ownerDocument.body ?? document.body
351
+ )
352
+ ]
353
+ }
354
+ );
355
+ }
356
+ function ClipTray({ start }) {
357
+ const all = chunkXTR26TNE_cjs.usePresentation((s) => s.clips);
358
+ const clips = react.useMemo(() => all.filter((clip) => !clip.plain), [all]);
359
+ const slide = chunkXTR26TNE_cjs.usePresentation(chunkXTR26TNE_cjs.currentSlide);
360
+ const onSlide = new Set(slide?.items.map((item) => item.clipId));
361
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "shrink-0 border-t border-border bg-background", "data-testid": "clip-tray-bar", children: clips.length || start ? /* @__PURE__ */ jsxRuntime.jsxs(
362
+ "ul",
363
+ {
364
+ className: "flex items-start gap-2 overflow-x-auto overscroll-x-contain px-3 py-2 [scrollbar-width:none]",
365
+ "aria-label": "Clips",
366
+ "data-testid": "clip-tray",
367
+ children: [
368
+ start && /* @__PURE__ */ jsxRuntime.jsx("li", { className: "shrink-0", children: start }),
369
+ clips.map((clip) => /* @__PURE__ */ jsxRuntime.jsx(TrayTile, { clip, onSlide: onSlide.has(clip.id) }, clip.id))
370
+ ]
371
+ }
372
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(
373
+ "p",
374
+ {
375
+ className: "px-4 py-5 text-center text-xs text-muted-foreground",
376
+ "data-testid": "clip-tray-empty",
377
+ children: [
378
+ "Your clips appear here. In a document, choose ",
379
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Clip" }),
380
+ " and drag over a page, image or video."
381
+ ]
382
+ }
383
+ ) });
384
+ }
385
+ function Section({ title, children }) {
386
+ return /* @__PURE__ */ jsxRuntime.jsxs("section", { className: "flex flex-col gap-2", children: [
387
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-xs font-medium text-muted-foreground", children: title }),
388
+ children
389
+ ] });
390
+ }
391
+ function CustomColor({
392
+ value,
393
+ selected,
394
+ label,
395
+ round,
396
+ onChange
397
+ }) {
398
+ return /* @__PURE__ */ jsxRuntime.jsxs(
399
+ "label",
400
+ {
401
+ title: label,
402
+ className: ui.cn(
403
+ "relative flex size-8 cursor-pointer items-center justify-center border border-dashed border-border text-muted-foreground hover:text-foreground",
404
+ round ? "rounded-full" : "rounded-md",
405
+ selected && "border-solid ring-2 ring-primary ring-offset-1 ring-offset-background"
406
+ ),
407
+ style: selected ? { background: value } : void 0,
408
+ children: [
409
+ !selected && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-3.5" }),
410
+ /* @__PURE__ */ jsxRuntime.jsx(
411
+ "input",
412
+ {
413
+ type: "color",
414
+ value: chunkXTR26TNE_cjs.isHexColor(value) ? value : "#1f2937",
415
+ onChange: (event) => onChange(event.target.value),
416
+ "aria-label": label,
417
+ className: "absolute inset-0 cursor-pointer opacity-0"
418
+ }
419
+ )
420
+ ]
421
+ }
422
+ );
423
+ }
424
+ function DesignMenu({ onChooseLogo }) {
425
+ const theme = chunkXTR26TNE_cjs.usePresentation((s) => s.theme);
426
+ const setTheme = chunkXTR26TNE_cjs.usePresentation((s) => s.setTheme);
427
+ const customBackground = chunkXTR26TNE_cjs.isHexColor(theme.background);
428
+ const customAccent = !chunkXTR26TNE_cjs.ACCENTS.some((accent) => accent.color === theme.accent);
429
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Popover, { children: [
430
+ /* @__PURE__ */ jsxRuntime.jsx(ui.PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
431
+ ui.Button,
432
+ {
433
+ type: "button",
434
+ size: "icon",
435
+ variant: "ghost",
436
+ className: "size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11",
437
+ title: "Design: background, typeface, accent, logo",
438
+ "aria-label": "Design",
439
+ "data-testid": "presentation-design",
440
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Palette, { className: "size-4" })
441
+ }
442
+ ) }),
443
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.PopoverContent, { align: "end", className: "flex w-[21.5rem] flex-col gap-4 overflow-x-hidden p-4", children: [
444
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Background", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-7 gap-2", children: [
445
+ chunkXTR26TNE_cjs.BACKGROUNDS.map((preset) => /* @__PURE__ */ jsxRuntime.jsx(
446
+ "button",
447
+ {
448
+ type: "button",
449
+ title: preset.label,
450
+ "aria-label": `${preset.label} background`,
451
+ "aria-pressed": theme.background === preset.id,
452
+ onClick: () => setTheme({ background: preset.id }),
453
+ className: ui.cn(
454
+ "size-8 rounded-md border border-border shadow-sm",
455
+ theme.background === preset.id && "ring-2 ring-primary ring-offset-1 ring-offset-background"
456
+ ),
457
+ style: { background: chunkXTR26TNE_cjs.backgroundCss(preset.id), containerType: "inline-size" },
458
+ "data-testid": `design-background-${preset.id}`
459
+ },
460
+ preset.id
461
+ )),
462
+ /* @__PURE__ */ jsxRuntime.jsx(
463
+ CustomColor,
464
+ {
465
+ value: theme.background,
466
+ selected: customBackground,
467
+ label: "Your own background colour",
468
+ onChange: (background) => setTheme({ background })
469
+ }
470
+ )
471
+ ] }) }),
472
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Typeface", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-3 gap-2", children: Object.keys(chunkXTR26TNE_cjs.FONTS).map((font) => /* @__PURE__ */ jsxRuntime.jsxs(
473
+ "button",
474
+ {
475
+ type: "button",
476
+ "aria-pressed": theme.font === font,
477
+ onClick: () => setTheme({ font }),
478
+ className: ui.cn(
479
+ "flex flex-col items-center gap-0.5 rounded-md border border-border px-2 py-1.5 hover:bg-muted",
480
+ theme.font === font && "border-primary bg-primary/5"
481
+ ),
482
+ "data-testid": `design-font-${font}`,
483
+ children: [
484
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xl leading-tight", style: { fontFamily: chunkXTR26TNE_cjs.FONTS[font].css }, children: "Aa" }),
485
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[11px] text-muted-foreground", children: chunkXTR26TNE_cjs.FONTS[font].label })
486
+ ]
487
+ },
488
+ font
489
+ )) }) }),
490
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Accent", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
491
+ chunkXTR26TNE_cjs.ACCENTS.map((accent) => /* @__PURE__ */ jsxRuntime.jsx(
492
+ "button",
493
+ {
494
+ type: "button",
495
+ title: accent.label,
496
+ "aria-label": `${accent.label} accent`,
497
+ "aria-pressed": theme.accent === accent.color,
498
+ onClick: () => setTheme({ accent: accent.color }),
499
+ className: ui.cn(
500
+ "flex size-8 items-center justify-center rounded-full border border-black/10 shadow-sm",
501
+ theme.accent === accent.color && "ring-2 ring-primary ring-offset-1 ring-offset-background"
502
+ ),
503
+ style: { background: chunkXTR26TNE_cjs.accentColor(accent.color) },
504
+ children: theme.accent === accent.color && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "size-3.5 text-black/70" })
505
+ },
506
+ accent.color
507
+ )),
508
+ /* @__PURE__ */ jsxRuntime.jsx(
509
+ CustomColor,
510
+ {
511
+ value: chunkXTR26TNE_cjs.accentColor(theme.accent),
512
+ selected: customAccent,
513
+ label: "Your own accent colour",
514
+ round: true,
515
+ onChange: (accent) => setTheme({ accent })
516
+ }
517
+ )
518
+ ] }) }),
519
+ /* @__PURE__ */ jsxRuntime.jsx(Section, { title: "Logo", children: theme.logo ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
520
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-sm", title: theme.logo.fileName, children: theme.logo.fileName || "Logo" }),
521
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex rounded-md border border-border p-0.5", role: "group", children: ["bl", "br"].map((corner) => /* @__PURE__ */ jsxRuntime.jsx(
522
+ "button",
523
+ {
524
+ type: "button",
525
+ "aria-pressed": theme.logo?.corner === corner,
526
+ onClick: () => theme.logo && setTheme({ logo: { ...theme.logo, corner } }),
527
+ className: ui.cn(
528
+ "rounded px-2 py-0.5 text-xs text-muted-foreground",
529
+ theme.logo?.corner === corner && "bg-muted text-foreground"
530
+ ),
531
+ children: corner === "bl" ? "Left" : "Right"
532
+ },
533
+ corner
534
+ )) }),
535
+ /* @__PURE__ */ jsxRuntime.jsx(
536
+ ui.Button,
537
+ {
538
+ type: "button",
539
+ size: "icon",
540
+ variant: "ghost",
541
+ className: "size-7 text-muted-foreground",
542
+ title: "Remove the logo",
543
+ "aria-label": "Remove the logo",
544
+ onClick: () => setTheme({ logo: null }),
545
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-3.5" })
546
+ }
547
+ )
548
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(
549
+ ui.Button,
550
+ {
551
+ type: "button",
552
+ variant: "outline",
553
+ size: "sm",
554
+ className: "self-start",
555
+ disabled: !onChooseLogo,
556
+ onClick: onChooseLogo,
557
+ "data-testid": "design-choose-logo",
558
+ children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImagePlus, { className: "size-4" }),
560
+ "Choose an image\u2026"
561
+ ]
562
+ }
563
+ ) })
564
+ ] })
565
+ ] });
566
+ }
567
+ var at = (box2) => ({
568
+ left: `${box2.x * 100}%`,
569
+ top: `${box2.y * 100}%`,
570
+ width: `${box2.w * 100}%`,
571
+ height: `${box2.h * 100}%`
572
+ });
573
+ function LayoutThumb({ layout, className }) {
574
+ const geometry = chunkXTR26TNE_cjs.slideGeometry({ layout, caption: "Title" }, { labels: false, logo: null });
575
+ const { top, right = 0, bottom = 0, columns } = geometry.reserve;
576
+ const room = {
577
+ x: chunkXTR26TNE_cjs.STAGE_MARGIN,
578
+ y: chunkXTR26TNE_cjs.STAGE_MARGIN + top,
579
+ w: 1 - 2 * chunkXTR26TNE_cjs.STAGE_MARGIN - right,
580
+ h: 1 - 2 * chunkXTR26TNE_cjs.STAGE_MARGIN - top - bottom
581
+ };
582
+ const blocks = layout === "title" || layout === "section" || layout === "free" ? [] : columns === 2 ? [
583
+ { ...room, w: room.w / 2 - 0.02 },
584
+ { ...room, x: room.x + room.w / 2 + 0.02, w: room.w / 2 - 0.02 }
585
+ ] : [room];
586
+ const title = geometry.title;
587
+ return /* @__PURE__ */ jsxRuntime.jsxs(
588
+ "span",
589
+ {
590
+ "aria-hidden": true,
591
+ className: ui.cn(
592
+ "relative block aspect-video shrink-0 overflow-hidden rounded-[3px] bg-zinc-800",
593
+ className
594
+ ),
595
+ children: [
596
+ title && /* @__PURE__ */ jsxRuntime.jsx(
597
+ "span",
598
+ {
599
+ className: "absolute rounded-full bg-white/90",
600
+ style: at({
601
+ x: title.align === "center" ? title.x + title.w * 0.2 : title.x,
602
+ y: title.y + title.h * 0.3,
603
+ w: title.align === "center" ? title.w * 0.6 : title.w * 0.55,
604
+ h: Math.max(0.05, title.h * 0.35)
605
+ })
606
+ }
607
+ ),
608
+ geometry.text && [0, 1, 2].slice(0, geometry.text.bullets ? 3 : 1).map((line) => /* @__PURE__ */ jsxRuntime.jsx(
609
+ "span",
610
+ {
611
+ className: "absolute rounded-full bg-white/45",
612
+ style: at({
613
+ x: geometry.text.align === "center" ? geometry.text.x + geometry.text.w * 0.3 : geometry.text.x,
614
+ y: geometry.text.y + line * 0.1,
615
+ w: geometry.text.align === "center" ? geometry.text.w * 0.4 : geometry.text.w * 0.85,
616
+ h: 0.045
617
+ })
618
+ },
619
+ line
620
+ )),
621
+ blocks.map((block, index) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "absolute rounded-[1px] bg-white/25", style: at(block) }, index))
622
+ ]
623
+ }
624
+ );
625
+ }
626
+ function LayoutMenu({
627
+ index,
628
+ className,
629
+ compact = false
630
+ }) {
631
+ const layout = chunkXTR26TNE_cjs.usePresentation(
632
+ (s) => (index === void 0 ? chunkXTR26TNE_cjs.currentSlide(s) : s.slides[index])?.layout ?? "clips"
633
+ );
634
+ const setSlideLayout = chunkXTR26TNE_cjs.usePresentation((s) => s.setSlideLayout);
635
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
636
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsxs(
637
+ ui.Button,
638
+ {
639
+ type: "button",
640
+ variant: "ghost",
641
+ size: "sm",
642
+ className: ui.cn(
643
+ "h-7 gap-1.5 px-1.5 text-xs text-muted-foreground hover:text-foreground",
644
+ className
645
+ ),
646
+ title: "Slide layout",
647
+ "aria-label": `Slide layout: ${chunkXTR26TNE_cjs.LAYOUT_INFO[layout].label}`,
648
+ "data-testid": "presentation-layout",
649
+ children: [
650
+ /* @__PURE__ */ jsxRuntime.jsx(LayoutThumb, { layout, className: compact ? "w-6" : "w-7" }),
651
+ !compact && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "size-3" })
652
+ ]
653
+ }
654
+ ) }),
655
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuContent, { align: "start", className: "w-56", children: chunkXTR26TNE_cjs.SLIDE_LAYOUTS.map((option) => /* @__PURE__ */ jsxRuntime.jsxs(
656
+ ui.DropdownMenuItem,
657
+ {
658
+ onSelect: () => setSlideLayout(option, index),
659
+ className: ui.cn(option === layout && "bg-accent"),
660
+ "data-testid": `presentation-layout-${option}`,
661
+ children: [
662
+ /* @__PURE__ */ jsxRuntime.jsx(LayoutThumb, { layout: option, className: "w-12" }),
663
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: chunkXTR26TNE_cjs.LAYOUT_INFO[option].label })
664
+ ]
665
+ },
666
+ option
667
+ )) })
668
+ ] });
669
+ }
670
+ function Notice() {
671
+ const notice = chunkXTR26TNE_cjs.usePresentation((s) => s.notice);
672
+ react.useEffect(() => {
673
+ if (!notice) return;
674
+ const timer = setTimeout(
675
+ () => chunkXTR26TNE_cjs.usePresentation.getState().clearNotice(notice.id),
676
+ notice.kind === "error" ? 8e3 : 3e3
677
+ );
678
+ return () => clearTimeout(timer);
679
+ }, [notice]);
680
+ if (!notice) return null;
681
+ return /* @__PURE__ */ jsxRuntime.jsxs(
682
+ "div",
683
+ {
684
+ role: notice.kind === "error" ? "alert" : "status",
685
+ className: "pointer-events-auto fixed left-1/2 top-3 z-[100] flex max-w-[90vw] -translate-x-1/2 items-center gap-2 rounded-lg border border-border bg-popover px-3 py-2 text-sm shadow-lg",
686
+ "data-testid": "presentation-notice",
687
+ children: [
688
+ notice.kind === "error" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleAlert, { className: "size-4 shrink-0 text-destructive" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.CircleCheck, { className: "size-4 shrink-0 text-primary" }),
689
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: notice.text }),
690
+ /* @__PURE__ */ jsxRuntime.jsx(
691
+ "button",
692
+ {
693
+ type: "button",
694
+ className: "ml-1 rounded p-0.5 text-muted-foreground hover:text-foreground",
695
+ "aria-label": "Dismiss",
696
+ onClick: () => chunkXTR26TNE_cjs.usePresentation.getState().clearNotice(notice.id),
697
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.X, { className: "size-3.5" })
698
+ }
699
+ )
700
+ ]
701
+ }
702
+ );
703
+ }
704
+ var ACTION = "inline-flex size-7 items-center justify-center rounded text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring pointer-coarse:size-11";
705
+ var SIZES = [
706
+ { command: "fit", label: "Fit the slide", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Maximize2, { className: "size-4" }) },
707
+ { command: "fullWidth", label: "Full width", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoveHorizontal, { className: "size-4" }) },
708
+ { command: "fullHeight", label: "Full height", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoveVertical, { className: "size-4" }) }
709
+ ];
710
+ var ALIGNMENTS = [
711
+ { alignment: "left", label: "Left", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignStartVertical, { className: "size-4" }) },
712
+ { alignment: "centre", label: "Centre", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignCenterVertical, { className: "size-4" }) },
713
+ { alignment: "right", label: "Right", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignEndVertical, { className: "size-4" }) },
714
+ { alignment: "top", label: "Top", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignStartHorizontal, { className: "size-4" }) },
715
+ { alignment: "middle", label: "Middle", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignCenterHorizontal, { className: "size-4" }) },
716
+ { alignment: "bottom", label: "Bottom", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignEndHorizontal, { className: "size-4" }) }
717
+ ];
718
+ function Action({
719
+ label,
720
+ onClick,
721
+ children
722
+ }) {
723
+ return /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: ACTION, title: label, "aria-label": label, onClick, children });
724
+ }
725
+ function ClipActionBar({
726
+ clipId,
727
+ at: at2,
728
+ onRemoved,
729
+ onEditImage,
730
+ onCropImage
731
+ }) {
732
+ const actions = chunkXTR26TNE_cjs.usePresentation.getState();
733
+ return /* @__PURE__ */ jsxRuntime.jsxs(
734
+ "div",
735
+ {
736
+ role: "toolbar",
737
+ "aria-label": "Clip",
738
+ className: "absolute z-20 flex items-center gap-0.5 rounded-md border border-border bg-background p-0.5 shadow-md",
739
+ style: { left: `${at2.x * 100}%`, top: `calc(${at2.y * 100}% - 40px)` },
740
+ onPointerDown: (event) => event.stopPropagation(),
741
+ "data-testid": "clip-action-bar",
742
+ children: [
743
+ SIZES.map(({ command, label, icon }) => /* @__PURE__ */ jsxRuntime.jsx(Action, { label, onClick: () => actions.applyFrame(clipId, command), children: icon }, command)),
744
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
745
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx("button", { type: "button", className: ACTION, title: "Align", "aria-label": "Align", children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignCenterHorizontal, { className: "size-4" }) }) }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuContent, { align: "start", children: ALIGNMENTS.map(({ alignment, label, icon }) => /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuItem, { onSelect: () => actions.align(clipId, alignment), children: [
747
+ icon,
748
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: label })
749
+ ] }, alignment)) })
750
+ ] }),
751
+ onCropImage && /* @__PURE__ */ jsxRuntime.jsx(Action, { label: "Crop", onClick: onCropImage, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Crop, { className: "size-4" }) }),
752
+ onEditImage && /* @__PURE__ */ jsxRuntime.jsx(Action, { label: "Edit image", onClick: onEditImage, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-4" }) }),
753
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-5 w-px bg-border" }),
754
+ /* @__PURE__ */ jsxRuntime.jsx(Action, { label: "Bring to front", onClick: () => actions.bringToFront(clipId), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BringToFront, { className: "size-4" }) }),
755
+ /* @__PURE__ */ jsxRuntime.jsx(Action, { label: "Send to back", onClick: () => actions.sendToBack(clipId), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SendToBack, { className: "size-4" }) }),
756
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-5 w-px bg-border" }),
757
+ /* @__PURE__ */ jsxRuntime.jsx(
758
+ "button",
759
+ {
760
+ type: "button",
761
+ className: ui.cn(ACTION, "text-destructive hover:bg-destructive/10"),
762
+ title: "Remove from slide (Delete)",
763
+ "aria-label": "Remove from slide",
764
+ onClick: () => {
765
+ actions.unplace(clipId);
766
+ onRemoved();
767
+ },
768
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-4" })
769
+ }
770
+ )
771
+ ]
772
+ }
773
+ );
774
+ }
775
+ var boxStyle = (box2) => ({
776
+ left: `${box2.x * 100}%`,
777
+ top: `${box2.y * 100}%`,
778
+ width: `${box2.w * 100}%`,
779
+ height: `${box2.h * 100}%`
780
+ });
781
+ var textStyle = (box2, color) => ({
782
+ ...boxStyle(box2),
783
+ fontSize: `${box2.size * 100}cqw`,
784
+ fontWeight: box2.weight,
785
+ lineHeight: box2.leading,
786
+ color,
787
+ letterSpacing: box2.tone === "title" ? "-0.01em" : void 0
788
+ });
789
+ var PLACEHOLDER_COLOR = "rgba(255,255,255,0.32)";
790
+ function Text({
791
+ box: box2,
792
+ lines,
793
+ accent,
794
+ placeholder,
795
+ onEdit,
796
+ testId
797
+ }) {
798
+ const empty = lines.length === 0;
799
+ if (empty && !placeholder) return null;
800
+ return /* @__PURE__ */ jsxRuntime.jsx(
801
+ "div",
802
+ {
803
+ className: ui.cn(
804
+ "absolute flex flex-col overflow-hidden",
805
+ onEdit ? "pointer-events-auto cursor-text rounded-[0.3cqw] hover:outline hover:outline-1 hover:outline-white/25" : "pointer-events-none",
806
+ box2.align === "center" && "text-center",
807
+ box2.bullets && "gap-[0.6cqw]",
808
+ box2.anchor === "middle" && "justify-center",
809
+ box2.anchor === "bottom" && "justify-end"
810
+ ),
811
+ style: textStyle(box2, empty ? PLACEHOLDER_COLOR : chunkXTR26TNE_cjs.TEXT_COLORS[box2.tone]),
812
+ onPointerDown: onEdit ? (event) => event.stopPropagation() : void 0,
813
+ onClick: onEdit,
814
+ "data-testid": testId,
815
+ children: (empty ? [placeholder] : lines).map(
816
+ (line, index) => box2.bullets ? /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-[0.8em]", children: [
817
+ /* @__PURE__ */ jsxRuntime.jsx(
818
+ "span",
819
+ {
820
+ "aria-hidden": true,
821
+ className: "inline-block size-[0.42em] shrink-0 -translate-y-[0.12em]",
822
+ style: { background: empty ? "currentColor" : accent }
823
+ }
824
+ ),
825
+ /* @__PURE__ */ jsxRuntime.jsx("span", { children: line })
826
+ ] }, index) : /* @__PURE__ */ jsxRuntime.jsx("div", { children: line }, index)
827
+ )
828
+ }
829
+ );
830
+ }
831
+ function TextField({
832
+ box: box2,
833
+ value,
834
+ placeholder,
835
+ multiline,
836
+ onChange,
837
+ onDone,
838
+ testId
839
+ }) {
840
+ return /* @__PURE__ */ jsxRuntime.jsx(
841
+ "textarea",
842
+ {
843
+ autoFocus: true,
844
+ value,
845
+ placeholder,
846
+ onChange: (event) => onChange(multiline ? event.target.value : event.target.value.replace(/\n/g, " ")),
847
+ onBlur: onDone,
848
+ onPointerDown: (event) => event.stopPropagation(),
849
+ onKeyDown: (event) => {
850
+ if (event.key === "Escape" || !multiline && event.key === "Enter") {
851
+ event.preventDefault();
852
+ event.currentTarget.blur();
853
+ }
854
+ },
855
+ className: ui.cn(
856
+ "pointer-events-auto absolute resize-none overflow-hidden rounded-[0.3cqw] bg-white/5 p-0 outline outline-1 outline-white/40 placeholder:text-white/30 focus:outline-white/60",
857
+ box2.align === "center" && "text-center"
858
+ ),
859
+ style: {
860
+ ...textStyle(box2, chunkXTR26TNE_cjs.TEXT_COLORS[box2.tone]),
861
+ fontFamily: "inherit",
862
+ // Bulleted lines keep their indent while typed.
863
+ paddingLeft: box2.bullets ? "1.22em" : void 0
864
+ },
865
+ "aria-label": placeholder,
866
+ "data-testid": testId
867
+ }
868
+ );
869
+ }
870
+ function Logo({ image, box: box2 }) {
871
+ const url = chunkWOOOMTTA_cjs.useObjectUrl(image);
872
+ if (!url) return null;
873
+ return /* @__PURE__ */ jsxRuntime.jsx(
874
+ "img",
875
+ {
876
+ src: url,
877
+ alt: "",
878
+ className: "pointer-events-none absolute object-contain opacity-90",
879
+ style: { ...boxStyle(box2), objectPosition: box2.x < 0.5 ? "left bottom" : "right bottom" },
880
+ "data-testid": "stage-logo"
881
+ }
882
+ );
883
+ }
884
+ var SlideDecor = react.memo(function SlideDecor2({
885
+ slide,
886
+ theme,
887
+ labels,
888
+ logoImage,
889
+ editable,
890
+ editing,
891
+ dim
892
+ }) {
893
+ const [typing, setTyping] = react.useState(null);
894
+ const geometry = chunkXTR26TNE_cjs.slideGeometry(slide, { labels, logo: theme.logo?.corner ?? null });
895
+ const title = slide.caption.trim();
896
+ const titled = slide.layout === "title" || slide.layout === "section";
897
+ const info = chunkXTR26TNE_cjs.LAYOUT_INFO[slide.layout];
898
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("pointer-events-none absolute inset-0", dim && "opacity-30"), children: [
899
+ !geometry.title ? null : editing && typing === "title" ? /* @__PURE__ */ jsxRuntime.jsx(
900
+ TextField,
901
+ {
902
+ box: geometry.title,
903
+ value: slide.caption,
904
+ placeholder: "Title",
905
+ multiline: false,
906
+ onChange: editing.onTitle,
907
+ onDone: () => setTyping(null),
908
+ testId: "stage-slide-caption-input"
909
+ }
910
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
911
+ Text,
912
+ {
913
+ box: geometry.title,
914
+ lines: title ? [title] : [],
915
+ accent: chunkXTR26TNE_cjs.accentColor(theme.accent),
916
+ placeholder: editable && (titled || editing) ? "Click to add a title" : void 0,
917
+ onEdit: editing ? () => setTyping("title") : void 0,
918
+ testId: "stage-slide-caption"
919
+ }
920
+ ),
921
+ geometry.rule && (title || editable && titled) && /* @__PURE__ */ jsxRuntime.jsx(
922
+ "div",
923
+ {
924
+ className: "absolute",
925
+ style: { ...boxStyle(geometry.rule), background: chunkXTR26TNE_cjs.accentColor(theme.accent) }
926
+ }
927
+ ),
928
+ geometry.text && (editing && typing === "text" ? /* @__PURE__ */ jsxRuntime.jsx(
929
+ TextField,
930
+ {
931
+ box: geometry.text,
932
+ value: slide.points,
933
+ placeholder: info.text ?? "",
934
+ multiline: true,
935
+ onChange: editing.onPoints,
936
+ onDone: () => setTyping(null),
937
+ testId: "stage-slide-points-input"
938
+ }
939
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
940
+ Text,
941
+ {
942
+ box: geometry.text,
943
+ lines: chunkXTR26TNE_cjs.textLines(slide.points),
944
+ accent: chunkXTR26TNE_cjs.accentColor(theme.accent),
945
+ placeholder: editable ? info.text : void 0,
946
+ onEdit: editing ? () => setTyping("text") : void 0,
947
+ testId: "stage-slide-points"
948
+ }
949
+ )),
950
+ geometry.logo && logoImage && /* @__PURE__ */ jsxRuntime.jsx(Logo, { image: logoImage, box: geometry.logo })
951
+ ] });
952
+ });
953
+ var PAN_STEP = 0.1;
954
+ var MAX_ZOOM = 800;
955
+ var HOLD_DELAY = 350;
956
+ var HOLD_EVERY = 90;
957
+ var BUTTON = "pointer-events-auto flex size-7 items-center justify-center rounded-full border border-border bg-background/90 text-foreground shadow-sm backdrop-blur-sm hover:bg-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-30 pointer-coarse:size-11";
958
+ function HoldButton({
959
+ label,
960
+ disabled,
961
+ onStep,
962
+ className,
963
+ children
964
+ }) {
965
+ const timer = react.useRef(null);
966
+ const stop = () => {
967
+ if (timer.current) clearTimeout(timer.current);
968
+ timer.current = null;
969
+ };
970
+ react.useEffect(() => stop, []);
971
+ react.useEffect(() => {
972
+ if (disabled) stop();
973
+ }, [disabled]);
974
+ const start = (event) => {
975
+ event.stopPropagation();
976
+ if (disabled || event.button !== 0) return;
977
+ onStep();
978
+ const repeat = () => {
979
+ onStep();
980
+ timer.current = setTimeout(repeat, HOLD_EVERY);
981
+ };
982
+ timer.current = setTimeout(repeat, HOLD_DELAY);
983
+ };
984
+ return /* @__PURE__ */ jsxRuntime.jsx(
985
+ "button",
986
+ {
987
+ type: "button",
988
+ className: ui.cn(BUTTON, className),
989
+ title: label,
990
+ "aria-label": label,
991
+ disabled,
992
+ onPointerDown: start,
993
+ onPointerUp: stop,
994
+ onPointerLeave: stop,
995
+ onPointerCancel: stop,
996
+ onClick: (event) => event.detail === 0 && onStep(),
997
+ children
998
+ }
999
+ );
1000
+ }
1001
+ function ClipViewControls({ clip }) {
1002
+ const { panClip, setClipZoom } = chunkXTR26TNE_cjs.usePresentation.getState();
1003
+ const { rect } = clip;
1004
+ const zoomBase = clip.zoomBase ?? rect.w;
1005
+ const minZoom = Math.min(100, Math.floor(zoomBase / Math.min(1, rect.w / rect.h) * 100));
1006
+ const pan = (dx, dy) => () => panClip(clip.id, dx * rect.w, dy * rect.h);
1007
+ const zoom = Math.round(zoomBase / rect.w * 100);
1008
+ const edge = 1e-6;
1009
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1010
+ "div",
1011
+ {
1012
+ className: "pointer-events-none absolute inset-0 z-10",
1013
+ onPointerDown: (event) => event.stopPropagation(),
1014
+ "data-testid": "clip-view-controls",
1015
+ children: [
1016
+ /* @__PURE__ */ jsxRuntime.jsx(
1017
+ HoldButton,
1018
+ {
1019
+ label: "Show more above",
1020
+ disabled: rect.y <= edge,
1021
+ onStep: pan(0, -PAN_STEP),
1022
+ className: "absolute left-1/2 top-4 -translate-x-1/2 pointer-coarse:top-7",
1023
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronUp, { className: "size-4" })
1024
+ }
1025
+ ),
1026
+ /* @__PURE__ */ jsxRuntime.jsx(
1027
+ HoldButton,
1028
+ {
1029
+ label: "Show more below",
1030
+ disabled: rect.y + rect.h >= 1 - edge,
1031
+ onStep: pan(0, PAN_STEP),
1032
+ className: "absolute bottom-10 left-1/2 -translate-x-1/2 pointer-coarse:bottom-14",
1033
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "size-4" })
1034
+ }
1035
+ ),
1036
+ /* @__PURE__ */ jsxRuntime.jsx(
1037
+ HoldButton,
1038
+ {
1039
+ label: "Show more on the left",
1040
+ disabled: rect.x <= edge,
1041
+ onStep: pan(-PAN_STEP, 0),
1042
+ className: "absolute left-4 top-1/2 -translate-y-1/2 pointer-coarse:left-7",
1043
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronLeft, { className: "size-4" })
1044
+ }
1045
+ ),
1046
+ /* @__PURE__ */ jsxRuntime.jsx(
1047
+ HoldButton,
1048
+ {
1049
+ label: "Show more on the right",
1050
+ disabled: rect.x + rect.w >= 1 - edge,
1051
+ onStep: pan(PAN_STEP, 0),
1052
+ className: "absolute right-4 top-1/2 -translate-y-1/2 pointer-coarse:right-7",
1053
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronRight, { className: "size-4" })
1054
+ }
1055
+ ),
1056
+ /* @__PURE__ */ jsxRuntime.jsx(
1057
+ "div",
1058
+ {
1059
+ className: "pointer-events-auto absolute bottom-1.5 left-1/2 -translate-x-1/2",
1060
+ "data-testid": "clip-zoom-control",
1061
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1062
+ chunk5GCCUXMT_cjs.ZoomSlider,
1063
+ {
1064
+ value: zoom,
1065
+ min: minZoom,
1066
+ max: Math.max(MAX_ZOOM, zoom),
1067
+ zoomOutStep: 25,
1068
+ onChange: (value) => setClipZoom(clip.id, value),
1069
+ label: "Zoom",
1070
+ valueLabel: /* @__PURE__ */ jsxRuntime.jsxs("span", { "data-testid": "clip-zoom", children: [
1071
+ zoom,
1072
+ "%"
1073
+ ] }),
1074
+ className: "bg-background/95 shadow-sm backdrop-blur-sm pointer-coarse:h-11",
1075
+ sliderClassName: "w-24"
1076
+ }
1077
+ )
1078
+ }
1079
+ )
1080
+ ]
1081
+ }
1082
+ );
1083
+ }
1084
+ var isEdge = (kind) => kind === "n" || kind === "e" || kind === "s" || kind === "w";
1085
+ var EDGES = [
1086
+ { edge: "n", left: "50%", top: "0%", cursor: "ns-resize", label: "top" },
1087
+ { edge: "e", left: "100%", top: "50%", cursor: "ew-resize", label: "right" },
1088
+ { edge: "s", left: "50%", top: "100%", cursor: "ns-resize", label: "bottom" },
1089
+ { edge: "w", left: "0%", top: "50%", cursor: "ew-resize", label: "left" }
1090
+ ];
1091
+ var CORNERS = [
1092
+ { corner: "nw", left: "0%", top: "0%", cursor: "nwse-resize" },
1093
+ { corner: "ne", left: "100%", top: "0%", cursor: "nesw-resize" },
1094
+ { corner: "sw", left: "0%", top: "100%", cursor: "nesw-resize" },
1095
+ { corner: "se", left: "100%", top: "100%", cursor: "nwse-resize" }
1096
+ ];
1097
+ var EDGE_CSS = chunkXTR26TNE_cjs.clipEdgeCss();
1098
+ var StageItem = react.memo(function StageItem2({
1099
+ clip,
1100
+ frame,
1101
+ editable,
1102
+ selected,
1103
+ dim,
1104
+ label,
1105
+ host
1106
+ }) {
1107
+ const element = react.useRef(null);
1108
+ const gesture = react.useRef(null);
1109
+ const [dragging, setDragging] = react.useState(false);
1110
+ const [reshape, setReshape] = react.useState(null);
1111
+ const shown = reshape ? { ...clip, ...reshape.shape } : clip;
1112
+ const box2 = reshape?.frame ?? frame;
1113
+ const view = () => element.current?.ownerDocument.defaultView ?? window;
1114
+ const apply = () => {
1115
+ const g = gesture.current;
1116
+ const el = element.current;
1117
+ if (!g) return;
1118
+ g.request = null;
1119
+ const next = g.next;
1120
+ if (!next || !el) return;
1121
+ if (next.shape) {
1122
+ setReshape({ frame: next.frame, shape: next.shape });
1123
+ } else {
1124
+ const dx = (next.frame.x - g.frame.x) * g.stage.width;
1125
+ const dy = (next.frame.y - g.frame.y) * g.stage.height;
1126
+ const scale = next.frame.w / g.frame.w;
1127
+ el.style.transform = `translate3d(${dx}px, ${dy}px, 0) scale(${scale})`;
1128
+ }
1129
+ const key = next.guides.map((guide) => `${guide.axis}${guide.at}`).join();
1130
+ if (key !== g.guidesKey) {
1131
+ g.guidesKey = key;
1132
+ host.guides(next.guides);
1133
+ }
1134
+ };
1135
+ const start = (event, kind) => {
1136
+ if (!editable || event.button !== 0) return;
1137
+ const stage = host.stageBox();
1138
+ if (!stage) return;
1139
+ event.preventDefault();
1140
+ event.stopPropagation();
1141
+ host.select(clip.id);
1142
+ event.currentTarget.setPointerCapture(event.pointerId);
1143
+ gesture.current = {
1144
+ kind,
1145
+ pointer: { x: event.clientX, y: event.clientY },
1146
+ frame,
1147
+ shape: { rect: clip.rect, aspect: clip.aspect },
1148
+ stage,
1149
+ others: host.others(clip.id),
1150
+ next: null,
1151
+ request: null,
1152
+ guidesKey: ""
1153
+ };
1154
+ };
1155
+ const move = (event) => {
1156
+ const g = gesture.current;
1157
+ if (!g) return;
1158
+ if (!g.next) {
1159
+ setDragging(true);
1160
+ host.dragging(true);
1161
+ if (element.current) element.current.style.willChange = "transform";
1162
+ }
1163
+ const dx = (event.clientX - g.pointer.x) / g.stage.width;
1164
+ const dy = (event.clientY - g.pointer.y) / g.stage.height;
1165
+ const free = event.altKey;
1166
+ if (isEdge(g.kind)) {
1167
+ const result = chunkXTR26TNE_cjs.reshapeFrame(g.frame, g.shape, g.kind, dx, dy, g.others, free);
1168
+ g.next = {
1169
+ frame: result.frame,
1170
+ shape: { rect: result.rect, aspect: result.aspect },
1171
+ guides: result.guides
1172
+ };
1173
+ } else {
1174
+ g.next = g.kind === "move" ? chunkXTR26TNE_cjs.moveFrame(g.frame, g.shape.aspect, dx, dy, g.others, free) : chunkXTR26TNE_cjs.resizeFrame(g.frame, g.shape.aspect, g.kind, dx, dy, g.others, free);
1175
+ }
1176
+ g.request ??= view().requestAnimationFrame(apply);
1177
+ };
1178
+ const end = () => {
1179
+ const g = gesture.current;
1180
+ gesture.current = null;
1181
+ if (!g) return;
1182
+ if (g.request !== null) view().cancelAnimationFrame(g.request);
1183
+ const el = element.current;
1184
+ if (el) {
1185
+ el.style.transform = "";
1186
+ el.style.willChange = "";
1187
+ }
1188
+ if (!g.next) return;
1189
+ setReshape(null);
1190
+ setDragging(false);
1191
+ host.dragging(false);
1192
+ host.guides([]);
1193
+ host.commit(clip.id, g.next.frame, g.next.shape);
1194
+ };
1195
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1196
+ "div",
1197
+ {
1198
+ ref: element,
1199
+ className: ui.cn(
1200
+ "absolute origin-top-left",
1201
+ // Lifted off the background (a picture that is part of the slide's design is not).
1202
+ !clip.plain && "rounded-[0.6cqw] shadow-[0_0.5cqw_1.6cqw_rgba(0,0,0,0.5)]",
1203
+ editable && "cursor-move touch-none",
1204
+ selected && "outline outline-2 outline-offset-2 outline-primary",
1205
+ dim && "opacity-30"
1206
+ ),
1207
+ style: {
1208
+ left: `${box2.x * 100}%`,
1209
+ top: `${box2.y * 100}%`,
1210
+ width: `${box2.w * 100}%`,
1211
+ height: `${chunkXTR26TNE_cjs.frameHeight(box2, shown.aspect) * 100}%`
1212
+ },
1213
+ onPointerDown: (event) => start(event, "move"),
1214
+ onPointerMove: move,
1215
+ onPointerUp: end,
1216
+ onPointerCancel: end,
1217
+ "data-testid": `stage-item-${clip.id}`,
1218
+ children: [
1219
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: ui.cn("absolute inset-0 overflow-hidden", !clip.plain && "rounded-[0.6cqw]"), children: [
1220
+ /* @__PURE__ */ jsxRuntime.jsx(ClipImage, { clip: shown, live: !editable }),
1221
+ clip.kind === "pdf" && !clip.plain && /* @__PURE__ */ jsxRuntime.jsx(
1222
+ "div",
1223
+ {
1224
+ className: "pointer-events-none absolute inset-0",
1225
+ style: { background: EDGE_CSS },
1226
+ "data-testid": "stage-clip-edge"
1227
+ }
1228
+ ),
1229
+ !clip.plain && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-0 rounded-[0.6cqw] ring-1 ring-inset ring-white/15" })
1230
+ ] }),
1231
+ label && !clip.plain && // Under the clip, in the room the layout keeps (CAPTION_SPACE): never over it.
1232
+ /* @__PURE__ */ jsxRuntime.jsx(
1233
+ "div",
1234
+ {
1235
+ className: "pointer-events-none absolute left-0 top-full mt-[0.5cqw] max-w-full truncate text-[1.7cqw] leading-tight",
1236
+ style: { color: chunkXTR26TNE_cjs.TEXT_COLORS.muted },
1237
+ children: label
1238
+ }
1239
+ ),
1240
+ selected && !dragging && /* @__PURE__ */ jsxRuntime.jsx(ClipViewControls, { clip }),
1241
+ selected && CORNERS.map(({ corner, left, top, cursor }) => /* @__PURE__ */ jsxRuntime.jsx(
1242
+ "div",
1243
+ {
1244
+ className: "absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11",
1245
+ style: { left, top, cursor, touchAction: "none" },
1246
+ onPointerDown: (event) => start(event, corner),
1247
+ "aria-label": `Resize from the ${corner} corner`,
1248
+ "data-testid": `stage-item-resize-${corner}`,
1249
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none size-3 rounded-full border-2 border-primary bg-background" })
1250
+ },
1251
+ corner
1252
+ )),
1253
+ selected && EDGES.map(({ edge, left, top, cursor, label: side }) => /* @__PURE__ */ jsxRuntime.jsx(
1254
+ "div",
1255
+ {
1256
+ className: "absolute flex size-6 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11",
1257
+ style: { left, top, cursor, touchAction: "none" },
1258
+ onPointerDown: (event) => start(event, edge),
1259
+ "aria-label": `Show more or less at the ${side}`,
1260
+ title: "Drag to show more or less of the page",
1261
+ "data-testid": `stage-item-reshape-${edge}`,
1262
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1263
+ "span",
1264
+ {
1265
+ className: ui.cn(
1266
+ "pointer-events-none rounded-full border-2 border-primary bg-background",
1267
+ edge === "n" || edge === "s" ? "h-2 w-4" : "h-4 w-2"
1268
+ )
1269
+ }
1270
+ )
1271
+ },
1272
+ edge
1273
+ ))
1274
+ ]
1275
+ }
1276
+ );
1277
+ });
1278
+
1279
+ // src/presentation/box-style.ts
1280
+ var cqw = (points) => `${chunkXTR26TNE_cjs.pointsToStage(points) * 100}cqw`;
1281
+ function runStyle(run, font) {
1282
+ return {
1283
+ fontSize: cqw(run.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE),
1284
+ fontWeight: run.bold ? 700 : 400,
1285
+ fontStyle: run.italic ? "italic" : void 0,
1286
+ textDecoration: run.underline ? "underline" : void 0,
1287
+ color: run.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body,
1288
+ fontFamily: run.font ? `"${run.font}", ${font}` : font
1289
+ };
1290
+ }
1291
+ var HANDLES = [
1292
+ { handle: "nw", left: "0%", top: "0%", cursor: "nwse-resize" },
1293
+ { handle: "n", left: "50%", top: "0%", cursor: "ns-resize" },
1294
+ { handle: "ne", left: "100%", top: "0%", cursor: "nesw-resize" },
1295
+ { handle: "e", left: "100%", top: "50%", cursor: "ew-resize" },
1296
+ { handle: "se", left: "100%", top: "100%", cursor: "nwse-resize" },
1297
+ { handle: "s", left: "50%", top: "100%", cursor: "ns-resize" },
1298
+ { handle: "sw", left: "0%", top: "100%", cursor: "nesw-resize" },
1299
+ { handle: "w", left: "0%", top: "50%", cursor: "ew-resize" }
1300
+ ];
1301
+ var withAlpha = (hex3, alpha = 1) => {
1302
+ if (alpha >= 1) return hex3;
1303
+ const n = parseInt(hex3.slice(1), 16);
1304
+ return `rgba(${n >> 16 & 255}, ${n >> 8 & 255}, ${n & 255}, ${alpha})`;
1305
+ };
1306
+ function shapeStyle(box2) {
1307
+ const { frame } = box2;
1308
+ const line = box2.line;
1309
+ const shorter = Math.min(frame.w, frame.h * 1 / chunkXTR26TNE_cjs.STAGE_ASPECT);
1310
+ return {
1311
+ background: box2.fill && box2.shape !== "line" ? withAlpha(box2.fill, box2.fillAlpha) : void 0,
1312
+ border: line && box2.shape !== "line" ? `${cqw(line.width)} ${line.dash ? "dashed" : "solid"} ${line.color}` : void 0,
1313
+ borderRadius: box2.shape === "ellipse" ? "50%" : box2.shape === "roundRect" ? `${shorter * 16.67}cqw` : void 0
1314
+ };
1315
+ }
1316
+ function Paragraphs({
1317
+ paragraphs,
1318
+ font
1319
+ }) {
1320
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: paragraphs.map((paragraph, index) => {
1321
+ const first = paragraph.runs[0];
1322
+ const size = first?.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE;
1323
+ const indent = (paragraph.level ?? 0) * 0.5 * size;
1324
+ const empty = !paragraph.runs.some((run) => run.text);
1325
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1326
+ "div",
1327
+ {
1328
+ className: "whitespace-pre-wrap break-words",
1329
+ style: {
1330
+ textAlign: paragraph.align ?? "left",
1331
+ lineHeight: 1.2,
1332
+ paddingLeft: cqw(indent + (paragraph.bullet ? size * 0.9 : 0)),
1333
+ textIndent: paragraph.bullet ? cqw(-size * 0.9) : void 0,
1334
+ // An empty line keeps its height.
1335
+ fontSize: cqw(size)
1336
+ },
1337
+ children: [
1338
+ paragraph.bullet && /* @__PURE__ */ jsxRuntime.jsx(
1339
+ "span",
1340
+ {
1341
+ style: {
1342
+ ...runStyle(first ?? { }, font),
1343
+ display: "inline-block",
1344
+ width: cqw(size * 0.9),
1345
+ textIndent: 0
1346
+ },
1347
+ children: "\u2022"
1348
+ }
1349
+ ),
1350
+ empty ? "\u200B" : paragraph.runs.map((run, i) => /* @__PURE__ */ jsxRuntime.jsx("span", { style: runStyle(run, font), children: run.text }, i))
1351
+ ]
1352
+ },
1353
+ index
1354
+ );
1355
+ }) });
1356
+ }
1357
+ var StageBox = react.memo(function StageBox2({
1358
+ box: box2,
1359
+ font,
1360
+ editable,
1361
+ selected,
1362
+ host
1363
+ }) {
1364
+ const element = react.useRef(null);
1365
+ const gesture = react.useRef(null);
1366
+ const [typing, setTyping] = react.useState(false);
1367
+ const [dragging, setDragging] = react.useState(false);
1368
+ const { frame } = box2;
1369
+ const view = () => element.current?.ownerDocument.defaultView ?? window;
1370
+ const apply = () => {
1371
+ const g = gesture.current;
1372
+ const el = element.current;
1373
+ if (!g || !el) return;
1374
+ g.request = null;
1375
+ if (!g.next) return;
1376
+ if (g.handle === "move") {
1377
+ el.style.translate = `${(g.next.x - g.frame.x) * g.stage.width}px ${(g.next.y - g.frame.y) * g.stage.height}px`;
1378
+ } else {
1379
+ el.style.left = `${g.next.x * 100}%`;
1380
+ el.style.top = `${g.next.y * 100}%`;
1381
+ el.style.width = `${g.next.w * 100}%`;
1382
+ el.style.height = `${g.next.h * 100}%`;
1383
+ }
1384
+ };
1385
+ const start = (event, handle) => {
1386
+ if (!editable || typing || event.button !== 0) return;
1387
+ const stage = host.stageBox();
1388
+ if (!stage) return;
1389
+ event.preventDefault();
1390
+ event.stopPropagation();
1391
+ host.select(box2.id);
1392
+ event.currentTarget.setPointerCapture(event.pointerId);
1393
+ gesture.current = {
1394
+ handle,
1395
+ pointer: { x: event.clientX, y: event.clientY },
1396
+ frame,
1397
+ stage,
1398
+ next: null,
1399
+ request: null,
1400
+ wasSelected: selected
1401
+ };
1402
+ };
1403
+ const move = (event) => {
1404
+ const g = gesture.current;
1405
+ if (!g) return;
1406
+ const dx = (event.clientX - g.pointer.x) / g.stage.width;
1407
+ const dy = (event.clientY - g.pointer.y) / g.stage.height;
1408
+ if (!g.next) setDragging(true);
1409
+ g.next = g.handle === "move" ? chunkXTR26TNE_cjs.moveBox(g.frame, dx, dy) : chunkXTR26TNE_cjs.stretchBox(g.frame, g.handle, dx, dy);
1410
+ g.request ??= view().requestAnimationFrame(apply);
1411
+ };
1412
+ const end = () => {
1413
+ const g = gesture.current;
1414
+ gesture.current = null;
1415
+ if (!g) return;
1416
+ if (g.request !== null) view().cancelAnimationFrame(g.request);
1417
+ if (!g.next) {
1418
+ if (g.wasSelected && g.handle === "move" && hasText) setTyping(true);
1419
+ return;
1420
+ }
1421
+ const el = element.current;
1422
+ if (el) {
1423
+ el.style.translate = "";
1424
+ el.style.left = `${g.frame.x * 100}%`;
1425
+ el.style.top = `${g.frame.y * 100}%`;
1426
+ el.style.width = `${g.frame.w * 100}%`;
1427
+ el.style.height = `${g.frame.h * 100}%`;
1428
+ }
1429
+ setDragging(false);
1430
+ host.commit(box2.id, g.next);
1431
+ };
1432
+ const hasText = box2.shape !== "line";
1433
+ const inset = cqw(box2.inset ?? chunkXTR26TNE_cjs.DEFAULT_INSET);
1434
+ const run = chunkXTR26TNE_cjs.firstRun(box2);
1435
+ const transform = box2.rotation ? `rotate(${box2.rotation}deg)` : void 0;
1436
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1437
+ "div",
1438
+ {
1439
+ ref: element,
1440
+ className: ui.cn(
1441
+ "absolute",
1442
+ editable ? "pointer-events-auto" : "pointer-events-none",
1443
+ editable && !typing && "cursor-move touch-none",
1444
+ editable && !selected && "hover:outline hover:outline-1 hover:outline-white/30",
1445
+ selected && "outline outline-2 outline-offset-1 outline-primary"
1446
+ ),
1447
+ style: {
1448
+ left: `${frame.x * 100}%`,
1449
+ top: `${frame.y * 100}%`,
1450
+ width: `${frame.w * 100}%`,
1451
+ height: `${frame.h * 100}%`,
1452
+ transform,
1453
+ willChange: dragging ? "translate" : void 0
1454
+ },
1455
+ onPointerDown: (event) => start(event, "move"),
1456
+ onPointerMove: move,
1457
+ onPointerUp: end,
1458
+ onPointerCancel: end,
1459
+ onDoubleClick: () => editable && hasText && setTyping(true),
1460
+ onKeyDown: (event) => {
1461
+ if (selected && !typing && event.key === "Enter" && hasText) {
1462
+ event.preventDefault();
1463
+ setTyping(true);
1464
+ }
1465
+ },
1466
+ tabIndex: editable ? 0 : void 0,
1467
+ "data-testid": `stage-box-${box2.id}`,
1468
+ children: [
1469
+ box2.shape === "line" ? /* @__PURE__ */ jsxRuntime.jsx(
1470
+ "svg",
1471
+ {
1472
+ className: "absolute inset-0 size-full overflow-visible",
1473
+ preserveAspectRatio: "none",
1474
+ "aria-hidden": true,
1475
+ children: /* @__PURE__ */ jsxRuntime.jsx(
1476
+ "line",
1477
+ {
1478
+ x1: "0%",
1479
+ y1: box2.flipV ? "100%" : "0%",
1480
+ x2: "100%",
1481
+ y2: box2.flipV ? "0%" : "100%",
1482
+ stroke: box2.line?.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body,
1483
+ style: { strokeWidth: cqw(box2.line?.width ?? 1) },
1484
+ strokeDasharray: box2.line?.dash ? "6 4" : void 0
1485
+ }
1486
+ )
1487
+ }
1488
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1489
+ "div",
1490
+ {
1491
+ className: ui.cn(
1492
+ "absolute inset-0 box-border flex flex-col overflow-hidden",
1493
+ box2.anchor === "middle" && "justify-center",
1494
+ box2.anchor === "bottom" && "justify-end"
1495
+ ),
1496
+ style: { ...shapeStyle(box2), padding: inset },
1497
+ children: typing ? /* @__PURE__ */ jsxRuntime.jsx(
1498
+ "textarea",
1499
+ {
1500
+ autoFocus: true,
1501
+ defaultValue: chunkXTR26TNE_cjs.boxText(box2),
1502
+ className: "pointer-events-auto w-full flex-1 resize-none bg-transparent p-0 outline-none",
1503
+ style: {
1504
+ ...runStyle(run ?? { }, font),
1505
+ lineHeight: 1.2,
1506
+ textAlign: box2.paragraphs[0]?.align ?? "left"
1507
+ },
1508
+ onPointerDown: (event) => event.stopPropagation(),
1509
+ onBlur: (event) => {
1510
+ host.setText(box2.id, event.currentTarget.value);
1511
+ setTyping(false);
1512
+ },
1513
+ onKeyDown: (event) => {
1514
+ event.stopPropagation();
1515
+ if (event.key === "Escape") event.currentTarget.blur();
1516
+ },
1517
+ "aria-label": "Text",
1518
+ "data-testid": "stage-box-text-input"
1519
+ }
1520
+ ) : chunkXTR26TNE_cjs.boxText(box2) || !editable ? /* @__PURE__ */ jsxRuntime.jsx(Paragraphs, { paragraphs: box2.paragraphs, font }) : box2.shape === "text" && /* @__PURE__ */ jsxRuntime.jsx(
1521
+ "span",
1522
+ {
1523
+ className: "text-white/40",
1524
+ style: { fontSize: cqw(run?.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE) },
1525
+ children: "Double-click to type"
1526
+ }
1527
+ )
1528
+ }
1529
+ ),
1530
+ selected && !typing && !dragging && HANDLES.filter(
1531
+ ({ handle }) => box2.shape !== "line" || handle === "e" || handle === "w" || handle === "n" || handle === "s"
1532
+ ).map(({ handle, left, top, cursor }) => /* @__PURE__ */ jsxRuntime.jsx(
1533
+ "div",
1534
+ {
1535
+ className: "absolute flex size-5 -translate-x-1/2 -translate-y-1/2 items-center justify-center pointer-coarse:size-11",
1536
+ style: { left, top, cursor, touchAction: "none" },
1537
+ onPointerDown: (event) => start(event, handle),
1538
+ "aria-label": `Resize from ${handle}`,
1539
+ "data-testid": `stage-box-resize-${handle}`,
1540
+ children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "pointer-events-none size-2.5 rounded-full border-2 border-primary bg-background" })
1541
+ },
1542
+ handle
1543
+ ))
1544
+ ]
1545
+ }
1546
+ );
1547
+ });
1548
+ var SIZE_STEPS = [8, 10, 12, 14, 16, 18, 20, 24, 28, 32, 36, 44, 54, 66, 80, 96];
1549
+ var nextSize = (size, up) => up ? SIZE_STEPS.find((step) => step > size + 0.5) ?? size : [...SIZE_STEPS].reverse().find((step) => step < size - 0.5) ?? size;
1550
+ var ALIGN_NEXT = {
1551
+ left: "center",
1552
+ center: "right",
1553
+ right: "left",
1554
+ justify: "left"
1555
+ };
1556
+ var ALIGN_ICON = {
1557
+ left: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignLeft, { className: "size-4" }),
1558
+ center: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignCenter, { className: "size-4" }),
1559
+ right: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignRight, { className: "size-4" }),
1560
+ justify: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlignLeft, { className: "size-4" })
1561
+ };
1562
+ function ColorAction({
1563
+ label,
1564
+ value,
1565
+ onChange,
1566
+ children
1567
+ }) {
1568
+ return /* @__PURE__ */ jsxRuntime.jsxs("label", { className: ui.cn(ACTION, "relative cursor-pointer"), title: label, "aria-label": label, children: [
1569
+ children,
1570
+ /* @__PURE__ */ jsxRuntime.jsx(
1571
+ "span",
1572
+ {
1573
+ className: "pointer-events-none absolute inset-x-1.5 bottom-0.5 h-0.5 rounded-full",
1574
+ style: { background: value }
1575
+ }
1576
+ ),
1577
+ /* @__PURE__ */ jsxRuntime.jsx(
1578
+ "input",
1579
+ {
1580
+ type: "color",
1581
+ value,
1582
+ onChange: (event) => onChange(event.target.value),
1583
+ className: "absolute inset-0 size-full cursor-pointer opacity-0",
1584
+ "aria-label": label
1585
+ }
1586
+ )
1587
+ ] });
1588
+ }
1589
+ function BoxActionBar({ box: box2, onRemoved }) {
1590
+ const actions = chunkXTR26TNE_cjs.usePresentation.getState();
1591
+ const run = chunkXTR26TNE_cjs.firstRun(box2);
1592
+ const size = run?.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE;
1593
+ const align = box2.paragraphs[0]?.align ?? "left";
1594
+ const update = (change) => actions.updateBox(box2.id, change);
1595
+ const text = box2.shape !== "line";
1596
+ const top = Math.max(0, box2.frame.y);
1597
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1598
+ "div",
1599
+ {
1600
+ role: "toolbar",
1601
+ "aria-label": "Text box",
1602
+ className: "absolute z-20 flex items-center gap-0.5 rounded-md border border-border bg-background p-0.5 shadow-md",
1603
+ style: {
1604
+ left: `${Math.min(Math.max(0, box2.frame.x), 0.6) * 100}%`,
1605
+ top: `calc(${top * 100}% - 40px)`
1606
+ },
1607
+ onPointerDown: (event) => event.stopPropagation(),
1608
+ "data-testid": "box-action-bar",
1609
+ children: [
1610
+ text && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1611
+ /* @__PURE__ */ jsxRuntime.jsx(
1612
+ Action,
1613
+ {
1614
+ label: "Smaller",
1615
+ onClick: () => update((b) => chunkXTR26TNE_cjs.styleRuns(b, (r) => ({ ...r, size: nextSize(r.size ?? size, false) }))),
1616
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AArrowDown, { className: "size-4" })
1617
+ }
1618
+ ),
1619
+ /* @__PURE__ */ jsxRuntime.jsx(
1620
+ "span",
1621
+ {
1622
+ className: "w-7 text-center text-xs tabular-nums text-muted-foreground",
1623
+ "data-testid": "box-text-size",
1624
+ children: Math.round(size)
1625
+ }
1626
+ ),
1627
+ /* @__PURE__ */ jsxRuntime.jsx(
1628
+ Action,
1629
+ {
1630
+ label: "Larger",
1631
+ onClick: () => update((b) => chunkXTR26TNE_cjs.styleRuns(b, (r) => ({ ...r, size: nextSize(r.size ?? size, true) }))),
1632
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AArrowUp, { className: "size-4" })
1633
+ }
1634
+ ),
1635
+ /* @__PURE__ */ jsxRuntime.jsx(
1636
+ Action,
1637
+ {
1638
+ label: "Bold",
1639
+ onClick: () => update((b) => chunkXTR26TNE_cjs.styleRuns(b, (r) => ({ ...r, bold: !run?.bold }))),
1640
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Bold, { className: ui.cn("size-4", run?.bold && "text-primary") })
1641
+ }
1642
+ ),
1643
+ /* @__PURE__ */ jsxRuntime.jsx(
1644
+ Action,
1645
+ {
1646
+ label: "Italic",
1647
+ onClick: () => update((b) => chunkXTR26TNE_cjs.styleRuns(b, (r) => ({ ...r, italic: !run?.italic }))),
1648
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Italic, { className: ui.cn("size-4", run?.italic && "text-primary") })
1649
+ }
1650
+ ),
1651
+ /* @__PURE__ */ jsxRuntime.jsx(
1652
+ Action,
1653
+ {
1654
+ label: "Align",
1655
+ onClick: () => update((b) => ({
1656
+ ...b,
1657
+ paragraphs: b.paragraphs.map((p) => ({ ...p, align: ALIGN_NEXT[align] }))
1658
+ })),
1659
+ children: ALIGN_ICON[align]
1660
+ }
1661
+ ),
1662
+ /* @__PURE__ */ jsxRuntime.jsx(
1663
+ ColorAction,
1664
+ {
1665
+ label: "Text colour",
1666
+ value: run?.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body,
1667
+ onChange: (color) => update((b) => chunkXTR26TNE_cjs.styleRuns(b, (r) => ({ ...r, color }))),
1668
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Baseline, { className: "size-4" })
1669
+ }
1670
+ )
1671
+ ] }),
1672
+ /* @__PURE__ */ jsxRuntime.jsx(
1673
+ ColorAction,
1674
+ {
1675
+ label: box2.shape === "line" ? "Line colour" : "Fill",
1676
+ value: (box2.shape === "line" ? box2.line?.color : box2.fill) ?? "#ffffff",
1677
+ onChange: (color) => update(
1678
+ (b) => b.shape === "line" ? { ...b, line: { width: b.line?.width ?? 2, ...b.line, color } } : { ...b, fill: color, shape: b.shape === "text" ? "rect" : b.shape }
1679
+ ),
1680
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PaintBucket, { className: "size-4" })
1681
+ }
1682
+ ),
1683
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-5 w-px bg-border" }),
1684
+ /* @__PURE__ */ jsxRuntime.jsx(Action, { label: "In front of the clips", onClick: () => actions.restackBox(box2.id, "front"), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.BringToFront, { className: "size-4" }) }),
1685
+ /* @__PURE__ */ jsxRuntime.jsx(Action, { label: "Behind the clips", onClick: () => actions.restackBox(box2.id, "back"), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.SendToBack, { className: "size-4" }) }),
1686
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-0.5 h-5 w-px bg-border" }),
1687
+ /* @__PURE__ */ jsxRuntime.jsx(
1688
+ "button",
1689
+ {
1690
+ type: "button",
1691
+ className: ui.cn(ACTION, "text-destructive hover:bg-destructive/10"),
1692
+ title: "Delete (Delete)",
1693
+ "aria-label": "Delete",
1694
+ onClick: () => {
1695
+ actions.removeBox(box2.id);
1696
+ onRemoved();
1697
+ },
1698
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-4" })
1699
+ }
1700
+ )
1701
+ ]
1702
+ }
1703
+ );
1704
+ }
1705
+ var GuideLayer = react.memo(
1706
+ react.forwardRef(function GuideLayer2(_, ref) {
1707
+ const [guides, setGuides] = react.useState([]);
1708
+ react.useImperativeHandle(ref, () => ({ set: setGuides }), []);
1709
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: guides.map((guide) => /* @__PURE__ */ jsxRuntime.jsx(
1710
+ "div",
1711
+ {
1712
+ className: "pointer-events-none absolute z-10 bg-primary",
1713
+ style: guide.axis === "x" ? { left: `${guide.at * 100}%`, top: 0, bottom: 0, width: 1 } : { top: `${guide.at * 100}%`, left: 0, right: 0, height: 1 },
1714
+ "data-testid": `stage-guide-${guide.axis}`
1715
+ },
1716
+ `${guide.axis}-${guide.at}`
1717
+ )) });
1718
+ })
1719
+ );
1720
+ function PresentationStage({
1721
+ editable = false,
1722
+ slideIndex,
1723
+ selected = null,
1724
+ onSelect,
1725
+ onEditImage,
1726
+ onCropImage,
1727
+ textEditing,
1728
+ overlay,
1729
+ className
1730
+ }) {
1731
+ const index = chunkXTR26TNE_cjs.usePresentation((s) => slideIndex ?? s.current);
1732
+ const slide = chunkXTR26TNE_cjs.usePresentation((s) => s.slides[index]);
1733
+ const clips = chunkXTR26TNE_cjs.usePresentation((s) => s.clips);
1734
+ const blank = chunkXTR26TNE_cjs.usePresentation((s) => s.blank && !editable);
1735
+ const captions = chunkXTR26TNE_cjs.usePresentation((s) => s.captions);
1736
+ const columnValues = chunkXTR26TNE_cjs.usePresentation((s) => s.columnValues);
1737
+ const theme = chunkXTR26TNE_cjs.usePresentation((s) => s.theme);
1738
+ const logoImage = chunkXTR26TNE_cjs.usePresentation((s) => s.logoImage);
1739
+ const media = chunkXTR26TNE_cjs.usePresentation((s) => s.media);
1740
+ const stageRef = react.useRef(null);
1741
+ const guideLayer = react.useRef(null);
1742
+ const dropping = chunkXTR26TNE_cjs.usePresentation((s) => editable && s.dragging !== null);
1743
+ const [dragging, setDragging] = react.useState(false);
1744
+ const latest = react.useRef({ slide, clips, index, onSelect });
1745
+ latest.current = { slide, clips, index, onSelect };
1746
+ const host = react.useMemo(
1747
+ () => ({
1748
+ stageBox: () => stageRef.current?.getBoundingClientRect() ?? null,
1749
+ others: (clipId) => {
1750
+ const { slide: current2, clips: all } = latest.current;
1751
+ return (current2?.items ?? []).filter((item) => item.clipId !== clipId).map((item) => ({
1752
+ frame: item.frame,
1753
+ aspect: all.find((clip) => clip.id === item.clipId)?.aspect ?? 1
1754
+ }));
1755
+ },
1756
+ select: (clipId) => {
1757
+ chunkXTR26TNE_cjs.usePresentation.getState().goTo(latest.current.index);
1758
+ latest.current.onSelect?.(clipId);
1759
+ },
1760
+ guides: (guides) => guideLayer.current?.set(guides),
1761
+ dragging: setDragging,
1762
+ commit: (clipId, frame, shape) => {
1763
+ const state = chunkXTR26TNE_cjs.usePresentation.getState();
1764
+ state.goTo(latest.current.index);
1765
+ if (shape) state.reshapeClip(clipId, frame, shape.rect, shape.aspect);
1766
+ else state.setFrame(clipId, frame);
1767
+ }
1768
+ }),
1769
+ []
1770
+ );
1771
+ const boxHost = react.useMemo(
1772
+ () => ({
1773
+ stageBox: () => stageRef.current?.getBoundingClientRect() ?? null,
1774
+ select: (boxId) => {
1775
+ chunkXTR26TNE_cjs.usePresentation.getState().goTo(latest.current.index);
1776
+ latest.current.onSelect?.(boxId);
1777
+ },
1778
+ commit: (boxId, frame) => chunkXTR26TNE_cjs.usePresentation.getState().updateBox(boxId, (box2) => ({ ...box2, frame })),
1779
+ setText: (boxId, text) => chunkXTR26TNE_cjs.usePresentation.getState().setBoxText(boxId, text)
1780
+ }),
1781
+ []
1782
+ );
1783
+ const font = chunkXTR26TNE_cjs.FONTS[theme.font].css;
1784
+ const boxes = slide?.boxes ?? [];
1785
+ const selectedBox = selected ? boxes.find((box2) => box2.id === selected) : void 0;
1786
+ const renderBoxes = (front) => boxes.filter((box2) => !!box2.front === front).map((box2) => /* @__PURE__ */ jsxRuntime.jsx(
1787
+ StageBox,
1788
+ {
1789
+ box: box2,
1790
+ font,
1791
+ editable,
1792
+ selected: editable && selected === box2.id,
1793
+ host: boxHost
1794
+ },
1795
+ box2.id
1796
+ ));
1797
+ const clipOf = (id) => clips.find((clip) => clip.id === id);
1798
+ const selectedFrame = selected ? slide?.items.find((item) => item.clipId === selected)?.frame : void 0;
1799
+ react.useEffect(() => {
1800
+ const stage = stageRef.current;
1801
+ const view = stage?.ownerDocument.defaultView;
1802
+ if (!editable || !selected || !stage || !view) return;
1803
+ let sum = { x: 0, y: 0 };
1804
+ let request = null;
1805
+ const onWheel = (event) => {
1806
+ const item = event.target?.closest?.(
1807
+ `[data-testid="stage-item-${selected}"]`
1808
+ );
1809
+ const clip = chunkXTR26TNE_cjs.usePresentation.getState().clips.find((entry) => entry.id === selected);
1810
+ if (!item || !clip) return;
1811
+ event.preventDefault();
1812
+ const box2 = item.getBoundingClientRect();
1813
+ const horizontal = event.shiftKey && !event.deltaX;
1814
+ const deltaX = horizontal ? event.deltaY : event.deltaX;
1815
+ const deltaY = horizontal ? 0 : event.deltaY;
1816
+ sum = {
1817
+ x: sum.x + deltaX / box2.width * clip.rect.w,
1818
+ y: sum.y + deltaY / box2.height * clip.rect.h
1819
+ };
1820
+ request ??= view.requestAnimationFrame(() => {
1821
+ request = null;
1822
+ chunkXTR26TNE_cjs.usePresentation.getState().panClip(selected, sum.x, sum.y);
1823
+ sum = { x: 0, y: 0 };
1824
+ });
1825
+ };
1826
+ stage.addEventListener("wheel", onWheel, { passive: false });
1827
+ return () => {
1828
+ stage.removeEventListener("wheel", onWheel);
1829
+ if (request !== null) view.cancelAnimationFrame(request);
1830
+ };
1831
+ }, [editable, selected]);
1832
+ return /* @__PURE__ */ jsxRuntime.jsx(
1833
+ "div",
1834
+ {
1835
+ className: ui.cn("relative h-full w-full", !editable && "overflow-hidden bg-black", className),
1836
+ style: { containerType: "size" },
1837
+ "data-testid": editable ? "presentation-stage-editor" : "presentation-stage",
1838
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
1839
+ "div",
1840
+ {
1841
+ ref: stageRef,
1842
+ className: ui.cn(
1843
+ "absolute inset-0 m-auto",
1844
+ editable ? "shadow-md" : "overflow-hidden",
1845
+ dropping && "outline outline-2 outline-offset-2 outline-primary"
1846
+ ),
1847
+ style: {
1848
+ width: "min(100cqw, calc(100cqh * 16 / 9))",
1849
+ height: "min(100cqh, calc(100cqw * 9 / 16))",
1850
+ containerType: "inline-size",
1851
+ background: slide?.background ? chunkXTR26TNE_cjs.slideBackgroundCss(slide.background, media) : chunkXTR26TNE_cjs.backgroundCss(theme.background),
1852
+ fontFamily: chunkXTR26TNE_cjs.FONTS[theme.font].css
1853
+ },
1854
+ onPointerDown: () => {
1855
+ if (!editable) return;
1856
+ chunkXTR26TNE_cjs.usePresentation.getState().goTo(index);
1857
+ onSelect?.(null);
1858
+ },
1859
+ ...editable ? { [DROP_ATTRIBUTE]: "stage", "data-slide-index": index } : {},
1860
+ "aria-label": `Slide ${index + 1}`,
1861
+ children: [
1862
+ blank && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 z-20 bg-black" }),
1863
+ slide && /* @__PURE__ */ jsxRuntime.jsx(
1864
+ SlideDecor,
1865
+ {
1866
+ slide,
1867
+ theme,
1868
+ labels: captions,
1869
+ logoImage,
1870
+ editable,
1871
+ editing: textEditing
1872
+ }
1873
+ ),
1874
+ renderBoxes(false),
1875
+ slide?.items.map((item) => {
1876
+ const clip = clipOf(item.clipId);
1877
+ if (!clip) return null;
1878
+ return /* @__PURE__ */ jsxRuntime.jsx(
1879
+ StageItem,
1880
+ {
1881
+ clip,
1882
+ frame: item.frame,
1883
+ editable,
1884
+ selected: editable && selected === clip.id,
1885
+ dim: false,
1886
+ label: captions ? chunkXTR26TNE_cjs.clipLabel(clip, columnValues) : "",
1887
+ host
1888
+ },
1889
+ clip.id
1890
+ );
1891
+ }),
1892
+ renderBoxes(true),
1893
+ editable && /* @__PURE__ */ jsxRuntime.jsx(GuideLayer, { ref: guideLayer }),
1894
+ editable && selectedFrame && selected && !dragging && /* @__PURE__ */ jsxRuntime.jsx(
1895
+ ClipActionBar,
1896
+ {
1897
+ clipId: selected,
1898
+ at: selectedFrame,
1899
+ onRemoved: () => onSelect?.(null),
1900
+ onEditImage: onEditImage && clipOf(selected)?.kind === "image" ? () => onEditImage(selected) : void 0,
1901
+ onCropImage: onCropImage && clipOf(selected)?.kind === "image" ? () => onCropImage(selected) : void 0
1902
+ }
1903
+ ),
1904
+ editable && selectedBox && /* @__PURE__ */ jsxRuntime.jsx(BoxActionBar, { box: selectedBox, onRemoved: () => onSelect?.(null) }),
1905
+ overlay
1906
+ ]
1907
+ }
1908
+ )
1909
+ }
1910
+ );
1911
+ }
1912
+ function SlideAction({
1913
+ label,
1914
+ onClick,
1915
+ disabled,
1916
+ children
1917
+ }) {
1918
+ return /* @__PURE__ */ jsxRuntime.jsx(
1919
+ ui.Button,
1920
+ {
1921
+ type: "button",
1922
+ size: "icon",
1923
+ variant: "ghost",
1924
+ className: "size-7 text-muted-foreground hover:text-foreground pointer-coarse:size-10",
1925
+ title: label,
1926
+ "aria-label": label,
1927
+ disabled,
1928
+ onClick,
1929
+ children
1930
+ }
1931
+ );
1932
+ }
1933
+ var SlideCard = react.memo(function SlideCard2({
1934
+ index,
1935
+ count,
1936
+ current: current2,
1937
+ selected,
1938
+ onSelect,
1939
+ onEditImage,
1940
+ onCropImage
1941
+ }) {
1942
+ const empty = chunkXTR26TNE_cjs.usePresentation((s) => {
1943
+ const slide = s.slides[index];
1944
+ return !!slide && !slide.items.length && !slide.boxes.length && slide.layout !== "title" && slide.layout !== "section";
1945
+ });
1946
+ const notes = chunkXTR26TNE_cjs.usePresentation((s) => s.slides[index]?.notes ?? "");
1947
+ const textEditing = react.useMemo(
1948
+ () => ({
1949
+ onTitle: (title) => chunkXTR26TNE_cjs.usePresentation.getState().setSlideCaption(title, index),
1950
+ onPoints: (points) => chunkXTR26TNE_cjs.usePresentation.getState().setSlidePoints(points, index)
1951
+ }),
1952
+ [index]
1953
+ );
1954
+ const select = react.useCallback((clipId) => onSelect(index, clipId), [onSelect, index]);
1955
+ const actions = chunkXTR26TNE_cjs.usePresentation.getState();
1956
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "group relative flex gap-2", "data-testid": `slide-${index}`, children: [
1957
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex w-9 shrink-0 flex-col items-center gap-0.5", children: [
1958
+ /* @__PURE__ */ jsxRuntime.jsx(
1959
+ "span",
1960
+ {
1961
+ className: ui.cn(
1962
+ "py-1 text-xs tabular-nums",
1963
+ current2 ? "font-semibold text-foreground" : "text-muted-foreground"
1964
+ ),
1965
+ children: index + 1
1966
+ }
1967
+ ),
1968
+ /* @__PURE__ */ jsxRuntime.jsxs(
1969
+ "div",
1970
+ {
1971
+ className: ui.cn(
1972
+ "flex flex-col items-center gap-0.5 transition-opacity",
1973
+ "opacity-0 focus-within:opacity-100 group-hover:opacity-100",
1974
+ current2 && "pointer-coarse:opacity-100"
1975
+ ),
1976
+ "data-testid": `slide-actions-${index}`,
1977
+ children: [
1978
+ /* @__PURE__ */ jsxRuntime.jsx(LayoutMenu, { index, compact: true }),
1979
+ /* @__PURE__ */ jsxRuntime.jsx(
1980
+ SlideAction,
1981
+ {
1982
+ label: "Move up",
1983
+ disabled: index === 0,
1984
+ onClick: () => actions.moveSlide(index, index - 1),
1985
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowUp, { className: "size-3.5" })
1986
+ }
1987
+ ),
1988
+ /* @__PURE__ */ jsxRuntime.jsx(
1989
+ SlideAction,
1990
+ {
1991
+ label: "Move down",
1992
+ disabled: index === count - 1,
1993
+ onClick: () => actions.moveSlide(index, index + 1),
1994
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowDown, { className: "size-3.5" })
1995
+ }
1996
+ ),
1997
+ /* @__PURE__ */ jsxRuntime.jsx(SlideAction, { label: "Duplicate", onClick: () => actions.duplicateSlide(index), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Copy, { className: "size-3.5" }) }),
1998
+ /* @__PURE__ */ jsxRuntime.jsx(SlideAction, { label: "Delete slide", onClick: () => actions.removeSlide(index), children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "size-3.5" }) })
1999
+ ]
2000
+ }
2001
+ )
2002
+ ] }),
2003
+ /* @__PURE__ */ jsxRuntime.jsxs(
2004
+ "div",
2005
+ {
2006
+ className: ui.cn(
2007
+ "relative aspect-video min-w-0 flex-1 rounded-sm",
2008
+ current2 && "ring-1 ring-primary/40 ring-offset-4 ring-offset-transparent"
2009
+ ),
2010
+ children: [
2011
+ /* @__PURE__ */ jsxRuntime.jsx(
2012
+ PresentationStage,
2013
+ {
2014
+ editable: true,
2015
+ slideIndex: index,
2016
+ selected,
2017
+ onSelect: select,
2018
+ onEditImage,
2019
+ onCropImage,
2020
+ textEditing,
2021
+ overlay: empty && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "pointer-events-none absolute inset-x-0 bottom-[8%] flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "rounded-full bg-black/40 px-3 py-1 text-[1.4cqw] text-white/70", children: "Drag a clip here from the tray" }) })
2022
+ }
2023
+ ),
2024
+ (notes || current2) && /* @__PURE__ */ jsxRuntime.jsx(
2025
+ "textarea",
2026
+ {
2027
+ value: notes,
2028
+ rows: notes.split("\n").length,
2029
+ placeholder: "Speaker notes",
2030
+ onChange: (event) => chunkXTR26TNE_cjs.usePresentation.getState().setSlideNotes(event.target.value, index),
2031
+ className: "mt-2 block w-full resize-none rounded-sm border-0 bg-transparent px-1 py-0.5 text-xs text-muted-foreground placeholder:text-muted-foreground/50 focus:bg-background focus:text-foreground focus:outline-none focus:ring-1 focus:ring-ring",
2032
+ "aria-label": `Speaker notes for slide ${index + 1}`,
2033
+ "data-testid": `slide-notes-${index}`
2034
+ }
2035
+ )
2036
+ ]
2037
+ }
2038
+ )
2039
+ ] });
2040
+ });
2041
+ var INSERTS = [
2042
+ { shape: "text", label: "Text box", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Type, { className: "size-4" }) },
2043
+ { shape: "rect", label: "Rectangle", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Square, { className: "size-4" }) },
2044
+ {
2045
+ shape: "roundRect",
2046
+ label: "Rounded rectangle",
2047
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.RectangleHorizontal, { className: "size-4" })
2048
+ },
2049
+ { shape: "ellipse", label: "Ellipse", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Circle, { className: "size-4" }) },
2050
+ { shape: "line", label: "Line", icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Minus, { className: "size-4" }) }
2051
+ ];
2052
+ function InsertMenu({ onInserted }) {
2053
+ const insert = (shape) => {
2054
+ const state = chunkXTR26TNE_cjs.usePresentation.getState();
2055
+ const slide = state.slides[state.current];
2056
+ const box2 = chunkXTR26TNE_cjs.newBox(shape, chunkXTR26TNE_cjs.accentColor(state.theme.accent));
2057
+ const dark = chunkXTR26TNE_cjs.isLightBackground(slide?.background);
2058
+ const { id, ...rest } = dark ? {
2059
+ ...box2,
2060
+ paragraphs: box2.paragraphs.map((p) => ({
2061
+ ...p,
2062
+ runs: p.runs.map((r) => ({ color: "#1f2328", ...r }))
2063
+ }))
2064
+ } : box2;
2065
+ onInserted(state.current, state.addBox(rest));
2066
+ };
2067
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2068
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2069
+ ui.Button,
2070
+ {
2071
+ type: "button",
2072
+ size: "icon",
2073
+ variant: "ghost",
2074
+ className: "size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11",
2075
+ title: "Insert a text box or shape",
2076
+ "aria-label": "Insert",
2077
+ "data-testid": "slides-insert",
2078
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Shapes, { className: "size-4" })
2079
+ }
2080
+ ) }),
2081
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuContent, { align: "end", children: INSERTS.map(({ shape, label, icon }) => /* @__PURE__ */ jsxRuntime.jsxs(
2082
+ ui.DropdownMenuItem,
2083
+ {
2084
+ onSelect: () => insert(shape),
2085
+ "data-testid": `slides-insert-${shape}`,
2086
+ children: [
2087
+ icon,
2088
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2", children: label })
2089
+ ]
2090
+ },
2091
+ shape
2092
+ )) })
2093
+ ] });
2094
+ }
2095
+ function InsertSlide({ after, always }) {
2096
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "group/insert relative flex h-8 items-center justify-center pl-11", children: /* @__PURE__ */ jsxRuntime.jsxs(
2097
+ "button",
2098
+ {
2099
+ type: "button",
2100
+ onClick: () => chunkXTR26TNE_cjs.usePresentation.getState().addSlide(void 0, after),
2101
+ className: ui.cn(
2102
+ "flex items-center gap-1 rounded-full border border-dashed border-border bg-background px-2.5 py-0.5 text-xs text-muted-foreground transition-opacity hover:border-solid hover:text-foreground focus-visible:opacity-100",
2103
+ !always && "opacity-0 group-hover/insert:opacity-100 pointer-coarse:opacity-100"
2104
+ ),
2105
+ "aria-label": `New slide after slide ${after + 1}`,
2106
+ "data-testid": `slide-insert-${after}`,
2107
+ children: [
2108
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "size-3" }),
2109
+ "Slide"
2110
+ ]
2111
+ }
2112
+ ) });
2113
+ }
2114
+ function SlidesEditor({
2115
+ onPresent,
2116
+ actions,
2117
+ menuItems,
2118
+ trayStart,
2119
+ onChooseLogo,
2120
+ onEditImage,
2121
+ onCropImage
2122
+ }) {
2123
+ const ids = chunkXTR26TNE_cjs.usePresentation((s) => s.slides.map((slide) => slide.id).join(" "));
2124
+ const slideIds = react.useMemo(() => ids.split(" "), [ids]);
2125
+ const current2 = chunkXTR26TNE_cjs.usePresentation((s) => s.current);
2126
+ const captions = chunkXTR26TNE_cjs.usePresentation((s) => s.captions);
2127
+ const [selection, setSelection] = react.useState(null);
2128
+ const [view, setView] = react.useState(null);
2129
+ const list = react.useRef(null);
2130
+ const rootRef = react.useCallback((node) => {
2131
+ if (node) setView(node.ownerDocument.defaultView);
2132
+ }, []);
2133
+ const onSelect = react.useCallback((index, clipId) => {
2134
+ setSelection(clipId ? { index, clipId } : null);
2135
+ }, []);
2136
+ const keySelection = react.useMemo(
2137
+ () => ({ clipId: selection?.clipId ?? null, clear: () => setSelection(null) }),
2138
+ [selection]
2139
+ );
2140
+ chunk5LTIZ44D_cjs.usePresenterKeys(view, false, keySelection);
2141
+ react.useEffect(() => {
2142
+ const card = list.current?.querySelector(`[data-testid="slide-${current2}"]`);
2143
+ card?.scrollIntoView?.({ block: "nearest", behavior: "smooth" });
2144
+ }, [current2, slideIds.length]);
2145
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2146
+ "div",
2147
+ {
2148
+ ref: rootRef,
2149
+ className: "relative flex h-full min-h-0 flex-col bg-muted/30",
2150
+ "data-testid": "slides-editor",
2151
+ children: [
2152
+ /* @__PURE__ */ jsxRuntime.jsx("div", { ref: list, className: "min-h-0 flex-1 overflow-y-auto", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto flex max-w-[1100px] flex-col pb-6 pl-2 pr-6 pt-14", children: slideIds.map((id, index) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2153
+ /* @__PURE__ */ jsxRuntime.jsx(
2154
+ SlideCard,
2155
+ {
2156
+ index,
2157
+ count: slideIds.length,
2158
+ current: index === current2,
2159
+ selected: selection?.index === index ? selection.clipId : null,
2160
+ onSelect,
2161
+ onEditImage,
2162
+ onCropImage
2163
+ }
2164
+ ),
2165
+ /* @__PURE__ */ jsxRuntime.jsx(InsertSlide, { after: index, always: index === slideIds.length - 1 })
2166
+ ] }, id)) }) }),
2167
+ /* @__PURE__ */ jsxRuntime.jsxs(
2168
+ "div",
2169
+ {
2170
+ className: "absolute right-4 top-2.5 z-40 flex items-center gap-0.5 rounded-full border border-border bg-background p-1 shadow-md",
2171
+ "data-testid": "slides-toolbar",
2172
+ children: [
2173
+ /* @__PURE__ */ jsxRuntime.jsx(DesignMenu, { onChooseLogo }),
2174
+ /* @__PURE__ */ jsxRuntime.jsx(InsertMenu, { onInserted: (index, boxId) => setSelection({ index, clipId: boxId }) }),
2175
+ actions,
2176
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenu, { children: [
2177
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxRuntime.jsx(
2178
+ ui.Button,
2179
+ {
2180
+ type: "button",
2181
+ size: "icon",
2182
+ variant: "ghost",
2183
+ className: "size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11",
2184
+ title: "More",
2185
+ "aria-label": "More",
2186
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.MoreHorizontal, { className: "size-4" })
2187
+ }
2188
+ ) }),
2189
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuContent, { align: "end", children: [
2190
+ menuItems,
2191
+ menuItems && /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuSeparator, {}),
2192
+ /* @__PURE__ */ jsxRuntime.jsx(
2193
+ ui.DropdownMenuCheckboxItem,
2194
+ {
2195
+ checked: captions,
2196
+ onCheckedChange: (on) => chunkXTR26TNE_cjs.usePresentation.getState().setCaptions(!!on),
2197
+ children: "Show source labels"
2198
+ }
2199
+ )
2200
+ ] })
2201
+ ] }),
2202
+ /* @__PURE__ */ jsxRuntime.jsxs(
2203
+ ui.Button,
2204
+ {
2205
+ type: "button",
2206
+ size: "sm",
2207
+ className: "ml-0.5 h-8 rounded-full",
2208
+ onClick: () => {
2209
+ setSelection(null);
2210
+ onPresent();
2211
+ },
2212
+ title: "Present: the slides alone, in a window to share or put full screen",
2213
+ "data-testid": "presentation-present-button",
2214
+ children: [
2215
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Play, { className: "size-3.5" }),
2216
+ "Present"
2217
+ ]
2218
+ }
2219
+ )
2220
+ ]
2221
+ }
2222
+ ),
2223
+ /* @__PURE__ */ jsxRuntime.jsx(ClipTray, { start: trayStart }),
2224
+ /* @__PURE__ */ jsxRuntime.jsx(Notice, {})
2225
+ ]
2226
+ }
2227
+ );
2228
+ }
2229
+
2230
+ // src/presentation/draw-boxes.ts
2231
+ var LINE_HEIGHT = 1.2;
2232
+ function fontOf(run, themeFont, width) {
2233
+ const size = chunkXTR26TNE_cjs.pointsToStage(run.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE) * width;
2234
+ const family = run.font ? `"${run.font}", ${themeFont}` : themeFont;
2235
+ return `${run.italic ? "italic " : ""}${run.bold ? 700 : 400} ${size}px ${family}`;
2236
+ }
2237
+ function outline(context, box2, w, h) {
2238
+ context.beginPath();
2239
+ if (box2.shape === "ellipse") context.ellipse(w / 2, h / 2, w / 2, h / 2, 0, 0, Math.PI * 2);
2240
+ else if (box2.shape === "roundRect") context.roundRect(0, 0, w, h, Math.min(w, h) / 6);
2241
+ else context.rect(0, 0, w, h);
2242
+ }
2243
+ function drawBox(context, box2, width, height, themeFont) {
2244
+ const { frame } = box2;
2245
+ const w = frame.w * width;
2246
+ const h = frame.h * height;
2247
+ context.save();
2248
+ context.translate(frame.x * width + w / 2, frame.y * height + h / 2);
2249
+ if (box2.rotation) context.rotate(box2.rotation * Math.PI / 180);
2250
+ context.translate(-w / 2, -h / 2);
2251
+ const lineWidth = box2.line ? Math.max(1, chunkXTR26TNE_cjs.pointsToStage(box2.line.width) * width) : 0;
2252
+ if (box2.shape === "line") {
2253
+ context.strokeStyle = box2.line?.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body;
2254
+ context.lineWidth = lineWidth || 1;
2255
+ if (box2.line?.dash) context.setLineDash([lineWidth * 3, lineWidth * 2]);
2256
+ context.beginPath();
2257
+ context.moveTo(0, box2.flipV ? h : 0);
2258
+ context.lineTo(w, box2.flipV ? 0 : h);
2259
+ context.stroke();
2260
+ context.restore();
2261
+ return;
2262
+ }
2263
+ if (box2.fill) {
2264
+ context.globalAlpha = box2.fillAlpha ?? 1;
2265
+ context.fillStyle = box2.fill;
2266
+ outline(context, box2, w, h);
2267
+ context.fill();
2268
+ context.globalAlpha = 1;
2269
+ }
2270
+ if (box2.line && lineWidth) {
2271
+ context.strokeStyle = box2.line.color;
2272
+ context.lineWidth = lineWidth;
2273
+ if (box2.line.dash) context.setLineDash([lineWidth * 3, lineWidth * 2]);
2274
+ outline(context, box2, w, h);
2275
+ context.stroke();
2276
+ context.setLineDash([]);
2277
+ }
2278
+ const inset = chunkXTR26TNE_cjs.pointsToStage(box2.inset ?? chunkXTR26TNE_cjs.DEFAULT_INSET) * width;
2279
+ const room = w - 2 * inset;
2280
+ context.textBaseline = "alphabetic";
2281
+ const lines = [];
2282
+ for (const paragraph of box2.paragraphs) {
2283
+ const size = chunkXTR26TNE_cjs.pointsToStage(paragraph.runs[0]?.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE) * width;
2284
+ const indent = (paragraph.level ?? 0) * 0.5 * size + (paragraph.bullet ? size * 0.9 : 0);
2285
+ const words = [];
2286
+ for (const run of paragraph.runs) {
2287
+ context.font = fontOf(run, themeFont, width);
2288
+ for (const part of run.text.split(/(\s+)/).filter(Boolean)) {
2289
+ words.push({ text: part, run, width: context.measureText(part).width });
2290
+ }
2291
+ }
2292
+ let line = [];
2293
+ let used = 0;
2294
+ let first = true;
2295
+ const flush = () => {
2296
+ lines.push({
2297
+ pieces: line,
2298
+ size,
2299
+ align: paragraph.align ?? "left",
2300
+ indent,
2301
+ ...first && paragraph.bullet ? { bullet: paragraph.runs[0] } : {}
2302
+ });
2303
+ first = false;
2304
+ line = [];
2305
+ used = 0;
2306
+ };
2307
+ for (const word of words) {
2308
+ if (used + word.width > room - indent && line.length && word.text.trim()) flush();
2309
+ if (!line.length && !word.text.trim()) continue;
2310
+ line.push(word);
2311
+ used += word.width;
2312
+ }
2313
+ flush();
2314
+ }
2315
+ const total = lines.reduce((sum, line) => sum + line.size * LINE_HEIGHT, 0);
2316
+ const free = h - 2 * inset - total;
2317
+ let y = inset + (box2.anchor === "middle" ? free / 2 : box2.anchor === "bottom" ? free : 0);
2318
+ context.save();
2319
+ context.beginPath();
2320
+ context.rect(0, 0, w, h);
2321
+ context.clip();
2322
+ for (const line of lines) {
2323
+ const lineWidth2 = line.pieces.reduce((sum, piece) => sum + piece.width, 0);
2324
+ const space = room - line.indent;
2325
+ let x = inset + line.indent + (line.align === "center" ? (space - lineWidth2) / 2 : line.align === "right" ? space - lineWidth2 : 0);
2326
+ const baseline = y + line.size * (LINE_HEIGHT - 0.2);
2327
+ if (line.bullet) {
2328
+ context.font = fontOf(line.bullet, themeFont, width);
2329
+ context.fillStyle = line.bullet.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body;
2330
+ context.fillText("\u2022", inset + line.indent - line.size * 0.9, baseline);
2331
+ }
2332
+ for (const piece of line.pieces) {
2333
+ context.font = fontOf(piece.run, themeFont, width);
2334
+ context.fillStyle = piece.run.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body;
2335
+ context.fillText(piece.text, x, baseline);
2336
+ if (piece.run.underline) {
2337
+ context.fillRect(x, baseline + line.size * 0.08, piece.width, Math.max(1, line.size * 0.05));
2338
+ }
2339
+ x += piece.width;
2340
+ }
2341
+ y += line.size * LINE_HEIGHT;
2342
+ }
2343
+ context.restore();
2344
+ context.restore();
2345
+ }
2346
+ var boxWords = (box2) => box2.paragraphs.map(
2347
+ (paragraph) => paragraph.runs.map((run) => run.text).join("").trim()
2348
+ ).filter(Boolean);
2349
+
2350
+ // src/presentation/render-slides.ts
2351
+ var SLIDE_PIXELS = { width: 1920, height: Math.round(1920 / chunkXTR26TNE_cjs.STAGE_ASPECT) };
2352
+ async function videoFrame(source, seconds) {
2353
+ const video = document.createElement("video");
2354
+ video.muted = true;
2355
+ video.playsInline = true;
2356
+ video.preload = "auto";
2357
+ const url = URL.createObjectURL(source);
2358
+ video.src = url;
2359
+ const ready = new Promise((resolve) => {
2360
+ const timer = setTimeout(() => resolve(false), 8e3);
2361
+ video.addEventListener(
2362
+ "loadedmetadata",
2363
+ () => {
2364
+ video.currentTime = Math.min(seconds, Math.max(0, (video.duration || 0) - 0.05));
2365
+ },
2366
+ { once: true }
2367
+ );
2368
+ video.addEventListener(
2369
+ "seeked",
2370
+ () => {
2371
+ clearTimeout(timer);
2372
+ resolve(true);
2373
+ },
2374
+ { once: true }
2375
+ );
2376
+ video.addEventListener("error", () => resolve(false), { once: true });
2377
+ });
2378
+ const ok = await ready;
2379
+ URL.revokeObjectURL(url);
2380
+ return ok ? video : null;
2381
+ }
2382
+ async function drawClip(context, clip, box2) {
2383
+ const { rect } = clip;
2384
+ if (clip.kind === "pdf") {
2385
+ const image = await viewers.renderPdfRegion({
2386
+ file: clip.source,
2387
+ page: clip.page,
2388
+ rect,
2389
+ masks: clip.masks,
2390
+ width: Math.ceil(box2.width)
2391
+ });
2392
+ context.drawImage(image, box2.x, box2.y, box2.width, box2.height);
2393
+ viewers.closePdfRegionImage(image);
2394
+ return;
2395
+ }
2396
+ if (clip.kind === "image") {
2397
+ const bitmap = await createImageBitmap(clip.source);
2398
+ context.drawImage(
2399
+ bitmap,
2400
+ rect.x * bitmap.width,
2401
+ rect.y * bitmap.height,
2402
+ rect.w * bitmap.width,
2403
+ rect.h * bitmap.height,
2404
+ box2.x,
2405
+ box2.y,
2406
+ box2.width,
2407
+ box2.height
2408
+ );
2409
+ bitmap.close();
2410
+ return;
2411
+ }
2412
+ const video = await videoFrame(clip.source, clip.start ?? 0);
2413
+ if (video) {
2414
+ const w = video.videoWidth;
2415
+ const h = video.videoHeight;
2416
+ context.drawImage(
2417
+ video,
2418
+ rect.x * w,
2419
+ rect.y * h,
2420
+ rect.w * w,
2421
+ rect.h * h,
2422
+ box2.x,
2423
+ box2.y,
2424
+ box2.width,
2425
+ box2.height
2426
+ );
2427
+ return;
2428
+ }
2429
+ context.fillStyle = "#262626";
2430
+ context.fillRect(box2.x, box2.y, box2.width, box2.height);
2431
+ context.fillStyle = "#e5e5e5";
2432
+ context.font = `${Math.round(box2.height * 0.12)}px sans-serif`;
2433
+ context.textAlign = "center";
2434
+ context.textBaseline = "middle";
2435
+ context.fillText(
2436
+ `\u25B6 ${clip.caption}`,
2437
+ box2.x + box2.width / 2,
2438
+ box2.y + box2.height / 2,
2439
+ box2.width * 0.9
2440
+ );
2441
+ }
2442
+ var CLIP_RADIUS = 6e-3;
2443
+ async function drawRoundedClip(context, clip, box2, stageWidth, shadow = true) {
2444
+ const radius = stageWidth * CLIP_RADIUS;
2445
+ const outline2 = () => {
2446
+ context.beginPath();
2447
+ context.roundRect(box2.x, box2.y, box2.width, box2.height, radius);
2448
+ };
2449
+ context.save();
2450
+ if (shadow) {
2451
+ context.shadowColor = "rgba(0,0,0,0.5)";
2452
+ context.shadowBlur = stageWidth * 0.016;
2453
+ context.shadowOffsetY = stageWidth * 5e-3;
2454
+ }
2455
+ context.fillStyle = "#fff";
2456
+ outline2();
2457
+ context.fill();
2458
+ context.restore();
2459
+ context.save();
2460
+ outline2();
2461
+ context.clip();
2462
+ await drawClip(context, clip, box2);
2463
+ if (clip.kind === "pdf") chunkXTR26TNE_cjs.paintClipEdge(context, box2, stageWidth);
2464
+ context.restore();
2465
+ context.save();
2466
+ context.strokeStyle = "rgba(255,255,255,0.15)";
2467
+ context.lineWidth = Math.max(1, stageWidth / 1280);
2468
+ outline2();
2469
+ context.stroke();
2470
+ context.restore();
2471
+ }
2472
+ function wrapLines(context, text, width, max) {
2473
+ const lines = [];
2474
+ let line = "";
2475
+ for (const word of text.split(/\s+/).filter(Boolean)) {
2476
+ const next = line ? `${line} ${word}` : word;
2477
+ if (context.measureText(next).width <= width || !line) {
2478
+ line = next;
2479
+ continue;
2480
+ }
2481
+ lines.push(line);
2482
+ line = word;
2483
+ }
2484
+ if (line) lines.push(line);
2485
+ if (lines.length <= max) return lines;
2486
+ const kept = lines.slice(0, max);
2487
+ let last = kept[max - 1];
2488
+ while (last && context.measureText(`${last}\u2026`).width > width) last = last.slice(0, -1);
2489
+ kept[max - 1] = `${last}\u2026`;
2490
+ return kept;
2491
+ }
2492
+ function drawText(context, box2, lines, theme, width, height) {
2493
+ const size = box2.size * width;
2494
+ const lineHeight = size * box2.leading;
2495
+ context.font = `${box2.weight} ${size}px ${chunkXTR26TNE_cjs.FONTS[theme.font].css}`;
2496
+ context.textBaseline = "top";
2497
+ const indent = box2.bullets ? size * 1.22 : 0;
2498
+ const left = box2.x * width;
2499
+ const boxWidth = box2.w * width - indent;
2500
+ const gap = box2.bullets ? size * 0.3 : 0;
2501
+ const room = box2.h * height;
2502
+ const paragraphs = [];
2503
+ let used = 0;
2504
+ for (const paragraph of lines) {
2505
+ const fits = Math.floor((room - used + 1e-6) / lineHeight);
2506
+ if (fits < 1) break;
2507
+ const wrapped = wrapLines(context, paragraph, boxWidth, fits);
2508
+ paragraphs.push(wrapped);
2509
+ used += wrapped.length * lineHeight + gap;
2510
+ }
2511
+ used = Math.max(0, used - gap);
2512
+ const offset = box2.anchor === "bottom" ? room - used : box2.anchor === "middle" ? (room - used) / 2 : 0;
2513
+ let y = box2.y * height + offset + (lineHeight - size) / 2;
2514
+ for (const wrapped of paragraphs) {
2515
+ if (box2.bullets) {
2516
+ const square = size * 0.42;
2517
+ context.fillStyle = chunkXTR26TNE_cjs.accentColor(theme.accent);
2518
+ context.fillRect(left, y + size * 0.5 - square / 2, square, square);
2519
+ }
2520
+ context.fillStyle = chunkXTR26TNE_cjs.TEXT_COLORS[box2.tone];
2521
+ context.textAlign = box2.align === "center" ? "center" : "left";
2522
+ for (const line of wrapped) {
2523
+ const x = box2.align === "center" ? left + box2.w * width / 2 : left + indent;
2524
+ context.fillText(line, x, y);
2525
+ y += lineHeight;
2526
+ }
2527
+ y += gap;
2528
+ }
2529
+ }
2530
+ async function drawLogo(context, logo, box2, width, height) {
2531
+ const image = await loadImage(logo);
2532
+ const scale = Math.min(box2.w * width / image.width, box2.h * height / image.height);
2533
+ const w = image.width * scale;
2534
+ const h = image.height * scale;
2535
+ const x = box2.x < 0.5 ? box2.x * width : (box2.x + box2.w) * width - w;
2536
+ context.globalAlpha = 0.9;
2537
+ context.drawImage(image, x, (box2.y + box2.h) * height - h, w, h);
2538
+ context.globalAlpha = 1;
2539
+ }
2540
+ async function loadImage(file) {
2541
+ const url = URL.createObjectURL(file);
2542
+ try {
2543
+ const image = new Image();
2544
+ image.src = url;
2545
+ await image.decode();
2546
+ if (!image.naturalWidth) {
2547
+ image.width = 600;
2548
+ image.height = 200;
2549
+ }
2550
+ return image;
2551
+ } finally {
2552
+ URL.revokeObjectURL(url);
2553
+ }
2554
+ }
2555
+ function slideWords(slide) {
2556
+ const own = slide.layout === "free" ? [] : [slide.caption.trim(), ...chunkXTR26TNE_cjs.textLines(slide.points)];
2557
+ return [...own, ...slide.boxes.flatMap(boxWords)].filter(Boolean);
2558
+ }
2559
+ function drawSlideText(context, slide, look, width, height) {
2560
+ const geometry = chunkXTR26TNE_cjs.slideGeometry(slide, {
2561
+ labels: look.labels,
2562
+ logo: look.theme.logo?.corner ?? null
2563
+ });
2564
+ const title = slide.caption.trim();
2565
+ if (title && geometry.title) {
2566
+ drawText(context, geometry.title, [title], look.theme, width, height);
2567
+ if (geometry.rule) {
2568
+ context.fillStyle = chunkXTR26TNE_cjs.accentColor(look.theme.accent);
2569
+ const r = geometry.rule;
2570
+ context.fillRect(r.x * width, r.y * height, r.w * width, r.h * height);
2571
+ }
2572
+ }
2573
+ if (geometry.text) {
2574
+ drawText(context, geometry.text, chunkXTR26TNE_cjs.textLines(slide.points), look.theme, width, height);
2575
+ }
2576
+ return geometry;
2577
+ }
2578
+ async function renderSlide(slide, clips, look) {
2579
+ const { width, height } = SLIDE_PIXELS;
2580
+ const canvas = document.createElement("canvas");
2581
+ canvas.width = width;
2582
+ canvas.height = height;
2583
+ const context = canvas.getContext("2d");
2584
+ if (!context) throw new Error("Could not draw the slides");
2585
+ if (slide.background) {
2586
+ await chunkXTR26TNE_cjs.paintSlideBackground(context, width, height, slide.background, look.media);
2587
+ } else {
2588
+ chunkXTR26TNE_cjs.paintBackground(context, width, height, look.theme.background);
2589
+ }
2590
+ const geometry = drawSlideText(context, slide, look, width, height);
2591
+ const text = slideWords(slide);
2592
+ const font = chunkXTR26TNE_cjs.FONTS[look.theme.font].css;
2593
+ const drawBoxes = (front) => {
2594
+ for (const box2 of slide.boxes) {
2595
+ if (!!box2.front === front) drawBox(context, box2, width, height, font);
2596
+ }
2597
+ };
2598
+ drawBoxes(false);
2599
+ for (const item of slide.items) {
2600
+ const clip = clips.find((entry) => entry.id === item.clipId);
2601
+ if (!clip) continue;
2602
+ const box2 = {
2603
+ x: item.frame.x * width,
2604
+ y: item.frame.y * height,
2605
+ width: item.frame.w * width,
2606
+ height: chunkXTR26TNE_cjs.frameHeight(item.frame, clip.aspect) * height
2607
+ };
2608
+ if (clip.plain) {
2609
+ await drawClip(context, clip, box2);
2610
+ continue;
2611
+ }
2612
+ await drawRoundedClip(context, clip, box2, width);
2613
+ const label = chunkXTR26TNE_cjs.clipLabel(clip, look.columnValues);
2614
+ if (look.labels && label) {
2615
+ context.fillStyle = chunkXTR26TNE_cjs.TEXT_COLORS.muted;
2616
+ context.font = `${Math.round(width * 0.017)}px ${font}`;
2617
+ context.textAlign = "left";
2618
+ context.textBaseline = "top";
2619
+ context.fillText(label, box2.x, box2.y + box2.height + width * 5e-3, box2.width);
2620
+ text.push(label);
2621
+ }
2622
+ }
2623
+ drawBoxes(true);
2624
+ if (geometry.logo && look.logo) await drawLogo(context, look.logo, geometry.logo, width, height);
2625
+ const image = await new Promise(
2626
+ (resolve, reject) => canvas.toBlob(
2627
+ (blob) => blob ? resolve(blob) : reject(new Error("Could not draw the slides")),
2628
+ "image/jpeg",
2629
+ 0.92
2630
+ )
2631
+ );
2632
+ canvas.width = canvas.height = 0;
2633
+ return { image, text };
2634
+ }
2635
+
2636
+ // src/presentation/export-boxes.ts
2637
+ var SLIDE_INCHES = { w: 13.333, h: 7.5 };
2638
+ var hex = (color) => color.replace("#", "").toUpperCase();
2639
+ function addBox(pptx, page, box2, themeFont) {
2640
+ const { frame } = box2;
2641
+ const place = {
2642
+ x: frame.x * SLIDE_INCHES.w,
2643
+ y: frame.y * SLIDE_INCHES.h,
2644
+ w: Math.max(0.01, frame.w * SLIDE_INCHES.w),
2645
+ h: frame.h * SLIDE_INCHES.h,
2646
+ ...box2.rotation ? { rotate: box2.rotation } : {},
2647
+ ...box2.flipH ? { flipH: true } : {},
2648
+ ...box2.flipV ? { flipV: true } : {}
2649
+ };
2650
+ const line = box2.line ? {
2651
+ color: hex(box2.line.color),
2652
+ width: box2.line.width,
2653
+ ...box2.line.dash ? { dashType: "dash" } : {}
2654
+ } : void 0;
2655
+ if (box2.shape === "line") {
2656
+ page.addShape(pptx.ShapeType.line, {
2657
+ ...place,
2658
+ line: line ?? { color: hex(chunkXTR26TNE_cjs.TEXT_COLORS.body), width: 1 }
2659
+ });
2660
+ return;
2661
+ }
2662
+ const shape = box2.shape === "ellipse" ? pptx.ShapeType.ellipse : box2.shape === "roundRect" ? pptx.ShapeType.roundRect : pptx.ShapeType.rect;
2663
+ const fill = box2.fill ? {
2664
+ color: hex(box2.fill),
2665
+ ...box2.fillAlpha !== void 0 ? { transparency: Math.round((1 - box2.fillAlpha) * 100) } : {}
2666
+ } : void 0;
2667
+ const runs = [];
2668
+ box2.paragraphs.forEach((paragraph, p) => {
2669
+ const last = p === box2.paragraphs.length - 1;
2670
+ const paragraphRuns = paragraph.runs.length ? paragraph.runs : [{ text: "" }];
2671
+ paragraphRuns.forEach((run, r) => {
2672
+ runs.push({
2673
+ text: run.text,
2674
+ options: {
2675
+ fontSize: run.size ?? chunkXTR26TNE_cjs.DEFAULT_TEXT_SIZE,
2676
+ fontFace: run.font ?? themeFont,
2677
+ color: hex(run.color ?? chunkXTR26TNE_cjs.TEXT_COLORS.body),
2678
+ ...run.bold ? { bold: true } : {},
2679
+ ...run.italic ? { italic: true } : {},
2680
+ ...run.underline ? { underline: { style: "sng" } } : {},
2681
+ align: paragraph.align ?? "left",
2682
+ ...paragraph.level ? { indentLevel: paragraph.level } : {},
2683
+ ...paragraph.bullet && r === 0 ? { bullet: true } : {},
2684
+ ...r === paragraphRuns.length - 1 && !last ? { breakLine: true } : {}
2685
+ }
2686
+ });
2687
+ });
2688
+ });
2689
+ const text = runs.some((run) => run.text);
2690
+ if (!text && box2.shape === "text") return;
2691
+ page.addText(text ? runs : "", {
2692
+ ...place,
2693
+ shape,
2694
+ ...fill ? { fill } : {},
2695
+ ...line ? { line } : {},
2696
+ // Its part in the slide (its title, its body), kept by name: read back when imported.
2697
+ ...box2.role ? { objectName: `ARBI ${box2.role}` } : {},
2698
+ valign: box2.anchor ?? "top",
2699
+ margin: box2.inset ?? chunkXTR26TNE_cjs.DEFAULT_INSET,
2700
+ lineSpacingMultiple: 1,
2701
+ fit: "none"
2702
+ });
2703
+ }
2704
+
2705
+ // src/presentation/export-pptx.ts
2706
+ var SLIDE_INCHES2 = { w: 13.333, h: 7.5 };
2707
+ var toPoints = (size) => Math.round(size * SLIDE_INCHES2.w * 72 * 10) / 10;
2708
+ var hex2 = (color) => color.replace("#", "").toUpperCase();
2709
+ async function canvasImage(width, height, draw) {
2710
+ const canvas = document.createElement("canvas");
2711
+ canvas.width = Math.max(1, Math.round(width));
2712
+ canvas.height = Math.max(1, Math.round(height));
2713
+ const context = canvas.getContext("2d");
2714
+ if (!context) throw new Error("Could not draw the slides");
2715
+ await draw(context);
2716
+ const url = canvas.toDataURL("image/png");
2717
+ canvas.width = canvas.height = 0;
2718
+ return url.replace(/^data:/, "");
2719
+ }
2720
+ var box = (b) => ({
2721
+ x: b.x * SLIDE_INCHES2.w,
2722
+ y: b.y * SLIDE_INCHES2.h,
2723
+ w: b.w * SLIDE_INCHES2.w,
2724
+ h: b.h * SLIDE_INCHES2.h
2725
+ });
2726
+ function textOptions(text, look) {
2727
+ return {
2728
+ ...box(text),
2729
+ fontFace: chunkXTR26TNE_cjs.FONTS[look.theme.font].pptx,
2730
+ fontSize: toPoints(text.size),
2731
+ bold: text.weight >= 600,
2732
+ color: hex2(chunkXTR26TNE_cjs.TEXT_COLORS[text.tone]),
2733
+ align: text.align,
2734
+ valign: text.anchor ?? "top",
2735
+ lineSpacingMultiple: text.leading,
2736
+ margin: 0
2737
+ };
2738
+ }
2739
+ async function buildPptx(deck, look) {
2740
+ const { default: PptxGenJS } = await import('pptxgenjs');
2741
+ const pptx = new PptxGenJS();
2742
+ pptx.layout = "LAYOUT_WIDE";
2743
+ pptx.title = deck.title;
2744
+ pptx.theme = {
2745
+ headFontFace: chunkXTR26TNE_cjs.FONTS[look.theme.font].pptx,
2746
+ bodyFontFace: chunkXTR26TNE_cjs.FONTS[look.theme.font].pptx
2747
+ };
2748
+ const { width, height } = SLIDE_PIXELS;
2749
+ const background = await canvasImage(
2750
+ width,
2751
+ height,
2752
+ (context) => chunkXTR26TNE_cjs.paintBackground(context, width, height, look.theme.background)
2753
+ );
2754
+ const logo = look.logo ? await loadImage(look.logo) : null;
2755
+ const logoData = logo ? await canvasImage(logo.width, logo.height, (context) => context.drawImage(logo, 0, 0)) : null;
2756
+ const themeFont = chunkXTR26TNE_cjs.FONTS[look.theme.font].pptx;
2757
+ for (const slide of deck.slides) {
2758
+ const page = pptx.addSlide();
2759
+ if (slide.background?.color && !slide.background.gradient && !slide.background.image) {
2760
+ page.background = { color: hex2(slide.background.color) };
2761
+ } else if (slide.background) {
2762
+ const own = slide.background;
2763
+ page.background = {
2764
+ data: await canvasImage(
2765
+ width,
2766
+ height,
2767
+ (context) => chunkXTR26TNE_cjs.paintSlideBackground(context, width, height, own, look.media)
2768
+ )
2769
+ };
2770
+ } else {
2771
+ page.background = { data: background };
2772
+ }
2773
+ if (slide.notes?.trim()) page.addNotes(slide.notes);
2774
+ const geometry = chunkXTR26TNE_cjs.slideGeometry(slide, {
2775
+ labels: look.labels,
2776
+ logo: look.theme.logo?.corner ?? null
2777
+ });
2778
+ for (const shape of slide.boxes) if (!shape.front) addBox(pptx, page, shape, themeFont);
2779
+ for (const item of slide.items) {
2780
+ const clip = deck.clips.find((entry) => entry.id === item.clipId);
2781
+ if (!clip) continue;
2782
+ const frame = { ...item.frame, h: chunkXTR26TNE_cjs.frameHeight(item.frame, clip.aspect) };
2783
+ if (clip.plain) {
2784
+ const source = clip.kind === "image" ? await createImageBitmap(clip.source) : null;
2785
+ const scale = source ? Math.min(1, 2400 / Math.max(1, clip.rect.w * source.width)) : 1.5;
2786
+ const pixels2 = source ? { w: clip.rect.w * source.width * scale, h: clip.rect.h * source.height * scale } : { w: frame.w * width * 1.5, h: frame.h * height * 1.5 };
2787
+ source?.close();
2788
+ const data2 = await canvasImage(
2789
+ pixels2.w,
2790
+ pixels2.h,
2791
+ (context) => drawClip(context, clip, { x: 0, y: 0, width: pixels2.w, height: pixels2.h })
2792
+ );
2793
+ page.addImage({ data: data2, ...box(frame), altText: clip.caption || clip.fileName });
2794
+ continue;
2795
+ }
2796
+ const pixels = { w: frame.w * width * 1.5, h: frame.h * height * 1.5 };
2797
+ const data = await canvasImage(
2798
+ pixels.w,
2799
+ pixels.h,
2800
+ (context) => drawRoundedClip(
2801
+ context,
2802
+ clip,
2803
+ { x: 0, y: 0, width: pixels.w, height: pixels.h },
2804
+ width * 1.5,
2805
+ false
2806
+ )
2807
+ );
2808
+ page.addImage({
2809
+ data,
2810
+ ...box(frame),
2811
+ altText: clip.caption,
2812
+ shadow: { type: "outer", blur: 10, offset: 3, angle: 90, color: "000000", opacity: 0.45 }
2813
+ });
2814
+ const label = chunkXTR26TNE_cjs.clipLabel(clip, look.columnValues);
2815
+ if (look.labels && label) {
2816
+ page.addText(label, {
2817
+ ...box({ x: frame.x, y: frame.y + frame.h + 8e-3, w: frame.w, h: 0.045 }),
2818
+ fontFace: chunkXTR26TNE_cjs.FONTS[look.theme.font].pptx,
2819
+ fontSize: toPoints(0.017),
2820
+ color: hex2(chunkXTR26TNE_cjs.TEXT_COLORS.muted),
2821
+ margin: 0,
2822
+ valign: "top",
2823
+ fit: "shrink"
2824
+ });
2825
+ }
2826
+ }
2827
+ const title = slide.caption.trim();
2828
+ if (title && geometry.title) {
2829
+ page.addText(title, textOptions(geometry.title, look));
2830
+ if (geometry.rule) {
2831
+ page.addShape(pptx.ShapeType.rect, {
2832
+ ...box(geometry.rule),
2833
+ fill: { color: hex2(chunkXTR26TNE_cjs.accentColor(look.theme.accent)) },
2834
+ line: { color: hex2(chunkXTR26TNE_cjs.accentColor(look.theme.accent)), width: 0 }
2835
+ });
2836
+ }
2837
+ }
2838
+ const lines = chunkXTR26TNE_cjs.textLines(slide.points);
2839
+ if (geometry.text && lines.length) {
2840
+ const text = geometry.text;
2841
+ page.addText(
2842
+ lines.map((line) => ({
2843
+ text: line,
2844
+ options: {
2845
+ breakLine: true,
2846
+ ...text.bullets ? { bullet: { characterCode: "25A0", indent: toPoints(text.size) * 1.2 } } : {}
2847
+ }
2848
+ })),
2849
+ {
2850
+ ...textOptions(text, look),
2851
+ ...text.bullets ? { paraSpaceAfter: toPoints(text.size) * 0.45 } : {}
2852
+ }
2853
+ );
2854
+ }
2855
+ for (const shape of slide.boxes) if (shape.front) addBox(pptx, page, shape, themeFont);
2856
+ if (geometry.logo && logo && logoData) {
2857
+ const area = box(geometry.logo);
2858
+ const scale = Math.min(area.w / logo.width, area.h / logo.height);
2859
+ const w = logo.width * scale;
2860
+ const h = logo.height * scale;
2861
+ page.addImage({
2862
+ data: logoData,
2863
+ x: geometry.logo.x < 0.5 ? area.x : area.x + area.w - w,
2864
+ y: area.y + area.h - h,
2865
+ w,
2866
+ h,
2867
+ transparency: 10
2868
+ });
2869
+ }
2870
+ }
2871
+ const deckFile = await pptx.write({ outputType: "blob" });
2872
+ return accentBullets(deckFile, chunkXTR26TNE_cjs.accentColor(look.theme.accent));
2873
+ }
2874
+ async function accentBullets(deck, accent) {
2875
+ const { default: JSZip } = await import('jszip');
2876
+ const zip = await JSZip.loadAsync(deck);
2877
+ const slides = Object.keys(zip.files).filter((name) => /^ppt\/slides\/slide\d+\.xml$/.test(name));
2878
+ for (const name of slides) {
2879
+ const xml = await zip.file(name).async("string");
2880
+ zip.file(
2881
+ name,
2882
+ xml.replaceAll(
2883
+ '<a:buSzPct val="100000"/><a:buChar',
2884
+ `<a:buClr><a:srgbClr val="${hex2(accent)}"/></a:buClr><a:buSzPct val="70000"/><a:buChar`
2885
+ )
2886
+ );
2887
+ }
2888
+ return zip.generateAsync({ type: "blob", mimeType: deck.type || void 0 });
2889
+ }
2890
+
2891
+ // src/presentation/pptx-data.ts
2892
+ var PRESENTATION_XML_NS = "urn:arbi:presentation:v1";
2893
+ var RELS_NS = "http://schemas.openxmlformats.org/package/2006/relationships";
2894
+ var CUSTOM_XML_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXml";
2895
+ var CUSTOM_XML_PROPS_REL = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/customXmlProps";
2896
+ var CUSTOM_XML_PROPS_TYPE = "application/vnd.openxmlformats-officedocument.customXmlProperties+xml";
2897
+ var escapeXml = (text) => text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
2898
+ var unescapeXml = (text) => text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
2899
+ var ROOT = /<arbiPresentation\b[^>]*xmlns="urn:arbi:presentation:v1"[^>]*>([\s\S]*?)<\/arbiPresentation>/;
2900
+ async function loadZip(file) {
2901
+ const { default: JSZip } = await import('jszip');
2902
+ return JSZip.loadAsync(file instanceof Blob ? await file.arrayBuffer() : file);
2903
+ }
2904
+ var isZipBytes = (bytes) => bytes[0] === 80 && bytes[1] === 75 && bytes[2] === 3 && bytes[3] === 4;
2905
+ async function readPptxPresentationData(file) {
2906
+ const zip = await loadZip(file);
2907
+ for (const name of Object.keys(zip.files)) {
2908
+ if (!/^customXml\/item\d+\.xml$/.test(name)) continue;
2909
+ const match = ROOT.exec(await zip.file(name).async("string"));
2910
+ if (match) return unescapeXml(match[1]);
2911
+ }
2912
+ return null;
2913
+ }
2914
+ async function writePptxPresentationData(file, json) {
2915
+ const zip = await loadZip(file);
2916
+ let index = 1;
2917
+ for (const name of Object.keys(zip.files)) {
2918
+ const match = /^customXml\/item(\d+)\.xml$/.exec(name);
2919
+ if (!match) continue;
2920
+ if (ROOT.test(await zip.file(name).async("string"))) {
2921
+ index = Number(match[1]);
2922
+ break;
2923
+ }
2924
+ index = Math.max(index, Number(match[1]) + 1);
2925
+ }
2926
+ const item = `customXml/item${index}.xml`;
2927
+ const props = `customXml/itemProps${index}.xml`;
2928
+ zip.file(
2929
+ item,
2930
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2931
+ <arbiPresentation xmlns="${PRESENTATION_XML_NS}">${escapeXml(json)}</arbiPresentation>`
2932
+ );
2933
+ zip.file(
2934
+ props,
2935
+ `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2936
+ <ds:datastoreItem ds:itemID="{8B1C2E5A-7D40-4E9B-A1F3-0C6D2B9E4A71}" xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml"><ds:schemaRefs><ds:schemaRef ds:uri="${PRESENTATION_XML_NS}"/></ds:schemaRefs></ds:datastoreItem>`
2937
+ );
2938
+ zip.file(
2939
+ `customXml/_rels/item${index}.xml.rels`,
2940
+ `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2941
+ <Relationships xmlns="${RELS_NS}"><Relationship Id="rId1" Type="${CUSTOM_XML_PROPS_REL}" Target="itemProps${index}.xml"/></Relationships>`
2942
+ );
2943
+ const typesName = "[Content_Types].xml";
2944
+ let types = await zip.file(typesName).async("string");
2945
+ if (!types.includes(`PartName="/${props}"`)) {
2946
+ types = types.replace(
2947
+ "</Types>",
2948
+ `<Override PartName="/${props}" ContentType="${CUSTOM_XML_PROPS_TYPE}"/></Types>`
2949
+ );
2950
+ zip.file(typesName, types);
2951
+ }
2952
+ const relsName = "ppt/_rels/presentation.xml.rels";
2953
+ let rels = await zip.file(relsName).async("string");
2954
+ if (!rels.includes(`Target="../${item}"`)) {
2955
+ rels = rels.replace(
2956
+ "</Relationships>",
2957
+ `<Relationship Id="rIdArbiPresentation" Type="${CUSTOM_XML_REL}" Target="../${item}"/></Relationships>`
2958
+ );
2959
+ zip.file(relsName, rels);
2960
+ }
2961
+ return zip.generateAsync({
2962
+ type: "blob",
2963
+ mimeType: "application/vnd.openxmlformats-officedocument.presentationml.presentation",
2964
+ compression: "DEFLATE"
2965
+ });
2966
+ }
2967
+
2968
+ // src/presentation/pptx-xml.ts
2969
+ var NS = {
2970
+ r: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"
2971
+ };
2972
+ var EMU_PER_POINT = 12700;
2973
+ function kids(element, name) {
2974
+ if (!element) return [];
2975
+ const found = [];
2976
+ for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
2977
+ if (!name || child.localName === name) found.push(child);
2978
+ }
2979
+ return found;
2980
+ }
2981
+ function kid(element, name) {
2982
+ if (!element) return null;
2983
+ for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
2984
+ if (child.localName === name) return child;
2985
+ }
2986
+ return null;
2987
+ }
2988
+ function path(element, ...names) {
2989
+ let at2 = element ?? null;
2990
+ for (const name of names) at2 = kid(at2, name);
2991
+ return at2;
2992
+ }
2993
+ function numAttr(element, name, fallback = 0) {
2994
+ const value = element?.getAttribute(name);
2995
+ if (value === null || value === void 0 || value === "") return fallback;
2996
+ const parsed = Number(value);
2997
+ return Number.isFinite(parsed) ? parsed : fallback;
2998
+ }
2999
+ var relAttr = (element, name) => element?.getAttributeNS(NS.r, name) ?? element?.getAttribute(`r:${name}`) ?? null;
3000
+ var boolAttr = (element, name) => {
3001
+ const value = element?.getAttribute(name);
3002
+ if (value === null || value === void 0) return void 0;
3003
+ return value === "1" || value === "true";
3004
+ };
3005
+ var Package = class {
3006
+ constructor(zip) {
3007
+ this.zip = zip;
3008
+ }
3009
+ zip;
3010
+ parsed = /* @__PURE__ */ new Map();
3011
+ rels = /* @__PURE__ */ new Map();
3012
+ async xml(part) {
3013
+ if (this.parsed.has(part)) return this.parsed.get(part) ?? null;
3014
+ const file = this.zip.file(part);
3015
+ const text = file ? await file.async("string") : null;
3016
+ const doc = text ? new DOMParser().parseFromString(text, "application/xml") : null;
3017
+ const ok = doc && !doc.getElementsByTagName("parsererror").length ? doc : null;
3018
+ this.parsed.set(part, ok);
3019
+ return ok;
3020
+ }
3021
+ async bytes(part) {
3022
+ const file = this.zip.file(part);
3023
+ return file ? file.async("uint8array") : null;
3024
+ }
3025
+ /** A part's relationships by id, their targets resolved to part names. */
3026
+ async relationships(part) {
3027
+ const cached = this.rels.get(part);
3028
+ if (cached) return cached;
3029
+ const slash = part.lastIndexOf("/");
3030
+ const dir = part.slice(0, slash + 1);
3031
+ const relsPart = `${dir}_rels/${part.slice(slash + 1)}.rels`;
3032
+ const doc = await this.xml(relsPart);
3033
+ const map = /* @__PURE__ */ new Map();
3034
+ for (const rel of kids(doc?.documentElement, "Relationship")) {
3035
+ const id = rel.getAttribute("Id");
3036
+ const target = rel.getAttribute("Target") ?? "";
3037
+ const external = rel.getAttribute("TargetMode") === "External";
3038
+ if (!id) continue;
3039
+ map.set(id, {
3040
+ target: external ? target : resolvePart(dir, target),
3041
+ type: rel.getAttribute("Type") ?? "",
3042
+ external
3043
+ });
3044
+ }
3045
+ this.rels.set(part, map);
3046
+ return map;
3047
+ }
3048
+ /** The first relationship of `part` whose type ends with `type` ("/slideLayout"). */
3049
+ async related(part, type) {
3050
+ for (const rel of (await this.relationships(part)).values()) {
3051
+ if (rel.type.endsWith(type) && !rel.external) return rel.target;
3052
+ }
3053
+ return null;
3054
+ }
3055
+ };
3056
+ function resolvePart(dir, target) {
3057
+ if (target.startsWith("/")) return target.slice(1);
3058
+ const parts = (dir + target).split("/");
3059
+ const out = [];
3060
+ for (const part of parts) {
3061
+ if (part === "..") out.pop();
3062
+ else if (part !== "." && part !== "") out.push(part);
3063
+ }
3064
+ return out.join("/");
3065
+ }
3066
+ var PRESET_COLORS = {
3067
+ black: "000000",
3068
+ white: "ffffff",
3069
+ red: "ff0000",
3070
+ green: "008000",
3071
+ blue: "0000ff",
3072
+ yellow: "ffff00",
3073
+ gray: "808080",
3074
+ grey: "808080",
3075
+ darkGray: "a9a9a9",
3076
+ lightGray: "d3d3d3",
3077
+ orange: "ffa500",
3078
+ navy: "000080"
3079
+ };
3080
+ var toHex = (r, g, b) => `#${[r, g, b].map(
3081
+ (v) => Math.round(Math.min(255, Math.max(0, v))).toString(16).padStart(2, "0")
3082
+ ).join("")}`;
3083
+ function rgbToHsl(r, g, b) {
3084
+ r /= 255;
3085
+ g /= 255;
3086
+ b /= 255;
3087
+ const max = Math.max(r, g, b);
3088
+ const min = Math.min(r, g, b);
3089
+ const l = (max + min) / 2;
3090
+ if (max === min) return [0, 0, l];
3091
+ const d = max - min;
3092
+ const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
3093
+ let h = max === r ? (g - b) / d + (g < b ? 6 : 0) : max === g ? (b - r) / d + 2 : (r - g) / d + 4;
3094
+ h /= 6;
3095
+ return [h, s, l];
3096
+ }
3097
+ function hslToRgb(h, s, l) {
3098
+ if (s === 0) return [l * 255, l * 255, l * 255];
3099
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
3100
+ const p = 2 * l - q;
3101
+ const hue = (t) => {
3102
+ if (t < 0) t += 1;
3103
+ if (t > 1) t -= 1;
3104
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
3105
+ if (t < 1 / 2) return q;
3106
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
3107
+ return p;
3108
+ };
3109
+ return [hue(h + 1 / 3) * 255, hue(h) * 255, hue(h - 1 / 3) * 255];
3110
+ }
3111
+ function readColor(element, theme, placeholder) {
3112
+ if (!element) return null;
3113
+ let hex3 = null;
3114
+ switch (element.localName) {
3115
+ case "srgbClr":
3116
+ hex3 = element.getAttribute("val");
3117
+ break;
3118
+ case "sysClr":
3119
+ hex3 = element.getAttribute("lastClr") ?? (element.getAttribute("val") === "window" ? "ffffff" : "000000");
3120
+ break;
3121
+ case "prstClr":
3122
+ hex3 = PRESET_COLORS[element.getAttribute("val") ?? ""] ?? "000000";
3123
+ break;
3124
+ case "scrgbClr": {
3125
+ const c = (name) => Math.round(numAttr(element, name) / 1e5 * 255);
3126
+ hex3 = toHex(c("r"), c("g"), c("b")).slice(1);
3127
+ break;
3128
+ }
3129
+ case "schemeClr": {
3130
+ const name = element.getAttribute("val") ?? "";
3131
+ if (name === "phClr") {
3132
+ hex3 = "000000";
3133
+ break;
3134
+ }
3135
+ const mapped = theme.map[name] ?? name;
3136
+ hex3 = theme.scheme[mapped] ?? theme.scheme[name] ?? null;
3137
+ break;
3138
+ }
3139
+ default:
3140
+ return null;
3141
+ }
3142
+ if (!hex3 || !/^[0-9a-f]{6}$/i.test(hex3)) return null;
3143
+ let r = parseInt(hex3.slice(0, 2), 16);
3144
+ let g = parseInt(hex3.slice(2, 4), 16);
3145
+ let b = parseInt(hex3.slice(4, 6), 16);
3146
+ let alpha = 1;
3147
+ for (const mod of kids(element)) {
3148
+ const val = numAttr(mod, "val") / 1e5;
3149
+ switch (mod.localName) {
3150
+ case "alpha":
3151
+ alpha = val;
3152
+ break;
3153
+ case "lumMod":
3154
+ case "lumOff":
3155
+ case "satMod": {
3156
+ const [h, s, l] = rgbToHsl(r, g, b);
3157
+ const next = mod.localName === "lumMod" ? hslToRgb(h, s, Math.min(1, l * val)) : mod.localName === "lumOff" ? hslToRgb(h, s, Math.min(1, Math.max(0, l + val))) : hslToRgb(h, Math.min(1, s * val), l);
3158
+ [r, g, b] = next;
3159
+ break;
3160
+ }
3161
+ case "tint":
3162
+ r = 255 - (255 - r) * val;
3163
+ g = 255 - (255 - g) * val;
3164
+ b = 255 - (255 - b) * val;
3165
+ break;
3166
+ case "shade":
3167
+ r *= val;
3168
+ g *= val;
3169
+ b *= val;
3170
+ break;
3171
+ }
3172
+ }
3173
+ return { hex: toHex(r, g, b), alpha };
3174
+ }
3175
+ var COLOR_NAMES = ["srgbClr", "schemeClr", "sysClr", "prstClr", "scrgbClr"];
3176
+ function colorIn(element, theme, placeholder) {
3177
+ for (const child of kids(element)) {
3178
+ if (COLOR_NAMES.includes(child.localName)) return readColor(child, theme);
3179
+ }
3180
+ return null;
3181
+ }
3182
+ function readFill(element, theme, placeholder) {
3183
+ for (const child of kids(element)) {
3184
+ switch (child.localName) {
3185
+ case "noFill":
3186
+ return { kind: "none" };
3187
+ case "solidFill": {
3188
+ const color = colorIn(child, theme);
3189
+ return color ? { kind: "solid", color } : null;
3190
+ }
3191
+ case "gradFill": {
3192
+ const stops = kids(kid(child, "gsLst"), "gs").map((gs) => ({ pos: numAttr(gs, "pos"), color: colorIn(gs, theme) })).filter((stop) => stop.color !== null).sort((x, y) => x.pos - y.pos);
3193
+ if (!stops.length) return null;
3194
+ const ang = numAttr(kid(child, "lin"), "ang") / 6e4;
3195
+ return {
3196
+ kind: "gradient",
3197
+ from: stops[0].color,
3198
+ to: stops[stops.length - 1].color,
3199
+ angle: (ang + 90) % 360
3200
+ };
3201
+ }
3202
+ case "pattFill": {
3203
+ const color = colorIn(kid(child, "fgClr"), theme);
3204
+ return color ? { kind: "solid", color } : null;
3205
+ }
3206
+ case "blipFill": {
3207
+ const blip = kid(child, "blip");
3208
+ const embed = relAttr(blip, "embed");
3209
+ return embed ? { kind: "image", embed, srcRect: kid(child, "srcRect") } : null;
3210
+ }
3211
+ }
3212
+ }
3213
+ return null;
3214
+ }
3215
+ function readLine(ln, theme, placeholder) {
3216
+ if (!ln) return void 0;
3217
+ const fill = readFill(ln, theme);
3218
+ if (fill?.kind === "none") return null;
3219
+ const color = fill?.kind === "solid" ? fill.color : fill?.kind === "gradient" ? fill.from : null;
3220
+ if (!color) return void 0;
3221
+ const dash = kid(ln, "prstDash")?.getAttribute("val");
3222
+ return {
3223
+ color,
3224
+ width: ln.hasAttribute("w") ? numAttr(ln, "w") / EMU_PER_POINT : 0.75,
3225
+ dash: !!dash && dash !== "solid"
3226
+ };
3227
+ }
3228
+ async function readTheme(pkg, master) {
3229
+ const themePart = await pkg.related(master, "/theme");
3230
+ const doc = themePart ? await pkg.xml(themePart) : null;
3231
+ const elements = path(doc?.documentElement, "themeElements");
3232
+ const scheme = {};
3233
+ const bare = {
3234
+ scheme,
3235
+ map: {}};
3236
+ for (const entry of kids(kid(elements, "clrScheme"))) {
3237
+ const color = colorIn(entry, bare);
3238
+ if (color) scheme[entry.localName] = color.hex.slice(1);
3239
+ }
3240
+ const fonts = kid(elements, "fontScheme");
3241
+ const typeface = (name) => path(fonts, name, "latin")?.getAttribute("typeface") || "Calibri";
3242
+ const masterDoc = await pkg.xml(master);
3243
+ const clrMap = kid(masterDoc?.documentElement, "clrMap");
3244
+ const map = {};
3245
+ for (const attr of Array.from(clrMap?.attributes ?? [])) map[attr.name] = attr.value;
3246
+ const lineWidths = kids(path(elements, "fmtScheme", "lnStyleLst"), "ln").map(
3247
+ (ln) => numAttr(ln, "w", 9525) / EMU_PER_POINT
3248
+ );
3249
+ return {
3250
+ scheme,
3251
+ map,
3252
+ majorFont: typeface("majorFont"),
3253
+ minorFont: typeface("minorFont"),
3254
+ lineWidths
3255
+ };
3256
+ }
3257
+ function typefaceOf(name, theme) {
3258
+ if (!name) return void 0;
3259
+ if (name.startsWith("+mj")) return theme.majorFont;
3260
+ if (name.startsWith("+mn")) return theme.minorFont;
3261
+ return name;
3262
+ }
3263
+ async function mediaDataUrl(pkg, part) {
3264
+ const ext = part.split(".").pop()?.toLowerCase() ?? "";
3265
+ const mime = ext === "png" ? "image/png" : ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "gif" ? "image/gif" : ext === "svg" ? "image/svg+xml" : ext === "webp" ? "image/webp" : ext === "bmp" ? "image/bmp" : null;
3266
+ if (!mime) return null;
3267
+ const bytes = await pkg.bytes(part);
3268
+ if (!bytes) return null;
3269
+ let binary = "";
3270
+ for (let i = 0; i < bytes.length; i += 32768) {
3271
+ binary += String.fromCharCode(...bytes.subarray(i, i + 32768));
3272
+ }
3273
+ return `data:${mime};base64,${btoa(binary)}`;
3274
+ }
3275
+
3276
+ // src/presentation/import-pptx.ts
3277
+ var IDENTITY = { sx: 1, sy: 1, ox: 0, oy: 0 };
3278
+ var TITLE_TYPES = /* @__PURE__ */ new Set(["title", "ctrTitle"]);
3279
+ var FOOTER_TYPES = /* @__PURE__ */ new Set(["dt", "ftr", "sldNum", "hdr"]);
3280
+ function placeholderOf(shape) {
3281
+ const nv = kids(shape).find((child) => child.localName.startsWith("nv"));
3282
+ const ph = path(nv, "nvPr", "ph");
3283
+ if (!ph) return null;
3284
+ return { type: ph.getAttribute("type") ?? "body", idx: ph.getAttribute("idx") };
3285
+ }
3286
+ function findPlaceholder(tree, ph, master) {
3287
+ const shapes = kids(tree).filter((child) => child.localName === "sp");
3288
+ const typeOf = (type) => master ? TITLE_TYPES.has(type) ? "title" : FOOTER_TYPES.has(type) ? type : "body" : type;
3289
+ if (!master && ph.idx !== null) {
3290
+ const byIdx = shapes.find((shape) => placeholderOf(shape)?.idx === ph.idx);
3291
+ if (byIdx) return byIdx;
3292
+ }
3293
+ const wanted = typeOf(ph.type);
3294
+ return shapes.find((shape) => {
3295
+ const other = placeholderOf(shape);
3296
+ return other !== null && typeOf(other.type) === wanted;
3297
+ }) ?? null;
3298
+ }
3299
+ var ALIGN = { l: "left", ctr: "center", r: "right", just: "justify" };
3300
+ function readRunProps(rPr, theme) {
3301
+ if (!rPr) return {};
3302
+ const style = {};
3303
+ if (rPr.hasAttribute("sz")) style.size = numAttr(rPr, "sz") / 100;
3304
+ const b = boolAttr(rPr, "b");
3305
+ if (b !== void 0) style.bold = b;
3306
+ const i = boolAttr(rPr, "i");
3307
+ if (i !== void 0) style.italic = i;
3308
+ const u = rPr.getAttribute("u");
3309
+ if (u) style.underline = u !== "none";
3310
+ const fill = readFill(rPr, theme);
3311
+ if (fill?.kind === "solid") style.color = fill.color;
3312
+ else if (fill?.kind === "gradient") style.color = fill.from;
3313
+ const font = typefaceOf(kid(rPr, "latin")?.getAttribute("typeface"), theme);
3314
+ if (font) style.font = font;
3315
+ return style;
3316
+ }
3317
+ function readLevel(pPr, theme) {
3318
+ if (!pPr) return {};
3319
+ const style = { ...readRunProps(kid(pPr, "defRPr"), theme) };
3320
+ const algn = pPr.getAttribute("algn");
3321
+ if (algn && ALIGN[algn]) style.align = ALIGN[algn];
3322
+ if (kid(pPr, "buNone")) style.bullet = false;
3323
+ else if (kid(pPr, "buChar") || kid(pPr, "buAutoNum") || kid(pPr, "buBlip")) style.bullet = true;
3324
+ return style;
3325
+ }
3326
+ function merge(styles) {
3327
+ const out = {};
3328
+ for (const style of styles) {
3329
+ for (const key of Object.keys(style)) {
3330
+ if (out[key] === void 0 && style[key] !== void 0) {
3331
+ out[key] = style[key];
3332
+ }
3333
+ }
3334
+ }
3335
+ return out;
3336
+ }
3337
+ function levelStyle(chain, level, theme) {
3338
+ return merge(chain.map((list) => readLevel(kid(list, `lvl${level + 1}pPr`), theme)));
3339
+ }
3340
+ var note = (deck, text) => deck.notes.set(text, (deck.notes.get(text) ?? 0) + 1);
3341
+ function hash(text) {
3342
+ let h = 2166136261;
3343
+ for (let i = 0; i < text.length; i++) {
3344
+ h ^= text.charCodeAt(i);
3345
+ h = Math.imul(h, 16777619);
3346
+ }
3347
+ return (h >>> 0).toString(36);
3348
+ }
3349
+ async function mediaKey(deck, part) {
3350
+ if (deck.mediaKeys.has(part)) return deck.mediaKeys.get(part) ?? null;
3351
+ const url = await mediaDataUrl(deck.pkg, part);
3352
+ const key = url ? `m${hash(url)}` : null;
3353
+ if (url && key) deck.media[key] = url;
3354
+ deck.mediaKeys.set(part, key);
3355
+ return key;
3356
+ }
3357
+ function areaOf(xfrm, transform) {
3358
+ const off = kid(xfrm, "off");
3359
+ const ext = kid(xfrm, "ext");
3360
+ if (!off || !ext) return null;
3361
+ return {
3362
+ x: transform.ox + numAttr(off, "x") * transform.sx,
3363
+ y: transform.oy + numAttr(off, "y") * transform.sy,
3364
+ w: numAttr(ext, "cx") * transform.sx,
3365
+ h: numAttr(ext, "cy") * transform.sy,
3366
+ rotation: numAttr(xfrm, "rot") / 6e4,
3367
+ flipH: boolAttr(xfrm, "flipH") === true,
3368
+ flipV: boolAttr(xfrm, "flipV") === true
3369
+ };
3370
+ }
3371
+ function stageFrame(deck, area) {
3372
+ const { cx, cy } = deck.slideSize;
3373
+ const { place } = deck;
3374
+ return {
3375
+ x: place.x + area.x / cx * place.w,
3376
+ y: place.y + area.y / cy * place.h,
3377
+ w: area.w / cx * place.w,
3378
+ h: area.h / cy * place.h
3379
+ };
3380
+ }
3381
+ var hexOf = (color) => color.hex;
3382
+ function renditionClip(deck, part, area, what) {
3383
+ const { documentId, version, fileName } = deck.options;
3384
+ if (!documentId || part.page === null) {
3385
+ note(deck, `${what} could not be shown`);
3386
+ return null;
3387
+ }
3388
+ const { cx, cy } = deck.slideSize;
3389
+ const clamp = (v) => Math.min(1, Math.max(0, v));
3390
+ const x = clamp(area.x / cx);
3391
+ const y = clamp(area.y / cy);
3392
+ const rect = { x, y, w: clamp((area.x + area.w) / cx) - x, h: clamp((area.y + area.h) / cy) - y };
3393
+ if (rect.w <= 0 || rect.h <= 0) return null;
3394
+ const frame = stageFrame(deck, area);
3395
+ note(deck, `${what} shown as a picture`);
3396
+ deck.clips.push({
3397
+ kind: "pdf",
3398
+ documentId,
3399
+ ...version !== void 0 ? { version } : {},
3400
+ fileName: fileName ?? "Presentation.pptx",
3401
+ page: part.page,
3402
+ rect,
3403
+ aspect: frame.w * chunkXTR26TNE_cjs.STAGE_ASPECT / Math.max(frame.h, 1e-6),
3404
+ caption: "",
3405
+ plain: true
3406
+ });
3407
+ return deck.clips.length - 1;
3408
+ }
3409
+ function readParagraphs(body, chain, deck, defaults, fontScale, fallback = []) {
3410
+ const { theme } = deck;
3411
+ const paragraphs = [];
3412
+ for (const p of kids(body, "p")) {
3413
+ const pPr = kid(p, "pPr");
3414
+ const level = Math.min(8, numAttr(pPr, "lvl"));
3415
+ const own = readLevel(pPr, theme);
3416
+ const style = merge([
3417
+ own,
3418
+ levelStyle(chain, level, theme),
3419
+ defaults.color ? { color: defaults.color } : {},
3420
+ levelStyle(fallback, level, theme)
3421
+ ]);
3422
+ const runStyle2 = (rPr) => {
3423
+ const s = merge([readRunProps(rPr, theme), style]);
3424
+ const color = s.color ?? defaults.base;
3425
+ return {
3426
+ text: "",
3427
+ size: Math.round((s.size ?? 18) * fontScale * deck.pointScale * 10) / 10,
3428
+ ...s.bold ? { bold: true } : {},
3429
+ ...s.italic ? { italic: true } : {},
3430
+ ...s.underline ? { underline: true } : {},
3431
+ ...color ? { color: hexOf(color) } : {},
3432
+ font: s.font ?? theme.minorFont
3433
+ };
3434
+ };
3435
+ const shape = (runs2) => ({
3436
+ runs: runs2,
3437
+ ...style.align && style.align !== "left" ? { align: style.align } : {},
3438
+ ...level ? { level } : {},
3439
+ ...style.bullet && runs2.some((run) => run.text.trim()) ? { bullet: true } : {}
3440
+ });
3441
+ let runs = [];
3442
+ for (const child of kids(p)) {
3443
+ if (child.localName === "r" || child.localName === "fld") {
3444
+ const text = kid(child, "t")?.textContent ?? "";
3445
+ runs.push({ ...runStyle2(kid(child, "rPr")), text });
3446
+ } else if (child.localName === "br") {
3447
+ paragraphs.push(shape(runs.length ? runs : [{ ...runStyle2(kid(child, "rPr")), text: "" }]));
3448
+ runs = [];
3449
+ }
3450
+ }
3451
+ if (!runs.length) runs = [{ ...runStyle2(kid(p, "endParaRPr")), text: "" }];
3452
+ paragraphs.push(shape(runs));
3453
+ }
3454
+ while (paragraphs.length > 1 && !paragraphs[paragraphs.length - 1].runs.some((run) => run.text.trim())) {
3455
+ paragraphs.pop();
3456
+ }
3457
+ return paragraphs;
3458
+ }
3459
+ var SHAPES = {
3460
+ rect: "rect",
3461
+ flowChartProcess: "rect",
3462
+ snip1Rect: "rect",
3463
+ roundRect: "roundRect",
3464
+ round1Rect: "roundRect",
3465
+ round2SameRect: "roundRect",
3466
+ flowChartAlternateProcess: "roundRect",
3467
+ ellipse: "ellipse",
3468
+ flowChartConnector: "ellipse",
3469
+ line: "line",
3470
+ straightConnector1: "line"
3471
+ };
3472
+ async function readShape(shape, transform, deck, part, inherit, built) {
3473
+ const { theme } = deck;
3474
+ const name = shape.localName;
3475
+ if (name === "AlternateContent") {
3476
+ const choice = kid(shape, "Fallback") ?? kid(shape, "Choice");
3477
+ for (const child of kids(choice)) await readShape(child, transform, deck, part, inherit, built);
3478
+ return;
3479
+ }
3480
+ if (name === "grpSp") {
3481
+ const xfrm2 = path(shape, "grpSpPr", "xfrm");
3482
+ const off = kid(xfrm2, "off");
3483
+ const ext = kid(xfrm2, "ext");
3484
+ const chOff = kid(xfrm2, "chOff");
3485
+ const chExt = kid(xfrm2, "chExt");
3486
+ let inner = transform;
3487
+ if (off && ext && chOff && chExt) {
3488
+ const sx = numAttr(chExt, "cx") ? numAttr(ext, "cx") / numAttr(chExt, "cx") : 1;
3489
+ const sy = numAttr(chExt, "cy") ? numAttr(ext, "cy") / numAttr(chExt, "cy") : 1;
3490
+ inner = {
3491
+ sx: transform.sx * sx,
3492
+ sy: transform.sy * sy,
3493
+ ox: transform.ox + (numAttr(off, "x") - numAttr(chOff, "x") * sx) * transform.sx,
3494
+ oy: transform.oy + (numAttr(off, "y") - numAttr(chOff, "y") * sy) * transform.sy
3495
+ };
3496
+ }
3497
+ for (const child of kids(shape)) await readShape(child, inner, deck, part, inherit, built);
3498
+ return;
3499
+ }
3500
+ const ph = name === "sp" || name === "pic" ? placeholderOf(shape) : null;
3501
+ if (ph && !inherit) return;
3502
+ if (ph && FOOTER_TYPES.has(ph.type)) return;
3503
+ const layoutPh = ph && inherit ? findPlaceholder(inherit.layoutTree, ph, false) : null;
3504
+ const masterPh = ph && inherit ? findPlaceholder(inherit.masterTree, ph, true) : null;
3505
+ const spPr = kid(shape, name === "graphicFrame" ? "xfrm" : "spPr");
3506
+ const xfrm = name === "graphicFrame" ? kid(shape, "xfrm") : kid(spPr, "xfrm") ?? path(layoutPh, "spPr", "xfrm") ?? path(masterPh, "spPr", "xfrm");
3507
+ const area = areaOf(xfrm, transform);
3508
+ if (!area) return;
3509
+ if (name === "pic") {
3510
+ const blipFill = kid(shape, "blipFill");
3511
+ const embed = relAttr(kid(blipFill, "blip"), "embed");
3512
+ const target = embed ? (await deck.pkg.relationships(part.part)).get(embed) : void 0;
3513
+ const key = target && !target.external ? await mediaKey(deck, target.target) : null;
3514
+ if (!key || Math.abs(area.rotation) > 0.5 || area.flipH || area.flipV) {
3515
+ const clip = renditionClip(deck, part, area, "A picture");
3516
+ if (clip !== null) {
3517
+ const frame2 = stageFrame(deck, area);
3518
+ built.items.push({ clip, frame: frame2, after: built.boxes.length });
3519
+ }
3520
+ return;
3521
+ }
3522
+ const src = kid(blipFill, "srcRect");
3523
+ const l = numAttr(src, "l") / 1e5;
3524
+ const t = numAttr(src, "t") / 1e5;
3525
+ const r = numAttr(src, "r") / 1e5;
3526
+ const b = numAttr(src, "b") / 1e5;
3527
+ const rect = {
3528
+ x: Math.max(0, l),
3529
+ y: Math.max(0, t),
3530
+ w: Math.min(1, 1 - r) - Math.max(0, l),
3531
+ h: Math.min(1, 1 - b) - Math.max(0, t)
3532
+ };
3533
+ if (rect.w <= 0 || rect.h <= 0) return;
3534
+ if (kid(path(shape, "nvPicPr", "nvPr"), "videoFile")) note(deck, "A video shown as its picture");
3535
+ const frame = stageFrame(deck, area);
3536
+ deck.clips.push({
3537
+ kind: "image",
3538
+ documentId: "",
3539
+ fileName: target?.target.split("/").pop() ?? "Picture",
3540
+ page: 1,
3541
+ rect,
3542
+ aspect: frame.w * chunkXTR26TNE_cjs.STAGE_ASPECT / Math.max(frame.h, 1e-6),
3543
+ caption: "",
3544
+ media: key,
3545
+ plain: true
3546
+ });
3547
+ built.items.push({ clip: deck.clips.length - 1, frame, after: built.boxes.length });
3548
+ return;
3549
+ }
3550
+ if (name === "graphicFrame") {
3551
+ const data = path(shape, "graphic", "graphicData");
3552
+ const table = kid(data, "tbl");
3553
+ if (table) {
3554
+ readTable(table, area, deck, built);
3555
+ return;
3556
+ }
3557
+ const uri = data?.getAttribute("uri") ?? "";
3558
+ const what = uri.includes("chart") ? "A chart" : uri.includes("diagram") ? "A SmartArt graphic" : "An embedded object";
3559
+ const clip = renditionClip(deck, part, area, what);
3560
+ if (clip !== null)
3561
+ built.items.push({ clip, frame: stageFrame(deck, area), after: built.boxes.length });
3562
+ return;
3563
+ }
3564
+ if (name !== "sp" && name !== "cxnSp") return;
3565
+ const style = kid(shape, "style");
3566
+ const prst = path(spPr, "prstGeom")?.getAttribute("prst") ?? (kid(spPr, "custGeom") ? "custom" : "rect");
3567
+ let fill = readFill(spPr, theme);
3568
+ const fillRef = kid(style, "fillRef");
3569
+ if (!fill && fillRef && numAttr(fillRef, "idx") > 0) {
3570
+ const color = colorIn(fillRef, theme);
3571
+ if (color) fill = { kind: "solid", color };
3572
+ }
3573
+ if (!fill && ph)
3574
+ fill = readFill(kid(layoutPh, "spPr"), theme) ?? readFill(kid(masterPh, "spPr"), theme);
3575
+ let line = readLine(kid(spPr, "ln"), theme);
3576
+ const lnRef = kid(style, "lnRef");
3577
+ if (line === void 0 && lnRef && numAttr(lnRef, "idx") > 0) {
3578
+ const color = colorIn(lnRef, theme);
3579
+ if (color)
3580
+ line = { color, width: theme.lineWidths[numAttr(lnRef, "idx") - 1] ?? 0.75, dash: false };
3581
+ }
3582
+ if (line === void 0 && name === "cxnSp") line = null;
3583
+ const body = kid(shape, "txBody");
3584
+ const bodyPr = kid(body, "bodyPr");
3585
+ const inheritedBodyPr = [
3586
+ bodyPr,
3587
+ path(layoutPh, "txBody", "bodyPr"),
3588
+ path(masterPh, "txBody", "bodyPr")
3589
+ ];
3590
+ const category = !ph ? "otherStyle" : TITLE_TYPES.has(ph.type) ? "titleStyle" : "bodyStyle";
3591
+ const chain = [
3592
+ kid(body, "lstStyle"),
3593
+ path(layoutPh, "txBody", "lstStyle"),
3594
+ path(masterPh, "txBody", "lstStyle"),
3595
+ ph ? kid(inherit?.masterTxStyles, category) : null
3596
+ ];
3597
+ const fontRef = kid(style, "fontRef");
3598
+ const tx1 = theme.scheme[theme.map.tx1 ?? "dk1"];
3599
+ const fontScale = numAttr(kid(bodyPr, "normAutofit"), "fontScale", 1e5) / 1e5;
3600
+ const paragraphs = body ? readParagraphs(
3601
+ body,
3602
+ chain,
3603
+ deck,
3604
+ {
3605
+ color: colorIn(fontRef, theme) ?? void 0,
3606
+ base: tx1 ? { hex: `#${tx1}`, alpha: 1 } : void 0
3607
+ },
3608
+ fontScale,
3609
+ [deck.defaultText]
3610
+ ) : [];
3611
+ const hasText = paragraphs.some((p) => p.runs.some((run) => run.text.trim()));
3612
+ const visibleFill = fill && fill.kind !== "none" && fill.kind !== "image";
3613
+ if (!hasText && !visibleFill && !line && fill?.kind !== "image") return;
3614
+ let shapeKind = SHAPES[prst] ?? (name === "cxnSp" ? "line" : "rect");
3615
+ const exotic = !SHAPES[prst] && name !== "cxnSp";
3616
+ if (exotic && (visibleFill || line) || fill?.kind === "image") {
3617
+ const clip = renditionClip(deck, part, area, exotic ? "A shape" : "A picture fill");
3618
+ if (clip !== null) {
3619
+ built.items.push({ clip, frame: stageFrame(deck, area), after: built.boxes.length });
3620
+ if (!hasText) return;
3621
+ fill = null;
3622
+ line = null;
3623
+ shapeKind = "text";
3624
+ }
3625
+ }
3626
+ if (shapeKind === "rect" && !visibleFill && !line) shapeKind = "text";
3627
+ const anchorOf = (value) => value === "ctr" ? "middle" : value === "b" ? "bottom" : value === "t" ? "top" : void 0;
3628
+ const anchor = inheritedBodyPr.map((pr) => anchorOf(pr?.getAttribute("anchor"))).find(Boolean) ?? (ph && TITLE_TYPES.has(ph.type) ? "middle" : "top");
3629
+ const insetPr = inheritedBodyPr.find((pr) => pr?.hasAttribute("lIns"));
3630
+ const inset = numAttr(insetPr, "lIns", 91440) / EMU_PER_POINT;
3631
+ const shapeName = path(shape, "nvSpPr", "cNvPr")?.getAttribute("name") ?? "";
3632
+ const role = ph ? TITLE_TYPES.has(ph.type) ? "title" : "body" : shapeName === "ARBI title" ? "title" : shapeName === "ARBI body" ? "body" : void 0;
3633
+ const box2 = {
3634
+ frame: stageFrame(deck, area),
3635
+ shape: shapeKind,
3636
+ paragraphs: hasText ? paragraphs : [],
3637
+ ...Math.abs(area.rotation) > 0.01 ? { rotation: area.rotation } : {},
3638
+ ...area.flipH ? { flipH: true } : {},
3639
+ ...area.flipV ? { flipV: true } : {},
3640
+ ...fill?.kind === "solid" ? { fill: fill.color.hex, ...fill.color.alpha < 1 ? { fillAlpha: fill.color.alpha } : {} } : fill?.kind === "gradient" ? { fill: fill.from.hex } : {},
3641
+ ...line ? { line: { color: line.color.hex, width: line.width, ...line.dash ? { dash: true } : {} } } : {},
3642
+ ...anchor !== "top" ? { anchor } : {},
3643
+ ...Math.abs(inset - 7.2) > 0.05 ? { inset: inset * deck.pointScale } : {},
3644
+ ...role ? { role } : {}
3645
+ };
3646
+ built.boxes.push(box2);
3647
+ }
3648
+ function readTable(table, area, deck, built) {
3649
+ const { theme } = deck;
3650
+ const tblPr = kid(table, "tblPr");
3651
+ const styled = !!kid(tblPr, "tableStyleId");
3652
+ const firstRow = boolAttr(tblPr, "firstRow") === true;
3653
+ const banded = boolAttr(tblPr, "bandRow") === true;
3654
+ const accent = `#${theme.scheme.accent1 ?? "4472c4"}`;
3655
+ const tint = (hex3, amount) => `#${[1, 3, 5].map(
3656
+ (i) => Math.round(255 - (255 - parseInt(hex3.slice(i, i + 2), 16)) * amount).toString(16).padStart(2, "0")
3657
+ ).join("")}`;
3658
+ const columns = kids(kid(table, "tblGrid"), "gridCol").map((col) => numAttr(col, "w"));
3659
+ const totalW = columns.reduce((sum, w) => sum + w, 0) || area.w;
3660
+ const rows = kids(table, "tr");
3661
+ const totalH = rows.reduce((sum, row) => sum + numAttr(row, "h"), 0) || area.h;
3662
+ const scaleY = area.h / totalH;
3663
+ let y = area.y;
3664
+ rows.forEach((row, r) => {
3665
+ const h = numAttr(row, "h") * scaleY;
3666
+ let x = area.x;
3667
+ let c = 0;
3668
+ for (const cell of kids(row, "tc")) {
3669
+ const span = Math.max(1, numAttr(cell, "gridSpan", 1));
3670
+ const w = columns.slice(c, c + span).reduce((sum, cw) => sum + cw, 0) * (area.w / totalW);
3671
+ c += span;
3672
+ if (boolAttr(cell, "hMerge") || boolAttr(cell, "vMerge")) {
3673
+ x += w;
3674
+ continue;
3675
+ }
3676
+ const rowSpan = Math.max(1, numAttr(cell, "rowSpan", 1));
3677
+ const cellH = rows.slice(r, r + rowSpan).reduce((sum, rr) => sum + numAttr(rr, "h") * scaleY, 0);
3678
+ const tcPr = kid(cell, "tcPr");
3679
+ const own = readFill(tcPr, theme);
3680
+ const header = styled && firstRow && r === 0;
3681
+ const fill = own?.kind === "solid" ? own.color.hex : own?.kind === "none" ? void 0 : styled ? header ? accent : banded && (r - (firstRow ? 1 : 0)) % 2 === 0 ? tint(accent, 0.2) : tint(accent, 0.1) : void 0;
3682
+ const text = readParagraphs(
3683
+ kid(cell, "txBody"),
3684
+ // The table's style sets its text colour, over the deck's default text.
3685
+ [kid(kid(cell, "txBody"), "lstStyle")],
3686
+ deck,
3687
+ {
3688
+ color: {
3689
+ hex: header ? "#ffffff" : `#${theme.scheme[theme.map.tx1 ?? "dk1"] ?? "000000"}`,
3690
+ alpha: 1
3691
+ }
3692
+ },
3693
+ 1
3694
+ ).map((p) => header ? { ...p, runs: p.runs.map((run) => ({ ...run, bold: true })) } : p);
3695
+ const border = readLine(kid(tcPr, "lnB"), theme) ?? readLine(kid(tcPr, "lnT"), theme);
3696
+ built.boxes.push({
3697
+ frame: stageFrame(deck, { x, y, w, h: cellH }),
3698
+ shape: fill || border || styled ? "rect" : "text",
3699
+ paragraphs: text,
3700
+ ...fill ? { fill } : {},
3701
+ ...border ? { line: { color: border.color.hex, width: border.width } } : styled ? { line: { color: "#ffffff", width: 1 } } : {},
3702
+ anchor: tcPr?.getAttribute("anchor") === "ctr" ? "middle" : "top",
3703
+ inset: 7.2 * deck.pointScale
3704
+ });
3705
+ x += w;
3706
+ }
3707
+ y += h;
3708
+ });
3709
+ }
3710
+ async function readBackground(bg, deck, part) {
3711
+ if (!bg) return null;
3712
+ const bgPr = kid(bg, "bgPr");
3713
+ const bgRef = kid(bg, "bgRef");
3714
+ const fill = bgPr ? readFill(bgPr, deck.theme) : null;
3715
+ if (fill?.kind === "solid") return { color: fill.color.hex };
3716
+ if (fill?.kind === "gradient") {
3717
+ return { gradient: { from: fill.from.hex, to: fill.to.hex, angle: fill.angle } };
3718
+ }
3719
+ if (fill?.kind === "image") {
3720
+ const target = (await deck.pkg.relationships(part)).get(fill.embed);
3721
+ const key = target && !target.external ? await mediaKey(deck, target.target) : null;
3722
+ return key ? { image: key } : null;
3723
+ }
3724
+ if (bgRef) {
3725
+ const color = colorIn(bgRef, deck.theme);
3726
+ if (color) return { color: color.hex };
3727
+ }
3728
+ return null;
3729
+ }
3730
+ async function slideParts(pkg) {
3731
+ const pres = await pkg.xml("ppt/presentation.xml");
3732
+ const rels = await pkg.relationships("ppt/presentation.xml");
3733
+ const parts = [];
3734
+ for (const id of kids(kid(pres?.documentElement, "sldIdLst"), "sldId")) {
3735
+ const rel = rels.get(relAttr(id, "id") ?? "");
3736
+ if (!rel) continue;
3737
+ const slide = await pkg.xml(rel.target);
3738
+ parts.push({ part: rel.target, hidden: slide?.documentElement.getAttribute("show") === "0" });
3739
+ }
3740
+ return parts;
3741
+ }
3742
+ async function speakerNotes(pkg, part) {
3743
+ const notesPart = await pkg.related(part, "/notesSlide");
3744
+ const doc = notesPart ? await pkg.xml(notesPart) : null;
3745
+ const tree = path(doc?.documentElement, "cSld", "spTree");
3746
+ for (const shape of kids(tree, "sp")) {
3747
+ const ph = placeholderOf(shape);
3748
+ if (ph?.type !== "body") continue;
3749
+ return kids(kid(shape, "txBody"), "p").map(
3750
+ (p) => kids(p).filter((child) => child.localName === "r" || child.localName === "fld").map((run) => kid(run, "t")?.textContent ?? "").join("")
3751
+ ).join("\n").trim();
3752
+ }
3753
+ return "";
3754
+ }
3755
+ var JSZIP = () => import('jszip').then((module) => module.default);
3756
+ async function importPptx(bytes, options = {}) {
3757
+ const JSZip = await JSZIP();
3758
+ const pkg = new Package(await JSZip.loadAsync(bytes));
3759
+ const pres = await pkg.xml("ppt/presentation.xml");
3760
+ if (!pres) throw new Error("This file is not a PowerPoint presentation.");
3761
+ const size = kid(pres.documentElement, "sldSz");
3762
+ const cx = numAttr(size, "cx", 12192e3);
3763
+ const cy = numAttr(size, "cy", 6858e3);
3764
+ const deckAspect = cx / cy;
3765
+ const place = deckAspect < chunkXTR26TNE_cjs.STAGE_ASPECT ? { w: deckAspect / chunkXTR26TNE_cjs.STAGE_ASPECT, h: 1, x: (1 - deckAspect / chunkXTR26TNE_cjs.STAGE_ASPECT) / 2, y: 0 } : { w: 1, h: chunkXTR26TNE_cjs.STAGE_ASPECT / deckAspect, x: 0, y: (1 - chunkXTR26TNE_cjs.STAGE_ASPECT / deckAspect) / 2 };
3766
+ const slides = await slideParts(pkg);
3767
+ const firstLayout = slides.length ? await pkg.related(slides[0].part, "/slideLayout") : null;
3768
+ const firstMaster = firstLayout ? await pkg.related(firstLayout, "/slideMaster") : null;
3769
+ const deck = {
3770
+ pkg,
3771
+ theme: firstMaster ? await readTheme(pkg, firstMaster) : { scheme: {}, map: {}, majorFont: "Calibri", minorFont: "Calibri", lineWidths: [] },
3772
+ slideSize: { cx, cy },
3773
+ place,
3774
+ pointScale: chunkXTR26TNE_cjs.SLIDE_POINTS.h * place.h / (cy / EMU_PER_POINT),
3775
+ defaultText: kid(pres.documentElement, "defaultTextStyle"),
3776
+ options,
3777
+ clips: [],
3778
+ media: {},
3779
+ mediaKeys: /* @__PURE__ */ new Map(),
3780
+ notes: /* @__PURE__ */ new Map()
3781
+ };
3782
+ const saved = [];
3783
+ let page = 0;
3784
+ for (const { part, hidden } of slides) {
3785
+ if (!hidden) page += 1;
3786
+ const layout = await pkg.related(part, "/slideLayout");
3787
+ const master = layout ? await pkg.related(layout, "/slideMaster") : null;
3788
+ if (master && master !== firstMaster) deck.theme = await readTheme(pkg, master);
3789
+ const slideDoc = await pkg.xml(part);
3790
+ const layoutDoc = layout ? await pkg.xml(layout) : null;
3791
+ const masterDoc = master ? await pkg.xml(master) : null;
3792
+ const tree = (doc) => path(doc?.documentElement, "cSld", "spTree");
3793
+ const built = { boxes: [], items: [] };
3794
+ const partContext = { part, page: hidden ? null : page };
3795
+ const showMaster = (doc) => doc?.documentElement.getAttribute("showMasterSp") !== "0";
3796
+ if (masterDoc && showMaster(slideDoc) && showMaster(layoutDoc)) {
3797
+ for (const shape of kids(tree(masterDoc))) {
3798
+ await readShape(
3799
+ shape,
3800
+ IDENTITY,
3801
+ deck,
3802
+ { part: master, page: partContext.page },
3803
+ null,
3804
+ built
3805
+ );
3806
+ }
3807
+ }
3808
+ if (layoutDoc && showMaster(slideDoc)) {
3809
+ for (const shape of kids(tree(layoutDoc))) {
3810
+ await readShape(
3811
+ shape,
3812
+ IDENTITY,
3813
+ deck,
3814
+ { part: layout, page: partContext.page },
3815
+ null,
3816
+ built
3817
+ );
3818
+ }
3819
+ }
3820
+ const inherit = {
3821
+ layoutTree: tree(layoutDoc),
3822
+ masterTree: tree(masterDoc),
3823
+ masterTxStyles: kid(masterDoc?.documentElement, "txStyles")
3824
+ };
3825
+ for (const shape of kids(tree(slideDoc))) {
3826
+ await readShape(shape, IDENTITY, deck, partContext, inherit, built);
3827
+ }
3828
+ const bg = await readBackground(path(slideDoc?.documentElement, "cSld", "bg"), deck, part) ?? (layout ? await readBackground(path(layoutDoc?.documentElement, "cSld", "bg"), deck, layout) : null) ?? (master ? await readBackground(path(masterDoc?.documentElement, "cSld", "bg"), deck, master) : null) ?? {
3829
+ color: "#ffffff"
3830
+ };
3831
+ if (hidden) note(deck, "A hidden slide");
3832
+ const firstClipAt = built.items.length ? Math.min(...built.items.map((item) => item.after)) : Infinity;
3833
+ const boxes = built.boxes.map(
3834
+ (box2, index) => index >= firstClipAt ? { ...box2, front: true } : box2
3835
+ );
3836
+ const title = boxes.find((box2) => box2.role === "title");
3837
+ const notes = await speakerNotes(pkg, part);
3838
+ saved.push({
3839
+ caption: title ? title.paragraphs.map((p) => p.runs.map((run) => run.text).join("")).join(" ").replace(/\s+/g, " ").trim() : "",
3840
+ auto: false,
3841
+ layout: "free",
3842
+ items: built.items.map(({ clip, frame }) => ({
3843
+ clip,
3844
+ frame: { x: frame.x, y: frame.y, w: frame.w }
3845
+ })),
3846
+ boxes,
3847
+ background: bg,
3848
+ ...notes ? { notes } : {}
3849
+ });
3850
+ }
3851
+ for (const part of Object.keys(pkg.zip.files)) {
3852
+ if (/^ppt\/slides\/slide\d+\.xml$/.test(part)) {
3853
+ const xml = await pkg.zip.file(part).async("string");
3854
+ if (/<p:timing\b/.test(xml)) {
3855
+ note(deck, "Animations are not kept");
3856
+ break;
3857
+ }
3858
+ }
3859
+ }
3860
+ const core = await pkg.xml("docProps/core.xml");
3861
+ const coreTitle = Array.from(core?.documentElement.childNodes ?? []).find(
3862
+ (node) => node instanceof Element && node.localName === "title"
3863
+ )?.textContent;
3864
+ return {
3865
+ saved: {
3866
+ format: "arbi-presentation",
3867
+ version: 1,
3868
+ title: options.title ?? (coreTitle?.trim() || "Presentation"),
3869
+ captions: false,
3870
+ theme: chunkXTR26TNE_cjs.DEFAULT_THEME,
3871
+ clips: deck.clips,
3872
+ slides: saved,
3873
+ ...Object.keys(deck.media).length ? { media: deck.media } : {}
3874
+ },
3875
+ notes: [...deck.notes.entries()].map(
3876
+ ([text, count]) => count > 1 && !text.startsWith("Animations") ? `${text} (${count})` : text
3877
+ ),
3878
+ fingerprints: await fingerprintsOf(pkg, slides)
3879
+ };
3880
+ }
3881
+ async function fingerprintsOf(pkg, slides) {
3882
+ const out = [];
3883
+ for (const { part } of slides) {
3884
+ const doc = await pkg.xml(part);
3885
+ const tree = path(doc?.documentElement, "cSld", "spTree");
3886
+ const parts = [];
3887
+ const visit = (element) => {
3888
+ for (const child of kids(element)) {
3889
+ const name = child.localName;
3890
+ if (name === "t") parts.push(child.textContent ?? "");
3891
+ else if (name === "off") {
3892
+ parts.push(
3893
+ `@${Math.round(numAttr(child, "x") / 12e4)},${Math.round(numAttr(child, "y") / 68e3)}`
3894
+ );
3895
+ } else if (name === "blip") parts.push("pic");
3896
+ else visit(child);
3897
+ }
3898
+ };
3899
+ if (tree) visit(tree);
3900
+ parts.push(`notes:${await speakerNotes(pkg, part)}`);
3901
+ out.push(hash(parts.join("|").replace(/\s+/g, " ")));
3902
+ }
3903
+ return out;
3904
+ }
3905
+ async function slideFingerprints(bytes) {
3906
+ const JSZip = await JSZIP();
3907
+ const pkg = new Package(await JSZip.loadAsync(bytes));
3908
+ return fingerprintsOf(pkg, await slideParts(pkg));
3909
+ }
3910
+
3911
+ // src/presentation/merge-outside-edits.ts
3912
+ function mergeOutsideEdits(saved, imported, fingerprints) {
3913
+ const used = /* @__PURE__ */ new Set();
3914
+ const offset = saved.clips.length;
3915
+ const changed = [];
3916
+ const slides = fingerprints.map((fingerprint, index) => {
3917
+ const own = saved.slides.findIndex(
3918
+ (slide2, i) => !used.has(i) && slide2.fingerprint === fingerprint
3919
+ );
3920
+ if (own >= 0) {
3921
+ used.add(own);
3922
+ return saved.slides[own];
3923
+ }
3924
+ changed.push(index + 1);
3925
+ const slide = imported.slides[index];
3926
+ return {
3927
+ ...slide,
3928
+ items: slide.items.map((item) => ({ ...item, clip: item.clip + offset })),
3929
+ fingerprint
3930
+ };
3931
+ });
3932
+ return {
3933
+ saved: {
3934
+ ...saved,
3935
+ clips: [...saved.clips, ...imported.clips],
3936
+ slides,
3937
+ ...saved.media || imported.media ? { media: { ...saved.media, ...imported.media } } : {}
3938
+ },
3939
+ changed
3940
+ };
3941
+ }
3942
+
3943
+ // src/connection/presentation/usePresentationFiles.ts
3944
+ var PPTX_TYPE = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
3945
+ var SLIDE_PAGE = { width: 1280, height: 720 };
3946
+ function dataUrlBlob(url) {
3947
+ const [head, body] = url.split(",", 2);
3948
+ const type = /^data:([^;]+)/.exec(head)?.[1] ?? "application/octet-stream";
3949
+ const binary = atob(body ?? "");
3950
+ const bytes = new Uint8Array(binary.length);
3951
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
3952
+ return new Blob([bytes], { type });
3953
+ }
3954
+ async function presentationData(bytes) {
3955
+ if (isZipBytes(bytes)) return readPptxPresentationData(bytes);
3956
+ const isPdf = bytes[0] === 37 && bytes[1] === 80 && bytes[2] === 68 && bytes[3] === 70;
3957
+ if (!isPdf) return new TextDecoder().decode(bytes);
3958
+ const data = await documentTools.readPdfAttachment(bytes, chunkXTR26TNE_cjs.PRESENTATION_ATTACHMENT);
3959
+ if (!data) {
3960
+ throw new Error(
3961
+ "This PDF has no editable presentation in it (it may have been changed outside ARBI)."
3962
+ );
3963
+ }
3964
+ return new TextDecoder().decode(data);
3965
+ }
3966
+ function current(withoutClips) {
3967
+ const state = chunkXTR26TNE_cjs.usePresentation.getState();
3968
+ return withoutClips ? {
3969
+ ...state,
3970
+ clips: [],
3971
+ slides: state.slides.map((slide) => ({ ...slide, items: [] }))
3972
+ } : state;
3973
+ }
3974
+ var lookOf = (state) => ({
3975
+ labels: state.captions,
3976
+ columnValues: state.columnValues,
3977
+ theme: state.theme,
3978
+ logo: state.logoImage,
3979
+ media: state.media
3980
+ });
3981
+ function usePresentationFiles() {
3982
+ const arbi = react$1.useArbi();
3983
+ const { client } = documentTools.useDocumentTools();
3984
+ const workspaceId = react$1.useArbiSessionState((s) => s.workspaceId);
3985
+ const buildFile = react.useCallback(async (title, withoutClips) => {
3986
+ const state = current(withoutClips);
3987
+ const deck = await buildPptx({ title, slides: state.slides, clips: state.clips }, lookOf(state));
3988
+ const fingerprints = await slideFingerprints(deck);
3989
+ const data = chunkXTR26TNE_cjs.serializePresentation({ ...state, title, fingerprints });
3990
+ return writePptxPresentationData(deck, JSON.stringify(data, null, 2));
3991
+ }, []);
3992
+ const save = react.useCallback(
3993
+ async (title, { asNew = false, template = false } = {}) => {
3994
+ if (!arbi || !workspaceId) throw new Error("Open a workspace to save the presentation.");
3995
+ const state = chunkXTR26TNE_cjs.usePresentation.getState();
3996
+ const versionOf = asNew || template ? null : state.savedId;
3997
+ const deck = await buildFile(title, template);
3998
+ const file = new File([deck], chunkXTR26TNE_cjs.presentationFileName(title), { type: PPTX_TYPE });
3999
+ const result = await arbi.documents.upload([file], {
4000
+ folder: template ? chunkXTR26TNE_cjs.TEMPLATES_FOLDER : chunkXTR26TNE_cjs.PRESENTATIONS_FOLDER,
4001
+ workspaceExtId: workspaceId,
4002
+ ...versionOf ? { versionOf } : {}
4003
+ });
4004
+ const id = versionOf ?? result.doc_ext_ids?.[0];
4005
+ if (!id) throw new Error("The presentation could not be saved.");
4006
+ if (!template) {
4007
+ const [doc] = await arbi.documents.get([id]).catch(() => []);
4008
+ state.setSaved(id, title, doc?.current_version ?? null);
4009
+ }
4010
+ return id;
4011
+ },
4012
+ [arbi, workspaceId, buildFile]
4013
+ );
4014
+ const exportPdf = react.useCallback(
4015
+ async (title) => {
4016
+ if (!arbi || !workspaceId) throw new Error("Open a workspace to export the PDF.");
4017
+ const state = current(false);
4018
+ const pages = [];
4019
+ for (const slide of state.slides) {
4020
+ const { image, text } = await renderSlide(slide, state.clips, lookOf(state));
4021
+ pages.push({ image, text, width: SLIDE_PAGE.width, height: SLIDE_PAGE.height });
4022
+ }
4023
+ const pdf = await documentTools.imagesToPdf(pages, { title });
4024
+ const result = await arbi.documents.upload(
4025
+ [new File([pdf], `${chunkXTR26TNE_cjs.safeFileStem(title)}.pdf`, { type: "application/pdf" })],
4026
+ { folder: chunkXTR26TNE_cjs.EXPORTS_FOLDER, workspaceExtId: workspaceId }
4027
+ );
4028
+ const id = result.doc_ext_ids?.[0];
4029
+ if (!id) throw new Error("The PDF could not be saved.");
4030
+ return id;
4031
+ },
4032
+ [arbi, workspaceId]
4033
+ );
4034
+ const list = react.useCallback(
4035
+ async (scope = "presentations") => {
4036
+ if (!arbi || !workspaceId) return [];
4037
+ const docs = await arbi.documents.list({ workspaceId });
4038
+ return docs.filter((doc) => {
4039
+ const folder = doc.folder ?? "";
4040
+ if (!ui.isPresentationFileName(doc.file_name, folder)) return false;
4041
+ return scope === "templates" ? folder === chunkXTR26TNE_cjs.TEMPLATES_FOLDER : folder.startsWith(chunkXTR26TNE_cjs.PRESENTATIONS_FOLDER) && folder !== chunkXTR26TNE_cjs.TEMPLATES_FOLDER && !folder.startsWith(chunkXTR26TNE_cjs.EXPORTS_FOLDER);
4042
+ }).map((doc) => ({
4043
+ id: doc.external_id,
4044
+ title: chunkXTR26TNE_cjs.presentationTitle(doc.file_name ?? ""),
4045
+ updatedAt: doc.updated_at ?? doc.created_at ?? null
4046
+ })).sort((a, b) => (a.updatedAt ?? "") < (b.updatedAt ?? "") ? 1 : -1);
4047
+ },
4048
+ [arbi, workspaceId]
4049
+ );
4050
+ const readSaved = react.useCallback(
4051
+ async (id, version) => {
4052
+ if (!arbi) throw new Error("Sign in to open a presentation.");
4053
+ const response = await arbi.documents.download(id, version);
4054
+ const bytes = new Uint8Array(await response.arrayBuffer());
4055
+ const text = await presentationData(bytes);
4056
+ const [doc] = await arbi.documents.get([id]).catch(() => []);
4057
+ const openedVersion = version ?? doc?.current_version ?? null;
4058
+ if (text === null) {
4059
+ const imported = await importPptx(bytes, {
4060
+ title: chunkXTR26TNE_cjs.presentationTitle(doc?.file_name ?? "Presentation.pptx"),
4061
+ documentId: id,
4062
+ version: version ?? doc?.current_version,
4063
+ fileName: doc?.file_name ?? void 0
4064
+ });
4065
+ return {
4066
+ saved: imported.saved,
4067
+ legacy: false,
4068
+ imported: { notes: imported.notes },
4069
+ openedVersion
4070
+ };
4071
+ }
4072
+ let saved = chunkXTR26TNE_cjs.parsePresentation(text);
4073
+ let changedOutside;
4074
+ if (isZipBytes(bytes) && saved.slides.some((slide) => slide.fingerprint)) {
4075
+ const now = await slideFingerprints(bytes);
4076
+ const unchanged = now.length === saved.slides.length && now.every((fingerprint, i) => fingerprint === saved.slides[i].fingerprint);
4077
+ if (!unchanged) {
4078
+ const imported = await importPptx(bytes, {
4079
+ title: saved.title,
4080
+ documentId: id,
4081
+ version: version ?? doc?.current_version,
4082
+ fileName: doc?.file_name ?? void 0
4083
+ });
4084
+ const merged = mergeOutsideEdits(saved, imported.saved, now);
4085
+ saved = merged.saved;
4086
+ changedOutside = merged.changed;
4087
+ }
4088
+ }
4089
+ return {
4090
+ saved,
4091
+ // Saved before presentations were PowerPoint files: its next save is a new PowerPoint
4092
+ // file, not a version of this one (a document's versions keep one format).
4093
+ legacy: !isZipBytes(bytes),
4094
+ changedOutside,
4095
+ openedVersion
4096
+ };
4097
+ },
4098
+ [arbi]
4099
+ );
4100
+ const startFromTemplate = react.useCallback(
4101
+ async (id) => {
4102
+ const { saved } = await readSaved(id);
4103
+ chunkXTR26TNE_cjs.usePresentation.getState().load({
4104
+ title: "Untitled presentation",
4105
+ savedId: null,
4106
+ captions: saved.captions,
4107
+ labelColumn: saved.labelColumn ?? null,
4108
+ theme: saved.theme,
4109
+ clips: [],
4110
+ slides: saved.slides.map((slide) => ({ ...slide, items: [] })),
4111
+ media: saved.media
4112
+ });
4113
+ },
4114
+ [readSaved]
4115
+ );
4116
+ const downloadFile = react.useCallback(
4117
+ async (id) => {
4118
+ if (!arbi) throw new Error("Sign in to read the file.");
4119
+ return (await arbi.documents.download(id)).blob();
4120
+ },
4121
+ [arbi]
4122
+ );
4123
+ const open = react.useCallback(
4124
+ async (id, version) => {
4125
+ if (!arbi) throw new Error("Sign in to open a presentation.");
4126
+ const { saved, legacy, imported, changedOutside, openedVersion } = await readSaved(
4127
+ id,
4128
+ version
4129
+ );
4130
+ const media = saved.media ?? {};
4131
+ const sources = /* @__PURE__ */ new Map();
4132
+ const sourceOf = (clip) => {
4133
+ if (clip.media) {
4134
+ const url = media[clip.media];
4135
+ return url ? Promise.resolve(dataUrlBlob(url)) : Promise.reject(new Error("No image"));
4136
+ }
4137
+ const key = `${clip.kind === "pdf" ? "pdf" : "file"}:${clip.documentId}@${clip.version ?? ""}`;
4138
+ let pending = sources.get(key);
4139
+ if (!pending) {
4140
+ pending = clip.kind === "pdf" ? documentTools.fetchDocumentPdf(client, clip.documentId, { version: clip.version ?? null }) : arbi.documents.download(clip.documentId, clip.version).then((r) => r.blob());
4141
+ sources.set(key, pending);
4142
+ }
4143
+ return pending;
4144
+ };
4145
+ const markups = /* @__PURE__ */ new Map();
4146
+ const masksOf = (clip) => {
4147
+ const key = `${clip.documentId}@${clip.version ?? ""}`;
4148
+ let pending = markups.get(key);
4149
+ if (!pending) {
4150
+ pending = documentTools.createMarkupStore({ version: clip.version, client }).load(clip.documentId).then((layer) => {
4151
+ const byPage = /* @__PURE__ */ new Map();
4152
+ for (const mark of layer.annotations) {
4153
+ if (mark.kind !== "redaction" || mark.status === "rejected") continue;
4154
+ byPage.set(mark.page, [...byPage.get(mark.page) ?? [], ...mark.rects]);
4155
+ }
4156
+ return byPage;
4157
+ }).catch((error) => {
4158
+ if (error instanceof documentTools.MarkupStoreError && error.status === 404) return /* @__PURE__ */ new Map();
4159
+ throw error;
4160
+ });
4161
+ markups.set(key, pending);
4162
+ }
4163
+ return pending;
4164
+ };
4165
+ const opened = await Promise.all(
4166
+ saved.clips.map(async (clip) => {
4167
+ try {
4168
+ const [source, masks] = await Promise.all([
4169
+ sourceOf(clip),
4170
+ clip.kind === "pdf" && !clip.media ? masksOf(clip) : Promise.resolve(/* @__PURE__ */ new Map())
4171
+ ]);
4172
+ return { ...clip, source, masks: masks.get(clip.page) ?? [] };
4173
+ } catch {
4174
+ return null;
4175
+ }
4176
+ })
4177
+ );
4178
+ const kept = [];
4179
+ const newIndex = opened.map((clip) => clip ? kept.push(clip) - 1 : -1);
4180
+ chunkXTR26TNE_cjs.usePresentation.getState().load({
4181
+ title: saved.title,
4182
+ savedId: legacy ? null : id,
4183
+ version: legacy ? null : openedVersion,
4184
+ boundTo: id,
4185
+ captions: saved.captions,
4186
+ labelColumn: saved.labelColumn ?? null,
4187
+ theme: saved.theme,
4188
+ clips: kept,
4189
+ slides: saved.slides.map((slide) => ({
4190
+ ...slide,
4191
+ items: slide.items.filter((item) => newIndex[item.clip] >= 0).map((item) => ({ ...item, clip: newIndex[item.clip] }))
4192
+ })),
4193
+ media
4194
+ });
4195
+ return {
4196
+ missing: opened.length - kept.length,
4197
+ stale: saved.slidesStale === true,
4198
+ ...imported ? { imported } : {},
4199
+ ...changedOutside?.length ? { changedOutside } : {}
4200
+ };
4201
+ },
4202
+ [arbi, client, readSaved]
4203
+ );
4204
+ return {
4205
+ save,
4206
+ exportPdf,
4207
+ list,
4208
+ open,
4209
+ startFromTemplate,
4210
+ downloadFile,
4211
+ canSave: !!arbi && !!workspaceId
4212
+ };
4213
+ }
4214
+ var WHOLE = { x: 0, y: 0, w: 1, h: 1 };
4215
+ async function aspectOf(blob) {
4216
+ const bitmap = await createImageBitmap(blob);
4217
+ const aspect = bitmap.width / bitmap.height;
4218
+ bitmap.close();
4219
+ return aspect;
4220
+ }
4221
+ function useGenerateImageLaunch() {
4222
+ const arbi = react$1.useArbi();
4223
+ const workspaceId = react$1.useArbiSessionState((s) => s.workspaceId);
4224
+ return react.useCallback(
4225
+ async ({ inputs }) => {
4226
+ if (!arbi || !workspaceId) throw new Error("Open a workspace to generate images.");
4227
+ const prompt = typeof inputs.prompt === "string" ? inputs.prompt.trim() : "";
4228
+ if (!prompt) return;
4229
+ const tools = chunkNLOETJCB_cjs.PROMPT_BINDINGS[chunk7JLJH5CW_cjs.GENERATE_IMAGE_ID].tools?.(inputs);
4230
+ let image;
4231
+ let answer = "";
4232
+ let failure = "";
4233
+ await arbi.assistant.ask(
4234
+ { question: `/image ${prompt}`, workspaceId, ...tools ? { tools } : {} },
4235
+ {
4236
+ onToken: (token) => {
4237
+ answer += token;
4238
+ },
4239
+ onError: (error) => {
4240
+ failure = error instanceof Error ? error.message : String(error);
4241
+ },
4242
+ onArtifact: (artifact) => {
4243
+ if (artifact.mode === "image" && artifact.doc_ext_id) image = artifact;
4244
+ }
4245
+ }
4246
+ ).catch((error) => {
4247
+ failure ||= error instanceof Error ? error.message : String(error);
4248
+ });
4249
+ const documentId = image?.doc_ext_id;
4250
+ if (!documentId)
4251
+ throw new Error(failure || answer.trim() || "The image could not be generated.");
4252
+ const source = await (await arbi.documents.download(documentId)).blob();
4253
+ const fileName = `${prompt.split("\n")[0].slice(0, 60)}.png`;
4254
+ chunkXTR26TNE_cjs.usePresentation.getState().addClip({
4255
+ kind: "image",
4256
+ documentId,
4257
+ fileName,
4258
+ page: 1,
4259
+ rect: WHOLE,
4260
+ aspect: await aspectOf(source),
4261
+ masks: [],
4262
+ source,
4263
+ caption: chunkXTR26TNE_cjs.mediaCaption(fileName)
4264
+ });
4265
+ },
4266
+ [arbi, workspaceId]
4267
+ );
4268
+ }
4269
+ function GenerateImageDialog({
4270
+ open,
4271
+ onOpenChange,
4272
+ referenceId
4273
+ }) {
4274
+ const notify = chunkXTR26TNE_cjs.usePresentation((s) => s.notify);
4275
+ const { skills } = chunkGKVE5JGB_cjs.useWorkflowDefinitions();
4276
+ const run = useGenerateImageLaunch();
4277
+ const workflows = react.useMemo(
4278
+ () => skills.filter((w) => w.id === chunk7JLJH5CW_cjs.GENERATE_IMAGE_ID).map((w) => ({ ...w, options: { ...w.options, agent: false } })),
4279
+ [skills]
4280
+ );
4281
+ const onLaunch = react.useCallback(
4282
+ async (launch) => {
4283
+ try {
4284
+ await run(launch);
4285
+ notify("The image is in your clips", "success");
4286
+ } catch (error) {
4287
+ notify(error instanceof Error ? error.message : "The image could not be generated", "error");
4288
+ throw error;
4289
+ }
4290
+ },
4291
+ [run, notify]
4292
+ );
4293
+ if (!open || !workflows.length) return null;
4294
+ return /* @__PURE__ */ jsxRuntime.jsx(
4295
+ chunkNLOETJCB_cjs.ConnectedWorkflowStart,
4296
+ {
4297
+ workflows,
4298
+ showTiles: false,
4299
+ activeId: chunk7JLJH5CW_cjs.GENERATE_IMAGE_ID,
4300
+ onActiveIdChange: (id) => id === null && onOpenChange(false),
4301
+ initialInputsFor: () => referenceId ? { references: { folders: [], labels: [], documents: [referenceId] } } : {},
4302
+ onLaunch,
4303
+ uploadInPlace: true
4304
+ }
4305
+ );
4306
+ }
4307
+ var SAVE_TEXT = {
4308
+ save: {
4309
+ title: "Save presentation",
4310
+ description: "Saved in Files \u203A Presentations as a PowerPoint file: searchable, it opens again here for editing, and in PowerPoint.",
4311
+ action: "Save"
4312
+ },
4313
+ saveAs: {
4314
+ title: "Save as",
4315
+ description: "A new copy under this name in Files \u203A Presentations.",
4316
+ action: "Save"
4317
+ },
4318
+ template: {
4319
+ title: "Save as template",
4320
+ description: "Its look and slides, without clips, in Files \u203A Presentations \u203A Templates: New from template starts from it.",
4321
+ action: "Save template"
4322
+ },
4323
+ export: {
4324
+ title: "Export PDF",
4325
+ description: "A PDF of the slides in Files \u203A Presentations \u203A Exports: to send or file. Redactions are burnt in.",
4326
+ action: "Export"
4327
+ }
4328
+ };
4329
+ function SavePresentationDialog({
4330
+ open,
4331
+ onOpenChange,
4332
+ mode = "save"
4333
+ }) {
4334
+ const notify = chunkXTR26TNE_cjs.usePresentation((s) => s.notify);
4335
+ const { save, exportPdf } = usePresentationFiles();
4336
+ const current2 = chunkXTR26TNE_cjs.usePresentation((s) => s.title);
4337
+ const [title, setTitle] = react.useState(current2);
4338
+ const [busy, setBusy] = react.useState(false);
4339
+ const text = SAVE_TEXT[mode];
4340
+ react.useEffect(() => {
4341
+ if (!open) return;
4342
+ const named = current2 === "Untitled presentation" ? "" : current2;
4343
+ setTitle(mode === "saveAs" && named ? `${named} (copy)` : mode === "template" ? "" : named);
4344
+ }, [open, current2, mode]);
4345
+ const submit = async () => {
4346
+ const name = title.trim();
4347
+ if (!name || busy) return;
4348
+ setBusy(true);
4349
+ try {
4350
+ if (mode === "export") {
4351
+ await exportPdf(name);
4352
+ notify(`"${name}.pdf" saved in Presentations`, "success");
4353
+ } else {
4354
+ await save(name, { asNew: mode === "saveAs", template: mode === "template" });
4355
+ notify(`"${name}" saved in Presentations`, "success");
4356
+ }
4357
+ onOpenChange(false);
4358
+ } catch (error) {
4359
+ notify(error instanceof Error ? error.message : "Could not save", "error");
4360
+ } finally {
4361
+ setBusy(false);
4362
+ }
4363
+ };
4364
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4365
+ chunkULWRNGGE_cjs.ModalScaffold,
4366
+ {
4367
+ open,
4368
+ onOpenChange,
4369
+ title: text.title,
4370
+ description: text.description,
4371
+ showDescription: true,
4372
+ icon: mode === "export" ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileDown, { className: "size-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { className: "size-5" }),
4373
+ size: "sm",
4374
+ testId: "presentation-save-dialog",
4375
+ children: [
4376
+ /* @__PURE__ */ jsxRuntime.jsx(chunkULWRNGGE_cjs.ModalScaffold.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
4377
+ ui.Input,
4378
+ {
4379
+ autoFocus: true,
4380
+ value: title,
4381
+ onChange: (event) => setTitle(event.target.value),
4382
+ onKeyDown: (event) => event.key === "Enter" && void submit(),
4383
+ placeholder: "Name, e.g. Opening statement",
4384
+ "aria-label": "Name",
4385
+ disabled: busy
4386
+ }
4387
+ ) }),
4388
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkULWRNGGE_cjs.ModalScaffold.Footer, { children: [
4389
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
4390
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { type: "button", onClick: () => void submit(), disabled: !title.trim() || busy, children: [
4391
+ busy && /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin" }),
4392
+ text.action
4393
+ ] })
4394
+ ] })
4395
+ ]
4396
+ }
4397
+ );
4398
+ }
4399
+ function OpenPresentationDialog({
4400
+ open,
4401
+ onOpenChange,
4402
+ onOpened,
4403
+ templates = false
4404
+ }) {
4405
+ const { confirm } = chunkTKHCIHE2_cjs.useBlocksEnvironment();
4406
+ const notify = chunkXTR26TNE_cjs.usePresentation((s) => s.notify);
4407
+ const { list, startFromTemplate } = usePresentationFiles();
4408
+ const [entries, setEntries] = react.useState(null);
4409
+ const [opening, setOpening] = react.useState(null);
4410
+ react.useEffect(() => {
4411
+ if (!open) return;
4412
+ setEntries(null);
4413
+ list(templates ? "templates" : "presentations").then(setEntries).catch(() => setEntries([]));
4414
+ }, [open, list, templates]);
4415
+ const choose = async (entry) => {
4416
+ if (!templates) {
4417
+ onOpenChange(false);
4418
+ onOpened(entry.id);
4419
+ return;
4420
+ }
4421
+ if (chunkXTR26TNE_cjs.usePresentation.getState().clips.length) {
4422
+ const ok = await confirm({
4423
+ title: "New from template",
4424
+ message: "Replace the presentation that is open? Save it first to keep it.",
4425
+ kind: "confirm"
4426
+ });
4427
+ if (!ok) return;
4428
+ }
4429
+ setOpening(entry.id);
4430
+ try {
4431
+ await startFromTemplate(entry.id);
4432
+ onOpenChange(false);
4433
+ onOpened(null);
4434
+ } catch (error) {
4435
+ notify(error instanceof Error ? error.message : "The template could not be opened", "error");
4436
+ } finally {
4437
+ setOpening(null);
4438
+ }
4439
+ };
4440
+ return /* @__PURE__ */ jsxRuntime.jsx(
4441
+ chunkULWRNGGE_cjs.ModalScaffold,
4442
+ {
4443
+ open,
4444
+ onOpenChange,
4445
+ title: templates ? "New from template" : "Open presentation",
4446
+ description: templates ? "A new presentation with the template\u2019s look and slides." : "Presentations saved in this workspace.",
4447
+ showDescription: true,
4448
+ icon: templates ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LayoutTemplate, { className: "size-5" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FolderOpen, { className: "size-5" }),
4449
+ testId: templates ? "presentation-template-dialog" : "presentation-open-dialog",
4450
+ children: /* @__PURE__ */ jsxRuntime.jsx(chunkULWRNGGE_cjs.ModalScaffold.Body, { children: entries === null ? /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [
4451
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 animate-spin" }),
4452
+ " Loading\u2026"
4453
+ ] }) : entries.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: templates ? "No templates yet. Save as template\u2026 keeps one in Files \u203A Presentations \u203A Templates." : "No saved presentations yet." }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { className: "divide-y divide-border rounded-lg border border-border", children: entries.map((entry) => /* @__PURE__ */ jsxRuntime.jsx("li", { children: /* @__PURE__ */ jsxRuntime.jsxs(
4454
+ "button",
4455
+ {
4456
+ type: "button",
4457
+ disabled: !!opening,
4458
+ onClick: () => void choose(entry),
4459
+ className: "flex w-full items-center gap-3 px-3 py-2.5 text-left hover:bg-muted focus-visible:bg-muted focus-visible:outline-none disabled:opacity-60 pointer-coarse:min-h-11",
4460
+ children: [
4461
+ opening === entry.id ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "size-4 shrink-0 animate-spin text-muted-foreground" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Presentation, { className: "size-4 shrink-0 text-muted-foreground" }),
4462
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate text-sm font-medium", children: entry.title }),
4463
+ entry.updatedAt && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs text-muted-foreground", children: new Date(entry.updatedAt).toLocaleDateString() })
4464
+ ]
4465
+ }
4466
+ ) }, entry.id)) }) })
4467
+ }
4468
+ );
4469
+ }
4470
+ var IMAGE_FILE = /\.(png|jpe?g|gif|webp|svg)$/i;
4471
+ function ChooseLogoDialog({ open, onOpenChange }) {
4472
+ const setTheme = chunkXTR26TNE_cjs.usePresentation((s) => s.setTheme);
4473
+ const corner = chunkXTR26TNE_cjs.usePresentation((s) => s.theme.logo?.corner ?? "br");
4474
+ const { data: documents } = react$1.useDocuments(void 0, { enabled: open });
4475
+ const [picked, setPicked] = react.useState("");
4476
+ const groups = react.useMemo(
4477
+ () => [
4478
+ {
4479
+ id: "images",
4480
+ docs: (documents ?? []).filter((doc) => IMAGE_FILE.test(doc.file_name ?? "")).map((doc) => ({
4481
+ id: doc.external_id,
4482
+ title: ui.getDocumentTitle(doc, "Untitled"),
4483
+ fileName: doc.file_name
4484
+ })).sort((a, b) => a.title.localeCompare(b.title))
4485
+ }
4486
+ ],
4487
+ [documents]
4488
+ );
4489
+ const selected = groups[0].docs.find((doc) => doc.id === picked);
4490
+ const use = () => {
4491
+ if (!selected) return;
4492
+ setTheme({ logo: { documentId: selected.id, fileName: selected.fileName ?? "", corner } });
4493
+ setPicked("");
4494
+ onOpenChange(false);
4495
+ };
4496
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4497
+ chunkULWRNGGE_cjs.ModalScaffold,
4498
+ {
4499
+ open,
4500
+ onOpenChange,
4501
+ title: "Logo",
4502
+ description: "An image of this workspace (a PNG with a transparent background looks best), small in the footer of every slide.",
4503
+ showDescription: true,
4504
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImageIcon, { className: "size-5" }),
4505
+ size: "sm",
4506
+ testId: "presentation-logo-dialog",
4507
+ children: [
4508
+ /* @__PURE__ */ jsxRuntime.jsx(chunkULWRNGGE_cjs.ModalScaffold.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(
4509
+ chunkVZOOGA47_cjs.DocumentPicker,
4510
+ {
4511
+ groups,
4512
+ value: picked,
4513
+ onValueChange: setPicked,
4514
+ labels: { placeholder: "Choose an image", noDocuments: "No images in this workspace." }
4515
+ }
4516
+ ) }),
4517
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkULWRNGGE_cjs.ModalScaffold.Footer, { children: [
4518
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
4519
+ /* @__PURE__ */ jsxRuntime.jsx(
4520
+ ui.Button,
4521
+ {
4522
+ type: "button",
4523
+ onClick: use,
4524
+ disabled: !selected,
4525
+ "data-testid": "presentation-logo-use",
4526
+ children: "Use as logo"
4527
+ }
4528
+ )
4529
+ ] })
4530
+ ]
4531
+ }
4532
+ );
4533
+ }
4534
+ function CropClipDialog({ clip, onOpenChange }) {
4535
+ const url = chunkWOOOMTTA_cjs.useObjectUrl(clip?.source ?? EMPTY);
4536
+ const [natural, setNatural] = react.useState(null);
4537
+ const [area, setArea] = react.useState(null);
4538
+ react.useEffect(() => {
4539
+ setNatural(null);
4540
+ setArea(null);
4541
+ if (!clip) return;
4542
+ let cancelled = false;
4543
+ void createImageBitmap(clip.source).then((bitmap) => {
4544
+ if (!cancelled) setNatural({ width: bitmap.width, height: bitmap.height });
4545
+ bitmap.close();
4546
+ });
4547
+ return () => {
4548
+ cancelled = true;
4549
+ };
4550
+ }, [clip]);
4551
+ const original = natural ? natural.width / natural.height : 1;
4552
+ const apply = () => {
4553
+ if (!clip || !natural || !area || !area.width || !area.height) return;
4554
+ const rect = {
4555
+ x: Math.max(0, area.x / natural.width),
4556
+ y: Math.max(0, area.y / natural.height),
4557
+ w: Math.min(1, area.width / natural.width),
4558
+ h: Math.min(1, area.height / natural.height)
4559
+ };
4560
+ chunkXTR26TNE_cjs.usePresentation.getState().cropClip(clip.id, rect, area.width / area.height);
4561
+ onOpenChange(false);
4562
+ };
4563
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4564
+ chunkULWRNGGE_cjs.ModalScaffold,
4565
+ {
4566
+ open: !!clip,
4567
+ onOpenChange,
4568
+ title: "Crop",
4569
+ description: "Choose the part of the image the clip shows.",
4570
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Crop, { className: "size-5" }),
4571
+ size: "lg",
4572
+ testId: "presentation-crop-dialog",
4573
+ children: [
4574
+ /* @__PURE__ */ jsxRuntime.jsx(chunkULWRNGGE_cjs.ModalScaffold.Body, { children: url && natural && /* @__PURE__ */ jsxRuntime.jsx(
4575
+ react$1.ImageCropper,
4576
+ {
4577
+ image: url,
4578
+ aspect: original,
4579
+ aspectOptions: [
4580
+ { label: "Original", value: original },
4581
+ { label: "16:9", value: 16 / 9 },
4582
+ { label: "4:3", value: 4 / 3 },
4583
+ { label: "1:1", value: 1 },
4584
+ { label: "3:4", value: 3 / 4 }
4585
+ ],
4586
+ onCropComplete: (next) => setArea(next)
4587
+ }
4588
+ ) }),
4589
+ /* @__PURE__ */ jsxRuntime.jsxs(chunkULWRNGGE_cjs.ModalScaffold.Footer, { children: [
4590
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), children: "Cancel" }),
4591
+ /* @__PURE__ */ jsxRuntime.jsx(
4592
+ ui.Button,
4593
+ {
4594
+ type: "button",
4595
+ onClick: apply,
4596
+ disabled: !area,
4597
+ "data-testid": "presentation-crop-apply",
4598
+ children: "Crop"
4599
+ }
4600
+ )
4601
+ ] })
4602
+ ]
4603
+ }
4604
+ );
4605
+ }
4606
+ var EMPTY = new Blob();
4607
+ var NONE = "none";
4608
+ function LabelColumnMenu({ columns }) {
4609
+ const labelColumn = chunkXTR26TNE_cjs.usePresentation((s) => s.labelColumn);
4610
+ const setLabelColumn = chunkXTR26TNE_cjs.usePresentation((s) => s.setLabelColumn);
4611
+ if (!columns.length) return null;
4612
+ const chosen = columns.find((column) => column.id === labelColumn);
4613
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuSub, { children: [
4614
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.DropdownMenuSubTrigger, { "data-testid": "presentation-label-column", children: [
4615
+ "Label clips with",
4616
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-auto pl-3 text-xs text-muted-foreground", children: chosen?.name ?? "None" })
4617
+ ] }),
4618
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuSubContent, { children: /* @__PURE__ */ jsxRuntime.jsxs(
4619
+ ui.DropdownMenuRadioGroup,
4620
+ {
4621
+ value: chosen?.id ?? NONE,
4622
+ onValueChange: (value) => setLabelColumn(value === NONE ? null : value),
4623
+ children: [
4624
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuRadioItem, { value: NONE, children: "No column" }),
4625
+ columns.map((column) => /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuRadioItem, { value: column.id, children: column.name }, column.id))
4626
+ ]
4627
+ }
4628
+ ) })
4629
+ ] });
4630
+ }
4631
+ function GenerateTile({ onClick }) {
4632
+ return /* @__PURE__ */ jsxRuntime.jsxs(
4633
+ "button",
4634
+ {
4635
+ type: "button",
4636
+ onClick,
4637
+ title: "Generate an image",
4638
+ "aria-label": "Generate image",
4639
+ className: "flex h-[72px] w-[72px] flex-col items-center justify-center gap-1 rounded-lg border border-dashed border-border text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
4640
+ "data-testid": "presentation-generate-image",
4641
+ children: [
4642
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Sparkles, { className: "size-4" }),
4643
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium", children: "Generate" })
4644
+ ]
4645
+ }
4646
+ );
4647
+ }
4648
+ var VALUE_FORMATS = /* @__PURE__ */ new Set(["text", "number", "select"]);
4649
+ function useLabelColumns(active) {
4650
+ const workspaceId = react$1.useArbiSessionState((s) => s.workspaceId);
4651
+ const { data: tags } = react$1.useTags(workspaceId, { enabled: active });
4652
+ const { data: documents } = react$1.useDocuments(workspaceId, { enabled: active });
4653
+ const labelColumn = chunkXTR26TNE_cjs.usePresentation((s) => s.labelColumn);
4654
+ const documentIds = chunkXTR26TNE_cjs.usePresentation((s) => s.clips.map((clip) => clip.documentId).join(" "));
4655
+ const columns = react.useMemo(
4656
+ () => (tags ?? []).filter((tag) => tag.kind !== "search" && VALUE_FORMATS.has(tag.tag_type.type)).map((tag) => ({ id: tag.external_id, name: tag.name })).sort((a, b) => a.name.localeCompare(b.name)),
4657
+ [tags]
4658
+ );
4659
+ react.useEffect(() => {
4660
+ const values = {};
4661
+ if (labelColumn) {
4662
+ const wanted = new Set(documentIds.split(" "));
4663
+ for (const doc of documents ?? []) {
4664
+ if (!wanted.has(doc.external_id)) continue;
4665
+ const note2 = doc.doctags?.find((dt) => dt.tag_ext_id === labelColumn)?.note?.trim();
4666
+ if (note2) values[doc.external_id] = note2;
4667
+ }
4668
+ }
4669
+ chunkXTR26TNE_cjs.usePresentation.getState().setColumnValues(values);
4670
+ }, [labelColumn, documentIds, documents]);
4671
+ return columns;
4672
+ }
4673
+ function usePresentationController(active) {
4674
+ const session = react$1.useArbiSessionStore();
4675
+ const notify = chunkXTR26TNE_cjs.usePresentation((s) => s.notify);
4676
+ const { save, canSave } = usePresentationFiles();
4677
+ const labelColumns = useLabelColumns(active);
4678
+ const [dialog, setDialog] = react.useState(null);
4679
+ const [reference, setReference] = react.useState(null);
4680
+ const [cropping, setCropping] = react.useState(null);
4681
+ const cropClip = chunkXTR26TNE_cjs.usePresentation((s) => s.clips.find((c) => c.id === cropping) ?? null);
4682
+ const [saving, setSaving] = react.useState(false);
4683
+ const close = (open) => !open && setDialog(null);
4684
+ const quickSave = react.useCallback(async () => {
4685
+ const { savedId, title } = chunkXTR26TNE_cjs.usePresentation.getState();
4686
+ if (!savedId) return setDialog("save");
4687
+ setSaving(true);
4688
+ try {
4689
+ await save(title);
4690
+ notify("Presentation saved", "success");
4691
+ } catch (error) {
4692
+ notify(
4693
+ error instanceof Error ? error.message : "The presentation could not be saved",
4694
+ "error"
4695
+ );
4696
+ } finally {
4697
+ setSaving(false);
4698
+ }
4699
+ }, [save, notify]);
4700
+ const generate = react.useCallback((referenceId) => {
4701
+ setReference(referenceId);
4702
+ setDialog("generate");
4703
+ }, []);
4704
+ const saveModes = ["save", "saveAs", "export", "template"];
4705
+ const dialogs = /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4706
+ /* @__PURE__ */ jsxRuntime.jsx(
4707
+ SavePresentationDialog,
4708
+ {
4709
+ open: saveModes.includes(dialog),
4710
+ mode: saveModes.includes(dialog) ? dialog : "save",
4711
+ onOpenChange: close
4712
+ }
4713
+ ),
4714
+ /* @__PURE__ */ jsxRuntime.jsx(
4715
+ OpenPresentationDialog,
4716
+ {
4717
+ open: dialog === "open" || dialog === "fromTemplate",
4718
+ templates: dialog === "fromTemplate",
4719
+ onOpened: (id) => chunk5LTIZ44D_cjs.editPresentation(session, id ? { kind: "open", documentId: id } : { kind: "new" }),
4720
+ onOpenChange: close
4721
+ }
4722
+ ),
4723
+ /* @__PURE__ */ jsxRuntime.jsx(ChooseLogoDialog, { open: dialog === "logo", onOpenChange: close }),
4724
+ /* @__PURE__ */ jsxRuntime.jsx(CropClipDialog, { clip: cropClip, onOpenChange: (open) => !open && setCropping(null) }),
4725
+ /* @__PURE__ */ jsxRuntime.jsx(
4726
+ GenerateImageDialog,
4727
+ {
4728
+ open: dialog === "generate",
4729
+ referenceId: reference,
4730
+ onOpenChange: close
4731
+ }
4732
+ )
4733
+ ] });
4734
+ const editor = {
4735
+ actions: canSave && /* @__PURE__ */ jsxRuntime.jsx(
4736
+ ui.Button,
4737
+ {
4738
+ type: "button",
4739
+ size: "icon",
4740
+ variant: "ghost",
4741
+ className: "size-8 text-muted-foreground hover:text-foreground pointer-coarse:size-11",
4742
+ title: "Save (in Files \u203A Presentations)",
4743
+ "aria-label": "Save",
4744
+ disabled: saving,
4745
+ onClick: () => void quickSave(),
4746
+ "data-testid": "presentation-save",
4747
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Save, { className: "size-4" })
4748
+ }
4749
+ ),
4750
+ menuItems: canSave && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4751
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuItem, { onSelect: () => setDialog("open"), children: "Open\u2026" }),
4752
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuItem, { onSelect: () => setDialog("fromTemplate"), children: "New from template\u2026" }),
4753
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuItem, { onSelect: () => setDialog("saveAs"), children: "Save as\u2026" }),
4754
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuItem, { onSelect: () => setDialog("template"), children: "Save as template\u2026" }),
4755
+ /* @__PURE__ */ jsxRuntime.jsx(ui.DropdownMenuItem, { onSelect: () => setDialog("export"), children: "Export PDF\u2026" }),
4756
+ /* @__PURE__ */ jsxRuntime.jsx(LabelColumnMenu, { columns: labelColumns })
4757
+ ] }),
4758
+ trayStart: canSave && /* @__PURE__ */ jsxRuntime.jsx(GenerateTile, { onClick: () => generate(null) }),
4759
+ onChooseLogo: canSave ? () => setDialog("logo") : void 0,
4760
+ onEditImage: (clipId) => {
4761
+ const clip = chunkXTR26TNE_cjs.usePresentation.getState().clips.find((c) => c.id === clipId);
4762
+ if (clip) generate(clip.documentId);
4763
+ },
4764
+ onCropImage: setCropping
4765
+ };
4766
+ return { editor, dialogs };
4767
+ }
4768
+
4769
+ exports.PresentationStage = PresentationStage;
4770
+ exports.SlidesEditor = SlidesEditor;
4771
+ exports.usePresentationController = usePresentationController;
4772
+ exports.usePresentationFiles = usePresentationFiles;
4773
+ //# sourceMappingURL=chunk-Q3XJUXID.cjs.map
4774
+ //# sourceMappingURL=chunk-Q3XJUXID.cjs.map