9da3c20c2b91df301c3365c279df037ee1d9bbb0
[arvados-workbench2.git] / src / components / data-table-filters / data-table-filters-popover.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import * as React from "react";
6 import {
7     WithStyles,
8     withStyles,
9     ButtonBase,
10     StyleRulesCallback,
11     Theme,
12     Popover,
13     Button,
14     Card,
15     CardActions,
16     Typography,
17     CardContent,
18     Tooltip
19 } from "@material-ui/core";
20 import * as classnames from "classnames";
21 import { DefaultTransformOrigin } from "~/components/popover/helpers";
22 import { createTree } from '~/models/tree';
23 import { DataTableFilters, DataTableFiltersTree } from "./data-table-filters-tree";
24 import { getNodeDescendants } from '~/models/tree';
25
26 export type CssRules = "root" | "icon" | "active" | "checkbox";
27
28 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
29     root: {
30         cursor: "pointer",
31         display: "inline-flex",
32         justifyContent: "flex-start",
33         flexDirection: "inherit",
34         alignItems: "center",
35         "&:hover": {
36             color: theme.palette.text.primary,
37         },
38         "&:focus": {
39             color: theme.palette.text.primary,
40         },
41     },
42     active: {
43         color: theme.palette.text.primary,
44         '& $icon': {
45             opacity: 1,
46         },
47     },
48     icon: {
49         marginRight: 4,
50         marginLeft: 4,
51         opacity: 0.7,
52         userSelect: "none",
53         width: 16
54     },
55     checkbox: {
56         width: 24,
57         height: 24
58     }
59 });
60
61 enum SelectionMode {
62     ALL = 'all',
63     NONE = 'none'
64 }
65
66 export interface DataTableFilterProps {
67     name: string;
68     filters: DataTableFilters;
69     onChange?: (filters: DataTableFilters) => void;
70
71     /**
72      * By default `all` filters selection means that label should be grayed out.
73      * Use `none` when label is supposed to be grayed out when no filter is selected.
74      */
75     defaultSelection?: SelectionMode;
76 }
77
78 interface DataTableFilterState {
79     anchorEl?: HTMLElement;
80     filters: DataTableFilters;
81     prevFilters: DataTableFilters;
82 }
83
84 export const DataTableFiltersPopover = withStyles(styles)(
85     class extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
86         state: DataTableFilterState = {
87             anchorEl: undefined,
88             filters: createTree(),
89             prevFilters: createTree(),
90         };
91         icon = React.createRef<HTMLElement>();
92
93         render() {
94             const { name, classes, defaultSelection = SelectionMode.ALL, children } = this.props;
95             const isActive = getNodeDescendants('')(this.state.filters)
96                 .some(f => defaultSelection === SelectionMode.ALL
97                     ? !f.selected
98                     : f.selected
99                 );
100             return <>
101                 <Tooltip title='Filters'>
102                     <ButtonBase
103                         className={classnames([classes.root, { [classes.active]: isActive }])}
104                         component="span"
105                         onClick={this.open}
106                         disableRipple>
107                         {children}
108                         <i className={classnames(["fas fa-filter", classes.icon])}
109                             data-fa-transform="shrink-3"
110                             ref={this.icon} />
111                     </ButtonBase>
112                 </Tooltip>
113                 <Popover
114                     anchorEl={this.state.anchorEl}
115                     open={!!this.state.anchorEl}
116                     anchorOrigin={DefaultTransformOrigin}
117                     transformOrigin={DefaultTransformOrigin}
118                     onClose={this.cancel}>
119                     <Card>
120                         <CardContent>
121                             <Typography variant="caption">
122                                 {name}
123                             </Typography>
124                         </CardContent>
125                         <DataTableFiltersTree
126                             filters={this.state.filters}
127                             onChange={filters => this.setState({ filters })} />
128                         <CardActions>
129                             <Button
130                                 color="primary"
131                                 variant='contained'
132                                 size="small"
133                                 onClick={this.submit}>
134                                 Ok
135                             </Button>
136                             <Button
137                                 color="primary"
138                                 variant="outlined"
139                                 size="small"
140                                 onClick={this.cancel}>
141                                 Cancel
142                             </Button>
143                         </CardActions >
144                     </Card>
145                 </Popover>
146             </>;
147         }
148
149         static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
150             return props.filters !== state.prevFilters
151                 ? { ...state, filters: props.filters, prevFilters: props.filters }
152                 : state;
153         }
154
155         open = () => {
156             this.setState({ anchorEl: this.icon.current || undefined });
157         }
158
159         submit = () => {
160             const { onChange } = this.props;
161             if (onChange) {
162                 onChange(this.state.filters);
163             }
164             this.setState({ anchorEl: undefined });
165         }
166
167         cancel = () => {
168             this.setState(prev => ({
169                 ...prev,
170                 filters: prev.prevFilters,
171                 anchorEl: undefined
172             }));
173         }
174
175         setFilters = (filters: DataTableFilters) => {
176             this.setState({ filters });
177         }
178
179     }
180 );