apply suggestions
continuous-integration/drone/push Build is passing Details

pull/11/head
maxime.batista 1 year ago committed by Override-6
parent ef80aa3192
commit c53a1b024c
Signed by untrusted user who does not match committer: maxime.batista
GPG Key ID: 8002CC4B4DD9ECA5

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

@ -1,35 +1,27 @@
import CourtSvg from '../../assets/basketball_court.svg';
import CourtSvg from '../../assets/basketball_court.svg?react';
import '../../style/basket_court.css';
import {MouseEvent, ReactElement, useEffect, useRef, useState} from "react";
import {useRef} from "react";
import CourtPlayer from "./CourtPlayer";
import {Player} from "../../data/Player";
export function BasketCourt({players, onPlayerRemove}: { players: Player[], onPlayerRemove: (Player) => void }) {
const [courtPlayers, setCourtPlayers] = useState<ReactElement[]>([])
const divRef = useRef<HTMLDivElement>(null);
export interface BasketCourtProps {
players: Player[],
onPlayerRemove: (p: Player) => void,
}
useEffect(() => {
const bounds = divRef.current!.getBoundingClientRect();
setCourtPlayers(players.map(player => {
return (
<CourtPlayer key={player.id}
pos={player.position}
team={player.team}
x={(bounds.width * player.right_percentage)}
y={(bounds.height * player.bottom_percentage)}
bounds={{bottom: bounds.height, top: 0, left: 0, right: bounds.width}}
onRemove={() => onPlayerRemove(player)}
/>
)
}))
}, [players, divRef]);
export function BasketCourt({players, onPlayerRemove}: BasketCourtProps) {
const divRef = useRef<HTMLDivElement>(null);
return (
<div id="court-container" ref={divRef}>
<div id="court-container" ref={divRef} style={{position: "relative"}}>
<CourtSvg id="court-svg"/>
{courtPlayers}
{players.map(player => {
return <CourtPlayer key={player.id}
player={player}
onRemove={() => onPlayerRemove(player)}
/>
})}
</div>
)
}

@ -1,35 +1,37 @@
import React, {useRef} from "react";
import {useRef} from "react";
import "../../style/player.css";
import RemoveIcon from "../../assets/icon/remove.svg";
import Draggable, {DraggableBounds} from "react-draggable";
import RemoveIcon from "../../assets/icon/remove.svg?react";
import Draggable from "react-draggable";
import {PlayerPiece} from "./PlayerPiece";
import {Player} from "../../data/Player";
export interface PlayerOptions {
pos: string,
team: string,
x: number,
y: number,
bounds: DraggableBounds,
export interface PlayerProps {
player: Player,
onRemove: () => void
}
/**
* A player that is placed on the court, which can be selected, and moved in the associated bounds
* */
export default function CourtPlayer({pos, team, x, y, bounds, onRemove}: PlayerOptions) {
export default function CourtPlayer({player, onRemove}: PlayerProps) {
const ref = useRef<HTMLDivElement>(null);
const x = player.rightRatio;
const y = player.bottomRatio;
return (
<Draggable
handle={".player-piece"}
nodeRef={ref}
bounds={bounds}
defaultPosition={{x: x, y: y}}
bounds="parent"
>
<div ref={ref}
className={"player"}
style={{
position: "absolute",
left: `${x * 100}%`,
top: `${y * 100}%`,
}}>
<div tabIndex={0}
@ -41,9 +43,9 @@ export default function CourtPlayer({pos, team, x, y, bounds, onRemove}: PlayerO
<div className="player-selection-tab">
<RemoveIcon
className="player-selection-tab-remove"
onClick={() => onRemove()}/>
onClick={onRemove}/>
</div>
<PlayerPiece team={team} text={pos}/>
<PlayerPiece team={player.team} text={player.role}/>
</div>
</div>

@ -1,8 +1,9 @@
import React from "react";
import '../../style/player.css'
import {Team} from "../../data/Team";
export function PlayerPiece({team, text}: { team: string, text: string }) {
export function PlayerPiece({team, text}: { team: Team, text: string }) {
return (
<div className={`player-piece ${team}`}>
<p>{text}</p>

@ -1,3 +1,5 @@
import {Team} from "./Team";
export interface Player {
/**
* unique identifier of the player.
@ -8,20 +10,20 @@ export interface Player {
/**
* the player's team
* */
team: "allies" | "opponents",
team: Team,
/**
* player's position
* */
position: string,
role: string,
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
*/
bottom_percentage: number
bottomRatio: number
/**
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
right_percentage: number,
rightRatio: number,
}

@ -0,0 +1,4 @@
export enum Team {
Allies = "allies",
Opponents = "opponents"
}

@ -3,6 +3,7 @@
#court-container {
display: flex;
background-color: var(--main-color);
}

@ -52,3 +52,8 @@
#court-div-bounds {
width: 60%;
}
.react-draggable {
z-index: 2;
}

@ -9,15 +9,11 @@ on the court.
}
.player-content {
/*apply a translation to center the player piece when placed*/
transform: translate(-29%, -46%);
display: flex;
flex-direction: column;
align-content: center;
align-items: center;
outline: none;
}
.player-piece {
@ -44,14 +40,18 @@ on the court.
.player-selection-tab {
display: flex;
position: absolute;
margin-bottom: 10%;
justify-content: center;
visibility: hidden;
width: 100%;
transform: translateY(-20px);
}
.player-selection-tab-remove {
pointer-events: all;
width: 25%;
height: 25%;
}

@ -7,27 +7,36 @@ import {BasketCourt} from "../components/editor/BasketCourt";
import {Rack} from "../components/Rack";
import {PlayerPiece} from "../components/editor/PlayerPiece";
import {Player} from "../data/Player";
import {Team} from "../data/Team";
const ERROR_STYLE: CSSProperties = {
borderColor: "red"
}
/**
* information about a player that is into a rack
*/
interface RackedPlayer {
team: Team,
key: string,
}
export default function Editor({id, name}: { id: number, name: string }) {
const [style, setStyle] = useState<CSSProperties>({});
const positions = ["1", "2", "3", "4", "5"]
const [allies, setAllies] = useState(
positions.map(pos => <PlayerPiece team="allies" key={pos} text={pos}/>)
positions.map(key => ({team: Team.Allies, key}))
)
const [opponents, setOpponents] = useState(
positions.map(pos => <PlayerPiece team="opponents" key={pos} text={pos}/>)
positions.map(key => ({team: Team.Opponents, key}))
)
const [players, setPlayers] = useState<Player[]>([]);
const courtDivContentRef = useRef<HTMLDivElement>(null);
const canDetach = (ref: RefObject<HTMLDivElement>) => {
const refBounds = ref.current!.getBoundingClientRect();
const canDetach = (ref: HTMLDivElement) => {
const refBounds = ref.getBoundingClientRect();
const courtBounds = courtDivContentRef.current!.getBoundingClientRect();
// check if refBounds overlaps courtBounds
@ -39,23 +48,23 @@ export default function Editor({id, name}: { id: number, name: string }) {
);
}
const onElementDetach = (ref: RefObject<HTMLDivElement>, element: ReactElement) => {
const refBounds = ref.current!.getBoundingClientRect();
const onPieceDetach = (ref: HTMLDivElement, element: RackedPlayer) => {
const refBounds = ref.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;
const xRatio = relativeXPixels / courtBounds.width;
const yRatio = relativeYPixels / courtBounds.height;
setPlayers(players => {
return [...players, {
id: players.length,
team: element.props.team,
position: element.props.text,
right_percentage: xPercent,
bottom_percentage: yPercent
team: element.team,
role: element.key,
rightRatio: xRatio,
bottomRatio: yRatio
}]
})
}
@ -87,13 +96,17 @@ export default function Editor({id, name}: { id: number, name: string }) {
<div id="edit-div">
<div id="racks">
<Rack id="allies-rack"
objects={[allies, setAllies]}
objects={allies}
onChange={setAllies}
canDetach={canDetach}
onElementDetached={onElementDetach}/>
onElementDetached={onPieceDetach}
render={({team, key}) => <PlayerPiece team={team} text={key} key={key}/>}/>
<Rack id="opponent-rack"
objects={[opponents, setOpponents]}
objects={opponents}
onChange={setOpponents}
canDetach={canDetach}
onElementDetached={onElementDetach}/>
onElementDetached={onPieceDetach}
render={({team, key}) => <PlayerPiece team={team} text={key} key={key}/>}/>
</div>
<div id="court-div">
<div id="court-div-bounds" ref={courtDivContentRef}>
@ -104,16 +117,23 @@ export default function Editor({id, name}: { id: number, name: string }) {
const idx = players.indexOf(player)
return players.toSpliced(idx, 1)
})
const piece = <PlayerPiece team={player.team} key={player.position} text={player.position}/>
switch (player.team) {
case "opponents":
case Team.Opponents:
setOpponents(opponents => (
[...opponents, piece]
[...opponents, {
team: player.team,
pos: player.role,
key: player.role
}]
))
break
case "allies":
case Team.Allies:
setAllies(allies => (
[...allies, piece]
[...allies, {
team: player.team,
pos: player.role,
key: player.role
}]
))
}
}}/>

@ -6,7 +6,7 @@
"dom.iterable",
"esnext"
],
"types": ["vite/client"],
"types": ["vite/client", "vite-plugin-svgr/client"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,

@ -41,7 +41,7 @@ export default defineConfig({
relativeCSSInjection: true,
}),
svgr({
include: "**/*.svg"
include: "**/*.svg?react"
})
]
})

Loading…
Cancel
Save