19231: Add smaller page sizes (10 and 20 items) to load faster
[arvados-workbench2.git] / src / components / data-table-filters / data-table-filters-tree.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from "react";
6 import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds, selectNode, deselectNodes } from 'models/tree';
7 import { Tree as TreeComponent, TreeItem, TreeItemStatus } from 'components/tree/tree';
8 import { noop, map } from "lodash/fp";
9 import { toggleNodeCollapse } from 'models/tree';
10 import { countNodes, countChildren } from 'models/tree';
11
12 export interface DataTableFilterItem {
13     name: string;
14 }
15
16 export type DataTableFilters = Tree<DataTableFilterItem>;
17
18 export interface DataTableFilterProps {
19     filters: DataTableFilters;
20     onChange?: (filters: DataTableFilters) => void;
21
22     /**
23      * When set to true, only one filter can be selected at a time.
24      */
25     mutuallyExclusive?: boolean;
26 }
27
28 export class DataTableFiltersTree extends React.Component<DataTableFilterProps> {
29
30     render() {
31         const { filters } = this.props;
32         const hasSubfilters = countNodes(filters) !== countChildren('')(filters);
33         return <TreeComponent
34             levelIndentation={hasSubfilters ? 20 : 0}
35             itemRightPadding={20}
36             items={filtersToTree(filters)}
37             render={this.props.mutuallyExclusive ? renderRadioItem : renderItem}
38             showSelection
39             useRadioButtons={this.props.mutuallyExclusive}
40             disableRipple
41             onContextMenu={noop}
42             toggleItemActive={
43                 this.props.mutuallyExclusive
44                     ? this.toggleRadioButtonFilter
45                     : this.toggleFilter
46             }
47             toggleItemOpen={this.toggleOpen}
48         />;
49     }
50
51     /**
52      * Handler for when a tree item is toggled via a radio button.
53      * Ensures mutual exclusivity among filter tree items.
54      */
55     toggleRadioButtonFilter = (_: any, item: TreeItem<DataTableFilterItem>) => {
56         const { onChange = noop } = this.props;
57
58         // If the filter is already selected, do nothing.
59         if (item.selected) { return; }
60
61         // Otherwise select this node and deselect the others
62         const filters = selectNode(item.id)(this.props.filters);
63         const toDeselect = Object.keys(this.props.filters).filter((id) => (id !== item.id));
64         onChange(deselectNodes(toDeselect)(filters));
65     }
66
67     toggleFilter = (_: React.MouseEvent, item: TreeItem<DataTableFilterItem>) => {
68         const { onChange = noop } = this.props;
69         onChange(toggleNodeSelection(item.id)(this.props.filters));
70     }
71
72     toggleOpen = (_: React.MouseEvent, item: TreeItem<DataTableFilterItem>) => {
73         const { onChange = noop } = this.props;
74         onChange(toggleNodeCollapse(item.id)(this.props.filters));
75     }
76 }
77
78 const renderItem = (item: TreeItem<DataTableFilterItem>) =>
79     <span>
80         {item.data.name}
81         {item.initialState !== item.selected ? <>
82             *
83         </> : null}
84     </span>;
85
86 const renderRadioItem = (item: TreeItem<DataTableFilterItem>) =>
87     <span>
88         {item.data.name}
89     </span>;
90
91 const filterToTreeItem = (filters: DataTableFilters) =>
92     (id: string): TreeItem<any> => {
93         const node = getNode(id)(filters) || initTreeNode({ id: '', value: 'InvalidNode' });
94         const items = getNodeChildrenIds(node.id)(filters)
95             .map(filterToTreeItem(filters));
96         const isIndeterminate = !node.selected && items.some(i => i.selected || i.indeterminate);
97
98         return {
99             active: node.active,
100             data: node.value,
101             id: node.id,
102             items: items.length > 0 ? items : undefined,
103             open: node.expanded,
104             selected: node.selected,
105             initialState: node.initialState,
106             indeterminate: isIndeterminate,
107             status: TreeItemStatus.LOADED,
108         };
109     };
110
111 const filtersToTree = (filters: DataTableFilters): TreeItem<DataTableFilterItem>[] =>
112     map(filterToTreeItem(filters), getNodeChildrenIds('')(filters));