123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516 |
- 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<CreateFieldsProps> = ({
- 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 (
- <CustomSelector
- wrapperClass={classes.select}
- options={
- options
- ? options
- : type === 'all'
- ? ALL_OPTIONS
- : VECTOR_FIELDS_OPTIONS
- }
- onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
- 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 (
- <TextField
- label={label}
- // value={value}
- onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
- 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 (
- <div className={`${classes.rowWrapper}`}>
- {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)}
- <CustomSelector
- label={collectionTrans('autoId')}
- options={AUTO_ID_OPTIONS}
- value={autoIdOff}
- onChange={(e: React.ChangeEvent<{ value: unknown }>) => {
- const autoId = e.target.value === 'true';
- setAutoID(autoId);
- }}
- variant="filled"
- wrapperClass={classes.select}
- disabled={isVarChar}
- />
- {isVarChar && generateMaxLength(field)}
- {generateDesc(field)}
- </div>
- );
- };
- const generateDefaultVectorRow = (field: Field): ReactElement => {
- return (
- <>
- <div className={`${classes.rowWrapper}`}>
- {getSelector(
- 'vector',
- collectionTrans('fieldType'),
- field.data_type,
- (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
- )}
- {generateFieldName(field)}
- {generateDimension(field)}
- {generateDesc(field)}
- </div>
- <CustomButton onClick={handleAddNewField} className={classes.mb2}>
- <AddIcon />
- <span className={classes.btnTxt}>{collectionTrans('newBtn')}</span>
- </CustomButton>
- </>
- );
- };
- const generateNumberRow = (field: Field): ReactElement => {
- const isVarChar = field.data_type === DataTypeEnum.VarChar;
- return (
- <div className={`${classes.rowWrapper}`}>
- <IconButton
- onClick={() => handleRemoveField(field)}
- classes={{ root: classes.iconBtn }}
- aria-label="delete"
- >
- <RemoveIcon />
- </IconButton>
- {generateFieldName(field)}
- {getSelector(
- 'all',
- collectionTrans('fieldType'),
- field.data_type,
- (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
- )}
- {isVarChar && generateMaxLength(field)}
- {generateDesc(field)}
- </div>
- );
- };
- const generateVectorRow = (field: Field) => {
- return (
- <div className={`${classes.rowWrapper}`}>
- <IconButton classes={{ root: classes.iconBtn }} aria-label="delete">
- <RemoveIcon />
- </IconButton>
- {generateFieldName(field)}
- {getSelector(
- 'all',
- collectionTrans('fieldType'),
- field.data_type,
- (value: DataTypeEnum) => changeFields(field.id!, 'data_type', value)
- )}
- {generateDimension(field)}
- {generateDesc(field)}
- </div>
- );
- };
- 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) => (
- <Fragment key={index}>
- {generateRequiredFieldRow(field, autoID)}
- </Fragment>
- ))}
- <div className={classes.optionalWrapper}>
- {optionalFields.map((field, index) => (
- <Fragment key={index}>{generateOptionalFieldRow(field)}</Fragment>
- ))}
- </div>
- </>
- );
- };
- export default CreateFields;
|