Changeset View
Changeset View
Standalone View
Standalone View
lib/components/chat-mention-provider.react.js
// @flow | // @flow | ||||
import * as React from 'react'; | import * as React from 'react'; | ||||
import genesis from '../facts/genesis.js'; | import genesis from '../facts/genesis.js'; | ||||
import { threadInfoSelector } from '../selectors/thread-selectors.js'; | import { threadInfoSelector } from '../selectors/thread-selectors.js'; | ||||
import SentencePrefixSearchIndex from '../shared/sentence-prefix-search-index.js'; | import SentencePrefixSearchIndex from '../shared/sentence-prefix-search-index.js'; | ||||
import { threadTypes } from '../types/thread-types-enum.js'; | import { threadTypes } from '../types/thread-types-enum.js'; | ||||
import type { | import type { | ||||
ChatMentionCandidates, | |||||
ChatMentionCandidatesObj, | ChatMentionCandidatesObj, | ||||
ChatMentionCandidate, | |||||
ResolvedThreadInfo, | ResolvedThreadInfo, | ||||
ThreadInfo, | ThreadInfo, | ||||
} from '../types/thread-types.js'; | } from '../types/thread-types.js'; | ||||
import { useResolvedThreadInfosObj } from '../utils/entity-helpers.js'; | import { useResolvedThreadInfosObj } from '../utils/entity-helpers.js'; | ||||
import { useSelector } from '../utils/redux-utils.js'; | import { useSelector } from '../utils/redux-utils.js'; | ||||
type Props = { | type Props = { | ||||
+children: React.Node, | +children: React.Node, | ||||
▲ Show 20 Lines • Show All 55 Lines • ▼ Show 20 Lines | function getChatMentionCandidates( | ||||
threadInfos: { +[id: string]: ThreadInfo }, | threadInfos: { +[id: string]: ThreadInfo }, | ||||
resolvedThreadInfos: { +[id: string]: ResolvedThreadInfo }, | resolvedThreadInfos: { +[id: string]: ResolvedThreadInfo }, | ||||
): { | ): { | ||||
chatMentionCandidatesObj: ChatMentionCandidatesObj, | chatMentionCandidatesObj: ChatMentionCandidatesObj, | ||||
communityThreadIDForGenesisThreads: { +[id: string]: string }, | communityThreadIDForGenesisThreads: { +[id: string]: string }, | ||||
} { | } { | ||||
const result: { | const result: { | ||||
[string]: { | [string]: { | ||||
[string]: ResolvedThreadInfo, | [string]: ChatMentionCandidate, | ||||
}, | }, | ||||
} = {}; | } = {}; | ||||
const visitedGenesisThreads = new Set<string>(); | const visitedGenesisThreads = new Set<string>(); | ||||
const communityThreadIDForGenesisThreads: { [string]: string } = {}; | const communityThreadIDForGenesisThreads: { [string]: string } = {}; | ||||
for (const currentThreadID in resolvedThreadInfos) { | for (const currentThreadID in resolvedThreadInfos) { | ||||
const currentResolvedThreadInfo = resolvedThreadInfos[currentThreadID]; | const currentResolvedThreadInfo = resolvedThreadInfos[currentThreadID]; | ||||
const { community: currentThreadCommunity } = currentResolvedThreadInfo; | const { community: currentThreadCommunity } = currentResolvedThreadInfo; | ||||
if (!currentThreadCommunity) { | if (!currentThreadCommunity) { | ||||
if (!result[currentThreadID]) { | if (!result[currentThreadID]) { | ||||
result[currentThreadID] = { | result[currentThreadID] = { | ||||
[currentThreadID]: currentResolvedThreadInfo, | [currentThreadID]: { | ||||
threadInfo: currentResolvedThreadInfo, | |||||
rawChatName: threadInfos[currentThreadID].uiName, | |||||
}, | |||||
}; | }; | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
if (!result[currentThreadCommunity]) { | if (!result[currentThreadCommunity]) { | ||||
result[currentThreadCommunity] = {}; | result[currentThreadCommunity] = {}; | ||||
result[currentThreadCommunity][currentThreadCommunity] = | result[currentThreadCommunity][currentThreadCommunity] = { | ||||
resolvedThreadInfos[currentThreadCommunity]; | threadInfo: resolvedThreadInfos[currentThreadCommunity], | ||||
rawChatName: threadInfos[currentThreadCommunity].uiName, | |||||
}; | |||||
} | } | ||||
// Handle GENESIS community case: mentioning inside GENESIS should only | // Handle GENESIS community case: mentioning inside GENESIS should only | ||||
// show chats and threads inside the top level that is below GENESIS. | // show chats and threads inside the top level that is below GENESIS. | ||||
if ( | if ( | ||||
resolvedThreadInfos[currentThreadCommunity].type === threadTypes.GENESIS | resolvedThreadInfos[currentThreadCommunity].type === threadTypes.GENESIS | ||||
) { | ) { | ||||
if (visitedGenesisThreads.has(currentThreadID)) { | if (visitedGenesisThreads.has(currentThreadID)) { | ||||
continue; | continue; | ||||
Show All 30 Lines | ) { | ||||
if ( | if ( | ||||
resolvedThreadInfos[lastThreadInTraversePathParentID].type === | resolvedThreadInfos[lastThreadInTraversePathParentID].type === | ||||
threadTypes.GENESIS | threadTypes.GENESIS | ||||
) { | ) { | ||||
if (!result[lastThreadInTraversePath.id]) { | if (!result[lastThreadInTraversePath.id]) { | ||||
result[lastThreadInTraversePath.id] = {}; | result[lastThreadInTraversePath.id] = {}; | ||||
} | } | ||||
for (const threadInfo of threadTraversePath) { | for (const threadInfo of threadTraversePath) { | ||||
result[lastThreadInTraversePath.id][threadInfo.id] = threadInfo; | result[lastThreadInTraversePath.id][threadInfo.id] = { | ||||
threadInfo, | |||||
rawChatName: threadInfos[threadInfo.id].uiName, | |||||
}; | |||||
communityThreadIDForGenesisThreads[threadInfo.id] = | communityThreadIDForGenesisThreads[threadInfo.id] = | ||||
lastThreadInTraversePath.id; | lastThreadInTraversePath.id; | ||||
} | } | ||||
if ( | if ( | ||||
lastThreadInTraversePath.type !== threadTypes.PERSONAL && | lastThreadInTraversePath.type !== threadTypes.PERSONAL && | ||||
lastThreadInTraversePath.type !== threadTypes.PRIVATE | lastThreadInTraversePath.type !== threadTypes.PRIVATE | ||||
) { | ) { | ||||
result[genesis.id][lastThreadInTraversePath.id] = | result[genesis.id][lastThreadInTraversePath.id] = { | ||||
lastThreadInTraversePath; | threadInfo: lastThreadInTraversePath, | ||||
rawChatName: threadInfos[lastThreadInTraversePath.id].uiName, | |||||
}; | |||||
} | } | ||||
} else { | } else { | ||||
if ( | if ( | ||||
!communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID] | !communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID] | ||||
) { | ) { | ||||
result[lastThreadInTraversePathParentID] = {}; | result[lastThreadInTraversePathParentID] = {}; | ||||
communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID] = | communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID] = | ||||
lastThreadInTraversePathParentID; | lastThreadInTraversePathParentID; | ||||
} | } | ||||
const lastThreadInTraversePathParentCommunityThreadID = | const lastThreadInTraversePathParentCommunityThreadID = | ||||
communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID]; | communityThreadIDForGenesisThreads[lastThreadInTraversePathParentID]; | ||||
for (const threadInfo of threadTraversePath) { | for (const threadInfo of threadTraversePath) { | ||||
result[lastThreadInTraversePathParentCommunityThreadID][ | result[lastThreadInTraversePathParentCommunityThreadID][ | ||||
threadInfo.id | threadInfo.id | ||||
] = threadInfo; | ] = { | ||||
threadInfo, | |||||
rawChatName: threadInfos[threadInfo.id].uiName, | |||||
}; | |||||
communityThreadIDForGenesisThreads[threadInfo.id] = | communityThreadIDForGenesisThreads[threadInfo.id] = | ||||
lastThreadInTraversePathParentCommunityThreadID; | lastThreadInTraversePathParentCommunityThreadID; | ||||
} | } | ||||
} | } | ||||
continue; | continue; | ||||
} | } | ||||
result[currentThreadCommunity][currentThreadID] = currentResolvedThreadInfo; | result[currentThreadCommunity][currentThreadID] = { | ||||
threadInfo: currentResolvedThreadInfo, | |||||
rawChatName: threadInfos[currentThreadID].uiName, | |||||
}; | |||||
} | } | ||||
return { | return { | ||||
chatMentionCandidatesObj: result, | chatMentionCandidatesObj: result, | ||||
communityThreadIDForGenesisThreads, | communityThreadIDForGenesisThreads, | ||||
}; | }; | ||||
} | } | ||||
// Without allAtOnce, useChatMentionCandidatesObjAndUtils is very expensive. | // Without allAtOnce, useChatMentionCandidatesObjAndUtils is very expensive. | ||||
// useResolvedThreadInfosObj would trigger its recalculation for each ENS name | // useResolvedThreadInfosObj would trigger its recalculation for each ENS name | ||||
// as it streams in, but we would prefer to trigger its recaculation just once | // as it streams in, but we would prefer to trigger its recaculation just once | ||||
// for every update of the underlying Redux data. | // for every update of the underlying Redux data. | ||||
const useResolvedThreadInfosObjOptions = { allAtOnce: true }; | const useResolvedThreadInfosObjOptions = { allAtOnce: true }; | ||||
function useChatMentionCandidatesObjAndUtils(): { | function useChatMentionCandidatesObjAndUtils(): { | ||||
chatMentionCandidatesObj: ChatMentionCandidatesObj, | chatMentionCandidatesObj: ChatMentionCandidatesObj, | ||||
resolvedThreadInfos: ChatMentionCandidates, | resolvedThreadInfos: { +[id: string]: ResolvedThreadInfo }, | ||||
communityThreadIDForGenesisThreads: { +[id: string]: string }, | communityThreadIDForGenesisThreads: { +[id: string]: string }, | ||||
} { | } { | ||||
const threadInfos = useSelector(threadInfoSelector); | const threadInfos = useSelector(threadInfoSelector); | ||||
const resolvedThreadInfos = useResolvedThreadInfosObj( | const resolvedThreadInfos = useResolvedThreadInfosObj( | ||||
threadInfos, | threadInfos, | ||||
useResolvedThreadInfosObjOptions, | useResolvedThreadInfosObjOptions, | ||||
); | ); | ||||
const { chatMentionCandidatesObj, communityThreadIDForGenesisThreads } = | const { chatMentionCandidatesObj, communityThreadIDForGenesisThreads } = | ||||
Show All 16 Lines | ): { | ||||
return React.useMemo(() => { | return React.useMemo(() => { | ||||
const result: { [string]: SentencePrefixSearchIndex } = {}; | const result: { [string]: SentencePrefixSearchIndex } = {}; | ||||
for (const communityThreadID in chatMentionCandidatesObj) { | for (const communityThreadID in chatMentionCandidatesObj) { | ||||
const searchIndex = new SentencePrefixSearchIndex(); | const searchIndex = new SentencePrefixSearchIndex(); | ||||
const searchIndexEntries = []; | const searchIndexEntries = []; | ||||
for (const threadID in chatMentionCandidatesObj[communityThreadID]) { | for (const threadID in chatMentionCandidatesObj[communityThreadID]) { | ||||
searchIndexEntries.push({ | searchIndexEntries.push({ | ||||
id: threadID, | id: threadID, | ||||
uiName: chatMentionCandidatesObj[communityThreadID][threadID].uiName, | uiName: | ||||
chatMentionCandidatesObj[communityThreadID][threadID].threadInfo | |||||
.uiName, | |||||
}); | }); | ||||
} | } | ||||
// Sort the keys so that the order of the search result is consistent | // Sort the keys so that the order of the search result is consistent | ||||
searchIndexEntries.sort(({ uiName: uiNameA }, { uiName: uiNameB }) => | searchIndexEntries.sort(({ uiName: uiNameA }, { uiName: uiNameB }) => | ||||
uiNameA.localeCompare(uiNameB), | uiNameA.localeCompare(uiNameB), | ||||
); | ); | ||||
for (const { id, uiName } of searchIndexEntries) { | for (const { id, uiName } of searchIndexEntries) { | ||||
searchIndex.addEntry(id, uiName); | searchIndex.addEntry(id, uiName); | ||||
} | } | ||||
result[communityThreadID] = searchIndex; | result[communityThreadID] = searchIndex; | ||||
} | } | ||||
return result; | return result; | ||||
}, [chatMentionCandidatesObj]); | }, [chatMentionCandidatesObj]); | ||||
} | } | ||||
export { ChatMentionContextProvider, ChatMentionContext }; | export { ChatMentionContextProvider, ChatMentionContext }; |