use-selection.ts 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. import { Rect } from "konva/lib/shapes/Rect";
  2. import {
  3. globalWatch,
  4. installGlobalVar,
  5. useForciblyShowItemIds,
  6. useMountParts,
  7. useStage,
  8. } from "./use-global-vars";
  9. import {
  10. useGetFormalChildren,
  11. useFormalLayer,
  12. useHelperLayer,
  13. } from "./use-layer";
  14. import { themeColor } from "@/constant";
  15. import { dragListener } from "@/utils/event";
  16. import { Layer } from "konva/lib/Layer";
  17. import { useOperMode } from "./use-status";
  18. import {
  19. computed,
  20. markRaw,
  21. nextTick,
  22. reactive,
  23. ref,
  24. toRaw,
  25. watch,
  26. watchEffect,
  27. } from "vue";
  28. import { EntityShape } from "@/deconstruction";
  29. import { Util } from "konva/lib/Util";
  30. import { useViewerInvertTransform, useViewerInvertTransformConfig } from "./use-viewer";
  31. import { debounce, diffArrayChange, mergeFuns, onlyId } from "@/utils/shared";
  32. import { IRect } from "konva/lib/types";
  33. import { useMouseShapesStatus } from "./use-mouse-status";
  34. import Icon from "../components/icon/temp-icon.vue";
  35. import { Group } from "konva/lib/Group";
  36. import { Component as GroupComp, GroupData } from "../components/group/";
  37. import { DrawStore, useStore } from "../store";
  38. import { DrawItem } from "../components";
  39. import { Stage } from "konva/lib/Stage";
  40. import { useOnComponentBoundChange } from "./use-component";
  41. import { useHistory } from "./use-history";
  42. import { isRectContained } from "@/utils/math";
  43. import { useTransformer } from "./use-transformer";
  44. const normalSelectIds = (
  45. store: DrawStore,
  46. ids: string[],
  47. needChildren = false
  48. ) => {
  49. if (!store.typeItems.group) return ids;
  50. const gChildrenIds = store.typeItems.group.map((item) => item.ids);
  51. const findNdx = (id: string) =>
  52. gChildrenIds.findIndex((cIds) => cIds.includes(id));
  53. if (!needChildren) {
  54. return ids.filter((id) => !~findNdx(id));
  55. }
  56. const groupIds = store.typeItems.group.map((item) => item.id);
  57. const nIds: string[] = [];
  58. for (let i = 0; i < ids.length; i++) {
  59. let ndx = findNdx(ids[i]);
  60. ~ndx || (ndx = groupIds.indexOf(ids[i]));
  61. if (!~ndx) {
  62. nIds.push(ids[i]);
  63. continue;
  64. }
  65. const group = store.typeItems.group[ndx];
  66. const addIds = [group.id, ...group.ids].filter(
  67. (aid) => !nIds.includes(aid)
  68. );
  69. nIds.push(...addIds);
  70. }
  71. return nIds;
  72. };
  73. export const normalSelectShapes = (
  74. stage: Stage,
  75. store: DrawStore,
  76. shapes: EntityShape[],
  77. needChildren = false
  78. ) => {
  79. let ids: string[] = [];
  80. for (let i = 0; i < shapes.length; i++) {
  81. const shape = shapes[i];
  82. const id = shape.id();
  83. id && ids.push(id);
  84. }
  85. ids = normalSelectIds(store, ids, needChildren);
  86. return ids.map((id) => stage.findOne(`#${id}`)!) as EntityShape[];
  87. };
  88. export const normalSelectItems = (
  89. store: DrawStore,
  90. items: DrawItem[],
  91. needChildren = false
  92. ) => {
  93. return normalSelectIds(
  94. store,
  95. items.map((item) => item.id),
  96. needChildren
  97. ).map((id) => store.getItemById(id)!);
  98. };
  99. export const useExcludeSelection = installGlobalVar(() => ref<string[]>([]))
  100. export const useSelection = installGlobalVar(() => {
  101. const layer = useHelperLayer();
  102. const eSelection = useExcludeSelection()
  103. const getChildren = useGetFormalChildren();
  104. const box = new Rect({
  105. stroke: themeColor,
  106. strokeWidth: 1,
  107. fill: "#fff",
  108. listening: false,
  109. opacity: 0.5,
  110. });
  111. const stage = useStage();
  112. const operMode = useOperMode();
  113. const selections = ref<EntityShape[]>();
  114. const transformer = useTransformer();
  115. let shapeBoxs: IRect[] = [];
  116. let shapes: EntityShape[] = [];
  117. const updateSelections = () => {
  118. const boxRect = box.getClientRect();
  119. selections.value = [];
  120. for (let i = 0; i < shapeBoxs.length; i++) {
  121. if (
  122. Util.haveIntersection(boxRect, shapeBoxs[i]) &&
  123. !isRectContained(shapeBoxs[i], boxRect) &&
  124. shapes[i] !== toRaw(transformer)
  125. ) {
  126. if (!selections.value.includes(shapes[i])) {
  127. selections.value.push(shapes[i]);
  128. }
  129. }
  130. }
  131. };
  132. const init = (dom: HTMLDivElement, layer: Layer) => {
  133. const stopListener = dragListener(dom, {
  134. down(pos) {
  135. layer.add(box);
  136. box.x(pos.x);
  137. box.y(pos.y);
  138. box.width(0);
  139. box.height(0);
  140. },
  141. move({ end }) {
  142. box.width(end.x - box.x());
  143. box.height(end.y - box.y());
  144. updateSelections();
  145. },
  146. up() {
  147. selections.value = undefined;
  148. box.remove();
  149. },
  150. });
  151. return () => {
  152. stopListener();
  153. box.remove();
  154. };
  155. };
  156. const updateInitData = () => {
  157. shapes = getChildren().filter(shape => !eSelection.value.includes(shape.id()));
  158. shapeBoxs = shapes.map((shape) => shape.getClientRect());
  159. };
  160. const stopWatch = globalWatch(
  161. () => operMode.value.mulSelection,
  162. (mulSelection, _, onCleanup) => {
  163. if (!mulSelection) return;
  164. const dom = stage.value?.getNode().container()!;
  165. updateInitData();
  166. onCleanup(init(dom, layer.value!));
  167. }
  168. );
  169. return {
  170. onDestroy: stopWatch,
  171. var: {selections, box},
  172. };
  173. });
  174. export const useSelectionShowIcons = installGlobalVar(() => {
  175. const mParts = useMountParts();
  176. const { on } = useOnComponentBoundChange();
  177. const iconProps = {
  178. width: 12,
  179. height: 12,
  180. url: "./icons/state_s.svg",
  181. fill: themeColor,
  182. stroke: "#fff",
  183. listening: false,
  184. };
  185. const invConfig = useViewerInvertTransformConfig()
  186. const status = useMouseShapesStatus();
  187. const getChildren = useGetFormalChildren()
  188. const store = useStore();
  189. const invMat = useViewerInvertTransform();
  190. const getShapeMat = (shape: EntityShape) => {
  191. const rect = shape.getClientRect();
  192. const center = invMat.value.point({
  193. x: rect.x + rect.width / 2,
  194. y: rect.y + rect.height / 2,
  195. });
  196. return [1, 0, 0, 1, center.x, center.y];
  197. };
  198. const shapes = computed(() => {
  199. const child = getChildren()
  200. return status.selects.filter((shape) => store.getType(shape.id()) !== "group" && child.includes(shape))
  201. });
  202. const unMountMap = new WeakMap<EntityShape, () => void>();
  203. watch(shapes, (shapes, oldShapes) => {
  204. const { added, deleted } = diffArrayChange(shapes, oldShapes);
  205. for (const addShape of added) {
  206. const mat = ref(getShapeMat(addShape));
  207. const data = reactive({ ...iconProps, mat: mat })
  208. const unHooks = [
  209. on(addShape, () => (mat.value = getShapeMat(addShape))),
  210. watch(invConfig, () => {
  211. data.width = invConfig.value.scaleX * iconProps.width
  212. data.height = invConfig.value.scaleY * iconProps.height
  213. }, {immediate: true}),
  214. mParts.add({
  215. comp: markRaw(Icon),
  216. props: { data },
  217. }),
  218. ];
  219. unMountMap.set(addShape, mergeFuns(unHooks));
  220. }
  221. for (const delShape of deleted) {
  222. const fn = unMountMap.get(delShape);
  223. fn && fn();
  224. }
  225. });
  226. });
  227. const useWatchSelection = () => {
  228. const status = useMouseShapesStatus();
  229. const addShapes = (allShapes: Set<EntityShape>, iShapes: EntityShape[]) => {
  230. iShapes.forEach((shape) => allShapes.add(toRaw(shape)));
  231. return allShapes;
  232. };
  233. const delShapes = (allShapes: Set<EntityShape>, dShapes: EntityShape[]) => {
  234. dShapes.forEach((item) => allShapes.delete(toRaw(item)));
  235. return allShapes;
  236. };
  237. // 分组管理
  238. const watchSelection = () =>
  239. watch(
  240. () => status.selects,
  241. (shapes) => {
  242. const fShapes = Array.from(new Set(shapes));
  243. const { added, deleted } = diffArrayChange(shapes, fShapes);
  244. if (added.length || deleted.length) {
  245. status.selects = fShapes;
  246. }
  247. },
  248. { flush: "post" }
  249. );
  250. return {
  251. addShapes,
  252. delShapes,
  253. watchSelection,
  254. };
  255. };
  256. const useWatchSelectionGroup = () => {
  257. const stage = useStage();
  258. const store = useStore();
  259. const status = useMouseShapesStatus();
  260. const addShapes = (allShapes: Set<EntityShape>, iShapes: EntityShape[]) => {
  261. const shapes = normalSelectShapes(
  262. stage.value!.getNode(),
  263. store,
  264. iShapes,
  265. true
  266. );
  267. shapes.forEach((shape) => allShapes.add(shape));
  268. return allShapes;
  269. };
  270. const delShapes = (allShapes: Set<EntityShape>, dShapes: EntityShape[]) => {
  271. const shapes = normalSelectShapes(
  272. stage.value!.getNode(),
  273. store,
  274. dShapes,
  275. true
  276. );
  277. shapes.forEach((item) => allShapes.delete(item));
  278. return allShapes;
  279. };
  280. // 分组管理
  281. const watchSelection = () =>
  282. watch(
  283. () => status.selects,
  284. (shapes, oldShapes) => {
  285. const { added, deleted } = diffArrayChange(shapes, oldShapes);
  286. const filterShapes = new Set(shapes);
  287. added.length && addShapes(filterShapes, added);
  288. deleted.length && delShapes(filterShapes, deleted);
  289. if (added.length || deleted.length) {
  290. status.selects = Array.from(filterShapes);
  291. }
  292. },
  293. { flush: "post" }
  294. );
  295. return {
  296. addShapes,
  297. delShapes,
  298. watchSelection,
  299. };
  300. };
  301. export const useSelectionRevise = () => {
  302. const mParts = useMountParts();
  303. const status = useMouseShapesStatus();
  304. const store = useStore();
  305. const eSelection = useExcludeSelection()
  306. const { addShapes, delShapes, watchSelection } = useWatchSelection();
  307. useSelectionShowIcons();
  308. const getFormatChildren = useGetFormalChildren();
  309. const filterSelect = debounce(() => {
  310. const children = getFormatChildren();
  311. const mouseSelects = status.selects.filter((shape) =>
  312. !eSelection.value.includes(shape.id()) && children.includes(shape)
  313. );
  314. status.selects = mouseSelects;
  315. }, 16);
  316. store.bus.on("delItemAfter", filterSelect);
  317. store.bus.on("clearAfter", filterSelect);
  318. store.bus.on("dataChangeAfter", filterSelect);
  319. store.bus.on("setCurrentLayerAfter", filterSelect);
  320. const { selections: rectSelects } = useSelection();
  321. let initSelections: EntityShape[] = [];
  322. let stopWatchSelection = watchSelection();
  323. watch(
  324. () => rectSelects.value && [...rectSelects.value],
  325. (rectSelects, oldRectSelects) => {
  326. if (!oldRectSelects) {
  327. initSelections = [...status.selects];
  328. stopWatchSelection();
  329. } else if (!rectSelects) {
  330. initSelections = [];
  331. stopWatchSelection = watchSelection();
  332. } else {
  333. status.selects = Array.from(
  334. addShapes(new Set(initSelections), rectSelects)
  335. );
  336. // filterSelect()
  337. }
  338. }
  339. );
  340. const ids = computed(() => [...new Set(status.selects.map((item) => item.id()))]);
  341. const groupConfig = {
  342. id: onlyId(),
  343. createTime: Date.now(),
  344. lock: false,
  345. opacity: 1,
  346. ref: false,
  347. listening: false,
  348. stroke: themeColor,
  349. };
  350. const operMode = useOperMode();
  351. const layer = useFormalLayer();
  352. watch(
  353. () => [!!ids.value.length, operMode.value.mulSelection],
  354. (_a, _b) => {
  355. const groupShape = layer.value?.findOne<Group>(`#${groupConfig.id}`);
  356. if (!groupShape) return;
  357. if (ids.value.length && !operMode.value.mulSelection) {
  358. status.actives = [groupShape];
  359. } else if (status.actives.includes(groupShape)) {
  360. status.actives = [];
  361. }
  362. }
  363. );
  364. const stage = useStage();
  365. const history = useHistory();
  366. const showItemId = useForciblyShowItemIds();
  367. watchEffect((onCleanup) => {
  368. if (!ids.value.length) return;
  369. const props = {
  370. data: { ...groupConfig, ids: ids.value },
  371. key: groupConfig.id,
  372. onUpdateShape(data: GroupData) {
  373. status.selects;
  374. data.ids;
  375. },
  376. onDelShape() {
  377. status.selects = [];
  378. },
  379. onAddShape(data: GroupData) {
  380. history.onceTrack(() => {
  381. const ids = data.ids;
  382. const cIds = ids.filter((id) => store.getType(id) !== "group");
  383. const groups = store.typeItems.group;
  384. const exists = groups?.some((group) => {
  385. if (group.ids.length !== cIds.length) return false;
  386. const diff = diffArrayChange(group.ids, cIds);
  387. return diff.added.length === 0 && diff.deleted.length == 0;
  388. });
  389. if (exists) return;
  390. let selects = new Set(status.selects);
  391. for (let i = 0; i < ids.length; i++) {
  392. if (store.getType(ids[i]) === "group") {
  393. delShapes(
  394. selects,
  395. status.selects.filter((shape) => shape.id() === ids[i])
  396. );
  397. store.delItem("group", ids[i]);
  398. }
  399. }
  400. store.addItem("group", { ...data, ids: cIds });
  401. showItemId.cycle(data.id, async () => {
  402. await nextTick();
  403. const $stage = stage.value!.getNode();
  404. const addShape = $stage.findOne("#" + data.id) as EntityShape;
  405. addShapes(selects, [addShape]);
  406. status.selects = Array.from(selects);
  407. });
  408. });
  409. },
  410. };
  411. onCleanup(mParts.add({ comp: markRaw(GroupComp), props }));
  412. });
  413. };