import { DC, EntityShape } from "../../deconstruction"; import { Stage } from "konva/lib/Stage"; import { computed, getCurrentInstance, nextTick, reactive, Ref, ref, shallowRef, watch, WatchCallback, WatchEffect, watchEffect, WatchEffectOptions, WatchOptions, WatchSource, } from "vue"; import { Layer } from "konva/lib/Layer"; import { Pos } from "@/utils/math.ts"; import { listener } from "@/utils/event.ts"; import { debounce, mergeFuns, onlyId } from "@/utils/shared.ts"; import { StoreData } from "../store/store.ts"; import { rendererMap, rendererName } from "@/constant/index.ts"; import { Shape, ShapeConfig } from "konva/lib/Shape"; import { ElLoading } from "element-plus"; import { PropertyDescribes } from "../html-mount/propertys/index.ts"; import { ShapeType } from "@/index.ts"; import { isEditableElement } from "@/utils/dom.ts"; let getInstance = getCurrentInstance export const useRendererInstance = () => { let instance = getInstance()!; while (instance.type.name !== rendererName) { if (instance.parent) { instance = instance.parent; } else { throw "未发现渲染实例"; } } return instance; }; export const installGlobalVar = ( create: () => { var: T; onDestroy: () => void } | T, key = Symbol("globalVar") ) => { const useGlobalVar = (): T => { const instance = useRendererInstance() as any; const { unmounteds } = rendererMap.get(instance)!; if (!(key in instance)) { let val = create() as any; if (typeof val === "object" && "var" in val && "onDestroy" in val) { val.onDestroy && unmounteds.push(val.onDestroy); if (import.meta.env.DEV) { unmounteds.push(() => { console.log("销毁变量", key); }); } val = val.var; } instance[key] = val; } return instance[key]; }; return useGlobalVar; }; export const useRunHook = installGlobalVar(() => { const instance = getCurrentInstance() return R>(hook: T): R => { const back = getInstance getInstance = () => instance const result = hook() getInstance = back return result } }) export type InstanceProps = { id?: string; data?: StoreData; handlerResource(file: File): Promise; }; export const useInstanceProps = installGlobalVar(() => { const props = ref(); return { set(val: InstanceProps) { props.value = val; }, get() { return props.value!; }, }; }, Symbol("instanceId")); export const stackVar = (init?: T) => { const factory = (init: T) => ({ var: init, id: onlyId() }); const stack = reactive([]) as { var: T; id: string }[]; if (init) { stack.push(factory(init)); } const result = { get value() { return stack[stack.length - 1]?.var; }, set value(val) { stack[stack.length - 1].var = val; }, push(data: T) { stack.push(factory(data)); const item = stack[stack.length - 1]; const pop = (() => { const ndx = stack.findIndex(({ id }) => id === item.id); if (~ndx) { stack.splice(ndx, 1); } }) as (() => void) & { set: (data: T) => void }; pop.set = (data) => { item.var = data; }; return pop; }, 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 globalWatchEffect = ( cb: WatchEffect, options?: WatchEffectOptions ): (() => void) => { let stop: () => void; nextTick(() => { stop = watchEffect(cb as any, options); }); return () => { stop && stop(); }; }; export const useStage = installGlobalVar( () => shallowRef | undefined>(), Symbol("stage") ); export const usePointerPos = installGlobalVar(() => { const stage = useStage(); const pos = ref(null) as Ref & { replay: () => void }; let lastClient: { clientX: number; clientY: number } | null = null; let replayIng = false; const replay = () => { const $stage = stage.value?.getNode(); if (!$stage || !lastClient) return; replayIng = true; const dom = $stage.container().querySelector("canvas") as HTMLCanvasElement; const moveConf = { bubbles: true, // 事件是否能够冒泡 cancelable: true, // 事件是否可以被取消 isPrimary: true, pointerId: 1, }; dom.dispatchEvent( new PointerEvent("pointermove", { ...moveConf, clientX: -9999999, clientY: -9999999, }) ); dom.dispatchEvent( new PointerEvent("pointermove", { ...lastClient, ...moveConf }) ); replayIng = false; }; const stopWatch = globalWatchEffect((onCleanup) => { const $stage = stage.value?.getNode(); if (!$stage) return; const mount = $stage.container().parentElement!; pos.value = $stage.pointerPos; onCleanup( listener(mount, "pointermove", (ev) => { pos.value = $stage.pointerPos; if (pos.value && !replayIng) { lastClient = { clientX: ev.clientX, clientY: ev.clientY, }; } }) ); }); pos.replay = replay; return { var: pos, onDestroy: stopWatch, }; }, Symbol("pointerPos")); export const usePointerIntersections = installGlobalVar(() => { const shapes = ref[]>([]); const pos = usePointerPos(); const stage = useStage(); const updateShapes = debounce(() => { if (!pos.value || !stage.value) { return; } shapes.value = stage.value.getNode().getAllIntersections(pos.value) || []; }, 300); const stopWatch = watch(pos, updateShapes); return { var: shapes, onDestroy: () => { stopWatch(); shapes.value = []; }, }; }); export const usePointerIntersection = installGlobalVar(() => { const shape = ref | null>(null); const pos = usePointerPos(); const stage = useStage(); const updateShape = debounce(() => { if (!pos.value || !stage.value) { return; } shape.value = stage.value.getNode().getIntersection(pos.value); }, 16); const stopWatch = watch(pos, updateShape); return { var: shape, onDestroy: () => { stopWatch(); shape.value = null; }, }; }); export const useDownKeys = installGlobalVar(() => { const keyKeys = reactive(new Set()); const mouseKeys = reactive(new Set()); const evHandler = (ev: KeyboardEvent | MouseEvent, keys: Set) => { ev.shiftKey ? keys.add("Shift") : keys.delete("Shift"); ev.altKey ? keys.add("Alt") : keys.delete("Alt"); ev.metaKey ? keys.add("Meta") : keys.delete("Meta"); ev.ctrlKey ? keys.add("Ctrl") : keys.delete("Ctrl"); } const cleanup = mergeFuns( listener(window, "keydown", (ev) => { if (!isEditableElement(ev.target as HTMLElement)) { keyKeys.add(ev.key); evHandler(ev, keyKeys) } }), listener(window, "keyup", (ev) => { keyKeys.delete(ev.key); evHandler(ev, keyKeys) }), listener(window, "mousemove", (ev) => { evHandler(ev, mouseKeys) }) ); const keys = reactive(new Set()); watchEffect(() => { keys.clear(); for (const key of keyKeys.values()) { keys.add(key); } for (const key of mouseKeys.values()) { keys.add(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") ); export type MPart = { comp: any; props: any }; export const useMountParts = installGlobalVar(() => { const mParts = reactive([]); const del = (part: MPart) => { const ndx = mParts.indexOf(part); ~ndx && mParts.splice(ndx, 1); }; return { value: mParts, add(part: MPart) { mParts.push(part); return () => del(part); }, del, }; }); export const useForciblyShowItemIds = installGlobalVar(() => { const set = new Set() as Set & { cycle: (id: string, fn: () => any) => void; }; set.cycle = (id, fn) => { set.add(id); const result = fn(); if (result instanceof Promise) { result.then(() => set.delete(id)); } else { set.delete(id); } }; return set; }, Symbol("forciblyShowItemId")); export const useRendererDOM = installGlobalVar(() => ref()) export const useTempStatus = installGlobalVar(() => { const temp = ref(false); const enterTemp = (fn: () => T): T => { temp.value = true const result = fn() if (result instanceof Promise) { return result.then(async (data) => { temp.value = false await nextTick() return data; }).catch(r => { temp.value = false throw r }) as T } else { temp.value = false return result } } const dom = useRendererDOM() watch(temp, (_a, _b, onCleanup) => { if (temp.value && dom.value) { const instance = ElLoading.service({ fullscreen: true, target: dom.value }) onCleanup(() => instance.close()) } }) return { tempStatus: temp, enterTemp } }); const getFilters = () => { type Val = (d: T) => T const globalFilter = ref<{[key in ShapeType]?: Val[]}>({}) const shapeFilter = ref>({}) const setShapeFilter = (id: string, descs: Val) => { if (shapeFilter.value[id]) { shapeFilter.value[id].push(descs) } else { shapeFilter.value[id] = [descs] } return () => { if (shapeFilter.value[id]) { const ndx = shapeFilter.value[id].indexOf(descs) shapeFilter.value[id].splice(ndx, 1) } } } const setFilter = (type: ShapeType, descs: Val) => { if (globalFilter.value[type]) { globalFilter.value[type].push(descs) } else { globalFilter.value[type] = [descs] } return () => { if (globalFilter.value[type]) { const ndx = globalFilter.value[type].indexOf(descs) globalFilter.value[type].splice(ndx, 1) } } } return { setFilter, setShapeFilter, getFilter(type: ShapeType, id: string) { return (menus: T) => { if (globalFilter.value[type]) { for (const filter of globalFilter.value[type]) { menus = filter(menus) } } if (shapeFilter.value[id]) { for (const filter of shapeFilter.value[id]) { menus = filter(menus) } } return menus } } } } export const useMountMenusFilter = installGlobalVar(() => { const menusFilter = getFilters() return { setMenusFilter: menusFilter.setFilter, setShapeMenusFilter: menusFilter.setShapeFilter, getFilter: menusFilter.getFilter } }) export const useMouseMenusFilter = installGlobalVar(() => { type Menu = { icon?: any; label?: string; handler: () => void } const propsFilter = getFilters() return { setMenusFilter: propsFilter.setFilter, setShapeMenusFilter: propsFilter.setShapeFilter, getFilter: propsFilter.getFilter } })