mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-07-06 05:23:56 -07:00
99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
import { GameSelectors, useAppSelector } from '@app/store';
|
|
import { cx } from '@app/utils';
|
|
|
|
import './PlayerList.css';
|
|
|
|
export interface PlayerListProps {
|
|
gameId: number | undefined;
|
|
}
|
|
|
|
// HSV gradient from green (0s) to red (>=10s); black when disconnected
|
|
// (ping < 0). Mirrors desktop's PingPixmapGenerator in
|
|
// cockatrice/src/interface/pixel_map_generator.cpp.
|
|
function pingCssColor(pingSeconds: number | undefined): string {
|
|
if (pingSeconds == null || pingSeconds < 0) {
|
|
return '#000';
|
|
}
|
|
const max = 10;
|
|
const ratio = Math.min(pingSeconds, max) / max;
|
|
const hue = 120 * (1 - ratio);
|
|
return `hsl(${hue}, 100%, 50%)`;
|
|
}
|
|
|
|
function PlayerList({ gameId }: PlayerListProps) {
|
|
const players = useAppSelector((state) =>
|
|
gameId != null ? GameSelectors.getPlayers(state, gameId) : undefined,
|
|
);
|
|
const activePlayerId = useAppSelector((state) =>
|
|
gameId != null ? GameSelectors.getActivePlayerId(state, gameId) : undefined,
|
|
);
|
|
const hostId = useAppSelector((state) =>
|
|
gameId != null ? GameSelectors.getHostId(state, gameId) : undefined,
|
|
);
|
|
|
|
const entries = players ? Object.values(players) : [];
|
|
|
|
return (
|
|
<div className="player-list" data-testid="player-list">
|
|
<div className="player-list__heading">Players</div>
|
|
<ul className="player-list__items">
|
|
{entries.length === 0 && (
|
|
<li className="player-list__empty">no players</li>
|
|
)}
|
|
{entries.map((p) => {
|
|
const pid = p.properties.playerId;
|
|
const name = p.properties.userInfo?.name ?? '(unknown)';
|
|
const isActive = pid === activePlayerId;
|
|
const isHost = pid === hostId;
|
|
const sideboardLocked = p.properties.sideboardLocked ?? false;
|
|
const pingSeconds = p.properties.pingSeconds;
|
|
const pingLabel = `ping ${pingSeconds ?? '?'}s`;
|
|
return (
|
|
<li
|
|
key={pid}
|
|
className={cx('player-list__item', {
|
|
'player-list__item--active': isActive,
|
|
'player-list__item--conceded': p.properties.conceded,
|
|
})}
|
|
data-testid={`player-list-item-${pid}`}
|
|
>
|
|
<span
|
|
className={cx('player-list__indicator', {
|
|
'player-list__indicator--active': isActive,
|
|
})}
|
|
/>
|
|
{isHost && (
|
|
<span
|
|
className="player-list__host-badge"
|
|
aria-label="host"
|
|
title="Host"
|
|
>
|
|
♛
|
|
</span>
|
|
)}
|
|
<span className="player-list__name">{name}</span>
|
|
{sideboardLocked && (
|
|
<span
|
|
className="player-list__sideboard-lock"
|
|
aria-label="sideboard locked"
|
|
title="Sideboard locked"
|
|
>
|
|
🔒
|
|
</span>
|
|
)}
|
|
<span
|
|
className="player-list__ping-dot"
|
|
style={{ background: pingCssColor(pingSeconds) }}
|
|
aria-label={pingLabel}
|
|
title={pingLabel}
|
|
data-testid={`ping-dot-${pid}`}
|
|
/>
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default PlayerList;
|