import { makeStyles, Theme, TextField, IconButton } from '@material-ui/core'; import { FC, Fragment, ReactElement, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import CustomButton from '../../components/customButton/CustomButton'; import CustomSelector from '../../components/customSelector/CustomSelector'; import icons from '../../components/icons/Icons'; import { generateId } from '../../utils/Common'; import { getCreateFieldType } from '../../utils/Format'; import { checkEmptyValid, checkRange, getCheckResult, } from '../../utils/Validation'; import { ALL_OPTIONS, AUTO_ID_OPTIONS, PRIMARY_FIELDS_OPTIONS, VECTOR_FIELDS_OPTIONS, } from './Constants'; import { CreateFieldsProps, CreateFieldType, DataTypeEnum, Field, } from './Types'; const useStyles = makeStyles((theme: Theme) => ({ optionalWrapper: { width: '100%', paddingRight: theme.spacing(1), maxHeight: '240px', overflowY: 'auto', }, rowWrapper: { display: 'flex', flexWrap: 'nowrap', alignItems: 'center', // only Safari 14.1+ support flexbox gap // gap: '10px', width: '100%', '& > *': { marginLeft: '10px', }, '& .dimension': { maxWidth: '160px', }, }, input: { fontSize: '14px', }, primaryInput: { maxWidth: '160px', '&:first-child': { marginLeft: 0, }, }, select: { width: '160px', marginBottom: '22px', '&:first-child': { marginLeft: 0, }, }, descInput: { minWidth: '100px', flexGrow: 1, }, btnTxt: { textTransform: 'uppercase', }, iconBtn: { marginLeft: 0, padding: 0, width: '20px', height: '20px', }, mb2: { marginBottom: theme.spacing(2), }, helperText: { lineHeight: '20px', margin: theme.spacing(0.25, 0), marginLeft: '12px', }, })); type inputType = { label: string; value: string | number | null; handleChange?: (value: string) => void; className?: string; inputClassName?: string; isReadOnly?: boolean; validate?: (value: string | number | null) => string; type?: 'number' | 'text'; }; const CreateFields: FC = ({ fields, setFields, setAutoID, autoID, setFieldsValidation, }) => { const { t: collectionTrans } = useTranslation('collection'); const { t: warningTrans } = useTranslation('warning'); const classes = useStyles(); const AddIcon = icons.add; const RemoveIcon = icons.remove; const { requiredFields, optionalFields } = useMemo( () => fields.reduce( (acc, field) => { const createType: CreateFieldType = getCreateFieldType(field); const requiredTypes: CreateFieldType[] = [ 'primaryKey', 'defaultVector', ]; const key = requiredTypes.includes(createType) ? 'requiredFields' : 'optionalFields'; acc[key].push({ ...field, createType, }); return acc; }, { requiredFields: [] as Field[], optionalFields: [] as Field[], } ), [fields] ); const getSelector = ( type: 'all' | 'vector', label: string, value: number, onChange: (value: DataTypeEnum) => void, options?: any[] ) => { return ( ) => { onChange(e.target.value as DataTypeEnum); }} value={value} variant="filled" label={label} /> ); }; const getInput = (data: inputType) => { const { label, value, handleChange = () => {}, className = '', inputClassName = '', isReadOnly = false, validate = (value: string | number | null) => ' ', type = 'text', } = data; return ( ) => { handleChange(e.target.value as string); }} variant="filled" className={className} InputProps={{ classes: { input: inputClassName, }, }} disabled={isReadOnly} error={validate(value) !== ' '} helperText={validate(value)} FormHelperTextProps={{ className: classes.helperText, }} defaultValue={value} type={type} /> ); }; const generateFieldName = (field: Field) => { return getInput({ label: collectionTrans('fieldName'), value: field.name, handleChange: (value: string) => { const isValid = checkEmptyValid(value); setFieldsValidation(v => v.map(item => item.id === field.id! ? { ...item, name: isValid } : item ) ); changeFields(field.id!, 'name', value); }, validate: (value: any) => { if (value === null) return ' '; const isValid = checkEmptyValid(value); return isValid ? ' ' : warningTrans('required', { name: collectionTrans('fieldName') }); }, }); }; const generateDesc = (field: Field) => { return getInput({ label: collectionTrans('description'), value: field.description, handleChange: (value: string) => changeFields(field.id!, 'description', value), className: classes.descInput, }); }; const generateDimension = (field: Field) => { const validateDimension = (value: string) => { const isPositive = getCheckResult({ value, rule: 'positiveNumber', }); const isMutiple = getCheckResult({ value, rule: 'multiple', extraParam: { multipleNumber: 8, }, }); if (field.data_type === DataTypeEnum.BinaryVector) { return { isMutiple, isPositive, }; } return { isPositive, }; }; return getInput({ label: collectionTrans('dimension'), value: field.dimension as number, handleChange: (value: string) => { const { isPositive, isMutiple } = validateDimension(value); const isValid = field.data_type === DataTypeEnum.BinaryVector ? !!isMutiple && isPositive : isPositive; changeFields(field.id!, 'dimension', `${value}`); setFieldsValidation(v => v.map(item => item.id === field.id! ? { ...item, dimension: isValid } : item ) ); }, type: 'number', validate: (value: any) => { const { isPositive, isMutiple } = validateDimension(value); if (isMutiple === false) { return collectionTrans('dimensionMutipleWarning'); } return isPositive ? ' ' : collectionTrans('dimensionPositiveWarning'); }, }); }; const generateMaxLength = (field: Field) => { return getInput({ label: 'Max Length', value: field.max_length!, type: 'number', handleChange: (value: string) => changeFields(field.id!, 'max_length', value), validate: (value: any) => { if (value === null) return ' '; const isEmptyValid = checkEmptyValid(value); const isRangeValid = checkRange({ value, min: 1, max: 65535, type: 'number', }); return !isEmptyValid ? warningTrans('required', { name: collectionTrans('fieldName'), }) : !isRangeValid ? warningTrans('range', { min: 1, max: 65535, }) : ' '; }, }); }; const changeFields = (id: string, key: string, value: any) => { const newFields = fields.map(f => { if (f.id !== id) { return f; } return { ...f, [key]: value, }; }); setFields(newFields); }; const handleAddNewField = () => { const id = generateId(); const newDefaultItem: Field = { name: null, data_type: DataTypeEnum.Int16, is_primary_key: false, description: '', isDefault: false, dimension: '128', max_length: null, id, }; const newValidation = { id, name: false, dimension: true, }; setFields([...fields, newDefaultItem]); setFieldsValidation(v => [...v, newValidation]); }; const handleRemoveField = (field: Field) => { const newFields = fields.filter(f => f.id !== field.id); setFields(newFields); setFieldsValidation(v => v.filter(item => item.id !== field.id)); }; const generatePrimaryKeyRow = ( field: Field, autoID: boolean ): ReactElement => { const isVarChar = field.data_type === DataTypeEnum.VarChar; const autoIdOff = isVarChar ? 'false' : autoID ? 'true' : 'false'; return (
{getSelector( 'vector', `Primary ${collectionTrans('fieldType')} `, field.data_type, (value: DataTypeEnum) => { changeFields(field.id!, 'data_type', value); if (value === DataTypeEnum.VarChar) { setAutoID(false); } }, PRIMARY_FIELDS_OPTIONS )} {generateFieldName(field)} ) => { const autoId = e.target.value === 'true'; setAutoID(autoId); }} variant="filled" wrapperClass={classes.select} disabled={isVarChar} /> {isVarChar && generateMaxLength(field)} {generateDesc(field)}
); }; const generateDefaultVectorRow = (field: Field): ReactElement => { return ( <>
{getSelector( 'vector', collectionTrans('fieldType'), field.data_type, (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value) )} {generateFieldName(field)} {generateDimension(field)} {generateDesc(field)}
{collectionTrans('newBtn')} ); }; const generateNumberRow = (field: Field): ReactElement => { const isVarChar = field.data_type === DataTypeEnum.VarChar; return (
handleRemoveField(field)} classes={{ root: classes.iconBtn }} aria-label="delete" > {generateFieldName(field)} {getSelector( 'all', collectionTrans('fieldType'), field.data_type, (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value) )} {isVarChar && generateMaxLength(field)} {generateDesc(field)}
); }; const generateVectorRow = (field: Field) => { return (
{generateFieldName(field)} {getSelector( 'all', collectionTrans('fieldType'), field.data_type, (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value) )} {generateDimension(field)} {generateDesc(field)}
); }; const generateRequiredFieldRow = (field: Field, autoID: boolean) => { // required type is primaryKey or defaultVector if (field.createType === 'primaryKey') { return generatePrimaryKeyRow(field, autoID); } // use defaultVector as default return type return generateDefaultVectorRow(field); }; const generateOptionalFieldRow = (field: Field) => { // optional type is vector or number if (field.createType === 'vector') { return generateVectorRow(field); } // use number as default createType return generateNumberRow(field); }; return ( <> {requiredFields.map((field, index) => ( {generateRequiredFieldRow(field, autoID)} ))}
{optionalFields.map((field, index) => ( {generateOptionalFieldRow(field)} ))}
); }; export default CreateFields;