From 85e8f8a08f76f4d456b96404403fa71d6fbf7fb7 Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 10 Apr 2021 12:37:00 -0400 Subject: [PATCH 1/2] Add example for FilteringVariableSizeTree --- .../FilteringVariableSizeTree.story.tsx | 215 ++++++++++++++++++ __stories__/index.ts | 1 + 2 files changed, 216 insertions(+) create mode 100644 __stories__/FilteringVariableSizeTree.story.tsx diff --git a/__stories__/FilteringVariableSizeTree.story.tsx b/__stories__/FilteringVariableSizeTree.story.tsx new file mode 100644 index 0000000..7f00330 --- /dev/null +++ b/__stories__/FilteringVariableSizeTree.story.tsx @@ -0,0 +1,215 @@ +/* eslint-disable max-depth */ +import {number, withKnobs} from '@storybook/addon-knobs'; +import {storiesOf} from '@storybook/react'; +import React, {FC, useState, useCallback, useEffect, useRef} from 'react'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { + TreeWalker, + TreeWalkerValue, + VariableSizeNodeData, + VariableSizeNodePublicState, + VariableSizeTree, +} from '../src'; +import {NodeComponentProps} from '../src/Tree'; + +document.body.style.margin = '0'; +document.body.style.display = 'flex'; +document.body.style.minHeight = '100vh'; + +const root = document.getElementById('root')!; +root.style.margin = '10px 0 0 10px'; +root.style.flex = '1'; + +type TreeNode = Readonly<{ + children: TreeNode[]; + id: number; + name: string; +}>; + +type NodeMeta = Readonly<{ + nestingLevel: number; + node: TreeNode; +}>; + +type ExtendedData = VariableSizeNodeData & + Readonly<{ + isLeaf: boolean; + name: string; + nestingLevel: number; + }>; + +let nodeId = 0; + +const createNode = (depth: number = 0) => { + const node: TreeNode = { + children: [], + id: nodeId, + name: `test-${nodeId}`, + }; + + nodeId += 1; + + if (depth === 5) { + return node; + } + + for (let i = 0; i < 10; i++) { + node.children.push(createNode(depth + 1)); + } + + return node; +}; + +const rootNode = createNode(); +const defaultGapStyle = {marginLeft: 10}; +const defaultButtonStyle = {fontFamily: 'Courier New'}; + +const Node: FC +>> = ({ + height, + data: {isLeaf, name, nestingLevel}, + isOpen, + style, + setOpen, + treeData: itemSize, +}) => { + const canOpen = height <= itemSize; + + return ( +
+ {!isLeaf && ( +
+ +
+ )} +
{name}
+
+ ); +}; + +type TreePresenterProps = Readonly<{ + itemSize: number; +}>; + +const getNodeData = ( + node: TreeNode, + nestingLevel: number, + itemSize: number, +): TreeWalkerValue => ({ + data: { + defaultHeight: node.children.length === 0 ? itemSize : 50, + id: node.id.toString(), + isLeaf: node.children.length === 0, + isOpenByDefault: true, + name: node.name, + nestingLevel, + }, + nestingLevel, + node, +}); + +let i = 0; +function filterTree(tree: TreeNode, text: string) { + if (tree.children.length) { + const subtree = { + ...tree, + children: tree.children + .map((child) => filterTree(child, text)) + .filter((child) => !!child), + }; + + return subtree.children.length ? subtree : null; + } + + if (i++ < 50) { + console.log(tree.name, tree.name.startsWith(text), text); + } + + return tree.name.startsWith(text) ? tree : null; +} + +const TreePresenter: FC = ({itemSize}) => { + const tree = useRef>(null); + const [filter, setFilter] = useState(''); + const filteredRootNode = filterTree(rootNode, filter); + console.log({filteredRootNode}); + i = 0; + + const treeWalker = useCallback( + function* treeWalker(): ReturnType> { + yield getNodeData(filteredRootNode, 0, itemSize); + + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + while (true) { + const parentMeta = yield; + + // eslint-disable-next-line @typescript-eslint/prefer-for-of + for (let i = 0; i < parentMeta.node.children.length; i++) { + yield getNodeData( + parentMeta.node.children[i], + parentMeta.nestingLevel + 1, + itemSize, + ); + } + } + }, + [itemSize, filteredRootNode], + ); + + useEffect(() => { + // eslint-disable-next-line @typescript-eslint/no-floating-promises + tree.current?.recomputeTree({ + refreshNodes: true, + useDefaultHeight: true, + }); + }, [itemSize]); + + return ( + <> + + { + setFilter(event.target.value); + }} + value={filter} + /> + + {({height}) => ( + + {Node} + + )} + + + ); +}; + +storiesOf('Tree', module) + .addDecorator(withKnobs) + .add('FilteringVariableSizeTree', () => ( + + )); diff --git a/__stories__/index.ts b/__stories__/index.ts index 4c4c910..7e2304b 100644 --- a/__stories__/index.ts +++ b/__stories__/index.ts @@ -1,5 +1,6 @@ import './FixedSizeTree.story'; import './VariableSizeTree.story'; +import './FilteringVariableSizeTree.story'; import './MultipleRoots.story'; import './AsyncData.story'; import './AsyncDataIdle.story'; From b3889964c478072786340f18961e468927ae6ec2 Mon Sep 17 00:00:00 2001 From: Colin Date: Sat, 10 Apr 2021 12:45:25 -0400 Subject: [PATCH 2/2] More filtering --- .../FilteringVariableSizeTree.story.tsx | 43 +++++++------------ 1 file changed, 15 insertions(+), 28 deletions(-) diff --git a/__stories__/FilteringVariableSizeTree.story.tsx b/__stories__/FilteringVariableSizeTree.story.tsx index 7f00330..b78295f 100644 --- a/__stories__/FilteringVariableSizeTree.story.tsx +++ b/__stories__/FilteringVariableSizeTree.story.tsx @@ -67,22 +67,13 @@ const defaultButtonStyle = {fontFamily: 'Courier New'}; const Node: FC ->> = ({ - height, - data: {isLeaf, name, nestingLevel}, - isOpen, - style, - setOpen, - treeData: itemSize, -}) => { - const canOpen = height <= itemSize; - +>> = ({data: {isLeaf, name, nestingLevel}, isOpen, style, setOpen}) => { return (
=> ({ data: { - defaultHeight: node.children.length === 0 ? itemSize : 50, + defaultHeight: node.children.length === 0 ? 30 : 60, id: node.id.toString(), isLeaf: node.children.length === 0, isOpenByDefault: true, @@ -137,23 +127,23 @@ function filterTree(tree: TreeNode, text: string) { return subtree.children.length ? subtree : null; } - if (i++ < 50) { - console.log(tree.name, tree.name.startsWith(text), text); - } - return tree.name.startsWith(text) ? tree : null; } -const TreePresenter: FC = ({itemSize}) => { +const TreePresenter: FC = () => { const tree = useRef>(null); const [filter, setFilter] = useState(''); - const filteredRootNode = filterTree(rootNode, filter); - console.log({filteredRootNode}); + const filteredRootNode = filterTree(rootNode, filter) || { + name: 'no results', + children: [], + id: 'na', + nestingLevel: 0, + }; i = 0; const treeWalker = useCallback( function* treeWalker(): ReturnType> { - yield getNodeData(filteredRootNode, 0, itemSize); + yield getNodeData(filteredRootNode, 0); // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition while (true) { @@ -164,12 +154,11 @@ const TreePresenter: FC = ({itemSize}) => { yield getNodeData( parentMeta.node.children[i], parentMeta.nestingLevel + 1, - itemSize, ); } } }, - [itemSize, filteredRootNode], + [filteredRootNode], ); useEffect(() => { @@ -178,7 +167,8 @@ const TreePresenter: FC = ({itemSize}) => { refreshNodes: true, useDefaultHeight: true, }); - }, [itemSize]); + // Important, recompute tree on filter text changing + }, [filter]); return ( <> @@ -195,7 +185,6 @@ const TreePresenter: FC = ({itemSize}) => { {({height}) => ( = ({itemSize}) => { storiesOf('Tree', module) .addDecorator(withKnobs) - .add('FilteringVariableSizeTree', () => ( - - )); + .add('FilteringVariableSizeTree', () => );