15781: Allows multi-value properties on the search bar/editor.
[arvados-workbench2.git] / 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 * as React from 'react';
6 import { Dispatch, compose } from 'redux';
7 import { connect } from 'react-redux';
8 import { InjectedFormProps, formValueSelector } from 'redux-form';
9 import { Grid, withStyles, StyleRulesCallback, WithStyles, Button } from '@material-ui/core';
10 import { RootState } from '~/store/store';
11 import {
12     SEARCH_BAR_ADVANCED_FORM_NAME,
13     changeAdvancedFormProperty,
14     resetAdvancedFormProperty
15 } from '~/store/search-bar/search-bar-actions';
16 import { PropertyValue } from '~/models/search-bar';
17 import { ArvadosTheme } from '~/common/custom-theme';
18 import { SearchBarKeyField, SearchBarValueField } from '~/views-components/form-fields/search-bar-form-fields';
19 import { Chips } from '~/components/chips/chips';
20 import { formatPropertyValue } from "~/common/formatters";
21 import { Vocabulary } from '~/models/vocabulary';
22 import { connectVocabulary } from '../resource-properties-form/property-field-common';
23
24 type CssRules = 'label' | 'button';
25
26 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
27     label: {
28         color: theme.palette.grey["500"],
29         fontSize: '0.8125rem',
30         alignSelf: 'center'
31     },
32     button: {
33         boxShadow: 'none'
34     }
35 });
36
37 interface SearchBarAdvancedPropertiesViewDataProps {
38     submitting: boolean;
39     invalid: boolean;
40     pristine: boolean;
41     propertyValues: PropertyValue;
42     fields: PropertyValue[];
43     vocabulary: Vocabulary;
44 }
45
46 interface SearchBarAdvancedPropertiesViewActionProps {
47     setProps: () => void;
48     setProp: (propertyValues: PropertyValue, properties: PropertyValue[]) => void;
49     getAllFields: (propertyValues: PropertyValue[]) => PropertyValue[] | [];
50 }
51
52 type SearchBarAdvancedPropertiesViewProps = SearchBarAdvancedPropertiesViewDataProps
53     & SearchBarAdvancedPropertiesViewActionProps
54     & InjectedFormProps & WithStyles<CssRules>;
55
56 const selector = formValueSelector(SEARCH_BAR_ADVANCED_FORM_NAME);
57 const mapStateToProps = (state: RootState) => {
58     return {
59         propertyValues: selector(state, 'key', 'value', 'keyID', 'valueID')
60     };
61 };
62
63 const mapDispatchToProps = (dispatch: Dispatch) => ({
64     setProps: (propertyValues: PropertyValue[]) => {
65         dispatch<any>(changeAdvancedFormProperty('properties', propertyValues));
66     },
67     setProp: (propertyValue: PropertyValue, properties: PropertyValue[]) => {
68         dispatch<any>(changeAdvancedFormProperty(
69             'properties',
70             [...properties, propertyValue]
71         ));
72         dispatch<any>(resetAdvancedFormProperty('key'));
73         dispatch<any>(resetAdvancedFormProperty('value'));
74         dispatch<any>(resetAdvancedFormProperty('keyID'));
75         dispatch<any>(resetAdvancedFormProperty('valueID'));
76     },
77     getAllFields: (fields: any) => {
78         return fields.getAll() || [];
79     }
80 });
81
82 export const SearchBarAdvancedPropertiesView = compose(
83     connectVocabulary,
84     connect(mapStateToProps, mapDispatchToProps))(
85     withStyles(styles)(
86         ({ classes, fields, propertyValues, setProps, setProp, getAllFields, vocabulary }: SearchBarAdvancedPropertiesViewProps) =>
87             <Grid container item xs={12} spacing={16}>
88                 <Grid item xs={2} className={classes.label}>Properties</Grid>
89                 <Grid item xs={4}>
90                     <SearchBarKeyField />
91                 </Grid>
92                 <Grid item xs={4}>
93                     <SearchBarValueField />
94                 </Grid>
95                 <Grid container item xs={2} justify='flex-end' alignItems="center">
96                     <Button className={classes.button} onClick={() => setProp(propertyValues, getAllFields(fields))}
97                         color="primary"
98                         size='small'
99                         variant="contained"
100                         disabled={!Boolean(propertyValues.key && propertyValues.value)}>
101                         Add
102                     </Button>
103                 </Grid>
104                 <Grid item xs={2} />
105                 <Grid container item xs={10} spacing={8}>
106                     <Chips values={getAllFields(fields)}
107                         deletable
108                         onChange={setProps}
109                         getLabel={(field: PropertyValue) => formatPropertyValue(field, vocabulary)} />
110                 </Grid>
111             </Grid>
112     )
113 );