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>
|
||||
)
|
||||
}
|
||||
|
@ -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 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)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in new issue