feat(ui): Add fusejs to NodeSearch

This commit is contained in:
blessedcoolant 2023-04-23 15:14:44 +12:00
parent ff891b1ff2
commit 3f334d9e5e
3 changed files with 68 additions and 30 deletions

View File

@ -54,6 +54,7 @@
"dateformat": "^5.0.3", "dateformat": "^5.0.3",
"formik": "^2.2.9", "formik": "^2.2.9",
"framer-motion": "^9.0.4", "framer-motion": "^9.0.4",
"fuse.js": "^6.6.2",
"i18next": "^22.4.10", "i18next": "^22.4.10",
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^2.1.1", "i18next-http-backend": "^2.1.1",

View File

@ -5,6 +5,7 @@ import IAIInput from 'common/components/IAIInput';
import { Panel } from 'reactflow'; import { Panel } from 'reactflow';
import { map } from 'lodash'; import { map } from 'lodash';
import { import {
ChangeEvent,
FocusEvent, FocusEvent,
KeyboardEvent, KeyboardEvent,
memo, memo,
@ -19,6 +20,8 @@ import { useBuildInvocation } from 'features/nodes/hooks/useBuildInvocation';
import { makeToast } from 'features/system/hooks/useToastWatcher'; import { makeToast } from 'features/system/hooks/useToastWatcher';
import { addToast } from 'features/system/store/systemSlice'; import { addToast } from 'features/system/store/systemSlice';
import { nodeAdded } from '../../store/nodesSlice'; import { nodeAdded } from '../../store/nodesSlice';
import Fuse from 'fuse.js';
import { InvocationTemplate } from 'features/nodes/types/types';
interface NodeListItemProps { interface NodeListItemProps {
title: string; title: string;
@ -55,6 +58,9 @@ const NodeSearch = () => {
); );
const nodes = map(invocationTemplates); const nodes = map(invocationTemplates);
const [filteredNodes, setFilteredNodes] = useState<
Fuse.FuseResult<InvocationTemplate>[]
>([]);
const buildInvocation = useBuildInvocation(); const buildInvocation = useBuildInvocation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -64,6 +70,21 @@ const NodeSearch = () => {
const [focusedIndex, setFocusedIndex] = useState<number>(-1); const [focusedIndex, setFocusedIndex] = useState<number>(-1);
const nodeSearchRef = useRef<HTMLDivElement>(null); const nodeSearchRef = useRef<HTMLDivElement>(null);
const fuseOptions = {
findAllMatches: true,
threshold: 0,
ignoreLocation: true,
keys: ['title', 'type', 'tags'],
};
const fuse = new Fuse(nodes, fuseOptions);
const findNode = (e: ChangeEvent<HTMLInputElement>) => {
setSearchText(e.target.value);
setFilteredNodes(fuse.search(e.target.value));
setShowNodeList(true);
};
const addNode = useCallback( const addNode = useCallback(
(nodeType: AnyInvocationType) => { (nodeType: AnyInvocationType) => {
const invocation = buildInvocation(nodeType); const invocation = buildInvocation(nodeType);
@ -85,8 +106,24 @@ const NodeSearch = () => {
const renderNodeList = () => { const renderNodeList = () => {
const nodeListToRender: ReactNode[] = []; const nodeListToRender: ReactNode[] = [];
nodes.forEach(({ title, description, type }, index) => { if (searchText.length > 0) {
if (title.toLowerCase().includes(searchText)) { filteredNodes.forEach(({ item }, index) => {
const { title, description, type } = item;
if (title.toLowerCase().includes(searchText)) {
nodeListToRender.push(
<NodeListItem
key={index}
title={title}
description={description}
type={type}
isSelected={focusedIndex === index}
addNode={addNode}
/>
);
}
});
} else {
nodes.forEach(({ title, description, type }, index) => {
nodeListToRender.push( nodeListToRender.push(
<NodeListItem <NodeListItem
key={index} key={index}
@ -97,26 +134,11 @@ const NodeSearch = () => {
addNode={addNode} addNode={addNode}
/> />
); );
} else { });
<NodeListItem }
key={index}
title={title}
description={description}
type={type}
isSelected={focusedIndex === index}
addNode={addNode}
/>;
}
});
return ( return (
<Flex <Flex flexDirection="column" background="base.900" borderRadius={6}>
flexDirection="column"
background="base.900"
borderRadius={6}
maxHeight={400}
overflowY="scroll"
>
{nodeListToRender} {nodeListToRender}
</Flex> </Flex>
); );
@ -128,12 +150,21 @@ const NodeSearch = () => {
if (key === 'ArrowDown') { if (key === 'ArrowDown') {
setShowNodeList(true); setShowNodeList(true);
nextIndex = (focusedIndex + 1) % nodes.length; if (searchText.length > 0) {
nextIndex = (focusedIndex + 1) % filteredNodes.length;
} else {
nextIndex = (focusedIndex + 1) % nodes.length;
}
} }
if (key === 'ArrowUp') { if (key === 'ArrowUp') {
setShowNodeList(true); setShowNodeList(true);
nextIndex = (focusedIndex + nodes.length - 1) % nodes.length; if (searchText.length > 0) {
nextIndex =
(focusedIndex + filteredNodes.length - 1) % filteredNodes.length;
} else {
nextIndex = (focusedIndex + filteredNodes.length - 1) % nodes.length;
}
} }
// # TODO Handle Blur // # TODO Handle Blur
@ -141,7 +172,14 @@ const NodeSearch = () => {
// } // }
if (key === 'Enter') { if (key === 'Enter') {
const selectedNodeType = nodes[focusedIndex].type; let selectedNodeType: AnyInvocationType;
if (searchText.length > 0) {
selectedNodeType = filteredNodes[focusedIndex].item.type;
} else {
selectedNodeType = nodes[focusedIndex].type;
}
addNode(selectedNodeType); addNode(selectedNodeType);
setShowNodeList(false); setShowNodeList(false);
} }
@ -163,13 +201,7 @@ const NodeSearch = () => {
onBlur={searchInputBlurHandler} onBlur={searchInputBlurHandler}
ref={nodeSearchRef} ref={nodeSearchRef}
> >
<IAIInput <IAIInput value={searchText} onChange={findNode} />
value={searchText}
onChange={(e) => {
setSearchText(e.target.value);
setShowNodeList(true);
}}
/>
{showNodeList && renderNodeList()} {showNodeList && renderNodeList()}
</Flex> </Flex>
</Panel> </Panel>

View File

@ -3454,6 +3454,11 @@ functions-have-names@^1.2.2:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
fuse.js@^6.6.2:
version "6.6.2"
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-6.6.2.tgz#fe463fed4b98c0226ac3da2856a415576dc9a111"
integrity sha512-cJaJkxCCxC8qIIcPBF9yGxY0W/tVZS3uEISDxhYIdtk8OL93pe+6Zj7LjCqVV4dzbqcriOZ+kQ/NE4RXZHsIGA==
get-amd-module-type@^3.0.0: get-amd-module-type@^3.0.0:
version "3.0.2" version "3.0.2"
resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.2.tgz#46550cee2b8e1fa4c3f2c8a5753c36990aa49ab0" resolved "https://registry.yarnpkg.com/get-amd-module-type/-/get-amd-module-type-3.0.2.tgz#46550cee2b8e1fa4c3f2c8a5753c36990aa49ab0"