|
|
|
@ -1,13 +1,17 @@
|
|
|
|
|
import React, { useCallback, useMemo } from 'react';
|
|
|
|
|
import React, { useCallback, useMemo, useRef } from 'react';
|
|
|
|
|
import {
|
|
|
|
|
GroupPanelType,
|
|
|
|
|
useGroupInfo,
|
|
|
|
|
GroupPanel as GroupPanelInfo,
|
|
|
|
|
showToasts,
|
|
|
|
|
} from 'tailchat-shared';
|
|
|
|
|
import { Tree } from 'antd';
|
|
|
|
|
import type { NodeDragEventParams } from 'rc-tree/lib/contextTypes';
|
|
|
|
|
import type { DataNode, EventDataNode } from 'antd/lib/tree';
|
|
|
|
|
import type { Key } from 'rc-tree/lib/interface';
|
|
|
|
|
import type { AllowDrop } from 'rc-tree/lib/Tree';
|
|
|
|
|
import _cloneDeep from 'lodash/cloneDeep';
|
|
|
|
|
import _isNil from 'lodash/isNil';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 将一维的面板列表构筑成立体的数组
|
|
|
|
@ -31,12 +35,63 @@ function buildTreeDataWithGroupPanel(
|
|
|
|
|
}));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const GroupPanel: React.FC<{
|
|
|
|
|
groupId: string;
|
|
|
|
|
}> = React.memo((props) => {
|
|
|
|
|
const groupId = props.groupId;
|
|
|
|
|
const groupInfo = useGroupInfo(groupId);
|
|
|
|
|
const groupPanels = groupInfo?.panels ?? [];
|
|
|
|
|
/**
|
|
|
|
|
* 重新构建面板顺序
|
|
|
|
|
*/
|
|
|
|
|
function rebuildGroupPanelOrder(
|
|
|
|
|
groupPanels: GroupPanelInfo[]
|
|
|
|
|
): GroupPanelInfo[] {
|
|
|
|
|
const originGroupPanels = _cloneDeep(groupPanels);
|
|
|
|
|
const newPanel = originGroupPanels.filter((panel) => _isNil(panel.parentId)); // 第一层
|
|
|
|
|
const len = newPanel.length;
|
|
|
|
|
for (let i = len - 1; i >= 0; i--) {
|
|
|
|
|
const currentId = newPanel[i].id;
|
|
|
|
|
newPanel.splice(
|
|
|
|
|
i + 1,
|
|
|
|
|
0,
|
|
|
|
|
...originGroupPanels.filter((p) => p.parentId === currentId)
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return newPanel;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface GroupPanelTree {
|
|
|
|
|
groupPanels: GroupPanelInfo[];
|
|
|
|
|
onChange: (newGroupPanels: GroupPanelInfo[]) => void;
|
|
|
|
|
}
|
|
|
|
|
/**
|
|
|
|
|
* 仅同层级相互拉的树形结构
|
|
|
|
|
*/
|
|
|
|
|
const GroupPanelTree: React.FC<GroupPanelTree> = React.memo((props) => {
|
|
|
|
|
const draggingNode = useRef<DataNode | null>(null);
|
|
|
|
|
const treeData: DataNode[] = useMemo(
|
|
|
|
|
() => buildTreeDataWithGroupPanel(props.groupPanels),
|
|
|
|
|
[props.groupPanels]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleDragStart = useCallback(
|
|
|
|
|
(info: NodeDragEventParams<HTMLDivElement>) => {
|
|
|
|
|
draggingNode.current = info.node;
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleDragEnd = useCallback(() => {
|
|
|
|
|
draggingNode.current = null;
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
const handleAllowDrop = useCallback(
|
|
|
|
|
({ dropNode, dropPosition }: Parameters<AllowDrop>[0]) => {
|
|
|
|
|
if (dropPosition === 0 && draggingNode.current?.isLeaf === false) {
|
|
|
|
|
// 不允许容器之间产生父子节点
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dropNode.isLeaf === draggingNode.current?.isLeaf;
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const handleDrop = useCallback(
|
|
|
|
|
(
|
|
|
|
@ -47,25 +102,84 @@ export const GroupPanel: React.FC<{
|
|
|
|
|
dropToGap: boolean;
|
|
|
|
|
}
|
|
|
|
|
) => {
|
|
|
|
|
// TODO
|
|
|
|
|
console.log(info);
|
|
|
|
|
const newGroupPanels = _cloneDeep(props.groupPanels);
|
|
|
|
|
const dropNodePos = newGroupPanels.findIndex(
|
|
|
|
|
(panel) => panel.id === info.node.key
|
|
|
|
|
);
|
|
|
|
|
const dropGroupPanel = newGroupPanels[dropNodePos];
|
|
|
|
|
if (dropNodePos === -1) {
|
|
|
|
|
showToasts('异常, 目标节点未找到', 'error');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const dragPanelPos = newGroupPanels.findIndex(
|
|
|
|
|
(panel) => panel.id === info.dragNode.key
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (draggingNode.current?.isLeaf === true && info.node.isLeaf === true) {
|
|
|
|
|
// 如果是面板且目标也是面板
|
|
|
|
|
// 则更新它的父节点id
|
|
|
|
|
info.dragNodesKeys
|
|
|
|
|
// 获取所有的移动节点的位置
|
|
|
|
|
.map((key) => newGroupPanels.findIndex((panel) => panel.id === key))
|
|
|
|
|
// 过滤掉没找到的
|
|
|
|
|
.filter((index) => index !== -1)
|
|
|
|
|
.forEach((pos) => {
|
|
|
|
|
newGroupPanels[pos].parentId = dropGroupPanel.parentId;
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (info.node.dragOverGapTop === true) {
|
|
|
|
|
// 移动到目标节点之前
|
|
|
|
|
newGroupPanels.splice(
|
|
|
|
|
dropNodePos,
|
|
|
|
|
0,
|
|
|
|
|
newGroupPanels.splice(dragPanelPos, 1)[0]
|
|
|
|
|
);
|
|
|
|
|
} else if (info.node.dragOverGapBottom === true) {
|
|
|
|
|
// 移动到目标节点之后
|
|
|
|
|
newGroupPanels.splice(
|
|
|
|
|
dragPanelPos,
|
|
|
|
|
0,
|
|
|
|
|
newGroupPanels.splice(dropNodePos, 1)[0]
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (typeof props.onChange === 'function') {
|
|
|
|
|
props.onChange(rebuildGroupPanelOrder(newGroupPanels));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
[]
|
|
|
|
|
[props.groupPanels, props.onChange]
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const treeData: DataNode[] = useMemo(
|
|
|
|
|
() => buildTreeDataWithGroupPanel(groupPanels),
|
|
|
|
|
[groupPanels]
|
|
|
|
|
return (
|
|
|
|
|
<Tree
|
|
|
|
|
treeData={treeData}
|
|
|
|
|
defaultExpandAll={true}
|
|
|
|
|
draggable={true}
|
|
|
|
|
onDrop={handleDrop}
|
|
|
|
|
// TODO: 待简化 https://github.com/react-component/tree/pull/482
|
|
|
|
|
onDragStart={handleDragStart}
|
|
|
|
|
onDragEnd={handleDragEnd}
|
|
|
|
|
allowDrop={handleAllowDrop}
|
|
|
|
|
/>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
GroupPanelTree.displayName = 'GroupPanelTree';
|
|
|
|
|
|
|
|
|
|
export const GroupPanel: React.FC<{
|
|
|
|
|
groupId: string;
|
|
|
|
|
}> = React.memo((props) => {
|
|
|
|
|
const groupId = props.groupId;
|
|
|
|
|
const groupInfo = useGroupInfo(groupId);
|
|
|
|
|
const groupPanels = groupInfo?.panels ?? [];
|
|
|
|
|
|
|
|
|
|
const handleChange = useCallback((newGroupPanels: GroupPanelInfo[]) => {
|
|
|
|
|
console.log('newGroupPanels', newGroupPanels);
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div>
|
|
|
|
|
<Tree
|
|
|
|
|
treeData={treeData}
|
|
|
|
|
defaultExpandAll={true}
|
|
|
|
|
draggable={true}
|
|
|
|
|
onDrop={handleDrop}
|
|
|
|
|
/>
|
|
|
|
|
<GroupPanelTree groupPanels={groupPanels} onChange={handleChange} />
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|