From c3cf23da0c29d33d6e149c23d125cc0bb61ddfe8 Mon Sep 17 00:00:00 2001 From: maxime Date: Sat, 24 Feb 2024 14:53:03 +0100 Subject: [PATCH 1/7] add phantoms for screen actions --- src/App.tsx | 2 +- src/components/arrows/BendableArrow.tsx | 4 +- src/components/editor/BasketCourt.tsx | 43 ++++++----- src/components/editor/CourtBall.tsx | 15 ++-- src/components/editor/CourtPlayer.tsx | 32 +++++---- src/editor/ActionsDomains.ts | 95 ++++++++++++++++--------- src/editor/PlayerDomains.ts | 85 +++++++++++++++++----- src/editor/TacticContentDomains.ts | 84 +++++++++++++--------- src/model/tactic/Action.ts | 3 +- src/model/tactic/CourtObjects.ts | 3 +- src/model/tactic/Player.ts | 38 ++++++---- src/model/tactic/Tactic.ts | 11 +-- src/pages/Editor.tsx | 7 +- src/style/steps_tree.css | 87 ---------------------- 14 files changed, 271 insertions(+), 238 deletions(-) delete mode 100644 src/style/steps_tree.css diff --git a/src/App.tsx b/src/App.tsx index c277daa..29ab83b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -17,7 +17,7 @@ const Editor = lazy(() => import("./pages/Editor.tsx")) export default function App() { function suspense(node: ReactNode) { return ( - Loading, please wait...suspense(

}> + Loading, please wait...

}> {node}
) diff --git a/src/components/arrows/BendableArrow.tsx b/src/components/arrows/BendableArrow.tsx index 7a4760b..18aeea3 100644 --- a/src/components/arrows/BendableArrow.tsx +++ b/src/components/arrows/BendableArrow.tsx @@ -613,7 +613,7 @@ function wavyBezier( wavesPer100px: number, amplitude: number, ): string { - function getVerticalAmplification(t: number): Pos { + function getVerticalDerivativeProjectionAmplification(t: number): Pos { const velocity = cubicBeziersDerivative(start, cp1, cp2, end, t) const velocityLength = norm(velocity) //rotate the velocity by 90 deg @@ -641,7 +641,7 @@ function wavyBezier( for (let t = step; t <= 1; ) { const pos = cubicBeziers(start, cp1, cp2, end, t) - const amplification = getVerticalAmplification(t) + const amplification = getVerticalDerivativeProjectionAmplification(t) let nextPos if (phase == 1 || phase == 3) { diff --git a/src/components/editor/BasketCourt.tsx b/src/components/editor/BasketCourt.tsx index 2213525..4cab8cf 100644 --- a/src/components/editor/BasketCourt.tsx +++ b/src/components/editor/BasketCourt.tsx @@ -1,8 +1,8 @@ -import { ReactElement, ReactNode, RefObject } from "react" -import { Action } from "../../model/tactic/Action" +import {ReactElement, ReactNode, RefObject, useLayoutEffect, useState} from "react" +import {Action} from "../../model/tactic/Action" -import { CourtAction } from "./CourtAction.tsx" -import { ComponentId, TacticComponent } from "../../model/tactic/Tactic" +import {CourtAction} from "./CourtAction.tsx" +import {ComponentId, TacticComponent} from "../../model/tactic/Tactic" export interface BasketCourtProps { components: TacticComponent[] @@ -21,24 +21,33 @@ export interface ActionPreview extends Action { } export function BasketCourt({ - components, - previewAction, + components, + previewAction, - renderComponent, - renderActions, + renderComponent, + renderActions, + + courtImage, + courtRef, + }: BasketCourtProps) { + + const [forceEmptyComponents, setForceEmptyComponents] = useState(true) + + useLayoutEffect(() => { + setForceEmptyComponents(false) + }, [setForceEmptyComponents]); + + const usedComponents = forceEmptyComponents ? [] : components - courtImage, - courtRef, -}: BasketCourtProps) { return (
+ style={{position: "relative"}}> {courtImage} - {components.map(renderComponent)} - {components.flatMap(renderActions)} + {usedComponents.map(renderComponent)} + {usedComponents.flatMap(renderActions)} {previewAction && ( {}} - onActionChanges={() => {}} + onActionDeleted={() => { + }} + onActionChanges={() => { + }} /> )}
diff --git a/src/components/editor/CourtBall.tsx b/src/components/editor/CourtBall.tsx index e1ac542..e368598 100644 --- a/src/components/editor/CourtBall.tsx +++ b/src/components/editor/CourtBall.tsx @@ -1,8 +1,8 @@ -import { useRef } from "react" +import {useRef} from "react" import Draggable from "react-draggable" -import { BallPiece } from "./BallPiece" -import { NULL_POS } from "../../geo/Pos" -import { Ball } from "../../model/tactic/CourtObjects" +import {BallPiece} from "./BallPiece" +import {NULL_POS} from "../../geo/Pos" +import {Ball} from "../../model/tactic/CourtObjects" export interface CourtBallProps { onPosValidated: (rect: DOMRect) => void @@ -10,11 +10,10 @@ export interface CourtBallProps { ball: Ball } -export function CourtBall({ onPosValidated, ball, onRemove }: CourtBallProps) { +export function CourtBall({onPosValidated, ball, onRemove}: CourtBallProps) { const pieceRef = useRef(null) - const x = ball.rightRatio - const y = ball.bottomRatio + const {x, y} = ball.pos return ( - + ) diff --git a/src/components/editor/CourtPlayer.tsx b/src/components/editor/CourtPlayer.tsx index 58fbc48..2f8e5cb 100644 --- a/src/components/editor/CourtPlayer.tsx +++ b/src/components/editor/CourtPlayer.tsx @@ -1,9 +1,9 @@ -import React, { ReactNode, RefObject, useCallback, useRef } from "react" +import React, {ReactNode, RefObject, useCallback, useRef} from "react" import "../../style/player.css" import Draggable from "react-draggable" -import { PlayerPiece } from "./PlayerPiece" -import { BallState, PlayerInfo } from "../../model/tactic/Player" -import { NULL_POS, Pos, ratioWithinBase } from "../../geo/Pos" +import {PlayerPiece} from "./PlayerPiece" +import {BallState, PlayerInfo} from "../../model/tactic/Player" +import {NULL_POS, Pos, ratioWithinBase} from "../../geo/Pos" export interface CourtPlayerProps { playerInfo: PlayerInfo @@ -15,21 +15,24 @@ export interface CourtPlayerProps { availableActions: (ro: HTMLElement) => ReactNode[] } +const MOVE_AREA_SENSIBILITY = 0.001 + +export const PLAYER_RADIUS_PIXELS = 20 + /** * A player that is placed on the court, which can be selected, and moved in the associated bounds * */ export default function CourtPlayer({ - playerInfo, - className, + playerInfo, + className, - onPositionValidated, - onRemove, - courtRef, - availableActions, -}: CourtPlayerProps) { + onPositionValidated, + onRemove, + courtRef, + availableActions, + }: CourtPlayerProps) { const usesBall = playerInfo.ballState != BallState.NONE - const x = playerInfo.rightRatio - const y = playerInfo.bottomRatio + const {x, y} = playerInfo.pos const pieceRef = useRef(null) return ( @@ -44,7 +47,8 @@ export default function CourtPlayer({ const pos = ratioWithinBase(pieceBounds, parentBounds) - if (pos.x !== x || pos.y != y) onPositionValidated(pos) + + if (Math.abs(pos.x - x) >= MOVE_AREA_SENSIBILITY || Math.abs(pos.y - y) >= MOVE_AREA_SENSIBILITY) onPositionValidated(pos) }, [courtRef, onPositionValidated, x, y])}>
c.id == pathItem.originPlayerId)! as Player } +//FIXME this function can be a bottleneck if the phantom's position is +// following another phantom and / or the origin of the phantom is another +export function computePhantomPositioning(phantom: PlayerPhantom, + content: TacticContent, + area: DOMRect): Pos { + const positioning = phantom.pos + + // If the position is already known and fixed, return the pos + if (positioning.type === "fixed") + return positioning + + // If the position is to determine (positioning.type = "follows"), determine the phantom's pos + // by calculating it from the referent position, and the action that targets the referent. + + const components = content.components + + // Get the referent from the components + const referent: PlayerLike = getComponent(positioning.attach, components) + const referentPos = referent.type === "player" + ? referent.pos + : computePhantomPositioning(referent, content, area) + + // Get the origin + const origin = getOrigin(phantom, components) + const originPathItems = origin.path!.items + const phantomIdx = originPathItems.indexOf(phantom.id) + + const playerBeforePhantom: PlayerLike = phantomIdx == 0 ? origin : getComponent(originPathItems[phantomIdx - 1], components) + const action = playerBeforePhantom.actions.find(a => a.target === positioning.attach)! + + const segments = action.segments + const lastSegment = segments[segments.length - 1] + const lastSegmentStart = segments[segments.length - 2]?.next + const pivotPoint = lastSegment.controlPoint ?? (lastSegmentStart + ? typeof lastSegmentStart === "string" + ? document.getElementById(lastSegmentStart)!.getBoundingClientRect() + : lastSegmentStart + : playerBeforePhantom.type === "phantom" + ? computePhantomPositioning(playerBeforePhantom, content, area) + : playerBeforePhantom.pos) + + + + const segment = posWithinBase(relativeTo(referentPos, pivotPoint), area) + const segmentLength = norm(segment) + const phantomDistanceFromReferent = PLAYER_RADIUS_PIXELS //TODO Place this in constants + const segmentProjection = minus(area, { + x: (segment.x / segmentLength) * phantomDistanceFromReferent, + y: (segment.y / segmentLength) * phantomDistanceFromReferent + }) + const segmentProjectionRatio: Pos = ratioWithinBase(segmentProjection, area) + + return add(referentPos, segmentProjectionRatio) +} + +export function getComponent(id: string, components: TacticComponent[]): T { + return components.find(c => c.id === id)! as T +} + export function areInSamePath(a: PlayerLike, b: PlayerLike) { if (a.type === "phantom" && b.type === "phantom") { return a.originPlayerId === b.originPlayerId @@ -132,9 +185,9 @@ export function truncatePlayerPath( truncateStartIdx == 0 ? null : { - ...path, - items: path.items.toSpliced(truncateStartIdx), - }, + ...path, + items: path.items.toSpliced(truncateStartIdx), + }, }, content, ) diff --git a/src/editor/TacticContentDomains.ts b/src/editor/TacticContentDomains.ts index 5839bee..a8d02d3 100644 --- a/src/editor/TacticContentDomains.ts +++ b/src/editor/TacticContentDomains.ts @@ -1,39 +1,25 @@ -import { Pos, ratioWithinBase } from "../geo/Pos" -import { - BallState, - Player, - PlayerInfo, - PlayerTeam, -} from "../model/tactic/Player" -import { - Ball, - BALL_ID, - BALL_TYPE, - CourtObject, -} from "../model/tactic/CourtObjects" -import { - ComponentId, - TacticComponent, - TacticContent, -} from "../model/tactic/Tactic" -import { overlaps } from "../geo/Box" -import { RackedCourtObject, RackedPlayer } from "./RackedItems" -import { changePlayerBallState } from "./PlayerDomains" +import {Pos, ratioWithinBase} from "../geo/Pos" +import {BallState, Player, PlayerInfo, PlayerLike, PlayerTeam,} from "../model/tactic/Player" +import {Ball, BALL_ID, BALL_TYPE, CourtObject,} from "../model/tactic/CourtObjects" +import {ComponentId, TacticComponent, TacticContent,} from "../model/tactic/Tactic" +import {overlaps} from "../geo/Box" +import {RackedCourtObject, RackedPlayer} from "./RackedItems" +import {changePlayerBallState, getComponent, getOrigin} from "./PlayerDomains" +import {ActionKind} from "../model/tactic/Action.ts"; export function placePlayerAt( refBounds: DOMRect, courtBounds: DOMRect, element: RackedPlayer, ): Player { - const { x, y } = ratioWithinBase(refBounds, courtBounds) + const pos = ratioWithinBase(refBounds, courtBounds) return { type: "player", id: "player-" + element.key + "-" + element.team, team: element.team, role: element.key, - rightRatio: x, - bottomRatio: y, + pos, ballState: BallState.NONE, path: null, actions: [], @@ -46,7 +32,7 @@ export function placeObjectAt( rackedObject: RackedCourtObject, content: TacticContent, ): TacticContent { - const { x, y } = ratioWithinBase(refBounds, courtBounds) + const pos = ratioWithinBase(refBounds, courtBounds) let courtObject: CourtObject @@ -64,8 +50,7 @@ export function placeObjectAt( courtObject = { type: BALL_TYPE, id: BALL_ID, - rightRatio: x, - bottomRatio: y, + pos, actions: [], } break @@ -134,13 +119,12 @@ export function placeBallAt( const ballIdx = content.components.findIndex((o) => o.type == "ball") - const { x, y } = ratioWithinBase(refBounds, courtBounds) + const pos = ratioWithinBase(refBounds, courtBounds) const ball: Ball = { type: BALL_TYPE, id: BALL_ID, - rightRatio: x, - bottomRatio: y, + pos, actions: [], } @@ -174,14 +158,44 @@ export function moveComponent( if (!overlaps(playerBounds, courtBounds)) { return removed(content) } - return updateComponent( - { + + const isPhantom = component.type === "phantom" + + if (isPhantom && component.pos.type === "follows") { + const referent = component.pos.attach + const origin = getOrigin(component, content.components) + const originPathItems = origin.path!.items + const phantomIdx = originPathItems.indexOf(component.id) + + const playerBeforePhantom: PlayerLike = phantomIdx == 0 ? origin : getComponent(originPathItems[phantomIdx - 1], content.components) + // detach the action from the screen target and transform it to a regular move action to the phantom. + content = updateComponent({ + ...playerBeforePhantom, + actions: playerBeforePhantom.actions.map(a => a.target === referent ? { + ...a, + segments: a.segments.toSpliced(a.segments.length - 2, 1, { + ...a.segments[a.segments.length - 1], + next: component.id, + }), + target: component.id, + type: ActionKind.MOVE + } : a), + }, content) + } + + content = updateComponent( + { ...component, - rightRatio: newPos.x, - bottomRatio: newPos.y, + pos: isPhantom + ? { + type: "fixed", + ...newPos + } + : newPos }, content, ) + return content } export function removeComponent( @@ -239,5 +253,5 @@ export function getRackPlayers( c.type == "player" && c.team == team && c.role == role, ) == -1, ) - .map((key) => ({ team, key })) + .map((key) => ({team, key})) } diff --git a/src/model/tactic/Action.ts b/src/model/tactic/Action.ts index c97cdd4..b2dca4f 100644 --- a/src/model/tactic/Action.ts +++ b/src/model/tactic/Action.ts @@ -9,9 +9,10 @@ export enum ActionKind { SHOOT = "SHOOT", } -export type Action = { type: ActionKind } & MovementAction +export type Action = MovementAction export interface MovementAction { + type: ActionKind target: ComponentId | Pos segments: Segment[] } diff --git a/src/model/tactic/CourtObjects.ts b/src/model/tactic/CourtObjects.ts index 96cde26..5f72199 100644 --- a/src/model/tactic/CourtObjects.ts +++ b/src/model/tactic/CourtObjects.ts @@ -1,4 +1,5 @@ import { Component } from "./Tactic" +import { Pos } from "../../geo/Pos.ts" export const BALL_ID = "ball" export const BALL_TYPE = "ball" @@ -6,4 +7,4 @@ export const BALL_TYPE = "ball" //place here all different kinds of objects export type CourtObject = Ball -export type Ball = Component +export type Ball = Component diff --git a/src/model/tactic/Player.ts b/src/model/tactic/Player.ts index a257103..ab4d116 100644 --- a/src/model/tactic/Player.ts +++ b/src/model/tactic/Player.ts @@ -1,4 +1,5 @@ import { Component, ComponentId } from "./Tactic" +import { Pos } from "../../geo/Pos.ts" export type PlayerId = string @@ -9,7 +10,7 @@ export enum PlayerTeam { Opponents = "opponents", } -export interface Player extends PlayerInfo, Component<"player"> { +export interface Player extends PlayerInfo, Component<"player", Pos> { readonly id: PlayerId } @@ -33,15 +34,7 @@ export interface PlayerInfo { */ readonly ballState: BallState - /** - * 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 + readonly pos: Pos, } export enum BallState { @@ -52,7 +45,7 @@ export enum BallState { PASSED_ORIGIN, } -export interface Player extends Component<"player">, PlayerInfo { +export interface Player extends Component<"player", Pos>, PlayerInfo { /** * True if the player has a basketball */ @@ -65,11 +58,32 @@ export interface MovementPath { readonly items: ComponentId[] } +/** + * The position of the phantom is known and fixed + */ +export type FixedPhantomPositioning = ({ type: "fixed" } & Pos) +/** + * The position of the phantom is constrained to a given component. + * The actual position of the phantom is to determine given its environment. + */ +export type FollowsPhantomPositioning = { type: "follows", attach: ComponentId } + +/** + * Defines the different kind of positioning a phantom can have + */ +export type PhantomPositioning = FixedPhantomPositioning | FollowsPhantomPositioning + + /** * A player phantom is a kind of component that represents the future state of a player * according to the court's step information */ -export interface PlayerPhantom extends Component<"phantom"> { +export interface PlayerPhantom extends Component<"phantom", PhantomPositioning> { readonly originPlayerId: ComponentId readonly ballState: BallState + + /** + * Defines a component this phantom will be attached to. + */ + readonly attachedTo?: ComponentId } diff --git a/src/model/tactic/Tactic.ts b/src/model/tactic/Tactic.ts index acce6f0..b22473f 100644 --- a/src/model/tactic/Tactic.ts +++ b/src/model/tactic/Tactic.ts @@ -18,7 +18,7 @@ export interface TacticContent { export type TacticComponent = Player | CourtObject | PlayerPhantom export type ComponentId = string -export interface Component { +export interface Component { /** * The component's type */ @@ -27,15 +27,8 @@ export interface Component { * The component's identifier */ readonly id: ComponentId - /** - * Percentage of the component's position to the bottom (0 means top, 1 means bottom, 0.5 means middle) - */ - readonly bottomRatio: number - /** - * Percentage of the component's position to the right (0 means left, 1 means right, 0.5 means middle) - */ - readonly rightRatio: number + readonly pos: Positioning, readonly actions: Action[] } diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 7f2b797..f8d6d97 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -67,7 +67,7 @@ import { middlePos, Pos, ratioWithinBase } from "../geo/Pos" import { Action, ActionKind } from "../model/tactic/Action" import BallAction from "../components/actions/BallAction" import { - changePlayerBallState, + changePlayerBallState, computePhantomPositioning, getOrigin, removePlayer, } from "../editor/PlayerDomains" @@ -329,7 +329,7 @@ function EditorView({ content, (content) => { - if (player.type == "player") insertRackedPlayer(player) + if (player.type === "player") insertRackedPlayer(player) return removePlayer(player, content) }, ), @@ -402,8 +402,7 @@ function EditorView({ id: component.id, team: origin.team, role: origin.role, - bottomRatio: component.bottomRatio, - rightRatio: component.rightRatio, + pos: computePhantomPositioning(component, content, courtBounds()), ballState: component.ballState, } } else { diff --git a/src/style/steps_tree.css b/src/style/steps_tree.css deleted file mode 100644 index eadeaf6..0000000 --- a/src/style/steps_tree.css +++ /dev/null @@ -1,87 +0,0 @@ -.step-piece { - position: relative; - font-family: monospace; - pointer-events: all; - - background-color: var(--editor-tree-step-piece); - color: var(--selected-team-secondarycolor); - - border-radius: 100px; - - width: 20px; - height: 20px; - - display: flex; - - align-items: center; - justify-content: center; - - user-select: none; - cursor: pointer; - - border: 2px solid var(--editor-tree-background); -} - -.step-piece-selected { - border: 2px solid var(--selection-color-light); -} - -.step-piece-selected, -.step-piece:focus, -.step-piece:hover { - background-color: var(--editor-tree-step-piece-hovered); -} - -.step-piece-actions { - display: none; - position: absolute; - column-gap: 5px; - top: -140%; -} - -.step-piece-selected .step-piece-actions { - display: flex; -} - -.add-icon, -.remove-icon { - background-color: white; - border-radius: 100%; -} - -.add-icon { - fill: var(--add-icon-fill); -} - -.remove-icon { - fill: var(--remove-icon-fill); -} - -.step-children { - margin-top: 10vh; - display: flex; - flex-direction: row; - width: 100%; - height: 100%; -} - -.step-group { - position: relative; - - display: flex; - flex-direction: column; - align-items: center; - - width: 100%; - height: 100%; -} - -.steps-tree { - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - padding-top: 10%; - - height: 100%; -} -- 2.36.3 From 8b407b67eb71068682b0e2fa8c9b3a7fd9754dbf Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Sun, 25 Feb 2024 23:22:29 +0100 Subject: [PATCH 2/7] fix crash when removing a player that had phantoms attached to it --- src/editor/PlayerDomains.ts | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/editor/PlayerDomains.ts b/src/editor/PlayerDomains.ts index 7c2d23c..623d0ec 100644 --- a/src/editor/PlayerDomains.ts +++ b/src/editor/PlayerDomains.ts @@ -1,5 +1,5 @@ import {BallState, Player, PlayerLike, PlayerPhantom,} from "../model/tactic/Player" -import {TacticComponent, TacticContent} from "../model/tactic/Tactic" +import {ComponentId, TacticComponent, TacticContent} from "../model/tactic/Tactic" import {removeComponent, updateComponent} from "./TacticContentDomains" import {removeAllActionsTargeting, spreadNewStateFromOriginStateChange,} from "./ActionsDomains" import {ActionKind} from "../model/tactic/Action" @@ -56,7 +56,6 @@ export function computePhantomPositioning(phantom: PlayerPhantom, : playerBeforePhantom.pos) - const segment = posWithinBase(relativeTo(referentPos, pivotPoint), area) const segmentLength = norm(segment) const phantomDistanceFromReferent = PLAYER_RADIUS_PIXELS //TODO Place this in constants @@ -107,7 +106,7 @@ export function isNextInPath( ) } -export function removePlayerPath( +export function clearPlayerPath( player: Player, content: TacticContent, ): TacticContent { @@ -128,18 +127,35 @@ export function removePlayerPath( ) } +function removeAllPhantomsAttached(to: ComponentId, content: TacticContent): TacticContent { + let i = 0 + while (i < content.components.length) { + const component = content.components[i] + if (component.type === "phantom") { + + if (component.pos.type === "follows" && component.pos.attach === to) { + content = removePlayer(component, content) + continue + } + } + i++ + } + return content +} + export function removePlayer( player: PlayerLike, content: TacticContent, ): TacticContent { content = removeAllActionsTargeting(player.id, content) + content = removeAllPhantomsAttached(player.id, content) - if (player.type == "phantom") { + if (player.type === "phantom") { const origin = getOrigin(player, content.components) return truncatePlayerPath(origin, player, content) } - content = removePlayerPath(player, content) + content = clearPlayerPath(player, content) content = removeComponent(player.id, content) for (const action of player.actions) { -- 2.36.3 From b6f2a97d8f6d50b43e5a8e1d1726202f1d656615 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 26 Feb 2024 00:11:31 +0100 Subject: [PATCH 3/7] fix crash when removing a player that was attached, or a screen action --- src/editor/ActionsDomains.ts | 14 +++++--------- src/editor/PlayerDomains.ts | 28 ++++++++++++++++++++++++++++ src/pages/Editor.tsx | 4 ++-- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/editor/ActionsDomains.ts b/src/editor/ActionsDomains.ts index 08ddfbb..d0d42b6 100644 --- a/src/editor/ActionsDomains.ts +++ b/src/editor/ActionsDomains.ts @@ -7,8 +7,8 @@ import {removeBall, updateComponent} from "./TacticContentDomains" import { areInSamePath, changePlayerBallState, - getComponent, getOrigin, + getPlayerNextTo, isNextInPath, removePlayer } from "./PlayerDomains" @@ -379,10 +379,10 @@ export function removeAllActionsTargeting( export function removeAction( origin: TacticComponent, - action: Action, actionIdx: number, content: TacticContent, ): TacticContent { + const action = origin.actions[actionIdx] origin = { ...origin, actions: origin.actions.toSpliced(actionIdx, 1), @@ -431,12 +431,8 @@ export function removeAction( // if the action type is a screen over a player, remove the phantom bound to the target if (action.type === ActionKind.SCREEN && (origin.type === "phantom" || origin.type === "player")) { - const playerOrigin = origin.type === "phantom" ? getOrigin(origin, content.components) : origin - const pathItems = playerOrigin.path?.items! - const originIdx = pathItems.indexOf(origin.id) - // remove the screen phantom - const phantomId = pathItems.at(originIdx + 1)! - content = removePlayer(getComponent(phantomId, content.components), content) + const screenPhantom = getPlayerNextTo(origin, 1, content.components)! + content = removePlayer(screenPhantom, content) } return content @@ -506,7 +502,7 @@ export function spreadNewStateFromOriginStateChange( } if (deleteAction) { - content = removeAction(origin, action, i, content) + content = removeAction(origin, i, content) origin = content.components.find((c) => c.id === origin.id)! as | Player | PlayerPhantom diff --git a/src/editor/PlayerDomains.ts b/src/editor/PlayerDomains.ts index 623d0ec..add77a1 100644 --- a/src/editor/PlayerDomains.ts +++ b/src/editor/PlayerDomains.ts @@ -14,6 +14,21 @@ export function getOrigin( return components.find((c) => c.id == pathItem.originPlayerId)! as Player } +export function getPlayerNextTo(player: PlayerLike, n: number, components: TacticComponent[]): PlayerLike | undefined { + const playerOrigin = player.type === "player" ? player : getOrigin(player, components) + const pathItems = playerOrigin.path?.items! + + // add one as there is a shifting because a Player is never at the head of its own path + const idx = pathItems.indexOf(player.id) + 1 // is 0 if the player is the origin + + const targetIdx = idx + n + + // remove the screen phantom + const result = targetIdx == 0 ? playerOrigin : getComponent(pathItems[targetIdx - 1], components) + console.log(result, targetIdx, idx, n) + return result +} + //FIXME this function can be a bottleneck if the phantom's position is // following another phantom and / or the origin of the phantom is another export function computePhantomPositioning(phantom: PlayerPhantom, @@ -151,6 +166,19 @@ export function removePlayer( content = removeAllPhantomsAttached(player.id, content) if (player.type === "phantom") { + + const pos = player.pos + // if the phantom was attached to another player, remove the action that symbolizes the attachment + if (pos.type === "follows") { + const playerBefore = getPlayerNextTo(player, -1, content.components)! + const actionIdx = playerBefore.actions.findIndex(a => a.target === pos.attach) + console.log(actionIdx, playerBefore) + content = updateComponent({ + ...playerBefore, + actions: playerBefore.actions.toSpliced(actionIdx, 1) + }, content) + } + const origin = getOrigin(player, content.components) return truncatePlayerPath(origin, player, content) } diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index f8d6d97..776623a 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -434,8 +434,8 @@ function EditorView({ ) const doDeleteAction = useCallback( - (action: Action, idx: number, origin: TacticComponent) => { - setContent((content) => removeAction(origin, action, idx, content)) + (_: Action, idx: number, origin: TacticComponent) => { + setContent((content) => removeAction(origin, idx, content)) }, [setContent], ) -- 2.36.3 From 5963290f676d96675966434a934f920a72b76b77 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 26 Feb 2024 16:13:00 +0100 Subject: [PATCH 4/7] fix bug when a player got the ball while doing a screen to another player --- src/editor/ActionsDomains.ts | 20 ++++++++++---------- src/editor/PlayerDomains.ts | 6 +++--- src/pages/Editor.tsx | 3 ++- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/editor/ActionsDomains.ts b/src/editor/ActionsDomains.ts index d0d42b6..0530e68 100644 --- a/src/editor/ActionsDomains.ts +++ b/src/editor/ActionsDomains.ts @@ -7,6 +7,7 @@ import {removeBall, updateComponent} from "./TacticContentDomains" import { areInSamePath, changePlayerBallState, + getComponent, getOrigin, getPlayerNextTo, isNextInPath, @@ -165,11 +166,9 @@ function alreadyHasAnAnteriorActionWith( const targetIdx = targetOriginPath.indexOf(target.id) for (let i = targetIdx; i < targetOriginPath.length; i++) { - const phantom = components.find( - (c) => c.id === targetOriginPath[i], - )! as PlayerLike + const component = getComponent(targetOriginPath[i], components) if ( - phantom.actions.find( + component.actions.find( (a) => typeof a.target === "string" && moves(a.type) && @@ -467,9 +466,7 @@ export function spreadNewStateFromOriginStateChange( continue } - const actionTarget = content.components.find( - (c) => action.target === c.id, - )! as PlayerLike + const actionTarget: PlayerLike = getComponent(action.target, content.components) let targetState: BallState = actionTarget.ballState let deleteAction = false @@ -499,13 +496,16 @@ export function spreadNewStateFromOriginStateChange( action.type === ActionKind.SCREEN ) { targetState = BallState.HOLDS_BY_PASS + const screenPhantom = getPlayerNextTo(origin, 1, content.components)! + if (screenPhantom.type === "phantom" && screenPhantom.pos.type === "follows") { + content = removePlayer(screenPhantom, content) + origin = getComponent(origin.id, content.components) + } } if (deleteAction) { content = removeAction(origin, i, content) - origin = content.components.find((c) => c.id === origin.id)! as - | Player - | PlayerPhantom + origin = getComponent(origin.id, content.components) i-- // step back } else { // do not change the action type if it is a shoot action diff --git a/src/editor/PlayerDomains.ts b/src/editor/PlayerDomains.ts index add77a1..03aa898 100644 --- a/src/editor/PlayerDomains.ts +++ b/src/editor/PlayerDomains.ts @@ -25,7 +25,6 @@ export function getPlayerNextTo(player: PlayerLike, n: number, components: Tacti // remove the screen phantom const result = targetIdx == 0 ? playerOrigin : getComponent(pathItems[targetIdx - 1], components) - console.log(result, targetIdx, idx, n) return result } @@ -172,7 +171,6 @@ export function removePlayer( if (pos.type === "follows") { const playerBefore = getPlayerNextTo(player, -1, content.components)! const actionIdx = playerBefore.actions.findIndex(a => a.target === pos.attach) - console.log(actionIdx, playerBefore) content = updateComponent({ ...playerBefore, actions: playerBefore.actions.toSpliced(actionIdx, 1) @@ -180,7 +178,9 @@ export function removePlayer( } const origin = getOrigin(player, content.components) - return truncatePlayerPath(origin, player, content) + content = truncatePlayerPath(origin, player, content) + console.log(content) + return content } content = clearPlayerPath(player, content) diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index 776623a..cc51abb 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -67,7 +67,8 @@ import { middlePos, Pos, ratioWithinBase } from "../geo/Pos" import { Action, ActionKind } from "../model/tactic/Action" import BallAction from "../components/actions/BallAction" import { - changePlayerBallState, computePhantomPositioning, + changePlayerBallState, + computePhantomPositioning, getOrigin, removePlayer, } from "../editor/PlayerDomains" -- 2.36.3 From 5359bb12de3e60b595c693d0e3b311aff5f90689 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Mon, 26 Feb 2024 16:14:07 +0100 Subject: [PATCH 5/7] format --- src/components/editor/BasketCourt.tsx | 39 +++---- src/components/editor/CourtBall.tsx | 14 +-- src/components/editor/CourtPlayer.tsx | 31 +++--- src/editor/ActionsDomains.ts | 89 ++++++++++------ src/editor/PlayerDomains.ts | 142 ++++++++++++++++++-------- src/editor/TacticContentDomains.ts | 84 ++++++++++----- src/model/tactic/Player.ts | 14 +-- src/model/tactic/Tactic.ts | 2 +- src/pages/Editor.tsx | 6 +- src/pages/HomePage.tsx | 48 +++++---- 10 files changed, 295 insertions(+), 174 deletions(-) diff --git a/src/components/editor/BasketCourt.tsx b/src/components/editor/BasketCourt.tsx index 4cab8cf..25b53b5 100644 --- a/src/components/editor/BasketCourt.tsx +++ b/src/components/editor/BasketCourt.tsx @@ -1,8 +1,14 @@ -import {ReactElement, ReactNode, RefObject, useLayoutEffect, useState} from "react" -import {Action} from "../../model/tactic/Action" +import { + ReactElement, + ReactNode, + RefObject, + useLayoutEffect, + useState, +} from "react" +import { Action } from "../../model/tactic/Action" -import {CourtAction} from "./CourtAction.tsx" -import {ComponentId, TacticComponent} from "../../model/tactic/Tactic" +import { CourtAction } from "./CourtAction.tsx" +import { ComponentId, TacticComponent } from "../../model/tactic/Tactic" export interface BasketCourtProps { components: TacticComponent[] @@ -21,21 +27,20 @@ export interface ActionPreview extends Action { } export function BasketCourt({ - components, - previewAction, + components, + previewAction, - renderComponent, - renderActions, - - courtImage, - courtRef, - }: BasketCourtProps) { + renderComponent, + renderActions, + courtImage, + courtRef, +}: BasketCourtProps) { const [forceEmptyComponents, setForceEmptyComponents] = useState(true) useLayoutEffect(() => { setForceEmptyComponents(false) - }, [setForceEmptyComponents]); + }, [setForceEmptyComponents]) const usedComponents = forceEmptyComponents ? [] : components @@ -43,7 +48,7 @@ export function BasketCourt({
+ style={{ position: "relative" }}> {courtImage} {usedComponents.map(renderComponent)} @@ -56,10 +61,8 @@ export function BasketCourt({ origin={previewAction.origin} isInvalid={previewAction.isInvalid} //do nothing on interacted, not really possible as it's a preview arrow - onActionDeleted={() => { - }} - onActionChanges={() => { - }} + onActionDeleted={() => {}} + onActionChanges={() => {}} /> )}
diff --git a/src/components/editor/CourtBall.tsx b/src/components/editor/CourtBall.tsx index e368598..c9c48dc 100644 --- a/src/components/editor/CourtBall.tsx +++ b/src/components/editor/CourtBall.tsx @@ -1,8 +1,8 @@ -import {useRef} from "react" +import { useRef } from "react" import Draggable from "react-draggable" -import {BallPiece} from "./BallPiece" -import {NULL_POS} from "../../geo/Pos" -import {Ball} from "../../model/tactic/CourtObjects" +import { BallPiece } from "./BallPiece" +import { NULL_POS } from "../../geo/Pos" +import { Ball } from "../../model/tactic/CourtObjects" export interface CourtBallProps { onPosValidated: (rect: DOMRect) => void @@ -10,10 +10,10 @@ export interface CourtBallProps { ball: Ball } -export function CourtBall({onPosValidated, ball, onRemove}: CourtBallProps) { +export function CourtBall({ onPosValidated, ball, onRemove }: CourtBallProps) { const pieceRef = useRef(null) - const {x, y} = ball.pos + const { x, y } = ball.pos return ( - +
) diff --git a/src/components/editor/CourtPlayer.tsx b/src/components/editor/CourtPlayer.tsx index 2f8e5cb..6b8e8dd 100644 --- a/src/components/editor/CourtPlayer.tsx +++ b/src/components/editor/CourtPlayer.tsx @@ -1,9 +1,9 @@ -import React, {ReactNode, RefObject, useCallback, useRef} from "react" +import React, { ReactNode, RefObject, useCallback, useRef } from "react" import "../../style/player.css" import Draggable from "react-draggable" -import {PlayerPiece} from "./PlayerPiece" -import {BallState, PlayerInfo} from "../../model/tactic/Player" -import {NULL_POS, Pos, ratioWithinBase} from "../../geo/Pos" +import { PlayerPiece } from "./PlayerPiece" +import { BallState, PlayerInfo } from "../../model/tactic/Player" +import { NULL_POS, Pos, ratioWithinBase } from "../../geo/Pos" export interface CourtPlayerProps { playerInfo: PlayerInfo @@ -23,16 +23,16 @@ export const PLAYER_RADIUS_PIXELS = 20 * A player that is placed on the court, which can be selected, and moved in the associated bounds * */ export default function CourtPlayer({ - playerInfo, - className, + playerInfo, + className, - onPositionValidated, - onRemove, - courtRef, - availableActions, - }: CourtPlayerProps) { + onPositionValidated, + onRemove, + courtRef, + availableActions, +}: CourtPlayerProps) { const usesBall = playerInfo.ballState != BallState.NONE - const {x, y} = playerInfo.pos + const { x, y } = playerInfo.pos const pieceRef = useRef(null) return ( @@ -47,8 +47,11 @@ export default function CourtPlayer({ const pos = ratioWithinBase(pieceBounds, parentBounds) - - if (Math.abs(pos.x - x) >= MOVE_AREA_SENSIBILITY || Math.abs(pos.y - y) >= MOVE_AREA_SENSIBILITY) onPositionValidated(pos) + if ( + Math.abs(pos.x - x) >= MOVE_AREA_SENSIBILITY || + Math.abs(pos.y - y) >= MOVE_AREA_SENSIBILITY + ) + onPositionValidated(pos) }, [courtRef, onPositionValidated, x, y])}>
c.id == pathItem.originPlayerId)! as Player } -export function getPlayerNextTo(player: PlayerLike, n: number, components: TacticComponent[]): PlayerLike | undefined { - const playerOrigin = player.type === "player" ? player : getOrigin(player, components) +export function getPlayerNextTo( + player: PlayerLike, + n: number, + components: TacticComponent[], +): PlayerLike | undefined { + const playerOrigin = + player.type === "player" ? player : getOrigin(player, components) const pathItems = playerOrigin.path?.items! // add one as there is a shifting because a Player is never at the head of its own path @@ -24,20 +49,24 @@ export function getPlayerNextTo(player: PlayerLike, n: number, components: Tacti const targetIdx = idx + n // remove the screen phantom - const result = targetIdx == 0 ? playerOrigin : getComponent(pathItems[targetIdx - 1], components) + const result = + targetIdx == 0 + ? playerOrigin + : getComponent(pathItems[targetIdx - 1], components) return result } //FIXME this function can be a bottleneck if the phantom's position is // following another phantom and / or the origin of the phantom is another -export function computePhantomPositioning(phantom: PlayerPhantom, - content: TacticContent, - area: DOMRect): Pos { +export function computePhantomPositioning( + phantom: PlayerPhantom, + content: TacticContent, + area: DOMRect, +): Pos { const positioning = phantom.pos // If the position is already known and fixed, return the pos - if (positioning.type === "fixed") - return positioning + if (positioning.type === "fixed") return positioning // If the position is to determine (positioning.type = "follows"), determine the phantom's pos // by calculating it from the referent position, and the action that targets the referent. @@ -46,44 +75,56 @@ export function computePhantomPositioning(phantom: PlayerPhantom, // Get the referent from the components const referent: PlayerLike = getComponent(positioning.attach, components) - const referentPos = referent.type === "player" - ? referent.pos - : computePhantomPositioning(referent, content, area) + const referentPos = + referent.type === "player" + ? referent.pos + : computePhantomPositioning(referent, content, area) // Get the origin const origin = getOrigin(phantom, components) const originPathItems = origin.path!.items const phantomIdx = originPathItems.indexOf(phantom.id) - const playerBeforePhantom: PlayerLike = phantomIdx == 0 ? origin : getComponent(originPathItems[phantomIdx - 1], components) - const action = playerBeforePhantom.actions.find(a => a.target === positioning.attach)! + const playerBeforePhantom: PlayerLike = + phantomIdx == 0 + ? origin + : getComponent(originPathItems[phantomIdx - 1], components) + const action = playerBeforePhantom.actions.find( + (a) => a.target === positioning.attach, + )! const segments = action.segments const lastSegment = segments[segments.length - 1] const lastSegmentStart = segments[segments.length - 2]?.next - const pivotPoint = lastSegment.controlPoint ?? (lastSegmentStart - ? typeof lastSegmentStart === "string" - ? document.getElementById(lastSegmentStart)!.getBoundingClientRect() - : lastSegmentStart - : playerBeforePhantom.type === "phantom" - ? computePhantomPositioning(playerBeforePhantom, content, area) - : playerBeforePhantom.pos) - + const pivotPoint = + lastSegment.controlPoint ?? + (lastSegmentStart + ? typeof lastSegmentStart === "string" + ? document + .getElementById(lastSegmentStart)! + .getBoundingClientRect() + : lastSegmentStart + : playerBeforePhantom.type === "phantom" + ? computePhantomPositioning(playerBeforePhantom, content, area) + : playerBeforePhantom.pos) const segment = posWithinBase(relativeTo(referentPos, pivotPoint), area) const segmentLength = norm(segment) const phantomDistanceFromReferent = PLAYER_RADIUS_PIXELS //TODO Place this in constants const segmentProjection = minus(area, { x: (segment.x / segmentLength) * phantomDistanceFromReferent, - y: (segment.y / segmentLength) * phantomDistanceFromReferent + y: (segment.y / segmentLength) * phantomDistanceFromReferent, }) const segmentProjectionRatio: Pos = ratioWithinBase(segmentProjection, area) return add(referentPos, segmentProjectionRatio) } -export function getComponent(id: string, components: TacticComponent[]): T { - return components.find(c => c.id === id)! as T +export function getComponent( + id: string, + components: TacticComponent[], +): T { + return components.find((c) => c.id === id)! as T } export function areInSamePath(a: PlayerLike, b: PlayerLike) { @@ -141,13 +182,18 @@ export function clearPlayerPath( ) } -function removeAllPhantomsAttached(to: ComponentId, content: TacticContent): TacticContent { +function removeAllPhantomsAttached( + to: ComponentId, + content: TacticContent, +): TacticContent { let i = 0 while (i < content.components.length) { const component = content.components[i] if (component.type === "phantom") { - - if (component.pos.type === "follows" && component.pos.attach === to) { + if ( + component.pos.type === "follows" && + component.pos.attach === to + ) { content = removePlayer(component, content) continue } @@ -165,16 +211,24 @@ export function removePlayer( content = removeAllPhantomsAttached(player.id, content) if (player.type === "phantom") { - const pos = player.pos // if the phantom was attached to another player, remove the action that symbolizes the attachment if (pos.type === "follows") { - const playerBefore = getPlayerNextTo(player, -1, content.components)! - const actionIdx = playerBefore.actions.findIndex(a => a.target === pos.attach) - content = updateComponent({ - ...playerBefore, - actions: playerBefore.actions.toSpliced(actionIdx, 1) - }, content) + const playerBefore = getPlayerNextTo( + player, + -1, + content.components, + )! + const actionIdx = playerBefore.actions.findIndex( + (a) => a.target === pos.attach, + ) + content = updateComponent( + { + ...playerBefore, + actions: playerBefore.actions.toSpliced(actionIdx, 1), + }, + content, + ) } const origin = getOrigin(player, content.components) @@ -229,9 +283,9 @@ export function truncatePlayerPath( truncateStartIdx == 0 ? null : { - ...path, - items: path.items.toSpliced(truncateStartIdx), - }, + ...path, + items: path.items.toSpliced(truncateStartIdx), + }, }, content, ) diff --git a/src/editor/TacticContentDomains.ts b/src/editor/TacticContentDomains.ts index a8d02d3..1f3a9cc 100644 --- a/src/editor/TacticContentDomains.ts +++ b/src/editor/TacticContentDomains.ts @@ -1,11 +1,26 @@ -import {Pos, ratioWithinBase} from "../geo/Pos" -import {BallState, Player, PlayerInfo, PlayerLike, PlayerTeam,} from "../model/tactic/Player" -import {Ball, BALL_ID, BALL_TYPE, CourtObject,} from "../model/tactic/CourtObjects" -import {ComponentId, TacticComponent, TacticContent,} from "../model/tactic/Tactic" -import {overlaps} from "../geo/Box" -import {RackedCourtObject, RackedPlayer} from "./RackedItems" -import {changePlayerBallState, getComponent, getOrigin} from "./PlayerDomains" -import {ActionKind} from "../model/tactic/Action.ts"; +import { Pos, ratioWithinBase } from "../geo/Pos" +import { + BallState, + Player, + PlayerInfo, + PlayerLike, + PlayerTeam, +} from "../model/tactic/Player" +import { + Ball, + BALL_ID, + BALL_TYPE, + CourtObject, +} from "../model/tactic/CourtObjects" +import { + ComponentId, + TacticComponent, + TacticContent, +} from "../model/tactic/Tactic" +import { overlaps } from "../geo/Box" +import { RackedCourtObject, RackedPlayer } from "./RackedItems" +import { changePlayerBallState, getComponent, getOrigin } from "./PlayerDomains" +import { ActionKind } from "../model/tactic/Action.ts" export function placePlayerAt( refBounds: DOMRect, @@ -167,20 +182,37 @@ export function moveComponent( const originPathItems = origin.path!.items const phantomIdx = originPathItems.indexOf(component.id) - const playerBeforePhantom: PlayerLike = phantomIdx == 0 ? origin : getComponent(originPathItems[phantomIdx - 1], content.components) + const playerBeforePhantom: PlayerLike = + phantomIdx == 0 + ? origin + : getComponent( + originPathItems[phantomIdx - 1], + content.components, + ) // detach the action from the screen target and transform it to a regular move action to the phantom. - content = updateComponent({ - ...playerBeforePhantom, - actions: playerBeforePhantom.actions.map(a => a.target === referent ? { - ...a, - segments: a.segments.toSpliced(a.segments.length - 2, 1, { - ...a.segments[a.segments.length - 1], - next: component.id, - }), - target: component.id, - type: ActionKind.MOVE - } : a), - }, content) + content = updateComponent( + { + ...playerBeforePhantom, + actions: playerBeforePhantom.actions.map((a) => + a.target === referent + ? { + ...a, + segments: a.segments.toSpliced( + a.segments.length - 2, + 1, + { + ...a.segments[a.segments.length - 1], + next: component.id, + }, + ), + target: component.id, + type: ActionKind.MOVE, + } + : a, + ), + }, + content, + ) } content = updateComponent( @@ -188,10 +220,10 @@ export function moveComponent( ...component, pos: isPhantom ? { - type: "fixed", - ...newPos - } - : newPos + type: "fixed", + ...newPos, + } + : newPos, }, content, ) @@ -253,5 +285,5 @@ export function getRackPlayers( c.type == "player" && c.team == team && c.role == role, ) == -1, ) - .map((key) => ({team, key})) + .map((key) => ({ team, key })) } diff --git a/src/model/tactic/Player.ts b/src/model/tactic/Player.ts index ab4d116..a22eaee 100644 --- a/src/model/tactic/Player.ts +++ b/src/model/tactic/Player.ts @@ -34,7 +34,7 @@ export interface PlayerInfo { */ readonly ballState: BallState - readonly pos: Pos, + readonly pos: Pos } export enum BallState { @@ -61,24 +61,26 @@ export interface MovementPath { /** * The position of the phantom is known and fixed */ -export type FixedPhantomPositioning = ({ type: "fixed" } & Pos) +export type FixedPhantomPositioning = { type: "fixed" } & Pos /** * The position of the phantom is constrained to a given component. * The actual position of the phantom is to determine given its environment. */ -export type FollowsPhantomPositioning = { type: "follows", attach: ComponentId } +export type FollowsPhantomPositioning = { type: "follows"; attach: ComponentId } /** * Defines the different kind of positioning a phantom can have */ -export type PhantomPositioning = FixedPhantomPositioning | FollowsPhantomPositioning - +export type PhantomPositioning = + | FixedPhantomPositioning + | FollowsPhantomPositioning /** * A player phantom is a kind of component that represents the future state of a player * according to the court's step information */ -export interface PlayerPhantom extends Component<"phantom", PhantomPositioning> { +export interface PlayerPhantom + extends Component<"phantom", PhantomPositioning> { readonly originPlayerId: ComponentId readonly ballState: BallState diff --git a/src/model/tactic/Tactic.ts b/src/model/tactic/Tactic.ts index b22473f..0ad312c 100644 --- a/src/model/tactic/Tactic.ts +++ b/src/model/tactic/Tactic.ts @@ -28,7 +28,7 @@ export interface Component { */ readonly id: ComponentId - readonly pos: Positioning, + readonly pos: Positioning readonly actions: Action[] } diff --git a/src/pages/Editor.tsx b/src/pages/Editor.tsx index cc51abb..83c4dac 100644 --- a/src/pages/Editor.tsx +++ b/src/pages/Editor.tsx @@ -403,7 +403,11 @@ function EditorView({ id: component.id, team: origin.team, role: origin.role, - pos: computePhantomPositioning(component, content, courtBounds()), + pos: computePhantomPositioning( + component, + content, + courtBounds(), + ), ballState: component.ballState, } } else { diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 2a6bb64..faba0d7 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -57,10 +57,10 @@ export default function HomePage() { } function Home({ - lastTactics, - allTactics, - teams, - }: { + lastTactics, + allTactics, + teams, +}: { lastTactics: Tactic[] allTactics: Tactic[] teams: Team[] @@ -77,10 +77,10 @@ function Home({ } function Body({ - lastTactics, - allTactics, - teams, - }: { + lastTactics, + allTactics, + teams, +}: { lastTactics: Tactic[] allTactics: Tactic[] teams: Team[] @@ -100,10 +100,10 @@ function Body({ } function SideMenu({ - width, - lastTactics, - teams, - }: { + width, + lastTactics, + teams, +}: { width: number lastTactics: Tactic[] teams: Team[] @@ -123,9 +123,9 @@ function SideMenu({ } function PersonalSpace({ - width, - allTactics, - }: { + width, + allTactics, +}: { width: number allTactics: Tactic[] }) { @@ -198,17 +198,15 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) { function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) { return (
- { - allTactics.length == 0 - ?

Aucune tactique créée !

- : - - + {allTactics.length == 0 ? ( +

Aucune tactique créée !

+ ) : ( +
+ - -
- } - + + + )}
) } -- 2.36.3 From 1043207e2d104ee5e6ba11d21a316f9b1d24980a Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Tue, 27 Feb 2024 19:34:27 +0100 Subject: [PATCH 6/7] apply suggestions --- src/components/editor/BasketCourt.tsx | 20 +++----------------- src/editor/PlayerDomains.ts | 18 +++++++----------- 2 files changed, 10 insertions(+), 28 deletions(-) diff --git a/src/components/editor/BasketCourt.tsx b/src/components/editor/BasketCourt.tsx index 25b53b5..4f4c216 100644 --- a/src/components/editor/BasketCourt.tsx +++ b/src/components/editor/BasketCourt.tsx @@ -1,10 +1,4 @@ -import { - ReactElement, - ReactNode, - RefObject, - useLayoutEffect, - useState, -} from "react" +import { ReactElement, ReactNode, RefObject } from "react" import { Action } from "../../model/tactic/Action" import { CourtAction } from "./CourtAction.tsx" @@ -36,14 +30,6 @@ export function BasketCourt({ courtImage, courtRef, }: BasketCourtProps) { - const [forceEmptyComponents, setForceEmptyComponents] = useState(true) - - useLayoutEffect(() => { - setForceEmptyComponents(false) - }, [setForceEmptyComponents]) - - const usedComponents = forceEmptyComponents ? [] : components - return (
{courtImage} - {usedComponents.map(renderComponent)} - {usedComponents.flatMap(renderActions)} + {courtRef.current && components.map(renderComponent)} + {courtRef.current && components.flatMap(renderActions)} {previewAction && ( (pathItems[targetIdx - 1], components) - return result + return targetIdx == 0 + ? playerOrigin + : getComponent(pathItems[targetIdx - 1], components) } //FIXME this function can be a bottleneck if the phantom's position is @@ -219,22 +217,20 @@ export function removePlayer( -1, content.components, )! - const actionIdx = playerBefore.actions.findIndex( + const actions = playerBefore.actions.filter( (a) => a.target === pos.attach, ) content = updateComponent( { ...playerBefore, - actions: playerBefore.actions.toSpliced(actionIdx, 1), + actions, }, content, ) } const origin = getOrigin(player, content.components) - content = truncatePlayerPath(origin, player, content) - console.log(content) - return content + return truncatePlayerPath(origin, player, content) } content = clearPlayerPath(player, content) -- 2.36.3 From 2a5de88380209c0cbcb51a87c0c09fc0693d3f4b Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Fri, 1 Mar 2024 18:30:24 +0100 Subject: [PATCH 7/7] Apply suggestions --- src/editor/PlayerDomains.ts | 32 +++++++++++++++++++++----------- src/model/tactic/Player.ts | 4 ---- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/src/editor/PlayerDomains.ts b/src/editor/PlayerDomains.ts index 3168b8d..0473d72 100644 --- a/src/editor/PlayerDomains.ts +++ b/src/editor/PlayerDomains.ts @@ -94,17 +94,27 @@ export function computePhantomPositioning( const segments = action.segments const lastSegment = segments[segments.length - 1] const lastSegmentStart = segments[segments.length - 2]?.next - const pivotPoint = - lastSegment.controlPoint ?? - (lastSegmentStart - ? typeof lastSegmentStart === "string" - ? document - .getElementById(lastSegmentStart)! - .getBoundingClientRect() - : lastSegmentStart - : playerBeforePhantom.type === "phantom" - ? computePhantomPositioning(playerBeforePhantom, content, area) - : playerBeforePhantom.pos) + let pivotPoint = lastSegment.controlPoint + + if (!pivotPoint) { + if (lastSegmentStart) { + pivotPoint = + typeof lastSegmentStart === "string" + ? document + .getElementById(lastSegmentStart)! + .getBoundingClientRect() + : lastSegmentStart + } else { + pivotPoint = + playerBeforePhantom.type === "phantom" + ? computePhantomPositioning( + playerBeforePhantom, + content, + area, + ) + : playerBeforePhantom.pos + } + } const segment = posWithinBase(relativeTo(referentPos, pivotPoint), area) const segmentLength = norm(segment) diff --git a/src/model/tactic/Player.ts b/src/model/tactic/Player.ts index a22eaee..2dee897 100644 --- a/src/model/tactic/Player.ts +++ b/src/model/tactic/Player.ts @@ -10,10 +10,6 @@ export enum PlayerTeam { Opponents = "opponents", } -export interface Player extends PlayerInfo, Component<"player", Pos> { - readonly id: PlayerId -} - /** * All information about a player */ -- 2.36.3