import { Size } from "@/utils/math.ts"; import { listener } from "../../utils/event.ts"; import { globalWatch, installGlobalVar, useStage } from "./use-global-vars.ts"; import { nextTick, reactive, ref, watch, watchEffect } from "vue"; import { KonvaEventObject } from "konva/lib/Node"; import { debounce } from "@/utils/shared.ts"; import { Shape } from "konva/lib/Shape"; import { shapeTreeContain } from "@/utils/shape.ts"; import { EntityShape } from "@/deconstruction.js"; export const useListener = < T extends HTMLElement, K extends keyof HTMLElementEventMap >( eventName: K, callback: ( this: T, ev: HTMLElementEventMap[K], stageDOM: HTMLDivElement ) => any, target?: HTMLElement | Window ) => { const stage = useStage(); return watchEffect((onCleanup) => { if (stage.value) { const $stage = stage.value!.getStage(); const dom = $stage.container() as any; onCleanup( listener(target || dom, eventName, function (ev) { callback.call(this, ev, dom); }) ); } }); }; export const useGlobalResize = installGlobalVar(() => { const stage = useStage(); const size = ref(); const setSize = () => { if (fix.value) return; const container = stage.value?.getStage().container(); if (container) { container.style.setProperty("display", "none"); } const dom = stage.value!.getNode().container().parentElement!; size.value = { width: dom.offsetWidth, height: dom.offsetHeight, }; if (container) { container.style.removeProperty("display"); } }; const stopWatch = watchEffect(() => { if (stage.value) { setSize(); nextTick(() => stopWatch()); } }); const fix = ref(false); const setFixSize = (fixSize: { width: number; height: number } | null) => { if (fixSize) { size.value = { ...fixSize }; const unWatch = watchEffect(() => { const $stage = stage.value?.getStage(); if ($stage) { $stage.width(fixSize.width); $stage.height(fixSize.height); nextTick(() => unWatch()); } }); } fix.value = !!fixSize; setSize(); }; return { var: { setFixSize: setFixSize, updateSize: setSize, size, fix, }, onDestroy: listener(window, "resize", debounce(setSize, 16)), }; }, Symbol("resize")); export const useResize = () => useGlobalResize().size; export const usePreemptiveListener = installGlobalVar(() => { const cbs: Record void)[]> = {}; const eventDestorys: Record void> = {}; const stage = useStage(); const add = (eventName: string, cb: (ev: Event) => void) => { if (!cbs[eventName]) { cbs[eventName] = []; const $stage = stage.value!.getStage(); const dom = $stage.container() as any; eventDestorys[eventName] = listener(dom, eventName as any, (ev: any) => { console.log(cbs[eventName], cbs[eventName][0]); cbs[eventName][0].call(this, ev); }); } cbs[eventName].push(cb); }; const remove = (eventName: string, cb?: (ev: Event) => void) => { if (!cbs[eventName]) return; if (cb) { const index = cbs[eventName].indexOf(cb); if (index !== -1) { cbs[eventName].splice(index, 1); } if (cbs[eventName].length === 0) { delete cbs[eventName]; } } else { delete cbs[eventName]; } if (!cbs[eventName]) { eventDestorys[eventName](); } }; const on = ( eventName: T, callback: (this: HTMLDivElement, ev: HTMLElementEventMap[T]) => any ) => { const stopWatch = watchEffect((onCleanup) => { if (stage.value) { add(eventName, callback as any); onCleanup(() => { remove(eventName, callback as any); }); } }); return stopWatch; }; return on; }, Symbol("preemptiveListener")); export const cancelBubbleEvent = (ev: KonvaEventObject) => { ev.cancelBubble = true; ev.evt.stopPropagation; }; export const useGlobalOnlyRightClickShape = installGlobalVar(() => { const stage = useStage(); const checkShapes = reactive([]) as EntityShape[]; const events = [] as Array<((ev: MouseEvent) => void)[]>; const cancels = [] as Array<(((ev?: MouseEvent) => void) | undefined)[]>; const addShape = ( shape: EntityShape, fn: (ev: MouseEvent) => void, cancel?: (ev?: MouseEvent) => void ) => { const ndx = checkShapes.indexOf(shape); if (~ndx) { events[ndx].push(fn); cancels[ndx].push(cancel); } else { checkShapes.push(shape); events.push([fn]); cancels.push([cancel]); } return () => delShape(shape, fn); }; const delShape = (shape: EntityShape, fn: (ev: MouseEvent) => void) => { if (shape === prevShape) { cancel() } const ndx = checkShapes.indexOf(shape); if (!~ndx) return; const evNdx = events[ndx].indexOf(fn); if (!~evNdx) return; events[ndx].splice(evNdx, 1); cancels[ndx].splice(evNdx, 1); if (events[ndx].length === 0) { checkShapes.splice(ndx, 1); cancels.splice(ndx, 1); events.splice(ndx, 1); } }; let prevShape: EntityShape | null = null; const cancel = () => { if (prevShape) { const prevNdx = checkShapes.indexOf(prevShape!); ~prevNdx && cancels[prevNdx].forEach((fn) => fn && fn()); prevShape = null } } const init = (dom: HTMLDivElement) => { const hasRClick = (ev: MouseEvent) => { let clickShape: any let ndx = -1 if (ev.button === 2) { const pos = stage.value?.getNode().pointerPos; if (!pos) return false; clickShape = stage.value?.getNode().getIntersection(pos); console.log(clickShape) do { ndx = checkShapes.indexOf(clickShape!); if (~ndx) { break; } } while ((clickShape = clickShape?.parent)); } cancel() prevShape = clickShape; return ~ndx && events[ndx].forEach((fn) => fn(ev)); }; dom.addEventListener("contextmenu", hasRClick); dom.addEventListener("pointerdown", hasRClick); return () => { cancel() dom.removeEventListener("contextmenu", hasRClick); dom.removeEventListener("pointerdown", hasRClick); }; }; globalWatch( () => stage.value?.getNode().container(), (dom, _, onCleanup) => { if (dom) { onCleanup(init(dom)); } }, { immediate: true } ); return { add: addShape, remove: delShape, }; });