use-global-vars.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. import { DC, EntityShape } from "../../deconstruction";
  2. import { Stage } from "konva/lib/Stage";
  3. import {
  4. computed,
  5. getCurrentInstance,
  6. nextTick,
  7. reactive,
  8. Ref,
  9. ref,
  10. shallowRef,
  11. watch,
  12. WatchCallback,
  13. WatchEffect,
  14. watchEffect,
  15. WatchEffectOptions,
  16. WatchOptions,
  17. WatchSource,
  18. } from "vue";
  19. import { Layer } from "konva/lib/Layer";
  20. import { Pos } from "@/utils/math.ts";
  21. import { listener } from "@/utils/event.ts";
  22. import { debounce, mergeFuns, onlyId } from "@/utils/shared.ts";
  23. import { StoreData } from "../store/store.ts";
  24. import { rendererMap, rendererName } from "@/constant/index.ts";
  25. import { Shape, ShapeConfig } from "konva/lib/Shape";
  26. import { ElLoading } from "element-plus";
  27. import { PropertyDescribes } from "../html-mount/propertys/index.ts";
  28. import { ShapeType } from "@/index.ts";
  29. import { isEditableElement } from "@/utils/dom.ts";
  30. let getInstance = getCurrentInstance
  31. export const useRendererInstance = () => {
  32. let instance = getInstance()!;
  33. while (instance.type.name !== rendererName) {
  34. if (instance.parent) {
  35. instance = instance.parent;
  36. } else {
  37. throw "未发现渲染实例";
  38. }
  39. }
  40. return instance;
  41. };
  42. export const installGlobalVar = <T>(
  43. create: () => { var: T; onDestroy: () => void } | T,
  44. key = Symbol("globalVar")
  45. ) => {
  46. const useGlobalVar = (): T => {
  47. const instance = useRendererInstance() as any;
  48. const { unmounteds } = rendererMap.get(instance)!;
  49. if (!(key in instance)) {
  50. let val = create() as any;
  51. if (typeof val === "object" && "var" in val && "onDestroy" in val) {
  52. val.onDestroy && unmounteds.push(val.onDestroy);
  53. if (import.meta.env.DEV) {
  54. unmounteds.push(() => {
  55. console.log("销毁变量", key);
  56. });
  57. }
  58. val = val.var;
  59. }
  60. instance[key] = val;
  61. }
  62. return instance[key];
  63. };
  64. return useGlobalVar;
  65. };
  66. export const useRunHook = installGlobalVar(() => {
  67. const instance = getCurrentInstance()
  68. return <R, T extends () => R>(hook: T): R => {
  69. const back = getInstance
  70. getInstance = () => instance
  71. const result = hook()
  72. getInstance = back
  73. return result
  74. }
  75. })
  76. export type InstanceProps = {
  77. id?: string;
  78. data?: StoreData;
  79. handlerResource(file: File): Promise<string>;
  80. };
  81. export const useInstanceProps = installGlobalVar(() => {
  82. const props = ref<InstanceProps>();
  83. return {
  84. set(val: InstanceProps) {
  85. props.value = val;
  86. },
  87. get() {
  88. return props.value!;
  89. },
  90. };
  91. }, Symbol("instanceId"));
  92. export const stackVar = <T>(init?: T) => {
  93. const factory = (init: T) => ({ var: init, id: onlyId() });
  94. const stack = reactive([]) as { var: T; id: string }[];
  95. if (init) {
  96. stack.push(factory(init));
  97. }
  98. const result = {
  99. get value() {
  100. return stack[stack.length - 1]?.var;
  101. },
  102. set value(val) {
  103. stack[stack.length - 1].var = val;
  104. },
  105. push(data: T) {
  106. stack.push(factory(data));
  107. const item = stack[stack.length - 1];
  108. const pop = (() => {
  109. const ndx = stack.findIndex(({ id }) => id === item.id);
  110. if (~ndx) {
  111. stack.splice(ndx, 1);
  112. }
  113. }) as (() => void) & { set: (data: T) => void };
  114. pop.set = (data) => {
  115. item.var = data;
  116. };
  117. return pop;
  118. },
  119. pop() {
  120. if (stack.length - 1 > 0) {
  121. stack.pop();
  122. } else {
  123. console.error("已到达栈顶");
  124. }
  125. },
  126. cycle<R>(data: T, run: () => R): R {
  127. result.push(data);
  128. const r = run();
  129. result.pop();
  130. return r;
  131. },
  132. };
  133. return result;
  134. };
  135. export const globalWatch = <T>(
  136. source: WatchSource<T>,
  137. cb: WatchCallback<T, T>,
  138. options?: WatchOptions
  139. ): (() => void) => {
  140. let stop: () => void;
  141. nextTick(() => {
  142. stop = watch(source, cb as any, options as any);
  143. });
  144. return () => {
  145. stop && stop();
  146. };
  147. };
  148. export const globalWatchEffect = (
  149. cb: WatchEffect,
  150. options?: WatchEffectOptions
  151. ): (() => void) => {
  152. let stop: () => void;
  153. nextTick(() => {
  154. stop = watchEffect(cb as any, options);
  155. });
  156. return () => {
  157. stop && stop();
  158. };
  159. };
  160. export const useStage = installGlobalVar(
  161. () => shallowRef<DC<Stage> | undefined>(),
  162. Symbol("stage")
  163. );
  164. export const usePointerPos = installGlobalVar(() => {
  165. const stage = useStage();
  166. const pos = ref(null) as Ref<Pos | null> & { replay: () => void };
  167. let lastClient: { clientX: number; clientY: number } | null = null;
  168. let replayIng = false;
  169. const replay = () => {
  170. const $stage = stage.value?.getNode();
  171. if (!$stage || !lastClient) return;
  172. replayIng = true;
  173. const dom = $stage.container().querySelector("canvas") as HTMLCanvasElement;
  174. const moveConf = {
  175. bubbles: true, // 事件是否能够冒泡
  176. cancelable: true, // 事件是否可以被取消
  177. isPrimary: true,
  178. pointerId: 1,
  179. };
  180. dom.dispatchEvent(
  181. new PointerEvent("pointermove", {
  182. ...moveConf,
  183. clientX: -9999999,
  184. clientY: -9999999,
  185. })
  186. );
  187. dom.dispatchEvent(
  188. new PointerEvent("pointermove", { ...lastClient, ...moveConf })
  189. );
  190. replayIng = false;
  191. };
  192. const stopWatch = globalWatchEffect((onCleanup) => {
  193. const $stage = stage.value?.getNode();
  194. if (!$stage) return;
  195. const mount = $stage.container().parentElement!;
  196. pos.value = $stage.pointerPos;
  197. onCleanup(
  198. listener(mount, "pointermove", (ev) => {
  199. pos.value = $stage.pointerPos;
  200. if (pos.value && !replayIng) {
  201. lastClient = {
  202. clientX: ev.clientX,
  203. clientY: ev.clientY,
  204. };
  205. }
  206. })
  207. );
  208. });
  209. pos.replay = replay;
  210. return {
  211. var: pos,
  212. onDestroy: stopWatch,
  213. };
  214. }, Symbol("pointerPos"));
  215. export const usePointerIntersections = installGlobalVar(() => {
  216. const shapes = ref<Shape<ShapeConfig>[]>([]);
  217. const pos = usePointerPos();
  218. const stage = useStage();
  219. const updateShapes = debounce(() => {
  220. if (!pos.value || !stage.value) {
  221. return;
  222. }
  223. shapes.value = stage.value.getNode().getAllIntersections(pos.value) || [];
  224. }, 300);
  225. const stopWatch = watch(pos, updateShapes);
  226. return {
  227. var: shapes,
  228. onDestroy: () => {
  229. stopWatch();
  230. shapes.value = [];
  231. },
  232. };
  233. });
  234. export const usePointerIntersection = installGlobalVar(() => {
  235. const shape = ref<Shape<ShapeConfig> | null>(null);
  236. const pos = usePointerPos();
  237. const stage = useStage();
  238. const updateShape = debounce(() => {
  239. if (!pos.value || !stage.value) {
  240. return;
  241. }
  242. shape.value = stage.value.getNode().getIntersection(pos.value);
  243. }, 16);
  244. const stopWatch = watch(pos, updateShape);
  245. return {
  246. var: shape,
  247. onDestroy: () => {
  248. stopWatch();
  249. shape.value = null;
  250. },
  251. };
  252. });
  253. export const useDownKeys = installGlobalVar(() => {
  254. const keyKeys = reactive(new Set<string>());
  255. const mouseKeys = reactive(new Set<string>());
  256. const evHandler = (ev: KeyboardEvent | MouseEvent, keys: Set<string>) => {
  257. ev.shiftKey ? keys.add("Shift") : keys.delete("Shift");
  258. ev.altKey ? keys.add("Alt") : keys.delete("Alt");
  259. ev.metaKey ? keys.add("Meta") : keys.delete("Meta");
  260. ev.ctrlKey ? keys.add("Ctrl") : keys.delete("Ctrl");
  261. }
  262. const cleanup = mergeFuns(
  263. listener(window, "keydown", (ev) => {
  264. if (!isEditableElement(ev.target as HTMLElement)) {
  265. keyKeys.add(ev.key);
  266. evHandler(ev, keyKeys)
  267. }
  268. }),
  269. listener(window, "keyup", (ev) => {
  270. keyKeys.delete(ev.key);
  271. evHandler(ev, keyKeys)
  272. }),
  273. listener(window, "mousemove", (ev) => {
  274. evHandler(ev, mouseKeys)
  275. })
  276. );
  277. const keys = reactive(new Set<string>());
  278. watchEffect(() => {
  279. keys.clear();
  280. for (const key of keyKeys.values()) {
  281. keys.add(key);
  282. }
  283. for (const key of mouseKeys.values()) {
  284. keys.add(key);
  285. }
  286. });
  287. return {
  288. var: keys,
  289. onDestroy: cleanup,
  290. };
  291. });
  292. export const useLayers = () => {
  293. const stage = useStage();
  294. return computed(() => stage.value?.getNode().children as Layer[]);
  295. };
  296. export const useTransformIngShapes = installGlobalVar(
  297. () => ref<EntityShape[]>([]),
  298. Symbol("transformIngShapes")
  299. );
  300. export const useCursor = installGlobalVar(
  301. () => stackVar("default"),
  302. Symbol("cursor")
  303. );
  304. export type MPart = { comp: any; props: any };
  305. export const useMountParts = installGlobalVar(() => {
  306. const mParts = reactive<MPart[]>([]);
  307. const del = (part: MPart) => {
  308. const ndx = mParts.indexOf(part);
  309. ~ndx && mParts.splice(ndx, 1);
  310. };
  311. return {
  312. value: mParts,
  313. add(part: MPart) {
  314. mParts.push(part);
  315. return () => del(part);
  316. },
  317. del,
  318. };
  319. });
  320. export const useForciblyShowItemIds = installGlobalVar(() => {
  321. const set = new Set() as Set<string> & {
  322. cycle: (id: string, fn: () => any) => void;
  323. };
  324. set.cycle = (id, fn) => {
  325. set.add(id);
  326. const result = fn();
  327. if (result instanceof Promise) {
  328. result.then(() => set.delete(id));
  329. } else {
  330. set.delete(id);
  331. }
  332. };
  333. return set;
  334. }, Symbol("forciblyShowItemId"));
  335. export const useRendererDOM = installGlobalVar(() => ref<HTMLDivElement>())
  336. export const useTempStatus = installGlobalVar(() => {
  337. const temp = ref(false);
  338. const enterTemp = <T>(fn: () => T): T => {
  339. temp.value = true
  340. const result = fn()
  341. if (result instanceof Promise) {
  342. return result.then(async (data) => {
  343. temp.value = false
  344. await nextTick()
  345. return data;
  346. }).catch(r => {
  347. temp.value = false
  348. throw r
  349. }) as T
  350. } else {
  351. temp.value = false
  352. return result
  353. }
  354. }
  355. const dom = useRendererDOM()
  356. watch(temp, (_a, _b, onCleanup) => {
  357. if (temp.value && dom.value) {
  358. const instance = ElLoading.service({ fullscreen: true, target: dom.value })
  359. onCleanup(() => instance.close())
  360. }
  361. })
  362. return { tempStatus: temp, enterTemp }
  363. });
  364. const getFilters = <T>() => {
  365. type Val = (d: T) => T
  366. const globalFilter = ref<{[key in ShapeType]?: Val[]}>({})
  367. const shapeFilter = ref<Record<string, Val[]>>({})
  368. const setShapeFilter = (id: string, descs: Val) => {
  369. if (shapeFilter.value[id]) {
  370. shapeFilter.value[id].push(descs)
  371. } else {
  372. shapeFilter.value[id] = [descs]
  373. }
  374. return () => {
  375. if (shapeFilter.value[id]) {
  376. const ndx = shapeFilter.value[id].indexOf(descs)
  377. shapeFilter.value[id].splice(ndx, 1)
  378. }
  379. }
  380. }
  381. const setFilter = (type: ShapeType, descs: Val) => {
  382. if (globalFilter.value[type]) {
  383. globalFilter.value[type].push(descs)
  384. } else {
  385. globalFilter.value[type] = [descs]
  386. }
  387. return () => {
  388. if (globalFilter.value[type]) {
  389. const ndx = globalFilter.value[type].indexOf(descs)
  390. globalFilter.value[type].splice(ndx, 1)
  391. }
  392. }
  393. }
  394. return {
  395. setFilter,
  396. setShapeFilter,
  397. getFilter(type: ShapeType, id: string) {
  398. return (menus: T) => {
  399. if (globalFilter.value[type]) {
  400. for (const filter of globalFilter.value[type]) {
  401. menus = filter(menus)
  402. }
  403. }
  404. if (shapeFilter.value[id]) {
  405. for (const filter of shapeFilter.value[id]) {
  406. menus = filter(menus)
  407. }
  408. }
  409. return menus
  410. }
  411. }
  412. }
  413. }
  414. export const useMountMenusFilter = installGlobalVar(() => {
  415. const menusFilter = getFilters<PropertyDescribes>()
  416. return {
  417. setMenusFilter: menusFilter.setFilter,
  418. setShapeMenusFilter: menusFilter.setShapeFilter,
  419. getFilter: menusFilter.getFilter
  420. }
  421. })
  422. export const useMouseMenusFilter = installGlobalVar(() => {
  423. type Menu = { icon?: any; label?: string; handler: () => void }
  424. const propsFilter = getFilters<Menu[]>()
  425. return {
  426. setMenusFilter: propsFilter.setFilter,
  427. setShapeMenusFilter: propsFilter.setShapeFilter,
  428. getFilter: propsFilter.getFilter
  429. }
  430. })