use-component.ts 13 KB

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