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.
Application-Web/src/components/editor/CourtPlayer.tsx

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>
)
}