RoomView.jsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import React from "react";
  2. import { nanoid } from "nanoid";
  3. import styled from "styled-components";
  4. import { RoomWrapper, useWire, useUsers } from "react-sync-board";
  5. import { Switch, Route, Link } from "react-router-dom";
  6. import Session from "../Session";
  7. import UserCircle from "../../users/UserCircle";
  8. import RoomNavBar from "./RoomNavBar";
  9. import table from "../../images/table.png";
  10. import { useTranslation } from "react-i18next";
  11. import { useSocket } from "@scripters/use-socket.io";
  12. const StyledPlayer = styled.li`
  13. position: absolute;
  14. top: 0;
  15. left: 0;
  16. right: 0;
  17. bottom: 0;
  18. display: flex;
  19. justify-content: center;
  20. transform: rotate(${({ rotate }) => rotate}deg);
  21. & > div {
  22. transform: rotate(-${({ rotate }) => rotate}deg);
  23. }
  24. `;
  25. const StyledRoom = styled.div`
  26. padding-top: 5em;
  27. background-color: var(--color-darkGrey);
  28. min-height: 100vh;
  29. & .tables {
  30. display: flex;
  31. justify-content: center;
  32. flex-wrap: wrap;
  33. margin: 0 10%;
  34. }
  35. & ul {
  36. list-style: none;
  37. margin: 0;
  38. padding: 0;
  39. }
  40. `;
  41. const StyledTable = styled.li`
  42. margin: 2em;
  43. position: relative;
  44. width: 250px;
  45. height: 250px;
  46. background-image: url(${table});
  47. background-size: cover;
  48. display: flex;
  49. justify-content: center;
  50. align-items: center;
  51. opacity: ${({ add }) => (add ? "0.5" : "1")};
  52. & .players {
  53. position: absolute;
  54. top: -1em;
  55. left: -1em;
  56. right: -1em;
  57. bottom: -1em;
  58. }
  59. & a {
  60. z-index: 1;
  61. position: absolute;
  62. top: 0;
  63. left: 0;
  64. right: 0;
  65. bottom: 0;
  66. display: flex;
  67. justify-content: center;
  68. align-items: center;
  69. font-size: 3em;
  70. }
  71. & .removeTable {
  72. z-index: 2;
  73. position: absolute;
  74. top: 0;
  75. right: 0;
  76. background: none;
  77. display: none;
  78. }
  79. &:hover {
  80. opacity: 1;
  81. }
  82. &:hover .removeTable {
  83. display: block;
  84. }
  85. `;
  86. const Room = ({ roomId, room, setRoom }) => {
  87. const { t } = useTranslation();
  88. const { users, setCurrentUser } = useUsers();
  89. const { isMaster } = useWire("room");
  90. const onAdd = () => {
  91. setRoom((prev) => ({
  92. ...prev,
  93. sessions: [...prev.sessions, { id: nanoid() }],
  94. }));
  95. };
  96. const onRemove = (idToRemove) => {
  97. setRoom((prev) => ({
  98. ...prev,
  99. sessions: prev.sessions.filter(({ id }) => id !== idToRemove),
  100. }));
  101. };
  102. React.useEffect(() => {
  103. setCurrentUser((prev) => ({ ...prev, space: roomId }));
  104. }, [roomId, setCurrentUser]);
  105. const usersBySpace = React.useMemo(
  106. () =>
  107. room.sessions.map(({ id: sessionId }) => ({
  108. id: sessionId,
  109. users: users.filter(({ space }) => space === sessionId),
  110. })),
  111. [room.sessions, users]
  112. );
  113. return (
  114. <StyledRoom>
  115. <RoomNavBar />
  116. <ul className="tables">
  117. {usersBySpace.map(({ id: sessionId, users: spaceUsers }, index) => (
  118. <StyledTable key={sessionId}>
  119. <ul className="players">
  120. {spaceUsers.map((user, index) => {
  121. return (
  122. <StyledPlayer
  123. key={user.uid}
  124. rotate={(360 / spaceUsers.length) * index}
  125. >
  126. <UserCircle {...user} />
  127. </StyledPlayer>
  128. );
  129. })}
  130. </ul>
  131. <Link to={`/room/${roomId}/session/${sessionId}`}>
  132. <span>#{index + 1}</span>
  133. </Link>
  134. {isMaster && spaceUsers.length === 0 && (
  135. <button
  136. className="removeTable"
  137. onClick={() => onRemove(sessionId)}
  138. >
  139. X
  140. </button>
  141. )}
  142. </StyledTable>
  143. ))}
  144. {isMaster && (
  145. <StyledTable add>
  146. <button className="addTable" onClick={onAdd}>
  147. {t("Add new table")}
  148. </button>
  149. </StyledTable>
  150. )}
  151. </ul>
  152. </StyledRoom>
  153. );
  154. };
  155. const SubscribeRoomEvents = ({ room, setRoom }) => {
  156. const { wire, isMaster } = useWire("room");
  157. const isMasterRef = React.useRef(isMaster);
  158. isMasterRef.current = isMaster;
  159. const roomRef = React.useRef(room);
  160. roomRef.current = room;
  161. // Register at startup
  162. React.useEffect(() => {
  163. const unsub = [];
  164. // Master register getRoom for peers
  165. if (isMaster) {
  166. wire
  167. .register("getRoom", () => {
  168. return roomRef.current;
  169. })
  170. .then((unregister) => {
  171. unsub.push(unregister);
  172. });
  173. } else {
  174. // Others register roomUpdate
  175. unsub.push(
  176. wire.subscribe("roomUpdate", (newRoom) => {
  177. setRoom(newRoom);
  178. })
  179. );
  180. }
  181. return () => {
  182. unsub.forEach((u) => u());
  183. };
  184. }, [wire, isMaster, setRoom]);
  185. // Get Room from master at connection
  186. React.useEffect(() => {
  187. if (!isMaster) {
  188. const onGetRoom = (roomData) => {
  189. setRoom(roomData);
  190. };
  191. wire.call("getRoom").then(onGetRoom, () => {
  192. // retry later
  193. setTimeout(() => {
  194. wire.call("getRoom").then(onGetRoom, (error) => console.log(error));
  195. }, 2000);
  196. });
  197. }
  198. }, [wire, isMaster, setRoom]);
  199. // Send room update on change if master
  200. React.useEffect(() => {
  201. if (isMaster) {
  202. wire.publish("roomUpdate", room);
  203. }
  204. }, [wire, isMaster, room]);
  205. return null;
  206. };
  207. const RoomView = ({ roomId }) => {
  208. const [room, setRoom] = React.useState(() => ({
  209. sessions: [{ id: nanoid() }, { id: nanoid() }, { id: nanoid() }],
  210. }));
  211. return (
  212. <>
  213. <Switch>
  214. <Route path="/room/:roomId/" exact>
  215. <Room roomId={roomId} room={room} setRoom={setRoom} />
  216. </Route>
  217. <Route path="/room/:roomId/session/:sessionId">
  218. {({
  219. match: {
  220. params: { sessionId },
  221. },
  222. }) => {
  223. return <Session sessionId={sessionId} />;
  224. }}
  225. </Route>
  226. </Switch>
  227. <SubscribeRoomEvents room={room} setRoom={setRoom} />
  228. </>
  229. );
  230. };
  231. const ConnectedRoomView = (props) => {
  232. const socket = useSocket();
  233. return (
  234. <RoomWrapper room={props.roomId} socket={socket}>
  235. <RoomView {...props} />
  236. </RoomWrapper>
  237. );
  238. };
  239. export default ConnectedRoomView;