reset arrow curves by deleting the central control point

pull/82/head
maxime 1 year ago committed by maxime.batista
parent 6c92471062
commit afd7b0570c

@ -1,7 +1,7 @@
import "../../style/actions/arrow_action.css" import "../../style/actions/arrow_action.css"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import {useRef} from "react" import { useRef } from "react"
export interface ArrowActionProps { export interface ArrowActionProps {
onHeadDropped: (headBounds: DOMRect) => void onHeadDropped: (headBounds: DOMRect) => void
@ -18,7 +18,7 @@ export default function ArrowAction({
return ( return (
<div className="arrow-action"> <div className="arrow-action">
<div className="arrow-action-pin"/> <div className="arrow-action-pin" />
<Draggable <Draggable
nodeRef={arrowHeadRef} nodeRef={arrowHeadRef}
@ -37,10 +37,8 @@ export default function ArrowAction({
arrowHeadRef.current!.getBoundingClientRect() arrowHeadRef.current!.getBoundingClientRect()
onHeadMoved(headBounds) onHeadMoved(headBounds)
}} }}
position={{x: 0, y: 0}}> position={{ x: 0, y: 0 }}>
<div <div ref={arrowHeadRef} className="arrow-head-pick" />
ref={arrowHeadRef}
className="arrow-head-pick"/>
</Draggable> </Draggable>
</div> </div>
) )

@ -19,4 +19,5 @@ export default function BallAction({onDrop}: BallActionProps) {
</div> </div>
</Draggable> </Draggable>
) )
} }

@ -1,5 +1,12 @@
import {CSSProperties, ReactElement, useCallback, useEffect, useRef, useState,} from "react" import {
import {angle, middlePos, Pos, relativeTo} from "./Pos" CSSProperties,
ReactElement,
useCallback,
useEffect,
useRef,
useState,
} from "react"
import { angle, middlePos, Pos, relativeTo } from "./Pos"
import "../../style/bendable_arrows.css" import "../../style/bendable_arrows.css"
import Draggable from "react-draggable" import Draggable from "react-draggable"
@ -39,27 +46,24 @@ function constraintInCircle(pos: Pos, from: Pos, radius: number): Pos {
} }
} }
function Triangle({ fill }: { fill: string }) {
function Triangle({fill}: {fill: string}) {
return ( return (
<svg viewBox={"0 0 50 50"} width={20} height={20}> <svg viewBox={"0 0 50 50"} width={20} height={20}>
<polygon points={"50 0, 0 0, 25 40"} fill={fill}/> <polygon points={"50 0, 0 0, 25 40"} fill={fill} />
</svg> </svg>
) )
} }
export default function BendableArrow({ export default function BendableArrow({
startPos, startPos,
segments,
onSegmentsChanges,
style,
startRadius = 0,
endRadius = 0,
}: BendableArrowProps) {
segments,
onSegmentsChanges,
style,
startRadius = 0,
endRadius = 0,
}: BendableArrowProps) {
const containerRef = useRef<HTMLDivElement>(null) const containerRef = useRef<HTMLDivElement>(null)
const svgRef = useRef<SVGSVGElement>(null) const svgRef = useRef<SVGSVGElement>(null)
@ -73,47 +77,45 @@ export default function BendableArrow({
useEffect(() => { useEffect(() => {
setInternalSegments(segments) setInternalSegments(segments)
}, [segments]); }, [segments])
const [internalSegments, setInternalSegments] = useState(segments) const [internalSegments, setInternalSegments] = useState(segments)
const [isSelected, setIsSelected] = useState(false) const [isSelected, setIsSelected] = useState(false)
const headRef = useRef<HTMLDivElement>(null) const headRef = useRef<HTMLDivElement>(null)
const tailRef = useRef<HTMLDivElement>(null) const tailRef = useRef<HTMLDivElement>(null)
useEffect(() => { useEffect(() => {
const basePos =
const basePos = containerRef.current!.parentElement!.getBoundingClientRect() containerRef.current!.parentElement!.getBoundingClientRect()
setControlPointsDots(computeControlPoints(basePos)) setControlPointsDots(computeControlPoints(basePos))
const selectionHandler = (e: MouseEvent) => { const selectionHandler = (e: MouseEvent) => {
if (!(e.target instanceof Node)) if (!(e.target instanceof Node)) return
return
setIsSelected(containerRef.current!.contains(e.target)) const isSelected = containerRef.current!.contains(e.target)
setIsSelected(isSelected)
} }
document.addEventListener('mousedown', selectionHandler) document.addEventListener("mousedown", selectionHandler)
return () => document.removeEventListener('mousedown', selectionHandler) return () => document.removeEventListener("mousedown", selectionHandler)
}, [])
}, []);
function computeControlPoints(basePos: Pos) { function computeControlPoints(basePos: Pos) {
return internalSegments.map(({next, controlPoint}, i) => { return internalSegments.map(({ next, controlPoint }, i) => {
const prev = i == 0 ? startPos : internalSegments[i - 1].next const prev = i == 0 ? startPos : internalSegments[i - 1].next
const prevRelative = relativeTo(prev, basePos) const prevRelative = relativeTo(prev, basePos)
const nextRelative = relativeTo(next, basePos) const nextRelative = relativeTo(next, basePos)
const cpPos = controlPoint
const cpPos = controlPoint ? relativeTo(controlPoint, basePos) : { ? relativeTo(controlPoint, basePos)
x: prevRelative.x / 2 + nextRelative.x / 2, : {
y: prevRelative.y / 2 + nextRelative.y / 2, x: prevRelative.x / 2 + nextRelative.x / 2,
} y: prevRelative.y / 2 + nextRelative.y / 2,
}
return ( return (
<ControlPoint <ControlPoint
@ -122,12 +124,18 @@ export default function BendableArrow({
basePos={basePos} basePos={basePos}
onPosValidated={(controlPoint) => { onPosValidated={(controlPoint) => {
const segment = internalSegments[i] const segment = internalSegments[i]
const segments = internalSegments.toSpliced(i, 1, {...segment, controlPoint}) const segments = internalSegments.toSpliced(i, 1, {
...segment,
controlPoint,
})
onSegmentsChanges(segments) onSegmentsChanges(segments)
}} }}
onMoves={(controlPoint) => { onMoves={(controlPoint) => {
setInternalSegments(is => { setInternalSegments((is) => {
return is.toSpliced(i, 1, {...is[i], controlPoint}) return is.toSpliced(i, 1, {
...is[i],
controlPoint,
})
}) })
}} }}
/> />
@ -135,9 +143,7 @@ export default function BendableArrow({
}) })
} }
const update = useCallback(() => { const update = useCallback(() => {
// only one segment is supported for now, which is the first. // only one segment is supported for now, which is the first.
// any other segments will be ignored // any other segments will be ignored
const segment = internalSegments[0] ?? null const segment = internalSegments[0] ?? null
@ -146,16 +152,18 @@ export default function BendableArrow({
const endPos = segment.next const endPos = segment.next
const basePos = containerRef.current!.parentElement!.getBoundingClientRect() const basePos =
containerRef.current!.parentElement!.getBoundingClientRect()
const startRelative = relativeTo(startPos, basePos) const startRelative = relativeTo(startPos, basePos)
const endRelative = relativeTo(endPos!, basePos) const endRelative = relativeTo(endPos!, basePos)
const controlPoint = segment.controlPoint ? relativeTo(segment.controlPoint, basePos) : { const controlPoint = segment.controlPoint
x: startRelative.x / 2 + endRelative.x / 2, ? relativeTo(segment.controlPoint, basePos)
y: startRelative.y / 2 + endRelative.y / 2, : {
} x: startRelative.x / 2 + endRelative.x / 2,
y: startRelative.y / 2 + endRelative.y / 2,
}
const tailPos = constraintInCircle( const tailPos = constraintInCircle(
startRelative, startRelative,
@ -171,53 +179,56 @@ export default function BendableArrow({
const left = Math.min(tailPos.x, headPos.x) const left = Math.min(tailPos.x, headPos.x)
const top = Math.min(tailPos.y, headPos.y) const top = Math.min(tailPos.y, headPos.y)
Object.assign(tailRef.current!.style, { Object.assign(tailRef.current!.style, {
left: tailPos.x + "px", left: tailPos.x + "px",
top: tailPos.y + "px", top: tailPos.y + "px",
transformOrigin: "top center", transformOrigin: "top center",
transform: `translateX(-50%) rotate(${-angle(tailPos, controlPoint) * (180 / Math.PI)}deg)` transform: `translateX(-50%) rotate(${
-angle(tailPos, controlPoint) * (180 / Math.PI)
}deg)`,
} as CSSProperties) } as CSSProperties)
Object.assign(headRef.current!.style, { Object.assign(headRef.current!.style, {
left: headPos.x + "px", left: headPos.x + "px",
top: headPos.y + "px", top: headPos.y + "px",
transformOrigin: "top center", transformOrigin: "top center",
transform: `translateX(-50%) rotate(${-angle(headPos, controlPoint) * (180 / Math.PI)}deg)` transform: `translateX(-50%) rotate(${
-angle(headPos, controlPoint) * (180 / Math.PI)
}deg)`,
} as CSSProperties) } as CSSProperties)
const svgStyle: CSSProperties = { const svgStyle: CSSProperties = {
left: left + "px", left: left + "px",
top: top + "px", top: top + "px",
} }
const segmentsRelatives = internalSegments.map(({next, controlPoint}) => { const segmentsRelatives = internalSegments.map(
return { ({ next, controlPoint }) => {
next: relativeTo(next, basePos), return {
cp: controlPoint ? relativeTo(controlPoint, basePos) : undefined next: relativeTo(next, basePos),
} cp: controlPoint
}) ? relativeTo(controlPoint, basePos)
: undefined,
}
},
)
const computedSegments = segmentsRelatives const computedSegments = segmentsRelatives
.map(({next: n, cp}, idx) => { .map(({ next: n, cp }, idx) => {
let next = n let next = n
if (idx == internalSegments.length - 1) { if (idx == internalSegments.length - 1) {
//if it is the last element //if it is the last element
next = constraintInCircle( next = constraintInCircle(next, controlPoint, endRadius!)
next,
controlPoint,
endRadius!,
)
} }
if (cp == undefined) { if (cp == undefined) {
return `L${next.x - left} ${next.y - top}` return `L${next.x - left} ${next.y - top}`
} }
return `C${cp.x - left} ${cp.y - top}, ${cp.x - left} ${cp.y - top}, ${next.x - left} ${next.y - top}` return `C${cp.x - left} ${cp.y - top}, ${cp.x - left} ${
cp.y - top
}, ${next.x - left} ${next.y - top}`
}) })
.join(" ") .join(" ")
@ -225,16 +236,15 @@ export default function BendableArrow({
pathRef.current!.setAttribute("d", d) pathRef.current!.setAttribute("d", d)
Object.assign(svgRef.current!.style, svgStyle) Object.assign(svgRef.current!.style, svgStyle)
if (isSelected) { setControlPointsDots(computeControlPoints(basePos))
setControlPointsDots(computeControlPoints(basePos))
}
}, [startPos, internalSegments]) }, [startPos, internalSegments])
useEffect(update, [update]) useEffect(update, [update])
return ( return (
<div ref={containerRef} style={{position: "absolute", top: 0, left: 0}}> <div
ref={containerRef}
style={{ position: "absolute", top: 0, left: 0 }}>
<svg <svg
ref={svgRef} ref={svgRef}
style={{ style={{
@ -251,18 +261,18 @@ export default function BendableArrow({
/> />
</svg> </svg>
<div className={"arrow-head"} <div
style={{position: "absolute", transformOrigin: "center"}} className={"arrow-head"}
ref={headRef} style={{ position: "absolute", transformOrigin: "center" }}
> ref={headRef}>
{style?.head?.call(style) ?? <Triangle fill={"red"}/>} {style?.head?.call(style) ?? <Triangle fill={"red"} />}
</div> </div>
<div className={"arrow-tail"} <div
style={{position: "absolute", transformOrigin: "center"}} className={"arrow-tail"}
ref={tailRef} style={{ position: "absolute", transformOrigin: "center" }}
> ref={tailRef}>
{style?.tail?.call(style) ?? <Triangle fill={"blue"}/>} {style?.tail?.call(style) ?? <Triangle fill={"blue"} />}
</div> </div>
{isSelected && controlPointsDots} {isSelected && controlPointsDots}
@ -272,18 +282,18 @@ export default function BendableArrow({
interface ControlPointProps { interface ControlPointProps {
pos: Pos pos: Pos
basePos: Pos, basePos: Pos
onMoves: (currentPos: Pos) => void onMoves: (currentPos: Pos) => void
onPosValidated: (newPos: Pos) => void, onPosValidated: (newPos: Pos | undefined) => void
radius?: number radius?: number
} }
function ControlPoint({ function ControlPoint({
pos, pos,
onMoves, onMoves,
onPosValidated, onPosValidated,
radius = 7, radius = 7,
}: ControlPointProps) { }: ControlPointProps) {
const ref = useRef<HTMLDivElement>(null) const ref = useRef<HTMLDivElement>(null)
return ( return (
@ -297,8 +307,7 @@ function ControlPoint({
const pointPos = middlePos(ref.current!.getBoundingClientRect()) const pointPos = middlePos(ref.current!.getBoundingClientRect())
onMoves(pointPos) onMoves(pointPos)
}} }}
position={{x: pos.x - radius, y: pos.y - radius}} position={{ x: pos.x - radius, y: pos.y - radius }}>
>
<div <div
ref={ref} ref={ref}
className={"arrow-edge-control-point"} className={"arrow-edge-control-point"}
@ -307,6 +316,12 @@ function ControlPoint({
width: radius * 2, width: radius * 2,
height: radius * 2, height: radius * 2,
}} }}
onKeyDown={(e) => {
if (e.key == "Delete") {
onPosValidated(undefined)
}
}}
tabIndex={0}
/> />
</Draggable> </Draggable>
) )

@ -3,7 +3,7 @@ export interface Pos {
y: number y: number
} }
export const NULL_POS: Pos = {x: 0, y: 0} export const NULL_POS: Pos = { x: 0, y: 0 }
/** /**
* Returns position of a relative to b * Returns position of a relative to b
@ -11,7 +11,7 @@ export const NULL_POS: Pos = {x: 0, y: 0}
* @param b * @param b
*/ */
export function relativeTo(a: Pos, b: Pos): Pos { export function relativeTo(a: Pos, b: Pos): Pos {
return {x: a.x - b.x, y: a.y - b.y} return { x: a.x - b.x, y: a.y - b.y }
} }
/** /**
@ -19,7 +19,7 @@ export function relativeTo(a: Pos, b: Pos): Pos {
* @param rect * @param rect
*/ */
export function middlePos(rect: DOMRect): Pos { export function middlePos(rect: DOMRect): Pos {
return {x: rect.x + rect.width / 2, y: rect.y + rect.height / 2} return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }
} }
/** /**
@ -28,11 +28,11 @@ export function middlePos(rect: DOMRect): Pos {
* @param b * @param b
*/ */
export function size(a: Pos, b: Pos): Pos { export function size(a: Pos, b: Pos): Pos {
return {x: Math.abs(a.x - b.x), y: Math.abs(a.y - b.y)} return { x: Math.abs(a.x - b.x), y: Math.abs(a.y - b.y) }
} }
export function add(a: Pos, b: Pos): Pos { export function add(a: Pos, b: Pos): Pos {
return {x: a.x + b.x, y: a.y + b.y} return { x: a.x + b.x, y: a.y + b.y }
} }
export function angle(a: Pos, b: Pos): number { export function angle(a: Pos, b: Pos): number {

@ -3,29 +3,31 @@ import {CourtBall} from "./CourtBall";
import {ReactElement, RefObject, useCallback, useState,} from "react" import {ReactElement, RefObject, useCallback, useState,} from "react"
import CourtPlayer from "./CourtPlayer" import CourtPlayer from "./CourtPlayer"
import {Player} from "../../tactic/Player" import { Player } from "../../tactic/Player"
import {Action, MovementActionKind} from "../../tactic/Action" import { Action, MovementActionKind } from "../../tactic/Action"
import RemoveAction from "../actions/RemoveAction" import RemoveAction from "../actions/RemoveAction"
import ArrowAction from "../actions/ArrowAction" import ArrowAction from "../actions/ArrowAction"
import BendableArrow, {Segment} from "../arrows/BendableArrow" import BendableArrow, { Segment } from "../arrows/BendableArrow"
import {middlePos, NULL_POS, Pos} from "../arrows/Pos" import { middlePos, NULL_POS, Pos } from "../arrows/Pos"
import BallAction from "../actions/BallAction"; import BallAction from "../actions/BallAction"
import {CourtObject} from "../../tactic/CourtObjects"; import {CourtObject} from "../../tactic/CourtObjects";
export interface BasketCourtProps { export interface BasketCourtProps {
players: Player[] players: Player[]
actions: Action[] actions: Action[]
objects: CourtObject[] objects: CourtObject[]
renderAction: (a: Action, key: number) => ReactElement renderAction: (a: Action, key: number) => ReactElement
setActions: (f: (a: Action[]) => Action[]) => void setActions: (f: (a: Action[]) => Action[]) => void
onPlayerRemove: (p: Player) => void onPlayerRemove: (p: Player) => void
onPlayerChange: (p: Player) => void onPlayerChange: (p: Player) => void
onBallRemove: () => void onBallRemove: () => void
onBallMoved: (ball: DOMRect) => void onBallMoved: (ball: DOMRect) => void
courtImage: string courtImage: string
@ -33,20 +35,21 @@ export interface BasketCourtProps {
} }
export function BasketCourt({ export function BasketCourt({
objects, players,
onBallMoved, actions,
onBallRemove, objects,
players, renderAction,
actions, setActions,
renderAction, onPlayerRemove,
setActions, onPlayerChange,
onPlayerRemove,
onPlayerChange, onBallMoved,
courtImage, onBallRemove,
courtRef,
}: BasketCourtProps) { courtImage,
courtRef,
function bindArrowToPlayer(originRef: HTMLElement, arrowHead: DOMRect) { }: BasketCourtProps) {
function placeArrow(originRef: HTMLElement, arrowHead: DOMRect) {
for (const player of players) { for (const player of players) {
if (player.id == originRef.id) { if (player.id == originRef.id) {
continue continue
@ -64,17 +67,28 @@ export function BasketCourt({
playerBounds.left > arrowHead.right playerBounds.left > arrowHead.right
) )
) { ) {
const targetPos = document.getElementById(player.id)!.getBoundingClientRect() const targetPos = document
.getElementById(player.id)!
.getBoundingClientRect()
const action: Action = { const action: Action = {
fromPlayerId: originRef.id, fromPlayerId: originRef.id,
toPlayerId: player.id, toPlayerId: player.id,
type: MovementActionKind.SCREEN, type: MovementActionKind.SCREEN,
moveFrom: middlePos(originRef.getBoundingClientRect()), moveFrom: middlePos(originRef.getBoundingClientRect()),
segments: [{next: middlePos(targetPos)}], segments: [{ next: middlePos(targetPos) }],
} }
setActions((actions) => [...actions, action]) setActions((actions) => [...actions, action])
return
} }
} }
const action: Action = {
fromPlayerId: originRef.id,
type: MovementActionKind.MOVE,
moveFrom: middlePos(originRef.getBoundingClientRect()),
segments: [{ next: middlePos(arrowHead) }],
}
setActions((actions) => [...actions, action])
} }
const [previewArrowOriginPos, setPreviewArrowOriginPos] = const [previewArrowOriginPos, setPreviewArrowOriginPos] =
@ -84,27 +98,38 @@ export function BasketCourt({
const [previewArrowEdges, setPreviewArrowEdges] = useState<Segment[]>([]) const [previewArrowEdges, setPreviewArrowEdges] = useState<Segment[]>([])
const updateActionsRelatedTo = useCallback((player: Player) => { const updateActionsRelatedTo = useCallback((player: Player) => {
const newPos = middlePos(document.getElementById(player.id)!.getBoundingClientRect()) const newPos = middlePos(
setActions(actions => actions.map(a => { document.getElementById(player.id)!.getBoundingClientRect(),
if (a.fromPlayerId == player.id) { )
return {...a, moveFrom: newPos} setActions((actions) =>
} actions.map((a) => {
if (a.fromPlayerId == player.id) {
return { ...a, moveFrom: newPos }
}
if (a.toPlayerId == player.id) { if (a.toPlayerId == player.id) {
const segments = a.segments.toSpliced(a.segments.length - 1, 1, { const segments = a.segments.toSpliced(
...a.segments[a.segments.length - 1], a.segments.length - 1,
next: newPos 1,
}) {
return {...a, segments} ...a.segments[a.segments.length - 1],
} next: newPos,
},
)
return { ...a, segments }
}
return a return a
})) }),
)
}, []) }, [])
return ( return (
<div id="court-container" ref={courtRef} style={{position: "relative"}}> <div
<img src={courtImage} alt={"court"} id="court-svg"/> id="court-container"
ref={courtRef}
style={{ position: "relative" }}>
<img src={courtImage} alt={"court"} id="court-svg" />
{actions.map((action, idx) => renderAction(action, idx))} {actions.map((action, idx) => renderAction(action, idx))}
@ -125,7 +150,7 @@ export function BasketCourt({
key={2} key={2}
onHeadMoved={(headPos) => onHeadMoved={(headPos) =>
setPreviewArrowEdges([ setPreviewArrowEdges([
{next: middlePos(headPos)}, { next: middlePos(headPos) },
]) ])
} }
onHeadPicked={(headPos) => { onHeadPicked={(headPos) => {
@ -133,12 +158,12 @@ export function BasketCourt({
middlePos(pieceRef.getBoundingClientRect()), middlePos(pieceRef.getBoundingClientRect()),
) )
setPreviewArrowEdges([ setPreviewArrowEdges([
{next: middlePos(headPos)}, { next: middlePos(headPos) },
]) ])
setPreviewArrowEnabled(true) setPreviewArrowEnabled(true)
}} }}
onHeadDropped={(headRect) => { onHeadDropped={(headRect) => {
bindArrowToPlayer(pieceRef, headRect) placeArrow(pieceRef, headRect)
setPreviewArrowEnabled(false) setPreviewArrowEnabled(false)
}} }}
/>, />,
@ -167,6 +192,7 @@ export function BasketCourt({
segments={previewArrowEdges} segments={previewArrowEdges}
//do nothing on change, not really possible as it's a preview arrow //do nothing on change, not really possible as it's a preview arrow
onSegmentsChanges={() => {}} onSegmentsChanges={() => {}}
//TODO place those values in constants
endRadius={17} endRadius={17}
startRadius={26} startRadius={26}
/> />

@ -1,10 +1,9 @@
import { ReactNode, RefObject, useRef } from "react"
import {ReactNode, RefObject, useRef} from "react"
import "../../style/player.css" import "../../style/player.css"
import Draggable from "react-draggable" import Draggable from "react-draggable"
import {PlayerPiece} from "./PlayerPiece" import { PlayerPiece } from "./PlayerPiece"
import {Player} from "../../tactic/Player" import { Player } from "../../tactic/Player"
import {calculateRatio} from "../../Utils" import { calculateRatio } from "../../Utils"
export interface PlayerProps<A extends ReactNode> { export interface PlayerProps<A extends ReactNode> {
player: Player player: Player
@ -62,12 +61,20 @@ export default function CourtPlayer<A extends ReactNode>({
left: `${x * 100}%`, left: `${x * 100}%`,
top: `${y * 100}%`, top: `${y * 100}%`,
}}> }}>
<div tabIndex={0} className="player-content" <div
onKeyUp={(e) => { tabIndex={0}
if (e.key == "Delete") onRemove() className="player-content"
}}> onKeyUp={(e) => {
<div className="player-actions">{availableActions(pieceRef.current!)}</div> if (e.key == "Delete") onRemove()
<PlayerPiece team={player.team} text={player.role} hasBall={hasBall} /> }}>
<div className="player-actions">
{availableActions(pieceRef.current!)}
</div>
<PlayerPiece
team={player.team}
text={player.role}
hasBall={hasBall}
/>
</div> </div>
</div> </div>
</Draggable> </Draggable>

@ -3,18 +3,19 @@
border-radius: 100px; border-radius: 100px;
background-color: black; background-color: black;
outline: none;
} }
.arrow-edge-control-point:hover { .arrow-edge-control-point:hover {
background-color: var(--selection-color); background-color: var(--selection-color);
} }
.arrow-path { .arrow-path {
pointer-events: stroke; pointer-events: stroke;
cursor: pointer; cursor: pointer;
} }
.arrow-path:hover, .arrow-path:active { .arrow-path:hover,
stroke: var(--selection-color) .arrow-path:active {
} stroke: var(--selection-color);
}

@ -11,8 +11,8 @@ export enum MovementActionKind {
export type Action = { type: MovementActionKind } & MovementAction export type Action = { type: MovementActionKind } & MovementAction
export interface MovementAction { export interface MovementAction {
fromPlayerId: PlayerId, fromPlayerId: PlayerId
toPlayerId: PlayerId, toPlayerId?: PlayerId
moveFrom: Pos moveFrom: Pos
segments: Segment[] segments: Segment[]
} }

@ -3,7 +3,6 @@ import { Team } from "./Team"
export type PlayerId = string export type PlayerId = string
export interface Player { export interface Player {
readonly id: PlayerId readonly id: PlayerId
/** /**

@ -108,11 +108,17 @@ function EditorView({
const [content, setContent, saveState] = useContentState( const [content, setContent, saveState] = useContentState(
initialContent, initialContent,
isInGuestMode ? SaveStates.Guest : SaveStates.Ok, isInGuestMode ? SaveStates.Guest : SaveStates.Ok,
useMemo(() => debounceAsync((content) => useMemo(
onContentChange(content).then((success) => () =>
success ? SaveStates.Ok : SaveStates.Err, debounceAsync(
) (content) =>
, 250), [onContentChange]) onContentChange(content).then((success) =>
success ? SaveStates.Ok : SaveStates.Err,
),
250,
),
[onContentChange],
),
) )
const [allies, setAllies] = useState( const [allies, setAllies] = useState(
@ -358,7 +364,7 @@ function EditorView({
}} }}
/> />
</div> </div>
<div id="topbar-right"/> <div id="topbar-right" />
</div> </div>
<div id="edit-div"> <div id="edit-div">
<div id="racks"> <div id="racks">
@ -490,11 +496,14 @@ function getRackPlayers(team: Team, players: Player[]): RackedPlayer[] {
.map((key) => ({ team, key })) .map((key) => ({ team, key }))
} }
function debounceAsync<A, B>(f: (args: A) => Promise<B>, delay = 1000): (args: A) => Promise<B> { function debounceAsync<A, B>(
let task = 0; f: (args: A) => Promise<B>,
delay = 1000,
): (args: A) => Promise<B> {
let task = 0
return (args: A) => { return (args: A) => {
clearTimeout(task) clearTimeout(task)
return new Promise(resolve => { return new Promise((resolve) => {
task = setTimeout(() => f(args).then(resolve), delay) task = setTimeout(() => f(args).then(resolve), delay)
}) })
} }

@ -1,4 +1,4 @@
import {Action} from "../../tactic/Action" import { Action } from "../../tactic/Action"
import BendableArrow from "../../components/arrows/BendableArrow" import BendableArrow from "../../components/arrows/BendableArrow"
export interface CourtActionProps { export interface CourtActionProps {
@ -6,19 +6,16 @@ export interface CourtActionProps {
onActionChanges: (a: Action) => void onActionChanges: (a: Action) => void
} }
export function CourtAction({ export function CourtAction({ action, onActionChanges }: CourtActionProps) {
action,
onActionChanges,
}: CourtActionProps) {
return ( return (
<BendableArrow <BendableArrow
startPos={action.moveFrom} startPos={action.moveFrom}
segments={action.segments} segments={action.segments}
onSegmentsChanges={(edges) => { onSegmentsChanges={(edges) => {
onActionChanges({...action, segments: edges}) onActionChanges({ ...action, segments: edges })
}} }}
endRadius={26} //TODO place those magic values in constants
endRadius={action.toPlayerId ? 26 : 17}
startRadius={26} startRadius={26}
/> />
) )

Loading…
Cancel
Save