import React from 'react'; import Box from '@mui/material/Box'; import { observer } from 'mobx-react-lite'; import { globalStorage } from '@/storage'; import { ActionType, getAgentActionStyle } from '@/storage/plan'; interface ILine { type: T; from: string; to: string; } interface RehearsalSvgProps { cardRefMap: Map>; renderCount: number; objectStepOrder: string[]; importantLines: ILine[]; actionIsHovered: string | undefined; } const getIOLineHeight = (nodeOrder: string[], lines: ILine[]) => { const edgeHeightIndexMap_ = new Map(); const compareFunction = (a: ILine, b: ILine): number => { const [afrom, ato] = [a.from, a.to]; const [bfrom, bto] = [b.from, b.to]; const afromPos = nodeOrder.indexOf(afrom); const bfromPos = nodeOrder.indexOf(bfrom); const atoPos = nodeOrder.indexOf(ato); const btoPos = nodeOrder.indexOf(bto); // 如果最小位置相同,则比较位置之间的距离 const aDistance = atoPos - afromPos; const bDistance = btoPos - bfromPos; if (aDistance !== bDistance) { return aDistance - bDistance; } else { return afromPos - bfromPos; } }; lines.sort(compareFunction); const isCrossOver = (ptPair: number[], ptList: number[][]) => { for (const pt of ptList) { if (pt[1] <= ptPair[0] || pt[0] >= ptPair[1]) { continue; } return true; } return false; }; lines.forEach(line => { const fromIndex = nodeOrder.indexOf(line.from); const toIndex = nodeOrder.indexOf(line.to); let h = 1; while ( isCrossOver([fromIndex, toIndex], edgeHeightIndexMap_.get(h) || []) ) { h += 1; } edgeHeightIndexMap_.set(h, [ ...(edgeHeightIndexMap_.get(h) || []), [fromIndex, toIndex], ]); }); const edgeHeightMap_ = new Map(); edgeHeightIndexMap_.forEach((pairList, height) => { // 遍历当前条目的数组,将数字替换为数组b中对应的名称 pairList.forEach(pair => { edgeHeightMap_.set(pair.map(index => nodeOrder[index]).join('.'), height); }); }); return edgeHeightMap_; }; const getOffset = (child: HTMLElement, parent: HTMLElement) => { const parentRect = parent.getBoundingClientRect(); const childRect = child.getBoundingClientRect(); // 计算相对位置 return new DOMRect( childRect.left - parentRect.left, childRect.top - parentRect.top, childRect.width, childRect.height, ); }; const calcCurve = (fromCard: DOMRect, toCard: DOMRect, height: number) => { // calc bezier curve // from [fromCard.left, fromCard.top+0.5*fromCard.height] // to [toCard.left, toCard.top+0.5*toCard.height const h = fromCard.left * height; return `M ${fromCard.left + fromCard.width},${ fromCard.top + 0.5 * fromCard.height } C ${fromCard.left + fromCard.width + 1.5 * h},${ fromCard.top + 0.5 * fromCard.height }, ${toCard.left + toCard.width + 1.5 * h},${toCard.top + 0.5 * toCard.height}, ${toCard.left + toCard.width},${toCard.top + 0.5 * toCard.height}`; }; const calcPath = (objectCard: DOMRect, stepCard: DOMRect, height: number) => { // console.log('calcPath', fromCard, toCard, height); const fromCard = objectCard.top < stepCard.top ? objectCard : stepCard; const toCard = objectCard.top < stepCard.top ? stepCard : objectCard; const h = fromCard.left * height; const ptList = [ { x: fromCard.left, y: fromCard.top + fromCard.height - 10 }, { x: fromCard.left - h, y: fromCard.top + fromCard.height + 10 - 10 }, { x: toCard.left - h, y: toCard.top + 0 * toCard.height - 10 + 10 }, { x: toCard.left, y: toCard.top + 0 * toCard.height + 10 }, ]; const path = [ `M ${ptList[0].x},${ptList[0].y}`, `L ${ptList[1].x},${ptList[1].y}`, `L ${ptList[2].x},${ptList[2].y}`, `L ${ptList[3].x},${ptList[3].y}`, ].join(' '); return path; }; export default observer( ({ cardRefMap, renderCount, objectStepOrder, importantLines, actionIsHovered, }: RehearsalSvgProps) => { const IOLines = globalStorage.renderingIOLines; const edgeHeightMap = React.useMemo( () => getIOLineHeight(objectStepOrder, IOLines), [objectStepOrder, IOLines], ); const [ioLineRects, setIOLineRects] = React.useState< [ILine, DOMRect, DOMRect][] >([]); const [importantLineRects, setImportantLineRects] = React.useState< [ILine, DOMRect, DOMRect][] >([]); const refreshCurrentIdRef = React.useRef(-1); React.useEffect(() => { refreshCurrentIdRef.current = (refreshCurrentIdRef.current + 1) % 100000; const currentId = refreshCurrentIdRef.current; const sleep = (time: number) => new Promise(resolve => setTimeout(resolve, time)); (async () => { let ioAllReady = false; let importantAllReady = false; const ioLineRects: [ILine, DOMRect, DOMRect][] = []; const importantLineRects: [ILine, DOMRect, DOMRect][] = []; while (true) { if (refreshCurrentIdRef.current !== currentId) { return; } const rootElement = cardRefMap.get('root')?.current; if (!rootElement) { await sleep(5); continue; } if (!ioAllReady) { ioAllReady = true; for (const line of IOLines) { const fromElement = cardRefMap.get(line.from)?.current; const toElement = cardRefMap.get(line.to)?.current; if (fromElement && toElement) { ioLineRects.push([ line, getOffset(fromElement, rootElement), getOffset(toElement, rootElement), ]); } else { ioAllReady = false; continue; } } if (!ioAllReady) { ioLineRects.length = 0; await sleep(5); continue; } } if (!importantAllReady) { importantAllReady = true; for (const line of importantLines) { const fromElement = cardRefMap.get(line.from)?.current; const toElement = cardRefMap.get(line.to)?.current; if (fromElement && toElement) { importantLineRects.push([ line, getOffset(fromElement, rootElement), getOffset(toElement, rootElement), ]); } else { importantAllReady = false; break; } } if (!importantAllReady) { importantLineRects.length = 0; await sleep(5); continue; } } break; } setIOLineRects(ioLineRects); setImportantLineRects(importantLineRects); })(); }, [edgeHeightMap, renderCount, cardRefMap]); const ioLinesEle = React.useMemo( () => ioLineRects.map(([line, from, to]) => { const key = `${line.from}.${line.to}`; const height = edgeHeightMap.get(key) || 0; return ( ); }), [ioLineRects], ); const importantLinesEle = React.useMemo( () => importantLineRects .sort((a, b) => { // eslint-disable-next-line no-nested-ternary return a[0].to === b[0].to ? 0 : a[0].to === actionIsHovered ? 1 : -1; }) .map(([line, from, to]) => { const key = `${line.from}.${line.to}`; return ( ); }), [importantLineRects, actionIsHovered], ); const height = cardRefMap .get('root') ?.current?.querySelector?.('.contents-stack')?.scrollHeight; return ( {ioLinesEle} {importantLinesEle} ); }, );