use-component.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491
  1. import { DC, EntityShape } from "@/deconstruction";
  2. import {
  3. computed,
  4. EmitFn,
  5. isRef,
  6. markRaw,
  7. nextTick,
  8. onUnmounted,
  9. reactive,
  10. ref,
  11. Ref,
  12. shallowReactive,
  13. shallowRef,
  14. watch,
  15. watchEffect,
  16. } from "vue";
  17. import { useAutomaticData } from "./use-automatic-data";
  18. import { useCurrentZIndex, useZIndex } from "./use-layer";
  19. import {
  20. useAnimationMouseStyle,
  21. useMouseShapesStatus,
  22. } from "./use-mouse-status";
  23. import { components, DrawItem, ShapeType } from "../components";
  24. import { useMatCompTransformer, useLineTransformer } from "./use-transformer";
  25. import { useGetShapeCopyTransform } from "./use-copy";
  26. import { asyncTimeout, copy, mergeFuns, onlyId } from "@/utils/shared";
  27. import { Shape } from "konva/lib/Shape";
  28. import { Transform } from "konva/lib/Util";
  29. import {
  30. mergeDescribes,
  31. PropertyDescribes,
  32. PropertyKeys,
  33. } from "../html-mount/propertys";
  34. import { itemCopyHandler, useStore } from "../store";
  35. import { globalWatch, useMountMenusFilter, useStage } from "./use-global-vars";
  36. import { useAlignmentShape } from "./use-alignment";
  37. import { useViewerTransform } from "./use-viewer";
  38. import { usePause } from "./use-pause";
  39. import { useGlobalDescribes } from "./use-group";
  40. import { ui18n } from "@/lang";
  41. type Emit<T> = EmitFn<{
  42. updateShape: (value: T) => void;
  43. addShape: (value: T) => void;
  44. delShape: () => void;
  45. }>;
  46. export const useComponentMenus = <T extends DrawItem>(
  47. shape: Ref<DC<EntityShape> | undefined>,
  48. data: Ref<T>,
  49. emit: Emit<T>,
  50. alignment?: (data: T, mat: Transform) => void,
  51. copyHandler?: (transform: Transform, data: T) => T,
  52. ) => {
  53. const operateMenus: Array<{
  54. icon?: any;
  55. label?: string;
  56. hide?: boolean;
  57. handler: () => void;
  58. }> = shallowReactive([]);
  59. // 置顶 置底
  60. const currentZIndex = useCurrentZIndex();
  61. operateMenus.push(
  62. reactive({
  63. label: computed(() =>
  64. data.value.lock ? ui18n.t("sys.unlock") : ui18n.t("sys.lock"),
  65. ) as any,
  66. handler() {
  67. data.value.lock = !data.value.lock;
  68. emit("updateShape", { ...data.value });
  69. },
  70. }),
  71. reactive({
  72. label: ui18n.t("sys.hide"),
  73. hide: true,
  74. handler() {
  75. data.value.hide = true;
  76. emit("updateShape", { ...data.value });
  77. },
  78. }),
  79. // {
  80. // label: `上移`,
  81. // icon: Top,
  82. // handler() {
  83. // data.value.zIndex += 1;
  84. // emit("updateShape", { ...data.value });
  85. // },
  86. // },
  87. // {
  88. // label: `下移`,
  89. // icon: Bottom,
  90. // handler() {
  91. // data.value.zIndex -= 1;
  92. // emit("updateShape", { ...data.value });
  93. // },
  94. // },
  95. );
  96. if (alignment) {
  97. const [alignmentShape] = useAlignmentShape(shape);
  98. operateMenus.push({
  99. label: ui18n.t("sys.alignment"),
  100. async handler() {
  101. const mat = await alignmentShape();
  102. alignment(data.value, mat);
  103. emit("updateShape", copy({ ...data.value }));
  104. // let ndata = { ...data.value, id: onlyId() }
  105. // emit('addShape', ndata)
  106. // emit('delShape')
  107. },
  108. });
  109. }
  110. if (copyHandler) {
  111. const getCopyTransform = useGetShapeCopyTransform(shape);
  112. const status = useMouseShapesStatus();
  113. const stage = useStage();
  114. operateMenus.push({
  115. label: ui18n.t("sys.copy"),
  116. async handler() {
  117. const transform = getCopyTransform();
  118. const copyData = itemCopyHandler(copyHandler(
  119. transform,
  120. JSON.parse(JSON.stringify(data.value)) as T,
  121. ));
  122. copyData.id = onlyId();
  123. emit("addShape", copyData);
  124. status.actives = [];
  125. await asyncTimeout(100);
  126. const $stage = stage.value?.getNode();
  127. if (!$stage) return;
  128. const $shape = $stage.findOne<Shape>(`#${copyData.id}`);
  129. if ($shape) {
  130. status.actives = [$shape];
  131. }
  132. },
  133. });
  134. }
  135. operateMenus.push(
  136. {
  137. label: ui18n.t("sys.ztop"),
  138. handler() {
  139. data.value.zIndex = currentZIndex.max + 1;
  140. emit("updateShape", { ...data.value });
  141. },
  142. },
  143. {
  144. label: ui18n.t("sys.zbot"),
  145. handler() {
  146. data.value.zIndex = currentZIndex.min - 1;
  147. emit("updateShape", { ...data.value });
  148. },
  149. },
  150. );
  151. if (!data.value.disableDelete) {
  152. operateMenus.push({
  153. label: ui18n.t("sys.del"),
  154. handler() {
  155. emit("delShape");
  156. },
  157. });
  158. }
  159. return operateMenus;
  160. };
  161. export type UseComponentStatusProps<
  162. T extends DrawItem,
  163. S extends EntityShape,
  164. > = {
  165. emit: Emit<T>;
  166. type?: ShapeType;
  167. props: { data: T };
  168. alignment?: (data: T, mat: Transform) => void;
  169. getMouseStyle: any;
  170. defaultStyle: any;
  171. propertys: PropertyKeys;
  172. selfData?: boolean;
  173. debug?: boolean;
  174. noJoinZindex?: boolean;
  175. noOperateMenus?: boolean;
  176. transformType?: "line" | "mat" | "custom";
  177. customTransform?: (
  178. callback: () => void,
  179. shape: Ref<DC<S> | undefined>,
  180. data: Ref<T>,
  181. ) => void;
  182. getRepShape?: () => Shape;
  183. copyHandler?: (transform: Transform, data: T) => T;
  184. };
  185. export const useComponentDescribes = <T extends { id: string }>(
  186. data: Ref<T>,
  187. propertys: PropertyKeys,
  188. defaultStyle: any,
  189. ) => {
  190. const store = useStore();
  191. const id = computed(() => data.value.id);
  192. const type = computed(() => id.value && store.getType(id.value));
  193. const initDescs = mergeDescribes(data, defaultStyle, propertys || []);
  194. const { getFilter } = useMountMenusFilter();
  195. const gdescs = useGlobalDescribes();
  196. let descs = ref(initDescs);
  197. watchEffect(() => {
  198. const iDescs =
  199. type.value && id.value
  200. ? getFilter(type.value, id.value)(initDescs)
  201. : initDescs;
  202. descs.value = Object.fromEntries(
  203. Object.entries(iDescs).sort(
  204. ([_a, a], [_b, b]) => (b.sort || -1) - (a.sort || -1),
  205. ),
  206. ) as PropertyDescribes;
  207. });
  208. watchEffect((onCleanup) => {
  209. gdescs.set(data.value, descs.value);
  210. onCleanup(() => {
  211. gdescs.del(data.value.id);
  212. });
  213. });
  214. watch(
  215. descs,
  216. (descs) => {
  217. for (const key in descs) {
  218. const initProps = descs[key].props;
  219. watchEffect(() => {
  220. if (!type.value) return;
  221. const getPredefine = components[type.value].getPredefine;
  222. const predefine =
  223. getPredefine && (getPredefine as any)(key as keyof DrawItem);
  224. if (!predefine) return;
  225. if (initProps) {
  226. descs[key].props = { ...initProps, ...predefine };
  227. } else {
  228. descs[key].props = predefine;
  229. }
  230. });
  231. }
  232. },
  233. { immediate: true },
  234. );
  235. return descs;
  236. };
  237. export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
  238. args: UseComponentStatusProps<T, S>,
  239. ) => {
  240. const shape = shallowRef<DC<S>>();
  241. watchEffect(
  242. () => {
  243. if (shape.value) {
  244. markRaw(shape.value);
  245. }
  246. },
  247. { flush: "sync" },
  248. );
  249. const data = useAutomaticData(
  250. () => args.props.data,
  251. (data) => (args.selfData ? data : copy(data)),
  252. );
  253. const [style, pause, resume] = useAnimationMouseStyle({
  254. data: data,
  255. shape,
  256. getMouseStyle: args.getMouseStyle,
  257. }) as any;
  258. watchEffect(() => {
  259. if (data.value.lock) {
  260. pause();
  261. } else {
  262. resume();
  263. }
  264. });
  265. if (args.transformType === "line") {
  266. useLineTransformer(
  267. shape as any,
  268. data as any,
  269. (newData) => args.emit("updateShape", newData as T),
  270. args.getRepShape as any,
  271. );
  272. } else if (args.transformType === "mat") {
  273. useMatCompTransformer(shape, data as any, (nData) =>
  274. args.emit("updateShape", nData as any),
  275. );
  276. } else if (args.transformType === "custom" && args.customTransform) {
  277. args.customTransform(
  278. () => args.emit("updateShape", data.value as any),
  279. shape,
  280. data,
  281. );
  282. }
  283. if (!args.noJoinZindex) {
  284. useZIndex(shape, data);
  285. }
  286. const defStyle = { ...args.defaultStyle };
  287. return {
  288. data,
  289. style,
  290. tData: computed(() => {
  291. const tData = { ...defStyle, ...data.value };
  292. if (style) {
  293. Object.assign(tData, style.value);
  294. }
  295. return tData;
  296. }),
  297. shape,
  298. operateMenus: args.noOperateMenus
  299. ? []
  300. : useComponentMenus(
  301. shape,
  302. data,
  303. args.emit,
  304. args.alignment,
  305. args.copyHandler,
  306. ),
  307. describes: useComponentDescribes(
  308. data,
  309. args.propertys || [],
  310. args.defaultStyle,
  311. ),
  312. };
  313. };
  314. export const useGetShapeBelong = () => {
  315. const store = useStore();
  316. return (shape: EntityShape) => {
  317. let curId = shape.id();
  318. let id: string;
  319. let item: DrawItem;
  320. let type: ShapeType;
  321. do {
  322. id = shape.id();
  323. item = store.getItemById(id)!;
  324. type = store.getType(id)!;
  325. if (item && type) {
  326. break;
  327. }
  328. } while ((shape = shape.parent as any));
  329. return item ? { item, isSelf: curId === id, type, id, curId } : null;
  330. };
  331. };
  332. export const useGetComponentData = () => {
  333. const store = useStore();
  334. const getShapeBelong = useGetShapeBelong();
  335. return (shape: Ref<EntityShape | undefined> | EntityShape | undefined) =>
  336. computed(() => {
  337. shape = isRef(shape) ? shape.value : shape;
  338. if (!shape?.id()) return;
  339. let item: any = store.getItemById(shape.id());
  340. if (!item) {
  341. const belong = getShapeBelong(shape);
  342. if (belong && !belong.isSelf) {
  343. const getter = components[belong.type].childrenDataGetter;
  344. let parent: any;
  345. if (getter && (parent = store.getItemById(belong.id))) {
  346. item = getter(parent, belong.curId);
  347. }
  348. }
  349. }
  350. return item;
  351. });
  352. };
  353. export const useComponentsAttach = <T>(
  354. getter: <K extends ShapeType>(type: K, data: DrawItem<K>) => T,
  355. types = Object.keys(components) as ShapeType[],
  356. ) => {
  357. const store = useStore();
  358. const attachs = reactive([]) as T[];
  359. const cleanups = [] as Array<() => void>;
  360. for (const type of types) {
  361. cleanups.push(
  362. globalWatch(
  363. () => store.getTypeItems(type),
  364. (_a, _, onCleanup) => {
  365. const items = store.getTypeItems(type);
  366. if (!items) return;
  367. for (const item of items) {
  368. const attachWatchStop = watchEffect((onCleanup) => {
  369. const attach = getter(type, item);
  370. attachs.push(attach);
  371. onCleanup(() => {
  372. const ndx = attachs.indexOf(attach);
  373. ~ndx && attachs.splice(ndx, 1);
  374. });
  375. });
  376. const existsWatchStop = watchEffect(() => {
  377. if (!items.includes(item)) {
  378. attachWatchStop();
  379. existsWatchStop();
  380. }
  381. });
  382. onCleanup(() => {
  383. attachWatchStop();
  384. existsWatchStop();
  385. });
  386. }
  387. },
  388. { immediate: true, deep: true },
  389. ),
  390. );
  391. }
  392. return {
  393. attachs,
  394. cleanup: mergeFuns(cleanups),
  395. };
  396. };
  397. export const useOnComponentBoundChange = () => {
  398. const getComponentData = useGetComponentData();
  399. const transform = useViewerTransform();
  400. const quitHooks = [] as Array<() => void>;
  401. const destory = () => mergeFuns(quitHooks)();
  402. const on = <T extends EntityShape>(
  403. shapes: Ref<T | T[] | undefined> | T | T[] | undefined,
  404. callback: (shape: T, type: "transform" | "data") => void,
  405. viewListener = true,
  406. ) => {
  407. const $shapes = computed(() => {
  408. shapes = isRef(shapes) ? shapes.value : shapes;
  409. if (!shapes) return [];
  410. return Array.isArray(shapes) ? shapes : [shapes];
  411. });
  412. const update = ($shape: T, type?: "transform" | "data") => {
  413. if (api.isPause) return;
  414. callback($shape, type || "data");
  415. };
  416. const shapeListener = (shape: T) => {
  417. const repShape = (shape.repShape as T) || shape;
  418. const syncBd = () => update(repShape);
  419. repShape.on("transform", syncBd);
  420. shape.on("bound-change", syncBd);
  421. return () => {
  422. repShape.off("transform", syncBd);
  423. shape.off("bound-change", syncBd);
  424. };
  425. };
  426. const cleanups: (() => void)[] = [];
  427. if (viewListener) {
  428. cleanups.push(
  429. watch(transform, () =>
  430. $shapes.value.forEach(($shape) => update($shape, "transform")),
  431. ),
  432. );
  433. }
  434. cleanups.push(
  435. watch(
  436. $shapes,
  437. ($shapes, _, onCleanup) => {
  438. const cleanups = $shapes.flatMap(($shape) => {
  439. let item = getComponentData($shape);
  440. return [
  441. watch(item, () => nextTick(() => update($shape, "data")), {
  442. deep: true,
  443. }),
  444. shapeListener($shape),
  445. ];
  446. });
  447. onCleanup(mergeFuns(cleanups));
  448. },
  449. { immediate: true },
  450. ),
  451. );
  452. const onDestroy = mergeFuns(cleanups);
  453. quitHooks.push(onDestroy);
  454. return () => {
  455. const ndx = quitHooks.indexOf(onDestroy);
  456. ~ndx && quitHooks.splice(ndx, 1);
  457. onDestroy();
  458. };
  459. };
  460. const api = usePause({ destory, on });
  461. onUnmounted(destory);
  462. return api;
  463. };