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/front/components/editor/BasketCourt.tsx

249 lines
8.5 KiB

import { CourtBall } from "./CourtBall"
import {
ReactElement,
RefObject,
useCallback,
useLayoutEffect,
useState,
} from "react"
import CourtPlayer from "./CourtPlayer"
import { Player } from "../../tactic/Player"
import { Action, MovementActionKind } from "../../tactic/Action"
import RemoveAction from "../actions/RemoveAction"
import ArrowAction from "../actions/ArrowAction"
import BendableArrow, { Segment } from "../arrows/BendableArrow"
import { middlePos, NULL_POS, Pos, ratioWithinBase } from "../arrows/Pos"
import BallAction from "../actions/BallAction"
import { CourtObject } from "../../tactic/CourtObjects"
export interface BasketCourtProps {
players: Player[]
actions: Action[]
objects: CourtObject[]
renderAction: (a: Action, key: number) => ReactElement
setActions: (f: (a: Action[]) => Action[]) => void
onPlayerRemove: (p: Player) => void
onPlayerChange: (p: Player) => void
onBallRemove: () => void
onBallMoved: (ball: DOMRect) => void
courtImage: () => ReactElement
courtRef: RefObject<HTMLDivElement>
}
export function BasketCourt({
players,
actions,
objects,
renderAction,
setActions,
onPlayerRemove,
onPlayerChange,
onBallMoved,
onBallRemove,
courtImage,
courtRef,
}: BasketCourtProps) {
function placeArrow(originRef: HTMLElement, arrowHead: DOMRect) {
for (const player of players) {
if (player.id == originRef.id) {
continue
}
const playerBounds = document
.getElementById(player.id)!
.getBoundingClientRect()
if (
!(
playerBounds.top > arrowHead.bottom ||
playerBounds.right < arrowHead.left ||
playerBounds.bottom < arrowHead.top ||
playerBounds.left > arrowHead.right
)
) {
const targetPos = document
.getElementById(player.id)!
.getBoundingClientRect()
const courtBounds = courtRef.current!.getBoundingClientRect()
const start = ratioWithinBase(
middlePos(originRef.getBoundingClientRect()),
courtBounds,
)
const end = ratioWithinBase(middlePos(targetPos), courtBounds)
const action: Action = {
fromPlayerId: originRef.id,
toPlayerId: player.id,
type: MovementActionKind.SCREEN,
moveFrom: start,
segments: [{ next: end }],
}
setActions((actions) => [...actions, action])
return
}
}
const action: Action = {
fromPlayerId: originRef.id,
type: MovementActionKind.MOVE,
moveFrom: middlePos(originRef.getBoundingClientRect()),
segments: [{ next: middlePos(arrowHead) }],
}
setActions((actions) => [...actions, action])
}
const [previewArrowOriginPos, setPreviewArrowOriginPos] =
useState<Pos>(NULL_POS)
const [isPreviewArrowEnabled, setPreviewArrowEnabled] = useState(false)
const [previewArrowEdges, setPreviewArrowEdges] = useState<Segment[]>([])
const updateActionsRelatedTo = useCallback((player: Player) => {
const newPos = ratioWithinBase(
middlePos(
document.getElementById(player.id)!.getBoundingClientRect(),
),
courtRef.current!.getBoundingClientRect(),
)
setActions((actions) =>
actions.map((a) => {
if (a.fromPlayerId == player.id) {
return { ...a, moveFrom: newPos }
}
if (a.toPlayerId == player.id) {
const segments = a.segments.toSpliced(
a.segments.length - 1,
1,
{
...a.segments[a.segments.length - 1],
next: newPos,
},
)
return { ...a, segments }
}
return a
}),
)
}, [])
const [internActions, setInternActions] = useState<Action[]>([])
useLayoutEffect(() => setInternActions(actions), [actions])
return (
<div
className="court-container"
ref={courtRef}
style={{ position: "relative" }}>
{courtImage()}
{internActions.map((action, idx) => renderAction(action, idx))}
{players.map((player) => (
<CourtPlayer
key={player.id}
player={player}
onDrag={() => updateActionsRelatedTo(player)}
onChange={onPlayerChange}
onRemove={() => onPlayerRemove(player)}
parentRef={courtRef}
availableActions={(pieceRef) => [
<RemoveAction
key={1}
onRemove={() => onPlayerRemove(player)}
/>,
<ArrowAction
key={2}
onHeadMoved={(headPos) => {
const baseBounds =
courtRef.current!.getBoundingClientRect()
setPreviewArrowEdges([
{
next: ratioWithinBase(
middlePos(headPos),
baseBounds,
),
},
])
}}
onHeadPicked={(headPos) => {
const baseBounds =
courtRef.current!.getBoundingClientRect()
setPreviewArrowOriginPos(
ratioWithinBase(
middlePos(
pieceRef.getBoundingClientRect(),
),
baseBounds,
),
)
setPreviewArrowEdges([
{
next: ratioWithinBase(
middlePos(headPos),
baseBounds,
),
},
])
setPreviewArrowEnabled(true)
}}
onHeadDropped={(headRect) => {
placeArrow(pieceRef, headRect)
setPreviewArrowEnabled(false)
}}
/>,
player.hasBall && (
<BallAction
key={3}
onDrop={(ref) =>
onBallMoved(ref.getBoundingClientRect())
}
/>
),
]}
/>
))}
{objects.map((object) => {
if (object.type == "ball") {
return (
<CourtBall
onMoved={onBallMoved}
ball={object}
onRemove={onBallRemove}
key="ball"
/>
)
}
throw new Error("unknown court object", object.type)
})}
{isPreviewArrowEnabled && (
<BendableArrow
area={courtRef}
startPos={previewArrowOriginPos}
segments={previewArrowEdges}
//do nothing on change, not really possible as it's a preview arrow
onSegmentsChanges={() => {}}
//TODO place those values in constants
endRadius={17}
startRadius={26}
/>
)}
</div>
)
}