diff --git a/lib/reducers/calendar-filters-reducer.js b/lib/reducers/calendar-filters-reducer.js index b0e6737b1..cbf2fb07e 100644 --- a/lib/reducers/calendar-filters-reducer.js +++ b/lib/reducers/calendar-filters-reducer.js @@ -1,194 +1,177 @@ // @flow import { updateCalendarCommunityFilter, clearCalendarCommunityFilter, } from '../actions/community-actions.js'; import { siweAuthActionTypes } from '../actions/siwe-actions.js'; import { newThreadActionTypes, joinThreadActionTypes, leaveThreadActionTypes, deleteThreadActionTypes, } from '../actions/thread-actions.js'; import { logOutActionTypes, deleteAccountActionTypes, logInActionTypes, registerActionTypes, } from '../actions/user-actions.js'; import { filteredThreadIDs, nonThreadCalendarFilters, nonExcludeDeletedCalendarFilters, } from '../selectors/calendar-filter-selectors.js'; import { threadInFilterList } from '../shared/thread-utils.js'; +import { updateSpecs } from '../shared/updates/update-specs.js'; import { type CalendarFilter, defaultCalendarFilters, updateCalendarThreadFilter, clearCalendarThreadFilter, setCalendarDeletedFilter, calendarThreadFilterTypes, } from '../types/filter-types.js'; import type { BaseAction } from '../types/redux-types.js'; import { fullStateSyncActionType, incrementalStateSyncActionType, } from '../types/socket-types.js'; import type { RawThreadInfo, ThreadStore } from '../types/thread-types.js'; -import { updateTypes } from '../types/update-types-enum.js'; import { type ClientUpdateInfo, processUpdatesActionType, } from '../types/update-types.js'; import { setNewSessionActionType } from '../utils/action-utils.js'; import { filterThreadIDsBelongingToCommunity } from '../utils/drawer-utils.react.js'; export default function reduceCalendarFilters( state: $ReadOnlyArray, action: BaseAction, threadStore: ThreadStore, ): $ReadOnlyArray { if ( action.type === logOutActionTypes.success || action.type === deleteAccountActionTypes.success || action.type === logInActionTypes.success || action.type === siweAuthActionTypes.success || action.type === registerActionTypes.success || (action.type === setNewSessionActionType && action.payload.sessionChange.cookieInvalidated) ) { return defaultCalendarFilters; } else if (action.type === updateCalendarThreadFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); return [ ...nonThreadFilters, { type: calendarThreadFilterTypes.THREAD_LIST, threadIDs: action.payload.threadIDs, }, ]; } else if (action.type === clearCalendarThreadFilter) { return nonThreadCalendarFilters(state); } else if (action.type === setCalendarDeletedFilter) { const otherFilters = nonExcludeDeletedCalendarFilters(state); if (action.payload.includeDeleted && otherFilters.length === state.length) { // Attempting to remove NOT_DELETED filter, but it doesn't exist return state; } else if (action.payload.includeDeleted) { // Removing NOT_DELETED filter return otherFilters; } else if (otherFilters.length < state.length) { // Attempting to add NOT_DELETED filter, but it already exists return state; } else { // Adding NOT_DELETED filter return [...state, { type: calendarThreadFilterTypes.NOT_DELETED }]; } } else if ( action.type === newThreadActionTypes.success || action.type === joinThreadActionTypes.success || action.type === leaveThreadActionTypes.success || action.type === deleteThreadActionTypes.success || action.type === processUpdatesActionType ) { return updateFilterListFromUpdateInfos( state, action.payload.updatesResult.newUpdates, ); } else if (action.type === incrementalStateSyncActionType) { return updateFilterListFromUpdateInfos( state, action.payload.updatesResult.newUpdates, ); } else if (action.type === fullStateSyncActionType) { return removeDeletedThreadIDsFromFilterList( state, action.payload.threadInfos, ); } else if (action.type === updateCalendarCommunityFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); const threadIDs = Array.from( filterThreadIDsBelongingToCommunity( action.payload, threadStore.threadInfos, ), ); return [ ...nonThreadFilters, { type: calendarThreadFilterTypes.THREAD_LIST, threadIDs, }, ]; } else if (action.type === clearCalendarCommunityFilter) { const nonThreadFilters = nonThreadCalendarFilters(state); return nonThreadFilters; } return state; } function updateFilterListFromUpdateInfos( state: $ReadOnlyArray, updateInfos: $ReadOnlyArray, ): $ReadOnlyArray { const currentlyFilteredIDs = filteredThreadIDs(state); if (!currentlyFilteredIDs) { return state; } - let changeOccurred = false; - for (const update of updateInfos) { - if (update.type === updateTypes.DELETE_THREAD) { - const result = currentlyFilteredIDs.delete(update.threadID); - if (result) { - changeOccurred = true; - } - } else if (update.type === updateTypes.JOIN_THREAD) { - if ( - !threadInFilterList(update.threadInfo) || - currentlyFilteredIDs.has(update.threadInfo.id) - ) { - continue; - } - currentlyFilteredIDs.add(update.threadInfo.id); - changeOccurred = true; - } else if (update.type === updateTypes.UPDATE_THREAD) { - if (threadInFilterList(update.threadInfo)) { - continue; - } - const result = currentlyFilteredIDs.delete(update.threadInfo.id); - if (result) { - changeOccurred = true; - } - } - } - if (changeOccurred) { + const newFilteredThreadIDs = updateInfos.reduce( + (reducedFilteredThreadIDs, update) => { + const { reduceCalendarThreadFilters } = updateSpecs[update.type]; + return reduceCalendarThreadFilters + ? reduceCalendarThreadFilters(reducedFilteredThreadIDs, update) + : reducedFilteredThreadIDs; + }, + currentlyFilteredIDs, + ); + if (currentlyFilteredIDs !== newFilteredThreadIDs) { return [ ...nonThreadCalendarFilters(state), - { type: 'threads', threadIDs: [...currentlyFilteredIDs] }, + { type: 'threads', threadIDs: [...newFilteredThreadIDs] }, ]; } return state; } function removeDeletedThreadIDsFromFilterList( state: $ReadOnlyArray, threadInfos: { +[id: string]: RawThreadInfo }, ): $ReadOnlyArray { const currentlyFilteredIDs = filteredThreadIDs(state); if (!currentlyFilteredIDs) { return state; } const filtered = [...currentlyFilteredIDs].filter(threadID => threadInFilterList(threadInfos[threadID]), ); if (filtered.length < currentlyFilteredIDs.size) { return [ ...nonThreadCalendarFilters(state), { type: 'threads', threadIDs: filtered }, ]; } return state; } diff --git a/lib/shared/updates/delete-thread-spec.js b/lib/shared/updates/delete-thread-spec.js index dd1ddacec..bae1e970c 100644 --- a/lib/shared/updates/delete-thread-spec.js +++ b/lib/shared/updates/delete-thread-spec.js @@ -1,25 +1,36 @@ // @flow import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; import type { ThreadDeletionUpdateInfo } from '../../types/update-types.js'; export const deleteThreadSpec: UpdateSpec = Object.freeze({ generateOpsForThreadUpdates( storeThreadInfos: RawThreadInfos, update: ThreadDeletionUpdateInfo, ) { if (storeThreadInfos[update.threadID]) { return [ { type: 'remove', payload: { ids: [update.threadID], }, }, ]; } return null; }, + reduceCalendarThreadFilters( + filteredThreadIDs: $ReadOnlySet, + update: ThreadDeletionUpdateInfo, + ) { + if (!filteredThreadIDs.has(update.threadID)) { + return filteredThreadIDs; + } + return new Set( + [...filteredThreadIDs].filter(id => id !== update.threadID), + ); + }, }); diff --git a/lib/shared/updates/join-thread-spec.js b/lib/shared/updates/join-thread-spec.js index 37d842227..55442f085 100644 --- a/lib/shared/updates/join-thread-spec.js +++ b/lib/shared/updates/join-thread-spec.js @@ -1,42 +1,55 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import type { UpdateSpec } from './update-spec.js'; import type { RawEntryInfo } from '../../types/entry-types.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; import type { ThreadJoinUpdateInfo } from '../../types/update-types.js'; +import { threadInFilterList } from '../thread-utils.js'; export const joinThreadSpec: UpdateSpec = Object.freeze({ generateOpsForThreadUpdates( storeThreadInfos: RawThreadInfos, update: ThreadJoinUpdateInfo, ) { if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { return null; } return [ { type: 'replace', payload: { id: update.threadInfo.id, threadInfo: update.threadInfo, }, }, ]; }, mergeEntryInfos( entryIDs: Set, mergedEntryInfos: Array, update: ThreadJoinUpdateInfo, ) { for (const entryInfo of update.rawEntryInfos) { const entryID = entryInfo.id; if (!entryID || entryIDs.has(entryID)) { continue; } mergedEntryInfos.push(entryInfo); entryIDs.add(entryID); } }, + reduceCalendarThreadFilters( + filteredThreadIDs: $ReadOnlySet, + update: ThreadJoinUpdateInfo, + ) { + if ( + !threadInFilterList(update.threadInfo) || + filteredThreadIDs.has(update.threadInfo.id) + ) { + return filteredThreadIDs; + } + return new Set([...filteredThreadIDs, update.threadInfo.id]); + }, }); diff --git a/lib/shared/updates/update-spec.js b/lib/shared/updates/update-spec.js index f687d0db7..bbb47eef2 100644 --- a/lib/shared/updates/update-spec.js +++ b/lib/shared/updates/update-spec.js @@ -1,24 +1,28 @@ // @flow import type { ThreadStoreOperation } from '../../ops/thread-store-ops.js'; import type { RawEntryInfo } from '../../types/entry-types.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; import type { ClientUpdateInfo } from '../../types/update-types.js'; import type { CurrentUserInfo, UserInfos } from '../../types/user-types.js'; export type UpdateSpec = { +generateOpsForThreadUpdates?: ( storeThreadInfos: RawThreadInfos, update: UpdateInfo, ) => ?$ReadOnlyArray, +mergeEntryInfos?: ( entryIDs: Set, mergedEntryInfos: Array, update: UpdateInfo, ) => void, +reduceCurrentUser?: ( state: ?CurrentUserInfo, update: UpdateInfo, ) => ?CurrentUserInfo, +reduceUserInfos?: (state: UserInfos, update: UpdateInfo) => UserInfos, + +reduceCalendarThreadFilters?: ( + filteredThreadIDs: $ReadOnlySet, + update: UpdateInfo, + ) => $ReadOnlySet, }; diff --git a/lib/shared/updates/update-thread-spec.js b/lib/shared/updates/update-thread-spec.js index 34d8d9569..ac196ce6d 100644 --- a/lib/shared/updates/update-thread-spec.js +++ b/lib/shared/updates/update-thread-spec.js @@ -1,27 +1,42 @@ // @flow import _isEqual from 'lodash/fp/isEqual.js'; import type { UpdateSpec } from './update-spec.js'; import type { RawThreadInfos } from '../../types/thread-types.js'; import type { ThreadUpdateInfo } from '../../types/update-types.js'; +import { threadInFilterList } from '../thread-utils.js'; export const updateThreadSpec: UpdateSpec = Object.freeze({ generateOpsForThreadUpdates( storeThreadInfos: RawThreadInfos, update: ThreadUpdateInfo, ) { if (_isEqual(storeThreadInfos[update.threadInfo.id])(update.threadInfo)) { return null; } return [ { type: 'replace', payload: { id: update.threadInfo.id, threadInfo: update.threadInfo, }, }, ]; }, + reduceCalendarThreadFilters( + filteredThreadIDs: $ReadOnlySet, + update: ThreadUpdateInfo, + ) { + if ( + threadInFilterList(update.threadInfo) || + !filteredThreadIDs.has(update.threadInfo.id) + ) { + return filteredThreadIDs; + } + return new Set( + [...filteredThreadIDs].filter(id => id !== update.threadInfo.id), + ); + }, });