// Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 import React, { useEffect } from 'react'; import { WithStyles, withStyles, ButtonBase, StyleRulesCallback, Theme, Popover, Button, Card, CardActions, Typography, CardContent, Tooltip, IconButton, } from '@material-ui/core'; import classnames from 'classnames'; import { DefaultTransformOrigin } from 'components/popover/helpers'; import { createTree } from 'models/tree'; import { DataTableFilters, DataTableFiltersTree } from './data-table-filters-tree'; import { getNodeDescendants } from 'models/tree'; import debounce from 'lodash/debounce'; export type CssRules = 'root' | 'icon' | 'iconButton' | 'active' | 'checkbox'; const styles: StyleRulesCallback = (theme: Theme) => ({ root: { cursor: 'pointer', display: 'inline-flex', justifyContent: 'flex-start', flexDirection: 'inherit', alignItems: 'center', '&:hover': { color: theme.palette.text.primary, }, '&:focus': { color: theme.palette.text.primary, }, }, active: { color: theme.palette.text.primary, '& $iconButton': { opacity: 1, }, }, icon: { fontSize: 12, userSelect: 'none', width: 16, height: 15, marginTop: 1, }, iconButton: { color: theme.palette.text.primary, opacity: 0.7, }, checkbox: { width: 24, height: 24, }, }); enum SelectionMode { ALL = 'all', NONE = 'none', } export interface DataTableFilterProps { name: string; filters: DataTableFilters; onChange?: (filters: DataTableFilters) => void; /** * When set to true, only one filter can be selected at a time. */ mutuallyExclusive?: boolean; /** * By default `all` filters selection means that label should be grayed out. * Use `none` when label is supposed to be grayed out when no filter is selected. */ defaultSelection?: SelectionMode; } interface DataTableFilterState { anchorEl?: HTMLElement; filters: DataTableFilters; prevFilters: DataTableFilters; } export const DataTableFiltersPopover = withStyles(styles)( class extends React.Component, DataTableFilterState> { state: DataTableFilterState = { anchorEl: undefined, filters: createTree(), prevFilters: createTree(), }; icon = React.createRef(); render() { const { name, classes, defaultSelection = SelectionMode.ALL, children } = this.props; const isActive = getNodeDescendants('')(this.state.filters).some((f) => (defaultSelection === SelectionMode.ALL ? !f.selected : f.selected)); return ( <> {children} {name} <> {this.props.mutuallyExclusive || ( )} ); } static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState { return props.filters !== state.prevFilters ? { ...state, filters: props.filters, prevFilters: props.filters } : state; } open = () => { this.setState({ anchorEl: this.icon.current || undefined }); }; onChange = (filters) => { this.setState({ filters }); if (this.props.mutuallyExclusive) { // Mutually exclusive filters apply immediately const { onChange } = this.props; if (onChange) { onChange(filters); } this.close(); } else { // Non-mutually exclusive filters are debounced this.submit(); } }; submit = debounce(() => { const { onChange } = this.props; if (onChange) { onChange(this.state.filters); } }, 1000); MountHandler = () => { useEffect(() => { return () => { this.submit.cancel(); }; }, []); return null; }; close = () => { this.setState((prev) => ({ ...prev, anchorEl: undefined, })); }; } );