|
@@ -3,6 +3,14 @@ import { Entity } from "../base/entity";
|
|
|
import { Root } from "./entity-root";
|
|
|
import { Pos } from "../type";
|
|
|
import { Transform } from "konva/lib/Util";
|
|
|
+import {
|
|
|
+ canEntityReply,
|
|
|
+ onEntity,
|
|
|
+ openOnEntityTree,
|
|
|
+ TreeEvent,
|
|
|
+} from "../event";
|
|
|
+import { findEntityByShape } from "./entity-server";
|
|
|
+import { debounce, getChangePart, mergeFuns } from "../../shared";
|
|
|
|
|
|
// 指定某些entity可编辑
|
|
|
export const openOnlyMode = (
|
|
@@ -10,31 +18,38 @@ export const openOnlyMode = (
|
|
|
_entitys: Entity[],
|
|
|
includeNews = true
|
|
|
) => {
|
|
|
- const prevRootReply = root.replyEvents;
|
|
|
-
|
|
|
const entitys: (Entity | null)[] = [];
|
|
|
const prevEntitysReply: Entity["replyEvents"][] = [];
|
|
|
- const pushEntity = (entity: Entity) => {
|
|
|
+ const setEntityReply = (entity: Entity, reply: Entity["replyEvents"]) => {
|
|
|
const ndx = entitys.length;
|
|
|
entitys[ndx] = entity;
|
|
|
prevEntitysReply[ndx] = entity.replyEvents;
|
|
|
- entity.replyEvents = "all";
|
|
|
+ entity.replyEvents = reply;
|
|
|
};
|
|
|
- const delEntity = (entity: Entity) => {
|
|
|
+
|
|
|
+ root.children.forEach((item) => setEntityReply(item, "none"));
|
|
|
+ _entitys.forEach((item) => setEntityReply(item, "all"));
|
|
|
+
|
|
|
+ const delHandler = (entity: Entity) => {
|
|
|
const ndx = entitys.indexOf(entity);
|
|
|
prevEntitysReply[ndx] = null;
|
|
|
entitys[ndx] = null;
|
|
|
};
|
|
|
|
|
|
- _entitys.forEach(pushEntity);
|
|
|
- // 新增的entity可以使用事件
|
|
|
- includeNews && root.bus.on("addEntity", pushEntity);
|
|
|
- root.bus.on("delEntity", delEntity);
|
|
|
+ const addHandler = (entity: Entity) => {
|
|
|
+ if (includeNews) {
|
|
|
+ setEntityReply(entity, "all");
|
|
|
+ } else if (root.children.includes(entity)) {
|
|
|
+ setEntityReply(entity, "none");
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ root.bus.on("delEntity", delHandler);
|
|
|
+ root.bus.on("addEntity", addHandler);
|
|
|
|
|
|
return () => {
|
|
|
- root.bus.off("addEntity", pushEntity);
|
|
|
- root.bus.off("delEntity", delEntity);
|
|
|
- root.replyEvents = prevRootReply;
|
|
|
+ root.bus.off("delEntity", delHandler);
|
|
|
+ root.bus.off("addEntity", addHandler);
|
|
|
entitys.forEach((entity, ndx) => {
|
|
|
if (entity) {
|
|
|
entity.replyEvents = prevEntitysReply[ndx];
|
|
@@ -44,76 +59,152 @@ export const openOnlyMode = (
|
|
|
};
|
|
|
|
|
|
export type EditModeProps = {
|
|
|
- entitys: Entity[];
|
|
|
- only: boolean;
|
|
|
+ entitys?: Entity[];
|
|
|
+ only?: boolean;
|
|
|
includeNews?: boolean;
|
|
|
};
|
|
|
-export const openEditMode = async (
|
|
|
+export type EditModeChange = {
|
|
|
+ addEntitys?: Entity[];
|
|
|
+ setEntitys?: Entity[];
|
|
|
+ delEntitys?: Entity[];
|
|
|
+};
|
|
|
+
|
|
|
+export const openEditMode = (
|
|
|
root: Root,
|
|
|
main: () => any,
|
|
|
- props: EditModeProps = { entitys: [], only: false, includeNews: false }
|
|
|
+ props: EditModeProps = {
|
|
|
+ entitys: [],
|
|
|
+ only: false,
|
|
|
+ includeNews: false,
|
|
|
+ }
|
|
|
) => {
|
|
|
- root.bus.emit("dataChangeBefore");
|
|
|
+ if (!props.entitys) props.entitys = [];
|
|
|
+ if (!props.only) props.only = false;
|
|
|
+ if (!props.includeNews) props.includeNews = false;
|
|
|
+
|
|
|
+ root.bus.emit("entityChangeBefore");
|
|
|
const quitOnlyMode =
|
|
|
props.only && openOnlyMode(root, props.entitys, props.includeNews);
|
|
|
|
|
|
- const addEntitys = [];
|
|
|
- const setEntitys = [];
|
|
|
- const delEntitys = [];
|
|
|
- const addHandler = (entity: Entity) => {
|
|
|
- addEntitys.push(entity);
|
|
|
+ let state = "normal";
|
|
|
+ let resolve: () => void;
|
|
|
+ const interrupt = () => {
|
|
|
+ state = "interrupt";
|
|
|
+ resolve();
|
|
|
};
|
|
|
- const setHandler = (entity: Entity) => {
|
|
|
- if (
|
|
|
- !addEntitys.includes(entity) &&
|
|
|
- !delEntitys.includes(entity) &&
|
|
|
- !setEntitys.includes(entity)
|
|
|
- ) {
|
|
|
- setEntitys.push(entity);
|
|
|
+
|
|
|
+ const destoryAutoEmit = root.history && autoEmitDataChange(root);
|
|
|
+
|
|
|
+ const interruptPromise = new Promise<void>((r) => (resolve = r));
|
|
|
+ const draw = Promise.any([interruptPromise, Promise.resolve(main())]).then(
|
|
|
+ () => {
|
|
|
+ quitOnlyMode && quitOnlyMode();
|
|
|
+ destoryAutoEmit && destoryAutoEmit();
|
|
|
+ setTimeout(() => {
|
|
|
+ root.bus.emit("entityChangeAfter");
|
|
|
+ });
|
|
|
+ return { state };
|
|
|
}
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ draw,
|
|
|
+ interrupt,
|
|
|
};
|
|
|
- const delHandler = (entity: Entity) => {
|
|
|
- const delNdx = delEntitys.indexOf(entity);
|
|
|
- if (~delNdx) return;
|
|
|
+};
|
|
|
|
|
|
- const addNdx = addEntitys.indexOf(entity);
|
|
|
- if (~addNdx) addEntitys.splice(addNdx, 1);
|
|
|
+export const openEditModePacking = (root: Root, props?: EditModeProps) => {
|
|
|
+ let complete: () => void;
|
|
|
+ const { interrupt } = openEditMode(
|
|
|
+ root,
|
|
|
+ () => {
|
|
|
+ return new Promise<void>((r) => (complete = r));
|
|
|
+ },
|
|
|
+ props
|
|
|
+ );
|
|
|
+ return {
|
|
|
+ interrupt,
|
|
|
+ complete,
|
|
|
+ };
|
|
|
+};
|
|
|
|
|
|
- const setNdx = setEntitys.indexOf(entity);
|
|
|
- if (~setNdx) setEntitys.splice(setNdx, 1);
|
|
|
+const autoedRoots = new WeakMap<
|
|
|
+ Root,
|
|
|
+ {
|
|
|
+ quete: number;
|
|
|
+ destory: () => void;
|
|
|
+ pause: () => void;
|
|
|
+ continue: () => void;
|
|
|
+ }
|
|
|
+>();
|
|
|
+export const hasAutoEmitDataChange = (root: Root) => {
|
|
|
+ return autoedRoots.has(root);
|
|
|
+};
|
|
|
+export const pauseAutoEmitDataChange = (root: Root) => {
|
|
|
+ if (autoedRoots.has(root)) {
|
|
|
+ autoedRoots.get(root).pause();
|
|
|
+ }
|
|
|
+};
|
|
|
+export const continueAutoEmitDataChange = (root: Root) => {
|
|
|
+ if (autoedRoots.has(root)) {
|
|
|
+ autoedRoots.get(root).continue();
|
|
|
+ }
|
|
|
+};
|
|
|
+export const autoEmitDataChange = (root: Root) => {
|
|
|
+ if (autoedRoots.has(root)) {
|
|
|
+ const old = autoedRoots.get(root);
|
|
|
+ old.quete++;
|
|
|
+ return old.destory;
|
|
|
+ }
|
|
|
+ let pause = false;
|
|
|
+ const addHandler = (entity: Entity) =>
|
|
|
+ pause ||
|
|
|
+ (!root.history?.hasRecovery &&
|
|
|
+ root.bus.emit("entityChange", { addEntitys: [entity] }));
|
|
|
+ const delHandler = (entity: Entity) =>
|
|
|
+ pause ||
|
|
|
+ (!root.history?.hasRecovery &&
|
|
|
+ root.bus.emit("entityChange", { delEntitys: [entity] }));
|
|
|
|
|
|
- delEntitys.push(entity);
|
|
|
+ const changeEntitys = new Set<Entity>();
|
|
|
+ const setHandler = (entity: Entity) => {
|
|
|
+ if (!pause) {
|
|
|
+ if (!entity.root.dragEntity) {
|
|
|
+ !root.history?.hasRecovery &&
|
|
|
+ root.bus.emit("entityChange", { setEntitys: [entity] });
|
|
|
+ } else {
|
|
|
+ changeEntitys.add(entity);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ };
|
|
|
+ const triggerDragHandler = (entity: Entity) => {
|
|
|
+ if (!entity) {
|
|
|
+ changeEntitys.forEach(setHandler);
|
|
|
+ }
|
|
|
};
|
|
|
|
|
|
root.bus.on("addEntity", addHandler);
|
|
|
- root.bus.on("updateEntity", setHandler);
|
|
|
root.bus.on("delEntity", delHandler);
|
|
|
+ root.bus.on("setEntity", setHandler);
|
|
|
+ root.bus.on("triggerDrag", triggerDragHandler);
|
|
|
|
|
|
- let state = "normal";
|
|
|
- const interrupt = new Promise<void>((resolve) => {
|
|
|
- state = "interrupt";
|
|
|
- resolve();
|
|
|
- });
|
|
|
+ const destory = () => {
|
|
|
+ if (--autoedRoots.get(root).quete === 0) {
|
|
|
+ root.bus.off("setEntity", setHandler);
|
|
|
+ root.bus.off("delEntity", delHandler);
|
|
|
+ root.bus.off("addEntity", addHandler);
|
|
|
+ root.bus.off("triggerDrag", triggerDragHandler);
|
|
|
+ autoedRoots.delete(root);
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
- const draw = Promise.any([interrupt, Promise.resolve(main())]).then(() => {
|
|
|
- root.bus.off("addEntity", addHandler);
|
|
|
- root.bus.off("updateEntity", setHandler);
|
|
|
- root.bus.off("delEntity", addHandler);
|
|
|
- quitOnlyMode && quitOnlyMode();
|
|
|
-
|
|
|
- const change = {
|
|
|
- addEntitys,
|
|
|
- delEntitys,
|
|
|
- setEntitys,
|
|
|
- };
|
|
|
- root.bus.emit("dataChangeAfter", change);
|
|
|
- return { state, change };
|
|
|
+ autoedRoots.set(root, {
|
|
|
+ quete: 1,
|
|
|
+ destory,
|
|
|
+ pause: () => (pause = true),
|
|
|
+ continue: () => (pause = false),
|
|
|
});
|
|
|
-
|
|
|
- return {
|
|
|
- draw,
|
|
|
- interrupt,
|
|
|
- };
|
|
|
+ return destory;
|
|
|
};
|
|
|
|
|
|
const cursorResources = {
|
|
@@ -266,3 +357,138 @@ export const currentConstant = new Proxy(
|
|
|
},
|
|
|
}
|
|
|
);
|
|
|
+
|
|
|
+export const injectPointerEvents = (root: Root) => {
|
|
|
+ const store = {
|
|
|
+ hovers: new Set<Entity>(),
|
|
|
+ focus: new Set<Entity>(),
|
|
|
+ drag: null as Entity,
|
|
|
+ };
|
|
|
+ const oldStore = {
|
|
|
+ hovers: [] as Entity[],
|
|
|
+ focus: [] as Entity[],
|
|
|
+ drag: null as Entity,
|
|
|
+ };
|
|
|
+
|
|
|
+ const emit = debounce(() => {
|
|
|
+ const hovers = [...store.hovers];
|
|
|
+ const focus = [...store.focus];
|
|
|
+ const hoverChange = getChangePart(hovers, oldStore.hovers);
|
|
|
+ const focusChange = getChangePart(focus, oldStore.focus);
|
|
|
+
|
|
|
+ hoverChange.addPort.forEach((entity) => entity.bus.emit("hover"));
|
|
|
+ hoverChange.delPort.forEach((entity) => entity.bus.emit("leave"));
|
|
|
+ focusChange.addPort.forEach((entity) => entity.bus.emit("focus"));
|
|
|
+ focusChange.delPort.forEach((entity) => entity.bus.emit("blur"));
|
|
|
+
|
|
|
+ if (oldStore.drag !== store.drag) {
|
|
|
+ oldStore.drag && oldStore.drag.bus.emit("drop");
|
|
|
+ store.drag && store.drag.bus.emit("drag");
|
|
|
+ }
|
|
|
+
|
|
|
+ oldStore.drag = store.drag;
|
|
|
+ oldStore.hovers = hovers;
|
|
|
+ oldStore.focus = focus;
|
|
|
+ }, 16);
|
|
|
+
|
|
|
+ const needReleases = [
|
|
|
+ openOnEntityTree(root, "mouseover"),
|
|
|
+ openOnEntityTree(root, "mouseout"),
|
|
|
+ openOnEntityTree(root, "click"),
|
|
|
+ openOnEntityTree(root, "touchend"),
|
|
|
+ onEntity(root, "dragstart", (ev) => {
|
|
|
+ const hit = findEntityByShape(root, ev.target);
|
|
|
+ if (canEntityReply(hit)) {
|
|
|
+ store.drag = hit;
|
|
|
+ root.dragEntity = hit;
|
|
|
+ root.bus.emit("triggerDrag", hit);
|
|
|
+ emit();
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ onEntity(root, "dragend", () => {
|
|
|
+ if (store.drag && canEntityReply(store.drag)) {
|
|
|
+ store.drag = null;
|
|
|
+ root.dragEntity = null;
|
|
|
+ root.bus.emit("triggerDrag", null);
|
|
|
+ emit();
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ ];
|
|
|
+
|
|
|
+ const enterHandler = ({ paths }: TreeEvent) => {
|
|
|
+ paths.forEach((entity) => {
|
|
|
+ if (canEntityReply(entity)) {
|
|
|
+ store.hovers.add(entity);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ emit();
|
|
|
+ };
|
|
|
+ const leaveHandler = ({ paths }: TreeEvent) => {
|
|
|
+ paths.forEach((entity) => {
|
|
|
+ if (canEntityReply(entity)) {
|
|
|
+ store.hovers.delete(entity);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ emit();
|
|
|
+ };
|
|
|
+ const clickHandler = ({ paths }: TreeEvent) => {
|
|
|
+ store.focus.clear();
|
|
|
+ if (canEntityReply(paths[0])) {
|
|
|
+ root.bus.emit("triggerFocus", paths[0]);
|
|
|
+ }
|
|
|
+ paths.forEach((entity) => {
|
|
|
+ if (canEntityReply(entity)) {
|
|
|
+ store.focus.add(entity);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ emit();
|
|
|
+ };
|
|
|
+
|
|
|
+ root.bus.on("mouseover" as any, enterHandler);
|
|
|
+ root.bus.on("mouseout" as any, leaveHandler);
|
|
|
+ root.bus.on("click", clickHandler);
|
|
|
+ root.bus.on("touchend", clickHandler);
|
|
|
+
|
|
|
+ const destory = () => {
|
|
|
+ mergeFuns(needReleases)();
|
|
|
+ root.bus.off("mouseover" as any, enterHandler);
|
|
|
+ root.bus.off("mouseout" as any, leaveHandler);
|
|
|
+ root.bus.off("click", clickHandler);
|
|
|
+ root.bus.off("touchend", clickHandler);
|
|
|
+ };
|
|
|
+
|
|
|
+ root.bus.on("destroyBefore", destory);
|
|
|
+ return {
|
|
|
+ focus(...entitys: Entity[]) {
|
|
|
+ store.focus.clear();
|
|
|
+ entitys.forEach((entity) => store.focus.add(entity));
|
|
|
+ emit();
|
|
|
+ },
|
|
|
+ blur(...entitys: Entity[]) {
|
|
|
+ entitys.forEach((entity) => store.focus.delete(entity));
|
|
|
+ emit();
|
|
|
+ },
|
|
|
+ hover(...entitys: Entity[]) {
|
|
|
+ store.hovers.clear();
|
|
|
+ entitys.forEach((entity) => store.hovers.add(entity));
|
|
|
+ emit();
|
|
|
+ },
|
|
|
+ leave(...entitys: Entity[]) {
|
|
|
+ entitys.forEach((entity) => store.hovers.delete(entity));
|
|
|
+ emit();
|
|
|
+ },
|
|
|
+ drag(entity: Entity) {
|
|
|
+ store.drag = entity;
|
|
|
+ emit();
|
|
|
+ },
|
|
|
+ drop(entity: Entity) {
|
|
|
+ if (store.drag === entity) {
|
|
|
+ store.drag = entity;
|
|
|
+ emit();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ destory,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export type PointerEvents = ReturnType<typeof injectPointerEvents>;
|