1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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';
12 export interface DataTableFilterItem {
16 export type DataTableFilters = Tree<DataTableFilterItem>;
18 export interface DataTableFilterProps {
19 filters: DataTableFilters;
20 onChange?: (filters: DataTableFilters) => void;
23 * When set to true, only one filter can be selected at a time.
25 mutuallyExclusive?: boolean;
28 export class DataTableFiltersTree extends React.Component<DataTableFilterProps> {
30 const { filters } = this.props;
31 const hasSubfilters = countNodes(filters) !== countChildren('')(filters);
34 levelIndentation={hasSubfilters ? 20 : 0}
36 items={filtersToTree(filters)}
37 render={this.props.mutuallyExclusive ? renderRadioItem : renderItem}
39 useRadioButtons={this.props.mutuallyExclusive}
42 toggleItemActive={this.props.mutuallyExclusive ? this.toggleRadioButtonFilter : this.toggleFilter}
43 toggleItemOpen={this.toggleOpen}
49 * Handler for when a tree item is toggled via a radio button.
50 * Ensures mutual exclusivity among filter tree items.
52 toggleRadioButtonFilter = (_: any, item: TreeItem<DataTableFilterItem>) => {
53 const { onChange = noop } = this.props;
55 // If the filter is already selected, do nothing.
60 // Otherwise select this node and deselect the others
61 const filters = selectNode(item.id)(this.props.filters);
62 const toDeselect = Object.keys(this.props.filters).filter((id) => id !== item.id);
63 onChange(deselectNodes(toDeselect)(filters));
66 toggleFilter = (_: React.MouseEvent, item: TreeItem<DataTableFilterItem>) => {
67 const { onChange = noop } = this.props;
68 onChange(toggleNodeSelection(item.id)(this.props.filters));
71 toggleOpen = (_: React.MouseEvent, item: TreeItem<DataTableFilterItem>) => {
72 const { onChange = noop } = this.props;
73 onChange(toggleNodeCollapse(item.id)(this.props.filters));
77 const renderItem = (item: TreeItem<DataTableFilterItem>) => (
80 {item.initialState !== item.selected ? <>*</> : null}
84 const renderRadioItem = (item: TreeItem<DataTableFilterItem>) => <span>{item.data.name}</span>;
86 const filterToTreeItem =
87 (filters: DataTableFilters) =>
88 (id: string): TreeItem<any> => {
89 const node = getNode(id)(filters) || initTreeNode({ id: '', value: 'InvalidNode' });
90 const items = getNodeChildrenIds(node.id)(filters).map(filterToTreeItem(filters));
91 const isIndeterminate = !node.selected && items.some((i) => i.selected || i.indeterminate);
97 items: items.length > 0 ? items : undefined,
99 selected: node.selected,
100 initialState: node.initialState,
101 indeterminate: isIndeterminate,
102 status: TreeItemStatus.LOADED,
106 const filtersToTree = (filters: DataTableFilters): TreeItem<DataTableFilterItem>[] => map(filterToTreeItem(filters), getNodeChildrenIds('')(filters));