Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit aec4985

Browse files
nvollrothAclrianDavidTen1lars-reimann
authored
feat: Save filters (#667)
* working base on behalf of Co-Authored-By: Aclrian <[email protected]> Co-Authored-By: DavidTen1 <[email protected]> * end of day commit and fix linter error on behalf of Co-Authored-By: Aclrian <[email protected]> Co-Authored-By: DavidTen1 <[email protected]> * fix: name & filter will be saved * disable buttons on already take names and filters * style: apply automatic fixes of linters * add default saved filter * style: apply automatic fixes of linters * refactor(gui): extract filter controls from menu bar * refactor(gui): move filter components to new feature * refactor(gui): move filter model classes into filter feature * refactor(gui): simplify persistence component * feat(gui): add means to remove filter again * refactor(gui): remove unused reducer * refactor(gui): make filter name local state of the filter dialog (only component that needs it) * fix(gui): don't allow saving of filters when validation of name fails * feat(gui): improved validation messages * style: apply automatic fixes of linters * feat(gui): disable loading of filters when none exist Co-authored-by: Aclrian <[email protected]> Co-authored-by: DavidTen1 <[email protected]> Co-authored-by: Aclrian <[email protected]> Co-authored-by: Lars Reimann <[email protected]> Co-authored-by: lars-reimann <[email protected]>
1 parent 2a41e8b commit aec4985

32 files changed

+328
-132
lines changed

api-editor/gui/src/app/App.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ import {
5050
selectShowAPIImportDialog,
5151
selectShowUsageImportDialog,
5252
selectUI,
53+
selectShowAddFilterDialog,
5354
} from '../features/ui/uiSlice';
5455
import { initializeUsages, persistUsages, selectUsages } from '../features/usages/usageSlice';
5556
import { initializePythonPackage, selectRawPythonPackage } from '../features/packageData/apiSlice';
@@ -64,9 +65,10 @@ import { OptionalBatchForm } from '../features/annotations/batchforms/OptionalBa
6465
import { RemoveBatchForm } from '../features/annotations/batchforms/RemoveBatchForm';
6566
import { MoveBatchForm } from '../features/annotations/batchforms/MoveBatchForm';
6667
import { PythonPackage } from '../features/packageData/model/PythonPackage';
67-
import { AbstractPythonFilter } from '../features/packageData/model/filters/AbstractPythonFilter';
68+
import { AbstractPythonFilter } from '../features/filter/model/AbstractPythonFilter';
6869
import { UsageCountStore } from '../features/usages/model/UsageCountStore';
6970
import { PythonDeclaration } from '../features/packageData/model/PythonDeclaration';
71+
import { SaveFilterDialog } from '../features/filter/SaveFilterDialog';
7072

7173
export const App: React.FC = function () {
7274
useIndexedDB();
@@ -90,6 +92,7 @@ export const App: React.FC = function () {
9092
const showAPIImportDialog = useAppSelector(selectShowAPIImportDialog);
9193
const showUsagesImportDialog = useAppSelector(selectShowUsageImportDialog);
9294
const batchMode = useAppSelector(selectBatchMode);
95+
const showAddFilterDialog = useAppSelector(selectShowAddFilterDialog);
9396

9497
return (
9598
<>
@@ -200,6 +203,7 @@ export const App: React.FC = function () {
200203
{showAnnotationImportDialog && <AnnotationImportDialog />}
201204
{showAPIImportDialog && <PackageDataImportDialog />}
202205
{showUsagesImportDialog && <UsageImportDialog />}
206+
{showAddFilterDialog && <SaveFilterDialog />}
203207
</Grid>
204208

205209
<Modal

api-editor/gui/src/common/MenuBar.tsx

Lines changed: 2 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,12 @@ import {
1313
MenuList,
1414
MenuOptionGroup,
1515
Spacer,
16-
Text as ChakraText,
1716
useColorMode,
1817
} from '@chakra-ui/react';
1918
import React from 'react';
2019
import { FaChevronDown } from 'react-icons/fa';
2120
import { useAppDispatch, useAppSelector } from '../app/hooks';
2221
import { selectAnnotationStore } from '../features/annotations/annotationSlice';
23-
import { FilterHelpButton } from './FilterHelpButton';
2422
import {
2523
BatchMode,
2624
HeatMapMode,
@@ -36,9 +34,8 @@ import {
3634
} from '../features/ui/uiSlice';
3735
import { DeleteAllAnnotations } from './DeleteAllAnnotations';
3836
import { GenerateAdapters } from './GenerateAdapters';
39-
import { FilterInput } from './FilterInput';
40-
import { selectNumberOfMatchedNodes } from '../features/packageData/apiSlice';
4137
import { useNavigate } from 'react-router-dom';
38+
import { FilterControls } from '../features/filter/FilterControls';
4239

4340
interface MenuBarProps {
4441
displayInferErrors: (errors: string[]) => void;
@@ -212,25 +209,7 @@ export const MenuBar: React.FC<MenuBarProps> = function ({ displayInferErrors })
212209

213210
<Spacer />
214211

215-
<HStack>
216-
<MatchCount />
217-
<FilterInput />
218-
<FilterHelpButton />
219-
</HStack>
212+
<FilterControls />
220213
</Flex>
221214
);
222215
};
223-
224-
const MatchCount = function () {
225-
const count = useAppSelector(selectNumberOfMatchedNodes);
226-
let text;
227-
if (count === 0) {
228-
text = 'No matches';
229-
} else if (count === 1) {
230-
text = '1 match';
231-
} else {
232-
text = `${count} matches`;
233-
}
234-
235-
return <ChakraText fontWeight="bold">{text}</ChakraText>;
236-
};

api-editor/gui/src/features/actionBar/ActionBar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { useAppDispatch, useAppSelector } from '../../app/hooks';
55
import { PythonPackage } from '../packageData/model/PythonPackage';
66
import { selectFilteredPythonPackage, selectFlatSortedDeclarationList } from '../packageData/apiSlice';
77
import { Optional } from '../../common/util/types';
8-
import { AbstractPythonFilter } from '../packageData/model/filters/AbstractPythonFilter';
8+
import { AbstractPythonFilter } from '../filter/model/AbstractPythonFilter';
99
import { AnnotationStore, redo, selectAnnotationStore, undo } from '../annotations/annotationSlice';
1010
import { selectFilter, setAllCollapsedInTreeView, setAllExpandedInTreeView } from '../ui/uiSlice';
1111
import { PythonDeclaration } from '../packageData/model/PythonDeclaration';
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { FilterInput } from './FilterInput';
2+
import { FilterHelpButton } from './FilterHelpButton';
3+
import React from 'react';
4+
import { HStack } from '@chakra-ui/react';
5+
import { MatchCount } from './FilterMatchCount';
6+
import { FilterPersistence } from './FilterPersistence';
7+
8+
export const FilterControls = function () {
9+
return (
10+
<HStack>
11+
<FilterPersistence />
12+
<MatchCount />
13+
<FilterInput />
14+
<FilterHelpButton />
15+
</HStack>
16+
);
17+
};
File renamed without changes.

api-editor/gui/src/common/FilterInput.tsx renamed to api-editor/gui/src/features/filter/FilterInput.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
UnorderedList,
1313
} from '@chakra-ui/react';
1414
import React from 'react';
15-
import { useAppDispatch, useAppSelector } from '../app/hooks';
16-
import { selectFilterString, setFilterString } from '../features/ui/uiSlice';
17-
import { isValidFilterToken } from '../features/packageData/model/filters/filterFactory';
15+
import { useAppDispatch, useAppSelector } from '../../app/hooks';
16+
import { selectFilterString, setFilterString } from '../ui/uiSlice';
17+
import { isValidFilterToken } from './model/filterFactory';
1818

1919
export const FilterInput: React.FC = function () {
2020
const dispatch = useAppDispatch();
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { useAppSelector } from '../../app/hooks';
2+
import { selectNumberOfMatchedNodes } from '../packageData/apiSlice';
3+
import { Text as ChakraText } from '@chakra-ui/react';
4+
import React from 'react';
5+
6+
export const MatchCount = function () {
7+
const count = useAppSelector(selectNumberOfMatchedNodes);
8+
9+
let text;
10+
if (count === 0) {
11+
text = 'No matches';
12+
} else if (count === 1) {
13+
text = '1 match';
14+
} else {
15+
text = `${count} matches`;
16+
}
17+
18+
return <ChakraText fontWeight="bold">{text}</ChakraText>;
19+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import { Box, Button, Icon, Menu, MenuButton, MenuGroup, MenuItem, MenuList } from '@chakra-ui/react';
3+
import { FaChevronDown } from 'react-icons/fa';
4+
import { useAppDispatch, useAppSelector } from '../../app/hooks';
5+
import {
6+
removeFilter,
7+
selectFilterList,
8+
selectFilterString,
9+
setFilterString,
10+
toggleAddFilterDialog,
11+
} from '../ui/uiSlice';
12+
import { isValidFilterToken } from './model/filterFactory';
13+
import { isEmptyList } from '../../common/util/listOperations';
14+
15+
export const FilterPersistence = function () {
16+
const dispatch = useAppDispatch();
17+
18+
const currentFilterString = useAppSelector(selectFilterString);
19+
const savedFilters = useAppSelector(selectFilterList);
20+
21+
const filterIsValid = currentFilterString.split(' ').every((token) => token === '' || isValidFilterToken(token));
22+
const alreadyIncluded = savedFilters.some((it) => {
23+
return it.filter === currentFilterString;
24+
});
25+
26+
return (
27+
<>
28+
{alreadyIncluded ? (
29+
<Button
30+
onClick={() => dispatch(removeFilter(currentFilterString))}
31+
isDisabled={!filterIsValid || !alreadyIncluded}
32+
>
33+
Remove Filter
34+
</Button>
35+
) : (
36+
<Button
37+
onClick={() => dispatch(toggleAddFilterDialog())}
38+
isDisabled={!filterIsValid || alreadyIncluded}
39+
>
40+
Save Filter
41+
</Button>
42+
)}
43+
44+
<Box>
45+
<Menu>
46+
<MenuButton
47+
as={Button}
48+
rightIcon={<Icon as={FaChevronDown} />}
49+
disabled={isEmptyList(savedFilters)}
50+
>
51+
Load Filter
52+
</MenuButton>
53+
<MenuList>
54+
<MenuGroup>
55+
{savedFilters.map(({ name, filter }) => (
56+
<MenuItem key={name + filter} onClick={() => dispatch(setFilterString(filter))}>
57+
{name}
58+
</MenuItem>
59+
))}
60+
</MenuGroup>
61+
</MenuList>
62+
</Menu>
63+
</Box>
64+
</>
65+
);
66+
};
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import {
2+
Button,
3+
Code,
4+
FormControl,
5+
FormErrorMessage,
6+
FormLabel,
7+
Heading,
8+
HStack,
9+
Input,
10+
Modal,
11+
ModalBody,
12+
ModalContent,
13+
ModalFooter,
14+
ModalHeader,
15+
ModalOverlay,
16+
} from '@chakra-ui/react';
17+
import React, { useState } from 'react';
18+
import { useAppDispatch, useAppSelector } from '../../app/hooks';
19+
import { addFilter, selectFilterList, selectFilterString, toggleAddFilterDialog } from '../ui/uiSlice';
20+
21+
export const SaveFilterDialog: React.FC = function () {
22+
const dispatch = useAppDispatch();
23+
const filter = useAppSelector(selectFilterString);
24+
const savedFilters = useAppSelector(selectFilterList);
25+
const [filterName, setFilterName] = useState('');
26+
27+
const alreadyIncluded: boolean = savedFilters.some((it) => {
28+
return it.name === filterName;
29+
});
30+
31+
const submit = () => {
32+
if (filterName !== '' && !alreadyIncluded) {
33+
dispatch(addFilter({ filter, name: filterName }));
34+
dispatch(toggleAddFilterDialog());
35+
}
36+
};
37+
const close = () => {
38+
dispatch(toggleAddFilterDialog());
39+
};
40+
41+
return (
42+
<Modal onClose={close} isOpen size="xl">
43+
<ModalOverlay />
44+
<ModalContent>
45+
<ModalHeader>
46+
<Heading>Save Filter</Heading>
47+
</ModalHeader>
48+
<ModalBody>
49+
<FormControl isInvalid={alreadyIncluded || filterName.trim() === ''}>
50+
<FormLabel htmlFor="newFilterName">
51+
Name for the current filter <Code>{filter}</Code>:
52+
</FormLabel>
53+
<Input
54+
type="text"
55+
id="newFilterName"
56+
value={filterName}
57+
onChange={(event) => setFilterName(event.target.value)}
58+
spellCheck={false}
59+
/>
60+
{alreadyIncluded && (
61+
<FormErrorMessage>A filter with this name is saved already.</FormErrorMessage>
62+
)}
63+
{filterName.trim() === '' && (
64+
<FormErrorMessage>The filter name must not be blank.</FormErrorMessage>
65+
)}
66+
</FormControl>
67+
</ModalBody>
68+
<ModalFooter>
69+
<HStack spacing={4}>
70+
<Button
71+
colorScheme="blue"
72+
onClick={submit}
73+
isDisabled={alreadyIncluded || filterName.trim() === ''}
74+
>
75+
Submit
76+
</Button>
77+
<Button colorScheme="red" onClick={close}>
78+
Cancel
79+
</Button>
80+
</HStack>
81+
</ModalFooter>
82+
</ModalContent>
83+
</Modal>
84+
);
85+
};
Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
// noinspection UnnecessaryLocalVariableJS,DuplicatedCode
22

3-
import { PythonPackage } from '../PythonPackage';
4-
import { PythonParameter } from '../PythonParameter';
5-
import { PythonModule } from '../PythonModule';
6-
import { PythonClass } from '../PythonClass';
7-
import { PythonFunction } from '../PythonFunction';
3+
import { PythonClass } from '../../packageData/model/PythonClass';
4+
import { PythonFunction } from '../../packageData/model/PythonFunction';
5+
import { PythonModule } from '../../packageData/model/PythonModule';
6+
import { PythonParameter } from '../../packageData/model/PythonParameter';
7+
import { PythonDeclaration } from '../../packageData/model/PythonDeclaration';
8+
import { UsageCountStore } from '../../usages/model/UsageCountStore';
9+
import { PythonPackage } from '../../packageData/model/PythonPackage';
810
import { NameStringFilter } from './NameStringFilter';
9-
import { initialAnnotationStore as annotations } from '../../../annotations/annotationSlice';
10-
import { PythonDeclaration } from '../PythonDeclaration';
11-
import { UsageCountStore } from '../../../usages/model/UsageCountStore';
11+
import { initialAnnotationStore as annotations } from '../../annotations/annotationSlice';
1212

1313
let pythonPackage: PythonPackage;
1414

0 commit comments

Comments
 (0)