|
@@ -1,32 +1,105 @@
|
|
|
import { Rect } from "konva/lib/shapes/Rect";
|
|
|
import {
|
|
|
globalWatch,
|
|
|
- globalWatchEffect,
|
|
|
installGlobalVar,
|
|
|
+ useForciblyShowItemIds,
|
|
|
useMountParts,
|
|
|
useStage,
|
|
|
} from "./use-global-vars";
|
|
|
-import { useFormalLayer, useHelperLayer } from "./use-layer";
|
|
|
+import {
|
|
|
+ useGetFormalChildren,
|
|
|
+ useFormalLayer,
|
|
|
+ useHelperLayer,
|
|
|
+} from "./use-layer";
|
|
|
import { themeColor } from "@/constant/help-style";
|
|
|
import { dragListener } from "@/utils/event";
|
|
|
import { Layer } from "konva/lib/Layer";
|
|
|
import { useOperMode } from "./use-status";
|
|
|
-import { computed, markRaw, ref, watch, watchEffect } from "vue";
|
|
|
+import { computed, markRaw, nextTick, reactive, ref, watch, watchEffect } from "vue";
|
|
|
import { EntityShape } from "@/deconstruction";
|
|
|
import { Util } from "konva/lib/Util";
|
|
|
-import { useViewerInvertTransform, useViewerTransform } from "./use-viewer";
|
|
|
-import { mergeFuns, onlyId } from "@/utils/shared";
|
|
|
+import { useViewerInvertTransform } from "./use-viewer";
|
|
|
+import {
|
|
|
+ diffArrayChange,
|
|
|
+ mergeFuns,
|
|
|
+ onlyId,
|
|
|
+} from "@/utils/shared";
|
|
|
import { IRect } from "konva/lib/types";
|
|
|
import { useMouseShapesStatus } from "./use-mouse-status";
|
|
|
-import { Pos } from "@/utils/math";
|
|
|
import Icon from "../components/icon/temp-icon.vue";
|
|
|
import { Group } from "konva/lib/Group";
|
|
|
-import { Component as GroupComp } from "../components/group/";
|
|
|
-import { useStore } from "../store";
|
|
|
+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 { useHistory } from "./use-history";
|
|
|
+
|
|
|
+const normalSelectIds = (
|
|
|
+ store: DrawStore,
|
|
|
+ ids: string[],
|
|
|
+ needChildren = false
|
|
|
+) => {
|
|
|
+ if (!store.data.typeItems.group) return ids;
|
|
|
+
|
|
|
+ const gChildrenIds = store.data.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.data.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.data.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 formatLayer = useFormalLayer();
|
|
|
+ const getChildren = useGetFormalChildren();
|
|
|
const box = new Rect({
|
|
|
stroke: themeColor,
|
|
|
strokeWidth: 1,
|
|
@@ -37,11 +110,19 @@ export const useSelection = installGlobalVar(() => {
|
|
|
const stage = useStage();
|
|
|
const operMode = useOperMode();
|
|
|
const selections = ref<EntityShape[]>();
|
|
|
- const viewMat = useViewerTransform();
|
|
|
- const store = useStore();
|
|
|
|
|
|
let shapeBoxs: IRect[] = [];
|
|
|
- let shpaes: EntityShape[] = []
|
|
|
+ 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]))
|
|
|
+ selections.value.push(shapes[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
const init = (dom: HTMLDivElement, layer: Layer) => {
|
|
|
const stopListener = dragListener(dom, {
|
|
@@ -55,14 +136,7 @@ export const useSelection = installGlobalVar(() => {
|
|
|
move({ end }) {
|
|
|
box.width(end.x - box.x());
|
|
|
box.height(end.y - box.y());
|
|
|
-
|
|
|
- const boxRect = box.getClientRect();
|
|
|
- selections.value = [];
|
|
|
- for (let i = 0; i < shapeBoxs.length; i++) {
|
|
|
- if (Util.haveIntersection(boxRect, shapeBoxs[i])) {
|
|
|
- selections.value!.push(shpaes[i]);
|
|
|
- }
|
|
|
- }
|
|
|
+ updateSelections()
|
|
|
},
|
|
|
up() {
|
|
|
selections.value = undefined;
|
|
@@ -75,22 +149,32 @@ export const useSelection = installGlobalVar(() => {
|
|
|
};
|
|
|
};
|
|
|
|
|
|
- const stopWatch = mergeFuns(
|
|
|
- globalWatchEffect((onCleanup) => {
|
|
|
- const dom = stage.value?.getNode().container();
|
|
|
- if (dom && operMode.value.mulSelection && layer.value) {
|
|
|
- onCleanup(init(dom, layer.value));
|
|
|
- }
|
|
|
- }),
|
|
|
- globalWatch(
|
|
|
- () => [viewMat.value, operMode.value.mulSelection],
|
|
|
- () => {
|
|
|
- if (operMode.value.mulSelection) {
|
|
|
- shpaes = formatLayer.value!.children.filter((shape) => store.getItemById(shape.id()))
|
|
|
- shapeBoxs = shpaes.map((shape) => shape.getClientRect());
|
|
|
- }
|
|
|
- }
|
|
|
- )
|
|
|
+ const updateInitData = () => {
|
|
|
+ shapes = getChildren();
|
|
|
+ shapeBoxs = shapes.map((shape) => shape.getClientRect());
|
|
|
+ };
|
|
|
+ const shpaesChange = () => {
|
|
|
+ updateInitData()
|
|
|
+ updateSelections()
|
|
|
+ }
|
|
|
+
|
|
|
+ const store = useStore()
|
|
|
+ const stopWatch = globalWatch(
|
|
|
+ () => operMode.value.mulSelection,
|
|
|
+ (mulSelection, _, onCleanup) => {
|
|
|
+ if (!mulSelection) return;
|
|
|
+ const dom = stage.value?.getNode().container()!;
|
|
|
+ updateInitData();
|
|
|
+
|
|
|
+ const unInit = init(dom, layer.value!);
|
|
|
+ store.bus.on('dataChangeAfter', shpaesChange)
|
|
|
+ store.bus.on('delItemAfter', shpaesChange)
|
|
|
+ onCleanup(() => {
|
|
|
+ unInit()
|
|
|
+ store.bus.off('dataChangeAfter', shpaesChange)
|
|
|
+ store.bus.off('delItemAfter', shpaesChange)
|
|
|
+ })
|
|
|
+ }
|
|
|
);
|
|
|
|
|
|
return {
|
|
@@ -101,21 +185,117 @@ export const useSelection = installGlobalVar(() => {
|
|
|
|
|
|
export const useSelectionShowIcons = installGlobalVar(() => {
|
|
|
const mParts = useMountParts();
|
|
|
+ const { on } = useOnComponentBoundChange();
|
|
|
const iconProps = {
|
|
|
width: 20,
|
|
|
height: 20,
|
|
|
- zIndex: 99999,
|
|
|
url: "/icons/state_s.svg",
|
|
|
fill: themeColor,
|
|
|
stroke: "#fff",
|
|
|
+ listening: false,
|
|
|
};
|
|
|
|
|
|
const status = useMouseShapesStatus();
|
|
|
- const formatLayer = useFormalLayer();
|
|
|
- const mouseSelects = computed(() => {
|
|
|
- const selectShapes = status.selects.filter((shape) =>
|
|
|
- formatLayer.value?.children.includes(shape)
|
|
|
+
|
|
|
+ const store = useStore();
|
|
|
+ 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 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);
|
|
|
+ for (const addShape of added) {
|
|
|
+ const mat = ref(getShapeMat(addShape));
|
|
|
+ const unHooks = [
|
|
|
+ on(addShape, () => (mat.value = getShapeMat(addShape))),
|
|
|
+ mParts.add({
|
|
|
+ comp: markRaw(Icon),
|
|
|
+ props: {
|
|
|
+ data: reactive({ ...iconProps, mat: mat }),
|
|
|
+ },
|
|
|
+ }),
|
|
|
+ ];
|
|
|
+ unMountMap.set(addShape, mergeFuns(unHooks));
|
|
|
+ }
|
|
|
+ for (const delShape of deleted) {
|
|
|
+ const fn = unMountMap.get(delShape);
|
|
|
+ fn && fn();
|
|
|
+ }
|
|
|
+ });
|
|
|
+});
|
|
|
+
|
|
|
+export const useWatchSelectionGroup = () => {
|
|
|
+ const stage = useStage();
|
|
|
+ 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 watchSelectionGroup = () =>
|
|
|
+ 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);
|
|
|
+
|
|
|
+ if (added.length || deleted.length) {
|
|
|
+ status.selects = Array.from(filterShapes);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { flush: "post" }
|
|
|
+ );
|
|
|
+
|
|
|
+ return {
|
|
|
+ addShapes,
|
|
|
+ delShapes,
|
|
|
+ watchSelectionGroup,
|
|
|
+ };
|
|
|
+};
|
|
|
+
|
|
|
+export const useSelectionRevise = () => {
|
|
|
+ const mParts = useMountParts();
|
|
|
+ const status = useMouseShapesStatus();
|
|
|
+ const { addShapes, delShapes, watchSelectionGroup } =
|
|
|
+ useWatchSelectionGroup();
|
|
|
+
|
|
|
+ useSelectionShowIcons();
|
|
|
+
|
|
|
+ const getFormatChildren = useGetFormalChildren();
|
|
|
+ const mouseSelects = computed(() => {
|
|
|
+ const children = getFormatChildren();
|
|
|
+ const selectShapes = status.selects.filter((shape) => {
|
|
|
+ return children.includes(shape);
|
|
|
+ });
|
|
|
return selectShapes;
|
|
|
});
|
|
|
|
|
@@ -127,78 +307,34 @@ export const useSelectionShowIcons = installGlobalVar(() => {
|
|
|
|
|
|
const rectSelects = useSelection();
|
|
|
let initSelections: EntityShape[] = [];
|
|
|
+ let stopWatchSelectionGroup = watchSelectionGroup();
|
|
|
watch(
|
|
|
() => rectSelects.value && [...rectSelects.value],
|
|
|
(rectSelects, oldRectSelects) => {
|
|
|
if (!oldRectSelects) {
|
|
|
initSelections = [...mouseSelects.value];
|
|
|
+ stopWatchSelectionGroup();
|
|
|
} else if (!rectSelects) {
|
|
|
initSelections = [];
|
|
|
+ stopWatchSelectionGroup = watchSelectionGroup();
|
|
|
} else {
|
|
|
status.selects = Array.from(
|
|
|
- new Set([...initSelections, ...rectSelects])
|
|
|
+ addShapes(new Set(initSelections), rectSelects)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
|
|
|
- const selectionCenters = ref<Pos[]>([]);
|
|
|
- const invMat = useViewerInvertTransform()
|
|
|
- watch(
|
|
|
- () => [invMat.value, status.selects],
|
|
|
- (_a, _b, onCleanup) => {
|
|
|
- selectionCenters.value = [];
|
|
|
- onCleanup(mergeFuns(status.selects.map((shape, ndx) => {
|
|
|
- const set = () => {
|
|
|
- const rect = shape.getClientRect();
|
|
|
- selectionCenters.value[ndx] = invMat.value.point({
|
|
|
- x: rect.x + rect.width / 2,
|
|
|
- y: rect.y + rect.height / 2,
|
|
|
- });
|
|
|
- }
|
|
|
- set()
|
|
|
- shape.on('bound-change', set)
|
|
|
- return () => shape.off('bound-change', set)
|
|
|
- })))
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
- watchEffect((onCleanup) => {
|
|
|
- onCleanup(
|
|
|
- mergeFuns(
|
|
|
- selectionCenters.value.map((center) =>
|
|
|
- mParts.add({
|
|
|
- comp: markRaw(Icon),
|
|
|
- props: {
|
|
|
- data: { ...iconProps, mat: [1, 0, 0, 1, center.x, center.y] },
|
|
|
- },
|
|
|
- })
|
|
|
- )
|
|
|
- )
|
|
|
- );
|
|
|
- });
|
|
|
-
|
|
|
- return computed({
|
|
|
- get: () => status.selects,
|
|
|
- set: (val: EntityShape[]) => (status.selects = val),
|
|
|
- });
|
|
|
-});
|
|
|
-
|
|
|
-export const useSelectionRevise = () => {
|
|
|
- const mParts = useMountParts();
|
|
|
- const selects = useSelectionShowIcons();
|
|
|
-
|
|
|
- const ids = computed(() => selects.value.map((item) => item.id()));
|
|
|
+ const ids = computed(() => status.selects.map((item) => item.id()));
|
|
|
const groupConfig = {
|
|
|
id: onlyId(),
|
|
|
createTime: Date.now(),
|
|
|
- zIndex: 9999,
|
|
|
lock: false,
|
|
|
opacity: 1,
|
|
|
ref: false,
|
|
|
listening: false,
|
|
|
+ stroke: themeColor,
|
|
|
};
|
|
|
- const status = useMouseShapesStatus();
|
|
|
const operMode = useOperMode();
|
|
|
const layer = useFormalLayer();
|
|
|
watch(
|
|
@@ -213,16 +349,58 @@ export const useSelectionRevise = () => {
|
|
|
}
|
|
|
}
|
|
|
);
|
|
|
-
|
|
|
|
|
|
+ const stage = useStage();
|
|
|
+ const store = useStore();
|
|
|
+ const history = useHistory();
|
|
|
+ const showItemId = useForciblyShowItemIds();
|
|
|
watchEffect((onCleanup) => {
|
|
|
- if (ids.value.length) {
|
|
|
- onCleanup(
|
|
|
- mParts.add({
|
|
|
- comp: markRaw(GroupComp),
|
|
|
- props: { data: { ...groupConfig, ids: ids.value } },
|
|
|
- })
|
|
|
- );
|
|
|
- }
|
|
|
+ if (!ids.value.length) return;
|
|
|
+ const props = {
|
|
|
+ data: { ...groupConfig, ids: ids.value },
|
|
|
+ onUpdateShape(data: GroupData) {
|
|
|
+ status.selects
|
|
|
+ data.ids
|
|
|
+ },
|
|
|
+ onDelShape() {
|
|
|
+ console.log('delShape')
|
|
|
+ status.selects = []
|
|
|
+ },
|
|
|
+ onAddShape(data: GroupData) {
|
|
|
+ history.onceTrack(() => {
|
|
|
+ const ids = data.ids;
|
|
|
+ const cIds = ids.filter(id => store.getType(id) !== "group")
|
|
|
+
|
|
|
+ const groups = store.data.typeItems.group
|
|
|
+ const exists = groups?.some(group => {
|
|
|
+ if (group.ids.length !== cIds.length) return false
|
|
|
+ const diff = diffArrayChange(group.ids, cIds)
|
|
|
+ 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 });
|
|
|
+ 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);
|
|
|
+ });
|
|
|
+ });
|
|
|
+ },
|
|
|
+ };
|
|
|
+ onCleanup(mParts.add({ comp: markRaw(GroupComp), props }));
|
|
|
});
|
|
|
};
|