setup(frontend): rename frontend-react
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
import React from 'react';
|
||||
import _ from 'lodash';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useTaskModificationContext } from './context';
|
||||
import { globalStorage } from '@/storage';
|
||||
|
||||
const getRefOffset = (
|
||||
child: React.RefObject<HTMLElement>,
|
||||
grandParent: React.RefObject<HTMLElement>,
|
||||
) => {
|
||||
const offset = { top: 0, left: 0, width: 0, height: 0 };
|
||||
if (!child.current || !grandParent.current) {
|
||||
return offset;
|
||||
}
|
||||
let node = child.current;
|
||||
// Traverse up the DOM tree until we reach the grandparent or run out of elements
|
||||
while (node && node !== grandParent.current) {
|
||||
offset.top += node.offsetTop;
|
||||
offset.left += node.offsetLeft;
|
||||
// Move to the offset parent (the nearest positioned ancestor)
|
||||
node = node.offsetParent as HTMLElement;
|
||||
}
|
||||
// If we didn't reach the grandparent, return null
|
||||
if (node !== grandParent.current) {
|
||||
return offset;
|
||||
}
|
||||
offset.width = child.current.offsetWidth;
|
||||
offset.height = child.current.offsetHeight;
|
||||
return offset;
|
||||
};
|
||||
|
||||
const TaskModificationSvg = observer(() => {
|
||||
const {
|
||||
forestPaths,
|
||||
whoIsAddingBranch,
|
||||
nodeRefMap,
|
||||
svgForceRenderCounter,
|
||||
containerRef,
|
||||
} = useTaskModificationContext();
|
||||
const { currentActionNodeSet } = globalStorage;
|
||||
|
||||
const [nodeRectMap, setNodeRectMap] = React.useState(
|
||||
new Map<
|
||||
string,
|
||||
{
|
||||
top: number;
|
||||
left: number;
|
||||
width: number;
|
||||
height: number;
|
||||
}
|
||||
>(),
|
||||
);
|
||||
React.useEffect(() => {
|
||||
if (containerRef) {
|
||||
const nodeRectMap_ = new Map(
|
||||
[...nodeRefMap].map(kv => {
|
||||
return [kv[0], getRefOffset(kv[1], containerRef)];
|
||||
}),
|
||||
);
|
||||
setNodeRectMap(nodeRectMap_);
|
||||
}
|
||||
}, [svgForceRenderCounter, whoIsAddingBranch]);
|
||||
|
||||
const renderLine = (startid: string, endid: string) => {
|
||||
const startRect = nodeRectMap.get(startid);
|
||||
const endRect = nodeRectMap.get(endid);
|
||||
if (!startRect || !endRect) {
|
||||
return <></>;
|
||||
}
|
||||
let isCurrent = false;
|
||||
if (currentActionNodeSet.has(startid) && currentActionNodeSet.has(endid)) {
|
||||
isCurrent = true;
|
||||
}
|
||||
if (startid === 'root' && currentActionNodeSet.has(endid)) {
|
||||
isCurrent = true;
|
||||
}
|
||||
// console.log(`line.${startid}${startRect.left}.${endid}${endRect.left}`);
|
||||
return (
|
||||
<path
|
||||
key={`line.${startid}${startRect.left}.${endid}${endRect.left}`}
|
||||
d={`M ${startRect.left + 0.5 * startRect.width} ${
|
||||
startRect.top + 0.5 * startRect.height
|
||||
}
|
||||
C ${startRect.left + startRect.width * 0.5} ${
|
||||
endRect.top + 0.5 * endRect.height
|
||||
},
|
||||
${endRect.left} ${endRect.top + 0.5 * endRect.height},
|
||||
${endRect.left} ${endRect.top + 0.5 * endRect.height}`}
|
||||
fill="none"
|
||||
stroke={isCurrent ? '#4a9c9e' : '#D9D9D9'}
|
||||
strokeWidth="6"
|
||||
></path>
|
||||
);
|
||||
};
|
||||
const renderRoot = () => {
|
||||
const rootRect = nodeRectMap.get('root');
|
||||
if (rootRect && forestPaths.length > 0) {
|
||||
return (
|
||||
<circle
|
||||
key={`root${rootRect.left}`}
|
||||
cx={rootRect.left + 0.5 * rootRect.width}
|
||||
cy={rootRect.top + 0.5 * rootRect.height}
|
||||
r="10"
|
||||
fill="#4a9c9e"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return <></>;
|
||||
};
|
||||
return (
|
||||
<svg
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
// backgroundColor: 'red',
|
||||
width: _.max(
|
||||
[...nodeRectMap.values()].map(rect => rect.left + rect.width),
|
||||
),
|
||||
height: _.max(
|
||||
[...nodeRectMap.values()].map(rect => rect.top + rect.height),
|
||||
),
|
||||
}}
|
||||
>
|
||||
{forestPaths.map(pair => renderLine(pair[0], pair[1]))}
|
||||
{whoIsAddingBranch && renderLine(whoIsAddingBranch, 'requirement')}
|
||||
{renderRoot()}
|
||||
</svg>
|
||||
);
|
||||
});
|
||||
|
||||
export default TaskModificationSvg;
|
||||
210
frontend-react/src/components/TaskModification/context.tsx
Normal file
210
frontend-react/src/components/TaskModification/context.tsx
Normal file
@@ -0,0 +1,210 @@
|
||||
// TaskModificationContext.tsx
|
||||
import React, {
|
||||
ReactNode,
|
||||
RefObject,
|
||||
createContext,
|
||||
useContext,
|
||||
useState,
|
||||
useEffect,
|
||||
} from 'react';
|
||||
import { IAgentActionTreeNode, globalStorage } from '@/storage';
|
||||
|
||||
interface TaskModificationContextProps {
|
||||
forest: IAgentActionTreeNode | undefined;
|
||||
setForest: (forest: IAgentActionTreeNode) => void;
|
||||
forestPaths: [string, string][];
|
||||
setForestPaths: (paths: [string, string][]) => void;
|
||||
|
||||
whoIsAddingBranch: string | undefined;
|
||||
setWhoIsAddingBranch: (whoIsAddingBranch: string | undefined) => void;
|
||||
updateWhoIsAddingBranch: (whoIsAddingBranch: string | undefined) => void;
|
||||
|
||||
containerRef: React.RefObject<HTMLElement> | undefined;
|
||||
setContainerRef: (containerRef: React.RefObject<HTMLElement>) => void;
|
||||
nodeRefMap: Map<string, RefObject<HTMLElement>>;
|
||||
updateNodeRefMap: (key: string, value: RefObject<HTMLElement>) => void;
|
||||
|
||||
baseNodeSet: Set<string>;
|
||||
setBaseNodeSet: (baseNodeSet: Set<string>) => void;
|
||||
baseLeafNodeId: string | undefined;
|
||||
setBaseLeafNodeId: (baseLeafNodeId: string | undefined) => void;
|
||||
|
||||
handleRequirementSubmit: (requirement: string, number: number) => void;
|
||||
handleNodeClick: (nodeId: string) => void;
|
||||
|
||||
handleNodeHover: (nodeId: string | undefined) => void;
|
||||
|
||||
svgForceRenderCounter: number;
|
||||
setSVGForceRenderCounter: (n: number) => void;
|
||||
svgForceRender: () => void;
|
||||
}
|
||||
|
||||
const TaskModificationContext = createContext<TaskModificationContextProps>(
|
||||
{} as TaskModificationContextProps,
|
||||
);
|
||||
|
||||
export const useTaskModificationContext = () =>
|
||||
useContext(TaskModificationContext);
|
||||
|
||||
export const TaskModificationProvider: React.FC<{ children: ReactNode }> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [forest, setForest] = useState<IAgentActionTreeNode>();
|
||||
const [forestPaths, setForestPaths] = useState<[string, string][]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (forest) {
|
||||
setForestPaths(_getFatherChildrenIdPairs(forest));
|
||||
}
|
||||
}, [forest]);
|
||||
|
||||
const [whoIsAddingBranch, setWhoIsAddingBranch] = useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const updateWhoIsAddingBranch = (whoId: string | undefined) => {
|
||||
if (whoId === whoIsAddingBranch) {
|
||||
setWhoIsAddingBranch(undefined);
|
||||
} else {
|
||||
setWhoIsAddingBranch(whoId);
|
||||
}
|
||||
};
|
||||
const [containerRef, setContainerRef] = React.useState<
|
||||
React.RefObject<HTMLElement> | undefined
|
||||
>(undefined);
|
||||
const [baseNodeSet, setBaseNodeSet] = React.useState<Set<string>>(
|
||||
new Set<string>(),
|
||||
);
|
||||
const [baseLeafNodeId, setBaseLeafNodeId] = React.useState<
|
||||
string | undefined
|
||||
>(undefined);
|
||||
const [nodeRefMap] = React.useState(
|
||||
new Map<string, RefObject<HTMLElement>>(),
|
||||
);
|
||||
const updateNodeRefMap = (key: string, value: RefObject<HTMLElement>) => {
|
||||
nodeRefMap.set(key, value);
|
||||
};
|
||||
|
||||
const handleRequirementSubmit = (requirement: string, number: number) => {
|
||||
if (whoIsAddingBranch) {
|
||||
const start =
|
||||
whoIsAddingBranch === 'root' ? undefined : whoIsAddingBranch;
|
||||
// globalStorage.newPlanBranch(start, requirement, number, baseLeafNodeId);
|
||||
globalStorage.newActionBranch(
|
||||
globalStorage.currentFocusingAgentSelection?.id,
|
||||
start,
|
||||
requirement,
|
||||
number,
|
||||
baseLeafNodeId,
|
||||
);
|
||||
setWhoIsAddingBranch(undefined);
|
||||
setBaseNodeSet(new Set());
|
||||
setBaseLeafNodeId(undefined);
|
||||
}
|
||||
};
|
||||
const handleNodeClick = (nodeId: string) => {
|
||||
const leafId = globalStorage.getFirstLeafAction(nodeId)?.node.id;
|
||||
console.log(leafId);
|
||||
if (leafId) {
|
||||
if (whoIsAddingBranch) {
|
||||
if (baseLeafNodeId === leafId) {
|
||||
setBaseNodeSet(new Set());
|
||||
setBaseLeafNodeId(undefined);
|
||||
} else {
|
||||
const pathNodeSet = new Set(globalStorage.getActionLeafPath(leafId));
|
||||
if (
|
||||
pathNodeSet.has(whoIsAddingBranch) ||
|
||||
whoIsAddingBranch === 'root'
|
||||
) {
|
||||
setBaseLeafNodeId(leafId);
|
||||
setBaseNodeSet(pathNodeSet);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
globalStorage.setCurrentAgentActionBranch(leafId, undefined);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const [svgForceRenderCounter, setSVGForceRenderCounter] = useState(0);
|
||||
const svgForceRender = () => {
|
||||
setSVGForceRenderCounter((svgForceRenderCounter + 1) % 100);
|
||||
};
|
||||
|
||||
const handleNodeHover = (nodeId: string | undefined) => {
|
||||
if (!whoIsAddingBranch) {
|
||||
if (nodeId) {
|
||||
const leafNode = globalStorage.getFirstLeafAction(nodeId);
|
||||
if (leafNode?.branchInfo) {
|
||||
// const branchInfo =
|
||||
// globalStorage.focusingStepTask.agentSelection.branches[leafNode.id];
|
||||
// console.log(leafNode);
|
||||
if (leafNode.branchInfo.base) {
|
||||
const pathNodeSet = new Set(
|
||||
globalStorage.getActionLeafPath(leafNode.branchInfo.base),
|
||||
);
|
||||
setBaseNodeSet(pathNodeSet);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setBaseNodeSet(new Set());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setBaseNodeSet(new Set());
|
||||
setBaseLeafNodeId(undefined);
|
||||
}, [whoIsAddingBranch]);
|
||||
|
||||
useEffect(() => {
|
||||
svgForceRender();
|
||||
}, [forest, whoIsAddingBranch]);
|
||||
|
||||
return (
|
||||
<TaskModificationContext.Provider
|
||||
value={{
|
||||
forest,
|
||||
setForest,
|
||||
forestPaths,
|
||||
setForestPaths,
|
||||
|
||||
whoIsAddingBranch,
|
||||
setWhoIsAddingBranch,
|
||||
updateWhoIsAddingBranch,
|
||||
|
||||
containerRef,
|
||||
setContainerRef,
|
||||
nodeRefMap,
|
||||
updateNodeRefMap,
|
||||
|
||||
baseNodeSet,
|
||||
setBaseNodeSet,
|
||||
baseLeafNodeId,
|
||||
setBaseLeafNodeId,
|
||||
|
||||
handleRequirementSubmit,
|
||||
handleNodeClick,
|
||||
handleNodeHover,
|
||||
|
||||
svgForceRenderCounter,
|
||||
setSVGForceRenderCounter,
|
||||
svgForceRender,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</TaskModificationContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
const _getFatherChildrenIdPairs = (
|
||||
node: IAgentActionTreeNode,
|
||||
): [string, string][] => {
|
||||
let pairs: [string, string][] = [];
|
||||
// 对于每个子节点,添加 (父ID, 子ID) 对,并递归调用函数
|
||||
node.children.forEach(child => {
|
||||
pairs.push([node.id, child.id]);
|
||||
pairs = pairs.concat(_getFatherChildrenIdPairs(child));
|
||||
});
|
||||
return pairs;
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
export const fakeTaskTree = {
|
||||
id: 'root', // 根节点
|
||||
children: [
|
||||
{
|
||||
id: 'child1', // 子节点
|
||||
children: [
|
||||
// 子节点的子节点数组
|
||||
{
|
||||
id: 'child1_1', // 子节点的子节点
|
||||
children: [], // 叶节点,没有子节点
|
||||
},
|
||||
{
|
||||
id: 'child1_2', // 另一个子节点的子节点
|
||||
children: [], // 叶节点,没有子节点
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'child2', // 另一个子节点
|
||||
children: [
|
||||
{
|
||||
id: 'child2_1',
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const fakeNodeData = new Map([
|
||||
[
|
||||
'child1',
|
||||
{
|
||||
agentIcon: 'John_Lin',
|
||||
style: { backgroundColor: '#b4d0d9', borderColor: '#b4d099' },
|
||||
isLeaf: true,
|
||||
requirement: 'hhhh',
|
||||
},
|
||||
],
|
||||
[
|
||||
'child2',
|
||||
{
|
||||
agentIcon: 'Mei_Lin',
|
||||
style: { backgroundColor: '#b4d0d9', borderColor: '#b4d099' },
|
||||
isLeaf: false,
|
||||
},
|
||||
],
|
||||
[
|
||||
'child1_1',
|
||||
{
|
||||
agentIcon: 'Tamara_Taylor',
|
||||
style: { backgroundColor: '#b4d0d9', borderColor: '#b4d099' },
|
||||
isLeaf: true,
|
||||
requirement: 'requirement',
|
||||
},
|
||||
],
|
||||
[
|
||||
'child1_2',
|
||||
{
|
||||
agentIcon: 'Sam_Moore',
|
||||
style: { backgroundColor: '#b4d0d9', borderColor: '#b4d099' },
|
||||
isLeaf: true,
|
||||
requirement: 'requirement',
|
||||
},
|
||||
],
|
||||
[
|
||||
'child2_1',
|
||||
{
|
||||
agentIcon: 'Sam_Moore',
|
||||
style: { backgroundColor: '#b4d0d9', borderColor: '#b4d099' },
|
||||
isLeaf: true,
|
||||
requirement: 'requirement',
|
||||
},
|
||||
],
|
||||
]);
|
||||
457
frontend-react/src/components/TaskModification/index.tsx
Normal file
457
frontend-react/src/components/TaskModification/index.tsx
Normal file
@@ -0,0 +1,457 @@
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import React from 'react';
|
||||
import { SxProps, CircularProgress } from '@mui/material';
|
||||
import Box from '@mui/material/Box';
|
||||
import AddIcon from '@mui/icons-material/Add';
|
||||
import RemoveIcon from '@mui/icons-material/Remove';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import Paper from '@mui/material/Paper';
|
||||
import InputBase from '@mui/material/InputBase';
|
||||
// import SendIcon from '@mui/icons-material/Send';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import AgentIcon from '../AgentIcon';
|
||||
import TaskModificationSvg from './TaskModificationSvg';
|
||||
import {
|
||||
TaskModificationProvider,
|
||||
useTaskModificationContext,
|
||||
} from './context';
|
||||
import { IAgentActionTreeNode, globalStorage } from '@/storage';
|
||||
import SendIcon from '@/icons/SendIcon';
|
||||
import { ActionType } from '@/storage/plan';
|
||||
|
||||
const RequirementNoteNode: React.FC<{
|
||||
data: {
|
||||
text: string;
|
||||
};
|
||||
style?: SxProps;
|
||||
}> = ({ data, style }) => {
|
||||
return (
|
||||
<Box sx={{ ...style }}>
|
||||
<Box sx={{ color: '#ACACAC', userSelect: 'none', minWidth: '250px' }}>
|
||||
{data.text}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const RequirementInputNode: React.FC<{
|
||||
style?: SxProps;
|
||||
}> = ({ style }) => {
|
||||
const { handleRequirementSubmit, updateNodeRefMap } =
|
||||
useTaskModificationContext();
|
||||
const [number, setNumber] = React.useState(1);
|
||||
const myRef = React.useRef<HTMLElement>(null);
|
||||
React.useEffect(() => {
|
||||
updateNodeRefMap('requirement', myRef);
|
||||
}, []);
|
||||
// const handleWheel = (event: any) => {
|
||||
// // 向上滚动时减少数字,向下滚动时增加数字
|
||||
// if (event.deltaY < 0) {
|
||||
// setNumber(prevNumber => prevNumber + 1);
|
||||
// } else {
|
||||
// setNumber(prevNumber => Math.max(1, prevNumber - 1));
|
||||
// }
|
||||
// };
|
||||
|
||||
const handleSubmit = () => {
|
||||
handleRequirementSubmit(textValue, number);
|
||||
};
|
||||
const [textValue, setTextValue] = React.useState('');
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
...style,
|
||||
}}
|
||||
ref={myRef}
|
||||
>
|
||||
<Paper
|
||||
sx={{
|
||||
p: '0px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
width: 250,
|
||||
backgroundColor: 'white',
|
||||
boxShadow: 'none',
|
||||
border: '2px solid #b0b0b0',
|
||||
borderRadius: '8px',
|
||||
}}
|
||||
>
|
||||
<InputBase
|
||||
sx={{ marginLeft: 1, flex: 1, backgroundColor: 'white' }}
|
||||
placeholder="Add Branch"
|
||||
onChange={e => {
|
||||
setTextValue(e.target.value);
|
||||
}}
|
||||
onKeyDown={e => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
setNumber(prevNumber => prevNumber + 1);
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
setNumber(prevNumber => Math.max(1, prevNumber - 1));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'flex-end',
|
||||
height: '100%',
|
||||
}}
|
||||
// onWheel={handleWheel}
|
||||
>
|
||||
<IconButton
|
||||
type="submit"
|
||||
sx={{
|
||||
color: 'primary',
|
||||
'&:hover': {
|
||||
color: 'primary.dark',
|
||||
},
|
||||
padding: '0px',
|
||||
}}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<SendIcon color="#b6b6b6" />
|
||||
</IconButton>
|
||||
<Box
|
||||
sx={{
|
||||
height: 'min-content',
|
||||
paddingLeft: '4px',
|
||||
paddingRight: '4px',
|
||||
cursor: 'pointer', // 提示用户可以与之互动
|
||||
}}
|
||||
>
|
||||
<Box component="span" sx={{ fontSize: '0.5em' }}>
|
||||
X
|
||||
</Box>
|
||||
<Box component="span" sx={{ fontSize: '1em' }}>
|
||||
{number}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
const RootNode: React.FC<{
|
||||
style?: SxProps;
|
||||
}> = ({ style }) => {
|
||||
const { updateNodeRefMap, updateWhoIsAddingBranch, whoIsAddingBranch } =
|
||||
useTaskModificationContext();
|
||||
const [onHover, setOnHover] = React.useState(false);
|
||||
const myRef = React.useRef<HTMLElement>(null);
|
||||
React.useEffect(() => {
|
||||
updateNodeRefMap('root', myRef);
|
||||
}, []);
|
||||
// React.useEffect(() => {
|
||||
// console.log(onHover, whoIsAddingBranch);
|
||||
// }, [onHover, whoIsAddingBranch]);
|
||||
return (
|
||||
<Box
|
||||
onMouseOver={() => setOnHover(true)}
|
||||
onMouseOut={() => setOnHover(false)}
|
||||
sx={{
|
||||
...style,
|
||||
flexDirection: 'column',
|
||||
position: 'relative',
|
||||
backgroundColor: 'red',
|
||||
}}
|
||||
ref={myRef}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translateX(-50%) ',
|
||||
color: 'primary',
|
||||
'&:hover': {
|
||||
color: 'primary.dark',
|
||||
},
|
||||
opacity: onHover || whoIsAddingBranch === 'root' ? '1' : '0',
|
||||
// visibility: 'visible',
|
||||
// visibility:
|
||||
// onHover || whoIsAddingBranch === 'root' ? 'visible' : 'hidden',
|
||||
padding: '0px',
|
||||
borderRadius: '50%',
|
||||
border: '1px dotted #333',
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
marginTop: '-6px',
|
||||
'& .MuiSvgIcon-root': {
|
||||
fontSize: '14px',
|
||||
},
|
||||
}}
|
||||
onClick={() => updateWhoIsAddingBranch('root')}
|
||||
>
|
||||
{whoIsAddingBranch !== 'root' ? <AddIcon /> : <RemoveIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
const Node: React.FC<{
|
||||
data: {
|
||||
id: string;
|
||||
agentIcon: string;
|
||||
agent: string;
|
||||
action: { type: ActionType; description: string; style: SxProps };
|
||||
focusing: boolean;
|
||||
};
|
||||
style?: SxProps;
|
||||
}> = ({ data, style }) => {
|
||||
const {
|
||||
updateNodeRefMap,
|
||||
whoIsAddingBranch,
|
||||
updateWhoIsAddingBranch,
|
||||
handleNodeClick,
|
||||
handleNodeHover,
|
||||
baseNodeSet,
|
||||
} = useTaskModificationContext();
|
||||
const myRef = React.useRef<HTMLElement>(null);
|
||||
const [onHover, setOnHover] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
updateNodeRefMap(data.id, myRef);
|
||||
}, []);
|
||||
return (
|
||||
// <RectWatcher onRectChange={onRectChange}>
|
||||
<Box
|
||||
onMouseOver={() => {
|
||||
setOnHover(true);
|
||||
handleNodeHover(data.id);
|
||||
}}
|
||||
onMouseOut={() => {
|
||||
setOnHover(false);
|
||||
handleNodeHover(undefined);
|
||||
}}
|
||||
sx={{
|
||||
...style,
|
||||
flexDirection: 'column',
|
||||
// backgroundColor: '#d9d9d9',
|
||||
borderStyle: 'solid',
|
||||
borderRadius: '50%',
|
||||
height: data.focusing ? '45px' : '30px',
|
||||
width: data.focusing ? '45px' : '30px',
|
||||
position: 'relative',
|
||||
borderWidth: data.focusing ? '3px' : '2px',
|
||||
boxShadow: baseNodeSet.has(data.id) ? '0 0 10px 5px #43b2aa' : '',
|
||||
}}
|
||||
ref={myRef}
|
||||
onClick={() => handleNodeClick(data.id)}
|
||||
>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<AgentIcon
|
||||
name={data.agentIcon}
|
||||
style={{
|
||||
width: data.focusing ? '36px' : '28px',
|
||||
height: 'auto',
|
||||
margin: '0px',
|
||||
userSelect: 'none',
|
||||
}}
|
||||
tooltipInfo={{
|
||||
...globalStorage.agentMap.get(data.agent)!,
|
||||
action: data.action,
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
left: '50%',
|
||||
top: '100%',
|
||||
transform: 'translateX(-50%) translateY(-50%)',
|
||||
}}
|
||||
>
|
||||
<IconButton
|
||||
sx={{
|
||||
color: 'primary',
|
||||
'&:hover': {
|
||||
color: 'primary.dark',
|
||||
},
|
||||
visibility:
|
||||
onHover || whoIsAddingBranch === data.id ? 'visible' : 'hidden',
|
||||
padding: '0px',
|
||||
borderRadius: '50%',
|
||||
border: '1px dotted #333',
|
||||
height: '16px',
|
||||
width: '16px',
|
||||
marginTop: '-6px',
|
||||
'& .MuiSvgIcon-root': {
|
||||
fontSize: '14px',
|
||||
},
|
||||
}}
|
||||
onClick={() => {
|
||||
updateWhoIsAddingBranch(data.id);
|
||||
}}
|
||||
>
|
||||
{whoIsAddingBranch !== data.id ? <AddIcon /> : <RemoveIcon />}
|
||||
</IconButton>
|
||||
</Box>
|
||||
</Box>
|
||||
// </RectWatcher>
|
||||
);
|
||||
};
|
||||
|
||||
const Tree: React.FC<{
|
||||
tree: IAgentActionTreeNode;
|
||||
}> = ({ tree }) => {
|
||||
const { whoIsAddingBranch } = useTaskModificationContext();
|
||||
|
||||
const generalNodeStyle = {
|
||||
height: '30px',
|
||||
// padding: '4px 8px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
margin: '8px 20px 8px 8px',
|
||||
};
|
||||
const focusNodeStyle = {
|
||||
height: '45px',
|
||||
// padding: '4px 8px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
margin: '8px 20px 8px 8px',
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row' }}>
|
||||
<>
|
||||
{tree.id !== 'root' && (
|
||||
<Node
|
||||
data={{
|
||||
id: tree.id,
|
||||
agentIcon: tree.icon,
|
||||
agent: tree.agent,
|
||||
action: { ...tree.action, style: tree.style },
|
||||
focusing: tree.focusing,
|
||||
}}
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
alignSelf: 'flex-start',
|
||||
cursor: 'pointer',
|
||||
aspectRatio: '1 / 1',
|
||||
...(tree.focusing ? focusNodeStyle : generalNodeStyle),
|
||||
...tree.style,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{tree.id === 'root' && (
|
||||
<RootNode
|
||||
style={{
|
||||
justifyContent: 'center',
|
||||
...(tree.children[0] && tree.children[0].focusing
|
||||
? focusNodeStyle
|
||||
: generalNodeStyle),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
<>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
{tree.id !== 'root' && tree.leaf && (
|
||||
<RequirementNoteNode
|
||||
data={{ text: tree.requirement || '' }}
|
||||
style={{
|
||||
...(tree.focusing ? focusNodeStyle : generalNodeStyle),
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{tree.children.map(childTree => (
|
||||
<Tree key={`taskTree-${childTree.id}`} tree={childTree} />
|
||||
))}
|
||||
{tree.id === whoIsAddingBranch && (
|
||||
<RequirementInputNode
|
||||
style={{ height: '50px', display: 'flex', alignItems: 'center' }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
interface ITaskModificationProps {
|
||||
style?: SxProps;
|
||||
resizeSignal?: number;
|
||||
}
|
||||
|
||||
const TheViewContent = observer(
|
||||
({ style, resizeSignal }: ITaskModificationProps) => {
|
||||
const { renderingActionForest } = globalStorage;
|
||||
|
||||
const { forest, setForest, setContainerRef, svgForceRender } =
|
||||
useTaskModificationContext();
|
||||
|
||||
const myRef = React.useRef<HTMLElement>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
setForest({
|
||||
agent: '',
|
||||
icon: '',
|
||||
children: renderingActionForest,
|
||||
id: 'root',
|
||||
leaf: false,
|
||||
style: {},
|
||||
action: { type: ActionType.Propose, description: '' },
|
||||
focusing: true,
|
||||
});
|
||||
}, [renderingActionForest]);
|
||||
|
||||
React.useEffect(() => {
|
||||
setContainerRef(myRef);
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
svgForceRender();
|
||||
}, [resizeSignal]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
backgroundColor: 'white',
|
||||
position: 'relative',
|
||||
overflowY: 'auto',
|
||||
overflowX: 'auto',
|
||||
padding: '4px 6px',
|
||||
userSelect: 'none',
|
||||
...style,
|
||||
}}
|
||||
ref={myRef}
|
||||
>
|
||||
{myRef.current && <TaskModificationSvg />}
|
||||
{forest && <Tree tree={forest} />}
|
||||
</Box>
|
||||
);
|
||||
},
|
||||
);
|
||||
/* eslint-enable max-lines */
|
||||
|
||||
const TaskModification: React.FC<ITaskModificationProps> = observer(
|
||||
({ style, resizeSignal }) => {
|
||||
return (
|
||||
<TaskModificationProvider>
|
||||
<TheViewContent style={style} resizeSignal={resizeSignal} />
|
||||
{globalStorage.api.agentActionTreeGenerating && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: '10px',
|
||||
right: '20px',
|
||||
zIndex: 999,
|
||||
}}
|
||||
>
|
||||
<CircularProgress size={40} />
|
||||
</Box>
|
||||
)}
|
||||
</TaskModificationProvider>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default TaskModification;
|
||||
Reference in New Issue
Block a user