parent
de8fbdb3a2
commit
df3e60ca1a
@ -1,52 +1,47 @@
|
|||||||
import "../../style/actions/arrow_action.css"
|
import "../../style/actions/arrow_action.css"
|
||||||
import Draggable from "react-draggable"
|
import Draggable from "react-draggable"
|
||||||
import {RefObject, useRef} from "react"
|
|
||||||
import Xarrow, {useXarrow, Xwrapper} from "react-xarrows"
|
import {useRef} from "react"
|
||||||
|
|
||||||
export interface ArrowActionProps {
|
export interface ArrowActionProps {
|
||||||
originRef: RefObject<HTMLDivElement>
|
onHeadDropped: (headBounds: DOMRect) => void
|
||||||
onArrowDropped: (arrowHead: DOMRect) => void
|
onHeadPicked: (headBounds: DOMRect) => void,
|
||||||
|
onHeadMoved: (headBounds: DOMRect) => void,
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ArrowAction({
|
export default function ArrowAction({
|
||||||
originRef,
|
onHeadDropped,
|
||||||
onArrowDropped,
|
onHeadPicked,
|
||||||
|
onHeadMoved
|
||||||
}: ArrowActionProps) {
|
}: ArrowActionProps) {
|
||||||
const arrowHeadRef = useRef<HTMLDivElement>(null)
|
const arrowHeadRef = useRef<HTMLDivElement>(null)
|
||||||
const updateXarrow = useXarrow()
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="arrow-action">
|
<div className="arrow-action">
|
||||||
<div className="arrow-action-pin"/>
|
<div className="arrow-action-pin"/>
|
||||||
|
|
||||||
<Xwrapper>
|
|
||||||
<Draggable
|
<Draggable
|
||||||
nodeRef={arrowHeadRef}
|
nodeRef={arrowHeadRef}
|
||||||
onDrag={updateXarrow}
|
onStart={() => {
|
||||||
|
const headBounds =
|
||||||
|
arrowHeadRef.current!.getBoundingClientRect()
|
||||||
|
onHeadPicked(headBounds)
|
||||||
|
}}
|
||||||
onStop={() => {
|
onStop={() => {
|
||||||
const headBounds =
|
const headBounds =
|
||||||
arrowHeadRef.current!.getBoundingClientRect()
|
arrowHeadRef.current!.getBoundingClientRect()
|
||||||
updateXarrow()
|
onHeadDropped(headBounds)
|
||||||
onArrowDropped(headBounds)
|
}}
|
||||||
|
onDrag={() => {
|
||||||
|
const headBounds =
|
||||||
|
arrowHeadRef.current!.getBoundingClientRect()
|
||||||
|
onHeadMoved(headBounds)
|
||||||
}}
|
}}
|
||||||
position={{x: 0, y: 0}}>
|
position={{x: 0, y: 0}}>
|
||||||
<div
|
<div
|
||||||
style={{
|
|
||||||
position: "absolute",
|
|
||||||
}}
|
|
||||||
ref={arrowHeadRef}
|
ref={arrowHeadRef}
|
||||||
className="arrow-head-pick"
|
className="arrow-head-pick"/>
|
||||||
onMouseDown={updateXarrow}/>
|
|
||||||
</Draggable>
|
</Draggable>
|
||||||
|
|
||||||
<div className={"arrow-head-xarrow"}>
|
|
||||||
<Xarrow
|
|
||||||
start={originRef}
|
|
||||||
end={arrowHeadRef}
|
|
||||||
startAnchor={"auto"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</Xwrapper>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,35 +1,24 @@
|
|||||||
import { Action, MovementActionKind } from "../../tactic/Action"
|
import { Action, MovementActionKind } from "../../tactic/Action"
|
||||||
import Xarrow, { Xwrapper } from "react-xarrows"
|
import Xarrow, { Xwrapper } from "react-xarrows"
|
||||||
import { xarrowPropsType } from "react-xarrows/lib/types"
|
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 from = action.moveFrom;
|
||||||
const to = action.moveTo;
|
const to = action.moveTo;
|
||||||
|
|
||||||
let arrowStyle: xarrowPropsType = {start: from, end: to, color: "var(--arrows-color)"}
|
const fromPos = document.getElementById(from)!.getBoundingClientRect()
|
||||||
|
const toPos = document.getElementById(to)!.getBoundingClientRect()
|
||||||
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:
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Xwrapper key={`${action.type}-${from}-${to}`}>
|
<BendableArrow
|
||||||
<Xarrow {...arrowStyle}/>
|
key={`${action.type}-${from}-${to}`}
|
||||||
</Xwrapper>
|
basePos={basePos}
|
||||||
|
startPos={middlePos(fromPos)}
|
||||||
|
endPos={middlePos(toPos)}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in new issue