diff --git a/web/calendar/thread-picker.react.js b/web/calendar/thread-picker.react.js index 016239784..81633c99e 100644 --- a/web/calendar/thread-picker.react.js +++ b/web/calendar/thread-picker.react.js @@ -1,119 +1,160 @@ // @flow import invariant from 'invariant'; import * as React from 'react'; +import { createSelector } from 'reselect'; +import { threadSearchIndex } from 'lib/selectors/nav-selectors'; import { onScreenEntryEditableThreadInfos } from 'lib/selectors/thread-selectors'; +import SearchIndex from 'lib/shared/search-index'; import type { ThreadInfo } from 'lib/types/thread-types'; import { useSelector } from '../redux/redux-utils'; import { htmlTargetFromEvent } from '../vector-utils'; import css from './thread-picker.css'; type OptionProps = { +threadInfo: ThreadInfo, +createNewEntry: (threadID: string) => void, }; function ThreadPickerOption(props: OptionProps) { const { threadInfo, createNewEntry } = props; const onClick = React.useCallback(() => createNewEntry(threadInfo.id), [ threadInfo.id, createNewEntry, ]); const colorStyle = { backgroundColor: `#${props.threadInfo.color}` }; return (
{props.threadInfo.uiName}
); } type BaseProps = { +createNewEntry: (threadID: string) => void, +closePicker: () => void, }; type Props = { ...BaseProps, +onScreenThreadInfos: $ReadOnlyArray, + +searchIndex: SearchIndex, }; +type State = { + +searchText: string, + +searchResults: Set, +}; +type PropsAndState = { ...Props, ...State }; -class ThreadPicker extends React.PureComponent { +class ThreadPicker extends React.PureComponent { pickerDiv: ?HTMLDivElement; constructor(props: Props) { super(props); + this.state = { + searchText: '', + searchResults: new Set(), + }; invariant( props.onScreenThreadInfos.length > 0, "ThreadPicker can't be open when onScreenThreadInfos is empty", ); } componentDidMount() { invariant(this.pickerDiv, 'pickerDiv ref unset'); this.pickerDiv.focus(); } + listDataSelector = createSelector( + (propsAndState: PropsAndState) => propsAndState.onScreenThreadInfos, + (propsAndState: PropsAndState) => propsAndState.searchText, + (propsAndState: PropsAndState) => propsAndState.searchResults, + ( + threadInfos: $ReadOnlyArray, + text: string, + searchResults: Set, + ) => + text + ? threadInfos.filter(threadInfo => searchResults.has(threadInfo.id)) + : [...threadInfos], + ); + + get getListData() { + return this.listDataSelector({ ...this.props, ...this.state }); + } + render() { const length = this.props.onScreenThreadInfos.length; invariant( length > 0, "ThreadPicker can't be open when onScreenThreadInfos is empty", ); - const options = this.props.onScreenThreadInfos.map(threadInfo => ( + const options = this.getListData.map(threadInfo => ( )); return (
{options}
); } pickerDivRef = (pickerDiv: ?HTMLDivElement) => { this.pickerDiv = pickerDiv; }; onPickerKeyDown = (event: SyntheticKeyboardEvent) => { if (event.keyCode === 27) { // Esc this.props.closePicker(); } }; onMouseDown = (event: SyntheticEvent) => { const target = htmlTargetFromEvent(event); invariant(this.pickerDiv, 'pickerDiv ref not set'); if (this.pickerDiv.contains(target)) { // This prevents onBlur from firing event.preventDefault(); } }; + + onChangeSearchText = (searchText: string) => { + const results = this.props.searchIndex.getSearchResults(searchText); + this.setState({ searchText, searchResults: new Set(results) }); + }; } const ConnectedThreadPicker: React.ComponentType = React.memo( function ConnectedThreadPicker(props) { const onScreenThreadInfos = useSelector(onScreenEntryEditableThreadInfos); + const index = useSelector(state => threadSearchIndex(state)); return ( - + ); }, ); export default ConnectedThreadPicker;