import * as React from "react"; import { MOVEABLE_CSS, PREFIX } from "./consts"; import { prefix, getLineStyle, getTargetInfo, unset, createIdentityMatrix3, isInside, getAbsolutePosesByState, getRect, filterAbles, } from "./utils"; import styler from "react-css-styler"; import Dragger from "@daybrush/drag"; import { ref } from "framework-utils"; import { MoveableManagerProps, MoveableManagerState, Able, RectInfo } from "./types"; import { getAbleDragger } from "./getAbleDragger"; import CustomDragger from "./CustomDragger"; const ControlBoxElement = styler("div", MOVEABLE_CSS); function renderLine(direction: string, pos1: number[], pos2: number[], index: number) { return
; } export default class MoveableManager extends React.PureComponent, MoveableManagerState> { public static defaultProps: Required = { target: null, container: null, origin: true, keepRatio: false, edge: false, parentMoveable: null, parentPosition: null, ables: [], pinchThreshold: 20, dragArea: false, transformOrigin: "", className: "", }; public state: MoveableManagerState = { conatainer: null, target: null, beforeMatrix: createIdentityMatrix3(), matrix: createIdentityMatrix3(), targetMatrix: createIdentityMatrix3(), targetTransform: "", is3d: false, left: 0, top: 0, width: 0, height: 0, transformOrigin: [0, 0], direction: 1, beforeDirection: 1, beforeOrigin: [0, 0], origin: [0, 0], pos1: [0, 0], pos2: [0, 0], pos3: [0, 0], pos4: [0, 0], clientRect: { left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0 }, containerRect: { left: 0, top: 0, bottom: 0, right: 0, width: 0, height: 0 }, } as any; public targetAbles: Array> = []; public controlAbles: Array> = []; public controlBox!: typeof ControlBoxElement extends new (...args: any[]) => infer K ? K : never; public areaElement!: HTMLElement; public targetDragger!: Dragger; public controlDragger!: Dragger; public customDragger!: CustomDragger; public render() { const { edge, parentPosition, className } = this.props; this.checkUpdate(); const { left: parentLeft, top: parentTop } = parentPosition! || { left: 0, top: 0 }; const { left, top, pos1, pos2, pos3, pos4, target, direction } = this.state; return ( {this.renderAbles()} {renderLine(edge ? "n" : "", pos1, pos2, 0)} {renderLine(edge ? "e" : "", pos2, pos4, 1)} {renderLine(edge ? "w" : "", pos1, pos3, 2)} {renderLine(edge ? "s" : "", pos3, pos4, 3)} ); } public componentDidMount() { this.controlBox.getElement(); const props = this.props; const { parentMoveable, container } = props; this.updateEvent(props); if (!container && !parentMoveable) { this.updateRect("End", false, true); } } public componentDidUpdate(prevProps: MoveableManagerProps) { this.updateEvent(prevProps); } public componentWillUnmount() { unset(this, "targetDragger"); unset(this, "controlDragger"); } public getContainer(): HTMLElement | SVGElement | null { const { parentMoveable, container } = this.props; return container! || (parentMoveable && parentMoveable.getContainer()) || this.controlBox.getElement().offsetParent as HTMLElement; } public isMoveableElement(target: HTMLElement | SVGElement) { return target && ((target.getAttribute("class") || "").indexOf(PREFIX) > -1); } public dragStart(e: MouseEvent | TouchEvent) { if (this.targetDragger) { this.targetDragger.onDragStart(e); } } public isInside(clientX: number, clientY: number) { const { pos1, pos2, pos3, pos4, target } = this.state; if (!target) { return false; } const { left, top } = target.getBoundingClientRect(); const pos = [clientX - left, clientY - top]; return isInside(pos, pos1, pos2, pos4, pos3); } public updateRect(type?: "Start" | "" | "End", isTarget?: boolean, isSetState: boolean = true) { const parentMoveable = this.props.parentMoveable; const state = this.state; const target = (state.target || this.props.target) as HTMLElement | SVGElement; const container = this.getContainer(); this.updateState( getTargetInfo(target, container, container, isTarget ? state : undefined), parentMoveable ? false : isSetState, ); } public updateEvent(prevProps: MoveableManagerProps) { const controlBoxElement = this.controlBox.getElement(); const hasTargetAble = this.targetAbles.length; const hasControlAble = this.controlAbles.length; const target = this.props.target; const prevTarget = prevProps.target; const dragArea = this.props.dragArea; const prevDragArea = prevProps.dragArea; const isTargetChanged = !dragArea && prevTarget !== target; const isUnset = (!hasTargetAble && this.targetDragger) || isTargetChanged || prevDragArea !== dragArea; if (isUnset) { unset(this, "targetDragger"); this.updateState({ dragger: null }); } if (!hasControlAble) { unset(this, "controlDragger"); } if (target && hasTargetAble && !this.targetDragger) { if (dragArea) { this.targetDragger = getAbleDragger(this, this.areaElement!, "targetAbles", ""); } else { this.targetDragger = getAbleDragger(this, target!, "targetAbles", ""); } } if (!this.controlDragger && hasControlAble) { this.controlDragger = getAbleDragger(this, controlBoxElement, "controlAbles", "Control"); } if (isUnset) { this.unsetAbles(); } } public updateTarget(type?: "Start" | "" | "End") { this.updateRect(type, true); } public getRect(): RectInfo { const poses = getAbsolutePosesByState(this.state); const [pos1, pos2, pos3, pos4] = poses; const rect = getRect(poses); const { width, height, left, top, } = rect; return { width, height, left, top, pos1, pos2, pos3, pos4, }; } public checkUpdate() { const { target, container, parentMoveable } = this.props; const { target: stateTarget, container: stateContainer, } = this.state; if (!stateTarget && !target) { return; } this.updateAbles(); const isChanged = stateTarget !== target || stateContainer !== container; if (!isChanged) { return; } this.updateState({ target, container }); if (!parentMoveable && (container || this.controlBox)) { this.updateRect("End", false, false); } } public triggerEvent(name: string, e: any): any { const callback = (this.props as any)[name]; return callback && callback(e); } protected unsetAbles() { if (this.targetAbles.filter(able => { if (able.unset) { able.unset(this); return true; } return false; }).length) { this.forceUpdate(); } } protected updateAbles( ables: Able[] = this.props.ables!, eventAffix: string = "", ) { const props = this.props as any; const enabledAbles = ables!.filter(able => able && props[able.name]); const dragStart = `drag${eventAffix}Start` as "dragStart"; const pinchStart = `pinch${eventAffix}Start` as "pinchStart"; const dragControlStart = `drag${eventAffix}ControlStart` as "dragControlStart"; const targetAbles = filterAbles(enabledAbles, [dragStart, pinchStart]); const controlAbles = filterAbles(enabledAbles, [dragControlStart]); this.targetAbles = targetAbles; this.controlAbles = controlAbles; } protected updateState(nextState: any, isSetState?: boolean) { if (isSetState) { this.setState(nextState); } else { const state = this.state as any; for (const name in nextState) { state[name] = nextState[name]; } } } protected renderAbles() { const props = this.props as any; const ables: Able[] = props.ables!; const enabledAbles = ables.filter(able => able && props[able.name]); return filterAbles(enabledAbles, ["render"]).map(({ render }) => render!(this, React)); } }