import React from 'react'; import { useKeyPress } from 'react-use'; import { ComponentTypeEnum, isMac } from '@easytwin/core'; import TreeNode, { TreeNodeDataSource } from './TreeNode'; export interface DraggableTreeRenderNodeState { isSelected: boolean; isExpanded: boolean; } export interface DraggableTreeDropEventInfo { source: TreeNodeDataSource; sourceParent?: TreeNodeDataSource; target: TreeNodeDataSource; targetParent?: TreeNodeDataSource; targetNext?: TreeNodeDataSource; } export interface DraggableTreeProp { dataSource: TreeNodeDataSource[]; editingKey?: string; keyPropName?: string; pidPropName?: string; titlePropName?: string; isGroupPropName?: string; expandPropName?: string; selectedKeys?: string[]; dragType: string; allowVisible?: (item: TreeNodeDataSource) => boolean; iconRender?: (item: any, state: DraggableTreeRenderNodeState) => React.ReactNode; extendRender?: (item: any, state: DraggableTreeRenderNodeState) => React.ReactNode; onSelect?: (keys: string[], infos: any[]) => void; onDragStart?: (e: React.DragEvent, item: TreeNodeDataSource) => void; onDragEnd?: (e: React.DragEvent, item: TreeNodeDataSource) => void; onDrop?: (info: DraggableTreeDropEventInfo) => void; onRightClick?: (e: React.MouseEvent, item: TreeNodeDataSource) => void; onExpandChange?: (e: React.MouseEvent, item: TreeNodeDataSource) => void; onNameEditChange?: (id: string, name: string) => void; } const DraggableTree: React.FC = function ({ dataSource, editingKey, keyPropName = 'id', pidPropName = 'pid', titlePropName = 'name', isGroupPropName = 'isGroup', expandPropName = 'collapsed', selectedKeys, dragType, allowVisible, iconRender, extendRender, onSelect, onDragStart, onDragEnd, onDrop, onRightClick, onExpandChange, onNameEditChange, }) { const [isCtrlPressed] = useKeyPress((event) => event.key === (isMac ? 'Meta' : 'Control')); const [isShiftPressed] = useKeyPress('Shift'); const [innerSelectedKeys, setInnerSelectedKeys] = React.useState([]); React.useEffect(() => { setInnerSelectedKeys(selectedKeys || []); }, [selectedKeys]); // TODO: 需要添加 “如果 组/母版 被选中了,那么 子要素 就无法被选中了” 逻辑 const handleItemClick = React.useCallback( (key: string) => { const newSelectedKeys: string[] = []; if (isCtrlPressed) { const index = innerSelectedKeys.indexOf(key); if (index === -1) newSelectedKeys.push(...innerSelectedKeys, key); else newSelectedKeys.push(...innerSelectedKeys.filter((k) => key !== k)); } else if (isShiftPressed) { if (innerSelectedKeys.length === 0) newSelectedKeys.push(key); else { const [startSelectedKey] = innerSelectedKeys; const endSelectedKey = innerSelectedKeys[innerSelectedKeys.length - 1]; const startIndex = dataSource.findIndex((k) => k[keyPropName] === startSelectedKey); const endIndex = dataSource.findIndex((k) => k[keyPropName] === endSelectedKey); const currentIndex = dataSource.findIndex((k) => k[keyPropName] === key); const pushList: any[] = []; if (startIndex > currentIndex) { pushList.push(...dataSource.slice(currentIndex, endIndex + 1)); } else { pushList.push(...dataSource.slice(startIndex, currentIndex + 1)); } newSelectedKeys.push(...pushList.map((k) => k[keyPropName])); } } else { newSelectedKeys.push(key); } setInnerSelectedKeys(newSelectedKeys); onSelect?.( newSelectedKeys, newSelectedKeys.map((key) => dataSource.find((i) => i[keyPropName] === key)), ); }, [dataSource, innerSelectedKeys, isCtrlPressed, isShiftPressed, keyPropName, onSelect], ); const handleDrop = React.useCallback( (sourceKey: string, targetKey: string) => { const source = dataSource.find((i) => i[keyPropName] === sourceKey); const target = dataSource.find((i) => i[keyPropName] === targetKey); if (!source || !target) return; const targetRefList = dataSource.filter((i) => i[pidPropName] === target[pidPropName]); const targetIndex = targetRefList.findIndex((i) => i[keyPropName] === target[keyPropName]); // 判断是否是子要素 const isChild = (data: TreeNodeDataSource, list: TreeNodeDataSource[]) => { const pid = data[pidPropName]; const parent = list.find((item) => item.id === pid); return !!(pid && parent.type !== ComponentTypeEnum.BIZGROUP); }; // 判断是否为母版 const isTemplate = (data: TreeNodeDataSource) => !!(data[isGroupPropName] && data.type !== ComponentTypeEnum.BIZGROUP); // const isGroup = (data: TreeNodeDataSource) => // !!(data[isGroupPropName] && data.type === ComponentTypeEnum.BIZGROUP); const sourceIsChild = isChild(source, dataSource); const targetIsChild = isChild(target, dataSource); // const sourceIsTemplate = isTemplate(source); const targetIsTemplate = isTemplate(target); // const sourceIsGroup = isGroup(source); // const targetIsGroup = isGroup(target); if ( // 子要素只允许在同一母版内移动 ((sourceIsChild || targetIsChild) && source[pidPropName] !== target[pidPropName]) || // 如果 target 是template (targetIsTemplate && !sourceIsChild) ) return; onDrop?.({ source, sourceParent: dataSource.find((i) => i[keyPropName] === source[pidPropName]), target, targetParent: dataSource.find((i) => i[keyPropName] === target[pidPropName]), targetNext: targetRefList[targetIndex + 1], }); }, [dataSource, keyPropName, onDrop, pidPropName], ); return (
{dataSource.map((item) => { const key = item[keyPropName]; const isSelected = innerSelectedKeys.includes(key); const isExpanded = item[expandPropName]; const state: DraggableTreeRenderNodeState = { isSelected, isExpanded, }; return ( { e.stopPropagation(); handleItemClick(key); }} onRightClick={(e) => onRightClick?.(e, item)} onExpandClick={(e) => { e.stopPropagation(); onExpandChange?.(e, item); }} onDrop={handleDrop} onDragStart={(e) => onDragStart?.(e, item)} onDragEnd={(e) => onDragEnd?.(e, item)} onNameEditChange={(name) => onNameEditChange?.(key, name)} /> ); })}
); }; export default DraggableTree;