import { DC, EntityShape } from "../../deconstruction"; import { Stage } from "konva/lib/Stage"; import { computed, getCurrentInstance, nextTick, onUnmounted, reactive, ref, shallowRef, watch, WatchCallback, watchEffect, WatchOptions, WatchSource, } from "vue"; import { Mode } from "../../constant/mode.ts"; import { Layer } from "konva/lib/Layer"; import { Pos } from "@/utils/math.ts"; import { listener } from "@/utils/event.ts"; import { mergeFuns } from "@/utils/shared.ts"; export const installGlobalVar = ( create: () => { var: T; onDestroy: () => void } | T, key = Symbol("globalVar"), noRefDel = true ) => { let initialed = false; let refCount = 0; let onDestroy: (() => void) | null = null; const useGlobalVar = (): T => { const instance = getCurrentInstance() as any; const ctx = instance.appContext; if (!initialed) { let val = create() as any; if (typeof val === "object" && "var" in val && "onDestroy" in val) { onDestroy = val.onDestory; val = val.var; } ctx[key] = val; initialed = true; } return ctx[key]; }; return noRefDel ? () => { const instance = getCurrentInstance() as any; const ctx = instance.appContext; ++refCount; onUnmounted(() => { if (--refCount === 0 && noRefDel) { initialed = false; delete ctx[key]; console.log("销毁", key); onDestroy && onDestroy(); onDestroy = null; } }); return useGlobalVar(); } : useGlobalVar; }; export const stackVar = (init?: T) => { const stack = reactive([init]) as T[]; const result = { get value() { return stack[stack.length - 1]; }, set value(val) { stack[stack.length - 1] = val; }, push(data: T) { stack.push(data); }, pop() { if (stack.length - 1 > 0) { stack.pop(); } else { console.error("已到达栈顶"); } }, cycle(data: T, run: () => R): R { result.push(data); const r = run(); result.pop(); return r; }, }; return result; }; export const globalWatch = ( source: WatchSource, cb: WatchCallback, options?: WatchOptions ): (() => void) => { let stop: () => void; nextTick(() => { stop = watch(source, cb as any, options as any); }); return () => { stop && stop(); }; }; export const useStage = installGlobalVar( () => shallowRef | undefined>(), Symbol("stage") ); export const useMode = installGlobalVar(() => { const stack = stackVar(new Set([Mode.viewer])) const modeStack = { ...stack, get value() { return stack.value }, set value(val: Set) { stack.value = val }, push(...modes: Mode[]) { return stack.push(new Set(modes)) }, include(...modes: Mode[]) { return modes.every((m) => modeStack.value.has(m)); }, add(...modes: Mode[]) { modes.forEach((mode) => modeStack.value.add(mode)); }, del(...modes: Mode[]) { modes.forEach((mode) => modeStack.value.delete(mode)); } } if (import.meta.env.DEV) { watchEffect(() => { console.error([...modeStack.value.values()].join(',')) }, { flush: 'sync' }) } return modeStack; }, Symbol("mode")); export const useCan = installGlobalVar(() => { const mode = useMode(); const stage = useStage(); const key = useDownKeys() const loaded = computed(() => !!stage.value?.getStage()) // 鼠标是否可用 const mouse = computed(() => loaded.value && !mode.include(Mode.readonly)) // 可以进入拖拽模式 const dragMode = computed(() => { if (!mouse.value || mode.include(Mode.viewer) || key.has(' ')) return false; return mode.include(Mode.draw) || mode.include(Mode.update) }) // 是否在视图模式 const viewMode = computed(() => { return mouse.value && (!mode.include(Mode.draging) || key.has(' ')) }) // shape是否可以对鼠标做出反应 const mouseReact = computed(() => mouse.value && (mode.include(Mode.viewer) || mode.include(Mode.update))) // 可以进入编辑模式 const editMode = computed(() => mouse.value && mode.include(Mode.viewer)) // 可以进入绘制模式 const drawMode = computed(() => mouse.value && mode.include(Mode.viewer)) return reactive({ viewMouseReact: mouse, viewMode, drawMode, mouseReact, editMode, dragMode, }); }); export const usePointerPos = installGlobalVar(() => { const stage = useStage(); const pos = ref(null); watchEffect((onCleanup) => { const $stage = stage.value?.getNode(); if (!$stage) return; const mount = $stage.container().parentElement!; pos.value = $stage.pointerPos; onCleanup( listener(mount, "pointermove", () => { pos.value = $stage.pointerPos; }) ); }); return pos; }, Symbol("pointerPos")); export const useDownKeys = installGlobalVar(() => { const keys = reactive(new Set()); const cleanup = mergeFuns( listener(window, "keydown", (ev) => { keys.add(ev.key); }), listener(window, "keyup", (ev) => { keys.delete(ev.key); }) ); return { var: keys, onDestroy: cleanup, }; }); export const useLayers = () => { const stage = useStage(); return computed(() => stage.value?.getNode().children as Layer[]); }; export const useTransformIngShapes = installGlobalVar( () => ref([]), Symbol("transformIngShapes") ); export const useCursor = installGlobalVar( () => stackVar('default'), Symbol('cursor') )