import konva from "konva"; import { Attrib, ShapeStyles, ShapeStylesStatus, ShapeStylesStatusKey, ShapeType, } from "../type"; import { DEV } from "../env"; import { Group } from "konva/lib/Group"; import { Layer } from "konva/lib/Layer"; import { Stage } from "konva/lib/Stage"; import { reactive, toRaw, watch, watchEffect } from "vue"; import { DragHandlers, openShapeDrag, openShapeMouseStyles, } from "../shared/shape-mose"; import { Container } from "./container"; import mitt from "mitt"; export type EntityBaseProps = { reactive?: boolean; readonly?: boolean; }; export type EntityProps = EntityBaseProps & { name?: string; attrib: T; zIndex?: number; teleport?: ShapeType; }; type ParentShapeType = T extends Layer ? Stage : T extends Stage ? null : Group; export type EntityTypeEvent = { [key in T]: R; }; export type EntityEvent = { created: void; mounted: void; destroyed: void; statusChange: Partial | null; updateAttrib: void; shapeStatusChange: { type?: "click" | "mouse"; current: ShapeStylesStatusKey; before: ShapeStylesStatusKey; }; "*": void; }; export type EntityType< T extends Attrib, S extends ShapeType, P extends EntityProps = EntityProps, K extends Entity = Entity, EK extends string = string, ER = any > = (new (props: P) => K) & { EventHandler?: { [key in EK]: ER } }; export abstract class Entity< T extends Attrib = Attrib, S extends ShapeType = ShapeType, E extends EntityEvent = EntityEvent > { props: EntityProps; bus = mitt(); container: Container< string, Attrib, ShapeType, EntityType >; attrib: T; shape: S; name: string; private zIndex: number; teleport: ParentShapeType; children: Entity[] = []; parent: Entity; constructor(props: EntityProps) { this.name = props.name; this.teleport = props.teleport as any; this.props = props; this.attrib = props.reactive ? (reactive(props.attrib) as T) : props.attrib; this.zIndex = props.zIndex || 0; } abstract initShape(): S; getShape() { return this.shape; } abstract diffRedraw(): void; redraw() { this.diffRedraw(); this.children.forEach((child) => child.redraw()); } setAttrib(newAttrib: Partial) { if (newAttrib.id !== this.attrib.id) { console.error("id 确定后无法更改"); } newAttrib.id = this.attrib.id; if (this.props.reactive) { if (toRaw(newAttrib) !== toRaw(this.attrib)) { this.attrib = ( this.props.reactive ? reactive(newAttrib) : newAttrib ) as T; this.bus.emit("updateAttrib"); this.initReactive(); } } else { this.attrib = newAttrib as T; this.diffRedraw(); } } private destoryReactive: () => void; private flush: "pre" | "post" | "sync" = "pre"; protected initReactive(flush = this.flush) { this.destoryReactive && this.destoryReactive(); let isStop = false; const stop = watchEffect( () => { isStop || this.diffRedraw(); }, { flush } ); this.flush = flush; return () => { isStop = true; stop(); }; } init() { this.shape = this.initShape(); this.shape.id(this.name); this.bus.emit("created"); } mount(tel?: ParentShapeType) { if (this.shape instanceof Stage) { throw "stage 为顶级容器无法挂载"; } else if (tel && this.shape instanceof Layer && !(tel instanceof Stage)) { console.log(this.name, tel); throw "layer 只能挂载到 Stage"; } if (this.teleport && this.teleport === tel) { return; } this.children.sort((a, b) => a.zIndex - b.zIndex); const parentShape = (tel || this.parent.shape) as Layer; parentShape.add(this.shape); this.teleport = parentShape as unknown as ParentShapeType; this.setZIndex(this.zIndex); if (this.props.reactive) { this.destoryReactive = this.initReactive(); } if (DEV) { let raw = { ...this.attrib }; watch( () => this.attrib.id, (newId, oldId) => { if (newId !== oldId) { console.error("changeId", raw, this.attrib, newId, oldId); } }, { flush: "sync" } ); } } isMounted = false; mounted() { this.diffRedraw(); this.children.forEach((child) => child.mounted()); this.isMounted = true; this.bus.emit("mounted"); } setParent(parent: Entity | null) { if (this.parent) { const ndx = this.parent.children.indexOf(this as any); ~ndx && this.parent.children.splice(ndx, 1); } this.parent = parent; if (parent) { this.parent.children.push(this as any); } } private getLevelNdx(parent = this.parent) { if (parent) { const level = parent.children; for (let i = level.length - 1; i >= 0; i--) { if (level[i] !== this && level[i].zIndex <= this.zIndex) { return i; } } return -1; } return null; } setZIndex(index: number) { this.zIndex = index; const packNdx = this.getLevelNdx(); if (packNdx === null) return; const packChild = this.parent.children; const oldNdx = packChild.indexOf(this); if (oldNdx !== packNdx + 1) { let rep: any = this; for (let i = packNdx + 1; i < packChild.length; i++) { const temp = packChild[i]; packChild[i] = rep; rep = temp; } } const parentShape = this.teleport as any; const levelShapes = parentShape.children; const shapeNdx = packNdx === -1 ? 0 : levelShapes.indexOf(packChild[packNdx].getShape()) + 1; const oldShapeNdx = levelShapes.indexOf(this.shape); if (oldShapeNdx !== shapeNdx) { if (shapeNdx !== 0) { let repShape: any = this.shape; for (let i = shapeNdx; i < levelShapes.length; i++) { const temp = levelShapes[i]; parentShape.add(repShape); repShape.zIndex(i); repShape = temp; } } else { this.shape.zIndex(0); } } } private dragDestory: () => void; enableDrag(draghandler: DragHandlers) { this.disableDrag(); this.dragDestory = openShapeDrag(this.shape, draghandler); } disableDrag() { this.dragDestory && this.dragDestory(); this.dragDestory = null; } activeDestory: () => void; enableActive(activeCallback: (isActive: boolean) => void) { this.disableActive(); this.activeDestory = openShapeMouseStyles( this.shape, { active: () => activeCallback(true), bus: this.bus as any, common: () => activeCallback(false), }, "mouse-active" ); } disableActive() { this.activeDestory && this.activeDestory(); this.activeDestory = null; } private mouseActDestory: () => void; enableMouseAct(styles: ShapeStyles) { this.disableMouseAct(); this.mouseActDestory = openShapeMouseStyles( this.shape, { ...styles, bus: this.bus as any }, "act-mouse" ); } disableMouseAct() { this.mouseActDestory && this.mouseActDestory(); this.mouseActDestory = null; } visible(visibled: boolean) { this.shape.visible(visibled); } destory() { while (this.children.length) { this.children[0].destory(); } this.setParent(null); this.destoryReactive && this.destoryReactive(); this.disableDrag(); this.disableActive(); this.disableMouseAct(); if (DEV) { console.log(this.name, "destory"); } if (this.shape instanceof konva.Group) { this.shape.destroyChildren(); } else { this.shape.destroy(); } if (this.parent) { const ndx = this.parent.children.indexOf(this); ~ndx && this.parent.children.splice(ndx, 1); } this.bus.emit("destroyed"); this.bus.off("*"); } find(name: string): Entity { for (const child of this.children) { if (child.name === name) { return child; } const childQueryed = child.find(name); if (childQueryed) { return childQueryed; } } return null; } }