188 lines
4.9 KiB
TypeScript
188 lines
4.9 KiB
TypeScript
import React, { useState, useEffect, useContext, useRef } from "react";
|
|
import Paper from "@mui/material/Paper";
|
|
import List from "@mui/material/List";
|
|
|
|
import "./PlayerList.css";
|
|
import { PlayerColor } from "./PlayerColor";
|
|
import { MediaAgent, MediaControl } from "./MediaControl";
|
|
|
|
import { GlobalContext } from "./GlobalContext";
|
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */
|
|
|
|
const PlayerList: React.FC = () => {
|
|
const { ws, name, sendJsonMessage } = useContext(GlobalContext);
|
|
const [players, setPlayers] = useState<{ [key: string]: any }>({});
|
|
const [unselected, setUneslected] = useState<string[]>([]);
|
|
const [state, setState] = useState<string>("lobby");
|
|
const [color, setColor] = useState<string | undefined>(undefined);
|
|
const [peers, setPeers] = useState<{ [key: string]: any }>({});
|
|
|
|
const onWsMessage = (event: MessageEvent) => {
|
|
const data = JSON.parse(event.data);
|
|
switch (data.type) {
|
|
case "game-update":
|
|
console.log(`player-list - game update`, data.update);
|
|
|
|
if ("unselected" in data.update) {
|
|
setUneslected(data.update.unselected);
|
|
}
|
|
|
|
if ("players" in data.update) {
|
|
let found = false;
|
|
for (const key in data.update.players) {
|
|
if (data.update.players[key].name === name) {
|
|
found = true;
|
|
setColor(key);
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
setColor(undefined);
|
|
}
|
|
setPlayers(data.update.players);
|
|
}
|
|
|
|
if ("state" in data.update && data.update.state !== state) {
|
|
setState(data.update.state);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
};
|
|
const refWsMessage = useRef(onWsMessage);
|
|
|
|
useEffect(() => {
|
|
refWsMessage.current = onWsMessage;
|
|
});
|
|
|
|
useEffect(() => {
|
|
if (!ws) {
|
|
return;
|
|
}
|
|
const cbMessage = (e: MessageEvent) => refWsMessage.current(e);
|
|
ws.addEventListener("message", cbMessage);
|
|
return () => {
|
|
ws.removeEventListener("message", cbMessage);
|
|
};
|
|
}, [ws, refWsMessage]);
|
|
|
|
useEffect(() => {
|
|
if (!sendJsonMessage) {
|
|
return;
|
|
}
|
|
sendJsonMessage({
|
|
type: "get",
|
|
fields: ["state", "players", "unselected"],
|
|
});
|
|
}, [ws]);
|
|
|
|
const toggleSelected = (key: string) => {
|
|
ws!.send(
|
|
JSON.stringify({
|
|
type: "set",
|
|
field: "color",
|
|
value: color === key ? "" : key,
|
|
})
|
|
);
|
|
};
|
|
|
|
const playerElements: React.ReactElement[] = [];
|
|
|
|
const inLobby = state === "lobby";
|
|
const sortedPlayers: any[] = [];
|
|
|
|
for (const key in players) {
|
|
sortedPlayers.push(players[key]);
|
|
}
|
|
|
|
const sortPlayers = (A: any, B: any) => {
|
|
/* active player first */
|
|
if (A.name === name) {
|
|
return -1;
|
|
}
|
|
if (B.name === name) {
|
|
return +1;
|
|
}
|
|
|
|
/* Sort active players first */
|
|
if (A.name && !B.name) {
|
|
return -1;
|
|
}
|
|
if (B.name && !A.name) {
|
|
return +1;
|
|
}
|
|
|
|
/* Ohterwise, sort by color */
|
|
return A.color.localeCompare(B.color);
|
|
};
|
|
|
|
sortedPlayers.sort(sortPlayers);
|
|
|
|
/* Array of just names... */
|
|
unselected.sort((A, B) => {
|
|
/* active player first */
|
|
if (A === name) {
|
|
return -1;
|
|
}
|
|
if (B === name) {
|
|
return +1;
|
|
}
|
|
/* Then sort alphabetically */
|
|
return A.localeCompare(B);
|
|
});
|
|
|
|
const videoClass = sortedPlayers.length <= 2 ? "Medium" : "Small";
|
|
|
|
sortedPlayers.forEach((player) => {
|
|
const playerName = player.name;
|
|
const selectable = inLobby && (player.status === "Not active" || color === player.color);
|
|
playerElements.push(
|
|
<div
|
|
data-selectable={selectable}
|
|
data-selected={player.color === color}
|
|
className="PlayerEntry"
|
|
onClick={() => {
|
|
inLobby && selectable && toggleSelected(player.color);
|
|
}}
|
|
key={`player-${player.color}`}
|
|
>
|
|
<div>
|
|
<PlayerColor color={player.color} />
|
|
<div className="Name">{playerName ? playerName : "Available"}</div>
|
|
{playerName && !player.live && <div className="NoNetwork"></div>}
|
|
</div>
|
|
{playerName && player.live && (
|
|
<MediaControl className={videoClass} peer={peers[playerName]} isSelf={player.color === color} />
|
|
)}
|
|
{!playerName && <div></div>}
|
|
</div>
|
|
);
|
|
});
|
|
|
|
const waiting = unselected.map((player) => {
|
|
return (
|
|
<div className={player === name ? "Self" : ""} key={player}>
|
|
<div>{player}</div>
|
|
<MediaControl className={"Small"} peer={peers[player]} isSelf={name === player} />
|
|
</div>
|
|
);
|
|
});
|
|
|
|
return (
|
|
<Paper className={`PlayerList ${videoClass}`}>
|
|
<MediaAgent setPeers={setPeers} />
|
|
<List className="PlayerSelector">{playerElements}</List>
|
|
{unselected && unselected.length !== 0 && (
|
|
<div className="Unselected">
|
|
<div>In lobby</div>
|
|
<div>{waiting}</div>
|
|
</div>
|
|
)}
|
|
</Paper>
|
|
);
|
|
};
|
|
|
|
export { PlayerList };
|