//
// SPDX-License-Identifier: AGPL-3.0
-import * as React from 'react';
-import { WrappedFieldProps, Field } from 'redux-form';
-import { identity, memoize } from 'lodash';
-import { Autocomplete } from '~/components/autocomplete/autocomplete';
-import { Vocabulary } from '~/models/vocabulary';
-import { require } from '~/validators/require';
-import { ITEMS_PLACEHOLDER, connectVocabulary, VocabularyProp } from '~/views-components/resource-properties-form/property-field-common';
+import React from 'react';
+import { WrappedFieldProps, Field, FormName, reset, change, WrappedFieldInputProps, WrappedFieldMetaProps } from 'redux-form';
+import { memoize } from 'lodash';
+import { Autocomplete } from 'components/autocomplete/autocomplete';
+import {
+ Vocabulary,
+ getTags,
+ getTagKeyID,
+ getTagKeyLabel,
+ getPreferredTags,
+ PropFieldSuggestion
+} from 'models/vocabulary';
+import {
+ handleSelect,
+ handleBlur,
+ connectVocabulary,
+ VocabularyProp,
+ ValidationProp,
+ buildProps
+} from 'views-components/resource-properties-form/property-field-common';
+import { TAG_KEY_VALIDATION } from 'validators/validators';
+import { escapeRegExp } from 'common/regexp';
+import { ChangeEvent } from 'react';
export const PROPERTY_KEY_FIELD_NAME = 'key';
+export const PROPERTY_KEY_FIELD_ID = 'keyID';
export const PropertyKeyField = connectVocabulary(
- ({ vocabulary }: VocabularyProp) =>
+ ({ vocabulary, skipValidation, clearPropertyKeyOnSelect }: VocabularyProp & ValidationProp) =>
+ <span data-cy='property-field-key'>
<Field
+ clearPropertyKeyOnSelect
name={PROPERTY_KEY_FIELD_NAME}
component={PropertyKeyInput}
vocabulary={vocabulary}
- validate={getValidation(vocabulary)} />);
-
-const PropertyKeyInput = ({ input, meta, vocabulary }: WrappedFieldProps & VocabularyProp) =>
- <Autocomplete
- value={input.value}
- onChange={input.onChange}
- label='Key'
- suggestions={getSuggestions(input.value, vocabulary)}
- items={ITEMS_PLACEHOLDER}
- onSelect={input.onChange}
- renderSuggestion={identity}
- error={meta.invalid}
- helperText={meta.error}
- />;
+ validate={skipValidation ? undefined : getValidation(vocabulary)} />
+ </span>
+);
+
+const PropertyKeyInput = ({ vocabulary, ...props }: WrappedFieldProps & VocabularyProp & { clearPropertyKeyOnSelect?: boolean }) =>
+ <FormName children={data => (
+ <Autocomplete
+ {...buildProps(props)}
+ label='Key'
+ suggestions={getSuggestions(props.input.value, vocabulary)}
+ renderSuggestion={
+ (s: PropFieldSuggestion) => s.synonyms && s.synonyms.length > 0
+ ? `${s.label} (${s.synonyms.join('; ')})`
+ : s.label
+ }
+ onFocus={() => {
+ if (props.clearPropertyKeyOnSelect && props.input.value) {
+ props.meta.dispatch(reset(props.meta.form));
+ }
+ }}
+ onSelect={handleSelect(PROPERTY_KEY_FIELD_ID, data.form, props.input, props.meta)}
+ onBlur={() => {
+ // Case-insensitive search for the key in the vocabulary
+ const foundKeyID = getTagKeyID(props.input.value, vocabulary);
+ if (foundKeyID !== '') {
+ props.input.value = getTagKeyLabel(foundKeyID, vocabulary);
+ }
+ handleBlur(PROPERTY_KEY_FIELD_ID, data.form, props.meta, props.input, foundKeyID)();
+ }}
+ onChange={(e: ChangeEvent<HTMLInputElement>) => {
+ const newValue = e.currentTarget.value;
+ handleChange(data.form, props.input, props.meta, newValue);
+ }}
+ />
+ )} />;
const getValidation = memoize(
(vocabulary: Vocabulary) =>
- vocabulary.strict
- ? [require, matchTags(vocabulary)]
- : [require]);
+ vocabulary.strict_tags
+ ? [...TAG_KEY_VALIDATION, matchTags(vocabulary)]
+ : TAG_KEY_VALIDATION);
const matchTags = (vocabulary: Vocabulary) =>
(value: string) =>
- getTagsList(vocabulary).find(tag => tag.includes(value))
+ getTags(vocabulary).find(tag => tag.label === value)
? undefined
: 'Incorrect key';
-const getSuggestions = (value: string, vocabulary: Vocabulary) =>
- getTagsList(vocabulary).filter(tag => tag.includes(value) && tag !== value);
+const getSuggestions = (value: string, vocabulary: Vocabulary): PropFieldSuggestion[] => {
+ const re = new RegExp(escapeRegExp(value), "i");
+ return getPreferredTags(vocabulary, value).filter(
+ tag => (tag.label !== value && re.test(tag.label)) ||
+ (tag.synonyms && tag.synonyms.some(s => re.test(s))));
+};
+
+const handleChange = (
+ formName: string,
+ { onChange }: WrappedFieldInputProps,
+ { dispatch }: WrappedFieldMetaProps,
+ value: string) => {
+ // Properties' values are dependant on the keys, if any value is
+ // pre-existant, a change on the property key should mean that the
+ // previous value is invalid, so we better reset the whole form before
+ // setting the new tag key.
+ dispatch(reset(formName));
-const getTagsList = ({ tags }: Vocabulary) =>
- Object.keys(tags);
+ onChange(value);
+ dispatch(change(formName, PROPERTY_KEY_FIELD_NAME, value));
+ };
\ No newline at end of file