Skip to content
25 changes: 14 additions & 11 deletions src/account/accountsSelectors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* @flow strict-local */
import { createSelector } from 'reselect';
import type { Account, Auth, GlobalState, Identity, Selector } from '../types';
import type { Account, Auth, GlobalState, Identity, GlobalSelector } from '../types';
import { getAccounts } from '../directSelectors';
import { identityOfAccount, keyOfIdentity, identityOfAuth, authOfAccount } from './accountMisc';
import { ZulipVersion } from '../utils/zulipVersion';
Expand All @@ -14,22 +14,22 @@ export type AccountStatus = {| ...Identity, isLoggedIn: boolean |};
* This should be used in preference to `getAccounts` where we don't
* actually need the API keys, but just need to know whether we have them.
*/
export const getAccountStatuses: Selector<
export const getAccountStatuses: GlobalSelector<
$ReadOnlyArray<AccountStatus>,
> = createSelector(getAccounts, accounts =>
accounts.map(({ realm, email, apiKey }) => ({ realm, email, isLoggedIn: apiKey !== '' })),
);

/** The list of known accounts, reduced to `Identity`. */
export const getIdentities: Selector<$ReadOnlyArray<Identity>> = createSelector(
export const getIdentities: GlobalSelector<$ReadOnlyArray<Identity>> = createSelector(
getAccounts,
accounts => accounts.map(identityOfAccount),
);

/**
* All known accounts, indexed by identity.
*/
export const getAccountsByIdentity: Selector<(Identity) => Account | void> = createSelector(
export const getAccountsByIdentity: GlobalSelector<(Identity) => Account | void> = createSelector(
getAccounts,
accounts => {
const map = new Map(
Expand Down Expand Up @@ -86,12 +86,15 @@ export const getCurrentRealm = (state: GlobalState): URL => getActiveAccount(sta
* * `getAuth` for use in the bulk of the app, operating on a logged-in
* active account.
*/
export const tryGetAuth: Selector<Auth | void> = createSelector(tryGetActiveAccount, account => {
if (!account || account.apiKey === '') {
return undefined;
}
return authOfAccount(account);
});
export const tryGetAuth: GlobalSelector<Auth | void> = createSelector(
tryGetActiveAccount,
account => {
if (!account || account.apiKey === '') {
return undefined;
}
return authOfAccount(account);
},
);

/**
* True just if there is an active, logged-in account.
Expand Down Expand Up @@ -123,7 +126,7 @@ export const getAuth = (state: GlobalState): Auth => {
*
* See `getAuth` and `tryGetAuth` for discussion.
*/
export const getIdentity: Selector<Identity> = createSelector(getAuth, auth =>
export const getIdentity: GlobalSelector<Identity> = createSelector(getAuth, auth =>
identityOfAuth(auth),
);

Expand Down
6 changes: 3 additions & 3 deletions src/caughtup/caughtUpSelectors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* @flow strict-local */
import type { CaughtUp, CaughtUpState, GlobalState, Narrow } from '../types';
import type { CaughtUp, CaughtUpState, PerAccountState, Narrow } from '../types';
import { NULL_OBJECT } from '../nullObjects';
import { keyFromNarrow } from '../utils/narrow';

Expand All @@ -9,7 +9,7 @@ export const DEFAULT_CAUGHTUP: CaughtUp = {
newer: false,
};

export const getCaughtUp = (state: GlobalState): CaughtUpState => state.caughtUp || NULL_OBJECT;
export const getCaughtUp = (state: PerAccountState): CaughtUpState => state.caughtUp || NULL_OBJECT;

export const getCaughtUpForNarrow = (state: GlobalState, narrow: Narrow): CaughtUp =>
export const getCaughtUpForNarrow = (state: PerAccountState, narrow: Narrow): CaughtUp =>
getCaughtUp(state)[keyFromNarrow(narrow)] || DEFAULT_CAUGHTUP;
4 changes: 2 additions & 2 deletions src/chat/fetchingSelectors.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/* @flow strict-local */
import type { Fetching, GlobalState, Narrow } from '../types';
import type { Fetching, PerAccountState, Narrow } from '../types';
import { getFetching } from '../directSelectors';
import { keyFromNarrow } from '../utils/narrow';

/** The value implicitly represented by a missing entry in FetchingState. */
export const DEFAULT_FETCHING = { older: false, newer: false };

export const getFetchingForNarrow = (state: GlobalState, narrow: Narrow): Fetching =>
export const getFetchingForNarrow = (state: PerAccountState, narrow: Narrow): Fetching =>
getFetching(state)[keyFromNarrow(narrow)] || DEFAULT_FETCHING;
8 changes: 4 additions & 4 deletions src/chat/narrowsSelectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import isEqual from 'lodash.isequal';
import { createSelector } from 'reselect';

import type {
GlobalState,
PerAccountState,
Message,
Narrow,
Outbox,
Expand Down Expand Up @@ -61,7 +61,7 @@ export const outboxMessagesForNarrow: Selector<$ReadOnlyArray<Outbox>, Narrow> =
);

export const getFetchedMessageIdsForNarrow = (
state: GlobalState,
state: PerAccountState,
narrow: Narrow,
): $ReadOnlyArray<number> => getAllNarrows(state).get(keyFromNarrow(narrow)) || NULL_ARRAY;

Expand Down Expand Up @@ -181,12 +181,12 @@ export const getShownMessagesForNarrow: Selector<$ReadOnlyArray<Message | Outbox
}),
);

export const getFirstMessageId = (state: GlobalState, narrow: Narrow): number | void => {
export const getFirstMessageId = (state: PerAccountState, narrow: Narrow): number | void => {
const ids = getFetchedMessageIdsForNarrow(state, narrow);
return ids.length > 0 ? ids[0] : undefined;
};

export const getLastMessageId = (state: GlobalState, narrow: Narrow): number | void => {
export const getLastMessageId = (state: PerAccountState, narrow: Narrow): number | void => {
const ids = getFetchedMessageIdsForNarrow(state, narrow);
return ids.length > 0 ? ids[ids.length - 1] : undefined;
};
Expand Down
50 changes: 26 additions & 24 deletions src/directSelectors.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* @flow strict-local */
import type {
PerAccountState,
GlobalState,
AccountsState,
SubscriptionsState,
Expand Down Expand Up @@ -36,66 +37,67 @@ export const getIsOnline = (state: GlobalState): boolean | null => state.session
export const getDebug = (state: GlobalState): Debug => state.session.debug;
export const getIsHydrated = (state: GlobalState): boolean => state.session.isHydrated;

export const getCanCreateStreams = (state: GlobalState): boolean => state.realm.canCreateStreams;
export const getCanCreateStreams = (state: PerAccountState): boolean =>
state.realm.canCreateStreams;

export const getDrafts = (state: GlobalState): DraftsState => state.drafts;
export const getDrafts = (state: PerAccountState): DraftsState => state.drafts;

export const getLoading = (state: GlobalState): boolean => state.session.loading;
export const getLoading = (state: PerAccountState): boolean => state.session.loading;

export const getMessages = (state: GlobalState): MessagesState => state.messages;
export const getMessages = (state: PerAccountState): MessagesState => state.messages;

export const getMute = (state: GlobalState): MuteState => state.mute;
export const getMute = (state: PerAccountState): MuteState => state.mute;

export const getMutedUsers = (state: GlobalState): MutedUsersState => state.mutedUsers;
export const getMutedUsers = (state: PerAccountState): MutedUsersState => state.mutedUsers;

export const getTyping = (state: GlobalState): TypingState => state.typing;
export const getTyping = (state: PerAccountState): TypingState => state.typing;

export const getTopics = (state: GlobalState): TopicsState => state.topics;
export const getTopics = (state: PerAccountState): TopicsState => state.topics;

export const getUserGroups = (state: GlobalState): UserGroupsState => state.userGroups;
export const getUserGroups = (state: PerAccountState): UserGroupsState => state.userGroups;

export const getUserStatus = (state: GlobalState): UserStatusState => state.userStatus;
export const getUserStatus = (state: PerAccountState): UserStatusState => state.userStatus;

/**
* WARNING: despite the name, only (a) `is_active` users (b) excluding cross-realm bots.
*
* See `getAllUsers`.
*/
export const getUsers = (state: GlobalState): UsersState => state.users;
export const getUsers = (state: PerAccountState): UsersState => state.users;

export const getFetching = (state: GlobalState): FetchingState => state.fetching;
export const getFetching = (state: PerAccountState): FetchingState => state.fetching;

export const getFlags = (state: GlobalState): FlagsState => state.flags;
export const getFlags = (state: PerAccountState): FlagsState => state.flags;

export const getAllNarrows = (state: GlobalState): NarrowsState => state.narrows;
export const getAllNarrows = (state: PerAccountState): NarrowsState => state.narrows;

export const getSettings = (state: GlobalState): SettingsState => state.settings;

export const getSubscriptions = (state: GlobalState): SubscriptionsState => state.subscriptions;
export const getSubscriptions = (state: PerAccountState): SubscriptionsState => state.subscriptions;

/**
* All streams in the current realm.
*
* This is rarely the right selector to use: consider `getStreamForId`
* or `getStreamsById` instead.
*/
export const getStreams = (state: GlobalState): StreamsState => state.streams;
export const getStreams = (state: PerAccountState): StreamsState => state.streams;

export const getPresence = (state: GlobalState): PresenceState => state.presence;
export const getPresence = (state: PerAccountState): PresenceState => state.presence;

export const getOutbox = (state: GlobalState): OutboxState => state.outbox;
export const getOutbox = (state: PerAccountState): OutboxState => state.outbox;

export const getRealm = (state: GlobalState): RealmState => state.realm;
export const getRealm = (state: PerAccountState): RealmState => state.realm;

export const getCrossRealmBots = (state: GlobalState): $ReadOnlyArray<CrossRealmBot> =>
export const getCrossRealmBots = (state: PerAccountState): $ReadOnlyArray<CrossRealmBot> =>
state.realm.crossRealmBots;

export const getRawRealmEmoji = (state: GlobalState): RealmEmojiById => state.realm.emoji;
export const getRawRealmEmoji = (state: PerAccountState): RealmEmojiById => state.realm.emoji;

export const getNonActiveUsers = (state: GlobalState): $ReadOnlyArray<User> =>
export const getNonActiveUsers = (state: PerAccountState): $ReadOnlyArray<User> =>
state.realm.nonActiveUsers;

export const getIsAdmin = (state: GlobalState): boolean => state.realm.isAdmin;
export const getIsAdmin = (state: PerAccountState): boolean => state.realm.isAdmin;

export const getVideoChatProvider = (state: GlobalState): VideoChatProvider | null =>
export const getVideoChatProvider = (state: PerAccountState): VideoChatProvider | null =>
state.realm.videoChatProvider;
4 changes: 2 additions & 2 deletions src/drafts/draftsSelectors.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* @flow strict-local */
import type { Narrow, GlobalState } from '../types';
import type { Narrow, PerAccountState } from '../types';
import { keyFromNarrow } from '../utils/narrow';

export const getDraftForNarrow = (state: GlobalState, narrow: Narrow): string =>
export const getDraftForNarrow = (state: PerAccountState, narrow: Narrow): string =>
state.drafts[keyFromNarrow(narrow)] || '';
3 changes: 2 additions & 1 deletion src/emoji/emojiSelectors.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/* @flow strict-local */
import { createSelector } from 'reselect';
import type { Selector, RealmEmojiById, ImageEmojiType } from '../types';
import { assumeSecretlyGlobalState } from '../reduxTypes';
import { getRawRealmEmoji } from '../directSelectors';
import { getIdentity } from '../account/accountsSelectors';
import zulipExtraEmojiMap from './zulipExtraEmojiMap';
import { objectFromEntries } from '../jsBackport';

export const getAllImageEmojiById: Selector<RealmEmojiById> = createSelector(
getIdentity,
state => getIdentity(assumeSecretlyGlobalState(state)),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

redux types: Add two assumeSecretlyGlobalState markings

Just to check my understanding: Is it intentional that getIdentity is still a Selector at the time this change is made, and the switch to GlobalSelector comes after?

In the commit message, I see "[…] invokes one of those selectors (which demands a GlobalState)", and I think I'm a little confused because at this commit getIdentity doesn't yet demand a GlobalState, by that name, anyway.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess that is kind of talking about the future. Probably it'd be clearer to switch things to GlobalSelector where appropriate, then add these two markings, then switch other selectors to PerAccountState where appropriate (and then make the change that distinguishes PerAccountState from GlobalState.) I'll quick make that change.

getRawRealmEmoji,
(identity, realmEmoji) => {
const result: {| [string]: ImageEmojiType |} = {};
Expand Down
7 changes: 4 additions & 3 deletions src/pm-conversations/pmConversationsSelectors.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import invariant from 'invariant';
import { createSelector } from 'reselect';

import type { GlobalState, Message, PmConversationData, Selector } from '../types';
import type { PerAccountState, Message, PmConversationData, Selector } from '../types';
import { assumeSecretlyGlobalState } from '../reduxTypes';
import { getPrivateMessages } from '../message/messageSelectors';
import { getAllUsersById, getOwnUserId } from '../users/userSelectors';
import { getUnreadByPms, getUnreadByHuddles } from '../unread/unreadSelectors';
Expand Down Expand Up @@ -121,14 +122,14 @@ function getRecentConversationsModernImpl(
}

const getServerIsOld: Selector<boolean> = createSelector(
getServerVersion,
state => getServerVersion(assumeSecretlyGlobalState(state)),
version => !(version && version.isAtLeast(model.MIN_RECENTPMS_SERVER_VERSION)),
);

/**
* The most recent PM conversations, with unread count and latest message ID.
*/
export const getRecentConversations = (state: GlobalState): PmConversationData[] =>
export const getRecentConversations = (state: PerAccountState): PmConversationData[] =>
getServerIsOld(state) ? getRecentConversationsLegacy(state) : getRecentConversationsModern(state);

export const getUnreadConversations: Selector<
Expand Down
4 changes: 2 additions & 2 deletions src/redux.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* @flow strict-local */
import type { Store } from 'redux';

import type { Selector, GlobalState, Action } from './reduxTypes';
import type { GlobalSelector, GlobalState, Action } from './reduxTypes';

/**
* Call a function whenever a selected piece of state changes.
Expand All @@ -15,7 +15,7 @@ import type { Selector, GlobalState, Action } from './reduxTypes';
*/
export function observeStore<T>(
store: Store<GlobalState, Action>,
select: Selector<T>,
select: GlobalSelector<T>,
onChange: (state: T) => void,
): () => void {
// Start as a nonce object, so the initial `prevState !== state`
Expand Down
Loading