diff --git a/web/account/siwe-login-form.react.js b/web/account/siwe-login-form.react.js --- a/web/account/siwe-login-form.react.js +++ b/web/account/siwe-login-form.react.js @@ -17,7 +17,10 @@ import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; import stores from 'lib/facts/stores.js'; import { createLoadingStatusSelector } from 'lib/selectors/loading-selectors.js'; -import type { LogInStartingPayload } from 'lib/types/account-types.js'; +import type { + LogInStartingPayload, + LogInExtraInfo, +} from 'lib/types/account-types.js'; import type { OLMIdentityKeys, SignedIdentityKeysBlob, @@ -92,7 +95,7 @@ useSignedIdentityKeysBlob(); const callSIWEAuthEndpoint = React.useCallback( - async (message: string, signature: string, extraInfo) => { + async (message: string, signature: string, extraInfo: LogInExtraInfo) => { invariant( signedIdentityKeysBlob, 'signedIdentityKeysBlob must be set in attemptSIWEAuth', diff --git a/web/account/traditional-login-form.react.js b/web/account/traditional-login-form.react.js --- a/web/account/traditional-login-form.react.js +++ b/web/account/traditional-login-form.react.js @@ -45,20 +45,26 @@ }, []); const [username, setUsername] = React.useState(''); - const onUsernameChange = React.useCallback(e => { - invariant(e.target instanceof HTMLInputElement, 'target not input'); - setUsername(e.target.value); - }, []); + const onUsernameChange = React.useCallback( + (e: SyntheticEvent) => { + invariant(e.target instanceof HTMLInputElement, 'target not input'); + setUsername(e.target.value); + }, + [], + ); const onUsernameBlur = React.useCallback(() => { setUsername(untrimmedUsername => untrimmedUsername.trim()); }, []); const [password, setPassword] = React.useState(''); - const onPasswordChange = React.useCallback(e => { - invariant(e.target instanceof HTMLInputElement, 'target not input'); - setPassword(e.target.value); - }, []); + const onPasswordChange = React.useCallback( + (e: SyntheticEvent) => { + invariant(e.target instanceof HTMLInputElement, 'target not input'); + setPassword(e.target.value); + }, + [], + ); const [errorMessage, setErrorMessage] = React.useState(''); diff --git a/web/app.react.js b/web/app.react.js --- a/web/app.react.js +++ b/web/app.react.js @@ -165,7 +165,7 @@ }); }; - render() { + render(): React.Node { let content; if (this.props.loggedIn) { content = ( @@ -213,10 +213,11 @@ ); } - onHeaderDoubleClick = () => electron?.doubleClickTopBar(); - stopDoubleClickPropagation = electron ? e => e.stopPropagation() : null; + onHeaderDoubleClick = (): void => electron?.doubleClickTopBar(); + stopDoubleClickPropagation: ?(SyntheticEvent) => void = + electron ? e => e.stopPropagation() : null; - renderLoginPage() { + renderLoginPage(): React.Node { const { loginMethod } = this.props.navInfo; if (loginMethod === 'qr-code') { @@ -226,7 +227,7 @@ return ; } - renderMainContent() { + renderMainContent(): React.Node { const mainContent = this.getMainContentWithSwitcher(); let navigationArrows = null; @@ -284,7 +285,7 @@ ); } - getMainContentWithSwitcher() { + getMainContentWithSwitcher(): React.Node { const { tab, settingsSection } = this.props.navInfo; let mainContent; diff --git a/web/avatars/avatar-hooks.react.js b/web/avatars/avatar-hooks.react.js --- a/web/avatars/avatar-hooks.react.js +++ b/web/avatars/avatar-hooks.react.js @@ -21,7 +21,7 @@ const callUploadMultimedia = useServerCall(uploadMultimedia); const callBlobServiceUpload = useBlobServiceUpload(); const uploadAvatarMedia = React.useCallback( - async file => { + async (file: File): Promise => { const validatedFile = await validateFile(file); const { result } = validatedFile; if (!result.success) { diff --git a/web/avatars/edit-thread-avatar-menu.react.js b/web/avatars/edit-thread-avatar-menu.react.js --- a/web/avatars/edit-thread-avatar-menu.react.js +++ b/web/avatars/edit-thread-avatar-menu.react.js @@ -57,8 +57,10 @@ const uploadAvatarMedia = useUploadAvatarMedia(); const onImageSelected = React.useCallback( - async event => { - const uploadResult = await uploadAvatarMedia(event.target.files[0]); + async (event: SyntheticEvent) => { + const { target } = event; + invariant(target instanceof HTMLInputElement, 'target not input'); + const uploadResult = await uploadAvatarMedia(target.files[0]); baseSetThreadAvatar(threadInfo.id, uploadResult); }, [baseSetThreadAvatar, threadInfo.id, uploadAvatarMedia], diff --git a/web/avatars/edit-user-avatar-menu.react.js b/web/avatars/edit-user-avatar-menu.react.js --- a/web/avatars/edit-user-avatar-menu.react.js +++ b/web/avatars/edit-user-avatar-menu.react.js @@ -69,8 +69,10 @@ const uploadAvatarMedia = useUploadAvatarMedia(); const onImageSelected = React.useCallback( - async event => { - const uploadResult = await uploadAvatarMedia(event.target.files[0]); + async (event: SyntheticEvent) => { + const { target } = event; + invariant(target instanceof HTMLInputElement, 'target not input'); + const uploadResult = await uploadAvatarMedia(target.files[0]); baseSetUserAvatar(uploadResult); }, [baseSetUserAvatar, uploadAvatarMedia], diff --git a/web/avatars/emoji-avatar-selection-modal.react.js b/web/avatars/emoji-avatar-selection-modal.react.js --- a/web/avatars/emoji-avatar-selection-modal.react.js +++ b/web/avatars/emoji-avatar-selection-modal.react.js @@ -53,10 +53,13 @@ [pendingAvatarColor, pendingAvatarEmoji], ); - const onEmojiSelect = React.useCallback(selection => { - setUpdateAvatarStatus(); - setPendingAvatarEmoji(selection.native); - }, []); + const onEmojiSelect = React.useCallback( + (selection: { +native: string, ... }) => { + setUpdateAvatarStatus(); + setPendingAvatarEmoji(selection.native); + }, + [], + ); const onColorSelection = React.useCallback((hex: string) => { setUpdateAvatarStatus(); diff --git a/web/calendar/calendar.react.js b/web/calendar/calendar.react.js --- a/web/calendar/calendar.react.js +++ b/web/calendar/calendar.react.js @@ -41,6 +41,11 @@ import type { NavInfo } from '../types/nav-types.js'; import { canonicalURLFromReduxState } from '../url-utils.js'; +type StartAndEndDates = { + +startDate: string, + +endDate: string, +}; + type BaseProps = { +url: string, }; @@ -69,7 +74,7 @@ dayOfMonth: number, monthInput: ?number = undefined, yearInput: ?number = undefined, - ) { + ): Date { return getDate( yearInput ? yearInput : this.props.year, monthInput ? monthInput : this.props.month, @@ -77,7 +82,7 @@ ); } - prevMonthDates() { + prevMonthDates(): StartAndEndDates { const { year, month } = this.props; const lastMonthDate = getDate(year, month - 1, 1); const prevYear = lastMonthDate.getFullYear(); @@ -88,7 +93,7 @@ }; } - nextMonthDates() { + nextMonthDates(): StartAndEndDates { const { year, month } = this.props; const nextMonthDate = getDate(year, month + 1, 1); const nextYear = nextMonthDate.getFullYear(); @@ -99,7 +104,7 @@ }; } - render() { + render(): React.Node { const { year, month } = this.props; const monthName = dateFormat(getDate(year, month, 1), 'mmmm'); const prevURL = canonicalURLFromReduxState( diff --git a/web/calendar/day.react.js b/web/calendar/day.react.js --- a/web/calendar/day.react.js +++ b/web/calendar/day.react.js @@ -65,7 +65,7 @@ } } - render() { + render(): React.Node { const now = new Date(); const isToday = dateString(now) === this.props.dayString; const tdClasses = classNames(css.day, { [css.currentDay]: isToday }); diff --git a/web/calendar/filter-panel.react.js b/web/calendar/filter-panel.react.js --- a/web/calendar/filter-panel.react.js +++ b/web/calendar/filter-panel.react.js @@ -77,7 +77,7 @@ return this.props.filteredCommunityThreadIDs.has(threadID); } - render() { + render(): React.Node { const filterThreadInfos = this.state.query ? this.state.searchResults : this.props.filterThreadInfos; @@ -275,7 +275,7 @@ +selected: boolean, }; class Item extends React.PureComponent { - render() { + render(): React.Node { const threadInfo = this.props.filterThreadInfo.threadInfo; const beforeCheckStyles = { borderColor: `#${threadInfo.color}` }; let afterCheck = null; @@ -343,7 +343,7 @@ +selected: boolean, }; class Category extends React.PureComponent { - render() { + render(): React.Node { const beforeCheckStyles = { borderColor: 'white' }; let afterCheck = null; if (this.props.selected) { diff --git a/web/chat/chat-input-bar.react.js b/web/chat/chat-input-bar.react.js --- a/web/chat/chat-input-bar.react.js +++ b/web/chat/chat-input-bar.react.js @@ -166,7 +166,7 @@ static unassignedUploadIDs( pendingUploads: $ReadOnlyArray, - ) { + ): Array { return pendingUploads .filter( (pendingUpload: PendingMultimediaUpload) => !pendingUpload.messageID, @@ -199,7 +199,7 @@ this.props.inputState.removeReplyListener(this.focusAndUpdateText); } - render() { + render(): React.Node { const isMember = viewerIsMember(this.props.threadInfo); const canJoin = threadHasPermission( this.props.threadInfo, @@ -540,7 +540,7 @@ this.props.dispatchActionPromise(joinThreadActionTypes, this.joinAction()); }; - async joinAction() { + async joinAction(): Promise { const query = this.props.calendarQuery(); return await this.props.joinThread({ threadID: this.props.threadInfo.id, diff --git a/web/chat/chat-message-list.react.js b/web/chat/chat-message-list.react.js --- a/web/chat/chat-message-list.react.js +++ b/web/chat/chat-message-list.react.js @@ -107,7 +107,7 @@ this.props.removeScrollToMessageListener(this.scrollToMessage); } - getSnapshotBeforeUpdate(prevProps: Props) { + getSnapshotBeforeUpdate(prevProps: Props): ?Snapshot { if ( ChatMessageList.hasNewMessage(this.props, prevProps) && this.messageContainer @@ -118,7 +118,7 @@ return null; } - static hasNewMessage(props: Props, prevProps: Props) { + static hasNewMessage(props: Props, prevProps: Props): boolean { const { messageListData } = props; if (!messageListData || messageListData.length === 0) { return false; @@ -133,7 +133,7 @@ ); } - componentDidUpdate(prevProps: Props, prevState, snapshot: ?Snapshot) { + componentDidUpdate(prevProps: Props, prevState: State, snapshot: ?Snapshot) { const { messageListData } = this.props; const prevMessageListData = prevProps.messageListData; @@ -180,14 +180,14 @@ } } - static keyExtractor(item: ChatMessageItem) { + static keyExtractor(item: ChatMessageItem): string { if (item.itemType === 'loader') { return 'loader'; } return messageKey(item.messageInfo); } - renderItem = item => { + renderItem = (item: ChatMessageItem): React.Node => { if (item.itemType === 'loader') { return (
@@ -271,7 +271,7 @@ return maxHeight; }; - willMessageEditWindowOverflow(composedMessageID: string) { + willMessageEditWindowOverflow(composedMessageID: string): boolean { const { messageContainer } = this; if (!messageContainer) { return false; @@ -305,7 +305,7 @@ return messageBottom > containerBottom || messageTop < containerTop; } - render() { + render(): React.Node { const { messageListData, threadInfo, inputState, isEditState } = this.props; if (!messageListData) { return
; @@ -353,7 +353,7 @@ this.debounceEditModeAfterScrollToMessage(); }; - debounceEditModeAfterScrollToMessage = _debounce(() => { + debounceEditModeAfterScrollToMessage: () => void = _debounce(() => { if (this.state.scrollingEndCallback) { this.state.scrollingEndCallback(); } diff --git a/web/chat/chat-thread-list-item-menu.react.js b/web/chat/chat-thread-list-item-menu.react.js --- a/web/chat/chat-thread-list-item-menu.react.js +++ b/web/chat/chat-thread-list-item-menu.react.js @@ -21,7 +21,7 @@ const active = useThreadIsActive(threadInfo.id); const [menuVisible, setMenuVisible] = React.useState(false); const toggleMenu = React.useCallback( - event => { + (event: SyntheticEvent) => { event.stopPropagation(); setMenuVisible(!menuVisible); }, @@ -39,7 +39,7 @@ ); const onToggleUnreadStatusClicked = React.useCallback( - event => { + (event: SyntheticEvent) => { event.stopPropagation(); toggleUnreadStatus(); }, diff --git a/web/chat/chat-thread-list.react.js b/web/chat/chat-thread-list.react.js --- a/web/chat/chat-thread-list.react.js +++ b/web/chat/chat-thread-list.react.js @@ -41,7 +41,12 @@ } }; -const renderItem = ({ index, data, style }) => { +type RenderItemInput = { + +index: number, + +data: $ReadOnlyArray, + +style: CSSStyleDeclaration, +}; +const renderItem: RenderItemInput => React.Node = ({ index, data, style }) => { let item; if (data[index].type === 'search') { item = ; @@ -129,7 +134,7 @@ items.push({ type: 'empty' }); } - const itemSize = index => { + const itemSize = (index: number) => { if (items[index].type === 'search') { return sizes.search; } else if (items[index].type === 'empty') { diff --git a/web/chat/edit-text-message.react.js b/web/chat/edit-text-message.react.js --- a/web/chat/edit-text-message.react.js +++ b/web/chat/edit-text-message.react.js @@ -98,7 +98,7 @@ }, [background, updatePosition]); const preventCloseTab = React.useCallback( - event => { + (event: BeforeUnloadEvent) => { if (!isMessageEdited) { return null; } diff --git a/web/chat/failed-send.react.js b/web/chat/failed-send.react.js --- a/web/chat/failed-send.react.js +++ b/web/chat/failed-send.react.js @@ -79,7 +79,7 @@ } } - render() { + render(): React.Node { return (
Delivery failed. diff --git a/web/chat/message-tooltip.react.js b/web/chat/message-tooltip.react.js --- a/web/chat/message-tooltip.react.js +++ b/web/chat/message-tooltip.react.js @@ -179,7 +179,7 @@ ); const onEmojiSelect = React.useCallback( - emoji => { + (emoji: { +native: string, ... }) => { const reactionInput = emoji.native; sendReaction(reactionInput); }, diff --git a/web/chat/multimedia-message.react.js b/web/chat/multimedia-message.react.js --- a/web/chat/multimedia-message.react.js +++ b/web/chat/multimedia-message.react.js @@ -27,7 +27,7 @@ +inputState: ?InputState, }; class MultimediaMessage extends React.PureComponent { - render() { + render(): React.Node { const { item, inputState } = this.props; invariant( item.messageInfo.type === messageTypes.IMAGES || diff --git a/web/chat/robotext-message.react.js b/web/chat/robotext-message.react.js --- a/web/chat/robotext-message.react.js +++ b/web/chat/robotext-message.react.js @@ -108,7 +108,7 @@ +dispatch: Dispatch, }; class InnerThreadEntity extends React.PureComponent { - render() { + render(): React.Node { return {this.props.name}; } diff --git a/web/components/button.react.js b/web/components/button.react.js --- a/web/components/button.react.js +++ b/web/components/button.react.js @@ -61,7 +61,7 @@ style = buttonThemes.standard; } - const wrappedChildren = React.Children.map(children, child => { + const wrappedChildren = React.Children.map(children, (child: React.Node) => { if (typeof child === 'string' || typeof child === 'number') { return {child}; } diff --git a/web/components/dropdown.react.js b/web/components/dropdown.react.js --- a/web/components/dropdown.react.js +++ b/web/components/dropdown.react.js @@ -48,7 +48,7 @@ }, [disabled, isOpen]); const handleSelection = React.useCallback( - selection => { + (selection: DropdownOption) => { setActiveSelection(selection.id); setIsOpen(false); }, diff --git a/web/components/menu.react.js b/web/components/menu.react.js --- a/web/components/menu.react.js +++ b/web/components/menu.react.js @@ -102,7 +102,7 @@ }, [closeMenu]); const onClickMenuCallback = React.useCallback( - e => { + (e: SyntheticEvent) => { e.stopPropagation(); setCurrentOpenMenu(ourSymbol.current); }, diff --git a/web/components/navigation-arrows.react.js b/web/components/navigation-arrows.react.js --- a/web/components/navigation-arrows.react.js +++ b/web/components/navigation-arrows.react.js @@ -9,7 +9,8 @@ import electron from '../electron.js'; import history from '../router-history.js'; -const stopDoubleClickPropagation = e => e.stopPropagation(); +const stopDoubleClickPropagation = (e: SyntheticEvent) => + e.stopPropagation(); function NavigationArrows(): React.Node { const goBack = React.useCallback( diff --git a/web/components/search.react.js b/web/components/search.react.js --- a/web/components/search.react.js +++ b/web/components/search.react.js @@ -1,5 +1,6 @@ // @flow +import invariant from 'invariant'; import * as React from 'react'; import SWMansionIcon from 'lib/components/SWMansionIcon.react.js'; @@ -30,8 +31,10 @@ }, [onChangeText, onClearText]); const onChange = React.useCallback( - event => { - onChangeText(event.target.value); + (event: SyntheticEvent) => { + const { target } = event; + invariant(target instanceof HTMLInputElement, 'target not input'); + onChangeText(target.value); }, [onChangeText], ); 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 @@ -100,6 +100,9 @@ type PendingMultimediaUpload, type TypeaheadState, InputStateContext, + type BaseInputState, + type TypeaheadInputState, + type InputState, } from './input-state.js'; import { encryptFile } from '../media/encryption-utils.js'; import { generateThumbHash } from '../media/image-utils.js'; @@ -172,7 +175,10 @@ }, }; replyCallbacks: Array<(message: string) => void> = []; - pendingThreadCreations = new Map>(); + pendingThreadCreations: Map> = new Map< + string, + Promise, + >(); // TODO: flip the switch // Note that this enables Blob service for encrypted media only useBlobServiceUploads = false; @@ -181,7 +187,7 @@ // sidebar, the sidebar gets created right away, but the message needs to wait // for the uploads to complete before sending. We use this Set to track the // message localIDs that need sidebarCreation: true. - pendingSidebarCreationMessageLocalIDs = new Set(); + pendingSidebarCreationMessageLocalIDs: Set = new Set(); static reassignToRealizedThreads( state: { +[threadID: string]: T }, @@ -200,7 +206,7 @@ return updated ? newState : null; } - static getDerivedStateFromProps(props: Props, state: State) { + static getDerivedStateFromProps(props: Props, state: State): ?Partial { const pendingUploads = InputStateContainer.reassignToRealizedThreads( state.pendingUploads, props, @@ -224,7 +230,7 @@ return stateUpdate; } - static completedMessageIDs(state: State) { + static completedMessageIDs(state: State): Set { const completed = new Map(); for (const threadID in state.pendingUploads) { const pendingUploads = state.pendingUploads[threadID]; @@ -404,7 +410,9 @@ return threadInfoInsideCommunity(threadInfo, commStaffCommunity.id); } - async sendMultimediaMessage(messageInfo: RawMultimediaMessageInfo) { + async sendMultimediaMessage( + messageInfo: RawMultimediaMessageInfo, + ): Promise { if (!threadIsPending(messageInfo.threadID)) { this.props.dispatchActionPromise( sendMultimediaMessageActionTypes, @@ -564,74 +572,84 @@ return threadCreationPromise; } - inputBaseStateSelector = _memoize((threadID: ?string) => - createSelector( - (propsAndState: PropsAndState) => - threadID ? propsAndState.pendingUploads[threadID] : null, - (propsAndState: PropsAndState) => - threadID ? propsAndState.drafts[draftKeyFromThreadID(threadID)] : null, - (propsAndState: PropsAndState) => - threadID ? propsAndState.textCursorPositions[threadID] : null, - ( - pendingUploads: ?{ [localUploadID: string]: PendingMultimediaUpload }, - draft: ?string, - textCursorPosition: ?number, - ) => { - let threadPendingUploads = []; - const assignedUploads = {}; - if (pendingUploads) { - const [uploadsWithMessageIDs, uploadsWithoutMessageIDs] = - _partition('messageID')(pendingUploads); - threadPendingUploads = _sortBy('localID')(uploadsWithoutMessageIDs); - const threadAssignedUploads = _groupBy('messageID')( - uploadsWithMessageIDs, - ); - for (const messageID in threadAssignedUploads) { - // lodash libdefs don't return $ReadOnlyArray - assignedUploads[messageID] = [...threadAssignedUploads[messageID]]; + inputBaseStateSelector: (?string) => PropsAndState => BaseInputState = + _memoize((threadID: ?string) => + createSelector( + (propsAndState: PropsAndState) => + threadID ? propsAndState.pendingUploads[threadID] : null, + (propsAndState: PropsAndState) => + threadID + ? propsAndState.drafts[draftKeyFromThreadID(threadID)] + : null, + (propsAndState: PropsAndState) => + threadID ? propsAndState.textCursorPositions[threadID] : null, + ( + pendingUploads: ?{ [localUploadID: string]: PendingMultimediaUpload }, + draft: ?string, + textCursorPosition: ?number, + ) => { + let threadPendingUploads = []; + const assignedUploads = {}; + if (pendingUploads) { + const [uploadsWithMessageIDs, uploadsWithoutMessageIDs] = + _partition('messageID')(pendingUploads); + threadPendingUploads = _sortBy('localID')(uploadsWithoutMessageIDs); + const threadAssignedUploads = _groupBy('messageID')( + uploadsWithMessageIDs, + ); + for (const messageID in threadAssignedUploads) { + // lodash libdefs don't return $ReadOnlyArray + assignedUploads[messageID] = [ + ...threadAssignedUploads[messageID], + ]; + } } - } - return { - pendingUploads: threadPendingUploads, - assignedUploads, - draft: draft ?? '', - textCursorPosition: textCursorPosition ?? 0, - appendFiles: (threadInfo: ThreadInfo, files: $ReadOnlyArray) => - this.appendFiles(threadInfo, files), - cancelPendingUpload: (localUploadID: string) => - this.cancelPendingUpload(threadID, localUploadID), - sendTextMessage: ( - messageInfo: RawTextMessageInfo, - threadInfo: ThreadInfo, - parentThreadInfo: ?ThreadInfo, - ) => this.sendTextMessage(messageInfo, threadInfo, parentThreadInfo), - createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) => - this.createMultimediaMessage(localID, threadInfo), - setDraft: (newDraft: string) => this.setDraft(threadID, newDraft), - setTextCursorPosition: (newPosition: number) => - this.setTextCursorPosition(threadID, newPosition), - messageHasUploadFailure: (localMessageID: string) => - this.messageHasUploadFailure(assignedUploads[localMessageID]), - retryMultimediaMessage: ( - localMessageID: string, - threadInfo: ThreadInfo, - ) => - this.retryMultimediaMessage( - localMessageID, - threadInfo, - assignedUploads[localMessageID], - ), - addReply: (message: string) => this.addReply(message), - addReplyListener: this.addReplyListener, - removeReplyListener: this.removeReplyListener, - registerSendCallback: this.props.registerSendCallback, - unregisterSendCallback: this.props.unregisterSendCallback, - }; - }, - ), - ); + return { + pendingUploads: threadPendingUploads, + assignedUploads, + draft: draft ?? '', + textCursorPosition: textCursorPosition ?? 0, + appendFiles: ( + threadInfo: ThreadInfo, + files: $ReadOnlyArray, + ) => this.appendFiles(threadInfo, files), + cancelPendingUpload: (localUploadID: string) => + this.cancelPendingUpload(threadID, localUploadID), + sendTextMessage: ( + messageInfo: RawTextMessageInfo, + threadInfo: ThreadInfo, + parentThreadInfo: ?ThreadInfo, + ) => + this.sendTextMessage(messageInfo, threadInfo, parentThreadInfo), + createMultimediaMessage: ( + localID: number, + threadInfo: ThreadInfo, + ) => this.createMultimediaMessage(localID, threadInfo), + setDraft: (newDraft: string) => this.setDraft(threadID, newDraft), + setTextCursorPosition: (newPosition: number) => + this.setTextCursorPosition(threadID, newPosition), + messageHasUploadFailure: (localMessageID: string) => + this.messageHasUploadFailure(assignedUploads[localMessageID]), + retryMultimediaMessage: ( + localMessageID: string, + threadInfo: ThreadInfo, + ) => + this.retryMultimediaMessage( + localMessageID, + threadInfo, + assignedUploads[localMessageID], + ), + addReply: (message: string) => this.addReply(message), + addReplyListener: this.addReplyListener, + removeReplyListener: this.removeReplyListener, + registerSendCallback: this.props.registerSendCallback, + unregisterSendCallback: this.props.unregisterSendCallback, + }; + }, + ), + ); - typeaheadStateSelector = createSelector( + typeaheadStateSelector: PropsAndState => TypeaheadInputState = createSelector( (propsAndState: PropsAndState) => propsAndState.typeaheadState, (typeaheadState: TypeaheadState) => ({ typeaheadState, @@ -639,7 +657,10 @@ }), ); - inputStateSelector = createSelector( + inputStateSelector: ({ + +inputBaseState: BaseInputState, + +typeaheadState: TypeaheadInputState, + }) => InputState = createSelector( state => state.inputBaseState, state => state.typeaheadState, (inputBaseState, typeaheadState) => ({ @@ -816,7 +837,7 @@ uploadFiles( threadID: string, uploads: $ReadOnlyArray, - ) { + ): Promise { return Promise.all( uploads.map(upload => this.uploadFile(threadID, upload)), ); @@ -1450,7 +1471,7 @@ messageHasUploadFailure( pendingUploads: ?$ReadOnlyArray, - ) { + ): boolean { if (!pendingUploads) { return false; } @@ -1566,7 +1587,7 @@ ); }; - render() { + render(): React.Node { const { activeChatThreadID } = this.props; // we're going with two selectors as we want to avoid 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 @@ -55,15 +55,13 @@ +accept: ?() => void, }; -// This type represents the input state for a particular thread -export type InputState = { +export type BaseInputState = { +pendingUploads: $ReadOnlyArray, +assignedUploads: { [messageID: string]: $ReadOnlyArray, }, +draft: string, +textCursorPosition: number, - +typeaheadState: TypeaheadState, +appendFiles: ( threadInfo: ThreadInfo, files: $ReadOnlyArray, @@ -77,7 +75,6 @@ +createMultimediaMessage: (localID: number, threadInfo: ThreadInfo) => void, +setDraft: (draft: string) => void, +setTextCursorPosition: (newPosition: number) => void, - +setTypeaheadState: ($Shape) => void, +messageHasUploadFailure: (localMessageID: string) => boolean, +retryMultimediaMessage: ( localMessageID: string, @@ -90,6 +87,17 @@ +unregisterSendCallback: (() => mixed) => void, }; +export type TypeaheadInputState = { + +typeaheadState: TypeaheadState, + +setTypeaheadState: ($Shape) => void, +}; + +// This type represents the input state for a particular thread +export type InputState = { + ...BaseInputState, + ...TypeaheadInputState, +}; + const InputStateContext: React.Context = React.createContext(null); diff --git a/web/modals/history/history-entry.react.js b/web/modals/history/history-entry.react.js --- a/web/modals/history/history-entry.react.js +++ b/web/modals/history/history-entry.react.js @@ -17,6 +17,7 @@ type RestoreEntryInfo, type RestoreEntryResult, type CalendarQuery, + type RestoreEntryPayload, } from 'lib/types/entry-types.js'; import type { LoadingStatus } from 'lib/types/loading-types.js'; import type { ResolvedThreadInfo } from 'lib/types/thread-types.js'; @@ -49,7 +50,7 @@ }; class HistoryEntry extends React.PureComponent { - render() { + render(): React.Node { let deleted = null; if (this.props.entryInfo.deleted) { let restore = null; @@ -133,7 +134,7 @@ this.props.onClick(entryID); }; - async restoreEntryAction() { + async restoreEntryAction(): Promise { const entryID = this.props.entryInfo.id; invariant(entryID, 'entry should have ID'); const result = await this.props.restoreEntry({ diff --git a/web/modals/history/history-modal.react.js b/web/modals/history/history-modal.react.js --- a/web/modals/history/history-modal.react.js +++ b/web/modals/history/history-modal.react.js @@ -71,7 +71,7 @@ +revisions: $ReadOnlyArray, }; class HistoryModal extends React.PureComponent { - static defaultProps = { currentEntryID: null }; + static defaultProps: Partial = { currentEntryID: null }; constructor(props: Props) { super(props); @@ -91,7 +91,7 @@ } } - render() { + render(): React.Node { let allHistoryButton = null; if (this.state.mode === 'entry') { allHistoryButton = ( diff --git a/web/modals/search/message-search-modal.react.js b/web/modals/search/message-search-modal.react.js --- a/web/modals/search/message-search-modal.react.js +++ b/web/modals/search/message-search-modal.react.js @@ -3,6 +3,7 @@ import * as React from 'react'; import { useModalContext } from 'lib/components/modal-provider.react.js'; +import type { ChatMessageInfoItem } from 'lib/selectors/chat-selectors.js'; import type { ThreadInfo } from 'lib/types/thread-types.js'; import { useResolvedThreadInfo } from 'lib/utils/entity-helpers.js'; @@ -40,7 +41,7 @@ }, [setQuery, input, searchMessages, threadInfo.id]); const onKeyDown = React.useCallback( - event => { + (event: SyntheticKeyboardEvent) => { if (event.key === 'Enter') { onPressSearch(); } @@ -76,7 +77,7 @@ }, [clearTooltip, possiblyLoadMoreMessages]); const renderItem = React.useCallback( - item => ( + (item: ChatMessageInfoItem) => ( { + async (event: SyntheticEvent) => { const container = event.target; + invariant(container instanceof HTMLDivElement, 'target not div'); // Load more data when the user is within 1000 pixels of the end const buffer = 1000; diff --git a/web/modals/threads/members/add-members-modal.react.js b/web/modals/threads/members/add-members-modal.react.js --- a/web/modals/threads/members/add-members-modal.react.js +++ b/web/modals/threads/members/add-members-modal.react.js @@ -78,7 +78,7 @@ const userSearchResultsWithENSNames = useENSNames(userSearchResults); const onSwitchUser = React.useCallback( - userID => + (userID: string) => setPendingUsersToAdd(users => { const newUsers = new Set(users); if (newUsers.has(userID)) { diff --git a/web/modals/threads/members/member.react.js b/web/modals/threads/members/member.react.js --- a/web/modals/threads/members/member.react.js +++ b/web/modals/threads/members/member.react.js @@ -44,7 +44,7 @@ const roleName = roles.get(memberInfo.id)?.name; const onMenuChange = React.useCallback( - menuOpen => { + (menuOpen: boolean) => { if (menuOpen) { setOpenMenu(() => memberInfo.id); } else { diff --git a/web/modals/threads/sidebars/sidebar.react.js b/web/modals/threads/sidebars/sidebar.react.js --- a/web/modals/threads/sidebars/sidebar.react.js +++ b/web/modals/threads/sidebars/sidebar.react.js @@ -31,7 +31,7 @@ const navigateToThread = useOnClickThread(threadInfo); const onClickThread = React.useCallback( - event => { + (event: SyntheticEvent) => { popModal(); navigateToThread(event); }, diff --git a/web/modals/threads/subchannels/subchannel.react.js b/web/modals/threads/subchannels/subchannel.react.js --- a/web/modals/threads/subchannels/subchannel.react.js +++ b/web/modals/threads/subchannels/subchannel.react.js @@ -40,7 +40,7 @@ const navigateToThread = useOnClickThread(threadInfo); const onClickThread = React.useCallback( - event => { + (event: SyntheticEvent) => { popModal(); navigateToThread(event); }, diff --git a/web/modals/update-modal.react.js b/web/modals/update-modal.react.js --- a/web/modals/update-modal.react.js +++ b/web/modals/update-modal.react.js @@ -73,7 +73,7 @@ React.useEffect( () => - electron?.onNewVersionAvailable?.(version => { + electron?.onNewVersionAvailable?.((version: string) => { // On these versions we want to update immediately because there's // an issue if the user decides to update 10min after showing the modal if (electron?.version === '1.0.0' || electron?.version === '2.0.0') { diff --git a/web/push-notif/push-notifs-handler.js b/web/push-notif/push-notifs-handler.js --- a/web/push-notif/push-notifs-handler.js +++ b/web/push-notif/push-notifs-handler.js @@ -28,7 +28,7 @@ React.useEffect( () => - electron?.onDeviceTokenRegistered?.(token => { + electron?.onDeviceTokenRegistered?.((token: ?string) => { dispatchActionPromise( setDeviceTokenActionTypes, callSetDeviceToken(token), @@ -41,20 +41,22 @@ React.useEffect( () => - electron?.onNotificationClicked?.(({ threadID }) => { - const convertedThreadID = convertNonPendingIDToNewSchema( - threadID, - ashoatKeyserverID, - ); - - const payload = { - chatMode: 'view', - activeChatThreadID: convertedThreadID, - tab: 'chat', - }; - - dispatch({ type: updateNavInfoActionType, payload }); - }), + electron?.onNotificationClicked?.( + ({ threadID }: { +threadID: string }) => { + const convertedThreadID = convertNonPendingIDToNewSchema( + threadID, + ashoatKeyserverID, + ); + + const payload = { + chatMode: 'view', + activeChatThreadID: convertedThreadID, + tab: 'chat', + }; + + dispatch({ type: updateNavInfoActionType, payload }); + }, + ), [dispatch], ); } diff --git a/web/redux/focus-handler.react.js b/web/redux/focus-handler.react.js --- a/web/redux/focus-handler.react.js +++ b/web/redux/focus-handler.react.js @@ -35,7 +35,7 @@ const dispatch = useDispatch(); const curWindowActive = useSelector(state => state.windowActive); const updateRedux = React.useCallback( - windowActive => { + (windowActive: boolean) => { if (windowActive === curWindowActive) { return; } diff --git a/web/redux/initial-state-gate.js b/web/redux/initial-state-gate.js --- a/web/redux/initial-state-gate.js +++ b/web/redux/initial-state-gate.js @@ -61,7 +61,7 @@ const childFunction = React.useCallback( // This argument is passed from `PersistGate`. It means that the state is // rehydrated and we can start fetching the initial info. - bootstrapped => { + (bootstrapped: boolean) => { if (bootstrapped && initialStateLoaded) { return children; } else { diff --git a/web/redux/persist.js b/web/redux/persist.js --- a/web/redux/persist.js +++ b/web/redux/persist.js @@ -41,7 +41,7 @@ declare var keyserverURL: string; const migrations = { - [1]: async state => { + [1]: async (state: any) => { const { primaryIdentityPublicKey, ...stateWithoutPrimaryIdentityPublicKey @@ -56,7 +56,7 @@ }, }; }, - [2]: async state => { + [2]: async (state: AppState) => { return state; }, [3]: async (state: AppState) => { @@ -90,7 +90,7 @@ return newState; }, - [4]: async state => { + [4]: async (state: any) => { const { lastCommunicatedPlatformDetails, keyserverStore, ...rest } = state; return { @@ -107,7 +107,7 @@ }, }; }, - [5]: async state => { + [5]: async (state: any) => { const databaseModule = await getDatabaseModule(); const isDatabaseSupported = await databaseModule.isDatabaseSupported(); if (!isDatabaseSupported) { @@ -135,7 +135,7 @@ return state; }, - [6]: async state => ({ + [6]: async (state: AppState) => ({ ...state, integrityStore: { threadHashes: {}, threadHashingStatus: 'starting' }, }), @@ -166,11 +166,11 @@ }, }; }, - [8]: async state => ({ + [8]: async (state: AppState) => ({ ...state, globalThemeInfo: defaultGlobalThemeInfo, }), - [9]: async state => ({ + [9]: async (state: AppState) => ({ ...state, keyserverStore: { ...state.keyserverStore, diff --git a/web/redux/visibility-handler.react.js b/web/redux/visibility-handler.react.js --- a/web/redux/visibility-handler.react.js +++ b/web/redux/visibility-handler.react.js @@ -11,9 +11,12 @@ function VisibilityHandler(): React.Node { const visibility = useVisibility(); const [visible, setVisible] = React.useState(!visibility.hidden()); - const onVisibilityChange = React.useCallback((event, state: string) => { - setVisible(state === 'visible'); - }, []); + const onVisibilityChange = React.useCallback( + (event: mixed, state: string) => { + setVisible(state === 'visible'); + }, + [], + ); React.useEffect(() => { const listener = visibility.change(onVisibilityChange); return () => { @@ -24,7 +27,7 @@ const dispatch = useDispatch(); const curForeground = useIsAppForegrounded(); const updateRedux = React.useCallback( - foreground => { + (foreground: boolean) => { if (foreground === curForeground) { return; } diff --git a/web/settings/password-change-modal.js b/web/settings/password-change-modal.js --- a/web/settings/password-change-modal.js +++ b/web/settings/password-change-modal.js @@ -56,7 +56,7 @@ this.newPasswordInput.focus(); } - render() { + render(): React.Node { let errorMsg; if (this.state.errorMessage) { errorMsg = ( @@ -188,7 +188,7 @@ ); }; - async changeUserSettingsAction() { + async changeUserSettingsAction(): Promise { try { await this.props.changeUserPassword({ updatedFields: { diff --git a/web/settings/tunnelbroker-test.react.js b/web/settings/tunnelbroker-test.react.js --- a/web/settings/tunnelbroker-test.react.js +++ b/web/settings/tunnelbroker-test.react.js @@ -25,7 +25,7 @@ const messageInput = React.useRef(null); const onSubmit = React.useCallback( - async event => { + async (event: SyntheticEvent) => { event.preventDefault(); setLoading(true); diff --git a/web/utils/tooltip-action-utils.js b/web/utils/tooltip-action-utils.js --- a/web/utils/tooltip-action-utils.js +++ b/web/utils/tooltip-action-utils.js @@ -410,7 +410,7 @@ }, [messageTimestamp, tooltipActions]); const createMessageTooltip = React.useCallback( - tooltipPositionStyle => ( + (tooltipPositionStyle: TooltipPositionStyle) => ( = { +type MentionTypeaheadSharedParams = { +inputStateDraft: string, +inputStateSetDraft: (draft: string) => mixed, +inputStateSetTextCursorPosition: (newPosition: number) => mixed, - +suggestions: $ReadOnlyArray, +textBeforeAtSymbol: string, +query: string, }; +export type GetTypeaheadTooltipActionsParams = { + ...MentionTypeaheadSharedParams, + +suggestions: $ReadOnlyArray, +}; +export type MentionTypeaheadTooltipActionExecuteHandlerParams = { + ...MentionTypeaheadSharedParams, + +mentionText: string, +}; function mentionTypeaheadTooltipActionExecuteHandler({ textBeforeAtSymbol, inputStateDraft, @@ -90,7 +97,7 @@ mentionText, inputStateSetDraft, inputStateSetTextCursorPosition, -}) { +}: MentionTypeaheadTooltipActionExecuteHandlerParams) { const { newText, newSelectionStart } = getNewTextAndSelection( textBeforeAtSymbol, inputStateDraft,