fix ball drop issues

pull/77/head
maxime 2 years ago committed by vivien.dufour
parent 9fd4868d05
commit bfdde852f6

@ -1,27 +1,17 @@
import React, {RefObject, useRef} from "react"
import "../../style/ball.css"
import Ball from "../../assets/icon/ball.svg?react"
import Draggable from "react-draggable"
import BallSvg from "../../assets/icon/ball.svg?react"
import {Ball} from "../../tactic/CourtObjects";
export interface CourtBallProps {
onDrop: (pos: DOMRect) => void
onMoved: (rect: DOMRect) => void
ball: Ball
}
export function CourtBall({ onDrop }: CourtBallProps) {
const ref = useRef<HTMLElement>()
return (
<Draggable onStop={() => onDrop(ref.current!.getBoundingClientRect())} nodeRef={ref}>
<BallPiece pieceRef={ref}/>
</Draggable>
)
}
export function BallPiece({pieceRef}: {pieceRef: RefObject<HTMLElement>}) {
export function BallPiece() {
return (
<div ref={pieceRef} className={`ball-div`} >
<Ball className={"ball"} />
</div>
<BallSvg className={"ball"}/>
)
}

@ -1,36 +1,41 @@
import "../../style/basket_court.css"
import { RefObject, useRef } from "react"
import {RefObject} from "react"
import CourtPlayer from "./CourtPlayer"
import { Player } from "../../tactic/Player"
import {BallPiece, CourtBall} from "./BallPiece";
import {Ball} from "../../tactic/Ball";
import {Player} from "../../tactic/Player"
import {CourtObject} from "../../tactic/CourtObjects";
import {CourtBall} from "./CourtBall";
export interface BasketCourtProps {
players: Player[]
ball: Ball
objects: CourtObject[]
onPlayerRemove: (p: Player) => void
onBallDrop: (b: DOMRect) => void
onPlayerChange: (p: Player) => void
onBallMoved: (ball: DOMRect) => void,
courtImage: string
courtRef: RefObject<HTMLDivElement>
}
export function BasketCourt({
players,
ball,
onPlayerRemove,
onBallDrop,
onPlayerChange,
courtImage,
courtRef,
}: BasketCourtProps) {
players,
objects,
onPlayerRemove,
onBallMoved,
onPlayerChange,
courtImage,
courtRef,
}: BasketCourtProps) {
return (
<div
id="court-container"
ref={courtRef}
style={{ position: "relative" }}>
<img src={courtImage} alt={"court"} id="court-svg" />
style={{position: "relative"}}>
<img src={courtImage} alt={"court"} id="court-svg"/>
{players.map((player) => {
return (
<CourtPlayer
@ -38,12 +43,24 @@ export function BasketCourt({
player={player}
onChange={onPlayerChange}
onRemove={() => onPlayerRemove(player)}
onBallDrop={onBallDrop}
onBallDrop={onBallMoved}
parentRef={courtRef}
/>
)
})}
<CourtBall ball={ball}/>
{objects.map(object => {
if (object.type == "ball") {
return <CourtBall
onMoved={onBallMoved}
ball={object}
key="ball"
/>
}
throw new Error("unknown court object", object.type)
})}
</div>
)
}

@ -0,0 +1,28 @@
import React, {useRef} from "react";
import Draggable from "react-draggable";
import {BallPiece, CourtBallProps} from "./BallPiece";
export function CourtBall({onMoved, ball}: CourtBallProps) {
const pieceRef = useRef<HTMLDivElement>(null)
const x = ball.rightRatio
const y = ball.bottomRatio
return (
<Draggable
onStop={() => onMoved(pieceRef.current!.getBoundingClientRect())}
nodeRef={pieceRef}
>
<div className={"ball-div"}
ref={pieceRef}
style={{
position: "absolute",
left: `${x * 100}%`,
top: `${y * 100}%`,
}}
>
<BallPiece/>
</div>
</Draggable>
)
}

@ -1,16 +1,17 @@
import { RefObject, useRef, useState } from "react"
import {RefObject, useRef} from "react"
import "../../style/player.css"
import { BallPiece } from "./BallPiece"
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"
import { calculateRatio } from "../../Utils"
import {PlayerPiece} from "./PlayerPiece"
import {Player} from "../../tactic/Player"
import {calculateRatio} from "../../Utils"
export interface PlayerProps {
player: Player
onChange: (p: Player) => void
onRemove: () => void
onBallDrop: (b: DOMRect) => void
onBallDrop: (bounds: DOMRect) => void
parentRef: RefObject<HTMLDivElement>
}
@ -25,6 +26,7 @@ export default function CourtPlayer({
parentRef,
}: PlayerProps) {
const pieceRef = useRef<HTMLDivElement>(null)
const ballPiece = useRef<HTMLDivElement>(null)
const x = player.rightRatio
const y = player.bottomRatio
@ -67,12 +69,15 @@ export default function CourtPlayer({
if (e.key == "Delete") onRemove()
}}>
<div className="player-selection-tab">
{hasBall && (
<BallPiece
onDrop={() => onBallDrop(ball)}
pieceRef={ball}
/>
)}
<RemoveIcon
className="player-selection-tab-remove"
onClick={onRemove}
/>
{hasBall && (<Draggable nodeRef={ballPiece} onStop={() => onBallDrop(ballPiece.current!.getBoundingClientRect())}>
<div ref={ballPiece}>
<BallPiece />
</div>
</Draggable>)}
</div>
<PlayerPiece
team={player.team}

@ -1,11 +1,20 @@
export type CourtObject = {type: "ball"} & Ball
export interface Ball {
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
* The ball is a "ball" court object
*/
bottomRatio: number
readonly type: "ball",
/**
* Percentage of the player's position to the bottom (0 means top, 1 means bottom, 0.5 means middle)
*/
readonly bottomRatio: number
/**
* Percentage of the player's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
rightRatio: number
readonly rightRatio: number
}

@ -1,5 +1,5 @@
import { Player } from "./Player"
import {Ball} from "./Ball";
import {CourtObject} from "./CourtObjects";
export interface Tactic {
id: number
@ -9,5 +9,5 @@ export interface Tactic {
export interface TacticContent {
players: Player[]
ball : Ball
objects: CourtObject[]
}

@ -1,11 +1,4 @@
import {
CSSProperties,
Dispatch,
SetStateAction,
useCallback,
useRef,
useState,
} from "react"
import {CSSProperties, Dispatch, SetStateAction, useCallback, useRef, useState,} from "react"
import "../style/editor.css"
import TitleInput from "../components/TitleInput"
import { BasketCourt } from "../components/editor/BasketCourt"
@ -13,13 +6,13 @@ import { BasketCourt } from "../components/editor/BasketCourt"
import plainCourt from "../assets/court/full_court.svg"
import halfCourt from "../assets/court/half_court.svg"
import { Rack } from "../components/Rack"
import { PlayerPiece } from "../components/editor/PlayerPiece"
import {BallPiece, CourtBall} from "../components/editor/BallPiece";
import { Player } from "../tactic/Player"
import { Tactic, TacticContent } from "../tactic/Tactic"
import { fetchAPI } from "../Fetcher"
import { Team } from "../tactic/Team"
@ -28,8 +21,9 @@ import SavingState, {
SaveState,
SaveStates,
} from "../components/editor/SavingState"
import {Ball} from "../tactic/Ball";
import Draggable from "react-draggable";
import {CourtObject} from "../tactic/CourtObjects";
const ERROR_STYLE: CSSProperties = {
borderColor: "red",
@ -60,6 +54,8 @@ interface RackedPlayer {
key: string
}
type RackedCourtObject = { key: "ball" }
export default function Editor({
id,
name,
@ -130,15 +126,11 @@ function EditorView({
const [opponents, setOpponents] = useState(
getRackPlayers(Team.Opponents, content.players),
)
const [showBall, setShowBall] = useState(
content.players.find((p) => p.hasBall) == undefined,
)
const ballPiece = useRef<HTMLDivElement>(null)
const [objects, setObjects] = useState<RackedCourtObject[]>([{key: "ball"}])
const courtDivContentRef = useRef<HTMLDivElement>(null)
const canDetach = (bounds: DOMRect) => {
const courtBounds = courtDivContentRef.current!.getBoundingClientRect()
@ -159,6 +151,7 @@ function EditorView({
setContent((content) => {
return {
...content,
players: [
...content.players,
{
@ -170,14 +163,43 @@ function EditorView({
hasBall: false,
},
],
ball: content.ball
}
})
}
const onObjectDetach = (ref: HTMLDivElement, rackedObject: RackedCourtObject) => {
const refBounds = ref.getBoundingClientRect()
const courtBounds = courtDivContentRef.current!.getBoundingClientRect()
const {x, y} = calculateRatio(refBounds, courtBounds)
let courtObject: CourtObject
switch (rackedObject.key) {
case "ball":
courtObject = {
type: "ball",
rightRatio: x,
bottomRatio: y
}
break
default:
throw new Error("unknown court object ", rackedObject.key)
}
setContent((content) =>
({
...content,
objects: [
...content.objects,
courtObject,
]
})
)
}
const onBallDrop = (ballBounds: DOMRect) => {
let ballAssigned = false
@ -200,7 +222,14 @@ function EditorView({
}
return { ...player, hasBall: doesOverlap }
})
return {players: players, ball: content.ball}
let objects = content.objects
if (ballAssigned) {
const ballPieceIdx = content.objects.findIndex(obj => obj.type === "ball")
objects = objects.toSpliced(ballPieceIdx, 1)
}
return {...content, objects, players}
})
}
@ -231,7 +260,7 @@ function EditorView({
id="allies-rack"
objects={allies}
onChange={setAllies}
canDetach={canDetach}
canDetach={div => canDetach(div.getBoundingClientRect())}
onElementDetached={onPieceDetach}
render={({ team, key }) => (
<PlayerPiece
@ -243,17 +272,18 @@ function EditorView({
)}
/>
{rackBall && <CourtBall onDrop={pos => {
if (canDetach(pos)) {
onBallDetach(pos)
}
}}/>}
<Rack id={"objects"}
objects={objects}
onChange={setObjects}
canDetach={div => canDetach(div.getBoundingClientRect())}
onElementDetached={onObjectDetach}
render={renderCourtObject}/>
<Rack
id="opponent-rack"
objects={opponents}
onChange={setOpponents}
canDetach={canDetach}
canDetach={div => canDetach(div.getBoundingClientRect())}
onElementDetached={onPieceDetach}
render={({ team, key }) => (
<PlayerPiece
@ -269,30 +299,30 @@ function EditorView({
<div id="court-div-bounds">
<BasketCourt
players={content.players}
ball={content.ball}
onBallDrop={onBallDrop}
objects={content.objects}
onBallMoved={onBallDrop}
courtImage={
courtType == "PLAIN" ? plainCourt : halfCourt
}
courtRef={courtDivContentRef}
onPlayerChange={(player) => {
setContent((content) => ({
...content,
players: toSplicedPlayers(
content.players,
player,
true,
),
ball: content.ball
}))
}}
onPlayerRemove={(player) => {
setContent((content) => ({
...content,
players: toSplicedPlayers(
content.players,
player,
false,
),
ball: content.ball
}))
let setter
switch (player.team) {
@ -303,9 +333,8 @@ function EditorView({
setter = setAllies
}
if (player.hasBall) {
setShowBall(true)
/// add an instance of RackedBall back to the rack (objects)
}
setter((players) => [
...players,
{
@ -323,6 +352,13 @@ function EditorView({
)
}
function renderCourtObject(courtObject: RackedCourtObject) {
if (courtObject.key == "ball") {
return <BallPiece/>
}
throw new Error("unknown racked court object ", courtObject.key)
}
function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] {
return ["1", "2", "3", "4", "5"]
.filter(

@ -19,7 +19,7 @@ CREATE TABLE Tactic
name varchar NOT NULL,
creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL,
owner integer NOT NULL,
content varchar DEFAULT '{"players": []}' NOT NULL,
content varchar DEFAULT '{"players": [], "objects": []}' NOT NULL,
court_type varchar CHECK ( court_type IN ('HALF', 'PLAIN')) NOT NULL,
FOREIGN KEY (owner) REFERENCES Account
);

@ -42,7 +42,7 @@ class EditorController {
return ViewHttpResponse::react("views/Editor.tsx", [
"id" => -1, //-1 id means that the editor will not support saves
"name" => TacticModel::TACTIC_DEFAULT_NAME,
"content" => '{"players": []}',
"content" => '{"players": [], "objects": []}',
"courtType" => $courtType->name(),
]);
}

Loading…
Cancel
Save