From 05a22c6f7a10dbd6602a52ad916bcc7394e773b2 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Thu, 4 Jan 2024 19:20:37 +0100 Subject: [PATCH] use one array of TacticComponent --- front/components/arrows/BendableArrow.tsx | 2 +- front/components/editor/BallPiece.tsx | 3 +- front/components/editor/BasketCourt.tsx | 256 +++++++++---------- front/components/editor/CourtBall.tsx | 15 +- front/components/editor/CourtPlayer.tsx | 5 +- front/{components/arrows => geo}/Box.ts | 8 + front/{components/arrows => geo}/Pos.ts | 0 front/model/tactic/Action.ts | 9 +- front/model/tactic/Ball.ts | 22 +- front/model/tactic/Player.ts | 15 +- front/model/tactic/Tactic.ts | 32 ++- front/views/Editor.tsx | 284 +++++++++++----------- front/views/editor/CourtAction.tsx | 2 +- front/views/template/Header.tsx | 2 +- sql/setup-tables.sql | 2 +- src/App/Controller/EditorController.php | 2 +- src/App/Views/home.twig | 2 +- 17 files changed, 353 insertions(+), 308 deletions(-) rename front/{components/arrows => geo}/Box.ts (81%) rename front/{components/arrows => geo}/Pos.ts (100%) diff --git a/front/components/arrows/BendableArrow.tsx b/front/components/arrows/BendableArrow.tsx index b8f0f19..e2219bb 100644 --- a/front/components/arrows/BendableArrow.tsx +++ b/front/components/arrows/BendableArrow.tsx @@ -22,7 +22,7 @@ import { ratioWithinBase, relativeTo, norm, -} from "./Pos" +} from "../../geo/Pos" import "../../style/bendable_arrows.css" import Draggable from "react-draggable" diff --git a/front/components/editor/BallPiece.tsx b/front/components/editor/BallPiece.tsx index 2741249..d72ad75 100644 --- a/front/components/editor/BallPiece.tsx +++ b/front/components/editor/BallPiece.tsx @@ -1,7 +1,8 @@ import "../../style/ball.css" import BallSvg from "../../assets/icon/ball.svg?react" +import {BALL_ID} from "../../model/tactic/Ball"; export function BallPiece() { - return + return } diff --git a/front/components/editor/BasketCourt.tsx b/front/components/editor/BasketCourt.tsx index 525e232..7aba76c 100644 --- a/front/components/editor/BasketCourt.tsx +++ b/front/components/editor/BasketCourt.tsx @@ -12,16 +12,17 @@ import CourtPlayer from "./CourtPlayer" import { Player } from "../../model/tactic/Player" import { Action, ActionKind } from "../../model/tactic/Action" import ArrowAction from "../actions/ArrowAction" -import { middlePos, ratioWithinBase } from "../arrows/Pos" +import { middlePos, ratioWithinBase } from "../../geo/Pos" import BallAction from "../actions/BallAction" -import { CourtObject } from "../../model/tactic/Ball" -import { contains } from "../arrows/Box" +import {BALL_ID} from "../../model/tactic/Ball" +import { contains, overlaps } from "../../geo/Box" + import { CourtAction } from "../../views/editor/CourtAction" +import { TacticComponent } from "../../model/tactic/Tactic" export interface BasketCourtProps { - players: Player[] + components: TacticComponent[] actions: Action[] - objects: CourtObject[] renderAction: (a: Action, key: number) => ReactElement setActions: (f: (a: Action[]) => Action[]) => void @@ -37,9 +38,8 @@ export interface BasketCourtProps { } export function BasketCourt({ - players, + components, actions, - objects, renderAction, setActions, onPlayerRemove, @@ -59,33 +59,31 @@ export function BasketCourt({ courtBounds, ) - for (const player of players) { - if (player.id == origin.id) { + for (const component of components) { + if (component.id == origin.id) { continue } const playerBounds = document - .getElementById(player.id)! + .getElementById(component.id)! .getBoundingClientRect() - if ( - !( - playerBounds.top > arrowHead.bottom || - playerBounds.right < arrowHead.left || - playerBounds.bottom < arrowHead.top || - playerBounds.left > arrowHead.right - ) - ) { + if (overlaps(playerBounds, arrowHead)) { const targetPos = document - .getElementById(player.id)! + .getElementById(component.id)! .getBoundingClientRect() const end = ratioWithinBase(middlePos(targetPos), courtBounds) const action: Action = { - fromPlayerId: originRef.id, - toPlayerId: player.id, - type: origin.hasBall ? ActionKind.SHOOT : ActionKind.SCREEN, + fromId: originRef.id, + toId: component.id, + type: + component.type == "player" + ? origin.hasBall + ? ActionKind.SHOOT + : ActionKind.SCREEN + : ActionKind.MOVE, moveFrom: start, segments: [{ next: end }], } @@ -95,7 +93,7 @@ export function BasketCourt({ } const action: Action = { - fromPlayerId: originRef.id, + fromId: originRef.id, type: origin.hasBall ? ActionKind.DRIBBLE : ActionKind.MOVE, moveFrom: ratioWithinBase( middlePos(originRef.getBoundingClientRect()), @@ -110,20 +108,20 @@ export function BasketCourt({ const [previewAction, setPreviewAction] = useState(null) - const updateActionsRelatedTo = useCallback((player: Player) => { + const updateActionsRelatedTo = useCallback((comp: TacticComponent) => { const newPos = ratioWithinBase( middlePos( - document.getElementById(player.id)!.getBoundingClientRect(), + document.getElementById(comp.id)!.getBoundingClientRect(), ), courtRef.current!.getBoundingClientRect(), ) setActions((actions) => actions.map((a) => { - if (a.fromPlayerId == player.id) { + if (a.fromId == comp.id) { return { ...a, moveFrom: newPos } } - if (a.toPlayerId == player.id) { + if (a.toId == comp.id) { const segments = a.segments.toSpliced( a.segments.length - 1, 1, @@ -151,113 +149,125 @@ export function BasketCourt({ style={{ position: "relative" }}> {courtImage} - {players.map((player) => ( - updateActionsRelatedTo(player)} - onChange={onPlayerChange} - onRemove={() => onPlayerRemove(player)} - courtRef={courtRef} - availableActions={(pieceRef) => [ - { - const baseBounds = - courtRef.current!.getBoundingClientRect() - - const arrowHeadPos = middlePos(headPos) - - const target = players.find( - (p) => - p != player && - contains( - document - .getElementById(p.id)! - .getBoundingClientRect(), - arrowHeadPos, - ), - ) - - setPreviewAction((action) => ({ - ...action!, - segments: [ - { - next: ratioWithinBase( - arrowHeadPos, - baseBounds, - ), - }, - ], - type: player.hasBall - ? target - ? ActionKind.SHOOT - : ActionKind.DRIBBLE - : target - ? ActionKind.SCREEN - : ActionKind.MOVE, - })) - }} - onHeadPicked={(headPos) => { - ;(document.activeElement as HTMLElement).blur() - const baseBounds = - courtRef.current!.getBoundingClientRect() - - setPreviewAction({ - type: player.hasBall - ? ActionKind.DRIBBLE - : ActionKind.MOVE, - fromPlayerId: player.id, - toPlayerId: undefined, - moveFrom: ratioWithinBase( - middlePos( - pieceRef.getBoundingClientRect(), - ), - baseBounds, - ), - segments: [ - { - next: ratioWithinBase( - middlePos(headPos), - baseBounds, - ), - }, - ], - }) - }} - onHeadDropped={(headRect) => { - placeArrow(player, headRect) - setPreviewAction(null) - }} - />, - player.hasBall && ( - - onBallMoved(ref.getBoundingClientRect()) - } - /> - ), - ]} - /> - ))} + {components.map((component) => { + if (component.type == "player") { + const player = component + return ( + updateActionsRelatedTo(player)} + onChange={onPlayerChange} + onRemove={() => onPlayerRemove(player)} + courtRef={courtRef} + availableActions={(pieceRef) => [ + { + const baseBounds = + courtRef.current!.getBoundingClientRect() - {internActions.map((action, idx) => renderAction(action, idx))} + const arrowHeadPos = middlePos(headPos) + + const target = components.find( + (c) => + c.id != player.id && + contains( + document + .getElementById(c.id)! + .getBoundingClientRect(), + arrowHeadPos, + ), + ) - {objects.map((object) => { - if (object.type == "ball") { + const type = + target?.type == "player" + ? player.hasBall + ? target + ? ActionKind.SHOOT + : ActionKind.DRIBBLE + : target + ? ActionKind.SCREEN + : ActionKind.MOVE + : ActionKind.MOVE + + setPreviewAction((action) => ({ + ...action!, + segments: [ + { + next: ratioWithinBase( + arrowHeadPos, + baseBounds, + ), + }, + ], + type, + })) + }} + onHeadPicked={(headPos) => { + ;( + document.activeElement as HTMLElement + ).blur() + const baseBounds = + courtRef.current!.getBoundingClientRect() + + setPreviewAction({ + type: player.hasBall + ? ActionKind.DRIBBLE + : ActionKind.MOVE, + fromId: player.id, + toId: undefined, + moveFrom: ratioWithinBase( + middlePos( + pieceRef.getBoundingClientRect(), + ), + baseBounds, + ), + segments: [ + { + next: ratioWithinBase( + middlePos(headPos), + baseBounds, + ), + }, + ], + }) + }} + onHeadDropped={(headRect) => { + placeArrow(player, headRect) + setPreviewAction(null) + }} + />, + player.hasBall && ( + + onBallMoved( + ref.getBoundingClientRect(), + ) + } + /> + ), + ]} + /> + ) + } + if (component.type == BALL_ID) { return ( updateActionsRelatedTo(component)} + ball={component} onRemove={onBallRemove} key="ball" /> ) } - throw new Error("unknown court object" + object.type) + throw new Error("unknown tactic component " + component) })} + {internActions.map((action, idx) => renderAction(action, idx))} + {previewAction && ( void + onPosValidated: (rect: DOMRect) => void + onMoves: () => void onRemove: () => void ball: Ball } -export function CourtBall({ onMoved, ball, onRemove }: CourtBallProps) { +export function CourtBall({ + onPosValidated, + ball, + onRemove, + onMoves, +}: CourtBallProps) { const pieceRef = useRef(null) const x = ball.rightRatio @@ -17,7 +23,10 @@ export function CourtBall({ onMoved, ball, onRemove }: CourtBallProps) { return ( onMoved(pieceRef.current!.getBoundingClientRect())} + onStop={() => + onPosValidated(pieceRef.current!.getBoundingClientRect()) + } + onDrag={onMoves} nodeRef={pieceRef}>
= box.x && diff --git a/front/components/arrows/Pos.ts b/front/geo/Pos.ts similarity index 100% rename from front/components/arrows/Pos.ts rename to front/geo/Pos.ts diff --git a/front/model/tactic/Action.ts b/front/model/tactic/Action.ts index 0b5aee5..d238398 100644 --- a/front/model/tactic/Action.ts +++ b/front/model/tactic/Action.ts @@ -1,6 +1,7 @@ -import { Pos } from "../../components/arrows/Pos" + +import { Pos } from "../../geo/Pos" import { Segment } from "../../components/arrows/BendableArrow" -import { PlayerId } from "./Player" +import { ComponentId } from "./Tactic" export enum ActionKind { SCREEN = "SCREEN", @@ -12,8 +13,8 @@ export enum ActionKind { export type Action = { type: ActionKind } & MovementAction export interface MovementAction { - fromPlayerId: PlayerId - toPlayerId?: PlayerId + fromId: ComponentId + toId?: ComponentId moveFrom: Pos segments: Segment[] } diff --git a/front/model/tactic/Ball.ts b/front/model/tactic/Ball.ts index 28e4830..96cde26 100644 --- a/front/model/tactic/Ball.ts +++ b/front/model/tactic/Ball.ts @@ -1,17 +1,9 @@ -export type CourtObject = { type: "ball" } & Ball +import { Component } from "./Tactic" -export interface Ball { - /** - * The ball is a "ball" court object - */ - readonly type: "ball" +export const BALL_ID = "ball" +export const BALL_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) - */ - readonly rightRatio: number -} +//place here all different kinds of objects +export type CourtObject = Ball + +export type Ball = Component diff --git a/front/model/tactic/Player.ts b/front/model/tactic/Player.ts index f94d6bf..e558496 100644 --- a/front/model/tactic/Player.ts +++ b/front/model/tactic/Player.ts @@ -1,3 +1,5 @@ +import {Component} from "./Tactic"; + export type PlayerId = string export enum PlayerTeam { @@ -7,7 +9,9 @@ export enum PlayerTeam { export interface Player { readonly id: PlayerId +} +export interface Player extends Component<"player"> { /** * the player's team * */ @@ -18,18 +22,9 @@ export interface Player { * */ readonly role: string - /** - * 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) - */ - readonly rightRatio: number - /** * True if the player has a basketball */ readonly hasBall: boolean } + diff --git a/front/model/tactic/Tactic.ts b/front/model/tactic/Tactic.ts index 2eab85b..6580dbb 100644 --- a/front/model/tactic/Tactic.ts +++ b/front/model/tactic/Tactic.ts @@ -1,6 +1,6 @@ -import { Player } from "./Player" -import { CourtObject } from "./Ball" -import { Action } from "./Action" +import {Player} from "./Player" +import {Action} from "./Action" +import {CourtObject} from "./Ball" export interface Tactic { id: number @@ -9,7 +9,29 @@ export interface Tactic { } export interface TacticContent { - players: Player[] - objects: CourtObject[] + components: TacticComponent[] actions: Action[] } + +export type TacticComponent = Player | CourtObject +export type ComponentId = string + +export interface Component { + /** + * The component's type + */ + readonly type: T + /** + * The component's identifier + */ + readonly id: ComponentId + /** + * 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) + */ + readonly rightRatio: number +} diff --git a/front/views/Editor.tsx b/front/views/Editor.tsx index cbb2da5..387ad74 100644 --- a/front/views/Editor.tsx +++ b/front/views/Editor.tsx @@ -1,38 +1,27 @@ -import { - CSSProperties, - Dispatch, - SetStateAction, - useCallback, - useMemo, - useRef, - useState, -} from "react" +import {CSSProperties, Dispatch, SetStateAction, useCallback, useMemo, useRef, useState,} from "react" import "../style/editor.css" import TitleInput from "../components/TitleInput" import PlainCourt from "../assets/court/full_court.svg?react" import HalfCourt from "../assets/court/half_court.svg?react" -import { BallPiece } from "../components/editor/BallPiece" +import {BallPiece} from "../components/editor/BallPiece" -import { Rack } from "../components/Rack" -import { PlayerPiece } from "../components/editor/PlayerPiece" -import { Player } from "../model/tactic/Player" +import {Rack} from "../components/Rack" +import {PlayerPiece} from "../components/editor/PlayerPiece" +import {Player, PlayerTeam} from "../model/tactic/Player" -import { Tactic, TacticContent } from "../model/tactic/Tactic" -import { fetchAPI } from "../Fetcher" -import { PlayerTeam } from "../model/tactic/Player" +import {Tactic, TacticComponent, TacticContent} from "../model/tactic/Tactic" +import {fetchAPI} from "../Fetcher" -import SavingState, { - SaveState, - SaveStates, -} from "../components/editor/SavingState" +import SavingState, {SaveState, SaveStates,} from "../components/editor/SavingState" -import { CourtObject } from "../model/tactic/Ball" -import { CourtAction } from "./editor/CourtAction" -import { BasketCourt } from "../components/editor/BasketCourt" -import { ratioWithinBase } from "../components/arrows/Pos" -import { Action, ActionKind } from "../model/tactic/Action" -import { BASE } from "../Constants" +import {BALL_ID, BALL_TYPE, CourtObject, Ball} from "../model/tactic/Ball" +import {CourtAction} from "./editor/CourtAction" +import {BasketCourt} from "../components/editor/BasketCourt" +import {Action, ActionKind} from "../model/tactic/Action" +import {BASE} from "../Constants" +import {overlaps} from "../geo/Box" +import {ratioWithinBase} from "../geo/Pos" const ERROR_STYLE: CSSProperties = { borderColor: "red", @@ -135,10 +124,10 @@ function EditorView({ ) const [allies, setAllies] = useState( - getRackPlayers(PlayerTeam.Allies, content.players), + getRackPlayers(PlayerTeam.Allies, content.components), ) const [opponents, setOpponents] = useState( - getRackPlayers(PlayerTeam.Opponents, content.players), + getRackPlayers(PlayerTeam.Opponents, content.components), ) const [objects, setObjects] = useState( @@ -151,15 +140,10 @@ function EditorView({ const courtBounds = courtDivContentRef.current!.getBoundingClientRect() // check if refBounds overlaps courtBounds - return !( - bounds.top > courtBounds.bottom || - bounds.right < courtBounds.left || - bounds.bottom < courtBounds.top || - bounds.left > courtBounds.right - ) + return overlaps(courtBounds, bounds) } - const onPieceDetach = (ref: HTMLDivElement, element: RackedPlayer) => { + const onRackPieceDetach = (ref: HTMLDivElement, element: RackedPlayer) => { const refBounds = ref.getBoundingClientRect() const courtBounds = courtDivContentRef.current!.getBoundingClientRect() @@ -168,23 +152,24 @@ function EditorView({ setContent((content) => { return { ...content, - players: [ - ...content.players, + components: [ + ...content.components, { + type: "player", id: "player-" + element.key + "-" + element.team, team: element.team, role: element.key, rightRatio: x, bottomRatio: y, hasBall: false, - }, + } as Player, ], actions: content.actions, } }) } - const onObjectDetach = ( + const onRackedObjectDetach = ( ref: HTMLDivElement, rackedObject: RackedCourtObject, ) => { @@ -196,27 +181,22 @@ function EditorView({ let courtObject: CourtObject switch (rackedObject.key) { - case "ball": - const ballObj = content.objects.findIndex( - (o) => o.type == "ball", + case BALL_TYPE: + const ballObj = content.components.findIndex( + (o) => o.type == BALL_TYPE, ) - const playerCollidedIdx = getPlayerCollided( + const playerCollidedIdx = getComponentCollided( refBounds, - content.players, + content.components.toSpliced(ballObj, 1), ) if (playerCollidedIdx != -1) { - onBallDropOnPlayer(playerCollidedIdx) - setContent((content) => { - return { - ...content, - objects: content.objects.toSpliced(ballObj, 1), - } - }) + onBallDropOnComponent(playerCollidedIdx) return } courtObject = { - type: "ball", + type: BALL_TYPE, + id: BALL_ID, rightRatio: x, bottomRatio: y, } @@ -229,38 +209,34 @@ function EditorView({ setContent((content) => { return { ...content, - objects: [...content.objects, courtObject], + components: [...content.components, courtObject], } }) } - const getPlayerCollided = ( + const getComponentCollided = ( bounds: DOMRect, - players: Player[], + components: TacticComponent[], ): number | -1 => { - for (let i = 0; i < players.length; i++) { - const player = players[i] + for (let i = 0; i < components.length; i++) { + const component = components[i] const playerBounds = document - .getElementById(player.id)! + .getElementById(component.id)! .getBoundingClientRect() - const doesOverlap = !( - bounds.top > playerBounds.bottom || - bounds.right < playerBounds.left || - bounds.bottom < playerBounds.top || - bounds.left > playerBounds.right - ) - if (doesOverlap) { + if (overlaps(playerBounds, bounds)) { return i } } return -1 } - function updateActions(actions: Action[], players: Player[]) { + function updateActions(actions: Action[], components: TacticComponent[]) { return actions.map((action) => { - const originHasBall = players.find( - (p) => p.id == action.fromPlayerId, - )!.hasBall + const originHasBall = ( + components.find( + (p) => p.type == "player" && p.id == action.fromId, + )! as Player + ).hasBall let type = action.type @@ -280,80 +256,101 @@ function EditorView({ }) } - const onBallDropOnPlayer = (playerCollidedIdx: number) => { + const onBallDropOnComponent = (collidedComponentIdx: number) => { setContent((content) => { - const ballObj = content.objects.findIndex((o) => o.type == "ball") - let player = content.players.at(playerCollidedIdx) as Player - const players = content.players.toSpliced(playerCollidedIdx, 1, { - ...player, - hasBall: true, - }) + const ballObj = content.components.findIndex( + (p) => p.type == BALL_TYPE, + ) + let component = content.components[collidedComponentIdx] + if (component.type != "player") { + return content //do nothing if the ball isn't dropped on a player. + } + const components = content.components.toSpliced( + collidedComponentIdx, + 1, + { + ...component, + hasBall: true, + }, + ) + // Maybe the ball is not present on the court as an object component + // if so, don't bother removing it from the court. + // This can occur if the user drags and drop the ball from a player that already has the ball + // to another component + if (ballObj != -1) { + components.splice(ballObj, 1) + } return { ...content, - actions: updateActions(content.actions, players), - players, - objects: content.objects.toSpliced(ballObj, 1), + actions: updateActions(content.actions, components), + components, } }) } - const onBallDrop = (refBounds: DOMRect) => { + const onBallMoved = (refBounds: DOMRect) => { if (!isBoundsOnCourt(refBounds)) { removeCourtBall() return } - const playerCollidedIdx = getPlayerCollided(refBounds, content.players) + const playerCollidedIdx = getComponentCollided( + refBounds, + content.components, + ) if (playerCollidedIdx != -1) { setContent((content) => { return { ...content, - players: content.players.map((player) => ({ - ...player, - hasBall: false, - })), + components: content.components.map((c) => + c.type == "player" + ? { + ...c, + hasBall: false, + } + : c, + ), } }) - onBallDropOnPlayer(playerCollidedIdx) + onBallDropOnComponent(playerCollidedIdx) return } - if (content.objects.findIndex((o) => o.type == "ball") != -1) { + if (content.components.findIndex((o) => o.type == "ball") != -1) { return } const courtBounds = courtDivContentRef.current!.getBoundingClientRect() const { x, y } = ratioWithinBase(refBounds, courtBounds) - let courtObject: CourtObject - - courtObject = { - type: "ball", + const courtObject = { + type: BALL_TYPE, + id: BALL_ID, rightRatio: x, bottomRatio: y, - } + } as Ball + + let components = content.components.map((c) => + c.type == "player" + ? { + ...c, + hasBall: false, + } + : c, + ) + components = [...components, courtObject] - const players = content.players.map((player) => ({ - ...player, - hasBall: false, + setContent((content) => ({ + ...content, + actions: updateActions(content.actions, components), + components, })) - - setContent((content) => { - return { - ...content, - actions: updateActions(content.actions, players), - players, - objects: [...content.objects, courtObject], - } - }) } const removePlayer = (player: Player) => { setContent((content) => ({ ...content, - players: toSplicedPlayers(content.players, player, false), - objects: [...content.objects], + components: replaceOrInsert(content.components, player, false), actions: content.actions.filter( - (a) => - a.toPlayerId !== player.id && a.fromPlayerId !== player.id, + (a) => a.toId !== player.id && a.fromId !== player.id, ), })) let setter @@ -379,14 +376,21 @@ function EditorView({ const removeCourtBall = () => { setContent((content) => { - const ballObj = content.objects.findIndex((o) => o.type == "ball") + const ballObj = content.components.findIndex( + (o) => o.type == "ball", + ) + const components = content.components.map((c) => + c.type == "player" + ? ({ + ...c, + hasBall: false, + } as Player) + : c, + ) + components.splice(ballObj, 1) return { ...content, - players: content.players.map((player) => ({ - ...player, - hasBall: false, - })), - objects: content.objects.toSpliced(ballObj, 1), + components, } }) setObjects([{ key: "ball" }]) @@ -423,7 +427,7 @@ function EditorView({ canDetach={(div) => isBoundsOnCourt(div.getBoundingClientRect()) } - onElementDetached={onPieceDetach} + onElementDetached={onRackPieceDetach} render={({ team, key }) => ( isBoundsOnCourt(div.getBoundingClientRect()) } - onElementDetached={onObjectDetach} + onElementDetached={onRackedObjectDetach} render={renderCourtObject} /> @@ -452,7 +456,7 @@ function EditorView({ canDetach={(div) => isBoundsOnCourt(div.getBoundingClientRect()) } - onElementDetached={onPieceDetach} + onElementDetached={onRackPieceDetach} render={({ team, key }) => (
} courtRef={courtDivContentRef} setActions={(actions) => setContent((content) => ({ ...content, - players: content.players, actions: actions(content.actions), })) } @@ -515,8 +517,8 @@ function EditorView({ } setContent((content) => ({ ...content, - players: toSplicedPlayers( - content.players, + components: replaceOrInsert( + content.components, player, true, ), @@ -533,10 +535,11 @@ function EditorView({ } function isBallOnCourt(content: TacticContent) { - if (content.players.findIndex((p) => p.hasBall) != -1) { - return true - } - return content.objects.findIndex((o) => o.type == "ball") != -1 + return ( + content.components.findIndex( + (c) => (c.type == "player" && c.hasBall) || c.type == BALL_TYPE, + ) != -1 + ) } function renderCourtObject(courtObject: RackedCourtObject) { @@ -558,12 +561,18 @@ function Court({ courtType }: { courtType: string }) { ) } -function getRackPlayers(team: PlayerTeam, players: Player[]): RackedPlayer[] { + +function getRackPlayers( + team: PlayerTeam, + components: TacticComponent[], +): RackedPlayer[] { return ["1", "2", "3", "4", "5"] .filter( (role) => - players.findIndex((p) => p.team == team && p.role == role) == - -1, + components.findIndex( + (c) => + c.type == "player" && c.team == team && c.role == role, + ) == -1, ) .map((key) => ({ team, key })) } @@ -611,14 +620,11 @@ function useContentState( return [content, setContentSynced, savingState] } -function toSplicedPlayers( - players: Player[], - player: Player, +function replaceOrInsert( + array: A[], + it: A, replace: boolean, -): Player[] { - const idx = players.findIndex( - (p) => p.team === player.team && p.role === player.role, - ) - - return players.toSpliced(idx, 1, ...(replace ? [player] : [])) +): A[] { + const idx = array.findIndex((i) => i.id == it.id) + return array.toSpliced(idx, 1, ...(replace ? [it] : [])) } diff --git a/front/views/editor/CourtAction.tsx b/front/views/editor/CourtAction.tsx index de33224..cef1e86 100644 --- a/front/views/editor/CourtAction.tsx +++ b/front/views/editor/CourtAction.tsx @@ -46,7 +46,7 @@ export function CourtAction({ }} wavy={action.type == ActionKind.DRIBBLE} //TODO place those magic values in constants - endRadius={action.toPlayerId ? 26 : 17} + endRadius={action.toId ? 26 : 17} startRadius={0} onDeleteRequested={onActionDeleted} style={{ diff --git a/front/views/template/Header.tsx b/front/views/template/Header.tsx index 5c8cbcd..8555133 100644 --- a/front/views/template/Header.tsx +++ b/front/views/template/Header.tsx @@ -17,7 +17,7 @@ export function Header({ username }: { username: string }) { location.pathname = BASE + "/" }}> IQ - Ball + CourtObjects
diff --git a/sql/setup-tables.sql b/sql/setup-tables.sql index 0d157d9..a8ce6bd 100644 --- a/sql/setup-tables.sql +++ b/sql/setup-tables.sql @@ -20,7 +20,7 @@ CREATE TABLE Tactic name varchar NOT NULL, creation_date timestamp DEFAULT CURRENT_TIMESTAMP NOT NULL, owner integer NOT NULL, - content varchar DEFAULT '{"players": [], "actions": [], "objects": []}' NOT NULL, + content varchar DEFAULT '{"components": [], "actions": []}' NOT NULL, court_type varchar CHECK ( court_type IN ('HALF', 'PLAIN')) NOT NULL, FOREIGN KEY (owner) REFERENCES Account ); diff --git a/src/App/Controller/EditorController.php b/src/App/Controller/EditorController.php index 4bdcfae..b6ebd6e 100644 --- a/src/App/Controller/EditorController.php +++ b/src/App/Controller/EditorController.php @@ -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": [], "objects": [], "actions": []}', + "content" => '{"components": [], "actions": []}', "courtType" => $courtType->name(), ]); } diff --git a/src/App/Views/home.twig b/src/App/Views/home.twig index 0fc426a..2438ca1 100644 --- a/src/App/Views/home.twig +++ b/src/App/Views/home.twig @@ -52,7 +52,7 @@
-

IQ Ball

+

IQ CourtObjects