GameView.jsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import React from "react";
  2. import { useTranslation } from "react-i18next";
  3. import useAsyncEffect from "use-async-effect";
  4. import { BoardWrapper } from "react-sync-board";
  5. import { useSocket } from "@scripters/use-socket.io";
  6. import { itemTemplates, itemLibrary, premadeItems } from "../gameComponents";
  7. import Waiter from "../ui/Waiter";
  8. import BoardView from "./BoardView";
  9. import { getGame } from "../utils/api";
  10. import { uid } from "../utils";
  11. import useGame, { GameProvider } from "../hooks/useGame";
  12. import { GlobalConfProvider } from "../hooks/useGlobalConf";
  13. // Keep compatibility with previous availableItems shape
  14. const migrateAvailableItemList = (old) => {
  15. const groupMap = old.reduce((acc, { groupId, ...item }) => {
  16. if (!acc[groupId]) {
  17. acc[groupId] = [];
  18. }
  19. acc[groupId].push(item);
  20. return acc;
  21. }, {});
  22. return Object.keys(groupMap).map((name) => ({
  23. name,
  24. items: groupMap[name],
  25. }));
  26. };
  27. const adaptItem = (item) => ({
  28. type: item.type,
  29. template: item,
  30. component: itemTemplates[item.type].component,
  31. name: item.name || item.label || item.text || itemTemplates[item.type].name,
  32. uid: uid(),
  33. });
  34. const adaptItems = (nodes) => {
  35. return nodes.map((node) => {
  36. if (node.type) {
  37. return adaptItem(node);
  38. } else {
  39. return { ...node, items: adaptItems(node.items) };
  40. }
  41. });
  42. };
  43. const newGameData = {
  44. items: [],
  45. availableItems: [],
  46. board: { size: 2000, scale: 1, imageUrl: "/game_assets/default.png" },
  47. };
  48. export const GameView = ({ create = false }) => {
  49. const [gameLoaded, setGameLoaded] = React.useState(false);
  50. const { setGame, gameId, availableItems } = useGame();
  51. const gameLoadingRef = React.useRef(false);
  52. const { t } = useTranslation();
  53. useAsyncEffect(
  54. async (isMounted) => {
  55. if (!gameLoaded && !gameLoadingRef.current) {
  56. gameLoadingRef.current = true;
  57. try {
  58. let gameData;
  59. if (create) {
  60. // Create new game
  61. newGameData.board.defaultName = t("New game");
  62. gameData = JSON.parse(JSON.stringify(newGameData));
  63. } else {
  64. // Load game from server
  65. gameData = await getGame(gameId);
  66. }
  67. if (!isMounted) return;
  68. setGame(gameData);
  69. setGameLoaded(true);
  70. } catch (e) {
  71. console.log(e);
  72. }
  73. }
  74. },
  75. [gameLoaded]
  76. );
  77. const itemLibraries = React.useMemo(() => {
  78. let itemList = availableItems;
  79. if (itemList.length && itemList[0].groupId) {
  80. itemList = migrateAvailableItemList(itemList);
  81. }
  82. const availableItemLibrary = adaptItems(itemList);
  83. const premadeLibrary = adaptItems(premadeItems);
  84. const libraries = [
  85. {
  86. name: t("Standard"),
  87. key: "standard",
  88. items: itemLibrary,
  89. },
  90. {
  91. name: t("Premade"),
  92. key: "premade",
  93. items: premadeLibrary,
  94. },
  95. ];
  96. if (availableItems.length) {
  97. libraries.push({
  98. name: t("Box"),
  99. key: "box",
  100. items: availableItemLibrary,
  101. });
  102. }
  103. return libraries;
  104. }, [availableItems, t]);
  105. const mediaLibraries = React.useMemo(() => {
  106. return [{ id: "game", name: t("Game"), boxId: "game", resourceId: gameId }];
  107. }, [gameId, t]);
  108. if (!gameLoaded) {
  109. return <Waiter message={t("Session loading...")} />;
  110. }
  111. return (
  112. <GlobalConfProvider editMode={true}>
  113. <BoardView
  114. mediaLibraries={mediaLibraries}
  115. itemLibraries={itemLibraries}
  116. edit={true}
  117. />
  118. </GlobalConfProvider>
  119. );
  120. };
  121. const ConnectedGameView = ({ gameId }) => {
  122. const socket = useSocket();
  123. const [sessionId] = React.useState(uid());
  124. const [realGameId] = React.useState(() => gameId || uid());
  125. return (
  126. <BoardWrapper
  127. room={`room_${sessionId}`}
  128. session={sessionId}
  129. style={{
  130. position: "fixed",
  131. inset: 0,
  132. overflow: "hidden",
  133. }}
  134. socket={socket}
  135. >
  136. <GameProvider gameId={realGameId} create={!gameId}>
  137. <GameView create={!gameId} />
  138. </GameProvider>
  139. </BoardWrapper>
  140. );
  141. };
  142. export default ConnectedGameView;