Cursors.jsx 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import React from "react";
  2. import { useC2C } from "../../../hooks/useC2C";
  3. import Cursor from "./Cursor";
  4. export const Cursors = ({ users }) => {
  5. const [c2c] = useC2C();
  6. const [cursors, setCursors] = React.useState({});
  7. const preventRef = React.useRef(false);
  8. const usersById = React.useMemo(() => {
  9. return users.reduce((acc, user) => {
  10. acc[user.id] = user;
  11. return acc;
  12. }, {});
  13. }, [users]);
  14. // Prevent race condition when removing user
  15. const currentCursor = React.useMemo(() => {
  16. return users.reduce((acc, user) => {
  17. if (cursors[user.id]) {
  18. acc[user.id] = cursors[user.id];
  19. }
  20. return acc;
  21. }, {});
  22. }, [users, cursors]);
  23. React.useEffect(() => {
  24. setCursors((prevCursors) => {
  25. return users.reduce((acc, user) => {
  26. if (prevCursors[user.id]) {
  27. acc[user.id] = prevCursors[user.id];
  28. }
  29. return acc;
  30. }, {});
  31. });
  32. }, [users]);
  33. React.useEffect(() => {
  34. const unsub = [];
  35. unsub.push(
  36. c2c.subscribe("cursorMove", ({ userId, pos }) => {
  37. // Avoid move after cursor off
  38. if (preventRef.current) return;
  39. setCursors((prevCursors) => {
  40. return {
  41. ...prevCursors,
  42. [userId]: pos,
  43. };
  44. });
  45. })
  46. );
  47. unsub.push(
  48. c2c.subscribe("cursorOff", ({ userId }) => {
  49. setCursors((prevCursors) => {
  50. const newCursors = {
  51. ...prevCursors,
  52. };
  53. delete newCursors[userId];
  54. return newCursors;
  55. });
  56. // Prevent next moves
  57. preventRef.current = true;
  58. setTimeout(() => {
  59. preventRef.current = false;
  60. }, 100);
  61. })
  62. );
  63. return () => {
  64. unsub.map((c) => c());
  65. };
  66. }, [c2c]);
  67. return (
  68. <div>
  69. {Object.entries(currentCursor).map(([userId, pos]) => (
  70. <Cursor
  71. key={userId}
  72. pos={pos}
  73. text={usersById[userId].name}
  74. color={usersById[userId].color}
  75. />
  76. ))}
  77. </div>
  78. );
  79. };
  80. export default Cursors;