1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import 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, getTagValueLabel, PropFieldSuggestion, getPreferredTagValues } from 'models/vocabulary';
10 import { PROPERTY_KEY_FIELD_ID, PROPERTY_KEY_FIELD_NAME } from 'views-components/resource-properties-form/property-key-field';
18 } from 'views-components/resource-properties-form/property-field-common';
19 import { TAG_VALUE_VALIDATION } from 'validators/validators';
20 import { escapeRegExp } from 'common/regexp';
21 import { ChangeEvent } from 'react';
23 interface PropertyKeyProp {
24 propertyKeyId: string;
25 propertyKeyName: string;
28 interface PropertyValueInputProp {
32 type PropertyValueFieldProps = VocabularyProp & PropertyKeyProp & ValidationProp & PropertyValueInputProp;
34 export const PROPERTY_VALUE_FIELD_NAME = 'value';
35 export const PROPERTY_VALUE_FIELD_ID = 'valueID';
37 const connectVocabularyAndPropertyKey = compose(
40 propertyKeyId: PROPERTY_KEY_FIELD_ID,
41 propertyKeyName: PROPERTY_KEY_FIELD_NAME,
45 export const PropertyValueField = connectVocabularyAndPropertyKey(
46 ({ skipValidation, ...props }: PropertyValueFieldProps) =>
47 <span data-cy='property-field-value'>
49 name={PROPERTY_VALUE_FIELD_NAME}
50 component={PropertyValueInput}
51 validate={skipValidation ? undefined : getValidation(props)}
52 {...{...props, disabled: !props.propertyKeyName}} />
56 const PropertyValueInput = ({ vocabulary, propertyKeyId, propertyKeyName, ...props }: WrappedFieldProps & PropertyValueFieldProps) =>
57 <FormName children={data => (
59 {...buildProps(props)}
61 disabled={props.disabled}
62 suggestions={getSuggestions(props.input.value, propertyKeyId, vocabulary)}
64 (s: PropFieldSuggestion) => s.synonyms && s.synonyms.length > 0
65 ? `${s.label} (${s.synonyms.join('; ')})`
68 onSelect={handleSelect(PROPERTY_VALUE_FIELD_ID, data.form, props.input, props.meta)}
70 // Case-insensitive search for the value in the vocabulary
71 const foundValueID = getTagValueID(propertyKeyId, props.input.value, vocabulary);
72 if (foundValueID !== '') {
73 props.input.value = getTagValueLabel(propertyKeyId, foundValueID, vocabulary);
75 handleBlur(PROPERTY_VALUE_FIELD_ID, data.form, props.meta, props.input, foundValueID)();
77 onChange={(e: ChangeEvent<HTMLInputElement>) => {
78 const newValue = e.currentTarget.value;
79 const tagValueID = getTagValueID(propertyKeyId, newValue, vocabulary);
80 handleChange(data.form, tagValueID, props.input, props.meta, newValue);
85 const getValidation = (props: PropertyValueFieldProps) =>
86 isStrictTag(props.propertyKeyId, props.vocabulary)
87 ? [...TAG_VALUE_VALIDATION, matchTagValues(props)]
88 : TAG_VALUE_VALIDATION;
90 const matchTagValues = ({ vocabulary, propertyKeyId }: PropertyValueFieldProps) =>
92 getTagValues(propertyKeyId, vocabulary).find(v => !value || v.label === value)
96 const getSuggestions = (value: string, tagName: string, vocabulary: Vocabulary) => {
97 const re = new RegExp(escapeRegExp(value), "i");
98 return getPreferredTagValues(tagName, vocabulary, value).filter(
99 val => (val.label !== value && re.test(val.label)) ||
100 (val.synonyms && val.synonyms.some(s => re.test(s))));
103 const handleChange = (
106 { onChange }: WrappedFieldInputProps,
107 { dispatch }: WrappedFieldMetaProps,
110 dispatch(change(formName, PROPERTY_VALUE_FIELD_NAME, value));
111 dispatch(change(formName, PROPERTY_VALUE_FIELD_ID, tagValueID));