Merge branch '18203-Support-setting-multi-properties-at-once' into main
[arvados-workbench2.git] / src / components / data-table-filters / data-table-filters-popover.tsx
index b79d36b47a9236e11503bd4e995daac18a703ca2..b51878664449b67a9f3a33b992fa9a0605820173 100644 (file)
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from "react";
+import React, { useEffect } from "react";
 import {
     WithStyles,
     withStyles,
@@ -15,15 +15,17 @@ import {
     CardActions,
     Typography,
     CardContent,
-    Tooltip
+    Tooltip,
+    IconButton
 } from "@material-ui/core";
-import * as classnames from "classnames";
-import { DefaultTransformOrigin } from "~/components/popover/helpers";
-import { createTree } from '~/models/tree';
+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 { getNodeDescendants } from 'models/tree';
+import debounce from "lodash/debounce";
 
-export type CssRules = "root" | "icon" | "active" | "checkbox";
+export type CssRules = "root" | "icon" | "iconButton" | "active" | "checkbox";
 
 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     root: {
@@ -41,16 +43,20 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     },
     active: {
         color: theme.palette.text.primary,
-        '& $icon': {
+        '& $iconButton': {
             opacity: 1,
         },
     },
     icon: {
-        marginRight: 4,
-        marginLeft: 4,
+        fontSize: 12,
+        userSelect: 'none',
+        width: 16,
+        height: 15,
+        marginTop: 1
+    },
+    iconButton: {
+        color: theme.palette.text.primary,
         opacity: 0.7,
-        userSelect: "none",
-        width: 16
     },
     checkbox: {
         width: 24,
@@ -58,10 +64,26 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     }
 });
 
+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 {
@@ -80,19 +102,25 @@ export const DataTableFiltersPopover = withStyles(styles)(
         icon = React.createRef<HTMLElement>();
 
         render() {
-            const { name, classes, children } = this.props;
-            const isActive = getNodeDescendants('')(this.state.filters).some(f => f.selected);
+            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 <>
-                <Tooltip title='Filters'>
+                <Tooltip disableFocusListener title='Filters'>
                     <ButtonBase
                         className={classnames([classes.root, { [classes.active]: isActive }])}
                         component="span"
                         onClick={this.open}
                         disableRipple>
                         {children}
-                        <i className={classnames(["fas fa-filter", classes.icon])}
-                            data-fa-transform="shrink-3"
-                            ref={this.icon} />
+                        <IconButton component='span' classes={{ root: classes.iconButton }} tabIndex={-1}>
+                            <i className={classnames(["fas fa-filter", classes.icon])}
+                                data-fa-transform="shrink-3"
+                                ref={this.icon} />
+                        </IconButton>
                     </ButtonBase>
                 </Tooltip>
                 <Popover
@@ -100,7 +128,7 @@ export const DataTableFiltersPopover = withStyles(styles)(
                     open={!!this.state.anchorEl}
                     anchorOrigin={DefaultTransformOrigin}
                     transformOrigin={DefaultTransformOrigin}
-                    onClose={this.cancel}>
+                    onClose={this.close}>
                     <Card>
                         <CardContent>
                             <Typography variant="caption">
@@ -109,25 +137,22 @@ export const DataTableFiltersPopover = withStyles(styles)(
                         </CardContent>
                         <DataTableFiltersTree
                             filters={this.state.filters}
-                            onChange={filters => this.setState({ filters })} />
+                            mutuallyExclusive={this.props.mutuallyExclusive}
+                            onChange={this.onChange} />
+                        {this.props.mutuallyExclusive ||
                         <CardActions>
-                            <Button
-                                color="primary"
-                                variant="raised"
-                                size="small"
-                                onClick={this.submit}>
-                                Ok
-                            </Button>
                             <Button
                                 color="primary"
                                 variant="outlined"
                                 size="small"
-                                onClick={this.cancel}>
-                                Cancel
+                                onClick={this.close}>
+                                Close
                             </Button>
                         </CardActions >
+                        }
                     </Card>
                 </Popover>
+                <this.MountHandler />
             </>;
         }
 
@@ -141,25 +166,43 @@ export const DataTableFiltersPopover = withStyles(styles)(
             this.setState({ anchorEl: this.icon.current || undefined });
         }
 
-        submit = () => {
+        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);
             }
-            this.setState({ anchorEl: undefined });
-        }
+        }, 1000);
 
-        cancel = () => {
+        MountHandler = () => {
+            useEffect(() => {
+                return () => {
+                    this.submit.cancel();
+                }
+            },[]);
+            return null;
+        };
+
+        close = () => {
             this.setState(prev => ({
                 ...prev,
-                filters: prev.prevFilters,
                 anchorEl: undefined
             }));
         }
 
-        setFilters = (filters: DataTableFilters) => {
-            this.setState({ filters });
-        }
-
     }
 );