Add screen phantoms #113

Merged
maxime.batista merged 7 commits from editor/screen-phantoms into master 1 year ago

@ -17,7 +17,7 @@ const Editor = lazy(() => import("./pages/Editor.tsx"))
export default function App() { export default function App() {
function suspense(node: ReactNode) { function suspense(node: ReactNode) {
return ( return (
<Suspense fallback={<p>Loading, please wait...suspense(</p>}> <Suspense fallback={<p>Loading, please wait...</p>}>
{node} {node}
</Suspense> </Suspense>
) )

@ -613,7 +613,7 @@ function wavyBezier(
wavesPer100px: number, wavesPer100px: number,
amplitude: number, amplitude: number,
): string { ): string {
function getVerticalAmplification(t: number): Pos { function getVerticalDerivativeProjectionAmplification(t: number): Pos {
const velocity = cubicBeziersDerivative(start, cp1, cp2, end, t) const velocity = cubicBeziersDerivative(start, cp1, cp2, end, t)
const velocityLength = norm(velocity) const velocityLength = norm(velocity)
//rotate the velocity by 90 deg //rotate the velocity by 90 deg
@ -641,7 +641,7 @@ function wavyBezier(
for (let t = step; t <= 1; ) { for (let t = step; t <= 1; ) {
const pos = cubicBeziers(start, cp1, cp2, end, t) const pos = cubicBeziers(start, cp1, cp2, end, t)
const amplification = getVerticalAmplification(t) const amplification = getVerticalDerivativeProjectionAmplification(t)
let nextPos let nextPos
if (phase == 1 || phase == 3) { if (phase == 1 || phase == 3) {

@ -37,8 +37,8 @@ export function BasketCourt({
style={{ position: "relative" }}> style={{ position: "relative" }}>
{courtImage} {courtImage}
{components.map(renderComponent)} {courtRef.current && components.map(renderComponent)}
{components.flatMap(renderActions)} {courtRef.current && components.flatMap(renderActions)}
{previewAction && ( {previewAction && (
<CourtAction <CourtAction

@ -13,8 +13,7 @@ export interface CourtBallProps {
export function CourtBall({ onPosValidated, ball, onRemove }: CourtBallProps) { export function CourtBall({ onPosValidated, ball, onRemove }: CourtBallProps) {
const pieceRef = useRef<HTMLDivElement>(null) const pieceRef = useRef<HTMLDivElement>(null)
const x = ball.rightRatio const { x, y } = ball.pos
const y = ball.bottomRatio
return ( return (
<Draggable <Draggable

@ -15,6 +15,10 @@ export interface CourtPlayerProps {
availableActions: (ro: HTMLElement) => ReactNode[] 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 * A player that is placed on the court, which can be selected, and moved in the associated bounds
* */ * */
@ -28,8 +32,7 @@ export default function CourtPlayer({
availableActions, availableActions,
}: CourtPlayerProps) { }: CourtPlayerProps) {
const usesBall = playerInfo.ballState != BallState.NONE const usesBall = playerInfo.ballState != BallState.NONE
const x = playerInfo.rightRatio const { x, y } = playerInfo.pos
const y = playerInfo.bottomRatio
const pieceRef = useRef<HTMLDivElement>(null) const pieceRef = useRef<HTMLDivElement>(null)
return ( return (
@ -44,7 +47,11 @@ export default function CourtPlayer({
const pos = ratioWithinBase(pieceBounds, parentBounds) 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])}> }, [courtRef, onPositionValidated, x, y])}>
<div <div
id={playerInfo.id} id={playerInfo.id}

@ -1,8 +1,8 @@
import { import {
BallState, BallState,
Player, Player,
PlayerPhantom,
PlayerLike, PlayerLike,
PlayerPhantom,
} from "../model/tactic/Player" } from "../model/tactic/Player"
import { ratioWithinBase } from "../geo/Pos" import { ratioWithinBase } from "../geo/Pos"
import { import {
@ -16,7 +16,9 @@ import { removeBall, updateComponent } from "./TacticContentDomains"
import { import {
areInSamePath, areInSamePath,
changePlayerBallState, changePlayerBallState,
getComponent,
getOrigin, getOrigin,
getPlayerNextTo,
isNextInPath, isNextInPath,
removePlayer, removePlayer,
} from "./PlayerDomains" } from "./PlayerDomains"
@ -173,11 +175,9 @@ function alreadyHasAnAnteriorActionWith(
const targetIdx = targetOriginPath.indexOf(target.id) const targetIdx = targetOriginPath.indexOf(target.id)
for (let i = targetIdx; i < targetOriginPath.length; i++) { for (let i = targetIdx; i < targetOriginPath.length; i++) {
const phantom = components.find( const component = getComponent(targetOriginPath[i], components)
(c) => c.id === targetOriginPath[i],
)! as PlayerLike
if ( if (
phantom.actions.find( component.actions.find(
(a) => (a) =>
typeof a.target === "string" && typeof a.target === "string" &&
moves(a.type) && moves(a.type) &&
@ -218,7 +218,10 @@ export function createAction(
* Creates a new phantom component. * Creates a new phantom component.
* Be aware that this function will reassign the `content` parameter. * Be aware that this function will reassign the `content` parameter.
*/ */
function createPhantom(forceHasBall: boolean): ComponentId { function createPhantom(
forceHasBall: boolean,
attachedTo?: ComponentId,
): ComponentId {
const { x, y } = ratioWithinBase(arrowHead, courtBounds) const { x, y } = ratioWithinBase(arrowHead, courtBounds)
let itemIndex: number let itemIndex: number
@ -269,8 +272,16 @@ export function createAction(
const phantom: PlayerPhantom = { const phantom: PlayerPhantom = {
type: "phantom", type: "phantom",
id: phantomId, id: phantomId,
rightRatio: x, pos: attachedTo
bottomRatio: y, ? {
type: "follows",
attach: attachedTo,
}
: {
type: "fixed",
x,
y,
},
originPlayerId: originPlayer.id, originPlayerId: originPlayer.id,
ballState: phantomState, ballState: phantomState,
actions: [], actions: [],
@ -299,10 +310,24 @@ export function createAction(
content = removeBall(content) content = removeBall(content)
} }
const action: Action = { const actionKind = getActionKind(component, origin.ballState).kind
target: toId,
type: getActionKind(component, origin.ballState).kind, let action: Action
segments: [{ next: toId }],
if (actionKind === ActionKind.SCREEN) {
createPhantom(false, toId)
action = {
target: toId,
type: actionKind,
segments: [{ next: toId }],
}
} else {
action = {
target: toId,
type: actionKind,
segments: [{ next: toId }],
}
} }
return { return {
@ -318,11 +343,18 @@ export function createAction(
} }
} }
const actionKind = getActionKind(null, origin.ballState).kind
if (actionKind === ActionKind.SCREEN)
throw new Error(
"Attempted to create a screen action with nothing targeted",
)
const phantomId = createPhantom(false) const phantomId = createPhantom(false)
const action: Action = { const action: Action = {
target: phantomId, target: phantomId,
type: getActionKind(null, origin.ballState).kind, type: actionKind,
segments: [{ next: phantomId }], segments: [{ next: phantomId }],
} }
return { return {
@ -358,10 +390,10 @@ export function removeAllActionsTargeting(
export function removeAction( export function removeAction(
origin: TacticComponent, origin: TacticComponent,
action: Action,
actionIdx: number, actionIdx: number,
content: TacticContent, content: TacticContent,
): TacticContent { ): TacticContent {
const action = origin.actions[actionIdx]
origin = { origin = {
...origin, ...origin,
actions: origin.actions.toSpliced(actionIdx, 1), actions: origin.actions.toSpliced(actionIdx, 1),
@ -408,6 +440,15 @@ 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 screenPhantom = getPlayerNextTo(origin, 1, content.components)!
content = removePlayer(screenPhantom, content)
}
return content return content
} }
@ -440,9 +481,10 @@ export function spreadNewStateFromOriginStateChange(
continue continue
} }
const actionTarget = content.components.find( const actionTarget: PlayerLike = getComponent(
(c) => action.target === c.id, action.target,
)! as PlayerLike content.components,
)
let targetState: BallState = actionTarget.ballState let targetState: BallState = actionTarget.ballState
let deleteAction = false let deleteAction = false
@ -472,13 +514,23 @@ export function spreadNewStateFromOriginStateChange(
action.type === ActionKind.SCREEN action.type === ActionKind.SCREEN
) { ) {
targetState = BallState.HOLDS_BY_PASS 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) { if (deleteAction) {
content = removeAction(origin, action, i, content) content = removeAction(origin, i, content)
origin = content.components.find((c) => c.id === origin.id)! as origin = getComponent(origin.id, content.components)
| Player
| PlayerPhantom
i-- // step back i-- // step back
} else { } else {
// do not change the action type if it is a shoot action // do not change the action type if it is a shoot action

@ -4,13 +4,27 @@ import {
PlayerLike, PlayerLike,
PlayerPhantom, PlayerPhantom,
} from "../model/tactic/Player" } 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 { removeComponent, updateComponent } from "./TacticContentDomains"
import { import {
removeAllActionsTargeting, removeAllActionsTargeting,
spreadNewStateFromOriginStateChange, spreadNewStateFromOriginStateChange,
} from "./ActionsDomains" } from "./ActionsDomains"
import { ActionKind } from "../model/tactic/Action" import { ActionKind } from "../model/tactic/Action"
import {
add,
minus,
norm,
Pos,
posWithinBase,
ratioWithinBase,
relativeTo,
} from "../geo/Pos.ts"
import { PLAYER_RADIUS_PIXELS } from "../components/editor/CourtPlayer.tsx"
export function getOrigin( export function getOrigin(
pathItem: PlayerPhantom, pathItem: PlayerPhantom,
@ -20,6 +34,107 @@ export function getOrigin(
return components.find((c) => c.id == pathItem.originPlayerId)! as Player 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
return targetIdx == 0
? playerOrigin
: getComponent<PlayerLike>(pathItems[targetIdx - 1], components)
}
//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
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)
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<T extends TacticComponent>(
id: string,
components: TacticComponent[],
): T {
return components.find((c) => c.id === id)! as T
}
export function areInSamePath(a: PlayerLike, b: PlayerLike) { export function areInSamePath(a: PlayerLike, b: PlayerLike) {
if (a.type === "phantom" && b.type === "phantom") { if (a.type === "phantom" && b.type === "phantom") {
return a.originPlayerId === b.originPlayerId return a.originPlayerId === b.originPlayerId
@ -54,7 +169,7 @@ export function isNextInPath(
) )
} }
export function removePlayerPath( export function clearPlayerPath(
player: Player, player: Player,
content: TacticContent, content: TacticContent,
): TacticContent { ): TacticContent {
@ -75,18 +190,60 @@ 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( export function removePlayer(
player: PlayerLike, player: PlayerLike,
content: TacticContent, content: TacticContent,
): TacticContent { ): TacticContent {
content = removeAllActionsTargeting(player.id, content) content = removeAllActionsTargeting(player.id, content)
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 actions = playerBefore.actions.filter(
(a) => a.target === pos.attach,
)
content = updateComponent(
{
...playerBefore,
actions,
},
content,
)
}
if (player.type == "phantom") {
const origin = getOrigin(player, content.components) const origin = getOrigin(player, content.components)
return truncatePlayerPath(origin, player, content) return truncatePlayerPath(origin, player, content)
} }
content = removePlayerPath(player, content) content = clearPlayerPath(player, content)
content = removeComponent(player.id, content) content = removeComponent(player.id, content)
for (const action of player.actions) { for (const action of player.actions) {

@ -3,6 +3,7 @@ import {
BallState, BallState,
Player, Player,
PlayerInfo, PlayerInfo,
PlayerLike,
PlayerTeam, PlayerTeam,
} from "../model/tactic/Player" } from "../model/tactic/Player"
import { import {
@ -18,22 +19,22 @@ import {
} from "../model/tactic/Tactic" } from "../model/tactic/Tactic"
import { overlaps } from "../geo/Box" import { overlaps } from "../geo/Box"
import { RackedCourtObject, RackedPlayer } from "./RackedItems" import { RackedCourtObject, RackedPlayer } from "./RackedItems"
import { changePlayerBallState } from "./PlayerDomains" import { changePlayerBallState, getComponent, getOrigin } from "./PlayerDomains"
import { ActionKind } from "../model/tactic/Action.ts"
export function placePlayerAt( export function placePlayerAt(
refBounds: DOMRect, refBounds: DOMRect,
courtBounds: DOMRect, courtBounds: DOMRect,
element: RackedPlayer, element: RackedPlayer,
): Player { ): Player {
const { x, y } = ratioWithinBase(refBounds, courtBounds) const pos = ratioWithinBase(refBounds, courtBounds)
return { return {
type: "player", type: "player",
id: "player-" + element.key + "-" + element.team, id: "player-" + element.key + "-" + element.team,
team: element.team, team: element.team,
role: element.key, role: element.key,
rightRatio: x, pos,
bottomRatio: y,
ballState: BallState.NONE, ballState: BallState.NONE,
path: null, path: null,
actions: [], actions: [],
@ -46,7 +47,7 @@ export function placeObjectAt(
rackedObject: RackedCourtObject, rackedObject: RackedCourtObject,
content: TacticContent, content: TacticContent,
): TacticContent { ): TacticContent {
const { x, y } = ratioWithinBase(refBounds, courtBounds) const pos = ratioWithinBase(refBounds, courtBounds)
let courtObject: CourtObject let courtObject: CourtObject
@ -64,8 +65,7 @@ export function placeObjectAt(
courtObject = { courtObject = {
type: BALL_TYPE, type: BALL_TYPE,
id: BALL_ID, id: BALL_ID,
rightRatio: x, pos,
bottomRatio: y,
actions: [], actions: [],
} }
break break
@ -134,13 +134,12 @@ export function placeBallAt(
const ballIdx = content.components.findIndex((o) => o.type == "ball") const ballIdx = content.components.findIndex((o) => o.type == "ball")
const { x, y } = ratioWithinBase(refBounds, courtBounds) const pos = ratioWithinBase(refBounds, courtBounds)
const ball: Ball = { const ball: Ball = {
type: BALL_TYPE, type: BALL_TYPE,
id: BALL_ID, id: BALL_ID,
rightRatio: x, pos,
bottomRatio: y,
actions: [], actions: [],
} }
@ -174,14 +173,61 @@ export function moveComponent(
if (!overlaps(playerBounds, courtBounds)) { if (!overlaps(playerBounds, courtBounds)) {
return removed(content) 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(
<TacticComponent>{
...component, ...component,
rightRatio: newPos.x, pos: isPhantom
bottomRatio: newPos.y, ? {
type: "fixed",
...newPos,
}
: newPos,
}, },
content, content,
) )
return content
} }
export function removeComponent( export function removeComponent(

@ -9,9 +9,10 @@ export enum ActionKind {
SHOOT = "SHOOT", SHOOT = "SHOOT",
} }
export type Action = { type: ActionKind } & MovementAction export type Action = MovementAction
export interface MovementAction { export interface MovementAction {
type: ActionKind
target: ComponentId | Pos target: ComponentId | Pos
segments: Segment[] segments: Segment[]
} }

@ -1,4 +1,5 @@
import { Component } from "./Tactic" import { Component } from "./Tactic"
import { Pos } from "../../geo/Pos.ts"
export const BALL_ID = "ball" export const BALL_ID = "ball"
export const BALL_TYPE = "ball" export const BALL_TYPE = "ball"
@ -6,4 +7,4 @@ export const BALL_TYPE = "ball"
//place here all different kinds of objects //place here all different kinds of objects
export type CourtObject = Ball export type CourtObject = Ball
export type Ball = Component<typeof BALL_TYPE> export type Ball = Component<typeof BALL_TYPE, Pos>

@ -1,4 +1,5 @@
import { Component, ComponentId } from "./Tactic" import { Component, ComponentId } from "./Tactic"
import { Pos } from "../../geo/Pos.ts"
export type PlayerId = string export type PlayerId = string
@ -9,10 +10,6 @@ export enum PlayerTeam {
Opponents = "opponents", Opponents = "opponents",
} }
export interface Player extends PlayerInfo, Component<"player"> {
readonly id: PlayerId
}
/** /**
* All information about a player * All information about a player
*/ */
@ -33,15 +30,7 @@ export interface PlayerInfo {
*/ */
readonly ballState: BallState readonly ballState: BallState
/** readonly pos: Pos
* 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
} }
export enum BallState { export enum BallState {
@ -52,7 +41,7 @@ export enum BallState {
PASSED_ORIGIN, PASSED_ORIGIN,
} }
export interface Player extends Component<"player">, PlayerInfo { export interface Player extends Component<"player", Pos>, PlayerInfo {
/** /**
* True if the player has a basketball * True if the player has a basketball
*/ */
@ -65,11 +54,34 @@ export interface MovementPath {
readonly items: ComponentId[] 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 * A player phantom is a kind of component that represents the future state of a player
* according to the court's step information * according to the court's step information
*/ */
export interface PlayerPhantom extends Component<"phantom"> { export interface PlayerPhantom
extends Component<"phantom", PhantomPositioning> {
readonly originPlayerId: ComponentId readonly originPlayerId: ComponentId
readonly ballState: BallState readonly ballState: BallState
/**
* Defines a component this phantom will be attached to.
*/
readonly attachedTo?: ComponentId
} }

@ -18,7 +18,7 @@ export interface TacticContent {
export type TacticComponent = Player | CourtObject | PlayerPhantom export type TacticComponent = Player | CourtObject | PlayerPhantom
export type ComponentId = string export type ComponentId = string
export interface Component<T> { export interface Component<T, Positioning> {
/** /**
* The component's type * The component's type
*/ */
@ -27,15 +27,8 @@ export interface Component<T> {
* The component's identifier * The component's identifier
*/ */
readonly id: ComponentId 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
/** readonly pos: Positioning
* Percentage of the component's position to the right (0 means left, 1 means right, 0.5 means middle)
*/
readonly rightRatio: number
readonly actions: Action[] readonly actions: Action[]
} }

@ -68,6 +68,7 @@ import { Action, ActionKind } from "../model/tactic/Action"
import BallAction from "../components/actions/BallAction" import BallAction from "../components/actions/BallAction"
import { import {
changePlayerBallState, changePlayerBallState,
computePhantomPositioning,
getOrigin, getOrigin,
removePlayer, removePlayer,
} from "../editor/PlayerDomains" } from "../editor/PlayerDomains"
@ -329,7 +330,7 @@ function EditorView({
content, content,
(content) => { (content) => {
if (player.type == "player") insertRackedPlayer(player) if (player.type === "player") insertRackedPlayer(player)
return removePlayer(player, content) return removePlayer(player, content)
}, },
), ),
@ -402,8 +403,11 @@ function EditorView({
id: component.id, id: component.id,
team: origin.team, team: origin.team,
role: origin.role, role: origin.role,
bottomRatio: component.bottomRatio, pos: computePhantomPositioning(
rightRatio: component.rightRatio, component,
content,
courtBounds(),
),
ballState: component.ballState, ballState: component.ballState,
} }
} else { } else {
@ -435,8 +439,8 @@ function EditorView({
) )
const doDeleteAction = useCallback( const doDeleteAction = useCallback(
(action: Action, idx: number, origin: TacticComponent) => { (_: Action, idx: number, origin: TacticComponent) => {
setContent((content) => removeAction(origin, action, idx, content)) setContent((content) => removeAction(origin, idx, content))
}, },
[setContent], [setContent],
) )

@ -57,10 +57,10 @@ export default function HomePage() {
} }
function Home({ function Home({
lastTactics, lastTactics,
allTactics, allTactics,
teams, teams,
}: { }: {
lastTactics: Tactic[] lastTactics: Tactic[]
allTactics: Tactic[] allTactics: Tactic[]
teams: Team[] teams: Team[]
@ -77,10 +77,10 @@ function Home({
} }
function Body({ function Body({
lastTactics, lastTactics,
allTactics, allTactics,
teams, teams,
}: { }: {
lastTactics: Tactic[] lastTactics: Tactic[]
allTactics: Tactic[] allTactics: Tactic[]
teams: Team[] teams: Team[]
@ -100,10 +100,10 @@ function Body({
} }
function SideMenu({ function SideMenu({
width, width,
lastTactics, lastTactics,
teams, teams,
}: { }: {
width: number width: number
lastTactics: Tactic[] lastTactics: Tactic[]
teams: Team[] teams: Team[]
@ -123,9 +123,9 @@ function SideMenu({
} }
function PersonalSpace({ function PersonalSpace({
width, width,
allTactics, allTactics,
}: { }: {
width: number width: number
allTactics: Tactic[] allTactics: Tactic[]
}) { }) {
@ -198,17 +198,15 @@ function TableData({ allTactics }: { allTactics: Tactic[] }) {
function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) { function BodyPersonalSpace({ allTactics }: { allTactics: Tactic[] }) {
return ( return (
<div id="body-personal-space"> <div id="body-personal-space">
{ {allTactics.length == 0 ? (
allTactics.length == 0 <p>Aucune tactique créée !</p>
? <p>Aucune tactique créée !</p> ) : (
: <table>
<table> <tbody key="tbody">
<tbody key="tbody">
<TableData allTactics={allTactics} /> <TableData allTactics={allTactics} />
</tbody> </tbody>
</table> </table>
} )}
</div> </div>
) )
} }

@ -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%;
}
Loading…
Cancel
Save