diff --git a/packages/lb-components/src/utils/ToolUtils.ts b/packages/lb-components/src/utils/ToolUtils.ts index 01acbb77a..293f1f2cf 100644 --- a/packages/lb-components/src/utils/ToolUtils.ts +++ b/packages/lb-components/src/utils/ToolUtils.ts @@ -20,6 +20,10 @@ class ToolUtils { return tool ? (Object.values(EPointCloudName) as string[]).includes(tool) : false; } + public static isMappingTool(tool?: string) { + return tool ? ([EToolName.Point, EToolName.Tag, EToolName.Polygon, EToolName.Line] as string[]).includes(tool) : false; + } + public static getPointCloudToolList() { return [EToolName.Point, EToolName.Line, EToolName.PointCloudPolygon]; } diff --git a/packages/lb-components/src/views/MainView/index.tsx b/packages/lb-components/src/views/MainView/index.tsx index bd234c88c..6e7759932 100644 --- a/packages/lb-components/src/views/MainView/index.tsx +++ b/packages/lb-components/src/views/MainView/index.tsx @@ -23,6 +23,7 @@ import PreviewResult from '@/components/predictTracking/previewResult'; import { LabelBeeContext } from '@/store/ctx'; import { EToolName } from '@/data/enums/ToolType'; import LLMLayout from './LLMLayout'; +import { AnnotationMappingWrapper } from './mappingWrapper/AnnotationMappingWrapper' interface IProps { path: string; @@ -61,6 +62,7 @@ const AnnotatedArea: React.FC = (props) => { const currentToolName = getStepConfig(stepList, step)?.tool; const isVideoTool = ToolUtils.isVideoTool(currentToolName); const isPointCloudTool = ToolUtils.isPointCloudTool(currentToolName); + const isMappingTool = ToolUtils.isMappingTool(currentToolName) if (isVideoTool) { return ; @@ -70,6 +72,12 @@ const AnnotatedArea: React.FC = (props) => { return ; } + if (isMappingTool) { + return + + + } + return ; }; diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/AnnotationMappingWrapper.tsx b/packages/lb-components/src/views/MainView/mappingWrapper/AnnotationMappingWrapper.tsx new file mode 100644 index 000000000..3b80d517e --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/AnnotationMappingWrapper.tsx @@ -0,0 +1,80 @@ +/** + * @author lijingchi + * @file 标注页面的映射视图,注入annotation的数据以嵌入到标注工具组件 + * @date 2021-12-15 + */ + +import React, { useMemo } from 'react'; +import { getImageFromMappingUnit } from '@/utils/ImageManager'; +import { connectComponent } from '@/utils/store'; +import { jsonParser } from '@/utils/tool/common'; +import { EAnnotationMapping } from '@/constant/task'; +import { StateType } from '@/models/annotation'; +import { StateType as EditAnnotationType } from '@/models/editAnnotation'; +import { IMappingWrapperRenderProps, MappingWrapperRender } from './index'; +import _ from 'lodash'; + +export interface IAnnotationMappingWrapper { + annotation: StateType; + editAnnotation: EditAnnotationType; + children: JSX.Element; + result: any[]; + rotate: number; + basicResult: any; +} + +/** + * 标注页面(包含编辑步骤),根据标注单元(unitType)判读是否需要显示对应的视图 + * @param props: IAnnotationMappingWrapper + */ +export const AnnotationMappingWrapper = (props: IAnnotationMappingWrapper) => { + const { annotation, editAnnotation, children, result, rotate, basicResult } = props; + const isEdit = annotation.isEdit || editAnnotation.isEdit; + const anno: StateType | EditAnnotationType = isEdit ? editAnnotation : annotation; + const imageBSrc = getImageFromMappingUnit(anno, 'B'); + const imageASrc = getImageFromMappingUnit(anno); + const { currentStep, stepList } = anno; + const completeResult = isEdit ? {} : jsonParser(annotation.result) || {}; + const mappingConfig = annotation?.mappingConfig; + const mappingType = mappingConfig?.mappingType; + const isNoMapping = mappingType === EAnnotationMapping.No; + const writtingResult = useMemo(() => { + return isNoMapping ? [] : _.cloneDeep(result); + }, [JSON.stringify(result), isNoMapping]); + const unitType = annotation?.unitType; + + const mappingWrapperRenderProps: IMappingWrapperRenderProps = { + imageBSrc, + imageASrc, + writtingResult, + stepList, + step: currentStep, + rotate, + unitType, + children, + mappingConfig, + completeResult, + basicResult: isNoMapping ? undefined : basicResult, + isAnnotatePage: true, + }; + + return ; +}; + +export const AnnoCtxMappingWrapper = connectComponent( + ['annotation', 'editAnnotation', 'createStep'], + AnnotationMappingWrapper, +); + +/** + * 计算A图的宽度 + * @param size + * @param right + */ +export const canvasSizeForAImage = (size: ISize, right?: number) => { + if (right) { + return { ...size, width: size.width * right }; + } + + return size; +}; diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/GraphicMappingView.tsx b/packages/lb-components/src/views/MainView/mappingWrapper/GraphicMappingView.tsx new file mode 100644 index 000000000..de9d4c3f6 --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/GraphicMappingView.tsx @@ -0,0 +1,206 @@ +/** + * @author lijingchi + * @file B图映射渲染组件,根据A图的结果进行悬案 + * @date 2021-12-15 + */ + +import React, { useContext, useMemo } from 'react'; +import { BasicOperationContext } from '../tools/basicOperation'; +import { EToolName } from '@/constant/tool'; +import DrawPolygon from '@/pages/checkTool/components/DrawPolygon'; +import DrawLine from '@/pages/checkTool/components/DrawLine'; +import DrawPoint from '@/components/tools/pointTool/drawPoint'; +import DrawTag from '@/pages/checkTool/components/tagTool'; +import DrawImage from '@/components/tools/drawImage'; +import { StaticViewWithRelyData } from '../StaticView'; +import { getCurrentStepInfo, jsonParser } from '@/utils/tool/common'; +import { IStepConfig } from '@/service/task'; +import { ITagConfig } from '@/types/toolConfig'; +import { uniqWith } from 'lodash'; +import { EStepType } from '@/constant/task'; +import { ReactComponent as BPoint } from '@/assets/icon/BPoint.svg'; +import { connectComponent } from '@/utils/store'; + +export interface IMappingViewGraphicProps { + transferedResult: any[]; + step: number; + stepList: IStepConfig[]; + isCheckTool?: boolean; + isAnnotatePage?: boolean; +} + +const fillLableAndFindCoordForTags = (transferedResult: any[], config: ITagConfig) => { + const tags = transferedResult.map((i) => { + const resultKeyFromInputList: any = {}; + if (i.result) { + Object.keys(i.result).forEach((r) => { + const inputItem = config.inputList.find((s) => s.value === r); + const valueItem = inputItem && inputItem.subSelected?.find((s) => s.value === i.result[r]); + + if (inputItem && valueItem) { + resultKeyFromInputList[inputItem.key] = valueItem.key; + } else { + resultKeyFromInputList[r] = i.result[r]; + } + }); + + return { + ...i, + result: resultKeyFromInputList, + }; + } + + return i; + }); + + return tags; +}; + +/** + * 支持多个形式的参数传递 + * @param props + */ +export const GraphicMappingViewWrapper = (props: IMappingViewGraphicProps) => { + const { transferedResult, stepList, step, isCheckTool } = props; + const stepArray = isCheckTool + ? uniqWith(transferedResult, (arrVal, othVal) => { + return arrVal.step === othVal.step && arrVal.stepType === othVal.stepType; + }) + : [{ step }]; + + return stepArray.map((s) => { + const stepType = s.stepType ?? EStepType.ANNOTATION; + const isPre = stepType === EStepType.PRE_ANNOTATION; + const stepInfo = isPre + ? { tool: s.tool, step: s.step, config: '{}' } + : getCurrentStepInfo(s.step, stepList); + + const result = isCheckTool + ? transferedResult.filter((r) => r.step === s.step && r.stepType === stepType) + : transferedResult; + + return ( + + ); + }); +}; + +/** + * 映射视图,目前支持 多边形、线条、标点、标签 + * @param IMappingViewGraphicProps + */ +const GraphicMappingView = (props: { result: any[]; stepInfo: any }) => { + const { result, stepInfo } = props; + const { tool, config: configStr } = stepInfo || {}; + const config = jsonParser(configStr); + const { imgNode, currentPos, zoom, rotate, size } = useContext(BasicOperationContext); + + const toolProps = { + imgNode, + currentPos, + zoom, + rotate, + size, + config, + result, + }; + + if (tool === EToolName.Polygon) { + return ; + } + + if (tool === EToolName.Line) { + return ; + } + + if (tool === EToolName.Point) { + return ; + } + + if (tool === EToolName.Tag) { + const tags = fillLableAndFindCoordForTags(result, config); + const hasPos = tags.some((i) => i.x || i.y); + + return ( + + ); + } + + return null; +}; + +const ImageViewUsedContext = () => { + const context = useContext(BasicOperationContext); + return ; +}; + +/** 图片B光标中心点, 在syncMappingView(缩放同步)开启后显示 */ +const ImageBCursor = connectComponent( + ['imgAttribute'], + ({ + isAnnotatePage, + imgAttribute, + }: { + isAnnotatePage?: boolean; + imgAttribute: IImageAttribute; + }) => { + const context = useContext(BasicOperationContext); + const { top, left } = useMemo(() => { + /** 中心点对齐 图片size为 12*12 */ + return { + top: context.imageACoord.y - 6, + left: context.imageACoord.x - 6, + }; + }, [context.imageACoord.x, context.imageACoord.y]); + + if (isAnnotatePage && imgAttribute.syncMappingView) { + return ; + } + + return null; + }, +); + +export const MappingView = ( + props: IMappingViewGraphicProps & { completeResult: any; transferedResult: any }, +) => { + const { + transferedResult, + step, + stepList, + completeResult, + basicResult, + isCheckTool, + isAnnotatePage, + } = props; + + return ( + <> + + + + + + ); +}; diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/ImageMappingTag.tsx b/packages/lb-components/src/views/MainView/mappingWrapper/ImageMappingTag.tsx new file mode 100644 index 000000000..b3d32260e --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/ImageMappingTag.tsx @@ -0,0 +1,54 @@ +/** + * @author lijingchi + * @file 映射视图左上角Tag组件 + * @date 2021-12-15 + */ + +import React from 'react'; +import { EAnnotationMapping } from '@/constant/task'; +import { MappingImageType } from '@/types/task'; + +/** + * 根据mappingType, 获取B图映射描述 + * @param mappingType + */ +const getImageBDesc = (mappingType?: EAnnotationMapping) => { + if (mappingType === EAnnotationMapping.OneOne) { + return '(A图映射)'; + } + + if (mappingType === EAnnotationMapping.Fisheye) { + return '(A图鱼眼映射)'; + } + + return ''; +}; + +/** + * 映射Tag, 定位在A、B图的左上角 + * @param props + */ +export const ImageMappingTag = (props: { + imageType: MappingImageType; + mappingType?: EAnnotationMapping; +}) => { + const { imageType, mappingType } = props; + const text = imageType === 'A' ? 'A图' : `B图,仅预览${getImageBDesc(mappingType)}`; + + return ( +
+ {text} +
+ ); +}; diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/MappingWrapperDivide.tsx b/packages/lb-components/src/views/MainView/mappingWrapper/MappingWrapperDivide.tsx new file mode 100644 index 000000000..0d13d71ea --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/MappingWrapperDivide.tsx @@ -0,0 +1,32 @@ +/** + * @author lijingchi + * @file 映射视图的分割线 + * @date 2021-12-15 + */ +import React from 'react'; +import { MAPPING_WRAPPER_DIVIDE_ZINDEX } from '@/constant/zIndex'; + +export const MappingWrapperDivide = (props: { + left: string; + onMouseDown: () => void; + onMouseUp: () => void; +}) => { + const { left, onMouseDown, onMouseUp } = props; + return ( +
onMouseDown()} + onMouseUp={() => onMouseUp()} + style={{ + position: 'absolute', + zIndex: MAPPING_WRAPPER_DIVIDE_ZINDEX, + top: 0, + cursor: 'ew-resize', + width: 4, + bottom: 0, + left, + background: 'white', + transform: 'translateX(-2px)', + }} + /> + ); +}; diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/ResizeLayer.tsx b/packages/lb-components/src/views/MainView/mappingWrapper/ResizeLayer.tsx new file mode 100644 index 000000000..9083624b1 --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/ResizeLayer.tsx @@ -0,0 +1,57 @@ +/** + * @author lijingchi + * @file 映射视图的resize蒙版,当前的调整参数传递到上层 + * @date 2021-12-15 + */ + +import React, { useState } from 'react'; +import { MAPPING_WRAPPER_RESIZE_LAYER_ZINDEX } from '@/constant/zIndex'; + +export const ResizeLayer = (props: { + onResize: boolean; + cancelResize: () => void; + updateOffset: (offsetPercent: number) => void; +}) => { + const { onResize, cancelResize, updateOffset } = props; + + const [prevClientX, setPrevClientX] = useState(null); + + const mouseLeave = () => { + cancelResize(); + }; + + const mouseup = () => { + cancelResize(); + }; + + const mousemove = (e: React.MouseEvent) => { + const curClientX = e.clientX; + setPrevClientX(curClientX); + + const offsetX = prevClientX ? curClientX - prevClientX : 0; + + if (offsetX !== 0) { + const targetWidth = e.target.clientWidth; + updateOffset(offsetX / targetWidth); + } + }; + + if (!onResize) { + return null; + } + + return ( +
+ ); +}; diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/index.scss b/packages/lb-components/src/views/MainView/mappingWrapper/index.scss new file mode 100644 index 000000000..c5521e936 --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/index.scss @@ -0,0 +1,6 @@ +.mappingWrapper { + display: flex; + flex: 1; + overflow: hidden; + position: relative; +} \ No newline at end of file diff --git a/packages/lb-components/src/views/MainView/mappingWrapper/index.tsx b/packages/lb-components/src/views/MainView/mappingWrapper/index.tsx new file mode 100644 index 000000000..d54f687a3 --- /dev/null +++ b/packages/lb-components/src/views/MainView/mappingWrapper/index.tsx @@ -0,0 +1,453 @@ +/** + * @author lijingchi + * @file 映射视图的包裹组件,负责A图插槽和B图的数据处理 + * @date 2021-12-15 + */ + +import React, { useState, useEffect, useRef } from 'react'; +import { withinRange } from '@/utils/math'; +import { BasicOperationProvider } from '../tools/basicOperation'; +import styles from './index.scss'; +import { MappingWrapperDivide } from './MappingWrapperDivide'; +import { ResizeLayer } from './ResizeLayer'; +import { MappingView, IMappingViewGraphicProps } from './GraphicMappingView'; +import { EAnnotationMapping, EUnitMark } from '@/constant/task'; +import useSize from '@/hooks/useSize'; +import { ImageMappingTag } from './ImageMappingTag'; +import { getDependPattern } from '@/utils/tool/common'; +import { connectComponent } from '@/utils/store'; +import { IStyleState } from '@/models/toolStyle'; +import { getFishEyesResult } from '@/service/pythonApi'; +import { IMappingConfig } from '@/types/task'; +import { Dispatch, AnyAction } from 'redux'; +import _ from 'lodash'; +import { useRequest, usePrevious } from 'ahooks'; +import { EDependPattern } from '@/constant/tool'; +import EventBus from '@/utils/EventBus'; + +interface IMappingWrapperProps extends IMappingViewGraphicProps { + imageBSrc: string; + imageASrc: string; + rotate: number; + mappingConfig: IMappingConfig; + children?: JSX.Element; + completeResult: { [key: string]: any }; + basicResult: any[]; + writtingResult: any; + dependPattern?: EDependPattern; + isCheckTool?: boolean; + isAnnotatePage?: boolean; +} + +export interface IMappingWrapperRenderProps extends IMappingWrapperProps { + unitType?: EUnitMark; +} + +export const MappingWrapperContext = React.createContext<{ + width: { left: number; right: number }; +}>({ + width: { + left: 0, + right: 1, + }, +}); + +type PointList = Array<{ x: number; y: number; pointList?: PointList }>; +type PointArray = ICoordArray[]; + +const sensebeeRes2NumArray = (result: PointList) => { + const array: PointArray = []; + + result.forEach((i) => { + if (!i) { + return; + } + + if (i?.pointList) { + i.pointList.forEach((p) => { + array.push([p.x.decimalReserved(4), p.y.decimalReserved(4)]); + }); + } + + if (i?.x !== undefined && i?.y !== undefined) { + array.push([i.x.decimalReserved(4), i.y.decimalReserved(4)]); + } + }); + + return array; +}; + +const fisheyeRes2SensebeeRes = (fishPointList: PointArray, result: any) => { + let startIdx = 0; + const res = _.cloneDeep(result); + const isPositiveNum = (num: number | null) => num !== null && num >= 0; + + res.forEach((i: any, index: number) => { + if (i?.pointList) { + const transferedPointList: any[] = []; + i.pointList.forEach((p: any) => { + const x = fishPointList[startIdx][0]; + const y = fishPointList[startIdx][1]; + + if (isPositiveNum(x) && isPositiveNum(y)) { + transferedPointList.push({ ...p, x, y }); + } + + startIdx += 1; + }); + i.pointList = transferedPointList; + } else { + const coord = fishPointList[index]; + i.x = coord ? coord[0] : null; + i.y = coord ? coord[1] : null; + } + }); + + return res.filter((i: ICoord) => + i.x !== undefined && i.y !== undefined ? isPositiveNum(i.x) && isPositiveNum(i.y) : true, + ); +}; + +const useRequestTransferRes = ( + result: any, + setFn: (d: any) => void, + mappingConfig: IMappingConfig, + imageASize: ISize, + requet: any, +) => { + const { mappingType, fisheyeConfig } = mappingConfig; + if (!result?.length) { + setFn([]); + return; + } + + if (mappingType === EAnnotationMapping.Fisheye) { + const xyArray = sensebeeRes2NumArray(result); + + if (!fisheyeConfig) { + return; + } + + if (xyArray.length > 0) { + requet({ + point: xyArray, + config: fisheyeConfig, + fisheyeHeight: imageASize.height, + fisheyeWidth: imageASize.width, + }); + } else { + setFn(result); + } + } else { + setFn(result ?? []); + } +}; + +/** 获取结果的特征值用于比对 */ +const getResultArrayUnique = (result?: any[]) => + result + ?.map( + (i) => `${i.id}${JSON.stringify(i.pointList)}${i.x}${i.y}${i.text}${i.attribute}${i.label}`, + ) + .join(''); +/** + * 映射包裹组件 + * 1、A图和B图的宽度调整 + * 2、B图的缩放支持 + * 3、B图的渲染数据注入(依赖数据、渲染数据、A图宽高) + * 4、B图鱼眼映射的计算(原图和依赖信息) + */ +const MappingWrapper = connectComponent( + ['toolStyle', 'imgAttribute'], + ( + props: IMappingWrapperProps & { + toolStyle: IStyleState; + dispatch: Dispatch; + imgAttribute: IImageAttribute; + }, + ) => { + const { + imageBSrc, + writtingResult, + stepList, + step, + rotate, + imageASrc, + children, + completeResult, + basicResult, + toolStyle, + mappingConfig, + dispatch, + imgAttribute, + isCheckTool, + isAnnotatePage, + } = props; + + const { mappingType } = mappingConfig; + const [left, setLeft] = useState(0.5); + const [onResize, setOnResize] = useState(false); + const [imgNode, setImgNode] = useState(null); + const wraperRef = useRef(null); + const wrapperSize = useSize(wraperRef); + const [transferedResult, setTransferedResult] = useState([]); + const [transferedBasicResult, setTransferedBasicResult] = useState([]); + const [imageASize, setImageASize] = useState({ width: 0, height: 0 }); + const [imageBSize, setImageBSize] = useState({ width: 0, height: 0 }); + const leftPercentage = left.toPercentage(2); + const imageBRef = React.useRef(null); + const [imageACurrentPos, setImageACurrentPos] = useState({ x: 0, y: 0 }); + const [imageACoord, setImageACoord] = useState({ x: 0, y: 0 }); + const [imageAZoom, setImageAZoom] = useState(0); + + const [imageALoaded, setImageALoaded] = useState(false); + const [imageBLoaded, setImageBLoaded] = useState(false); + + const preWrittingResult = usePrevious(writtingResult); + const preBasicResult = usePrevious(basicResult); + + const resRequest = useRequest(getFishEyesResult, { + manual: true, + onSuccess: (r: any) => { + setTransferedResult(fisheyeRes2SensebeeRes(r.point_list, writtingResult)); + }, + onError: () => { + setTransferedResult([]); + }, + throttleWait: 300, + throttleTrailing: true, + }); + + const basicResRequest = useRequest(getFishEyesResult, { + manual: true, + onSuccess: (r: any) => { + setTransferedBasicResult(fisheyeRes2SensebeeRes(r.point_list, basicResult)); + }, + onError: () => { + setTransferedBasicResult([]); + }, + throttleWait: 300, + throttleTrailing: true, + }); + + const changeLeftZoom = (zoom: number) => { + dispatch({ + type: 'toolStyle/setLeftZoom', + payload: { + leftZoom: zoom, + }, + }); + }; + + const size = { + width: wrapperSize?.width ? wrapperSize?.width * left : 0, + height: wrapperSize?.height ?? 0, + }; + + const right = 1 - left; + + const dependPattern = props.dependPattern ?? getDependPattern(step, stepList, completeResult); + + const changeImageBCurrentPosAndZoom = ( + params: { zoom: number; currentPos: ICoord }, + changeInnerZoom: boolean = false, + ) => { + if (imageBRef?.current) { + changeLeftZoom(params.zoom); + if (changeInnerZoom) { + imageBRef.current.innerZoom = params.zoom; + } + imageBRef?.current?.updatePosDirectly(params); + } + }; + + useEffect(() => { + EventBus.singleOn('imageACurrentPosOrZoomChanged', (params: any) => { + if (params.zoom) { + setImageAZoom(params.zoom); + } + + if (params.currentPos) { + setImageACurrentPos(params.currentPos); + } + }); + EventBus.singleOn('imageACoordChanged', ({ coord }: { coord: ICoord }) => { + setImageACoord(coord); + }); + }, []); + + useEffect(() => { + if (imgAttribute.syncMappingView) { + changeImageBCurrentPosAndZoom({ currentPos: imageACurrentPos, zoom: imageAZoom }, true); + } + }, [imageACurrentPos, imageAZoom, imgAttribute.syncMappingView]); + + useEffect(() => { + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.src = imageBSrc; + setImageBLoaded(false); + + img.onload = () => { + setImgNode(img); + setImageBSize({ + width: img.width, + height: img.height, + }); + + setImageBLoaded(true); + }; + }, [imageBSrc]); + + useEffect(() => { + const img = new Image(); + img.crossOrigin = 'Anonymous'; + img.src = imageASrc; + setImageALoaded(false); + + img.onload = () => { + setImageASize({ + width: img.width, + height: img.height, + }); + + setImageALoaded(true); + }; + }, [imageASrc]); + + useEffect(() => { + /** 禁止查看模式在步骤节点数据hover时触发更新 */ + if (getResultArrayUnique(writtingResult) === getResultArrayUnique(preWrittingResult)) { + return; + } + + useRequestTransferRes( + writtingResult, + setTransferedResult, + mappingConfig, + imageASize, + resRequest.run, + ); + }, [writtingResult]); + + useEffect(() => { + if (getResultArrayUnique(preBasicResult) !== getResultArrayUnique(basicResult)) { + useRequestTransferRes( + basicResult, + setTransferedBasicResult, + mappingConfig, + imageASize, + basicResRequest.run, + ); + } + }, [basicResult]); + + useEffect(() => { + /** 图片加载后判断宽高是否在误差内,若在开启同步缩放 */ + if (imageALoaded && imageBLoaded && isAnnotatePage) { + const SYNC_WHILE_DIFF = 0.03; + const widthInDiff = Math.abs(imageASize.width / imageBSize.width - 1) < SYNC_WHILE_DIFF; + const heightInDiff = Math.abs(imageASize.height / imageBSize.height - 1) < SYNC_WHILE_DIFF; + if (widthInDiff && heightInDiff) { + dispatch({ + type: 'imgAttribute/changeImgAttribute', + payload: { + syncMappingView: true, + }, + }); + } + } + }, [imageALoaded, imageBLoaded]); + + return ( + +
+
+ + + { + imageBRef.current = basicOperationRef; + }} + > + + +
+ + { + setOnResize(true); + }} + onMouseUp={() => { + setOnResize(false); + }} + /> +
+ + {children} +
+
+ + { + setOnResize(false); + }} + updateOffset={(offsetX: number) => { + const updatedLeft = (left + offsetX).decimalReserved(2); + setLeft(withinRange(updatedLeft, [0.2, 0.8])); + }} + /> +
+ ); + }, +); + +/** + * 映射渲染组件,根据unitType判断是否渲染成映射视图 + * @param IMappingWrapperRenderProps + */ +export const MappingWrapperRender = (props: IMappingWrapperRenderProps) => { + const { unitType, children } = props; + const isCustomUnit = unitType === EUnitMark.Custom; + + if (!children) { + return null; + } + + if (isCustomUnit) { + return {children}; + } + + return children; +}; + +export default MappingWrapper;