浏览代码

feat: 添加多选管理器

bill 2 月之前
父节点
当前提交
f7e735c8e6

+ 3 - 7
src/core/components/group/index.ts

@@ -3,7 +3,6 @@ import { getMouseColors } from "@/utils/colors.ts";
 import { DrawItem, InteractiveFix, InteractiveTo, MatResponseProps } from "../index.ts";
 import { Transform } from "konva/lib/Util";
 import { components } from "../index";
-import { normalSelectItems } from "@/core/hook/use-selection.ts";
 
 export { default as Component } from "./group.vue";
 export { default as TempComponent } from "./temp-group.vue";
@@ -78,12 +77,9 @@ export const matResponse = ({data, mat, store}: MatResponseProps<'group'>, prevM
     if (!item) continue;
     items.push(item)
   }
-  let fitems = normalSelectItems(store, items, false)
-
-  fitems = fitems.length ? fitems : items
-  for (let i = 0; i < fitems.length; i++) {
-    const type = store.getType(fitems[i].id)!
-    const item = fitems[i]
+  for (let i = 0; i < items.length; i++) {
+    const type = store.getType(items[i].id)!
+    const item = items[i]
     components[type].matResponse({ data: item as any, mat: incMat, increment: true, store });
     store.setItem(type, { value: item, id: item.id });
   }

+ 4 - 0
src/core/components/index.ts

@@ -31,6 +31,7 @@ import { Transform } from "konva/lib/Util";
 import { DrawStore } from "../store";
 import { DrawHistory } from "../hook/use-history";
 import { TransformerVectorType } from "../hook/use-transformer";
+import { UseGetSelectionManage } from "../hook/use-selection";
 
 const _components = {
   arrow,
@@ -59,6 +60,7 @@ type CompAttach<key extends ShapeType> = {
   getSnapInfos?: (items: DrawItem<key>) => ComponentSnapInfo[];
   GroupComponent: (props: { data: DrawItem[] }) => any;
   getPredefine?: (attrKey: keyof DrawItem<key>) => any;
+  useGetSelectionManage?: UseGetSelectionManage
 };
 type _Components = {
   [key in keyof typeof _components]: (typeof _components)[key];
@@ -133,3 +135,5 @@ export type MatResponseProps<T extends ShapeType> = {
   operType?: TransformerVectorType;
   store?: DrawStore;
 };
+
+export const shapeTypes = Object.keys(components) as ShapeType[]

+ 2 - 2
src/core/components/line/temp-line.vue

@@ -17,10 +17,10 @@
         @del-line="delLineHandler(item)"
       />
     </v-group>
-    <v-rect
+    <!-- <v-rect
       v-if="operMode.mulSelection"
       :config="{ fill: 'red', ...lineBox, opacity: 0.1 }"
-    />
+    /> -->
   </v-group>
 </template>
 

+ 7 - 7
src/core/hook/use-component.ts

@@ -263,20 +263,20 @@ export const useGetShapeBelong = () => {
   return (shape: EntityShape) => {
     let curId = shape.id();
     let id: string;
-    let item: DrawItem | undefined;
-    let type: ShapeType | undefined;
+    let item: DrawItem;
+    let type: ShapeType;
     do {
       id = shape.id();
-      item = store.getItemById(id);
-      type = store.getType(id);
+      item = store.getItemById(id)!;
+      type = store.getType(id)!;
       if (item && type) {
         break;
       }
     } while ((shape = shape.parent as any));
 
-    return item ? [item, curId === id, type, id] as const : null
-  }
-}
+    return item ? { item, isSelf: curId === id, type, id, curId } : null;
+  };
+};
 
 export const useGetComponentData = <D extends DrawItem>() => {
   const store = useStore();

+ 25 - 47
src/core/hook/use-expose.ts

@@ -27,15 +27,13 @@ import { useMouseShapesStatus } from "./use-mouse-status.ts";
 import { Mode } from "@/constant/mode.ts";
 import { ElMessageBox } from "element-plus";
 import { mergeFuns } from "@/utils/shared.ts";
-import { themeColor } from "@/constant";
 import { getImage, isSvgString } from "@/utils/resource.ts";
 import { useResourceHandler } from "./use-fetch.ts";
 import { useConfig } from "./use-config.ts";
 import { useSelectionRevise } from "./use-selection.ts";
 import { useFormalLayer, useGetFormalChildren } from "./use-layer.ts";
-import { components, DrawItem } from "../components/index.ts";
+import { components } from "../components/index.ts";
 import { useProportion } from "./use-proportion.ts";
-import { ShapeType } from "@/index.ts";
 import { useGetDXF } from "./use-dxf.ts";
 import { getIconStyle } from "../components/icon/index.ts";
 import { useGetShapeBelong } from "./use-component.ts";
@@ -50,8 +48,13 @@ export const useAutoPaste = () => {
       async handler(pos, val) {
         if (isSvgString(val)) {
           const url = await resourceHandler(val, "svg");
-          const style = await getIconStyle(url, 100, 100)
-          drawAPI.addShape("icon", { ...style, fill: undefined, stroke: undefined }, pos, true);
+          const style = await getIconStyle(url, 100, 100);
+          drawAPI.addShape(
+            "icon",
+            { ...style, fill: undefined, stroke: undefined },
+            pos,
+            true
+          );
         } else {
           drawAPI.addShape("text", { content: val }, pos, true);
         }
@@ -62,8 +65,13 @@ export const useAutoPaste = () => {
       async handler(pos, val, type) {
         const url = await resourceHandler(val, type);
         if (type.includes("svg")) {
-          const style = await getIconStyle(url, 100, 100)
-          drawAPI.addShape("icon", { ...style, fill: undefined, stroke: undefined }, pos, true);
+          const style = await getIconStyle(url, 100, 100);
+          drawAPI.addShape(
+            "icon",
+            { ...style, fill: undefined, stroke: undefined },
+            pos,
+            true
+          );
         } else {
           const image = await getImage(url);
           drawAPI.addShape(
@@ -107,7 +115,7 @@ export const useShortcutKey = () => {
   const status = useMouseShapesStatus();
   const getChildren = useGetFormalChildren();
   const operMode = useOperMode();
-  const getShapeBelong = useGetShapeBelong()
+  const getShapeBelong = useGetShapeBelong();
   useListener(
     "keydown",
     (ev) => {
@@ -130,46 +138,16 @@ export const useShortcutKey = () => {
         const isSelect = status.selects.length;
         const shapes = isSelect ? status.selects : status.actives;
         const delItems = shapes
-          .map((shape) => {
-            // getShapeBelong(shape)
-
-            let curId = shape.id();
-            if (!curId) return;
-            let id: string;
-            let item: DrawItem | undefined;
-            let type: ShapeType | undefined;
-            do {
-              id = shape.id();
-              item = store.getItemById(id);
-              type = store.getType(id);
-              if (item && type) {
-                break;
-              }
-            } while ((shape = shape.parent as any));
-            if (!type) return;
-            if (id === curId) {
-              if (!item?.disableDelete && type) {
-                return [type, item, undefined] as const;
-              }
-            } else {
-              return [type, item, curId] as const;
-            }
-          })
+          .map(getShapeBelong)
           .filter((item) => !!item);
-        history.onceTrack(() => {
-          delItems.forEach(([type, item, childId]) => {
-            if (!childId) {
-              if (components[type as ShapeType].delItem) {
-                components[type as ShapeType].delItem!(store, item as any);
-              } else {
-                store.delItem(type as ShapeType, item!.id);
-              }
-            } else {
-              components[type as ShapeType].delItem!(
-                store,
-                item as any,
-                childId
-              );
+
+          history.onceTrack(() => {
+          delItems.forEach((belong) => {
+            const compDelItem = components[belong.type].delItem
+            if (compDelItem) {
+              compDelItem(store, belong.item as any, belong.isSelf ? undefined : belong.curId)
+            } else if (belong.isSelf) {
+              store.delItem(belong.type, belong.id);
             }
           });
         });

+ 11 - 0
src/core/hook/use-layer.ts

@@ -15,6 +15,7 @@ import { Layer } from "konva/lib/Layer";
 import { useMouseShapeStatus } from "./use-mouse-status";
 import { DataGroupId } from "@/constant";
 import { Group } from "konva/lib/Group";
+import { getFlatChildren } from "@/utils/shape";
 
 // const useRefreshCount = installGlobalVar(() => ref(0));
 // const useRefresh = () => {
@@ -39,6 +40,16 @@ export const useGetFormalChildren = () => {
   };
 };
 
+export const useGetFormalFlatChildren = () => {
+  const formal = useFormalLayer();
+  return () => {
+    const children = formal.value?.findOne<Group>(`#${DataGroupId}`)?.children
+    if (!children) return []
+    return children.flatMap(item => getFlatChildren(item))
+  };
+};
+
+
 
 export const useHelperLayer = () => {
   const stage = useStage();

+ 0 - 320
src/core/hook/use-selection-n.ts

@@ -1,320 +0,0 @@
-import { Rect } from "konva/lib/shapes/Rect";
-import {
-  globalWatch,
-  installGlobalVar,
-  useForciblyShowItemIds,
-  useMountParts,
-  useStage,
-} from "./use-global-vars";
-import {
-  useGetFormalChildren,
-  useFormalLayer,
-  useHelperLayer,
-} from "./use-layer";
-import { themeColor } from "@/constant";
-import { dragListener } from "@/utils/event";
-import { Layer } from "konva/lib/Layer";
-import { useOperMode } from "./use-status";
-import {
-  computed,
-  markRaw,
-  nextTick,
-  reactive,
-  Ref,
-  ref,
-  toRaw,
-  watch,
-  watchEffect,
-} from "vue";
-import { EntityShape } from "@/deconstruction";
-import { Util } from "konva/lib/Util";
-import {
-  useViewerInvertTransform,
-  useViewerInvertTransformConfig,
-} from "./use-viewer";
-import { debounce, diffArrayChange, mergeFuns, onlyId } from "@/utils/shared";
-import { IRect } from "konva/lib/types";
-import { useMouseShapesStatus } from "./use-mouse-status";
-import Icon from "../components/icon/temp-icon.vue";
-import { Group } from "konva/lib/Group";
-import { Component as GroupComp, GroupData } from "../components/group/";
-import { useStore } from "../store";
-import { useOnComponentBoundChange } from "./use-component";
-import { useHistory } from "./use-history";
-import { isRectContained } from "@/utils/math";
-import { useTransformer } from "./use-transformer";
-import { IconData } from "../components/icon";
-import { usePause } from "./use-pause";
-import mitt, { Emitter } from "mitt";
-
-// 多选不包含分组, 只包含选中者
-export const useSelection = installGlobalVar(() => {
-  const layer = useHelperLayer();
-  const getChildren = useGetFormalChildren();
-  const box = new Rect({
-    stroke: themeColor,
-    strokeWidth: 1,
-    fill: "#fff",
-    listening: false,
-    opacity: 0.5,
-  });
-  const stage = useStage();
-  const operMode = useOperMode();
-  const selections = ref<EntityShape[]>();
-  const transformer = useTransformer();
-
-  let shapeBoxs: IRect[] = [];
-  let shapes: EntityShape[] = [];
-
-  const updateSelections = () => {
-    const boxRect = box.getClientRect();
-    selections.value = [];
-
-    for (let i = 0; i < shapeBoxs.length; i++) {
-      if (
-        Util.haveIntersection(boxRect, shapeBoxs[i]) &&
-        !isRectContained(shapeBoxs[i], boxRect) &&
-        shapes[i] !== toRaw(transformer)
-      ) {
-        if (!selections.value.includes(shapes[i])) {
-          selections.value.push(shapes[i]);
-        }
-      }
-    }
-  };
-
-  const init = (dom: HTMLDivElement, layer: Layer) => {
-    const stopListener = dragListener(dom, {
-      down(pos) {
-        layer.add(box);
-        box.x(pos.x);
-        box.y(pos.y);
-        box.width(0);
-        box.height(0);
-      },
-      move({ end }) {
-        box.width(end.x - box.x());
-        box.height(end.y - box.y());
-        updateSelections();
-      },
-      up() {
-        selections.value = undefined;
-        box.remove();
-      },
-    });
-    return () => {
-      stopListener();
-      box.remove();
-    };
-  };
-
-  const updateInitData = () => {
-    shapes = getChildren();
-    shapeBoxs = shapes.map((shape) => shape.getClientRect());
-  };
-
-  const stopWatch = globalWatch(
-    () => operMode.value.mulSelection,
-    (mulSelection, _, onCleanup) => {
-      if (!mulSelection) return;
-      const dom = stage.value?.getNode().container()!;
-      updateInitData();
-      onCleanup(init(dom, layer.value!));
-    }
-  );
-
-  return {
-    onDestroy: stopWatch,
-    var: { selections, box },
-  };
-});
-
-type ShapeIconArgs = Partial<
-  Pick<IconData, "width" | "height" | "url" | "fill" | "stroke">
->;
-export const useShapesIcon = (
-  shapes: Ref<EntityShape[] | undefined>,
-  args: ShapeIconArgs = {}
-) => {
-  const mParts = useMountParts();
-  const { on } = useOnComponentBoundChange();
-  const iconProps = {
-    width: 12,
-    height: 12,
-    url: "./icons/state_s.svg",
-    fill: themeColor,
-    stroke: "#fff",
-    ...args,
-    listening: false,
-  };
-  const invConfig = useViewerInvertTransformConfig();
-  const invMat = useViewerInvertTransform();
-  const getShapeMat = (shape: EntityShape) => {
-    const rect = shape.getClientRect();
-    const center = invMat.value.point({
-      x: rect.x + rect.width / 2,
-      y: rect.y + rect.height / 2,
-    });
-    return [1, 0, 0, 1, center.x, center.y];
-  };
-  const unMountMap = new WeakMap<EntityShape, () => void>();
-
-  const pause = usePause();
-  const stop = watch([shapes, () => pause.isPause], ([shapes], [oldShapes]) => {
-    if (pause.isPause) {
-      shapes = [];
-    }
-
-    const { added, deleted } = diffArrayChange(shapes || [], oldShapes || []);
-    for (const addShape of added) {
-      const mat = ref(getShapeMat(addShape));
-      const data = reactive({ ...iconProps, mat: mat });
-      const unHooks = [
-        on(addShape, () => (mat.value = getShapeMat(addShape))),
-        watch(
-          invConfig,
-          () => {
-            data.width = invConfig.value.scaleX * iconProps.width;
-            data.height = invConfig.value.scaleY * iconProps.height;
-          },
-          { immediate: true }
-        ),
-        mParts.add({
-          comp: markRaw(Icon),
-          props: { data },
-        }),
-      ];
-      unMountMap.set(addShape, mergeFuns(unHooks));
-    }
-    for (const delShape of deleted) {
-      const fn = unMountMap.get(delShape);
-      fn && fn();
-    }
-  });
-  return [stop, pause];
-};
-
-export const useStoreSelectionManage = installGlobalVar(() => {
-  const store = useStore();
-  const bus: Emitter<Record<"del" | "update", EntityShape>> = mitt();
-  const { on } = useOnComponentBoundChange();
-
-  const canSelect = (shape: EntityShape) => {
-    const id = shape.id();
-    return id && store.items.some((item) => item.id === id);
-  };
-  const listener = (shape: EntityShape) => {
-    return watch(
-      () => canSelect(shape),
-      (exixts, _, onCleanup) => {
-        if (!exixts) {
-          bus.emit("del", shape);
-        } else {
-          onCleanup(on(shape, () => bus.emit("update", shape)));
-        }
-      },
-      { immediate: true }
-    );
-  };
-
-  return { canSelect, listener };
-});
-
-export const useSelectionRevise = () => {
-  const storeManage = useStoreSelectionManage()
-  const mParts = useMountParts();
-  const status = useMouseShapesStatus();
-  const store = useStore();
-  const { selections: rectSelects } = useSelection();
-
-  let initSelections: EntityShape[] = [];
-  watch(
-    () => rectSelects.value && [...rectSelects.value],
-    (rectSelects, oldRectSelects) => {
-      if (!oldRectSelects) {
-        initSelections = [...status.selects];
-      } else if (!rectSelects) {
-        initSelections = [];
-      } else {
-        status.selects = initSelections.concat(rectSelects);
-        filterSelect()
-      }
-    }
-  );
-  useShapesIcon(computed(() => status.selects.concat(rectSelects.value || [])));
-
-  const filterSelect = debounce(() => {
-    const mouseSelects = status.selects.filter((shape) => storeManage.canSelect(shape));
-    status.selects = mouseSelects;
-  }, 16);
-  store.bus.on("delItemAfter", filterSelect);
-  store.bus.on("clearAfter", filterSelect);
-  store.bus.on("dataChangeAfter", filterSelect);
-  store.bus.on("setCurrentLayerAfter", filterSelect);
-
-  const ids = computed(() => [
-    ...new Set(status.selects.map((item) => item.id())),
-  ]);
-  const groupConfig = {
-    id: onlyId(),
-    createTime: Date.now(),
-    lock: false,
-    opacity: 1,
-    ref: false,
-    listening: false,
-    stroke: themeColor,
-  };
-  const operMode = useOperMode();
-  const layer = useFormalLayer();
-  watch(
-    () => [!!ids.value.length, operMode.value.mulSelection],
-    (_a, _b) => {
-      const groupShape = layer.value?.findOne<Group>(`#${groupConfig.id}`);
-      if (!groupShape) return;
-      if (ids.value.length && !operMode.value.mulSelection) {
-        status.actives = [groupShape];
-      } else if (status.actives.includes(groupShape)) {
-        status.actives = [];
-      }
-    }
-  );
-
-  const stage = useStage();
-  const history = useHistory();
-  const showItemId = useForciblyShowItemIds();
-  watchEffect((onCleanup) => {
-    if (!ids.value.length) return;
-    const props = {
-      data: { ...groupConfig, ids: ids.value },
-      key: groupConfig.id,
-      onUpdateShape(data: GroupData) {
-        // status.selects;
-        // data.ids;
-      },
-      onDelShape() {
-        status.selects = [];
-      },
-      onAddShape(data: GroupData) {
-        history.onceTrack(() => {
-          const ids = data.ids;
-          const groups = store.typeItems.group;
-          const exists = groups?.some((group) => {
-            if (group.ids.length !== ids.length) return false;
-            const diff = diffArrayChange(group.ids, ids);
-            return diff.added.length === 0 && diff.deleted.length == 0;
-          });
-          if (exists) return;
-
-          store.addItem("group", { ...data, ids });
-          showItemId.cycle(data.id, async () => {
-            await nextTick();
-            const $stage = stage.value!.getNode();
-            const addShape = $stage.findOne("#" + data.id) as EntityShape;
-            status.selects = [addShape];
-          });
-        });
-      },
-    };
-    onCleanup(mParts.add({ comp: markRaw(GroupComp), props }));
-  });
-};

+ 116 - 201
src/core/hook/use-selection.ts

@@ -20,6 +20,7 @@ import {
   markRaw,
   nextTick,
   reactive,
+  Ref,
   ref,
   toRaw,
   watch,
@@ -27,88 +28,32 @@ import {
 } from "vue";
 import { EntityShape } from "@/deconstruction";
 import { Util } from "konva/lib/Util";
-import { useViewerInvertTransform, useViewerInvertTransformConfig } from "./use-viewer";
+import {
+  useViewerInvertTransform,
+  useViewerInvertTransformConfig,
+} from "./use-viewer";
 import { debounce, diffArrayChange, mergeFuns, onlyId } from "@/utils/shared";
 import { IRect } from "konva/lib/types";
 import { useMouseShapesStatus } from "./use-mouse-status";
 import Icon from "../components/icon/temp-icon.vue";
 import { Group } from "konva/lib/Group";
-import { Component as GroupComp, GroupData } from "../components/group/";
+import { Component as GroupComp, GroupData } from "../components/group";
 import { DrawStore, useStore } from "../store";
-import { DrawItem } from "../components";
-import { Stage } from "konva/lib/Stage";
-import { useOnComponentBoundChange } from "./use-component";
+import { useGetShapeBelong, useOnComponentBoundChange } from "./use-component";
 import { useHistory } from "./use-history";
 import { isRectContained } from "@/utils/math";
 import { useTransformer } from "./use-transformer";
+import { IconData } from "../components/icon";
+import { usePause } from "./use-pause";
+import mitt, { Emitter } from "mitt";
+import { components, ShapeType, shapeTypes } from "../components";
 
-const normalSelectIds = (
-  store: DrawStore,
-  ids: string[],
-  needChildren = false
-) => {
-  if (!store.typeItems.group) return ids;
-
-  const gChildrenIds = store.typeItems.group.map((item) => item.ids);
-  const findNdx = (id: string) =>
-    gChildrenIds.findIndex((cIds) => cIds.includes(id));
-  if (!needChildren) {
-    return ids.filter((id) => !~findNdx(id));
-  }
-
-  const groupIds = store.typeItems.group.map((item) => item.id);
-  const nIds: string[] = [];
-  for (let i = 0; i < ids.length; i++) {
-    let ndx = findNdx(ids[i]);
-    ~ndx || (ndx = groupIds.indexOf(ids[i]));
-
-    if (!~ndx) {
-      nIds.push(ids[i]);
-      continue;
-    }
-
-    const group = store.typeItems.group[ndx];
-    const addIds = [group.id, ...group.ids].filter(
-      (aid) => !nIds.includes(aid)
-    );
-    nIds.push(...addIds);
-  }
-  return nIds;
-};
-
-export const normalSelectShapes = (
-  stage: Stage,
-  store: DrawStore,
-  shapes: EntityShape[],
-  needChildren = false
-) => {
-  let ids: string[] = [];
-  for (let i = 0; i < shapes.length; i++) {
-    const shape = shapes[i];
-    const id = shape.id();
-    id && ids.push(id);
-  }
-  ids = normalSelectIds(store, ids, needChildren);
-  return ids.map((id) => stage.findOne(`#${id}`)!) as EntityShape[];
-};
-
-export const normalSelectItems = (
-  store: DrawStore,
-  items: DrawItem[],
-  needChildren = false
-) => {
-  return normalSelectIds(
-    store,
-    items.map((item) => item.id),
-    needChildren
-  ).map((id) => store.getItemById(id)!);
-};
-
+// 多选不包含分组, 只包含选中者
 export const useSelection = installGlobalVar(() => {
   const layer = useHelperLayer();
   const getChildren = useGetFormalChildren();
   const box = new Rect({
-    stroke: themeColor, 
+    stroke: themeColor,
     strokeWidth: 1,
     fill: "#fff",
     listening: false,
@@ -181,11 +126,17 @@ export const useSelection = installGlobalVar(() => {
 
   return {
     onDestroy: stopWatch,
-    var: {selections, box},
+    var: { selections, box },
   };
 });
 
-export const useSelectionShowIcons = installGlobalVar(() => {
+type ShapeIconArgs = Partial<
+  Pick<IconData, "width" | "height" | "url" | "fill" | "stroke">
+>;
+export const useShapesIcon = (
+  shapes: Ref<EntityShape[] | undefined>,
+  args: ShapeIconArgs = {}
+) => {
   const mParts = useMountParts();
   const { on } = useOnComponentBoundChange();
   const iconProps = {
@@ -194,12 +145,10 @@ export const useSelectionShowIcons = installGlobalVar(() => {
     url: "./icons/state_s.svg",
     fill: themeColor,
     stroke: "#fff",
+    ...args,
     listening: false,
   };
-  const invConfig = useViewerInvertTransformConfig()
-  const status = useMouseShapesStatus();
-
-  const store = useStore();
+  const invConfig = useViewerInvertTransformConfig();
   const invMat = useViewerInvertTransform();
   const getShapeMat = (shape: EntityShape) => {
     const rect = shape.getClientRect();
@@ -209,21 +158,28 @@ export const useSelectionShowIcons = installGlobalVar(() => {
     });
     return [1, 0, 0, 1, center.x, center.y];
   };
-  const shapes = computed(() =>
-    status.selects.filter((shape) => store.getType(shape.id()) !== "group")
-  );
   const unMountMap = new WeakMap<EntityShape, () => void>();
-  watch(shapes, (shapes, oldShapes) => {
-    const { added, deleted } = diffArrayChange(shapes, oldShapes);
+
+  const pause = usePause();
+  const stop = watch([shapes, () => pause.isPause], ([shapes], [oldShapes]) => {
+    if (pause.isPause) {
+      shapes = [];
+    }
+
+    const { added, deleted } = diffArrayChange(shapes || [], oldShapes || []);
     for (const addShape of added) {
       const mat = ref(getShapeMat(addShape));
-      const data = reactive({ ...iconProps, mat: mat })
+      const data = reactive({ ...iconProps, mat: mat });
       const unHooks = [
         on(addShape, () => (mat.value = getShapeMat(addShape))),
-        watch(invConfig, () => {
-          data.width = invConfig.value.scaleX * iconProps.width
-          data.height = invConfig.value.scaleY * iconProps.height
-        }, {immediate: true}),
+        watch(
+          invConfig,
+          () => {
+            data.width = invConfig.value.scaleX * iconProps.width;
+            data.height = invConfig.value.scaleY * iconProps.height;
+          },
+          { immediate: true }
+        ),
         mParts.add({
           comp: markRaw(Icon),
           props: { data },
@@ -236,132 +192,105 @@ export const useSelectionShowIcons = installGlobalVar(() => {
       fn && fn();
     }
   });
-});
-
-const useWatchSelection = () => {
-  const status = useMouseShapesStatus();
-  const addShapes = (allShapes: Set<EntityShape>, iShapes: EntityShape[]) => {
-    iShapes.forEach((shape) => allShapes.add(toRaw(shape)));
-    return allShapes;
-  };
-  const delShapes = (allShapes: Set<EntityShape>, dShapes: EntityShape[]) => {
-    dShapes.forEach((item) => allShapes.delete(toRaw(item)));
-    return allShapes;
-  };
-
-  // 分组管理
-  const watchSelection = () =>
-    watch(
-      () => status.selects,
-      (shapes) => {
-        const fShapes = Array.from(new Set(shapes));
-        const { added, deleted } = diffArrayChange(shapes, fShapes);
-        if (added.length || deleted.length) {
-          status.selects = fShapes;
-        }
-      },
-      { flush: "post" }
-    );
+  return [stop, pause];
+};
 
-  return {
-    addShapes,
-    delShapes,
-    watchSelection,
+export type SelectionManageBus = Emitter<Record<"del" | "update", EntityShape>>
+export type SelectionManage = {
+  canSelect: (shape: EntityShape) => boolean;
+  listener: (shape: EntityShape) => {
+    stop: () => void;
+    bus: SelectionManageBus;
   };
 };
+export type UseGetSelectionManage = () => SelectionManage
 
-const useWatchSelectionGroup = () => {
-  const stage = useStage();
+export const useStoreSelectionManage = installGlobalVar((): SelectionManage => {
   const store = useStore();
-  const status = useMouseShapesStatus();
-  const addShapes = (allShapes: Set<EntityShape>, iShapes: EntityShape[]) => {
-    const shapes = normalSelectShapes(
-      stage.value!.getNode(),
-      store,
-      iShapes,
-      true
-    );
-    shapes.forEach((shape) => allShapes.add(shape));
-    return allShapes;
-  };
-  const delShapes = (allShapes: Set<EntityShape>, dShapes: EntityShape[]) => {
-    const shapes = normalSelectShapes(
-      stage.value!.getNode(),
-      store,
-      dShapes,
-      true
-    );
-    shapes.forEach((item) => allShapes.delete(item));
-    return allShapes;
-  };
-
-  // 分组管理
-  const watchSelection = () =>
-    watch(
-      () => status.selects,
-      (shapes, oldShapes) => {
-        const { added, deleted } = diffArrayChange(shapes, oldShapes);
-        const filterShapes = new Set(shapes);
-        added.length && addShapes(filterShapes, added);
-        deleted.length && delShapes(filterShapes, deleted);
+  const { on } = useOnComponentBoundChange();
 
-        if (added.length || deleted.length) {
-          status.selects = Array.from(filterShapes);
+  const canSelect = (shape: EntityShape) => {
+    const id = shape.id();
+    return !!(id && store.items.some((item) => item.id === id));
+  };
+  const listener = (shape: EntityShape) => {
+    const bus: SelectionManageBus = mitt();
+    const stop = watch(
+      () => canSelect(shape),
+      (exixts, _, onCleanup) => {
+        if (!exixts) {
+          bus.emit("del", shape);
+        } else {
+          onCleanup(on(shape, () => bus.emit("update", shape)));
         }
       },
-      { flush: "post" }
+      { immediate: true }
     );
-
-  return {
-    addShapes,
-    delShapes,
-    watchSelection,
+    return { stop, bus }
   };
-};
+
+  return { canSelect, listener };
+});
+
+export const useGetShapeSelectionManage = installGlobalVar(() => {
+  const compManages: Partial<Record<ShapeType, SelectionManage>> = {}
+  for (const type of shapeTypes) {
+    compManages[type] = components[type].useGetSelectionManage && components[type].useGetSelectionManage()
+  }
+  const storeManage = useStoreSelectionManage();
+  const getShapeBelong = useGetShapeBelong();
+  return (shape: EntityShape) => {
+      const bl = getShapeBelong(shape);
+      if (!bl) return;
+      const manage = bl.isSelf ? storeManage : compManages[bl.type]
+      if (!manage) {
+        console.log('找不到多选管理器', bl)
+      }
+      return manage;
+  }
+})
 
 export const useSelectionRevise = () => {
+  const getShapeSelectionManage = useGetShapeSelectionManage();
   const mParts = useMountParts();
   const status = useMouseShapesStatus();
   const store = useStore();
-
-  const { addShapes, delShapes, watchSelection } = useWatchSelection();
-
-  useSelectionShowIcons();
-
-  const getFormatChildren = useGetFormalChildren();
-  const filterSelect = debounce(() => {
-    const children = getFormatChildren();
-    const mouseSelects = status.selects.filter((shape) =>
-      children.includes(shape)
-    );
-    status.selects = mouseSelects;
-  }, 16);
-  store.bus.on("delItemAfter", filterSelect);
-  store.bus.on("clearAfter", filterSelect);
-  store.bus.on("dataChangeAfter", filterSelect);
-  store.bus.on("setCurrentLayerAfter", filterSelect);
-
   const { selections: rectSelects } = useSelection();
+
   let initSelections: EntityShape[] = [];
-  let stopWatchSelection = watchSelection();
   watch(
     () => rectSelects.value && [...rectSelects.value],
     (rectSelects, oldRectSelects) => {
       if (!oldRectSelects) {
         initSelections = [...status.selects];
-        stopWatchSelection();
       } else if (!rectSelects) {
         initSelections = [];
-        stopWatchSelection = watchSelection();
       } else {
-        status.selects = Array.from(
-          addShapes(new Set(initSelections), rectSelects)
-        );
+        status.selects = initSelections.concat(rectSelects);
+        filterSelect();
       }
     }
   );
+  useShapesIcon(computed(() => status.selects.concat(rectSelects.value || [])));
+  
+  const filterSelect = debounce(() => {
+    const selects: EntityShape[] = []
+    for (const shape of status.selects) {
+      const manage = getShapeSelectionManage(shape)
+      if (manage?.canSelect(shape)) {
+        selects.push(shape)
+      }
+    }
+    status.selects = selects;
+  }, 16);
+  store.bus.on("delItemAfter", filterSelect);
+  store.bus.on("clearAfter", filterSelect);
+  store.bus.on("dataChangeAfter", filterSelect);
+  store.bus.on("setCurrentLayerAfter", filterSelect);
 
-  const ids = computed(() => [...new Set(status.selects.map((item) => item.id()))]);
+  const ids = computed(() => [
+    ...new Set(status.selects.map((item) => item.id())),
+  ]);
   const groupConfig = {
     id: onlyId(),
     createTime: Date.now(),
@@ -395,8 +324,8 @@ export const useSelectionRevise = () => {
       data: { ...groupConfig, ids: ids.value },
       key: groupConfig.id,
       onUpdateShape(data: GroupData) {
-        status.selects;
-        data.ids;
+        // status.selects;
+        // data.ids;
       },
       onDelShape() {
         status.selects = [];
@@ -404,34 +333,20 @@ export const useSelectionRevise = () => {
       onAddShape(data: GroupData) {
         history.onceTrack(() => {
           const ids = data.ids;
-          const cIds = ids.filter((id) => store.getType(id) !== "group");
-
           const groups = store.typeItems.group;
           const exists = groups?.some((group) => {
-            if (group.ids.length !== cIds.length) return false;
-            const diff = diffArrayChange(group.ids, cIds);
+            if (group.ids.length !== ids.length) return false;
+            const diff = diffArrayChange(group.ids, ids);
             return diff.added.length === 0 && diff.deleted.length == 0;
           });
           if (exists) return;
 
-          let selects = new Set(status.selects);
-          for (let i = 0; i < ids.length; i++) {
-            if (store.getType(ids[i]) === "group") {
-              delShapes(
-                selects,
-                status.selects.filter((shape) => shape.id() === ids[i])
-              );
-              store.delItem("group", ids[i]);
-            }
-          }
-
-          store.addItem("group", { ...data, ids: cIds });
+          store.addItem("group", { ...data, ids });
           showItemId.cycle(data.id, async () => {
             await nextTick();
             const $stage = stage.value!.getNode();
             const addShape = $stage.findOne("#" + data.id) as EntityShape;
-            addShapes(selects, [addShape]);
-            status.selects = Array.from(selects);
+            status.selects = [addShape];
           });
         });
       },

+ 11 - 0
src/utils/shape.ts

@@ -93,4 +93,15 @@ export const getImageSize = (url: string | Blob): Promise<Size> => {
 		};
 		image.src = typeof url === 'string' ? url : URL.createObjectURL(url);
 	});
+}
+
+export const getFlatChildren = (shape: EntityShape, children: EntityShape[] = []) => {
+	if ('children' in shape) {
+		for (const item of shape.children) {
+			getFlatChildren(item)
+			children.push(item)
+		}
+	} else {
+		children.push(shape)
+	}
 }