entity-root-server.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. import { Entity } from "../base/entity";
  2. import { Root } from "./entity-root";
  3. import { Pos } from "../type";
  4. import { Transform } from "konva/lib/Util";
  5. import {
  6. canEntityReply,
  7. onEntity,
  8. openOnEntityTree,
  9. TreeEvent,
  10. } from "../event";
  11. import { findEntityByShape } from "./entity-server";
  12. import { debounce, getChangePart, mergeFuns } from "../../shared";
  13. // 指定某些entity可编辑
  14. export const openOnlyMode = (
  15. root: Root,
  16. _entitys: Entity[],
  17. includeNews = true
  18. ) => {
  19. const entitys: (Entity | null)[] = [];
  20. const prevEntitysReply: Entity["replyEvents"][] = [];
  21. const setEntityReply = (entity: Entity, reply: Entity["replyEvents"]) => {
  22. const ndx = entitys.length;
  23. entitys[ndx] = entity;
  24. prevEntitysReply[ndx] = entity.replyEvents;
  25. entity.replyEvents = reply;
  26. };
  27. root.children.forEach((item) => setEntityReply(item, "none"));
  28. _entitys.forEach((item) => setEntityReply(item, "all"));
  29. const delHandler = (entity: Entity) => {
  30. const ndx = entitys.indexOf(entity);
  31. prevEntitysReply[ndx] = null;
  32. entitys[ndx] = null;
  33. };
  34. const addHandler = (entity: Entity) => {
  35. if (includeNews) {
  36. setEntityReply(entity, "all");
  37. } else if (root.children.includes(entity)) {
  38. setEntityReply(entity, "none");
  39. }
  40. };
  41. root.bus.on("delEntity", delHandler);
  42. root.bus.on("addEntity", addHandler);
  43. return () => {
  44. root.bus.off("delEntity", delHandler);
  45. root.bus.off("addEntity", addHandler);
  46. entitys.forEach((entity, ndx) => {
  47. if (entity) {
  48. entity.replyEvents = prevEntitysReply[ndx];
  49. }
  50. });
  51. };
  52. };
  53. export type EditModeProps = {
  54. entitys?: Entity[];
  55. only?: boolean;
  56. includeNews?: boolean;
  57. };
  58. export type EditModeChange = {
  59. addEntitys?: Entity[];
  60. setEntitys?: Entity[];
  61. delEntitys?: Entity[];
  62. };
  63. export const openEditMode = (
  64. root: Root,
  65. main: () => any,
  66. props: EditModeProps = {
  67. entitys: [],
  68. only: false,
  69. includeNews: false,
  70. }
  71. ) => {
  72. if (!props.entitys) props.entitys = [];
  73. if (!props.only) props.only = false;
  74. if (!props.includeNews) props.includeNews = false;
  75. root.bus.emit("entityChangeBefore");
  76. const quitOnlyMode =
  77. props.only && openOnlyMode(root, props.entitys, props.includeNews);
  78. let state = "normal";
  79. let resolve: () => void;
  80. const interrupt = () => {
  81. state = "interrupt";
  82. resolve();
  83. };
  84. const destoryAutoEmit = root.history && autoEmitDataChange(root);
  85. const interruptPromise = new Promise<void>((r) => (resolve = r));
  86. const draw = Promise.any([interruptPromise, Promise.resolve(main())]).then(
  87. () => {
  88. quitOnlyMode && quitOnlyMode();
  89. destoryAutoEmit && destoryAutoEmit();
  90. setTimeout(() => {
  91. root.bus.emit("entityChangeAfter");
  92. });
  93. return { state };
  94. }
  95. );
  96. return {
  97. draw,
  98. interrupt,
  99. };
  100. };
  101. export const openEditModePacking = (root: Root, props?: EditModeProps) => {
  102. let complete: () => void;
  103. const { interrupt } = openEditMode(
  104. root,
  105. () => {
  106. return new Promise<void>((r) => (complete = r));
  107. },
  108. props
  109. );
  110. return {
  111. interrupt,
  112. complete,
  113. };
  114. };
  115. const autoedRoots = new WeakMap<
  116. Root,
  117. {
  118. quete: number;
  119. destory: () => void;
  120. pause: () => void;
  121. continue: () => void;
  122. }
  123. >();
  124. export const hasAutoEmitDataChange = (root: Root) => {
  125. return autoedRoots.has(root);
  126. };
  127. export const pauseAutoEmitDataChange = (root: Root) => {
  128. if (autoedRoots.has(root)) {
  129. autoedRoots.get(root).pause();
  130. }
  131. };
  132. export const continueAutoEmitDataChange = (root: Root) => {
  133. if (autoedRoots.has(root)) {
  134. autoedRoots.get(root).continue();
  135. }
  136. };
  137. export const autoEmitDataChange = (root: Root) => {
  138. if (autoedRoots.has(root)) {
  139. const old = autoedRoots.get(root);
  140. old.quete++;
  141. return old.destory;
  142. }
  143. let pause = false;
  144. const addHandler = (entity: Entity) =>
  145. pause ||
  146. (!root.history?.hasRecovery &&
  147. root.bus.emit("entityChange", { addEntitys: [entity] }));
  148. const delHandler = (entity: Entity) =>
  149. pause ||
  150. (!root.history?.hasRecovery &&
  151. root.bus.emit("entityChange", { delEntitys: [entity] }));
  152. const changeEntitys = new Set<Entity>();
  153. const setHandler = (entity: Entity) => {
  154. if (!pause) {
  155. if (!entity.root.dragEntity) {
  156. !root.history?.hasRecovery &&
  157. root.bus.emit("entityChange", { setEntitys: [entity] });
  158. } else {
  159. changeEntitys.add(entity);
  160. }
  161. }
  162. };
  163. const triggerDragHandler = (entity: Entity) => {
  164. if (!entity) {
  165. changeEntitys.forEach(setHandler);
  166. }
  167. };
  168. root.bus.on("addEntity", addHandler);
  169. root.bus.on("delEntity", delHandler);
  170. root.bus.on("setEntity", setHandler);
  171. root.bus.on("triggerDrag", triggerDragHandler);
  172. const destory = () => {
  173. if (--autoedRoots.get(root).quete === 0) {
  174. root.bus.off("setEntity", setHandler);
  175. root.bus.off("delEntity", delHandler);
  176. root.bus.off("addEntity", addHandler);
  177. root.bus.off("triggerDrag", triggerDragHandler);
  178. autoedRoots.delete(root);
  179. }
  180. };
  181. autoedRoots.set(root, {
  182. quete: 1,
  183. destory,
  184. pause: () => (pause = true),
  185. continue: () => (pause = false),
  186. });
  187. return destory;
  188. };
  189. const cursorResources = {
  190. pic_pen_a: "/cursors/pic_pen_a.ico",
  191. pic_pen_r: "/cursors/pic_pen_r.ico",
  192. pic_pen: "/cursors/pic_pen.ico",
  193. };
  194. export const addCursorResource = (key: string, url: string) => {
  195. cursorResources[key] = url;
  196. };
  197. export const injectSetCursor = (root: Root) => {
  198. const cursorStack = [];
  199. const setCursorStyle = (ico: string) => {
  200. const url = ico in cursorResources ? cursorResources[ico] : null;
  201. root.container.style.cursor = url ? `url("${ico}"), auto` : ico;
  202. };
  203. return (ico: string) => {
  204. const ndx = cursorStack.length;
  205. cursorStack[ndx] = ico;
  206. setCursorStyle(ico);
  207. return () => {
  208. cursorStack[ndx] = null;
  209. let last = cursorStack.length - 1;
  210. for (; last >= 0; last--) {
  211. if (cursorStack[last] !== null) {
  212. break;
  213. }
  214. }
  215. if (last === -1) {
  216. setCursorStyle("inherit");
  217. cursorStack.length = 0;
  218. } else if (last < ndx) {
  219. setCursorStyle(cursorStack[last]);
  220. }
  221. };
  222. };
  223. };
  224. export const injectConstant = (root: Root) => {
  225. const origin: { [key in string]: number | Pos } = {};
  226. const current: { [key in string]: number | Pos } = {};
  227. let mat: Transform;
  228. let scale: Pos;
  229. let position: Pos;
  230. let rCos: number, rSin: number;
  231. root.bus.on("mounted", function handler() {
  232. mat = root.stage.getTransform().invert();
  233. scale = root.stage.scale();
  234. position = root.stage.position();
  235. let radians = root.stage.rotation() * (Math.PI / 180);
  236. rCos = Math.cos(radians);
  237. rSin = Math.sin(radians);
  238. root.bus.off("mounted", handler);
  239. });
  240. const invView = (key: string) => {
  241. if (key.startsWith("fix:")) {
  242. if (typeof origin[key] === "number") {
  243. current[key] = mat.point({ x: origin[key], y: 0 }).x;
  244. } else {
  245. current[key] = mat.point(origin[key]);
  246. }
  247. }
  248. };
  249. root.bus.on("changeView", () => {
  250. mat = root.stage.getTransform().invert();
  251. Object.keys(origin).forEach(invView);
  252. });
  253. const invScale = (key: string) => {
  254. if (key.startsWith("fixScale:")) {
  255. if (typeof origin[key] === "number") {
  256. current[key] = origin[key] / scale.x;
  257. } else {
  258. current[key] = {
  259. x: origin[key].x / scale.x,
  260. y: origin[key].y / scale.y,
  261. };
  262. }
  263. }
  264. };
  265. root.bus.on("changeViewScale", (nscale) => {
  266. scale = nscale;
  267. Object.keys(origin).forEach(invScale);
  268. });
  269. const invPosition = (key: string) => {
  270. if (key.startsWith("fixPosition:")) {
  271. if (typeof origin[key] === "number") {
  272. current[key] = origin[key] - position.x;
  273. } else {
  274. current[key] = {
  275. x: origin[key].x - position.x,
  276. y: origin[key].y - position.y,
  277. };
  278. }
  279. }
  280. };
  281. root.bus.on("changeViewPosition", (nposition) => {
  282. position = nposition;
  283. Object.keys(origin).forEach(invPosition);
  284. });
  285. const invRotation = (key: string) => {
  286. if (key.startsWith("fixRotation:")) {
  287. const p = origin[key];
  288. if (typeof p !== "number") {
  289. current[key] = {
  290. x: p.x * rCos - p.y * rSin,
  291. y: p.x * rSin + p.y * rCos,
  292. };
  293. }
  294. }
  295. };
  296. root.bus.on("changeViewRotation", (rotation) => {
  297. let radians = rotation * (Math.PI / 180);
  298. rCos = Math.cos(radians);
  299. rSin = Math.sin(radians);
  300. Object.keys(origin).forEach(invRotation);
  301. });
  302. return {
  303. set(key: string, val: number) {
  304. origin[key] = val;
  305. invView(key);
  306. invPosition(key);
  307. invRotation(key);
  308. invScale(key);
  309. },
  310. get<T extends number | Pos>(key: string): T {
  311. return (current[key] || origin[key]) as T;
  312. },
  313. };
  314. };
  315. const rootStack: Root[] = [];
  316. export const pushRoot = (root: Root) => rootStack.push(root);
  317. export const popRoot = () => rootStack.pop();
  318. export const currentRoot = () => rootStack[rootStack.length - 1];
  319. export const currentConstant = new Proxy(
  320. {},
  321. {
  322. get(_, p) {
  323. return currentRoot().constant.get(p as string);
  324. },
  325. }
  326. );
  327. export const injectPointerEvents = (root: Root) => {
  328. const store = {
  329. hovers: new Set<Entity>(),
  330. focus: new Set<Entity>(),
  331. drag: null as Entity,
  332. };
  333. const oldStore = {
  334. hovers: [] as Entity[],
  335. focus: [] as Entity[],
  336. drag: null as Entity,
  337. };
  338. const emit = debounce(() => {
  339. const hovers = [...store.hovers];
  340. const focus = [...store.focus];
  341. const hoverChange = getChangePart(hovers, oldStore.hovers);
  342. const focusChange = getChangePart(focus, oldStore.focus);
  343. hoverChange.addPort.forEach((entity) => entity.bus.emit("hover"));
  344. hoverChange.delPort.forEach((entity) => entity.bus.emit("leave"));
  345. focusChange.addPort.forEach((entity) => entity.bus.emit("focus"));
  346. focusChange.delPort.forEach((entity) => entity.bus.emit("blur"));
  347. if (oldStore.drag !== store.drag) {
  348. oldStore.drag && oldStore.drag.bus.emit("drop");
  349. store.drag && store.drag.bus.emit("drag");
  350. }
  351. oldStore.drag = store.drag;
  352. oldStore.hovers = hovers;
  353. oldStore.focus = focus;
  354. }, 16);
  355. const needReleases = [
  356. openOnEntityTree(root, "mouseover"),
  357. openOnEntityTree(root, "mouseout"),
  358. openOnEntityTree(root, "click"),
  359. openOnEntityTree(root, "touchend"),
  360. onEntity(root, "dragstart", (ev) => {
  361. const hit = findEntityByShape(root, ev.target);
  362. if (canEntityReply(hit)) {
  363. store.drag = hit;
  364. root.dragEntity = hit;
  365. root.bus.emit("triggerDrag", hit);
  366. emit();
  367. }
  368. }),
  369. onEntity(root, "dragend", () => {
  370. if (store.drag && canEntityReply(store.drag)) {
  371. store.drag = null;
  372. root.dragEntity = null;
  373. root.bus.emit("triggerDrag", null);
  374. emit();
  375. }
  376. }),
  377. ];
  378. const enterHandler = ({ paths }: TreeEvent) => {
  379. paths.forEach((entity) => {
  380. if (canEntityReply(entity)) {
  381. store.hovers.add(entity);
  382. }
  383. });
  384. emit();
  385. };
  386. const leaveHandler = ({ paths }: TreeEvent) => {
  387. paths.forEach((entity) => {
  388. if (canEntityReply(entity)) {
  389. store.hovers.delete(entity);
  390. }
  391. });
  392. emit();
  393. };
  394. const clickHandler = ({ paths }: TreeEvent) => {
  395. store.focus.clear();
  396. if (canEntityReply(paths[0])) {
  397. root.bus.emit("triggerFocus", paths[0]);
  398. }
  399. paths.forEach((entity) => {
  400. if (canEntityReply(entity)) {
  401. store.focus.add(entity);
  402. }
  403. });
  404. emit();
  405. };
  406. root.bus.on("mouseover" as any, enterHandler);
  407. root.bus.on("mouseout" as any, leaveHandler);
  408. root.bus.on("click", clickHandler);
  409. root.bus.on("touchend", clickHandler);
  410. const destory = () => {
  411. mergeFuns(needReleases)();
  412. root.bus.off("mouseover" as any, enterHandler);
  413. root.bus.off("mouseout" as any, leaveHandler);
  414. root.bus.off("click", clickHandler);
  415. root.bus.off("touchend", clickHandler);
  416. };
  417. root.bus.on("destroyBefore", destory);
  418. return {
  419. focus(...entitys: Entity[]) {
  420. store.focus.clear();
  421. entitys.forEach((entity) => store.focus.add(entity));
  422. emit();
  423. },
  424. blur(...entitys: Entity[]) {
  425. entitys.forEach((entity) => store.focus.delete(entity));
  426. emit();
  427. },
  428. hover(...entitys: Entity[]) {
  429. store.hovers.clear();
  430. entitys.forEach((entity) => store.hovers.add(entity));
  431. emit();
  432. },
  433. leave(...entitys: Entity[]) {
  434. entitys.forEach((entity) => store.hovers.delete(entity));
  435. emit();
  436. },
  437. drag(entity: Entity) {
  438. store.drag = entity;
  439. emit();
  440. },
  441. drop(entity: Entity) {
  442. if (store.drag === entity) {
  443. store.drag = entity;
  444. emit();
  445. }
  446. },
  447. destory,
  448. };
  449. };
  450. export type PointerEvents = ReturnType<typeof injectPointerEvents>;