|
@@ -1,268 +1,265 @@
|
|
|
-import { installGlobalVar, useMode, useStage, } from "./use-global-vars.ts";
|
|
|
-import { ComponentValue, DrawItem, ShapeType } from "../components";
|
|
|
-import { nextTick, reactive, Ref, ref, watch, watchEffect } from "vue";
|
|
|
+import {
|
|
|
+ installGlobalVar,
|
|
|
+ useCan,
|
|
|
+ useMode,
|
|
|
+ useStage,
|
|
|
+} from "./use-global-vars.ts";
|
|
|
+import { DrawItem, ShapeType } from "../components";
|
|
|
+import { reactive, Ref, ref, watch, watchEffect } from "vue";
|
|
|
import { Pos } from "../../utils/math.ts";
|
|
|
-import { clickListener, dragListener, getOffset, listener } from "../../utils/event.ts";
|
|
|
-import { Mode } from "../../constant/mode.ts";
|
|
|
-import { inRevise, mergeFuns } from "../../utils/shared.ts";
|
|
|
-import { useConversionPosition } from "./use-coversion-position.ts";
|
|
|
-
|
|
|
+import { clickListener, getOffset, listener } from "../../utils/event.ts";
|
|
|
+import { mergeFuns } from "../../utils/shared.ts";
|
|
|
+import { Mode } from "@/constant/mode.ts";
|
|
|
|
|
|
export type InteractivePreset<T extends ShapeType = ShapeType> = {
|
|
|
- type: T;
|
|
|
- preset?: Partial<DrawItem<T>>,
|
|
|
- operate?: {
|
|
|
- immediate?: boolean,
|
|
|
- single?: boolean,
|
|
|
- data?: any
|
|
|
- }
|
|
|
+ type: T;
|
|
|
+ callback?: () => void;
|
|
|
+ preset?: Partial<DrawItem<T>>;
|
|
|
+ operate?: {
|
|
|
+ immediate?: boolean;
|
|
|
+ single?: boolean;
|
|
|
+ data?: any;
|
|
|
+ };
|
|
|
};
|
|
|
-export const useInteractiveProps = installGlobalVar(() => ref<InteractivePreset | undefined>(), Symbol("interactiveProps"));
|
|
|
+export const useInteractiveProps = installGlobalVar(
|
|
|
+ () => ref<InteractivePreset | undefined>(),
|
|
|
+ Symbol("interactiveProps")
|
|
|
+);
|
|
|
|
|
|
-type Area = [Pos, Pos];
|
|
|
-export enum InteractiveAction { delete }
|
|
|
-export type InteractiveMessage = { area?: Area; dot?: Pos, ndx?: number, action?: InteractiveAction };
|
|
|
-export type InteractiveMessageData<T extends ShapeType> = ComponentValue<T, 'addMode'> extends 'area' ? Area : Pos
|
|
|
-export type InteractiveAreas = ReturnType<typeof useInteractiveAreas>;
|
|
|
-export type InteractiveDots = ReturnType<typeof useInteractiveDots>;
|
|
|
-export type Interactive = InteractiveAreas | InteractiveDots
|
|
|
-
|
|
|
-export const useInteractiveShapeAPI = () => {
|
|
|
- const mode = useMode();
|
|
|
- const interactiveProps = useInteractiveProps();
|
|
|
- const conversion = useConversionPosition(true)
|
|
|
- return {
|
|
|
- addShape: <T extends ShapeType>(
|
|
|
- shapeType: T,
|
|
|
- preset: Partial<DrawItem<T>> = {},
|
|
|
- data: InteractiveMessageData<T>,
|
|
|
- pixel = false
|
|
|
- ) => {
|
|
|
- mode.value = Mode.add
|
|
|
- if (pixel) {
|
|
|
- data = (Array.isArray(data) ? data.map(conversion) : conversion(data)) as InteractiveMessageData<T>
|
|
|
- }
|
|
|
- interactiveProps.value = {
|
|
|
- type: shapeType, preset,
|
|
|
- operate: { single: true, immediate: true, data }
|
|
|
- }
|
|
|
- },
|
|
|
- enterMouseAddShape: <T extends ShapeType>(shapeType: T, preset: Partial<DrawItem<T>> = {}, single = false) => {
|
|
|
- mode.value = Mode.add
|
|
|
- interactiveProps.value = {type: shapeType, preset, operate: {single}}
|
|
|
- },
|
|
|
- quitMouseAddShape: () => {
|
|
|
- mode.value = Mode.viewer
|
|
|
- interactiveProps.value = void 0
|
|
|
- }
|
|
|
- }
|
|
|
+export type Area = [Pos, Pos];
|
|
|
+export enum InteractiveAction {
|
|
|
+ delete,
|
|
|
}
|
|
|
-
|
|
|
-
|
|
|
-export const useIsRunning = (shapeType?: ShapeType) => {
|
|
|
- const stage = useStage();
|
|
|
- const mode = useMode();
|
|
|
- const interactiveProps = useInteractiveProps();
|
|
|
- const isRunning = ref<boolean>(false);
|
|
|
- const updateIsRunning = () => {
|
|
|
- isRunning.value = !!(stage.value &&
|
|
|
- mode.value === Mode.add &&
|
|
|
- (!shapeType || shapeType === interactiveProps.value?.type))
|
|
|
- }
|
|
|
- watchEffect(updateIsRunning)
|
|
|
- watch(
|
|
|
- () => interactiveProps.value?.preset,
|
|
|
- (nPreset, oPreset) => {
|
|
|
- if (isRunning.value && inRevise(nPreset, oPreset)) {
|
|
|
- isRunning.value = false
|
|
|
- nextTick(updateIsRunning)
|
|
|
- }
|
|
|
- }, {flush: 'post'})
|
|
|
-
|
|
|
- return isRunning
|
|
|
+export type InteractiveMessage = {
|
|
|
+ area?: Area;
|
|
|
+ dot?: Pos;
|
|
|
+ ndx?: number;
|
|
|
+ action?: InteractiveAction;
|
|
|
};
|
|
|
-
|
|
|
+export type InteractiveAreas = ReturnType<typeof useInteractiveAreas>;
|
|
|
+export type InteractiveDots = ReturnType<typeof useInteractiveDots>;
|
|
|
+export type Interactive = InteractiveAreas | InteractiveDots;
|
|
|
|
|
|
const useInteractiveExpose = <T extends object>(
|
|
|
- messages: Ref<T[]>,
|
|
|
- init: (dom: HTMLDivElement) => () => void,
|
|
|
- singleDone: Ref<boolean>,
|
|
|
- shapeType?: ShapeType,
|
|
|
- autoConsumed?: boolean,
|
|
|
+ messages: Ref<T[]>,
|
|
|
+ init: (dom: HTMLDivElement) => () => void,
|
|
|
+ singleDone: Ref<boolean>,
|
|
|
+ isRunning: Ref<boolean>,
|
|
|
+ quit: () => void,
|
|
|
+ autoConsumed?: boolean
|
|
|
) => {
|
|
|
- const consumedMessages = reactive(new WeakSet<T>()) as WeakSet<T>;
|
|
|
- const stage = useStage();
|
|
|
- const interactiveProps = useInteractiveProps();
|
|
|
- const isRunning = useIsRunning(shapeType);
|
|
|
- const {quitMouseAddShape} = useInteractiveShapeAPI()
|
|
|
-
|
|
|
- watch(isRunning, (can, _, onCleanup) => {
|
|
|
- if (can) {
|
|
|
- const props = interactiveProps.value!
|
|
|
- const cleanups = [] as Array<() => void>
|
|
|
- if (props.operate?.single) {
|
|
|
- // 如果指定单次则消息中有信息,并且确定完成则马上退出
|
|
|
- cleanups.push(
|
|
|
- watchEffect(() => {
|
|
|
- if (messages.value.length > 0 && singleDone.value) {
|
|
|
- quitMouseAddShape()
|
|
|
- }
|
|
|
- }, {flush: 'post'})
|
|
|
- )
|
|
|
- }
|
|
|
+ const consumedMessages = reactive(new WeakSet<T>()) as WeakSet<T>;
|
|
|
+ const stage = useStage();
|
|
|
+ const interactiveProps = useInteractiveProps();
|
|
|
|
|
|
- // 单纯添加
|
|
|
- if (props.operate?.immediate) {
|
|
|
- messages.value.push(props.operate.data as T)
|
|
|
- singleDone.value = true
|
|
|
- } else {
|
|
|
- const $stage = stage.value!.getStage();
|
|
|
- const dom = $stage.container();
|
|
|
- cleanups.push(init(dom))
|
|
|
- }
|
|
|
- onCleanup(mergeFuns(cleanups));
|
|
|
- } else {
|
|
|
- messages.value = [];
|
|
|
- }
|
|
|
- });
|
|
|
+ watch(isRunning, (can, _, onCleanup) => {
|
|
|
+ if (can) {
|
|
|
+ const props = interactiveProps.value!;
|
|
|
+ const cleanups = [] as Array<() => void>;
|
|
|
+ if (props.operate?.single) {
|
|
|
+ // 如果指定单次则消息中有信息,并且确定完成则马上退出
|
|
|
+ cleanups.push(
|
|
|
+ watchEffect(
|
|
|
+ () => {
|
|
|
+ if (messages.value.length > 0 && singleDone.value) {
|
|
|
+ quit();
|
|
|
+ props.callback && props.callback();
|
|
|
+ }
|
|
|
+ },
|
|
|
+ { flush: "post" }
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
|
|
|
- return {
|
|
|
- isRunning,
|
|
|
- get preset() {
|
|
|
- return interactiveProps.value?.preset;
|
|
|
- },
|
|
|
- get messages() {
|
|
|
- const items = messages.value;
|
|
|
- const result = items.filter((item) => !consumedMessages.has(item));
|
|
|
- autoConsumed && result.forEach((item) => consumedMessages.add(item));
|
|
|
- return result as T[];
|
|
|
- },
|
|
|
- getNdx(item: T) {
|
|
|
- return messages.value.indexOf(item)
|
|
|
- },
|
|
|
- get consumedMessage() {
|
|
|
- const items = messages.value;
|
|
|
- return items.filter((item) => consumedMessages.has(item)) as T[];
|
|
|
- },
|
|
|
- consume(items: T[]) {
|
|
|
- items.forEach((item) => consumedMessages.add(item));
|
|
|
- },
|
|
|
- singleDone
|
|
|
- };
|
|
|
-}
|
|
|
+ // 单纯添加
|
|
|
+ if (props.operate?.immediate) {
|
|
|
+ messages.value.push(props.operate.data as T);
|
|
|
+ singleDone.value = true;
|
|
|
+ } else {
|
|
|
+ const $stage = stage.value!.getStage();
|
|
|
+ const dom = $stage.container();
|
|
|
+ cleanups.push(init(dom));
|
|
|
+ cleanups.push(() => {
|
|
|
+ quit();
|
|
|
+ props.callback && props.callback();
|
|
|
+ });
|
|
|
+ }
|
|
|
+ onCleanup(mergeFuns(cleanups));
|
|
|
+ } else {
|
|
|
+ messages.value = [];
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
+ return {
|
|
|
+ isRunning,
|
|
|
+ get preset() {
|
|
|
+ return interactiveProps.value?.preset;
|
|
|
+ },
|
|
|
+ get messages() {
|
|
|
+ const items = messages.value;
|
|
|
+ const result = items.filter((item) => !consumedMessages.has(item));
|
|
|
+ autoConsumed && result.forEach((item) => consumedMessages.add(item));
|
|
|
+ return result as T[];
|
|
|
+ },
|
|
|
+ getNdx(item: T) {
|
|
|
+ return messages.value.indexOf(item);
|
|
|
+ },
|
|
|
+ get consumedMessage() {
|
|
|
+ const items = messages.value;
|
|
|
+ return items.filter((item) => consumedMessages.has(item)) as T[];
|
|
|
+ },
|
|
|
+ consume(items: T[]) {
|
|
|
+ items.forEach((item) => consumedMessages.add(item));
|
|
|
+ },
|
|
|
+ singleDone,
|
|
|
+ };
|
|
|
+};
|
|
|
|
|
|
type UseInteractiveProps = {
|
|
|
- shapeType?: ShapeType;
|
|
|
- enableTransform?: boolean;
|
|
|
- autoConsumed?: boolean;
|
|
|
+ isRuning: Ref<boolean>;
|
|
|
+ quit: () => void;
|
|
|
+ beforeHandler?: (p: Pos) => Pos;
|
|
|
+ shapeType?: ShapeType;
|
|
|
+ autoConsumed?: boolean;
|
|
|
};
|
|
|
|
|
|
-
|
|
|
export const useInteractiveAreas = ({
|
|
|
- shapeType,
|
|
|
- enableTransform,
|
|
|
- autoConsumed,
|
|
|
- }: UseInteractiveProps = {}) => {
|
|
|
- if (enableTransform === void 0) enableTransform = true;
|
|
|
+ isRuning,
|
|
|
+ autoConsumed,
|
|
|
+ beforeHandler,
|
|
|
+ quit,
|
|
|
+}: UseInteractiveProps) => {
|
|
|
+ const mode = useMode();
|
|
|
+ const can = useCan();
|
|
|
+ const singleDone = ref(true);
|
|
|
+ const messages = ref<Area[]>([]);
|
|
|
|
|
|
- const singleDone = ref(true);
|
|
|
- const messages = ref<Area[]>([])
|
|
|
- const conversionPosition = useConversionPosition(enableTransform);
|
|
|
+ const init = (dom: HTMLDivElement) => {
|
|
|
+ let pushed = false;
|
|
|
+ let pushNdx = -1;
|
|
|
+ let downed = false;
|
|
|
+ let tempArea: Area;
|
|
|
+ let dragging = false;
|
|
|
|
|
|
- const init = (dom: HTMLDivElement) => {
|
|
|
- let pushed = false;
|
|
|
- let pushNdx = -1;
|
|
|
- let downed = false;
|
|
|
- let tempArea: Area;
|
|
|
- let dragging = false
|
|
|
- return dragListener(dom, {
|
|
|
- down(position, ev) {
|
|
|
- if (ev.button === 0) {
|
|
|
- tempArea = [conversionPosition(position)] as unknown as Area;
|
|
|
- downed = true;
|
|
|
- singleDone.value = false;
|
|
|
- dragging = false
|
|
|
- }
|
|
|
- },
|
|
|
- move({end}) {
|
|
|
- if (!downed) return;
|
|
|
- if (pushed) {
|
|
|
- messages.value[pushNdx]![1] = conversionPosition(end);
|
|
|
- } else {
|
|
|
- tempArea[1] = conversionPosition(end);
|
|
|
- pushed = true;
|
|
|
- pushNdx = messages.value.length;
|
|
|
- messages.value[pushNdx] = tempArea;
|
|
|
- }
|
|
|
- dragging = true
|
|
|
- },
|
|
|
- up(position) {
|
|
|
- if (!downed || !dragging) return;
|
|
|
- messages.value[pushNdx]![1] = conversionPosition(position);
|
|
|
- pushNdx = -1;
|
|
|
- pushed = false;
|
|
|
- downed = false;
|
|
|
- dragging = false;
|
|
|
- singleDone.value = true;
|
|
|
- },
|
|
|
- });
|
|
|
- };
|
|
|
+ return mergeFuns(
|
|
|
+ listener(dom, "pointerdown", (ev) => {
|
|
|
+ if (!can.dragMode) return;
|
|
|
+ const position = getOffset(ev, dom);
|
|
|
+ if (ev.button === 0) {
|
|
|
+ tempArea = [
|
|
|
+ beforeHandler ? beforeHandler(position) : position,
|
|
|
+ ] as unknown as Area;
|
|
|
+ downed = true;
|
|
|
+ singleDone.value = false;
|
|
|
+ dragging = false;
|
|
|
+ mode.add(Mode.draging);
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ listener(document.documentElement, "pointermove", (ev) => {
|
|
|
+ if (!can.dragMode) return;
|
|
|
+ const end = getOffset(ev, dom);
|
|
|
+ const point = beforeHandler ? beforeHandler(end) : end;
|
|
|
|
|
|
- return useInteractiveExpose(
|
|
|
- messages,
|
|
|
- init,
|
|
|
- singleDone,
|
|
|
- shapeType,
|
|
|
- autoConsumed
|
|
|
- )
|
|
|
+ if (downed) {
|
|
|
+ if (pushed) {
|
|
|
+ messages.value[pushNdx]![1] = point;
|
|
|
+ } else {
|
|
|
+ tempArea[1] = point;
|
|
|
+ pushed = true;
|
|
|
+ pushNdx = messages.value.length;
|
|
|
+ messages.value[pushNdx] = tempArea;
|
|
|
+ }
|
|
|
+ dragging = true;
|
|
|
+ } else {
|
|
|
+ tempArea = [point] as unknown as Area;
|
|
|
+ }
|
|
|
+ }),
|
|
|
+ listener(dom, "pointerup", (ev) => {
|
|
|
+ if (downed) {
|
|
|
+ mode.del(Mode.draging);
|
|
|
+ } else if (!dragging) return;
|
|
|
+
|
|
|
+ if (can.dragMode) {
|
|
|
+ const position = getOffset(ev, dom);
|
|
|
+ messages.value[pushNdx]![1] = beforeHandler
|
|
|
+ ? beforeHandler(position)
|
|
|
+ : position;
|
|
|
+ }
|
|
|
+
|
|
|
+ pushNdx = -1;
|
|
|
+ pushed = false;
|
|
|
+ downed = false;
|
|
|
+ dragging = false;
|
|
|
+ singleDone.value = true;
|
|
|
+ })
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ return useInteractiveExpose(
|
|
|
+ messages,
|
|
|
+ init,
|
|
|
+ singleDone,
|
|
|
+ isRuning,
|
|
|
+ quit,
|
|
|
+ autoConsumed
|
|
|
+ );
|
|
|
};
|
|
|
|
|
|
export const useInteractiveDots = ({
|
|
|
- shapeType,
|
|
|
- enableTransform,
|
|
|
- autoConsumed,
|
|
|
- }: UseInteractiveProps = {}) => {
|
|
|
- if (enableTransform === void 0) enableTransform = true;
|
|
|
- if (autoConsumed === void 0) autoConsumed = false;
|
|
|
+ autoConsumed,
|
|
|
+ isRuning,
|
|
|
+ quit,
|
|
|
+ beforeHandler,
|
|
|
+}: UseInteractiveProps) => {
|
|
|
+ if (autoConsumed === void 0) autoConsumed = false;
|
|
|
+
|
|
|
+ const mode = useMode();
|
|
|
+ const can = useCan();
|
|
|
+ const singleDone = ref(true);
|
|
|
+ const messages = ref<Pos[]>([]);
|
|
|
|
|
|
- const singleDone = ref(true);
|
|
|
- const conversionPosition = useConversionPosition(enableTransform);
|
|
|
- const messages = ref<Pos[]>([])
|
|
|
+ const init = (dom: HTMLDivElement) => {
|
|
|
+ if (!can.dragMode) return () => {};
|
|
|
+ let moveIng = false;
|
|
|
+ let pushed = false;
|
|
|
+ const empty = { x: -9999, y: -9999 };
|
|
|
+ const pointer = ref(empty);
|
|
|
|
|
|
- const init = (dom: HTMLDivElement) => {
|
|
|
- let moveIng = false;
|
|
|
- let pushed = false;
|
|
|
- const empty = {x: -9999, y: -9999}
|
|
|
- const pointer = ref(empty);
|
|
|
+ mode.add(Mode.draging);
|
|
|
|
|
|
- return mergeFuns(
|
|
|
- clickListener(dom, () => {
|
|
|
- if (!moveIng) return;
|
|
|
- pointer.value = {...empty}
|
|
|
- singleDone.value = true
|
|
|
- moveIng = false
|
|
|
- pushed = false
|
|
|
- }),
|
|
|
- listener(dom, "pointermove", (ev) => {
|
|
|
- if (!pushed) {
|
|
|
- messages.value.push(pointer.value);
|
|
|
- singleDone.value = false;
|
|
|
- pushed = true
|
|
|
- }
|
|
|
+ return mergeFuns(
|
|
|
+ () => {
|
|
|
+ mode.del(Mode.draging);
|
|
|
+ },
|
|
|
+ clickListener(dom, (_, ev) => {
|
|
|
+ if (!moveIng || !can.dragMode) return;
|
|
|
+ pointer.value = { ...empty };
|
|
|
+ singleDone.value = true;
|
|
|
+ moveIng = false;
|
|
|
+ pushed = false;
|
|
|
+ }),
|
|
|
+ listener(dom, "pointermove", (ev) => {
|
|
|
+ if (!can.dragMode) return;
|
|
|
+ if (!pushed) {
|
|
|
+ messages.value.push(pointer.value);
|
|
|
+ singleDone.value = false;
|
|
|
+ pushed = true;
|
|
|
+ }
|
|
|
|
|
|
- moveIng = true
|
|
|
- const current = conversionPosition(getOffset(ev))
|
|
|
- pointer.value.x = current.x;
|
|
|
- pointer.value.y = current.y;
|
|
|
- })
|
|
|
- );
|
|
|
- };
|
|
|
- return useInteractiveExpose(
|
|
|
- messages,
|
|
|
- init,
|
|
|
- singleDone,
|
|
|
- shapeType,
|
|
|
- autoConsumed
|
|
|
- )
|
|
|
+ moveIng = true;
|
|
|
+ const position = getOffset(ev);
|
|
|
+ const current = beforeHandler ? beforeHandler(position) : position;
|
|
|
+ pointer.value.x = current.x;
|
|
|
+ pointer.value.y = current.y;
|
|
|
+ })
|
|
|
+ );
|
|
|
+ };
|
|
|
+ return useInteractiveExpose(
|
|
|
+ messages,
|
|
|
+ init,
|
|
|
+ singleDone,
|
|
|
+ isRuning,
|
|
|
+ quit,
|
|
|
+ autoConsumed
|
|
|
+ );
|
|
|
};
|