use-selection.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  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. useFormalLayer,
  11. useHelperLayer,
  12. useGetFormalChildren,
  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. ref,
  25. toRaw,
  26. watch,
  27. watchEffect,
  28. } from "vue";
  29. import { EntityShape } from "@/deconstruction";
  30. import { Util } from "konva/lib/Util";
  31. import {
  32. useFixedScale,
  33. useViewerInvertTransform,
  34. useViewerInvertTransformConfig,
  35. } from "./use-viewer";
  36. import {
  37. debounce,
  38. diffArrayChange,
  39. frameEebounce,
  40. mergeFuns,
  41. onlyId,
  42. } from "@/utils/shared";
  43. import { IRect } from "konva/lib/types";
  44. import { useMouseShapesStatus } from "./use-mouse-status";
  45. import Icon from "../components/icon/temp-icon.vue";
  46. import { Group } from "konva/lib/Group";
  47. import { Component as GroupComp, GroupData } from "../components/group";
  48. import { useStore } from "../store";
  49. import { useGetShapeBelong, useOnComponentBoundChange } from "./use-component";
  50. import { useHistory } from "./use-history";
  51. import { isRectContained } from "@/utils/math";
  52. import { useTransformer } from "./use-transformer";
  53. import { IconData } from "../components/icon";
  54. import { usePause } from "./use-pause";
  55. import mitt, { Emitter } from "mitt";
  56. import { components, ShapeType, shapeTypes } from "../components";
  57. import { getFlatChildren } from "@/utils/shape";
  58. export const useExcludeSelection = installGlobalVar(() => ref<string[]>([]));
  59. // 多选不包含分组, 只包含选中者
  60. export const useSelection = installGlobalVar(() => {
  61. const layer = useHelperLayer();
  62. const eSelection = useExcludeSelection();
  63. const getChildren = useGetFormalChildren();
  64. const box = new Rect({
  65. stroke: themeColor,
  66. strokeWidth: 1,
  67. fill: "#fff",
  68. listening: false,
  69. opacity: 0.5,
  70. });
  71. const stage = useStage();
  72. const operMode = useOperMode();
  73. const selections = ref<EntityShape[]>();
  74. const transformer = useTransformer();
  75. const getShapeSelectionManage = useGetShapeSelectionManage();
  76. let itemShapeBoxs: IRect[][] = [];
  77. let itemShapes: EntityShape[][] = [];
  78. const updateSelections = () => {
  79. const boxRect = box.getClientRect();
  80. selections.value = [];
  81. for (let i = 0; i < itemShapeBoxs.length; i++) {
  82. for (let j = 0; j < itemShapeBoxs[i].length; j++) {
  83. const shape = itemShapes[i][j];
  84. const box = itemShapeBoxs[i][j];
  85. const itemSelects: EntityShape[] = [];
  86. if (
  87. Util.haveIntersection(boxRect, box) &&
  88. !isRectContained(box, boxRect)
  89. ) {
  90. if (!selections.value.includes(shape)) {
  91. selections.value.push(shape);
  92. itemSelects.push(shape);
  93. }
  94. }
  95. }
  96. }
  97. };
  98. const store = useStore();
  99. const init = (dom: HTMLDivElement, layer: Layer) => {
  100. store.bus.on("addItemAfter", updateInitData);
  101. store.bus.on("dataChangeAfter", updateInitData);
  102. const stopListener = dragListener(dom, {
  103. down(pos) {
  104. layer.add(box);
  105. box.x(pos.x);
  106. box.y(pos.y);
  107. box.width(0);
  108. box.height(0);
  109. },
  110. move({ end }) {
  111. box.width(end.x - box.x());
  112. box.height(end.y - box.y());
  113. updateSelections();
  114. },
  115. up() {
  116. selections.value = undefined;
  117. box.remove();
  118. },
  119. });
  120. return () => {
  121. store.bus.off("addItemAfter", updateInitData);
  122. store.bus.off("dataChangeAfter", updateInitData);
  123. stopListener();
  124. box.remove();
  125. };
  126. };
  127. const updateInitData = () => {
  128. itemShapes = getChildren().map((item) =>
  129. getFlatChildren(item).filter(
  130. (shape) =>
  131. !eSelection.value.includes(shape.id()) &&
  132. shape !== toRaw(transformer) &&
  133. getShapeSelectionManage(shape)?.canSelect(shape)
  134. )
  135. );
  136. itemShapeBoxs = itemShapes.map((shapes) =>
  137. shapes.map((shape) => shape.getClientRect())
  138. );
  139. };
  140. const stopWatch = globalWatch(
  141. () => operMode.value.mulSelection,
  142. (mulSelection, _, onCleanup) => {
  143. if (!mulSelection) return;
  144. const dom = stage.value?.getNode().container()!;
  145. updateInitData();
  146. onCleanup(init(dom, layer.value!));
  147. }
  148. );
  149. return {
  150. onDestroy: stopWatch,
  151. var: { selections, box },
  152. };
  153. });
  154. type ShapeIconArgs = Partial<
  155. Pick<IconData, "width" | "height" | "url" | "fill" | "stroke">
  156. >;
  157. export const useShapesIcon = (
  158. shapes: Ref<EntityShape[] | undefined>,
  159. args: ShapeIconArgs = {}
  160. ) => {
  161. const mParts = useMountParts();
  162. const { on } = useOnComponentBoundChange();
  163. const iconProps = {
  164. width: 12,
  165. height: 12,
  166. url: "./icons/state_s.svg",
  167. fill: themeColor,
  168. stroke: "#fff",
  169. ...args,
  170. listening: false,
  171. };
  172. const fScale = useFixedScale()
  173. const invMat = useViewerInvertTransform();
  174. const getShapeMat = (shape: EntityShape) => {
  175. const rect = shape.getClientRect();
  176. const center = invMat.value.point({
  177. x: rect.x + rect.width / 2,
  178. y: rect.y + rect.height / 2,
  179. });
  180. return [1, 0, 0, 1, center.x, center.y];
  181. };
  182. const unMountMap = new WeakMap<EntityShape, () => void>();
  183. const pause = usePause();
  184. const stop = watch(
  185. [() => [...(shapes.value || [])], () => pause.isPause],
  186. ([shapes], [oldShapes]) => {
  187. if (pause.isPause) {
  188. shapes = [];
  189. }
  190. const { added, deleted } = diffArrayChange(shapes || [], oldShapes || []);
  191. for (const addShape of added) {
  192. const mat = ref(getShapeMat(addShape));
  193. const data = reactive({ ...iconProps, mat: mat });
  194. const update = frameEebounce(() => {
  195. data.width = fScale.value * iconProps.width;
  196. data.height = fScale.value * iconProps.height;
  197. mat.value = getShapeMat(addShape);
  198. });
  199. const unHooks = [
  200. on(addShape, update),
  201. watch(fScale, update, { immediate: true, flush: "post" }),
  202. mParts.add({
  203. comp: markRaw(Icon),
  204. props: { data },
  205. }),
  206. ];
  207. unMountMap.set(addShape, mergeFuns(unHooks));
  208. }
  209. for (const delShape of deleted) {
  210. const fn = unMountMap.get(delShape);
  211. fn && fn();
  212. }
  213. }
  214. );
  215. return [stop, pause];
  216. };
  217. export type SelectionManageBus = Emitter<Record<"del" | "update", EntityShape>>;
  218. export type SelectionManage = {
  219. canSelect: (shape: EntityShape, selects?: EntityShape[]) => boolean;
  220. listener: (shape: EntityShape) => {
  221. stop: () => void;
  222. bus: SelectionManageBus;
  223. };
  224. };
  225. export type UseGetSelectionManage = () => SelectionManage;
  226. export const useStoreSelectionManage = installGlobalVar((): SelectionManage => {
  227. const store = useStore();
  228. const { on } = useOnComponentBoundChange();
  229. const canSelect = (shape: EntityShape) => {
  230. const id = shape.id();
  231. if (!id) {
  232. return false;
  233. }
  234. const item = store.items.find((item) => item.id === id);
  235. return !!(item && !item.lock);
  236. };
  237. const listener = (shape: EntityShape) => {
  238. const bus: SelectionManageBus = mitt();
  239. const stop = watch(
  240. () => canSelect(shape),
  241. (exixts, _, onCleanup) => {
  242. if (!exixts) {
  243. bus.emit("del", shape);
  244. } else {
  245. onCleanup(on(shape, () => bus.emit("update", shape)));
  246. }
  247. },
  248. { immediate: true }
  249. );
  250. return { stop, bus };
  251. };
  252. return { canSelect, listener };
  253. });
  254. export const useGetShapeSelectionManage = installGlobalVar(() => {
  255. const compManages: Partial<Record<ShapeType, SelectionManage>> = {};
  256. for (const type of shapeTypes) {
  257. compManages[type] =
  258. components[type].useGetSelectionManage &&
  259. components[type].useGetSelectionManage();
  260. }
  261. const storeManage = useStoreSelectionManage();
  262. const getShapeBelong = useGetShapeBelong();
  263. return (shape: EntityShape) => {
  264. const bl = getShapeBelong(shape);
  265. if (!bl) return;
  266. if (compManages[bl.type]) {
  267. return compManages[bl.type];
  268. } else if (bl.isSelf) {
  269. return storeManage;
  270. }
  271. };
  272. });
  273. export const useSelectionRevise = () => {
  274. const getShapeSelectionManage = useGetShapeSelectionManage();
  275. const mParts = useMountParts();
  276. const status = useMouseShapesStatus();
  277. const store = useStore();
  278. const { selections: rectSelects } = useSelection();
  279. let selfSet = false;
  280. const setSelectShapes = (shapes: EntityShape[]) => {
  281. selfSet = true;
  282. status.selects = shapes;
  283. selfSet = false;
  284. };
  285. let initSelections: EntityShape[] = [];
  286. watch(
  287. () => rectSelects.value && [...rectSelects.value],
  288. (rectSelects, oldRectSelects) => {
  289. if (!oldRectSelects) {
  290. initSelections = [...status.selects];
  291. } else if (!rectSelects) {
  292. initSelections = [];
  293. } else {
  294. setSelectShapes(initSelections.concat(rectSelects));
  295. }
  296. }
  297. );
  298. useShapesIcon(computed(() => status.selects));
  299. const filterSelect = debounce(() => {
  300. const selects = new Set<EntityShape>();
  301. for (const shape of status.selects) {
  302. const children = getFlatChildren(shape);
  303. children.forEach((childShape) => {
  304. const manage = getShapeSelectionManage(childShape);
  305. if (manage?.canSelect(childShape)) {
  306. selects.add(childShape);
  307. }
  308. });
  309. }
  310. setSelectShapes([...selects]);
  311. }, 16);
  312. store.bus.on("delItemAfter", filterSelect);
  313. store.bus.on("clearAfter", filterSelect);
  314. store.bus.on("dataChangeAfter", filterSelect);
  315. store.bus.on("setCurrentLayerAfter", filterSelect);
  316. watch(
  317. () => status.selects,
  318. () => selfSet || filterSelect(),
  319. { flush: "sync" }
  320. );
  321. const ids = computed(() => [
  322. ...new Set(status.selects.map((item) => item.id())),
  323. ]);
  324. const groupConfig = {
  325. id: onlyId(),
  326. createTime: Date.now(),
  327. lock: false,
  328. opacity: 1,
  329. ref: false,
  330. listening: false,
  331. stroke: themeColor,
  332. };
  333. const operMode = useOperMode();
  334. const layer = useFormalLayer();
  335. watch(
  336. () => operMode.value.mulSelection,
  337. (muls, olv) => {
  338. if (muls) {
  339. status.selects = [...status.selects, ...status.actives];
  340. }
  341. },
  342. { flush: "pre" }
  343. );
  344. watch(
  345. () => [!!ids.value.length, operMode.value.mulSelection],
  346. (_a, _b) => {
  347. const groupShape = layer.value?.findOne<Group>(`#${groupConfig.id}`);
  348. if (!groupShape) return;
  349. if (ids.value.length && !operMode.value.mulSelection) {
  350. status.actives = [groupShape];
  351. } else if (status.actives.includes(groupShape)) {
  352. status.actives = [];
  353. }
  354. }
  355. );
  356. const stage = useStage();
  357. const history = useHistory();
  358. const showItemId = useForciblyShowItemIds();
  359. watchEffect((onCleanup) => {
  360. if (!ids.value.length) return;
  361. const props = {
  362. data: { ...groupConfig, ids: ids.value },
  363. key: groupConfig.id,
  364. onUpdateShape(data: GroupData) {
  365. // status.selects;
  366. // data.ids;
  367. },
  368. onDelShape() {
  369. setSelectShapes([]);
  370. },
  371. onAddShape(data: GroupData) {
  372. history.onceTrack(() => {
  373. const ids = data.ids;
  374. const groups = store.typeItems.group;
  375. const exists = groups?.some((group) => {
  376. if (group.ids.length !== ids.length) return false;
  377. const diff = diffArrayChange(group.ids, ids);
  378. return diff.added.length === 0 && diff.deleted.length == 0;
  379. });
  380. if (exists) return;
  381. store.addItem("group", { ...data, ids });
  382. showItemId.cycle(data.id, async () => {
  383. await nextTick();
  384. const $stage = stage.value!.getNode();
  385. const addShape = $stage.findOne("#" + data.id) as EntityShape;
  386. setSelectShapes([addShape]);
  387. });
  388. });
  389. },
  390. };
  391. onCleanup(mParts.add({ comp: markRaw(GroupComp), props }));
  392. });
  393. };