b71b950fb0add9a3509bf766a6ecfc162f2ca079
[arvados-workbench2.git] / src / views-components / resource-properties-form / property-value-field.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 { WrappedFieldProps, Field, formValues, FormName, WrappedFieldInputProps, WrappedFieldMetaProps, change } from 'redux-form';
7 import { compose } from 'redux';
8 import { Autocomplete } from 'components/autocomplete/autocomplete';
9 import { Vocabulary, isStrictTag, getTagValues, getTagValueID } from 'models/vocabulary';
10 import { PROPERTY_KEY_FIELD_ID, PROPERTY_KEY_FIELD_NAME } from 'views-components/resource-properties-form/property-key-field';
11 import {
12     handleSelect,
13     handleBlur,
14     VocabularyProp,
15     ValidationProp,
16     connectVocabulary,
17     buildProps
18 } from 'views-components/resource-properties-form/property-field-common';
19 import { TAG_VALUE_VALIDATION } from 'validators/validators';
20 import { escapeRegExp } from 'common/regexp.ts';
21 import { ChangeEvent } from 'react';
22
23 interface PropertyKeyProp {
24     propertyKeyId: string;
25     propertyKeyName: string;
26 }
27
28 interface PropertyValueInputProp {
29     disabled: boolean;
30 }
31
32 type PropertyValueFieldProps = VocabularyProp & PropertyKeyProp & ValidationProp & PropertyValueInputProp;
33
34 export const PROPERTY_VALUE_FIELD_NAME = 'value';
35 export const PROPERTY_VALUE_FIELD_ID = 'valueID';
36
37 const connectVocabularyAndPropertyKey = compose(
38     connectVocabulary,
39     formValues({
40         propertyKeyId: PROPERTY_KEY_FIELD_ID,
41         propertyKeyName: PROPERTY_KEY_FIELD_NAME,
42     }),
43 );
44
45 export const PropertyValueField = connectVocabularyAndPropertyKey(
46     ({ skipValidation, ...props }: PropertyValueFieldProps) =>
47         <span data-cy='property-field-value'>
48         <Field
49             name={PROPERTY_VALUE_FIELD_NAME}
50             component={PropertyValueInput}
51             validate={skipValidation ? undefined : getValidation(props)}
52             {...{...props, disabled: !props.propertyKeyName}} />
53         </span>
54 );
55
56 const PropertyValueInput = ({ vocabulary, propertyKeyId, propertyKeyName, ...props }: WrappedFieldProps & PropertyValueFieldProps) =>
57     <FormName children={data => (
58         <Autocomplete
59             label='Value'
60             disabled={props.disabled}
61             suggestions={getSuggestions(props.input.value, propertyKeyId, vocabulary)}
62             onSelect={handleSelect(PROPERTY_VALUE_FIELD_ID, data.form, props.input, props.meta)}
63             onBlur={handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, getTagValueID(propertyKeyId, props.input.value, vocabulary))}
64             onChange={(e: ChangeEvent<HTMLInputElement>) => {
65                 const newValue = e.currentTarget.value;
66                 const tagValueID = getTagValueID(propertyKeyId, newValue, vocabulary);
67                 handleChange(data.form, tagValueID, props.input, props.meta, newValue);
68             }}
69             {...buildProps(props)}
70         />
71     )} />;
72
73 const getValidation = (props: PropertyValueFieldProps) =>
74     isStrictTag(props.propertyKeyId, props.vocabulary)
75         ? [...TAG_VALUE_VALIDATION, matchTagValues(props)]
76         : TAG_VALUE_VALIDATION;
77
78 const matchTagValues = ({ vocabulary, propertyKeyId }: PropertyValueFieldProps) =>
79     (value: string) =>
80         getTagValues(propertyKeyId, vocabulary).find(v => v.label === value)
81             ? undefined
82             : 'Incorrect value';
83
84 const getSuggestions = (value: string, tagName: string, vocabulary: Vocabulary) => {
85     const re = new RegExp(escapeRegExp(value), "i");
86     return getTagValues(tagName, vocabulary).filter(v => re.test(v.label) && v.label !== value);
87 };
88
89 const handleChange = (
90     formName: string,
91     tagValueID: string,
92     { onChange }: WrappedFieldInputProps,
93     { dispatch }: WrappedFieldMetaProps,
94     value: string) => {
95         onChange(value);
96         dispatch(change(formName, PROPERTY_VALUE_FIELD_NAME, value));
97         dispatch(change(formName, PROPERTY_VALUE_FIELD_ID, tagValueID));
98     };