mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-16 21:03:29 +00:00
124 lines
3.9 KiB
TypeScript
124 lines
3.9 KiB
TypeScript
import { useDebouncedCallback } from '@mantine/hooks';
|
|
import { SupportedEntry } from 'ontime-types';
|
|
import { KeyboardEvent, useState } from 'react';
|
|
|
|
import Input from '../../../common/components/input/input/Input';
|
|
import Kbd from '../../../common/components/kbd/Kbd';
|
|
import Modal from '../../../common/components/modal/Modal';
|
|
import useFinder from './useFinder';
|
|
|
|
import style from './Finder.module.scss';
|
|
|
|
interface FinderProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
export default function Finder({ isOpen, onClose }: FinderProps) {
|
|
const { find, select, results, error } = useFinder();
|
|
const [selected, setSelected] = useState(0);
|
|
|
|
const debouncedFind = useDebouncedCallback(find, 100);
|
|
|
|
const navigate = (event: KeyboardEvent<HTMLDivElement>) => {
|
|
// all operations need results
|
|
if (results.length === 0) {
|
|
return;
|
|
}
|
|
if (event.key === 'ArrowDown') {
|
|
setSelected((prev) => (prev + 1) % results.length);
|
|
}
|
|
if (event.key === 'ArrowUp') {
|
|
setSelected((prev) => (prev - 1 + results.length) % results.length);
|
|
}
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
submit();
|
|
}
|
|
};
|
|
|
|
const submit = () => {
|
|
const selectedEvent = results[selected];
|
|
select(selectedEvent);
|
|
onClose();
|
|
};
|
|
|
|
const handleMouseMoveEvent = (event: React.MouseEvent<HTMLUListElement>) => {
|
|
const target = event.target as HTMLElement;
|
|
const li = target.closest('li');
|
|
if (li) {
|
|
const index = Number(li.dataset.index);
|
|
if (!isNaN(index)) {
|
|
setSelected(index);
|
|
}
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Modal
|
|
title=''
|
|
isOpen={isOpen}
|
|
onClose={onClose}
|
|
showBackdrop
|
|
bodyElements={
|
|
<div onKeyDown={navigate}>
|
|
<Input height='large' fluid onChange={debouncedFind} placeholder='Search...' />
|
|
<ul className={style.scrollContainer} onMouseMove={handleMouseMoveEvent}>
|
|
{error && <li className={style.error}>{error}</li>}
|
|
{results.length === 0 && <li className={style.empty}>No results</li>}
|
|
{results.length > 0 &&
|
|
results.map((entry, index) => {
|
|
const isSelected = selected === index;
|
|
const displayIndex = entry.type === SupportedEntry.Event ? entry.eventIndex : '-';
|
|
const displayCue = 'cue' in entry ? entry.cue : '';
|
|
|
|
return (
|
|
<li
|
|
key={entry.id}
|
|
className={style.entry}
|
|
data-selected={isSelected}
|
|
data-index={index}
|
|
onClick={submit}
|
|
>
|
|
<div className={style.data}>
|
|
<div className={style.index} style={{ '--color': entry.colour }}>
|
|
{displayIndex}
|
|
</div>
|
|
<div className={style.cue}>{displayCue}</div>
|
|
<div className={style.title}>{entry.title}</div>
|
|
</div>
|
|
{isSelected && <span>Go ⏎</span>}
|
|
</li>
|
|
);
|
|
})}
|
|
</ul>
|
|
</div>
|
|
}
|
|
footerElements={
|
|
<div className={style.footer}>
|
|
<div className={style.hints}>
|
|
<span className={style.hintItem}>
|
|
<Kbd>↑</Kbd>
|
|
<Kbd>↓</Kbd>
|
|
Navigate
|
|
</span>
|
|
<span className={style.hintItem}>
|
|
<Kbd>Enter</Kbd>
|
|
Go
|
|
</span>
|
|
<span className={style.hintItem}>
|
|
<Kbd>Esc</Kbd>
|
|
Close
|
|
</span>
|
|
</div>
|
|
<div className={style.filterHint}>
|
|
Filter by <span className={style.em}>cue</span>, <span className={style.em}>index</span>, or
|
|
<span className={style.em}>title</span>
|
|
</div>
|
|
</div>
|
|
}
|
|
/>
|
|
);
|
|
}
|