Spaces:
Running
Running
import Fuse from 'fuse.js' | |
import { useEffect, useMemo, useRef, useState } from 'react'; | |
export type OpsOp = { | |
name: string | |
type: string | |
position: { x: number, y: number } | |
params: { name: string, default: any }[] | |
} | |
export type Catalog = { [op: string]: OpsOp }; | |
export type Catalogs = { [env: string]: Catalog }; | |
export default function (props: { boxes: Catalog, onCancel: any, onAdd: any, pos: { x: number, y: number } }) { | |
const searchBox = useRef(null as unknown as HTMLInputElement); | |
const [searchText, setSearchText] = useState(''); | |
const fuse = useMemo(() => new Fuse(Object.values(props.boxes), { | |
keys: ['name'] | |
}), [props.boxes]); | |
const hits: { item: OpsOp }[] = searchText ? fuse.search<OpsOp>(searchText) : Object.values(props.boxes).map(box => ({ item: box })); | |
const [selectedIndex, setSelectedIndex] = useState(0); | |
useEffect(() => searchBox.current.focus()); | |
function typed(text: string) { | |
setSearchText(text); | |
setSelectedIndex(Math.max(0, Math.min(selectedIndex, hits.length - 1))); | |
} | |
function onKeyDown(e: React.KeyboardEvent<HTMLInputElement>) { | |
if (e.key === 'ArrowDown') { | |
e.preventDefault(); | |
setSelectedIndex(Math.min(selectedIndex + 1, hits.length - 1)); | |
} else if (e.key === 'ArrowUp') { | |
e.preventDefault(); | |
setSelectedIndex(Math.max(selectedIndex - 1, 0)); | |
} else if (e.key === 'Enter') { | |
addSelected(); | |
} else if (e.key === 'Escape') { | |
props.onCancel(); | |
} | |
} | |
function addSelected() { | |
const node = { ...hits[selectedIndex].item }; | |
node.position = props.pos; | |
props.onAdd(node); | |
} | |
async function lostFocus(e: any) { | |
// If it's a click on a result, let the click handler handle it. | |
if (e.relatedTarget && e.relatedTarget.closest('.node-search')) return; | |
props.onCancel(); | |
} | |
return ( | |
<div className="node-search" style={{ top: props.pos.y, left: props.pos.x }}> | |
<input | |
ref={searchBox} | |
value={searchText} | |
onChange={event => typed(event.target.value)} | |
onKeyDown={onKeyDown} | |
onBlur={lostFocus} | |
placeholder="Search for box" /> | |
<div className="matches"> | |
{hits.map((box, index) => | |
<div | |
key={box.item.name} | |
tabIndex={0} | |
onFocus={() => setSelectedIndex(index)} | |
onMouseEnter={() => setSelectedIndex(index)} | |
onClick={addSelected} | |
className={`search-result ${index === selectedIndex ? 'selected' : ''}`}> | |
{box.item.name} | |
</div> | |
)} | |
</div> | |
</div > | |
); | |
} | |