diff --git a/front/assets/icon/ball.svg b/front/assets/icon/ball.svg new file mode 100644 index 0000000..6351088 --- /dev/null +++ b/front/assets/icon/ball.svg @@ -0,0 +1,62 @@ + + + + +Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + + + + + + + + diff --git a/front/components/editor/BallPiece.tsx b/front/components/editor/BallPiece.tsx new file mode 100644 index 0000000..baaba70 --- /dev/null +++ b/front/components/editor/BallPiece.tsx @@ -0,0 +1,21 @@ +import React, { RefObject } from "react" + +import "../../style/ball.css" + +import Ball from "../../assets/icon/ball.svg?react" +import Draggable from "react-draggable" + +export interface BallPieceProps { + onDrop: () => void + pieceRef: RefObject +} + +export function BallPiece({ onDrop, pieceRef }: BallPieceProps) { + return ( + +
+ +
+
+ ) +} diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index cd4774e..6229afd 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -1,3 +1,4 @@ +import CourtSvg from "../../assets/basketball_court.svg?react" import "../../style/basket_court.css" import { RefObject, useRef } from "react" import CourtPlayer from "./CourtPlayer" @@ -6,6 +7,7 @@ import { Player } from "../../tactic/Player" export interface BasketCourtProps { players: Player[] onPlayerRemove: (p: Player) => void + onBallDrop: (ref: HTMLDivElement) => void onPlayerChange: (p: Player) => void courtImage: string courtRef: RefObject @@ -14,6 +16,7 @@ export interface BasketCourtProps { export function BasketCourt({ players, onPlayerRemove, + onBallDrop, onPlayerChange, courtImage, courtRef, @@ -31,6 +34,7 @@ export function BasketCourt({ player={player} onChange={onPlayerChange} onRemove={() => onPlayerRemove(player)} + onBallDrop={onBallDrop} parentRef={courtRef} /> ) diff --git a/front/components/editor/CourtPlayer.tsx b/front/components/editor/CourtPlayer.tsx index 12083d3..6aebdcb 100644 --- a/front/components/editor/CourtPlayer.tsx +++ b/front/components/editor/CourtPlayer.tsx @@ -1,6 +1,7 @@ import { RefObject, useRef, useState } from "react" import "../../style/player.css" import RemoveIcon from "../../assets/icon/remove.svg?react" +import { BallPiece } from "./BallPiece" import Draggable from "react-draggable" import { PlayerPiece } from "./PlayerPiece" import { Player } from "../../tactic/Player" @@ -10,6 +11,7 @@ export interface PlayerProps { player: Player onChange: (p: Player) => void onRemove: () => void + onBallDrop: (ref: HTMLDivElement) => void parentRef: RefObject } @@ -20,12 +22,15 @@ export default function CourtPlayer({ player, onChange, onRemove, + onBallDrop, parentRef, }: PlayerProps) { const pieceRef = useRef(null) + const ballPiece = useRef(null) const x = player.rightRatio const y = player.bottomRatio + const hasBall = player.hasBall return (
{ @@ -65,8 +73,18 @@ export default function CourtPlayer({ className="player-selection-tab-remove" onClick={onRemove} /> + {hasBall && ( + onBallDrop(ballPiece.current!)} + pieceRef={ballPiece} + /> + )}
- +
diff --git a/front/components/editor/PlayerPiece.tsx b/front/components/editor/PlayerPiece.tsx index 69b38c2..e725d31 100644 --- a/front/components/editor/PlayerPiece.tsx +++ b/front/components/editor/PlayerPiece.tsx @@ -2,9 +2,20 @@ import React from "react" import "../../style/player.css" import { Team } from "../../tactic/Team" -export function PlayerPiece({ team, text }: { team: Team; text: string }) { +export interface PlayerPieceProps { + team: Team + text: string + hasBall: boolean +} + +export function PlayerPiece({ team, text, hasBall }: PlayerPieceProps) { + let className = `player-piece ${team}` + if (hasBall) { + className += ` player-piece-has-ball` + } + return ( -
+

{text}

) diff --git a/front/style/ball.css b/front/style/ball.css new file mode 100644 index 0000000..c14c196 --- /dev/null +++ b/front/style/ball.css @@ -0,0 +1,11 @@ +.ball * { + fill: #c5520d; +} + +.ball-div, +.ball { + pointer-events: all; + width: 20px; + height: 20px; + cursor: pointer; +} diff --git a/front/style/colors.css b/front/style/colors.css new file mode 100644 index 0000000..53a9f65 --- /dev/null +++ b/front/style/colors.css @@ -0,0 +1,13 @@ +:root { + --main-color: #ffffff; + --second-color: #ccde54; + + --background-color: #d2cdd3; + + --selected-team-primarycolor: #50b63a; + --selected-team-secondarycolor: #000000; + + --selection-color: #3f7fc4; + + --player-piece-ball-border-color: #000000; +} diff --git a/front/style/player.css b/front/style/player.css index 71baddb..6fa31a9 100644 --- a/front/style/player.css +++ b/front/style/player.css @@ -23,9 +23,7 @@ on the court. background-color: var(--selected-team-primarycolor); color: var(--selected-team-secondarycolor); - border-width: 2px; border-radius: 100px; - border-style: solid; width: 20px; height: 20px; @@ -38,20 +36,28 @@ on the court. user-select: none; } +.player-piece-has-ball { + border-width: 2px; + border-style: solid; + border-color: var(--player-piece-ball-border-color); +} + .player-selection-tab { display: none; position: absolute; - margin-bottom: 10%; + margin-bottom: -20%; justify-content: center; - width: 100%; + width: fit-content; transform: translateY(-20px); } .player-selection-tab-remove { pointer-events: all; - height: 25%; + width: 25px; + height: 17px; + justify-content: center; } .player-selection-tab-remove * { diff --git a/front/tactic/Ball.ts b/front/tactic/Ball.ts new file mode 100644 index 0000000..443e4f9 --- /dev/null +++ b/front/tactic/Ball.ts @@ -0,0 +1,11 @@ +export interface Ball { + /** + * 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 +} diff --git a/front/tactic/Player.ts b/front/tactic/Player.ts index 6530612..553b85e 100644 --- a/front/tactic/Player.ts +++ b/front/tactic/Player.ts @@ -1,6 +1,7 @@ import { Team } from "./Team" export interface Player { + id: string /** * the player's team * */ @@ -20,4 +21,6 @@ export interface Player { * Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle) */ rightRatio: number + + hasBall: boolean } diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index 99e5177..213607f 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -16,6 +16,7 @@ import halfCourt from "../assets/court/half_court.svg" import { Rack } from "../components/Rack" import { PlayerPiece } from "../components/editor/PlayerPiece" +import { BallPiece } from "../components/editor/BallPiece" import { Player } from "../tactic/Player" import { Tactic, TacticContent } from "../tactic/Tactic" import { fetchAPI } from "../Fetcher" @@ -121,6 +122,12 @@ function EditorView({ getRackPlayers(Team.Opponents, content.players), ) + const [showBall, setShowBall] = useState( + content.players.find((p) => p.hasBall) == undefined, + ) + + const ballPiece = useRef(null) + const courtDivContentRef = useRef(null) const canDetach = (ref: HTMLDivElement) => { @@ -147,16 +154,46 @@ function EditorView({ players: [ ...content.players, { + id: "player-" + content.players.length, team: element.team, role: element.key, rightRatio: x, bottomRatio: y, + hasBall: false, }, ], } }) } + const onBallDrop = (ref: HTMLDivElement) => { + const ballBounds = ref.getBoundingClientRect() + let ballAssigned = false + + setContent((content) => { + const players = content.players.map((player) => { + if (ballAssigned) { + return { ...player, hasBall: false } + } + const playerBounds = document + .getElementById(player.id)! + .getBoundingClientRect() + const doesOverlap = !( + ballBounds.top > playerBounds.bottom || + ballBounds.right < playerBounds.left || + ballBounds.bottom < playerBounds.top || + ballBounds.left > playerBounds.right + ) + if (doesOverlap) { + ballAssigned = true + } + return { ...player, hasBall: doesOverlap } + }) + setShowBall(!ballAssigned) + return { players: players } + }) + } + return (
@@ -186,9 +223,22 @@ function EditorView({ canDetach={canDetach} onElementDetached={onPieceDetach} render={({ team, key }) => ( - + )} /> + + {showBall && ( + onBallDrop(ballPiece.current!)} + pieceRef={ballPiece} + /> + )} + ( - + )} />
@@ -204,6 +259,7 @@ function EditorView({
[ ...players, {