Merge branch '21720-material-ui-upgrade'
[arvados.git] / services / workbench2 / src / views-components / search-bar / search-bar-advanced-properties-view.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { Dispatch, compose } from 'redux';
7 import { connect } from 'react-redux';
8 import { InjectedFormProps, formValueSelector } from 'redux-form';
9 import { CustomStyleRulesCallback } from 'common/custom-theme';
10 import { Grid, Button } from '@mui/material';
11 import { WithStyles } from '@mui/styles';
12 import withStyles from '@mui/styles/withStyles';
13 import { RootState } from 'store/store';
14 import {
15     SEARCH_BAR_ADVANCED_FORM_NAME,
16     changeAdvancedFormProperty,
17     resetAdvancedFormProperty
18 } from 'store/search-bar/search-bar-actions';
19 import { PropertyValue } from 'models/search-bar';
20 import { ArvadosTheme } from 'common/custom-theme';
21 import { SearchBarKeyField, SearchBarValueField } from 'views-components/form-fields/search-bar-form-fields';
22 import { Chips } from 'components/chips/chips';
23 import { formatPropertyValue } from "common/formatters";
24 import { Vocabulary } from 'models/vocabulary';
25 import { connectVocabulary } from '../resource-properties-form/property-field-common';
26 import { isEqual } from 'lodash';
27
28 type CssRules = 'label' | 'button';
29
30 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
31     label: {
32         color: theme.palette.grey["500"],
33         fontSize: '0.8125rem',
34         alignSelf: 'center'
35     },
36     button: {
37         boxShadow: 'none'
38     }
39 });
40
41 interface SearchBarAdvancedPropertiesViewDataProps {
42     submitting: boolean;
43     invalid: boolean;
44     pristine: boolean;
45     propertyValues: PropertyValue;
46     fields: PropertyValue[];
47     vocabulary: Vocabulary;
48 }
49
50 interface SearchBarAdvancedPropertiesViewActionProps {
51     setProps: () => void;
52     addProp: (propertyValues: PropertyValue, properties: PropertyValue[]) => void;
53     getAllFields: (propertyValues: PropertyValue[]) => PropertyValue[] | [];
54 }
55
56 type SearchBarAdvancedPropertiesViewProps = SearchBarAdvancedPropertiesViewDataProps
57     & SearchBarAdvancedPropertiesViewActionProps
58     & InjectedFormProps & WithStyles<CssRules>;
59
60 const selector = formValueSelector(SEARCH_BAR_ADVANCED_FORM_NAME);
61 const mapStateToProps = (state: RootState) => {
62     return {
63         propertyValues: selector(state, 'key', 'value', 'keyID', 'valueID')
64     };
65 };
66
67 const mapDispatchToProps = (dispatch: Dispatch) => ({
68     setProps: (propertyValues: PropertyValue[]) => {
69         dispatch<any>(changeAdvancedFormProperty('properties', propertyValues));
70     },
71     addProp: (propertyValue: PropertyValue, properties: PropertyValue[]) => {
72         // Remove potential duplicates
73         properties = properties.filter(x => ! isEqual(
74             {
75                 key: x.keyID || x.key,
76                 value: x.valueID || x.value
77             }, {
78                 key: propertyValue.keyID || propertyValue.key,
79                 value: propertyValue.valueID || propertyValue.value
80             }));
81         dispatch<any>(changeAdvancedFormProperty(
82             'properties',
83             [...properties, propertyValue]
84         ));
85         dispatch<any>(resetAdvancedFormProperty('key'));
86         dispatch<any>(resetAdvancedFormProperty('value'));
87         dispatch<any>(resetAdvancedFormProperty('keyID'));
88         dispatch<any>(resetAdvancedFormProperty('valueID'));
89     },
90     getAllFields: (fields: any) => {
91         return fields.getAll() || [];
92     }
93 });
94
95 export const SearchBarAdvancedPropertiesView = compose(
96     connectVocabulary,
97     connect(mapStateToProps, mapDispatchToProps))(
98     withStyles(styles)(
99         ({ classes, fields, propertyValues, setProps, addProp, getAllFields, vocabulary }: SearchBarAdvancedPropertiesViewProps) =>
100             <Grid container item xs={12} spacing={2}>
101                 <Grid item xs={2} className={classes.label}>Properties</Grid>
102                 <Grid item xs={4}>
103                     <SearchBarKeyField />
104                 </Grid>
105                 <Grid item xs={4}>
106                     <SearchBarValueField />
107                 </Grid>
108                 <Grid container item xs={2} justifyContent='flex-end' alignItems="center">
109                     <Button className={classes.button} onClick={() => addProp(propertyValues, getAllFields(fields))}
110                         color="primary"
111                         size='small'
112                         variant="contained"
113                         disabled={!Boolean(propertyValues.key && propertyValues.value)}>
114                         Add
115                     </Button>
116                 </Grid>
117                 <Grid item xs={2} />
118                 <Grid container item xs={10} spacing={1}>
119                     <Chips values={getAllFields(fields)}
120                         deletable
121                         onChange={setProps}
122                         getLabel={(field: PropertyValue) => formatPropertyValue(field, vocabulary)} />
123                 </Grid>
124             </Grid>
125     )
126 );