make straight lines

pull/82/head
Override-6 2 years ago committed by maxime.batista
parent de8fbdb3a2
commit df3e60ca1a

@ -1,52 +1,47 @@
import "../../style/actions/arrow_action.css"
import Draggable from "react-draggable"
import {RefObject, useRef} from "react"
import Xarrow, {useXarrow, Xwrapper} from "react-xarrows"
import {useRef} from "react"
export interface ArrowActionProps {
originRef: RefObject<HTMLDivElement>
onArrowDropped: (arrowHead: DOMRect) => void
onHeadDropped: (headBounds: DOMRect) => void
onHeadPicked: (headBounds: DOMRect) => void,
onHeadMoved: (headBounds: DOMRect) => void,
}
export default function ArrowAction({
originRef,
onArrowDropped,
onHeadDropped,
onHeadPicked,
onHeadMoved
}: ArrowActionProps) {
const arrowHeadRef = useRef<HTMLDivElement>(null)
const updateXarrow = useXarrow()
return (
<div className="arrow-action">
<div className="arrow-action-pin"/>
<Xwrapper>
<Draggable
nodeRef={arrowHeadRef}
onDrag={updateXarrow}
onStart={() => {
const headBounds =
arrowHeadRef.current!.getBoundingClientRect()
onHeadPicked(headBounds)
}}
onStop={() => {
const headBounds =
arrowHeadRef.current!.getBoundingClientRect()
updateXarrow()
onArrowDropped(headBounds)
onHeadDropped(headBounds)
}}
onDrag={() => {
const headBounds =
arrowHeadRef.current!.getBoundingClientRect()
onHeadMoved(headBounds)
}}
position={{x: 0, y: 0}}>
<div
style={{
position: "absolute",
}}
ref={arrowHeadRef}
className="arrow-head-pick"
onMouseDown={updateXarrow}/>
className="arrow-head-pick"/>
</Draggable>
<div className={"arrow-head-xarrow"}>
<Xarrow
start={originRef}
end={arrowHeadRef}
startAnchor={"auto"}
/>
</div>
</Xwrapper>
</div>
)
}

@ -1,4 +1,6 @@
import {BallPiece} from "../editor/BallPiece";
import Draggable from "react-draggable";
import {useRef} from "react";
export interface BallActionProps {
@ -6,7 +8,15 @@ export interface BallActionProps {
}
export default function BallAction({onDrop}: BallActionProps) {
const ref = useRef<HTMLDivElement>(null)
return (
<BallPiece onDrop={onDrop} />
<Draggable
onStop={() => onDrop(ref.current!)}
nodeRef={ref}
>
<div ref={ref}>
<BallPiece/>
</div>
</Draggable>
)
}

@ -0,0 +1,81 @@
import { CSSProperties, ReactElement, useCallback, useEffect, useRef } from "react"
import { add, Pos, relativeTo, size } from "./Pos"
export interface BendableArrowProps {
basePos: Pos
startPos: Pos
endPos: Pos
startRadius?: number
endRadius?: number
style?: ArrowStyle
}
export interface ArrowStyle {
width?: number,
head?: () => ReactElement,
tail?: () => ReactElement,
}
const ArrowStyleDefaults = {
width: 4
}
export default function BendableArrow({ basePos, startPos, endPos, style, startRadius = 0, endRadius = 0 }: BendableArrowProps) {
const svgRef = useRef<SVGSVGElement>(null)
const pathRef = useRef<SVGPathElement>(null);
const styleWidth = style?.width ?? ArrowStyleDefaults.width
const update = () => {
const startRelative = relativeTo(startPos, basePos)
const endRelative = relativeTo(endPos, basePos)
// the width and height of the arrow svg
const svgBoxBounds = size(startPos, endPos)
const left = Math.min(startRelative.x, endRelative.x)
const top = Math.min(startRelative.y, endRelative.y)
const svgStyle: CSSProperties = {
width: `${svgBoxBounds.x}px`,
height: `${svgBoxBounds.y}px`,
left: `${left}px`,
top: `${top}px`,
}
const d = `M${startRelative.x - left} ${startRelative.y - top} L${endRelative.x - left} ${endRelative.y - top}`
pathRef.current!.setAttribute("d", d)
Object.assign(svgRef.current!.style, svgStyle)
}
useEffect(() => {
//update on resize
window.addEventListener('resize', update)
return () => window.removeEventListener('resize', update)
}, [svgRef, basePos, startPos, endPos])
//update on position changes
useEffect(update, [svgRef, basePos, startPos, endPos])
return (
<svg ref={svgRef} style={{
overflow: "visible",
position: "absolute",
}}>
<path
ref={pathRef}
stroke={"#000"}
strokeWidth={styleWidth} />
</svg>
)
}

@ -0,0 +1,36 @@
export interface Pos {
x: number
y: number
}
export const NULL_POS: Pos = { x: 0, y: 0 }
/**
* Returns position of a relative to b
* @param a
* @param b
*/
export function relativeTo(a: Pos, b: Pos): Pos {
return { x: a.x - b.x, y: a.y - b.y }
}
/**
* Returns the middle position of the given rectangle
* @param rect
*/
export function middlePos(rect: DOMRect): Pos {
return { x: rect.x + rect.width / 2, y: rect.y + rect.height / 2 }
}
/**
* Returns x and y distance between given two pos
* @param a
* @param b
*/
export function size(a: Pos, b: Pos): Pos {
return { x: Math.abs(a.x - b.x), y: Math.abs(a.y - b.y) }
}
export function add(a: Pos, b: Pos): Pos {
return { x: a.x + b.x, y: a.y + b.y }
}

@ -1,23 +1,27 @@
import "../../style/basket_court.css"
import {ReactElement, RefObject} from "react"
import {ReactElement, RefObject, useState} from "react"
import CourtPlayer from "./CourtPlayer"
import {Player} from "../../tactic/Player"
import {Action, MovementActionKind} from "../../tactic/Action"
import RemoveAction from "../actions/RemoveAction"
import ArrowAction from "../actions/ArrowAction"
import {useXarrow} from "react-xarrows"
import BallAction from "../actions/BallAction";
import BendableArrow from "../arrows/BendableArrow"
import {middlePos, NULL_POS, Pos} from "../arrows/Pos"
import {CourtObject} from "../../tactic/CourtObjects";
import {CourtBall} from "./CourtBall";
import BallAction from "../actions/BallAction";
export interface BasketCourtProps {
players: Player[]
actions: Action[]
objects: CourtObject[]
renderAction: (a: Action) => ReactElement
renderAction: (courtBounds: DOMRect, a: Action, idx: number) => ReactElement
setActions: (f: (a: Action[]) => Action[]) => void
onPlayerRemove: (p: Player) => void
onBallDrop: (ref: HTMLElement) => void
onPlayerChange: (p: Player) => void
onBallRemove: () => void
@ -29,25 +33,22 @@ export interface BasketCourtProps {
}
export function BasketCourt({
players,
objects,
onBallMoved,
onBallRemove,
players,
actions,
renderAction,
setActions,
onBallDrop,
onPlayerRemove,
onBallRemove,
onBallMoved,
onPlayerChange,
courtImage,
courtRef,
}: BasketCourtProps) {
function bindArrowToPlayer(
originRef: RefObject<HTMLDivElement>,
arrowHead: DOMRect,
) {
function bindArrowToPlayer(originRef: HTMLElement, arrowHead: DOMRect) {
for (const player of players) {
if (player.id == originRef.current!.id) {
if (player.id == originRef.id) {
continue
}
@ -65,7 +66,7 @@ export function BasketCourt({
) {
const action = {
type: MovementActionKind.SCREEN,
moveFrom: originRef.current!.id,
moveFrom: originRef.id,
moveTo: player.id,
}
setActions((actions) => [...actions, action])
@ -75,9 +76,18 @@ export function BasketCourt({
const updateArrows = useXarrow()
const [previewArrowOriginPos, setPreviewArrowOriginPos] =
useState<Pos>(NULL_POS)
const [previewArrowEndPos, setPreviewArrowEndPos] = useState<Pos>(NULL_POS)
const [isPreviewArrowEnabled, setPreviewArrowEnabled] = useState(false)
return (
<div id="court-container" ref={courtRef} style={{position: "relative"}}>
<img src={courtImage} alt={"court"} id="court-svg"/>
{actions.map((action, idx) => renderAction(courtRef.current!.getBoundingClientRect(), action, idx))}
{players.map((player) => (
<CourtPlayer
key={player.id}
@ -93,12 +103,22 @@ export function BasketCourt({
/>,
<ArrowAction
key={2}
originRef={pieceRef}
onArrowDropped={(headRect) =>
bindArrowToPlayer(pieceRef, headRect)
onHeadMoved={(headPos) =>
setPreviewArrowEndPos(middlePos(headPos))
}
onHeadPicked={(headRef) => {
setPreviewArrowOriginPos(
middlePos(pieceRef.getBoundingClientRect()),
)
setPreviewArrowEndPos(middlePos(headRef))
setPreviewArrowEnabled(true)
}}
onHeadDropped={(headRect) => {
setPreviewArrowEnabled(false)
bindArrowToPlayer(pieceRef, headRect)
}}
/>,
player.hasBall && <BallAction key={3} onDrop={onBallDrop}/>
player.hasBall && <BallAction key={3} onDrop={ref => onBallMoved(ref.getBoundingClientRect())}/>
]}
/>
))}
@ -117,7 +137,13 @@ export function BasketCourt({
throw new Error("unknown court object", object.type)
})}
{actions.map(renderAction)}
{isPreviewArrowEnabled && (
<BendableArrow
basePos={courtRef.current!.getBoundingClientRect()}
startPos={previewArrowOriginPos}
endPos={previewArrowEndPos}
/>
)}
</div>
)
}

@ -11,8 +11,8 @@ export interface PlayerProps<A extends ReactNode> {
onDrag: () => void,
onChange: (p: Player) => void
onRemove: () => void
parentRef: RefObject<HTMLDivElement>
availableActions: (ro: RefObject<HTMLDivElement>) => A[]
parentRef: RefObject<HTMLElement>
availableActions: (ro: HTMLElement) => A[]
}
/**
@ -66,7 +66,7 @@ export default function CourtPlayer<A extends ReactNode>({
onKeyUp={(e) => {
if (e.key == "Delete") onRemove()
}}>
<div className="player-actions">{availableActions(pieceRef)}</div>
<div className="player-actions">{availableActions(pieceRef.current!)}</div>
<PlayerPiece team={player.team} text={player.role} hasBall={hasBall} />
</div>
</div>

@ -18,16 +18,16 @@
}
.arrow-head-xarrow {
visibility: hidden;
visibility: visible;
}
.arrow-action:active .arrow-head-xarrow {
visibility: visible;
}
.arrow-action:active .arrow-head-pick {
min-height: unset;
min-width: unset;
width: 0;
height: 0;
}
/*.arrow-action:active .arrow-head-pick {*/
/* min-height: unset;*/
/* min-width: unset;*/
/* width: 0;*/
/* height: 0;*/
/*}*/

@ -18,7 +18,7 @@ import {Team} from "../tactic/Team"
import {calculateRatio} from "../Utils"
import SavingState, {SaveState, SaveStates,} from "../components/editor/SavingState"
import {renderAction} from "./editor/ActionsRender"
import {ActionRender} from "./editor/ActionsRender"
import {CourtObject} from "../tactic/CourtObjects"
@ -417,12 +417,13 @@ function EditorView({
courtRef={courtDivContentRef}
actions={content.actions}
setActions={(actions) =>
setContent((c) => ({
players: c.players,
actions: actions(c.actions),
setContent((content) => ({
...content,
players: content.players,
actions: actions(content.actions),
}))
}
renderAction={renderAction}
renderAction={(basePos, action, idx) => <ActionRender key={idx} basePos={basePos} action={action}/>}
onPlayerChange={(player) => {
const playerBounds = document
.getElementById(player.id)!

@ -1,35 +1,24 @@
import { Action, MovementActionKind } from "../../tactic/Action"
import Xarrow, { Xwrapper } from "react-xarrows"
import { xarrowPropsType } from "react-xarrows/lib/types"
import BendableArrow from "../../components/arrows/BendableArrow"
import { middlePos, Pos } from "../../components/arrows/Pos"
export function renderAction(action: Action) {
export function ActionRender({basePos, action}: {basePos: Pos, action: Action}) {
const from = action.moveFrom;
const to = action.moveTo;
let arrowStyle: xarrowPropsType = {start: from, end: to, color: "var(--arrows-color)"}
switch (action.type) {
case MovementActionKind.DRIBBLE:
arrowStyle.dashness = {
animation: true,
strokeLen: 5,
nonStrokeLen: 5
}
break
case MovementActionKind.SCREEN:
arrowStyle.headShape = "circle"
arrowStyle.headSize = 2.5
break
case MovementActionKind.MOVE:
}
const fromPos = document.getElementById(from)!.getBoundingClientRect()
const toPos = document.getElementById(to)!.getBoundingClientRect()
return (
<Xwrapper key={`${action.type}-${from}-${to}`}>
<Xarrow {...arrowStyle}/>
</Xwrapper>
<BendableArrow
key={`${action.type}-${from}-${to}`}
basePos={basePos}
startPos={middlePos(fromPos)}
endPos={middlePos(toPos)}
/>
)
}

@ -33,6 +33,7 @@
},
"devDependencies": {
"@vitejs/plugin-react": "^4.1.0",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^3.1.0",
"typescript": "^5.2.2",
"vite-plugin-svgr": "^4.1.0"

Loading…
Cancel
Save