import { DC, EntityShape } from "@/deconstruction"; import { computed, EmitFn, isRef, markRaw, nextTick, onUnmounted, reactive, ref, Ref, shallowReactive, shallowRef, watch, watchEffect, } from "vue"; import { useAutomaticData } from "./use-automatic-data"; import { useCurrentZIndex, useZIndex } from "./use-layer"; import { useAnimationMouseStyle, useMouseShapesStatus, } from "./use-mouse-status"; import { components, DrawItem, ShapeType } from "../components"; import { useMatCompTransformer, useLineTransformer } from "./use-transformer"; import { useGetShapeCopyTransform } from "./use-copy"; import { asyncTimeout, copy, mergeFuns, onlyId } from "@/utils/shared"; import { Shape } from "konva/lib/Shape"; import { Transform } from "konva/lib/Util"; import { mergeDescribes, PropertyDescribes, PropertyKeys, } from "../html-mount/propertys"; import { useStore } from "../store"; import { globalWatch, useMountMenusFilter, useStage } from "./use-global-vars"; import { useAlignmentShape } from "./use-alignment"; import { useViewerTransform } from "./use-viewer"; import { usePause } from "./use-pause"; import { useGlobalDescribes } from "./use-group"; import { ui18n } from "@/lang"; type Emit = EmitFn<{ updateShape: (value: T) => void; addShape: (value: T) => void; delShape: () => void; }>; export const useComponentMenus = ( shape: Ref | undefined>, data: Ref, emit: Emit, alignment?: (data: T, mat: Transform) => void, copyHandler?: (transform: Transform, data: T) => T ) => { const operateMenus: Array<{ icon?: any; label?: string; hide?: boolean, handler: () => void; }> = shallowReactive([]); // 置顶 置底 const currentZIndex = useCurrentZIndex(); operateMenus.push( reactive({ label: computed(() => (data.value.lock ? ui18n.t('sys.unlock') : ui18n.t('sys.lock'))) as any, handler() { data.value.lock = !data.value.lock; emit("updateShape", { ...data.value }); }, }), reactive({ label: ui18n.t('sys.hide'), hide: true, handler() { data.value.hide = true; emit("updateShape", { ...data.value }); }, }) // { // label: `上移`, // icon: Top, // handler() { // data.value.zIndex += 1; // emit("updateShape", { ...data.value }); // }, // }, // { // label: `下移`, // icon: Bottom, // handler() { // data.value.zIndex -= 1; // emit("updateShape", { ...data.value }); // }, // }, ); if (alignment) { const [alignmentShape] = useAlignmentShape(shape); operateMenus.push({ label: ui18n.t('sys.alignment'), async handler() { const mat = await alignmentShape(); alignment(data.value, mat); emit("updateShape", copy({ ...data.value })); // let ndata = { ...data.value, id: onlyId() } // emit('addShape', ndata) // emit('delShape') }, }); } if (copyHandler) { const getCopyTransform = useGetShapeCopyTransform(shape); const status = useMouseShapesStatus(); const stage = useStage(); operateMenus.push({ label: ui18n.t('sys.copy'), async handler() { const transform = getCopyTransform(); const copyData = copyHandler( transform, JSON.parse(JSON.stringify(data.value)) as T ); copyData.id = onlyId(); emit("addShape", copyData); status.actives = []; await asyncTimeout(100); const $stage = stage.value?.getNode(); if (!$stage) return; const $shape = $stage.findOne(`#${copyData.id}`); if ($shape) { status.actives = [$shape]; } }, }); } operateMenus.push( { label: ui18n.t('sys.ztop'), handler() { data.value.zIndex = currentZIndex.max + 1; emit("updateShape", { ...data.value }); }, }, { label: ui18n.t('sys.zbot'), handler() { data.value.zIndex = currentZIndex.min - 1; emit("updateShape", { ...data.value }); }, } ); if (!data.value.disableDelete) { operateMenus.push({ label: ui18n.t('sys.del'), handler() { emit("delShape"); }, }); } return operateMenus; }; export type UseComponentStatusProps< T extends DrawItem, S extends EntityShape > = { emit: Emit; type?: ShapeType; props: { data: T }; alignment?: (data: T, mat: Transform) => void; getMouseStyle: any; defaultStyle: any; propertys: PropertyKeys; selfData?: boolean; debug?: boolean; noJoinZindex?: boolean; noOperateMenus?: boolean; transformType?: "line" | "mat" | "custom"; customTransform?: ( callback: () => void, shape: Ref | undefined>, data: Ref ) => void; getRepShape?: () => Shape; copyHandler?: (transform: Transform, data: T) => T; }; export const useComponentDescribes = ( data: Ref, propertys: PropertyKeys, defaultStyle: any ) => { const store = useStore(); const id = computed(() => data.value.id); const type = computed(() => id.value && store.getType(id.value)); const initDescs = mergeDescribes(data, defaultStyle, propertys || []); const { getFilter } = useMountMenusFilter(); const gdescs = useGlobalDescribes(); let descs = ref(initDescs); watchEffect(() => { const iDescs = type.value && id.value ? getFilter(type.value, id.value)(initDescs) : initDescs; descs.value = Object.fromEntries( Object.entries(iDescs).sort( ([_a, a], [_b, b]) => (b.sort || -1) - (a.sort || -1) ) ) as PropertyDescribes; }); watchEffect((onCleanup) => { gdescs.set(data.value, descs.value); onCleanup(() => { gdescs.del(data.value.id); }); }); watch( descs, (descs) => { for (const key in descs) { const initProps = descs[key].props; watchEffect(() => { if (!type.value) return; const getPredefine = components[type.value].getPredefine; const predefine = getPredefine && (getPredefine as any)(key as keyof DrawItem); if (!predefine) return; if (initProps) { descs[key].props = { ...initProps, ...predefine }; } else { descs[key].props = predefine; } }); } }, { immediate: true } ); return descs; }; export const useComponentStatus = ( args: UseComponentStatusProps ) => { const shape = shallowRef>(); watchEffect( () => { if (shape.value) { markRaw(shape.value); } }, { flush: "sync" } ); const data = useAutomaticData( () => args.props.data, (data) => (args.selfData ? data : copy(data)) ); const [style, pause, resume] = useAnimationMouseStyle({ data: data, shape, getMouseStyle: args.getMouseStyle, }) as any; watchEffect(() => { if (data.value.lock) { pause(); } else { resume(); } }); if (args.transformType === "line") { useLineTransformer( shape as any, data as any, (newData) => args.emit("updateShape", newData as T), args.getRepShape as any ); } else if (args.transformType === "mat") { useMatCompTransformer(shape, data as any, (nData) => args.emit("updateShape", nData as any) ); } else if (args.transformType === "custom" && args.customTransform) { args.customTransform( () => args.emit("updateShape", data.value as any), shape, data ); } if (!args.noJoinZindex) { useZIndex(shape, data); } return { data, style, tData: computed(() => { const tData = { ...args.defaultStyle, ...data.value }; if (style) { Object.assign(tData, style.value); } return tData; }), shape, operateMenus: args.noOperateMenus ? [] : useComponentMenus( shape, data, args.emit, args.alignment, args.copyHandler ), describes: useComponentDescribes( data, args.propertys || [], args.defaultStyle ), }; }; export const useGetShapeBelong = () => { const store = useStore(); return (shape: EntityShape) => { let curId = shape.id(); let id: string; let item: DrawItem; let type: ShapeType; do { id = shape.id(); item = store.getItemById(id)!; type = store.getType(id)!; if (item && type) { break; } } while ((shape = shape.parent as any)); return item ? { item, isSelf: curId === id, type, id, curId } : null; }; }; export const useGetComponentData = () => { const store = useStore(); const getShapeBelong = useGetShapeBelong(); return (shape: Ref | EntityShape | undefined) => computed(() => { shape = isRef(shape) ? shape.value : shape; if (!shape?.id()) return; let item: any = store.getItemById(shape.id()); if (!item) { const belong = getShapeBelong(shape); if (belong && !belong.isSelf) { const getter = components[belong.type].childrenDataGetter; let parent: any; if (getter && (parent = store.getItemById(belong.id))) { item = getter(parent, belong.curId); } } } return item; }); }; export const useComponentsAttach = ( getter: (type: K, data: DrawItem) => T, types = Object.keys(components) as ShapeType[] ) => { const store = useStore(); const attachs = reactive([]) as T[]; const cleanups = [] as Array<() => void>; for (const type of types) { cleanups.push( globalWatch( () => store.getTypeItems(type), (_a, _, onCleanup) => { const items = store.getTypeItems(type); if (!items) return; for (const item of items) { const attachWatchStop = watchEffect((onCleanup) => { const attach = getter(type, item); attachs.push(attach); onCleanup(() => { const ndx = attachs.indexOf(attach); ~ndx && attachs.splice(ndx, 1); }); }); const existsWatchStop = watchEffect(() => { if (!items.includes(item)) { attachWatchStop(); existsWatchStop(); } }); onCleanup(() => { attachWatchStop(); existsWatchStop(); }); } }, { immediate: true, deep: true } ) ); } return { attachs, cleanup: mergeFuns(cleanups), }; }; export const useOnComponentBoundChange = () => { const getComponentData = useGetComponentData(); const transform = useViewerTransform(); const quitHooks = [] as Array<() => void>; const destory = () => mergeFuns(quitHooks)(); const on = ( shapes: Ref | T | T[] | undefined, callback: (shape: T, type: "transform" | "data") => void, viewListener = true ) => { const $shapes = computed(() => { shapes = isRef(shapes) ? shapes.value : shapes; if (!shapes) return []; return Array.isArray(shapes) ? shapes : [shapes]; }); const update = ($shape: T, type?: "transform" | "data") => { if (api.isPause) return; callback($shape, type || "data"); }; const shapeListener = (shape: T) => { const repShape = (shape.repShape as T) || shape; const syncBd = () => update(repShape); repShape.on("transform", syncBd); shape.on("bound-change", syncBd); return () => { repShape.off("transform", syncBd); shape.off("bound-change", syncBd); }; }; const cleanups: (() => void)[] = []; if (viewListener) { cleanups.push( watch(transform, () => $shapes.value.forEach(($shape) => update($shape, "transform")) ) ); } cleanups.push( watch( $shapes, ($shapes, _, onCleanup) => { const cleanups = $shapes.flatMap(($shape) => { let item = getComponentData($shape); return [ watch(item, () => nextTick(() => update($shape, "data")), { deep: true, }), shapeListener($shape), ]; }); onCleanup(mergeFuns(cleanups)); }, { immediate: true } ) ); const onDestroy = mergeFuns(cleanups); quitHooks.push(onDestroy); return () => { const ndx = quitHooks.indexOf(onDestroy); ~ndx && quitHooks.splice(ndx, 1); onDestroy(); }; }; const api = usePause({ destory, on }); onUnmounted(destory); return api; };