X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/7be9038d988e4dd566536be646769f720b1ae04c..2a7fd99c212c33a1ec9911f8529fa5afc59a7bb2:/src/components/data-table-filters/data-table-filters-popover.tsx

diff --git a/src/components/data-table-filters/data-table-filters-popover.tsx b/src/components/data-table-filters/data-table-filters-popover.tsx
index 670afa95e4..557abd825a 100644
--- a/src/components/data-table-filters/data-table-filters-popover.tsx
+++ b/src/components/data-table-filters/data-table-filters-popover.tsx
@@ -2,7 +2,7 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import * as React from "react";
+import React, { useEffect } from 'react';
 import {
     WithStyles,
     withStyles,
@@ -16,27 +16,28 @@ import {
     Typography,
     CardContent,
     Tooltip,
-    IconButton
-} from "@material-ui/core";
-import * as 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';
+    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";
+export type CssRules = 'root' | 'icon' | 'iconButton' | 'active' | 'checkbox';
 
 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     root: {
-        cursor: "pointer",
-        display: "inline-flex",
-        justifyContent: "flex-start",
-        flexDirection: "inherit",
-        alignItems: "center",
-        "&:hover": {
+        cursor: 'pointer',
+        display: 'inline-flex',
+        justifyContent: 'flex-start',
+        flexDirection: 'inherit',
+        alignItems: 'center',
+        '&:hover': {
             color: theme.palette.text.primary,
         },
-        "&:focus": {
+        '&:focus': {
             color: theme.palette.text.primary,
         },
     },
@@ -51,7 +52,7 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
         userSelect: 'none',
         width: 16,
         height: 15,
-        marginTop: 1
+        marginTop: 1,
     },
     iconButton: {
         color: theme.palette.text.primary,
@@ -59,13 +60,13 @@ const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
     },
     checkbox: {
         width: 24,
-        height: 24
-    }
+        height: 24,
+    },
 });
 
 enum SelectionMode {
     ALL = 'all',
-    NONE = 'none'
+    NONE = 'none',
 }
 
 export interface DataTableFilterProps {
@@ -102,94 +103,89 @@ export const DataTableFiltersPopover = withStyles(styles)(
 
         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 <>
-                <Tooltip title='Filters'>
-                    <ButtonBase
-                        className={classnames([classes.root, { [classes.active]: isActive }])}
-                        component="span"
-                        onClick={this.open}
-                        disableRipple>
-                        {children}
-                        <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
-                    anchorEl={this.state.anchorEl}
-                    open={!!this.state.anchorEl}
-                    anchorOrigin={DefaultTransformOrigin}
-                    transformOrigin={DefaultTransformOrigin}
-                    onClose={this.cancel}>
-                    <Card>
-                        <CardContent>
-                            <Typography variant="caption">
-                                {name}
-                            </Typography>
-                        </CardContent>
-                        <DataTableFiltersTree
-                            filters={this.state.filters}
-                            mutuallyExclusive={this.props.mutuallyExclusive}
-                            onChange={filters => {
-                                this.setState({ filters });
-                            }} />
-                        <CardActions>
-                            <Button
-                                color="primary"
-                                variant='contained'
-                                size="small"
-                                onClick={this.submit}>
-                                Ok
-                            </Button>
-                            <Button
-                                color="primary"
-                                variant="outlined"
-                                size="small"
-                                onClick={this.cancel}>
-                                Cancel
-                            </Button>
-                        </CardActions >
-                    </Card>
-                </Popover>
-            </>;
+            const isActive = getNodeDescendants('')(this.state.filters).some((f) => (defaultSelection === SelectionMode.ALL ? !f.selected : f.selected));
+            return (
+                <>
+                    <Tooltip disableFocusListener title='Filters'>
+                        <ButtonBase className={classnames([classes.root, { [classes.active]: isActive }])} component='span' onClick={this.open} disableRipple>
+                            {children}
+                            <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
+                        anchorEl={this.state.anchorEl}
+                        open={!!this.state.anchorEl}
+                        anchorOrigin={DefaultTransformOrigin}
+                        transformOrigin={DefaultTransformOrigin}
+                        onClose={this.close}
+                    >
+                        <Card>
+                            <CardContent>
+                                <Typography variant='caption'>{name}</Typography>
+                            </CardContent>
+                            <DataTableFiltersTree filters={this.state.filters} mutuallyExclusive={this.props.mutuallyExclusive} onChange={this.onChange} />
+                            <>
+                                {this.props.mutuallyExclusive || (
+                                    <CardActions>
+                                        <Button color='primary' variant='outlined' size='small' onClick={this.close}>
+                                            Close
+                                        </Button>
+                                    </CardActions>
+                                )}
+                            </>
+                        </Card>
+                    </Popover>
+                    <this.MountHandler />
+                </>
+            );
         }
 
         static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
-            return props.filters !== state.prevFilters
-                ? { ...state, filters: props.filters, prevFilters: props.filters }
-                : state;
+            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 = () => {
+        submit = debounce(() => {
             const { onChange } = this.props;
             if (onChange) {
                 onChange(this.state.filters);
             }
-            this.setState({ anchorEl: undefined });
-        }
+        }, 1000);
+
+        MountHandler = () => {
+            useEffect(() => {
+                return () => {
+                    this.submit.cancel();
+                };
+            }, []);
+            return null;
+        };
 
-        cancel = () => {
-            this.setState(prev => ({
+        close = () => {
+            this.setState((prev) => ({
                 ...prev,
-                filters: prev.prevFilters,
-                anchorEl: undefined
+                anchorEl: undefined,
             }));
-        }
-
-        setFilters = (filters: DataTableFilters) => {
-            this.setState({ filters });
-        }
-
+        };
     }
 );
