From 2c71f9b2bd46d97d50933d9850d565e8abcb4331 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Wed, 18 May 2022 17:52:05 +0800 Subject: [PATCH 1/3] listen editor capture scroll event to update mask coordinates system --- packages/editor/src/components/Editor.tsx | 2 -- .../src/components/EditorMaskWrapper/EditorMask.tsx | 4 ++++ .../components/EditorMaskWrapper/EditorMaskWrapper.tsx | 10 ++++++---- packages/editor/src/services/eventBus.ts | 5 +++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index a6f83f9c..d20324b2 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -160,8 +160,6 @@ export const Editor: React.FC = observer( flexDirection="column" width="full" height="full" - overflow="auto" - padding="20px" transform={`scale(${scale / 100})`} position="relative" > diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx b/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx index 44af662c..090684e9 100644 --- a/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx @@ -132,6 +132,10 @@ export const EditorMask: React.FC = observer((props: Props) => { observeResize(eleMap); updateCoordinateSystem(eleMap); }); + + eventBus.on('captureEditorScroll', () => { + updateCoordinateSystem(eleMap); + }); }, [eleMap, eventBus, observeResize, updateCoordinateSystem]); // listen elements resize and update coordinates diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx b/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx index 37098a63..12dc6668 100644 --- a/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx @@ -30,6 +30,7 @@ export const EditorMaskWrapper: React.FC = observer(props => { const onScroll = () => { if (wrapperRef.current) { setScrollOffset([wrapperRef.current.scrollLeft, wrapperRef.current.scrollTop]); + eventBus.send('captureEditorScroll'); } }; @@ -74,17 +75,18 @@ export const EditorMaskWrapper: React.FC = observer(props => { {children} diff --git a/packages/editor/src/services/eventBus.ts b/packages/editor/src/services/eventBus.ts index a0607b8a..e73d0539 100644 --- a/packages/editor/src/services/eventBus.ts +++ b/packages/editor/src/services/eventBus.ts @@ -13,6 +13,7 @@ export type EventNames = { // it is only used for some operations' side effect selectComponent: string; HTMLElementsUpdated: undefined; + captureEditorScroll: undefined; }; export const initEventBus = () => { @@ -22,6 +23,6 @@ export const initEventBus = () => { off: emitter.off, send: emitter.emit, }; -} +}; -export type EventBusType = ReturnType +export type EventBusType = ReturnType; From fb669a52d61350fb78c2b0c417857ebbb80390d7 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Thu, 19 May 2022 14:08:42 +0800 Subject: [PATCH 2/3] use intersection observer to filter unvisible elements in mask --- packages/editor/src/components/Editor.tsx | 1 + .../EditorMaskWrapper/EditorMask.tsx | 64 ++++++++++++++++--- 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index d20324b2..a61c5f20 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -162,6 +162,7 @@ export const Editor: React.FC = observer( height="full" transform={`scale(${scale / 100})`} position="relative" + overflow='hidden' > {appComponent} diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx b/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx index 090684e9..029b0d56 100644 --- a/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx @@ -57,6 +57,7 @@ export const EditorMask: React.FC = observer((props: Props) => { const maskContainerRect = useRef(); const [coordinates, setCoordinates] = useState>({}); const [coordinatesOffset, setCoordinatedOffset] = useState<[number, number]>([0, 0]); + const visibleMap = useRef(new Map()); // establish the coordinateSystem by getting all the rect of elements, // and recording the current scroll Offset @@ -87,12 +88,12 @@ export const EditorMask: React.FC = observer((props: Props) => { } const foregroundEleMap = modalEleMap.size > 0 ? modalEleMap : eleMap; - - for (const id of foregroundEleMap.keys()) { - const ele = eleMap.get(id)!; - const rect = ele.getBoundingClientRect(); - _rects[id] = rect; - } + foregroundEleMap.forEach((ele, id) => { + if (visibleMap.current.get(ele)) { + const rect = ele.getBoundingClientRect(); + _rects[id] = rect; + } + }); maskContainerRect.current = maskContainerRef.current?.getBoundingClientRect(); setCoordinates(_rects); setCoordinatedOffset([wrapperRef.current.scrollLeft, wrapperRef.current.scrollTop]); @@ -118,33 +119,64 @@ export const EditorMask: React.FC = observer((props: Props) => { }, [resizeObserver] ); + const intersectionObserver = useMemo(() => { + const debouncedUpdateRects = debounce(updateCoordinateSystem, 50); + + const options = { + root: document.getElementById('editor-mask-wrapper'), + rootMargin: '0px', + threshold: buildThresholdList(), + }; + + return new IntersectionObserver(entries => { + entries.forEach(e => { + visibleMap.current.set(e.target, e.isIntersecting); + }); + console.log('visibleMap', visibleMap); + debouncedUpdateRects(eleMap); + }, options); + }, [eleMap, updateCoordinateSystem]); + + const observeIntersect = useCallback( + (eleMap: Map) => { + eleMap.forEach(ele => { + intersectionObserver.observe(ele); + }); + }, + [intersectionObserver] + ); // because this useEffect would run after sunmao didMount hook, so it cannot subscribe the first HTMLElementsUpdated event // we should call the callback function after first render useEffect(() => { observeResize(eleMap); + observeIntersect(eleMap); updateCoordinateSystem(eleMap); + // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { eventBus.on('HTMLElementsUpdated', () => { observeResize(eleMap); + observeIntersect(eleMap); updateCoordinateSystem(eleMap); }); eventBus.on('captureEditorScroll', () => { updateCoordinateSystem(eleMap); }); - }, [eleMap, eventBus, observeResize, updateCoordinateSystem]); + }, [eleMap, eventBus, observeIntersect, observeResize, updateCoordinateSystem]); // listen elements resize and update coordinates useEffect(() => { observeResize(eleMap); + observeIntersect(eleMap); return () => { resizeObserver.disconnect(); + intersectionObserver.disconnect(); }; - }, [eleMap, observeResize, resizeObserver]); + }, [eleMap, intersectionObserver, observeIntersect, observeResize, resizeObserver]); const hoverComponentId = useMemo(() => { const where = whereIsMouse( @@ -255,6 +287,7 @@ export function whereIsMouse( id: '', sum: 0, }; + console.log(left, top, rects) for (const id in rects) { const rect = rects[id]; if ( @@ -265,10 +298,23 @@ export function whereIsMouse( ) { continue; } - const sum = rect.top + rect.left; + const sum = (top - rect.top) + (left - rect.left); if (sum > nearest.sum) { nearest = { id, sum }; } } + console.log('nearest.id', nearest.id); return nearest.id; } +function buildThresholdList() { + const thresholds: number[] = []; + const numSteps = 20; + + for (let i = 1.0; i <= numSteps; i++) { + const ratio = i / numSteps; + thresholds.push(ratio); + } + + thresholds.push(0); + return thresholds; +} From 26230e6575543f92b16d8815e07b512b4099b67d Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Fri, 20 May 2022 17:03:21 +0800 Subject: [PATCH 3/3] refactor mask with mobx store --- packages/editor/__tests__/editorMask.spec.ts | 2 +- .../EditorMaskWrapper/EditorMask.tsx | 278 ++++-------------- .../EditorMaskWrapper/EditorMaskManager.ts | 239 +++++++++++++++ .../EditorMaskWrapper/EditorMaskWrapper.tsx | 2 +- .../src/components/EditorMaskWrapper/index.ts | 1 - packages/editor/src/services/eventBus.ts | 1 - 6 files changed, 293 insertions(+), 230 deletions(-) create mode 100644 packages/editor/src/components/EditorMaskWrapper/EditorMaskManager.ts diff --git a/packages/editor/__tests__/editorMask.spec.ts b/packages/editor/__tests__/editorMask.spec.ts index 8a2d7a8b..30fdc8a6 100644 --- a/packages/editor/__tests__/editorMask.spec.ts +++ b/packages/editor/__tests__/editorMask.spec.ts @@ -1,4 +1,4 @@ -import {whereIsMouse} from '../src/components/EditorMaskWrapper' +import {whereIsMouse} from '../src/components/EditorMaskWrapper/EditorMaskManager' const mockRects = JSON.parse( '{"button19":{"x":307,"y":85,"width":45.046875,"height":40,"top":85,"right":352.046875,"bottom":125,"left":307},"button15":{"x":393.046875,"y":102,"width":40,"height":40,"top":102,"right":433.046875,"bottom":142,"left":393.046875},"input18":{"x":457.046875,"y":102,"width":34,"height":40,"top":102,"right":491.046875,"bottom":142,"left":457.046875},"vstack20":{"x":515.046875,"y":102,"width":55.359375,"height":40,"top":102,"right":570.40625,"bottom":142,"left":515.046875},"hstack14":{"x":376.046875,"y":85,"width":211.359375,"height":74,"top":85,"right":587.40625,"bottom":159,"left":376.046875},"input16":{"x":376.046875,"y":199,"width":211.359375,"height":40,"top":199,"right":587.40625,"bottom":239,"left":376.046875},"stack13":{"x":376.046875,"y":85,"width":211.359375,"height":154,"top":85,"right":587.40625,"bottom":239,"left":376.046875},"button17":{"x":611.40625,"y":85,"width":45.046875,"height":40,"top":85,"right":656.453125,"bottom":125,"left":611.40625},"button23":{"x":714.453125,"y":119,"width":61.359375,"height":40,"top":119,"right":775.8125,"bottom":159,"left":714.453125},"button24":{"x":799.8125,"y":119,"width":61.359375,"height":40,"top":119,"right":861.171875,"bottom":159,"left":799.8125},"hstack22":{"x":697.453125,"y":102,"width":298.546875,"height":74,"top":102,"right":996,"bottom":176,"left":697.453125},"vstack21":{"x":680.453125,"y":85,"width":332.546875,"height":108,"top":85,"right":1013,"bottom":193,"left":680.453125},"hstack12":{"x":290,"y":68,"width":740,"height":188,"top":68,"right":1030,"bottom":256,"left":290}}' diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx b/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx index 029b0d56..adf62f49 100644 --- a/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMask.tsx @@ -1,11 +1,11 @@ -import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { CSSProperties, useEffect, useRef } from 'react'; import { css } from '@emotion/css'; -import { EditorServices } from '../../types'; -import { observer } from 'mobx-react-lite'; -import { DropSlotMask } from './DropSlotMask'; -import { debounce } from 'lodash-es'; -import { Box, Text } from '@chakra-ui/react'; import { DIALOG_CONTAINER_ID } from '@sunmao-ui/runtime'; +import { Box, Text } from '@chakra-ui/react'; +import { observer, useLocalStore } from 'mobx-react-lite'; +import { DropSlotMask } from './DropSlotMask'; +import { EditorMaskManager } from './EditorMaskManager'; +import { EditorServices } from '../../types'; const outlineMaskTextStyle = css` position: absolute; @@ -51,189 +51,30 @@ type Props = { export const EditorMask: React.FC = observer((props: Props) => { const { services, mousePosition, wrapperRef, hoverComponentIdRef, dragOverSlotRef } = props; - const { eventBus, editorStore } = services; - const { selectedComponentId, eleMap, isDraggingNewComponent } = editorStore; + const { editorStore } = services; + const { isDraggingNewComponent } = editorStore; const maskContainerRef = useRef(null); - const maskContainerRect = useRef(); - const [coordinates, setCoordinates] = useState>({}); - const [coordinatesOffset, setCoordinatedOffset] = useState<[number, number]>([0, 0]); - const visibleMap = useRef(new Map()); - // establish the coordinateSystem by getting all the rect of elements, - // and recording the current scroll Offset - // and the updating maskContainerRect, because maskContainer shares the same coordinates with app - const updateCoordinateSystem = useCallback( - (eleMap: Map) => { - function isChild(child: HTMLElement, parent: HTMLElement) { - let curr = child; - while (curr.parentElement && !curr.parentElement.isSameNode(wrapperRef.current)) { - if (curr.parentElement.isSameNode(parent)) { - return true; - } - curr = curr.parentElement; - } - return false; - } - - if (!wrapperRef.current) return; - const _rects: Record = {}; - const modalContainerEle = document.getElementById(DIALOG_CONTAINER_ID)!; - const modalEleMap = new Map(); - // detect if there are components in modal - for (const id of eleMap.keys()) { - const ele = eleMap.get(id)!; - if (isChild(ele, modalContainerEle)) { - modalEleMap.set(id, ele); - } - } - - const foregroundEleMap = modalEleMap.size > 0 ? modalEleMap : eleMap; - foregroundEleMap.forEach((ele, id) => { - if (visibleMap.current.get(ele)) { - const rect = ele.getBoundingClientRect(); - _rects[id] = rect; - } - }); - maskContainerRect.current = maskContainerRef.current?.getBoundingClientRect(); - setCoordinates(_rects); - setCoordinatedOffset([wrapperRef.current.scrollLeft, wrapperRef.current.scrollTop]); - }, - [wrapperRef] + const store = useLocalStore( + () => + new EditorMaskManager(services, wrapperRef, maskContainerRef, hoverComponentIdRef) ); - const resizeObserver = useMemo(() => { - const debouncedUpdateRects = debounce(updateCoordinateSystem, 50); - return new ResizeObserver(() => { - debouncedUpdateRects(eleMap); - }); - }, [eleMap, updateCoordinateSystem]); - - const observeResize = useCallback( - (eleMap: Map) => { - for (const id of eleMap.keys()) { - const ele = eleMap.get(id); - if (ele) { - resizeObserver.observe(ele); - } - } - }, - [resizeObserver] - ); - const intersectionObserver = useMemo(() => { - const debouncedUpdateRects = debounce(updateCoordinateSystem, 50); - - const options = { - root: document.getElementById('editor-mask-wrapper'), - rootMargin: '0px', - threshold: buildThresholdList(), - }; - - return new IntersectionObserver(entries => { - entries.forEach(e => { - visibleMap.current.set(e.target, e.isIntersecting); - }); - console.log('visibleMap', visibleMap); - debouncedUpdateRects(eleMap); - }, options); - }, [eleMap, updateCoordinateSystem]); - - const observeIntersect = useCallback( - (eleMap: Map) => { - eleMap.forEach(ele => { - intersectionObserver.observe(ele); - }); - }, - [intersectionObserver] - ); - - // because this useEffect would run after sunmao didMount hook, so it cannot subscribe the first HTMLElementsUpdated event - // we should call the callback function after first render + const { hoverComponentId, hoverMaskPosition, selectedMaskPosition } = store; useEffect(() => { - observeResize(eleMap); - observeIntersect(eleMap); - updateCoordinateSystem(eleMap); - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - eventBus.on('HTMLElementsUpdated', () => { - observeResize(eleMap); - observeIntersect(eleMap); - updateCoordinateSystem(eleMap); - }); - - eventBus.on('captureEditorScroll', () => { - updateCoordinateSystem(eleMap); - }); - }, [eleMap, eventBus, observeIntersect, observeResize, updateCoordinateSystem]); - - // listen elements resize and update coordinates - useEffect(() => { - observeResize(eleMap); - observeIntersect(eleMap); - return () => { - resizeObserver.disconnect(); - intersectionObserver.disconnect(); - }; - }, [eleMap, intersectionObserver, observeIntersect, observeResize, resizeObserver]); - - const hoverComponentId = useMemo(() => { - const where = whereIsMouse( - mousePosition[0] - coordinatesOffset[0], - mousePosition[1] - coordinatesOffset[1], - coordinates - ); - return where; - }, [coordinatesOffset, mousePosition, coordinates]); + store.setMousePosition(mousePosition); + }, [mousePosition, store]); useEffect(() => { hoverComponentIdRef.current = hoverComponentId; - }, [hoverComponentIdRef, hoverComponentId]); + }, [hoverComponentId, hoverComponentIdRef]); - const getMaskPosition = useCallback( - (componentId: string) => { - const rect = coordinates[componentId]; - const padding = 4; - if (!maskContainerRect.current || !wrapperRef.current || !rect) return; - return { - id: componentId, - style: { - top: rect.top - maskContainerRect.current.top - padding, - left: rect.left - maskContainerRect.current.left - padding, - height: rect.height + padding * 2, - width: rect.width + padding * 2, - }, - }; - }, - [coordinates, wrapperRef] - ); - - const hoverMaskPosition = useMemo(() => { - if (!maskContainerRect.current || !hoverComponentId) { - return undefined; - } - - return getMaskPosition(hoverComponentId); - }, [hoverComponentId, getMaskPosition]); - - const selectedMaskPosition = useMemo(() => { - if (!maskContainerRect.current || !selectedComponentId) return undefined; - - return getMaskPosition(selectedComponentId); - }, [selectedComponentId, getMaskPosition]); + useEffect(() => { + store.modalContainerEle = document.getElementById(DIALOG_CONTAINER_ID); + }); const hoverMask = hoverMaskPosition ? ( - - - {hoverMaskPosition.id} - - + ) : undefined; const dragMask = hoverMaskPosition ? ( @@ -248,16 +89,7 @@ export const EditorMask: React.FC = observer((props: Props) => { ) : undefined; const selectMask = selectedMaskPosition ? ( - - - {selectedMaskPosition.id} - - + ) : undefined; return ( @@ -278,43 +110,37 @@ export const EditorMask: React.FC = observer((props: Props) => { ); }); -export function whereIsMouse( - left: number, - top: number, - rects: Record -): string { - let nearest = { - id: '', - sum: 0, - }; - console.log(left, top, rects) - for (const id in rects) { - const rect = rects[id]; - if ( - top < rect.top || - left < rect.left || - top > rect.top + rect.height || - left > rect.left + rect.width - ) { - continue; - } - const sum = (top - rect.top) + (left - rect.left); - if (sum > nearest.sum) { - nearest = { id, sum }; - } - } - console.log('nearest.id', nearest.id); - return nearest.id; -} -function buildThresholdList() { - const thresholds: number[] = []; - const numSteps = 20; +type MaskProps = { + style: CSSProperties; + id: string; +}; - for (let i = 1.0; i <= numSteps; i++) { - const ratio = i / numSteps; - thresholds.push(ratio); - } +const HoverMask: React.FC = (props: MaskProps) => { + return ( + + + {props.id} + + + ); +}; - thresholds.push(0); - return thresholds; -} +const SelectMask: React.FC = (props: MaskProps) => { + return ( + + + {props.id} + + + ); +}; diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMaskManager.ts b/packages/editor/src/components/EditorMaskWrapper/EditorMaskManager.ts new file mode 100644 index 00000000..7bfa7b46 --- /dev/null +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMaskManager.ts @@ -0,0 +1,239 @@ +import { action, computed, makeObservable, observable } from 'mobx'; +import React from 'react'; +import { EditorServices } from '../../types'; + +export class EditorMaskManager { + // observable: current mouse position + mousePosition: [number, number] = [0, 0]; + // observable: rects of all foreground components + rects: Record = {}; + // observable: rect of mask container + maskContainerRect: DOMRect | null = null; + // observable: the coordinate system offset, it is almost equal to the scroll value of maskWrapper + systemOffset: [number, number] = [0, 0]; + modalContainerEle: HTMLElement | null = null; + // visible status of all components. + private visibleMap = new Map(); + private resizeObserver: ResizeObserver; + private intersectionObserver: IntersectionObserver; + private MaskPadding = 4; + + get hoverComponentId() { + const where = whereIsMouse( + this.mousePosition[0] - this.systemOffset[0], + this.mousePosition[1] - this.systemOffset[1], + this.rects + ); + return where; + } + + get hoverMaskPosition() { + return this.getMaskPosition(this.hoverComponentId); + } + + get selectedMaskPosition() { + return this.getMaskPosition(this.services.editorStore.selectedComponentId); + } + + constructor( + public services: EditorServices, + public wrapperRef: React.MutableRefObject, + public maskContainerRef: React.MutableRefObject, + public hoverComponentIdRef: React.MutableRefObject + ) { + makeObservable(this, { + rects: observable.shallow, + mousePosition: observable.ref, + maskContainerRect: observable.ref, + systemOffset: observable.ref, + setRects: action, + setMousePosition: action, + setMaskContainerRect: action, + setSystemOffset: action, + hoverComponentId: computed, + hoverMaskPosition: computed, + selectedMaskPosition: computed, + }); + + this.resizeObserver = new ResizeObserver(() => { + this.refreshSystem(); + }); + + this.intersectionObserver = this.initIntersectionObserver(); + + this.observeIntersection(); + this.observeResize(); + this.observeEvents(); + } + + private initIntersectionObserver(): IntersectionObserver { + const options = { + root: document.getElementById('editor-mask-wrapper'), + rootMargin: '0px', + threshold: buildThresholdList(), + }; + + return new IntersectionObserver(entries => { + // Update visibleMap. + // Every time intersection observer is triggered, some components' visibility must have changed. + entries.forEach(e => { + this.visibleMap.set(e.target, e.isIntersecting); + }); + // the coordinate system need to be refresh + this.refreshSystem(); + }, options); + } + + // listen resize events of dom elements + private observeResize() { + this.resizeObserver.disconnect(); + this.eleMap.forEach(ele => { + this.resizeObserver.observe(ele); + }); + } + + private observeIntersection() { + this.intersectionObserver.disconnect(); + this.eleMap.forEach(ele => { + this.intersectionObserver.observe(ele); + }); + } + + private observeEvents() { + // listen to the DOM elements' mount and unmount events + // TODO: This is not very accurate, because sunmao runtime 'didDOMUpdate' hook is not accurate. + // We will refactor the 'didDOMUpdate' hook with components' life cycle in the future. + this.services.eventBus.on('HTMLElementsUpdated', () => { + this.refreshSystem(); + this.observeIntersection(); + this.observeResize(); + }); + } + + // Refresh the whole coordinate system. + // Coordinate system is made up of: reacts, maskContainerRect and systemOffset. + private refreshSystem() { + this.updateEleRects(); + if (this.maskContainerEle) { + this.setMaskContainerRect(this.maskContainerEle.getBoundingClientRect()); + } + if (this.wrapperEle) { + this.setSystemOffset([this.wrapperEle.scrollLeft, this.wrapperEle.scrollTop]); + } + } + + private updateEleRects() { + const _rects: Record = {}; + const modalEleMap = new Map(); + // detect if there are components in modal + for (const id of this.eleMap.keys()) { + const ele = this.eleMap.get(id)!; + if (this.isChild(ele, this.modalContainerEle!)) { + modalEleMap.set(id, ele); + } + } + + const foregroundEleMap = modalEleMap.size > 0 ? modalEleMap : this.eleMap; + + foregroundEleMap.forEach((ele, id) => { + if (this.visibleMap.get(ele)) { + const rect = ele.getBoundingClientRect(); + _rects[id] = rect; + } + }); + this.setRects(_rects); + } + + private isChild(child: HTMLElement, parent: HTMLElement) { + let curr = child; + while (curr.parentElement && !curr.parentElement.isSameNode(this.wrapperEle)) { + if (curr.parentElement.isSameNode(parent)) { + return true; + } + curr = curr.parentElement; + } + return false; + } + + private getMaskPosition(id: string) { + const rect = this.rects[id]; + if (!this.maskContainerRect || !rect) return null; + return { + id, + style: { + top: rect.top - this.maskContainerRect.top - this.MaskPadding, + left: rect.left - this.maskContainerRect.left - this.MaskPadding, + height: rect.height + this.MaskPadding * 2, + width: rect.width + this.MaskPadding * 2, + }, + }; + } + + setMousePosition(val: [number, number]) { + this.mousePosition = val; + } + + setSystemOffset(systemOffset: [number, number]) { + this.systemOffset = systemOffset; + } + + setRects(rects: Record) { + this.rects = rects; + } + + setMaskContainerRect(maskContainerRect: DOMRect) { + this.maskContainerRect = maskContainerRect; + } + + private get eleMap() { + return this.services.editorStore.eleMap; + } + + private get wrapperEle() { + return this.wrapperRef.current; + } + + private get maskContainerEle() { + return this.maskContainerRef.current; + } +} + +function buildThresholdList() { + const thresholds: number[] = []; + const numSteps = 20; + + for (let i = 1.0; i <= numSteps; i++) { + const ratio = i / numSteps; + thresholds.push(ratio); + } + + thresholds.push(0); + return thresholds; +} + +export function whereIsMouse( + left: number, + top: number, + rects: Record +): string { + let nearest = { + id: '', + sum: Infinity, + }; + for (const id in rects) { + const rect = rects[id]; + if ( + top < rect.top || + left < rect.left || + top > rect.top + rect.height || + left > rect.left + rect.width + ) { + continue; + } + const sum = top - rect.top + (left - rect.left); + if (sum < nearest.sum) { + nearest = { id, sum }; + } + } + return nearest.id; +} diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx b/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx index 12dc6668..6b70ee98 100644 --- a/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx @@ -30,7 +30,6 @@ export const EditorMaskWrapper: React.FC = observer(props => { const onScroll = () => { if (wrapperRef.current) { setScrollOffset([wrapperRef.current.scrollLeft, wrapperRef.current.scrollTop]); - eventBus.send('captureEditorScroll'); } }; @@ -75,6 +74,7 @@ export const EditorMaskWrapper: React.FC = observer(props => { {