X-Git-Url: https://git.arvados.org/arvados-workbench2.git/blobdiff_plain/1bbdd9c70d0099e0bf46b3c1ac55e423e3406314..765f6475a53ac7e635b737642ef375459324a117:/src/components/data-table-filters/data-table-filters-tree.tsx diff --git a/src/components/data-table-filters/data-table-filters-tree.tsx b/src/components/data-table-filters/data-table-filters-tree.tsx index b13224ba..7b97865b 100644 --- a/src/components/data-table-filters/data-table-filters-tree.tsx +++ b/src/components/data-table-filters/data-table-filters-tree.tsx @@ -2,12 +2,12 @@ // // SPDX-License-Identifier: AGPL-3.0 -import * as React from "react"; -import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds } from '~/models/tree'; -import { Tree as TreeComponent, TreeItem, TreeItemStatus } from '~/components/tree/tree'; +import React from "react"; +import { Tree, toggleNodeSelection, getNode, initTreeNode, getNodeChildrenIds, selectNode, deselectNodes } from 'models/tree'; +import { Tree as TreeComponent, TreeItem, TreeItemStatus } from 'components/tree/tree'; import { noop, map } from "lodash/fp"; -import { toggleNodeCollapse } from '~/models/tree'; -import { countNodes, countChildren } from '~/models/tree'; +import { toggleNodeCollapse } from 'models/tree'; +import { countNodes, countChildren } from 'models/tree'; export interface DataTableFilterItem { name: string; @@ -18,6 +18,11 @@ export type DataTableFilters = Tree; export interface DataTableFilterProps { filters: DataTableFilters; onChange?: (filters: DataTableFilters) => void; + + /** + * When set to true, only one filter can be selected at a time. + */ + mutuallyExclusive?: boolean; } export class DataTableFiltersTree extends React.Component { @@ -29,15 +34,36 @@ export class DataTableFiltersTree extends React.Component levelIndentation={hasSubfilters ? 20 : 0} itemRightPadding={20} items={filtersToTree(filters)} - render={renderItem} + render={this.props.mutuallyExclusive ? renderRadioItem : renderItem} showSelection + useRadioButtons={this.props.mutuallyExclusive} + disableRipple onContextMenu={noop} - toggleItemActive={noop} + toggleItemActive={ + this.props.mutuallyExclusive + ? this.toggleRadioButtonFilter + : this.toggleFilter + } toggleItemOpen={this.toggleOpen} - toggleItemSelection={this.toggleFilter} />; } + /** + * Handler for when a tree item is toggled via a radio button. + * Ensures mutual exclusivity among filter tree items. + */ + toggleRadioButtonFilter = (_: any, item: TreeItem) => { + const { onChange = noop } = this.props; + + // If the filter is already selected, do nothing. + if (item.selected) { return; } + + // Otherwise select this node and deselect the others + const filters = selectNode(item.id)(this.props.filters); + const toDeselect = Object.keys(this.props.filters).filter((id) => (id !== item.id)); + onChange(deselectNodes(toDeselect)(filters)); + } + toggleFilter = (_: React.MouseEvent, item: TreeItem) => { const { onChange = noop } = this.props; onChange(toggleNodeSelection(item.id)(this.props.filters)); @@ -50,13 +76,24 @@ export class DataTableFiltersTree extends React.Component } const renderItem = (item: TreeItem) => - {item.data.name}; + + {item.data.name} + {item.initialState !== item.selected ? <> + * + : null} + ; + +const renderRadioItem = (item: TreeItem) => + + {item.data.name} + ; const filterToTreeItem = (filters: DataTableFilters) => (id: string): TreeItem => { const node = getNode(id)(filters) || initTreeNode({ id: '', value: 'InvalidNode' }); const items = getNodeChildrenIds(node.id)(filters) .map(filterToTreeItem(filters)); + const isIndeterminate = !node.selected && items.some(i => i.selected || i.indeterminate); return { active: node.active, @@ -65,6 +102,8 @@ const filterToTreeItem = (filters: DataTableFilters) => items: items.length > 0 ? items : undefined, open: node.expanded, selected: node.selected, + initialState: node.initialState, + indeterminate: isIndeterminate, status: TreeItemStatus.LOADED, }; };