7033d369e7dcdb0f1daa0e9168fac1427719db3c
[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     List,
14     ListItem,
15     Checkbox,
16     ListItemText,
17     Button,
18     Card,
19     CardActions,
20     Typography,
21     CardContent,
22     Tooltip
23 } from "@material-ui/core";
24 import * as classnames from "classnames";
25 import { DefaultTransformOrigin } from "../popover/helpers";
26 import { createTree, initTreeNode, mapTree } from '~/models/tree';
27 import { DataTableFilters as DataTableFiltersModel, DataTableFiltersTree } from "./data-table-filters-tree";
28 import { pipe } from 'lodash/fp';
29 import { setNode } from '~/models/tree';
30
31 export type CssRules = "root" | "icon" | "active" | "checkbox";
32
33 const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
34     root: {
35         cursor: "pointer",
36         display: "inline-flex",
37         justifyContent: "flex-start",
38         flexDirection: "inherit",
39         alignItems: "center",
40         "&:hover": {
41             color: theme.palette.text.primary,
42         },
43         "&:focus": {
44             color: theme.palette.text.primary,
45         },
46     },
47     active: {
48         color: theme.palette.text.primary,
49         '& $icon': {
50             opacity: 1,
51         },
52     },
53     icon: {
54         marginRight: 4,
55         marginLeft: 4,
56         opacity: 0.7,
57         userSelect: "none",
58         width: 16
59     },
60     checkbox: {
61         width: 24,
62         height: 24
63     }
64 });
65
66 export interface DataTableFilterItem {
67     name: string;
68     selected: boolean;
69 }
70
71 export interface DataTableFilterProps {
72     name: string;
73     filters: DataTableFilterItem[];
74     onChange?: (filters: DataTableFilterItem[]) => void;
75 }
76
77 interface DataTableFilterState {
78     anchorEl?: HTMLElement;
79     filters: DataTableFilterItem[];
80     prevFilters: DataTableFilterItem[];
81     filtersTree: DataTableFiltersModel;
82 }
83
84 const filters: DataTableFiltersModel = pipe(
85     createTree,
86     setNode(initTreeNode({ id: 'Project', value: { name: 'Project' } })),
87     setNode(initTreeNode({ id: 'Process', value: { name: 'Process' } })),
88     setNode(initTreeNode({ id: 'Data collection', value: { name: 'Data collection' } })),
89     setNode(initTreeNode({ id: 'General', parent: 'Data collection', value: { name: 'General' } })),
90     setNode(initTreeNode({ id: 'Output', parent: 'Data collection', value: { name: 'Output' } })),
91     setNode(initTreeNode({ id: 'Log', parent: 'Data collection', value: { name: 'Log' } })),
92     mapTree(node => ({...node, selected: true})),
93 )();
94
95 export const DataTableFilters = withStyles(styles)(
96     class extends React.Component<DataTableFilterProps & WithStyles<CssRules>, DataTableFilterState> {
97         state: DataTableFilterState = {
98             anchorEl: undefined,
99             filters: [],
100             prevFilters: [],
101             filtersTree: filters,
102         };
103         icon = React.createRef<HTMLElement>();
104
105         render() {
106             const { name, classes, children } = this.props;
107             const isActive = this.state.filters.some(f => f.selected);
108             return <>
109                 <Tooltip title='Filters'>
110                     <ButtonBase
111                         className={classnames([classes.root, { [classes.active]: isActive }])}
112                         component="span"
113                         onClick={this.open}
114                         disableRipple>
115                         {children}
116                         <i className={classnames(["fas fa-filter", classes.icon])}
117                             data-fa-transform="shrink-3"
118                             ref={this.icon} />
119                     </ButtonBase>
120                 </Tooltip>
121                 <Popover
122                     anchorEl={this.state.anchorEl}
123                     open={!!this.state.anchorEl}
124                     anchorOrigin={DefaultTransformOrigin}
125                     transformOrigin={DefaultTransformOrigin}
126                     onClose={this.cancel}>
127                     <Card>
128                         <CardContent>
129                             <Typography variant="caption">
130                                 {name}
131                             </Typography>
132                         </CardContent>
133                         <List dense>
134                             {this.state.filters.map((filter, index) =>
135                                 <ListItem
136                                     key={index}>
137                                     <Checkbox
138                                         onClick={this.toggleFilter(filter)}
139                                         color="primary"
140                                         checked={filter.selected}
141                                         className={classes.checkbox} />
142                                     <ListItemText>
143                                         {filter.name}
144                                     </ListItemText>
145                                 </ListItem>
146                             )}
147                         </List>
148                         <DataTableFiltersTree
149                             filters={this.state.filtersTree}
150                             onChange={filtersTree => this.setState({ filtersTree })} />
151                         <CardActions>
152                             <Button
153                                 color="primary"
154                                 variant="raised"
155                                 size="small"
156                                 onClick={this.submit}>
157                                 Ok
158                             </Button>
159                             <Button
160                                 color="primary"
161                                 variant="outlined"
162                                 size="small"
163                                 onClick={this.cancel}>
164                                 Cancel
165                             </Button>
166                         </CardActions >
167                     </Card>
168                 </Popover>
169             </>;
170         }
171
172         static getDerivedStateFromProps(props: DataTableFilterProps, state: DataTableFilterState): DataTableFilterState {
173             return props.filters !== state.prevFilters
174                 ? { ...state, filters: props.filters, prevFilters: props.filters }
175                 : state;
176         }
177
178         open = () => {
179             this.setState({ anchorEl: this.icon.current || undefined });
180         }
181
182         submit = () => {
183             const { onChange } = this.props;
184             if (onChange) {
185                 onChange(this.state.filters);
186             }
187             this.setState({ anchorEl: undefined });
188         }
189
190         cancel = () => {
191             this.setState(prev => ({
192                 ...prev,
193                 filters: prev.prevFilters,
194                 anchorEl: undefined
195             }));
196         }
197
198         toggleFilter = (toggledFilter: DataTableFilterItem) => () => {
199             this.setState(prev => ({
200                 ...prev,
201                 filters: prev.filters.map(filter =>
202                     filter === toggledFilter
203                         ? { ...filter, selected: !filter.selected }
204                         : filter)
205             }));
206         }
207     }
208 );