You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
137 lines
4.3 KiB
137 lines
4.3 KiB
import React, { KeyboardEventHandler, ReactNode, RefObject, useCallback, useRef } from "react"
|
|
import "../../style/player.css"
|
|
import Draggable from "react-draggable"
|
|
import { PlayerPiece } from "./PlayerPiece"
|
|
import { BallState, PlayerInfo } from "../../model/tactic/Player"
|
|
import { NULL_POS, Pos, ratioWithinBase } from "../../geo/Pos"
|
|
|
|
export interface CourtPlayerProps {
|
|
playerInfo: PlayerInfo
|
|
className?: string
|
|
availableActions: (ro: HTMLElement) => ReactNode[]
|
|
}
|
|
|
|
export interface EditableCourtPlayerProps extends CourtPlayerProps {
|
|
courtRef: RefObject<HTMLElement>
|
|
onPositionValidated: (newPos: Pos) => void
|
|
onRemove: () => void
|
|
}
|
|
|
|
const MOVE_AREA_SENSIBILITY = 0.001
|
|
|
|
export const PLAYER_RADIUS_PIXELS = 20
|
|
|
|
export function CourtPlayer({
|
|
playerInfo,
|
|
className,
|
|
availableActions,
|
|
}: CourtPlayerProps) {
|
|
|
|
const pieceRef = useRef<HTMLDivElement>(null)
|
|
|
|
return courtPlayerPiece({
|
|
playerInfo,
|
|
pieceRef,
|
|
className,
|
|
availableActions: () => availableActions(pieceRef.current!),
|
|
})
|
|
}
|
|
|
|
/**
|
|
* A player that is placed on the court, which can be selected, and moved in the associated bounds
|
|
* */
|
|
export function EditableCourtPlayer({
|
|
playerInfo,
|
|
className,
|
|
courtRef,
|
|
|
|
onPositionValidated,
|
|
onRemove,
|
|
availableActions,
|
|
}: EditableCourtPlayerProps) {
|
|
const pieceRef = useRef<HTMLDivElement>(null)
|
|
const { x, y } = playerInfo.pos
|
|
|
|
|
|
return (
|
|
<Draggable
|
|
handle=".player-piece"
|
|
nodeRef={pieceRef}
|
|
position={NULL_POS}
|
|
onStop={useCallback(() => {
|
|
const pieceBounds = pieceRef.current!.getBoundingClientRect()
|
|
const parentBounds = courtRef.current!.getBoundingClientRect()
|
|
|
|
const pos = ratioWithinBase(pieceBounds, parentBounds)
|
|
|
|
if (
|
|
Math.abs(pos.x - x) >= MOVE_AREA_SENSIBILITY ||
|
|
Math.abs(pos.y - y) >= MOVE_AREA_SENSIBILITY
|
|
)
|
|
onPositionValidated(pos)
|
|
}, [courtRef, onPositionValidated, x, y])}>
|
|
|
|
{courtPlayerPiece({
|
|
playerInfo,
|
|
className,
|
|
pieceRef,
|
|
availableActions: () => availableActions(pieceRef.current!),
|
|
onKeyUp: useCallback(
|
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
|
if (e.key == "Delete") onRemove()
|
|
},
|
|
[onRemove],
|
|
),
|
|
})}
|
|
</Draggable>
|
|
)
|
|
}
|
|
|
|
interface CourtPlayerPieceProps extends CourtPlayerProps {
|
|
pieceRef?: RefObject<HTMLDivElement>
|
|
availableActions?: () => ReactNode[]
|
|
onKeyUp?: KeyboardEventHandler<HTMLDivElement>
|
|
}
|
|
|
|
function courtPlayerPiece({
|
|
playerInfo,
|
|
className,
|
|
pieceRef,
|
|
onKeyUp,
|
|
availableActions,
|
|
}: CourtPlayerPieceProps) {
|
|
const usesBall = playerInfo.ballState != BallState.NONE
|
|
const { x, y } = playerInfo.pos
|
|
|
|
|
|
return (
|
|
<div
|
|
ref={pieceRef}
|
|
id={playerInfo.id}
|
|
className={"player " + (className ?? "")}
|
|
style={{
|
|
position: "absolute",
|
|
left: `${x * 100}%`,
|
|
top: `${y * 100}%`,
|
|
}}>
|
|
<div
|
|
tabIndex={0}
|
|
className="player-content"
|
|
onKeyUp={onKeyUp}
|
|
>
|
|
{
|
|
availableActions && (
|
|
<div className="player-actions">
|
|
{availableActions()}
|
|
</div>
|
|
)
|
|
}
|
|
<PlayerPiece
|
|
team={playerInfo.team}
|
|
text={playerInfo.role}
|
|
hasBall={usesBall}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
} |