diff --git a/jbrowse/src/client/JBrowse/VariantSearch/components/FilterForm.tsx b/jbrowse/src/client/JBrowse/VariantSearch/components/FilterForm.tsx index f24b2e857..6a8346ea0 100644 --- a/jbrowse/src/client/JBrowse/VariantSearch/components/FilterForm.tsx +++ b/jbrowse/src/client/JBrowse/VariantSearch/components/FilterForm.tsx @@ -9,7 +9,8 @@ import FormControl from '@mui/material/FormControl'; import InputLabel from '@mui/material/InputLabel'; import CardActions from '@mui/material/CardActions'; import Card from '@mui/material/Card'; -import { FieldModel, Filter, getOperatorsForField, searchStringToInitialFilters } from '../../utils'; +import { FieldModel, Filter, searchStringToInitialFilters } from '../../utils'; +import { OperatorKey, OperatorRegistry } from '../operators'; import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown'; import { Box, Menu } from '@mui/material'; import { styled } from '@mui/material/styles'; @@ -77,19 +78,22 @@ const SubmitAndExternal = styled('div')(({ theme }) => ({ const FilterForm = (props: FilterFormProps ) => { const { handleQuery, setFilters, handleClose, fieldTypeInfo, allowedGroupNames, promotedFilters } = props - const [filters, localSetFilters] = useState(searchStringToInitialFilters(fieldTypeInfo.map((x) => x.name))); + const initial = searchStringToInitialFilters(fieldTypeInfo.map(x => x.name)) + const [filters, localSetFilters] = useState( + initial.length ? initial : [ new Filter('', OperatorKey.None, '') ] + ) const [highlightedInputs, setHighlightedInputs] = useState<{ [index: number]: { field: boolean, operator: boolean, value: boolean } }>({}); const [commonFilterMenuOpen, setCommonFilterMenuOpen] = useState(false) const buttonRef = React.useRef(null); const handleAddFilter = () => { - localSetFilters([...filters, new Filter()]); + localSetFilters([...filters, new Filter('', OperatorKey.None, '')]); }; const handleRemoveFilter = (index) => { // If it's the last filter, just reset its values to default empty values if (filters.length === 1) { - localSetFilters([new Filter()]); + localSetFilters([new Filter('', OperatorKey.None, '')]); } else { // Otherwise, remove the filter normally localSetFilters( @@ -100,64 +104,81 @@ const FilterForm = (props: FilterFormProps ) => { } }; - const handleFilterChange = (index, key, value) => { - const newFilters = filters.map((filter, i) => { - if (i === index) { - const updatedFilter = Object.assign(new Filter(), { ...filter, [key]: value }); - - if (key === "operator") { - if (value === "is empty" || value === "is not empty") { - updatedFilter.value = ''; - } - - if (value === "equals one of" || filter.operator === "equals one of") { - updatedFilter.value = ''; - } - } + const handleFilterChange = ( + index: number, + key: 'field' | 'operator' | 'value', + value: any + ) => { + const newFilters = filters.map((filter, i) => { + if (i !== index) return filter; + + const updatedFilter = Object.assign( + new Filter('', OperatorKey.None, ''), + { ...filter, [key]: value } + ); + + if (key === 'field') { + const fieldInfo = fieldTypeInfo.find(f => f.name === value); + const defaultOp = fieldInfo?.getDefaultOperator() ?? OperatorRegistry[OperatorKey.None]; + updatedFilter.operator = defaultOp; + updatedFilter.value = ''; + } + else if (key === 'operator') { + updatedFilter.operator = OperatorRegistry[value as OperatorKey]; + updatedFilter.value = ''; + } - return updatedFilter; - } - return filter; - }); + return updatedFilter; + }); - localSetFilters(newFilters); + localSetFilters(newFilters); }; + const handleSubmit = (event) => { - event.preventDefault(); - const highlightedInputs = {}; - - filters.forEach((filter, index) => { - highlightedInputs[index] = { field: false, operator: false, value: false }; - - filter.field = filter.field ?? ''; - filter.operator = filter.operator ?? ''; - filter.value = filter.value ?? ''; - - if (filter.field === '') { - highlightedInputs[index].field = true; - } - - if (filter.operator === '') { - highlightedInputs[index].operator = true; - } - - if (filter.operator === 'is empty' || filter.operator === 'is not empty') { - filter.value = ''; - } else if (filter.value === '') { - highlightedInputs[index].value = true; - } - }); - - const isSingleEmptyFilter = filters.length === 1 && !filters[0].field && !filters[0].operator && !filters[0].value; - - setHighlightedInputs(highlightedInputs); - if (isSingleEmptyFilter || !Object.values(highlightedInputs).some(v => (v as any).field || (v as any).operator || (v as any).value)) { - handleQuery(filters); - setFilters(filters); - handleClose(); + event.preventDefault() + const highlighted: Record = {} + + filters.forEach((filter, i) => { + highlighted[i] = { field: false, operator: false, value: false } + + filter.field = filter.field ?? '' + filter.value = filter.value ?? '' + + if (!filter.field) { + highlighted[i].field = true } - }; + + if (!filter.operator.key) { + highlighted[i].operator = true + } + + if ( + filter.operator.key === OperatorKey.IsEmpty || + filter.operator.key === OperatorKey.IsNotEmpty + ) { + filter.value = '' + } else if (!filter.value) { + highlighted[i].value = true + } + }) + + const isSingleEmpty = + filters.length === 1 && + !filters[0].field && + !filters[0].operator.key && + !filters[0].value + + setHighlightedInputs(highlighted) + if ( + isSingleEmpty || + !Object.values(highlighted).some(v => v.field || v.operator || v.value) + ) { + handleQuery(filters) + setFilters(filters) + handleClose?.() + } + } const handleMenuClose = () => { setCommonFilterMenuOpen(false) @@ -241,34 +262,33 @@ const FilterForm = (props: FilterFormProps ) => { /> - - Operator - + + Operator + - {filter.operator === "equals one of" ? ( + {filter.operator.key === OperatorKey.EqualsOneOf ? ( Value