|
|
@ -1,6 +1,5 @@ |
|
|
|
import * as React from 'react'; |
|
|
|
import classNames from 'classnames'; |
|
|
|
import omit from 'omit.js'; |
|
|
|
import debounce from 'lodash/debounce'; |
|
|
|
import { conductExpandParent } from 'rc-tree/lib/util'; |
|
|
|
import { EventDataNode, DataNode, Key } from 'rc-tree/lib/interface'; |
|
|
@ -8,7 +7,7 @@ import { convertDataToEntities, convertTreeToData } from 'rc-tree/lib/utils/tree |
|
|
|
import FileOutlined from '@ant-design/icons/FileOutlined'; |
|
|
|
import FolderOpenOutlined from '@ant-design/icons/FolderOpenOutlined'; |
|
|
|
import FolderOutlined from '@ant-design/icons/FolderOutlined'; |
|
|
|
import { ConfigConsumer, ConfigConsumerProps } from '../config-provider'; |
|
|
|
import { ConfigContext } from '../config-provider'; |
|
|
|
|
|
|
|
import Tree, { TreeProps, AntdTreeNodeAttribute } from './Tree'; |
|
|
|
import { calcRangeKeys, convertDirectoryKeysToNodes } from './utils/dictUtil'; |
|
|
@ -36,109 +35,116 @@ function getTreeData({ treeData, children }: DirectoryTreeProps) { |
|
|
|
return treeData || convertTreeToData(children); |
|
|
|
} |
|
|
|
|
|
|
|
class DirectoryTree extends React.Component<DirectoryTreeProps, DirectoryTreeState> { |
|
|
|
static defaultProps = { |
|
|
|
showIcon: true, |
|
|
|
expandAction: 'click' as DirectoryTreeProps['expandAction'], |
|
|
|
}; |
|
|
|
|
|
|
|
static getDerivedStateFromProps(nextProps: DirectoryTreeProps) { |
|
|
|
const newState: DirectoryTreeState = {}; |
|
|
|
if ('expandedKeys' in nextProps) { |
|
|
|
newState.expandedKeys = nextProps.expandedKeys; |
|
|
|
} |
|
|
|
if ('selectedKeys' in nextProps) { |
|
|
|
newState.selectedKeys = nextProps.selectedKeys; |
|
|
|
} |
|
|
|
return newState; |
|
|
|
} |
|
|
|
|
|
|
|
state: DirectoryTreeState; |
|
|
|
|
|
|
|
tree: Tree; |
|
|
|
|
|
|
|
onDebounceExpand: (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => void; |
|
|
|
|
|
|
|
const DirectoryTree: React.FC<DirectoryTreeProps> = ({ |
|
|
|
defaultExpandAll, |
|
|
|
defaultExpandParent, |
|
|
|
defaultExpandedKeys, |
|
|
|
...props |
|
|
|
}) => { |
|
|
|
// Shift click usage
|
|
|
|
lastSelectedKey?: Key; |
|
|
|
const lastSelectedKey = React.useRef<Key>(); |
|
|
|
|
|
|
|
cachedSelectedKeys?: Key[]; |
|
|
|
const cachedSelectedKeys = React.useRef<Key[]>(); |
|
|
|
|
|
|
|
constructor(props: DirectoryTreeProps) { |
|
|
|
super(props); |
|
|
|
const ref = React.createRef<any>(); |
|
|
|
|
|
|
|
const { defaultExpandAll, defaultExpandParent, expandedKeys, defaultExpandedKeys } = props; |
|
|
|
const getInitExpandedKeys = () => { |
|
|
|
const { keyEntities } = convertDataToEntities(getTreeData(props)); |
|
|
|
|
|
|
|
// Selected keys
|
|
|
|
this.state = { |
|
|
|
selectedKeys: props.selectedKeys || props.defaultSelectedKeys || [], |
|
|
|
}; |
|
|
|
let initExpandedKeys: any; |
|
|
|
|
|
|
|
// Expanded keys
|
|
|
|
if (defaultExpandAll) { |
|
|
|
this.state.expandedKeys = Object.keys(keyEntities); |
|
|
|
initExpandedKeys = Object.keys(keyEntities); |
|
|
|
} else if (defaultExpandParent) { |
|
|
|
this.state.expandedKeys = conductExpandParent( |
|
|
|
expandedKeys || defaultExpandedKeys, |
|
|
|
initExpandedKeys = conductExpandParent( |
|
|
|
props.expandedKeys || defaultExpandedKeys, |
|
|
|
keyEntities, |
|
|
|
); |
|
|
|
} else { |
|
|
|
this.state.expandedKeys = expandedKeys || defaultExpandedKeys; |
|
|
|
initExpandedKeys = props.expandedKeys || defaultExpandedKeys; |
|
|
|
} |
|
|
|
return initExpandedKeys; |
|
|
|
}; |
|
|
|
|
|
|
|
this.onDebounceExpand = debounce(this.expandFolderNode, 200, { |
|
|
|
leading: true, |
|
|
|
}); |
|
|
|
} |
|
|
|
const [selectedKeys, setSelectedKeys] = React.useState( |
|
|
|
props.selectedKeys || props.defaultSelectedKeys || [], |
|
|
|
); |
|
|
|
const [expandedKeys, setExpandedKeys] = React.useState(getInitExpandedKeys()); |
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
|
if ('selectedKeys' in props) { |
|
|
|
setSelectedKeys(props.selectedKeys!); |
|
|
|
} |
|
|
|
}, [props.selectedKeys]); |
|
|
|
|
|
|
|
React.useEffect(() => { |
|
|
|
if ('expandedKeys' in props) { |
|
|
|
setExpandedKeys(props.expandedKeys); |
|
|
|
} |
|
|
|
}, [props.expandedKeys]); |
|
|
|
|
|
|
|
const expandFolderNode = (event: React.MouseEvent<HTMLElement>, node: any) => { |
|
|
|
const { isLeaf } = node; |
|
|
|
|
|
|
|
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// Call internal rc-tree expand function
|
|
|
|
// https://github.com/ant-design/ant-design/issues/12567
|
|
|
|
ref.current.onNodeExpand(event, node); |
|
|
|
}; |
|
|
|
|
|
|
|
onExpand = ( |
|
|
|
expandedKeys: Key[], |
|
|
|
const onDebounceExpand = debounce(expandFolderNode, 200, { |
|
|
|
leading: true, |
|
|
|
}); |
|
|
|
const onExpand = ( |
|
|
|
keys: Key[], |
|
|
|
info: { |
|
|
|
node: EventDataNode; |
|
|
|
expanded: boolean; |
|
|
|
nativeEvent: MouseEvent; |
|
|
|
}, |
|
|
|
) => { |
|
|
|
const { onExpand } = this.props; |
|
|
|
|
|
|
|
this.setUncontrolledState({ expandedKeys }); |
|
|
|
|
|
|
|
if (!('expandedKeys' in props)) { |
|
|
|
setExpandedKeys(keys); |
|
|
|
} |
|
|
|
// Call origin function
|
|
|
|
if (onExpand) { |
|
|
|
return onExpand(expandedKeys, info); |
|
|
|
if (props.onExpand) { |
|
|
|
return props.onExpand(keys, info); |
|
|
|
} |
|
|
|
|
|
|
|
return undefined; |
|
|
|
}; |
|
|
|
|
|
|
|
onClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => { |
|
|
|
const { onClick, expandAction } = this.props; |
|
|
|
const onClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => { |
|
|
|
const { expandAction } = props; |
|
|
|
|
|
|
|
// Expand the tree
|
|
|
|
if (expandAction === 'click') { |
|
|
|
this.onDebounceExpand(event, node); |
|
|
|
onDebounceExpand(event, node); |
|
|
|
} |
|
|
|
|
|
|
|
if (onClick) { |
|
|
|
onClick(event, node); |
|
|
|
if (props.onClick) { |
|
|
|
props.onClick(event, node); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
onDoubleClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => { |
|
|
|
const { onDoubleClick, expandAction } = this.props; |
|
|
|
const onDoubleClick = (event: React.MouseEvent<HTMLElement>, node: EventDataNode) => { |
|
|
|
const { expandAction } = props; |
|
|
|
|
|
|
|
// Expand the tree
|
|
|
|
if (expandAction === 'doubleClick') { |
|
|
|
this.onDebounceExpand(event, node); |
|
|
|
onDebounceExpand(event, node); |
|
|
|
} |
|
|
|
|
|
|
|
if (onDoubleClick) { |
|
|
|
onDoubleClick(event, node); |
|
|
|
if (props.onDoubleClick) { |
|
|
|
props.onDoubleClick(event, node); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
onSelect = ( |
|
|
|
const onSelect = ( |
|
|
|
keys: Key[], |
|
|
|
event: { |
|
|
|
event: 'select'; |
|
|
@ -148,13 +154,12 @@ class DirectoryTree extends React.Component<DirectoryTreeProps, DirectoryTreeSta |
|
|
|
nativeEvent: MouseEvent; |
|
|
|
}, |
|
|
|
) => { |
|
|
|
const { onSelect, multiple } = this.props; |
|
|
|
const { expandedKeys = [] } = this.state; |
|
|
|
const { multiple } = props; |
|
|
|
const { node, nativeEvent } = event; |
|
|
|
const { key = '' } = node; |
|
|
|
|
|
|
|
const treeData = getTreeData(this.props); |
|
|
|
const newState: DirectoryTreeState = {}; |
|
|
|
const treeData = getTreeData(props); |
|
|
|
// const newState: DirectoryTreeState = {};
|
|
|
|
|
|
|
|
// We need wrap this event since some value is not same
|
|
|
|
const newEvent: any = { |
|
|
@ -171,90 +176,63 @@ class DirectoryTree extends React.Component<DirectoryTreeProps, DirectoryTreeSta |
|
|
|
if (multiple && ctrlPick) { |
|
|
|
// Control click
|
|
|
|
newSelectedKeys = keys; |
|
|
|
this.lastSelectedKey = key; |
|
|
|
this.cachedSelectedKeys = newSelectedKeys; |
|
|
|
lastSelectedKey.current = key; |
|
|
|
cachedSelectedKeys.current = newSelectedKeys; |
|
|
|
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys); |
|
|
|
} else if (multiple && shiftPick) { |
|
|
|
// Shift click
|
|
|
|
newSelectedKeys = Array.from( |
|
|
|
new Set([ |
|
|
|
...(this.cachedSelectedKeys || []), |
|
|
|
...calcRangeKeys(treeData, expandedKeys, key, this.lastSelectedKey), |
|
|
|
...(cachedSelectedKeys.current || []), |
|
|
|
...calcRangeKeys(treeData, expandedKeys, key, lastSelectedKey.current), |
|
|
|
]), |
|
|
|
); |
|
|
|
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys); |
|
|
|
} else { |
|
|
|
// Single click
|
|
|
|
newSelectedKeys = [key]; |
|
|
|
this.lastSelectedKey = key; |
|
|
|
this.cachedSelectedKeys = newSelectedKeys; |
|
|
|
lastSelectedKey.current = key; |
|
|
|
cachedSelectedKeys.current = newSelectedKeys; |
|
|
|
newEvent.selectedNodes = convertDirectoryKeysToNodes(treeData, newSelectedKeys); |
|
|
|
} |
|
|
|
newState.selectedKeys = newSelectedKeys; |
|
|
|
|
|
|
|
if (onSelect) { |
|
|
|
onSelect(newSelectedKeys, newEvent); |
|
|
|
if (props.onSelect) { |
|
|
|
props.onSelect(newSelectedKeys, newEvent); |
|
|
|
} |
|
|
|
|
|
|
|
this.setUncontrolledState(newState); |
|
|
|
}; |
|
|
|
|
|
|
|
setTreeRef = (node: Tree) => { |
|
|
|
this.tree = node; |
|
|
|
}; |
|
|
|
|
|
|
|
expandFolderNode = (event: React.MouseEvent<HTMLElement>, node: any) => { |
|
|
|
const { isLeaf } = node; |
|
|
|
|
|
|
|
if (isLeaf || event.shiftKey || event.metaKey || event.ctrlKey) { |
|
|
|
return; |
|
|
|
if (!('selectedKeys' in props)) { |
|
|
|
setSelectedKeys(newSelectedKeys); |
|
|
|
} |
|
|
|
|
|
|
|
// Get internal rc-tree
|
|
|
|
const internalTree = this.tree.tree; |
|
|
|
|
|
|
|
// Call internal rc-tree expand function
|
|
|
|
// https://github.com/ant-design/ant-design/issues/12567
|
|
|
|
internalTree.onNodeExpand(event, node); |
|
|
|
}; |
|
|
|
|
|
|
|
setUncontrolledState = (state: DirectoryTreeState) => { |
|
|
|
const newState = omit(state, Object.keys(this.props)); |
|
|
|
if (Object.keys(newState).length) { |
|
|
|
this.setState(newState); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
renderDirectoryTree = ({ getPrefixCls, direction }: ConfigConsumerProps) => { |
|
|
|
const { prefixCls: customizePrefixCls, className, ...props } = this.props; |
|
|
|
const { expandedKeys, selectedKeys } = this.state; |
|
|
|
|
|
|
|
const prefixCls = getPrefixCls('tree', customizePrefixCls); |
|
|
|
const connectClassName = classNames(`${prefixCls}-directory`, className, { |
|
|
|
[`${prefixCls}-directory-rtl`]: direction === 'rtl', |
|
|
|
}); |
|
|
|
|
|
|
|
return ( |
|
|
|
<Tree |
|
|
|
icon={getIcon} |
|
|
|
ref={this.setTreeRef} |
|
|
|
blockNode |
|
|
|
{...props} |
|
|
|
prefixCls={prefixCls} |
|
|
|
className={connectClassName} |
|
|
|
expandedKeys={expandedKeys} |
|
|
|
selectedKeys={selectedKeys} |
|
|
|
onSelect={this.onSelect} |
|
|
|
onClick={this.onClick} |
|
|
|
onDoubleClick={this.onDoubleClick} |
|
|
|
onExpand={this.onExpand} |
|
|
|
/> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
render() { |
|
|
|
return <ConfigConsumer>{this.renderDirectoryTree}</ConfigConsumer>; |
|
|
|
} |
|
|
|
} |
|
|
|
const { getPrefixCls, direction } = React.useContext(ConfigContext); |
|
|
|
|
|
|
|
const { prefixCls: customizePrefixCls, className, ...otherProps } = props; |
|
|
|
|
|
|
|
const prefixCls = getPrefixCls('tree', customizePrefixCls); |
|
|
|
const connectClassName = classNames(`${prefixCls}-directory`, className, { |
|
|
|
[`${prefixCls}-directory-rtl`]: direction === 'rtl', |
|
|
|
}); |
|
|
|
|
|
|
|
return ( |
|
|
|
<Tree |
|
|
|
icon={getIcon} |
|
|
|
ref={ref} |
|
|
|
blockNode |
|
|
|
{...otherProps} |
|
|
|
prefixCls={prefixCls} |
|
|
|
className={connectClassName} |
|
|
|
expandedKeys={expandedKeys} |
|
|
|
selectedKeys={selectedKeys} |
|
|
|
onSelect={onSelect} |
|
|
|
onClick={onClick} |
|
|
|
onDoubleClick={onDoubleClick} |
|
|
|
onExpand={onExpand} |
|
|
|
/> |
|
|
|
); |
|
|
|
}; |
|
|
|
|
|
|
|
DirectoryTree.defaultProps = { |
|
|
|
showIcon: true, |
|
|
|
expandAction: 'click' as DirectoryTreeProps['expandAction'], |
|
|
|
}; |
|
|
|
|
|
|
|
export default DirectoryTree; |
|
|
|