import { FixedSizeList } from '@o/react-window' import { Flex, gloss } from 'gloss' import * as React from 'react' import { TreeItem, TreeItemID, TreeProps } from './Tree' import { TreeItemsRow } from './TreeItemsRow' type FlatTreeItem = { key: TreeItemID element: TreeItem level: number } type FlatTreeItems = FlatTreeItem[] export class TreeItems extends React.PureComponent< TreeProps & { rowHeight: number | string width: number height: number } > { state: { flatKeys: TreeItemID[] flatTreeItems: FlatTreeItems maxDepth: number } = { flatTreeItems: [], flatKeys: [], maxDepth: -1, } static getDerivedStateFromProps(props, state) { if (props.itemsKey && props.itemsKey === state.itemsKey) { return null } const flatTreeItems: FlatTreeItems = [] const flatKeys: number[] = [] let maxDepth = 0 function seed(key: TreeItemID, level: number) { const element = props.elements[key] if (!element) { return } maxDepth = Math.max(maxDepth, level) flatTreeItems.push({ element, key, level, }) flatKeys.push(key) if (element.children != null && element.children.length > 0 && element.expanded) { for (const child of element.children) { seed(child, level + 1) } } } if (props.root != null) { seed(props.root, 1) } return { itemsKey: props.itemsKey, flatTreeItems, flatKeys, maxDepth } } selectElement = (key: TreeItemID) => { this.props.onTreeItemSelected(key) } onKeyDown = (e: React.KeyboardEvent) => { const { selected } = this.props if (selected == null) { return } const { props } = this const { flatTreeItems, flatKeys } = this.state const selectedIndex = flatKeys.indexOf(selected) if (selectedIndex < 0) { return } const selectedElement = props.elements[selected] if (!selectedElement) { return } if ( e.key === 'c' && ((e.metaKey && process.platform === 'darwin') || (e.ctrlKey && process.platform !== 'darwin')) ) { e.preventDefault() // clipboard.writeText(selectedElement.name); return } if (e.key === 'ArrowUp') { if (selectedIndex === 0 || flatKeys.length === 1) { return } e.preventDefault() this.selectElement(flatKeys[selectedIndex - 1]) } if (e.key === 'ArrowDown') { if (selectedIndex === flatKeys.length - 1) { return } e.preventDefault() this.selectElement(flatKeys[selectedIndex + 1]) } if (e.key === 'ArrowLeft') { e.preventDefault() if (selectedElement.expanded) { // unexpand props.onTreeItemExpanded(selected, false) } else { // jump to parent let parentKey const targetLevel = flatTreeItems[selectedIndex].level - 1 for (let i = selectedIndex; i >= 0; i--) { const { level } = flatTreeItems[i] if (level === targetLevel) { parentKey = flatKeys[i] break } } if (parentKey) { this.selectElement(parentKey) } } } if (e.key === 'ArrowRight' && selectedElement.children.length > 0) { e.preventDefault() if (selectedElement.expanded) { // go to first child this.selectElement(selectedElement.children[0]) } else { // expand props.onTreeItemExpanded(selected, false) } } } buildRow = ({ index, style }: { index: number; style: Object }) => { const { onTreeItemExpanded, onTreeItemHovered, onTreeItemSelected, selected, searchResults, zebra, rowHeight, } = this.props const { flatTreeItems } = this.state const row = flatTreeItems[index] let childrenCount = 0 for (let i = index + 1; i < flatTreeItems.length; i++) { const child = flatTreeItems[i] if (!child) { console.warn('empty tree items child!', index, child) return null } if (child.level <= row.level) { break } else { childrenCount++ } } return ( ) } keyMapper = (index: number): number => { return this.state.flatTreeItems[index].key } render() { const items = this.state.flatTreeItems return ( {this.buildRow} ) } } const TreeItemsContainer = gloss(Flex, { minHeight: '100%', minWidth: '100%', overflow: 'auto', })