diff --git a/web/redux/nav-reducer.js b/web/redux/nav-reducer.js index dc903e20c..4048db3f2 100644 --- a/web/redux/nav-reducer.js +++ b/web/redux/nav-reducer.js @@ -1,14 +1,37 @@ // @flow +import { pendingToRealizedThreadIDsSelector } from 'lib/selectors/thread-selectors'; +import type { RawThreadInfo } from 'lib/types/thread-types'; + import type { Action } from '../redux/redux-setup'; import { type NavInfo, updateNavInfoActionType } from '../types/nav-types'; -export default function reduceNavInfo(state: NavInfo, action: Action): NavInfo { +export default function reduceNavInfo( + oldState: NavInfo, + action: Action, + newThreadInfos: { [id: string]: RawThreadInfo }, +): NavInfo { + let state = oldState; if (action.type === updateNavInfoActionType) { - return { + state = { ...state, ...action.payload, }; } + + const { activeChatThreadID } = state; + if (activeChatThreadID) { + const pendingToRealizedThreadIDs = pendingToRealizedThreadIDsSelector( + newThreadInfos, + ); + const realizedThreadID = pendingToRealizedThreadIDs.get(activeChatThreadID); + if (realizedThreadID) { + state = { + ...state, + activeChatThreadID: realizedThreadID, + }; + } + } + return state; } diff --git a/web/redux/redux-setup.js b/web/redux/redux-setup.js index faaa3fd45..35e4765f8 100644 --- a/web/redux/redux-setup.js +++ b/web/redux/redux-setup.js @@ -1,201 +1,205 @@ // @flow import invariant from 'invariant'; import { logOutActionTypes, deleteAccountActionTypes, } from 'lib/actions/user-actions'; import baseReducer from 'lib/reducers/master-reducer'; import { mostRecentReadThreadSelector } from 'lib/selectors/thread-selectors'; import { invalidSessionDowngrade } from 'lib/shared/account-utils'; import type { Shape } from 'lib/types/core'; import type { EntryStore } from 'lib/types/entry-types'; import type { CalendarFilter } from 'lib/types/filter-types'; import type { LifecycleState } from 'lib/types/lifecycle-state-types'; import type { LoadingStatus } from 'lib/types/loading-types'; import type { MessageStore } from 'lib/types/message-types'; import type { BaseAction } from 'lib/types/redux-types'; import type { ClientReportCreationRequest } from 'lib/types/report-types'; import type { ConnectionInfo } from 'lib/types/socket-types'; import type { ThreadStore } from 'lib/types/thread-types'; import type { CurrentUserInfo, UserStore } from 'lib/types/user-types'; import type { ServerVerificationResult } from 'lib/types/verify-types'; import { setNewSessionActionType } from 'lib/utils/action-utils'; import { activeThreadSelector } from '../selectors/nav-selectors'; import { type NavInfo, updateNavInfoActionType } from '../types/nav-types'; import { updateWindowActiveActionType } from './action-types'; import reduceNavInfo from './nav-reducer'; import { getVisibility } from './visibility'; export type WindowDimensions = {| width: number, height: number |}; export type AppState = {| navInfo: NavInfo, currentUserInfo: ?CurrentUserInfo, sessionID: ?string, serverVerificationResult: ?ServerVerificationResult, entryStore: EntryStore, threadStore: ThreadStore, userStore: UserStore, messageStore: MessageStore, updatesCurrentAsOf: number, loadingStatuses: { [key: string]: { [idx: number]: LoadingStatus } }, calendarFilters: $ReadOnlyArray, urlPrefix: string, windowDimensions: WindowDimensions, cookie?: void, deviceToken?: void, baseHref: string, connection: ConnectionInfo, watchedThreadIDs: $ReadOnlyArray, lifecycleState: LifecycleState, nextLocalID: number, queuedReports: $ReadOnlyArray, timeZone: ?string, userAgent: ?string, dataLoaded: boolean, windowActive: boolean, |}; export const updateWindowDimensions = 'UPDATE_WINDOW_DIMENSIONS'; export type Action = | BaseAction | {| type: 'UPDATE_NAV_INFO', payload: Shape |} | {| type: 'UPDATE_WINDOW_DIMENSIONS', payload: WindowDimensions, |} | {| type: 'UPDATE_WINDOW_ACTIVE', payload: boolean, |}; export function reducer(oldState: AppState | void, action: Action) { invariant(oldState, 'should be set'); let state = oldState; if (action.type === updateWindowDimensions) { return validateState(oldState, { ...state, windowDimensions: action.payload, }); } else if (action.type === updateWindowActiveActionType) { return validateState(oldState, { ...state, windowActive: action.payload, }); } else if (action.type === setNewSessionActionType) { if ( invalidSessionDowngrade( oldState, action.payload.sessionChange.currentUserInfo, action.payload.preRequestUserState, ) ) { return oldState; } state = { ...state, sessionID: action.payload.sessionChange.sessionID, }; } else if ( (action.type === logOutActionTypes.success && invalidSessionDowngrade( oldState, action.payload.currentUserInfo, action.payload.preRequestUserState, )) || (action.type === deleteAccountActionTypes.success && invalidSessionDowngrade( oldState, action.payload.currentUserInfo, action.payload.preRequestUserState, )) ) { return oldState; } if (action.type !== updateNavInfoActionType) { state = baseReducer(state, action); } state = { ...state, - navInfo: reduceNavInfo(state.navInfo, action), + navInfo: reduceNavInfo( + state.navInfo, + action, + state.threadStore.threadInfos, + ), }; return validateState(oldState, state); } function validateState(oldState: AppState, state: AppState): AppState { if ( state.navInfo.activeChatThreadID && !state.navInfo.pendingThread && !state.threadStore.threadInfos[state.navInfo.activeChatThreadID] ) { // Makes sure the active thread always exists state = { ...state, navInfo: { ...state.navInfo, activeChatThreadID: mostRecentReadThreadSelector(state), }, }; } const activeThread = activeThreadSelector(state); if ( activeThread && !getVisibility().hidden() && typeof document !== 'undefined' && document && document.hasFocus && document.hasFocus() && !state.navInfo.pendingThread && state.threadStore.threadInfos[activeThread].currentUser.unread ) { // Makes sure a currently focused thread is never unread state = { ...state, threadStore: { ...state.threadStore, threadInfos: { ...state.threadStore.threadInfos, [activeThread]: { ...state.threadStore.threadInfos[activeThread], currentUser: { ...state.threadStore.threadInfos[activeThread].currentUser, unread: false, }, }, }, }, }; } const oldActiveThread = activeThreadSelector(oldState); if ( activeThread && oldActiveThread !== activeThread && state.messageStore.threads[activeThread] ) { // Update messageStore.threads[activeThread].lastNavigatedTo state = { ...state, messageStore: { ...state.messageStore, threads: { ...state.messageStore.threads, [activeThread]: { ...state.messageStore.threads[activeThread], lastNavigatedTo: Date.now(), }, }, }, }; } return state; }