drag and drop players and opponents from racks

pull/11/head
Override-6 1 year ago committed by maxime.batista
parent 2a5ece19a9
commit 5439fc6c47

@ -0,0 +1,59 @@
import {Dispatch, ReactElement, RefObject, SetStateAction, useRef} from "react";
import Draggable from "react-draggable";
export interface RackInput {
id: string,
objects: [ReactElement[], Dispatch<SetStateAction<ReactElement[]>>],
canDetach: (ref: RefObject<HTMLDivElement>) => boolean,
onElementDetached: (ref: RefObject<HTMLDivElement>, el: ReactElement) => void,
}
interface RackItemInput {
item: ReactElement,
onTryDetach: (ref: RefObject<HTMLDivElement>, el: ReactElement) => void
}
/**
* A container of draggable objects
* */
export function Rack({id, objects, canDetach, onElementDetached}: RackInput) {
const [rackObjects, setRackObjects] = objects
return (
<div id={id} style={{
display: "flex"
}}>
{rackObjects.map(element => (
<RackItem key={element.key}
item={element}
onTryDetach={(ref, element) => {
if (!canDetach(ref))
return
setRackObjects(objects => {
const index = objects.findIndex(o => o.key === element.key)
return objects.toSpliced(index, 1);
})
onElementDetached(ref, element)
}}/>
))}
</div>
)
}
function RackItem({item, onTryDetach}: RackItemInput) {
const divRef = useRef<HTMLDivElement>(null);
return (
<Draggable
position={{x: 0, y: 0}}
nodeRef={divRef}
onStop={() => onTryDetach(divRef, item)}>
<div ref={divRef}>
{item}
</div>
</Draggable>
)
}

@ -1,54 +1,39 @@
import CourtSvg from '../../assets/basketball_court.svg';
import '../../style/basket_court.css';
import React, {MouseEvent, ReactElement, useEffect, useRef, useState} from "react";
import Player from "./Player";
import Draggable from "react-draggable";
import {MouseEvent, ReactElement, useEffect, useRef, useState} from "react";
import CourtPlayer from "./CourtPlayer";
import {Player} from "../../data/Player";
const TEAM_MAX_PLAYER = 5;
export function BasketCourt() {
const [players, setPlayers] = useState<ReactElement[]>([])
export function BasketCourt({players}: { players: Player[] }) {
const [courtPlayers, setCourtPlayers] = useState<ReactElement[]>([])
const divRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const bounds = divRef.current!.getBoundingClientRect();
setCourtPlayers(players.map(player => {
return (
<CourtPlayer key={player.id}
pos={player.position}
x={(bounds.width * player.right_percentage)}
y={(bounds.height * player.bottom_percentage)}
bounds={{bottom: bounds.height, top: 0, left: 0, right: bounds.width}}
onRemove={() => {
// setCourtPlayers(players => {
// // recompute the player's index as it may have been moved if
// // previous players were removed and added.
// const playerCurrentIndex = players.findIndex(p => p.key === playerIndex.toString())
// return players.toSpliced(playerCurrentIndex, 1)
// })
}}
/>
)
}))
}, [players, divRef]);
return (
<div id="court-container" ref={divRef}>
<CourtSvg
id="court-svg"
onClick={(e: MouseEvent) => {
const bounds = divRef.current!.getBoundingClientRect();
if (players.length >= TEAM_MAX_PLAYER) {
return;
}
// find a valid number for the player to place.
let playerIndex = players.findIndex((v, i) =>
v.key !== i.toString()
);
if (playerIndex == -1) {
playerIndex = players.length;
}
const player = (
<Player key={playerIndex}
id={playerIndex + 1}
x={e.clientX - bounds.x}
y={e.clientY - bounds.y}
bounds={{bottom: bounds.height, top: 0, left: 0, right: bounds.width}}
onRemove={() => {
setPlayers(players => {
// recompute the player's index as it may have been moved if
// previous players were removed and added.
const playerCurrentIndex = players.findIndex(p => p.key === playerIndex.toString())
return players.toSpliced(playerCurrentIndex, 1)
})
}}
/>
);
setPlayers(players => players.toSpliced(playerIndex, 0, player))
}}/>
{players}
<CourtSvg id="court-svg"/>
{courtPlayers}
</div>
)
}

@ -2,16 +2,20 @@ import React, {useRef} from "react";
import "../../style/player.css";
import RemoveIcon from "../../assets/icon/remove.svg";
import Draggable, {DraggableBounds} from "react-draggable";
import {PlayerPiece} from "./PlayerPiece";
export interface PlayerOptions {
id: number,
pos: string,
x: number,
y: number,
bounds: DraggableBounds,
onRemove: () => void
}
export default function Player({id, x, y, bounds, onRemove}: PlayerOptions) {
/**
* A player that is placed on the court, which can be selected, and moved in the associated bounds
* */
export default function CourtPlayer({pos, x, y, bounds, onRemove}: PlayerOptions) {
const ref = useRef<HTMLDivElement>(null);
return (
@ -38,10 +42,7 @@ export default function Player({id, x, y, bounds, onRemove}: PlayerOptions) {
className="player-selection-tab-remove"
onClick={() => onRemove()}/>
</div>
<div
className="player-piece">
<p>{id}</p>
</div>
<PlayerPiece text={pos}/>
</div>
</div>

@ -0,0 +1,11 @@
import React from "react";
import '../../style/player.css'
export function PlayerPiece({text}: { text: string }) {
return (
<div className="player-piece">
<p>{text}</p>
</div>
)
}

@ -0,0 +1,22 @@
export interface Player {
/**
* unique identifier of the player.
* This identifier must be unique to the associated court.
*/
id: number,
/**
* player's position
* */
position: string,
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
*/
bottom_percentage: number
/**
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
right_percentage: number,
}

@ -18,10 +18,23 @@
align-items: stretch;
}
#racks {
display: flex;
justify-content: space-between;
}
.title_input {
width: 25ch;
}
#edit-div {
height: 100%;
}
#team-rack .player-piece , #opponent-rack .player-piece {
margin-left: 5px;
}
#court-div {
background-color: var(--background-color);
height: 100%;

@ -10,7 +10,7 @@ on the court.
.player-content {
/*apply a translation to center the player piece when placed*/
transform: translate(-50%, -75%);
transform: translate(-29%, -46%);
display: flex;
flex-direction: column;

@ -1,17 +1,64 @@
import React, {CSSProperties, useState} from "react";
import {CSSProperties, ReactElement, RefObject, useRef, useState} from "react";
import "../style/editor.css";
import TitleInput from "../components/TitleInput";
import {API} from "../Constants";
import {BasketCourt} from "../components/editor/BasketCourt";
import {Rack} from "../components/Rack";
import {PlayerPiece} from "../components/editor/PlayerPiece";
import {Player} from "../data/Player";
const ERROR_STYLE: CSSProperties = {
borderColor: "red"
}
export default function Editor({id, name}: { id: number, name: string }) {
const [style, setStyle] = useState<CSSProperties>({});
const positions = ["PG", "SG", "SF", "PF", "C"]
const [team, setTeams] = useState(
positions.map(pos => <PlayerPiece key={pos} text={pos}/>)
)
const [opponents, setOpponents] = useState(
positions.map(pos => <PlayerPiece key={pos} text={pos}/>)
)
const [players, setPlayers] = useState<Player[]>([]);
const courtDivContentRef = useRef<HTMLDivElement>(null);
const canDetach = (ref: RefObject<HTMLDivElement>) => {
const refBounds = ref.current!.getBoundingClientRect();
const courtBounds = courtDivContentRef.current!.getBoundingClientRect();
// check if refBounds overlaps courtBounds
return !(
refBounds.top > courtBounds.bottom ||
refBounds.right < courtBounds.left ||
refBounds.bottom < courtBounds.top ||
refBounds.left > courtBounds.right
);
}
const onElementDetach = (ref: RefObject<HTMLDivElement>, element: ReactElement) => {
const refBounds = ref.current!.getBoundingClientRect();
const courtBounds = courtDivContentRef.current!.getBoundingClientRect();
const relativeXPixels = refBounds.x - courtBounds.x;
const relativeYPixels = refBounds.y - courtBounds.y;
const xPercent = relativeXPixels / courtBounds.width;
const yPercent = relativeYPixels / courtBounds.height;
setPlayers(players => {
return [...players, {
id: players.length,
position: element.props.text,
right_percentage: xPercent,
bottom_percentage: yPercent
}]
})
}
return (
<div id="main-div">
<div id="topbar-div">
@ -36,8 +83,22 @@ export default function Editor({id, name}: { id: number, name: string }) {
}}/>
<div>RIGHT</div>
</div>
<div id="court-div">
<BasketCourt/>
<div id="edit-div">
<div id="racks">
<Rack id="team-rack"
objects={[team, setTeams]}
canDetach={canDetach}
onElementDetached={onElementDetach}/>
<Rack id="opponent-rack"
objects={[opponents, setOpponents]}
canDetach={canDetach}
onElementDetached={onElementDetach}/>
</div>
<div id="court-div">
<div ref={courtDivContentRef}>
<BasketCourt players={players}/>
</div>
</div>
</div>
</div>
)

Loading…
Cancel
Save