Session.jsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. import React from "react";
  2. import { useTranslation } from "react-i18next";
  3. import useAsyncEffect from "use-async-effect";
  4. import { BoardWrapper, useWire } from "react-sync-board";
  5. import { useSocket } from "@scripters/use-socket.io";
  6. import { itemTemplates, itemLibrary, premadeItems } from "../gameComponents";
  7. import BoardView from "./BoardView";
  8. import SessionRestoreDim from "./SessionRestoreDim";
  9. import Waiter from "../ui/Waiter";
  10. import { uid } from "../utils";
  11. import { GlobalConfProvider } from "../hooks/useGlobalConf";
  12. import useSession, { SessionProvider } from "../hooks/useSession";
  13. import AutoSaveSession from "./AutoSaveSession";
  14. // Keep compatibility with previous availableItems shape
  15. const migrateAvailableItemList = (old) => {
  16. const groupMap = old.reduce((acc, { groupId, ...item }) => {
  17. if (!acc[groupId]) {
  18. acc[groupId] = [];
  19. }
  20. acc[groupId].push(item);
  21. return acc;
  22. }, {});
  23. return Object.keys(groupMap).map((name) => ({
  24. name,
  25. items: groupMap[name],
  26. }));
  27. };
  28. const adaptItem = (item) => ({
  29. type: item.type,
  30. template: item,
  31. component: itemTemplates[item.type].component,
  32. name: item.name || item.label || item.text || itemTemplates[item.type].name,
  33. uid: uid(),
  34. });
  35. const adaptItems = (nodes) => {
  36. return nodes.map((node) => {
  37. if (node.type) {
  38. return adaptItem(node);
  39. } else {
  40. return { ...node, items: adaptItems(node.items) };
  41. }
  42. });
  43. };
  44. export const Session = () => {
  45. const {
  46. loadSession,
  47. setSession,
  48. sessionLoaded,
  49. gameId,
  50. sessionId,
  51. availableItems,
  52. } = useSession();
  53. const gameLoadingRef = React.useRef(false);
  54. const { wire, isMaster } = useWire("board");
  55. const { t } = useTranslation();
  56. useAsyncEffect(
  57. async (isMounted) => {
  58. if (isMaster && !sessionLoaded && !gameLoadingRef.current) {
  59. gameLoadingRef.current = true;
  60. const sessionData = await loadSession();
  61. if (!isMounted) return;
  62. setSession(sessionData, true);
  63. }
  64. },
  65. [sessionLoaded, isMaster]
  66. );
  67. // Load game from master if any
  68. React.useEffect(() => {
  69. if (!isMaster && !sessionLoaded && !gameLoadingRef.current) {
  70. gameLoadingRef.current = true;
  71. const onReceiveGame = (receivedSession) => {
  72. setSession(receivedSession);
  73. };
  74. wire.call("getSession").then(onReceiveGame, () => {
  75. setTimeout(
  76. () =>
  77. wire
  78. .call("getSession")
  79. .then(onReceiveGame, (error) =>
  80. console.log("Failed to call getSession with error", error)
  81. ),
  82. 1000
  83. );
  84. });
  85. }
  86. }, [wire, isMaster, sessionLoaded, setSession]);
  87. const itemLibraries = React.useMemo(() => {
  88. let itemList = availableItems;
  89. if (itemList.length && itemList[0].groupId) {
  90. itemList = migrateAvailableItemList(itemList);
  91. }
  92. const availableItemLibrary = adaptItems(itemList);
  93. const premadeLibrary = adaptItems(premadeItems);
  94. const libraries = [
  95. {
  96. name: t("Standard"),
  97. key: "standard",
  98. items: itemLibrary,
  99. },
  100. {
  101. name: t("Premade"),
  102. key: "premade",
  103. items: premadeLibrary,
  104. },
  105. ];
  106. if (availableItems.length) {
  107. libraries.push({
  108. name: t("Box"),
  109. key: "box",
  110. items: availableItemLibrary,
  111. });
  112. }
  113. return libraries;
  114. }, [availableItems, t]);
  115. const mediaLibraries = React.useMemo(() => {
  116. if (gameId) {
  117. return [
  118. {
  119. id: "session",
  120. name: t("Session"),
  121. boxId: "session",
  122. resourceId: sessionId,
  123. },
  124. { id: "game", name: t("Game"), boxId: "game", resourceId: gameId },
  125. ];
  126. }
  127. return [
  128. {
  129. id: "session",
  130. name: t("Session"),
  131. boxId: "session",
  132. resourceId: sessionId,
  133. },
  134. ];
  135. }, [gameId, sessionId, t]);
  136. if (!sessionLoaded) {
  137. return <Waiter message={t("Session loading...")} />;
  138. }
  139. return (
  140. <>
  141. <GlobalConfProvider>
  142. <BoardView
  143. mediaLibraries={mediaLibraries}
  144. itemLibraries={itemLibraries}
  145. />
  146. </GlobalConfProvider>
  147. <SessionRestoreDim />
  148. {isMaster && <AutoSaveSession />}
  149. </>
  150. );
  151. };
  152. const ConnectedSessionView = ({ sessionId, fromGame }) => {
  153. const socket = useSocket();
  154. return (
  155. <BoardWrapper
  156. room={`room_${sessionId}`}
  157. session={sessionId}
  158. style={{
  159. position: "fixed",
  160. inset: "0",
  161. overflow: "hidden",
  162. }}
  163. socket={socket}
  164. >
  165. <SessionProvider sessionId={sessionId} fromGameId={fromGame}>
  166. <Session />
  167. </SessionProvider>
  168. </BoardWrapper>
  169. );
  170. };
  171. export default ConnectedSessionView;