@pretty-chitty/core 1.1.2 → 1.2.1

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 (351) hide show
  1. package/dist/components/BottomBarButton.d.ts +2 -1
  2. package/dist/components/BottomBarButton.d.ts.map +1 -1
  3. package/dist/components/BottomBarButton.js +2 -1
  4. package/dist/components/BottomBarButton.js.map +1 -1
  5. package/dist/components/ClientTrustMatchViewer.d.ts +4 -1
  6. package/dist/components/ClientTrustMatchViewer.d.ts.map +1 -1
  7. package/dist/components/ClientTrustMatchViewer.js +11 -4
  8. package/dist/components/ClientTrustMatchViewer.js.map +1 -1
  9. package/dist/components/ContextGalleryDisplay.js +1 -1
  10. package/dist/components/ContextGalleryDisplay.js.map +1 -1
  11. package/dist/components/DemoWrapper.d.ts +9 -0
  12. package/dist/components/DemoWrapper.d.ts.map +1 -0
  13. package/dist/components/DemoWrapper.js +76 -0
  14. package/dist/components/DemoWrapper.js.map +1 -0
  15. package/dist/components/FullScreenGalleryDisplay.d.ts +2 -0
  16. package/dist/components/FullScreenGalleryDisplay.d.ts.map +1 -0
  17. package/dist/components/FullScreenGalleryDisplay.js +56 -0
  18. package/dist/components/FullScreenGalleryDisplay.js.map +1 -0
  19. package/dist/components/Gallery/AnimationController.d.ts +19 -0
  20. package/dist/components/Gallery/AnimationController.d.ts.map +1 -0
  21. package/dist/components/Gallery/AnimationController.js +82 -0
  22. package/dist/components/Gallery/AnimationController.js.map +1 -0
  23. package/dist/components/Gallery/BuiltItem.d.ts +58 -0
  24. package/dist/components/Gallery/BuiltItem.d.ts.map +1 -0
  25. package/dist/components/Gallery/BuiltItem.js +313 -0
  26. package/dist/components/Gallery/BuiltItem.js.map +1 -0
  27. package/dist/components/Gallery/CameraManager.d.ts +14 -0
  28. package/dist/components/Gallery/CameraManager.d.ts.map +1 -0
  29. package/dist/components/Gallery/CameraManager.js +43 -0
  30. package/dist/components/Gallery/CameraManager.js.map +1 -0
  31. package/dist/components/Gallery/GalleryController.d.ts +30 -0
  32. package/dist/components/Gallery/GalleryController.d.ts.map +1 -0
  33. package/dist/components/Gallery/GalleryController.js +137 -0
  34. package/dist/components/Gallery/GalleryController.js.map +1 -0
  35. package/dist/components/Gallery/GalleryViewer.d.ts +18 -0
  36. package/dist/components/Gallery/GalleryViewer.d.ts.map +1 -0
  37. package/dist/components/Gallery/GalleryViewer.js +141 -0
  38. package/dist/components/Gallery/GalleryViewer.js.map +1 -0
  39. package/dist/components/Gallery/LayoutManager.d.ts +49 -0
  40. package/dist/components/Gallery/LayoutManager.d.ts.map +1 -0
  41. package/dist/components/Gallery/LayoutManager.js +132 -0
  42. package/dist/components/Gallery/LayoutManager.js.map +1 -0
  43. package/dist/components/Gallery/constants.d.ts +13 -0
  44. package/dist/components/Gallery/constants.d.ts.map +1 -0
  45. package/dist/components/Gallery/constants.js +13 -0
  46. package/dist/components/Gallery/constants.js.map +1 -0
  47. package/dist/components/Gallery/index.d.ts +3 -0
  48. package/dist/components/Gallery/index.d.ts.map +1 -0
  49. package/dist/components/Gallery/index.js +2 -0
  50. package/dist/components/Gallery/index.js.map +1 -0
  51. package/dist/components/Gallery/types.d.ts +45 -0
  52. package/dist/components/Gallery/types.d.ts.map +1 -0
  53. package/dist/components/Gallery/types.js +2 -0
  54. package/dist/components/Gallery/types.js.map +1 -0
  55. package/dist/components/GalleryPlayground.js +1 -1
  56. package/dist/components/GalleryPlayground.js.map +1 -1
  57. package/dist/components/GalleryViewer.d.ts +2 -47
  58. package/dist/components/GalleryViewer.d.ts.map +1 -1
  59. package/dist/components/GalleryViewer.js +3 -569
  60. package/dist/components/GalleryViewer.js.map +1 -1
  61. package/dist/components/GameDesigner.d.ts.map +1 -1
  62. package/dist/components/GameDesigner.js +13 -2
  63. package/dist/components/GameDesigner.js.map +1 -1
  64. package/dist/components/InlineGalleryDisplay.d.ts +2 -0
  65. package/dist/components/InlineGalleryDisplay.d.ts.map +1 -0
  66. package/dist/components/InlineGalleryDisplay.js +69 -0
  67. package/dist/components/InlineGalleryDisplay.js.map +1 -0
  68. package/dist/components/LiveButton.d.ts.map +1 -1
  69. package/dist/components/LiveButton.js +1 -1
  70. package/dist/components/LiveButton.js.map +1 -1
  71. package/dist/components/MatchViewer.d.ts +0 -1
  72. package/dist/components/MatchViewer.d.ts.map +1 -1
  73. package/dist/components/MatchViewer.js +20 -10
  74. package/dist/components/MatchViewer.js.map +1 -1
  75. package/dist/components/Panel/MultiPanel.d.ts.map +1 -1
  76. package/dist/components/Panel/MultiPanel.js +14 -7
  77. package/dist/components/Panel/MultiPanel.js.map +1 -1
  78. package/dist/components/Panel/PanelContents.d.ts +2 -1
  79. package/dist/components/Panel/PanelContents.d.ts.map +1 -1
  80. package/dist/components/Panel/PanelContents.js +16 -11
  81. package/dist/components/Panel/PanelContents.js.map +1 -1
  82. package/dist/components/Panel/SinglePanel.d.ts.map +1 -1
  83. package/dist/components/Panel/SinglePanel.js +4 -3
  84. package/dist/components/Panel/SinglePanel.js.map +1 -1
  85. package/dist/components/Panel/ViewerWrapper.d.ts +1 -3
  86. package/dist/components/Panel/ViewerWrapper.d.ts.map +1 -1
  87. package/dist/components/Panel/ViewerWrapper.js +2 -3
  88. package/dist/components/Panel/ViewerWrapper.js.map +1 -1
  89. package/dist/components/Playground.d.ts.map +1 -1
  90. package/dist/components/Playground.js +353 -50
  91. package/dist/components/Playground.js.map +1 -1
  92. package/dist/components/PromptControls.d.ts.map +1 -1
  93. package/dist/components/PromptControls.js +39 -6
  94. package/dist/components/PromptControls.js.map +1 -1
  95. package/dist/components/ServerTrustMatchViewer.d.ts +11 -0
  96. package/dist/components/ServerTrustMatchViewer.d.ts.map +1 -0
  97. package/dist/components/ServerTrustMatchViewer.js +26 -0
  98. package/dist/components/ServerTrustMatchViewer.js.map +1 -0
  99. package/dist/components/Viewer.d.ts.map +1 -1
  100. package/dist/components/Viewer.js +54 -14
  101. package/dist/components/Viewer.js.map +1 -1
  102. package/dist/game/Chit.d.ts +6 -2
  103. package/dist/game/Chit.d.ts.map +1 -1
  104. package/dist/game/Chit.js +50 -6
  105. package/dist/game/Chit.js.map +1 -1
  106. package/dist/game/ClientTimeState.d.ts +1 -0
  107. package/dist/game/ClientTimeState.d.ts.map +1 -1
  108. package/dist/game/ClientTimeState.js +4 -1
  109. package/dist/game/ClientTimeState.js.map +1 -1
  110. package/dist/game/GalleryItemChitChildrenSource.d.ts +1 -0
  111. package/dist/game/GalleryItemChitChildrenSource.d.ts.map +1 -1
  112. package/dist/game/GalleryItemChitChildrenSource.js +1 -0
  113. package/dist/game/GalleryItemChitChildrenSource.js.map +1 -1
  114. package/dist/game/GalleryItemRawSource.d.ts +1 -0
  115. package/dist/game/GalleryItemRawSource.d.ts.map +1 -1
  116. package/dist/game/GalleryItemRawSource.js +1 -0
  117. package/dist/game/GalleryItemRawSource.js.map +1 -1
  118. package/dist/game/Game.d.ts +2 -1
  119. package/dist/game/Game.d.ts.map +1 -1
  120. package/dist/game/GameButton.d.ts +1 -0
  121. package/dist/game/GameButton.d.ts.map +1 -1
  122. package/dist/game/GameButton.js +2 -0
  123. package/dist/game/GameButton.js.map +1 -1
  124. package/dist/game/GameDeckChit.d.ts +6 -0
  125. package/dist/game/GameDeckChit.d.ts.map +1 -1
  126. package/dist/game/GameDeckChit.js +32 -5
  127. package/dist/game/GameDeckChit.js.map +1 -1
  128. package/dist/game/GameMetaData.d.ts +18 -0
  129. package/dist/game/GameMetaData.d.ts.map +1 -0
  130. package/dist/game/GameMetaData.js +2 -0
  131. package/dist/game/GameMetaData.js.map +1 -0
  132. package/dist/game/GameTheme.d.ts +11 -0
  133. package/dist/game/GameTheme.d.ts.map +1 -1
  134. package/dist/game/GameTheme.js +19 -4
  135. package/dist/game/GameTheme.js.map +1 -1
  136. package/dist/game/Match.d.ts +2 -1
  137. package/dist/game/Match.d.ts.map +1 -1
  138. package/dist/game/Match.js +4 -3
  139. package/dist/game/Match.js.map +1 -1
  140. package/dist/game/MatchStorage.d.ts +10 -0
  141. package/dist/game/MatchStorage.d.ts.map +1 -1
  142. package/dist/game/MatchStorage.js +29 -0
  143. package/dist/game/MatchStorage.js.map +1 -1
  144. package/dist/game/ModalState.d.ts +1 -0
  145. package/dist/game/ModalState.d.ts.map +1 -1
  146. package/dist/game/ModalState.js +1 -0
  147. package/dist/game/ModalState.js.map +1 -1
  148. package/dist/game/OrderedOutlet.d.ts.map +1 -1
  149. package/dist/game/OrderedOutlet.js +6 -6
  150. package/dist/game/OrderedOutlet.js.map +1 -1
  151. package/dist/game/Pick.d.ts +11 -1
  152. package/dist/game/Pick.d.ts.map +1 -1
  153. package/dist/game/Pick.js +83 -1
  154. package/dist/game/Pick.js.map +1 -1
  155. package/dist/game/PlayerChit.d.ts +2 -1
  156. package/dist/game/PlayerChit.d.ts.map +1 -1
  157. package/dist/game/PlayerChit.js +13 -1
  158. package/dist/game/PlayerChit.js.map +1 -1
  159. package/dist/game/PlayerInfo.d.ts +2 -1
  160. package/dist/game/PlayerInfo.d.ts.map +1 -1
  161. package/dist/game/PlayerInfo.js +20 -3
  162. package/dist/game/PlayerInfo.js.map +1 -1
  163. package/dist/game/Prompt.d.ts +1 -11
  164. package/dist/game/Prompt.d.ts.map +1 -1
  165. package/dist/game/Prompt.js +0 -32
  166. package/dist/game/Prompt.js.map +1 -1
  167. package/dist/game/RootChit.d.ts +4 -0
  168. package/dist/game/RootChit.d.ts.map +1 -1
  169. package/dist/game/RootChit.js +36 -1
  170. package/dist/game/RootChit.js.map +1 -1
  171. package/dist/game/Turn.d.ts +9 -8
  172. package/dist/game/Turn.d.ts.map +1 -1
  173. package/dist/game/Turn.js +34 -34
  174. package/dist/game/Turn.js.map +1 -1
  175. package/dist/game/TurnState.d.ts +3 -2
  176. package/dist/game/TurnState.d.ts.map +1 -1
  177. package/dist/game/TurnState.js +22 -2
  178. package/dist/game/TurnState.js.map +1 -1
  179. package/dist/game/badAiTransport/BadAIClientPrompts.d.ts +14 -0
  180. package/dist/game/badAiTransport/BadAIClientPrompts.d.ts.map +1 -0
  181. package/dist/game/badAiTransport/BadAIClientPrompts.js +50 -0
  182. package/dist/game/badAiTransport/BadAIClientPrompts.js.map +1 -0
  183. package/dist/game/clientTransport/ClientTime.js +1 -1
  184. package/dist/game/clientTransport/ClientTime.js.map +1 -1
  185. package/dist/game/serverTransport/ServerTime.d.ts.map +1 -1
  186. package/dist/game/serverTransport/ServerTime.js +1 -1
  187. package/dist/game/serverTransport/ServerTime.js.map +1 -1
  188. package/dist/hooks/useButtonGalleriesOptions.d.ts +4 -0
  189. package/dist/hooks/useButtonGalleriesOptions.d.ts.map +1 -0
  190. package/dist/hooks/useButtonGalleriesOptions.js +7 -0
  191. package/dist/hooks/useButtonGalleriesOptions.js.map +1 -0
  192. package/dist/hooks/useEventChannelState.js +1 -1
  193. package/dist/hooks/useEventChannelState.js.map +1 -1
  194. package/dist/hooks/useLoadingStates.d.ts +17 -0
  195. package/dist/hooks/useLoadingStates.d.ts.map +1 -0
  196. package/dist/hooks/useLoadingStates.js +44 -0
  197. package/dist/hooks/useLoadingStates.js.map +1 -0
  198. package/dist/hooks/useModalState.d.ts +2 -1
  199. package/dist/hooks/useModalState.d.ts.map +1 -1
  200. package/dist/hooks/useModalState.js +2 -2
  201. package/dist/hooks/useModalState.js.map +1 -1
  202. package/dist/hooks/usePanelPositioning.d.ts +0 -1
  203. package/dist/hooks/usePanelPositioning.d.ts.map +1 -1
  204. package/dist/hooks/usePanelPositioning.js.map +1 -1
  205. package/dist/index.d.ts +7 -5
  206. package/dist/index.d.ts.map +1 -1
  207. package/dist/index.js +17 -1
  208. package/dist/index.js.map +1 -1
  209. package/dist/rendering/CameraWrapperPerspective.d.ts.map +1 -1
  210. package/dist/rendering/CameraWrapperPerspective.js +1 -1
  211. package/dist/rendering/CameraWrapperPerspective.js.map +1 -1
  212. package/dist/rendering/ChitGalleryItemInstance.d.ts +2 -0
  213. package/dist/rendering/ChitGalleryItemInstance.d.ts.map +1 -1
  214. package/dist/rendering/ChitGalleryItemInstance.js +2 -0
  215. package/dist/rendering/ChitGalleryItemInstance.js.map +1 -1
  216. package/dist/rendering/ChitRenderInstance.d.ts +13 -2
  217. package/dist/rendering/ChitRenderInstance.d.ts.map +1 -1
  218. package/dist/rendering/ChitRenderInstance.js +95 -47
  219. package/dist/rendering/ChitRenderInstance.js.map +1 -1
  220. package/dist/rendering/ChitRenderSpec.d.ts +3 -0
  221. package/dist/rendering/ChitRenderSpec.d.ts.map +1 -1
  222. package/dist/rendering/ChitRenderSpec.js +3 -0
  223. package/dist/rendering/ChitRenderSpec.js.map +1 -1
  224. package/dist/rendering/RootChitRenderInstance.d.ts +5 -2
  225. package/dist/rendering/RootChitRenderInstance.d.ts.map +1 -1
  226. package/dist/rendering/RootChitRenderInstance.js +76 -13
  227. package/dist/rendering/RootChitRenderInstance.js.map +1 -1
  228. package/dist/rendering/SplayCounter.d.ts.map +1 -1
  229. package/dist/rendering/SplayCounter.js +1 -1
  230. package/dist/rendering/SplayCounter.js.map +1 -1
  231. package/dist/rendering/TextureReferenceCounter.d.ts +1 -1
  232. package/dist/rendering/TextureReferenceCounter.d.ts.map +1 -1
  233. package/dist/rendering/TextureReferenceCounter.js +10 -10
  234. package/dist/rendering/TextureReferenceCounter.js.map +1 -1
  235. package/dist/rendering/outline/passes/DepthOcclusionPass.js +1 -1
  236. package/dist/rendering/outline/passes/DepthOcclusionPass.js.map +1 -1
  237. package/dist/utilities/Annotations.d.ts +59 -0
  238. package/dist/utilities/Annotations.d.ts.map +1 -1
  239. package/dist/utilities/Annotations.js +63 -0
  240. package/dist/utilities/Annotations.js.map +1 -1
  241. package/dist/utilities/CanvasStack/CanvasOperations.d.ts +10 -1
  242. package/dist/utilities/CanvasStack/CanvasOperations.d.ts.map +1 -1
  243. package/dist/utilities/CanvasStack/CanvasOperations.js +8 -0
  244. package/dist/utilities/CanvasStack/CanvasOperations.js.map +1 -1
  245. package/dist/utilities/CanvasStack/ReactCanvas.d.ts +4 -2
  246. package/dist/utilities/CanvasStack/ReactCanvas.d.ts.map +1 -1
  247. package/dist/utilities/CanvasStack/ReactCanvas.js +5 -2
  248. package/dist/utilities/CanvasStack/ReactCanvas.js.map +1 -1
  249. package/dist/utilities/CanvasStack/RichTextRenderer.d.ts.map +1 -1
  250. package/dist/utilities/CanvasStack/RichTextRenderer.js +12 -4
  251. package/dist/utilities/CanvasStack/RichTextRenderer.js.map +1 -1
  252. package/dist/utilities/EventChannel.d.ts.map +1 -1
  253. package/dist/utilities/EventChannel.js +2 -3
  254. package/dist/utilities/EventChannel.js.map +1 -1
  255. package/dist/utilities/GlbLoader.d.ts +15 -0
  256. package/dist/utilities/GlbLoader.d.ts.map +1 -0
  257. package/dist/utilities/GlbLoader.js +212 -0
  258. package/dist/utilities/GlbLoader.js.map +1 -0
  259. package/dist/utilities/LayoutHelper.js +23 -2
  260. package/dist/utilities/LayoutHelper.js.map +1 -1
  261. package/dist/utilities/ObjectWithProps.d.ts.map +1 -1
  262. package/dist/utilities/ObjectWithProps.js +32 -3
  263. package/dist/utilities/ObjectWithProps.js.map +1 -1
  264. package/package.json +6 -5
  265. package/src/library/components/BottomBarButton.tsx +3 -0
  266. package/src/library/components/ClientTrustMatchViewer.tsx +23 -8
  267. package/src/library/components/ContextGalleryDisplay.tsx +2 -2
  268. package/src/library/components/DemoWrapper.tsx +113 -0
  269. package/src/library/components/{GalleryDisplay.tsx → FullScreenGalleryDisplay.tsx} +28 -2
  270. package/src/library/components/Gallery/AnimationController.ts +110 -0
  271. package/src/library/components/Gallery/BuiltItem.ts +385 -0
  272. package/src/library/components/Gallery/CameraManager.ts +54 -0
  273. package/src/library/components/Gallery/GalleryController.ts +193 -0
  274. package/src/library/components/Gallery/GalleryViewer.tsx +211 -0
  275. package/src/library/components/Gallery/LayoutManager.ts +166 -0
  276. package/src/library/components/Gallery/constants.ts +12 -0
  277. package/src/library/components/Gallery/index.ts +2 -0
  278. package/src/library/components/Gallery/types.ts +55 -0
  279. package/src/library/components/GalleryPlayground.tsx +1 -1
  280. package/src/library/components/GalleryViewer.tsx +4 -773
  281. package/src/library/components/GameDesigner.tsx +21 -4
  282. package/src/library/components/InlineGalleryDisplay.tsx +101 -0
  283. package/src/library/components/LiveButton.tsx +2 -1
  284. package/src/library/components/MatchViewer.tsx +32 -14
  285. package/src/library/components/Panel/MultiPanel.tsx +20 -8
  286. package/src/library/components/Panel/PanelContents.tsx +17 -12
  287. package/src/library/components/Panel/SinglePanel.tsx +5 -4
  288. package/src/library/components/Panel/ViewerWrapper.tsx +0 -5
  289. package/src/library/components/Playground.tsx +692 -119
  290. package/src/library/components/PromptControls.tsx +61 -8
  291. package/src/library/components/ServerTrustMatchViewer.tsx +53 -0
  292. package/src/library/components/Viewer.tsx +60 -20
  293. package/src/library/game/Chit.ts +53 -6
  294. package/src/library/game/ClientTimeState.ts +5 -1
  295. package/src/library/game/GalleryItemChitChildrenSource.ts +2 -0
  296. package/src/library/game/GalleryItemRawSource.ts +2 -0
  297. package/src/library/game/Game.ts +3 -1
  298. package/src/library/game/GameButton.ts +3 -0
  299. package/src/library/game/GameDeckChit.ts +36 -5
  300. package/src/library/game/GameMetaData.ts +19 -0
  301. package/src/library/game/GameTheme.ts +23 -5
  302. package/src/library/game/Match.ts +4 -3
  303. package/src/library/game/MatchStorage.ts +37 -0
  304. package/src/library/game/ModalState.ts +1 -0
  305. package/src/library/game/OrderedOutlet.ts +6 -6
  306. package/src/library/game/Pick.ts +98 -2
  307. package/src/library/game/PlayerChit.ts +13 -1
  308. package/src/library/game/PlayerInfo.ts +22 -3
  309. package/src/library/game/Prompt.ts +1 -36
  310. package/src/library/game/RootChit.ts +41 -1
  311. package/src/library/game/Turn.ts +37 -40
  312. package/src/library/game/TurnState.ts +22 -3
  313. package/src/library/game/badAiTransport/BadAIClientPrompts.ts +60 -0
  314. package/src/library/game/clientTransport/ClientTime.ts +1 -1
  315. package/src/library/game/serverTransport/ServerTime.ts +2 -2
  316. package/src/library/hooks/useButtonGalleriesOptions.tsx +9 -0
  317. package/src/library/hooks/useEventChannelState.ts +1 -1
  318. package/src/library/hooks/useLoadingStates.tsx +55 -0
  319. package/src/library/hooks/useModalState.tsx +2 -2
  320. package/src/library/hooks/usePanelPositioning.tsx +0 -1
  321. package/src/library/index.ts +21 -1
  322. package/src/library/rendering/CameraWrapperPerspective.ts +10 -12
  323. package/src/library/rendering/ChitGalleryItemInstance.ts +2 -0
  324. package/src/library/rendering/ChitRenderInstance.ts +119 -61
  325. package/src/library/rendering/ChitRenderSpec.ts +4 -0
  326. package/src/library/rendering/RootChitRenderInstance.ts +87 -13
  327. package/src/library/rendering/SplayCounter.tsx +2 -1
  328. package/src/library/rendering/TextureReferenceCounter.ts +9 -9
  329. package/src/library/rendering/outline/passes/DepthOcclusionPass.ts +1 -1
  330. package/src/library/utilities/Annotations.ts +99 -0
  331. package/src/library/utilities/CanvasStack/CanvasOperations.tsx +19 -1
  332. package/src/library/utilities/CanvasStack/ReactCanvas.tsx +10 -3
  333. package/src/library/utilities/CanvasStack/RichTextRenderer.ts +14 -4
  334. package/src/library/utilities/EventChannel.ts +2 -4
  335. package/src/library/utilities/GlbLoader.ts +292 -0
  336. package/src/library/utilities/LayoutHelper.ts +28 -2
  337. package/src/library/utilities/ObjectWithProps.ts +27 -3
  338. package/dist/components/GalleryDisplay.d.ts +0 -2
  339. package/dist/components/GalleryDisplay.d.ts.map +0 -1
  340. package/dist/components/GalleryDisplay.js +0 -42
  341. package/dist/components/GalleryDisplay.js.map +0 -1
  342. package/dist/utilities/OutlineCanvas.d.ts +0 -12
  343. package/dist/utilities/OutlineCanvas.d.ts.map +0 -1
  344. package/dist/utilities/OutlineCanvas.js +0 -31
  345. package/dist/utilities/OutlineCanvas.js.map +0 -1
  346. package/dist/utilities/OutlineGeometry.d.ts +0 -3
  347. package/dist/utilities/OutlineGeometry.d.ts.map +0 -1
  348. package/dist/utilities/OutlineGeometry.js +0 -57
  349. package/dist/utilities/OutlineGeometry.js.map +0 -1
  350. package/src/library/utilities/OutlineCanvas.tsx +0 -45
  351. package/src/library/utilities/OutlineGeometry.ts +0 -69
@@ -1,8 +1,28 @@
1
- import React, { useEffect, useState, ReactNode } from "react";
1
+ import React, { useEffect, useState, useCallback } from "react";
2
2
 
3
- import SelectableItemAndStage from "./SelectableItemAndStage";
4
3
  import { Chit } from "../game/Chit";
5
- import { Box, Button, Stack, Tabs, Tab, ToggleButton, ToggleButtonGroup } from "@mui/material";
4
+ import {
5
+ Box,
6
+ Button,
7
+ Stack,
8
+ Tabs,
9
+ Tab,
10
+ ToggleButton,
11
+ ToggleButtonGroup,
12
+ Select,
13
+ MenuItem,
14
+ Paper,
15
+ Dialog,
16
+ DialogTitle,
17
+ DialogContent,
18
+ DialogActions,
19
+ TextField,
20
+ FormControl,
21
+ InputLabel,
22
+ IconButton,
23
+ Tooltip,
24
+ } from "@mui/material";
25
+ import { Add, Delete, Refresh, GridOn, PhoneIphone, Tab as TabIcon, Upload, Download } from "@mui/icons-material";
6
26
  import useLocalStorageState from "use-local-storage-state";
7
27
  import { MatchViewer } from "./MatchViewer";
8
28
  import { Game } from "../game/Game";
@@ -13,8 +33,10 @@ import { LocalConnectionTransport } from "../game/ConnectionTransport";
13
33
  import { Match } from "../game/Match";
14
34
  import { PlayerInfo } from "../game/PlayerInfo";
15
35
  import { LocalMatchStorage } from "../game/MatchStorage";
16
- import { GridOn, PhoneIphone, Tab as TabIcon } from "@material-ui/icons";
17
36
  import { PlayerProvider } from "../hooks/usePlayer";
37
+ import { LoadingStateProvider, LoadingStates } from "../hooks/useLoadingStates";
38
+ import { RootChit } from "../game/RootChit";
39
+ import { SelectablePropertyInfo } from "../utilities/Annotations";
18
40
 
19
41
  type Layout = "tile" | "tab" | "phone";
20
42
 
@@ -22,7 +44,374 @@ export interface IChitLibrary {
22
44
  [key: string]: new () => Chit;
23
45
  }
24
46
 
25
- function PlayerEditor({ playerId, match, showBack }: { showBack?: boolean; playerId: string; match: Match<any, any> }) {
47
+ // Saved match metadata structure
48
+ interface SavedMatchInfo {
49
+ matchId: string;
50
+ name: string;
51
+ playerCount: number;
52
+ matchOptions: any;
53
+ createdAt: number;
54
+ }
55
+
56
+ // Helper functions for match storage
57
+ function getGameStoragePrefix(gameName: string): string {
58
+ return `playground_${gameName}`;
59
+ }
60
+
61
+ function getMatchListKey(gameName: string): string {
62
+ return `${getGameStoragePrefix(gameName)}_matchList`;
63
+ }
64
+
65
+ function getMatchMetaKey(gameName: string, matchId: string): string {
66
+ return `${getGameStoragePrefix(gameName)}_meta_${matchId}`;
67
+ }
68
+
69
+ function getMatchStateKey(gameName: string, matchId: string): string {
70
+ return `${getGameStoragePrefix(gameName)}_state_${matchId}`;
71
+ }
72
+
73
+ function loadMatchList(gameName: string): string[] {
74
+ const raw = localStorage.getItem(getMatchListKey(gameName));
75
+ return raw ? JSON.parse(raw) : [];
76
+ }
77
+
78
+ function saveMatchList(gameName: string, matchIds: string[]): void {
79
+ localStorage.setItem(getMatchListKey(gameName), JSON.stringify(matchIds));
80
+ }
81
+
82
+ function loadMatchMeta(gameName: string, matchId: string): SavedMatchInfo | null {
83
+ const raw = localStorage.getItem(getMatchMetaKey(gameName, matchId));
84
+ return raw ? JSON.parse(raw) : null;
85
+ }
86
+
87
+ function saveMatchMeta(gameName: string, info: SavedMatchInfo): void {
88
+ localStorage.setItem(getMatchMetaKey(gameName, info.matchId), JSON.stringify(info));
89
+ }
90
+
91
+ function deleteMatchData(gameName: string, matchId: string): void {
92
+ localStorage.removeItem(getMatchMetaKey(gameName, matchId));
93
+ localStorage.removeItem(getMatchStateKey(gameName, matchId));
94
+ // Also remove match state stored via LocalMatchStorage under its composite key.
95
+ localStorage.removeItem(`match${getMatchStateKey(gameName, matchId)}`);
96
+ }
97
+
98
+ function generateMatchId(): string {
99
+ return `match_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
100
+ }
101
+
102
+ const FIRST_NAMES = ["Fred", "Steve", "Paul", "Josh", "Sara", "Miles", "Anna", "Chris", "Dana", "Eric"];
103
+ const LAST_NAMES = ["Johnson", "Dennis", "Green", "Breckman", "Stevens", "Smith", "Brown", "Wilson", "Moore", "Taylor"];
104
+
105
+ function generatePlayerName(index: number): string {
106
+ return `${FIRST_NAMES[index % FIRST_NAMES.length]} ${LAST_NAMES[index % LAST_NAMES.length]}`;
107
+ }
108
+
109
+ // Dialog for creating a new match
110
+ function NewMatchDialog({
111
+ open,
112
+ onClose,
113
+ onCreate,
114
+ configOptions,
115
+ minPlayers,
116
+ maxPlayers,
117
+ defaultPlayerCount,
118
+ defaultMatchOptions,
119
+ }: {
120
+ open: boolean;
121
+ onClose: () => void;
122
+ onCreate: (name: string, playerCount: number, matchOptions: any) => void;
123
+ configOptions: SelectablePropertyInfo[];
124
+ minPlayers: number;
125
+ maxPlayers: number;
126
+ defaultPlayerCount: number;
127
+ defaultMatchOptions: any;
128
+ }) {
129
+ const [name, setName] = useState(`Match ${Date.now()}`);
130
+ const [playerCount, setPlayerCount] = useState(defaultPlayerCount);
131
+ const [matchOptions, setMatchOptions] = useState<any>({ ...defaultMatchOptions });
132
+
133
+ useEffect(() => {
134
+ if (open) {
135
+ setName(`Match ${new Date().toLocaleString()}`);
136
+ setPlayerCount(defaultPlayerCount);
137
+ setMatchOptions({ ...defaultMatchOptions });
138
+ }
139
+ }, [open, defaultPlayerCount, defaultMatchOptions]);
140
+
141
+ const handleCreate = () => {
142
+ onCreate(name, playerCount, matchOptions);
143
+ onClose();
144
+ };
145
+
146
+ const playerCountOptions = [];
147
+ for (let i = minPlayers; i <= maxPlayers; i++) {
148
+ playerCountOptions.push(i);
149
+ }
150
+
151
+ return (
152
+ <Dialog open={open} onClose={onClose} maxWidth="sm" fullWidth>
153
+ <DialogTitle>Create New Match</DialogTitle>
154
+ <DialogContent>
155
+ <Stack spacing={3} sx={{ mt: 1 }}>
156
+ <TextField
157
+ label="Match Name"
158
+ value={name}
159
+ onChange={(e) => setName(e.target.value)}
160
+ fullWidth
161
+ autoFocus
162
+ />
163
+ <FormControl fullWidth>
164
+ <InputLabel>Player Count</InputLabel>
165
+ <Select
166
+ value={playerCount}
167
+ label="Player Count"
168
+ onChange={(e) => setPlayerCount(Number(e.target.value))}
169
+ >
170
+ {playerCountOptions.map((count) => (
171
+ <MenuItem key={count} value={count}>
172
+ {count} Players
173
+ </MenuItem>
174
+ ))}
175
+ </Select>
176
+ </FormControl>
177
+ {configOptions.map((option) => (
178
+ <FormControl key={option.fieldName} fullWidth>
179
+ <InputLabel>{option.config.label}</InputLabel>
180
+ <Select
181
+ value={matchOptions[option.fieldName] ?? option.currentValue}
182
+ label={option.config.label}
183
+ onChange={(e) =>
184
+ setMatchOptions((prev: any) => ({
185
+ ...prev,
186
+ [option.fieldName]: e.target.value,
187
+ }))
188
+ }
189
+ >
190
+ {option.config.choices.map((choice) => (
191
+ <MenuItem key={choice.id} value={choice.id}>
192
+ {choice.label ?? choice.id}
193
+ </MenuItem>
194
+ ))}
195
+ </Select>
196
+ </FormControl>
197
+ ))}
198
+ </Stack>
199
+ </DialogContent>
200
+ <DialogActions>
201
+ <Button onClick={onClose}>Cancel</Button>
202
+ <Button onClick={handleCreate} variant="contained" disabled={!name.trim()}>
203
+ Create
204
+ </Button>
205
+ </DialogActions>
206
+ </Dialog>
207
+ );
208
+ }
209
+
210
+ // Dialog for loading state from a database export
211
+ function LoadStateDialog({
212
+ open,
213
+ onClose,
214
+ onLoad,
215
+ }: {
216
+ open: boolean;
217
+ onClose: () => void;
218
+ onLoad: (data: {
219
+ state: any;
220
+ options: any;
221
+ playerCount: number;
222
+ playerNames: string[];
223
+ name?: string;
224
+ }) => void;
225
+ }) {
226
+ const [jsonText, setJsonText] = useState("");
227
+ const [error, setError] = useState<string | null>(null);
228
+
229
+ useEffect(() => {
230
+ if (open) {
231
+ setJsonText("");
232
+ setError(null);
233
+ }
234
+ }, [open]);
235
+
236
+ const handleLoad = () => {
237
+ try {
238
+ const parsed = JSON.parse(jsonText);
239
+
240
+ // Extract relevant fields
241
+ const state = parsed.state;
242
+ if (!state) {
243
+ setError("No 'state' field found in the JSON");
244
+ return;
245
+ }
246
+
247
+ const options = parsed.options || {};
248
+ const numPlayers = parsed.numPlayers || Object.keys(parsed.players || {}).length || 2;
249
+
250
+ // Extract player names from the players object
251
+ const playerNames: string[] = [];
252
+ if (parsed.players && typeof parsed.players === "object") {
253
+ // Players might be keyed by ID, so we need to get them in order
254
+ const playerEntries = Object.values(parsed.players) as any[];
255
+ for (let i = 0; i < numPlayers; i++) {
256
+ if (playerEntries[i]?.name) {
257
+ playerNames.push(playerEntries[i].name);
258
+ } else {
259
+ playerNames.push(generatePlayerName(i));
260
+ }
261
+ }
262
+ } else {
263
+ for (let i = 0; i < numPlayers; i++) {
264
+ playerNames.push(generatePlayerName(i));
265
+ }
266
+ }
267
+
268
+ onLoad({
269
+ state,
270
+ options,
271
+ playerCount: numPlayers,
272
+ playerNames,
273
+ name: parsed.name,
274
+ });
275
+ onClose();
276
+ } catch (e) {
277
+ setError(`Invalid JSON: ${e instanceof Error ? e.message : "Parse error"}`);
278
+ }
279
+ };
280
+
281
+ return (
282
+ <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
283
+ <DialogTitle>Load State from Database Export</DialogTitle>
284
+ <DialogContent>
285
+ <Stack spacing={2} sx={{ mt: 1 }}>
286
+ <TextField
287
+ label="Paste database JSON here"
288
+ value={jsonText}
289
+ onChange={(e) => {
290
+ setJsonText(e.target.value);
291
+ setError(null);
292
+ }}
293
+ multiline
294
+ rows={15}
295
+ fullWidth
296
+ placeholder='{"state": {...}, "players": {...}, "options": {...}, ...}'
297
+ error={!!error}
298
+ helperText={error}
299
+ sx={{
300
+ "& .MuiInputBase-input": {
301
+ fontFamily: "monospace",
302
+ fontSize: "12px",
303
+ },
304
+ }}
305
+ />
306
+ </Stack>
307
+ </DialogContent>
308
+ <DialogActions>
309
+ <Button onClick={onClose}>Cancel</Button>
310
+ <Button onClick={handleLoad} variant="contained" disabled={!jsonText.trim()}>
311
+ Load State
312
+ </Button>
313
+ </DialogActions>
314
+ </Dialog>
315
+ );
316
+ }
317
+
318
+ // Dialog for exporting/viewing current state
319
+ function ExportStateDialog({
320
+ open,
321
+ onClose,
322
+ matchInfo,
323
+ gameName,
324
+ }: {
325
+ open: boolean;
326
+ onClose: () => void;
327
+ matchInfo: SavedMatchInfo | null;
328
+ gameName: string;
329
+ }) {
330
+ const [jsonText, setJsonText] = useState("");
331
+ const [copied, setCopied] = useState(false);
332
+
333
+ useEffect(() => {
334
+ if (open && matchInfo) {
335
+ // Build the export object
336
+ const storageKey = getMatchStateKey(gameName, matchInfo.matchId);
337
+ const stateRaw = localStorage.getItem(`match${storageKey}`);
338
+ const state = stateRaw ? JSON.parse(stateRaw) : {};
339
+
340
+ // Build players object
341
+ const players: Record<string, { name: string; id: string }> = {};
342
+ for (let i = 0; i < matchInfo.playerCount; i++) {
343
+ const playerId = `p${i}`;
344
+ players[playerId] = {
345
+ id: playerId,
346
+ name: generatePlayerName(i),
347
+ };
348
+ }
349
+
350
+ const exportData = {
351
+ name: matchInfo.name,
352
+ numPlayers: matchInfo.playerCount,
353
+ options: matchInfo.matchOptions,
354
+ players,
355
+ state,
356
+ };
357
+
358
+ setJsonText(JSON.stringify(exportData, null, 2));
359
+ setCopied(false);
360
+ }
361
+ }, [open, matchInfo, gameName]);
362
+
363
+ const handleCopy = async () => {
364
+ try {
365
+ await navigator.clipboard.writeText(jsonText);
366
+ setCopied(true);
367
+ setTimeout(() => setCopied(false), 2000);
368
+ } catch (e) {
369
+ console.error("Failed to copy:", e);
370
+ }
371
+ };
372
+
373
+ return (
374
+ <Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
375
+ <DialogTitle>Export Match State</DialogTitle>
376
+ <DialogContent>
377
+ <Stack spacing={2} sx={{ mt: 1 }}>
378
+ <TextField
379
+ label="Match State JSON"
380
+ value={jsonText}
381
+ multiline
382
+ rows={15}
383
+ fullWidth
384
+ InputProps={{
385
+ readOnly: true,
386
+ }}
387
+ sx={{
388
+ "& .MuiInputBase-input": {
389
+ fontFamily: "monospace",
390
+ fontSize: "12px",
391
+ },
392
+ }}
393
+ />
394
+ </Stack>
395
+ </DialogContent>
396
+ <DialogActions>
397
+ <Button onClick={onClose}>Close</Button>
398
+ <Button onClick={handleCopy} variant="contained">
399
+ {copied ? "Copied!" : "Copy to Clipboard"}
400
+ </Button>
401
+ </DialogActions>
402
+ </Dialog>
403
+ );
404
+ }
405
+
406
+ function PlayerEditor({
407
+ playerId,
408
+ match,
409
+ showBack,
410
+ }: {
411
+ showBack?: boolean;
412
+ playerId: string;
413
+ match: Match<any, any>;
414
+ }) {
26
415
  const [localConnection, setLocalConnection] = useState<Connection | undefined>();
27
416
  const [, setRemoteConnection] = useState<LocalConnectionTransport>(new LocalConnectionTransport());
28
417
 
@@ -36,8 +425,6 @@ function PlayerEditor({ playerId, match, showBack }: { showBack?: boolean; playe
36
425
  setRemoteConnection(newRemoteConnection);
37
426
  return () => {
38
427
  newConnection.dispose();
39
- // TODO:
40
- // match.disconnection(newRemoteConnection);
41
428
  };
42
429
  }, [match, playerId]);
43
430
 
@@ -131,107 +518,46 @@ function MatchPhone({ match }: { match: Match<any, any> }) {
131
518
  }
132
519
 
133
520
  function Editor({
134
- setButtons,
135
521
  layout,
136
522
  game,
137
- matchInformation,
523
+ matchInfo,
138
524
  }: {
139
- setButtons: (buttons: ReactNode) => void;
140
525
  layout: Layout;
141
526
  game: Game<any, any>;
142
- matchInformation: string;
527
+ matchInfo: SavedMatchInfo;
143
528
  }) {
144
529
  const [match, setMatch] = useState<Match<any, any> | null>(null);
530
+ const [storage, setStorage] = useState<LocalMatchStorage | null>(null);
145
531
 
146
532
  useEffect(() => {
147
- const FIRST_NAMES = ["Fred", "Steve", "Paul", "Josh", "Sara", "Miles"];
148
- const LAST_NAMES = ["Johnson", "Dennis", "Green", "Breckman", "Stevens", "Smith"];
149
- const storage = new LocalMatchStorage(matchInformation);
150
- const players = [];
151
- let playerCount = parseInt(matchInformation);
152
- if (!Number.isFinite(playerCount)) {
153
- playerCount = 2;
154
- }
155
- for (let i = 0; i < playerCount; i++) {
156
- players.push(
157
- new PlayerInfo(
158
- `p${i}`,
159
- `${FIRST_NAMES[(i + playerCount) % FIRST_NAMES.length]} ${LAST_NAMES[i % LAST_NAMES.length]}`,
160
- ),
161
- );
533
+ const gameName = game.metadata.name;
534
+ const storageKey = getMatchStateKey(gameName, matchInfo.matchId);
535
+ const newStorage = new LocalMatchStorage(storageKey);
536
+ setStorage(newStorage);
537
+
538
+ const players: PlayerInfo[] = [];
539
+ for (let i = 0; i < matchInfo.playerCount; i++) {
540
+ players.push(new PlayerInfo(`p${i}`, generatePlayerName(i)));
162
541
  }
163
- const match = new Match(game, players, storage);
542
+
543
+ const newMatch = new Match(game, players, newStorage, matchInfo.matchOptions);
164
544
  let cancelled = false;
165
- match.load().then(() => {
545
+
546
+ newMatch.load().then(() => {
166
547
  if (cancelled) {
167
548
  return;
168
549
  }
169
- match.start();
170
- setMatch(match);
171
-
172
- const saveStateToLocalStorage = (key: string, state: any) => {
173
- localStorage.setItem(key, JSON.stringify(state));
174
- };
175
-
176
- const readStateFromLocalStorage = (key: string) => {
177
- const state = localStorage.getItem(key);
178
- return state ? JSON.parse(state) : {};
179
- };
180
-
181
- let aState = readStateFromLocalStorage("aState");
182
- let bState = readStateFromLocalStorage("bState");
183
-
184
- setButtons(
185
- <>
186
- <Button>New</Button>
187
- <Button onClick={() => storage.saveState({}, [], "active", undefined, true)}>Reset</Button>
188
-
189
- <Button
190
- onClick={() =>
191
- storage.readState().then((d) => {
192
- aState = d;
193
- saveStateToLocalStorage("aState", aState);
194
- })
195
- }
196
- >
197
- Save A
198
- </Button>
199
- <Button
200
- onClick={() =>
201
- storage.readState().then((d) => {
202
- bState = d;
203
- saveStateToLocalStorage("bState", bState);
204
- })
205
- }
206
- >
207
- Save B
208
- </Button>
209
- <Button
210
- onClick={() => {
211
- aState = readStateFromLocalStorage("aState");
212
- storage.saveState(aState, [], "active", undefined, true);
213
- }}
214
- >
215
- Read A
216
- </Button>
217
- <Button
218
- onClick={() => {
219
- bState = readStateFromLocalStorage("bState");
220
- storage.saveState(bState, [], "active", undefined, true);
221
- }}
222
- >
223
- Read B
224
- </Button>
225
- </>,
226
- );
550
+ newMatch.start();
551
+ setMatch(newMatch);
227
552
  });
553
+
228
554
  return () => {
229
- match.dispose();
555
+ newMatch.dispose();
230
556
  cancelled = true;
231
557
  };
232
- }, [game, matchInformation, setButtons]);
558
+ }, [game, matchInfo]);
233
559
 
234
- if (!match) {
560
+ if (!match || !storage) {
235
561
  return null;
236
562
  }
237
563
 
@@ -245,41 +571,288 @@ function Editor({
245
571
  }
246
572
 
247
573
  export default function Playground({ game }: { game: Game<any, any> }) {
574
+ const [loadingStates] = useState<LoadingStates>(new LoadingStates());
248
575
  const [layout, setLayout] = useLocalStorageState<Layout>("matchViewerLayout", {
249
576
  defaultValue: "tile",
250
577
  });
251
- const [buttons, setButtons] = useState<ReactNode | null>(null);
578
+ const [loadingProgress, setLoadingProgress] = useState<string>("");
579
+ const [newMatchDialogOpen, setNewMatchDialogOpen] = useState(false);
580
+ const [matches, setMatches] = useState<SavedMatchInfo[]>([]);
581
+ const [selectedMatchId, setSelectedMatchId] = useLocalStorageState<string>(
582
+ `playground_${game.metadata.name}_selectedMatch`,
583
+ { defaultValue: "" }
584
+ );
585
+ const [matchKey, setMatchKey] = useState(0); // For forcing re-render on reset
586
+ const [loadStateDialogOpen, setLoadStateDialogOpen] = useState(false);
587
+ const [exportStateDialogOpen, setExportStateDialogOpen] = useState(false);
252
588
 
253
- const items = ["2", "3", "4", "5", "1", "6", "7", "8", "9", "10"];
589
+ const gameName = game.metadata.name;
254
590
 
255
- return (
256
- <SelectableItemAndStage
257
- keySpace="playground"
258
- items={items}
259
- topOptions={
260
- <Stack direction="row">
261
- {buttons}
262
-
263
- <ToggleButtonGroup
264
- exclusive
265
- size="small"
266
- sx={{ m: 2 }}
267
- value={layout}
268
- onChange={(e, newValue) => setLayout(newValue)}
269
- >
270
- <ToggleButton value="tile">
271
- <GridOn />
272
- </ToggleButton>
273
- <ToggleButton value="tab">
274
- <TabIcon />
275
- </ToggleButton>
276
- <ToggleButton value="phone">
277
- <PhoneIphone />
278
- </ToggleButton>
279
- </ToggleButtonGroup>
280
- </Stack>
591
+ // Get config options from a temporary RootChit instance
592
+ const getConfigInfo = useCallback(() => {
593
+ const tempRoot = new game.chitLibrary.Root() as RootChit<any>;
594
+ const defaultPlayerCount = tempRoot.setupDemoGame();
595
+ const configOptions = tempRoot.getConfigurationOptions();
596
+ const defaultMatchOptions = tempRoot.getCurrentlySelectedMatchOptions();
597
+ return {
598
+ configOptions,
599
+ defaultPlayerCount,
600
+ defaultMatchOptions,
601
+ minPlayers: tempRoot.minPlayers,
602
+ maxPlayers: tempRoot.maxPlayers,
603
+ };
604
+ }, [game]);
605
+
606
+ // Load matches from localStorage
607
+ const loadMatches = useCallback(() => {
608
+ const matchIds = loadMatchList(gameName);
609
+ const loadedMatches: SavedMatchInfo[] = [];
610
+ for (const id of matchIds) {
611
+ const meta = loadMatchMeta(gameName, id);
612
+ if (meta) {
613
+ loadedMatches.push(meta);
614
+ }
615
+ }
616
+ // Sort by creation time, newest first
617
+ loadedMatches.sort((a, b) => b.createdAt - a.createdAt);
618
+ setMatches(loadedMatches);
619
+ return loadedMatches;
620
+ }, [gameName]);
621
+
622
+ // Initialize: load matches and create default if none exist
623
+ useEffect(() => {
624
+ const loaded = loadMatches();
625
+ if (loaded.length === 0) {
626
+ // Create a default match from demo game settings
627
+ const { defaultPlayerCount, defaultMatchOptions } = getConfigInfo();
628
+ const matchId = generateMatchId();
629
+ const newMatchInfo: SavedMatchInfo = {
630
+ matchId,
631
+ name: "Default Match",
632
+ playerCount: defaultPlayerCount,
633
+ matchOptions: defaultMatchOptions,
634
+ createdAt: Date.now(),
635
+ };
636
+ saveMatchMeta(gameName, newMatchInfo);
637
+ saveMatchList(gameName, [matchId]);
638
+ setMatches([newMatchInfo]);
639
+ setSelectedMatchId(matchId);
640
+ } else if (!selectedMatchId || !loaded.find((m) => m.matchId === selectedMatchId)) {
641
+ setSelectedMatchId(loaded[0].matchId);
642
+ }
643
+ }, [gameName, loadMatches, getConfigInfo, selectedMatchId, setSelectedMatchId]);
644
+
645
+ useEffect(() => {
646
+ return loadingStates.onChange((total, loaded) => {
647
+ if (total === 0 || loaded >= total) {
648
+ setLoadingProgress("");
649
+ } else {
650
+ const percent = Math.round((loaded / total) * 100);
651
+ setLoadingProgress(`${percent}%`);
281
652
  }
282
- render={(item) => <Editor setButtons={setButtons} layout={layout} matchInformation={item} game={game} />}
283
- />
653
+ });
654
+ }, [loadingStates]);
655
+
656
+ const handleCreateMatch = (name: string, playerCount: number, matchOptions: any) => {
657
+ const matchId = generateMatchId();
658
+ const newMatchInfo: SavedMatchInfo = {
659
+ matchId,
660
+ name,
661
+ playerCount,
662
+ matchOptions,
663
+ createdAt: Date.now(),
664
+ };
665
+ saveMatchMeta(gameName, newMatchInfo);
666
+ const matchIds = loadMatchList(gameName);
667
+ matchIds.unshift(matchId);
668
+ saveMatchList(gameName, matchIds);
669
+ setMatches((prev) => [newMatchInfo, ...prev]);
670
+ setSelectedMatchId(matchId);
671
+ };
672
+
673
+ const handleDeleteMatch = () => {
674
+ if (!selectedMatchId) return;
675
+ if (!confirm("Are you sure you want to delete this match?")) return;
676
+
677
+ deleteMatchData(gameName, selectedMatchId);
678
+ const matchIds = loadMatchList(gameName).filter((id) => id !== selectedMatchId);
679
+ saveMatchList(gameName, matchIds);
680
+
681
+ const newMatches = matches.filter((m) => m.matchId !== selectedMatchId);
682
+ setMatches(newMatches);
683
+
684
+ if (newMatches.length > 0) {
685
+ setSelectedMatchId(newMatches[0].matchId);
686
+ } else {
687
+ // Create a new default match if all were deleted
688
+ const { defaultPlayerCount, defaultMatchOptions } = getConfigInfo();
689
+ const matchId = generateMatchId();
690
+ const newMatchInfo: SavedMatchInfo = {
691
+ matchId,
692
+ name: "Default Match",
693
+ playerCount: defaultPlayerCount,
694
+ matchOptions: defaultMatchOptions,
695
+ createdAt: Date.now(),
696
+ };
697
+ saveMatchMeta(gameName, newMatchInfo);
698
+ saveMatchList(gameName, [matchId]);
699
+ setMatches([newMatchInfo]);
700
+ setSelectedMatchId(matchId);
701
+ }
702
+ };
703
+
704
+ const handleResetMatch = () => {
705
+ if (!selectedMatchId) return;
706
+ const matchInfo = matches.find((m) => m.matchId === selectedMatchId);
707
+ if (!matchInfo) return;
708
+
709
+ // Clear the match state in localStorage
710
+ const storageKey = getMatchStateKey(gameName, selectedMatchId);
711
+ localStorage.removeItem(`match${storageKey}`);
712
+
713
+ // Force re-render of the Editor
714
+ setMatchKey((prev) => prev + 1);
715
+ };
716
+
717
+ const handleLoadState = (data: {
718
+ state: any;
719
+ options: any;
720
+ playerCount: number;
721
+ playerNames: string[];
722
+ name?: string;
723
+ }) => {
724
+ if (!selectedMatchId) return;
725
+ const matchInfo = matches.find((m) => m.matchId === selectedMatchId);
726
+ if (!matchInfo) return;
727
+
728
+ // Update match metadata with new player count and options
729
+ const updatedMatchInfo: SavedMatchInfo = {
730
+ ...matchInfo,
731
+ playerCount: data.playerCount,
732
+ matchOptions: data.options,
733
+ name: data.name ? `${matchInfo.name} (${data.name})` : matchInfo.name,
734
+ };
735
+ saveMatchMeta(gameName, updatedMatchInfo);
736
+ setMatches((prev) => prev.map((m) => (m.matchId === selectedMatchId ? updatedMatchInfo : m)));
737
+
738
+ // Save the state to localStorage
739
+ const storageKey = getMatchStateKey(gameName, selectedMatchId);
740
+ localStorage.setItem(`match${storageKey}`, JSON.stringify(data.state));
741
+
742
+ // Force re-render of the Editor
743
+ setMatchKey((prev) => prev + 1);
744
+ };
745
+
746
+ const selectedMatch = matches.find((m) => m.matchId === selectedMatchId);
747
+ const configInfo = getConfigInfo();
748
+
749
+ return (
750
+ <LoadingStateProvider loadingStates={loadingStates}>
751
+ <Stack sx={{ height: "100%" }}>
752
+ <Paper elevation={3} sx={{ position: "relative" }}>
753
+ <Stack direction={"row"} alignItems="center" spacing={1} sx={{ p: 1 }}>
754
+ <FormControl sx={{ minWidth: 200 }}>
755
+ <Select
756
+ variant="standard"
757
+ value={selectedMatch ? selectedMatchId : ""}
758
+ onChange={(e) => setSelectedMatchId(e.target.value)}
759
+ displayEmpty
760
+ >
761
+ {matches.map((m) => (
762
+ <MenuItem key={m.matchId} value={m.matchId}>
763
+ {m.name} ({m.playerCount}P)
764
+ </MenuItem>
765
+ ))}
766
+ </Select>
767
+ </FormControl>
768
+
769
+ <Tooltip title="New Match">
770
+ <IconButton onClick={() => setNewMatchDialogOpen(true)} size="small">
771
+ <Add />
772
+ </IconButton>
773
+ </Tooltip>
774
+
775
+ <Tooltip title="Reset Match">
776
+ <IconButton onClick={handleResetMatch} size="small" disabled={!selectedMatchId}>
777
+ <Refresh />
778
+ </IconButton>
779
+ </Tooltip>
780
+
781
+ <Tooltip title="Delete Match">
782
+ <IconButton onClick={handleDeleteMatch} size="small" disabled={!selectedMatchId}>
783
+ <Delete />
784
+ </IconButton>
785
+ </Tooltip>
786
+
787
+ <Tooltip title="Load State">
788
+ <IconButton onClick={() => setLoadStateDialogOpen(true)} size="small" disabled={!selectedMatchId}>
789
+ <Upload />
790
+ </IconButton>
791
+ </Tooltip>
792
+
793
+ <Tooltip title="Export State">
794
+ <IconButton onClick={() => setExportStateDialogOpen(true)} size="small" disabled={!selectedMatchId}>
795
+ <Download />
796
+ </IconButton>
797
+ </Tooltip>
798
+
799
+ <Box sx={{ flexGrow: 1 }} />
800
+
801
+ {loadingProgress && <Box sx={{ mr: 2 }}>{loadingProgress}</Box>}
802
+
803
+ <ToggleButtonGroup
804
+ exclusive
805
+ size="small"
806
+ value={layout}
807
+ onChange={(e, newValue) => newValue && setLayout(newValue)}
808
+ >
809
+ <ToggleButton value="tile">
810
+ <GridOn />
811
+ </ToggleButton>
812
+ <ToggleButton value="tab">
813
+ <TabIcon />
814
+ </ToggleButton>
815
+ <ToggleButton value="phone">
816
+ <PhoneIphone />
817
+ </ToggleButton>
818
+ </ToggleButtonGroup>
819
+ </Stack>
820
+ </Paper>
821
+ <Box flexGrow={1}>
822
+ {selectedMatch && (
823
+ <Editor
824
+ key={`${selectedMatch.matchId}-${matchKey}`}
825
+ layout={layout}
826
+ game={game}
827
+ matchInfo={selectedMatch}
828
+ />
829
+ )}
830
+ </Box>
831
+ </Stack>
832
+
833
+ <NewMatchDialog
834
+ open={newMatchDialogOpen}
835
+ onClose={() => setNewMatchDialogOpen(false)}
836
+ onCreate={handleCreateMatch}
837
+ configOptions={configInfo.configOptions}
838
+ minPlayers={configInfo.minPlayers}
839
+ maxPlayers={configInfo.maxPlayers}
840
+ defaultPlayerCount={configInfo.defaultPlayerCount}
841
+ defaultMatchOptions={configInfo.defaultMatchOptions}
842
+ />
843
+
844
+ <LoadStateDialog
845
+ open={loadStateDialogOpen}
846
+ onClose={() => setLoadStateDialogOpen(false)}
847
+ onLoad={handleLoadState}
848
+ />
849
+
850
+ <ExportStateDialog
851
+ open={exportStateDialogOpen}
852
+ onClose={() => setExportStateDialogOpen(false)}
853
+ matchInfo={selectedMatch ?? null}
854
+ gameName={gameName}
855
+ />
856
+ </LoadingStateProvider>
284
857
  );
285
858
  }