@dxos/plugin-thread 0.8.2-main.f11618f → 0.8.2-staging.42af850

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 (400) hide show
  1. package/dist/lib/browser/ChannelContainer-LGYSQMC7.mjs +15 -0
  2. package/dist/lib/browser/ChatContainer-JWRI73OG.mjs +14 -0
  3. package/dist/lib/browser/app-graph-builder-K3CFBN7V.mjs +171 -0
  4. package/dist/lib/browser/app-graph-builder-K3CFBN7V.mjs.map +7 -0
  5. package/dist/lib/browser/call-manager-APQAZWHT.mjs +23 -0
  6. package/dist/lib/browser/call-manager-APQAZWHT.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-4H4A264L.mjs +1708 -0
  8. package/dist/lib/browser/chunk-4H4A264L.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-AYNXYEKP.mjs → chunk-5P5BH5GE.mjs} +57 -35
  10. package/dist/lib/browser/chunk-5P5BH5GE.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-5PGLGDIP.mjs +983 -0
  12. package/dist/lib/browser/chunk-5PGLGDIP.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-CF2MXRCH.mjs +21 -0
  14. package/dist/lib/browser/chunk-CF2MXRCH.mjs.map +7 -0
  15. package/dist/lib/browser/chunk-G3TS64FP.mjs +351 -0
  16. package/dist/lib/browser/chunk-G3TS64FP.mjs.map +7 -0
  17. package/dist/lib/browser/{chunk-WBJLUUOK.mjs → chunk-IIUTBDVB.mjs} +4 -4
  18. package/dist/lib/browser/chunk-IIUTBDVB.mjs.map +7 -0
  19. package/dist/lib/browser/chunk-JKQUJOTX.mjs +31 -0
  20. package/dist/lib/browser/chunk-JKQUJOTX.mjs.map +7 -0
  21. package/dist/lib/browser/chunk-KMVGJG23.mjs +545 -0
  22. package/dist/lib/browser/chunk-KMVGJG23.mjs.map +7 -0
  23. package/dist/lib/browser/{chunk-EP6MRLY7.mjs → chunk-R5MDLUPC.mjs} +5 -3
  24. package/dist/lib/browser/chunk-R5MDLUPC.mjs.map +7 -0
  25. package/dist/lib/browser/chunk-WDOQXTI2.mjs +126 -0
  26. package/dist/lib/browser/chunk-WDOQXTI2.mjs.map +7 -0
  27. package/dist/lib/browser/index.mjs +107 -41
  28. package/dist/lib/browser/index.mjs.map +4 -4
  29. package/dist/lib/browser/{intent-resolver-3UW6ROYT.mjs → intent-resolver-YUYZCTLF.mjs} +100 -72
  30. package/dist/lib/browser/intent-resolver-YUYZCTLF.mjs.map +7 -0
  31. package/dist/lib/browser/markdown-4EC3OCF2.mjs +169 -0
  32. package/dist/lib/browser/markdown-4EC3OCF2.mjs.map +7 -0
  33. package/dist/lib/browser/meta.json +1 -1
  34. package/dist/lib/browser/react-root-EECCWHCW.mjs +27 -0
  35. package/dist/lib/browser/react-root-EECCWHCW.mjs.map +7 -0
  36. package/dist/lib/browser/react-surface-5QLZAFDL.mjs +108 -0
  37. package/dist/lib/browser/react-surface-5QLZAFDL.mjs.map +7 -0
  38. package/dist/lib/browser/{settings-2VZ7S4SK.mjs → settings-KVBQQSO5.mjs} +3 -3
  39. package/dist/lib/browser/{state-KBGI3QTR.mjs → state-RKL5MSDP.mjs} +4 -4
  40. package/dist/lib/browser/{state-KBGI3QTR.mjs.map → state-RKL5MSDP.mjs.map} +3 -3
  41. package/dist/lib/browser/types/index.mjs +16 -0
  42. package/dist/lib/browser/types/index.mjs.map +7 -0
  43. package/dist/lib/node/ChannelContainer-5EKNEH56.cjs +36 -0
  44. package/dist/lib/node/ChannelContainer-5EKNEH56.cjs.map +7 -0
  45. package/dist/lib/node/ChatContainer-EE3FPSYO.cjs +35 -0
  46. package/dist/lib/node/ChatContainer-EE3FPSYO.cjs.map +7 -0
  47. package/dist/lib/node/app-graph-builder-IRE3GYKE.cjs +179 -0
  48. package/dist/lib/node/app-graph-builder-IRE3GYKE.cjs.map +7 -0
  49. package/dist/lib/node/call-manager-HV2XJW5O.cjs +37 -0
  50. package/dist/lib/node/call-manager-HV2XJW5O.cjs.map +7 -0
  51. package/dist/lib/node/chunk-44I33JDT.cjs +1731 -0
  52. package/dist/lib/node/chunk-44I33JDT.cjs.map +7 -0
  53. package/dist/lib/node/chunk-4B3PPXA6.cjs +371 -0
  54. package/dist/lib/node/chunk-4B3PPXA6.cjs.map +7 -0
  55. package/dist/lib/node/{chunk-NQ5DTPOY.cjs → chunk-4YSNVPWU.cjs} +10 -8
  56. package/dist/lib/node/chunk-4YSNVPWU.cjs.map +7 -0
  57. package/dist/lib/node/chunk-6B3M6XRE.cjs +984 -0
  58. package/dist/lib/node/chunk-6B3M6XRE.cjs.map +7 -0
  59. package/dist/lib/node/{chunk-W25MBII2.cjs → chunk-FUXJAVFZ.cjs} +50 -29
  60. package/dist/lib/node/chunk-FUXJAVFZ.cjs.map +7 -0
  61. package/dist/lib/node/{chunk-ONLWOUYZ.cjs → chunk-HLMCEE5K.cjs} +7 -7
  62. package/dist/lib/node/{chunk-ONLWOUYZ.cjs.map → chunk-HLMCEE5K.cjs.map} +2 -2
  63. package/dist/lib/node/chunk-JLHENFGK.cjs +549 -0
  64. package/dist/lib/node/chunk-JLHENFGK.cjs.map +7 -0
  65. package/dist/lib/node/chunk-LR5NENBE.cjs +54 -0
  66. package/dist/lib/node/chunk-LR5NENBE.cjs.map +7 -0
  67. package/dist/lib/node/chunk-TKHJVC2C.cjs +147 -0
  68. package/dist/lib/node/chunk-TKHJVC2C.cjs.map +7 -0
  69. package/dist/lib/node/{chunk-IX63EKGL.cjs → chunk-U7NOIXN6.cjs} +16 -10
  70. package/dist/lib/node/chunk-U7NOIXN6.cjs.map +7 -0
  71. package/dist/lib/node/index.cjs +142 -80
  72. package/dist/lib/node/index.cjs.map +4 -4
  73. package/dist/lib/node/{intent-resolver-DC6HDNTX.cjs → intent-resolver-MZ3HADNY.cjs} +108 -82
  74. package/dist/lib/node/intent-resolver-MZ3HADNY.cjs.map +7 -0
  75. package/dist/lib/node/markdown-NJDPXIKD.cjs +178 -0
  76. package/dist/lib/node/markdown-NJDPXIKD.cjs.map +7 -0
  77. package/dist/lib/node/meta.json +1 -1
  78. package/dist/lib/node/react-root-IAYQ2DBO.cjs +51 -0
  79. package/dist/lib/node/react-root-IAYQ2DBO.cjs.map +7 -0
  80. package/dist/lib/node/react-surface-AVC5VVIS.cjs +122 -0
  81. package/dist/lib/node/react-surface-AVC5VVIS.cjs.map +7 -0
  82. package/dist/lib/node/{settings-7MGTJ46L.cjs → settings-H45P5YO2.cjs} +8 -8
  83. package/dist/lib/node/{state-FOHMAVSU.cjs → state-IY5XX3H2.cjs} +8 -8
  84. package/dist/lib/node/{state-FOHMAVSU.cjs.map → state-IY5XX3H2.cjs.map} +3 -3
  85. package/dist/lib/node/{types.cjs → types/index.cjs} +12 -6
  86. package/dist/lib/node/types/index.cjs.map +7 -0
  87. package/dist/lib/node-esm/ChannelContainer-KFS2RWWW.mjs +16 -0
  88. package/dist/lib/node-esm/ChannelContainer-KFS2RWWW.mjs.map +7 -0
  89. package/dist/lib/node-esm/ChatContainer-UJYPQMLV.mjs +15 -0
  90. package/dist/lib/node-esm/ChatContainer-UJYPQMLV.mjs.map +7 -0
  91. package/dist/lib/node-esm/app-graph-builder-I5VMPLE4.mjs +172 -0
  92. package/dist/lib/node-esm/app-graph-builder-I5VMPLE4.mjs.map +7 -0
  93. package/dist/lib/node-esm/call-manager-UUYXLF4T.mjs +24 -0
  94. package/dist/lib/node-esm/call-manager-UUYXLF4T.mjs.map +7 -0
  95. package/dist/lib/node-esm/{chunk-42F2DTMM.mjs → chunk-ESQC6SYE.mjs} +4 -4
  96. package/dist/lib/node-esm/chunk-ESQC6SYE.mjs.map +7 -0
  97. package/dist/lib/node-esm/chunk-J323VQ4P.mjs +127 -0
  98. package/dist/lib/node-esm/chunk-J323VQ4P.mjs.map +7 -0
  99. package/dist/lib/node-esm/{chunk-WLY2HMDB.mjs → chunk-K4KAUOKT.mjs} +5 -3
  100. package/dist/lib/node-esm/chunk-K4KAUOKT.mjs.map +7 -0
  101. package/dist/lib/node-esm/chunk-MNJ3V4XM.mjs +546 -0
  102. package/dist/lib/node-esm/chunk-MNJ3V4XM.mjs.map +7 -0
  103. package/dist/lib/node-esm/{chunk-T6E5EY7Z.mjs → chunk-O3V45YJP.mjs} +57 -35
  104. package/dist/lib/node-esm/chunk-O3V45YJP.mjs.map +7 -0
  105. package/dist/lib/node-esm/chunk-ODUB4GAB.mjs +23 -0
  106. package/dist/lib/node-esm/chunk-ODUB4GAB.mjs.map +7 -0
  107. package/dist/lib/node-esm/chunk-PGRP3O4O.mjs +33 -0
  108. package/dist/lib/node-esm/chunk-PGRP3O4O.mjs.map +7 -0
  109. package/dist/lib/node-esm/chunk-POKXRTII.mjs +352 -0
  110. package/dist/lib/node-esm/chunk-POKXRTII.mjs.map +7 -0
  111. package/dist/lib/node-esm/chunk-WXNPD542.mjs +1710 -0
  112. package/dist/lib/node-esm/chunk-WXNPD542.mjs.map +7 -0
  113. package/dist/lib/node-esm/chunk-X6AUQDTE.mjs +984 -0
  114. package/dist/lib/node-esm/chunk-X6AUQDTE.mjs.map +7 -0
  115. package/dist/lib/node-esm/index.mjs +107 -41
  116. package/dist/lib/node-esm/index.mjs.map +4 -4
  117. package/dist/lib/node-esm/{intent-resolver-OAMDKGAA.mjs → intent-resolver-CU22OPAH.mjs} +100 -72
  118. package/dist/lib/node-esm/intent-resolver-CU22OPAH.mjs.map +7 -0
  119. package/dist/lib/node-esm/markdown-HRGGO7IA.mjs +170 -0
  120. package/dist/lib/node-esm/markdown-HRGGO7IA.mjs.map +7 -0
  121. package/dist/lib/node-esm/meta.json +1 -1
  122. package/dist/lib/node-esm/react-root-NQMUEI6Z.mjs +28 -0
  123. package/dist/lib/node-esm/react-root-NQMUEI6Z.mjs.map +7 -0
  124. package/dist/lib/node-esm/react-surface-Z4X5VLMY.mjs +109 -0
  125. package/dist/lib/node-esm/react-surface-Z4X5VLMY.mjs.map +7 -0
  126. package/dist/lib/node-esm/{settings-AJAZ3O47.mjs → settings-IJGUOU3F.mjs} +3 -3
  127. package/dist/lib/node-esm/{state-IRLEO2SB.mjs → state-6AUTNROZ.mjs} +4 -4
  128. package/dist/lib/node-esm/{state-IRLEO2SB.mjs.map → state-6AUTNROZ.mjs.map} +3 -3
  129. package/dist/lib/node-esm/types/index.mjs +17 -0
  130. package/dist/lib/node-esm/types/index.mjs.map +7 -0
  131. package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
  132. package/dist/types/src/calls/call-manager.d.ts +77 -0
  133. package/dist/types/src/calls/call-manager.d.ts.map +1 -0
  134. package/dist/types/src/calls/call-swarm-synchronizer.d.ts +66 -0
  135. package/dist/types/src/calls/call-swarm-synchronizer.d.ts.map +1 -0
  136. package/dist/types/src/calls/index.d.ts +7 -0
  137. package/dist/types/src/calls/index.d.ts.map +1 -0
  138. package/dist/types/src/calls/media-manager.d.ts +60 -0
  139. package/dist/types/src/calls/media-manager.d.ts.map +1 -0
  140. package/dist/types/src/calls/speaking-monitor.d.ts +24 -0
  141. package/dist/types/src/calls/speaking-monitor.d.ts.map +1 -0
  142. package/dist/types/src/calls/types.d.ts +45 -0
  143. package/dist/types/src/calls/types.d.ts.map +1 -0
  144. package/dist/types/src/calls/util/audio.d.ts +6 -0
  145. package/dist/types/src/calls/util/audio.d.ts.map +1 -0
  146. package/dist/types/src/calls/util/calls-service.d.ts +72 -0
  147. package/dist/types/src/calls/util/calls-service.d.ts.map +1 -0
  148. package/dist/types/src/calls/util/history-cache.d.ts +11 -0
  149. package/dist/types/src/calls/util/history-cache.d.ts.map +1 -0
  150. package/dist/types/src/calls/util/index.d.ts +5 -0
  151. package/dist/types/src/calls/util/index.d.ts.map +1 -0
  152. package/dist/types/src/calls/util/media.d.ts +6 -0
  153. package/dist/types/src/calls/util/media.d.ts.map +1 -0
  154. package/dist/types/src/calls/util/stub-media-streams.d.ts +18 -0
  155. package/dist/types/src/calls/util/stub-media-streams.d.ts.map +1 -0
  156. package/dist/types/src/calls/util/task-scheduling.d.ts +17 -0
  157. package/dist/types/src/calls/util/task-scheduling.d.ts.map +1 -0
  158. package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
  159. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  160. package/dist/types/src/capabilities/call-manager.d.ts +5 -0
  161. package/dist/types/src/capabilities/call-manager.d.ts.map +1 -0
  162. package/dist/types/src/capabilities/capabilities.d.ts +13 -1
  163. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  164. package/dist/types/src/capabilities/index.d.ts +8 -179
  165. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  166. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  167. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  168. package/dist/types/src/capabilities/markdown.d.ts +2 -2
  169. package/dist/types/src/capabilities/markdown.d.ts.map +1 -1
  170. package/dist/types/src/capabilities/react-root.d.ts +7 -0
  171. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  172. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  173. package/dist/types/src/components/Call/Call.d.ts +17 -0
  174. package/dist/types/src/components/Call/Call.d.ts.map +1 -0
  175. package/dist/types/src/components/Call/Toolbar.d.ts +16 -0
  176. package/dist/types/src/components/Call/Toolbar.d.ts.map +1 -0
  177. package/dist/types/src/components/Call/index.d.ts +3 -0
  178. package/dist/types/src/components/Call/index.d.ts.map +1 -0
  179. package/dist/types/src/components/CallDebugPanel.d.ts +8 -0
  180. package/dist/types/src/components/CallDebugPanel.d.ts.map +1 -0
  181. package/dist/types/src/components/CallSidebar.d.ts +4 -0
  182. package/dist/types/src/components/CallSidebar.d.ts.map +1 -0
  183. package/dist/types/src/components/ChannelContainer.d.ts +14 -0
  184. package/dist/types/src/components/ChannelContainer.d.ts.map +1 -0
  185. package/dist/types/src/components/ChannelContainer.stories.d.ts +10 -0
  186. package/dist/types/src/components/ChannelContainer.stories.d.ts.map +1 -0
  187. package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
  188. package/dist/types/src/components/ChatContainer.d.ts +9 -7
  189. package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
  190. package/dist/types/src/components/CommentContainer.d.ts +11 -2
  191. package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
  192. package/dist/types/src/components/Comments.stories.d.ts +1 -1
  193. package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
  194. package/dist/types/src/components/CommentsContainer.d.ts +5 -9
  195. package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
  196. package/dist/types/src/components/Lobby/Lobby.d.ts +15 -0
  197. package/dist/types/src/components/Lobby/Lobby.d.ts.map +1 -0
  198. package/dist/types/src/components/Lobby/index.d.ts +2 -0
  199. package/dist/types/src/components/Lobby/index.d.ts.map +1 -0
  200. package/dist/types/src/components/Media/AudioStream.d.ts +6 -0
  201. package/dist/types/src/components/Media/AudioStream.d.ts.map +1 -0
  202. package/dist/types/src/components/Media/VideoObject.d.ts +15 -0
  203. package/dist/types/src/components/Media/VideoObject.d.ts.map +1 -0
  204. package/dist/types/src/components/Media/VideoObject.stories.d.ts +8 -0
  205. package/dist/types/src/components/Media/VideoObject.stories.d.ts.map +1 -0
  206. package/dist/types/src/components/Media/index.d.ts +3 -0
  207. package/dist/types/src/components/Media/index.d.ts.map +1 -0
  208. package/dist/types/src/components/MessageContainer.d.ts +5 -4
  209. package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
  210. package/dist/types/src/components/Participant/Participant.d.ts +6 -0
  211. package/dist/types/src/components/Participant/Participant.d.ts.map +1 -0
  212. package/dist/types/src/components/Participant/ParticipantGrid.d.ts +11 -0
  213. package/dist/types/src/components/Participant/ParticipantGrid.d.ts.map +1 -0
  214. package/dist/types/src/components/Participant/index.d.ts +3 -0
  215. package/dist/types/src/components/Participant/index.d.ts.map +1 -0
  216. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.d.ts +12 -0
  217. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.d.ts.map +1 -0
  218. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts +10 -0
  219. package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts.map +1 -0
  220. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.d.ts +32 -0
  221. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.d.ts.map +1 -0
  222. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts +23 -0
  223. package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts.map +1 -0
  224. package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts +21 -0
  225. package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts.map +1 -0
  226. package/dist/types/src/components/ResponsiveGrid/index.d.ts +4 -0
  227. package/dist/types/src/components/ResponsiveGrid/index.d.ts.map +1 -0
  228. package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
  229. package/dist/types/src/components/ThreadSettings.d.ts.map +1 -1
  230. package/dist/types/src/components/index.d.ts +5 -1
  231. package/dist/types/src/components/index.d.ts.map +1 -1
  232. package/dist/types/src/components/testing.d.ts +54 -119
  233. package/dist/types/src/components/testing.d.ts.map +1 -1
  234. package/dist/types/src/extensions/threads.d.ts.map +1 -1
  235. package/dist/types/src/hooks/analytics.d.ts +2 -2
  236. package/dist/types/src/hooks/analytics.d.ts.map +1 -1
  237. package/dist/types/src/hooks/index.d.ts +2 -1
  238. package/dist/types/src/hooks/index.d.ts.map +1 -1
  239. package/dist/types/src/hooks/useDebugMode.d.ts +2 -0
  240. package/dist/types/src/hooks/useDebugMode.d.ts.map +1 -0
  241. package/dist/types/src/hooks/useStatus.d.ts.map +1 -1
  242. package/dist/types/src/index.d.ts +1 -1
  243. package/dist/types/src/index.d.ts.map +1 -1
  244. package/dist/types/src/meta.d.ts.map +1 -1
  245. package/dist/types/src/peers.stories.d.ts +11 -0
  246. package/dist/types/src/peers.stories.d.ts.map +1 -0
  247. package/dist/types/src/testing/index.d.ts +2 -0
  248. package/dist/types/src/testing/index.d.ts.map +1 -0
  249. package/dist/types/src/testing/stories.d.ts +2 -0
  250. package/dist/types/src/testing/stories.d.ts.map +1 -0
  251. package/dist/types/src/translations.d.ts +35 -56
  252. package/dist/types/src/translations.d.ts.map +1 -1
  253. package/dist/types/src/types/index.d.ts +3 -0
  254. package/dist/types/src/types/index.d.ts.map +1 -0
  255. package/dist/types/src/types/schema.d.ts +317 -0
  256. package/dist/types/src/types/schema.d.ts.map +1 -0
  257. package/dist/types/src/types/types.d.ts +1015 -0
  258. package/dist/types/src/types/types.d.ts.map +1 -0
  259. package/dist/types/src/util.d.ts +2 -0
  260. package/dist/types/src/util.d.ts.map +1 -1
  261. package/dist/types/tsconfig.tsbuildinfo +1 -1
  262. package/package.json +60 -40
  263. package/src/ThreadPlugin.tsx +33 -14
  264. package/src/calls/call-manager.ts +242 -0
  265. package/src/calls/call-swarm-synchronizer.ts +350 -0
  266. package/src/calls/index.ts +10 -0
  267. package/src/calls/media-manager.ts +342 -0
  268. package/src/calls/speaking-monitor.ts +85 -0
  269. package/src/calls/types.ts +70 -0
  270. package/src/calls/util/audio.ts +58 -0
  271. package/src/calls/util/calls-service.ts +527 -0
  272. package/src/calls/util/history-cache.ts +27 -0
  273. package/src/calls/util/index.ts +8 -0
  274. package/src/calls/util/media.ts +52 -0
  275. package/src/calls/util/stub-media-streams.ts +73 -0
  276. package/src/calls/util/task-scheduling.ts +92 -0
  277. package/src/capabilities/app-graph-builder.ts +171 -25
  278. package/src/capabilities/call-manager.ts +19 -0
  279. package/src/capabilities/capabilities.ts +15 -1
  280. package/src/capabilities/index.ts +2 -0
  281. package/src/capabilities/intent-resolver.ts +76 -55
  282. package/src/capabilities/markdown.ts +54 -4
  283. package/src/capabilities/react-root.tsx +17 -0
  284. package/src/capabilities/react-surface.tsx +52 -27
  285. package/src/capabilities/state.ts +1 -1
  286. package/src/components/Call/Call.tsx +88 -0
  287. package/src/components/Call/Toolbar.tsx +182 -0
  288. package/src/components/Call/index.ts +6 -0
  289. package/src/components/CallDebugPanel.tsx +115 -0
  290. package/src/components/CallSidebar.tsx +17 -0
  291. package/src/components/ChannelContainer.stories.tsx +58 -0
  292. package/src/components/ChannelContainer.tsx +203 -0
  293. package/src/components/Chat.stories.tsx +9 -10
  294. package/src/components/ChatContainer.tsx +37 -25
  295. package/src/components/CommentContainer.tsx +56 -61
  296. package/src/components/Comments.stories.tsx +20 -16
  297. package/src/components/CommentsContainer.tsx +18 -28
  298. package/src/components/Lobby/Lobby.tsx +119 -0
  299. package/src/components/Lobby/index.ts +5 -0
  300. package/src/components/Media/AudioStream.tsx +67 -0
  301. package/src/components/Media/VideoObject.stories.tsx +52 -0
  302. package/src/components/Media/VideoObject.tsx +58 -0
  303. package/src/components/Media/index.ts +6 -0
  304. package/src/components/MessageContainer.tsx +31 -50
  305. package/src/components/Participant/Participant.tsx +57 -0
  306. package/src/components/Participant/ParticipantGrid.tsx +88 -0
  307. package/src/components/Participant/index.ts +6 -0
  308. package/src/components/ResponsiveGrid/ResponsiveContainer.stories.tsx +54 -0
  309. package/src/components/ResponsiveGrid/ResponsiveContainer.tsx +31 -0
  310. package/src/components/ResponsiveGrid/ResponsiveGrid.stories.tsx +156 -0
  311. package/src/components/ResponsiveGrid/ResponsiveGrid.tsx +341 -0
  312. package/src/components/ResponsiveGrid/ResponsiveGridItem.tsx +131 -0
  313. package/src/components/ResponsiveGrid/index.ts +7 -0
  314. package/src/components/ThreadComplementary.tsx +37 -42
  315. package/src/components/index.ts +4 -0
  316. package/src/components/testing.ts +4 -3
  317. package/src/extensions/threads.ts +42 -24
  318. package/src/hooks/analytics.ts +6 -2
  319. package/src/hooks/index.ts +2 -1
  320. package/src/hooks/useDebugMode.ts +25 -0
  321. package/src/index.ts +1 -1
  322. package/src/meta.ts +4 -3
  323. package/src/peers.stories.tsx +189 -0
  324. package/src/testing/index.ts +5 -0
  325. package/src/testing/stories.ts +50 -0
  326. package/src/translations.ts +46 -5
  327. package/src/types/index.ts +6 -0
  328. package/src/types/schema.ts +29 -0
  329. package/src/types/types.ts +110 -0
  330. package/src/typings/peermetrics-webrtc-stats.d.ts +30 -0
  331. package/src/util.ts +12 -0
  332. package/dist/lib/browser/ChatContainer-WC6KQDDU.mjs +0 -138
  333. package/dist/lib/browser/ChatContainer-WC6KQDDU.mjs.map +0 -7
  334. package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs +0 -41
  335. package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs.map +0 -7
  336. package/dist/lib/browser/chunk-AYNXYEKP.mjs.map +0 -7
  337. package/dist/lib/browser/chunk-EP6MRLY7.mjs.map +0 -7
  338. package/dist/lib/browser/chunk-FY5OCUP6.mjs +0 -207
  339. package/dist/lib/browser/chunk-FY5OCUP6.mjs.map +0 -7
  340. package/dist/lib/browser/chunk-Q66FBBJD.mjs +0 -17
  341. package/dist/lib/browser/chunk-Q66FBBJD.mjs.map +0 -7
  342. package/dist/lib/browser/chunk-WBJLUUOK.mjs.map +0 -7
  343. package/dist/lib/browser/chunk-WOG72DWE.mjs +0 -93
  344. package/dist/lib/browser/chunk-WOG72DWE.mjs.map +0 -7
  345. package/dist/lib/browser/intent-resolver-3UW6ROYT.mjs.map +0 -7
  346. package/dist/lib/browser/markdown-7KXBXCUS.mjs +0 -111
  347. package/dist/lib/browser/markdown-7KXBXCUS.mjs.map +0 -7
  348. package/dist/lib/browser/react-surface-F2AIYIUH.mjs +0 -437
  349. package/dist/lib/browser/react-surface-F2AIYIUH.mjs.map +0 -7
  350. package/dist/lib/browser/types.mjs +0 -10
  351. package/dist/lib/node/ChatContainer-LVYQ67PJ.cjs +0 -163
  352. package/dist/lib/node/ChatContainer-LVYQ67PJ.cjs.map +0 -7
  353. package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs +0 -57
  354. package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs.map +0 -7
  355. package/dist/lib/node/chunk-46BSFPBV.cjs +0 -113
  356. package/dist/lib/node/chunk-46BSFPBV.cjs.map +0 -7
  357. package/dist/lib/node/chunk-IX63EKGL.cjs.map +0 -7
  358. package/dist/lib/node/chunk-NQ5DTPOY.cjs.map +0 -7
  359. package/dist/lib/node/chunk-RIYLEDQ7.cjs +0 -232
  360. package/dist/lib/node/chunk-RIYLEDQ7.cjs.map +0 -7
  361. package/dist/lib/node/chunk-W25MBII2.cjs.map +0 -7
  362. package/dist/lib/node/intent-resolver-DC6HDNTX.cjs.map +0 -7
  363. package/dist/lib/node/markdown-L2WMCQKM.cjs +0 -121
  364. package/dist/lib/node/markdown-L2WMCQKM.cjs.map +0 -7
  365. package/dist/lib/node/react-surface-GP5IH5LN.cjs +0 -439
  366. package/dist/lib/node/react-surface-GP5IH5LN.cjs.map +0 -7
  367. package/dist/lib/node/types.cjs.map +0 -7
  368. package/dist/lib/node-esm/ChatContainer-WMOPMK5H.mjs +0 -139
  369. package/dist/lib/node-esm/ChatContainer-WMOPMK5H.mjs.map +0 -7
  370. package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs +0 -42
  371. package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs.map +0 -7
  372. package/dist/lib/node-esm/chunk-42F2DTMM.mjs.map +0 -7
  373. package/dist/lib/node-esm/chunk-7DOPZH6Y.mjs +0 -208
  374. package/dist/lib/node-esm/chunk-7DOPZH6Y.mjs.map +0 -7
  375. package/dist/lib/node-esm/chunk-E446MGLP.mjs +0 -94
  376. package/dist/lib/node-esm/chunk-E446MGLP.mjs.map +0 -7
  377. package/dist/lib/node-esm/chunk-QUTIMZJ4.mjs +0 -19
  378. package/dist/lib/node-esm/chunk-QUTIMZJ4.mjs.map +0 -7
  379. package/dist/lib/node-esm/chunk-T6E5EY7Z.mjs.map +0 -7
  380. package/dist/lib/node-esm/chunk-WLY2HMDB.mjs.map +0 -7
  381. package/dist/lib/node-esm/intent-resolver-OAMDKGAA.mjs.map +0 -7
  382. package/dist/lib/node-esm/markdown-ZKU23UVD.mjs +0 -112
  383. package/dist/lib/node-esm/markdown-ZKU23UVD.mjs.map +0 -7
  384. package/dist/lib/node-esm/react-surface-45GRKLWG.mjs +0 -438
  385. package/dist/lib/node-esm/react-surface-45GRKLWG.mjs.map +0 -7
  386. package/dist/lib/node-esm/types.mjs +0 -11
  387. package/dist/types/src/components/types.d.ts +0 -28
  388. package/dist/types/src/components/types.d.ts.map +0 -1
  389. package/dist/types/src/events.d.ts +0 -4
  390. package/dist/types/src/events.d.ts.map +0 -1
  391. package/dist/types/src/types.d.ts +0 -231
  392. package/dist/types/src/types.d.ts.map +0 -1
  393. package/src/components/types.ts +0 -32
  394. package/src/events.ts +0 -11
  395. package/src/types.ts +0 -100
  396. /package/dist/lib/browser/{types.mjs.map → ChannelContainer-LGYSQMC7.mjs.map} +0 -0
  397. /package/dist/lib/{node-esm/types.mjs.map → browser/ChatContainer-JWRI73OG.mjs.map} +0 -0
  398. /package/dist/lib/browser/{settings-2VZ7S4SK.mjs.map → settings-KVBQQSO5.mjs.map} +0 -0
  399. /package/dist/lib/node/{settings-7MGTJ46L.cjs.map → settings-H45P5YO2.cjs.map} +0 -0
  400. /package/dist/lib/node-esm/{settings-AJAZ3O47.mjs.map → settings-IJGUOU3F.mjs.map} +0 -0
@@ -0,0 +1,54 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+ import React from 'react';
9
+
10
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
11
+
12
+ import { ResponsiveContainer } from './ResponsiveContainer';
13
+ import translations from '../../translations';
14
+ import { VideoObject as VideoObjectComponent } from '../Media';
15
+
16
+ const meta: Meta<typeof ResponsiveContainer> = {
17
+ title: 'plugins/plugin-thread/ResponsiveContainer',
18
+ component: ResponsiveContainer,
19
+ decorators: [withTheme, withLayout({ fullscreen: true })],
20
+ parameters: {
21
+ translations,
22
+ },
23
+ };
24
+
25
+ export default meta;
26
+
27
+ type Story = StoryObj<typeof ResponsiveContainer>;
28
+
29
+ export const Image: Story = {
30
+ args: {
31
+ children: <img src='https://placehold.co/3200x1800/333/999?font=roboto&text=X' />,
32
+ },
33
+ };
34
+
35
+ export const Video: Story = {
36
+ // NOTE: The video's max size is 1280x720.
37
+ args: {
38
+ children: (
39
+ <video
40
+ src='https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4'
41
+ playsInline
42
+ autoPlay
43
+ muted
44
+ loop
45
+ />
46
+ ),
47
+ },
48
+ };
49
+
50
+ export const VideoObject: Story = {
51
+ args: {
52
+ children: <VideoObjectComponent classNames='rounded-md outline outline-primary-500' />,
53
+ },
54
+ };
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type PropsWithChildren } from 'react';
6
+
7
+ import { mx } from '@dxos/react-ui-theme';
8
+
9
+ /**
10
+ * A container component that maintains the child's aspect ratio while centering its content.
11
+ * The container will scale to fit within its parent's bounds while preserving the aspect ratio.
12
+ *
13
+ * @example
14
+ * <ResponsiveContainer>
15
+ * <VideoObject />
16
+ * </ResponsiveContainer>
17
+ */
18
+ export const ResponsiveContainer = ({ children }: PropsWithChildren) => {
19
+ return (
20
+ // Outer container that takes full size of parent.
21
+ <div role='none' className='relative flex w-full h-full'>
22
+ {/* Absolute positioning layer for centering content. */}
23
+ <div role='none' className='absolute inset-0 flex justify-center items-center'>
24
+ {/* Content container that maintains given aspect ratio and proper scaling. */}
25
+ <div role='none' className={mx('max-h-full max-w-full w-auto h-auto aspect-video')}>
26
+ {children}
27
+ </div>
28
+ </div>
29
+ </div>
30
+ );
31
+ };
@@ -0,0 +1,156 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import '@dxos-theme';
6
+
7
+ import { type StoryObj, type Meta } from '@storybook/react';
8
+ import React, { useEffect, useState } from 'react';
9
+
10
+ import { faker } from '@dxos/random';
11
+ import { withTheme, withLayout } from '@dxos/storybook-utils';
12
+
13
+ import { ResponsiveGrid, type ResponsiveGridProps } from './ResponsiveGrid';
14
+ import { ResponsiveGridItem, type ResponsiveGridItemProps } from './ResponsiveGridItem';
15
+ import translations from '../../translations';
16
+
17
+ type TestItem = {
18
+ id: string;
19
+ name: string;
20
+ type?: 'image' | 'video';
21
+ imageUrl?: string;
22
+ videoUrl?: string;
23
+ };
24
+
25
+ const TestCell = ({ item, ...props }: ResponsiveGridItemProps<TestItem>) => {
26
+ const [speaking, setSpeaking] = useState(false);
27
+ const [wave, setWave] = useState(false);
28
+ const [mute, setMute] = useState(false);
29
+ useEffect(() => {
30
+ const interval = setInterval(() => {
31
+ const mute = Math.random() > 0.7;
32
+ setMute(() => mute);
33
+ setSpeaking(() => !mute && Math.random() > 0.5);
34
+ setWave(() => Math.random() > 0.7);
35
+ }, 3_000);
36
+
37
+ return () => clearInterval(interval);
38
+ }, []);
39
+
40
+ return (
41
+ <ResponsiveGridItem {...props} item={item} name={item.name} mute={mute} speaking={speaking} wave={wave}>
42
+ {item.type === 'image' && <img className='aspect-video object-contain' src={item.imageUrl} />}
43
+ {item.type === 'video' && (
44
+ <video
45
+ className='w-full aspect-video object-cover rounded-md'
46
+ src={item.videoUrl}
47
+ playsInline
48
+ autoPlay
49
+ loop
50
+ muted
51
+ />
52
+ )}
53
+ </ResponsiveGridItem>
54
+ );
55
+ };
56
+
57
+ type StoryProps = ResponsiveGridProps<TestItem> & { random?: boolean; autoHideGallery?: boolean };
58
+
59
+ const meta: Meta<StoryProps> = {
60
+ title: 'plugins/plugin-thread/ResponsiveGrid',
61
+ component: ResponsiveGrid,
62
+ render: (args) => {
63
+ const [pinned, setPinned] = useState<string | undefined>(
64
+ args.pinned ?? args.items.length > 1 ? args.items[0]?.id : undefined,
65
+ );
66
+ const [items, setItems] = useState<TestItem[]>(args.items);
67
+ useEffect(() => {
68
+ if (!args.random) {
69
+ return;
70
+ }
71
+
72
+ const interval = setInterval(() => {
73
+ setItems((items) => {
74
+ const p = Math.random();
75
+ if (p < 0.5) {
76
+ return items;
77
+ } else if (p < 0.99 && items.length > 1) {
78
+ return items.slice(0, -1);
79
+ } else {
80
+ return [...items, createItem(items[0]?.type)];
81
+ }
82
+ });
83
+ }, 3_000);
84
+
85
+ return () => clearInterval(interval);
86
+ }, []);
87
+
88
+ return (
89
+ <div className='grid grow p-4'>
90
+ <ResponsiveGrid {...args} Cell={TestCell} items={items} pinned={pinned} onPinnedChange={setPinned} />
91
+ </div>
92
+ );
93
+ },
94
+ decorators: [withTheme, withLayout({ fullscreen: true, classNames: 'justify-center' })],
95
+ parameters: {
96
+ translations,
97
+ },
98
+ };
99
+
100
+ const videoUrls = [
101
+ 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4',
102
+ 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerEscapes.mp4',
103
+ 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerJoyrides.mp4',
104
+ 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerMeltdowns.mp4',
105
+ ];
106
+
107
+ const createItem = (type?: 'image' | 'video') => {
108
+ const id = faker.string.uuid();
109
+ return {
110
+ id,
111
+ type,
112
+ name: faker.person.fullName(),
113
+ imageUrl: `https://placehold.co/3200x1800/333/999?font=roboto&text=${id.slice(0, 2)}`,
114
+ videoUrl: faker.helpers.arrayElement(videoUrls),
115
+ };
116
+ };
117
+
118
+ export default meta;
119
+
120
+ type Story = StoryObj<StoryProps>;
121
+
122
+ // TODO(burdon): Story to join/leave repeatedly to test stable position.
123
+
124
+ export const Default: Story = {
125
+ args: {
126
+ random: true,
127
+ items: Array.from({ length: 8 }, (_, i) => createItem('video')),
128
+ },
129
+ };
130
+
131
+ export const Fullscreen: Story = {
132
+ args: {
133
+ random: true,
134
+ autoHideGallery: true,
135
+ items: Array.from({ length: 8 }, (_, i) => createItem('video')),
136
+ },
137
+ };
138
+
139
+ export const Images: Story = {
140
+ args: {
141
+ debug: true,
142
+ items: Array.from({ length: 8 }, (_, i) => createItem('image')),
143
+ },
144
+ };
145
+
146
+ export const Solo: Story = {
147
+ args: {
148
+ items: Array.from({ length: 1 }, (_, i) => createItem('image')),
149
+ },
150
+ };
151
+
152
+ export const TwoUp: Story = {
153
+ args: {
154
+ items: Array.from({ length: 2 }, (_, i) => createItem('image')),
155
+ },
156
+ };
@@ -0,0 +1,341 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React, { type ComponentType, type FC, useLayoutEffect, useState, useEffect, useMemo, useCallback } from 'react';
6
+ import { useResizeDetector } from 'react-resize-detector';
7
+
8
+ import { invariant } from '@dxos/invariant';
9
+ import { type ThemedClassName } from '@dxos/react-ui';
10
+ import { type Size } from '@dxos/react-ui-dnd';
11
+ import { mx } from '@dxos/react-ui-theme';
12
+
13
+ import { ResponsiveContainer } from './ResponsiveContainer';
14
+ import { type ResponsiveGridItemProps } from './ResponsiveGridItem';
15
+
16
+ const ASPECT_RATIO = 16 / 9;
17
+ const MIN_GALLERY_HEIGHT = 250;
18
+
19
+ const DEFAULT_GAP = 8;
20
+
21
+ const maxImageSize = 'w-[2560px] h-[1440px]';
22
+
23
+ /**
24
+ * Props for the ResponsiveGrid component.
25
+ */
26
+ export type ResponsiveGridProps<T extends object = any> = ThemedClassName<{
27
+ /** Cell component. */
28
+ Cell: ComponentType<ResponsiveGridItemProps<T>>;
29
+
30
+ /** Space between grid items in pixels. */
31
+ gap?: number;
32
+
33
+ /** Function to get the ID of an item. */
34
+ getId?: (item: T) => string;
35
+
36
+ /** Array of items to display in the grid. */
37
+ items: T[];
38
+
39
+ /** ID of the pinned item. */
40
+ pinned?: string;
41
+
42
+ /** Whether to hide the gallery (unpinned items) when an item is pinned. */
43
+ autoHideGallery?: boolean;
44
+
45
+ /** Whether the divider is resizable. */
46
+ resizable?: boolean;
47
+
48
+ /** Debug mode. */
49
+ debug?: boolean;
50
+
51
+ /** Callback when the pinned item changes. */
52
+ onPinnedChange?: (pinned: string | undefined) => void;
53
+ }>;
54
+
55
+ const defaultGetId: ResponsiveGridProps<any>['getId'] = (item: any) => item.id;
56
+
57
+ /**
58
+ * A responsive grid component that automatically adjusts its layout to optimize space usage.
59
+ * Maintains aspect ratio of items while ensuring uniform gaps between them.
60
+ */
61
+ export const ResponsiveGrid = <T extends object = any>({
62
+ classNames,
63
+ Cell,
64
+ gap = DEFAULT_GAP,
65
+ getId = defaultGetId,
66
+ items,
67
+ pinned,
68
+ autoHideGallery = false,
69
+ debug,
70
+ onPinnedChange,
71
+ }: ResponsiveGridProps<T>) => {
72
+ const {
73
+ width: containerWidth = 0,
74
+ height: containerHeight = 0,
75
+ ref: containerRef,
76
+ } = useResizeDetector<HTMLDivElement>({ refreshRate: 100 });
77
+ const [dividerHeight, setDividerHeight] = useState<Size>(0);
78
+ useEffect(() => {
79
+ if (containerWidth && containerHeight) {
80
+ const { height } = fitAspectRatio(containerWidth, containerHeight, ASPECT_RATIO);
81
+ setDividerHeight(Math.min(height, containerHeight - MIN_GALLERY_HEIGHT));
82
+ }
83
+ }, [containerWidth, containerHeight]);
84
+
85
+ const pinnedItem = useMemo(() => items.find((item) => getId(item) === pinned), [items, pinned]);
86
+ const mainItems = useMemo(() => items.filter((item) => getId(item) !== pinned), [items, pinned]);
87
+ const hideGallery = autoHideGallery && pinnedItem !== undefined;
88
+
89
+ //
90
+ // Recalculate optimal layout when container size or items change.
91
+ //
92
+ const [{ time, columns, cellWidth }, setLayout] = useState<Layout>({ time: 0, columns: 0, cellWidth: 0 });
93
+ const { width = 0, height = 0, ref: gridContainerRef } = useResizeDetector<HTMLDivElement>({ refreshRate: 200 });
94
+ useEffect(() => {
95
+ if (containerHeight && width && height) {
96
+ const layout = calculateLayout(width, height, mainItems.length, gap);
97
+ setLayout({ time: Date.now(), ...layout });
98
+ }
99
+ }, [containerHeight, width, height, mainItems.length, gap, pinned]);
100
+
101
+ //
102
+ // We use the browser to layout invisible divs and then calculate the absolute position of these items,
103
+ // which are animated into position.
104
+ //
105
+ const [bounds, setBounds] = useState<[T, DOMRectBounds][]>([]);
106
+ useLayoutEffect(() => {
107
+ if ((!hideGallery && !gridContainerRef.current) || !containerHeight) {
108
+ return;
109
+ }
110
+
111
+ // TODO(burdon): Consider directly setting bounds on DOM elementsinstead of state update?
112
+ const t = setTimeout(() => {
113
+ setBounds(
114
+ items
115
+ .filter((item) => !hideGallery || item === pinnedItem)
116
+ .map((item) => {
117
+ invariant(containerRef.current);
118
+ const el = containerRef.current.querySelector(`[data-grid-item="${getId(item)}"]`);
119
+ if (!el) {
120
+ return null;
121
+ }
122
+
123
+ const bounds = getRelativeBounds(containerRef.current, el as HTMLElement);
124
+ return [item, bounds];
125
+ })
126
+ .filter((item): item is [T, DOMRectBounds] => item !== null),
127
+ );
128
+ });
129
+
130
+ return () => clearTimeout(t);
131
+ }, [containerHeight, items, pinnedItem, hideGallery, time]);
132
+
133
+ const handleClick = useCallback(
134
+ (item: T) => onPinnedChange?.(getId(item) === pinned ? undefined : getId(item)),
135
+ [pinned, onPinnedChange],
136
+ );
137
+
138
+ return (
139
+ <div ref={containerRef} className={mx('relative w-full h-full', classNames)}>
140
+ {/* Placeholder elements to calculate layout. */}
141
+ <div className='absolute inset-0 flex flex-col grow gap-2'>
142
+ {/* Pinned item. */}
143
+ {pinnedItem && (
144
+ <div
145
+ className={mx('flex grow-[2] shrink overflow-hidden justify-center items-center', hideGallery && 'h-full')}
146
+ style={hideGallery ? {} : { height: dividerHeight }}
147
+ >
148
+ <SoloItem id={getId(pinnedItem)} debug={debug} />
149
+ </div>
150
+ )}
151
+
152
+ {/* Gallery. */}
153
+ {!hideGallery && (
154
+ <div
155
+ ref={gridContainerRef}
156
+ className='flex grow-[1] overflow-hidden justify-center items-center'
157
+ style={hideGallery ? {} : { minHeight: MIN_GALLERY_HEIGHT }}
158
+ >
159
+ {mainItems.length === 1 && (
160
+ <div style={{ width: cellWidth }} className='flex h-full'>
161
+ <SoloItem id={getId(mainItems[0])} debug={debug} />
162
+ </div>
163
+ )}
164
+
165
+ {mainItems.length > 1 && columns > 0 && (
166
+ <div
167
+ role='grid'
168
+ style={{
169
+ display: 'grid',
170
+ gridTemplateColumns: `repeat(${columns}, ${cellWidth}px)`,
171
+ gap: `${gap}px`,
172
+ }}
173
+ >
174
+ {mainItems.map((item) => (
175
+ <div
176
+ key={getId(item)}
177
+ {...{ 'data-grid-item': getId(item) }}
178
+ className={mx(
179
+ 'aspect-video max-h-full max-w-full w-auto h-auto',
180
+ debug && 'border border-primary-500',
181
+ )}
182
+ />
183
+ ))}
184
+ </div>
185
+ )}
186
+ </div>
187
+ )}
188
+ </div>
189
+
190
+ {/* Absolutely positioned items. */}
191
+ <div className={mx(debug && 'opacity-10')}>
192
+ {bounds.map(([item, bounds]) => (
193
+ <Cell
194
+ key={getId(item)}
195
+ classNames='absolute transition-all duration-500'
196
+ item={item}
197
+ style={bounds}
198
+ pinned={getId(item) === pinned}
199
+ onClick={items.length > 1 ? handleClick : undefined}
200
+ />
201
+ ))}
202
+ </div>
203
+ </div>
204
+ );
205
+ };
206
+
207
+ const SoloItem: FC<Pick<ResponsiveGridProps, 'debug'> & { id: string }> = ({ debug, id }) => {
208
+ return (
209
+ <ResponsiveContainer>
210
+ <div
211
+ {...{ 'data-grid-item': id }}
212
+ className={mx('aspect-video overflow-hidden', debug && 'z-20 border-2 border-primary-500')}
213
+ >
214
+ {/* Maximum size placeholder image forces aspect ratio. */}
215
+ <img alt='placeholder video' className={mx('opacity-0', maxImageSize)} />
216
+ </div>
217
+ </ResponsiveContainer>
218
+ );
219
+ };
220
+
221
+ type DOMRectBounds = Pick<DOMRect, 'top' | 'left' | 'width' | 'height'>;
222
+
223
+ // TODO(burdon): Reconcile with react-ui-gameboard.
224
+ const getRelativeBounds = (container: HTMLElement, el: HTMLElement): DOMRectBounds => {
225
+ const containerRect = container.getBoundingClientRect();
226
+ const elementRect = el.getBoundingClientRect();
227
+ return {
228
+ top: elementRect.top - containerRect.top,
229
+ left: elementRect.left - containerRect.left,
230
+ width: elementRect.width,
231
+ height: elementRect.height,
232
+ };
233
+ };
234
+
235
+ type Layout = {
236
+ time?: number;
237
+ columns: number;
238
+ cellWidth: number;
239
+ };
240
+
241
+ /**
242
+ * Calculates the optimal number of columns for a grid layout that minimizes wasted space.
243
+ * @param containerWidth - Width of the container in pixels.
244
+ * @param containerHeight - Height of the container in pixels.
245
+ * @param count - Total number of items to display.
246
+ * @param gap - Space between items in pixels.
247
+ * @param aspectRatio - Desired aspect ratio of items (width/height).
248
+ * @returns The optimal number of columns.
249
+ */
250
+ const calculateLayout = (
251
+ containerWidth: number,
252
+ containerHeight: number,
253
+ count: number,
254
+ gap: number,
255
+ aspectRatio = ASPECT_RATIO,
256
+ ): Layout => {
257
+ if (count === 0) {
258
+ return { columns: 1, cellWidth: 1 };
259
+ }
260
+
261
+ let minWastedSpace = Infinity;
262
+ let bestColumns = 1;
263
+ let cellWidth = 0;
264
+
265
+ // Try different rows counts to find the optimal layout that minimizes wasted space.
266
+ for (let rows = 1; rows <= count; rows++) {
267
+ const cols = Math.ceil(count / rows);
268
+
269
+ // Calculate available space accounting for gaps.
270
+ const { width: itemWidth, height: itemHeight } = fitItemsToGrid(
271
+ containerWidth,
272
+ containerHeight,
273
+ cols,
274
+ rows,
275
+ aspectRatio,
276
+ gap,
277
+ );
278
+
279
+ // Calculate total wasted space for this configuration.
280
+ const usedWidth = itemWidth * cols + gap * cols - 1;
281
+ const usedHeight = itemHeight * rows + gap * rows - 1;
282
+
283
+ // Determine if optimal.
284
+ const wastedSpace = containerWidth * containerHeight - usedWidth * usedHeight;
285
+ if (wastedSpace < minWastedSpace) {
286
+ minWastedSpace = wastedSpace;
287
+ bestColumns = cols;
288
+ cellWidth = itemWidth;
289
+ }
290
+ }
291
+
292
+ return { columns: bestColumns, cellWidth };
293
+ };
294
+
295
+ /**
296
+ * Returns the size of the largest rectangle with the given aspect ration that fits within the grid.
297
+ */
298
+ const fitItemsToGrid = (
299
+ outerWidth: number,
300
+ outerHeight: number,
301
+ numCols: number,
302
+ numRows: number,
303
+ aspectRatio: number,
304
+ gap: number,
305
+ ): { width: number; height: number } => {
306
+ // Calculate available space accounting for gaps.
307
+ const availableWidth = outerWidth - gap * (numCols - 1);
308
+ const availableHeight = outerHeight - gap * (numRows - 1);
309
+
310
+ // Calculate max dimensions.
311
+ const maxCellWidth = availableWidth / numCols;
312
+ const maxCellHeight = availableHeight / numRows;
313
+
314
+ // Use fitAspectRatio to get dimensions that fit within max cell size while maintaining the desired aspect ratio
315
+ return fitAspectRatio(maxCellWidth, maxCellHeight, aspectRatio);
316
+ };
317
+
318
+ /**
319
+ * Returns the size of the largest rectangle the given aspect ratio that fits within the outer rectangle.
320
+ */
321
+ const fitAspectRatio = (
322
+ outerWidth: number,
323
+ outerHeight: number,
324
+ aspectRatio: number,
325
+ ): { width: number; height: number } => {
326
+ if (outerWidth <= 0 || outerHeight <= 0 || aspectRatio <= 0) {
327
+ return { width: 0, height: 0 };
328
+ }
329
+
330
+ // First try fitting to width.
331
+ let width = outerWidth;
332
+ let height = width / aspectRatio;
333
+
334
+ // If too tall, fit to height instead.
335
+ if (height > outerHeight) {
336
+ height = outerHeight;
337
+ width = height * aspectRatio;
338
+ }
339
+
340
+ return { width, height };
341
+ };