|
@@ -0,0 +1,320 @@
|
|
|
+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 }));
|
|
|
+ });
|
|
|
+};
|