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));
}
}