entity-root-server.ts 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. import { Entity } from "../base/entity";
  2. import { onEntity, openOnEntityTree, TreeEvent } from "../event/spread";
  3. import { findEntityByShape } from "../base/entity-server";
  4. import { debounce, getChangePart, mergeFuns } from "../../shared";
  5. import { Root } from "./entity-root";
  6. export const injectPointerEvents = (root: Root) => {
  7. const store = {
  8. hovers: new Set<Entity>(),
  9. focus: new Set<Entity>(),
  10. drag: null as Entity,
  11. };
  12. const oldStore = {
  13. hovers: [] as Entity[],
  14. focus: [] as Entity[],
  15. drag: null as Entity,
  16. };
  17. const emit = debounce(() => {
  18. const hovers = [...store.hovers];
  19. const focus = [...store.focus];
  20. const hoverChange = getChangePart(hovers, oldStore.hovers);
  21. const focusChange = getChangePart(focus, oldStore.hovers);
  22. hoverChange.addPort.forEach((entity) => entity.bus.emit("hover"));
  23. hoverChange.delPort.forEach((entity) => entity.bus.emit("leave"));
  24. focusChange.addPort.forEach((entity) => entity.bus.emit("focus"));
  25. focusChange.delPort.forEach((entity) => entity.bus.emit("blur"));
  26. if (oldStore.drag !== store.drag) {
  27. oldStore.drag && oldStore.drag.bus.emit("drop");
  28. store.drag && store.drag.bus.emit("drag");
  29. }
  30. oldStore.drag = store.drag;
  31. oldStore.hovers = hovers;
  32. oldStore.focus = focus;
  33. }, 16);
  34. const needReleases = [
  35. openOnEntityTree(root, "mouseenter"),
  36. openOnEntityTree(root, "mouseleave"),
  37. openOnEntityTree(root, "click"),
  38. openOnEntityTree(root, "touchend"),
  39. onEntity(root, "dragstart", (ev) => {
  40. const hit = findEntityByShape(root, ev.target);
  41. hit.contenteditable && (store.drag = hit);
  42. emit();
  43. }),
  44. onEntity(root, "dragend", () => {
  45. store.drag = null;
  46. emit();
  47. }),
  48. ];
  49. const enterHandler = ({ paths }: TreeEvent) => {
  50. paths.forEach((entity) => {
  51. store.hovers.add(entity);
  52. });
  53. emit();
  54. };
  55. const leaveHandler = ({ paths }: TreeEvent) => {
  56. paths.forEach((entity) => {
  57. store.hovers.delete(entity);
  58. });
  59. emit();
  60. };
  61. const clickHandler = ({ paths }: TreeEvent) => {
  62. store.focus.clear();
  63. paths.forEach((entity) => {
  64. store.focus.add(entity);
  65. });
  66. emit();
  67. };
  68. root.bus.on("mouseenter", enterHandler);
  69. root.bus.on("mouseleave", leaveHandler);
  70. root.bus.on("click", clickHandler);
  71. root.bus.on("touchend", clickHandler);
  72. const destory = () => {
  73. mergeFuns(needReleases)();
  74. root.bus.off("mouseenter", enterHandler);
  75. root.bus.off("mouseleave", leaveHandler);
  76. root.bus.off("click", clickHandler);
  77. root.bus.off("touchend", clickHandler);
  78. };
  79. root.bus.on("destroyBefore", destory);
  80. return {
  81. focus(...entitys: Entity[]) {
  82. store.focus.clear();
  83. entitys.forEach((entity) => store.focus.add(entity));
  84. emit();
  85. },
  86. blur(...entitys: Entity[]) {
  87. entitys.forEach((entity) => store.focus.delete(entity));
  88. emit();
  89. },
  90. hover(...entitys: Entity[]) {
  91. store.hovers.clear();
  92. entitys.forEach((entity) => store.hovers.add(entity));
  93. emit();
  94. },
  95. leave(...entitys: Entity[]) {
  96. entitys.forEach((entity) => store.hovers.delete(entity));
  97. emit();
  98. },
  99. drag(entity: Entity) {
  100. store.drag = entity;
  101. emit();
  102. },
  103. drop(entity: Entity) {
  104. if (store.drag === entity) {
  105. store.drag = entity;
  106. emit();
  107. }
  108. },
  109. destory,
  110. };
  111. };
  112. export type PointerEvents = ReturnType<typeof injectPointerEvents>;
  113. // 指定某些entity可编辑
  114. export const openOnlyMode = (
  115. root: Root,
  116. _entitys: Entity[],
  117. includeNews = true
  118. ) => {
  119. const prevRootReply = root.replyEvents;
  120. const entitys: (Entity | null)[] = [];
  121. const prevEntitysReply: Entity["replyEvents"][] = [];
  122. const pushEntity = (entity: Entity) => {
  123. const ndx = entitys.length;
  124. entitys[ndx] = entity;
  125. prevEntitysReply[ndx] = entity.replyEvents;
  126. entity.replyEvents = "all";
  127. };
  128. const delEntity = (entity: Entity) => {
  129. const ndx = entitys.indexOf(entity);
  130. prevEntitysReply[ndx] = null;
  131. entitys[ndx] = null;
  132. };
  133. _entitys.forEach(pushEntity);
  134. // 新增的entity可以使用事件
  135. includeNews && root.bus.on("addEntity", pushEntity);
  136. root.bus.on("delEntity", delEntity);
  137. return () => {
  138. root.bus.off("addEntity", pushEntity);
  139. root.bus.off("delEntity", delEntity);
  140. root.replyEvents = prevRootReply;
  141. entitys.forEach((entity, ndx) => {
  142. if (entity) {
  143. entity.replyEvents = prevEntitysReply[ndx];
  144. }
  145. });
  146. };
  147. };
  148. export type EditModeProps = {
  149. entitys: Entity[];
  150. only: boolean;
  151. includeNews: boolean;
  152. };
  153. export const openEditMode = async (
  154. root: Root,
  155. main: () => any,
  156. props: EditModeProps = { entitys: [], only: false, includeNews: false }
  157. ) => {
  158. root.bus.emit("dataChangeBefore");
  159. const quitOnlyMode =
  160. props.only && openOnlyMode(root, props.entitys, props.includeNews);
  161. const addEntitys = [];
  162. const setEntitys = [];
  163. const delEntitys = [];
  164. const addHandler = (entity: Entity) => {
  165. addEntitys.push(entity);
  166. };
  167. const setHandler = (entity: Entity) => {
  168. if (
  169. !addEntitys.includes(entity) &&
  170. !delEntitys.includes(entity) &&
  171. !setEntitys.includes(entity)
  172. ) {
  173. setEntitys.push(entity);
  174. }
  175. };
  176. const delHandler = (entity: Entity) => {
  177. const delNdx = delEntitys.indexOf(entity);
  178. if (~delNdx) return;
  179. const addNdx = addEntitys.indexOf(entity);
  180. if (~addNdx) addEntitys.splice(addNdx, 1);
  181. const setNdx = setEntitys.indexOf(entity);
  182. if (~setNdx) setEntitys.splice(setNdx, 1);
  183. delEntitys.push(entity);
  184. };
  185. root.bus.on("addEntity", addHandler);
  186. root.bus.on("updateEntity", setHandler);
  187. root.bus.on("delEntity", delHandler);
  188. let state = "normal";
  189. const interrupt = new Promise<void>((resolve) => {
  190. state = "interrupt";
  191. resolve();
  192. });
  193. const draw = Promise.any([interrupt, Promise.resolve(main())]).then(() => {
  194. root.bus.off("addEntity", addHandler);
  195. root.bus.off("updateEntity", setHandler);
  196. root.bus.off("delEntity", addHandler);
  197. quitOnlyMode && quitOnlyMode();
  198. const change = {
  199. addEntitys,
  200. delEntitys,
  201. setEntitys,
  202. };
  203. root.bus.emit("dataChangeAfter", change);
  204. return { state, change };
  205. });
  206. return {
  207. draw,
  208. interrupt,
  209. };
  210. };
  211. const cursorResources = import.meta.glob("../resource/cursor?url");
  212. const cursorDefs = ["move", "inherit", "pointer"];
  213. const cursorMap = new WeakMap<Root, string[]>();
  214. const setCursorStyle = (root: Root, ico: string) => {
  215. const style = cursorDefs.includes(ico)
  216. ? ico
  217. : ico in cursorResources
  218. ? `url("${ico}"), auto`
  219. : null;
  220. if (!style) throw "ico 不存在!";
  221. root.container.style.cursor = ico;
  222. };
  223. export const setCursor = (root: Root, ico: string) => {
  224. if (!cursorMap.get(root)) {
  225. cursorMap.set(root, []);
  226. }
  227. const stack = cursorMap.get(root);
  228. const ndx = stack.length;
  229. stack[ndx] = ico;
  230. setCursorStyle(root, ico);
  231. return () => {
  232. stack[ndx] = null;
  233. let last = stack.length - 1;
  234. for (; last >= 0; last--) {
  235. if (stack[last] !== null) {
  236. break;
  237. }
  238. }
  239. if (last === -1) {
  240. setCursor(root, "inherit");
  241. stack.length = 0;
  242. } else if (last < ndx) {
  243. setCursor(root, stack[last]);
  244. }
  245. };
  246. };