/* * * Copyright 2016-2017 RIFT.IO Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ import _debounce from 'lodash/debounce'; import React from 'react' import ClassNames from 'classnames' import changeCase from 'change-case' import Property from '../../libraries/model/DescriptorModelMetaProperty' import CatalogDataStore from '../../stores/CatalogDataStore' import _isInt from 'validator/lib/isInt' import _toInt from 'validator/lib/toInt' import _isFloat from 'validator/lib/isFloat' import _toFloat from 'validator/lib/toFloat' import _trim from 'validator/lib/trim' import _isIP from 'validator/lib/isIP' import { startEditing, endEditing, onFocusPropertyFormInputElement } from './EditDescriptorUtils' import Select from './Select' function validateRequired(isRequired, value) { value = value.trim(); return isRequired && !value ? { success: false, message: "A value is required." } : { success: true, value: null }; } function editorExitHandler(isValueRequired, onExit, onError, event) { const value = event.target.value; const result = validateRequired(isValueRequired, value); onExit && onExit(result); endEditing(); } function Enumeration(props) { const { id, property, title, readOnly, onChange, onError, onExit } = props; let value = props.value; const enumeration = Property.getEnumeration(property, value); const hasDefaultValue = !!property['default-value']; const required = property.mandatory || hasDefaultValue; if (!value && hasDefaultValue) { value = property['default-value']; } return ( ); } function Boolean(props) { const { id, property, title, readOnly, onChange, onError, onExit } = props; let value = props.value; const hasDefaultValue = !!property['default-value']; const required = property.mandatory || hasDefaultValue; const typeOfValue = typeof value; if (typeOfValue === 'number' || typeOfValue === 'boolean') { value = value ? 'TRUE' : 'FALSE'; } else if (value) { value = value.toUpperCase(); } else { if (hasDefaultValue) { value = ('' + property['default-value']).toUpperCase(); } } const options = [ { name: "TRUE", value: 'TRUE' }, { name: "FALSE", value: 'FALSE' } ] return ( ); } function getValidator(property) { function validateInteger(constraints, value) { return _isInt(value, constraints) ? { success: true, value: _toInt(value) } : { success: false, value, message: "The value is not an integer or does not meet the property constraints." }; } function validateDecimal(constraints, value) { return _isFloat(value, constraints) ? { success: true, value: _toFloat(value) } : { success: false, value, message: "The value is not a decimal number or does not meet the property constraints." }; } function validateProperty(validator, errorMessage, value) { return validator(value) ? { success: true, value } : { success: false, value, message: errorMessage }; } const name = property.name; if (name === 'ip-address' || name.endsWith('-ip-address')) { return validateProperty.bind(null, _isIP, "The value is not a valid ip address.") } switch (property['data-type']) { case 'int8': return validateInteger.bind(null, { min: -128, max: 127 }); case 'int16': return validateInteger.bind(null, { min: -32768, max: 32767 }); case 'int32': return validateInteger.bind(null, { min: -2147483648, max: 2147483647 }); case 'int64': return validateInteger.bind(null, null); case 'uint8': return validateInteger.bind(null, { min: 0, max: 255 }); case 'uint16': return validateInteger.bind(null, { min: 0, max: 65535 }); case 'uint32': return validateInteger.bind(null, { min: 0, max: 4294967295 }); case 'uint64': return validateInteger.bind(null, { min: 0 }); case 'decimal64': return validateDecimal.bind(null, null) case 'string': default: return function (value) { return { success: true, value } }; } } function messageTemplate(strings, ...keys) { return (function (...vars) { let helpInfo = vars.reduce((o, info) => Object.assign(o, info), {}); return keys.reduce((s, key, i) => { return s + helpInfo[key] + strings[i + 1]; }, strings[0]); }); } const errorMessage = messageTemplate`"${'value'}" is ${'error'}. ${'message'}`; class Input extends React.Component { constructor(props) { super(props); let originalValue = props.value ? props.value : null; // normalize empty value this.state = { originalValue }; } componentWillReceiveProps(nextProps) { const { value } = nextProps if (value !== this.state.originalValue) { let originalValue = value ? value : null; // normalize empty value this.setState({ originalValue }) } } render() { const { id, property, value, title, readOnly, onChange, onError, onExit } = this.props; const { originalValue } = this.state; const isGuid = Property.isGuid(property); const className = ClassNames(property.name + '-input', { '-is-guid': isGuid, '-is-required': required }); const placeholder = property.name; const required = property.mandatory; const validator = getValidator(property); function handleValueChanged(newValue) { newValue = newValue.trim(); const result = !newValue ? validateRequired(required, newValue) : validator(newValue); result.success ? (originalValue !== result.value) && onChange(result.value) : onError(result.message); } const changeHandler = _debounce(handleValueChanged, 2000); function onInputChange(e) { e.preventDefault(); changeHandler(_trim(e.target.value)); } function onBlur(e) { changeHandler.cancel(); const value = _trim(e.target.value); const result = !value ? validateRequired(required, value) : validator(value); if (result.success) { // just in case we missed it by cancelling the debouncer (originalValue !== result.value) && onChange(result.value); } onExit(result); } if (property['preserve-line-breaks']) { return (