Explorar el Código

fix: 优化书结构

bill hace 1 año
padre
commit
0eeffcd752

+ 29 - 0
src/board/core/base/entity-group.ts

@@ -0,0 +1,29 @@
+import { Group } from "konva/lib/Group";
+import {
+  Entity,
+  EntityProps,
+  EntityShape,
+  EntityTree,
+  EntityType,
+} from "./entity";
+import { IncEntitysFactory, incEntitysFactoryGenerate } from "./factory";
+
+export class EntityGroup<
+  T extends [],
+  C extends EntityType<T[0], EntityShape, EntityTree<EntityGroup<T, C, TR>>>,
+  TR extends EntityTree = EntityTree<never, InstanceType<C>>
+> extends Entity<T, Group, TR> {
+  private incFactory: IncEntitysFactory<EntityGroup<T, C, TR>, T[0], C>;
+
+  constructor(props: EntityProps<T> & { type: C }) {
+    super(props);
+    this.incFactory = incEntitysFactoryGenerate(
+      props.type,
+      this as EntityGroup<T, C, TR>
+    );
+  }
+
+  diffRedraw() {
+    return this.incFactory(this.attrib);
+  }
+}

+ 52 - 0
src/board/core/base/entity-map.ts

@@ -0,0 +1,52 @@
+import { Group } from "konva/lib/Group";
+import {
+  Entity,
+  EntityProps,
+  EntityShape,
+  EntityTree,
+  EntityType,
+} from "./entity";
+import { SingleEntityFactory, singleEntityFactory } from "./factory";
+
+export type MapEntityTypes<T extends {}> = {
+  [key in keyof T]: EntityType<T[key], EntityShape, EntityTree<EntityMap<T>>>;
+};
+
+export type MapEntityTree<
+  T extends {},
+  Types extends MapEntityTypes<T> = MapEntityTypes<T>
+> = EntityTree<never, InstanceType<Types[keyof T]>>;
+
+export type MapEntitys<T extends {}> = {
+  [key in keyof T]?: InstanceType<MapEntityTypes<T>[key]>;
+};
+
+export class EntityMap<
+  T extends {},
+  Types extends MapEntityTree<T> = MapEntityTree<T>
+> extends Entity<T, Group, MapEntityTree<T>> {
+  private entries: MapEntitys<T> = {};
+  private mapEntityFactory = {} as {
+    [key in keyof T]: SingleEntityFactory<
+      EntityMap<T>,
+      T[key],
+      MapEntityTypes<T>[key]
+    >;
+  };
+
+  constructor(props: EntityProps<T> & { types: MapEntityTypes<T> }) {
+    super(props);
+    for (const key in props.types) {
+      this.mapEntityFactory[key] = singleEntityFactory(
+        props.types[key],
+        this as EntityMap<T>
+      );
+    }
+  }
+
+  diffRedraw() {
+    for (const key in this.entries) {
+      // this.entries[key].parent.entries[key].parent.types;
+    }
+  }
+}

+ 33 - 0
src/board/core/base/entity-root.ts

@@ -0,0 +1,33 @@
+import { Layer } from "konva/lib/Layer";
+import { Entity, EntityEvent, EntityTree, EntityType } from "./entity";
+
+export type RootChildrenTypes<
+  T extends string = string,
+  E extends EntityType = EntityType
+> = {
+  [key in T]: E;
+};
+
+type RootChildrenAttrib<T extends RootChildrenTypes> = {
+  [key in keyof T]: T[key] extends EntityType<infer A> ? A | A[] : never;
+};
+
+export type RootEvent = EntityEvent & {
+  dataChange: void;
+  dataChangeBefore: void;
+  dataChangeAfter: void;
+  viewChange: void;
+};
+
+export class Root<CTypes extends RootChildrenTypes = any> extends Entity<
+  RootChildrenAttrib<CTypes>,
+  Layer,
+  EntityTree
+> {
+  initShape(): Layer {
+    throw new Error("Method not implemented.");
+  }
+  diffRedraw(): void {}
+
+  init() {}
+}

+ 206 - 0
src/board/core/base/entity.ts

@@ -0,0 +1,206 @@
+import { Group } from "konva/lib/Group";
+import { Layer } from "konva/lib/Layer";
+import { EntityMouseStatus } from "../event";
+import mitt from "mitt";
+import { Root } from "./entity-root";
+import { Shape } from "konva/lib/Shape";
+import { findEntity, summarizeEntity, traversEntityTree } from "./util";
+import { mergeFuns } from "../../shared";
+import { Stage } from "konva/lib/Stage";
+import { DEV } from "../../env";
+
+export type EntityTransmit = {
+  root: Root;
+};
+export type EntityShape = Layer | Group | Shape;
+
+export type EntityProps<T> = {
+  key?: string;
+  attrib: T;
+  name?: string;
+  zIndex?: number;
+  teleport?: Group | Layer;
+};
+
+export type EntityEvent = {
+  createBefore: void;
+  created: void;
+  mountBefore: void;
+  mounted: void;
+  destroyBefore: void;
+  destroyed: void;
+  updateAttrib: void;
+  updateMouseStatus: EntityMouseStatus;
+};
+
+export type EntityType<
+  T = any,
+  S extends Layer | Group | Shape = any,
+  TR extends EntityTree = EntityTree
+> = typeof Entity<T, S, TR>;
+
+export type EntityTree<
+  P extends Entity = Entity,
+  C extends Entity = Entity,
+  R extends Root = Root
+> = {
+  root: R;
+  children: C[];
+  parent: P;
+};
+
+type DEntityTree<
+  P extends Entity = any,
+  C extends Entity = any,
+  R extends Root = Root
+> = EntityTree<P, C, R>;
+
+export class Entity<
+  T = any,
+  S extends EntityShape = EntityShape,
+  TR extends EntityTree = DEntityTree
+> {
+  private zIndex: number;
+  private key: string;
+  private teleport: Group | Layer;
+
+  attrib: T;
+  shape: S;
+  name: string;
+  props: EntityProps<T>;
+  bus = mitt<EntityEvent>();
+
+  // tree
+  root: TR["root"];
+  parent: TR["parent"];
+  children: TR["children"];
+
+  constructor(props: EntityProps<T>) {
+    this.name = props.name;
+    this.teleport = props.teleport as any;
+    this.props = props;
+    this.attrib = props.attrib;
+    this.zIndex = props.zIndex || 0;
+    this.key = props.key;
+
+    if (typeof this.key !== "string") {
+      throw "entity 的key 必须为string";
+    }
+  }
+
+  initShape(): S {
+    return new Group() as S;
+  }
+  diffRedraw() {}
+
+  redraw() {
+    traversEntityTree(this, (entity) => entity.redraw(), true);
+  }
+
+  getTeleport() {
+    return this.teleport;
+  }
+
+  getKey() {
+    return this.key;
+  }
+
+  setTeleport(tel?: Group | Layer) {
+    if (this.shape instanceof Stage) {
+      throw "stage 为顶级容器无法挂载";
+    } else if (tel && this.shape instanceof Layer && !(tel instanceof Stage)) {
+      throw "layer 只能挂载到 Stage";
+    }
+    this.teleport = tel;
+  }
+
+  setAttrib(newAttrib: Partial<T>) {
+    const attribUpdated = this.attrib !== newAttrib;
+    this.attrib = newAttrib as T;
+    this.diffRedraw();
+    attribUpdated && this.bus.emit("updateAttrib");
+  }
+
+  setParent(parent: TR["parent"] | null) {
+    const ndx = this.parent.children.indexOf(this);
+    ~ndx && this.parent.children.splice(ndx, 1);
+    this.parent = parent;
+
+    parent && this.parent.children.push(this);
+  }
+
+  needReleases(): Array<() => {}> {
+    return [];
+  }
+
+  init() {
+    this.bus.emit("createBefore");
+    this.shape = this.initShape();
+    this.shape.id(this.name);
+    this.bus.emit("created");
+
+    const releases = this.needReleases();
+    if (releases.length) {
+      this.bus.on("destroyBefore", () => {
+        mergeFuns(releases)();
+      });
+    }
+  }
+
+  mount() {
+    this.bus.emit("mountBefore");
+    const parentShape = (this.teleport || this.parent.shape) as Layer;
+    parentShape.add(this.shape);
+    this.setZIndex(this.zIndex);
+  }
+
+  isMounted = false;
+  mounted() {
+    traversEntityTree(
+      this,
+      () => {
+        this.diffRedraw();
+        this.isMounted = true;
+        this.bus.emit("mounted");
+      },
+      true
+    );
+  }
+
+  getZIndex() {
+    return this.zIndex;
+  }
+
+  setZIndex(index: number) {
+    this.zIndex = index;
+    summarizeEntity(this);
+  }
+
+  visible(visibled: boolean) {
+    this.shape.visible(visibled);
+  }
+
+  destory() {
+    this.bus.emit("destroyBefore");
+    while (this.children.length) {
+      this.children[0].destory();
+    }
+    this.setParent(null);
+
+    if (DEV) {
+      console.log(this.name, "destory");
+    }
+
+    if (this.shape instanceof Group || this.shape instanceof Layer) {
+      this.shape.destroyChildren();
+    } else {
+      this.shape.destroy();
+    }
+    this.bus.emit("destroyed");
+    this.bus.off("*" as any);
+  }
+
+  find<T extends Entity>(name: string) {
+    return findEntity<T>(this, name);
+  }
+}

+ 209 - 0
src/board/core/base/factory.ts

@@ -0,0 +1,209 @@
+import { getChangePart } from "../../shared";
+import {
+  Entity,
+  EntityShape,
+  EntityTransmit,
+  EntityTree,
+  EntityType,
+} from "./entity";
+
+const getEntityTransmitProps = (parent: Entity): EntityTransmit => {
+  return {
+    root: parent.root,
+  };
+};
+
+export const entityFactory = <
+  P extends Entity,
+  T,
+  S extends EntityShape,
+  C extends EntityType<T, S, EntityTree<P>>
+>(
+  attrib: T,
+  key: string,
+  Type: C,
+  parent?: P,
+  extra?: (self: InstanceType<C>) => void,
+  created?: (self: InstanceType<C>) => void
+): InstanceType<C> => {
+  const entity = new Type({
+    attrib,
+    key,
+  }) as InstanceType<C>;
+
+  extra && extra(entity);
+
+  if (parent) {
+    const transmit = getEntityTransmitProps(parent);
+    for (const key in transmit) {
+      entity[key] = parent[key];
+    }
+    entity.setParent(parent);
+  }
+
+  entity.init();
+  entity.mount();
+  if (parent?.isMounted) {
+    entity.mounted();
+  }
+
+  created && created(entity);
+  return entity;
+};
+
+export type IncEntitys<
+  P extends Entity,
+  T,
+  C extends EntityType<T, S, EntityTree<P>>,
+  S extends EntityShape
+> = {
+  adds: InstanceType<C>[];
+  dels: InstanceType<C>[];
+  upds: InstanceType<C>[];
+};
+
+export type IncEntitysFactory<
+  P extends Entity,
+  T,
+  C extends EntityType<T, S, EntityTree<P>>,
+  S extends EntityShape = EntityShape
+> = (attribs: T[]) => IncEntitys<P, T, C, S>;
+
+// 增量工厂
+export const incEntitysFactoryGenerate = <
+  P extends Entity,
+  T,
+  S extends EntityShape,
+  C extends EntityType<T, S, EntityTree<P>>
+>(
+  Type: C,
+  parent?: P,
+  extra?: (self: InstanceType<C>) => void,
+  created?: (self: InstanceType<C>) => void
+): IncEntitysFactory<P, T, C, S> => {
+  let oldKeys: string[] = [];
+  let useIndex = false;
+  let inited = false;
+
+  const cache: { [key in string]: InstanceType<C> } = {};
+
+  const findAttrib = (attribs: any[], key: string) => {
+    if (useIndex) {
+      return attribs[key];
+    } else {
+      return attribs.find((attrib) => attrib.id === key);
+    }
+  };
+
+  const destory = (key: string) => {
+    const delEntity = cache[key];
+    delEntity.destory();
+    delete cache[key];
+    return delEntity;
+  };
+
+  const add = (attrib: T, key: string) => {
+    const addEntity = entityFactory(attrib, key, Type, parent, extra, created);
+    return (cache[key] = addEntity);
+  };
+
+  return (attribsRaw: T[]) => {
+    const attribs = attribsRaw as any[];
+    if (!inited && attribs.length && typeof attribs[0] === "object") {
+      useIndex = !!attribs[0].id;
+      inited = true;
+    }
+
+    if (
+      !useIndex &&
+      attribs.length &&
+      attribs.some(
+        (item) => typeof item !== "object" || typeof item.id !== "string"
+      )
+    ) {
+      throw "attribs 不合法,缺少id";
+    }
+    const newKeys = attribs.map((attrib, ndx) =>
+      useIndex ? ndx.toString() : attrib.id
+    );
+
+    if (new Set(newKeys).size !== newKeys.length) {
+      throw "attribs 的id不合法 不可重复";
+    }
+
+    const { addPort, delPort, holdPort } = getChangePart(newKeys, oldKeys);
+
+    const dels = delPort.map(destory);
+    const adds = addPort.map((key) => add(findAttrib(attribs, key), key));
+    const upds = holdPort.map((key) => {
+      const newAttrib = findAttrib(attribs, key);
+      cache[key].setAttrib(newAttrib);
+      return cache[key];
+    });
+    oldKeys = newKeys;
+
+    return {
+      adds,
+      dels,
+      upds,
+    };
+  };
+};
+
+export type SingleEntity<
+  P extends Entity,
+  T,
+  C extends EntityType<T, S, EntityTree<P>>,
+  S extends EntityShape
+> = {
+  add?: InstanceType<C>;
+  del?: InstanceType<C>;
+  upd?: InstanceType<C>;
+};
+
+export type SingleEntityFactory<
+  P extends Entity,
+  T,
+  C extends EntityType<T, S, EntityTree<P>>,
+  S extends EntityShape = EntityShape
+> = (data: { attrib: T; key?: string }) => SingleEntity<P, T, C, S>;
+
+export const singleEntityFactory = <
+  P extends Entity,
+  T,
+  S extends EntityShape,
+  C extends EntityType<T, S, EntityTree<P>>
+>(
+  Type: C,
+  parent?: P,
+  extra?: (self: InstanceType<C>) => void,
+  created?: (self: InstanceType<C>) => void
+): SingleEntityFactory<P, T, C, S> => {
+  let entity: InstanceType<C> | null = null;
+
+  return ({ attrib, key }) => {
+    const result: SingleEntity<P, T, C, S> = {};
+
+    if (entity) {
+      if (!key) {
+        entity.destory();
+        result.del = entity;
+        entity = null;
+      } else if (key !== entity.getKey()) {
+        console.error("entity 主键更改 销毁重建");
+        entity.destory();
+        result.del = entity;
+        result.add = entityFactory(attrib, key, Type, parent, extra, created);
+        entity = result.add;
+      } else {
+        entity.setAttrib(attrib);
+        result.upd = entity;
+      }
+    } else if (key && attrib) {
+      result.add = entityFactory(attrib, key, Type, parent, extra, created);
+      entity = result.add;
+    }
+
+    return result;
+  };
+};

+ 82 - 0
src/board/core/base/util.ts

@@ -0,0 +1,82 @@
+import { Entity } from "./entity";
+
+export const traversEntityTree = (
+  entity: Entity,
+  call: (entity: Entity, inverse: boolean) => void | "interrupt",
+  inverse: boolean | "all" = false
+) => {
+  if (!inverse || inverse === "all") {
+    if (call(entity, false) === "interrupt") return;
+  }
+  for (const child of entity.children) {
+    traversEntityTree(child, call, inverse);
+  }
+  if (inverse || inverse === "all") {
+    if (call(entity, true) === "interrupt") return;
+  }
+};
+
+export const getEntityNdx = (entity: Entity) => {
+  const parent = entity.parent;
+  if (!parent) return null;
+
+  const zIndex = entity.getZIndex();
+  const level = parent.children;
+  for (let i = level.length - 1; i >= 0; i--) {
+    if (level[i] !== this && level[i].getZIndex() <= zIndex) {
+      return i;
+    }
+  }
+  return -1;
+};
+
+export const summarizeEntity = (entity: Entity) => {
+  const packNdx = getEntityNdx(entity);
+  if (packNdx === null) return;
+
+  const packChild = entity.children;
+  const oldNdx = packChild.indexOf(this);
+
+  if (oldNdx !== packNdx + 1) {
+    let rep = entity;
+    for (let i = packNdx + 1; i < packChild.length; i++) {
+      const temp = packChild[i];
+      packChild[i] = rep;
+      rep = temp;
+    }
+  }
+
+  const parentShape = entity.getTeleport();
+  const levelShapes = parentShape.children;
+  const shapeNdx =
+    packNdx === -1 ? 0 : levelShapes.indexOf(packChild[packNdx].getShape()) + 1;
+  const oldShapeNdx = levelShapes.indexOf(entity.shape);
+
+  if (oldShapeNdx !== shapeNdx) {
+    if (shapeNdx !== 0) {
+      let repShape = entity.shape;
+      for (let i = shapeNdx; i < levelShapes.length; i++) {
+        const temp = levelShapes[i];
+        parentShape.add(repShape);
+        repShape.zIndex(i);
+        repShape = temp;
+      }
+    } else {
+      entity.shape.zIndex(0);
+    }
+  }
+};
+
+export const findEntity = <T extends Entity>(
+  entity: Entity,
+  name: string
+): T => {
+  let find: T;
+  traversEntityTree(entity, (t) => {
+    if (t.name === name) {
+      find = t as T;
+      return "interrupt";
+    }
+  });
+  return find;
+};

+ 7 - 0
src/board/core/event/index.ts

@@ -0,0 +1,7 @@
+export type EntityMouseStatus = {
+  draging: boolean;
+  hover: boolean;
+  active: boolean;
+  focus: boolean;
+  common: boolean;
+};

+ 7 - 0
src/board/packages/entity.ts

@@ -46,6 +46,7 @@ export type EntityEvent = {
   mounted: void;
   destroyed: void;
   statusChange: Partial<ShapeStylesStatus> | null;
+  updateAttrib: void;
   shapeStatusChange: {
     type?: "click" | "mouse";
     current: ShapeStylesStatusKey;
@@ -107,12 +108,17 @@ export abstract class Entity<
   }
 
   setAttrib(newAttrib: Partial<T>) {
+    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 {
@@ -161,6 +167,7 @@ export abstract class Entity<
 
     const parentShape = (tel || this.parent.shape) as Layer;
     parentShape.add(this.shape);
+
     this.teleport = parentShape as unknown as ParentShapeType<S>;
     this.setZIndex(this.zIndex);
 

+ 4 - 6
src/board/packages/whole-line/helper/whole-line-line-helper.ts

@@ -5,7 +5,7 @@ import {
 } from "../view/whole-line-line";
 import { getLineDireAngle } from "../../../shared/math";
 import { MathUtils } from "three";
-import { WholeLineAttrib } from "../view/whole-line";
+import { WholeLine } from "../view/whole-line";
 import { Entity } from "../../entity";
 import { Group } from "konva/lib/Group";
 import { Label } from "konva/lib/shapes/Label";
@@ -15,7 +15,9 @@ import { getRealAbsoluteSize } from "../../../shared";
 
 export class WholeLineLineHelper extends Entity<WholeLineLineAttrib, Group> {
   static namespace = "line-helper";
-  private config: WholeLineAttrib;
+  get config() {
+    return (this.parent as WholeLine).attrib;
+  }
 
   constructor(props: WholeLineLineProps) {
     props.zIndex = props.zIndex || 4;
@@ -23,10 +25,6 @@ export class WholeLineLineHelper extends Entity<WholeLineLineAttrib, Group> {
     super(props);
   }
 
-  setConfig(config: WholeLineAttrib) {
-    this.config = config;
-  }
-
   initShape() {
     const label = new konva.Label({
       opacity: 0.75,

+ 4 - 6
src/board/packages/whole-line/view/whole-line-line.ts

@@ -5,7 +5,7 @@ import {
   ShapeType,
 } from "../../../type";
 import { lineShapeFactory, line } from "../style";
-import { WholeLineAttrib } from "./whole-line";
+import { WholeLine, WholeLineAttrib } from "./whole-line";
 import { getWholeLinePoints } from "../service/whole-line-base";
 import { Entity, EntityProps } from "../../entity";
 import { Line } from "konva/lib/shapes/Line";
@@ -23,7 +23,9 @@ export class WholeLineLine<
   R extends ShapeType = Line
 > extends Entity<T, R> {
   static namespace = "line";
-  private config: WholeLineAttrib;
+  get config() {
+    return (this.parent as WholeLine).attrib;
+  }
 
   actShape: CustomizeShape<number[], R>;
   actShapeFactory: CustomizeShapeFactory<T, number[], R, any, {}>;
@@ -60,10 +62,6 @@ export class WholeLineLine<
     }
   }
 
-  setConfig(config: WholeLineAttrib) {
-    this.config = config;
-  }
-
   getCoords() {
     const result: number[] = [];
     const points = getWholeLinePoints(this.config, this.attrib.pointIds);

+ 4 - 6
src/board/packages/whole-line/view/whole-line-polygon.ts

@@ -1,6 +1,6 @@
 import { Attrib, CustomizeShape, CustomizeShapeFactory } from "../../../type";
 import { polygonShapeFactory, polygon } from "../style";
-import { WholeLineAttrib } from "./whole-line";
+import { WholeLine, WholeLineAttrib } from "./whole-line";
 import { getWholeLinePolygonPoints } from "../service/whole-line-base";
 import { Entity, EntityProps } from "../../entity";
 import { Line } from "konva/lib/shapes/Line";
@@ -20,7 +20,9 @@ export class WholeLinePolygon<
   R extends Shape | Group = Line
 > extends Entity<T, R> {
   static namespace = "polygon";
-  private config: WholeLineAttrib;
+  get config() {
+    return (this.parent as WholeLine).attrib;
+  }
 
   actShape: CustomizeShape<number[], R>;
   actShapeFactory: CustomizeShapeFactory<T, number[], R>;
@@ -47,10 +49,6 @@ export class WholeLinePolygon<
     this.actShape.setData(this.getCoords());
   }
 
-  setConfig(config: WholeLineAttrib) {
-    this.config = config;
-  }
-
   getCoords() {
     const result: number[] = [];
     const points = getWholeLinePolygonPoints(this.config, this.attrib.id);

+ 3 - 12
src/board/packages/whole-line/view/whole-line.ts

@@ -86,10 +86,7 @@ export class WholeLine<
   initIncFactory() {
     this.incLinesFactory = incEntitysFactoryGenerate(
       WholeLineLine<WLL<W>, LS>,
-      this,
-      (line) => {
-        line.setConfig(this.attrib);
-      }
+      this
     );
     this.incPointsFactory = incEntitysFactoryGenerate(
       WholeLinePoint<WLP<W>, PS>,
@@ -98,19 +95,13 @@ export class WholeLine<
 
     this.incPolygonFactory = incEntitysFactoryGenerate(
       WholeLinePolygon<WLPY<W>, PYS>,
-      this,
-      (py) => {
-        py.setConfig(this.attrib);
-      }
+      this
     );
 
     if (DEV) {
       this.incLinesHelperFactory = incEntitysFactoryGenerate(
         WholeLineLineHelper,
-        this,
-        (line) => {
-          line.setConfig(this.attrib);
-        }
+        this
       );
       this.incPointsHelperFactory = incEntitysFactoryGenerate(
         WholeLinePointHelper,

+ 1 - 1
src/board/type.ts

@@ -42,7 +42,7 @@ export type ShapeStyles = {
 };
 
 export type Attrib = {
-  id: string;
+  id?: string;
 };
 
 export type CustomizeShape<T, R = Shape | Group, Attach = {}> = {