2018-02-08 19:41:02 +03:00
|
|
|
|
import * as React from 'react';
|
|
|
|
|
|
|
|
|
|
import { IMenuItem } from '../../services/MenuStore';
|
|
|
|
|
import { SearchStore } from '../../services/SearchStore';
|
|
|
|
|
import { MenuItem } from '../SideMenu/MenuItem';
|
2018-02-22 12:36:03 +03:00
|
|
|
|
|
2018-02-22 12:26:53 +03:00
|
|
|
|
import { MarkerService } from '../../services/MarkerService';
|
|
|
|
|
import { SearchDocument } from '../../services/SearchWorker.worker';
|
2018-02-08 19:41:02 +03:00
|
|
|
|
|
2018-03-07 14:00:03 +03:00
|
|
|
|
import { ClearIcon, SearchIcon, SearchInput, SearchResultsBox, SearchWrap } from './elements';
|
2018-02-08 19:41:02 +03:00
|
|
|
|
|
|
|
|
|
export interface SearchBoxProps {
|
|
|
|
|
search: SearchStore;
|
2018-02-22 12:26:53 +03:00
|
|
|
|
marker: MarkerService;
|
2018-02-08 19:41:02 +03:00
|
|
|
|
getItemById: (id: string) => IMenuItem | undefined;
|
|
|
|
|
onActivate: (item: IMenuItem) => void;
|
2018-02-22 19:48:50 +03:00
|
|
|
|
|
|
|
|
|
className?: string;
|
2018-02-08 19:41:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface SearchBoxState {
|
|
|
|
|
results: any;
|
|
|
|
|
term: string;
|
2018-03-14 13:55:52 +03:00
|
|
|
|
activeItemIdx: number;
|
2018-02-08 19:41:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-02-22 19:48:50 +03:00
|
|
|
|
interface SearchResult {
|
|
|
|
|
item: IMenuItem;
|
|
|
|
|
score: number;
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 19:41:02 +03:00
|
|
|
|
export class SearchBox extends React.PureComponent<SearchBoxProps, SearchBoxState> {
|
2018-03-14 13:55:52 +03:00
|
|
|
|
activeItemRef: MenuItem | null = null;
|
|
|
|
|
|
2018-02-08 19:41:02 +03:00
|
|
|
|
constructor(props) {
|
|
|
|
|
super(props);
|
|
|
|
|
this.state = {
|
|
|
|
|
results: [],
|
|
|
|
|
term: '',
|
2018-03-14 13:55:52 +03:00
|
|
|
|
activeItemIdx: -1,
|
2018-02-08 19:41:02 +03:00
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-22 12:26:53 +03:00
|
|
|
|
clearResults(term: string) {
|
|
|
|
|
this.setState({
|
|
|
|
|
results: [],
|
|
|
|
|
term,
|
|
|
|
|
});
|
|
|
|
|
this.props.marker.unmark();
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-22 12:36:03 +03:00
|
|
|
|
clear = () => {
|
2018-02-22 12:26:53 +03:00
|
|
|
|
this.setState({
|
|
|
|
|
results: [],
|
|
|
|
|
term: '',
|
2018-03-14 13:55:52 +03:00
|
|
|
|
activeItemIdx: -1,
|
2018-02-22 12:26:53 +03:00
|
|
|
|
});
|
|
|
|
|
this.props.marker.unmark();
|
2018-02-22 12:36:03 +03:00
|
|
|
|
};
|
2018-02-22 12:26:53 +03:00
|
|
|
|
|
2018-03-14 13:55:52 +03:00
|
|
|
|
handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
|
|
|
if (event.keyCode === 27) {
|
|
|
|
|
// ESQ
|
2018-02-22 12:26:53 +03:00
|
|
|
|
this.clear();
|
|
|
|
|
}
|
2018-03-14 13:55:52 +03:00
|
|
|
|
if (event.keyCode === 40) {
|
|
|
|
|
// Arrow down
|
|
|
|
|
this.setState({
|
|
|
|
|
activeItemIdx: Math.min(this.state.activeItemIdx + 1, this.state.results.length - 1),
|
|
|
|
|
});
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
if (event.keyCode === 38) {
|
|
|
|
|
// Arrow up
|
|
|
|
|
this.setState({
|
|
|
|
|
activeItemIdx: Math.max(0, this.state.activeItemIdx - 1),
|
|
|
|
|
});
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
}
|
|
|
|
|
if (event.keyCode === 13) {
|
|
|
|
|
// enter
|
|
|
|
|
const activeResult = this.state.results[this.state.activeItemIdx];
|
|
|
|
|
if (activeResult) {
|
|
|
|
|
const item = this.props.getItemById(activeResult.id);
|
|
|
|
|
if (item) {
|
|
|
|
|
this.props.onActivate(item);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2018-02-22 12:26:53 +03:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
setResults(results: SearchDocument[], term: string) {
|
|
|
|
|
this.setState({
|
|
|
|
|
results,
|
|
|
|
|
term,
|
|
|
|
|
});
|
|
|
|
|
this.props.marker.mark(term);
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-08 19:41:02 +03:00
|
|
|
|
search = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
|
|
|
const q = event.target.value;
|
|
|
|
|
if (q.length < 3) {
|
2018-02-22 12:26:53 +03:00
|
|
|
|
this.clearResults(q);
|
2018-02-08 19:41:02 +03:00
|
|
|
|
return;
|
|
|
|
|
}
|
2018-02-22 12:26:53 +03:00
|
|
|
|
|
2018-02-08 19:41:02 +03:00
|
|
|
|
this.setState({
|
|
|
|
|
term: q,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
this.props.search.search(event.target.value).then(res => {
|
2018-02-22 12:26:53 +03:00
|
|
|
|
this.setResults(res, q);
|
2018-02-08 19:41:02 +03:00
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render() {
|
2018-03-14 13:55:52 +03:00
|
|
|
|
const { activeItemIdx } = this.state;
|
2018-02-22 19:48:50 +03:00
|
|
|
|
const results: SearchResult[] = this.state.results.map(res => ({
|
|
|
|
|
item: this.props.getItemById(res.id),
|
|
|
|
|
score: res.score,
|
|
|
|
|
}));
|
2018-03-05 18:55:12 +03:00
|
|
|
|
|
|
|
|
|
results.sort((a, b) => b.score - a.score);
|
2018-02-08 19:41:02 +03:00
|
|
|
|
|
|
|
|
|
return (
|
2018-03-07 14:00:03 +03:00
|
|
|
|
<SearchWrap>
|
2018-02-22 12:36:03 +03:00
|
|
|
|
{this.state.term && <ClearIcon onClick={this.clear}>×</ClearIcon>}
|
2018-02-08 19:41:02 +03:00
|
|
|
|
<SearchIcon />
|
|
|
|
|
<SearchInput
|
|
|
|
|
value={this.state.term}
|
2018-03-14 13:55:52 +03:00
|
|
|
|
onKeyDown={this.handleKeyDown}
|
2018-02-08 19:41:02 +03:00
|
|
|
|
placeholder="Search..."
|
|
|
|
|
type="text"
|
|
|
|
|
onChange={this.search}
|
|
|
|
|
/>
|
2018-02-22 19:48:50 +03:00
|
|
|
|
{results.length > 0 && (
|
2018-02-08 19:41:02 +03:00
|
|
|
|
<SearchResultsBox>
|
2018-03-14 13:55:52 +03:00
|
|
|
|
{results.map((res, idx) => (
|
2018-02-08 19:41:02 +03:00
|
|
|
|
<MenuItem
|
2018-03-14 13:55:52 +03:00
|
|
|
|
item={Object.create(res.item, {
|
|
|
|
|
active: {
|
|
|
|
|
value: idx === activeItemIdx,
|
|
|
|
|
},
|
|
|
|
|
})}
|
2018-02-08 19:41:02 +03:00
|
|
|
|
onActivate={this.props.onActivate}
|
|
|
|
|
withoutChildren={true}
|
2018-02-22 19:48:50 +03:00
|
|
|
|
key={res.item.id}
|
2018-02-08 19:41:02 +03:00
|
|
|
|
/>
|
|
|
|
|
))}
|
|
|
|
|
</SearchResultsBox>
|
|
|
|
|
)}
|
2018-03-07 14:00:03 +03:00
|
|
|
|
</SearchWrap>
|
2018-02-08 19:41:02 +03:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|