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, 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 { DrawStore, useStore } from "../store"; import { DrawItem } from "../components"; import { Stage } from "konva/lib/Stage"; import { useOnComponentBoundChange } from "./use-component"; import { useHistory } from "./use-history"; import { isRectContained } from "@/utils/math"; import { useTransformer } from "./use-transformer"; 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 useExcludeSelection = installGlobalVar(() => ref([])) export const useSelection = installGlobalVar(() => { const layer = useHelperLayer(); const eSelection = useExcludeSelection() 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(); 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().filter(shape => !eSelection.value.includes(shape.id())); 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}, }; }); export const useSelectionShowIcons = installGlobalVar(() => { const mParts = useMountParts(); const { on } = useOnComponentBoundChange(); const iconProps = { width: 12, height: 12, url: "./icons/state_s.svg", fill: themeColor, stroke: "#fff", listening: false, }; const invConfig = useViewerInvertTransformConfig() const status = useMouseShapesStatus(); const getChildren = useGetFormalChildren() 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(() => { const child = getChildren() return status.selects.filter((shape) => store.getType(shape.id()) !== "group" && child.includes(shape)) }); const unMountMap = new WeakMap void>(); watch(shapes, (shapes, oldShapes) => { 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(); } }); }); const useWatchSelection = () => { const status = useMouseShapesStatus(); const addShapes = (allShapes: Set, iShapes: EntityShape[]) => { iShapes.forEach((shape) => allShapes.add(toRaw(shape))); return allShapes; }; const delShapes = (allShapes: Set, 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 { addShapes, delShapes, watchSelection, }; }; const useWatchSelectionGroup = () => { const stage = useStage(); const store = useStore(); const status = useMouseShapesStatus(); const addShapes = (allShapes: Set, iShapes: EntityShape[]) => { const shapes = normalSelectShapes( stage.value!.getNode(), store, iShapes, true ); shapes.forEach((shape) => allShapes.add(shape)); return allShapes; }; const delShapes = (allShapes: Set, 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); if (added.length || deleted.length) { status.selects = Array.from(filterShapes); } }, { flush: "post" } ); return { addShapes, delShapes, watchSelection, }; }; export const useSelectionRevise = () => { const mParts = useMountParts(); const status = useMouseShapesStatus(); const store = useStore(); const eSelection = useExcludeSelection() const { addShapes, delShapes, watchSelection } = useWatchSelection(); useSelectionShowIcons(); const getFormatChildren = useGetFormalChildren(); const filterSelect = debounce(() => { const children = getFormatChildren(); const mouseSelects = status.selects.filter((shape) => !eSelection.value.includes(shape.id()) && 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) ); // 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(`#${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 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); 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 })); }); };