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;