/** * empty comment line Ivan Marshalkin * * TODO: navigation using the keyboard * * @author: Ivan Marshalkin * @date: 2019-05-27 */ import * as React from 'react'; import keyBy from 'lodash/keyBy'; import {IFlatListItem, ITreeColumn, ITreeNode} from './TreeType'; import {IStringKeyMap} from '../../type'; import {safeInvoke} from '../../utils/safeInvoke'; import {Button, INTENT} from '../../index'; import {ActionItem, ConditionFunc, IDropConfig, Node as NodeComponent} from './Node'; import * as styles from './tree.m.scss'; import {TreeContainer, ScrollEvent} from './TreeContainer'; export interface ITreeTableProps { nodes: Array>; flatNodes?: T[]; isLoading?: boolean; readOnly?: boolean; columns: Array>; onNodeSelect?: (node: ITreeNode) => void; onNodeExpand?: (node: ITreeNode) => void; onNodeDelete?: (node: ITreeNode) => void; onBeforeNodeSelect?: (node: ITreeNode) => boolean; selectedNodes: string[]; disabledNodes?: string[]; canSelectDisabled?: boolean; colorScheme?: ColorScheme; isStriped?: boolean; isAutoHeightFill?: boolean; maxRowsBeforeOverflow?: number; isDeletable?: boolean | ConditionFunc; filter?: (node: ITreeNode) => boolean; canAdd?: boolean; addText?: React.ReactNode; isAddButtonDisabled?: boolean; onAddNode?: () => void; isEditable?: boolean | ConditionFunc; onEdit?: (node: ITreeNode, newValue: string, cellId: string) => void; // Need to disable draggable mode when use Tree filter // You can use filter nodes outside the Tree component if you want to use drag&drop for filtered tree isDraggable?: boolean; onDragStart?: (event: React.DragEvent, node: ITreeNode) => void; onDragEnd?: (event: React.DragEvent) => void; onDrop?: (event: React.DragEvent, node: ITreeNode) => void; onMoveNode?: (node: ITreeNode, parentNode?: ITreeNode, previousNode?: ITreeNode) => void; allowDragOver?: (event: React.DragEvent, dropToNode: ITreeNode, draggedNode?: ITreeNode) => boolean; canHasChildren?: boolean | ConditionFunc; actions?: Array>; isActionsEnabled?: (node: ITreeNode) => boolean; rightExtraItem?: (nodeData: T) => React.ReactNode; } interface IState { selectedItems: IStringKeyMap; disabledItems: IStringKeyMap; draggedNode: ITreeNode | undefined; } export enum ColorScheme { light = 'theme-light', dark = 'theme-dark' } const defaultProps = { colorScheme: ColorScheme.light, disabled: [], isStriped: true, isAutoHeightFill: true, maxRowsBeforeOverflow: 20, canSelectDisabled: false, addText: 'add' }; export { ITreeNode, NodeComponent as Node, ITreeColumn, IFlatListItem, ActionItem, TreeContainer, ScrollEvent }; export {TreeUtil} from './TreeUtil'; export class Tree extends React.PureComponent, IState> { static defaultProps = defaultProps; constructor (props: ITreeTableProps) { super(props); this.state = { selectedItems: keyBy(this.props.selectedNodes), disabledItems: keyBy(this.props.disabledNodes), draggedNode: undefined }; } addNode = () => { safeInvoke(this.props.onAddNode); }; onDragStart = (event: React.DragEvent, node: ITreeNode) => { safeInvoke(this.props.onDragStart, event, node); this.setState({draggedNode: node}); } onDragEnd = (event: React.DragEvent) => { safeInvoke(this.props.onDragEnd, event); this.setState({draggedNode: undefined}); } onDrop = ( zeroLevelNode: ITreeNode, zeroLevelNodeIndex: number ) => ( event: React.DragEvent, node: ITreeNode, dropConfig: IDropConfig ) => { const draggedNode = this.state.draggedNode; if (!draggedNode) { return; } if (!dropConfig.configFilled && dropConfig.previousNode === undefined) { dropConfig.previousNode = zeroLevelNodeIndex === 0 ? undefined : this.props.nodes[zeroLevelNodeIndex - 1]; } safeInvoke(this.props.onDrop, event, node); safeInvoke(this.props.onMoveNode, draggedNode, dropConfig.parentNode, dropConfig.previousNode); this.setState({draggedNode: undefined}); } override componentDidUpdate (prevProps: Readonly>, prevState: Readonly>, snapshot?: any): void { if (prevProps.selectedNodes !== this.props.selectedNodes) { this.setState({selectedItems: keyBy(this.props.selectedNodes)}); } if (prevProps.disabledNodes !== this.props.disabledNodes) { this.setState({disabledItems: keyBy(this.props.disabledNodes)}); } } override render () { const { isLoading, nodes, colorScheme, isDeletable, columns, onNodeSelect, onNodeExpand, onNodeDelete, onBeforeNodeSelect, filter, isEditable, onEdit, isStriped, isAutoHeightFill, canSelectDisabled, flatNodes, maxRowsBeforeOverflow = Infinity, isDraggable, actions = [], isActionsEnabled, canHasChildren, rightExtraItem } = this.props; const nodesCount = flatNodes?.length; const draggedNode = this.state.draggedNode; return ( (
) : undefined}> {nodes && nodes.map((node, index) => ( key={node.key} readOnly={node.readOnly} columns={columns} onNodeSelect={onNodeSelect} onNodeExpand={onNodeExpand} onBeforeNodeSelect={onBeforeNodeSelect} selectedItems={this.state.selectedItems} disabledItems={this.state.disabledItems} canSelectDisabled={Boolean(canSelectDisabled)} level={0} node={node} isLastChild={false} isDeletable={isDeletable} onNodeDelete={onNodeDelete} filter={filter} isEditable={isEditable} onEdit={onEdit} isDraggable={isDraggable} draggedNode={draggedNode} lastChildStreak={0} onDragStart={this.onDragStart} onDragEnd={this.onDragEnd} onBackwardDrop={this.onDrop(node, index)} allowDragOver={this.props.allowDragOver} canHasChildren={canHasChildren} actions={actions} isActionsEnabled={isActionsEnabled} rightExtraItem={rightExtraItem} isExpanded={node.expanded} /> ))}
); } }