darabos's picture
Split LynxKite into several Python packages.
d7ccb5f
raw
history blame
2.59 kB
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 >
);
}