diff --git a/web/input/input-state-container.react.js b/web/input/input-state-container.react.js --- a/web/input/input-state-container.react.js +++ b/web/input/input-state-container.react.js @@ -78,6 +78,7 @@ import { useSelector } from '../redux/redux-utils'; import { nonThreadCalendarQuery } from '../selectors/nav-selectors'; import { type PendingMultimediaUpload, InputStateContext } from './input-state'; +import type { InputState, TypeaheadState } from './input-state'; type BaseProps = { +children: React.Node, @@ -120,6 +121,7 @@ [threadID: string]: { [localUploadID: string]: PendingMultimediaUpload }, }, +textCursorPositions: { [threadID: string]: number }, + +typeaheadState: TypeaheadState, }; type PropsAndState = { @@ -130,6 +132,12 @@ state: State = { pendingUploads: {}, textCursorPositions: {}, + typeaheadState: { + chosenButtonNumber: 0, + isVisible: false, + close: null, + accept: null, + }, }; replyCallbacks: Array<(message: string) => void> = []; pendingThreadCreations = new Map>(); @@ -454,7 +462,7 @@ return threadCreationPromise; } - inputStateSelector = _memoize((threadID: string) => + inputBaseStateSelector = _memoize((threadID: string) => createSelector( (propsAndState: PropsAndState) => propsAndState.pendingUploads[threadID], (propsAndState: PropsAndState) => @@ -520,6 +528,30 @@ ), ); + typeaheadStateSelector = _memoize(() => + createSelector( + (propsAndState: PropsAndState) => propsAndState.typeaheadState, + (typeaheadState: TypeaheadState) => { + return { + typeaheadState, + setTypeaheadState: this.setTypeaheadState, + }; + }, + ), + ); + + inputStateSelector = _memoize(() => + createSelector( + (inputState: InputState) => inputState, + + inputState => { + return inputState; + }, + ), + ); + + i; + getRealizedOrPendingThreadID(threadID: string): string { return this.props.pendingRealizedThreadIDs.get(threadID) ?? threadID; } @@ -1091,6 +1123,17 @@ }); } + setTypeaheadState = (newState: $Shape) => { + this.setState(prevState => { + return { + typeaheadState: { + ...prevState.typeaheadState, + ...newState, + }, + }; + }); + }; + setProgress( threadID: string, localUploadID: string, @@ -1236,12 +1279,27 @@ render() { const { activeChatThreadID } = this.props; - const inputState = activeChatThreadID - ? this.inputStateSelector(activeChatThreadID)({ - ...this.state, - ...this.props, - }) - : null; + + let inputState: ?InputState; + if (activeChatThreadID) { + const inputBaseState = this.inputBaseStateSelector(activeChatThreadID)({ + ...this.state, + ...this.props, + }); + + const typeaheadState = this.typeaheadStateSelector()({ + ...this.state, + ...this.props, + }); + + inputState = this.inputStateSelector()({ + ...inputBaseState, + ...typeaheadState, + }); + } else { + inputState = null; + } + return ( {this.props.children} diff --git a/web/input/input-state.js b/web/input/input-state.js --- a/web/input/input-state.js +++ b/web/input/input-state.js @@ -34,6 +34,13 @@ selectTime: number, }; +export type TypeaheadState = { + +isVisible: boolean, + +chosenButtonNumber: number, + +close: ?() => void, + +accept: ?() => void, +}; + // This type represents the input state for a particular thread export type InputState = { +pendingUploads: $ReadOnlyArray, @@ -42,6 +49,7 @@ }, +draft: string, +textCursorPosition: number, + +typeaheadState: TypeaheadState, +appendFiles: (files: $ReadOnlyArray) => Promise, +cancelPendingUpload: (localUploadID: string) => void, +sendTextMessage: ( @@ -51,6 +59,7 @@ +createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) => void, +setDraft: (draft: string) => void, +setTextCursorPosition: (newPosition: number) => void, + +setTypeaheadState: ($Shape) => void, +messageHasUploadFailure: (localMessageID: string) => boolean, +retryMultimediaMessage: ( localMessageID: string,